Compare commits

...

124 Commits

Author SHA1 Message Date
9407162543 Merge branch 'main' into pierremtb/issue4472-Add-shell-point-and-click-operation-backup 2024-12-09 14:04:18 -05:00
067b83b468 Manual resolution of snapshot conflicts 2024-12-09 13:56:33 -05: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
c5b30341eb Add unit tests for doesSceneHaveExtrudedSketch 2024-12-09 12:26:21 -05: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
3e6441b563 Fix test annotations 2024-12-09 11:36:46 -05:00
acafcf2d4d Apply suggestions from Frank's review
Co-authored-by: Frank Noirot <frank@zoo.dev>
2024-12-09 11:33:41 -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
b9f31d94d5 Merge branch 'main' into pierremtb/issue4472-Add-shell-point-and-click-operation 2024-12-05 18:43:44 -05: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
9e03b58ae5 Cap and wall pw test 2024-12-05 18:21:20 -05:00
c591f73c70 Working multi-face shell across types 2024-12-05 17:56:18 -05:00
9330aaba13 Trigger CI 2024-12-05 17:27:16 -05:00
5a14f0189e Look at this (photo)Graph *in the voice of Nickelback* 2024-12-05 22:14:20 +00:00
9af001f22e Lint 2024-12-05 17:04:02 -05:00
435d1ea52e WIP circular dep 2024-12-05 16:53:57 -05: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
54847139f2 Merge branch 'main' into pierremtb/issue4472-Add-shell-point-and-click-operation 2024-12-05 15:00:34 -05:00
9369a17ea7 WIP mutliple faces 2024-12-05 14:59:44 -05:00
8072f1db63 Add support for line comments in playwright-secrets.env (#4671) 2024-12-05 19:45:33 +00:00
9ee2e7c3b0 A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest-8-cores) 2024-12-05 19:42:39 +00:00
28ae261e5f Lint fix 2024-12-05 14:38:23 -05:00
e4b0de0ead Add selection guard and clean up 2024-12-05 14:06:09 -05:00
12859598a3 Merge branch 'main' into pierremtb/issue4472-Add-shell-point-and-click-operation 2024-12-05 13:02:35 -05:00
e9a334f433 Fix lint 2024-12-05 13:02:22 -05: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
e470a7b4af Add shell wall test 2024-12-05 12:33:46 -05:00
2bf20988ef Fix to never have undefined iteration order and lint against it (#4665) 2024-12-05 17:09:35 +00:00
602c39f63c Add pw tests for cap shell 2024-12-05 12:03:45 -05:00
4994aa6f61 Handle walls 2024-12-05 11:38:03 -05: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
4aa07b81db Add extrude lookup for more generic shell 2024-12-05 10:26:58 -05:00
f876e6ca3c Bump and release kcl-lib 0.2.28 (#4669)
bump kcl version
2024-12-05 15:03:55 +00:00
89ef4b3243 A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest-8-cores) 2024-12-05 14:55:04 +00:00
001c9a8219 Update from main 2024-12-05 09:49:14 -05: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
4dde3f60e0 WIP: first time working shell mod 2024-12-04 18:41:45 -05:00
f407c53032 WIP: closer 2024-12-04 17:53:04 -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
9ba584487a WIP: more additions 2024-12-04 17:01:11 -05: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
96b66d6bca Merge branch 'pierremtb/issue4470-loft-ui' into pierremtb/issue4472-Add-shell-point-and-click-operation 2024-12-04 16:44:40 -05:00
8d66f3ffad Rollback pw values to pre cam change 2024-12-04 16:43:29 -05:00
f4c54cbbe4 WIP: initial shell code addition 2024-12-04 16:38:44 -05: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
5156b847f3 Trigger CI 2024-12-04 15:56:48 -05:00
ded9f2c56b A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest-8-cores) 2024-12-04 20:54:59 +00:00
5db5f79f9a Merge branch 'main' into pierremtb/issue4470-loft-ui 2024-12-04 15:46:18 -05:00
6a883f4a8d A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest-8-cores) 2024-12-04 20:43:39 +00:00
dd3a2b14f9 Bump hashbrown from 0.15.0 to 0.15.2 (#4659) 2024-12-04 20:40:46 +00:00
07b91f0fb1 Trigger CI 2024-12-04 15:39:26 -05:00
1b2e213afe A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest-8-cores) 2024-12-04 20:37:28 +00:00
f48a23c35e A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest-8-cores) 2024-12-04 20:36:20 +00:00
7e1d102496 Revert snapshots 2024-12-04 15:32:14 -05:00
94cb2535c0 Fix typo 2024-12-04 15:21:03 -05:00
9e08ec9096 A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest-8-cores) 2024-12-04 20:00:36 +00:00
17bd8ec32a A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest-8-cores) 2024-12-04 19:58:28 +00:00
d0f12e85e5 Trigger CI 2024-12-04 14:54:05 -05:00
94d185944e A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest-8-cores) 2024-12-04 19:52:22 +00:00
c38b2270c3 A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest-8-cores) 2024-12-04 19:51:34 +00:00
967ad66c98 A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest-8-cores) 2024-12-04 19:47:04 +00:00
afeca9ca39 A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest-8-cores) 2024-12-04 19:46:25 +00:00
61242282f0 Trigger CI 2024-12-04 14:42:00 -05:00
0065df13ce A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest-8-cores) 2024-12-04 16:55:39 +00:00
01c8d45c13 Remove comments 2024-12-04 11:44:49 -05:00
8b25527f21 Move error logic out of loftSketches, fix pw tests 2024-12-04 11:39:35 -05:00
2abd980de9 Move to fromPromise-based Actor 2024-12-04 11:02:51 -05:00
f783deb706 A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest-8-cores) 2024-12-04 15:25:38 +00:00
f4dd295ca1 A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest-8-cores) 2024-12-04 15:17:19 +00:00
ceaa85fe3f A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest-8-cores) 2024-12-04 15:16:16 +00:00
3991bd9173 Trigger CI 2024-12-04 10:11:47 -05:00
b8f9da36c0 A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest-8-cores) 2024-12-04 10:54:14 +00:00
283315b5d2 A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest-8-cores) 2024-12-04 10:44:01 +00:00
e204dfe564 A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest-8-cores) 2024-12-04 10:43:15 +00:00
208a36196b Merge branch 'main' into pierremtb/issue4470-loft-ui 2024-12-04 05:38:43 -05: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
660a349588 Add pw test for preselected sketches 2024-12-03 15:17:21 -05:00
089d6df889 Update fn syntax in module docs (#4641) 2024-12-03 15:14:32 -05:00
56c37da317 Clean up loftSketches function 2024-12-03 15:02:56 -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
a46734b76d Add test for doesSceneHaveSweepableSketch with count = 2 2024-12-03 14:39:06 -05:00
4347e0cf84 Clean up and working pw test 2024-12-03 13:49:26 -05: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
df3e541cdf A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest-8-cores) 2024-12-03 17:51:57 +00:00
b1cec443b9 Merge branch 'main' into pierremtb/issue4470-loft-ui 2024-12-03 12:48:32 -05:00
967f49055d Lint 2024-12-02 14:30:43 -05:00
fe977524b5 First point-click loft test (not working locally, loft gets inserted at the wrong place) 2024-12-02 14:19:32 -05:00
d6f271fb0f Enable multiple selections after the button click 2024-12-01 07:00:13 -05:00
e851b2bcc4 Merge branch 'main' into pierremtb/issue4470-loft-ui 2024-11-29 20:01:32 -05:00
be569c91de Clean up 2024-11-29 19:51:56 -05:00
5080e304b9 Appends the loft line after the 'last' sketch in the code 2024-11-29 18:30:15 -05:00
f4e75b7b4f More checks 2024-11-29 13:38:52 -05:00
31cbc90f56 WIP selections 2024-11-29 12:37:53 -05:00
a7d3552472 WIP selections 2024-11-29 11:17:31 -05:00
e984b20664 First pass at handling more than 2 sketches 2024-11-29 10:09:10 -05:00
0c2cd24bda Working loft for two sketches in the right hardcoded order 2024-11-28 20:28:37 -05:00
9b2de237b8 Add selection guard 2024-11-28 16:45:41 -05:00
c79c02f18e A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest-8-cores) 2024-11-28 20:25:44 +00:00
4851aa2d71 A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest-8-cores) 2024-11-28 20:17:17 +00:00
76fafa6fd0 A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest-8-cores) 2024-11-28 20:11:35 +00:00
647ca11e08 WIP: experimenting with Loft UI
Relates to #4470
2024-11-27 15:55:01 -05:00
431 changed files with 110825 additions and 109750 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'
@ -382,3 +393,18 @@ jobs:
glob: '*'
parent: false
destination: 'dl.kittycad.io/releases/modeling-app/nightly'
- 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:
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

@ -126,11 +126,7 @@ 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' }}

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`.

File diff suppressed because one or more lines are too long

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
}
```

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

@ -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

@ -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,257 @@ 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 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 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

@ -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

@ -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

@ -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.')
@ -590,7 +594,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,7 +618,7 @@ 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?
@ -627,10 +633,12 @@ const ConstraintSymbol = ({
})
} 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

@ -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

