Compare commits

..

38 Commits

Author SHA1 Message Date
6f33dc4bde Try with taiki-e/install-action@2dbeb927f5 2025-02-21 14:38:06 -05:00
42dc3e2ed9 Try with taiki-e/install-action@2dbeb92 2025-02-21 14:36:51 -05:00
086b14f9e6 Pin install-action to v2.48.20 2025-02-21 13:58:03 -05:00
1a74aea879 Fix flip-files-to-nightly after PS7 upgrade 2025-02-21 10:09:07 -05:00
2287d90995 Fix build:wasm:windows 2025-02-21 09:49:14 -05:00
9c1b59f745 Add PS7 link 2025-02-20 20:47:42 -05:00
9d1b4dbab9 Fix QL suggestions 2025-02-20 19:54:02 -05:00
06cee86772 Use binary install of wasm-pack through taiki-e/install-action 2025-02-20 19:38:00 -05:00
431af3b457 Back to cargo 2025-02-20 17:21:18 -05:00
d8f7c49b30 @jtran's comments 2025-02-20 16:51:35 -05:00
3dfe781bac A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores) 2025-02-20 20:58:00 +00:00
af467ca3f0 A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores) 2025-02-20 20:51:14 +00:00
711055f741 Use install:wasm-pack:sh where possible 2025-02-20 15:44:59 -05:00
9f93cffc6c A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores) 2025-02-20 19:42:50 +00:00
2286455c7b A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores) 2025-02-20 19:33:47 +00:00
7a9155b493 Decouple wasm-pack install and build:wasm 2025-02-20 14:26:45 -05:00
28b6afe9d0 Try with install:wasm-pack:cargo 2025-02-20 13:49:14 -05:00
38cde4bf05 Another fix 2025-02-20 13:44:04 -05:00
c286a17dc5 A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores) 2025-02-20 17:37:41 +00:00
a29ef310d8 A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores) 2025-02-20 17:29:57 +00:00
bffecf17d2 Attempt 5 2025-02-20 17:22:49 +00:00
74d20060ad A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores) 2025-02-20 17:20:58 +00:00
126ccdfc18 Attempt 3 for Vercel 2025-02-20 12:11:55 -05:00
b2155f7e85 Attempt 2 for Vercel 2025-02-20 12:06:06 -05:00
cedcece3aa Fix vercel (hopefully) 2025-02-20 11:58:16 -05:00
c4ef639679 install:wasm-pack fix and cleanup 2025-02-20 11:42:51 -05:00
5d3d2fec7e Contd clean up 2025-02-20 11:31:35 -05:00
9cf5de1e50 Update to Powershell 7, fixes left and right 2025-02-20 11:23:45 -05:00
c2a690c3a9 Merge branch 'main' into pierremtb/windows-dev-experience 2025-02-20 09:00:32 -05:00
d2fd6350ba yarn.lock 2025-02-13 10:13:08 -05:00
28dd3e7e1a Remove wasm-pack and improve README instruction 2025-02-13 10:08:30 -05:00
4bbe973598 Merge branch 'main' into pierremtb/windows-dev-experience 2025-02-13 09:13:18 -05:00
bd8ce20e21 Update install:tools:windows 2025-02-13 05:21:07 -05:00
197a4965d6 Merge branch 'main' into pierremtb/windows-dev-experience 2025-02-12 14:05:11 -05:00
44fe8e5aa5 Merge branch 'main' into pierremtb/windows-dev-experience 2025-02-11 16:06:42 -05:00
4f65a9b5c6 Add install:tools:windows 2025-02-11 10:19:34 -05:00
26ef0f5ae1 Add fetch:wasm:windows script 2025-02-11 10:14:07 -05:00
d10e358bec Add files:flip-to-nightly:windows 2025-02-11 10:01:55 -05:00
373 changed files with 22193 additions and 21734 deletions

View File

@ -21,7 +21,7 @@ if [[ ! -f "test-results/.last-run.json" ]]; then
fi
retry=1
max_retrys=1
max_retrys=5
# retry failed tests, doing our own retries because using inbuilt playwright retries causes connection issues
while [[ $retry -le $max_retrys ]]; do

View File

@ -5,6 +5,7 @@ on:
push:
branches:
- main
- pierremtb/windows-dev-experience-pin-v2-release
tags:
- 'v[0-9]+.[0-9]+.[0-9]+'
schedule:
@ -132,7 +133,7 @@ jobs:
- name: yarn install
# Windows is picky sometimes and fails on fetch. Step takes about ~30s
uses: nick-fields/retry@v3.0.1
uses: nick-fields/retry@v3.0.0
with:
timeout_minutes: 2
max_attempts: 3
@ -183,7 +184,7 @@ jobs:
WINDOWS_CERTIFICATE_THUMBPRINT: ${{ secrets.WINDOWS_CERTIFICATE_THUMBPRINT }}
DEBUG: "electron-notarize*"
# TODO: Fix electron-notarize flakes. The logs above should help gather more data on failures
uses: nick-fields/retry@v3.0.1
uses: nick-fields/retry@v3.0.0
with:
timeout_minutes: 10
max_attempts: 3
@ -244,7 +245,7 @@ jobs:
WINDOWS_CERTIFICATE_THUMBPRINT: ${{ secrets.WINDOWS_CERTIFICATE_THUMBPRINT }}
DEBUG: "electron-notarize*"
# TODO: Fix electron-notarize flakes. The logs above should help gather more data on failures
uses: nick-fields/retry@v3.0.1
uses: nick-fields/retry@v3.0.0
with:
timeout_minutes: 10
max_attempts: 3

View File

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

View File

@ -26,21 +26,11 @@ jobs:
const issue_number = context.payload.pull_request.number;
const owner = context.repo.owner;
const repo = context.repo.repo;
const { data: comments } = await github.rest.issues.listComments({
// Post a comment on the PR
await github.rest.issues.createComment({
owner,
repo,
issue_number
});
const commentExists = comments.some(comment => comment.body === message);
if (!commentExists) {
// Post a comment on the PR
await github.rest.issues.createComment({
owner,
repo,
issue_number,
body: message,
});
}
issue_number,
body: message,
});

View File

@ -203,11 +203,9 @@ jobs:
- name: Run playwright/electron flow (with retries)
id: retry
if: ${{ !cancelled() && (success() || failure()) }}
uses: nick-fields/retry@v3.0.1
with:
command: .github/ci-cd-scripts/playwright-electron.sh ${{matrix.shardIndex}} ${{matrix.shardTotal}} ${{matrix.os}}
timeout_minutes: 30
max_attempts: 25
shell: bash
run: |
.github/ci-cd-scripts/playwright-electron.sh ${{matrix.shardIndex}} ${{matrix.shardTotal}} ${{matrix.os}}
env:
CI: true
FAIL_ON_CONSOLE_ERRORS: true

3
.gitignore vendored
View File

