Compare commits

...

130 Commits

Author SHA1 Message Date
12b3717eb5 Cut release v0.9.2 (#714) 2023-09-26 20:39:05 -04:00
0bc685b0c4 Bump tungstenite from 0.20.0 to 0.20.1 in /src/wasm-lib/kcl/fuzz (#709)
Bumps [tungstenite](https://github.com/snapview/tungstenite-rs) from 0.20.0 to 0.20.1.
- [Changelog](https://github.com/snapview/tungstenite-rs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/snapview/tungstenite-rs/compare/v0.20.0...v0.20.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-25 20:49:16 -07:00
9ee032771a unused dep (#710) 2023-09-26 03:22:05 +00:00
c307ddd1b1 resize (#706)
* start of resize

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

* refactor

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

* check if 0

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>

* will work w new lib

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

* new types

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

* handle resize effect

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
Co-authored-by: Kurt Hutten Irev-Dev <k.hutten@protonmail.ch>
2023-09-25 19:49:53 -07:00
a30818ff2b fixes negative args in function (#707)
* updates

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

* fixes

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

* updates

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

* fixes

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

* fixes

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

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2023-09-25 15:25:58 -07:00
53e763d938 fix close arc (#704)
* fix close arc

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

* much bigger radius

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

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2023-09-25 12:14:41 -07:00
8f74cd1d0c Bump tauri-plugin-fs-extra from 0190f68 to b04bde3 in /src-tauri (#702)
Bumps [tauri-plugin-fs-extra](https://github.com/tauri-apps/plugins-workspace) from `0190f68` to `b04bde3`.
- [Release notes](https://github.com/tauri-apps/plugins-workspace/releases)
- [Commits](0190f68f1d...b04bde3461)

---
updated-dependencies:
- dependency-name: tauri-plugin-fs-extra
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-25 09:33:53 -07:00
c271942897 remove errors (#703) 2023-09-25 07:28:03 +00:00
a03d09b41d Restructure tokenizer module (#700)
* Remove duplicated tests

These tests already were copied to tokeniser2.rs, so removing them doesn't affect code coverage.

* Move tokeniser to its own module

Now there's a module for tokens, and the tokenizer/lexer implementation is private within the token module.
2023-09-24 20:01:17 -05:00
2971b7752b Bump rust websocket libraries (#701)
Changelog: https://github.com/snapview/tokio-tungstenite/blob/master/CHANGELOG.md#0201
2023-09-24 21:34:31 +00:00
70e99eb00b Refactor is_code_token into a method (#699)
* Refactor is_code_token into a method

* Fix typos, use Parser as it was imported
2023-09-24 21:11:36 +00:00
5c66af59d2 New tokenizer based on winnow (#697)
* New tokenizer, using Winnow instead of regexes.

Between 1.3x and 4.4x speedup on lexer benchmarks :)

* Use dispatch instead of alt

Most of the time, if you know the first character of a token, you can narrow down its potential possible token types, instead of just trying each token type until one succeeds.

This further speeds up the lexer. Compared to main, this branch is now between 3x and 12x faster than main.
2023-09-22 21:57:39 -05:00
6dda6daeef Use separate benchmarks for lexing and parsing (#698) 2023-09-23 02:01:18 +00:00
b5387f1220 Cut release v0.9.1 (#693)
* bump to ️v0.9.1️

* update bump instructions

* readme update

* read me again

* change pr convention
2023-09-22 10:38:17 +10:00
fd5921b366 Convert the lexer to be iterative not recursive (#691)
This is often more memory-efficient (does not create a bunch of stack
frames)
2023-09-21 19:19:08 -05:00
716ad938fc stop gap for large files making editor slow (#690)
Signed-off-by: Jess Frazelle <github@jessfraz.com>
2023-09-21 16:13:22 -07:00
40136eb392 Bump kittycad from 0.2.25 to 0.2.26 in /src-tauri (#680)
Bumps [kittycad](https://github.com/KittyCAD/kittycad.rs) from 0.2.25 to 0.2.26.
- [Release notes](https://github.com/KittyCAD/kittycad.rs/releases)
- [Commits](https://github.com/KittyCAD/kittycad.rs/compare/v0.2.25...v0.2.26)

---
updated-dependencies:
- dependency-name: kittycad
  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>
2023-09-21 14:05:55 -07:00
8d2b89fcd1 Bump openapitor from 0d121f6 to 61a1605 in /src/wasm-lib (#679)
Bumps [openapitor](https://github.com/KittyCAD/kittycad.rs) from `0d121f6` to `61a1605`.
- [Release notes](https://github.com/KittyCAD/kittycad.rs/releases)
- [Commits](0d121f6881...61a16059b3)

---
updated-dependencies:
- dependency-name: openapitor
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-21 15:22:04 -05:00
ad9fba3390 Bump tauri-plugin-fs-extra from 76832e6 to 0190f68 in /src-tauri (#681)
Bumps [tauri-plugin-fs-extra](https://github.com/tauri-apps/plugins-workspace) from `76832e6` to `0190f68`.
- [Release notes](https://github.com/tauri-apps/plugins-workspace/releases)
- [Commits](76832e60bf...0190f68f1d)

---
updated-dependencies:
- dependency-name: tauri-plugin-fs-extra
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-21 15:21:08 -05:00
911c43af50 Bump phonenumber from 0.3.2+8.13.9 to 0.3.3+8.13.9 in /src-tauri (#687)
Bumps [phonenumber](https://github.com/whisperfish/rust-phonenumber) from 0.3.2+8.13.9 to 0.3.3+8.13.9.
- [Commits](https://github.com/whisperfish/rust-phonenumber/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-21 15:02:37 -05:00
ab4e04f6c2 Bump phonenumber from 0.3.2+8.13.9 to 0.3.3+8.13.9 in /src/wasm-lib (#685)
Bumps [phonenumber](https://github.com/whisperfish/rust-phonenumber) from 0.3.2+8.13.9 to 0.3.3+8.13.9.
- [Commits](https://github.com/whisperfish/rust-phonenumber/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-21 15:02:23 -05:00
94aef05f74 Bump phonenumber from 0.3.2+8.13.9 to 0.3.3+8.13.9 in /src/wasm-lib/kcl/fuzz (#686)
Bump phonenumber in /src/wasm-lib/kcl/fuzz

Bumps [phonenumber](https://github.com/whisperfish/rust-phonenumber) from 0.3.2+8.13.9 to 0.3.3+8.13.9.
- [Commits](https://github.com/whisperfish/rust-phonenumber/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-21 15:02:10 -05:00
d820cf2446 Tokenizer is accidentally quadratic (#689)
* Add comments and rename a function

* Typo: paran -> paren

* Use bytes, not string, for the tokenizer

* Fix typo
2023-09-21 14:18:42 -05:00
0c724c4971 Start to restructure the Engine's connection to the backend (#674)
* Start to restructure the Engine's connectio to the backend

1) Add in a tearDown stub for when the Engine is torn down. This is now
   distinct from a 'close', which will not stop connect from trying
   again. Running tearDown will mark the connection to not be retried
   and close active connections.

2) Move the retry logic out of connect and into the constructor. It will
   attempt to reconnect at the same rate as we had previously.

3) The timeout will now only close the connection, not restart it.

Signed-off-by: Paul Tagliamonte <paul@kittycad.io>

* Don't continue on dead conn & setTimeout on init only

* Clean up extra setTimeout

* Keep track of connection timeouts and clear on close

* Fix tsc by defining Timeout

Signed-off-by: Paul Tagliamonte <paul@kittycad.io>

* appease the format gods

---------

Signed-off-by: Paul Tagliamonte <paul@kittycad.io>
Co-authored-by: Adam Sunderland <adam@kittycad.io>
2023-09-21 12:07:47 -04:00
b54ac4a694 improve getNodePathFromSourceRange and therefore the ast explorer aswell (#683)
improve getNodePathFromSourceRange and therefore the ast explorer as well
2023-09-21 05:40:41 +00:00
27227092b1 app stuck on blur when engine errors (#682)
* temp fix for when engine returns error

* don't add extrued to show function
2023-09-21 04:32:47 +00:00
04e1b92a5b Add a benchmark for parsing pipes-on-pipes (#678) 2023-09-21 03:13:07 +00:00
0553cd4621 tests for big files (#675)
* shit;

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

* updates

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

* cleanup

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

* updates

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

* u[dates;

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

* updates

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

* still ignore the big one

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

* Add big kitt SVG to benchmarks

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
Co-authored-by: Adam Chalmers <adam.s.chalmers@gmail.com>
2023-09-20 19:35:37 -07:00
61a0c88af4 Add IDE dirs to .gitignore (#676) 2023-09-21 02:03:09 +00:00
d5b0544437 Bump tauri-plugin-fs-extra from 5b814f5 to 76832e6 in /src-tauri (#657)
Bumps [tauri-plugin-fs-extra](https://github.com/tauri-apps/plugins-workspace) from `5b814f5` to `76832e6`.
- [Release notes](https://github.com/tauri-apps/plugins-workspace/releases)
- [Commits](5b814f56e6...76832e60bf)

---
updated-dependencies:
- dependency-name: tauri-plugin-fs-extra
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-20 18:47:38 -07:00
6cc8af5c23 make stdlib functions async (#672)
* wip

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>

fixes

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

closer

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

fixes

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

* fixes

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

* closer

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

* closer

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

* compiles

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

* connection

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

* fixes

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

* fix wasm

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

* timeout

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

* remove the drop

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

* drop handle

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

* updates

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

* fixes

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

* fix

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

* fixes

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

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2023-09-20 18:27:08 -07:00
888104080e bump v0.9.0 (#673) 2023-09-21 10:38:40 +10:00
b6769889e3 Handle relative paths at kcl level (#506)
* handle relative paths at kcl level

* fmt

* update kittycad

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

* updates

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

* bump

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

* fix tests

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

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
Co-authored-by: Jess Frazelle <github@jessfraz.com>
Co-authored-by: Jess Frazelle <jessfraz@users.noreply.github.com>
2023-09-21 10:36:26 +10:00
a32258dac4 Engine manager can be cloned (#671) 2023-09-20 16:22:47 -07:00
18dbbad244 Use an actor to manage the Tokio engine connection (#669)
* Use an actor to manage the Tokio engine connection

This means EngineManager trait's methods take &self not &mut self, and the tokio implementation can be cloned.

* Clean up code
2023-09-20 16:59:03 -05:00
b67c16cc9d Benchmark for KCL parser (#664)
* KCL benchmarks

* CI for benchmarks

* More specific name for benchmark

* Benchmark the right directory

* Format
2023-09-20 13:15:28 -05:00
ad482641ef Unit test for zero-param programs (#663) 2023-09-20 10:51:49 -05:00
9ee24845a1 Bump to v0.8.2 (#656)
Co-authored-by: Frank Noirot <frank@kittycad.io>
2023-09-20 14:41:23 +00:00
e69d263252 Revert swapping setCode for deferredSetCode in App (#662)
This fix was implemented in https://github.com/KittyCAD/modeling-app/pull/649
to try and address https://github.com/KittyCAD/modeling-app/issues/545.
However, we need to run `setCode` to execute immediately here or else
files will not render in the 3D view when opened, as reported by @pierremtb.

Reverting for now to allow for a timely release https://github.com/KittyCAD/modeling-app/pull/656

Signed-off-by: Frank Noirot <frank@kittycad.io>
2023-09-20 10:32:36 -04:00
111738f38e Fix the debug panel overflow (#653) 2023-09-20 05:36:29 -04:00
e34501cc5a Ast fixes (#650)
* allow using member expressions for memory items

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

* fixes pi in binary expressions

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

* add fix

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>
2023-09-19 16:05:53 -07:00
c767c1c3a6 Replace setCode with deferredSetCode in App (#649)
* Replace `setCode` with `deferredSetCode` in App

* Remove unused OpenFileButton component
2023-09-19 22:07:54 +00:00
e399a8f938 Franknoirot/ux papercuts 4 (#640)
* Add Cmd + / to support windows, update walkthrough

* Fix #628 dark mode <select> bg color

* Fix #621 by narrowing margins and moving to left

---------

Co-authored-by: Jess Frazelle <jessfraz@users.noreply.github.com>
2023-09-19 18:06:13 -04:00
59d5f2524a fix function inside show not executing (#641)
* start of heap changes

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

* fix show bug

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

* cleanup

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

* new images

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

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2023-09-19 14:20:14 -07:00
b47ebd14d2 Bump to v0.8.1 (#637)
Signed-off-by: Frank Noirot <frank@kittycad.io>
2023-09-19 15:55:55 -04:00
e74bcd0695 make it so the lsp server doesnt vom on restart (#636)
Signed-off-by: Jess Frazelle <github@jessfraz.com>
2023-09-19 15:17:34 -04:00
22161ec386 Variables needs to scroll (#624)
* Variables needs to scroll
Fixes #609

* Run yarn fmt

---------

Co-authored-by: Frank Noirot <frank@kittycad.io>
2023-09-19 18:29:22 +00:00
ada46c4317 Fix Tauri auth in development (#635)
* Fix Tauri auth in development

* Fix Rust formatting
2023-09-19 14:08:26 -04:00
6675fa8d1e UX Papercuts 3: use absolute paths, add error page with buttons to help refresh, etc (#615)
* Fix #593: don't prevent default on link click

* Use absolute/explicit path for settings
Trying to test fix for #594

* Broken: replace almost all relative URLs with absolute

* Clean up to use clean useDismiss with absolute path

* Merge branch 'main' into franknoirot/ux-papercuts-3a

* Add buttons to home, reload, clear, and bug report on error screen
2023-09-19 14:06:56 -04:00
075d2debce Bump to v0.8.0 (#561)
Co-authored-by: Frank Noirot <frank@kittycad.io>
2023-09-19 00:22:18 -04:00
488e41ac0e Fix docs link, test fixing relative URLs in Windows (#606)
* Fix #593: don't prevent default on link click

* Use absolute/explicit path for settings
Trying to test fix for #594

* Broken: replace almost all relative URLs with absolute

* add relative jump backs util

* dot dot slash everywhere

* use usLocation not window.location

* the one that got away

* fmt 🤦‍♂️

---------

Co-authored-by: Kurt Hutten Irev-Dev <k.hutten@protonmail.ch>
2023-09-19 13:55:14 +10:00
8147f5f1eb CLI-only code behind a feature flag (#614) 2023-09-18 21:38:40 -05:00
bc7e9d9789 non wasm engine errors (#612)
updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2023-09-18 17:31:11 -07:00
8d493d6517 Cargo update (#611) 2023-09-18 17:22:25 -07:00
9fa98d6f3f Don't wipe cache for yarn build:wasm (#610)
You can still run a clean build (no cache) with yarn build:wasm-clean
2023-09-18 17:10:16 -07:00
24a31c94e7 Typo: noneCode => nonCode (#607) 2023-09-18 18:14:12 -05:00
76e3207251 recast fix (#608)
fixes

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2023-09-18 15:56:11 -07:00
e2237fa9f6 use deferredSetCode so the 3D view resets (#604) 2023-09-18 17:44:25 -04:00
ae4aa82129 windows fix (#598)
Break out windows sed in CI action

Add run-script-os, break apart find-replace for each

Fix importmeta command typos

Fix windows commands in packag.json and ci.yml
I actually ran these on my windows machine

Update package.json

call remove-import-meta from ci



fucking sed



updates



only remove import meta pre tests



updates



fix order

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2023-09-18 13:59:40 -07:00
14b287a746 Franknoirot/ux papercuts 1 (#596)
* Properly show dark logo in System-light theme

* Fix linting errors for fill-rule and clip-rule

* Support system-light theme on Parametric Modeling step as well

Signed-off-by: Frank Noirot <frank@kittycad.io>

* Fix line number ref in Parametric Modeling copy

Signed-off-by: Frank Noirot <frank@kittycad.io>

* Copyediting tweaks

* Fix part name disappearing even when there is space

---------

Signed-off-by: Frank Noirot <frank@kittycad.io>
2023-09-18 13:13:04 -07:00
dd1b7631fa round two decimal places on move (#591)
Signed-off-by: Jess Frazelle <github@jessfraz.com>
2023-09-18 16:42:53 +10:00
f98f782b40 Bump syn from 2.0.33 to 2.0.37 in /src/wasm-lib (#586)
Bumps [syn](https://github.com/dtolnay/syn) from 2.0.33 to 2.0.37.
- [Release notes](https://github.com/dtolnay/syn/releases)
- [Commits](https://github.com/dtolnay/syn/compare/2.0.33...2.0.37)

---
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>
2023-09-17 23:40:33 -07:00
01f5ecdc36 Bump schemars from 0.8.13 to 0.8.15 in /src/wasm-lib (#587)
Bumps [schemars](https://github.com/GREsau/schemars) from 0.8.13 to 0.8.15.
- [Release notes](https://github.com/GREsau/schemars/releases)
- [Changelog](https://github.com/GREsau/schemars/blob/master/CHANGELOG.md)
- [Commits](https://github.com/GREsau/schemars/compare/v0.8.13...v0.8.15)

---
updated-dependencies:
- dependency-name: schemars
  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>
2023-09-17 23:38:05 -07:00
5297d3e142 both edit and move in one PR (#566)
* get the data for where lines are

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

* make pretty

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

* updates

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

* fmt

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

* new shit

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

* beginning of stufff

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

* cleanup

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

* updates

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

* add new fns

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

* basic function

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

* fix ups to keep order

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

* further

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

* failing test

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

* do it in rust

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

* trait

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

* start of ui integration

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

* updates

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

* weird shit

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

* generate close on close

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

* start of constraint functions

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

* helper functions

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

* make work

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

* updates

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

* constraints w ranges

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

* updates

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

* fmt

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

* skip

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

* fixes

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

* fixes

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

* comment

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

* fixes

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

* throw

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

* make close a bit less sensitive in move scenario

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

* cleanup shit we didnt end up using

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

* make it less hard to close

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

* Fix edit after sketch

* Move to plane for sketch

* Fix pathToNode for ast mods

* Fix exit sketch mode with escape

* Fix fmt since my editor did it wrong

* updates

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

* fix link

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

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
Co-authored-by: Adam Sunderland <adam@kittycad.io>
2023-09-17 21:57:43 -07:00
f71f44968b remove unneeded import (#565) 2023-09-17 09:19:53 +00:00
7b79998c40 put .env.dev back (#564) 2023-09-17 09:17:14 +00:00
4632d407c1 Sign macOS builds (#539)
* Sign macOS builds
Fixes #295

* Try without creds, only cert

* Add more creds
2023-09-16 13:41:07 -04:00
58d7e59ca4 Let tauri-action perform Windows signing (#535)
* Check if tauri-action could sign Windows builds automatically
Fixes #533

* Cleanup

* Change to sha256

* Clean up
2023-09-16 04:48:32 -04:00
f592d8db84 Franknoirot/pretty buttons (#550) 2023-09-16 01:23:11 -04:00
31eca3728e add sample script as integration test (#559)
* updates

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

* fixes

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

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2023-09-15 20:45:28 -07:00
c5d8779af4 Update onboarding to be more complete (#551)
* Update Introduction

* Update Camera step

* Change link to expectations

Co-authored-by: Josh Gomez <114548659+jgomez720@users.noreply.github.com>

* Set outline for onboarding

* Add Streaming step

* Remove Units step

* Add default kcl script

* Add Code Editor step

* Add Parametric Modeling step

* Add Interactive Numbers step

* Update bracket to use sqrt

* Add Command K step

* Assuage @jessfraz's code itchies

* Add User Menu step

* Add Project Menu step

* Add Export step

* Improve error page to actually show error

* Update the sketch step

* Add Future Work section

* Bring back the bracket code on the final step

* Set up the code to the bracket when starting onboarding

* Fix missing import

* Don't throw away users code if not empty

* Prompt the user if they have content in their file

---------

Co-authored-by: Josh Gomez <114548659+jgomez720@users.noreply.github.com>
2023-09-16 02:37:40 +00:00
cf686bdeb0 adds more math functions and fixes parens (#558)
* nested parens fix

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

* e, tau

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

* docs

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

* remove test w log since that is a stdlib fn now

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

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2023-09-15 17:40:57 -07:00
ae7143a94f add a bunch of math stdlib fns (#555)
add a bunch of math stuff

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2023-09-15 15:54:49 -07:00
f2b24849b3 Move calls to the unreliable channel into a helper method (#543)
This'll let us handle JSON encoding in one place, as well as adding any
logging or anything. Not a huge deal, but it's a minor cleanup I made
while looking into an unrelated bug.

Signed-off-by: Paul Tagliamonte <paul@kittycad.io>
2023-09-15 17:14:58 -04:00
35d6530406 Bump actions/checkout from 3 to 4 (#379)
Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 4.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-15 13:20:59 -07:00
01208221c7 Bump serde_json from 1.0.106 to 1.0.107 in /src/wasm-lib (#518)
Bumps [serde_json](https://github.com/serde-rs/json) from 1.0.106 to 1.0.107.
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.106...v1.0.107)

---
updated-dependencies:
- dependency-name: serde_json
  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>
2023-09-15 13:20:43 -07:00
fbbed3fbfb Bump serde_json from 1.0.106 to 1.0.107 in /src-tauri (#519)
Bumps [serde_json](https://github.com/serde-rs/json) from 1.0.106 to 1.0.107.
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.106...v1.0.107)

---
updated-dependencies:
- dependency-name: serde_json
  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>
2023-09-15 13:20:22 -07:00
ce51f26701 fix negative word binary expression (#549)
* fix negative word binary expression

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>
2023-09-15 13:19:53 -07:00
caddac5059 Bump to v0.7.1 (#548) 2023-09-15 15:43:48 -04:00
54751aa7bb Fix rtc_freeze_count error (#544) 2023-09-15 12:05:46 -04:00
7b7d5e5f5e Fix extrude w/o pipe (#542) 2023-09-15 11:51:39 -04:00
f7971bddef Rename TooTip to ToolTip (#541) 2023-09-15 11:48:23 -04:00
e4f2e66029 inital rework of execution (#528)
* inital rework

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

* updates

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

* update the program memory as well

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

* cleanups

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

* code

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

* updates for typing code

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

* fixing

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

* some fixes

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

* more fixes

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

* Only unselect line or move tool on escape, don't exit sketch

* Make scrollbar on toolbar smaller

* Add escape to exit sketch mode

* tidy up usestore

* clear scene on empty file

* disable sketch mode and re-execute on sketch loop close

* disable all but xy plane

* fix entering back into edit mode

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
Co-authored-by: Frank Noirot <frank@kittycad.io>
Co-authored-by: Kurt Hutten Irev-Dev <k.hutten@protonmail.ch>
2023-09-15 21:35:48 +10:00
663c396128 Add link to KCL docs to CodeMenu (#526)
Co-authored-by: Kurt Hutten <k.hutten@protonmail.ch>
2023-09-15 00:07:11 +00:00
8db86a6783 Fix sketch mode planes visibility on enter, exit, enter (#527)
* Fix sketch mode planes visibility on enter, exit, enter

* Fix tsc

* Rename to something that makes more sense
2023-09-15 10:03:06 +10:00
d7ad7c749e Franknoirot/fix prod tauri auth (#531)
* Invoke tauri-based logout if in tauri

* Add kittycad Rust library

* Add logout and get_user Tauri commands

* Invoke get_user instead of fetching in Tauri

* @jessfraz review

Signed-off-by: Frank Noirot <frank@kittycad.io>

* Remove unnecessary logout command

* Fix rushed last commit

---------

Signed-off-by: Frank Noirot <frank@kittycad.io>
2023-09-14 19:31:16 -04:00
6e3c642d22 add expectations to modeling repo (#530) 2023-09-14 15:41:38 -07:00
4d7433ff3a Nicer geometry types (#522)
* Angle type

* Use Point2d

* use angle in more places

* Fix doctests

* Use angle in more places

* Import pi
2023-09-14 14:51:26 -07:00
4e93146559 Remove old deps (#529)
Updates chrono to remove time 0.1
Updates openapitor to remove clap 3
2023-09-14 15:05:07 -06:00
731a9bfbdb Enable devtools on Tauri builds (#525) 2023-09-14 10:25:17 -04:00
cdb4c36cf5 addNewSketchLn should close when latest point matches start (#479)
* addNewSketchLn should close when latest point matches start

* Fix types

* Include close in test case

* Add handling for continuing to sketch

* Fix types again

* close line edits (#523)

* add close to pipe

* undo some previous changes

---------

Co-authored-by: Kurt Hutten <k.hutten@protonmail.ch>
2023-09-14 09:34:37 -04:00
66ba60dc8e Bump to v0.7.0 (#524) 2023-09-14 07:35:08 -04:00
8fcc8cdd17 Change updater from .nsis to .msi, add EV Code Sign to it (#435)
* Change updater to msi over nsis, more common to sign

* Add sign-windows-msi from docs

* Add secrets and enable step

* Update ci.yml

* Add todo and fix download.json
2023-09-14 07:04:11 -04:00
bba9bdc563 Add logomark, update README (#412) 2023-09-14 05:16:24 +00:00
760a180f56 Convert to radians/degrees using Rust methods (#516)
use to_radians and to_degrees
2023-09-13 22:25:41 -06:00
0eeff8cb45 Add @replit/codemirror-interact (#500)
* Add @replit/codemirror-interact

Signed off by Frank Noirot <frank@kittycad.io>

* Add support for Alt + Meta + Shift for 0.01 increment

* Remove pixelsPerIncrement
This doesn't work because we have to rely on movementX, which means
only mouse movements of 3px or greater register while dragging. Although
I would eventually like to control the screen speed of incrementing,
I'd rather have consistent behavior for now.

Signed off by Frank Noirot <frank@kittycad.io>

---------

Co-authored-by: Kurt Hutten <k.hutten@protonmail.ch>
2023-09-14 04:03:51 +00:00
3c76721159 Fix id source range mapping with path_get_info (#517) 2023-09-14 13:49:59 +10:00
6ac79ae645 Move app.tsx effects (#513)
* move useSetupEngineManager

* move code eval

* add comment
2023-09-14 10:47:55 +10:00
90d7c33c92 add roadmap md file (#514) 2023-09-13 17:35:31 -07:00
e02bc76bdb add ast explorer (#460)
* add crude ast explorer

* tsc
2023-09-14 08:51:23 +10:00
0466f04d82 add sin cos tan to stdlib and make sure you cant redeclare a stdlib fn (#497)
more tests

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2023-09-13 15:09:07 -07:00
f8ed830b60 Bump tauri from 1.3.0 to 1.4.1 in /src-tauri (#490)
Bumps [tauri](https://github.com/tauri-apps/tauri) from 1.3.0 to 1.4.1.
- [Release notes](https://github.com/tauri-apps/tauri/releases)
- [Commits](https://github.com/tauri-apps/tauri/compare/tauri-v1.3...tauri-v1.4.1)

---
updated-dependencies:
- dependency-name: tauri
  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>
2023-09-13 14:15:46 -07:00
b7ca91bf6d Bump oauth2 from 4.4.1 to 4.4.2 in /src-tauri (#489)
Bumps [oauth2](https://github.com/ramosbugs/oauth2-rs) from 4.4.1 to 4.4.2.
- [Release notes](https://github.com/ramosbugs/oauth2-rs/releases)
- [Commits](https://github.com/ramosbugs/oauth2-rs/compare/4.4.1...4.4.2)

---
updated-dependencies:
- dependency-name: oauth2
  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>
2023-09-13 13:46:17 -07:00
2261f92b0b Bump anyhow from 1.0.71 to 1.0.75 in /src-tauri (#488)
Bumps [anyhow](https://github.com/dtolnay/anyhow) from 1.0.71 to 1.0.75.
- [Release notes](https://github.com/dtolnay/anyhow/releases)
- [Commits](https://github.com/dtolnay/anyhow/compare/1.0.71...1.0.75)

---
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>
2023-09-13 13:46:07 -07:00
bbe9e621b1 Bump toml from 0.7.3 to 0.8.0 in /src-tauri (#493)
Bumps [toml](https://github.com/toml-rs/toml) from 0.7.3 to 0.8.0.
- [Commits](https://github.com/toml-rs/toml/compare/toml-v0.7.3...toml-v0.8.0)

---
updated-dependencies:
- dependency-name: toml
  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>
2023-09-13 13:45:59 -07:00
bf087d760b Bump thiserror from 1.0.47 to 1.0.48 in /src/wasm-lib (#375)
Bumps [thiserror](https://github.com/dtolnay/thiserror) from 1.0.47 to 1.0.48.
- [Release notes](https://github.com/dtolnay/thiserror/releases)
- [Commits](https://github.com/dtolnay/thiserror/compare/1.0.47...1.0.48)

---
updated-dependencies:
- dependency-name: thiserror
  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>
2023-09-13 13:45:49 -07:00
a4353c63fd Bump tauri-plugin-fs-extra from 7e58dc8 to 5b814f5 in /src-tauri (#491)
Bumps [tauri-plugin-fs-extra](https://github.com/tauri-apps/plugins-workspace) from `7e58dc8` to `5b814f5`.
- [Release notes](https://github.com/tauri-apps/plugins-workspace/releases)
- [Commits](7e58dc8502...5b814f56e6)

---
updated-dependencies:
- dependency-name: tauri-plugin-fs-extra
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-13 13:45:36 -07:00
c438d11c3d make .4 parse as number (#492)
updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2023-09-13 13:10:55 -07:00
43284e33c8 Bump toml from 0.6.0 to 0.7.3 in /src-tauri (#277)
Bumps [toml](https://github.com/toml-rs/toml) from 0.6.0 to 0.7.3.
- [Commits](https://github.com/toml-rs/toml/compare/toml-v0.6.0...toml-v0.7.3)

---
updated-dependencies:
- dependency-name: toml
  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>
2023-09-13 12:50:52 -07:00
77dce7f0dd Bump syn from 2.0.32 to 2.0.33 in /src/wasm-lib (#485)
Bumps [syn](https://github.com/dtolnay/syn) from 2.0.32 to 2.0.33.
- [Release notes](https://github.com/dtolnay/syn/releases)
- [Commits](https://github.com/dtolnay/syn/compare/2.0.32...2.0.33)

---
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>
2023-09-13 12:50:39 -07:00
d559862051 Bump proc-macro2 from 1.0.66 to 1.0.67 in /src/wasm-lib (#486)
Bumps [proc-macro2](https://github.com/dtolnay/proc-macro2) from 1.0.66 to 1.0.67.
- [Release notes](https://github.com/dtolnay/proc-macro2/releases)
- [Commits](https://github.com/dtolnay/proc-macro2/compare/1.0.66...1.0.67)

---
updated-dependencies:
- dependency-name: proc-macro2
  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>
2023-09-13 12:50:22 -07:00
7382ed87ba Bump tokio from 1.29.1 to 1.32.0 in /src-tauri (#280)
Bumps [tokio](https://github.com/tokio-rs/tokio) from 1.29.1 to 1.32.0.
- [Release notes](https://github.com/tokio-rs/tokio/releases)
- [Commits](https://github.com/tokio-rs/tokio/compare/tokio-1.29.1...tokio-1.32.0)

---
updated-dependencies:
- dependency-name: tokio
  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>
2023-09-13 12:23:56 -07:00
3324ed31de Bump serde_json from 1.0.96 to 1.0.106 in /src-tauri (#429)
Bumps [serde_json](https://github.com/serde-rs/json) from 1.0.96 to 1.0.106.
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.96...v1.0.106)

---
updated-dependencies:
- dependency-name: serde_json
  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>
2023-09-13 12:23:43 -07:00
ba9dbc2205 Bump rustls-webpki from 0.101.1 to 0.101.5 in /src-tauri (#449)
Bumps [rustls-webpki](https://github.com/rustls/webpki) from 0.101.1 to 0.101.5.
- [Release notes](https://github.com/rustls/webpki/releases)
- [Commits](https://github.com/rustls/webpki/compare/v/0.101.1...v/0.101.5)

---
updated-dependencies:
- dependency-name: rustls-webpki
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-13 12:23:32 -07:00
b0028d4874 add basic docs for lang things (#484)
Signed-off-by: Jess Frazelle <github@jessfraz.com>
2023-09-13 11:59:21 -07:00
9e6be9651c Bump webpki from 0.22.0 to 0.22.1 in /src-tauri (#399)
Bumps [webpki](https://github.com/briansmith/webpki) from 0.22.0 to 0.22.1.
- [Commits](https://github.com/briansmith/webpki/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-13 11:54:34 -07:00
b145ab0106 Bump clap from 4.4.2 to 4.4.3 in /src/wasm-lib (#459)
Bumps [clap](https://github.com/clap-rs/clap) from 4.4.2 to 4.4.3.
- [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/v4.4.2...v4.4.3)

---
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>
2023-09-13 11:54:13 -07:00
84e0fbb70f Bump serde from 1.0.186 to 1.0.188 in /src/wasm-lib (#330)
Bumps [serde](https://github.com/serde-rs/serde) from 1.0.186 to 1.0.188.
- [Release notes](https://github.com/serde-rs/serde/releases)
- [Commits](https://github.com/serde-rs/serde/compare/v1.0.186...v1.0.188)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-13 11:54:02 -07:00
990605bbea fn required and can only be used for fn not var (#483)
* fixups

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

* fix javascript tests

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

* fix clippy

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

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2023-09-13 11:42:09 -07:00
d075c4ad13 Bump syn from 2.0.29 to 2.0.32 in /src/wasm-lib (#427)
Bumps [syn](https://github.com/dtolnay/syn) from 2.0.29 to 2.0.32.
- [Release notes](https://github.com/dtolnay/syn/releases)
- [Commits](https://github.com/dtolnay/syn/compare/2.0.29...2.0.32)

---
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>
2023-09-13 11:12:18 -07:00
a3f41f5519 Bump serde_json from 1.0.105 to 1.0.106 in /src/wasm-lib (#428)
Bumps [serde_json](https://github.com/serde-rs/json) from 1.0.105 to 1.0.106.
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.105...v1.0.106)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Jess Frazelle <jessfraz@users.noreply.github.com>
2023-09-13 11:00:50 -07:00
cb173e2850 Error on keywords used in wrong way (#481)
* error if someone is using keyword where they should not

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

* updates

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

* cleanup

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

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2023-09-13 10:51:03 -07:00
87cd3b67f4 allow ranges in arrays to work (#476)
Signed-off-by: Jess Frazelle <github@jessfraz.com>
2023-09-13 10:03:28 -07:00
fe3ee3806e Fix array expressions (#473)
* fix array expressions

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

* fix-tests

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

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2023-09-13 09:23:24 -07:00
c9ed6c724c Fix path for starting second sketch (#474) 2023-09-13 11:49:19 -04:00
a5fa259d55 fixes obj.thing stuff and member expressions and bugs (#461)
* add failing tests for loops

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

* fix obj["thing"] and obj.thing complex cases

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

* fix clippy

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

* remove println

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

* fix test

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

* fixes more tests

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

* fixups

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

* fixups

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

* fix tests

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

* add more tests

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

* more test fixes

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

* fixes

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

* last test fix

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

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2023-09-13 07:23:14 -07:00
33822b5a19 Bump to v0.6.1 (#465) 2023-09-13 06:33:11 -04:00
a2a4daebe3 fix move cmd order (#462) 2023-09-13 07:42:42 +00:00
a17ede50bd Build endpoint for download page on website (#451) 2023-09-13 03:03:13 +00:00
2d452f80d1 ts-rs changes (#450)
* initial changes

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

* updates

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

* fix tests

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

* fixes

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

* updates

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

* bust cache

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

* add dumb shit

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

* Revert "add dumb shit"

This reverts commit 638e9cf72f75e1ad08fb6b22d2a7b143ab7e06e5.

* Revert "bust cache"

This reverts commit fd6f53ba0757d635190aa82d4b055a83755f3027.

* fix

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

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2023-09-13 11:10:27 +10:00
158 changed files with 12925 additions and 3691 deletions

View File

@ -24,7 +24,7 @@ jobs:
matrix:
dir: ['src/wasm-lib']
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Install latest rust
uses: actions-rs/toolchain@v1

View File

@ -24,7 +24,7 @@ jobs:
matrix:
dir: ['src/wasm-lib']
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Install latest rust
uses: actions-rs/toolchain@v1
with:
@ -54,4 +54,4 @@ jobs:
- name: Run clippy
run: |
cd "${{ matrix.dir }}"
cargo clippy --all --tests -- -D warnings
cargo clippy --all --tests --benches -- -D warnings

37
.github/workflows/cargo-criterion.yml vendored Normal file
View File

@ -0,0 +1,37 @@
on:
push:
branches:
- main
paths:
- '**.rs'
- '**/Cargo.toml'
- '**/Cargo.lock'
- '**/rust-toolchain.toml'
- .github/workflows/cargo-criterion.yml
pull_request:
paths:
- '**.rs'
- '**/Cargo.toml'
- '**/Cargo.lock'
- '**/rust-toolchain.toml'
- .github/workflows/cargo-criterion.yml
workflow_dispatch:
permissions: read-all
name: cargo criterion
jobs:
cargocriterion:
name: cargo criterion
runs-on: ubuntu-latest-8-cores
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- name: Install dependencies
run: |
cargo install cargo-criterion
- name: Rust Cache
uses: Swatinem/rust-cache@v2.6.1
- name: Benchmark kcl library
shell: bash
run: |-
cd src/wasm-lib/kcl; cargo criterion

View File

@ -27,7 +27,7 @@ jobs:
matrix:
dir: ['src/wasm-lib', 'src-tauri']
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Install latest rust
uses: actions-rs/toolchain@v1
with:

View File

@ -26,7 +26,7 @@ jobs:
matrix:
dir: ['src/wasm-lib']
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Install latest rust
uses: actions-rs/toolchain@v1
with:
@ -55,7 +55,8 @@ jobs:
shell: bash
run: |-
cd "${{ matrix.dir }}"
cargo test --all
cargo nextest run --workspace --no-fail-fast -P ci
env:
KITTYCAD_API_TOKEN: ${{secrets.KITTYCAD_API_TOKEN}}
RUST_MIN_STACK: 10485760000

View File

@ -4,16 +4,15 @@ on:
pull_request:
push:
branches:
- main
- main
release:
types: [published]
jobs:
check-format:
runs-on: 'ubuntu-20.04'
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: actions/setup-node@v3
with:
node-version-file: '.nvmrc'
@ -25,7 +24,7 @@ jobs:
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: actions/setup-node@v3
with:
node-version-file: '.nvmrc'
@ -33,19 +32,17 @@ jobs:
- run: yarn install
- uses: Swatinem/rust-cache@v2
with:
workspaces: "./src/wasm-lib"
workspaces: './src/wasm-lib'
- run: yarn build:wasm
- run: yarn tsc
build-test-web:
runs-on: ubuntu-20.04
outputs:
version: ${{ steps.export_version.outputs.version }}
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: actions/setup-node@v3
with:
@ -56,7 +53,7 @@ jobs:
- uses: Swatinem/rust-cache@v2
with:
workspaces: "./src/wasm-lib"
workspaces: './src/wasm-lib'
- run: yarn build:wasm
@ -69,7 +66,6 @@ jobs:
- id: export_version
run: echo "version=`cat package.json | jq -r '.version'`" >> "$GITHUB_OUTPUT"
build-apps:
needs: [check-format, build-test-web, check-types]
runs-on: ${{ matrix.os }}
@ -77,8 +73,7 @@ jobs:
matrix:
os: [macos-latest, ubuntu-20.04, windows-latest]
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: install ubuntu system dependencies
if: matrix.os == 'ubuntu-20.04'
@ -104,7 +99,7 @@ jobs:
- uses: Swatinem/rust-cache@v2
with:
workspaces: "./src/wasm-lib"
workspaces: './src/wasm-lib'
- name: wasm prep
shell: bash
@ -114,18 +109,6 @@ jobs:
cd ../../
cp src/wasm-lib/pkg/wasm_lib_bg.wasm public
- name: macos sed
if: matrix.os == 'macos-latest'
shell: bash
run: |
sed -i '' 's/import.meta.url//g' "./src/wasm-lib/pkg/wasm_lib.js"
- name: ubuntu and windows sed
if: matrix.os != 'macos-latest'
shell: bash
run: |
sed -i 's/import.meta.url//g' "./src/wasm-lib/pkg/wasm_lib.js"
- name: Fix format
run: yarn fmt
@ -134,11 +117,42 @@ jobs:
run: |
rustup target add aarch64-apple-darwin
- name: Build the app for the current platform (no upload)
- name: Prepare Windows certificate and variables
if: matrix.os == 'windows-latest'
run: |
echo "${{secrets.SM_CLIENT_CERT_FILE_B64 }}" | base64 --decode > /d/Certificate_pkcs12.p12
cat /d/Certificate_pkcs12.p12
echo "::set-output name=version::${GITHUB_REF#refs/tags/v}"
echo "SM_HOST=${{ secrets.SM_HOST }}" >> "$GITHUB_ENV"
echo "SM_API_KEY=${{ secrets.SM_API_KEY }}" >> "$GITHUB_ENV"
echo "SM_CLIENT_CERT_FILE=D:\\Certificate_pkcs12.p12" >> "$GITHUB_ENV"
echo "SM_CLIENT_CERT_PASSWORD=${{ secrets.SM_CLIENT_CERT_PASSWORD }}" >> "$GITHUB_ENV"
echo "C:\Program Files (x86)\Windows Kits\10\App Certification Kit" >> $GITHUB_PATH
echo "C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.8 Tools" >> $GITHUB_PATH
echo "C:\Program Files\DigiCert\DigiCert One Signing Manager Tools" >> $GITHUB_PATH
shell: bash
- name: Setup Windows certicate with SSM KSP
if: matrix.os == 'windows-latest'
run: |
curl -X GET https://one.digicert.com/signingmanager/api-ui/v1/releases/smtools-windows-x64.msi/download -H "x-api-key:%SM_API_KEY%" -o smtools-windows-x64.msi
msiexec /i smtools-windows-x64.msi /quiet /qn
smksp_registrar.exe list
smctl.exe keypair ls
C:\Windows\System32\certutil.exe -csp "DigiCert Signing Manager KSP" -key -user
smksp_cert_sync.exe
shell: cmd
- name: Build and sign the app for the current platform
uses: tauri-apps/tauri-action@v0
env:
TAURI_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
TAURI_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
with:
args: ${{ matrix.os == 'macos-latest' && '--target universal-apple-darwin' || '' }}
@ -146,15 +160,15 @@ jobs:
with:
path: ${{ matrix.os == 'macos-latest' && 'src-tauri/target/universal-apple-darwin/release/bundle/*/*' || 'src-tauri/target/release/bundle/*/*' }}
publish-apps-release:
runs-on: ubuntu-20.04
if: github.event_name == 'release'
needs: [build-test-web, build-apps]
env:
VERSION_NO_V: ${{ needs.build-test-web.outputs.version }}
PUB_DATE: ${{ github.event.release.created_at }}
NOTES: ${{ github.event.release.body }}
steps:
- uses: actions/download-artifact@v3
- name: Generate the update static endpoint
@ -162,18 +176,22 @@ jobs:
ls -l artifact/*/*itty*
DARWIN_SIG=`cat artifact/macos/*.app.tar.gz.sig`
LINUX_SIG=`cat artifact/appimage/*.AppImage.tar.gz.sig`
WINDOWS_SIG=`cat artifact/nsis/*.nsis.zip.sig`
WINDOWS_SIG=`cat artifact/msi/*.msi.zip.sig`
RELEASE_DIR=https://dl.kittycad.io/releases/modeling-app/v${VERSION_NO_V}
jq --null-input \
--arg version "v${VERSION_NO_V}" \
--arg pub_date "${PUB_DATE}" \
--arg notes "${NOTES}" \
--arg darwin_sig "$DARWIN_SIG" \
--arg darwin_url "$RELEASE_DIR/macos/KittyCAD%20Modeling.app.tar.gz" \
--arg linux_sig "$LINUX_SIG" \
--arg linux_url "$RELEASE_DIR/appimage/kittycad-modeling_${VERSION_NO_V}_amd64.AppImage.tar.gz" \
--arg windows_sig "$WINDOWS_SIG" \
--arg windows_url "$RELEASE_DIR/nsis/KittyCAD%20Modeling_${VERSION_NO_V}_x64-setup.nsis.zip" \
--arg windows_url "$RELEASE_DIR/msi/KittyCAD%20Modeling_${VERSION_NO_V}_x64_en-US.msi.zip" \
'{
"version": $version,
"pub_date": $pub_date,
"notes": $notes,
"platforms": {
"darwin-x86_64": {
"signature": $darwin_sig,
@ -195,6 +213,34 @@ jobs:
}' > last_update.json
cat last_update.json
- name: Generate the download static endpoint
run: |
RELEASE_DIR=https://dl.kittycad.io/releases/modeling-app/v${VERSION_NO_V}
jq --null-input \
--arg version "v${VERSION_NO_V}" \
--arg pub_date "${PUB_DATE}" \
--arg notes "${NOTES}" \
--arg darwin_url "$RELEASE_DIR/dmg/KittyCAD%20Modeling_${VERSION_NO_V}_universal.dmg" \
--arg linux_url "$RELEASE_DIR/appimage/kittycad-modeling_${VERSION_NO_V}_amd64.AppImage" \
--arg windows_url "$RELEASE_DIR/msi/KittyCAD%20Modeling_${VERSION_NO_V}_x64_en-US.msi" \
'{
"version": $version,
"pub_date": $pub_date,
"notes": $notes,
"platforms": {
"dmg-universal": {
"url": $darwin_url
},
"appimage-x86_64": {
"url": $linux_url
},
"msi-x86_64": {
"url": $windows_url
}
}
}' > last_download.json
cat last_download.json
- name: Authenticate to Google Cloud
uses: 'google-github-actions/auth@v1.1.1'
with:
@ -219,6 +265,12 @@ jobs:
path: last_update.json
destination: dl.kittycad.io/releases/modeling-app
- name: Upload download endpoint to public bucket
uses: google-github-actions/upload-cloud-storage@v1.0.3
with:
path: last_download.json
destination: dl.kittycad.io/releases/modeling-app
- name: Upload release files to Github
uses: softprops/action-gh-release@v1
with:

View File

@ -16,7 +16,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3.5.0
- uses: actions/checkout@v4
- shell: bash
run: |
# checkout our branch

5
.gitignore vendored
View File

@ -22,6 +22,11 @@ npm-debug.log*
yarn-debug.log*
yarn-error.log*
.idea
.vscode
src/wasm-lib/.idea
src/wasm-lib/.vscode
# rust
src/wasm-lib/target
src/wasm-lib/bindings

115
README.md
View File

@ -1,48 +1,72 @@
## Kurt demo project
![KittyCAD Modeling App](/public/kcma-logomark.png)
## KittyCAD Modeling App
live at [app.kittycad.io](https://app.kittycad.io/)
Not sure what to call this, it's both a language/interpreter and a UI that uses the language as the source of truth model the user build with direct-manipulation with the UI.
A CAD application from the future, brought to you by the [KittyCAD team](https://kittycad.io).
It might make sense to split this repo up at some point, but not the lang and the UI are all togther in a react app
The KittyCAD modeling app is our take on what a modern modelling experience can be. It is applying several lessons learned in the decades since most major CAD tools came into existence:
Originally Presented on 10/01/2023
- All artifacts—including parts and assemblies—should be represented as human-readable code. At the end of the day, your CAD project should be "plain text"
- This makes version control—which is a solved problem in software engineering—trivial for CAD
- All GUI (or point-and-click) interactions should be actions performed on this code representation under the hood
- This unlocks a hybrid approach to modeling. Whether you point-and-click as you always have or you write your own KCL code, you are performing the same action in KittyCAD Modeling App
- Everything graphics _has_ to be built for the GPU
- Most CAD applications have had to retrofit support for GPUs, but our geometry engine is made for GPUs (primarily Nvidia's Vulkan), getting the order of magnitude rendering performance boost with it
- Make the resource-intensive pieces of an application auto-scaling
- One of the bottlenecks of today's hardware design tools is that they all rely on the local machine's resources to do the hardest parts, which include geometry rendering and analysis. Our geometry engine parallelizes rendering and just sends video frames back to the app (seriously, inspect source, it's just a `<video>` element), and our API will offload analysis as we build it in
[Video](https://drive.google.com/file/d/183_wjqGdzZ8EEZXSqZ3eDcJocYPCyOdC/view?pli=1)
We are excited about what a small team of people could build in a short time with our API. We welcome you to try our API, build your own applications, or contribute to ours!
[demo-slides.pdf](https://github.com/KittyCAD/Eng/files/10398178/demo.pdf)
KittyCAD Modeling App is a _hybrid_ user interface for CAD modeling. You can point-and-click to design parts (and soon assemblies), but everything you make is really just [`kcl` code](https://github.com/KittyCAD/kcl-experiments) under the hood. All of your CAD models can be checked into source control such as GitHub and responsibly versioned, rolled back, and more.
## To run, there are a couple steps since we're compiling rust to WASM, you'll need to have rust stuff installed, then
The 3D view in KittyCAD Modeling App is just a video stream from our hosted geometry engine. The app sends new modeling commands to the engine via WebSockets, which returns back video frames of the view within the engine.
## Tools
- UI
- [React](https://react.dev/)
- [Headless UI](https://headlessui.com/)
- [TailwindCSS](https://tailwindcss.com/)
- Networking
- WebSockets (via [KittyCAD TS client](https://github.com/KittyCAD/kittycad.ts))
- Code Editor
- [CodeMirror](https://codemirror.net/)
- Custom WASM LSP Server
- Modeling
- [KittyCAD TypeScript client](https://github.com/KittyCAD/kittycad.ts)
[Original demo video](https://drive.google.com/file/d/183_wjqGdzZ8EEZXSqZ3eDcJocYPCyOdC/view?pli=1)
[Original demo slides](https://github.com/KittyCAD/Eng/files/10398178/demo.pdf)
## Get started
We recommend downloading the latest application binary from [our Releases page](https://github.com/KittyCAD/modeling-app/releases). If you don't see your platform or architecture supported there, please file an issue.
## Running a development build
First, [install Rust via `rustup`](https://www.rust-lang.org/tools/install). This project uses a lot of Rust compiled to [WASM](https://webassembly.org/) within it. Then, run:
```
yarn install
```
then
followed by:
```
yarn build:wasm
```
That will build the WASM binary and put in the `public` dir (though gitignored)
finally
finally, to run the web app only, run:
```
yarn start
```
and `yarn test` you would have need to have built the WASM previously. The tests need to download the binary from a server, so if you've already got `yarn start` running, that will work, otherwise running
```
yarn simpleserver
```
in one terminal
and
```
yarn test
```
in another.
If you want to edit the rust files, you can cd into `src/wasm-lib` and then use the usual rust commands, `cargo build`, `cargo test`, when you want to bring the changes back to the web-app, a fresh `yarn build:wasm` in the root will be needed.
Worth noting that the integration of the WASM into this project is very hacky because I'm really pushing create-react-app further than what's practical, but focusing on features atm rather than the setup.
## Developing in Chrome
Chrome is in the process of rolling out a new default which
@ -52,12 +76,26 @@ enable third-party cookies. You can enable third-party cookies by clicking on
the eye with a slash through it in the URL bar, and clicking on "Enable
Third-Party Cookies".
## Running tests
First, start the dev server following "Running a development build" above.
Then in another terminal tab, run:
```
yarn test
```
Which will run our suite of [Vitest unit](https://vitest.dev/) and [React Testing Library E2E](https://testing-library.com/docs/react-testing-library/intro/) tests, in interactive mode by default.
## Tauri
To spin up up tauri dev, `yarn install` and `yarn build:wasm` need to have been done before hand then
```
yarn tauri dev
```
Will spin up the web app before opening up the tauri dev desktop app. Note that it's probably a good idea to close the browser tab that gets opened since at the time of writting they can conflict.
The dev instance automatically opens up the browser devtools which can be disabled by [commenting it out](https://github.com/KittyCAD/modeling-app/blob/main/src-tauri/src/main.rs#L92.)
@ -67,19 +105,42 @@ To build, run `yarn tauri build`, or `yarn tauri build --debug` to keep access t
Note that these became separate apps on Macos, so make sure you open the right one after a build 😉
![image](https://github.com/KittyCAD/modeling-app/assets/29681384/a08762c5-8d16-42d8-a02f-a5efc9ae5551)
<img width="1232" alt="image" src="https://user-images.githubusercontent.com/29681384/211947063-46164bb4-7bdd-45cb-9a76-2f40c71a24aa.png">
<img width="1232" alt="image (1)" src="https://user-images.githubusercontent.com/29681384/211947073-e76b4933-bef5-4636-bc4d-e930ac8e290f.png">
## Before submitting a PR
Before you submit a contribution PR to this repo, please ensure that:
- There is a corresponding issue for the changes you want to make, so that discussion of approach can be had before work begins.
- You have separated out refactoring commits from feature commits as much as possible
- You have run all of the following commands locally:
- `yarn fmt`
- `yarn tsc`
- `yarn test`
- Here they are all together: `yarn fmt && yarn tsc && yarn test`
## Release a new version
1. Bump the versions in the .json files by creating a `Bump to v{x}.{y}.{z}` PR, committing the changes from
1. Bump the versions in the .json files by creating a `Cut release v{x}.{y}.{z}` PR, committing the changes from
```bash
VERSION=x.y.z yarn run bump-jsons
```
The PR may serve as a place to discuss the human-readable changelog and extra QA.
The PR may serve as a place to discuss the human-readable changelog and extra QA. A quick way of getting PR's merged since the last bump is to [use this PR filter](https://github.com/KittyCAD/modeling-app/pulls?q=is%3Apr+sort%3Aupdated-desc+is%3Amerged+), open up the browser console and past in the following
```typescript
console.log(
'- ' +
Array.from(
document.querySelectorAll('[data-hovercard-type="pull_request"]')
).map((a) => `[${a.innerText}](${a.href})`).join(`
- `)
)
```
grab the md list and delete any that are older than the last bump
2. Merge the PR

View File

@ -1,4 +1,60 @@
[
{
"name": "abs",
"summary": "Computes the absolute value of a number.",
"description": "",
"tags": [],
"args": [
{
"name": "num",
"type": "number",
"schema": {
"type": "number",
"format": "double"
},
"required": true
}
],
"returnValue": {
"name": "",
"type": "number",
"schema": {
"type": "number",
"format": "double"
},
"required": true
},
"unpublished": false,
"deprecated": false
},
{
"name": "acos",
"summary": "Computes the arccosine of a number (in radians).",
"description": "",
"tags": [],
"args": [
{
"name": "num",
"type": "number",
"schema": {
"type": "number",
"format": "double"
},
"required": true
}
],
"returnValue": {
"name": "",
"type": "number",
"schema": {
"type": "number",
"format": "double"
},
"required": true
},
"unpublished": false,
"deprecated": false
},
{
"name": "angleToMatchLengthX",
"summary": "Returns the angle to match the given length for x.",
@ -7473,6 +7529,62 @@
"unpublished": false,
"deprecated": false
},
{
"name": "asin",
"summary": "Computes the arcsine of a number (in radians).",
"description": "",
"tags": [],
"args": [
{
"name": "num",
"type": "number",
"schema": {
"type": "number",
"format": "double"
},
"required": true
}
],
"returnValue": {
"name": "",
"type": "number",
"schema": {
"type": "number",
"format": "double"
},
"required": true
},
"unpublished": false,
"deprecated": false
},
{
"name": "atan",
"summary": "Computes the arctangent of a number (in radians).",
"description": "",
"tags": [],
"args": [
{
"name": "num",
"type": "number",
"schema": {
"type": "number",
"format": "double"
},
"required": true
}
],
"returnValue": {
"name": "",
"type": "number",
"schema": {
"type": "number",
"format": "double"
},
"required": true
},
"unpublished": false,
"deprecated": false
},
{
"name": "bezierCurve",
"summary": "Draw a bezier curve.",
@ -8446,6 +8558,34 @@
"unpublished": false,
"deprecated": false
},
{
"name": "ceil",
"summary": "Computes the smallest integer greater than or equal to a number.",
"description": "",
"tags": [],
"args": [
{
"name": "num",
"type": "number",
"schema": {
"type": "number",
"format": "double"
},
"required": true
}
],
"returnValue": {
"name": "",
"type": "number",
"schema": {
"type": "number",
"format": "double"
},
"required": true
},
"unpublished": false,
"deprecated": false
},
{
"name": "close",
"summary": "Close the current sketch.",
@ -9322,6 +9462,52 @@
"unpublished": false,
"deprecated": false
},
{
"name": "cos",
"summary": "Computes the sine of a number (in radians).",
"description": "",
"tags": [],
"args": [
{
"name": "num",
"type": "number",
"schema": {
"type": "number",
"format": "double"
},
"required": true
}
],
"returnValue": {
"name": "",
"type": "number",
"schema": {
"type": "number",
"format": "double"
},
"required": true
},
"unpublished": false,
"deprecated": false
},
{
"name": "e",
"summary": "Return the value of Eulers number `e`.",
"description": "",
"tags": [],
"args": [],
"returnValue": {
"name": "",
"type": "number",
"schema": {
"type": "number",
"format": "double"
},
"required": true
},
"unpublished": false,
"deprecated": false
},
{
"name": "extrude",
"summary": "Extrudes by a given amount.",
@ -9916,6 +10102,34 @@
"unpublished": false,
"deprecated": false
},
{
"name": "floor",
"summary": "Computes the largest integer less than or equal to a number.",
"description": "",
"tags": [],
"args": [
{
"name": "num",
"type": "number",
"schema": {
"type": "number",
"format": "double"
},
"required": true
}
],
"returnValue": {
"name": "",
"type": "number",
"schema": {
"type": "number",
"format": "double"
},
"required": true
},
"unpublished": false,
"deprecated": false
},
{
"name": "getExtrudeWallTransform",
"summary": "Returns the extrude wall transform.",
@ -11173,22 +11387,13 @@
},
"to": {
"description": "The to point.",
"anyOf": [
{
"description": "A point.",
"type": "array",
"items": {
"type": "number",
"format": "double"
},
"maxItems": 2,
"minItems": 2
},
{
"description": "A string like `default`.",
"type": "string"
}
]
"type": "array",
"items": {
"type": "number",
"format": "double"
},
"maxItems": 2,
"minItems": 2
}
}
},
@ -11201,10 +11406,6 @@
},
"maxItems": 2,
"minItems": 2
},
{
"description": "A string like `default`.",
"type": "string"
}
]
},
@ -13001,8 +13202,129 @@
"deprecated": false
},
{
"name": "min",
"summary": "Returns the minimum of the given arguments.",
"name": "ln",
"summary": "Computes the natural logarithm of the number.",
"description": "",
"tags": [],
"args": [
{
"name": "num",
"type": "number",
"schema": {
"type": "number",
"format": "double"
},
"required": true
}
],
"returnValue": {
"name": "",
"type": "number",
"schema": {
"type": "number",
"format": "double"
},
"required": true
},
"unpublished": false,
"deprecated": false
},
{
"name": "log",
"summary": "Computes the logarithm of the number with respect to an arbitrary base.",
"description": "The result might not be correctly rounded owing to implementation details; `log2()` can produce more accurate results for base 2, and `log10()` can produce more accurate results for base 10.",
"tags": [],
"args": [
{
"name": "num",
"type": "number",
"schema": {
"type": "number",
"format": "double"
},
"required": true
},
{
"name": "base",
"type": "number",
"schema": {
"type": "number",
"format": "double"
},
"required": true
}
],
"returnValue": {
"name": "",
"type": "number",
"schema": {
"type": "number",
"format": "double"
},
"required": true
},
"unpublished": false,
"deprecated": false
},
{
"name": "log10",
"summary": "Computes the base 10 logarithm of the number.",
"description": "",
"tags": [],
"args": [
{
"name": "num",
"type": "number",
"schema": {
"type": "number",
"format": "double"
},
"required": true
}
],
"returnValue": {
"name": "",
"type": "number",
"schema": {
"type": "number",
"format": "double"
},
"required": true
},
"unpublished": false,
"deprecated": false
},
{
"name": "log2",
"summary": "Computes the base 2 logarithm of the number.",
"description": "",
"tags": [],
"args": [
{
"name": "num",
"type": "number",
"schema": {
"type": "number",
"format": "double"
},
"required": true
}
],
"returnValue": {
"name": "",
"type": "number",
"schema": {
"type": "number",
"format": "double"
},
"required": true
},
"unpublished": false,
"deprecated": false
},
{
"name": "max",
"summary": "Computes the maximum of the given arguments.",
"description": "",
"tags": [],
"args": [
@ -13031,6 +13353,92 @@
"unpublished": false,
"deprecated": false
},
{
"name": "min",
"summary": "Computes the minimum of the given arguments.",
"description": "",
"tags": [],
"args": [
{
"name": "args",
"type": "[number]",
"schema": {
"type": "array",
"items": {
"type": "number",
"format": "double"
}
},
"required": true
}
],
"returnValue": {
"name": "",
"type": "number",
"schema": {
"type": "number",
"format": "double"
},
"required": true
},
"unpublished": false,
"deprecated": false
},
{
"name": "pi",
"summary": "Return the value of `pi`. Archimedes constant (π).",
"description": "",
"tags": [],
"args": [],
"returnValue": {
"name": "",
"type": "number",
"schema": {
"type": "number",
"format": "double"
},
"required": true
},
"unpublished": false,
"deprecated": false
},
{
"name": "pow",
"summary": "Computes the number to a power.",
"description": "",
"tags": [],
"args": [
{
"name": "num",
"type": "number",
"schema": {
"type": "number",
"format": "double"
},
"required": true
},
{
"name": "pow",
"type": "number",
"schema": {
"type": "number",
"format": "double"
},
"required": true
}
],
"returnValue": {
"name": "",
"type": "number",
"schema": {
"type": "number",
"format": "double"
},
"required": true
},
"unpublished": false,
"deprecated": false
},
{
"name": "segAng",
"summary": "Returns the angle of the segment.",
@ -15315,6 +15723,62 @@
"unpublished": false,
"deprecated": false
},
{
"name": "sin",
"summary": "Computes the sine of a number (in radians).",
"description": "",
"tags": [],
"args": [
{
"name": "num",
"type": "number",
"schema": {
"type": "number",
"format": "double"
},
"required": true
}
],
"returnValue": {
"name": "",
"type": "number",
"schema": {
"type": "number",
"format": "double"
},
"required": true
},
"unpublished": false,
"deprecated": false
},
{
"name": "sqrt",
"summary": "Computes the square root of a number.",
"description": "",
"tags": [],
"args": [
{
"name": "num",
"type": "number",
"schema": {
"type": "number",
"format": "double"
},
"required": true
}
],
"returnValue": {
"name": "",
"type": "number",
"schema": {
"type": "number",
"format": "double"
},
"required": true
},
"unpublished": false,
"deprecated": false
},
{
"name": "startSketchAt",
"summary": "Start a sketch at a given point.",
@ -15341,22 +15805,13 @@
},
"to": {
"description": "The to point.",
"anyOf": [
{
"description": "A point.",
"type": "array",
"items": {
"type": "number",
"format": "double"
},
"maxItems": 2,
"minItems": 2
},
{
"description": "A string like `default`.",
"type": "string"
}
]
"type": "array",
"items": {
"type": "number",
"format": "double"
},
"maxItems": 2,
"minItems": 2
}
}
},
@ -15369,10 +15824,6 @@
},
"maxItems": 2,
"minItems": 2
},
{
"description": "A string like `default`.",
"type": "string"
}
]
},
@ -15815,6 +16266,52 @@
"unpublished": false,
"deprecated": false
},
{
"name": "tan",
"summary": "Computes the tangent of a number (in radians).",
"description": "",
"tags": [],
"args": [
{
"name": "num",
"type": "number",
"schema": {
"type": "number",
"format": "double"
},
"required": true
}
],
"returnValue": {
"name": "",
"type": "number",
"schema": {
"type": "number",
"format": "double"
},
"required": true
},
"unpublished": false,
"deprecated": false
},
{
"name": "tau",
"summary": "Return the value of `tau`. The full circle constant (τ). Equal to 2π.",
"description": "",
"tags": [],
"args": [],
"returnValue": {
"name": "",
"type": "number",
"schema": {
"type": "number",
"format": "double"
},
"required": true
},
"unpublished": false,
"deprecated": false
},
{
"name": "xLine",
"summary": "Draw a line on the x-axis.",

View File

@ -5,6 +5,8 @@
## Table of Contents
* [Functions](#functions)
* [`abs`](#abs)
* [`acos`](#acos)
* [`angleToMatchLengthX`](#angleToMatchLengthX)
* [`angleToMatchLengthY`](#angleToMatchLengthY)
* [`angledLine`](#angledLine)
@ -14,9 +16,15 @@
* [`angledLineToX`](#angledLineToX)
* [`angledLineToY`](#angledLineToY)
* [`arc`](#arc)
* [`asin`](#asin)
* [`atan`](#atan)
* [`bezierCurve`](#bezierCurve)
* [`ceil`](#ceil)
* [`close`](#close)
* [`cos`](#cos)
* [`e`](#e)
* [`extrude`](#extrude)
* [`floor`](#floor)
* [`getExtrudeWallTransform`](#getExtrudeWallTransform)
* [`lastSegX`](#lastSegX)
* [`lastSegY`](#lastSegY)
@ -25,13 +33,24 @@
* [`legLen`](#legLen)
* [`line`](#line)
* [`lineTo`](#lineTo)
* [`ln`](#ln)
* [`log`](#log)
* [`log10`](#log10)
* [`log2`](#log2)
* [`max`](#max)
* [`min`](#min)
* [`pi`](#pi)
* [`pow`](#pow)
* [`segAng`](#segAng)
* [`segEndX`](#segEndX)
* [`segEndY`](#segEndY)
* [`segLen`](#segLen)
* [`show`](#show)
* [`sin`](#sin)
* [`sqrt`](#sqrt)
* [`startSketchAt`](#startSketchAt)
* [`tan`](#tan)
* [`tau`](#tau)
* [`xLine`](#xLine)
* [`xLineTo`](#xLineTo)
* [`yLine`](#yLine)
@ -40,6 +59,46 @@
## Functions
### abs
Computes the absolute value of a number.
```
abs(num: number) -> number
```
#### Arguments
* `num`: `number`
#### Returns
* `number`
### acos
Computes the arccosine of a number (in radians).
```
acos(num: number) -> number
```
#### Arguments
* `num`: `number`
#### Returns
* `number`
### angleToMatchLengthX
Returns the angle to match the given length for x.
@ -1328,6 +1387,46 @@ arc(data: ArcData, sketch_group: SketchGroup) -> SketchGroup
### asin
Computes the arcsine of a number (in radians).
```
asin(num: number) -> number
```
#### Arguments
* `num`: `number`
#### Returns
* `number`
### atan
Computes the arctangent of a number (in radians).
```
atan(num: number) -> number
```
#### Arguments
* `num`: `number`
#### Returns
* `number`
### bezierCurve
Draw a bezier curve.
@ -1493,6 +1592,26 @@ bezierCurve(data: BezierData, sketch_group: SketchGroup) -> SketchGroup
### ceil
Computes the smallest integer greater than or equal to a number.
```
ceil(num: number) -> number
```
#### Arguments
* `num`: `number`
#### Returns
* `number`
### close
Close the current sketch.
@ -1637,6 +1756,45 @@ close(sketch_group: SketchGroup) -> SketchGroup
### cos
Computes the sine of a number (in radians).
```
cos(num: number) -> number
```
#### Arguments
* `num`: `number`
#### Returns
* `number`
### e
Return the value of Eulers number `e`.
```
e() -> number
```
#### Arguments
#### Returns
* `number`
### extrude
Extrudes by a given amount.
@ -1746,6 +1904,26 @@ extrude(length: number, sketch_group: SketchGroup) -> ExtrudeGroup
### floor
Computes the largest integer less than or equal to a number.
```
floor(num: number) -> number
```
#### Arguments
* `num`: `number`
#### Returns
* `number`
### getExtrudeWallTransform
Returns the extrude wall transform.
@ -2044,11 +2222,9 @@ line(data: LineData, sketch_group: SketchGroup) -> SketchGroup
// The tag.
tag: string,
// The to point.
to: [number] |
string,
to: [number],
} |
[number] |
string
[number]
```
* `sketch_group`: `SketchGroup` - A sketch group is a collection of paths.
```
@ -2336,9 +2512,110 @@ lineTo(data: LineToData, sketch_group: SketchGroup) -> SketchGroup
### ln
Computes the natural logarithm of the number.
```
ln(num: number) -> number
```
#### Arguments
* `num`: `number`
#### Returns
* `number`
### log
Computes the logarithm of the number with respect to an arbitrary base.
The result might not be correctly rounded owing to implementation details; `log2()` can produce more accurate results for base 2, and `log10()` can produce more accurate results for base 10.
```
log(num: number, base: number) -> number
```
#### Arguments
* `num`: `number`
* `base`: `number`
#### Returns
* `number`
### log10
Computes the base 10 logarithm of the number.
```
log10(num: number) -> number
```
#### Arguments
* `num`: `number`
#### Returns
* `number`
### log2
Computes the base 2 logarithm of the number.
```
log2(num: number) -> number
```
#### Arguments
* `num`: `number`
#### Returns
* `number`
### max
Computes the maximum of the given arguments.
```
max(args: [number]) -> number
```
#### Arguments
* `args`: `[number]`
#### Returns
* `number`
### min
Returns the minimum of the given arguments.
Computes the minimum of the given arguments.
@ -2356,6 +2633,46 @@ min(args: [number]) -> number
### pi
Return the value of `pi`. Archimedes constant (π).
```
pi() -> number
```
#### Arguments
#### Returns
* `number`
### pow
Computes the number to a power.
```
pow(num: number, pow: number) -> number
```
#### Arguments
* `num`: `number`
* `pow`: `number`
#### Returns
* `number`
### segAng
Returns the angle of the segment.
@ -2766,6 +3083,46 @@ show(sketch: SketchGroup)
### sin
Computes the sine of a number (in radians).
```
sin(num: number) -> number
```
#### Arguments
* `num`: `number`
#### Returns
* `number`
### sqrt
Computes the square root of a number.
```
sqrt(num: number) -> number
```
#### Arguments
* `num`: `number`
#### Returns
* `number`
### startSketchAt
Start a sketch at a given point.
@ -2784,11 +3141,9 @@ startSketchAt(data: LineData) -> SketchGroup
// The tag.
tag: string,
// The to point.
to: [number] |
string,
to: [number],
} |
[number] |
string
[number]
```
#### Returns
@ -2859,6 +3214,45 @@ string
### tan
Computes the tangent of a number (in radians).
```
tan(num: number) -> number
```
#### Arguments
* `num`: `number`
#### Returns
* `number`
### tau
Return the value of `tau`. The full circle constant (τ). Equal to 2π.
```
tau() -> number
```
#### Arguments
#### Returns
* `number`
### xLine
Draw a line on the x-axis.

75
docs/kcl/types.md Normal file
View File

@ -0,0 +1,75 @@
# Types
`KCL` defines the following types and keywords the language.
All these types can be nested in various forms where nesting applies. Like
arrays can hold objects and vice versa.
## Boolean
`true` or `false` work when defining values.
## Variable declaration
Variables are defined with the `let` keyword like so:
```
let myBool = false
```
## Array
An array is defined with `[]` braces. What is inside the brackets can
be of any type. For example, the following is completely valid:
```
let myArray = ["thing", 2, false]
```
If you want to get a value from an array you can use the index like so:
`myArray[0]`.
## Object
An object is defined with `{}` braces. Here is an example object:
```
let myObj = {a: 0, b: "thing"}
```
We support two different ways of getting properties from objects, you can call
`myObj.a` or `myObj["a"]` both work.
## Functions
We also have support for defining your own functions. Functions can take in any
type of argument. Below is an example of the syntax:
```
fn myFn = (x) => {
return x
}
```
As you can see above `myFn` just returns whatever it is given.
## Binary expressions
You can also do math! Let's show an example below:
```
let myMathExpression = 3 + 1 * 2 / 3 - 7
```
You can nest expressions in parenthesis as well:
```
let myMathExpression = 3 + (1 * 2 / (3 - 7))
```
Please if you find any issues using any of the above expressions or syntax
please file an issue with the `ast` label on the [modeling-app
repo](https://github.com/KittyCAD/modeling-app/issues/new).

View File

@ -1,6 +1,6 @@
{
"name": "untitled-app",
"version": "0.6.0",
"version": "0.9.2",
"private": true,
"dependencies": {
"@codemirror/autocomplete": "^6.9.0",
@ -10,10 +10,11 @@
"@fortawesome/react-fontawesome": "^0.2.0",
"@headlessui/react": "^1.7.13",
"@headlessui/tailwindcss": "^0.2.0",
"@kittycad/lib": "^0.0.37",
"@kittycad/lib": "^0.0.39",
"@lezer/javascript": "^1.4.7",
"@open-rpc/client-js": "^1.8.1",
"@react-hook/resize-observer": "^1.2.6",
"@replit/codemirror-interact": "^6.3.0",
"@sentry/react": "^7.65.0",
"@tauri-apps/api": "^1.3.0",
"@testing-library/jest-dom": "^5.14.1",
@ -26,6 +27,7 @@
"@uiw/react-codemirror": "^4.21.13",
"@xstate/react": "^3.2.2",
"crypto-js": "^4.1.1",
"debounce-promise": "^3.1.2",
"formik": "^2.4.3",
"fuse.js": "^6.6.2",
"http-server": "^14.1.1",
@ -61,15 +63,17 @@
"build:local": "vite build",
"build:both": "vite build",
"build:both:local": "yarn build:wasm && vite build",
"pretest": "yarn remove-importmeta",
"test": "vitest --mode development",
"test:nowatch": "vitest run --mode development",
"test:rust": "(cd src/wasm-lib && cargo test --all && cargo clippy --all --tests)",
"test:rust": "(cd src/wasm-lib && cargo test --all && cargo clippy --all --tests --benches)",
"test:cov": "vitest run --coverage --mode development",
"simpleserver:ci": "http-server ./public --cors -p 3000 &",
"simpleserver": "http-server ./public --cors -p 3000",
"simpleserver:ci": "yarn pretest && http-server ./public --cors -p 3000 &",
"simpleserver": "yarn pretest && http-server ./public --cors -p 3000",
"fmt": "prettier --write ./src",
"fmt-check": "prettier --check ./src",
"build:wasm": "yarn wasm-prep && (cd src/wasm-lib && wasm-pack build --target web --out-dir pkg && cargo test -p kcl-lib export_bindings) && cp src/wasm-lib/pkg/wasm_lib_bg.wasm public && yarn fmt && yarn remove-importmeta",
"build:wasm": "(cd src/wasm-lib && wasm-pack build --target web --out-dir pkg && cargo test -p kcl-lib export_bindings) && cp src/wasm-lib/pkg/wasm_lib_bg.wasm public && yarn fmt",
"build:wasm-clean": "yarn wasm-prep && yarn build:wasm",
"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": "rm -rf src/wasm-lib/pkg && mkdir src/wasm-lib/pkg && rm -rf src/wasm-lib/kcl/bindings",
"lint": "eslint --fix src",
@ -98,7 +102,7 @@
"@babel/preset-env": "^7.22.9",
"@tauri-apps/cli": "^1.3.1",
"@types/crypto-js": "^4.1.1",
"@types/debounce": "^1.2.1",
"@types/debounce-promise": "^3.1.6",
"@types/isomorphic-fetch": "^0.0.36",
"@types/react-modal": "^3.16.0",
"@types/uuid": "^9.0.1",

42
public/expectations.md Normal file
View File

@ -0,0 +1,42 @@
## Alpha Users Expectations
### Welcome
First off, thank you so much for your interest in being a part of the closed Alpha program! We are thrilled to have others use our product and see what you build with it (and truthfully, how you break it too).
### KittyCAD Modeling App (KCMA)
What we are introducing to you is our KittyCAD Modeling App (KCMA). KCMA is a CAD application that expresses a hybrid style of traditional CAD interface along with a code-CAD interface. KCMA is a great way for us to test our own APIs as well as inspire others to develop their own applications.
### Why Code?
Plenty of you have professional CAD experience, and may not understand why coding your model would be helpful. The "code-CAD" paradigm isnt as popular as traditional CAD programs (SolidWorks, NX, CREO, OnShape, etc.), but it certainly has its benefits. Some benefits include:
- Automation and parametric design
- Customization and flexibility
- Algorithmic and generative design
- Reproducibility
- Easier integration with other tools
### Before You Use KCMA
Before you dive straight into the app, we wanted to lay some expectations out for you.
- KCMA is in early development. Kurt pitched the idea back in January, and the team has been working hard on it since then. KCMA has really basic CAD features for now, but we have plenty of features on our roadmap. Most of the features that you may be currently used to in your CAD workflow today will be available down the road.
- For a list of all scripting functions, please reference our [documentation](https://github.com/KittyCAD/modeling-app/blob/main/docs/kcl/std.md). For a basic rundown of our types, please reference [this document](https://github.com/KittyCAD/modeling-app/blob/main/docs/kcl/types.md).
- With that being said, we have created an external new features list in [GH Discussions](https://github.com/KittyCAD/modeling-app/discussions). For our current priority list, please click [here](https://github.com/KittyCAD/modeling-app/blob/main/public/roadmap.md). Please upvote any features in the GH Discussions page that you would like to see implemented first. We will prioritize the highest upvoted items or items that are foundational for other features on the list. You can also add your own, but we will review it to make sure its not a duplicate or its feasible for the current state of the app.
- Please report any and all bugs/issues you find. Even the smallest bugs are important! You can report them in a GH Issue [here](https://github.com/KittyCAD/modeling-app/issues/new). You are more than welcome to link your GH Issue in the **bugs** section of our Discord, but if you want to discuss the bug further, please keep that in the GH Issue thread. Please include the severity of the bug in your GH Issue ticket (High, Medium, or Low). If you are having trouble deciding what severity the bug is, use this guideline:
- **High:** The bug is blocking you from continuing.
- Example: Every time I click the extrude button with two faces selected, the app crashes.
- **Medium:** You can find a workaround to the problem, but it increases your time spent working or makes it unenjoyable.
- Example: When the app is full screen on Mac, the settings are not showing properly. It works if I have the app windowed.
- **Low:** The bug is annoying but doesnt affect workflow or block you from continuing (usually you can say “It would be nice if ___, but its not needed”)
- Example: It would be nice if the camera would orient normal to the sketching surface when I select a face/plane and click “sketch”.
- We want you all to be aware that we may reach out to you in regard to issues, bugs, problems, and satisfaction. This will typically be for further clarification so we can really nail things down.
### Discord
We will be using Discord a lot more now that the Alpha has been released to people outside of the company. Please feel free to discuss and talk with us in the **alpha users** section of the server. We highly encourage you to engage with us on Discord!
### Thank You!
Once again, from all of us to you, thank you for being a part of the closed Alpha. We are happy to chat with you all, hear your feedback, and see some of your projects!

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 16 KiB

BIN
public/kcma-logomark.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

46
public/kcma-logomark.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 148 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 142 KiB

26
public/roadmap.md Normal file
View File

@ -0,0 +1,26 @@
## KittyCAD Modeling App Roadmap
This document ties into our [GH Discussions Feature List](https://github.com/KittyCAD/modeling-app/discussions). Please upvote any features that you want to see next, or add ones that are not listed and we will review.
### Current Priority List
1. [Sketch on Face](https://github.com/KittyCAD/modeling-app/discussions/477)
2. [Revolve](https://github.com/KittyCAD/modeling-app/discussions/496)
3. [Fillet](https://github.com/KittyCAD/modeling-app/discussions/501)
4. [Linear Pattern](https://github.com/KittyCAD/modeling-app/discussions/256)
5. [Circular Pattern](https://github.com/KittyCAD/modeling-app/discussions/257)
6. [Mirror-Sketch](https://github.com/KittyCAD/modeling-app/discussions/507)
7. [Chamfer](https://github.com/KittyCAD/modeling-app/discussions/502)
8. [Sweep](https://github.com/KittyCAD/modeling-app/discussions/498)
9. [Draft](https://github.com/KittyCAD/modeling-app/discussions/495)
10. [Shell](https://github.com/KittyCAD/modeling-app/discussions/503)
11. [Union](https://github.com/KittyCAD/modeling-app/discussions/509)
12. [Mirror-Model](https://github.com/KittyCAD/modeling-app/discussions/508)
13. [Subtract](https://github.com/KittyCAD/modeling-app/discussions/510)
14. [Intersect](https://github.com/KittyCAD/modeling-app/discussions/511)
15. [Offset](https://github.com/KittyCAD/modeling-app/discussions/512)
16. [Thicken](https://github.com/KittyCAD/modeling-app/discussions/499)
17. [Import](https://github.com/KittyCAD/modeling-app/discussions/478)
18. [Assemblies](https://github.com/KittyCAD/modeling-app/discussions/494)
19. [External Thread](https://github.com/KittyCAD/modeling-app/discussions/505)

1031
src-tauri/Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -16,13 +16,14 @@ tauri-build = { version = "1.4.0", features = [] }
[dependencies]
anyhow = "1"
oauth2 = "4.4.1"
kittycad = "0.2.26"
oauth2 = "4.4.2"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
tauri = { version = "1.3.0", features = [ "updater", "path-all", "dialog-all", "fs-all", "http-request", "shell-open", "shell-open-api"] }
tokio = { version = "1.29.1", features = ["time"] }
toml = "0.6.0"
tauri = { version = "1.4.1", features = [ "os-all", "dialog-all", "fs-all", "http-request", "path-all", "shell-open", "shell-open-api", "updater", "devtools"] }
tauri-plugin-fs-extra = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" }
tokio = { version = "1.32.0", features = ["time"] }
toml = "0.8.0"
[features]
# this feature is used for production builds or when `devPath` points to the filesystem and the built-in dev server is disabled.

View File

@ -6,6 +6,7 @@ use std::io::Read;
use anyhow::Result;
use oauth2::TokenResponse;
use tauri::{InvokeError, Manager};
const DEFAULT_HOST: &str = "https://api.kittycad.io";
/// This command returns the a json string parse from a toml file at the path.
#[tauri::command]
@ -85,6 +86,47 @@ async fn login(app: tauri::AppHandle, host: &str) -> Result<String, InvokeError>
Ok(token)
}
///This command returns the KittyCAD user info given a token.
/// The string returned from this method is the user info as a json string.
#[tauri::command]
async fn get_user(
token: Option<String>,
hostname: &str,
) -> Result<kittycad::types::User, InvokeError> {
// Use the host passed in if it's set.
// Otherwise, use the default host.
let host = if hostname.is_empty() {
DEFAULT_HOST.to_string()
} else {
hostname.to_string()
};
// Change the baseURL to the one we want.
let mut baseurl = host.to_string();
if !host.starts_with("http://") && !host.starts_with("https://") {
baseurl = format!("https://{host}");
if host.starts_with("localhost") {
baseurl = format!("http://{host}")
}
}
println!("Getting user info...");
// use kittycad library to fetch the user info from /user/me
let mut client = kittycad::Client::new(token.unwrap());
if baseurl != DEFAULT_HOST {
client.set_base_url(&baseurl);
}
let user_info: kittycad::types::User = client
.users()
.get_self()
.await
.map_err(|e| InvokeError::from_anyhow(e.into()))?;
Ok(user_info)
}
fn main() {
tauri::Builder::default()
.setup(|app| {
@ -97,7 +139,12 @@ fn main() {
}
Ok(())
})
.invoke_handler(tauri::generate_handler![login, read_toml, read_txt_file])
.invoke_handler(tauri::generate_handler![
get_user,
login,
read_toml,
read_txt_file
])
.plugin(tauri_plugin_fs_extra::init())
.run(tauri::generate_context!())
.expect("error while running tauri application");

View File

@ -8,7 +8,7 @@
},
"package": {
"productName": "kittycad-modeling",
"version": "0.6.0"
"version": "0.9.2"
},
"tauri": {
"allowlist": {
@ -36,6 +36,9 @@
"https://api.dev.kittycad.io/*"
]
},
"os": {
"all": true
},
"shell": {
"open": true
},
@ -71,9 +74,9 @@
"shortDescription": "",
"targets": "all",
"windows": {
"certificateThumbprint": null,
"certificateThumbprint": "F4C9A52FF7BC26EE5E054946F6B11DEEA94C748D",
"digestAlgorithm": "sha256",
"timestampUrl": ""
"timestampUrl": "http://timestamp.digicert.com"
}
},
"security": {

View File

@ -1,14 +1,6 @@
import {
useRef,
useEffect,
useLayoutEffect,
useCallback,
MouseEventHandler,
} from 'react'
import { useRef, useEffect, useCallback, MouseEventHandler } from 'react'
import { DebugPanel } from './components/DebugPanel'
import { v4 as uuidv4 } from 'uuid'
import { asyncParser } from './lang/abstractSyntaxTree'
import { _executor } from './lang/executor'
import { PaneType, useStore } from './useStore'
import { Logs, KCLErrors } from './components/Logs'
import { CollapsiblePanel } from './components/CollapsiblePanel'
@ -16,13 +8,9 @@ import { MemoryPanel } from './components/MemoryPanel'
import { useHotKeyListener } from './hooks/useHotKeyListener'
import { Stream } from './components/Stream'
import ModalContainer from 'react-modal-promise'
import {
EngineCommand,
EngineCommandManager,
} from './lang/std/engineConnection'
import { EngineCommand } from './lang/std/engineConnection'
import { throttle } from './lib/utils'
import { AppHeader } from './components/AppHeader'
import { KCLError } from './lang/errors'
import { Resizable } from 're-resizable'
import {
faCode,
@ -41,6 +29,9 @@ import { CameraDragInteractionType_type } from '@kittycad/lib/dist/types/src/mod
import { CodeMenu } from 'components/CodeMenu'
import { TextEditor } from 'components/TextEditor'
import { Themes, getSystemTheme } from 'lib/theme'
import { useSetupEngineManager } from 'hooks/useSetupEngineManager'
import { useEngineConnectionSubscriptions } from 'hooks/useEngineConnectionSubscriptions'
import { engineCommandManager } from './lang/std/engineConnection'
export function App() {
const { code: loadedCode, project } = useLoaderData() as IndexLoaderData
@ -48,59 +39,25 @@ export function App() {
const streamRef = useRef<HTMLDivElement>(null)
useHotKeyListener()
const {
addLog,
addKCLError,
setCode,
setAst,
setError,
setProgramMemory,
resetLogs,
resetKCLErrors,
setArtifactMap,
engineCommandManager,
setEngineCommandManager,
highlightRange,
setHighlightRange,
setCursor2,
setMediaStream,
setIsStreamReady,
isStreamReady,
buttonDownInStream,
openPanes,
setOpenPanes,
didDragInStream,
setStreamDimensions,
streamDimensions,
setIsExecuting,
defferedCode,
guiMode,
setGuiMode,
executeAst,
} = useStore((s) => ({
guiMode: s.guiMode,
addLog: s.addLog,
defferedCode: s.defferedCode,
setGuiMode: s.setGuiMode,
setCode: s.setCode,
setAst: s.setAst,
setError: s.setError,
setProgramMemory: s.setProgramMemory,
resetLogs: s.resetLogs,
resetKCLErrors: s.resetKCLErrors,
setArtifactMap: s.setArtifactNSourceRangeMaps,
engineCommandManager: s.engineCommandManager,
setEngineCommandManager: s.setEngineCommandManager,
highlightRange: s.highlightRange,
setHighlightRange: s.setHighlightRange,
setCursor2: s.setCursor2,
setMediaStream: s.setMediaStream,
isStreamReady: s.isStreamReady,
setIsStreamReady: s.setIsStreamReady,
buttonDownInStream: s.buttonDownInStream,
addKCLError: s.addKCLError,
openPanes: s.openPanes,
setOpenPanes: s.setOpenPanes,
didDragInStream: s.didDragInStream,
setStreamDimensions: s.setStreamDimensions,
streamDimensions: s.streamDimensions,
setIsExecuting: s.setIsExecuting,
executeAst: s.executeAst,
}))
const {
@ -127,13 +84,58 @@ export function App() {
useHotkeys('shift + l', () => togglePane('logs'))
useHotkeys('shift + e', () => togglePane('kclErrors'))
useHotkeys('shift + d', () => togglePane('debug'))
useHotkeys('esc', () => {
if (guiMode.mode === 'sketch') {
if (guiMode.sketchMode === 'selectFace') return
if (guiMode.sketchMode === 'sketchEdit') {
// TODO: share this with Toolbar's "Exit sketch" button
// exiting sketch should be done consistently across all exits
engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: { type: 'edit_mode_exit' },
})
engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: { type: 'default_camera_disable_sketch_mode' },
})
setGuiMode({ mode: 'default' })
// this is necessary to get the UI back into a consistent
// state right now, hopefully won't need to rerender
// when exiting sketch mode in the future
executeAst()
} else {
engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'set_tool',
tool: 'select',
},
})
setGuiMode({
mode: 'sketch',
sketchMode: 'sketchEdit',
rotation: guiMode.rotation,
position: guiMode.position,
pathToNode: guiMode.pathToNode,
pathId: guiMode.pathId,
// todo: ...guiMod is adding isTooltip: true, will probably just fix with xstate migtaion
})
}
} else {
setGuiMode({ mode: 'default' })
}
})
const paneOpacity =
onboardingStatus === onboardingPaths.CAMERA
? 'opacity-20'
: didDragInStream
? 'opacity-40'
: ''
const paneOpacity = [onboardingPaths.CAMERA, onboardingPaths.STREAMING].some(
(p) => p === onboardingStatus
)
? 'opacity-20'
: didDragInStream
? 'opacity-40'
: ''
// Use file code loaded from disk
// on mount, and overwrite any locally-stored code
@ -149,145 +151,11 @@ export function App() {
}
}, [loadedCode, setCode])
const streamWidth = streamRef?.current?.offsetWidth
const streamHeight = streamRef?.current?.offsetHeight
const width = streamWidth ? streamWidth : 0
const quadWidth = Math.round(width / 4) * 4
const height = streamHeight ? streamHeight : 0
const quadHeight = Math.round(height / 4) * 4
useLayoutEffect(() => {
setStreamDimensions({
streamWidth: quadWidth,
streamHeight: quadHeight,
})
if (!width || !height) return
const eng = new EngineCommandManager({
setMediaStream,
setIsStreamReady,
width: quadWidth,
height: quadHeight,
token,
})
setEngineCommandManager(eng)
return () => {
eng?.tearDown()
}
}, [quadWidth, quadHeight])
useEffect(() => {
if (!isStreamReady) return
if (!engineCommandManager) return
let unsubFn: any[] = []
const asyncWrap = async () => {
try {
if (!defferedCode) {
setAst({
start: 0,
end: 0,
body: [],
nonCodeMeta: {
noneCodeNodes: {},
start: null,
},
})
setProgramMemory({ root: {} })
engineCommandManager.endSession()
engineCommandManager.startNewSession()
return
}
const _ast = await asyncParser(defferedCode)
setAst(_ast)
resetLogs()
resetKCLErrors()
engineCommandManager.endSession()
engineCommandManager.startNewSession()
setIsExecuting(true)
const programMemory = await _executor(
_ast,
{
root: {
_0: {
type: 'userVal',
value: 0,
__meta: [],
},
_90: {
type: 'userVal',
value: 90,
__meta: [],
},
_180: {
type: 'userVal',
value: 180,
__meta: [],
},
_270: {
type: 'userVal',
value: 270,
__meta: [],
},
},
},
engineCommandManager
)
const { artifactMap, sourceRangeMap } =
await engineCommandManager.waitForAllCommands()
setIsExecuting(false)
if (programMemory !== undefined) {
setProgramMemory(programMemory)
}
setArtifactMap({ artifactMap, sourceRangeMap })
const unSubHover = engineCommandManager.subscribeToUnreliable({
event: 'highlight_set_entity',
callback: ({ data }) => {
if (data?.entity_id) {
const sourceRange = sourceRangeMap[data.entity_id]
setHighlightRange(sourceRange)
} else if (
!highlightRange ||
(highlightRange[0] !== 0 && highlightRange[1] !== 0)
) {
setHighlightRange([0, 0])
}
},
})
const unSubClick = engineCommandManager.subscribeTo({
event: 'select_with_point',
callback: ({ data }) => {
if (!data?.entity_id) {
setCursor2()
return
}
const sourceRange = sourceRangeMap[data.entity_id]
setCursor2({ range: sourceRange, type: 'default' })
},
})
unsubFn.push(unSubHover, unSubClick)
setError()
} catch (e: any) {
setIsExecuting(false)
if (e instanceof KCLError) {
addKCLError(e)
} else {
setError('problem')
console.log(e)
addLog(e)
}
}
}
asyncWrap()
return () => {
unsubFn.forEach((fn) => fn())
}
}, [defferedCode, isStreamReady, engineCommandManager])
useSetupEngineManager(streamRef, token)
useEngineConnectionSubscriptions()
const debounceSocketSend = throttle<EngineCommand>((message) => {
engineCommandManager?.sendSceneCommand(message)
engineCommandManager.sendSceneCommand(message)
}, 16)
const handleMouseMove: MouseEventHandler<HTMLDivElement> = (e) => {
e.nativeEvent.preventDefault()
@ -313,18 +181,6 @@ export function App() {
window: { x, y },
},
})
} else if (
guiMode.mode === 'sketch' &&
guiMode.sketchMode === ('move' as any)
) {
debounceSocketSend({
type: 'modeling_cmd_req',
cmd_id: newCmdId,
cmd: {
type: 'handle_mouse_drag_move',
window: { x, y },
},
})
} else {
debounceSocketSend({
type: 'modeling_cmd_req',
@ -336,6 +192,17 @@ export function App() {
})
}
} else {
if (guiMode.mode === 'sketch' && guiMode.sketchMode === ('move' as any)) {
debounceSocketSend({
type: 'modeling_cmd_req',
cmd_id: newCmdId,
cmd: {
type: 'handle_mouse_drag_move',
window: { x, y },
},
})
return
}
const interactionGuards = cameraMouseDragGuards[cameraControls]
let interaction: CameraDragInteractionType_type
@ -348,7 +215,6 @@ export function App() {
} else if (interactionGuards.zoom.dragCallback(eWithButton)) {
interaction = 'zoom'
} else {
console.log('none')
return
}
@ -401,7 +267,7 @@ export function App() {
'hover:bg-liquid-30/40 dark:hover:bg-liquid-10/40 bg-transparent transition-colors duration-100 transition-ease-out delay-100',
}}
>
<div className="h-full flex flex-col justify-between">
<div id="code-pane" className="h-full flex flex-col justify-between">
<CollapsiblePanel
title="Code"
icon={faCode}

View File

@ -6,9 +6,9 @@ export const Auth = ({ children }: React.PropsWithChildren) => {
const {
auth: { state },
} = useGlobalStateContext()
const isLoggedIn = state.matches('checkIfLoggedIn')
const isLoggingIn = state.matches('checkIfLoggedIn')
return isLoggedIn ? (
return isLoggingIn ? (
<Loading>Loading KittyCAD Modeling App...</Loading>
) : (
<>{children}</>

View File

@ -130,6 +130,7 @@ const router = createBrowserRouter(
path: paths.INDEX,
loader: () =>
isTauri() ? redirect(paths.HOME) : redirect(paths.FILE + '/new'),
errorElement: <ErrorPage />,
},
{
path: paths.FILE + '/:id',
@ -140,7 +141,6 @@ const router = createBrowserRouter(
{!isTauri() && import.meta.env.PROD && <DownloadAppBanner />}
</Auth>
),
errorElement: <ErrorPage />,
id: paths.FILE,
loader: async ({
request,

View File

@ -47,6 +47,52 @@
@apply hover:bg-cool-20;
}
.toolbarButtons::-webkit-scrollbar {
@apply h-0.5;
}
.toolbarButtons {
@apply flex items-center overflow-x-auto;
scrollbar-width: thin;
}
.toolbarButtons button {
@apply text-chalkboard-90 bg-chalkboard-10/50 border-chalkboard-50 whitespace-nowrap;
display: inline-flex;
align-items: center;
justify-content: center;
@apply gap-1.5 p-0.5 pr-1;
@apply rounded-sm;
}
:global(.dark) .toolbarButtons button {
@apply text-chalkboard-30 bg-chalkboard-90/50 border-chalkboard-50;
}
.toolbarButtons button:hover {
@apply text-cool-90 bg-cool-10;
}
:global(.sketch) .toolbarButtons button:hover {
@apply text-fern-90 bg-fern-10;
}
.toolbarButtons button:disabled {
@apply text-chalkboard-70 bg-chalkboard-30;
}
.toolbarButtons button:disabled:hover {
@apply !bg-inherit !text-inherit cursor-not-allowed;
}
:global(.dark) .toolbarButtons button {
@apply text-chalkboard-20 border-chalkboard-50;
}
:global(.dark) .toolbarButtons button:hover {
@apply text-cool-10 border-chalkboard-50 bg-cool-90;
}
:global(.dark .sketch) .toolbarButtons button:hover {
@apply text-fern-10 border-chalkboard-50 bg-fern-90;
}
:global(.dark) .toolbarButtons button:disabled {
@apply text-chalkboard-40 bg-chalkboard-80;
}
:global(.dark) .popoverToggle {
@apply hover:bg-cool-90;
}

View File

@ -1,4 +1,4 @@
import { useStore, toolTips, Selections } from './useStore'
import { useStore, toolTips, ToolTip } from './useStore'
import { extrudeSketch, sketchOnExtrudedFace } from './lang/modifyAst'
import { getNodePathFromSourceRange } from './lang/queryAst'
import { HorzVert } from './components/Toolbar/HorzVert'
@ -17,6 +17,31 @@ import { Popover, Transition } from '@headlessui/react'
import styles from './Toolbar.module.css'
import { v4 as uuidv4 } from 'uuid'
import { useAppMode } from 'hooks/useAppMode'
import { ActionIcon } from 'components/ActionIcon'
import { engineCommandManager } from './lang/std/engineConnection'
export const sketchButtonClassnames = {
background:
'bg-chalkboard-100 group-hover:bg-chalkboard-90 hover:bg-chalkboard-90 dark:bg-fern-20 dark:group-hover:bg-fern-10 dark:hover:bg-fern-10 group-disabled:bg-chalkboard-50 dark:group-disabled:bg-chalkboard-60 group-hover:group-disabled:bg-chalkboard-50 dark:group-hover:group-disabled:bg-chalkboard-50',
icon: 'text-fern-20 h-auto group-hover:text-fern-10 hover:text-fern-10 dark:text-chalkboard-100 dark:group-hover:text-chalkboard-100 dark:hover:text-chalkboard-100 group-disabled:bg-chalkboard-60 hover:group-disabled:text-inherit',
}
const sketchFnLabels: Record<ToolTip | 'sketch_line' | 'move', string> = {
sketch_line: 'Line',
line: 'Line',
move: 'Move',
angledLine: 'Angled Line',
angledLineThatIntersects: 'Angled Line That Intersects',
angledLineOfXLength: 'Angled Line Of X Length',
angledLineOfYLength: 'Angled Line Of Y Length',
angledLineToX: 'Angled Line To X',
angledLineToY: 'Angled Line To Y',
lineTo: 'Line to Point',
xLine: 'Horizontal Line',
yLine: 'Vertical Line',
xLineTo: 'Horizontal Line to Point',
yLineTo: 'Vertical Line to Point',
}
export const Toolbar = () => {
const {
@ -26,7 +51,7 @@ export const Toolbar = () => {
ast,
updateAst,
programMemory,
engineCommandManager,
executeAst,
} = useStore((s) => ({
guiMode: s.guiMode,
setGuiMode: s.setGuiMode,
@ -34,17 +59,13 @@ export const Toolbar = () => {
ast: s.ast,
updateAst: s.updateAst,
programMemory: s.programMemory,
engineCommandManager: s.engineCommandManager,
executeAst: s.executeAst,
}))
useAppMode()
useEffect(() => {
console.log('guiMode', guiMode)
}, [guiMode])
function ToolbarButtons() {
function ToolbarButtons({ className }: React.HTMLAttributes<HTMLElement>) {
return (
<span className="overflow-x-auto">
<span className={styles.toolbarButtons + ' ' + className}>
{guiMode.mode === 'default' && (
<button
onClick={() => {
@ -53,7 +74,9 @@ export const Toolbar = () => {
sketchMode: 'selectFace',
})
}}
className="group"
>
<ActionIcon icon="sketch" className="!p-0.5" size="md" />
Start Sketch
</button>
)}
@ -70,33 +93,33 @@ export const Toolbar = () => {
pathToNode,
programMemory
)
updateAst(modifiedAst)
updateAst(modifiedAst, true)
}}
className="group"
>
SketchOnFace
<ActionIcon icon="sketch" className="!p-0.5" size="md" />
Sketch on Face
</button>
)}
{guiMode.mode === 'canEditSketch' && (
<button
onClick={() => {
console.log('guiMode.pathId', guiMode.pathId)
engineCommandManager?.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'edit_mode_enter',
target: guiMode.pathId,
},
})
const pathToNode = getNodePathFromSourceRange(
ast,
selectionRanges.codeBasedSelections[0].range
)
setGuiMode({
mode: 'sketch',
sketchMode: 'sketchEdit',
pathToNode: guiMode.pathToNode,
rotation: guiMode.rotation,
position: guiMode.position,
sketchMode: 'enterSketchEdit',
pathToNode: pathToNode,
rotation: [0, 0, 0, 1],
position: [0, 0, 0],
pathId: guiMode.pathId,
})
}}
className="group"
>
<ActionIcon icon="sketch" className="!p-0.5" size="md" />
Edit Sketch
</button>
)}
@ -113,10 +136,12 @@ export const Toolbar = () => {
ast,
pathToNode
)
updateAst(modifiedAst, { focusPath: pathToExtrudeArg })
updateAst(modifiedAst, true, { focusPath: pathToExtrudeArg })
}}
className="group"
>
ExtrudeSketch
<ActionIcon icon="extrude" className="!p-0.5" size="md" />
Extrude
</button>
<button
onClick={() => {
@ -130,10 +155,12 @@ export const Toolbar = () => {
pathToNode,
false
)
updateAst(modifiedAst, { focusPath: pathToExtrudeArg })
updateAst(modifiedAst, true, { focusPath: pathToExtrudeArg })
}}
className="group"
>
ExtrudeSketch (w/o pipe)
<ActionIcon icon="extrude" className="!p-0.5" size="md" />
Extrude as new
</button>
</>
)}
@ -141,14 +168,29 @@ export const Toolbar = () => {
{guiMode.mode === 'sketch' && (
<button
onClick={() => {
engineCommandManager?.sendSceneCommand({
engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: { type: 'edit_mode_exit' },
})
engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: { type: 'default_camera_disable_sketch_mode' },
})
setGuiMode({ mode: 'default' })
executeAst()
}}
className="group"
>
<ActionIcon
icon="exit"
className="!p-0.5"
bgClassName={sketchButtonClassnames.background}
iconClassName={sketchButtonClassnames.icon}
size="md"
/>
Exit sketch
</button>
)}
@ -167,7 +209,7 @@ export const Toolbar = () => {
<button
key={sketchFnName}
onClick={() => {
engineCommandManager?.sendSceneCommand({
engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
@ -189,12 +231,25 @@ export const Toolbar = () => {
sketchMode: sketchFnName,
waitingFirstClick: true,
isTooltip: true,
pathId: guiMode.pathId,
}),
})
}}
className={
'group ' +
(guiMode.sketchMode === sketchFnName
? '!text-fern-70 !bg-fern-10 !dark:text-fern-20 !border-fern-50'
: '')
}
>
{sketchFnName}
{guiMode.sketchMode === sketchFnName && ''}
<ActionIcon
icon={sketchFnName.includes('line') ? 'line' : 'move'}
className="!p-0.5"
bgClassName={sketchButtonClassnames.background}
iconClassName={sketchButtonClassnames.icon}
size="md"
/>
{sketchFnLabels[sketchFnName]}
</button>
)
})}
@ -225,7 +280,7 @@ export const Toolbar = () => {
<span className={styles.toolbarCap + ' ' + styles.label}>
{guiMode.mode === 'sketch' ? '2D' : '3D'}
</span>
<menu className="flex flex-1 gap-2 py-0.5 overflow-hidden whitespace-nowrap">
<menu className="flex-1 gap-2 py-0.5 overflow-hidden whitespace-nowrap">
<ToolbarButtons />
</menu>
<Popover.Button
@ -266,7 +321,7 @@ export const Toolbar = () => {
</Popover.Button>
</section>
<section>
<ToolbarButtons />
<ToolbarButtons className="flex-wrap" />
</section>
</Popover.Panel>
</Transition>

View File

@ -4,6 +4,7 @@ import {
} from '@fortawesome/free-solid-svg-icons'
import { IconDefinition as BrandIconDefinition } from '@fortawesome/free-brands-svg-icons'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { CustomIcon, CustomIconName } from './CustomIcon'
const iconSizes = {
sm: 12,
@ -13,7 +14,7 @@ const iconSizes = {
}
export interface ActionIconProps extends React.PropsWithChildren {
icon?: SolidIconDefinition | BrandIconDefinition
icon?: SolidIconDefinition | BrandIconDefinition | CustomIconName
className?: string
bgClassName?: string
iconClassName?: string
@ -28,25 +29,39 @@ export const ActionIcon = ({
size = 'md',
children,
}: ActionIconProps) => {
// By default, we reverse the icon color and background color in dark mode
const computedIconClassName =
iconClassName ||
`text-liquid-20 h-auto group-hover:text-liquid-10 hover:text-liquid-10 dark:text-chalkboard-100 dark:group-hover:text-chalkboard-100 dark:hover:text-chalkboard-100 group-disabled:bg-chalkboard-50 dark:group-disabled:bg-chalkboard-60 group-hover:group-disabled:bg-chalkboard-50 dark:group-hover:group-disabled:bg-chalkboard-50`
const computedBgClassName =
bgClassName ||
`bg-chalkboard-100 group-hover:bg-chalkboard-90 hover:bg-chalkboard-90 dark:bg-liquid-20 dark:group-hover:bg-liquid-10 dark:hover:bg-liquid-10 group-disabled:bg-chalkboard-80 dark:group-disabled:bg-chalkboard-80`
return (
<div
className={
`p-${
size === 'xl' ? '2' : '1'
} w-fit inline-grid place-content-center ${className} ` +
(bgClassName ||
'bg-chalkboard-100 group-hover:bg-chalkboard-90 hover:bg-chalkboard-90 dark:bg-liquid-20 dark:group-hover:bg-liquid-10 dark:hover:bg-liquid-10')
computedBgClassName
}
>
{children || (
{children ? (
children
) : typeof icon === 'string' ? (
<CustomIcon
name={icon}
width={iconSizes[size]}
height={iconSizes[size]}
className={computedIconClassName}
/>
) : (
<FontAwesomeIcon
icon={icon}
width={iconSizes[size]}
height={iconSizes[size]}
className={
iconClassName ||
'text-liquid-20 h-auto group-hover:text-liquid-10 hover:text-liquid-10 dark:text-liquid-100 dark:group-hover:text-liquid-100 dark:hover:text-liquid-100'
}
className={computedIconClassName}
/>
)}
</div>

View File

@ -29,7 +29,7 @@ export const AppHeader = ({
return (
<header
className={
(showToolbar ? 'grid ' : 'flex justify-between ') +
(showToolbar ? 'w-full grid ' : 'flex justify-between ') +
styles.header +
' overlaid-panes sticky top-0 z-20 py-1 px-5 bg-chalkboard-10/70 dark:bg-chalkboard-100/50 border-b dark:border-b-2 border-chalkboard-30 dark:border-chalkboard-90 items-center ' +
className
@ -38,7 +38,7 @@ export const AppHeader = ({
<ProjectSidebarMenu renderAsLink={!enableMenu} project={project} />
{/* Toolbar if the context deems it */}
{showToolbar && (
<div className="max-w-4xl">
<div className="max-w-lg md:max-w-xl lg:max-w-2xl xl:max-w-4xl 2xl:max-w-5xl">
<Toolbar />
</div>
)}

View File

@ -0,0 +1,182 @@
import { getNodeFromPath, getNodePathFromSourceRange } from 'lang/queryAst'
import { useEffect, useRef, useState } from 'react'
import { useStore } from 'useStore'
export function AstExplorer() {
const { ast, setHighlightRange, selectionRanges } = useStore((s) => ({
ast: s.ast,
setHighlightRange: s.setHighlightRange,
selectionRanges: s.selectionRanges,
}))
const pathToNode = getNodePathFromSourceRange(
ast,
selectionRanges.codeBasedSelections?.[0]?.range
)
const node = getNodeFromPath(ast, pathToNode).node
const [filterKeys, setFilterKeys] = useState<string[]>(['start', 'end'])
return (
<div className="relative" style={{ width: '300px' }}>
<div className="">
filter out keys:<div className="w-2 inline-block"></div>
{['start', 'end', 'type'].map((key) => {
return (
<label key={key} className="inline-flex items-center">
<input
type="checkbox"
className="form-checkbox"
checked={filterKeys.includes(key)}
onChange={(e) => {
if (filterKeys.includes(key)) {
setFilterKeys(filterKeys.filter((k) => k !== key))
} else {
setFilterKeys([...filterKeys, key])
}
}}
/>
<span className="mr-2">{key}</span>
</label>
)
})}
</div>
<div
className="h-full relative"
onMouseLeave={(e) => {
setHighlightRange([0, 0])
}}
>
<pre className=" text-xs overflow-y-auto" style={{ width: '300px' }}>
<DisplayObj obj={ast} filterKeys={filterKeys} node={node} />
</pre>
</div>
</div>
)
}
function DisplayBody({
body,
filterKeys,
node,
}: {
body: { start: number; end: number; [key: string]: any }[]
filterKeys: string[]
node: any
}) {
return (
<>
{body.map((b, index) => {
return (
<div className="my-2" key={index}>
<DisplayObj obj={b} filterKeys={filterKeys} node={node} />
</div>
)
})}
</>
)
}
function DisplayObj({
obj,
filterKeys,
node,
}: {
obj: { start: number; end: number; [key: string]: any }
filterKeys: string[]
node: any
}) {
const { setHighlightRange, setCursor2 } = useStore((s) => ({
setHighlightRange: s.setHighlightRange,
setCursor2: s.setCursor2,
}))
const ref = useRef<HTMLPreElement>(null)
const [hasCursor, setHasCursor] = useState(false)
const [isCollapsed, setIsCollapsed] = useState(false)
useEffect(() => {
if (
node?.start === obj?.start &&
node?.end === obj?.end &&
node.type === obj?.type
) {
ref?.current?.scrollIntoView?.({ behavior: 'smooth', block: 'center' })
setHasCursor(true)
} else {
setHasCursor(false)
}
}, [node.start, node.end, node.type])
return (
<pre
ref={ref}
className={`ml-2 border-l border-violet-600 pl-1 ${
hasCursor ? 'bg-violet-100/25' : ''
}`}
onMouseEnter={(e) => {
setHighlightRange([obj?.start || 0, obj.end])
e.stopPropagation()
}}
onMouseMove={(e) => {
e.stopPropagation()
setHighlightRange([obj?.start || 0, obj.end])
}}
onClick={(e) => {
setCursor2({ type: 'default', range: [obj?.start || 0, obj.end || 0] })
e.stopPropagation()
}}
>
{isCollapsed ? (
<button
className="m-0 p-0 border-0"
onClick={() => setIsCollapsed(false)}
>
{'>'}type: {obj.type}
</button>
) : (
<span className="flex">
{/* <button className="m-0 p-0 border-0 mb-auto" onClick={() => setIsCollapsed(true)}>{'⬇️'}</button> */}
<ul className="inline-block">
{Object.entries(obj).map(([key, value]) => {
if (filterKeys.includes(key)) {
return null
} else if (Array.isArray(value)) {
return (
<li key={key}>
{`${key}: [`}
<DisplayBody
body={value}
filterKeys={filterKeys}
node={node}
/>
{']'}
</li>
)
} else if (
typeof value === 'object' &&
value !== null &&
value?.end
) {
return (
<li key={key}>
{key}:
<DisplayObj
obj={value}
filterKeys={filterKeys}
node={node}
/>
</li>
)
} else if (
typeof value === 'string' ||
typeof value === 'number'
) {
return (
<li key={key}>
{key}: {value}
</li>
)
}
})}
</ul>
</span>
)}
</pre>
)
}

View File

@ -10,6 +10,7 @@ import {
} from '../lang/modifyAst'
import { findAllPreviousVariables, PrevVariable } from '../lang/queryAst'
import { useStore } from '../useStore'
import { engineCommandManager } from '../lang/std/engineConnection'
export const AvailableVars = ({
onVarClick,
@ -92,14 +93,11 @@ export function useCalc({
newVariableInsertIndex: number
setNewVariableName: (a: string) => void
} {
const { ast, programMemory, selectionRange, engineCommandManager } = useStore(
(s) => ({
ast: s.ast,
programMemory: s.programMemory,
selectionRange: s.selectionRanges.codeBasedSelections[0].range,
engineCommandManager: s.engineCommandManager,
})
)
const { ast, programMemory, selectionRange } = useStore((s) => ({
ast: s.ast,
programMemory: s.programMemory,
selectionRange: s.selectionRanges.codeBasedSelections[0].range,
}))
const inputRef = useRef<HTMLInputElement>(null)
const [availableVarInfo, setAvailableVarInfo] = useState<
ReturnType<typeof findAllPreviousVariables>
@ -140,11 +138,10 @@ export function useCalc({
}, [ast, programMemory, selectionRange])
useEffect(() => {
if (!engineCommandManager) return
try {
const code = `const __result__ = ${value}\nshow(__result__)`
const ast = parser_wasm(code)
const _programMem: any = { root: {} }
const _programMem: any = { root: {}, return: null }
availableVarInfo.variables.forEach(({ key, value }) => {
_programMem.root[key] = { type: 'userVal', value, __meta: [] }
})

View File

@ -1,11 +1,15 @@
import { Menu } from '@headlessui/react'
import { PropsWithChildren } from 'react'
import { faEllipsis } from '@fortawesome/free-solid-svg-icons'
import {
faArrowUpRightFromSquare,
faEllipsis,
} from '@fortawesome/free-solid-svg-icons'
import { ActionIcon } from './ActionIcon'
import { useStore } from 'useStore'
import styles from './CodeMenu.module.css'
import { useConvertToVariable } from 'hooks/useToolbarGuards'
import { editorShortcutMeta } from './TextEditor'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
export const CodeMenu = ({ children }: PropsWithChildren) => {
const { formatCode } = useStore((s) => ({
@ -19,7 +23,8 @@ export const CodeMenu = ({ children }: PropsWithChildren) => {
<div
className="relative"
onClick={(e) => {
if (e.eventPhase === 3) {
const target = e.target as HTMLElement
if (e.eventPhase === 3 && target.closest('a') === null) {
e.stopPropagation()
e.preventDefault()
}
@ -52,6 +57,24 @@ export const CodeMenu = ({ children }: PropsWithChildren) => {
</button>
</Menu.Item>
)}
<Menu.Item>
<a
className={styles.button}
href="https://github.com/KittyCAD/modeling-app/blob/main/docs/kcl/std.md"
target="_blank"
rel="noopener noreferrer"
>
<span>Read the KCL docs</span>
<small>
On GitHub
<FontAwesomeIcon
icon={faArrowUpRightFromSquare}
className="ml-1 align-text-top"
width={12}
/>
</small>
</a>
</Menu.Item>
</Menu.Items>
</div>
</Menu>

View File

@ -62,7 +62,7 @@ export const CommandBarProvider = ({
const CommandBar = () => {
const { commands, commandBarOpen, setCommandBarOpen } = useCommandsContext()
useHotkeys('meta+k', () => {
useHotkeys(['meta+k', 'meta+/'], () => {
if (commands.length === 0) return
setCommandBarOpen(!commandBarOpen)
})
@ -221,10 +221,10 @@ const CommandBar = () => {
<Combobox
value={selectedCommand}
onChange={handleCommandSelection}
className="rounded relative mx-auto p-2 bg-chalkboard-10 dark:bg-chalkboard-100 border dark:border-chalkboard-70 max-w-xl w-full shadow-lg"
className="relative w-full max-w-xl p-2 mx-auto border rounded shadow-lg bg-chalkboard-10 dark:bg-chalkboard-100 dark:border-chalkboard-70"
as="div"
>
<div className="flex gap-2 items-center">
<div className="flex items-center gap-2">
<ActionIcon icon={faSearch} size="xl" className="rounded-sm" />
<div>
{inSubCommand && (
@ -235,7 +235,7 @@ const CommandBar = () => {
)}
<Combobox.Input
onChange={(event) => setQuery(event.target.value)}
className="bg-transparent focus:outline-none w-full"
className="w-full bg-transparent focus:outline-none"
onKeyDown={(event) => {
if (event.metaKey && event.key === 'k')
setCommandBarOpen(false)
@ -264,12 +264,12 @@ const CommandBar = () => {
/>
</div>
</div>
<Combobox.Options static className="max-h-96 overflow-y-auto">
<Combobox.Options static className="overflow-y-auto max-h-96">
{filteredCommands?.map((commandResult) => (
<Combobox.Option
key={commandResult.item.name}
value={commandResult}
className="my-2 first:mt-4 last:mb-4 ui-active:bg-liquid-10 dark:ui-active:bg-liquid-90 py-1 px-2"
className="px-2 py-1 my-2 first:mt-4 last:mb-4 ui-active:bg-liquid-10 dark:ui-active:bg-liquid-90"
>
<p>{commandResult.item.name}</p>
{(commandResult.item as SubCommand).description && (

View File

@ -0,0 +1,161 @@
export type CustomIconName =
| 'equal'
| 'exit'
| 'extrude'
| 'horizontal'
| 'line'
| 'move'
| 'parallel'
| 'sketch'
| 'vertical'
export const CustomIcon = ({
name,
...props
}: {
name: CustomIconName
} & React.SVGProps<SVGSVGElement>) => {
switch (name) {
case 'equal':
return (
<svg
{...props}
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M5 8.78V7H14.52V8.78H5ZM5 13.02V11.24H14.52V13.02H5Z"
fill="currentColor"
/>
</svg>
)
case 'exit':
return (
<svg
{...props}
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M17 10L3 10M3 10L6.5 6.5M3 10L6.5 13.5"
stroke="currentColor"
/>
</svg>
)
case 'extrude':
return (
<svg
{...props}
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M10 3L10.3536 3.35355L12.3536 5.35355L11.6465 6.06066L10.5 4.91421V11.5854C11.0826 11.7913 11.5 12.3469 11.5 13C11.5 13.8284 10.8284 14.5 10 14.5C9.17157 14.5 8.5 13.8284 8.5 13C8.5 12.3469 8.91741 11.7913 9.5 11.5854V4.91421L8.35356 6.06066L7.64645 5.35355L9.64645 3.35355L10 3ZM1.95887 12.3282L8 8.63644V9.80838L2.91773 12.9142L10 17.2423L17.0823 12.9142L12 9.80838V8.63644L18.0411 12.3282L19 12.9142L19 14.9683H18V13.5253L10.5 18.1087V19.9683H9.5V18.1087L2 13.5253V14.9683H1L1 12.9142L1.95887 12.3282Z"
fill="currentColor"
/>
</svg>
)
case 'horizontal':
return (
<svg
{...props}
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M4 9.5H16V11.5H4V9.5Z"
fill="currentColor"
/>
</svg>
)
case 'line':
return (
<svg
{...props}
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M15.5 6C16.3284 6 17 5.32843 17 4.5C17 3.67157 16.3284 3 15.5 3C14.6716 3 14 3.67157 14 4.5C14 4.73107 14.0522 4.94993 14.1456 5.14543L5.14543 14.1456C4.94993 14.0522 4.73107 14 4.5 14C3.67157 14 3 14.6716 3 15.5C3 16.3284 3.67157 17 4.5 17C5.32843 17 6 16.3284 6 15.5C6 15.2679 5.94729 15.0482 5.8532 14.852L14.852 5.8532C15.0482 5.94729 15.2679 6 15.5 6Z"
fill="currentColor"
/>
</svg>
)
case 'move':
return (
<svg
{...props}
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M10 2.29289L10.3536 2.64645L12.3536 4.64645L11.6465 5.35355L10.5 4.20711V8V9.50001H12L15.7929 9.50001L14.6465 8.35356L15.3536 7.64645L17.3536 9.64645L17.7071 10L17.3536 10.3536L15.3536 12.3536L14.6465 11.6465L15.7929 10.5H12H10.5V12V15.7929L11.6465 14.6464L12.3536 15.3536L10.3536 17.3536L10 17.7071L9.64645 17.3536L7.64645 15.3536L8.35356 14.6464L9.50001 15.7929V12V10.5H8.00001H4.20712L5.35357 11.6465L4.64646 12.3536L2.64646 10.3536L2.29291 10L2.64646 9.64645L4.64646 7.64645L5.35357 8.35356L4.20712 9.50001H8.00001H9.50001V8V4.20711L8.35356 5.35355L7.64645 4.64645L9.64645 2.64645L10 2.29289Z"
fill="currentColor"
/>
</svg>
)
case 'parallel':
return (
<svg
{...props}
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M8 16V4H6V16H8ZM14 16V4H12V16H14Z"
fill="currentColor"
/>
</svg>
)
case 'sketch':
return (
<svg
{...props}
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M14.8037 13.4035L15.5509 14.1635L16.3682 16.8386L13.5521 16.1346L12.8186 15.3885L14.8037 13.4035ZM14.1025 12.6903L12.1175 14.6754L3.48609 5.89624C2.94588 5.34678 2.94963 4.46456 3.49448 3.91971C4.04591 3.36828 4.94112 3.37208 5.48786 3.92817L14.1025 12.6903ZM6.20094 3.22709L16.4357 13.6371L17.5003 17.1216L17.8412 18.2376L16.7091 17.9546L13.0364 17.0364L2.77301 6.59732C1.84793 5.6564 1.85434 4.14564 2.78737 3.2126C3.73167 2.2683 5.26468 2.27481 6.20094 3.22709Z"
fill="currentColor"
/>
</svg>
)
case 'vertical':
return (
<svg
{...props}
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M11 4V16H9V4H11Z"
fill="currentColor"
/>
</svg>
)
}
}

View File

@ -1,11 +1,12 @@
import { CollapsiblePanel, CollapsiblePanelProps } from './CollapsiblePanel'
import { useStore } from '../useStore'
import { v4 as uuidv4 } from 'uuid'
import { EngineCommand } from '../lang/std/engineConnection'
import { useState } from 'react'
import { ActionButton } from '../components/ActionButton'
import { faCheck } from '@fortawesome/free-solid-svg-icons'
import { isReducedMotion } from 'lang/util'
import { AstExplorer } from './AstExplorer'
import { engineCommandManager } from '../lang/std/engineConnection'
type SketchModeCmd = Extract<
Extract<EngineCommand, { type: 'modeling_cmd_req' }>['cmd'],
@ -13,9 +14,6 @@ type SketchModeCmd = Extract<
>
export const DebugPanel = ({ className, ...props }: CollapsiblePanelProps) => {
const { engineCommandManager } = useStore((s) => ({
engineCommandManager: s.engineCommandManager,
}))
const [sketchModeCmd, setSketchModeCmd] = useState<SketchModeCmd>({
type: 'default_camera_enable_sketch_mode',
origin: { x: 0, y: 0, z: 0 },
@ -29,7 +27,11 @@ export const DebugPanel = ({ className, ...props }: CollapsiblePanelProps) => {
return (
<CollapsiblePanel
{...props}
className={'!absolute !h-auto bottom-5 right-5 ' + className}
className={
'!absolute overflow-hidden !h-auto bottom-5 right-5 ' + className
}
// header height, top-5, and bottom-5
style={{ maxHeight: 'calc(100% - 3rem - 1.25rem - 1.25rem)' }}
>
<section className="p-4 flex flex-col gap-4">
<Xyz
@ -65,19 +67,18 @@ export const DebugPanel = ({ className, ...props }: CollapsiblePanelProps) => {
className="w-16"
type="checkbox"
checked={sketchModeCmd.ortho}
onChange={(a) => {
console.log(a, (a as any).checked)
onChange={(a) =>
setSketchModeCmd({
...sketchModeCmd,
ortho: a.target.checked,
})
}}
}
/>
</div>
<ActionButton
Element="button"
onClick={() => {
engineCommandManager?.sendSceneCommand({
engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd: sketchModeCmd,
cmd_id: uuidv4(),
@ -94,6 +95,9 @@ export const DebugPanel = ({ className, ...props }: CollapsiblePanelProps) => {
>
Send sketch mode command
</ActionButton>
<div style={{ height: '400px' }} className="overflow-y-auto">
<AstExplorer />
</div>
</section>
</CollapsiblePanel>
)

View File

@ -40,12 +40,12 @@ const DownloadAppBanner = () => {
</code>
, and isn't backed up anywhere! Visit{' '}
<a
href="https://github.com/KittyCAD/modeling-app/releases"
href="https://kittycad.io/modeling-app/download"
rel="noopener noreferrer"
target="_blank"
className="text-warn-80 dark:text-warn-80 dark:hover:text-warn-70 underline"
>
our GitHub repository
our website
</a>{' '}
to download the app for the best experience.
</p>

View File

@ -1,8 +1,62 @@
import { isTauri } from 'lib/isTauri'
import { useRouteError, isRouteErrorResponse } from 'react-router-dom'
import { ActionButton } from './ActionButton'
import {
faBug,
faHome,
faRefresh,
faTrash,
} from '@fortawesome/free-solid-svg-icons'
export const ErrorPage = () => {
let error = useRouteError()
console.error('error', error)
return (
<div className="flex flex-col items-center justify-center h-screen">
<h1 className="text-4xl font-bold">404</h1>
<p className="text-2xl font-bold">Page not found</p>
<section className="max-w-full xl:max-w-4xl mx-auto">
<h1 className="text-4xl mb-8 font-bold">
An unexpected error occurred
</h1>
{isRouteErrorResponse(error) && (
<p className="mb-8">
{error.status}: {error.data}
</p>
)}
<div className="flex justify-between gap-2 mt-6">
{isTauri() && (
<ActionButton Element="link" to={'/'} icon={{ icon: faHome }}>
Go Home
</ActionButton>
)}
<ActionButton
Element="button"
icon={{ icon: faRefresh }}
onClick={() => window.location.reload()}
>
Reload
</ActionButton>
<ActionButton
Element="button"
icon={{ icon: faTrash }}
onClick={() => {
window.localStorage.clear()
}}
>
Clear storage
</ActionButton>
<ActionButton
Element="link"
icon={{ icon: faBug }}
target="_blank"
rel="noopener noreferrer"
to="https://discord.com/channels/915388055236509727/1138967922614743060"
>
Report Bug
</ActionButton>
</div>
</section>
</div>
)
}

View File

@ -1,11 +1,11 @@
import { v4 as uuidv4 } from 'uuid'
import { useStore } from '../useStore'
import { faFileExport, faXmark } from '@fortawesome/free-solid-svg-icons'
import { ActionButton } from './ActionButton'
import Modal from 'react-modal'
import React from 'react'
import { useFormik } from 'formik'
import { Models } from '@kittycad/lib'
import { engineCommandManager } from '../lang/std/engineConnection'
type OutputFormat = Models['OutputFormat_type']
@ -18,10 +18,6 @@ interface ExportButtonProps extends React.PropsWithChildren {
}
export const ExportButton = ({ children, className }: ExportButtonProps) => {
const { engineCommandManager } = useStore((s) => ({
engineCommandManager: s.engineCommandManager,
}))
const [modalIsOpen, setIsOpen] = React.useState(false)
const defaultType = 'gltf'
@ -66,7 +62,7 @@ export const ExportButton = ({ children, className }: ExportButtonProps) => {
},
}
}
engineCommandManager?.sendSceneCommand({
engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd: {
type: 'export',

View File

@ -24,6 +24,9 @@ import {
StateFrom,
} from 'xstate'
import { useCommandsContext } from 'hooks/useCommandsContext'
import { invoke } from '@tauri-apps/api'
import { isTauri } from 'lib/isTauri'
import { VITE_KC_API_BASE_URL } from 'env'
type MachineContext<T extends AnyStateMachine> = {
state: StateFrom<T>
@ -108,6 +111,7 @@ export const GlobalStateProvider = ({
actions: {
goToSignInPage: () => {
navigate(paths.SIGN_IN)
logout()
},
goToIndexPage: () => {
@ -149,10 +153,12 @@ export const GlobalStateProvider = ({
export default GlobalStateProvider
export function logout() {
const url = withBaseUrl('/logout')
localStorage.removeItem(TOKEN_PERSIST_KEY)
return fetch(url, {
method: 'POST',
credentials: 'include',
})
return (
!isTauri() &&
fetch(withBaseUrl('/logout'), {
method: 'POST',
credentials: 'include',
})
)
}

View File

@ -10,7 +10,7 @@ describe('processMemory', () => {
// Enable rotations #152
const code = `
const myVar = 5
const myFn = (a) => {
fn myFn = (a) => {
return a - 2
}
const otherVar = myFn(5)
@ -29,6 +29,7 @@ describe('processMemory', () => {
const ast = parser_wasm(code)
const programMemory = await enginelessExecutor(ast, {
root: {},
return: null,
})
const output = processMemory(programMemory)
expect(output.myVar).toEqual(5)

View File

@ -2,7 +2,7 @@ import ReactJson from 'react-json-view'
import { CollapsiblePanel, CollapsiblePanelProps } from './CollapsiblePanel'
import { useStore } from '../useStore'
import { useMemo } from 'react'
import { ProgramMemory } from '../lang/executor'
import { ProgramMemory, Path, ExtrudeSurface } from '../lang/executor'
import { Themes } from '../lib/theme'
interface MemoryPanelProps extends CollapsiblePanelProps {
@ -24,7 +24,11 @@ export const MemoryPanel = ({
<CollapsiblePanel {...props}>
<div className="h-full relative">
<div className="absolute inset-0 flex flex-col items-start">
<div className=" h-full console-tile w-full">
<div
className="overflow-y-auto h-full console-tile w-full"
style={{ marginBottom: 36 }}
>
{/* 36px is the height of PanelHeader */}
<ReactJson
src={ProcessedMemory}
collapsed={1}
@ -46,11 +50,15 @@ export const MemoryPanel = ({
export const processMemory = (programMemory: ProgramMemory) => {
const processedMemory: any = {}
Object.keys(programMemory.root).forEach((key) => {
Object.keys(programMemory?.root || {}).forEach((key) => {
const val = programMemory.root[key]
if (typeof val.value !== 'function') {
if (val.type === 'sketchGroup' || val.type === 'extrudeGroup') {
processedMemory[key] = val.value.map(({ __geoMeta, ...rest }) => {
if (val.type === 'SketchGroup') {
processedMemory[key] = val.value.map(({ __geoMeta, ...rest }: Path) => {
return rest
})
} else if (val.type === 'ExtrudeGroup') {
processedMemory[key] = val.value.map(({ ...rest }: ExtrudeSurface) => {
return rest
})
} else {

View File

@ -1,42 +0,0 @@
import { invoke } from '@tauri-apps/api/tauri'
import { open } from '@tauri-apps/api/dialog'
import { useStore } from '../useStore'
export const OpenFileButton = () => {
const { setCode } = useStore((s) => ({
setCode: s.setCode,
}))
const handleClick = async () => {
const selected = await open({
multiple: false,
directory: false,
filters: [
{
name: 'CAD',
extensions: ['toml'],
},
],
})
if (Array.isArray(selected)) {
// User selected multiple files
// We should not get here, since multiple is false.
} else if (selected === null) {
// User cancelled the selection
// Do nothing.
} else {
// User selected a single file
// We want to invoke our command to read the file.
const json: string = await invoke('read_toml', { path: selected })
const packageDetails = JSON.parse(json).package
if (packageDetails.main) {
const absPath = [
...selected.split('/').slice(0, -1),
packageDetails.main,
].join('/')
const file: string = await invoke('read_txt_file', { path: absPath })
setCode(file)
}
}
}
return <button onClick={() => handleClick()}>Open File</button>
}

View File

@ -16,8 +16,8 @@ const ProjectSidebarMenu = ({
}) => {
return renderAsLink ? (
<Link
to={'../'}
className="flex items-center gap-4 my-2"
to={paths.HOME}
className="h-9 max-h-min min-w-max border-0 p-0.5 pr-2 flex items-center gap-4 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-energy-50"
data-testid="project-sidebar-link"
>
<img
@ -26,7 +26,7 @@ const ProjectSidebarMenu = ({
className="h-9 w-auto"
/>
<span
className="text-sm text-chalkboard-110 dark:text-chalkboard-20 min-w-max"
className="text-sm text-chalkboard-110 dark:text-chalkboard-20 whitespace-nowrap hidden lg:block"
data-testid="project-sidebar-link-name"
>
{project?.name ? project.name : 'KittyCAD Modeling App'}
@ -35,15 +35,15 @@ const ProjectSidebarMenu = ({
) : (
<Popover className="relative">
<Popover.Button
className="border-0 p-0.5 pr-2 flex items-center gap-4 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-energy-50"
className="h-9 max-h-min min-w-max border-0 p-0.5 pr-2 flex items-center gap-4 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-energy-50"
data-testid="project-sidebar-toggle"
>
<img
src="/kitt-8bit-winking.svg"
alt="KittyCAD App"
className="h-9 w-auto"
className="h-full w-auto"
/>
<span className="text-sm text-chalkboard-110 dark:text-chalkboard-20 min-w-max">
<span className="text-sm text-chalkboard-110 dark:text-chalkboard-20 whitespace-nowrap hidden lg:block">
{isTauri() && project?.name ? project.name : 'KittyCAD Modeling App'}
</span>
</Popover.Button>

View File

@ -14,7 +14,18 @@ import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
import { CameraDragInteractionType_type } from '@kittycad/lib/dist/types/src/models'
import { Models } from '@kittycad/lib'
import { addStartSketch } from 'lang/modifyAst'
import { addNewSketchLn } from 'lang/std/sketch'
import {
addCloseToPipe,
addNewSketchLn,
compareVec2Epsilon,
} from 'lang/std/sketch'
import { getNodeFromPath } from 'lang/queryAst'
import { Program, VariableDeclarator } from 'lang/abstractSyntaxTreeTypes'
import { modify_ast_for_sketch } from '../wasm-lib/pkg/wasm_lib'
import { KCLError } from 'lang/errors'
import { KclError as RustKclError } from '../wasm-lib/kcl/bindings/KclError'
import { rangeTypeFix } from 'lang/abstractSyntaxTree'
import { engineCommandManager } from '../lang/std/engineConnection'
export const Stream = ({ className = '' }) => {
const [isLoading, setIsLoading] = useState(true)
@ -22,7 +33,6 @@ export const Stream = ({ className = '' }) => {
const videoRef = useRef<HTMLVideoElement>(null)
const {
mediaStream,
engineCommandManager,
setButtonDownInStream,
didDragInStream,
setDidDragInStream,
@ -35,7 +45,6 @@ export const Stream = ({ className = '' }) => {
programMemory,
} = useStore((s) => ({
mediaStream: s.mediaStream,
engineCommandManager: s.engineCommandManager,
setButtonDownInStream: s.setButtonDownInStream,
fileId: s.fileId,
didDragInStream: s.didDragInStream,
@ -63,7 +72,7 @@ export const Stream = ({ className = '' }) => {
if (!videoRef.current) return
if (!mediaStream) return
videoRef.current.srcObject = mediaStream
}, [mediaStream, engineCommandManager])
}, [mediaStream])
const handleMouseDown: MouseEventHandler<HTMLVideoElement> = (e) => {
if (!videoRef.current) return
@ -97,7 +106,7 @@ export const Stream = ({ className = '' }) => {
}
if (guiMode.mode === 'sketch' && guiMode.sketchMode === ('move' as any)) {
engineCommandManager?.sendSceneCommand({
engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd: {
type: 'handle_mouse_drag_start',
@ -111,7 +120,7 @@ export const Stream = ({ className = '' }) => {
guiMode.sketchMode === ('sketch_line' as any)
)
) {
engineCommandManager?.sendSceneCommand({
engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd: {
type: 'camera_drag_start',
@ -129,7 +138,7 @@ export const Stream = ({ className = '' }) => {
const handleScroll: WheelEventHandler<HTMLVideoElement> = (e) => {
if (!cameraMouseDragGuards[cameraControls].zoom.scrollCallback(e)) return
engineCommandManager?.sendSceneCommand({
engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd: {
type: 'default_camera_zoom',
@ -167,7 +176,7 @@ export const Stream = ({ className = '' }) => {
}
if (!didDragInStream) {
engineCommandManager?.sendSceneCommand({
engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd: {
type: 'select_with_point',
@ -204,23 +213,80 @@ export const Stream = ({ className = '' }) => {
window: { x, y },
}
}
engineCommandManager?.sendSceneCommand(command).then(async ({ data }) => {
if (command.cmd.type !== 'mouse_click' || !ast) return
if (
!(
guiMode.mode === 'sketch' &&
guiMode.sketchMode === ('sketch_line' as any as 'line')
)
)
return
engineCommandManager.sendSceneCommand(command).then(async (resp) => {
if (!(guiMode.mode === 'sketch')) return
if (data?.data?.entities_modified?.length && guiMode.waitingFirstClick) {
const curve = await engineCommandManager?.sendSceneCommand({
if (guiMode.sketchMode === 'selectFace') return
// Check if the sketch group already exists.
const varDec = getNodeFromPath<VariableDeclarator>(
ast,
guiMode.pathToNode,
'VariableDeclarator'
).node
const variableName = varDec?.id?.name
const sketchGroup = programMemory.root[variableName]
const isEditingExistingSketch =
sketchGroup?.type === 'SketchGroup' && sketchGroup.value.length
let sketchGroupId = ''
if (sketchGroup && sketchGroup.type === 'SketchGroup') {
sketchGroupId = sketchGroup.id
}
if (
guiMode.sketchMode === ('move' as any as 'line') &&
command.cmd.type === 'handle_mouse_drag_end'
) {
// Let's get the updated ast.
if (sketchGroupId === '') return
// We have a problem if we do not have an id for the sketch group.
if (
guiMode.pathId === undefined ||
guiMode.pathId === null ||
guiMode.pathId === ''
)
return
let engineId = guiMode.pathId
try {
const updatedAst: Program = await modify_ast_for_sketch(
engineCommandManager,
JSON.stringify(ast),
variableName,
engineId
)
updateAst(updatedAst, false)
} catch (e: any) {
const parsed: RustKclError = JSON.parse(e.toString())
const kclError = new KCLError(
parsed.kind,
parsed.msg,
rangeTypeFix(parsed.sourceRanges)
)
console.log(kclError)
throw kclError
}
return
}
if (command?.cmd?.type !== 'mouse_click' || !ast) return
if (!(guiMode.sketchMode === ('sketch_line' as any as 'line'))) return
if (
resp?.data?.data?.entities_modified?.length &&
guiMode.waitingFirstClick &&
!isEditingExistingSketch
) {
const curve = await engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'curve_get_control_points',
curve_id: data?.data?.entities_modified[0],
curve_id: resp?.data?.data?.entities_modified[0],
},
})
const coords: { x: number; y: number }[] =
@ -236,34 +302,83 @@ export const Stream = ({ className = '' }) => {
const _modifiedAst = _addStartSketch.modifiedAst
const _pathToNode = _addStartSketch.pathToNode
// We need to update the guiMode with the right pathId so that we can
// move lines later and send the right sketch id to the engine.
for (const [id, artifact] of Object.entries(
engineCommandManager.artifactMap
)) {
if (artifact.commandType === 'start_path') {
guiMode.pathId = id
}
}
setGuiMode({
...guiMode,
pathToNode: _pathToNode,
waitingFirstClick: false,
})
updateAst(_modifiedAst)
updateAst(_modifiedAst, false)
} else if (
data?.data?.entities_modified?.length &&
!guiMode.waitingFirstClick
resp?.data?.data?.entities_modified?.length &&
(!guiMode.waitingFirstClick || isEditingExistingSketch)
) {
const curve = await engineCommandManager?.sendSceneCommand({
const curve = await engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'curve_get_control_points',
curve_id: data?.data?.entities_modified[0],
curve_id: resp?.data?.data?.entities_modified[0],
},
})
const coords: { x: number; y: number }[] =
curve.data.data.control_points
const _modifiedAst = addNewSketchLn({
node: ast,
programMemory,
to: [coords[1].x, coords[1].y],
fnName: 'line',
pathToNode: guiMode.pathToNode,
}).modifiedAst
updateAst(_modifiedAst)
const { node: varDec } = getNodeFromPath<VariableDeclarator>(
ast,
guiMode.pathToNode,
'VariableDeclarator'
)
const variableName = varDec.id.name
const sketchGroup = programMemory.root[variableName]
if (!sketchGroup || sketchGroup.type !== 'SketchGroup') return
const initialCoords = sketchGroup.value[0].from
const isClose = compareVec2Epsilon(initialCoords, [
coords[1].x,
coords[1].y,
])
let _modifiedAst: Program
if (!isClose) {
_modifiedAst = addNewSketchLn({
node: ast,
programMemory,
to: [coords[1].x, coords[1].y],
fnName: 'line',
pathToNode: guiMode.pathToNode,
}).modifiedAst
updateAst(_modifiedAst, false)
} else {
_modifiedAst = addCloseToPipe({
node: ast,
programMemory,
pathToNode: guiMode.pathToNode,
})
setGuiMode({
mode: 'default',
})
engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: { type: 'edit_mode_exit' },
})
engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: { type: 'default_camera_disable_sketch_mode' },
})
updateAst(_modifiedAst, true)
}
}
})
setDidDragInStream(false)

View File

@ -26,9 +26,11 @@ import {
addLineHighlight,
lineHighlightField,
} from 'editor/highlightextension'
import { isOverlap } from 'lib/utils'
import { isOverlap, roundOff } from 'lib/utils'
import { kclErrToDiagnostic } from 'lang/errors'
import { CSSRuleObject } from 'tailwindcss/types/config'
import interact from '@replit/codemirror-interact'
import { engineCommandManager } from '../lang/std/engineConnection'
export const editorShortcutMeta = {
formatCode: {
@ -49,9 +51,8 @@ export const TextEditor = ({
const pathParams = useParams()
const {
code,
defferedSetCode,
deferredSetCode,
editorView,
engineCommandManager,
formatCode,
isLSPServerReady,
selectionRanges,
@ -59,12 +60,10 @@ export const TextEditor = ({
setEditorView,
setIsLSPServerReady,
setSelectionRanges,
sourceRangeMap,
} = useStore((s) => ({
code: s.code,
defferedSetCode: s.defferedSetCode,
deferredSetCode: s.deferredSetCode,
editorView: s.editorView,
engineCommandManager: s.engineCommandManager,
formatCode: s.formatCode,
isLSPServerReady: s.isLSPServerReady,
selectionRanges: s.selectionRanges,
@ -72,7 +71,6 @@ export const TextEditor = ({
setEditorView: s.setEditorView,
setIsLSPServerReady: s.setIsLSPServerReady,
setSelectionRanges: s.setSelectionRanges,
sourceRangeMap: s.sourceRangeMap,
}))
const {
@ -125,7 +123,7 @@ export const TextEditor = ({
// const onChange = React.useCallback((value: string, viewUpdate: ViewUpdate) => {
const onChange = (value: string, viewUpdate: ViewUpdate) => {
defferedSetCode(value)
deferredSetCode(value)
if (isTauri() && pathParams.id) {
// Save the file to disk
// Note that PROJECT_ENTRYPOINT is hardcoded until we support multiple files
@ -173,11 +171,11 @@ export const TextEditor = ({
)
const idBasedSelections = codeBasedSelections
.map(({ type, range }) => {
const hasOverlap = Object.entries(sourceRangeMap).filter(
([_, sourceRange]) => {
return isOverlap(sourceRange, range)
}
)
const hasOverlap = Object.entries(
engineCommandManager.sourceRangeMap || {}
).filter(([_, sourceRange]) => {
return isOverlap(sourceRange, range)
})
if (hasOverlap.length) {
return {
type,
@ -187,7 +185,7 @@ export const TextEditor = ({
})
.filter(Boolean) as any
engineCommandManager?.cusorsSelected({
engineCommandManager.cusorsSelected({
otherSelections: [],
idBasedSelections,
})
@ -237,6 +235,38 @@ export const TextEditor = ({
lintGutter(),
linter((_view) => {
return kclErrToDiagnostic(useStore.getState().kclErrors)
}),
interact({
rules: [
// a rule for a number dragger
{
// the regexp matching the value
regexp: /-?\b\d+\.?\d*\b/g,
// set cursor to "ew-resize" on hover
cursor: 'ew-resize',
// change number value based on mouse X movement on drag
onDrag: (text, setText, e) => {
const multiplier =
e.shiftKey && e.metaKey
? 0.01
: e.metaKey
? 0.1
: e.shiftKey
? 10
: 1
const delta = e.movementX * multiplier
const newVal = roundOff(
Number(text) + delta,
multiplier === 0.01 ? 2 : multiplier === 0.1 ? 1 : 0
)
if (isNaN(newVal)) return
setText(newVal.toString())
},
},
],
})
)
if (textWrapping === 'On') extensions.push(EditorView.lineWrapping)

View File

@ -12,6 +12,8 @@ import {
getTransformInfos,
} from '../../lang/std/sketchcombos'
import { updateCursors } from '../../lang/util'
import { ActionIcon } from 'components/ActionIcon'
import { sketchButtonClassnames } from 'Toolbar'
export const EqualAngle = () => {
const { guiMode, selectionRanges, ast, programMemory, updateAst, setCursor } =
@ -82,14 +84,22 @@ export const EqualAngle = () => {
transformInfos,
programMemory,
})
updateAst(modifiedAst, {
updateAst(modifiedAst, true, {
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
})
}}
disabled={!enableEqual}
title="yo dawg"
title="Parallel (or equal angle)"
className="group"
>
parallel
<ActionIcon
icon="parallel"
className="!p-0.5"
bgClassName={sketchButtonClassnames.background}
iconClassName={sketchButtonClassnames.icon}
size="md"
/>
Parallel
</button>
)
}

View File

@ -12,6 +12,8 @@ import {
getTransformInfos,
} from '../../lang/std/sketchcombos'
import { updateCursors } from '../../lang/util'
import { ActionIcon } from 'components/ActionIcon'
import { sketchButtonClassnames } from 'Toolbar'
export const EqualLength = () => {
const { guiMode, selectionRanges, ast, programMemory, updateAst, setCursor } =
@ -82,14 +84,22 @@ export const EqualLength = () => {
transformInfos,
programMemory,
})
updateAst(modifiedAst, {
updateAst(modifiedAst, true, {
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
})
}}
disabled={!enableEqual}
title="yo dawg"
className="group"
title="Equal Length"
>
EqualLength
<ActionIcon
icon="equal"
className="!p-0.5"
bgClassName={sketchButtonClassnames.background}
iconClassName={sketchButtonClassnames.icon}
size="md"
/>
Equal Length
</button>
)
}

View File

@ -11,6 +11,8 @@ import {
transformAstSketchLines,
} from '../../lang/std/sketchcombos'
import { updateCursors } from '../../lang/util'
import { ActionIcon } from 'components/ActionIcon'
import { sketchButtonClassnames } from 'Toolbar'
export const HorzVert = ({
horOrVert,
@ -61,14 +63,22 @@ export const HorzVert = ({
programMemory,
referenceSegName: '',
})
updateAst(modifiedAst, {
updateAst(modifiedAst, true, {
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
})
}}
disabled={!enableHorz}
title="yo dawg"
className="group"
title={horOrVert === 'horizontal' ? 'Horizontal' : 'Vertical'}
>
{horOrVert === 'horizontal' ? 'Horz' : 'Vert'}
<ActionIcon
icon={horOrVert === 'horizontal' ? 'horizontal' : 'vertical'}
className="!p-0.5"
bgClassName={sketchButtonClassnames.background}
iconClassName={sketchButtonClassnames.icon}
size="md"
/>
{horOrVert === 'horizontal' ? 'Horizontal' : 'Vertical'}
</button>
)
}

View File

@ -154,7 +154,7 @@ export const Intersect = () => {
initialVariableName: 'offset',
} as any)
if (segName === tagInfo?.tag && value === valueUsedInTransform) {
updateAst(modifiedAst, {
updateAst(modifiedAst, true, {
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
})
} else {
@ -182,14 +182,15 @@ export const Intersect = () => {
)
_modifiedAst.body = newBody
}
updateAst(_modifiedAst, {
updateAst(_modifiedAst, true, {
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
})
}
}}
disabled={!enable}
title="Set Perpendicular Distance"
>
perpendicularDistance
Set Perpendicular Distance
</button>
)
}

View File

@ -65,14 +65,14 @@ export const RemoveConstrainingValues = () => {
programMemory,
referenceSegName: '',
})
updateAst(modifiedAst, {
updateAst(modifiedAst, true, {
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
})
}}
disabled={!enableHorz}
title="yo dawg"
title="Remove Constraining Values"
>
RemoveConstrainingValues
Remove Constraining Values
</button>
)
}

View File

@ -22,11 +22,16 @@ import { updateCursors } from '../../lang/util'
const getModalInfo = create(SetAngleLengthModal as any)
export const SetAbsDistance = ({
buttonType,
}: {
buttonType: 'xAbs' | 'yAbs' | 'snapToYAxis' | 'snapToXAxis'
}) => {
type ButtonType = 'xAbs' | 'yAbs' | 'snapToYAxis' | 'snapToXAxis'
const buttonLabels: Record<ButtonType, string> = {
xAbs: 'Set distance from X Axis',
yAbs: 'Set distance from Y Axis',
snapToYAxis: 'Snap To Y Axis',
snapToXAxis: 'Snap To X Axis',
}
export const SetAbsDistance = ({ buttonType }: { buttonType: ButtonType }) => {
const { guiMode, selectionRanges, ast, programMemory, updateAst, setCursor } =
useStore((s) => ({
guiMode: s.guiMode,
@ -124,16 +129,17 @@ export const SetAbsDistance = ({
_modifiedAst.body = newBody
}
updateAst(_modifiedAst, {
updateAst(_modifiedAst, true, {
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
})
} catch (e) {
console.log('e', e)
console.log('error', e)
}
}}
disabled={!enableAngLen}
title={buttonLabels[buttonType]}
>
{buttonType}
{buttonLabels[buttonType]}
</button>
)
}

View File

@ -113,7 +113,7 @@ export const SetAngleBetween = () => {
initialVariableName: 'angle',
} as any)
if (segName === tagInfo?.tag && value === valueUsedInTransform) {
updateAst(modifiedAst, {
updateAst(modifiedAst, true, {
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
})
} else {
@ -141,14 +141,15 @@ export const SetAngleBetween = () => {
)
_modifiedAst.body = newBody
}
updateAst(_modifiedAst, {
updateAst(_modifiedAst, true, {
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
})
}
}}
disabled={!enable}
title="Set Angle Between"
>
angleBetween
Set Angle Between
</button>
)
}

View File

@ -21,17 +21,28 @@ import { GetInfoModal } from '../SetHorVertDistanceModal'
import { createLiteral, createVariableDeclaration } from '../../lang/modifyAst'
import { removeDoubleNegatives } from '../AvailableVarsHelpers'
import { updateCursors } from '../../lang/util'
import { ActionIcon } from 'components/ActionIcon'
import { sketchButtonClassnames } from 'Toolbar'
const getModalInfo = create(GetInfoModal as any)
type ButtonType =
| 'setHorzDistance'
| 'setVertDistance'
| 'alignEndsHorizontally'
| 'alignEndsVertically'
const buttonLabels: Record<ButtonType, string> = {
setHorzDistance: 'Set Horizontal Distance',
setVertDistance: 'Set Vertical Distance',
alignEndsHorizontally: 'Align Ends Horizontally',
alignEndsVertically: 'Align Ends Vertically',
}
export const SetHorzVertDistance = ({
buttonType,
}: {
buttonType:
| 'setHorzDistance'
| 'setVertDistance'
| 'alignEndsHorizontally'
| 'alignEndsVertically'
buttonType: ButtonType
}) => {
const { guiMode, selectionRanges, ast, programMemory, updateAst, setCursor } =
useStore((s) => ({
@ -137,7 +148,7 @@ export const SetHorzVertDistance = ({
constraint === 'setHorzDistance' ? 'xDis' : 'yDis',
} as any))
if (segName === tagInfo?.tag && value === valueUsedInTransform) {
updateAst(modifiedAst, {
updateAst(modifiedAst, true, {
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
})
} else {
@ -163,14 +174,15 @@ export const SetHorzVertDistance = ({
)
_modifiedAst.body = newBody
}
updateAst(_modifiedAst, {
updateAst(_modifiedAst, true, {
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
})
}
}}
disabled={!enable}
title={buttonLabels[buttonType]}
>
{buttonType}
{buttonLabels[buttonType]}
</button>
)
}

View File

@ -23,10 +23,17 @@ import { updateCursors } from '../../lang/util'
const getModalInfo = create(SetAngleLengthModal as any)
type ButtonType = 'setAngle' | 'setLength'
const buttonLabels: Record<ButtonType, string> = {
setAngle: 'Set Angle',
setLength: 'Set Length',
}
export const SetAngleLength = ({
angleOrLength,
}: {
angleOrLength: 'setAngle' | 'setLength'
angleOrLength: ButtonType
}) => {
const { guiMode, selectionRanges, ast, programMemory, updateAst, setCursor } =
useStore((s) => ({
@ -136,16 +143,17 @@ export const SetAngleLength = ({
_modifiedAst.body = newBody
}
updateAst(_modifiedAst, {
updateAst(_modifiedAst, true, {
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
})
} catch (e) {
console.log('e', e)
console.log('erorr', e)
}
}}
disabled={!enableAngLen}
title={buttonLabels[angleOrLength]}
>
{angleOrLength}
{buttonLabels[angleOrLength]}
</button>
)
}

View File

@ -2,7 +2,7 @@ import { Popover, Transition } from '@headlessui/react'
import { ActionButton } from './ActionButton'
import { faBars, faGear, faSignOutAlt } from '@fortawesome/free-solid-svg-icons'
import { faGithub } from '@fortawesome/free-brands-svg-icons'
import { useNavigate } from 'react-router-dom'
import { useLocation, useNavigate } from 'react-router-dom'
import { Fragment, useState } from 'react'
import { paths } from '../Router'
import makeUrlPathRelative from '../lib/makeUrlPathRelative'
@ -12,6 +12,7 @@ import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
type User = Models['User_type']
const UserSidebarMenu = ({ user }: { user?: User }) => {
const location = useLocation()
const displayedName = getDisplayName(user)
const [imageLoadFailed, setImageLoadFailed] = useState(false)
const navigate = useNavigate()
@ -38,7 +39,7 @@ const UserSidebarMenu = ({ user }: { user?: User }) => {
<Popover className="relative">
{user?.image && !imageLoadFailed ? (
<Popover.Button
className="border-0 rounded-full w-fit p-0 focus:outline-none group"
className="border-0 rounded-full w-fit min-w-max p-0 focus:outline-none group"
data-testid="user-sidebar-toggle"
>
<div className="rounded-full border border-chalkboard-70/50 hover:border-liquid-50 group-focus:border-liquid-50 overflow-hidden">
@ -126,7 +127,11 @@ const UserSidebarMenu = ({ user }: { user?: User }) => {
// since /settings is a nested route the sidebar doesn't close
// automatically when navigating to it
close()
navigate(makeUrlPathRelative(paths.SETTINGS))
navigate(
(location.pathname.endsWith('/')
? location.pathname.slice(0, -1)
: location.pathname) + paths.SETTINGS
)
}}
>
Settings

View File

@ -109,7 +109,6 @@ export default class Client extends jsrpc.JSONRPCServerAndClient {
}
}
messageString += message
// console.log(messageString)
return
})

View File

@ -13,6 +13,7 @@ import {
CompletionItemKind,
CompletionTriggerKind,
} from 'vscode-languageserver-protocol'
import debounce from 'debounce-promise'
import type {
Completion,
@ -53,14 +54,11 @@ export class LanguageServerPlugin implements PluginValue {
private languageId: string
private documentVersion: number
private changesTimeout: number
constructor(private view: EditorView, private allowHTMLContent: boolean) {
this.client = this.view.state.facet(client)
this.documentUri = this.view.state.facet(documentUri)
this.languageId = this.view.state.facet(languageId)
this.documentVersion = 0
this.changesTimeout = 0
this.client.attachPlugin(this)
@ -71,12 +69,10 @@ export class LanguageServerPlugin implements PluginValue {
update({ docChanged }: ViewUpdate) {
if (!docChanged) return
if (this.changesTimeout) clearTimeout(this.changesTimeout)
this.changesTimeout = window.setTimeout(() => {
this.sendChange({
documentText: this.view.state.doc.toString(),
})
}, changesDelay)
this.sendChange({
documentText: this.view.state.doc.toString(),
})
}
destroy() {
@ -99,14 +95,32 @@ export class LanguageServerPlugin implements PluginValue {
async sendChange({ documentText }: { documentText: string }) {
if (!this.client.ready) return
if (documentText.length > 5000) {
// Clear out the text it thinks we have, large documents will throw a stack error.
// This is obviously not a good fix but it works for now til we figure
// out the stack limits in wasm and also rewrite the parser.
// Since this is only for hover and completions it will be fine,
// completions will still work for stdlib but hover will not.
// That seems like a fine trade-off for a working editor for the time
// being.
documentText = ''
}
try {
await this.client.textDocumentDidChange({
textDocument: {
uri: this.documentUri,
version: this.documentVersion++,
debounce(
() => {
return this.client.textDocumentDidChange({
textDocument: {
uri: this.documentUri,
version: this.documentVersion++,
},
contentChanges: [{ text: documentText }],
})
},
contentChanges: [{ text: documentText }],
})
changesDelay,
{ leading: true }
)
} catch (e) {
console.error(e)
}

View File

@ -5,8 +5,6 @@ import init, {
} from '../../wasm-lib/pkg/wasm_lib'
import { FromServer, IntoServer } from './codec'
let server: null | Server
export default class Server {
readonly initOutput: InitOutput
readonly #intoServer: IntoServer
@ -26,12 +24,8 @@ export default class Server {
intoServer: IntoServer,
fromServer: FromServer
): Promise<Server> {
if (null == server) {
const initOutput = await init()
server = new Server(initOutput, intoServer, fromServer)
} else {
console.warn('Server already initialized; ignoring')
}
const initOutput = await init()
const server = new Server(initOutput, intoServer, fromServer)
return server
}

View File

@ -8,27 +8,23 @@ import { ArtifactMap, EngineCommandManager } from 'lang/std/engineConnection'
import { Models } from '@kittycad/lib/dist/types/src'
import { isReducedMotion } from 'lang/util'
import { isOverlap } from 'lib/utils'
import { engineCommandManager } from '../lang/std/engineConnection'
interface DefaultPlanes {
xy: string
yz: string
xz: string
// TODO re-enable
// yz: string
// xz: string
}
export function useAppMode() {
const {
guiMode,
setGuiMode,
selectionRanges,
engineCommandManager,
selectionRangeTypeMap,
} = useStore((s) => ({
guiMode: s.guiMode,
setGuiMode: s.setGuiMode,
selectionRanges: s.selectionRanges,
engineCommandManager: s.engineCommandManager,
selectionRangeTypeMap: s.selectionRangeTypeMap,
}))
const { guiMode, setGuiMode, selectionRanges, selectionRangeTypeMap } =
useStore((s) => ({
guiMode: s.guiMode,
setGuiMode: s.setGuiMode,
selectionRanges: s.selectionRanges,
selectionRangeTypeMap: s.selectionRangeTypeMap,
}))
const [defaultPlanes, setDefaultPlanes] = useState<DefaultPlanes | null>(null)
useEffect(() => {
if (
@ -36,40 +32,67 @@ export function useAppMode() {
guiMode.sketchMode === 'selectFace' &&
engineCommandManager
) {
if (!defaultPlanes) {
const xy = createPlane(engineCommandManager, {
x_axis: { x: 1, y: 0, z: 0 },
y_axis: { x: 0, y: 1, z: 0 },
color: { r: 0.7, g: 0.28, b: 0.28, a: 0.4 },
})
const yz = createPlane(engineCommandManager, {
x_axis: { x: 0, y: 1, z: 0 },
y_axis: { x: 0, y: 0, z: 1 },
color: { r: 0.28, g: 0.7, b: 0.28, a: 0.4 },
})
const xz = createPlane(engineCommandManager, {
x_axis: { x: 1, y: 0, z: 0 },
y_axis: { x: 0, y: 0, z: 1 },
color: { r: 0.28, g: 0.28, b: 0.7, a: 0.4 },
})
setDefaultPlanes({ xy, yz, xz })
} else {
hideDefaultPlanes(engineCommandManager, defaultPlanes)
const createAndShowPlanes = async () => {
let localDefaultPlanes: DefaultPlanes
if (!defaultPlanes) {
localDefaultPlanes = await initDefaultPlanes(engineCommandManager)
setDefaultPlanes(localDefaultPlanes)
} else {
localDefaultPlanes = defaultPlanes
}
setDefaultPlanesHidden(engineCommandManager, localDefaultPlanes, false)
}
createAndShowPlanes()
}
if (guiMode.mode !== 'sketch' && defaultPlanes) {
Object.values(defaultPlanes).forEach((planeId) => {
engineCommandManager?.sendSceneCommand({
if (
guiMode.mode === 'sketch' &&
guiMode.sketchMode === 'enterSketchEdit' &&
engineCommandManager
) {
const enableSketchMode = async () => {
let localDefaultPlanes: DefaultPlanes
if (!defaultPlanes) {
localDefaultPlanes = await initDefaultPlanes(engineCommandManager)
setDefaultPlanes(localDefaultPlanes)
} else {
localDefaultPlanes = defaultPlanes
}
setDefaultPlanesHidden(engineCommandManager, localDefaultPlanes, true)
// TODO figure out the plane to use based on the sketch
// maybe it's easier to make a new plane than rely on the defaults
await engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'object_visible',
object_id: planeId,
hidden: true,
type: 'sketch_mode_enable',
plane_id: localDefaultPlanes.xy,
ortho: true,
animated: !isReducedMotion(),
},
})
const proms: any[] = []
proms.push(
engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'edit_mode_enter',
target: guiMode.pathId,
},
})
)
await Promise.all(proms)
}
enableSketchMode()
setGuiMode({
...guiMode,
sketchMode: 'sketchEdit',
})
} else if (guiMode.mode === 'default') {
}
if (guiMode.mode !== 'sketch' && defaultPlanes) {
setDefaultPlanesHidden(engineCommandManager, defaultPlanes, true)
}
if (guiMode.mode === 'default') {
const pathId =
engineCommandManager &&
isCursorInSketchCommandRange(
@ -107,7 +130,7 @@ export function useAppMode() {
])
useEffect(() => {
const unSub = engineCommandManager?.subscribeTo({
const unSub = engineCommandManager.subscribeTo({
event: 'select_with_point',
callback: async ({ data }) => {
if (!data.entity_id) return
@ -116,19 +139,17 @@ export function useAppMode() {
// user clicked something else in the scene
return
}
const sketchModeResponse = await engineCommandManager?.sendSceneCommand(
{
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'sketch_mode_enable',
plane_id: data.entity_id,
ortho: true,
animated: !isReducedMotion(),
},
}
)
hideDefaultPlanes(engineCommandManager, defaultPlanes)
const sketchModeResponse = await engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'sketch_mode_enable',
plane_id: data.entity_id,
ortho: true,
animated: !isReducedMotion(),
},
})
setDefaultPlanesHidden(engineCommandManager, defaultPlanes, true)
const sketchUuid = uuidv4()
const proms: any[] = []
proms.push(
@ -150,14 +171,14 @@ export function useAppMode() {
},
})
)
const res = await Promise.all(proms)
console.log('res', res)
await Promise.all(proms)
setGuiMode({
mode: 'sketch',
sketchMode: 'sketchEdit',
rotation: [0, 0, 0, 1],
position: [0, 0, 0],
pathToNode: [],
pathId: sketchUuid,
})
console.log('sketchModeResponse', sketchModeResponse)
@ -167,7 +188,7 @@ export function useAppMode() {
}, [engineCommandManager, defaultPlanes])
}
function createPlane(
async function createPlane(
engineCommandManager: EngineCommandManager,
{
x_axis,
@ -180,7 +201,7 @@ function createPlane(
}
) {
const planeId = uuidv4()
engineCommandManager?.sendSceneCommand({
await engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd: {
type: 'make_plane',
@ -192,7 +213,7 @@ function createPlane(
},
cmd_id: planeId,
})
engineCommandManager?.sendSceneCommand({
await engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd: {
type: 'plane_set_color',
@ -204,23 +225,46 @@ function createPlane(
return planeId
}
function hideDefaultPlanes(
function setDefaultPlanesHidden(
engineCommandManager: EngineCommandManager,
defaultPlanes: DefaultPlanes
defaultPlanes: DefaultPlanes,
hidden: boolean
) {
Object.values(defaultPlanes).forEach((planeId) => {
engineCommandManager?.sendSceneCommand({
engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'object_visible',
object_id: planeId,
hidden: true,
hidden: hidden,
},
})
})
}
async function initDefaultPlanes(
engineCommandManager: EngineCommandManager
): Promise<DefaultPlanes> {
const xy = await createPlane(engineCommandManager, {
x_axis: { x: 1, y: 0, z: 0 },
y_axis: { x: 0, y: 1, z: 0 },
color: { r: 0.7, g: 0.28, b: 0.28, a: 0.4 },
})
// TODO re-enable
// const yz = createPlane(engineCommandManager, {
// x_axis: { x: 0, y: 1, z: 0 },
// y_axis: { x: 0, y: 0, z: 1 },
// color: { r: 0.28, g: 0.7, b: 0.28, a: 0.4 },
// })
// const xz = createPlane(engineCommandManager, {
// x_axis: { x: 1, y: 0, z: 0 },
// y_axis: { x: 0, y: 0, z: 1 },
// color: { r: 0.28, g: 0.28, b: 0.7, a: 0.4 },
// })
return { xy }
}
function isCursorInSketchCommandRange(
artifactMap: ArtifactMap,
selectionRanges: Selections
@ -229,15 +273,17 @@ function isCursorInSketchCommandRange(
([id, artifact]) =>
selectionRanges.codeBasedSelections.some(
(selection) =>
Array.isArray(selection.range) &&
Array.isArray(artifact.range) &&
Array.isArray(selection?.range) &&
Array.isArray(artifact?.range) &&
isOverlap(selection.range, artifact.range) &&
(artifact.commandType === 'start_path' ||
artifact.commandType === 'extend_path' ||
'close_path')
artifact.commandType === 'close_path')
)
)
return overlapingEntries.length === 1 && overlapingEntries[0][1].parentId
return overlapingEntries.length && overlapingEntries[0][1].parentId
? overlapingEntries[0][1].parentId
: false
: overlapingEntries.find(
([, artifact]) => artifact.commandType === 'start_path'
)?.[0] || false
}

View File

@ -0,0 +1,13 @@
import { useLocation } from 'react-router-dom'
export function useDotDotSlash(): (count?: number) => string {
const location = useLocation()
const dotDotSlash = (count = 1): string => {
// since we can't use relative paths (../) for windows
if (location.pathname === '/') return ''
const path = location.pathname.slice(0, location.pathname.lastIndexOf('/'))
if (count <= 1) return path
return dotDotSlash(count - 1)
}
return dotDotSlash
}

View File

@ -0,0 +1,45 @@
import { useEffect } from 'react'
import { useStore } from 'useStore'
import { engineCommandManager } from '../lang/std/engineConnection'
export function useEngineConnectionSubscriptions() {
const { setCursor2, setHighlightRange, highlightRange } = useStore((s) => ({
setCursor2: s.setCursor2,
setHighlightRange: s.setHighlightRange,
highlightRange: s.highlightRange,
}))
useEffect(() => {
if (!engineCommandManager) return
const unSubHover = engineCommandManager.subscribeToUnreliable({
event: 'highlight_set_entity',
callback: ({ data }) => {
if (data?.entity_id) {
const sourceRange =
engineCommandManager.sourceRangeMap[data.entity_id]
setHighlightRange(sourceRange)
} else if (
!highlightRange ||
(highlightRange[0] !== 0 && highlightRange[1] !== 0)
) {
setHighlightRange([0, 0])
}
},
})
const unSubClick = engineCommandManager.subscribeTo({
event: 'select_with_point',
callback: ({ data }) => {
if (!data?.entity_id) {
setCursor2()
return
}
const sourceRange = engineCommandManager.sourceRangeMap[data.entity_id]
setCursor2({ range: sourceRange, type: 'default' })
},
})
return () => {
unSubHover()
unSubClick()
}
}, [engineCommandManager, setCursor2, setHighlightRange, highlightRange])
}

View File

@ -0,0 +1,90 @@
import { useLayoutEffect, useEffect, useRef } from 'react'
import { _executor } from '../lang/executor'
import { useStore } from '../useStore'
import { engineCommandManager } from '../lang/std/engineConnection'
import { deferExecution } from 'lib/utils'
export function useSetupEngineManager(
streamRef: React.RefObject<HTMLDivElement>,
token?: string
) {
const {
setMediaStream,
setIsStreamReady,
setStreamDimensions,
executeCode,
streamDimensions,
} = useStore((s) => ({
setMediaStream: s.setMediaStream,
setIsStreamReady: s.setIsStreamReady,
setStreamDimensions: s.setStreamDimensions,
executeCode: s.executeCode,
streamDimensions: s.streamDimensions,
}))
const streamWidth = streamRef?.current?.offsetWidth
const streamHeight = streamRef?.current?.offsetHeight
const hasSetNonZeroDimensions = useRef<boolean>(false)
useLayoutEffect(() => {
// Load the engine command manager once with the initial width and height,
// then we do not want to reload it.
const { width: quadWidth, height: quadHeight } = getDimensions(
streamWidth,
streamHeight
)
if (!hasSetNonZeroDimensions.current && quadHeight && quadWidth) {
engineCommandManager.start({
setMediaStream,
setIsStreamReady,
width: quadWidth,
height: quadHeight,
token,
})
engineCommandManager.waitForReady.then(() => {
executeCode()
})
setStreamDimensions({
streamWidth: quadWidth,
streamHeight: quadHeight,
})
hasSetNonZeroDimensions.current = true
}
}, [streamRef?.current?.offsetWidth, streamRef?.current?.offsetHeight])
useEffect(() => {
const handleResize = deferExecution(() => {
const { width, height } = getDimensions(
streamRef?.current?.offsetWidth,
streamRef?.current?.offsetHeight
)
if (
streamDimensions.streamWidth !== width ||
streamDimensions.streamHeight !== height
) {
engineCommandManager.handleResize({
streamWidth: width,
streamHeight: height,
})
setStreamDimensions({
streamWidth: width,
streamHeight: height,
})
}
}, 500)
window.addEventListener('resize', handleResize)
return () => {
window.removeEventListener('resize', handleResize)
}
}, [])
}
function getDimensions(streamWidth?: number, streamHeight?: number) {
const width = streamWidth ? streamWidth : 0
const quadWidth = Math.round(width / 4) * 4
const height = streamHeight ? streamHeight : 0
const quadHeight = Math.round(height / 4) * 4
return { width: quadWidth, height: quadHeight }
}

View File

@ -46,9 +46,9 @@ export function useConvertToVariable() {
variableName
)
updateAst(_modifiedAst)
updateAst(_modifiedAst, true)
} catch (e) {
console.log('e', e)
console.log('error', e)
}
}

View File

@ -31,6 +31,14 @@ body.dark {
@apply text-chalkboard-10;
}
select {
@apply bg-chalkboard-20;
}
.dark select {
@apply bg-chalkboard-90;
}
::-webkit-scrollbar {
@apply w-2 h-2 rounded-sm;
@apply bg-chalkboard-20;

View File

@ -1,5 +1,5 @@
import { parser_wasm } from './abstractSyntaxTree'
import { KCLUnexpectedError } from './errors'
import { KCLError } from './errors'
import { initPromise } from './rust'
beforeAll(() => initPromise)
@ -139,54 +139,6 @@ const newVar = myVar + 1
},
])
})
test('using std function "log"', () => {
const code = `log(5, "hello", aIdentifier)`
const { body } = parser_wasm(code)
expect(body).toEqual([
{
type: 'ExpressionStatement',
start: 0,
end: 28,
expression: {
type: 'CallExpression',
start: 0,
end: 28,
callee: {
type: 'Identifier',
start: 0,
end: 3,
name: 'log',
},
arguments: [
{
type: 'Literal',
start: 4,
end: 5,
value: 5,
raw: '5',
},
{
type: 'Literal',
start: 7,
end: 14,
value: 'hello',
raw: '"hello"',
},
{
type: 'Identifier',
start: 16,
end: 27,
name: 'aIdentifier',
},
],
function: {
type: 'InMemory',
},
optional: false,
},
},
])
})
})
describe('testing function declaration', () => {
@ -1560,7 +1512,7 @@ const yo = { a: { b: { c: '123' } } }
// this is a comment
const key = 'c'`
const nonCodeMetaInstance = {
type: 'NoneCodeNode',
type: 'NonCodeNode',
start: code.indexOf('\n// this is a comment'),
end: code.indexOf('const key'),
value: {
@ -1569,17 +1521,17 @@ const key = 'c'`
},
}
const { nonCodeMeta } = parser_wasm(code)
expect(nonCodeMeta.noneCodeNodes[0]).toEqual(nonCodeMetaInstance)
expect(nonCodeMeta.nonCodeNodes[0]).toEqual(nonCodeMetaInstance)
// extra whitespace won't change it's position (0) or value (NB the start end would have changed though)
const codeWithExtraStartWhitespace = '\n\n\n' + code
const { nonCodeMeta: nonCodeMeta2 } = parser_wasm(
codeWithExtraStartWhitespace
)
expect(nonCodeMeta2.noneCodeNodes[0].value).toStrictEqual(
expect(nonCodeMeta2.nonCodeNodes[0].value).toStrictEqual(
nonCodeMetaInstance.value
)
expect(nonCodeMeta2.noneCodeNodes[0].start).not.toBe(
expect(nonCodeMeta2.nonCodeNodes[0].start).not.toBe(
nonCodeMetaInstance.start
)
})
@ -1596,9 +1548,9 @@ const key = 'c'`
const { body } = parser_wasm(code)
const indexOfSecondLineToExpression = 2
const sketchNonCodeMeta = (body as any)[0].declarations[0].init.nonCodeMeta
.noneCodeNodes
.nonCodeNodes
expect(sketchNonCodeMeta[indexOfSecondLineToExpression]).toEqual({
type: 'NoneCodeNode',
type: 'NonCodeNode',
start: 106,
end: 166,
value: {
@ -1619,9 +1571,9 @@ const key = 'c'`
const { body } = parser_wasm(code)
const sketchNonCodeMeta = (body[0] as any).declarations[0].init.nonCodeMeta
.noneCodeNodes
.nonCodeNodes
expect(sketchNonCodeMeta[3]).toEqual({
type: 'NoneCodeNode',
type: 'NonCodeNode',
start: 125,
end: 141,
value: {
@ -1739,11 +1691,16 @@ describe('parsing errors', () => {
let _theError
try {
const result = expect(parser_wasm(code))
console.log('result', result)
} catch (e) {
_theError = e
}
const theError = _theError as any
expect(theError).toEqual(new KCLUnexpectedError('Brace', [[29, 30]]))
expect(theError).toEqual(
new KCLError(
'unexpected',
'Unexpected token Token { token_type: Brace, start: 29, end: 30, value: "}" }',
[[29, 30]]
)
)
})
})

View File

@ -33,5 +33,5 @@ export type SyntaxType =
| 'PipeExpression'
| 'PipeSubstitution'
| 'Literal'
| 'NoneCodeNode'
| 'NonCodeNode'
| 'UnaryExpression'

View File

@ -21,7 +21,7 @@ show(mySketch001)`
)
expect(shown).toEqual([
{
type: 'sketchGroup',
type: 'SketchGroup',
start: {
to: [0, 0],
from: [0, 0],
@ -77,7 +77,7 @@ show(mySketch001)`
)
expect(shown).toEqual([
{
type: 'extrudeGroup',
type: 'ExtrudeGroup',
id: expect.any(String),
value: [],
height: 2,
@ -117,7 +117,7 @@ show(theExtrude, sk2)`
)
expect(geos).toEqual([
{
type: 'extrudeGroup',
type: 'ExtrudeGroup',
id: expect.any(String),
value: [],
height: 2,
@ -126,7 +126,7 @@ show(theExtrude, sk2)`
__meta: [{ sourceRange: [13, 34] }],
},
{
type: 'extrudeGroup',
type: 'ExtrudeGroup',
id: expect.any(String),
value: [],
height: 2,

View File

@ -1,7 +1,7 @@
import fs from 'node:fs'
import { parser_wasm } from './abstractSyntaxTree'
import { ProgramMemory } from './executor'
import { ProgramMemory, SketchGroup } from './executor'
import { initPromise } from './rust'
import { enginelessExecutor } from '../lib/testHelpers'
import { vi } from 'vitest'
@ -117,10 +117,10 @@ show(mySketch)
// ].join('\n')
// const { root } = await exe(code)
// expect(root.mySk1.value).toHaveLength(3)
// expect(root?.rotated?.type).toBe('sketchGroup')
// expect(root?.rotated?.type).toBe('SketchGroup')
// if (
// root?.mySk1?.type !== 'sketchGroup' ||
// root?.rotated?.type !== 'sketchGroup'
// root?.mySk1?.type !== 'SketchGroup' ||
// root?.rotated?.type !== 'SketchGroup'
// )
// throw new Error('not a sketch group')
// expect(root.mySk1.rotation).toEqual([0, 0, 0, 1])
@ -143,7 +143,7 @@ show(mySketch)
].join('\n')
const { root } = await exe(code)
expect(root.mySk1).toEqual({
type: 'sketchGroup',
type: 'SketchGroup',
start: {
to: [0, 0],
from: [0, 0],
@ -199,7 +199,7 @@ show(mySketch)
// TODO path to node is probably wrong here, zero indexes are not correct
expect(root).toEqual({
three: {
type: 'userVal',
type: 'UserVal',
value: 3,
__meta: [
{
@ -208,7 +208,7 @@ show(mySketch)
],
},
yo: {
type: 'userVal',
type: 'UserVal',
value: [1, '2', 3, 9],
__meta: [
{
@ -225,7 +225,7 @@ show(mySketch)
].join('\n')
const { root } = await exe(code)
expect(root.yo).toEqual({
type: 'userVal',
type: 'UserVal',
value: { aStr: 'str', anum: 2, identifier: 3, binExp: 9 },
__meta: [
{
@ -240,7 +240,7 @@ show(mySketch)
)
const { root } = await exe(code)
expect(root.myVar).toEqual({
type: 'userVal',
type: 'UserVal',
value: '123',
__meta: [
{
@ -338,7 +338,7 @@ describe('testing math operators', () => {
const { root } = await exe(code)
const sketch = root.part001
// result of `-legLen(5, min(3, 999))` should be -4
const yVal = sketch.value?.[0]?.to?.[1]
const yVal = (sketch as SketchGroup).value?.[0]?.to?.[1]
expect(yVal).toBe(-4)
})
it('test that % substitution feeds down CallExp->ArrExp->UnaryExp->CallExp', async () => {
@ -356,8 +356,8 @@ describe('testing math operators', () => {
const { root } = await exe(code)
const sketch = root.part001
// expect -legLen(segLen('seg01', %), myVar) to equal -4 setting the y value back to 0
expect(sketch.value?.[1]?.from).toEqual([3, 4])
expect(sketch.value?.[1]?.to).toEqual([6, 0])
expect((sketch as SketchGroup).value?.[1]?.from).toEqual([3, 4])
expect((sketch as SketchGroup).value?.[1]?.to).toEqual([6, 0])
const removedUnaryExp = code.replace(
`-legLen(segLen('seg01', %), myVar)`,
`legLen(segLen('seg01', %), myVar)`
@ -366,7 +366,9 @@ describe('testing math operators', () => {
const removedUnaryExpRootSketch = removedUnaryExpRoot.part001
// without the minus sign, the y value should be 8
expect(removedUnaryExpRootSketch.value?.[1]?.to).toEqual([6, 8])
expect((removedUnaryExpRootSketch as SketchGroup).value?.[1]?.to).toEqual([
6, 8,
])
})
it('with nested callExpression and binaryExpression', async () => {
const code = 'const myVar = 2 + min(100, -1 + legLen(5, 3))'
@ -397,7 +399,10 @@ show(theExtrude)`
// helpers
async function exe(code: string, programMemory: ProgramMemory = { root: {} }) {
async function exe(
code: string,
programMemory: ProgramMemory = { root: {}, return: null }
) {
const ast = parser_wasm(code)
const result = await enginelessExecutor(ast, programMemory)

View File

@ -5,96 +5,21 @@ import {
SourceRangeMap,
} from './std/engineConnection'
import { ProgramReturn } from '../wasm-lib/kcl/bindings/ProgramReturn'
import { MemoryItem } from '../wasm-lib/kcl/bindings/MemoryItem'
import { execute_wasm } from '../wasm-lib/pkg/wasm_lib'
import { KCLError } from './errors'
import { KclError as RustKclError } from '../wasm-lib/kcl/bindings/KclError'
import { rangeTypeFix } from './abstractSyntaxTree'
export type SourceRange = [number, number]
export type PathToNode = [string | number, string][] // [pathKey, nodeType][]
export type Metadata = {
sourceRange: SourceRange
}
export type Position = [number, number, number]
export type Rotation = [number, number, number, number]
export type { SourceRange } from '../wasm-lib/kcl/bindings/SourceRange'
export type { Position } from '../wasm-lib/kcl/bindings/Position'
export type { Rotation } from '../wasm-lib/kcl/bindings/Rotation'
export type { Path } from '../wasm-lib/kcl/bindings/Path'
export type { SketchGroup } from '../wasm-lib/kcl/bindings/SketchGroup'
export type { MemoryItem } from '../wasm-lib/kcl/bindings/MemoryItem'
export type { ExtrudeSurface } from '../wasm-lib/kcl/bindings/ExtrudeSurface'
interface BasePath {
from: [number, number]
to: [number, number]
name?: string
__geoMeta: {
id: string
sourceRange: SourceRange
}
}
export interface ToPoint extends BasePath {
type: 'toPoint'
}
export interface Base extends BasePath {
type: 'base'
}
export interface HorizontalLineTo extends BasePath {
type: 'horizontalLineTo'
x: number
}
export interface AngledLineTo extends BasePath {
type: 'angledLineTo'
angle: number
x?: number
y?: number
}
interface GeoMeta {
__geoMeta: {
id: string
sourceRange: SourceRange
}
}
export type Path = ToPoint | HorizontalLineTo | AngledLineTo | Base
export interface SketchGroup {
type: 'sketchGroup'
id: string
value: Path[]
start?: Base
position: Position
rotation: Rotation
__meta: Metadata[]
}
interface ExtrudePlane {
type: 'extrudePlane'
position: Position
rotation: Rotation
name?: string
}
export type ExtrudeSurface = GeoMeta &
ExtrudePlane /* | ExtrudeRadius | ExtrudeSpline */
export interface ExtrudeGroup {
type: 'extrudeGroup'
id: string
value: ExtrudeSurface[]
height: number
position: Position
rotation: Rotation
__meta: Metadata[]
}
/** UserVal not produced by one of our internal functions */
export interface UserVal {
type: 'userVal'
value: any
__meta: Metadata[]
}
type MemoryItem = UserVal | SketchGroup | ExtrudeGroup
export type PathToNode = [string | number, string][]
interface Memory {
[key: string]: MemoryItem
@ -102,12 +27,12 @@ interface Memory {
export interface ProgramMemory {
root: Memory
return?: ProgramReturn
return: ProgramReturn | null
}
export const executor = async (
node: Program,
programMemory: ProgramMemory = { root: {} },
programMemory: ProgramMemory = { root: {}, return: null },
engineCommandManager: EngineCommandManager,
// work around while the gemotry is still be stored on the frontend
// will be removed when the stream UI is added.
@ -123,7 +48,7 @@ export const executor = async (
engineCommandManager
)
const { artifactMap, sourceRangeMap } =
await engineCommandManager.waitForAllCommands()
await engineCommandManager.waitForAllCommands(node, _programMemory)
tempMapCallback({ artifactMap, sourceRangeMap })
engineCommandManager.endSession()
@ -132,7 +57,7 @@ export const executor = async (
export const _executor = async (
node: Program,
programMemory: ProgramMemory = { root: {} },
programMemory: ProgramMemory = { root: {}, return: null },
engineCommandManager: EngineCommandManager
): Promise<ProgramMemory> => {
try {

View File

@ -1,6 +1,7 @@
import { getNodePathFromSourceRange, getNodeFromPath } from './queryAst'
import { parser_wasm } from './abstractSyntaxTree'
import { initPromise } from './rust'
import { Identifier } from './abstractSyntaxTreeTypes'
beforeAll(() => initPromise)
@ -27,4 +28,81 @@ const sk3 = startSketchAt([0, 0])
expect([node.start, node.end]).toEqual(sourceRange)
expect(node.type).toBe('CallExpression')
})
it('gets path right for function definition params', () => {
const code = `fn cube = (pos, scale) => {
const sg = startSketchAt(pos)
|> line([0, scale], %)
|> line([scale, 0], %)
|> line([0, -scale], %)
return sg
}
const b1 = cube([0,0], 10)`
const subStr = 'pos, scale'
const subStrIndex = code.indexOf(subStr)
const sourceRange: [number, number] = [
subStrIndex,
subStrIndex + 'pos'.length,
]
const ast = parser_wasm(code)
const nodePath = getNodePathFromSourceRange(ast, sourceRange)
const node = getNodeFromPath<Identifier>(ast, nodePath).node
expect(nodePath).toEqual([
['body', ''],
[0, 'index'],
['declarations', 'VariableDeclaration'],
[0, 'index'],
['init', ''],
['params', 'FunctionExpression'],
[0, 'index'],
])
expect(node.type).toBe('Identifier')
expect(node.name).toBe('pos')
})
it('gets path right for deep within function definition body', () => {
const code = `fn cube = (pos, scale) => {
const sg = startSketchAt(pos)
|> line([0, scale], %)
|> line([scale, 0], %)
|> line([0, -scale], %)
return sg
}
const b1 = cube([0,0], 10)`
const subStr = 'scale, 0'
const subStrIndex = code.indexOf(subStr)
const sourceRange: [number, number] = [
subStrIndex,
subStrIndex + 'scale'.length,
]
const ast = parser_wasm(code)
const nodePath = getNodePathFromSourceRange(ast, sourceRange)
const node = getNodeFromPath<Identifier>(ast, nodePath).node
expect(nodePath).toEqual([
['body', ''],
[0, 'index'],
['declarations', 'VariableDeclaration'],
[0, 'index'],
['init', ''],
['body', 'FunctionExpression'],
['body', 'FunctionExpression'],
[0, 'index'],
['declarations', 'VariableDeclaration'],
[0, 'index'],
['init', ''],
['body', 'PipeExpression'],
[2, 'index'],
['arguments', 'CallExpression'],
[0, 'index'],
['elements', 'ArrayExpression'],
[0, 'index'],
])
expect(node.type).toBe('Identifier')
expect(node.name).toBe('scale')
})
})

View File

@ -106,7 +106,7 @@ describe('Testing addSketchTo', () => {
body: [],
start: 0,
end: 0,
nonCodeMeta: { noneCodeNodes: {}, start: null },
nonCodeMeta: { nonCodeNodes: {}, start: null },
},
'yz'
)
@ -176,7 +176,7 @@ show(part001)`
})
describe('Testing moveValueIntoNewVariable', () => {
const fn = (fnName: string) => `const ${fnName} = (x) => {
const fn = (fnName: string) => `fn ${fnName} = (x) => {
return x
}
`

View File

@ -1,4 +1,4 @@
import { Selection, TooTip } from '../useStore'
import { Selection, ToolTip } from '../useStore'
import {
Program,
CallExpression,
@ -27,6 +27,7 @@ import {
getFirstArg,
createFirstArg,
} from './std/sketch'
import { isLiteralArrayOrStatic } from './std/sketchcombos'
export function addStartSketch(
node: Program,
@ -51,11 +52,12 @@ export function addStartSketch(
createPipeExpression(pipeBody)
)
const newIndex = node.body.length
_node.body = [...node.body, variableDeclaration]
let pathToNode: PathToNode = [
['body', ''],
['0', 'index'],
[newIndex.toString(10), 'index'],
['declarations', 'VariableDeclaration'],
['0', 'index'],
['init', 'VariableDeclarator'],
@ -191,7 +193,7 @@ export function mutateArrExp(
): boolean {
if (node.type === 'ArrayExpression') {
node.elements.forEach((element, i) => {
if (element.type === 'Literal') {
if (isLiteralArrayOrStatic(element)) {
node.elements[i] = updateWith.elements[i]
}
})
@ -209,8 +211,8 @@ export function mutateObjExpProp(
const keyIndex = node.properties.findIndex((a) => a.key.name === key)
if (keyIndex !== -1) {
if (
updateWith.type === 'Literal' &&
node.properties[keyIndex].value.type === 'Literal'
isLiteralArrayOrStatic(updateWith) &&
isLiteralArrayOrStatic(node.properties[keyIndex].value)
) {
node.properties[keyIndex].value = updateWith
return true
@ -220,7 +222,7 @@ export function mutateObjExpProp(
) {
const arrExp = node.properties[keyIndex].value as ArrayExpression
arrExp.elements.forEach((element, i) => {
if (element.type === 'Literal') {
if (isLiteralArrayOrStatic(element)) {
arrExp.elements[i] = updateWith.elements[i]
}
})
@ -303,7 +305,11 @@ export function extrudeSketch(
}
const name = findUniqueName(node, 'part')
const VariableDeclaration = createVariableDeclaration(name, extrudeCall)
const showCallIndex = getShowIndex(_node)
let showCallIndex = getShowIndex(_node)
if (showCallIndex == -1) {
// We didn't find a show, so let's just append everything
showCallIndex = _node.body.length
}
_node.body.splice(showCallIndex, 0, VariableDeclaration)
const pathToExtrudeArg: PathToNode = [
['body', ''],
@ -315,7 +321,7 @@ export function extrudeSketch(
[0, 'index'],
]
return {
modifiedAst: addToShow(_node, name),
modifiedAst: node,
pathToNode: [...pathToNode.slice(0, -1), [showCallIndex, 'index']],
pathToExtrudeArg,
}
@ -531,7 +537,7 @@ export function createPipeExpression(
start: 0,
end: 0,
body,
nonCodeMeta: { noneCodeNodes: {}, start: null },
nonCodeMeta: { nonCodeNodes: {}, start: null },
}
}
@ -633,7 +639,7 @@ export function giveSketchFnCallTag(
createLiteral(tag || findUniqueName(ast, 'seg', 2))) as Literal
const tagStr = String(tagValue.value)
const newFirstArg = createFirstArg(
primaryCallExp.callee.name as TooTip,
primaryCallExp.callee.name as ToolTip,
firstArg.val,
tagValue
)

View File

@ -1,5 +1,5 @@
import { PathToNode, ProgramMemory, SketchGroup, SourceRange } from './executor'
import { Selection, TooTip } from '../useStore'
import { Selection, ToolTip } from '../useStore'
import {
BinaryExpression,
Program,
@ -239,7 +239,29 @@ function moreNodePathFromSourceRange(
}
return path
}
console.error('not implemented')
if (_node.type === 'FunctionExpression' && isInRange) {
for (let i = 0; i < _node.params.length; i++) {
const param = _node.params[i]
if (param.start <= start && param.end >= end) {
path.push(['params', 'FunctionExpression'])
path.push([i, 'index'])
return moreNodePathFromSourceRange(param, sourceRange, path)
}
}
if (_node.body.start <= start && _node.body.end >= end) {
path.push(['body', 'FunctionExpression'])
const fnBody = _node.body.body
for (let i = 0; i < fnBody.length; i++) {
const statement = fnBody[i]
if (statement.start <= start && statement.end >= end) {
path.push(['body', 'FunctionExpression'])
path.push([i, 'index'])
return moreNodePathFromSourceRange(statement, sourceRange, path)
}
}
}
}
console.error('not implemented: ' + node.type)
return path
}
@ -457,7 +479,7 @@ export function isLinesParallelAndConstrained(
const secondaryFirstArg = getFirstArg(secondaryNode)
const constraintType = getConstraintType(
secondaryFirstArg.val,
secondaryNode.callee.name as TooTip
secondaryNode.callee.name as ToolTip
)
const constraintLevel = getConstraintLevelFromSourceRange(
secondaryLine.range,

View File

@ -224,7 +224,7 @@ const key = 'c'
expect(recasted).toBe(code)
})
it('comments in a fn block', () => {
const code = `const myFn = () => {
const code = `fn myFn = () => {
// this is a comment
const yo = { a: { b: { c: '123' } } }

View File

@ -7,7 +7,7 @@ export const recast = (ast: Program): string => {
return s
} catch (e) {
// TODO: do something real with the error.
console.log('recast', e)
console.log('recast error', e)
throw e
}
}

View File

@ -1,10 +1,12 @@
import { SourceRange } from 'lang/executor'
import { ProgramMemory, SourceRange } from 'lang/executor'
import { Selections } from 'useStore'
import { VITE_KC_API_WS_MODELING_URL, VITE_KC_CONNECTION_TIMEOUT_MS } from 'env'
import { Models } from '@kittycad/lib'
import { exportSave } from 'lib/exportSave'
import { v4 as uuidv4 } from 'uuid'
import * as Sentry from '@sentry/react'
import { getNodeFromPath, getNodePathFromSourceRange } from 'lang/queryAst'
import { Program, VariableDeclarator } from 'lang/abstractSyntaxTreeTypes'
let lastMessage = ''
@ -13,9 +15,17 @@ interface CommandInfo {
range: SourceRange
parentId?: string
}
type WebSocketResponse = Models['OkWebSocketResponseData_type']
interface ResultCommand extends CommandInfo {
type: 'result'
data: any
raw: WebSocketResponse
}
interface FailedCommand extends CommandInfo {
type: 'failed'
errors: Models['FailureWebSocketResponse_type']['errors']
}
interface PendingCommand extends CommandInfo {
type: 'pending'
@ -24,7 +34,7 @@ interface PendingCommand extends CommandInfo {
}
export interface ArtifactMap {
[key: string]: ResultCommand | PendingCommand
[key: string]: ResultCommand | PendingCommand | FailedCommand
}
export interface SourceRangeMap {
[key: string]: SourceRange
@ -35,7 +45,10 @@ interface NewTrackArgs {
mediaStream: MediaStream
}
type WebSocketResponse = Models['OkWebSocketResponseData_type']
// This looks funny, I know. This is needed because node and the browser
// disagree as to the type. In a browser it's a number, but in node it's a
// "Timeout".
type Timeout = ReturnType<typeof setTimeout>
type ClientMetrics = Models['ClientMetrics_type']
@ -48,6 +61,9 @@ export class EngineConnection {
unreliableDataChannel?: RTCDataChannel
private ready: boolean
private connecting: boolean
private dead: boolean
private failedConnTimeout: Timeout | null
readonly url: string
private readonly token?: string
@ -83,6 +99,9 @@ export class EngineConnection {
this.url = url
this.token = token
this.ready = false
this.connecting = false
this.dead = false
this.failedConnTimeout = null
this.onWebsocketOpen = onWebsocketOpen
this.onDataChannelOpen = onDataChannelOpen
this.onEngineConnectionOpen = onEngineConnectionOpen
@ -93,7 +112,10 @@ export class EngineConnection {
// TODO(paultag): This ought to be tweakable.
const pingIntervalMs = 10000
setInterval(() => {
let pingInterval = setInterval(() => {
if (this.dead) {
clearInterval(pingInterval)
}
if (this.isReady()) {
// When we're online, every 10 seconds, we'll attempt to put a 'ping'
// command through the WebSocket connection. This will help both ends
@ -102,6 +124,24 @@ export class EngineConnection {
this.send({ type: 'ping' })
}
}, pingIntervalMs)
const connectionTimeoutMs = VITE_KC_CONNECTION_TIMEOUT_MS
let connectInterval = setInterval(() => {
if (this.dead) {
clearInterval(connectInterval)
return
}
if (this.isReady()) {
return
}
console.log('connecting via retry')
this.connect()
}, connectionTimeoutMs)
}
// isConnecting will return true when connect has been called, but the full
// WebRTC is not online.
isConnecting() {
return this.connecting
}
// isReady will return true only when the WebRTC *and* WebSocket connection
// are connected. During setup, the WebSocket connection comes online first,
@ -110,6 +150,10 @@ export class EngineConnection {
isReady() {
return this.ready
}
tearDown() {
this.dead = true
this.close()
}
// shouldTrace will return true when Sentry should be used to instrument
// the Engine.
shouldTrace() {
@ -121,8 +165,10 @@ export class EngineConnection {
// This will attempt the full handshake, and retry if the connection
// did not establish.
connect() {
// TODO(paultag): make this safe to call multiple times, and figure out
// when a connection is in progress (state: connecting or something).
console.log('connect was called')
if (this.isConnecting() || this.isReady()) {
return
}
// Information on the connect transaction
@ -354,20 +400,6 @@ export class EngineConnection {
})
})
}
// TODO(paultag): This ought to be both controllable, as well as something
// like exponential backoff to have some grace on the backend, as well as
// fix responsiveness for clients that had a weird network hiccup.
const connectionTimeoutMs = VITE_KC_CONNECTION_TIMEOUT_MS
setTimeout(() => {
if (this.isReady()) {
return
}
console.log('engine connection timeout on connection, retrying')
this.close()
this.connect()
}, connectionTimeoutMs)
})
this.pc.addEventListener('track', (event) => {
@ -393,8 +425,6 @@ export class EngineConnection {
let videoTrack = mediaStream.getVideoTracks()[0]
this.pc?.getStats(videoTrack).then((videoTrackStats) => {
// TODO(paultag): this needs type information from the KittyCAD typescript
// library once it's updated
let client_metrics: ClientMetrics = {
rtc_frames_decoded: 0,
rtc_frames_dropped: 0,
@ -422,12 +452,13 @@ export class EngineConnection {
videoTrackReport.framesReceived
client_metrics.rtc_frames_per_second =
videoTrackReport.framesPerSecond || 0
client_metrics.rtc_freeze_count = videoTrackReport.freezeCount
client_metrics.rtc_freeze_count =
videoTrackReport.freezeCount || 0
client_metrics.rtc_jitter_sec = videoTrackReport.jitter
client_metrics.rtc_keyframes_decoded =
videoTrackReport.keyFramesDecoded
client_metrics.rtc_total_freezes_duration_sec =
videoTrackReport.totalFreezesDuration
videoTrackReport.totalFreezesDuration || 0
} else if (videoTrackReport.type === 'transport') {
// videoTrackReport.bytesReceived,
// videoTrackReport.bytesSent,
@ -458,6 +489,7 @@ export class EngineConnection {
this.onEngineConnectionOpen(this)
this.ready = true
this.connecting = false
})
this.unreliableDataChannel.addEventListener('close', (event) => {
@ -471,8 +503,31 @@ export class EngineConnection {
})
})
const connectionTimeoutMs = VITE_KC_CONNECTION_TIMEOUT_MS
if (this.failedConnTimeout) {
console.log('clearing timeout before set')
clearTimeout(this.failedConnTimeout)
this.failedConnTimeout = null
}
console.log('timeout set')
this.failedConnTimeout = setTimeout(() => {
if (this.isReady()) {
return
}
console.log('engine connection timeout on connection, closing')
this.close()
}, connectionTimeoutMs)
this.onConnectionStarted(this)
}
unreliableSend(message: object | string) {
// TODO(paultag): Add in logic to determine the connection state and
// take actions if needed?
this.unreliableDataChannel?.send(
typeof message === 'string' ? message : JSON.stringify(message)
)
}
send(message: object | string) {
// TODO(paultag): Add in logic to determine the connection state and
// take actions if needed?
@ -488,9 +543,15 @@ export class EngineConnection {
this.pc = undefined
this.unreliableDataChannel = undefined
this.webrtcStatsCollector = undefined
if (this.failedConnTimeout) {
console.log('closed timeout in close')
clearTimeout(this.failedConnTimeout)
this.failedConnTimeout = null
}
this.onClose(this)
this.ready = false
this.connecting = false
}
}
@ -534,7 +595,12 @@ export class EngineCommandManager {
[localUnsubscribeId: string]: (a: any) => void
}
} = {} as any
constructor({
constructor() {
this.engineConnection = undefined
}
start({
setMediaStream,
setIsStreamReady,
width,
@ -547,6 +613,16 @@ export class EngineCommandManager {
height: number
token?: string
}) {
if (width === 0 || height === 0) {
return
}
// If we already have an engine connection, just need to resize the stream.
if (this.engineConnection) {
this.handleResize({ streamWidth: width, streamHeight: height })
return
}
this.waitForReady = new Promise((resolve) => {
this.resolveReady = resolve
})
@ -607,6 +683,8 @@ export class EngineCommandManager {
message.request_id
) {
this.handleModelingCommand(message.resp, message.request_id)
} else if (!message.success && message.request_id) {
this.handleFailedModelingCommand(message)
}
}
})
@ -626,7 +704,35 @@ export class EngineCommandManager {
this.engineConnection?.connect()
}
handleResize({
streamWidth,
streamHeight,
}: {
streamWidth: number
streamHeight: number
}) {
console.log('handleResize', streamWidth, streamHeight)
if (!this.engineConnection?.isReady()) {
return
}
const resizeCmd: EngineCommand = {
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'reconfigure_stream',
width: streamWidth,
height: streamHeight,
fps: 60,
},
}
this.engineConnection?.send(resizeCmd)
}
handleModelingCommand(message: WebSocketResponse, id: string) {
if (this.engineConnection === undefined) {
return
}
if (message.type !== 'modeling') {
return
}
@ -644,12 +750,14 @@ export class EngineCommandManager {
commandType: command.commandType,
parentId: command.parentId ? command.parentId : undefined,
data: modelingResponse,
raw: message,
}
resolve({
id,
commandType: command.commandType,
range: command.range,
data: modelingResponse,
raw: message,
})
} else {
this.artifactMap[id] = {
@ -657,11 +765,44 @@ export class EngineCommandManager {
commandType: command?.commandType,
range: command?.range,
data: modelingResponse,
raw: message,
}
}
}
handleFailedModelingCommand({
request_id,
errors,
}: Models['FailureWebSocketResponse_type']) {
const id = request_id
if (!id) return
const command = this.artifactMap[id]
if (command && command.type === 'pending') {
const resolve = command.resolve
this.artifactMap[id] = {
type: 'failed',
range: command.range,
commandType: command.commandType,
parentId: command.parentId ? command.parentId : undefined,
errors,
}
resolve({
id,
commandType: command.commandType,
range: command.range,
errors,
})
} else {
this.artifactMap[id] = {
type: 'failed',
range: command.range,
commandType: command.commandType,
parentId: command.parentId ? command.parentId : undefined,
errors,
}
}
}
tearDown() {
this.engineConnection?.close()
this.engineConnection?.tearDown()
}
startNewSession() {
this.artifactMap = {}
@ -756,6 +897,9 @@ export class EngineCommandManager {
})
}
sendSceneCommand(command: EngineCommand): Promise<any> {
if (this.engineConnection === undefined) {
return Promise.resolve()
}
if (
command.type === 'modeling_cmd_req' &&
command.cmd.type !== lastMessage
@ -764,20 +908,18 @@ export class EngineCommandManager {
lastMessage = command.cmd.type
}
if (!this.engineConnection?.isReady()) {
console.log('socket not ready')
return Promise.resolve()
}
if (command.type !== 'modeling_cmd_req') return Promise.resolve()
const cmd = command.cmd
if (
cmd.type === 'camera_drag_move' &&
(cmd.type === 'camera_drag_move' ||
cmd.type === 'handle_mouse_drag_move') &&
this.engineConnection?.unreliableDataChannel
) {
cmd.sequence = this.outSequence
this.outSequence++
this.engineConnection?.unreliableDataChannel?.send(
JSON.stringify(command)
)
this.engineConnection?.unreliableSend(command)
return Promise.resolve()
} else if (
cmd.type === 'highlight_set_entity' &&
@ -785,9 +927,7 @@ export class EngineCommandManager {
) {
cmd.sequence = this.outSequence
this.outSequence++
this.engineConnection?.unreliableDataChannel?.send(
JSON.stringify(command)
)
this.engineConnection?.unreliableSend(command)
return Promise.resolve()
} else if (
cmd.type === 'mouse_move' &&
@ -795,9 +935,7 @@ export class EngineCommandManager {
) {
cmd.sequence = this.outSequence
this.outSequence++
this.engineConnection?.unreliableDataChannel?.send(
JSON.stringify(command)
)
this.engineConnection?.unreliableSend(command)
return Promise.resolve()
}
// since it's not mouse drag or highlighting send over TCP and keep track of the command
@ -813,10 +951,12 @@ export class EngineCommandManager {
range: SourceRange
command: EngineCommand | string
}): Promise<any> {
if (this.engineConnection === undefined) {
return Promise.resolve()
}
this.sourceRangeMap[id] = range
if (!this.engineConnection?.isReady()) {
console.log('socket not ready')
return Promise.resolve()
}
this.engineConnection?.send(command)
@ -859,6 +999,9 @@ export class EngineCommandManager {
rangeStr: string,
commandStr: string
): Promise<any> {
if (this.engineConnection === undefined) {
return Promise.resolve()
}
if (id === undefined) {
throw new Error('id is undefined')
}
@ -870,7 +1013,10 @@ export class EngineCommandManager {
}
const range: SourceRange = JSON.parse(rangeStr)
return this.sendModelingCommand({ id, range, command: commandStr })
// We only care about the modeling command response.
return this.sendModelingCommand({ id, range, command: commandStr }).then(
({ raw }) => JSON.stringify(raw)
)
}
commandResult(id: string): Promise<any> {
const command = this.artifactMap[id]
@ -879,10 +1025,15 @@ export class EngineCommandManager {
}
if (command.type === 'result') {
return command.data
} else if (command.type === 'failed') {
return Promise.resolve(command.errors)
}
return command.promise
}
async waitForAllCommands(): Promise<{
async waitForAllCommands(
ast?: Program,
programMemory?: ProgramMemory
): Promise<{
artifactMap: ArtifactMap
sourceRangeMap: SourceRangeMap
}> {
@ -891,9 +1042,97 @@ export class EngineCommandManager {
) as PendingCommand[]
const proms = pendingCommands.map(({ promise }) => promise)
await Promise.all(proms)
if (ast && programMemory) {
await this.fixIdMappings(ast, programMemory)
}
return {
artifactMap: this.artifactMap,
sourceRangeMap: this.sourceRangeMap,
}
}
private async fixIdMappings(ast: Program, programMemory: ProgramMemory) {
if (this.engineConnection === undefined) {
return
}
/* This is a temporary solution since the cmd_ids that are sent through when
sending 'extend_path' ids are not used as the segment ids.
We have a way to back fill them with 'path_get_info', however this relies on one
the sketchGroup array and the segements array returned from the server to be in
the same length and order. plus it's super hacky, we first use the path_id to get
the source range of the pipe expression then use the name of the variable to get
the sketchGroup from programMemory.
I feel queezy about relying on all these steps to always line up.
We have also had to pollute this EngineCommandManager class with knowledge of both the ast and programMemory
We should get the cmd_ids to match with the segment ids and delete this method.
*/
const pathInfoProms = []
for (const [id, artifact] of Object.entries(this.artifactMap)) {
if (artifact.commandType === 'start_path') {
pathInfoProms.push(
this.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'path_get_info',
path_id: id,
},
}).then(({ data }) => ({
originalId: id,
segments: data?.data?.segments,
}))
)
}
}
const pathInfos = await Promise.all(pathInfoProms)
pathInfos.forEach(({ originalId, segments }) => {
const originalArtifact = this.artifactMap[originalId]
if (!originalArtifact || originalArtifact.type === 'pending') {
return
}
const pipeExpPath = getNodePathFromSourceRange(
ast,
originalArtifact.range
)
const pipeExp = getNodeFromPath<VariableDeclarator>(
ast,
pipeExpPath,
'VariableDeclarator'
).node
if (pipeExp.type !== 'VariableDeclarator') {
return
}
const variableName = pipeExp.id.name
const memoryItem = programMemory.root[variableName]
if (!memoryItem) {
return
} else if (memoryItem.type !== 'SketchGroup') {
return
}
const relevantSegments = segments.filter(
({ command_id }: { command_id: string | null }) => command_id
)
if (memoryItem.value.length !== relevantSegments.length) {
return
}
for (let i = 0; i < relevantSegments.length; i++) {
const engineSegment = relevantSegments[i]
const memorySegment = memoryItem.value[i]
const oldId = memorySegment.__geoMeta.id
const artifact = this.artifactMap[oldId]
delete this.artifactMap[oldId]
delete this.sourceRangeMap[oldId]
if (artifact) {
this.artifactMap[engineSegment.command_id] = artifact
this.sourceRangeMap[engineSegment.command_id] = artifact.range
}
}
})
}
}
export const engineCommandManager = new EngineCommandManager()

View File

@ -4,6 +4,7 @@ import {
addNewSketchLn,
getYComponent,
getXComponent,
addCloseToPipe,
} from './sketch'
import { parser_wasm } from '../abstractSyntaxTree'
import { getNodePathFromSourceRange } from '../queryAst'
@ -116,6 +117,7 @@ show(mySketch001)
{
mode: 'sketch',
sketchMode: 'sketchEdit',
pathId: '',
rotation: [0, 0, 0, 1],
position: [0, 0, 0],
pathToNode: [
@ -146,7 +148,7 @@ show(mySketch001)`
const programMemory = await enginelessExecutor(ast)
const sourceStart = code.indexOf(lineToChange)
expect(sourceStart).toBe(66)
const { modifiedAst } = addNewSketchLn({
let { modifiedAst } = addNewSketchLn({
node: ast,
programMemory,
to: [2, 3],
@ -160,12 +162,33 @@ show(mySketch001)`
],
})
// Enable rotations #152
const expectedCode = `const mySketch001 = startSketchAt([0, 0])
let expectedCode = `const mySketch001 = startSketchAt([0, 0])
// |> rx(45, %)
|> lineTo([-1.59, -1.54], %)
|> lineTo([0.46, -5.82], %)
|> lineTo([2, 3], %)
show(mySketch001)
`
expect(recast(modifiedAst)).toBe(expectedCode)
modifiedAst = addCloseToPipe({
node: ast,
programMemory,
pathToNode: [
['body', ''],
[0, 'index'],
['declarations', 'VariableDeclaration'],
[0, 'index'],
['init', 'VariableDeclarator'],
],
})
expectedCode = `const mySketch001 = startSketchAt([0, 0])
// |> rx(45, %)
|> lineTo([-1.59, -1.54], %)
|> lineTo([0.46, -5.82], %)
|> close(%)
show(mySketch001)
`
expect(recast(modifiedAst)).toBe(expectedCode)
})

View File

@ -4,6 +4,7 @@ import {
SketchGroup,
SourceRange,
PathToNode,
MemoryItem,
} from '../executor'
import {
Program,
@ -19,7 +20,8 @@ import {
getNodeFromPathCurry,
getNodePathFromSourceRange,
} from '../queryAst'
import { GuiModes, toolTips, TooTip } from '../../useStore'
import { isLiteralArrayOrStatic } from './sketchcombos'
import { GuiModes, toolTips, ToolTip } from '../../useStore'
import { createPipeExpression, splitPathAtPipeExpression } from '../modifyAst'
import { generateUuidFromHashSeed } from '../../lib/uuid'
@ -55,7 +57,7 @@ export function getCoordsFromPaths(skGroup: SketchGroup, index = 0): Coords2d {
}
export function createFirstArg(
sketchFn: TooTip,
sketchFn: ToolTip,
val: Value | [Value, Value] | [Value, Value, Value],
tag?: Value
): Value {
@ -197,7 +199,7 @@ export const line: SketchLineHelper = {
)
const variableName = varDec.id.name
const sketch = previousProgramMemory?.root?.[variableName]
if (sketch.type !== 'sketchGroup') throw new Error('not a sketchGroup')
if (sketch.type !== 'SketchGroup') throw new Error('not a SketchGroup')
const newXVal = createLiteral(roundOff(to[0] - from[0], 2))
const newYVal = createLiteral(roundOff(to[1] - from[1], 2))
@ -293,7 +295,7 @@ export const xLineTo: SketchLineHelper = {
pathToNode
)
const newX = createLiteral(roundOff(to[0], 2))
if (callExpression.arguments?.[0]?.type === 'Literal') {
if (isLiteralArrayOrStatic(callExpression.arguments?.[0])) {
callExpression.arguments[0] = newX
} else {
mutateObjExpProp(callExpression.arguments?.[0], newX, 'to')
@ -341,7 +343,7 @@ export const yLineTo: SketchLineHelper = {
pathToNode
)
const newY = createLiteral(roundOff(to[1], 2))
if (callExpression.arguments?.[0]?.type === 'Literal') {
if (isLiteralArrayOrStatic(callExpression.arguments?.[0])) {
callExpression.arguments[0] = newY
} else {
mutateObjExpProp(callExpression.arguments?.[0], newY, 'to')
@ -391,7 +393,7 @@ export const xLine: SketchLineHelper = {
pathToNode
)
const newX = createLiteral(roundOff(to[0] - from[0], 2))
if (callExpression.arguments?.[0]?.type === 'Literal') {
if (isLiteralArrayOrStatic(callExpression.arguments?.[0])) {
callExpression.arguments[0] = newX
} else {
mutateObjExpProp(callExpression.arguments?.[0], newX, 'length')
@ -435,7 +437,7 @@ export const yLine: SketchLineHelper = {
pathToNode
)
const newY = createLiteral(roundOff(to[1] - from[1], 2))
if (callExpression.arguments?.[0]?.type === 'Literal') {
if (isLiteralArrayOrStatic(callExpression.arguments?.[0])) {
callExpression.arguments[0] = newY
} else {
mutateObjExpProp(callExpression.arguments?.[0], newY, 'length')
@ -538,7 +540,7 @@ export const angledLineOfXLength: SketchLineHelper = {
)
const variableName = varDec.id.name
const sketch = previousProgramMemory?.root?.[variableName]
if (sketch.type !== 'sketchGroup') throw new Error('not a sketchGroup')
if (sketch.type !== 'SketchGroup') throw new Error('not a SketchGroup')
const angle = createLiteral(roundOff(getAngle(from, to), 0))
const xLength = createLiteral(roundOff(Math.abs(from[0] - to[0]), 2) || 0.1)
const newLine = createCallback
@ -611,7 +613,7 @@ export const angledLineOfYLength: SketchLineHelper = {
)
const variableName = varDec.id.name
const sketch = previousProgramMemory?.root?.[variableName]
if (sketch.type !== 'sketchGroup') throw new Error('not a sketchGroup')
if (sketch.type !== 'SketchGroup') throw new Error('not a SketchGroup')
const angle = createLiteral(roundOff(getAngle(from, to), 0))
const yLength = createLiteral(roundOff(Math.abs(from[1] - to[1]), 2) || 0.1)
@ -868,7 +870,7 @@ export const angledLineThatIntersects: SketchLineHelper = {
const varName = varDec.declarations[0].id.name
const sketchGroup = previousProgramMemory.root[varName] as SketchGroup
const intersectPath = sketchGroup.value.find(
({ name }) => name === intersectTagName
({ name }: Path) => name === intersectTagName
)
let offset = 0
if (intersectPath) {
@ -941,17 +943,29 @@ interface CreateLineFnCallArgs {
programMemory: ProgramMemory
to: [number, number]
from: [number, number]
fnName: TooTip
fnName: ToolTip
pathToNode: PathToNode
}
export function compareVec2Epsilon(
vec1: [number, number],
vec2: [number, number]
) {
const compareEpsilon = 0.015625 // or 2^-6
const xDifference = Math.abs(vec1[0] - vec2[0])
const yDifference = Math.abs(vec1[0] - vec2[0])
return xDifference < compareEpsilon && yDifference < compareEpsilon
}
export function addNewSketchLn({
node: _node,
programMemory: previousProgramMemory,
to,
fnName,
pathToNode,
}: Omit<CreateLineFnCallArgs, 'from'>): { modifiedAst: Program } {
}: Omit<CreateLineFnCallArgs, 'from'>): {
modifiedAst: Program
} {
const node = JSON.parse(JSON.stringify(_node))
const { add, updateArgs } = sketchLineHelperMap?.[fnName] || {}
if (!add || !updateArgs) throw new Error('not a sketch line helper')
@ -965,11 +979,10 @@ export function addNewSketchLn({
>(node, pathToNode, 'PipeExpression')
const variableName = varDec.id.name
const sketch = previousProgramMemory?.root?.[variableName]
if (sketch.type !== 'sketchGroup') throw new Error('not a sketchGroup')
if (sketch.type !== 'SketchGroup') throw new Error('not a SketchGroup')
const last = sketch.value[sketch.value.length - 1] || sketch.start
const from = last.to
return add({
node,
previousProgramMemory,
@ -980,6 +993,29 @@ export function addNewSketchLn({
})
}
export function addCloseToPipe({
node,
pathToNode,
}: {
node: Program
programMemory: ProgramMemory
pathToNode: PathToNode
}) {
const _node = { ...node }
const closeExpression = createCallExpression('close', [
createPipeSubstitution(),
])
const pipeExpression = getNodeFromPath<PipeExpression>(
_node,
pathToNode,
'PipeExpression'
).node
if (pipeExpression.type !== 'PipeExpression')
throw new Error('not a pipe expression')
pipeExpression.body = [...pipeExpression.body, closeExpression]
return _node
}
export function replaceSketchLine({
node,
programMemory,
@ -993,7 +1029,7 @@ export function replaceSketchLine({
node: Program
programMemory: ProgramMemory
sourceRange: SourceRange
fnName: TooTip
fnName: ToolTip
to: [number, number]
from: [number, number]
createCallback: TransformCallback
@ -1035,10 +1071,11 @@ export function addTagForSketchOnFace(
function isAngleLiteral(lineArugement: Value): boolean {
return lineArugement?.type === 'ArrayExpression'
? lineArugement.elements[0].type === 'Literal'
? isLiteralArrayOrStatic(lineArugement.elements[0])
: lineArugement?.type === 'ObjectExpression'
? lineArugement.properties.find(({ key }) => key.name === 'angle')?.value
.type === 'Literal'
? isLiteralArrayOrStatic(
lineArugement.properties.find(({ key }) => key.name === 'angle')?.value
)
: false
}
@ -1171,7 +1208,7 @@ function getFirstArgValuesForAngleFns(callExpression: CallExpression): {
const tag = firstArg.properties.find((p) => p.key.name === 'tag')?.value
const angle = firstArg.properties.find((p) => p.key.name === 'angle')?.value
const secondArgName = ['angledLineToX', 'angledLineToY'].includes(
callExpression?.callee?.name as TooTip
callExpression?.callee?.name as ToolTip
)
? 'to'
: 'length'

View File

@ -401,6 +401,11 @@ show(part001)`
programMemory.root['part001'] as SketchGroup,
[index, index]
).segment
expect(segment).toEqual({ to: [0, 0.04], from: [0, 0.04], name: '' })
expect(segment).toEqual({
to: [0, 0.04],
from: [0, 0.04],
name: '',
type: 'base',
})
})
})

View File

@ -1,10 +1,10 @@
import { TooTip, toolTips } from '../../useStore'
import { ToolTip, toolTips } from '../../useStore'
import {
Program,
VariableDeclarator,
CallExpression,
} from '../abstractSyntaxTreeTypes'
import { SketchGroup, SourceRange } from '../executor'
import { SketchGroup, SourceRange, Path } from '../executor'
export function getSketchSegmentFromSourceRange(
sketchGroup: SketchGroup,
@ -20,10 +20,10 @@ export function getSketchSegmentFromSourceRange(
startSourceRange[1] >= rangeEnd &&
sketchGroup.start
)
return { segment: sketchGroup.start, index: -1 }
return { segment: { ...sketchGroup.start, type: 'base' }, index: -1 }
const lineIndex = sketchGroup.value.findIndex(
({ __geoMeta: { sourceRange } }) =>
({ __geoMeta: { sourceRange } }: Path) =>
sourceRange[0] <= rangeStart && sourceRange[1] >= rangeEnd
)
const line = sketchGroup.value[lineIndex]
@ -67,7 +67,10 @@ export function isSketchVariablesLinked(
return false
const firstCallExp = // first in pipe expression or just the call expression
init?.type === 'CallExpression' ? init : (init?.body[0] as CallExpression)
if (!firstCallExp || !toolTips.includes(firstCallExp?.callee?.name as TooTip))
if (
!firstCallExp ||
!toolTips.includes(firstCallExp?.callee?.name as ToolTip)
)
return false
// convention for sketch fns is that the second argument is the sketch group
const secondArg = firstCallExp?.arguments[1]

View File

@ -9,7 +9,7 @@ import {
getConstraintLevelFromSourceRange,
} from './sketchcombos'
import { initPromise } from '../rust'
import { Selections, TooTip } from '../../useStore'
import { Selections, ToolTip } from '../../useStore'
import { enginelessExecutor } from '../../lib/testHelpers'
import { recast } from '../../lang/recast'
@ -68,7 +68,7 @@ function getConstraintTypeFromSourceHelper(
Value,
Value
]
const fnName = (ast.body[0] as any).expression.callee.name as TooTip
const fnName = (ast.body[0] as any).expression.callee.name as ToolTip
return getConstraintType(args, fnName)
}
function getConstraintTypeFromSourceHelper2(
@ -76,7 +76,7 @@ function getConstraintTypeFromSourceHelper2(
): ReturnType<typeof getConstraintType> {
const ast = parser_wasm(code)
const arg = (ast.body[0] as any).expression.arguments[0] as Value
const fnName = (ast.body[0] as any).expression.callee.name as TooTip
const fnName = (ast.body[0] as any).expression.callee.name as ToolTip
return getConstraintType(arg, fnName)
}

View File

@ -1,5 +1,5 @@
import { TransformCallback } from './stdTypes'
import { Selections, toolTips, TooTip, Selection } from '../../useStore'
import { Selections, toolTips, ToolTip, Selection } from '../../useStore'
import {
CallExpression,
Program,
@ -28,6 +28,7 @@ import { createFirstArg, getFirstArg, replaceSketchLine } from './sketch'
import { PathToNode, ProgramMemory } from '../executor'
import { getSketchSegmentFromSourceRange } from './sketchConstraints'
import { getAngle, roundOff, normaliseAngle } from '../../lib/utils'
import { MemoryItem } from 'wasm-lib/kcl/bindings/MemoryItem'
type LineInputsType =
| 'xAbsolute'
@ -53,7 +54,7 @@ export type ConstraintType =
| 'setAngleBetween'
function createCallWrapper(
a: TooTip,
a: ToolTip,
val: [Value, Value] | Value,
tag?: Value,
valueUsedInTransform?: number
@ -100,7 +101,7 @@ function intersectCallWrapper({
}
export type TransformInfo = {
tooltip: TooTip
tooltip: ToolTip
createNode: (a: {
varValA: Value // x / angle
varValB: Value // y / length or x y for angledLineOfXlength etc
@ -111,7 +112,7 @@ export type TransformInfo = {
}
type TransformMap = {
[key in TooTip]?: {
[key in ToolTip]?: {
[key in LineInputsType | 'free']?: {
[key in ConstraintType]?: TransformInfo
}
@ -1094,12 +1095,12 @@ export function getRemoveConstraintsTransform(
sketchFnExp: CallExpression,
constraintType: ConstraintType
): TransformInfo | false {
let name = sketchFnExp.callee.name as TooTip
let name = sketchFnExp.callee.name as ToolTip
if (!toolTips.includes(name)) {
return false
}
const xyLineMap: {
[key in TooTip]?: TooTip
[key in ToolTip]?: ToolTip
} = {
xLine: 'line',
yLine: 'line',
@ -1136,27 +1137,18 @@ export function getRemoveConstraintsTransform(
// check if the function is locked down and so can't be transformed
const firstArg = getFirstArg(sketchFnExp)
if (Array.isArray(firstArg.val)) {
const [a, b] = firstArg.val
if (a?.type !== 'Literal' || b?.type !== 'Literal') {
return transformInfo
}
} else {
if (firstArg.val?.type !== 'Literal') {
return transformInfo
}
if (isNotLiteralArrayOrStatic(firstArg.val)) {
return transformInfo
}
// check if the function has no constraints
const isTwoValFree =
Array.isArray(firstArg.val) &&
firstArg.val?.[0]?.type === 'Literal' &&
firstArg.val?.[1]?.type === 'Literal'
Array.isArray(firstArg.val) && isLiteralArrayOrStatic(firstArg.val)
if (isTwoValFree) {
return false
}
const isOneValFree =
!Array.isArray(firstArg.val) && firstArg.val?.type === 'Literal'
!Array.isArray(firstArg.val) && isLiteralArrayOrStatic(firstArg.val)
if (isOneValFree) {
return transformInfo
}
@ -1175,37 +1167,24 @@ function getTransformMapPath(
constraintType: ConstraintType
):
| {
toolTip: TooTip
toolTip: ToolTip
lineInputType: LineInputsType | 'free'
constraintType: ConstraintType
}
| false {
const name = sketchFnExp.callee.name as TooTip
const name = sketchFnExp.callee.name as ToolTip
if (!toolTips.includes(name)) {
return false
}
// check if the function is locked down and so can't be transformed
const firstArg = getFirstArg(sketchFnExp)
if (Array.isArray(firstArg.val)) {
const [a, b] = firstArg.val
if (a?.type !== 'Literal' && b?.type !== 'Literal') {
return false
}
} else {
if (firstArg.val?.type !== 'Literal') {
return false
}
if (isNotLiteralArrayOrStatic(firstArg.val)) {
return false
}
// check if the function has no constraints
const isTwoValFree =
Array.isArray(firstArg.val) &&
firstArg.val?.[0]?.type === 'Literal' &&
firstArg.val?.[1]?.type === 'Literal'
const isOneValFree =
!Array.isArray(firstArg.val) && firstArg.val?.type === 'Literal'
if (isTwoValFree || isOneValFree) {
if (isLiteralArrayOrStatic(firstArg.val)) {
const info = transformMap?.[name]?.free?.[constraintType]
if (info)
return {
@ -1246,7 +1225,7 @@ export function getTransformInfo(
export function getConstraintType(
val: Value | [Value, Value] | [Value, Value, Value],
fnName: TooTip
fnName: ToolTip
): LineInputsType | null {
// this function assumes that for two val sketch functions that one arg is locked down not both
// and for one val sketch functions that the arg is NOT locked down
@ -1259,7 +1238,7 @@ export function getConstraintType(
if (fnName === 'xLineTo') return 'yAbsolute'
if (fnName === 'yLineTo') return 'xAbsolute'
} else {
const isFirstArgLockedDown = val?.[0]?.type !== 'Literal'
const isFirstArgLockedDown = isNotLiteralArrayOrStatic(val[0])
if (fnName === 'line')
return isFirstArgLockedDown ? 'xRelative' : 'yRelative'
if (fnName === 'lineTo')
@ -1300,7 +1279,7 @@ export function getTransformInfos(
}) as TransformInfo[]
return theTransforms
} catch (error) {
console.log(error)
console.log('error', error)
return []
}
}
@ -1452,7 +1431,7 @@ export function transformAstSketchLines({
const varName = varDec.id.name
const sketchGroup = programMemory.root?.[varName]
if (!sketchGroup || sketchGroup.type !== 'sketchGroup')
if (!sketchGroup || sketchGroup.type !== 'SketchGroup')
throw new Error('not a sketch group')
const seg = getSketchSegmentFromSourceRange(sketchGroup, range).segment
const referencedSegment = referencedSegmentRange
@ -1466,7 +1445,7 @@ export function transformAstSketchLines({
programMemory,
sourceRange: range,
referencedSegment,
fnName: transformTo || (callExp.callee.name as TooTip),
fnName: transformTo || (callExp.callee.name as ToolTip),
to,
from,
createCallback: callBack({
@ -1532,29 +1511,52 @@ export function getConstraintLevelFromSourceRange(
getNodePathFromSourceRange(ast, cursorRange),
'CallExpression'
)
const name = sketchFnExp?.callee?.name as TooTip
const name = sketchFnExp?.callee?.name as ToolTip
if (!toolTips.includes(name)) return 'free'
const firstArg = getFirstArg(sketchFnExp)
// check if the function is fully constrained
if (Array.isArray(firstArg.val)) {
const [a, b] = firstArg.val
if (a?.type !== 'Literal' && b?.type !== 'Literal') return 'full'
} else {
if (firstArg.val?.type !== 'Literal') return 'full'
if (isNotLiteralArrayOrStatic(firstArg.val)) {
return 'full'
}
// check if the function has no constraints
const isTwoValFree =
Array.isArray(firstArg.val) &&
firstArg.val?.[0]?.type === 'Literal' &&
firstArg.val?.[1]?.type === 'Literal'
Array.isArray(firstArg.val) && isLiteralArrayOrStatic(firstArg.val)
const isOneValFree =
!Array.isArray(firstArg.val) && firstArg.val?.type === 'Literal'
!Array.isArray(firstArg.val) && isLiteralArrayOrStatic(firstArg.val)
if (isTwoValFree) return 'free'
if (isOneValFree) return 'partial'
return 'partial'
}
export function isLiteralArrayOrStatic(
val: Value | [Value, Value] | [Value, Value, Value] | undefined
): boolean {
if (!val) return false
if (Array.isArray(val)) {
const [a, b] = val
return isLiteralArrayOrStatic(a) && isLiteralArrayOrStatic(b)
}
return (
val.type === 'Literal' ||
(val.type === 'UnaryExpression' && val.argument.type === 'Literal')
)
}
export function isNotLiteralArrayOrStatic(
val: Value | [Value, Value] | [Value, Value, Value]
): boolean {
if (Array.isArray(val)) {
const [a, b] = val
return isNotLiteralArrayOrStatic(a) && isNotLiteralArrayOrStatic(b)
}
return (
(val.type !== 'Literal' && val.type !== 'UnaryExpression') ||
(val.type === 'UnaryExpression' && val.argument.type !== 'Literal')
)
}

View File

@ -1,6 +1,6 @@
import { ProgramMemory, Path, SourceRange } from '../executor'
import { Program, Value } from '../abstractSyntaxTreeTypes'
import { TooTip } from '../../useStore'
import { ToolTip } from '../../useStore'
import { PathToNode } from '../executor'
import { EngineCommandManager } from './engineConnection'
@ -45,7 +45,7 @@ export type TransformCallback = (
}
export type SketchCallTransfromMap = {
[key in TooTip]: TransformCallback
[key in ToolTip]: TransformCallback
}
export interface SketchLineHelper {

View File

@ -131,10 +131,12 @@ const yi=45`
})
it('test negative and decimal numbers', () => {
expect(stringSummaryLexer('-1')).toEqual([
"number '-1' from 0 to 2",
"operator '-' from 0 to 1",
"number '1' from 1 to 2",
])
expect(stringSummaryLexer('-1.5')).toEqual([
"number '-1.5' from 0 to 4",
"operator '-' from 0 to 1",
"number '1.5' from 1 to 4",
])
expect(stringSummaryLexer('1.5')).toEqual([
"number '1.5' from 0 to 3",
@ -158,10 +160,12 @@ const yi=45`
"whitespace ' ' from 3 to 4",
"operator '+' from 4 to 5",
"whitespace ' ' from 5 to 6",
"number '-2.5' from 6 to 10",
"operator '-' from 6 to 7",
"number '2.5' from 7 to 10",
])
expect(stringSummaryLexer('-1.5 + 2.5')).toEqual([
"number '-1.5' from 0 to 4",
"operator '-' from 0 to 1",
"number '1.5' from 1 to 4",
"whitespace ' ' from 4 to 5",
"operator '+' from 5 to 6",
"whitespace ' ' from 6 to 7",

View File

@ -11,7 +11,7 @@ export async function asyncLexer(str: string): Promise<Token[]> {
return tokens
} catch (e) {
// TODO: do something real with the error.
console.log('lexer', e)
console.log('lexer error', e)
throw e
}
}
@ -22,7 +22,7 @@ export function lexer(str: string): Token[] {
return tokens
} catch (e) {
// TODO: do something real with the error.
console.log('lexer', e)
console.log('lexer error', e)
throw e
}
}

21
src/lib/exampleKcl.ts Normal file
View File

@ -0,0 +1,21 @@
export const bracket = `// Material: 6061-T6 Aluminum
const sigmaAllow = 35000 // psi
const width = 9 // inch
const p = 150 // Force on shelf - lbs
const distance = 6 // inches
const FOS = 2
const leg1 = 5 // inches
const leg2 = 8 // inches
const thickness = sqrt(distance * p * FOS * 6 / sigmaAllow / width) // inches
const bracket = startSketchAt([0, 0])
|> line([0, leg1], %)
|> line([leg2, 0], %)
|> line([0, -thickness], %)
|> line([-leg2 + thickness, 0], %)
|> line([0, -leg1 + thickness], %)
|> close(%)
|> extrude(width, %)
show(bracket)
`

View File

@ -39,6 +39,6 @@ export async function exportSave(data: ArrayBuffer) {
}
} catch (e) {
// TODO: do something real with the error.
console.log('export', e)
console.log('export error', e)
}
}

View File

@ -36,7 +36,7 @@ export async function initializeProjectDirectory(directory: string) {
try {
docDirectory = await documentDir()
} catch (e) {
console.log(e)
console.log('error', e)
docDirectory = await homeDir() // seems to work better on Linux
}

View File

@ -5,6 +5,9 @@ import {
EngineCommand,
} from '../lang/std/engineConnection'
import { SourceRange } from 'lang/executor'
import { Models } from '@kittycad/lib'
type WebSocketResponse = Models['OkWebSocketResponseData_type']
class MockEngineCommandManager {
constructor(mockParams: {
@ -23,7 +26,13 @@ class MockEngineCommandManager {
range: SourceRange
command: EngineCommand
}): Promise<any> {
return Promise.resolve()
const response: WebSocketResponse = {
type: 'modeling',
data: {
modeling_response: { type: 'empty' },
},
}
return Promise.resolve(JSON.stringify(response))
}
sendModelingCommandFromWasm(
id: string,
@ -49,7 +58,7 @@ class MockEngineCommandManager {
export async function enginelessExecutor(
ast: Program,
pm: ProgramMemory = { root: {} }
pm: ProgramMemory = { root: {}, return: null }
): Promise<ProgramMemory> {
const mockEngineCommandManager = new MockEngineCommandManager({
setIsStreamReady: () => {},
@ -64,17 +73,18 @@ export async function enginelessExecutor(
export async function executor(
ast: Program,
pm: ProgramMemory = { root: {} }
pm: ProgramMemory = { root: {}, return: null }
): Promise<ProgramMemory> {
const engineCommandManager = new EngineCommandManager({
const engineCommandManager = new EngineCommandManager()
engineCommandManager.start({
setIsStreamReady: () => {},
setMediaStream: () => {},
width: 100,
height: 100,
width: 0,
height: 0,
})
await engineCommandManager.waitForReady
engineCommandManager.startNewSession()
const programMemory = await _executor(ast, pm, engineCommandManager)
await engineCommandManager.waitForAllCommands()
await engineCommandManager.waitForAllCommands(ast, programMemory)
return programMemory
}

View File

@ -57,7 +57,7 @@ export function throttle<T>(
}
// takes a function and executes it after the wait time, if the function is called again before the wait time is up, the timer is reset
export function defferExecution<T>(func: (args: T) => any, wait: number) {
export function deferExecution<T>(func: (args: T) => any, wait: number) {
let timeout: ReturnType<typeof setTimeout> | null
let latestArgs: T
@ -66,7 +66,7 @@ export function defferExecution<T>(func: (args: T) => any, wait: number) {
func(latestArgs)
}
function deffered(args: T) {
function deferred(args: T) {
latestArgs = args
if (timeout) {
clearTimeout(timeout)
@ -74,7 +74,7 @@ export function defferExecution<T>(func: (args: T) => any, wait: number) {
timeout = setTimeout(later, wait)
}
return deffered
return deferred
}
export function getNormalisedCoordinates({

View File

@ -2,6 +2,9 @@ import { createMachine, assign } from 'xstate'
import { Models } from '@kittycad/lib'
import withBaseURL from '../lib/withBaseURL'
import { CommandBarMeta } from '../lib/commands'
import { isTauri } from 'lib/isTauri'
import { invoke } from '@tauri-apps/api'
import { VITE_KC_API_BASE_URL } from 'env'
const SKIP_AUTH =
import.meta.env.VITE_KC_SKIP_AUTH === 'true' && import.meta.env.DEV
@ -115,16 +118,26 @@ async function getUser(context: UserContext) {
const headers: { [key: string]: string } = {
'Content-Type': 'application/json',
}
if (!context.token && '__TAURI__' in window) throw 'not log in'
if (!context.token && isTauri()) throw new Error('No token found')
if (context.token) headers['Authorization'] = `Bearer ${context.token}`
if (SKIP_AUTH) return LOCAL_USER
const response = await fetch(url, {
method: 'GET',
credentials: 'include',
headers,
})
const user = await response.json()
const userPromise = !isTauri()
? fetch(url, {
method: 'GET',
credentials: 'include',
headers,
})
.then((res) => res.json())
.catch((err) => console.error('error from Browser getUser', err))
: invoke<Models['User_type'] | Record<'error_code', unknown>>('get_user', {
token: context.token,
hostname: VITE_KC_API_BASE_URL,
}).catch((err) => console.error('error from Tauri getUser', err))
const user = await userPromise
if ('error_code' in user) throw new Error(user.message)
return user

View File

@ -2,48 +2,78 @@ import { faArrowRight, faXmark } from '@fortawesome/free-solid-svg-icons'
import { ActionButton } from '../../components/ActionButton'
import { onboardingPaths, useDismiss, useNextClick } from '.'
import { useStore } from '../../useStore'
import { SettingsSection } from 'routes/Settings'
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
import {
CameraSystem,
cameraMouseDragGuards,
cameraSystems,
} from 'lib/cameraControls'
export default function Units() {
const { buttonDownInStream } = useStore((s) => ({
buttonDownInStream: s.buttonDownInStream,
}))
const dismiss = useDismiss()
const next = useNextClick(onboardingPaths.SKETCHING)
const next = useNextClick(onboardingPaths.STREAMING)
const {
settings: {
send,
state: {
context: { cameraControls },
},
},
} = useGlobalStateContext()
return (
<div className="fixed grid justify-center items-end inset-0 z-50 pointer-events-none">
<div className="fixed inset-0 z-50 grid items-end justify-start px-4 pointer-events-none">
<div
className={
'max-w-2xl flex flex-col justify-center bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded' +
(buttonDownInStream ? '' : ' pointer-events-auto')
}
>
<h1 className="text-2xl font-bold">Camera</h1>
<p className="mt-6">
Moving the camera is easy! The controls are as you might expect:
</p>
<ul className="list-disc list-outside ms-8 mb-4">
<li>Click and drag anywhere in the scene to rotate the camera</li>
<li>
Hold down the <kbd>Shift</kbd> key while clicking and dragging to
pan the camera
</li>
<li>
Hold down the <kbd>Ctrl</kbd> key while dragging to zoom. You can
also use the scroll wheel to zoom in and out.
</li>
</ul>
<p>
What you're seeing here is just a video, and your interactions are
being sent to our Geometry Engine API, which sends back video frames
in real time. How cool is that? It means that you can use KittyCAD
Modeling App (or whatever you want to build) on any device, even a
cheap laptop with no graphics card!
</p>
<div className="flex justify-between mt-6">
<SettingsSection
title="Camera Controls"
description="How you want to control the camera in the 3D view. Try them out above and choose the one that feels most comfortable to you."
className="my-4 last-of-type:mb-12"
>
<select
id="camera-controls"
className="block w-full px-3 py-1 bg-transparent border border-chalkboard-30"
value={cameraControls}
onChange={(e) => {
send({
type: 'Set Camera Controls',
data: { cameraControls: e.target.value as CameraSystem },
})
}}
>
{cameraSystems.map((program) => (
<option key={program} value={program}>
{program}
</option>
))}
</select>
<ul className="mx-4 my-2 text-sm leading-relaxed">
<li>
<strong>Pan:</strong>{' '}
{cameraMouseDragGuards[cameraControls].pan.description}
</li>
<li>
<strong>Zoom:</strong>{' '}
{cameraMouseDragGuards[cameraControls].zoom.description}
</li>
<li>
<strong>Rotate:</strong>{' '}
{cameraMouseDragGuards[cameraControls].rotate.description}
</li>
</ul>
</SettingsSection>
<div className="flex justify-between">
<ActionButton
Element="button"
onClick={() => dismiss('../../')}
onClick={dismiss}
icon={{
icon: faXmark,
bgClassName: 'bg-destroy-80',
@ -59,7 +89,7 @@ export default function Units() {
onClick={next}
icon={{ icon: faArrowRight }}
>
Next: Sketching
Next: Streaming
</ActionButton>
</div>
</div>

View File

@ -0,0 +1,85 @@
import { faArrowRight, faXmark } from '@fortawesome/free-solid-svg-icons'
import { ActionButton } from '../../components/ActionButton'
import { onboardingPaths, useDismiss, useNextClick } from '.'
import { useStore } from '../../useStore'
import { Platform, platform } from '@tauri-apps/api/os'
import { useEffect, useState } from 'react'
export default function CmdK() {
const { buttonDownInStream } = useStore((s) => ({
buttonDownInStream: s.buttonDownInStream,
}))
const dismiss = useDismiss()
const next = useNextClick(onboardingPaths.USER_MENU)
const [platformName, setPlatformName] = useState<Platform | ''>('')
useEffect(() => {
async function getPlatform() {
setPlatformName(await platform())
}
getPlatform()
}, [setPlatformName])
return (
<div className="fixed inset-0 z-50 grid items-end justify-center pointer-events-none">
<div
className={
'max-w-full xl:max-w-4xl flex flex-col justify-center bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded' +
(buttonDownInStream ? '' : ' pointer-events-auto')
}
>
<h2 className="text-2xl">Command Bar</h2>
<p className="my-4">
Press{' '}
{platformName === 'win32' ? (
<>
<kbd>Win</kbd> + <kbd>/</kbd>
</>
) : (
<>
<kbd>OS</kbd> + <kbd>K</kbd>
</>
)}{' '}
to open the command bar. Try changing your theme with it.
</p>
<p className="my-4">
We are working on a command bar that will allow you to quickly see and
search for any available commands. We are building KittyCAD Modeling
App's state management system on top of{' '}
<a
href="https://xstate.js.org/"
rel="noreferrer noopener"
target="_blank"
>
XState
</a>
. Currently you can only control settings, authentication, and file
management from the command bar, but we will be powering modeling
commands with it soon.
</p>
<div className="flex justify-between">
<ActionButton
Element="button"
onClick={dismiss}
icon={{
icon: faXmark,
bgClassName: 'bg-destroy-80',
iconClassName:
'text-destroy-20 group-hover:text-destroy-10 hover:text-destroy-10',
}}
className="hover:border-destroy-40"
>
Dismiss
</ActionButton>
<ActionButton
Element="button"
onClick={next}
icon={{ icon: faArrowRight }}
>
Next: User Menu
</ActionButton>
</div>
</div>
</div>
)
}

View File

@ -0,0 +1,85 @@
import { faArrowRight, faXmark } from '@fortawesome/free-solid-svg-icons'
import { ActionButton } from '../../components/ActionButton'
import { onboardingPaths, useDismiss, useNextClick } from '.'
import { useStore } from '../../useStore'
import { useBackdropHighlight } from 'hooks/useBackdropHighlight'
export default function CodeEditor() {
const { buttonDownInStream } = useStore((s) => ({
buttonDownInStream: s.buttonDownInStream,
}))
const dismiss = useDismiss()
const next = useNextClick(onboardingPaths.PARAMETRIC_MODELING)
return (
<div className="fixed grid justify-end items-center inset-0 z-50 pointer-events-none">
<div
className="fixed inset-0 bg-black opacity-50 pointer-events-none"
style={{ clipPath: useBackdropHighlight('code-pane') }}
></div>
<div
className={
'z-10 max-w-xl h-3/4 flex flex-col justify-center bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded' +
(buttonDownInStream ? '' : ' pointer-events-auto')
}
>
<section className="flex-1">
<h2 className="text-2xl">
Editing code with <code>kcl</code>
</h2>
<p className="my-4">
The left pane is where you write your code. It's a code editor with
syntax highlighting and autocompletion. We've decided to take the
difficult route of writing our own languagecalled <code>kcl</code>
for describing geometry, because don't want to inherit all the
other functionality from existing languages. We have a lot of ideas
about how <code>kcl</code> will evolve, and we want to hear your
thoughts on it.
</p>
<p className="my-4">
We've built a language server for <code>kcl</code> that provides
documentation and autocompletion automatically generated from our
compiler code. You can try it out by hovering over some of the
function names in the pane now. If you like using VSCode, you can
try out our{' '}
<a
href="https://marketplace.visualstudio.com/items?itemName=KittyCAD.kcl-language-server"
rel="noreferrer noopener"
target="_blank"
>
VSCode extension
</a>
.
</p>
<p className="my-4">
You can resize the pane by dragging the handle on the right, and you
can collapse it by clicking the title bar or pressing{' '}
<kbd>Shift</kbd> + <kbd>C</kbd>.
</p>
</section>
<div className="flex justify-between">
<ActionButton
Element="button"
onClick={dismiss}
icon={{
icon: faXmark,
bgClassName: 'bg-destroy-80',
iconClassName:
'text-destroy-20 group-hover:text-destroy-10 hover:text-destroy-10',
}}
className="hover:border-destroy-40"
>
Dismiss
</ActionButton>
<ActionButton
Element="button"
onClick={next}
icon={{ icon: faArrowRight }}
>
Next: Parametric Modeling
</ActionButton>
</div>
</div>
</div>
)
}

View File

@ -0,0 +1,65 @@
import { faArrowRight, faXmark } from '@fortawesome/free-solid-svg-icons'
import { ActionButton } from '../../components/ActionButton'
import { onboardingPaths, useDismiss, useNextClick } from '.'
import { useStore } from '../../useStore'
export default function Export() {
const { buttonDownInStream } = useStore((s) => ({
buttonDownInStream: s.buttonDownInStream,
}))
const dismiss = useDismiss()
const next = useNextClick(onboardingPaths.SKETCHING)
return (
<div className="fixed grid justify-center items-end inset-0 z-50 pointer-events-none">
<div
className={
'max-w-full xl:max-w-2xl flex flex-col justify-center bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded' +
(buttonDownInStream ? '' : ' pointer-events-auto')
}
>
<section className="flex-1">
<h2 className="text-2xl">Export</h2>
<p className="my-4">
Try opening the project menu and clicking "Export Model".
</p>
<p className="my-4">
KittyCAD Modeling App uses our open-source extension proposal for
the GLTF file format.{' '}
<a
href="https://kittycad.io/docs/api/convert-cad-file"
rel="noopener noreferrer"
target="_blank"
>
Our conversion API
</a>{' '}
can convert to and from most common CAD file formats, allowing
export to almost any CAD software.
</p>
</section>
<div className="flex justify-between">
<ActionButton
Element="button"
onClick={dismiss}
icon={{
icon: faXmark,
bgClassName: 'bg-destroy-80',
iconClassName:
'text-destroy-20 group-hover:text-destroy-10 hover:text-destroy-10',
}}
className="hover:border-destroy-40"
>
Dismiss
</ActionButton>
<ActionButton
Element="button"
onClick={next}
icon={{ icon: faArrowRight }}
>
Next: Sketching
</ActionButton>
</div>
</div>
</div>
)
}

View File

@ -0,0 +1,59 @@
import { faArrowRight, faXmark } from '@fortawesome/free-solid-svg-icons'
import { ActionButton } from '../../components/ActionButton'
import { useDismiss } from '.'
import { useEffect } from 'react'
import { useStore } from 'useStore'
import { bracket } from 'lib/exampleKcl'
export default function FutureWork() {
const dismiss = useDismiss()
const { deferredSetCode } = useStore((s) => ({
deferredSetCode: s.deferredSetCode,
}))
useEffect(() => {
deferredSetCode(bracket)
}, [deferredSetCode])
return (
<div className="fixed grid justify-center items-center inset-0 bg-chalkboard-100/50 z-50">
<div className="max-w-full xl:max-w-2xl flex flex-col justify-center bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded">
<h1 className="text-2xl font-bold">Future Work</h1>
<p className="my-4">
We have curves, cuts, and many more CAD features coming soon. We want
your feedback on this user interface, and we want to know what
features you want to see next. Please message us in the Discord server
and open issues on GitHub.
</p>
<p className="my-4">
If you make anything with the app we'd love to see it! Thank you for
taking time to try out KittyCAD Modeling App, and build the future of
hardware design with us 💚.
</p>
<p className="my-4">— The KittyCAD Team</p>
<div className="flex justify-between mt-6">
<ActionButton
Element="button"
onClick={dismiss}
icon={{
icon: faXmark,
bgClassName: 'bg-destroy-80',
iconClassName:
'text-destroy-20 group-hover:text-destroy-10 hover:text-destroy-10',
}}
className="hover:border-destroy-40"
>
Dismiss
</ActionButton>
<ActionButton
Element="button"
onClick={dismiss}
icon={{ icon: faArrowRight }}
>
Finish
</ActionButton>
</div>
</div>
</div>
)
}

View File

@ -0,0 +1,125 @@
import { faArrowRight, faXmark } from '@fortawesome/free-solid-svg-icons'
import { ActionButton } from '../../components/ActionButton'
import { onboardingPaths, useDismiss, useNextClick } from '.'
import { useStore } from '../../useStore'
import { useBackdropHighlight } from 'hooks/useBackdropHighlight'
export default function InteractiveNumbers() {
const { buttonDownInStream } = useStore((s) => ({
buttonDownInStream: s.buttonDownInStream,
}))
const dismiss = useDismiss()
const next = useNextClick(onboardingPaths.COMMAND_K)
return (
<div className="fixed grid justify-end items-center inset-0 z-50 pointer-events-none">
<div
className="fixed inset-0 bg-black opacity-50 pointer-events-none"
style={{ clipPath: useBackdropHighlight('code-pane') }}
></div>
<div
className={
'z-10 max-w-xl h-3/4 flex flex-col justify-center bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded' +
(buttonDownInStream ? '' : ' pointer-events-auto')
}
>
<section className="flex-1 overflow-y-auto mb-6">
<h2 className="text-2xl">Interactive Numbers</h2>
<p className="my-4">
Let's do a little bit of hybrid editing to this part.
</p>
<p className="my-4">
Try changing the value of <code>width</code> on line 3 by holding
the <kbd>Alt</kbd> (or <kbd>Option</kbd>) key and dragging the
number left and right. You can hold down different modifier keys to
change the value by different increments:
</p>
<table className="border-collapse text-sm mx-auto my-4">
<tbody>
<tr>
<td className="border border-solid w-1/2 py-1 px-2 border-chalkboard-40 dark:border-chalkboard-70">
<kbd>Alt + Shift + Cmd/Win</kbd>
</td>
<td className="border border-solid w-1/2 py-1 px-2 border-chalkboard-40 dark:border-chalkboard-70 text-right">
0.01
</td>
</tr>
<tr>
<td className="border border-solid w-1/2 py-1 px-2 border-chalkboard-40 dark:border-chalkboard-70">
<kbd>Alt + Cmd/Win</kbd>
</td>
<td className="border border-solid w-1/2 py-1 px-2 border-chalkboard-40 dark:border-chalkboard-70 text-right">
0.1
</td>
</tr>
<tr>
<td className="border border-solid w-1/2 py-1 px-2 border-chalkboard-40 dark:border-chalkboard-70">
<kbd>Alt</kbd>
</td>
<td className="border border-solid w-1/2 py-1 px-2 border-chalkboard-40 dark:border-chalkboard-70 text-right">
1
</td>
</tr>
<tr>
<td className="border border-solid w-1/2 py-1 px-2 border-chalkboard-40 dark:border-chalkboard-70">
<kbd>Alt + Shift</kbd>
</td>
<td className="border border-solid w-1/2 py-1 px-2 border-chalkboard-40 dark:border-chalkboard-70 text-right">
10
</td>
</tr>
</tbody>
</table>
<p className="my-4">
Our code editor is built with{' '}
<a
href="https://codemirror.net/"
target="_blank"
rel="noreferrer noopeneer"
>
CodeMirror
</a>
, a great open-source project with extensions that make it even more
dynamic and interactive, including{' '}
<a
href="https://github.com/replit/codemirror-interact/"
target="_blank"
rel="noreferrer noopeneer"
>
one by the Replit team
</a>{' '}
lets you interact with numbers in your code by dragging them around.
</p>
<p className="my-4">
Editing code should feel as interactive as point-and-click when you
want it to be, so that you can work in the way that feels most
natural to you. We're going to keep extending the text editor, and
we'd love to hear your ideas for how to make it better.
</p>
</section>
<div className="flex justify-between">
<ActionButton
Element="button"
onClick={dismiss}
icon={{
icon: faXmark,
bgClassName: 'bg-destroy-80',
iconClassName:
'text-destroy-20 group-hover:text-destroy-10 hover:text-destroy-10',
}}
className="hover:border-destroy-40"
>
Dismiss
</ActionButton>
<ActionButton
Element="button"
onClick={next}
icon={{ icon: faArrowRight }}
>
Next: Command Bar
</ActionButton>
</div>
</div>
</div>
)
}

View File

@ -1,29 +1,183 @@
import { faArrowRight, faXmark } from '@fortawesome/free-solid-svg-icons'
import { ActionButton } from '../../components/ActionButton'
import { onboardingPaths, useDismiss, useNextClick } from '.'
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
import { Themes, getSystemTheme } from 'lib/theme'
import { bracket } from 'lib/exampleKcl'
import { useStore } from 'useStore'
import {
createNewProject,
getNextProjectIndex,
getProjectsInDir,
interpolateProjectNameWithIndex,
} from 'lib/tauriFS'
import { isTauri } from 'lib/isTauri'
import { useNavigate } from 'react-router-dom'
import { paths } from 'Router'
import { useEffect } from 'react'
export default function Introduction() {
function OnboardingWithNewFile() {
const navigate = useNavigate()
const dismiss = useDismiss()
const next = useNextClick(onboardingPaths.UNITS)
const next = useNextClick(onboardingPaths.INDEX)
const { deferredSetCode } = useStore((s) => ({
deferredSetCode: s.deferredSetCode,
}))
const {
settings: {
context: { defaultDirectory, defaultProjectName },
},
} = useGlobalStateContext()
async function createAndOpenNewProject() {
const projects = await getProjectsInDir(defaultDirectory)
const nextIndex = await getNextProjectIndex(defaultProjectName, projects)
const name = interpolateProjectNameWithIndex(defaultProjectName, nextIndex)
const newFile = await createNewProject(defaultDirectory + '/' + name)
navigate(`${paths.FILE}/${encodeURIComponent(newFile.path)}`)
}
return (
<div className="fixed grid place-content-center inset-0 bg-chalkboard-110/50 z-50">
<div className="max-w-3xl bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded">
<h1 className="text-2xl font-bold">
Welcome to the KittyCAD Modeling App
{!isTauri() ? (
<>
<h1 className="text-2xl font-bold text-warn-80 dark:text-warn-10">
Replaying onboarding resets your code
</h1>
<p className="my-4">
We see you have some of your own code written in this project.
Please save it somewhere else before continuing the onboarding.
</p>
<div className="flex justify-between mt-6">
<ActionButton
Element="button"
onClick={dismiss}
icon={{
icon: faXmark,
bgClassName: 'bg-destroy-80',
iconClassName:
'text-destroy-20 group-hover:text-destroy-10 hover:text-destroy-10',
}}
className="hover:border-destroy-40"
>
Dismiss
</ActionButton>
<ActionButton
Element="button"
onClick={() => {
deferredSetCode(bracket)
next()
}}
icon={{ icon: faArrowRight }}
>
Overwrite code and continue
</ActionButton>
</div>
</>
) : (
<>
<h1 className="text-2xl font-bold flex gap-4 flex-wrap items-center">
Would you like to create a new project?
</h1>
<section className="my-12">
<p className="my-4">
You have some content in this project that we don't want to
overwrite. If you would like to create a new project, please
click the button below.
</p>
</section>
<div className="flex justify-between mt-6">
<ActionButton
Element="button"
onClick={dismiss}
icon={{
icon: faXmark,
bgClassName: 'bg-destroy-80',
iconClassName:
'text-destroy-20 group-hover:text-destroy-10 hover:text-destroy-10',
}}
className="hover:border-destroy-40"
>
Dismiss
</ActionButton>
<ActionButton
Element="button"
onClick={createAndOpenNewProject}
icon={{ icon: faArrowRight }}
>
Make a new project
</ActionButton>
</div>
</>
)}
</div>
</div>
)
}
export default function Introduction() {
const { deferredSetCode, code } = useStore((s) => ({
code: s.code,
deferredSetCode: s.deferredSetCode,
}))
const {
settings: {
state: {
context: { theme },
},
},
} = useGlobalStateContext()
const getLogoTheme = () =>
theme === Themes.Light ||
(theme === Themes.System && getSystemTheme() === Themes.Light)
? '-dark'
: ''
const dismiss = useDismiss()
const next = useNextClick(onboardingPaths.CAMERA)
useEffect(() => {
if (code === '') deferredSetCode(bracket)
}, [code, deferredSetCode])
return !(code !== '' && code !== bracket) ? (
<div className="fixed grid place-content-center inset-0 bg-chalkboard-110/50 z-50">
<div className="max-w-3xl bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded">
<h1 className="text-2xl font-bold flex gap-4 flex-wrap items-center">
<img
src={`/kcma-logomark${getLogoTheme()}.svg`}
alt="KittyCAD Modeling App"
className="max-w-full h-20"
/>
<span className="bg-energy-10 text-energy-80 px-3 py-1 rounded-full text-base">
Alpha
</span>
</h1>
<p className="my-2">
A browser-first, GPU-streaming hardware design tool that lets you edit
visually, with code, or both.
</p>
<p className="my-2">
Powered by the first API created for anyone to build hardware design
tools.
</p>
<section className="my-12">
<p className="my-4">
Welcome to KittyCAD Modeling App! This is a hardware design tool
that lets you edit visually, with code, or both. It's powered by the
first API created for anyone to build hardware design tools. The 3D
view is not running on your computer, but is instead being streamed
to you from a remote GPU as video.
</p>
<p className="my-4">
This is an alpha release, so you will encounter bugs and missing
features. You can read our{' '}
<a
href="https://gist.github.com/jgomez720/5cd53fb7e8e54079f6dc0d2625de5393"
target="_blank"
rel="noreferrer noopener"
>
expectations for alpha users here
</a>
. Please give us feedback on your experience! We are trying to
release as early as possible to get feedback from users like you.
</p>
</section>
<div className="flex justify-between mt-6">
<ActionButton
Element="button"
onClick={() => dismiss('../')}
onClick={dismiss}
icon={{
icon: faXmark,
bgClassName: 'bg-destroy-80',
@ -44,5 +198,7 @@ export default function Introduction() {
</div>
</div>
</div>
) : (
<OnboardingWithNewFile />
)
}

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