Compare commits

...

34 Commits

Author SHA1 Message Date
30afa65ccf Cut release v0.27.0 (#4516)
* Cut release v0.26.6

* Cut release v0.27.0
2024-11-20 10:09:53 -06:00
a2f9e70d18 breaking change: Change "type" to be a keyword, import() "type:" parameter to "format:" (#4517) 2024-11-20 14:53:37 +00:00
986675fe89 Fix formatting for nested function returns (#4518)
Previously, this was the output of the formatter:

```
fn f = () => {
  return () => {
  return 1
}
}
```

Now the above will be reformatted as

```
fn f = () => {
  return () => {
    return 1
  }
}
```

Much better!
2024-11-20 09:23:30 -05:00
d8ce5ad8bd Make most top-level modules in KCL private (#4478)
* Make ast module private

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

* Make most other modules private

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

* Expand API to support CLI, Python bindings, and LSP crate

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

---------

Signed-off-by: Nick Cameron <nrc@ncameron.org>
2024-11-20 15:19:25 +13:00
1a9926be8a Fix to not have unneeded console errors (#4482)
* Fix to not have unneeded console errors

* Change to use a type that isn't string
2024-11-19 17:34:54 -05:00
max
54b5774f9e Add Support for Fillet with Extrude in the Sketch Pipe (#4168)
* update code mod

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

* fmt

* lint

* make yarn-tsc happy

* fmt

* typo

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-11-19 21:30:26 +01:00
66bbbf81e2 Pass current file name through to export command (#4503)
* Pass current file name through to export command

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

* Oops I needed a couple other things, not just that one line change

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

* Undo overriding of internal zipped file names
That was liable to cause conflicts and whatnot per @jessfraz feedback

* Update E2E test that was still looking for `output.gltf`

* Missed one other test my bad

* Should've just grepped for output.gltf to begin with

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Paul Tagliamonte <paul@zoo.dev>
2024-11-19 11:30:23 -05:00
652519aeae fix: center rectangle code writing bug (#4512)
fix: bug that did not write the code to the editor when the workflow finished
2024-11-19 11:10:08 -05:00
f826afb32d Clear code mirror history on file change (#4510)
* clear history when loading a new file

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>

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2024-11-19 02:08:22 +00:00
f71fafdece breaking change: Add more KCL reserved words, part 1 (#4502) 2024-11-19 00:54:25 +00:00
16b7544d69 fix missing docs files (#4509)
* fix missing docs files

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

* fix justfile

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

* fix justfile

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

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2024-11-18 22:58:33 +00:00
34f019305b Move tests into simulation (#4487)
Replace wasm-lib integration tests with KCL simulation tests.
2024-11-18 22:20:32 +00:00
79e06b3a00 Nadro/adhoc/stdlib arcTo (three point arc) (#4485)
* feat: implementing arcTo in standard library, first pass

* feat: computing center and radius for arcto

* fix: updating comment for arcTo

* fix: cargo fmt fix

* fix: bug, the x was used twice!

* fix: Cleaning up some code and adding more comments

* fix: this has to be removed

* fix: resolved merge conflicts with main and updated the codebase to remove the JSON stuff

* fix: addressing cargo clippy issues

* fix: typos

* fix: adding generated docs

* Update doc test snapshots

---------

Co-authored-by: Jonathan Tran <jonnytran@gmail.com>
2024-11-18 22:17:16 +00:00
24bc4fcd8c Show offset planes in the scene, let user select them (#4481)
* Update offset_plane to actually create and show the plane in-engine

* Fix broken ability to use offsetPlanes in startSketchOn

* Make the newly-visible offset planes usable for sketching via UI

* Add a playwright test for sketching on an offset plane via point-and-click

* cargo clippy & cargo fmt

* Make `PlaneData` the first item of `SketchData` so autocomplete continues to work well for `startSketchOn`

* @nadr0 feedback re: `offsetIndex`

* From @jtran: "Need to call the ID generator so that IDs are stable."

* More feedback from @jtran and fix incomplete use of `id_generator` in last commit

* Oops I missed saving `isPathToNodeNumber` earlier 🤦🏻

* Make the distinction between `Plane` and `PlaneOrientationData` more clear per @nadr0 and @lf94's feedback

* Make `newPathToNode` less hardcoded, per @lf94's feedback

* Don't need to unbox and rebox `plane`

* 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)

* Rearranging of enums and structs, but the offsetPlanes are still not used by their sketches

* 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)

* Revert all my little newtype fiddling it's a waste of time.

* Update docs

* cargo fmt

* Remove log

* Print the unexpected diagnostics

* Undo renaming of `PlaneData`

* Remove generated PlaneRientationData docs page

* Redo doc generation after undoing `PlaneData` rename

* Impl FromKclValue for the new plane datatypes

* Clippy lint

* When starting a sketch, only hide the plane if it's a custom plane

* Fix FromKclValue and macro use since merge

* Fix to not convert Plane to PlaneData

* Make sure offset planes are `Custom` type

* SketchData actually doesn't need to be in a certain order
This avoids the autocompletion issue I was having.

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Adam Chalmers <adam.chalmers@zoo.dev>
Co-authored-by: Jonathan Tran <jonnytran@gmail.com>
2024-11-18 16:25:25 -05:00
97b9529c81 doc: Add more docs and links for KCL import (#4488)
* Add links in docs to KCL module documentation

* Add import statement test in import() stdlib doc comments

* Update doc test outputs

* Update doc outputs
2024-11-18 18:47:24 +00:00
8e5fc02941 Revert electron-updater to 6.3.0 (Linux AppImage updater fix) (#4501)
* WIP: Fix linux updater 'mv' error

First commit to test the updater-test builds

* Downgrade electron-updater to 6.3.0

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

* Trigger CI

* Pin to 6.3.0

* Clean up

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-11-18 18:31:45 +00:00
909690f3c7 Better reduce docs (#4493)
Based on conversation with Josh and Jordan earlier this week.
2024-11-18 10:34:57 -06:00
14ba66378d Nadro/adhoc/center rectangle (#4480)
* fix: big commit, doing this to save work then do a PR cleanup; center rectangle

* fix: making a center function for each scenario

* fix: reverting the update rectangle code since I have a center rectangle one

* fix: does not allow seletcing circle or rectangle tool while selecting a face

* chore: adding comment to better read the HTML

* fix: cleaning up for PR

* fix: pushing broken code for someone to checkout

* fix: fixed the typescript issues, removed the as keyword for my center rectangle expressions

* fix: removing comment

* fix: removed as for type narrowing checks
2024-11-18 09:04:09 -06:00
b2e895e508 Fix finding path to node for import and if-else (#4483) 2024-11-17 18:03:21 -05:00
05f4f34269 Do not write to file or update code editor a ridiculous amount of times and update them both at the most appropriate moments. (#4479)
* Reapply "Deflake project settings override on desktop (#4370)" (#4450)

This reverts commit b11040c23c.

* Refactor writeToFile and updateCodeEditor to happen at appropriate times

* Turn error into warning about out of date AST.

* Rename setUp to setup

* ONLY reload current file on changes.

* If value is falsey then don't try to executeAst

* Fix up code based selections after constraints

* Correct any last missing code mods

* Update src/clientSideScene/ClientSideSceneComp.tsx

Remove eslint rule no-floating-promises

Co-authored-by: Jonathan Tran <jonnytran@gmail.com>

* Fixups

* Fix FileTree failing

---------

Co-authored-by: Jonathan Tran <jonnytran@gmail.com>
2024-11-16 21:49:44 +00:00
fbb7b08b62 Refactor: Clean up FromKclValue code (#4489)
* Rename ::from_mem_item to ::from_kcl_val

The old name is from a dark time lost to human memory,
when we had an enum called MemoryItem.

* Simplify the FromKclValue macros

There's only really need for one macro, not three.
2024-11-16 12:19:00 -05:00
16c7a2457a Add docs for modules and import statements (#4435) 2024-11-15 11:36:38 -05:00
c429bc6ed7 chore: implemented a O(n) unique and added unit tests (#4429) 2024-11-15 09:09:16 -06:00
a0493cb332 Divorce JSON and KCL (#4436)
Removes JSON from the KCL object model. Closes https://github.com/KittyCAD/modeling-app/issues/1130 -- it was filed on Nov 27 last year. Hopefully I close it before its one year anniversary.

Changes:

- Removed the UserVal variant from `enum KclValue`. That variant held JSON data.
- Replaced it with several new variants like Number, String, Array (of KCL values), Object (where keys are String and values are KCL values)
- Added a dedicated Sketch variant to KclValue. We used to have a variant like this, but I removed it as an experimental approach to fix this issue. Eventually I decided to undo it and use the approach of this PR instead.
- Removed the `impl_from_arg_via_json` macro, which implemented conversion from KclValue to Rust types by matching the KclValue to its UserVal variant, grabbing the JSON, then deserializing that into the desired Rust type. 
- Instead, replaced it with manual conversion from KclValue to Rust types, using some convenience macros like `field!`
2024-11-14 17:27:19 -06:00
b798f7da03 Add basic horizontal and vertical snapping for Line tool (#4465)
* Add on-click snapping behavior

* Fix math error with horizontal/vertical determination

* Move snap constant to where other sketch constants are

* fmt

* Fixing up some of the tests, some still need updating

* A couple other little PW test issues

* 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)

* Fix remaining tests that need updating

* Make `yarn lint` happy

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-11-13 09:41:27 -05:00
c5d051855f Fix description of angleToMatchLengthX (#4467)
* fix description of angleToMatchLengthX

* Update docs

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

* update doc regen instructions

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

* Re-run CI after snapshots

---------

Co-authored-by: Adam Chalmers <adam.chalmers@zoo.dev>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Frank Noirot <frank@kittycad.io>
2024-11-13 09:04:35 -05:00
0b24216dc5 Cut release v0.26.5 (#4477) 2024-11-12 22:05:56 -05:00
2d3841bf61 Bug fix: fix autocompletion regression (#4476)
* Add final error-less expectation to autocomplete test

* Fix failing test

* tweak to leave function in place

---------

Co-authored-by: Kurt Hutten Irev-Dev <k.hutten@protonmail.ch>
2024-11-13 12:01:47 +11:00
51a71a180e Internal: Pass through sweep.subType in expandSweep (#4383)
* Internal: Pass through `sweep.subType` in `expandSweep`

* 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)

* implement a simple startSketchOn / offsetPlane lint rule (#4384)

* Bump kittycad-modeling-cmds from 0.2.71 to 0.2.72 in /src/wasm-lib (#4356)

Bumps [kittycad-modeling-cmds](https://github.com/KittyCAD/modeling-api) from 0.2.71 to 0.2.72.
- [Commits](https://github.com/KittyCAD/modeling-api/compare/kittycad-modeling-cmds-0.2.71...kittycad-modeling-cmds-0.2.72)

---
updated-dependencies:
- dependency-name: kittycad-modeling-cmds
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* Bump google-github-actions/upload-cloud-storage from 2.2.0 to 2.2.1 (#4364)

Bumps [google-github-actions/upload-cloud-storage](https://github.com/google-github-actions/upload-cloud-storage) from 2.2.0 to 2.2.1.
- [Release notes](https://github.com/google-github-actions/upload-cloud-storage/releases)
- [Changelog](https://github.com/google-github-actions/upload-cloud-storage/blob/main/CHANGELOG.md)
- [Commits](https://github.com/google-github-actions/upload-cloud-storage/compare/v2.2.0...v2.2.1)

---
updated-dependencies:
- dependency-name: google-github-actions/upload-cloud-storage
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: 49fl <ircsurfer33@gmail.com>

* Internal fix: make `expandPath` not assume path has associated sweep (#4386)

* Add a test that shows current error within `expandPath`

* Make `expandPath` not assume there is an associated sweep artifact

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

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>

* Bump happy-dom from 14.12.3 to 15.10.1 (#4404)

Bumps [happy-dom](https://github.com/capricorn86/happy-dom) from 14.12.3 to 15.10.1.
- [Release notes](https://github.com/capricorn86/happy-dom/releases)
- [Commits](https://github.com/capricorn86/happy-dom/compare/v14.12.3...v15.10.1)

---
updated-dependencies:
- dependency-name: happy-dom
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* Bump @electron-forge/maker-zip from 7.4.0 to 7.5.0 (#4394)

Bumps [@electron-forge/maker-zip](https://github.com/electron/forge) from 7.4.0 to 7.5.0.
- [Release notes](https://github.com/electron/forge/releases)
- [Changelog](https://github.com/electron/forge/blob/main/CHANGELOG.md)
- [Commits](https://github.com/electron/forge/compare/v7.4.0...v7.5.0)

---
updated-dependencies:
- dependency-name: "@electron-forge/maker-zip"
  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>

* Bump uuid from 9.0.1 to 11.0.2 (#4393)

Bumps [uuid](https://github.com/uuidjs/uuid) from 9.0.1 to 11.0.2.
- [Release notes](https://github.com/uuidjs/uuid/releases)
- [Changelog](https://github.com/uuidjs/uuid/blob/main/CHANGELOG.md)
- [Commits](https://github.com/uuidjs/uuid/compare/v9.0.1...v11.0.2)

---
updated-dependencies:
- dependency-name: uuid
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* Bump validator from 0.18.1 to 0.19.0 in /src/wasm-lib (#4396)

Bumps [validator](https://github.com/Keats/validator) from 0.18.1 to 0.19.0.
- [Changelog](https://github.com/Keats/validator/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Keats/validator/commits)

---
updated-dependencies:
- dependency-name: validator
  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>

* Bump insta from 1.41.0 to 1.41.1 in /src/wasm-lib (#4400)

Bumps [insta](https://github.com/mitsuhiko/insta) from 1.41.0 to 1.41.1.
- [Release notes](https://github.com/mitsuhiko/insta/releases)
- [Changelog](https://github.com/mitsuhiko/insta/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mitsuhiko/insta/compare/1.41.0...1.41.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* Bump pyo3 from 0.22.5 to 0.22.6 in /src/wasm-lib (#4398)

Bumps [pyo3](https://github.com/pyo3/pyo3) from 0.22.5 to 0.22.6.
- [Release notes](https://github.com/pyo3/pyo3/releases)
- [Changelog](https://github.com/PyO3/pyo3/blob/v0.22.6/CHANGELOG.md)
- [Commits](https://github.com/pyo3/pyo3/compare/v0.22.5...v0.22.6)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* Bump thiserror from 1.0.65 to 2.0.0 in /src/wasm-lib (#4397)

Bumps [thiserror](https://github.com/dtolnay/thiserror) from 1.0.65 to 2.0.0.
- [Release notes](https://github.com/dtolnay/thiserror/releases)
- [Commits](https://github.com/dtolnay/thiserror/compare/1.0.65...2.0.0)

---
updated-dependencies:
- dependency-name: thiserror
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* Bump url from 2.5.2 to 2.5.3 in /src/wasm-lib (#4399)

Bumps [url](https://github.com/servo/rust-url) from 2.5.2 to 2.5.3.
- [Release notes](https://github.com/servo/rust-url/releases)
- [Commits](https://github.com/servo/rust-url/compare/v2.5.2...v2.5.3)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* Bump @types/ws from 8.5.12 to 8.5.13 (#4395)

Bumps [@types/ws](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/ws) from 8.5.12 to 8.5.13.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/ws)

---
updated-dependencies:
- dependency-name: "@types/ws"
  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>

* File tree to act more like VS Code's file tree (#4392)

* File tree acts as VS Code's file tree

* Adjust test for new expectations

* Remove screenshot

* Actually remove this screenshot

* 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: 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)

* 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)

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>

* Fixing directory/file selection logic to deselect folders properly and always hightlight files.  (#4408)

* File tree acts as VS Code's file tree

* Adjust test for new expectations

* Remove screenshot

* Actually remove this screenshot

* 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: ubuntu-latest-8-cores)

* fix: fixed logic for selection

* fix: always show the current file

---------

Co-authored-by: 49lf <ircsurfer33@gmail.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>

* Bump happy-dom from 15.10.1 to 15.10.2 (#4409)

Bumps [happy-dom](https://github.com/capricorn86/happy-dom) from 15.10.1 to 15.10.2.
- [Release notes](https://github.com/capricorn86/happy-dom/releases)
- [Commits](https://github.com/capricorn86/happy-dom/compare/v15.10.1...v15.10.2)

---
updated-dependencies:
- dependency-name: happy-dom
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* Change Dependabot PR frequency to weekly (#4424)

* Update insta snapshots (#4423)

No meaningful changes, they just added a field to the frontmatter

* Fix KCL source ranges to know which source file they point to (#4418)

* Add ts_rs feature to work with indexmap

* Add feature for schemars to work with indexmap

* Add module ID to intern module paths

* Update code to use new source range with three fields

* Update generated files

* Update docs

* Fix wasm

* Fix TS code to use new SourceRange

* Fix TS tests to use new SourceRange and moduleId

* Fix formatting

* Fix to filter errors and source ranges to only show the top-level module

* Fix to reuse module IDs

* Fix to disallow empty path for import

* Revert unneeded Self change

* Rename field to be clearer

* Fix parser tests

* Update snapshots

* Change to not serialize module_id of 0

* Update snapshots after adding default module_id

* Move module_id functions to separate module

* Fix tests for console errors

* Proposal: module ID = 0 gets skipped when serializing tokens too (#4422)

Just like in AST nodes.

Also I think "is_top_level" communicates intention better than is_default

---------

Co-authored-by: Adam Chalmers <adam.chalmers@zoo.dev>

* Bump anyhow from 1.0.92 to 1.0.93 in /src/wasm-lib (#4417)

Bumps [anyhow](https://github.com/dtolnay/anyhow) from 1.0.92 to 1.0.93.
- [Release notes](https://github.com/dtolnay/anyhow/releases)
- [Commits](https://github.com/dtolnay/anyhow/compare/1.0.92...1.0.93)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* Cut release v0.26.3 (#4427)

* Cut release v0.26.3

* Support new electron-builder env variable requirements for building in release mode

* Bump image from 0.25.3 to 0.25.5 in /src/wasm-lib (#4416)

Bumps [image](https://github.com/image-rs/image) from 0.25.3 to 0.25.5.
- [Changelog](https://github.com/image-rs/image/blob/main/CHANGES.md)
- [Commits](https://github.com/image-rs/image/compare/v0.25.3...v0.25.5)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* Nadro/3799/perf (#4145)

* chore: building out perf testing

* chore: adding my printing code for the different formats of the marks

* feat: adding invocation count table

* fix: markOnce iunstead

* fix: typescript additions

* fix: adding more types

* chore: adding telemetry panel as MVP, gonna remove the pane

* chore: view telemetry from command bar in file route and home route

* fix: deleting unused imports

* fix: deleting some unused files

* fix: auto cleanup

* chore: adding other routes, these will need to be moved...

* chore: moving some printing logic around and unit testing some of it

* fix: moving command init

* fix: removing debugging marks

* fix: adding some comments

* fix: fixed a bug with generating the go to page commands

* chore: adding will pages load within the router config

* chore: implementing marks for routes

* fix: auto fixes and checkers

* chore: implemented a route watcher at the root level...

* fix: auto fixes, removing unused code

* chore: timing for syntax highlighting and auto fixes

* fix: didAuth issue and syntax highlighting in the packaged application. Constructor name gets renamed

* fix: fixing typescript checks

* chore: adding mag bar chart icon for telemetry

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

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

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

* chore: swapped telemetry icon for stopwatch

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

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

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

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

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

* chore: writing telemetry to disk

* fix: auto fixers

* chore: getting args parsed for cli flags and writing telemetry file

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

* chore: swapped mark for markOnce since we infinitely write marks to a JS array... need to solve this run time marking in another way. We only need this for startup right now

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

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

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

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

* chore: writing raw marks to disk as well

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

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

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

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

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

* fix: cleaned up the testing names

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

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

* Fix fmt and codespell

* 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: 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)

* 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)

* fix: moving this route loader data stuff

* chore: adding comment

* fix: fmt

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

* empty :(

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

* empty :(

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: 49fl <ircsurfer33@gmail.com>

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

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: 49fl <ircsurfer33@gmail.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Paul Tagliamonte <paul@zoo.dev>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Kevin Nadro <nadr0@users.noreply.github.com>
Co-authored-by: Jonathan Tran <jonnytran@gmail.com>
Co-authored-by: Adam Chalmers <adam.chalmers@zoo.dev>
2024-11-12 15:49:49 -05:00
1ec25dfe96 Remove references to wasm-dev build (#4449) 2024-11-11 14:58:44 -05:00
8de29dd461 Cut release v0.26.4 (#4452)
* Cut release v0.26.4

* Turn of setting persistence test with `fixme`
2024-11-08 23:49:34 +00:00
b11040c23c Revert "Deflake project settings override on desktop (#4370)" (#4450)
* Revert "Deflake project settings override on desktop (#4370)"

This reverts commit ad1cd56891.

* Part of the revert
2024-11-08 16:16:46 -06:00
2bc4f076cb Fix to run cargo tests when generated files change (#4430) 2024-11-08 15:30:37 -05:00
9e1cf90c81 Release derive-docs (#4446) 2024-11-08 15:23:55 -05:00
393 changed files with 379052 additions and 6103 deletions

View File

@ -5,6 +5,8 @@ on:
paths:
- 'src/wasm-lib/**.rs'
- 'src/wasm-lib/**.hbs'
- 'src/wasm-lib/**.gen'
- 'src/wasm-lib/**.snap'
- '**/Cargo.toml'
- '**/Cargo.lock'
- '**/rust-toolchain.toml'
@ -15,6 +17,8 @@ on:
paths:
- 'src/wasm-lib/**.rs'
- 'src/wasm-lib/**.hbs'
- 'src/wasm-lib/**.gen'
- 'src/wasm-lib/**.snap'
- '**/Cargo.toml'
- '**/Cargo.lock'
- '**/rust-toolchain.toml'

View File

@ -19,7 +19,7 @@ $(XSTATE_TYPEGENS): $(TS_SRC)
yarn xstate typegen 'src/**/*.ts?(x)'
public/wasm_lib_bg.wasm: $(WASM_LIB_FILES)
yarn build:wasm-dev
yarn build:wasm
node_modules: package.json yarn.lock
yarn install

View File

@ -110,7 +110,7 @@ Which commands from setup are one off vs need to be run every time?
The following will need to be run when checking out a new commit and guarantees the build is not stale:
```bash
yarn install
yarn build:wasm-dev # or yarn build:wasm for slower but more production-like build
yarn build:wasm
yarn start # or yarn build:local && yarn serve for slower but more production-like build
```

View File

@ -1,10 +1,10 @@
---
title: "angleToMatchLengthX"
excerpt: "Compute the angle (in degrees) in o"
excerpt: "Returns the angle to match the given length for x."
layout: manual
---
Compute the angle (in degrees) in o
Returns the angle to match the given length for x.

File diff suppressed because one or more lines are too long

41
docs/kcl/arcTo.md Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -7,6 +7,7 @@ layout: manual
## Table of Contents
* [Types](kcl/types)
* [Modules](kcl/modules)
* [Known Issues](kcl/KNOWN-ISSUES)
* [`abs`](kcl/abs)
* [`acos`](kcl/acos)
@ -19,6 +20,7 @@ layout: manual
* [`angledLineToX`](kcl/angledLineToX)
* [`angledLineToY`](kcl/angledLineToY)
* [`arc`](kcl/arc)
* [`arcTo`](kcl/arcTo)
* [`asin`](kcl/asin)
* [`assert`](kcl/assert)
* [`assertEqual`](kcl/assertEqual)

59
docs/kcl/modules.md Normal file
View File

@ -0,0 +1,59 @@
---
title: "KCL Modules"
excerpt: "Documentation of modules for the KCL language for the Zoo Modeling App."
layout: manual
---
`KCL` allows splitting code up into multiple files. Each file is somewhat
isolated from other files as a separate module.
When you define a function, you can use `export` before it to make it available
to other modules.
```
// util.kcl
export fn increment = (x) => {
return x + 1
}
```
Other files in the project can now import functions that have been exported.
This makes them available to use in another file.
```
// main.kcl
import increment from "util.kcl"
answer = increment(41)
```
Imported files _must_ be in the same project so that units are uniform across
modules. This means that it must be in the same directory.
Import statements must be at the top-level of a file. It is not allowed to have
an `import` statement inside a function or in the body of an if-else.
Multiple functions can be exported in a file.
```
// util.kcl
export fn increment = (x) => {
return x + 1
}
export fn decrement = (x) => {
return x - 1
}
```
When importing, you can import multiple functions at once.
```
import increment, decrement from "util.kcl"
```
Imported symbols can be renamed for convenience or to avoid name collisions.
```
import increment as inc, decrement as dec from "util.kcl"
```

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,22 @@
---
title: "ArcToData"
excerpt: "Data to draw a three point arc (arcTo)."
layout: manual
---
Data to draw a three point arc (arcTo).
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `end` |`[number, number]`| End point of the arc. A point in 3D space | No |
| `interior` |`[number, number]`| Interior point of the arc. A point in 3D space | No |

View File

@ -24,7 +24,7 @@ Autodesk Filmbox (FBX) format
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `fbx`| | No |
| `format` |enum: `fbx`| | No |
----
@ -40,7 +40,7 @@ Binary glTF 2.0. We refer to this as glTF since that is how our customers refer
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `gltf`| | No |
| `format` |enum: `gltf`| | No |
----
@ -56,7 +56,7 @@ Wavefront OBJ format.
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `obj`| | No |
| `format` |enum: `obj`| | No |
| `coords` |[`System`](/docs/kcl/types/System)| Co-ordinate system of input data. Defaults to the [KittyCAD co-ordinate system. | No |
| `units` |[`UnitLength`](/docs/kcl/types/UnitLength)| The units of the input data. This is very important for correct scaling and when calculating physics properties like mass, etc. Defaults to millimeters. | No |
@ -74,7 +74,7 @@ The PLY Polygon File Format.
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `ply`| | No |
| `format` |enum: `ply`| | No |
| `coords` |[`System`](/docs/kcl/types/System)| Co-ordinate system of input data. Defaults to the [KittyCAD co-ordinate system. | No |
| `units` |[`UnitLength`](/docs/kcl/types/UnitLength)| The units of the input data. This is very important for correct scaling and when calculating physics properties like mass, etc. Defaults to millimeters. | No |
@ -92,7 +92,7 @@ SolidWorks part (SLDPRT) format.
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `sldprt`| | No |
| `format` |enum: `sldprt`| | No |
----
@ -108,7 +108,7 @@ ISO 10303-21 (STEP) format.
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `step`| | No |
| `format` |enum: `step`| | No |
----
@ -124,7 +124,7 @@ ST**ereo**L**ithography format.
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `stl`| | No |
| `format` |enum: `stl`| | No |
| `coords` |[`System`](/docs/kcl/types/System)| Co-ordinate system of input data. Defaults to the [KittyCAD co-ordinate system. | No |
| `units` |[`UnitLength`](/docs/kcl/types/UnitLength)| The units of the input data. This is very important for correct scaling and when calculating physics properties like mass, etc. Defaults to millimeters. | No |

16
docs/kcl/types/KclNone.md Normal file
View File

@ -0,0 +1,16 @@
---
title: "KclNone"
excerpt: "KCL value for an optional parameter which was not given an argument. (remember, parameters are in the function declaration, arguments are in the function call/application)."
layout: manual
---
KCL value for an optional parameter which was not given an argument. (remember, parameters are in the function declaration, arguments are in the function call/application).
**Type:** `object`

View File

@ -23,8 +23,110 @@ Any KCL value.
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `UserVal`| | No |
| `value` |``| | No |
| `type` |enum: `Uuid`| | No |
| `value` |`string`| | No |
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
----
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `Bool`| | No |
| `value` |`boolean`| | No |
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
----
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `Number`| | No |
| `value` |`number`| | No |
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
----
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `Int`| | No |
| `value` |`integer`| | No |
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
----
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `String`| | No |
| `value` |`string`| | No |
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
----
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `Array`| | No |
| `value` |`[` [`KclValue`](/docs/kcl/types/KclValue) `]`| | No |
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
----
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `Object`| | No |
| `value` |`object`| | No |
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
@ -78,7 +180,7 @@ A plane.
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `Plane`| | No |
| `type` |enum: [`Plane`](/docs/kcl/types/Plane)| | No |
| `id` |`string`| The id of the plane. | No |
| `value` |[`PlaneType`](/docs/kcl/types/PlaneType)| Any KCL value. | No |
| `origin` |[`Point3d`](/docs/kcl/types/Point3d)| Origin of the plane. | No |
@ -111,6 +213,38 @@ A face.
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
----
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: [`Sketch`](/docs/kcl/types/Sketch)| | No |
| `value` |[`Sketch`](/docs/kcl/types/Sketch)| Any KCL value. | No |
----
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `Sketches`| | No |
| `value` |`[` [`Sketch`](/docs/kcl/types/Sketch) `]`| | No |
----
An solid is a collection of extrude surfaces.
@ -190,6 +324,23 @@ Data for an imported geometry.
----
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: [`KclNone`](/docs/kcl/types/KclNone)| | No |
| `value` |[`KclNone`](/docs/kcl/types/KclNone)| Any KCL value. | No |
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
----

27
docs/kcl/types/Plane.md Normal file
View File

@ -0,0 +1,27 @@
---
title: "Plane"
excerpt: "A plane."
layout: manual
---
A plane.
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `id` |`string`| The id of the plane. | No |
| `value` |[`PlaneType`](/docs/kcl/types/PlaneType)| A plane. | No |
| `origin` |[`Point3d`](/docs/kcl/types/Point3d)| Origin of the plane. | No |
| `xAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the planes X axis be? | No |
| `yAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the planes Y axis be? | No |
| `zAxis` |[`Point3d`](/docs/kcl/types/Point3d)| The z-axis (normal). | No |
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |

View File

@ -1,10 +1,10 @@
---
title: "PlaneData"
excerpt: "Data for a plane."
excerpt: "Orientation data that can be used to construct a plane, not a plane in itself."
layout: manual
---
Data for a plane.
Orientation data that can be used to construct a plane, not a plane in itself.

View File

@ -22,6 +22,18 @@ Data for start sketch on. You can start a sketch on a plane or an solid.
----
Data for start sketch on. You can start a sketch on a plane or an solid.
[`Plane`](/docs/kcl/types/Plane)
----
Data for start sketch on. You can start a sketch on a plane or an solid.

View File

@ -67,15 +67,15 @@ async function doBasicSketch(page: Page, openPanes: string[]) {
if (openPanes.includes('code')) {
await expect(u.codeLocator).toHaveText(`sketch001 = startSketchOn('XZ')
|> startProfileAt(${commonPoints.startAt}, %)
|> line([${commonPoints.num1}, 0], %)`)
|> xLine(${commonPoints.num1}, %)`)
}
await page.waitForTimeout(500)
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 20)
if (openPanes.includes('code')) {
await expect(u.codeLocator).toHaveText(`sketch001 = startSketchOn('XZ')
|> startProfileAt(${commonPoints.startAt}, %)
|> line([${commonPoints.num1}, 0], %)
|> line([0, ${commonPoints.num1 + 0.01}], %)`)
|> xLine(${commonPoints.num1}, %)
|> yLine(${commonPoints.num1 + 0.01}, %)`)
} else {
await page.waitForTimeout(500)
}
@ -84,9 +84,9 @@ async function doBasicSketch(page: Page, openPanes: string[]) {
if (openPanes.includes('code')) {
await expect(u.codeLocator).toHaveText(`sketch001 = startSketchOn('XZ')
|> startProfileAt(${commonPoints.startAt}, %)
|> line([${commonPoints.num1}, 0], %)
|> line([0, ${commonPoints.num1 + 0.01}], %)
|> lineTo([0, ${commonPoints.num3}], %)`)
|> xLine(${commonPoints.num1}, %)
|> yLine(${commonPoints.num1 + 0.01}, %)
|> xLine(${commonPoints.num2 * -1}, %)`)
}
// deselect line tool
@ -142,9 +142,9 @@ async function doBasicSketch(page: Page, openPanes: string[]) {
await u.openKclCodePanel()
await expect(u.codeLocator).toHaveText(`sketch001 = startSketchOn('XZ')
|> startProfileAt(${commonPoints.startAt}, %)
|> line([${commonPoints.num1}, 0], %, $seg01)
|> line([0, ${commonPoints.num1 + 0.01}], %)
|> angledLine([180, segLen(seg01)], %)`)
|> xLine(${commonPoints.num1}, %, $seg01)
|> yLine(${commonPoints.num1 + 0.01}, %)
|> xLine(-segLen(seg01), %)`)
}
test.describe('Basic sketch', () => {

View File

@ -62,6 +62,8 @@ test(
const errorToastMessage = page.getByText(`Error while exporting`)
const engineErrorToastMessage = page.getByText(`Nothing to export`)
const alreadyExportingToastMessage = page.getByText(`Already exporting`)
// The open file's name is `main.kcl`, so the export file name should be `main.gltf`
const exportFileName = `main.gltf`
// Click the export button
await exportButton.click()
@ -96,7 +98,7 @@ test(
.poll(
async () => {
try {
const outputGltf = await fsp.readFile('output.gltf')
const outputGltf = await fsp.readFile(exportFileName)
return outputGltf.byteLength
} catch (e) {
return 0
@ -106,8 +108,8 @@ test(
)
.toBeGreaterThan(300_000)
// clean up output.gltf
await fsp.rm('output.gltf')
// clean up exported file
await fsp.rm(exportFileName)
})
})
@ -138,6 +140,8 @@ test(
const errorToastMessage = page.getByText(`Error while exporting`)
const engineErrorToastMessage = page.getByText(`Nothing to export`)
const alreadyExportingToastMessage = page.getByText(`Already exporting`)
// The open file's name is `other.kcl`, so the export file name should be `other.gltf`
const exportFileName = `other.gltf`
// Click the export button
await exportButton.click()
@ -171,7 +175,7 @@ test(
.poll(
async () => {
try {
const outputGltf = await fsp.readFile('output.gltf')
const outputGltf = await fsp.readFile(exportFileName)
return outputGltf.byteLength
} catch (e) {
return 0
@ -181,8 +185,8 @@ test(
)
.toBeGreaterThan(100_000)
// clean up output.gltf
await fsp.rm('output.gltf')
// clean up exported file
await fsp.rm(exportFileName)
})
await electronApp.close()
})

View File

@ -694,6 +694,9 @@ test.describe('Editor tests', () => {
.toHaveText(`sketch001 = startSketchOn('XZ')
|> startProfileAt([3.14, 12], %)
|> xLine(5, %) // lin`)
// expect there to be no KCL errors
await expect(page.locator('.cm-lint-marker-error')).toHaveCount(0)
})
test('with tab to accept the completion', async ({ page }) => {

View File

@ -1135,3 +1135,189 @@ _test.describe('Deleting items from the file pane', () => {
}
)
})
_test.describe(
'Undo and redo do not keep history when navigating between files',
() => {
_test(
`open a file, change something, open a different file, hitting undo should do nothing`,
{ tag: '@electron' },
async ({ browserName }, testInfo) => {
const { page } = await setupElectron({
testInfo,
folderSetupFn: async (dir) => {
const testDir = join(dir, 'testProject')
await fsp.mkdir(testDir, { recursive: true })
await fsp.copyFile(
executorInputPath('cylinder.kcl'),
join(testDir, 'main.kcl')
)
await fsp.copyFile(
executorInputPath('basic_fillet_cube_end.kcl'),
join(testDir, 'other.kcl')
)
},
})
const u = await getUtils(page)
await page.setViewportSize({ width: 1200, height: 500 })
page.on('console', console.log)
// Constants and locators
const projectCard = page.getByText('testProject')
const otherFile = page
.getByRole('listitem')
.filter({ has: page.getByRole('button', { name: 'other.kcl' }) })
await _test.step(
'Open project and make a change to the file',
async () => {
await projectCard.click()
await u.waitForPageLoad()
// Get the text in the code locator.
const originalText = await u.codeLocator.innerText()
// Click in the editor and add some new lines.
await u.codeLocator.click()
await page.keyboard.type(`sketch001 = startSketchOn('XY')
some other shit`)
// Ensure the content in the editor changed.
const newContent = await u.codeLocator.innerText()
expect(originalText !== newContent)
}
)
await _test.step('navigate to other.kcl', async () => {
await u.openFilePanel()
await otherFile.click()
await u.waitForPageLoad()
await u.openKclCodePanel()
await _expect(u.codeLocator).toContainText('getOppositeEdge(thing)')
})
await _test.step('hit undo', async () => {
// Get the original content of the file.
const originalText = await u.codeLocator.innerText()
// Now hit undo
await page.keyboard.down('ControlOrMeta')
await page.keyboard.press('KeyZ')
await page.keyboard.up('ControlOrMeta')
await page.waitForTimeout(100)
await expect(u.codeLocator).toContainText(originalText)
})
}
)
_test(
`open a file, change something, undo it, open a different file, hitting redo should do nothing`,
{ tag: '@electron' },
// Skip on windows i think the keybindings are different for redo.
async ({ browserName }, testInfo) => {
test.skip(process.platform === 'win32', 'Skip on windows')
const { page } = await setupElectron({
testInfo,
folderSetupFn: async (dir) => {
const testDir = join(dir, 'testProject')
await fsp.mkdir(testDir, { recursive: true })
await fsp.copyFile(
executorInputPath('cylinder.kcl'),
join(testDir, 'main.kcl')
)
await fsp.copyFile(
executorInputPath('basic_fillet_cube_end.kcl'),
join(testDir, 'other.kcl')
)
},
})
const u = await getUtils(page)
await page.setViewportSize({ width: 1200, height: 500 })
page.on('console', console.log)
// Constants and locators
const projectCard = page.getByText('testProject')
const otherFile = page
.getByRole('listitem')
.filter({ has: page.getByRole('button', { name: 'other.kcl' }) })
const badContent = 'this shit'
await _test.step(
'Open project and make a change to the file',
async () => {
await projectCard.click()
await u.waitForPageLoad()
// Get the text in the code locator.
const originalText = await u.codeLocator.innerText()
// Click in the editor and add some new lines.
await u.codeLocator.click()
await page.keyboard.type(badContent)
// Ensure the content in the editor changed.
const newContent = await u.codeLocator.innerText()
expect(originalText !== newContent)
// Now hit undo
await page.keyboard.down('ControlOrMeta')
await page.keyboard.press('KeyZ')
await page.keyboard.up('ControlOrMeta')
await page.waitForTimeout(100)
await expect(u.codeLocator).toContainText(originalText)
await expect(u.codeLocator).not.toContainText(badContent)
// Hit redo.
await page.keyboard.down('Shift')
await page.keyboard.down('ControlOrMeta')
await page.keyboard.press('KeyZ')
await page.keyboard.up('ControlOrMeta')
await page.keyboard.up('Shift')
await page.waitForTimeout(100)
await expect(u.codeLocator).toContainText(originalText)
await expect(u.codeLocator).toContainText(badContent)
// Now hit undo
await page.keyboard.down('ControlOrMeta')
await page.keyboard.press('KeyZ')
await page.keyboard.up('ControlOrMeta')
await page.waitForTimeout(100)
await expect(u.codeLocator).toContainText(originalText)
await expect(u.codeLocator).not.toContainText(badContent)
}
)
await _test.step('navigate to other.kcl', async () => {
await u.openFilePanel()
await otherFile.click()
await u.waitForPageLoad()
await u.openKclCodePanel()
await _expect(u.codeLocator).toContainText('getOppositeEdge(thing)')
await expect(u.codeLocator).not.toContainText(badContent)
})
await _test.step('hit redo', async () => {
// Get the original content of the file.
const originalText = await u.codeLocator.innerText()
// Now hit redo
await page.keyboard.down('Shift')
await page.keyboard.down('ControlOrMeta')
await page.keyboard.press('KeyZ')
await page.keyboard.up('ControlOrMeta')
await page.keyboard.up('Shift')
await page.waitForTimeout(100)
await expect(u.codeLocator).toContainText(originalText)
await expect(u.codeLocator).not.toContainText(badContent)
})
}
)
}
)

View File

@ -452,7 +452,7 @@ sketch002 = startSketchOn(extrude001, seg03)
)
})
test(`Verify axis and origin snapping`, async ({
test(`Verify axis, origin, and horizontal snapping`, async ({
app,
editor,
toolbar,
@ -505,7 +505,7 @@ test(`Verify axis and origin snapping`, async ({
const expectedCodeSnippets = {
sketchOnXzPlane: `sketch001 = startSketchOn('XZ')`,
pointAtOrigin: `startProfileAt([${originSloppy.kcl[0]}, ${originSloppy.kcl[1]}], %)`,
segmentOnXAxis: `lineTo([${xAxisSloppy.kcl[0]}, ${xAxisSloppy.kcl[1]}], %)`,
segmentOnXAxis: `xLine(${xAxisSloppy.kcl[0]}, %)`,
afterSegmentDraggedOffYAxis: `startProfileAt([${offYAxis.kcl[0]}, ${offYAxis.kcl[1]}], %)`,
afterSegmentDraggedOnYAxis: `startProfileAt([${yAxisSloppy.kcl[0]}, ${yAxisSloppy.kcl[1]}], %)`,
}

View File

@ -247,7 +247,7 @@ test.describe('Can export from electron app', () => {
.poll(
async () => {
try {
const outputGltf = await fsp.readFile('output.gltf')
const outputGltf = await fsp.readFile('main.gltf')
return outputGltf.byteLength
} catch (e) {
return 0
@ -257,8 +257,8 @@ test.describe('Can export from electron app', () => {
)
.toBeGreaterThan(300_000)
// clean up output.gltf
await fsp.rm('output.gltf')
// clean up exported file
await fsp.rm('main.gltf')
})
await electronApp.close()

View File

@ -115,7 +115,7 @@ test.describe('Sketch tests', () => {
'persistCode',
`sketch001 = startSketchOn('XZ')
|> startProfileAt([4.61, -14.01], %)
|> line([12.73, -0.09], %)
|> xLine(12.73, %)
|> tangentialArcTo([24.95, -5.38], %)`
)
})
@ -156,7 +156,7 @@ test.describe('Sketch tests', () => {
await expect.poll(u.normalisedEditorCode, { timeout: 1000 })
.toBe(`sketch001 = startSketchOn('XZ')
|> startProfileAt([12.34, -12.34], %)
|> line([-12.34, 12.34], %)
|> yLine(12.34, %)
`)
}).toPass({ timeout: 40_000, intervals: [1_000] })
@ -645,7 +645,7 @@ test.describe('Sketch tests', () => {
await u.openDebugPanel()
const center = { x: viewportSize.width / 2, y: viewportSize.height / 2 }
const { toSU, click00r } = getMovementUtils({ center, page })
const { toSU, toU, click00r } = getMovementUtils({ center, page })
await expect(
page.getByRole('button', { name: 'Start Sketch' })
@ -674,16 +674,15 @@ test.describe('Sketch tests', () => {
await click00r(50, 0)
await page.waitForTimeout(100)
codeStr += ` |> lineTo(${toSU([50, 0])}, %)`
codeStr += ` |> xLine(${toU(50, 0)[0]}, %)`
await expect(u.codeLocator).toHaveText(codeStr)
await click00r(0, 50)
codeStr += ` |> line(${toSU([0, 50])}, %)`
codeStr += ` |> yLine(${toU(0, 50)[1]}, %)`
await expect(u.codeLocator).toHaveText(codeStr)
let clickCoords = await click00r(-50, 0)
expect(clickCoords).not.toBeUndefined()
codeStr += ` |> lineTo(${toSU(clickCoords!)}, %)`
await click00r(-50, 0)
codeStr += ` |> xLine(${toU(-50, 0)[0]}, %)`
await expect(u.codeLocator).toHaveText(codeStr)
// exit the sketch, reset relative clicker
@ -712,15 +711,15 @@ test.describe('Sketch tests', () => {
// TODO: I couldn't use `toSU` here because of some rounding error causing
// it to be off by 0.01
await click00r(30, 0)
codeStr += ` |> lineTo([4.07, 0], %)`
codeStr += ` |> xLine(2.04, %)`
await expect(u.codeLocator).toHaveText(codeStr)
await click00r(0, 30)
codeStr += ` |> line([0, -2.03], %)`
codeStr += ` |> yLine(-2.03, %)`
await expect(u.codeLocator).toHaveText(codeStr)
await click00r(-30, 0)
codeStr += ` |> line([-2.04, 0], %)`
codeStr += ` |> xLine(-2.04, %)`
await expect(u.codeLocator).toHaveText(codeStr)
await click00r(undefined, undefined)
@ -744,8 +743,8 @@ test.describe('Sketch tests', () => {
const code = `sketch001 = startSketchOn('-XZ')
|> startProfileAt([${roundOff(scale * 69.6)}, ${roundOff(scale * 34.8)}], %)
|> line([${roundOff(scale * 139.19)}, 0], %)
|> line([0, -${roundOff(scale * 139.2)}], %)
|> xLine(${roundOff(scale * 139.19)}, %)
|> yLine(-${roundOff(scale * 139.2)}, %)
|> lineTo([profileStartX(%), profileStartY(%)], %)
|> close(%)`
@ -1275,3 +1274,44 @@ test2.describe('Sketch mode should be toleratant to syntax errors', () => {
}
)
})
test2.describe(`Sketching with offset planes`, () => {
test2(
`Can select an offset plane to sketch on`,
async ({ app, scene, toolbar, editor }) => {
// We seed the scene with a single offset plane
await app.initialise(`offsetPlane001 = offsetPlane("XY", 10)`)
const [planeClick, planeHover] = scene.makeMouseHelpers(650, 200)
await test2.step(`Start sketching on the offset plane`, async () => {
await toolbar.startSketchPlaneSelection()
await test2.step(`Hovering should highlight code`, async () => {
await planeHover()
await editor.expectState({
activeLines: [`offsetPlane001=offsetPlane("XY",10)`],
diagnostics: [],
highlightedCode: 'offsetPlane("XY", 10)',
})
})
await test2.step(
`Clicking should select the plane and enter sketch mode`,
async () => {
await planeClick()
// Have to wait for engine-side animation to finish
await app.page.waitForTimeout(600)
await expect2(toolbar.lineBtn).toBeEnabled()
await editor.expectEditor.toContain('startSketchOn(offsetPlane001)')
await editor.expectState({
activeLines: [`offsetPlane001=offsetPlane("XY",10)`],
diagnostics: [],
highlightedCode: '',
})
}
)
})
}
)
})

View File

@ -283,7 +283,7 @@ part001 = startSketchOn('-XZ')
const gltfFilename = filenames.filter((t: string) =>
t.includes('.gltf')
)[0]
if (!gltfFilename) throw new Error('No output.gltf in this archive')
if (!gltfFilename) throw new Error('No gLTF in this archive')
cliCommand = `export ZOO_TOKEN=${secrets.snapshottoken} && zoo file snapshot --output-format=png --src-format=${outputType} ${parentPath}/${gltfFilename} ${imagePath}`
}
@ -462,7 +462,7 @@ test(
await page.waitForTimeout(100)
code += `
|> line([7.25, 0], %)`
|> xLine(7.25, %)`
await expect(page.locator('.cm-content')).toHaveText(code)
await page
@ -647,7 +647,7 @@ test.describe(
await page.waitForTimeout(100)
code += `
|> line([7.25, 0], %)`
|> xLine(7.25, %)`
await expect(u.codeLocator).toHaveText(code)
await page
@ -752,7 +752,7 @@ test.describe(
await page.waitForTimeout(100)
code += `
|> line([184.3, 0], %)`
|> xLine(184.3, %)`
await expect(u.codeLocator).toHaveText(code)
await page

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 40 KiB

View File

@ -141,7 +141,7 @@ test.describe('Test network and connection issues', () => {
await expect(page.locator('.cm-content'))
.toHaveText(`sketch001 = startSketchOn('XZ')
|> startProfileAt(${commonPoints.startAt}, %)
|> line([${commonPoints.num1}, 0], %)`)
|> xLine(${commonPoints.num1}, %)`)
// Expect the network to be up
await expect(networkToggle).toContainText('Connected')
@ -207,7 +207,7 @@ test.describe('Test network and connection issues', () => {
await expect.poll(u.normalisedEditorCode)
.toBe(`sketch001 = startSketchOn('XZ')
|> startProfileAt([12.34, -12.34], %)
|> line([12.34, 0], %)
|> xLine(12.34, %)
|> line([-12.34, 12.34], %)
`)
@ -217,9 +217,9 @@ test.describe('Test network and connection issues', () => {
await expect.poll(u.normalisedEditorCode)
.toBe(`sketch001 = startSketchOn('XZ')
|> startProfileAt([12.34, -12.34], %)
|> line([12.34, 0], %)
|> xLine(12.34, %)
|> line([-12.34, 12.34], %)
|> lineTo([0, -12.34], %)
|> xLine(-12.34, %)
`)

View File

@ -305,7 +305,7 @@ export const getMovementUtils = (opts: any) => {
return [last.x, last.y]
}
return { toSU, click00r }
return { toSU, toU, click00r }
}
async function waitForAuthAndLsp(page: Page) {

View File

@ -32,10 +32,17 @@ test.describe('Testing selections', () => {
await u.waitForAuthSkipAppStart()
await u.openDebugPanel()
const xAxisClick = () =>
page.mouse.click(700, 253).then(() => page.waitForTimeout(100))
const yAxisClick = () =>
test.step('Click on Y axis', async () => {
await page.mouse.move(600, 200, { steps: 5 })
await page.mouse.click(600, 200)
await page.waitForTimeout(100)
})
const xAxisClickAfterExitingSketch = () =>
page.mouse.click(639, 278).then(() => page.waitForTimeout(100))
test.step(`Click on X axis after exiting sketch, which shifts it at the moment`, async () => {
await page.mouse.click(639, 278)
await page.waitForTimeout(100)
})
const emptySpaceHover = () =>
test.step('Hover over empty space', async () => {
await page.mouse.move(700, 143, { steps: 5 })
@ -80,23 +87,23 @@ test.describe('Testing selections', () => {
await expect(page.locator('.cm-content'))
.toHaveText(`sketch001 = startSketchOn('XZ')
|> startProfileAt(${commonPoints.startAt}, %)
|> line([${commonPoints.num1}, 0], %)`)
|> xLine(${commonPoints.num1}, %)`)
await page.waitForTimeout(100)
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 20)
await expect(page.locator('.cm-content'))
.toHaveText(`sketch001 = startSketchOn('XZ')
|> startProfileAt(${commonPoints.startAt}, %)
|> line([${commonPoints.num1}, 0], %)
|> line([0, ${commonPoints.num1 + 0.01}], %)`)
|> xLine(${commonPoints.num1}, %)
|> yLine(${commonPoints.num1 + 0.01}, %)`)
await page.waitForTimeout(100)
await page.mouse.click(startXPx, 500 - PUR * 20)
await expect(page.locator('.cm-content'))
.toHaveText(`sketch001 = startSketchOn('XZ')
|> startProfileAt(${commonPoints.startAt}, %)
|> line([${commonPoints.num1}, 0], %)
|> line([0, ${commonPoints.num1 + 0.01}], %)
|> lineTo([0, ${commonPoints.num3}], %)`)
|> xLine(${commonPoints.num1}, %)
|> yLine(${commonPoints.num1 + 0.01}, %)
|> xLine(${commonPoints.num2 * -1}, %)`)
// deselect line tool
await page.getByRole('button', { name: 'line Line', exact: true }).click()
@ -121,29 +128,31 @@ test.describe('Testing selections', () => {
// now check clicking works including axis
// click a segment hold shift and click an axis, see that a relevant constraint is enabled
await topHorzSegmentClick()
await page.keyboard.down('Shift')
const constrainButton = page.getByRole('button', {
name: 'Length: open menu',
})
const absYButton = page.getByRole('button', { name: 'Absolute Y' })
const absXButton = page.getByRole('button', { name: 'Absolute X' })
await test.step(`Select a segment and an axis, see that a relevant constraint is enabled`, async () => {
await topHorzSegmentClick()
await page.keyboard.down('Shift')
await constrainButton.click()
await expect(absYButton).toBeDisabled()
await expect(absXButton).toBeDisabled()
await page.waitForTimeout(100)
await xAxisClick()
await yAxisClick()
await page.keyboard.up('Shift')
await constrainButton.click()
await absYButton.and(page.locator(':not([disabled])')).waitFor()
await expect(absYButton).not.toBeDisabled()
await absXButton.and(page.locator(':not([disabled])')).waitFor()
await expect(absXButton).not.toBeDisabled()
})
// clear selection by clicking on nothing
await emptySpaceClick()
await page.waitForTimeout(100)
// same selection but click the axis first
await xAxisClick()
await test.step(`Same selection but click the axis first`, async () => {
await yAxisClick()
await constrainButton.click()
await expect(absYButton).toBeDisabled()
await expect(absXButton).toBeDisabled()
await page.keyboard.down('Shift')
await page.waitForTimeout(100)
await topHorzSegmentClick()
@ -151,23 +160,26 @@ test.describe('Testing selections', () => {
await page.keyboard.up('Shift')
await constrainButton.click()
await expect(absYButton).not.toBeDisabled()
await expect(absXButton).not.toBeDisabled()
})
// clear selection by clicking on nothing
await emptySpaceClick()
// check the same selection again by putting cursor in code first then selecting axis
await test.step(`Same selection but code selection then axis`, async () => {
await page
.getByText(` |> lineTo([0, ${commonPoints.num3}], %)`)
.getByText(` |> xLine(${commonPoints.num2 * -1}, %)`)
.click()
await page.keyboard.down('Shift')
await constrainButton.click()
await expect(absYButton).toBeDisabled()
await expect(absXButton).toBeDisabled()
await page.waitForTimeout(100)
await xAxisClick()
await yAxisClick()
await page.keyboard.up('Shift')
await constrainButton.click()
await expect(absYButton).not.toBeDisabled()
await expect(absXButton).not.toBeDisabled()
})
// clear selection by clicking on nothing
await emptySpaceClick()
@ -182,9 +194,7 @@ test.describe('Testing selections', () => {
process.platform === 'linux' ? 'Control' : 'Meta'
)
await page.waitForTimeout(100)
await page
.getByText(` |> lineTo([0, ${commonPoints.num3}], %)`)
.click()
await page.getByText(` |> xLine(${commonPoints.num2 * -1}, %)`).click()
await expect(page.locator('.cm-cursor')).toHaveCount(2)
await page.waitForTimeout(500)
@ -928,6 +938,7 @@ sketch002 = startSketchOn(extrude001, $seg01)
// test fillet button with the body in the scene
const codeToAdd = `${await u.codeLocator.allInnerTexts()}
extrude001 = extrude(10, sketch001)`
await u.codeLocator.clear()
await u.codeLocator.fill(codeToAdd)
await selectSegment()
await expect(page.getByRole('button', { name: 'Fillet' })).toBeEnabled()

View File

@ -258,7 +258,7 @@ test.describe('Testing settings', () => {
})
})
test(
test.fixme(
`Project settings override user settings on desktop`,
{ tag: ['@electron', '@skipWin'] },
async ({ browser: _ }, testInfo) => {
@ -318,7 +318,6 @@ test.describe('Testing settings', () => {
timeout: 5_000,
})
.toContain(`themeColor = "${userThemeColor}"`)
// Only close the button after we've confirmed
})
await test.step('Set project theme color', async () => {

View File

@ -1,6 +1,6 @@
{
"name": "zoo-modeling-app",
"version": "0.26.3",
"version": "0.27.0",
"private": true,
"productName": "Zoo Modeling App",
"author": {
@ -40,7 +40,7 @@
"codemirror": "^6.0.1",
"decamelize": "^6.0.0",
"electron-squirrel-startup": "^1.0.1",
"electron-updater": "^6.3.9",
"electron-updater": "6.3.0",
"fuse.js": "^7.0.0",
"html2canvas-pro": "^1.5.8",
"isomorphic-fetch": "^3.0.0",

View File

@ -141,6 +141,7 @@ export function Toolbar({
>
{/* A menu item will either be a vertical line break, a button with a dropdown, or a single button */}
{currentModeItems.map((maybeIconConfig, i) => {
// Vertical Line Break
if (maybeIconConfig === 'break') {
return (
<div
@ -149,6 +150,7 @@ export function Toolbar({
/>
)
} else if (Array.isArray(maybeIconConfig)) {
// A button with a dropdown
return (
<ActionButtonDropdown
Element="button"
@ -215,6 +217,7 @@ export function Toolbar({
}
const itemConfig = maybeIconConfig
// A single button
return (
<div className="relative" key={itemConfig.id}>
<ActionButton

View File

@ -202,12 +202,20 @@ const Overlay = ({
let xAlignment = overlay.angle < 0 ? '0%' : '-100%'
let yAlignment = overlay.angle < -90 || overlay.angle >= 90 ? '0%' : '-100%'
// It's possible for the pathToNode to request a newer AST node
// than what's available in the AST at the moment of query.
// It eventually settles on being updated.
const _node1 = getNodeFromPath<Node<CallExpression>>(
kclManager.ast,
overlay.pathToNode,
'CallExpression'
)
if (err(_node1)) return
// For that reason, to prevent console noise, we do not use err here.
if (_node1 instanceof Error) {
console.warn('ast older than pathToNode, not fatal, eventually settles', '')
return
}
const callExpression = _node1.node
const constraints = getConstraintInfo(
@ -637,10 +645,16 @@ const ConstraintSymbol = ({
kclManager.ast,
kclManager.programMemory
)
if (!transform) return
const { modifiedAst } = transform
// eslint-disable-next-line @typescript-eslint/no-floating-promises
kclManager.updateAst(modifiedAst, true)
await kclManager.updateAst(modifiedAst, true)
// Code editor will be updated in the modelingMachine.
const newCode = recast(modifiedAst)
if (err(newCode)) return
await codeManager.updateCodeEditor(newCode)
} catch (e) {
console.log('error', e)
}

View File

@ -17,6 +17,7 @@ import {
Vector3,
} from 'three'
import {
ANGLE_SNAP_THRESHOLD_DEGREES,
ARROWHEAD,
AXIS_GROUP,
DRAFT_POINT,
@ -46,6 +47,7 @@ import {
VariableDeclaration,
VariableDeclarator,
sketchFromKclValue,
sketchFromKclValueOptional,
} from 'lang/wasm'
import {
engineCommandManager,
@ -88,13 +90,15 @@ import { EngineCommandManager } from 'lang/std/engineConnection'
import {
getRectangleCallExpressions,
updateRectangleSketch,
updateCenterRectangleSketch,
} from 'lib/rectangleTool'
import { getThemeColorForThreeJs, Themes } from 'lib/theme'
import { err, reportRejection, trap } from 'lib/trap'
import { err, Reason, reportRejection, trap } from 'lib/trap'
import { CSS2DObject } from 'three/examples/jsm/renderers/CSS2DRenderer'
import { Point3d } from 'wasm-lib/kcl/bindings/Point3d'
import { SegmentInputs } from 'lang/std/stdTypes'
import { Node } from 'wasm-lib/kcl/bindings/Node'
import { radToDeg } from 'three/src/math/MathUtils'
type DraftSegment = 'line' | 'tangentialArcTo'
@ -451,6 +455,7 @@ export class SceneEntities {
const { modifiedAst } = addStartProfileAtRes
await kclManager.updateAst(modifiedAst, false)
this.removeIntersectionPlane()
this.scene.remove(draftPointGroup)
@ -683,7 +688,7 @@ export class SceneEntities {
})
return nextAst
}
setUpDraftSegment = async (
setupDraftSegment = async (
sketchPathToNode: PathToNode,
forward: [number, number, number],
up: [number, number, number],
@ -798,11 +803,24 @@ export class SceneEntities {
(sceneObject) => sceneObject.object.name === X_AXIS
)
const lastSegment = sketch.paths.slice(-1)[0]
const lastSegment = sketch.paths.slice(-1)[0] || sketch.start
const snappedPoint = {
x: intersectsYAxis ? 0 : intersection2d.x,
y: intersectsXAxis ? 0 : intersection2d.y,
}
// Get the angle between the previous segment (or sketch start)'s end and this one's
const angle = Math.atan2(
snappedPoint.y - lastSegment.to[1],
snappedPoint.x - lastSegment.to[0]
)
const isHorizontal =
radToDeg(Math.abs(angle)) < ANGLE_SNAP_THRESHOLD_DEGREES ||
Math.abs(radToDeg(Math.abs(angle) - Math.PI)) <
ANGLE_SNAP_THRESHOLD_DEGREES
const isVertical =
Math.abs(radToDeg(Math.abs(angle) - Math.PI / 2)) <
ANGLE_SNAP_THRESHOLD_DEGREES
let resolvedFunctionName: ToolTip = 'line'
@ -810,6 +828,12 @@ export class SceneEntities {
// case-based logic for different segment types
if (lastSegment.type === 'TangentialArcTo') {
resolvedFunctionName = 'tangentialArcTo'
} else if (isHorizontal) {
// If the angle between is 0 or 180 degrees (+/- the snapping angle), make the line an xLine
resolvedFunctionName = 'xLine'
} else if (isVertical) {
// If the angle between is 90 or 270 degrees (+/- the snapping angle), make the line a yLine
resolvedFunctionName = 'yLine'
} else if (snappedPoint.x === 0 || snappedPoint.y === 0) {
// We consider a point placed on axes or origin to be absolute
resolvedFunctionName = 'lineTo'
@ -835,10 +859,11 @@ export class SceneEntities {
}
await kclManager.executeAstMock(modifiedAst)
if (intersectsProfileStart) {
sceneInfra.modelingSend({ type: 'CancelSketch' })
} else {
await this.setUpDraftSegment(
await this.setupDraftSegment(
sketchPathToNode,
forward,
up,
@ -846,6 +871,8 @@ export class SceneEntities {
segmentName
)
}
await codeManager.updateEditorWithAstAndWriteToFile(modifiedAst)
},
onMove: (args) => {
this.onDragSegment({
@ -970,16 +997,192 @@ export class SceneEntities {
if (trap(_node)) return
const sketchInit = _node.node?.declarations?.[0]?.init
if (sketchInit.type === 'PipeExpression') {
if (sketchInit.type !== 'PipeExpression') {
return
}
updateRectangleSketch(sketchInit, x, y, tags[0])
const newCode = recast(_ast)
let _recastAst = parse(newCode)
if (trap(_recastAst)) return
_ast = _recastAst
// Update the primary AST and unequip the rectangle tool
await kclManager.executeAstMock(_ast)
sceneInfra.modelingSend({ type: 'Finish rectangle' })
// lee: I had this at the bottom of the function, but it's
// possible sketchFromKclValue "fails" when sketching on a face,
// and this couldn't wouldn't run.
await codeManager.updateEditorWithAstAndWriteToFile(_ast)
const { execState } = await executeAst({
ast: _ast,
useFakeExecutor: true,
engineCommandManager: this.engineCommandManager,
programMemoryOverride,
idGenerator: kclManager.execState.idGenerator,
})
const programMemory = execState.memory
// Prepare to update the THREEjs scene
this.sceneProgramMemory = programMemory
const sketch = sketchFromKclValue(
programMemory.get(variableDeclarationName),
variableDeclarationName
)
if (err(sketch)) return
const sgPaths = sketch.paths
const orthoFactor = orthoScale(sceneInfra.camControls.camera)
// Update the starting segment of the THREEjs scene
this.updateSegment(sketch.start, 0, 0, _ast, orthoFactor, sketch)
// Update the rest of the segments of the THREEjs scene
sgPaths.forEach((seg, index) =>
this.updateSegment(seg, index, 0, _ast, orthoFactor, sketch)
)
},
})
}
setupDraftCenterRectangle = async (
sketchPathToNode: PathToNode,
forward: [number, number, number],
up: [number, number, number],
sketchOrigin: [number, number, number],
rectangleOrigin: [x: number, y: number]
) => {
let _ast = structuredClone(kclManager.ast)
const _node1 = getNodeFromPath<VariableDeclaration>(
_ast,
sketchPathToNode || [],
'VariableDeclaration'
)
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 tags: [string, string, string] = [
findUniqueName(_ast, 'rectangleSegmentA'),
findUniqueName(_ast, 'rectangleSegmentB'),
findUniqueName(_ast, 'rectangleSegmentC'),
]
startSketchOn[0].init = createPipeExpression([
startSketchOnInit,
...getRectangleCallExpressions(rectangleOrigin, tags),
])
let _recastAst = parse(recast(_ast))
if (trap(_recastAst)) return Promise.reject(_recastAst)
_ast = _recastAst
const { programMemoryOverride, truncatedAst } = await this.setupSketch({
sketchPathToNode,
forward,
up,
position: sketchOrigin,
maybeModdedAst: _ast,
draftExpressionsIndices: { start: 0, end: 3 },
})
sceneInfra.setCallbacks({
onMove: async (args) => {
// Update the width and height of the draft rectangle
const pathToNodeTwo = structuredClone(sketchPathToNode)
pathToNodeTwo[1][0] = 0
const _node = getNodeFromPath<VariableDeclaration>(
truncatedAst,
pathToNodeTwo || [],
'VariableDeclaration'
)
if (trap(_node)) return Promise.reject(_node)
const sketchInit = _node.node?.declarations?.[0]?.init
const x = (args.intersectionPoint.twoD.x || 0) - rectangleOrigin[0]
const y = (args.intersectionPoint.twoD.y || 0) - rectangleOrigin[1]
if (sketchInit.type === 'PipeExpression') {
updateCenterRectangleSketch(
sketchInit,
x,
y,
tags[0],
rectangleOrigin[0],
rectangleOrigin[1]
)
}
const { execState } = await executeAst({
ast: truncatedAst,
useFakeExecutor: true,
engineCommandManager: this.engineCommandManager,
programMemoryOverride,
idGenerator: kclManager.execState.idGenerator,
})
const programMemory = execState.memory
this.sceneProgramMemory = programMemory
const sketch = sketchFromKclValue(
programMemory.get(variableDeclarationName),
variableDeclarationName
)
if (err(sketch)) return Promise.reject(sketch)
const sgPaths = sketch.paths
const orthoFactor = orthoScale(sceneInfra.camControls.camera)
this.updateSegment(sketch.start, 0, 0, _ast, orthoFactor, sketch)
sgPaths.forEach((seg, index) =>
this.updateSegment(seg, index, 0, _ast, orthoFactor, sketch)
)
},
onClick: async (args) => {
// If there is a valid camera interaction that matches, do that instead
const interaction = sceneInfra.camControls.getInteractionType(
args.mouseEvent
)
if (interaction !== 'none') return
// Commit the rectangle to the full AST/code and return to sketch.idle
const cornerPoint = args.intersectionPoint?.twoD
if (!cornerPoint || args.mouseEvent.button !== 0) return
const x = roundOff((cornerPoint.x || 0) - rectangleOrigin[0])
const y = roundOff((cornerPoint.y || 0) - rectangleOrigin[1])
const _node = getNodeFromPath<VariableDeclaration>(
_ast,
sketchPathToNode || [],
'VariableDeclaration'
)
if (trap(_node)) return
const sketchInit = _node.node?.declarations?.[0]?.init
if (sketchInit.type === 'PipeExpression') {
updateCenterRectangleSketch(
sketchInit,
x,
y,
tags[0],
rectangleOrigin[0],
rectangleOrigin[1]
)
let _recastAst = parse(recast(_ast))
if (trap(_recastAst)) return
_ast = _recastAst
// Update the primary AST and unequip the rectangle tool
await kclManager.executeAstMock(_ast)
sceneInfra.modelingSend({ type: 'Finish rectangle' })
sceneInfra.modelingSend({ type: 'Finish center rectangle' })
// lee: I had this at the bottom of the function, but it's
// possible sketchFromKclValue "fails" when sketching on a face,
// and this couldn't wouldn't run.
await codeManager.updateEditorWithAstAndWriteToFile(_ast)
const { execState } = await executeAst({
ast: _ast,
@ -1166,13 +1369,17 @@ export class SceneEntities {
if (err(moddedResult)) return
modded = moddedResult.modifiedAst
let _recastAst = parse(recast(modded))
const newCode = recast(modded)
if (err(newCode)) return
let _recastAst = parse(newCode)
if (trap(_recastAst)) return Promise.reject(_recastAst)
_ast = _recastAst
// Update the primary AST and unequip the rectangle tool
await kclManager.executeAstMock(_ast)
sceneInfra.modelingSend({ type: 'Finish circle' })
await codeManager.updateEditorWithAstAndWriteToFile(_ast)
}
},
})
@ -1208,6 +1415,7 @@ export class SceneEntities {
forward,
position,
})
await codeManager.writeToFile()
}
},
onDrag: async ({
@ -1490,10 +1698,13 @@ export class SceneEntities {
this.sceneProgramMemory = programMemory
const maybeSketch = programMemory.get(variableDeclarationName)
let sketch = undefined
const sg = sketchFromKclValue(maybeSketch, variableDeclarationName)
if (!err(sg)) {
sketch = sg
let sketch: Sketch | undefined
const sk = sketchFromKclValueOptional(
maybeSketch,
variableDeclarationName
)
if (!(sk instanceof Reason)) {
sketch = sk
} else if ((maybeSketch as Solid).sketch) {
sketch = (maybeSketch as Solid).sketch
}

View File

@ -50,6 +50,8 @@ export const RAYCASTABLE_PLANE = 'raycastable-plane'
export const X_AXIS = 'xAxis'
export const Y_AXIS = 'yAxis'
/** If a segment angle is less than this many degrees off a meanginful angle it'll snap to it */
export const ANGLE_SNAP_THRESHOLD_DEGREES = 3
/** the THREEjs representation of the group surrounding a "snapped" point that is not yet placed */
export const DRAFT_POINT_GROUP = 'draft-point-group'
/** the THREEjs representation of a "snapped" point that is not yet placed */

View File

@ -145,7 +145,7 @@ export function useCalc({
const _programMem: ProgramMemory = ProgramMemory.empty()
for (const { key, value } of availableVarInfo.variables) {
const error = _programMem.set(key, {
type: 'UserVal',
type: 'String',
value,
__meta: [],
})

View File

@ -22,6 +22,7 @@ import usePlatform from 'hooks/usePlatform'
import { FileEntry } from 'lib/project'
import { useFileSystemWatcher } from 'hooks/useFileSystemWatcher'
import { normalizeLineEndings } from 'lib/codeEditor'
import { reportRejection } from 'lib/trap'
function getIndentationCSS(level: number) {
return `calc(1rem * ${level + 1})`
@ -196,8 +197,7 @@ const FileTreeItem = ({
return
}
// Don't try to read a file that was removed.
if (isCurrentFile && eventType !== 'unlink') {
if (isCurrentFile && eventType === 'change') {
let code = await window.electron.readFile(path, { encoding: 'utf-8' })
code = normalizeLineEndings(code)
codeManager.updateCodeStateEditor(code)
@ -242,7 +242,7 @@ const FileTreeItem = ({
// Show the renaming form
addCurrentItemToRenaming()
} else if (e.code === 'Space') {
void handleClick()
void handleClick().catch(reportRejection)
}
}
@ -293,7 +293,7 @@ const FileTreeItem = ({
style={{ paddingInlineStart: getIndentationCSS(level) }}
onClick={(e) => {
e.currentTarget.focus()
void handleClick()
void handleClick().catch(reportRejection)
}}
onKeyUp={handleKeyUp}
>

View File

@ -63,6 +63,7 @@ import {
import {
moveValueIntoNewVariablePath,
sketchOnExtrudedFace,
sketchOnOffsetPlane,
startSketchOnDefault,
} from 'lang/modifyAst'
import { Program, parse, recast } from 'lang/wasm'
@ -304,6 +305,7 @@ export const ModelingMachineProvider = ({
const dispatchSelection = (selection?: EditorSelection) => {
if (!selection) return // TODO less of hack for the below please
if (!editorManager.editorView) return
setTimeout(() => {
if (!editorManager.editorView) return
editorManager.editorView.dispatch({
@ -482,7 +484,7 @@ export const ModelingMachineProvider = ({
engineCommandManager.exportInfo = {
intent: ExportIntent.Save,
// This never gets used its only for make.
name: '',
name: file?.name?.replace('.kcl', `.${event.data.type}`) || '',
}
const format = {
@ -635,13 +637,16 @@ export const ModelingMachineProvider = ({
),
'animate-to-face': fromPromise(async ({ input }) => {
if (!input) return undefined
if (input.type === 'extrudeFace') {
const sketched = sketchOnExtrudedFace(
if (input.type === 'extrudeFace' || input.type === 'offsetPlane') {
const sketched =
input.type === 'extrudeFace'
? sketchOnExtrudedFace(
kclManager.ast,
input.sketchPathToNode,
input.extrudePathToNode,
input.faceInfo
)
: sketchOnOffsetPlane(kclManager.ast, input.pathToNode)
if (err(sketched)) {
const sketchedError = new Error(
'Incompatible face, please try another'
@ -653,10 +658,9 @@ export const ModelingMachineProvider = ({
await kclManager.executeAstMock(modifiedAst)
await letEngineAnimateAndSyncCamAfter(
engineCommandManager,
input.faceId
)
const id =
input.type === 'extrudeFace' ? input.faceId : input.planeId
await letEngineAnimateAndSyncCamAfter(engineCommandManager, id)
sceneInfra.camControls.syncDirection = 'clientToEngine'
return {
sketchPathToNode: pathToNewSketchNode,
@ -732,6 +736,11 @@ export const ModelingMachineProvider = ({
sketchDetails.origin
)
if (err(updatedAst)) return Promise.reject(updatedAst)
await codeManager.updateEditorWithAstAndWriteToFile(
updatedAst.newAst
)
const selection = updateSelections(
pathToNodeMap,
selectionRanges,
@ -768,6 +777,11 @@ export const ModelingMachineProvider = ({
sketchDetails.origin
)
if (err(updatedAst)) return Promise.reject(updatedAst)
await codeManager.updateEditorWithAstAndWriteToFile(
updatedAst.newAst
)
const selection = updateSelections(
pathToNodeMap,
selectionRanges,
@ -813,6 +827,11 @@ export const ModelingMachineProvider = ({
sketchDetails.origin
)
if (err(updatedAst)) return Promise.reject(updatedAst)
await codeManager.updateEditorWithAstAndWriteToFile(
updatedAst.newAst
)
const selection = updateSelections(
pathToNodeMap,
selectionRanges,
@ -846,6 +865,11 @@ export const ModelingMachineProvider = ({
sketchDetails.origin
)
if (err(updatedAst)) return Promise.reject(updatedAst)
await codeManager.updateEditorWithAstAndWriteToFile(
updatedAst.newAst
)
const selection = updateSelections(
pathToNodeMap,
selectionRanges,
@ -881,6 +905,11 @@ export const ModelingMachineProvider = ({
sketchDetails.origin
)
if (err(updatedAst)) return Promise.reject(updatedAst)
await codeManager.updateEditorWithAstAndWriteToFile(
updatedAst.newAst
)
const selection = updateSelections(
pathToNodeMap,
selectionRanges,
@ -917,6 +946,11 @@ export const ModelingMachineProvider = ({
sketchDetails.origin
)
if (err(updatedAst)) return Promise.reject(updatedAst)
await codeManager.updateEditorWithAstAndWriteToFile(
updatedAst.newAst
)
const selection = updateSelections(
pathToNodeMap,
selectionRanges,
@ -953,6 +987,11 @@ export const ModelingMachineProvider = ({
sketchDetails.origin
)
if (err(updatedAst)) return Promise.reject(updatedAst)
await codeManager.updateEditorWithAstAndWriteToFile(
updatedAst.newAst
)
const selection = updateSelections(
pathToNodeMap,
selectionRanges,
@ -999,6 +1038,11 @@ export const ModelingMachineProvider = ({
sketchDetails.origin
)
if (err(updatedAst)) return Promise.reject(updatedAst)
await codeManager.updateEditorWithAstAndWriteToFile(
updatedAst.newAst
)
const selection = updateSelections(
{ 0: pathToReplacedNode },
selectionRanges,

View File

@ -43,6 +43,7 @@ import {
completionKeymap,
} from '@codemirror/autocomplete'
import CodeEditor from './CodeEditor'
import { codeManagerHistoryCompartment } from 'lang/codeManager'
export const editorShortcutMeta = {
formatCode: {
@ -89,7 +90,7 @@ export const KclEditorPane = () => {
cursorBlinkRate: cursorBlinking.current ? 1200 : 0,
}),
lineHighlightField,
history(),
codeManagerHistoryCompartment.of(history()),
closeBrackets(),
codeFolding(),
keymap.of([
@ -121,7 +122,6 @@ export const KclEditorPane = () => {
lineNumbers(),
highlightActiveLineGutter(),
highlightSpecialChars(),
history(),
foldGutter(),
EditorState.allowMultipleSelections.of(true),
indentOnInput(),

View File

@ -5,12 +5,12 @@ import {
ProgramMemory,
Path,
ExtrudeSurface,
sketchFromKclValue,
sketchFromKclValueOptional,
} from 'lang/wasm'
import { useKclContext } from 'lang/KclProvider'
import { useResolvedTheme } from 'hooks/useResolvedTheme'
import { ActionButton } from 'components/ActionButton'
import { err, trap } from 'lib/trap'
import { Reason, trap } from 'lib/trap'
import Tooltip from 'components/Tooltip'
import { useModelingContext } from 'hooks/useModelingContext'
@ -89,17 +89,17 @@ export const processMemory = (programMemory: ProgramMemory) => {
const processedMemory: any = {}
for (const [key, val] of programMemory?.visibleEntries()) {
if (
(val.type === 'UserVal' && val.value.type === 'Sketch') ||
val.type === 'Sketch' ||
// @ts-ignore
(val.type !== 'Function' && val.type !== 'UserVal')
val.type !== 'Function'
) {
const sg = sketchFromKclValue(val, key)
const sk = sketchFromKclValueOptional(val, key)
if (val.type === 'Solid') {
processedMemory[key] = val.value.map(({ ...rest }: ExtrudeSurface) => {
return rest
})
} else if (!err(sg)) {
processedMemory[key] = sg.paths.map(({ __geoMeta, ...rest }: Path) => {
} else if (!(sk instanceof Reason)) {
processedMemory[key] = sk.paths.map(({ __geoMeta, ...rest }: Path) => {
return rest
})
} else {
@ -110,8 +110,6 @@ export const processMemory = (programMemory: ProgramMemory) => {
processedMemory[key] = `__function(${(val as any)?.expression?.params
?.map?.(({ identifier }: any) => identifier?.name || '')
.join(', ')})__`
} else {
processedMemory[key] = val.value
}
}
return processedMemory

View File

@ -0,0 +1,81 @@
import { editorManager } from 'lib/singletons'
import { Diagnostic } from '@codemirror/lint'
describe('EditorManager Class', () => {
describe('makeUniqueDiagnostics', () => {
it('should filter out duplicated diagnostics', () => {
const duplicatedDiagnostics: Diagnostic[] = [
{
from: 2,
to: 10,
severity: 'hint',
message: 'my cool message',
},
{
from: 2,
to: 10,
severity: 'hint',
message: 'my cool message',
},
{
from: 2,
to: 10,
severity: 'hint',
message: 'my cool message',
},
]
const expected: Diagnostic[] = [
{
from: 2,
to: 10,
severity: 'hint',
message: 'my cool message',
},
]
const actual = editorManager.makeUniqueDiagnostics(duplicatedDiagnostics)
expect(actual).toStrictEqual(expected)
})
it('should filter out duplicated diagnostic and keep some original ones', () => {
const duplicatedDiagnostics: Diagnostic[] = [
{
from: 0,
to: 10,
severity: 'hint',
message: 'my cool message',
},
{
from: 0,
to: 10,
severity: 'hint',
message: 'my cool message',
},
{
from: 88,
to: 99,
severity: 'hint',
message: 'my super cool message',
},
]
const expected: Diagnostic[] = [
{
from: 0,
to: 10,
severity: 'hint',
message: 'my cool message',
},
{
from: 88,
to: 99,
severity: 'hint',
message: 'my super cool message',
},
]
const actual = editorManager.makeUniqueDiagnostics(duplicatedDiagnostics)
expect(actual).toStrictEqual(expected)
})
})
})

View File

@ -24,10 +24,6 @@ export const modelingMachineEvent = modelingMachineAnnotation.of(true)
const setDiagnosticsAnnotation = Annotation.define<boolean>()
export const setDiagnosticsEvent = setDiagnosticsAnnotation.of(true)
function diagnosticIsEqual(d1: Diagnostic, d2: Diagnostic): boolean {
return d1.from === d2.from && d1.to === d2.to && d1.message === d2.message
}
export default class EditorManager {
private _editorView: EditorView | null = null
private _copilotEnabled: boolean = true
@ -72,9 +68,10 @@ export default class EditorManager {
// we cannot use <>.constructor.name since it will get destroyed
// when packaging the application.
const isTreeHighlightPlugin =
e.value.hasOwnProperty('tree') &&
e.value.hasOwnProperty('decoratedTo') &&
e.value.hasOwnProperty('decorations')
e?.value &&
e.value?.hasOwnProperty('tree') &&
e.value?.hasOwnProperty('decoratedTo') &&
e.value?.hasOwnProperty('decorations')
if (isTreeHighlightPlugin) {
let originalUpdate = e.value.update
@ -161,20 +158,29 @@ export default class EditorManager {
}
}
/**
* Given an array of Diagnostics remove any duplicates by hashing a key
* in the format of from + ' ' + to + ' ' + message.
*/
makeUniqueDiagnostics(duplicatedDiagnostics: Diagnostic[]): Diagnostic[] {
const uniqueDiagnostics: Diagnostic[] = []
const seenDiagnostic: { [key: string]: boolean } = {}
duplicatedDiagnostics.forEach((diagnostic: Diagnostic) => {
const hash = `${diagnostic.from} ${diagnostic.to} ${diagnostic.message}`
if (!seenDiagnostic[hash]) {
uniqueDiagnostics.push(diagnostic)
seenDiagnostic[hash] = true
}
})
return uniqueDiagnostics
}
setDiagnostics(diagnostics: Diagnostic[]): void {
if (!this._editorView) return
// Clear out any existing diagnostics that are the same.
for (const diagnostic of diagnostics) {
for (const otherDiagnostic of diagnostics) {
if (diagnosticIsEqual(diagnostic, otherDiagnostic)) {
diagnostics = diagnostics.filter(
(d) => !diagnosticIsEqual(d, diagnostic)
)
diagnostics.push(diagnostic)
break
}
}
}
diagnostics = this.makeUniqueDiagnostics(diagnostics)
this._editorView.dispatch({
effects: [setDiagnosticsEffect.of(diagnostics)],

View File

@ -88,6 +88,10 @@ export function useEngineConnectionSubscriptions() {
? [codeRef.range]
: [codeRef.range, consumedCodeRef.range]
)
} else if (artifact?.type === 'plane') {
const codeRef = artifact.codeRef
if (err(codeRef)) return
editorManager.setHighlightRange([codeRef.range])
} else {
editorManager.setHighlightRange([[0, 0]])
}
@ -186,8 +190,42 @@ export function useEngineConnectionSubscriptions() {
})
return
}
const artifact =
engineCommandManager.artifactGraph.get(planeOrFaceId)
if (artifact?.type === 'plane') {
const planeInfo = await getFaceDetails(planeOrFaceId)
sceneInfra.modelingSend({
type: 'Select default plane',
data: {
type: 'offsetPlane',
zAxis: [
planeInfo.z_axis.x,
planeInfo.z_axis.y,
planeInfo.z_axis.z,
],
yAxis: [
planeInfo.y_axis.x,
planeInfo.y_axis.y,
planeInfo.y_axis.z,
],
position: [
planeInfo.origin.x,
planeInfo.origin.y,
planeInfo.origin.z,
].map((num) => num / sceneInfra._baseUnitMultiplier) as [
number,
number,
number
],
planeId: planeOrFaceId,
pathToNode: artifact.codeRef.pathToNode,
},
})
}
// Artifact is likely an extrusion face
const faceId = planeOrFaceId
const artifact = engineCommandManager.artifactGraph.get(faceId)
const extrusion = getSweepFromSuspectedSweepSurface(
faceId,
engineCommandManager.artifactGraph

View File

@ -2,13 +2,13 @@ import {
SetVarNameModal,
createSetVarNameModal,
} from 'components/SetVarNameModal'
import { editorManager, kclManager } from 'lib/singletons'
import { reportRejection, trap } from 'lib/trap'
import { editorManager, kclManager, codeManager } from 'lib/singletons'
import { reportRejection, trap, err } from 'lib/trap'
import { moveValueIntoNewVariable } from 'lang/modifyAst'
import { isNodeSafeToReplace } from 'lang/queryAst'
import { useEffect, useState } from 'react'
import { useModelingContext } from './useModelingContext'
import { PathToNode, SourceRange } from 'lang/wasm'
import { PathToNode, SourceRange, recast } from 'lang/wasm'
import { useKclContext } from 'lang/KclProvider'
import { toSync } from 'lib/utils'
@ -57,6 +57,11 @@ export function useConvertToVariable(range?: SourceRange) {
)
await kclManager.updateAst(_modifiedAst, true)
const newCode = recast(_modifiedAst)
if (err(newCode)) return
codeManager.updateCodeEditor(newCode)
return pathToReplacedNode
} catch (e) {
console.log('error', e)

View File

@ -125,7 +125,7 @@ export class KclManager {
if (this.lints.length > 0) {
diagnostics = diagnostics.concat(this.lints)
}
editorManager.setDiagnostics(diagnostics)
editorManager?.setDiagnostics(diagnostics)
}
addKclErrors(kclErrors: KCLError[]) {
@ -357,9 +357,6 @@ export class KclManager {
this.clearAst()
return
}
codeManager.updateCodeEditor(newCode)
// Write the file to disk.
await codeManager.writeToFile()
this._ast = { ...newAst }
const { logs, errors, execState } = await executeAst({
@ -497,11 +494,6 @@ export class KclManager {
}
if (execute) {
// Call execute on the set ast.
// Update the code state and editor.
codeManager.updateCodeEditor(newCode)
// Write the file to disk.
await codeManager.writeToFile()
await this.executeAst({
ast: astWithUpdatedSource,
zoomToFit: optionalParams?.zoomToFit,

View File

@ -18,8 +18,7 @@ const mySketch001 = startSketchOn('XY')
// @ts-ignore
const sketch001 = execState.memory.get('mySketch001')
expect(sketch001).toEqual({
type: 'UserVal',
__meta: [{ sourceRange: [46, 71, 0] }],
type: 'Sketch',
value: {
type: 'Sketch',
on: expect.any(Object),

View File

@ -6,12 +6,17 @@ import { isDesktop } from 'lib/isDesktop'
import toast from 'react-hot-toast'
import { editorManager } from 'lib/singletons'
import { Annotation, Transaction } from '@codemirror/state'
import { KeyBinding } from '@codemirror/view'
import { EditorView, KeyBinding } from '@codemirror/view'
import { recast, Program } from 'lang/wasm'
import { err } from 'lib/trap'
import { Compartment } from '@codemirror/state'
import { history } from '@codemirror/commands'
const PERSIST_CODE_KEY = 'persistCode'
const codeManagerUpdateAnnotation = Annotation.define<boolean>()
export const codeManagerUpdateEvent = codeManagerUpdateAnnotation.of(true)
export const codeManagerHistoryCompartment = new Compartment()
export default class CodeManager {
private _code: string = bracket
@ -88,9 +93,12 @@ export default class CodeManager {
/**
* Update the code in the editor.
*/
updateCodeEditor(code: string): void {
updateCodeEditor(code: string, clearHistory?: boolean): void {
this.code = code
if (editorManager.editorView) {
if (clearHistory) {
clearCodeMirrorHistory(editorManager.editorView)
}
editorManager.editorView.dispatch({
changes: {
from: 0,
@ -99,7 +107,7 @@ export default class CodeManager {
},
annotations: [
codeManagerUpdateEvent,
Transaction.addToHistory.of(true),
Transaction.addToHistory.of(!clearHistory),
],
})
}
@ -108,11 +116,11 @@ export default class CodeManager {
/**
* Update the code, state, and the code the code mirror editor sees.
*/
updateCodeStateEditor(code: string): void {
updateCodeStateEditor(code: string, clearHistory?: boolean): void {
if (this._code !== code) {
this.code = code
this.#updateState(code)
this.updateCodeEditor(code)
this.updateCodeEditor(code, clearHistory)
}
}
@ -147,6 +155,13 @@ export default class CodeManager {
safeLSSetItem(PERSIST_CODE_KEY, this.code)
}
}
async updateEditorWithAstAndWriteToFile(ast: Program) {
const newCode = recast(ast)
if (err(newCode)) return
this.updateCodeStateEditor(newCode)
await this.writeToFile()
}
}
function safeLSGetItem(key: string) {
@ -158,3 +173,17 @@ function safeLSSetItem(key: string, value: string) {
if (typeof window === 'undefined') return
localStorage?.setItem(key, value)
}
function clearCodeMirrorHistory(view: EditorView) {
// Clear history
view.dispatch({
effects: [codeManagerHistoryCompartment.reconfigure([])],
annotations: [codeManagerUpdateEvent],
})
// Add history back
view.dispatch({
effects: [codeManagerHistoryCompartment.reconfigure([history()])],
annotations: [codeManagerUpdateEvent],
})
}

View File

@ -58,7 +58,13 @@ const newVar = myVar + 1`
`
const mem = await exe(code)
// geo is three js buffer geometry and is very bloated to have in tests
const minusGeo = mem.get('mySketch')?.value?.paths
const sk = mem.get('mySketch')
expect(sk?.type).toEqual('Sketch')
if (sk?.type !== 'Sketch') {
return
}
const minusGeo = sk?.value?.paths
expect(minusGeo).toEqual([
{
type: 'ToPoint',
@ -150,7 +156,7 @@ const newVar = myVar + 1`
].join('\n')
const mem = await exe(code)
expect(mem.get('mySk1')).toEqual({
type: 'UserVal',
type: 'Sketch',
value: {
type: 'Sketch',
on: expect.any(Object),
@ -215,7 +221,6 @@ const newVar = myVar + 1`
id: expect.any(String),
__meta: [{ sourceRange: [39, 63, 0] }],
},
__meta: [{ sourceRange: [39, 63, 0] }],
})
})
it('execute array expression', async () => {
@ -225,7 +230,7 @@ const newVar = myVar + 1`
const mem = await exe(code)
// TODO path to node is probably wrong here, zero indexes are not correct
expect(mem.get('three')).toEqual({
type: 'UserVal',
type: 'Int',
value: 3,
__meta: [
{
@ -234,8 +239,17 @@ const newVar = myVar + 1`
],
})
expect(mem.get('yo')).toEqual({
type: 'UserVal',
value: [1, '2', 3, 9],
type: 'Array',
value: [
{ type: 'Int', value: 1, __meta: [{ sourceRange: [28, 29, 0] }] },
{ type: 'String', value: '2', __meta: [{ sourceRange: [31, 34, 0] }] },
{ type: 'Int', value: 3, __meta: [{ sourceRange: [14, 15, 0] }] },
{
type: 'Number',
value: 9,
__meta: [{ sourceRange: [43, 44, 0] }, { sourceRange: [47, 48, 0] }],
},
],
__meta: [
{
sourceRange: [27, 49, 0],
@ -253,8 +267,25 @@ const newVar = myVar + 1`
].join('\n')
const mem = await exe(code)
expect(mem.get('yo')).toEqual({
type: 'UserVal',
value: { aStr: 'str', anum: 2, identifier: 3, binExp: 9 },
type: 'Object',
value: {
aStr: {
type: 'String',
value: 'str',
__meta: [{ sourceRange: [34, 39, 0] }],
},
anum: { type: 'Int', value: 2, __meta: [{ sourceRange: [47, 48, 0] }] },
identifier: {
type: 'Int',
value: 3,
__meta: [{ sourceRange: [14, 15, 0] }],
},
binExp: {
type: 'Number',
value: 9,
__meta: [{ sourceRange: [77, 78, 0] }, { sourceRange: [81, 82, 0] }],
},
},
__meta: [
{
sourceRange: [27, 83, 0],
@ -268,11 +299,11 @@ const newVar = myVar + 1`
)
const mem = await exe(code)
expect(mem.get('myVar')).toEqual({
type: 'UserVal',
type: 'String',
value: '123',
__meta: [
{
sourceRange: [41, 50, 0],
sourceRange: [19, 24, 0],
},
],
})
@ -356,7 +387,26 @@ describe('testing math operators', () => {
it('with unaryExpression in ArrayExpression', async () => {
const code = 'const myVar = [1,-legLen(5, 4)]'
const mem = await exe(code)
expect(mem.get('myVar')?.value).toEqual([1, -3])
expect(mem.get('myVar')?.value).toEqual([
{
__meta: [
{
sourceRange: [15, 16, 0],
},
],
type: 'Int',
value: 1,
},
{
__meta: [
{
sourceRange: [17, 30, 0],
},
],
type: 'Number',
value: -3,
},
])
})
it('with unaryExpression in ArrayExpression in CallExpression, checking nothing funny happens when used in a sketch', async () => {
const code = [

View File

@ -55,18 +55,13 @@ describe('Test KCL Samples from public Github repository', () => {
})
// Run through all of the files in the manifest json. This will allow us to be automatically updated
// with the latest changes in github. We won't be hard coding the filenames
it(
'should run through all the files',
async () => {
for (let i = 0; i < files.length; i++) {
const file: KclSampleFile = files[i]
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))
}
},
files.length * 1000
)
}, 1000)
})
})
describe('when performing enginelessExecutor', () => {

View File

@ -8,6 +8,7 @@ import {
VariableDeclarator,
Expr,
Literal,
LiteralValue,
PipeSubstitution,
Identifier,
ArrayExpression,
@ -18,6 +19,7 @@ import {
ProgramMemory,
SourceRange,
sketchFromKclValue,
isPathToNodeNumber,
} from './wasm'
import {
isNodeSafeToReplacePath,
@ -525,6 +527,60 @@ export function sketchOnExtrudedFace(
}
}
/**
* Modify the AST to create a new sketch using the variable declaration
* of an offset plane. The new sketch just has to come after the offset
* plane declaration.
*/
export function sketchOnOffsetPlane(
node: Node<Program>,
offsetPathToNode: PathToNode
) {
let _node = { ...node }
// Find the offset plane declaration
const offsetPlaneDeclarator = getNodeFromPath<VariableDeclarator>(
_node,
offsetPathToNode,
'VariableDeclarator',
true
)
if (err(offsetPlaneDeclarator)) return offsetPlaneDeclarator
const { node: offsetPlaneNode } = offsetPlaneDeclarator
const offsetPlaneName = offsetPlaneNode.id.name
// Create a new sketch declaration
const newSketchName = findUniqueName(
node,
KCL_DEFAULT_CONSTANT_PREFIXES.SKETCH
)
const newSketch = createVariableDeclaration(
newSketchName,
createCallExpressionStdLib('startSketchOn', [
createIdentifier(offsetPlaneName),
]),
undefined,
'const'
)
// Decide where to insert the new sketch declaration
const offsetIndex = offsetPathToNode[1][0]
if (!isPathToNodeNumber(offsetIndex)) {
return new Error('Expected offsetIndex to be a number')
}
// and insert it
_node.body.splice(offsetIndex + 1, 0, newSketch)
const newPathToNode = structuredClone(offsetPathToNode)
newPathToNode[1][0] = offsetIndex + 1
// Return the modified AST and the path to the new sketch declaration
return {
modifiedAst: _node,
pathToNode: newPathToNode,
}
}
export const getLastIndex = (pathToNode: PathToNode): number =>
splitPathAtLastIndex(pathToNode).index
@ -573,7 +629,7 @@ export function splitPathAtPipeExpression(pathToNode: PathToNode): {
return splitPathAtPipeExpression(pathToNode.slice(0, -1))
}
export function createLiteral(value: string | number): Node<Literal> {
export function createLiteral(value: LiteralValue): Node<Literal> {
return {
type: 'Literal',
start: 0,

View File

@ -77,15 +77,21 @@ const runGetPathToExtrudeForSegmentSelectionTest = async (
code.indexOf(expectedExtrudeSnippet),
code.indexOf(expectedExtrudeSnippet) + expectedExtrudeSnippet.length,
]
const expedtedExtrudePath = getNodePathFromSourceRange(ast, extrudeRange)
const expedtedExtrudeNodeResult = getNodeFromPath<VariableDeclarator>(
ast,
expedtedExtrudePath
)
if (err(expedtedExtrudeNodeResult)) {
return expedtedExtrudeNodeResult
const expectedExtrudePath = getNodePathFromSourceRange(ast, extrudeRange)
const expectedExtrudeNodeResult = getNodeFromPath<
VariableDeclarator | CallExpression
>(ast, expectedExtrudePath)
if (err(expectedExtrudeNodeResult)) {
return expectedExtrudeNodeResult
}
const expectedExtrudeNode = expedtedExtrudeNodeResult.node
const expectedExtrudeNode = expectedExtrudeNodeResult.node
// check whether extrude is in the sketch pipe
const extrudeInSketchPipe = expectedExtrudeNode.type === 'CallExpression'
if (extrudeInSketchPipe) {
return expectedExtrudeNode
}
if (!extrudeInSketchPipe) {
const init = expectedExtrudeNode.init
if (init.type !== 'CallExpression' && init.type !== 'PipeExpression') {
return new Error(
@ -94,6 +100,8 @@ const runGetPathToExtrudeForSegmentSelectionTest = async (
}
return init
}
return new Error('Expected extrude expression not found')
}
// ast
const astOrError = parse(code)
@ -160,6 +168,23 @@ extrude001 = extrude(-15, sketch001)`
expectedExtrudeSnippet
)
}, 5_000)
it('should return the correct paths when extrusion occurs within the sketch pipe', async () => {
const code = `sketch001 = startSketchOn('XY')
|> startProfileAt([-10, 10], %)
|> line([20, 0], %)
|> line([0, -20], %)
|> line([-20, 0], %)
|> lineTo([profileStartX(%), profileStartY(%)], %)
|> close(%)
|> extrude(15, %)`
const selectedSegmentSnippet = `line([20, 0], %)`
const expectedExtrudeSnippet = `extrude(15, %)`
await runGetPathToExtrudeForSegmentSelectionTest(
code,
selectedSegmentSnippet,
expectedExtrudeSnippet
)
}, 5_000)
it('should return the correct paths for a valid selection and extrusion in case of several extrusions and sketches', async () => {
const code = `sketch001 = startSketchOn('XY')
|> startProfileAt([-30, 30], %)
@ -296,6 +321,34 @@ extrude001 = extrude(-15, sketch001)`
|> lineTo([profileStartX(%), profileStartY(%)], %)
|> close(%)
extrude001 = extrude(-15, sketch001)
|> fillet({ radius: 3, tags: [seg01] }, %)`
await runModifyAstCloneWithFilletAndTag(
code,
segmentSnippets,
radiusValue,
expectedCode
)
})
it('should add a fillet to the sketch pipe', async () => {
const code = `sketch001 = startSketchOn('XY')
|> startProfileAt([-10, 10], %)
|> line([20, 0], %)
|> line([0, -20], %)
|> line([-20, 0], %)
|> lineTo([profileStartX(%), profileStartY(%)], %)
|> close(%)
|> extrude(-15, %)`
const segmentSnippets = ['line([0, -20], %)']
const radiusValue = 3
const expectedCode = `sketch001 = startSketchOn('XY')
|> startProfileAt([-10, 10], %)
|> line([20, 0], %)
|> line([0, -20], %, $seg01)
|> line([-20, 0], %)
|> lineTo([profileStartX(%), profileStartY(%)], %)
|> close(%)
|> extrude(-15, %)
|> fillet({ radius: 3, tags: [seg01] }, %)`
await runModifyAstCloneWithFilletAndTag(

View File

@ -35,7 +35,12 @@ import {
ArtifactGraph,
getSweepFromSuspectedPath,
} from 'lang/std/artifactGraph'
import { kclManager, engineCommandManager, editorManager } from 'lib/singletons'
import {
kclManager,
engineCommandManager,
editorManager,
codeManager,
} from 'lib/singletons'
import { Node } from 'wasm-lib/kcl/bindings/Node'
// Apply Fillet To Selection
@ -141,7 +146,7 @@ export function modifyAstCloneWithFilletAndTag(
// Modify the extrude expression to include this fillet expression
// CallExpression - no fillet
// PipeExpression - fillet exists
// PipeExpression - fillet exists or extrude in sketch pipe
let pathToFilletNode: PathToNode = []
@ -162,15 +167,7 @@ export function modifyAstCloneWithFilletAndTag(
)
pathToFilletNodes.push(pathToFilletNode)
} else if (extrudeDeclarator.init.type === 'PipeExpression') {
// 2. case when fillet exists
const existingFilletCall = extrudeDeclarator.init.body.find((node) => {
return node.type === 'CallExpression' && node.callee.name === 'fillet'
})
if (!existingFilletCall || existingFilletCall.type !== 'CallExpression') {
return new Error('Fillet CallExpression not found.')
}
// 2. case when fillet exists or extrude in sketch pipe
// mutate the extrude node with the new fillet call
extrudeDeclarator.init.body.push(filletCall)
@ -253,6 +250,9 @@ async function updateAstAndFocus(
const updatedAst = await kclManager.updateAst(modifiedAst, true, {
focusPath: pathToFilletNode,
})
await codeManager.updateEditorWithAstAndWriteToFile(updatedAst.newAst)
if (updatedAst?.selections) {
editorManager.selectRange(updatedAst?.selections)
}
@ -309,14 +309,14 @@ function locateExtrudeDeclarator(
node: Program,
pathToExtrudeNode: PathToNode
): { extrudeDeclarator: VariableDeclarator } | Error {
const extrudeChunk = getNodeFromPath<VariableDeclaration>(
const nodeOfExtrudeCall = getNodeFromPath<VariableDeclaration>(
node,
pathToExtrudeNode,
'VariableDeclaration'
)
if (err(extrudeChunk)) return extrudeChunk
if (err(nodeOfExtrudeCall)) return nodeOfExtrudeCall
const { node: extrudeVarDecl } = extrudeChunk
const { node: extrudeVarDecl } = nodeOfExtrudeCall
const extrudeDeclarator = extrudeVarDecl.declarations[0]
if (!extrudeDeclarator) {
return new Error('Extrude Declarator not found.')

View File

@ -1,4 +1,4 @@
import { parse, recast, initPromise, PathToNode } from './wasm'
import { parse, recast, initPromise, PathToNode, Identifier } from './wasm'
import {
findAllPreviousVariables,
isNodeSafeToReplace,
@ -10,6 +10,7 @@ import {
hasSketchPipeBeenExtruded,
doesSceneHaveSweepableSketch,
traverse,
getNodeFromPath,
} from './queryAst'
import { enginelessExecutor } from '../lib/testHelpers'
import {
@ -266,6 +267,86 @@ describe('testing getNodePathFromSourceRange', () => {
])
expect(selectWholeThing).toEqual(expected)
})
it('finds the node in if-else condition', () => {
const code = `y = 0
x = if x > y {
x + 1
} else {
y
}`
const searchLn = `x > y`
const sourceIndex = code.indexOf(searchLn)
const ast = parse(code)
if (err(ast)) throw ast
const result = getNodePathFromSourceRange(ast, [sourceIndex, sourceIndex])
expect(result).toEqual([
['body', ''],
[1, 'index'],
['declarations', 'VariableDeclaration'],
[0, 'index'],
['init', ''],
['cond', 'IfExpression'],
['left', 'BinaryExpression'],
])
const _node = getNodeFromPath<Identifier>(ast, result)
if (err(_node)) throw _node
expect(_node.node.type).toEqual('Identifier')
expect(_node.node.name).toEqual('x')
})
it('finds the node in if-else then', () => {
const code = `y = 0
x = if x > y {
x + 1
} else {
y
}`
const searchLn = `x + 1`
const sourceIndex = code.indexOf(searchLn)
const ast = parse(code)
if (err(ast)) throw ast
const result = getNodePathFromSourceRange(ast, [sourceIndex, sourceIndex])
expect(result).toEqual([
['body', ''],
[1, 'index'],
['declarations', 'VariableDeclaration'],
[0, 'index'],
['init', ''],
['then_val', 'IfExpression'],
['body', 'IfExpression'],
[0, 'index'],
['expression', 'ExpressionStatement'],
['left', 'BinaryExpression'],
])
const _node = getNodeFromPath<Identifier>(ast, result)
if (err(_node)) throw _node
expect(_node.node.type).toEqual('Identifier')
expect(_node.node.name).toEqual('x')
})
it('finds the node in import statement item', () => {
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 result = getNodePathFromSourceRange(ast, [sourceIndex, sourceIndex])
expect(result).toEqual([
['body', ''],
[0, 'index'],
['items', 'ImportStatement'],
[1, 'index'],
['name', 'ImportItem'],
])
const _node = getNodeFromPath<Identifier>(ast, result)
if (err(_node)) throw _node
expect(_node.node.type).toEqual('Identifier')
expect(_node.node.name).toEqual('bar')
})
})
describe('testing doesPipeHave', () => {
@ -449,14 +530,25 @@ describe('Testing hasSketchPipeBeenExtruded', () => {
|> line([-17.67, 0.85], %)
|> close(%)
extrude001 = extrude(10, sketch001)
sketch002 = startSketchOn(extrude001, $seg01)
sketch002 = startSketchOn(extrude001, seg01)
|> startProfileAt([-12.94, 6.6], %)
|> line([2.45, -0.2], %)
|> line([-2, -1.25], %)
|> lineTo([profileStartX(%), profileStartY(%)], %)
|> close(%)
sketch003 = startSketchOn(extrude001, 'END')
|> startProfileAt([8.14, 2.8], %)
|> line([-1.24, 4.39], %)
|> line([3.79, 1.91], %)
|> line([1.77, -2.95], %)
|> line([3.12, 1.74], %)
|> line([1.91, -4.09], %)
|> line([-5.6, -2.75], %)
|> lineTo([profileStartX(%), profileStartY(%)], %)
|> close(%)
|> extrude(3.14, %)
`
it('finds sketch001 pipe to be extruded', async () => {
it('identifies sketch001 pipe as extruded (extrusion after pipe)', async () => {
const ast = parse(exampleCode)
if (err(ast)) throw ast
const lineOfInterest = `line([4.99, -0.46], %, $seg01)`
@ -471,7 +563,7 @@ sketch002 = startSketchOn(extrude001, $seg01)
)
expect(extruded).toBeTruthy()
})
it('find sketch002 NOT pipe to be extruded', async () => {
it('identifies sketch002 pipe as not extruded', async () => {
const ast = parse(exampleCode)
if (err(ast)) throw ast
const lineOfInterest = `line([2.45, -0.2], %)`
@ -486,6 +578,21 @@ sketch002 = startSketchOn(extrude001, $seg01)
)
expect(extruded).toBeFalsy()
})
it('identifies sketch003 pipe as extruded (extrusion within pipe)', async () => {
const ast = parse(exampleCode)
if (err(ast)) throw ast
const lineOfInterest = `|> line([3.12, 1.74], %)`
const characterIndex =
exampleCode.indexOf(lineOfInterest) + lineOfInterest.length
const extruded = hasSketchPipeBeenExtruded(
{
range: [characterIndex, characterIndex],
type: 'default',
},
ast
)
expect(extruded).toBeTruthy()
})
})
describe('Testing doesSceneHaveSweepableSketch', () => {

View File

@ -14,6 +14,7 @@ import {
ProgramMemory,
ReturnStatement,
sketchFromKclValue,
sketchFromKclValueOptional,
SourceRange,
SyntaxType,
VariableDeclaration,
@ -27,7 +28,7 @@ import {
getConstraintLevelFromSourceRange,
getConstraintType,
} from './std/sketchcombos'
import { err } from 'lib/trap'
import { err, Reason } from 'lib/trap'
import { ImportStatement } from 'wasm-lib/kcl/bindings/ImportStatement'
import { Node } from 'wasm-lib/kcl/bindings/Node'
@ -317,6 +318,62 @@ function moreNodePathFromSourceRange(
}
if (_node.type === 'PipeSubstitution' && isInRange) return path
if (_node.type === 'IfExpression' && isInRange) {
const { cond, then_val, else_ifs, final_else } = _node
if (cond.start <= start && cond.end >= end) {
path.push(['cond', 'IfExpression'])
return moreNodePathFromSourceRange(cond, sourceRange, path)
}
if (then_val.start <= start && then_val.end >= end) {
path.push(['then_val', 'IfExpression'])
path.push(['body', 'IfExpression'])
return getNodePathFromSourceRange(then_val, sourceRange, path)
}
for (let i = 0; i < else_ifs.length; i++) {
const else_if = else_ifs[i]
if (else_if.start <= start && else_if.end >= end) {
path.push(['else_ifs', 'IfExpression'])
path.push([i, 'index'])
const { cond, then_val } = else_if
if (cond.start <= start && cond.end >= end) {
path.push(['cond', 'IfExpression'])
return moreNodePathFromSourceRange(cond, sourceRange, path)
}
path.push(['then_val', 'IfExpression'])
path.push(['body', 'IfExpression'])
return getNodePathFromSourceRange(then_val, sourceRange, path)
}
}
if (final_else.start <= start && final_else.end >= end) {
path.push(['final_else', 'IfExpression'])
path.push(['body', 'IfExpression'])
return getNodePathFromSourceRange(final_else, sourceRange, path)
}
return path
}
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'])
return path
}
if (item.alias && item.alias.start <= start && item.alias.end >= end) {
path.push(['alias', 'ImportItem'])
return path
}
return path
}
}
return path
}
console.error('not implemented: ' + node.type)
return path
@ -790,7 +847,8 @@ export function hasExtrudeSketch({
const varName = varDec.declarations[0].id.name
const varValue = programMemory?.get(varName)
return (
varValue?.type === 'Solid' || !err(sketchFromKclValue(varValue, varName))
varValue?.type === 'Solid' ||
!(sketchFromKclValueOptional(varValue, varName) instanceof Reason)
)
}
@ -871,7 +929,11 @@ export function findUsesOfTagInPipe(
export function hasSketchPipeBeenExtruded(selection: Selection, ast: Program) {
const path = getNodePathFromSourceRange(ast, selection.range)
const _node = getNodeFromPath<PipeExpression>(ast, path, 'PipeExpression')
const _node = getNodeFromPath<Node<PipeExpression>>(
ast,
path,
'PipeExpression'
)
if (err(_node)) return false
const { node: pipeExpression } = _node
if (pipeExpression.type !== 'PipeExpression') return false
@ -884,6 +946,19 @@ export function hasSketchPipeBeenExtruded(selection: Selection, ast: Program) {
const varDec = _varDec.node
if (varDec.type !== 'VariableDeclarator') return false
let extruded = false
// option 1: extrude or revolve is called in the sketch pipe
traverse(pipeExpression, {
enter(node) {
if (
node.type === 'CallExpression' &&
(node.callee.name === 'extrude' || node.callee.name === 'revolve')
) {
extruded = true
}
},
})
// option 2: extrude or revolve is called in the separate pipe
if (!extruded) {
traverse(ast as any, {
enter(node) {
if (
@ -897,6 +972,7 @@ export function hasSketchPipeBeenExtruded(selection: Selection, ast: Program) {
}
},
})
}
return extruded
}

View File

@ -98,12 +98,22 @@ sketch004 = startSketchOn(extrude003, seg02)
|> close(%)
extrude004 = extrude(3, sketch004)
`
const exampleCodeOffsetPlanes = `
offsetPlane001 = offsetPlane("XY", 20)
offsetPlane002 = offsetPlane("XZ", -50)
offsetPlane003 = offsetPlane("YZ", 10)
sketch002 = startSketchOn(offsetPlane001)
|> startProfileAt([0, 0], %)
|> line([6.78, 15.01], %)
`
// add more code snippets here and use `getCommands` to get the orderedCommands and responseMap for more tests
const codeToWriteCacheFor = {
exampleCode1,
sketchOnFaceOnFaceEtc,
exampleCodeNo3D,
exampleCodeOffsetPlanes,
} as const
type CodeKey = keyof typeof codeToWriteCacheFor
@ -165,6 +175,52 @@ afterAll(() => {
})
describe('testing createArtifactGraph', () => {
describe('code with offset planes and a sketch:', () => {
let ast: Program
let theMap: ReturnType<typeof createArtifactGraph>
it('setup', () => {
// putting this logic in here because describe blocks runs before beforeAll has finished
const {
orderedCommands,
responseMap,
ast: _ast,
} = getCommands('exampleCodeOffsetPlanes')
ast = _ast
theMap = createArtifactGraph({ orderedCommands, responseMap, ast })
})
it(`there should be one sketch`, () => {
const sketches = [...filterArtifacts({ types: ['path'] }, theMap)].map(
(path) => expandPath(path[1], theMap)
)
expect(sketches).toHaveLength(1)
sketches.forEach((path) => {
if (err(path)) throw path
expect(path.type).toBe('path')
})
})
it(`there should be three offsetPlanes`, () => {
const offsetPlanes = [
...filterArtifacts({ types: ['plane'] }, theMap),
].map((plane) => expandPlane(plane[1], theMap))
expect(offsetPlanes).toHaveLength(3)
offsetPlanes.forEach((path) => {
expect(path.type).toBe('plane')
})
})
it(`Only one offset plane should have a path`, () => {
const offsetPlanes = [
...filterArtifacts({ types: ['plane'] }, theMap),
].map((plane) => expandPlane(plane[1], theMap))
const offsetPlaneWithPaths = offsetPlanes.filter(
(plane) => plane.paths.length
)
expect(offsetPlaneWithPaths).toHaveLength(1)
})
})
describe('code with an extrusion, fillet and sketch of face:', () => {
let ast: Program
let theMap: ReturnType<typeof createArtifactGraph>

View File

@ -249,7 +249,20 @@ export function getArtifactsToUpdate({
const cmd = command.cmd
const returnArr: ReturnType<typeof getArtifactsToUpdate> = []
if (!response) return returnArr
if (cmd.type === 'enable_sketch_mode') {
if (cmd.type === 'make_plane' && range[1] !== 0) {
// If we're calling `make_plane` and the code range doesn't end at `0`
// it's not a default plane, but a custom one from the offsetPlane standard library function
return [
{
id,
artifact: {
type: 'plane',
pathIds: [],
codeRef: { range, pathToNode },
},
},
]
} else if (cmd.type === 'enable_sketch_mode') {
const plane = getArtifact(currentPlaneId)
const pathIds = plane?.type === 'plane' ? plane?.pathIds : []
const codeRef =
@ -633,7 +646,7 @@ export function expandSweep(
if (err(path)) return path
return {
type: 'sweep',
subType: 'extrusion',
subType: sweep.subType,
surfaces: Array.from(surfs.values()),
edges: Array.from(edges.values()),
path,

View File

@ -1631,7 +1631,11 @@ export class EngineCommandManager extends EventTarget {
switch (this.exportInfo.intent) {
case ExportIntent.Save: {
exportSave(event.data, this.pendingExport.toastId).then(() => {
exportSave({
data: event.data,
fileName: this.exportInfo.name,
toastId: this.pendingExport.toastId,
}).then(() => {
this.pendingExport?.resolve(null)
}, this.pendingExport?.reject)
break

View File

@ -1,5 +1,12 @@
import { Selections } from 'lib/selections'
import { Program, PathToNode } from './wasm'
import {
Program,
PathToNode,
CallExpression,
Literal,
ArrayExpression,
BinaryExpression,
} from './wasm'
import { getNodeFromPath } from './queryAst'
import { ArtifactGraph, filterArtifacts } from 'lang/std/artifactGraph'
import { isOverlap } from 'lib/utils'
@ -84,3 +91,19 @@ export function isCursorInSketchCommandRange(
([, artifact]) => artifact.type === 'path'
)?.[0] || false
}
export function isCallExpression(e: any): e is CallExpression {
return e && e.type === 'CallExpression'
}
export function isArrayExpression(e: any): e is ArrayExpression {
return e && e.type === 'ArrayExpression'
}
export function isLiteral(e: any): e is Literal {
return e && e.type === 'Literal'
}
export function isBinaryExpression(e: any): e is BinaryExpression {
return e && e.type === 'BinaryExpression'
}

View File

@ -32,7 +32,7 @@ import { CoreDumpManager } from 'lib/coredump'
import openWindow from 'lib/openWindow'
import { DefaultPlanes } from 'wasm-lib/kcl/bindings/DefaultPlanes'
import { TEST } from 'env'
import { err } from 'lib/trap'
import { err, Reason } from 'lib/trap'
import { Configuration } from 'wasm-lib/kcl/bindings/Configuration'
import { DeepPartial } from 'lib/types'
import { ProjectConfiguration } from 'wasm-lib/kcl/bindings/ProjectConfiguration'
@ -62,6 +62,7 @@ export type { CallExpression } from '../wasm-lib/kcl/bindings/CallExpression'
export type { VariableDeclarator } from '../wasm-lib/kcl/bindings/VariableDeclarator'
export type { BinaryPart } from '../wasm-lib/kcl/bindings/BinaryPart'
export type { Literal } from '../wasm-lib/kcl/bindings/Literal'
export type { LiteralValue } from '../wasm-lib/kcl/bindings/LiteralValue'
export type { ArrayExpression } from '../wasm-lib/kcl/bindings/ArrayExpression'
export type SyntaxType =
@ -81,6 +82,7 @@ export type SyntaxType =
| 'PipeExpression'
| 'PipeSubstitution'
| 'Literal'
| 'LiteralValue'
| 'NonCodeNode'
| 'UnaryExpression'
@ -142,6 +144,12 @@ export const parse = (code: string | Error): Node<Program> | Error => {
export type PathToNode = [string | number, string][]
export const isPathToNodeNumber = (
pathToNode: string | number
): pathToNode is number => {
return typeof pathToNode === 'number'
}
export interface ExecState {
memory: ProgramMemory
idGenerator: IdGenerator
@ -336,7 +344,7 @@ export class ProgramMemory {
*/
hasSketchOrSolid(): boolean {
for (const node of this.visibleEntries().values()) {
if (node.type === 'Solid' || node.value?.type === 'Sketch') {
if (node.type === 'Solid' || node.type === 'Sketch') {
return true
}
}
@ -357,10 +365,10 @@ export class ProgramMemory {
}
// TODO: In the future, make the parameter be a KclValue.
export function sketchFromKclValue(
export function sketchFromKclValueOptional(
obj: any,
varName: string | null
): Sketch | Error {
): Sketch | Reason {
if (obj?.value?.type === 'Sketch') return obj.value
if (obj?.value?.type === 'Solid') return obj.value.sketch
if (obj?.type === 'Solid') return obj.sketch
@ -369,15 +377,26 @@ export function sketchFromKclValue(
}
const actualType = obj?.value?.type ?? obj?.type
if (actualType) {
console.log(obj)
return new Error(
return new Reason(
`Expected ${varName} to be a sketch or solid, but it was ${actualType} instead.`
)
} else {
return new Error(`Expected ${varName} to be a sketch, but it wasn't.`)
return new Reason(`Expected ${varName} to be a sketch, but it wasn't.`)
}
}
// TODO: In the future, make the parameter be a KclValue.
export function sketchFromKclValue(
obj: any,
varName: string | null
): Sketch | Error {
const result = sketchFromKclValueOptional(obj, varName)
if (result instanceof Reason) {
return result.toError()
}
return result
}
export const executor = async (
node: Node<Program>,
programMemory: ProgramMemory | Error = ProgramMemory.empty(),

View File

@ -68,7 +68,16 @@ const save_ = async (file: ModelingAppFile, toastId: string) => {
}
// Saves files locally from an export call.
export async function exportSave(data: ArrayBuffer, toastId: string) {
// We override the file's name with one passed in from the client side.
export async function exportSave({
data,
fileName,
toastId,
}: {
data: ArrayBuffer
fileName: string
toastId: string
}) {
// This converts the ArrayBuffer to a Rust equivalent Vec<u8>.
let uintArray = new Uint8Array(data)
@ -80,9 +89,10 @@ export async function exportSave(data: ArrayBuffer, toastId: string) {
zip.file(file.name, new Uint8Array(file.contents), { binary: true })
}
return zip.generateAsync({ type: 'array' }).then((contents) => {
return save_({ name: 'output.zip', contents }, toastId)
return save_({ name: `${fileName || 'output'}.zip`, contents }, toastId)
})
} else {
files[0].name = fileName || files[0].name
return save_(files[0], toastId)
}
}

View File

@ -9,8 +9,16 @@ import {
createUnaryExpression,
} from 'lang/modifyAst'
import { ArrayExpression, CallExpression, PipeExpression } from 'lang/wasm'
import { roundOff } from 'lib/utils'
import {
isCallExpression,
isArrayExpression,
isLiteral,
isBinaryExpression,
} from 'lang/util'
/**
* It does not create the startSketchOn and it does not create the startProfileAt.
* Returns AST expressions for this KCL code:
* const yo = startSketchOn('XY')
* |> startProfileAt([0, 0], %)
@ -92,3 +100,69 @@ export function updateRectangleSketch(
createLiteral(Math.abs(y)), // This will be the height of the rectangle
])
}
/**
* Mutates the pipeExpression to update the center rectangle sketch
* @param pipeExpression
* @param x
* @param y
* @param tag
*/
export function updateCenterRectangleSketch(
pipeExpression: PipeExpression,
deltaX: number,
deltaY: number,
tag: string,
originX: number,
originY: number
) {
let startX = originX - Math.abs(deltaX)
let startY = originY - Math.abs(deltaY)
// pipeExpression.body[1] is startProfileAt
let callExpression = pipeExpression.body[1]
if (isCallExpression(callExpression)) {
const arrayExpression = callExpression.arguments[0]
if (isArrayExpression(arrayExpression)) {
callExpression.arguments[0] = createArrayExpression([
createLiteral(roundOff(startX)),
createLiteral(roundOff(startY)),
])
}
}
const twoX = deltaX * 2
const twoY = deltaY * 2
callExpression = pipeExpression.body[2]
if (isCallExpression(callExpression)) {
const arrayExpression = callExpression.arguments[0]
if (isArrayExpression(arrayExpression)) {
const literal = arrayExpression.elements[0]
if (isLiteral(literal)) {
callExpression.arguments[0] = createArrayExpression([
createLiteral(literal.value),
createLiteral(Math.abs(twoX)),
])
}
}
}
callExpression = pipeExpression.body[3]
if (isCallExpression(callExpression)) {
const arrayExpression = callExpression.arguments[0]
if (isArrayExpression(arrayExpression)) {
const binaryExpression = arrayExpression.elements[0]
if (isBinaryExpression(binaryExpression)) {
callExpression.arguments[0] = createArrayExpression([
createBinaryExpression([
createCallExpressionStdLib('segAng', [createIdentifier(tag)]),
binaryExpression.operator,
createLiteral(90),
]), // 90 offset from the previous line
createLiteral(Math.abs(twoY)), // This will be the height of the rectangle
])
}
}
}
}

View File

@ -124,7 +124,9 @@ export const fileLoader: LoaderFunction = async (
// We explicitly do not write to the file here since we are loading from
// the file system and not the editor.
codeManager.updateCurrentFilePath(currentFilePath)
codeManager.updateCodeStateEditor(code)
// We pass true on the end here to clear the code editor history.
// This way undo and redo are not super weird when opening new files.
codeManager.updateCodeStateEditor(code, true)
}
// Set the file system manager to the project path

View File

@ -407,8 +407,9 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
status: 'available',
title: 'Center circle',
disabled: (state) =>
!canRectangleOrCircleTool(state.context) &&
!state.matches({ Sketch: 'Circle tool' }),
state.matches('Sketch no face') ||
(!canRectangleOrCircleTool(state.context) &&
!state.matches({ Sketch: 'Circle tool' })),
isActive: (state) => state.matches({ Sketch: 'Circle tool' }),
hotkey: (state) =>
state.matches({ Sketch: 'Circle tool' }) ? ['Esc', 'C'] : 'C',
@ -448,8 +449,9 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
icon: 'rectangle',
status: 'available',
disabled: (state) =>
!canRectangleOrCircleTool(state.context) &&
!state.matches({ Sketch: 'Rectangle tool' }),
state.matches('Sketch no face') ||
(!canRectangleOrCircleTool(state.context) &&
!state.matches({ Sketch: 'Rectangle tool' })),
title: 'Corner rectangle',
hotkey: (state) =>
state.matches({ Sketch: 'Rectangle tool' }) ? ['Esc', 'R'] : 'R',
@ -459,13 +461,33 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
},
{
id: 'center-rectangle',
onClick: () => console.error('Center rectangle not yet implemented'),
icon: 'rectangle',
status: 'unavailable',
onClick: ({ modelingState, modelingSend }) =>
modelingSend({
type: 'change tool',
data: {
tool: !modelingState.matches({
Sketch: 'Center Rectangle tool',
})
? 'center rectangle'
: 'none',
},
}),
icon: 'arc',
status: 'available',
disabled: (state) =>
state.matches('Sketch no face') ||
(!canRectangleOrCircleTool(state.context) &&
!state.matches({ Sketch: 'Center Rectangle tool' })),
title: 'Center rectangle',
showTitle: false,
hotkey: (state) =>
state.matches({ Sketch: 'Center Rectangle tool' })
? ['Esc', 'C']
: 'C',
description: 'Start drawing a rectangle from its center',
links: [],
isActive: (state) => {
return state.matches({ Sketch: 'Center Rectangle tool' })
},
},
],
{

View File

@ -2,6 +2,23 @@ import toast from 'react-hot-toast'
type ExcludeErr<T> = Exclude<T, Error>
/**
* Similar to Error, but more lightweight, without the stack trace. It can also
* be used to represent a reason for not being able to provide an alternative,
* which isn't necessarily an error.
*/
export class Reason {
message: string
constructor(message: string) {
this.message = message
}
toError() {
return new Error(this.message)
}
}
/**
* This is intentionally *not* exported due to misuse. We'd like to add a lint.
*/

View File

@ -91,7 +91,7 @@ export function useCalculateKclExpression({
const _programMem: ProgramMemory = ProgramMemory.empty()
for (const { key, value } of availableVarInfo.variables) {
const error = _programMem.set(key, {
type: 'UserVal',
type: 'String',
value,
__meta: [],
})
@ -115,6 +115,7 @@ export function useCalculateKclExpression({
setCalcResult(typeof result === 'number' ? String(result) : 'NAN')
init && setValueNode(init)
}
if (!value) return
execAstAndSetResult().catch(() => {
setCalcResult('NAN')
setValueNode(null)

View File

@ -18,6 +18,7 @@ import {
sceneEntitiesManager,
engineCommandManager,
editorManager,
codeManager,
} from 'lib/singletons'
import {
horzVertInfo,
@ -158,6 +159,15 @@ export type DefaultPlane = {
yAxis: [number, number, number]
}
export type OffsetPlane = {
type: 'offsetPlane'
position: [number, number, number]
planeId: string
pathToNode: PathToNode
zAxis: [number, number, number]
yAxis: [number, number, number]
}
export type SegmentOverlayPayload =
| {
type: 'set-one'
@ -183,6 +193,7 @@ export type SketchTool =
| 'line'
| 'tangentialArc'
| 'rectangle'
| 'center rectangle'
| 'circle'
| 'none'
@ -196,7 +207,7 @@ export type ModelingMachineEvent =
| { type: 'Sketch On Face' }
| {
type: 'Select default plane'
data: DefaultPlane | ExtrudeFacePlane
data: DefaultPlane | ExtrudeFacePlane | OffsetPlane
}
| {
type: 'Set selection'
@ -237,6 +248,10 @@ export type ModelingMachineEvent =
type: 'Add rectangle origin'
data: [x: number, y: number]
}
| {
type: 'Add center rectangle origin'
data: [x: number, y: number]
}
| {
type: 'Add circle origin'
data: [x: number, y: number]
@ -277,6 +292,7 @@ export type ModelingMachineEvent =
}
}
| { type: 'Finish rectangle' }
| { type: 'Finish center rectangle' }
| { type: 'Finish circle' }
| { type: 'Artifact graph populated' }
| { type: 'Artifact graph emptied' }
@ -505,6 +521,9 @@ export const modelingMachine = setup({
'next is rectangle': ({ context: { sketchDetails, currentTool } }) =>
currentTool === 'rectangle' &&
canRectangleOrCircleTool({ sketchDetails }),
'next is center rectangle': ({ context: { sketchDetails, currentTool } }) =>
currentTool === 'center rectangle' &&
canRectangleOrCircleTool({ sketchDetails }),
'next is circle': ({ context: { sketchDetails, currentTool } }) =>
currentTool === 'circle' && canRectangleOrCircleTool({ sketchDetails }),
'next is line': ({ context }) => context.currentTool === 'line',
@ -531,8 +550,10 @@ export const modelingMachine = setup({
}
}
),
// eslint-disable-next-line @typescript-eslint/no-misused-promises
'hide default planes': () => kclManager.hidePlanes(),
'hide default planes': () => {
// eslint-disable-next-line @typescript-eslint/no-floating-promises
kclManager.hidePlanes()
},
'reset sketch metadata': assign({
sketchDetails: null,
sketchEnginePathId: '',
@ -595,7 +616,6 @@ export const modelingMachine = setup({
if (trap(extrudeSketchRes)) return
const { modifiedAst, pathToExtrudeArg } = extrudeSketchRes
store.videoElement?.pause()
const updatedAst = await kclManager.updateAst(modifiedAst, true, {
focusPath: [pathToExtrudeArg],
zoomToFit: true,
@ -604,11 +624,9 @@ export const modelingMachine = setup({
type: 'path',
},
})
if (!engineCommandManager.engineConnection?.idleMode) {
store.videoElement?.play().catch((e) => {
console.warn('Video playing was prevented', e)
})
}
await codeManager.updateEditorWithAstAndWriteToFile(updatedAst.newAst)
if (updatedAst?.selections) {
editorManager.selectRange(updatedAst?.selections)
}
@ -642,7 +660,6 @@ export const modelingMachine = setup({
if (trap(revolveSketchRes)) return
const { modifiedAst, pathToRevolveArg } = revolveSketchRes
store.videoElement?.pause()
const updatedAst = await kclManager.updateAst(modifiedAst, true, {
focusPath: [pathToRevolveArg],
zoomToFit: true,
@ -651,11 +668,9 @@ export const modelingMachine = setup({
type: 'path',
},
})
if (!engineCommandManager.engineConnection?.idleMode) {
store.videoElement?.play().catch((e) => {
console.warn('Video playing was prevented', e)
})
}
await codeManager.updateEditorWithAstAndWriteToFile(updatedAst.newAst)
if (updatedAst?.selections) {
editorManager.selectRange(updatedAst?.selections)
}
@ -685,6 +700,7 @@ export const modelingMachine = setup({
}
await kclManager.updateAst(modifiedAst, true)
await codeManager.updateEditorWithAstAndWriteToFile(modifiedAst)
})().catch(reportRejection)
},
'AST fillet': ({ event }) => {
@ -702,6 +718,9 @@ export const modelingMachine = setup({
radius
)
if (err(applyFilletToSelectionResult)) return applyFilletToSelectionResult
// eslint-disable-next-line @typescript-eslint/no-floating-promises
codeManager.updateEditorWithAstAndWriteToFile(kclManager.ast)
},
'set selection filter to curves only': () => {
;(async () => {
@ -758,25 +777,35 @@ export const modelingMachine = setup({
'remove sketch grid': () => sceneEntitiesManager.removeSketchGrid(),
'set up draft line': ({ context: { sketchDetails } }) => {
if (!sketchDetails) return
// eslint-disable-next-line @typescript-eslint/no-floating-promises
sceneEntitiesManager.setUpDraftSegment(
sceneEntitiesManager
.setupDraftSegment(
sketchDetails.sketchPathToNode,
sketchDetails.zAxis,
sketchDetails.yAxis,
sketchDetails.origin,
'line'
)
.then(() => {
return codeManager.updateEditorWithAstAndWriteToFile(kclManager.ast)
})
},
'set up draft arc': ({ context: { sketchDetails } }) => {
if (!sketchDetails) return
// eslint-disable-next-line @typescript-eslint/no-floating-promises
sceneEntitiesManager.setUpDraftSegment(
sceneEntitiesManager
.setupDraftSegment(
sketchDetails.sketchPathToNode,
sketchDetails.zAxis,
sketchDetails.yAxis,
sketchDetails.origin,
'tangentialArcTo'
)
.then(() => {
return codeManager.updateEditorWithAstAndWriteToFile(kclManager.ast)
})
},
'listen for rectangle origin': ({ context: { sketchDetails } }) => {
if (!sketchDetails) return
@ -795,6 +824,26 @@ export const modelingMachine = setup({
},
})
},
'listen for center rectangle origin': ({ context: { sketchDetails } }) => {
if (!sketchDetails) return
// setupNoPointsListener has the code for startProfileAt onClick
sceneEntitiesManager.setupNoPointsListener({
sketchDetails,
afterClick: (args) => {
const twoD = args.intersectionPoint?.twoD
if (twoD) {
sceneInfra.modelingSend({
type: 'Add center rectangle origin',
data: [twoD.x, twoD.y],
})
} else {
console.error('No intersection point found')
}
},
})
},
'listen for circle origin': ({ context: { sketchDetails } }) => {
if (!sketchDetails) return
sceneEntitiesManager.createIntersectionPlane()
@ -834,8 +883,28 @@ export const modelingMachine = setup({
'set up draft rectangle': ({ context: { sketchDetails }, event }) => {
if (event.type !== 'Add rectangle origin') return
if (!sketchDetails || !event.data) return
// eslint-disable-next-line @typescript-eslint/no-floating-promises
sceneEntitiesManager.setupDraftRectangle(
sceneEntitiesManager
.setupDraftRectangle(
sketchDetails.sketchPathToNode,
sketchDetails.zAxis,
sketchDetails.yAxis,
sketchDetails.origin,
event.data
)
.then(() => {
return codeManager.updateEditorWithAstAndWriteToFile(kclManager.ast)
})
},
'set up draft center rectangle': ({
context: { sketchDetails },
event,
}) => {
if (event.type !== 'Add center rectangle origin') return
if (!sketchDetails || !event.data) return
// eslint-disable-next-line @typescript-eslint/no-floating-promises
sceneEntitiesManager.setupDraftCenterRectangle(
sketchDetails.sketchPathToNode,
sketchDetails.zAxis,
sketchDetails.yAxis,
@ -846,19 +915,26 @@ export const modelingMachine = setup({
'set up draft circle': ({ context: { sketchDetails }, event }) => {
if (event.type !== 'Add circle origin') return
if (!sketchDetails || !event.data) return
// eslint-disable-next-line @typescript-eslint/no-floating-promises
sceneEntitiesManager.setupDraftCircle(
sceneEntitiesManager
.setupDraftCircle(
sketchDetails.sketchPathToNode,
sketchDetails.zAxis,
sketchDetails.yAxis,
sketchDetails.origin,
event.data
)
.then(() => {
return codeManager.updateEditorWithAstAndWriteToFile(kclManager.ast)
})
},
'set up draft line without teardown': ({ context: { sketchDetails } }) => {
if (!sketchDetails) return
// eslint-disable-next-line @typescript-eslint/no-floating-promises
sceneEntitiesManager.setUpDraftSegment(
sceneEntitiesManager
.setupDraftSegment(
sketchDetails.sketchPathToNode,
sketchDetails.zAxis,
sketchDetails.yAxis,
@ -866,6 +942,9 @@ export const modelingMachine = setup({
'line',
false
)
.then(() => {
return codeManager.updateEditorWithAstAndWriteToFile(kclManager.ast)
})
},
'show default planes': () => {
// eslint-disable-next-line @typescript-eslint/no-floating-promises
@ -882,12 +961,17 @@ export const modelingMachine = setup({
'add axis n grid': ({ context: { sketchDetails } }) => {
if (!sketchDetails) return
if (localStorage.getItem('disableAxis')) return
// eslint-disable-next-line @typescript-eslint/no-floating-promises
sceneEntitiesManager.createSketchAxis(
sketchDetails.sketchPathToNode || [],
sketchDetails.zAxis,
sketchDetails.yAxis,
sketchDetails.origin
)
// eslint-disable-next-line @typescript-eslint/no-floating-promises
codeManager.updateEditorWithAstAndWriteToFile(kclManager.ast)
},
'reset client scene mouse handlers': () => {
// when not in sketch mode we don't need any mouse listeners
@ -916,10 +1000,13 @@ export const modelingMachine = setup({
'Delete segment': ({ context: { sketchDetails }, event }) => {
if (event.type !== 'Delete segment') return
if (!sketchDetails || !event.data) return
// eslint-disable-next-line @typescript-eslint/no-floating-promises
deleteSegment({
pathToNode: event.data,
sketchDetails,
}).then(() => {
return codeManager.updateEditorWithAstAndWriteToFile(kclManager.ast)
})
},
'Reset Segment Overlays': () => sceneEntitiesManager.resetOverlays(),
@ -984,6 +1071,9 @@ export const modelingMachine = setup({
)
if (trap(updatedAst, { suppress: true })) return
if (!updatedAst) return
await codeManager.updateEditorWithAstAndWriteToFile(updatedAst.newAst)
return {
selectionType: 'completeSelection',
selection: updateSelections(
@ -1018,6 +1108,7 @@ export const modelingMachine = setup({
)
if (trap(updatedAst, { suppress: true })) return
if (!updatedAst) return
await codeManager.updateEditorWithAstAndWriteToFile(updatedAst.newAst)
return {
selectionType: 'completeSelection',
selection: updateSelections(
@ -1052,6 +1143,7 @@ export const modelingMachine = setup({
)
if (trap(updatedAst, { suppress: true })) return
if (!updatedAst) return
await codeManager.updateEditorWithAstAndWriteToFile(updatedAst.newAst)
return {
selectionType: 'completeSelection',
selection: updateSelections(
@ -1084,6 +1176,7 @@ export const modelingMachine = setup({
)
if (trap(updatedAst, { suppress: true })) return
if (!updatedAst) return
await codeManager.updateEditorWithAstAndWriteToFile(updatedAst.newAst)
const updatedSelectionRanges = updateSelections(
pathToNodeMap,
selectionRanges,
@ -1117,6 +1210,7 @@ export const modelingMachine = setup({
)
if (trap(updatedAst, { suppress: true })) return
if (!updatedAst) return
await codeManager.updateEditorWithAstAndWriteToFile(updatedAst.newAst)
const updatedSelectionRanges = updateSelections(
pathToNodeMap,
selectionRanges,
@ -1150,6 +1244,7 @@ export const modelingMachine = setup({
)
if (trap(updatedAst, { suppress: true })) return
if (!updatedAst) return
await codeManager.updateEditorWithAstAndWriteToFile(updatedAst.newAst)
const updatedSelectionRanges = updateSelections(
pathToNodeMap,
selectionRanges,
@ -1183,6 +1278,7 @@ export const modelingMachine = setup({
)
if (trap(updatedAst, { suppress: true })) return
if (!updatedAst) return
await codeManager.updateEditorWithAstAndWriteToFile(updatedAst.newAst)
const updatedSelectionRanges = updateSelections(
pathToNodeMap,
selectionRanges,
@ -1220,6 +1316,8 @@ export const modelingMachine = setup({
)
if (trap(updatedAst, { suppress: true })) return
if (!updatedAst) return
await codeManager.updateEditorWithAstAndWriteToFile(updatedAst.newAst)
const updatedSelectionRanges = updateSelections(
pathToNodeMap,
selectionRanges,
@ -1252,6 +1350,7 @@ export const modelingMachine = setup({
)
if (trap(updatedAst, { suppress: true })) return
if (!updatedAst) return
await codeManager.updateEditorWithAstAndWriteToFile(updatedAst.newAst)
const updatedSelectionRanges = updateSelections(
pathToNodeMap,
selectionRanges,
@ -1304,7 +1403,7 @@ export const modelingMachine = setup({
}
),
'animate-to-face': fromPromise(
async (_: { input?: ExtrudeFacePlane | DefaultPlane }) => {
async (_: { input?: ExtrudeFacePlane | DefaultPlane | OffsetPlane }) => {
return {} as
| undefined
| {
@ -1556,7 +1655,7 @@ export const modelingMachine = setup({
},
},
entry: 'setup client side sketch segments',
entry: ['setup client side sketch segments'],
},
'Await horizontal distance info': {
@ -1776,6 +1875,40 @@ export const modelingMachine = setup({
},
},
'Center Rectangle tool': {
entry: ['listen for center rectangle origin'],
states: {
'Awaiting corner': {
on: {
'Finish center rectangle': 'Finished Center Rectangle',
},
},
'Awaiting origin': {
on: {
'Add center rectangle origin': {
target: 'Awaiting corner',
// TODO
actions: 'set up draft center rectangle',
},
},
},
'Finished Center Rectangle': {
always: '#Modeling.Sketch.SketchIdle',
},
},
initial: 'Awaiting origin',
on: {
'change tool': {
target: 'Change Tool',
},
},
},
'clean slate': {
always: 'SketchIdle',
},
@ -1801,7 +1934,7 @@ export const modelingMachine = setup({
onError: 'SketchIdle',
onDone: {
target: 'SketchIdle',
actions: ['Set selection'],
actions: 'Set selection',
},
},
},
@ -1969,6 +2102,10 @@ export const modelingMachine = setup({
target: 'Circle tool',
guard: 'next is circle',
},
{
target: 'Center Rectangle tool',
guard: 'next is center rectangle',
},
],
entry: 'assign tool in context',

View File

@ -737,7 +737,7 @@ dependencies = [
[[package]]
name = "derive-docs"
version = "0.1.29"
version = "0.1.30"
dependencies = [
"Inflector",
"anyhow",
@ -1589,6 +1589,8 @@ dependencies = [
"console",
"lazy_static",
"linked-hash-map",
"pest",
"pest_derive",
"regex",
"serde",
"similar",
@ -1689,6 +1691,7 @@ dependencies = [
"databake",
"derive-docs",
"expectorate",
"fnv",
"form_urlencoded",
"futures",
"git_rev",
@ -1734,18 +1737,6 @@ dependencies = [
"zip",
]
[[package]]
name = "kcl-macros"
version = "0.1.0"
dependencies = [
"databake",
"kcl-lib",
"pretty_assertions",
"proc-macro2",
"quote",
"syn 2.0.87",
]
[[package]]
name = "kcl-test-server"
version = "0.1.16"

View File

@ -68,7 +68,6 @@ debug = "line-tables-only"
members = [
"derive-docs",
"kcl",
"kcl-macros",
"kcl-test-server",
"kcl-to-core",
]

View File

@ -1,7 +1,7 @@
[package]
name = "derive-docs"
description = "A tool for generating documentation from Rust derive macros"
version = "0.1.29"
version = "0.1.30"
edition = "2021"
license = "MIT"
repository = "https://github.com/KittyCAD/modeling-app"

View File

@ -173,11 +173,11 @@ fn do_stdlib_inner(
quote! {
let code_blocks = vec![#(#cb),*];
code_blocks.iter().map(|cb| {
let program = crate::parser::top_level_parse(cb).unwrap();
let program = crate::Program::parse(cb).unwrap();
let mut options: crate::ast::types::FormatOptions = Default::default();
options.insert_final_newline = false;
program.recast(&options, 0)
program.ast.recast(&options, 0)
}).collect::<Vec<String>>()
}
} else {
@ -748,8 +748,7 @@ 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::parser::top_level_parse(#code_block).unwrap();
let id_generator = crate::executor::IdGenerator::default();
let program = crate::Program::parse(#code_block).unwrap();
let ctx = crate::executor::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()),
@ -758,7 +757,7 @@ fn generate_code_block_test(fn_name: &str, code_block: &str, index: usize) -> pr
context_type: crate::executor::ContextType::Mock,
};
ctx.run(&program, None, id_generator, None).await.unwrap();
ctx.run(&program, &mut crate::ExecState::default()).await.unwrap();
}
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]

View File

@ -2,8 +2,7 @@
mod test_examples_someFn {
#[tokio::test(flavor = "multi_thread")]
async fn test_mock_example_someFn0() {
let program = crate::parser::top_level_parse("someFn()").unwrap();
let id_generator = crate::executor::IdGenerator::default();
let program = crate::Program::parse("someFn()").unwrap();
let ctx = crate::executor::ExecutorContext {
engine: std::sync::Arc::new(Box::new(
crate::engine::conn_mock::EngineConnection::new()
@ -15,7 +14,9 @@ mod test_examples_someFn {
settings: Default::default(),
context_type: crate::executor::ContextType::Mock,
};
ctx.run(&program, None, id_generator, None).await.unwrap();
ctx.run(&program, &mut crate::ExecState::default())
.await
.unwrap();
}
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
@ -111,10 +112,10 @@ impl crate::docs::StdLibFn for SomeFn {
code_blocks
.iter()
.map(|cb| {
let program = crate::parser::top_level_parse(cb).unwrap();
let program = crate::Program::parse(cb).unwrap();
let mut options: crate::ast::types::FormatOptions = Default::default();
options.insert_final_newline = false;
program.recast(&options, 0)
program.ast.recast(&options, 0)
})
.collect::<Vec<String>>()
}

View File

@ -2,8 +2,7 @@
mod test_examples_someFn {
#[tokio::test(flavor = "multi_thread")]
async fn test_mock_example_someFn0() {
let program = crate::parser::top_level_parse("someFn()").unwrap();
let id_generator = crate::executor::IdGenerator::default();
let program = crate::Program::parse("someFn()").unwrap();
let ctx = crate::executor::ExecutorContext {
engine: std::sync::Arc::new(Box::new(
crate::engine::conn_mock::EngineConnection::new()
@ -15,7 +14,9 @@ mod test_examples_someFn {
settings: Default::default(),
context_type: crate::executor::ContextType::Mock,
};
ctx.run(&program, None, id_generator, None).await.unwrap();
ctx.run(&program, &mut crate::ExecState::default())
.await
.unwrap();
}
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
@ -111,10 +112,10 @@ impl crate::docs::StdLibFn for SomeFn {
code_blocks
.iter()
.map(|cb| {
let program = crate::parser::top_level_parse(cb).unwrap();
let program = crate::Program::parse(cb).unwrap();
let mut options: crate::ast::types::FormatOptions = Default::default();
options.insert_final_newline = false;
program.recast(&options, 0)
program.ast.recast(&options, 0)
})
.collect::<Vec<String>>()
}

View File

@ -3,9 +3,7 @@ mod test_examples_show {
#[tokio::test(flavor = "multi_thread")]
async fn test_mock_example_show0() {
let program =
crate::parser::top_level_parse("This is another code block.\nyes sirrr.\nshow")
.unwrap();
let id_generator = crate::executor::IdGenerator::default();
crate::Program::parse("This is another code block.\nyes sirrr.\nshow").unwrap();
let ctx = crate::executor::ExecutorContext {
engine: std::sync::Arc::new(Box::new(
crate::engine::conn_mock::EngineConnection::new()
@ -17,7 +15,9 @@ mod test_examples_show {
settings: Default::default(),
context_type: crate::executor::ContextType::Mock,
};
ctx.run(&program, None, id_generator, None).await.unwrap();
ctx.run(&program, &mut crate::ExecState::default())
.await
.unwrap();
}
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
@ -36,9 +36,7 @@ mod test_examples_show {
#[tokio::test(flavor = "multi_thread")]
async fn test_mock_example_show1() {
let program =
crate::parser::top_level_parse("This is code.\nIt does other shit.\nshow").unwrap();
let id_generator = crate::executor::IdGenerator::default();
let program = crate::Program::parse("This is code.\nIt does other shit.\nshow").unwrap();
let ctx = crate::executor::ExecutorContext {
engine: std::sync::Arc::new(Box::new(
crate::engine::conn_mock::EngineConnection::new()
@ -50,7 +48,9 @@ mod test_examples_show {
settings: Default::default(),
context_type: crate::executor::ContextType::Mock,
};
ctx.run(&program, None, id_generator, None).await.unwrap();
ctx.run(&program, &mut crate::ExecState::default())
.await
.unwrap();
}
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
@ -149,10 +149,10 @@ impl crate::docs::StdLibFn for Show {
code_blocks
.iter()
.map(|cb| {
let program = crate::parser::top_level_parse(cb).unwrap();
let program = crate::Program::parse(cb).unwrap();
let mut options: crate::ast::types::FormatOptions = Default::default();
options.insert_final_newline = false;
program.recast(&options, 0)
program.ast.recast(&options, 0)
})
.collect::<Vec<String>>()
}

View File

@ -2,9 +2,7 @@
mod test_examples_show {
#[tokio::test(flavor = "multi_thread")]
async fn test_mock_example_show0() {
let program =
crate::parser::top_level_parse("This is code.\nIt does other shit.\nshow").unwrap();
let id_generator = crate::executor::IdGenerator::default();
let program = crate::Program::parse("This is code.\nIt does other shit.\nshow").unwrap();
let ctx = crate::executor::ExecutorContext {
engine: std::sync::Arc::new(Box::new(
crate::engine::conn_mock::EngineConnection::new()
@ -16,7 +14,9 @@ mod test_examples_show {
settings: Default::default(),
context_type: crate::executor::ContextType::Mock,
};
ctx.run(&program, None, id_generator, None).await.unwrap();
ctx.run(&program, &mut crate::ExecState::default())
.await
.unwrap();
}
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
@ -112,10 +112,10 @@ impl crate::docs::StdLibFn for Show {
code_blocks
.iter()
.map(|cb| {
let program = crate::parser::top_level_parse(cb).unwrap();
let program = crate::Program::parse(cb).unwrap();
let mut options: crate::ast::types::FormatOptions = Default::default();
options.insert_final_newline = false;
program.recast(&options, 0)
program.ast.recast(&options, 0)
})
.collect::<Vec<String>>()
}

View File

@ -3,9 +3,7 @@ mod test_examples_my_func {
#[tokio::test(flavor = "multi_thread")]
async fn test_mock_example_my_func0() {
let program =
crate::parser::top_level_parse("This is another code block.\nyes sirrr.\nmyFunc")
.unwrap();
let id_generator = crate::executor::IdGenerator::default();
crate::Program::parse("This is another code block.\nyes sirrr.\nmyFunc").unwrap();
let ctx = crate::executor::ExecutorContext {
engine: std::sync::Arc::new(Box::new(
crate::engine::conn_mock::EngineConnection::new()
@ -17,7 +15,9 @@ mod test_examples_my_func {
settings: Default::default(),
context_type: crate::executor::ContextType::Mock,
};
ctx.run(&program, None, id_generator, None).await.unwrap();
ctx.run(&program, &mut crate::ExecState::default())
.await
.unwrap();
}
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
@ -36,9 +36,7 @@ mod test_examples_my_func {
#[tokio::test(flavor = "multi_thread")]
async fn test_mock_example_my_func1() {
let program =
crate::parser::top_level_parse("This is code.\nIt does other shit.\nmyFunc").unwrap();
let id_generator = crate::executor::IdGenerator::default();
let program = crate::Program::parse("This is code.\nIt does other shit.\nmyFunc").unwrap();
let ctx = crate::executor::ExecutorContext {
engine: std::sync::Arc::new(Box::new(
crate::engine::conn_mock::EngineConnection::new()
@ -50,7 +48,9 @@ mod test_examples_my_func {
settings: Default::default(),
context_type: crate::executor::ContextType::Mock,
};
ctx.run(&program, None, id_generator, None).await.unwrap();
ctx.run(&program, &mut crate::ExecState::default())
.await
.unwrap();
}
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
@ -149,10 +149,10 @@ impl crate::docs::StdLibFn for MyFunc {
code_blocks
.iter()
.map(|cb| {
let program = crate::parser::top_level_parse(cb).unwrap();
let program = crate::Program::parse(cb).unwrap();
let mut options: crate::ast::types::FormatOptions = Default::default();
options.insert_final_newline = false;
program.recast(&options, 0)
program.ast.recast(&options, 0)
})
.collect::<Vec<String>>()
}

View File

@ -3,9 +3,7 @@ mod test_examples_line_to {
#[tokio::test(flavor = "multi_thread")]
async fn test_mock_example_line_to0() {
let program =
crate::parser::top_level_parse("This is another code block.\nyes sirrr.\nlineTo")
.unwrap();
let id_generator = crate::executor::IdGenerator::default();
crate::Program::parse("This is another code block.\nyes sirrr.\nlineTo").unwrap();
let ctx = crate::executor::ExecutorContext {
engine: std::sync::Arc::new(Box::new(
crate::engine::conn_mock::EngineConnection::new()
@ -17,7 +15,9 @@ mod test_examples_line_to {
settings: Default::default(),
context_type: crate::executor::ContextType::Mock,
};
ctx.run(&program, None, id_generator, None).await.unwrap();
ctx.run(&program, &mut crate::ExecState::default())
.await
.unwrap();
}
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
@ -36,9 +36,7 @@ mod test_examples_line_to {
#[tokio::test(flavor = "multi_thread")]
async fn test_mock_example_line_to1() {
let program =
crate::parser::top_level_parse("This is code.\nIt does other shit.\nlineTo").unwrap();
let id_generator = crate::executor::IdGenerator::default();
let program = crate::Program::parse("This is code.\nIt does other shit.\nlineTo").unwrap();
let ctx = crate::executor::ExecutorContext {
engine: std::sync::Arc::new(Box::new(
crate::engine::conn_mock::EngineConnection::new()
@ -50,7 +48,9 @@ mod test_examples_line_to {
settings: Default::default(),
context_type: crate::executor::ContextType::Mock,
};
ctx.run(&program, None, id_generator, None).await.unwrap();
ctx.run(&program, &mut crate::ExecState::default())
.await
.unwrap();
}
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
@ -157,10 +157,10 @@ impl crate::docs::StdLibFn for LineTo {
code_blocks
.iter()
.map(|cb| {
let program = crate::parser::top_level_parse(cb).unwrap();
let program = crate::Program::parse(cb).unwrap();
let mut options: crate::ast::types::FormatOptions = Default::default();
options.insert_final_newline = false;
program.recast(&options, 0)
program.ast.recast(&options, 0)
})
.collect::<Vec<String>>()
}

View File

@ -3,8 +3,7 @@ mod test_examples_min {
#[tokio::test(flavor = "multi_thread")]
async fn test_mock_example_min0() {
let program =
crate::parser::top_level_parse("This is another code block.\nyes sirrr.\nmin").unwrap();
let id_generator = crate::executor::IdGenerator::default();
crate::Program::parse("This is another code block.\nyes sirrr.\nmin").unwrap();
let ctx = crate::executor::ExecutorContext {
engine: std::sync::Arc::new(Box::new(
crate::engine::conn_mock::EngineConnection::new()
@ -16,7 +15,9 @@ mod test_examples_min {
settings: Default::default(),
context_type: crate::executor::ContextType::Mock,
};
ctx.run(&program, None, id_generator, None).await.unwrap();
ctx.run(&program, &mut crate::ExecState::default())
.await
.unwrap();
}
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
@ -35,9 +36,7 @@ mod test_examples_min {
#[tokio::test(flavor = "multi_thread")]
async fn test_mock_example_min1() {
let program =
crate::parser::top_level_parse("This is code.\nIt does other shit.\nmin").unwrap();
let id_generator = crate::executor::IdGenerator::default();
let program = crate::Program::parse("This is code.\nIt does other shit.\nmin").unwrap();
let ctx = crate::executor::ExecutorContext {
engine: std::sync::Arc::new(Box::new(
crate::engine::conn_mock::EngineConnection::new()
@ -49,7 +48,9 @@ mod test_examples_min {
settings: Default::default(),
context_type: crate::executor::ContextType::Mock,
};
ctx.run(&program, None, id_generator, None).await.unwrap();
ctx.run(&program, &mut crate::ExecState::default())
.await
.unwrap();
}
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
@ -148,10 +149,10 @@ impl crate::docs::StdLibFn for Min {
code_blocks
.iter()
.map(|cb| {
let program = crate::parser::top_level_parse(cb).unwrap();
let program = crate::Program::parse(cb).unwrap();
let mut options: crate::ast::types::FormatOptions = Default::default();
options.insert_final_newline = false;
program.recast(&options, 0)
program.ast.recast(&options, 0)
})
.collect::<Vec<String>>()
}

View File

@ -2,9 +2,7 @@
mod test_examples_show {
#[tokio::test(flavor = "multi_thread")]
async fn test_mock_example_show0() {
let program =
crate::parser::top_level_parse("This is code.\nIt does other shit.\nshow").unwrap();
let id_generator = crate::executor::IdGenerator::default();
let program = crate::Program::parse("This is code.\nIt does other shit.\nshow").unwrap();
let ctx = crate::executor::ExecutorContext {
engine: std::sync::Arc::new(Box::new(
crate::engine::conn_mock::EngineConnection::new()
@ -16,7 +14,9 @@ mod test_examples_show {
settings: Default::default(),
context_type: crate::executor::ContextType::Mock,
};
ctx.run(&program, None, id_generator, None).await.unwrap();
ctx.run(&program, &mut crate::ExecState::default())
.await
.unwrap();
}
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
@ -112,10 +112,10 @@ impl crate::docs::StdLibFn for Show {
code_blocks
.iter()
.map(|cb| {
let program = crate::parser::top_level_parse(cb).unwrap();
let program = crate::Program::parse(cb).unwrap();
let mut options: crate::ast::types::FormatOptions = Default::default();
options.insert_final_newline = false;
program.recast(&options, 0)
program.ast.recast(&options, 0)
})
.collect::<Vec<String>>()
}

View File

@ -2,9 +2,7 @@
mod test_examples_import {
#[tokio::test(flavor = "multi_thread")]
async fn test_mock_example_import0() {
let program =
crate::parser::top_level_parse("This is code.\nIt does other shit.\nimport").unwrap();
let id_generator = crate::executor::IdGenerator::default();
let program = crate::Program::parse("This is code.\nIt does other shit.\nimport").unwrap();
let ctx = crate::executor::ExecutorContext {
engine: std::sync::Arc::new(Box::new(
crate::engine::conn_mock::EngineConnection::new()
@ -16,7 +14,9 @@ mod test_examples_import {
settings: Default::default(),
context_type: crate::executor::ContextType::Mock,
};
ctx.run(&program, None, id_generator, None).await.unwrap();
ctx.run(&program, &mut crate::ExecState::default())
.await
.unwrap();
}
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
@ -112,10 +112,10 @@ impl crate::docs::StdLibFn for Import {
code_blocks
.iter()
.map(|cb| {
let program = crate::parser::top_level_parse(cb).unwrap();
let program = crate::Program::parse(cb).unwrap();
let mut options: crate::ast::types::FormatOptions = Default::default();
options.insert_final_newline = false;
program.recast(&options, 0)
program.ast.recast(&options, 0)
})
.collect::<Vec<String>>()
}

View File

@ -2,9 +2,7 @@
mod test_examples_import {
#[tokio::test(flavor = "multi_thread")]
async fn test_mock_example_import0() {
let program =
crate::parser::top_level_parse("This is code.\nIt does other shit.\nimport").unwrap();
let id_generator = crate::executor::IdGenerator::default();
let program = crate::Program::parse("This is code.\nIt does other shit.\nimport").unwrap();
let ctx = crate::executor::ExecutorContext {
engine: std::sync::Arc::new(Box::new(
crate::engine::conn_mock::EngineConnection::new()
@ -16,7 +14,9 @@ mod test_examples_import {
settings: Default::default(),
context_type: crate::executor::ContextType::Mock,
};
ctx.run(&program, None, id_generator, None).await.unwrap();
ctx.run(&program, &mut crate::ExecState::default())
.await
.unwrap();
}
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
@ -112,10 +112,10 @@ impl crate::docs::StdLibFn for Import {
code_blocks
.iter()
.map(|cb| {
let program = crate::parser::top_level_parse(cb).unwrap();
let program = crate::Program::parse(cb).unwrap();
let mut options: crate::ast::types::FormatOptions = Default::default();
options.insert_final_newline = false;
program.recast(&options, 0)
program.ast.recast(&options, 0)
})
.collect::<Vec<String>>()
}

View File

@ -2,9 +2,7 @@
mod test_examples_import {
#[tokio::test(flavor = "multi_thread")]
async fn test_mock_example_import0() {
let program =
crate::parser::top_level_parse("This is code.\nIt does other shit.\nimport").unwrap();
let id_generator = crate::executor::IdGenerator::default();
let program = crate::Program::parse("This is code.\nIt does other shit.\nimport").unwrap();
let ctx = crate::executor::ExecutorContext {
engine: std::sync::Arc::new(Box::new(
crate::engine::conn_mock::EngineConnection::new()
@ -16,7 +14,9 @@ mod test_examples_import {
settings: Default::default(),
context_type: crate::executor::ContextType::Mock,
};
ctx.run(&program, None, id_generator, None).await.unwrap();
ctx.run(&program, &mut crate::ExecState::default())
.await
.unwrap();
}
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
@ -112,10 +112,10 @@ impl crate::docs::StdLibFn for Import {
code_blocks
.iter()
.map(|cb| {
let program = crate::parser::top_level_parse(cb).unwrap();
let program = crate::Program::parse(cb).unwrap();
let mut options: crate::ast::types::FormatOptions = Default::default();
options.insert_final_newline = false;
program.recast(&options, 0)
program.ast.recast(&options, 0)
})
.collect::<Vec<String>>()
}

View File

@ -2,9 +2,7 @@
mod test_examples_show {
#[tokio::test(flavor = "multi_thread")]
async fn test_mock_example_show0() {
let program =
crate::parser::top_level_parse("This is code.\nIt does other shit.\nshow").unwrap();
let id_generator = crate::executor::IdGenerator::default();
let program = crate::Program::parse("This is code.\nIt does other shit.\nshow").unwrap();
let ctx = crate::executor::ExecutorContext {
engine: std::sync::Arc::new(Box::new(
crate::engine::conn_mock::EngineConnection::new()
@ -16,7 +14,9 @@ mod test_examples_show {
settings: Default::default(),
context_type: crate::executor::ContextType::Mock,
};
ctx.run(&program, None, id_generator, None).await.unwrap();
ctx.run(&program, &mut crate::ExecState::default())
.await
.unwrap();
}
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
@ -112,10 +112,10 @@ impl crate::docs::StdLibFn for Show {
code_blocks
.iter()
.map(|cb| {
let program = crate::parser::top_level_parse(cb).unwrap();
let program = crate::Program::parse(cb).unwrap();
let mut options: crate::ast::types::FormatOptions = Default::default();
options.insert_final_newline = false;
program.recast(&options, 0)
program.ast.recast(&options, 0)
})
.collect::<Vec<String>>()
}

View File

@ -2,8 +2,7 @@
mod test_examples_some_function {
#[tokio::test(flavor = "multi_thread")]
async fn test_mock_example_some_function0() {
let program = crate::parser::top_level_parse("someFunction()").unwrap();
let id_generator = crate::executor::IdGenerator::default();
let program = crate::Program::parse("someFunction()").unwrap();
let ctx = crate::executor::ExecutorContext {
engine: std::sync::Arc::new(Box::new(
crate::engine::conn_mock::EngineConnection::new()
@ -15,7 +14,9 @@ mod test_examples_some_function {
settings: Default::default(),
context_type: crate::executor::ContextType::Mock,
};
ctx.run(&program, None, id_generator, None).await.unwrap();
ctx.run(&program, &mut crate::ExecState::default())
.await
.unwrap();
}
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
@ -106,10 +107,10 @@ impl crate::docs::StdLibFn for SomeFunction {
code_blocks
.iter()
.map(|cb| {
let program = crate::parser::top_level_parse(cb).unwrap();
let program = crate::Program::parse(cb).unwrap();
let mut options: crate::ast::types::FormatOptions = Default::default();
options.insert_final_newline = false;
program.recast(&options, 0)
program.ast.recast(&options, 0)
})
.collect::<Vec<String>>()
}

View File

@ -1,27 +1,18 @@
cnr := "cargo nextest run"
cita := "cargo insta test --accept"
# Create a new KCL snapshot test from `tests/inputs/my-test.kcl`.
new-test name:
echo "kcl_test!(\"{{name}}\", {{name}});" >> tests/executor/visuals.rs
TWENTY_TWENTY=overwrite {{cnr}} --test executor -E 'test(=visuals::{{name}})'
# Run the same lint checks we run in CI.
lint:
cargo clippy --workspace --all-targets -- -D warnings
# Generate the stdlib image artifacts
# Then run the stdlib docs generation
redo-kcl-stdlib-docs:
TWENTY_TWENTY=overwrite {{cnr}} -p kcl-lib kcl_test_example
EXPECTORATE=overwrite {{cnr}} -p kcl-lib docs::gen_std_tests::test_generate_stdlib
# Create a new KCL deterministic simulation test case.
new-sim-test test_name kcl_program render_to_png="true":
# Each test file gets its own directory. This will contain the KCL program, and its
# snapshotted artifacts (e.g. serialized tokens, serialized ASTs, program memory,
# PNG snapshots, etc).
mkdir kcl/tests/{{test_name}}
echo "{{kcl_program}}" > kcl/tests/{{test_name}}/input.kcl
# Add the various tests for this new test case.
cat kcl/tests/simtest.tmpl | sed "s/TEST_NAME_HERE/{{test_name}}/" | sed "s/RENDER_TO_PNG/{{render_to_png}}/" >> kcl/src/simulation_tests.rs
# Run all the tests for the first time, in the right order.
new-sim-test test_name render_to_png="true":
{{cita}} -p kcl-lib -- tests::{{test_name}}::tokenize
{{cita}} -p kcl-lib -- tests::{{test_name}}::parse
{{cita}} -p kcl-lib -- tests::{{test_name}}::unparse

View File

@ -1,21 +0,0 @@
[package]
name = "kcl-macros"
description = "Macro for compiling KCL to its AST during Rust compile-time"
version = "0.1.0"
edition = "2021"
license = "MIT"
repository = "https://github.com/KittyCAD/modeling-app"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
proc-macro = true
[dependencies]
databake = "0.1.8"
kcl-lib = { path = "../kcl" }
proc-macro2 = "1"
quote = "1"
syn = { version = "2.0.87", features = ["full"] }
[dev-dependencies]
pretty_assertions = "1.4.1"

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