@ -41,12 +41,9 @@ e2e/playwright/playwright-secrets.env
e2e/playwright/temp1.png
e2e/playwright/temp2.png
e2e/playwright/temp3.png
# this will be overridden for specific directories
e2e/playwright/**/*.png
# exports from snapshot-tests.spec.ts "exports of each format should work"
e2e/playwright/export-snapshots/*
!e2e/playwright/export-snapshots/*.png
!e2e/playwright/snapshot-tests.spec.ts-snapshots/*.png
/kcl-samples
/test-results/

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -82,11 +82,11 @@ helixPath = helix(
length = 10,
radius = 5,
axis = {
custom = {
axis = [0, 0, 1.0],
origin = [0, 0.25, 0]
}
},
custom = {
axis = [0, 0, 1.0],
origin = [0, 0.25, 0]
}
},
)
// Create a spring by sweeping around the helix path.

File diff suppressed because one or more lines are too long

View File

@ -95,10 +95,10 @@ circleSketch1 = startSketchOn(offsetPlane('XY', offset = 150))
loft(
[
squareSketch,
circleSketch0,
circleSketch1
],
squareSketch,
circleSketch0,
circleSketch1
],
baseCurveIndex = 0,
bezApproximateRational = false,
tolerance = 0.000001,

View File

@ -9,7 +9,7 @@ Apply a function to every element of a list.
Given a list like `[a, b, c]`, and a function like `f`, returns `[f(a), f(b), f(c)]`
```js
map(array: [KclValue], map_fn: FunctionSource) -> [KclValue]
map(array: [KclValue], map_fn: FunctionParam) -> [KclValue]
```
@ -18,7 +18,7 @@ map(array: [KclValue], map_fn: FunctionSource) -> [KclValue]
| Name | Type | Description | Required |
|----------|------|-------------|----------|
| `array` | [`[KclValue]`](/docs/kcl/types/KclValue) | | Yes |
| `map_fn` | `FunctionSource` | | Yes |
| `map_fn` | `FunctionParam` | | Yes |
### Returns

View File

@ -35,7 +35,7 @@ The transform function returns a transform object. All properties of the object
- `rotation.origin` (either "local" i.e. rotate around its own center, "global" i.e. rotate around the scene's center, or a 3D point, defaults to "local")
```js
patternTransform(solid_set: SolidSet, instances: integer, transform: FunctionSource, use_original?: bool) -> [Solid]
patternTransform(solid_set: SolidSet, instances: integer, transform: FunctionParam, use_original?: bool) -> [Solid]
```
@ -45,7 +45,7 @@ patternTransform(solid_set: SolidSet, instances: integer, transform: FunctionSou
|----------|------|-------------|----------|
| `solid_set` | [`SolidSet`](/docs/kcl/types/SolidSet) | The solid(s) to duplicate | Yes |
| `instances` | `integer` | The number of total instances. Must be greater than or equal to 1. This includes the original entity. For example, if instances is 2, there will be two copies -- the original, and one new copy. If instances is 1, this has no effect. | Yes |
| `transform` | `FunctionSource` | How each replica should be transformed. The transform function takes a single parameter: an integer representing which number replication the transform is for. E.g. the first replica to be transformed will be passed the argument `1`. This simplifies your math: the transform function can rely on id `0` being the original instance passed into the `patternTransform`. See the examples. | Yes |
| `transform` | `FunctionParam` | How each replica should be transformed. The transform function takes a single parameter: an integer representing which number replication the transform is for. E.g. the first replica to be transformed will be passed the argument `1`. This simplifies your math: the transform function can rely on id `0` being the original instance passed into the `patternTransform`. See the examples. | Yes |
| `use_original` | `bool` | If the target was sketched on an extrusion, setting this will use the original sketch as the target, not the entire joined solid. Defaults to false. | No |
### Returns

View File

@ -9,7 +9,7 @@ Just like patternTransform, but works on 2D sketches not 3D solids.
```js
patternTransform2d(sketch_set: SketchSet, instances: integer, transform: FunctionSource, use_original?: bool) -> [Sketch]
patternTransform2d(sketch_set: SketchSet, instances: integer, transform: FunctionParam, use_original?: bool) -> [Sketch]
```
@ -19,7 +19,7 @@ patternTransform2d(sketch_set: SketchSet, instances: integer, transform: Functio
|----------|------|-------------|----------|
| `sketch_set` | [`SketchSet`](/docs/kcl/types/SketchSet) | The sketch(es) to duplicate | Yes |
| `instances` | `integer` | The number of total instances. Must be greater than or equal to 1. This includes the original entity. For example, if instances is 2, there will be two copies -- the original, and one new copy. If instances is 1, this has no effect. | Yes |
| `transform` | `FunctionSource` | How each replica should be transformed. The transform function takes a single parameter: an integer representing which number replication the transform is for. E.g. the first replica to be transformed will be passed the argument `1`. This simplifies your math: the transform function can rely on id `0` being the original instance passed into the `patternTransform`. See the examples. | Yes |
| `transform` | `FunctionParam` | How each replica should be transformed. The transform function takes a single parameter: an integer representing which number replication the transform is for. E.g. the first replica to be transformed will be passed the argument `1`. This simplifies your math: the transform function can rely on id `0` being the original instance passed into the `patternTransform`. See the examples. | Yes |
| `use_original` | `bool` | If the target was sketched on an extrusion, setting this will use the original sketch as the target, not the entire joined solid. Defaults to false. | No |
### Returns

View File

@ -9,7 +9,7 @@ Take a starting value. Then, for each element of an array, calculate the next va
using the previous value and the element.
```js
reduce(array: [KclValue], start: KclValue, reduce_fn: FunctionSource) -> KclValue
reduce(array: [KclValue], start: KclValue, reduce_fn: FunctionParam) -> KclValue
```
@ -19,7 +19,7 @@ reduce(array: [KclValue], start: KclValue, reduce_fn: FunctionSource) -> KclValu
|----------|------|-------------|----------|
| `array` | [`[KclValue]`](/docs/kcl/types/KclValue) | | Yes |
| `start` | [`KclValue`](/docs/kcl/types/KclValue) | Any KCL value. | Yes |
| `reduce_fn` | `FunctionSource` | | Yes |
| `reduce_fn` | `FunctionParam` | | Yes |
### Returns

View File

@ -6,15 +6,7 @@ layout: manual
Start a new 2-dimensional sketch on a specific plane or face.
### Sketch on Face Behavior
There are some important behaviors to understand when sketching on a face:
The resulting sketch will _include_ the face and thus Solid that was sketched on. So say you were to export the resulting Sketch / Solid from a sketch on a face, you would get both the artifact of the sketch on the face and the parent face / Solid itself.
This is important to understand because if you were to then sketch on the resulting Solid, it would again include the face and parent Solid that was sketched on. This could go on indefinitely.
The point is if you want to export the result of a sketch on a face, you only need to export the final Solid that was created from the sketch on the face, since it will include all the parent faces and Solids.
```js
startSketchOn(data: SketchData, tag?: FaceTag) -> SketchSurface

File diff suppressed because it is too large Load Diff

View File

@ -266,10 +266,10 @@ myRect = rect([20, 0])
myRect
|> extrude(10, %)
|> fillet(
|> fillet({
radius = 0.5,
tags = [myRect.tags.rectangleSegmentA001]
)
}, %)
```
See how we use the tag `rectangleSegmentA001` in the `fillet` function outside

View File

@ -18,7 +18,7 @@ A base path.
|----------|------|-------------|----------|
| `from` |`[number, number]`| The from point. | No |
| `to` |`[number, number]`| The to point. | No |
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A unit of length. | No |
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A base path. | No |
| `tag` |[`TagDeclarator`](/docs/kcl/types#tag-declaration)| The tag of the path. | No |
| `__geoMeta` |[`GeoMeta`](/docs/kcl/types/GeoMeta)| Metadata. | No |

View File

@ -1,10 +1,9 @@
---
title: "EnvironmentRef"
excerpt: "An index pointing to a snapshot within a specific (unspecified) environment."
excerpt: ""
layout: manual
---
An index pointing to a snapshot within a specific (unspecified) environment.
[`SnapshotRef`](/docs/kcl/types/SnapshotRef)

View File

@ -28,7 +28,7 @@ An extrude plane.
| `faceId` |`string`| The face id for the extrude plane. | No |
| `tag` |[`TagDeclarator`](/docs/kcl/types#tag-declaration)| The tag. | No |
| `id` |`string`| The id of the geometry. | No |
| `sourceRange` |[`SourceRange`](/docs/kcl/types/SourceRange)| The source range. | No |
| `sourceRange` |`SourceRange`| The source range. | No |
----
@ -48,7 +48,7 @@ An extruded arc.
| `faceId` |`string`| The face id for the extrude plane. | No |
| `tag` |[`TagDeclarator`](/docs/kcl/types#tag-declaration)| The tag. | No |
| `id` |`string`| The id of the geometry. | No |
| `sourceRange` |[`SourceRange`](/docs/kcl/types/SourceRange)| The source range. | No |
| `sourceRange` |`SourceRange`| The source range. | No |
----
@ -68,7 +68,7 @@ Geometry metadata.
| `faceId` |`string`| The id for the chamfer surface. | No |
| `tag` |[`TagDeclarator`](/docs/kcl/types#tag-declaration)| The tag. | No |
| `id` |`string`| The id of the geometry. | No |
| `sourceRange` |[`SourceRange`](/docs/kcl/types/SourceRange)| The source range. | No |
| `sourceRange` |`SourceRange`| The source range. | No |
----
@ -88,7 +88,7 @@ Geometry metadata.
| `faceId` |`string`| The id for the fillet surface. | No |
| `tag` |[`TagDeclarator`](/docs/kcl/types#tag-declaration)| The tag. | No |
| `id` |`string`| The id of the geometry. | No |
| `sourceRange` |[`SourceRange`](/docs/kcl/types/SourceRange)| The source range. | No |
| `sourceRange` |`SourceRange`| The source range. | No |
----

View File

@ -23,7 +23,7 @@ A face.
| `yAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the face's Y axis be? | No |
| `zAxis` |[`Point3d`](/docs/kcl/types/Point3d)| The z-axis (normal). | No |
| `solid` |[`Solid`](/docs/kcl/types/Solid)| The solid the face is on. | No |
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A unit of length. | No |
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A face. | No |
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |

View File

@ -11,6 +11,7 @@ A tag for a face.
**This schema accepts any of the following:**
A tag for a face.
[`StartOrEnd`](/docs/kcl/types/StartOrEnd)

View File

@ -17,6 +17,6 @@ Geometry metadata.
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `id` |`string`| The id of the geometry. | No |
| `sourceRange` |[`SourceRange`](/docs/kcl/types/SourceRange)| The source range. | No |
| `sourceRange` |`SourceRange`| The source range. | No |

View File

@ -21,7 +21,7 @@ A helix.
| `revolutions` |`number`| Number of revolutions. | No |
| `angleStart` |`number`| Start angle (in degrees). | 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 helix. | No |
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |

View File

@ -21,7 +21,7 @@ A helix.
| `revolutions` |`number`| Number of revolutions. | No |
| `angleStart` |`number`| Start angle (in degrees). | 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 helix. | No |
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |

View File

@ -59,7 +59,7 @@ Any KCL value.
|----------|------|-------------|----------|
| `type` |enum: `Number`| | No |
| `value` |`number`| | No |
| `ty` |[`NumericType`](/docs/kcl/types/NumericType)| | No |
| `ty` |[`NumericType`](/docs/kcl/types/NumericType)| Any KCL value. | No |
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
@ -164,7 +164,7 @@ Any KCL value.
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: [`Plane`](/docs/kcl/types/Plane)| | No |
| `value` |[`Plane`](/docs/kcl/types/Plane)| A plane. | No |
| `value` |[`Plane`](/docs/kcl/types/Plane)| Any KCL value. | No |
----
@ -180,7 +180,7 @@ Any KCL value.
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: [`Face`](/docs/kcl/types/Face)| | No |
| `value` |[`Face`](/docs/kcl/types/Face)| A face. | No |
| `value` |[`Face`](/docs/kcl/types/Face)| Any KCL value. | No |
----
@ -196,7 +196,7 @@ Any KCL value.
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: [`Sketch`](/docs/kcl/types/Sketch)| | No |
| `value` |[`Sketch`](/docs/kcl/types/Sketch)| A sketch is a collection of paths. | No |
| `value` |[`Sketch`](/docs/kcl/types/Sketch)| Any KCL value. | No |
----
@ -228,7 +228,7 @@ Any KCL value.
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: [`Solid`](/docs/kcl/types/Solid)| | No |
| `value` |[`Solid`](/docs/kcl/types/Solid)| A solid is a collection of extrude surfaces. | No |
| `value` |[`Solid`](/docs/kcl/types/Solid)| Any KCL value. | No |
----
@ -260,7 +260,7 @@ Any KCL value.
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: [`Helix`](/docs/kcl/types/Helix)| | No |
| `value` |[`Helix`](/docs/kcl/types/Helix)| A helix. | No |
| `value` |[`Helix`](/docs/kcl/types/Helix)| Any KCL value. | No |
----
@ -295,6 +295,7 @@ Data for an imported geometry.
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `Function`| | No |
| `memory` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
@ -311,7 +312,7 @@ Data for an imported geometry.
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `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)| Any KCL value. | No |
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
@ -328,7 +329,7 @@ Data for an imported geometry.
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `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)| Any KCL value. | No |
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |

View File

@ -16,6 +16,6 @@ Metadata.
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `sourceRange` |[`SourceRange`](/docs/kcl/types/SourceRange)| The source range. | No |
| `sourceRange` |`SourceRange`| The source range. | No |

View File

@ -211,8 +211,8 @@ A unit of angle.
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `Default`| | No |
| `len` |[`UnitLen`](/docs/kcl/types/UnitLen)| A unit of length. | No |
| `angle` |[`UnitAngle`](/docs/kcl/types/UnitAngle)| A unit of angle. | No |
| `len` |[`UnitLen`](/docs/kcl/types/UnitLen)| | No |
| `angle` |[`UnitAngle`](/docs/kcl/types/UnitAngle)| | No |
----

View File

@ -27,7 +27,7 @@ A path that goes to a point.
| `type` |enum: `ToPoint`| | No |
| `from` |`[number, number]`| The from point. | No |
| `to` |`[number, number]`| The to point. | No |
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A unit of length. | No |
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A path. | No |
| `tag` |[`TagDeclarator`](/docs/kcl/types#tag-declaration)| The tag of the path. | No |
| `__geoMeta` |[`GeoMeta`](/docs/kcl/types/GeoMeta)| Metadata. | No |
@ -50,7 +50,7 @@ A arc that is tangential to the last path segment that goes to a point
| `ccw` |`boolean`| arc's direction | No |
| `from` |`[number, number]`| The from point. | No |
| `to` |`[number, number]`| The to point. | No |
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A unit of length. | No |
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A path. | No |
| `tag` |[`TagDeclarator`](/docs/kcl/types#tag-declaration)| The tag of the path. | No |
| `__geoMeta` |[`GeoMeta`](/docs/kcl/types/GeoMeta)| Metadata. | No |
@ -73,7 +73,7 @@ A arc that is tangential to the last path segment
| `ccw` |`boolean`| arc's direction | No |
| `from` |`[number, number]`| The from point. | No |
| `to` |`[number, number]`| The to point. | No |
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A unit of length. | No |
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A path. | No |
| `tag` |[`TagDeclarator`](/docs/kcl/types#tag-declaration)| The tag of the path. | No |
| `__geoMeta` |[`GeoMeta`](/docs/kcl/types/GeoMeta)| Metadata. | No |
@ -97,7 +97,7 @@ a complete arc
| `ccw` |`boolean`| arc's direction This is used to compute the tangential angle. | No |
| `from` |`[number, number]`| The from point. | No |
| `to` |`[number, number]`| The to point. | No |
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A unit of length. | No |
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A path. | No |
| `tag` |[`TagDeclarator`](/docs/kcl/types#tag-declaration)| The tag of the path. | No |
| `__geoMeta` |[`GeoMeta`](/docs/kcl/types/GeoMeta)| Metadata. | No |
@ -121,7 +121,7 @@ A base path.
| `p3` |`[number, number]`| Point 3 of the circle | No |
| `from` |`[number, number]`| The from point. | No |
| `to` |`[number, number]`| The to point. | No |
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A unit of length. | No |
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A path. | No |
| `tag` |[`TagDeclarator`](/docs/kcl/types#tag-declaration)| The tag of the path. | No |
| `__geoMeta` |[`GeoMeta`](/docs/kcl/types/GeoMeta)| Metadata. | No |
@ -143,7 +143,7 @@ A path that is horizontal.
| `x` |`number`| The x coordinate. | No |
| `from` |`[number, number]`| The from point. | No |
| `to` |`[number, number]`| The to point. | No |
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A unit of length. | No |
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A path. | No |
| `tag` |[`TagDeclarator`](/docs/kcl/types#tag-declaration)| The tag of the path. | No |
| `__geoMeta` |[`GeoMeta`](/docs/kcl/types/GeoMeta)| Metadata. | No |
@ -166,7 +166,7 @@ An angled line to.
| `y` |`number`| The y coordinate. | No |
| `from` |`[number, number]`| The from point. | No |
| `to` |`[number, number]`| The to point. | No |
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A unit of length. | No |
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A path. | No |
| `tag` |[`TagDeclarator`](/docs/kcl/types#tag-declaration)| The tag of the path. | No |
| `__geoMeta` |[`GeoMeta`](/docs/kcl/types/GeoMeta)| Metadata. | No |
@ -187,7 +187,7 @@ A base path.
| `type` |enum: `Base`| | No |
| `from` |`[number, number]`| The from point. | No |
| `to` |`[number, number]`| The to point. | No |
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A unit of length. | No |
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A path. | No |
| `tag` |[`TagDeclarator`](/docs/kcl/types#tag-declaration)| The tag of the path. | No |
| `__geoMeta` |[`GeoMeta`](/docs/kcl/types/GeoMeta)| Metadata. | No |
@ -211,7 +211,7 @@ A circular arc, not necessarily tangential to the current point.
| `ccw` |`boolean`| True if the arc is counterclockwise. | No |
| `from` |`[number, number]`| The from point. | No |
| `to` |`[number, number]`| The to point. | No |
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A unit of length. | No |
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A path. | No |
| `tag` |[`TagDeclarator`](/docs/kcl/types#tag-declaration)| The tag of the path. | No |
| `__geoMeta` |[`GeoMeta`](/docs/kcl/types/GeoMeta)| Metadata. | No |

View File

@ -18,12 +18,12 @@ A plane.
|----------|------|-------------|----------|
| `id` |`string`| The id of the plane. | No |
| `artifactId` |[`ArtifactId`](/docs/kcl/types/ArtifactId)| The artifact ID. | No |
| `value` |[`PlaneType`](/docs/kcl/types/PlaneType)| Type for a plane. | No |
| `value` |[`PlaneType`](/docs/kcl/types/PlaneType)| A plane. | No |
| `origin` |[`Point3d`](/docs/kcl/types/Point3d)| Origin of the plane. | No |
| `xAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the plane's X axis be? | No |
| `yAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the plane's Y axis be? | No |
| `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 plane. | No |
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |

View File

@ -17,6 +17,6 @@ Data for polar coordinates.
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `angle` |`number`| The angle of the line (in degrees). | No |
| `length` |[`TyF64`](/docs/kcl/types/TyF64)| The length of the line. | No |
| `length` |`number`| The length of the line. | No |

View File

@ -57,7 +57,7 @@ You can still execute _new_ commands on the sketch like `extrude`, `revolve`, `l
| `tags` |`object`| Tag identifiers that have been declared in this sketch. | 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`| | No |
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A unit of length. | No |
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A sketch is a collection of paths. | No |
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| Metadata. | No |

View File

@ -11,7 +11,7 @@ Data for start sketch on. You can start a sketch on a plane or an solid.
**This schema accepts any of the following:**
Orientation data that can be used to construct a plane, not a plane in itself.
Data for start sketch on. You can start a sketch on a plane or an solid.
[`PlaneData`](/docs/kcl/types/PlaneData)
@ -23,7 +23,7 @@ Orientation data that can be used to construct a plane, not a plane in itself.
----
A plane.
Data for start sketch on. You can start a sketch on a plane or an solid.
[`Plane`](/docs/kcl/types/Plane)
@ -35,43 +35,7 @@ A plane.
----
A solid is a collection of extrude surfaces.
When you define a solid to a variable like:
```kcl
myPart = startSketchOn('XY')
|> startProfileAt([-12, 12], %)
|> line(end = [24, 0])
|> line(end = [0, -24])
|> line(end = [-24, 0])
|> close()
|> extrude(length = 6)
```
The `myPart` variable will be an executed [`Solid`](/docs/kcl/types/Solid) object. Executed being past tense, because the engine has already executed the commands to create the solid.
The previous solid commands will never be executed again, in this case.
If you would like to encapsulate the commands to create the solid any time you call it, you can use a function.
```kcl
fn createPart() {
return startSketchOn('XY')
|> startProfileAt([-12, 12], %)
|> line(end = [24, 0])
|> line(end = [0, -24])
|> line(end = [-24, 0])
|> close()
|> extrude(length = 6)
}
```
Now, every time you call `createPart()`, the commands will be executed and a new solid will be created.
When you assign the result of `createPart()` to a variable (`myPart = createPart()`), you are assigning the executed solid to that variable. Meaning that the solid `myPart` will not be executed again.
You can still execute _new_ commands on the solid like `shell`, `fillet`, `chamfer`, etc. and the solid will be updated.
Data for start sketch on. You can start a sketch on a plane or an solid.
[`Solid`](/docs/kcl/types/Solid)

View File

@ -11,7 +11,7 @@ A sketch surface or a sketch.
**This schema accepts any of the following:**
A sketch type.
A sketch surface or a sketch.
[`SketchSurface`](/docs/kcl/types/SketchSurface)
@ -23,41 +23,7 @@ A sketch type.
----
A sketch is a collection of paths.
When you define a sketch to a variable like:
```kcl
mySketch = startSketchOn('XY')
|> startProfileAt([-12, 12], %)
|> line(end = [24, 0])
|> line(end = [0, -24])
|> line(end = [-24, 0])
|> close()
```
The `mySketch` variable will be an executed [`Sketch`](/docs/kcl/types/Sketch) object. Executed being past tense, because the engine has already executed the commands to create the sketch.
The previous sketch commands will never be executed again, in this case.
If you would like to encapsulate the commands to create the sketch any time you call it, you can use a function.
```kcl
fn createSketch() {
return startSketchOn('XY')
|> startProfileAt([-12, 12], %)
|> line(end = [24, 0])
|> line(end = [0, -24])
|> line(end = [-24, 0])
|> close()
}
```
Now, every time you call `createSketch()`, the commands will be executed and a new sketch will be created.
When you assign the result of `createSketch()` to a variable (`mySketch = createSketch()`), you are assigning the executed sketch to that variable. Meaning that the sketch `mySketch` will not be executed again.
You can still execute _new_ commands on the sketch like `extrude`, `revolve`, `loft`, etc. and the sketch will be updated.
A sketch surface or a sketch.
[`Sketch`](/docs/kcl/types/Sketch)

View File

@ -66,7 +66,7 @@ You can still execute _new_ commands on the sketch like `extrude`, `revolve`, `l
| `tags` |`object`| Tag identifiers that have been declared in this sketch. | 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`| | No |
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A unit of length. | No |
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A sketch or a group of sketches. | No |
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| Metadata. | No |

View File

@ -27,12 +27,12 @@ A plane.
| `type` |enum: `plane`| | No |
| `id` |`string`| The id of the plane. | No |
| `artifactId` |[`ArtifactId`](/docs/kcl/types/ArtifactId)| The artifact ID. | No |
| `value` |[`PlaneType`](/docs/kcl/types/PlaneType)| Type for a plane. | No |
| `value` |[`PlaneType`](/docs/kcl/types/PlaneType)| A sketch type. | No |
| `origin` |[`Point3d`](/docs/kcl/types/Point3d)| Origin of the plane. | No |
| `xAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the plane's X axis be? | No |
| `yAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the plane's Y axis be? | No |
| `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 sketch type. | No |
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
@ -57,7 +57,7 @@ A face.
| `yAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the face's Y axis be? | No |
| `zAxis` |[`Point3d`](/docs/kcl/types/Point3d)| The z-axis (normal). | No |
| `solid` |[`Solid`](/docs/kcl/types/Solid)| The solid the face is on. | No |
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A unit of length. | No |
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A sketch type. | No |
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |

View File

@ -60,7 +60,7 @@ You can still execute _new_ commands on the solid like `shell`, `fillet`, `chamf
| `startCapId` |`string`| The id of the extrusion start cap | No |
| `endCapId` |`string`| The id of the extrusion end cap | No |
| `edgeCuts` |`[` [`EdgeCut`](/docs/kcl/types/EdgeCut) `]`| Chamfers or fillets on this solid. | No |
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A unit of length. | No |
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A solid is a collection of extrude surfaces. | No |
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| Metadata. | No |

View File

@ -69,7 +69,7 @@ You can still execute _new_ commands on the solid like `shell`, `fillet`, `chamf
| `startCapId` |`string`| The id of the extrusion start cap | No |
| `endCapId` |`string`| The id of the extrusion end cap | No |
| `edgeCuts` |`[` [`EdgeCut`](/docs/kcl/types/EdgeCut) `]`| Chamfers or fillets on this solid. | No |
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A unit of length. | No |
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A solid or a group of solids. | No |
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| Metadata. | No |

View File

@ -1,15 +0,0 @@
---
title: "SourceRange"
excerpt: ""
layout: manual
---
**Type:** `integer` (`uint`)

View File

@ -0,0 +1,90 @@
---
title: "StandardPlane"
excerpt: "One of the standard planes."
layout: manual
---
One of the standard planes.
**This schema accepts exactly one of the following:**
The XY plane.
**enum:** `XY`
----
The opposite side of the XY plane.
**enum:** `-XY`
----
The XZ plane.
**enum:** `XZ`
----
The opposite side of the XZ plane.
**enum:** `-XZ`
----
The YZ plane.
**enum:** `YZ`
----
The opposite side of the YZ plane.
**enum:** `-YZ`
----

View File

@ -11,41 +11,7 @@ A path to sweep along.
**This schema accepts any of the following:**
A sketch is a collection of paths.
When you define a sketch to a variable like:
```kcl
mySketch = startSketchOn('XY')
|> startProfileAt([-12, 12], %)
|> line(end = [24, 0])
|> line(end = [0, -24])
|> line(end = [-24, 0])
|> close()
```
The `mySketch` variable will be an executed [`Sketch`](/docs/kcl/types/Sketch) object. Executed being past tense, because the engine has already executed the commands to create the sketch.
The previous sketch commands will never be executed again, in this case.
If you would like to encapsulate the commands to create the sketch any time you call it, you can use a function.
```kcl
fn createSketch() {
return startSketchOn('XY')
|> startProfileAt([-12, 12], %)
|> line(end = [24, 0])
|> line(end = [0, -24])
|> line(end = [-24, 0])
|> close()
}
```
Now, every time you call `createSketch()`, the commands will be executed and a new sketch will be created.
When you assign the result of `createSketch()` to a variable (`mySketch = createSketch()`), you are assigning the executed sketch to that variable. Meaning that the sketch `mySketch` will not be executed again.
You can still execute _new_ commands on the sketch like `extrude`, `revolve`, `loft`, etc. and the sketch will be updated.
A path to sweep along.
[`Sketch`](/docs/kcl/types/Sketch)
@ -57,7 +23,7 @@ You can still execute _new_ commands on the sketch like `extrude`, `revolve`, `l
----
A helix.
A path to sweep along.
[`Helix`](/docs/kcl/types/Helix)

View File

@ -1,21 +0,0 @@
---
title: "TyF64"
excerpt: ""
layout: manual
---
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `n` |`number`| | No |
| `ty` |[`NumericType`](/docs/kcl/types/NumericType)| | No |

View File

@ -82,7 +82,7 @@ test.describe('Command bar tests', { tag: ['@skipWin'] }, () => {
await page.keyboard.press('Enter') // submit
await page.waitForTimeout(100)
await expect(page.locator('.cm-activeLine')).toContainText(
`fillet( radius = ${KCL_DEFAULT_LENGTH}, tags = [seg01] )`
`fillet({ radius = ${KCL_DEFAULT_LENGTH}, tags = [seg01] }, %)`
)
})
@ -219,11 +219,7 @@ test.describe('Command bar tests', { tag: ['@skipWin'] }, () => {
}
)
test('Can extrude from the command bar', async ({
page,
homePage,
cmdBar,
}) => {
test('Can extrude from the command bar', async ({ page, homePage }) => {
await page.addInitScript(async () => {
localStorage.setItem(
'persistCode',
@ -258,7 +254,7 @@ test.describe('Command bar tests', { tag: ['@skipWin'] }, () => {
await expect(cmdSearchBar).toBeVisible()
// Search for extrude command and choose it
await cmdBar.cmdOptions.getByText('Extrude').click()
await page.getByRole('option', { name: 'Extrude' }).click()
// Assert that we're on the selection step
await expect(page.getByRole('button', { name: 'selection' })).toBeDisabled()

View File

@ -59,25 +59,18 @@ export interface Fixtures {
homePage: HomePageFixture
}
export class AuthenticatedTronApp {
public originalPage: Page
public readonly _page: 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(
browserContext: BrowserContext,
originalPage: Page,
testInfo: TestInfo
) {
this.page = originalPage
this.originalPage = originalPage
this.browserContext = browserContext
// Will be overwritten in the initializer
this.context = browserContext
constructor(context: BrowserContext, page: Page, testInfo: TestInfo) {
this._page = page
this.page = page
this.context = context
this.testInfo = testInfo
}
async initialise(
@ -93,16 +86,9 @@ export class AuthenticatedTronApp {
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
// running against electron applications.
// The timeline is still broken but failure screenshots work again.
this.context = context
Object.assign(this.browserContext, this.context)
this.electronApp = electronApp
this.dir = dir

View File

@ -13,8 +13,8 @@ import {
import * as TOML from '@iarna/toml'
import { expectPixelColor } from './fixtures/sceneFixture'
// Because our default test settings have the onboardingStatus set to 'dismissed',
// we must set it to empty for the tests where we want to see the onboarding immediately.
// Because onboarding relies on an app setting we need to set it as incompletel
// for all these tests.
test.describe('Onboarding tests', () => {
test(
@ -22,7 +22,7 @@ test.describe('Onboarding tests', () => {
{
appSettings: {
app: {
onboardingStatus: '',
onboardingStatus: 'incomplete',
},
},
cleanProjectDir: true,
@ -63,7 +63,7 @@ test.describe('Onboarding tests', () => {
tag: '@electron',
appSettings: {
app: {
onboardingStatus: '',
onboardingStatus: 'incomplete',
},
},
cleanProjectDir: true,
@ -106,6 +106,11 @@ test.describe('Onboarding tests', () => {
test(
'Code resets after confirmation',
{
appSettings: {
app: {
onboardingStatus: 'incomplete',
},
},
cleanProjectDir: true,
},
async ({ context, page, homePage }) => {
@ -153,7 +158,7 @@ test.describe('Onboarding tests', () => {
{
appSettings: {
app: {
onboardingStatus: '',
onboardingStatus: 'incomplete',
},
},
},
@ -314,7 +319,7 @@ test.describe('Onboarding tests', () => {
{
appSettings: {
app: {
onboardingStatus: '',
onboardingStatus: 'incomplete',
},
},
cleanProjectDir: true,
@ -387,7 +392,7 @@ test.describe('Onboarding tests', () => {
{
appSettings: {
app: {
onboardingStatus: '',
onboardingStatus: 'incomplete',
},
},
cleanProjectDir: true,

View File

@ -211,13 +211,12 @@ test.describe('Point-and-click tests', { tag: ['@skipWin'] }, () => {
cameraPos: { x: 16020, y: -2000, z: 10500 },
cameraTarget: { x: -150, y: -4500, z: -80 },
beforeChamferSnippet: `angledLine([segAng(rectangleSegmentA001)-90,217.26],%,$seg01)
chamfer(length = 30,tags = [
chamfer({length = 30,tags = [
seg01,
getNextAdjacentEdge(yo),
getNextAdjacentEdge(seg02),
getOppositeEdge(seg01)
],
)`,
]}, %)`,
afterChamferSelectSnippet:
'sketch002 = startSketchOn(extrude001, seg03)',
@ -237,14 +236,14 @@ test.describe('Point-and-click tests', { tag: ['@skipWin'] }, () => {
beforeChamferSnippet: `angledLine([
segAng(rectangleSegmentA001) - 90,
217.26
], %, $seg01)chamfer(
], %, $seg01)chamfer({
length = 30,
tags = [
seg01,
getNextAdjacentEdge(yo),
getNextAdjacentEdge(seg02)
]
)`,
}, %)`,
afterChamferSelectSnippet:
'sketch003 = startSketchOn(extrude001, seg04)',
@ -261,13 +260,13 @@ test.describe('Point-and-click tests', { tag: ['@skipWin'] }, () => {
clickCoords: { x: 677, y: 87 },
cameraPos: { x: -6200, y: 1500, z: 6200 },
cameraTarget: { x: 8300, y: 1100, z: 4800 },
beforeChamferSnippet: `angledLine([0, 268.43], %, $rectangleSegmentA001)chamfer(
beforeChamferSnippet: `angledLine([0, 268.43], %, $rectangleSegmentA001)chamfer({
length = 30,
tags = [
getNextAdjacentEdge(yo),
getNextAdjacentEdge(seg02)
]
)`,
}, %)`,
afterChamferSelectSnippet:
'sketch004 = startSketchOn(extrude001, seg05)',
afterRectangle1stClickSnippet:
@ -283,9 +282,10 @@ test.describe('Point-and-click tests', { tag: ['@skipWin'] }, () => {
clickCoords: { x: 620, y: 300 },
cameraPos: { x: -1100, y: -7700, z: 1600 },
cameraTarget: { x: 1450, y: 670, z: 4000 },
beforeChamferSnippet: `chamfer(length = 30, tags = [getNextAdjacentEdge(yo)])`,
beforeChamferSnippetEnd:
'|> chamfer(length = 30, tags = [getNextAdjacentEdge(yo)])',
beforeChamferSnippet: `chamfer({
length = 30,
tags = [getNextAdjacentEdge(yo)]
}, %)`,
afterChamferSelectSnippet:
'sketch005 = startSketchOn(extrude001, seg06)',
afterRectangle1stClickSnippet:
@ -313,16 +313,31 @@ test.describe('Point-and-click tests', { tag: ['@skipWin'] }, () => {
|> line(endAbsolute = [profileStartX(%), profileStartY(%)], tag = $seg02)
|> close()
extrude001 = extrude(sketch001, length = 100)
|> chamfer(length = 30, tags = [getOppositeEdge(seg01)], tag = $seg03)
|> chamfer(length = 30, tags = [seg01], tag = $seg04)
|> chamfer(length = 30, tags = [getNextAdjacentEdge(seg02)], tag = $seg05)
|> chamfer(length = 30, tags = [getNextAdjacentEdge(yo)], tag = $seg06)
|> chamfer({
length = 30,
tags = [getOppositeEdge(seg01)]
}, %, $seg03)
|> chamfer({ length = 30, tags = [seg01] }, %, $seg04)
|> chamfer({
length = 30,
tags = [getNextAdjacentEdge(seg02)]
}, %, $seg05)
|> chamfer({
length = 30,
tags = [getNextAdjacentEdge(yo)]
}, %, $seg06)
sketch005 = startSketchOn(extrude001, seg06)
profile004=startProfileAt([-23.43,19.69], sketch005)
profile004 = startProfileAt([-23.43, 19.69], sketch005)
|> angledLine([0, 9.1], %, $rectangleSegmentA005)
|> angledLine([segAng(rectangleSegmentA005) - 90, 84.07], %)
|> angledLine([segAng(rectangleSegmentA005), -segLen(rectangleSegmentA005)], %)
|> line(endAbsolute=[profileStartX(%), profileStartY(%)])
|> angledLine([
segAng(rectangleSegmentA005) - 90,
84.07
], %)
|> angledLine([
segAng(rectangleSegmentA005),
-segLen(rectangleSegmentA005)
], %)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
sketch004 = startSketchOn(extrude001, seg05)
profile003 = startProfileAt([82.57, 322.96], sketch004)
@ -363,6 +378,7 @@ profile001 = startProfileAt([205.96, 254.59], sketch002)
], %)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
`,
{ shouldNormalise: true }
)
@ -399,13 +415,13 @@ profile001 = startProfileAt([205.96, 254.59], sketch002)
cameraPos: { x: 16020, y: -2000, z: 10500 },
cameraTarget: { x: -150, y: -4500, z: -80 },
beforeChamferSnippet: `angledLine([segAng(rectangleSegmentA001)-90,217.26],%,$seg01)
chamfer(extrude001,length=30,tags=[
chamfer({length=30,tags=[
seg01,
getNextAdjacentEdge(yo),
getNextAdjacentEdge(seg02),
getOppositeEdge(seg01),
])`,
beforeChamferSnippetEnd: ')',
getOppositeEdge(seg01)
]}, extrude001)`,
beforeChamferSnippetEnd: '}, extrude001)',
afterChamferSelectSnippet:
'sketch002 = startSketchOn(extrude001, seg03)',
afterRectangle1stClickSnippet:
@ -431,20 +447,18 @@ profile001 = startProfileAt([205.96, 254.59], sketch002)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)], tag = $seg02)
|> close()
extrude001 = extrude(sketch001, length = 100)
chamf = chamfer(
extrude001,
chamf = chamfer({
length = 30,
tags = [getOppositeEdge(seg01)],
tag = $seg03,
)
|> chamfer(
tags = [getOppositeEdge(seg01)]
}, extrude001, $seg03)
|> chamfer({
length = 30,
tags = [
seg01,
getNextAdjacentEdge(yo),
getNextAdjacentEdge(seg02)
],
)
]
}, %)
sketch002 = startSketchOn(extrude001, seg03)
profile001 = startProfileAt([205.96, 254.59], sketch002)
|> angledLine([0, 11.39], %, $rectangleSegmentA002)
@ -456,7 +470,7 @@ profile001 = startProfileAt([205.96, 254.59], sketch002)
segAng(rectangleSegmentA002),
-segLen(rectangleSegmentA002)
], %)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> line(endAbsolute=[profileStartX(%), profileStartY(%)])
|> close()
`,
{ shouldNormalise: true }
@ -1478,9 +1492,9 @@ sketch002 = startSketchOn('XZ')
|> close()
extrude001 = extrude(sketch001, length = -12)
`
const firstFilletDeclaration = 'fillet(radius = 5, tags = [seg01])'
const firstFilletDeclaration = 'fillet({ radius = 5, tags = [seg01] }, %)'
const secondFilletDeclaration =
'fillet(radius = 5, tags = [getOppositeEdge(seg01)])'
'fillet({ radius = 5, tags = [getOppositeEdge(seg01)] }, %)'
// Locators
const firstEdgeLocation = { x: 600, y: 193 }
@ -1580,7 +1594,7 @@ extrude001 = extrude(sketch001, length = -12)
await editor.expectEditor.toContain(firstFilletDeclaration)
await editor.expectState({
diagnostics: [],
activeLines: ['|> fillet(radius = 5, tags = [seg01])'],
activeLines: ['|>fillet({radius=5,tags=[seg01]},%)'],
highlightedCode: '',
})
})
@ -1660,7 +1674,7 @@ extrude001 = extrude(sketch001, length = -12)
await editor.expectEditor.toContain(secondFilletDeclaration)
await editor.expectState({
diagnostics: [],
activeLines: ['|>fillet(radius=5,tags=[getOppositeEdge(seg01)])'],
activeLines: ['radius=5,'],
highlightedCode: '',
})
})
@ -1712,17 +1726,18 @@ extrude001 = extrude(sketch001, length = -12)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)], tag = $seg01)
|> close()
extrude001 = extrude(sketch001, length = -12)
|> fillet(radius = 5, tags = [seg01]) // fillet01
|> fillet(radius = 5, tags = [seg02]) // fillet02
fillet03 = fillet(extrude001, radius = 5, tags = [getOppositeEdge(seg01)])
fillet04 = fillet(extrude001, radius = 5, tags = [getOppositeEdge(seg02)])
|> fillet({ radius = 5, tags = [seg01] }, %) // fillet01
|> fillet({ radius = 5, tags = [seg02] }, %) // fillet02
fillet03 = fillet({ radius = 5, tags = [getOppositeEdge(seg01)]}, extrude001)
fillet04 = fillet({ radius = 5, tags = [getOppositeEdge(seg02)]}, extrude001)
`
const pipedFilletDeclaration = 'fillet(radius = 5, tags = [seg01])'
const secondPipedFilletDeclaration = 'fillet(radius = 5, tags = [seg02])'
const pipedFilletDeclaration = 'fillet({ radius = 5, tags = [seg01] }, %)'
const secondPipedFilletDeclaration =
'fillet({ radius = 5, tags = [seg02] }, %)'
const standaloneFilletDeclaration =
'fillet03 = fillet(extrude001, radius = 5, tags = [getOppositeEdge(seg01)])'
'fillet03 = fillet({ radius = 5, tags = [getOppositeEdge(seg01)]}, extrude001)'
const secondStandaloneFilletDeclaration =
'fillet04 = fillet(extrude001, radius = 5, tags = [getOppositeEdge(seg02)])'
'fillet04 = fillet({ radius = 5, tags = [getOppositeEdge(seg02)]}, extrude001)'
// Locators
const pipedFilletEdgeLocation = { x: 600, y: 193 }
@ -1856,9 +1871,9 @@ fillet04 = fillet(extrude001, radius = 5, tags = [getOppositeEdge(seg02)])
|> close()
extrude001 = extrude(sketch001, length = -12)
`
const firstChamferDeclaration = 'chamfer(length = 5, tags = [seg01])'
const firstChamferDeclaration = 'chamfer({ length = 5, tags = [seg01] }, %)'
const secondChamferDeclaration =
'chamfer(length = 5, tags = [getOppositeEdge(seg01)])'
'chamfer({ length = 5, tags = [getOppositeEdge(seg01)] }, %)'
// Locators
const firstEdgeLocation = { x: 600, y: 193 }
@ -1949,7 +1964,7 @@ extrude001 = extrude(sketch001, length = -12)
await editor.expectEditor.toContain(firstChamferDeclaration)
await editor.expectState({
diagnostics: [],
activeLines: ['|>chamfer(length=5,tags=[seg01])'],
activeLines: ['|>chamfer({length=5,tags=[seg01]},%)'],
highlightedCode: '',
})
})
@ -2033,7 +2048,7 @@ extrude001 = extrude(sketch001, length = -12)
await editor.expectEditor.toContain(secondChamferDeclaration)
await editor.expectState({
diagnostics: [],
activeLines: ['|>chamfer(length=5,tags=[getOppositeEdge(seg01)])'],
activeLines: ['length=5,'],
highlightedCode: '',
})
})
@ -2081,17 +2096,18 @@ extrude001 = extrude(sketch001, length = -12)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)], tag = $seg01)
|> close()
extrude001 = extrude(sketch001, length = -12)
|> chamfer(length = 5, tags = [seg01]) // chamfer01
|> chamfer(length = 5, tags = [seg02]) // chamfer02
chamfer03 = chamfer(extrude001, length = 5, tags = [getOppositeEdge(seg01)])
chamfer04 = chamfer(extrude001, length = 5, tags = [getOppositeEdge(seg02)])
|> chamfer({ length = 5, tags = [seg01] }, %) // chamfer01
|> chamfer({ length = 5, tags = [seg02] }, %) // chamfer02
chamfer03 = chamfer({ length = 5, tags = [getOppositeEdge(seg01)]}, extrude001)
chamfer04 = chamfer({ length = 5, tags = [getOppositeEdge(seg02)]}, extrude001)
`
const pipedChamferDeclaration = 'chamfer(length = 5, tags = [seg01])'
const secondPipedChamferDeclaration = 'chamfer(length = 5, tags = [seg02])'
const pipedChamferDeclaration = 'chamfer({ length = 5, tags = [seg01] }, %)'
const secondPipedChamferDeclaration =
'chamfer({ length = 5, tags = [seg02] }, %)'
const standaloneChamferDeclaration =
'chamfer03 = chamfer(extrude001, length = 5, tags = [getOppositeEdge(seg01)])'
'chamfer03 = chamfer({ length = 5, tags = [getOppositeEdge(seg01)]}, extrude001)'
const secondStandaloneChamferDeclaration =
'chamfer04 = chamfer(extrude001, length = 5, tags = [getOppositeEdge(seg02)])'
'chamfer04 = chamfer({ length = 5, tags = [getOppositeEdge(seg02)]}, extrude001)'
// Locators
const pipedChamferEdgeLocation = { x: 600, y: 193 }
@ -2796,107 +2812,4 @@ radius = 8.69
expect(editor.expectEditor.toContain(newCodeToFind)).toBeTruthy()
})
})
test(`Set appearance`, async ({
context,
page,
homePage,
scene,
editor,
toolbar,
cmdBar,
}) => {
const initialCode = `sketch001 = startSketchOn('XZ')
profile001 = circle({
center = [0, 0],
radius = 100
}, sketch001)
extrude001 = extrude(profile001, length = 100)
`
await context.addInitScript((initialCode) => {
localStorage.setItem('persistCode', initialCode)
}, initialCode)
await page.setBodyDimensions({ width: 1000, height: 500 })
await homePage.goToModelingScene()
await scene.waitForExecutionDone()
// One dumb hardcoded screen pixel value
const testPoint = { x: 500, y: 250 }
const initialColor: [number, number, number] = [135, 135, 135]
await test.step(`Confirm extrude exists with default appearance`, async () => {
await toolbar.closePane('code')
await scene.expectPixelColor(initialColor, testPoint, 15)
})
async function setApperanceAndCheck(
option: string,
hex: string,
shapeColor: [number, number, number]
) {
await toolbar.openPane('feature-tree')
const operationButton = await toolbar.getFeatureTreeOperation(
'Extrude',
0
)
await operationButton.click({ button: 'right' })
const menuButton = page.getByTestId('context-menu-set-appearance')
await menuButton.click()
await cmdBar.expectState({
commandName: 'Appearance',
currentArgKey: 'color',
currentArgValue: '',
headerArguments: {
Color: '',
},
highlightedHeaderArg: 'color',
stage: 'arguments',
})
const item = page.getByText(option, { exact: true })
await item.click()
await cmdBar.expectState({
commandName: 'Appearance',
headerArguments: {
Color: hex,
},
stage: 'review',
})
await cmdBar.progressCmdBar()
await toolbar.closePane('feature-tree')
await scene.expectPixelColor(shapeColor, testPoint, 40)
await toolbar.openPane('code')
if (hex === 'default') {
const anyAppearanceDeclaration = `|> appearance(`
await editor.expectEditor.not.toContain(anyAppearanceDeclaration)
} else {
const declaration = `|> appearance(%, color = '${hex}')`
await editor.expectEditor.toContain(declaration)
// TODO: fix selection range after appearance update
// await editor.expectState({
// diagnostics: [],
// activeLines: [declaration],
// highlightedCode: '',
// })
}
await toolbar.closePane('code')
}
await test.step(`Go through the Set Appearance flow for all options`, async () => {
await setApperanceAndCheck('Red', '#FF0000', [180, 0, 0])
await setApperanceAndCheck('Green', '#00FF00', [0, 180, 0])
await setApperanceAndCheck('Blue', '#0000FF', [0, 0, 180])
await setApperanceAndCheck('Turquoise', '#00FFFF', [0, 180, 180])
await setApperanceAndCheck('Purple', '#FF00FF', [180, 0, 180])
await setApperanceAndCheck('Yellow', '#FFFF00', [180, 180, 0])
await setApperanceAndCheck('Black', '#000000', [0, 0, 0])
await setApperanceAndCheck('Dark Grey', '#080808', [10, 10, 10])
await setApperanceAndCheck('Light Grey', '#D3D3D3', [190, 190, 190])
await setApperanceAndCheck('White', '#FFFFFF', [200, 200, 200])
await setApperanceAndCheck(
'Default (clear appearance)',
'default',
initialColor
)
})
})
})

View File

@ -117,7 +117,7 @@ test(
test(
'open a file in a project works and renders, open another file in different project with errors, it should clear the scene',
{ tag: '@electron' },
async ({ context, page, editor }, testInfo) => {
async ({ context, page }, testInfo) => {
await context.folderSetupFn(async (dir) => {
const bracketDir = path.join(dir, 'bracket')
await fsp.mkdir(bracketDir, { recursive: true })
@ -180,11 +180,6 @@ test(
await page.getByText('broken-code').click()
await page.waitForTimeout(2000)
await editor.scrollToText(
"|> line(end = [0, wallMountL], tag = 'outerEdge')"
)
// error in guter
await expect(page.locator('.cm-lint-marker-error')).toBeVisible()
@ -424,7 +419,7 @@ test(
test(
'when code with error first loads you get errors in console',
{ tag: '@electron' },
async ({ context, page, editor }, testInfo) => {
async ({ context, page }, testInfo) => {
await context.folderSetupFn(async (dir) => {
await fsp.mkdir(path.join(dir, 'broken-code'), { recursive: true })
await fsp.copyFile(
@ -434,19 +429,16 @@ test(
})
await page.setBodyDimensions({ width: 1200, height: 500 })
await expect(page.getByText('broken-code')).toBeVisible()
await page.getByText('broken-code').click()
// Gotcha: You can not use scene.waitForExecutionDone() since the KCL code is going to fail
await expect(page.getByTestId('loading')).toBeAttached()
await expect(page.getByTestId('loading')).not.toBeAttached({
timeout: 20_000,
})
// Gotcha: Scroll to the text content in code mirror because CodeMirror lazy loads DOM content
await editor.scrollToText(
"|> line(end = [0, wallMountL], tag = 'outerEdge')"
)
// error in guter
await expect(page.locator('.cm-lint-marker-error')).toBeVisible()

View File

@ -2046,9 +2046,7 @@ profile003 = circle({ center = [6.92, -4.2], radius = 3.16 }, sketch001)
await test.step('add random new var between profiles', async () => {
await page.keyboard.type('myVar = 5')
await page.keyboard.press('Enter')
// If this timeout isn't long enough, the test breaks.
// TODO: fix https://github.com/KittyCAD/modeling-app/issues/5437
await page.waitForTimeout(3_000)
await page.waitForTimeout(600)
})
await sketchIsDrawnProperly()

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 127 KiB

After

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 54 KiB

View File

@ -937,16 +937,11 @@ export async function setupElectron({
testInfo,
cleanProjectDir = true,
appSettings,
viewport,
}: {
testInfo: TestInfo
folderSetupFn?: (projectDirName: string) => Promise<void>
cleanProjectDir?: boolean
appSettings?: Partial<SaveSettingsPayload>
viewport: {
width: number
height: number
}
}): Promise<{
electronApp: ElectronApplication
context: BrowserContext
@ -977,14 +972,6 @@ export async function setupElectron({
...(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.

View File

@ -358,7 +358,9 @@ test.describe('Testing Camera Movement', { tag: ['@skipWin'] }, () => {
exact: true,
})
const userSettingsTab = page.getByRole('radio', { name: 'User' })
const mouseControlsSetting = () => page.locator('#camera-controls').first()
const mouseControlsSetting = page
.locator('#mouseControls')
.getByRole('combobox')
const mouseControlSuccesToast = page.getByText(
'Set mouse controls to "Solidworks"'
)
@ -388,14 +390,7 @@ test.describe('Testing Camera Movement', { tag: ['@skipWin'] }, () => {
await settingsLink.click()
await expect(settingsDialogHeading).toBeVisible()
await userSettingsTab.click()
const setting = mouseControlsSetting()
await expect(setting).toBeAttached()
await setting.scrollIntoViewIfNeeded()
await setting.selectOption({ label: 'Solidworks' })
await expect(setting, 'Setting value did not change').toHaveValue(
'Solidworks',
{ timeout: 120_000 }
)
await mouseControlsSetting.selectOption({ label: 'Solidworks' })
await expect(mouseControlSuccesToast).toBeVisible()
await settingsCloseButton.click()
})

View File

@ -764,15 +764,15 @@ profile003 = startProfileAt([40.16, -120.48], sketch006)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)], tag = $seg02)
|> close()
extrude001 = extrude(sketch001, length = 100)
|> chamfer(
|> chamfer({
length = 30,
tags = [
seg01,
getNextAdjacentEdge(yo),
getNextAdjacentEdge(seg02),
getOppositeEdge(seg01)
],
)
]
}, %)
`)
await expect(
page.getByTestId('model-state-indicator-execution-done')
@ -810,15 +810,15 @@ profile003 = startProfileAt([40.16, -120.48], sketch006)
await checkCodeAtHoverPosition(
'oppositeChamfer',
oppositeChamfer,
`angledLine([segAng(rectangleSegmentA001)-90,217.26],%,$seg01)chamfer(length=30,tags=[seg01,getNextAdjacentEdge(yo),getNextAdjacentEdge(seg02),getOppositeEdge(seg01)],)`,
' )'
`angledLine([segAng(rectangleSegmentA001)-90,217.26],%,$seg01)chamfer({length=30,tags=[seg01,getNextAdjacentEdge(yo),getNextAdjacentEdge(seg02),getOppositeEdge(seg01)]},%)`,
'}, %)'
)
await checkCodeAtHoverPosition(
'baseChamfer',
baseChamfer,
`angledLine([segAng(rectangleSegmentA001)-90,217.26],%,$seg01)chamfer(length=30,tags=[seg01,getNextAdjacentEdge(yo),getNextAdjacentEdge(seg02),getOppositeEdge(seg01)],)`,
' )'
`angledLine([segAng(rectangleSegmentA001)-90,217.26],%,$seg01)chamfer({length=30,tags=[seg01,getNextAdjacentEdge(yo),getNextAdjacentEdge(seg02),getOppositeEdge(seg01)]},%)`,
'}, %)'
)
await u.openAndClearDebugPanel()
@ -848,15 +848,15 @@ profile003 = startProfileAt([40.16, -120.48], sketch006)
await checkCodeAtHoverPosition(
'adjacentChamfer1',
adjacentChamfer1,
`line(endAbsolute=[profileStartX(%),profileStartY(%)],tag=$seg02)chamfer(length=30,tags=[seg01,getNextAdjacentEdge(yo),getNextAdjacentEdge(seg02),getOppositeEdge(seg01)],)`,
' )'
`line(endAbsolute=[profileStartX(%),profileStartY(%)],tag=$seg02)chamfer({length=30,tags=[seg01,getNextAdjacentEdge(yo),getNextAdjacentEdge(seg02),getOppositeEdge(seg01)]},%)`,
'}, %)'
)
await checkCodeAtHoverPosition(
'adjacentChamfer2',
adjacentChamfer2,
`angledLine([segAng(rectangleSegmentA001),-segLen(rectangleSegmentA001)],%,$yo)chamfer(length=30,tags=[seg01,getNextAdjacentEdge(yo),getNextAdjacentEdge(seg02),getOppositeEdge(seg01)],)`,
' )'
`angledLine([segAng(rectangleSegmentA001),-segLen(rectangleSegmentA001)],%,$yo)chamfer({length=30,tags=[seg01,getNextAdjacentEdge(yo),getNextAdjacentEdge(seg02),getOppositeEdge(seg01)]},%)`,
'}, %)'
)
})
test("Extrude button should be disabled if there's no extrudable geometry when nothing is selected", async ({

View File

@ -633,7 +633,6 @@ test.describe('Testing settings', () => {
`Set default unit to "${unitOfMeasure}" as a user default`
)
await expect(toastMessage).toBeVisible()
await expect(toastMessage).not.toBeVisible()
})
}
await changeUnitOfMeasureInUserTab('in')
@ -946,76 +945,4 @@ fn cube`
).toBeVisible()
})
})
/**
* This test assumes that the default value of the "highlight edges" setting is "on".
*/
test(`Toggle stream settings multiple times`, async ({
page,
scene,
homePage,
context,
toolbar,
cmdBar,
}, testInfo) => {
await context.folderSetupFn(async (dir) => {
const projectDir = join(dir, 'project-000')
await fsp.mkdir(projectDir, { recursive: true })
await fsp.copyFile(
executorInputPath('cube.kcl'),
join(projectDir, 'main.kcl')
)
})
await test.step(`First snapshot`, async () => {
await homePage.openProject('project-000')
await toolbar.closePane('code')
await expect(toolbar.startSketchBtn).toBeEnabled({ timeout: 20_000 })
await scene.clickNoWhere()
})
const toast = (value: boolean) =>
page.getByText(
`Set highlight edges to "${String(value)}" as a user default`
)
const initialPath = testInfo.snapshotPath('toggle-settings-initial.png')
const initialScreenshot = await scene.streamWrapper.screenshot({
path: initialPath,
mask: [page.getByTestId('model-state-indicator')],
})
await test.step(`Toggle highlightEdges off`, async () => {
await cmdBar.openCmdBar()
await cmdBar.chooseCommand('Settings · modeling · highlight edges')
await cmdBar.selectOption({ name: 'off' }).click()
const falseToast = toast(false)
await expect(falseToast).toBeVisible()
await falseToast.waitFor({ state: 'detached' })
})
await expect(scene.streamWrapper).not.toHaveScreenshot(
'toggle-settings-initial.png',
{
maxDiffPixels: 15,
mask: [page.getByTestId('model-state-indicator')],
}
)
await test.step(`Toggle highlightEdges on`, async () => {
await cmdBar.openCmdBar()
await cmdBar.chooseCommand('Settings · modeling · highlight edges')
await cmdBar.selectOption({ name: 'on' }).click()
const trueToast = toast(true)
await expect(trueToast).toBeVisible()
await trueToast.waitFor({ state: 'detached' })
})
await expect(scene.streamWrapper).toHaveScreenshot(
'toggle-settings-initial.png',
{
maxDiffPixels: 15,
mask: [page.getByTestId('model-state-indicator')],
}
)
})
})

