Compare commits

...

73 Commits

Author SHA1 Message Date
34272b872d WIP: workaround coredump hotkey linux
Fixes #4059
2024-12-10 15:47:30 -05:00
668e2afb99 Fix race condition in wasm test (#4740) 2024-12-10 20:38:51 +00:00
548c664db0 chore: implemented multiple instances instead of multiple appications? (#4733) 2024-12-10 18:51:24 +00:00
d3a3f4410c Fix to not have browser tooltip on top of CodeMirror tooltips (#4730)
* Fix to not have browser tooltip on top of LSP tooltips

* Switch title to aria-label
2024-12-10 12:15:55 -05:00
22eb343171 Add more Rust lints (#4698)
Add more lints
2024-12-10 12:14:03 -05:00
f2cfa4d5cf Adding point and click revolve workflow for sketch and axis selection (#4687)
* selection stuff

* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest)

* trigger CI

* fix bugs

* some edge cut stuff

* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest-8-cores)

* trigger CI

* A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest-8-cores)

* fix sketch mode issues

* fix more tests, selection in sketch related

* more test fixing

* A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest-8-cores)

* Trigger ci

* A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest-8-cores)

* Trigger ci

* more sketch mode selection fixes

* fix unit tests

* rename function

* remove .only

* migrate a more selections types

* migrate a more selections types

* migrate a more selections types

* lint

* migrate a more selections types

* migrate a more selections types

* migrate a more selections types

* migrate a more selections types

* migrate a more selections types

* migrate a more selections types

* migrate a more selections types

* migrate a more selections types

* migrate a more selections types

* migrate a more selections types

* fix bad pathToNode issue

* A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest-8-cores)

* fix sketch on face

* migrate a more selections types

* migrate a more selections types

* fix code selection of fillets

* migrate a more selections types

* migrate a more selections types

* migrate a more selections types

* migrate a more selections types

* migrate a more selections types

* fix bad path to node, looks like a race

* migrate a more selections types

* migrate a more selections types

* fix cmd bar selections

* fix cmd bar selections

* fix display issues

* feat: implementing axis selection for point and click revolve

* feat: enforcing selection of 2 options for axis rotation

* feat: added negative rotations for the revolve

* fix: fmt, tsc fixes

* migrate a more selections types

* Revert "migrate a more selections types"

This reverts commit 0d0e453bbb.

* migrate a more selections types

* clean up1

* clean up 2

* chore: improving the copy after discussing with Frank

* fix: merge main fixes

* chore: was able to add a seg to a line. Does not check if one exists already

* saving off some code

* chore: moving revolveSketch into own file for readability, improving variable names instead of node1

* chore: renaming more variables for readability

* chore: more renaming

* fix: allows creating a custom rotation on axis

* fix: added opposite edge logic and adj, need to error handle still

* fix: use other import

* feat: point and click on edges, crude implementation

* feat: implemented toast message and returned error message from validation

* fix: auto linter

* fix: addressing tsc errors

* fix: fighting typescript

* fix: cleaning up PR

* fix: trying to resolve more typescript issues

* fix: save off tsc fixes

* fix: adding comments

* fix: resolving tsc errors

* fix: tsc errors

* fix: auto linter fixes and tsc fixes

* fix:??

* fix: revolve ast works with declaration

* fix: retry logic to make sure the disable dry run actually runs

* fix: codespell typo

---------

Co-authored-by: Kurt Hutten Irev-Dev <k.hutten@protonmail.ch>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-12-10 11:11:01 -06:00
3f1f40eeba Revert "More aggressive using of cache on engine settings changes (#4691)" (#4729)
This reverts commit 943cf21d34.
2024-12-10 12:03:41 -05:00
ff2d161606 KCL parser: fns with optional keyword args can set defaults (#4727)
Part of #4600
2024-12-10 10:20:51 -06:00
210c78029d KCL keyword args: calling user-defined functions (#4722)
Part of https://github.com/KittyCAD/modeling-app/issues/4600

You can now call a user-defined function via keyword args. E.g.

```
fn increment(@x) {
  return x + 1
}

fn add(@x, delta) {
  return x + delta
}

two = increment(1)
three = add(1, delta: 2)
```
2024-12-10 04:11:16 +00:00
e27840219b Fix so that tag declarators can be used as parameters (#4692)
* Add test with tag parameter

* Fix to not define TagIdentifiers pointing to nothing

* Add fixed test output

* Rename function to be clearer

* Remove optional param so that unparse works

* Fix to not allow redefining tags

* Fix to only ever define tags in memory when used with stdlib functions

* Fix to not define local variables when non-literal tag declarator is used

* Add removing mutability

* Change function signature since it isn't falliable
2024-12-10 01:47:34 +00:00
c943a3f192 Refactor TokenStream (and some minor changes to Token) (#4695)
* Refactor TokenStream (and some minor changes to Token)

Signed-off-by: Nick Cameron <nrc@ncameron.org>

* Tidy up lexer tests

Signed-off-by: Nick Cameron <nrc@ncameron.org>

---------

Signed-off-by: Nick Cameron <nrc@ncameron.org>
2024-12-10 14:26:53 +13:00
6aa588f09f Bug: KCL formatter removes 'fn' from closures: (#4718)
# Problem

Before this PR, our formatter reformats
```
squares_out = reduce(arr, 0, fn (i, squares)  {
  return 1
})
```
to 
```
squares_out = reduce(arr, 0, (i, squares) {
  return 1
})
```
i.e. it removes the `fn` keyword from the closure. This keyword is required, so, our formatter turned working code into invalid code.

# Cause

When this closure parameter is formatted, the ExprContext is ::Decl, so `Expr::recast` skips adding the `fn` keyword. The reason it's ::Decl is because the `squares_out = ` declaration sets it, and no subsequent call sets the context to something else.

# Solution

When recasting a call expression, set the context for every argument to `ExprContext::Other`.
2024-12-09 19:13:49 -06:00
59a6333aad Fix nightly release page link in bottom bar (#4714)
* Fix nightly release page link in bottom bar
Fixes #4713

* Working now

* Cleaner

* Add IS_NIGHTLY var to make these checks more consistent

* Review from Kevin

* Update url for settings content too
2024-12-10 00:43:11 +00:00
403f1507ae Update snapshots (#4724) 2024-12-10 00:32:00 +00:00
eac7b83504 Rework the walk module a bit (#4707)
* start a rework of the walk module

I'd like to have the code explicitly recurse in the visitor rather than
implicitly. This allows the calling code to build up an idea of the
depth of each node, or skip parts of the AST entirely.

i'm going to rebuild this enough to get the old API to work before
looking to merge this.

I've also discovered a number of new AST types that were just papered
over and/or excluded entirely -- I'm going to have to go through and
make sure that both Digest and the walker are still current, or if we
silently added types or, more likely, fields getting ignored.
2024-12-10 00:23:53 +00:00
667500d1b9 Bug: Setting mouse controls to OnShape or AutoCAD resets to default (#4681)
* Bugfix: Setting mouse controls to OnShape resets to KittyCAD
Fixes #4639

* Revert "Bugfix: Setting mouse controls to OnShape resets to KittyCAD"

This reverts commit c95878d617.

* Add cases for values without underscores

* Fix lint

* Pass yarn tsc and yarn fmt-check
2024-12-09 19:16:29 -05:00
b15aac9f48 Move length and named value constraint flows into command palette (#4675)
* Extend KCL argument input

* Migrate length constraint to be a command

* Add ability for `kcl` arguments to provide an initial variable name

* Move named variable flow into command palette

* Fix one e2e test

* Remove unwanted `ZERO` behavior when length constraint has no `variableName`

* Fix issue with `getSelectionCountByType` with sketches not yet in artifactGraph

* Update broken constraint tests

* Look at this (photo)Graph *in the voice of Nickelback*

* Fix segment overlays tests, which had out-of-date selectors

* Return early from `useConvertToVariable` if no selectionRanges

* Fixup for review comment from #4677 (#4696)

Signed-off-by: Nick Cameron <nrc@ncameron.org>

* Invalidate nightly bucket files after publish (#4627)

* Invalidate nightly bucket files after publish

* Fix conflict resolution

* Add some more warnings (#4697)

* Add installation instructions for all platforms (#4592)

* Add installation instructions for all platforms
Fixes #4511

* Typo

* Typo2

* Improve linux instructions, thanks @TomPridham

Co-authored-by: Tom Pridham <pridham.tom@gmail.com>

---------

Co-authored-by: Tom Pridham <pridham.tom@gmail.com>

* Bump node to v22.12.0 (LTS) (#4706)

* Point-and-click Shell (#4666)

* WIP: experimenting with Loft UI
Relates to #4470

* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest-8-cores)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest-8-cores)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest-8-cores)

* Add selection guard

* Working loft for two sketches in the right hardcoded order

* First pass at handling more than 2 sketches

* WIP selections

* WIP selections

* More checks

* Appends the loft line after the 'last' sketch in the code

* Clean up

* Enable multiple selections after the button click

* First point-click loft test (not working locally, loft gets inserted at the wrong place)

* Lint

* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest-8-cores)

* Clean up and working pw test

* Add test for doesSceneHaveSweepableSketch with count = 2

* Clean up loftSketches function

* Add pw test for preselected sketches

* A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest-8-cores)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest-8-cores)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest-8-cores)

* Trigger CI

* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest-8-cores)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest-8-cores)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest-8-cores)

* Move to fromPromise-based Actor

* Move error logic out of loftSketches, fix pw tests

* Remove comments

* A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest-8-cores)

* Trigger CI

* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest-8-cores)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest-8-cores)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest-8-cores)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest-8-cores)

* Trigger CI

* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest-8-cores)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest-8-cores)

* Fix typo

* Revert snapshots

* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest-8-cores)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest-8-cores)

* Trigger CI

* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest-8-cores)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest-8-cores)

* Trigger CI

* WIP: initial shell code addition

* Rollback pw values to pre cam change

* WIP: more additions

* WIP: closer

* WIP: first time working shell mod

* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest-8-cores)

* Add extrude lookup for more generic shell

* Handle walls

* Add pw tests for cap shell

* Add shell wall test

* Fix lint

* Add selection guard and clean up

* Lint fix

* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest-8-cores)

* WIP mutliple faces

* WIP circular dep

* Lint

* Look at this (photo)Graph *in the voice of Nickelback*

* Trigger CI

* Working multi-face shell across types

* Cap and wall pw test

* Apply suggestions from Frank's review

Co-authored-by: Frank Noirot <frank@zoo.dev>

* Fix test annotations

* Add unit tests for doesSceneHaveExtrudedSketch

* Manual resolution of snapshot conflicts

* Fix assertParse

* Updated pathToNode construct

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Frank Noirot <frank@zoo.dev>

* More aggressive using of cache on engine settings changes (#4691)

* move around the files for cache to better localtions

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* cleanup

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* udpates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* cleanup

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* ensure we can change the grid setting via the command bar

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* pass thru all setttings

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* fix playwright test

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest-8-cores)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest-8-cores)

* emoty

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>

* fix use of `as`

---------

Signed-off-by: Nick Cameron <nrc@ncameron.org>
Signed-off-by: Jess Frazelle <github@jessfraz.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Nick Cameron <nrc@ncameron.org>
Co-authored-by: Pierre Jacquier <pierrejacquier39@gmail.com>
Co-authored-by: Tom Pridham <pridham.tom@gmail.com>
Co-authored-by: Jess Frazelle <jessfraz@users.noreply.github.com>
2024-12-09 21:43:58 +00:00
54153aa646 fix: replace whitespace with a - so that ids are valid and scroll to works (#4710)
Co-authored-by: Tom Pridham <pridham.tom@gmail.com>
2024-12-09 16:10:33 -05:00
943cf21d34 More aggressive using of cache on engine settings changes (#4691)
* move around the files for cache to better localtions

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* cleanup

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* udpates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* cleanup

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* ensure we can change the grid setting via the command bar

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* pass thru all setttings

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* fix playwright test

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest-8-cores)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest-8-cores)

* emoty

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-12-09 21:06:53 +00:00
5a6728c45a Point-and-click Shell (#4666)
* WIP: experimenting with Loft UI
Relates to #4470

* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest-8-cores)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest-8-cores)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest-8-cores)

* Add selection guard

* Working loft for two sketches in the right hardcoded order

* First pass at handling more than 2 sketches

* WIP selections

* WIP selections

* More checks

* Appends the loft line after the 'last' sketch in the code

* Clean up

* Enable multiple selections after the button click

* First point-click loft test (not working locally, loft gets inserted at the wrong place)

* Lint

* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest-8-cores)

* Clean up and working pw test

* Add test for doesSceneHaveSweepableSketch with count = 2

* Clean up loftSketches function

* Add pw test for preselected sketches

* A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest-8-cores)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest-8-cores)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest-8-cores)

* Trigger CI

* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest-8-cores)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest-8-cores)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest-8-cores)

* Move to fromPromise-based Actor

* Move error logic out of loftSketches, fix pw tests

* Remove comments

* A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest-8-cores)

* Trigger CI

* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest-8-cores)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest-8-cores)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest-8-cores)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest-8-cores)

* Trigger CI

* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest-8-cores)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest-8-cores)

* Fix typo

* Revert snapshots

* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest-8-cores)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest-8-cores)

* Trigger CI

* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest-8-cores)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest-8-cores)

* Trigger CI

* WIP: initial shell code addition

* Rollback pw values to pre cam change

* WIP: more additions

* WIP: closer

* WIP: first time working shell mod

* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest-8-cores)

* Add extrude lookup for more generic shell

* Handle walls

* Add pw tests for cap shell

* Add shell wall test

* Fix lint

* Add selection guard and clean up

* Lint fix

* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest-8-cores)

* WIP mutliple faces

* WIP circular dep

* Lint

* Look at this (photo)Graph *in the voice of Nickelback*

* Trigger CI

* Working multi-face shell across types

* Cap and wall pw test

* Apply suggestions from Frank's review

Co-authored-by: Frank Noirot <frank@zoo.dev>

* Fix test annotations

* Add unit tests for doesSceneHaveExtrudedSketch

* Manual resolution of snapshot conflicts

* Fix assertParse

