Compare commits
18 Commits
kcl-49
...
lf94/mac-m
Author | SHA1 | Date | |
---|---|---|---|
19a8a2bba8 | |||
e9806b83d7 | |||
0229105158 | |||
9e37e13b6b | |||
58e0c0e916 | |||
dd99c27d56 | |||
3cff26b987 | |||
78ac5b0a11 | |||
24d0b14668 | |||
6fb32eeff2 | |||
ec64daa01f | |||
e8886bb358 | |||
05a6313d97 | |||
80f78e1c61 | |||
c441a3ab1c | |||
e894242768 | |||
d8dff03746 | |||
60aee7ddba |
39
.github/workflows/build-apps.yml
vendored
@ -33,26 +33,63 @@ jobs:
|
|||||||
|
|
||||||
- run: yarn install
|
- run: yarn install
|
||||||
|
|
||||||
|
- id: filter
|
||||||
|
name: Check for Rust changes
|
||||||
|
uses: dorny/paths-filter@v3
|
||||||
|
with:
|
||||||
|
filters: |
|
||||||
|
rust:
|
||||||
|
- 'rust/**'
|
||||||
|
|
||||||
|
- name: Download Wasm Cache
|
||||||
|
id: download-wasm
|
||||||
|
if: ${{ github.event_name == 'pull_request' && steps.filter.outputs.rust == 'false' }}
|
||||||
|
uses: dawidd6/action-download-artifact@v7
|
||||||
|
continue-on-error: true
|
||||||
|
with:
|
||||||
|
github_token: ${{secrets.GITHUB_TOKEN}}
|
||||||
|
name: wasm-bundle
|
||||||
|
workflow: build-and-store-wasm.yml
|
||||||
|
branch: main
|
||||||
|
path: rust/kcl-wasm-lib/pkg
|
||||||
|
|
||||||
|
- name: Build WASM condition
|
||||||
|
id: wasm
|
||||||
|
run: |
|
||||||
|
set -euox pipefail
|
||||||
|
# Build wasm if this is a push to main or tag, there are Rust changes, or
|
||||||
|
# downloading from the wasm cache failed.
|
||||||
|
if [[ ${{github.event_name}} == 'push' || ${{steps.filter.outputs.rust}} == 'true' || ${{steps.download-wasm.outcome}} == 'failure' ]]; then
|
||||||
|
echo "should-build-wasm=true" >> $GITHUB_OUTPUT
|
||||||
|
else
|
||||||
|
echo "should-build-wasm=false" >> $GITHUB_OUTPUT
|
||||||
|
fi
|
||||||
|
|
||||||
- name: Use correct Rust toolchain
|
- name: Use correct Rust toolchain
|
||||||
|
if: ${{ steps.wasm.outputs.should-build-wasm == 'true' }}
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
[ -e rust-toolchain.toml ] || cp rust/rust-toolchain.toml ./
|
[ -e rust-toolchain.toml ] || cp rust/rust-toolchain.toml ./
|
||||||
|
|
||||||
- name: Install rust
|
- name: Install rust
|
||||||
|
if: ${{ steps.wasm.outputs.should-build-wasm == 'true' }}
|
||||||
uses: actions-rust-lang/setup-rust-toolchain@v1
|
uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||||
with:
|
with:
|
||||||
cache: false # Configured below.
|
cache: false # Configured below.
|
||||||
|
|
||||||
# TODO: see if we can fetch from main instead if no diff at rust
|
|
||||||
- uses: taiki-e/install-action@955a6ff1416eae278c9f833008a9beb4b7f9afe3
|
- uses: taiki-e/install-action@955a6ff1416eae278c9f833008a9beb4b7f9afe3
|
||||||
|
if: ${{ steps.wasm.outputs.should-build-wasm == 'true' }}
|
||||||
with:
|
with:
|
||||||
tool: wasm-pack
|
tool: wasm-pack
|
||||||
|
|
||||||
- name: Rust Cache
|
- name: Rust Cache
|
||||||
|
if: ${{ steps.wasm.outputs.should-build-wasm == 'true' }}
|
||||||
uses: Swatinem/rust-cache@v2
|
uses: Swatinem/rust-cache@v2
|
||||||
with:
|
with:
|
||||||
workspaces: rust
|
workspaces: rust
|
||||||
|
|
||||||
- name: Run build:wasm
|
- name: Run build:wasm
|
||||||
|
if: ${{ steps.wasm.outputs.should-build-wasm == 'true' }}
|
||||||
run: "yarn build:wasm"
|
run: "yarn build:wasm"
|
||||||
|
|
||||||
- name: Set nightly version, product name, release notes, and icons
|
- name: Set nightly version, product name, release notes, and icons
|
||||||
|
3
.github/workflows/cargo-bench.yml
vendored
@ -50,12 +50,13 @@ jobs:
|
|||||||
- name: Build the benchmark target(s)
|
- name: Build the benchmark target(s)
|
||||||
run: |
|
run: |
|
||||||
cd rust
|
cd rust
|
||||||
cargo codspeed build
|
cargo codspeed build --measurement-mode walltime
|
||||||
- name: Run the benchmarks
|
- name: Run the benchmarks
|
||||||
uses: CodSpeedHQ/action@v3
|
uses: CodSpeedHQ/action@v3
|
||||||
with:
|
with:
|
||||||
working-directory: rust
|
working-directory: rust
|
||||||
run: cargo codspeed run
|
run: cargo codspeed run
|
||||||
token: ${{ secrets.CODSPEED_TOKEN }}
|
token: ${{ secrets.CODSPEED_TOKEN }}
|
||||||
|
mode: walltime
|
||||||
env:
|
env:
|
||||||
KITTYCAD_API_TOKEN: ${{ secrets.KITTYCAD_API_TOKEN }}
|
KITTYCAD_API_TOKEN: ${{ secrets.KITTYCAD_API_TOKEN }}
|
||||||
|
3
.gitignore
vendored
@ -53,13 +53,14 @@ e2e/playwright/export-snapshots/*
|
|||||||
|
|
||||||
/public/kcl-samples.zip
|
/public/kcl-samples.zip
|
||||||
/public/kcl-samples/.github
|
/public/kcl-samples/.github
|
||||||
|
/public/kcl-samples/screenshots/main.kcl
|
||||||
|
/public/kcl-samples/step/main.kcl
|
||||||
/test-results/
|
/test-results/
|
||||||
/playwright-report/
|
/playwright-report/
|
||||||
/blob-report/
|
/blob-report/
|
||||||
/playwright/.cache/
|
/playwright/.cache/
|
||||||
/src/lang/std/artifactMapCache
|
/src/lang/std/artifactMapCache
|
||||||
|
|
||||||
|
|
||||||
## generated files
|
## generated files
|
||||||
src/**/*.typegen.ts
|
src/**/*.typegen.ts
|
||||||
|
|
||||||
|
@ -6,7 +6,7 @@ layout: manual
|
|||||||
|
|
||||||
Extend a 2-dimensional sketch through a third dimension in order to create new 3-dimensional volume, or if extruded into an existing volume, cut into an existing solid.
|
Extend a 2-dimensional sketch through a third dimension in order to create new 3-dimensional volume, or if extruded into an existing volume, cut into an existing solid.
|
||||||
|
|
||||||
|
You can provide more than one sketch to extrude, and they will all be extruded in the same direction.
|
||||||
|
|
||||||
```js
|
```js
|
||||||
extrude(
|
extrude(
|
||||||
@ -20,7 +20,7 @@ extrude(
|
|||||||
|
|
||||||
| Name | Type | Description | Required |
|
| Name | Type | Description | Required |
|
||||||
|----------|------|-------------|----------|
|
|----------|------|-------------|----------|
|
||||||
| `sketchSet` | [`SketchSet`](/docs/kcl/types/SketchSet) | Which sketches should be extruded | Yes |
|
| `sketchSet` | [`SketchSet`](/docs/kcl/types/SketchSet) | Which sketch or set of sketches should be extruded | Yes |
|
||||||
| `length` | [`number`](/docs/kcl/types/number) | How far to extrude the given sketches | Yes |
|
| `length` | [`number`](/docs/kcl/types/number) | How far to extrude the given sketches | Yes |
|
||||||
|
|
||||||
### Returns
|
### Returns
|
||||||
|
17418
docs/kcl/std.json
@ -24,6 +24,5 @@ A face.
|
|||||||
| `zAxis` |[`Point3d`](/docs/kcl/types/Point3d)| The z-axis (normal). | 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 |
|
| `solid` |[`Solid`](/docs/kcl/types/Solid)| The solid the face is on. | No |
|
||||||
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A unit of length. | No |
|
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A unit of length. | No |
|
||||||
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -22,6 +22,5 @@ A helix.
|
|||||||
| `angleStart` |[`number`](/docs/kcl/types/number)| Start angle (in degrees). | No |
|
| `angleStart` |[`number`](/docs/kcl/types/number)| Start angle (in degrees). | No |
|
||||||
| `ccw` |`boolean`| Is the helix rotation counter clockwise? | No |
|
| `ccw` |`boolean`| Is the helix rotation counter clockwise? | No |
|
||||||
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A unit of length. | No |
|
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A unit of length. | No |
|
||||||
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -22,6 +22,5 @@ A helix.
|
|||||||
| `angleStart` |[`number`](/docs/kcl/types/number)| Start angle (in degrees). | No |
|
| `angleStart` |[`number`](/docs/kcl/types/number)| Start angle (in degrees). | No |
|
||||||
| `ccw` |`boolean`| Is the helix rotation counter clockwise? | No |
|
| `ccw` |`boolean`| Is the helix rotation counter clockwise? | No |
|
||||||
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A unit of length. | No |
|
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A unit of length. | No |
|
||||||
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -18,6 +18,5 @@ Data for an imported geometry.
|
|||||||
|----------|------|-------------|----------|
|
|----------|------|-------------|----------|
|
||||||
| `id` |[`string`](/docs/kcl/types/string)| The ID of the imported geometry. | No |
|
| `id` |[`string`](/docs/kcl/types/string)| The ID of the imported geometry. | No |
|
||||||
| `value` |`[` [`string`](/docs/kcl/types/string) `]`| The original file paths. | No |
|
| `value` |`[` [`string`](/docs/kcl/types/string) `]`| The original file paths. | No |
|
||||||
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -25,7 +25,6 @@ Any KCL value.
|
|||||||
|----------|------|-------------|----------|
|
|----------|------|-------------|----------|
|
||||||
| `type` |enum: `Uuid`| | No |
|
| `type` |enum: `Uuid`| | No |
|
||||||
| `value` |[`string`](/docs/kcl/types/string)| | No |
|
| `value` |[`string`](/docs/kcl/types/string)| | No |
|
||||||
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
|
|
||||||
|
|
||||||
|
|
||||||
----
|
----
|
||||||
@ -42,7 +41,6 @@ Any KCL value.
|
|||||||
|----------|------|-------------|----------|
|
|----------|------|-------------|----------|
|
||||||
| `type` |enum: `Bool`| | No |
|
| `type` |enum: `Bool`| | No |
|
||||||
| `value` |`boolean`| | No |
|
| `value` |`boolean`| | No |
|
||||||
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
|
|
||||||
|
|
||||||
|
|
||||||
----
|
----
|
||||||
@ -60,7 +58,6 @@ Any KCL value.
|
|||||||
| `type` |enum: `Number`| | No |
|
| `type` |enum: `Number`| | No |
|
||||||
| `value` |[`number`](/docs/kcl/types/number)| | No |
|
| `value` |[`number`](/docs/kcl/types/number)| | No |
|
||||||
| `ty` |[`NumericType`](/docs/kcl/types/NumericType)| | No |
|
| `ty` |[`NumericType`](/docs/kcl/types/NumericType)| | No |
|
||||||
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
|
|
||||||
|
|
||||||
|
|
||||||
----
|
----
|
||||||
@ -77,7 +74,6 @@ Any KCL value.
|
|||||||
|----------|------|-------------|----------|
|
|----------|------|-------------|----------|
|
||||||
| `type` |enum: `String`| | No |
|
| `type` |enum: `String`| | No |
|
||||||
| `value` |[`string`](/docs/kcl/types/string)| | No |
|
| `value` |[`string`](/docs/kcl/types/string)| | No |
|
||||||
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
|
|
||||||
|
|
||||||
|
|
||||||
----
|
----
|
||||||
@ -94,7 +90,6 @@ Any KCL value.
|
|||||||
|----------|------|-------------|----------|
|
|----------|------|-------------|----------|
|
||||||
| `type` |enum: `MixedArray`| | No |
|
| `type` |enum: `MixedArray`| | No |
|
||||||
| `value` |`[` [`KclValue`](/docs/kcl/types/KclValue) `]`| | No |
|
| `value` |`[` [`KclValue`](/docs/kcl/types/KclValue) `]`| | No |
|
||||||
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
|
|
||||||
|
|
||||||
|
|
||||||
----
|
----
|
||||||
@ -111,7 +106,6 @@ Any KCL value.
|
|||||||
|----------|------|-------------|----------|
|
|----------|------|-------------|----------|
|
||||||
| `type` |enum: `Object`| | No |
|
| `type` |enum: `Object`| | No |
|
||||||
| `value` |`object`| | No |
|
| `value` |`object`| | No |
|
||||||
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
|
|
||||||
|
|
||||||
|
|
||||||
----
|
----
|
||||||
@ -129,7 +123,6 @@ Any KCL value.
|
|||||||
| `type` |enum: [`TagIdentifier`](/docs/kcl/types#tag-identifier)| | No |
|
| `type` |enum: [`TagIdentifier`](/docs/kcl/types#tag-identifier)| | No |
|
||||||
| `value` |[`string`](/docs/kcl/types/string)| | No |
|
| `value` |[`string`](/docs/kcl/types/string)| | No |
|
||||||
| `info` |[`TagEngineInfo`](/docs/kcl/types/TagEngineInfo)| | No |
|
| `info` |[`TagEngineInfo`](/docs/kcl/types/TagEngineInfo)| | No |
|
||||||
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
|
|
||||||
|
|
||||||
|
|
||||||
----
|
----
|
||||||
@ -279,7 +272,6 @@ Data for an imported geometry.
|
|||||||
| `type` |enum: [`ImportedGeometry`](/docs/kcl/types/ImportedGeometry)| | No |
|
| `type` |enum: [`ImportedGeometry`](/docs/kcl/types/ImportedGeometry)| | No |
|
||||||
| `id` |[`string`](/docs/kcl/types/string)| The ID of the imported geometry. | No |
|
| `id` |[`string`](/docs/kcl/types/string)| The ID of the imported geometry. | No |
|
||||||
| `value` |`[` [`string`](/docs/kcl/types/string) `]`| The original file paths. | No |
|
| `value` |`[` [`string`](/docs/kcl/types/string) `]`| The original file paths. | No |
|
||||||
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
|
|
||||||
|
|
||||||
|
|
||||||
----
|
----
|
||||||
@ -295,7 +287,6 @@ Data for an imported geometry.
|
|||||||
| Property | Type | Description | Required |
|
| Property | Type | Description | Required |
|
||||||
|----------|------|-------------|----------|
|
|----------|------|-------------|----------|
|
||||||
| `type` |enum: `Function`| | No |
|
| `type` |enum: `Function`| | No |
|
||||||
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
|
|
||||||
|
|
||||||
|
|
||||||
----
|
----
|
||||||
@ -312,7 +303,6 @@ Data for an imported geometry.
|
|||||||
|----------|------|-------------|----------|
|
|----------|------|-------------|----------|
|
||||||
| `type` |enum: `Module`| | No |
|
| `type` |enum: `Module`| | No |
|
||||||
| `value` |[`ModuleId`](/docs/kcl/types/ModuleId)| Identifier of a source file. Uses a u32 to keep the size small. | No |
|
| `value` |[`ModuleId`](/docs/kcl/types/ModuleId)| Identifier of a source file. Uses a u32 to keep the size small. | No |
|
||||||
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
|
|
||||||
|
|
||||||
|
|
||||||
----
|
----
|
||||||
@ -328,7 +318,6 @@ Data for an imported geometry.
|
|||||||
| Property | Type | Description | Required |
|
| Property | Type | Description | Required |
|
||||||
|----------|------|-------------|----------|
|
|----------|------|-------------|----------|
|
||||||
| `type` |enum: `Type`| | No |
|
| `type` |enum: `Type`| | No |
|
||||||
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
|
|
||||||
|
|
||||||
|
|
||||||
----
|
----
|
||||||
@ -345,7 +334,6 @@ Data for an imported geometry.
|
|||||||
|----------|------|-------------|----------|
|
|----------|------|-------------|----------|
|
||||||
| `type` |enum: [`KclNone`](/docs/kcl/types/KclNone)| | No |
|
| `type` |enum: [`KclNone`](/docs/kcl/types/KclNone)| | No |
|
||||||
| `value` |[`KclNone`](/docs/kcl/types/KclNone)| 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). | No |
|
| `value` |[`KclNone`](/docs/kcl/types/KclNone)| 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). | No |
|
||||||
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
|
|
||||||
|
|
||||||
|
|
||||||
----
|
----
|
||||||
@ -362,7 +350,6 @@ Data for an imported geometry.
|
|||||||
|----------|------|-------------|----------|
|
|----------|------|-------------|----------|
|
||||||
| `type` |enum: `Tombstone`| | No |
|
| `type` |enum: `Tombstone`| | No |
|
||||||
| `value` |`null`| | No |
|
| `value` |`null`| | No |
|
||||||
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
|
|
||||||
|
|
||||||
|
|
||||||
----
|
----
|
||||||
|
@ -32,7 +32,6 @@ A sketch or a group of sketches.
|
|||||||
| `artifactId` |[`ArtifactId`](/docs/kcl/types/ArtifactId)| The original id of the sketch. This stays the same even if the sketch is is sketched on face etc. | No |
|
| `artifactId` |[`ArtifactId`](/docs/kcl/types/ArtifactId)| The original id of the sketch. This stays the same even if the sketch is is sketched on face etc. | No |
|
||||||
| `originalId` |[`string`](/docs/kcl/types/string)| | No |
|
| `originalId` |[`string`](/docs/kcl/types/string)| | No |
|
||||||
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A unit of length. | No |
|
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A unit of length. | No |
|
||||||
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| Metadata. | No |
|
|
||||||
|
|
||||||
|
|
||||||
----
|
----
|
||||||
|
@ -32,7 +32,6 @@ A sketch type.
|
|||||||
| `yAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the plane's Y 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 |
|
| `zAxis` |[`Point3d`](/docs/kcl/types/Point3d)| The z-axis (normal). | No |
|
||||||
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A unit of length. | No |
|
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A unit of length. | No |
|
||||||
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
|
|
||||||
|
|
||||||
|
|
||||||
----
|
----
|
||||||
@ -57,7 +56,6 @@ A face.
|
|||||||
| `zAxis` |[`Point3d`](/docs/kcl/types/Point3d)| The z-axis (normal). | 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 |
|
| `solid` |[`Solid`](/docs/kcl/types/Solid)| The solid the face is on. | No |
|
||||||
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A unit of length. | No |
|
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A unit of length. | No |
|
||||||
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
|
|
||||||
|
|
||||||
|
|
||||||
----
|
----
|
||||||
|
@ -33,7 +33,6 @@ Data for a solid or an imported geometry.
|
|||||||
| `endCapId` |[`string`](/docs/kcl/types/string)| The id of the extrusion end cap | No |
|
| `endCapId` |[`string`](/docs/kcl/types/string)| The id of the extrusion end cap | No |
|
||||||
| `edgeCuts` |`[` [`EdgeCut`](/docs/kcl/types/EdgeCut) `]`| Chamfers or fillets on this solid. | No |
|
| `edgeCuts` |`[` [`EdgeCut`](/docs/kcl/types/EdgeCut) `]`| Chamfers or fillets on this solid. | No |
|
||||||
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A unit of length. | No |
|
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A unit of length. | No |
|
||||||
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| Metadata. | No |
|
|
||||||
|
|
||||||
|
|
||||||
----
|
----
|
||||||
@ -52,7 +51,21 @@ Data for an imported geometry.
|
|||||||
| `type` |enum: `importedGeometry`| | No |
|
| `type` |enum: `importedGeometry`| | No |
|
||||||
| `id` |[`string`](/docs/kcl/types/string)| The ID of the imported geometry. | No |
|
| `id` |[`string`](/docs/kcl/types/string)| The ID of the imported geometry. | No |
|
||||||
| `value` |`[` [`string`](/docs/kcl/types/string) `]`| The original file paths. | No |
|
| `value` |`[` [`string`](/docs/kcl/types/string) `]`| The original file paths. | No |
|
||||||
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
|
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
**Type:** `[object, array]`
|
||||||
|
|
||||||
|
`[` [`Solid`](/docs/kcl/types/Solid) `]`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Properties
|
||||||
|
|
||||||
|
| Property | Type | Description | Required |
|
||||||
|
|----------|------|-------------|----------|
|
||||||
|
| `type` |enum: `solidSet`| | No |
|
||||||
|
|
||||||
|
|
||||||
----
|
----
|
||||||
|
@ -33,7 +33,6 @@ A solid or a group of solids.
|
|||||||
| `endCapId` |[`string`](/docs/kcl/types/string)| The id of the extrusion end cap | No |
|
| `endCapId` |[`string`](/docs/kcl/types/string)| The id of the extrusion end cap | No |
|
||||||
| `edgeCuts` |`[` [`EdgeCut`](/docs/kcl/types/EdgeCut) `]`| Chamfers or fillets on this solid. | No |
|
| `edgeCuts` |`[` [`EdgeCut`](/docs/kcl/types/EdgeCut) `]`| Chamfers or fillets on this solid. | No |
|
||||||
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A unit of length. | No |
|
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A unit of length. | No |
|
||||||
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| Metadata. | No |
|
|
||||||
|
|
||||||
|
|
||||||
----
|
----
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { test, expect, Page } from './zoo-test'
|
import { Page } from '@playwright/test'
|
||||||
|
import { test, expect } from './zoo-test'
|
||||||
import {
|
import {
|
||||||
getUtils,
|
getUtils,
|
||||||
TEST_COLORS,
|
TEST_COLORS,
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { test, expect, Page } from './zoo-test'
|
import { Page } from '@playwright/test'
|
||||||
|
import { test, expect } from './zoo-test'
|
||||||
import { HomePageFixture } from './fixtures/homePageFixture'
|
import { HomePageFixture } from './fixtures/homePageFixture'
|
||||||
import { getUtils } from './test-utils'
|
import { getUtils } from './test-utils'
|
||||||
import { EngineCommand } from 'lang/std/artifactGraph'
|
import { EngineCommand } from 'lang/std/artifactGraph'
|
||||||
|
@ -10,7 +10,11 @@ import fsp from 'fs/promises'
|
|||||||
test(
|
test(
|
||||||
'export works on the first try',
|
'export works on the first try',
|
||||||
{ tag: ['@electron', '@skipLocalEngine'] },
|
{ tag: ['@electron', '@skipLocalEngine'] },
|
||||||
async ({ page, context, scene }, testInfo) => {
|
async ({ page, context, scene, tronApp }, testInfo) => {
|
||||||
|
if (!tronApp) {
|
||||||
|
fail()
|
||||||
|
}
|
||||||
|
|
||||||
await context.folderSetupFn(async (dir) => {
|
await context.folderSetupFn(async (dir) => {
|
||||||
const bracketDir = path.join(dir, 'bracket')
|
const bracketDir = path.join(dir, 'bracket')
|
||||||
await Promise.all([fsp.mkdir(bracketDir, { recursive: true })])
|
await Promise.all([fsp.mkdir(bracketDir, { recursive: true })])
|
||||||
@ -86,7 +90,7 @@ test(
|
|||||||
await expect(exportingToastMessage).not.toBeVisible()
|
await expect(exportingToastMessage).not.toBeVisible()
|
||||||
|
|
||||||
const firstFileFullPath = path.resolve(
|
const firstFileFullPath = path.resolve(
|
||||||
getPlaywrightDownloadDir(page),
|
getPlaywrightDownloadDir(tronApp.projectDirName),
|
||||||
exportFileName
|
exportFileName
|
||||||
)
|
)
|
||||||
await test.step('Check the export size', async () => {
|
await test.step('Check the export size', async () => {
|
||||||
@ -165,7 +169,7 @@ test(
|
|||||||
]))
|
]))
|
||||||
|
|
||||||
const secondFileFullPath = path.resolve(
|
const secondFileFullPath = path.resolve(
|
||||||
getPlaywrightDownloadDir(page),
|
getPlaywrightDownloadDir(tronApp.projectDirName),
|
||||||
exportFileName
|
exportFileName
|
||||||
)
|
)
|
||||||
await test.step('Check the export size', async () => {
|
await test.step('Check the export size', async () => {
|
||||||
@ -181,7 +185,7 @@ test(
|
|||||||
},
|
},
|
||||||
{ timeout: 15_000 }
|
{ timeout: 15_000 }
|
||||||
)
|
)
|
||||||
.toBeGreaterThan(100_000)
|
.toBeGreaterThan(70_000)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -158,11 +158,14 @@ test.describe('when using the file tree to', () => {
|
|||||||
await createNewFile('lee')
|
await createNewFile('lee')
|
||||||
|
|
||||||
await test.step('Postcondition: there are 5 new lee-*.kcl files', async () => {
|
await test.step('Postcondition: there are 5 new lee-*.kcl files', async () => {
|
||||||
await expect(
|
await expect
|
||||||
|
.poll(() =>
|
||||||
page
|
page
|
||||||
.locator('[data-testid="file-pane-scroll-container"] button')
|
.locator('[data-testid="file-pane-scroll-container"] button')
|
||||||
.filter({ hasText: /lee[-]?[0-5]?/ })
|
.filter({ hasText: /lee[-]?[0-5]?/ })
|
||||||
).toHaveCount(5)
|
.count()
|
||||||
|
)
|
||||||
|
.toEqual(5)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ -27,28 +27,19 @@ type CmdBarSerialised =
|
|||||||
|
|
||||||
export class CmdBarFixture {
|
export class CmdBarFixture {
|
||||||
public page: Page
|
public page: Page
|
||||||
|
public cmdBarOpenBtn!: Locator
|
||||||
get cmdBarOpenBtn() {
|
public cmdBarElement!: Locator
|
||||||
return this.page.getByTestId('command-bar-open-button')
|
|
||||||
}
|
|
||||||
|
|
||||||
get cmdBarElement() {
|
|
||||||
return this.page.getByTestId('command-bar')
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(page: Page) {
|
constructor(page: Page) {
|
||||||
this.page = page
|
this.page = page
|
||||||
|
this.cmdBarOpenBtn = this.page.getByTestId('command-bar-open-button')
|
||||||
|
this.cmdBarElement = this.page.getByTestId('command-bar')
|
||||||
}
|
}
|
||||||
|
|
||||||
get currentArgumentInput() {
|
get currentArgumentInput() {
|
||||||
return this.page.getByTestId('cmd-bar-arg-value')
|
return this.page.getByTestId('cmd-bar-arg-value')
|
||||||
}
|
}
|
||||||
|
|
||||||
// Put all selectors here because this method is re-run on fixture creation.
|
|
||||||
reConstruct = (page: Page) => {
|
|
||||||
this.page = page
|
|
||||||
}
|
|
||||||
|
|
||||||
private _serialiseCmdBar = async (): Promise<CmdBarSerialised> => {
|
private _serialiseCmdBar = async (): Promise<CmdBarSerialised> => {
|
||||||
if (!(await this.page.getByTestId('command-bar-wrapper').isVisible())) {
|
if (!(await this.page.getByTestId('command-bar-wrapper').isVisible())) {
|
||||||
return { stage: 'commandBarClosed' }
|
return { stage: 'commandBarClosed' }
|
||||||
|
@ -24,11 +24,6 @@ export class EditorFixture {
|
|||||||
|
|
||||||
constructor(page: Page) {
|
constructor(page: Page) {
|
||||||
this.page = page
|
this.page = page
|
||||||
this.reConstruct(page)
|
|
||||||
}
|
|
||||||
reConstruct = (page: Page) => {
|
|
||||||
this.page = page
|
|
||||||
|
|
||||||
this.codeContent = page.locator('.cm-content[data-language="kcl"]')
|
this.codeContent = page.locator('.cm-content[data-language="kcl"]')
|
||||||
this.diagnosticsTooltip = page.locator('.cm-tooltip-lint')
|
this.diagnosticsTooltip = page.locator('.cm-tooltip-lint')
|
||||||
this.diagnosticsGutterIcon = page.locator('.cm-lint-marker-error')
|
this.diagnosticsGutterIcon = page.locator('.cm-lint-marker-error')
|
||||||
@ -87,6 +82,30 @@ export class EditorFixture {
|
|||||||
toContain: this._expectEditorToContain(),
|
toContain: this._expectEditorToContain(),
|
||||||
not: { toContain: this._expectEditorToContain(true) },
|
not: { toContain: this._expectEditorToContain(true) },
|
||||||
}
|
}
|
||||||
|
snapshot = async (options?: { timeout?: number; name?: string }) => {
|
||||||
|
const wasPaneOpen = await this.checkIfPaneIsOpen()
|
||||||
|
if (!wasPaneOpen) {
|
||||||
|
await this.openPane()
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Use expect.poll to implement retry logic
|
||||||
|
await expect
|
||||||
|
.poll(
|
||||||
|
async () => {
|
||||||
|
const code = await this.codeContent.textContent()
|
||||||
|
return code || ''
|
||||||
|
},
|
||||||
|
{ timeout: options?.timeout || 5000 }
|
||||||
|
)
|
||||||
|
.toMatchSnapshot(options?.name || 'editor-content')
|
||||||
|
} finally {
|
||||||
|
// Reset pane state if needed
|
||||||
|
if (!wasPaneOpen) {
|
||||||
|
await this.closePane()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
private _serialiseDiagnostics = async (): Promise<Array<string>> => {
|
private _serialiseDiagnostics = async (): Promise<Array<string>> => {
|
||||||
const diagnostics = await this.diagnosticsGutterIcon.all()
|
const diagnostics = await this.diagnosticsGutterIcon.all()
|
||||||
const diagnosticsContent: string[] = []
|
const diagnosticsContent: string[] = []
|
||||||
|
@ -1,13 +1,31 @@
|
|||||||
|
/* eslint-disable react-hooks/rules-of-hooks */
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
BrowserContext,
|
BrowserContext,
|
||||||
ElectronApplication,
|
ElectronApplication,
|
||||||
|
Fixtures as PlaywrightFixtures,
|
||||||
TestInfo,
|
TestInfo,
|
||||||
Page,
|
Page,
|
||||||
} from '@playwright/test'
|
} from '@playwright/test'
|
||||||
|
|
||||||
import { getUtils, setup, setupElectron } from '../test-utils'
|
import {
|
||||||
|
_electron as electron,
|
||||||
|
PlaywrightTestArgs,
|
||||||
|
PlaywrightWorkerArgs,
|
||||||
|
} from '@playwright/test'
|
||||||
|
|
||||||
|
import * as TOML from '@iarna/toml'
|
||||||
|
import {
|
||||||
|
TEST_SETTINGS_KEY,
|
||||||
|
TEST_SETTINGS_CORRUPTED,
|
||||||
|
TEST_SETTINGS,
|
||||||
|
TEST_SETTINGS_DEFAULT_THEME,
|
||||||
|
} from '../storageStates'
|
||||||
|
import { SETTINGS_FILE_NAME, PROJECT_SETTINGS_FILE_NAME } from 'lib/constants'
|
||||||
|
import { getUtils, setup } from '../test-utils'
|
||||||
import fsp from 'fs/promises'
|
import fsp from 'fs/promises'
|
||||||
import { join } from 'path'
|
import fs from 'node:fs'
|
||||||
|
import path from 'path'
|
||||||
import { CmdBarFixture } from './cmdBarFixture'
|
import { CmdBarFixture } from './cmdBarFixture'
|
||||||
import { EditorFixture } from './editorFixture'
|
import { EditorFixture } from './editorFixture'
|
||||||
import { ToolbarFixture } from './toolbarFixture'
|
import { ToolbarFixture } from './toolbarFixture'
|
||||||
@ -23,7 +41,7 @@ export class AuthenticatedApp {
|
|||||||
public readonly testInfo: TestInfo
|
public readonly testInfo: TestInfo
|
||||||
public readonly viewPortSize = { width: 1200, height: 500 }
|
public readonly viewPortSize = { width: 1200, height: 500 }
|
||||||
public electronApp: undefined | ElectronApplication
|
public electronApp: undefined | ElectronApplication
|
||||||
public dir: string = ''
|
public projectDirName: string = ''
|
||||||
|
|
||||||
constructor(context: BrowserContext, page: Page, testInfo: TestInfo) {
|
constructor(context: BrowserContext, page: Page, testInfo: TestInfo) {
|
||||||
this.context = context
|
this.context = context
|
||||||
@ -46,7 +64,7 @@ export class AuthenticatedApp {
|
|||||||
}
|
}
|
||||||
getInputFile = (fileName: string) => {
|
getInputFile = (fileName: string) => {
|
||||||
return fsp.readFile(
|
return fsp.readFile(
|
||||||
join('rust', 'kcl-lib', 'e2e', 'executor', 'inputs', fileName),
|
path.join('rust', 'kcl-lib', 'e2e', 'executor', 'inputs', fileName),
|
||||||
'utf-8'
|
'utf-8'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -59,101 +77,326 @@ export interface Fixtures {
|
|||||||
scene: SceneFixture
|
scene: SceneFixture
|
||||||
homePage: HomePageFixture
|
homePage: HomePageFixture
|
||||||
}
|
}
|
||||||
export class AuthenticatedTronApp {
|
|
||||||
public originalPage: Page
|
|
||||||
public page: Page
|
|
||||||
public browserContext: BrowserContext
|
|
||||||
public context: BrowserContext
|
|
||||||
public readonly testInfo: TestInfo
|
|
||||||
public electronApp: ElectronApplication | undefined
|
|
||||||
public readonly viewPortSize = { width: 1200, height: 500 }
|
|
||||||
public dir: string = ''
|
|
||||||
|
|
||||||
constructor(
|
export class ElectronZoo {
|
||||||
browserContext: BrowserContext,
|
public available: boolean = true
|
||||||
originalPage: Page,
|
public electron!: ElectronApplication
|
||||||
testInfo: TestInfo
|
public firstUrl = ''
|
||||||
) {
|
public viewPortSize = { width: 1200, height: 500 }
|
||||||
this.page = originalPage
|
public projectDirName = ''
|
||||||
this.originalPage = originalPage
|
|
||||||
this.browserContext = browserContext
|
public page!: Page
|
||||||
// Will be overwritten in the initializer
|
public context!: BrowserContext
|
||||||
this.context = browserContext
|
|
||||||
this.testInfo = testInfo
|
constructor() {}
|
||||||
|
|
||||||
|
// Help remote end by signaling we're done with the connection.
|
||||||
|
// If it takes longer than 10s to stop, just resolve.
|
||||||
|
async makeAvailableAgain() {
|
||||||
|
await this.page.evaluate(async () => {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
if (!window.engineCommandManager.engineConnection?.state?.type) {
|
||||||
|
return resolve(undefined)
|
||||||
}
|
}
|
||||||
async initialise(
|
|
||||||
arg: {
|
|
||||||
fixtures: Partial<Fixtures>
|
|
||||||
folderSetupFn?: (projectDirName: string) => Promise<void>
|
|
||||||
cleanProjectDir?: boolean
|
|
||||||
appSettings?: DeepPartial<Settings>
|
|
||||||
} = { fixtures: {} }
|
|
||||||
) {
|
|
||||||
const { electronApp, page, context, dir } = await setupElectron({
|
|
||||||
testInfo: this.testInfo,
|
|
||||||
folderSetupFn: arg.folderSetupFn,
|
|
||||||
cleanProjectDir: arg.cleanProjectDir,
|
|
||||||
appSettings: arg.appSettings,
|
|
||||||
viewport: this.viewPortSize,
|
|
||||||
})
|
|
||||||
this.page = page
|
|
||||||
|
|
||||||
// These assignments "fix" some brokenness in the Playwright Workbench when
|
window.engineCommandManager.tearDown()
|
||||||
// running against electron applications.
|
|
||||||
// The timeline is still broken but failure screenshots work again.
|
|
||||||
this.context = context
|
|
||||||
// TODO: try to get this to work again for screenshots, but it messed with test ends when enabled
|
|
||||||
// Object.assign(this.browserContext, this.context)
|
|
||||||
|
|
||||||
this.electronApp = electronApp
|
// Keep polling (per js event tick) until state is Disconnected.
|
||||||
this.dir = dir
|
const timeA = Date.now()
|
||||||
|
const checkDisconnected = () => {
|
||||||
// Easier to access throughout utils
|
// It's possible we never even created an engineConnection
|
||||||
this.page.dir = dir
|
// e.g. never left Projects view.
|
||||||
|
|
||||||
// Setup localStorage, addCookies, reload
|
|
||||||
await setup(this.context, this.page, this.testInfo)
|
|
||||||
|
|
||||||
for (const key of unsafeTypedKeys(arg.fixtures)) {
|
|
||||||
const fixture = arg.fixtures[key]
|
|
||||||
if (
|
if (
|
||||||
!fixture ||
|
window.engineCommandManager?.engineConnection?.state.type ===
|
||||||
fixture instanceof AuthenticatedApp ||
|
'disconnected'
|
||||||
fixture instanceof AuthenticatedTronApp
|
) {
|
||||||
)
|
return resolve(undefined)
|
||||||
continue
|
|
||||||
fixture.reConstruct(page)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
close = async () => {
|
if (Date.now() - timeA > 10000) {
|
||||||
await this.electronApp?.close?.()
|
return resolve(undefined)
|
||||||
}
|
}
|
||||||
debugPause = () =>
|
|
||||||
new Promise(() => {
|
setTimeout(checkDisconnected, 0)
|
||||||
console.log('UN-RESOLVING PROMISE')
|
}
|
||||||
|
checkDisconnected()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
await this.context.tracing.stopChunk({ path: 'trace.zip' })
|
||||||
|
|
||||||
|
// Only after cleanup we're ready.
|
||||||
|
this.available = true
|
||||||
|
}
|
||||||
|
|
||||||
|
async createInstanceIfMissing(testInfo: TestInfo) {
|
||||||
|
// Create or otherwise clear the folder.
|
||||||
|
this.projectDirName = testInfo.outputPath('electron-test-projects-dir')
|
||||||
|
|
||||||
|
// We need to expose this in order for some tests that require folder
|
||||||
|
// creation and some code below.
|
||||||
|
const that = this
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
timeout: 120000,
|
||||||
|
args: ['.', '--no-sandbox'],
|
||||||
|
env: {
|
||||||
|
...process.env,
|
||||||
|
TEST_SETTINGS_FILE_KEY: this.projectDirName,
|
||||||
|
IS_PLAYWRIGHT: 'true',
|
||||||
|
},
|
||||||
|
...(process.env.ELECTRON_OVERRIDE_DIST_PATH
|
||||||
|
? {
|
||||||
|
executablePath:
|
||||||
|
process.env.ELECTRON_OVERRIDE_DIST_PATH + 'electron',
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
|
...(process.env.PLAYWRIGHT_RECORD_VIDEO
|
||||||
|
? {
|
||||||
|
recordVideo: {
|
||||||
|
dir: testInfo.snapshotPath(),
|
||||||
|
size: this.viewPortSize,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do this once and then reuse window on subsequent calls.
|
||||||
|
if (!this.electron) {
|
||||||
|
this.electron = await electron.launch(options)
|
||||||
|
|
||||||
|
// Mac takes quite a long time to create the first window in CI.
|
||||||
|
// Turns out we can't trust firstWindow() either. So loop.
|
||||||
|
let timeoutId: ReturnType<typeof setTimeout>
|
||||||
|
const tryToGetWindowPage = () =>
|
||||||
|
new Promise((resolve) => {
|
||||||
|
const fn = () => {
|
||||||
|
this.page = this.electron.windows()[0]
|
||||||
|
timeoutId = setTimeout(() => {
|
||||||
|
if (this.page) {
|
||||||
|
clearTimeout(timeoutId)
|
||||||
|
return resolve(undefined)
|
||||||
|
}
|
||||||
|
fn()
|
||||||
|
}, 0)
|
||||||
|
}
|
||||||
|
fn()
|
||||||
|
})
|
||||||
|
|
||||||
|
await tryToGetWindowPage()
|
||||||
|
|
||||||
|
this.context = this.electron.context()
|
||||||
|
await this.context.tracing.start({ screenshots: true, snapshots: true })
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.context.tracing.startChunk()
|
||||||
|
|
||||||
|
await setup(this.context, this.page, testInfo)
|
||||||
|
|
||||||
|
await this.cleanProjectDir()
|
||||||
|
|
||||||
|
// Create a consistent way to resize the page across electron and web.
|
||||||
|
// (lee) I had to do everything in the book to make electron change its
|
||||||
|
// damn window size. I succeeded in making it consistently and reliably
|
||||||
|
// do it after a whole afternoon.
|
||||||
|
this.page.setBodyDimensions = async function (dims: {
|
||||||
|
width: number
|
||||||
|
height: number
|
||||||
|
}) {
|
||||||
|
await this.setViewportSize(dims)
|
||||||
|
|
||||||
|
await that.electron?.evaluateHandle(async ({ app }, dims) => {
|
||||||
|
// @ts-ignore sorry jon but see comment in main.ts why this is ignored
|
||||||
|
await app.resizeWindow(dims.width, dims.height)
|
||||||
|
}, dims)
|
||||||
|
|
||||||
|
return this.evaluate(async (dims: { width: number; height: number }) => {
|
||||||
|
await window.electron.resizeWindow(dims.width, dims.height)
|
||||||
|
window.document.body.style.width = dims.width + 'px'
|
||||||
|
window.document.body.style.height = dims.height + 'px'
|
||||||
|
window.document.documentElement.style.width = dims.width + 'px'
|
||||||
|
window.document.documentElement.style.height = dims.height + 'px'
|
||||||
|
}, dims)
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.page.setBodyDimensions(this.viewPortSize)
|
||||||
|
|
||||||
|
this.context.folderSetupFn = async function (fn) {
|
||||||
|
return fn(that.projectDirName)
|
||||||
|
.then(() => that.page.reload())
|
||||||
|
.then(() => ({
|
||||||
|
dir: that.projectDirName,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
// We need to patch this because addInitScript will bind too late in our
|
||||||
|
// electron tests, never running. We need to call reload() after each call
|
||||||
|
// to guarantee it runs.
|
||||||
|
const oldContextAddInitScript = this.context.addInitScript
|
||||||
|
this.context.addInitScript = async function (a, b) {
|
||||||
|
// @ts-ignore pretty sure way out of tsc's type checking capabilities.
|
||||||
|
// This code works perfectly fine.
|
||||||
|
await oldContextAddInitScript.apply(this, [a, b])
|
||||||
|
await that.page.reload()
|
||||||
|
}
|
||||||
|
|
||||||
|
// No idea why we mix and match page and context's addInitScript but we do
|
||||||
|
const oldPageAddInitScript = this.page.addInitScript
|
||||||
|
this.page.addInitScript = async function (a: any, b: any) {
|
||||||
|
// @ts-ignore pretty sure way out of tsc's type checking capabilities.
|
||||||
|
// This code works perfectly fine.
|
||||||
|
await oldPageAddInitScript.apply(this, [a, b])
|
||||||
|
await that.page.reload()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.firstUrl) {
|
||||||
|
await this.page.getByText('Your Projects').count()
|
||||||
|
this.firstUrl = this.page.url()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Due to the app controlling its own window context we need to inject new
|
||||||
|
// options and context here.
|
||||||
|
// NOTE TO LEE: Seems to destroy page context when calling an electron loadURL.
|
||||||
|
// await tronApp.electronApp.evaluate(({ app }) => {
|
||||||
|
// return app.reuseWindowForTest();
|
||||||
|
// });
|
||||||
|
|
||||||
|
await this.electron?.evaluate(({ app }, projectDirName) => {
|
||||||
|
// @ts-ignore can't declaration merge see main.ts
|
||||||
|
app.testProperty['TEST_SETTINGS_FILE_KEY'] = projectDirName
|
||||||
|
}, this.projectDirName)
|
||||||
|
|
||||||
|
// Always start at the root view
|
||||||
|
await this.page.goto(this.firstUrl)
|
||||||
|
|
||||||
|
// Force a hard reload, destroying the stream and other state
|
||||||
|
await this.page.reload()
|
||||||
|
}
|
||||||
|
|
||||||
|
async cleanProjectDir(appSettings?: DeepPartial<Settings>) {
|
||||||
|
try {
|
||||||
|
if (fs.existsSync(this.projectDirName)) {
|
||||||
|
await fsp.rm(this.projectDirName, { recursive: true })
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await fsp.mkdir(this.projectDirName)
|
||||||
|
} catch (e) {
|
||||||
|
// Not a problem if it already exists.
|
||||||
|
}
|
||||||
|
|
||||||
|
const tempSettingsFilePath = path.join(
|
||||||
|
this.projectDirName,
|
||||||
|
SETTINGS_FILE_NAME
|
||||||
|
)
|
||||||
|
|
||||||
|
let settingsOverridesToml = ''
|
||||||
|
|
||||||
|
if (appSettings) {
|
||||||
|
settingsOverridesToml = TOML.stringify({
|
||||||
|
// @ts-expect-error
|
||||||
|
settings: {
|
||||||
|
...TEST_SETTINGS,
|
||||||
|
...appSettings,
|
||||||
|
app: {
|
||||||
|
...TEST_SETTINGS.app,
|
||||||
|
project_directory: this.projectDirName,
|
||||||
|
...appSettings.app,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
settingsOverridesToml = TOML.stringify({
|
||||||
|
// @ts-expect-error
|
||||||
|
settings: {
|
||||||
|
...TEST_SETTINGS,
|
||||||
|
app: {
|
||||||
|
...TEST_SETTINGS.app,
|
||||||
|
project_directory: this.projectDirName,
|
||||||
|
},
|
||||||
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
await fsp.writeFile(tempSettingsFilePath, settingsOverridesToml)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export const fixtures = {
|
// If yee encounter this, please try to type it.
|
||||||
cmdBar: async ({ page }: { page: Page }, use: any) => {
|
type FnUse = any
|
||||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
||||||
|
const fixturesForElectron = {
|
||||||
|
page: async (
|
||||||
|
{ tronApp }: { tronApp: ElectronZoo },
|
||||||
|
use: FnUse,
|
||||||
|
testInfo: TestInfo
|
||||||
|
) => {
|
||||||
|
await use(tronApp.page)
|
||||||
|
},
|
||||||
|
context: async (
|
||||||
|
{ tronApp }: { tronApp: ElectronZoo },
|
||||||
|
use: FnUse,
|
||||||
|
testInfo: TestInfo
|
||||||
|
) => {
|
||||||
|
await use(tronApp.context)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const fixturesForWeb = {
|
||||||
|
page: async (
|
||||||
|
{ page, context }: { page: Page; context: BrowserContext },
|
||||||
|
use: FnUse,
|
||||||
|
testInfo: TestInfo
|
||||||
|
) => {
|
||||||
|
page.setBodyDimensions = page.setViewportSize
|
||||||
|
|
||||||
|
// We do the same thing in ElectronZoo. addInitScript simply doesn't fire
|
||||||
|
// at the correct time, so we reload the page and it fires appropriately.
|
||||||
|
const oldPageAddInitScript = page.addInitScript
|
||||||
|
page.addInitScript = async function (...args) {
|
||||||
|
// @ts-expect-error
|
||||||
|
await oldPageAddInitScript.apply(this, args)
|
||||||
|
await page.reload()
|
||||||
|
}
|
||||||
|
|
||||||
|
const oldContextAddInitScript = context.addInitScript
|
||||||
|
context.addInitScript = async function (...args) {
|
||||||
|
// @ts-expect-error
|
||||||
|
await oldContextAddInitScript.apply(this, args)
|
||||||
|
await page.reload()
|
||||||
|
}
|
||||||
|
|
||||||
|
const webApp = new AuthenticatedApp(context, page, testInfo)
|
||||||
|
await webApp.initialise()
|
||||||
|
|
||||||
|
await use(page)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const fixturesBasedOnProcessEnvPlatform = {
|
||||||
|
cmdBar: async ({ page }: { page: Page }, use: FnUse) => {
|
||||||
await use(new CmdBarFixture(page))
|
await use(new CmdBarFixture(page))
|
||||||
},
|
},
|
||||||
editor: async ({ page }: { page: Page }, use: any) => {
|
editor: async ({ page }: { page: Page }, use: FnUse) => {
|
||||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
||||||
await use(new EditorFixture(page))
|
await use(new EditorFixture(page))
|
||||||
},
|
},
|
||||||
toolbar: async ({ page }: { page: Page }, use: any) => {
|
toolbar: async ({ page }: { page: Page }, use: FnUse) => {
|
||||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
||||||
await use(new ToolbarFixture(page))
|
await use(new ToolbarFixture(page))
|
||||||
},
|
},
|
||||||
scene: async ({ page }: { page: Page }, use: any) => {
|
scene: async ({ page }: { page: Page }, use: FnUse) => {
|
||||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
||||||
await use(new SceneFixture(page))
|
await use(new SceneFixture(page))
|
||||||
},
|
},
|
||||||
homePage: async ({ page }: { page: Page }, use: any) => {
|
homePage: async ({ page }: { page: Page }, use: FnUse) => {
|
||||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
||||||
await use(new HomePageFixture(page))
|
await use(new HomePageFixture(page))
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (process.env.PLATFORM === 'web') {
|
||||||
|
Object.assign(fixturesBasedOnProcessEnvPlatform, fixturesForWeb)
|
||||||
|
} else {
|
||||||
|
Object.assign(fixturesBasedOnProcessEnvPlatform, fixturesForElectron)
|
||||||
|
}
|
||||||
|
|
||||||
|
export { fixturesBasedOnProcessEnvPlatform }
|
||||||
|
@ -27,10 +27,6 @@ export class HomePageFixture {
|
|||||||
|
|
||||||
constructor(page: Page) {
|
constructor(page: Page) {
|
||||||
this.page = page
|
this.page = page
|
||||||
this.reConstruct(page)
|
|
||||||
}
|
|
||||||
reConstruct = (page: Page) => {
|
|
||||||
this.page = page
|
|
||||||
|
|
||||||
this.projectSection = this.page.getByTestId('home-section')
|
this.projectSection = this.page.getByTestId('home-section')
|
||||||
|
|
||||||
@ -96,8 +92,12 @@ export class HomePageFixture {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
createAndGoToProject = async (projectTitle = 'project-$nnn') => {
|
projectsLoaded = async () => {
|
||||||
await expect(this.projectSection).not.toHaveText('Loading your Projects...')
|
await expect(this.projectSection).not.toHaveText('Loading your Projects...')
|
||||||
|
}
|
||||||
|
|
||||||
|
createAndGoToProject = async (projectTitle = 'project-$nnn') => {
|
||||||
|
await this.projectsLoaded()
|
||||||
await this.projectButtonNew.click()
|
await this.projectButtonNew.click()
|
||||||
await this.projectTextName.click()
|
await this.projectTextName.click()
|
||||||
await this.projectTextName.fill(projectTitle)
|
await this.projectTextName.fill(projectTitle)
|
||||||
|
@ -53,7 +53,12 @@ export class SceneFixture {
|
|||||||
|
|
||||||
constructor(page: Page) {
|
constructor(page: Page) {
|
||||||
this.page = page
|
this.page = page
|
||||||
this.reConstruct(page)
|
this.streamWrapper = page.getByTestId('stream')
|
||||||
|
this.networkToggleConnected = page.getByTestId('network-toggle-ok')
|
||||||
|
this.loadingIndicator = this.streamWrapper.getByTestId('loading')
|
||||||
|
this.startEditSketchBtn = page
|
||||||
|
.getByRole('button', { name: 'Start Sketch' })
|
||||||
|
.or(page.getByRole('button', { name: 'Edit Sketch' }))
|
||||||
}
|
}
|
||||||
private _serialiseScene = async (): Promise<SceneSerialised> => {
|
private _serialiseScene = async (): Promise<SceneSerialised> => {
|
||||||
const camera = await this.getCameraInfo()
|
const camera = await this.getCameraInfo()
|
||||||
@ -72,17 +77,6 @@ export class SceneFixture {
|
|||||||
.toEqual(expected)
|
.toEqual(expected)
|
||||||
}
|
}
|
||||||
|
|
||||||
reConstruct = (page: Page) => {
|
|
||||||
this.page = page
|
|
||||||
|
|
||||||
this.streamWrapper = page.getByTestId('stream')
|
|
||||||
this.networkToggleConnected = page.getByTestId('network-toggle-ok')
|
|
||||||
this.loadingIndicator = this.streamWrapper.getByTestId('loading')
|
|
||||||
this.startEditSketchBtn = page
|
|
||||||
.getByRole('button', { name: 'Start Sketch' })
|
|
||||||
.or(page.getByRole('button', { name: 'Edit Sketch' }))
|
|
||||||
}
|
|
||||||
|
|
||||||
makeMouseHelpers = (
|
makeMouseHelpers = (
|
||||||
x: number,
|
x: number,
|
||||||
y: number,
|
y: number,
|
||||||
@ -253,7 +247,7 @@ export class SceneFixture {
|
|||||||
|
|
||||||
await u.openDebugPanel()
|
await u.openDebugPanel()
|
||||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||||
await u.clearAndCloseDebugPanel()
|
await u.closeDebugPanel()
|
||||||
|
|
||||||
await this.waitForExecutionDone()
|
await this.waitForExecutionDone()
|
||||||
await expect(this.startEditSketchBtn).not.toBeDisabled()
|
await expect(this.startEditSketchBtn).not.toBeDisabled()
|
||||||
|
@ -37,13 +37,12 @@ export class ToolbarFixture {
|
|||||||
featureTreeId = 'feature-tree' as const
|
featureTreeId = 'feature-tree' as const
|
||||||
/** The pane element for the Feature Tree */
|
/** The pane element for the Feature Tree */
|
||||||
featureTreePane!: Locator
|
featureTreePane!: Locator
|
||||||
|
gizmo!: Locator
|
||||||
|
gizmoDisabled!: Locator
|
||||||
|
|
||||||
constructor(page: Page) {
|
constructor(page: Page) {
|
||||||
this.page = page
|
this.page = page
|
||||||
this.reConstruct(page)
|
|
||||||
}
|
|
||||||
reConstruct = (page: Page) => {
|
|
||||||
this.page = page
|
|
||||||
this.extrudeButton = page.getByTestId('extrude')
|
this.extrudeButton = page.getByTestId('extrude')
|
||||||
this.loftButton = page.getByTestId('loft')
|
this.loftButton = page.getByTestId('loft')
|
||||||
this.sweepButton = page.getByTestId('sweep')
|
this.sweepButton = page.getByTestId('sweep')
|
||||||
@ -67,6 +66,13 @@ export class ToolbarFixture {
|
|||||||
this.filePane = page.locator('#files-pane')
|
this.filePane = page.locator('#files-pane')
|
||||||
this.featureTreePane = page.locator('#feature-tree-pane')
|
this.featureTreePane = page.locator('#feature-tree-pane')
|
||||||
this.fileCreateToast = page.getByText('Successfully created')
|
this.fileCreateToast = page.getByText('Successfully created')
|
||||||
|
|
||||||
|
// Note to test writers: having two locators like this is preferable to one
|
||||||
|
// which changes another el property because it means our test "signal" is
|
||||||
|
// completely decoupled from the elements themselves. It means the same
|
||||||
|
// element or two different elements can represent these states.
|
||||||
|
this.gizmo = page.getByTestId('gizmo')
|
||||||
|
this.gizmoDisabled = page.getByTestId('gizmo-disabled')
|
||||||
}
|
}
|
||||||
|
|
||||||
get editSketchBtn() {
|
get editSketchBtn() {
|
||||||
@ -86,6 +92,18 @@ export class ToolbarFixture {
|
|||||||
startSketchPlaneSelection = async () =>
|
startSketchPlaneSelection = async () =>
|
||||||
doAndWaitForImageDiff(this.page, () => this.startSketchBtn.click(), 500)
|
doAndWaitForImageDiff(this.page, () => this.startSketchBtn.click(), 500)
|
||||||
|
|
||||||
|
waitUntilSketchingReady = async () => {
|
||||||
|
await expect(this.gizmoDisabled).toBeVisible()
|
||||||
|
}
|
||||||
|
|
||||||
|
startSketchThenCallbackThenWaitUntilReady = async (
|
||||||
|
cb: () => Promise<void>
|
||||||
|
) => {
|
||||||
|
await this.startSketchBtn.click()
|
||||||
|
await cb()
|
||||||
|
await this.waitUntilSketchingReady()
|
||||||
|
}
|
||||||
|
|
||||||
exitSketch = async () => {
|
exitSketch = async () => {
|
||||||
await this.exitSketchBtn.click()
|
await this.exitSketchBtn.click()
|
||||||
await expect(
|
await expect(
|
||||||
|
@ -21,58 +21,54 @@ import { expectPixelColor } from './fixtures/sceneFixture'
|
|||||||
// we must set it to empty for the tests where we want to see the onboarding immediately.
|
// we must set it to empty for the tests where we want to see the onboarding immediately.
|
||||||
|
|
||||||
test.describe('Onboarding tests', () => {
|
test.describe('Onboarding tests', () => {
|
||||||
test(
|
test('Onboarding code is shown in the editor', async ({
|
||||||
'Onboarding code is shown in the editor',
|
page,
|
||||||
{
|
homePage,
|
||||||
appSettings: {
|
tronApp,
|
||||||
|
}) => {
|
||||||
|
if (!tronApp) {
|
||||||
|
fail()
|
||||||
|
}
|
||||||
|
await tronApp.cleanProjectDir({
|
||||||
app: {
|
app: {
|
||||||
onboarding_status: '',
|
onboarding_status: '',
|
||||||
},
|
},
|
||||||
},
|
})
|
||||||
cleanProjectDir: true,
|
|
||||||
},
|
|
||||||
async ({ page, homePage }) => {
|
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
|
|
||||||
// Test that the onboarding pane loaded
|
// Test that the onboarding pane loaded
|
||||||
await expect(
|
await expect(page.getByText('Welcome to Modeling App! This')).toBeVisible()
|
||||||
page.getByText('Welcome to Modeling App! This')
|
|
||||||
).toBeVisible()
|
|
||||||
|
|
||||||
// Test that the onboarding pane loaded
|
// Test that the onboarding pane loaded
|
||||||
await expect(
|
await expect(page.getByText('Welcome to Modeling App! This')).toBeVisible()
|
||||||
page.getByText('Welcome to Modeling App! This')
|
|
||||||
).toBeVisible()
|
|
||||||
|
|
||||||
// *and* that the code is shown in the editor
|
// *and* that the code is shown in the editor
|
||||||
await expect(page.locator('.cm-content')).toContainText(
|
await expect(page.locator('.cm-content')).toContainText('// Shelf Bracket')
|
||||||
'// Shelf Bracket'
|
|
||||||
)
|
|
||||||
|
|
||||||
// Make sure the model loaded
|
// Make sure the model loaded
|
||||||
const XYPlanePoint = { x: 774, y: 116 } as const
|
const XYPlanePoint = { x: 774, y: 116 } as const
|
||||||
const modelColor: [number, number, number] = [45, 45, 45]
|
const modelColor: [number, number, number] = [45, 45, 45]
|
||||||
await page.mouse.move(XYPlanePoint.x, XYPlanePoint.y)
|
await page.mouse.move(XYPlanePoint.x, XYPlanePoint.y)
|
||||||
expect(await u.getGreatestPixDiff(XYPlanePoint, modelColor)).toBeLessThan(
|
expect(await u.getGreatestPixDiff(XYPlanePoint, modelColor)).toBeLessThan(8)
|
||||||
8
|
})
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
test(
|
test(
|
||||||
'Desktop: fresh onboarding executes and loads',
|
'Desktop: fresh onboarding executes and loads',
|
||||||
{
|
{
|
||||||
tag: '@electron',
|
tag: '@electron',
|
||||||
appSettings: {
|
},
|
||||||
|
async ({ page, tronApp }) => {
|
||||||
|
if (!tronApp) {
|
||||||
|
fail()
|
||||||
|
}
|
||||||
|
await tronApp.cleanProjectDir({
|
||||||
app: {
|
app: {
|
||||||
onboarding_status: '',
|
onboarding_status: '',
|
||||||
},
|
},
|
||||||
},
|
})
|
||||||
cleanProjectDir: true,
|
|
||||||
},
|
|
||||||
async ({ page }) => {
|
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
|
|
||||||
const viewportSize = { width: 1200, height: 500 }
|
const viewportSize = { width: 1200, height: 500 }
|
||||||
@ -107,22 +103,30 @@ test.describe('Onboarding tests', () => {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
test(
|
test('Code resets after confirmation', async ({
|
||||||
'Code resets after confirmation',
|
context,
|
||||||
{
|
page,
|
||||||
cleanProjectDir: true,
|
homePage,
|
||||||
},
|
tronApp,
|
||||||
async ({ context, page, homePage }) => {
|
scene,
|
||||||
|
cmdBar,
|
||||||
|
}) => {
|
||||||
|
if (!tronApp) {
|
||||||
|
fail()
|
||||||
|
}
|
||||||
|
await tronApp.cleanProjectDir()
|
||||||
|
|
||||||
const initialCode = `sketch001 = startSketchOn('XZ')`
|
const initialCode = `sketch001 = startSketchOn('XZ')`
|
||||||
|
|
||||||
// Load the page up with some code so we see the confirmation warning
|
// Load the page up with some code so we see the confirmation warning
|
||||||
// when we go to replay onboarding
|
// when we go to replay onboarding
|
||||||
await context.addInitScript((code) => {
|
await page.addInitScript((code) => {
|
||||||
localStorage.setItem('persistCode', code)
|
localStorage.setItem('persistCode', code)
|
||||||
}, initialCode)
|
}, initialCode)
|
||||||
|
|
||||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
|
await scene.connectionEstablished()
|
||||||
|
|
||||||
// Replay the onboarding
|
// Replay the onboarding
|
||||||
await page.getByRole('link', { name: 'Settings' }).last().click()
|
await page.getByRole('link', { name: 'Settings' }).last().click()
|
||||||
@ -142,26 +146,27 @@ test.describe('Onboarding tests', () => {
|
|||||||
|
|
||||||
// Ensure we see the introduction and that the code has been reset
|
// Ensure we see the introduction and that the code has been reset
|
||||||
await expect(page.getByText('Welcome to Modeling App!')).toBeVisible()
|
await expect(page.getByText('Welcome to Modeling App!')).toBeVisible()
|
||||||
await expect(page.locator('.cm-content')).toContainText(
|
await expect(page.locator('.cm-content')).toContainText('// Shelf Bracket')
|
||||||
'// Shelf Bracket'
|
|
||||||
)
|
|
||||||
|
|
||||||
// There used to be old code here that checked if we stored the reset
|
// There used to be old code here that checked if we stored the reset
|
||||||
// code into localStorage but that isn't the case on desktop. It gets
|
// code into localStorage but that isn't the case on desktop. It gets
|
||||||
// saved to the file system, which we have other tests for.
|
// saved to the file system, which we have other tests for.
|
||||||
}
|
})
|
||||||
)
|
|
||||||
|
|
||||||
test(
|
test('Click through each onboarding step and back', async ({
|
||||||
'Click through each onboarding step and back',
|
context,
|
||||||
{
|
page,
|
||||||
appSettings: {
|
homePage,
|
||||||
|
tronApp,
|
||||||
|
}) => {
|
||||||
|
if (!tronApp) {
|
||||||
|
fail()
|
||||||
|
}
|
||||||
|
await tronApp.cleanProjectDir({
|
||||||
app: {
|
app: {
|
||||||
onboarding_status: '',
|
onboarding_status: '',
|
||||||
},
|
},
|
||||||
},
|
})
|
||||||
},
|
|
||||||
async ({ context, page, homePage }) => {
|
|
||||||
// Override beforeEach test setup
|
// Override beforeEach test setup
|
||||||
await context.addInitScript(
|
await context.addInitScript(
|
||||||
async ({ settingsKey, settings }) => {
|
async ({ settingsKey, settings }) => {
|
||||||
@ -181,9 +186,7 @@ test.describe('Onboarding tests', () => {
|
|||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
|
|
||||||
// Test that the onboarding pane loaded
|
// Test that the onboarding pane loaded
|
||||||
await expect(
|
await expect(page.getByText('Welcome to Modeling App! This')).toBeVisible()
|
||||||
page.getByText('Welcome to Modeling App! This')
|
|
||||||
).toBeVisible()
|
|
||||||
|
|
||||||
const nextButton = page.getByTestId('onboarding-next')
|
const nextButton = page.getByTestId('onboarding-next')
|
||||||
const prevButton = page.getByTestId('onboarding-prev')
|
const prevButton = page.getByTestId('onboarding-prev')
|
||||||
@ -205,20 +208,23 @@ test.describe('Onboarding tests', () => {
|
|||||||
// Test that the onboarding pane is gone
|
// Test that the onboarding pane is gone
|
||||||
await expect(page.getByTestId('onboarding-content')).not.toBeVisible()
|
await expect(page.getByTestId('onboarding-content')).not.toBeVisible()
|
||||||
await expect.poll(() => page.url()).not.toContain('/onboarding')
|
await expect.poll(() => page.url()).not.toContain('/onboarding')
|
||||||
}
|
})
|
||||||
)
|
|
||||||
|
|
||||||
test(
|
test('Onboarding redirects and code updating', async ({
|
||||||
'Onboarding redirects and code updating',
|
context,
|
||||||
{
|
page,
|
||||||
appSettings: {
|
homePage,
|
||||||
|
tronApp,
|
||||||
|
}) => {
|
||||||
|
if (!tronApp) {
|
||||||
|
fail()
|
||||||
|
}
|
||||||
|
await tronApp.cleanProjectDir({
|
||||||
app: {
|
app: {
|
||||||
onboarding_status: '/export',
|
onboarding_status: '/export',
|
||||||
},
|
},
|
||||||
},
|
})
|
||||||
cleanProjectDir: true,
|
|
||||||
},
|
|
||||||
async ({ context, page, homePage }) => {
|
|
||||||
const originalCode = 'sigmaAllow = 15000'
|
const originalCode = 'sigmaAllow = 15000'
|
||||||
|
|
||||||
// Override beforeEach test setup
|
// Override beforeEach test setup
|
||||||
@ -260,21 +266,22 @@ test.describe('Onboarding tests', () => {
|
|||||||
await page.locator('[data-testid="onboarding-next"]').hover()
|
await page.locator('[data-testid="onboarding-next"]').hover()
|
||||||
await page.locator('[data-testid="onboarding-next"]').click()
|
await page.locator('[data-testid="onboarding-next"]').click()
|
||||||
await expect(page.locator('.cm-content')).toHaveText(/.+/)
|
await expect(page.locator('.cm-content')).toHaveText(/.+/)
|
||||||
}
|
})
|
||||||
)
|
|
||||||
|
|
||||||
test(
|
test('Onboarding code gets reset to demo on Interactive Numbers step', async ({
|
||||||
'Onboarding code gets reset to demo on Interactive Numbers step',
|
page,
|
||||||
{
|
homePage,
|
||||||
appSettings: {
|
tronApp,
|
||||||
|
}) => {
|
||||||
|
if (!tronApp) {
|
||||||
|
fail()
|
||||||
|
}
|
||||||
|
await tronApp.cleanProjectDir({
|
||||||
app: {
|
app: {
|
||||||
onboarding_status: '/parametric-modeling',
|
onboarding_status: '/parametric-modeling',
|
||||||
},
|
},
|
||||||
},
|
})
|
||||||
cleanProjectDir: true,
|
|
||||||
},
|
|
||||||
|
|
||||||
async ({ page, homePage }) => {
|
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
const badCode = `// This is bad code we shouldn't see`
|
const badCode = `// This is bad code we shouldn't see`
|
||||||
|
|
||||||
@ -307,23 +314,24 @@ test.describe('Onboarding tests', () => {
|
|||||||
|
|
||||||
// Check that the code has been reset
|
// Check that the code has been reset
|
||||||
await expect(u.codeLocator).toHaveText(bracketNoNewLines)
|
await expect(u.codeLocator).toHaveText(bracketNoNewLines)
|
||||||
}
|
})
|
||||||
)
|
|
||||||
|
|
||||||
// (lee) The two avatar tests are weird because even on main, we don't have
|
// (lee) The two avatar tests are weird because even on main, we don't have
|
||||||
// anything to do with the avatar inside the onboarding test. Due to the
|
// anything to do with the avatar inside the onboarding test. Due to the
|
||||||
// low impact of an avatar not showing I'm changing this to fixme.
|
// low impact of an avatar not showing I'm changing this to fixme.
|
||||||
test.fixme(
|
test.fixme(
|
||||||
'Avatar text updates depending on image load success',
|
'Avatar text updates depending on image load success',
|
||||||
{
|
async ({ context, page, homePage, tronApp }) => {
|
||||||
appSettings: {
|
if (!tronApp) {
|
||||||
|
fail()
|
||||||
|
}
|
||||||
|
|
||||||
|
await tronApp.cleanProjectDir({
|
||||||
app: {
|
app: {
|
||||||
onboarding_status: '',
|
onboarding_status: '',
|
||||||
},
|
},
|
||||||
},
|
})
|
||||||
cleanProjectDir: true,
|
|
||||||
},
|
|
||||||
async ({ context, page, homePage }) => {
|
|
||||||
// Override beforeEach test setup
|
// Override beforeEach test setup
|
||||||
await context.addInitScript(
|
await context.addInitScript(
|
||||||
async ({ settingsKey, settings }) => {
|
async ({ settingsKey, settings }) => {
|
||||||
@ -388,15 +396,16 @@ test.describe('Onboarding tests', () => {
|
|||||||
|
|
||||||
test.fixme(
|
test.fixme(
|
||||||
"Avatar text doesn't mention avatar when no avatar",
|
"Avatar text doesn't mention avatar when no avatar",
|
||||||
{
|
async ({ context, page, homePage, tronApp }) => {
|
||||||
appSettings: {
|
if (!tronApp) {
|
||||||
|
fail()
|
||||||
|
}
|
||||||
|
|
||||||
|
await tronApp.cleanProjectDir({
|
||||||
app: {
|
app: {
|
||||||
onboarding_status: '',
|
onboarding_status: '',
|
||||||
},
|
},
|
||||||
},
|
})
|
||||||
cleanProjectDir: true,
|
|
||||||
},
|
|
||||||
async ({ context, page, homePage }) => {
|
|
||||||
// Override beforeEach test setup
|
// Override beforeEach test setup
|
||||||
await context.addInitScript(
|
await context.addInitScript(
|
||||||
async ({ settingsKey, settings }) => {
|
async ({ settingsKey, settings }) => {
|
||||||
@ -444,15 +453,17 @@ test.describe('Onboarding tests', () => {
|
|||||||
|
|
||||||
test.fixme(
|
test.fixme(
|
||||||
'Restarting onboarding on desktop takes one attempt',
|
'Restarting onboarding on desktop takes one attempt',
|
||||||
{
|
async ({ context, page, tronApp }) => {
|
||||||
appSettings: {
|
if (!tronApp) {
|
||||||
|
fail()
|
||||||
|
}
|
||||||
|
|
||||||
|
await tronApp.cleanProjectDir({
|
||||||
app: {
|
app: {
|
||||||
onboarding_status: 'dismissed',
|
onboarding_status: 'dismissed',
|
||||||
},
|
},
|
||||||
},
|
})
|
||||||
cleanProjectDir: true,
|
|
||||||
},
|
|
||||||
async ({ context, page }) => {
|
|
||||||
await context.folderSetupFn(async (dir) => {
|
await context.folderSetupFn(async (dir) => {
|
||||||
const routerTemplateDir = join(dir, 'router-template-slate')
|
const routerTemplateDir = join(dir, 'router-template-slate')
|
||||||
await fsp.mkdir(routerTemplateDir, { recursive: true })
|
await fsp.mkdir(routerTemplateDir, { recursive: true })
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { test, expect, Page } from './zoo-test'
|
import { Page } from '@playwright/test'
|
||||||
|
import { test, expect } from './zoo-test'
|
||||||
import { EditorFixture } from './fixtures/editorFixture'
|
import { EditorFixture } from './fixtures/editorFixture'
|
||||||
import { SceneFixture } from './fixtures/sceneFixture'
|
import { SceneFixture } from './fixtures/sceneFixture'
|
||||||
import { ToolbarFixture } from './fixtures/toolbarFixture'
|
import { ToolbarFixture } from './fixtures/toolbarFixture'
|
||||||
@ -1407,7 +1408,7 @@ sketch002 = startSketchOn('XZ')
|
|||||||
})
|
})
|
||||||
|
|
||||||
await test.step(`Confirm code is added to the editor, scene has changed`, async () => {
|
await test.step(`Confirm code is added to the editor, scene has changed`, async () => {
|
||||||
await scene.expectPixelColor([135, 64, 73], testPoint, 15)
|
// await scene.expectPixelColor([135, 64, 73], testPoint, 15) // FIXME
|
||||||
await editor.expectEditor.toContain(sweepDeclaration)
|
await editor.expectEditor.toContain(sweepDeclaration)
|
||||||
await editor.expectState({
|
await editor.expectState({
|
||||||
diagnostics: [],
|
diagnostics: [],
|
||||||
|
@ -163,7 +163,7 @@ test(
|
|||||||
.poll(() => u.getGreatestPixDiff(pointOnModel, [85, 85, 85]), {
|
.poll(() => u.getGreatestPixDiff(pointOnModel, [85, 85, 85]), {
|
||||||
timeout: 10_000,
|
timeout: 10_000,
|
||||||
})
|
})
|
||||||
.toBeLessThan(15)
|
.toBeLessThan(20)
|
||||||
})
|
})
|
||||||
|
|
||||||
await test.step('Clicking the logo takes us back to the projects page / home', async () => {
|
await test.step('Clicking the logo takes us back to the projects page / home', async () => {
|
||||||
@ -464,7 +464,11 @@ test.describe('Can export from electron app', () => {
|
|||||||
test(
|
test(
|
||||||
`Can export using ${method}`,
|
`Can export using ${method}`,
|
||||||
{ tag: ['@electron', '@skipLocalEngine'] },
|
{ tag: ['@electron', '@skipLocalEngine'] },
|
||||||
async ({ context, page }, testInfo) => {
|
async ({ context, page, tronApp }, testInfo) => {
|
||||||
|
if (!tronApp) {
|
||||||
|
fail()
|
||||||
|
}
|
||||||
|
|
||||||
await context.folderSetupFn(async (dir) => {
|
await context.folderSetupFn(async (dir) => {
|
||||||
const bracketDir = path.join(dir, 'bracket')
|
const bracketDir = path.join(dir, 'bracket')
|
||||||
await fsp.mkdir(bracketDir, { recursive: true })
|
await fsp.mkdir(bracketDir, { recursive: true })
|
||||||
@ -516,6 +520,7 @@ test.describe('Can export from electron app', () => {
|
|||||||
storage: 'embedded',
|
storage: 'embedded',
|
||||||
presentation: 'pretty',
|
presentation: 'pretty',
|
||||||
},
|
},
|
||||||
|
tronApp.projectDirName,
|
||||||
page,
|
page,
|
||||||
method
|
method
|
||||||
)
|
)
|
||||||
@ -523,7 +528,7 @@ test.describe('Can export from electron app', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const filepath = path.resolve(
|
const filepath = path.resolve(
|
||||||
getPlaywrightDownloadDir(page),
|
getPlaywrightDownloadDir(tronApp.projectDirName),
|
||||||
'main.gltf'
|
'main.gltf'
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -781,6 +786,7 @@ test(
|
|||||||
page.on('console', console.log)
|
page.on('console', console.log)
|
||||||
|
|
||||||
await expect(page.getByText('router-template-slate')).toBeVisible()
|
await expect(page.getByText('router-template-slate')).toBeVisible()
|
||||||
|
await expect(page.getByText('Loading your Projects...')).not.toBeVisible()
|
||||||
await expect(page.getByText('Your Projects')).toBeVisible()
|
await expect(page.getByText('Your Projects')).toBeVisible()
|
||||||
|
|
||||||
await page.keyboard.press('Delete')
|
await page.keyboard.press('Delete')
|
||||||
@ -858,7 +864,7 @@ test.describe(`Project management commands`, () => {
|
|||||||
test(
|
test(
|
||||||
`Delete from project page`,
|
`Delete from project page`,
|
||||||
{ tag: '@electron' },
|
{ tag: '@electron' },
|
||||||
async ({ context, page }, testInfo) => {
|
async ({ context, page, scene, cmdBar }, testInfo) => {
|
||||||
const projectName = `my_project_to_delete`
|
const projectName = `my_project_to_delete`
|
||||||
await context.folderSetupFn(async (dir) => {
|
await context.folderSetupFn(async (dir) => {
|
||||||
await fsp.mkdir(`${dir}/${projectName}`, { recursive: true })
|
await fsp.mkdir(`${dir}/${projectName}`, { recursive: true })
|
||||||
@ -887,6 +893,8 @@ test.describe(`Project management commands`, () => {
|
|||||||
|
|
||||||
await projectHomeLink.click()
|
await projectHomeLink.click()
|
||||||
await u.waitForPageLoad()
|
await u.waitForPageLoad()
|
||||||
|
await scene.connectionEstablished()
|
||||||
|
await scene.settled(cmdBar)
|
||||||
})
|
})
|
||||||
|
|
||||||
await test.step(`Run delete command via command palette`, async () => {
|
await test.step(`Run delete command via command palette`, async () => {
|
||||||
@ -909,7 +917,7 @@ test.describe(`Project management commands`, () => {
|
|||||||
test(
|
test(
|
||||||
`Rename from home page`,
|
`Rename from home page`,
|
||||||
{ tag: '@electron' },
|
{ tag: '@electron' },
|
||||||
async ({ context, page }, testInfo) => {
|
async ({ context, page, homePage }, testInfo) => {
|
||||||
const projectName = `my_project_to_rename`
|
const projectName = `my_project_to_rename`
|
||||||
await context.folderSetupFn(async (dir) => {
|
await context.folderSetupFn(async (dir) => {
|
||||||
await fsp.mkdir(`${dir}/${projectName}`, { recursive: true })
|
await fsp.mkdir(`${dir}/${projectName}`, { recursive: true })
|
||||||
@ -936,6 +944,7 @@ test.describe(`Project management commands`, () => {
|
|||||||
await test.step(`Setup`, async () => {
|
await test.step(`Setup`, async () => {
|
||||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
page.on('console', console.log)
|
page.on('console', console.log)
|
||||||
|
await homePage.projectsLoaded()
|
||||||
await expect(projectHomeLink).toBeVisible()
|
await expect(projectHomeLink).toBeVisible()
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -1682,7 +1691,11 @@ test(
|
|||||||
test(
|
test(
|
||||||
'You can change the root projects directory and nothing is lost',
|
'You can change the root projects directory and nothing is lost',
|
||||||
{ tag: '@electron' },
|
{ tag: '@electron' },
|
||||||
async ({ context, page, electronApp }, testInfo) => {
|
async ({ context, page, tronApp, homePage }, testInfo) => {
|
||||||
|
if (!tronApp) {
|
||||||
|
fail()
|
||||||
|
}
|
||||||
|
|
||||||
await context.folderSetupFn(async (dir) => {
|
await context.folderSetupFn(async (dir) => {
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
fsp.mkdir(`${dir}/router-template-slate`, { recursive: true }),
|
fsp.mkdir(`${dir}/router-template-slate`, { recursive: true }),
|
||||||
@ -1712,6 +1725,8 @@ test(
|
|||||||
await fsp.rm(newProjectDirName, { recursive: true })
|
await fsp.rm(newProjectDirName, { recursive: true })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await homePage.projectsLoaded()
|
||||||
|
|
||||||
await test.step('We can change the root project directory', async () => {
|
await test.step('We can change the root project directory', async () => {
|
||||||
// expect to see the project directory settings link
|
// expect to see the project directory settings link
|
||||||
await expect(
|
await expect(
|
||||||
@ -1725,7 +1740,7 @@ test(
|
|||||||
.locator('section#projectDirectory input')
|
.locator('section#projectDirectory input')
|
||||||
.inputValue()
|
.inputValue()
|
||||||
|
|
||||||
const handleFile = electronApp?.evaluate(
|
const handleFile = tronApp.electron.evaluate(
|
||||||
async ({ dialog }, filePaths) => {
|
async ({ dialog }, filePaths) => {
|
||||||
dialog.showOpenDialog = () =>
|
dialog.showOpenDialog = () =>
|
||||||
Promise.resolve({ canceled: false, filePaths })
|
Promise.resolve({ canceled: false, filePaths })
|
||||||
@ -1741,6 +1756,8 @@ test(
|
|||||||
|
|
||||||
await page.getByTestId('settings-close-button').click()
|
await page.getByTestId('settings-close-button').click()
|
||||||
|
|
||||||
|
await homePage.projectsLoaded()
|
||||||
|
|
||||||
await expect(page.getByText('No Projects found')).toBeVisible()
|
await expect(page.getByText('No Projects found')).toBeVisible()
|
||||||
await createProject({ name: 'project-000', page, returnHome: true })
|
await createProject({ name: 'project-000', page, returnHome: true })
|
||||||
await expect(
|
await expect(
|
||||||
@ -1755,7 +1772,7 @@ test(
|
|||||||
|
|
||||||
await page.getByTestId('project-directory-settings-link').click()
|
await page.getByTestId('project-directory-settings-link').click()
|
||||||
|
|
||||||
const handleFile = electronApp?.evaluate(
|
const handleFile = tronApp.electron.evaluate(
|
||||||
async ({ dialog }, filePaths) => {
|
async ({ dialog }, filePaths) => {
|
||||||
dialog.showOpenDialog = () =>
|
dialog.showOpenDialog = () =>
|
||||||
Promise.resolve({ canceled: false, filePaths })
|
Promise.resolve({ canceled: false, filePaths })
|
||||||
@ -1767,6 +1784,7 @@ test(
|
|||||||
await page.getByTestId('project-directory-button').click()
|
await page.getByTestId('project-directory-button').click()
|
||||||
await handleFile
|
await handleFile
|
||||||
|
|
||||||
|
await homePage.projectsLoaded()
|
||||||
await expect(page.locator('section#projectDirectory input')).toHaveValue(
|
await expect(page.locator('section#projectDirectory input')).toHaveValue(
|
||||||
originalProjectDirName
|
originalProjectDirName
|
||||||
)
|
)
|
||||||
@ -2000,8 +2018,8 @@ test(
|
|||||||
|
|
||||||
test(
|
test(
|
||||||
'Settings persist across restarts',
|
'Settings persist across restarts',
|
||||||
{ tag: '@electron', cleanProjectDir: true },
|
{ tag: '@electron' },
|
||||||
async ({ page }, testInfo) => {
|
async ({ page, scene, cmdBar }, testInfo) => {
|
||||||
await test.step('We can change a user setting like theme', async () => {
|
await test.step('We can change a user setting like theme', async () => {
|
||||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
|
|
||||||
@ -2014,6 +2032,10 @@ test(
|
|||||||
await expect(page.getByTestId('app-theme')).toHaveValue('dark')
|
await expect(page.getByTestId('app-theme')).toHaveValue('dark')
|
||||||
|
|
||||||
await page.getByTestId('app-theme').selectOption('light')
|
await page.getByTestId('app-theme').selectOption('light')
|
||||||
|
await expect(page.getByTestId('app-theme')).toHaveValue('light')
|
||||||
|
|
||||||
|
// Give time to system for writing to a persistent store
|
||||||
|
await page.waitForTimeout(1000)
|
||||||
})
|
})
|
||||||
|
|
||||||
await test.step('Starting the app again and we can see the same theme', async () => {
|
await test.step('Starting the app again and we can see the same theme', async () => {
|
||||||
|
@ -233,7 +233,7 @@ test.describe('Prompt-to-edit tests', { tag: '@skipWin' }, () => {
|
|||||||
await cmdBar.openCmdBar('promptToEdit')
|
await cmdBar.openCmdBar('promptToEdit')
|
||||||
await page
|
await page
|
||||||
.getByTestId('cmd-bar-arg-value')
|
.getByTestId('cmd-bar-arg-value')
|
||||||
.fill('Please rename to mySketch')
|
.fill('Please rename to mySketch001')
|
||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
await cmdBar.progressCmdBar()
|
await cmdBar.progressCmdBar()
|
||||||
await expect(submittingToast).toBeVisible()
|
await expect(submittingToast).toBeVisible()
|
||||||
@ -244,10 +244,10 @@ test.describe('Prompt-to-edit tests', { tag: '@skipWin' }, () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
await test.step('verify rename change and accept it', async () => {
|
await test.step('verify rename change and accept it', async () => {
|
||||||
await editor.expectEditor.toContain('mySketch = startSketchOn')
|
await editor.expectEditor.toContain('mySketch001 = startSketchOn')
|
||||||
await editor.expectEditor.not.toContain('sketch002 = startSketchOn')
|
await editor.expectEditor.not.toContain('sketch002 = startSketchOn')
|
||||||
await editor.expectEditor.toContain(
|
await editor.expectEditor.toContain(
|
||||||
'extrude002 = extrude(mySketch, length = 50)'
|
'extrude002 = extrude(mySketch001, length = 50)'
|
||||||
)
|
)
|
||||||
|
|
||||||
await acceptBtn.click()
|
await acceptBtn.click()
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { test, expect, Page } from './zoo-test'
|
import { Page } from '@playwright/test'
|
||||||
|
import { test, expect } from './zoo-test'
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
import * as fsp from 'fs/promises'
|
import * as fsp from 'fs/promises'
|
||||||
import { getUtils, executorInputPath } from './test-utils'
|
import { getUtils, executorInputPath } from './test-utils'
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { test, expect, Page } from './zoo-test'
|
import { Page } from '@playwright/test'
|
||||||
|
import { test, expect } from './zoo-test'
|
||||||
import fs from 'node:fs/promises'
|
import fs from 'node:fs/promises'
|
||||||
import path from 'node:path'
|
import path from 'node:path'
|
||||||
import { HomePageFixture } from './fixtures/homePageFixture'
|
import { HomePageFixture } from './fixtures/homePageFixture'
|
||||||
@ -2153,6 +2154,8 @@ extrude001 = extrude(profile003, length = 5)
|
|||||||
|
|
||||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
|
|
||||||
|
await page.waitForTimeout(5000)
|
||||||
await expect(
|
await expect(
|
||||||
page.getByRole('button', { name: 'Start Sketch' })
|
page.getByRole('button', { name: 'Start Sketch' })
|
||||||
).not.toBeDisabled()
|
).not.toBeDisabled()
|
||||||
@ -2165,7 +2168,7 @@ extrude001 = extrude(profile003, length = 5)
|
|||||||
await page.waitForTimeout(600)
|
await page.waitForTimeout(600)
|
||||||
|
|
||||||
await editor.expectEditor.toContain(`sketch001 = startSketchOn('XZ')`)
|
await editor.expectEditor.toContain(`sketch001 = startSketchOn('XZ')`)
|
||||||
await toolbar.exitSketchBtn.click()
|
await toolbar.exitSketch()
|
||||||
|
|
||||||
await editor.expectEditor.not.toContain(`sketch001 = startSketchOn('XZ')`)
|
await editor.expectEditor.not.toContain(`sketch001 = startSketchOn('XZ')`)
|
||||||
|
|
||||||
@ -2181,6 +2184,8 @@ extrude001 = extrude(profile003, length = 5)
|
|||||||
)`
|
)`
|
||||||
)
|
)
|
||||||
|
|
||||||
|
await scene.settled(cmdBar)
|
||||||
|
|
||||||
await scene.expectPixelColor([255, 255, 255], { x: 633, y: 211 }, 15)
|
await scene.expectPixelColor([255, 255, 255], { x: 633, y: 211 }, 15)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -31,8 +31,7 @@ test.beforeEach(async ({ page, context }) => {
|
|||||||
// Help engine-manager: tear shit down.
|
// Help engine-manager: tear shit down.
|
||||||
test.afterEach(async ({ page }) => {
|
test.afterEach(async ({ page }) => {
|
||||||
await page.evaluate(() => {
|
await page.evaluate(() => {
|
||||||
// @ts-expect-error
|
window.engineCommandManager.tearDown()
|
||||||
window.tearDown()
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -45,7 +44,11 @@ test.setTimeout(60_000)
|
|||||||
test.skip(
|
test.skip(
|
||||||
'exports of each format should work',
|
'exports of each format should work',
|
||||||
{ tag: ['@snapshot', '@skipWin', '@skipMacos'] },
|
{ tag: ['@snapshot', '@skipWin', '@skipMacos'] },
|
||||||
async ({ page, context, scene, cmdBar }) => {
|
async ({ page, context, scene, cmdBar, tronApp }) => {
|
||||||
|
if (!tronApp) {
|
||||||
|
fail()
|
||||||
|
}
|
||||||
|
|
||||||
// FYI this test doesn't work with only engine running locally
|
// FYI this test doesn't work with only engine running locally
|
||||||
// And you will need to have the KittyCAD CLI installed
|
// And you will need to have the KittyCAD CLI installed
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
@ -134,6 +137,7 @@ part001 = startSketchOn('-XZ')
|
|||||||
storage: 'ascii',
|
storage: 'ascii',
|
||||||
units: 'in',
|
units: 'in',
|
||||||
},
|
},
|
||||||
|
tronApp.projectDirName,
|
||||||
page
|
page
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -146,6 +150,7 @@ part001 = startSketchOn('-XZ')
|
|||||||
selection: { type: 'default_scene' },
|
selection: { type: 'default_scene' },
|
||||||
units: 'in',
|
units: 'in',
|
||||||
},
|
},
|
||||||
|
tronApp.projectDirName,
|
||||||
page
|
page
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -158,6 +163,7 @@ part001 = startSketchOn('-XZ')
|
|||||||
selection: { type: 'default_scene' },
|
selection: { type: 'default_scene' },
|
||||||
units: 'in',
|
units: 'in',
|
||||||
},
|
},
|
||||||
|
tronApp.projectDirName,
|
||||||
page
|
page
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -170,6 +176,7 @@ part001 = startSketchOn('-XZ')
|
|||||||
units: 'in',
|
units: 'in',
|
||||||
selection: { type: 'default_scene' },
|
selection: { type: 'default_scene' },
|
||||||
},
|
},
|
||||||
|
tronApp.projectDirName,
|
||||||
page
|
page
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -182,6 +189,7 @@ part001 = startSketchOn('-XZ')
|
|||||||
units: 'in',
|
units: 'in',
|
||||||
selection: { type: 'default_scene' },
|
selection: { type: 'default_scene' },
|
||||||
},
|
},
|
||||||
|
tronApp.projectDirName,
|
||||||
page
|
page
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -193,6 +201,7 @@ part001 = startSketchOn('-XZ')
|
|||||||
coords: sysType,
|
coords: sysType,
|
||||||
units: 'in',
|
units: 'in',
|
||||||
},
|
},
|
||||||
|
tronApp.projectDirName,
|
||||||
page
|
page
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -203,6 +212,7 @@ part001 = startSketchOn('-XZ')
|
|||||||
storage: 'embedded',
|
storage: 'embedded',
|
||||||
presentation: 'pretty',
|
presentation: 'pretty',
|
||||||
},
|
},
|
||||||
|
tronApp.projectDirName,
|
||||||
page
|
page
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -213,6 +223,7 @@ part001 = startSketchOn('-XZ')
|
|||||||
storage: 'binary',
|
storage: 'binary',
|
||||||
presentation: 'pretty',
|
presentation: 'pretty',
|
||||||
},
|
},
|
||||||
|
tronApp.projectDirName,
|
||||||
page
|
page
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -223,6 +234,7 @@ part001 = startSketchOn('-XZ')
|
|||||||
storage: 'standard',
|
storage: 'standard',
|
||||||
presentation: 'pretty',
|
presentation: 'pretty',
|
||||||
},
|
},
|
||||||
|
tronApp.projectDirName,
|
||||||
page
|
page
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
Before Width: | Height: | Size: 75 KiB After Width: | Height: | Size: 74 KiB |
@ -84,7 +84,6 @@ test.describe('Test network and connection issues', () => {
|
|||||||
'Engine disconnect & reconnect in sketch mode',
|
'Engine disconnect & reconnect in sketch mode',
|
||||||
{ tag: '@skipLocalEngine' },
|
{ tag: '@skipLocalEngine' },
|
||||||
async ({ page, homePage }) => {
|
async ({ page, homePage }) => {
|
||||||
// TODO: Don't skip Mac for these. After `window.tearDown` is working in Safari, these should work on webkit
|
|
||||||
const networkToggle = page.getByTestId('network-toggle')
|
const networkToggle = page.getByTestId('network-toggle')
|
||||||
|
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
|
@ -5,8 +5,9 @@ import {
|
|||||||
_electron as electron,
|
_electron as electron,
|
||||||
ElectronApplication,
|
ElectronApplication,
|
||||||
Locator,
|
Locator,
|
||||||
|
Page,
|
||||||
} from '@playwright/test'
|
} from '@playwright/test'
|
||||||
import { test, Page } from './zoo-test'
|
import { test } from './zoo-test'
|
||||||
import { EngineCommand } from 'lang/std/artifactGraph'
|
import { EngineCommand } from 'lang/std/artifactGraph'
|
||||||
import fsp from 'fs/promises'
|
import fsp from 'fs/promises'
|
||||||
import fsSync from 'fs'
|
import fsSync from 'fs'
|
||||||
@ -337,7 +338,7 @@ export const getMovementUtils = (opts: any) => {
|
|||||||
|
|
||||||
async function waitForAuthAndLsp(page: Page) {
|
async function waitForAuthAndLsp(page: Page) {
|
||||||
const waitForLspPromise = page.waitForEvent('console', {
|
const waitForLspPromise = page.waitForEvent('console', {
|
||||||
predicate: async (message) => {
|
predicate: async (message: any) => {
|
||||||
// it would be better to wait for a message that the kcl lsp has started by looking for the message message.text().includes('[lsp] [window/logMessage]')
|
// it would be better to wait for a message that the kcl lsp has started by looking for the message message.text().includes('[lsp] [window/logMessage]')
|
||||||
// but that doesn't seem to make it to the console for macos/safari :(
|
// but that doesn't seem to make it to the console for macos/safari :(
|
||||||
if (message.text().includes('start kcl lsp')) {
|
if (message.text().includes('start kcl lsp')) {
|
||||||
@ -420,7 +421,7 @@ export async function getUtils(page: Page, test_?: typeof test) {
|
|||||||
const overlay = page.locator(locator)
|
const overlay = page.locator(locator)
|
||||||
const bbox = await overlay
|
const bbox = await overlay
|
||||||
.boundingBox({ timeout: 5_000 })
|
.boundingBox({ timeout: 5_000 })
|
||||||
.then((box) => ({ ...box, x: box?.x || 0, y: box?.y || 0 }))
|
.then((box: any) => ({ ...box, x: box?.x || 0, y: box?.y || 0 }))
|
||||||
const angle = Number(await overlay.getAttribute('data-overlay-angle'))
|
const angle = Number(await overlay.getAttribute('data-overlay-angle'))
|
||||||
const angleXOffset = Math.cos(((angle - 180) * Math.PI) / 180) * px
|
const angleXOffset = Math.cos(((angle - 180) * Math.PI) / 180) * px
|
||||||
const angleYOffset = Math.sin(((angle - 180) * Math.PI) / 180) * px
|
const angleYOffset = Math.sin(((angle - 180) * Math.PI) / 180) * px
|
||||||
@ -437,7 +438,7 @@ export async function getUtils(page: Page, test_?: typeof test) {
|
|||||||
page
|
page
|
||||||
.locator(locator)
|
.locator(locator)
|
||||||
.boundingBox({ timeout: 5_000 })
|
.boundingBox({ timeout: 5_000 })
|
||||||
.then((box) => ({ ...box, x: box?.x || 0, y: box?.y || 0 })),
|
.then((box: any) => ({ ...box, x: box?.x || 0, y: box?.y || 0 })),
|
||||||
codeLocator: page.locator('.cm-content'),
|
codeLocator: page.locator('.cm-content'),
|
||||||
crushKclCodeIntoOneLineAndThenMaybeSome: async () => {
|
crushKclCodeIntoOneLineAndThenMaybeSome: async () => {
|
||||||
const code = await page.locator('.cm-content').innerText()
|
const code = await page.locator('.cm-content').innerText()
|
||||||
@ -504,7 +505,7 @@ export async function getUtils(page: Page, test_?: typeof test) {
|
|||||||
) => {
|
) => {
|
||||||
if (cdpSession === null) {
|
if (cdpSession === null) {
|
||||||
// Use a fail safe if we can't simulate disconnect (on Safari)
|
// Use a fail safe if we can't simulate disconnect (on Safari)
|
||||||
return page.evaluate('window.tearDown()')
|
return page.evaluate('window.engineCommandManager.tearDown()')
|
||||||
}
|
}
|
||||||
|
|
||||||
return cdpSession?.send(
|
return cdpSession?.send(
|
||||||
@ -631,7 +632,7 @@ export async function getUtils(page: Page, test_?: typeof test) {
|
|||||||
panesOpen: async (paneIds: PaneId[]) => {
|
panesOpen: async (paneIds: PaneId[]) => {
|
||||||
return test?.step(`Setting ${paneIds} panes to be open`, async () => {
|
return test?.step(`Setting ${paneIds} panes to be open`, async () => {
|
||||||
await page.addInitScript(
|
await page.addInitScript(
|
||||||
({ PERSIST_MODELING_CONTEXT, paneIds }) => {
|
({ PERSIST_MODELING_CONTEXT, paneIds }: any) => {
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
PERSIST_MODELING_CONTEXT,
|
PERSIST_MODELING_CONTEXT,
|
||||||
JSON.stringify({ openPanes: paneIds })
|
JSON.stringify({ openPanes: paneIds })
|
||||||
@ -722,14 +723,14 @@ export const makeTemplate: (
|
|||||||
|
|
||||||
const PLAYWRIGHT_DOWNLOAD_DIR = 'downloads-during-playwright'
|
const PLAYWRIGHT_DOWNLOAD_DIR = 'downloads-during-playwright'
|
||||||
|
|
||||||
export const getPlaywrightDownloadDir = (page: Page) => {
|
export const getPlaywrightDownloadDir = (rootDir: string) => {
|
||||||
return path.resolve(page.dir, PLAYWRIGHT_DOWNLOAD_DIR)
|
return path.resolve(rootDir, PLAYWRIGHT_DOWNLOAD_DIR)
|
||||||
}
|
}
|
||||||
|
|
||||||
const moveDownloadedFileTo = async (page: Page, toLocation: string) => {
|
const moveDownloadedFileTo = async (rootDir: string, toLocation: string) => {
|
||||||
await fsp.mkdir(path.dirname(toLocation), { recursive: true })
|
await fsp.mkdir(path.dirname(toLocation), { recursive: true })
|
||||||
|
|
||||||
const downloadDir = getPlaywrightDownloadDir(page)
|
const downloadDir = getPlaywrightDownloadDir(rootDir)
|
||||||
|
|
||||||
// Expect there to be at least one file
|
// Expect there to be at least one file
|
||||||
await expect
|
await expect
|
||||||
@ -756,6 +757,7 @@ export interface Paths {
|
|||||||
|
|
||||||
export const doExport = async (
|
export const doExport = async (
|
||||||
output: Models['OutputFormat_type'],
|
output: Models['OutputFormat_type'],
|
||||||
|
rootDir: string,
|
||||||
page: Page,
|
page: Page,
|
||||||
exportFrom: 'dropdown' | 'sidebarButton' | 'commandBar' = 'dropdown'
|
exportFrom: 'dropdown' | 'sidebarButton' | 'commandBar' = 'dropdown'
|
||||||
): Promise<Paths> => {
|
): Promise<Paths> => {
|
||||||
@ -836,7 +838,7 @@ export const doExport = async (
|
|||||||
// (declared in src/lib/exportSave)
|
// (declared in src/lib/exportSave)
|
||||||
// To remain consistent with our old web tests, we want to move some downloads
|
// To remain consistent with our old web tests, we want to move some downloads
|
||||||
// (images) to another directory.
|
// (images) to another directory.
|
||||||
await moveDownloadedFileTo(page, downloadLocation)
|
await moveDownloadedFileTo(rootDir, downloadLocation)
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -859,12 +861,6 @@ export async function tearDown(page: Page, testInfo: TestInfo) {
|
|||||||
downloadThroughput: -1,
|
downloadThroughput: -1,
|
||||||
uploadThroughput: -1,
|
uploadThroughput: -1,
|
||||||
})
|
})
|
||||||
|
|
||||||
// It seems it's best to give the browser about 3s to close things
|
|
||||||
// It's not super reliable but we have no real other choice for now
|
|
||||||
await page.waitForTimeout(3000)
|
|
||||||
|
|
||||||
await testInfo.tronApp?.close()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// settingsOverrides may need to be augmented to take more generic items,
|
// settingsOverrides may need to be augmented to take more generic items,
|
||||||
@ -936,107 +932,11 @@ let electronApp: ElectronApplication | undefined = undefined
|
|||||||
let context: BrowserContext | undefined = undefined
|
let context: BrowserContext | undefined = undefined
|
||||||
let page: Page | undefined = undefined
|
let page: Page | undefined = undefined
|
||||||
|
|
||||||
export async function setupElectron({
|
|
||||||
testInfo,
|
|
||||||
cleanProjectDir = true,
|
|
||||||
appSettings,
|
|
||||||
viewport,
|
|
||||||
}: {
|
|
||||||
testInfo: TestInfo
|
|
||||||
folderSetupFn?: (projectDirName: string) => Promise<void>
|
|
||||||
cleanProjectDir?: boolean
|
|
||||||
appSettings?: DeepPartial<Settings>
|
|
||||||
viewport: {
|
|
||||||
width: number
|
|
||||||
height: number
|
|
||||||
}
|
|
||||||
}): Promise<{
|
|
||||||
electronApp: ElectronApplication
|
|
||||||
context: BrowserContext
|
|
||||||
page: Page
|
|
||||||
dir: string
|
|
||||||
}> {
|
|
||||||
// create or otherwise clear the folder
|
|
||||||
const projectDirName = testInfo.outputPath('electron-test-projects-dir')
|
|
||||||
try {
|
|
||||||
if (fsSync.existsSync(projectDirName) && cleanProjectDir) {
|
|
||||||
await fsp.rm(projectDirName, { recursive: true })
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cleanProjectDir) {
|
|
||||||
await fsp.mkdir(projectDirName)
|
|
||||||
}
|
|
||||||
|
|
||||||
const options = {
|
|
||||||
args: ['.', '--no-sandbox'],
|
|
||||||
env: {
|
|
||||||
...process.env,
|
|
||||||
TEST_SETTINGS_FILE_KEY: projectDirName,
|
|
||||||
IS_PLAYWRIGHT: 'true',
|
|
||||||
},
|
|
||||||
...(process.env.ELECTRON_OVERRIDE_DIST_PATH
|
|
||||||
? { executablePath: process.env.ELECTRON_OVERRIDE_DIST_PATH + 'electron' }
|
|
||||||
: {}),
|
|
||||||
...(process.env.PLAYWRIGHT_RECORD_VIDEO
|
|
||||||
? {
|
|
||||||
recordVideo: {
|
|
||||||
dir: testInfo.snapshotPath(),
|
|
||||||
size: viewport,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
: {}),
|
|
||||||
}
|
|
||||||
|
|
||||||
// Do this once and then reuse window on subsequent calls.
|
|
||||||
if (!electronApp) {
|
|
||||||
electronApp = await electron.launch(options)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!context || !page) {
|
|
||||||
context = electronApp.context()
|
|
||||||
page = await electronApp.firstWindow()
|
|
||||||
context.on('console', console.log)
|
|
||||||
page.on('console', console.log)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cleanProjectDir) {
|
|
||||||
const tempSettingsFilePath = path.join(projectDirName, SETTINGS_FILE_NAME)
|
|
||||||
const settingsOverrides = settingsToToml(
|
|
||||||
appSettings
|
|
||||||
? {
|
|
||||||
settings: {
|
|
||||||
...TEST_SETTINGS,
|
|
||||||
...appSettings,
|
|
||||||
app: {
|
|
||||||
...TEST_SETTINGS.app,
|
|
||||||
project_directory: projectDirName,
|
|
||||||
...appSettings.app,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
: {
|
|
||||||
settings: {
|
|
||||||
...TEST_SETTINGS,
|
|
||||||
app: {
|
|
||||||
...TEST_SETTINGS.app,
|
|
||||||
project_directory: projectDirName,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
)
|
|
||||||
await fsp.writeFile(tempSettingsFilePath, settingsOverrides)
|
|
||||||
}
|
|
||||||
|
|
||||||
return { electronApp, page, context, dir: projectDirName }
|
|
||||||
}
|
|
||||||
|
|
||||||
function failOnConsoleErrors(page: Page, testInfo?: TestInfo) {
|
function failOnConsoleErrors(page: Page, testInfo?: TestInfo) {
|
||||||
// enabled for chrome for now
|
// enabled for chrome for now
|
||||||
if (page.context().browser()?.browserType().name() === 'chromium') {
|
if (page.context().browser()?.browserType().name() === 'chromium') {
|
||||||
page.on('pageerror', (exception) => {
|
// No idea wtf exception is
|
||||||
|
page.on('pageerror', (exception: any) => {
|
||||||
if (isErrorWhitelisted(exception)) {
|
if (isErrorWhitelisted(exception)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { test, expect, Page } from './zoo-test'
|
import { Page } from '@playwright/test'
|
||||||
|
import { test, expect } from './zoo-test'
|
||||||
|
|
||||||
import { deg, getUtils, wiggleMove } from './test-utils'
|
import { deg, getUtils, wiggleMove } from './test-utils'
|
||||||
import { LineInputsType } from 'lang/std/sketchcombos'
|
import { LineInputsType } from 'lang/std/sketchcombos'
|
||||||
|
@ -257,6 +257,7 @@ test.describe('Testing selections', { tag: ['@skipWin'] }, () => {
|
|||||||
page,
|
page,
|
||||||
homePage,
|
homePage,
|
||||||
scene,
|
scene,
|
||||||
|
cmdBar,
|
||||||
}) => {
|
}) => {
|
||||||
test.setTimeout(90_000)
|
test.setTimeout(90_000)
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
@ -352,28 +353,15 @@ profile003 = startProfileAt([40.16, -120.48], sketch006)
|
|||||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||||
|
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
await scene.waitForExecutionDone()
|
await scene.settled(cmdBar)
|
||||||
|
|
||||||
await u.openAndClearDebugPanel()
|
const camPosition1 = async () => {
|
||||||
await u.sendCustomCmd({
|
await scene.moveCameraTo(
|
||||||
type: 'modeling_cmd_req',
|
{ x: 1139.49, y: -7053, z: 8597.31 },
|
||||||
cmd_id: uuidv4(),
|
{ x: -2206.68, y: -1298.36, z: 60 }
|
||||||
cmd: {
|
)
|
||||||
type: 'default_camera_look_at',
|
}
|
||||||
vantage: { x: 1139.49, y: -7053, z: 8597.31 },
|
await camPosition1()
|
||||||
center: { x: -2206.68, y: -1298.36, z: 60 },
|
|
||||||
up: { x: 0, y: 0, z: 1 },
|
|
||||||
},
|
|
||||||
})
|
|
||||||
await page.waitForTimeout(100)
|
|
||||||
await u.sendCustomCmd({
|
|
||||||
type: 'modeling_cmd_req',
|
|
||||||
cmd_id: uuidv4(),
|
|
||||||
cmd: {
|
|
||||||
type: 'default_camera_get_settings',
|
|
||||||
},
|
|
||||||
})
|
|
||||||
await page.waitForTimeout(100)
|
|
||||||
|
|
||||||
const revolve = { x: 635, y: 253 }
|
const revolve = { x: 635, y: 253 }
|
||||||
const parentExtrude = { x: 915, y: 133 }
|
const parentExtrude = { x: 915, y: 133 }
|
||||||
@ -386,7 +374,7 @@ profile003 = startProfileAt([40.16, -120.48], sketch006)
|
|||||||
await expect(page.locator('.cm-activeLine')).toHaveText(
|
await expect(page.locator('.cm-activeLine')).toHaveText(
|
||||||
'|> line(end = [0, -pipeLength])'
|
'|> line(end = [0, -pipeLength])'
|
||||||
)
|
)
|
||||||
await u.clearCommandLogs()
|
await u.openAndClearDebugPanel()
|
||||||
await page.keyboard.press('Delete')
|
await page.keyboard.press('Delete')
|
||||||
await u.expectCmdLog('[data-message-type="execution-done"]', 10_000)
|
await u.expectCmdLog('[data-message-type="execution-done"]', 10_000)
|
||||||
await page.waitForTimeout(200)
|
await page.waitForTimeout(200)
|
||||||
@ -399,11 +387,12 @@ profile003 = startProfileAt([40.16, -120.48], sketch006)
|
|||||||
// and replace the sketch on face with a hard coded custom plane, but since there was a sketch on that plane maybe it
|
// and replace the sketch on face with a hard coded custom plane, but since there was a sketch on that plane maybe it
|
||||||
// should have delete the sketch? it's broken atm, but not sure if worth fixing since desired behaviour is a little
|
// should have delete the sketch? it's broken atm, but not sure if worth fixing since desired behaviour is a little
|
||||||
// vague
|
// vague
|
||||||
// // DELETE PARENT EXTRUDE
|
// DELETE PARENT EXTRUDE
|
||||||
|
// await camPosition2()
|
||||||
// await page.mouse.click(parentExtrude.x, parentExtrude.y)
|
// await page.mouse.click(parentExtrude.x, parentExtrude.y)
|
||||||
// await page.waitForTimeout(100)
|
// await page.waitForTimeout(100)
|
||||||
// await expect(page.locator('.cm-activeLine')).toHaveText(
|
// await expect(page.locator('.cm-activeLine')).toHaveText(
|
||||||
// '|> line(end = [170.36, -121.61], tag = $seg01)'
|
// '|> line(end = [112.54, 127.64], tag = $seg02)'
|
||||||
// )
|
// )
|
||||||
// await u.clearCommandLogs()
|
// await u.clearCommandLogs()
|
||||||
// await page.keyboard.press('Backspace')
|
// await page.keyboard.press('Backspace')
|
||||||
@ -463,71 +452,77 @@ profile003 = startProfileAt([40.16, -120.48], sketch006)
|
|||||||
await page.waitForTimeout(200)
|
await page.waitForTimeout(200)
|
||||||
await expect(u.codeLocator).not.toContainText(codeToBeDeletedSnippet)
|
await expect(u.codeLocator).not.toContainText(codeToBeDeletedSnippet)
|
||||||
})
|
})
|
||||||
test.fixme(
|
test('parent Solid should be select and deletable and uses custom planes to position children', async ({
|
||||||
"Deleting solid that the AST mod can't handle results in a toast message",
|
page,
|
||||||
async ({ page, homePage }) => {
|
homePage,
|
||||||
|
scene,
|
||||||
|
cmdBar,
|
||||||
|
editor,
|
||||||
|
}) => {
|
||||||
|
test.setTimeout(90_000)
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await page.addInitScript(async () => {
|
await page.addInitScript(async () => {
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
'persistCode',
|
'persistCode',
|
||||||
`sketch001 = startSketchOn('XZ')
|
`part001 = startSketchOn('XY')
|
||||||
|> startProfileAt([-79.26, 95.04], %)
|
yo = startProfileAt([4.83, 12.56], part001)
|
||||||
|> line(end = [112.54, 127.64], tag = $seg02)
|
|> line(end = [15.1, 2.48])
|
||||||
|> line(end = [170.36, -121.61], tag = $seg01)
|
|> line(end = [3.15, -9.85], tag = $seg01)
|
||||||
|
|> line(end = [-15.17, -4.1])
|
||||||
|
|> angledLine([segAng(seg01), 12.35], %, $seg02)
|
||||||
|
|> line(end = [-13.02, 10.03])
|
||||||
|
|> close()
|
||||||
|
yoo = extrude(yo, length = 4)
|
||||||
|
sketch002 = startSketchOn(yoo, seg02)
|
||||||
|
sketch001 = startSketchOn(yoo, 'END')
|
||||||
|
profile002 = startProfileAt([-11.08, 2.39], sketch002)
|
||||||
|
|> line(end = [4.89, 0.9])
|
||||||
|
|> line(end = [-0.61, -2.41])
|
||||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||||
|> close()
|
|> close()
|
||||||
extrude001 = extrude(sketch001, length = 50)
|
extrude001 = extrude(profile002, length = 15)
|
||||||
launderExtrudeThroughVar = extrude001
|
profile001 = startProfileAt([7.49, 9.96], sketch001)
|
||||||
sketch002 = startSketchOn(launderExtrudeThroughVar, seg02)
|
|> angledLine([0, 5.05], %, $rectangleSegmentA001)
|
||||||
|> startProfileAt([-100.54, 16.99], %)
|
|> angledLine([
|
||||||
|> line(end = [0, 20.03])
|
segAng(rectangleSegmentA001) - 90,
|
||||||
|> line(end = [62.61, 0], tag = $seg03)
|
4.81
|
||||||
|
], %)
|
||||||
|
|> angledLine([
|
||||||
|
segAng(rectangleSegmentA001),
|
||||||
|
-segLen(rectangleSegmentA001)
|
||||||
|
], %)
|
||||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||||
|> close()
|
|> close()
|
||||||
|
|
||||||
`
|
`
|
||||||
)
|
)
|
||||||
}, KCL_DEFAULT_LENGTH)
|
}, KCL_DEFAULT_LENGTH)
|
||||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||||
|
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
|
await scene.settled(cmdBar)
|
||||||
|
|
||||||
await u.openDebugPanel()
|
const extrudeWall = { x: 575, y: 238 }
|
||||||
await u.expectCmdLog('[data-message-type="execution-done"]', 10_000)
|
|
||||||
await u.closeDebugPanel()
|
|
||||||
|
|
||||||
await u.openAndClearDebugPanel()
|
// DELETE with selection on face of parent
|
||||||
await u.sendCustomCmd({
|
await page.mouse.click(extrudeWall.x, extrudeWall.y)
|
||||||
type: 'modeling_cmd_req',
|
|
||||||
cmd_id: uuidv4(),
|
|
||||||
cmd: {
|
|
||||||
type: 'default_camera_look_at',
|
|
||||||
vantage: { x: 1139.49, y: -7053, z: 8597.31 },
|
|
||||||
center: { x: -2206.68, y: -1298.36, z: 60 },
|
|
||||||
up: { x: 0, y: 0, z: 1 },
|
|
||||||
},
|
|
||||||
})
|
|
||||||
await page.waitForTimeout(100)
|
|
||||||
await u.sendCustomCmd({
|
|
||||||
type: 'modeling_cmd_req',
|
|
||||||
cmd_id: uuidv4(),
|
|
||||||
cmd: {
|
|
||||||
type: 'default_camera_get_settings',
|
|
||||||
},
|
|
||||||
})
|
|
||||||
await page.waitForTimeout(100)
|
|
||||||
|
|
||||||
// attempt delete
|
|
||||||
await page.mouse.click(930, 139)
|
|
||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
await expect(page.locator('.cm-activeLine')).toHaveText(
|
await expect(page.locator('.cm-activeLine')).toHaveText(
|
||||||
'|> line(end = [170.36, -121.61], tag = $seg01)'
|
'|> line(end = [-15.17, -4.1])'
|
||||||
)
|
)
|
||||||
await u.clearCommandLogs()
|
await u.openAndClearDebugPanel()
|
||||||
await page.keyboard.press('Delete')
|
await page.keyboard.press('Delete')
|
||||||
|
await u.expectCmdLog('[data-message-type="execution-done"]', 10_000)
|
||||||
|
await page.waitForTimeout(200)
|
||||||
|
|
||||||
await expect(page.getByText('Unable to delete selection')).toBeVisible()
|
await editor.expectEditor.not.toContain(`yoo = extrude(yo, length = 4)`, {
|
||||||
}
|
shouldNormalise: true,
|
||||||
)
|
})
|
||||||
|
await editor.expectEditor.toContain(`startSketchOn({plane={origin`, {
|
||||||
|
shouldNormalise: true,
|
||||||
|
})
|
||||||
|
await editor.snapshot()
|
||||||
|
})
|
||||||
test('Hovering over 3d features highlights code, clicking puts the cursor in the right place and sends selection id to engine', async ({
|
test('Hovering over 3d features highlights code, clicking puts the cursor in the right place and sends selection id to engine', async ({
|
||||||
page,
|
page,
|
||||||
homePage,
|
homePage,
|
||||||
|
@ -0,0 +1 @@
|
|||||||
|
part001 = startSketchOn('XY')yo = startProfileAt([4.83, 12.56], part001) |> line(end = [15.1, 2.48]) |> line(end = [3.15, -9.85], tag = $seg01) |> line(end = [-15.17, -4.1]) |> angledLine([segAng(seg01), 12.35], %, $seg02) |> line(end = [-13.02, 10.03]) |> close()sketch002 = startSketchOn({ plane = { origin = { x = 7.49, y = 2.4, z = 0 }, xAxis = { x = -0.3, y = 0.95, z = 0 }, yAxis = { x = 0, y = 0, z = 1 }, zAxis = { x = 0.95, y = 0.3, z = 0 } }})sketch001 = startSketchOn({ plane = { origin = { x = 0, y = 0, z = 4 }, xAxis = { x = 1, y = 0, z = 0 }, yAxis = { x = 0, y = 1, z = 0 }, zAxis = { x = 0, y = 0, z = 1 } }})profile002 = startProfileAt([-11.08, 2.39], sketch002) |> line(end = [4.89, 0.9]) |> line(end = [-0.61, -2.41]) |> line(endAbsolute = [profileStartX(%), profileStartY(%)]) |> close()extrude001 = extrude(profile002, length = 15)profile001 = startProfileAt([7.49, 9.96], sketch001) |> angledLine([0, 5.05], %, $rectangleSegmentA001) |> angledLine([ segAng(rectangleSegmentA001) - 90, 4.81 ], %) |> angledLine([ segAng(rectangleSegmentA001), -segLen(rectangleSegmentA001) ], %) |> line(endAbsolute = [profileStartX(%), profileStartY(%)]) |> close()
|
@ -20,14 +20,20 @@ import { DeepPartial } from 'lib/types'
|
|||||||
import { Settings } from '@rust/kcl-lib/bindings/Settings'
|
import { Settings } from '@rust/kcl-lib/bindings/Settings'
|
||||||
|
|
||||||
test.describe('Testing settings', () => {
|
test.describe('Testing settings', () => {
|
||||||
test(
|
test('Stored settings are validated and fall back to defaults', async ({
|
||||||
'Stored settings are validated and fall back to defaults',
|
page,
|
||||||
|
homePage,
|
||||||
|
tronApp,
|
||||||
|
}) => {
|
||||||
|
if (!tronApp) {
|
||||||
|
fail()
|
||||||
|
}
|
||||||
// Override beforeEach test setup
|
// Override beforeEach test setup
|
||||||
// with corrupted settings
|
// with corrupted settings
|
||||||
{
|
await tronApp.cleanProjectDir(
|
||||||
appSettings: TEST_SETTINGS_CORRUPTED as DeepPartial<Settings>,
|
TEST_SETTINGS_CORRUPTED as DeepPartial<Settings>
|
||||||
},
|
)
|
||||||
async ({ page, homePage }) => {
|
|
||||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
|
|
||||||
// Check the settings were reset
|
// Check the settings were reset
|
||||||
@ -47,8 +53,7 @@ test.describe('Testing settings', () => {
|
|||||||
expect(storedSettings.settings?.project?.default_project_name).toBe(
|
expect(storedSettings.settings?.project?.default_project_name).toBe(
|
||||||
'project-$nnn'
|
'project-$nnn'
|
||||||
)
|
)
|
||||||
}
|
})
|
||||||
)
|
|
||||||
|
|
||||||
// The behavior is actually broken. Parent always takes precedence
|
// The behavior is actually broken. Parent always takes precedence
|
||||||
test.fixme(
|
test.fixme(
|
||||||
@ -357,8 +362,6 @@ test.describe('Testing settings', () => {
|
|||||||
`Load desktop app with no settings file`,
|
`Load desktop app with no settings file`,
|
||||||
{
|
{
|
||||||
tag: '@electron',
|
tag: '@electron',
|
||||||
// This is what makes no settings file get created
|
|
||||||
cleanProjectDir: false,
|
|
||||||
},
|
},
|
||||||
async ({ page }, testInfo) => {
|
async ({ page }, testInfo) => {
|
||||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
@ -379,13 +382,17 @@ test.describe('Testing settings', () => {
|
|||||||
`Load desktop app with a settings file, but no project directory setting`,
|
`Load desktop app with a settings file, but no project directory setting`,
|
||||||
{
|
{
|
||||||
tag: '@electron',
|
tag: '@electron',
|
||||||
appSettings: {
|
},
|
||||||
|
async ({ context, page, tronApp }, testInfo) => {
|
||||||
|
if (!tronApp) {
|
||||||
|
fail()
|
||||||
|
}
|
||||||
|
await tronApp.cleanProjectDir({
|
||||||
app: {
|
app: {
|
||||||
theme_color: '259',
|
theme_color: '259',
|
||||||
},
|
},
|
||||||
},
|
})
|
||||||
},
|
|
||||||
async ({ context, page }, testInfo) => {
|
|
||||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
|
|
||||||
// Selectors and constants
|
// Selectors and constants
|
||||||
@ -405,15 +412,20 @@ test.describe('Testing settings', () => {
|
|||||||
'user settings reload on external change, on project and modeling view',
|
'user settings reload on external change, on project and modeling view',
|
||||||
{
|
{
|
||||||
tag: '@electron',
|
tag: '@electron',
|
||||||
appSettings: {
|
},
|
||||||
|
async ({ context, page, tronApp }, testInfo) => {
|
||||||
|
if (!tronApp) {
|
||||||
|
fail()
|
||||||
|
}
|
||||||
|
|
||||||
|
await tronApp.cleanProjectDir({
|
||||||
app: {
|
app: {
|
||||||
// Doesn't matter what you set it to. It will
|
// Doesn't matter what you set it to. It will
|
||||||
// default to 264.5
|
// default to 264.5
|
||||||
theme_color: '0',
|
theme_color: '0',
|
||||||
},
|
},
|
||||||
},
|
})
|
||||||
},
|
|
||||||
async ({ context, page }, testInfo) => {
|
|
||||||
const { dir: projectDirName } = await context.folderSetupFn(
|
const { dir: projectDirName } = await context.folderSetupFn(
|
||||||
async () => {}
|
async () => {}
|
||||||
)
|
)
|
||||||
@ -783,13 +795,20 @@ test.describe('Testing settings', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
test(
|
test(`Changing system theme preferences (via media query) should update UI and stream`, async ({
|
||||||
`Changing system theme preferences (via media query) should update UI and stream`,
|
page,
|
||||||
{
|
homePage,
|
||||||
|
tronApp,
|
||||||
|
}) => {
|
||||||
|
if (!tronApp) {
|
||||||
|
fail()
|
||||||
|
}
|
||||||
|
|
||||||
|
await tronApp.cleanProjectDir({
|
||||||
// Override the settings so that the theme is set to `system`
|
// Override the settings so that the theme is set to `system`
|
||||||
appSettings: TEST_SETTINGS_DEFAULT_THEME,
|
...TEST_SETTINGS_DEFAULT_THEME,
|
||||||
},
|
})
|
||||||
async ({ page, homePage }) => {
|
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
|
|
||||||
// Selectors and constants
|
// Selectors and constants
|
||||||
@ -829,29 +848,31 @@ test.describe('Testing settings', () => {
|
|||||||
.poll(() => streamBackgroundPixelIsColor(darkBackgroundColor))
|
.poll(() => streamBackgroundPixelIsColor(darkBackgroundColor))
|
||||||
.toBeLessThan(15)
|
.toBeLessThan(15)
|
||||||
})
|
})
|
||||||
}
|
})
|
||||||
)
|
|
||||||
|
|
||||||
test(
|
test(`Turning off "Show debug panel" with debug panel open leaves no phantom panel`, async ({
|
||||||
`Turning off "Show debug panel" with debug panel open leaves no phantom panel`,
|
context,
|
||||||
{
|
page,
|
||||||
|
homePage,
|
||||||
|
tronApp,
|
||||||
|
}) => {
|
||||||
|
if (!tronApp) {
|
||||||
|
fail()
|
||||||
|
}
|
||||||
|
|
||||||
|
await tronApp.cleanProjectDir({
|
||||||
// Override beforeEach test setup
|
// Override beforeEach test setup
|
||||||
// with debug panel open
|
// with debug panel open
|
||||||
// but "show debug panel" set to false
|
// but "show debug panel" set to false
|
||||||
appSettings: {
|
|
||||||
...TEST_SETTINGS,
|
...TEST_SETTINGS,
|
||||||
app: { ...TEST_SETTINGS.app, show_debug_panel: false },
|
app: { ...TEST_SETTINGS.app, show_debug_panel: false },
|
||||||
modeling: { ...TEST_SETTINGS.modeling },
|
modeling: { ...TEST_SETTINGS.modeling },
|
||||||
},
|
})
|
||||||
},
|
|
||||||
async ({ context, page, homePage }) => {
|
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
|
|
||||||
await context.addInitScript(async () => {
|
await context.addInitScript(async () => {
|
||||||
localStorage.setItem(
|
localStorage.setItem('persistModelingContext', '{"openPanes":["debug"]}')
|
||||||
'persistModelingContext',
|
|
||||||
'{"openPanes":["debug"]}'
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
@ -903,8 +924,7 @@ test.describe('Testing settings', () => {
|
|||||||
await expect(debugPaneButton).not.toBeVisible()
|
await expect(debugPaneButton).not.toBeVisible()
|
||||||
await expect(resizeHandle).not.toBeVisible()
|
await expect(resizeHandle).not.toBeVisible()
|
||||||
})
|
})
|
||||||
}
|
})
|
||||||
)
|
|
||||||
|
|
||||||
test(`Change inline units setting`, async ({
|
test(`Change inline units setting`, async ({
|
||||||
page,
|
page,
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { test, expect, Page } from './zoo-test'
|
import { Page } from '@playwright/test'
|
||||||
|
import { test, expect } from './zoo-test'
|
||||||
import { getUtils, createProject } from './test-utils'
|
import { getUtils, createProject } from './test-utils'
|
||||||
import { join } from 'path'
|
import { join } from 'path'
|
||||||
import fs from 'fs'
|
import fs from 'fs'
|
||||||
@ -429,7 +430,8 @@ test.describe('Text-to-CAD tests', { tag: ['@skipWin'] }, () => {
|
|||||||
await expect(page.getByText(promptWithNewline)).toBeVisible()
|
await expect(page.getByText(promptWithNewline)).toBeVisible()
|
||||||
})
|
})
|
||||||
|
|
||||||
test(
|
// This will be fine once greg makes prompt at top of file deterministic
|
||||||
|
test.fixme(
|
||||||
'can do many at once and get many prompts back, and interact with many',
|
'can do many at once and get many prompts back, and interact with many',
|
||||||
{ tag: ['@skipWin'] },
|
{ tag: ['@skipWin'] },
|
||||||
async ({ page, homePage }) => {
|
async ({ page, homePage }) => {
|
||||||
@ -490,8 +492,15 @@ test.describe('Text-to-CAD tests', { tag: ['@skipWin'] }, () => {
|
|||||||
// Click the button.
|
// Click the button.
|
||||||
await copyToClipboardButton.first().click()
|
await copyToClipboardButton.first().click()
|
||||||
|
|
||||||
// Expect the code to be pasted.
|
// Do NOT do AI tests like this: "Expect the code to be pasted."
|
||||||
await expect(page.locator('.cm-content')).toContainText(`2x8`)
|
// Reason: AI tests are NONDETERMINISTIC. Thus we need to be as most
|
||||||
|
// general as we can for the assertion.
|
||||||
|
// We can use Kolmogorov complexity as a measurement of the
|
||||||
|
// "probably most minimal version of this program" to have a lower
|
||||||
|
// bound to work with. It is completely by feel because there are
|
||||||
|
// no proofs that any program is its smallest self.
|
||||||
|
const code2x8 = await page.locator('.cm-content').innerText()
|
||||||
|
await expect(code2x8.length).toBeGreaterThan(249)
|
||||||
|
|
||||||
// Ensure the final toast remains.
|
// Ensure the final toast remains.
|
||||||
await expect(page.getByText(`a 2x10 lego`)).not.toBeVisible()
|
await expect(page.getByText(`a 2x10 lego`)).not.toBeVisible()
|
||||||
@ -504,7 +513,8 @@ test.describe('Text-to-CAD tests', { tag: ['@skipWin'] }, () => {
|
|||||||
await copyToClipboardButton.click()
|
await copyToClipboardButton.click()
|
||||||
|
|
||||||
// Expect the code to be pasted.
|
// Expect the code to be pasted.
|
||||||
await expect(page.locator('.cm-content')).toContainText(`2x4`)
|
const code2x4 = await page.locator('.cm-content').innerText()
|
||||||
|
await expect(code2x4.length).toBeGreaterThan(249)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -35,7 +35,7 @@ test.fixme('Units menu', async ({ page, homePage }) => {
|
|||||||
test(
|
test(
|
||||||
'Successful export shows a success toast',
|
'Successful export shows a success toast',
|
||||||
{ tag: '@skipLocalEngine' },
|
{ tag: '@skipLocalEngine' },
|
||||||
async ({ page, homePage }) => {
|
async ({ page, homePage, tronApp }) => {
|
||||||
// FYI this test doesn't work with only engine running locally
|
// FYI this test doesn't work with only engine running locally
|
||||||
// And you will need to have the KittyCAD CLI installed
|
// And you will need to have the KittyCAD CLI installed
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
@ -92,12 +92,17 @@ part001 = startSketchOn('-XZ')
|
|||||||
await page.waitForTimeout(1000)
|
await page.waitForTimeout(1000)
|
||||||
await u.clearAndCloseDebugPanel()
|
await u.clearAndCloseDebugPanel()
|
||||||
|
|
||||||
|
if (!tronApp?.projectDirName) {
|
||||||
|
fail()
|
||||||
|
}
|
||||||
|
|
||||||
await doExport(
|
await doExport(
|
||||||
{
|
{
|
||||||
type: 'gltf',
|
type: 'gltf',
|
||||||
storage: 'embedded',
|
storage: 'embedded',
|
||||||
presentation: 'pretty',
|
presentation: 'pretty',
|
||||||
},
|
},
|
||||||
|
tronApp?.projectDirName,
|
||||||
page
|
page
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -465,7 +470,7 @@ test('Delete key does not navigate back', async ({ page, homePage }) => {
|
|||||||
await expect.poll(() => page.url()).not.toContain('/settings')
|
await expect.poll(() => page.url()).not.toContain('/settings')
|
||||||
})
|
})
|
||||||
|
|
||||||
test('Sketch on face', async ({ page, homePage, scene, cmdBar }) => {
|
test('Sketch on face', async ({ page, homePage, scene, cmdBar, toolbar }) => {
|
||||||
test.setTimeout(90_000)
|
test.setTimeout(90_000)
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await page.addInitScript(async () => {
|
await page.addInitScript(async () => {
|
||||||
@ -491,17 +496,12 @@ extrude001 = extrude(sketch001, length = 5 + 7)`
|
|||||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
|
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
await scene.waitForExecutionDone()
|
await scene.connectionEstablished()
|
||||||
|
await scene.settled(cmdBar)
|
||||||
await expect(
|
|
||||||
page.getByRole('button', { name: 'Start Sketch' })
|
|
||||||
).not.toBeDisabled()
|
|
||||||
|
|
||||||
await page.getByRole('button', { name: 'Start Sketch' }).click()
|
|
||||||
await page.waitForTimeout(300)
|
|
||||||
|
|
||||||
let previousCodeContent = await page.locator('.cm-content').innerText()
|
let previousCodeContent = await page.locator('.cm-content').innerText()
|
||||||
|
|
||||||
|
await toolbar.startSketchThenCallbackThenWaitUntilReady(async () => {
|
||||||
await u.openAndClearDebugPanel()
|
await u.openAndClearDebugPanel()
|
||||||
await u.doAndWaitForCmd(
|
await u.doAndWaitForCmd(
|
||||||
() => page.mouse.click(625, 165),
|
() => page.mouse.click(625, 165),
|
||||||
@ -510,6 +510,8 @@ extrude001 = extrude(sketch001, length = 5 + 7)`
|
|||||||
)
|
)
|
||||||
await page.waitForTimeout(150)
|
await page.waitForTimeout(150)
|
||||||
await u.closeDebugPanel()
|
await u.closeDebugPanel()
|
||||||
|
})
|
||||||
|
await page.waitForTimeout(300)
|
||||||
|
|
||||||
const firstClickPosition = [612, 238]
|
const firstClickPosition = [612, 238]
|
||||||
const secondClickPosition = [661, 242]
|
const secondClickPosition = [661, 242]
|
||||||
|
@ -1,21 +1,11 @@
|
|||||||
import {
|
/* eslint-disable react-hooks/rules-of-hooks */
|
||||||
test as playwrightTestFn,
|
|
||||||
TestInfo as TestInfoPlaywright,
|
import { test as playwrightTestFn, ElectronApplication } from '@playwright/test'
|
||||||
BrowserContext as BrowserContextPlaywright,
|
|
||||||
Page as PagePlaywright,
|
|
||||||
TestDetails as TestDetailsPlaywright,
|
|
||||||
PlaywrightTestArgs,
|
|
||||||
PlaywrightTestOptions,
|
|
||||||
PlaywrightWorkerArgs,
|
|
||||||
PlaywrightWorkerOptions,
|
|
||||||
ElectronApplication,
|
|
||||||
} from '@playwright/test'
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
fixtures,
|
fixturesBasedOnProcessEnvPlatform,
|
||||||
Fixtures,
|
Fixtures,
|
||||||
AuthenticatedTronApp,
|
ElectronZoo,
|
||||||
AuthenticatedApp,
|
|
||||||
} from './fixtures/fixtureSetup'
|
} from './fixtures/fixtureSetup'
|
||||||
|
|
||||||
import { Settings } from '@rust/kcl-lib/bindings/Settings'
|
import { Settings } from '@rust/kcl-lib/bindings/Settings'
|
||||||
@ -23,9 +13,6 @@ import { DeepPartial } from 'lib/types'
|
|||||||
export { expect } from '@playwright/test'
|
export { expect } from '@playwright/test'
|
||||||
|
|
||||||
declare module '@playwright/test' {
|
declare module '@playwright/test' {
|
||||||
interface TestInfo {
|
|
||||||
tronApp?: AuthenticatedTronApp
|
|
||||||
}
|
|
||||||
interface BrowserContext {
|
interface BrowserContext {
|
||||||
folderSetupFn: (
|
folderSetupFn: (
|
||||||
cb: (dir: string) => Promise<void>
|
cb: (dir: string) => Promise<void>
|
||||||
@ -41,288 +28,31 @@ declare module '@playwright/test' {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TestInfo = TestInfoPlaywright
|
// Each worker spawns a new thread, which will spawn its own ElectronZoo.
|
||||||
export type BrowserContext = BrowserContextPlaywright
|
// So in some sense there is an implicit pool.
|
||||||
export type Page = PagePlaywright
|
// For example, the variable just beneath this text is reused many times
|
||||||
export type TestDetails = TestDetailsPlaywright & {
|
// *for one worker*.
|
||||||
cleanProjectDir?: boolean
|
const electronZooInstance = new ElectronZoo()
|
||||||
appSettings?: DeepPartial<Settings>
|
|
||||||
}
|
|
||||||
|
|
||||||
// Our custom decorated Zoo test object. Makes it easier to add fixtures, and
|
// Our custom decorated Zoo test object. Makes it easier to add fixtures, and
|
||||||
// switch between web and electron if needed.
|
// switch between web and electron if needed.
|
||||||
const pwTestFnWithFixtures = playwrightTestFn.extend<Fixtures>(fixtures)
|
const playwrightTestFnWithFixtures_ = playwrightTestFn.extend<{
|
||||||
|
tronApp?: ElectronZoo
|
||||||
// In JavaScript you cannot replace a function's body only (despite functions
|
}>({
|
||||||
// are themselves objects, which you'd expect a body property or something...)
|
tronApp: async ({}, use, testInfo) => {
|
||||||
// So we must redefine the function and then re-attach properties.
|
|
||||||
type PWFunction = (
|
|
||||||
args: PlaywrightTestArgs &
|
|
||||||
Fixtures &
|
|
||||||
PlaywrightWorkerArgs &
|
|
||||||
PlaywrightTestOptions &
|
|
||||||
PlaywrightWorkerOptions & {
|
|
||||||
electronApp?: ElectronApplication
|
|
||||||
},
|
|
||||||
testInfo: TestInfo
|
|
||||||
) => void | Promise<void>
|
|
||||||
|
|
||||||
let firstUrl = ''
|
|
||||||
|
|
||||||
export const test = (
|
|
||||||
desc: string,
|
|
||||||
objOrFn: PWFunction | TestDetails,
|
|
||||||
fnMaybe?: PWFunction
|
|
||||||
) => {
|
|
||||||
const hasTestConf = typeof objOrFn === 'object'
|
|
||||||
const fn = hasTestConf ? fnMaybe : objOrFn
|
|
||||||
|
|
||||||
return pwTestFnWithFixtures(
|
|
||||||
desc,
|
|
||||||
hasTestConf ? objOrFn : {},
|
|
||||||
async (
|
|
||||||
{
|
|
||||||
page,
|
|
||||||
context,
|
|
||||||
cmdBar,
|
|
||||||
editor,
|
|
||||||
toolbar,
|
|
||||||
scene,
|
|
||||||
homePage,
|
|
||||||
request,
|
|
||||||
playwright,
|
|
||||||
browser,
|
|
||||||
acceptDownloads,
|
|
||||||
bypassCSP,
|
|
||||||
colorScheme,
|
|
||||||
clientCertificates,
|
|
||||||
deviceScaleFactor,
|
|
||||||
extraHTTPHeaders,
|
|
||||||
geolocation,
|
|
||||||
hasTouch,
|
|
||||||
httpCredentials,
|
|
||||||
ignoreHTTPSErrors,
|
|
||||||
isMobile,
|
|
||||||
javaScriptEnabled,
|
|
||||||
locale,
|
|
||||||
offline,
|
|
||||||
permissions,
|
|
||||||
proxy,
|
|
||||||
storageState,
|
|
||||||
timezoneId,
|
|
||||||
userAgent,
|
|
||||||
viewport,
|
|
||||||
baseURL,
|
|
||||||
contextOptions,
|
|
||||||
actionTimeout,
|
|
||||||
navigationTimeout,
|
|
||||||
serviceWorkers,
|
|
||||||
testIdAttribute,
|
|
||||||
browserName,
|
|
||||||
defaultBrowserType,
|
|
||||||
headless,
|
|
||||||
channel,
|
|
||||||
launchOptions,
|
|
||||||
connectOptions,
|
|
||||||
screenshot,
|
|
||||||
trace,
|
|
||||||
video,
|
|
||||||
},
|
|
||||||
testInfo
|
|
||||||
) => {
|
|
||||||
// To switch to web, use PLATFORM=web environment variable.
|
|
||||||
// Only use this for debugging, since the playwright tracer is busted
|
|
||||||
// for electron.
|
|
||||||
|
|
||||||
let tronApp
|
|
||||||
|
|
||||||
if (process.env.PLATFORM === 'web') {
|
if (process.env.PLATFORM === 'web') {
|
||||||
tronApp = new AuthenticatedApp(context, page, testInfo)
|
await use(undefined)
|
||||||
} else {
|
|
||||||
tronApp = new AuthenticatedTronApp(context, page, testInfo)
|
|
||||||
}
|
|
||||||
|
|
||||||
const fixtures: Fixtures = { cmdBar, editor, toolbar, scene, homePage }
|
|
||||||
if (tronApp instanceof AuthenticatedTronApp) {
|
|
||||||
const options = {
|
|
||||||
fixtures,
|
|
||||||
}
|
|
||||||
if (hasTestConf) {
|
|
||||||
Object.assign(options, {
|
|
||||||
appSettings: objOrFn?.appSettings,
|
|
||||||
cleanProjectDir: objOrFn?.cleanProjectDir,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
await tronApp.initialise(options)
|
|
||||||
} else {
|
|
||||||
await tronApp.initialise('')
|
|
||||||
}
|
|
||||||
|
|
||||||
// We need to patch this because addInitScript will bind too late in our
|
|
||||||
// electron tests, never running. We need to call reload() after each call
|
|
||||||
// to guarantee it runs.
|
|
||||||
const oldContextAddInitScript = tronApp.context.addInitScript
|
|
||||||
tronApp.context.addInitScript = async function (a, b) {
|
|
||||||
// @ts-ignore pretty sure way out of tsc's type checking capabilities.
|
|
||||||
// This code works perfectly fine.
|
|
||||||
await oldContextAddInitScript.apply(this, [a, b])
|
|
||||||
await tronApp.page.reload()
|
|
||||||
}
|
|
||||||
|
|
||||||
// No idea why we mix and match page and context's addInitScript but we do
|
|
||||||
const oldPageAddInitScript = tronApp.page.addInitScript
|
|
||||||
tronApp.page.addInitScript = async function (a: any, b: any) {
|
|
||||||
// @ts-ignore pretty sure way out of tsc's type checking capabilities.
|
|
||||||
// This code works perfectly fine.
|
|
||||||
await oldPageAddInitScript.apply(this, [a, b])
|
|
||||||
await tronApp.page.reload()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a consistent way to resize the page across electron and web.
|
|
||||||
// (lee) I had to do everything in the book to make electron change its
|
|
||||||
// damn window size. I succeeded in making it consistently and reliably
|
|
||||||
// do it after a whole afternoon.
|
|
||||||
tronApp.page.setBodyDimensions = async function (dims: {
|
|
||||||
width: number
|
|
||||||
height: number
|
|
||||||
}) {
|
|
||||||
await tronApp.page.setViewportSize(dims)
|
|
||||||
|
|
||||||
if (!(tronApp instanceof AuthenticatedTronApp)) {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
await tronApp.electronApp?.evaluateHandle(async ({ app }, dims) => {
|
await electronZooInstance.createInstanceIfMissing(testInfo)
|
||||||
// @ts-ignore sorry jon but see comment in main.ts why this is ignored
|
await use(electronZooInstance)
|
||||||
await app.resizeWindow(dims.width, dims.height)
|
await electronZooInstance.makeAvailableAgain()
|
||||||
}, dims)
|
|
||||||
|
|
||||||
return tronApp.page.evaluate(
|
|
||||||
async (dims: { width: number; height: number }) => {
|
|
||||||
await window.electron.resizeWindow(dims.width, dims.height)
|
|
||||||
window.document.body.style.width = dims.width + 'px'
|
|
||||||
window.document.body.style.height = dims.height + 'px'
|
|
||||||
window.document.documentElement.style.width = dims.width + 'px'
|
|
||||||
window.document.documentElement.style.height = dims.height + 'px'
|
|
||||||
},
|
},
|
||||||
dims
|
})
|
||||||
|
|
||||||
|
const test = playwrightTestFnWithFixtures_.extend<Fixtures>(
|
||||||
|
fixturesBasedOnProcessEnvPlatform
|
||||||
)
|
)
|
||||||
}
|
|
||||||
|
|
||||||
await tronApp.page.setBodyDimensions(tronApp.viewPortSize)
|
export { test }
|
||||||
|
|
||||||
// We need to expose this in order for some tests that require folder
|
|
||||||
// creation. Before they used to do this by their own electronSetup({...})
|
|
||||||
// calls.
|
|
||||||
if (tronApp instanceof AuthenticatedTronApp) {
|
|
||||||
tronApp.context.folderSetupFn = async function (fn) {
|
|
||||||
return fn(tronApp.dir)
|
|
||||||
.then(() => tronApp.page.reload())
|
|
||||||
.then(() => ({
|
|
||||||
dir: tronApp.dir,
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!firstUrl) {
|
|
||||||
await tronApp.page.getByText('Your Projects').count()
|
|
||||||
firstUrl = tronApp.page.url()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Due to the app controlling its own window context we need to inject new
|
|
||||||
// options and context here.
|
|
||||||
// NOTE TO LEE: Seems to destroy page context when calling an electron loadURL.
|
|
||||||
// await tronApp.electronApp.evaluate(({ app }) => {
|
|
||||||
// return app.reuseWindowForTest();
|
|
||||||
// });
|
|
||||||
|
|
||||||
await tronApp.electronApp?.evaluate(({ app }, projectDirName) => {
|
|
||||||
// @ts-ignore can't declaration merge see main.ts
|
|
||||||
app.testProperty['TEST_SETTINGS_FILE_KEY'] = projectDirName
|
|
||||||
}, tronApp.dir)
|
|
||||||
|
|
||||||
// Always start at the root view
|
|
||||||
await tronApp.page.goto(firstUrl)
|
|
||||||
|
|
||||||
// Force a hard reload, destroying the stream and other state
|
|
||||||
await tronApp.page.reload()
|
|
||||||
|
|
||||||
// tsc aint smart enough to know this'll never be undefined
|
|
||||||
// but I dont blame it, the logic to know is complex
|
|
||||||
if (fn) {
|
|
||||||
await fn(
|
|
||||||
{
|
|
||||||
context: tronApp.context,
|
|
||||||
page: tronApp.page,
|
|
||||||
electronApp:
|
|
||||||
tronApp instanceof AuthenticatedTronApp
|
|
||||||
? tronApp.electronApp
|
|
||||||
: undefined,
|
|
||||||
...fixtures,
|
|
||||||
request,
|
|
||||||
playwright,
|
|
||||||
browser,
|
|
||||||
acceptDownloads,
|
|
||||||
bypassCSP,
|
|
||||||
colorScheme,
|
|
||||||
clientCertificates,
|
|
||||||
deviceScaleFactor,
|
|
||||||
extraHTTPHeaders,
|
|
||||||
geolocation,
|
|
||||||
hasTouch,
|
|
||||||
httpCredentials,
|
|
||||||
ignoreHTTPSErrors,
|
|
||||||
isMobile,
|
|
||||||
javaScriptEnabled,
|
|
||||||
locale,
|
|
||||||
offline,
|
|
||||||
permissions,
|
|
||||||
proxy,
|
|
||||||
storageState,
|
|
||||||
timezoneId,
|
|
||||||
userAgent,
|
|
||||||
viewport,
|
|
||||||
baseURL,
|
|
||||||
contextOptions,
|
|
||||||
actionTimeout,
|
|
||||||
navigationTimeout,
|
|
||||||
serviceWorkers,
|
|
||||||
testIdAttribute,
|
|
||||||
browserName,
|
|
||||||
defaultBrowserType,
|
|
||||||
headless,
|
|
||||||
channel,
|
|
||||||
launchOptions,
|
|
||||||
connectOptions,
|
|
||||||
screenshot,
|
|
||||||
trace,
|
|
||||||
video,
|
|
||||||
},
|
|
||||||
testInfo
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
testInfo.tronApp =
|
|
||||||
tronApp instanceof AuthenticatedTronApp ? tronApp : undefined
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
type ZooTest = typeof test
|
|
||||||
|
|
||||||
test.describe = pwTestFnWithFixtures.describe
|
|
||||||
test.beforeEach = pwTestFnWithFixtures.beforeEach
|
|
||||||
test.afterEach = pwTestFnWithFixtures.afterEach
|
|
||||||
test.step = pwTestFnWithFixtures.step
|
|
||||||
test.skip = pwTestFnWithFixtures.skip
|
|
||||||
test.setTimeout = pwTestFnWithFixtures.setTimeout
|
|
||||||
test.fixme = pwTestFnWithFixtures.fixme as unknown as ZooTest
|
|
||||||
test.only = pwTestFnWithFixtures.only
|
|
||||||
test.fail = pwTestFnWithFixtures.fail
|
|
||||||
test.slow = pwTestFnWithFixtures.slow
|
|
||||||
test.beforeAll = pwTestFnWithFixtures.beforeAll
|
|
||||||
test.afterAll = pwTestFnWithFixtures.afterAll
|
|
||||||
test.use = pwTestFnWithFixtures.use
|
|
||||||
test.expect = pwTestFnWithFixtures.expect
|
|
||||||
test.extend = pwTestFnWithFixtures.extend
|
|
||||||
test.info = pwTestFnWithFixtures.info
|
|
||||||
|
@ -93,7 +93,7 @@
|
|||||||
"fetch:wasm:windows": "./scripts/get-latest-wasm-bundle.ps1",
|
"fetch:wasm:windows": "./scripts/get-latest-wasm-bundle.ps1",
|
||||||
"fetch:samples": "rm -rf public/kcl-samples* && curl -L -o public/kcl-samples.zip https://github.com/KittyCAD/kcl-samples/archive/refs/heads/achalmers/kw-args-xylineto.zip && unzip -o public/kcl-samples.zip -d public && mv public/kcl-samples-* public/kcl-samples",
|
"fetch:samples": "rm -rf public/kcl-samples* && curl -L -o public/kcl-samples.zip https://github.com/KittyCAD/kcl-samples/archive/refs/heads/achalmers/kw-args-xylineto.zip && unzip -o public/kcl-samples.zip -d public && mv public/kcl-samples-* public/kcl-samples",
|
||||||
"build:wasm-dev": "yarn wasm-prep && (cd rust && wasm-pack build kcl-wasm-lib --dev --target web --out-dir pkg && cargo test -p kcl-lib export_bindings) && yarn isomorphic-copy-wasm && yarn fmt",
|
"build:wasm-dev": "yarn wasm-prep && (cd rust && wasm-pack build kcl-wasm-lib --dev --target web --out-dir pkg && cargo test -p kcl-lib export_bindings) && yarn isomorphic-copy-wasm && yarn fmt",
|
||||||
"build:wasm:nocopy": "yarn wasm-prep && cd rust && wasm-pack build kcl-wasm-lib --release --target web --out-dir pkg && cargo test -p kcl-lib export_bindings",
|
"build:wasm:nocopy": "yarn wasm-prep && cd rust && RUSTFLAGS='--cfg getrandom_backend=\"wasm_js\"' wasm-pack build kcl-wasm-lib --release --target web --out-dir pkg && cargo test -p kcl-lib export_bindings",
|
||||||
"build:wasm": "yarn build:wasm:nocopy && cp rust/kcl-wasm-lib/pkg/kcl_wasm_lib_bg.wasm public && yarn fmt",
|
"build:wasm": "yarn build:wasm:nocopy && cp rust/kcl-wasm-lib/pkg/kcl_wasm_lib_bg.wasm public && yarn fmt",
|
||||||
"build:wasm:windows": "yarn install:wasm-pack:cargo && yarn build:wasm:nocopy && ./scripts/copy-wasm.ps1 && yarn fmt",
|
"build:wasm:windows": "yarn install:wasm-pack:cargo && yarn build:wasm:nocopy && ./scripts/copy-wasm.ps1 && yarn fmt",
|
||||||
"remove-importmeta": "sed -i 's/import.meta.url/window.location.origin/g' \"./rust/kcl-wasm-lib/pkg/kcl_wasm_lib.js\"; sed -i '' 's/import.meta.url/window.location.origin/g' \"./rust/kcl-wasm-lib/pkg/kcl_wasm_lib.js\" || echo \"sed for both mac and linux\"",
|
"remove-importmeta": "sed -i 's/import.meta.url/window.location.origin/g' \"./rust/kcl-wasm-lib/pkg/kcl_wasm_lib.js\"; sed -i '' 's/import.meta.url/window.location.origin/g' \"./rust/kcl-wasm-lib/pkg/kcl_wasm_lib.js\" || echo \"sed for both mac and linux\"",
|
||||||
@ -106,7 +106,7 @@
|
|||||||
"files:flip-to-nightly:windows": "./scripts/flip-files-to-nightly.ps1",
|
"files:flip-to-nightly:windows": "./scripts/flip-files-to-nightly.ps1",
|
||||||
"files:invalidate-bucket": "./scripts/invalidate-files-bucket.sh",
|
"files:invalidate-bucket": "./scripts/invalidate-files-bucket.sh",
|
||||||
"files:invalidate-bucket:nightly": "./scripts/invalidate-files-bucket.sh --nightly",
|
"files:invalidate-bucket:nightly": "./scripts/invalidate-files-bucket.sh --nightly",
|
||||||
"postinstall": "./node_modules/.bin/electron-rebuild",
|
"postinstall": "yarn --cwd ./rust/kcl-language-server --modules-folder node_modules install && ./node_modules/.bin/electron-rebuild",
|
||||||
"make:dev": "make dev",
|
"make:dev": "make dev",
|
||||||
"generate:machine-api": "npx openapi-typescript ./openapi/machine-api.json -o src/lib/machine-api.d.ts",
|
"generate:machine-api": "npx openapi-typescript ./openapi/machine-api.json -o src/lib/machine-api.d.ts",
|
||||||
"generate:samples-manifest": "cd public/kcl-samples && node generate-manifest.js",
|
"generate:samples-manifest": "cd public/kcl-samples && node generate-manifest.js",
|
||||||
|
@ -23,14 +23,14 @@ KCL samples conform to a set of style guidelines to ensure consistency and reada
|
|||||||
When you submit a PR to add or modify KCL samples, images and STEP files will be generated and added to the repository automatically.
|
When you submit a PR to add or modify KCL samples, images and STEP files will be generated and added to the repository automatically.
|
||||||
|
|
||||||
---
|
---
|
||||||
#### [3d-boaty](3d-boaty/main.kcl) ([step](step/3d-boaty.step)) ([screenshot](screenshots/3d-boaty.png))
|
|
||||||
[](3d-boaty/main.kcl)
|
|
||||||
#### [80-20-rail](80-20-rail/main.kcl) ([step](step/80-20-rail.step)) ([screenshot](screenshots/80-20-rail.png))
|
#### [80-20-rail](80-20-rail/main.kcl) ([step](step/80-20-rail.step)) ([screenshot](screenshots/80-20-rail.png))
|
||||||
[](80-20-rail/main.kcl)
|
[](80-20-rail/main.kcl)
|
||||||
#### [a-parametric-bearing-pillow-block](a-parametric-bearing-pillow-block/main.kcl) ([step](step/a-parametric-bearing-pillow-block.step)) ([screenshot](screenshots/a-parametric-bearing-pillow-block.png))
|
#### [a-parametric-bearing-pillow-block](a-parametric-bearing-pillow-block/main.kcl) ([step](step/a-parametric-bearing-pillow-block.step)) ([screenshot](screenshots/a-parametric-bearing-pillow-block.png))
|
||||||
[](a-parametric-bearing-pillow-block/main.kcl)
|
[](a-parametric-bearing-pillow-block/main.kcl)
|
||||||
#### [ball-bearing](ball-bearing/main.kcl) ([step](step/ball-bearing.step)) ([screenshot](screenshots/ball-bearing.png))
|
#### [ball-bearing](ball-bearing/main.kcl) ([step](step/ball-bearing.step)) ([screenshot](screenshots/ball-bearing.png))
|
||||||
[](ball-bearing/main.kcl)
|
[](ball-bearing/main.kcl)
|
||||||
|
#### [bench](bench/main.kcl) ([step](step/bench.step)) ([screenshot](screenshots/bench.png))
|
||||||
|
[](bench/main.kcl)
|
||||||
#### [bracket](bracket/main.kcl) ([step](step/bracket.step)) ([screenshot](screenshots/bracket.png))
|
#### [bracket](bracket/main.kcl) ([step](step/bracket.step)) ([screenshot](screenshots/bracket.png))
|
||||||
[](bracket/main.kcl)
|
[](bracket/main.kcl)
|
||||||
#### [car-wheel-assembly](car-wheel-assembly/main.kcl) ([step](step/car-wheel-assembly.step)) ([screenshot](screenshots/car-wheel-assembly.png))
|
#### [car-wheel-assembly](car-wheel-assembly/main.kcl) ([step](step/car-wheel-assembly.step)) ([screenshot](screenshots/car-wheel-assembly.png))
|
||||||
@ -45,10 +45,8 @@ When you submit a PR to add or modify KCL samples, images and STEP files will be
|
|||||||
[](enclosure/main.kcl)
|
[](enclosure/main.kcl)
|
||||||
#### [exhaust-manifold](exhaust-manifold/main.kcl) ([step](step/exhaust-manifold.step)) ([screenshot](screenshots/exhaust-manifold.png))
|
#### [exhaust-manifold](exhaust-manifold/main.kcl) ([step](step/exhaust-manifold.step)) ([screenshot](screenshots/exhaust-manifold.png))
|
||||||
[](exhaust-manifold/main.kcl)
|
[](exhaust-manifold/main.kcl)
|
||||||
#### [flange-with-patterns](flange-with-patterns/main.kcl) ([step](step/flange-with-patterns.step)) ([screenshot](screenshots/flange-with-patterns.png))
|
#### [flange](flange/main.kcl) ([step](step/flange.step)) ([screenshot](screenshots/flange.png))
|
||||||
[](flange-with-patterns/main.kcl)
|
[](flange/main.kcl)
|
||||||
#### [flange-xy](flange-xy/main.kcl) ([step](step/flange-xy.step)) ([screenshot](screenshots/flange-xy.png))
|
|
||||||
[](flange-xy/main.kcl)
|
|
||||||
#### [focusrite-scarlett-mounting-bracket](focusrite-scarlett-mounting-bracket/main.kcl) ([step](step/focusrite-scarlett-mounting-bracket.step)) ([screenshot](screenshots/focusrite-scarlett-mounting-bracket.png))
|
#### [focusrite-scarlett-mounting-bracket](focusrite-scarlett-mounting-bracket/main.kcl) ([step](step/focusrite-scarlett-mounting-bracket.step)) ([screenshot](screenshots/focusrite-scarlett-mounting-bracket.png))
|
||||||
[](focusrite-scarlett-mounting-bracket/main.kcl)
|
[](focusrite-scarlett-mounting-bracket/main.kcl)
|
||||||
#### [food-service-spatula](food-service-spatula/main.kcl) ([step](step/food-service-spatula.step)) ([screenshot](screenshots/food-service-spatula.png))
|
#### [food-service-spatula](food-service-spatula/main.kcl) ([step](step/food-service-spatula.step)) ([screenshot](screenshots/food-service-spatula.png))
|
||||||
|
@ -15,90 +15,57 @@ padding = 1.5
|
|||||||
bearingDia = 3
|
bearingDia = 3
|
||||||
|
|
||||||
// (Needs to be updated). Sketch the block and extrude up to where the counterbore diameter starts.
|
// (Needs to be updated). Sketch the block and extrude up to where the counterbore diameter starts.
|
||||||
block = startSketchOn('XY')
|
extrude001 = startSketchOn('XY')
|
||||||
|> startProfileAt([-width / 2, -length / 2], %)
|
|> startProfileAt([-width / 2, -length / 2], %)
|
||||||
|> line(endAbsolute = [width / 2, -length / 2])
|
|> line(endAbsolute = [width / 2, -length / 2])
|
||||||
|> line(endAbsolute = [width / 2, length / 2])
|
|> line(endAbsolute = [width / 2, length / 2])
|
||||||
|> line(endAbsolute = [-width / 2, length / 2])
|
|> line(endAbsolute = [-width / 2, length / 2])
|
||||||
|> close()
|
|> close()
|
||||||
|> hole(circle(
|
|> extrude(length = height)
|
||||||
center = [
|
|
||||||
-(width / 2 - (padding / 2)),
|
|
||||||
-(length / 2 - (padding / 2))
|
|
||||||
],
|
|
||||||
radius = holeDia / 2
|
|
||||||
), %)
|
|
||||||
|> hole(circle(
|
|
||||||
center = [
|
|
||||||
-(width / 2 - (padding / 2)),
|
|
||||||
length / 2 - (padding / 2)
|
|
||||||
],
|
|
||||||
radius = holeDia / 2
|
|
||||||
), %)
|
|
||||||
|> hole(circle(
|
|
||||||
center = [
|
|
||||||
width / 2 - (padding / 2),
|
|
||||||
length / 2 - (padding / 2)
|
|
||||||
],
|
|
||||||
radius = holeDia / 2
|
|
||||||
), %)
|
|
||||||
|> hole(circle(
|
|
||||||
center = [
|
|
||||||
width / 2 - (padding / 2),
|
|
||||||
-(length / 2 - (padding / 2))
|
|
||||||
],
|
|
||||||
radius = holeDia / 2
|
|
||||||
), %)
|
|
||||||
|> hole(circle(
|
|
||||||
center = [0, 0],
|
|
||||||
radius = bearingDia / 2
|
|
||||||
), %)
|
|
||||||
|> extrude(length = height - cbDepth)
|
|
||||||
|
|
||||||
// Create a second sketch that creates the counterbore diameters and extrude the rest of the way to get the total height. Note: You cannot use startSketchOn(block, 'end'). The extrude lives outside the bounds, and the engine will not execute. This is a known issue.
|
extrude002 = startSketchOn(extrude001, 'end')
|
||||||
secondHalf = startSketchOn({
|
|> circle(
|
||||||
plane = {
|
|
||||||
origin = { x = 0, y = 0, z = height - cbDepth },
|
|
||||||
xAxis = { x = 1, y = 0, z = 0 },
|
|
||||||
yAxis = { x = 0, y = 1, z = 0 },
|
|
||||||
zAxis = { x = 0, y = 0, z = 1 }
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|> startProfileAt([-width / 2, -length / 2], %)
|
|
||||||
|> line(endAbsolute = [width / 2, -length / 2])
|
|
||||||
|> line(endAbsolute = [width / 2, length / 2])
|
|
||||||
|> line(endAbsolute = [-width / 2, length / 2])
|
|
||||||
|> close()
|
|
||||||
|> hole(circle(
|
|
||||||
center = [
|
center = [
|
||||||
-(width / 2 - (padding / 2)),
|
-(width / 2 - (padding / 2)),
|
||||||
-(length / 2 - (padding / 2))
|
-(length / 2 - (padding / 2))
|
||||||
],
|
],
|
||||||
radius = cbDia / 2
|
radius = cbDia / 2,
|
||||||
), %)
|
)
|
||||||
|> hole(circle(
|
|> patternLinear2d(
|
||||||
|
instances = 2,
|
||||||
|
distance = length - padding,
|
||||||
|
axis = [0, 1],
|
||||||
|
)
|
||||||
|
|> patternLinear2d(
|
||||||
|
instances = 2,
|
||||||
|
distance = width - padding,
|
||||||
|
axis = [1, 0],
|
||||||
|
)
|
||||||
|
|> extrude(%, length = -cbDepth)
|
||||||
|
|
||||||
|
extrude003 = startSketchOn(extrude001, 'start')
|
||||||
|
|> circle(
|
||||||
center = [
|
center = [
|
||||||
-(width / 2 - (padding / 2)),
|
-(width / 2 - (padding / 2)),
|
||||||
length / 2 - (padding / 2)
|
|
||||||
],
|
|
||||||
radius = cbDia / 2
|
|
||||||
), %)
|
|
||||||
|> hole(circle(
|
|
||||||
center = [
|
|
||||||
width / 2 - (padding / 2),
|
|
||||||
length / 2 - (padding / 2)
|
|
||||||
],
|
|
||||||
radius = cbDia / 2
|
|
||||||
), %)
|
|
||||||
|> hole(circle(
|
|
||||||
center = [
|
|
||||||
width / 2 - (padding / 2),
|
|
||||||
-(length / 2 - (padding / 2))
|
-(length / 2 - (padding / 2))
|
||||||
],
|
],
|
||||||
radius = cbDia / 2
|
radius = holeDia / 2,
|
||||||
), %)
|
)
|
||||||
|> hole(circle(
|
|> patternLinear2d(
|
||||||
|
instances = 2,
|
||||||
|
distance = length - padding,
|
||||||
|
axis = [0, 1],
|
||||||
|
)
|
||||||
|
|> patternLinear2d(
|
||||||
|
instances = 2,
|
||||||
|
distance = width - padding,
|
||||||
|
axis = [1, 0],
|
||||||
|
)
|
||||||
|
|> extrude(length = -height + cbDepth)
|
||||||
|
|
||||||
|
extrude004 = startSketchOn(extrude001, 'end')
|
||||||
|
|> circle(
|
||||||
center = [0, 0],
|
center = [0, 0],
|
||||||
radius = bearingDia / 2
|
radius = bearingDia/2,
|
||||||
), %)
|
)
|
||||||
|> extrude(length = cbDepth)
|
|> extrude(length = -height)
|
@ -16,21 +16,8 @@ chainWidth = sphereDia / 2
|
|||||||
chainThickness = sphereDia / 8
|
chainThickness = sphereDia / 8
|
||||||
linkDiameter = sphereDia / 4
|
linkDiameter = sphereDia / 4
|
||||||
|
|
||||||
customPlane = {
|
|
||||||
plane = {
|
|
||||||
origin = {
|
|
||||||
x = 0,
|
|
||||||
y = 0,
|
|
||||||
z = -overallThickness / 2
|
|
||||||
},
|
|
||||||
xAxis = { x = 1, y = 0, z = 0 },
|
|
||||||
yAxis = { x = 0, y = 1, z = 0 },
|
|
||||||
zAxis = { x = 0, y = 0, z = 1 }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sketch the inside bearing piece
|
// Sketch the inside bearing piece
|
||||||
insideWallSketch = startSketchOn(customPlane)
|
insideWallSketch = startSketchOn(offsetPlane("XY", offset = -overallThickness / 2))
|
||||||
|> circle(
|
|> circle(
|
||||||
center = [0, 0],
|
center = [0, 0],
|
||||||
radius = shaftDia / 2 + wallThickness
|
radius = shaftDia / 2 + wallThickness
|
||||||
@ -109,7 +96,7 @@ linkRevolve = revolve({ axis = 'Y', angle = 360 / nBalls }, linkSketch)
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Create the sketch for the outside walls
|
// Create the sketch for the outside walls
|
||||||
outsideWallSketch = startSketchOn(customPlane)
|
outsideWallSketch = startSketchOn(offsetPlane("XY", offset = -overallThickness / 2))
|
||||||
|> circle(
|
|> circle(
|
||||||
center = [0, 0],
|
center = [0, 0],
|
||||||
radius = outsideDiameter / 2
|
radius = outsideDiameter / 2
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
// 3D Boaty
|
// Bench
|
||||||
// This is a slight remix of Depep1's original 3D Boaty (https://www.printables.com/model/1141963-3d-boaty). This is a tool used for benchmarking 3D FDM printers for bed adhesion, overhangs, bridging and top surface quality. The name of this file is a bit of misnomer, the shape of the object is a typical park bench.
|
// This is a slight remix of Depep1's original 3D Boaty (https://www.printables.com/model/1141963-3d-boaty). This is a tool used for benchmarking 3D FDM printers for bed adhesion, overhangs, bridging and top surface quality. The name of this file is a bit of misnomer, the shape of the object is a typical park bench.
|
||||||
|
|
||||||
// Set units in millimeters (mm)
|
// Set units in millimeters (mm)
|
||||||
@ -8,12 +8,12 @@
|
|||||||
benchLength = 56
|
benchLength = 56
|
||||||
|
|
||||||
// Import various constants and functions from our library
|
// Import various constants and functions from our library
|
||||||
import dividerThickness from "boat-parts.kcl"
|
import dividerThickness from "bench-parts.kcl"
|
||||||
import divider from "boat-parts.kcl"
|
import divider from "bench-parts.kcl"
|
||||||
import connector from "boat-parts.kcl"
|
import connector from "bench-parts.kcl"
|
||||||
import seatSlats from "boat-parts.kcl"
|
import seatSlats from "bench-parts.kcl"
|
||||||
import backSlats from "boat-parts.kcl"
|
import backSlats from "bench-parts.kcl"
|
||||||
import armRest from "boat-parts.kcl"
|
import armRest from "bench-parts.kcl"
|
||||||
|
|
||||||
// Create the dividers, these hold the seat and back slats
|
// Create the dividers, these hold the seat and back slats
|
||||||
divider("YZ")
|
divider("YZ")
|
@ -1,113 +1,76 @@
|
|||||||
// Shelf Bracket
|
// Shelf Bracket
|
||||||
// This is a bracket that holds a shelf. It is made of aluminum and is designed to hold a force of 300 lbs. The bracket is 6 inches wide and the force is applied at the end of the shelf, 12 inches from the wall. The bracket has a factor of safety of 1.2. The legs of the bracket are 5 inches and 2 inches long. The thickness of the bracket is calculated from the constraints provided.
|
// This is a bracket that holds a shelf. It is made of aluminum and is designed to hold a force of 300 lbs. The bracket is 6 inches wide and the force is applied at the end of the shelf, 12 inches from the wall. The bracket has a factor of safety of 1.2. The legs of the bracket are 5 inches and 2 inches long. The thickness of the bracket is calculated from the constraints provided.
|
||||||
|
|
||||||
// Set units
|
|
||||||
@settings(defaultLengthUnit = in)
|
|
||||||
|
|
||||||
// Define constants
|
// Define constants
|
||||||
sigmaAllow = 35000 // psi (6061-T6 aluminum)
|
sigmaAllow = 35000 // psi (6061-T6 aluminum)
|
||||||
width = 6
|
width = 6 // inch
|
||||||
p = 300 // Force on shelf - lbs
|
p = 300 // Force on shelf - lbs
|
||||||
factorOfSafety = 1.2 // FOS of 1.2
|
factorOfSafety = 1.2 // FOS of 1.2
|
||||||
shelfMountL = 5
|
shelfMountL = 5 // inches
|
||||||
wallMountL = 2
|
wallMountL = 2 // inches
|
||||||
shelfDepth = 12 // Shelf is 12 inches in depth from the wall
|
shelfDepth = 12 // Shelf is 12 inches in depth from the wall
|
||||||
moment = shelfDepth * p // assume the force is applied at the end of the shelf to be conservative (lb-in)
|
moment = shelfDepth * p // assume the force is applied at the end of the shelf to be conservative (lb-in)
|
||||||
|
|
||||||
|
|
||||||
filletRadius = .375
|
|
||||||
extFilletRadius = .25
|
|
||||||
mountingHoleDiameter = 0.5
|
|
||||||
|
|
||||||
// Calculate required thickness of bracket
|
// Calculate required thickness of bracket
|
||||||
thickness = sqrt(moment * factorOfSafety * 6 / (sigmaAllow * width)) // this is the calculation of two brackets holding up the shelf (inches)
|
thickness = sqrt(moment * factorOfSafety * 6 / (sigmaAllow * width)) // this is the calculation of two brackets holding up the shelf (inches)
|
||||||
|
|
||||||
|
filletRadius = .25
|
||||||
|
extFilletRadius = filletRadius + thickness
|
||||||
|
mountingHoleDiameter = 0.5
|
||||||
|
|
||||||
// Sketch the bracket body and fillet the inner and outer edges of the bend
|
sketch001 = startSketchOn('XZ')
|
||||||
bracketLeg1Sketch = startSketchOn('XY')
|
|
||||||
|> startProfileAt([0, 0], %)
|
|> startProfileAt([0, 0], %)
|
||||||
|> line(end = [shelfMountL - filletRadius, 0], tag = $fillet1)
|
|> xLine(length = shelfMountL - thickness, tag = $seg01)
|
||||||
|> line(end = [0, width], tag = $fillet2)
|
|> yLine(length = thickness, tag = $seg02)
|
||||||
|> line(end = [-shelfMountL + filletRadius, 0])
|
|> xLine(length = -shelfMountL, tag = $seg03)
|
||||||
|
|> yLine(length = -wallMountL, tag = $seg04)
|
||||||
|
|> xLine(length = thickness, tag = $seg05)
|
||||||
|
|> line(endAbsolute = [profileStartX(%), profileStartY(%)], tag = $seg06)
|
||||||
|> close()
|
|> close()
|
||||||
|> hole(circle(
|
|> extrude(%, length = width)
|
||||||
center = [1, 1],
|
|
||||||
radius = mountingHoleDiameter / 2
|
|
||||||
), %)
|
|
||||||
|> hole(circle(
|
|
||||||
center = [shelfMountL - 1.5, width - 1],
|
|
||||||
radius = mountingHoleDiameter / 2
|
|
||||||
), %)
|
|
||||||
|> hole(circle(
|
|
||||||
center = [1, width - 1],
|
|
||||||
radius = mountingHoleDiameter / 2
|
|
||||||
), %)
|
|
||||||
|> hole(circle(
|
|
||||||
center = [shelfMountL - 1.5, 1],
|
|
||||||
radius = mountingHoleDiameter / 2
|
|
||||||
), %)
|
|
||||||
|
|
||||||
// Extrude the leg 2 bracket sketch
|
|
||||||
bracketLeg1Extrude = extrude(bracketLeg1Sketch, length = thickness)
|
|
||||||
|> fillet(
|
|> fillet(
|
||||||
radius = extFilletRadius,
|
radius = extFilletRadius,
|
||||||
tags = [
|
tags = [getNextAdjacentEdge(seg03)],
|
||||||
getNextAdjacentEdge(fillet1),
|
|
||||||
getNextAdjacentEdge(fillet2)
|
|
||||||
]
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Sketch the fillet arc
|
|
||||||
filletSketch = startSketchOn('XZ')
|
|
||||||
|> startProfileAt([0, 0], %)
|
|
||||||
|> line(end = [0, thickness])
|
|
||||||
|> arc({
|
|
||||||
angleEnd = 180,
|
|
||||||
angleStart = 90,
|
|
||||||
radius = filletRadius + thickness
|
|
||||||
}, %)
|
|
||||||
|> line(end = [thickness, 0])
|
|
||||||
|> arc({
|
|
||||||
angleEnd = 90,
|
|
||||||
angleStart = 180,
|
|
||||||
radius = filletRadius
|
|
||||||
}, %)
|
|
||||||
|
|
||||||
// Sketch the bend
|
|
||||||
filletExtrude = extrude(filletSketch, length = -width)
|
|
||||||
|
|
||||||
// Create a custom plane for the leg that sits on the wall
|
|
||||||
customPlane = {
|
|
||||||
plane = {
|
|
||||||
origin = { x = -filletRadius, y = 0, z = 0 },
|
|
||||||
xAxis = { x = 0, y = 1, z = 0 },
|
|
||||||
yAxis = { x = 0, y = 0, z = 1 },
|
|
||||||
zAxis = { x = 1, y = 0, z = 0 }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a sketch for the second leg
|
|
||||||
bracketLeg2Sketch = startSketchOn(customPlane)
|
|
||||||
|> startProfileAt([0, -filletRadius], %)
|
|
||||||
|> line(end = [width, 0])
|
|
||||||
|> line(end = [0, -wallMountL], tag = $fillet3)
|
|
||||||
|> line(end = [-width, 0], tag = $fillet4)
|
|
||||||
|> close()
|
|
||||||
|> hole(circle(
|
|
||||||
center = [1, -1.5],
|
|
||||||
radius = mountingHoleDiameter / 2
|
|
||||||
), %)
|
|
||||||
|> hole(circle(
|
|
||||||
center = [5, -1.5],
|
|
||||||
radius = mountingHoleDiameter / 2
|
|
||||||
), %)
|
|
||||||
|
|
||||||
// Extrude the second leg
|
|
||||||
bracketLeg2Extrude = extrude(bracketLeg2Sketch, length = -thickness)
|
|
||||||
|> fillet(
|
|> fillet(
|
||||||
radius = extFilletRadius,
|
radius = filletRadius,
|
||||||
tags = [
|
tags = [getNextAdjacentEdge(seg06)],
|
||||||
getNextAdjacentEdge(fillet3),
|
|
||||||
getNextAdjacentEdge(fillet4)
|
|
||||||
]
|
|
||||||
)
|
)
|
||||||
|
|> fillet(
|
||||||
|
radius = filletRadius,
|
||||||
|
tags = [seg02, getOppositeEdge(seg02)],
|
||||||
|
)
|
||||||
|
|> fillet(
|
||||||
|
radius = filletRadius,
|
||||||
|
tags = [seg05, getOppositeEdge(seg05)],
|
||||||
|
)
|
||||||
|
|
||||||
|
sketch002 = startSketchOn(sketch001, seg03)
|
||||||
|
|> circle(
|
||||||
|
center = [-1.25, 1],
|
||||||
|
radius = mountingHoleDiameter / 2,
|
||||||
|
)
|
||||||
|
|> patternLinear2d(
|
||||||
|
instances = 2,
|
||||||
|
distance = 2.5,
|
||||||
|
axis = [-1, 0],
|
||||||
|
)
|
||||||
|
|> patternLinear2d(
|
||||||
|
instances = 2,
|
||||||
|
distance = 4,
|
||||||
|
axis = [0, 1],
|
||||||
|
)
|
||||||
|
|> extrude(%, length = -thickness-.01)
|
||||||
|
|
||||||
|
sketch003 = startSketchOn(sketch001, seg04)
|
||||||
|
|> circle(
|
||||||
|
center = [1, -1],
|
||||||
|
radius = mountingHoleDiameter / 2,
|
||||||
|
)
|
||||||
|
|> patternLinear2d(
|
||||||
|
instances = 2,
|
||||||
|
distance = 4,
|
||||||
|
axis = [1, 0],
|
||||||
|
)
|
||||||
|
|> extrude(%, length = -thickness-0.1)
|
||||||
|
@ -9,18 +9,8 @@
|
|||||||
// Import Constants
|
// Import Constants
|
||||||
import caliperTolerance, caliperPadLength, caliperThickness, caliperOuterEdgeRadius, caliperInnerEdgeRadius, rotorDiameter, rotorTotalThickness, yAxisOffset from "globals.kcl"
|
import caliperTolerance, caliperPadLength, caliperThickness, caliperOuterEdgeRadius, caliperInnerEdgeRadius, rotorDiameter, rotorTotalThickness, yAxisOffset from "globals.kcl"
|
||||||
|
|
||||||
// Create the plane for the brake caliper. This is so it can match up with the rotor model.
|
|
||||||
brakeCaliperPlane = {
|
|
||||||
plane = {
|
|
||||||
origin = { x = 0, y = yAxisOffset, z = 0 },
|
|
||||||
xAxis = { x = 1, y = 0, z = 0 },
|
|
||||||
yAxis = { x = 0, y = 1, z = 0 },
|
|
||||||
zAxis = { x = 0, y = 0, z = 1 }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sketch the brake caliper profile
|
// Sketch the brake caliper profile
|
||||||
brakeCaliperSketch = startSketchOn(brakeCaliperPlane)
|
brakeCaliperSketch = startSketchOn('XY')
|
||||||
|> startProfileAt([
|
|> startProfileAt([
|
||||||
rotorDiameter / 2 + caliperTolerance,
|
rotorDiameter / 2 + caliperTolerance,
|
||||||
0
|
0
|
||||||
|
@ -9,17 +9,23 @@
|
|||||||
// Import Constants
|
// Import Constants
|
||||||
import rotorDiameter, rotorInnerDiameter, rotorSinglePlateThickness, rotorInnerDiameterThickness, lugHolePatternDia, lugSpacing, rotorTotalThickness, spacerPatternDiameter, spacerDiameter, spacerLength, spacerCount, wheelDiameter, lugCount, yAxisOffset, drillAndSlotCount from "globals.kcl"
|
import rotorDiameter, rotorInnerDiameter, rotorSinglePlateThickness, rotorInnerDiameterThickness, lugHolePatternDia, lugSpacing, rotorTotalThickness, spacerPatternDiameter, spacerDiameter, spacerLength, spacerCount, wheelDiameter, lugCount, yAxisOffset, drillAndSlotCount from "globals.kcl"
|
||||||
|
|
||||||
rotorPlane = {
|
rotorSketch = startSketchOn('XZ')
|
||||||
plane = {
|
|> circle(
|
||||||
origin = { x = 0, y = yAxisOffset, z = 0 },
|
center = [0, 0],
|
||||||
xAxis = { x = -1, y = 0, z = 0 },
|
radius = rotorDiameter / 2
|
||||||
yAxis = { x = 0, y = 0, z = 1 },
|
)
|
||||||
zAxis = { x = 0, y = 1, z = 0 }
|
rotor = extrude(rotorSketch, length = rotorSinglePlateThickness)
|
||||||
}
|
|> appearance(color = "#dbcd70", roughness = 90, metalness = 90)
|
||||||
}
|
|
||||||
fn lugPattern(plane) {
|
rotorBumpSketch = startSketchOn(rotor, 'end')
|
||||||
lugHolePattern = circle(
|
|> circle(
|
||||||
plane,
|
center = [0, 0],
|
||||||
|
radius = rotorInnerDiameter / 2
|
||||||
|
)
|
||||||
|
rotorBump = extrude(rotorBumpSketch, length = rotorInnerDiameterThickness)
|
||||||
|
|
||||||
|
lugHoles = startSketchOn(rotorBump, 'end')
|
||||||
|
|> circle(
|
||||||
center = [-lugSpacing / 2, 0],
|
center = [-lugSpacing / 2, 0],
|
||||||
radius = 0.315
|
radius = 0.315
|
||||||
)
|
)
|
||||||
@ -29,44 +35,35 @@ fn lugPattern(plane) {
|
|||||||
instances = lugCount,
|
instances = lugCount,
|
||||||
rotateDuplicates = true
|
rotateDuplicates = true
|
||||||
)
|
)
|
||||||
return lugHolePattern
|
|> extrude(%, length = -(rotorInnerDiameterThickness + rotorSinglePlateThickness))
|
||||||
}
|
|> appearance(color = "#dbcd70", roughness = 90, metalness = 90)
|
||||||
rotorSketch = startSketchOn(rotorPlane)
|
|
||||||
|
// (update when boolean is available)
|
||||||
|
centerSpacer = startSketchOn(rotor, 'start')
|
||||||
|
|> circle(%, center = [0, 0], radius = .25)
|
||||||
|
|> extrude(%, length = spacerLength)
|
||||||
|
|
||||||
|
secondaryRotorSketch = startSketchOn(centerSpacer, 'end')
|
||||||
|> circle(
|
|> circle(
|
||||||
center = [0, 0],
|
center = [0, 0],
|
||||||
radius = rotorDiameter / 2
|
radius = rotorDiameter / 2
|
||||||
)
|
)
|
||||||
|> hole(lugPattern(%), %)
|
|
||||||
rotor = extrude(rotorSketch, length = rotorSinglePlateThickness)
|
|
||||||
|> appearance(color = "#dbcd70", roughness = 90, metalness = 90)
|
|
||||||
rotorBumpSketch = startSketchOn(rotorPlane)
|
|
||||||
|> circle(
|
|
||||||
center = [0, 0],
|
|
||||||
radius = rotorInnerDiameter / 2
|
|
||||||
)
|
|
||||||
|> hole(lugPattern(%), %)
|
|
||||||
rotorBump = extrude(rotorBumpSketch, length = -rotorInnerDiameterThickness)
|
|
||||||
|> appearance(color = "#dbcd70", roughness = 90, metalness = 90)
|
|
||||||
rotorSecondaryPlatePlane = {
|
|
||||||
plane = {
|
|
||||||
origin = {
|
|
||||||
x = 0,
|
|
||||||
y = yAxisOffset + rotorTotalThickness * 0.75,
|
|
||||||
z = 0
|
|
||||||
},
|
|
||||||
xAxis = { x = -1, y = 0, z = 0 },
|
|
||||||
yAxis = { x = 0, y = 0, z = 1 },
|
|
||||||
zAxis = { x = 0, y = 1, z = 0 }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
secondaryRotorSketch = startSketchOn(rotorSecondaryPlatePlane)
|
|
||||||
|> circle(
|
|
||||||
center = [0, 0],
|
|
||||||
radius = rotorDiameter / 2
|
|
||||||
)
|
|
||||||
|> hole(lugPattern(%), %)
|
|
||||||
secondRotor = extrude(secondaryRotorSketch, length = rotorSinglePlateThickness)
|
secondRotor = extrude(secondaryRotorSketch, length = rotorSinglePlateThickness)
|
||||||
spacerSketch = startSketchOn(rotorSecondaryPlatePlane)
|
|
||||||
|
lugHoles2 = startSketchOn(secondRotor, 'end')
|
||||||
|
|> circle(
|
||||||
|
center = [-lugSpacing / 2, 0],
|
||||||
|
radius = 0.315
|
||||||
|
)
|
||||||
|
|> patternCircular2d(
|
||||||
|
arcDegrees = 360,
|
||||||
|
center = [0, 0],
|
||||||
|
instances = lugCount,
|
||||||
|
rotateDuplicates = true
|
||||||
|
)
|
||||||
|
|> extrude(length = -rotorSinglePlateThickness)
|
||||||
|
|
||||||
|
spacerSketch = startSketchOn(rotor, 'start')
|
||||||
|> circle(
|
|> circle(
|
||||||
center = [spacerPatternDiameter / 2, 0],
|
center = [spacerPatternDiameter / 2, 0],
|
||||||
radius = spacerDiameter
|
radius = spacerDiameter
|
||||||
@ -77,8 +74,8 @@ spacerSketch = startSketchOn(rotorSecondaryPlatePlane)
|
|||||||
instances = spacerCount,
|
instances = spacerCount,
|
||||||
rotateDuplicates = true
|
rotateDuplicates = true
|
||||||
)
|
)
|
||||||
spacers = extrude(spacerSketch, length = -spacerLength)
|
spacers = extrude(spacerSketch, length = spacerLength)
|
||||||
|> appearance(color = "#dbcd70", roughness = 90, metalness = 90)
|
|
||||||
rotorSlottedSketch = startSketchOn(rotor, 'START')
|
rotorSlottedSketch = startSketchOn(rotor, 'START')
|
||||||
|> startProfileAt([2.17, 2.56], %)
|
|> startProfileAt([2.17, 2.56], %)
|
||||||
|> xLine(length = 0.12)
|
|> xLine(length = 0.12)
|
||||||
@ -107,5 +104,6 @@ secondRotorSlottedSketch = startSketchOn(secondRotor, 'END')
|
|||||||
arcDegrees = 360,
|
arcDegrees = 360,
|
||||||
rotateDuplicates = true
|
rotateDuplicates = true
|
||||||
)
|
)
|
||||||
secondRotorSlotted = extrude(secondRotorSlottedSketch, length = -rotorSinglePlateThickness / 2)
|
|
||||||
|
extrude(secondRotorSlottedSketch, length = -rotorSinglePlateThickness / 2)
|
||||||
|> appearance(color = "#dbcd70", roughness = 90, metalness = 90)
|
|> appearance(color = "#dbcd70", roughness = 90, metalness = 90)
|
||||||
|
@ -12,6 +12,7 @@ import 'car-tire.kcl' as carTire
|
|||||||
import lugCount from 'globals.kcl'
|
import lugCount from 'globals.kcl'
|
||||||
|
|
||||||
carRotor
|
carRotor
|
||||||
|
|> translate(translate = [0, 0.5, 0])
|
||||||
carWheel
|
carWheel
|
||||||
lugNut
|
lugNut
|
||||||
|> patternCircular3d(
|
|> patternCircular3d(
|
||||||
@ -22,4 +23,5 @@ lugNut
|
|||||||
rotateDuplicates = false
|
rotateDuplicates = false
|
||||||
)
|
)
|
||||||
brakeCaliper
|
brakeCaliper
|
||||||
|
|> translate(translate = [0, 0.5, 0])
|
||||||
carTire
|
carTire
|
||||||
|
@ -1,87 +0,0 @@
|
|||||||
// Flange with XY coordinates
|
|
||||||
// A flange is a flat rim, collar, or rib, typically forged or cast, that is used to strengthen an object, guide it, or attach it to another object. Flanges are known for their use in various applications, including piping, plumbing, and mechanical engineering, among others.
|
|
||||||
|
|
||||||
// Set units
|
|
||||||
@settings(defaultLengthUnit = in)
|
|
||||||
|
|
||||||
// Define constants
|
|
||||||
mountingHoleDia = .625
|
|
||||||
baseDia = 4.625
|
|
||||||
pipeDia = 1.25
|
|
||||||
thickness = .625
|
|
||||||
totalThickness = 0.813
|
|
||||||
topTotalDiameter = 2.313
|
|
||||||
bottomThickness = 0.06
|
|
||||||
bottomTotalDiameter = 2.5
|
|
||||||
mountingHolePlacementDiameter = 3.5
|
|
||||||
baseThickness = .625
|
|
||||||
topTotalThickness = totalThickness - (bottomThickness + baseThickness)
|
|
||||||
holeLocator = baseDia - 8
|
|
||||||
nHoles = 4
|
|
||||||
|
|
||||||
// Add assertion so nHoles are always greater than 1
|
|
||||||
assertGreaterThan(nHoles, 1, "nHoles must be greater than 1")
|
|
||||||
|
|
||||||
// Create the flange base and the six mounting holes
|
|
||||||
flangeBase = startSketchOn('XY')
|
|
||||||
|> circle(
|
|
||||||
center = [0, 0],
|
|
||||||
radius = baseDia / 2
|
|
||||||
)
|
|
||||||
|> hole(circle(
|
|
||||||
center = [mountingHolePlacementDiameter / 2, 0],
|
|
||||||
radius = mountingHoleDia / 2
|
|
||||||
), %)
|
|
||||||
|> hole(circle(
|
|
||||||
center = [0, mountingHolePlacementDiameter / 2],
|
|
||||||
radius = mountingHoleDia / 2
|
|
||||||
), %)
|
|
||||||
|> hole(circle(
|
|
||||||
center = [-mountingHolePlacementDiameter / 2, 0],
|
|
||||||
radius = mountingHoleDia / 2
|
|
||||||
), %)
|
|
||||||
|> hole(circle(
|
|
||||||
center = [0, -mountingHolePlacementDiameter / 2],
|
|
||||||
radius = mountingHoleDia / 2
|
|
||||||
), %)
|
|
||||||
|> hole(circle(
|
|
||||||
center = [0, 0],
|
|
||||||
radius = pipeDia / 2
|
|
||||||
), %)
|
|
||||||
|> extrude(length = baseThickness)
|
|
||||||
|
|
||||||
// Plane for top face
|
|
||||||
topFacePlane = {
|
|
||||||
plane = {
|
|
||||||
origin = { x = 0, y = 0, z = baseThickness },
|
|
||||||
xAxis = { x = 1, y = 0, z = 0 },
|
|
||||||
yAxis = { x = 0, y = 1, z = 0 },
|
|
||||||
zAxis = { x = 0, y = 0, z = 1 }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create the extrusion on the top of the flange base
|
|
||||||
topExtrusion = startSketchOn(topFacePlane, 'end')
|
|
||||||
|> circle(
|
|
||||||
center = [0, 0],
|
|
||||||
radius = topTotalDiameter / 2
|
|
||||||
)
|
|
||||||
|> hole(circle(
|
|
||||||
center = [0, 0],
|
|
||||||
radius = pipeDia / 2
|
|
||||||
), %)
|
|
||||||
|> extrude(length = topTotalThickness)
|
|
||||||
|
|
||||||
// Create the extrusion on the bottom of the flange base
|
|
||||||
bottomExtrusion = startSketchOn("XY")
|
|
||||||
|> circle(
|
|
||||||
center = [0, 0],
|
|
||||||
radius = bottomTotalDiameter / 2
|
|
||||||
)
|
|
||||||
|> hole(circle(
|
|
||||||
center = [0, 0],
|
|
||||||
radius = pipeDia / 2
|
|
||||||
), %)
|
|
||||||
|> extrude(length = -bottomThickness)
|
|
||||||
|
|
||||||
// https://www.mcmaster.com/44685K193/
|
|
@ -8,7 +8,6 @@
|
|||||||
mountingHoleDia = .625
|
mountingHoleDia = .625
|
||||||
baseDia = 4.625
|
baseDia = 4.625
|
||||||
pipeDia = 1.25
|
pipeDia = 1.25
|
||||||
thickness = .625
|
|
||||||
totalThickness = 0.813
|
totalThickness = 0.813
|
||||||
topTotalDiameter = 2.313
|
topTotalDiameter = 2.313
|
||||||
bottomThickness = 0.06
|
bottomThickness = 0.06
|
||||||
@ -16,7 +15,6 @@ bottomTotalDiameter = 2.5
|
|||||||
mountingHolePlacementDiameter = 3.5
|
mountingHolePlacementDiameter = 3.5
|
||||||
baseThickness = .625
|
baseThickness = .625
|
||||||
topTotalThickness = totalThickness - (bottomThickness + baseThickness)
|
topTotalThickness = totalThickness - (bottomThickness + baseThickness)
|
||||||
holeLocator = baseDia - 8
|
|
||||||
nHoles = 4
|
nHoles = 4
|
||||||
|
|
||||||
// Add assertion so nHoles are always greater than 1
|
// Add assertion so nHoles are always greater than 1
|
||||||
@ -42,42 +40,25 @@ flangeBase = startSketchOn('XY')
|
|||||||
radius = baseDia / 2
|
radius = baseDia / 2
|
||||||
)
|
)
|
||||||
|> hole(circles, %)
|
|> hole(circles, %)
|
||||||
|> hole(circle(
|
|
||||||
center = [0, 0],
|
|
||||||
radius = pipeDia / 2
|
|
||||||
), %)
|
|
||||||
|> extrude(length = baseThickness)
|
|> extrude(length = baseThickness)
|
||||||
|
|
||||||
// Plane for top face
|
|
||||||
topFacePlane = {
|
|
||||||
plane = {
|
|
||||||
origin = { x = 0, y = 0, z = baseThickness },
|
|
||||||
xAxis = { x = 1, y = 0, z = 0 },
|
|
||||||
yAxis = { x = 0, y = 1, z = 0 },
|
|
||||||
zAxis = { x = 0, y = 0, z = 1 }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create the extrusion on the top of the flange base
|
// Create the extrusion on the top of the flange base
|
||||||
topExtrusion = startSketchOn(topFacePlane)
|
topExtrusion = startSketchOn(flangeBase, 'end')
|
||||||
|> circle(
|
|> circle(
|
||||||
center = [0, 0],
|
center = [0, 0],
|
||||||
radius = topTotalDiameter / 2
|
radius = topTotalDiameter / 2
|
||||||
)
|
)
|
||||||
|> hole(circle(
|
|
||||||
center = [0, 0],
|
|
||||||
radius = pipeDia / 2
|
|
||||||
), %)
|
|
||||||
|> extrude(length = topTotalThickness)
|
|> extrude(length = topTotalThickness)
|
||||||
|
|
||||||
// Create the extrusion on the bottom of the flange base
|
// Create the extrusion on the bottom of the flange base
|
||||||
bottomExtrusion = startSketchOn("XY")
|
bottomExtrusion = startSketchOn(flangeBase, 'start')
|
||||||
|> circle(
|
|> circle(
|
||||||
center = [0, 0],
|
center = [0, 0],
|
||||||
radius = bottomTotalDiameter / 2
|
radius = bottomTotalDiameter / 2
|
||||||
)
|
)
|
||||||
|> hole(circle(
|
|> extrude(length = bottomThickness)
|
||||||
center = [0, 0],
|
|
||||||
radius = pipeDia / 2
|
// Cut a hole through the entire body
|
||||||
), %)
|
pipeHole = startSketchOn(topExtrusion, 'end')
|
||||||
|> extrude(length = -bottomThickness)
|
|> circle(center = [0, 0], radius = pipeDia/2)
|
||||||
|
|> extrude(%, length = -(topTotalThickness + baseThickness + bottomThickness))
|
@ -5,8 +5,8 @@
|
|||||||
@settings(defaultLengthUnit = in)
|
@settings(defaultLengthUnit = in)
|
||||||
|
|
||||||
// Define constants
|
// Define constants
|
||||||
lbumps = 5 // number of bumps long
|
lbumps = 10 // number of bumps long
|
||||||
wbumps = 3 // number of bumps wide
|
wbumps = 5 // number of bumps wide
|
||||||
pitch = 8.0
|
pitch = 8.0
|
||||||
clearance = 0.1
|
clearance = 0.1
|
||||||
bumpDiam = 4.8
|
bumpDiam = 4.8
|
||||||
@ -25,28 +25,8 @@ wSegments = totalWidth / wbumps
|
|||||||
assertGreaterThan(lbumps, 1, "lbumps must be greater than 1")
|
assertGreaterThan(lbumps, 1, "lbumps must be greater than 1")
|
||||||
assertGreaterThan(wbumps, 1, "wbumps must be greater than 1")
|
assertGreaterThan(wbumps, 1, "wbumps must be greater than 1")
|
||||||
|
|
||||||
// Create the plane for the pegs. This is a hack so that the pegs can be patterned along the face of the lego base.
|
|
||||||
pegFace = {
|
|
||||||
plane = {
|
|
||||||
origin = { x = 0, y = 0, z = height },
|
|
||||||
xAxis = { x = 1, y = 0, z = 0 },
|
|
||||||
yAxis = { x = 0, y = 1, z = 0 },
|
|
||||||
zAxis = { x = 0, y = 0, z = 1 }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create the plane for the tubes underneath the lego. This is a hack so that the tubes can be patterned underneath the lego.
|
|
||||||
tubeFace = {
|
|
||||||
plane = {
|
|
||||||
origin = { x = 0, y = 0, z = height - t },
|
|
||||||
xAxis = { x = 1, y = 0, z = 0 },
|
|
||||||
yAxis = { x = 0, y = 1, z = 0 },
|
|
||||||
zAxis = { x = 0, y = 0, z = 1 }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make the base
|
// Make the base
|
||||||
s = startSketchOn('XY')
|
base = startSketchOn('XY')
|
||||||
|> startProfileAt([-totalWidth / 2, -totalLength / 2], %)
|
|> startProfileAt([-totalWidth / 2, -totalLength / 2], %)
|
||||||
|> line(end = [totalWidth, 0])
|
|> line(end = [totalWidth, 0])
|
||||||
|> line(end = [0, totalLength])
|
|> line(end = [0, totalLength])
|
||||||
@ -54,8 +34,8 @@ s = startSketchOn('XY')
|
|||||||
|> close()
|
|> close()
|
||||||
|> extrude(length = height)
|
|> extrude(length = height)
|
||||||
|
|
||||||
// Sketch and extrude a rectangular shape to create the shell underneath the lego. This is a hack until we have a shell function.
|
// Sketch and extrude a rectangular shape to create the shell underneath the lego. Will replace with shell function when able to call a face created from shell.
|
||||||
shellExtrude = startSketchOn(s, "start")
|
shellExtrude = startSketchOn(base, "start")
|
||||||
|> startProfileAt([
|
|> startProfileAt([
|
||||||
-(totalWidth / 2 - t),
|
-(totalWidth / 2 - t),
|
||||||
-(totalLength / 2 - t)
|
-(totalLength / 2 - t)
|
||||||
@ -67,7 +47,7 @@ shellExtrude = startSketchOn(s, "start")
|
|||||||
|> extrude(length = -(height - t))
|
|> extrude(length = -(height - t))
|
||||||
|
|
||||||
// Create the pegs on the top of the base
|
// Create the pegs on the top of the base
|
||||||
peg = startSketchOn(s, 'end')
|
peg = startSketchOn(base, 'end')
|
||||||
|> circle(
|
|> circle(
|
||||||
center = [
|
center = [
|
||||||
-(pitch * (wbumps - 1) / 2),
|
-(pitch * (wbumps - 1) / 2),
|
||||||
@ -88,7 +68,7 @@ peg = startSketchOn(s, 'end')
|
|||||||
|> extrude(length = bumpHeight)
|
|> extrude(length = bumpHeight)
|
||||||
|
|
||||||
// Create the pegs on the bottom of the base
|
// Create the pegs on the bottom of the base
|
||||||
tubePattern = startSketchOn(tubeFace)
|
tubePattern = startSketchOn(shellExtrude, 'start')
|
||||||
|> circle(
|
|> circle(
|
||||||
center = [
|
center = [
|
||||||
-(pitch * (wbumps - 1) / 2 - (pitch / 2)),
|
-(pitch * (wbumps - 1) / 2 - (pitch / 2)),
|
||||||
@ -106,4 +86,4 @@ tubePattern = startSketchOn(tubeFace)
|
|||||||
instances = lbumps - 1,
|
instances = lbumps - 1,
|
||||||
distance = pitch
|
distance = pitch
|
||||||
)
|
)
|
||||||
|> extrude(length = -bumpHeight)
|
|> extrude(length = bumpHeight)
|
||||||
|
@ -1,11 +1,4 @@
|
|||||||
[
|
[
|
||||||
{
|
|
||||||
"file": "main.kcl",
|
|
||||||
"pathFromProjectDirectoryToFirstFile": "3d-boaty/main.kcl",
|
|
||||||
"multipleFiles": true,
|
|
||||||
"title": "3D Boaty",
|
|
||||||
"description": "This is a slight remix of Depep1's original 3D Boaty (https://www.printables.com/model/1141963-3d-boaty). This is a tool used for benchmarking 3D FDM printers for bed adhesion, overhangs, bridging and top surface quality. The name of this file is a bit of misnomer, the shape of the object is a typical park bench."
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"file": "main.kcl",
|
"file": "main.kcl",
|
||||||
"pathFromProjectDirectoryToFirstFile": "80-20-rail/main.kcl",
|
"pathFromProjectDirectoryToFirstFile": "80-20-rail/main.kcl",
|
||||||
@ -27,6 +20,13 @@
|
|||||||
"title": "Ball Bearing",
|
"title": "Ball Bearing",
|
||||||
"description": "A ball bearing is a type of rolling-element bearing that uses balls to maintain the separation between the bearing races. The primary purpose of a ball bearing is to reduce rotational friction and support radial and axial loads."
|
"description": "A ball bearing is a type of rolling-element bearing that uses balls to maintain the separation between the bearing races. The primary purpose of a ball bearing is to reduce rotational friction and support radial and axial loads."
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"file": "main.kcl",
|
||||||
|
"pathFromProjectDirectoryToFirstFile": "bench/main.kcl",
|
||||||
|
"multipleFiles": true,
|
||||||
|
"title": "Bench",
|
||||||
|
"description": "This is a slight remix of Depep1's original 3D Boaty (https://www.printables.com/model/1141963-3d-boaty). This is a tool used for benchmarking 3D FDM printers for bed adhesion, overhangs, bridging and top surface quality. The name of this file is a bit of misnomer, the shape of the object is a typical park bench."
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"file": "main.kcl",
|
"file": "main.kcl",
|
||||||
"pathFromProjectDirectoryToFirstFile": "bracket/main.kcl",
|
"pathFromProjectDirectoryToFirstFile": "bracket/main.kcl",
|
||||||
@ -78,18 +78,11 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"file": "main.kcl",
|
"file": "main.kcl",
|
||||||
"pathFromProjectDirectoryToFirstFile": "flange-with-patterns/main.kcl",
|
"pathFromProjectDirectoryToFirstFile": "flange/main.kcl",
|
||||||
"multipleFiles": false,
|
"multipleFiles": false,
|
||||||
"title": "Flange",
|
"title": "Flange",
|
||||||
"description": "A flange is a flat rim, collar, or rib, typically forged or cast, that is used to strengthen an object, guide it, or attach it to another object. Flanges are known for their use in various applications, including piping, plumbing, and mechanical engineering, among others."
|
"description": "A flange is a flat rim, collar, or rib, typically forged or cast, that is used to strengthen an object, guide it, or attach it to another object. Flanges are known for their use in various applications, including piping, plumbing, and mechanical engineering, among others."
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"file": "main.kcl",
|
|
||||||
"pathFromProjectDirectoryToFirstFile": "flange-xy/main.kcl",
|
|
||||||
"multipleFiles": false,
|
|
||||||
"title": "Flange with XY coordinates",
|
|
||||||
"description": "A flange is a flat rim, collar, or rib, typically forged or cast, that is used to strengthen an object, guide it, or attach it to another object. Flanges are known for their use in various applications, including piping, plumbing, and mechanical engineering, among others."
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"file": "main.kcl",
|
"file": "main.kcl",
|
||||||
"pathFromProjectDirectoryToFirstFile": "focusrite-scarlett-mounting-bracket/main.kcl",
|
"pathFromProjectDirectoryToFirstFile": "focusrite-scarlett-mounting-bracket/main.kcl",
|
||||||
|
Before Width: | Height: | Size: 73 KiB After Width: | Height: | Size: 69 KiB |
Before Width: | Height: | Size: 80 KiB After Width: | Height: | Size: 80 KiB |
Before Width: | Height: | Size: 65 KiB After Width: | Height: | Size: 60 KiB |
Before Width: | Height: | Size: 215 KiB After Width: | Height: | Size: 216 KiB |
Before Width: | Height: | Size: 117 KiB After Width: | Height: | Size: 116 KiB |
BIN
public/kcl-samples/screenshots/flange.png
Normal file
After Width: | Height: | Size: 72 KiB |
Before Width: | Height: | Size: 105 KiB After Width: | Height: | Size: 113 KiB |
Before Width: | Height: | Size: 99 KiB After Width: | Height: | Size: 96 KiB |
@ -1,7 +1,7 @@
|
|||||||
ISO-10303-21;
|
ISO-10303-21;
|
||||||
HEADER;
|
HEADER;
|
||||||
FILE_DESCRIPTION((('zoo.dev export')), '2;1');
|
FILE_DESCRIPTION((('zoo.dev export')), '2;1');
|
||||||
FILE_NAME('test.step', '2021-01-01T00:00:00Z', ('Test'), ('Zoo'), 'zoo.dev beta', 'zoo.dev', 'Test');
|
FILE_NAME('dump.step', '2021-01-01T00:00:00+00:00', ('Author unknown'), ('Organization unknown'), 'zoo.dev beta', 'zoo.dev', 'Authorization unknown');
|
||||||
FILE_SCHEMA(('AP203_CONFIGURATION_CONTROLLED_3D_DESIGN_OF_MECHANICAL_PARTS_AND_ASSEMBLIES_MIM_LF'));
|
FILE_SCHEMA(('AP203_CONFIGURATION_CONTROLLED_3D_DESIGN_OF_MECHANICAL_PARTS_AND_ASSEMBLIES_MIM_LF'));
|
||||||
ENDSEC;
|
ENDSEC;
|
||||||
DATA;
|
DATA;
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
ISO-10303-21;
|
ISO-10303-21;
|
||||||
HEADER;
|
HEADER;
|
||||||
FILE_DESCRIPTION((('zoo.dev export')), '2;1');
|
FILE_DESCRIPTION((('zoo.dev export')), '2;1');
|
||||||
FILE_NAME('test.step', '2021-01-01T00:00:00Z', ('Test'), ('Zoo'), 'zoo.dev beta', 'zoo.dev', 'Test');
|
FILE_NAME('dump.step', '2021-01-01T00:00:00+00:00', ('Author unknown'), ('Organization unknown'), 'zoo.dev beta', 'zoo.dev', 'Authorization unknown');
|
||||||
FILE_SCHEMA(('AP203_CONFIGURATION_CONTROLLED_3D_DESIGN_OF_MECHANICAL_PARTS_AND_ASSEMBLIES_MIM_LF'));
|
FILE_SCHEMA(('AP203_CONFIGURATION_CONTROLLED_3D_DESIGN_OF_MECHANICAL_PARTS_AND_ASSEMBLIES_MIM_LF'));
|
||||||
ENDSEC;
|
ENDSEC;
|
||||||
DATA;
|
DATA;
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
ISO-10303-21;
|
ISO-10303-21;
|
||||||
HEADER;
|
HEADER;
|
||||||
FILE_DESCRIPTION((('zoo.dev export')), '2;1');
|
FILE_DESCRIPTION((('zoo.dev export')), '2;1');
|
||||||
FILE_NAME('test.step', '2021-01-01T00:00:00Z', ('Test'), ('Zoo'), 'zoo.dev beta', 'zoo.dev', 'Test');
|
FILE_NAME('dump.step', '2021-01-01T00:00:00+00:00', ('Author unknown'), ('Organization unknown'), 'zoo.dev beta', 'zoo.dev', 'Authorization unknown');
|
||||||
FILE_SCHEMA(('AP203_CONFIGURATION_CONTROLLED_3D_DESIGN_OF_MECHANICAL_PARTS_AND_ASSEMBLIES_MIM_LF'));
|
FILE_SCHEMA(('AP203_CONFIGURATION_CONTROLLED_3D_DESIGN_OF_MECHANICAL_PARTS_AND_ASSEMBLIES_MIM_LF'));
|
||||||
ENDSEC;
|
ENDSEC;
|
||||||
DATA;
|
DATA;
|
||||||
|
11603
public/kcl-samples/step/bench.step
Normal file
@ -1,7 +1,7 @@
|
|||||||
ISO-10303-21;
|
ISO-10303-21;
|
||||||
HEADER;
|
HEADER;
|
||||||
FILE_DESCRIPTION((('zoo.dev export')), '2;1');
|
FILE_DESCRIPTION((('zoo.dev export')), '2;1');
|
||||||
FILE_NAME('test.step', '2021-01-01T00:00:00Z', ('Test'), ('Zoo'), 'zoo.dev beta', 'zoo.dev', 'Test');
|
FILE_NAME('dump.step', '2021-01-01T00:00:00+00:00', ('Author unknown'), ('Organization unknown'), 'zoo.dev beta', 'zoo.dev', 'Authorization unknown');
|
||||||
FILE_SCHEMA(('AP203_CONFIGURATION_CONTROLLED_3D_DESIGN_OF_MECHANICAL_PARTS_AND_ASSEMBLIES_MIM_LF'));
|
FILE_SCHEMA(('AP203_CONFIGURATION_CONTROLLED_3D_DESIGN_OF_MECHANICAL_PARTS_AND_ASSEMBLIES_MIM_LF'));
|
||||||
ENDSEC;
|
ENDSEC;
|
||||||
DATA;
|
DATA;
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
ISO-10303-21;
|
ISO-10303-21;
|
||||||
HEADER;
|
HEADER;
|
||||||
FILE_DESCRIPTION((('zoo.dev export')), '2;1');
|
FILE_DESCRIPTION((('zoo.dev export')), '2;1');
|
||||||
FILE_NAME('test.step', '2021-01-01T00:00:00Z', ('Test'), ('Zoo'), 'zoo.dev beta', 'zoo.dev', 'Test');
|
FILE_NAME('dump.step', '2021-01-01T00:00:00+00:00', ('Author unknown'), ('Organization unknown'), 'zoo.dev beta', 'zoo.dev', 'Authorization unknown');
|
||||||
FILE_SCHEMA(('AP203_CONFIGURATION_CONTROLLED_3D_DESIGN_OF_MECHANICAL_PARTS_AND_ASSEMBLIES_MIM_LF'));
|
FILE_SCHEMA(('AP203_CONFIGURATION_CONTROLLED_3D_DESIGN_OF_MECHANICAL_PARTS_AND_ASSEMBLIES_MIM_LF'));
|
||||||
ENDSEC;
|
ENDSEC;
|
||||||
DATA;
|
DATA;
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
ISO-10303-21;
|
ISO-10303-21;
|
||||||
HEADER;
|
HEADER;
|
||||||
FILE_DESCRIPTION((('zoo.dev export')), '2;1');
|
FILE_DESCRIPTION((('zoo.dev export')), '2;1');
|
||||||
FILE_NAME('test.step', '2021-01-01T00:00:00Z', ('Test'), ('Zoo'), 'zoo.dev beta', 'zoo.dev', 'Test');
|
FILE_NAME('dump.step', '2021-01-01T00:00:00+00:00', ('Author unknown'), ('Organization unknown'), 'zoo.dev beta', 'zoo.dev', 'Authorization unknown');
|
||||||
FILE_SCHEMA(('AP203_CONFIGURATION_CONTROLLED_3D_DESIGN_OF_MECHANICAL_PARTS_AND_ASSEMBLIES_MIM_LF'));
|
FILE_SCHEMA(('AP203_CONFIGURATION_CONTROLLED_3D_DESIGN_OF_MECHANICAL_PARTS_AND_ASSEMBLIES_MIM_LF'));
|
||||||
ENDSEC;
|
ENDSEC;
|
||||||
DATA;
|
DATA;
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
ISO-10303-21;
|
ISO-10303-21;
|
||||||
HEADER;
|
HEADER;
|
||||||
FILE_DESCRIPTION((('zoo.dev export')), '2;1');
|
FILE_DESCRIPTION((('zoo.dev export')), '2;1');
|
||||||
FILE_NAME('test.step', '2021-01-01T00:00:00Z', ('Test'), ('Zoo'), 'zoo.dev beta', 'zoo.dev', 'Test');
|
FILE_NAME('dump.step', '2021-01-01T00:00:00+00:00', ('Author unknown'), ('Organization unknown'), 'zoo.dev beta', 'zoo.dev', 'Authorization unknown');
|
||||||
FILE_SCHEMA(('AP203_CONFIGURATION_CONTROLLED_3D_DESIGN_OF_MECHANICAL_PARTS_AND_ASSEMBLIES_MIM_LF'));
|
FILE_SCHEMA(('AP203_CONFIGURATION_CONTROLLED_3D_DESIGN_OF_MECHANICAL_PARTS_AND_ASSEMBLIES_MIM_LF'));
|
||||||
ENDSEC;
|
ENDSEC;
|
||||||
DATA;
|
DATA;
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
ISO-10303-21;
|
ISO-10303-21;
|
||||||
HEADER;
|
HEADER;
|
||||||
FILE_DESCRIPTION((('zoo.dev export')), '2;1');
|
FILE_DESCRIPTION((('zoo.dev export')), '2;1');
|
||||||
FILE_NAME('test.step', '2021-01-01T00:00:00Z', ('Test'), ('Zoo'), 'zoo.dev beta', 'zoo.dev', 'Test');
|
FILE_NAME('dump.step', '2021-01-01T00:00:00+00:00', ('Author unknown'), ('Organization unknown'), 'zoo.dev beta', 'zoo.dev', 'Authorization unknown');
|
||||||
FILE_SCHEMA(('AP203_CONFIGURATION_CONTROLLED_3D_DESIGN_OF_MECHANICAL_PARTS_AND_ASSEMBLIES_MIM_LF'));
|
FILE_SCHEMA(('AP203_CONFIGURATION_CONTROLLED_3D_DESIGN_OF_MECHANICAL_PARTS_AND_ASSEMBLIES_MIM_LF'));
|
||||||
ENDSEC;
|
ENDSEC;
|
||||||
DATA;
|
DATA;
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
ISO-10303-21;
|
ISO-10303-21;
|
||||||
HEADER;
|
HEADER;
|
||||||
FILE_DESCRIPTION((('zoo.dev export')), '2;1');
|
FILE_DESCRIPTION((('zoo.dev export')), '2;1');
|
||||||
FILE_NAME('test.step', '2021-01-01T00:00:00Z', ('Test'), ('Zoo'), 'zoo.dev beta', 'zoo.dev', 'Test');
|
FILE_NAME('dump.step', '2021-01-01T00:00:00+00:00', ('Author unknown'), ('Organization unknown'), 'zoo.dev beta', 'zoo.dev', 'Authorization unknown');
|
||||||
FILE_SCHEMA(('AP203_CONFIGURATION_CONTROLLED_3D_DESIGN_OF_MECHANICAL_PARTS_AND_ASSEMBLIES_MIM_LF'));
|
FILE_SCHEMA(('AP203_CONFIGURATION_CONTROLLED_3D_DESIGN_OF_MECHANICAL_PARTS_AND_ASSEMBLIES_MIM_LF'));
|
||||||
ENDSEC;
|
ENDSEC;
|
||||||
DATA;
|
DATA;
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
ISO-10303-21;
|
ISO-10303-21;
|
||||||
HEADER;
|
HEADER;
|
||||||
FILE_DESCRIPTION((('zoo.dev export')), '2;1');
|
FILE_DESCRIPTION((('zoo.dev export')), '2;1');
|
||||||
FILE_NAME('test.step', '2021-01-01T00:00:00Z', ('Test'), ('Zoo'), 'zoo.dev beta', 'zoo.dev', 'Test');
|
FILE_NAME('dump.step', '2021-01-01T00:00:00+00:00', ('Author unknown'), ('Organization unknown'), 'zoo.dev beta', 'zoo.dev', 'Authorization unknown');
|
||||||
FILE_SCHEMA(('AP203_CONFIGURATION_CONTROLLED_3D_DESIGN_OF_MECHANICAL_PARTS_AND_ASSEMBLIES_MIM_LF'));
|
FILE_SCHEMA(('AP203_CONFIGURATION_CONTROLLED_3D_DESIGN_OF_MECHANICAL_PARTS_AND_ASSEMBLIES_MIM_LF'));
|
||||||
ENDSEC;
|
ENDSEC;
|
||||||
DATA;
|
DATA;
|
||||||
|
864
public/kcl-samples/step/flange.step
Normal file
@ -1,7 +1,7 @@
|
|||||||
ISO-10303-21;
|
ISO-10303-21;
|
||||||
HEADER;
|
HEADER;
|
||||||
FILE_DESCRIPTION((('zoo.dev export')), '2;1');
|
FILE_DESCRIPTION((('zoo.dev export')), '2;1');
|
||||||
FILE_NAME('test.step', '2021-01-01T00:00:00Z', ('Test'), ('Zoo'), 'zoo.dev beta', 'zoo.dev', 'Test');
|
FILE_NAME('dump.step', '2021-01-01T00:00:00+00:00', ('Author unknown'), ('Organization unknown'), 'zoo.dev beta', 'zoo.dev', 'Authorization unknown');
|
||||||
FILE_SCHEMA(('AP203_CONFIGURATION_CONTROLLED_3D_DESIGN_OF_MECHANICAL_PARTS_AND_ASSEMBLIES_MIM_LF'));
|
FILE_SCHEMA(('AP203_CONFIGURATION_CONTROLLED_3D_DESIGN_OF_MECHANICAL_PARTS_AND_ASSEMBLIES_MIM_LF'));
|
||||||
ENDSEC;
|
ENDSEC;
|
||||||
DATA;
|
DATA;
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
ISO-10303-21;
|
ISO-10303-21;
|
||||||
HEADER;
|
HEADER;
|
||||||
FILE_DESCRIPTION((('zoo.dev export')), '2;1');
|
FILE_DESCRIPTION((('zoo.dev export')), '2;1');
|
||||||
FILE_NAME('test.step', '2021-01-01T00:00:00Z', ('Test'), ('Zoo'), 'zoo.dev beta', 'zoo.dev', 'Test');
|
FILE_NAME('dump.step', '2021-01-01T00:00:00+00:00', ('Author unknown'), ('Organization unknown'), 'zoo.dev beta', 'zoo.dev', 'Authorization unknown');
|
||||||
FILE_SCHEMA(('AP203_CONFIGURATION_CONTROLLED_3D_DESIGN_OF_MECHANICAL_PARTS_AND_ASSEMBLIES_MIM_LF'));
|
FILE_SCHEMA(('AP203_CONFIGURATION_CONTROLLED_3D_DESIGN_OF_MECHANICAL_PARTS_AND_ASSEMBLIES_MIM_LF'));
|
||||||
ENDSEC;
|
ENDSEC;
|
||||||
DATA;
|
DATA;
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
ISO-10303-21;
|
ISO-10303-21;
|
||||||
HEADER;
|
HEADER;
|
||||||
FILE_DESCRIPTION((('zoo.dev export')), '2;1');
|
FILE_DESCRIPTION((('zoo.dev export')), '2;1');
|
||||||
FILE_NAME('test.step', '2021-01-01T00:00:00Z', ('Test'), ('Zoo'), 'zoo.dev beta', 'zoo.dev', 'Test');
|
FILE_NAME('dump.step', '2021-01-01T00:00:00+00:00', ('Author unknown'), ('Organization unknown'), 'zoo.dev beta', 'zoo.dev', 'Authorization unknown');
|
||||||
FILE_SCHEMA(('AP203_CONFIGURATION_CONTROLLED_3D_DESIGN_OF_MECHANICAL_PARTS_AND_ASSEMBLIES_MIM_LF'));
|
FILE_SCHEMA(('AP203_CONFIGURATION_CONTROLLED_3D_DESIGN_OF_MECHANICAL_PARTS_AND_ASSEMBLIES_MIM_LF'));
|
||||||
ENDSEC;
|
ENDSEC;
|
||||||
DATA;
|
DATA;
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
ISO-10303-21;
|
ISO-10303-21;
|
||||||
HEADER;
|
HEADER;
|
||||||
FILE_DESCRIPTION((('zoo.dev export')), '2;1');
|
FILE_DESCRIPTION((('zoo.dev export')), '2;1');
|
||||||
FILE_NAME('test.step', '2021-01-01T00:00:00Z', ('Test'), ('Zoo'), 'zoo.dev beta', 'zoo.dev', 'Test');
|
FILE_NAME('dump.step', '2021-01-01T00:00:00+00:00', ('Author unknown'), ('Organization unknown'), 'zoo.dev beta', 'zoo.dev', 'Authorization unknown');
|
||||||
FILE_SCHEMA(('AP203_CONFIGURATION_CONTROLLED_3D_DESIGN_OF_MECHANICAL_PARTS_AND_ASSEMBLIES_MIM_LF'));
|
FILE_SCHEMA(('AP203_CONFIGURATION_CONTROLLED_3D_DESIGN_OF_MECHANICAL_PARTS_AND_ASSEMBLIES_MIM_LF'));
|
||||||
ENDSEC;
|
ENDSEC;
|
||||||
DATA;
|
DATA;
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
ISO-10303-21;
|
ISO-10303-21;
|
||||||
HEADER;
|
HEADER;
|
||||||
FILE_DESCRIPTION((('zoo.dev export')), '2;1');
|
FILE_DESCRIPTION((('zoo.dev export')), '2;1');
|
||||||
FILE_NAME('test.step', '2021-01-01T00:00:00Z', ('Test'), ('Zoo'), 'zoo.dev beta', 'zoo.dev', 'Test');
|
FILE_NAME('dump.step', '2021-01-01T00:00:00+00:00', ('Author unknown'), ('Organization unknown'), 'zoo.dev beta', 'zoo.dev', 'Authorization unknown');
|
||||||
FILE_SCHEMA(('AP203_CONFIGURATION_CONTROLLED_3D_DESIGN_OF_MECHANICAL_PARTS_AND_ASSEMBLIES_MIM_LF'));
|
FILE_SCHEMA(('AP203_CONFIGURATION_CONTROLLED_3D_DESIGN_OF_MECHANICAL_PARTS_AND_ASSEMBLIES_MIM_LF'));
|
||||||
ENDSEC;
|
ENDSEC;
|
||||||
DATA;
|
DATA;
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
ISO-10303-21;
|
ISO-10303-21;
|
||||||
HEADER;
|
HEADER;
|
||||||
FILE_DESCRIPTION((('zoo.dev export')), '2;1');
|
FILE_DESCRIPTION((('zoo.dev export')), '2;1');
|
||||||
FILE_NAME('test.step', '2021-01-01T00:00:00Z', ('Test'), ('Zoo'), 'zoo.dev beta', 'zoo.dev', 'Test');
|
FILE_NAME('dump.step', '2021-01-01T00:00:00+00:00', ('Author unknown'), ('Organization unknown'), 'zoo.dev beta', 'zoo.dev', 'Authorization unknown');
|
||||||
FILE_SCHEMA(('AP203_CONFIGURATION_CONTROLLED_3D_DESIGN_OF_MECHANICAL_PARTS_AND_ASSEMBLIES_MIM_LF'));
|
FILE_SCHEMA(('AP203_CONFIGURATION_CONTROLLED_3D_DESIGN_OF_MECHANICAL_PARTS_AND_ASSEMBLIES_MIM_LF'));
|
||||||
ENDSEC;
|
ENDSEC;
|
||||||
DATA;
|
DATA;
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
ISO-10303-21;
|
ISO-10303-21;
|
||||||
HEADER;
|
HEADER;
|
||||||
FILE_DESCRIPTION((('zoo.dev export')), '2;1');
|
FILE_DESCRIPTION((('zoo.dev export')), '2;1');
|
||||||
FILE_NAME('test.step', '2021-01-01T00:00:00Z', ('Test'), ('Zoo'), 'zoo.dev beta', 'zoo.dev', 'Test');
|
FILE_NAME('dump.step', '2021-01-01T00:00:00+00:00', ('Author unknown'), ('Organization unknown'), 'zoo.dev beta', 'zoo.dev', 'Authorization unknown');
|
||||||
FILE_SCHEMA(('AP203_CONFIGURATION_CONTROLLED_3D_DESIGN_OF_MECHANICAL_PARTS_AND_ASSEMBLIES_MIM_LF'));
|
FILE_SCHEMA(('AP203_CONFIGURATION_CONTROLLED_3D_DESIGN_OF_MECHANICAL_PARTS_AND_ASSEMBLIES_MIM_LF'));
|
||||||
ENDSEC;
|
ENDSEC;
|
||||||
DATA;
|
DATA;
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
ISO-10303-21;
|
ISO-10303-21;
|
||||||
HEADER;
|
HEADER;
|
||||||
FILE_DESCRIPTION((('zoo.dev export')), '2;1');
|
FILE_DESCRIPTION((('zoo.dev export')), '2;1');
|
||||||
FILE_NAME('test.step', '2021-01-01T00:00:00Z', ('Test'), ('Zoo'), 'zoo.dev beta', 'zoo.dev', 'Test');
|
FILE_NAME('dump.step', '2021-01-01T00:00:00+00:00', ('Author unknown'), ('Organization unknown'), 'zoo.dev beta', 'zoo.dev', 'Authorization unknown');
|
||||||
FILE_SCHEMA(('AP203_CONFIGURATION_CONTROLLED_3D_DESIGN_OF_MECHANICAL_PARTS_AND_ASSEMBLIES_MIM_LF'));
|
FILE_SCHEMA(('AP203_CONFIGURATION_CONTROLLED_3D_DESIGN_OF_MECHANICAL_PARTS_AND_ASSEMBLIES_MIM_LF'));
|
||||||
ENDSEC;
|
ENDSEC;
|
||||||
DATA;
|
DATA;
|
||||||
|