Compare commits

..

88 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
062fae1e54 Release kcl and kcl-test-server (#4438) 2024-11-08 11:09:58 -06:00
d7660e221c Nadro/1919/on drag number fix (#3997)
* fix: fixing on drag number inc/dec massive amount of unit tests

* fix: implemented all scenarios for inc/dec formatting

* fix: deleting unused code

* fix: clearer name

* fix: adding commments

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

* fix: does this trigger the CI?

* 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: 49fl <ircsurfer33@gmail.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-11-08 08:24:37 -06:00
938e27adac 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>
2024-11-07 16:23:03 -06:00
17b9af2416 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>
2024-11-07 13:04:33 -08:00
64f0f5b773 Cut release v0.26.3 (#4427)
* Cut release v0.26.3

* Support new electron-builder env variable requirements for building in release mode
2024-11-07 15:51:49 -05:00
f452f9bf00 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>
2024-11-07 10:30:05 -06:00
97705234c6 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>
2024-11-07 11:23:41 -05:00
30dfc167d3 Update insta snapshots (#4423)
No meaningful changes, they just added a field to the frontmatter
2024-11-07 11:20:10 -05:00
d8105627c0 Change Dependabot PR frequency to weekly (#4424) 2024-11-07 16:04:21 +00:00
6b7fac3642 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>
2024-11-07 04:54:00 +00:00
35805916aa 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>
2024-11-06 15:04:22 -05:00
4a4400e979 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>
2024-11-06 13:32:06 -06:00
efd1f288b9 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>
2024-11-06 13:25:06 -05:00
0337ab9cff 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>
2024-11-06 13:05:33 -05:00
f0dda692f6 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>
2024-11-06 11:36:58 -05:00
2ce0c59d08 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>
2024-11-06 11:25:49 -05:00
393b43d485 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>
2024-11-06 11:25:24 -05:00
4fbcde8773 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>
2024-11-06 11:22:21 -05:00
12d444fa69 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>
2024-11-06 11:21:20 -05:00
683b4488af 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>
2024-11-06 11:21:03 -05:00
e1c1e07046 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>
2024-11-06 11:20:41 -05:00
984420c155 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>
2024-11-06 00:32:05 -05:00
7bad60dfa3 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>
2024-11-05 21:44:20 +00:00
aaca88220c 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>
2024-11-05 21:35:42 +00:00
360384e8c8 implement a simple startSketchOn / offsetPlane lint rule (#4384) 2024-11-05 16:33:52 -05:00
ab2ad1313f Bump @electron-forge/maker-wix from 7.4.0 to 7.5.0 (#4033)
Bumps [@electron-forge/maker-wix](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-wix"
  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>
Co-authored-by: 49fl <ircsurfer33@gmail.com>
2024-11-05 16:06:23 -05:00
897205acc2 KCL: More ways to reference paths (#4387)
Adds new stdlib functions segStart, segStartX, segStartY, segEnd

Part of <https://github.com/KittyCAD/modeling-app/issues/4382>
2024-11-05 14:10:35 -06:00
862ca1124e chore: implementing kclsamples in stand alone unit tests (#4358)
* chore: implementing kclsamples in stand alone unit tests

* fix: fmt, lint, and tsc

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

* fix: fixed program memory and test file pattern. Don't know how to exclude though?

* fix: trying to fix the exclude logic

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

* fix: bump CI

* fix:typo

* fix: had conflicting filters ope, now fixed

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: 49fl <ircsurfer33@gmail.com>
2024-11-05 14:10:00 -05:00
d9981d9d7b Bump insta from 1.40.0 to 1.41.0 in /src/wasm-lib (#4331)
Bumps [insta](https://github.com/mitsuhiko/insta) from 1.40.0 to 1.41.0.
- [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.40.0...1.41.0)

---
updated-dependencies:
- dependency-name: insta
  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>
Co-authored-by: 49fl <ircsurfer33@gmail.com>
2024-11-05 12:43:18 -05:00
8df0581831 Bump electron-updater from 6.3.0 to 6.3.9 (#4093)
Bumps [electron-updater](https://github.com/electron-userland/electron-builder/tree/HEAD/packages/electron-updater) from 6.3.0 to 6.3.9.
- [Release notes](https://github.com/electron-userland/electron-builder/releases)
- [Changelog](https://github.com/electron-userland/electron-builder/blob/master/packages/electron-updater/CHANGELOG.md)
- [Commits](https://github.com/electron-userland/electron-builder/commits/electron-updater@6.3.9/packages/electron-updater)

---
updated-dependencies:
- dependency-name: electron-updater
  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>
2024-11-05 11:50:25 -05:00
54e6358df1 Bump reqwest from 0.12.8 to 0.12.9 in /src/wasm-lib (#4346)
Bumps [reqwest](https://github.com/seanmonstar/reqwest) from 0.12.8 to 0.12.9.
- [Release notes](https://github.com/seanmonstar/reqwest/releases)
- [Changelog](https://github.com/seanmonstar/reqwest/blob/master/CHANGELOG.md)
- [Commits](https://github.com/seanmonstar/reqwest/compare/v0.12.8...v0.12.9)

---
updated-dependencies:
- dependency-name: reqwest
  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>
2024-11-05 11:49:23 -05:00
daf20a978d Bump @codemirror/language from 6.10.2 to 6.10.3 (#4357)
Bumps [@codemirror/language](https://github.com/codemirror/language) from 6.10.2 to 6.10.3.
- [Changelog](https://github.com/codemirror/language/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codemirror/language/compare/6.10.2...6.10.3)

---
updated-dependencies:
- dependency-name: "@codemirror/language"
  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>
2024-11-05 15:19:37 +00:00
8e64798dda Bump google-github-actions/auth from 2.1.6 to 2.1.7 (#4363)
Bumps [google-github-actions/auth](https://github.com/google-github-actions/auth) from 2.1.6 to 2.1.7.
- [Release notes](https://github.com/google-github-actions/auth/releases)
- [Changelog](https://github.com/google-github-actions/auth/blob/main/CHANGELOG.md)
- [Commits](https://github.com/google-github-actions/auth/compare/v2.1.6...v2.1.7)

---
updated-dependencies:
- dependency-name: google-github-actions/auth
  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>
2024-11-05 14:50:14 +00:00
a1ceb4fa47 Bump google-github-actions/setup-gcloud from 2.1.0 to 2.1.2 (#4365)
Bumps [google-github-actions/setup-gcloud](https://github.com/google-github-actions/setup-gcloud) from 2.1.0 to 2.1.2.
- [Release notes](https://github.com/google-github-actions/setup-gcloud/releases)
- [Changelog](https://github.com/google-github-actions/setup-gcloud/blob/main/CHANGELOG.md)
- [Commits](https://github.com/google-github-actions/setup-gcloud/compare/v2.1.0...v2.1.2)

---
updated-dependencies:
- dependency-name: google-github-actions/setup-gcloud
  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>
2024-11-05 14:25:36 +00:00
2db8d13051 Back to regular updater-test URL (#4332)
* Back to regular updater-test URL

* Test: build release

* Revert "Test: build release"

This reverts commit 7ed98cc9ed.
2024-11-05 13:46:07 +00:00
aceb8052e2 Bump syn from 2.0.85 to 2.0.87 in /src/wasm-lib (#4379)
Bumps [syn](https://github.com/dtolnay/syn) from 2.0.85 to 2.0.87.
- [Release notes](https://github.com/dtolnay/syn/releases)
- [Commits](https://github.com/dtolnay/syn/compare/2.0.85...2.0.87)

---
updated-dependencies:
- dependency-name: syn
  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>
2024-11-04 23:30:01 -05:00
62fae1e93b Bump anyhow from 1.0.91 to 1.0.92 in /src/wasm-lib (#4378)
Bumps [anyhow](https://github.com/dtolnay/anyhow) from 1.0.91 to 1.0.92.
- [Release notes](https://github.com/dtolnay/anyhow/releases)
- [Commits](https://github.com/dtolnay/anyhow/compare/1.0.91...1.0.92)

---
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>
Co-authored-by: 49fl <ircsurfer33@gmail.com>
2024-11-04 23:29:33 -05:00
2abfbb9788 Move tests from no_visuals into simulation tests (#4367)
Now you can properly inspect program memory for the no_visuals tests, instead of relying on a lot of KCL asserts.
2024-11-05 02:34:22 +00:00
ad1cd56891 Deflake project settings override on desktop (#4370) 2024-10-31 21:42:52 -04:00
26951364cf KCL: More simulation tests (#4359)
Demonstrate simulation tests where we don't care about visuals, e.g. the double-map test. 

The `just new-sim-test` now accepts an optional argument, `render_to_png` which can be either  "true" or "false" (defaults to "true"). Tests like double_map that don't render anything can use false, rather than rendering an empty PNG with nothing in it.

This means the [tests under `no_visuals/`](https://github.com/KittyCAD/modeling-app/tree/v0.26.2/src/wasm-lib/tests/executor/inputs/no_visuals) can be entirely replaced by simulation tests. This is much better! For example, I moved `double_map.kcl` from a no_visuals test to a simulation test. Here's the file:

```
fn increment = (i) => {
  return i + 1
}

xs = [0..2]
ys = xs
  |> map(%, increment)
  |> map(%, increment)
```

Previously the `no_visuals` test just checked that the program ran successfully without panicking. Now the simulation test lets you see the value of `xs` and `ys` and immediately see they're correct. If our map logic changes (for example, we have an off-by-one error and don't apply the `map` to the last element) it'll show up in the program memory snapshot.
2024-10-31 11:43:14 -05:00
26e995dc3f Snap to origin and axis behavior for profile starts and segments (#4344)
* Visualize draft point when near axes (only works on XY rn due to quaternion rotation issue)

* Slightly better quaternion rotation

* Actually snap new profiles to the X and Y axis

* Add snapping behavior while dragging

* Fix flickering on non-XY planes

* Add some fixture additions to support click-and-drag tests

* Add new test to verify snapping behavior

* Make the editor test fixture auto-open and close as needed

* All feedback except absolute lines

* Use `lineTo` for lines that have snapped

* Get other existing tests passing after switching to `lineTo` when snapping

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

* Re-run CI

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-10-31 10:04:38 -04:00
a8b816a3e2 Added test to ensure array push is immutable (#4361)
added test to ensure array push is immutable
2024-10-30 23:04:26 +00:00
43bec115c0 Refactor source ranges into a generic node type (#4350)
* WIP

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

* Fix formatting

* Fix yarn build:wasm

* Fix ts_rs bindings

* Fix tsc errors

* Fix wasm TS types

* Add minimal failing test

* Rename field to avoid name collisions

* Remove node wrapper around NonCodeMeta

Trying to fix TS unit test errors deserializing JSON AST in Rust.

* Rename Node to BoxNode

* Fix lints

* Fix lint by boxing literals

* Rename UnboxedNode to Node

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

* Update docs

* Update snapshots

* initial trait

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

* update docs

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

* updates

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

* gross hack for TagNode

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

* extend gross hack

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

* fix EnvRef bullshit

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

* Fix to fail parsing when a tag declarator matches a stdlib function name

* Fix test errors after merging main

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

* Confirm

* Change to use simpler map_err

* Add comment

---------

Signed-off-by: Nick Cameron <nrc@ncameron.org>
Signed-off-by: Jess Frazelle <github@jessfraz.com>
Co-authored-by: Nick Cameron <nrc@ncameron.org>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Jess Frazelle <github@jessfraz.com>
2024-10-30 20:52:17 +00:00
0c6c646fe7 KCL: New simulation test pipeline (#4351)
The idea behind this is to test all the various stages of executing KCL
separately, i.e.

 - Start with a program
 - Tokenize it
 - Parse those tokens into an AST
 - Recast the AST
 - Execute the AST, outputting
   - a PNG of the rendered model
   - serialized program memory

Each of these steps reads some input and writes some output to disk.
The output of one step becomes the input to the next step. These
intermediate artifacts are also snapshotted (like expectorate or 2020)
to ensure we're aware of any changes to how KCL works. A change could
be a bug, or it could be harmless, or deliberate, but keeping it checked
into the repo means we can easily track changes.

Note: UUIDs sent back by the engine are currently nondeterministic, so
they would break all the snapshot tests. So, the snapshots use a regex
filter and replace anything that looks like a uuid with [uuid] when
writing program memory to a snapshot. In the future I hope our UUIDs will
be seedable and easy to make deterministic. At that point, we can stop
filtering the UUIDs.

We run this pipeline on many different KCL programs. Each keeps its
inputs (KCL programs), outputs (PNG, program memory snapshot) and
intermediate artifacts (AST, token lists, etc) in that directory.

I also added a new `just` command to easily generate these tests.
You can run `just new-sim-test gear $(cat gear.kcl)` to set up a new
gear test directory and generate all the intermediate artifacts for the
first time. This doesn't need any macros, it just appends some new lines
of normal Rust source code to `tests.rs`, so it's easy to see exactly
what the code is doing.

This uses `cargo insta` for convenient snapshot testing of artifacts
as JSON, and `twenty-twenty` for snapshotting PNGs.

This was heavily inspired by Predrag Gruevski's talk at EuroRust 2024
about deterministic simulation testing, and how it can both reduce bugs
and also reduce testing/CI time. Very grateful to him for chatting with
me about this over the last couple of weeks.
2024-10-30 12:14:17 -05:00
0d52851da2 Bump serde from 1.0.213 to 1.0.214 in /src/wasm-lib (#4345)
Bumps [serde](https://github.com/serde-rs/serde) from 1.0.213 to 1.0.214.
- [Release notes](https://github.com/serde-rs/serde/releases)
- [Commits](https://github.com/serde-rs/serde/compare/v1.0.213...v1.0.214)

---
updated-dependencies:
- dependency-name: serde
  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>
2024-10-29 23:12:44 -04:00
6b105897f7 Bump handlebars from 6.1.0 to 6.2.0 in /src/wasm-lib (#4330)
Bumps [handlebars](https://github.com/sunng87/handlebars-rust) from 6.1.0 to 6.2.0.
- [Release notes](https://github.com/sunng87/handlebars-rust/releases)
- [Changelog](https://github.com/sunng87/handlebars-rust/blob/master/CHANGELOG.md)
- [Commits](https://github.com/sunng87/handlebars-rust/compare/v6.1.0...v6.2.0)

---
updated-dependencies:
- dependency-name: handlebars
  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>
Co-authored-by: 49fl <ircsurfer33@gmail.com>
2024-10-29 23:01:31 -04:00
9ff51de301 fix auth test in engine (#4354)
* fix auth test in engine

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

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

* emoty

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

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-10-29 19:24:24 -07:00
c161f578fd KCL test for subset of poop chute (#4343)
This would have caught the regression in https://github.com/KittyCAD/modeling-app/pull/4333

which had to be reverted in https://github.com/KittyCAD/modeling-app/pull/4339
2024-10-30 02:17:48 +00:00
4804eedf3e Bump react-router-dom from 6.26.1 to 6.27.0 (#4286)
Bumps [react-router-dom](https://github.com/remix-run/react-router/tree/HEAD/packages/react-router-dom) from 6.26.1 to 6.27.0.
- [Release notes](https://github.com/remix-run/react-router/releases)
- [Changelog](https://github.com/remix-run/react-router/blob/react-router-dom@6.27.0/packages/react-router-dom/CHANGELOG.md)
- [Commits](https://github.com/remix-run/react-router/commits/react-router-dom@6.27.0/packages/react-router-dom)

---
updated-dependencies:
- dependency-name: react-router-dom
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-29 22:17:19 -04:00
99db31a6a4 Tests: remove all timeouts and pasting into editor from file name collision PW test (#4352)
remove all timeouts and pasting into editor from file name collision PW test
2024-10-29 21:42:53 -04:00
90b57ec202 Add lsystem.kcl to tests (#4146)
* Add lsystem.kcl to tests

* Reduce iterations

* Fix the user settings flake shit (NOTE TO ALL FUTURE PEOPLE MODELING-APP DOES NOT WAIT FOR I/O IN SOME CASES BEFORE ROUTER NAVIGATION)
2024-10-29 21:40:31 -04:00
3f86f99f5e Fix just lint and yarn script to check all targets (#4348)
* Fix just lint to check all targets

* Fix yarn test:rust to lint all targets

* Remove redundant options

* Change cargo --all to --workspace

* Update readme to use just command
2024-10-29 19:46:59 +00:00
83e2b093a6 Deflake settings persistence desktop test by verifying we have written to the disk before continuing (#4349) 2024-10-29 12:31:52 -04:00
58f7e0086d Fix CI docs generation after #4329 (#4347)
Fix CI
2024-10-29 14:39:50 +00:00
366 changed files with 370825 additions and 4961 deletions

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)

File diff suppressed because one or more lines are too long

View File

@ -31,7 +31,7 @@ map(array: [KclValue], map_fn: FunctionParam) -> [KclValue]
r = 10 // radius
fn drawCircle = (id) => {
return startSketchOn("XY")
|> circle({ center: [id * 2 * r, 0], radius: r }, %)
|> circle({ center: [id * 2 * r, 0], radius: r }, %)
}
// Call `drawCircle`, passing in each element of the array.
@ -47,7 +47,7 @@ r = 10 // radius
// Call `map`, using an anonymous function instead of a named one.
circles = map([1..3], (id) => {
return startSketchOn("XY")
|> circle({ center: [id * 2 * r, 0], radius: r }, %)
|> circle({ center: [id * 2 * r, 0], radius: r }, %)
})
```

View File

@ -9,7 +9,7 @@ Offset a plane by a distance along its normal.
For example, if you offset the 'XZ' plane by 10, the new plane will be parallel to the 'XZ' plane and 10 units away from it.
```js
offsetPlane(std_plane: StandardPlane, offset: number) -> PlaneData
offsetPlane(std_plane: StandardPlane, offset: number) -> Plane
```
@ -22,7 +22,7 @@ offsetPlane(std_plane: StandardPlane, offset: number) -> PlaneData
### Returns
[`PlaneData`](/docs/kcl/types/PlaneData) - Data for a plane.
[`Plane`](/docs/kcl/types/Plane) - A plane.
### Examples

View File

@ -96,24 +96,24 @@ fn cube = (length, center) => {
p3 = [l + x, -l + y]
return startSketchAt(p0)
|> lineTo(p1, %)
|> lineTo(p2, %)
|> lineTo(p3, %)
|> lineTo(p0, %)
|> close(%)
|> extrude(length, %)
|> lineTo(p1, %)
|> lineTo(p2, %)
|> lineTo(p3, %)
|> lineTo(p0, %)
|> close(%)
|> extrude(length, %)
}
width = 20
fn transform = (i) => {
return {
// Move down each time.
translate: [0, 0, -i * width],
// Make the cube longer, wider and flatter each time.
scale: [pow(1.1, i), pow(1.1, i), pow(0.9, i)],
// Turn by 15 degrees each time.
rotation: { angle: 15 * i, origin: "local" }
}
// Move down each time.
translate: [0, 0, -i * width],
// Make the cube longer, wider and flatter each time.
scale: [pow(1.1, i), pow(1.1, i), pow(0.9, i)],
// Turn by 15 degrees each time.
rotation: { angle: 15 * i, origin: "local" }
}
}
myCubes = cube(width, [100, 0])
@ -133,25 +133,25 @@ fn cube = (length, center) => {
p3 = [l + x, -l + y]
return startSketchAt(p0)
|> lineTo(p1, %)
|> lineTo(p2, %)
|> lineTo(p3, %)
|> lineTo(p0, %)
|> close(%)
|> extrude(length, %)
|> lineTo(p1, %)
|> lineTo(p2, %)
|> lineTo(p3, %)
|> lineTo(p0, %)
|> close(%)
|> extrude(length, %)
}
width = 20
fn transform = (i) => {
return {
translate: [0, 0, -i * width],
rotation: {
angle: 90 * i,
// Rotate around the overall scene's origin.
origin: "global"
translate: [0, 0, -i * width],
rotation: {
angle: 90 * i,
// Rotate around the overall scene's origin.
origin: "global"
}
}
}
}
myCubes = cube(width, [100, 100])
|> patternTransform(4, transform, %)
```
@ -168,16 +168,16 @@ t = 0.005 // taper factor [0-1)
fn transform = (replicaId) => {
scale = r * abs(1 - (t * replicaId)) * (5 + cos(replicaId / 8))
return {
translate: [0, 0, replicaId * 10],
scale: [scale, scale, 0]
}
translate: [0, 0, replicaId * 10],
scale: [scale, scale, 0]
}
}
// Each layer is just a pretty thin cylinder.
fn layer = () => {
return startSketchOn("XY")
// or some other plane idk
|> circle({ center: [0, 0], radius: 1 }, %, $tag1)
|> extrude(h, %)
// or some other plane idk
|> circle({ center: [0, 0], radius: 1 }, %, $tag1)
|> extrude(h, %)
}
// The vase is 100 layers tall.
// The 100 layers are replica of each other, with a slight transformation applied to each.

File diff suppressed because one or more lines are too long

View File

@ -38,8 +38,8 @@ cube = startSketchAt([0, 0])
fn cylinder = (radius, tag) => {
return startSketchAt([0, 0])
|> circle({ radius: radius, center: segEnd(tag) }, %)
|> extrude(radius, %)
|> circle({ radius: radius, center: segEnd(tag) }, %)
|> extrude(radius, %)
}
cylinder(1, line1)

View File

@ -38,11 +38,11 @@ cube = startSketchAt([0, 0])
fn cylinder = (radius, tag) => {
return startSketchAt([0, 0])
|> circle({
radius: radius,
center: segStart(tag)
}, %)
|> extrude(radius, %)
|> circle({
radius: radius,
center: segStart(tag)
}, %)
|> extrude(radius, %)
}
cylinder(1, line1)

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 |

View File

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

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

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

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

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

@ -202,19 +202,35 @@ test.describe('Sketch tests', () => {
})
const u = await getUtils(page)
const viewport = { width: 1200, height: 500 }
await page.setViewportSize(viewport)
await page.setViewportSize({ width: 1200, height: 500 })
await u.waitForAuthSkipAppStart()
await expect(
page.getByRole('button', { name: 'Start Sketch' })
).not.toBeDisabled()
const center = {
x: viewport.width / 2,
y: viewport.height / 2,
}
const modelAreaSize = await u.getModelViewAreaSize()
await page.waitForTimeout(100)
await u.openAndClearDebugPanel()
await u.sendCustomCmd({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'default_camera_look_at',
vantage: { x: 0, y: -1250, z: 580 },
center: { x: 0, y: 0, z: 0 },
up: { x: 0, y: 0, z: 1 },
},
})
await page.waitForTimeout(100)
await u.sendCustomCmd({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'default_camera_get_settings',
},
})
await page.waitForTimeout(100)
await u.closeDebugPanel()
// If we have the code pane open, we should see the code.
if (openPanes.includes('code')) {
@ -228,7 +244,7 @@ test.describe('Sketch tests', () => {
await expect(u.codeLocator).not.toBeVisible()
}
const startPX = [center.x + 65, 458]
const startPX = [665, 458]
const dragPX = 30
let prevContent = ''
@ -239,7 +255,7 @@ test.describe('Sketch tests', () => {
// Wait for the render.
await page.waitForTimeout(1000)
// Select the sketch
await page.mouse.click(center.x + 100, 370)
await page.mouse.click(700, 370)
}
await expect(
page.getByRole('button', { name: 'Edit Sketch' })
@ -250,74 +266,45 @@ test.describe('Sketch tests', () => {
prevContent = await page.locator('.cm-content').innerText()
}
await page.waitForTimeout(1000)
await u.openAndClearDebugPanel()
await u.sendCustomCmd({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'default_camera_look_at',
vantage: { x: 0, y: -1250, z: 580 - modelAreaSize.w },
center: { x: 0, y: 0, z: 0 },
up: { x: 0, y: 0, z: 1 },
},
})
await page.waitForTimeout(100)
await u.sendCustomCmd({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'default_camera_get_settings',
},
})
await page.waitForTimeout(1000)
await u.closeDebugPanel()
const step5 = { steps: 5 }
await expect(page.getByTestId('segment-overlay')).toHaveCount(2)
test.step('drag startProfileAt handle', async () => {
await page.mouse.move(startPX[0], startPX[1])
await page.mouse.down()
await page.mouse.move(startPX[0] + dragPX, startPX[1] - dragPX, step5)
await page.mouse.up()
if (openPanes.includes('code')) {
await expect(page.locator('.cm-content')).not.toHaveText(prevContent)
prevContent = await page.locator('.cm-content').innerText()
}
})
// drag startProfieAt handle
await page.mouse.move(startPX[0], startPX[1])
await page.mouse.down()
await page.mouse.move(startPX[0] + dragPX, startPX[1] - dragPX, step5)
await page.mouse.up()
if (openPanes.includes('code')) {
await expect(page.locator('.cm-content')).not.toHaveText(prevContent)
prevContent = await page.locator('.cm-content').innerText()
}
// drag line handle
await page.waitForTimeout(100)
test.step('drag line handle', async () => {
const lineEnd = await u.getBoundingBox('[data-overlay-index="0"]')
await page.mouse.move(lineEnd.x - 5, lineEnd.y)
await page.mouse.down()
await page.mouse.move(lineEnd.x + dragPX, lineEnd.y - dragPX, step5)
await page.mouse.up()
await page.waitForTimeout(100)
if (openPanes.includes('code')) {
await expect(page.locator('.cm-content')).not.toHaveText(prevContent)
prevContent = await page.locator('.cm-content').innerText()
}
})
const lineEnd = await u.getBoundingBox('[data-overlay-index="0"]')
await page.mouse.move(lineEnd.x - 5, lineEnd.y)
await page.mouse.down()
await page.mouse.move(lineEnd.x + dragPX, lineEnd.y - dragPX, step5)
await page.mouse.up()
await page.waitForTimeout(100)
if (openPanes.includes('code')) {
await expect(page.locator('.cm-content')).not.toHaveText(prevContent)
prevContent = await page.locator('.cm-content').innerText()
}
test.step('drag tangentialArcTo handle', async () => {
const tangentEnd = await u.getBoundingBox('[data-overlay-index="1"]')
await page.mouse.move(tangentEnd.x, tangentEnd.y - 5)
await page.mouse.down()
await page.mouse.move(
tangentEnd.x + dragPX,
tangentEnd.y - dragPX,
step5
)
await page.mouse.up()
await page.waitForTimeout(100)
if (openPanes.includes('code')) {
await expect(page.locator('.cm-content')).not.toHaveText(prevContent)
}
})
// drag tangentialArcTo handle
const tangentEnd = await u.getBoundingBox('[data-overlay-index="1"]')
await page.mouse.move(tangentEnd.x, tangentEnd.y - 5)
await page.mouse.down()
await page.mouse.move(tangentEnd.x + dragPX, tangentEnd.y - dragPX, step5)
await page.mouse.up()
await page.waitForTimeout(100)
if (openPanes.includes('code')) {
await expect(page.locator('.cm-content')).not.toHaveText(prevContent)
}
// Open the code pane
await u.openKclCodePanel()
@ -593,7 +580,7 @@ test.describe('Sketch tests', () => {
})
await page.waitForTimeout(100)
const center = await u.getCenterOfModelViewArea()
const startPX = [665, 458]
const dragPX = 30
@ -609,7 +596,7 @@ test.describe('Sketch tests', () => {
await expect(page.getByTestId('segment-overlay')).toHaveCount(2)
// drag startProfileAt handle
// drag startProfieAt handle
await page.mouse.move(startPX[0], startPX[1])
await page.mouse.down()
await page.mouse.move(startPX[0] + dragPX, startPX[1] - dragPX, step5)
@ -651,7 +638,6 @@ test.describe('Sketch tests', () => {
})
test('Can add multiple sketches', async ({ page }) => {
const u = await getUtils(page)
const viewportSize = { width: 1200, height: 500 }
await page.setViewportSize(viewportSize)
@ -675,19 +661,15 @@ test.describe('Sketch tests', () => {
200
)
const center = await u.getCenterOfModelViewArea()
let codeStr = "sketch001 = startSketchOn('XY')"
await page.mouse.click(center.x - 50, viewportSize.height * 0.55)
await page.mouse.click(center.x, viewportSize.height * 0.55)
await expect(u.codeLocator).toHaveText(codeStr)
await u.closeDebugPanel()
await page.waitForTimeout(500) // TODO detect animation ending, or disable animation
const { click00r } = await getMovementUtils({ center, page })
let coord = await click00r(0, 0)
codeStr += ` |> startProfileAt(${coord.kcl}, %)`
await click00r(0, 0)
codeStr += ` |> startProfileAt(${toSU([0, 0])}, %)`
await expect(u.codeLocator).toHaveText(codeStr)
await click00r(50, 0)
@ -716,15 +698,14 @@ test.describe('Sketch tests', () => {
// when exiting the sketch above the camera is still looking down at XY,
// so selecting the plane again is a bit easier.
await page.mouse.move(center.x - 100, center.y + 50, { steps: 5 })
await page.mouse.click(center.x - 100, center.y + 50)
await page.mouse.click(center.x + 200, center.y + 100)
await page.waitForTimeout(600) // TODO detect animation ending, or disable animation
codeStr += "sketch002 = startSketchOn('XY')"
await expect(u.codeLocator).toHaveText(codeStr)
await u.closeDebugPanel()
coord = await click00r(30, 0)
codeStr += ` |> startProfileAt(${coord.kcl}, %)`
await click00r(30, 0)
codeStr += ` |> startProfileAt([2.03, 0], %)`
await expect(u.codeLocator).toHaveText(codeStr)
// TODO: I couldn't use `toSU` here because of some rounding error causing
@ -782,21 +763,20 @@ test.describe('Sketch tests', () => {
await u.updateCamPosition(camPos)
await u.closeDebugPanel()
const center = await u.getCenterOfModelViewArea()
await page.mouse.move(0, 0)
// select a plane
await page.mouse.move(center.x + 100, 200, { steps: 10 })
await page.mouse.click(center.x + 100, 200, { delay: 200 })
await page.mouse.move(700, 200, { steps: 10 })
await page.mouse.click(700, 200, { delay: 200 })
await expect(page.locator('.cm-content')).toHaveText(
`sketch001 = startSketchOn('-XZ')`
)
let prevContent = await page.locator('.cm-content').innerText()
const pointA = [center.x + 100, 200]
const pointB = [center.x + 300, 200]
const pointC = [center.x + 300, 400]
const pointA = [700, 200]
const pointB = [900, 200]
const pointC = [900, 400]
// draw three lines
await page.waitForTimeout(500)
@ -933,9 +913,7 @@ extrude001 = extrude(5, sketch001)
await page.getByRole('button', { name: 'Start Sketch' }).click()
const center = await u.getCenterOfModelViewArea()
await page.mouse.click(center.x + 22, 355)
await page.mouse.click(622, 355)
await page.waitForTimeout(800)
await page.getByText(`END')`).click()
@ -1296,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}`
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 59 KiB

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 37 KiB

View File

@ -8,21 +8,6 @@ import {
Locator,
test,
} from '@playwright/test'
import {
OrthographicCamera,
Mesh,
Scene,
Raycaster,
PlaneGeometry,
MeshBasicMaterial,
DoubleSide,
Vector2,
Vector3,
} from 'three'
import {
RAYCASTABLE_PLANE,
INTERSECTION_PLANE_LAYER,
} from 'clientSideScene/constants'
import { EngineCommand } from 'lang/std/artifactGraph'
import fsp from 'fs/promises'
import fsSync from 'fs'
@ -272,138 +257,52 @@ export const circleMove = async (
}
}
export function rollingRound(n: number, digitsAfterDecimal: number) {
const s = String(n).split('.')
export const getMovementUtils = (opts: any) => {
// The way we truncate is kinda odd apparently, so we need this function
// "[k]itty[c]ad round"
const kcRound = (n: number) => Math.trunc(n * 100) / 100
// There are no decimals, just return the number.
if (s.length === 1) return n
// To translate between screen and engine ("[U]nit") coordinates
// NOTE: these pretty much can't be perfect because of screen scaling.
// Handle on a case-by-case.
const toU = (x: number, y: number) => [
kcRound(x * 0.0678),
kcRound(-y * 0.0678), // Y is inverted in our coordinate system
]
// Find the closest 9. We don't care about anything beyond that.
const nineIndex = s[1].indexOf('9')
// Turn the array into a string with specific formatting
const fromUToString = (xy: number[]) => `[${xy[0]}, ${xy[1]}]`
const fractStr = nineIndex > 0 ? s[1].slice(0, nineIndex + 1) : s[1]
let fract = Number(fractStr) / 10 ** fractStr.length
for (let i = fractStr.length - 1; i >= 0; i -= 1) {
if (i === digitsAfterDecimal) break
fract = Math.round(fract * 10 ** i) / 10 ** i
}
return (Number(s[0]) + fract).toFixed(digitsAfterDecimal)
}
export const getMovementUtils = async (opts: any) => {
const sceneInfra = await opts.page.evaluate(() => window.sceneInfra)
// Various data for raycasting into the scene to get our XY.
const hundredM = 100_0000
const planeGeometry = new PlaneGeometry(hundredM, hundredM)
const planeMaterial = new MeshBasicMaterial({
color: 0xff0000,
side: DoubleSide,
transparent: true,
opacity: 0.5,
})
const scene = new Scene()
const intersectionPlane = new Mesh(planeGeometry, planeMaterial)
intersectionPlane.userData = { type: RAYCASTABLE_PLANE }
intersectionPlane.name = RAYCASTABLE_PLANE
intersectionPlane.layers.set(INTERSECTION_PLANE_LAYER)
scene.add(intersectionPlane)
const planeRaycaster = new Raycaster()
planeRaycaster.far = Infinity
planeRaycaster.layers.enable(INTERSECTION_PLANE_LAYER)
const kcRound = (n: number) => Math.round(n * 100) / 100
// Combine because used often
const toSU = (xy: number[]) => fromUToString(toU(xy[0], xy[1]))
// Make it easier to click around from center ("click [from] zero zero")
const click00 = (x: number, y: number) =>
opts.page.mouse.click(x, y, { delay: 100 })
opts.page.mouse.click(opts.center.x + x, opts.center.y + y, { delay: 100 })
// Relative clicker, must keep state
let last = { x: 0, y: 0 }
let lastScreenSpace = { x: 0, y: 0 }
const click00r = async (x?: number, y?: number) => {
// reset relative coordinates when anything is undefined
if (x === undefined || y === undefined) {
last = { x: 0, y: 0 }
lastScreenSpace = { x: 0, y: 0 }
return {
nextXY: [0, 0],
kcl: `[0, 0]`,
}
last.x = 0
last.y = 0
return
}
const absX = opts.center.x + x
const absY = opts.center.y + y
const nextX = last.x + x
const nextY = last.y + y
const targetX = opts.center.x + nextX
const targetY = opts.center.y + -nextY
// Use the current camera specification
const camera = await opts.page.evaluate(() => {
window.sceneInfra.camControls.onCameraChange(true)
return window.sceneInfra.camControls.camera
})
const windowWH = await opts.page.evaluate(() => ({
w: window.innerWidth,
h: window.innerHeight,
}))
// I didn't write this math, it's copied from sceneInfra.ts, and I understand
// it's just normalizing the point, but why *-2 ± 1 I have no idea.
const mouseVector = new Vector2(
(targetX / windowWH.w) * 2 - 1,
-(targetY / windowWH.h) * 2 + 1
await circleMove(
opts.page,
opts.center.x + last.x + x,
opts.center.y + last.y + y,
10,
10
)
planeRaycaster.setFromCamera(mouseVector, camera)
const intersections = planeRaycaster.intersectObjects(scene.children, true)
const planePosition = intersections[0].object.position
const inversePlaneQuaternion = intersections[0].object.quaternion
.clone()
.invert()
let transformedPoint = intersections[0].point.clone()
if (transformedPoint) {
transformedPoint.applyQuaternion(inversePlaneQuaternion)
}
const twoD = new Vector2(
// I think the intersection plane doesn't get scale when nearly everything else does, maybe that should change
transformedPoint.x / sceneInfra._baseUnitMultiplier,
transformedPoint.y / sceneInfra._baseUnitMultiplier
) // z should be 0
const planePositionCorrected = new Vector3(
...planePosition
).applyQuaternion(inversePlaneQuaternion)
twoD.sub(new Vector2(...planePositionCorrected))
await circleMove(opts.page, targetX, targetY, 10, 10)
await click00(targetX, targetY)
await click00(last.x + x, last.y + y)
last.x += x
last.y += y
const relativeScreenSpace = {
x: twoD.x - lastScreenSpace.x,
y: -(twoD.y - lastScreenSpace.y),
}
lastScreenSpace.x = kcRound(twoD.x)
lastScreenSpace.y = kcRound(twoD.y)
// Returns the new absolute coordinate and the screen space coordinate if you need it.
return {
nextXY: [last.x, last.y],
kcl: `[${kcRound(relativeScreenSpace.x)}, ${-kcRound(
relativeScreenSpace.y
)}]`,
}
// Returns the new absolute coordinate if you need it.
return [last.x, last.y]
}
return { toSU, toU, click00r }
@ -457,30 +356,6 @@ export async function getUtils(page: Page, test_?: typeof test) {
browserType !== 'chromium' ? null : await page.context().newCDPSession(page)
const util = {
async getModelViewAreaSize() {
const windowInnerWidth = await page.evaluate(() => window.innerWidth)
const windowInnerHeight = await page.evaluate(() => window.innerHeight)
const sidebar = page.getByTestId('modeling-sidebar')
const bb = await sidebar.boundingBox()
return {
w: windowInnerWidth - (bb?.width ?? 0),
h: windowInnerHeight - (bb?.height ?? 0),
}
},
async getCenterOfModelViewArea() {
const windowInnerWidth = await page.evaluate(() => window.innerWidth)
const windowInnerHeight = await page.evaluate(() => window.innerHeight)
const sidebar = page.getByTestId('modeling-sidebar')
const bb = await sidebar.boundingBox()
const goRightPx = (bb?.width ?? 0) / 2
const borderWidthsCombined = 2
return {
x: Math.round(windowInnerWidth / 2 + goRightPx) - borderWidthsCombined,
y: Math.round(windowInnerHeight / 2),
}
},
waitForAuthSkipAppStart: () => waitForAuthAndLsp(page),
waitForPageLoad: () => waitForPageLoad(page),
waitForPageLoadWithRetry: () => waitForPageLoadWithRetry(page),

View File

@ -43,12 +43,10 @@ test.describe('Testing constraints', () => {
await page.getByRole('button', { name: 'Edit Sketch' }).click()
await page.waitForTimeout(500) // wait for animation
const center = await u.getCenterOfModelViewArea()
const startXPx = center.x - 100
const startXPx = 500
await page.mouse.move(startXPx + PUR * 15, 250 - PUR * 10)
await page.keyboard.down('Shift')
await page.mouse.click(center.x + 234, 244)
await page.mouse.click(834, 244)
await page.keyboard.up('Shift')
await page

View File

@ -743,19 +743,18 @@ extrude001 = extrude(5, sketch001)
)
})
await page.setViewportSize({ width: 1200, height: 500 })
await u.waitForAuthSkipAppStart()
// Selectors and constants
const editSketchButton = page.getByRole('button', { name: 'Edit Sketch' })
const lineToolButton = page.getByTestId('line')
const segmentOverlays = page.getByTestId('segment-overlay')
const sketchOriginLocation = await u.getCenterOfModelViewArea()
const sketchOriginLocation = { x: 600, y: 250 }
const darkThemeSegmentColor: [number, number, number] = [215, 215, 215]
const lightThemeSegmentColor: [number, number, number] = [90, 90, 90]
await test.step(`Get into sketch mode`, async () => {
await page.mouse.click(sketchOriginLocation.x, sketchOriginLocation.y)
await u.waitForAuthSkipAppStart()
await page.mouse.click(700, 200)
await expect(editSketchButton).toBeVisible()
await editSketchButton.click()
@ -766,18 +765,12 @@ extrude001 = extrude(5, sketch001)
await page.waitForTimeout(1000)
})
const line1 = await u.getSegmentBodyCoords(`[data-overlay-index="${0}"]`, 0)
// Our lines are translucent (surprise!), so we need to get on portion
// of the line that is only on the background, and not on top of something
// like the axis lines.
line1.x -= 1
line1.y -= 1
await test.step(`Check the sketch line color before`, async () => {
await expect
.poll(() => u.getGreatestPixDiff(line1, darkThemeSegmentColor))
.toBeLessThanOrEqual(34)
.poll(() =>
u.getGreatestPixDiff(sketchOriginLocation, darkThemeSegmentColor)
)
.toBeLessThan(15)
})
await test.step(`Change theme to light using command palette`, async () => {
@ -792,8 +785,10 @@ extrude001 = extrude(5, sketch001)
await test.step(`Check the sketch line color after`, async () => {
await expect
.poll(() => u.getGreatestPixDiff(line1, lightThemeSegmentColor))
.toBeLessThanOrEqual(34)
.poll(() =>
u.getGreatestPixDiff(sketchOriginLocation, lightThemeSegmentColor)
)
.toBeLessThan(15)
})
})

View File

@ -503,16 +503,14 @@ test('Sketch on face', async ({ page }) => {
let previousCodeContent = await page.locator('.cm-content').innerText()
const center = await u.getCenterOfModelViewArea()
// This basically waits for sketch mode to be ready.
await u.openAndClearDebugPanel()
await u.doAndWaitForCmd(
async () => page.mouse.click(center.x, 180),
() => page.mouse.click(625, 165),
'default_camera_get_settings',
true
)
await page.waitForTimeout(300)
await page.waitForTimeout(150)
await u.closeDebugPanel()
const firstClickPosition = [612, 238]
const secondClickPosition = [661, 242]

View File

@ -1,6 +1,6 @@
{
"name": "zoo-modeling-app",
"version": "0.26.5",
"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

@ -1,14 +1,15 @@
import { useEffect, useMemo, useRef } from 'react'
import { useHotKeyListener } from './hooks/useHotKeyListener'
import { Stream } from './components/Stream'
import { AppHeader } from './components/AppHeader'
import { useHotkeys } from 'react-hotkeys-hook'
import { useLoaderData, useNavigate, useSearchParams } from 'react-router-dom'
import { useLoaderData, useNavigate } from 'react-router-dom'
import { type IndexLoaderData } from 'lib/types'
import { PATHS } from 'lib/paths'
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
import { onboardingPaths } from 'routes/Onboarding/paths'
import { useEngineConnectionSubscriptions } from 'hooks/useEngineConnectionSubscriptions'
import { codeManager, engineCommandManager, sceneInfra } from 'lib/singletons'
import { codeManager, engineCommandManager } from 'lib/singletons'
import { useAbsoluteFilePath } from 'hooks/useAbsoluteFilePath'
import { isDesktop } from 'lib/isDesktop'
import { useLspContext } from 'components/LspProvider'
@ -21,8 +22,6 @@ import Gizmo from 'components/Gizmo'
import { CoreDumpManager } from 'lib/coredump'
import { UnitsMenu } from 'components/UnitsMenu'
import { CameraProjectionToggle } from 'components/CameraProjectionToggle'
import EngineStreamContext from 'hooks/useEngineStreamContext'
import { EngineStream } from 'components/EngineStream'
import { maybeWriteToDisk } from 'lib/telemetry'
maybeWriteToDisk()
.then(() => {})
@ -38,13 +37,6 @@ export function App() {
// the coredump.
const ref = useRef<HTMLDivElement>(null)
// Stream related refs and data
const videoRef = useRef<HTMLVideoElement>(null)
const canvasRef = useRef<HTMLCanvasElement>(null)
const modelingSidebarRef = useRef<HTMLUListElement>(null)
let [searchParams] = useSearchParams()
const pool = searchParams.get('pool')
const projectName = project?.name || null
const projectPath = project?.path || null
useEffect(() => {
@ -65,10 +57,6 @@ export function App() {
app: { onboardingStatus },
} = settings.context
useEffect(() => {
sceneInfra.camControls.modelingSidebarRef = modelingSidebarRef
}, [modelingSidebarRef.current])
useHotkeys('backspace', (e) => {
e.preventDefault()
})
@ -96,26 +84,14 @@ export function App() {
enableMenu={true}
/>
<ModalContainer />
<ModelingSidebar paneOpacity={paneOpacity} ref={modelingSidebarRef} />
<EngineStreamContext.Provider
options={{
input: {
videoRef,
canvasRef,
mediaStream: null,
authToken: auth?.context?.token ?? null,
pool,
},
}}
>
<EngineStream />
{/* <CamToggle /> */}
<LowerRightControls coreDumpManager={coreDumpManager}>
<UnitsMenu />
<Gizmo />
<CameraProjectionToggle />
</LowerRightControls>
</EngineStreamContext.Provider>
<ModelingSidebar paneOpacity={paneOpacity} />
<Stream />
{/* <CamToggle /> */}
<LowerRightControls coreDumpManager={coreDumpManager}>
<UnitsMenu />
<Gizmo />
<CameraProjectionToggle />
</LowerRightControls>
</div>
)
}

View File

@ -22,7 +22,6 @@ import {
} from 'lib/toolbar'
import { isDesktop } from 'lib/isDesktop'
import { openExternalBrowserIfDesktop } from 'lib/openWindow'
import { EngineConnectionStateType } from 'lang/std/engineConnection'
export function Toolbar({
className = '',
@ -49,7 +48,7 @@ export function Toolbar({
}, [engineCommandManager.artifactGraph, context.selectionRanges])
const toolbarButtonsRef = useRef<HTMLUListElement>(null)
const { overallState, immediateState } = useNetworkContext()
const { overallState } = useNetworkContext()
const { isExecuting } = useKclContext()
const { isStreamReady } = useAppState()
@ -57,7 +56,6 @@ export function Toolbar({
(overallState !== NetworkHealthState.Ok &&
overallState !== NetworkHealthState.Weak) ||
isExecuting ||
immediateState.type !== EngineConnectionStateType.ConnectionEstablished ||
!isStreamReady
const currentMode =
@ -143,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
@ -151,6 +150,7 @@ export function Toolbar({
/>
)
} else if (Array.isArray(maybeIconConfig)) {
// A button with a dropdown
return (
<ActionButtonDropdown
Element="button"
@ -217,6 +217,7 @@ export function Toolbar({
}
const itemConfig = maybeIconConfig
// A single button
return (
<div className="relative" key={itemConfig.id}>
<ActionButton

View File

@ -1,5 +1,3 @@
import { Models } from '@kittycad/lib'
import { MutableRefObject } from 'react'
import { cameraMouseDragGuards, MouseGuard } from 'lib/cameraControls'
import {
Euler,
@ -89,9 +87,6 @@ class CameraRateLimiter {
export class CameraControls {
engineCommandManager: EngineCommandManager
modelingSidebarRef: MutableRefObject<HTMLUListElement | null> = {
current: null,
}
syncDirection: 'clientToEngine' | 'engineToClient' = 'engineToClient'
camera: PerspectiveCamera | OrthographicCamera
target: Vector3
@ -100,13 +95,6 @@ export class CameraControls {
wasDragging: boolean
mouseDownPosition: Vector2
mouseNewPosition: Vector2
cameraDragStartXY = new Vector2()
old:
| {
camera: PerspectiveCamera | OrthographicCamera
target: Vector3
}
| undefined
rotationSpeed = 0.3
enableRotate = true
enablePan = true
@ -473,7 +461,6 @@ export class CameraControls {
if (this.syncDirection === 'engineToClient') {
const interaction = this.getInteractionType(event)
if (interaction === 'none') return
void this.engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd: {
@ -922,123 +909,18 @@ export class CameraControls {
up: { x: 0, y: 0, z: 1 },
},
})
await this.centerModelRelativeToPanes({
zoomToFit: true,
resetLastPaneWidth: true,
})
this.cameraDragStartXY = new Vector2()
this.cameraDragStartXY.x = 0
this.cameraDragStartXY.y = 0
}
async restoreCameraPosition(): Promise<void> {
if (!this.old) return
this.camera = this.old.camera.clone()
this.target = this.old.target.clone()
void this.engineCommandManager.sendSceneCommand({
await this.engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'default_camera_look_at',
...convertThreeCamValuesToEngineCam({
isPerspective: true,
position: this.camera.position,
quaternion: this.camera.quaternion,
zoom: this.camera.zoom,
target: this.target,
}),
type: 'zoom_to_fit',
object_ids: [], // leave empty to zoom to all objects
padding: 0.2, // padding around the objects
animated: false, // don't animate the zoom for now
},
})
}
private lastFramePaneWidth: number = 0
async centerModelRelativeToPanes(args?: {
zoomObjectId?: string
zoomToFit?: boolean
resetLastPaneWidth?: boolean
}): Promise<void> {
const panes = this.modelingSidebarRef?.current
if (!panes) return
const panesWidth = panes.offsetWidth + panes.offsetLeft
if (args?.resetLastPaneWidth) {
this.lastFramePaneWidth = 0
}
const goPx =
(panesWidth - this.lastFramePaneWidth) / 2 / window.devicePixelRatio
this.lastFramePaneWidth = panesWidth
// Originally I had tried to use the default_camera_look_at endpoint and
// some quaternion math to move the camera right, but it ended up being
// overly complicated, and I think the threejs scene also doesn't have the
// camera coordinates after a zoom-to-fit... So this is much easier, and
// maps better to screen coordinates.
const requests: Models['ModelingCmdReq_type'][] = [
{
cmd: {
type: 'camera_drag_start',
interaction: 'pan',
window: { x: goPx < 0 ? -goPx : 0, y: 0 },
},
cmd_id: uuidv4(),
},
{
cmd: {
type: 'camera_drag_move',
interaction: 'pan',
window: {
x: goPx < 0 ? 0 : goPx,
y: 0,
},
},
cmd_id: uuidv4(),
},
]
if (args?.zoomToFit) {
requests.unshift({
cmd: {
type: 'zoom_to_fit',
object_ids: args?.zoomObjectId ? [args?.zoomObjectId] : [], // leave empty to zoom to all objects
padding: 0.2, // padding around the objects
},
cmd_id: uuidv4(),
})
}
await this.engineCommandManager
.sendSceneCommand({
type: 'modeling_cmd_batch_req',
batch_id: uuidv4(),
responses: true,
requests,
})
// engineCommandManager can't subscribe to batch responses so we'll send
// this one off by its lonesome after.
.then(() =>
this.engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd: {
type: 'camera_drag_end',
interaction: 'pan',
window: {
x: goPx < 0 ? 0 : goPx,
y: 0,
},
},
cmd_id: uuidv4(),
})
)
}
async tweenCameraToQuaternion(
targetQuaternion: Quaternion,
targetPosition = new Vector3(),

View File

@ -1,11 +1,4 @@
import {
CSSProperties,
useRef,
useEffect,
useState,
useMemo,
Fragment,
} from 'react'
import { useRef, useEffect, useState, useMemo, Fragment } from 'react'
import { useModelingContext } from 'hooks/useModelingContext'
import { cameraMouseDragGuards } from 'lib/cameraControls'
@ -249,13 +242,6 @@ const Overlay = ({
state.matches({ Sketch: 'Rectangle tool' })
)
// Line labels will cover the constraints overlay if this is not used.
// For each line label, ThreeJS increments each CSS2DObject z-index as they
// are added. I have looked into overriding renderOrder and depthTest and
// while renderOrder is set, ThreeJS still sets z-index on these 2D objects.
// It is easier to set this to a large number, such as a billion.
const zIndex = 1000000000
return (
<div className={`absolute w-0 h-0`}>
<div
@ -266,7 +252,6 @@ const Overlay = ({
data-overlay-angle={overlay.angle}
className="pointer-events-auto absolute w-0 h-0"
style={{
zIndex,
transform: `translate3d(${overlay.windowCoords[0]}px, ${overlay.windowCoords[1]}px, 0)`,
}}
></div>
@ -275,7 +260,6 @@ const Overlay = ({
data-overlay-toolbar-index={overlayIndex}
className={`px-0 pointer-events-auto absolute flex gap-1`}
style={{
zIndex,
transform: `translate3d(calc(${
overlay.windowCoords[0] + xOffset
}px + ${xAlignment}), calc(${
@ -317,7 +301,6 @@ const Overlay = ({
*/}
{callExpression?.callee?.name !== 'circle' && (
<SegmentMenu
style={{ zIndex }}
verticalPosition={
overlay.windowCoords[1] > window.innerHeight / 2
? 'top'
@ -459,17 +442,15 @@ const SegmentMenu = ({
verticalPosition,
pathToNode,
stdLibFnName,
style,
}: {
verticalPosition: 'top' | 'bottom'
pathToNode: PathToNode
stdLibFnName: string
style?: CSSProperties
}) => {
const { send } = useModelingContext()
const dependentSourceRanges = findUsesOfTagInPipe(kclManager.ast, pathToNode)
return (
<Popover style={style} className="relative">
<Popover className="relative">
{({ open }) => (
<>
<Popover.Button

View File

@ -1,22 +0,0 @@
// 63.5 is definitely a bit of a magic number, play with it until it looked right
// if it were 64, that would feel like it's something in the engine where a random
// power of 2 is used, but it's the 0.5 seems to make things look much more correct
export const ZOOM_MAGIC_NUMBER = 63.5
export const INTERSECTION_PLANE_LAYER = 1
export const SKETCH_LAYER = 2
export const RAYCASTABLE_PLANE = 'raycastable-plane'
// redundant types so that it can be changed temporarily but CI will catch the wrong type
export const DEBUG_SHOW_INTERSECTION_PLANE: false = false
export const DEBUG_SHOW_BOTH_SCENES: false = false
export const X_AXIS = 'xAxis'
export const Y_AXIS = 'yAxis'
export const AXIS_GROUP = 'axisGroup'
export const SKETCH_GROUP_SEGMENTS = 'sketch-group-segments'
export const ARROWHEAD = 'arrowhead'
export const SEGMENT_LENGTH_LABEL = 'segment-length-label'
export const SEGMENT_LENGTH_LABEL_TEXT = 'segment-length-label-text'
export const SEGMENT_LENGTH_LABEL_OFFSET_PX = 30

View File

@ -2,7 +2,10 @@ import { compareVec2Epsilon2 } from 'lang/std/sketch'
import {
GridHelper,
LineBasicMaterial,
OrthographicCamera,
PerspectiveCamera,
Group,
Mesh,
Quaternion,
Vector3,
} from 'three'
@ -25,9 +28,15 @@ export function createGridHelper({
gridHelper.rotation.x = Math.PI / 2
return gridHelper
}
const fudgeFactor = 72.66985970437086
// Re-export scale.ts
export * from './scale'
export const orthoScale = (cam: OrthographicCamera | PerspectiveCamera) =>
(0.55 * fudgeFactor) / cam.zoom / window.innerHeight
export const perspScale = (cam: PerspectiveCamera, group: Group | Mesh) =>
(group.position.distanceTo(cam.position) * cam.fov * fudgeFactor) /
4000 /
window.innerHeight
export function isQuaternionVertical(q: Quaternion) {
const v = new Vector3(0, 0, 1).applyQuaternion(q)

View File

@ -1,17 +0,0 @@
import { OrthographicCamera, PerspectiveCamera, Group, Mesh } from 'three'
export const fudgeFactor = 72.66985970437086
export const orthoScale = (
cam: OrthographicCamera | PerspectiveCamera,
innerHeight?: number
) => (0.55 * fudgeFactor) / cam.zoom / (innerHeight ?? window.innerHeight)
export const perspScale = (
cam: PerspectiveCamera,
group: Group | Mesh,
innerHeight?: number
) =>
(group.position.distanceTo(cam.position) * cam.fov * fudgeFactor) /
4000 /
(innerHeight ?? window.innerHeight)

View File

@ -47,6 +47,7 @@ import {
VariableDeclaration,
VariableDeclarator,
sketchFromKclValue,
sketchFromKclValueOptional,
} from 'lang/wasm'
import {
engineCommandManager,
@ -89,9 +90,10 @@ 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'
@ -1043,6 +1045,174 @@ export class SceneEntities {
},
})
}
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 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,
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)
)
}
},
})
}
setupDraftCircle = async (
sketchPathToNode: PathToNode,
forward: [number, number, number],
@ -1528,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

@ -291,14 +291,14 @@ export class SceneInfra {
engineCommandManager
)
this.camControls.subscribeToCamChange(() => this.onCameraChange())
this.camControls.camera.layers.enable(constants.SKETCH_LAYER)
if (constants.DEBUG_SHOW_INTERSECTION_PLANE)
this.camControls.camera.layers.enable(constants.INTERSECTION_PLANE_LAYER)
this.camControls.camera.layers.enable(SKETCH_LAYER)
if (DEBUG_SHOW_INTERSECTION_PLANE)
this.camControls.camera.layers.enable(INTERSECTION_PLANE_LAYER)
// RAYCASTERS
this.raycaster.layers.enable(constants.SKETCH_LAYER)
this.raycaster.layers.enable(SKETCH_LAYER)
this.raycaster.layers.disable(0)
this.planeRaycaster.layers.enable(constants.INTERSECTION_PLANE_LAYER)
this.planeRaycaster.layers.enable(INTERSECTION_PLANE_LAYER)
// GRID
const size = 100
@ -333,7 +333,7 @@ export class SceneInfra {
this.camControls.target
)
const axisGroup = this.scene
.getObjectByName(constants.AXIS_GROUP)
.getObjectByName(AXIS_GROUP)
?.getObjectByName('gridHelper')
axisGroup?.name === 'gridHelper' && axisGroup.scale.set(scale, scale, scale)
}
@ -344,6 +344,7 @@ export class SceneInfra {
}
animate = () => {
requestAnimationFrame(this.animate)
TWEEN.update() // This will update all tweens during the animation loop
if (!this.isFovAnimationInProgress) {
// console.log('animation frame', this.cameraControls.camera)
@ -351,7 +352,6 @@ export class SceneInfra {
this.renderer.render(this.scene, this.camControls.camera)
this.labelRenderer.render(this.scene, this.camControls.camera)
}
requestAnimationFrame(this.animate)
}
dispose = () => {
@ -655,11 +655,11 @@ export class SceneInfra {
}
updateOtherSelectionColors = (otherSelections: Axis[]) => {
const axisGroup = this.scene.children.find(
({ userData }) => userData?.type === constants.AXIS_GROUP
({ userData }) => userData?.type === AXIS_GROUP
)
const axisMap: { [key: string]: Axis } = {
[constants.X_AXIS]: 'x-axis',
[constants.Y_AXIS]: 'y-axis',
[X_AXIS]: 'x-axis',
[Y_AXIS]: 'y-axis',
}
axisGroup?.children.forEach((_mesh) => {
const mesh = _mesh as Mesh

View File

@ -300,7 +300,7 @@ class StraightSegment implements SegmentUtils {
sceneInfra.updateOverlayDetails({
arrowGroup,
group,
isHandlesVisible: true,
isHandlesVisible,
from,
to,
})
@ -476,7 +476,7 @@ class TangentialArcToSegment implements SegmentUtils {
sceneInfra.updateOverlayDetails({
arrowGroup,
group,
isHandlesVisible: true,
isHandlesVisible,
from,
to,
angle,
@ -542,7 +542,7 @@ class CircleSegment implements SegmentUtils {
}
group.name = CIRCLE_SEGMENT
group.add(arcMesh, arrowGroup, circleCenterGroup)
group.add(arcMesh, arrowGroup, circleCenterGroup, radiusIndicatorGroup)
const updateOverlaysCallback = this.update({
prevSegment,
input,
@ -677,7 +677,7 @@ class CircleSegment implements SegmentUtils {
sceneInfra.updateOverlayDetails({
arrowGroup,
group,
isHandlesVisible: true,
isHandlesVisible,
from: from,
to: [center[0], center[1]],
angle: Math.PI / 4,

View File

@ -1,8 +1,6 @@
import { Dialog, Popover, Transition } from '@headlessui/react'
import { Fragment, useEffect } from 'react'
import { useCommandsContext } from 'hooks/useCommandsContext'
import { useNetworkContext } from 'hooks/useNetworkContext'
import { EngineConnectionStateType } from 'lang/std/engineConnection'
import CommandBarArgument from './CommandBarArgument'
import CommandComboBox from '../CommandComboBox'
import CommandBarReview from './CommandBarReview'
@ -16,7 +14,6 @@ export const COMMAND_PALETTE_HOTKEY = 'mod+k'
export const CommandBar = () => {
const { pathname } = useLocation()
const { commandBarState, commandBarSend } = useCommandsContext()
const { immediateState } = useNetworkContext()
const {
context: { selectedCommand, currentArgument, commands },
} = commandBarState
@ -28,14 +25,6 @@ export const CommandBar = () => {
commandBarSend({ type: 'Close' })
}, [pathname])
useEffect(() => {
if (
immediateState.type !== EngineConnectionStateType.ConnectionEstablished
) {
commandBarSend({ type: 'Close' })
}
}, [immediateState])
// Hook up keyboard shortcuts
useHotkeyWrapper([COMMAND_PALETTE_HOTKEY], () => {
if (commandBarState.context.commands.length === 0) return

View File

@ -2,20 +2,13 @@ import { useCommandsContext } from 'hooks/useCommandsContext'
import usePlatform from 'hooks/usePlatform'
import { hotkeyDisplay } from 'lib/hotkeyWrapper'
import { COMMAND_PALETTE_HOTKEY } from './CommandBar/CommandBar'
import { useNetworkContext } from 'hooks/useNetworkContext'
import { EngineConnectionStateType } from 'lang/std/engineConnection'
export function CommandBarOpenButton() {
const { commandBarSend } = useCommandsContext()
const { immediateState } = useNetworkContext()
const platform = usePlatform()
const isDisabled =
immediateState.type !== EngineConnectionStateType.ConnectionEstablished
return (
<button
disabled={isDisabled}
className="group rounded-full flex items-center justify-center gap-2 px-2 py-1 bg-primary/10 dark:bg-chalkboard-90 dark:backdrop-blur-sm border-primary hover:border-primary dark:border-chalkboard-50 dark:hover:border-inherit text-primary dark:text-inherit"
onClick={() => commandBarSend({ type: 'Open' })}
data-testid="command-bar-open-button"

View File

@ -1,293 +0,0 @@
import { MouseEventHandler, useEffect, useRef } from 'react'
import { useAppState } from 'AppState'
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
import { useModelingContext } from 'hooks/useModelingContext'
import { useNetworkContext } from 'hooks/useNetworkContext'
import { NetworkHealthState } from 'hooks/useNetworkStatus'
import { ClientSideScene } from 'clientSideScene/ClientSideSceneComp'
import { btnName } from 'lib/cameraControls'
import { trap } from 'lib/trap'
import { sendSelectEventToEngine } from 'lib/selections'
import { kclManager, engineCommandManager } from 'lib/singletons'
import { EngineCommandManagerEvents } from 'lang/std/engineConnection'
import { useRouteLoaderData } from 'react-router-dom'
import { PATHS } from 'lib/paths'
import { IndexLoaderData } from 'lib/types'
import useEngineStreamContext, {
EngineStreamState,
EngineStreamTransition,
} from 'hooks/useEngineStreamContext'
import { REASONABLE_TIME_TO_REFRESH_STREAM_SIZE } from 'lib/timings'
export const EngineStream = () => {
const { setAppState } = useAppState()
const { overallState } = useNetworkContext()
const { settings } = useSettingsAuthContext()
const { file } = useRouteLoaderData(PATHS.FILE) as IndexLoaderData
const last = useRef<number>(Date.now())
const settingsEngine = {
theme: settings.context.app.theme.current,
enableSSAO: settings.context.app.enableSSAO.current,
highlightEdges: settings.context.modeling.highlightEdges.current,
showScaleGrid: settings.context.modeling.showScaleGrid.current,
cameraProjection: settings.context.modeling.cameraProjection.current,
}
const { state: modelingMachineState, send: modelingMachineActorSend } =
useModelingContext()
const engineStreamActor = useEngineStreamContext.useActorRef()
const engineStreamState = engineStreamActor.getSnapshot()
const streamIdleMode = settings.context.app.streamIdleMode.current
const configure = () => {
engineStreamActor.send({
type: EngineStreamTransition.StartOrReconfigureEngine,
modelingMachineActorSend,
settings: settingsEngine,
setAppState,
// It's possible a reconnect happens as we drag the window :')
onMediaStream(mediaStream: MediaStream) {
engineStreamActor.send({
type: EngineStreamTransition.SetMediaStream,
mediaStream,
})
},
})
}
useEffect(() => {
const play = () => {
engineStreamActor.send({
type: EngineStreamTransition.Play,
})
}
engineCommandManager.addEventListener(
EngineCommandManagerEvents.SceneReady,
play
)
return () => {
engineCommandManager.removeEventListener(
EngineCommandManagerEvents.SceneReady,
play
)
}
}, [])
useEffect(() => {
const video = engineStreamState.context.videoRef?.current
if (!video) return
const canvas = engineStreamState.context.canvasRef?.current
if (!canvas) return
new ResizeObserver(() => {
if (Date.now() - last.current < REASONABLE_TIME_TO_REFRESH_STREAM_SIZE)
return
last.current = Date.now()
if (
Math.abs(video.width - window.innerWidth) > 4 ||
Math.abs(video.height - window.innerHeight) > 4
) {
timeoutStart.current = Date.now()
configure()
}
}).observe(document.body)
}, [engineStreamState.value])
// When the video and canvas element references are set, start the engine.
useEffect(() => {
if (
engineStreamState.context.canvasRef.current &&
engineStreamState.context.videoRef.current
) {
engineStreamActor.send({
type: EngineStreamTransition.StartOrReconfigureEngine,
modelingMachineActorSend,
settings: settingsEngine,
setAppState,
onMediaStream(mediaStream: MediaStream) {
engineStreamActor.send({
type: EngineStreamTransition.SetMediaStream,
mediaStream,
})
},
})
}
}, [
engineStreamState.context.canvasRef.current,
engineStreamState.context.videoRef.current,
])
// On settings change, reconfigure the engine. When paused this gets really tricky,
// and also requires onMediaStream to be set!
useEffect(() => {
engineStreamActor.send({
type: EngineStreamTransition.StartOrReconfigureEngine,
modelingMachineActorSend,
settings: settingsEngine,
setAppState,
onMediaStream(mediaStream: MediaStream) {
engineStreamActor.send({
type: EngineStreamTransition.SetMediaStream,
mediaStream,
})
},
})
}, [settings.context])
/**
* Subscribe to execute code when the file changes
* but only if the scene is already ready.
* See onSceneReady for the initial scene setup.
*/
useEffect(() => {
if (engineCommandManager.engineConnection?.isReady() && file?.path) {
console.log('execute on file change')
void kclManager.executeCode(true).catch(trap)
}
}, [file?.path, engineCommandManager.engineConnection])
const IDLE_TIME_MS = Number(streamIdleMode)
// When streamIdleMode is changed, setup or teardown the timeouts
const timeoutStart = useRef<number | null>(null)
useEffect(() => {
timeoutStart.current = streamIdleMode ? Date.now() : null
}, [streamIdleMode])
useEffect(() => {
let frameId: ReturnType<typeof window.requestAnimationFrame> = 0
const frameLoop = () => {
// Do not pause if the user is in the middle of an operation
if (!modelingMachineState.matches('idle')) {
// In fact, stop the timeout, because we don't want to trigger the
// pause when we exit the operation.
timeoutStart.current = null
} else if (timeoutStart.current) {
const elapsed = Date.now() - timeoutStart.current
if (elapsed >= IDLE_TIME_MS) {
timeoutStart.current = null
engineStreamActor.send({ type: EngineStreamTransition.Pause })
}
}
frameId = window.requestAnimationFrame(frameLoop)
}
frameId = window.requestAnimationFrame(frameLoop)
return () => {
window.cancelAnimationFrame(frameId)
}
}, [modelingMachineState])
useEffect(() => {
if (!streamIdleMode) return
const onAnyInput = () => {
// Just in case it happens in the middle of the user turning off
// idle mode.
if (!streamIdleMode) {
timeoutStart.current = null
return
}
if (engineStreamState.value === EngineStreamState.Paused) {
engineStreamActor.send({
type: EngineStreamTransition.StartOrReconfigureEngine,
modelingMachineActorSend,
settings: settingsEngine,
setAppState,
onMediaStream(mediaStream: MediaStream) {
engineStreamActor.send({
type: EngineStreamTransition.SetMediaStream,
mediaStream,
})
},
})
}
timeoutStart.current = Date.now()
}
// It's possible after a reconnect, the user doesn't move their mouse at
// all, meaning the timer is not reset to run. We need to set it every
// time our effect dependencies change then.
timeoutStart.current = Date.now()
window.document.addEventListener('keydown', onAnyInput)
window.document.addEventListener('keyup', onAnyInput)
window.document.addEventListener('mousemove', onAnyInput)
window.document.addEventListener('mousedown', onAnyInput)
window.document.addEventListener('mouseup', onAnyInput)
window.document.addEventListener('scroll', onAnyInput)
window.document.addEventListener('touchstart', onAnyInput)
window.document.addEventListener('touchstop', onAnyInput)
return () => {
timeoutStart.current = null
window.document.removeEventListener('keydown', onAnyInput)
window.document.removeEventListener('keyup', onAnyInput)
window.document.removeEventListener('mousemove', onAnyInput)
window.document.removeEventListener('mousedown', onAnyInput)
window.document.removeEventListener('mouseup', onAnyInput)
window.document.removeEventListener('scroll', onAnyInput)
window.document.removeEventListener('touchstart', onAnyInput)
window.document.removeEventListener('touchstop', onAnyInput)
}
}, [streamIdleMode, engineStreamState.value])
const isNetworkOkay =
overallState === NetworkHealthState.Ok ||
overallState === NetworkHealthState.Weak
const handleMouseUp: MouseEventHandler<HTMLDivElement> = (e) => {
if (!isNetworkOkay) return
if (!engineStreamState.context.videoRef.current) return
if (modelingMachineState.matches('Sketch')) return
if (modelingMachineState.matches({ idle: 'showPlanes' })) return
if (btnName(e.nativeEvent).left) {
// eslint-disable-next-line @typescript-eslint/no-floating-promises
sendSelectEventToEngine(e, engineStreamState.context.videoRef.current)
}
}
return (
<div
className="absolute inset-0 z-0"
id="stream"
data-testid="stream"
onMouseUp={handleMouseUp}
onContextMenu={(e) => e.preventDefault()}
onContextMenuCapture={(e) => e.preventDefault()}
>
<video
autoPlay
muted
key={engineStreamActor.id + 'video'}
ref={engineStreamState.context.videoRef}
controls={false}
className="cursor-pointer"
disablePictureInPicture
id="video-stream"
/>
<canvas
key={engineStreamActor.id + 'canvas'}
ref={engineStreamState.context.canvasRef}
className="cursor-pointer"
id="freeze-frame"
>
No canvas support
</canvas>
<ClientSideScene
cameraControls={settings.context.modeling.mouseControls.current}
/>
</div>
)
}

View File

@ -1,47 +1,40 @@
import { useEffect, useState } from 'react'
import { useEngineCommands } from './EngineCommands'
import { Spinner } from './Spinner'
import { CustomIcon } from './CustomIcon'
import useEngineStreamContext, {
EngineStreamState,
} from 'hooks/useEngineStreamContext'
import { CommandLogType } from 'lang/std/engineConnection'
export const ModelStateIndicator = () => {
const [commands] = useEngineCommands()
const [isDone, setIsDone] = useState<boolean>(false)
const engineStreamActor = useEngineStreamContext.useActorRef()
const engineStreamState = engineStreamActor.getSnapshot()
const lastCommandType = commands[commands.length - 1]?.type
useEffect(() => {
if (lastCommandType === CommandLogType.SetDefaultSystemProperties) {
setIsDone(false)
}
if (lastCommandType === CommandLogType.ExecutionDone) {
setIsDone(true)
}
}, [lastCommandType])
let className = 'w-6 h-6 '
let icon = <div className={className}></div>
let icon = <Spinner className={className} />
let dataTestId = 'model-state-indicator'
if (engineStreamState.value === EngineStreamState.Paused) {
className += 'text-secondary'
icon = <CustomIcon data-testid={dataTestId + '-paused'} name="parallel" />
} else if (engineStreamState.value === EngineStreamState.Resuming) {
className += 'text-secondary'
icon = <CustomIcon data-testid={dataTestId + '-resuming'} name="parallel" />
} else if (isDone) {
className += 'text-secondary'
if (lastCommandType === 'receive-reliable') {
className +=
'bg-chalkboard-20 dark:bg-chalkboard-80 !group-disabled:bg-chalkboard-30 !dark:group-disabled:bg-chalkboard-80 rounded-sm bg-succeed-10/30 dark:bg-succeed'
icon = (
<CustomIcon
data-testid={dataTestId + '-receive-reliable'}
name="checkmark"
/>
)
} else if (lastCommandType === 'execution-done') {
className +=
'border-6 border border-solid border-chalkboard-60 dark:border-chalkboard-80 bg-chalkboard-20 dark:bg-chalkboard-80 !group-disabled:bg-chalkboard-30 !dark:group-disabled:bg-chalkboard-80 rounded-sm bg-succeed-10/30 dark:bg-succeed'
icon = (
<CustomIcon
data-testid={dataTestId + '-execution-done'}
name="checkmark"
/>
)
} else if (lastCommandType === 'export-done') {
className +=
'border-6 border border-solid border-chalkboard-60 dark:border-chalkboard-80 bg-chalkboard-20 dark:bg-chalkboard-80 !group-disabled:bg-chalkboard-30 !dark:group-disabled:bg-chalkboard-80 rounded-sm bg-succeed-10/30 dark:bg-succeed'
icon = (
<CustomIcon data-testid={dataTestId + '-export-done'} name="checkmark" />
)
}
return (

View File

@ -20,6 +20,7 @@ import {
modelingMachine,
modelingMachineDefaultContext,
} from 'machines/modelingMachine'
import { useSetupEngineManager } from 'hooks/useSetupEngineManager'
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
import {
isCursorInSketchCommandRange,
@ -62,6 +63,7 @@ import {
import {
moveValueIntoNewVariablePath,
sketchOnExtrudedFace,
sketchOnOffsetPlane,
startSketchOnDefault,
} from 'lang/modifyAst'
import { Program, parse, recast } from 'lang/wasm'
@ -111,8 +113,13 @@ export const ModelingMachineProvider = ({
auth,
settings: {
context: {
app: { theme },
modeling: { defaultUnit, highlightEdges, cameraProjection },
app: { theme, enableSSAO },
modeling: {
defaultUnit,
cameraProjection,
highlightEdges,
showScaleGrid,
},
},
},
} = useSettingsAuthContext()
@ -123,6 +130,9 @@ export const ModelingMachineProvider = ({
const streamRef = useRef<HTMLDivElement>(null)
const persistedContext = useMemo(() => getPersistedContext(), [])
let [searchParams] = useSearchParams()
const pool = searchParams.get('pool')
const { commandBarState, commandBarSend } = useCommandsContext()
// Settings machine setup
@ -474,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 = {
@ -627,13 +637,16 @@ export const ModelingMachineProvider = ({
),
'animate-to-face': fromPromise(async ({ input }) => {
if (!input) return undefined
if (input.type === 'extrudeFace') {
const sketched = sketchOnExtrudedFace(
kclManager.ast,
input.sketchPathToNode,
input.extrudePathToNode,
input.faceInfo
)
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'
@ -645,13 +658,9 @@ export const ModelingMachineProvider = ({
await kclManager.executeAstMock(modifiedAst)
await letEngineAnimateAndSyncCamAfter(
engineCommandManager,
input.faceId
)
await sceneInfra.camControls.centerModelRelativeToPanes({
resetLastPaneWidth: true,
})
const id =
input.type === 'extrudeFace' ? input.faceId : input.planeId
await letEngineAnimateAndSyncCamAfter(engineCommandManager, id)
sceneInfra.camControls.syncDirection = 'clientToEngine'
return {
sketchPathToNode: pathToNewSketchNode,
@ -672,9 +681,6 @@ export const ModelingMachineProvider = ({
engineCommandManager,
input.planeId
)
await sceneInfra.camControls.centerModelRelativeToPanes({
resetLastPaneWidth: true,
})
return {
sketchPathToNode: pathToNode,
@ -697,9 +703,6 @@ export const ModelingMachineProvider = ({
engineCommandManager,
info?.sketchDetails?.faceId || ''
)
await sceneInfra.camControls.centerModelRelativeToPanes({
resetLastPaneWidth: true,
})
return {
sketchPathToNode: sketchPathToNode || [],
zAxis: info.sketchDetails.zAxis || null,
@ -1068,6 +1071,21 @@ export const ModelingMachineProvider = ({
}
)
useSetupEngineManager(
streamRef,
modelingSend,
modelingState.context,
{
pool: pool,
theme: theme.current,
highlightEdges: highlightEdges.current,
enableSSAO: enableSSAO.current,
showScaleGrid: showScaleGrid.current,
cameraProjection: cameraProjection.current,
},
token
)
useEffect(() => {
kclManager.registerExecuteCallback(() => {
modelingSend({ type: 'Re-execute' })

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'
@ -93,13 +93,13 @@ export const processMemory = (programMemory: ProgramMemory) => {
// @ts-ignore
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 {

View File

@ -6,11 +6,6 @@ import {
useEffect,
useMemo,
useContext,
MutableRefObject,
forwardRef,
// https://stackoverflow.com/a/77055468 Thank you.
useImperativeHandle,
useRef,
} from 'react'
import { useHotkeys } from 'react-hotkeys-hook'
import { SidebarAction, SidebarType, sidebarPanes } from './ModelingPanes'
@ -24,12 +19,9 @@ import { useCommandsContext } from 'hooks/useCommandsContext'
import { IconDefinition } from '@fortawesome/free-solid-svg-icons'
import { useKclContext } from 'lang/KclProvider'
import { MachineManagerContext } from 'components/MachineManagerProvider'
import { sceneInfra } from 'lib/singletons'
import { REASONABLE_TIME_TO_REFRESH_STREAM_SIZE } from 'lib/timings'
interface ModelingSidebarProps {
paneOpacity: '' | 'opacity-20' | 'opacity-40'
ref: MutableRefObject<HTMLDivElement>
}
interface BadgeInfoComputed {
@ -41,34 +33,19 @@ function getPlatformString(): 'web' | 'desktop' {
return isDesktop() ? 'desktop' : 'web'
}
export const ModelingSidebar = forwardRef<
HTMLUListElement,
ModelingSidebarProps
>(function ModelingSidebar({ paneOpacity }, outerRef) {
export function ModelingSidebar({ paneOpacity }: ModelingSidebarProps) {
const machineManager = useContext(MachineManagerContext)
const { commandBarSend } = useCommandsContext()
const kclContext = useKclContext()
const { settings } = useSettingsAuthContext()
const onboardingStatus = settings.context.app.onboardingStatus
const { send, state, context } = useModelingContext()
const { send, context } = useModelingContext()
const pointerEventsCssClass =
onboardingStatus.current === 'camera' ||
context.store?.openPanes.length === 0
? 'pointer-events-none '
: 'pointer-events-auto '
const showDebugPanel = settings.context.modeling.showDebugPanel
const innerRef = useRef<HTMLUListElement>(null)
// forwardRef's type causes me to do this type narrowing.
useEffect(() => {
if (typeof outerRef === 'function') {
outerRef(innerRef.current)
} else {
if (outerRef) {
outerRef.current = innerRef.current
}
}
}, [innerRef.current])
const paneCallbackProps = useMemo(
() => ({
@ -182,37 +159,8 @@ export const ModelingSidebar = forwardRef<
[context.store?.openPanes, send]
)
useEffect(() => {
// Don't send camera adjustment commands after 1 pane is open. It
// won't make any difference.
if (context.store?.openPanes.length > 1) return
void sceneInfra.camControls.centerModelRelativeToPanes()
}, [context.store?.openPanes])
// If the panes are resized then center the model also
useEffect(() => {
if (!innerRef.current) return
let last = Date.now()
const observer = new ResizeObserver(() => {
if (Date.now() - last < REASONABLE_TIME_TO_REFRESH_STREAM_SIZE) return
if (!innerRef.current) return
last = Date.now()
void sceneInfra.camControls.centerModelRelativeToPanes()
})
observer.observe(innerRef.current)
return () => {
observer.disconnect()
}
}, [state, innerRef.current])
return (
<Resizable
data-testid="modeling-sidebar"
className={`group flex-1 flex flex-col z-10 my-2 pr-1 ${paneOpacity} ${pointerEventsCssClass}`}
defaultSize={{
width: '550px',
@ -244,7 +192,6 @@ export const ModelingSidebar = forwardRef<
>
<ul
id="pane-buttons-section"
data-testid="pane-buttons-section"
className={
'w-fit p-2 flex flex-col gap-2 ' +
(context.store?.openPanes.length >= 1 ? 'pr-0.5' : '')
@ -289,8 +236,6 @@ export const ModelingSidebar = forwardRef<
</ul>
<ul
id="pane-section"
data-testid="pane-section"
ref={innerRef}
className={
'ml-[-1px] col-start-2 col-span-1 flex flex-col items-stretch gap-2 ' +
(context.store?.openPanes.length >= 1 ? `w-full` : `hidden`)
@ -320,7 +265,7 @@ export const ModelingSidebar = forwardRef<
</div>
</Resizable>
)
})
}
interface ModelingPaneButtonProps
extends React.HTMLAttributes<HTMLButtonElement> {

340
src/components/Stream.tsx Normal file
View File

@ -0,0 +1,340 @@
import { MouseEventHandler, useEffect, useRef, useState } from 'react'
import Loading from './Loading'
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
import { useModelingContext } from 'hooks/useModelingContext'
import { useNetworkContext } from 'hooks/useNetworkContext'
import { NetworkHealthState } from 'hooks/useNetworkStatus'
import { ClientSideScene } from 'clientSideScene/ClientSideSceneComp'
import { btnName } from 'lib/cameraControls'
import { sendSelectEventToEngine } from 'lib/selections'
import { kclManager, engineCommandManager, sceneInfra } from 'lib/singletons'
import { useAppStream } from 'AppState'
import {
EngineCommandManagerEvents,
EngineConnectionStateType,
DisconnectingType,
} from 'lang/std/engineConnection'
import { useRouteLoaderData } from 'react-router-dom'
import { PATHS } from 'lib/paths'
import { IndexLoaderData } from 'lib/types'
enum StreamState {
Playing = 'playing',
Paused = 'paused',
Resuming = 'resuming',
Unset = 'unset',
}
export const Stream = () => {
const [isLoading, setIsLoading] = useState(true)
const videoRef = useRef<HTMLVideoElement>(null)
const { settings } = useSettingsAuthContext()
const { state, send } = useModelingContext()
const { mediaStream } = useAppStream()
const { overallState, immediateState } = useNetworkContext()
const [streamState, setStreamState] = useState(StreamState.Unset)
const { file } = useRouteLoaderData(PATHS.FILE) as IndexLoaderData
const IDLE = settings.context.app.streamIdleMode.current
const isNetworkOkay =
overallState === NetworkHealthState.Ok ||
overallState === NetworkHealthState.Weak
/**
* Execute code and show a "building scene message"
* in Stream.tsx in the meantime.
*
* I would like for this to live somewhere more central,
* but it seems to me that we need the video element ref
* to be able to play the video after the code has been
* executed. If we can find a way to do this from a more
* central place, we can move this code there.
*/
function executeCodeAndPlayStream() {
// eslint-disable-next-line @typescript-eslint/no-floating-promises
kclManager.executeCode(true).then(async () => {
await videoRef.current?.play().catch((e) => {
console.warn('Video playing was prevented', e, videoRef.current)
})
setStreamState(StreamState.Playing)
})
}
/**
* Subscribe to execute code when the file changes
* but only if the scene is already ready.
* See onSceneReady for the initial scene setup.
*/
useEffect(() => {
if (engineCommandManager.engineConnection?.isReady() && file?.path) {
console.log('execute on file change')
executeCodeAndPlayStream()
}
}, [file?.path, engineCommandManager.engineConnection])
useEffect(() => {
if (
immediateState.type === EngineConnectionStateType.Disconnecting &&
immediateState.value.type === DisconnectingType.Pause
) {
setStreamState(StreamState.Paused)
}
}, [immediateState])
// Linux has a default behavior to paste text on middle mouse up
// This adds a listener to block that pasting if the click target
// is not a text input, so users can move in the 3D scene with
// middle mouse drag with a text input focused without pasting.
useEffect(() => {
const handlePaste = (e: ClipboardEvent) => {
const isHtmlElement = e.target && e.target instanceof HTMLElement
const isEditable =
(isHtmlElement && !('explicitOriginalTarget' in e)) ||
('explicitOriginalTarget' in e &&
((e.explicitOriginalTarget as HTMLElement).contentEditable ===
'true' ||
['INPUT', 'TEXTAREA'].some(
(tagName) =>
tagName === (e.explicitOriginalTarget as HTMLElement).tagName
)))
if (!isEditable) {
e.preventDefault()
e.stopPropagation()
e.stopImmediatePropagation()
}
}
globalThis?.window?.document?.addEventListener('paste', handlePaste, {
capture: true,
})
const IDLE_TIME_MS = 1000 * 60 * 2
let timeoutIdIdleA: ReturnType<typeof setTimeout> | undefined = undefined
const teardown = () => {
// Already paused
if (streamState === StreamState.Paused) return
videoRef.current?.pause()
setStreamState(StreamState.Paused)
sceneInfra.modelingSend({ type: 'Cancel' })
// Give video time to pause
window.requestAnimationFrame(() => {
engineCommandManager.tearDown({ idleMode: true })
})
}
const onVisibilityChange = () => {
if (globalThis.window.document.visibilityState === 'hidden') {
clearTimeout(timeoutIdIdleA)
timeoutIdIdleA = setTimeout(teardown, IDLE_TIME_MS)
} else if (!engineCommandManager.engineConnection?.isReady()) {
clearTimeout(timeoutIdIdleA)
setStreamState(StreamState.Resuming)
}
}
// Teardown everything if we go hidden or reconnect
if (IDLE) {
globalThis?.window?.document?.addEventListener(
'visibilitychange',
onVisibilityChange
)
}
let timeoutIdIdleB: ReturnType<typeof setTimeout> | undefined = undefined
const onAnyInput = () => {
if (streamState === StreamState.Playing) {
// Clear both timers
clearTimeout(timeoutIdIdleA)
clearTimeout(timeoutIdIdleB)
timeoutIdIdleB = setTimeout(teardown, IDLE_TIME_MS)
}
if (streamState === StreamState.Paused) {
setStreamState(StreamState.Resuming)
}
}
if (IDLE) {
globalThis?.window?.document?.addEventListener('keydown', onAnyInput)
globalThis?.window?.document?.addEventListener('mousemove', onAnyInput)
globalThis?.window?.document?.addEventListener('mousedown', onAnyInput)
globalThis?.window?.document?.addEventListener('scroll', onAnyInput)
globalThis?.window?.document?.addEventListener('touchstart', onAnyInput)
}
if (IDLE) {
timeoutIdIdleB = setTimeout(teardown, IDLE_TIME_MS)
}
/**
* Add a listener to execute code and play the stream
* on initial stream setup.
*/
engineCommandManager.addEventListener(
EngineCommandManagerEvents.SceneReady,
executeCodeAndPlayStream
)
return () => {
engineCommandManager.removeEventListener(
EngineCommandManagerEvents.SceneReady,
executeCodeAndPlayStream
)
globalThis?.window?.document?.removeEventListener('paste', handlePaste, {
capture: true,
})
if (IDLE) {
clearTimeout(timeoutIdIdleA)
clearTimeout(timeoutIdIdleB)
globalThis?.window?.document?.removeEventListener(
'visibilitychange',
onVisibilityChange
)
globalThis?.window?.document?.removeEventListener('keydown', onAnyInput)
globalThis?.window?.document?.removeEventListener(
'mousemove',
onAnyInput
)
globalThis?.window?.document?.removeEventListener(
'mousedown',
onAnyInput
)
globalThis?.window?.document?.removeEventListener('scroll', onAnyInput)
globalThis?.window?.document?.removeEventListener(
'touchstart',
onAnyInput
)
}
}
}, [IDLE, streamState])
/**
* Play the vid
*/
useEffect(() => {
if (!kclManager.isExecuting) {
setTimeout(() => {
// execute in the next event loop
videoRef.current?.play().catch((e) => {
console.warn('Video playing was prevented', e, videoRef.current)
})
})
}
}, [kclManager.isExecuting])
useEffect(() => {
if (
typeof window === 'undefined' ||
typeof RTCPeerConnection === 'undefined'
)
return
if (!videoRef.current) return
if (!mediaStream) return
// The browser complains if we try to load a new stream without pausing first.
// Do not immediately play the stream!
try {
videoRef.current.srcObject = mediaStream
videoRef.current.pause()
} catch (e) {
console.warn('Attempted to pause stream while play was still loading', e)
}
send({
type: 'Set context',
data: {
videoElement: videoRef.current,
},
})
setIsLoading(false)
}, [mediaStream])
const handleMouseUp: MouseEventHandler<HTMLDivElement> = (e) => {
// If we've got no stream or connection, don't do anything
if (!isNetworkOkay) return
if (!videoRef.current) return
// If we're in sketch mode, don't send a engine-side select event
if (state.matches('Sketch')) return
if (state.matches({ idle: 'showPlanes' })) return
// If we're mousing up from a camera drag, don't send a select event
if (sceneInfra.camControls.wasDragging === true) return
if (btnName(e.nativeEvent).left) {
// eslint-disable-next-line @typescript-eslint/no-floating-promises
sendSelectEventToEngine(e, videoRef.current)
}
}
return (
<div
className="absolute inset-0 z-0"
id="stream"
data-testid="stream"
onClick={handleMouseUp}
onContextMenu={(e) => e.preventDefault()}
onContextMenuCapture={(e) => e.preventDefault()}
>
<video
ref={videoRef}
muted
autoPlay
controls={false}
onPlay={() => setIsLoading(false)}
className="w-full cursor-pointer h-full"
disablePictureInPicture
id="video-stream"
/>
<ClientSideScene
cameraControls={settings.context.modeling.mouseControls.current}
/>
{(streamState === StreamState.Paused ||
streamState === StreamState.Resuming) && (
<div className="text-center absolute inset-0">
<div
className="flex flex-col items-center justify-center h-screen"
data-testid="paused"
>
<div className="border-primary border p-2 rounded-sm">
<svg
width="8"
height="12"
viewBox="0 0 8 12"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M2 12V0H0V12H2ZM8 12V0H6V12H8Z"
fill="var(--primary)"
/>
</svg>
</div>
<p className="text-base mt-2 text-primary bold">
{streamState === StreamState.Paused && 'Paused'}
{streamState === StreamState.Resuming && 'Resuming'}
</p>
</div>
</div>
)}
{(!isNetworkOkay || isLoading) && (
<div className="text-center absolute inset-0">
<Loading>
{!isNetworkOkay && !isLoading ? (
<span data-testid="loading-stream">Stream disconnected...</span>
) : (
!isLoading && (
<span data-testid="loading-stream">Loading stream...</span>
)
)}
</Loading>
</div>
)}
</div>
)
}

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

@ -1,237 +0,0 @@
import { makeDefaultPlanes, modifyGrid } from 'lang/wasm'
import { MutableRefObject } from 'react'
import { setup, assign } from 'xstate'
import { createActorContext } from '@xstate/react'
import { kclManager, sceneInfra, engineCommandManager } from 'lib/singletons'
import { trap } from 'lib/trap'
export enum EngineStreamState {
Off = 'off',
On = 'on',
Playing = 'playing',
Paused = 'paused',
Resuming = 'resuming',
}
export enum EngineStreamTransition {
SetMediaStream = 'set-context',
Play = 'play',
Resume = 'resume',
Pause = 'pause',
StartOrReconfigureEngine = 'start-or-reconfigure-engine',
}
export interface EngineStreamContext {
pool: string | null
authToken: string | null
mediaStream: MediaStream | null
videoRef: MutableRefObject<HTMLVideoElement | null>
canvasRef: MutableRefObject<HTMLCanvasElement | null>
}
export function getDimensions(streamWidth: number, streamHeight: number) {
const factorOf = 4
const maxResolution = 2160
const ratio = Math.min(
Math.min(maxResolution / streamWidth, maxResolution / streamHeight),
1.0
)
const quadWidth = Math.round((streamWidth * ratio) / factorOf) * factorOf
const quadHeight = Math.round((streamHeight * ratio) / factorOf) * factorOf
return { width: quadWidth, height: quadHeight }
}
const engineStreamMachine = setup({
types: {
context: {} as EngineStreamContext,
input: {} as EngineStreamContext,
},
actions: {
[EngineStreamTransition.Play]({ context }, params: { zoomToFit: boolean }) {
const canvas = context.canvasRef.current
if (!canvas) return false
const video = context.videoRef.current
if (!video) return false
const mediaStream = context.mediaStream
if (!mediaStream) return false
video.style.display = 'block'
canvas.style.display = 'none'
video.srcObject = mediaStream
void sceneInfra.camControls
.restoreCameraPosition()
.then(() => video.play())
.catch((e) => {
console.warn('Video playing was prevented', e, video)
})
.then(() => kclManager.executeCode(params.zoomToFit))
.catch(trap)
},
[EngineStreamTransition.Pause]({ context }) {
const video = context.videoRef.current
if (!video) return
video.pause()
const canvas = context.canvasRef.current
if (!canvas) return
canvas.width = video.videoWidth
canvas.height = video.videoHeight
canvas.style.width = video.videoWidth + 'px'
canvas.style.height = video.videoHeight + 'px'
canvas.style.display = 'block'
const ctx = canvas.getContext('2d')
if (!ctx) return
ctx.drawImage(video, 0, 0, canvas.width, canvas.height)
// Make sure we're on the next frame for no flickering between canvas
// and the video elements.
window.requestAnimationFrame(() => {
video.style.display = 'none'
// Destroy the media stream only. We will re-establish it. We could
// leave everything at pausing, preventing video decoders from running
// but we can do even better by significantly reducing network
// cards also.
context.mediaStream?.getVideoTracks()[0].stop()
video.srcObject = null
sceneInfra.camControls.old = {
camera: sceneInfra.camControls.camera.clone(),
target: sceneInfra.camControls.target.clone(),
}
engineCommandManager.tearDown({ idleMode: true })
})
},
async [EngineStreamTransition.StartOrReconfigureEngine]({
context,
event,
}) {
if (!context.authToken) return
const video = context.videoRef.current
if (!video) return
const { width, height } = getDimensions(
window.innerWidth,
window.innerHeight
)
video.width = width
video.height = height
const settingsNext = {
// override the pool param (?pool=) to request a specific engine instance
// from a particular pool.
pool: context.pool,
...event.settings,
}
engineCommandManager.settings = settingsNext
engineCommandManager.start({
setMediaStream: event.onMediaStream,
setIsStreamReady: (isStreamReady) =>
event.setAppState({ isStreamReady }),
width,
height,
token: context.authToken,
settings: settingsNext,
makeDefaultPlanes: () => {
return makeDefaultPlanes(kclManager.engineCommandManager)
},
modifyGrid: (hidden: boolean) => {
return modifyGrid(kclManager.engineCommandManager, hidden)
},
})
event.modelingMachineActorSend({
type: 'Set context',
data: {
streamDimensions: {
streamWidth: width,
streamHeight: height,
},
},
})
},
async [EngineStreamTransition.Resume]({ context, event }) {
// engineCommandManager.engineConnection?.reattachMediaStream()
},
},
}).createMachine({
context: (initial) => initial.input,
initial: EngineStreamState.Off,
states: {
[EngineStreamState.Off]: {
on: {
[EngineStreamTransition.StartOrReconfigureEngine]: {
target: EngineStreamState.On,
actions: [{ type: EngineStreamTransition.StartOrReconfigureEngine }],
},
},
},
[EngineStreamState.On]: {
on: {
[EngineStreamTransition.SetMediaStream]: {
target: EngineStreamState.On,
actions: [
assign({ mediaStream: ({ context, event }) => event.mediaStream }),
],
},
[EngineStreamTransition.Play]: {
target: EngineStreamState.Playing,
actions: [
{ type: EngineStreamTransition.Play, params: { zoomToFit: true } },
],
},
},
},
[EngineStreamState.Playing]: {
on: {
[EngineStreamTransition.StartOrReconfigureEngine]: {
target: EngineStreamState.Playing,
reenter: true,
actions: [{ type: EngineStreamTransition.StartOrReconfigureEngine }],
},
[EngineStreamTransition.Pause]: {
target: EngineStreamState.Paused,
actions: [{ type: EngineStreamTransition.Pause }],
},
},
},
[EngineStreamState.Paused]: {
on: {
[EngineStreamTransition.StartOrReconfigureEngine]: {
target: EngineStreamState.Resuming,
actions: [{ type: EngineStreamTransition.StartOrReconfigureEngine }],
},
},
},
[EngineStreamState.Resuming]: {
on: {
[EngineStreamTransition.SetMediaStream]: {
target: EngineStreamState.Resuming,
actions: [
assign({ mediaStream: ({ context, event }) => event.mediaStream }),
],
},
[EngineStreamTransition.Play]: {
target: EngineStreamState.Playing,
actions: [
{ type: EngineStreamTransition.Play, params: { zoomToFit: false } },
],
},
},
},
},
})
export default createActorContext(engineStreamMachine)

View File

@ -2,7 +2,7 @@ import { executeAst, lintAst } from 'lang/langHelpers'
import { Selections } from 'lib/selections'
import { KCLError, kclErrorsToDiagnostics } from './errors'
import { uuidv4 } from 'lib/utils'
import { EngineCommandManager, CommandLogType } from './std/engineConnection'
import { EngineCommandManager } from './std/engineConnection'
import { err } from 'lib/trap'
import { EXECUTE_AST_INTERRUPT_ERROR_MESSAGE } from 'lib/constants'
@ -290,9 +290,15 @@ export class KclManager {
)
}
await sceneInfra.camControls.centerModelRelativeToPanes({
zoomToFit: true,
zoomObjectId,
await this.engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'zoom_to_fit',
object_ids: zoomObjectId ? [zoomObjectId] : [], // leave empty to zoom to all objects
padding: 0.1, // padding around the objects
animated: false, // don't animate the zoom for now
},
})
}
}
@ -322,7 +328,7 @@ export class KclManager {
this.ast = { ...ast }
this._executeCallback()
this.engineCommandManager.addCommandLog({
type: CommandLogType.ExecutionDone,
type: 'execution-done',
data: null,
})

View File

@ -6,14 +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
@ -90,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,
@ -101,7 +107,7 @@ export default class CodeManager {
},
annotations: [
codeManagerUpdateEvent,
Transaction.addToHistory.of(true),
Transaction.addToHistory.of(!clearHistory),
],
})
}
@ -110,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)
}
}
@ -167,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

@ -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,22 +77,30 @@ 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 init = expectedExtrudeNode.init
if (init.type !== 'CallExpression' && init.type !== 'PipeExpression') {
return new Error(
'Expected extrude expression is not a CallExpression or PipeExpression'
)
const expectedExtrudeNode = expectedExtrudeNodeResult.node
// check whether extrude is in the sketch pipe
const extrudeInSketchPipe = expectedExtrudeNode.type === 'CallExpression'
if (extrudeInSketchPipe) {
return expectedExtrudeNode
}
return init
if (!extrudeInSketchPipe) {
const init = expectedExtrudeNode.init
if (init.type !== 'CallExpression' && init.type !== 'PipeExpression') {
return new Error(
'Expected extrude expression is not a CallExpression or PipeExpression'
)
}
return init
}
return new Error('Expected extrude expression not found')
}
// ast
@ -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

@ -146,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 = []
@ -167,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)
@ -317,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,19 +946,33 @@ export function hasSketchPipeBeenExtruded(selection: Selection, ast: Program) {
const varDec = _varDec.node
if (varDec.type !== 'VariableDeclarator') return false
let extruded = false
traverse(ast as any, {
// option 1: extrude or revolve is called in the sketch pipe
traverse(pipeExpression, {
enter(node) {
if (
node.type === 'CallExpression' &&
node.callee.type === 'Identifier' &&
(node.callee.name === 'extrude' || node.callee.name === 'revolve') &&
node.arguments?.[1]?.type === 'Identifier' &&
node.arguments[1].name === varDec.id.name
(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 (
node.type === 'CallExpression' &&
node.callee.type === 'Identifier' &&
(node.callee.name === 'extrude' || node.callee.name === 'revolve') &&
node.arguments?.[1]?.type === 'Identifier' &&
node.arguments[1].name === varDec.id.name
) {
extruded = true
}
},
})
}
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 =

View File

@ -406,14 +406,13 @@ class EngineConnection extends EventTarget {
default:
if (this.isConnecting()) break
// Means we never could do an initial connection. Reconnect everything.
if (!this.pingPongSpan.ping)
this.connect({ reconnect: false }).catch(reportRejection)
if (!this.pingPongSpan.ping) this.connect().catch(reportRejection)
break
}
}, pingIntervalMs)
// eslint-disable-next-line @typescript-eslint/no-floating-promises
this.connect({ reconnect: false })
this.connect()
}
// SHOULD ONLY BE USED FOR VITESTS
@ -524,9 +523,7 @@ class EngineConnection extends EventTarget {
this.idleMode = opts?.idleMode ?? false
clearInterval(this.pingIntervalId)
this.disconnectAll()
if (this.idleMode) {
if (opts?.idleMode) {
this.state = {
type: EngineConnectionStateType.Disconnecting,
value: {
@ -545,6 +542,8 @@ class EngineConnection extends EventTarget {
type: DisconnectingType.Quit,
},
}
this.disconnectAll()
}
/**
@ -554,7 +553,7 @@ class EngineConnection extends EventTarget {
* This will attempt the full handshake, and retry if the connection
* did not establish.
*/
connect(args: { reconnect: boolean }): Promise<void> {
connect(reconnecting?: boolean): Promise<void> {
return new Promise((resolve) => {
if (this.isConnecting() || this.isReady()) {
return
@ -1166,7 +1165,7 @@ class EngineConnection extends EventTarget {
this.websocket.addEventListener('message', this.onWebSocketMessage)
}
if (args.reconnect) {
if (reconnecting) {
createWebSocketConnection()
} else {
this.onNetworkStatusReady = () => {
@ -1179,32 +1178,6 @@ class EngineConnection extends EventTarget {
}
})
}
async reattachMediaStream() {
return this.pc
?.createOffer({ iceRestart: true })
.then((offer: RTCSessionDescriptionInit) => {
this.state = {
type: EngineConnectionStateType.Connecting,
value: {
type: ConnectingType.SetLocalDescription,
},
}
return this.pc?.setLocalDescription(offer).then(() => {
this.send({
type: 'sdp_offer',
offer: offer as Models['RtcSessionDescription_type'],
})
this.state = {
type: EngineConnectionStateType.Connecting,
value: {
type: ConnectingType.OfferedSdp,
},
}
})
})
}
// Do not change this back to an object or any, we should only be sending the
// WebSocketRequest type!
unreliableSend(message: Models['WebSocketRequest_type']) {
@ -1256,17 +1229,8 @@ class EngineConnection extends EventTarget {
this.websocket?.readyState === 3
if (closedPc && closedUDC && closedWS) {
if (!this.idleMode) {
// Do not notify the rest of the program that we have cut off anything.
this.state = { type: EngineConnectionStateType.Disconnected }
} else {
this.state = {
type: EngineConnectionStateType.Disconnecting,
value: {
type: DisconnectingType.Pause,
},
}
}
// Do not notify the rest of the program that we have cut off anything.
this.state = { type: EngineConnectionStateType.Disconnected }
}
}
}
@ -1291,40 +1255,27 @@ export interface Subscription<T extends ModelTypes> {
) => void
}
export enum CommandLogType {
SendModeling = 'send-modeling',
SendScene = 'send-scene',
ReceiveReliable = 'receive-reliable',
ExecutionDone = 'execution-done',
ExportDone = 'export-done',
SetDefaultSystemProperties = 'set_default_system_properties',
}
export type CommandLog =
| {
type: CommandLogType.SendModeling
type: 'send-modeling'
data: EngineCommand
}
| {
type: CommandLogType.SendScene
type: 'send-scene'
data: EngineCommand
}
| {
type: CommandLogType.ReceiveReliable
type: 'receive-reliable'
data: OkWebSocketResponseData
id: string
cmd_type?: string
}
| {
type: CommandLogType.ExecutionDone
type: 'execution-done'
data: null
}
| {
type: CommandLogType.ExportDone
data: null
}
| {
type: CommandLogType.SetDefaultSystemProperties
type: 'export-done'
data: null
}
@ -1680,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
@ -1735,7 +1690,7 @@ export class EngineCommandManager extends EventTarget {
message.request_id
) {
this.addCommandLog({
type: CommandLogType.ReceiveReliable,
type: 'receive-reliable',
data: message.resp,
id: message?.request_id || '',
cmd_type: pending?.command?.cmd?.type,
@ -1769,7 +1724,7 @@ export class EngineCommandManager extends EventTarget {
if (!command) return
if (command.type === 'modeling_cmd_req')
this.addCommandLog({
type: CommandLogType.ReceiveReliable,
type: 'receive-reliable',
data: {
type: 'modeling',
data: {
@ -1811,7 +1766,7 @@ export class EngineCommandManager extends EventTarget {
)
// eslint-disable-next-line @typescript-eslint/no-floating-promises
this.engineConnection?.connect({ reconnect: false })
this.engineConnection?.connect()
}
this.engineConnection.addEventListener(
EngineConnectionEvents.ConnectionStarted,
@ -1873,7 +1828,6 @@ export class EngineCommandManager extends EventTarget {
)
this.engineConnection?.tearDown(opts)
this.engineConnection = undefined
// Our window.tearDown assignment causes this case to happen which is
// only really for tests.
@ -1881,8 +1835,6 @@ export class EngineCommandManager extends EventTarget {
} else if (this.engineCommandManager?.engineConnection) {
// @ts-ignore
this.engineCommandManager?.engineConnection?.tearDown(opts)
// @ts-ignore
this.engineCommandManager.engineConnection = null
}
}
async startNewSession() {
@ -1981,7 +1933,7 @@ export class EngineCommandManager extends EventTarget {
) {
// highlight_set_entity, mouse_move and camera_drag_move are sent over the unreliable channel and are too noisy
this.addCommandLog({
type: CommandLogType.SendScene,
type: 'send-scene',
data: command,
})
}
@ -2040,7 +1992,7 @@ export class EngineCommandManager extends EventTarget {
toastId,
resolve: (passThrough) => {
this.addCommandLog({
type: CommandLogType.ExportDone,
type: 'export-done',
data: null,
})
resolve(passThrough)

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
@ -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
@ -145,6 +147,12 @@ export const fileLoader: LoaderFunction = async (
? await getProjectInfo(projectPath)
: null
console.log('maybeProjectInfo', {
maybeProjectInfo,
defaultProjectData,
projectPathData,
})
const projectData: IndexLoaderData = {
code,
project: maybeProjectInfo ?? defaultProjectData,

View File

@ -118,8 +118,6 @@ export class Setting<T = unknown> {
}
}
const MS_IN_MINUTE = 1000 * 60
export function createSettings() {
return {
/** Settings that affect the behavior of the entire app,
@ -183,58 +181,13 @@ export function createSettings() {
/**
* Stream resource saving behavior toggle
*/
streamIdleMode: new Setting<number | undefined>({
defaultValue: undefined,
streamIdleMode: new Setting<boolean>({
defaultValue: false,
description: 'Toggle stream idling, saving bandwidth and battery',
validate: (v) =>
v === undefined ||
(typeof v === 'number' &&
v >= 1 * MS_IN_MINUTE &&
v <= 60 * MS_IN_MINUTE),
Component: ({ value, updateValue }) => (
<div className="flex item-center gap-4 px-2 m-0 py-0">
<div className="flex flex-col">
<input
type="checkbox"
checked={value !== undefined}
onChange={(e) =>
updateValue(
!e.currentTarget.checked ? undefined : 5 * MS_IN_MINUTE
)
}
className="block w-4 h-4"
/>
<div></div>
</div>
<div className="flex flex-col grow">
<input
type="range"
onChange={(e) =>
updateValue(Number(e.currentTarget.value) * MS_IN_MINUTE)
}
disabled={value === undefined}
value={
value !== null && value !== undefined
? value / MS_IN_MINUTE
: 5
}
min={1}
max={60}
step={1}
className="block flex-1"
/>
{value !== undefined && value !== null && (
<div>
{value / MS_IN_MINUTE === 60
? '1 hour'
: value / MS_IN_MINUTE === 1
? '1 minute'
: value / MS_IN_MINUTE + ' minutes'}
</div>
)}
</div>
</div>
),
validate: (v) => typeof v === 'boolean',
commandConfig: {
inputType: 'boolean',
},
}),
onboardingStatus: new Setting<string>({
defaultValue: '',

View File

@ -24,10 +24,6 @@ import { ProjectConfiguration } from 'wasm-lib/kcl/bindings/ProjectConfiguration
import { BROWSER_PROJECT_NAME } from 'lib/constants'
import { DeepPartial } from 'lib/types'
type OmitNull<T> = T extends null ? undefined : T
const toUndefinedIfNull = (a: any): OmitNull<any> =>
a === null ? undefined : a
/**
* Convert from a rust settings struct into the JS settings struct.
* We do this because the JS settings type has all the fancy shit
@ -44,9 +40,7 @@ export function configurationToSettingsPayload(
: undefined,
onboardingStatus: configuration?.settings?.app?.onboarding_status,
dismissWebBanner: configuration?.settings?.app?.dismiss_web_banner,
streamIdleMode: toUndefinedIfNull(
configuration?.settings?.app?.stream_idle_mode
),
streamIdleMode: configuration?.settings?.app?.stream_idle_mode,
projectDirectory: configuration?.settings?.project?.directory,
enableSSAO: configuration?.settings?.modeling?.enable_ssao,
},
@ -85,9 +79,7 @@ export function projectConfigurationToSettingsPayload(
: undefined,
onboardingStatus: configuration?.settings?.app?.onboarding_status,
dismissWebBanner: configuration?.settings?.app?.dismiss_web_banner,
streamIdleMode: toUndefinedIfNull(
configuration?.settings?.app?.stream_idle_mode
),
streamIdleMode: configuration?.settings?.app?.stream_idle_mode,
enableSSAO: configuration?.settings?.modeling?.enable_ssao,
},
modeling: {

View File

@ -10,14 +10,8 @@ export const codeManager = new CodeManager()
export const engineCommandManager = new EngineCommandManager()
declare global {
interface Window {
tearDown: typeof engineCommandManager.tearDown
sceneInfra: typeof sceneInfra
}
}
// Accessible for tests
// Accessible for tests mostly
// @ts-ignore
window.tearDown = engineCommandManager.tearDown
// This needs to be after codeManager is created.
@ -27,9 +21,7 @@ engineCommandManager.kclManager = kclManager
engineCommandManager.getAstCb = () => kclManager.ast
export const sceneInfra = new SceneInfra(engineCommandManager)
// Accessible for tests
window.sceneInfra = sceneInfra
engineCommandManager.camControlsCameraChange = sceneInfra.onCameraChange
export const sceneEntitiesManager = new SceneEntities(engineCommandManager)

View File

@ -1,3 +0,0 @@
// 0.25s is the average visual reaction time for humans so we'll go a bit less
// so those exception people don't see.
export const REASONABLE_TIME_TO_REFRESH_STREAM_SIZE = 100

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

@ -159,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'
@ -184,6 +193,7 @@ export type SketchTool =
| 'line'
| 'tangentialArc'
| 'rectangle'
| 'center rectangle'
| 'circle'
| 'none'
@ -197,7 +207,7 @@ export type ModelingMachineEvent =
| { type: 'Sketch On Face' }
| {
type: 'Select default plane'
data: DefaultPlane | ExtrudeFacePlane
data: DefaultPlane | ExtrudeFacePlane | OffsetPlane
}
| {
type: 'Set selection'
@ -238,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]
@ -278,6 +292,7 @@ export type ModelingMachineEvent =
}
}
| { type: 'Finish rectangle' }
| { type: 'Finish center rectangle' }
| { type: 'Finish circle' }
| { type: 'Artifact graph populated' }
| { type: 'Artifact graph emptied' }
@ -506,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',
@ -806,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()
@ -859,6 +897,21 @@ export const modelingMachine = setup({
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,
sketchDetails.origin,
event.data
)
},
'set up draft circle': ({ context: { sketchDetails }, event }) => {
if (event.type !== 'Add circle origin') return
if (!sketchDetails || !event.data) return
@ -1350,7 +1403,7 @@ export const modelingMachine = setup({
}
),
'animate-to-face': fromPromise(
async (_: { input?: ExtrudeFacePlane | DefaultPlane }) => {
async (_: { input?: ExtrudeFacePlane | DefaultPlane | OffsetPlane }) => {
return {} as
| undefined
| {
@ -1822,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',
},
@ -2015,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

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

@ -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,29 +1,20 @@
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
TWENTY_TWENTY=overwrite {{cita}} -p kcl-lib -- tests::{{test_name}}::kcl_test_execute

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"

View File

@ -1,22 +0,0 @@
//! This crate contains macros for parsing KCL at Rust compile-time.
use databake::*;
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, LitStr};
/// Parses KCL into its AST at compile-time.
/// This macro takes exactly one argument: A string literal containing KCL.
/// # Examples
/// ```
/// extern crate alloc;
/// use kcl_compile_macro::parse_kcl;
/// let ast: kcl_lib::ast::types::Program = parse_kcl!("const y = 4");
/// ```
#[proc_macro]
pub fn parse(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as LitStr);
let kcl_src = input.value();
let ast = kcl_lib::parser::top_level_parse(&kcl_src).unwrap();
let ast_struct = ast.bake(&Default::default());
quote!(#ast_struct).into()
}

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