Compare commits
15 Commits
v0.25.5
...
kurt-zoom-
Author | SHA1 | Date | |
---|---|---|---|
252fde9be6 | |||
cd91774881 | |||
88cd27425e | |||
a284a270b7 | |||
e2f5ad47a2 | |||
2a3693651a | |||
9ca49c6366 | |||
2a2e4a8b63 | |||
258bce8adc | |||
a3c0a2b03b | |||
2ed2e9cf86 | |||
438d1ec746 | |||
5112b48324 | |||
8cb17a8936 | |||
125207f60c |
68
.github/workflows/build-test-publish-apps.yml
vendored
@ -51,8 +51,6 @@ jobs:
|
||||
run: |
|
||||
VERSION=$(date +'%-y.%-m.%-d') yarn bump-jsons
|
||||
|
||||
# TODO: see if we need to inject updater nightly URL here https://dl.zoo.dev/releases/modeling-app/nightly/last_update.json
|
||||
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: prepared-files
|
||||
@ -63,12 +61,25 @@ jobs:
|
||||
- id: export_version
|
||||
run: echo "version=`cat package.json | jq -r '.version'`" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Prepare electron-builder.yml file for nightly
|
||||
if: ${{ github.event_name == 'schedule' }}
|
||||
run: |
|
||||
yq -i '.publish[0].url = "https://dl.zoo.dev/releases/modeling-app/nightly"' electron-builder.yml
|
||||
|
||||
- uses: actions/upload-artifact@v3
|
||||
if: ${{ github.event_name == 'schedule' }}
|
||||
with:
|
||||
name: prepared-files-nightly
|
||||
path: |
|
||||
electron-builder.yml
|
||||
|
||||
- name: Prepare electron-builder.yml file for updater test
|
||||
if: ${{ env.CUT_RELEASE_PR == 'true' }}
|
||||
run: |
|
||||
yq -i '.publish[0].url = "https://dl.zoo.dev/releases/modeling-app/updater-test"' electron-builder.yml
|
||||
|
||||
- uses: actions/upload-artifact@v3
|
||||
if: ${{ env.CUT_RELEASE_PR == 'true' }}
|
||||
with:
|
||||
name: prepared-files-updater-test
|
||||
path: |
|
||||
@ -108,6 +119,16 @@ jobs:
|
||||
mkdir src/wasm-lib/pkg
|
||||
cp prepared-files/src/wasm-lib/pkg/wasm_lib* src/wasm-lib/pkg
|
||||
|
||||
- uses: actions/download-artifact@v3
|
||||
if: ${{ github.event_name == 'schedule' }}
|
||||
name: prepared-files-nightly
|
||||
|
||||
- name: Copy updated electron-builder.yml file for nightly build
|
||||
if: ${{ github.event_name == 'schedule' }}
|
||||
run: |
|
||||
ls -R prepared-files-nightly
|
||||
cp prepared-files-nightly/electron-builder.yml electron-builder.yml
|
||||
|
||||
- name: Sync node version and setup cache
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
@ -152,11 +173,17 @@ jobs:
|
||||
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: out-${{ matrix.os }}
|
||||
name: out-arm64-${{ matrix.os }}
|
||||
path: |
|
||||
out/Zoo*.*
|
||||
out/Zoo*arm64*.*
|
||||
out/latest*.yml
|
||||
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: out-x64-${{ matrix.os }}
|
||||
path: |
|
||||
out/Zoo*x*64*.*
|
||||
|
||||
# TODO: add the 'Build for Mac TestFlight (nightly)' stage back
|
||||
|
||||
- uses: actions/download-artifact@v3
|
||||
@ -176,10 +203,16 @@ jobs:
|
||||
- uses: actions/upload-artifact@v3
|
||||
if: ${{ env.CUT_RELEASE_PR == 'true' }}
|
||||
with:
|
||||
name: updater-test-${{ matrix.os }}
|
||||
name: updater-test-arm64-${{ matrix.os }}
|
||||
path: |
|
||||
out/Zoo*.*
|
||||
out/latest*.yml
|
||||
out/Zoo*arm64*.*
|
||||
|
||||
- uses: actions/upload-artifact@v3
|
||||
if: ${{ env.CUT_RELEASE_PR == 'true' }}
|
||||
with:
|
||||
name: updater-test-x64-${{ matrix.os }}
|
||||
path: |
|
||||
out/Zoo*x64*.*
|
||||
|
||||
|
||||
publish-apps-release:
|
||||
@ -201,17 +234,32 @@ jobs:
|
||||
|
||||
- uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: out-windows-2022
|
||||
name: out-arm64-windows-2022
|
||||
path: out
|
||||
|
||||
- uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: out-macos-14
|
||||
name: out-x64-windows-2022
|
||||
path: out
|
||||
|
||||
- uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: out-ubuntu-22.04
|
||||
name: out-arm64-macos-14
|
||||
path: out
|
||||
|
||||
- uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: out-x64-macos-14
|
||||
path: out
|
||||
|
||||
- uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: out-arm64-ubuntu-22.04
|
||||
path: out
|
||||
|
||||
- uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: out-x64-ubuntu-22.04
|
||||
path: out
|
||||
|
||||
- name: Generate the download static endpoint
|
||||
|
@ -19,7 +19,6 @@ layout: manual
|
||||
* [`angledLineToX`](kcl/angledLineToX)
|
||||
* [`angledLineToY`](kcl/angledLineToY)
|
||||
* [`arc`](kcl/arc)
|
||||
* [`arrayReduce`](kcl/arrayReduce)
|
||||
* [`asin`](kcl/asin)
|
||||
* [`assert`](kcl/assert)
|
||||
* [`assertEqual`](kcl/assertEqual)
|
||||
@ -62,6 +61,7 @@ layout: manual
|
||||
* [`log10`](kcl/log10)
|
||||
* [`log2`](kcl/log2)
|
||||
* [`m`](kcl/m)
|
||||
* [`map`](kcl/map)
|
||||
* [`max`](kcl/max)
|
||||
* [`min`](kcl/min)
|
||||
* [`mirror2d`](kcl/mirror2d)
|
||||
@ -78,6 +78,7 @@ layout: manual
|
||||
* [`profileStart`](kcl/profileStart)
|
||||
* [`profileStartX`](kcl/profileStartX)
|
||||
* [`profileStartY`](kcl/profileStartY)
|
||||
* [`reduce`](kcl/reduce)
|
||||
* [`rem`](kcl/rem)
|
||||
* [`revolve`](kcl/revolve)
|
||||
* [`segAng`](kcl/segAng)
|
||||
|
56
docs/kcl/map.md
Normal file
47
docs/kcl/reduce.md
Normal file
18452
docs/kcl/std.json
89
docs/kcl/types/BinaryOperator.md
Normal file
@ -0,0 +1,89 @@
|
||||
---
|
||||
title: "BinaryOperator"
|
||||
excerpt: ""
|
||||
layout: manual
|
||||
---
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
**This schema accepts exactly one of the following:**
|
||||
|
||||
Add two numbers.
|
||||
|
||||
**enum:** `+`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
----
|
||||
Subtract two numbers.
|
||||
|
||||
**enum:** `-`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
----
|
||||
Multiply two numbers.
|
||||
|
||||
**enum:** `*`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
----
|
||||
Divide two numbers.
|
||||
|
||||
**enum:** `/`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
----
|
||||
Modulo two numbers.
|
||||
|
||||
**enum:** `%`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
----
|
||||
Raise a number to a power.
|
||||
|
||||
**enum:** `^`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
----
|
||||
|
||||
|
||||
|
||||
|
161
docs/kcl/types/BinaryPart.md
Normal file
@ -0,0 +1,161 @@
|
||||
---
|
||||
title: "BinaryPart"
|
||||
excerpt: ""
|
||||
layout: manual
|
||||
---
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
**This schema accepts exactly one of the following:**
|
||||
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `Literal`| | No |
|
||||
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | 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 |
|
||||
|
||||
|
||||
----
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: [`Identifier`](/docs/kcl/types/Identifier)| | No |
|
||||
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | 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 |
|
||||
|
||||
|
||||
----
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `BinaryExpression`| | No |
|
||||
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | 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 |
|
||||
|
||||
|
||||
----
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `CallExpression`| | No |
|
||||
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | 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 |
|
||||
|
||||
|
||||
----
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `UnaryExpression`| | No |
|
||||
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | 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 |
|
||||
|
||||
|
||||
----
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `MemberExpression`| | No |
|
||||
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | 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 |
|
||||
|
||||
|
||||
----
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `IfExpression`| | No |
|
||||
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | 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 |
|
||||
|
||||
|
||||
----
|
||||
|
||||
|
||||
|
||||
|
75
docs/kcl/types/BodyItem.md
Normal file
@ -0,0 +1,75 @@
|
||||
---
|
||||
title: "BodyItem"
|
||||
excerpt: ""
|
||||
layout: manual
|
||||
---
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
**This schema accepts exactly one of the following:**
|
||||
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `ExpressionStatement`| | No |
|
||||
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | 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 |
|
||||
|
||||
|
||||
----
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `VariableDeclaration`| | No |
|
||||
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `declarations` |`[` [`VariableDeclarator`](/docs/kcl/types/VariableDeclarator) `]`| | 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 |
|
||||
|
||||
|
||||
----
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `ReturnStatement`| | No |
|
||||
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | 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 |
|
||||
|
||||
|
||||
----
|
||||
|
||||
|
||||
|
||||
|
41
docs/kcl/types/CommentStyle.md
Normal file
@ -0,0 +1,41 @@
|
||||
---
|
||||
title: "CommentStyle"
|
||||
excerpt: ""
|
||||
layout: manual
|
||||
---
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
**This schema accepts exactly one of the following:**
|
||||
|
||||
Like // foo
|
||||
|
||||
**enum:** `line`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
----
|
||||
Like /* foo */
|
||||
|
||||
**enum:** `block`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
----
|
||||
|
||||
|
||||
|
||||
|
24
docs/kcl/types/ElseIf.md
Normal file
@ -0,0 +1,24 @@
|
||||
---
|
||||
title: "ElseIf"
|
||||
excerpt: ""
|
||||
layout: manual
|
||||
---
|
||||
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `cond` |[`Expr`](/docs/kcl/types/Expr)| | No |
|
||||
| `then_val` |[`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 |
|
||||
|
||||
|
21
docs/kcl/types/Environment.md
Normal file
@ -0,0 +1,21 @@
|
||||
---
|
||||
title: "Environment"
|
||||
excerpt: ""
|
||||
layout: manual
|
||||
---
|
||||
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `bindings` |`object`| | No |
|
||||
| `parent` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
|
||||
|
16
docs/kcl/types/EnvironmentRef.md
Normal file
@ -0,0 +1,16 @@
|
||||
---
|
||||
title: "EnvironmentRef"
|
||||
excerpt: "An index pointing to an environment."
|
||||
layout: manual
|
||||
---
|
||||
|
||||
An index pointing to an environment.
|
||||
|
||||
**Type:** `integer` (`uint`)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
297
docs/kcl/types/Expr.md
Normal file
@ -0,0 +1,297 @@
|
||||
---
|
||||
title: "Expr"
|
||||
excerpt: "An expression can be evaluated to yield a single KCL value."
|
||||
layout: manual
|
||||
---
|
||||
|
||||
An expression can be evaluated to yield a single KCL value.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
**This schema accepts exactly one of the following:**
|
||||
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `Literal`| | No |
|
||||
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | 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 |
|
||||
|
||||
|
||||
----
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: [`Identifier`](/docs/kcl/types/Identifier)| | No |
|
||||
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | 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 |
|
||||
|
||||
|
||||
----
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: [`TagDeclarator`](/docs/kcl/types#tag-declaration)| | No |
|
||||
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | 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 |
|
||||
|
||||
|
||||
----
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `BinaryExpression`| | No |
|
||||
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | 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 |
|
||||
|
||||
|
||||
----
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: [`FunctionExpression`](/docs/kcl/types/FunctionExpression)| | No |
|
||||
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | 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 |
|
||||
|
||||
|
||||
----
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `CallExpression`| | No |
|
||||
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | 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 |
|
||||
|
||||
|
||||
----
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `PipeExpression`| | No |
|
||||
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | 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 |
|
||||
|
||||
|
||||
----
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `PipeSubstitution`| | No |
|
||||
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | 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 |
|
||||
|
||||
|
||||
----
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `ArrayExpression`| | No |
|
||||
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | 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 |
|
||||
|
||||
|
||||
----
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `ObjectExpression`| | No |
|
||||
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | 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 |
|
||||
|
||||
|
||||
----
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `MemberExpression`| | No |
|
||||
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | 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 |
|
||||
|
||||
|
||||
----
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `UnaryExpression`| | No |
|
||||
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | 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 |
|
||||
|
||||
|
||||
----
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `IfExpression`| | No |
|
||||
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | 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 |
|
||||
|
||||
|
||||
----
|
||||
KCL value for an optional parameter which was not given an argument. (remember, parameters are in the function declaration, arguments are in the function call/application).
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `None`| | No |
|
||||
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
|
||||
|
||||
----
|
||||
|
||||
|
||||
|
||||
|
24
docs/kcl/types/FunctionExpression.md
Normal file
@ -0,0 +1,24 @@
|
||||
---
|
||||
title: "FunctionExpression"
|
||||
excerpt: ""
|
||||
layout: manual
|
||||
---
|
||||
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `params` |`[` [`Parameter`](/docs/kcl/types/Parameter) `]`| | No |
|
||||
| `body` |[`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 |
|
||||
|
||||
|
23
docs/kcl/types/Identifier.md
Normal file
@ -0,0 +1,23 @@
|
||||
---
|
||||
title: "Identifier"
|
||||
excerpt: ""
|
||||
layout: manual
|
||||
---
|
||||
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | 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 |
|
||||
|
||||
|
195
docs/kcl/types/KclValue.md
Normal file
@ -0,0 +1,195 @@
|
||||
---
|
||||
title: "KclValue"
|
||||
excerpt: "A memory item."
|
||||
layout: manual
|
||||
---
|
||||
|
||||
A memory item.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
**This schema accepts exactly one of the following:**
|
||||
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `UserVal`| | No |
|
||||
| `value` |``| | No |
|
||||
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
|
||||
|
||||
|
||||
----
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: [`TagIdentifier`](/docs/kcl/types#tag-identifier)| | No |
|
||||
| `value` |`string`| | No |
|
||||
| `info` |[`TagEngineInfo`](/docs/kcl/types/TagEngineInfo)| | No |
|
||||
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
|
||||
|
||||
|
||||
----
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: [`TagDeclarator`](/docs/kcl/types#tag-declaration)| | No |
|
||||
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | 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 |
|
||||
|
||||
|
||||
----
|
||||
A plane.
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `Plane`| | No |
|
||||
| `id` |`string`| The id of the plane. | No |
|
||||
| `value` |[`PlaneType`](/docs/kcl/types/PlaneType)| A memory item. | No |
|
||||
| `origin` |[`Point3d`](/docs/kcl/types/Point3d)| Origin of the plane. | No |
|
||||
| `xAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the plane’s X axis be? | No |
|
||||
| `yAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the plane’s Y axis be? | No |
|
||||
| `zAxis` |[`Point3d`](/docs/kcl/types/Point3d)| The z-axis (normal). | No |
|
||||
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
|
||||
|
||||
|
||||
----
|
||||
A face.
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `Face`| | No |
|
||||
| `id` |`string`| The id of the face. | No |
|
||||
| `value` |`string`| The tag of the face. | No |
|
||||
| `xAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the face’s X axis be? | No |
|
||||
| `yAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the face’s Y axis be? | No |
|
||||
| `zAxis` |[`Point3d`](/docs/kcl/types/Point3d)| The z-axis (normal). | No |
|
||||
| `solid` |[`Solid`](/docs/kcl/types/Solid)| The solid the face is on. | No |
|
||||
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
|
||||
|
||||
|
||||
----
|
||||
An solid is a collection of extrude surfaces.
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: [`Solid`](/docs/kcl/types/Solid)| | No |
|
||||
| `id` |`string`| The id of the solid. | No |
|
||||
| `value` |`[` [`ExtrudeSurface`](/docs/kcl/types/ExtrudeSurface) `]`| The extrude surfaces. | No |
|
||||
| `sketch` |[`Sketch`](/docs/kcl/types/Sketch)| The sketch. | No |
|
||||
| `height` |`number`| The height of the solid. | No |
|
||||
| `startCapId` |`string`| The id of the extrusion start cap | No |
|
||||
| `endCapId` |`string`| The id of the extrusion end cap | No |
|
||||
| `edgeCuts` |`[` [`EdgeCut`](/docs/kcl/types/EdgeCut) `]`| Chamfers or fillets on this solid. | No |
|
||||
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| Metadata. | No |
|
||||
|
||||
|
||||
----
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `Solids`| | No |
|
||||
| `value` |`[` [`Solid`](/docs/kcl/types/Solid) `]`| | No |
|
||||
|
||||
|
||||
----
|
||||
Data for an imported geometry.
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: [`ImportedGeometry`](/docs/kcl/types/ImportedGeometry)| | No |
|
||||
| `id` |`string`| The ID of the imported geometry. | No |
|
||||
| `value` |`[` `string` `]`| The original file paths. | No |
|
||||
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
|
||||
|
||||
|
||||
----
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `Function`| | No |
|
||||
| `expression` |[`FunctionExpression`](/docs/kcl/types/FunctionExpression)| A memory item. | No |
|
||||
| `memory` |[`ProgramMemory`](/docs/kcl/types/ProgramMemory)| A memory item. | No |
|
||||
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
|
||||
|
||||
|
||||
----
|
||||
|
||||
|
||||
|
||||
|
56
docs/kcl/types/LiteralIdentifier.md
Normal file
@ -0,0 +1,56 @@
|
||||
---
|
||||
title: "LiteralIdentifier"
|
||||
excerpt: ""
|
||||
layout: manual
|
||||
---
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
**This schema accepts exactly one of the following:**
|
||||
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: [`Identifier`](/docs/kcl/types/Identifier)| | No |
|
||||
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | 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 |
|
||||
|
||||
|
||||
----
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `Literal`| | No |
|
||||
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | 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 |
|
||||
|
||||
|
||||
----
|
||||
|
||||
|
||||
|
||||
|
57
docs/kcl/types/LiteralValue.md
Normal file
@ -0,0 +1,57 @@
|
||||
---
|
||||
title: "LiteralValue"
|
||||
excerpt: ""
|
||||
layout: manual
|
||||
---
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
**This schema accepts any of the following:**
|
||||
|
||||
|
||||
**Type:** `integer` (`int64`)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
----
|
||||
|
||||
**Type:** `number` (`double`)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
----
|
||||
|
||||
**Type:** `string`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
----
|
||||
|
||||
**Type:** `boolean`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
----
|
||||
|
||||
|
||||
|
||||
|
||||
|
57
docs/kcl/types/MemberObject.md
Normal file
@ -0,0 +1,57 @@
|
||||
---
|
||||
title: "MemberObject"
|
||||
excerpt: ""
|
||||
layout: manual
|
||||
---
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
**This schema accepts exactly one of the following:**
|
||||
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `MemberExpression`| | No |
|
||||
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | 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 |
|
||||
|
||||
|
||||
----
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: [`Identifier`](/docs/kcl/types/Identifier)| | No |
|
||||
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | 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 |
|
||||
|
||||
|
||||
----
|
||||
|
||||
|
||||
|
||||
|
22
docs/kcl/types/NonCodeMeta.md
Normal file
@ -0,0 +1,22 @@
|
||||
---
|
||||
title: "NonCodeMeta"
|
||||
excerpt: ""
|
||||
layout: manual
|
||||
---
|
||||
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `nonCodeNodes` |`object`| | No |
|
||||
| `start` |`[` [`NonCodeNode`](/docs/kcl/types/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 |
|
||||
|
||||
|
23
docs/kcl/types/NonCodeNode.md
Normal file
@ -0,0 +1,23 @@
|
||||
---
|
||||
title: "NonCodeNode"
|
||||
excerpt: ""
|
||||
layout: manual
|
||||
---
|
||||
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `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 |
|
||||
|
||||
|
103
docs/kcl/types/NonCodeValue.md
Normal file
@ -0,0 +1,103 @@
|
||||
---
|
||||
title: "NonCodeValue"
|
||||
excerpt: ""
|
||||
layout: manual
|
||||
---
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
**This schema accepts exactly one of the following:**
|
||||
|
||||
A shebang. This is a special type of comment that is at the top of the file. It looks like this: ```python,no_run #!/usr/bin/env python ```
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `shebang`| | No |
|
||||
| `value` |`string`| | No |
|
||||
|
||||
|
||||
----
|
||||
An inline comment. Here are examples: `1 + 1 // This is an inline comment`. `1 + 1 /* Here's another */`.
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `inlineComment`| | No |
|
||||
| `value` |`string`| | No |
|
||||
| `style` |[`CommentStyle`](/docs/kcl/types/CommentStyle)| | No |
|
||||
|
||||
|
||||
----
|
||||
A block comment. An example of this is the following: ```python,no_run /* This is a block comment */ 1 + 1 ``` Now this is important. The block comment is attached to the next line. This is always the case. Also the block comment doesn't have a new line above it. If it did it would be a `NewLineBlockComment`.
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `blockComment`| | No |
|
||||
| `value` |`string`| | No |
|
||||
| `style` |[`CommentStyle`](/docs/kcl/types/CommentStyle)| | No |
|
||||
|
||||
|
||||
----
|
||||
A block comment that has a new line above it. The user explicitly added a new line above the block comment.
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `newLineBlockComment`| | No |
|
||||
| `value` |`string`| | No |
|
||||
| `style` |[`CommentStyle`](/docs/kcl/types/CommentStyle)| | No |
|
||||
|
||||
|
||||
----
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `newLine`| | No |
|
||||
|
||||
|
||||
----
|
||||
|
||||
|
||||
|
||||
|
24
docs/kcl/types/ObjectProperty.md
Normal file
@ -0,0 +1,24 @@
|
||||
---
|
||||
title: "ObjectProperty"
|
||||
excerpt: ""
|
||||
layout: manual
|
||||
---
|
||||
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `key` |[`Identifier`](/docs/kcl/types/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 |
|
||||
|
||||
|
23
docs/kcl/types/Parameter.md
Normal file
@ -0,0 +1,23 @@
|
||||
---
|
||||
title: "Parameter"
|
||||
excerpt: "Parameter of a KCL function."
|
||||
layout: manual
|
||||
---
|
||||
|
||||
Parameter of a KCL function.
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `identifier` |[`Identifier`](/docs/kcl/types/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 |
|
||||
|
||||
|
25
docs/kcl/types/Program.md
Normal file
@ -0,0 +1,25 @@
|
||||
---
|
||||
title: "Program"
|
||||
excerpt: "A KCL program top level, or function body."
|
||||
layout: manual
|
||||
---
|
||||
|
||||
A KCL program top level, or function body.
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `body` |`[` [`BodyItem`](/docs/kcl/types/BodyItem) `]`| | No |
|
||||
| `nonCodeMeta` |[`NonCodeMeta`](/docs/kcl/types/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 |
|
||||
|
||||
|
22
docs/kcl/types/ProgramMemory.md
Normal file
@ -0,0 +1,22 @@
|
||||
---
|
||||
title: "ProgramMemory"
|
||||
excerpt: ""
|
||||
layout: manual
|
||||
---
|
||||
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `environments` |`[` [`Environment`](/docs/kcl/types/Environment) `]`| | No |
|
||||
| `currentEnv` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `return` |[`KclValue`](/docs/kcl/types/KclValue)| | No |
|
||||
|
||||
|
41
docs/kcl/types/UnaryOperator.md
Normal file
@ -0,0 +1,41 @@
|
||||
---
|
||||
title: "UnaryOperator"
|
||||
excerpt: ""
|
||||
layout: manual
|
||||
---
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
**This schema accepts exactly one of the following:**
|
||||
|
||||
Negate a number.
|
||||
|
||||
**enum:** `-`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
----
|
||||
Negate a boolean.
|
||||
|
||||
**enum:** `!`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
----
|
||||
|
||||
|
||||
|
||||
|
24
docs/kcl/types/VariableDeclarator.md
Normal file
@ -0,0 +1,24 @@
|
||||
---
|
||||
title: "VariableDeclarator"
|
||||
excerpt: ""
|
||||
layout: manual
|
||||
---
|
||||
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| 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 |
|
||||
|
||||
|
65
docs/kcl/types/VariableKind.md
Normal file
@ -0,0 +1,65 @@
|
||||
---
|
||||
title: "VariableKind"
|
||||
excerpt: ""
|
||||
layout: manual
|
||||
---
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
**This schema accepts exactly one of the following:**
|
||||
|
||||
Declare a variable.
|
||||
|
||||
**enum:** `let`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
----
|
||||
Declare a variable that is read-only.
|
||||
|
||||
**enum:** `const`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
----
|
||||
Declare a function.
|
||||
|
||||
**enum:** `fn`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
----
|
||||
Declare a variable.
|
||||
|
||||
**enum:** `var`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
----
|
||||
|
||||
|
||||
|
||||
|
@ -155,15 +155,16 @@ test(
|
||||
// Find the toast.
|
||||
// Look out for the toast message
|
||||
await expect(exportingToastMessage).toBeVisible()
|
||||
await expect(alreadyExportingToastMessage).not.toBeVisible()
|
||||
|
||||
// Expect it to succeed.
|
||||
await expect(errorToastMessage).not.toBeVisible()
|
||||
await expect(engineErrorToastMessage).not.toBeVisible()
|
||||
|
||||
const successToastMessage = page.getByText(`Exported successfully`)
|
||||
await expect(successToastMessage).toBeVisible()
|
||||
await expect(exportingToastMessage).not.toBeVisible()
|
||||
await test.step('Check the success toast message shows and nothing else', async () =>
|
||||
Promise.all([
|
||||
expect(alreadyExportingToastMessage).not.toBeVisible(),
|
||||
expect(errorToastMessage).not.toBeVisible(),
|
||||
expect(engineErrorToastMessage).not.toBeVisible(),
|
||||
expect(successToastMessage).toBeVisible(),
|
||||
expect(exportingToastMessage).not.toBeVisible(),
|
||||
]))
|
||||
|
||||
await test.step('Check the export size', async () => {
|
||||
await expect
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { test, expect } from '@playwright/test'
|
||||
import { _test, _expect } from './playwright-deprecated'
|
||||
import { test, expect } from './fixtures/fixtureSetup'
|
||||
import * as fsp from 'fs/promises'
|
||||
import * as fs from 'fs'
|
||||
import {
|
||||
@ -11,14 +12,98 @@ import {
|
||||
import { join } from 'path'
|
||||
import { FILE_EXT } from 'lib/constants'
|
||||
|
||||
test.beforeEach(async ({ context, page }, testInfo) => {
|
||||
_test.beforeEach(async ({ context, page }, testInfo) => {
|
||||
await setup(context, page, testInfo)
|
||||
})
|
||||
|
||||
test.afterEach(async ({ page }, testInfo) => {
|
||||
_test.afterEach(async ({ page }, testInfo) => {
|
||||
await tearDown(page, testInfo)
|
||||
})
|
||||
|
||||
test.describe('integrations tests', () => {
|
||||
test(
|
||||
'Creating a new file or switching file while in sketchMode should exit sketchMode',
|
||||
{ tag: '@electron' },
|
||||
async ({ tronApp, homePage, scene, editor, toolbar }) => {
|
||||
test.skip(
|
||||
process.platform === 'win32',
|
||||
'windows times out will waiting for the execution indicator?'
|
||||
)
|
||||
await tronApp.initialise({
|
||||
fixtures: { homePage, scene, editor, toolbar },
|
||||
folderSetupFn: async (dir) => {
|
||||
const bracketDir = join(dir, 'test-sample')
|
||||
await fsp.mkdir(bracketDir, { recursive: true })
|
||||
await fsp.copyFile(
|
||||
executorInputPath('e2e-can-sketch-on-chamfer.kcl'),
|
||||
join(bracketDir, 'main.kcl')
|
||||
)
|
||||
},
|
||||
})
|
||||
const [clickObj] = await scene.makeMouseHelpers(600, 300)
|
||||
|
||||
await test.step('setup test', async () => {
|
||||
await homePage.expectState({
|
||||
projectCards: [
|
||||
{
|
||||
title: 'test-sample',
|
||||
fileCount: 1,
|
||||
folderCount: 1,
|
||||
},
|
||||
],
|
||||
sortBy: 'last-modified-desc',
|
||||
})
|
||||
await homePage.openProject('test-sample')
|
||||
// windows times out here, hence the skip above
|
||||
await scene.waitForExecutionDone()
|
||||
})
|
||||
await test.step('enter sketch mode', async () => {
|
||||
await clickObj()
|
||||
await scene.moveNoWhere()
|
||||
await editor.expectState({
|
||||
activeLines: [
|
||||
'|>startProfileAt([75.8,317.2],%)//[$startCapTag,$EndCapTag]',
|
||||
],
|
||||
highlightedCode: '',
|
||||
diagnostics: [],
|
||||
})
|
||||
await toolbar.editSketch()
|
||||
await expect(toolbar.exitSketchBtn).toBeVisible()
|
||||
})
|
||||
await test.step('check sketch mode is exited when creating new file', async () => {
|
||||
await toolbar.fileTreeBtn.click()
|
||||
await toolbar.expectFileTreeState(['main.kcl'])
|
||||
await toolbar.createFile({ wait: true })
|
||||
|
||||
// check we're out of sketch mode
|
||||
await expect(toolbar.exitSketchBtn).not.toBeVisible()
|
||||
await expect(toolbar.startSketchBtn).toBeVisible()
|
||||
})
|
||||
await test.step('setup for next assertion', async () => {
|
||||
await toolbar.openFile('main.kcl')
|
||||
await clickObj()
|
||||
await scene.moveNoWhere()
|
||||
await editor.expectState({
|
||||
activeLines: [
|
||||
'|>startProfileAt([75.8,317.2],%)//[$startCapTag,$EndCapTag]',
|
||||
],
|
||||
highlightedCode: '',
|
||||
diagnostics: [],
|
||||
})
|
||||
await toolbar.editSketch()
|
||||
await expect(toolbar.exitSketchBtn).toBeVisible()
|
||||
await toolbar.expectFileTreeState(['main.kcl', 'Untitled.kcl'])
|
||||
})
|
||||
await test.step('check sketch mode is exited when opening a different file', async () => {
|
||||
await toolbar.openFile('untitled.kcl', { wait: false })
|
||||
|
||||
// check we're out of sketch mode
|
||||
await expect(toolbar.exitSketchBtn).not.toBeVisible()
|
||||
await expect(toolbar.startSketchBtn).toBeVisible()
|
||||
})
|
||||
}
|
||||
)
|
||||
})
|
||||
test.describe('when using the file tree to', () => {
|
||||
const fromFile = 'main.kcl'
|
||||
const toFile = 'hello.kcl'
|
||||
@ -26,11 +111,8 @@ test.describe('when using the file tree to', () => {
|
||||
test(
|
||||
`rename ${fromFile} to ${toFile}, and doesn't crash on reload and settings load`,
|
||||
{ tag: '@electron' },
|
||||
async ({ browser: _ }, testInfo) => {
|
||||
const { electronApp, page } = await setupElectron({
|
||||
testInfo,
|
||||
folderSetupFn: async () => {},
|
||||
})
|
||||
async ({ browser: _, tronApp }, testInfo) => {
|
||||
await tronApp.initialise()
|
||||
|
||||
const {
|
||||
panesOpen,
|
||||
@ -38,10 +120,10 @@ test.describe('when using the file tree to', () => {
|
||||
pasteCodeInEditor,
|
||||
renameFile,
|
||||
editorTextMatches,
|
||||
} = await getUtils(page, test)
|
||||
} = await getUtils(tronApp.page, test)
|
||||
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
page.on('console', console.log)
|
||||
await tronApp.page.setViewportSize({ width: 1200, height: 500 })
|
||||
tronApp.page.on('console', console.log)
|
||||
|
||||
await panesOpen(['files', 'code'])
|
||||
|
||||
@ -55,39 +137,38 @@ test.describe('when using the file tree to', () => {
|
||||
await pasteCodeInEditor(kclCube)
|
||||
|
||||
await renameFile(fromFile, toFile)
|
||||
await page.reload()
|
||||
await tronApp.page.reload()
|
||||
|
||||
await test.step('Postcondition: editor has same content as before the rename', async () => {
|
||||
await editorTextMatches(kclCube)
|
||||
})
|
||||
|
||||
await test.step('Postcondition: opening and closing settings works', async () => {
|
||||
const settingsOpenButton = page.getByRole('link', {
|
||||
const settingsOpenButton = tronApp.page.getByRole('link', {
|
||||
name: 'settings Settings',
|
||||
})
|
||||
const settingsCloseButton = page.getByTestId('settings-close-button')
|
||||
const settingsCloseButton = tronApp.page.getByTestId(
|
||||
'settings-close-button'
|
||||
)
|
||||
await settingsOpenButton.click()
|
||||
await settingsCloseButton.click()
|
||||
})
|
||||
|
||||
await electronApp.close()
|
||||
await tronApp.close()
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
`create many new untitled files they increment their names`,
|
||||
{ tag: '@electron' },
|
||||
async ({ browser: _ }, testInfo) => {
|
||||
const { electronApp, page } = await setupElectron({
|
||||
testInfo,
|
||||
folderSetupFn: async () => {},
|
||||
})
|
||||
async ({ browser: _, tronApp }, testInfo) => {
|
||||
await tronApp.initialise()
|
||||
|
||||
const { panesOpen, createAndSelectProject, createNewFile } =
|
||||
await getUtils(page, test)
|
||||
await getUtils(tronApp.page, test)
|
||||
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
page.on('console', console.log)
|
||||
await tronApp.page.setViewportSize({ width: 1200, height: 500 })
|
||||
tronApp.page.on('console', console.log)
|
||||
|
||||
await panesOpen(['files'])
|
||||
|
||||
@ -101,23 +182,21 @@ test.describe('when using the file tree to', () => {
|
||||
|
||||
await test.step('Postcondition: there are 5 new Untitled-*.kcl files', async () => {
|
||||
await expect(
|
||||
page
|
||||
tronApp.page
|
||||
.locator('[data-testid="file-pane-scroll-container"] button')
|
||||
.filter({ hasText: /Untitled[-]?[0-5]?/ })
|
||||
).toHaveCount(5)
|
||||
})
|
||||
|
||||
await electronApp.close()
|
||||
await tronApp.close()
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
'create a new file with the same name as an existing file cancels the operation',
|
||||
{ tag: '@electron' },
|
||||
async ({ browser: _ }, testInfo) => {
|
||||
const { electronApp, page } = await setupElectron({
|
||||
testInfo,
|
||||
})
|
||||
async ({ browser: _, tronApp }, testInfo) => {
|
||||
await tronApp.initialise()
|
||||
|
||||
const {
|
||||
openKclCodePanel,
|
||||
@ -128,10 +207,10 @@ test.describe('when using the file tree to', () => {
|
||||
renameFile,
|
||||
selectFile,
|
||||
editorTextMatches,
|
||||
} = await getUtils(page, test)
|
||||
} = await getUtils(tronApp.page, _test)
|
||||
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
page.on('console', console.log)
|
||||
await tronApp.page.setViewportSize({ width: 1200, height: 500 })
|
||||
tronApp.page.on('console', console.log)
|
||||
|
||||
await createAndSelectProject('project-000')
|
||||
await openKclCodePanel()
|
||||
@ -159,25 +238,22 @@ test.describe('when using the file tree to', () => {
|
||||
await selectFile(kcl1)
|
||||
await editorTextMatches(kclCube)
|
||||
})
|
||||
await page.waitForTimeout(500)
|
||||
await tronApp.page.waitForTimeout(500)
|
||||
|
||||
await test.step(`Postcondition: ${kcl2} still exists with the original content`, async () => {
|
||||
await selectFile(kcl2)
|
||||
await editorTextMatches(kclCylinder)
|
||||
})
|
||||
|
||||
await electronApp.close()
|
||||
await tronApp?.close?.()
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
'deleting all files recreates a default main.kcl with no code',
|
||||
{ tag: '@electron' },
|
||||
async ({ browser: _ }, testInfo) => {
|
||||
const { electronApp, page } = await setupElectron({
|
||||
testInfo,
|
||||
folderSetupFn: async () => {},
|
||||
})
|
||||
async ({ browser: _, tronApp }, testInfo) => {
|
||||
await tronApp.initialise()
|
||||
|
||||
const {
|
||||
panesOpen,
|
||||
@ -185,10 +261,10 @@ test.describe('when using the file tree to', () => {
|
||||
pasteCodeInEditor,
|
||||
deleteFile,
|
||||
editorTextMatches,
|
||||
} = await getUtils(page, test)
|
||||
} = await getUtils(tronApp.page, _test)
|
||||
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
page.on('console', console.log)
|
||||
await tronApp.page.setViewportSize({ width: 1200, height: 500 })
|
||||
tronApp.page.on('console', console.log)
|
||||
|
||||
await panesOpen(['files', 'code'])
|
||||
|
||||
@ -208,7 +284,7 @@ test.describe('when using the file tree to', () => {
|
||||
await editorTextMatches('')
|
||||
})
|
||||
|
||||
await electronApp.close()
|
||||
await tronApp.close()
|
||||
}
|
||||
)
|
||||
|
||||
@ -217,10 +293,8 @@ test.describe('when using the file tree to', () => {
|
||||
{
|
||||
tag: '@electron',
|
||||
},
|
||||
async ({ browser: _ }, testInfo) => {
|
||||
const { page } = await setupElectron({
|
||||
testInfo,
|
||||
})
|
||||
async ({ browser: _, tronApp }, testInfo) => {
|
||||
await tronApp.initialise()
|
||||
|
||||
const {
|
||||
panesOpen,
|
||||
@ -230,10 +304,10 @@ test.describe('when using the file tree to', () => {
|
||||
openDebugPanel,
|
||||
closeDebugPanel,
|
||||
expectCmdLog,
|
||||
} = await getUtils(page, test)
|
||||
} = await getUtils(tronApp.page, test)
|
||||
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
page.on('console', console.log)
|
||||
await tronApp.page.setViewportSize({ width: 1200, height: 500 })
|
||||
tronApp.page.on('console', console.log)
|
||||
|
||||
await panesOpen(['files', 'code'])
|
||||
await createAndSelectProject('project-000')
|
||||
@ -248,30 +322,30 @@ test.describe('when using the file tree to', () => {
|
||||
|
||||
// Create a large lego file
|
||||
await createNewFile('lego')
|
||||
const legoFile = page.getByRole('listitem').filter({
|
||||
has: page.getByRole('button', { name: 'lego.kcl' }),
|
||||
const legoFile = tronApp.page.getByRole('listitem').filter({
|
||||
has: tronApp.page.getByRole('button', { name: 'lego.kcl' }),
|
||||
})
|
||||
await expect(legoFile).toBeVisible({ timeout: 60_000 })
|
||||
await _expect(legoFile).toBeVisible({ timeout: 60_000 })
|
||||
await legoFile.click()
|
||||
const kclLego = await fsp.readFile(
|
||||
'src/wasm-lib/tests/executor/inputs/lego.kcl',
|
||||
'utf-8'
|
||||
)
|
||||
await pasteCodeInEditor(kclLego)
|
||||
const mainFile = page.getByRole('listitem').filter({
|
||||
has: page.getByRole('button', { name: 'main.kcl' }),
|
||||
const mainFile = tronApp.page.getByRole('listitem').filter({
|
||||
has: tronApp.page.getByRole('button', { name: 'main.kcl' }),
|
||||
})
|
||||
|
||||
// Open settings and enable the debug panel
|
||||
await page
|
||||
await tronApp.page
|
||||
.getByRole('link', {
|
||||
name: 'settings Settings',
|
||||
})
|
||||
.click()
|
||||
await page.locator('#showDebugPanel').getByText('OffOn').click()
|
||||
await page.getByTestId('settings-close-button').click()
|
||||
await tronApp.page.locator('#showDebugPanel').getByText('OffOn').click()
|
||||
await tronApp.page.getByTestId('settings-close-button').click()
|
||||
|
||||
await test.step('swap between small and large files', async () => {
|
||||
await _test.step('swap between small and large files', async () => {
|
||||
await openDebugPanel()
|
||||
// Previously created a file so we need to start back at main.kcl
|
||||
await mainFile.click()
|
||||
@ -283,12 +357,14 @@ test.describe('when using the file tree to', () => {
|
||||
await expectCmdLog('[data-message-type="execution-done"]', 60_000)
|
||||
await closeDebugPanel()
|
||||
})
|
||||
|
||||
await tronApp.close()
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
test.describe('Renaming in the file tree', () => {
|
||||
test(
|
||||
_test.describe('Renaming in the file tree', () => {
|
||||
_test(
|
||||
'A file you have open',
|
||||
{ tag: '@electron' },
|
||||
async ({ browser: _ }, testInfo) => {
|
||||
@ -333,56 +409,56 @@ test.describe('Renaming in the file tree', () => {
|
||||
const renameInput = page.getByPlaceholder('fileToRename.kcl')
|
||||
const codeLocator = page.locator('.cm-content')
|
||||
|
||||
await test.step('Open project and file pane', async () => {
|
||||
await expect(projectLink).toBeVisible()
|
||||
await _test.step('Open project and file pane', async () => {
|
||||
await _expect(projectLink).toBeVisible()
|
||||
await projectLink.click()
|
||||
await expect(projectMenuButton).toBeVisible()
|
||||
await expect(projectMenuButton).toContainText('main.kcl')
|
||||
await _expect(projectMenuButton).toBeVisible()
|
||||
await _expect(projectMenuButton).toContainText('main.kcl')
|
||||
|
||||
await u.openFilePanel()
|
||||
await expect(fileToRename).toBeVisible()
|
||||
expect(checkUnRenamedFS()).toBeTruthy()
|
||||
expect(checkRenamedFS()).toBeFalsy()
|
||||
await _expect(fileToRename).toBeVisible()
|
||||
_expect(checkUnRenamedFS()).toBeTruthy()
|
||||
_expect(checkRenamedFS()).toBeFalsy()
|
||||
await fileToRename.click()
|
||||
await expect(projectMenuButton).toContainText('fileToRename.kcl')
|
||||
await _expect(projectMenuButton).toContainText('fileToRename.kcl')
|
||||
await u.openKclCodePanel()
|
||||
await expect(codeLocator).toContainText('circle(')
|
||||
await _expect(codeLocator).toContainText('circle(')
|
||||
await u.closeKclCodePanel()
|
||||
})
|
||||
|
||||
await test.step('Rename the file', async () => {
|
||||
await _test.step('Rename the file', async () => {
|
||||
await fileToRename.click({ button: 'right' })
|
||||
await renameMenuItem.click()
|
||||
await expect(renameInput).toBeVisible()
|
||||
await _expect(renameInput).toBeVisible()
|
||||
await renameInput.fill(newFileName)
|
||||
await page.keyboard.press('Enter')
|
||||
})
|
||||
|
||||
await test.step('Verify the file is renamed', async () => {
|
||||
await expect(fileToRename).not.toBeAttached()
|
||||
await expect(renamedFile).toBeVisible()
|
||||
expect(checkUnRenamedFS()).toBeFalsy()
|
||||
expect(checkRenamedFS()).toBeTruthy()
|
||||
await _test.step('Verify the file is renamed', async () => {
|
||||
await _expect(fileToRename).not.toBeAttached()
|
||||
await _expect(renamedFile).toBeVisible()
|
||||
_expect(checkUnRenamedFS()).toBeFalsy()
|
||||
_expect(checkRenamedFS()).toBeTruthy()
|
||||
})
|
||||
|
||||
await test.step('Verify we navigated', async () => {
|
||||
await expect(projectMenuButton).toContainText(newFileName + FILE_EXT)
|
||||
await _test.step('Verify we navigated', async () => {
|
||||
await _expect(projectMenuButton).toContainText(newFileName + FILE_EXT)
|
||||
const url = page.url()
|
||||
expect(url).toContain(newFileName)
|
||||
await expect(projectMenuButton).not.toContainText('fileToRename.kcl')
|
||||
await expect(projectMenuButton).not.toContainText('main.kcl')
|
||||
expect(url).not.toContain('fileToRename.kcl')
|
||||
expect(url).not.toContain('main.kcl')
|
||||
_expect(url).toContain(newFileName)
|
||||
await _expect(projectMenuButton).not.toContainText('fileToRename.kcl')
|
||||
await _expect(projectMenuButton).not.toContainText('main.kcl')
|
||||
_expect(url).not.toContain('fileToRename.kcl')
|
||||
_expect(url).not.toContain('main.kcl')
|
||||
|
||||
await u.openKclCodePanel()
|
||||
await expect(codeLocator).toContainText('circle(')
|
||||
await _expect(codeLocator).toContainText('circle(')
|
||||
})
|
||||
|
||||
await electronApp.close()
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
_test(
|
||||
'A file you do not have open',
|
||||
{ tag: '@electron' },
|
||||
async ({ browser: _ }, testInfo) => {
|
||||
@ -426,54 +502,54 @@ test.describe('Renaming in the file tree', () => {
|
||||
const renameInput = page.getByPlaceholder('fileToRename.kcl')
|
||||
const codeLocator = page.locator('.cm-content')
|
||||
|
||||
await test.step('Open project and file pane', async () => {
|
||||
await expect(projectLink).toBeVisible()
|
||||
await _test.step('Open project and file pane', async () => {
|
||||
await _expect(projectLink).toBeVisible()
|
||||
await projectLink.click()
|
||||
await expect(projectMenuButton).toBeVisible()
|
||||
await expect(projectMenuButton).toContainText('main.kcl')
|
||||
await _expect(projectMenuButton).toBeVisible()
|
||||
await _expect(projectMenuButton).toContainText('main.kcl')
|
||||
|
||||
await u.openFilePanel()
|
||||
await expect(fileToRename).toBeVisible()
|
||||
expect(checkUnRenamedFS()).toBeTruthy()
|
||||
expect(checkRenamedFS()).toBeFalsy()
|
||||
await _expect(fileToRename).toBeVisible()
|
||||
_expect(checkUnRenamedFS()).toBeTruthy()
|
||||
_expect(checkRenamedFS()).toBeFalsy()
|
||||
})
|
||||
|
||||
await test.step('Rename the file', async () => {
|
||||
await _test.step('Rename the file', async () => {
|
||||
await fileToRename.click({ button: 'right' })
|
||||
await renameMenuItem.click()
|
||||
await expect(renameInput).toBeVisible()
|
||||
await _expect(renameInput).toBeVisible()
|
||||
await renameInput.fill(newFileName)
|
||||
await page.keyboard.press('Enter')
|
||||
})
|
||||
|
||||
await test.step('Verify the file is renamed', async () => {
|
||||
await expect(fileToRename).not.toBeAttached()
|
||||
await expect(renamedFile).toBeVisible()
|
||||
expect(checkUnRenamedFS()).toBeFalsy()
|
||||
expect(checkRenamedFS()).toBeTruthy()
|
||||
await _test.step('Verify the file is renamed', async () => {
|
||||
await _expect(fileToRename).not.toBeAttached()
|
||||
await _expect(renamedFile).toBeVisible()
|
||||
_expect(checkUnRenamedFS()).toBeFalsy()
|
||||
_expect(checkRenamedFS()).toBeTruthy()
|
||||
})
|
||||
|
||||
await test.step('Verify we have not navigated', async () => {
|
||||
await expect(projectMenuButton).toContainText('main.kcl')
|
||||
await expect(projectMenuButton).not.toContainText(
|
||||
await _test.step('Verify we have not navigated', async () => {
|
||||
await _expect(projectMenuButton).toContainText('main.kcl')
|
||||
await _expect(projectMenuButton).not.toContainText(
|
||||
newFileName + FILE_EXT
|
||||
)
|
||||
await expect(projectMenuButton).not.toContainText('fileToRename.kcl')
|
||||
await _expect(projectMenuButton).not.toContainText('fileToRename.kcl')
|
||||
|
||||
const url = page.url()
|
||||
expect(url).toContain('main.kcl')
|
||||
expect(url).not.toContain(newFileName)
|
||||
expect(url).not.toContain('fileToRename.kcl')
|
||||
_expect(url).toContain('main.kcl')
|
||||
_expect(url).not.toContain(newFileName)
|
||||
_expect(url).not.toContain('fileToRename.kcl')
|
||||
|
||||
await u.openKclCodePanel()
|
||||
await expect(codeLocator).toContainText('fillet(')
|
||||
await _expect(codeLocator).toContainText('fillet(')
|
||||
})
|
||||
|
||||
await electronApp.close()
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
_test(
|
||||
`A folder you're not inside`,
|
||||
{ tag: '@electron' },
|
||||
async ({ browser: _ }, testInfo) => {
|
||||
@ -519,48 +595,51 @@ test.describe('Renaming in the file tree', () => {
|
||||
return fs.existsSync(folderPath)
|
||||
}
|
||||
|
||||
await test.step('Open project and file pane', async () => {
|
||||
await expect(projectLink).toBeVisible()
|
||||
await _test.step('Open project and file pane', async () => {
|
||||
await _expect(projectLink).toBeVisible()
|
||||
await projectLink.click()
|
||||
await expect(projectMenuButton).toBeVisible()
|
||||
await expect(projectMenuButton).toContainText('main.kcl')
|
||||
await _expect(projectMenuButton).toBeVisible()
|
||||
await _expect(projectMenuButton).toContainText('main.kcl')
|
||||
|
||||
const url = page.url()
|
||||
expect(url).toContain('main.kcl')
|
||||
expect(url).not.toContain('folderToRename')
|
||||
_expect(url).toContain('main.kcl')
|
||||
_expect(url).not.toContain('folderToRename')
|
||||
|
||||
await u.openFilePanel()
|
||||
await expect(folderToRename).toBeVisible()
|
||||
expect(checkUnRenamedFolderFS()).toBeTruthy()
|
||||
expect(checkRenamedFolderFS()).toBeFalsy()
|
||||
await _expect(folderToRename).toBeVisible()
|
||||
_expect(checkUnRenamedFolderFS()).toBeTruthy()
|
||||
_expect(checkRenamedFolderFS()).toBeFalsy()
|
||||
})
|
||||
|
||||
await test.step('Rename the folder', async () => {
|
||||
await _test.step('Rename the folder', async () => {
|
||||
await folderToRename.click({ button: 'right' })
|
||||
await expect(renameMenuItem).toBeVisible()
|
||||
await _expect(renameMenuItem).toBeVisible()
|
||||
await renameMenuItem.click()
|
||||
await expect(renameInput).toBeVisible()
|
||||
await _expect(renameInput).toBeVisible()
|
||||
await renameInput.fill(newFolderName)
|
||||
await page.keyboard.press('Enter')
|
||||
})
|
||||
|
||||
await test.step('Verify the folder is renamed, and no navigation occurred', async () => {
|
||||
const url = page.url()
|
||||
expect(url).toContain('main.kcl')
|
||||
expect(url).not.toContain('folderToRename')
|
||||
await _test.step(
|
||||
'Verify the folder is renamed, and no navigation occurred',
|
||||
async () => {
|
||||
const url = page.url()
|
||||
_expect(url).toContain('main.kcl')
|
||||
_expect(url).not.toContain('folderToRename')
|
||||
|
||||
await expect(projectMenuButton).toContainText('main.kcl')
|
||||
await expect(renamedFolder).toBeVisible()
|
||||
await expect(folderToRename).not.toBeAttached()
|
||||
expect(checkUnRenamedFolderFS()).toBeFalsy()
|
||||
expect(checkRenamedFolderFS()).toBeTruthy()
|
||||
})
|
||||
await _expect(projectMenuButton).toContainText('main.kcl')
|
||||
await _expect(renamedFolder).toBeVisible()
|
||||
await _expect(folderToRename).not.toBeAttached()
|
||||
_expect(checkUnRenamedFolderFS()).toBeFalsy()
|
||||
_expect(checkRenamedFolderFS()).toBeTruthy()
|
||||
}
|
||||
)
|
||||
|
||||
await electronApp.close()
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
_test(
|
||||
`A folder you are inside`,
|
||||
{ tag: '@electron' },
|
||||
async ({ browser: _ }, testInfo) => {
|
||||
@ -609,66 +688,69 @@ test.describe('Renaming in the file tree', () => {
|
||||
return fs.existsSync(folderPath)
|
||||
}
|
||||
|
||||
await test.step('Open project and navigate into folder', async () => {
|
||||
await expect(projectLink).toBeVisible()
|
||||
await _test.step('Open project and navigate into folder', async () => {
|
||||
await _expect(projectLink).toBeVisible()
|
||||
await projectLink.click()
|
||||
await expect(projectMenuButton).toBeVisible()
|
||||
await expect(projectMenuButton).toContainText('main.kcl')
|
||||
await _expect(projectMenuButton).toBeVisible()
|
||||
await _expect(projectMenuButton).toContainText('main.kcl')
|
||||
|
||||
const url = page.url()
|
||||
expect(url).toContain('main.kcl')
|
||||
expect(url).not.toContain('folderToRename')
|
||||
_expect(url).toContain('main.kcl')
|
||||
_expect(url).not.toContain('folderToRename')
|
||||
|
||||
await u.openFilePanel()
|
||||
await expect(folderToRename).toBeVisible()
|
||||
await _expect(folderToRename).toBeVisible()
|
||||
await folderToRename.click()
|
||||
await expect(fileWithinFolder).toBeVisible()
|
||||
await _expect(fileWithinFolder).toBeVisible()
|
||||
await fileWithinFolder.click()
|
||||
|
||||
await expect(projectMenuButton).toContainText('someFileWithin.kcl')
|
||||
await _expect(projectMenuButton).toContainText('someFileWithin.kcl')
|
||||
const newUrl = page.url()
|
||||
expect(newUrl).toContain('folderToRename')
|
||||
expect(newUrl).toContain('someFileWithin.kcl')
|
||||
expect(newUrl).not.toContain('main.kcl')
|
||||
expect(checkUnRenamedFolderFS()).toBeTruthy()
|
||||
expect(checkRenamedFolderFS()).toBeFalsy()
|
||||
_expect(newUrl).toContain('folderToRename')
|
||||
_expect(newUrl).toContain('someFileWithin.kcl')
|
||||
_expect(newUrl).not.toContain('main.kcl')
|
||||
_expect(checkUnRenamedFolderFS()).toBeTruthy()
|
||||
_expect(checkRenamedFolderFS()).toBeFalsy()
|
||||
})
|
||||
|
||||
await test.step('Rename the folder', async () => {
|
||||
await _test.step('Rename the folder', async () => {
|
||||
await page.waitForTimeout(60000)
|
||||
await folderToRename.click({ button: 'right' })
|
||||
await expect(renameMenuItem).toBeVisible()
|
||||
await _expect(renameMenuItem).toBeVisible()
|
||||
await renameMenuItem.click()
|
||||
await expect(renameInput).toBeVisible()
|
||||
await _expect(renameInput).toBeVisible()
|
||||
await renameInput.fill(newFolderName)
|
||||
await page.keyboard.press('Enter')
|
||||
})
|
||||
|
||||
await test.step('Verify the folder is renamed, and navigated to new path', async () => {
|
||||
const urlSnippet = encodeURIComponent(
|
||||
join(newFolderName, 'someFileWithin.kcl')
|
||||
)
|
||||
await page.waitForURL(new RegExp(urlSnippet))
|
||||
await expect(projectMenuButton).toContainText('someFileWithin.kcl')
|
||||
await expect(renamedFolder).toBeVisible()
|
||||
await expect(folderToRename).not.toBeAttached()
|
||||
await _test.step(
|
||||
'Verify the folder is renamed, and navigated to new path',
|
||||
async () => {
|
||||
const urlSnippet = encodeURIComponent(
|
||||
join(newFolderName, 'someFileWithin.kcl')
|
||||
)
|
||||
await page.waitForURL(new RegExp(urlSnippet))
|
||||
await _expect(projectMenuButton).toContainText('someFileWithin.kcl')
|
||||
await _expect(renamedFolder).toBeVisible()
|
||||
await _expect(folderToRename).not.toBeAttached()
|
||||
|
||||
// URL is synchronous, so we check the other stuff first
|
||||
const url = page.url()
|
||||
expect(url).not.toContain('main.kcl')
|
||||
expect(url).toContain(newFolderName)
|
||||
expect(url).toContain('someFileWithin.kcl')
|
||||
expect(checkUnRenamedFolderFS()).toBeFalsy()
|
||||
expect(checkRenamedFolderFS()).toBeTruthy()
|
||||
})
|
||||
// URL is synchronous, so we check the other stuff first
|
||||
const url = page.url()
|
||||
_expect(url).not.toContain('main.kcl')
|
||||
_expect(url).toContain(newFolderName)
|
||||
_expect(url).toContain('someFileWithin.kcl')
|
||||
_expect(checkUnRenamedFolderFS()).toBeFalsy()
|
||||
_expect(checkRenamedFolderFS()).toBeTruthy()
|
||||
}
|
||||
)
|
||||
|
||||
await electronApp.close()
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
test.describe('Deleting items from the file pane', () => {
|
||||
test(
|
||||
_test.describe('Deleting items from the file pane', () => {
|
||||
_test(
|
||||
`delete file when main.kcl exists, navigate to main.kcl`,
|
||||
{ tag: '@electron' },
|
||||
async ({ browserName }, testInfo) => {
|
||||
@ -700,45 +782,48 @@ test.describe('Deleting items from the file pane', () => {
|
||||
const deleteMenuItem = page.getByRole('button', { name: 'Delete' })
|
||||
const deleteConfirmation = page.getByTestId('delete-confirmation')
|
||||
|
||||
await test.step('Open project and navigate to fileToDelete.kcl', async () => {
|
||||
await projectCard.click()
|
||||
await u.waitForPageLoad()
|
||||
await u.openFilePanel()
|
||||
await _test.step(
|
||||
'Open project and navigate to fileToDelete.kcl',
|
||||
async () => {
|
||||
await projectCard.click()
|
||||
await u.waitForPageLoad()
|
||||
await u.openFilePanel()
|
||||
|
||||
await fileToDelete.click()
|
||||
await u.waitForPageLoad()
|
||||
await u.openKclCodePanel()
|
||||
await expect(u.codeLocator).toContainText('getOppositeEdge(thing)')
|
||||
await u.closeKclCodePanel()
|
||||
})
|
||||
await fileToDelete.click()
|
||||
await u.waitForPageLoad()
|
||||
await u.openKclCodePanel()
|
||||
await _expect(u.codeLocator).toContainText('getOppositeEdge(thing)')
|
||||
await u.closeKclCodePanel()
|
||||
}
|
||||
)
|
||||
|
||||
await test.step('Delete fileToDelete.kcl', async () => {
|
||||
await _test.step('Delete fileToDelete.kcl', async () => {
|
||||
await fileToDelete.click({ button: 'right' })
|
||||
await expect(deleteMenuItem).toBeVisible()
|
||||
await _expect(deleteMenuItem).toBeVisible()
|
||||
await deleteMenuItem.click()
|
||||
await expect(deleteConfirmation).toBeVisible()
|
||||
await _expect(deleteConfirmation).toBeVisible()
|
||||
await deleteConfirmation.click()
|
||||
})
|
||||
|
||||
await test.step('Check deletion and navigation', async () => {
|
||||
await _test.step('Check deletion and navigation', async () => {
|
||||
await u.waitForPageLoad()
|
||||
await expect(fileToDelete).not.toBeVisible()
|
||||
await _expect(fileToDelete).not.toBeVisible()
|
||||
await u.closeFilePanel()
|
||||
await u.openKclCodePanel()
|
||||
await expect(u.codeLocator).toContainText('circle(')
|
||||
await expect(projectMenuButton).toContainText('main.kcl')
|
||||
await _expect(u.codeLocator).toContainText('circle(')
|
||||
await _expect(projectMenuButton).toContainText('main.kcl')
|
||||
})
|
||||
|
||||
await electronApp.close()
|
||||
}
|
||||
)
|
||||
|
||||
test.fixme(
|
||||
_test.fixme(
|
||||
'TODO - delete file we have open when main.kcl does not exist',
|
||||
async () => {}
|
||||
)
|
||||
|
||||
test(
|
||||
_test(
|
||||
`Delete folder we are not in, don't navigate`,
|
||||
{ tag: '@electron' },
|
||||
async ({ browserName }, testInfo) => {
|
||||
@ -772,32 +857,32 @@ test.describe('Deleting items from the file pane', () => {
|
||||
const deleteMenuItem = page.getByRole('button', { name: 'Delete' })
|
||||
const deleteConfirmation = page.getByTestId('delete-confirmation')
|
||||
|
||||
await test.step('Open project and open project pane', async () => {
|
||||
await _test.step('Open project and open project pane', async () => {
|
||||
await projectCard.click()
|
||||
await u.waitForPageLoad()
|
||||
await expect(projectMenuButton).toContainText('main.kcl')
|
||||
await _expect(projectMenuButton).toContainText('main.kcl')
|
||||
await u.closeKclCodePanel()
|
||||
await u.openFilePanel()
|
||||
})
|
||||
|
||||
await test.step('Delete folderToDelete', async () => {
|
||||
await _test.step('Delete folderToDelete', async () => {
|
||||
await folderToDelete.click({ button: 'right' })
|
||||
await expect(deleteMenuItem).toBeVisible()
|
||||
await _expect(deleteMenuItem).toBeVisible()
|
||||
await deleteMenuItem.click()
|
||||
await expect(deleteConfirmation).toBeVisible()
|
||||
await _expect(deleteConfirmation).toBeVisible()
|
||||
await deleteConfirmation.click()
|
||||
})
|
||||
|
||||
await test.step('Check deletion and no navigation', async () => {
|
||||
await expect(folderToDelete).not.toBeAttached()
|
||||
await expect(projectMenuButton).toContainText('main.kcl')
|
||||
await _test.step('Check deletion and no navigation', async () => {
|
||||
await _expect(folderToDelete).not.toBeAttached()
|
||||
await _expect(projectMenuButton).toContainText('main.kcl')
|
||||
})
|
||||
|
||||
await electronApp.close()
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
_test(
|
||||
`Delete folder we are in, navigate to main.kcl`,
|
||||
{ tag: '@electron' },
|
||||
async ({ browserName }, testInfo) => {
|
||||
@ -834,36 +919,45 @@ test.describe('Deleting items from the file pane', () => {
|
||||
const deleteMenuItem = page.getByRole('button', { name: 'Delete' })
|
||||
const deleteConfirmation = page.getByTestId('delete-confirmation')
|
||||
|
||||
await test.step('Open project and navigate into folderToDelete', async () => {
|
||||
await projectCard.click()
|
||||
await u.waitForPageLoad()
|
||||
await expect(projectMenuButton).toContainText('main.kcl')
|
||||
await u.closeKclCodePanel()
|
||||
await u.openFilePanel()
|
||||
await _test.step(
|
||||
'Open project and navigate into folderToDelete',
|
||||
async () => {
|
||||
await projectCard.click()
|
||||
await u.waitForPageLoad()
|
||||
await _expect(projectMenuButton).toContainText('main.kcl')
|
||||
await u.closeKclCodePanel()
|
||||
await u.openFilePanel()
|
||||
|
||||
await folderToDelete.click()
|
||||
await expect(fileWithinFolder).toBeVisible()
|
||||
await fileWithinFolder.click()
|
||||
await expect(projectMenuButton).toContainText('someFileWithin.kcl')
|
||||
})
|
||||
await folderToDelete.click()
|
||||
await _expect(fileWithinFolder).toBeVisible()
|
||||
await fileWithinFolder.click()
|
||||
await _expect(projectMenuButton).toContainText('someFileWithin.kcl')
|
||||
}
|
||||
)
|
||||
|
||||
await test.step('Delete folderToDelete', async () => {
|
||||
await _test.step('Delete folderToDelete', async () => {
|
||||
await folderToDelete.click({ button: 'right' })
|
||||
await expect(deleteMenuItem).toBeVisible()
|
||||
await _expect(deleteMenuItem).toBeVisible()
|
||||
await deleteMenuItem.click()
|
||||
await expect(deleteConfirmation).toBeVisible()
|
||||
await _expect(deleteConfirmation).toBeVisible()
|
||||
await deleteConfirmation.click()
|
||||
})
|
||||
|
||||
await test.step('Check deletion and navigation to main.kcl', async () => {
|
||||
await expect(folderToDelete).not.toBeAttached()
|
||||
await expect(fileWithinFolder).not.toBeAttached()
|
||||
await expect(projectMenuButton).toContainText('main.kcl')
|
||||
})
|
||||
await _test.step(
|
||||
'Check deletion and navigation to main.kcl',
|
||||
async () => {
|
||||
await _expect(folderToDelete).not.toBeAttached()
|
||||
await _expect(fileWithinFolder).not.toBeAttached()
|
||||
await _expect(projectMenuButton).toContainText('main.kcl')
|
||||
}
|
||||
)
|
||||
|
||||
await electronApp.close()
|
||||
}
|
||||
)
|
||||
|
||||
test.fixme('TODO - delete folder we are in, with no main.kcl', async () => {})
|
||||
_test.fixme(
|
||||
'TODO - delete folder we are in, with no main.kcl',
|
||||
async () => {}
|
||||
)
|
||||
})
|
||||
|
@ -1,70 +0,0 @@
|
||||
import type { Page } from '@playwright/test'
|
||||
import { test as base } from '@playwright/test'
|
||||
import { getUtils, setup, tearDown } from './test-utils'
|
||||
import fsp from 'fs/promises'
|
||||
import { join } from 'path'
|
||||
import { CmdBarFixture } from './cmdBarFixture'
|
||||
import { EditorFixture } from './editorFixture'
|
||||
import { ToolbarFixture } from './toolbarFixture'
|
||||
import { SceneFixture } from './sceneFixture'
|
||||
|
||||
export class AuthenticatedApp {
|
||||
public readonly page: Page
|
||||
|
||||
constructor(page: Page) {
|
||||
this.page = page
|
||||
}
|
||||
|
||||
async initialise(code = '') {
|
||||
const u = await getUtils(this.page)
|
||||
|
||||
await this.page.addInitScript(async (code) => {
|
||||
localStorage.setItem('persistCode', code)
|
||||
;(window as any).playwrightSkipFilePicker = true
|
||||
}, code)
|
||||
|
||||
await this.page.setViewportSize({ width: 1000, height: 500 })
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
}
|
||||
getInputFile = (fileName: string) => {
|
||||
return fsp.readFile(
|
||||
join('src', 'wasm-lib', 'tests', 'executor', 'inputs', fileName),
|
||||
'utf-8'
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export const test = base.extend<{
|
||||
app: AuthenticatedApp
|
||||
cmdBar: CmdBarFixture
|
||||
editor: EditorFixture
|
||||
toolbar: ToolbarFixture
|
||||
scene: SceneFixture
|
||||
}>({
|
||||
app: async ({ page }, use) => {
|
||||
await use(new AuthenticatedApp(page))
|
||||
},
|
||||
cmdBar: async ({ page }, use) => {
|
||||
await use(new CmdBarFixture(page))
|
||||
},
|
||||
editor: async ({ page }, use) => {
|
||||
await use(new EditorFixture(page))
|
||||
},
|
||||
toolbar: async ({ page }, use) => {
|
||||
await use(new ToolbarFixture(page))
|
||||
},
|
||||
scene: async ({ page }, use) => {
|
||||
await use(new SceneFixture(page))
|
||||
},
|
||||
})
|
||||
|
||||
test.beforeEach(async ({ context, page }, testInfo) => {
|
||||
await setup(context, page, testInfo)
|
||||
})
|
||||
|
||||
test.afterEach(async ({ page }, testInfo) => {
|
||||
await tearDown(page, testInfo)
|
||||
})
|
||||
|
||||
export { expect } from '@playwright/test'
|
@ -25,11 +25,14 @@ type CmdBarSerialised =
|
||||
}
|
||||
|
||||
export class CmdBarFixture {
|
||||
public readonly page: Page
|
||||
public page: Page
|
||||
|
||||
constructor(page: Page) {
|
||||
this.page = page
|
||||
}
|
||||
reConstruct = (page: Page) => {
|
||||
this.page = page
|
||||
}
|
||||
|
||||
private _serialiseCmdBar = async (): Promise<CmdBarSerialised> => {
|
||||
const reviewForm = await this.page.locator('#review-form')
|
@ -1,5 +1,6 @@
|
||||
import type { Page, Locator } from '@playwright/test'
|
||||
import { expect } from '@playwright/test'
|
||||
import { sansWhitespace } from '../test-utils'
|
||||
|
||||
interface EditorState {
|
||||
activeLines: Array<string>
|
||||
@ -7,19 +8,20 @@ interface EditorState {
|
||||
diagnostics: Array<string>
|
||||
}
|
||||
|
||||
function removeWhitespace(str: string) {
|
||||
return str.replace(/\s+/g, '').trim()
|
||||
}
|
||||
export class EditorFixture {
|
||||
public readonly page: Page
|
||||
public page: Page
|
||||
|
||||
private readonly diagnosticsTooltip: Locator
|
||||
private readonly diagnosticsGutterIcon: Locator
|
||||
private readonly codeContent: Locator
|
||||
private readonly activeLine: Locator
|
||||
private diagnosticsTooltip!: Locator
|
||||
private diagnosticsGutterIcon!: Locator
|
||||
private codeContent!: Locator
|
||||
private activeLine!: Locator
|
||||
|
||||
constructor(page: Page) {
|
||||
this.page = page
|
||||
this.reConstruct(page)
|
||||
}
|
||||
reConstruct = (page: Page) => {
|
||||
this.page = page
|
||||
|
||||
this.codeContent = page.locator('.cm-content')
|
||||
this.diagnosticsTooltip = page.locator('.cm-tooltip-lint')
|
||||
@ -94,16 +96,16 @@ export class EditorFixture {
|
||||
this._serialiseDiagnostics(),
|
||||
])
|
||||
const state: EditorState = {
|
||||
activeLines: activeLines.map(removeWhitespace).filter(Boolean),
|
||||
highlightedCode: removeWhitespace(highlightedCode),
|
||||
activeLines: activeLines.map(sansWhitespace).filter(Boolean),
|
||||
highlightedCode: sansWhitespace(highlightedCode),
|
||||
diagnostics,
|
||||
}
|
||||
return state
|
||||
})
|
||||
.toEqual({
|
||||
activeLines: expectedState.activeLines.map(removeWhitespace),
|
||||
highlightedCode: removeWhitespace(expectedState.highlightedCode),
|
||||
diagnostics: expectedState.diagnostics.map(removeWhitespace),
|
||||
activeLines: expectedState.activeLines.map(sansWhitespace),
|
||||
highlightedCode: sansWhitespace(expectedState.highlightedCode),
|
||||
diagnostics: expectedState.diagnostics.map(sansWhitespace),
|
||||
})
|
||||
}
|
||||
}
|
140
e2e/playwright/fixtures/fixtureSetup.ts
Normal file
@ -0,0 +1,140 @@
|
||||
import type {
|
||||
BrowserContext,
|
||||
ElectronApplication,
|
||||
Page,
|
||||
TestInfo,
|
||||
} from '@playwright/test'
|
||||
import { test as base } from '@playwright/test'
|
||||
import { getUtils, setup, setupElectron, tearDown } from '../test-utils'
|
||||
import fsp from 'fs/promises'
|
||||
import { join } from 'path'
|
||||
import { CmdBarFixture } from './cmdBarFixture'
|
||||
import { EditorFixture } from './editorFixture'
|
||||
import { ToolbarFixture } from './toolbarFixture'
|
||||
import { SceneFixture } from './sceneFixture'
|
||||
import { SaveSettingsPayload } from 'lib/settings/settingsTypes'
|
||||
import { HomePageFixture } from './homePageFixture'
|
||||
import { unsafeTypedKeys } from 'lib/utils'
|
||||
|
||||
export class AuthenticatedApp {
|
||||
public readonly page: Page
|
||||
public readonly context: BrowserContext
|
||||
public readonly testInfo: TestInfo
|
||||
|
||||
constructor(context: BrowserContext, page: Page, testInfo: TestInfo) {
|
||||
this.page = page
|
||||
this.context = context
|
||||
this.testInfo = testInfo
|
||||
}
|
||||
|
||||
async initialise(code = '') {
|
||||
await setup(this.context, this.page, this.testInfo)
|
||||
const u = await getUtils(this.page)
|
||||
|
||||
await this.page.addInitScript(async (code) => {
|
||||
localStorage.setItem('persistCode', code)
|
||||
;(window as any).playwrightSkipFilePicker = true
|
||||
}, code)
|
||||
|
||||
await this.page.setViewportSize({ width: 1000, height: 500 })
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
}
|
||||
getInputFile = (fileName: string) => {
|
||||
return fsp.readFile(
|
||||
join('src', 'wasm-lib', 'tests', 'executor', 'inputs', fileName),
|
||||
'utf-8'
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
interface Fixtures {
|
||||
app: AuthenticatedApp
|
||||
tronApp: AuthenticatedTronApp
|
||||
cmdBar: CmdBarFixture
|
||||
editor: EditorFixture
|
||||
toolbar: ToolbarFixture
|
||||
scene: SceneFixture
|
||||
homePage: HomePageFixture
|
||||
}
|
||||
export class AuthenticatedTronApp {
|
||||
public readonly _page: Page
|
||||
public page: Page
|
||||
public readonly context: BrowserContext
|
||||
public readonly testInfo: TestInfo
|
||||
public electronApp?: ElectronApplication
|
||||
|
||||
constructor(context: BrowserContext, page: Page, testInfo: TestInfo) {
|
||||
this._page = page
|
||||
this.page = page
|
||||
this.context = context
|
||||
this.testInfo = testInfo
|
||||
}
|
||||
async initialise(
|
||||
arg: {
|
||||
fixtures: Partial<Fixtures>
|
||||
folderSetupFn?: (projectDirName: string) => Promise<void>
|
||||
cleanProjectDir?: boolean
|
||||
appSettings?: Partial<SaveSettingsPayload>
|
||||
} = { fixtures: {} }
|
||||
) {
|
||||
const { electronApp, page } = await setupElectron({
|
||||
testInfo: this.testInfo,
|
||||
folderSetupFn: arg.folderSetupFn,
|
||||
cleanProjectDir: arg.cleanProjectDir,
|
||||
appSettings: arg.appSettings,
|
||||
})
|
||||
this.page = page
|
||||
this.electronApp = electronApp
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
for (const key of unsafeTypedKeys(arg.fixtures)) {
|
||||
const fixture = arg.fixtures[key]
|
||||
if (
|
||||
!fixture ||
|
||||
fixture instanceof AuthenticatedApp ||
|
||||
fixture instanceof AuthenticatedTronApp
|
||||
)
|
||||
continue
|
||||
fixture.reConstruct(page)
|
||||
}
|
||||
}
|
||||
|
||||
close = async () => {
|
||||
await this.electronApp?.close?.()
|
||||
}
|
||||
debugPause = () =>
|
||||
new Promise(() => {
|
||||
console.log('UN-RESOLVING PROMISE')
|
||||
})
|
||||
}
|
||||
|
||||
export const test = base.extend<Fixtures>({
|
||||
app: async ({ page, context }, use, testInfo) => {
|
||||
await use(new AuthenticatedApp(context, page, testInfo))
|
||||
},
|
||||
tronApp: async ({ page, context }, use, testInfo) => {
|
||||
await use(new AuthenticatedTronApp(context, page, testInfo))
|
||||
},
|
||||
cmdBar: async ({ page }, use) => {
|
||||
await use(new CmdBarFixture(page))
|
||||
},
|
||||
editor: async ({ page }, use) => {
|
||||
await use(new EditorFixture(page))
|
||||
},
|
||||
toolbar: async ({ page }, use) => {
|
||||
await use(new ToolbarFixture(page))
|
||||
},
|
||||
scene: async ({ page }, use) => {
|
||||
await use(new SceneFixture(page))
|
||||
},
|
||||
homePage: async ({ page }, use) => {
|
||||
await use(new HomePageFixture(page))
|
||||
},
|
||||
})
|
||||
|
||||
test.afterEach(async ({ page }, testInfo) => {
|
||||
await tearDown(page, testInfo)
|
||||
})
|
||||
|
||||
export { expect } from '@playwright/test'
|
103
e2e/playwright/fixtures/homePageFixture.ts
Normal file
@ -0,0 +1,103 @@
|
||||
import type { Page, Locator } from '@playwright/test'
|
||||
import { expect } from '@playwright/test'
|
||||
|
||||
interface ProjectCardState {
|
||||
title: string
|
||||
fileCount: number
|
||||
folderCount: number
|
||||
}
|
||||
|
||||
interface HomePageState {
|
||||
projectCards: ProjectCardState[]
|
||||
sortBy: 'last-modified-desc' | 'last-modified-asc' | 'name-asc' | 'name-desc'
|
||||
}
|
||||
|
||||
export class HomePageFixture {
|
||||
public page: Page
|
||||
|
||||
projectCard!: Locator
|
||||
projectCardTitle!: Locator
|
||||
projectCardFile!: Locator
|
||||
projectCardFolder!: Locator
|
||||
sortByDateBtn!: Locator
|
||||
sortByNameBtn!: Locator
|
||||
|
||||
constructor(page: Page) {
|
||||
this.page = page
|
||||
this.reConstruct(page)
|
||||
}
|
||||
reConstruct = (page: Page) => {
|
||||
this.page = page
|
||||
|
||||
this.projectCard = this.page.getByTestId('project-link')
|
||||
this.projectCardTitle = this.page.getByTestId('project-title')
|
||||
this.projectCardFile = this.page.getByTestId('project-file-count')
|
||||
this.projectCardFolder = this.page.getByTestId('project-folder-count')
|
||||
|
||||
this.sortByDateBtn = this.page.getByTestId('home-sort-by-modified')
|
||||
this.sortByNameBtn = this.page.getByTestId('home-sort-by-name')
|
||||
}
|
||||
|
||||
private _serialiseSortBy = async (): Promise<
|
||||
HomePageState['sortBy'] | null
|
||||
> => {
|
||||
const [dateBtnDesc, dateBtnAsc, nameBtnDesc, nameBtnAsc] =
|
||||
await Promise.all([
|
||||
this.sortByDateBtn.getByLabel('arrow down').isVisible(),
|
||||
this.sortByDateBtn.getByLabel('arrow up').isVisible(),
|
||||
this.sortByNameBtn.getByLabel('arrow down').isVisible(),
|
||||
this.sortByNameBtn.getByLabel('arrow up').isVisible(),
|
||||
])
|
||||
if (dateBtnDesc) return 'last-modified-desc'
|
||||
if (dateBtnAsc) return 'last-modified-asc'
|
||||
if (nameBtnDesc) return 'name-desc'
|
||||
if (nameBtnAsc) return 'name-asc'
|
||||
return null
|
||||
}
|
||||
|
||||
private _serialiseProjectCards = async (): Promise<
|
||||
Array<ProjectCardState>
|
||||
> => {
|
||||
const projectCards = await this.projectCard.all()
|
||||
const projectCardStates: Array<ProjectCardState> = []
|
||||
for (const projectCard of projectCards) {
|
||||
const [title, fileCount, folderCount] = await Promise.all([
|
||||
(await projectCard.locator(this.projectCardTitle).textContent()) || '',
|
||||
Number(await projectCard.locator(this.projectCardFile).textContent()),
|
||||
Number(await projectCard.locator(this.projectCardFolder).textContent()),
|
||||
])
|
||||
projectCardStates.push({
|
||||
title: title,
|
||||
fileCount,
|
||||
folderCount,
|
||||
})
|
||||
}
|
||||
return projectCardStates
|
||||
}
|
||||
|
||||
/**
|
||||
* Date is excluded from expectState, since it changes
|
||||
* Maybe there a good sanity check we can do each time?
|
||||
*/
|
||||
expectState = async (expectedState: HomePageState) => {
|
||||
await expect
|
||||
.poll(async () => {
|
||||
const [projectCards, sortBy] = await Promise.all([
|
||||
this._serialiseProjectCards(),
|
||||
this._serialiseSortBy(),
|
||||
])
|
||||
return {
|
||||
projectCards,
|
||||
sortBy,
|
||||
}
|
||||
})
|
||||
.toEqual(expectedState)
|
||||
}
|
||||
|
||||
openProject = async (projectTitle: string) => {
|
||||
const projectCard = this.projectCard.locator(
|
||||
this.page.getByText(projectTitle)
|
||||
)
|
||||
await projectCard.click()
|
||||
}
|
||||
}
|
@ -6,18 +6,24 @@ import {
|
||||
doAndWaitForImageDiff,
|
||||
openAndClearDebugPanel,
|
||||
sendCustomCmd,
|
||||
} from './test-utils'
|
||||
} from '../test-utils'
|
||||
|
||||
type mouseParams = {
|
||||
pixelDiff: number
|
||||
}
|
||||
|
||||
export class SceneFixture {
|
||||
public readonly page: Page
|
||||
private readonly exeIndicator: Locator
|
||||
public page: Page
|
||||
|
||||
private exeIndicator!: Locator
|
||||
|
||||
constructor(page: Page) {
|
||||
this.page = page
|
||||
this.reConstruct(page)
|
||||
}
|
||||
reConstruct = (page: Page) => {
|
||||
this.page = page
|
||||
|
||||
this.exeIndicator = page.getByTestId('model-state-indicator-execution-done')
|
||||
}
|
||||
|
||||
@ -25,33 +31,38 @@ export class SceneFixture {
|
||||
x: number,
|
||||
y: number,
|
||||
{ steps }: { steps: number } = { steps: 5000 }
|
||||
) => [
|
||||
(params?: mouseParams) => {
|
||||
if (params?.pixelDiff) {
|
||||
return doAndWaitForImageDiff(
|
||||
this.page,
|
||||
() => this.page.mouse.click(x, y),
|
||||
params.pixelDiff
|
||||
)
|
||||
}
|
||||
return this.page.mouse.click(x, y)
|
||||
},
|
||||
(params?: mouseParams) => {
|
||||
if (params?.pixelDiff) {
|
||||
return doAndWaitForImageDiff(
|
||||
this.page,
|
||||
() => this.page.mouse.move(x, y, { steps }),
|
||||
params.pixelDiff
|
||||
)
|
||||
}
|
||||
return this.page.mouse.move(x, y, { steps })
|
||||
},
|
||||
]
|
||||
) =>
|
||||
[
|
||||
(clickParams?: mouseParams) => {
|
||||
if (clickParams?.pixelDiff) {
|
||||
return doAndWaitForImageDiff(
|
||||
this.page,
|
||||
() => this.page.mouse.click(x, y),
|
||||
clickParams.pixelDiff
|
||||
)
|
||||
}
|
||||
return this.page.mouse.click(x, y)
|
||||
},
|
||||
(moveParams?: mouseParams) => {
|
||||
if (moveParams?.pixelDiff) {
|
||||
return doAndWaitForImageDiff(
|
||||
this.page,
|
||||
() => this.page.mouse.move(x, y, { steps }),
|
||||
moveParams.pixelDiff
|
||||
)
|
||||
}
|
||||
return this.page.mouse.move(x, y, { steps })
|
||||
},
|
||||
] as const
|
||||
|
||||
/** Likely no where, there's a chance it will click something in the scene, depending what you have in the scene.
|
||||
*
|
||||
* Expects the viewPort to be 1000x500 */
|
||||
clickNoWhere = () => this.page.mouse.click(998, 60)
|
||||
/** Likely no where, there's a chance it will click something in the scene, depending what you have in the scene.
|
||||
*
|
||||
* Expects the viewPort to be 1000x500 */
|
||||
moveNoWhere = (steps?: number) => this.page.mouse.move(998, 60, { steps })
|
||||
|
||||
moveCameraTo = async (
|
||||
pos: { x: number; y: number; z: number },
|
79
e2e/playwright/fixtures/toolbarFixture.ts
Normal file
@ -0,0 +1,79 @@
|
||||
import type { Page, Locator } from '@playwright/test'
|
||||
import { expect } from './fixtureSetup'
|
||||
import { doAndWaitForImageDiff } from '../test-utils'
|
||||
|
||||
export class ToolbarFixture {
|
||||
public page: Page
|
||||
|
||||
extrudeButton!: Locator
|
||||
startSketchBtn!: Locator
|
||||
rectangleBtn!: Locator
|
||||
exitSketchBtn!: Locator
|
||||
editSketchBtn!: Locator
|
||||
fileTreeBtn!: Locator
|
||||
createFileBtn!: Locator
|
||||
fileCreateToast!: Locator
|
||||
filePane!: Locator
|
||||
exeIndicator!: Locator
|
||||
|
||||
constructor(page: Page) {
|
||||
this.page = page
|
||||
this.reConstruct(page)
|
||||
}
|
||||
reConstruct = (page: Page) => {
|
||||
this.page = page
|
||||
this.extrudeButton = page.getByTestId('extrude')
|
||||
this.startSketchBtn = page.getByTestId('sketch')
|
||||
this.rectangleBtn = page.getByTestId('corner-rectangle')
|
||||
this.exitSketchBtn = page.getByTestId('sketch-exit')
|
||||
this.editSketchBtn = page.getByText('Edit Sketch')
|
||||
this.fileTreeBtn = page.locator('[id="files-button-holder"]')
|
||||
this.createFileBtn = page.getByTestId('create-file-button')
|
||||
|
||||
this.filePane = page.locator('#files-pane')
|
||||
this.fileCreateToast = page.getByText('Successfully created')
|
||||
this.exeIndicator = page.getByTestId('model-state-indicator-execution-done')
|
||||
}
|
||||
|
||||
startSketchPlaneSelection = async () =>
|
||||
doAndWaitForImageDiff(this.page, () => this.startSketchBtn.click(), 500)
|
||||
|
||||
editSketch = async () => {
|
||||
await this.editSketchBtn.first().click()
|
||||
// One of the rare times we want to allow a arbitrary wait
|
||||
// this is for the engine animation, as it takes 500ms to complete
|
||||
await this.page.waitForTimeout(600)
|
||||
}
|
||||
|
||||
private _serialiseFileTree = async () => {
|
||||
return this.page
|
||||
.locator('#files-pane')
|
||||
.getByTestId('file-tree-item')
|
||||
.allInnerTexts()
|
||||
}
|
||||
/**
|
||||
* TODO folders, in expect state
|
||||
*/
|
||||
expectFileTreeState = async (expected: string[]) => {
|
||||
await expect.poll(this._serialiseFileTree).toEqual(expected)
|
||||
}
|
||||
createFile = async ({ wait }: { wait: boolean } = { wait: false }) => {
|
||||
await this.createFileBtn.click()
|
||||
await expect(this.fileCreateToast).toBeVisible()
|
||||
if (wait) {
|
||||
await this.fileCreateToast.waitFor({ state: 'detached' })
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Opens file by it's name and waits for execution to finish
|
||||
*/
|
||||
openFile = async (
|
||||
fileName: string,
|
||||
{ wait }: { wait?: boolean } = { wait: true }
|
||||
) => {
|
||||
await this.filePane.getByText(fileName).click()
|
||||
if (wait) {
|
||||
await expect(this.exeIndicator).toBeVisible({ timeout: 15_000 })
|
||||
}
|
||||
}
|
||||
}
|
7
e2e/playwright/playwright-deprecated.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import { test, expect } from '@playwright/test'
|
||||
|
||||
/** @deprecated, import from ./fixtureSetup.ts instead */
|
||||
export const _test = test
|
||||
|
||||
/** @deprecated, import from ./fixtureSetup.ts instead */
|
||||
export const _expect = expect
|
@ -1,7 +1,7 @@
|
||||
import { test, expect, AuthenticatedApp } from './fixtureSetup'
|
||||
import { EditorFixture } from './editorFixture'
|
||||
import { SceneFixture } from './sceneFixture'
|
||||
import { ToolbarFixture } from './toolbarFixture'
|
||||
import { test, expect, AuthenticatedApp } from './fixtures/fixtureSetup'
|
||||
import { EditorFixture } from './fixtures/editorFixture'
|
||||
import { SceneFixture } from './fixtures/sceneFixture'
|
||||
import { ToolbarFixture } from './fixtures/toolbarFixture'
|
||||
|
||||
// test file is for testing point an click code gen functionality that's not sketch mode related
|
||||
|
||||
|
@ -455,6 +455,7 @@ test(
|
||||
await page.mouse.move(startXPx + PUR * 20, 500 - PUR * 10)
|
||||
await expect(page).toHaveScreenshot({
|
||||
maxDiffPixels: 100,
|
||||
mask: [page.getByTestId('model-state-indicator')],
|
||||
})
|
||||
|
||||
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10)
|
||||
@ -474,6 +475,7 @@ test(
|
||||
|
||||
await expect(page).toHaveScreenshot({
|
||||
maxDiffPixels: 100,
|
||||
mask: [page.getByTestId('model-state-indicator')],
|
||||
})
|
||||
}
|
||||
)
|
||||
@ -531,6 +533,7 @@ test(
|
||||
// Ensure the draft rectangle looks the same as it usually does
|
||||
await expect(page).toHaveScreenshot({
|
||||
maxDiffPixels: 100,
|
||||
mask: [page.getByTestId('model-state-indicator')],
|
||||
})
|
||||
}
|
||||
)
|
||||
@ -585,6 +588,7 @@ test(
|
||||
// Ensure the draft rectangle looks the same as it usually does
|
||||
await expect(page).toHaveScreenshot({
|
||||
maxDiffPixels: 100,
|
||||
mask: [page.getByTestId('model-state-indicator')],
|
||||
})
|
||||
await expect(page.locator('.cm-content')).toHaveText(
|
||||
`const sketch001 = startSketchOn('XZ')
|
||||
|
Before Width: | Height: | Size: 53 KiB After Width: | Height: | Size: 53 KiB |
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 50 KiB |
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 58 KiB |
Before Width: | Height: | Size: 51 KiB After Width: | Height: | Size: 51 KiB |
Before Width: | Height: | Size: 45 KiB After Width: | Height: | Size: 45 KiB |
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 42 KiB |
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 44 KiB |
Before Width: | Height: | Size: 41 KiB After Width: | Height: | Size: 41 KiB |
Before Width: | Height: | Size: 41 KiB After Width: | Height: | Size: 41 KiB |
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 38 KiB |
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 49 KiB |
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 47 KiB |
@ -1066,3 +1066,7 @@ export async function openAndClearDebugPanel(page: Page) {
|
||||
await openDebugPanel(page)
|
||||
return clearCommandLogs(page)
|
||||
}
|
||||
|
||||
export function sansWhitespace(str: string) {
|
||||
return str.replace(/\s+/g, '').trim()
|
||||
}
|
||||
|
@ -1,21 +0,0 @@
|
||||
import type { Page, Locator } from '@playwright/test'
|
||||
import { doAndWaitForImageDiff } from './test-utils'
|
||||
|
||||
export class ToolbarFixture {
|
||||
public readonly page: Page
|
||||
readonly extrudeButton: Locator
|
||||
readonly startSketchBtn: Locator
|
||||
readonly rectangleBtn: Locator
|
||||
readonly exitSketchBtn: Locator
|
||||
|
||||
constructor(page: Page) {
|
||||
this.page = page
|
||||
this.extrudeButton = page.getByTestId('extrude')
|
||||
this.startSketchBtn = page.getByTestId('sketch')
|
||||
this.rectangleBtn = page.getByTestId('corner-rectangle')
|
||||
this.exitSketchBtn = page.getByTestId('sketch-exit')
|
||||
}
|
||||
|
||||
startSketchPlaneSelection = async () =>
|
||||
doAndWaitForImageDiff(this.page, () => this.startSketchBtn.click(), 500)
|
||||
}
|
@ -306,9 +306,19 @@ export class CameraControls {
|
||||
event: 'camera_drag_end',
|
||||
callback: cb,
|
||||
})
|
||||
this.engineCommandManager.subscribeToUnreliable({
|
||||
event: 'default_camera_zoom',
|
||||
callback: (a) => {
|
||||
console.log('zoom', a)
|
||||
cb(a)
|
||||
},
|
||||
})
|
||||
this.engineCommandManager.subscribeTo({
|
||||
event: 'default_camera_zoom',
|
||||
callback: cb,
|
||||
callback: (a) => {
|
||||
console.log('zoom', a)
|
||||
cb(a)
|
||||
},
|
||||
})
|
||||
this.engineCommandManager.subscribeTo({
|
||||
event: 'default_camera_get_settings',
|
||||
|
@ -194,7 +194,7 @@ const FileTreeItem = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="contents" ref={itemRef}>
|
||||
<div className="contents" data-testid="file-tree-item" ref={itemRef}>
|
||||
{fileOrDir.children === null ? (
|
||||
<li
|
||||
className={
|
||||
@ -389,12 +389,14 @@ interface FileTreeProps {
|
||||
|
||||
export const FileTreeMenu = () => {
|
||||
const { send } = useFileContext()
|
||||
const { send: modelingSend } = useModelingContext()
|
||||
|
||||
function createFile() {
|
||||
send({
|
||||
type: 'Create file',
|
||||
data: { name: '', makeDir: false, shouldSetToRename: true },
|
||||
})
|
||||
modelingSend({ type: 'Cancel' })
|
||||
}
|
||||
|
||||
function createFolder() {
|
||||
|
@ -104,20 +104,33 @@ function ProjectCard({
|
||||
ref={inputRef}
|
||||
/>
|
||||
) : (
|
||||
<h3 className="font-sans relative z-0 p-2">
|
||||
<h3
|
||||
className="font-sans relative z-0 p-2"
|
||||
data-testid="project-title"
|
||||
>
|
||||
{project.name?.replace(FILE_EXT, '')}
|
||||
</h3>
|
||||
)}
|
||||
<span className="px-2 text-chalkboard-60 text-xs">
|
||||
{numberOfFiles} file{numberOfFiles === 1 ? '' : 's'}{' '}
|
||||
{numberOfFolders > 0 &&
|
||||
`/ ${numberOfFolders} folder${numberOfFolders === 1 ? '' : 's'}`}
|
||||
<span data-testid="project-file-count">{numberOfFiles}</span> file
|
||||
{numberOfFiles === 1 ? '' : 's'}{' '}
|
||||
{numberOfFolders > 0 && (
|
||||
<>
|
||||
{'/ '}
|
||||
<span data-testid="project-folder-count">
|
||||
{numberOfFolders}
|
||||
</span>{' '}
|
||||
folder{numberOfFolders === 1 ? '' : 's'}
|
||||
</>
|
||||
)}
|
||||
</span>
|
||||
<span className="px-2 text-chalkboard-60 text-xs">
|
||||
Edited{' '}
|
||||
{project.metadata && project.metadata.modified
|
||||
? getDisplayedTime(parseInt(project.metadata.modified))
|
||||
: 'never'}
|
||||
<span data-testid="project-edit-date">
|
||||
{project.metadata && project.metadata.modified
|
||||
? getDisplayedTime(parseInt(project.metadata.modified))
|
||||
: 'never'}
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</Link>
|
||||
|
@ -1230,7 +1230,7 @@ type ModelTypes = Models['OkModelingCmdResponse_type']['type']
|
||||
|
||||
type UnreliableResponses = Extract<
|
||||
Models['OkModelingCmdResponse_type'],
|
||||
{ type: 'highlight_set_entity' | 'camera_drag_move' }
|
||||
{ type: 'highlight_set_entity' | 'camera_drag_move' | 'default_camera_zoom' }
|
||||
>
|
||||
export interface UnreliableSubscription<T extends UnreliableResponses['type']> {
|
||||
event: T
|
||||
@ -1572,9 +1572,11 @@ export class EngineCommandManager extends EventTarget {
|
||||
'message',
|
||||
(event: MessageEvent) => {
|
||||
const result: UnreliableResponses = JSON.parse(event.data)
|
||||
console.log('result', result)
|
||||
Object.values(
|
||||
this.unreliableSubscriptions[result.type] || {}
|
||||
).forEach(
|
||||
|
||||
// TODO: There is only one response that uses the unreliable channel atm,
|
||||
// highlight_set_entity, if there are more it's likely they will all have the same
|
||||
// sequence logic, but I'm not sure if we use a single global sequence or a sequence
|
||||
@ -1933,6 +1935,9 @@ export class EngineCommandManager extends EventTarget {
|
||||
) {
|
||||
;(cmd as any).sequence = this.outSequence
|
||||
this.outSequence++
|
||||
if(cmd.type === 'default_camera_zoom') {
|
||||
console.log('sending zoom', cmd)
|
||||
}
|
||||
this.engineConnection?.unreliableSend(command)
|
||||
return Promise.resolve(null)
|
||||
} else if (
|
||||
|
@ -15,6 +15,30 @@ export function isArray(val: any): val is unknown[] {
|
||||
}
|
||||
|
||||
/**
|
||||
* An alternative to `Object.keys()` that returns an array of keys with types.
|
||||
*
|
||||
* It's UNSAFE because because of TS's structural subtyping and how at runtime, you can
|
||||
* extend a JS object with whatever keys you want.
|
||||
*
|
||||
* Why we shouldn't be extending objects with arbitrary keys at run time, the structural subtyping
|
||||
* issue could be a confusing bug, for example, in the below snippet `myKeys` is typed as
|
||||
* `('x' | 'y')[]` but is really `('x' | 'y' | 'name')[]`
|
||||
* ```ts
|
||||
* interface Point { x: number; y: number }
|
||||
* interface NamedPoint { x: number; y: number; name: string }
|
||||
*
|
||||
* let point: Point = { x: 1, y: 2 }
|
||||
* let namedPoint: NamedPoint = { x: 1, y: 2, name: 'A' }
|
||||
*
|
||||
* // Structural subtyping allows this assignment
|
||||
* point = namedPoint // This is allowed because NamedPoint has all properties of Point
|
||||
* const myKeys = unsafeTypedKeys(point) // typed as ('x' | 'y')[] but is really ('x' | 'y' | 'name')[]
|
||||
* ```
|
||||
*/
|
||||
export function unsafeTypedKeys<T extends object>(obj: T): Array<keyof T> {
|
||||
return Object.keys(obj) as Array<keyof T>
|
||||
}
|
||||
/*
|
||||
* Predicate that checks if a value is not null and not undefined. This is
|
||||
* useful for functions like Array::filter() and Array::find() that have
|
||||
* overloads that accept a type guard.
|
||||
|
@ -78,7 +78,7 @@ export const authMachine = setup({
|
||||
),
|
||||
},
|
||||
}).createMachine({
|
||||
/** @xstate-layout N4IgpgJg5mDOIC5QEECuAXAFgOgMabFwGsBJAMwBkB7KGCEgOwGIIqGxsBLBgNyqI75CRALQAbGnRHcA2gAYAuolAAHKrE7pObZSAAeiAIwBmAEzYA7ABYAbAFZTcgBzGbN44adWANCACeiKbGdthypk4AnBFyVs6uQXYAvom+aFh4BMTk1LSQjExgAE6FVIXYKmIAhuhkpQC2GcLikpDSDPJKSCBqGlo6XQYIrk7YETYWctYRxmMWFk6+AUPj2I5OdjZyrnZOFmbJqRg4Ern0zDkABFQYHbo9mtoMuoOGFhHYxlZOhvbOsUGGRaIL4WbBONzWQxWYwWOx2H4HEBpY4tCAAeQwTEuskUd3UD36oEGIlMNlCuzk8Js0TcVisgP8iG2lmcGysb0mW3ByRSIAYVAgcF0yLxvUez0QIms5ImVJpNjpDKWxmw9PGdLh4Te00+iORjSylFRjFFBKeA0QThGQWcexMwWhniBCGiqrepisUVMdlszgieqO2BOdBNXXufXNRKMHtGVuphlJkXs4Wdriso2CCasdgipOidID6WDkAx6FNEYlCAT5jmcjrckMdj2b3GzpsjbBMVMWezDbGPMSQA */
|
||||
/** @xstate-layout N4IgpgJg5mDOIC5QEECuAXAFgOgMabFwGsBJAMwBkB7KGCEgOwGIIqGxsBLBgNyqI75CRALQAbGnRHcA2gAYAuolAAHKrE7pObZSAAeiAIwAWQ9gBspuQCYAnAGYAHPYCsx+4ccAaEAE9E1q7YcoZyxrYR1m7mcrYAvnE+aFh4BMTk1LSQjExgAE55VHnYKmIAhuhkRQC2qcLikpDSDPJKSCBqGlo67QYI9gDs5tge5o6h5vau7oY+-v3mA9jWco4u5iu21ua2YcYJSRg4Eln0zJkABFQYrbqdmtoMun2GA7YjxuPmLqvGNh5zRCfJaOcyLUzuAYuFyGcwHEDJY6NCAAeQwTEuskUd3UDx6oD6Im2wUcAzkMJ2cjBxlMgIWLmwZLWljecjJTjh8IYVAgcF0iJxXUez0QIgGxhJZIpu2ptL8AWwtje1nCW2iq1shns8MRdXSlGRjEFeKevUQjkcy3sqwGHimbg83nlCF22GMytVUWMMUc8USCKO2BOdCN7Xu3VNBKMKsVFp2hm2vu+1id83slkVrgTxhcW0pNJ1geDkDR6GNEZFCAT1kZZLk9cMLltb0WdPMjewjjC1mzOZCtk5CSAA */
|
||||
id: 'Auth',
|
||||
initial: 'checkIfLoggedIn',
|
||||
context: {
|
||||
@ -162,15 +162,15 @@ async function getUser(input: { token?: string }) {
|
||||
}
|
||||
}
|
||||
|
||||
const userPromise = !isDesktop()
|
||||
? fetch(url, {
|
||||
const userPromise = isDesktop()
|
||||
? getUserDesktop(token, VITE_KC_API_BASE_URL)
|
||||
: fetch(url, {
|
||||
method: 'GET',
|
||||
credentials: 'include',
|
||||
headers,
|
||||
})
|
||||
.then((res) => res.json())
|
||||
.catch((err) => console.error('error from Browser getUser', err))
|
||||
: getUserDesktop(input.token ?? '', VITE_KC_API_BASE_URL)
|
||||
|
||||
const user = await userPromise
|
||||
|
||||
|
@ -248,6 +248,7 @@ const Home = () => {
|
||||
<small>Sort by</small>
|
||||
<ActionButton
|
||||
Element="button"
|
||||
data-testid="home-sort-by-name"
|
||||
className={
|
||||
'text-xs border-primary/10 ' +
|
||||
(!sort.includes('name')
|
||||
@ -269,6 +270,7 @@ const Home = () => {
|
||||
</ActionButton>
|
||||
<ActionButton
|
||||
Element="button"
|
||||
data-testid="home-sort-by-modified"
|
||||
className={
|
||||
'text-xs border-primary/10 ' +
|
||||
(!isSortByModified
|
||||
|
1271
src/wasm-lib/Cargo.lock
generated
@ -24,12 +24,11 @@ wasm-bindgen-futures = "0.4.42"
|
||||
|
||||
[dev-dependencies]
|
||||
anyhow = "1"
|
||||
hyper = { version = "0.14.29", features = ["server", "http1"] }
|
||||
image = { version = "0.25.1", default-features = false, features = ["png"] }
|
||||
kittycad = { workspace = true, default-features = true }
|
||||
kittycad-modeling-cmds = { workspace = true }
|
||||
pretty_assertions = "1.4.1"
|
||||
reqwest = { version = "0.11.26", default-features = false }
|
||||
reqwest = { version = "0.12", default-features = false }
|
||||
tokio = { version = "1.40.0", features = ["rt-multi-thread", "macros", "time"] }
|
||||
twenty-twenty = "0.8"
|
||||
uuid = { version = "1.10.0", features = ["v4", "js", "serde"] }
|
||||
@ -67,12 +66,12 @@ members = [
|
||||
"kcl",
|
||||
"kcl-macros",
|
||||
"kcl-test-server",
|
||||
"kcl-to-core",
|
||||
]
|
||||
|
||||
[workspace.dependencies]
|
||||
http = "0.2.12"
|
||||
kittycad = { version = "0.3.20", default-features = false, features = ["js", "requests"] }
|
||||
kittycad-modeling-session = "0.1.4"
|
||||
http = "1"
|
||||
kittycad = { version = "0.3.23", default-features = false, features = ["js", "requests"] }
|
||||
kittycad-modeling-cmds = { version = "0.2.64", features = ["websocket"] }
|
||||
|
||||
[[test]]
|
||||
|
@ -20,7 +20,7 @@ quote = "1"
|
||||
regex = "1.10"
|
||||
serde = { version = "1.0.210", features = ["derive"] }
|
||||
serde_tokenstream = "0.2"
|
||||
syn = { version = "2.0.77", features = ["full"] }
|
||||
syn = { version = "2.0.79", features = ["full"] }
|
||||
|
||||
[dev-dependencies]
|
||||
anyhow = "1.0.89"
|
||||
|
@ -758,7 +758,7 @@ fn generate_code_block_test(fn_name: &str, code_block: &str, index: usize) -> pr
|
||||
fs: std::sync::Arc::new(crate::fs::FileManager::new()),
|
||||
stdlib: std::sync::Arc::new(crate::std::StdLib::new()),
|
||||
settings: Default::default(),
|
||||
is_mock: true,
|
||||
context_type: crate::executor::ContextType::Mock,
|
||||
};
|
||||
|
||||
ctx.run(&program, None).await.unwrap();
|
||||
|
@ -14,7 +14,7 @@ mod test_examples_someFn {
|
||||
fs: std::sync::Arc::new(crate::fs::FileManager::new()),
|
||||
stdlib: std::sync::Arc::new(crate::std::StdLib::new()),
|
||||
settings: Default::default(),
|
||||
is_mock: true,
|
||||
context_type: crate::executor::ContextType::Mock,
|
||||
};
|
||||
ctx.run(&program, None).await.unwrap();
|
||||
}
|
||||
|
@ -14,7 +14,7 @@ mod test_examples_someFn {
|
||||
fs: std::sync::Arc::new(crate::fs::FileManager::new()),
|
||||
stdlib: std::sync::Arc::new(crate::std::StdLib::new()),
|
||||
settings: Default::default(),
|
||||
is_mock: true,
|
||||
context_type: crate::executor::ContextType::Mock,
|
||||
};
|
||||
ctx.run(&program, None).await.unwrap();
|
||||
}
|
||||
|
@ -14,7 +14,7 @@ mod test_examples_show {
|
||||
fs: std::sync::Arc::new(crate::fs::FileManager::new()),
|
||||
stdlib: std::sync::Arc::new(crate::std::StdLib::new()),
|
||||
settings: Default::default(),
|
||||
is_mock: true,
|
||||
context_type: crate::executor::ContextType::Mock,
|
||||
};
|
||||
ctx.run(&program, None).await.unwrap();
|
||||
}
|
||||
@ -47,7 +47,7 @@ mod test_examples_show {
|
||||
fs: std::sync::Arc::new(crate::fs::FileManager::new()),
|
||||
stdlib: std::sync::Arc::new(crate::std::StdLib::new()),
|
||||
settings: Default::default(),
|
||||
is_mock: true,
|
||||
context_type: crate::executor::ContextType::Mock,
|
||||
};
|
||||
ctx.run(&program, None).await.unwrap();
|
||||
}
|
||||
|
@ -14,7 +14,7 @@ mod test_examples_show {
|
||||
fs: std::sync::Arc::new(crate::fs::FileManager::new()),
|
||||
stdlib: std::sync::Arc::new(crate::std::StdLib::new()),
|
||||
settings: Default::default(),
|
||||
is_mock: true,
|
||||
context_type: crate::executor::ContextType::Mock,
|
||||
};
|
||||
ctx.run(&program, None).await.unwrap();
|
||||
}
|
||||
|
@ -15,7 +15,7 @@ mod test_examples_my_func {
|
||||
fs: std::sync::Arc::new(crate::fs::FileManager::new()),
|
||||
stdlib: std::sync::Arc::new(crate::std::StdLib::new()),
|
||||
settings: Default::default(),
|
||||
is_mock: true,
|
||||
context_type: crate::executor::ContextType::Mock,
|
||||
};
|
||||
ctx.run(&program, None).await.unwrap();
|
||||
}
|
||||
@ -48,7 +48,7 @@ mod test_examples_my_func {
|
||||
fs: std::sync::Arc::new(crate::fs::FileManager::new()),
|
||||
stdlib: std::sync::Arc::new(crate::std::StdLib::new()),
|
||||
settings: Default::default(),
|
||||
is_mock: true,
|
||||
context_type: crate::executor::ContextType::Mock,
|
||||
};
|
||||
ctx.run(&program, None).await.unwrap();
|
||||
}
|
||||
|
@ -15,7 +15,7 @@ mod test_examples_line_to {
|
||||
fs: std::sync::Arc::new(crate::fs::FileManager::new()),
|
||||
stdlib: std::sync::Arc::new(crate::std::StdLib::new()),
|
||||
settings: Default::default(),
|
||||
is_mock: true,
|
||||
context_type: crate::executor::ContextType::Mock,
|
||||
};
|
||||
ctx.run(&program, None).await.unwrap();
|
||||
}
|
||||
@ -48,7 +48,7 @@ mod test_examples_line_to {
|
||||
fs: std::sync::Arc::new(crate::fs::FileManager::new()),
|
||||
stdlib: std::sync::Arc::new(crate::std::StdLib::new()),
|
||||
settings: Default::default(),
|
||||
is_mock: true,
|
||||
context_type: crate::executor::ContextType::Mock,
|
||||
};
|
||||
ctx.run(&program, None).await.unwrap();
|
||||
}
|
||||
|
@ -14,7 +14,7 @@ mod test_examples_min {
|
||||
fs: std::sync::Arc::new(crate::fs::FileManager::new()),
|
||||
stdlib: std::sync::Arc::new(crate::std::StdLib::new()),
|
||||
settings: Default::default(),
|
||||
is_mock: true,
|
||||
context_type: crate::executor::ContextType::Mock,
|
||||
};
|
||||
ctx.run(&program, None).await.unwrap();
|
||||
}
|
||||
@ -47,7 +47,7 @@ mod test_examples_min {
|
||||
fs: std::sync::Arc::new(crate::fs::FileManager::new()),
|
||||
stdlib: std::sync::Arc::new(crate::std::StdLib::new()),
|
||||
settings: Default::default(),
|
||||
is_mock: true,
|
||||
context_type: crate::executor::ContextType::Mock,
|
||||
};
|
||||
ctx.run(&program, None).await.unwrap();
|
||||
}
|
||||
|
@ -14,7 +14,7 @@ mod test_examples_show {
|
||||
fs: std::sync::Arc::new(crate::fs::FileManager::new()),
|
||||
stdlib: std::sync::Arc::new(crate::std::StdLib::new()),
|
||||
settings: Default::default(),
|
||||
is_mock: true,
|
||||
context_type: crate::executor::ContextType::Mock,
|
||||
};
|
||||
ctx.run(&program, None).await.unwrap();
|
||||
}
|
||||
|
@ -14,7 +14,7 @@ mod test_examples_import {
|
||||
fs: std::sync::Arc::new(crate::fs::FileManager::new()),
|
||||
stdlib: std::sync::Arc::new(crate::std::StdLib::new()),
|
||||
settings: Default::default(),
|
||||
is_mock: true,
|
||||
context_type: crate::executor::ContextType::Mock,
|
||||
};
|
||||
ctx.run(&program, None).await.unwrap();
|
||||
}
|
||||
|
@ -14,7 +14,7 @@ mod test_examples_import {
|
||||
fs: std::sync::Arc::new(crate::fs::FileManager::new()),
|
||||
stdlib: std::sync::Arc::new(crate::std::StdLib::new()),
|
||||
settings: Default::default(),
|
||||
is_mock: true,
|
||||
context_type: crate::executor::ContextType::Mock,
|
||||
};
|
||||
ctx.run(&program, None).await.unwrap();
|
||||
}
|
||||
|
@ -14,7 +14,7 @@ mod test_examples_import {
|
||||
fs: std::sync::Arc::new(crate::fs::FileManager::new()),
|
||||
stdlib: std::sync::Arc::new(crate::std::StdLib::new()),
|
||||
settings: Default::default(),
|
||||
is_mock: true,
|
||||
context_type: crate::executor::ContextType::Mock,
|
||||
};
|
||||
ctx.run(&program, None).await.unwrap();
|
||||
}
|
||||
|
@ -14,7 +14,7 @@ mod test_examples_show {
|
||||
fs: std::sync::Arc::new(crate::fs::FileManager::new()),
|
||||
stdlib: std::sync::Arc::new(crate::std::StdLib::new()),
|
||||
settings: Default::default(),
|
||||
is_mock: true,
|
||||
context_type: crate::executor::ContextType::Mock,
|
||||
};
|
||||
ctx.run(&program, None).await.unwrap();
|
||||
}
|
||||
|
@ -14,7 +14,7 @@ mod test_examples_some_function {
|
||||
fs: std::sync::Arc::new(crate::fs::FileManager::new()),
|
||||
stdlib: std::sync::Arc::new(crate::std::StdLib::new()),
|
||||
settings: Default::default(),
|
||||
is_mock: true,
|
||||
context_type: crate::executor::ContextType::Mock,
|
||||
};
|
||||
ctx.run(&program, None).await.unwrap();
|
||||
}
|
||||
|
@ -15,7 +15,7 @@ databake = "0.1.8"
|
||||
kcl-lib = { path = "../kcl" }
|
||||
proc-macro2 = "1"
|
||||
quote = "1"
|
||||
syn = { version = "2.0.77", features = ["full"] }
|
||||
syn = { version = "2.0.79", features = ["full"] }
|
||||
|
||||
[dev-dependencies]
|
||||
pretty_assertions = "1.4.1"
|
||||
|
@ -7,7 +7,7 @@ license = "MIT"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.89"
|
||||
hyper = { version = "0.14.29", features = ["server"] }
|
||||
hyper = { version = "0.14.29", features = ["http1", "server", "tcp"] }
|
||||
kcl-lib = { version = "0.2", path = "../kcl" }
|
||||
pico-args = "0.5.0"
|
||||
serde = { version = "1.0.210", features = ["derive"] }
|
||||
|
23
src/wasm-lib/kcl-to-core/Cargo.toml
Normal file
@ -0,0 +1,23 @@
|
||||
[package]
|
||||
name = "kcl-to-core"
|
||||
description = "Utility methods to convert kcl to engine core executable tests"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
repository = "https://github.com/KittyCAD/modeling-app"
|
||||
|
||||
[lib]
|
||||
|
||||
[[bin]]
|
||||
name = "kcl-to-core"
|
||||
path = "src/tool.rs"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1"
|
||||
async-trait = "0.1.81"
|
||||
indexmap = "2.5.0"
|
||||
kcl-lib = { path = "../kcl" }
|
||||
kittycad = { workspace = true, features = ["clap"] }
|
||||
kittycad-modeling-cmds = { workspace = true }
|
||||
tokio = { version = "1.38", features = ["full", "time", "rt", "tracing"] }
|
||||
uuid = { version = "1.9.1", features = ["v4", "js", "serde"] }
|
469
src/wasm-lib/kcl-to-core/src/conn_mock_core.rs
Normal file
@ -0,0 +1,469 @@
|
||||
use anyhow::Result;
|
||||
use indexmap::IndexMap;
|
||||
use kcl_lib::{errors::KclError, executor::DefaultPlanes};
|
||||
use kittycad_modeling_cmds::{
|
||||
self as kcmc,
|
||||
id::ModelingCmdId,
|
||||
ok_response::OkModelingCmdResponse,
|
||||
shared::PathSegment::{self, *},
|
||||
websocket::{ModelingBatch, ModelingCmdReq, OkWebSocketResponseData, WebSocketRequest, WebSocketResponse},
|
||||
};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
const CPP_PREFIX: &str = "const double scaleFactor = 100;\n";
|
||||
const NEED_PLANES: bool = true;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct EngineConnection {
|
||||
batch: Arc<Mutex<Vec<(WebSocketRequest, kcl_lib::executor::SourceRange)>>>,
|
||||
batch_end: Arc<Mutex<IndexMap<uuid::Uuid, (WebSocketRequest, kcl_lib::executor::SourceRange)>>>,
|
||||
core_test: Arc<Mutex<String>>,
|
||||
default_planes: Arc<RwLock<Option<DefaultPlanes>>>,
|
||||
}
|
||||
|
||||
impl EngineConnection {
|
||||
pub async fn new(result: Arc<Mutex<String>>) -> Result<EngineConnection> {
|
||||
if let Ok(mut code) = result.lock() {
|
||||
code.push_str(CPP_PREFIX);
|
||||
}
|
||||
|
||||
Ok(EngineConnection {
|
||||
batch: Arc::new(Mutex::new(Vec::new())),
|
||||
batch_end: Arc::new(Mutex::new(IndexMap::new())),
|
||||
core_test: result,
|
||||
default_planes: Default::default(),
|
||||
})
|
||||
}
|
||||
|
||||
fn handle_command(&self, cmd_id: &ModelingCmdId, cmd: &kcmc::ModelingCmd) -> (String, OkModelingCmdResponse) {
|
||||
let cpp_id = id_to_cpp(cmd_id);
|
||||
let cmd_id = format!("{}", cmd_id);
|
||||
let mut this_response = OkModelingCmdResponse::Empty {};
|
||||
|
||||
let new_code = match cmd {
|
||||
kcmc::ModelingCmd::ObjectVisible(kcmc::ObjectVisible { hidden, object_id }) => {
|
||||
format!(r#"scene->getSceneObject(Utils::UUID("{object_id}"))->setHidden({hidden});"#)
|
||||
}
|
||||
kcmc::ModelingCmd::EnableSketchMode(kcmc::EnableSketchMode {
|
||||
entity_id,
|
||||
animated: _,
|
||||
ortho: _,
|
||||
adjust_camera: _,
|
||||
planar_normal,
|
||||
}) => {
|
||||
if let Some(normal) = planar_normal {
|
||||
format!(
|
||||
r#"
|
||||
if(!scene->enableSketchMode(Utils::UUID("{entity_id}"), glm::dvec3 {{ {}, {}, {}, }}, nullopt))
|
||||
{{
|
||||
Utils::Plane plane_{cpp_id}(glm::dvec3 {{ 0, 0, 0 }}, glm::dvec3 {{ 1, 0, 0 }}, glm::dvec3 {{ 0, 1, 0 }});
|
||||
scene->enableSketchMode(plane_{cpp_id}, nullopt, nullopt, false);
|
||||
}}
|
||||
"#,
|
||||
normal.x, normal.y, normal.z
|
||||
)
|
||||
} else {
|
||||
"".into()
|
||||
}
|
||||
}
|
||||
kcmc::ModelingCmd::SketchModeDisable(kcmc::SketchModeDisable {}) => "scene->disableSketchMode();".into(),
|
||||
kcmc::ModelingCmd::MakePlane(kcmc::MakePlane {
|
||||
origin,
|
||||
x_axis,
|
||||
y_axis,
|
||||
size,
|
||||
..
|
||||
}) => {
|
||||
let plane_id = format!("plane_{}", cpp_id);
|
||||
format!(
|
||||
r#"
|
||||
auto {plane_id} = make_shared<Object>("plane", glm::dvec3 {{ 0, 0, 0 }});
|
||||
{plane_id}->setUUID(Utils::UUID("{cmd_id}"));
|
||||
{plane_id}->makePlane(glm::dvec3 {{ {}, {}, {} }} * scaleFactor, glm::dvec3 {{ {}, {}, {} }}, glm::dvec3 {{ {}, {}, {} }}, {}, false);
|
||||
{plane_id}->setHidden();
|
||||
scene->addSceneObject({plane_id});
|
||||
"#,
|
||||
origin.x.0,
|
||||
origin.y.0,
|
||||
origin.z.0,
|
||||
x_axis.x,
|
||||
x_axis.y,
|
||||
x_axis.z,
|
||||
y_axis.x,
|
||||
y_axis.y,
|
||||
y_axis.z,
|
||||
size.0
|
||||
)
|
||||
}
|
||||
kcmc::ModelingCmd::StartPath(kcmc::StartPath {}) => {
|
||||
let sketch_id = format!("sketch_{}", cpp_id);
|
||||
let path_id = format!("path_{}", cpp_id);
|
||||
format!(
|
||||
r#"
|
||||
auto {sketch_id} = make_shared<Object>("sketch", glm::dvec3 {{ 0, 0, 0 }});
|
||||
{sketch_id}->setUUID(Utils::UUID("{cmd_id}"));
|
||||
{sketch_id}->makePath(true);
|
||||
auto {path_id} = {sketch_id}->get<Model::Brep::Path>();
|
||||
scene->addSceneObject({sketch_id});
|
||||
"#
|
||||
)
|
||||
}
|
||||
kcmc::ModelingCmd::MovePathPen(kcmc::MovePathPen { path, to }) => {
|
||||
format!(
|
||||
r#"
|
||||
path_{}->moveTo(glm::dvec3 {{ {}, {}, 0.0 }} * scaleFactor);
|
||||
"#,
|
||||
id_to_cpp(path),
|
||||
to.x.0,
|
||||
to.y.0
|
||||
)
|
||||
}
|
||||
kcmc::ModelingCmd::ExtendPath(kcmc::ExtendPath { path, segment }) => match segment {
|
||||
Line { end, relative } => {
|
||||
format!(
|
||||
r#"
|
||||
path_{}->lineTo(glm::dvec3 {{ {}, {}, 0.0 }} * scaleFactor, {{ {} }});
|
||||
"#,
|
||||
id_to_cpp(path),
|
||||
end.x.0,
|
||||
end.y.0,
|
||||
relative
|
||||
)
|
||||
}
|
||||
PathSegment::Arc {
|
||||
center,
|
||||
radius,
|
||||
start,
|
||||
end,
|
||||
relative,
|
||||
} => {
|
||||
let start = start.value;
|
||||
let end = end.value;
|
||||
let radius = radius.0;
|
||||
|
||||
format!(
|
||||
r#"
|
||||
path_{}->addArc(glm::dvec2 {{ {}, {} }} * scaleFactor, {radius} * scaleFactor, {start}, {end}, {{ {} }});
|
||||
"#,
|
||||
id_to_cpp(path),
|
||||
center.x.0,
|
||||
center.y.0,
|
||||
relative
|
||||
)
|
||||
}
|
||||
PathSegment::TangentialArcTo {
|
||||
angle_snap_increment: _,
|
||||
to,
|
||||
} => {
|
||||
format!(
|
||||
r#"
|
||||
path_{}->tangentialArcTo(glm::dvec3 {{ {}, {}, {} }} * scaleFactor, nullopt, {{ true }});
|
||||
"#,
|
||||
id_to_cpp(path),
|
||||
to.x.0,
|
||||
to.y.0,
|
||||
to.z.0,
|
||||
)
|
||||
}
|
||||
_ => {
|
||||
format!("//{:?}", cmd)
|
||||
}
|
||||
},
|
||||
kcmc::ModelingCmd::ClosePath(kcmc::ClosePath { path_id }) => {
|
||||
format!(
|
||||
r#"
|
||||
path_{}->close();
|
||||
sketch_{}->toSolid2D();
|
||||
"#,
|
||||
uuid_to_cpp(path_id),
|
||||
uuid_to_cpp(path_id)
|
||||
)
|
||||
}
|
||||
kcmc::ModelingCmd::Extrude(kcmc::Extrude { distance, target }) => {
|
||||
format!(
|
||||
r#"
|
||||
scene->getSceneObject(Utils::UUID("{target}"))->extrudeToSolid3D({} * scaleFactor, true);
|
||||
"#,
|
||||
distance.0
|
||||
)
|
||||
}
|
||||
kcmc::ModelingCmd::Revolve(kcmc::Revolve {
|
||||
angle,
|
||||
axis,
|
||||
axis_is_2d,
|
||||
origin,
|
||||
target,
|
||||
tolerance,
|
||||
}) => {
|
||||
let ox = origin.x.0;
|
||||
let oy = origin.y.0;
|
||||
let oz = origin.z.0;
|
||||
let ax = axis.x;
|
||||
let ay = axis.y;
|
||||
let az = axis.z;
|
||||
let angle = angle.value;
|
||||
let tolerance = tolerance.0;
|
||||
format!(
|
||||
r#"
|
||||
scene->getSceneObject(Utils::UUID("{target}"))->revolveToSolid3D(nullopt, glm::dvec3 {{ {ox}, {oy}, {oz} }} * scaleFactor, glm::dvec3 {{ {ax}, {ay}, {az} }}, {axis_is_2d}, {angle}, {tolerance});
|
||||
"#
|
||||
)
|
||||
}
|
||||
kcmc::ModelingCmd::Solid2dAddHole(kcmc::Solid2dAddHole { hole_id, object_id }) => {
|
||||
format!(
|
||||
r#"scene->getSceneObject(Utils::UUID("{object_id}"))->get<Model::Brep::Solid2D>()->addHole(
|
||||
make_shared<Model::Brep::Path>(*scene->getSceneObject(Utils::UUID("{hole_id}"))->get<Model::Brep::Solid2D>()->getPath())
|
||||
);"#
|
||||
)
|
||||
}
|
||||
kcmc::ModelingCmd::Solid3dGetExtrusionFaceInfo(kcmc::Solid3dGetExtrusionFaceInfo {
|
||||
object_id,
|
||||
edge_id,
|
||||
}) => {
|
||||
format!(
|
||||
r#"
|
||||
//face info get {} {}
|
||||
"#,
|
||||
object_id, edge_id
|
||||
)
|
||||
}
|
||||
kcmc::ModelingCmd::EntityCircularPattern(kcmc::EntityCircularPattern {
|
||||
entity_id,
|
||||
axis,
|
||||
center,
|
||||
num_repetitions,
|
||||
arc_degrees,
|
||||
rotate_duplicates,
|
||||
}) => {
|
||||
let entity_ids = generate_repl_uuids(*num_repetitions as usize);
|
||||
|
||||
this_response = OkModelingCmdResponse::EntityCircularPattern(kcmc::output::EntityCircularPattern {
|
||||
entity_ids: entity_ids.clone(),
|
||||
});
|
||||
|
||||
let mut base_code: String = format!(
|
||||
r#"
|
||||
auto reps_{cpp_id} = scene->entityCircularPattern(Utils::UUID("{}"), {num_repetitions}, glm::dvec3 {{ {}, {}, {} }} * scaleFactor, glm::dvec3 {{ {}, {}, {} }} * scaleFactor, {arc_degrees}, {rotate_duplicates});
|
||||
"#,
|
||||
entity_id, axis.x, axis.y, axis.z, center.x.0, center.y.0, center.z.0
|
||||
);
|
||||
|
||||
let repl_uuid_fix_code = codegen_cpp_repl_uuid_setters(&cpp_id, &entity_ids);
|
||||
base_code.push_str(&repl_uuid_fix_code);
|
||||
|
||||
base_code
|
||||
}
|
||||
kcmc::ModelingCmd::EntityLinearPattern(kcmc::EntityLinearPattern {
|
||||
entity_id: _,
|
||||
axis: _,
|
||||
num_repetitions: _,
|
||||
spacing: _,
|
||||
}) => {
|
||||
// let num_transforms = transforms.len();
|
||||
// let num_repetitions = transform.iter().map(|t| if t.replicate { 1 } else { 0 } ).sum();
|
||||
|
||||
// let mut base_code: String = format!(
|
||||
// r#"
|
||||
// std::vector<std::optional<Scene::Scene::LinearPatternTransform>> transforms_{cpp_id}({num_transforms});
|
||||
// "#);
|
||||
|
||||
// for t in transform {
|
||||
// translations_xyz.push(t.translate.x.to_millimeters(state.units));
|
||||
// translations_xyz.push(t.translate.y.to_millimeters(state.units));
|
||||
// translations_xyz.push(t.translate.z.to_millimeters(state.units));
|
||||
// scale_xyz.push(t.scale.x);
|
||||
// scale_xyz.push(t.scale.y);
|
||||
// scale_xyz.push(t.scale.z);
|
||||
// }
|
||||
|
||||
// let entity_ids = generate_repl_uuids(*num_repetitions as usize);
|
||||
|
||||
// this_response = OkModelingCmdResponse::EntityLinearPattern {
|
||||
// data: kittycad::types::EntityLinearPattern {
|
||||
// entity_ids: entity_ids.clone(),
|
||||
// },
|
||||
// };
|
||||
|
||||
// let mut base_code: String = format!(
|
||||
// r#"
|
||||
// auto reps_{cpp_id} = scene->entityCircularPattern(Utils::UUID("{}"), {num_repetitions}, glm::dvec3 {{ {}, {}, {} }} * scaleFactor, glm::dvec3 {{ {}, {}, {} }} * scaleFactor, {arc_degrees}, {rotate_duplicates});
|
||||
// "#,
|
||||
// entity_id, axis.x, axis.y, axis.z, center.x, center.y, center.z
|
||||
// );
|
||||
|
||||
// let repl_uuid_fix_code = codegen_cpp_repl_uuid_setters(&cpp_id, &entity_ids);
|
||||
// base_code.push_str(&repl_uuid_fix_code);
|
||||
|
||||
// base_code
|
||||
format!("//{:?}", cmd)
|
||||
}
|
||||
_ => {
|
||||
//helps us follow along with the currently unhandled engine commands
|
||||
format!("//{:?}", cmd)
|
||||
}
|
||||
};
|
||||
|
||||
(new_code, this_response)
|
||||
}
|
||||
}
|
||||
|
||||
fn id_to_cpp(id: &ModelingCmdId) -> String {
|
||||
uuid_to_cpp(&id.0)
|
||||
}
|
||||
|
||||
fn uuid_to_cpp(id: &uuid::Uuid) -> String {
|
||||
let str = format!("{}", id);
|
||||
str::replace(&str, "-", "_")
|
||||
}
|
||||
|
||||
fn generate_repl_uuids(count: usize) -> Vec<uuid::Uuid> {
|
||||
let mut repl_ids: Vec<uuid::Uuid> = Vec::new();
|
||||
|
||||
repl_ids.resize_with(count, uuid::Uuid::new_v4);
|
||||
repl_ids
|
||||
}
|
||||
|
||||
fn codegen_cpp_repl_uuid_setters(reps_id: &str, entity_ids: &[uuid::Uuid]) -> String {
|
||||
let mut codegen = String::new();
|
||||
|
||||
for (i, id) in entity_ids.iter().enumerate() {
|
||||
let cpp_id = uuid_to_cpp(id);
|
||||
let iter = format!(
|
||||
r#"
|
||||
//change object id -> {id}
|
||||
auto repl_{cpp_id} = scene->getSceneObject(reps_{reps_id}[{i}]);
|
||||
scene->removeSceneObject(repl_{cpp_id}->getUUID(), false);
|
||||
repl_{cpp_id}->setUUID(Utils::UUID("{id}"));
|
||||
scene->addSceneObject(repl_{cpp_id});
|
||||
"#
|
||||
);
|
||||
codegen.push_str(&iter);
|
||||
}
|
||||
|
||||
codegen
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl kcl_lib::engine::EngineManager for EngineConnection {
|
||||
fn batch(&self) -> Arc<Mutex<Vec<(WebSocketRequest, kcl_lib::executor::SourceRange)>>> {
|
||||
self.batch.clone()
|
||||
}
|
||||
|
||||
fn batch_end(&self) -> Arc<Mutex<IndexMap<uuid::Uuid, (WebSocketRequest, kcl_lib::executor::SourceRange)>>> {
|
||||
self.batch_end.clone()
|
||||
}
|
||||
|
||||
async fn default_planes(&self, source_range: kcl_lib::executor::SourceRange) -> Result<DefaultPlanes, KclError> {
|
||||
if NEED_PLANES {
|
||||
{
|
||||
let opt = self.default_planes.read().await.as_ref().cloned();
|
||||
if let Some(planes) = opt {
|
||||
return Ok(planes);
|
||||
}
|
||||
} // drop the read lock
|
||||
|
||||
let new_planes = self.new_default_planes(source_range).await?;
|
||||
*self.default_planes.write().await = Some(new_planes.clone());
|
||||
|
||||
Ok(new_planes)
|
||||
} else {
|
||||
Ok(DefaultPlanes::default())
|
||||
}
|
||||
}
|
||||
|
||||
async fn clear_scene_post_hook(&self, _source_range: kcl_lib::executor::SourceRange) -> Result<(), KclError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn inner_send_modeling_cmd(
|
||||
&self,
|
||||
id: uuid::Uuid,
|
||||
_source_range: kcl_lib::executor::SourceRange,
|
||||
cmd: WebSocketRequest,
|
||||
_id_to_source_range: std::collections::HashMap<uuid::Uuid, kcl_lib::executor::SourceRange>,
|
||||
) -> Result<WebSocketResponse, KclError> {
|
||||
match cmd {
|
||||
WebSocketRequest::ModelingCmdBatchReq(ModelingBatch {
|
||||
ref requests,
|
||||
batch_id: _,
|
||||
responses: _,
|
||||
}) => {
|
||||
let mut responses = HashMap::new();
|
||||
for request in requests {
|
||||
let (new_code, this_response);
|
||||
|
||||
if let Ok(mut test_code) = self.core_test.lock() {
|
||||
(new_code, this_response) = self.handle_command(&request.cmd_id, &request.cmd);
|
||||
|
||||
if !new_code.is_empty() {
|
||||
let new_code = new_code
|
||||
.trim()
|
||||
.split(' ')
|
||||
.filter(|s| !s.is_empty())
|
||||
.collect::<Vec<_>>()
|
||||
.join(" ")
|
||||
+ "\n";
|
||||
//println!("{new_code}");
|
||||
test_code.push_str(&new_code);
|
||||
}
|
||||
} else {
|
||||
this_response = OkModelingCmdResponse::Empty {};
|
||||
}
|
||||
|
||||
responses.insert(
|
||||
request.cmd_id,
|
||||
kcmc::websocket::BatchResponse::Success {
|
||||
response: this_response,
|
||||
},
|
||||
);
|
||||
}
|
||||
Ok(WebSocketResponse::Success(kcmc::websocket::SuccessWebSocketResponse {
|
||||
success: true,
|
||||
request_id: Some(id),
|
||||
resp: OkWebSocketResponseData::ModelingBatch { responses },
|
||||
}))
|
||||
}
|
||||
WebSocketRequest::ModelingCmdReq(ModelingCmdReq { cmd, cmd_id }) => {
|
||||
//also handle unbatched requests inline
|
||||
let (new_code, this_response);
|
||||
|
||||
if let Ok(mut test_code) = self.core_test.lock() {
|
||||
(new_code, this_response) = self.handle_command(&cmd_id, &cmd);
|
||||
|
||||
if !new_code.is_empty() {
|
||||
let new_code = new_code
|
||||
.trim()
|
||||
.split(' ')
|
||||
.filter(|s| !s.is_empty())
|
||||
.collect::<Vec<_>>()
|
||||
.join(" ")
|
||||
+ "\n";
|
||||
//println!("{new_code}");
|
||||
test_code.push_str(&new_code);
|
||||
}
|
||||
} else {
|
||||
this_response = OkModelingCmdResponse::Empty {};
|
||||
}
|
||||
|
||||
Ok(WebSocketResponse::Success(kcmc::websocket::SuccessWebSocketResponse {
|
||||
success: true,
|
||||
request_id: Some(id),
|
||||
resp: OkWebSocketResponseData::Modeling {
|
||||
modeling_response: this_response,
|
||||
},
|
||||
}))
|
||||
}
|
||||
_ => Ok(WebSocketResponse::Success(kcmc::websocket::SuccessWebSocketResponse {
|
||||
success: true,
|
||||
request_id: Some(id),
|
||||
resp: OkWebSocketResponseData::Modeling {
|
||||
modeling_response: OkModelingCmdResponse::Empty {},
|
||||
},
|
||||
})),
|
||||
}
|
||||
}
|
||||
}
|
30
src/wasm-lib/kcl-to-core/src/lib.rs
Normal file
@ -0,0 +1,30 @@
|
||||
use anyhow::Result;
|
||||
use kcl_lib::executor::ExecutorContext;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
mod conn_mock_core;
|
||||
|
||||
///Converts the given kcl code to an engine test
|
||||
pub async fn kcl_to_engine_core(code: &str) -> Result<String> {
|
||||
let tokens = kcl_lib::token::lexer(code)?;
|
||||
let parser = kcl_lib::parser::Parser::new(tokens);
|
||||
let program = parser.ast()?;
|
||||
|
||||
let result = Arc::new(Mutex::new("".into()));
|
||||
let ref_result = Arc::clone(&result);
|
||||
|
||||
let ctx = ExecutorContext {
|
||||
engine: Arc::new(Box::new(
|
||||
crate::conn_mock_core::EngineConnection::new(ref_result).await?,
|
||||
)),
|
||||
fs: Arc::new(kcl_lib::fs::FileManager::new()),
|
||||
stdlib: Arc::new(kcl_lib::std::StdLib::new()),
|
||||
settings: Default::default(),
|
||||
context_type: kcl_lib::executor::ContextType::MockCustomForwarded,
|
||||
};
|
||||
let _memory = ctx.run(&program, None).await?;
|
||||
|
||||
let result = result.lock().expect("mutex lock").clone();
|
||||
Ok(result)
|
||||
}
|
19
src/wasm-lib/kcl-to-core/src/tool.rs
Normal file
@ -0,0 +1,19 @@
|
||||
use kcl_to_core::*;
|
||||
use std::{env, fs};
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
let args: Vec<String> = env::args().collect();
|
||||
|
||||
if args.len() < 2 {
|
||||
println!("Usage: kcl-to-core path/to/file.kcl");
|
||||
return;
|
||||
}
|
||||
|
||||
let file_path = &args[1];
|
||||
let kcl = fs::read_to_string(file_path).expect("read file");
|
||||
|
||||
let result = kcl_to_engine_core(&kcl).await.expect("kcl conversion");
|
||||
|
||||
println!("{}", result);
|
||||
}
|
19
src/wasm-lib/kcl-to-core/tests/kcl_to_core_test.rs
Normal file
@ -0,0 +1,19 @@
|
||||
use kcl_to_core::*;
|
||||
|
||||
#[tokio::test]
|
||||
async fn kcl_to_core_test() {
|
||||
let result = kcl_to_engine_core(
|
||||
r#"
|
||||
const part001 = startSketchOn('XY')
|
||||
|> startProfileAt([11.19, 28.35], %)
|
||||
|> line([28.67, -13.25], %, $here)
|
||||
|> line([-4.12, -22.81], %)
|
||||
|> line([-33.24, 14.55], %)
|
||||
|> close(%)
|
||||
|> extrude(5, %)
|
||||
"#,
|
||||
)
|
||||
.await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
}
|
@ -35,7 +35,7 @@ measurements = "0.11.0"
|
||||
mime_guess = "2.0.5"
|
||||
parse-display = "0.9.1"
|
||||
pyo3 = { version = "0.22.3", optional = true }
|
||||
reqwest = { version = "0.11.26", default-features = false, features = ["stream", "rustls-tls"] }
|
||||
reqwest = { version = "0.12", default-features = false, features = ["stream", "rustls-tls"] }
|
||||
ropey = "1.6.1"
|
||||
schemars = { version = "0.8.17", features = ["impl_json_schema", "url", "uuid1", "preserve_order"] }
|
||||
serde = { version = "1.0.210", features = ["derive"] }
|
||||
|
@ -18,7 +18,11 @@ use tower_lsp::lsp_types::{
|
||||
CompletionItem, CompletionItemKind, DocumentSymbol, FoldingRange, FoldingRangeKind, Range as LspRange, SymbolKind,
|
||||
};
|
||||
|
||||
pub use crate::ast::types::{literal_value::LiteralValue, none::KclNone};
|
||||
pub use crate::ast::types::{
|
||||
condition::{ElseIf, IfExpression},
|
||||
literal_value::LiteralValue,
|
||||
none::KclNone,
|
||||
};
|
||||
use crate::{
|
||||
docs::StdLibFn,
|
||||
errors::{KclError, KclErrorDetails},
|
||||
@ -30,12 +34,14 @@ use crate::{
|
||||
std::{kcl_stdlib::KclStdLibFn, FunctionKind},
|
||||
};
|
||||
|
||||
mod condition;
|
||||
mod literal_value;
|
||||
mod none;
|
||||
|
||||
/// Position-independent digest of the AST node.
|
||||
pub type Digest = [u8; 32];
|
||||
|
||||
/// A KCL program top level, or function body.
|
||||
#[derive(Debug, Default, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema, Bake)]
|
||||
#[databake(path = kcl_lib::ast::types)]
|
||||
#[ts(export)]
|
||||
@ -72,6 +78,7 @@ macro_rules! compute_digest {
|
||||
}
|
||||
};
|
||||
}
|
||||
pub(crate) use compute_digest;
|
||||
|
||||
impl Program {
|
||||
compute_digest!(|slf, hasher| {
|
||||
@ -82,6 +89,14 @@ impl Program {
|
||||
hasher.update(slf.non_code_meta.compute_digest());
|
||||
});
|
||||
|
||||
/// Is the last body item an expression?
|
||||
pub fn ends_with_expr(&self) -> bool {
|
||||
let Some(ref last) = self.body.last() else {
|
||||
return false;
|
||||
};
|
||||
matches!(last, BodyItem::ExpressionStatement(_))
|
||||
}
|
||||
|
||||
pub fn get_hover_value_for_position(&self, pos: usize, code: &str) -> Option<Hover> {
|
||||
// Check if we are in the non code meta.
|
||||
if let Some(meta) = self.get_non_code_meta_for_position(pos) {
|
||||
@ -501,6 +516,7 @@ pub enum Expr {
|
||||
ObjectExpression(Box<ObjectExpression>),
|
||||
MemberExpression(Box<MemberExpression>),
|
||||
UnaryExpression(Box<UnaryExpression>),
|
||||
IfExpression(Box<IfExpression>),
|
||||
None(KclNone),
|
||||
}
|
||||
|
||||
@ -519,6 +535,7 @@ impl Expr {
|
||||
Expr::ObjectExpression(oe) => oe.compute_digest(),
|
||||
Expr::MemberExpression(me) => me.compute_digest(),
|
||||
Expr::UnaryExpression(ue) => ue.compute_digest(),
|
||||
Expr::IfExpression(e) => e.compute_digest(),
|
||||
Expr::None(_) => {
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.update(b"Value::None");
|
||||
@ -562,6 +579,7 @@ impl Expr {
|
||||
Expr::PipeExpression(pipe_exp) => Some(&pipe_exp.non_code_meta),
|
||||
Expr::UnaryExpression(_unary_exp) => None,
|
||||
Expr::PipeSubstitution(_pipe_substitution) => None,
|
||||
Expr::IfExpression(_) => None,
|
||||
Expr::None(_none) => None,
|
||||
}
|
||||
}
|
||||
@ -584,6 +602,7 @@ impl Expr {
|
||||
Expr::TagDeclarator(_) => {}
|
||||
Expr::PipeExpression(ref mut pipe_exp) => pipe_exp.replace_value(source_range, new_value),
|
||||
Expr::UnaryExpression(ref mut unary_exp) => unary_exp.replace_value(source_range, new_value),
|
||||
Expr::IfExpression(_) => {}
|
||||
Expr::PipeSubstitution(_) => {}
|
||||
Expr::None(_) => {}
|
||||
}
|
||||
@ -603,6 +622,7 @@ impl Expr {
|
||||
Expr::ObjectExpression(object_expression) => object_expression.start(),
|
||||
Expr::MemberExpression(member_expression) => member_expression.start(),
|
||||
Expr::UnaryExpression(unary_expression) => unary_expression.start(),
|
||||
Expr::IfExpression(expr) => expr.start(),
|
||||
Expr::None(none) => none.start,
|
||||
}
|
||||
}
|
||||
@ -621,6 +641,7 @@ impl Expr {
|
||||
Expr::ObjectExpression(object_expression) => object_expression.end(),
|
||||
Expr::MemberExpression(member_expression) => member_expression.end(),
|
||||
Expr::UnaryExpression(unary_expression) => unary_expression.end(),
|
||||
Expr::IfExpression(expr) => expr.end(),
|
||||
Expr::None(none) => none.end,
|
||||
}
|
||||
}
|
||||
@ -639,6 +660,7 @@ impl Expr {
|
||||
Expr::ObjectExpression(object_expression) => object_expression.get_hover_value_for_position(pos, code),
|
||||
Expr::MemberExpression(member_expression) => member_expression.get_hover_value_for_position(pos, code),
|
||||
Expr::UnaryExpression(unary_expression) => unary_expression.get_hover_value_for_position(pos, code),
|
||||
Expr::IfExpression(expr) => expr.get_hover_value_for_position(pos, code),
|
||||
// TODO: LSP hover information for values/types. https://github.com/KittyCAD/modeling-app/issues/1126
|
||||
Expr::None(_) => None,
|
||||
Expr::Literal(_) => None,
|
||||
@ -670,11 +692,12 @@ impl Expr {
|
||||
member_expression.rename_identifiers(old_name, new_name)
|
||||
}
|
||||
Expr::UnaryExpression(ref mut unary_expression) => unary_expression.rename_identifiers(old_name, new_name),
|
||||
Expr::IfExpression(ref mut expr) => expr.rename_identifiers(old_name, new_name),
|
||||
Expr::None(_) => {}
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the constraint level for a value type.
|
||||
/// Get the constraint level for an expression.
|
||||
pub fn get_constraint_level(&self) -> ConstraintLevel {
|
||||
match self {
|
||||
Expr::Literal(literal) => literal.get_constraint_level(),
|
||||
@ -692,6 +715,7 @@ impl Expr {
|
||||
Expr::ObjectExpression(object_expression) => object_expression.get_constraint_level(),
|
||||
Expr::MemberExpression(member_expression) => member_expression.get_constraint_level(),
|
||||
Expr::UnaryExpression(unary_expression) => unary_expression.get_constraint_level(),
|
||||
Expr::IfExpression(expr) => expr.get_constraint_level(),
|
||||
Expr::None(none) => none.get_constraint_level(),
|
||||
}
|
||||
}
|
||||
@ -720,6 +744,7 @@ pub enum BinaryPart {
|
||||
CallExpression(Box<CallExpression>),
|
||||
UnaryExpression(Box<UnaryExpression>),
|
||||
MemberExpression(Box<MemberExpression>),
|
||||
IfExpression(Box<IfExpression>),
|
||||
}
|
||||
|
||||
impl From<BinaryPart> for SourceRange {
|
||||
@ -743,6 +768,7 @@ impl BinaryPart {
|
||||
BinaryPart::CallExpression(ce) => ce.compute_digest(),
|
||||
BinaryPart::UnaryExpression(ue) => ue.compute_digest(),
|
||||
BinaryPart::MemberExpression(me) => me.compute_digest(),
|
||||
BinaryPart::IfExpression(e) => e.compute_digest(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -755,6 +781,7 @@ impl BinaryPart {
|
||||
BinaryPart::CallExpression(call_expression) => call_expression.get_constraint_level(),
|
||||
BinaryPart::UnaryExpression(unary_expression) => unary_expression.get_constraint_level(),
|
||||
BinaryPart::MemberExpression(member_expression) => member_expression.get_constraint_level(),
|
||||
BinaryPart::IfExpression(e) => e.get_constraint_level(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -772,6 +799,7 @@ impl BinaryPart {
|
||||
unary_expression.replace_value(source_range, new_value)
|
||||
}
|
||||
BinaryPart::MemberExpression(_) => {}
|
||||
BinaryPart::IfExpression(e) => e.replace_value(source_range, new_value),
|
||||
}
|
||||
}
|
||||
|
||||
@ -783,6 +811,7 @@ impl BinaryPart {
|
||||
BinaryPart::CallExpression(call_expression) => call_expression.start(),
|
||||
BinaryPart::UnaryExpression(unary_expression) => unary_expression.start(),
|
||||
BinaryPart::MemberExpression(member_expression) => member_expression.start(),
|
||||
BinaryPart::IfExpression(e) => e.start(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -794,6 +823,7 @@ impl BinaryPart {
|
||||
BinaryPart::CallExpression(call_expression) => call_expression.end(),
|
||||
BinaryPart::UnaryExpression(unary_expression) => unary_expression.end(),
|
||||
BinaryPart::MemberExpression(member_expression) => member_expression.end(),
|
||||
BinaryPart::IfExpression(e) => e.end(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -809,6 +839,7 @@ impl BinaryPart {
|
||||
BinaryPart::CallExpression(call_expression) => call_expression.execute(exec_state, ctx).await,
|
||||
BinaryPart::UnaryExpression(unary_expression) => unary_expression.get_result(exec_state, ctx).await,
|
||||
BinaryPart::MemberExpression(member_expression) => member_expression.get_result(exec_state),
|
||||
BinaryPart::IfExpression(e) => e.get_result(exec_state, ctx).await,
|
||||
}
|
||||
}
|
||||
|
||||
@ -822,6 +853,7 @@ impl BinaryPart {
|
||||
}
|
||||
BinaryPart::CallExpression(call_expression) => call_expression.get_hover_value_for_position(pos, code),
|
||||
BinaryPart::UnaryExpression(unary_expression) => unary_expression.get_hover_value_for_position(pos, code),
|
||||
BinaryPart::IfExpression(e) => e.get_hover_value_for_position(pos, code),
|
||||
BinaryPart::MemberExpression(member_expression) => {
|
||||
member_expression.get_hover_value_for_position(pos, code)
|
||||
}
|
||||
@ -845,6 +877,7 @@ impl BinaryPart {
|
||||
BinaryPart::MemberExpression(ref mut member_expression) => {
|
||||
member_expression.rename_identifiers(old_name, new_name)
|
||||
}
|
||||
BinaryPart::IfExpression(ref mut if_expression) => if_expression.rename_identifiers(old_name, new_name),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1331,7 +1364,7 @@ impl CallExpression {
|
||||
};
|
||||
|
||||
match exec_result {
|
||||
Ok(()) => {}
|
||||
Ok(_) => {}
|
||||
Err(err) => {
|
||||
// We need to override the source ranges so we don't get the embedded kcl
|
||||
// function from the stdlib.
|
||||
@ -3149,6 +3182,7 @@ async fn inner_execute_pipe_body(
|
||||
| Expr::ObjectExpression(_)
|
||||
| Expr::MemberExpression(_)
|
||||
| Expr::UnaryExpression(_)
|
||||
| Expr::IfExpression(_)
|
||||
| Expr::None(_) => {}
|
||||
};
|
||||
let metadata = Metadata {
|
||||
|
215
src/wasm-lib/kcl/src/ast/types/condition.rs
Normal file
@ -0,0 +1,215 @@
|
||||
use crate::errors::KclError;
|
||||
use crate::executor::BodyType;
|
||||
use crate::executor::ExecState;
|
||||
use crate::executor::ExecutorContext;
|
||||
use crate::executor::KclValue;
|
||||
use crate::executor::Metadata;
|
||||
use crate::executor::SourceRange;
|
||||
use crate::executor::StatementKind;
|
||||
|
||||
use super::compute_digest;
|
||||
use super::impl_value_meta;
|
||||
use super::ConstraintLevel;
|
||||
use super::Hover;
|
||||
use super::{Digest, Expr};
|
||||
use databake::*;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sha2::{Digest as DigestTrait, Sha256};
|
||||
|
||||
// TODO: This should be its own type, similar to Program,
|
||||
// but guaranteed to have an Expression as its final item.
|
||||
// https://github.com/KittyCAD/modeling-app/issues/4015
|
||||
type IfBlock = crate::ast::types::Program;
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema, Bake)]
|
||||
#[databake(path = kcl_lib::ast::types)]
|
||||
#[ts(export)]
|
||||
#[serde(tag = "type")]
|
||||
pub struct IfExpression {
|
||||
pub start: usize,
|
||||
pub end: usize,
|
||||
pub cond: Box<Expr>,
|
||||
pub then_val: Box<IfBlock>,
|
||||
pub else_ifs: Vec<ElseIf>,
|
||||
pub final_else: Box<IfBlock>,
|
||||
|
||||
pub digest: Option<Digest>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema, Bake)]
|
||||
#[databake(path = kcl_lib::ast::types)]
|
||||
#[ts(export)]
|
||||
#[serde(tag = "type")]
|
||||
pub struct ElseIf {
|
||||
pub start: usize,
|
||||
pub end: usize,
|
||||
pub cond: Expr,
|
||||
pub then_val: Box<IfBlock>,
|
||||
|
||||
pub digest: Option<Digest>,
|
||||
}
|
||||
|
||||
// Source code metadata
|
||||
|
||||
impl_value_meta!(IfExpression);
|
||||
impl_value_meta!(ElseIf);
|
||||
|
||||
impl IfExpression {
|
||||
compute_digest!(|slf, hasher| {
|
||||
hasher.update(slf.cond.compute_digest());
|
||||
hasher.update(slf.then_val.compute_digest());
|
||||
for else_if in &mut slf.else_ifs {
|
||||
hasher.update(else_if.compute_digest());
|
||||
}
|
||||
hasher.update(slf.final_else.compute_digest());
|
||||
});
|
||||
fn source_ranges(&self) -> Vec<SourceRange> {
|
||||
vec![SourceRange::from(self)]
|
||||
}
|
||||
}
|
||||
|
||||
impl From<IfExpression> for Metadata {
|
||||
fn from(value: IfExpression) -> Self {
|
||||
Self {
|
||||
source_range: value.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ElseIf> for Metadata {
|
||||
fn from(value: ElseIf) -> Self {
|
||||
Self {
|
||||
source_range: value.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl From<&IfExpression> for Metadata {
|
||||
fn from(value: &IfExpression) -> Self {
|
||||
Self {
|
||||
source_range: value.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&ElseIf> for Metadata {
|
||||
fn from(value: &ElseIf) -> Self {
|
||||
Self {
|
||||
source_range: value.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ElseIf {
|
||||
compute_digest!(|slf, hasher| {
|
||||
hasher.update(slf.cond.compute_digest());
|
||||
hasher.update(slf.then_val.compute_digest());
|
||||
});
|
||||
#[allow(dead_code)]
|
||||
fn source_ranges(&self) -> Vec<SourceRange> {
|
||||
vec![SourceRange([self.start, self.end])]
|
||||
}
|
||||
}
|
||||
|
||||
// Execution
|
||||
|
||||
impl IfExpression {
|
||||
#[async_recursion::async_recursion]
|
||||
pub async fn get_result(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
|
||||
// Check the `if` branch.
|
||||
let cond = ctx
|
||||
.execute_expr(&self.cond, exec_state, &Metadata::from(self), StatementKind::Expression)
|
||||
.await?
|
||||
.get_bool()?;
|
||||
if cond {
|
||||
let block_result = ctx.inner_execute(&self.then_val, exec_state, BodyType::Block).await?;
|
||||
// Block must end in an expression, so this has to be Some.
|
||||
// Enforced by the parser.
|
||||
// See https://github.com/KittyCAD/modeling-app/issues/4015
|
||||
return Ok(block_result.unwrap());
|
||||
}
|
||||
|
||||
// Check any `else if` branches.
|
||||
for else_if in &self.else_ifs {
|
||||
let cond = ctx
|
||||
.execute_expr(
|
||||
&else_if.cond,
|
||||
exec_state,
|
||||
&Metadata::from(self),
|
||||
StatementKind::Expression,
|
||||
)
|
||||
.await?
|
||||
.get_bool()?;
|
||||
if cond {
|
||||
let block_result = ctx
|
||||
.inner_execute(&else_if.then_val, exec_state, BodyType::Block)
|
||||
.await?;
|
||||
// Block must end in an expression, so this has to be Some.
|
||||
// Enforced by the parser.
|
||||
// See https://github.com/KittyCAD/modeling-app/issues/4015
|
||||
return Ok(block_result.unwrap());
|
||||
}
|
||||
}
|
||||
|
||||
// Run the final `else` branch.
|
||||
ctx.inner_execute(&self.final_else, exec_state, BodyType::Block)
|
||||
.await
|
||||
.map(|expr| expr.unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
// IDE support and refactors
|
||||
|
||||
impl IfExpression {
|
||||
pub fn get_hover_value_for_position(&self, pos: usize, code: &str) -> Option<Hover> {
|
||||
self.cond
|
||||
.get_hover_value_for_position(pos, code)
|
||||
.or_else(|| self.then_val.get_hover_value_for_position(pos, code))
|
||||
.or_else(|| {
|
||||
self.else_ifs
|
||||
.iter()
|
||||
.find_map(|else_if| else_if.get_hover_value_for_position(pos, code))
|
||||
})
|
||||
.or_else(|| self.final_else.get_hover_value_for_position(pos, code))
|
||||
}
|
||||
|
||||
/// Rename all identifiers that have the old name to the new given name.
|
||||
pub fn rename_identifiers(&mut self, old_name: &str, new_name: &str) {
|
||||
self.cond.rename_identifiers(old_name, new_name);
|
||||
self.then_val.rename_identifiers(old_name, new_name);
|
||||
for else_if in &mut self.else_ifs {
|
||||
else_if.rename_identifiers(old_name, new_name);
|
||||
}
|
||||
self.final_else.rename_identifiers(old_name, new_name);
|
||||
}
|
||||
/// Get the constraint level.
|
||||
pub fn get_constraint_level(&self) -> ConstraintLevel {
|
||||
ConstraintLevel::Full {
|
||||
source_ranges: self.source_ranges(),
|
||||
}
|
||||
}
|
||||
pub fn replace_value(&mut self, source_range: SourceRange, new_value: Expr) {
|
||||
self.cond.replace_value(source_range, new_value.clone());
|
||||
for else_if in &mut self.else_ifs {
|
||||
else_if.cond.replace_value(source_range, new_value.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ElseIf {
|
||||
fn get_hover_value_for_position(&self, pos: usize, code: &str) -> Option<Hover> {
|
||||
self.cond
|
||||
.get_hover_value_for_position(pos, code)
|
||||
.or_else(|| self.then_val.get_hover_value_for_position(pos, code))
|
||||
}
|
||||
/// Rename all identifiers that have the old name to the new given name.
|
||||
fn rename_identifiers(&mut self, old_name: &str, new_name: &str) {
|
||||
self.cond.rename_identifiers(old_name, new_name);
|
||||
self.then_val.rename_identifiers(old_name, new_name);
|
||||
}
|
||||
}
|
||||
|
||||
// Linting
|
||||
|
||||
impl IfExpression {}
|
||||
impl ElseIf {}
|
@ -81,6 +81,8 @@ impl StdLibFnArg {
|
||||
} else if self.type_ == "TagIdentifier" && self.required {
|
||||
// TODO: actually use the ast to populate this.
|
||||
return Ok(Some((index, format!("${{{}:{}}}", index, "myTag"))));
|
||||
} else if self.type_ == "[KclValue]" && self.required {
|
||||
return Ok(Some((index, format!("${{{}:{}}}", index, "[0..9]"))));
|
||||
}
|
||||
get_autocomplete_snippet_from_schema(&self.schema.schema.clone().into(), index)
|
||||
}
|
||||
@ -903,6 +905,13 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_autocomplete_snippet_map() {
|
||||
let map_fn: Box<dyn StdLibFn> = Box::new(crate::std::array::Map);
|
||||
let snippet = map_fn.to_autocomplete_snippet().unwrap();
|
||||
assert_eq!(snippet, r#"map(${0:[0..9]})${}"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_autocomplete_snippet_pattern_linear_2d() {
|
||||
let pattern_fn: Box<dyn StdLibFn> = Box::new(crate::std::patterns::PatternLinear2D);
|
||||
@ -916,4 +925,18 @@ mod tests {
|
||||
}, ${4:%})${}"#
|
||||
);
|
||||
}
|
||||
|
||||
// We want to test the snippets we compile at lsp start.
|
||||
#[test]
|
||||
fn get_all_stdlib_autocomplete_snippets() {
|
||||
let stdlib = crate::std::StdLib::new();
|
||||
crate::lsp::kcl::get_completions_from_stdlib(&stdlib).unwrap();
|
||||
}
|
||||
|
||||
// We want to test the signatures we compile at lsp start.
|
||||
#[test]
|
||||
fn get_all_stdlib_signatures() {
|
||||
let stdlib = crate::std::StdLib::new();
|
||||
crate::lsp::kcl::get_signatures_from_stdlib(&stdlib).unwrap();
|
||||
}
|
||||
}
|
||||
|
@ -776,6 +776,25 @@ impl From<KclValue> for Vec<SourceRange> {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&KclValue> for Vec<SourceRange> {
|
||||
fn from(item: &KclValue) -> Self {
|
||||
match item {
|
||||
KclValue::UserVal(u) => u.meta.iter().map(|m| m.source_range).collect(),
|
||||
KclValue::TagDeclarator(ref t) => vec![t.into()],
|
||||
KclValue::TagIdentifier(t) => t.meta.iter().map(|m| m.source_range).collect(),
|
||||
KclValue::Solid(e) => e.meta.iter().map(|m| m.source_range).collect(),
|
||||
KclValue::Solids { value } => value
|
||||
.iter()
|
||||
.flat_map(|eg| eg.meta.iter().map(|m| m.source_range))
|
||||
.collect(),
|
||||
KclValue::ImportedGeometry(i) => i.meta.iter().map(|m| m.source_range).collect(),
|
||||
KclValue::Function { meta, .. } => meta.iter().map(|m| m.source_range).collect(),
|
||||
KclValue::Plane(p) => p.meta.iter().map(|m| m.source_range).collect(),
|
||||
KclValue::Face(f) => f.meta.iter().map(|m| m.source_range).collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl KclValue {
|
||||
pub fn get_json_value(&self) -> Result<serde_json::Value, KclError> {
|
||||
if let KclValue::UserVal(user_val) = self {
|
||||
@ -906,6 +925,23 @@ impl KclValue {
|
||||
}
|
||||
}
|
||||
|
||||
/// If this KCL value is a bool, retrieve it.
|
||||
pub fn get_bool(&self) -> Result<bool, KclError> {
|
||||
let Self::UserVal(uv) = self else {
|
||||
return Err(KclError::Type(KclErrorDetails {
|
||||
source_ranges: self.into(),
|
||||
message: format!("Expected bool, found {}", self.human_friendly_type()),
|
||||
}));
|
||||
};
|
||||
let JValue::Bool(b) = uv.value else {
|
||||
return Err(KclError::Type(KclErrorDetails {
|
||||
source_ranges: self.into(),
|
||||
message: format!("Expected bool, found {}", human_friendly_type(&uv.value)),
|
||||
}));
|
||||
};
|
||||
Ok(b)
|
||||
}
|
||||
|
||||
/// If this memory item is a function, call it with the given arguments, return its val as Ok.
|
||||
/// If it's not a function, return Err.
|
||||
pub async fn call_fn(
|
||||
@ -1613,6 +1649,22 @@ impl ExtrudeSurface {
|
||||
}
|
||||
}
|
||||
|
||||
/// The type of ExecutorContext being used
|
||||
#[derive(PartialEq, Debug, Default, Clone)]
|
||||
pub enum ContextType {
|
||||
/// Live engine connection
|
||||
#[default]
|
||||
Live,
|
||||
|
||||
/// Completely mocked connection
|
||||
/// Mock mode is only for the modeling app when they just want to mock engine calls and not
|
||||
/// actually make them.
|
||||
Mock,
|
||||
|
||||
/// Handled by some other interpreter/conversion system
|
||||
MockCustomForwarded,
|
||||
}
|
||||
|
||||
/// The executor context.
|
||||
/// Cloning will return another handle to the same engine connection/session,
|
||||
/// as this uses `Arc` under the hood.
|
||||
@ -1622,9 +1674,7 @@ pub struct ExecutorContext {
|
||||
pub fs: Arc<FileManager>,
|
||||
pub stdlib: Arc<StdLib>,
|
||||
pub settings: ExecutorSettings,
|
||||
/// Mock mode is only for the modeling app when they just want to mock engine calls and not
|
||||
/// actually make them.
|
||||
pub is_mock: bool,
|
||||
pub context_type: ContextType,
|
||||
}
|
||||
|
||||
/// The executor settings.
|
||||
@ -1734,10 +1784,14 @@ impl ExecutorContext {
|
||||
fs: Arc::new(FileManager::new()),
|
||||
stdlib: Arc::new(StdLib::new()),
|
||||
settings,
|
||||
is_mock: false,
|
||||
context_type: ContextType::Live,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn is_mock(&self) -> bool {
|
||||
self.context_type == ContextType::Mock || self.context_type == ContextType::MockCustomForwarded
|
||||
}
|
||||
|
||||
/// For executing unit tests.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub async fn new_for_unit_test(units: UnitLength, engine_addr: Option<String>) -> Result<Self> {
|
||||
@ -1844,20 +1898,22 @@ impl ExecutorContext {
|
||||
program: &crate::ast::types::Program,
|
||||
exec_state: &mut ExecState,
|
||||
body_type: BodyType,
|
||||
) -> Result<(), KclError> {
|
||||
) -> Result<Option<KclValue>, KclError> {
|
||||
let mut last_expr = None;
|
||||
// Iterate over the body of the program.
|
||||
for statement in &program.body {
|
||||
match statement {
|
||||
BodyItem::ExpressionStatement(expression_statement) => {
|
||||
let metadata = Metadata::from(expression_statement);
|
||||
// Discard return value.
|
||||
self.execute_expr(
|
||||
&expression_statement.expression,
|
||||
exec_state,
|
||||
&metadata,
|
||||
StatementKind::Expression,
|
||||
)
|
||||
.await?;
|
||||
last_expr = Some(
|
||||
self.execute_expr(
|
||||
&expression_statement.expression,
|
||||
exec_state,
|
||||
&metadata,
|
||||
StatementKind::Expression,
|
||||
)
|
||||
.await?,
|
||||
);
|
||||
}
|
||||
BodyItem::VariableDeclaration(variable_declaration) => {
|
||||
for declaration in &variable_declaration.declarations {
|
||||
@ -1875,6 +1931,7 @@ impl ExecutorContext {
|
||||
.await?;
|
||||
exec_state.memory.add(&var_name, memory_item, source_range)?;
|
||||
}
|
||||
last_expr = None;
|
||||
}
|
||||
BodyItem::ReturnStatement(return_statement) => {
|
||||
let metadata = Metadata::from(return_statement);
|
||||
@ -1887,6 +1944,7 @@ impl ExecutorContext {
|
||||
)
|
||||
.await?;
|
||||
exec_state.memory.return_ = Some(value);
|
||||
last_expr = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1903,7 +1961,7 @@ impl ExecutorContext {
|
||||
.await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
Ok(last_expr)
|
||||
}
|
||||
|
||||
pub async fn execute_expr<'a>(
|
||||
@ -1960,6 +2018,7 @@ impl ExecutorContext {
|
||||
Expr::ObjectExpression(object_expression) => object_expression.execute(exec_state, self).await?,
|
||||
Expr::MemberExpression(member_expression) => member_expression.get_result(exec_state)?,
|
||||
Expr::UnaryExpression(unary_expression) => unary_expression.get_result(exec_state, self).await?,
|
||||
Expr::IfExpression(expr) => expr.get_result(exec_state, self).await?,
|
||||
};
|
||||
Ok(item)
|
||||
}
|
||||
@ -2088,7 +2147,7 @@ pub(crate) async fn call_user_defined_function(
|
||||
(result, fn_memory)
|
||||
};
|
||||
|
||||
result.map(|()| fn_memory.return_)
|
||||
result.map(|_| fn_memory.return_)
|
||||
}
|
||||
|
||||
pub enum StatementKind<'a> {
|
||||
@ -2114,7 +2173,7 @@ mod tests {
|
||||
fs: Arc::new(crate::fs::FileManager::new()),
|
||||
stdlib: Arc::new(crate::std::StdLib::new()),
|
||||
settings: Default::default(),
|
||||
is_mock: true,
|
||||
context_type: ContextType::Mock,
|
||||
};
|
||||
let exec_state = ctx.run(&program, None).await?;
|
||||
|
||||
|
@ -10,11 +10,11 @@ use winnow::{
|
||||
|
||||
use crate::{
|
||||
ast::types::{
|
||||
ArrayExpression, BinaryExpression, BinaryOperator, BinaryPart, BodyItem, CallExpression, CommentStyle, Expr,
|
||||
ExpressionStatement, FnArgPrimitive, FnArgType, FunctionExpression, Identifier, Literal, LiteralIdentifier,
|
||||
LiteralValue, MemberExpression, MemberObject, NonCodeMeta, NonCodeNode, NonCodeValue, ObjectExpression,
|
||||
ObjectProperty, Parameter, PipeExpression, PipeSubstitution, Program, ReturnStatement, TagDeclarator,
|
||||
UnaryExpression, UnaryOperator, VariableDeclaration, VariableDeclarator, VariableKind,
|
||||
ArrayExpression, BinaryExpression, BinaryOperator, BinaryPart, BodyItem, CallExpression, CommentStyle, ElseIf,
|
||||
Expr, ExpressionStatement, FnArgPrimitive, FnArgType, FunctionExpression, Identifier, IfExpression, Literal,
|
||||
LiteralIdentifier, LiteralValue, MemberExpression, MemberObject, NonCodeMeta, NonCodeNode, NonCodeValue,
|
||||
ObjectExpression, ObjectProperty, Parameter, PipeExpression, PipeSubstitution, Program, ReturnStatement,
|
||||
TagDeclarator, UnaryExpression, UnaryOperator, VariableDeclaration, VariableDeclarator, VariableKind,
|
||||
},
|
||||
errors::{KclError, KclErrorDetails},
|
||||
executor::SourceRange,
|
||||
@ -359,6 +359,7 @@ fn operand(i: TokenSlice) -> PResult<BinaryPart> {
|
||||
Expr::BinaryExpression(x) => BinaryPart::BinaryExpression(x),
|
||||
Expr::CallExpression(x) => BinaryPart::CallExpression(x),
|
||||
Expr::MemberExpression(x) => BinaryPart::MemberExpression(x),
|
||||
Expr::IfExpression(x) => BinaryPart::IfExpression(x),
|
||||
};
|
||||
Ok(expr)
|
||||
})
|
||||
@ -670,6 +671,119 @@ fn pipe_sub(i: TokenSlice) -> PResult<PipeSubstitution> {
|
||||
.parse_next(i)
|
||||
}
|
||||
|
||||
fn else_if(i: TokenSlice) -> PResult<ElseIf> {
|
||||
let start = any
|
||||
.try_map(|token: Token| {
|
||||
if matches!(token.token_type, TokenType::Keyword) && token.value == "else" {
|
||||
Ok(token.start)
|
||||
} else {
|
||||
Err(KclError::Syntax(KclErrorDetails {
|
||||
source_ranges: token.as_source_ranges(),
|
||||
message: format!("{} is not 'else'", token.value.as_str()),
|
||||
}))
|
||||
}
|
||||
})
|
||||
.context(expected("the 'else' keyword"))
|
||||
.parse_next(i)?;
|
||||
ignore_whitespace(i);
|
||||
let _if = any
|
||||
.try_map(|token: Token| {
|
||||
if matches!(token.token_type, TokenType::Keyword) && token.value == "if" {
|
||||
Ok(token.start)
|
||||
} else {
|
||||
Err(KclError::Syntax(KclErrorDetails {
|
||||
source_ranges: token.as_source_ranges(),
|
||||
message: format!("{} is not 'if'", token.value.as_str()),
|
||||
}))
|
||||
}
|
||||
})
|
||||
.context(expected("the 'if' keyword"))
|
||||
.parse_next(i)?;
|
||||
ignore_whitespace(i);
|
||||
let cond = expression(i)?;
|
||||
ignore_whitespace(i);
|
||||
let _ = open_brace(i)?;
|
||||
let then_val = program
|
||||
.verify(|block| block.ends_with_expr())
|
||||
.parse_next(i)
|
||||
.map(Box::new)?;
|
||||
ignore_whitespace(i);
|
||||
let end = close_brace(i)?.end;
|
||||
ignore_whitespace(i);
|
||||
Ok(ElseIf {
|
||||
start,
|
||||
end,
|
||||
cond,
|
||||
then_val,
|
||||
digest: Default::default(),
|
||||
})
|
||||
}
|
||||
|
||||
fn if_expr(i: TokenSlice) -> PResult<IfExpression> {
|
||||
let start = any
|
||||
.try_map(|token: Token| {
|
||||
if matches!(token.token_type, TokenType::Keyword) && token.value == "if" {
|
||||
Ok(token.start)
|
||||
} else {
|
||||
Err(KclError::Syntax(KclErrorDetails {
|
||||
source_ranges: token.as_source_ranges(),
|
||||
message: format!("{} is not 'if'", token.value.as_str()),
|
||||
}))
|
||||
}
|
||||
})
|
||||
.context(expected("the 'if' keyword"))
|
||||
.parse_next(i)?;
|
||||
let _ = whitespace(i)?;
|
||||
let cond = expression(i).map(Box::new)?;
|
||||
let _ = whitespace(i)?;
|
||||
let _ = open_brace(i)?;
|
||||
ignore_whitespace(i);
|
||||
let then_val = program
|
||||
.verify(|block| block.ends_with_expr())
|
||||
.parse_next(i)
|
||||
.map_err(|e| e.cut())
|
||||
.map(Box::new)?;
|
||||
ignore_whitespace(i);
|
||||
let _ = close_brace(i)?;
|
||||
ignore_whitespace(i);
|
||||
let else_ifs = repeat(0.., else_if).parse_next(i)?;
|
||||
|
||||
ignore_whitespace(i);
|
||||
let _ = any
|
||||
.try_map(|token: Token| {
|
||||
if matches!(token.token_type, TokenType::Keyword) && token.value == "else" {
|
||||
Ok(token.start)
|
||||
} else {
|
||||
Err(KclError::Syntax(KclErrorDetails {
|
||||
source_ranges: token.as_source_ranges(),
|
||||
message: format!("{} is not 'else'", token.value.as_str()),
|
||||
}))
|
||||
}
|
||||
})
|
||||
.context(expected("the 'else' keyword"))
|
||||
.parse_next(i)?;
|
||||
ignore_whitespace(i);
|
||||
let _ = open_brace(i)?;
|
||||
ignore_whitespace(i);
|
||||
|
||||
let final_else = program
|
||||
.verify(|block| block.ends_with_expr())
|
||||
.parse_next(i)
|
||||
.map_err(|e| e.cut())
|
||||
.map(Box::new)?;
|
||||
ignore_whitespace(i);
|
||||
let end = close_brace(i)?.end;
|
||||
Ok(IfExpression {
|
||||
start,
|
||||
end,
|
||||
cond,
|
||||
then_val,
|
||||
else_ifs,
|
||||
final_else,
|
||||
digest: Default::default(),
|
||||
})
|
||||
}
|
||||
|
||||
// Looks like
|
||||
// (arg0, arg1) => {
|
||||
// const x = arg0 + arg1;
|
||||
@ -1069,6 +1183,7 @@ fn expr_allowed_in_pipe_expr(i: TokenSlice) -> PResult<Expr> {
|
||||
object.map(Box::new).map(Expr::ObjectExpression),
|
||||
pipe_sub.map(Box::new).map(Expr::PipeSubstitution),
|
||||
function_expression.map(Box::new).map(Expr::FunctionExpression),
|
||||
if_expr.map(Box::new).map(Expr::IfExpression),
|
||||
unnecessarily_bracketed,
|
||||
))
|
||||
.context(expected("a KCL expression (but not a pipe expression)"))
|
||||
@ -3147,6 +3262,42 @@ e
|
||||
let _arr = array_elem_by_elem(&mut sl).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn basic_if_else() {
|
||||
let some_program_string = "if true {
|
||||
3
|
||||
} else {
|
||||
4
|
||||
}";
|
||||
let tokens = crate::token::lexer(some_program_string).unwrap();
|
||||
let mut sl: &[Token] = &tokens;
|
||||
let _res = if_expr(&mut sl).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn basic_else_if() {
|
||||
let some_program_string = "else if true {
|
||||
4
|
||||
}";
|
||||
let tokens = crate::token::lexer(some_program_string).unwrap();
|
||||
let mut sl: &[Token] = &tokens;
|
||||
let _res = else_if(&mut sl).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn basic_if_else_if() {
|
||||
let some_program_string = "if true {
|
||||
3
|
||||
} else if true {
|
||||
4
|
||||
} else {
|
||||
5
|
||||
}";
|
||||
let tokens = crate::token::lexer(some_program_string).unwrap();
|
||||
let mut sl: &[Token] = &tokens;
|
||||
let _res = if_expr(&mut sl).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_keyword_ok_in_fn_args_return() {
|
||||
let some_program_string = r#"fn thing = (param) => {
|
||||
@ -3511,6 +3662,24 @@ const sketch001 = startSketchOn('XY')
|
||||
const my14 = 4 ^ 2 - 3 ^ 2 * 2
|
||||
"#
|
||||
);
|
||||
snapshot_test!(
|
||||
bc,
|
||||
r#"const x = if true {
|
||||
3
|
||||
} else {
|
||||
4
|
||||
}"#
|
||||
);
|
||||
snapshot_test!(
|
||||
bd,
|
||||
r#"const x = if true {
|
||||
3
|
||||
} else if func(radius) {
|
||||
4
|
||||
} else {
|
||||
5
|
||||
}"#
|
||||
);
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
|
@ -0,0 +1,112 @@
|
||||
---
|
||||
source: kcl/src/parser/parser_impl.rs
|
||||
expression: actual
|
||||
---
|
||||
{
|
||||
"start": 0,
|
||||
"end": 74,
|
||||
"body": [
|
||||
{
|
||||
"type": "VariableDeclaration",
|
||||
"type": "VariableDeclaration",
|
||||
"start": 0,
|
||||
"end": 74,
|
||||
"declarations": [
|
||||
{
|
||||
"type": "VariableDeclarator",
|
||||
"start": 6,
|
||||
"end": 74,
|
||||
"id": {
|
||||
"type": "Identifier",
|
||||
"start": 6,
|
||||
"end": 7,
|
||||
"name": "x",
|
||||
"digest": null
|
||||
},
|
||||
"init": {
|
||||
"type": "IfExpression",
|
||||
"type": "IfExpression",
|
||||
"start": 10,
|
||||
"end": 74,
|
||||
"cond": {
|
||||
"type": "Literal",
|
||||
"type": "Literal",
|
||||
"start": 13,
|
||||
"end": 17,
|
||||
"value": true,
|
||||
"raw": "true",
|
||||
"digest": null
|
||||
},
|
||||
"then_val": {
|
||||
"start": 32,
|
||||
"end": 42,
|
||||
"body": [
|
||||
{
|
||||
"type": "ExpressionStatement",
|
||||
"type": "ExpressionStatement",
|
||||
"start": 32,
|
||||
"end": 33,
|
||||
"expression": {
|
||||
"type": "Literal",
|
||||
"type": "Literal",
|
||||
"start": 32,
|
||||
"end": 33,
|
||||
"value": 3,
|
||||
"raw": "3",
|
||||
"digest": null
|
||||
},
|
||||
"digest": null
|
||||
}
|
||||
],
|
||||
"nonCodeMeta": {
|
||||
"nonCodeNodes": {},
|
||||
"start": [],
|
||||
"digest": null
|
||||
},
|
||||
"digest": null
|
||||
},
|
||||
"else_ifs": [],
|
||||
"final_else": {
|
||||
"start": 63,
|
||||
"end": 73,
|
||||
"body": [
|
||||
{
|
||||
"type": "ExpressionStatement",
|
||||
"type": "ExpressionStatement",
|
||||
"start": 63,
|
||||
"end": 64,
|
||||
"expression": {
|
||||
"type": "Literal",
|
||||
"type": "Literal",
|
||||
"start": 63,
|
||||
"end": 64,
|
||||
"value": 4,
|
||||
"raw": "4",
|
||||
"digest": null
|
||||
},
|
||||
"digest": null
|
||||
}
|
||||
],
|
||||
"nonCodeMeta": {
|
||||
"nonCodeNodes": {},
|
||||
"start": [],
|
||||
"digest": null
|
||||
},
|
||||
"digest": null
|
||||
},
|
||||
"digest": null
|
||||
},
|
||||
"digest": null
|
||||
}
|
||||
],
|
||||
"kind": "const",
|
||||
"digest": null
|
||||
}
|
||||
],
|
||||
"nonCodeMeta": {
|
||||
"nonCodeNodes": {},
|
||||
"start": [],
|
||||
"digest": null
|
||||
},
|
||||
"digest": null
|
||||
}
|
@ -0,0 +1,172 @@
|
||||
---
|
||||
source: kcl/src/parser/parser_impl.rs
|
||||
expression: actual
|
||||
---
|
||||
{
|
||||
"start": 0,
|
||||
"end": 121,
|
||||
"body": [
|
||||
{
|
||||
"type": "VariableDeclaration",
|
||||
"type": "VariableDeclaration",
|
||||
"start": 0,
|
||||
"end": 121,
|
||||
"declarations": [
|
||||
{
|
||||
"type": "VariableDeclarator",
|
||||
"start": 6,
|
||||
"end": 121,
|
||||
"id": {
|
||||
"type": "Identifier",
|
||||
"start": 6,
|
||||
"end": 7,
|
||||
"name": "x",
|
||||
"digest": null
|
||||
},
|
||||
"init": {
|
||||
"type": "IfExpression",
|
||||
"type": "IfExpression",
|
||||
"start": 10,
|
||||
"end": 121,
|
||||
"cond": {
|
||||
"type": "Literal",
|
||||
"type": "Literal",
|
||||
"start": 13,
|
||||
"end": 17,
|
||||
"value": true,
|
||||
"raw": "true",
|
||||
"digest": null
|
||||
},
|
||||
"then_val": {
|
||||
"start": 32,
|
||||
"end": 42,
|
||||
"body": [
|
||||
{
|
||||
"type": "ExpressionStatement",
|
||||
"type": "ExpressionStatement",
|
||||
"start": 32,
|
||||
"end": 33,
|
||||
"expression": {
|
||||
"type": "Literal",
|
||||
"type": "Literal",
|
||||
"start": 32,
|
||||
"end": 33,
|
||||
"value": 3,
|
||||
"raw": "3",
|
||||
"digest": null
|
||||
},
|
||||
"digest": null
|
||||
}
|
||||
],
|
||||
"nonCodeMeta": {
|
||||
"nonCodeNodes": {},
|
||||
"start": [],
|
||||
"digest": null
|
||||
},
|
||||
"digest": null
|
||||
},
|
||||
"else_ifs": [
|
||||
{
|
||||
"type": "ElseIf",
|
||||
"start": 44,
|
||||
"end": 90,
|
||||
"cond": {
|
||||
"type": "CallExpression",
|
||||
"type": "CallExpression",
|
||||
"start": 52,
|
||||
"end": 64,
|
||||
"callee": {
|
||||
"type": "Identifier",
|
||||
"start": 52,
|
||||
"end": 56,
|
||||
"name": "func",
|
||||
"digest": null
|
||||
},
|
||||
"arguments": [
|
||||
{
|
||||
"type": "Identifier",
|
||||
"type": "Identifier",
|
||||
"start": 57,
|
||||
"end": 63,
|
||||
"name": "radius",
|
||||
"digest": null
|
||||
}
|
||||
],
|
||||
"optional": false,
|
||||
"digest": null
|
||||
},
|
||||
"then_val": {
|
||||
"start": 65,
|
||||
"end": 89,
|
||||
"body": [
|
||||
{
|
||||
"type": "ExpressionStatement",
|
||||
"type": "ExpressionStatement",
|
||||
"start": 79,
|
||||
"end": 80,
|
||||
"expression": {
|
||||
"type": "Literal",
|
||||
"type": "Literal",
|
||||
"start": 79,
|
||||
"end": 80,
|
||||
"value": 4,
|
||||
"raw": "4",
|
||||
"digest": null
|
||||
},
|
||||
"digest": null
|
||||
}
|
||||
],
|
||||
"nonCodeMeta": {
|
||||
"nonCodeNodes": {},
|
||||
"start": [],
|
||||
"digest": null
|
||||
},
|
||||
"digest": null
|
||||
},
|
||||
"digest": null
|
||||
}
|
||||
],
|
||||
"final_else": {
|
||||
"start": 110,
|
||||
"end": 120,
|
||||
"body": [
|
||||
{
|
||||
"type": "ExpressionStatement",
|
||||
"type": "ExpressionStatement",
|
||||
"start": 110,
|
||||
"end": 111,
|
||||
"expression": {
|
||||
"type": "Literal",
|
||||
"type": "Literal",
|
||||
"start": 110,
|
||||
"end": 111,
|
||||
"value": 5,
|
||||
"raw": "5",
|
||||
"digest": null
|
||||
},
|
||||
"digest": null
|
||||
}
|
||||
],
|
||||
"nonCodeMeta": {
|
||||
"nonCodeNodes": {},
|
||||
"start": [],
|
||||
"digest": null
|
||||
},
|
||||
"digest": null
|
||||
},
|
||||
"digest": null
|
||||
},
|
||||
"digest": null
|
||||
}
|
||||
],
|
||||
"kind": "const",
|
||||
"digest": null
|
||||
}
|
||||
],
|
||||
"nonCodeMeta": {
|
||||
"nonCodeNodes": {},
|
||||
"start": [],
|
||||
"digest": null
|
||||
},
|
||||
"digest": null
|
||||
}
|
@ -4,13 +4,14 @@ use anyhow::Result;
|
||||
use kcmc::{websocket::OkWebSocketResponseData, ModelingCmd};
|
||||
use kittycad_modeling_cmds as kcmc;
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde_json::Value as JValue;
|
||||
|
||||
use crate::{
|
||||
ast::types::{parse_json_number_as_f64, TagDeclarator},
|
||||
errors::{KclError, KclErrorDetails},
|
||||
executor::{
|
||||
ExecState, ExecutorContext, ExtrudeSurface, KclValue, Metadata, Sketch, SketchSet, SketchSurface, Solid,
|
||||
SolidSet, SourceRange, TagIdentifier,
|
||||
SolidSet, SourceRange, TagIdentifier, UserVal,
|
||||
},
|
||||
std::{shapes::SketchOrSurface, sketch::FaceTag, FnAsArg},
|
||||
};
|
||||
@ -43,7 +44,7 @@ impl Args {
|
||||
fs: Arc::new(crate::fs::FileManager::new()),
|
||||
stdlib: Arc::new(crate::std::StdLib::new()),
|
||||
settings: Default::default(),
|
||||
is_mock: true,
|
||||
context_type: crate::executor::ContextType::Mock,
|
||||
},
|
||||
})
|
||||
}
|
||||
@ -497,18 +498,6 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> FromArgs<'a> for KclValue {
|
||||
fn from_args(args: &'a Args, i: usize) -> Result<Self, KclError> {
|
||||
let Some(v) = args.args.get(i) else {
|
||||
return Err(KclError::Semantic(KclErrorDetails {
|
||||
message: format!("Argument at index {i} was missing",),
|
||||
source_ranges: vec![args.source_range],
|
||||
}));
|
||||
};
|
||||
Ok(v.to_owned())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> FromArgs<'a> for Option<T>
|
||||
where
|
||||
T: FromKclValue<'a> + Sized,
|
||||
@ -587,6 +576,20 @@ impl<'a> FromKclValue<'a> for i64 {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> FromKclValue<'a> for UserVal {
|
||||
fn from_mem_item(arg: &'a KclValue) -> Option<Self> {
|
||||
arg.as_user_val().map(|x| x.to_owned())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> FromKclValue<'a> for Vec<JValue> {
|
||||
fn from_mem_item(arg: &'a KclValue) -> Option<Self> {
|
||||
arg.as_user_val()
|
||||
.and_then(|uv| uv.value.as_array())
|
||||
.map(ToOwned::to_owned)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> FromKclValue<'a> for TagDeclarator {
|
||||
fn from_mem_item(arg: &'a KclValue) -> Option<Self> {
|
||||
arg.get_tag_declarator().ok()
|
||||
@ -599,6 +602,12 @@ impl<'a> FromKclValue<'a> for TagIdentifier {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> FromKclValue<'a> for KclValue {
|
||||
fn from_mem_item(arg: &'a KclValue) -> Option<Self> {
|
||||
Some(arg.clone())
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! impl_from_arg_via_json {
|
||||
($typ:path) => {
|
||||
impl<'a> FromKclValue<'a> for $typ {
|
||||
@ -609,6 +618,15 @@ macro_rules! impl_from_arg_via_json {
|
||||
};
|
||||
}
|
||||
|
||||
impl<'a, T> FromKclValue<'a> for Vec<T>
|
||||
where
|
||||
T: serde::de::DeserializeOwned + FromKclValue<'a>,
|
||||
{
|
||||
fn from_mem_item(arg: &'a KclValue) -> Option<Self> {
|
||||
from_user_val(arg)
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! impl_from_arg_for_array {
|
||||
($n:literal) => {
|
||||
impl<'a, T> FromKclValue<'a> for [T; $n]
|
||||
@ -722,23 +740,3 @@ impl<'a> FromKclValue<'a> for SketchSurface {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> FromKclValue<'a> for Vec<Sketch> {
|
||||
fn from_mem_item(arg: &'a KclValue) -> Option<Self> {
|
||||
let KclValue::UserVal(uv) = arg else {
|
||||
return None;
|
||||
};
|
||||
|
||||
uv.get::<Vec<Sketch>>().map(|x| x.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> FromKclValue<'a> for Vec<u64> {
|
||||
fn from_mem_item(arg: &'a KclValue) -> Option<Self> {
|
||||
let KclValue::UserVal(uv) = arg else {
|
||||
return None;
|
||||
};
|
||||
|
||||
uv.get::<Vec<u64>>().map(|x| x.0)
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
use derive_docs::stdlib;
|
||||
use serde_json::Value as JValue;
|
||||
|
||||
use super::{args::FromArgs, Args, FnAsArg};
|
||||
use crate::{
|
||||
@ -7,8 +8,96 @@ use crate::{
|
||||
function_param::FunctionParam,
|
||||
};
|
||||
|
||||
/// Apply a function to each element of an array.
|
||||
pub async fn map(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
||||
let (array, f): (Vec<JValue>, FnAsArg<'_>) = FromArgs::from_args(&args, 0)?;
|
||||
let array: Vec<KclValue> = array
|
||||
.into_iter()
|
||||
.map(|jval| {
|
||||
KclValue::UserVal(UserVal {
|
||||
value: jval,
|
||||
meta: vec![args.source_range.into()],
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
let map_fn = FunctionParam {
|
||||
inner: f.func,
|
||||
fn_expr: f.expr,
|
||||
meta: vec![args.source_range.into()],
|
||||
ctx: args.ctx.clone(),
|
||||
memory: *f.memory,
|
||||
};
|
||||
let new_array = inner_map(array, map_fn, exec_state, &args).await?;
|
||||
let uv = UserVal::new(vec![args.source_range.into()], new_array);
|
||||
Ok(KclValue::UserVal(uv))
|
||||
}
|
||||
|
||||
/// Apply a function to every element of a list.
|
||||
///
|
||||
/// Given a list like `[a, b, c]`, and a function like `f`, returns
|
||||
/// `[f(a), f(b), f(c)]`
|
||||
/// ```no_run
|
||||
/// const r = 10 // radius
|
||||
/// fn drawCircle = (id) => {
|
||||
/// return startSketchOn("XY")
|
||||
/// |> circle({ center: [id * 2 * r, 0], radius: r}, %)
|
||||
/// }
|
||||
///
|
||||
/// // Call `drawCircle`, passing in each element of the array.
|
||||
/// // The outputs from each `drawCircle` form a new array,
|
||||
/// // which is the return value from `map`.
|
||||
/// const circles = map(
|
||||
/// [1..3],
|
||||
/// drawCircle
|
||||
/// )
|
||||
/// ```
|
||||
/// ```no_run
|
||||
/// const r = 10 // radius
|
||||
/// // Call `map`, using an anonymous function instead of a named one.
|
||||
/// const circles = map(
|
||||
/// [1..3],
|
||||
/// (id) => {
|
||||
/// return startSketchOn("XY")
|
||||
/// |> circle({ center: [id * 2 * r, 0], radius: r}, %)
|
||||
/// }
|
||||
/// )
|
||||
/// ```
|
||||
#[stdlib {
|
||||
name = "map",
|
||||
}]
|
||||
async fn inner_map<'a>(
|
||||
array: Vec<KclValue>,
|
||||
map_fn: FunctionParam<'a>,
|
||||
exec_state: &mut ExecState,
|
||||
args: &'a Args,
|
||||
) -> Result<Vec<KclValue>, KclError> {
|
||||
let mut new_array = Vec::with_capacity(array.len());
|
||||
for elem in array {
|
||||
let new_elem = call_map_closure(elem, &map_fn, args.source_range, exec_state).await?;
|
||||
new_array.push(new_elem);
|
||||
}
|
||||
Ok(new_array)
|
||||
}
|
||||
|
||||
async fn call_map_closure<'a>(
|
||||
input: KclValue,
|
||||
map_fn: &FunctionParam<'a>,
|
||||
source_range: SourceRange,
|
||||
exec_state: &mut ExecState,
|
||||
) -> Result<KclValue, KclError> {
|
||||
let output = map_fn.call(exec_state, vec![input]).await?;
|
||||
let source_ranges = vec![source_range];
|
||||
let output = output.ok_or_else(|| {
|
||||
KclError::Semantic(KclErrorDetails {
|
||||
message: "Map function must return a value".to_string(),
|
||||
source_ranges,
|
||||
})
|
||||
})?;
|
||||
Ok(output)
|
||||
}
|
||||
|
||||
/// For each item in an array, update a value.
|
||||
pub async fn array_reduce(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
||||
pub async fn reduce(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
||||
let (array, start, f): (Vec<u64>, Sketch, FnAsArg<'_>) = FromArgs::from_args(&args, 0)?;
|
||||
let reduce_fn = FunctionParam {
|
||||
inner: f.func,
|
||||
@ -17,7 +106,7 @@ pub async fn array_reduce(exec_state: &mut ExecState, args: Args) -> Result<KclV
|
||||
ctx: args.ctx.clone(),
|
||||
memory: *f.memory,
|
||||
};
|
||||
inner_array_reduce(array, start, reduce_fn, exec_state, &args)
|
||||
inner_reduce(array, start, reduce_fn, exec_state, &args)
|
||||
.await
|
||||
.map(|sg| KclValue::UserVal(UserVal::new(sg.meta.clone(), sg)))
|
||||
}
|
||||
@ -28,7 +117,7 @@ pub async fn array_reduce(exec_state: &mut ExecState, args: Args) -> Result<KclV
|
||||
/// fn decagon = (radius) => {
|
||||
/// let step = (1/10) * tau()
|
||||
/// let sketch001 = startSketchAt([(cos(0)*radius), (sin(0) * radius)])
|
||||
/// return arrayReduce([1..10], sketch001, (i, sg) => {
|
||||
/// return reduce([1..10], sketch001, (i, sg) => {
|
||||
/// let x = cos(step * i) * radius
|
||||
/// let y = sin(step * i) * radius
|
||||
/// return lineTo([x, y], sg)
|
||||
@ -37,9 +126,9 @@ pub async fn array_reduce(exec_state: &mut ExecState, args: Args) -> Result<KclV
|
||||
/// decagon(5.0) |> close(%)
|
||||
/// ```
|
||||
#[stdlib {
|
||||
name = "arrayReduce",
|
||||
name = "reduce",
|
||||
}]
|
||||
async fn inner_array_reduce<'a>(
|
||||
async fn inner_reduce<'a>(
|
||||
array: Vec<u64>,
|
||||
start: Sketch,
|
||||
reduce_fn: FunctionParam<'a>,
|
||||
|
@ -247,7 +247,7 @@ pub(crate) async fn do_post_extrude(sketch: Sketch, length: f64, args: Args) ->
|
||||
Some(extrude_surface)
|
||||
}
|
||||
}
|
||||
} else if args.ctx.is_mock {
|
||||
} else if args.ctx.is_mock() {
|
||||
// Only pre-populate the extrude surface if we are in mock mode.
|
||||
|
||||
let extrude_surface = ExtrudeSurface::ExtrudePlane(crate::executor::ExtrudePlane {
|
||||
@ -296,7 +296,7 @@ fn analyze_faces(args: &Args, face_infos: Vec<ExtrusionFaceInfo>) -> Faces {
|
||||
sides: HashMap::with_capacity(face_infos.len()),
|
||||
..Default::default()
|
||||
};
|
||||
if args.ctx.is_mock {
|
||||
if args.ctx.is_mock() {
|
||||
// Create fake IDs for start and end caps, to make extrudes mock-execute safe
|
||||
faces.start_cap_id = Some(Uuid::new_v4());
|
||||
faces.end_cap_id = Some(Uuid::new_v4());
|
||||
|
@ -228,7 +228,7 @@ pub async fn get_opposite_edge(exec_state: &mut ExecState, args: Args) -> Result
|
||||
name = "getOppositeEdge",
|
||||
}]
|
||||
async fn inner_get_opposite_edge(tag: TagIdentifier, exec_state: &mut ExecState, args: Args) -> Result<Uuid, KclError> {
|
||||
if args.ctx.is_mock {
|
||||
if args.ctx.is_mock() {
|
||||
return Ok(Uuid::new_v4());
|
||||
}
|
||||
let face_id = args.get_adjacent_face_to_tag(exec_state, &tag, false).await?;
|
||||
@ -309,7 +309,7 @@ async fn inner_get_next_adjacent_edge(
|
||||
exec_state: &mut ExecState,
|
||||
args: Args,
|
||||
) -> Result<Uuid, KclError> {
|
||||
if args.ctx.is_mock {
|
||||
if args.ctx.is_mock() {
|
||||
return Ok(Uuid::new_v4());
|
||||
}
|
||||
let face_id = args.get_adjacent_face_to_tag(exec_state, &tag, false).await?;
|
||||
@ -398,7 +398,7 @@ async fn inner_get_previous_adjacent_edge(
|
||||
exec_state: &mut ExecState,
|
||||
args: Args,
|
||||
) -> Result<Uuid, KclError> {
|
||||
if args.ctx.is_mock {
|
||||
if args.ctx.is_mock() {
|
||||
return Ok(Uuid::new_v4());
|
||||
}
|
||||
let face_id = args.get_adjacent_face_to_tag(exec_state, &tag, false).await?;
|
||||
|
@ -284,7 +284,7 @@ async fn inner_import(
|
||||
}
|
||||
}
|
||||
|
||||
if args.ctx.is_mock {
|
||||
if args.ctx.is_mock() {
|
||||
return Ok(ImportedGeometry {
|
||||
id: uuid::Uuid::new_v4(),
|
||||
value: import_files.iter().map(|f| f.path.to_string()).collect(),
|
||||
|