Compare commits

...

53 Commits

Author SHA1 Message Date
76f1d09abb Fix broken golden standard tests caused by changes to kcl-samples (#5065)
* Fix our golden standard tests (broken by new assemblies kcl-samples)

* Finally use the right combination of env vars

* Fix the manifest

* Continue to fix multiple file kcl-samples

* Fix loading in desktop app

* Type narrow for tsc

* fmt

---------

Co-authored-by: Frank Noirot <frank@kittycad.io>
2025-01-15 18:32:34 -05:00
702e322f90 ci: Add yarn test of packages/codemirror-lang-kcl (#5035)
* ci: Add yarn test of packages/codemirror-lang-kcl

* Fix CI error running tests

* Fix postcss config error
2025-01-14 09:30:08 -05:00
e82830754d turns on helix from edge (#5036)
* updates for new lib

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

* autocomplete

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

* bump version

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

* bump all the things

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

* new samples

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

* docs

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

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2025-01-13 23:34:43 +00:00
7806377a5a Disable auto-updater on non-versioned builds (#5042) 2025-01-13 17:40:51 -05:00
859afa2fd8 Upgrade all wasm-bindgen dependencies together (#5037) 2025-01-13 13:24:23 -08:00
0a5f3093fc Fix Cargo.lock to not have changes (#5034) 2025-01-13 15:38:24 -05:00
b65f7939f6 Fix artifact types to be more accurate (#5022) 2025-01-13 15:02:55 -05:00
c35dea5e07 Bump syn from 2.0.95 to 2.0.96 in /src/wasm-lib (#5015)
Bumps [syn](https://github.com/dtolnay/syn) from 2.0.95 to 2.0.96.
- [Release notes](https://github.com/dtolnay/syn/releases)
- [Commits](https://github.com/dtolnay/syn/compare/2.0.95...2.0.96)

---
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>
2025-01-13 10:04:30 -08:00
fc66d4745f Bump handlebars from 6.2.0 to 6.3.0 in /src/wasm-lib (#5012)
Bumps [handlebars](https://github.com/sunng87/handlebars-rust) from 6.2.0 to 6.3.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.2.0...v6.3.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>
2025-01-13 13:03:34 -05:00
b313d26c2a Bump @lezer/generator from 1.7.1 to 1.7.2 (#5018)
Bumps [@lezer/generator](https://github.com/lezer-parser/generator) from 1.7.1 to 1.7.2.
- [Changelog](https://github.com/lezer-parser/generator/blob/main/CHANGELOG.md)
- [Commits](https://github.com/lezer-parser/generator/compare/1.7.1...1.7.2)

---
updated-dependencies:
- dependency-name: "@lezer/generator"
  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>
2025-01-13 11:48:13 -05:00
00b94ead62 Add packages to Dependabot updates (#5024) 2025-01-13 11:29:00 -05:00
0531ea1ce9 Change Dependabot PRs to always be made on Mondays (#5025) 2025-01-13 15:30:33 +00:00
5f9a4887c1 Developer workflow: added auto generated workspace file from vitest extension in vscode (#4997)
* chore: added auto generated workspace file from vitest extension in vscode

* fix: auto fmt fixes
2025-01-13 09:57:12 -05:00
da7dfa16d8 Fix lost lints and add new ones (#5011)
* Add eslint-plugin-jsx-a11y dependency

* Add jsx-a11y lint

* Add eslint-plugin-react-hooks dependency

* Add react hooks lints

* Ignore new react hooks lint in tests

* Add eslint-plugin-testing-library dependency

* Add testing-library lint

* Fix yarn lint to use all files recursively
2025-01-13 09:30:14 -05:00
363ae10658 Upgrade typescript-eslint from 5.62.0 to 8.19.1 and remove eslint-config-react-app (#5006) 2025-01-11 09:59:09 -05:00
ac4a6c84cf Point-and-click Sweep (first PR) (#4989)
* Refactor 'Delete selection' as actor
Will fix #4662

* WIP logging

* WIP: working Solid3dGetExtrusionFaceInfo for loft

* Working wall deletion of loft

* Add offset plane deletion

* Add feature tree deletion of shell

* Clean up

* Revert "Clean up"

This reverts commit 214763cc2b.

* Clean up rust changes, taking the sketch with the most paths

* Working cap selection and deletion

* Clean up

* Add test for loft and offset plane deletion via selection

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

* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-macos-8-cores)

* Set reenter: false as it was originally

* Passing test

* Add shell deletion via feature tree test

* Revert the migration to promise actor

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

* Trigger CI

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

* Trigger CI

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

* Trigger CI

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

* Trigger CI

* Use cmd.id as solid_id after latest engine merge

* Add feature tree deletion of offset plane and fix lint

* Add feature tree deletion of loft

* Clean up

* Better comment

* Lint fix

* Remove sketch sorting

* WIP: sweep point-and-click

* Working sweep

* Add test

* Make sweep a development command

* Fix tsc error

* Clean up for review

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-01-11 08:20:49 -05:00
c6fad2e2dc Add new lint to disallow use of confusing isNaN (#4999) 2025-01-11 05:28:12 +00:00
013cb10961 Fix so that all artifact commands are returned regardless of caching (#5005)
* Fix so that all artifact commands are returned regardless of caching

* Add some more docs and fix up old ones
2025-01-10 22:33:05 -05:00
6261083cb1 Make the test executor a bit more patient (#5004) 2025-01-10 20:05:27 -05:00
2b0ba37ed0 Use Chromium instead of Chrome for Playwright Electron (#5001)
* Use Chromium instead of Chrome for Playwright Electron

* Remove channel
2025-01-10 13:37:26 -05:00
96174f3cf6 Increase playwright retries to 5 (#5000) 2025-01-10 13:34:27 -05:00
aed62ff912 Fix flaky playwright test 'Shell point-and-click sketch on face' (#5002)
Fixes #4998
2025-01-10 13:32:31 -05:00
9334d64608 Allow under-development commands in Nightly builds (#4995)
* Allow under-development commands in Nightly builds
Fixes #4994

* Fix warning

* Add back status: development to Revolve
2025-01-10 16:24:07 +00:00
4fa7d2d8c8 Feature: new axis and edge selection workflow for point and click revolve (#4939)
* feat: implemented axis or edge selection workflow in the commandbar

* fix: removing comment

* fix: removing console logs from testing

* fix: fixing lint and tsc errors

* fix: changed copy

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

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

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Frank Noirot <frank@zoo.dev>
2025-01-10 08:52:04 -06:00
3e615dfdbc Update Katie's name reference and link in onboarding (#4967) 2025-01-09 22:07:40 -05:00
c9860af29f Fix Shell point-and-click picking the wrong face with piped extrudes (#4981)
* [BUG] Shell point and click references the wrong feature
Fixes #4961

* Add test for sketch on face based on extrudes in pipe

* Add no extrude in pipe case

* Lint

* Add scene.waitForExecutionDone()

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

* Trigger CI

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

* Trigger CI

* Update src/lang/modifyAst/addShell.ts

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

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Frank Noirot <frank@zoo.dev>
2025-01-10 01:20:07 +00:00
23a42f0195 Bump @kittycad/lib to v2.0.13 (#4988) 2025-01-09 16:02:05 -05:00
a77fa639f3 Point-and-click deletion of lofts, shells, and offset planes (#4898)
* Refactor 'Delete selection' as actor
Will fix #4662

* WIP logging

* WIP: working Solid3dGetExtrusionFaceInfo for loft

* Working wall deletion of loft

* Add offset plane deletion

* Add feature tree deletion of shell

* Clean up

* Revert "Clean up"

This reverts commit 214763cc2b.

* Clean up rust changes, taking the sketch with the most paths

* Working cap selection and deletion

* Clean up

* Add test for loft and offset plane deletion via selection

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

* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-macos-8-cores)

* Set reenter: false as it was originally

* Passing test

* Add shell deletion via feature tree test

* Revert the migration to promise actor

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

* Trigger CI

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

* Trigger CI

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

* Trigger CI

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

* Trigger CI

* Use cmd.id as solid_id after latest engine merge

* Add feature tree deletion of offset plane and fix lint

* Add feature tree deletion of loft

* Clean up

* Better comment

* Lint fix

* Remove sketch sorting

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-01-09 15:36:50 -05:00
0a5ad7c95b Show deprecated indicator in CodeMirror autocomplete (#4983) 2025-01-09 09:15:00 -05:00
4a654523d2 Prevent toSync from clobbering stack traces (#4980)
* Prevent toSync from clobbering stack traces

* Capture error on the outside of the toSync catch

* fmt

* Actually fix it 🤦
2025-01-09 03:40:42 +00:00
73a7e2bfd6 Return modeling commands from KCL execution (#4912)
* Add Rust side artifacts for startSketchOn face or plane

* Add Rust-generated artifacts to ExecOutcome

* Add output of artifact commands

* Add new output files

* Wire the artifact commands to the artifact graph creation

* Fix to use real PartialEq implemented in modeling commands

* Fix modeling commands with zero fields to work

* Fix missing artifactCommands field in errors

* Change artifact graph to be built from artifact commands

* Wire up ExecState artifacts, but not using them yet

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

* Remove unneeded local var

* Fix test to fail with a helpful error message when command isn't found

* Rename and deprecate orderedCommands

* Update comment about borrowing

* Move ArtifactCommand tracking to the EngineManager trait

* Update artifact commands since tracking in the engine

* Upgrade kittycad-modeling-cmds from 0.2.85 to 0.2.86

* Remove unneeded JsonSchema derive to speed up build

* Fix to not fail on floating point differences in CI

* Update artifact commands output since truncating floating point numbers

* Fix to ensure artifact commands get cleared after a clear scene

* Update artifact commands snapshot after clearing them on clear scene

* Remove all remnants of OrderedCommands

* Update output for new simulation tests

---------

Co-authored-by: Kurt Hutten <k.hutten@protonmail.ch>
2025-01-09 01:02:30 +00:00
eb0850fea9 Make codespell smooth when run locally (#4978)
* Skip the out/ directory produced by yarn tron:package

* Skip all dist/ dirs

Produced when building rollup packages like codemirror-lang-kcl.

* Skip typescript build info

* Fix typo instead of excluding file

---------

Co-authored-by: Matt Mundell <matt@mundell.me>
2025-01-08 10:36:37 -06:00
029f76f273 Nadro/4857/wasm panic catching errors (#4901)
* chore: skeleton code to initialize and detect the global WASM panic

* chore: implementing a reimport method to fix the wasm instance being bricked

* fix: cleaning up tsc/lint

* fix: renaming file to be more accurate

* fix: added toast message

* fix: types...

* fix: typed the functions with arg spreads
2025-01-08 15:58:41 +00:00
max
28b5f7080c Refactor Fillet AST Mod to Async Actor (#4803) 2025-01-08 16:05:24 +01:00
5b1dcfecd6 Open updater toast changelog links externally (#4970)
* fix: Hook into markdown-generated anchors to avoid e.g breaking the desktop app

* add comment

* Disable eslint on copied line from ts-stack

---------

Co-authored-by: marc2332 <mespinsanz@gmail.com>
2025-01-08 09:15:18 -05:00
f89d191425 add a test for foreign characters in project name (#4976)
Signed-off-by: Jess Frazelle <github@jessfraz.com>
2025-01-08 05:34:08 -05:00
2f4e4b62a8 Don't wait for !isExecuting to play the stream (#4971)
* Add failing test for current behavior

* Change stream behavior so that stream is played regardless of `isExecuting`

* Change expected pixel color

* Widen possible pixel color diff because local and CI produce slightly different colors
2025-01-08 04:34:57 -05:00
5ebd5c8dbb Enhance helixes (#4973)
* 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>

* allow a helix to go into a sweep

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

* fix clippy

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

* updates

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

* udpates

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

* updates

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

* snapshots

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

* docs

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

* docs

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

* fix

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>

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

* updates

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

* updates

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

* docs

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

* updates

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

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

* em,pty

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

* 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>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-01-08 03:10:53 +00:00
a9ceaf2678 fix: make error for missing a closing bracket clearer (#4974)
* fix: make error for missing a closing bracket clearer

* Fix test for error message

---------

Co-authored-by: Tom Pridham <pridham.tom@gmail.com>
2025-01-08 01:55:07 +00:00
c8afd3399b Dead code clean up (smol PR) (#4653)
* general clean up

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

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

* trigger CI

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-01-08 01:46:05 +00:00
5dda4828c6 Bump react-modal from 3.16.1 to 3.16.3 (#4924)
Bumps [react-modal](https://github.com/reactjs/react-modal) from 3.16.1 to 3.16.3.
- [Release notes](https://github.com/reactjs/react-modal/releases)
- [Changelog](https://github.com/reactjs/react-modal/blob/master/CHANGELOG.md)
- [Commits](https://github.com/reactjs/react-modal/compare/v3.16.1...v3.16.3)

---
updated-dependencies:
- dependency-name: react-modal
  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>
2025-01-07 15:24:24 -08:00
72acab752c Bump async-trait from 0.1.83 to 0.1.85 in /src/wasm-lib (#4969)
Bumps [async-trait](https://github.com/dtolnay/async-trait) from 0.1.83 to 0.1.85.
- [Release notes](https://github.com/dtolnay/async-trait/releases)
- [Commits](https://github.com/dtolnay/async-trait/compare/0.1.83...0.1.85)

---
updated-dependencies:
- dependency-name: async-trait
  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>
2025-01-07 12:54:16 -08:00
81df38ad1c Bump happy-dom from 15.11.7 to 16.3.0 (#4925)
* Bump happy-dom from 15.11.7 to 16.3.0

Bumps [happy-dom](https://github.com/capricorn86/happy-dom) from 15.11.7 to 16.3.0.
- [Release notes](https://github.com/capricorn86/happy-dom/releases)
- [Commits](https://github.com/capricorn86/happy-dom/compare/v15.11.7...v16.3.0)

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

Signed-off-by: dependabot[bot] <support@github.com>

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

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-01-07 20:51:15 +00:00
0576a2bef1 Bump react-hotkeys-hook from 4.5.1 to 4.6.1 (#4953)
Bumps [react-hotkeys-hook](https://github.com/JohannesKlauss/react-keymap-hook) from 4.5.1 to 4.6.1.
- [Release notes](https://github.com/JohannesKlauss/react-keymap-hook/releases)
- [Changelog](https://github.com/JohannesKlauss/react-hotkeys-hook/blob/main/CHANGELOG.md)
- [Commits](https://github.com/JohannesKlauss/react-keymap-hook/compare/v4.5.1...v4.6.1)

---
updated-dependencies:
- dependency-name: react-hotkeys-hook
  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>
2025-01-07 20:46:57 +00:00
4b2f6b4647 refactor: Remove unused UpdaterModal component (#4791) 2025-01-07 15:28:33 -05:00
69edaa4183 implementing array pop (#4806)
* implementing naive array pop

* multi-profile follow up. (#4802)

* multi-profile work

* fix enter sketch on cap

* fix coderef problem for walls and caps

* allow sketch mode entry from circle

* clean up

* update snapshot

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

* trigger CI

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

* add test

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

* fix how expression index is corrected, to make compatible with offset planes

* another test

* tweak test

* more test tweaks

* break up test to fix it hopfully

* fix onboarding test

* remove bad comment

---------

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

* get ready to bump (kcl-lib and friends) world (#4794)

get ready to bump world

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

* Revert multi-profile (#4812)

* Revert "multi-profile follow up. (#4802)"

This reverts commit 2b2ed470c1.

* Revert "multi profile (#4532)"

This reverts commit 04e586d07b.

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

* Re-run CI after snapshots

* Re-run CI after snapshots

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

* Re-run CI after snapshots

* Add `fixme` to onboarding test

---------

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

* Add tracking of operations for the feature tree (#4746)

* Add operations tracking for the timeline

* Change to only track certain stdlib functions as operations

* Update gen files

* Add operations to simulation snapshot tests

* Add tracking of positional function calls

* Fix generated field names to be camel case in TS

* Fix generated TS field names to match and better docs

* Fix order of ops with patternTransform

* Fix sweep to be included

* Add new expected test outputs

* Add tracking for startSketchOn

* Update ops output to include startSketchOn

* Fix serde field name

* Fix output field name

* Add tracking of operations that fail

* Add snapshots of operations even when there's a KCL execution error

* Add ops output for error executions

* Add operations output to executor error

* Update op source ranges

* Remove tracking of circle() and polygon() since they're not needed

* Update output without circle and polygon

* Fix to track patternCircular3d and patternLinear3d

* Remove tracking for mirror2d

* Update ops output

* Fix to track the correct source range of function definitions

---------

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

* Change KCL completion to use new object/record syntax (#4815)

* Reserve syntax for units of measure (#4783)

* Allow underscores but only for un-referenced names

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

* Support numeric suffixes for UoM types

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

* UoM type arguments

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

* warnings -> non-fatal errors

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

* type ascription

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

---------

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

* Whole module imports (#4767)

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

* Support completions from import statements (#4768)

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

* Implements boolean logical and/or in kcl  (#4678)

* redoing bool logic impl on latest main

* adding snapshot tests (removing .new)

* removing accidental change smh:(

* accepting client side scene snapshot

* accepting png snapshot and triggering ci

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

* accepting png again?

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

* accepting grid visibility snapshot

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

* accepting png snapshot

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

* accepting png snapshot

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

* accepting png snapshot

* rerunning simtest creation to get ops.snap files

---------

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

* Annotations syntax and per-file default units preparatory work (#4822)

* Parse annotations

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

* Propagate settings from annotations to exec_state

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

---------

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

* KCL: Unlabeled first param defaults to % (#4817)

Part of #4600

KCL functions can declare one special argument that doesn't require a label on its parameter when called.

This PR will default that arg to % (the current pipeline) if not given.

* Deprecate startSketchAt() stdlib function (#4819)

* Deprecate startSketchAt() stdlib function

* Remove uses of startSketchAt() from the doc tests

* Update docs

* Add dry-run validation for Loft (#4820)

* WIP: mess with shell selection validation
Will eventually fix #4711

* Update from main

* WIP: not working yet

* Working loft dry run validator

* Clean up shell (still not working)

* Bump kittycad-modeling-cmds

* Clean up

* Add logging

* Add proper object_id and face_id mapping, still not working for shell

* Fix faceId

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

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

* Trigger CI

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

* Add dry-run validation to Loft
Checks one box for #4711

* Add extra check for non solid2ds

---------

Co-authored-by: Adam Chalmers <adam.chalmers@zoo.dev>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>

* Double click label on sketch to dimension  (#4816)

* feat: double click segment label to dimension length, POC, need to clean up code!

* fix: cleaning up the PR for review

* fix: cleaning for the PR. Adding more comments and moving some logic

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

* fix: mergining main, auto linter and tsc fixes. Need to make some more

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

* fix: tsc errors are resolved

* chore: added test for constraint

* fix: fixing overlay bug since length labels can now be clicked.

---------

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

* KCL: Show beautiful Miette errors when a KCL example test fails (#4829)

* KCL: Show beautiful Miette errors when a KCL example test fails

Background: KCL example tests are generated from the stdlib KCL examples in the `#[stdlib]` macro in derive-docs.

Problem: When these tests fail, they output a really unhelpful error message like Kcl(Semantic(KclErrorDetails { source_ranges: [156, 160, 0], message: "Expected a sketch but found array" } )).

Solution: Use miette. Now the errors highlight the KCL code that failed and show exactly what went wrong, on which line, presenting nice diagnostics that look like cargo/rustc output.

* Update helix snapshots

* Move all tests over to electron (#4484)

* Move all tests over to electron

* Pass the correct param to playwright-electron.sh

* Add shebang to script and add macos-14-large as a target

* Get sketch-tests.spec.ts passing in electron

* Try out 4 workers

* Got testing-segment-overlays passing

* Pass testing-selections.spec.ts

* Go back to fix up sketch-tests test

* Pass various.spec.ts, by far the hardest one

* Pass can-sketch-on-all-planes... with ease

* Pass command bar tests

* fmt

* Completely fix code mirror text navigating for tests

* Pass debug pane tests

* Pass desktop export tests

* Pass editor tests

* Pass file tree tests

* Pass onboarding tests

* Corrected a fixme in file-tree.spec!

* Painfully fix hardcoded coordinates in point-click.spec

* Pass machine.spec tests

* Pass projects, fought hard with filechooser

* Pass regresion-tests.spec tests

* Pass network and connection tests

* Pass camera-movement.spec tests

* Extreme time eaten by gizmo test fixes. All passing now.

* Merge main (tests changed x_x) and pass all constraints.spec tests (pain)

* Pass another painful spec suite: testing-settings

* Pass perspective-toggle, interesting note

* Pass samples loading tests

* Pass app header tests

* Pass text-to-cad tests

* Pass segment-overlays (minor ache) and ability to switch to web if needed :)

* Fix a ton of syntax changes and deflake 2 more tests (pain)

* Correct all tsc errors

* Remove to-electron script

* Add an f-ton of shit because playwright doesnt want S P R E A D

* Try CI again

* Stop snapshots of exports (already test in e2e)

* Fix flake in double click editor

* Hopefully help CI flake

* Fixmes, fixmes everywhere

* One more fixme to settings

* Skip another code pane flake

* Port jess's projects.spec tests

* fixup

* Reuse electron window; difficult task

* Rebased and refixed

* Remove duplicate cases

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

* Reduce the workers to something CI can handle

* Lower it further, we need to think about the others

* Update package.json

Co-authored-by: Pierre Jacquier <pierrejacquier39@gmail.com>

* Update package.json

Co-authored-by: Pierre Jacquier <pierrejacquier39@gmail.com>

* Fix the last tests and tsc errors

* Timeout to 120 and windows-2022-16core

* Fix windows runner detection, enable concurrency temporarily

* Hopefully this time fix windows runner detection

* Comment out Vector, add back removed camera test code

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

* A snapshot a day keeps the bugs away! 📷🐛 (OS: macos-14-large)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: macos-14-large)

* Fix camera tests again

* Massively deflake a whole class of tests

* A snapshot a day keeps the bugs away! 📷🐛 (OS: macos-14-large)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: macos-14-large)

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

* Try new CI and fix small onboarding test

* Derp

* No github tuning

* Try mac

* Add back all the OS

* Lord, hallow be thy name

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

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

* One last try with window-16-cores

* Trigger CI

* Try AWS Windows runner

* Passing on windows locally with a few skips

* Skip more win tests, add back all three oses

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

* Add two more fixmes

* 2 more fixmes

* skip segment overlays on win32

* Another fixme

* Trigger CI

* Trigger CI

* Quick clean up

* Move all tests over to electron

* Pass the correct param to playwright-electron.sh

* Add shebang to script and add macos-14-large as a target

* Get sketch-tests.spec.ts passing in electron

* Try out 4 workers

* Got testing-segment-overlays passing

* Pass testing-selections.spec.ts

* Go back to fix up sketch-tests test

* Pass various.spec.ts, by far the hardest one

* Pass can-sketch-on-all-planes... with ease

* Pass command bar tests

* fmt

* Completely fix code mirror text navigating for tests

* Pass debug pane tests

* Pass desktop export tests

* Pass editor tests

* Pass file tree tests

* Pass onboarding tests

* Corrected a fixme in file-tree.spec!

* Painfully fix hardcoded coordinates in point-click.spec

* Pass machine.spec tests

* Pass projects, fought hard with filechooser

* Pass regresion-tests.spec tests

* Pass network and connection tests

* Pass camera-movement.spec tests

* Extreme time eaten by gizmo test fixes. All passing now.

* Merge main (tests changed x_x) and pass all constraints.spec tests (pain)

* Pass another painful spec suite: testing-settings

* Pass perspective-toggle, interesting note

* Pass samples loading tests

* Pass app header tests

* Pass text-to-cad tests

* Pass segment-overlays (minor ache) and ability to switch to web if needed :)

* Fix a ton of syntax changes and deflake 2 more tests (pain)

* Correct all tsc errors

* Remove to-electron script

* Add an f-ton of shit because playwright doesnt want S P R E A D

* Try CI again

* Stop snapshots of exports (already test in e2e)

* Fix flake in double click editor

* Hopefully help CI flake

* Fixmes, fixmes everywhere

* One more fixme to settings

* Skip another code pane flake

* Port jess's projects.spec tests

* fixup

* Reuse electron window; difficult task

* Rebased and refixed

* Remove duplicate cases

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

* Reduce the workers to something CI can handle

* Lower it further, we need to think about the others

* Update package.json

Co-authored-by: Pierre Jacquier <pierrejacquier39@gmail.com>

* Update package.json

Co-authored-by: Pierre Jacquier <pierrejacquier39@gmail.com>

* Fix the last tests and tsc errors

* Timeout to 120 and windows-2022-16core

* Fix windows runner detection, enable concurrency temporarily

* Hopefully this time fix windows runner detection

* Comment out Vector, add back removed camera test code

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

* A snapshot a day keeps the bugs away! 📷🐛 (OS: macos-14-large)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: macos-14-large)

* Fix camera tests again

* Massively deflake a whole class of tests

* A snapshot a day keeps the bugs away! 📷🐛 (OS: macos-14-large)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: macos-14-large)

* Try new CI and fix small onboarding test

* Derp

* No github tuning

* Try mac

* Add back all the OS

* Lord, hallow be thy name

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

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

* Try AWS Windows runner

* Passing on windows locally with a few skips

* Trigger CI

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

* fmt, tsc, lint

* Enable two fixmes again

* Fix lint, codespell, fmt

* Fix lint

* Don't run e2e on draft, add back concurrency, clean up

* One last windows skip

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Pierre Jacquier <pierrejacquier39@gmail.com>
Co-authored-by: Pierre Jacquier <pierre@zoo.dev>

* Add blank line to discord bot message (#4814)

* Add blank link to discord bot message

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

* Trigger CI

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

* Trigger CI

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

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

* Trigger CI

---------

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

* Implement some basic cache program generation (#4840)

Implement some basic cache program generation

A bit ago, @jessfraz added the ability to control reexecution from the
executor. When we did this, it used the digest to determine if there
was a code change rather than a non-code change (whitespace, comment,
etc).

This allows the creation of a new program to be run without clearing the
scene. This, in conjunction with being able to delete Engine objects by
ID will allow us to do some clever stuff when incrementally executing
a program.

I'm still working on something a bit more advanced, but a good first
step to derisk some of the caching behavior here fully is to implement a
basic "changed program" stub.

This process the ast programs (old and new) if it doesn't exactly match.
This would have been a complete refresh before this commit.

 1) Check all overlapping top-level statements of the body of the new and
    old AST and ensure they all match.
      - If this is true, this means that one of the two AST programs has more
        elements then the other, and they all otherwise match (addition or
        deletion of the end of the program). We continue to #2 in this
        case.
      - If this is false, we have a meaingful difference in a section of
        overlapping code. This will result in a cache miss and rebuild
        the scene. We short-cut here and the scene is rebuilt.

 2) Check the lengths of the two bodies.
   - If they're the same, we shouldn't have even been called. We will
     short-cut with a noop cache return (no clear, no program).
   - if the old ast is longer, we've removed instructions from the
     program. We can't delete things now, so this will result in a cache
     miss and rebuild the scene. We short-cut here and the scene is
     rebuilt.
   - If the new ast is longer, we have an insertion of code at the end.

 3) construct a new program using only the new elements from the new
    ast, and return a `CacheResult` that *does not clear the scene*.
    This means nothing will be rebuilt, and only a new object will polp
    onto the scene. This is the first case where we diverge with
    existing behavior.

Signed-off-by: Paul R. Tagliamonte <paul@zoo.dev>

* Fix mac issue after electron test migration PR (#4842)

Fix mac issue after electron test migration pR

* Remove guards from modeling commands in the toolbar (#4800)

* Remove guards from modeling commands in the toolbar

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

* Remove the deprecated function, update doc comment for the one still in use

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

* Remove more selection check functions that are no longer used

* Update E2E tests that assumed the extrude button could be disabled due to selection

* Update a few fillet tests that expected the button to disable based on selection

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

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

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

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

* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-macos-8-cores)

* Trigger CI

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

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

* Trigger CI

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Pierre Jacquier <pierre@zoo.dev>

* Prompt to edit (#4830)

* initial plumbing for getting the new option into the cmd-bar

* start of prompt edit

* update AI poll

* add spinner

* more prompt engineering

* add success toast, allowing user's to reject code

* select code that changed in prompt to edit

* selection in scene should not disappear when opening prompt cmd

* tweak

* fmt

* add tests

* some clean up

* clean up

* fix tests

* Yeet telemetry to text to cad endpoint (#4847)

yeet telemetry to text to cad endpoint

* Tweak prompt wording (#4846)

tweak prompt wording

* update discord bot (#4837)

* Re-enable test 'code pane closed at start' after electron migration (#4841)

* Re-enable 'code pane closed at start'
Relates to #4838

* Enable test on current branch

* Revert "Enable test on current branch"

This reverts commit 0d970b9ad6.

* Add 3-point circle tool (#4832)

* Add 3-point circle tool

This adds a 1st pass for the 3-point circle tool.

There is disabled code to drag around the 3 points and redraw the circle and
a triangle created by those points. It will be enabled in a follow-up PR
when we have circle3Point in the stdlib.

For now, all it does is after the 3rd click, will insert circle center-radius
KCL code for users to modify.

* PR comments

* First draft of a feature tree pane (#4782)

* Fix e2e default planes tests to use mask on state indicator (#4849)

* Fix e2e default planes tests to use mask on state indicator

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

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

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

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

* Trigger CI

---------

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

* fix: don't error on failure to fetch privacy settings (#4799)

* fix: dont error on failure to fetch privacy settings

* Add console warning when we ignore it

---------

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

* Match package license to LICENSE file (#4882)

* Remove the old Loft GitHub issue link in the toolbar (#4848)

Now that it's available, it shouldn't be there anymore.

* Fix fuzz crate lints and update deps (#4873)

* Fix fuzz to use new API

* Fix fuzz Cargo.toml lints and update lock

* Use app foreground color for focus outline button color so there's no hue collision (#4894)

Towards #4851

* Add support for float numbers in rem() arguments (#4858)

* Add support for float numbers in rem() arguments

* Update docs

* Rename to prevent name collision in docs

* Update docs after rename to NumberArg

* Fix parameter types to follow naming convention

* Change doc comment description to not refer to floating point

* Update docs after NumberArg doc change

* Change function to be more condensed

* Change to not try to preserve ints

* Update docs to use f64

* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-macos-8-cores)

* Trigger CI

---------

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

* Add auto-retries to macOS build notarization (#4892)

* Add DEBUG=electron-notarize* to build-apps

* Force IS_RELEASE: true

* Temp: Disable version setting based on tag

* Remove deprecated notarize.teamId and add retry logic

* Revert "Remove deprecated notarize.teamId and add retry logic"

This reverts commit 6ff98d784d.

* Retry only on macOS

* Better retry logic for macOS

* Use nick-fields/retry

* Clean up Temp: commits for PR

* Clean up

* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-macos-8-cores)

* Trigger CI

---------

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

* Bump nanoid from 3.3.7 to 3.3.8 (#4731)

Bumps [nanoid](https://github.com/ai/nanoid) from 3.3.7 to 3.3.8.
- [Release notes](https://github.com/ai/nanoid/releases)
- [Changelog](https://github.com/ai/nanoid/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ai/nanoid/compare/3.3.7...3.3.8)

---
updated-dependencies:
- dependency-name: nanoid
  dependency-type: indirect
...

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

* Enable enter for autocompletions in the command palette KCL input (#4896)

* Enable enter for autocompletions in the command palette KCL input

* Oops I commented out code for the variable name input
Thanks for the catch @pierremtb

---------

Co-authored-by: Pierre Jacquier <pierrejacquier39@gmail.com>

* Bump rollup from 4.21.0 to 4.29.1 (#4888)

* Bump rollup from 4.21.0 to 4.29.1

Bumps [rollup](https://github.com/rollup/rollup) from 4.21.0 to 4.29.1.
- [Release notes](https://github.com/rollup/rollup/releases)
- [Changelog](https://github.com/rollup/rollup/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rollup/rollup/compare/v4.21.0...v4.29.1)

---
updated-dependencies:
- dependency-name: rollup
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

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

---------

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

* Louder Windows codesign errors (#4762)

* WIP: Silent failure in signWin.js
Fixes #4582

* Temp: force release build

* Fake throw

* Temp: another test

* Clean up for merge

* Add parsing keyword function calls inside pipelines (#4907)

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

* Remove draft PR filter on e2e tests (#4908)

* Add three point circle stdlib function (#4893)

* Add parsing keyword function calls inside pipelines

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

* Add three point circle stdlib function

* Generate new documentation

* Fix 20:20 for the circle three point test

* Convert to using keyword arguments

* Wtf yo

* Remove unused structure

* Use the new simulation tests

* Regenerate documentation

---------

Co-authored-by: Jonathan Tran <jonnytran@gmail.com>
Co-authored-by: Adam Chalmers <adam.chalmers@zoo.dev>

* Move the base CodeMirror KCL support to a local package (#4897)

* Move CodeMirror LRLanguage to new file

This separates the base language support from the LSP and color picker.

* Move the base CodeMirror KCL support to a local package

* Start CodeMirror grammar tests

* Exclude vitest config in tsconfig

* Add KCL path to tsconfig

* Remove stray import

* Drop extension from import

* Use __filename for commonjs compat

* Check exec return before access

* Build ES and CJS to dist

* Format

* Exclude all.test.ts from codespell

This is to work around "fileTests" imported from Lezer. Future codespell versions look
like they'll allow the code to be annotated, which would be nicer.

---------

Co-authored-by: Matt Mundell <matt@mundell.me>

* Fix typo in README (#3843)

Co-authored-by: Jess Frazelle <jessfraz@users.noreply.github.com>

* remove backwards compatibility for snake case in objects (#4920)

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

* CM KCL: Support `=` in record init (#4933)

Support `=` in record init

Co-authored-by: Matt Mundell <matt@mundell.me>

* Bump clap from 4.5.21 to 4.5.23 in /src/wasm-lib (#4928)

Bumps [clap](https://github.com/clap-rs/clap) from 4.5.21 to 4.5.23.
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.21...clap_complete-v4.5.23)

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

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

* Bump @kittycad/lib from 2.0.7 to 2.0.12 (#4922)

Bumps [@kittycad/lib](https://github.com/KittyCAD/kittycad.ts) from 2.0.7 to 2.0.12.
- [Release notes](https://github.com/KittyCAD/kittycad.ts/releases)
- [Commits](https://github.com/KittyCAD/kittycad.ts/compare/v2.0.7...v2.0.12)

---
updated-dependencies:
- dependency-name: "@kittycad/lib"
  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 anyhow from 1.0.94 to 1.0.95 in /src/wasm-lib (#4929)

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

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

* Bump url from 2.5.3 to 2.5.4 in /src/wasm-lib (#4930)

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

---
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 wasm-streams from 0.4.1 to 0.4.2 in /src/wasm-lib (#4926)

Bumps [wasm-streams](https://github.com/MattiasBuelens/wasm-streams) from 0.4.1 to 0.4.2.
- [Release notes](https://github.com/MattiasBuelens/wasm-streams/releases)
- [Changelog](https://github.com/MattiasBuelens/wasm-streams/blob/main/CHANGELOG.md)
- [Commits](https://github.com/MattiasBuelens/wasm-streams/compare/v0.4.1...v0.4.2)

---
updated-dependencies:
- dependency-name: wasm-streams
  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 @csstools/postcss-oklab-function from 4.0.2 to 4.0.7 (#4923)

Bumps [@csstools/postcss-oklab-function](https://github.com/csstools/postcss-plugins/tree/HEAD/plugins/postcss-oklab-function) from 4.0.2 to 4.0.7.
- [Changelog](https://github.com/csstools/postcss-plugins/blob/main/plugins/postcss-oklab-function/CHANGELOG.md)
- [Commits](https://github.com/csstools/postcss-plugins/commits/HEAD/plugins/postcss-oklab-function)

---
updated-dependencies:
- dependency-name: "@csstools/postcss-oklab-function"
  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>

* fix: writes to disk when the user accepts the prompt to edit (#4942)

* fix: writes to disk when the user accepts the prompt to edit

* fix: then catch

* Make circle3Point tool an actor (#4906)

* Reduce the amount of data sent to TS and make new fields opt-in (#4913)

* Reduce the amount of data sent back to JS/TS from WASM

* Remove unneeded derives since we shouldn't expose these types

* Alias type to be clearer

* Bump syn from 2.0.87 to 2.0.95 to be compatible with modeling-cmds 0.… (#4945)

Bump syn from 2.0.87 to 2.0.95 to be compatible with modeling-cmds 0.2.86

* fix out of range error (#4931)

* fix out of range error

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

* updates

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

* remove console logs

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

* add a regression test

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

---------

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

* Run chrome e2e snapshots only on linux (#4947)

* Run chrome e2e snapshots only on linux

* Wtf is this 1-indexed

* Force 1/1 sharding

* Add TODO

* Nadro/3079/screenshot improvements (#3917)

* chore: swapped screenshot to only use the video stream

* feat: video stream screenshot, native electron screenshot

* fix: auto tsc, fmt, xgen, lint

* fix: fixing tsc errors

* fix: removing debug console.log

* fix: renaming ScreenShot to Screenshot

* fix: deleting console log from debugging

* fix: bug with what source was referenced

* fix: using a productName

* fix: improving usage for native screenshots and detecthing support

* fix: fmt

* chore: updated rust test documentation

* fix: typo in readme

* fix: leaving package.json and yarn.lock the same as main??

* bump

* bump

* bump again

* bump again2

* feat: implemented zoom to fit on code change if previous AST was empty (#3925)

* feat: implemented zoom to fit on code change if previous AST was empty

* feat: implementing selectAll text logic to enable select all and copy and paste and zoom to fit will work

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

* fix: clarifying comment in _isAstEmpty

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

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

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

* bump

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

* bump again

* fix: fixing new type since this branch is old

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

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

* bump

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

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

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

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

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

---------

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

* Add dependabot group for all serde dependencies (#4944)

* Fix formatting of dependabot.yml

* Add dependabot group for all serde dependencies

* CM KCL: = and => are optional in fn declarations (#4941)

CM KCL: `=` and `=>` are optional in fn declarations

Co-authored-by: Matt Mundell <matt@mundell.me>

* Bump ts-rs from 10.0.0 to 10.1.0 (#4949)

* Add toolbar buttons for text-to-cad and prompt-to-edit (#4938)

* Add toolbar buttons for text-to-cad and prompt-to-edit
Resolves #4890

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

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

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

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

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

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

* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-macos-8-cores)

* `preventDefault` on <kbd>Enter</kbd> with textarea input so buttons aren't clicked as well

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

* Trigger CI

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Pierre Jacquier <pierre@zoo.dev>

* Bump quote from 1.0.37 to 1.0.38 in /src/wasm-lib (#4951)

Bumps [quote](https://github.com/dtolnay/quote) from 1.0.37 to 1.0.38.
- [Release notes](https://github.com/dtolnay/quote/releases)
- [Commits](https://github.com/dtolnay/quote/compare/1.0.37...1.0.38)

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

* array pop with unlabled kw arg

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
Signed-off-by: Nick Cameron <nrc@ncameron.org>
Signed-off-by: Paul R. Tagliamonte <paul@zoo.dev>
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: Kurt Hutten <k.hutten@protonmail.ch>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Jess Frazelle <jessfraz@users.noreply.github.com>
Co-authored-by: Frank Noirot <frank@zoo.dev>
Co-authored-by: Jonathan Tran <jonnytran@gmail.com>
Co-authored-by: Nick Cameron <nrc@ncameron.org>
Co-authored-by: Adam Chalmers <adam.chalmers@zoo.dev>
Co-authored-by: Pierre Jacquier <pierrejacquier39@gmail.com>
Co-authored-by: Kevin Nadro <nadr0@users.noreply.github.com>
Co-authored-by: 49fl <ircsurfer33@gmail.com>
Co-authored-by: Pierre Jacquier <pierre@zoo.dev>
Co-authored-by: Paul Tagliamonte <paul@zoo.dev>
Co-authored-by: Josh Gomez <114548659+jgomez720@users.noreply.github.com>
Co-authored-by: Tom Pridham <pridham.tom@gmail.com>
Co-authored-by: Matt Mundell <32057441+mattmundell@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Matt Mundell <matt@mundell.me>
Co-authored-by: alteous <david@harvey-macaulay.com>
2025-01-07 20:24:24 +00:00
2eb7c382bf Bump @codemirror/lint from 6.8.1 to 6.8.4 (#4955)
Bumps [@codemirror/lint](https://github.com/codemirror/lint) from 6.8.1 to 6.8.4.
- [Changelog](https://github.com/codemirror/lint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codemirror/lint/compare/6.8.1...6.8.4)

---
updated-dependencies:
- dependency-name: "@codemirror/lint"
  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>
2025-01-07 20:23:07 +00:00
38913ecb98 Bump three and @types/three (#4956)
Bumps [three](https://github.com/mrdoob/three.js) and [@types/three](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/three). These dependencies needed to be updated together.

Updates `three` from 0.166.1 to 0.172.0
- [Release notes](https://github.com/mrdoob/three.js/releases)
- [Commits](https://github.com/mrdoob/three.js/commits)

Updates `@types/three` from 0.163.0 to 0.172.0
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/three)

---
updated-dependencies:
- dependency-name: three
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: "@types/three"
  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>
2025-01-07 20:19:58 +00:00
debd06129f CM KCL: numbers must have digits after dot (#4963)
Co-authored-by: Matt Mundell <matt@mundell.me>
2025-01-07 12:19:31 -08:00
d38bd342a0 Bump winnow from 0.6.20 to 0.6.22 in /src/wasm-lib (#4954)
Bumps [winnow](https://github.com/winnow-rs/winnow) from 0.6.20 to 0.6.22.
- [Changelog](https://github.com/winnow-rs/winnow/blob/main/CHANGELOG.md)
- [Commits](https://github.com/winnow-rs/winnow/compare/v0.6.20...v0.6.22)

---
updated-dependencies:
- dependency-name: winnow
  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>
2025-01-07 20:16:56 +00:00
f026f10335 Bump zip from 2.2.0 to 2.2.2 in /src/wasm-lib (#4952)
Bumps [zip](https://github.com/zip-rs/zip2) from 2.2.0 to 2.2.2.
- [Release notes](https://github.com/zip-rs/zip2/releases)
- [Changelog](https://github.com/zip-rs/zip2/blob/master/CHANGELOG.md)
- [Commits](https://github.com/zip-rs/zip2/compare/v2.2.0...v2.2.2)

---
updated-dependencies:
- dependency-name: zip
  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>
2025-01-07 18:06:37 +00:00
895d7ebc6d Bump the serde-dependencies group in /src/wasm-lib with 2 updates (#4950)
Bumps the serde-dependencies group in /src/wasm-lib with 2 updates: [serde_json](https://github.com/serde-rs/json) and [serde](https://github.com/serde-rs/serde).


Updates `serde_json` from 1.0.133 to 1.0.135
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.133...v1.0.135)

Updates `serde` from 1.0.216 to 1.0.217
- [Release notes](https://github.com/serde-rs/serde/releases)
- [Commits](https://github.com/serde-rs/serde/compare/v1.0.216...v1.0.217)

---
updated-dependencies:
- dependency-name: serde_json
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: serde-dependencies
- dependency-name: serde
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: serde-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-07 17:55:33 +00:00
65edf17a44 Bump quote from 1.0.37 to 1.0.38 in /src/wasm-lib (#4951)
Bumps [quote](https://github.com/dtolnay/quote) from 1.0.37 to 1.0.38.
- [Release notes](https://github.com/dtolnay/quote/releases)
- [Commits](https://github.com/dtolnay/quote/compare/1.0.37...1.0.38)

---
updated-dependencies:
- dependency-name: quote
  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>
2025-01-07 09:14:32 -08:00
265 changed files with 120859 additions and 2904 deletions

View File

@ -1,3 +1,3 @@
[codespell] [codespell]
ignore-words-list: crate,everytime,inout,co-ordinate,ot,nwo,atleast,ue,afterall ignore-words-list: crate,everytime,inout,co-ordinate,ot,nwo,atleast,ue,afterall
skip: **/target,node_modules,build,**/Cargo.lock,./docs/kcl/*.md,.yarn.lock,**/yarn.lock,./openapi/*.json,./src/lib/machine-api.d.ts,./packages/codemirror-lang-kcl/test/all.test.ts skip: **/target,node_modules,build,dist,./out,**/Cargo.lock,./docs/kcl/*.md,.yarn.lock,**/yarn.lock,./openapi/*.json,./packages/codemirror-lang-kcl/test/all.test.ts,tsconfig.tsbuildinfo

View File

@ -5,16 +5,32 @@
}, },
"plugins": [ "plugins": [
"css-modules", "css-modules",
"jest",
"jsx-a11y",
"react",
"react-hooks",
"suggest-no-throw", "suggest-no-throw",
"testing-library",
"@typescript-eslint"
], ],
"extends": [ "extends": [
"react-app", "plugin:css-modules/recommended",
"react-app/jest", "plugin:jsx-a11y/recommended",
"plugin:css-modules/recommended" "plugin:react-hooks/recommended"
], ],
"rules": { "rules": {
"@typescript-eslint/no-floating-promises": "error", "@typescript-eslint/no-floating-promises": "error",
"@typescript-eslint/no-misused-promises": "error", "@typescript-eslint/no-misused-promises": "error",
"jsx-a11y/click-events-have-key-events": "off",
"jsx-a11y/no-autofocus": "off",
"jsx-a11y/no-noninteractive-element-interactions": "off",
"no-restricted-globals": [
"error",
{
"name": "isNaN",
"message": "Use Number.isNaN() instead."
}
],
"semi": [ "semi": [
"error", "error",
"never" "never"
@ -25,6 +41,9 @@
"overrides": [ "overrides": [
{ {
"files": ["e2e/**/*.ts"], // Update the pattern based on your file structure "files": ["e2e/**/*.ts"], // Update the pattern based on your file structure
"extends": [
"plugin:testing-library/react"
],
"rules": { "rules": {
"suggest-no-throw/suggest-no-throw": "off", "suggest-no-throw/suggest-no-throw": "off",
"testing-library/prefer-screen-queries": "off", "testing-library/prefer-screen-queries": "off",
@ -33,6 +52,9 @@
}, },
{ {
"files": ["src/**/*.test.ts"], "files": ["src/**/*.test.ts"],
"extends": [
"plugin:testing-library/react"
],
"rules": { "rules": {
"suggest-no-throw/suggest-no-throw": "off", "suggest-no-throw/suggest-no-throw": "off",
} }

View File

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

View File

@ -6,23 +6,29 @@
version: 2 version: 2
updates: updates:
- package-ecosystem: 'npm' # See documentation for possible values - package-ecosystem: 'npm' # See documentation for possible values
directory: '/' # Location of package manifests directories:
- '/'
- '/packages/codemirror-lang-kcl/'
- '/packages/codemirror-lsp-client/'
schedule: schedule:
interval: 'weekly' interval: weekly
day: monday
reviewers: reviewers:
- franknoirot - franknoirot
- irev-dev - irev-dev
- package-ecosystem: 'github-actions' # See documentation for possible values - package-ecosystem: 'github-actions' # See documentation for possible values
directory: '/' # Location of package manifests directory: '/' # Location of package manifests
schedule: schedule:
interval: 'weekly' interval: weekly
day: monday
reviewers: reviewers:
- adamchalmers - adamchalmers
- jessfraz - jessfraz
- package-ecosystem: 'cargo' # See documentation for possible values - package-ecosystem: 'cargo' # See documentation for possible values
directory: '/src/wasm-lib/' # Location of package manifests directory: '/src/wasm-lib/' # Location of package manifests
schedule: schedule:
interval: 'weekly' interval: weekly
day: monday
reviewers: reviewers:
- adamchalmers - adamchalmers
- jessfraz - jessfraz
@ -30,3 +36,6 @@ updates:
serde-dependencies: serde-dependencies:
patterns: patterns:
- "serde*" - "serde*"
wasm-bindgen-deps:
patterns:
- "wasm-bindgen*"

View File

@ -0,0 +1,32 @@
name: CodeMirror Lang KCL
on:
pull_request:
push:
branches:
- main
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
jobs:
yarn-unit-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: 'yarn'
- run: yarn install
working-directory: packages/codemirror-lang-kcl
- run: yarn tsc
working-directory: packages/codemirror-lang-kcl
- name: run unit tests
run: yarn test
working-directory: packages/codemirror-lang-kcl

View File

@ -24,3 +24,5 @@ once fixed in engine will just start working here with no language changes.
chamfer cases work currently. chamfer cases work currently.
- **Appearance**: Changing the appearance on a loft does not work. - **Appearance**: Changing the appearance on a loft does not work.
- **Helix**: Currently sweeping a helix does not work.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -48,6 +48,7 @@ layout: manual
* [`getOppositeEdge`](kcl/getOppositeEdge) * [`getOppositeEdge`](kcl/getOppositeEdge)
* [`getPreviousAdjacentEdge`](kcl/getPreviousAdjacentEdge) * [`getPreviousAdjacentEdge`](kcl/getPreviousAdjacentEdge)
* [`helix`](kcl/helix) * [`helix`](kcl/helix)
* [`helixRevolutions`](kcl/helixRevolutions)
* [`hole`](kcl/hole) * [`hole`](kcl/hole)
* [`hollow`](kcl/hollow) * [`hollow`](kcl/hollow)
* [`import`](kcl/import) * [`import`](kcl/import)
@ -81,6 +82,7 @@ layout: manual
* [`pi`](kcl/pi) * [`pi`](kcl/pi)
* [`polar`](kcl/polar) * [`polar`](kcl/polar)
* [`polygon`](kcl/polygon) * [`polygon`](kcl/polygon)
* [`pop`](kcl/pop)
* [`pow`](kcl/pow) * [`pow`](kcl/pow)
* [`profileStart`](kcl/profileStart) * [`profileStart`](kcl/profileStart)
* [`profileStartX`](kcl/profileStartX) * [`profileStartX`](kcl/profileStartX)

39
docs/kcl/pop.md Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -1,19 +1,19 @@
--- ---
title: "AxisOrEdgeReference" title: "Axis2dOrEdgeReference"
excerpt: "Axis or tagged edge." excerpt: "A 2D axis or tagged edge."
layout: manual layout: manual
--- ---
Axis or tagged edge. A 2D axis or tagged edge.
**This schema accepts any of the following:** **This schema accepts any of the following:**
Axis and origin. 2D axis and origin.
[`AxisAndOrigin`](/docs/kcl/types/AxisAndOrigin) [`AxisAndOrigin2d`](/docs/kcl/types/AxisAndOrigin2d)

View File

@ -0,0 +1,42 @@
---
title: "Axis3dOrEdgeReference"
excerpt: "A 3D axis or tagged edge."
layout: manual
---
A 3D axis or tagged edge.
**This schema accepts any of the following:**
3D axis and origin.
[`AxisAndOrigin3d`](/docs/kcl/types/AxisAndOrigin3d)
----
Tagged edge.
[`EdgeReference`](/docs/kcl/types/EdgeReference)
----

View File

@ -1,10 +1,10 @@
--- ---
title: "AxisAndOrigin" title: "AxisAndOrigin2d"
excerpt: "Axis and origin." excerpt: "A 2D axis and origin."
layout: manual layout: manual
--- ---
Axis and origin. A 2D axis and origin.

View File

@ -0,0 +1,105 @@
---
title: "AxisAndOrigin3d"
excerpt: "A 3D axis and origin."
layout: manual
---
A 3D axis and origin.
**This schema accepts exactly one of the following:**
X-axis.
**enum:** `X`
----
Y-axis.
**enum:** `Y`
----
Z-axis.
**enum:** `Z`
----
Flip the X-axis.
**enum:** `-X`
----
Flip the Y-axis.
**enum:** `-Y`
----
Flip the Z-axis.
**enum:** `-Z`
----
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `custom` |`object`| | No |
----

25
docs/kcl/types/Helix.md Normal file
View File

@ -0,0 +1,25 @@
---
title: "Helix"
excerpt: "A helix."
layout: manual
---
A helix.
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `value` |`string`| The id of the helix. | No |
| `revolutions` |`number`| Number of revolutions. | No |
| `angleStart` |`number`| Start angle (in degrees). | No |
| `ccw` |`boolean`| Is the helix rotation counter clockwise? | No |
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |

View File

@ -1,10 +1,10 @@
--- ---
title: "HelixData" title: "HelixData"
excerpt: "Data for helices." excerpt: "Data for a helix."
layout: manual layout: manual
--- ---
Data for helices. Data for a helix.
**Type:** `object` **Type:** `object`
@ -19,6 +19,8 @@ Data for helices.
| `revolutions` |`number`| Number of revolutions. | No | | `revolutions` |`number`| Number of revolutions. | No |
| `angleStart` |`number`| Start angle (in degrees). | No | | `angleStart` |`number`| Start angle (in degrees). | No |
| `ccw` |`boolean`| Is the helix rotation counter clockwise? The default is `false`. | No | | `ccw` |`boolean`| Is the helix rotation counter clockwise? The default is `false`. | No |
| `length` |`number`| Length of the helix. If this argument is not provided, the height of the solid is used. | No | | `length` |`number`| Length of the helix. This is not necessary if the helix is created around an edge. If not given the length of the edge is used. | No |
| `radius` |`number`| Radius of the helix. | No |
| `axis` |[`Axis3dOrEdgeReference`](/docs/kcl/types/Axis3dOrEdgeReference)| Axis to use as mirror. | No |

View File

@ -0,0 +1,24 @@
---
title: "HelixRevolutionsData"
excerpt: "Data for helix revolutions."
layout: manual
---
Data for helix revolutions.
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `revolutions` |`number`| Number of revolutions. | No |
| `angleStart` |`number`| Start angle (in degrees). | No |
| `ccw` |`boolean`| Is the helix rotation counter clockwise? The default is `false`. | No |
| `length` |`number`| Length of the helix. If this argument is not provided, the height of the solid is used. | No |

View File

@ -0,0 +1,25 @@
---
title: "HelixValue"
excerpt: "A helix."
layout: manual
---
A helix.
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `value` |`string`| The id of the helix. | No |
| `revolutions` |`number`| Number of revolutions. | No |
| `angleStart` |`number`| Start angle (in degrees). | No |
| `ccw` |`boolean`| Is the helix rotation counter clockwise? | No |
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |

View File

@ -285,6 +285,27 @@ An solid is a collection of extrude surfaces.
| `value` |`[` [`Solid`](/docs/kcl/types/Solid) `]`| | No | | `value` |`[` [`Solid`](/docs/kcl/types/Solid) `]`| | No |
----
A helix.
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: [`Helix`](/docs/kcl/types/Helix)| | No |
| `value` |`string`| The id of the helix. | No |
| `revolutions` |`number`| Number of revolutions. | No |
| `angleStart` |`number`| Start angle (in degrees). | No |
| `ccw` |`boolean`| Is the helix rotation counter clockwise? | No |
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
---- ----
Data for an imported geometry. Data for an imported geometry.

View File

@ -16,6 +16,6 @@ Data for a mirror.
| Property | Type | Description | Required | | Property | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `axis` |[`AxisOrEdgeReference`](/docs/kcl/types/AxisOrEdgeReference)| Axis to use as mirror. | No | | `axis` |[`Axis2dOrEdgeReference`](/docs/kcl/types/Axis2dOrEdgeReference)| Axis to use as mirror. | No |

View File

@ -17,7 +17,7 @@ Data for revolution surfaces.
| Property | Type | Description | Required | | Property | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `angle` |`number` (**maximum:** 360.0) (**minimum:** -360.0)| Angle to revolve (in degrees). Default is 360. | No | | `angle` |`number` (**maximum:** 360.0) (**minimum:** -360.0)| Angle to revolve (in degrees). Default is 360. | No |
| `axis` |[`AxisOrEdgeReference`](/docs/kcl/types/AxisOrEdgeReference)| Axis of revolution. | No | | `axis` |[`Axis2dOrEdgeReference`](/docs/kcl/types/Axis2dOrEdgeReference)| Axis of revolution. | No |
| `tolerance` |`number`| Tolerance for the revolve operation. | No | | `tolerance` |`number`| Tolerance for the revolve operation. | No |

View File

@ -16,7 +16,7 @@ Data for a sweep.
| Property | Type | Description | Required | | Property | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `path` |[`Sketch`](/docs/kcl/types/Sketch)| The path to sweep along. | No | | `path` |[`SweepPath`](/docs/kcl/types/SweepPath)| The path to sweep along. | No |
| `sectional` |`boolean`| If true, the sweep will be broken up into sub-sweeps (extrusions, revolves, sweeps) based on the trajectory path components. | No | | `sectional` |`boolean`| If true, the sweep will be broken up into sub-sweeps (extrusions, revolves, sweeps) based on the trajectory path components. | No |
| `tolerance` |`number`| Tolerance for the sweep operation. | No | | `tolerance` |`number`| Tolerance for the sweep operation. | No |

View File

@ -0,0 +1,42 @@
---
title: "SweepPath"
excerpt: "A path to sweep along."
layout: manual
---
A path to sweep along.
**This schema accepts any of the following:**
A path to sweep along.
[`Sketch`](/docs/kcl/types/Sketch)
----
A path to sweep along.
[`Helix`](/docs/kcl/types/Helix)
----

View File

@ -121,18 +121,23 @@ export class AuthenticatedTronApp {
export const fixtures = { export const fixtures = {
cmdBar: async ({ page }: { page: Page }, use: any) => { cmdBar: async ({ page }: { page: Page }, use: any) => {
// eslint-disable-next-line react-hooks/rules-of-hooks
await use(new CmdBarFixture(page)) await use(new CmdBarFixture(page))
}, },
editor: async ({ page }: { page: Page }, use: any) => { editor: async ({ page }: { page: Page }, use: any) => {
// eslint-disable-next-line react-hooks/rules-of-hooks
await use(new EditorFixture(page)) await use(new EditorFixture(page))
}, },
toolbar: async ({ page }: { page: Page }, use: any) => { toolbar: async ({ page }: { page: Page }, use: any) => {
// eslint-disable-next-line react-hooks/rules-of-hooks
await use(new ToolbarFixture(page)) await use(new ToolbarFixture(page))
}, },
scene: async ({ page }: { page: Page }, use: any) => { scene: async ({ page }: { page: Page }, use: any) => {
// eslint-disable-next-line react-hooks/rules-of-hooks
await use(new SceneFixture(page)) await use(new SceneFixture(page))
}, },
homePage: async ({ page }: { page: Page }, use: any) => { homePage: async ({ page }: { page: Page }, use: any) => {
// eslint-disable-next-line react-hooks/rules-of-hooks
await use(new HomePageFixture(page)) await use(new HomePageFixture(page))
}, },
} }

View File

@ -36,7 +36,8 @@ type DragFromHandler = (
export class SceneFixture { export class SceneFixture {
public page: Page public page: Page
public streamWrapper!: Locator
public loadingIndicator!: Locator
private exeIndicator!: Locator private exeIndicator!: Locator
constructor(page: Page) { constructor(page: Page) {
@ -64,6 +65,8 @@ export class SceneFixture {
this.page = page this.page = page
this.exeIndicator = page.getByTestId('model-state-indicator-execution-done') this.exeIndicator = page.getByTestId('model-state-indicator-execution-done')
this.streamWrapper = page.getByTestId('stream')
this.loadingIndicator = this.streamWrapper.getByTestId('loading')
} }
makeMouseHelpers = ( makeMouseHelpers = (

View File

@ -14,6 +14,7 @@ export class ToolbarFixture {
extrudeButton!: Locator extrudeButton!: Locator
loftButton!: Locator loftButton!: Locator
sweepButton!: Locator
shellButton!: Locator shellButton!: Locator
offsetPlaneButton!: Locator offsetPlaneButton!: Locator
startSketchBtn!: Locator startSketchBtn!: Locator
@ -40,6 +41,7 @@ export class ToolbarFixture {
this.page = page this.page = page
this.extrudeButton = page.getByTestId('extrude') this.extrudeButton = page.getByTestId('extrude')
this.loftButton = page.getByTestId('loft') this.loftButton = page.getByTestId('loft')
this.sweepButton = page.getByTestId('sweep')
this.shellButton = page.getByTestId('shell') this.shellButton = page.getByTestId('shell')
this.offsetPlaneButton = page.getByTestId('plane-offset') this.offsetPlaneButton = page.getByTestId('plane-offset')
this.startSketchBtn = page.getByTestId('sketch') this.startSketchBtn = page.getByTestId('sketch')

View File

@ -756,6 +756,17 @@ test(`Offset plane point-and-click`, async ({
}) })
await scene.expectPixelColor([74, 74, 74], testPoint, 15) await scene.expectPixelColor([74, 74, 74], testPoint, 15)
}) })
await test.step('Delete offset plane via feature tree selection', async () => {
await editor.closePane()
const operationButton = await toolbar.getFeatureTreeOperation(
'Offset Plane',
0
)
await operationButton.click({ button: 'left' })
await page.keyboard.press('Backspace')
await scene.expectPixelColor([50, 51, 96], testPoint, 15)
})
}) })
const loftPointAndClickCases = [ const loftPointAndClickCases = [
@ -851,6 +862,173 @@ loftPointAndClickCases.forEach(({ shouldPreselect }) => {
}) })
await scene.expectPixelColor([89, 89, 89], testPoint, 15) await scene.expectPixelColor([89, 89, 89], testPoint, 15)
}) })
await test.step('Delete loft via feature tree selection', async () => {
await editor.closePane()
const operationButton = await toolbar.getFeatureTreeOperation('Loft', 0)
await operationButton.click({ button: 'left' })
await page.keyboard.press('Backspace')
await scene.expectPixelColor([254, 254, 254], testPoint, 15)
})
})
})
// TODO: merge with above test. Right now we're not able to delete a loft
// right after creation via selection for some reason, so we go with a new instance
test('Loft and offset plane deletion via selection', async ({
context,
page,
homePage,
scene,
}) => {
const initialCode = `sketch001 = startSketchOn('XZ')
|> circle({ center = [0, 0], radius = 30 }, %)
plane001 = offsetPlane('XZ', 50)
sketch002 = startSketchOn(plane001)
|> circle({ center = [0, 0], radius = 20 }, %)
loft001 = loft([sketch001, sketch002])
`
await context.addInitScript((initialCode) => {
localStorage.setItem('persistCode', initialCode)
}, initialCode)
await page.setBodyDimensions({ width: 1000, height: 500 })
await homePage.goToModelingScene()
// One dumb hardcoded screen pixel value
const testPoint = { x: 575, y: 200 }
const [clickOnSketch1] = scene.makeMouseHelpers(testPoint.x, testPoint.y)
const [clickOnSketch2] = scene.makeMouseHelpers(testPoint.x, testPoint.y + 80)
await test.step(`Delete loft`, async () => {
// Check for loft
await scene.expectPixelColor([89, 89, 89], testPoint, 15)
await clickOnSketch1()
await expect(page.locator('.cm-activeLine')).toHaveText(`
|> circle({ center = [0, 0], radius = 30 }, %)
`)
await page.keyboard.press('Backspace')
// Check for sketch 1
await scene.expectPixelColor([254, 254, 254], testPoint, 15)
})
await test.step('Delete sketch002', async () => {
await page.waitForTimeout(1000)
await clickOnSketch2()
await expect(page.locator('.cm-activeLine')).toHaveText(`
|> circle({ center = [0, 0], radius = 20 }, %)
`)
await page.keyboard.press('Backspace')
// Check for plane001
await scene.expectPixelColor([228, 228, 228], testPoint, 15)
})
await test.step('Delete plane001', async () => {
await page.waitForTimeout(1000)
await clickOnSketch2()
await expect(page.locator('.cm-activeLine')).toHaveText(`
plane001 = offsetPlane('XZ', 50)
`)
await page.keyboard.press('Backspace')
// Check for sketch 1
await scene.expectPixelColor([254, 254, 254], testPoint, 15)
})
})
test(`Sweep point-and-click`, async ({
context,
page,
homePage,
scene,
editor,
toolbar,
cmdBar,
}) => {
const initialCode = `sketch001 = startSketchOn('YZ')
|> circle({
center = [0, 0],
radius = 500
}, %)
sketch002 = startSketchOn('XZ')
|> startProfileAt([0, 0], %)
|> xLine(-500, %)
|> tangentialArcTo([-2000, 500], %)
`
await context.addInitScript((initialCode) => {
localStorage.setItem('persistCode', initialCode)
}, initialCode)
await page.setBodyDimensions({ width: 1000, height: 500 })
await homePage.goToModelingScene()
await scene.waitForExecutionDone()
// One dumb hardcoded screen pixel value
const testPoint = { x: 700, y: 250 }
const [clickOnSketch1] = scene.makeMouseHelpers(testPoint.x, testPoint.y)
const [clickOnSketch2] = scene.makeMouseHelpers(testPoint.x - 50, testPoint.y)
const sweepDeclaration = 'sweep001 = sweep({ path = sketch002 }, sketch001)'
await test.step(`Look for sketch001`, async () => {
await toolbar.closePane('code')
await scene.expectPixelColor([53, 53, 53], testPoint, 15)
})
await test.step(`Go through the command bar flow`, async () => {
await toolbar.sweepButton.click()
await cmdBar.expectState({
commandName: 'Sweep',
currentArgKey: 'profile',
currentArgValue: '',
headerArguments: {
Path: '',
Profile: '',
},
highlightedHeaderArg: 'profile',
stage: 'arguments',
})
await clickOnSketch1()
await cmdBar.expectState({
commandName: 'Sweep',
currentArgKey: 'path',
currentArgValue: '',
headerArguments: {
Path: '',
Profile: '1 face',
},
highlightedHeaderArg: 'path',
stage: 'arguments',
})
await clickOnSketch2()
await cmdBar.expectState({
commandName: 'Sweep',
headerArguments: {
Path: '1 face',
Profile: '1 face',
},
stage: 'review',
})
await cmdBar.progressCmdBar()
})
await test.step(`Confirm code is added to the editor, scene has changed`, async () => {
await scene.expectPixelColor([135, 64, 73], testPoint, 15)
await toolbar.openPane('code')
await editor.expectEditor.toContain(sweepDeclaration)
await editor.expectState({
diagnostics: [],
activeLines: [sweepDeclaration],
highlightedCode: '',
})
await toolbar.closePane('code')
})
await test.step('Delete sweep via feature tree selection', async () => {
await toolbar.openPane('feature-tree')
await page.waitForTimeout(500)
const operationButton = await toolbar.getFeatureTreeOperation('Sweep', 0)
await operationButton.click({ button: 'left' })
await page.keyboard.press('Backspace')
await page.waitForTimeout(500)
await toolbar.closePane('feature-tree')
await scene.expectPixelColor([53, 53, 53], testPoint, 15)
}) })
}) })
@ -1030,4 +1208,104 @@ extrude001 = extrude(40, sketch001)
}) })
await scene.expectPixelColor([49, 49, 49], testPoint, 15) await scene.expectPixelColor([49, 49, 49], testPoint, 15)
}) })
await test.step('Delete shell via feature tree selection', async () => {
await editor.closePane()
const operationButton = await toolbar.getFeatureTreeOperation('Shell', 0)
await operationButton.click({ button: 'left' })
await page.keyboard.press('Backspace')
await scene.expectPixelColor([99, 99, 99], testPoint, 15)
})
})
const shellSketchOnFacesCases = [
`sketch001 = startSketchOn('XZ')
|> circle({ center = [0, 0], radius = 100 }, %)
|> extrude(100, %)
sketch002 = startSketchOn(sketch001, 'END')
|> circle({ center = [0, 0], radius = 50 }, %)
|> extrude(50, %)
`,
`sketch001 = startSketchOn('XZ')
|> circle({ center = [0, 0], radius = 100 }, %)
extrude001 = extrude(100, sketch001)
sketch002 = startSketchOn(extrude001, 'END')
|> circle({ center = [0, 0], radius = 50 }, %)
extrude002 = extrude(50, sketch002)
`,
]
shellSketchOnFacesCases.forEach((initialCode, index) => {
const hasExtrudesInPipe = index === 0
test(`Shell point-and-click sketch on face (extrudes in pipes: ${hasExtrudesInPipe})`, async ({
context,
page,
homePage,
scene,
editor,
toolbar,
cmdBar,
}) => {
await context.addInitScript((initialCode) => {
localStorage.setItem('persistCode', initialCode)
}, initialCode)
await page.setBodyDimensions({ width: 1000, height: 500 })
await homePage.goToModelingScene()
await scene.waitForExecutionDone()
// One dumb hardcoded screen pixel value
const testPoint = { x: 550, y: 295 }
const [clickOnCap] = scene.makeMouseHelpers(testPoint.x, testPoint.y)
const shellDeclaration = `shell001 = shell({ faces = ['end'], thickness = 5 }, ${
hasExtrudesInPipe ? 'sketch002' : 'extrude002'
})`
await test.step(`Look for the grey of the shape`, async () => {
await toolbar.closePane('code')
await scene.expectPixelColor([128, 128, 128], testPoint, 15)
})
await test.step(`Go through the command bar flow, selecting a cap and keeping default thickness`, async () => {
await toolbar.shellButton.click()
await cmdBar.expectState({
stage: 'arguments',
currentArgKey: 'selection',
currentArgValue: '',
headerArguments: {
Selection: '',
Thickness: '',
},
highlightedHeaderArg: 'selection',
commandName: 'Shell',
})
await clickOnCap()
await page.waitForTimeout(500)
await cmdBar.progressCmdBar()
await page.waitForTimeout(500)
await cmdBar.progressCmdBar()
await page.waitForTimeout(500)
await cmdBar.expectState({
stage: 'review',
headerArguments: {
Selection: '1 cap',
Thickness: '5',
},
commandName: 'Shell',
})
await cmdBar.progressCmdBar()
})
await test.step(`Confirm code is added to the editor, scene has changed`, async () => {
await toolbar.openPane('code')
await editor.expectEditor.toContain(shellDeclaration)
await editor.expectState({
diagnostics: [],
activeLines: [shellDeclaration],
highlightedCode: '',
})
await toolbar.closePane('code')
await scene.expectPixelColor([73, 73, 73], testPoint, 15)
})
})
}) })

View File

@ -115,7 +115,7 @@ test(
) )
test( test(
'yyyyyyyyy open a file in a project works and renders, open another file in different project with errors, it should clear the scene', 'open a file in a project works and renders, open another file in different project with errors, it should clear the scene',
{ tag: '@electron' }, { tag: '@electron' },
async ({ context, page }, testInfo) => { async ({ context, page }, testInfo) => {
await context.folderSetupFn(async (dir) => { await context.folderSetupFn(async (dir) => {
@ -199,7 +199,7 @@ test(
) )
test( test(
'aaayyyyyyyy open a file in a project works and renders, open another file in different project that is empty, it should clear the scene', 'open a file in a project works and renders, open another file in different project that is empty, it should clear the scene',
{ tag: '@electron' }, { tag: '@electron' },
async ({ context, page }, testInfo) => { async ({ context, page }, testInfo) => {
await context.folderSetupFn(async (dir) => { await context.folderSetupFn(async (dir) => {
@ -276,7 +276,7 @@ test(
) )
test( test(
'nooooooooooooo open a file in a project works and renders, open empty file, it should clear the scene', 'open a file in a project works and renders, open empty file, it should clear the scene',
{ tag: '@electron' }, { tag: '@electron' },
async ({ context, page }, testInfo) => { async ({ context, page }, testInfo) => {
await context.folderSetupFn(async (dir) => { await context.folderSetupFn(async (dir) => {
@ -1885,3 +1885,48 @@ test.fixme(
}) })
} }
) )
test(
'project name with foreign characters should open',
{ tag: '@electron' },
async ({ context, page }, testInfo) => {
await context.folderSetupFn(async (dir) => {
const bracketDir = path.join(dir, 'اَلْعَرَبِيَّةُ')
await fsp.mkdir(bracketDir, { recursive: true })
await fsp.copyFile(
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
path.join(bracketDir, 'main.kcl')
)
await fsp.writeFile(path.join(bracketDir, 'empty.kcl'), '')
})
await page.setBodyDimensions({ width: 1200, height: 500 })
const u = await getUtils(page)
page.on('console', console.log)
const pointOnModel = { x: 630, y: 280 }
await test.step('Opening the اَلْعَرَبِيَّةُ project should load the stream', async () => {
// expect to see the text bracket
await expect(page.getByText('اَلْعَرَبِيَّةُ')).toBeVisible()
await page.getByText('اَلْعَرَبِيَّةُ').click()
await expect(
page.getByRole('button', { name: 'Start Sketch' })
).toBeEnabled({
timeout: 20_000,
})
// gray at this pixel means the stream has loaded in the most
// user way we can verify it (pixel color)
await expect
.poll(() => u.getGreatestPixDiff(pointOnModel, [85, 85, 85]), {
timeout: 10_000,
})
.toBeLessThan(15)
})
}
)

View File

@ -614,6 +614,38 @@ extrude001 = extrude(50, sketch001)
await expect(gizmo).toBeVisible() await expect(gizmo).toBeVisible()
}) })
}) })
test(`Refreshing the app doesn't cause the stream to pause on long-executing files`, async ({
context,
homePage,
scene,
toolbar,
viewport,
}) => {
await context.folderSetupFn(async (dir) => {
const legoDir = path.join(dir, 'lego')
await fsp.mkdir(legoDir, { recursive: true })
await fsp.copyFile(
executorInputPath('lego.kcl'),
path.join(legoDir, 'main.kcl')
)
})
await test.step(`Test setup`, async () => {
await homePage.openProject('lego')
await toolbar.closePane('code')
})
await test.step(`Waiting for the loading spinner to disappear`, async () => {
await scene.loadingIndicator.waitFor({ state: 'detached' })
})
await test.step(`The part should start loading quickly, not waiting until execution is complete`, async () => {
await scene.expectPixelColor(
[143, 143, 143],
{ x: (viewport?.width ?? 1200) / 2, y: (viewport?.height ?? 500) / 2 },
15
)
})
})
}) })
async function clickExportButton(page: Page) { async function clickExportButton(page: Page) {

View File

@ -39,8 +39,8 @@ test.describe('Sketch tests', () => {
${startProfileAt1} ${startProfileAt1}
|> arc({ |> arc({
radius = screwRadius, radius = screwRadius,
angle_start = 0, angleStart = 0,
angle_end = 360 angleEnd = 360
}, %) }, %)
part001 = startSketchOn('XY') part001 = startSketchOn('XY')
@ -60,8 +60,8 @@ test.describe('Sketch tests', () => {
|> yLine(wireOffset, %) |> yLine(wireOffset, %)
|> arc({ |> arc({
radius = wireRadius, radius = wireRadius,
angle_start = 0, angleStart = 0,
angle_end = 180 angleEnd = 180
}, %) }, %)
|> yLine(-wireOffset, %) |> yLine(-wireOffset, %)
|> xLine(-width / 4, %) |> xLine(-width / 4, %)

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

View File

@ -8,8 +8,8 @@ import { UnitLength_type } from '@kittycad/lib/dist/types/src/models'
test.describe('Testing in-app sample loading', () => { test.describe('Testing in-app sample loading', () => {
/** /**
* Note this test implicitly depends on the KCL sample "car-wheel.kcl", * Note this test implicitly depends on the KCL sample "a-parametric-bearing-pillow-block",
* its title, and its units settings. https://github.com/KittyCAD/kcl-samples/blob/main/car-wheel/car-wheel.kcl * its title, and its units settings. https://github.com/KittyCAD/kcl-samples/blob/main/a-parametric-bearing-pillow-block/main.kcl
*/ */
test('Web: should overwrite current code, cannot create new file', async ({ test('Web: should overwrite current code, cannot create new file', async ({
editor, editor,
@ -29,8 +29,8 @@ test.describe('Testing in-app sample loading', () => {
// Locators and constants // Locators and constants
const newSample = { const newSample = {
file: 'car-wheel' + FILE_EXT, file: 'a-parametric-bearing-pillow-block' + FILE_EXT,
title: 'Car Wheel', title: 'A Parametric Bearing Pillow Block',
} }
const commandBarButton = page.getByRole('button', { name: 'Commands' }) const commandBarButton = page.getByRole('button', { name: 'Commands' })
const samplesCommandOption = page.getByRole('option', { const samplesCommandOption = page.getByRole('option', {
@ -75,8 +75,8 @@ test.describe('Testing in-app sample loading', () => {
/** /**
* Note this test implicitly depends on the KCL samples: * Note this test implicitly depends on the KCL samples:
* "car-wheel.kcl": https://github.com/KittyCAD/kcl-samples/blob/main/car-wheel/car-wheel.kcl * "a-parametric-bearing-pillow-block": https://github.com/KittyCAD/kcl-samples/blob/main/a-parametric-bearing-pillow-block/main.kcl
* "gear-rack.kcl": https://github.com/KittyCAD/kcl-samples/blob/main/gear-rack/gear-rack.kcl * "gear-rack": https://github.com/KittyCAD/kcl-samples/blob/main/gear-rack/main.kcl
*/ */
test( test(
'Desktop: should create new file by default, optionally overwrite', 'Desktop: should create new file by default, optionally overwrite',
@ -93,8 +93,8 @@ test.describe('Testing in-app sample loading', () => {
// Locators and constants // Locators and constants
const sampleOne = { const sampleOne = {
file: 'car-wheel' + FILE_EXT, file: 'a-parametric-bearing-pillow-block' + FILE_EXT,
title: 'Car Wheel', title: 'A Parametric Bearing Pillow Block',
} }
const sampleTwo = { const sampleTwo = {
file: 'gear-rack' + FILE_EXT, file: 'gear-rack' + FILE_EXT,

View File

@ -389,25 +389,25 @@ test.describe('Testing selections', () => {
await expect(u.codeLocator).toContainText(`sketch005 = startSketchOn({ await expect(u.codeLocator).toContainText(`sketch005 = startSketchOn({
plane = { plane = {
origin = { x = 0, y = -50, z = 0 }, origin = { x = 0, y = -50, z = 0 },
x_axis = { x = 1, y = 0, z = 0 }, xAxis = { x = 1, y = 0, z = 0 },
y_axis = { x = 0, y = 0, z = 1 }, yAxis = { x = 0, y = 0, z = 1 },
z_axis = { x = 0, y = -1, z = 0 } zAxis = { x = 0, y = -1, z = 0 }
} }
})`) })`)
await expect(u.codeLocator).toContainText(`sketch003 = startSketchOn({ await expect(u.codeLocator).toContainText(`sketch003 = startSketchOn({
plane = { plane = {
origin = { x = 116.53, y = 0, z = 163.25 }, origin = { x = 116.53, y = 0, z = 163.25 },
x_axis = { x = -0.81, y = 0, z = 0.58 }, xAxis = { x = -0.81, y = 0, z = 0.58 },
y_axis = { x = 0, y = -1, z = 0 }, yAxis = { x = 0, y = -1, z = 0 },
z_axis = { x = 0.58, y = 0, z = 0.81 } zAxis = { x = 0.58, y = 0, z = 0.81 }
} }
})`) })`)
await expect(u.codeLocator).toContainText(`sketch002 = startSketchOn({ await expect(u.codeLocator).toContainText(`sketch002 = startSketchOn({
plane = { plane = {
origin = { x = -91.74, y = 0, z = 80.89 }, origin = { x = -91.74, y = 0, z = 80.89 },
x_axis = { x = -0.66, y = 0, z = -0.75 }, xAxis = { x = -0.66, y = 0, z = -0.75 },
y_axis = { x = 0, y = -1, z = 0 }, yAxis = { x = 0, y = -1, z = 0 },
z_axis = { x = -0.75, y = 0, z = 0.66 } zAxis = { x = -0.75, y = 0, z = 0.66 }
} }
})`) })`)

18
flake.lock generated
View File

@ -2,11 +2,11 @@
"nodes": { "nodes": {
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1721933792, "lastModified": 1736320768,
"narHash": "sha256-zYVwABlQnxpbaHMfX6Wt9jhyQstFYwN2XjleOJV3VVg=", "narHash": "sha256-nIYdTAiKIGnFNugbomgBJR+Xv5F1ZQU+HfaBqJKroC0=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "2122a9b35b35719ad9a395fe783eabb092df01b1", "rev": "4bc9c909d9ac828a039f288cf872d16d38185db8",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -18,11 +18,11 @@
}, },
"nixpkgs_2": { "nixpkgs_2": {
"locked": { "locked": {
"lastModified": 1718428119, "lastModified": 1728538411,
"narHash": "sha256-WdWDpNaq6u1IPtxtYHHWpl5BmabtpmLnMAx0RdJ/vo8=", "narHash": "sha256-f0SBJz1eZ2yOuKUr5CA9BHULGXVSn6miBuUWdTyhUhU=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "e6cea36f83499eb4e9cd184c8a8e823296b50ad5", "rev": "b69de56fac8c2b6f8fd27f2eca01dcda8e0a4221",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -43,11 +43,11 @@
"nixpkgs": "nixpkgs_2" "nixpkgs": "nixpkgs_2"
}, },
"locked": { "locked": {
"lastModified": 1721960387, "lastModified": 1736476219,
"narHash": "sha256-o21ax+745ETGXrcgc/yUuLw1SI77ymp3xEpJt+w/kks=", "narHash": "sha256-+qyv3QqdZCdZ3cSO/cbpEY6tntyYjfe1bB12mdpNFaY=",
"owner": "oxalica", "owner": "oxalica",
"repo": "rust-overlay", "repo": "rust-overlay",
"rev": "9cbf831c5b20a53354fc12758abd05966f9f1699", "rev": "de30cc5963da22e9742bbbbb9a3344570ed237b9",
"type": "github" "type": "github"
}, },
"original": { "original": {

1
interface.d.ts vendored
View File

@ -93,5 +93,6 @@ export interface IElectronAPI {
declare global { declare global {
interface Window { interface Window {
electron: IElectronAPI electron: IElectronAPI
openExternalLink: (e: React.MouseEvent<HTMLAnchorElement>) => void
} }
} }

View File

@ -15,7 +15,7 @@
"@codemirror/autocomplete": "^6.17.0", "@codemirror/autocomplete": "^6.17.0",
"@codemirror/commands": "^6.6.0", "@codemirror/commands": "^6.6.0",
"@codemirror/language": "^6.10.3", "@codemirror/language": "^6.10.3",
"@codemirror/lint": "^6.8.1", "@codemirror/lint": "^6.8.4",
"@codemirror/search": "^6.5.6", "@codemirror/search": "^6.5.6",
"@codemirror/state": "^6.4.1", "@codemirror/state": "^6.4.1",
"@codemirror/theme-one-dark": "^6.1.2", "@codemirror/theme-one-dark": "^6.1.2",
@ -26,7 +26,7 @@
"@fortawesome/react-fontawesome": "^0.2.0", "@fortawesome/react-fontawesome": "^0.2.0",
"@headlessui/react": "^1.7.19", "@headlessui/react": "^1.7.19",
"@headlessui/tailwindcss": "^0.2.0", "@headlessui/tailwindcss": "^0.2.0",
"@kittycad/lib": "2.0.12", "@kittycad/lib": "2.0.13",
"@lezer/highlight": "^1.2.1", "@lezer/highlight": "^1.2.1",
"@lezer/lr": "^1.4.1", "@lezer/lr": "^1.4.1",
"@react-hook/resize-observer": "^2.0.1", "@react-hook/resize-observer": "^2.0.1",
@ -52,13 +52,13 @@
"react": "^18.3.1", "react": "^18.3.1",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-hot-toast": "^2.4.1", "react-hot-toast": "^2.4.1",
"react-hotkeys-hook": "^4.5.1", "react-hotkeys-hook": "^4.6.1",
"react-json-view": "^1.21.3", "react-json-view": "^1.21.3",
"react-modal": "^3.16.1", "react-modal": "^3.16.3",
"react-modal-promise": "^1.0.2", "react-modal-promise": "^1.0.2",
"react-router-dom": "^6.28.0", "react-router-dom": "^6.28.0",
"sketch-helpers": "^0.0.4", "sketch-helpers": "^0.0.4",
"three": "^0.166.1", "three": "^0.172.0",
"ua-parser-js": "^1.0.37", "ua-parser-js": "^1.0.37",
"uuid": "^11.0.2", "uuid": "^11.0.2",
"vscode-jsonrpc": "^8.2.1", "vscode-jsonrpc": "^8.2.1",
@ -91,8 +91,8 @@
"build:wasm": "yarn wasm-prep && cd src/wasm-lib && wasm-pack build --release --target web --out-dir pkg && cargo test -p kcl-lib export_bindings && cd ../.. && yarn isomorphic-copy-wasm && yarn fmt", "build:wasm": "yarn wasm-prep && cd src/wasm-lib && wasm-pack build --release --target web --out-dir pkg && cargo test -p kcl-lib export_bindings && cd ../.. && yarn isomorphic-copy-wasm && yarn fmt",
"remove-importmeta": "sed -i 's/import.meta.url/window.location.origin/g' \"./src/wasm-lib/pkg/wasm_lib.js\"; sed -i '' 's/import.meta.url/window.location.origin/g' \"./src/wasm-lib/pkg/wasm_lib.js\" || echo \"sed for both mac and linux\"", "remove-importmeta": "sed -i 's/import.meta.url/window.location.origin/g' \"./src/wasm-lib/pkg/wasm_lib.js\"; sed -i '' 's/import.meta.url/window.location.origin/g' \"./src/wasm-lib/pkg/wasm_lib.js\" || echo \"sed for both mac and linux\"",
"wasm-prep": "rimraf src/wasm-lib/pkg && mkdirp src/wasm-lib/pkg && rimraf src/wasm-lib/kcl/bindings", "wasm-prep": "rimraf src/wasm-lib/pkg && mkdirp src/wasm-lib/pkg && rimraf src/wasm-lib/kcl/bindings",
"lint-fix": "eslint --fix src e2e packages/codemirror-lsp-client", "lint-fix": "eslint --fix --ext .ts --ext .tsx src e2e packages/codemirror-lsp-client/src",
"lint": "eslint --max-warnings 0 src e2e packages/codemirror-lsp-client", "lint": "eslint --max-warnings 0 --ext .ts --ext .tsx src e2e packages/codemirror-lsp-client/src",
"files:set-version": "echo \"$(jq --arg v \"$VERSION\" '.version=$v' package.json --indent 2)\" > package.json", "files:set-version": "echo \"$(jq --arg v \"$VERSION\" '.version=$v' package.json --indent 2)\" > package.json",
"files:set-notes": "./scripts/set-files-notes.sh", "files:set-notes": "./scripts/set-files-notes.sh",
"files:flip-to-nightly": "./scripts/flip-files-to-nightly.sh", "files:flip-to-nightly": "./scripts/flip-files-to-nightly.sh",
@ -149,7 +149,7 @@
"@electron-forge/plugin-vite": "7.4.0", "@electron-forge/plugin-vite": "7.4.0",
"@electron/fuses": "1.8.0", "@electron/fuses": "1.8.0",
"@iarna/toml": "^2.2.5", "@iarna/toml": "^2.2.5",
"@lezer/generator": "^1.7.1", "@lezer/generator": "^1.7.2",
"@nabla/vite-plugin-eslint": "^2.0.5", "@nabla/vite-plugin-eslint": "^2.0.5",
"@playwright/test": "^1.49.0", "@playwright/test": "^1.49.0",
"@testing-library/jest-dom": "^5.14.1", "@testing-library/jest-dom": "^5.14.1",
@ -166,13 +166,11 @@
"@types/react": "^18.3.4", "@types/react": "^18.3.4",
"@types/react-dom": "^18.3.1", "@types/react-dom": "^18.3.1",
"@types/react-modal": "^3.16.3", "@types/react-modal": "^3.16.3",
"@types/three": "^0.163.0", "@types/three": "^0.172.0",
"@types/ua-parser-js": "^0.7.39", "@types/ua-parser-js": "^0.7.39",
"@types/uuid": "^9.0.8", "@types/uuid": "^9.0.8",
"@types/wicg-file-system-access": "^2023.10.5", "@types/wicg-file-system-access": "^2023.10.5",
"@types/ws": "^8.5.13", "@types/ws": "^8.5.13",
"@typescript-eslint/eslint-plugin": "^5.0.0",
"@typescript-eslint/parser": "^5.0.0",
"@vitejs/plugin-react": "^4.3.0", "@vitejs/plugin-react": "^4.3.0",
"@vitest/web-worker": "^1.5.0", "@vitest/web-worker": "^1.5.0",
"@xstate/cli": "^0.5.17", "@xstate/cli": "^0.5.17",
@ -182,11 +180,15 @@
"electron-builder": "24.13.3", "electron-builder": "24.13.3",
"electron-notarize": "1.2.2", "electron-notarize": "1.2.2",
"eslint": "^8.0.1", "eslint": "^8.0.1",
"eslint-config-react-app": "^7.0.1",
"eslint-plugin-css-modules": "^2.12.0", "eslint-plugin-css-modules": "^2.12.0",
"eslint-plugin-import": "^2.30.0", "eslint-plugin-import": "^2.30.0",
"eslint-plugin-jest": "^28.10.0",
"eslint-plugin-jsx-a11y": "^6.10.2",
"eslint-plugin-react": "^7.37.3",
"eslint-plugin-react-hooks": "^5.1.0",
"eslint-plugin-suggest-no-throw": "^1.0.0", "eslint-plugin-suggest-no-throw": "^1.0.0",
"happy-dom": "^15.11.7", "eslint-plugin-testing-library": "^7.1.1",
"happy-dom": "^16.3.0",
"http-server": "^14.1.1", "http-server": "^14.1.1",
"husky": "^9.1.5", "husky": "^9.1.5",
"kill-port": "^2.0.1", "kill-port": "^2.0.1",
@ -200,6 +202,7 @@
"tailwindcss": "^3.4.1", "tailwindcss": "^3.4.1",
"ts-node": "^10.0.0", "ts-node": "^10.0.0",
"typescript": "^5.7.2", "typescript": "^5.7.2",
"typescript-eslint": "^8.19.1",
"vite": "^5.4.6", "vite": "^5.4.6",
"vite-plugin-package-version": "^1.1.0", "vite-plugin-package-version": "^1.1.0",
"vite-tsconfig-paths": "^4.3.2", "vite-tsconfig-paths": "^4.3.2",

View File

@ -4,4 +4,5 @@ dist
tsconfig.tsbuildinfo tsconfig.tsbuildinfo
*.d.ts *.d.ts
*.js *.js
!postcss.config.js
!rollup.config.js !rollup.config.js

View File

@ -28,6 +28,7 @@
"@rollup/plugin-typescript": "^12.1.2", "@rollup/plugin-typescript": "^12.1.2",
"rollup": "^4.29.1", "rollup": "^4.29.1",
"rollup-plugin-dts": "^6.1.1", "rollup-plugin-dts": "^6.1.1",
"vite-tsconfig-paths": "^4.3.2",
"vitest": "^2.1.8" "vitest": "^2.1.8"
}, },
"files": [ "files": [

View File

@ -0,0 +1 @@
// This is here to prevent using the one in the root of the project.

View File

@ -85,7 +85,7 @@ commaSep1NoTrailingComma<term> { term ("," term)* }
@tokens { @tokens {
String[isolate] { "'" ("\\" _ | !['\\])* "'" | '"' ("\\" _ | !["\\])* '"' } String[isolate] { "'" ("\\" _ | !['\\])* "'" | '"' ("\\" _ | !["\\])* '"' }
Number { "." @digit+ | @digit+ ("." @digit*)? } Number { "." @digit+ | @digit+ ("." @digit+)? }
@precedence { Number, "." } @precedence { Number, "." }
AddOp { "+" | "-" } AddOp { "+" | "-" }

View File

@ -0,0 +1,43 @@
# spaced
a = [0 .. 1]
==>
Program(VariableDeclaration(VariableDefinition,
Equals,
ArrayExpression(IntegerRange(Number,
Number))))
# compact
a = [0..1]
==>
Program(VariableDeclaration(VariableDefinition,
Equals,
ArrayExpression(IntegerRange(Number,
Number))))
# expr spaced
a = [start .. start + 10]
==>
Program(VariableDeclaration(VariableDefinition,
Equals,
ArrayExpression(IntegerRange(VariableName,
BinaryExpression(VariableName,
AddOp,
Number)))))
# expr compact
a = [start..start + 10]
==>
Program(VariableDeclaration(VariableDefinition,
Equals,
ArrayExpression(IntegerRange(VariableName,
BinaryExpression(VariableName,
AddOp,
Number)))))

View File

@ -398,7 +398,7 @@ check-error@^2.1.1:
resolved "https://registry.yarnpkg.com/check-error/-/check-error-2.1.1.tgz#87eb876ae71ee388fa0471fe423f494be1d96ccc" resolved "https://registry.yarnpkg.com/check-error/-/check-error-2.1.1.tgz#87eb876ae71ee388fa0471fe423f494be1d96ccc"
integrity sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw== integrity sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==
debug@^4.3.7: debug@^4.1.1, debug@^4.3.7:
version "4.4.0" version "4.4.0"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.0.tgz#2b3f2aea2ffeb776477460267377dc8710faba8a" resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.0.tgz#2b3f2aea2ffeb776477460267377dc8710faba8a"
integrity sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA== integrity sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==
@ -471,6 +471,11 @@ function-bind@^1.1.2:
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c"
integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==
globrex@^0.1.2:
version "0.1.2"
resolved "https://registry.yarnpkg.com/globrex/-/globrex-0.1.2.tgz#dd5d9ec826232730cd6793a5e33a9302985e6098"
integrity sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==
hasown@^2.0.2: hasown@^2.0.2:
version "2.0.2" version "2.0.2"
resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003"
@ -647,6 +652,11 @@ tinyspy@^3.0.2:
resolved "https://registry.yarnpkg.com/tinyspy/-/tinyspy-3.0.2.tgz#86dd3cf3d737b15adcf17d7887c84a75201df20a" resolved "https://registry.yarnpkg.com/tinyspy/-/tinyspy-3.0.2.tgz#86dd3cf3d737b15adcf17d7887c84a75201df20a"
integrity sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q== integrity sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==
tsconfck@^3.0.3:
version "3.1.4"
resolved "https://registry.yarnpkg.com/tsconfck/-/tsconfck-3.1.4.tgz#de01a15334962e2feb526824339b51be26712229"
integrity sha512-kdqWFGVJqe+KGYvlSO9NIaWn9jT1Ny4oKVzAJsKii5eoE9snzTJzL4+MMVOMn+fikWGFmKEylcXL710V/kIPJQ==
typescript@^5.7.2: typescript@^5.7.2:
version "5.7.2" version "5.7.2"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.7.2.tgz#3169cf8c4c8a828cde53ba9ecb3d2b1d5dd67be6" resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.7.2.tgz#3169cf8c4c8a828cde53ba9ecb3d2b1d5dd67be6"
@ -663,6 +673,15 @@ vite-node@2.1.8:
pathe "^1.1.2" pathe "^1.1.2"
vite "^5.0.0" vite "^5.0.0"
vite-tsconfig-paths@^4.3.2:
version "4.3.2"
resolved "https://registry.yarnpkg.com/vite-tsconfig-paths/-/vite-tsconfig-paths-4.3.2.tgz#321f02e4b736a90ff62f9086467faf4e2da857a9"
integrity sha512-0Vd/a6po6Q+86rPlntHye7F31zA2URZMbH8M3saAZ/xR9QoGN/L21bxEGfXdWmFdNkqPpRdxFT7nmNe12e9/uA==
dependencies:
debug "^4.1.1"
globrex "^0.1.2"
tsconfck "^3.0.3"
vite@^5.0.0: vite@^5.0.0:
version "5.4.11" version "5.4.11"
resolved "https://registry.yarnpkg.com/vite/-/vite-5.4.11.tgz#3b415cd4aed781a356c1de5a9ebafb837715f6e5" resolved "https://registry.yarnpkg.com/vite/-/vite-5.4.11.tgz#3b415cd4aed781a356c1de5a9ebafb837715f6e5"

View File

@ -42,7 +42,7 @@ export default class StreamDemuxer extends Queue<Uint8Array> {
// try to parse the content-length from the headers // try to parse the content-length from the headers
const length = parseInt(match[1]) const length = parseInt(match[1])
if (isNaN(length)) if (Number.isNaN(length))
return Promise.reject(new Error('invalid content length')) return Promise.reject(new Error('invalid content length'))
// slice the headers since we now have the content length // slice the headers since we now have the content length

View File

@ -368,13 +368,20 @@ export class LanguageServerPlugin implements PluginValue {
sortText, sortText,
filterText, filterText,
}) => { }) => {
const detailText = [
deprecated ? 'Deprecated' : undefined,
labelDetails ? labelDetails.detail : detail,
]
// Don't let undefined appear.
.filter(Boolean)
.join(' ')
const completion: Completion & { const completion: Completion & {
filterText: string filterText: string
sortText?: string sortText?: string
apply: string apply: string
} = { } = {
label, label,
detail: labelDetails ? labelDetails.detail : detail, detail: detailText,
apply: label, apply: label,
type: kind && CompletionItemKindMap[kind].toLowerCase(), type: kind && CompletionItemKindMap[kind].toLowerCase(),
sortText: sortText ?? label, sortText: sortText ?? label,
@ -382,7 +389,11 @@ export class LanguageServerPlugin implements PluginValue {
} }
if (documentation) { if (documentation) {
completion.info = () => { completion.info = () => {
const htmlString = formatMarkdownContents(documentation) const deprecatedHtml = deprecated
? '<p><strong>Deprecated</strong></p>'
: ''
const htmlString =
deprecatedHtml + formatMarkdownContents(documentation)
const htmlNode = document.createElement('div') const htmlNode = document.createElement('div')
htmlNode.style.display = 'contents' htmlNode.style.display = 'contents'
htmlNode.innerHTML = htmlString htmlNode.innerHTML = htmlString

View File

@ -32,10 +32,9 @@ export default defineConfig({
}, },
projects: [ projects: [
{ {
name: 'Google Chrome', name: 'chromium',
use: { use: {
...devices['Desktop Chrome'], ...devices['Desktop Chrome'],
channel: 'chrome',
contextOptions: { contextOptions: {
/* Chromium is the only one with these permission types */ /* Chromium is the only one with these permission types */
permissions: ['clipboard-write', 'clipboard-read'], permissions: ['clipboard-write', 'clipboard-read'],

View File

@ -1,166 +1,211 @@
[ [
{ {
"file": "80-20-rail.kcl", "file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "80-20-rail/main.kcl",
"multipleFiles": false,
"title": "80/20 Rail", "title": "80/20 Rail",
"description": "An 80/20 extruded aluminum linear rail. T-slot profile adjustable by profile height, rail length, and origin position" "description": "An 80/20 extruded aluminum linear rail. T-slot profile adjustable by profile height, rail length, and origin position"
}, },
{ {
"file": "a-parametric-bearing-pillow-block.kcl", "file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "a-parametric-bearing-pillow-block/main.kcl",
"multipleFiles": false,
"title": "A Parametric Bearing Pillow Block", "title": "A Parametric Bearing Pillow Block",
"description": "A bearing pillow block, also known as a plummer block or pillow block bearing, is a pedestal used to provide support for a rotating shaft with the help of compatible bearings and various accessories. Housing a bearing, the pillow block provides a secure and stable foundation that allows the shaft to rotate smoothly within its machinery setup. These components are essential in a wide range of mechanical systems and machinery, playing a key role in reducing friction and supporting radial and axial loads." "description": "A bearing pillow block, also known as a plummer block or pillow block bearing, is a pedestal used to provide support for a rotating shaft with the help of compatible bearings and various accessories. Housing a bearing, the pillow block provides a secure and stable foundation that allows the shaft to rotate smoothly within its machinery setup. These components are essential in a wide range of mechanical systems and machinery, playing a key role in reducing friction and supporting radial and axial loads."
}, },
{ {
"file": "ball-bearing.kcl", "file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "ball-bearing/main.kcl",
"multipleFiles": false,
"title": "Ball Bearing", "title": "Ball Bearing",
"description": "A ball bearing is a type of rolling-element bearing that uses balls to maintain the separation between the bearing races. The primary purpose of a ball bearing is to reduce rotational friction and support radial and axial loads." "description": "A ball bearing is a type of rolling-element bearing that uses balls to maintain the separation between the bearing races. The primary purpose of a ball bearing is to reduce rotational friction and support radial and axial loads."
}, },
{ {
"file": "bracket.kcl", "file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "bracket/main.kcl",
"multipleFiles": false,
"title": "Shelf Bracket", "title": "Shelf Bracket",
"description": "This is a bracket that holds a shelf. It is made of aluminum and is designed to hold a force of 300 lbs. The bracket is 6 inches wide and the force is applied at the end of the shelf, 12 inches from the wall. The bracket has a factor of safety of 1.2. The legs of the bracket are 5 inches and 2 inches long. The thickness of the bracket is calculated from the constraints provided." "description": "This is a bracket that holds a shelf. It is made of aluminum and is designed to hold a force of 300 lbs. The bracket is 6 inches wide and the force is applied at the end of the shelf, 12 inches from the wall. The bracket has a factor of safety of 1.2. The legs of the bracket are 5 inches and 2 inches long. The thickness of the bracket is calculated from the constraints provided."
}, },
{ {
"file": "brake-caliper.kcl", "file": "main.kcl",
"title": "Brake Caliper", "pathFromProjectDirectoryToFirstFile": "car-wheel-assembly/main.kcl",
"description": "Brake calipers are used to squeeze the brake pads against the rotor, causing larger and larger amounts of friction depending on how hard the brakes are pressed." "multipleFiles": true,
},
{
"file": "car-wheel.kcl",
"title": "Car Wheel",
"description": "A sports car wheel with a circular lug pattern and spokes."
},
{
"file": "car-wheel-assembly.kcl",
"title": "Car Wheel Assembly", "title": "Car Wheel Assembly",
"description": "A car wheel assembly with a rotor, tire, and lug nuts." "description": "A car wheel assembly with a rotor, tire, and lug nuts."
}, },
{ {
"file": "dodecahedron.kcl", "file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "dodecahedron/main.kcl",
"multipleFiles": false,
"title": "Hollow Dodecahedron", "title": "Hollow Dodecahedron",
"description": "A regular dodecahedron or pentagonal dodecahedron is a dodecahedron composed of regular pentagonal faces, three meeting at each vertex. This example shows constructing the individual faces of the dodecahedron and extruding inwards." "description": "A regular dodecahedron or pentagonal dodecahedron is a dodecahedron composed of regular pentagonal faces, three meeting at each vertex. This example shows constructing the individual faces of the dodecahedron and extruding inwards."
}, },
{ {
"file": "enclosure.kcl", "file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "enclosure/main.kcl",
"multipleFiles": false,
"title": "Enclosure", "title": "Enclosure",
"description": "An enclosure body and sealing lid for storing items" "description": "An enclosure body and sealing lid for storing items"
}, },
{ {
"file": "flange-with-patterns.kcl", "file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "flange-with-patterns/main.kcl",
"multipleFiles": false,
"title": "Flange", "title": "Flange",
"description": "A flange is a flat rim, collar, or rib, typically forged or cast, that is used to strengthen an object, guide it, or attach it to another object. Flanges are known for their use in various applications, including piping, plumbing, and mechanical engineering, among others." "description": "A flange is a flat rim, collar, or rib, typically forged or cast, that is used to strengthen an object, guide it, or attach it to another object. Flanges are known for their use in various applications, including piping, plumbing, and mechanical engineering, among others."
}, },
{ {
"file": "flange-xy.kcl", "file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "flange-xy/main.kcl",
"multipleFiles": false,
"title": "Flange with XY coordinates", "title": "Flange with XY coordinates",
"description": "A flange is a flat rim, collar, or rib, typically forged or cast, that is used to strengthen an object, guide it, or attach it to another object. Flanges are known for their use in various applications, including piping, plumbing, and mechanical engineering, among others." "description": "A flange is a flat rim, collar, or rib, typically forged or cast, that is used to strengthen an object, guide it, or attach it to another object. Flanges are known for their use in various applications, including piping, plumbing, and mechanical engineering, among others."
}, },
{ {
"file": "focusrite-scarlett-mounting-bracket.kcl", "file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "focusrite-scarlett-mounting-bracket/main.kcl",
"multipleFiles": false,
"title": "A mounting bracket for the Focusrite Scarlett Solo audio interface", "title": "A mounting bracket for the Focusrite Scarlett Solo audio interface",
"description": "This is a bracket that holds an audio device underneath a desk or shelf. The audio device has dimensions of 144mm wide, 80mm length and 45mm depth with fillets of 6mm. This mounting bracket is designed to be 3D printed with PLA material" "description": "This is a bracket that holds an audio device underneath a desk or shelf. The audio device has dimensions of 144mm wide, 80mm length and 45mm depth with fillets of 6mm. This mounting bracket is designed to be 3D printed with PLA material"
}, },
{ {
"file": "food-service-spatula.kcl", "file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "food-service-spatula/main.kcl",
"multipleFiles": false,
"title": "Food Service Spatula", "title": "Food Service Spatula",
"description": "Use these spatulas for mixing, flipping, and scraping." "description": "Use these spatulas for mixing, flipping, and scraping."
}, },
{ {
"file": "french-press.kcl", "file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "french-press/main.kcl",
"multipleFiles": false,
"title": "French Press", "title": "French Press",
"description": "A french press immersion coffee maker" "description": "A french press immersion coffee maker"
}, },
{ {
"file": "gear.kcl", "file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "gear/main.kcl",
"multipleFiles": false,
"title": "Spur Gear", "title": "Spur Gear",
"description": "A rotating machine part having cut teeth or, in the case of a cogwheel, inserted teeth (called cogs), which mesh with another toothed part to transmit torque. Geared devices can change the speed, torque, and direction of a power source. The two elements that define a gear are its circular shape and the teeth that are integrated into its outer edge, which are designed to fit into the teeth of another gear." "description": "A rotating machine part having cut teeth or, in the case of a cogwheel, inserted teeth (called cogs), which mesh with another toothed part to transmit torque. Geared devices can change the speed, torque, and direction of a power source. The two elements that define a gear are its circular shape and the teeth that are integrated into its outer edge, which are designed to fit into the teeth of another gear."
}, },
{ {
"file": "gear-rack.kcl", "file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "gear-rack/main.kcl",
"multipleFiles": false,
"title": "100mm Gear Rack", "title": "100mm Gear Rack",
"description": "A flat bar or rail that is engraved with teeth along its length. These teeth are designed to mesh with the teeth of a gear, known as a pinion. When the pinion, a small cylindrical gear, rotates, its teeth engage with the teeth on the rack, causing the rack to move linearly. Conversely, linear motion applied to the rack will cause the pinion to rotate." "description": "A flat bar or rail that is engraved with teeth along its length. These teeth are designed to mesh with the teeth of a gear, known as a pinion. When the pinion, a small cylindrical gear, rotates, its teeth engage with the teeth on the rack, causing the rack to move linearly. Conversely, linear motion applied to the rack will cause the pinion to rotate."
}, },
{ {
"file": "hex-nut.kcl", "file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "hex-nut/main.kcl",
"multipleFiles": false,
"title": "Hex nut", "title": "Hex nut",
"description": "A hex nut is a type of fastener with a threaded hole and a hexagonal outer shape, used in a wide variety of applications to secure parts together. The hexagonal shape allows for a greater torque to be applied with wrenches or tools, making it one of the most common nut types in hardware." "description": "A hex nut is a type of fastener with a threaded hole and a hexagonal outer shape, used in a wide variety of applications to secure parts together. The hexagonal shape allows for a greater torque to be applied with wrenches or tools, making it one of the most common nut types in hardware."
}, },
{ {
"file": "i-beam.kcl", "file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "i-beam/main.kcl",
"multipleFiles": false,
"title": "I-beam", "title": "I-beam",
"description": "A structural metal beam with an I shaped cross section. Often used in construction" "description": "A structural metal beam with an I shaped cross section. Often used in construction"
}, },
{ {
"file": "kitt.kcl", "file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "kitt/main.kcl",
"multipleFiles": false,
"title": "Kitt", "title": "Kitt",
"description": "The beloved KittyCAD mascot in a voxelized style." "description": "The beloved KittyCAD mascot in a voxelized style."
}, },
{ {
"file": "lego.kcl", "file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "lego/main.kcl",
"multipleFiles": false,
"title": "Lego Brick", "title": "Lego Brick",
"description": "A standard Lego brick. This is a small, plastic construction block toy that can be interlocked with other blocks to build various structures, models, and figures. There are a lot of hacks used in this code." "description": "A standard Lego brick. This is a small, plastic construction block toy that can be interlocked with other blocks to build various structures, models, and figures. There are a lot of hacks used in this code."
}, },
{ {
"file": "lug-nut.kcl", "file": "main.kcl",
"title": "Lug Nut", "pathFromProjectDirectoryToFirstFile": "mounting-plate/main.kcl",
"description": "lug Nuts are essential components used to create secure connections, whether for electrical purposes, like terminating wires or grounding, or for mechanical purposes, such as providing mounting points or reinforcing structural joints." "multipleFiles": false,
},
{
"file": "mounting-plate.kcl",
"title": "Mounting Plate", "title": "Mounting Plate",
"description": "A flat piece of material, often metal or plastic, that serves as a support or base for attaching, securing, or mounting various types of equipment, devices, or components." "description": "A flat piece of material, often metal or plastic, that serves as a support or base for attaching, securing, or mounting various types of equipment, devices, or components."
}, },
{ {
"file": "multi-axis-robot.kcl", "file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "multi-axis-robot/main.kcl",
"multipleFiles": true,
"title": "Robot Arm", "title": "Robot Arm",
"description": "A 4 axis robotic arm for industrial use. These machines can be used for assembly, packaging, organization of goods, and quality inspection processes" "description": "A 4 axis robotic arm for industrial use. These machines can be used for assembly, packaging, organization of goods, and quality inspection processes"
}, },
{ {
"file": "pipe.kcl", "file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "pipe/main.kcl",
"multipleFiles": false,
"title": "Pipe", "title": "Pipe",
"description": "A tubular section or hollow cylinder, usually but not necessarily of circular cross-section, used mainly to convey substances that can flow." "description": "A tubular section or hollow cylinder, usually but not necessarily of circular cross-section, used mainly to convey substances that can flow."
}, },
{ {
"file": "pipe-flange-assembly.kcl", "file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "pipe-flange-assembly/main.kcl",
"multipleFiles": false,
"title": "Pipe and Flange Assembly", "title": "Pipe and Flange Assembly",
"description": "A crucial component in various piping systems, designed to facilitate the connection, disconnection, and access to piping for inspection, cleaning, and modifications. This assembly combines pipes (long cylindrical conduits) with flanges (plate-like fittings) to create a secure yet detachable joint." "description": "A crucial component in various piping systems, designed to facilitate the connection, disconnection, and access to piping for inspection, cleaning, and modifications. This assembly combines pipes (long cylindrical conduits) with flanges (plate-like fittings) to create a secure yet detachable joint."
}, },
{ {
"file": "pipe-with-bend.kcl", "file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "pipe-with-bend/main.kcl",
"multipleFiles": false,
"title": "Pipe with bend", "title": "Pipe with bend",
"description": "A tubular section or hollow cylinder, usually but not necessarily of circular cross-section, used mainly to convey substances that can flow." "description": "A tubular section or hollow cylinder, usually but not necessarily of circular cross-section, used mainly to convey substances that can flow."
}, },
{ {
"file": "poopy-shoe.kcl", "file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "poopy-shoe/main.kcl",
"multipleFiles": false,
"title": "Poopy Shoe", "title": "Poopy Shoe",
"description": "poop shute for bambu labs printer - optimized for printing." "description": "poop shute for bambu labs printer - optimized for printing."
}, },
{ {
"file": "router-template-cross-bar.kcl", "file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "router-template-cross-bar/main.kcl",
"multipleFiles": false,
"title": "Router template for a cross bar", "title": "Router template for a cross bar",
"description": "A guide for routing a notch into a cross bar." "description": "A guide for routing a notch into a cross bar."
}, },
{ {
"file": "router-template-slate.kcl", "file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "router-template-slate/main.kcl",
"multipleFiles": false,
"title": "Router template for a slate", "title": "Router template for a slate",
"description": "A guide for routing a slate for a cross bar." "description": "A guide for routing a slate for a cross bar."
}, },
{ {
"file": "sheet-metal-bracket.kcl", "file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "sheet-metal-bracket/main.kcl",
"multipleFiles": false,
"title": "Sheet Metal Bracket", "title": "Sheet Metal Bracket",
"description": "A component typically made from flat sheet metal through various manufacturing processes such as bending, punching, cutting, and forming. These brackets are used to support, attach, or mount other hardware components, often providing a structural or functional base for assembly." "description": "A component typically made from flat sheet metal through various manufacturing processes such as bending, punching, cutting, and forming. These brackets are used to support, attach, or mount other hardware components, often providing a structural or functional base for assembly."
}, },
{ {
"file": "socket-head-cap-screw.kcl", "file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "socket-head-cap-screw/main.kcl",
"multipleFiles": false,
"title": "Socket Head Cap Screw", "title": "Socket Head Cap Screw",
"description": "This is for a #10-24 screw that is 1.00 inches long. A socket head cap screw is a type of fastener that is widely used in a variety of applications requiring a high strength fastening solution. It is characterized by its cylindrical head and internal hexagonal drive, which allows for tightening with an Allen wrench or hex key." "description": "This is for a #10-24 screw that is 1.00 inches long. A socket head cap screw is a type of fastener that is widely used in a variety of applications requiring a high strength fastening solution. It is characterized by its cylindrical head and internal hexagonal drive, which allows for tightening with an Allen wrench or hex key."
}, },
{ {
"file": "tire.kcl", "file": "main.kcl",
"title": "Tire", "pathFromProjectDirectoryToFirstFile": "walkie-talkie/main.kcl",
"description": "A tire is a critical component of a vehicle that provides the necessary traction and grip between the car and the road. It supports the vehicle's weight and absorbs shocks from road irregularities." "multipleFiles": true,
"title": "Walkie Talkie",
"description": "A portable, handheld two-way radio device that allows users to communicate wirelessly over short to medium distances. It operates on specific radio frequencies and features a push-to-talk button for transmitting messages, making it ideal for quick and reliable communication in outdoor, work, or emergency settings."
}, },
{ {
"file": "washer.kcl", "file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "washer/main.kcl",
"multipleFiles": false,
"title": "Washer", "title": "Washer",
"description": "A small, typically disk-shaped component with a hole in the middle, used in a wide range of applications, primarily in conjunction with fasteners like bolts and screws. Washers distribute the load of a fastener across a broader area. This is especially important when the fastening surface is soft or uneven, as it helps to prevent damage to the surface and ensures the load is evenly distributed, reducing the risk of the fastener becoming loose over time." "description": "A small, typically disk-shaped component with a hole in the middle, used in a wide range of applications, primarily in conjunction with fasteners like bolts and screws. Washers distribute the load of a fastener across a broader area. This is especially important when the fastening surface is soft or uneven, as it helps to prevent damage to the surface and ensures the load is evenly distributed, reducing the risk of the fastener becoming loose over time."
}, },

View File

@ -57,7 +57,9 @@ export const FileMachineProvider = ({
useEffect(() => { useEffect(() => {
markOnce('code/didLoadFile') markOnce('code/didLoadFile')
async function fetchKclSamples() { async function fetchKclSamples() {
setKclSamples(await getKclSamplesManifest()) const manifest = await getKclSamplesManifest()
const filteredFiles = manifest.filter((file) => !file.multipleFiles)
setKclSamples(filteredFiles)
} }
fetchKclSamples().catch(reportError) fetchKclSamples().catch(reportError)
}, []) }, [])
@ -324,7 +326,7 @@ export const FileMachineProvider = ({
} }
}, },
kclSamples.map((sample) => ({ kclSamples.map((sample) => ({
value: sample.file, value: sample.pathFromProjectDirectoryToFirstFile,
name: sample.title, name: sample.title,
})) }))
).filter( ).filter(

View File

@ -148,6 +148,7 @@ function HelpMenuItem({
return ( return (
<li className="p-0 m-0"> <li className="p-0 m-0">
{as === 'a' ? ( {as === 'a' ? (
// eslint-disable-next-line jsx-a11y/no-static-element-interactions
<a <a
{...(props as React.ComponentProps<'a'>)} {...(props as React.ComponentProps<'a'>)}
onClick={openExternalBrowserIfDesktop( onClick={openExternalBrowserIfDesktop(

View File

@ -157,12 +157,10 @@ export const ModelingMachineProvider = ({
'enable copilot': () => { 'enable copilot': () => {
editorManager.setCopilotEnabled(true) editorManager.setCopilotEnabled(true)
}, },
// tsc reports this typing as perfectly fine, but eslint is complaining. 'sketch exit execute': ({ context: { store } }) => {
// It's actually nonsensical, so I'm quieting. // TODO: Remove this async callback. For some reason eslint wouldn't
// eslint-disable-next-line @typescript-eslint/no-misused-promises // let me disable @typescript-eslint/no-misused-promises for the line.
'sketch exit execute': async ({ ;(async () => {
context: { store },
}): Promise<void> => {
// When cancelling the sketch mode we should disable sketch mode within the engine. // When cancelling the sketch mode we should disable sketch mode within the engine.
await engineCommandManager.sendSceneCommand({ await engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req', type: 'modeling_cmd_req',
@ -190,6 +188,7 @@ export const ModelingMachineProvider = ({
}) })
}) })
.catch(reportRejection) .catch(reportRejection)
})().catch(reportRejection)
}, },
'Set mouse state': assign(({ context, event }) => { 'Set mouse state': assign(({ context, event }) => {
if (event.type !== 'Set mouse state') return {} if (event.type !== 'Set mouse state') return {}
@ -271,6 +270,7 @@ export const ModelingMachineProvider = ({
cmd_id: uuidv4(), cmd_id: uuidv4(),
cmd: { cmd: {
type: 'default_camera_center_to_selection', type: 'default_camera_center_to_selection',
camera_movement: 'vantage',
}, },
}) })
.catch(reportRejection) .catch(reportRejection)

View File

@ -18,6 +18,7 @@ export const KclEditorMenu = ({ children }: PropsWithChildren) => {
return ( return (
<Menu> <Menu>
{/* eslint-disable-next-line jsx-a11y/no-static-element-interactions */}
<div <div
className="relative" className="relative"
onClick={(e) => { onClick={(e) => {

View File

@ -218,20 +218,6 @@ export const Stream = () => {
} }
}, [IDLE, streamState]) }, [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(() => { useEffect(() => {
if ( if (
typeof window === 'undefined' || typeof window === 'undefined' ||
@ -243,9 +229,15 @@ export const Stream = () => {
// The browser complains if we try to load a new stream without pausing first. // The browser complains if we try to load a new stream without pausing first.
// Do not immediately play the stream! // Do not immediately play the stream!
// we instead use a setTimeout to play the stream in the next event loop
try { try {
videoRef.current.srcObject = mediaStream videoRef.current.srcObject = mediaStream
videoRef.current.pause() videoRef.current.pause()
setTimeout(() => {
videoRef.current?.play().catch((e) => {
console.warn('Video playing was prevented', e, videoRef.current)
})
})
} catch (e) { } catch (e) {
console.warn('Attempted to pause stream while play was still loading', e) console.warn('Attempted to pause stream while play was still loading', e)
} }
@ -321,6 +313,7 @@ export const Stream = () => {
} }
return ( return (
// eslint-disable-next-line jsx-a11y/no-static-element-interactions
<div <div
ref={videoWrapperRef} ref={videoWrapperRef}
className="absolute inset-0 z-0" className="absolute inset-0 z-0"

View File

@ -150,4 +150,31 @@ describe('ToastUpdate tests', () => {
expect(restartButton).toBeEnabled() expect(restartButton).toBeEnabled()
expect(dismissButton).toBeEnabled() expect(dismissButton).toBeEnabled()
}) })
test('Happy path: external links render correctly', () => {
const releaseNotesWithBreakingChanges = `
## Some markdown release notes
- [Zoo](https://zoo.dev/)
`
const onRestart = vi.fn()
const onDismiss = vi.fn()
render(
<ToastUpdate
onRestart={onRestart}
onDismiss={onDismiss}
version={testData.version}
releaseNotes={releaseNotesWithBreakingChanges}
/>
)
// Locators and other constants
const zooDev = screen.getByText('Zoo', {
selector: 'a',
})
expect(zooDev).toHaveAttribute('href', 'https://zoo.dev/')
expect(zooDev).toHaveAttribute('target', '_blank')
expect(zooDev).toHaveAttribute('onClick')
})
}) })

View File

@ -1,8 +1,9 @@
import toast from 'react-hot-toast' import toast from 'react-hot-toast'
import { ActionButton } from './ActionButton' import { ActionButton } from './ActionButton'
import { openExternalBrowserIfDesktop } from 'lib/openWindow' import { openExternalBrowserIfDesktop } from 'lib/openWindow'
import { Marked } from '@ts-stack/markdown' import { escape, Marked, MarkedOptions, unescape } from '@ts-stack/markdown'
import { getReleaseUrl } from 'routes/Settings' import { getReleaseUrl } from 'routes/Settings'
import { SafeRenderer } from 'lib/markdown'
export function ToastUpdate({ export function ToastUpdate({
version, version,
@ -19,6 +20,14 @@ export function ToastUpdate({
?.toLocaleLowerCase() ?.toLocaleLowerCase()
.includes('breaking') .includes('breaking')
const markedOptions: MarkedOptions = {
gfm: true,
breaks: true,
sanitize: true,
unescape,
escape,
}
return ( return (
<div className="inset-0 z-50 grid place-content-center rounded bg-chalkboard-110/50 shadow-md"> <div className="inset-0 z-50 grid place-content-center rounded bg-chalkboard-110/50 shadow-md">
<div className="max-w-3xl min-w-[35rem] p-8 rounded bg-chalkboard-10 dark:bg-chalkboard-90"> <div className="max-w-3xl min-w-[35rem] p-8 rounded bg-chalkboard-10 dark:bg-chalkboard-90">
@ -58,9 +67,8 @@ export function ToastUpdate({
className="parsed-markdown py-2 px-4 mt-2 border-t border-chalkboard-30 dark:border-chalkboard-60 max-h-60 overflow-y-auto" className="parsed-markdown py-2 px-4 mt-2 border-t border-chalkboard-30 dark:border-chalkboard-60 max-h-60 overflow-y-auto"
dangerouslySetInnerHTML={{ dangerouslySetInnerHTML={{
__html: Marked.parse(releaseNotes, { __html: Marked.parse(releaseNotes, {
gfm: true, renderer: new SafeRenderer(markedOptions),
breaks: true, ...markedOptions,
sanitize: true,
}), }),
}} }}
></div> ></div>

View File

@ -1,42 +0,0 @@
import { fireEvent, render, screen } from '@testing-library/react'
import { vi } from 'vitest'
import { UpdaterModal } from './UpdaterModal'
describe('UpdaterModal tests', () => {
test('Renders the modal', () => {
const callback = vi.fn()
const data = {
version: '1.2.3',
date: '2021-22-23T21:22:23Z',
body: 'This is the body.',
}
render(
<UpdaterModal
isOpen={true}
onReject={() => {}}
onResolve={callback}
instanceId=""
open={false}
close={(res) => {}}
version={data.version}
date={data.date}
body={data.body}
/>
)
expect(screen.getByTestId('update-version')).toHaveTextContent(data.version)
const updateButton = screen.getByTestId('update-button-update')
expect(updateButton).toBeEnabled()
fireEvent.click(updateButton)
expect(callback.mock.calls).toHaveLength(1)
expect(callback.mock.lastCall[0]).toEqual({ wantUpdate: true })
const cancelButton = screen.getByTestId('update-button-cancel')
expect(cancelButton).toBeEnabled()
fireEvent.click(cancelButton)
expect(callback.mock.calls).toHaveLength(2)
expect(callback.mock.lastCall[0]).toEqual({ wantUpdate: false })
})
})

View File

@ -1,87 +0,0 @@
import { create, InstanceProps } from 'react-modal-promise'
import { ActionButton } from './ActionButton'
import { Logo } from './Logo'
import { Marked } from '@ts-stack/markdown'
type ModalResolve = {
wantUpdate: boolean
}
type ModalReject = boolean
type UpdaterModalProps = InstanceProps<ModalResolve, ModalReject> & {
version: string
date?: string
body?: string
}
export const createUpdaterModal = create<
UpdaterModalProps,
ModalResolve,
ModalReject
>
export const UpdaterModal = ({
onResolve,
version,
date,
body,
}: UpdaterModalProps) => (
<div className="fixed inset-0 z-50 grid place-content-center bg-chalkboard-110/50">
<div className="max-w-3xl min-w-[45rem] p-8 rounded bg-chalkboard-10 dark:bg-chalkboard-90">
<div className="flex items-center">
<h1 className="flex-grow text-3xl font-bold">New version available!</h1>
<Logo className="h-9" />
</div>
<div className="my-4 flex items-baseline">
<span
className="px-3 py-1 text-xl rounded-full bg-energy-10 text-energy-80"
data-testid="update-version"
>
v{version}
</span>
<span className="ml-4 text-sm text-gray-400">Published on {date}</span>
</div>
{/* TODO: fix list bullets */}
{body && (
<div
className="my-4 max-h-60 overflow-y-auto"
dangerouslySetInnerHTML={{
__html: Marked.parse(body, {
gfm: true,
breaks: true,
sanitize: true,
}),
}}
></div>
)}
<div className="flex justify-between">
<ActionButton
Element="button"
onClick={() => onResolve({ wantUpdate: false })}
iconStart={{
icon: 'close',
bgClassName: 'bg-destroy-80',
iconClassName: 'text-destroy-20 group-hover:text-destroy-10',
}}
className="hover:border-destroy-40 hover:bg-destroy-10/50 dark:hover:bg-destroy-80/50"
data-testid="update-button-cancel"
>
Not now
</ActionButton>
<ActionButton
Element="button"
onClick={() => onResolve({ wantUpdate: true })}
iconStart={{
icon: 'arrowRight',
bgClassName: 'dark:bg-chalkboard-80',
}}
className="dark:hover:bg-chalkboard-80/50"
data-testid="update-button-update"
>
Update
</ActionButton>
</div>
</div>
</div>
)

View File

@ -10,8 +10,11 @@ import { AppStreamProvider } from 'AppState'
import { ToastUpdate } from 'components/ToastUpdate' import { ToastUpdate } from 'components/ToastUpdate'
import { markOnce } from 'lib/performance' import { markOnce } from 'lib/performance'
import { AUTO_UPDATER_TOAST_ID } from 'lib/constants' import { AUTO_UPDATER_TOAST_ID } from 'lib/constants'
import { initializeWindowExceptionHandler } from 'lib/exceptions'
markOnce('code/willAuth') markOnce('code/willAuth')
initializeWindowExceptionHandler()
// uncomment for xstate inspector // uncomment for xstate inspector
// import { DEV } from 'env' // import { DEV } from 'env'
// import { inspect } from '@xstate/inspect' // import { inspect } from '@xstate/inspect'

View File

@ -376,7 +376,11 @@ export class KclManager {
} }
this.ast = { ...ast } this.ast = { ...ast }
// updateArtifactGraph relies on updated executeState/programMemory // updateArtifactGraph relies on updated executeState/programMemory
await this.engineCommandManager.updateArtifactGraph(this.ast) await this.engineCommandManager.updateArtifactGraph(
this.ast,
execState.artifactCommands,
execState.artifacts
)
this._executeCallback() this._executeCallback()
if (!isInterrupted) { if (!isInterrupted) {
sceneInfra.modelingSend({ type: 'code edit during sketch' }) sceneInfra.modelingSend({ type: 'code edit during sketch' })
@ -390,6 +394,24 @@ export class KclManager {
this._cancelTokens.delete(currentExecutionId) this._cancelTokens.delete(currentExecutionId)
markOnce('code/endExecuteAst') markOnce('code/endExecuteAst')
} }
/**
* This cleanup function is external and internal to the KclSingleton class.
* Since the WASM runtime can panic and the error cannot be caught in executeAst
* we need a global exception handler in exceptions.ts
* This file will interface with this cleanup as if it caught the original error
* to properly restore the TS application state.
*/
executeAstCleanUp() {
this.isExecuting = false
this.executeIsStale = null
this.engineCommandManager.addCommandLog({
type: 'execution-done',
data: null,
})
markOnce('code/endExecuteAst')
}
// NOTE: this always updates the code state and editor. // NOTE: this always updates the code state and editor.
// DO NOT CALL THIS from codemirror ever. // DO NOT CALL THIS from codemirror ever.
async executeAstMock( async executeAstMock(

View File

@ -47,7 +47,7 @@ describe('parsing errors', () => {
const result = parse(code) const result = parse(code)
if (err(result)) throw result if (err(result)) throw result
const error = result.errors[0] const error = result.errors[0]
expect(error.message).toBe('Unexpected token: (') expect(error.message).toBe('Array is missing a closing bracket(`]`)')
expect(error.sourceRange).toEqual([27, 28, 0]) expect(error.sourceRange).toEqual([28, 29, 0])
}) })
}) })

View File

@ -10,6 +10,7 @@ describe('test kclErrToDiagnostic', () => {
msg: 'Semantic error', msg: 'Semantic error',
sourceRange: [0, 1, true], sourceRange: [0, 1, true],
operations: [], operations: [],
artifactCommands: [],
}, },
{ {
name: '', name: '',
@ -18,6 +19,7 @@ describe('test kclErrToDiagnostic', () => {
msg: 'Type error', msg: 'Type error',
sourceRange: [4, 5, true], sourceRange: [4, 5, true],
operations: [], operations: [],
artifactCommands: [],
}, },
] ]
const diagnostics = kclErrorsToDiagnostics(errors) const diagnostics = kclErrorsToDiagnostics(errors)

View File

@ -5,7 +5,7 @@ import { posToOffset } from '@kittycad/codemirror-lsp-client'
import { Diagnostic as LspDiagnostic } from 'vscode-languageserver-protocol' import { Diagnostic as LspDiagnostic } from 'vscode-languageserver-protocol'
import { Text } from '@codemirror/state' import { Text } from '@codemirror/state'
import { EditorView } from 'codemirror' import { EditorView } from 'codemirror'
import { SourceRange } from 'lang/wasm' import { ArtifactCommand, SourceRange } from 'lang/wasm'
import { Operation } from 'wasm-lib/kcl/bindings/Operation' import { Operation } from 'wasm-lib/kcl/bindings/Operation'
type ExtractKind<T> = T extends { kind: infer K } ? K : never type ExtractKind<T> = T extends { kind: infer K } ? K : never
@ -14,86 +14,141 @@ export class KCLError extends Error {
sourceRange: SourceRange sourceRange: SourceRange
msg: string msg: string
operations: Operation[] operations: Operation[]
artifactCommands: ArtifactCommand[]
constructor( constructor(
kind: ExtractKind<RustKclError> | 'name', kind: ExtractKind<RustKclError> | 'name',
msg: string, msg: string,
sourceRange: SourceRange, sourceRange: SourceRange,
operations: Operation[] operations: Operation[],
artifactCommands: ArtifactCommand[]
) { ) {
super() super()
this.kind = kind this.kind = kind
this.msg = msg this.msg = msg
this.sourceRange = sourceRange this.sourceRange = sourceRange
this.operations = operations this.operations = operations
this.artifactCommands = artifactCommands
Object.setPrototypeOf(this, KCLError.prototype) Object.setPrototypeOf(this, KCLError.prototype)
} }
} }
export class KCLLexicalError extends KCLError { export class KCLLexicalError extends KCLError {
constructor(msg: string, sourceRange: SourceRange, operations: Operation[]) { constructor(
super('lexical', msg, sourceRange, operations) msg: string,
sourceRange: SourceRange,
operations: Operation[],
artifactCommands: ArtifactCommand[]
) {
super('lexical', msg, sourceRange, operations, artifactCommands)
Object.setPrototypeOf(this, KCLSyntaxError.prototype) Object.setPrototypeOf(this, KCLSyntaxError.prototype)
} }
} }
export class KCLInternalError extends KCLError { export class KCLInternalError extends KCLError {
constructor(msg: string, sourceRange: SourceRange, operations: Operation[]) { constructor(
super('internal', msg, sourceRange, operations) msg: string,
sourceRange: SourceRange,
operations: Operation[],
artifactCommands: ArtifactCommand[]
) {
super('internal', msg, sourceRange, operations, artifactCommands)
Object.setPrototypeOf(this, KCLSyntaxError.prototype) Object.setPrototypeOf(this, KCLSyntaxError.prototype)
} }
} }
export class KCLSyntaxError extends KCLError { export class KCLSyntaxError extends KCLError {
constructor(msg: string, sourceRange: SourceRange, operations: Operation[]) { constructor(
super('syntax', msg, sourceRange, operations) msg: string,
sourceRange: SourceRange,
operations: Operation[],
artifactCommands: ArtifactCommand[]
) {
super('syntax', msg, sourceRange, operations, artifactCommands)
Object.setPrototypeOf(this, KCLSyntaxError.prototype) Object.setPrototypeOf(this, KCLSyntaxError.prototype)
} }
} }
export class KCLSemanticError extends KCLError { export class KCLSemanticError extends KCLError {
constructor(msg: string, sourceRange: SourceRange, operations: Operation[]) { constructor(
super('semantic', msg, sourceRange, operations) msg: string,
sourceRange: SourceRange,
operations: Operation[],
artifactCommands: ArtifactCommand[]
) {
super('semantic', msg, sourceRange, operations, artifactCommands)
Object.setPrototypeOf(this, KCLSemanticError.prototype) Object.setPrototypeOf(this, KCLSemanticError.prototype)
} }
} }
export class KCLTypeError extends KCLError { export class KCLTypeError extends KCLError {
constructor(msg: string, sourceRange: SourceRange, operations: Operation[]) { constructor(
super('type', msg, sourceRange, operations) msg: string,
sourceRange: SourceRange,
operations: Operation[],
artifactCommands: ArtifactCommand[]
) {
super('type', msg, sourceRange, operations, artifactCommands)
Object.setPrototypeOf(this, KCLTypeError.prototype) Object.setPrototypeOf(this, KCLTypeError.prototype)
} }
} }
export class KCLUnimplementedError extends KCLError { export class KCLUnimplementedError extends KCLError {
constructor(msg: string, sourceRange: SourceRange, operations: Operation[]) { constructor(
super('unimplemented', msg, sourceRange, operations) msg: string,
sourceRange: SourceRange,
operations: Operation[],
artifactCommands: ArtifactCommand[]
) {
super('unimplemented', msg, sourceRange, operations, artifactCommands)
Object.setPrototypeOf(this, KCLUnimplementedError.prototype) Object.setPrototypeOf(this, KCLUnimplementedError.prototype)
} }
} }
export class KCLUnexpectedError extends KCLError { export class KCLUnexpectedError extends KCLError {
constructor(msg: string, sourceRange: SourceRange, operations: Operation[]) { constructor(
super('unexpected', msg, sourceRange, operations) msg: string,
sourceRange: SourceRange,
operations: Operation[],
artifactCommands: ArtifactCommand[]
) {
super('unexpected', msg, sourceRange, operations, artifactCommands)
Object.setPrototypeOf(this, KCLUnexpectedError.prototype) Object.setPrototypeOf(this, KCLUnexpectedError.prototype)
} }
} }
export class KCLValueAlreadyDefined extends KCLError { export class KCLValueAlreadyDefined extends KCLError {
constructor(key: string, sourceRange: SourceRange, operations: Operation[]) { constructor(
key: string,
sourceRange: SourceRange,
operations: Operation[],
artifactCommands: ArtifactCommand[]
) {
super( super(
'name', 'name',
`Key ${key} was already defined elsewhere`, `Key ${key} was already defined elsewhere`,
sourceRange, sourceRange,
operations operations,
artifactCommands
) )
Object.setPrototypeOf(this, KCLValueAlreadyDefined.prototype) Object.setPrototypeOf(this, KCLValueAlreadyDefined.prototype)
} }
} }
export class KCLUndefinedValueError extends KCLError { export class KCLUndefinedValueError extends KCLError {
constructor(key: string, sourceRange: SourceRange, operations: Operation[]) { constructor(
super('name', `Key ${key} has not been defined`, sourceRange, operations) key: string,
sourceRange: SourceRange,
operations: Operation[],
artifactCommands: ArtifactCommand[]
) {
super(
'name',
`Key ${key} has not been defined`,
sourceRange,
operations,
artifactCommands
)
Object.setPrototypeOf(this, KCLUndefinedValueError.prototype) Object.setPrototypeOf(this, KCLUndefinedValueError.prototype)
} }
} }
@ -113,6 +168,7 @@ export function lspDiagnosticsToKclErrors(
'unexpected', 'unexpected',
message, message,
[posToOffset(doc, range.start)!, posToOffset(doc, range.end)!, true], [posToOffset(doc, range.start)!, posToOffset(doc, range.end)!, true],
[],
[] []
) )
) )

View File

@ -481,6 +481,7 @@ const theExtrude = startSketchOn('XY')
'undefined_value', 'undefined_value',
'memory item key `myVarZ` is not defined', 'memory item key `myVarZ` is not defined',
[129, 135, true], [129, 135, true],
[],
[] []
) )
) )

View File

@ -1,79 +1,81 @@
import { assertParse, initPromise, programMemoryInit } from './wasm' import { assertParse, initPromise, programMemoryInit } from './wasm'
import { enginelessExecutor } from '../lib/testHelpers' import { enginelessExecutor } from '../lib/testHelpers'
// These unit tests makes web requests to a public github repository.
import path from 'node:path'
import fs from 'node:fs/promises'
import child_process from 'node:child_process'
// The purpose of these tests is to act as a first line of defense
// if something gets real screwy with our KCL ecosystem.
// THESE TESTS ONLY RUN UNDER A NODEJS ENVIRONMENT. They DO NOT
// test under our application.
const DIR_KCL_SAMPLES = 'kcl-samples'
const URL_GIT_KCL_SAMPLES = 'https://github.com/KittyCAD/kcl-samples.git'
interface KclSampleFile { interface KclSampleFile {
file: string file: string
pathFromProjectDirectoryToFirstFile: string
title: string title: string
filename: string filename: string
description: string description: string
} }
try {
// @ts-expect-error
await fs.rm(DIR_KCL_SAMPLES, { recursive: true })
} catch (e) {
console.log(e)
}
child_process.spawnSync('git', ['clone', URL_GIT_KCL_SAMPLES, DIR_KCL_SAMPLES])
// @ts-expect-error
let files = await fs.readdir(DIR_KCL_SAMPLES)
// @ts-expect-error
const manifestJsonStr = await fs.readFile(
path.resolve(DIR_KCL_SAMPLES, 'manifest.json'),
'utf-8'
)
const manifest = JSON.parse(manifestJsonStr)
process.chdir(DIR_KCL_SAMPLES)
beforeAll(async () => { beforeAll(async () => {
await initPromise await initPromise
}) })
// Only used to actually fetch an older version of KCL code that will break in the parser. afterAll(async () => {
/* eslint-disable @typescript-eslint/no-unused-vars */ try {
async function getBrokenSampleCodeForLocalTesting() { process.chdir('..')
const result = await fetch( await fs.rm(DIR_KCL_SAMPLES, { recursive: true })
'https://raw.githubusercontent.com/KittyCAD/kcl-samples/5ccd04a1773ebdbfd02684057917ce5dbe0eaab3/80-20-rail.kcl' } catch (e) {}
)
const text = await result.text()
return text
}
async function getKclSampleCodeFromGithub(file: string): Promise<string> {
const result = await fetch(
`https://raw.githubusercontent.com/KittyCAD/kcl-samples/refs/heads/main/${file}/${file}.kcl`
)
const text = await result.text()
return text
}
async function getFileNamesFromManifestJSON(): Promise<KclSampleFile[]> {
const result = await fetch(
'https://raw.githubusercontent.com/KittyCAD/kcl-samples/refs/heads/main/manifest.json'
)
const json = await result.json()
json.forEach((file: KclSampleFile) => {
const filenameWithoutExtension = file.file.split('.')[0]
file.filename = filenameWithoutExtension
})
return json
}
// Value to use across all tests!
let files: KclSampleFile[] = []
describe('Test KCL Samples from public Github repository', () => {
describe('When parsing source code', () => {
// THIS RUNS ACROSS OTHER TESTS!
it('should fetch files', async () => {
files = await getFileNamesFromManifestJSON()
})
// Run through all of the files in the manifest json. This will allow us to be automatically updated
// with the latest changes in github. We won't be hard coding the filenames
files.forEach((file: KclSampleFile) => {
it(`should parse ${file.filename} without errors`, async () => {
const code = await getKclSampleCodeFromGithub(file.filename)
assertParse(code)
}, 1000)
})
}) })
describe('when performing enginelessExecutor', () => { afterEach(() => {
it( process.chdir('..')
'should run through all the files', })
// The tests have to be sequential because we need to change directories
// to support `import` working properly.
// @ts-expect-error
describe.sequential('Test KCL Samples from public Github repository', () => {
// @ts-expect-error
describe.sequential('when performing enginelessExecutor', () => {
manifest.forEach((file: KclSampleFile) => {
// @ts-expect-error
it.sequential(
`should execute ${file.title} (${file.file}) successfully`,
async () => { async () => {
for (let i = 0; i < files.length; i++) { const [dirProject, fileKcl] =
const file: KclSampleFile = files[i] file.pathFromProjectDirectoryToFirstFile.split('/')
const code = await getKclSampleCodeFromGithub(file.filename) process.chdir(dirProject)
const code = await fs.readFile(fileKcl, 'utf-8')
const ast = assertParse(code) const ast = assertParse(code)
await enginelessExecutor(ast, programMemoryInit()) await enginelessExecutor(ast, programMemoryInit())
}
}, },
files.length * 1000 files.length * 1000
) )
}) })
}) })
})

View File

@ -1,6 +1,6 @@
import { import {
Program, Program,
_executor, executor,
ProgramMemory, ProgramMemory,
kclLint, kclLint,
emptyExecState, emptyExecState,
@ -64,7 +64,7 @@ export async function executeAst({
try { try {
const execState = await (programMemoryOverride const execState = await (programMemoryOverride
? enginelessExecutor(ast, programMemoryOverride) ? enginelessExecutor(ast, programMemoryOverride)
: _executor(ast, engineCommandManager)) : executor(ast, engineCommandManager))
await engineCommandManager.waitForAllCommands() await engineCommandManager.waitForAllCommands()

View File

@ -806,9 +806,9 @@ sketch001 = startSketchOn('XZ')
sketch002 = startSketchOn({ sketch002 = startSketchOn({
plane = { plane = {
origin = { x = 1, y = 2, z = 3 }, origin = { x = 1, y = 2, z = 3 },
x_axis = { x = 4, y = 5, z = 6 }, xAxis = { x = 4, y = 5, z = 6 },
y_axis = { x = 7, y = 8, z = 9 }, yAxis = { x = 7, y = 8, z = 9 },
z_axis = { x = 10, y = 11, z = 12 } zAxis = { x = 10, y = 11, z = 12 }
} }
}) })
|> startProfileAt([-12.55, 2.89], %) |> startProfileAt([-12.55, 2.89], %)
@ -862,9 +862,9 @@ sketch001 = startSketchOn('XZ')
sketch002 = startSketchOn({ sketch002 = startSketchOn({
plane = { plane = {
origin = { x = 1, y = 2, z = 3 }, origin = { x = 1, y = 2, z = 3 },
x_axis = { x = 4, y = 5, z = 6 }, xAxis = { x = 4, y = 5, z = 6 },
y_axis = { x = 7, y = 8, z = 9 }, yAxis = { x = 7, y = 8, z = 9 },
z_axis = { x = 10, y = 11, z = 12 } zAxis = { x = 10, y = 11, z = 12 }
} }
}) })
|> startProfileAt([-12.55, 2.89], %) |> startProfileAt([-12.55, 2.89], %)

View File

@ -374,6 +374,37 @@ export function loftSketches(
} }
} }
export function addSweep(
node: Node<Program>,
profileDeclarator: VariableDeclarator,
pathDeclarator: VariableDeclarator
): {
modifiedAst: Node<Program>
pathToNode: PathToNode
} {
const modifiedAst = structuredClone(node)
const name = findUniqueName(node, KCL_DEFAULT_CONSTANT_PREFIXES.SWEEP)
const sweep = createCallExpressionStdLib('sweep', [
createObjectExpression({ path: createIdentifier(pathDeclarator.id.name) }),
createIdentifier(profileDeclarator.id.name),
])
const declaration = createVariableDeclaration(name, sweep)
modifiedAst.body.push(declaration)
const pathToNode: PathToNode = [
['body', ''],
[modifiedAst.body.length - 1, 'index'],
['declaration', 'VariableDeclaration'],
['init', 'VariableDeclarator'],
['arguments', 'CallExpression'],
[0, 'index'],
]
return {
modifiedAst,
pathToNode,
}
}
export function revolveSketch( export function revolveSketch(
node: Node<Program>, node: Node<Program>,
pathToNode: PathToNode, pathToNode: PathToNode,
@ -1149,11 +1180,17 @@ export async function deleteFromSelection(
((selection?.artifact?.type === 'wall' || ((selection?.artifact?.type === 'wall' ||
selection?.artifact?.type === 'cap') && selection?.artifact?.type === 'cap') &&
varDec.node.init.type === 'PipeExpression') || varDec.node.init.type === 'PipeExpression') ||
selection.artifact?.type === 'sweep' selection.artifact?.type === 'sweep' ||
selection.artifact?.type === 'plane' ||
!selection.artifact // aka expected to be a shell at this point
) { ) {
let extrudeNameToDelete = '' let extrudeNameToDelete = ''
let pathToNode: PathToNode | null = null let pathToNode: PathToNode | null = null
if (selection.artifact?.type !== 'sweep') { if (
selection.artifact &&
selection.artifact.type !== 'sweep' &&
selection.artifact.type !== 'plane'
) {
const varDecName = varDec.node.id.name const varDecName = varDec.node.id.name
traverse(astClone, { traverse(astClone, {
enter: (node, path) => { enter: (node, path) => {
@ -1169,6 +1206,17 @@ export async function deleteFromSelection(
pathToNode = path pathToNode = path
extrudeNameToDelete = dec.id.name extrudeNameToDelete = dec.id.name
} }
if (
dec.init.type === 'CallExpression' &&
dec.init.callee.name === 'loft' &&
dec.init.arguments?.[0].type === 'ArrayExpression' &&
dec.init.arguments?.[0].elements.some(
(a) => a.type === 'Identifier' && a.name === varDecName
)
) {
pathToNode = path
extrudeNameToDelete = dec.id.name
}
} }
}, },
}) })
@ -1278,17 +1326,17 @@ export async function deleteFromSelection(
y: roundLiteral(faceDetails.origin.y), y: roundLiteral(faceDetails.origin.y),
z: roundLiteral(faceDetails.origin.z), z: roundLiteral(faceDetails.origin.z),
}), }),
x_axis: createObjectExpression({ xAxis: createObjectExpression({
x: roundLiteral(faceDetails.x_axis.x), x: roundLiteral(faceDetails.x_axis.x),
y: roundLiteral(faceDetails.x_axis.y), y: roundLiteral(faceDetails.x_axis.y),
z: roundLiteral(faceDetails.x_axis.z), z: roundLiteral(faceDetails.x_axis.z),
}), }),
y_axis: createObjectExpression({ yAxis: createObjectExpression({
x: roundLiteral(faceDetails.y_axis.x), x: roundLiteral(faceDetails.y_axis.x),
y: roundLiteral(faceDetails.y_axis.y), y: roundLiteral(faceDetails.y_axis.y),
z: roundLiteral(faceDetails.y_axis.z), z: roundLiteral(faceDetails.y_axis.z),
}), }),
z_axis: createObjectExpression({ zAxis: createObjectExpression({
x: roundLiteral(faceDetails.z_axis.x), x: roundLiteral(faceDetails.z_axis.x),
y: roundLiteral(faceDetails.z_axis.y), y: roundLiteral(faceDetails.z_axis.y),
z: roundLiteral(faceDetails.z_axis.z), z: roundLiteral(faceDetails.z_axis.z),

View File

@ -61,19 +61,18 @@ export interface FilletParameters {
export type EdgeTreatmentParameters = ChamferParameters | FilletParameters export type EdgeTreatmentParameters = ChamferParameters | FilletParameters
// Apply Edge Treatment (Fillet or Chamfer) To Selection // Apply Edge Treatment (Fillet or Chamfer) To Selection
export function applyEdgeTreatmentToSelection( export async function applyEdgeTreatmentToSelection(
ast: Node<Program>, ast: Node<Program>,
selection: Selections, selection: Selections,
parameters: EdgeTreatmentParameters parameters: EdgeTreatmentParameters
): void | Error { ): Promise<void | Error> {
// 1. clone and modify with edge treatment and tag // 1. clone and modify with edge treatment and tag
const result = modifyAstWithEdgeTreatmentAndTag(ast, selection, parameters) const result = modifyAstWithEdgeTreatmentAndTag(ast, selection, parameters)
if (err(result)) return result if (err(result)) return result
const { modifiedAst, pathToEdgeTreatmentNode } = result const { modifiedAst, pathToEdgeTreatmentNode } = result
// 2. update ast // 2. update ast
// eslint-disable-next-line @typescript-eslint/no-floating-promises await updateAstAndFocus(modifiedAst, pathToEdgeTreatmentNode)
updateAstAndFocus(modifiedAst, pathToEdgeTreatmentNode)
} }
export function modifyAstWithEdgeTreatmentAndTag( export function modifyAstWithEdgeTreatmentAndTag(
@ -291,7 +290,7 @@ export function getPathToExtrudeForSegmentSelection(
async function updateAstAndFocus( async function updateAstAndFocus(
modifiedAst: Node<Program>, modifiedAst: Node<Program>,
pathToEdgeTreatmentNode: Array<PathToNode> pathToEdgeTreatmentNode: Array<PathToNode>
) { ): Promise<void> {
const updatedAst = await kclManager.updateAst(modifiedAst, true, { const updatedAst = await kclManager.updateAst(modifiedAst, true, {
focusPath: pathToEdgeTreatmentNode, focusPath: pathToEdgeTreatmentNode,
}) })

View File

@ -29,7 +29,9 @@ export function revolveSketch(
pathToSketchNode: PathToNode, pathToSketchNode: PathToNode,
shouldPipe = false, shouldPipe = false,
angle: Expr = createLiteral(4), angle: Expr = createLiteral(4),
axis: Selections axisOrEdge: string,
axis: string,
edge: Selections
): ):
| { | {
modifiedAst: Node<Program> modifiedAst: Node<Program>
@ -41,12 +43,13 @@ export function revolveSketch(
const sketchNode = getNodeFromPath(clonedAst, pathToSketchNode) const sketchNode = getNodeFromPath(clonedAst, pathToSketchNode)
if (err(sketchNode)) return sketchNode if (err(sketchNode)) return sketchNode
// testing code let generatedAxis
if (axisOrEdge === 'Edge') {
const pathToAxisSelection = getNodePathFromSourceRange( const pathToAxisSelection = getNodePathFromSourceRange(
clonedAst, clonedAst,
axis.graphSelections[0]?.codeRef.range edge.graphSelections[0]?.codeRef.range
) )
const lineNode = getNodeFromPath<CallExpression>( const lineNode = getNodeFromPath<CallExpression>(
clonedAst, clonedAst,
pathToAxisSelection, pathToAxisSelection,
@ -54,10 +57,6 @@ export function revolveSketch(
) )
if (err(lineNode)) return lineNode if (err(lineNode)) return lineNode
// TODO Kevin: What if |> close(%)?
// TODO Kevin: What if opposite edge
// TODO Kevin: What if the edge isn't planar to the sketch?
// TODO Kevin: add a tag.
const tagResult = mutateAstWithTagForSketchSegment( const tagResult = mutateAstWithTagForSketchSegment(
clonedAst, clonedAst,
pathToAxisSelection pathToAxisSelection
@ -66,6 +65,12 @@ export function revolveSketch(
// Have the tag whether it is already created or a new one is generated // Have the tag whether it is already created or a new one is generated
if (err(tagResult)) return tagResult if (err(tagResult)) return tagResult
const { tag } = tagResult const { tag } = tagResult
const axisSelection = edge?.graphSelections[0]?.artifact
if (!axisSelection) return new Error('Generated axis selection is missing.')
generatedAxis = getEdgeTagCall(tag, axisSelection)
} else {
generatedAxis = createLiteral(axis)
}
/* Original Code */ /* Original Code */
const { node: sketchExpression } = sketchNode const { node: sketchExpression } = sketchNode
@ -91,14 +96,12 @@ export function revolveSketch(
shallowPath: sketchPathToDecleration, shallowPath: sketchPathToDecleration,
} = sketchVariableDeclaratorNode } = sketchVariableDeclaratorNode
const axisSelection = axis?.graphSelections[0]?.artifact if (!generatedAxis) return new Error('Generated axis selection is missing.')
if (!axisSelection) return new Error('Axis selection is missing.')
const revolveCall = createCallExpressionStdLib('revolve', [ const revolveCall = createCallExpressionStdLib('revolve', [
createObjectExpression({ createObjectExpression({
angle: angle, angle: angle,
axis: getEdgeTagCall(tag, axisSelection), axis: generatedAxis,
}), }),
createIdentifier(sketchVariableDeclarator.id.name), createIdentifier(sketchVariableDeclarator.id.name),
]) ])

View File

@ -49,17 +49,27 @@ export function addShell({
return new Error("Couldn't find extrude") return new Error("Couldn't find extrude")
} }
pathToExtrudeNode = extrudeLookupResult.pathToExtrudeNode
// Get the sketch ref from the selection
// TODO: this assumes the segment is piped directly from the sketch, with no intermediate `VariableDeclarator` between. // TODO: this assumes the segment is piped directly from the sketch, with no intermediate `VariableDeclarator` between.
// We must find a technique for these situations that is robust to intermediate declarations // We must find a technique for these situations that is robust to intermediate declarations
const sketchNode = getNodeFromPath<VariableDeclarator>( const extrudeNode = getNodeFromPath<VariableDeclarator>(
modifiedAst, modifiedAst,
graphSelection.codeRef.pathToNode, extrudeLookupResult.pathToExtrudeNode,
'VariableDeclarator' 'VariableDeclarator'
) )
if (err(sketchNode)) { const segmentNode = getNodeFromPath<VariableDeclarator>(
return sketchNode modifiedAst,
extrudeLookupResult.pathToSegmentNode,
'VariableDeclarator'
)
if (err(extrudeNode) || err(segmentNode)) {
return new Error("Couldn't find extrude")
}
if (extrudeNode.node.init.type === 'CallExpression') {
pathToExtrudeNode = extrudeLookupResult.pathToExtrudeNode
} else if (segmentNode.node.init.type === 'PipeExpression') {
pathToExtrudeNode = extrudeLookupResult.pathToSegmentNode
} else {
return new Error("Couldn't find extrude")
} }
const selectedArtifact = graphSelection.artifact const selectedArtifact = graphSelection.artifact

View File

@ -1,7 +1,13 @@
import { makeDefaultPlanes, assertParse, initPromise, Program } from 'lang/wasm' import {
makeDefaultPlanes,
assertParse,
initPromise,
Program,
ArtifactCommand,
ExecState,
} from 'lang/wasm'
import { Models } from '@kittycad/lib' import { Models } from '@kittycad/lib'
import { import {
OrderedCommand,
ResponseMap, ResponseMap,
createArtifactGraph, createArtifactGraph,
filterArtifacts, filterArtifacts,
@ -22,6 +28,7 @@ import * as d3 from 'd3-force'
import path from 'path' import path from 'path'
import pixelmatch from 'pixelmatch' import pixelmatch from 'pixelmatch'
import { PNG } from 'pngjs' import { PNG } from 'pngjs'
import { Node } from 'wasm-lib/kcl/bindings/Node'
/* /*
Note this is an integration test, these tests connect to our real dev server and make websocket commands. Note this is an integration test, these tests connect to our real dev server and make websocket commands.
@ -108,7 +115,7 @@ sketch002 = startSketchOn(offsetPlane001)
|> line([6.78, 15.01], %) |> line([6.78, 15.01], %)
` `
// add more code snippets here and use `getCommands` to get the orderedCommands and responseMap for more tests // add more code snippets here and use `getCommands` to get the artifactCommands and responseMap for more tests
const codeToWriteCacheFor = { const codeToWriteCacheFor = {
exampleCode1, exampleCode1,
sketchOnFaceOnFaceEtc, sketchOnFaceOnFaceEtc,
@ -120,8 +127,9 @@ type CodeKey = keyof typeof codeToWriteCacheFor
type CacheShape = { type CacheShape = {
[key in CodeKey]: { [key in CodeKey]: {
orderedCommands: OrderedCommand[] artifactCommands: ArtifactCommand[]
responseMap: ResponseMap responseMap: ResponseMap
execStateArtifacts: ExecState['artifacts']
} }
} }
@ -151,8 +159,9 @@ beforeAll(async () => {
await kclManager.executeAst({ ast }) await kclManager.executeAst({ ast })
cacheToWriteToFileTemp[codeKey] = { cacheToWriteToFileTemp[codeKey] = {
orderedCommands: engineCommandManager.orderedCommands, artifactCommands: kclManager.execState.artifactCommands,
responseMap: engineCommandManager.responseMap, responseMap: engineCommandManager.responseMap,
execStateArtifacts: kclManager.execState.artifacts,
} }
} }
const cache = JSON.stringify(cacheToWriteToFileTemp) const cache = JSON.stringify(cacheToWriteToFileTemp)
@ -171,18 +180,24 @@ afterAll(() => {
describe('testing createArtifactGraph', () => { describe('testing createArtifactGraph', () => {
describe('code with offset planes and a sketch:', () => { describe('code with offset planes and a sketch:', () => {
let ast: Program let ast: Node<Program>
let theMap: ReturnType<typeof createArtifactGraph> let theMap: ReturnType<typeof createArtifactGraph>
it('setup', () => { it('setup', () => {
// putting this logic in here because describe blocks runs before beforeAll has finished // putting this logic in here because describe blocks runs before beforeAll has finished
const { const {
orderedCommands, artifactCommands,
responseMap, responseMap,
ast: _ast, ast: _ast,
execStateArtifacts,
} = getCommands('exampleCodeOffsetPlanes') } = getCommands('exampleCodeOffsetPlanes')
ast = _ast ast = _ast
theMap = createArtifactGraph({ orderedCommands, responseMap, ast }) theMap = createArtifactGraph({
artifactCommands,
responseMap,
ast,
execStateArtifacts,
})
}) })
it(`there should be one sketch`, () => { it(`there should be one sketch`, () => {
@ -217,17 +232,23 @@ describe('testing createArtifactGraph', () => {
}) })
}) })
describe('code with an extrusion, fillet and sketch of face:', () => { describe('code with an extrusion, fillet and sketch of face:', () => {
let ast: Program let ast: Node<Program>
let theMap: ReturnType<typeof createArtifactGraph> let theMap: ReturnType<typeof createArtifactGraph>
it('setup', () => { it('setup', () => {
// putting this logic in here because describe blocks runs before beforeAll has finished // putting this logic in here because describe blocks runs before beforeAll has finished
const { const {
orderedCommands, artifactCommands,
responseMap, responseMap,
ast: _ast, ast: _ast,
execStateArtifacts,
} = getCommands('exampleCode1') } = getCommands('exampleCode1')
ast = _ast ast = _ast
theMap = createArtifactGraph({ orderedCommands, responseMap, ast }) theMap = createArtifactGraph({
artifactCommands,
responseMap,
ast,
execStateArtifacts,
})
}) })
it('there should be two planes for the extrusion and the sketch on face', () => { it('there should be two planes for the extrusion and the sketch on face', () => {
@ -312,17 +333,23 @@ describe('testing createArtifactGraph', () => {
}) })
describe(`code with sketches but no extrusions or other 3D elements`, () => { describe(`code with sketches but no extrusions or other 3D elements`, () => {
let ast: Program let ast: Node<Program>
let theMap: ReturnType<typeof createArtifactGraph> let theMap: ReturnType<typeof createArtifactGraph>
it(`setup`, () => { it(`setup`, () => {
// putting this logic in here because describe blocks runs before beforeAll has finished // putting this logic in here because describe blocks runs before beforeAll has finished
const { const {
orderedCommands, artifactCommands,
responseMap, responseMap,
ast: _ast, ast: _ast,
execStateArtifacts,
} = getCommands('exampleCodeNo3D') } = getCommands('exampleCodeNo3D')
ast = _ast ast = _ast
theMap = createArtifactGraph({ orderedCommands, responseMap, ast }) theMap = createArtifactGraph({
artifactCommands,
responseMap,
ast,
execStateArtifacts,
})
}) })
it('there should be two planes, one for each sketch path', () => { it('there should be two planes, one for each sketch path', () => {
@ -377,17 +404,23 @@ describe('testing createArtifactGraph', () => {
describe('capture graph of sketchOnFaceOnFace...', () => { describe('capture graph of sketchOnFaceOnFace...', () => {
describe('code with an extrusion, fillet and sketch of face:', () => { describe('code with an extrusion, fillet and sketch of face:', () => {
let ast: Program let ast: Node<Program>
let theMap: ReturnType<typeof createArtifactGraph> let theMap: ReturnType<typeof createArtifactGraph>
it('setup', async () => { it('setup', async () => {
// putting this logic in here because describe blocks runs before beforeAll has finished // putting this logic in here because describe blocks runs before beforeAll has finished
const { const {
orderedCommands, artifactCommands,
responseMap, responseMap,
ast: _ast, ast: _ast,
execStateArtifacts,
} = getCommands('sketchOnFaceOnFaceEtc') } = getCommands('sketchOnFaceOnFaceEtc')
ast = _ast ast = _ast
theMap = createArtifactGraph({ orderedCommands, responseMap, ast }) theMap = createArtifactGraph({
artifactCommands,
responseMap,
ast,
execStateArtifacts,
})
// Ostensibly this takes a screen shot of the graph of the artifactGraph // Ostensibly this takes a screen shot of the graph of the artifactGraph
// but it's it also tests that all of the id links are correct because if one // but it's it also tests that all of the id links are correct because if one
@ -399,17 +432,21 @@ describe('capture graph of sketchOnFaceOnFace...', () => {
}) })
}) })
function getCommands(codeKey: CodeKey): CacheShape[CodeKey] & { ast: Program } { function getCommands(
codeKey: CodeKey
): CacheShape[CodeKey] & { ast: Node<Program> } {
const ast = assertParse(codeKey) const ast = assertParse(codeKey)
const file = fs.readFileSync(fullPath, 'utf-8') const file = fs.readFileSync(fullPath, 'utf-8')
const parsed: CacheShape = JSON.parse(file) const parsed: CacheShape = JSON.parse(file)
// these either already exist from the last run, or were created in // these either already exist from the last run, or were created in
const orderedCommands = parsed[codeKey].orderedCommands const artifactCommands = parsed[codeKey].artifactCommands
const responseMap = parsed[codeKey].responseMap const responseMap = parsed[codeKey].responseMap
const execStateArtifacts = parsed[codeKey].execStateArtifacts
return { return {
orderedCommands, artifactCommands,
responseMap, responseMap,
ast, ast,
execStateArtifacts,
} }
} }
@ -635,20 +672,30 @@ async function GraphTheGraph(
describe('testing getArtifactsToUpdate', () => { describe('testing getArtifactsToUpdate', () => {
it('should return an array of artifacts to update', () => { it('should return an array of artifacts to update', () => {
const { orderedCommands, responseMap, ast } = getCommands('exampleCode1') const { artifactCommands, responseMap, ast, execStateArtifacts } =
const map = createArtifactGraph({ orderedCommands, responseMap, ast }) getCommands('exampleCode1')
const map = createArtifactGraph({
artifactCommands,
responseMap,
ast,
execStateArtifacts,
})
const getArtifact = (id: string) => map.get(id) const getArtifact = (id: string) => map.get(id)
const currentPlaneId = 'UUID-1' const currentPlaneId = 'UUID-1'
const getUpdateObjects = (type: Models['ModelingCmd_type']['type']) => { const getUpdateObjects = (type: Models['ModelingCmd_type']['type']) => {
const artifactCommand = artifactCommands.find(
(a) => a.command.type === type
)
if (!artifactCommand) {
throw new Error(`No artifactCommand found for ${type}`)
}
const artifactsToUpdate = getArtifactsToUpdate({ const artifactsToUpdate = getArtifactsToUpdate({
orderedCommand: orderedCommands.find( artifactCommand,
(a) =>
a.command.type === 'modeling_cmd_req' && a.command.cmd.type === type
)!,
responseMap, responseMap,
getArtifact, getArtifact,
currentPlaneId, currentPlaneId,
ast, ast,
execStateArtifacts,
}) })
return artifactsToUpdate.map(({ artifact }) => artifact) return artifactsToUpdate.map(({ artifact }) => artifact)
} }
@ -658,7 +705,7 @@ describe('testing getArtifactsToUpdate', () => {
segIds: [], segIds: [],
id: expect.any(String), id: expect.any(String),
planeId: 'UUID-1', planeId: 'UUID-1',
sweepId: '', sweepId: undefined,
codeRef: { codeRef: {
pathToNode: [['body', '']], pathToNode: [['body', '']],
range: [37, 64, true], range: [37, 64, true],
@ -696,7 +743,7 @@ describe('testing getArtifactsToUpdate', () => {
type: 'segment', type: 'segment',
id: expect.any(String), id: expect.any(String),
pathId: expect.any(String), pathId: expect.any(String),
surfaceId: '', surfaceId: undefined,
edgeIds: [], edgeIds: [],
codeRef: { codeRef: {
range: [70, 86, true], range: [70, 86, true],
@ -723,7 +770,7 @@ describe('testing getArtifactsToUpdate', () => {
id: expect.any(String), id: expect.any(String),
consumedEdgeId: expect.any(String), consumedEdgeId: expect.any(String),
edgeIds: [], edgeIds: [],
surfaceId: '', surfaceId: undefined,
codeRef: { codeRef: {
range: [260, 299, true], range: [260, 299, true],
pathToNode: [['body', '']], pathToNode: [['body', '']],

View File

@ -1,7 +1,15 @@
import { PathToNode, Program, SourceRange } from 'lang/wasm' import {
ArtifactCommand,
ExecState,
PathToNode,
Program,
SourceRange,
sourceRangeFromRust,
} from 'lang/wasm'
import { Models } from '@kittycad/lib' import { Models } from '@kittycad/lib'
import { getNodePathFromSourceRange } from 'lang/queryAst' import { getNodePathFromSourceRange } from 'lang/queryAst'
import { err } from 'lib/trap' import { err } from 'lib/trap'
import { Node } from 'wasm-lib/kcl/bindings/Node'
export type ArtifactId = string export type ArtifactId = string
@ -29,7 +37,7 @@ export interface PathArtifact extends BaseArtifact {
type: 'path' type: 'path'
planeId: ArtifactId planeId: ArtifactId
segIds: Array<ArtifactId> segIds: Array<ArtifactId>
sweepId: ArtifactId sweepId?: ArtifactId
solid2dId?: ArtifactId solid2dId?: ArtifactId
codeRef: CodeRef codeRef: CodeRef
} }
@ -52,7 +60,7 @@ export interface PathArtifactRich extends BaseArtifact {
export interface SegmentArtifact extends BaseArtifact { export interface SegmentArtifact extends BaseArtifact {
type: 'segment' type: 'segment'
pathId: ArtifactId pathId: ArtifactId
surfaceId: ArtifactId surfaceId?: ArtifactId
edgeIds: Array<ArtifactId> edgeIds: Array<ArtifactId>
edgeCutId?: ArtifactId edgeCutId?: ArtifactId
codeRef: CodeRef codeRef: CodeRef
@ -60,7 +68,7 @@ export interface SegmentArtifact extends BaseArtifact {
interface SegmentArtifactRich extends BaseArtifact { interface SegmentArtifactRich extends BaseArtifact {
type: 'segment' type: 'segment'
path: PathArtifact path: PathArtifact
surf: WallArtifact surf?: WallArtifact
edges: Array<SweepEdge> edges: Array<SweepEdge>
edgeCut?: EdgeCut edgeCut?: EdgeCut
codeRef: CodeRef codeRef: CodeRef
@ -69,7 +77,7 @@ interface SegmentArtifactRich extends BaseArtifact {
/** A Sweep is a more generic term for extrude, revolve, loft and sweep*/ /** A Sweep is a more generic term for extrude, revolve, loft and sweep*/
interface SweepArtifact extends BaseArtifact { interface SweepArtifact extends BaseArtifact {
type: 'sweep' type: 'sweep'
subType: 'extrusion' | 'revolve' subType: 'extrusion' | 'revolve' | 'loft' | 'sweep'
pathId: string pathId: string
surfaceIds: Array<string> surfaceIds: Array<string>
edgeIds: Array<string> edgeIds: Array<string>
@ -77,7 +85,7 @@ interface SweepArtifact extends BaseArtifact {
} }
interface SweepArtifactRich extends BaseArtifact { interface SweepArtifactRich extends BaseArtifact {
type: 'sweep' type: 'sweep'
subType: 'extrusion' | 'revolve' subType: 'extrusion' | 'revolve' | 'loft' | 'sweep'
path: PathArtifact path: PathArtifact
surfaces: Array<WallArtifact | CapArtifact> surfaces: Array<WallArtifact | CapArtifact>
edges: Array<SweepEdge> edges: Array<SweepEdge>
@ -112,7 +120,7 @@ interface EdgeCut extends BaseArtifact {
subType: 'fillet' | 'chamfer' subType: 'fillet' | 'chamfer'
consumedEdgeId: ArtifactId consumedEdgeId: ArtifactId
edgeIds: Array<ArtifactId> edgeIds: Array<ArtifactId>
surfaceId: ArtifactId surfaceId?: ArtifactId
codeRef: CodeRef codeRef: CodeRef
} }
@ -143,50 +151,47 @@ type OkWebSocketResponseData = Models['OkWebSocketResponseData_type']
export interface ResponseMap { export interface ResponseMap {
[commandId: string]: OkWebSocketResponseData [commandId: string]: OkWebSocketResponseData
} }
export interface OrderedCommand {
command: EngineCommand
range: SourceRange
}
/** Creates a graph of artifacts from a list of ordered commands and their responses /** Creates a graph of artifacts from a list of ordered commands and their responses
* muting the Map should happen entirely this function, other functions called within * muting the Map should happen entirely this function, other functions called within
* should return data on how to update the map, and not do so directly. * should return data on how to update the map, and not do so directly.
*/ */
export function createArtifactGraph({ export function createArtifactGraph({
orderedCommands, artifactCommands,
responseMap, responseMap,
ast, ast,
execStateArtifacts,
}: { }: {
orderedCommands: Array<OrderedCommand> artifactCommands: Array<ArtifactCommand>
responseMap: ResponseMap responseMap: ResponseMap
ast: Program ast: Node<Program>
execStateArtifacts: ExecState['artifacts']
}) { }) {
const myMap = new Map<ArtifactId, Artifact>() const myMap = new Map<ArtifactId, Artifact>()
/** see docstring for {@link getArtifactsToUpdate} as to why this is needed */ /** see docstring for {@link getArtifactsToUpdate} as to why this is needed */
let currentPlaneId = '' let currentPlaneId = ''
orderedCommands.forEach((orderedCommand) => { for (const artifactCommand of artifactCommands) {
if (orderedCommand.command?.type === 'modeling_cmd_req') { if (artifactCommand.command.type === 'enable_sketch_mode') {
if (orderedCommand.command.cmd.type === 'enable_sketch_mode') { currentPlaneId = artifactCommand.command.entity_id
currentPlaneId = orderedCommand.command.cmd.entity_id
} }
if (orderedCommand.command.cmd.type === 'sketch_mode_disable') { if (artifactCommand.command.type === 'sketch_mode_disable') {
currentPlaneId = '' currentPlaneId = ''
} }
}
const artifactsToUpdate = getArtifactsToUpdate({ const artifactsToUpdate = getArtifactsToUpdate({
orderedCommand, artifactCommand,
responseMap, responseMap,
getArtifact: (id: ArtifactId) => myMap.get(id), getArtifact: (id: ArtifactId) => myMap.get(id),
currentPlaneId, currentPlaneId,
ast, ast,
execStateArtifacts,
}) })
artifactsToUpdate.forEach(({ id, artifact }) => { artifactsToUpdate.forEach(({ id, artifact }) => {
const mergedArtifact = mergeArtifacts(myMap.get(id), artifact) const mergedArtifact = mergeArtifacts(myMap.get(id), artifact)
myMap.set(id, mergedArtifact) myMap.set(id, mergedArtifact)
}) })
}) }
return myMap return myMap
} }
@ -227,30 +232,30 @@ function mergeArtifacts(
* can remove this. * can remove this.
*/ */
export function getArtifactsToUpdate({ export function getArtifactsToUpdate({
orderedCommand: { command, range }, artifactCommand,
getArtifact, getArtifact,
responseMap, responseMap,
currentPlaneId, currentPlaneId,
ast, ast,
execStateArtifacts,
}: { }: {
orderedCommand: OrderedCommand artifactCommand: ArtifactCommand
responseMap: ResponseMap responseMap: ResponseMap
/** Passing in a getter because we don't wan this function to update the map directly */ /** Passing in a getter because we don't wan this function to update the map directly */
getArtifact: (id: ArtifactId) => Artifact | undefined getArtifact: (id: ArtifactId) => Artifact | undefined
currentPlaneId: ArtifactId currentPlaneId: ArtifactId
ast: Program ast: Node<Program>
execStateArtifacts: ExecState['artifacts']
}): Array<{ }): Array<{
id: ArtifactId id: ArtifactId
artifact: Artifact artifact: Artifact
}> { }> {
const range = sourceRangeFromRust(artifactCommand.range)
const pathToNode = getNodePathFromSourceRange(ast, range) const pathToNode = getNodePathFromSourceRange(ast, range)
// expect all to be `modeling_cmd_req` as batch commands have const id = artifactCommand.cmdId
// already been expanded before being added to orderedCommands
if (command.type !== 'modeling_cmd_req') return []
const id = command.cmd_id
const response = responseMap[id] const response = responseMap[id]
const cmd = command.cmd const cmd = artifactCommand.command
const returnArr: ReturnType<typeof getArtifactsToUpdate> = [] const returnArr: ReturnType<typeof getArtifactsToUpdate> = []
if (!response) return returnArr if (!response) return returnArr
if (cmd.type === 'make_plane' && range[1] !== 0) { if (cmd.type === 'make_plane' && range[1] !== 0) {
@ -303,7 +308,7 @@ export function getArtifactsToUpdate({
id, id,
segIds: [], segIds: [],
planeId: currentPlaneId, planeId: currentPlaneId,
sweepId: '', sweepId: undefined,
codeRef: { range, pathToNode }, codeRef: { range, pathToNode },
}, },
}) })
@ -338,7 +343,7 @@ export function getArtifactsToUpdate({
type: 'segment', type: 'segment',
id, id,
pathId, pathId,
surfaceId: '', surfaceId: undefined,
edgeIds: [], edgeIds: [],
codeRef: { range, pathToNode }, codeRef: { range, pathToNode },
}, },
@ -372,7 +377,11 @@ export function getArtifactsToUpdate({
}) })
} }
return returnArr return returnArr
} else if (cmd.type === 'extrude' || cmd.type === 'revolve') { } else if (
cmd.type === 'extrude' ||
cmd.type === 'revolve' ||
cmd.type === 'sweep'
) {
const subType = cmd.type === 'extrude' ? 'extrusion' : cmd.type const subType = cmd.type === 'extrude' ? 'extrusion' : cmd.type
returnArr.push({ returnArr.push({
id, id,
@ -393,6 +402,33 @@ export function getArtifactsToUpdate({
artifact: { ...path, sweepId: id }, artifact: { ...path, sweepId: id },
}) })
return returnArr return returnArr
} else if (
cmd.type === 'loft' &&
response.type === 'modeling' &&
response.data.modeling_response.type === 'loft'
) {
returnArr.push({
id,
artifact: {
type: 'sweep',
subType: 'loft',
id,
// TODO: make sure to revisit this choice, don't think it matters for now
pathId: cmd.section_ids[0],
surfaceIds: [],
edgeIds: [],
codeRef: { range, pathToNode },
},
})
for (const sectionId of cmd.section_ids) {
const path = getArtifact(sectionId)
if (path?.type === 'path')
returnArr.push({
id: sectionId,
artifact: { ...path, sweepId: id },
})
}
return returnArr
} else if ( } else if (
cmd.type === 'solid3d_get_extrusion_face_info' && cmd.type === 'solid3d_get_extrusion_face_info' &&
response?.type === 'modeling' && response?.type === 'modeling' &&
@ -414,7 +450,8 @@ export function getArtifactsToUpdate({
id: face_id, id: face_id,
segId: curve_id, segId: curve_id,
edgeCutEdgeIds: [], edgeCutEdgeIds: [],
sweepId: path.sweepId, // TODO: Add explicit check for sweepId. Should never use ''
sweepId: path.sweepId ?? '',
pathIds: [], pathIds: [],
}, },
}) })
@ -422,6 +459,7 @@ export function getArtifactsToUpdate({
id: curve_id, id: curve_id,
artifact: { ...seg, surfaceId: face_id }, artifact: { ...seg, surfaceId: face_id },
}) })
if (path.sweepId) {
const sweep = getArtifact(path.sweepId) const sweep = getArtifact(path.sweepId)
if (sweep?.type === 'sweep') { if (sweep?.type === 'sweep') {
returnArr.push({ returnArr.push({
@ -435,6 +473,7 @@ export function getArtifactsToUpdate({
} }
} }
} }
}
) )
response.data.modeling_response.data.faces.forEach(({ cap, face_id }) => { response.data.modeling_response.data.faces.forEach(({ cap, face_id }) => {
if ((cap === 'top' || cap === 'bottom') && face_id) { if ((cap === 'top' || cap === 'bottom') && face_id) {
@ -447,10 +486,12 @@ export function getArtifactsToUpdate({
id: face_id, id: face_id,
subType: cap === 'bottom' ? 'start' : 'end', subType: cap === 'bottom' ? 'start' : 'end',
edgeCutEdgeIds: [], edgeCutEdgeIds: [],
sweepId: path.sweepId, // TODO: Add explicit check for sweepId. Should never use ''
sweepId: path.sweepId ?? '',
pathIds: [], pathIds: [],
}, },
}) })
if (path.sweepId) {
const sweep = getArtifact(path.sweepId) const sweep = getArtifact(path.sweepId)
if (sweep?.type !== 'sweep') return if (sweep?.type !== 'sweep') return
returnArr.push({ returnArr.push({
@ -462,6 +503,7 @@ export function getArtifactsToUpdate({
}) })
} }
} }
}
}) })
return returnArr return returnArr
} else if ( } else if (
@ -497,7 +539,8 @@ export function getArtifactsToUpdate({
? 'adjacent' ? 'adjacent'
: 'opposite', : 'opposite',
segId: cmd.edge_id, segId: cmd.edge_id,
sweepId: path.sweepId, // TODO: Add explicit check for sweepId. Should never use ''
sweepId: path.sweepId ?? '',
}, },
}, },
{ {
@ -508,7 +551,7 @@ export function getArtifactsToUpdate({
}, },
}, },
{ {
id: path.sweepId, id: sweep.id,
artifact: { artifact: {
...sweep, ...sweep,
edgeIds: [response.data.modeling_response.data.edge], edgeIds: [response.data.modeling_response.data.edge],
@ -524,7 +567,7 @@ export function getArtifactsToUpdate({
subType: cmd.cut_type, subType: cmd.cut_type,
consumedEdgeId: cmd.edge_id, consumedEdgeId: cmd.edge_id,
edgeIds: [], edgeIds: [],
surfaceId: '', surfaceId: undefined,
codeRef: { range, pathToNode }, codeRef: { range, pathToNode },
}, },
}) })
@ -686,10 +729,12 @@ export function expandSegment(
{ key: segment.pathId, types: ['path'] }, { key: segment.pathId, types: ['path'] },
artifactGraph artifactGraph
) )
const surf = getArtifactOfTypes( const surf = segment.surfaceId
? getArtifactOfTypes(
{ key: segment.surfaceId, types: ['wall'] }, { key: segment.surfaceId, types: ['wall'] },
artifactGraph artifactGraph
) )
: undefined
const edges = getArtifactsOfTypes( const edges = getArtifactsOfTypes(
{ keys: segment.edgeIds, types: ['sweepEdge'] }, { keys: segment.edgeIds, types: ['sweepEdge'] },
artifactGraph artifactGraph
@ -806,6 +851,7 @@ export function getSweepFromSuspectedSweepSurface(
artifactGraph artifactGraph
) )
if (err(path)) return path if (err(path)) return path
if (!path.sweepId) return new Error('Path does not have a sweepId')
return getArtifactOfTypes( return getArtifactOfTypes(
{ key: path.sweepId, types: ['sweep'] }, { key: path.sweepId, types: ['sweep'] },
artifactGraph artifactGraph
@ -823,6 +869,7 @@ export function getSweepFromSuspectedPath(
): SweepArtifact | Error { ): SweepArtifact | Error {
const path = getArtifactOfTypes({ key: id, types: ['path'] }, artifactGraph) const path = getArtifactOfTypes({ key: id, types: ['path'] }, artifactGraph)
if (err(path)) return path if (err(path)) return path
if (!path.sweepId) return new Error('Path does not have a sweepId')
return getArtifactOfTypes( return getArtifactOfTypes(
{ key: path.sweepId, types: ['sweep'] }, { key: path.sweepId, types: ['sweep'] },
artifactGraph artifactGraph

View File

@ -1,10 +1,10 @@
import { import {
ArtifactCommand,
defaultRustSourceRange, defaultRustSourceRange,
defaultSourceRange, ExecState,
Program, Program,
RustSourceRange, RustSourceRange,
SourceRange, SourceRange,
sourceRangeFromRust,
} from 'lang/wasm' } from 'lang/wasm'
import { VITE_KC_API_WS_MODELING_URL, VITE_KC_DEV_TOKEN } from 'env' import { VITE_KC_API_WS_MODELING_URL, VITE_KC_DEV_TOKEN } from 'env'
import { Models } from '@kittycad/lib' import { Models } from '@kittycad/lib'
@ -20,7 +20,6 @@ import { DefaultPlanes } from 'wasm-lib/kcl/bindings/DefaultPlanes'
import { import {
ArtifactGraph, ArtifactGraph,
EngineCommand, EngineCommand,
OrderedCommand,
ResponseMap, ResponseMap,
createArtifactGraph, createArtifactGraph,
} from 'lang/std/artifactGraph' } from 'lang/std/artifactGraph'
@ -37,6 +36,7 @@ import { KclManager } from 'lang/KclSingleton'
import { reportRejection } from 'lib/trap' import { reportRejection } from 'lib/trap'
import { markOnce } from 'lib/performance' import { markOnce } from 'lib/performance'
import { MachineManager } from 'components/MachineManagerProvider' import { MachineManager } from 'components/MachineManagerProvider'
import { Node } from 'wasm-lib/kcl/bindings/Node'
// TODO(paultag): This ought to be tweakable. // TODO(paultag): This ought to be tweakable.
const pingIntervalMs = 5_000 const pingIntervalMs = 5_000
@ -1303,7 +1303,7 @@ export enum EngineCommandManagerEvents {
* *
* As commands are send their state is tracked in {@link pendingCommands} and clear as soon as we receive a response. * As commands are send their state is tracked in {@link pendingCommands} and clear as soon as we receive a response.
* *
* Also all commands that are sent are kept track of in {@link orderedCommands} and their responses are kept in {@link responseMap} * Also all commands that are sent are kept track of in WASM artifactCommands and their responses are kept in {@link responseMap}
* Both of these data structures are used to process the {@link artifactGraph}. * Both of these data structures are used to process the {@link artifactGraph}.
*/ */
@ -1329,12 +1329,7 @@ export class EngineCommandManager extends EventTarget {
[commandId: string]: PendingMessage [commandId: string]: PendingMessage
} = {} } = {}
/** /**
* The orderedCommands array of all the the commands sent to the engine, un-folded from batches, and made into one long * A map of the responses to the WASM artifactCommands, when processing the commands into the artifactGraph, this response map allow
* list of the individual commands, this is used to process all the commands into the artifactGraph
*/
orderedCommands: Array<OrderedCommand> = []
/**
* A map of the responses to the {@link orderedCommands}, when processing the commands into the artifactGraph, this response map allow
* us to look up the response by command id * us to look up the response by command id
*/ */
responseMap: ResponseMap = {} responseMap: ResponseMap = {}
@ -1830,7 +1825,6 @@ export class EngineCommandManager extends EventTarget {
} }
} }
async startNewSession() { async startNewSession() {
this.orderedCommands = []
this.responseMap = {} this.responseMap = {}
await this.initPlanes() await this.initPlanes()
} }
@ -2073,28 +2067,6 @@ export class EngineCommandManager extends EventTarget {
isSceneCommand, isSceneCommand,
} }
if (message.command.type === 'modeling_cmd_req') {
this.orderedCommands.push({
command: message.command,
range: sourceRangeFromRust(message.range),
})
} else if (message.command.type === 'modeling_cmd_batch_req') {
message.command.requests.forEach((req) => {
const cmdId = req.cmd_id || ''
const range = cmdId
? sourceRangeFromRust(message.idToRangeMap[cmdId])
: defaultSourceRange()
const cmd: EngineCommand = {
type: 'modeling_cmd_req',
cmd_id: req.cmd_id,
cmd: req.cmd,
}
this.orderedCommands.push({
command: cmd,
range,
})
})
}
this.engineConnection?.send(message.command) this.engineConnection?.send(message.command)
return promise return promise
} }
@ -2115,11 +2087,16 @@ export class EngineCommandManager extends EventTarget {
Object.values(this.pendingCommands).map((a) => a.promise) Object.values(this.pendingCommands).map((a) => a.promise)
) )
} }
updateArtifactGraph(ast: Program) { updateArtifactGraph(
ast: Node<Program>,
artifactCommands: ArtifactCommand[],
execStateArtifacts: ExecState['artifacts']
) {
this.artifactGraph = createArtifactGraph({ this.artifactGraph = createArtifactGraph({
orderedCommands: this.orderedCommands, artifactCommands,
responseMap: this.responseMap, responseMap: this.responseMap,
ast, ast,
execStateArtifacts,
}) })
// TODO check if these still need to be deferred once e2e tests are working again. // TODO check if these still need to be deferred once e2e tests are working again.
if (this.artifactGraph.size) { if (this.artifactGraph.size) {

View File

@ -1,5 +1,21 @@
import { isDesktop } from 'lib/isDesktop' import { isDesktop } from 'lib/isDesktop'
// Polyfill window.electron fs functions as needed when in a nodejs context
// (INTENDED FOR VITEST SHINANGANS.)
if (process.env.NODE_ENV === 'test' && process.env.VITEST) {
const fs = require('node:fs/promises')
const path = require('node:path')
Object.assign(window, {
electron: {
readFile: fs.readFile,
stat: fs.stat,
readdir: fs.readdir,
path,
process: {},
},
})
}
/// FileSystemManager is a class that provides a way to read files from the local file system. /// FileSystemManager is a class that provides a way to read files from the local file system.
/// It assumes that you are in a project since it is solely used by the std lib /// It assumes that you are in a project since it is solely used by the std lib
/// when executing code. /// when executing code.
@ -19,13 +35,9 @@ class FileSystemManager {
} }
async readFile(path: string): Promise<Uint8Array> { async readFile(path: string): Promise<Uint8Array> {
// Using local file system only works from desktop. // Using local file system only works from desktop and nodejs
if (!isDesktop()) { if (!window?.electron?.readFile) {
return Promise.reject( return Promise.reject(new Error('No polyfill found for this function'))
new Error(
'This function can only be called from the desktop application'
)
)
} }
return this.join(this.dir, path).then((filePath) => { return this.join(this.dir, path).then((filePath) => {
@ -35,12 +47,8 @@ class FileSystemManager {
async exists(path: string): Promise<boolean | void> { async exists(path: string): Promise<boolean | void> {
// Using local file system only works from desktop. // Using local file system only works from desktop.
if (!isDesktop()) { if (!window?.electron?.stat) {
return Promise.reject( return Promise.reject(new Error('No polyfill found for this function'))
new Error(
'This function can only be called from the desktop application'
)
)
} }
return this.join(this.dir, path).then(async (file) => { return this.join(this.dir, path).then(async (file) => {
@ -57,12 +65,8 @@ class FileSystemManager {
async getAllFiles(path: string): Promise<string[] | void> { async getAllFiles(path: string): Promise<string[] | void> {
// Using local file system only works from desktop. // Using local file system only works from desktop.
if (!isDesktop()) { if (!window?.electron?.readdir) {
return Promise.reject( return Promise.reject(new Error('No polyfill found for this function'))
new Error(
'This function can only be called from the desktop application'
)
)
} }
return this.join(this.dir, path).then((filepath) => { return this.join(this.dir, path).then((filepath) => {

View File

@ -1,4 +1,5 @@
import init, { import {
init,
parse_wasm, parse_wasm,
recast_wasm, recast_wasm,
execute, execute,
@ -16,7 +17,9 @@ import init, {
default_project_settings, default_project_settings,
base64_decode, base64_decode,
clear_scene_and_bust_cache, clear_scene_and_bust_cache,
} from '../wasm-lib/pkg/wasm_lib' reloadModule,
} from 'lib/wasm_lib_wrapper'
import { KCLError } from './errors' import { KCLError } from './errors'
import { KclError as RustKclError } from '../wasm-lib/kcl/bindings/KclError' import { KclError as RustKclError } from '../wasm-lib/kcl/bindings/KclError'
import { EngineCommandManager } from './std/engineConnection' import { EngineCommandManager } from './std/engineConnection'
@ -45,7 +48,13 @@ import { SourceRange as RustSourceRange } from 'wasm-lib/kcl/bindings/SourceRang
import { getAllCurrentSettings } from 'lib/settings/settingsUtils' import { getAllCurrentSettings } from 'lib/settings/settingsUtils'
import { Operation } from 'wasm-lib/kcl/bindings/Operation' import { Operation } from 'wasm-lib/kcl/bindings/Operation'
import { KclErrorWithOutputs } from 'wasm-lib/kcl/bindings/KclErrorWithOutputs' import { KclErrorWithOutputs } from 'wasm-lib/kcl/bindings/KclErrorWithOutputs'
import { Artifact } from 'wasm-lib/kcl/bindings/Artifact'
import { ArtifactId } from 'wasm-lib/kcl/bindings/ArtifactId'
import { ArtifactCommand } from 'wasm-lib/kcl/bindings/ArtifactCommand'
export type { Artifact } from 'wasm-lib/kcl/bindings/Artifact'
export type { ArtifactCommand } from 'wasm-lib/kcl/bindings/ArtifactCommand'
export type { ArtifactId } from 'wasm-lib/kcl/bindings/ArtifactId'
export type { Configuration } from 'wasm-lib/kcl/bindings/Configuration' export type { Configuration } from 'wasm-lib/kcl/bindings/Configuration'
export type { Program } from '../wasm-lib/kcl/bindings/Program' export type { Program } from '../wasm-lib/kcl/bindings/Program'
export type { Expr } from '../wasm-lib/kcl/bindings/Expr' export type { Expr } from '../wasm-lib/kcl/bindings/Expr'
@ -144,6 +153,7 @@ export const wasmUrl = () => {
// Initialise the wasm module. // Initialise the wasm module.
const initialise = async () => { const initialise = async () => {
try { try {
await reloadModule()
const fullUrl = wasmUrl() const fullUrl = wasmUrl()
const input = await fetch(fullUrl) const input = await fetch(fullUrl)
const buffer = await input.arrayBuffer() const buffer = await input.arrayBuffer()
@ -223,6 +233,7 @@ export const parse = (code: string | Error): ParseResult | Error => {
parsed.kind, parsed.kind,
parsed.msg, parsed.msg,
sourceRangeFromRust(parsed.sourceRanges[0]), sourceRangeFromRust(parsed.sourceRanges[0]),
[],
[] []
) )
} }
@ -247,6 +258,8 @@ export const isPathToNodeNumber = (
export interface ExecState { export interface ExecState {
memory: ProgramMemory memory: ProgramMemory
operations: Operation[] operations: Operation[]
artifacts: { [key in ArtifactId]?: Artifact }
artifactCommands: ArtifactCommand[]
} }
/** /**
@ -257,6 +270,8 @@ export function emptyExecState(): ExecState {
return { return {
memory: ProgramMemory.empty(), memory: ProgramMemory.empty(),
operations: [], operations: [],
artifacts: {},
artifactCommands: [],
} }
} }
@ -264,6 +279,8 @@ function execStateFromRust(execOutcome: RustExecOutcome): ExecState {
return { return {
memory: ProgramMemory.fromRaw(execOutcome.memory), memory: ProgramMemory.fromRaw(execOutcome.memory),
operations: execOutcome.operations, operations: execOutcome.operations,
artifacts: execOutcome.artifacts,
artifactCommands: execOutcome.artifactCommands,
} }
} }
@ -506,22 +523,6 @@ export const executor = async (
return Promise.reject(programMemoryOverride) return Promise.reject(programMemoryOverride)
// eslint-disable-next-line @typescript-eslint/no-floating-promises // eslint-disable-next-line @typescript-eslint/no-floating-promises
engineCommandManager.startNewSession()
const _programMemory = await _executor(
node,
engineCommandManager,
programMemoryOverride
)
await engineCommandManager.waitForAllCommands()
return _programMemory
}
export const _executor = async (
node: Node<Program>,
engineCommandManager: EngineCommandManager,
programMemoryOverride: ProgramMemory | Error | null = null
): Promise<ExecState> => {
if (programMemoryOverride !== null && err(programMemoryOverride)) if (programMemoryOverride !== null && err(programMemoryOverride))
return Promise.reject(programMemoryOverride) return Promise.reject(programMemoryOverride)
@ -550,7 +551,8 @@ export const _executor = async (
parsed.error.kind, parsed.error.kind,
parsed.error.msg, parsed.error.msg,
sourceRangeFromRust(parsed.error.sourceRanges[0]), sourceRangeFromRust(parsed.error.sourceRanges[0]),
parsed.operations parsed.operations,
parsed.artifactCommands
) )
return Promise.reject(kclError) return Promise.reject(kclError)
@ -610,6 +612,7 @@ export const modifyAstForSketch = async (
parsed.kind, parsed.kind,
parsed.msg, parsed.msg,
sourceRangeFromRust(parsed.sourceRanges[0]), sourceRangeFromRust(parsed.sourceRanges[0]),
[],
[] []
) )
@ -679,6 +682,7 @@ export function programMemoryInit(): ProgramMemory | Error {
parsed.kind, parsed.kind,
parsed.msg, parsed.msg,
sourceRangeFromRust(parsed.sourceRanges[0]), sourceRangeFromRust(parsed.sourceRanges[0]),
[],
[] []
) )
} }

View File

@ -37,6 +37,10 @@ export type ModelingCommandSchema = {
// result: (typeof EXTRUSION_RESULTS)[number] // result: (typeof EXTRUSION_RESULTS)[number]
distance: KclCommandValue distance: KclCommandValue
} }
Sweep: {
path: Selections
profile: Selections
}
Loft: { Loft: {
selection: Selections selection: Selections
} }
@ -47,7 +51,9 @@ export type ModelingCommandSchema = {
Revolve: { Revolve: {
selection: Selections selection: Selections
angle: KclCommandValue angle: KclCommandValue
axis: Selections axisOrEdge: string
axis: string
edge: Selections
} }
Fillet: { Fillet: {
// todo // todo
@ -290,6 +296,33 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
}, },
}, },
}, },
Sweep: {
description:
'Create a 3D body by moving a sketch region along an arbitrary path.',
icon: 'sweep',
status: 'development',
needsReview: true,
args: {
profile: {
inputType: 'selection',
selectionTypes: ['solid2D'],
required: true,
skip: true,
multiple: false,
// TODO: add dry-run validation
warningMessage:
'The sweep workflow is new and under tested. Please break it and report issues.',
},
path: {
inputType: 'selection',
selectionTypes: ['segment', 'path'],
required: true,
skip: true,
multiple: false,
// TODO: add dry-run validation
},
},
},
Loft: { Loft: {
description: 'Create a 3D body by blending between two or more sketches', description: 'Create a 3D body by blending between two or more sketches',
icon: 'loft', icon: 'loft',
@ -324,10 +357,10 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
}, },
}, },
}, },
// TODO: Update this configuration, copied from extrude for MVP of revolve, specifically the args.selection
Revolve: { Revolve: {
description: 'Create a 3D body by rotating a sketch region about an axis.', description: 'Create a 3D body by rotating a sketch region about an axis.',
icon: 'revolve', icon: 'revolve',
status: 'development',
needsReview: true, needsReview: true,
args: { args: {
selection: { selection: {
@ -336,9 +369,34 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
multiple: false, // TODO: multiple selection multiple: false, // TODO: multiple selection
required: true, required: true,
skip: true, skip: true,
warningMessage:
'The revolve workflow is new and under tested. Please break it and report issues.',
},
axisOrEdge: {
inputType: 'options',
required: true,
defaultValue: 'Axis',
options: [
{ name: 'Axis', isCurrent: true, value: 'Axis' },
{ name: 'Edge', isCurrent: false, value: 'Edge' },
],
}, },
axis: { axis: {
required: true, required: (commandContext) =>
['Axis'].includes(
commandContext.argumentsToSubmit.axisOrEdge as string
),
inputType: 'options',
options: [
{ name: 'X Axis', isCurrent: true, value: 'X' },
{ name: 'Y Axis', isCurrent: false, value: 'Y' },
],
},
edge: {
required: (commandContext) =>
['Edge'].includes(
commandContext.argumentsToSubmit.axisOrEdge as string
),
inputType: 'selection', inputType: 'selection',
selectionTypes: ['segment', 'sweepEdge', 'edgeCutEdge'], selectionTypes: ['segment', 'sweepEdge', 'edgeCutEdge'],
multiple: false, multiple: false,

View File

@ -68,7 +68,7 @@ export const revolveAxisValidator = async ({
} }
const sketchSelection = artifact.pathId const sketchSelection = artifact.pathId
let edgeSelection = data.axis.graphSelections[0].artifact?.id let edgeSelection = data.edge.graphSelections[0].artifact?.id
if (!sketchSelection) { if (!sketchSelection) {
return 'Unable to revolve, sketch is missing' return 'Unable to revolve, sketch is missing'
@ -101,7 +101,7 @@ export const revolveAxisValidator = async ({
return true return true
} else { } else {
// return error message for the toast // return error message for the toast
return 'Unable to revolve with selected axis' return 'Unable to revolve with selected edge'
} }
} }

View File

@ -53,6 +53,7 @@ export const KCL_DEFAULT_CONSTANT_PREFIXES = {
SKETCH: 'sketch', SKETCH: 'sketch',
EXTRUDE: 'extrude', EXTRUDE: 'extrude',
LOFT: 'loft', LOFT: 'loft',
SWEEP: 'sweep',
SHELL: 'shell', SHELL: 'shell',
SEGMENT: 'seg', SEGMENT: 'seg',
REVOLVE: 'revolve', REVOLVE: 'revolve',

View File

@ -15,6 +15,7 @@ import {
StateMachineCommandSetSchema, StateMachineCommandSetSchema,
} from './commandTypes' } from './commandTypes'
import { DEV } from 'env' import { DEV } from 'env'
import { IS_NIGHTLY_OR_DEBUG } from 'routes/Settings'
interface CreateMachineCommandProps< interface CreateMachineCommandProps<
T extends AnyStateMachine, T extends AnyStateMachine,
@ -84,7 +85,7 @@ export function createMachineCommand<
} else if ('status' in commandConfig) { } else if ('status' in commandConfig) {
const { status } = commandConfig const { status } = commandConfig
if (status === 'inactive') return null if (status === 'inactive') return null
if (status === 'development' && !DEV) return null if (status === 'development' && !(DEV || IS_NIGHTLY_OR_DEBUG)) return null
} }
const icon = ('icon' in commandConfig && commandConfig.icon) || undefined const icon = ('icon' in commandConfig && commandConfig.icon) || undefined

51
src/lib/exceptions.ts Normal file
View File

@ -0,0 +1,51 @@
import { kclManager } from 'lib/singletons'
import { reloadModule, getModule } from 'lib/wasm_lib_wrapper'
import toast from 'react-hot-toast'
import { reportRejection } from './trap'
let initialized = false
/**
* WASM/Rust runtime can panic and the original try/catch/finally blocks will not trigger
* on the await promise. The interface will killed. This means we need to catch the error at
* the global/DOM level. This will have to interface with whatever controlflow that needs to be picked up
* within the error branch in the typescript to cover the application state.
*/
export const initializeWindowExceptionHandler = () => {
if (window && !initialized) {
window.addEventListener('error', (event) => {
void (async () => {
if (matchImportExportErrorCrash(event.message)) {
// do global singleton cleanup
kclManager.executeAstCleanUp()
toast.error(
'You have hit a KCL execution bug! Put your KCL code in a github issue to help us resolve this bug.'
)
try {
await reloadModule()
await getModule().default()
} catch (e) {
console.error('Failed to initialize wasm_lib')
console.error(e)
}
}
})().catch(reportRejection)
})
// Make sure we only initialize this event listener once
initialized = true
} else {
console.error(
`Failed to initialize, window: ${window}, initialized:${initialized}`
)
}
}
/**
* Specifically match a substring of the message error to detect an import export runtime issue
* when the WASM runtime panics
*/
const matchImportExportErrorCrash = (message: string): boolean => {
// called `Result::unwrap_throw()` on an `Err` value
const substringError = '`Result::unwrap_throw()` on an `Err` value'
return message.indexOf(substringError) !== -1 ? true : false
}

View File

@ -3,6 +3,8 @@ import { isDesktop } from './isDesktop'
export type KclSamplesManifestItem = { export type KclSamplesManifestItem = {
file: string file: string
pathFromProjectDirectoryToFirstFile: string
multipleFiles: boolean
title: string title: string
description: string description: string
} }

View File

@ -49,20 +49,30 @@ export function kclCommands(
if (!data?.sample) { if (!data?.sample) {
return return
} }
const pathParts = data.sample.split('/')
const projectPathPart = pathParts[0]
const primaryKclFile = pathParts[1]
const sampleCodeUrl = `https://raw.githubusercontent.com/KittyCAD/kcl-samples/main/${encodeURIComponent( const sampleCodeUrl = `https://raw.githubusercontent.com/KittyCAD/kcl-samples/main/${encodeURIComponent(
data.sample.replace(FILE_EXT, '') projectPathPart
)}/${encodeURIComponent(data.sample)}` )}/${encodeURIComponent(primaryKclFile)}`
const sampleSettingsFileUrl = `https://raw.githubusercontent.com/KittyCAD/kcl-samples/main/${encodeURIComponent( const sampleSettingsFileUrl = `https://raw.githubusercontent.com/KittyCAD/kcl-samples/main/${encodeURIComponent(
data.sample.replace(FILE_EXT, '') projectPathPart
)}/${PROJECT_SETTINGS_FILE_NAME}` )}/${PROJECT_SETTINGS_FILE_NAME}`
Promise.all([fetch(sampleCodeUrl), fetch(sampleSettingsFileUrl)]) Promise.allSettled([fetch(sampleCodeUrl), fetch(sampleSettingsFileUrl)])
.then((results) => {
const a =
'value' in results[0] ? results[0].value : results[0].reason
const b =
'value' in results[1] ? results[1].value : results[1].reason
return [a, b]
})
.then( .then(
async ([ async ([
codeResponse, codeResponse,
settingsResponse, settingsResponse,
]): Promise<OnSubmitProps> => { ]): Promise<OnSubmitProps> => {
if (!(codeResponse.ok && settingsResponse.ok)) { if (!codeResponse.ok) {
console.error( console.error(
'Failed to fetch sample code:', 'Failed to fetch sample code:',
codeResponse.statusText codeResponse.statusText
@ -70,20 +80,24 @@ export function kclCommands(
return Promise.reject(new Error('Failed to fetch sample code')) return Promise.reject(new Error('Failed to fetch sample code'))
} }
const code = await codeResponse.text() const code = await codeResponse.text()
const parsedProjectSettings = parseProjectSettings(
await settingsResponse.text() // It's possible that a sample doesn't have a project.toml
) // associated with it.
let projectSettingsPayload: ReturnType< let projectSettingsPayload: ReturnType<
typeof projectConfigurationToSettingsPayload typeof projectConfigurationToSettingsPayload
> = {} > = {}
if (!err(parsedProjectSettings)) { if (settingsResponse.ok) {
projectSettingsPayload = projectConfigurationToSettingsPayload( const parsedProjectSettings = parseProjectSettings(
parsedProjectSettings await settingsResponse.text()
) )
if (!err(parsedProjectSettings)) {
projectSettingsPayload =
projectConfigurationToSettingsPayload(parsedProjectSettings)
}
} }
return { return {
sampleName: data.sample, sampleName: data.sample.split('/')[0] + FILE_EXT,
code, code,
method: data.method, method: data.method,
sampleUnits: sampleUnits:

View File

@ -155,7 +155,7 @@ export interface components {
color?: string | null color?: string | null
/** @description The material that the filament is made of. */ /** @description The material that the filament is made of. */
material: components['schemas']['FilamentMaterial'] material: components['schemas']['FilamentMaterial']
/** @description The name of the filament, this is likely specfic to the manufacturer. */ /** @description The name of the filament, this is likely specific to the manufacturer. */
name?: string | null name?: string | null
} }
/** @description The material that the filament is made of. */ /** @description The material that the filament is made of. */

52
src/lib/markdown.ts Normal file
View File

@ -0,0 +1,52 @@
import { MarkedOptions, Renderer, unescape } from '@ts-stack/markdown'
import { openExternalBrowserIfDesktop } from './openWindow'
/**
* Main goal of this custom renderer is to prevent links from changing the current location
* this is specially important for the desktop app.
*/
export class SafeRenderer extends Renderer {
constructor(options: MarkedOptions) {
super(options)
// Attach a global function for non-react anchor elements that need safe navigation
window.openExternalLink = (e: React.MouseEvent<HTMLAnchorElement>) => {
openExternalBrowserIfDesktop()(e)
}
}
// Extended from https://github.com/ts-stack/markdown/blob/c5c1925c1153ca2fe9051c356ef0ddc60b3e1d6a/packages/markdown/src/renderer.ts#L116
link(href: string, title: string, text: string): string {
if (this.options.sanitize) {
let prot: string
try {
prot = decodeURIComponent(unescape(href))
.replace(/[^\w:]/g, '')
.toLowerCase()
} catch (e) {
return text
}
if (
// eslint-disable-next-line no-script-url
prot.indexOf('javascript:') === 0 ||
prot.indexOf('vbscript:') === 0 ||
prot.indexOf('data:') === 0
) {
return text
}
}
let out =
'<a onclick="openExternalLink(event)" target="_blank" href="' + href + '"'
if (title) {
out += ' title="' + title + '"'
}
out += '>' + text + '</a>'
return out
}
}

View File

@ -137,7 +137,7 @@ See later source ranges for more context. about the sweep`,
{ key: artifact.pathId, types: ['path'] }, { key: artifact.pathId, types: ['path'] },
artifactGraph artifactGraph
) )
if (!err(path)) { if (!err(path) && path.sweepId) {
const sweep = getArtifactOfTypes( const sweep = getArtifactOfTypes(
{ key: path.sweepId, types: ['sweep'] }, { key: path.sweepId, types: ['sweep'] },
artifactGraph artifactGraph

View File

@ -670,6 +670,7 @@ export function codeToIdSelections(
} }
} }
if (type === 'extrude-wall' && entry.artifact.type === 'segment') { if (type === 'extrude-wall' && entry.artifact.type === 'segment') {
if (!entry.artifact.surfaceId) return
const wall = engineCommandManager.artifactGraph.get( const wall = engineCommandManager.artifactGraph.get(
entry.artifact.surfaceId entry.artifact.surfaceId
) )
@ -714,6 +715,7 @@ export function codeToIdSelections(
(type === 'end-cap' || type === 'start-cap') && (type === 'end-cap' || type === 'start-cap') &&
entry.artifact.type === 'path' entry.artifact.type === 'path'
) { ) {
if (!entry.artifact.sweepId) return
const extrusion = getArtifactOfTypes( const extrusion = getArtifactOfTypes(
{ {
key: entry.artifact.sweepId, key: entry.artifact.sweepId,

View File

@ -1,20 +1,16 @@
import { import {
Program, Program,
ProgramMemory, ProgramMemory,
_executor, executor,
SourceRange, SourceRange,
ExecState, ExecState,
} from '../lang/wasm' } from '../lang/wasm'
import { import { EngineCommandManager } from 'lang/std/engineConnection'
EngineCommandManager,
EngineCommandManagerEvents,
} from 'lang/std/engineConnection'
import { EngineCommand } from 'lang/std/artifactGraph' import { EngineCommand } from 'lang/std/artifactGraph'
import { Models } from '@kittycad/lib' import { Models } from '@kittycad/lib'
import { v4 as uuidv4 } from 'uuid' import { v4 as uuidv4 } from 'uuid'
import { DefaultPlanes } from 'wasm-lib/kcl/bindings/DefaultPlanes' import { DefaultPlanes } from 'wasm-lib/kcl/bindings/DefaultPlanes'
import { err, reportRejection } from 'lib/trap' import { err } from 'lib/trap'
import { toSync } from './utils'
import { Node } from 'wasm-lib/kcl/bindings/Node' import { Node } from 'wasm-lib/kcl/bindings/Node'
type WebSocketResponse = Models['WebSocketResponse_type'] type WebSocketResponse = Models['WebSocketResponse_type']
@ -94,36 +90,7 @@ export async function enginelessExecutor(
}) as any as EngineCommandManager }) as any as EngineCommandManager
// eslint-disable-next-line @typescript-eslint/no-floating-promises // eslint-disable-next-line @typescript-eslint/no-floating-promises
mockEngineCommandManager.startNewSession() mockEngineCommandManager.startNewSession()
const execState = await _executor(ast, mockEngineCommandManager, pmo) const execState = await executor(ast, mockEngineCommandManager, pmo)
await mockEngineCommandManager.waitForAllCommands() await mockEngineCommandManager.waitForAllCommands()
return execState return execState
} }
export async function executor(
ast: Node<Program>,
pmo: ProgramMemory = ProgramMemory.empty()
): Promise<ExecState> {
const engineCommandManager = new EngineCommandManager()
engineCommandManager.start({
setIsStreamReady: () => {},
setMediaStream: () => {},
width: 0,
height: 0,
makeDefaultPlanes: () => {
return new Promise((resolve) => resolve(defaultPlanes))
},
})
return new Promise((resolve) => {
engineCommandManager.addEventListener(
EngineCommandManagerEvents.SceneReady,
toSync(async () => {
// eslint-disable-next-line @typescript-eslint/no-floating-promises
engineCommandManager.startNewSession()
const execState = await _executor(ast, engineCommandManager, pmo)
await engineCommandManager.waitForAllCommands()
resolve(execState)
}, reportRejection)
)
})
}

View File

@ -8,6 +8,7 @@ import {
modelingMachine, modelingMachine,
pipeHasCircle, pipeHasCircle,
} from 'machines/modelingMachine' } from 'machines/modelingMachine'
import { IS_NIGHTLY_OR_DEBUG } from 'routes/Settings'
import { EventFrom, StateFrom } from 'xstate' import { EventFrom, StateFrom } from 'xstate'
export type ToolbarModeName = 'modeling' | 'sketching' export type ToolbarModeName = 'modeling' | 'sketching'
@ -103,7 +104,7 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
data: { name: 'Revolve', groupId: 'modeling' }, data: { name: 'Revolve', groupId: 'modeling' },
}), }),
icon: 'revolve', icon: 'revolve',
status: DEV ? 'available' : 'kcl-only', status: DEV || IS_NIGHTLY_OR_DEBUG ? 'available' : 'kcl-only',
title: 'Revolve', title: 'Revolve',
hotkey: 'R', hotkey: 'R',
description: description:
@ -118,17 +119,21 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
}, },
{ {
id: 'sweep', id: 'sweep',
onClick: () => console.error('Sweep not yet implemented'), onClick: ({ commandBarSend }) =>
commandBarSend({
type: 'Find and select command',
data: { name: 'Sweep', groupId: 'modeling' },
}),
icon: 'sweep', icon: 'sweep',
status: 'unavailable', status: DEV || IS_NIGHTLY_OR_DEBUG ? 'available' : 'kcl-only',
title: 'Sweep', title: 'Sweep',
hotkey: 'W', hotkey: 'W',
description: description:
'Create a 3D body by moving a sketch region along an arbitrary path.', 'Create a 3D body by moving a sketch region along an arbitrary path.',
links: [ links: [
{ {
label: 'GitHub discussion', label: 'KCL docs',
url: 'https://github.com/KittyCAD/modeling-app/discussions/498', url: 'https://zoo.dev/docs/kcl/sweep',
}, },
], ],
}, },
@ -161,7 +166,7 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
data: { name: 'Fillet', groupId: 'modeling' }, data: { name: 'Fillet', groupId: 'modeling' },
}), }),
icon: 'fillet3d', icon: 'fillet3d',
status: DEV ? 'available' : 'kcl-only', status: DEV || IS_NIGHTLY_OR_DEBUG ? 'available' : 'kcl-only',
title: 'Fillet', title: 'Fillet',
hotkey: 'F', hotkey: 'F',
description: 'Round the edges of a 3D solid.', description: 'Round the edges of a 3D solid.',

View File

@ -153,7 +153,10 @@ export function toSync<F extends AsyncFn<F>>(
) => void | PromiseLike<void | null | undefined> | null | undefined ) => void | PromiseLike<void | null | undefined> | null | undefined
): (...args: Parameters<F>) => void { ): (...args: Parameters<F>) => void {
return (...args: Parameters<F>) => { return (...args: Parameters<F>) => {
fn(...args).catch(onReject) void fn(...args).catch((...args) => {
console.error(...args)
return onReject(...args)
})
} }
} }
@ -342,7 +345,7 @@ export function onDragNumberCalculation(text: string, e: MouseEvent) {
) )
const newVal = roundOff(addition, precision) const newVal = roundOff(addition, precision)
if (isNaN(newVal)) { if (Number.isNaN(newVal)) {
return return
} }

108
src/lib/wasm_lib_wrapper.ts Normal file
View File

@ -0,0 +1,108 @@
/**
* This wrapper file is to enable reloading of the wasm_lib.js file.
* When the wasm instance bricks there is no API or interface to restart,
* restore, or re init the WebAssembly instance. The entire application would need
* to restart.
* A way to bypass this is by reloading the entire .js file so the global wasm variable
* gets reinitialized and we do not use that old reference
*/
import {
parse_wasm as ParseWasm,
recast_wasm as RecastWasm,
execute as Execute,
kcl_lint as KclLint,
modify_ast_for_sketch_wasm as ModifyAstForSketch,
is_points_ccw as IsPointsCcw,
get_tangential_arc_to_info as GetTangentialArcToInfo,
program_memory_init as ProgramMemoryInit,
make_default_planes as MakeDefaultPlanes,
coredump as CoreDump,
toml_stringify as TomlStringify,
default_app_settings as DefaultAppSettings,
parse_app_settings as ParseAppSettings,
parse_project_settings as ParseProjectSettings,
default_project_settings as DefaultProjectSettings,
base64_decode as Base64Decode,
clear_scene_and_bust_cache as ClearSceneAndBustCache,
} from '../wasm-lib/pkg/wasm_lib'
type ModuleType = typeof import('../wasm-lib/pkg/wasm_lib')
// Stores the result of the import of the wasm_lib file
let data: ModuleType
// Imports the .js file again which will clear the old import
// This allows us to reinitialize the wasm instance
export async function reloadModule() {
data = await import(`../wasm-lib/pkg/wasm_lib`)
}
export function getModule(): ModuleType {
return data
}
export async function init(module_or_path: any) {
return await getModule().default(module_or_path)
}
export const parse_wasm: typeof ParseWasm = (...args) => {
return getModule().parse_wasm(...args)
}
export const recast_wasm: typeof RecastWasm = (...args) => {
return getModule().recast_wasm(...args)
}
export const execute: typeof Execute = (...args) => {
return getModule().execute(...args)
}
export const kcl_lint: typeof KclLint = (...args) => {
return getModule().kcl_lint(...args)
}
export const modify_ast_for_sketch_wasm: typeof ModifyAstForSketch = (
...args
) => {
return getModule().modify_ast_for_sketch_wasm(...args)
}
export const is_points_ccw: typeof IsPointsCcw = (...args) => {
return getModule().is_points_ccw(...args)
}
export const get_tangential_arc_to_info: typeof GetTangentialArcToInfo = (
...args
) => {
return getModule().get_tangential_arc_to_info(...args)
}
export const program_memory_init: typeof ProgramMemoryInit = (...args) => {
return getModule().program_memory_init(...args)
}
export const make_default_planes: typeof MakeDefaultPlanes = (...args) => {
return getModule().make_default_planes(...args)
}
export const coredump: typeof CoreDump = (...args) => {
return getModule().coredump(...args)
}
export const toml_stringify: typeof TomlStringify = (...args) => {
return getModule().toml_stringify(...args)
}
export const default_app_settings: typeof DefaultAppSettings = (...args) => {
return getModule().default_app_settings(...args)
}
export const parse_app_settings: typeof ParseAppSettings = (...args) => {
return getModule().parse_app_settings(...args)
}
export const parse_project_settings: typeof ParseProjectSettings = (
...args
) => {
return getModule().parse_project_settings(...args)
}
export const default_project_settings: typeof DefaultProjectSettings = (
...args
) => {
return getModule().default_project_settings(...args)
}
export const base64_decode: typeof Base64Decode = (...args) => {
return getModule().base64_decode(...args)
}
export const clear_scene_and_bust_cache: typeof ClearSceneAndBustCache = (
...args
) => {
return getModule().clear_scene_and_bust_cache(...args)
}

View File

@ -45,6 +45,7 @@ import {
import { revolveSketch } from 'lang/modifyAst/addRevolve' import { revolveSketch } from 'lang/modifyAst/addRevolve'
import { import {
addOffsetPlane, addOffsetPlane,
addSweep,
deleteFromSelection, deleteFromSelection,
extrudeSketch, extrudeSketch,
loftSketches, loftSketches,
@ -266,6 +267,7 @@ export type ModelingMachineEvent =
| { type: 'Export'; data: ModelingCommandSchema['Export'] } | { type: 'Export'; data: ModelingCommandSchema['Export'] }
| { type: 'Make'; data: ModelingCommandSchema['Make'] } | { type: 'Make'; data: ModelingCommandSchema['Make'] }
| { type: 'Extrude'; data?: ModelingCommandSchema['Extrude'] } | { type: 'Extrude'; data?: ModelingCommandSchema['Extrude'] }
| { type: 'Sweep'; data?: ModelingCommandSchema['Sweep'] }
| { type: 'Loft'; data?: ModelingCommandSchema['Loft'] } | { type: 'Loft'; data?: ModelingCommandSchema['Loft'] }
| { type: 'Shell'; data?: ModelingCommandSchema['Shell'] } | { type: 'Shell'; data?: ModelingCommandSchema['Shell'] }
| { type: 'Revolve'; data?: ModelingCommandSchema['Revolve'] } | { type: 'Revolve'; data?: ModelingCommandSchema['Revolve'] }
@ -685,7 +687,7 @@ export const modelingMachine = setup({
if (event.type !== 'Revolve') return if (event.type !== 'Revolve') return
;(async () => { ;(async () => {
if (!event.data) return if (!event.data) return
const { selection, angle, axis } = event.data const { selection, angle, axis, edge, axisOrEdge } = event.data
let ast = kclManager.ast let ast = kclManager.ast
if ( if (
'variableName' in angle && 'variableName' in angle &&
@ -710,7 +712,9 @@ export const modelingMachine = setup({
'variableName' in angle 'variableName' in angle
? angle.variableIdentifierAst ? angle.variableIdentifierAst
: angle.valueAst, : angle.valueAst,
axis axisOrEdge,
axis,
edge
) )
if (trap(revolveSketchRes)) return if (trap(revolveSketchRes)) return
const { modifiedAst, pathToRevolveArg } = revolveSketchRes const { modifiedAst, pathToRevolveArg } = revolveSketchRes
@ -763,30 +767,6 @@ export const modelingMachine = setup({
await codeManager.updateEditorWithAstAndWriteToFile(modifiedAst) await codeManager.updateEditorWithAstAndWriteToFile(modifiedAst)
})().catch(reportRejection) })().catch(reportRejection)
}, },
'AST fillet': ({ event }) => {
if (event.type !== 'Fillet') return
if (!event.data) return
// Extract inputs
const ast = kclManager.ast
const { selection, radius } = event.data
const parameters: FilletParameters = {
type: EdgeTreatmentType.Fillet,
radius,
}
// Apply fillet to selection
const applyEdgeTreatmentToSelectionResult = applyEdgeTreatmentToSelection(
ast,
selection,
parameters
)
if (err(applyEdgeTreatmentToSelectionResult))
return applyEdgeTreatmentToSelectionResult
// eslint-disable-next-line @typescript-eslint/no-floating-promises
codeManager.updateEditorWithAstAndWriteToFile(kclManager.ast)
},
'set selection filter to curves only': () => { 'set selection filter to curves only': () => {
;(async () => { ;(async () => {
await engineCommandManager.sendSceneCommand({ await engineCommandManager.sendSceneCommand({
@ -1566,6 +1546,66 @@ export const modelingMachine = setup({
} }
} }
), ),
sweepAstMod: fromPromise(
async ({
input,
}: {
input: ModelingCommandSchema['Sweep'] | undefined
}) => {
if (!input) return new Error('No input provided')
// Extract inputs
const ast = kclManager.ast
const { profile, path } = input
// Find the profile declaration
const profileNodePath = getNodePathFromSourceRange(
ast,
profile.graphSelections[0].codeRef.range
)
const profileNode = getNodeFromPath<VariableDeclarator>(
ast,
profileNodePath,
'VariableDeclarator'
)
if (err(profileNode)) {
return new Error("Couldn't parse profile selection")
}
const profileDeclarator = profileNode.node
// Find the path declaration
const pathNodePath = getNodePathFromSourceRange(
ast,
path.graphSelections[0].codeRef.range
)
const pathNode = getNodeFromPath<VariableDeclarator>(
ast,
pathNodePath,
'VariableDeclarator'
)
if (err(pathNode)) {
return new Error("Couldn't parse path selection")
}
const pathDeclarator = pathNode.node
// Perform the sweep
const sweepRes = addSweep(ast, profileDeclarator, pathDeclarator)
const updateAstResult = await kclManager.updateAst(
sweepRes.modifiedAst,
true,
{
focusPath: [sweepRes.pathToNode],
}
)
await codeManager.updateEditorWithAstAndWriteToFile(
updateAstResult.newAst
)
if (updateAstResult?.selections) {
editorManager.selectRange(updateAstResult?.selections)
}
}
),
loftAstMod: fromPromise( loftAstMod: fromPromise(
async ({ async ({
input, input,
@ -1670,6 +1710,33 @@ export const modelingMachine = setup({
} }
} }
), ),
filletAstMod: fromPromise(
async ({
input,
}: {
input: ModelingCommandSchema['Fillet'] | undefined
}) => {
if (!input) {
return new Error('No input provided')
}
// Extract inputs
const ast = kclManager.ast
const { selection, radius } = input
const parameters: FilletParameters = {
type: EdgeTreatmentType.Fillet,
radius,
}
// Apply fillet to selection
const filletResult = await applyEdgeTreatmentToSelection(
ast,
selection,
parameters
)
if (err(filletResult)) return filletResult
}
),
'submit-prompt-edit': fromPromise( 'submit-prompt-edit': fromPromise(
async ({ input }: { input: ModelingCommandSchema['Prompt-to-edit'] }) => { async ({ input }: { input: ModelingCommandSchema['Prompt-to-edit'] }) => {
console.log('doing thing', input) console.log('doing thing', input)
@ -1734,6 +1801,11 @@ export const modelingMachine = setup({
reenter: false, reenter: false,
}, },
Sweep: {
target: 'Applying sweep',
reenter: true,
},
Loft: { Loft: {
target: 'Applying loft', target: 'Applying loft',
reenter: true, reenter: true,
@ -1745,9 +1817,8 @@ export const modelingMachine = setup({
}, },
Fillet: { Fillet: {
target: 'idle', target: 'Applying fillet',
actions: ['AST fillet'], reenter: true,
reenter: false,
}, },
Export: { Export: {
@ -2527,6 +2598,19 @@ export const modelingMachine = setup({
}, },
}, },
'Applying sweep': {
invoke: {
src: 'sweepAstMod',
id: 'sweepAstMod',
input: ({ event }) => {
if (event.type !== 'Sweep') return undefined
return event.data
},
onDone: ['idle'],
onError: ['idle'],
},
},
'Applying loft': { 'Applying loft': {
invoke: { invoke: {
src: 'loftAstMod', src: 'loftAstMod',
@ -2553,6 +2637,19 @@ export const modelingMachine = setup({
}, },
}, },
'Applying fillet': {
invoke: {
src: 'filletAstMod',
id: 'filletAstMod',
input: ({ event }) => {
if (event.type !== 'Fillet') return undefined
return event.data
},
onDone: ['idle'],
onError: ['idle'],
},
},
'Applying Prompt-to-edit': { 'Applying Prompt-to-edit': {
invoke: { invoke: {
src: 'submit-prompt-edit', src: 'submit-prompt-edit',

View File

@ -320,6 +320,11 @@ export function getAutoUpdater(): AppUpdater {
} }
app.on('ready', () => { app.on('ready', () => {
// Disable auto updater on non-versioned builds
if (packageJSON.version === '0.0.0') {
return
}
const autoUpdater = getAutoUpdater() const autoUpdater = getAutoUpdater()
// TODO: we're getting `Error: Response ends without calling any handlers` with our setup, // TODO: we're getting `Error: Response ends without calling any handlers` with our setup,
// so at the moment this isn't worth enabling // so at the moment this isn't worth enabling

View File

@ -41,13 +41,13 @@ export default function Export() {
export to almost any CAD software. export to almost any CAD software.
</p> </p>
<p className="my-4"> <p className="my-4">
Our teammate David is working on the file format, check out{' '} Our teammate Katie is working on the file format, check out{' '}
<a <a
href="https://www.youtube.com/watch?v=8SuW0qkYCZo" href="https://github.com/KhronosGroup/glTF/pull/2343"
target="_blank" target="_blank"
rel="noreferrer noopener" rel="noreferrer noopener"
> >
his talk with the Metaverse Standards Forum her standards proposal on GitHub
</a> </a>
! !
</p> </p>

View File

@ -32,6 +32,8 @@ export const PACKAGE_NAME = isDesktop()
export const IS_NIGHTLY = PACKAGE_NAME.indexOf('-nightly') > -1 export const IS_NIGHTLY = PACKAGE_NAME.indexOf('-nightly') > -1
export const IS_NIGHTLY_OR_DEBUG = IS_NIGHTLY || APP_VERSION === '0.0.0'
export function getReleaseUrl(version: string = APP_VERSION) { export function getReleaseUrl(version: string = APP_VERSION) {
return `https://github.com/KittyCAD/modeling-app/releases/tag/${ return `https://github.com/KittyCAD/modeling-app/releases/tag/${
IS_NIGHTLY ? 'nightly-' : '' IS_NIGHTLY ? 'nightly-' : ''

181
src/wasm-lib/Cargo.lock generated
View File

@ -176,18 +176,18 @@ checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.95", "syn 2.0.96",
] ]
[[package]] [[package]]
name = "async-trait" name = "async-trait"
version = "0.1.83" version = "0.1.85"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" checksum = "3f934833b4b7233644e5848f235df3f57ed8c80f1528a26c3dfa13d2147fa056"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.95", "syn 2.0.96",
] ]
[[package]] [[package]]
@ -204,7 +204,7 @@ checksum = "3c87f3f15e7794432337fc718554eaa4dc8f04c9677a950ffe366f20a162ae42"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.95", "syn 2.0.96",
] ]
[[package]] [[package]]
@ -474,7 +474,7 @@ dependencies = [
"heck 0.5.0", "heck 0.5.0",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.95", "syn 2.0.96",
] ]
[[package]] [[package]]
@ -665,7 +665,7 @@ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"strsim", "strsim",
"syn 2.0.95", "syn 2.0.96",
] ]
[[package]] [[package]]
@ -676,7 +676,7 @@ checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806"
dependencies = [ dependencies = [
"darling_core", "darling_core",
"quote", "quote",
"syn 2.0.95", "syn 2.0.96",
] ]
[[package]] [[package]]
@ -723,7 +723,7 @@ dependencies = [
[[package]] [[package]]
name = "derive-docs" name = "derive-docs"
version = "0.1.33" version = "0.1.34"
dependencies = [ dependencies = [
"Inflector", "Inflector",
"anyhow", "anyhow",
@ -737,7 +737,7 @@ dependencies = [
"rustfmt-wrapper", "rustfmt-wrapper",
"serde", "serde",
"serde_tokenstream", "serde_tokenstream",
"syn 2.0.95", "syn 2.0.96",
] ]
[[package]] [[package]]
@ -748,7 +748,38 @@ checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.95", "syn 2.0.96",
]
[[package]]
name = "derive_builder"
version = "0.20.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "507dfb09ea8b7fa618fcf76e953f4f5e192547945816d5358edffe39f6f94947"
dependencies = [
"derive_builder_macro",
]
[[package]]
name = "derive_builder_core"
version = "0.20.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8"
dependencies = [
"darling",
"proc-macro2",
"quote",
"syn 2.0.96",
]
[[package]]
name = "derive_builder_macro"
version = "0.20.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c"
dependencies = [
"derive_builder_core",
"syn 2.0.96",
] ]
[[package]] [[package]]
@ -791,7 +822,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.95", "syn 2.0.96",
] ]
[[package]] [[package]]
@ -829,7 +860,7 @@ checksum = "a1ab991c1362ac86c61ab6f556cff143daa22e5a15e4e189df818b2fd19fe65b"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.95", "syn 2.0.96",
] ]
[[package]] [[package]]
@ -990,7 +1021,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.95", "syn 2.0.96",
] ]
[[package]] [[package]]
@ -1086,7 +1117,7 @@ dependencies = [
"inflections", "inflections",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.95", "syn 2.0.96",
] ]
[[package]] [[package]]
@ -1132,17 +1163,18 @@ dependencies = [
[[package]] [[package]]
name = "handlebars" name = "handlebars"
version = "6.2.0" version = "6.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd4ccde012831f9a071a637b0d4e31df31c0f6c525784b35ae76a9ac6bc1e315" checksum = "3d6b224b95c1e668ac0270325ad563b2eef1469fbbb8959bc7c692c844b813d9"
dependencies = [ dependencies = [
"derive_builder",
"log", "log",
"num-order", "num-order",
"pest", "pest",
"pest_derive", "pest_derive",
"serde", "serde",
"serde_json", "serde_json",
"thiserror 1.0.68", "thiserror 2.0.0",
] ]
[[package]] [[package]]
@ -1494,7 +1526,7 @@ checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.95", "syn 2.0.96",
] ]
[[package]] [[package]]
@ -1684,7 +1716,7 @@ dependencies = [
[[package]] [[package]]
name = "kcl-lib" name = "kcl-lib"
version = "0.2.29" version = "0.2.30"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"approx 0.5.1", "approx 0.5.1",
@ -1752,7 +1784,7 @@ dependencies = [
[[package]] [[package]]
name = "kcl-test-server" name = "kcl-test-server"
version = "0.1.19" version = "0.1.20"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"hyper 0.14.30", "hyper 0.14.30",
@ -1819,9 +1851,9 @@ dependencies = [
[[package]] [[package]]
name = "kittycad-modeling-cmds" name = "kittycad-modeling-cmds"
version = "0.2.79" version = "0.2.89"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10a9cab4476455be70ea57643c31444068b056d091bd348cab6044c0d8ad7fcc" checksum = "ce9e58b34645facea36bc9f4868877bbe6fcac01b92896825e8d4f2f7c71dbd6"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"chrono", "chrono",
@ -1839,6 +1871,7 @@ dependencies = [
"serde", "serde",
"serde_bytes", "serde_bytes",
"serde_json", "serde_json",
"ts-rs",
"uuid", "uuid",
] ]
@ -1851,18 +1884,18 @@ dependencies = [
"kittycad-modeling-cmds-macros-impl", "kittycad-modeling-cmds-macros-impl",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.95", "syn 2.0.96",
] ]
[[package]] [[package]]
name = "kittycad-modeling-cmds-macros-impl" name = "kittycad-modeling-cmds-macros-impl"
version = "0.1.12" version = "0.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6607507a8a0e4273b943179f0a3ef8e90712308d1d3095246040c29cfdbf985b" checksum = "fdb4ee23cc996aa2dca7584d410e8826e08161e1ac4335bb646d5ede33f37cb3"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.95", "syn 2.0.96",
] ]
[[package]] [[package]]
@ -2012,7 +2045,7 @@ checksum = "dcf09caffaac8068c346b6df2a7fc27a177fd20b39421a39ce0a211bde679a6c"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.95", "syn 2.0.96",
] ]
[[package]] [[package]]
@ -2311,7 +2344,7 @@ dependencies = [
"regex", "regex",
"regex-syntax 0.8.5", "regex-syntax 0.8.5",
"structmeta", "structmeta",
"syn 2.0.95", "syn 2.0.96",
] ]
[[package]] [[package]]
@ -2325,7 +2358,7 @@ dependencies = [
"regex", "regex",
"regex-syntax 0.8.5", "regex-syntax 0.8.5",
"structmeta", "structmeta",
"syn 2.0.95", "syn 2.0.96",
] ]
[[package]] [[package]]
@ -2365,7 +2398,7 @@ dependencies = [
"pest_meta", "pest_meta",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.95", "syn 2.0.96",
] ]
[[package]] [[package]]
@ -2423,7 +2456,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.95", "syn 2.0.96",
] ]
[[package]] [[package]]
@ -2553,7 +2586,7 @@ dependencies = [
"proc-macro-error-attr2", "proc-macro-error-attr2",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.95", "syn 2.0.96",
] ]
[[package]] [[package]]
@ -2612,7 +2645,7 @@ dependencies = [
"proc-macro2", "proc-macro2",
"pyo3-macros-backend", "pyo3-macros-backend",
"quote", "quote",
"syn 2.0.95", "syn 2.0.96",
] ]
[[package]] [[package]]
@ -2625,7 +2658,7 @@ dependencies = [
"proc-macro2", "proc-macro2",
"pyo3-build-config", "pyo3-build-config",
"quote", "quote",
"syn 2.0.95", "syn 2.0.96",
] ]
[[package]] [[package]]
@ -2687,9 +2720,9 @@ dependencies = [
[[package]] [[package]]
name = "quote" name = "quote"
version = "1.0.37" version = "1.0.38"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
] ]
@ -3160,7 +3193,7 @@ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"serde_derive_internals", "serde_derive_internals",
"syn 2.0.95", "syn 2.0.96",
] ]
[[package]] [[package]]
@ -3200,9 +3233,9 @@ checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b"
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.216" version = "1.0.217"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b9781016e935a97e8beecf0c933758c97a5520d32930e460142b4cd80c6338e" checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70"
dependencies = [ dependencies = [
"serde_derive", "serde_derive",
] ]
@ -3218,13 +3251,13 @@ dependencies = [
[[package]] [[package]]
name = "serde_derive" name = "serde_derive"
version = "1.0.216" version = "1.0.217"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46f859dbbf73865c6627ed570e78961cd3ac92407a2d117204c49232485da55e" checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.95", "syn 2.0.96",
] ]
[[package]] [[package]]
@ -3235,14 +3268,14 @@ checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.95", "syn 2.0.96",
] ]
[[package]] [[package]]
name = "serde_json" name = "serde_json"
version = "1.0.133" version = "1.0.135"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377" checksum = "2b0d7ba2887406110130a978386c4e1befb98c674b4fba677954e4db976630d9"
dependencies = [ dependencies = [
"indexmap 2.7.0", "indexmap 2.7.0",
"itoa", "itoa",
@ -3259,7 +3292,7 @@ checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.95", "syn 2.0.96",
] ]
[[package]] [[package]]
@ -3280,7 +3313,7 @@ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"serde", "serde",
"syn 2.0.95", "syn 2.0.96",
] ]
[[package]] [[package]]
@ -3429,7 +3462,7 @@ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"structmeta-derive", "structmeta-derive",
"syn 2.0.95", "syn 2.0.96",
] ]
[[package]] [[package]]
@ -3440,7 +3473,7 @@ checksum = "152a0b65a590ff6c3da95cabe2353ee04e6167c896b28e3b14478c2636c922fc"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.95", "syn 2.0.96",
] ]
[[package]] [[package]]
@ -3462,7 +3495,7 @@ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"rustversion", "rustversion",
"syn 2.0.95", "syn 2.0.96",
] ]
[[package]] [[package]]
@ -3505,9 +3538,9 @@ dependencies = [
[[package]] [[package]]
name = "syn" name = "syn"
version = "2.0.95" version = "2.0.96"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46f71c0377baf4ef1cc3e3402ded576dccc315800fbc62dfc7fe04b009773b4a" checksum = "d5d0adab1ae378d7f53bdebc67a39f1f151407ef230f0ce2883572f5d8985c80"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -3531,7 +3564,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.95", "syn 2.0.96",
] ]
[[package]] [[package]]
@ -3639,7 +3672,7 @@ checksum = "a7c61ec9a6f64d2793d8a45faba21efbe3ced62a886d44c36a009b2b519b4c7e"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.95", "syn 2.0.96",
] ]
[[package]] [[package]]
@ -3650,7 +3683,7 @@ checksum = "22efd00f33f93fa62848a7cab956c3d38c8d43095efda1decfc2b3a5dc0b8972"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.95", "syn 2.0.96",
] ]
[[package]] [[package]]
@ -3762,7 +3795,7 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.95", "syn 2.0.96",
] ]
[[package]] [[package]]
@ -3904,7 +3937,7 @@ checksum = "84fd902d4e0b9a4b27f2f440108dc034e1758628a9b702f8ec61ad66355422fa"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.95", "syn 2.0.96",
] ]
[[package]] [[package]]
@ -3932,7 +3965,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.95", "syn 2.0.96",
] ]
[[package]] [[package]]
@ -4015,7 +4048,7 @@ checksum = "0e9d8656589772eeec2cf7a8264d9cda40fb28b9bc53118ceb9e8c07f8f38730"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.95", "syn 2.0.96",
"termcolor", "termcolor",
] ]
@ -4158,9 +4191,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]] [[package]]
name = "uuid" name = "uuid"
version = "1.11.0" version = "1.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a" checksum = "b913a3b5fe84142e269d63cc62b64319ccaf89b748fc31fe025177f767a756c4"
dependencies = [ dependencies = [
"getrandom", "getrandom",
"serde", "serde",
@ -4194,7 +4227,7 @@ dependencies = [
"proc-macro-error2", "proc-macro-error2",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.95", "syn 2.0.96",
] ]
[[package]] [[package]]
@ -4255,7 +4288,7 @@ dependencies = [
"log", "log",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.95", "syn 2.0.96",
"wasm-bindgen-shared", "wasm-bindgen-shared",
] ]
@ -4291,7 +4324,7 @@ checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.95", "syn 2.0.96",
"wasm-bindgen-backend", "wasm-bindgen-backend",
"wasm-bindgen-shared", "wasm-bindgen-shared",
] ]
@ -4609,9 +4642,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]] [[package]]
name = "winnow" name = "winnow"
version = "0.6.20" version = "0.6.22"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" checksum = "39281189af81c07ec09db316b302a3e67bf9bd7cbf6c820b50e35fee9c2fa980"
dependencies = [ dependencies = [
"memchr", "memchr",
] ]
@ -4672,7 +4705,7 @@ checksum = "28cc31741b18cb6f1d5ff12f5b7523e3d6eb0852bbbad19d73905511d9849b95"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.95", "syn 2.0.96",
"synstructure", "synstructure",
] ]
@ -4694,7 +4727,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.95", "syn 2.0.96",
] ]
[[package]] [[package]]
@ -4714,7 +4747,7 @@ checksum = "0ea7b4a3637ea8669cedf0f1fd5c286a17f3de97b8dd5a70a6c167a1730e63a5"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.95", "syn 2.0.96",
"synstructure", "synstructure",
] ]
@ -4743,14 +4776,14 @@ checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.95", "syn 2.0.96",
] ]
[[package]] [[package]]
name = "zip" name = "zip"
version = "2.2.0" version = "2.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc5e4288ea4057ae23afc69a4472434a87a2495cafce6632fd1c4ec9f5cf3494" checksum = "ae9c1ea7b3a5e1f4b922ff856a129881167511563dc219869afe3787fc0c1a45"
dependencies = [ dependencies = [
"arbitrary", "arbitrary",
"crc32fast", "crc32fast",
@ -4758,5 +4791,5 @@ dependencies = [
"displaydoc", "displaydoc",
"indexmap 2.7.0", "indexmap 2.7.0",
"memchr", "memchr",
"thiserror 1.0.68", "thiserror 2.0.0",
] ]

View File

@ -16,7 +16,7 @@ gloo-utils = "0.2.0"
kcl-lib = { path = "kcl" } kcl-lib = { path = "kcl" }
kittycad.workspace = true kittycad.workspace = true
lazy_static = "1.5.0" lazy_static = "1.5.0"
serde_json = "1.0.128" serde_json = "1.0.135"
tokio = { version = "1.41.1", features = ["sync"] } tokio = { version = "1.41.1", features = ["sync"] }
toml = "0.8.19" toml = "0.8.19"
uuid = { version = "1.11.0", features = ["v4", "js", "serde"] } uuid = { version = "1.11.0", features = ["v4", "js", "serde"] }
@ -76,7 +76,10 @@ members = [
[workspace.dependencies] [workspace.dependencies]
http = "1" http = "1"
kittycad = { version = "0.3.28", default-features = false, features = ["js", "requests"] } kittycad = { version = "0.3.28", default-features = false, features = ["js", "requests"] }
kittycad-modeling-cmds = { version = "0.2.79", features = ["websocket"] } kittycad-modeling-cmds = { version = "0.2.89", features = [
"ts-rs",
"websocket",
] }
[workspace.lints.clippy] [workspace.lints.clippy]
assertions_on_result_states = "warn" assertions_on_result_states = "warn"

View File

@ -1,7 +1,7 @@
[package] [package]
name = "derive-docs" name = "derive-docs"
description = "A tool for generating documentation from Rust derive macros" description = "A tool for generating documentation from Rust derive macros"
version = "0.1.33" version = "0.1.34"
edition = "2021" edition = "2021"
license = "MIT" license = "MIT"
repository = "https://github.com/KittyCAD/modeling-app" repository = "https://github.com/KittyCAD/modeling-app"
@ -18,9 +18,9 @@ once_cell = "1.20.2"
proc-macro2 = "1" proc-macro2 = "1"
quote = "1" quote = "1"
regex = "1.11" regex = "1.11"
serde = { version = "1.0.214", features = ["derive"] } serde = { version = "1.0.217", features = ["derive"] }
serde_tokenstream = "0.2" serde_tokenstream = "0.2"
syn = { version = "2.0.95", features = ["full"] } syn = { version = "2.0.96", features = ["full"] }
[dev-dependencies] [dev-dependencies]
anyhow = "1.0.95" anyhow = "1.0.95"

View File

@ -832,7 +832,7 @@ fn generate_code_block_test(fn_name: &str, code_block: &str, index: usize) -> pr
let result = match crate::test_server::execute_and_snapshot(code, crate::settings::types::UnitLength::Mm, None).await { let result = match crate::test_server::execute_and_snapshot(code, crate::settings::types::UnitLength::Mm, None).await {
Err(crate::errors::ExecError::Kcl(e)) => { Err(crate::errors::ExecError::Kcl(e)) => {
return Err(miette::Report::new(crate::errors::Report { return Err(miette::Report::new(crate::errors::Report {
error: e, error: e.error,
filename: format!("{}{}", #fn_name, #index), filename: format!("{}{}", #fn_name, #index),
kcl_source: #code_block.to_string(), kcl_source: #code_block.to_string(),
})); }));

View File

@ -37,7 +37,7 @@ mod test_examples_someFn {
{ {
Err(crate::errors::ExecError::Kcl(e)) => { Err(crate::errors::ExecError::Kcl(e)) => {
return Err(miette::Report::new(crate::errors::Report { return Err(miette::Report::new(crate::errors::Report {
error: e, error: e.error,
filename: format!("{}{}", "someFn", 0usize), filename: format!("{}{}", "someFn", 0usize),
kcl_source: "someFn()".to_string(), kcl_source: "someFn()".to_string(),
})); }));

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