* Updated pathToNode construct

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Frank Noirot <frank@zoo.dev>
2024-12-09 20:20:48 +00:00
ff2103d493 Bump node to v22.12.0 (LTS) (#4706) 2024-12-09 13:37:01 -05:00
2dfa8f2176 Add installation instructions for all platforms (#4592)
* Add installation instructions for all platforms
Fixes #4511

* Typo

* Typo2

* Improve linux instructions, thanks @TomPridham

Co-authored-by: Tom Pridham <pridham.tom@gmail.com>

---------

Co-authored-by: Tom Pridham <pridham.tom@gmail.com>
2024-12-09 12:30:24 -05:00
29ed330326 Add some more warnings (#4697) 2024-12-10 06:27:04 +13:00
ca2cc825a6 Invalidate nightly bucket files after publish (#4627)
* Invalidate nightly bucket files after publish

* Fix conflict resolution
2024-12-09 11:45:32 -05:00
83fe1b7ce0 Fixup for review comment from #4677 (#4696)
Signed-off-by: Nick Cameron <nrc@ncameron.org>
2024-12-09 10:42:03 -05:00
157b76cc78 KCL refactor: combine two fields into one struct (#4689) 2024-12-06 19:11:31 -06:00
cf957d880e KCL refactor: Fix argument names (#4690)
Does not change behaviour. It just clarifies whether JS is passing a string containing KCL source code, or containing a JSON-stringified KCL AST.
2024-12-06 17:20:06 -06:00
dfc3d19677 remove clearScene from TS side (#4684)
* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* fix lint

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* add failing tests

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* more tests

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* the scene is cleared

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* create new clear scene and bust cache function from rust side

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* pull thru

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* set that we switched files

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* fix two dirties

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* fix

Signed-off-by: Jess Frazelle <github@jessfraz.com>

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2024-12-06 22:56:53 +00:00
dd370a9365 AST: Allow unlabeled kw args (#4686)
When declaring a function, its first parameter is allowed to be prefixed with `@`. This means that when users call this function, they don't have to label this argument.

Only the first parameter is allowed this prefix, no others.

Part of https://github.com/KittyCAD/modeling-app/issues/4600
2024-12-06 15:44:39 -06:00
2274d6459c Bump @types/react-dom from 18.3.0 to 18.3.1 (#4411)
Bumps [@types/react-dom](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react-dom) from 18.3.0 to 18.3.1.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react-dom)

---
updated-dependencies:
- dependency-name: "@types/react-dom"
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Adam Chalmers <adam.chalmers@zoo.dev>
2024-12-06 15:21:28 -06:00
32ce857119 Update bracket KCL variable syntax in onboarding (#4685) 2024-12-06 16:04:17 -05:00
88b51da417 Run external contributor branch through CI (#4679)
* fix: make variable declaration errors Cut instead of Backtrace

* fix: clippy, move comma to empty case and add test

* fix: add missing TokenType case

* fix: incorrect fn args after merge

* fix: clippy lint

* fix: update error message being looked for in e2e test

---------

Co-authored-by: Tom Pridham <pridham.tom@gmail.com>
Co-authored-by: Jonathan Tran <jonnytran@gmail.com>
2024-12-06 14:12:20 -05:00
30d365aeb3 Module/import upgrades (#4677)
* Parse more import syntax

Signed-off-by: Nick Cameron <nrc@ncameron.org>

* Remove unnecessary Vec from VariableDeclaration

Signed-off-by: Nick Cameron <nrc@ncameron.org>

* Parse export import

Signed-off-by: Nick Cameron <nrc@ncameron.org>

* Factor out an execution module

Signed-off-by: Nick Cameron <nrc@ncameron.org>

* imports: constants, globs, export import

Signed-off-by: Nick Cameron <nrc@ncameron.org>

* test fixups

Signed-off-by: Nick Cameron <nrc@ncameron.org>

---------

Signed-off-by: Nick Cameron <nrc@ncameron.org>
2024-12-06 13:16:04 -05:00
7af62399ac Change vite-plugin-eslint to maintained package (#4645)
* Change vite-plugin-eslint to maintained package

* Add .eslintcache to ignores
2024-12-06 12:28:58 -05:00
441d957228 start of cache: don't re-execute on whitespace / top level code comment changes (#4663)
* start

Signed-off-by: Jess Frazelle <github@jessfraz.com>

working for whitespace

Signed-off-by: Jess Frazelle <github@jessfraz.com>

pull thru

Signed-off-by: Jess Frazelle <github@jessfraz.com>

fix wasm

Signed-off-by: Jess Frazelle <github@jessfraz.com>

pull thru to js start

Signed-off-by: Jess Frazelle <github@jessfraz.com>

actually use the cache in ts

Signed-off-by: Jess Frazelle <github@jessfraz.com>

rust owns clearing the scene

Signed-off-by: Jess Frazelle <github@jessfraz.com>

fixes

Signed-off-by: Jess Frazelle <github@jessfraz.com>

empty

stupid log

Signed-off-by: Jess Frazelle <github@jessfraz.com>

updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

fix tests

Signed-off-by: Jess Frazelle <github@jessfraz.com>

updatez

Signed-off-by: Jess Frazelle <github@jessfraz.com>

updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

save the state

Signed-off-by: Jess Frazelle <github@jessfraz.com>

save the state

Signed-off-by: Jess Frazelle <github@jessfraz.com>

updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

use the old memory

Signed-off-by: Jess Frazelle <github@jessfraz.com>

cleanup to use the old exec state

Signed-off-by: Jess Frazelle <github@jessfraz.com>

fices

Signed-off-by: Jess Frazelle <github@jessfraz.com>

updates;

Signed-off-by: Jess Frazelle <github@jessfraz.com>

fixes

Signed-off-by: Jess Frazelle <github@jessfraz.com>

fmt

Signed-off-by: Jess Frazelle <github@jessfraz.com>

updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

fixes

Signed-off-by: Jess Frazelle <github@jessfraz.com>

updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

cleanup

Signed-off-by: Jess Frazelle <github@jessfraz.com>

fixes

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* rebase and compile

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* Look at this (photo)Graph *in the voice of Nickelback*

* fix the lsp to use the cache

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* add comment

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* use a global static instead;

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* fix rust test

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* cleanup more

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* cleanups

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* cleanup the api even more

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* Look at this (photo)Graph *in the voice of Nickelback*

* bust the cache on unit changes

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* Look at this (photo)Graph *in the voice of Nickelback*

* stupid codespell

Signed-off-by: Jess Frazelle <github@jessfraz.com>

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-12-06 03:51:06 +00:00
9e57034873 AST: Allow KCL fn params to have defaults and labels (#4676)
Pure refactor, should not change any behaviour.

Previously, optional parameters in KCL function calls always set the parameter to KclNone. 

As of this PR, they can be set to KCL literals in addition to KCL none. However the parser does not actually ever use this (that'll be in a follow-up PR).

Also adds a `labeled: bool` to all parameters, which is always true. But it lays the groundwork for the unlabeled first parameter in a follow-up PR.
2024-12-06 03:04:40 +00:00
eb96d6539c Surface warnings to frontend and LSP (#4603)
* Send multiple errors and warnings to the frontend and LSP

Signed-off-by: Nick Cameron <nrc@ncameron.org>

* Refactor the parser to use CompilationError for parsing errors rather than KclError

Signed-off-by: Nick Cameron <nrc@ncameron.org>

* Refactoring: move CompilationError, etc.

Signed-off-by: Nick Cameron <nrc@ncameron.org>

* Integrate compilation errors with the frontend and CodeMirror

Signed-off-by: Nick Cameron <nrc@ncameron.org>

* Fix tests

Signed-off-by: Nick Cameron <nrc@ncameron.org>

* Review comments

Signed-off-by: Nick Cameron <nrc@ncameron.org>

* Fix module id/source range stuff

Signed-off-by: Nick Cameron <nrc@ncameron.org>

* More test fixups

Signed-off-by: Nick Cameron <nrc@ncameron.org>

---------

Signed-off-by: Nick Cameron <nrc@ncameron.org>
2024-12-06 13:57:31 +13:00
513c76ecc8 KCL: Remove stdlib written in KCL (#4673)
We don't have any of these, and I don't think it's
worth the complexity. The goal was to let us write
KCL stdlib functions in KCL not Rust. But who cares
really. We can always put this back if we need it.
2024-12-05 23:59:37 +00:00
51d9449280 Fix broken test from previous PR (#4674)
* Fix broken test from previous PR

* Look at this (photo)Graph *in the voice of Nickelback*

* void

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-12-05 23:39:40 +00:00
6366bc4766 KCL: Keyword function calls for stdlib (#4647)
Part of https://github.com/KittyCAD/modeling-app/issues/4600

Adds support for keyword arguments to the stdlib, and calling stdlib functions with keyword arguments.

So far, I've changed one function: `rem`. Previously you would have used `rem(7, 2)` but now it's `rem(7, divisor: 2)`.

This is a proof-of-concept. If it's approved, we will:

1. Support closures with keyword arguments, and calling them
2. Move the rest of the stdlib to use kw arguments
2024-12-05 14:27:51 -06:00
7a21918223 Cleaner nightly release notes (#4668)
* Bump: Improve and fix nightly release notes

* Clean up after manual push

* Consistency tweak
2024-12-05 15:14:58 -05:00
8072f1db63 Add support for line comments in playwright-secrets.env (#4671) 2024-12-05 19:45:33 +00:00
18e1855fa9 Bump indexmap from 2.6.0 to 2.7.0 in /src/wasm-lib (#4622)
Bumps [indexmap](https://github.com/indexmap-rs/indexmap) from 2.6.0 to 2.7.0.
- [Changelog](https://github.com/indexmap-rs/indexmap/blob/master/RELEASES.md)
- [Commits](https://github.com/indexmap-rs/indexmap/compare/2.6.0...2.7.0)

---
updated-dependencies:
- dependency-name: indexmap
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-05 17:44:35 +00:00
7be53c7d4a Bump KCL, KCL test server, derive-docs (#4670) 2024-12-05 17:34:43 +00:00
2bf20988ef Fix to never have undefined iteration order and lint against it (#4665) 2024-12-05 17:09:35 +00:00
1495cc6d18 Fix default planes to be created in deterministic order (#4664)
* Fix default planes to be created in deterministic order

* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest-8-cores)

* Trigger CI

* A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest-8-cores)

* Trigger CI

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-12-05 16:04:04 +00:00
f876e6ca3c Bump and release kcl-lib 0.2.28 (#4669)
bump kcl version
2024-12-05 15:03:55 +00:00
60a0c811ab Move parsing files around (#4626)
Signed-off-by: Nick Cameron <nrc@ncameron.org>
2024-12-05 17:56:49 +13:00
cab0c1e6a1 Add list of commits as changelog between nightly builds (#4654) 2024-12-04 19:06:17 -05:00
417d720b22 Point-and-click Loft (#4605)
* WIP: experimenting with Loft UI
Relates to #4470

* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest-8-cores)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest-8-cores)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest-8-cores)

* Add selection guard

* Working loft for two sketches in the right hardcoded order

* First pass at handling more than 2 sketches

* WIP selections

* WIP selections

* More checks

* Appends the loft line after the 'last' sketch in the code

* Clean up

* Enable multiple selections after the button click

* First point-click loft test (not working locally, loft gets inserted at the wrong place)

* Lint

* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest-8-cores)

* Clean up and working pw test

* Add test for doesSceneHaveSweepableSketch with count = 2

* Clean up loftSketches function

* Add pw test for preselected sketches

* A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest-8-cores)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest-8-cores)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest-8-cores)

* Trigger CI

* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest-8-cores)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest-8-cores)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest-8-cores)

* Move to fromPromise-based Actor

* Move error logic out of loftSketches, fix pw tests

* Remove comments

* A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest-8-cores)

* Trigger CI

* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest-8-cores)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest-8-cores)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest-8-cores)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest-8-cores)

* Trigger CI

* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest-8-cores)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest-8-cores)

* Fix typo

* Revert snapshots

* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest-8-cores)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest-8-cores)

* Trigger CI

* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest-8-cores)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest-8-cores)

* Trigger CI

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-12-04 22:24:16 +00:00
77293952c0 fix: upon entering sketch mode, axis do not rotate. (#4572)
* fix: upon entering sketch mode, axis do not rotate.

* fix: camera settings has the up vector to set, this should now work
2024-12-04 15:57:17 -06:00
ea3d604b73 Allow standard planes to appear in the artifact graph (#4594) 2024-12-04 21:06:02 +00:00
023a659491 Make some fields of lint::Discovered public again; update kittycad (#4658)
* Make some fields of lint::Discovered public again; update kittycad

Signed-off-by: Nick Cameron <nrc@ncameron.org>

* Empty commit to try to unstick CI

---------

Signed-off-by: Nick Cameron <nrc@ncameron.org>
2024-12-05 10:05:23 +13:00
dd3a2b14f9 Bump hashbrown from 0.15.0 to 0.15.2 (#4659) 2024-12-04 20:40:46 +00:00
424b409cc1 Bump and release kcl-lib 0.2.27 (#4643)
* bump and release kcl-lib

* update snapshot

* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest-8-cores)

* empty commit

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-12-03 17:58:21 -05:00
82a58e69c2 Bump wasm-pack from 0.13.0 to 0.13.1 (#4630) 2024-12-03 16:27:16 -06:00
max
776b420031 Rename addFillet files to addEdgeTreatment (#4644)
* rename

* update references
2024-12-04 08:30:02 +11:00
1087d4223b Bump happy-dom from 15.10.2 to 15.11.7 (#4625)
Bumps [happy-dom](https://github.com/capricorn86/happy-dom) from 15.10.2 to 15.11.7.
- [Release notes](https://github.com/capricorn86/happy-dom/releases)
- [Commits](https://github.com/capricorn86/happy-dom/compare/v15.10.2...v15.11.7)

---
updated-dependencies:
- dependency-name: happy-dom
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-03 15:40:35 -05:00
089d6df889 Update fn syntax in module docs (#4641) 2024-12-03 15:14:32 -05:00
efb067af58 Update fn syntax in module docs (#4641) 2024-12-03 19:47:21 +00:00
2aa27eab01 Bump happy-dom from 15.10.2 to 15.11.7 (#4625)
Bumps [happy-dom](https://github.com/capricorn86/happy-dom) from 15.10.2 to 15.11.7.
- [Release notes](https://github.com/capricorn86/happy-dom/releases)
- [Commits](https://github.com/capricorn86/happy-dom/compare/v15.10.2...v15.11.7)

---
updated-dependencies:
- dependency-name: happy-dom
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-03 19:40:36 +00:00
9c47ac5b57 Update fn syntax in KCL Types doc (#4640)
* update fn syntax

* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest-8-cores)

* Trigger CI

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Jonathan Tran <jonnytran@gmail.com>
2024-12-03 18:17:02 +00:00
5ae1aecd74 Reduce Python API surface area to what is necessary for kcl.py (#4637)
Signed-off-by: Nick Cameron <nrc@ncameron.org>
2024-12-03 17:34:58 +13:00
68ae7e98f9 Refactor SourceRange and ModuleId to make them better encapsualated (#4636)
Signed-off-by: Nick Cameron <nrc@ncameron.org>
2024-12-03 16:39:51 +13:00
56771d561a Bump rustls from 0.23.13 to 0.23.19 and rustls-pki-types (#4632) 2024-12-03 15:49:15 +13:00
f09411817c KCL AST: Call functions with keyword arguments (#4599)
Call expressions only, haven't done function expressions yet.

Part of https://github.com/KittyCAD/modeling-app/issues/4600
2024-12-02 21:23:18 +00:00
max
bed7ae3b8b Refactor addFillet into addEdgeTreatment Function Supporting Chamfers (#4593)
* refactor code mod and tests

* tsc

* make lint happy

* remove dumby data

Co-authored-by: Kurt Hutten <k.hutten@protonmail.ch>

---------

Co-authored-by: Kurt Hutten <k.hutten@protonmail.ch>
2024-12-02 21:43:59 +01:00
c43510732c KCL docs: Remove FunctionExpression (#4633)
KCL functions are a weird edge case, and the `FunctionExpression` field should not be included in its public API. That field is only there for implementation details, it shouldn't be exposed to users.

What's worse is that `FunctionExpression` includes a `Program` so every single AST node wound up being included in our docs.
2024-12-02 14:36:49 -06:00
51f0b669a4 fix: only count something as a directory if it has children (#4595)
* fix: only count something as a directory if it has children

* fix: playwright tests

* fix: return 0 if you cant find the projectfolder

* fix: remove folder count from e2e tests since it is unused currently

---------

Co-authored-by: Tom Pridham <pridham.tom@gmail.com>
2024-12-02 15:16:43 -05:00
3cbedcd3e7 Bump clap from 4.5.20 to 4.5.21 in /src/wasm-lib (#4623)
Bumps [clap](https://github.com/clap-rs/clap) from 4.5.20 to 4.5.21.
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.20...clap_complete-v4.5.21)

---
updated-dependencies:
- dependency-name: clap
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-02 09:28:57 -08:00
5d2fa43150 Bump dawidd6/action-download-artifact from 6 to 7 (#4621)
Bumps [dawidd6/action-download-artifact](https://github.com/dawidd6/action-download-artifact) from 6 to 7.
- [Release notes](https://github.com/dawidd6/action-download-artifact/releases)
- [Commits](https://github.com/dawidd6/action-download-artifact/compare/v6...v7)

---
updated-dependencies:
- dependency-name: dawidd6/action-download-artifact
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-12-02 09:27:57 -08:00
ec49b0752e Remove auto creation of the draft release (#4588)
Fixes #4586
2024-12-02 10:30:24 -05:00
3b171fb881 Update nightly and release icons from Figma (#4597)
* Update nightly and windows icons

* Remove icon.ico, keep only icon.png, see https://www.electron.build/icons.html#windows-nsis

* Revert "Remove icon.ico, keep only icon.png, see https://www.electron.build/icons.html#windows-nsis"

This reverts commit b97f81b07d.

* Update windows icons

* Reset windows ico

* Test ico no margin

* Converted with freeconvert

* Use convertico.com for conversion
2024-12-02 10:24:14 -05:00
517 changed files with 118498 additions and 134306 deletions

View File

@ -362,6 +362,17 @@ jobs:
- name: List artifacts
run: "ls -R out"
- name: Set more complete nightly release notes
if: ${{ env.IS_NIGHTLY == 'true' }}
run: |
# Note: preferred going this way instead of a full clone in the checkout step,
# see https://github.com/actions/checkout/issues/1471
git fetch --prune --unshallow --tags
export TAG="nightly-${VERSION}"
export PREVIOUS_TAG=$(git describe --tags --match="nightly-v[0-9]*" --abbrev=0)
export NOTES=$(./scripts/get-nightly-changelog.sh)
yarn files:set-notes
- name: Authenticate to Google Cloud
if: ${{ env.IS_NIGHTLY == 'true' }}
uses: 'google-github-actions/auth@v2.1.7'
@ -383,12 +394,17 @@ jobs:
parent: false
destination: 'dl.kittycad.io/releases/modeling-app/nightly'
- name: Create draft release
uses: softprops/action-gh-release@v2
if: ${{ env.IS_RELEASE == 'true' }}
- name: Invalidate bucket cache on latest*.yml and last_download.json files
if: ${{ env.IS_NIGHTLY == 'true' }}
run: yarn files:invalidate-bucket:nightly
- name: Tag nightly commit
if: ${{ env.IS_NIGHTLY == 'true' }}
uses: actions/github-script@v7
with:
name: ${{ env.VERSION }}
tag_name: ${{ env.VERSION }}
draft: true
generate_release_notes: true
files: 'out/Zoo*'
script: |
const { VERSION } = process.env
const { owner, repo } = context.repo
const { sha } = context
const ref = `refs/tags/nightly-${VERSION}`
github.rest.git.createRef({ owner, repo, sha, ref })

View File

@ -68,7 +68,7 @@ jobs:
- name: Download Wasm Cache
id: download-wasm
if: needs.check-rust-changes.outputs.rust-changed == 'false'
uses: dawidd6/action-download-artifact@v6
uses: dawidd6/action-download-artifact@v7
continue-on-error: true
with:
github_token: ${{secrets.GITHUB_TOKEN}}
@ -255,7 +255,7 @@ jobs:
- name: Download Wasm Cache
id: download-wasm
if: needs.check-rust-changes.outputs.rust-changed == 'false'
uses: dawidd6/action-download-artifact@v6
uses: dawidd6/action-download-artifact@v7
continue-on-error: true
with:
github_token: ${{secrets.GITHUB_TOKEN}}

View File

@ -126,11 +126,13 @@ jobs:
destination: 'dl.kittycad.io/releases/modeling-app'
- name: Invalidate bucket cache on latest*.yml and last_download.json files
run: |
gcloud compute url-maps invalidate-cdn-cache dl-url-map --path="/releases/modeling-app/last_download.json" --async
gcloud compute url-maps invalidate-cdn-cache dl-url-map --path="/releases/modeling-app/latest-linux-arm64.yml" --async
gcloud compute url-maps invalidate-cdn-cache dl-url-map --path="/releases/modeling-app/latest-mac.yml" --async
gcloud compute url-maps invalidate-cdn-cache dl-url-map --path="/releases/modeling-app/latest.yml" --async
run: yarn files:invalidate-bucket
- name: Upload release files to Github
if: ${{ github.event_name == 'release' }}
uses: softprops/action-gh-release@v2
with:
files: 'out/Zoo*'
announce_release:

1
.gitignore vendored
View File

@ -61,6 +61,7 @@ Mac_App_Distribution.provisionprofile
*.tsbuildinfo
src/wasm-lib/pkg
.eslintcache
venv
.vite/

2
.nvmrc
View File

@ -1 +1 @@
v21.7.3
v22.12.0

43
INSTALL.md Normal file
View File

@ -0,0 +1,43 @@
# Setting Up Zoo Modeling App
Compared to other CAD software, getting Zoo Modeling App up and running is quick and straightforward across platforms. It's about 100MB to download and is quick to install.
## Windows
1. Download the [Zoo Modeling App installer](https://zoo.dev/modeling-app/download) for Windows and for your processor type.
2. Once downloaded, run the installer `Zoo Modeling App-{version}-{arch}-win.exe` which should take a few seconds.
3. The installation happens at `C:\Program Files\Zoo Modeling App`. A shortcut in the start menu is also created so you can run the app easily by clicking on it.
## macOS
1. Download the [Zoo Modeling App installer](https://zoo.dev/modeling-app/download) for macOS and for your processor type.
2. Once downloaded, open the disk image `Zoo Modeling App-{version}-{arch}-mac.dmg` and drag the applications to your `Applications` directory.
3. You can then open your `Applications` directory and double-click on `Zoo Modeling App` to open.
## Linux
1. Download the [Zoo Modeling App installer](https://zoo.dev/modeling-app/download) for Linux and for your processor type.
2. Install the dependencies needed to run the [AppImage format](https://appimage.org/).
- On Ubuntu, install the FUSE library with these commands in a terminal.
```bash
sudo apt update
sudo apt install libfuse2
```
- Optionally, follow [these steps](https://github.com/probonopd/go-appimage/blob/master/src/appimaged/README.md#initial-setup) to install `appimaged`. It is a daemon that makes interacting with AppImage files more seamless.
- Once installed, copy the downloaded `Zoo Modeling App-{version}-{arch}-linux.AppImage` to the directory of your choice, for instance `~/Applications`.
- `appimaged` should automatically find it and make it executable. If not, run:
```bash
chmod a+x ~/Applications/Zoo\ Modeling\ App-{version}-{arch}-linux.AppImage
```
3. You can double-click on the AppImage to run it, or in a terminal with this command:
```bash
~/Applications/Zoo\ Modeling\ App-{version}-{arch}-linux.AppImage
```

View File

@ -99,7 +99,7 @@ yarn tron:start
This will start the application and hot-reload on changes.
Devtools can be opened with the usual Cmd/Ctrl-Shift-I.
Devtools can be opened with the usual Cmd-Opt-I (Mac) or Ctrl-Shift-I (Linux and Windows).
To build, run `yarn tron:package`.
@ -136,7 +136,7 @@ https://github.com/KittyCAD/modeling-app/issues/new
#### 2. Push a new tag
Create a new tag and push it to the repo (eg. `v0.28.0` for `$VERSION`)
Create a new tag and push it to the repo. The `semantic-release.sh` script will automatically bump the minor part, which we use the most. For instance going from `v0.27.0` to `v0.28.0`.
```
VERSION=$(./scripts/semantic-release.sh)
@ -146,16 +146,14 @@ git push origin --tags
This will trigger the `build-apps` workflow, set the version, build & sign the apps, and generate release files as well as updater-test artifacts.
Once the workflow succeeds, a draft release will be created at https://github.com/KittyCAD/modeling-app/releases.
The workflow should be listed right away [in this list](https://github.com/KittyCAD/modeling-app/actions/workflows/build-apps.yml?query=event%3Apush)).
#### 3. Manually test artifacts from the Cut Release PR
#### 3. Manually test artifacts
##### Release builds
The release builds can be found under the `out-{arch}-{platform}` zip files, at the very bottom of the `build-apps` summary page for the workflow (triggered by the tag in 2.).
Alternatively, the draft release will also include these builds.
Manually test against this [list](https://github.com/KittyCAD/modeling-app/issues/3588) across Windows, MacOS, Linux and posting results as comments in the issue.
##### Updater-test builds
@ -178,9 +176,11 @@ If the prompt doesn't show up, start the app in command line to grab the electro
#### 4. Publish the release
Head over to https://github.com/KittyCAD/modeling-app/releases, paste in the changelog discussed in the issue, and publish the draft release created by the `build-apps` workflow from step 2.
Head over to https://github.com/KittyCAD/modeling-app/releases/new, pick the newly created tag and type it in the _Release title_ field as well.
A new Action kicks in at https://github.com/KittyCAD/modeling-app/actions, which can be found under `release` event filter. On success, the files will be uploaded to the public bucket and the announcement on Discord will be sent.
Hit _Generate release notes_ as a starting point to discuss the changelog in the issue. Once done, make sure _Set as the latest release_ is checked, and hit _Publish release_.
A new `publish-apps-release` will kick in and you should be able to find it [here](https://github.com/KittyCAD/modeling-app/actions?query=event%3Arelease). On success, the files will be uploaded to the public bucket as well as to the GitHub release, and the announcement on Discord will be sent.
#### 5. Close the issue

Binary file not shown.

Before

Width:  |  Height:  |  Size: 176 KiB

After

Width:  |  Height:  |  Size: 119 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 157 KiB

After

Width:  |  Height:  |  Size: 259 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 183 KiB

After

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 113 KiB

After

Width:  |  Height:  |  Size: 113 KiB

File diff suppressed because one or more lines are too long

View File

@ -45,7 +45,7 @@ circles = map([1..3], drawCircle)
```js
r = 10 // radius
// Call `map`, using an anonymous function instead of a named one.
circles = map([1..3], (id) {
circles = map([1..3], fn(id) {
return startSketchOn("XY")
|> circle({ center = [id * 2 * r, 0], radius = r }, %)
})

View File

@ -12,7 +12,7 @@ to other modules.
```
// util.kcl
export fn increment = (x) => {
export fn increment(x) {
return x + 1
}
```
@ -37,11 +37,11 @@ Multiple functions can be exported in a file.
```
// util.kcl
export fn increment = (x) => {
export fn increment(x) {
return x + 1
}
export fn decrement = (x) => {
export fn decrement(x) {
return x - 1
}
```

View File

@ -61,7 +61,7 @@ assertEqual(sum([1, 2, 3]), 6, 0.00001, "1 + 2 + 3 summed is 6")
// an anonymous `add` function as its parameter, instead of declaring a
// named function outside.
arr = [1, 2, 3]
sum = reduce(arr, 0, (i, result_so_far) {
sum = reduce(arr, 0, fn(i, result_so_far) {
return i + result_so_far
})
@ -84,7 +84,7 @@ fn decagon(radius) {
// Use a `reduce` to draw the remaining decagon sides.
// For each number in the array 1..10, run the given function,
// which takes a partially-sketched decagon and adds one more edge to it.
fullDecagon = reduce([1..10], startOfDecagonSketch, (i, partialDecagon) {
fullDecagon = reduce([1..10], startOfDecagonSketch, fn(i, partialDecagon) {
// Draw one edge of the decagon.
x = cos(stepAngle * i) * radius
y = sin(stepAngle * i) * radius

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@ -54,7 +54,7 @@ We also have support for defining your own functions. Functions can take in any
type of argument. Below is an example of the syntax:
```
fn myFn = (x) => {
fn myFn(x) {
return x
}
```
@ -118,7 +118,7 @@ use the tag `rectangleSegmentA001` in any function or expression in the file.
However if the code was written like this:
```
fn rect = (origin) => {
fn rect(origin) {
return startSketchOn('XZ')
|> startProfileAt(origin, %)
|> angledLine([0, 191.26], %, $rectangleSegmentA001)
@ -146,7 +146,7 @@ Tags are accessible through the sketch group they are declared in.
For example the following code works.
```
fn rect = (origin) => {
fn rect(origin) {
return startSketchOn('XZ')
|> startProfileAt(origin, %)
|> angledLine([0, 191.26], %, $rectangleSegmentA001)

View File

@ -1,161 +0,0 @@
---
title: "BinaryOperator"
excerpt: ""
layout: manual
---
**This schema accepts exactly one of the following:**
Add two numbers.
**enum:** `+`
----
Subtract two numbers.
**enum:** `-`
----
Multiply two numbers.
**enum:** `*`
----
Divide two numbers.
**enum:** `/`
----
Modulo two numbers.
**enum:** `%`
----
Raise a number to a power.
**enum:** `^`
----
Are two numbers equal?
**enum:** `==`
----
Are two numbers not equal?
**enum:** `!=`
----
Is left greater than right
**enum:** `>`
----
Is left greater than or equal to right
**enum:** `>=`
----
Is left less than right
**enum:** `<`
----
Is left less than or equal to right
**enum:** `<=`
----

View File

@ -1,160 +0,0 @@
---
title: "BinaryPart"
excerpt: ""
layout: manual
---
**This schema accepts exactly one of the following:**
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `Literal`| | No |
| `value` |[`LiteralValue`](/docs/kcl/types/LiteralValue)| | No |
| `raw` |`string`| | No |
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
| `start` |`integer`| | No |
| `end` |`integer`| | No |
----
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: [`Identifier`](/docs/kcl/types/Identifier)| | No |
| `name` |`string`| | No |
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
| `start` |`integer`| | No |
| `end` |`integer`| | No |
----
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `BinaryExpression`| | No |
| `operator` |[`BinaryOperator`](/docs/kcl/types/BinaryOperator)| | No |
| `left` |[`BinaryPart`](/docs/kcl/types/BinaryPart)| | No |
| `right` |[`BinaryPart`](/docs/kcl/types/BinaryPart)| | No |
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
| `start` |`integer`| | No |
| `end` |`integer`| | No |
----
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `CallExpression`| | No |
| `callee` |[`Identifier`](/docs/kcl/types/Identifier)| | No |
| `arguments` |`[` [`Expr`](/docs/kcl/types/Expr) `]`| | No |
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
| `start` |`integer`| | No |
| `end` |`integer`| | No |
----
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `UnaryExpression`| | No |
| `operator` |[`UnaryOperator`](/docs/kcl/types/UnaryOperator)| | No |
| `argument` |[`BinaryPart`](/docs/kcl/types/BinaryPart)| | No |
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
| `start` |`integer`| | No |
| `end` |`integer`| | No |
----
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `MemberExpression`| | No |
| `object` |[`MemberObject`](/docs/kcl/types/MemberObject)| | No |
| `property` |[`LiteralIdentifier`](/docs/kcl/types/LiteralIdentifier)| | No |
| `computed` |`boolean`| | No |
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
| `start` |`integer`| | No |
| `end` |`integer`| | No |
----
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `IfExpression`| | No |
| `cond` |[`Expr`](/docs/kcl/types/Expr)| | No |
| `then_val` |[`Program`](/docs/kcl/types/Program)| | No |
| `else_ifs` |`[` [`ElseIf`](/docs/kcl/types/ElseIf) `]`| | No |
| `final_else` |[`Program`](/docs/kcl/types/Program)| | No |
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
| `start` |`integer`| | No |
| `end` |`integer`| | No |
----

View File

@ -1,97 +0,0 @@
---
title: "BodyItem"
excerpt: ""
layout: manual
---
**This schema accepts exactly one of the following:**
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `ImportStatement`| | No |
| `items` |`[` [`ImportItem`](/docs/kcl/types/ImportItem) `]`| | No |
| `path` |`string`| | No |
| `raw_path` |`string`| | No |
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
| `start` |`integer`| | No |
| `end` |`integer`| | No |
----
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `ExpressionStatement`| | No |
| `expression` |[`Expr`](/docs/kcl/types/Expr)| | No |
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
| `start` |`integer`| | No |
| `end` |`integer`| | No |
----
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `VariableDeclaration`| | No |
| `declarations` |`[` [`VariableDeclarator`](/docs/kcl/types/VariableDeclarator) `]`| | No |
| `visibility` |[`ItemVisibility`](/docs/kcl/types/ItemVisibility)| | No |
| `kind` |[`VariableKind`](/docs/kcl/types/VariableKind)| | No |
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
| `start` |`integer`| | No |
| `end` |`integer`| | No |
----
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `ReturnStatement`| | No |
| `argument` |[`Expr`](/docs/kcl/types/Expr)| | No |
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
| `start` |`integer`| | No |
| `end` |`integer`| | No |
----

View File

@ -1,41 +0,0 @@
---
title: "CommentStyle"
excerpt: ""
layout: manual
---
**This schema accepts exactly one of the following:**
Like // foo
**enum:** `line`
----
Like /* foo */
**enum:** `block`
----

View File

@ -1,24 +0,0 @@
---
title: "ElseIf"
excerpt: ""
layout: manual
---
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `cond` |[`Expr`](/docs/kcl/types/Expr)| | No |
| `then_val` |[`Program`](/docs/kcl/types/Program)| | No |
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
| `start` |`integer`| | No |
| `end` |`integer`| | No |

View File

@ -1,16 +0,0 @@
---
title: "EnvironmentRef"
excerpt: "An index pointing to an environment."
layout: manual
---
An index pointing to an environment.
**Type:** `integer` (`uint`)

View File

@ -1,317 +0,0 @@
---
title: "Expr"
excerpt: "An expression can be evaluated to yield a single KCL value."
layout: manual
---
An expression can be evaluated to yield a single KCL value.
**This schema accepts exactly one of the following:**
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `Literal`| | No |
| `value` |[`LiteralValue`](/docs/kcl/types/LiteralValue)| An expression can be evaluated to yield a single KCL value. | No |
| `raw` |`string`| | No |
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
| `start` |`integer`| | No |
| `end` |`integer`| | No |
----
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: [`Identifier`](/docs/kcl/types/Identifier)| | No |
| `name` |`string`| | No |
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
| `start` |`integer`| | No |
| `end` |`integer`| | No |
----
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: [`TagDeclarator`](/docs/kcl/types#tag-declaration)| | No |
| `value` |`string`| | No |
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
| `start` |`integer`| | No |
| `end` |`integer`| | No |
----
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `BinaryExpression`| | No |
| `operator` |[`BinaryOperator`](/docs/kcl/types/BinaryOperator)| An expression can be evaluated to yield a single KCL value. | No |
| `left` |[`BinaryPart`](/docs/kcl/types/BinaryPart)| An expression can be evaluated to yield a single KCL value. | No |
| `right` |[`BinaryPart`](/docs/kcl/types/BinaryPart)| An expression can be evaluated to yield a single KCL value. | No |
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
| `start` |`integer`| | No |
| `end` |`integer`| | No |
----
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: [`FunctionExpression`](/docs/kcl/types/FunctionExpression)| | No |
| `params` |`[` [`Parameter`](/docs/kcl/types/Parameter) `]`| | No |
| `body` |[`Program`](/docs/kcl/types/Program)| An expression can be evaluated to yield a single KCL value. | No |
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
| `start` |`integer`| | No |
| `end` |`integer`| | No |
----
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `CallExpression`| | No |
| `callee` |[`Identifier`](/docs/kcl/types/Identifier)| An expression can be evaluated to yield a single KCL value. | No |
| `arguments` |`[` [`Expr`](/docs/kcl/types/Expr) `]`| | No |
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
| `start` |`integer`| | No |
| `end` |`integer`| | No |
----
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `PipeExpression`| | No |
| `body` |`[` [`Expr`](/docs/kcl/types/Expr) `]`| | No |
| `nonCodeMeta` |[`NonCodeMeta`](/docs/kcl/types/NonCodeMeta)| An expression can be evaluated to yield a single KCL value. | No |
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
| `start` |`integer`| | No |
| `end` |`integer`| | No |
----
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `PipeSubstitution`| | No |
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
| `start` |`integer`| | No |
| `end` |`integer`| | No |
----
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `ArrayExpression`| | No |
| `elements` |`[` [`Expr`](/docs/kcl/types/Expr) `]`| | No |
| `nonCodeMeta` |[`NonCodeMeta`](/docs/kcl/types/NonCodeMeta)| An expression can be evaluated to yield a single KCL value. | No |
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
| `start` |`integer`| | No |
| `end` |`integer`| | No |
----
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `ArrayRangeExpression`| | No |
| `startElement` |[`Expr`](/docs/kcl/types/Expr)| An expression can be evaluated to yield a single KCL value. | No |
| `endElement` |[`Expr`](/docs/kcl/types/Expr)| An expression can be evaluated to yield a single KCL value. | No |
| `endInclusive` |`boolean`| Is the `end_element` included in the range? | No |
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
| `start` |`integer`| | No |
| `end` |`integer`| | No |
----
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `ObjectExpression`| | No |
| `properties` |`[` [`ObjectProperty`](/docs/kcl/types/ObjectProperty) `]`| | No |
| `nonCodeMeta` |[`NonCodeMeta`](/docs/kcl/types/NonCodeMeta)| An expression can be evaluated to yield a single KCL value. | No |
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
| `start` |`integer`| | No |
| `end` |`integer`| | No |
----
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `MemberExpression`| | No |
| `object` |[`MemberObject`](/docs/kcl/types/MemberObject)| An expression can be evaluated to yield a single KCL value. | No |
| `property` |[`LiteralIdentifier`](/docs/kcl/types/LiteralIdentifier)| An expression can be evaluated to yield a single KCL value. | No |
| `computed` |`boolean`| | No |
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
| `start` |`integer`| | No |
| `end` |`integer`| | No |
----
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `UnaryExpression`| | No |
| `operator` |[`UnaryOperator`](/docs/kcl/types/UnaryOperator)| An expression can be evaluated to yield a single KCL value. | No |
| `argument` |[`BinaryPart`](/docs/kcl/types/BinaryPart)| An expression can be evaluated to yield a single KCL value. | No |
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
| `start` |`integer`| | No |
| `end` |`integer`| | No |
----
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `IfExpression`| | No |
| `cond` |[`Expr`](/docs/kcl/types/Expr)| An expression can be evaluated to yield a single KCL value. | No |
| `then_val` |[`Program`](/docs/kcl/types/Program)| An expression can be evaluated to yield a single KCL value. | No |
| `else_ifs` |`[` [`ElseIf`](/docs/kcl/types/ElseIf) `]`| | No |
| `final_else` |[`Program`](/docs/kcl/types/Program)| An expression can be evaluated to yield a single KCL value. | No |
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
| `start` |`integer`| | No |
| `end` |`integer`| | No |
----
KCL value for an optional parameter which was not given an argument. (remember, parameters are in the function declaration, arguments are in the function call/application).
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `None`| | No |
| `start` |`integer`| | No |
| `end` |`integer`| | No |
----

View File

@ -1,24 +0,0 @@
---
title: "FunctionExpression"
excerpt: ""
layout: manual
---
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `params` |`[` [`Parameter`](/docs/kcl/types/Parameter) `]`| | No |
| `body` |[`Program`](/docs/kcl/types/Program)| | No |
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
| `start` |`integer`| | No |
| `end` |`integer`| | No |

View File

@ -1,23 +0,0 @@
---
title: "Identifier"
excerpt: ""
layout: manual
---
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `name` |`string`| | No |
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
| `start` |`integer`| | No |
| `end` |`integer`| | No |

View File

@ -1,24 +0,0 @@
---
title: "ImportItem"
excerpt: ""
layout: manual
---
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `name` |[`Identifier`](/docs/kcl/types/Identifier)| Name of the item to import. | No |
| `alias` |[`Identifier`](/docs/kcl/types/Identifier)| Rename the item using an identifier after "as". | No |
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
| `start` |`integer`| | No |
| `end` |`integer`| | No |

View File

@ -1,16 +0,0 @@
---
title: "ItemVisibility"
excerpt: ""
layout: manual
---
**enum:** `default`, `export`

View File

@ -317,7 +317,6 @@ Data for an imported geometry.
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `Function`| | No |
| `expression` |[`FunctionExpression`](/docs/kcl/types/FunctionExpression)| Any KCL value. | No |
| `memory` |[`ProgramMemory`](/docs/kcl/types/ProgramMemory)| Any KCL value. | No |
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |

View File

@ -1,56 +0,0 @@
---
title: "LiteralIdentifier"
excerpt: ""
layout: manual
---
**This schema accepts exactly one of the following:**
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: [`Identifier`](/docs/kcl/types/Identifier)| | No |
| `name` |`string`| | No |
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
| `start` |`integer`| | No |
| `end` |`integer`| | No |
----
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `Literal`| | No |
| `value` |[`LiteralValue`](/docs/kcl/types/LiteralValue)| | No |
| `raw` |`string`| | No |
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
| `start` |`integer`| | No |
| `end` |`integer`| | No |
----

View File

@ -1,47 +0,0 @@
---
title: "LiteralValue"
excerpt: ""
layout: manual
---
**This schema accepts any of the following:**
**Type:** `number` (`double`)
----
**Type:** `string`
----
**Type:** `boolean`
----

View File

@ -1,57 +0,0 @@
---
title: "MemberObject"
excerpt: ""
layout: manual
---
**This schema accepts exactly one of the following:**
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `MemberExpression`| | No |
| `object` |[`MemberObject`](/docs/kcl/types/MemberObject)| | No |
| `property` |[`LiteralIdentifier`](/docs/kcl/types/LiteralIdentifier)| | No |
| `computed` |`boolean`| | No |
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
| `start` |`integer`| | No |
| `end` |`integer`| | No |
----
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: [`Identifier`](/docs/kcl/types/Identifier)| | No |
| `name` |`string`| | No |
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
| `start` |`integer`| | No |
| `end` |`integer`| | No |
----

View File

@ -1,22 +0,0 @@
---
title: "NonCodeMeta"
excerpt: ""
layout: manual
---
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `nonCodeNodes` |`object`| | No |
| `startNodes` |`[` [`NonCodeNode`](/docs/kcl/types/NonCodeNode) `]`| | No |
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |

View File

@ -1,23 +0,0 @@
---
title: "NonCodeNode"
excerpt: ""
layout: manual
---
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `value` |[`NonCodeValue`](/docs/kcl/types/NonCodeValue)| | No |
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
| `start` |`integer`| | No |
| `end` |`integer`| | No |

View File

@ -1,86 +0,0 @@
---
title: "NonCodeValue"
excerpt: ""
layout: manual
---
**This schema accepts exactly one of the following:**
An inline comment. Here are examples: `1 + 1 // This is an inline comment`. `1 + 1 /* Here's another */`.
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `inlineComment`| | No |
| `value` |`string`| | No |
| `style` |[`CommentStyle`](/docs/kcl/types/CommentStyle)| | No |
----
A block comment. An example of this is the following: ```python,no_run /* This is a block comment */ 1 + 1 ``` Now this is important. The block comment is attached to the next line. This is always the case. Also the block comment doesn't have a new line above it. If it did it would be a `NewLineBlockComment`.
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `blockComment`| | No |
| `value` |`string`| | No |
| `style` |[`CommentStyle`](/docs/kcl/types/CommentStyle)| | No |
----
A block comment that has a new line above it. The user explicitly added a new line above the block comment.
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `newLineBlockComment`| | No |
| `value` |`string`| | No |
| `style` |[`CommentStyle`](/docs/kcl/types/CommentStyle)| | No |
----
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `newLine`| | No |
----

View File

@ -1,24 +0,0 @@
---
title: "ObjectProperty"
excerpt: ""
layout: manual
---
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `key` |[`Identifier`](/docs/kcl/types/Identifier)| | No |
| `value` |[`Expr`](/docs/kcl/types/Expr)| | No |
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
| `start` |`integer`| | No |
| `end` |`integer`| | No |

View File

@ -1,23 +0,0 @@
---
title: "Parameter"
excerpt: "Parameter of a KCL function."
layout: manual
---
Parameter of a KCL function.
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `identifier` |[`Identifier`](/docs/kcl/types/Identifier)| The parameter's label or name. | No |
| `optional` |`boolean`| Is the parameter optional? | No |
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |

View File

@ -1,26 +0,0 @@
---
title: "Program"
excerpt: "A KCL program top level, or function body."
layout: manual
---
A KCL program top level, or function body.
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `body` |`[` [`BodyItem`](/docs/kcl/types/BodyItem) `]`| | No |
| `nonCodeMeta` |[`NonCodeMeta`](/docs/kcl/types/NonCodeMeta)| A KCL program top level, or function body. | No |
| `shebang` |[`Shebang`](/docs/kcl/types/Shebang)| | No |
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
| `start` |`integer`| | No |
| `end` |`integer`| | No |

View File

@ -1,23 +0,0 @@
---
title: "Shebang"
excerpt: "A shebang. This is a special type of comment that is at the top of the file. It looks like this: ```python,no_run #!/usr/bin/env python ```"
layout: manual
---
A shebang. This is a special type of comment that is at the top of the file. It looks like this: ```python,no_run #!/usr/bin/env python ```
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `content` |`string`| | No |
| `start` |`integer`| | No |
| `end` |`integer`| | No |

View File

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

View File

@ -1,41 +0,0 @@
---
title: "UnaryOperator"
excerpt: ""
layout: manual
---
**This schema accepts exactly one of the following:**
Negate a number.
**enum:** `-`
----
Negate a boolean.
**enum:** `!`
----

View File

@ -1,24 +0,0 @@
---
title: "VariableDeclarator"
excerpt: ""
layout: manual
---
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `id` |[`Identifier`](/docs/kcl/types/Identifier)| The identifier of the variable. | No |
| `init` |[`Expr`](/docs/kcl/types/Expr)| The value of the variable. | No |
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
| `start` |`integer`| | No |
| `end` |`integer`| | No |

View File

@ -1,41 +0,0 @@
---
title: "VariableKind"
excerpt: ""
layout: manual
---
**This schema accepts exactly one of the following:**
Declare a named constant.
**enum:** `const`
----
Declare a function.
**enum:** `fn`
----

View File

@ -458,8 +458,8 @@ test.describe('Editor tests', () => {
/* add the following code to the editor ($ error is not a valid line)
$ error
const topAng = 30
const bottomAng = 25
topAng = 30
bottomAng = 25
*/
await u.codeLocator.click()
await page.keyboard.type('$ error')
@ -474,12 +474,14 @@ test.describe('Editor tests', () => {
await page.keyboard.type('bottomAng = 25')
await page.keyboard.press('Enter')
// error in guter
// error in gutter
await expect(page.locator('.cm-lint-marker-error')).toBeVisible()
// error text on hover
await page.hover('.cm-lint-marker-error')
await expect(page.getByText('Unexpected token: $').first()).toBeVisible()
await expect(
page.getByText('Tag names must not be empty').first()
).toBeVisible()
// select the line that's causing the error and delete it
await page.getByText('$ error').click()
@ -518,7 +520,10 @@ test.describe('Editor tests', () => {
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
})
test('error with 2 source ranges gets 2 diagnostics', async ({ page }) => {
// TODO currently multiple source ranges are not supported
test.skip('error with 2 source ranges gets 2 diagnostics', async ({
page,
}) => {
const u = await getUtils(page)
await page.addInitScript(async () => {
localStorage.setItem(

View File

@ -45,7 +45,6 @@ test.describe('integrations tests', () => {
{
title: 'test-sample',
fileCount: 1,
folderCount: 1,
},
],
sortBy: 'last-modified-desc',
@ -233,7 +232,6 @@ test.describe('when using the file tree to', () => {
{
title: projectName,
fileCount: 2,
folderCount: 2, // TODO: This is a pre-existing bug, there are no folders within the project
},
],
sortBy: 'last-modified-desc',

View File

@ -4,7 +4,6 @@ import { expect } from '@playwright/test'
interface ProjectCardState {
title: string
fileCount: number
folderCount: number
}
interface HomePageState {
@ -61,15 +60,13 @@ export class HomePageFixture {
const projectCards = await this.projectCard.all()
const projectCardStates: Array<ProjectCardState> = []
for (const projectCard of projectCards) {
const [title, fileCount, folderCount] = await Promise.all([
const [title, fileCount] = await Promise.all([
(await projectCard.locator(this.projectCardTitle).textContent()) || '',
Number(await projectCard.locator(this.projectCardFile).textContent()),
Number(await projectCard.locator(this.projectCardFolder).textContent()),
])
projectCardStates.push({
title: title,
fileCount,
folderCount,
})
}
return projectCardStates

View File

@ -6,6 +6,8 @@ export class ToolbarFixture {
public page: Page
extrudeButton!: Locator
loftButton!: Locator
shellButton!: Locator
offsetPlaneButton!: Locator
startSketchBtn!: Locator
lineBtn!: Locator
@ -26,6 +28,8 @@ export class ToolbarFixture {
reConstruct = (page: Page) => {
this.page = page
this.extrudeButton = page.getByTestId('extrude')
this.loftButton = page.getByTestId('loft')
this.shellButton = page.getByTestId('shell')
this.offsetPlaneButton = page.getByTestId('plane-offset')
this.startSketchBtn = page.getByTestId('sketch')
this.lineBtn = page.getByTestId('line')

View File

@ -677,3 +677,259 @@ test(`Offset plane point-and-click`, async ({
await scene.expectPixelColor([74, 74, 74], testPoint, 15)
})
})
const loftPointAndClickCases = [
{ shouldPreselect: true },
{ shouldPreselect: false },
]
loftPointAndClickCases.forEach(({ shouldPreselect }) => {
test(`Loft point-and-click (preselected sketches: ${shouldPreselect})`, async ({
app,
page,
scene,
editor,
toolbar,
cmdBar,
}) => {
const initialCode = `sketch001 = startSketchOn('XZ')
|> circle({ center = [0, 0], radius = 30 }, %)
plane001 = offsetPlane('XZ', 50)
sketch002 = startSketchOn(plane001)
|> circle({ center = [0, 0], radius = 20 }, %)
`
await app.initialise(initialCode)
// One dumb hardcoded screen pixel value
const testPoint = { x: 575, y: 200 }
const [clickOnSketch1] = scene.makeMouseHelpers(testPoint.x, testPoint.y)
const [clickOnSketch2] = scene.makeMouseHelpers(
testPoint.x,
testPoint.y + 80
)
const loftDeclaration = 'loft001 = loft([sketch001, sketch002])'
await test.step(`Look for the white of the sketch001 shape`, async () => {
await scene.expectPixelColor([254, 254, 254], testPoint, 15)
})
async function selectSketches() {
await clickOnSketch1()
await page.keyboard.down('Shift')
await clickOnSketch2()
await app.page.waitForTimeout(500)
await page.keyboard.up('Shift')
}
if (!shouldPreselect) {
await test.step(`Go through the command bar flow without preselected sketches`, async () => {
await toolbar.loftButton.click()
await cmdBar.expectState({
stage: 'arguments',
currentArgKey: 'selection',
currentArgValue: '',
headerArguments: { Selection: '' },
highlightedHeaderArg: 'selection',
commandName: 'Loft',
})
await selectSketches()
await cmdBar.progressCmdBar()
await cmdBar.expectState({
stage: 'review',
headerArguments: { Selection: '2 faces' },
commandName: 'Loft',
})
await cmdBar.progressCmdBar()
})
} else {
await test.step(`Preselect the two sketches`, async () => {
await selectSketches()
})
await test.step(`Go through the command bar flow with preselected sketches`, async () => {
await toolbar.loftButton.click()
await cmdBar.progressCmdBar()
await cmdBar.expectState({
stage: 'review',
headerArguments: { Selection: '2 faces' },
commandName: 'Loft',
})
await cmdBar.progressCmdBar()
})
}
await test.step(`Confirm code is added to the editor, scene has changed`, async () => {
await editor.expectEditor.toContain(loftDeclaration)
await editor.expectState({
diagnostics: [],
activeLines: [loftDeclaration],
highlightedCode: '',
})
await scene.expectPixelColor([89, 89, 89], testPoint, 15)
})
})
})
const shellPointAndClickCapCases = [
{ shouldPreselect: true },
{ shouldPreselect: false },
]
shellPointAndClickCapCases.forEach(({ shouldPreselect }) => {
test(`Shell point-and-click cap (preselected sketches: ${shouldPreselect})`, async ({
app,
scene,
editor,
toolbar,
cmdBar,
}) => {
const initialCode = `sketch001 = startSketchOn('XZ')
|> circle({ center = [0, 0], radius = 30 }, %)
extrude001 = extrude(30, sketch001)
`
await app.initialise(initialCode)
// One dumb hardcoded screen pixel value
const testPoint = { x: 575, y: 200 }
const [clickOnCap] = scene.makeMouseHelpers(testPoint.x, testPoint.y)
const shellDeclaration =
"shell001 = shell({ faces = ['end'], thickness = 5 }, extrude001)"
await test.step(`Look for the grey of the shape`, async () => {
await scene.expectPixelColor([127, 127, 127], testPoint, 15)
})
if (!shouldPreselect) {
await test.step(`Go through the command bar flow without preselected faces`, async () => {
await toolbar.shellButton.click()
await cmdBar.expectState({
stage: 'arguments',
currentArgKey: 'selection',
currentArgValue: '',
headerArguments: {
Selection: '',
Thickness: '',
},
highlightedHeaderArg: 'selection',
commandName: 'Shell',
})
await clickOnCap()
await app.page.waitForTimeout(500)
await cmdBar.progressCmdBar()
await cmdBar.progressCmdBar()
await cmdBar.expectState({
stage: 'review',
headerArguments: {
Selection: '1 cap',
Thickness: '5',
},
commandName: 'Shell',
})
await cmdBar.progressCmdBar()
})
} else {
await test.step(`Preselect the cap`, async () => {
await clickOnCap()
await app.page.waitForTimeout(500)
})
await test.step(`Go through the command bar flow with a preselected face (cap)`, async () => {
await toolbar.shellButton.click()
await cmdBar.progressCmdBar()
await cmdBar.progressCmdBar()
await cmdBar.expectState({
stage: 'review',
headerArguments: {
Selection: '1 cap',
Thickness: '5',
},
commandName: 'Shell',
})
await cmdBar.progressCmdBar()
})
}
await test.step(`Confirm code is added to the editor, scene has changed`, async () => {
await editor.expectEditor.toContain(shellDeclaration)
await editor.expectState({
diagnostics: [],
activeLines: [shellDeclaration],
highlightedCode: '',
})
await scene.expectPixelColor([146, 146, 146], testPoint, 15)
})
})
})
test('Shell point-and-click wall', async ({
app,
page,
scene,
editor,
toolbar,
cmdBar,
}) => {
const initialCode = `sketch001 = startSketchOn('XY')
|> startProfileAt([-20, 20], %)
|> xLine(40, %)
|> yLine(-60, %)
|> xLine(-40, %)
|> lineTo([profileStartX(%), profileStartY(%)], %)
|> close(%)
extrude001 = extrude(40, sketch001)
`
await app.initialise(initialCode)
// One dumb hardcoded screen pixel value
const testPoint = { x: 580, y: 180 }
const [clickOnCap] = scene.makeMouseHelpers(testPoint.x, testPoint.y)
const [clickOnWall] = scene.makeMouseHelpers(testPoint.x, testPoint.y + 70)
const mutatedCode = 'xLine(-40, %, $seg01)'
const shellDeclaration =
"shell001 = shell({ faces = ['end', seg01], thickness = 5}, extrude001)"
const formattedOutLastLine = '}, extrude001)'
await test.step(`Look for the grey of the shape`, async () => {
await scene.expectPixelColor([99, 99, 99], testPoint, 15)
})
await test.step(`Go through the command bar flow, selecting a wall and keeping default thickness`, async () => {
await toolbar.shellButton.click()
await cmdBar.expectState({
stage: 'arguments',
currentArgKey: 'selection',
currentArgValue: '',
headerArguments: {
Selection: '',
Thickness: '',
},
highlightedHeaderArg: 'selection',
commandName: 'Shell',
})
await clickOnCap()
await page.keyboard.down('Shift')
await clickOnWall()
await app.page.waitForTimeout(500)
await page.keyboard.up('Shift')
await cmdBar.progressCmdBar()
await cmdBar.progressCmdBar()
await cmdBar.expectState({
stage: 'review',
headerArguments: {
Selection: '1 cap, 1 face',
Thickness: '5',
},
commandName: 'Shell',
})
await cmdBar.progressCmdBar()
})
await test.step(`Confirm code is added to the editor, scene has changed`, async () => {
await editor.expectEditor.toContain(mutatedCode)
await editor.expectEditor.toContain(shellDeclaration)
await editor.expectState({
diagnostics: [],
activeLines: [formattedOutLastLine],
highlightedCode: '',
})
await scene.expectPixelColor([49, 49, 49], testPoint, 15)
})
})

View File

@ -136,6 +136,335 @@ 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 ({ browserName }, testInfo) => {
const { electronApp, page } = await setupElectron({
testInfo,
folderSetupFn: async (dir) => {
const bracketDir = join(dir, 'bracket')
await fsp.mkdir(bracketDir, { recursive: true })
await fsp.copyFile(
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
join(bracketDir, 'main.kcl')
)
const errorDir = join(dir, 'broken-code')
await fsp.mkdir(errorDir, { recursive: true })
await fsp.copyFile(
executorInputPath('broken-code-test.kcl'),
join(errorDir, 'main.kcl')
)
},
})
await page.setViewportSize({ width: 1200, height: 500 })
const u = await getUtils(page)
page.on('console', console.log)
const pointOnModel = { x: 630, y: 280 }
await test.step('Opening the bracket project should load the stream', async () => {
// expect to see the text bracket
await expect(page.getByText('bracket')).toBeVisible()
await page.getByText('bracket').click()
await expect(page.getByTestId('loading')).toBeAttached()
await expect(page.getByTestId('loading')).not.toBeAttached({
timeout: 20_000,
})
await expect(
page.getByRole('button', { name: 'Start Sketch' })
).toBeEnabled({
timeout: 20_000,
})
// gray at this pixel means the stream has loaded in the most
// user way we can verify it (pixel color)
await expect
.poll(() => u.getGreatestPixDiff(pointOnModel, [85, 85, 85]), {
timeout: 10_000,
})
.toBeLessThan(15)
})
await test.step('Clicking the logo takes us back to the projects page / home', async () => {
await page.getByTestId('app-logo').click()
await expect(page.getByRole('link', { name: 'bracket' })).toBeVisible()
await expect(page.getByText('broken-code')).toBeVisible()
await expect(page.getByText('bracket')).toBeVisible()
await expect(page.getByText('New Project')).toBeVisible()
})
await test.step('opening broken code project should clear the scene and show the error', async () => {
// Go back home.
await expect(page.getByText('broken-code')).toBeVisible()
await page.getByText('broken-code').click()
// error in guter
await expect(page.locator('.cm-lint-marker-error')).toBeVisible()
// error text on hover
await page.hover('.cm-lint-marker-error')
const crypticErrorText = `Expected a tag declarator`
await expect(page.getByText(crypticErrorText).first()).toBeVisible()
// black pixel means the scene has been cleared.
await expect
.poll(() => u.getGreatestPixDiff(pointOnModel, [30, 30, 30]), {
timeout: 10_000,
})
.toBeLessThan(15)
})
await electronApp.close()
}
)
test(
'open a file in a project works and renders, open another file in different project that is empty, it should clear the scene',
{ tag: '@electron' },
async ({ browserName }, testInfo) => {
const { electronApp, page } = await setupElectron({
testInfo,
folderSetupFn: async (dir) => {
const bracketDir = join(dir, 'bracket')
await fsp.mkdir(bracketDir, { recursive: true })
await fsp.copyFile(
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
join(bracketDir, 'main.kcl')
)
const emptyDir = join(dir, 'empty')
await fsp.mkdir(emptyDir, { recursive: true })
await fsp.writeFile(join(emptyDir, 'main.kcl'), '')
},
})
await page.setViewportSize({ width: 1200, height: 500 })
const u = await getUtils(page)
page.on('console', console.log)
const pointOnModel = { x: 630, y: 280 }
await test.step('Opening the bracket project should load the stream', async () => {
// expect to see the text bracket
await expect(page.getByText('bracket')).toBeVisible()
await page.getByText('bracket').click()
await expect(page.getByTestId('loading')).toBeAttached()
await expect(page.getByTestId('loading')).not.toBeAttached({
timeout: 20_000,
})
await expect(
page.getByRole('button', { name: 'Start Sketch' })
).toBeEnabled({
timeout: 20_000,
})
// gray at this pixel means the stream has loaded in the most
// user way we can verify it (pixel color)
await expect
.poll(() => u.getGreatestPixDiff(pointOnModel, [85, 85, 85]), {
timeout: 10_000,
})
.toBeLessThan(15)
})
await test.step('Clicking the logo takes us back to the projects page / home', async () => {
await page.getByTestId('app-logo').click()
await expect(page.getByRole('link', { name: 'bracket' })).toBeVisible()
await expect(page.getByText('empty')).toBeVisible()
await expect(page.getByText('bracket')).toBeVisible()
await expect(page.getByText('New Project')).toBeVisible()
})
await test.step('opening empty code project should clear the scene', async () => {
// Go back home.
await expect(page.getByText('empty')).toBeVisible()
await page.getByText('empty').click()
// Ensure the code is empty.
await expect(u.codeLocator).toContainText('')
expect(u.codeLocator.innerHTML.length).toBeLessThan(2)
// planes colors means the scene has been cleared.
await expect
.poll(() => u.getGreatestPixDiff(pointOnModel, [92, 53, 53]), {
timeout: 10_000,
})
.toBeLessThan(15)
})
await electronApp.close()
}
)
test(
'open a file in a project works and renders, open empty file, it should clear the scene',
{ tag: '@electron' },
async ({ browserName }, testInfo) => {
const { electronApp, page } = await setupElectron({
testInfo,
folderSetupFn: async (dir) => {
const bracketDir = join(dir, 'bracket')
await fsp.mkdir(bracketDir, { recursive: true })
await fsp.copyFile(
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
join(bracketDir, 'main.kcl')
)
await fsp.writeFile(join(bracketDir, 'empty.kcl'), '')
},
})
await page.setViewportSize({ width: 1200, height: 500 })
const u = await getUtils(page)
page.on('console', console.log)
const pointOnModel = { x: 630, y: 280 }
await test.step('Opening the bracket project should load the stream', async () => {
// expect to see the text bracket
await expect(page.getByText('bracket')).toBeVisible()
await page.getByText('bracket').click()
await expect(page.getByTestId('loading')).toBeAttached()
await expect(page.getByTestId('loading')).not.toBeAttached({
timeout: 20_000,
})
await expect(
page.getByRole('button', { name: 'Start Sketch' })
).toBeEnabled({
timeout: 20_000,
})
// gray at this pixel means the stream has loaded in the most
// user way we can verify it (pixel color)
await expect
.poll(() => u.getGreatestPixDiff(pointOnModel, [85, 85, 85]), {
timeout: 10_000,
})
.toBeLessThan(15)
})
await test.step('creating a empty file should clear the scene', async () => {
// open the file pane.
await page.getByTestId('files-pane-button').click()
// OPen the other file.
const file = page.getByRole('button', { name: 'empty.kcl' })
await expect(file).toBeVisible()
await file.click()
// planes colors means the scene has been cleared.
await expect
.poll(() => u.getGreatestPixDiff(pointOnModel, [92, 53, 53]), {
timeout: 10_000,
})
.toBeLessThan(15)
// Ensure the code is empty.
await expect(u.codeLocator).toContainText('')
expect(u.codeLocator.innerHTML.length).toBeLessThan(2)
})
await electronApp.close()
}
)
test(
'open a file in a project works and renders, open another file in the same project with errors, it should clear the scene',
{ tag: '@electron' },
async ({ browserName }, testInfo) => {
const { electronApp, page } = await setupElectron({
testInfo,
folderSetupFn: async (dir) => {
const bracketDir = join(dir, 'bracket')
await fsp.mkdir(bracketDir, { recursive: true })
await fsp.copyFile(
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
join(bracketDir, 'main.kcl')
)
await fsp.copyFile(
executorInputPath('broken-code-test.kcl'),
join(bracketDir, 'broken-code-test.kcl')
)
},
})
await page.setViewportSize({ width: 1200, height: 500 })
const u = await getUtils(page)
page.on('console', console.log)
const pointOnModel = { x: 630, y: 280 }
await test.step('Opening the bracket project should load the stream', async () => {
// expect to see the text bracket
await expect(page.getByText('bracket')).toBeVisible()
await page.getByText('bracket').click()
await expect(page.getByTestId('loading')).toBeAttached()
await expect(page.getByTestId('loading')).not.toBeAttached({
timeout: 20_000,
})
await expect(
page.getByRole('button', { name: 'Start Sketch' })
).toBeEnabled({
timeout: 20_000,
})
// gray at this pixel means the stream has loaded in the most
// user way we can verify it (pixel color)
await expect
.poll(() => u.getGreatestPixDiff(pointOnModel, [85, 85, 85]), {
timeout: 10_000,
})
.toBeLessThan(15)
})
await test.step('opening broken code file should clear the scene and show the error', async () => {
// open the file pane.
await page.getByTestId('files-pane-button').click()
// OPen the other file.
const file = page.getByRole('button', { name: 'broken-code-test.kcl' })
await expect(file).toBeVisible()
await file.click()
// error in guter
await expect(page.locator('.cm-lint-marker-error')).toBeVisible()
// error text on hover
await page.hover('.cm-lint-marker-error')
const crypticErrorText = `Expected a tag declarator`
await expect(page.getByText(crypticErrorText).first()).toBeVisible()
// black pixel means the scene has been cleared.
await expect
.poll(() => u.getGreatestPixDiff(pointOnModel, [30, 30, 30]), {
timeout: 10_000,
})
.toBeLessThan(15)
})
await electronApp.close()
}
)
test(
'when code with error first loads you get errors in console',
{ tag: '@electron' },

View File

@ -550,7 +550,7 @@ sketch001 = startSketchAt([-0, -0])
const u = await getUtils(page)
// Constants and locators
const planeColor: [number, number, number] = [170, 220, 170]
const planeColor: [number, number, number] = [161, 220, 155]
const bgColor: [number, number, number] = [27, 27, 27]
const middlePixelIsColor = async (color: [number, number, number]) => {
return u.getGreatestPixDiff({ x: 600, y: 250 }, color)

View File

@ -7,6 +7,8 @@ try {
.split('\n')
.filter((line) => line && line.length > 1)
.forEach((line) => {
// Allow line comments.
if (line.trimStart().startsWith('#')) return
const [key, value] = line.split('=')
// prefer env vars over secrets file
secrets[key] = process.env[key] || (value as any).replaceAll('"', '')

View File

@ -943,6 +943,110 @@ sketch002 = startSketchOn(extrude001, 'END')
`.replace(/\s/g, '')
)
})
/* TODO: once we fix bug turn on.
test('empty-scene default-planes act as expected when spaces in file', async ({
page,
browserName,
}) => {
const u = await getUtils(page)
await page.setViewportSize({ width: 1200, height: 500 })
await u.waitForAuthSkipAppStart()
await u.openDebugPanel()
await u.expectCmdLog('[data-message-type="execution-done"]')
await u.closeDebugPanel()
const XYPlanePoint = { x: 774, y: 116 } as const
const unHoveredColor: [number, number, number] = [47, 47, 93]
expect(
await u.getGreatestPixDiff(XYPlanePoint, unHoveredColor)
).toBeLessThan(8)
await page.mouse.move(XYPlanePoint.x, XYPlanePoint.y)
await page.waitForTimeout(200)
// color should not change for having been hovered
expect(
await u.getGreatestPixDiff(XYPlanePoint, unHoveredColor)
).toBeLessThan(8)
await u.openAndClearDebugPanel()
// Fill with spaces
await u.codeLocator.fill(`
`)
await u.openDebugPanel()
await u.expectCmdLog('[data-message-type="execution-done"]')
await u.closeDebugPanel()
expect(
await u.getGreatestPixDiff(XYPlanePoint, unHoveredColor)
).toBeLessThan(8)
await page.mouse.move(XYPlanePoint.x, XYPlanePoint.y)
await page.waitForTimeout(200)
// color should not change for having been hovered
expect(
await u.getGreatestPixDiff(XYPlanePoint, unHoveredColor)
).toBeLessThan(8)
})
test('empty-scene default-planes act as expected when only code comments in file', async ({
page,
browserName,
}) => {
const u = await getUtils(page)
await page.setViewportSize({ width: 1200, height: 500 })
await u.waitForAuthSkipAppStart()
await u.openDebugPanel()
await u.expectCmdLog('[data-message-type="execution-done"]')
await u.closeDebugPanel()
const XYPlanePoint = { x: 774, y: 116 } as const
const unHoveredColor: [number, number, number] = [47, 47, 93]
expect(
await u.getGreatestPixDiff(XYPlanePoint, unHoveredColor)
).toBeLessThan(8)
await page.mouse.move(XYPlanePoint.x, XYPlanePoint.y)
await page.waitForTimeout(200)
// color should not change for having been hovered
expect(
await u.getGreatestPixDiff(XYPlanePoint, unHoveredColor)
).toBeLessThan(8)
await u.openAndClearDebugPanel()
// Fill with spaces
await u.codeLocator.fill(`// this is a code comments ya nerds
`)
await u.openDebugPanel()
await u.expectCmdLog('[data-message-type="execution-done"]')
await u.closeDebugPanel()
expect(
await u.getGreatestPixDiff(XYPlanePoint, unHoveredColor)
).toBeLessThan(8)
await page.mouse.move(XYPlanePoint.x, XYPlanePoint.y)
await page.waitForTimeout(200)
// color should not change for having been hovered
expect(
await u.getGreatestPixDiff(XYPlanePoint, unHoveredColor)
).toBeLessThan(8)
})*/
test('empty-scene default-planes act as expected', async ({
page,
browserName,

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 33 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: 53 KiB

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 36 KiB

View File

@ -26,7 +26,17 @@ test.describe('Testing constraints', () => {
})
const u = await getUtils(page)
const PUR = 400 / 37.5 //pixeltoUnitRatio
// constants and locators
const lengthValue = {
old: '20',
new: '25',
}
const cmdBarKclInput = page
.getByTestId('cmd-bar-arg-value')
.getByRole('textbox')
const cmdBarSubmitButton = page.getByRole('button', {
name: 'arrow right Continue',
})
await page.setViewportSize({ width: 1200, height: 500 })
await u.waitForAuthSkipAppStart()
@ -36,26 +46,26 @@ test.describe('Testing constraints', () => {
await u.closeDebugPanel()
// Click the line of code for line.
await page.getByText(`line([0, 20], %)`).click() // TODO remove this and reinstate // await topHorzSegmentClick()
// TODO remove this and reinstate `await topHorzSegmentClick()`
await page.getByText(`line([0, ${lengthValue.old}], %)`).click()
await page.waitForTimeout(100)
// enter sketch again
await page.getByRole('button', { name: 'Edit Sketch' }).click()
await page.waitForTimeout(500) // wait for animation
const startXPx = 500
await page.mouse.move(startXPx + PUR * 15, 250 - PUR * 10)
await page.keyboard.down('Shift')
await page.mouse.click(834, 244)
await page.keyboard.up('Shift')
await page
.getByRole('button', { name: 'dimension Length', exact: true })
.click()
await page.getByText('Add constraining value').click()
await expect(cmdBarKclInput).toHaveText('20')
await cmdBarKclInput.fill(lengthValue.new)
await expect(
page.getByText(`Can't calculate`),
`Something went wrong with the KCL expression evaluation`
).not.toBeVisible()
await cmdBarSubmitButton.click()
await expect(page.locator('.cm-content')).toHaveText(
`length001 = 20sketch001 = startSketchOn('XY') |> startProfileAt([-10, -10], %) |> line([20, 0], %) |> angledLine([90, length001], %) |> xLine(-20, %)`
`length001 = ${lengthValue.new}sketch001 = startSketchOn('XY') |> startProfileAt([-10, -10], %) |> line([20, 0], %) |> angledLine([90, length001], %) |> xLine(-20, %)`
)
// Make sure we didn't pop out of sketch mode.
@ -66,7 +76,6 @@ test.describe('Testing constraints', () => {
await page.waitForTimeout(500) // wait for animation
// Exit sketch
await page.mouse.move(startXPx + PUR * 15, 250 - PUR * 10)
await page.keyboard.press('Escape')
await expect(
page.getByRole('button', { name: 'Exit Sketch' })
@ -524,7 +533,7 @@ part002 = startSketchOn('XZ')
})
}
})
test.describe('Test Angle/Length constraint single selection', () => {
test.describe('Test Angle constraint single selection', () => {
const cases = [
{
testName: 'Angle - Add variable',
@ -538,18 +547,6 @@ part002 = startSketchOn('XZ')
constraint: 'angle',
value: '83, 78.33',
},
{
testName: 'Length - Add variable',
addVariable: true,
constraint: 'length',
value: '83, length001',
},
{
testName: 'Length - No variable',
addVariable: false,
constraint: 'length',
value: '83, 78.33',
},
] as const
for (const { testName, addVariable, value, constraint } of cases) {
test(`${testName}`, async ({ page }) => {
@ -608,6 +605,90 @@ part002 = startSketchOn('XZ')
})
}
})
test.describe('Test Length constraint single selection', () => {
const cases = [
{
testName: 'Length - Add variable',
addVariable: true,
constraint: 'length',
value: '83, length001',
},
{
testName: 'Length - No variable',
addVariable: false,
constraint: 'length',
value: '83, 78.33',
},
] as const
for (const { testName, addVariable, value, constraint } of cases) {
test(`${testName}`, async ({ page }) => {
// constants and locators
const cmdBarKclInput = page
.getByTestId('cmd-bar-arg-value')
.getByRole('textbox')
const cmdBarKclVariableNameInput =
page.getByPlaceholder('Variable name')
const cmdBarSubmitButton = page.getByRole('button', {
name: 'arrow right Continue',
})
await page.addInitScript(async () => {
localStorage.setItem(
'persistCode',
`yo = 5
part001 = startSketchOn('XZ')
|> startProfileAt([-7.54, -26.74], %)
|> line([74.36, 130.4], %)
|> line([78.92, -120.11], %)
|> line([9.16, 77.79], %)
|> line([51.19, 48.97], %)
part002 = startSketchOn('XZ')
|> startProfileAt([299.05, 231.45], %)
|> xLine(-425.34, %, $seg_what)
|> yLine(-264.06, %)
|> xLine(segLen(seg_what), %)
|> lineTo([profileStartX(%), profileStartY(%)], %)`
)
})
const u = await getUtils(page)
await page.setViewportSize({ width: 1200, height: 500 })
await u.waitForAuthSkipAppStart()
await page.getByText('line([74.36, 130.4], %)').click()
await page.getByRole('button', { name: 'Edit Sketch' }).click()
const line3 = await u.getSegmentBodyCoords(
`[data-overlay-index="${2}"]`
)
await page.mouse.click(line3.x, line3.y)
await page
.getByRole('button', {
name: 'Length: open menu',
})
.click()
await page.getByTestId('dropdown-constraint-' + constraint).click()
if (!addVariable) {
await test.step(`Clear the variable input`, async () => {
await cmdBarKclVariableNameInput.clear()
await cmdBarKclVariableNameInput.press('Backspace')
})
}
await expect(cmdBarKclInput).toHaveText('78.33')
await cmdBarSubmitButton.click()
const changedCode = `|> angledLine([${value}], %)`
await expect(page.locator('.cm-content')).toContainText(changedCode)
// checking active assures the cursor is where it should be
await expect(page.locator('.cm-activeLine')).toHaveText(changedCode)
// checking the count of the overlays is a good proxy check that the client sketch scene is in a good state
await expect(page.getByTestId('segment-overlay')).toHaveCount(4)
})
}
})
test.describe('Many segments - no modal constraints', () => {
const cases = [
{
@ -868,6 +949,15 @@ part002 = startSketchOn('XZ')
|> line([3.13, -2.4], %)`
)
})
// constants and locators
const cmdBarKclInput = page
.getByTestId('cmd-bar-arg-value')
.getByRole('textbox')
const cmdBarSubmitButton = page.getByRole('button', {
name: 'arrow right Continue',
})
const u = await getUtils(page)
await page.setViewportSize({ width: 1200, height: 500 })
@ -928,8 +1018,8 @@ part002 = startSketchOn('XZ')
// await page.getByRole('button', { name: 'length', exact: true }).click()
await page.getByTestId('dropdown-constraint-length').click()
await page.getByLabel('length Value').fill('10')
await page.getByRole('button', { name: 'Add constraining value' }).click()
await cmdBarKclInput.fill('10')
await cmdBarSubmitButton.click()
activeLinesContent = await page.locator('.cm-activeLine').all()
await expect(activeLinesContent[0]).toHaveText(`|> xLine(length001, %)`)

View File

@ -23,7 +23,7 @@ test.describe('Test toggling perspective', () => {
y: screenHeight * 0.4,
}
const backgroundColor: [number, number, number] = [29, 29, 29]
const xzPlaneColor: [number, number, number] = [50, 50, 99]
const xzPlaneColor: [number, number, number] = [82, 55, 96]
const locationToHaveColor = async (color: [number, number, number]) => {
return u.getGreatestPixDiff(checkedScreenLocation, color)
}

View File

@ -91,7 +91,14 @@ test.describe('Testing segment overlays', () => {
await page.getByTestId('constraint-symbol-popover').count()
).toBeGreaterThan(0)
await unconstrainedLocator.click()
await page.getByText('Add variable').click()
await expect(
page.getByTestId('cmd-bar-arg-value').getByRole('textbox')
).toBeFocused()
await page
.getByRole('button', {
name: 'arrow right Continue',
})
.click()
await expect(page.locator('.cm-content')).toContainText(expectFinal)
}
@ -151,7 +158,14 @@ test.describe('Testing segment overlays', () => {
await page.getByTestId('constraint-symbol-popover').count()
).toBeGreaterThan(0)
await unconstrainedLocator.click()
await page.getByText('Add variable').click()
await expect(
page.getByTestId('cmd-bar-arg-value').getByRole('textbox')
).toBeFocused()
await page
.getByRole('button', {
name: 'arrow right Continue',
})
.click()
await expect(page.locator('.cm-content')).toContainText(
expectAfterUnconstrained
)

View File

@ -81,6 +81,7 @@
"simpleserver": "yarn pretest && http-server ./public --cors -p 3000",
"simpleserver:ci": "yarn pretest && http-server ./public --cors -p 3000 &",
"simpleserver:bg": "yarn pretest && http-server ./public --cors -p 3000 &",
"simpleserver:stop": "kill-port 3000",
"fmt": "prettier --write ./src *.ts *.json *.js ./e2e ./packages",
"fmt-check": "prettier --check ./src *.ts *.json *.js ./e2e ./packages",
"fetch:wasm": "./get-latest-wasm-bundle.sh",
@ -95,6 +96,8 @@
"files:set-version": "echo \"$(jq --arg v \"$VERSION\" '.version=$v' package.json --indent 2)\" > package.json",
"files:set-notes": "./scripts/set-files-notes.sh",
"files:flip-to-nightly": "./scripts/flip-files-to-nightly.sh",
"files:invalidate-bucket": "./scripts/invalidate-files-bucket.sh",
"files:invalidate-bucket:nightly": "./scripts/invalidate-files-bucket.sh --nightly",
"postinstall": "yarn fetch:samples && yarn xstate:typegen && ./node_modules/.bin/electron-rebuild",
"xstate:typegen": "yarn xstate typegen \"src/**/*.ts?(x)\"",
"make:dev": "make dev",
@ -158,6 +161,7 @@
"@electron/rebuild": "^3.6.0",
"@iarna/toml": "^2.2.5",
"@lezer/generator": "^1.7.1",
"@nabla/vite-plugin-eslint": "^2.0.5",
"@playwright/test": "^1.46.1",
"@testing-library/jest-dom": "^5.14.1",
"@testing-library/react": "^15.0.2",
@ -170,7 +174,7 @@
"@types/pixelmatch": "^5.2.6",
"@types/pngjs": "^6.0.4",
"@types/react": "^18.3.4",
"@types/react-dom": "^18.2.25",
"@types/react-dom": "^18.3.1",
"@types/react-modal": "^3.16.3",
"@types/three": "^0.163.0",
"@types/ua-parser-js": "^0.7.39",
@ -192,7 +196,7 @@
"eslint-plugin-css-modules": "^2.12.0",
"eslint-plugin-import": "^2.30.0",
"eslint-plugin-suggest-no-throw": "^1.0.0",
"happy-dom": "^15.10.2",
"happy-dom": "^15.11.7",
"http-server": "^14.1.1",
"husky": "^9.1.5",
"kill-port": "^2.0.1",
@ -207,12 +211,11 @@
"ts-node": "^10.0.0",
"typescript": "^5.7.2",
"vite": "^5.4.6",
"vite-plugin-eslint": "^1.8.1",
"vite-plugin-package-version": "^1.1.0",
"vite-tsconfig-paths": "^4.3.2",
"vitest": "^1.6.0",
"vitest-webgl-canvas-mock": "^1.1.0",
"wasm-pack": "^0.13.0",
"wasm-pack": "^0.13.1",
"ws": "^8.17.0",
"yarn": "^1.22.22"
}

View File

@ -0,0 +1,5 @@
#!/bin/bash
echo "## What's Changed"
git log ${PREVIOUS_TAG}..HEAD --oneline --pretty=format:%s | grep -v Bump | awk '{print "* "toupper(substr($0,0,1))substr($0,2)}'
echo ""
echo "**Full Changelog**: https://github.com/KittyCAD/modeling-app/compare/${PREVIOUS_TAG}...${TAG}"

View File

@ -0,0 +1,11 @@
#!/bin/bash
base_dir="/releases/modeling-app"
if [[ $1 = "--nightly" ]]; then
base_dir="/releases/modeling-app/nightly"
fi
echo "Invalidating json and yml files at $base_dir in the download bucket"
gcloud compute url-maps invalidate-cdn-cache dl-url-map --path="$base_dir/last_download.json" --async
gcloud compute url-maps invalidate-cdn-cache dl-url-map --path="$base_dir/latest-linux-arm64.yml" --async
gcloud compute url-maps invalidate-cdn-cache dl-url-map --path="$base_dir/latest-mac.yml" --async
gcloud compute url-maps invalidate-cdn-cache dl-url-map --path="$base_dir/latest.yml" --async

View File

@ -200,7 +200,10 @@ function CoreDump() {
() => new CoreDumpManager(engineCommandManager, codeManager, token),
[]
)
useHotkeyWrapper(['mod + shift + .'], () => {
// TODO: revisit once progress is made on upstream issue
// https://github.com/JohannesKlauss/react-hotkeys-hook/issues/1064
// const hotkey = process.platform !== 'linux' ? 'mod + shift + .' : 'mod + shift + >'
useHotkeyWrapper(['mod + shift + .', 'mod + shift + >'], () => {
toast
.promise(
coreDump(coreDumpManager, true),

View File

@ -155,7 +155,6 @@ export class CameraControls {
this.camera.zoom = camProps.zoom || 1
}
this.camera.updateProjectionMatrix()
console.log('doing this thing', camProps)
this.update(true)
}
@ -273,14 +272,26 @@ export class CameraControls {
camSettings.center.y,
camSettings.center.z
)
const quat = new Quaternion(
const orientation = new Quaternion(
camSettings.orientation.x,
camSettings.orientation.y,
camSettings.orientation.z,
camSettings.orientation.w
).invert()
this.camera.up.copy(new Vector3(0, 1, 0).applyQuaternion(quat))
const newUp = new Vector3(
camSettings.up.x,
camSettings.up.y,
camSettings.up.z
)
this.camera.quaternion.set(
orientation.x,
orientation.y,
orientation.z,
orientation.w
)
this.camera.up.copy(newUp)
this.camera.updateProjectionMatrix()
if (this.camera instanceof PerspectiveCamera && camSettings.ortho) {
this.useOrthographicCamera()
}
@ -1164,7 +1175,7 @@ export class CameraControls {
this.camera.updateProjectionMatrix()
}
if (this.syncDirection === 'clientToEngine' || forceUpdate)
if (this.syncDirection === 'clientToEngine' || forceUpdate) {
this.throttledUpdateEngineCamera({
quaternion: this.camera.quaternion,
position: this.camera.position,
@ -1172,6 +1183,7 @@ export class CameraControls {
isPerspective: this.isPerspective,
target: this.target,
})
}
this.deferReactUpdate(this.reactCameraProperties)
Object.values(this._camChangeCallbacks).forEach((cb) => cb())
}

View File

@ -29,6 +29,9 @@ import {
Expr,
parse,
recast,
defaultSourceRange,
resultIsOk,
ProgramMemory,
} from 'lang/wasm'
import { CustomIcon, CustomIconName } from 'components/CustomIcon'
import { ConstrainInfo } from 'lang/std/stdTypes'
@ -412,14 +415,15 @@ export async function deleteSegment({
if (err(modifiedAst)) return Promise.reject(modifiedAst)
const newCode = recast(modifiedAst)
modifiedAst = parse(newCode)
if (err(modifiedAst)) return Promise.reject(modifiedAst)
const pResult = parse(newCode)
if (err(pResult) || !resultIsOk(pResult)) return Promise.reject(pResult)
modifiedAst = pResult.program
const testExecute = await executeAst({
ast: modifiedAst,
idGenerator: kclManager.execState.idGenerator,
useFakeExecutor: true,
engineCommandManager: engineCommandManager,
// We make sure to send an empty program memory to denote we mean mock mode.
programMemoryOverride: ProgramMemory.empty(),
})
if (testExecute.errors.length) {
toast.error('Segment tag used outside of current Sketch. Could not delete.')
@ -501,7 +505,8 @@ const ConstraintSymbol = ({
constrainInfo: ConstrainInfo
verticalPosition: 'top' | 'bottom'
}) => {
const { context, send } = useModelingContext()
const { commandBarSend } = useCommandsContext()
const { context } = useModelingContext()
const varNameMap: {
[key in ConstrainInfo['type']]: {
varName: string
@ -590,7 +595,9 @@ const ConstraintSymbol = ({
if (err(_node)) return
const node = _node.node
const range: SourceRange = node ? [node.start, node.end] : [0, 0]
const range: SourceRange = node
? [node.start, node.end, true]
: defaultSourceRange()
if (_type === 'intersectionTag') return null
@ -612,25 +619,34 @@ const ConstraintSymbol = ({
editorManager.setHighlightRange([range])
}}
onMouseLeave={() => {
editorManager.setHighlightRange([[0, 0]])
editorManager.setHighlightRange([defaultSourceRange()])
}}
// disabled={isConstrained || !convertToVarEnabled}
// disabled={implicitDesc} TODO why does this change styles that are hard to override?
onClick={toSync(async () => {
if (!isConstrained) {
send({
type: 'Convert to variable',
commandBarSend({
type: 'Find and select command',
data: {
pathToNode,
variableName: varName,
name: 'Constrain with named value',
groupId: 'modeling',
argDefaultValues: {
currentValue: {
pathToNode,
variableName: varName,
valueText: value,
},
},
},
})
} else if (isConstrained) {
try {
const parsed = parse(recast(kclManager.ast))
if (trap(parsed)) return Promise.reject(parsed)
const pResult = parse(recast(kclManager.ast))
if (trap(pResult) || !resultIsOk(pResult))
return Promise.reject(pResult)
const _node1 = getNodeFromPath<CallExpression>(
parsed,
pResult.program!,
pathToNode,
'CallExpression',
true

View File

@ -48,6 +48,9 @@ import {
VariableDeclarator,
sketchFromKclValue,
sketchFromKclValueOptional,
defaultSourceRange,
sourceRangeFromRust,
resultIsOk,
} from 'lang/wasm'
import {
engineCommandManager,
@ -495,10 +498,9 @@ export class SceneEntities {
const { execState } = await executeAst({
ast: truncatedAst,
useFakeExecutor: true,
engineCommandManager: this.engineCommandManager,
// We make sure to send an empty program memory to denote we mean mock mode.
programMemoryOverride,
idGenerator: kclManager.execState.idGenerator,
})
const programMemory = execState.memory
const sketch = sketchFromPathToNode({
@ -530,7 +532,7 @@ export class SceneEntities {
const segPathToNode = getNodePathFromSourceRange(
maybeModdedAst,
sketch.start.__geoMeta.sourceRange
sourceRangeFromRust(sketch.start.__geoMeta.sourceRange)
)
if (sketch?.paths?.[0]?.type !== 'Circle') {
const _profileStart = createProfileStartHandle({
@ -552,7 +554,7 @@ export class SceneEntities {
sketch.paths.forEach((segment, index) => {
let segPathToNode = getNodePathFromSourceRange(
maybeModdedAst,
segment.__geoMeta.sourceRange
sourceRangeFromRust(segment.__geoMeta.sourceRange)
)
if (
draftExpressionsIndices &&
@ -561,12 +563,12 @@ export class SceneEntities {
const previousSegment = sketch.paths[index - 1] || sketch.start
const previousSegmentPathToNode = getNodePathFromSourceRange(
maybeModdedAst,
previousSegment.__geoMeta.sourceRange
sourceRangeFromRust(previousSegment.__geoMeta.sourceRange)
)
const bodyIndex = previousSegmentPathToNode[1][0]
segPathToNode = getNodePathFromSourceRange(
truncatedAst,
segment.__geoMeta.sourceRange
sourceRangeFromRust(segment.__geoMeta.sourceRange)
)
segPathToNode[1][0] = bodyIndex
}
@ -575,7 +577,10 @@ export class SceneEntities {
index <= draftExpressionsIndices.end &&
index >= draftExpressionsIndices.start
const isSelected = selectionRanges?.graphSelections.some((selection) =>
isOverlap(selection?.codeRef?.range, segment.__geoMeta.sourceRange)
isOverlap(
selection?.codeRef?.range,
sourceRangeFromRust(segment.__geoMeta.sourceRange)
)
)
let seg: Group
@ -657,13 +662,11 @@ export class SceneEntities {
}
updateAstAndRejigSketch = async (
sketchPathToNode: PathToNode,
modifiedAst: Node<Program> | Error,
modifiedAst: Node<Program>,
forward: [number, number, number],
up: [number, number, number],
origin: [number, number, number]
) => {
if (err(modifiedAst)) return modifiedAst
const nextAst = await kclManager.updateAst(modifiedAst, false)
await this.tearDownSketch({ removeAxis: false })
sceneInfra.resetMouseListeners()
@ -698,8 +701,7 @@ export class SceneEntities {
'VariableDeclaration'
)
if (trap(_node1)) return Promise.reject(_node1)
const variableDeclarationName =
_node1.node?.declarations?.[0]?.id?.name || ''
const variableDeclarationName = _node1.node?.declaration.id?.name || ''
const sg = sketchFromKclValue(
kclManager.programMemory.get(variableDeclarationName),
@ -721,8 +723,9 @@ export class SceneEntities {
pathToNode: sketchPathToNode,
})
if (trap(mod)) return Promise.reject(mod)
const modifiedAst = parse(recast(mod.modifiedAst))
if (trap(modifiedAst)) return Promise.reject(modifiedAst)
const pResult = parse(recast(mod.modifiedAst))
if (trap(pResult) || !resultIsOk(pResult)) return Promise.reject(pResult)
const modifiedAst = pResult.program
const draftExpressionsIndices = { start: index, end: index }
@ -898,10 +901,9 @@ export class SceneEntities {
'VariableDeclaration'
)
if (trap(_node1)) return Promise.reject(_node1)
const variableDeclarationName =
_node1.node?.declarations?.[0]?.id?.name || ''
const startSketchOn = _node1.node?.declarations
const startSketchOnInit = startSketchOn?.[0]?.init
const variableDeclarationName = _node1.node?.declaration.id?.name || ''
const startSketchOn = _node1.node?.declaration
const startSketchOnInit = startSketchOn?.init
const tags: [string, string, string] = [
findUniqueName(_ast, 'rectangleSegmentA'),
@ -909,14 +911,14 @@ export class SceneEntities {
findUniqueName(_ast, 'rectangleSegmentC'),
]
startSketchOn[0].init = createPipeExpression([
startSketchOn.init = createPipeExpression([
startSketchOnInit,
...getRectangleCallExpressions(rectangleOrigin, tags),
])
let _recastAst = parse(recast(_ast))
if (trap(_recastAst)) return Promise.reject(_recastAst)
_ast = _recastAst
const pResult = parse(recast(_ast))
if (trap(pResult) || !resultIsOk(pResult)) return Promise.reject(pResult)
_ast = pResult.program
const { programMemoryOverride, truncatedAst } = await this.setupSketch({
sketchPathToNode,
@ -939,7 +941,7 @@ export class SceneEntities {
'VariableDeclaration'
)
if (trap(_node)) return Promise.reject(_node)
const sketchInit = _node.node?.declarations?.[0]?.init
const sketchInit = _node.node?.declaration.init
const x = (args.intersectionPoint.twoD.x || 0) - rectangleOrigin[0]
const y = (args.intersectionPoint.twoD.y || 0) - rectangleOrigin[1]
@ -950,10 +952,9 @@ export class SceneEntities {
const { execState } = await executeAst({
ast: truncatedAst,
useFakeExecutor: true,
engineCommandManager: this.engineCommandManager,
// We make sure to send an empty program memory to denote we mean mock mode.
programMemoryOverride,
idGenerator: kclManager.execState.idGenerator,
})
const programMemory = execState.memory
this.sceneProgramMemory = programMemory
@ -989,7 +990,7 @@ export class SceneEntities {
'VariableDeclaration'
)
if (trap(_node)) return
const sketchInit = _node.node?.declarations?.[0]?.init
const sketchInit = _node.node?.declaration.init
if (sketchInit.type !== 'PipeExpression') {
return
@ -998,9 +999,10 @@ export class SceneEntities {
updateRectangleSketch(sketchInit, x, y, tags[0])
const newCode = recast(_ast)
let _recastAst = parse(newCode)
if (trap(_recastAst)) return
_ast = _recastAst
const pResult = parse(newCode)
if (trap(pResult) || !resultIsOk(pResult))
return Promise.reject(pResult)
_ast = pResult.program
// Update the primary AST and unequip the rectangle tool
await kclManager.executeAstMock(_ast)
@ -1013,10 +1015,9 @@ export class SceneEntities {
const { execState } = await executeAst({
ast: _ast,
useFakeExecutor: true,
engineCommandManager: this.engineCommandManager,
// We make sure to send an empty program memory to denote we mean mock mode.
programMemoryOverride,
idGenerator: kclManager.execState.idGenerator,
})
const programMemory = execState.memory
@ -1055,10 +1056,9 @@ export class SceneEntities {
if (trap(_node1)) return Promise.reject(_node1)
// startSketchOn already exists
const variableDeclarationName =
_node1.node?.declarations?.[0]?.id?.name || ''
const startSketchOn = _node1.node?.declarations
const startSketchOnInit = startSketchOn?.[0]?.init
const variableDeclarationName = _node1.node?.declaration.id?.name || ''
const startSketchOn = _node1.node?.declaration
const startSketchOnInit = startSketchOn?.init
const tags: [string, string, string] = [
findUniqueName(_ast, 'rectangleSegmentA'),
@ -1066,14 +1066,14 @@ export class SceneEntities {
findUniqueName(_ast, 'rectangleSegmentC'),
]
startSketchOn[0].init = createPipeExpression([
startSketchOn.init = createPipeExpression([
startSketchOnInit,
...getRectangleCallExpressions(rectangleOrigin, tags),
])
let _recastAst = parse(recast(_ast))
if (trap(_recastAst)) return Promise.reject(_recastAst)
_ast = _recastAst
const pResult = parse(recast(_ast))
if (trap(pResult) || !resultIsOk(pResult)) return Promise.reject(pResult)
_ast = pResult.program
const { programMemoryOverride, truncatedAst } = await this.setupSketch({
sketchPathToNode,
@ -1096,7 +1096,7 @@ export class SceneEntities {
'VariableDeclaration'
)
if (trap(_node)) return Promise.reject(_node)
const sketchInit = _node.node?.declarations?.[0]?.init
const sketchInit = _node.node?.declaration.init
const x = (args.intersectionPoint.twoD.x || 0) - rectangleOrigin[0]
const y = (args.intersectionPoint.twoD.y || 0) - rectangleOrigin[1]
@ -1114,10 +1114,9 @@ export class SceneEntities {
const { execState } = await executeAst({
ast: truncatedAst,
useFakeExecutor: true,
engineCommandManager: this.engineCommandManager,
// We make sure to send an empty program memory to denote we mean mock mode.
programMemoryOverride,
idGenerator: kclManager.execState.idGenerator,
})
const programMemory = execState.memory
this.sceneProgramMemory = programMemory
@ -1153,7 +1152,7 @@ export class SceneEntities {
'VariableDeclaration'
)
if (trap(_node)) return
const sketchInit = _node.node?.declarations?.[0]?.init
const sketchInit = _node.node?.declaration.init
if (sketchInit.type === 'PipeExpression') {
updateCenterRectangleSketch(
@ -1165,9 +1164,10 @@ export class SceneEntities {
rectangleOrigin[1]
)
let _recastAst = parse(recast(_ast))
if (trap(_recastAst)) return
_ast = _recastAst
const pResult = parse(recast(_ast))
if (trap(pResult) || !resultIsOk(pResult))
return Promise.reject(pResult)
_ast = pResult.program
// Update the primary AST and unequip the rectangle tool
await kclManager.executeAstMock(_ast)
@ -1180,10 +1180,9 @@ export class SceneEntities {
const { execState } = await executeAst({
ast: _ast,
useFakeExecutor: true,
engineCommandManager: this.engineCommandManager,
// We make sure to send an empty program memory to denote we mean mock mode.
programMemoryOverride,
idGenerator: kclManager.execState.idGenerator,
})
const programMemory = execState.memory
@ -1222,12 +1221,11 @@ export class SceneEntities {
'VariableDeclaration'
)
if (trap(_node1)) return Promise.reject(_node1)
const variableDeclarationName =
_node1.node?.declarations?.[0]?.id?.name || ''
const startSketchOn = _node1.node?.declarations
const startSketchOnInit = startSketchOn?.[0]?.init
const variableDeclarationName = _node1.node?.declaration.id?.name || ''
const startSketchOn = _node1.node?.declaration
const startSketchOnInit = startSketchOn?.init
startSketchOn[0].init = createPipeExpression([
startSketchOn.init = createPipeExpression([
startSketchOnInit,
createCallExpressionStdLib('circle', [
createObjectExpression({
@ -1241,9 +1239,9 @@ export class SceneEntities {
]),
])
let _recastAst = parse(recast(_ast))
if (trap(_recastAst)) return Promise.reject(_recastAst)
_ast = _recastAst
const pResult = parse(recast(_ast))
if (trap(pResult) || !resultIsOk(pResult)) return Promise.reject(pResult)
_ast = pResult.program
// do a quick mock execution to get the program memory up-to-date
await kclManager.executeAstMock(_ast)
@ -1269,7 +1267,7 @@ export class SceneEntities {
)
let modded = structuredClone(truncatedAst)
if (trap(_node)) return
const sketchInit = _node.node?.declarations?.[0]?.init
const sketchInit = _node.node.declaration.init
const x = (args.intersectionPoint.twoD.x || 0) - circleCenter[0]
const y = (args.intersectionPoint.twoD.y || 0) - circleCenter[1]
@ -1299,10 +1297,9 @@ export class SceneEntities {
const { execState } = await executeAst({
ast: modded,
useFakeExecutor: true,
engineCommandManager: this.engineCommandManager,
// We make sure to send an empty program memory to denote we mean mock mode.
programMemoryOverride,
idGenerator: kclManager.execState.idGenerator,
})
const programMemory = execState.memory
this.sceneProgramMemory = programMemory
@ -1338,7 +1335,7 @@ export class SceneEntities {
'VariableDeclaration'
)
if (trap(_node)) return
const sketchInit = _node.node?.declarations?.[0]?.init
const sketchInit = _node.node?.declaration.init
let modded = structuredClone(_ast)
if (sketchInit.type === 'PipeExpression') {
@ -1365,9 +1362,10 @@ export class SceneEntities {
const newCode = recast(modded)
if (err(newCode)) return
let _recastAst = parse(newCode)
if (trap(_recastAst)) return Promise.reject(_recastAst)
_ast = _recastAst
const pResult = parse(newCode)
if (trap(pResult) || !resultIsOk(pResult))
return Promise.reject(pResult)
_ast = pResult.program
// Update the primary AST and unequip the rectangle tool
await kclManager.executeAstMock(_ast)
@ -1660,7 +1658,7 @@ export class SceneEntities {
kclManager.programMemory,
{
type: 'sourceRange',
sourceRange: [node.start, node.end],
sourceRange: [node.start, node.end, true],
},
getChangeSketchInput()
)
@ -1683,10 +1681,9 @@ export class SceneEntities {
codeManager.updateCodeEditor(code)
const { execState } = await executeAst({
ast: truncatedAst,
useFakeExecutor: true,
engineCommandManager: this.engineCommandManager,
// We make sure to send an empty program memory to denote we mean mock mode.
programMemoryOverride,
idGenerator: kclManager.execState.idGenerator,
})
const programMemory = execState.memory
this.sceneProgramMemory = programMemory
@ -1750,7 +1747,7 @@ export class SceneEntities {
): (() => SegmentOverlayPayload | null) => {
const segPathToNode = getNodePathFromSourceRange(
modifiedAst,
segment.__geoMeta.sourceRange
sourceRangeFromRust(segment.__geoMeta.sourceRange)
)
const sgPaths = sketch.paths
const originalPathToNodeStr = JSON.stringify(segPathToNode)
@ -1901,8 +1898,10 @@ export class SceneEntities {
SEGMENT_BODIES_PLUS_PROFILE_START
)
if (parent?.userData?.pathToNode) {
const updatedAst = parse(recast(kclManager.ast))
if (trap(updatedAst)) return
const pResult = parse(recast(kclManager.ast))
if (trap(pResult) || !resultIsOk(pResult))
return Promise.reject(pResult)
const updatedAst = pResult.program
const _node = getNodeFromPath<Node<CallExpression>>(
updatedAst,
parent.userData.pathToNode,
@ -1910,7 +1909,7 @@ export class SceneEntities {
)
if (trap(_node, { suppress: true })) return
const node = _node.node
editorManager.setHighlightRange([[node.start, node.end]])
editorManager.setHighlightRange([[node.start, node.end, true]])
const yellow = 0xffff00
colorSegment(selected, yellow)
const extraSegmentGroup = parent.getObjectByName(EXTRA_SEGMENT_HANDLE)
@ -1955,10 +1954,10 @@ export class SceneEntities {
})
return
}
editorManager.setHighlightRange([[0, 0]])
editorManager.setHighlightRange([defaultSourceRange()])
},
onMouseLeave: ({ selected, ...rest }: OnMouseEnterLeaveArgs) => {
editorManager.setHighlightRange([[0, 0]])
editorManager.setHighlightRange([defaultSourceRange()])
const parent = getParentGroup(
selected,
SEGMENT_BODIES_PLUS_PROFILE_START
@ -2057,7 +2056,7 @@ function prepareTruncatedMemoryAndAst(
'VariableDeclaration'
)
if (err(_node)) return _node
const variableDeclarationName = _node.node?.declarations?.[0]?.id?.name || ''
const variableDeclarationName = _node.node?.declaration.id?.name || ''
const sg = sketchFromKclValue(
programMemory.get(variableDeclarationName),
variableDeclarationName
@ -2082,28 +2081,30 @@ function prepareTruncatedMemoryAndAst(
])
}
;(
(_ast.body[bodyIndex] as VariableDeclaration).declarations[0]
(_ast.body[bodyIndex] as VariableDeclaration).declaration
.init as PipeExpression
).body.push(newSegment)
// update source ranges to section we just added.
// hacks like this wouldn't be needed if the AST put pathToNode info in memory/sketch segments
const updatedSrcRangeAst = parse(recast(_ast)) // get source ranges correct since unfortunately we still rely on them
if (err(updatedSrcRangeAst)) return updatedSrcRangeAst
const pResult = parse(recast(_ast)) // get source ranges correct since unfortunately we still rely on them
if (trap(pResult) || !resultIsOk(pResult))
return Error('Unexpected compilation error')
const updatedSrcRangeAst = pResult.program
const lastPipeItem = (
(updatedSrcRangeAst.body[bodyIndex] as VariableDeclaration)
.declarations[0].init as PipeExpression
(updatedSrcRangeAst.body[bodyIndex] as VariableDeclaration).declaration
.init as PipeExpression
).body.slice(-1)[0]
;(
(_ast.body[bodyIndex] as VariableDeclaration).declarations[0]
(_ast.body[bodyIndex] as VariableDeclaration).declaration
.init as PipeExpression
).body.slice(-1)[0].start = lastPipeItem.start
_ast.end = lastPipeItem.end
const varDec = _ast.body[bodyIndex] as Node<VariableDeclaration>
varDec.end = lastPipeItem.end
const declarator = varDec.declarations[0]
const declarator = varDec.declaration
declarator.end = lastPipeItem.end
const init = declarator.init as Node<PipeExpression>
init.end = lastPipeItem.end
@ -2140,7 +2141,7 @@ function prepareTruncatedMemoryAndAst(
if (node.type !== 'VariableDeclaration') {
continue
}
const name = node.declarations[0].id.name
const name = node.declaration.id.name
const memoryItem = programMemory.get(name)
if (!memoryItem) {
continue

View File

@ -5,6 +5,7 @@ import { useEffect, useRef, useState } from 'react'
import { trap } from 'lib/trap'
import { codeToIdSelections } from 'lib/selections'
import { codeRefFromRange } from 'lang/std/artifactGraph'
import { defaultSourceRange } from 'lang/wasm'
export function AstExplorer() {
const { context } = useModelingContext()
@ -46,7 +47,7 @@ export function AstExplorer() {
<div
className="h-full relative"
onMouseLeave={(e) => {
editorManager.setHighlightRange([[0, 0]])
editorManager.setHighlightRange([defaultSourceRange()])
}}
>
<pre className="text-xs">
@ -115,15 +116,19 @@ function DisplayObj({
hasCursor ? 'bg-violet-100/80 dark:bg-violet-100/25' : ''
}`}
onMouseEnter={(e) => {
editorManager.setHighlightRange([[obj?.start || 0, obj.end]])
editorManager.setHighlightRange([[obj?.start || 0, obj.end, true]])
e.stopPropagation()
}}
onMouseMove={(e) => {
e.stopPropagation()
editorManager.setHighlightRange([[obj?.start || 0, obj.end]])
editorManager.setHighlightRange([[obj?.start || 0, obj.end, true]])
}}
onClick={(e) => {
const range: [number, number] = [obj?.start || 0, obj.end || 0]
const range: [number, number, boolean] = [
obj?.start || 0,
obj.end || 0,
true,
]
const idInfo = codeToIdSelections([
{ codeRef: codeRefFromRange(range, kclManager.ast) },
])[0]

View File

@ -1,5 +1,11 @@
import { useEffect, useState, useRef } from 'react'
import { parse, BinaryPart, Expr, ProgramMemory } from '../lang/wasm'
import {
parse,
BinaryPart,
Expr,
ProgramMemory,
resultIsOk,
} from '../lang/wasm'
import {
createIdentifier,
createLiteral,
@ -141,8 +147,9 @@ export function useCalc({
useEffect(() => {
try {
const code = `const __result__ = ${value}`
const ast = parse(code)
if (trap(ast)) return
const pResult = parse(code)
if (trap(pResult) || !resultIsOk(pResult)) return
const ast = pResult.program
const _programMem: ProgramMemory = ProgramMemory.empty()
for (const { key, value } of availableVarInfo.variables) {
const error = _programMem.set(key, {
@ -156,18 +163,17 @@ export function useCalc({
executeAst({
ast,
engineCommandManager,
useFakeExecutor: true,
// We make sure to send an empty program memory to denote we mean mock mode.
programMemoryOverride: kclManager.programMemory.clone(),
idGenerator: kclManager.execState.idGenerator,
}).then(({ execState }) => {
const resultDeclaration = ast.body.find(
(a) =>
a.type === 'VariableDeclaration' &&
a.declarations?.[0]?.id?.name === '__result__'
a.declaration.id?.name === '__result__'
)
const init =
resultDeclaration?.type === 'VariableDeclaration' &&
resultDeclaration?.declarations?.[0]?.init
resultDeclaration?.declaration.init
const result = execState.memory?.get('__result__')?.value
setCalcResult(typeof result === 'number' ? String(result) : 'NAN')
init && setValueNode(init)

View File

@ -8,11 +8,16 @@ import { getSystemTheme } from 'lib/theme'
import { useCalculateKclExpression } from 'lib/useCalculateKclExpression'
import { roundOff } from 'lib/utils'
import { varMentions } from 'lib/varCompletionExtension'
import { useEffect, useRef, useState } from 'react'
import { useEffect, useMemo, useRef, useState } from 'react'
import { useHotkeys } from 'react-hotkeys-hook'
import styles from './CommandBarKclInput.module.css'
import { createIdentifier, createVariableDeclaration } from 'lang/modifyAst'
import { useCodeMirror } from 'components/ModelingSidebar/ModelingPanes/CodeEditor'
import { useSelector } from '@xstate/react'
const machineContextSelector = (snapshot?: {
context: Record<string, unknown>
}) => snapshot?.context
function CommandBarKclInput({
arg,
@ -31,12 +36,44 @@ function CommandBarKclInput({
arg.name
] as KclCommandValue | undefined
const { settings } = useSettingsAuthContext()
const defaultValue = (arg.defaultValue as string) || ''
const argMachineContext = useSelector(
arg.machineActor,
machineContextSelector
)
const defaultValue = useMemo(
() =>
arg.defaultValue
? arg.defaultValue instanceof Function
? arg.defaultValue(commandBarState.context, argMachineContext)
: arg.defaultValue
: '',
[arg.defaultValue, commandBarState.context, argMachineContext]
)
const initialVariableName = useMemo(() => {
// Use the configured variable name if it exists
if (arg.variableName !== undefined) {
return arg.variableName instanceof Function
? arg.variableName(commandBarState.context, argMachineContext)
: arg.variableName
}
// or derive it from the previously set value or the argument name
return previouslySetValue && 'variableName' in previouslySetValue
? previouslySetValue.variableName
: arg.name
}, [
arg.variableName,
commandBarState.context,
argMachineContext,
arg.name,
previouslySetValue,
])
const [value, setValue] = useState(
previouslySetValue?.valueText || defaultValue || ''
)
const [createNewVariable, setCreateNewVariable] = useState(
previouslySetValue && 'variableName' in previouslySetValue
(previouslySetValue && 'variableName' in previouslySetValue) ||
arg.createVariableByDefault ||
false
)
const [canSubmit, setCanSubmit] = useState(true)
useHotkeys('mod + k, mod + /', () => commandBarSend({ type: 'Close' }))
@ -52,10 +89,7 @@ function CommandBarKclInput({
isNewVariableNameUnique,
} = useCalculateKclExpression({
value,
initialVariableName:
previouslySetValue && 'variableName' in previouslySetValue
? previouslySetValue.variableName
: arg.name,
initialVariableName,
})
const varMentionData: Completion[] = prevVariables.map((v) => ({
label: v.key,

View File

@ -266,6 +266,7 @@ const FileTreeItem = ({
// Let the lsp servers know we closed a file.
onFileClose(currentFile?.path || null, project?.path || null)
onFileOpen(fileOrDir.path, project?.path || null)
kclManager.switchedFiles = true
// Open kcl files
navigate(`${PATHS.FILE}/${encodeURIComponent(fileOrDir.path)}`)

View File

@ -1,4 +1,4 @@
import { APP_VERSION } from 'routes/Settings'
import { APP_VERSION, RELEASE_URL } from 'routes/Settings'
import { CustomIcon } from 'components/CustomIcon'
import Tooltip from 'components/Tooltip'
import { PATHS } from 'lib/paths'
@ -72,10 +72,8 @@ export function LowerRightControls({
<menu className="flex items-center justify-end gap-3 pointer-events-auto">
{!location.pathname.startsWith(PATHS.HOME) && <ModelStateIndicator />}
<a
onClick={openExternalBrowserIfDesktop(
`https://github.com/KittyCAD/modeling-app/releases/tag/v${APP_VERSION}`
)}
href={`https://github.com/KittyCAD/modeling-app/releases/tag/v${APP_VERSION}`}
onClick={openExternalBrowserIfDesktop(RELEASE_URL)}
href={RELEASE_URL}
target="_blank"
rel="noopener noreferrer"
className={'!no-underline font-mono text-xs ' + linkOverrideClassName}

View File

@ -41,7 +41,10 @@ import {
angleBetweenInfo,
applyConstraintAngleBetween,
} from './Toolbar/SetAngleBetween'
import { applyConstraintAngleLength } from './Toolbar/setAngleLength'
import {
applyConstraintAngleLength,
applyConstraintLength,
} from './Toolbar/setAngleLength'
import {
canSweepSelection,
handleSelectionBatch,
@ -50,6 +53,9 @@ import {
isSketchPipe,
Selections,
updateSelections,
canLoftSelection,
canRevolveSelection,
canShellSelection,
} from 'lib/selections'
import { applyConstraintIntersect } from './Toolbar/Intersect'
import { applyConstraintAbsDistance } from './Toolbar/SetAbsDistance'
@ -61,13 +67,15 @@ import {
getSketchOrientationDetails,
} from 'clientSideScene/sceneEntities'
import {
moveValueIntoNewVariablePath,
insertNamedConstant,
replaceValueAtNodePath,
sketchOnExtrudedFace,
sketchOnOffsetPlane,
startSketchOnDefault,
} from 'lang/modifyAst'
import { Program, parse, recast } from 'lang/wasm'
import { PathToNode, Program, parse, recast, resultIsOk } from 'lang/wasm'
import {
doesSceneHaveExtrudedSketch,
doesSceneHaveSweepableSketch,
getNodePathFromSourceRange,
isSingleCursorInPipe,
@ -78,11 +86,10 @@ import toast from 'react-hot-toast'
import { EditorSelection, Transaction } from '@codemirror/state'
import { useLoaderData, useNavigate, useSearchParams } from 'react-router-dom'
import { letEngineAnimateAndSyncCamAfter } from 'clientSideScene/CameraControls'
import { getVarNameModal } from 'hooks/useToolbarGuards'
import { err, reportRejection, trap } from 'lib/trap'
import { useCommandsContext } from 'hooks/useCommandsContext'
import { modelingMachineEvent } from 'editor/manager'
import { hasValidFilletSelection } from 'lang/modifyAst/addFillet'
import { hasValidEdgeTreatmentSelection } from 'lang/modifyAst/addEdgeTreatment'
import {
ExportIntent,
EngineConnectionStateType,
@ -569,6 +576,59 @@ export const ModelingMachineProvider = ({
if (err(canSweep)) return false
return canSweep
},
'has valid revolve selection': ({ context: { selectionRanges } }) => {
// A user can begin extruding if they either have 1+ faces selected or nothing selected
// TODO: I believe this guard only allows for extruding a single face at a time
const hasNoSelection =
selectionRanges.graphSelections.length === 0 ||
isRangeBetweenCharacters(selectionRanges) ||
isSelectionLastLine(selectionRanges, codeManager.code)
if (hasNoSelection) {
// they have no selection, we should enable the button
// so they can select the face through the cmdbar
// BUT only if there's extrudable geometry
return doesSceneHaveSweepableSketch(kclManager.ast)
}
if (!isSketchPipe(selectionRanges)) return false
const canSweep = canRevolveSelection(selectionRanges)
if (err(canSweep)) return false
return canSweep
},
'has valid loft selection': ({ context: { selectionRanges } }) => {
const hasNoSelection =
selectionRanges.graphSelections.length === 0 ||
isRangeBetweenCharacters(selectionRanges) ||
isSelectionLastLine(selectionRanges, codeManager.code)
if (hasNoSelection) {
const count = 2
return doesSceneHaveSweepableSketch(kclManager.ast, count)
}
const canLoft = canLoftSelection(selectionRanges)
if (err(canLoft)) return false
return canLoft
},
'has valid shell selection': ({
context: { selectionRanges },
event,
}) => {
const hasNoSelection =
selectionRanges.graphSelections.length === 0 ||
isRangeBetweenCharacters(selectionRanges) ||
isSelectionLastLine(selectionRanges, codeManager.code)
if (hasNoSelection) {
return doesSceneHaveExtrudedSketch(kclManager.ast)
}
const canShell = canShellSelection(selectionRanges)
console.log('canShellSelection', canShellSelection(selectionRanges))
if (err(canShell)) return false
return canShell
},
'has valid selection for deletion': ({
context: { selectionRanges },
}) => {
@ -576,8 +636,10 @@ export const ModelingMachineProvider = ({
if (selectionRanges.graphSelections.length <= 0) return false
return true
},
'has valid fillet selection': ({ context: { selectionRanges } }) => {
return hasValidFilletSelection({
'has valid edge treatment selection': ({
context: { selectionRanges },
}) => {
return hasValidEdgeTreatmentSelection({
selectionRanges,
ast: kclManager.ast,
code: codeManager.code,
@ -594,15 +656,11 @@ export const ModelingMachineProvider = ({
)
},
'Has exportable geometry': () => {
if (
kclManager.kclErrors.length === 0 &&
kclManager.ast.body.length > 0
)
if (!kclManager.hasErrors() && kclManager.ast.body.length > 0)
return true
else {
let errorMessage = 'Unable to Export '
if (kclManager.kclErrors.length > 0)
errorMessage += 'due to KCL Errors'
if (kclManager.hasErrors()) errorMessage += 'due to KCL Errors'
else if (kclManager.ast.body.length === 0)
errorMessage += 'due to Empty Scene'
console.error(errorMessage)
@ -720,7 +778,11 @@ export const ModelingMachineProvider = ({
constraint: 'setHorzDistance',
selectionRanges,
})
const _modifiedAst = parse(recast(modifiedAst))
const pResult = parse(recast(modifiedAst))
if (trap(pResult) || !resultIsOk(pResult))
return Promise.reject(new Error('Unexpected compilation error'))
const _modifiedAst = pResult.program
if (!sketchDetails)
return Promise.reject(new Error('No sketch details'))
const updatedPathToNode = updatePathToNodeFromMap(
@ -761,7 +823,10 @@ export const ModelingMachineProvider = ({
constraint: 'setVertDistance',
selectionRanges,
})
const _modifiedAst = parse(recast(modifiedAst))
const pResult = parse(recast(modifiedAst))
if (trap(pResult) || !resultIsOk(pResult))
return Promise.reject(new Error('Unexpected compilation error'))
const _modifiedAst = pResult.program
if (!sketchDetails)
return Promise.reject(new Error('No sketch details'))
const updatedPathToNode = updatePathToNodeFromMap(
@ -809,7 +874,10 @@ export const ModelingMachineProvider = ({
selectionRanges,
angleOrLength: 'setAngle',
}))
const _modifiedAst = parse(recast(modifiedAst))
const pResult = parse(recast(modifiedAst))
if (trap(pResult) || !resultIsOk(pResult))
return Promise.reject(new Error('Unexpected compilation error'))
const _modifiedAst = pResult.program
if (err(_modifiedAst)) return Promise.reject(_modifiedAst)
if (!sketchDetails)
@ -845,13 +913,22 @@ export const ModelingMachineProvider = ({
}
}
),
'Get length info': fromPromise(
async ({ input: { selectionRanges, sketchDetails } }) => {
const { modifiedAst, pathToNodeMap } =
await applyConstraintAngleLength({
selectionRanges,
})
const _modifiedAst = parse(recast(modifiedAst))
astConstrainLength: fromPromise(
async ({
input: { selectionRanges, sketchDetails, lengthValue },
}) => {
if (!lengthValue)
return Promise.reject(new Error('No length value'))
const constraintResult = await applyConstraintLength({
selectionRanges,
length: lengthValue,
})
if (err(constraintResult)) return Promise.reject(constraintResult)
const { modifiedAst, pathToNodeMap } = constraintResult
const pResult = parse(recast(modifiedAst))
if (trap(pResult) || !resultIsOk(pResult))
return Promise.reject(new Error('Unexpected compilation error'))
const _modifiedAst = pResult.program
if (!sketchDetails)
return Promise.reject(new Error('No sketch details'))
const updatedPathToNode = updatePathToNodeFromMap(
@ -891,7 +968,10 @@ export const ModelingMachineProvider = ({
await applyConstraintIntersect({
selectionRanges,
})
const _modifiedAst = parse(recast(modifiedAst))
const pResult = parse(recast(modifiedAst))
if (trap(pResult) || !resultIsOk(pResult))
return Promise.reject(new Error('Unexpected compilation error'))
const _modifiedAst = pResult.program
if (!sketchDetails)
return Promise.reject(new Error('No sketch details'))
const updatedPathToNode = updatePathToNodeFromMap(
@ -932,7 +1012,10 @@ export const ModelingMachineProvider = ({
constraint: 'xAbs',
selectionRanges,
})
const _modifiedAst = parse(recast(modifiedAst))
const pResult = parse(recast(modifiedAst))
if (trap(pResult) || !resultIsOk(pResult))
return Promise.reject(new Error('Unexpected compilation error'))
const _modifiedAst = pResult.program
if (!sketchDetails)
return Promise.reject(new Error('No sketch details'))
const updatedPathToNode = updatePathToNodeFromMap(
@ -973,7 +1056,10 @@ export const ModelingMachineProvider = ({
constraint: 'yAbs',
selectionRanges,
})
const _modifiedAst = parse(recast(modifiedAst))
const pResult = parse(recast(modifiedAst))
if (trap(pResult) || !resultIsOk(pResult))
return Promise.reject(new Error('Unexpected compilation error'))
const _modifiedAst = pResult.program
if (!sketchDetails)
return Promise.reject(new Error('No sketch details'))
const updatedPathToNode = updatePathToNodeFromMap(
@ -1007,33 +1093,88 @@ export const ModelingMachineProvider = ({
}
}
),
'Get convert to variable info': fromPromise(
'Apply named value constraint': fromPromise(
async ({ input: { selectionRanges, sketchDetails, data } }) => {
if (!sketchDetails)
if (!sketchDetails) {
return Promise.reject(new Error('No sketch details'))
const { variableName } = await getVarNameModal({
valueName: data?.variableName || 'var',
})
let parsed = parse(recast(kclManager.ast))
if (trap(parsed)) return Promise.reject(parsed)
parsed = parsed as Node<Program>
}
if (!data) {
return Promise.reject(new Error('No data from command flow'))
}
let pResult = parse(recast(kclManager.ast))
if (trap(pResult) || !resultIsOk(pResult))
return Promise.reject(new Error('Unexpected compilation error'))
let parsed = pResult.program
const { modifiedAst: _modifiedAst, pathToReplacedNode } =
moveValueIntoNewVariablePath(
parsed,
kclManager.programMemory,
data?.pathToNode || [],
variableName
let result: {
modifiedAst: Node<Program>
pathToReplaced: PathToNode | null
} = {
modifiedAst: parsed,
pathToReplaced: null,
}
// If the user provided a constant name,
// we need to insert the named constant
// and then replace the node with the constant's name.
if ('variableName' in data.namedValue) {
const astAfterReplacement = replaceValueAtNodePath({
ast: parsed,
pathToNode: data.currentValue.pathToNode,
newExpressionString: data.namedValue.variableName,
})
if (trap(astAfterReplacement)) {
return Promise.reject(astAfterReplacement)
}
const parseResultAfterInsertion = parse(
recast(
insertNamedConstant({
node: astAfterReplacement.modifiedAst,
newExpression: data.namedValue,
})
)
)
parsed = parse(recast(_modifiedAst))
if (
trap(parseResultAfterInsertion) ||
!resultIsOk(parseResultAfterInsertion)
)
return Promise.reject(parseResultAfterInsertion)
result = {
modifiedAst: parseResultAfterInsertion.program,
pathToReplaced: astAfterReplacement.pathToReplaced,
}
} else if ('valueText' in data.namedValue) {
// If they didn't provide a constant name,
// just replace the node with the value.
const astAfterReplacement = replaceValueAtNodePath({
ast: parsed,
pathToNode: data.currentValue.pathToNode,
newExpressionString: data.namedValue.valueText,
})
if (trap(astAfterReplacement)) {
return Promise.reject(astAfterReplacement)
}
// The `replacer` function returns a pathToNode that assumes
// an identifier is also being inserted into the AST, creating an off-by-one error.
// This corrects that error, but TODO we should fix this upstream
// to avoid this kind of error in the future.
astAfterReplacement.pathToReplaced[1][0] =
(astAfterReplacement.pathToReplaced[1][0] as number) - 1
result = astAfterReplacement
}
pResult = parse(recast(result.modifiedAst))
if (trap(pResult) || !resultIsOk(pResult))
return Promise.reject(new Error('Unexpected compilation error'))
parsed = pResult.program
if (trap(parsed)) return Promise.reject(parsed)
parsed = parsed as Node<Program>
if (!pathToReplacedNode)
if (!result.pathToReplaced)
return Promise.reject(new Error('No path to replaced node'))
const updatedAst =
await sceneEntitiesManager.updateAstAndRejigSketch(
pathToReplacedNode || [],
result.pathToReplaced || [],
parsed,
sketchDetails.zAxis,
sketchDetails.yAxis,
@ -1046,7 +1187,7 @@ export const ModelingMachineProvider = ({
)
const selection = updateSelections(
{ 0: pathToReplacedNode },
{ 0: result.pathToReplaced },
selectionRanges,
updatedAst.newAst
)
@ -1054,7 +1195,7 @@ export const ModelingMachineProvider = ({
return {
selectionType: 'completeSelection',
selection,
updatedPathToNode: pathToReplacedNode,
updatedPathToNode: result.pathToReplaced,
}
}
),

View File

@ -76,7 +76,7 @@ export const ModelingPane = ({
return (
<section
{...props}
title={title && typeof title === 'string' ? title : ''}
aria-label={title && typeof title === 'string' ? title : ''}
data-testid={detailsTestId}
id={id}
className={

View File

@ -40,7 +40,9 @@ export const KclEditorMenu = ({ children }: PropsWithChildren) => {
<Menu.Items className="absolute right-0 left-auto w-72 flex flex-col gap-1 divide-y divide-chalkboard-20 dark:divide-chalkboard-70 align-stretch px-0 py-1 bg-chalkboard-10 dark:bg-chalkboard-100 rounded-sm shadow-lg border border-solid border-chalkboard-20/50 dark:border-chalkboard-80/50">
<Menu.Item>
<button
onClick={() => kclManager.format()}
onClick={() => {
kclManager.format().catch(reportRejection)
}}
className={styles.button}
>
<span>Format code</span>

View File

@ -1,6 +1,6 @@
import { processMemory } from './MemoryPane'
import { enginelessExecutor } from '../../../lib/testHelpers'
import { initPromise, parse, ProgramMemory } from '../../../lang/wasm'
import { assertParse, initPromise, ProgramMemory } from '../../../lang/wasm'
beforeAll(async () => {
await initPromise
@ -28,12 +28,16 @@ describe('processMemory', () => {
|> lineTo([0.98, 5.16], %)
|> lineTo([2.15, 4.32], %)
// |> rx(90, %)`
const ast = parse(code)
const ast = assertParse(code)
const execState = await enginelessExecutor(ast, ProgramMemory.empty())
const output = processMemory(execState.memory)
expect(output.myVar).toEqual(5)
expect(output.otherVar).toEqual(3)
expect(output).toEqual({
HALF_TURN: 180,
QUARTER_TURN: 90,
THREE_QUARTER_TURN: 270,
ZERO: 0,
myVar: 5,
myFn: '__function(a)__',
otherVar: 3,

View File

@ -90,7 +90,7 @@ export const sidebarPanes: SidebarPane[] = [
keybinding: 'Shift + C',
showBadge: {
value: ({ kclContext }) => {
return kclContext.errors.length
return kclContext.diagnostics.length
},
onClick: (e) => {
e.preventDefault()

View File

@ -53,7 +53,7 @@ export function ModelingSidebar({ paneOpacity }: ModelingSidebarProps) {
settings: settings.context,
platform: getPlatformString(),
}),
[kclContext.errors, settings.context]
[kclContext.diagnostics, settings.context]
)
const sidebarActions: SidebarAction[] = [

View File

@ -10,7 +10,7 @@ import { APP_NAME } from 'lib/constants'
import { useCommandsContext } from 'hooks/useCommandsContext'
import { CustomIcon } from './CustomIcon'
import { useLspContext } from './LspProvider'
import { engineCommandManager } from 'lib/singletons'
import { engineCommandManager, kclManager } from 'lib/singletons'
import { MachineManagerContext } from 'components/MachineManagerProvider'
import usePlatform from 'hooks/usePlatform'
import { useAbsoluteFilePath } from 'hooks/useAbsoluteFilePath'
@ -68,8 +68,7 @@ function AppLogoLink({
data-testid="app-logo"
onClick={() => {
onProjectClose(file || null, project?.path || null, false)
// Clear the scene and end the session.
engineCommandManager.endSession()
kclManager.switchedFiles = true
}}
to={PATHS.HOME}
className={wrapperClassName + ' hover:before:brightness-110'}
@ -190,8 +189,7 @@ function ProjectMenuPopover({
className: !isDesktop() ? 'hidden' : '',
onClick: () => {
onProjectClose(file || null, project?.path || null, true)
// Clear the scene and end the session.
engineCommandManager.endSession()
kclManager.switchedFiles = true
},
},
].filter(

View File

@ -10,7 +10,7 @@ interface AllKeybindingsFieldsProps {}
export const AllKeybindingsFields = forwardRef(
(
props: AllKeybindingsFieldsProps,
_props: AllKeybindingsFieldsProps,
scrollRef: ForwardedRef<HTMLDivElement>
) => {
// This is how we will get the interaction map from the context
@ -25,7 +25,7 @@ export const AllKeybindingsFields = forwardRef(
.map(([category, categoryItems]) => (
<div className="flex flex-col gap-4 px-2 pr-4">
<h2
id={`category-${category}`}
id={`category-${category.replaceAll(/\s/g, '-')}`}
className="text-xl mt-6 first-of-type:mt-0 capitalize font-bold"
>
{category}

View File

@ -13,7 +13,7 @@ import { isDesktop } from 'lib/isDesktop'
import { ActionButton } from 'components/ActionButton'
import { SettingsFieldInput } from './SettingsFieldInput'
import toast from 'react-hot-toast'
import { APP_VERSION, PACKAGE_NAME } from 'routes/Settings'
import { APP_VERSION, IS_NIGHTLY, RELEASE_URL } from 'routes/Settings'
import { PATHS } from 'lib/paths'
import {
createAndOpenNewTutorialProject,
@ -246,10 +246,8 @@ export const AllSettingsFields = forwardRef(
to inject the version from package.json */}
App version {APP_VERSION}.{' '}
<a
onClick={openExternalBrowserIfDesktop(
`https://github.com/KittyCAD/modeling-app/releases/tag/v${APP_VERSION}`
)}
href={`https://github.com/KittyCAD/modeling-app/releases/tag/v${APP_VERSION}`}
onClick={openExternalBrowserIfDesktop(RELEASE_URL)}
href={RELEASE_URL}
target="_blank"
rel="noopener noreferrer"
>
@ -271,7 +269,7 @@ export const AllSettingsFields = forwardRef(
, and start a discussion if you don't see it! Your feedback will
help us prioritize what to build next.
</p>
{PACKAGE_NAME.indexOf('-nightly') === -1 && (
{!IS_NIGHTLY && (
<p className="max-w-2xl mt-6">
Want to experience the latest and (hopefully) greatest from our
main development branch?{' '}

View File

@ -19,7 +19,7 @@ export function KeybindingsSectionsList({
key={category}
onClick={() =>
scrollRef.current
?.querySelector(`#category-${category}`)
?.querySelector(`#category-${category.replaceAll(/\s/g, '-')}`)
?.scrollIntoView({
block: 'center',
behavior: 'smooth',

View File

@ -40,7 +40,10 @@ export function removeConstrainingValuesInfo({
otherSelections: [],
graphSelections: nodes.map(
(node): Selection => ({
codeRef: codeRefFromRange([node.start, node.end], kclManager.ast),
codeRef: codeRefFromRange(
[node.start, node.end, true],
kclManager.ast
),
})
),
}

View File

@ -22,6 +22,7 @@ import { removeDoubleNegatives } from '../AvailableVarsHelpers'
import { normaliseAngle } from '../../lib/utils'
import { kclManager } from 'lib/singletons'
import { err } from 'lib/trap'
import { KclCommandValue } from 'lib/commandTypes'
const getModalInfo = createSetAngleLengthModal(SetAngleLengthModal)
@ -63,6 +64,57 @@ export function angleLengthInfo({
return { enabled, transforms }
}
export async function applyConstraintLength({
length,
selectionRanges,
}: {
length: KclCommandValue
selectionRanges: Selections
}) {
const ast = kclManager.ast
const angleLength = angleLengthInfo({ selectionRanges })
if (err(angleLength)) return angleLength
const { transforms } = angleLength
let distanceExpression: Expr = length.valueAst
/**
* To be "constrained", the value must be a binary expression, a named value, or a function call.
* If it has a variable name, we need to insert a variable declaration at the correct index.
*/
if (
'variableName' in length &&
length.variableName &&
length.insertIndex !== undefined
) {
const newBody = [...ast.body]
newBody.splice(length.insertIndex, 0, length.variableDeclarationAst)
ast.body = newBody
distanceExpression = createIdentifier(length.variableName)
}
if (!isExprBinaryPart(distanceExpression)) {
return new Error('Invalid valueNode, is not a BinaryPart')
}
const retval = transformAstSketchLines({
ast,
selectionRanges,
transformInfos: transforms,
programMemory: kclManager.programMemory,
referenceSegName: '',
forceValueUsedInTransform: distanceExpression,
})
if (err(retval)) return Promise.reject(retval)
const { modifiedAst: _modifiedAst, pathToNodeMap } = retval
return {
modifiedAst: _modifiedAst,
pathToNodeMap,
}
}
export async function applyConstraintAngleLength({
selectionRanges,
angleOrLength = 'setLength',

View File

@ -139,7 +139,9 @@ export default class EditorManager {
}
setHighlightRange(range: Array<Selection['codeRef']['range']>): void {
this._highlightRange = range
this._highlightRange = range.map((s): [number, number] => {
return [s[0], s[1]]
})
const selectionsWithSafeEnds = range.map((s): [number, number] => {
const safeEnd = Math.min(s[1], this._editorView?.state.doc.length || s[1])

View File

@ -18,7 +18,7 @@ import {
import { err, reportRejection } from 'lib/trap'
import { DefaultPlaneStr, getFaceDetails } from 'clientSideScene/sceneEntities'
import { getNodeFromPath, getNodePathFromSourceRange } from 'lang/queryAst'
import { CallExpression } from 'lang/wasm'
import { CallExpression, defaultSourceRange } from 'lang/wasm'
import { EdgeCutInfo, ExtrudeFacePlane } from 'machines/modelingMachine'
export function useEngineConnectionSubscriptions() {
@ -46,7 +46,7 @@ export function useEngineConnectionSubscriptions() {
(editorManager.highlightRange[0][0] !== 0 &&
editorManager.highlightRange[0][1] !== 0)
) {
editorManager.setHighlightRange([[0, 0]])
editorManager.setHighlightRange([defaultSourceRange()])
}
},
})
@ -201,7 +201,7 @@ export function useEngineConnectionSubscriptions() {
const { z_axis, y_axis, origin } = faceInfo
const sketchPathToNode = getNodePathFromSourceRange(
kclManager.ast,
err(codeRef) ? [0, 0] : codeRef.range
err(codeRef) ? defaultSourceRange() : codeRef.range
)
const getEdgeCutMeta = (): null | EdgeCutInfo => {

View File

@ -24,6 +24,8 @@ export function useConvertToVariable(range?: SourceRange) {
}, [enable])
useEffect(() => {
// Return early if there are no selection ranges for whatever reason
if (!context.selectionRanges) return
const parsed = ast
const meta = isNodeSafeToReplace(

View File

@ -1,15 +1,15 @@
import { KCLError } from './errors'
import { createContext, useContext, useEffect, useState } from 'react'
import { type IndexLoaderData } from 'lib/types'
import { useLoaderData } from 'react-router-dom'
import { codeManager, kclManager } from 'lib/singletons'
import { Diagnostic } from '@codemirror/lint'
const KclContext = createContext({
code: codeManager?.code || '',
programMemory: kclManager?.programMemory,
ast: kclManager?.ast,
isExecuting: kclManager?.isExecuting,
errors: kclManager?.kclErrors,
diagnostics: kclManager?.diagnostics,
logs: kclManager?.logs,
wasmInitFailed: kclManager?.wasmInitFailed,
})
@ -32,7 +32,7 @@ export function KclContextProvider({
const [programMemory, setProgramMemory] = useState(kclManager.programMemory)
const [ast, setAst] = useState(kclManager.ast)
const [isExecuting, setIsExecuting] = useState(false)
const [errors, setErrors] = useState<KCLError[]>([])
const [diagnostics, setErrors] = useState<Diagnostic[]>([])
const [logs, setLogs] = useState<string[]>([])
const [wasmInitFailed, setWasmInitFailed] = useState(false)
@ -57,7 +57,7 @@ export function KclContextProvider({
programMemory,
ast,
isExecuting,
errors,
diagnostics,
logs,
wasmInitFailed,
}}

View File

@ -1,6 +1,10 @@
import { executeAst, lintAst } from 'lang/langHelpers'
import { Selections } from 'lib/selections'
import { KCLError, kclErrorsToDiagnostics } from './errors'
import {
KCLError,
complilationErrorsToDiagnostics,
kclErrorsToDiagnostics,
} from './errors'
import { uuidv4 } from 'lib/utils'
import { EngineCommandManager } from './std/engineConnection'
import { err } from 'lib/trap'
@ -8,6 +12,7 @@ import { EXECUTE_AST_INTERRUPT_ERROR_MESSAGE } from 'lib/constants'
import {
CallExpression,
clearSceneAndBustCache,
emptyExecState,
ExecState,
initPromise,
@ -51,11 +56,12 @@ export class KclManager {
private _programMemory: ProgramMemory = ProgramMemory.empty()
lastSuccessfulProgramMemory: ProgramMemory = ProgramMemory.empty()
private _logs: string[] = []
private _lints: Diagnostic[] = []
private _kclErrors: KCLError[] = []
private _diagnostics: Diagnostic[] = []
private _isExecuting = false
private _executeIsStale: ExecuteArgs | null = null
private _wasmInitFailed = true
private _hasErrors = false
private _switchedFiles = false
engineCommandManager: EngineCommandManager
@ -63,7 +69,7 @@ export class KclManager {
private _astCallBack: (arg: Node<Program>) => void = () => {}
private _programMemoryCallBack: (arg: ProgramMemory) => void = () => {}
private _logsCallBack: (arg: string[]) => void = () => {}
private _kclErrorsCallBack: (arg: KCLError[]) => void = () => {}
private _kclErrorsCallBack: (errors: Diagnostic[]) => void = () => {}
private _wasmInitFailedCallback: (arg: boolean) => void = () => {}
private _executeCallback: () => void = () => {}
@ -75,6 +81,10 @@ export class KclManager {
this._astCallBack(ast)
}
set switchedFiles(switchedFiles: boolean) {
this._switchedFiles = switchedFiles
}
get programMemory() {
return this._programMemory
}
@ -84,7 +94,7 @@ export class KclManager {
this._programMemoryCallBack(programMemory)
}
set execState(execState) {
private set execState(execState) {
this._execState = execState
this.programMemory = execState.memory
}
@ -101,38 +111,28 @@ export class KclManager {
this._logsCallBack(logs)
}
get lints() {
return this._lints
get diagnostics() {
return this._diagnostics
}
set lints(lints) {
if (lints === this._lints) return
this._lints = lints
// Run the lints through the diagnostics.
this.kclErrors = this._kclErrors
}
get kclErrors() {
return this._kclErrors
}
set kclErrors(kclErrors) {
if (kclErrors === this._kclErrors && this.lints.length === 0) return
this._kclErrors = kclErrors
set diagnostics(ds) {
if (ds === this._diagnostics) return
this._diagnostics = ds
this.setDiagnosticsForCurrentErrors()
this._kclErrorsCallBack(kclErrors)
}
addDiagnostics(ds: Diagnostic[]) {
if (ds.length === 0) return
this.diagnostics = this.diagnostics.concat(ds)
}
hasErrors(): boolean {
return this._hasErrors
}
setDiagnosticsForCurrentErrors() {
let diagnostics = kclErrorsToDiagnostics(this.kclErrors)
if (this.lints.length > 0) {
diagnostics = diagnostics.concat(this.lints)
}
editorManager?.setDiagnostics(diagnostics)
}
addKclErrors(kclErrors: KCLError[]) {
if (kclErrors.length === 0) return
this.kclErrors = this.kclErrors.concat(kclErrors)
editorManager?.setDiagnostics(this.diagnostics)
this._kclErrorsCallBack(this.diagnostics)
}
get isExecuting() {
@ -172,8 +172,12 @@ export class KclManager {
this.engineCommandManager = engineCommandManager
// eslint-disable-next-line @typescript-eslint/no-floating-promises
this.ensureWasmInit().then(() => {
this.ast = this.safeParse(codeManager.code) || this.ast
this.ensureWasmInit().then(async () => {
await this.safeParse(codeManager.code).then((ast) => {
if (ast) {
this.ast = ast
}
})
})
}
@ -188,7 +192,7 @@ export class KclManager {
setProgramMemory: (arg: ProgramMemory) => void
setAst: (arg: Node<Program>) => void
setLogs: (arg: string[]) => void
setKclErrors: (arg: KCLError[]) => void
setKclErrors: (errors: Diagnostic[]) => void
setIsExecuting: (arg: boolean) => void
setWasmInitFailed: (arg: boolean) => void
}) {
@ -217,18 +221,48 @@ export class KclManager {
}
}
safeParse(code: string): Node<Program> | null {
const ast = parse(code)
this.lints = []
this.kclErrors = []
if (!err(ast)) return ast
const kclerror: KCLError = ast as KCLError
// (jess) I'm not in love with this, but it ensures we clear the scene and
// bust the cache on
// errors from parsing when opening new files.
// Why not just clear the cache on all parse errors, you ask? well its actually
// really nice to keep the cache on parse errors within the same file, and
// only bust on engine errors esp if they take a long time to execute and
// you hit the wrong key!
private async checkIfSwitchedFilesShouldClear() {
// If we were switching files and we hit an error on parse we need to bust
// the cache and clear the scene.
if (this._hasErrors && this._switchedFiles) {
await clearSceneAndBustCache(this.engineCommandManager)
} else if (this._switchedFiles) {
// Reset the switched files boolean.
this._switchedFiles = false
}
}
this.addKclErrors([kclerror])
// TODO: re-eval if session should end?
if (kclerror.msg === 'file is empty')
this.engineCommandManager?.endSession()
return null
async safeParse(code: string): Promise<Node<Program> | null> {
const result = parse(code)
this.diagnostics = []
this._hasErrors = false
if (err(result)) {
const kclerror: KCLError = result as KCLError
this.diagnostics = kclErrorsToDiagnostics([kclerror])
this._hasErrors = true
await this.checkIfSwitchedFilesShouldClear()
return null
}
this.addDiagnostics(complilationErrorsToDiagnostics(result.errors))
this.addDiagnostics(complilationErrorsToDiagnostics(result.warnings))
if (result.errors.length > 0) {
this._hasErrors = true
await this.checkIfSwitchedFilesShouldClear()
return null
}
return result.program
}
async ensureWasmInit() {
@ -267,19 +301,16 @@ export class KclManager {
this._cancelTokens.set(currentExecutionId, false)
this.isExecuting = true
// Make sure we clear before starting again. End session will do this.
this.engineCommandManager?.endSession()
await this.ensureWasmInit()
const { logs, errors, execState, isInterrupted } = await executeAst({
ast,
idGenerator: this.execState.idGenerator,
engineCommandManager: this.engineCommandManager,
})
// Program was not interrupted, setup the scene
// Do not send send scene commands if the program was interrupted, go to clean up
if (!isInterrupted) {
this.lints = await lintAst({ ast: ast })
this.addDiagnostics(await lintAst({ ast: ast }))
sceneInfra.modelingSend({ type: 'code edit during sketch' })
setSelectionFilterToDefault(execState.memory, this.engineCommandManager)
@ -321,9 +352,7 @@ export class KclManager {
this.logs = logs
// Do not add the errors since the program was interrupted and the error is not a real KCL error
this.addKclErrors(isInterrupted ? [] : errors)
// Reset the next ID index so that we reuse the previous IDs next time.
execState.idGenerator.nextId = 0
this.addDiagnostics(isInterrupted ? [] : kclErrorsToDiagnostics(errors))
this.execState = execState
if (!errors.length) {
this.lastSuccessfulProgramMemory = execState.memory
@ -355,7 +384,7 @@ export class KclManager {
console.error(newCode)
return
}
const newAst = this.safeParse(newCode)
const newAst = await this.safeParse(newCode)
if (!newAst) {
this.clearAst()
return
@ -364,13 +393,13 @@ export class KclManager {
const { logs, errors, execState } = await executeAst({
ast: newAst,
idGenerator: this.execState.idGenerator,
engineCommandManager: this.engineCommandManager,
useFakeExecutor: true,
// We make sure to send an empty program memory to denote we mean mock mode.
programMemoryOverride: ProgramMemory.empty(),
})
this._logs = logs
this._kclErrors = errors
this.addDiagnostics(kclErrorsToDiagnostics(errors))
this._execState = execState
this._programMemory = execState.memory
if (!errors.length) {
@ -398,7 +427,7 @@ export class KclManager {
...artifact,
codeRef: {
...artifact.codeRef,
range: [node.start, node.end],
range: [node.start, node.end, true],
},
})
}
@ -410,7 +439,7 @@ export class KclManager {
})
}
async executeCode(zoomToFit?: boolean): Promise<void> {
const ast = this.safeParse(codeManager.code)
const ast = await this.safeParse(codeManager.code)
if (!ast) {
this.clearAst()
return
@ -418,9 +447,9 @@ export class KclManager {
this.ast = { ...ast }
return this.executeAst({ zoomToFit })
}
format() {
async format() {
const originalCode = codeManager.code
const ast = this.safeParse(originalCode)
const ast = await this.safeParse(originalCode)
if (!ast) {
this.clearAst()
return
@ -460,7 +489,7 @@ export class KclManager {
const newCode = recast(ast)
if (err(newCode)) return Promise.reject(newCode)
const astWithUpdatedSource = this.safeParse(newCode)
const astWithUpdatedSource = await this.safeParse(newCode)
if (!astWithUpdatedSource) return Promise.reject(new Error('bad ast'))
let returnVal: Selections | undefined = undefined
@ -490,7 +519,7 @@ export class KclManager {
if (start && end) {
returnVal.graphSelections.push({
codeRef: {
range: [start, end],
range: [start, end, true],
pathToNode: path,
},
})

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,4 @@
import { parse, initPromise } from './wasm'
import { assertParse, initPromise } from './wasm'
import { enginelessExecutor } from '../lib/testHelpers'
beforeAll(async () => {
@ -14,7 +14,7 @@ const mySketch001 = startSketchOn('XY')
|> lineTo([-1.59, -1.54], %)
|> lineTo([0.46, -5.82], %)
// |> rx(45, %)`
const execState = await enginelessExecutor(parse(code))
const execState = await enginelessExecutor(assertParse(code))
// @ts-ignore
const sketch001 = execState.memory.get('mySketch001')
expect(sketch001).toEqual({
@ -67,7 +67,7 @@ const mySketch001 = startSketchOn('XY')
|> lineTo([0.46, -5.82], %)
// |> rx(45, %)
|> extrude(2, %)`
const execState = await enginelessExecutor(parse(code))
const execState = await enginelessExecutor(assertParse(code))
// @ts-ignore
const sketch001 = execState.memory.get('mySketch001')
expect(sketch001).toEqual({
@ -147,7 +147,7 @@ const sk2 = startSketchOn('XY')
|> extrude(2, %)
`
const execState = await enginelessExecutor(parse(code))
const execState = await enginelessExecutor(assertParse(code))
const programMemory = execState.memory
// @ts-ignore
const geos = [programMemory.get('theExtrude'), programMemory.get('sk2')]

View File

@ -8,20 +8,14 @@ describe('test kclErrToDiagnostic', () => {
message: '',
kind: 'semantic',
msg: 'Semantic error',
sourceRanges: [
[0, 1, 0],
[2, 3, 0],
],
sourceRange: [0, 1, true],
},
{
name: '',
message: '',
kind: 'type',
msg: 'Type error',
sourceRanges: [
[4, 5, 0],
[6, 7, 0],
],
sourceRange: [4, 5, true],
},
]
const diagnostics = kclErrorsToDiagnostics(errors)
@ -32,24 +26,12 @@ describe('test kclErrToDiagnostic', () => {
message: 'Semantic error',
severity: 'error',
},
{
from: 2,
to: 3,
message: 'Semantic error',
severity: 'error',
},
{
from: 4,
to: 5,
message: 'Type error',
severity: 'error',
},
{
from: 6,
to: 7,
message: 'Type error',
severity: 'error',
},
])
})
})

View File

@ -1,88 +1,90 @@
import { KclError as RustKclError } from '../wasm-lib/kcl/bindings/KclError'
import { CompilationError } from 'wasm-lib/kcl/bindings/CompilationError'
import { Diagnostic as CodeMirrorDiagnostic } from '@codemirror/lint'
import { posToOffset } from '@kittycad/codemirror-lsp-client'
import { Diagnostic as LspDiagnostic } from 'vscode-languageserver-protocol'
import { Text } from '@codemirror/state'
const TOP_LEVEL_MODULE_ID = 0
import { EditorView } from 'codemirror'
import { SourceRange } from 'lang/wasm'
type ExtractKind<T> = T extends { kind: infer K } ? K : never
export class KCLError extends Error {
kind: ExtractKind<RustKclError> | 'name'
sourceRanges: [number, number, number][]
sourceRange: SourceRange
msg: string
constructor(
kind: ExtractKind<RustKclError> | 'name',
msg: string,
sourceRanges: [number, number, number][]
sourceRange: SourceRange
) {
super()
this.kind = kind
this.msg = msg
this.sourceRanges = sourceRanges
this.sourceRange = sourceRange
Object.setPrototypeOf(this, KCLError.prototype)
}
}
export class KCLLexicalError extends KCLError {
constructor(msg: string, sourceRanges: [number, number, number][]) {
super('lexical', msg, sourceRanges)
constructor(msg: string, sourceRange: SourceRange) {
super('lexical', msg, sourceRange)
Object.setPrototypeOf(this, KCLSyntaxError.prototype)
}
}
export class KCLInternalError extends KCLError {
constructor(msg: string, sourceRanges: [number, number, number][]) {
super('internal', msg, sourceRanges)
constructor(msg: string, sourceRange: SourceRange) {
super('internal', msg, sourceRange)
Object.setPrototypeOf(this, KCLSyntaxError.prototype)
}
}
export class KCLSyntaxError extends KCLError {
constructor(msg: string, sourceRanges: [number, number, number][]) {
super('syntax', msg, sourceRanges)
constructor(msg: string, sourceRange: SourceRange) {
super('syntax', msg, sourceRange)
Object.setPrototypeOf(this, KCLSyntaxError.prototype)
}
}
export class KCLSemanticError extends KCLError {
constructor(msg: string, sourceRanges: [number, number, number][]) {
super('semantic', msg, sourceRanges)
constructor(msg: string, sourceRange: SourceRange) {
super('semantic', msg, sourceRange)
Object.setPrototypeOf(this, KCLSemanticError.prototype)
}
}
export class KCLTypeError extends KCLError {
constructor(msg: string, sourceRanges: [number, number, number][]) {
super('type', msg, sourceRanges)
constructor(msg: string, sourceRange: SourceRange) {
super('type', msg, sourceRange)
Object.setPrototypeOf(this, KCLTypeError.prototype)
}
}
export class KCLUnimplementedError extends KCLError {
constructor(msg: string, sourceRanges: [number, number, number][]) {
super('unimplemented', msg, sourceRanges)
constructor(msg: string, sourceRange: SourceRange) {
super('unimplemented', msg, sourceRange)
Object.setPrototypeOf(this, KCLUnimplementedError.prototype)
}
}
export class KCLUnexpectedError extends KCLError {
constructor(msg: string, sourceRanges: [number, number, number][]) {
super('unexpected', msg, sourceRanges)
constructor(msg: string, sourceRange: SourceRange) {
super('unexpected', msg, sourceRange)
Object.setPrototypeOf(this, KCLUnexpectedError.prototype)
}
}
export class KCLValueAlreadyDefined extends KCLError {
constructor(key: string, sourceRanges: [number, number, number][]) {
super('name', `Key ${key} was already defined elsewhere`, sourceRanges)
constructor(key: string, sourceRange: SourceRange) {
super('name', `Key ${key} was already defined elsewhere`, sourceRange)
Object.setPrototypeOf(this, KCLValueAlreadyDefined.prototype)
}
}
export class KCLUndefinedValueError extends KCLError {
constructor(key: string, sourceRanges: [number, number, number][]) {
super('name', `Key ${key} has not been defined`, sourceRanges)
constructor(key: string, sourceRange: SourceRange) {
super('name', `Key ${key} has not been defined`, sourceRange)
Object.setPrototypeOf(this, KCLUndefinedValueError.prototype)
}
}
@ -99,27 +101,14 @@ export function lspDiagnosticsToKclErrors(
.flatMap(
({ range, message }) =>
new KCLError('unexpected', message, [
[
posToOffset(doc, range.start)!,
posToOffset(doc, range.end)!,
TOP_LEVEL_MODULE_ID,
],
posToOffset(doc, range.start)!,
posToOffset(doc, range.end)!,
true,
])
)
.filter(({ sourceRanges }) => {
const [from, to, moduleId] = sourceRanges[0]
return (
from !== null &&
to !== null &&
from !== undefined &&
to !== undefined &&
// Filter out errors that are not from the top-level module.
moduleId === TOP_LEVEL_MODULE_ID
)
})
.sort((a, b) => {
const c = a.sourceRanges[0][0]
const d = b.sourceRanges[0][0]
const c = a.sourceRange[0]
const d = b.sourceRange[0]
switch (true) {
case c < d:
return -1
@ -137,17 +126,48 @@ export function lspDiagnosticsToKclErrors(
export function kclErrorsToDiagnostics(
errors: KCLError[]
): CodeMirrorDiagnostic[] {
return errors?.flatMap((err) => {
const sourceRanges: CodeMirrorDiagnostic[] = err.sourceRanges
// Filter out errors that are not from the top-level module.
.filter(([_start, _end, moduleId]) => moduleId === TOP_LEVEL_MODULE_ID)
.map(([from, to]) => {
return { from, to, message: err.msg, severity: 'error' }
})
// Make sure we didn't filter out all the source ranges.
if (sourceRanges.length === 0) {
sourceRanges.push({ from: 0, to: 0, message: err.msg, severity: 'error' })
}
return sourceRanges
})
return errors
?.filter((err) => err.sourceRange[2])
.map((err) => {
return {
from: err.sourceRange[0],
to: err.sourceRange[1],
message: err.msg,
severity: 'error',
}
})
}
export function complilationErrorsToDiagnostics(
errors: CompilationError[]
): CodeMirrorDiagnostic[] {
return errors
?.filter((err) => err.sourceRange[2] === 0)
.map((err) => {
let severity: any = 'error'
if (err.severity === 'Warning') {
severity = 'warning'
}
let actions
const suggestion = err.suggestion
if (suggestion) {
actions = [
{
name: suggestion.title,
apply: (view: EditorView, from: number, to: number) => {
view.dispatch({
changes: { from, to, insert: suggestion.insert },
})
},
},
]
}
return {
from: err.sourceRange[0],
to: err.sourceRange[1],
message: err.message,
severity,
actions,
}
})
}

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