@ -50,6 +50,8 @@ import {
isSketchPipe,
Selections,
updateSelections,
canLoftSelection,
canShellSelection,
} from 'lib/selections'
import { applyConstraintIntersect } from './Toolbar/Intersect'
import { applyConstraintAbsDistance } from './Toolbar/SetAbsDistance'
@ -66,8 +68,9 @@ import {
sketchOnOffsetPlane,
startSketchOnDefault,
} from 'lang/modifyAst'
import { Program, parse, recast } from 'lang/wasm'
import { Program, parse, recast, resultIsOk } from 'lang/wasm'
import {
doesSceneHaveExtrudedSketch,
doesSceneHaveSweepableSketch,
getNodePathFromSourceRange,
isSingleCursorInPipe,
@ -82,7 +85,7 @@ import { getVarNameModal } from 'hooks/useToolbarGuards'
import { err, reportRejection, trap } from 'lib/trap'
import { useCommandsContext } from 'hooks/useCommandsContext'
import { modelingMachineEvent } from 'editor/manager'
import { hasValidEdgeTreatmentSelection } from 'lang/modifyAst/addFillet'
import { hasValidEdgeTreatmentSelection } from 'lang/modifyAst/addEdgeTreatment'
import {
ExportIntent,
EngineConnectionStateType,
@ -569,6 +572,39 @@ export const ModelingMachineProvider = ({
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 },
}) => {
@ -596,15 +632,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)
@ -722,7 +754,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(
@ -763,7 +799,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(
@ -811,7 +850,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)
@ -853,7 +895,10 @@ export const ModelingMachineProvider = ({
await applyConstraintAngleLength({
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(
@ -893,7 +938,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(
@ -934,7 +982,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(
@ -975,7 +1026,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(
@ -1016,9 +1070,10 @@ export const ModelingMachineProvider = ({
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>
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(
@ -1027,7 +1082,11 @@ export const ModelingMachineProvider = ({
data?.pathToNode || [],
variableName
)
parsed = parse(recast(_modifiedAst))
pResult = parse(recast(_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)

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

@ -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

@ -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

@ -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,
}
})
}

View File

@ -1,7 +1,7 @@
import fs from 'node:fs'
import {
parse,
assertParse,
ProgramMemory,
Sketch,
initPromise,
@ -472,7 +472,7 @@ describe('Testing Errors', () => {
const theExtrude = startSketchOn('XY')
|> startProfileAt([0, 0], %)
|> line([-2.4, 5], %)
|> line([-0.76], myVarZ, %)
|> line(myVarZ, %)
|> line([5,5], %)
|> close(%)
|> extrude(4, %)`
@ -480,7 +480,7 @@ const theExtrude = startSketchOn('XY')
new KCLError(
'undefined_value',
'memory item key `myVarZ` is not defined',
[[129, 135, 0]]
[129, 135, true]
)
)
})
@ -492,7 +492,7 @@ async function exe(
code: string,
programMemory: ProgramMemory = ProgramMemory.empty()
) {
const ast = parse(code)
const ast = assertParse(code)
const execState = await enginelessExecutor(ast, programMemory)
return execState.memory

View File

@ -1,5 +1,5 @@
import { getNodePathFromSourceRange, getNodeFromPath } from './queryAst'
import { Identifier, parse, initPromise, Parameter } from './wasm'
import { Identifier, assertParse, initPromise, Parameter } from './wasm'
import { err } from 'lib/trap'
beforeAll(async () => {
@ -17,19 +17,19 @@ const sk3 = startSketchAt([0, 0])
`
const subStr = 'lineTo([3, 4], %, $yo)'
const lineToSubstringIndex = code.indexOf(subStr)
const sourceRange: [number, number] = [
const sourceRange: [number, number, boolean] = [
lineToSubstringIndex,
lineToSubstringIndex + subStr.length,
true,
]
const ast = parse(code)
if (err(ast)) throw ast
const ast = assertParse(code)
const nodePath = getNodePathFromSourceRange(ast, sourceRange)
const _node = getNodeFromPath<any>(ast, nodePath)
if (err(_node)) throw _node
const { node } = _node
expect([node.start, node.end]).toEqual(sourceRange)
expect([node.start, node.end, true]).toEqual(sourceRange)
expect(node.type).toBe('CallExpression')
})
it('gets path right for function definition params', () => {
@ -45,13 +45,13 @@ const sk3 = startSketchAt([0, 0])
const b1 = cube([0,0], 10)`
const subStr = 'pos, scale'
const subStrIndex = code.indexOf(subStr)
const sourceRange: [number, number] = [
const sourceRange: [number, number, boolean] = [
subStrIndex,
subStrIndex + 'pos'.length,
true,
]
const ast = parse(code)
if (err(ast)) throw ast
const ast = assertParse(code)
const nodePath = getNodePathFromSourceRange(ast, sourceRange)
const _node = getNodeFromPath<Parameter>(ast, nodePath)
if (err(_node)) throw _node
@ -60,8 +60,7 @@ const b1 = cube([0,0], 10)`
expect(nodePath).toEqual([
['body', ''],
[0, 'index'],
['declarations', 'VariableDeclaration'],
[0, 'index'],
['declaration', 'VariableDeclaration'],
['init', ''],
['params', 'FunctionExpression'],
[0, 'index'],
@ -82,13 +81,13 @@ const b1 = cube([0,0], 10)`
const b1 = cube([0,0], 10)`
const subStr = 'scale, 0'
const subStrIndex = code.indexOf(subStr)
const sourceRange: [number, number] = [
const sourceRange: [number, number, boolean] = [
subStrIndex,
subStrIndex + 'scale'.length,
true,
]
const ast = parse(code)
if (err(ast)) throw ast
const ast = assertParse(code)
const nodePath = getNodePathFromSourceRange(ast, sourceRange)
const _node = getNodeFromPath<Identifier>(ast, nodePath)
if (err(_node)) throw _node
@ -96,14 +95,12 @@ const b1 = cube([0,0], 10)`
expect(nodePath).toEqual([
['body', ''],
[0, 'index'],
['declarations', 'VariableDeclaration'],
[0, 'index'],
['declaration', 'VariableDeclaration'],
['init', ''],
['body', 'FunctionExpression'],
['body', 'FunctionExpression'],
[0, 'index'],
['declarations', 'VariableDeclaration'],
[0, 'index'],
['declaration', 'VariableDeclaration'],
['init', ''],
['body', 'PipeExpression'],
[2, 'index'],

View File

@ -1,6 +1,5 @@
import { parse, initPromise, programMemoryInit } from './wasm'
import { assertParse, initPromise, programMemoryInit } from './wasm'
import { enginelessExecutor } from '../lib/testHelpers'
import { assert } from 'vitest'
// These unit tests makes web requests to a public github repository.
interface KclSampleFile {
@ -58,8 +57,7 @@ describe('Test KCL Samples from public Github repository', () => {
files.forEach((file: KclSampleFile) => {
it(`should parse ${file.filename} without errors`, async () => {
const code = await getKclSampleCodeFromGithub(file.filename)
const parsed = parse(code)
assert(!(parsed instanceof Error))
assertParse(code)
}, 1000)
})
})
@ -71,9 +69,8 @@ describe('Test KCL Samples from public Github repository', () => {
for (let i = 0; i < files.length; i++) {
const file: KclSampleFile = files[i]
const code = await getKclSampleCodeFromGithub(file.filename)
const parsed = parse(code)
assert(!(parsed instanceof Error))
await enginelessExecutor(parsed, programMemoryInit())
const ast = assertParse(code)
await enginelessExecutor(ast, programMemoryInit())
}
},
files.length * 1000

View File

@ -2,7 +2,6 @@ import {
Program,
_executor,
ProgramMemory,
programMemoryInit,
kclLint,
emptyExecState,
ExecState,
@ -11,7 +10,6 @@ import { enginelessExecutor } from 'lib/testHelpers'
import { EngineCommandManager } from 'lang/std/engineConnection'
import { KCLError } from 'lang/errors'
import { Diagnostic } from '@codemirror/lint'
import { IdGenerator } from 'wasm-lib/kcl/bindings/IdGenerator'
import { Node } from 'wasm-lib/kcl/bindings/Node'
export type ToolTip =
@ -49,15 +47,13 @@ export const toolTips: Array<ToolTip> = [
export async function executeAst({
ast,
engineCommandManager,
useFakeExecutor = false,
// If you set programMemoryOverride we assume you mean mock mode. Since that
// is the only way to go about it.
programMemoryOverride,
idGenerator,
}: {
ast: Node<Program>
engineCommandManager: EngineCommandManager
useFakeExecutor?: boolean
programMemoryOverride?: ProgramMemory
idGenerator?: IdGenerator
isInterrupted?: boolean
}): Promise<{
logs: string[]
@ -66,22 +62,14 @@ export async function executeAst({
isInterrupted: boolean
}> {
try {
if (!useFakeExecutor) {
engineCommandManager.endSession()
// eslint-disable-next-line @typescript-eslint/no-floating-promises
engineCommandManager.startNewSession()
}
const execState = await (useFakeExecutor
? enginelessExecutor(ast, programMemoryOverride || programMemoryInit())
: _executor(
ast,
programMemoryInit(),
idGenerator,
engineCommandManager,
false
))
const execState = await (programMemoryOverride
? enginelessExecutor(ast, programMemoryOverride)
: _executor(ast, engineCommandManager))
await engineCommandManager.waitForAllCommands(
programMemoryOverride !== undefined
)
await engineCommandManager.waitForAllCommands(useFakeExecutor)
return {
logs: [],
errors: [],

View File

@ -1,4 +1,4 @@
import { parse, recast, initPromise, Identifier } from './wasm'
import { assertParse, recast, initPromise, Identifier } from './wasm'
import {
createLiteral,
createIdentifier,
@ -82,11 +82,11 @@ describe('Testing createVariableDeclaration', () => {
it('should create a variable declaration', () => {
const result = createVariableDeclaration('myVar', createLiteral(5))
expect(result.type).toBe('VariableDeclaration')
expect(result.declarations[0].type).toBe('VariableDeclarator')
expect(result.declarations[0].id.type).toBe('Identifier')
expect(result.declarations[0].id.name).toBe('myVar')
expect(result.declarations[0].init.type).toBe('Literal')
expect((result.declarations[0].init as any).value).toBe(5)
expect(result.declaration.type).toBe('VariableDeclarator')
expect(result.declaration.id.type).toBe('Identifier')
expect(result.declaration.id.name).toBe('myVar')
expect(result.declaration.init.type).toBe('Literal')
expect((result.declaration.init as any).value).toBe(5)
})
})
describe('Testing createPipeExpression', () => {
@ -146,10 +146,13 @@ function giveSketchFnCallTagTestHelper(
// giveSketchFnCallTag inputs and outputs an ast, which is very verbose for testing
// this wrapper changes the input and output to code
// making it more of an integration test, but easier to read the test intention is the goal
const ast = parse(code)
if (err(ast)) throw ast
const ast = assertParse(code)
const start = code.indexOf(searchStr)
const range: [number, number] = [start, start + searchStr.length]
const range: [number, number, boolean] = [
start,
start + searchStr.length,
true,
]
const sketchRes = giveSketchFnCallTag(ast, range)
if (err(sketchRes)) throw sketchRes
const { modifiedAst, tag, isTagExisting } = sketchRes
@ -221,14 +224,13 @@ part001 = startSketchOn('XY')
|> angledLine([jkl(yo) + 2, 3.09], %)
yo2 = hmm([identifierGuy + 5])`
it('should move a binary expression into a new variable', async () => {
const ast = parse(code)
if (err(ast)) throw ast
const ast = assertParse(code)
const execState = await enginelessExecutor(ast)
const startIndex = code.indexOf('100 + 100') + 1
const { modifiedAst } = moveValueIntoNewVariable(
ast,
execState.memory,
[startIndex, startIndex],
[startIndex, startIndex, true],
'newVar'
)
const newCode = recast(modifiedAst)
@ -236,14 +238,13 @@ yo2 = hmm([identifierGuy + 5])`
expect(newCode).toContain(`angledLine([newVar, 3.09], %)`)
})
it('should move a value into a new variable', async () => {
const ast = parse(code)
if (err(ast)) throw ast
const ast = assertParse(code)
const execState = await enginelessExecutor(ast)
const startIndex = code.indexOf('2.8') + 1
const { modifiedAst } = moveValueIntoNewVariable(
ast,
execState.memory,
[startIndex, startIndex],
[startIndex, startIndex, true],
'newVar'
)
const newCode = recast(modifiedAst)
@ -251,14 +252,13 @@ yo2 = hmm([identifierGuy + 5])`
expect(newCode).toContain(`line([newVar, 0], %)`)
})
it('should move a callExpression into a new variable', async () => {
const ast = parse(code)
if (err(ast)) throw ast
const ast = assertParse(code)
const execState = await enginelessExecutor(ast)
const startIndex = code.indexOf('def(')
const { modifiedAst } = moveValueIntoNewVariable(
ast,
execState.memory,
[startIndex, startIndex],
[startIndex, startIndex, true],
'newVar'
)
const newCode = recast(modifiedAst)
@ -266,14 +266,13 @@ yo2 = hmm([identifierGuy + 5])`
expect(newCode).toContain(`angledLine([newVar, 3.09], %)`)
})
it('should move a binary expression with call expression into a new variable', async () => {
const ast = parse(code)
if (err(ast)) throw ast
const ast = assertParse(code)
const execState = await enginelessExecutor(ast)
const startIndex = code.indexOf('jkl(') + 1
const { modifiedAst } = moveValueIntoNewVariable(
ast,
execState.memory,
[startIndex, startIndex],
[startIndex, startIndex, true],
'newVar'
)
const newCode = recast(modifiedAst)
@ -281,14 +280,13 @@ yo2 = hmm([identifierGuy + 5])`
expect(newCode).toContain(`angledLine([newVar, 3.09], %)`)
})
it('should move a identifier into a new variable', async () => {
const ast = parse(code)
if (err(ast)) throw ast
const ast = assertParse(code)
const execState = await enginelessExecutor(ast)
const startIndex = code.indexOf('identifierGuy +') + 1
const { modifiedAst } = moveValueIntoNewVariable(
ast,
execState.memory,
[startIndex, startIndex],
[startIndex, startIndex, true],
'newVar'
)
const newCode = recast(modifiedAst)
@ -305,19 +303,20 @@ describe('testing sketchOnExtrudedFace', () => {
|> line([8.62, -9.57], %)
|> close(%)
|> extrude(5 + 7, %)`
const ast = parse(code)
if (err(ast)) throw ast
const ast = assertParse(code)
const segmentSnippet = `line([9.7, 9.19], %)`
const segmentRange: [number, number] = [
const segmentRange: [number, number, boolean] = [
code.indexOf(segmentSnippet),
code.indexOf(segmentSnippet) + segmentSnippet.length,
true,
]
const segmentPathToNode = getNodePathFromSourceRange(ast, segmentRange)
const extrudeSnippet = `extrude(5 + 7, %)`
const extrudeRange: [number, number] = [
const extrudeRange: [number, number, boolean] = [
code.indexOf(extrudeSnippet),
code.indexOf(extrudeSnippet) + extrudeSnippet.length,
true,
]
const extrudePathToNode = getNodePathFromSourceRange(ast, extrudeRange)
@ -345,18 +344,19 @@ sketch001 = startSketchOn(part001, seg01)`)
|> line([8.62, -9.57], %)
|> close(%)
|> extrude(5 + 7, %)`
const ast = parse(code)
if (err(ast)) throw ast
const ast = assertParse(code)
const segmentSnippet = `close(%)`
const segmentRange: [number, number] = [
const segmentRange: [number, number, boolean] = [
code.indexOf(segmentSnippet),
code.indexOf(segmentSnippet) + segmentSnippet.length,
true,
]
const segmentPathToNode = getNodePathFromSourceRange(ast, segmentRange)
const extrudeSnippet = `extrude(5 + 7, %)`
const extrudeRange: [number, number] = [
const extrudeRange: [number, number, boolean] = [
code.indexOf(extrudeSnippet),
code.indexOf(extrudeSnippet) + extrudeSnippet.length,
true,
]
const extrudePathToNode = getNodePathFromSourceRange(ast, extrudeRange)
@ -384,18 +384,19 @@ sketch001 = startSketchOn(part001, seg01)`)
|> line([8.62, -9.57], %)
|> close(%)
|> extrude(5 + 7, %)`
const ast = parse(code)
if (err(ast)) throw ast
const ast = assertParse(code)
const sketchSnippet = `startProfileAt([3.58, 2.06], %)`
const sketchRange: [number, number] = [
const sketchRange: [number, number, boolean] = [
code.indexOf(sketchSnippet),
code.indexOf(sketchSnippet) + sketchSnippet.length,
true,
]
const sketchPathToNode = getNodePathFromSourceRange(ast, sketchRange)
const extrudeSnippet = `extrude(5 + 7, %)`
const extrudeRange: [number, number] = [
const extrudeRange: [number, number, boolean] = [
code.indexOf(extrudeSnippet),
code.indexOf(extrudeSnippet) + extrudeSnippet.length,
true,
]
const extrudePathToNode = getNodePathFromSourceRange(ast, extrudeRange)
@ -432,18 +433,19 @@ sketch001 = startSketchOn(part001, 'END')`)
|> line([-17.67, 0.85], %)
|> close(%)
part001 = extrude(5 + 7, sketch001)`
const ast = parse(code)
if (err(ast)) throw ast
const ast = assertParse(code)
const segmentSnippet = `line([4.99, -0.46], %)`
const segmentRange: [number, number] = [
const segmentRange: [number, number, boolean] = [
code.indexOf(segmentSnippet),
code.indexOf(segmentSnippet) + segmentSnippet.length,
true,
]
const segmentPathToNode = getNodePathFromSourceRange(ast, segmentRange)
const extrudeSnippet = `extrude(5 + 7, sketch001)`
const extrudeRange: [number, number] = [
const extrudeRange: [number, number, boolean] = [
code.indexOf(extrudeSnippet),
code.indexOf(extrudeSnippet) + extrudeSnippet.length,
true,
]
const extrudePathToNode = getNodePathFromSourceRange(ast, extrudeRange)
@ -466,13 +468,13 @@ describe('Testing deleteSegmentFromPipeExpression', () => {
|> line([306.21, 198.82], %)
|> line([306.21, 198.85], %, $a)
|> line([306.21, 198.87], %)`
const ast = parse(code)
if (err(ast)) throw ast
const ast = assertParse(code)
const execState = await enginelessExecutor(ast)
const lineOfInterest = 'line([306.21, 198.85], %, $a)'
const range: [number, number] = [
const range: [number, number, boolean] = [
code.indexOf(lineOfInterest),
code.indexOf(lineOfInterest) + lineOfInterest.length,
true,
]
const pathToNode = getNodePathFromSourceRange(ast, range)
const modifiedAst = deleteSegmentFromPipeExpression(
@ -544,13 +546,13 @@ ${!replace1 ? ` |> ${line}\n` : ''} |> angledLine([-65, ${
],
])(`%s`, async (_, line, [replace1, replace2]) => {
const code = makeCode(line)
const ast = parse(code)
if (err(ast)) throw ast
const ast = assertParse(code)
const execState = await enginelessExecutor(ast)
const lineOfInterest = line
const range: [number, number] = [
const range: [number, number, boolean] = [
code.indexOf(lineOfInterest),
code.indexOf(lineOfInterest) + lineOfInterest.length,
true,
]
const pathToNode = getNodePathFromSourceRange(ast, range)
const dependentSegments = findUsesOfTagInPipe(ast, pathToNode)
@ -632,14 +634,14 @@ describe('Testing removeSingleConstraintInfo', () => {
],
['tangentialArcTo([3.14 + 0, 13.14], %)', 'arrayIndex', 1],
] as const)('stdlib fn: %s', async (expectedFinish, key, value) => {
const ast = parse(code)
if (err(ast)) throw ast
const ast = assertParse(code)
const execState = await enginelessExecutor(ast)
const lineOfInterest = expectedFinish.split('(')[0] + '('
const range: [number, number] = [
const range: [number, number, boolean] = [
code.indexOf(lineOfInterest) + 1,
code.indexOf(lineOfInterest) + lineOfInterest.length,
true,
]
const pathToNode = getNodePathFromSourceRange(ast, range)
let argPosition: SimplifiedArgDetails
@ -686,14 +688,14 @@ describe('Testing removeSingleConstraintInfo', () => {
['angledLineToX([12.14 + 0, 12], %)', 'arrayIndex', 1],
['angledLineToY([30, 10.14 + 0], %)', 'arrayIndex', 0],
])('stdlib fn: %s', async (expectedFinish, key, value) => {
const ast = parse(code)
if (err(ast)) throw ast
const ast = assertParse(code)
const execState = await enginelessExecutor(ast)
const lineOfInterest = expectedFinish.split('(')[0] + '('
const range: [number, number] = [
const range: [number, number, boolean] = [
code.indexOf(lineOfInterest) + 1,
code.indexOf(lineOfInterest) + lineOfInterest.length,
true,
]
let argPosition: SimplifiedArgDetails
if (key === 'arrayIndex' && typeof value === 'number') {
@ -883,14 +885,14 @@ sketch002 = startSketchOn({
'%s',
async (name, { codeBefore, codeAfter, lineOfInterest, type }) => {
// const lineOfInterest = 'line([-2.94, 2.7], %)'
const ast = parse(codeBefore)
if (err(ast)) throw ast
const ast = assertParse(codeBefore)
const execState = await enginelessExecutor(ast)
// deleteFromSelection
const range: [number, number] = [
const range: [number, number, boolean] = [
codeBefore.indexOf(lineOfInterest),
codeBefore.indexOf(lineOfInterest) + lineOfInterest.length,
true,
]
const artifact = { type } as Artifact
const newAst = await deleteFromSelection(

View File

@ -66,8 +66,7 @@ export function startSketchOnDefault(
let pathToNode: PathToNode = [
['body', ''],
[sketchIndex, 'index'],
['declarations', 'VariableDeclaration'],
['0', 'index'],
['declaration', 'VariableDeclaration'],
['init', 'VariableDeclarator'],
]
@ -94,7 +93,7 @@ export function addStartProfileAt(
return new Error('variableDeclaration.init.type !== PipeExpression')
}
const _node = { ...node }
const init = variableDeclaration.declarations[0].init
const init = variableDeclaration.declaration.init
const startProfileAt = createCallExpressionStdLib('startProfileAt', [
createArrayExpression([
createLiteral(roundOff(at[0])),
@ -105,7 +104,7 @@ export function addStartProfileAt(
if (init.type === 'PipeExpression') {
init.body.splice(1, 0, startProfileAt)
} else {
variableDeclaration.declarations[0].init = createPipeExpression([
variableDeclaration.declaration.init = createPipeExpression([
init,
startProfileAt,
])
@ -149,8 +148,7 @@ export function addSketchTo(
let pathToNode: PathToNode = [
['body', ''],
[sketchIndex, 'index'],
['declarations', 'VariableDeclaration'],
['0', 'index'],
['declaration', 'VariableDeclaration'],
['init', 'VariableDeclarator'],
]
if (axis !== 'xy') {
@ -333,8 +331,7 @@ export function extrudeSketch(
const pathToExtrudeArg: PathToNode = [
['body', ''],
[sketchIndexInBody + 1, 'index'],
['declarations', 'VariableDeclaration'],
[0, 'index'],
['declaration', 'VariableDeclaration'],
['init', 'VariableDeclarator'],
['arguments', 'CallExpression'],
[0, 'index'],
@ -346,6 +343,36 @@ export function extrudeSketch(
}
}
export function loftSketches(
node: Node<Program>,
declarators: VariableDeclarator[]
): {
modifiedAst: Node<Program>
pathToNode: PathToNode
} {
const modifiedAst = structuredClone(node)
const name = findUniqueName(node, KCL_DEFAULT_CONSTANT_PREFIXES.LOFT)
const elements = declarators.map((d) => createIdentifier(d.id.name))
const loft = createCallExpressionStdLib('loft', [
createArrayExpression(elements),
])
const declaration = createVariableDeclaration(name, loft)
modifiedAst.body.push(declaration)
const pathToNode: PathToNode = [
['body', ''],
[modifiedAst.body.length - 1, 'index'],
['declaration', 'VariableDeclaration'],
['init', 'VariableDeclarator'],
['arguments', 'CallExpression'],
[0, 'index'],
]
return {
modifiedAst,
pathToNode,
}
}
export function revolveSketch(
node: Node<Program>,
pathToNode: PathToNode,
@ -429,8 +456,7 @@ export function revolveSketch(
const pathToRevolveArg: PathToNode = [
['body', ''],
[sketchIndexInBody + 1, 'index'],
['declarations', 'VariableDeclaration'],
[0, 'index'],
['declaration', 'VariableDeclaration'],
['init', 'VariableDeclarator'],
['arguments', 'CallExpression'],
[0, 'index'],
@ -516,8 +542,7 @@ export function sketchOnExtrudedFace(
const newpathToNode: PathToNode = [
['body', ''],
[expressionIndex + 1, 'index'],
['declarations', 'VariableDeclaration'],
[0, 'index'],
['declaration', 'VariableDeclaration'],
['init', 'VariableDeclarator'],
]
@ -554,8 +579,7 @@ export function addOffsetPlane({
const pathToNode: PathToNode = [
['body', ''],
[modifiedAst.body.length - 1, 'index'],
['declarations', 'VariableDeclaration'],
['0', 'index'],
['declaration', 'VariableDeclaration'],
['init', 'VariableDeclarator'],
['arguments', 'CallExpression'],
[0, 'index'],
@ -792,17 +816,15 @@ export function createVariableDeclaration(
end: 0,
moduleId: 0,
declarations: [
{
type: 'VariableDeclarator',
start: 0,
end: 0,
moduleId: 0,
declaration: {
type: 'VariableDeclarator',
start: 0,
end: 0,
moduleId: 0,
id: createIdentifier(varName),
init,
},
],
id: createIdentifier(varName),
init,
},
visibility,
kind,
}
@ -1089,7 +1111,7 @@ export async function deleteFromSelection(
traverse(astClone, {
enter: (node, path) => {
if (node.type === 'VariableDeclaration') {
const dec = node.declarations[0]
const dec = node.declaration
if (
dec.init.type === 'CallExpression' &&
(dec.init.callee.name === 'extrude' ||
@ -1124,7 +1146,7 @@ export async function deleteFromSelection(
enter: (node, path) => {
;(async () => {
if (node.type === 'VariableDeclaration') {
currentVariableName = node.declarations[0].id.name
currentVariableName = node.declaration.id.name
}
if (
// match startSketchOn(${extrudeNameToDelete})

View File

@ -1,5 +1,5 @@
import {
parse,
assertParse,
recast,
initPromise,
PathToNode,
@ -18,11 +18,11 @@ import {
FilletParameters,
ChamferParameters,
EdgeTreatmentParameters,
} from './addFillet'
} from './addEdgeTreatment'
import { getNodeFromPath, getNodePathFromSourceRange } from '../queryAst'
import { createLiteral } from 'lang/modifyAst'
import { err } from 'lib/trap'
import { Selections } from 'lib/selections'
import { Selection, Selections } from 'lib/selections'
import { engineCommandManager, kclManager } from 'lib/singletons'
import { VITE_KC_DEV_TOKEN } from 'env'
import { isOverlap } from 'lib/utils'
@ -78,9 +78,10 @@ const runGetPathToExtrudeForSegmentSelectionTest = async (
code: string,
expectedExtrudeSnippet: string
): CallExpression | PipeExpression | Error {
const extrudeRange: [number, number] = [
const extrudeRange: [number, number, boolean] = [
code.indexOf(expectedExtrudeSnippet),
code.indexOf(expectedExtrudeSnippet) + expectedExtrudeSnippet.length,
true,
]
const expectedExtrudePath = getNodePathFromSourceRange(ast, extrudeRange)
const expectedExtrudeNodeResult = getNodeFromPath<
@ -109,22 +110,16 @@ const runGetPathToExtrudeForSegmentSelectionTest = async (
}
// ast
const astOrError = parse(code)
if (err(astOrError)) return new Error('AST not found')
const ast = astOrError
const ast = assertParse(code)
// selection
const segmentRange: [number, number] = [
const segmentRange: [number, number, boolean] = [
code.indexOf(selectedSegmentSnippet),
code.indexOf(selectedSegmentSnippet) + selectedSegmentSnippet.length,
true,
]
const selection: Selections = {
graphSelections: [
{
codeRef: codeRefFromRange(segmentRange, ast),
},
],
otherSelections: [],
const selection: Selection = {
codeRef: codeRefFromRange(segmentRange, ast),
}
// executeAst and artifactGraph
@ -263,17 +258,14 @@ const runModifyAstCloneWithEdgeTreatmentAndTag = async (
expectedCode: string
) => {
// ast
const astOrError = parse(code)
if (err(astOrError)) {
return new Error('AST not found')
}
const ast = astOrError
const ast = assertParse(code)
// selection
const segmentRanges: Array<[number, number]> = selectionSnippets.map(
const segmentRanges: Array<[number, number, boolean]> = selectionSnippets.map(
(selectionSnippet) => [
code.indexOf(selectionSnippet),
code.indexOf(selectionSnippet) + selectionSnippet.length,
true,
]
)
@ -603,12 +595,12 @@ extrude001 = extrude(-5, sketch001)
}, %)
`
it('should correctly identify getOppositeEdge and baseEdge edges', () => {
const ast = parse(code)
if (err(ast)) return
const ast = assertParse(code)
const lineOfInterest = `line([7.11, 3.48], %, $seg01)`
const range: [number, number] = [
const range: [number, number, boolean] = [
code.indexOf(lineOfInterest),
code.indexOf(lineOfInterest) + lineOfInterest.length,
true,
]
const pathToNode = getNodePathFromSourceRange(ast, range)
if (err(pathToNode)) return
@ -622,12 +614,12 @@ extrude001 = extrude(-5, sketch001)
expect(edges).toEqual(['getOppositeEdge', 'baseEdge'])
})
it('should correctly identify getPreviousAdjacentEdge edges', () => {
const ast = parse(code)
if (err(ast)) return
const ast = assertParse(code)
const lineOfInterest = `line([-6.37, 3.88], %, $seg02)`
const range: [number, number] = [
const range: [number, number, boolean] = [
code.indexOf(lineOfInterest),
code.indexOf(lineOfInterest) + lineOfInterest.length,
true,
]
const pathToNode = getNodePathFromSourceRange(ast, range)
if (err(pathToNode)) return
@ -641,12 +633,12 @@ extrude001 = extrude(-5, sketch001)
expect(edges).toEqual(['getPreviousAdjacentEdge'])
})
it('should correctly identify no edges', () => {
const ast = parse(code)
if (err(ast)) return
const ast = assertParse(code)
const lineOfInterest = `line([-3.29, -13.85], %)`
const range: [number, number] = [
const range: [number, number, boolean] = [
code.indexOf(lineOfInterest),
code.indexOf(lineOfInterest) + lineOfInterest.length,
true,
]
const pathToNode = getNodePathFromSourceRange(ast, range)
if (err(pathToNode)) return
@ -667,19 +659,15 @@ describe('Testing button states', () => {
segmentSnippet: string,
expectedState: boolean
) => {
// ast
const astOrError = parse(code)
if (err(astOrError)) {
return new Error('AST not found')
}
const ast = astOrError
const ast = assertParse(code)
const range: [number, number] = segmentSnippet
const range: [number, number, boolean] = segmentSnippet
? [
code.indexOf(segmentSnippet),
code.indexOf(segmentSnippet) + segmentSnippet.length,
true,
]
: [ast.end, ast.end] // empty line in the end of the code
: [ast.end, ast.end, true] // empty line in the end of the code
const selectionRanges: Selections = {
graphSelections: [

View File

@ -29,7 +29,7 @@ import {
sketchLineHelperMap,
} from '../std/sketch'
import { err, trap } from 'lib/trap'
import { Selections } from 'lib/selections'
import { Selection, Selections } from 'lib/selections'
import { KclCommandValue } from 'lib/commandTypes'
import {
Artifact,
@ -99,14 +99,9 @@ export function modifyAstWithEdgeTreatmentAndTag(
const lookupMap: Map<string, PathToNode> = new Map() // work around for Map key comparison
for (const selection of selections.graphSelections) {
const singleSelection = {
graphSelections: [selection],
otherSelections: [],
}
const result = getPathToExtrudeForSegmentSelection(
clonedAstForGetExtrude,
singleSelection,
selection,
artifactGraph
)
if (err(result)) return result
@ -259,12 +254,12 @@ function insertParametersIntoAst(
export function getPathToExtrudeForSegmentSelection(
ast: Program,
selection: Selections,
selection: Selection,
artifactGraph: ArtifactGraph
): { pathToSegmentNode: PathToNode; pathToExtrudeNode: PathToNode } | Error {
const pathToSegmentNode = getNodePathFromSourceRange(
ast,
selection.graphSelections[0]?.codeRef?.range
selection.codeRef?.range
)
const varDecNode = getNodeFromPath<VariableDeclaration>(
@ -273,7 +268,7 @@ export function getPathToExtrudeForSegmentSelection(
'VariableDeclaration'
)
if (err(varDecNode)) return varDecNode
const sketchVar = varDecNode.node.declarations[0].id.name
const sketchVar = varDecNode.node.declaration.id.name
const sketch = sketchFromKclValue(
kclManager.programMemory.get(sketchVar),
@ -308,7 +303,7 @@ async function updateAstAndFocus(
}
}
function mutateAstWithTagForSketchSegment(
export function mutateAstWithTagForSketchSegment(
astClone: Node<Program>,
pathToSegmentNode: PathToNode
): { modifiedAst: Program; tag: string } | Error {
@ -367,7 +362,7 @@ function locateExtrudeDeclarator(
if (err(nodeOfExtrudeCall)) return nodeOfExtrudeCall
const { node: extrudeVarDecl } = nodeOfExtrudeCall
const extrudeDeclarator = extrudeVarDecl.declarations[0]
const extrudeDeclarator = extrudeVarDecl.declaration
if (!extrudeDeclarator) {
return new Error('Extrude Declarator not found.')
}

View File

@ -0,0 +1,124 @@
import { ArtifactGraph } from 'lang/std/artifactGraph'
import { Selections } from 'lib/selections'
import { Expr } from 'wasm-lib/kcl/bindings/Expr'
import { Program } from 'wasm-lib/kcl/bindings/Program'
import { Node } from 'wasm-lib/kcl/bindings/Node'
import { PathToNode, VariableDeclarator } from 'lang/wasm'
import {
getPathToExtrudeForSegmentSelection,
mutateAstWithTagForSketchSegment,
} from './addEdgeTreatment'
import { getNodeFromPath } from 'lang/queryAst'
import { err } from 'lib/trap'
import {
createLiteral,
createIdentifier,
findUniqueName,
createCallExpressionStdLib,
createObjectExpression,
createArrayExpression,
createVariableDeclaration,
} from 'lang/modifyAst'
import { KCL_DEFAULT_CONSTANT_PREFIXES } from 'lib/constants'
export function addShell({
node,
selection,
artifactGraph,
thickness,
}: {
node: Node<Program>
selection: Selections
artifactGraph: ArtifactGraph
thickness: Expr
}): Error | { modifiedAst: Node<Program>; pathToNode: PathToNode } {
const modifiedAst = structuredClone(node)
// Look up the corresponding extrude
const clonedAstForGetExtrude = structuredClone(modifiedAst)
const expressions: Expr[] = []
let pathToExtrudeNode: PathToNode | undefined = undefined
for (const graphSelection of selection.graphSelections) {
const extrudeLookupResult = getPathToExtrudeForSegmentSelection(
clonedAstForGetExtrude,
graphSelection,
artifactGraph
)
if (err(extrudeLookupResult)) {
return new Error("Couldn't find extrude")
}
pathToExtrudeNode = extrudeLookupResult.pathToExtrudeNode
// Get the sketch ref from the selection
// TODO: this assumes the segment is piped directly from the sketch, with no intermediate `VariableDeclarator` between.
// We must find a technique for these situations that is robust to intermediate declarations
const sketchNode = getNodeFromPath<VariableDeclarator>(
modifiedAst,
graphSelection.codeRef.pathToNode,
'VariableDeclarator'
)
if (err(sketchNode)) {
return sketchNode
}
const selectedArtifact = graphSelection.artifact
if (!selectedArtifact) {
return new Error('Bad artifact')
}
// Check on the selection, and handle the wall vs cap casees
let expr: Expr
if (selectedArtifact.type === 'cap') {
expr = createLiteral(selectedArtifact.subType)
} else if (selectedArtifact.type === 'wall') {
const tagResult = mutateAstWithTagForSketchSegment(
modifiedAst,
extrudeLookupResult.pathToSegmentNode
)
if (err(tagResult)) return tagResult
const { tag } = tagResult
expr = createIdentifier(tag)
} else {
continue
}
expressions.push(expr)
}
if (!pathToExtrudeNode) return new Error('No extrude found')
const extrudeNode = getNodeFromPath<VariableDeclarator>(
modifiedAst,
pathToExtrudeNode,
'VariableDeclarator'
)
if (err(extrudeNode)) {
return extrudeNode
}
const name = findUniqueName(node, KCL_DEFAULT_CONSTANT_PREFIXES.SHELL)
const shell = createCallExpressionStdLib('shell', [
createObjectExpression({
faces: createArrayExpression(expressions),
thickness,
}),
createIdentifier(extrudeNode.node.id.name),
])
const declaration = createVariableDeclaration(name, shell)
// TODO: check if we should append at the end like here or right after the extrude
modifiedAst.body.push(declaration)
const pathToNode: PathToNode = [
['body', ''],
[modifiedAst.body.length - 1, 'index'],
['declarations', 'VariableDeclaration'],
['0', 'index'],
['init', 'VariableDeclarator'],
['arguments', 'CallExpression'],
[0, 'index'],
]
return {
modifiedAst,
pathToNode,
}
}

View File

@ -1,4 +1,10 @@
import { parse, recast, initPromise, PathToNode, Identifier } from './wasm'
import {
assertParse,
recast,
initPromise,
PathToNode,
Identifier,
} from './wasm'
import {
findAllPreviousVariables,
isNodeSafeToReplace,
@ -11,6 +17,7 @@ import {
doesSceneHaveSweepableSketch,
traverse,
getNodeFromPath,
doesSceneHaveExtrudedSketch,
} from './queryAst'
import { enginelessExecutor } from '../lib/testHelpers'
import {
@ -45,14 +52,13 @@ part001 = startSketchOn('XY')
variableBelowShouldNotBeIncluded = 3
`
const rangeStart = code.indexOf('// selection-range-7ish-before-this') - 7
const ast = parse(code)
if (err(ast)) throw ast
const ast = assertParse(code)
const execState = await enginelessExecutor(ast)
const { variables, bodyPath, insertIndex } = findAllPreviousVariables(
ast,
execState.memory,
[rangeStart, rangeStart]
[rangeStart, rangeStart, true]
)
expect(variables).toEqual([
{ key: 'baseThick', value: 1 },
@ -80,10 +86,9 @@ describe('testing argIsNotIdentifier', () => {
yo = 5 + 6
yo2 = hmm([identifierGuy + 5])`
it('find a safe binaryExpression', () => {
const ast = parse(code)
if (err(ast)) throw ast
const ast = assertParse(code)
const rangeStart = code.indexOf('100 + 100') + 2
const result = isNodeSafeToReplace(ast, [rangeStart, rangeStart])
const result = isNodeSafeToReplace(ast, [rangeStart, rangeStart, true])
if (err(result)) throw result
expect(result.isSafe).toBe(true)
expect(result.value?.type).toBe('BinaryExpression')
@ -94,20 +99,18 @@ yo2 = hmm([identifierGuy + 5])`
expect(outCode).toContain(`angledLine([replaceName, 3.09], %)`)
})
it('find a safe Identifier', () => {
const ast = parse(code)
if (err(ast)) throw ast
const ast = assertParse(code)
const rangeStart = code.indexOf('abc')
const result = isNodeSafeToReplace(ast, [rangeStart, rangeStart])
const result = isNodeSafeToReplace(ast, [rangeStart, rangeStart, true])
if (err(result)) throw result
expect(result.isSafe).toBe(true)
expect(result.value?.type).toBe('Identifier')
expect(code.slice(result.value.start, result.value.end)).toBe('abc')
})
it('find a safe CallExpression', () => {
const ast = parse(code)
if (err(ast)) throw ast
const ast = assertParse(code)
const rangeStart = code.indexOf('def')
const result = isNodeSafeToReplace(ast, [rangeStart, rangeStart])
const result = isNodeSafeToReplace(ast, [rangeStart, rangeStart, true])
if (err(result)) throw result
expect(result.isSafe).toBe(true)
expect(result.value?.type).toBe('CallExpression')
@ -118,10 +121,9 @@ yo2 = hmm([identifierGuy + 5])`
expect(outCode).toContain(`angledLine([replaceName, 3.09], %)`)
})
it('find an UNsafe CallExpression, as it has a PipeSubstitution', () => {
const ast = parse(code)
if (err(ast)) throw ast
const ast = assertParse(code)
const rangeStart = code.indexOf('ghi')
const range: [number, number] = [rangeStart, rangeStart]
const range: [number, number, boolean] = [rangeStart, rangeStart, true]
const result = isNodeSafeToReplace(ast, range)
if (err(result)) throw result
expect(result.isSafe).toBe(false)
@ -129,10 +131,9 @@ yo2 = hmm([identifierGuy + 5])`
expect(code.slice(result.value.start, result.value.end)).toBe('ghi(%)')
})
it('find an UNsafe Identifier, as it is a callee', () => {
const ast = parse(code)
if (err(ast)) throw ast
const ast = assertParse(code)
const rangeStart = code.indexOf('ine([2.8,')
const result = isNodeSafeToReplace(ast, [rangeStart, rangeStart])
const result = isNodeSafeToReplace(ast, [rangeStart, rangeStart, true])
if (err(result)) throw result
expect(result.isSafe).toBe(false)
expect(result.value?.type).toBe('CallExpression')
@ -141,10 +142,9 @@ yo2 = hmm([identifierGuy + 5])`
)
})
it("find a safe BinaryExpression that's assigned to a variable", () => {
const ast = parse(code)
if (err(ast)) throw ast
const ast = assertParse(code)
const rangeStart = code.indexOf('5 + 6') + 1
const result = isNodeSafeToReplace(ast, [rangeStart, rangeStart])
const result = isNodeSafeToReplace(ast, [rangeStart, rangeStart, true])
if (err(result)) throw result
expect(result.isSafe).toBe(true)
expect(result.value?.type).toBe('BinaryExpression')
@ -155,10 +155,9 @@ yo2 = hmm([identifierGuy + 5])`
expect(outCode).toContain(`yo = replaceName`)
})
it('find a safe BinaryExpression that has a CallExpression within', () => {
const ast = parse(code)
if (err(ast)) throw ast
const ast = assertParse(code)
const rangeStart = code.indexOf('jkl') + 1
const result = isNodeSafeToReplace(ast, [rangeStart, rangeStart])
const result = isNodeSafeToReplace(ast, [rangeStart, rangeStart, true])
if (err(result)) throw result
expect(result.isSafe).toBe(true)
expect(result.value?.type).toBe('BinaryExpression')
@ -172,11 +171,10 @@ yo2 = hmm([identifierGuy + 5])`
expect(outCode).toContain(`angledLine([replaceName, 3.09], %)`)
})
it('find a safe BinaryExpression within a CallExpression', () => {
const ast = parse(code)
if (err(ast)) throw ast
const ast = assertParse(code)
const rangeStart = code.indexOf('identifierGuy') + 1
const result = isNodeSafeToReplace(ast, [rangeStart, rangeStart])
const result = isNodeSafeToReplace(ast, [rangeStart, rangeStart, true])
if (err(result)) throw result
expect(result.isSafe).toBe(true)
@ -223,15 +221,17 @@ describe('testing getNodePathFromSourceRange', () => {
it('finds the second line when cursor is put at the end', () => {
const searchLn = `line([0.94, 2.61], %)`
const sourceIndex = code.indexOf(searchLn) + searchLn.length
const ast = parse(code)
if (err(ast)) throw ast
const ast = assertParse(code)
const result = getNodePathFromSourceRange(ast, [sourceIndex, sourceIndex])
const result = getNodePathFromSourceRange(ast, [
sourceIndex,
sourceIndex,
true,
])
expect(result).toEqual([
['body', ''],
[0, 'index'],
['declarations', 'VariableDeclaration'],
[0, 'index'],
['declaration', 'VariableDeclaration'],
['init', ''],
['body', 'PipeExpression'],
[2, 'index'],
@ -240,15 +240,17 @@ describe('testing getNodePathFromSourceRange', () => {
it('finds the last line when cursor is put at the end', () => {
const searchLn = `line([-0.21, -1.4], %)`
const sourceIndex = code.indexOf(searchLn) + searchLn.length
const ast = parse(code)
if (err(ast)) throw ast
const ast = assertParse(code)
const result = getNodePathFromSourceRange(ast, [sourceIndex, sourceIndex])
const result = getNodePathFromSourceRange(ast, [
sourceIndex,
sourceIndex,
true,
])
const expected = [
['body', ''],
[0, 'index'],
['declarations', 'VariableDeclaration'],
[0, 'index'],
['declaration', 'VariableDeclaration'],
['init', ''],
['body', 'PipeExpression'],
[3, 'index'],
@ -259,12 +261,14 @@ describe('testing getNodePathFromSourceRange', () => {
const startResult = getNodePathFromSourceRange(ast, [
startSourceIndex,
startSourceIndex,
true,
])
expect(startResult).toEqual([...expected, ['callee', 'CallExpression']])
// expect similar result when whole line is selected
const selectWholeThing = getNodePathFromSourceRange(ast, [
startSourceIndex,
sourceIndex,
true,
])
expect(selectWholeThing).toEqual(expected)
})
@ -278,15 +282,17 @@ describe('testing getNodePathFromSourceRange', () => {
}`
const searchLn = `x > y`
const sourceIndex = code.indexOf(searchLn)
const ast = parse(code)
if (err(ast)) throw ast
const ast = assertParse(code)
const result = getNodePathFromSourceRange(ast, [sourceIndex, sourceIndex])
const result = getNodePathFromSourceRange(ast, [
sourceIndex,
sourceIndex,
true,
])
expect(result).toEqual([
['body', ''],
[1, 'index'],
['declarations', 'VariableDeclaration'],
[0, 'index'],
['declaration', 'VariableDeclaration'],
['init', ''],
['cond', 'IfExpression'],
['left', 'BinaryExpression'],
@ -306,15 +312,17 @@ describe('testing getNodePathFromSourceRange', () => {
}`
const searchLn = `x + 1`
const sourceIndex = code.indexOf(searchLn)
const ast = parse(code)
if (err(ast)) throw ast
const ast = assertParse(code)
const result = getNodePathFromSourceRange(ast, [sourceIndex, sourceIndex])
const result = getNodePathFromSourceRange(ast, [
sourceIndex,
sourceIndex,
true,
])
expect(result).toEqual([
['body', ''],
[1, 'index'],
['declarations', 'VariableDeclaration'],
[0, 'index'],
['declaration', 'VariableDeclaration'],
['init', ''],
['then_val', 'IfExpression'],
['body', 'IfExpression'],
@ -332,14 +340,18 @@ describe('testing getNodePathFromSourceRange', () => {
const code = `import foo, bar as baz from 'thing.kcl'`
const searchLn = `bar`
const sourceIndex = code.indexOf(searchLn)
const ast = parse(code)
if (err(ast)) throw ast
const ast = assertParse(code)
const result = getNodePathFromSourceRange(ast, [sourceIndex, sourceIndex])
const result = getNodePathFromSourceRange(ast, [
sourceIndex,
sourceIndex,
true,
])
expect(result).toEqual([
['body', ''],
[0, 'index'],
['items', 'ImportStatement'],
['selector', 'ImportStatement'],
['items', 'ImportSelector'],
[1, 'index'],
['name', 'ImportItem'],
])
@ -360,14 +372,13 @@ part001 = startSketchAt([-1.41, 3.46])
|> angledLine([-175, segLen(seg01)], %)
|> close(%)
`
const ast = parse(exampleCode)
if (err(ast)) throw ast
const ast = assertParse(exampleCode)
const result = doesPipeHaveCallExp({
calleeName: 'close',
ast,
selection: {
codeRef: codeRefFromRange([100, 101], ast),
codeRef: codeRefFromRange([100, 101, true], ast),
},
})
expect(result).toEqual(true)
@ -382,14 +393,13 @@ part001 = startSketchAt([-1.41, 3.46])
|> close(%)
|> extrude(1, %)
`
const ast = parse(exampleCode)
if (err(ast)) throw ast
const ast = assertParse(exampleCode)
const result = doesPipeHaveCallExp({
calleeName: 'extrude',
ast,
selection: {
codeRef: codeRefFromRange([100, 101], ast),
codeRef: codeRefFromRange([100, 101, true], ast),
},
})
expect(result).toEqual(true)
@ -402,28 +412,26 @@ part001 = startSketchAt([-1.41, 3.46])
|> line([-3.22, -7.36], %)
|> angledLine([-175, segLen(seg01)], %)
`
const ast = parse(exampleCode)
if (err(ast)) throw ast
const ast = assertParse(exampleCode)
const result = doesPipeHaveCallExp({
calleeName: 'close',
ast,
selection: {
codeRef: codeRefFromRange([100, 101], ast),
codeRef: codeRefFromRange([100, 101, true], ast),
},
})
expect(result).toEqual(false)
})
it('returns false if not a pipe', () => {
const exampleCode = `length001 = 2`
const ast = parse(exampleCode)
if (err(ast)) throw ast
const ast = assertParse(exampleCode)
const result = doesPipeHaveCallExp({
calleeName: 'close',
ast,
selection: {
codeRef: codeRefFromRange([9, 10], ast),
codeRef: codeRefFromRange([9, 10, true], ast),
},
})
expect(result).toEqual(false)
@ -438,14 +446,13 @@ part001 = startSketchAt([-1.41, 3.46])
|> angledLine([-35, length001], %)
|> line([-3.22, -7.36], %)
|> angledLine([-175, segLen(seg01)], %)`
const ast = parse(exampleCode)
if (err(ast)) throw ast
const ast = assertParse(exampleCode)
const execState = await enginelessExecutor(ast)
const result = hasExtrudeSketch({
ast,
selection: {
codeRef: codeRefFromRange([100, 101], ast),
codeRef: codeRefFromRange([100, 101, true], ast),
},
programMemory: execState.memory,
})
@ -459,14 +466,13 @@ part001 = startSketchAt([-1.41, 3.46])
|> line([-3.22, -7.36], %)
|> angledLine([-175, segLen(seg01)], %)
|> extrude(1, %)`
const ast = parse(exampleCode)
if (err(ast)) throw ast
const ast = assertParse(exampleCode)
const execState = await enginelessExecutor(ast)
const result = hasExtrudeSketch({
ast,
selection: {
codeRef: codeRefFromRange([100, 101], ast),
codeRef: codeRefFromRange([100, 101, true], ast),
},
programMemory: execState.memory,
})
@ -474,14 +480,13 @@ part001 = startSketchAt([-1.41, 3.46])
})
it('finds nothing', async () => {
const exampleCode = `length001 = 2`
const ast = parse(exampleCode)
if (err(ast)) throw ast
const ast = assertParse(exampleCode)
const execState = await enginelessExecutor(ast)
const result = hasExtrudeSketch({
ast,
selection: {
codeRef: codeRefFromRange([10, 11], ast),
codeRef: codeRefFromRange([10, 11, true], ast),
},
programMemory: execState.memory,
})
@ -498,8 +503,7 @@ describe('Testing findUsesOfTagInPipe', () => {
|> line([306.21, 198.87], %)
|> angledLine([65, segLen(seg01)], %)`
it('finds the current segment', async () => {
const ast = parse(exampleCode)
if (err(ast)) throw ast
const ast = assertParse(exampleCode)
const lineOfInterest = `198.85], %, $seg01`
const characterIndex =
@ -507,6 +511,7 @@ describe('Testing findUsesOfTagInPipe', () => {
const pathToNode = getNodePathFromSourceRange(ast, [
characterIndex,
characterIndex,
true,
])
const result = findUsesOfTagInPipe(ast, pathToNode)
expect(result).toHaveLength(2)
@ -515,8 +520,7 @@ describe('Testing findUsesOfTagInPipe', () => {
})
})
it('find no tag if line has no tag', () => {
const ast = parse(exampleCode)
if (err(ast)) throw ast
const ast = assertParse(exampleCode)
const lineOfInterest = `line([306.21, 198.82], %)`
const characterIndex =
@ -524,6 +528,7 @@ describe('Testing findUsesOfTagInPipe', () => {
const pathToNode = getNodePathFromSourceRange(ast, [
characterIndex,
characterIndex,
true,
])
const result = findUsesOfTagInPipe(ast, pathToNode)
expect(result).toHaveLength(0)
@ -564,42 +569,39 @@ sketch003 = startSketchOn(extrude001, 'END')
|> extrude(3.14, %)
`
it('identifies sketch001 pipe as extruded (extrusion after pipe)', async () => {
const ast = parse(exampleCode)
if (err(ast)) throw ast
const ast = assertParse(exampleCode)
const lineOfInterest = `line([4.99, -0.46], %, $seg01)`
const characterIndex =
exampleCode.indexOf(lineOfInterest) + lineOfInterest.length
const extruded = hasSketchPipeBeenExtruded(
{
codeRef: codeRefFromRange([characterIndex, characterIndex], ast),
codeRef: codeRefFromRange([characterIndex, characterIndex, true], ast),
},
ast
)
expect(extruded).toBeTruthy()
})
it('identifies sketch002 pipe as not extruded', async () => {
const ast = parse(exampleCode)
if (err(ast)) throw ast
const ast = assertParse(exampleCode)
const lineOfInterest = `line([2.45, -0.2], %)`
const characterIndex =
exampleCode.indexOf(lineOfInterest) + lineOfInterest.length
const extruded = hasSketchPipeBeenExtruded(
{
codeRef: codeRefFromRange([characterIndex, characterIndex], ast),
codeRef: codeRefFromRange([characterIndex, characterIndex, true], ast),
},
ast
)
expect(extruded).toBeFalsy()
})
it('identifies sketch003 pipe as extruded (extrusion within pipe)', async () => {
const ast = parse(exampleCode)
if (err(ast)) throw ast
const ast = assertParse(exampleCode)
const lineOfInterest = `|> line([3.12, 1.74], %)`
const characterIndex =
exampleCode.indexOf(lineOfInterest) + lineOfInterest.length
const extruded = hasSketchPipeBeenExtruded(
{
codeRef: codeRefFromRange([characterIndex, characterIndex], ast),
codeRef: codeRefFromRange([characterIndex, characterIndex, true], ast),
},
ast
)
@ -623,11 +625,21 @@ sketch002 = startSketchOn(extrude001, $seg01)
|> lineTo([profileStartX(%), profileStartY(%)], %)
|> close(%)
`
const ast = parse(exampleCode)
if (err(ast)) throw ast
const ast = assertParse(exampleCode)
const extrudable = doesSceneHaveSweepableSketch(ast)
expect(extrudable).toBeTruthy()
})
it('finds sketch001 and sketch002 pipes to be lofted', async () => {
const exampleCode = `sketch001 = startSketchOn('XZ')
|> circle({ center = [0, 0], radius = 1 }, %)
plane001 = offsetPlane('XZ', 2)
sketch002 = startSketchOn(plane001)
|> circle({ center = [0, 0], radius = 3 }, %)
`
const ast = assertParse(exampleCode)
const extrudable = doesSceneHaveSweepableSketch(ast, 2)
expect(extrudable).toBeTruthy()
})
it('find sketch002 NOT pipe to be extruded', async () => {
const exampleCode = `sketch001 = startSketchOn('XZ')
|> startProfileAt([3.29, 7.86], %)
@ -636,10 +648,41 @@ sketch002 = startSketchOn(extrude001, $seg01)
|> line([-17.67, 0.85], %)
|> close(%)
extrude001 = extrude(10, sketch001)
`
const ast = assertParse(exampleCode)
const extrudable = doesSceneHaveSweepableSketch(ast)
expect(extrudable).toBeFalsy()
})
})
describe('Testing doesSceneHaveExtrudedSketch', () => {
it('finds extruded sketch as variable', async () => {
const exampleCode = `sketch001 = startSketchOn('XZ')
|> circle({ center = [0, 0], radius = 1 }, %)
extrude001 = extrude(1, sketch001)
`
const ast = parse(exampleCode)
if (err(ast)) throw ast
const extrudable = doesSceneHaveSweepableSketch(ast)
const extrudable = doesSceneHaveExtrudedSketch(ast)
expect(extrudable).toBeTruthy()
})
it('finds extruded sketch in pipe', async () => {
const exampleCode = `extrude001 = startSketchOn('XZ')
|> circle({ center = [0, 0], radius = 1 }, %)
|> extrude(1, %)
`
const ast = parse(exampleCode)
if (err(ast)) throw ast
const extrudable = doesSceneHaveExtrudedSketch(ast)
expect(extrudable).toBeTruthy()
})
it('finds no extrusion with sketch only', async () => {
const exampleCode = `extrude001 = startSketchOn('XZ')
|> circle({ center = [0, 0], radius = 1 }, %)
`
const ast = parse(exampleCode)
if (err(ast)) throw ast
const extrudable = doesSceneHaveExtrudedSketch(ast)
expect(extrudable).toBeFalsy()
})
})
@ -666,8 +709,7 @@ myNestedVar = [
}
]
`
const ast = parse(code)
if (err(ast)) throw ast
const ast = assertParse(code)
let pathToNode: PathToNode = []
traverse(ast, {
enter: (node, path) => {
@ -689,6 +731,7 @@ myNestedVar = [
const pathToNode2 = getNodePathFromSourceRange(ast, [
literalIndex + 2,
literalIndex + 2,
true,
])
expect(pathToNode).toEqual(pathToNode2)
})

View File

@ -16,6 +16,7 @@ import {
sketchFromKclValue,
sketchFromKclValueOptional,
SourceRange,
sourceRangeFromRust,
SyntaxType,
VariableDeclaration,
VariableDeclarator,
@ -173,6 +174,30 @@ function moreNodePathFromSourceRange(
}
return path
}
if (_node.type === 'CallExpressionKw' && isInRange) {
const { callee, arguments: args } = _node
if (
callee.type === 'Identifier' &&
callee.start <= start &&
callee.end >= end
) {
path.push(['callee', 'CallExpressionKw'])
return path
}
if (args.length > 0) {
for (let argIndex = 0; argIndex < args.length; argIndex++) {
const arg = args[argIndex].arg
if (arg.start <= start && arg.end >= end) {
path.push(['arguments', 'CallExpressionKw'])
path.push([argIndex, 'index'])
return moreNodePathFromSourceRange(arg, sourceRange, path)
}
}
}
return path
}
if (_node.type === 'BinaryExpression' && isInRange) {
const { left, right } = _node
if (left.start <= start && left.end >= end) {
@ -234,34 +259,26 @@ function moreNodePathFromSourceRange(
return moreNodePathFromSourceRange(expression, sourceRange, path)
}
if (_node.type === 'VariableDeclaration' && isInRange) {
const declarations = _node.declarations
const declaration = _node.declaration
for (let decIndex = 0; decIndex < declarations.length; decIndex++) {
const declaration = declarations[decIndex]
if (declaration.start <= start && declaration.end >= end) {
path.push(['declarations', 'VariableDeclaration'])
path.push([decIndex, 'index'])
const init = declaration.init
if (init.start <= start && init.end >= end) {
path.push(['init', ''])
return moreNodePathFromSourceRange(init, sourceRange, path)
}
if (declaration.start <= start && declaration.end >= end) {
path.push(['declaration', 'VariableDeclaration'])
const init = declaration.init
if (init.start <= start && init.end >= end) {
path.push(['init', ''])
return moreNodePathFromSourceRange(init, sourceRange, path)
}
}
}
if (_node.type === 'VariableDeclaration' && isInRange) {
const declarations = _node.declarations
const declaration = _node.declaration
for (let decIndex = 0; decIndex < declarations.length; decIndex++) {
const declaration = declarations[decIndex]
if (declaration.start <= start && declaration.end >= end) {
const init = declaration.init
if (init.start <= start && init.end >= end) {
path.push(['declarations', 'VariableDeclaration'])
path.push([decIndex, 'index'])
path.push(['init', ''])
return moreNodePathFromSourceRange(init, sourceRange, path)
}
if (declaration.start <= start && declaration.end >= end) {
const init = declaration.init
if (init.start <= start && init.end >= end) {
path.push(['declaration', 'VariableDeclaration'])
path.push(['init', ''])
return moreNodePathFromSourceRange(init, sourceRange, path)
}
}
return path
@ -355,24 +372,31 @@ function moreNodePathFromSourceRange(
}
if (_node.type === 'ImportStatement' && isInRange) {
const { items } = _node
for (let i = 0; i < items.length; i++) {
const item = items[i]
if (item.start <= start && item.end >= end) {
path.push(['items', 'ImportStatement'])
path.push([i, 'index'])
if (item.name.start <= start && item.name.end >= end) {
path.push(['name', 'ImportItem'])
if (_node.selector && _node.selector.type === 'List') {
path.push(['selector', 'ImportStatement'])
const { items } = _node.selector
for (let i = 0; i < items.length; i++) {
const item = items[i]
if (item.start <= start && item.end >= end) {
path.push(['items', 'ImportSelector'])
path.push([i, 'index'])
if (item.name.start <= start && item.name.end >= end) {
path.push(['name', 'ImportItem'])
return path
}
if (
item.alias &&
item.alias.start <= start &&
item.alias.end >= end
) {
path.push(['alias', 'ImportItem'])
return path
}
return path
}
if (item.alias && item.alias.start <= start && item.alias.end >= end) {
path.push(['alias', 'ImportItem'])
return path
}
return path
}
return path
}
return path
}
console.error('not implemented: ' + node.type)
@ -426,13 +450,10 @@ export function traverse(
traverse(node, option, pathToNode)
if (_node.type === 'VariableDeclaration') {
_node.declarations.forEach((declaration, index) =>
_traverse(declaration, [
...pathToNode,
['declarations', 'VariableDeclaration'],
[index, 'index'],
])
)
_traverse(_node.declaration, [
...pathToNode,
['declaration', 'VariableDeclaration'],
])
} else if (_node.type === 'VariableDeclarator') {
_traverse(_node.init, [...pathToNode, ['init', '']])
} else if (_node.type === 'PipeExpression') {
@ -542,7 +563,7 @@ export function findAllPreviousVariablesPath(
const variables: PrevVariable<any>[] = []
bodyItems?.forEach?.((item) => {
if (item.type !== 'VariableDeclaration' || item.end > startRange) return
const varName = item.declarations[0].id.name
const varName = item.declaration.id.name
const varValue = programMemory?.get(varName)
if (!varValue || typeof varValue?.value !== type) return
variables.push({
@ -645,7 +666,7 @@ export function isNodeSafeToReplacePath(
export function isNodeSafeToReplace(
ast: Node<Program>,
sourceRange: [number, number]
sourceRange: SourceRange
):
| {
isSafe: boolean
@ -736,7 +757,7 @@ export function isLinesParallelAndConstrained(
const _varDec = getNodeFromPath(ast, primaryPath, 'VariableDeclaration')
if (err(_varDec)) return _varDec
const varDec = _varDec.node
const varName = (varDec as VariableDeclaration)?.declarations[0]?.id?.name
const varName = (varDec as VariableDeclaration)?.declaration.id?.name
const sg = sketchFromKclValue(programMemory?.get(varName), varName)
if (err(sg)) return sg
const _primarySegment = getSketchSegmentFromSourceRange(
@ -797,7 +818,7 @@ export function isLinesParallelAndConstrained(
return {
isParallelAndConstrained,
selection: {
codeRef: codeRefFromRange(prevSourceRange, ast),
codeRef: codeRefFromRange(sourceRangeFromRust(prevSourceRange), ast),
artifact: artifactGraph.get(prevSegment.__geoMeta.id),
},
}
@ -856,7 +877,7 @@ export function hasExtrudeSketch({
}
const varDec = varDecMeta.node
if (varDec.type !== 'VariableDeclaration') return false
const varName = varDec.declarations[0].id.name
const varName = varDec.declaration.id.name
const varValue = programMemory?.get(varName)
return (
varValue?.type === 'Solid' ||
@ -933,7 +954,8 @@ export function findUsesOfTagInPipe(
return
const tagArgValue =
tagArg.type === 'TagDeclarator' ? String(tagArg.value) : tagArg.name
if (tagArgValue === tag) dependentRanges.push([node.start, node.end])
if (tagArgValue === tag)
dependentRanges.push([node.start, node.end, true])
},
})
return dependentRanges
@ -975,7 +997,9 @@ export function hasSketchPipeBeenExtruded(selection: Selection, ast: Program) {
if (
node.type === 'CallExpression' &&
node.callee.type === 'Identifier' &&
(node.callee.name === 'extrude' || node.callee.name === 'revolve') &&
(node.callee.name === 'extrude' ||
node.callee.name === 'revolve' ||
node.callee.name === 'loft') &&
node.arguments?.[1]?.type === 'Identifier' &&
node.arguments[1].name === varDec.id.name
) {
@ -988,7 +1012,7 @@ export function hasSketchPipeBeenExtruded(selection: Selection, ast: Program) {
}
/** File must contain at least one sketch that has not been extruded already */
export function doesSceneHaveSweepableSketch(ast: Node<Program>) {
export function doesSceneHaveSweepableSketch(ast: Node<Program>, count = 1) {
const theMap: any = {}
traverse(ast as any, {
enter(node) {
@ -1037,6 +1061,35 @@ export function doesSceneHaveSweepableSketch(ast: Node<Program>) {
}
},
})
return Object.keys(theMap).length >= count
}
export function doesSceneHaveExtrudedSketch(ast: Node<Program>) {
const theMap: any = {}
traverse(ast as any, {
enter(node) {
if (
node.type === 'VariableDeclarator' &&
node.init?.type === 'PipeExpression'
) {
for (const pipe of node.init.body) {
if (
pipe.type === 'CallExpression' &&
pipe.callee.name === 'extrude'
) {
theMap[node.id.name] = true
break
}
}
} else if (
node.type === 'CallExpression' &&
node.callee.name === 'extrude' &&
node.arguments[1]?.type === 'Identifier'
) {
theMap[node.moduleId] = true
}
},
})
return Object.keys(theMap).length > 0
}

View File

@ -1,4 +1,4 @@
import { parse, Program, recast, initPromise } from './wasm'
import { assertParse, Program, recast, initPromise } from './wasm'
import fs from 'node:fs'
import { err } from 'lib/trap'
@ -394,8 +394,6 @@ describe('it recasts binary expression using brackets where needed', () => {
// helpers
function code2ast(code: string): { ast: Program } {
const ast = parse(code)
// eslint-ignore-next-line
if (err(ast)) throw ast
const ast = assertParse(code)
return { ast }
}

View File

@ -11,8 +11,8 @@ Map {
],
],
"range": [
37,
64,
12,
31,
0,
],
},

View File

@ -1,4 +1,4 @@
import { makeDefaultPlanes, parse, initPromise, Program } from 'lang/wasm'
import { makeDefaultPlanes, assertParse, initPromise, Program } from 'lang/wasm'
import { Models } from '@kittycad/lib'
import {
OrderedCommand,
@ -148,11 +148,7 @@ beforeAll(async () => {
][]
const cacheToWriteToFileTemp: Partial<CacheShape> = {}
for (const [codeKey, code] of cacheEntries) {
const ast = parse(code)
if (err(ast)) {
console.error(ast)
return Promise.reject(ast)
}
const ast = assertParse(code)
await kclManager.executeAst({ ast })
cacheToWriteToFileTemp[codeKey] = {
@ -403,11 +399,7 @@ describe('capture graph of sketchOnFaceOnFace...', () => {
})
function getCommands(codeKey: CodeKey): CacheShape[CodeKey] & { ast: Program } {
const ast = parse(codeKey)
if (err(ast)) {
console.error(ast)
throw ast
}
const ast = assertParse(codeKey)
const file = fs.readFileSync(fullPath, 'utf-8')
const parsed: CacheShape = JSON.parse(file)
// these either already exist from the last run, or were created in

Binary file not shown.

Before

Width:  |  Height:  |  Size: 378 KiB

After

Width:  |  Height:  |  Size: 357 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 92 KiB

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 613 KiB

After

Width:  |  Height:  |  Size: 577 KiB

View File

@ -1,4 +1,4 @@
import { SourceRange } from 'lang/wasm'
import { defaultSourceRange, SourceRange } from 'lang/wasm'
import { VITE_KC_API_WS_MODELING_URL, VITE_KC_DEV_TOKEN } from 'env'
import { Models } from '@kittycad/lib'
import { exportSave } from 'lib/exportSave'
@ -1879,17 +1879,6 @@ export class EngineCommandManager extends EventTarget {
}
return JSON.stringify(this.defaultPlanes)
}
endSession() {
const deleteCmd: EngineCommand = {
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'scene_clear_all',
},
}
this.clearDefaultPlanes()
this.engineConnection?.send(deleteCmd)
}
addCommandLog(message: CommandLog) {
if (this.commandLogs.length > 500) {
this.commandLogs.shift()
@ -2014,7 +2003,7 @@ export class EngineCommandManager extends EventTarget {
{
command,
idToRangeMap: {},
range: [0, 0],
range: defaultSourceRange(),
},
true // isSceneCommand
)

View File

@ -8,7 +8,7 @@ import {
getConstraintInfo,
} from './sketch'
import {
parse,
assertParse,
recast,
initPromise,
SourceRange,
@ -115,8 +115,7 @@ describe('testing changeSketchArguments', () => {
`
const code = genCode(lineToChange)
const expectedCode = genCode(lineAfterChange)
const ast = parse(code)
if (err(ast)) return ast
const ast = assertParse(code)
const execState = await enginelessExecutor(ast)
const sourceStart = code.indexOf(lineToChange)
@ -125,7 +124,7 @@ describe('testing changeSketchArguments', () => {
execState.memory,
{
type: 'sourceRange',
sourceRange: [sourceStart, sourceStart + lineToChange.length],
sourceRange: [sourceStart, sourceStart + lineToChange.length, true],
},
{
type: 'straight-segment',
@ -148,8 +147,7 @@ mySketch001 = startSketchOn('XY')
// |> rx(45, %)
|> lineTo([-1.59, -1.54], %)
|> lineTo([0.46, -5.82], %)`
const ast = parse(code)
if (err(ast)) return ast
const ast = assertParse(code)
const execState = await enginelessExecutor(ast)
const sourceStart = code.indexOf(lineToChange)
@ -166,8 +164,7 @@ mySketch001 = startSketchOn('XY')
pathToNode: [
['body', ''],
[0, 'index'],
['declarations', 'VariableDeclaration'],
[0, 'index'],
['declaration', 'VariableDeclaration'],
['init', 'VariableDeclarator'],
],
})
@ -191,8 +188,7 @@ mySketch001 = startSketchOn('XY')
pathToNode: [
['body', ''],
[0, 'index'],
['declarations', 'VariableDeclaration'],
[0, 'index'],
['declaration', 'VariableDeclaration'],
['init', 'VariableDeclarator'],
],
})
@ -220,12 +216,13 @@ describe('testing addTagForSketchOnFace', () => {
|> lineTo([0.46, -5.82], %)
`
const code = genCode(originalLine)
const ast = parse(code)
const ast = assertParse(code)
await enginelessExecutor(ast)
const sourceStart = code.indexOf(originalLine)
const sourceRange: [number, number] = [
const sourceRange: [number, number, boolean] = [
sourceStart,
sourceStart + originalLine.length,
true,
]
if (err(ast)) return ast
const pathToNode = getNodePathFromSourceRange(ast, sourceRange)
@ -291,13 +288,14 @@ extrude001 = extrude(100, sketch001)
${insertCode}
`
const code = genCode(originalChamfer)
const ast = parse(code)
const ast = assertParse(code)
await enginelessExecutor(ast)
const sourceStart = code.indexOf(originalChamfer)
const extraChars = originalChamfer.indexOf('chamfer')
const sourceRange: [number, number] = [
const sourceRange: [number, number, boolean] = [
sourceStart + extraChars,
sourceStart + originalChamfer.length - extraChars,
true,
]
if (err(ast)) throw ast
@ -335,7 +333,7 @@ describe('testing getConstraintInfo', () => {
|> lineTo([6.14, 3.14], %)
|> xLineTo(8, %)
|> yLineTo(5, %)
|> yLine(3.14, %, 'a')
|> yLine(3.14, %, $a)
|> xLine(3.14, %)
|> angledLineOfXLength({
angle = 3.14,
@ -355,11 +353,11 @@ describe('testing getConstraintInfo', () => {
}, %)
|> angledLineThatIntersects({
angle = 3.14,
intersectTag = 'a',
intersectTag = a,
offset = 0
}, %)
|> tangentialArcTo([3.14, 13.14], %)`
const ast = parse(code)
const ast = assertParse(code)
test.each([
[
'line',
@ -368,7 +366,7 @@ describe('testing getConstraintInfo', () => {
type: 'xRelative',
isConstrained: false,
value: '3',
sourceRange: [78, 79],
sourceRange: [78, 79, true],
argPosition: { type: 'arrayItem', index: 0 },
pathToNode: expect.any(Array),
stdLibFnName: 'line',
@ -377,7 +375,7 @@ describe('testing getConstraintInfo', () => {
type: 'yRelative',
isConstrained: false,
value: '4',
sourceRange: [81, 82],
sourceRange: [81, 82, true],
argPosition: { type: 'arrayItem', index: 1 },
pathToNode: expect.any(Array),
stdLibFnName: 'line',
@ -391,7 +389,7 @@ describe('testing getConstraintInfo', () => {
type: 'angle',
isConstrained: false,
value: '3.14',
sourceRange: [117, 121],
sourceRange: [118, 122, true],
argPosition: { type: 'objectProperty', key: 'angle' },
pathToNode: expect.any(Array),
stdLibFnName: 'angledLine',
@ -400,7 +398,7 @@ describe('testing getConstraintInfo', () => {
type: 'length',
isConstrained: false,
value: '3.14',
sourceRange: [135, 139],
sourceRange: [137, 141, true],
argPosition: { type: 'objectProperty', key: 'length' },
pathToNode: expect.any(Array),
stdLibFnName: 'angledLine',
@ -414,7 +412,7 @@ describe('testing getConstraintInfo', () => {
type: 'xAbsolute',
isConstrained: false,
value: '6.14',
sourceRange: [162, 166],
sourceRange: [164, 168, true],
argPosition: { type: 'arrayItem', index: 0 },
pathToNode: expect.any(Array),
stdLibFnName: 'lineTo',
@ -423,7 +421,7 @@ describe('testing getConstraintInfo', () => {
type: 'yAbsolute',
isConstrained: false,
value: '3.14',
sourceRange: [168, 172],
sourceRange: [170, 174, true],
argPosition: { type: 'arrayItem', index: 1 },
pathToNode: expect.any(Array),
stdLibFnName: 'lineTo',
@ -437,7 +435,7 @@ describe('testing getConstraintInfo', () => {
type: 'horizontal',
isConstrained: true,
value: 'xLineTo',
sourceRange: [183, 190],
sourceRange: [185, 192, true],
argPosition: undefined,
pathToNode: expect.any(Array),
stdLibFnName: 'xLineTo',
@ -446,7 +444,7 @@ describe('testing getConstraintInfo', () => {
type: 'xAbsolute',
isConstrained: false,
value: '8',
sourceRange: [191, 192],
sourceRange: [193, 194, true],
argPosition: { type: 'singleValue' },
pathToNode: expect.any(Array),
stdLibFnName: 'xLineTo',
@ -460,7 +458,7 @@ describe('testing getConstraintInfo', () => {
type: 'vertical',
isConstrained: true,
value: 'yLineTo',
sourceRange: [202, 209],
sourceRange: [204, 211, true],
argPosition: undefined,
pathToNode: expect.any(Array),
stdLibFnName: 'yLineTo',
@ -469,7 +467,7 @@ describe('testing getConstraintInfo', () => {
type: 'yAbsolute',
isConstrained: false,
value: '5',
sourceRange: [210, 211],
sourceRange: [212, 213, true],
argPosition: { type: 'singleValue' },
pathToNode: expect.any(Array),
stdLibFnName: 'yLineTo',
@ -483,7 +481,7 @@ describe('testing getConstraintInfo', () => {
type: 'vertical',
isConstrained: true,
value: 'yLine',
sourceRange: [221, 226],
sourceRange: [223, 228, true],
argPosition: undefined,
pathToNode: expect.any(Array),
stdLibFnName: 'yLine',
@ -492,7 +490,7 @@ describe('testing getConstraintInfo', () => {
type: 'yRelative',
isConstrained: false,
value: '3.14',
sourceRange: [227, 231],
sourceRange: [229, 233, true],
argPosition: { type: 'singleValue' },
pathToNode: expect.any(Array),
stdLibFnName: 'yLine',
@ -506,7 +504,7 @@ describe('testing getConstraintInfo', () => {
type: 'horizontal',
isConstrained: true,
value: 'xLine',
sourceRange: [246, 251],
sourceRange: [247, 252, true],
argPosition: undefined,
pathToNode: expect.any(Array),
stdLibFnName: 'xLine',
@ -515,7 +513,7 @@ describe('testing getConstraintInfo', () => {
type: 'xRelative',
isConstrained: false,
value: '3.14',
sourceRange: [252, 256],
sourceRange: [253, 257, true],
argPosition: { type: 'singleValue' },
pathToNode: expect.any(Array),
stdLibFnName: 'xLine',
@ -529,7 +527,7 @@ describe('testing getConstraintInfo', () => {
type: 'angle',
isConstrained: false,
value: '3.14',
sourceRange: [299, 303],
sourceRange: [301, 305, true],
argPosition: { type: 'objectProperty', key: 'angle' },
pathToNode: expect.any(Array),
stdLibFnName: 'angledLineOfXLength',
@ -538,7 +536,7 @@ describe('testing getConstraintInfo', () => {
type: 'xRelative',
isConstrained: false,
value: '3.14',
sourceRange: [317, 321],
sourceRange: [320, 324, true],
argPosition: { type: 'objectProperty', key: 'length' },
pathToNode: expect.any(Array),
stdLibFnName: 'angledLineOfXLength',
@ -552,7 +550,7 @@ describe('testing getConstraintInfo', () => {
type: 'angle',
isConstrained: false,
value: '30',
sourceRange: [369, 371],
sourceRange: [373, 375, true],
argPosition: { type: 'objectProperty', key: 'angle' },
pathToNode: expect.any(Array),
stdLibFnName: 'angledLineOfYLength',
@ -561,7 +559,7 @@ describe('testing getConstraintInfo', () => {
type: 'yRelative',
isConstrained: false,
value: '3',
sourceRange: [385, 386],
sourceRange: [390, 391, true],
argPosition: { type: 'objectProperty', key: 'length' },
pathToNode: expect.any(Array),
stdLibFnName: 'angledLineOfYLength',
@ -575,7 +573,7 @@ describe('testing getConstraintInfo', () => {
type: 'angle',
isConstrained: false,
value: '12.14',
sourceRange: [428, 433],
sourceRange: [434, 439, true],
argPosition: { type: 'objectProperty', key: 'angle' },
pathToNode: expect.any(Array),
stdLibFnName: 'angledLineToX',
@ -584,7 +582,7 @@ describe('testing getConstraintInfo', () => {
type: 'xAbsolute',
isConstrained: false,
value: '12',
sourceRange: [443, 445],
sourceRange: [450, 452, true],
argPosition: { type: 'objectProperty', key: 'to' },
pathToNode: expect.any(Array),
stdLibFnName: 'angledLineToX',
@ -598,7 +596,7 @@ describe('testing getConstraintInfo', () => {
type: 'angle',
isConstrained: false,
value: '30',
sourceRange: [487, 489],
sourceRange: [495, 497, true],
argPosition: { type: 'objectProperty', key: 'angle' },
pathToNode: expect.any(Array),
stdLibFnName: 'angledLineToY',
@ -607,7 +605,7 @@ describe('testing getConstraintInfo', () => {
type: 'yAbsolute',
isConstrained: false,
value: '10.14',
sourceRange: [499, 504],
sourceRange: [508, 513, true],
argPosition: { type: 'objectProperty', key: 'to' },
pathToNode: expect.any(Array),
stdLibFnName: 'angledLineToY',
@ -621,7 +619,7 @@ describe('testing getConstraintInfo', () => {
type: 'angle',
isConstrained: false,
value: '3.14',
sourceRange: [557, 561],
sourceRange: [567, 571, true],
argPosition: { type: 'objectProperty', key: 'angle' },
pathToNode: expect.any(Array),
stdLibFnName: 'angledLineThatIntersects',
@ -630,7 +628,7 @@ describe('testing getConstraintInfo', () => {
type: 'intersectionOffset',
isConstrained: false,
value: '0',
sourceRange: [598, 599],
sourceRange: [608, 609, true],
argPosition: { type: 'objectProperty', key: 'offset' },
pathToNode: expect.any(Array),
stdLibFnName: 'angledLineThatIntersects',
@ -638,8 +636,8 @@ describe('testing getConstraintInfo', () => {
{
type: 'intersectionTag',
isConstrained: false,
value: "'a'",
sourceRange: [581, 584],
value: 'a',
sourceRange: [592, 593, true],
argPosition: {
key: 'intersectTag',
type: 'objectProperty',
@ -656,7 +654,7 @@ describe('testing getConstraintInfo', () => {
type: 'tangentialWithPrevious',
isConstrained: true,
value: 'tangentialArcTo',
sourceRange: [613, 628],
sourceRange: [623, 638, true],
argPosition: undefined,
pathToNode: expect.any(Array),
stdLibFnName: 'tangentialArcTo',
@ -665,7 +663,7 @@ describe('testing getConstraintInfo', () => {
type: 'xAbsolute',
isConstrained: false,
value: '3.14',
sourceRange: [630, 634],
sourceRange: [640, 644, true],
argPosition: { type: 'arrayItem', index: 0 },
pathToNode: expect.any(Array),
stdLibFnName: 'tangentialArcTo',
@ -674,7 +672,7 @@ describe('testing getConstraintInfo', () => {
type: 'yAbsolute',
isConstrained: false,
value: '13.14',
sourceRange: [636, 641],
sourceRange: [646, 651, true],
argPosition: { type: 'arrayItem', index: 1 },
pathToNode: expect.any(Array),
stdLibFnName: 'tangentialArcTo',
@ -685,6 +683,7 @@ describe('testing getConstraintInfo', () => {
const sourceRange: SourceRange = [
code.indexOf(functionName),
code.indexOf(functionName) + functionName.length,
true,
]
if (err(ast)) return ast
const pathToNode = getNodePathFromSourceRange(ast, sourceRange)
@ -706,7 +705,7 @@ describe('testing getConstraintInfo', () => {
|> lineTo([6.14, 3.14], %)
|> xLineTo(8, %)
|> yLineTo(5, %)
|> yLine(3.14, %, 'a')
|> yLine(3.14, %, $a)
|> xLine(3.14, %)
|> angledLineOfXLength([3.14, 3.14], %)
|> angledLineOfYLength([30, 3], %)
@ -714,11 +713,11 @@ describe('testing getConstraintInfo', () => {
|> angledLineToY([30, 10], %)
|> angledLineThatIntersects({
angle = 3.14,
intersectTag = 'a',
intersectTag = a,
offset = 0
}, %)
|> tangentialArcTo([3.14, 13.14], %)`
const ast = parse(code)
const ast = assertParse(code)
test.each([
[
`angledLine(`,
@ -727,7 +726,7 @@ describe('testing getConstraintInfo', () => {
type: 'angle',
isConstrained: false,
value: '3.14',
sourceRange: [112, 116],
sourceRange: [112, 116, true],
argPosition: { type: 'arrayItem', index: 0 },
pathToNode: expect.any(Array),
stdLibFnName: 'angledLine',
@ -736,7 +735,7 @@ describe('testing getConstraintInfo', () => {
type: 'length',
isConstrained: false,
value: '3.14',
sourceRange: [118, 122],
sourceRange: [118, 122, true],
argPosition: { type: 'arrayItem', index: 1 },
pathToNode: expect.any(Array),
stdLibFnName: 'angledLine',
@ -750,7 +749,7 @@ describe('testing getConstraintInfo', () => {
type: 'angle',
isConstrained: false,
value: '3.14',
sourceRange: [278, 282],
sourceRange: [277, 281, true],
argPosition: { type: 'arrayItem', index: 0 },
pathToNode: expect.any(Array),
stdLibFnName: 'angledLineOfXLength',
@ -759,7 +758,7 @@ describe('testing getConstraintInfo', () => {
type: 'xRelative',
isConstrained: false,
value: '3.14',
sourceRange: [284, 288],
sourceRange: [283, 287, true],
argPosition: { type: 'arrayItem', index: 1 },
pathToNode: expect.any(Array),
stdLibFnName: 'angledLineOfXLength',
@ -773,7 +772,7 @@ describe('testing getConstraintInfo', () => {
type: 'angle',
isConstrained: false,
value: '30',
sourceRange: [322, 324],
sourceRange: [321, 323, true],
argPosition: { type: 'arrayItem', index: 0 },
pathToNode: expect.any(Array),
stdLibFnName: 'angledLineOfYLength',
@ -782,7 +781,7 @@ describe('testing getConstraintInfo', () => {
type: 'yRelative',
isConstrained: false,
value: '3',
sourceRange: [326, 327],
sourceRange: [325, 326, true],
argPosition: { type: 'arrayItem', index: 1 },
pathToNode: expect.any(Array),
stdLibFnName: 'angledLineOfYLength',
@ -796,7 +795,7 @@ describe('testing getConstraintInfo', () => {
type: 'angle',
isConstrained: false,
value: '12',
sourceRange: [355, 357],
sourceRange: [354, 356, true],
argPosition: { type: 'arrayItem', index: 0 },
pathToNode: expect.any(Array),
stdLibFnName: 'angledLineToX',
@ -805,7 +804,7 @@ describe('testing getConstraintInfo', () => {
type: 'xAbsolute',
isConstrained: false,
value: '12',
sourceRange: [359, 361],
sourceRange: [358, 360, true],
argPosition: { type: 'arrayItem', index: 1 },
pathToNode: expect.any(Array),
stdLibFnName: 'angledLineToX',
@ -819,7 +818,7 @@ describe('testing getConstraintInfo', () => {
type: 'angle',
isConstrained: false,
value: '30',
sourceRange: [389, 391],
sourceRange: [388, 390, true],
argPosition: { type: 'arrayItem', index: 0 },
pathToNode: expect.any(Array),
stdLibFnName: 'angledLineToY',
@ -828,7 +827,7 @@ describe('testing getConstraintInfo', () => {
type: 'yAbsolute',
isConstrained: false,
value: '10',
sourceRange: [393, 395],
sourceRange: [392, 394, true],
argPosition: { type: 'arrayItem', index: 1 },
pathToNode: expect.any(Array),
stdLibFnName: 'angledLineToY',
@ -839,6 +838,7 @@ describe('testing getConstraintInfo', () => {
const sourceRange: SourceRange = [
code.indexOf(functionName),
code.indexOf(functionName) + functionName.length,
true,
]
if (err(ast)) return ast
const pathToNode = getNodePathFromSourceRange(ast, sourceRange)
@ -860,7 +860,7 @@ describe('testing getConstraintInfo', () => {
|> lineTo([6.14 + 0, 3.14 + 0], %)
|> xLineTo(8 + 0, %)
|> yLineTo(5 + 0, %)
|> yLine(3.14 + 0, %, 'a')
|> yLine(3.14 + 0, %, $a)
|> xLine(3.14 + 0, %)
|> angledLineOfXLength({ angle = 3.14 + 0, length = 3.14 + 0 }, %)
|> angledLineOfYLength({ angle = 30 + 0, length = 3 + 0 }, %)
@ -868,11 +868,11 @@ describe('testing getConstraintInfo', () => {
|> angledLineToY({ angle = 30 + 0, to = 10.14 + 0 }, %)
|> angledLineThatIntersects({
angle = 3.14 + 0,
intersectTag = 'a',
intersectTag = a,
offset = 0 + 0
}, %)
|> tangentialArcTo([3.14 + 0, 13.14 + 0], %)`
const ast = parse(code)
const ast = assertParse(code)
test.each([
[
'line',
@ -881,7 +881,7 @@ describe('testing getConstraintInfo', () => {
type: 'xRelative',
isConstrained: true,
value: '3 + 0',
sourceRange: [83, 88],
sourceRange: [83, 88, true],
argPosition: { type: 'arrayItem', index: 0 },
pathToNode: expect.any(Array),
stdLibFnName: 'line',
@ -890,7 +890,7 @@ describe('testing getConstraintInfo', () => {
type: 'yRelative',
isConstrained: true,
value: '4 + 0',
sourceRange: [90, 95],
sourceRange: [90, 95, true],
argPosition: { type: 'arrayItem', index: 1 },
pathToNode: expect.any(Array),
stdLibFnName: 'line',
@ -904,7 +904,7 @@ describe('testing getConstraintInfo', () => {
type: 'angle',
isConstrained: true,
value: '3.14 + 0',
sourceRange: [128, 136],
sourceRange: [129, 137, true],
argPosition: { type: 'objectProperty', key: 'angle' },
pathToNode: expect.any(Array),
stdLibFnName: 'angledLine',
@ -913,7 +913,7 @@ describe('testing getConstraintInfo', () => {
type: 'length',
isConstrained: true,
value: '3.14 + 0',
sourceRange: [146, 154],
sourceRange: [148, 156, true],
argPosition: { type: 'objectProperty', key: 'length' },
pathToNode: expect.any(Array),
stdLibFnName: 'angledLine',
@ -927,7 +927,7 @@ describe('testing getConstraintInfo', () => {
type: 'xAbsolute',
isConstrained: true,
value: '6.14 + 0',
sourceRange: [176, 184],
sourceRange: [178, 186, true],
argPosition: { type: 'arrayItem', index: 0 },
pathToNode: expect.any(Array),
stdLibFnName: 'lineTo',
@ -936,7 +936,7 @@ describe('testing getConstraintInfo', () => {
type: 'yAbsolute',
isConstrained: true,
value: '3.14 + 0',
sourceRange: [186, 194],
sourceRange: [188, 196, true],
argPosition: { type: 'arrayItem', index: 1 },
pathToNode: expect.any(Array),
stdLibFnName: 'lineTo',
@ -950,7 +950,7 @@ describe('testing getConstraintInfo', () => {
type: 'horizontal',
isConstrained: true,
value: 'xLineTo',
sourceRange: [207, 214],
sourceRange: [209, 216, true],
argPosition: undefined,
pathToNode: expect.any(Array),
stdLibFnName: 'xLineTo',
@ -959,7 +959,7 @@ describe('testing getConstraintInfo', () => {
type: 'xAbsolute',
isConstrained: true,
value: '8 + 0',
sourceRange: [215, 220],
sourceRange: [217, 222, true],
argPosition: { type: 'singleValue' },
pathToNode: expect.any(Array),
stdLibFnName: 'xLineTo',
@ -973,7 +973,7 @@ describe('testing getConstraintInfo', () => {
type: 'vertical',
isConstrained: true,
value: 'yLineTo',
sourceRange: [232, 239],
sourceRange: [234, 241, true],
argPosition: undefined,
pathToNode: expect.any(Array),
stdLibFnName: 'yLineTo',
@ -982,7 +982,7 @@ describe('testing getConstraintInfo', () => {
type: 'yAbsolute',
isConstrained: true,
value: '5 + 0',
sourceRange: [240, 245],
sourceRange: [242, 247, true],
argPosition: { type: 'singleValue' },
pathToNode: expect.any(Array),
stdLibFnName: 'yLineTo',
@ -996,7 +996,7 @@ describe('testing getConstraintInfo', () => {
type: 'vertical',
isConstrained: true,
value: 'yLine',
sourceRange: [257, 262],
sourceRange: [259, 264, true],
argPosition: undefined,
pathToNode: expect.any(Array),
stdLibFnName: 'yLine',
@ -1005,7 +1005,7 @@ describe('testing getConstraintInfo', () => {
type: 'yRelative',
isConstrained: true,
value: '3.14 + 0',
sourceRange: [263, 271],
sourceRange: [265, 273, true],
argPosition: { type: 'singleValue' },
pathToNode: expect.any(Array),
stdLibFnName: 'yLine',
@ -1019,7 +1019,7 @@ describe('testing getConstraintInfo', () => {
type: 'horizontal',
isConstrained: true,
value: 'xLine',
sourceRange: [288, 293],
sourceRange: [289, 294, true],
argPosition: undefined,
pathToNode: expect.any(Array),
stdLibFnName: 'xLine',
@ -1028,7 +1028,7 @@ describe('testing getConstraintInfo', () => {
type: 'xRelative',
isConstrained: true,
value: '3.14 + 0',
sourceRange: [294, 302],
sourceRange: [295, 303, true],
argPosition: { type: 'singleValue' },
pathToNode: expect.any(Array),
stdLibFnName: 'xLine',
@ -1042,7 +1042,7 @@ describe('testing getConstraintInfo', () => {
type: 'angle',
isConstrained: true,
value: '3.14 + 0',
sourceRange: [343, 351],
sourceRange: [345, 353, true],
argPosition: { type: 'objectProperty', key: 'angle' },
pathToNode: expect.any(Array),
stdLibFnName: 'angledLineOfXLength',
@ -1051,7 +1051,7 @@ describe('testing getConstraintInfo', () => {
type: 'xRelative',
isConstrained: true,
value: '3.14 + 0',
sourceRange: [361, 369],
sourceRange: [364, 372, true],
argPosition: { type: 'objectProperty', key: 'length' },
pathToNode: expect.any(Array),
stdLibFnName: 'angledLineOfXLength',
@ -1065,7 +1065,7 @@ describe('testing getConstraintInfo', () => {
type: 'angle',
isConstrained: true,
value: '30 + 0',
sourceRange: [412, 418],
sourceRange: [416, 422, true],
argPosition: { type: 'objectProperty', key: 'angle' },
pathToNode: expect.any(Array),
stdLibFnName: 'angledLineOfYLength',
@ -1074,7 +1074,7 @@ describe('testing getConstraintInfo', () => {
type: 'yRelative',
isConstrained: true,
value: '3 + 0',
sourceRange: [428, 433],
sourceRange: [433, 438, true],
argPosition: { type: 'objectProperty', key: 'length' },
pathToNode: expect.any(Array),
stdLibFnName: 'angledLineOfYLength',
@ -1088,7 +1088,7 @@ describe('testing getConstraintInfo', () => {
type: 'angle',
isConstrained: true,
value: '12.14 + 0',
sourceRange: [470, 479],
sourceRange: [476, 485, true],
argPosition: { type: 'objectProperty', key: 'angle' },
pathToNode: expect.any(Array),
stdLibFnName: 'angledLineToX',
@ -1097,7 +1097,7 @@ describe('testing getConstraintInfo', () => {
type: 'xAbsolute',
isConstrained: true,
value: '12 + 0',
sourceRange: [485, 491],
sourceRange: [492, 498, true],
argPosition: { type: 'objectProperty', key: 'to' },
pathToNode: expect.any(Array),
stdLibFnName: 'angledLineToX',
@ -1111,7 +1111,7 @@ describe('testing getConstraintInfo', () => {
type: 'angle',
isConstrained: true,
value: '30 + 0',
sourceRange: [528, 534],
sourceRange: [536, 542, true],
argPosition: { type: 'objectProperty', key: 'angle' },
pathToNode: expect.any(Array),
stdLibFnName: 'angledLineToY',
@ -1120,7 +1120,7 @@ describe('testing getConstraintInfo', () => {
type: 'yAbsolute',
isConstrained: true,
value: '10.14 + 0',
sourceRange: [540, 549],
sourceRange: [549, 558, true],
argPosition: { type: 'objectProperty', key: 'to' },
pathToNode: expect.any(Array),
stdLibFnName: 'angledLineToY',
@ -1134,7 +1134,7 @@ describe('testing getConstraintInfo', () => {
type: 'angle',
isConstrained: true,
value: '3.14 + 0',
sourceRange: [606, 614],
sourceRange: [616, 624, true],
argPosition: { type: 'objectProperty', key: 'angle' },
pathToNode: expect.any(Array),
stdLibFnName: 'angledLineThatIntersects',
@ -1143,7 +1143,7 @@ describe('testing getConstraintInfo', () => {
type: 'intersectionOffset',
isConstrained: true,
value: '0 + 0',
sourceRange: [661, 666],
sourceRange: [671, 676, true],
argPosition: { type: 'objectProperty', key: 'offset' },
pathToNode: expect.any(Array),
stdLibFnName: 'angledLineThatIntersects',
@ -1151,8 +1151,8 @@ describe('testing getConstraintInfo', () => {
{
type: 'intersectionTag',
isConstrained: false,
value: "'a'",
sourceRange: [639, 642],
value: 'a',
sourceRange: [650, 651, true],
argPosition: { key: 'intersectTag', type: 'objectProperty' },
pathToNode: expect.any(Array),
stdLibFnName: 'angledLineThatIntersects',
@ -1166,7 +1166,7 @@ describe('testing getConstraintInfo', () => {
type: 'tangentialWithPrevious',
isConstrained: true,
value: 'tangentialArcTo',
sourceRange: [687, 702],
sourceRange: [697, 712, true],
argPosition: undefined,
pathToNode: expect.any(Array),
stdLibFnName: 'tangentialArcTo',
@ -1175,7 +1175,7 @@ describe('testing getConstraintInfo', () => {
type: 'xAbsolute',
isConstrained: true,
value: '3.14 + 0',
sourceRange: [704, 712],
sourceRange: [714, 722, true],
argPosition: { type: 'arrayItem', index: 0 },
pathToNode: expect.any(Array),
stdLibFnName: 'tangentialArcTo',
@ -1184,7 +1184,7 @@ describe('testing getConstraintInfo', () => {
type: 'yAbsolute',
isConstrained: true,
value: '13.14 + 0',
sourceRange: [714, 723],
sourceRange: [724, 733, true],
argPosition: { type: 'arrayItem', index: 1 },
pathToNode: expect.any(Array),
stdLibFnName: 'tangentialArcTo',
@ -1195,6 +1195,7 @@ describe('testing getConstraintInfo', () => {
const sourceRange: SourceRange = [
code.indexOf(functionName),
code.indexOf(functionName) + functionName.length,
true,
]
if (err(ast)) return ast
const pathToNode = getNodePathFromSourceRange(ast, sourceRange)

View File

@ -222,7 +222,7 @@ const commonConstraintInfoHelper = (
code.slice(input1.start, input1.end),
stdLibFnName,
isArr ? abbreviatedInputs[0].arrayInput : abbreviatedInputs[0].objInput,
[input1.start, input1.end],
[input1.start, input1.end, true],
pathToFirstArg
)
)
@ -234,7 +234,7 @@ const commonConstraintInfoHelper = (
code.slice(input2.start, input2.end),
stdLibFnName,
isArr ? abbreviatedInputs[1].arrayInput : abbreviatedInputs[1].objInput,
[input2.start, input2.end],
[input2.start, input2.end, true],
pathToSecondArg
)
)
@ -266,7 +266,7 @@ const horzVertConstraintInfoHelper = (
callee.name,
stdLibFnName,
undefined,
[callee.start, callee.end],
[callee.start, callee.end, true],
pathToCallee
),
constrainInfo(
@ -275,7 +275,7 @@ const horzVertConstraintInfoHelper = (
code.slice(firstArg.start, firstArg.end),
stdLibFnName,
abbreviatedInput,
[firstArg.start, firstArg.end],
[firstArg.start, firstArg.end, true],
pathToFirstArg
),
]
@ -905,7 +905,7 @@ export const tangentialArcTo: SketchLineHelper = {
callee.name,
'tangentialArcTo',
undefined,
[callee.start, callee.end],
[callee.start, callee.end, true],
pathToCallee
),
constrainInfo(
@ -914,7 +914,7 @@ export const tangentialArcTo: SketchLineHelper = {
code.slice(firstArg.elements[0].start, firstArg.elements[0].end),
'tangentialArcTo',
0,
[firstArg.elements[0].start, firstArg.elements[0].end],
[firstArg.elements[0].start, firstArg.elements[0].end, true],
pathToFirstArg
),
constrainInfo(
@ -923,7 +923,7 @@ export const tangentialArcTo: SketchLineHelper = {
code.slice(firstArg.elements[1].start, firstArg.elements[1].end),
'tangentialArcTo',
1,
[firstArg.elements[1].start, firstArg.elements[1].end],
[firstArg.elements[1].start, firstArg.elements[1].end, true],
pathToSecondArg
),
]
@ -1052,7 +1052,7 @@ export const circle: SketchLineHelper = {
code.slice(radiusDetails.expr.start, radiusDetails.expr.end),
'circle',
'radius',
[radiusDetails.expr.start, radiusDetails.expr.end],
[radiusDetails.expr.start, radiusDetails.expr.end, true],
pathToRadiusLiteral
),
{
@ -1064,6 +1064,7 @@ export const circle: SketchLineHelper = {
sourceRange: [
centerDetails.expr.elements[0].start,
centerDetails.expr.elements[0].end,
true,
],
pathToNode: pathToXArg,
value: code.slice(
@ -1085,6 +1086,7 @@ export const circle: SketchLineHelper = {
sourceRange: [
centerDetails.expr.elements[1].start,
centerDetails.expr.elements[1].end,
true,
],
pathToNode: pathToYArg,
value: code.slice(
@ -1699,7 +1701,7 @@ export const angledLineThatIntersects: SketchLineHelper = {
if (err(nodeMeta2)) return nodeMeta2
const { node: varDec } = nodeMeta2
const varName = varDec.declarations[0].id.name
const varName = varDec.declaration.id.name
const sketch = sketchFromKclValue(
previousProgramMemory.get(varName),
varName
@ -1761,7 +1763,7 @@ export const angledLineThatIntersects: SketchLineHelper = {
code.slice(angle.start, angle.end),
'angledLineThatIntersects',
'angle',
[angle.start, angle.end],
[angle.start, angle.end, true],
pathToAngleProp
)
)
@ -1780,7 +1782,7 @@ export const angledLineThatIntersects: SketchLineHelper = {
code.slice(offset.start, offset.end),
'angledLineThatIntersects',
'offset',
[offset.start, offset.end],
[offset.start, offset.end, true],
pathToOffsetProp
)
)
@ -1799,7 +1801,7 @@ export const angledLineThatIntersects: SketchLineHelper = {
code.slice(tag.start, tag.end),
'angledLineThatIntersects',
'intersectTag',
[tag.start, tag.end],
[tag.start, tag.end, true],
pathToTagProp
)
returnVal.push(info)

View File

@ -1,5 +1,5 @@
import {
parse,
assertParse,
Sketch,
recast,
initPromise,
@ -31,12 +31,11 @@ async function testingSwapSketchFnCall({
constraintType: ConstraintType
}): Promise<{
newCode: string
originalRange: [number, number]
originalRange: [number, number, boolean]
}> {
const startIndex = inputCode.indexOf(callToSwap)
const range: SourceRange = [startIndex, startIndex + callToSwap.length]
const ast = parse(inputCode)
if (err(ast)) return Promise.reject(ast)
const range: SourceRange = [startIndex, startIndex + callToSwap.length, true]
const ast = assertParse(inputCode)
const execState = await enginelessExecutor(ast)
const selections = {
@ -370,13 +369,13 @@ part001 = startSketchOn('XY')
|> line([2.14, 1.35], %) // normal-segment
|> xLine(3.54, %)`
it('normal case works', async () => {
const execState = await enginelessExecutor(parse(code))
const execState = await enginelessExecutor(assertParse(code))
const index = code.indexOf('// normal-segment') - 7
const sg = sketchFromKclValue(
execState.memory.get('part001'),
'part001'
) as Sketch
const _segment = getSketchSegmentFromSourceRange(sg, [index, index])
const _segment = getSketchSegmentFromSourceRange(sg, [index, index, true])
if (err(_segment)) throw _segment
const { __geoMeta, ...segment } = _segment.segment
expect(segment).toEqual({
@ -387,11 +386,11 @@ part001 = startSketchOn('XY')
})
})
it('verify it works when the segment is in the `start` property', async () => {
const execState = await enginelessExecutor(parse(code))
const execState = await enginelessExecutor(assertParse(code))
const index = code.indexOf('// segment-in-start') - 7
const _segment = getSketchSegmentFromSourceRange(
sketchFromKclValue(execState.memory.get('part001'), 'part001') as Sketch,
[index, index]
[index, index, true]
)
if (err(_segment)) throw _segment
const { __geoMeta, ...segment } = _segment.segment

View File

@ -31,7 +31,7 @@ export function getSketchSegmentFromPathToNode(
const node = nodeMeta.node
if (!node || typeof node.start !== 'number' || !node.end)
return new Error('no node found')
const sourceRange: SourceRange = [node.start, node.end]
const sourceRange: SourceRange = [node.start, node.end, true]
return getSketchSegmentFromSourceRange(sketch, sourceRange)
}
export function getSketchSegmentFromSourceRange(
@ -111,12 +111,10 @@ export function isSketchVariablesLinked(
let nextVarDec: VariableDeclarator | undefined
for (const node of ast.body) {
if (node.type !== 'VariableDeclaration') continue
const found = node.declarations.find(
({ id }) => id?.name === secondArg.name
)
if (!found) continue
nextVarDec = found
break
if (node.declaration.id.name === secondArg.name) {
nextVarDec = node.declaration
break
}
}
if (!nextVarDec) return false
return isSketchVariablesLinked(nextVarDec, primaryVarDec, ast)

View File

@ -1,4 +1,4 @@
import { parse, Expr, recast, initPromise, Program } from '../wasm'
import { assertParse, Expr, recast, initPromise, Program } from '../wasm'
import {
getConstraintType,
getTransformInfos,
@ -66,8 +66,7 @@ describe('testing getConstraintType', () => {
function getConstraintTypeFromSourceHelper(
code: string
): ReturnType<typeof getConstraintType> | Error {
const ast = parse(code)
if (err(ast)) return ast
const ast = assertParse(code)
const args = (ast.body[0] as any).expression.arguments[0].elements as [
Expr,
@ -79,8 +78,7 @@ function getConstraintTypeFromSourceHelper(
function getConstraintTypeFromSourceHelper2(
code: string
): ReturnType<typeof getConstraintType> | Error {
const ast = parse(code)
if (err(ast)) return ast
const ast = assertParse(code)
const arg = (ast.body[0] as any).expression.arguments[0] as Expr
const fnName = (ast.body[0] as any).expression.callee.name as ToolTip
@ -127,7 +125,7 @@ describe('testing transformAstForSketchLines for equal length constraint', () =>
)
}
const start = codeBeforeLine + line.indexOf('|> ' + 5)
const range: [number, number] = [start, start]
const range: [number, number, boolean] = [start, start, true]
return {
codeRef: codeRefFromRange(range, ast),
}
@ -137,8 +135,7 @@ describe('testing transformAstForSketchLines for equal length constraint', () =>
inputCode: string,
selectionRanges: Selections['graphSelections']
) {
const ast = parse(inputCode)
if (err(ast)) return Promise.reject(ast)
const ast = assertParse(inputCode)
const execState = await enginelessExecutor(ast)
const transformInfos = getTransformInfos(
makeSelections(selectionRanges.slice(1)),
@ -161,8 +158,7 @@ describe('testing transformAstForSketchLines for equal length constraint', () =>
}
it(`Should reorder when user selects first-to-last`, async () => {
const ast = parse(inputScript)
if (err(ast)) return Promise.reject(ast)
const ast = assertParse(inputScript)
const selectionRanges: Selections['graphSelections'] = [
selectLine(inputScript, 3, ast),
selectLine(inputScript, 4, ast),
@ -173,8 +169,7 @@ describe('testing transformAstForSketchLines for equal length constraint', () =>
})
it(`Should reorder when user selects last-to-first`, async () => {
const ast = parse(inputScript)
if (err(ast)) return Promise.reject(ast)
const ast = assertParse(inputScript)
const selectionRanges: Selections['graphSelections'] = [
selectLine(inputScript, 4, ast),
selectLine(inputScript, 3, ast),
@ -293,8 +288,7 @@ part001 = startSketchOn('XY')
|> yLine(segLen(seg01), %) // ln-yLineTo-free should convert to yLine
`
it('should transform the ast', async () => {
const ast = parse(inputScript)
if (err(ast)) return Promise.reject(ast)
const ast = assertParse(inputScript)
const selectionRanges: Selections['graphSelections'] = inputScript
.split('\n')
@ -303,7 +297,7 @@ part001 = startSketchOn('XY')
const comment = ln.split('//')[1]
const start = inputScript.indexOf('//' + comment) - 7
return {
codeRef: codeRefFromRange([start, start], ast),
codeRef: codeRefFromRange([start, start, true], ast),
}
})
@ -383,8 +377,7 @@ part001 = startSketchOn('XY')
|> xLineTo(myVar3, %) // select for horizontal constraint 10
|> angledLineToY([301, myVar], %) // select for vertical constraint 10
`
const ast = parse(inputScript)
if (err(ast)) return Promise.reject(ast)
const ast = assertParse(inputScript)
const selectionRanges: Selections['graphSelections'] = inputScript
.split('\n')
@ -393,7 +386,7 @@ part001 = startSketchOn('XY')
const comment = ln.split('//')[1]
const start = inputScript.indexOf('//' + comment) - 7
return {
codeRef: codeRefFromRange([start, start], ast),
codeRef: codeRefFromRange([start, start, true], ast),
}
})
@ -444,8 +437,7 @@ part001 = startSketchOn('XY')
|> angledLineToX([333, myVar3], %) // select for horizontal constraint 10
|> yLineTo(myVar, %) // select for vertical constraint 10
`
const ast = parse(inputScript)
if (err(ast)) return Promise.reject(ast)
const ast = assertParse(inputScript)
const selectionRanges: Selections['graphSelections'] = inputScript
.split('\n')
@ -454,7 +446,7 @@ part001 = startSketchOn('XY')
const comment = ln.split('//')[1]
const start = inputScript.indexOf('//' + comment) - 7
return {
codeRef: codeRefFromRange([start, start], ast),
codeRef: codeRefFromRange([start, start, true], ast),
}
})
@ -538,8 +530,7 @@ async function helperThing(
linesOfInterest: string[],
constraint: ConstraintType
): Promise<string> {
const ast = parse(inputScript)
if (err(ast)) return Promise.reject(ast)
const ast = assertParse(inputScript)
const selectionRanges: Selections['graphSelections'] = inputScript
.split('\n')
@ -550,7 +541,7 @@ async function helperThing(
const comment = ln.split('//')[1]
const start = inputScript.indexOf('//' + comment) - 7
return {
codeRef: codeRefFromRange([start, start], ast),
codeRef: codeRefFromRange([start, start, true], ast),
}
})
@ -606,7 +597,7 @@ part001 = startSketchOn('XY')
|> line([-1.49, 1.06], %) // free
|> xLine(-3.43 + 0, %) // full
|> angledLineOfXLength([243 + 0, 1.2 + 0], %) // full`
const ast = parse(code)
const ast = assertParse(code)
const constraintLevels: ConstraintLevel[] = ['full', 'partial', 'free']
constraintLevels.forEach((constraintLevel) => {
const recursivelySearchCommentsAndCheckConstraintLevel = (
@ -619,7 +610,7 @@ part001 = startSketchOn('XY')
}
const offsetIndex = index - 7
const expectedConstraintLevel = getConstraintLevelFromSourceRange(
[offsetIndex, offsetIndex],
[offsetIndex, offsetIndex, true],
ast
)
if (err(expectedConstraintLevel)) {

View File

@ -1,4 +1,4 @@
import { parse, initPromise } from '../wasm'
import { assertParse, initPromise } from '../wasm'
import { enginelessExecutor } from '../../lib/testHelpers'
beforeAll(async () => {
@ -17,9 +17,9 @@ describe('testing angledLineThatIntersects', () => {
offset: ${offset},
}, %, $yo2)
intersect = segEndX(yo2)`
const execState = await enginelessExecutor(parse(code('-1')))
const execState = await enginelessExecutor(assertParse(code('-1')))
expect(execState.memory.get('intersect')?.value).toBe(1 + Math.sqrt(2))
const noOffset = await enginelessExecutor(parse(code('0')))
const noOffset = await enginelessExecutor(assertParse(code('0')))
expect(noOffset.memory.get('intersect')?.value).toBeCloseTo(1)
})
})

View File

@ -1,13 +1,18 @@
import { err } from 'lib/trap'
import { parse } from './wasm'
import { parse, ParseResult } from './wasm'
import { enginelessExecutor } from 'lib/testHelpers'
import { Node } from 'wasm-lib/kcl/bindings/Node'
import { Program } from '../wasm-lib/kcl/bindings/Program'
it('can execute parsed AST', async () => {
const code = `x = 1
// A comment.`
const ast = parse(code)
expect(err(ast)).toEqual(false)
const execState = await enginelessExecutor(ast)
expect(err(ast)).toEqual(false)
const result = parse(code)
expect(err(result)).toEqual(false)
const pResult = result as ParseResult
expect(pResult.errors.length).toEqual(0)
expect(pResult.program).not.toEqual(null)
const execState = await enginelessExecutor(pResult.program as Node<Program>)
expect(err(execState)).toEqual(false)
expect(execState.memory.get('x')?.value).toEqual(1)
})

View File

@ -16,6 +16,7 @@ import init, {
parse_project_settings,
default_project_settings,
base64_decode,
clear_scene_and_bust_cache,
} from '../wasm-lib/pkg/wasm_lib'
import { KCLError } from './errors'
import { KclError as RustKclError } from '../wasm-lib/kcl/bindings/KclError'
@ -35,12 +36,13 @@ import { Configuration } from 'wasm-lib/kcl/bindings/Configuration'
import { DeepPartial } from 'lib/types'
import { ProjectConfiguration } from 'wasm-lib/kcl/bindings/ProjectConfiguration'
import { Sketch } from '../wasm-lib/kcl/bindings/Sketch'
import { IdGenerator } from 'wasm-lib/kcl/bindings/IdGenerator'
import { ExecState as RawExecState } from '../wasm-lib/kcl/bindings/ExecState'
import { ProgramMemory as RawProgramMemory } from '../wasm-lib/kcl/bindings/ProgramMemory'
import { EnvironmentRef } from '../wasm-lib/kcl/bindings/EnvironmentRef'
import { Environment } from '../wasm-lib/kcl/bindings/Environment'
import { Node } from 'wasm-lib/kcl/bindings/Node'
import { CompilationError } from 'wasm-lib/kcl/bindings/CompilationError'
import { SourceRange as RustSourceRange } from 'wasm-lib/kcl/bindings/SourceRange'
export type { Program } from '../wasm-lib/kcl/bindings/Program'
export type { Expr } from '../wasm-lib/kcl/bindings/Expr'
@ -84,13 +86,22 @@ export type SyntaxType =
| 'NonCodeNode'
| 'UnaryExpression'
export type { SourceRange } from '../wasm-lib/kcl/bindings/SourceRange'
export type { Path } from '../wasm-lib/kcl/bindings/Path'
export type { Sketch } from '../wasm-lib/kcl/bindings/Sketch'
export type { Solid } from '../wasm-lib/kcl/bindings/Solid'
export type { KclValue } from '../wasm-lib/kcl/bindings/KclValue'
export type { ExtrudeSurface } from '../wasm-lib/kcl/bindings/ExtrudeSurface'
export type SourceRange = [number, number, boolean]
export function sourceRangeFromRust(s: RustSourceRange): SourceRange {
return [s[0], s[1], s[2] === 0]
}
export function defaultSourceRange(): SourceRange {
return [0, 0, true]
}
export const wasmUrl = () => {
// For when we're in electron (file based) or web server (network based)
// For some reason relative paths don't work as expected. Otherwise we would
@ -120,26 +131,81 @@ const initialise = async () => {
export const initPromise = initialise()
export const rangeTypeFix = (ranges: number[][]): [number, number, number][] =>
ranges.map(([start, end, moduleId]) => [start, end, moduleId])
const splitErrors = (
input: CompilationError[]
): { errors: CompilationError[]; warnings: CompilationError[] } => {
let errors = []
let warnings = []
for (const i of input) {
if (i.severity === 'Warning') {
warnings.push(i)
} else {
errors.push(i)
}
}
export const parse = (code: string | Error): Node<Program> | Error => {
return { errors, warnings }
}
export class ParseResult {
program: Node<Program> | null
errors: CompilationError[]
warnings: CompilationError[]
constructor(
program: Node<Program> | null,
errors: CompilationError[],
warnings: CompilationError[]
) {
this.program = program
this.errors = errors
this.warnings = warnings
}
}
class SuccessParseResult extends ParseResult {
program: Node<Program>
constructor(
program: Node<Program>,
errors: CompilationError[],
warnings: CompilationError[]
) {
super(program, errors, warnings)
this.program = program
}
}
export function resultIsOk(result: ParseResult): result is SuccessParseResult {
return !!result.program && result.errors.length === 0
}
export const parse = (code: string | Error): ParseResult | Error => {
if (err(code)) return code
try {
const program: Node<Program> = parse_wasm(code)
return program
const parsed: [Node<Program>, CompilationError[]] = parse_wasm(code)
let errs = splitErrors(parsed[1])
return new ParseResult(parsed[0], errs.errors, errs.warnings)
} catch (e: any) {
// throw e
const parsed: RustKclError = JSON.parse(e.toString())
return new KCLError(
parsed.kind,
parsed.msg,
rangeTypeFix(parsed.sourceRanges)
sourceRangeFromRust(parsed.sourceRanges[0])
)
}
}
// Parse and throw an exception if there are any errors (probably not suitable for use outside of testing).
export const assertParse = (code: string): Node<Program> => {
const result = parse(code)
// eslint-disable-next-line suggest-no-throw/suggest-no-throw
if (err(result) || !resultIsOk(result)) throw result
return result.program
}
export type PathToNode = [string | number, string][]
export const isPathToNodeNumber = (
@ -150,7 +216,6 @@ export const isPathToNodeNumber = (
export interface ExecState {
memory: ProgramMemory
idGenerator: IdGenerator
}
/**
@ -160,21 +225,12 @@ export interface ExecState {
export function emptyExecState(): ExecState {
return {
memory: ProgramMemory.empty(),
idGenerator: defaultIdGenerator(),
}
}
function execStateFromRaw(raw: RawExecState): ExecState {
return {
memory: ProgramMemory.fromRaw(raw.memory),
idGenerator: raw.idGenerator,
}
}
export function defaultIdGenerator(): IdGenerator {
return {
nextId: 0,
ids: [],
}
}
@ -188,6 +244,19 @@ function emptyEnvironment(): Environment {
return { bindings: {}, parent: null }
}
function emptyRootEnvironment(): Environment {
return {
// This is dumb this is copied from rust.
bindings: {
ZERO: { type: 'Number', value: 0.0, __meta: [] },
QUARTER_TURN: { type: 'Number', value: 90.0, __meta: [] },
HALF_TURN: { type: 'Number', value: 180.0, __meta: [] },
THREE_QUARTER_TURN: { type: 'Number', value: 270.0, __meta: [] },
},
parent: null,
}
}
/**
* This duplicates logic in Rust. The hope is to keep ProgramMemory internals
* isolated from the rest of the TypeScript code so that we can move it to Rust
@ -210,7 +279,7 @@ export class ProgramMemory {
}
constructor(
environments: Environment[] = [emptyEnvironment()],
environments: Environment[] = [emptyRootEnvironment()],
currentEnv: EnvironmentRef = ROOT_ENVIRONMENT_REF,
returnVal: KclValue | null = null
) {
@ -397,36 +466,31 @@ export function sketchFromKclValue(
export const executor = async (
node: Node<Program>,
programMemory: ProgramMemory | Error = ProgramMemory.empty(),
idGenerator: IdGenerator = defaultIdGenerator(),
engineCommandManager: EngineCommandManager,
isMock: boolean = false
programMemoryOverride: ProgramMemory | Error | null = null
): Promise<ExecState> => {
if (err(programMemory)) return Promise.reject(programMemory)
if (programMemoryOverride !== null && err(programMemoryOverride))
return Promise.reject(programMemoryOverride)
// eslint-disable-next-line @typescript-eslint/no-floating-promises
engineCommandManager.startNewSession()
const _programMemory = await _executor(
node,
programMemory,
idGenerator,
engineCommandManager,
isMock
programMemoryOverride
)
await engineCommandManager.waitForAllCommands()
engineCommandManager.endSession()
return _programMemory
}
export const _executor = async (
node: Node<Program>,
programMemory: ProgramMemory | Error = ProgramMemory.empty(),
idGenerator: IdGenerator = defaultIdGenerator(),
engineCommandManager: EngineCommandManager,
isMock: boolean
programMemoryOverride: ProgramMemory | Error | null = null
): Promise<ExecState> => {
if (err(programMemory)) return Promise.reject(programMemory)
if (programMemoryOverride !== null && err(programMemoryOverride))
return Promise.reject(programMemoryOverride)
try {
let baseUnit = 'mm'
@ -439,13 +503,10 @@ export const _executor = async (
}
const execState: RawExecState = await execute_wasm(
JSON.stringify(node),
JSON.stringify(programMemory.toRaw()),
JSON.stringify(idGenerator),
JSON.stringify(programMemoryOverride?.toRaw() || null),
baseUnit,
engineCommandManager,
fileSystemManager,
undefined,
isMock
fileSystemManager
)
return execStateFromRaw(execState)
} catch (e: any) {
@ -454,7 +515,7 @@ export const _executor = async (
const kclError = new KCLError(
parsed.kind,
parsed.msg,
rangeTypeFix(parsed.sourceRanges)
sourceRangeFromRust(parsed.sourceRanges[0])
)
return Promise.reject(kclError)
@ -527,7 +588,7 @@ export const modifyAstForSketch = async (
const kclError = new KCLError(
parsed.kind,
parsed.msg,
rangeTypeFix(parsed.sourceRanges)
sourceRangeFromRust(parsed.sourceRanges[0])
)
console.log(kclError)
@ -595,7 +656,7 @@ export function programMemoryInit(): ProgramMemory | Error {
return new KCLError(
parsed.kind,
parsed.msg,
rangeTypeFix(parsed.sourceRanges)
sourceRangeFromRust(parsed.sourceRanges[0])
)
}
}
@ -638,6 +699,21 @@ export function defaultAppSettings(): DeepPartial<Configuration> | Error {
return default_app_settings()
}
export async function clearSceneAndBustCache(
engineCommandManager: EngineCommandManager
): Promise<null | Error> {
try {
await clear_scene_and_bust_cache(engineCommandManager)
} catch (e: any) {
console.error('clear_scene_and_bust_cache: error', e)
return Promise.reject(
new Error(`Error on clear_scene_and_bust_cache: ${e}`)
)
}
return null
}
export function parseAppSettings(
toml: string
): DeepPartial<Configuration> | Error {

View File

@ -31,6 +31,13 @@ export type ModelingCommandSchema = {
// result: (typeof EXTRUSION_RESULTS)[number]
distance: KclCommandValue
}
Loft: {
selection: Selections
}
Shell: {
selection: Selections
thickness: KclCommandValue
}
Revolve: {
selection: Selections
angle: KclCommandValue
@ -260,6 +267,39 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
},
},
},
Loft: {
description: 'Create a 3D body by blending between two or more sketches',
icon: 'loft',
needsReview: true,
args: {
selection: {
inputType: 'selection',
selectionTypes: ['solid2D'],
multiple: true,
required: true,
skip: false,
},
},
},
Shell: {
description: 'Hollow out a 3D solid.',
icon: 'shell',
needsReview: true,
args: {
selection: {
inputType: 'selection',
selectionTypes: ['cap', 'wall'],
multiple: true,
required: true,
skip: false,
},
thickness: {
inputType: 'kcl',
defaultValue: KCL_DEFAULT_LENGTH,
required: true,
},
},
},
// TODO: Update this configuration, copied from extrude for MVP of revolve, specifically the args.selection
Revolve: {
description: 'Create a 3D body by rotating a sketch region about an axis.',

View File

@ -52,6 +52,8 @@ export const ONBOARDING_PROJECT_NAME = 'Tutorial Project $nn'
export const KCL_DEFAULT_CONSTANT_PREFIXES = {
SKETCH: 'sketch',
EXTRUDE: 'extrude',
LOFT: 'loft',
SHELL: 'shell',
SEGMENT: 'seg',
REVOLVE: 'revolve',
PLANE: 'plane',

View File

@ -13,7 +13,6 @@ import {
listProjects,
readAppSettingsFile,
} from './desktop'
import { engineCommandManager } from './singletons'
export const isHidden = (fileOrDir: FileEntry) =>
!!fileOrDir.name?.startsWith('.')
@ -116,9 +115,6 @@ export async function createAndOpenNewTutorialProject({
) => void
navigate: (path: string) => void
}) {
// Clear the scene and end the session.
engineCommandManager.endSession()
// Create a new project with the onboarding project name
const configuration = await readAppSettingsFile()
const projects = await listProjects(configuration)

View File

@ -3,109 +3,109 @@ export const bracket = `// Shelf Bracket
// Define constants
const sigmaAllow = 35000 // psi (6061-T6 aluminum)
const width = 6 // inch
const p = 300 // Force on shelf - lbs
const factorOfSafety = 1.2 // FOS of 1.2
const shelfMountL = 5 // inches
const wallMountL = 2 // inches
const shelfDepth = 12 // Shelf is 12 inches in depth from the wall
const moment = shelfDepth * p // assume the force is applied at the end of the shelf to be conservative (lb-in)
sigmaAllow = 35000 // psi (6061-T6 aluminum)
width = 6 // inch
p = 300 // Force on shelf - lbs
factorOfSafety = 1.2 // FOS of 1.2
shelfMountL = 5 // inches
wallMountL = 2 // inches
shelfDepth = 12 // Shelf is 12 inches in depth from the wall
moment = shelfDepth * p // assume the force is applied at the end of the shelf to be conservative (lb-in)
const filletRadius = .375 // inches
const extFilletRadius = .25 // inches
const mountingHoleDiameter = 0.5 // inches
filletRadius = .375 // inches
extFilletRadius = .25 // inches
mountingHoleDiameter = 0.5 // inches
// Calculate required thickness of bracket
const thickness = sqrt(moment * factorOfSafety * 6 / (sigmaAllow * width)) // this is the calculation of two brackets holding up the shelf (inches)
thickness = sqrt(moment * factorOfSafety * 6 / (sigmaAllow * width)) // this is the calculation of two brackets holding up the shelf (inches)
// Sketch the bracket body and fillet the inner and outer edges of the bend
const bracketLeg1Sketch = startSketchOn('XY')
bracketLeg1Sketch = startSketchOn('XY')
|> startProfileAt([0, 0], %)
|> line([shelfMountL - filletRadius, 0], %, $fillet1)
|> line([0, width], %, $fillet2)
|> line([-shelfMountL + filletRadius, 0], %)
|> close(%)
|> hole(circle({
center: [1, 1],
radius: mountingHoleDiameter / 2
center = [1, 1],
radius = mountingHoleDiameter / 2
}, %), %)
|> hole(circle({
center: [shelfMountL - 1.5, width - 1],
radius: mountingHoleDiameter / 2
center = [shelfMountL - 1.5, width - 1],
radius = mountingHoleDiameter / 2
}, %), %)
|> hole(circle({
center: [1, width - 1],
radius: mountingHoleDiameter / 2
center = [1, width - 1],
radius = mountingHoleDiameter / 2
}, %), %)
|> hole(circle({
center: [shelfMountL - 1.5, 1],
radius: mountingHoleDiameter / 2
center = [shelfMountL - 1.5, 1],
radius = mountingHoleDiameter / 2
}, %), %)
// Extrude the leg 2 bracket sketch
const bracketLeg1Extrude = extrude(thickness, bracketLeg1Sketch)
bracketLeg1Extrude = extrude(thickness, bracketLeg1Sketch)
|> fillet({
radius: extFilletRadius,
tags: [
radius = extFilletRadius,
tags = [
getNextAdjacentEdge(fillet1),
getNextAdjacentEdge(fillet2)
]
}, %)
// Sketch the fillet arc
const filletSketch = startSketchOn('XZ')
filletSketch = startSketchOn('XZ')
|> startProfileAt([0, 0], %)
|> line([0, thickness], %)
|> arc({
angleEnd: 180,
angleStart: 90,
radius: filletRadius + thickness
angleEnd = 180,
angleStart = 90,
radius = filletRadius + thickness
}, %)
|> line([thickness, 0], %)
|> arc({
angleEnd: 90,
angleStart: 180,
radius: filletRadius
angleEnd = 90,
angleStart = 180,
radius = filletRadius
}, %)
// Sketch the bend
const filletExtrude = extrude(-width, filletSketch)
filletExtrude = extrude(-width, filletSketch)
// Create a custom plane for the leg that sits on the wall
const customPlane = {
plane: {
origin: { x: -filletRadius, y: 0, z: 0 },
xAxis: { x: 0, y: 1, z: 0 },
yAxis: { x: 0, y: 0, z: 1 },
zAxis: { x: 1, y: 0, z: 0 }
customPlane = {
plane = {
origin = { x = -filletRadius, y = 0, z = 0 },
xAxis = { x = 0, y = 1, z = 0 },
yAxis = { x = 0, y = 0, z = 1 },
zAxis = { x = 1, y = 0, z = 0 }
}
}
// Create a sketch for the second leg
const bracketLeg2Sketch = startSketchOn(customPlane)
bracketLeg2Sketch = startSketchOn(customPlane)
|> startProfileAt([0, -filletRadius], %)
|> line([width, 0], %)
|> line([0, -wallMountL], %, $fillet3)
|> line([-width, 0], %, $fillet4)
|> close(%)
|> hole(circle({
center: [1, -1.5],
radius: mountingHoleDiameter / 2
center = [1, -1.5],
radius = mountingHoleDiameter / 2
}, %), %)
|> hole(circle({
center: [5, -1.5],
radius: mountingHoleDiameter / 2
center = [5, -1.5],
radius = mountingHoleDiameter / 2
}, %), %)
// Extrude the second leg
const bracketLeg2Extrude = extrude(-thickness, bracketLeg2Sketch)
bracketLeg2Extrude = extrude(-thickness, bracketLeg2Sketch)
|> fillet({
radius: extFilletRadius,
tags: [
radius = extFilletRadius,
tags = [
getNextAdjacentEdge(fillet3),
getNextAdjacentEdge(fillet4)
]
@ -135,8 +135,8 @@ function findLineInExampleCode({
}
export const bracketWidthConstantLine = findLineInExampleCode({
searchText: 'const width',
searchText: 'width =',
})
export const bracketThicknessCalculationLine = findLineInExampleCode({
searchText: 'const thickness',
searchText: 'thickness =',
})

View File

@ -5,7 +5,7 @@ import { isDesktop } from './isDesktop'
import { FILE_EXT, PROJECT_SETTINGS_FILE_NAME } from './constants'
import { UnitLength_type } from '@kittycad/lib/dist/types/src/models'
import { parseProjectSettings } from 'lang/wasm'
import { err } from './trap'
import { err, reportRejection } from './trap'
import { projectConfigurationToSettingsPayload } from './settings/settingsUtils'
interface OnSubmitProps {
@ -28,7 +28,7 @@ export function kclCommands(
groupId: 'code',
icon: 'code',
onSubmit: () => {
kclManager.format()
kclManager.format().catch(reportRejection)
},
},
{

View File

@ -5,7 +5,12 @@ import {
kclManager,
sceneEntitiesManager,
} from 'lib/singletons'
import { CallExpression, SourceRange, Expr } from 'lang/wasm'
import {
CallExpression,
SourceRange,
Expr,
defaultSourceRange,
} from 'lang/wasm'
import { ModelingMachineEvent } from 'machines/modelingMachine'
import { isNonNullable, uuidv4 } from 'lib/utils'
import { EditorSelection, SelectionRange } from '@codemirror/state'
@ -266,7 +271,7 @@ export function getEventForSegmentSelection(
selectionType: 'singleCodeCursor',
selection: {
codeRef: {
range: [node.node.start, node.node.end],
range: [node.node.start, node.node.end, true],
pathToNode: group.userData.pathToNode,
},
},
@ -309,10 +314,11 @@ export function handleSelectionBatch({
selectionToEngine.push({
type: 'default',
id: artifact?.id,
range: getCodeRefsByArtifactId(
artifact.id,
engineCommandManager.artifactGraph
)?.[0].range || [0, 0],
range:
getCodeRefsByArtifactId(
artifact.id,
engineCommandManager.artifactGraph
)?.[0].range || defaultSourceRange(),
})
})
const engineEvents: Models['WebSocketRequest_type'][] =
@ -376,10 +382,10 @@ export function processCodeMirrorRanges({
if (!isChange) return null
const codeBasedSelections: Selections['graphSelections'] =
codeMirrorRanges.map(({ from, to }) => {
const pathToNode = getNodePathFromSourceRange(ast, [from, to])
const pathToNode = getNodePathFromSourceRange(ast, [from, to, true])
return {
codeRef: {
range: [from, to],
range: [from, to, true],
pathToNode,
},
}
@ -442,7 +448,7 @@ function updateSceneObjectColors(codeBasedSelections: Selection[]) {
if (err(nodeMeta)) return
const node = nodeMeta.node
const groupHasCursor = codeBasedSelections.some((selection) => {
return isOverlap(selection?.codeRef?.range, [node.start, node.end])
return isOverlap(selection?.codeRef?.range, [node.start, node.end, true])
})
const color = groupHasCursor
@ -529,6 +535,10 @@ function nodeHasExtrude(node: CommonASTNode) {
doesPipeHaveCallExp({
calleeName: 'revolve',
...node,
}) ||
doesPipeHaveCallExp({
calleeName: 'loft',
...node,
})
)
}
@ -559,6 +569,33 @@ export function canSweepSelection(selection: Selections) {
)
}
export function canLoftSelection(selection: Selections) {
const commonNodes = selection.graphSelections.map((_, i) =>
buildCommonNodeFromSelection(selection, i)
)
return (
!!isCursorInSketchCommandRange(
engineCommandManager.artifactGraph,
selection
) &&
commonNodes.length > 1 &&
commonNodes.every((n) => !hasSketchPipeBeenExtruded(n.selection, n.ast)) &&
commonNodes.every((n) => nodeHasClose(n) || nodeHasCircle(n)) &&
commonNodes.every((n) => !nodeHasExtrude(n))
)
}
export function canShellSelection(selection: Selections) {
const commonNodes = selection.graphSelections.map((_, i) =>
buildCommonNodeFromSelection(selection, i)
)
return commonNodes.every(
(n) =>
n.selection.artifact?.type === 'cap' ||
n.selection.artifact?.type === 'wall'
)
}
// This accounts for non-geometry selections under "other"
export type ResolvedSelectionType = Artifact['type'] | 'other'
export type SelectionCountsByType = Map<ResolvedSelectionType, number>
@ -905,7 +942,7 @@ export function updateSelections(
return {
artifact: artifact,
codeRef: {
range: [node.start, node.end],
range: [node.start, node.end, true],
pathToNode: pathToNode,
},
}
@ -919,7 +956,7 @@ export function updateSelections(
if (err(node)) return node
pathToNodeBasedSelections.push({
codeRef: {
range: [node.node.start, node.node.end],
range: [node.node.start, node.node.end, true],
pathToNode: pathToNode,
},
})

View File

@ -4,7 +4,6 @@ import {
_executor,
SourceRange,
ExecState,
defaultIdGenerator,
} from '../lang/wasm'
import {
EngineCommandManager,
@ -16,7 +15,6 @@ import { v4 as uuidv4 } from 'uuid'
import { DefaultPlanes } from 'wasm-lib/kcl/bindings/DefaultPlanes'
import { err, reportRejection } from 'lib/trap'
import { toSync } from './utils'
import { IdGenerator } from 'wasm-lib/kcl/bindings/IdGenerator'
import { Node } from 'wasm-lib/kcl/bindings/Node'
type WebSocketResponse = Models['WebSocketResponse_type']
@ -85,12 +83,10 @@ class MockEngineCommandManager {
}
export async function enginelessExecutor(
ast: Node<Program> | Error,
pm: ProgramMemory | Error = ProgramMemory.empty(),
idGenerator: IdGenerator = defaultIdGenerator()
ast: Node<Program>,
pmo: ProgramMemory | Error = ProgramMemory.empty()
): Promise<ExecState> {
if (err(ast)) return Promise.reject(ast)
if (err(pm)) return Promise.reject(pm)
if (pmo !== null && err(pmo)) return Promise.reject(pmo)
const mockEngineCommandManager = new MockEngineCommandManager({
setIsStreamReady: () => {},
@ -98,21 +94,14 @@ export async function enginelessExecutor(
}) as any as EngineCommandManager
// eslint-disable-next-line @typescript-eslint/no-floating-promises
mockEngineCommandManager.startNewSession()
const execState = await _executor(
ast,
pm,
idGenerator,
mockEngineCommandManager,
true
)
const execState = await _executor(ast, mockEngineCommandManager, pmo)
await mockEngineCommandManager.waitForAllCommands()
return execState
}
export async function executor(
ast: Node<Program>,
pm: ProgramMemory = ProgramMemory.empty(),
idGenerator: IdGenerator = defaultIdGenerator()
pmo: ProgramMemory = ProgramMemory.empty()
): Promise<ExecState> {
const engineCommandManager = new EngineCommandManager()
engineCommandManager.start({
@ -134,13 +123,7 @@ export async function executor(
toSync(async () => {
// eslint-disable-next-line @typescript-eslint/no-floating-promises
engineCommandManager.startNewSession()
const execState = await _executor(
ast,
pm,
idGenerator,
engineCommandManager,
false
)
const execState = await _executor(ast, engineCommandManager, pmo)
await engineCommandManager.waitForAllCommands()
resolve(execState)
}, reportRejection)

View File

@ -139,9 +139,14 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
},
{
id: 'loft',
onClick: () => console.error('Loft not yet implemented'),
onClick: ({ commandBarSend }) =>
commandBarSend({
type: 'Find and select command',
data: { name: 'Loft', groupId: 'modeling' },
}),
disabled: (state) => !state.can({ type: 'Loft' }),
icon: 'loft',
status: 'kcl-only',
status: 'available',
title: 'Loft',
hotkey: 'L',
description:
@ -185,9 +190,15 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
},
{
id: 'shell',
onClick: () => console.error('Shell not yet implemented'),
onClick: ({ commandBarSend }) => {
commandBarSend({
type: 'Find and select command',
data: { name: 'Shell', groupId: 'modeling' },
})
},
disabled: (state) => !state.can({ type: 'Shell' }),
icon: 'shell',
status: 'kcl-only',
status: 'available',
title: 'Shell',
description: 'Hollow out a 3D solid.',
links: [{ label: 'KCL docs', url: 'https://zoo.dev/docs/kcl/shell' }],

View File

@ -3,7 +3,7 @@ import { kclManager, engineCommandManager } from 'lib/singletons'
import { useKclContext } from 'lang/KclProvider'
import { findUniqueName } from 'lang/modifyAst'
import { PrevVariable, findAllPreviousVariables } from 'lang/queryAst'
import { ProgramMemory, Expr, parse } from 'lang/wasm'
import { ProgramMemory, Expr, parse, resultIsOk } from 'lang/wasm'
import { useEffect, useRef, useState } from 'react'
import { executeAst } from 'lang/langHelpers'
import { err, trap } from 'lib/trap'
@ -87,9 +87,9 @@ export function useCalculateKclExpression({
useEffect(() => {
const execAstAndSetResult = async () => {
const _code = `const __result__ = ${value}`
const ast = parse(_code)
if (err(ast)) return
if (trap(ast, { suppress: true })) return
const pResult = parse(_code)
if (err(pResult) || !resultIsOk(pResult)) return
const ast = pResult.program
const _programMem: ProgramMemory = ProgramMemory.empty()
for (const { key, value } of availableVarInfo.variables) {
@ -103,18 +103,17 @@ export function useCalculateKclExpression({
const { execState } = await 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,
})
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

@ -9,13 +9,13 @@ import {
import { SourceRange } from '../lang/wasm'
describe('testing isOverlapping', () => {
testBothOrders([0, 3], [3, 10])
testBothOrders([0, 5], [3, 4])
testBothOrders([0, 5], [5, 10])
testBothOrders([0, 5], [6, 10], false)
testBothOrders([0, 5], [-1, 1])
testBothOrders([0, 5], [-1, 0])
testBothOrders([0, 5], [-2, -1], false)
testBothOrders([0, 3, true], [3, 10, true])
testBothOrders([0, 5, true], [3, 4, true])
testBothOrders([0, 5, true], [5, 10, true])
testBothOrders([0, 5, true], [6, 10, true], false)
testBothOrders([0, 5, true], [-1, 1, true])
testBothOrders([0, 5, true], [-1, 0, true])
testBothOrders([0, 5, true], [-2, -1, true], false)
})
function testBothOrders(a: SourceRange, b: SourceRange, result = true) {

View File

@ -1,9 +1,11 @@
import {
PathToNode,
ProgramMemory,
VariableDeclaration,
VariableDeclarator,
parse,
recast,
resultIsOk,
} from 'lang/wasm'
import {
Axis,
@ -44,13 +46,14 @@ import {
addOffsetPlane,
deleteFromSelection,
extrudeSketch,
loftSketches,
revolveSketch,
} from 'lang/modifyAst'
import {
applyEdgeTreatmentToSelection,
EdgeTreatmentType,
FilletParameters,
} from 'lang/modifyAst/addFillet'
} from 'lang/modifyAst/addEdgeTreatment'
import { getNodeFromPath } from '../lang/queryAst'
import {
applyConstraintEqualAngle,
@ -77,6 +80,7 @@ import { ToolbarModeName } from 'lib/toolbar'
import { quaternionFromUpNForward } from 'clientSideScene/helpers'
import { Vector3 } from 'three'
import { MachineManager } from 'components/MachineManagerProvider'
import { addShell } from 'lang/modifyAst/addShell'
export const MODELING_PERSIST_KEY = 'MODELING_PERSIST_KEY'
@ -256,6 +260,8 @@ export type ModelingMachineEvent =
| { type: 'Export'; data: ModelingCommandSchema['Export'] }
| { type: 'Make'; data: ModelingCommandSchema['Make'] }
| { type: 'Extrude'; data?: ModelingCommandSchema['Extrude'] }
| { type: 'Loft'; data?: ModelingCommandSchema['Loft'] }
| { type: 'Shell'; data?: ModelingCommandSchema['Shell'] }
| { type: 'Revolve'; data?: ModelingCommandSchema['Revolve'] }
| { type: 'Fillet'; data?: ModelingCommandSchema['Fillet'] }
| { type: 'Offset plane'; data: ModelingCommandSchema['Offset plane'] }
@ -387,6 +393,8 @@ export const modelingMachine = setup({
guards: {
'Selection is on face': () => false,
'has valid sweep selection': () => false,
'has valid loft selection': () => false,
'has valid shell selection': () => false,
'has valid edge treatment selection': () => false,
'Has exportable geometry': () => false,
'has valid selection for deletion': () => false,
@ -539,8 +547,11 @@ export const modelingMachine = setup({
if (event.type !== 'Convert to variable') return false
if (!event.data) return false
const ast = parse(recast(kclManager.ast))
if (err(ast)) return false
const isSafeRetVal = isNodeSafeToReplacePath(ast, event.data.pathToNode)
if (err(ast) || !ast.program || ast.errors.length > 0) return false
const isSafeRetVal = isNodeSafeToReplacePath(
ast.program,
event.data.pathToNode
)
if (err(isSafeRetVal)) return false
return isSafeRetVal.isSafe
},
@ -723,9 +734,9 @@ export const modelingMachine = setup({
const testExecute = await executeAst({
ast: modifiedAst,
idGenerator: kclManager.execState.idGenerator,
useFakeExecutor: true,
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('Unable to delete part')
@ -1329,9 +1340,12 @@ export const modelingMachine = setup({
return
}
const recastAst = parse(recast(modifiedAst))
if (err(recastAst) || !resultIsOk(recastAst)) return
const updatedAst = await sceneEntitiesManager.updateAstAndRejigSketch(
sketchDetails?.sketchPathToNode || [],
parse(recast(modifiedAst)),
recastAst.program,
sketchDetails.zAxis,
sketchDetails.yAxis,
sketchDetails.origin
@ -1529,6 +1543,110 @@ export const modelingMachine = setup({
updateAstResult.newAst
)
if (updateAstResult?.selections) {
editorManager.selectRange(updateAstResult?.selections)
}
}
),
loftAstMod: fromPromise(
async ({
input,
}: {
input: ModelingCommandSchema['Loft'] | undefined
}) => {
if (!input) return new Error('No input provided')
// Extract inputs
const ast = kclManager.ast
const { selection } = input
const declarators = selection.graphSelections.flatMap((s) => {
const path = getNodePathFromSourceRange(ast, s?.codeRef.range)
const nodeFromPath = getNodeFromPath<VariableDeclarator>(
ast,
path,
'VariableDeclarator'
)
return err(nodeFromPath) ? [] : nodeFromPath.node
})
// TODO: add better validation on selection
if (!(declarators && declarators.length > 1)) {
trap('Not enough sketches selected')
}
// Perform the loft
const loftSketchesRes = loftSketches(ast, declarators)
const updateAstResult = await kclManager.updateAst(
loftSketchesRes.modifiedAst,
true,
{
focusPath: [loftSketchesRes.pathToNode],
}
)
await codeManager.updateEditorWithAstAndWriteToFile(
updateAstResult.newAst
)
if (updateAstResult?.selections) {
editorManager.selectRange(updateAstResult?.selections)
}
}
),
shellAstMod: fromPromise(
async ({
input,
}: {
input: ModelingCommandSchema['Shell'] | undefined
}) => {
if (!input) {
return new Error('No input provided')
}
// Extract inputs
const ast = kclManager.ast
const { selection, thickness } = input
// Insert the thickness variable if it exists
if (
'variableName' in thickness &&
thickness.variableName &&
thickness.insertIndex !== undefined
) {
const newBody = [...ast.body]
newBody.splice(
thickness.insertIndex,
0,
thickness.variableDeclarationAst
)
ast.body = newBody
}
// Perform the shell op
const shellResult = addShell({
node: ast,
selection,
artifactGraph: engineCommandManager.artifactGraph,
thickness:
'variableName' in thickness
? thickness.variableIdentifierAst
: thickness.valueAst,
})
if (err(shellResult)) {
return err(shellResult)
}
const updateAstResult = await kclManager.updateAst(
shellResult.modifiedAst,
true,
{
focusPath: [shellResult.pathToNode],
}
)
await codeManager.updateEditorWithAstAndWriteToFile(
updateAstResult.newAst
)
if (updateAstResult?.selections) {
editorManager.selectRange(updateAstResult?.selections)
}
@ -1570,6 +1688,18 @@ export const modelingMachine = setup({
reenter: false,
},
Loft: {
target: 'Applying loft',
guard: 'has valid loft selection',
reenter: true,
},
Shell: {
target: 'Applying shell',
guard: 'has valid shell selection',
reenter: true,
},
Fillet: {
target: 'idle',
guard: 'has valid edge treatment selection',
@ -2318,6 +2448,32 @@ export const modelingMachine = setup({
onError: ['idle'],
},
},
'Applying loft': {
invoke: {
src: 'loftAstMod',
id: 'loftAstMod',
input: ({ event }) => {
if (event.type !== 'Loft') return undefined
return event.data
},
onDone: ['idle'],
onError: ['idle'],
},
},
'Applying shell': {
invoke: {
src: 'shellAstMod',
id: 'shellAstMod',
input: ({ event }) => {
if (event.type !== 'Shell') return undefined
return event.data
},
onDone: ['idle'],
onError: ['idle'],
},
},
},
initial: 'idle',
@ -2418,7 +2574,7 @@ export function canRectangleOrCircleTool({
// This should not be returning false, and it should be caught
// but we need to simulate old behavior to move on.
if (err(node)) return false
return node.node?.declarations?.[0]?.init.type !== 'PipeExpression'
return node.node?.declaration.init.type !== 'PipeExpression'
}
/** If the sketch contains `close` or `circle` stdlib functions it must be closed */
@ -2435,8 +2591,8 @@ export function isClosedSketch({
// This should not be returning false, and it should be caught
// but we need to simulate old behavior to move on.
if (err(node)) return false
if (node.node?.declarations?.[0]?.init.type !== 'PipeExpression') return false
return node.node.declarations[0].init.body.some(
if (node.node?.declaration.init.type !== 'PipeExpression') return false
return node.node.declaration.init.body.some(
(node) =>
node.type === 'CallExpression' &&
(node.callee.name === 'close' || node.callee.name === 'circle')

View File

@ -316,7 +316,7 @@ dependencies = [
"bitvec",
"chrono",
"hex",
"indexmap 2.6.0",
"indexmap 2.7.0",
"js-sys",
"once_cell",
"rand 0.8.5",
@ -712,29 +712,6 @@ version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2"
[[package]]
name = "databake"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a04fbfbecca8f0679c8c06fef907594adcc3e2052e11163a6d30535a1a5604d"
dependencies = [
"databake-derive",
"proc-macro2",
"quote",
]
[[package]]
name = "databake-derive"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4078275de501a61ceb9e759d37bdd3d7210e654dbc167ac1a3678ef4435ed57b"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.87",
"synstructure",
]
[[package]]
name = "deranged"
version = "0.3.11"
@ -746,7 +723,7 @@ dependencies = [
[[package]]
name = "derive-docs"
version = "0.1.31"
version = "0.1.32"
dependencies = [
"Inflector",
"anyhow",
@ -1136,7 +1113,7 @@ dependencies = [
"futures-core",
"futures-sink",
"http 1.1.0",
"indexmap 2.6.0",
"indexmap 2.7.0",
"slab",
"tokio",
"tokio-util",
@ -1182,9 +1159,9 @@ checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
[[package]]
name = "hashbrown"
version = "0.15.0"
version = "0.15.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb"
checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289"
[[package]]
name = "heck"
@ -1584,12 +1561,12 @@ dependencies = [
[[package]]
name = "indexmap"
version = "2.6.0"
version = "2.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da"
checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f"
dependencies = [
"equivalent",
"hashbrown 0.15.0",
"hashbrown 0.15.2",
"serde",
]
@ -1706,7 +1683,7 @@ dependencies = [
[[package]]
name = "kcl-lib"
version = "0.2.26"
version = "0.2.28"
dependencies = [
"anyhow",
"approx 0.5.1",
@ -1719,7 +1696,6 @@ dependencies = [
"convert_case",
"criterion",
"dashmap 6.1.0",
"databake",
"derive-docs",
"dhat",
"expectorate",
@ -1732,7 +1708,7 @@ dependencies = [
"http 1.1.0",
"iai",
"image",
"indexmap 2.6.0",
"indexmap 2.7.0",
"insta",
"itertools 0.13.0",
"js-sys",
@ -1773,7 +1749,7 @@ dependencies = [
[[package]]
name = "kcl-test-server"
version = "0.1.17"
version = "0.1.18"
dependencies = [
"anyhow",
"hyper 0.14.30",
@ -1790,7 +1766,7 @@ version = "0.1.0"
dependencies = [
"anyhow",
"async-trait",
"indexmap 2.6.0",
"indexmap 2.7.0",
"kcl-lib",
"kittycad",
"kittycad-modeling-cmds",
@ -1800,9 +1776,9 @@ dependencies = [
[[package]]
name = "kittycad"
version = "0.3.25"
version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6359cc0a1bbccbcf78775eea17a033cf2aa89d3fe6a9784f8ce94e5f882c185"
checksum = "933cb5f77624386c87d296e3fd493daf50156d1cbfa03b9f333a6d4da2896369"
dependencies = [
"anyhow",
"async-trait",
@ -1831,7 +1807,7 @@ dependencies = [
"serde_bytes",
"serde_json",
"serde_urlencoded",
"thiserror 1.0.68",
"thiserror 2.0.0",
"tokio",
"tracing",
"url",
@ -2921,9 +2897,9 @@ dependencies = [
[[package]]
name = "reqwest-conditional-middleware"
version = "0.3.0"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1663d9d4fbb6e3900f91455d6d7833301c91ae3c7fc6e116fd7acd40e478a93"
checksum = "f67ad7fdf5c0a015763fcd164bee294b13fb7b6f89f1b55961d40f00c3e32d6b"
dependencies = [
"async-trait",
"http 1.1.0",
@ -2933,9 +2909,9 @@ dependencies = [
[[package]]
name = "reqwest-middleware"
version = "0.3.3"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "562ceb5a604d3f7c885a792d42c199fd8af239d0a51b2fa6a78aafa092452b04"
checksum = "d1ccd3b55e711f91a9885a2fa6fbbb2e39db1776420b062efc058c6410f7e5e3"
dependencies = [
"anyhow",
"async-trait",
@ -2948,9 +2924,9 @@ dependencies = [
[[package]]
name = "reqwest-retry"
version = "0.6.1"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a83df1aaec00176d0fabb65dea13f832d2a446ca99107afc17c5d2d4981221d0"
checksum = "29c73e4195a6bfbcb174b790d9b3407ab90646976c55de58a6515da25d851178"
dependencies = [
"anyhow",
"async-trait",
@ -2962,6 +2938,7 @@ dependencies = [
"reqwest",
"reqwest-middleware",
"retry-policies",
"thiserror 1.0.68",
"tokio",
"tracing",
"wasm-timer",
@ -2969,9 +2946,9 @@ dependencies = [
[[package]]
name = "reqwest-tracing"
version = "0.5.3"
version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfdd9bfa64c72233d8dd99ab7883efcdefe9e16d46488ecb9228b71a2e2ceb45"
checksum = "ff82cf5730a1311fb9413b0bc2b8e743e0157cd73f010ab4ec374a923873b6a2"
dependencies = [
"anyhow",
"async-trait",
@ -3157,7 +3134,7 @@ dependencies = [
"chrono",
"dyn-clone",
"indexmap 1.9.3",
"indexmap 2.6.0",
"indexmap 2.7.0",
"schemars_derive",
"serde",
"serde_json",
@ -3258,7 +3235,7 @@ version = "1.0.133"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377"
dependencies = [
"indexmap 2.6.0",
"indexmap 2.7.0",
"itoa",
"memchr",
"ryu",
@ -3846,7 +3823,7 @@ version = "0.22.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5"
dependencies = [
"indexmap 2.6.0",
"indexmap 2.7.0",
"serde",
"serde_spanned",
"toml_datetime",
@ -4012,7 +3989,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a2f31991cee3dce1ca4f929a8a04fdd11fd8801aac0f2030b0fa8a0a3fef6b9"
dependencies = [
"chrono",
"indexmap 2.6.0",
"indexmap 2.7.0",
"lazy_static",
"serde_json",
"thiserror 1.0.68",
@ -4331,6 +4308,7 @@ dependencies = [
"kcl-lib",
"kittycad",
"kittycad-modeling-cmds",
"lazy_static",
"pretty_assertions",
"reqwest",
"serde_json",
@ -4769,7 +4747,7 @@ dependencies = [
"crc32fast",
"crossbeam-utils",
"displaydoc",
"indexmap 2.6.0",
"indexmap 2.7.0",
"memchr",
"thiserror 1.0.68",
]

View File

@ -15,6 +15,7 @@ data-encoding = "2.6.0"
gloo-utils = "0.2.0"
kcl-lib = { path = "kcl" }
kittycad.workspace = true
lazy_static = "1.5.0"
serde_json = "1.0.128"
tokio = { version = "1.41.1", features = ["sync"] }
toml = "0.8.19"
@ -74,8 +75,11 @@ members = [
[workspace.dependencies]
http = "1"
kittycad = { version = "0.3.25", default-features = false, features = ["js", "requests"] }
kittycad-modeling-cmds = { version = "0.2.76", features = ["websocket"] }
kittycad = { version = "0.3.28", default-features = false, features = ["js", "requests"] }
kittycad-modeling-cmds = { version = "0.2.77", features = ["websocket"] }
[workspace.lints.clippy]
iter_over_hash_type = "warn"
[[test]]
name = "executor"

View File

@ -1,7 +1,7 @@
[package]
name = "derive-docs"
description = "A tool for generating documentation from Rust derive macros"
version = "0.1.31"
version = "0.1.32"
edition = "2021"
license = "MIT"
repository = "https://github.com/KittyCAD/modeling-app"
@ -27,3 +27,6 @@ anyhow = "1.0.93"
expectorate = "1.1.0"
pretty_assertions = "1.4.1"
rustfmt-wrapper = "0.2.1"
[lints]
workspace = true

View File

@ -23,17 +23,30 @@ use unbox::unbox;
struct StdlibMetadata {
/// The name of the function in the API.
name: String,
/// Tags for the function.
#[serde(default)]
tags: Vec<String>,
/// Whether the function is unpublished.
/// Then docs will not be generated.
#[serde(default)]
unpublished: bool,
/// Whether the function is deprecated.
/// Then specific docs detailing that this is deprecated will be generated.
#[serde(default)]
deprecated: bool,
/// If true, expects keyword arguments.
/// If false, expects positional arguments.
#[serde(default)]
keywords: bool,
/// If true, the first argument is unlabeled.
/// If false, all arguments require labels.
#[serde(default)]
unlabeled_first: bool,
}
#[proc_macro_attribute]
@ -169,9 +182,9 @@ fn do_stdlib_inner(
quote! {
let code_blocks = vec![#(#cb),*];
code_blocks.iter().map(|cb| {
let program = crate::Program::parse(cb).unwrap();
let program = crate::Program::parse_no_errs(cb).unwrap();
let mut options: crate::ast::types::FormatOptions = Default::default();
let mut options: crate::parsing::ast::types::FormatOptions = Default::default();
options.insert_final_newline = false;
program.ast.recast(&options, 0)
}).collect::<Vec<String>>()
@ -225,6 +238,12 @@ fn do_stdlib_inner(
quote! { false }
};
let uses_keyword_arguments = if metadata.keywords {
quote! { true }
} else {
quote! { false }
};
let docs_crate = get_crate(None);
// When the user attaches this proc macro to a function with the wrong type
@ -233,7 +252,7 @@ fn do_stdlib_inner(
// of the various parameters. We do this by calling dummy functions that
// require a type that satisfies SharedExtractor or ExclusiveExtractor.
let mut arg_types = Vec::new();
for arg in ast.sig.inputs.iter() {
for (i, arg) in ast.sig.inputs.iter().enumerate() {
// Get the name of the argument.
let arg_name = match arg {
syn::FnArg::Receiver(pat) => {
@ -263,7 +282,7 @@ fn do_stdlib_inner(
let ty_string = rust_type_to_openapi_type(&ty_string);
let required = !ty_ident.to_string().starts_with("Option <");
let label_required = !(i == 0 && metadata.unlabeled_first);
if ty_string != "ExecState" && ty_string != "Args" {
let schema = quote! {
generator.root_schema_for::<#ty_ident>()
@ -274,6 +293,7 @@ fn do_stdlib_inner(
type_: #ty_string.to_string(),
schema: #schema,
required: #required,
label_required: #label_required,
}
});
}
@ -334,6 +354,7 @@ fn do_stdlib_inner(
type_: #ret_ty_string.to_string(),
schema,
required: true,
label_required: true,
})
}
} else {
@ -374,10 +395,10 @@ fn do_stdlib_inner(
#const_struct
fn #boxed_fn_name_ident(
exec_state: &mut crate::executor::ExecState,
exec_state: &mut crate::ExecState,
args: crate::std::Args,
) -> std::pin::Pin<
Box<dyn std::future::Future<Output = anyhow::Result<crate::executor::KclValue, crate::errors::KclError>> + Send + '_>,
Box<dyn std::future::Future<Output = anyhow::Result<crate::execution::KclValue, crate::errors::KclError>> + Send + '_>,
> {
Box::pin(#fn_name_ident(exec_state, args))
}
@ -400,6 +421,10 @@ fn do_stdlib_inner(
vec![#(#tags),*]
}
fn keyword_arguments(&self) -> bool {
#uses_keyword_arguments
}
fn args(&self, inline_subschemas: bool) -> Vec<#docs_crate::StdLibFnArg> {
let mut settings = schemars::gen::SchemaSettings::openapi3();
// We set this to false so we can recurse them later.
@ -744,23 +769,23 @@ fn generate_code_block_test(fn_name: &str, code_block: &str, index: usize) -> pr
quote! {
#[tokio::test(flavor = "multi_thread")]
async fn #test_name_mock() {
let program = crate::Program::parse(#code_block).unwrap();
let ctx = crate::executor::ExecutorContext {
let program = crate::Program::parse_no_errs(#code_block).unwrap();
let ctx = crate::ExecutorContext {
engine: std::sync::Arc::new(Box::new(crate::engine::conn_mock::EngineConnection::new().await.unwrap())),
fs: std::sync::Arc::new(crate::fs::FileManager::new()),
stdlib: std::sync::Arc::new(crate::std::StdLib::new()),
settings: Default::default(),
context_type: crate::executor::ContextType::Mock,
context_type: crate::execution::ContextType::Mock,
};
ctx.run(&program, &mut crate::ExecState::default()).await.unwrap();
ctx.run(program.into(), &mut crate::ExecState::default()).await.unwrap();
}
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
async fn #test_name() {
let code = #code_block;
// Note, `crate` must be kcl_lib
let result = crate::test_server::execute_and_snapshot(code, crate::settings::types::UnitLength::Mm).await.unwrap();
let result = crate::test_server::execute_and_snapshot(code, crate::settings::types::UnitLength::Mm, None).await.unwrap();
twenty_twenty::assert_image(&format!("tests/outputs/{}.png", #output_test_name_str), &result, 0.99);
}
}

View File

@ -2,8 +2,8 @@
mod test_examples_someFn {
#[tokio::test(flavor = "multi_thread")]
async fn test_mock_example_someFn0() {
let program = crate::Program::parse("someFn()").unwrap();
let ctx = crate::executor::ExecutorContext {
let program = crate::Program::parse_no_errs("someFn()").unwrap();
let ctx = crate::ExecutorContext {
engine: std::sync::Arc::new(Box::new(
crate::engine::conn_mock::EngineConnection::new()
.await
@ -12,9 +12,9 @@ mod test_examples_someFn {
fs: std::sync::Arc::new(crate::fs::FileManager::new()),
stdlib: std::sync::Arc::new(crate::std::StdLib::new()),
settings: Default::default(),
context_type: crate::executor::ContextType::Mock,
context_type: crate::execution::ContextType::Mock,
};
ctx.run(&program, &mut crate::ExecState::default())
ctx.run(program.into(), &mut crate::ExecState::default())
.await
.unwrap();
}
@ -22,10 +22,13 @@ mod test_examples_someFn {
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
async fn kcl_test_example_someFn0() {
let code = "someFn()";
let result =
crate::test_server::execute_and_snapshot(code, crate::settings::types::UnitLength::Mm)
.await
.unwrap();
let result = crate::test_server::execute_and_snapshot(
code,
crate::settings::types::UnitLength::Mm,
None,
)
.await
.unwrap();
twenty_twenty::assert_image(
&format!("tests/outputs/{}.png", "serial_test_example_someFn0"),
&result,
@ -44,12 +47,12 @@ pub(crate) struct SomeFn {}
#[doc = "Std lib function: someFn\nDocs"]
pub(crate) const SomeFn: SomeFn = SomeFn {};
fn boxed_someFn(
exec_state: &mut crate::executor::ExecState,
exec_state: &mut crate::ExecState,
args: crate::std::Args,
) -> std::pin::Pin<
Box<
dyn std::future::Future<
Output = anyhow::Result<crate::executor::KclValue, crate::errors::KclError>,
Output = anyhow::Result<crate::execution::KclValue, crate::errors::KclError>,
> + Send
+ '_,
>,
@ -74,6 +77,10 @@ impl crate::docs::StdLibFn for SomeFn {
vec![]
}
fn keyword_arguments(&self) -> bool {
false
}
fn args(&self, inline_subschemas: bool) -> Vec<crate::docs::StdLibFnArg> {
let mut settings = schemars::gen::SchemaSettings::openapi3();
settings.inline_subschemas = inline_subschemas;
@ -83,6 +90,7 @@ impl crate::docs::StdLibFn for SomeFn {
type_: "Foo".to_string(),
schema: generator.root_schema_for::<Foo>(),
required: true,
label_required: true,
}]
}
@ -96,6 +104,7 @@ impl crate::docs::StdLibFn for SomeFn {
type_: "i32".to_string(),
schema,
required: true,
label_required: true,
})
}
@ -112,8 +121,8 @@ impl crate::docs::StdLibFn for SomeFn {
code_blocks
.iter()
.map(|cb| {
let program = crate::Program::parse(cb).unwrap();
let mut options: crate::ast::types::FormatOptions = Default::default();
let program = crate::Program::parse_no_errs(cb).unwrap();
let mut options: crate::parsing::ast::types::FormatOptions = Default::default();
options.insert_final_newline = false;
program.ast.recast(&options, 0)
})

View File

@ -2,8 +2,8 @@
mod test_examples_someFn {
#[tokio::test(flavor = "multi_thread")]
async fn test_mock_example_someFn0() {
let program = crate::Program::parse("someFn()").unwrap();
let ctx = crate::executor::ExecutorContext {
let program = crate::Program::parse_no_errs("someFn()").unwrap();
let ctx = crate::ExecutorContext {
engine: std::sync::Arc::new(Box::new(
crate::engine::conn_mock::EngineConnection::new()
.await
@ -12,9 +12,9 @@ mod test_examples_someFn {
fs: std::sync::Arc::new(crate::fs::FileManager::new()),
stdlib: std::sync::Arc::new(crate::std::StdLib::new()),
settings: Default::default(),
context_type: crate::executor::ContextType::Mock,
context_type: crate::execution::ContextType::Mock,
};
ctx.run(&program, &mut crate::ExecState::default())
ctx.run(program.into(), &mut crate::ExecState::default())
.await
.unwrap();
}
@ -22,10 +22,13 @@ mod test_examples_someFn {
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
async fn kcl_test_example_someFn0() {
let code = "someFn()";
let result =
crate::test_server::execute_and_snapshot(code, crate::settings::types::UnitLength::Mm)
.await
.unwrap();
let result = crate::test_server::execute_and_snapshot(
code,
crate::settings::types::UnitLength::Mm,
None,
)
.await
.unwrap();
twenty_twenty::assert_image(
&format!("tests/outputs/{}.png", "serial_test_example_someFn0"),
&result,
@ -44,12 +47,12 @@ pub(crate) struct SomeFn {}
#[doc = "Std lib function: someFn\nDocs"]
pub(crate) const SomeFn: SomeFn = SomeFn {};
fn boxed_someFn(
exec_state: &mut crate::executor::ExecState,
exec_state: &mut crate::ExecState,
args: crate::std::Args,
) -> std::pin::Pin<
Box<
dyn std::future::Future<
Output = anyhow::Result<crate::executor::KclValue, crate::errors::KclError>,
Output = anyhow::Result<crate::execution::KclValue, crate::errors::KclError>,
> + Send
+ '_,
>,
@ -74,6 +77,10 @@ impl crate::docs::StdLibFn for SomeFn {
vec![]
}
fn keyword_arguments(&self) -> bool {
false
}
fn args(&self, inline_subschemas: bool) -> Vec<crate::docs::StdLibFnArg> {
let mut settings = schemars::gen::SchemaSettings::openapi3();
settings.inline_subschemas = inline_subschemas;
@ -83,6 +90,7 @@ impl crate::docs::StdLibFn for SomeFn {
type_: "string".to_string(),
schema: generator.root_schema_for::<str>(),
required: true,
label_required: true,
}]
}
@ -96,6 +104,7 @@ impl crate::docs::StdLibFn for SomeFn {
type_: "i32".to_string(),
schema,
required: true,
label_required: true,
})
}
@ -112,8 +121,8 @@ impl crate::docs::StdLibFn for SomeFn {
code_blocks
.iter()
.map(|cb| {
let program = crate::Program::parse(cb).unwrap();
let mut options: crate::ast::types::FormatOptions = Default::default();
let program = crate::Program::parse_no_errs(cb).unwrap();
let mut options: crate::parsing::ast::types::FormatOptions = Default::default();
options.insert_final_newline = false;
program.ast.recast(&options, 0)
})

View File

@ -3,8 +3,8 @@ mod test_examples_show {
#[tokio::test(flavor = "multi_thread")]
async fn test_mock_example_show0() {
let program =
crate::Program::parse("This is another code block.\nyes sirrr.\nshow").unwrap();
let ctx = crate::executor::ExecutorContext {
crate::Program::parse_no_errs("This is another code block.\nyes sirrr.\nshow").unwrap();
let ctx = crate::ExecutorContext {
engine: std::sync::Arc::new(Box::new(
crate::engine::conn_mock::EngineConnection::new()
.await
@ -13,9 +13,9 @@ mod test_examples_show {
fs: std::sync::Arc::new(crate::fs::FileManager::new()),
stdlib: std::sync::Arc::new(crate::std::StdLib::new()),
settings: Default::default(),
context_type: crate::executor::ContextType::Mock,
context_type: crate::execution::ContextType::Mock,
};
ctx.run(&program, &mut crate::ExecState::default())
ctx.run(program.into(), &mut crate::ExecState::default())
.await
.unwrap();
}
@ -23,10 +23,13 @@ mod test_examples_show {
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
async fn kcl_test_example_show0() {
let code = "This is another code block.\nyes sirrr.\nshow";
let result =
crate::test_server::execute_and_snapshot(code, crate::settings::types::UnitLength::Mm)
.await
.unwrap();
let result = crate::test_server::execute_and_snapshot(
code,
crate::settings::types::UnitLength::Mm,
None,
)
.await
.unwrap();
twenty_twenty::assert_image(
&format!("tests/outputs/{}.png", "serial_test_example_show0"),
&result,
@ -36,8 +39,9 @@ mod test_examples_show {
#[tokio::test(flavor = "multi_thread")]
async fn test_mock_example_show1() {
let program = crate::Program::parse("This is code.\nIt does other shit.\nshow").unwrap();
let ctx = crate::executor::ExecutorContext {
let program =
crate::Program::parse_no_errs("This is code.\nIt does other shit.\nshow").unwrap();
let ctx = crate::ExecutorContext {
engine: std::sync::Arc::new(Box::new(
crate::engine::conn_mock::EngineConnection::new()
.await
@ -46,9 +50,9 @@ mod test_examples_show {
fs: std::sync::Arc::new(crate::fs::FileManager::new()),
stdlib: std::sync::Arc::new(crate::std::StdLib::new()),
settings: Default::default(),
context_type: crate::executor::ContextType::Mock,
context_type: crate::execution::ContextType::Mock,
};
ctx.run(&program, &mut crate::ExecState::default())
ctx.run(program.into(), &mut crate::ExecState::default())
.await
.unwrap();
}
@ -56,10 +60,13 @@ mod test_examples_show {
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
async fn kcl_test_example_show1() {
let code = "This is code.\nIt does other shit.\nshow";
let result =
crate::test_server::execute_and_snapshot(code, crate::settings::types::UnitLength::Mm)
.await
.unwrap();
let result = crate::test_server::execute_and_snapshot(
code,
crate::settings::types::UnitLength::Mm,
None,
)
.await
.unwrap();
twenty_twenty::assert_image(
&format!("tests/outputs/{}.png", "serial_test_example_show1"),
&result,
@ -78,12 +85,12 @@ pub(crate) struct Show {}
#[doc = "Std lib function: show\nThis is some function.\nIt does shit."]
pub(crate) const Show: Show = Show {};
fn boxed_show(
exec_state: &mut crate::executor::ExecState,
exec_state: &mut crate::ExecState,
args: crate::std::Args,
) -> std::pin::Pin<
Box<
dyn std::future::Future<
Output = anyhow::Result<crate::executor::KclValue, crate::errors::KclError>,
Output = anyhow::Result<crate::execution::KclValue, crate::errors::KclError>,
> + Send
+ '_,
>,
@ -108,6 +115,10 @@ impl crate::docs::StdLibFn for Show {
vec![]
}
fn keyword_arguments(&self) -> bool {
false
}
fn args(&self, inline_subschemas: bool) -> Vec<crate::docs::StdLibFnArg> {
let mut settings = schemars::gen::SchemaSettings::openapi3();
settings.inline_subschemas = inline_subschemas;
@ -117,6 +128,7 @@ impl crate::docs::StdLibFn for Show {
type_: "[number]".to_string(),
schema: generator.root_schema_for::<[f64; 2usize]>(),
required: true,
label_required: true,
}]
}
@ -130,6 +142,7 @@ impl crate::docs::StdLibFn for Show {
type_: "number".to_string(),
schema,
required: true,
label_required: true,
})
}
@ -149,8 +162,8 @@ impl crate::docs::StdLibFn for Show {
code_blocks
.iter()
.map(|cb| {
let program = crate::Program::parse(cb).unwrap();
let mut options: crate::ast::types::FormatOptions = Default::default();
let program = crate::Program::parse_no_errs(cb).unwrap();
let mut options: crate::parsing::ast::types::FormatOptions = Default::default();
options.insert_final_newline = false;
program.ast.recast(&options, 0)
})

View File

@ -2,8 +2,9 @@
mod test_examples_show {
#[tokio::test(flavor = "multi_thread")]
async fn test_mock_example_show0() {
let program = crate::Program::parse("This is code.\nIt does other shit.\nshow").unwrap();
let ctx = crate::executor::ExecutorContext {
let program =
crate::Program::parse_no_errs("This is code.\nIt does other shit.\nshow").unwrap();
let ctx = crate::ExecutorContext {
engine: std::sync::Arc::new(Box::new(
crate::engine::conn_mock::EngineConnection::new()
.await
@ -12,9 +13,9 @@ mod test_examples_show {
fs: std::sync::Arc::new(crate::fs::FileManager::new()),
stdlib: std::sync::Arc::new(crate::std::StdLib::new()),
settings: Default::default(),
context_type: crate::executor::ContextType::Mock,
context_type: crate::execution::ContextType::Mock,
};
ctx.run(&program, &mut crate::ExecState::default())
ctx.run(program.into(), &mut crate::ExecState::default())
.await
.unwrap();
}
@ -22,10 +23,13 @@ mod test_examples_show {
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
async fn kcl_test_example_show0() {
let code = "This is code.\nIt does other shit.\nshow";
let result =
crate::test_server::execute_and_snapshot(code, crate::settings::types::UnitLength::Mm)
.await
.unwrap();
let result = crate::test_server::execute_and_snapshot(
code,
crate::settings::types::UnitLength::Mm,
None,
)
.await
.unwrap();
twenty_twenty::assert_image(
&format!("tests/outputs/{}.png", "serial_test_example_show0"),
&result,
@ -44,12 +48,12 @@ pub(crate) struct Show {}
#[doc = "Std lib function: show\nThis is some function.\nIt does shit."]
pub(crate) const Show: Show = Show {};
fn boxed_show(
exec_state: &mut crate::executor::ExecState,
exec_state: &mut crate::ExecState,
args: crate::std::Args,
) -> std::pin::Pin<
Box<
dyn std::future::Future<
Output = anyhow::Result<crate::executor::KclValue, crate::errors::KclError>,
Output = anyhow::Result<crate::execution::KclValue, crate::errors::KclError>,
> + Send
+ '_,
>,
@ -74,6 +78,10 @@ impl crate::docs::StdLibFn for Show {
vec![]
}
fn keyword_arguments(&self) -> bool {
false
}
fn args(&self, inline_subschemas: bool) -> Vec<crate::docs::StdLibFnArg> {
let mut settings = schemars::gen::SchemaSettings::openapi3();
settings.inline_subschemas = inline_subschemas;
@ -83,6 +91,7 @@ impl crate::docs::StdLibFn for Show {
type_: "number".to_string(),
schema: generator.root_schema_for::<f64>(),
required: true,
label_required: true,
}]
}
@ -96,6 +105,7 @@ impl crate::docs::StdLibFn for Show {
type_: "number".to_string(),
schema,
required: true,
label_required: true,
})
}
@ -112,8 +122,8 @@ impl crate::docs::StdLibFn for Show {
code_blocks
.iter()
.map(|cb| {
let program = crate::Program::parse(cb).unwrap();
let mut options: crate::ast::types::FormatOptions = Default::default();
let program = crate::Program::parse_no_errs(cb).unwrap();
let mut options: crate::parsing::ast::types::FormatOptions = Default::default();
options.insert_final_newline = false;
program.ast.recast(&options, 0)
})

View File

@ -3,8 +3,9 @@ mod test_examples_my_func {
#[tokio::test(flavor = "multi_thread")]
async fn test_mock_example_my_func0() {
let program =
crate::Program::parse("This is another code block.\nyes sirrr.\nmyFunc").unwrap();
let ctx = crate::executor::ExecutorContext {
crate::Program::parse_no_errs("This is another code block.\nyes sirrr.\nmyFunc")
.unwrap();
let ctx = crate::ExecutorContext {
engine: std::sync::Arc::new(Box::new(
crate::engine::conn_mock::EngineConnection::new()
.await
@ -13,9 +14,9 @@ mod test_examples_my_func {
fs: std::sync::Arc::new(crate::fs::FileManager::new()),
stdlib: std::sync::Arc::new(crate::std::StdLib::new()),
settings: Default::default(),
context_type: crate::executor::ContextType::Mock,
context_type: crate::execution::ContextType::Mock,
};
ctx.run(&program, &mut crate::ExecState::default())
ctx.run(program.into(), &mut crate::ExecState::default())
.await
.unwrap();
}
@ -23,10 +24,13 @@ mod test_examples_my_func {
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
async fn kcl_test_example_my_func0() {
let code = "This is another code block.\nyes sirrr.\nmyFunc";
let result =
crate::test_server::execute_and_snapshot(code, crate::settings::types::UnitLength::Mm)
.await
.unwrap();
let result = crate::test_server::execute_and_snapshot(
code,
crate::settings::types::UnitLength::Mm,
None,
)
.await
.unwrap();
twenty_twenty::assert_image(
&format!("tests/outputs/{}.png", "serial_test_example_my_func0"),
&result,
@ -36,8 +40,9 @@ mod test_examples_my_func {
#[tokio::test(flavor = "multi_thread")]
async fn test_mock_example_my_func1() {
let program = crate::Program::parse("This is code.\nIt does other shit.\nmyFunc").unwrap();
let ctx = crate::executor::ExecutorContext {
let program =
crate::Program::parse_no_errs("This is code.\nIt does other shit.\nmyFunc").unwrap();
let ctx = crate::ExecutorContext {
engine: std::sync::Arc::new(Box::new(
crate::engine::conn_mock::EngineConnection::new()
.await
@ -46,9 +51,9 @@ mod test_examples_my_func {
fs: std::sync::Arc::new(crate::fs::FileManager::new()),
stdlib: std::sync::Arc::new(crate::std::StdLib::new()),
settings: Default::default(),
context_type: crate::executor::ContextType::Mock,
context_type: crate::execution::ContextType::Mock,
};
ctx.run(&program, &mut crate::ExecState::default())
ctx.run(program.into(), &mut crate::ExecState::default())
.await
.unwrap();
}
@ -56,10 +61,13 @@ mod test_examples_my_func {
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
async fn kcl_test_example_my_func1() {
let code = "This is code.\nIt does other shit.\nmyFunc";
let result =
crate::test_server::execute_and_snapshot(code, crate::settings::types::UnitLength::Mm)
.await
.unwrap();
let result = crate::test_server::execute_and_snapshot(
code,
crate::settings::types::UnitLength::Mm,
None,
)
.await
.unwrap();
twenty_twenty::assert_image(
&format!("tests/outputs/{}.png", "serial_test_example_my_func1"),
&result,
@ -78,12 +86,12 @@ pub(crate) struct MyFunc {}
#[doc = "Std lib function: myFunc\nThis is some function.\nIt does shit."]
pub(crate) const MyFunc: MyFunc = MyFunc {};
fn boxed_my_func(
exec_state: &mut crate::executor::ExecState,
exec_state: &mut crate::ExecState,
args: crate::std::Args,
) -> std::pin::Pin<
Box<
dyn std::future::Future<
Output = anyhow::Result<crate::executor::KclValue, crate::errors::KclError>,
Output = anyhow::Result<crate::execution::KclValue, crate::errors::KclError>,
> + Send
+ '_,
>,
@ -108,6 +116,10 @@ impl crate::docs::StdLibFn for MyFunc {
vec![]
}
fn keyword_arguments(&self) -> bool {
false
}
fn args(&self, inline_subschemas: bool) -> Vec<crate::docs::StdLibFnArg> {
let mut settings = schemars::gen::SchemaSettings::openapi3();
settings.inline_subschemas = inline_subschemas;
@ -117,6 +129,7 @@ impl crate::docs::StdLibFn for MyFunc {
type_: "kittycad::types::InputFormat".to_string(),
schema: generator.root_schema_for::<Option<kittycad::types::InputFormat>>(),
required: false,
label_required: true,
}]
}
@ -130,6 +143,7 @@ impl crate::docs::StdLibFn for MyFunc {
type_: "[Sketch]".to_string(),
schema,
required: true,
label_required: true,
})
}
@ -149,8 +163,8 @@ impl crate::docs::StdLibFn for MyFunc {
code_blocks
.iter()
.map(|cb| {
let program = crate::Program::parse(cb).unwrap();
let mut options: crate::ast::types::FormatOptions = Default::default();
let program = crate::Program::parse_no_errs(cb).unwrap();
let mut options: crate::parsing::ast::types::FormatOptions = Default::default();
options.insert_final_newline = false;
program.ast.recast(&options, 0)
})

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