View File

@ -68,6 +68,13 @@ type PWFunction = (
let firstUrl = ''
// The below error is due to the extreme type spaghetti going on. playwright/
// types/test.d.ts does not export 2 functions (below is one of them) but tsc
// is trying to use a interface name it can't see.
// e2e/playwright/zoo-test.ts:64:14 - error TS4023: Exported variable 'test' has
// or is using name 'TestFunction' from external module
// "/home/lee/Code/Zoo/modeling-app/dirty2/node_modules/playwright/types/test"
// but cannot be named.
export const test = (
desc: string,
objOrFn: PWFunction | TestDetails,

1
exp
View File

@ -1 +0,0 @@
sketch001=startSketchOn('XZ')|>startProfileAt([75.8,317.2],%)//[$startCapTag,$EndCapTag]|>angledLine([0,268.43],%,$rectangleSegmentA001)|>angledLine([segAng(rectangleSegmentA001)-90,217.26],%,$seg01)|>angledLine([segAng(rectangleSegmentA001),-segLen(rectangleSegmentA001)],%,$yo)|>line(endAbsolute=[profileStartX(%),profileStartY(%)],tag=$seg02)|>close()extrude001=extrude(sketch001,length=100)|>chamfer(length=30,tags=[getOppositeEdge(seg01)],tag=$seg03)|>chamfer(length=30,tags=[seg01],tag=$seg04)|>chamfer(length=30,tags=[getNextAdjacentEdge(seg02)],tag=$seg05)|>chamfer(length=30,tags=[getNextAdjacentEdge(yo)],tag=$seg06)sketch004=startSketchOn(extrude001,seg05)profile003=startProfileAt([82.57,322.96],sketch004)|>angledLine([0,11.16],%,$rectangleSegmentA004)|>angledLine([segAng(rectangleSegmentA004)-90,103.07],%)|>angledLine([segAng(rectangleSegmentA004),-segLen(rectangleSegmentA004)],%)|>line(endAbsolute=[profileStartX(%),profileStartY(%)])|>close()sketch003=startSketchOn(extrude001,seg04)profile002=startProfileAt([-209.64,255.28],sketch003)|>angledLine([0,11.56],%,$rectangleSegmentA003)|>angledLine([segAng(rectangleSegmentA003)-90,106.84],%)|>angledLine([segAng(rectangleSegmentA003),-segLen(rectangleSegmentA003)],%)|>line(endAbsolute=[profileStartX(%),profileStartY(%)])|>close()sketch002=startSketchOn(extrude001,seg03)profile001=startProfileAt([205.96,254.59],sketch002)|>angledLine([0,11.39],%,$rectangleSegmentA002)|>angledLine([segAng(rectangleSegmentA002)-90,105.26],%)|>angledLine([segAng(rectangleSegmentA002),-segLen(rectangleSegmentA002)],%)|>line(endAbsolute=[profileStartX(%),profileStartY(%)])|>close()

1
got
View File

@ -1 +0,0 @@
sketch001=startSketchOn('XZ')|>startProfileAt([75.8,317.2],%)//[$startCapTag,$EndCapTag]|>angledLine([0,268.43],%,$rectangleSegmentA001)|>angledLine([segAng(rectangleSegmentA001)-90,217.26],%,$seg01)|>angledLine([segAng(rectangleSegmentA001),-segLen(rectangleSegmentA001)],%,$yo)|>line(endAbsolute=[profileStartX(%),profileStartY(%)],tag=$seg02)|>close()extrude001=extrude(sketch001,length=100)|>chamfer(length=30,tags=[getOppositeEdge(seg01)],tag=$seg03)|>chamfer(length=30,tags=[seg01],tag=$seg04)|>chamfer(length=30,tags=[getNextAdjacentEdge(seg02)],tag=$seg05)|>chamfer(length=30,tags=[getNextAdjacentEdge(yo)],tag=$seg06)sketch005=startSketchOn(extrude001,seg06)profile004=startProfileAt([-23.43,19.69],sketch005)|>angledLine([0,9.1],%,$rectangleSegmentA005)|>angledLine([segAng(rectangleSegmentA005)-90,84.07],%)|>angledLine([segAng(rectangleSegmentA005),-segLen(rectangleSegmentA005)],%)|>line(endAbsolute=[profileStartX(%),profileStartY(%)])|>close()sketch004=startSketchOn(extrude001,seg05)profile003=startProfileAt([82.57,322.96],sketch004)|>angledLine([0,11.16],%,$rectangleSegmentA004)|>angledLine([segAng(rectangleSegmentA004)-90,103.07],%)|>angledLine([segAng(rectangleSegmentA004),-segLen(rectangleSegmentA004)],%)|>line(endAbsolute=[profileStartX(%),profileStartY(%)])|>close()sketch003=startSketchOn(extrude001,seg04)profile002=startProfileAt([-209.64,255.28],sketch003)|>angledLine([0,11.56],%,$rectangleSegmentA003)|>angledLine([segAng(rectangleSegmentA003)-90,106.84],%)|>angledLine([segAng(rectangleSegmentA003),-segLen(rectangleSegmentA003)],%)|>line(endAbsolute=[profileStartX(%),profileStartY(%)])|>close()sketch002=startSketchOn(extrude001,seg03)profile001=startProfileAt([205.96,254.59],sketch002)|>angledLine([0,11.39],%,$rectangleSegmentA002)|>angledLine([segAng(rectangleSegmentA002)-90,105.26],%)|>angledLine([segAng(rectangleSegmentA002),-segLen(rectangleSegmentA002)],%)|>line(endAbsolute=[profileStartX(%),profileStartY(%)])|>close()

View File

@ -8,13 +8,13 @@
"email": "info@zoo.dev",
"url": "https://zoo.dev"
},
"description": "Zoo Modeling App",
"description": "Edit CAD visually or with code",
"main": ".vite/build/main.js",
"license": "MIT",
"dependencies": {
"@codemirror/autocomplete": "^6.17.0",
"@codemirror/commands": "^6.6.0",
"@codemirror/language": "^6.10.8",
"@codemirror/language": "^6.10.3",
"@codemirror/lint": "^6.8.4",
"@codemirror/search": "^6.5.6",
"@codemirror/state": "^6.4.1",
@ -40,7 +40,7 @@
"codemirror": "^6.0.1",
"decamelize": "^6.0.0",
"diff": "^7.0.0",
"electron-updater": "^6.6.0",
"electron-updater": "^6.5.0",
"fuse.js": "^7.0.0",
"html2canvas-pro": "^1.5.8",
"isomorphic-fetch": "^3.0.0",
@ -91,7 +91,7 @@
"fmt-check": "prettier --check ./src *.ts *.json *.js ./e2e ./packages",
"fetch:wasm": "./scripts/get-latest-wasm-bundle.sh",
"fetch:wasm:windows": "./scripts/get-latest-wasm-bundle.ps1",
"fetch:samples": "echo \"Fetching latest KCL samples...\" && curl -o public/kcl-samples-manifest-fallback.json https://raw.githubusercontent.com/KittyCAD/next/manifest.json",
"fetch:samples": "echo \"Fetching latest KCL samples...\" && curl -o public/kcl-samples-manifest-fallback.json https://raw.githubusercontent.com/KittyCAD/kcl-samples/next/manifest.json",
"build:wasm-dev": "yarn wasm-prep && (cd src/wasm-lib && wasm-pack build --dev --target web --out-dir pkg && cargo test -p kcl-lib export_bindings) && yarn isomorphic-copy-wasm && yarn fmt",
"build:wasm:nocopy": "yarn wasm-prep && cd src/wasm-lib && wasm-pack build --release --target web --out-dir pkg && cargo test -p kcl-lib export_bindings",
"build:wasm": "yarn build:wasm:nocopy && cp src/wasm-lib/pkg/wasm_lib_bg.wasm public && yarn fmt",
@ -206,7 +206,7 @@
"postinstall-postinstall": "^2.1.0",
"prettier": "^2.8.8",
"setimmediate": "^1.0.5",
"tailwindcss": "^3.4.17",
"tailwindcss": "^3.4.1",
"ts-node": "^10.0.0",
"typescript": "^5.7.3",
"typescript-eslint": "^8.23.0",

View File

@ -6,12 +6,14 @@ import { useHotkeys } from 'react-hotkeys-hook'
import { useLoaderData, useNavigate } from 'react-router-dom'
import { type IndexLoaderData } from 'lib/types'
import { PATHS } from 'lib/paths'
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
import { onboardingPaths } from 'routes/Onboarding/paths'
import { useEngineConnectionSubscriptions } from 'hooks/useEngineConnectionSubscriptions'
import { codeManager, engineCommandManager } from 'lib/singletons'
import { useAbsoluteFilePath } from 'hooks/useAbsoluteFilePath'
import { isDesktop } from 'lib/isDesktop'
import { useLspContext } from 'components/LspProvider'
import { useRefreshSettings } from 'hooks/useRefreshSettings'
import { ModelingSidebar } from 'components/ModelingSidebar/ModelingSidebar'
import { LowerRightControls } from 'components/LowerRightControls'
import ModalContainer from 'react-modal-promise'
@ -28,7 +30,6 @@ import { useRouteLoaderData } from 'react-router-dom'
import { useEngineCommands } from 'components/EngineCommands'
import { commandBarActor } from 'machines/commandBarMachine'
import { useToken } from 'machines/appMachine'
import { useSettings } from 'machines/appMachine'
maybeWriteToDisk()
.then(() => {})
.catch(() => {})
@ -48,6 +49,7 @@ export function App() {
})
})
useRefreshSettings(PATHS.FILE + 'SETTINGS')
const navigate = useNavigate()
const filePath = useAbsoluteFilePath()
const { onProjectOpen } = useLspContext()
@ -69,7 +71,7 @@ export function App() {
useHotKeyListener()
const settings = useSettings()
const { settings } = useSettingsAuthContext()
const token = useToken()
const coreDumpManager = useMemo(
@ -79,7 +81,7 @@ export function App() {
const {
app: { onboardingStatus },
} = settings
} = settings.context
useHotkeys('backspace', (e) => {
e.preventDefault()

View File

@ -28,8 +28,10 @@ import {
fileLoader,
homeLoader,
onboardingRedirectLoader,
settingsLoader,
telemetryLoader,
} from 'lib/routeLoaders'
import SettingsAuthProvider from 'components/SettingsAuthProvider'
import LspProvider from 'components/LspProvider'
import { KclContextProvider } from 'lang/KclProvider'
import { ASK_TO_OPEN_QUERY_PARAM, BROWSER_PROJECT_NAME } from 'lib/constants'
@ -43,28 +45,34 @@ import { AppStateProvider } from 'AppState'
import { reportRejection } from 'lib/trap'
import { RouteProvider } from 'components/RouteProvider'
import { ProjectsContextProvider } from 'components/ProjectsContextProvider'
import { useToken } from 'machines/appMachine'
import { OpenInDesktopAppHandler } from 'components/OpenInDesktopAppHandler'
import { useToken } from 'machines/appMachine'
const createRouter = isDesktop() ? createHashRouter : createBrowserRouter
const router = createRouter([
{
loader: settingsLoader,
id: PATHS.INDEX,
// TODO: Re-evaluate if this is true
/* Make sure auth is the outermost provider or else we will have
* inefficient re-renders, use the react profiler to see. */
element: (
<OpenInDesktopAppHandler>
<RouteProvider>
<LspProvider>
<ProjectsContextProvider>
<KclContextProvider>
<AppStateProvider>
<MachineManagerProvider>
<Outlet />
</MachineManagerProvider>
</AppStateProvider>
</KclContextProvider>
</ProjectsContextProvider>
</LspProvider>
<SettingsAuthProvider>
<LspProvider>
<ProjectsContextProvider>
<KclContextProvider>
<AppStateProvider>
<MachineManagerProvider>
<Outlet />
</MachineManagerProvider>
</AppStateProvider>
</KclContextProvider>
</ProjectsContextProvider>
</LspProvider>
</SettingsAuthProvider>
</RouteProvider>
</OpenInDesktopAppHandler>
),
@ -112,6 +120,7 @@ const router = createRouter([
children: [
{
id: PATHS.FILE + 'SETTINGS',
loader: settingsLoader,
children: [
{
loader: onboardingRedirectLoader,
@ -157,9 +166,11 @@ const router = createRouter([
index: true,
element: <></>,
id: PATHS.HOME + 'SETTINGS',
loader: settingsLoader,
},
{
path: makeUrlPathRelative(PATHS.SETTINGS),
loader: settingsLoader,
element: <Settings />,
},
{

View File

@ -2,6 +2,7 @@ import { useRef, useEffect, useState, useMemo, Fragment } from 'react'
import { useModelingContext } from 'hooks/useModelingContext'
import { cameraMouseDragGuards } from 'lib/cameraControls'
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
import { ARROWHEAD, DEBUG_SHOW_BOTH_SCENES } from './sceneInfra'
import { ReactCameraProperties } from './CameraControls'
import { throttle, toSync } from 'lib/utils'
@ -47,7 +48,6 @@ import { ActionButton } from 'components/ActionButton'
import { err, reportRejection, trap } from 'lib/trap'
import { Node } from 'wasm-lib/kcl/bindings/Node'
import { commandBarActor } from 'machines/commandBarMachine'
import { useSettings } from 'machines/appMachine'
function useShouldHideScene(): { hideClient: boolean; hideServer: boolean } {
const [isCamMoving, setIsCamMoving] = useState(false)
@ -76,8 +76,8 @@ export const ClientSideScene = ({
cameraControls,
}: {
cameraControls: ReturnType<
typeof useSettings
>['modeling']['mouseControls']['current']
typeof useSettingsAuthContext
>['settings']['context']['modeling']['mouseControls']['current']
}) => {
const canvasRef = useRef<HTMLDivElement>(null)
const { state, send, context } = useModelingContext()
@ -179,7 +179,10 @@ const Overlays = () => {
// Set a large zIndex, the overlay for hover dropdown menu on line segments needs to render
// over the length labels on the line segments
return (
<div className="absolute inset-0 pointer-events-none z-sketchOverlayDropdown">
<div
className="absolute inset-0 pointer-events-none"
style={{ zIndex: '99999999' }}
>
{Object.entries(context.segmentOverlays)
.flatMap((a) =>
a[1].map((b) => ({ pathToNodeString: a[0], overlay: b }))

View File

@ -291,7 +291,6 @@ export class SceneInfra {
this.labelRenderer.domElement.style.position = 'absolute'
this.labelRenderer.domElement.style.top = '0px'
this.labelRenderer.domElement.style.pointerEvents = 'none'
this.labelRenderer.domElement.className = 'z-sketchSegmentIndicators'
window.addEventListener('resize', this.onWindowResize)
this.camControls = new CameraControls(

View File

@ -1,82 +0,0 @@
import { getPathOrUrlFromArgs, parseCLIArgs } from 'commandLineArgs'
const linuxDeepLinkArgv = [
'/tmp/.mount_Zoo Movq3t0x/zoo-modeling-app',
'--no-sandbox',
'--allow-file-access-from-files',
'zoo-studio://?create-file=true&name=deeplinks&code=cGxhbmUwMDEgPSBvZmZzZXRQbGFuZSgnWFonLCBvZmZzZXQgPSA1KQ%3D%3D',
]
const linuxNoPathArgv = [
'/tmp/.mount_Zoo MogQS2hd/zoo-modeling-app',
'--no-sandbox',
'--allow-file-access-from-files',
]
const linuxPathArgv = [
'/tmp/.mount_Zoo MogQS2hd/zoo-modeling-app',
'--no-sandbox',
'--allow-file-access-from-files',
'/home/pierremtb/Documents/zoo-modeling-app-projects/project-001/main.kcl',
]
const winDeepLinkArgv = [
'C:\\Program Files\\Zoo Modeling App\\Zoo Modeling App.exe',
'--allow-file-access-from-files',
'zoo-studio:///?create-file=true&name=deeplinkscopy&code=cGxhbmUwMDEgPSBvZmZzZXRQbGFuZSgnWFonLCBvZmZzZXQgPSA1KQo%3D',
]
const winNoPathArgv = [
'C:\\Program Files\\Zoo Modeling App\\Zoo Modeling App.exe',
'--allow-file-access-from-files',
]
const winPathArgv = [
'C:\\Program Files\\Zoo Modeling App\\Zoo Modeling App.exe',
'--allow-file-access-from-files',
'C:\\Users\\pierr\\Documents\\zoo-modeling-app-projects\\deeplink\\main.kcl',
]
// macos doesn't uses the open-url scheme so is different so no macDeepLinkArgv
const macNoPathArgv = [
'/Applications/Zoo Modeling App.app/Contents/MacOS/Zoo Modeling App',
]
const macPathArgv = [
'/Applications/Zoo Modeling App.app/Contents/MacOS/Zoo Modeling App',
'/Users/pierremtb/Documents/zoo-modeling-app-projects/loft/main.kcl',
]
describe('getPathOrUrlFromArgs', () => {
;[
['linux', linuxDeepLinkArgv],
['windows', winDeepLinkArgv],
// macos doesn't uses the open-url scheme so is different
].map(([os, argv]) => {
it(`should parse second-instance deep link argv on ${os}`, () => {
const args = parseCLIArgs(argv as string[])
expect(getPathOrUrlFromArgs(args)).toContain('zoo-studio://')
})
})
;[
['linux', linuxPathArgv],
['windows', winPathArgv],
['mac', macPathArgv],
].map(([os, argv]) => {
it(`should parse path argv on ${os}`, () => {
const args = parseCLIArgs(argv as string[])
expect(getPathOrUrlFromArgs(args)).toContain('main.kcl')
})
})
;[
['linux', linuxNoPathArgv],
['windows', winNoPathArgv],
['mac', macNoPathArgv],
].map(([os, argv]) => {
it(`should return undefined without path argv on ${os}`, () => {
const args = parseCLIArgs(argv as string[])
expect(getPathOrUrlFromArgs(args)).toBeUndefined()
})
})
})

View File

@ -1,8 +1,7 @@
import minimist from 'minimist'
import yargs from 'yargs'
import { hideBin } from 'yargs/helpers'
export const argvFromYargs = yargs(hideBin(process.argv))
const argv = yargs(hideBin(process.argv))
.option('telemetry', {
alias: 't',
type: 'boolean',
@ -10,20 +9,4 @@ export const argvFromYargs = yargs(hideBin(process.argv))
})
.parse()
// TODO: find a better way to merge minimist and yargs parsers.
export function parseCLIArgs(argv: string[]): minimist.ParsedArgs {
return minimist(argv, {
// Treat all double-hyphenated arguments without equal signs as boolean
boolean: true,
})
}
export function getPathOrUrlFromArgs(
args: minimist.ParsedArgs
): string | undefined {
if (args._.length > 1) {
return args._[1]
}
return undefined
}
export default argv

View File

@ -1,22 +1,24 @@
import { Switch } from '@headlessui/react'
import { settingsActor, useSettings } from 'machines/appMachine'
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
import { useEffect, useState } from 'react'
export function CameraProjectionToggle() {
const settings = useSettings()
const { settings } = useSettingsAuthContext()
const isCameraProjectionPerspective =
settings.modeling.cameraProjection.current === 'perspective'
settings.context.modeling.cameraProjection.current === 'perspective'
const [checked, setChecked] = useState(isCameraProjectionPerspective)
useEffect(() => {
setChecked(settings.modeling.cameraProjection.current === 'perspective')
}, [settings.modeling.cameraProjection.current])
setChecked(
settings.context.modeling.cameraProjection.current === 'perspective'
)
}, [settings.context.modeling.cameraProjection.current])
return (
<Switch
checked={checked}
onChange={(newValue) => {
settingsActor.send({
settings.send({
type: 'set.modeling.cameraProjection',
data: {
level: 'user',

View File

@ -7,6 +7,7 @@ import {
} from '@codemirror/autocomplete'
import { EditorView, keymap, ViewUpdate } from '@codemirror/view'
import { CustomIcon } from 'components/CustomIcon'
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
import { CommandArgument, KclCommandValue } from 'lib/commandTypes'
import { getSystemTheme } from 'lib/theme'
import { useCalculateKclExpression } from 'lib/useCalculateKclExpression'
@ -19,7 +20,6 @@ import { createIdentifier, createVariableDeclaration } from 'lang/modifyAst'
import { useCodeMirror } from 'components/ModelingSidebar/ModelingPanes/CodeEditor'
import { useSelector } from '@xstate/react'
import { commandBarActor, useCommandBarState } from 'machines/commandBarMachine'
import { useSettings } from 'machines/appMachine'
import toast from 'react-hot-toast'
const machineContextSelector = (snapshot?: {
@ -42,7 +42,7 @@ function CommandBarKclInput({
const previouslySetValue = commandBarState.context.argumentsToSubmit[
arg.name
] as KclCommandValue | undefined
const settings = useSettings()
const { settings } = useSettingsAuthContext()
const argMachineContext = useSelector(
arg.machineActor,
machineContextSelector
@ -117,9 +117,9 @@ function CommandBarKclInput({
: defaultValue.length,
},
theme:
settings.app.theme.current === 'system'
settings.context.app.theme.current === 'system'
? getSystemTheme()
: settings.app.theme.current,
: settings.context.app.theme.current,
extensions: [
varMentionsExtension,
EditorView.updateListener.of((vu: ViewUpdate) => {

View File

@ -1,16 +1,16 @@
import { Dialog } from '@headlessui/react'
import { ActionButton } from './ActionButton'
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
import { useState } from 'react'
import { useSearchParams } from 'react-router-dom'
import { CREATE_FILE_URL_PARAM } from 'lib/constants'
import { useSettings } from 'machines/appMachine'
const DownloadAppBanner = () => {
const [searchParams] = useSearchParams()
const hasCreateFileParam = searchParams.has(CREATE_FILE_URL_PARAM)
const settings = useSettings()
const { settings } = useSettingsAuthContext()
const [isBannerDismissed, setIsBannerDismissed] = useState(
settings.app.dismissWebBanner.current
settings.context.app.dismissWebBanner.current || hasCreateFileParam
)
return (

View File

@ -1,7 +1,7 @@
import { useMachine } from '@xstate/react'
import { useLocation, useNavigate, useRouteLoaderData } from 'react-router-dom'
import { useNavigate, useRouteLoaderData } from 'react-router-dom'
import { type IndexLoaderData } from 'lib/types'
import { BROWSER_PATH, PATHS } from 'lib/paths'
import { PATHS } from 'lib/paths'
import React, { createContext, useEffect, useMemo } from 'react'
import { toast } from 'react-hot-toast'
import {
@ -27,10 +27,9 @@ import {
getKclSamplesManifest,
KclSamplesManifestItem,
} from 'lib/getKclSamplesManifest'
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
import { markOnce } from 'lib/performance'
import { commandBarActor } from 'machines/commandBarMachine'
import { settingsActor, useSettings } from 'machines/appMachine'
import { createRouteCommands } from 'lib/commandBarConfigs/routeCommandConfig'
import { useToken } from 'machines/appMachine'
type MachineContext<T extends AnyStateMachine> = {
@ -49,51 +48,14 @@ export const FileMachineProvider = ({
children: React.ReactNode
}) => {
const navigate = useNavigate()
const location = useLocation()
const { settings } = useSettingsAuthContext()
const token = useToken()
const settings = useSettings()
const projectData = useRouteLoaderData(PATHS.FILE) as IndexLoaderData
const { project, file } = projectData
const [kclSamples, setKclSamples] = React.useState<KclSamplesManifestItem[]>(
[]
)
// Due to the route provider, i've moved this to the FileMachineProvider instead of CommandBarProvider
// This will register the commands to route to Telemetry, Home, and Settings.
useEffect(() => {
const filePath =
PATHS.FILE + '/' + encodeURIComponent(file?.path || BROWSER_PATH)
const { RouteTelemetryCommand, RouteHomeCommand, RouteSettingsCommand } =
createRouteCommands(navigate, location, filePath)
commandBarActor.send({
type: 'Remove commands',
data: {
commands: [
RouteTelemetryCommand,
RouteHomeCommand,
RouteSettingsCommand,
],
},
})
if (location.pathname === PATHS.HOME) {
commandBarActor.send({
type: 'Add commands',
data: { commands: [RouteTelemetryCommand, RouteSettingsCommand] },
})
} else if (location.pathname.includes(PATHS.FILE)) {
commandBarActor.send({
type: 'Add commands',
data: {
commands: [
RouteTelemetryCommand,
RouteSettingsCommand,
RouteHomeCommand,
],
},
})
}
}, [location])
useEffect(() => {
markOnce('code/didLoadFile')
async function fetchKclSamples() {
@ -361,7 +323,7 @@ export const FileMachineProvider = ({
authToken: token ?? '',
projectData,
settings: {
defaultUnit: settings.modeling.defaultUnit.current ?? 'mm',
defaultUnit: settings?.context?.modeling.defaultUnit.current ?? 'mm',
},
specialPropsForSampleCommand: {
onSubmit: async (data) => {
@ -383,7 +345,7 @@ export const FileMachineProvider = ({
// Either way, we want to overwrite the defaultUnit project setting
// with the sample's setting.
if (data.sampleUnits) {
settingsActor.send({
settings.send({
type: 'set.modeling.defaultUnit',
data: {
level: 'project',

View File

@ -1,5 +1,6 @@
import { Popover } from '@headlessui/react'
import Tooltip from './Tooltip'
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
import { CustomIcon } from './CustomIcon'
import { useLocation, useNavigate } from 'react-router-dom'
import { PATHS } from 'lib/paths'
@ -8,7 +9,6 @@ import { useAbsoluteFilePath } from 'hooks/useAbsoluteFilePath'
import { useLspContext } from './LspProvider'
import { openExternalBrowserIfDesktop } from 'lib/openWindow'
import { reportRejection } from 'lib/trap'
import { settingsActor } from 'machines/appMachine'
const HelpMenuDivider = () => (
<div className="h-[1px] bg-chalkboard-110 dark:bg-chalkboard-80" />
@ -20,6 +20,7 @@ export function HelpMenu(props: React.PropsWithChildren) {
const filePath = useAbsoluteFilePath()
const isInProject = location.pathname.includes(PATHS.FILE)
const navigate = useNavigate()
const { settings } = useSettingsAuthContext()
return (
<Popover className="relative">
@ -105,7 +106,7 @@ export function HelpMenu(props: React.PropsWithChildren) {
<HelpMenuItem
as="button"
onClick={() => {
settingsActor.send({
settings.send({
type: 'set.app.onboardingStatus',
data: {
value: '',

View File

@ -10,6 +10,7 @@ import {
import { TEST, VITE_KC_API_BASE_URL } from 'env'
import { kcl } from 'editor/plugins/lsp/kcl/language'
import { copilotPlugin } from 'editor/plugins/lsp/copilot'
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
import { Extension } from '@codemirror/state'
import { LanguageSupport } from '@codemirror/language'
import { useNavigate } from 'react-router-dom'

View File

@ -22,6 +22,7 @@ import {
modelingMachineDefaultContext,
} from 'machines/modelingMachine'
import { useSetupEngineManager } from 'hooks/useSetupEngineManager'
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
import {
isCursorInSketchCommandRange,
updateSketchDetailsNodePaths,
@ -109,7 +110,6 @@ import { kclEditorActor } from 'machines/kclEditorMachine'
import { commandBarActor } from 'machines/commandBarMachine'
import { useToken } from 'machines/appMachine'
import { getNodePathFromSourceRange } from 'lang/queryAstNodePathUtils'
import { useSettings } from 'machines/appMachine'
type MachineContext<T extends AnyStateMachine> = {
state: StateFrom<T>
@ -131,15 +131,19 @@ export const ModelingMachineProvider = ({
children: React.ReactNode
}) => {
const {
app: { theme, enableSSAO, allowOrbitInSketchMode },
modeling: {
defaultUnit,
cameraProjection,
highlightEdges,
showScaleGrid,
cameraOrbit,
settings: {
context: {
app: { theme, enableSSAO, allowOrbitInSketchMode },
modeling: {
defaultUnit,
cameraProjection,
highlightEdges,
showScaleGrid,
cameraOrbit,
},
},
},
} = useSettings()
} = useSettingsAuthContext()
const previousAllowOrbitInSketchMode = useRef(allowOrbitInSketchMode.current)
const navigate = useNavigate()
const { context, send: fileMachineSend } = useFileContext()
@ -796,18 +800,11 @@ export const ModelingMachineProvider = ({
}),
'animate-to-sketch': fromPromise(
async ({ input: { selectionRanges } }) => {
const artifact = selectionRanges.graphSelections[0].artifact
const plane = getPlaneFromArtifact(
artifact,
selectionRanges.graphSelections[0].artifact,
engineCommandManager.artifactGraph
)
if (err(plane)) return Promise.reject(plane)
// if the user selected a segment, make sure we enter the right sketch as there can be multiple on a plan
// but still works if the user selected a plane/face by defaulting to the first path
const mainPath =
artifact?.type === 'segment' || artifact?.type === 'solid2d'
? artifact?.pathId
: plane?.pathIds[0]
let sketch: KclValue | null = null
for (const variable of Object.values(
kclManager.execState.variables
@ -815,7 +812,7 @@ export const ModelingMachineProvider = ({
// find programMemory that matches path artifact
if (
variable?.type === 'Sketch' &&
variable.value.artifactId === mainPath
variable.value.artifactId === plane.pathIds[0]
) {
sketch = variable
break
@ -824,7 +821,7 @@ export const ModelingMachineProvider = ({
// if the variable is an sweep, check if the underlying sketch matches the artifact
variable?.type === 'Solid' &&
variable.value.sketch.on.type === 'plane' &&
variable.value.sketch.artifactId === mainPath
variable.value.sketch.artifactId === plane.pathIds[0]
) {
sketch = {
type: 'Sketch',
@ -844,8 +841,9 @@ export const ModelingMachineProvider = ({
info?.sketchDetails?.faceId || ''
)
const sketchArtifact =
engineCommandManager.artifactGraph.get(mainPath)
const sketchArtifact = engineCommandManager.artifactGraph.get(
plane.pathIds[0]
)
if (sketchArtifact?.type !== 'path')
return Promise.reject(new Error('No sketch artifact'))
const sketchPaths = getPathsFromArtifact({

View File

@ -1,12 +1,12 @@
import { ReactNode } from 'react'
import styles from './ModelingPane.module.css'
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
import { ActionButton } from 'components/ActionButton'
import Tooltip from 'components/Tooltip'
import { CustomIconName } from 'components/CustomIcon'
import { IconDefinition } from '@fortawesome/free-solid-svg-icons'
import { ActionIcon } from 'components/ActionIcon'
import { onboardingPaths } from 'routes/Onboarding/paths'
import { useSettings } from 'machines/appMachine'
export interface ModelingPaneProps {
id: string
@ -68,8 +68,8 @@ export const ModelingPane = ({
title,
...props
}: ModelingPaneProps) => {
const settings = useSettings()
const onboardingStatus = settings.app.onboardingStatus
const { settings } = useSettingsAuthContext()
const onboardingStatus = settings.context.app.onboardingStatus
const pointerEventsCssClass =
onboardingStatus.current === onboardingPaths.CAMERA
? 'pointer-events-none '

View File

@ -324,18 +324,6 @@ const OperationItem = (props: {
}
}
function enterAppearanceFlow() {
if (props.item.type === 'StdLibCall') {
props.send({
type: 'enterAppearanceFlow',
data: {
targetSourceRange: sourceRangeFromRust(props.item.sourceRange),
currentOperation: props.item,
},
})
}
}
function deleteOperation() {
if (
props.item.type === 'StdLibCall' ||
@ -392,13 +380,6 @@ const OperationItem = (props: {
: []),
...(props.item.type === 'StdLibCall'
? [
<ContextMenuItem
disabled={!stdLibMap[props.item.name]?.supportsAppearance}
onClick={enterAppearanceFlow}
data-testid="context-menu-set-appearance"
>
Set appearance
</ContextMenuItem>,
<ContextMenuItem
disabled={!stdLibMap[props.item.name]?.prepareToEdit}
onClick={enterEditFlow}

View File

@ -1,4 +1,5 @@
import { TEST } from 'env'
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
import { Themes, getSystemTheme } from 'lib/theme'
import { useEffect, useMemo, useRef } from 'react'
import { highlightSelectionMatches, searchKeymap } from '@codemirror/search'
@ -50,7 +51,6 @@ import {
} from 'machines/kclEditorMachine'
import { useSelector } from '@xstate/react'
import { modelingMachineEvent } from 'editor/manager'
import { useSettings } from 'machines/appMachine'
export const editorShortcutMeta = {
formatCode: {
@ -63,7 +63,9 @@ export const editorShortcutMeta = {
}
export const KclEditorPane = () => {
const context = useSettings()
const {
settings: { context },
} = useSettingsAuthContext()
const lastSelectionEvent = useSelector(kclEditorActor, selectionEventSelector)
const editorIsMounted = useSelector(kclEditorActor, editorIsMountedSelector)
const theme =

View File

@ -33,7 +33,7 @@ describe('processMemory', () => {
const output = processMemory(execState.variables)
expect(output.myVar).toEqual(5)
expect(output.otherVar).toEqual(3)
expect(output.myFn).toEqual('__function__')
expect(output.myFn).toEqual('__function(a)__')
expect(output.theExtrude).toEqual([
{
type: 'extrudePlane',

View File

@ -107,7 +107,9 @@ export const processMemory = (variables: VariableMap) => {
}
//@ts-ignore
} else if (val.type === 'Function') {
processedMemory[key] = `__function__`
processedMemory[key] = `__function(${(val as any)?.expression?.params
?.map?.(({ identifier }: any) => identifier?.name || '')
.join(', ')})__`
}
}
return processedMemory

View File

@ -1,3 +1,4 @@
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
import { Resizable } from 're-resizable'
import {
MouseEventHandler,
@ -20,7 +21,6 @@ import { MachineManagerContext } from 'components/MachineManagerProvider'
import { onboardingPaths } from 'routes/Onboarding/paths'
import { SIDEBAR_BUTTON_SUFFIX } from 'lib/constants'
import { commandBarActor } from 'machines/commandBarMachine'
import { useSettings } from 'machines/appMachine'
interface ModelingSidebarProps {
paneOpacity: '' | 'opacity-20' | 'opacity-40'
@ -38,23 +38,23 @@ function getPlatformString(): 'web' | 'desktop' {
export function ModelingSidebar({ paneOpacity }: ModelingSidebarProps) {
const machineManager = useContext(MachineManagerContext)
const kclContext = useKclContext()
const settings = useSettings()
const onboardingStatus = settings.app.onboardingStatus
const { settings } = useSettingsAuthContext()
const onboardingStatus = settings.context.app.onboardingStatus
const { send, context } = useModelingContext()
const pointerEventsCssClass =
onboardingStatus.current === onboardingPaths.CAMERA ||
context.store?.openPanes.length === 0
? 'pointer-events-none '
: 'pointer-events-auto '
const showDebugPanel = settings.modeling.showDebugPanel
const showDebugPanel = settings.context.modeling.showDebugPanel
const paneCallbackProps = useMemo(
() => ({
kclContext,
settings,
settings: settings.context,
platform: getPlatformString(),
}),
[kclContext.diagnostics, settings]
[kclContext.diagnostics, settings.context]
)
const sidebarActions: SidebarAction[] = [
@ -144,7 +144,7 @@ export function ModelingSidebar({ paneOpacity }: ModelingSidebarProps) {
},
})
}
}, [settings.modeling.showDebugPanel])
}, [settings.context])
const togglePane = useCallback(
(newPane: SidebarType) => {

View File

@ -1,5 +1,6 @@
import { fireEvent, render, screen } from '@testing-library/react'
import { BrowserRouter } from 'react-router-dom'
import { SettingsAuthProviderJest } from './SettingsAuthProvider'
import {
NETWORK_HEALTH_TEXT,
NetworkHealthIndicator,
@ -8,7 +9,11 @@ import { NetworkHealthState } from 'hooks/useNetworkStatus'
function TestWrap({ children }: { children: React.ReactNode }) {
// wrap in router and xState context
return <BrowserRouter>{children}</BrowserRouter>
return (
<BrowserRouter>
<SettingsAuthProviderJest>{children}</SettingsAuthProviderJest>
</BrowserRouter>
)
}
// Our Playwright tests for this are much more comprehensive.

View File

@ -1,6 +1,7 @@
import { render, screen } from '@testing-library/react'
import { BrowserRouter } from 'react-router-dom'
import ProjectSidebarMenu from './ProjectSidebarMenu'
import { SettingsAuthProviderJest } from './SettingsAuthProvider'
import { Project } from 'lib/project'
const now = new Date()
@ -31,7 +32,9 @@ describe('ProjectSidebarMenu tests', () => {
test('Disables popover menu by default', () => {
render(
<BrowserRouter>
<ProjectSidebarMenu project={projectWellFormed} />
<SettingsAuthProviderJest>
<ProjectSidebarMenu project={projectWellFormed} />
</SettingsAuthProviderJest>
</BrowserRouter>
)

View File

@ -18,6 +18,7 @@ import { SnapshotFrom } from 'xstate'
import { commandBarActor } from 'machines/commandBarMachine'
import { useSelector } from '@xstate/react'
import { copyFileShareLink } from 'lib/links'
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
import { useToken } from 'machines/appMachine'
const ProjectSidebarMenu = ({
@ -102,6 +103,7 @@ function ProjectMenuPopover({
const location = useLocation()
const navigate = useNavigate()
const filePath = useAbsoluteFilePath()
useSettingsAuthContext()
const token = useToken()
const machineManager = useContext(MachineManagerContext)
const commands = useSelector(commandBarActor, commandsSelector)

View File

@ -20,11 +20,11 @@ import {
getUniqueProjectName,
getNextFileName,
} from 'lib/desktopFS'
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
import useStateMachineCommands from 'hooks/useStateMachineCommands'
import { projectsCommandBarConfig } from 'lib/commandBarConfigs/projectsCommandConfig'
import { isDesktop } from 'lib/isDesktop'
import { commandBarActor } from 'machines/commandBarMachine'
import { useSettings } from 'machines/appMachine'
import {
CREATE_FILE_URL_PARAM,
FILE_EXT,
@ -77,7 +77,9 @@ const ProjectsContextWeb = ({ children }: { children: React.ReactNode }) => {
searchParams.delete('units')
setSearchParams(searchParams)
}, [searchParams, setSearchParams])
const settings = useSettings()
const {
settings: { context: settings },
} = useSettingsAuthContext()
const [state, send, actor] = useMachine(
projectsMachine.provide({
@ -181,7 +183,9 @@ const ProjectsContextDesktop = ({
setSearchParams(searchParams)
}, [searchParams, setSearchParams])
const { onProjectOpen } = useLspContext()
const settings = useSettings()
const {
settings: { context: settings },
} = useSettingsAuthContext()
const [projectsLoaderTrigger, setProjectsLoaderTrigger] = useState(0)
const { projectPaths, projectsDir } = useProjectsLoader([

View File

@ -5,6 +5,7 @@ import { codeManager, engineCommandManager } from 'lib/singletons'
import React, { useMemo } from 'react'
import toast from 'react-hot-toast'
import Tooltip from './Tooltip'
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
import { reportRejection } from 'lib/trap'
import { toSync } from 'lib/utils'
import { useToken } from 'machines/appMachine'

View File

@ -1,36 +1,17 @@
import { useEffect, useState, createContext, ReactNode } from 'react'
import {
useNavigation,
useLocation,
useNavigate,
useRouteLoaderData,
} from 'react-router-dom'
import { useNavigation, useLocation } from 'react-router-dom'
import { PATHS } from 'lib/paths'
import { markOnce } from 'lib/performance'
import { useAuthNavigation } from 'hooks/useAuthNavigation'
import { useAuthState } from 'machines/appMachine'
import { IndexLoaderData } from 'lib/types'
import { getAppSettingsFilePath } from 'lib/desktop'
import { isDesktop } from 'lib/isDesktop'
import { trap } from 'lib/trap'
import { useFileSystemWatcher } from 'hooks/useFileSystemWatcher'
import { loadAndValidateSettings } from 'lib/settings/settingsUtils'
import { settingsActor } from 'machines/appMachine'
export const RouteProviderContext = createContext({})
export function RouteProvider({ children }: { children: ReactNode }) {
useAuthNavigation()
const loadedProject = useRouteLoaderData(PATHS.FILE) as IndexLoaderData
const [first, setFirstState] = useState(true)
const [settingsPath, setSettingsPath] = useState<string | undefined>(
undefined
)
const navigation = useNavigation()
const navigate = useNavigate()
const location = useLocation()
const authState = useAuthState()
useEffect(() => {
// On initialization, the react-router-dom does not send a 'loading' state event.
// it sends an idle event first.
@ -47,41 +28,6 @@ export function RouteProvider({ children }: { children: ReactNode }) {
setFirstState(false)
}, [navigation])
useEffect(() => {
if (!isDesktop()) return
getAppSettingsFilePath().then(setSettingsPath).catch(trap)
}, [])
useFileSystemWatcher(
async (eventType: string) => {
// If there is a projectPath but it no longer exists it means
// it was exterally removed. If we let the code past this condition
// execute it will recreate the directory due to code in
// loadAndValidateSettings trying to recreate files. I do not
// wish to change the behavior in case anything else uses it.
// Go home.
if (loadedProject?.project?.path) {
if (!window.electron.exists(loadedProject?.project?.path)) {
navigate(PATHS.HOME)
return
}
}
// Only reload if there are changes. Ignore everything else.
if (eventType !== 'change') return
const data = await loadAndValidateSettings(loadedProject?.project?.path)
settingsActor.send({
type: 'Set all settings',
settings: data.settings,
doNotPersist: true,
})
},
[settingsPath, loadedProject?.project?.path].filter(
(x: string | undefined) => x !== undefined
)
)
return (
<RouteProviderContext.Provider value={{}}>
{children}

View File

@ -1,4 +1,5 @@
import decamelize from 'decamelize'
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
import { Setting } from 'lib/settings/initialSettings'
import { SetEventTypes, SettingsLevel } from 'lib/settings/settingsTypes'
import {
@ -24,8 +25,6 @@ import { useLspContext } from 'components/LspProvider'
import { toSync } from 'lib/utils'
import { reportRejection } from 'lib/trap'
import { openExternalBrowserIfDesktop } from 'lib/openWindow'
import { settingsActor, useSettings } from 'machines/appMachine'
import { useSelector } from '@xstate/react'
interface AllSettingsFieldsProps {
searchParamTab: SettingsLevel
@ -41,7 +40,9 @@ export const AllSettingsFields = forwardRef(
const navigate = useNavigate()
const { onProjectOpen } = useLspContext()
const dotDotSlash = useDotDotSlash()
const context = useSettings()
const {
settings: { send, context, state },
} = useSettingsAuthContext()
const projectPath = useMemo(() => {
const filteredPathname = location.pathname
@ -61,7 +62,7 @@ export const AllSettingsFields = forwardRef(
}, [location.pathname])
function restartOnboarding() {
settingsActor.send({
send({
type: `set.app.onboardingStatus`,
data: { level: 'user', value: '' },
})
@ -71,14 +72,11 @@ export const AllSettingsFields = forwardRef(
* A "listener" for the XState to return to "idle" state
* when the user resets the onboarding, using the callback above
*/
const isSettingsMachineIdle = useSelector(settingsActor, (s) =>
s.matches('idle')
)
useEffect(() => {
async function navigateToOnboardingStart() {
if (
context.app.onboardingStatus.current === '' &&
isSettingsMachineIdle
state.context.app.onboardingStatus.user === '' &&
state.matches('idle')
) {
if (isFileSettings) {
// If we're in a project, first navigate to the onboarding start here
@ -93,12 +91,7 @@ export const AllSettingsFields = forwardRef(
}
// eslint-disable-next-line @typescript-eslint/no-floating-promises
navigateToOnboardingStart()
}, [
isFileSettings,
navigate,
isSettingsMachineIdle,
context.app.onboardingStatus.current,
])
}, [isFileSettings, navigate, state])
return (
<div className="relative overflow-y-auto">
@ -149,7 +142,7 @@ export const AllSettingsFields = forwardRef(
}
parentLevel={setting.getParentLevel(searchParamTab)}
onFallback={() =>
settingsActor.send({
send({
type: `set.${category}.${settingName}`,
data: {
level: searchParamTab,
@ -225,7 +218,7 @@ export const AllSettingsFields = forwardRef(
<ActionButton
Element="button"
onClick={() => {
settingsActor.send({
send({
type: 'Reset settings',
level: searchParamTab,
})

View File

@ -1,4 +1,5 @@
import { Toggle } from 'components/Toggle/Toggle'
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
import { Setting } from 'lib/settings/initialSettings'
import {
SetEventTypes,
@ -6,7 +7,6 @@ import {
WildcardSetEvent,
} from 'lib/settings/settingsTypes'
import { getSettingInputType } from 'lib/settings/settingsUtils'
import { settingsActor, useSettings } from 'machines/appMachine'
import { useMemo } from 'react'
import { EventFrom } from 'xstate'
@ -25,8 +25,9 @@ export function SettingsFieldInput({
settingsLevel,
setting,
}: SettingsFieldInputProps) {
const context = useSettings()
const send = settingsActor.send
const {
settings: { context, send },
} = useSettingsAuthContext()
const options = useMemo(() => {
return setting.commandConfig &&
'options' in setting.commandConfig &&

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