xstate migration (#713)

* Add basic Popover functionality

* Fix up light mode of basic bar

* Add support for 2D and 3D mode styling

* Turn toolbar buttons back on

* Remove ActionButton until after tool logic refactor

* Add transitions

* Add initial modeling machine
This is not a full description of how the modelingMachine should work,
but begins to replicate all of the features of our useStore in XState
instead of zustand.

* Add fillet tool flow

* Refactor: break out engine manager setup into hook
Preparing for making a wrapper component around the App
that will manage the engine manager at the same level as
the modelingMachine.

* Create modeling provider, move engine management to it

* Refactor: move other engine-related useEffect into hook

* Add TS schema, selection actions to modelingMachine

* Add barebones modeling machine to app
Only implementing adding to code-based selections in the text editor so far

* Update moved useEffect hook after merge

* give myself reminder TODO

* fix engineCommandManager waitForReady Promise

* enable devtools

* make utility class for handling default planes

* progresson startNewSketch and EditSketch

* add provider to tests

* too large of a commit
put all of the lang state into another singleton, but did lots of work on xstate too

* fix edit sketch ast issue

* re-execute on sketch exit

* prettierignore xstate typegen file

* add move tool button back in

* handle mouse commands with xState states

* fix move

* remove old imports

* big useStore delete

* fix some destructuring bugs

* start of constraint actions

* add horizontal/vertical distance constraints

* fix more destructuring errors

* fix

* add angle constaints

* add align vertically/horizontally constraints

* add length and equal length constraints

* rename modal states to be more cmd bar friendly

* add doesPipeHave query

* add another query

* add extrude states

* state machine clean up

* xstate layout tweak

* make xstate types happy

* Revamp cursor logic and place curors after ast mod

* Xstate merge (#796)

* turning back on all planes (#720)

* updates

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

tests

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

fix more tests

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

fixes

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

fix stdlib

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>

updates

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

compile

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

update sample code

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

re-enable the planes

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

fix tests

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

fix tests

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

fix tests

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

fix tests

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

fix tests

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

fix tests

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

fix tests

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

updates

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

fix all tests

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

updates

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

boilerplate

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

Cut release v0.9.2 (#714)

rust make default planes

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

updates

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

use the planes from engine

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

fixups

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

updates

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

fixes

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

updates

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

fixes

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

fixes

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

negative args

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

diable camera

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

hide planes

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>

updatress

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

fmt

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

Update src/hooks/useAppMode.ts

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

Update src/hooks/useAppMode.ts

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

cleanups

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

Bump kittycad from 0.2.26 to 0.2.27 in /src-tauri (#726)

Bumps [kittycad](https://github.com/KittyCAD/kittycad.rs) from 0.2.26 to 0.2.27.
- [Release notes](https://github.com/KittyCAD/kittycad.rs/releases)
- [Commits](https://github.com/KittyCAD/kittycad.rs/compare/v0.2.26...v0.2.27)

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

Bump tauri-plugin-fs-extra from `b04bde3` to `6c7a4c0` in /src-tauri (#725)

Bumps [tauri-plugin-fs-extra](https://github.com/tauri-apps/plugins-workspace) from `b04bde3` to `6c7a4c0`.
- [Release notes](https://github.com/tauri-apps/plugins-workspace/releases)
- [Commits](b04bde3461...6c7a4c0984)

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

Bump toml from 0.8.0 to 0.8.1 in /src-tauri (#724)

Bumps [toml](https://github.com/toml-rs/toml) from 0.8.0 to 0.8.1.
- [Commits](https://github.com/toml-rs/toml/compare/toml-v0.8.0...toml-v0.8.1)

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

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

Bump openapitor from `61a1605` to `d3e98c4` in /src/wasm-lib (#723)

Bumps [openapitor](https://github.com/KittyCAD/kittycad.rs) from `61a1605` to `d3e98c4`.
- [Release notes](https://github.com/KittyCAD/kittycad.rs/releases)
- [Commits](61a16059b3...d3e98c4ec0)

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

Bump kittycad from 0.2.26 to 0.2.27 in /src/wasm-lib (#722)

Bumps [kittycad](https://github.com/KittyCAD/kittycad.rs) from 0.2.26 to 0.2.27.
- [Release notes](https://github.com/KittyCAD/kittycad.rs/releases)
- [Commits](https://github.com/KittyCAD/kittycad.rs/compare/v0.2.26...v0.2.27)

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

Bump thiserror from 1.0.48 to 1.0.49 in /src/wasm-lib (#721)

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

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

Bump expectorate from 1.0.7 to 1.1.0 in /src/wasm-lib (#712)

Bumps [expectorate](https://github.com/oxidecomputer/expectorate) from 1.0.7 to 1.1.0.
- [Release notes](https://github.com/oxidecomputer/expectorate/releases)
- [Commits](https://github.com/oxidecomputer/expectorate/compare/v1.0.7...v1.1.0)

---
updated-dependencies:
- dependency-name: expectorate
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

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

Bump clap from 4.4.4 to 4.4.5 in /src/wasm-lib (#711)

Bumps [clap](https://github.com/clap-rs/clap) from 4.4.4 to 4.4.5.
- [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.4...v4.4.5)

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

refactor cleanup

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>

type improvements

* use new sketchmode no camera

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

* js working better

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

* start of negative planes

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

* tests and neg

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

* updates

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

* images

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

* norma;s

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

* better initial load of planes

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

* ts

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

* updates

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

* fixes

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

* updates

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

* fixes

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

* fix tsc

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

* fix edit sketch

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

* add regression test for 2d solid issue

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

* updates

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

* show planes

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

* fix clippy

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

* fix tests

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

* canecel in progress

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

* fix ci as well

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>

* stopping point

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>

* refactor

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

* updates

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

* it works

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

* updates

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

* Hide planes (#797)

* hide planes in one go

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

* update hide;

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>

* make tsc happy

* Make "Replay Onboarding" button available on home settings page (#804)

* Fix unrelated bug, settings button in the home sidebar
doesn't go to the home settings after my previous fixes to routes

* Turn on "Replay Onboarding" button in home settings

* Use ONBOARDING_PROJECT_NAME in both places

* Fix formatting

* Cut release v0.10.0 (#803)

Co-authored-by: Frank Noirot <frank@kittycad.io>

* Bump kittycad from 0.2.28 to 0.2.31 in /src-tauri (#798)

Bumps [kittycad](https://github.com/KittyCAD/kittycad.rs) from 0.2.28 to 0.2.31.
- [Release notes](https://github.com/KittyCAD/kittycad.rs/releases)
- [Commits](https://github.com/KittyCAD/kittycad.rs/compare/v0.2.28...v0.2.31)

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

* Bump openapitor from `fa0345c` to `c122a9b` in /src/wasm-lib (#800)

Bumps [openapitor](https://github.com/KittyCAD/kittycad.rs) from `fa0345c` to `c122a9b`.
- [Release notes](https://github.com/KittyCAD/kittycad.rs/releases)
- [Commits](fa0345c514...c122a9b1d6)

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

* Bump syn from 2.0.37 to 2.0.38 in /src/wasm-lib (#801)

Bumps [syn](https://github.com/dtolnay/syn) from 2.0.37 to 2.0.38.
- [Release notes](https://github.com/dtolnay/syn/releases)
- [Commits](https://github.com/dtolnay/syn/compare/2.0.37...2.0.38)

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

* Bump winnow from 0.5.15 to 0.5.16 in /src/wasm-lib (#799)

Bumps [winnow](https://github.com/winnow-rs/winnow) from 0.5.15 to 0.5.16.
- [Changelog](https://github.com/winnow-rs/winnow/blob/main/CHANGELOG.md)
- [Commits](https://github.com/winnow-rs/winnow/compare/v0.5.15...v0.5.16)

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

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

* Bump tauri-plugin-fs-extra from `fa32d1a` to `9f27e6e` in /src-tauri (#802)

Bumps [tauri-plugin-fs-extra](https://github.com/tauri-apps/plugins-workspace) from `fa32d1a` to `9f27e6e`.
- [Release notes](https://github.com/tauri-apps/plugins-workspace/releases)
- [Commits](fa32d1afa9...9f27e6e441)

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

* better plane selection

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

* use the sketch plane id

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

* add todo w bug

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

* Improve Prop Typings for Modals. Remove instances of `any`. (#792)

* Update typings for modals. Remove instances of `any`

* Fix generic type for creating modals

* cleanup other stuffs

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

* make plane id available when selecting default plane

* few clean up things

* change enter sketch action order to make sure plane id is available to 'enter edit mode'

* Revert "Improve Prop Typings for Modals. Remove instances of `any`. (… (#813)

Revert "Improve Prop Typings for Modals. Remove instances of `any`. (#792)"

This reverts commit 629f326f4c.

* ffmpeg instructions (#814)

* fix some tsc stuff

* small tweak

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: Kurt Hutten Irev-Dev <k.hutten@protonmail.ch>
Co-authored-by: Frank Noirot <frank@kittycad.io>
Co-authored-by: Pierre Jacquier <pierrejacquier39@gmail.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Jason Rametta <rametta@outlook.com>

* clean up

* fix test and tsc

* remove one more thing from useStore

* tweak state digrame layout

* fmt

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: Frank Johnson <frankjohnson1993@gmail.com>
Co-authored-by: Frank Noirot <frank@kittycad.io>
Co-authored-by: Jess Frazelle <jessfraz@users.noreply.github.com>
Co-authored-by: Pierre Jacquier <pierrejacquier39@gmail.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Jason Rametta <rametta@outlook.com>
This commit is contained in:
Kurt Hutten
2023-10-11 13:36:54 +11:00
committed by GitHub
parent e5e30d231b
commit d0930477ad
49 changed files with 3188 additions and 1777 deletions

View File

@ -7,3 +7,6 @@ coverage
target target
src/wasm-lib/pkg src/wasm-lib/pkg
src/wasm-lib/kcl/bindings src/wasm-lib/kcl/bindings
# XState generated files
src/machines/modelingMachine.typegen.ts

View File

@ -25,6 +25,7 @@
"@types/react": "^18.0.0", "@types/react": "^18.0.0",
"@types/react-dom": "^18.0.0", "@types/react-dom": "^18.0.0",
"@uiw/react-codemirror": "^4.21.13", "@uiw/react-codemirror": "^4.21.13",
"@xstate/inspect": "^0.8.0",
"@xstate/react": "^3.2.2", "@xstate/react": "^3.2.2",
"crypto-js": "^4.1.1", "crypto-js": "^4.1.1",
"debounce-promise": "^3.1.2", "debounce-promise": "^3.1.2",

View File

@ -9,6 +9,7 @@ import {
} from 'react-router-dom' } from 'react-router-dom'
import { GlobalStateProvider } from './components/GlobalStateProvider' import { GlobalStateProvider } from './components/GlobalStateProvider'
import CommandBarProvider from 'components/CommandBar' import CommandBarProvider from 'components/CommandBar'
import ModelingMachineProvider from 'components/ModelingMachineProvider'
import { BROWSER_FILE_NAME } from 'Router' import { BROWSER_FILE_NAME } from 'Router'
let listener: ((rect: any) => void) | undefined = undefined let listener: ((rect: any) => void) | undefined = undefined
@ -56,7 +57,9 @@ function TestWrap({ children }: { children: React.ReactNode }) {
path="/file/:id" path="/file/:id"
element={ element={
<CommandBarProvider> <CommandBarProvider>
<GlobalStateProvider>{children}</GlobalStateProvider> <GlobalStateProvider>
<ModelingMachineProvider>{children}</ModelingMachineProvider>
</GlobalStateProvider>
</CommandBarProvider> </CommandBarProvider>
} }
/> />

View File

@ -1,4 +1,4 @@
import { useRef, useEffect, useCallback, MouseEventHandler } from 'react' import { useEffect, useCallback, MouseEventHandler } from 'react'
import { DebugPanel } from './components/DebugPanel' import { DebugPanel } from './components/DebugPanel'
import { v4 as uuidv4 } from 'uuid' import { v4 as uuidv4 } from 'uuid'
import { PaneType, useStore } from './useStore' import { PaneType, useStore } from './useStore'
@ -29,45 +29,33 @@ import { CameraDragInteractionType_type } from '@kittycad/lib/dist/types/src/mod
import { CodeMenu } from 'components/CodeMenu' import { CodeMenu } from 'components/CodeMenu'
import { TextEditor } from 'components/TextEditor' import { TextEditor } from 'components/TextEditor'
import { Themes, getSystemTheme } from 'lib/theme' import { Themes, getSystemTheme } from 'lib/theme'
import { useSetupEngineManager } from 'hooks/useSetupEngineManager'
import { useEngineConnectionSubscriptions } from 'hooks/useEngineConnectionSubscriptions' import { useEngineConnectionSubscriptions } from 'hooks/useEngineConnectionSubscriptions'
import { engineCommandManager } from './lang/std/engineConnection' import { engineCommandManager } from './lang/std/engineConnection'
import { kclManager } from 'lang/KclSinglton'
import { useModelingContext } from 'hooks/useModelingContext'
export function App() { export function App() {
const { code: loadedCode, project } = useLoaderData() as IndexLoaderData const { code: loadedCode, project } = useLoaderData() as IndexLoaderData
const streamRef = useRef<HTMLDivElement>(null)
useHotKeyListener() useHotKeyListener()
const { const {
setCode,
buttonDownInStream, buttonDownInStream,
openPanes, openPanes,
setOpenPanes, setOpenPanes,
didDragInStream, didDragInStream,
streamDimensions, streamDimensions,
guiMode,
setGuiMode,
executeAst,
} = useStore((s) => ({ } = useStore((s) => ({
guiMode: s.guiMode,
setGuiMode: s.setGuiMode,
setCode: s.setCode,
buttonDownInStream: s.buttonDownInStream, buttonDownInStream: s.buttonDownInStream,
openPanes: s.openPanes, openPanes: s.openPanes,
setOpenPanes: s.setOpenPanes, setOpenPanes: s.setOpenPanes,
didDragInStream: s.didDragInStream, didDragInStream: s.didDragInStream,
streamDimensions: s.streamDimensions, streamDimensions: s.streamDimensions,
executeAst: s.executeAst,
})) }))
const { const { settings } = useGlobalStateContext()
auth: { const { showDebugPanel, onboardingStatus, cameraControls, theme } =
context: { token }, settings?.context || {}
}, const { state, send } = useModelingContext()
settings: {
context: { showDebugPanel, onboardingStatus, cameraControls, theme },
},
} = useGlobalStateContext()
const editorTheme = theme === Themes.System ? getSystemTheme() : theme const editorTheme = theme === Themes.System ? getSystemTheme() : theme
@ -84,50 +72,7 @@ export function App() {
useHotkeys('shift + l', () => togglePane('logs')) useHotkeys('shift + l', () => togglePane('logs'))
useHotkeys('shift + e', () => togglePane('kclErrors')) useHotkeys('shift + e', () => togglePane('kclErrors'))
useHotkeys('shift + d', () => togglePane('debug')) useHotkeys('shift + d', () => togglePane('debug'))
useHotkeys('esc', () => { useHotkeys('esc', () => send('Cancel'))
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 = [onboardingPaths.CAMERA, onboardingPaths.STREAMING].some( const paneOpacity = [onboardingPaths.CAMERA, onboardingPaths.STREAMING].some(
(p) => p === onboardingStatus (p) => p === onboardingStatus
@ -141,17 +86,16 @@ export function App() {
// on mount, and overwrite any locally-stored code // on mount, and overwrite any locally-stored code
useEffect(() => { useEffect(() => {
if (isTauri() && loadedCode !== null) { if (isTauri() && loadedCode !== null) {
setCode(loadedCode) kclManager.setCode(loadedCode)
} }
return () => { return () => {
// Clear code on unmount if in desktop app // Clear code on unmount if in desktop app
if (isTauri()) { if (isTauri()) {
setCode('') kclManager.setCode('')
} }
} }
}, [loadedCode, setCode]) }, [loadedCode])
useSetupEngineManager(streamRef, token)
useEngineConnectionSubscriptions() useEngineConnectionSubscriptions()
const debounceSocketSend = throttle<EngineCommand>((message) => { const debounceSocketSend = throttle<EngineCommand>((message) => {
@ -169,10 +113,7 @@ export function App() {
const newCmdId = uuidv4() const newCmdId = uuidv4()
if (buttonDownInStream === undefined) { if (buttonDownInStream === undefined) {
if ( if (state.matches('Sketch.Line Tool')) {
guiMode.mode === 'sketch' &&
guiMode.sketchMode === ('sketch_line' as any)
) {
debounceSocketSend({ debounceSocketSend({
type: 'modeling_cmd_req', type: 'modeling_cmd_req',
cmd_id: newCmdId, cmd_id: newCmdId,
@ -192,7 +133,7 @@ export function App() {
}) })
} }
} else { } else {
if (guiMode.mode === 'sketch' && guiMode.sketchMode === ('move' as any)) { if (state.matches('Sketch.Move Tool')) {
debounceSocketSend({ debounceSocketSend({
type: 'modeling_cmd_req', type: 'modeling_cmd_req',
cmd_id: newCmdId, cmd_id: newCmdId,
@ -232,9 +173,8 @@ export function App() {
return ( return (
<div <div
className="h-screen overflow-hidden relative flex flex-col cursor-pointer select-none" className="relative h-full flex flex-col"
onMouseMove={handleMouseMove} onMouseMove={handleMouseMove}
ref={streamRef}
> >
<AppHeader <AppHeader
className={ className={

View File

@ -3,10 +3,8 @@ import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
// Wrapper around protected routes, used in src/Router.tsx // Wrapper around protected routes, used in src/Router.tsx
export const Auth = ({ children }: React.PropsWithChildren) => { export const Auth = ({ children }: React.PropsWithChildren) => {
const { const { auth } = useGlobalStateContext()
auth: { state }, const isLoggingIn = auth?.state.matches('checkIfLoggedIn')
} = useGlobalStateContext()
const isLoggingIn = state.matches('checkIfLoggedIn')
return isLoggingIn ? ( return isLoggingIn ? (
<Loading>Loading KittyCAD Modeling App...</Loading> <Loading>Loading KittyCAD Modeling App...</Loading>

View File

@ -40,6 +40,8 @@ import { ContextFrom } from 'xstate'
import CommandBarProvider from 'components/CommandBar' import CommandBarProvider from 'components/CommandBar'
import { TEST, VITE_KC_SENTRY_DSN } from './env' import { TEST, VITE_KC_SENTRY_DSN } from './env'
import * as Sentry from '@sentry/react' import * as Sentry from '@sentry/react'
import ModelingMachineProvider from 'components/ModelingMachineProvider'
import { KclContextProvider } from 'lang/KclSinglton'
if (VITE_KC_SENTRY_DSN && !TEST) { if (VITE_KC_SENTRY_DSN && !TEST) {
Sentry.init({ Sentry.init({
@ -141,7 +143,11 @@ const router = createBrowserRouter(
element: ( element: (
<Auth> <Auth>
<Outlet /> <Outlet />
<KclContextProvider>
<ModelingMachineProvider>
<App /> <App />
</ModelingMachineProvider>
</KclContextProvider>
{!isTauri() && import.meta.env.PROD && <DownloadAppBanner />} {!isTauri() && import.meta.env.PROD && <DownloadAppBanner />}
</Auth> </Auth>
), ),

View File

@ -1,24 +1,26 @@
import { useStore, toolTips, ToolTip } from './useStore' import { useStore, toolTips, ToolTip } from './useStore'
import { extrudeSketch, sketchOnExtrudedFace } from './lang/modifyAst' import { extrudeSketch, sketchOnExtrudedFace } from './lang/modifyAst'
import { getNodePathFromSourceRange } from './lang/queryAst' import { getNodePathFromSourceRange } from './lang/queryAst'
import { HorzVert } from './components/Toolbar/HorzVert' // import { HorzVert } from './components/Toolbar/HorzVert'
import { RemoveConstrainingValues } from './components/Toolbar/RemoveConstrainingValues' // import { RemoveConstrainingValues } from './components/Toolbar/RemoveConstrainingValues'
import { EqualLength } from './components/Toolbar/EqualLength' // import { EqualLength } from './components/Toolbar/EqualLength'
import { EqualAngle } from './components/Toolbar/EqualAngle' // import { EqualAngle } from './components/Toolbar/EqualAngle'
import { Intersect } from './components/Toolbar/Intersect' // import { Intersect } from './components/Toolbar/Intersect'
import { SetHorzVertDistance } from './components/Toolbar/SetHorzVertDistance' // import { SetHorzVertDistance } from './components/Toolbar/SetHorzVertDistance'
import { SetAngleLength } from './components/Toolbar/setAngleLength' // import { SetAngleLength } from './components/Toolbar/setAngleLength'
import { SetAbsDistance } from './components/Toolbar/SetAbsDistance' // import { SetAbsDistance } from './components/Toolbar/SetAbsDistance'
import { SetAngleBetween } from './components/Toolbar/SetAngleBetween' // import { SetAngleBetween } from './components/Toolbar/SetAngleBetween'
import { Fragment, WheelEvent, useRef } from 'react' import { Fragment, WheelEvent, useRef, useMemo } from 'react'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faSearch, faX } from '@fortawesome/free-solid-svg-icons' import { faSearch, faX } from '@fortawesome/free-solid-svg-icons'
import { Popover, Transition } from '@headlessui/react' import { Popover, Transition } from '@headlessui/react'
import styles from './Toolbar.module.css' import styles from './Toolbar.module.css'
import { v4 as uuidv4 } from 'uuid' import { v4 as uuidv4 } from 'uuid'
import { useAppMode } from 'hooks/useAppMode' import { isCursorInSketchCommandRange } from 'hooks/useAppMode'
import { ActionIcon } from 'components/ActionIcon' import { ActionIcon } from 'components/ActionIcon'
import { engineCommandManager } from './lang/std/engineConnection' import { engineCommandManager } from './lang/std/engineConnection'
import { useModelingContext } from 'hooks/useModelingContext'
import { kclManager } from 'lang/KclSinglton'
export const sketchButtonClassnames = { export const sketchButtonClassnames = {
background: background:
@ -44,25 +46,16 @@ const sketchFnLabels: Record<ToolTip | 'sketch_line' | 'move', string> = {
} }
export const Toolbar = () => { export const Toolbar = () => {
const { const { state, send, context } = useModelingContext()
setGuiMode,
guiMode,
selectionRanges,
ast,
updateAst,
programMemory,
executeAst,
} = useStore((s) => ({
guiMode: s.guiMode,
setGuiMode: s.setGuiMode,
selectionRanges: s.selectionRanges,
ast: s.ast,
updateAst: s.updateAst,
programMemory: s.programMemory,
executeAst: s.executeAst,
}))
useAppMode()
const toolbarButtonsRef = useRef<HTMLSpanElement>(null) const toolbarButtonsRef = useRef<HTMLSpanElement>(null)
const pathId = useMemo(
() =>
isCursorInSketchCommandRange(
engineCommandManager.artifactMap,
context.selectionRanges
),
[engineCommandManager.artifactMap, context.selectionRanges]
)
function handleToolbarButtonsWheelEvent(ev: WheelEvent<HTMLSpanElement>) { function handleToolbarButtonsWheelEvent(ev: WheelEvent<HTMLSpanElement>) {
const span = toolbarButtonsRef.current const span = toolbarButtonsRef.current
@ -80,194 +73,113 @@ export const Toolbar = () => {
onWheel={handleToolbarButtonsWheelEvent} onWheel={handleToolbarButtonsWheelEvent}
className={styles.toolbarButtons + ' ' + className} className={styles.toolbarButtons + ' ' + className}
> >
{guiMode.mode === 'default' && ( {state.nextEvents.includes('Enter sketch') && (
<button <button
onClick={() => { onClick={() => send({ type: 'Enter sketch' })}
setGuiMode({
mode: 'sketch',
sketchMode: 'selectFace',
})
}}
className="group" className="group"
> >
<ActionIcon icon="sketch" className="!p-0.5" size="md" /> <ActionIcon icon="sketch" className="!p-0.5" size="md" />
Start Sketch Start Sketch
</button> </button>
)} )}
{guiMode.mode === 'canEditExtrude' && ( {state.nextEvents.includes('Enter sketch') && pathId && (
<button <button
onClick={() => { onClick={() => send({ type: 'Enter sketch' })}
if (!ast) return
const pathToNode = getNodePathFromSourceRange(
ast,
selectionRanges.codeBasedSelections[0].range
)
const { modifiedAst } = sketchOnExtrudedFace(
ast,
pathToNode,
programMemory
)
updateAst(modifiedAst, true)
}}
className="group"
>
<ActionIcon icon="sketch" className="!p-0.5" size="md" />
Sketch on Face
</button>
)}
{guiMode.mode === 'canEditSketch' && (
<button
onClick={() => {
const pathToNode = getNodePathFromSourceRange(
ast,
selectionRanges.codeBasedSelections[0].range
)
setGuiMode({
mode: 'sketch',
sketchMode: 'enterSketchEdit',
pathToNode: pathToNode,
rotation: [0, 0, 0, 1],
position: [0, 0, 0],
pathId: guiMode.pathId,
})
}}
className="group" className="group"
> >
<ActionIcon icon="sketch" className="!p-0.5" size="md" /> <ActionIcon icon="sketch" className="!p-0.5" size="md" />
Edit Sketch Edit Sketch
</button> </button>
)} )}
{guiMode.mode === 'canEditSketch' && ( {state.nextEvents.includes('Cancel') && !state.matches('idle') && (
<> <button onClick={() => send({ type: 'Cancel' })} className="group">
<button <ActionIcon icon="exit" className="!p-0.5" size="md" />
onClick={() => { Exit Sketch
if (!ast) return
const pathToNode = getNodePathFromSourceRange(
ast,
selectionRanges.codeBasedSelections[0].range
)
const { modifiedAst, pathToExtrudeArg } = extrudeSketch(
ast,
pathToNode
)
updateAst(modifiedAst, true, { focusPath: pathToExtrudeArg })
}}
className="group"
>
<ActionIcon icon="extrude" className="!p-0.5" size="md" />
Extrude
</button>
<button
onClick={() => {
if (!ast) return
const pathToNode = getNodePathFromSourceRange(
ast,
selectionRanges.codeBasedSelections[0].range
)
const { modifiedAst, pathToExtrudeArg } = extrudeSketch(
ast,
pathToNode,
false
)
updateAst(modifiedAst, true, { focusPath: pathToExtrudeArg })
}}
className="group"
>
<ActionIcon icon="extrude" className="!p-0.5" size="md" />
Extrude as new
</button>
</>
)}
{guiMode.mode === 'sketch' && (
<button
onClick={() => {
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> </button>
)} )}
{toolTips {state.matches('Sketch') && !state.matches('idle') && (
.filter(
// (sketchFnName) => !['angledLineThatIntersects'].includes(sketchFnName)
(sketchFnName) => ['sketch_line', 'move'].includes(sketchFnName)
)
.map((sketchFnName) => {
if (
guiMode.mode !== 'sketch' ||
!('isTooltip' in guiMode || guiMode.sketchMode === 'sketchEdit')
)
return null
return (
<button <button
key={sketchFnName} onClick={() =>
onClick={() => { state.matches('Sketch.Line Tool')
engineCommandManager.sendSceneCommand({ ? send('CancelSketch')
type: 'modeling_cmd_req', : send('Equip tool')
cmd_id: uuidv4(),
cmd: {
type: 'set_tool',
tool:
guiMode.sketchMode === sketchFnName
? 'select'
: (sketchFnName as any),
},
})
setGuiMode({
...guiMode,
...(guiMode.sketchMode === sketchFnName
? {
sketchMode: 'sketchEdit',
// todo: ...guiMod is adding isTooltip: true, will probably just fix with xstate migtaion
} }
: {
sketchMode: sketchFnName,
waitingFirstClick: true,
isTooltip: true,
pathId: guiMode.pathId,
}),
})
}}
className={ className={
'group ' + 'group ' +
(guiMode.sketchMode === sketchFnName (state.matches('Sketch.Line Tool')
? '!text-fern-70 !bg-fern-10 !dark:text-fern-20 !border-fern-50' ? '!text-fern-70 !bg-fern-10 !dark:text-fern-20 !border-fern-50'
: '') : '')
} }
>
<ActionIcon icon="line" className="!p-0.5" size="md" />
Line
</button>
)}
{state.matches('Sketch') && (
<button
onClick={() =>
state.matches('Sketch.Move Tool')
? send('CancelSketch')
: send('Equip move tool')
}
className={
'group ' +
(state.matches('Sketch.Move Tool')
? '!text-fern-70 !bg-fern-10 !dark:text-fern-20 !border-fern-50'
: '')
}
>
<ActionIcon icon="move" className="!p-0.5" size="md" />
Move
</button>
)}
{state.matches('Sketch.SketchIdle') &&
state.nextEvents
.filter(
(eventName) =>
eventName.includes('Make segment') ||
eventName.includes('Constrain')
)
.map((eventName) => (
<button
key={eventName}
onClick={() => send(eventName)}
className="group"
disabled={
!state.nextEvents
.filter((event) => state.can(event as any))
.includes(eventName)
}
title={eventName}
> >
<ActionIcon <ActionIcon
icon={sketchFnName.includes('line') ? 'line' : 'move'} icon={'line'} // TODO
className="!p-0.5"
bgClassName={sketchButtonClassnames.background} bgClassName={sketchButtonClassnames.background}
iconClassName={sketchButtonClassnames.icon} iconClassName={sketchButtonClassnames.icon}
size="md" size="md"
/> />
{sketchFnLabels[sketchFnName]} {eventName
.replace('Make segment ', '')
.replace('Constrain ', '')}
</button> </button>
) ))}
})} {state.matches('idle') && (
<HorzVert horOrVert="horizontal" /> <button
onClick={() => send('extrude intent')}
disabled={!state.can('extrude intent')}
className="group"
title={
state.can('extrude intent')
? 'extrude'
: 'sketches need to be closed, or not already extruded'
}
>
<ActionIcon icon="extrude" className="!p-0.5" size="md" />
Extrude
</button>
)}
{/* <HorzVert horOrVert="horizontal" />
<HorzVert horOrVert="vertical" /> <HorzVert horOrVert="vertical" />
<EqualLength /> <EqualLength />
<EqualAngle /> <EqualAngle />
@ -283,16 +195,20 @@ export const Toolbar = () => {
<SetAngleLength angleOrLength="setLength" /> <SetAngleLength angleOrLength="setLength" />
<Intersect /> <Intersect />
<RemoveConstrainingValues /> <RemoveConstrainingValues />
<SetAngleBetween /> <SetAngleBetween /> */}
</span> </span>
) )
} }
return ( return (
<Popover className={styles.toolbarWrapper + ' ' + guiMode.mode}> <Popover
className={
styles.toolbarWrapper + state.matches('Sketch') ? ' sketch' : ''
}
>
<div className={styles.toolbar}> <div className={styles.toolbar}>
<span className={styles.toolbarCap + ' ' + styles.label}> <span className={styles.toolbarCap + ' ' + styles.label}>
{guiMode.mode === 'sketch' ? '2D' : '3D'} {state.matches('Sketch') ? '2D' : '3D'}
</span> </span>
<menu className="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 /> <ToolbarButtons />
@ -328,7 +244,7 @@ export const Toolbar = () => {
<p <p
className={`${styles.toolbarCap} ${styles.label} !self-center rounded-r-full w-fit`} className={`${styles.toolbarCap} ${styles.label} !self-center rounded-r-full w-fit`}
> >
You're in {guiMode.mode === 'sketch' ? '2D' : '3D'} You're in {state.matches('Sketch') ? '2D' : '3D'}
</p> </p>
<Popover.Button className="p-2 flex items-center justify-center rounded-sm bg-chalkboard-20 text-chalkboard-110 dark:bg-chalkboard-70 dark:text-chalkboard-20 border-none hover:bg-chalkboard-30 dark:hover:bg-chalkboard-60"> <Popover.Button className="p-2 flex items-center justify-center rounded-sm bg-chalkboard-20 text-chalkboard-110 dark:bg-chalkboard-70 dark:text-chalkboard-20 border-none hover:bg-chalkboard-30 dark:hover:bg-chalkboard-60">
<FontAwesomeIcon icon={faX} className="w-4 h-4" /> <FontAwesomeIcon icon={faX} className="w-4 h-4" />

View File

@ -20,11 +20,8 @@ export const AppHeader = ({
className = '', className = '',
enableMenu = false, enableMenu = false,
}: AppHeaderProps) => { }: AppHeaderProps) => {
const { const { auth } = useGlobalStateContext()
auth: { const user = auth?.context?.user
context: { user },
},
} = useGlobalStateContext()
return ( return (
<header <header

View File

@ -1,18 +1,18 @@
import { useModelingContext } from 'hooks/useModelingContext'
import { kclManager } from 'lang/KclSinglton'
import { getNodeFromPath, getNodePathFromSourceRange } from 'lang/queryAst' import { getNodeFromPath, getNodePathFromSourceRange } from 'lang/queryAst'
import { useEffect, useRef, useState } from 'react' import { useEffect, useRef, useState } from 'react'
import { useStore } from 'useStore' import { useStore } from 'useStore'
export function AstExplorer() { export function AstExplorer() {
const { ast, setHighlightRange, selectionRanges } = useStore((s) => ({ const setHighlightRange = useStore((s) => s.setHighlightRange)
ast: s.ast, const { context } = useModelingContext()
setHighlightRange: s.setHighlightRange,
selectionRanges: s.selectionRanges,
}))
const pathToNode = getNodePathFromSourceRange( const pathToNode = getNodePathFromSourceRange(
ast, // TODO maybe need to have callback to make sure it stays in sync
selectionRanges.codeBasedSelections?.[0]?.range kclManager.ast,
context.selectionRanges.codeBasedSelections?.[0]?.range
) )
const node = getNodeFromPath(ast, pathToNode).node const node = getNodeFromPath(kclManager.ast, pathToNode).node
const [filterKeys, setFilterKeys] = useState<string[]>(['start', 'end']) const [filterKeys, setFilterKeys] = useState<string[]>(['start', 'end'])
return ( return (
@ -46,7 +46,11 @@ export function AstExplorer() {
}} }}
> >
<pre className=" text-xs overflow-y-auto" style={{ width: '300px' }}> <pre className=" text-xs overflow-y-auto" style={{ width: '300px' }}>
<DisplayObj obj={ast} filterKeys={filterKeys} node={node} /> <DisplayObj
obj={kclManager.ast}
filterKeys={filterKeys}
node={node}
/>
</pre> </pre>
</div> </div>
</div> </div>
@ -84,10 +88,8 @@ function DisplayObj({
filterKeys: string[] filterKeys: string[]
node: any node: any
}) { }) {
const { setHighlightRange, setCursor2 } = useStore((s) => ({ const setHighlightRange = useStore((s) => s.setHighlightRange)
setHighlightRange: s.setHighlightRange, const { send } = useModelingContext()
setCursor2: s.setCursor2,
}))
const ref = useRef<HTMLPreElement>(null) const ref = useRef<HTMLPreElement>(null)
const [hasCursor, setHasCursor] = useState(false) const [hasCursor, setHasCursor] = useState(false)
const [isCollapsed, setIsCollapsed] = useState(false) const [isCollapsed, setIsCollapsed] = useState(false)
@ -118,7 +120,16 @@ function DisplayObj({
setHighlightRange([obj?.start || 0, obj.end]) setHighlightRange([obj?.start || 0, obj.end])
}} }}
onClick={(e) => { onClick={(e) => {
setCursor2({ type: 'default', range: [obj?.start || 0, obj.end || 0] }) send({
type: 'Set selection',
data: {
selectionType: 'singleCodeCursor',
selection: {
type: 'default',
range: [obj?.start || 0, obj.end || 0],
},
},
})
e.stopPropagation() e.stopPropagation()
}} }}
> >

View File

@ -7,8 +7,9 @@ import {
findUniqueName, findUniqueName,
} from '../lang/modifyAst' } from '../lang/modifyAst'
import { findAllPreviousVariables, PrevVariable } from '../lang/queryAst' import { findAllPreviousVariables, PrevVariable } from '../lang/queryAst'
import { useStore } from '../useStore'
import { engineCommandManager } from '../lang/std/engineConnection' import { engineCommandManager } from '../lang/std/engineConnection'
import { kclManager, useKclContext } from 'lang/KclSinglton'
import { useModelingContext } from 'hooks/useModelingContext'
export const AvailableVars = ({ export const AvailableVars = ({
onVarClick, onVarClick,
@ -91,14 +92,9 @@ export function useCalc({
newVariableInsertIndex: number newVariableInsertIndex: number
setNewVariableName: (a: string) => void setNewVariableName: (a: string) => void
} { } {
const { ast, programMemory, selectionRange, defaultPlanes } = useStore( const { programMemory } = useKclContext()
(s) => ({ const { context } = useModelingContext()
ast: s.ast, const selectionRange = context.selectionRanges.codeBasedSelections[0].range
programMemory: s.programMemory,
selectionRange: s.selectionRanges.codeBasedSelections[0].range,
defaultPlanes: s.defaultPlanes,
})
)
const inputRef = useRef<HTMLInputElement>(null) const inputRef = useRef<HTMLInputElement>(null)
const [availableVarInfo, setAvailableVarInfo] = useState< const [availableVarInfo, setAvailableVarInfo] = useState<
ReturnType<typeof findAllPreviousVariables> ReturnType<typeof findAllPreviousVariables>
@ -118,9 +114,7 @@ export function useCalc({
inputRef.current && inputRef.current &&
inputRef.current.setSelectionRange(0, String(value).length) inputRef.current.setSelectionRange(0, String(value).length)
}, 100) }, 100)
if (ast) { setNewVariableName(findUniqueName(kclManager.ast, valueName))
setNewVariableName(findUniqueName(ast, valueName))
}
}, []) }, [])
useEffect(() => { useEffect(() => {
@ -133,10 +127,14 @@ export function useCalc({
}, [newVariableName]) }, [newVariableName])
useEffect(() => { useEffect(() => {
if (!ast || !programMemory || !selectionRange) return if (!programMemory || !selectionRange) return
const varInfo = findAllPreviousVariables(ast, programMemory, selectionRange) const varInfo = findAllPreviousVariables(
kclManager.ast,
programMemory,
selectionRange
)
setAvailableVarInfo(varInfo) setAvailableVarInfo(varInfo)
}, [ast, programMemory, selectionRange]) }, [kclManager.ast, programMemory, selectionRange])
useEffect(() => { useEffect(() => {
try { try {
@ -146,9 +144,13 @@ export function useCalc({
availableVarInfo.variables.forEach(({ key, value }) => { availableVarInfo.variables.forEach(({ key, value }) => {
_programMem.root[key] = { type: 'userVal', value, __meta: [] } _programMem.root[key] = { type: 'userVal', value, __meta: [] }
}) })
if (!defaultPlanes) return
executor(ast, _programMem, engineCommandManager, defaultPlanes!).then( executor(
(programMemory) => { ast,
_programMem,
engineCommandManager,
kclManager.defaultPlanes
).then((programMemory) => {
const resultDeclaration = ast.body.find( const resultDeclaration = ast.body.find(
(a) => (a) =>
a.type === 'VariableDeclaration' && a.type === 'VariableDeclaration' &&
@ -160,8 +162,7 @@ export function useCalc({
const result = programMemory?.root?.__result__?.value const result = programMemory?.root?.__result__?.value
setCalcResult(typeof result === 'number' ? String(result) : 'NAN') setCalcResult(typeof result === 'number' ? String(result) : 'NAN')
init && setValueNode(init) init && setValueNode(init)
} })
)
} catch (e) { } catch (e) {
setCalcResult('NAN') setCalcResult('NAN')
setValueNode(null) setValueNode(null)

View File

@ -5,16 +5,13 @@ import {
faEllipsis, faEllipsis,
} from '@fortawesome/free-solid-svg-icons' } from '@fortawesome/free-solid-svg-icons'
import { ActionIcon } from './ActionIcon' import { ActionIcon } from './ActionIcon'
import { useStore } from 'useStore'
import styles from './CodeMenu.module.css' import styles from './CodeMenu.module.css'
import { useConvertToVariable } from 'hooks/useToolbarGuards' import { useConvertToVariable } from 'hooks/useToolbarGuards'
import { editorShortcutMeta } from './TextEditor' import { editorShortcutMeta } from './TextEditor'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { kclManager } from 'lang/KclSinglton'
export const CodeMenu = ({ children }: PropsWithChildren) => { export const CodeMenu = ({ children }: PropsWithChildren) => {
const { formatCode } = useStore((s) => ({
formatCode: s.formatCode,
}))
const { enable: convertToVarEnabled, handleClick: handleConvertToVarClick } = const { enable: convertToVarEnabled, handleClick: handleConvertToVarClick } =
useConvertToVariable() useConvertToVariable()
@ -41,7 +38,10 @@ export const CodeMenu = ({ children }: PropsWithChildren) => {
</Menu.Button> </Menu.Button>
<Menu.Items className="absolute right-0 left-auto w-72 flex flex-col gap-1 divide-y divide-chalkboard-20 dark:divide-chalkboard-70 align-stretch px-0 py-1 bg-chalkboard-10 dark:bg-chalkboard-90 rounded-sm shadow-lg border border-solid border-chalkboard-20/50 dark:border-chalkboard-80/50"> <Menu.Items className="absolute right-0 left-auto w-72 flex flex-col gap-1 divide-y divide-chalkboard-20 dark:divide-chalkboard-70 align-stretch px-0 py-1 bg-chalkboard-10 dark:bg-chalkboard-90 rounded-sm shadow-lg border border-solid border-chalkboard-20/50 dark:border-chalkboard-80/50">
<Menu.Item> <Menu.Item>
<button onClick={() => formatCode()} className={styles.button}> <button
onClick={() => kclManager.format()}
className={styles.button}
>
<span>Format code</span> <span>Format code</span>
<small>{editorShortcutMeta.formatCode.display}</small> <small>{editorShortcutMeta.formatCode.display}</small>
</button> </button>

View File

@ -1,8 +1,8 @@
import ReactJson from 'react-json-view' import ReactJson from 'react-json-view'
import { useEffect } from 'react' import { useEffect } from 'react'
import { useStore } from '../useStore'
import { CollapsiblePanel, CollapsiblePanelProps } from './CollapsiblePanel' import { CollapsiblePanel, CollapsiblePanelProps } from './CollapsiblePanel'
import { Themes } from '../lib/theme' import { Themes } from '../lib/theme'
import { useKclContext } from 'lang/KclSinglton'
const ReactJsonTypeHack = ReactJson as any const ReactJsonTypeHack = ReactJson as any
@ -11,9 +11,7 @@ interface LogPanelProps extends CollapsiblePanelProps {
} }
export const Logs = ({ theme = Themes.Light, ...props }: LogPanelProps) => { export const Logs = ({ theme = Themes.Light, ...props }: LogPanelProps) => {
const { logs } = useStore(({ logs }) => ({ const { logs } = useKclContext()
logs,
}))
useEffect(() => { useEffect(() => {
const element = document.querySelector('.console-tile') const element = document.querySelector('.console-tile')
if (element) { if (element) {
@ -47,21 +45,19 @@ export const KCLErrors = ({
theme = Themes.Light, theme = Themes.Light,
...props ...props
}: LogPanelProps) => { }: LogPanelProps) => {
const { kclErrors } = useStore(({ kclErrors }) => ({ const { errors } = useKclContext()
kclErrors,
}))
useEffect(() => { useEffect(() => {
const element = document.querySelector('.console-tile') const element = document.querySelector('.console-tile')
if (element) { if (element) {
element.scrollTop = element.scrollHeight - element.clientHeight element.scrollTop = element.scrollHeight - element.clientHeight
} }
}, [kclErrors]) }, [errors])
return ( return (
<CollapsiblePanel {...props}> <CollapsiblePanel {...props}>
<div className="h-full relative"> <div className="h-full relative">
<div className="absolute inset-0 flex flex-col"> <div className="absolute inset-0 flex flex-col">
<ReactJsonTypeHack <ReactJsonTypeHack
src={kclErrors} src={errors}
collapsed={1} collapsed={1}
collapseStringsAfterLength={60} collapseStringsAfterLength={60}
enableClipboard={false} enableClipboard={false}

View File

@ -1,9 +1,9 @@
import ReactJson from 'react-json-view' import ReactJson from 'react-json-view'
import { CollapsiblePanel, CollapsiblePanelProps } from './CollapsiblePanel' import { CollapsiblePanel, CollapsiblePanelProps } from './CollapsiblePanel'
import { useStore } from '../useStore'
import { useMemo } from 'react' import { useMemo } from 'react'
import { ProgramMemory, Path, ExtrudeSurface } from '../lang/wasm' import { ProgramMemory, Path, ExtrudeSurface } from '../lang/wasm'
import { Themes } from '../lib/theme' import { Themes } from '../lib/theme'
import { useKclContext } from 'lang/KclSinglton'
interface MemoryPanelProps extends CollapsiblePanelProps { interface MemoryPanelProps extends CollapsiblePanelProps {
theme?: Exclude<Themes, Themes.System> theme?: Exclude<Themes, Themes.System>
@ -13,9 +13,7 @@ export const MemoryPanel = ({
theme = Themes.Light, theme = Themes.Light,
...props ...props
}: MemoryPanelProps) => { }: MemoryPanelProps) => {
const { programMemory } = useStore((s) => ({ const { programMemory } = useKclContext()
programMemory: s.programMemory,
}))
const ProcessedMemory = useMemo( const ProcessedMemory = useMemo(
() => processMemory(programMemory), () => processMemory(programMemory),
[programMemory] [programMemory]

View File

@ -0,0 +1,355 @@
import { useMachine } from '@xstate/react'
import React, { createContext, useEffect, useRef } from 'react'
import {
AnyStateMachine,
ContextFrom,
InterpreterFrom,
Prop,
StateFrom,
assign,
} from 'xstate'
import { SetSelections, modelingMachine } from 'machines/modelingMachine'
import { useSetupEngineManager } from 'hooks/useSetupEngineManager'
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
import { isCursorInSketchCommandRange } from 'hooks/useAppMode'
import { engineCommandManager } from 'lang/std/engineConnection'
import { v4 as uuidv4 } from 'uuid'
import { addStartSketch } from 'lang/modifyAst'
import { roundOff } from 'lib/utils'
import { recast, parse, Program, VariableDeclarator } from 'lang/wasm'
import { getNodeFromPath } from 'lang/queryAst'
import {
addCloseToPipe,
addNewSketchLn,
compareVec2Epsilon,
} from 'lang/std/sketch'
import { kclManager } from 'lang/KclSinglton'
import { applyConstraintHorzVertDistance } from './Toolbar/SetHorzVertDistance'
import { applyConstraintAngleBetween } from './Toolbar/SetAngleBetween'
import { applyConstraintAngleLength } from './Toolbar/setAngleLength'
import { toast } from 'react-hot-toast'
import { pathMapToSelections } from 'lang/util'
import {
dispatchCodeMirrorCursor,
setCodeMirrorCursor,
useStore,
} from 'useStore'
type MachineContext<T extends AnyStateMachine> = {
state: StateFrom<T>
context: ContextFrom<T>
send: Prop<InterpreterFrom<T>, 'send'>
}
export const ModelingMachineContext = createContext(
{} as MachineContext<typeof modelingMachine>
)
export const ModelingMachineProvider = ({
children,
}: {
children: React.ReactNode
}) => {
const { auth } = useGlobalStateContext()
const token = auth?.context?.token
const streamRef = useRef<HTMLDivElement>(null)
useSetupEngineManager(streamRef, token)
const { isShiftDown, editorView } = useStore((s) => ({
isShiftDown: s.isShiftDown,
editorView: s.editorView,
}))
// const { commands } = useCommandsContext()
// Settings machine setup
// const retrievedSettings = useRef(
// localStorage?.getItem(MODELING_PERSIST_KEY) || '{}'
// )
// What should we persist from modeling state? Nothing?
// const persistedSettings = Object.assign(
// settingsMachine.initialState.context,
// JSON.parse(retrievedSettings.current) as Partial<
// (typeof settingsMachine)['context']
// >
// )
const [modelingState, modelingSend] = useMachine(modelingMachine, {
// context: persistedSettings,
actions: {
'Modify AST': () => {},
'Update code selection cursors': () => {},
'show default planes': () => {
kclManager.showPlanes()
},
'create path': async () => {
const sketchUuid = uuidv4()
const proms = [
engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: sketchUuid,
cmd: {
type: 'start_path',
},
}),
engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'edit_mode_enter',
target: sketchUuid,
},
}),
]
await Promise.all(proms)
},
'AST start new sketch': assign((_, { data: { coords, axis } }) => {
// Something really weird must have happened for this to happen.
if (!axis) {
console.error('axis is undefined for starting a new sketch')
return {}
}
const _addStartSketch = addStartSketch(
kclManager.ast,
axis,
[roundOff(coords[0].x), roundOff(coords[0].y)],
[
roundOff(coords[1].x - coords[0].x),
roundOff(coords[1].y - coords[0].y),
]
)
const _modifiedAst = _addStartSketch.modifiedAst
const _pathToNode = _addStartSketch.pathToNode
const newCode = recast(_modifiedAst)
const astWithUpdatedSource = parse(newCode)
kclManager.executeAstMock(astWithUpdatedSource, true)
return {
sketchPathToNode: _pathToNode,
}
}),
'AST add line segment': ({ sketchPathToNode }, { data: { coords } }) => {
if (!sketchPathToNode) return
const lastCoord = coords[coords.length - 1]
const { node: varDec } = getNodeFromPath<VariableDeclarator>(
kclManager.ast,
sketchPathToNode,
'VariableDeclarator'
)
const variableName = varDec.id.name
const sketchGroup = kclManager.programMemory.root[variableName]
if (!sketchGroup || sketchGroup.type !== 'SketchGroup') return
const initialCoords = sketchGroup.value[0].from
const isClose = compareVec2Epsilon(initialCoords, [
lastCoord.x,
lastCoord.y,
])
let _modifiedAst: Program
if (!isClose) {
_modifiedAst = addNewSketchLn({
node: kclManager.ast,
programMemory: kclManager.programMemory,
to: [lastCoord.x, lastCoord.y],
fnName: 'line',
pathToNode: sketchPathToNode,
}).modifiedAst
kclManager.executeAstMock(_modifiedAst, true)
// kclManager.updateAst(_modifiedAst, false)
} else {
_modifiedAst = addCloseToPipe({
node: kclManager.ast,
programMemory: kclManager.programMemory,
pathToNode: sketchPathToNode,
})
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' },
})
kclManager.executeAstMock(_modifiedAst, true)
// updateAst(_modifiedAst, true)
}
},
'sketch exit execute': () => {
kclManager.executeAst()
},
'set tool': () => {}, // TODO
'toast extrude failed': () => {
toast.error(
'Extrude failed, sketches need to be closed, or not already extruded'
)
},
'Set selection': assign(({ selectionRanges }, event) => {
if (event.type !== 'Set selection') return {} // this was needed for ts after adding 'Set selection' action to on done modal events
const setSelections = event.data
if (setSelections.selectionType === 'mirrorCodeMirrorSelections')
return { selectionRanges: setSelections.selection }
else if (setSelections.selectionType === 'otherSelection')
return {
selectionRanges: {
...selectionRanges,
otherSelections: [setSelections.selection],
},
}
else if (!editorView) return {}
else if (setSelections.selectionType === 'singleCodeCursor') {
// This DOES NOT set the `selectionRanges` in xstate context
// instead it updates/dispatches to the editor, which in turn updates the xstate context
// I've found this the best way to deal with the editor without causing an infinite loop
// and really we want the editor to be in charge of cursor positions and for `selectionRanges` mirror it
// because we want to respect the user manually placing the cursor too.
const selectionRangeTypeMap = setCodeMirrorCursor({
codeSelection: setSelections.selection,
currestSelections: selectionRanges,
editorView,
isShiftDown,
})
return {
selectionRangeTypeMap,
}
}
// This DOES NOT set the `selectionRanges` in xstate context
// same as comment above
const selectionRangeTypeMap = dispatchCodeMirrorCursor({
selections: setSelections.selection,
editorView,
})
return {
selectionRangeTypeMap,
}
}),
},
guards: {
'Selection contains axis': () => true,
'Selection contains edge': () => true,
'Selection contains face': () => true,
'Selection contains line': () => true,
'Selection contains point': () => true,
'Selection is not empty': () => true,
'Selection is one face': ({ selectionRanges }) => {
return !!isCursorInSketchCommandRange(
engineCommandManager.artifactMap,
selectionRanges
)
},
},
services: {
'Get horizontal info': async ({
selectionRanges,
}): Promise<SetSelections> => {
const { modifiedAst, pathToNodeMap } =
await applyConstraintHorzVertDistance({
constraint: 'setHorzDistance',
selectionRanges,
})
await kclManager.updateAst(modifiedAst, true)
return {
selectionType: 'completeSelection',
selection: pathMapToSelections(
kclManager.ast,
selectionRanges,
pathToNodeMap
),
}
},
'Get vertical info': async ({
selectionRanges,
}): Promise<SetSelections> => {
const { modifiedAst, pathToNodeMap } =
await applyConstraintHorzVertDistance({
constraint: 'setVertDistance',
selectionRanges,
})
await kclManager.updateAst(modifiedAst, true)
return {
selectionType: 'completeSelection',
selection: pathMapToSelections(
kclManager.ast,
selectionRanges,
pathToNodeMap
),
}
},
'Get angle info': async ({ selectionRanges }): Promise<SetSelections> => {
const { modifiedAst, pathToNodeMap } =
await applyConstraintAngleBetween({
selectionRanges,
})
await kclManager.updateAst(modifiedAst, true)
return {
selectionType: 'completeSelection',
selection: pathMapToSelections(
kclManager.ast,
selectionRanges,
pathToNodeMap
),
}
},
'Get length info': async ({
selectionRanges,
}): Promise<SetSelections> => {
const { modifiedAst, pathToNodeMap } = await applyConstraintAngleLength(
{ selectionRanges }
)
await kclManager.updateAst(modifiedAst, true)
return {
selectionType: 'completeSelection',
selection: pathMapToSelections(
kclManager.ast,
selectionRanges,
pathToNodeMap
),
}
},
},
devTools: true,
})
useEffect(() => {
engineCommandManager.onPlaneSelected((plane_id: string) => {
if (modelingState.nextEvents.includes('Select default plane')) {
modelingSend({
type: 'Select default plane',
data: { planeId: plane_id },
})
}
})
}, [kclManager.defaultPlanes, modelingSend, modelingState.nextEvents])
// useStateMachineCommands({
// state: settingsState,
// send: settingsSend,
// commands,
// owner: 'settings',
// commandBarMeta: settingsCommandBarMeta,
// })
return (
<ModelingMachineContext.Provider
value={{
state: modelingState,
context: modelingState.context,
send: modelingSend,
}}
>
{/* TODO #818: maybe pass reff down to children/app.ts or render app.tsx directly?
since realistically it won't ever have generic children that isn't app.tsx */}
<div className="h-screen overflow-hidden select-none" ref={streamRef}>
{children}
</div>
</ModelingMachineContext.Provider>
)
}
export default ModelingMachineProvider

View File

@ -7,28 +7,17 @@ import {
} from 'react' } from 'react'
import { v4 as uuidv4 } from 'uuid' import { v4 as uuidv4 } from 'uuid'
import { useStore } from '../useStore' import { useStore } from '../useStore'
import { getNormalisedCoordinates, roundOff } from '../lib/utils' import { getNormalisedCoordinates } from '../lib/utils'
import Loading from './Loading' import Loading from './Loading'
import { cameraMouseDragGuards } from 'lib/cameraControls' import { cameraMouseDragGuards } from 'lib/cameraControls'
import { useGlobalStateContext } from 'hooks/useGlobalStateContext' import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
import { CameraDragInteractionType_type } from '@kittycad/lib/dist/types/src/models' import { CameraDragInteractionType_type } from '@kittycad/lib/dist/types/src/models'
import { Models } from '@kittycad/lib' import { Models } from '@kittycad/lib'
import { addStartSketch } from 'lang/modifyAst'
import {
addCloseToPipe,
addNewSketchLn,
compareVec2Epsilon,
} from 'lang/std/sketch'
import { getNodeFromPath } from 'lang/queryAst' import { getNodeFromPath } from 'lang/queryAst'
import { import { Program, VariableDeclarator, modifyAstForSketch } from 'lang/wasm'
Program,
VariableDeclarator,
rangeTypeFix,
modifyAstForSketch,
} from 'lang/wasm'
import { KCLError } from 'lang/errors'
import { KclError as RustKclError } from '../wasm-lib/kcl/bindings/KclError'
import { engineCommandManager } from '../lang/std/engineConnection' import { engineCommandManager } from '../lang/std/engineConnection'
import { useModelingContext } from 'hooks/useModelingContext'
import { kclManager, useKclContext } from 'lang/KclSinglton'
export const Stream = ({ className = '' }) => { export const Stream = ({ className = '' }) => {
const [isLoading, setIsLoading] = useState(true) const [isLoading, setIsLoading] = useState(true)
@ -40,35 +29,17 @@ export const Stream = ({ className = '' }) => {
didDragInStream, didDragInStream,
setDidDragInStream, setDidDragInStream,
streamDimensions, streamDimensions,
isExecuting,
guiMode,
ast,
updateAst,
setGuiMode,
programMemory,
defaultPlanes,
currentPlane,
} = useStore((s) => ({ } = useStore((s) => ({
mediaStream: s.mediaStream, mediaStream: s.mediaStream,
setButtonDownInStream: s.setButtonDownInStream, setButtonDownInStream: s.setButtonDownInStream,
fileId: s.fileId,
didDragInStream: s.didDragInStream, didDragInStream: s.didDragInStream,
setDidDragInStream: s.setDidDragInStream, setDidDragInStream: s.setDidDragInStream,
streamDimensions: s.streamDimensions, streamDimensions: s.streamDimensions,
isExecuting: s.isExecuting,
guiMode: s.guiMode,
ast: s.ast,
updateAst: s.updateAst,
setGuiMode: s.setGuiMode,
programMemory: s.programMemory,
defaultPlanes: s.defaultPlanes,
currentPlane: s.currentPlane,
})) }))
const { const { settings } = useGlobalStateContext()
settings: { const cameraControls = settings?.context?.cameraControls
context: { cameraControls }, const { send, state, context } = useModelingContext()
}, const { isExecuting } = useKclContext()
} = useGlobalStateContext()
useEffect(() => { useEffect(() => {
if ( if (
@ -112,7 +83,7 @@ export const Stream = ({ className = '' }) => {
interaction = 'zoom' interaction = 'zoom'
} }
if (guiMode.mode === 'sketch' && guiMode.sketchMode === ('move' as any)) { if (state.matches('Sketch.Move Tool')) {
engineCommandManager.sendSceneCommand({ engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req', type: 'modeling_cmd_req',
cmd: { cmd: {
@ -121,12 +92,7 @@ export const Stream = ({ className = '' }) => {
}, },
cmd_id: newId, cmd_id: newId,
}) })
} else if ( } else if (!state.matches('Sketch.Line Tool')) {
!(
guiMode.mode === 'sketch' &&
guiMode.sketchMode === ('sketch_line' as any)
)
) {
engineCommandManager.sendSceneCommand({ engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req', type: 'modeling_cmd_req',
cmd: { cmd: {
@ -182,127 +148,34 @@ export const Stream = ({ className = '' }) => {
cmd_id: newCmdId, cmd_id: newCmdId,
} }
if (!didDragInStream) { if (!didDragInStream && state.matches('Sketch no face')) {
engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd: {
type: 'select_with_point',
selection_type: 'add',
selected_at_window: { x, y },
},
cmd_id: uuidv4(),
})
}
if (!didDragInStream && guiMode.mode === 'default') {
command.cmd = { command.cmd = {
type: 'select_with_point', type: 'select_with_point',
selection_type: 'add', selection_type: 'add',
selected_at_window: { x, y }, selected_at_window: { x, y },
} }
} else if ( engineCommandManager.sendSceneCommand(command)
(!didDragInStream && } else if (!didDragInStream && state.matches('Sketch.Line Tool')) {
guiMode.mode === 'sketch' &&
['move', 'select'].includes(guiMode.sketchMode)) ||
(guiMode.mode === 'sketch' &&
guiMode.sketchMode === ('sketch_line' as any))
) {
command.cmd = { command.cmd = {
type: 'mouse_click', type: 'mouse_click',
window: { x, y }, window: { x, y },
} }
} else if (
guiMode.mode === 'sketch' &&
guiMode.sketchMode === ('move' as any)
) {
command.cmd = {
type: 'handle_mouse_drag_end',
window: { x, y },
}
}
engineCommandManager.sendSceneCommand(command).then(async (resp) => { engineCommandManager.sendSceneCommand(command).then(async (resp) => {
if (!(guiMode.mode === 'sketch')) return const entities_modified = resp?.data?.data?.entities_modified
if (!entities_modified) return
if (guiMode.sketchMode === 'selectFace') return if (state.matches('Sketch.Line Tool.No Points')) {
send('Add point')
// Check if the sketch group already exists. } else if (state.matches('Sketch.Line Tool.Point Added')) {
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
// Get the current plane string for plane we are on.
let currentPlaneString = ''
if (currentPlane === defaultPlanes?.xy) {
currentPlaneString = 'XY'
} else if (currentPlane === defaultPlanes?.yz) {
currentPlaneString = 'YZ'
} else if (currentPlane === defaultPlanes?.xz) {
currentPlaneString = 'XZ'
}
// Do not supporting editing/moving lines on a non-default plane.
// Eventually we can support this but for now we will just throw an
// error.
if (currentPlaneString === '') return
const updatedAst: Program = await modifyAstForSketch(
engineCommandManager,
ast,
variableName,
currentPlaneString,
engineId
)
updateAst(updatedAst, false)
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({ const curve = await engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req', type: 'modeling_cmd_req',
cmd_id: uuidv4(), cmd_id: uuidv4(),
cmd: { cmd: {
type: 'curve_get_control_points', type: 'curve_get_control_points',
curve_id: resp?.data?.data?.entities_modified[0], curve_id: entities_modified[0],
}, },
}) })
const coords: { x: number; y: number }[] = const coords: { x: number; y: number }[] =
curve.data.data.control_points curve.data.data.control_points
// We need the normal for the plane we are on. // We need the normal for the plane we are on.
const plane = await engineCommandManager.sendSceneCommand({ const plane = await engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req', type: 'modeling_cmd_req',
@ -316,19 +189,19 @@ export const Stream = ({ className = '' }) => {
// Get the current axis. // Get the current axis.
let currentAxis: 'xy' | 'xz' | 'yz' | '-xy' | '-xz' | '-yz' | null = let currentAxis: 'xy' | 'xz' | 'yz' | '-xy' | '-xz' | '-yz' | null =
null null
if (currentPlane === defaultPlanes?.xy) { if (context.sketchPlaneId === kclManager.getPlaneId('xy')) {
if (z_axis.z === -1) { if (z_axis.z === -1) {
currentAxis = '-xy' currentAxis = '-xy'
} else { } else {
currentAxis = 'xy' currentAxis = 'xy'
} }
} else if (currentPlane === defaultPlanes?.yz) { } else if (context.sketchPlaneId === kclManager.getPlaneId('yz')) {
if (z_axis.x === -1) { if (z_axis.x === -1) {
currentAxis = '-yz' currentAxis = '-yz'
} else { } else {
currentAxis = 'yz' currentAxis = 'yz'
} }
} else if (currentPlane === defaultPlanes?.xz) { } else if (context.sketchPlaneId === kclManager.getPlaneId('xz')) {
if (z_axis.y === -1) { if (z_axis.y === -1) {
currentAxis = '-xz' currentAxis = '-xz'
} else { } else {
@ -336,100 +209,80 @@ export const Stream = ({ className = '' }) => {
} }
} }
// Do not support starting a new sketch on a non-default plane. send({ type: 'Add point', data: { coords, axis: currentAxis } })
if (!currentAxis) return } else if (state.matches('Sketch.Line Tool.Segment Added')) {
const _addStartSketch = addStartSketch(
ast,
currentAxis,
[roundOff(coords[0].x), roundOff(coords[0].y)],
[
roundOff(coords[1].x - coords[0].x),
roundOff(coords[1].y - coords[0].y),
]
)
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, false)
} else if (
resp?.data?.data?.entities_modified?.length &&
(!guiMode.waitingFirstClick || isEditingExistingSketch)
) {
const curve = await engineCommandManager.sendSceneCommand({ const curve = await engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req', type: 'modeling_cmd_req',
cmd_id: uuidv4(), cmd_id: uuidv4(),
cmd: { cmd: {
type: 'curve_get_control_points', type: 'curve_get_control_points',
curve_id: resp?.data?.data?.entities_modified[0], curve_id: entities_modified[0],
}, },
}) })
const coords: { x: number; y: number }[] = const coords: { x: number; y: number }[] =
curve.data.data.control_points curve.data.data.control_points
send({ type: 'Add point', data: { coords, axis: null } })
const { node: varDec } = getNodeFromPath<VariableDeclarator>( }
ast, })
guiMode.pathToNode, } else if (
!didDragInStream &&
(state.matches('Sketch.SketchIdle') ||
state.matches('idle') ||
state.matches('awaiting selection'))
) {
command.cmd = {
type: 'select_with_point',
selected_at_window: { x, y },
selection_type: 'add',
}
engineCommandManager.sendSceneCommand(command)
} else if (!didDragInStream && state.matches('Sketch.Move Tool')) {
command.cmd = {
type: 'select_with_point',
selected_at_window: { x, y },
selection_type: 'add',
}
engineCommandManager.sendSceneCommand(command)
} else if (didDragInStream && state.matches('Sketch.Move Tool')) {
command.cmd = {
type: 'handle_mouse_drag_end',
window: { x, y },
}
engineCommandManager.sendSceneCommand(command).then(async () => {
if (!context.sketchPathToNode) return
const varDec = getNodeFromPath<VariableDeclarator>(
kclManager.ast,
context.sketchPathToNode,
'VariableDeclarator' 'VariableDeclarator'
).node
const variableName = varDec?.id?.name
// Get the current plane string for plane we are on.
let currentPlaneString = ''
if (context.sketchPlaneId === kclManager.getPlaneId('xy')) {
currentPlaneString = 'XY'
} else if (context.sketchPlaneId === kclManager.getPlaneId('yz')) {
currentPlaneString = 'YZ'
} else if (context.sketchPlaneId === kclManager.getPlaneId('xz')) {
currentPlaneString = 'XZ'
}
// Do not supporting editing/moving lines on a non-default plane.
// Eventually we can support this but for now we will just throw an
// error.
if (currentPlaneString === '') return
const updatedAst: Program = await modifyAstForSketch(
engineCommandManager,
kclManager.ast,
variableName,
currentPlaneString,
context.sketchEnginePathId
) )
const variableName = varDec.id.name kclManager.executeAstMock(updatedAst, true)
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) setDidDragInStream(false)
setClickCoords(undefined) setClickCoords(undefined)
} }
@ -460,8 +313,8 @@ export const Stream = ({ className = '' }) => {
onWheel={handleScroll} onWheel={handleScroll}
onPlay={() => setIsLoading(false)} onPlay={() => setIsLoading(false)}
onMouseMoveCapture={handleMouseMove} onMouseMoveCapture={handleMouseMove}
className={`w-full cursor-pointer h-full ${isExecuting && 'blur-md'}`}
disablePictureInPicture disablePictureInPicture
className={`w-full h-full ${isExecuting && 'blur-md'}`}
style={{ transitionDuration: '200ms', transitionProperty: 'filter' }} style={{ transitionDuration: '200ms', transitionProperty: 'filter' }}
/> />
{isLoading && ( {isLoading && (

View File

@ -11,7 +11,7 @@ import { useCommandsContext } from 'hooks/useCommandsContext'
import { useGlobalStateContext } from 'hooks/useGlobalStateContext' import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
import { useConvertToVariable } from 'hooks/useToolbarGuards' import { useConvertToVariable } from 'hooks/useToolbarGuards'
import { Themes } from 'lib/theme' import { Themes } from 'lib/theme'
import { useMemo } from 'react' import { useMemo, useState } from 'react'
import { linter, lintGutter } from '@codemirror/lint' import { linter, lintGutter } from '@codemirror/lint'
import { Selections, useStore } from 'useStore' import { Selections, useStore } from 'useStore'
import { LanguageServerClient } from 'editor/lsp' import { LanguageServerClient } from 'editor/lsp'
@ -29,8 +29,10 @@ import {
import { isOverlap, roundOff } from 'lib/utils' import { isOverlap, roundOff } from 'lib/utils'
import { kclErrToDiagnostic } from 'lang/errors' import { kclErrToDiagnostic } from 'lang/errors'
import { CSSRuleObject } from 'tailwindcss/types/config' import { CSSRuleObject } from 'tailwindcss/types/config'
import { useModelingContext } from 'hooks/useModelingContext'
import interact from '@replit/codemirror-interact' import interact from '@replit/codemirror-interact'
import { engineCommandManager } from '../lang/std/engineConnection' import { engineCommandManager } from '../lang/std/engineConnection'
import { kclManager, useKclContext } from 'lang/KclSinglton'
export const editorShortcutMeta = { export const editorShortcutMeta = {
formatCode: { formatCode: {
@ -49,35 +51,22 @@ export const TextEditor = ({
theme: Themes.Light | Themes.Dark theme: Themes.Light | Themes.Dark
}) => { }) => {
const pathParams = useParams() const pathParams = useParams()
const { const { editorView, isLSPServerReady, setEditorView, setIsLSPServerReady } =
code, useStore((s) => ({
deferredSetCode,
editorView,
formatCode,
isLSPServerReady,
selectionRanges,
selectionRangeTypeMap,
setEditorView,
setIsLSPServerReady,
setSelectionRanges,
} = useStore((s) => ({
code: s.code,
deferredSetCode: s.deferredSetCode,
editorView: s.editorView, editorView: s.editorView,
formatCode: s.formatCode,
isLSPServerReady: s.isLSPServerReady, isLSPServerReady: s.isLSPServerReady,
selectionRanges: s.selectionRanges,
selectionRangeTypeMap: s.selectionRangeTypeMap,
setEditorView: s.setEditorView, setEditorView: s.setEditorView,
setIsLSPServerReady: s.setIsLSPServerReady, setIsLSPServerReady: s.setIsLSPServerReady,
setSelectionRanges: s.setSelectionRanges,
})) }))
const { code, errors } = useKclContext()
const { const {
settings: { context: { selectionRanges, selectionRangeTypeMap },
context: { textWrapping }, send,
}, } = useModelingContext()
} = useGlobalStateContext()
const { settings: { context: { textWrapping } = {} } = {} } =
useGlobalStateContext()
const { setCommandBarOpen } = useCommandsContext() const { setCommandBarOpen } = useCommandsContext()
const { enable: convertEnabled, handleClick: convertCallback } = const { enable: convertEnabled, handleClick: convertCallback } =
useConvertToVariable() useConvertToVariable()
@ -122,12 +111,12 @@ export const TextEditor = ({
}, [lspClient, isLSPServerReady]) }, [lspClient, isLSPServerReady])
// const onChange = React.useCallback((value: string, viewUpdate: ViewUpdate) => { // const onChange = React.useCallback((value: string, viewUpdate: ViewUpdate) => {
const onChange = (value: string, viewUpdate: ViewUpdate) => { const onChange = (newCode: string, viewUpdate: ViewUpdate) => {
deferredSetCode(value) kclManager.setCodeAndExecute(newCode)
if (isTauri() && pathParams.id) { if (isTauri() && pathParams.id) {
// Save the file to disk // Save the file to disk
// Note that PROJECT_ENTRYPOINT is hardcoded until we support multiple files // Note that PROJECT_ENTRYPOINT is hardcoded until we support multiple files
writeTextFile(pathParams.id + '/' + PROJECT_ENTRYPOINT, value).catch( writeTextFile(pathParams.id + '/' + PROJECT_ENTRYPOINT, newCode).catch(
(err) => { (err) => {
// TODO: add Sentry per GH issue #254 (https://github.com/KittyCAD/modeling-app/issues/254) // TODO: add Sentry per GH issue #254 (https://github.com/KittyCAD/modeling-app/issues/254)
console.error('error saving file', err) console.error('error saving file', err)
@ -190,9 +179,16 @@ export const TextEditor = ({
idBasedSelections, idBasedSelections,
}) })
setSelectionRanges({ selectionRanges &&
otherSelections: [], send({
type: 'Set selection',
data: {
selectionType: 'mirrorCodeMirrorSelections',
selection: {
...selectionRanges,
codeBasedSelections, codeBasedSelections,
},
},
}) })
} }
@ -210,7 +206,7 @@ export const TextEditor = ({
{ {
key: editorShortcutMeta.formatCode.codeMirror, key: editorShortcutMeta.formatCode.codeMirror,
run: () => { run: () => {
formatCode() kclManager.format()
return true return true
}, },
}, },
@ -234,7 +230,7 @@ export const TextEditor = ({
extensions.push( extensions.push(
lintGutter(), lintGutter(),
linter((_view) => { linter((_view) => {
return kclErrToDiagnostic(useStore.getState().kclErrors) return kclErrToDiagnostic(errors)
}), }),
interact({ interact({
rules: [ rules: [

View File

@ -11,34 +11,30 @@ import {
transformSecondarySketchLinesTagFirst, transformSecondarySketchLinesTagFirst,
getTransformInfos, getTransformInfos,
} from '../../lang/std/sketchcombos' } from '../../lang/std/sketchcombos'
import { updateCursors } from '../../lang/util'
import { ActionIcon } from 'components/ActionIcon' import { ActionIcon } from 'components/ActionIcon'
import { sketchButtonClassnames } from 'Toolbar' import { sketchButtonClassnames } from 'Toolbar'
import { kclManager } from 'lang/KclSinglton'
/*
export const EqualAngle = () => { export const EqualAngle = () => {
const { guiMode, selectionRanges, ast, programMemory, updateAst, setCursor } = const { guiMode, selectionRanges, setCursor } = useStore((s) => ({
useStore((s) => ({
guiMode: s.guiMode, guiMode: s.guiMode,
ast: s.ast,
updateAst: s.updateAst,
selectionRanges: s.selectionRanges, selectionRanges: s.selectionRanges,
programMemory: s.programMemory,
setCursor: s.setCursor, setCursor: s.setCursor,
})) }))
const [enableEqual, setEnableEqual] = useState(false) const [enableEqual, setEnableEqual] = useState(false)
const [transformInfos, setTransformInfos] = useState<TransformInfo[]>() const [transformInfos, setTransformInfos] = useState<TransformInfo[]>()
useEffect(() => { useEffect(() => {
if (!ast) return
const paths = selectionRanges.codeBasedSelections.map(({ range }) => const paths = selectionRanges.codeBasedSelections.map(({ range }) =>
getNodePathFromSourceRange(ast, range) getNodePathFromSourceRange(kclManager.ast, range)
) )
const nodes = paths.map( const nodes = paths.map(
(pathToNode) => getNodeFromPath<Value>(ast, pathToNode).node (pathToNode) => getNodeFromPath<Value>(kclManager.ast, pathToNode).node
) )
const varDecs = paths.map( const varDecs = paths.map(
(pathToNode) => (pathToNode) =>
getNodeFromPath<VariableDeclarator>( getNodeFromPath<VariableDeclarator>(
ast, kclManager.ast,
pathToNode, pathToNode,
'VariableDeclarator' 'VariableDeclarator'
)?.node )?.node
@ -46,7 +42,7 @@ export const EqualAngle = () => {
const primaryLine = varDecs[0] const primaryLine = varDecs[0]
const secondaryVarDecs = varDecs.slice(1) const secondaryVarDecs = varDecs.slice(1)
const isOthersLinkedToPrimary = secondaryVarDecs.every((secondary) => const isOthersLinkedToPrimary = secondaryVarDecs.every((secondary) =>
isSketchVariablesLinked(secondary, primaryLine, ast) isSketchVariablesLinked(secondary, primaryLine, kclManager.ast)
) )
const isAllTooltips = nodes.every( const isAllTooltips = nodes.every(
(node) => (node) =>
@ -59,7 +55,7 @@ export const EqualAngle = () => {
...selectionRanges, ...selectionRanges,
codeBasedSelections: selectionRanges.codeBasedSelections.slice(1), codeBasedSelections: selectionRanges.codeBasedSelections.slice(1),
}, },
ast, kclManager.ast,
'equalAngle' 'equalAngle'
) )
setTransformInfos(theTransforms) setTransformInfos(theTransforms)
@ -76,15 +72,15 @@ export const EqualAngle = () => {
return ( return (
<button <button
onClick={async () => { onClick={async () => {
if (!(transformInfos && ast)) return if (!transformInfos) return
const { modifiedAst, pathToNodeMap } = const { modifiedAst, pathToNodeMap } =
transformSecondarySketchLinesTagFirst({ transformSecondarySketchLinesTagFirst({
ast, ast: kclManager.ast,
selectionRanges, selectionRanges,
transformInfos, transformInfos,
programMemory, programMemory: kclManager.programMemory,
}) })
updateAst(modifiedAst, true, { kclManager.updateAst(modifiedAst, true, {
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap), callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
}) })
}} }}
@ -103,3 +99,4 @@ export const EqualAngle = () => {
</button> </button>
) )
} }
*/

View File

@ -1,6 +1,6 @@
import { useState, useEffect } from 'react' import { useState, useEffect } from 'react'
import { toolTips, useStore } from '../../useStore' import { Selections, toolTips, useStore } from '../../useStore'
import { Value, VariableDeclarator } from '../../lang/wasm' import { Program, Value, VariableDeclarator } from '../../lang/wasm'
import { import {
getNodePathFromSourceRange, getNodePathFromSourceRange,
getNodeFromPath, getNodeFromPath,
@ -10,81 +10,41 @@ import {
TransformInfo, TransformInfo,
transformSecondarySketchLinesTagFirst, transformSecondarySketchLinesTagFirst,
getTransformInfos, getTransformInfos,
PathToNodeMap,
} from '../../lang/std/sketchcombos' } from '../../lang/std/sketchcombos'
import { updateCursors } from '../../lang/util'
import { ActionIcon } from 'components/ActionIcon' import { ActionIcon } from 'components/ActionIcon'
import { sketchButtonClassnames } from 'Toolbar' import { sketchButtonClassnames } from 'Toolbar'
import { kclManager } from 'lang/KclSinglton'
/*
export const EqualLength = () => { export const EqualLength = () => {
const { guiMode, selectionRanges, ast, programMemory, updateAst, setCursor } = const { guiMode, selectionRanges, setCursor } = useStore((s) => ({
useStore((s) => ({
guiMode: s.guiMode, guiMode: s.guiMode,
ast: s.ast,
updateAst: s.updateAst,
selectionRanges: s.selectionRanges, selectionRanges: s.selectionRanges,
programMemory: s.programMemory,
setCursor: s.setCursor, setCursor: s.setCursor,
})) }))
const [enableEqual, setEnableEqual] = useState(false) const [enableEqual, setEnableEqual] = useState(false)
const [transformInfos, setTransformInfos] = useState<TransformInfo[]>() const [transformInfos, setTransformInfos] = useState<TransformInfo[]>()
useEffect(() => { useEffect(() => {
if (!ast) return const { enabled, transforms } = setEqualLengthInfo({ selectionRanges })
const paths = selectionRanges.codeBasedSelections.map(({ range }) =>
getNodePathFromSourceRange(ast, range)
)
const nodes = paths.map(
(pathToNode) => getNodeFromPath<Value>(ast, pathToNode).node
)
const varDecs = paths.map(
(pathToNode) =>
getNodeFromPath<VariableDeclarator>(
ast,
pathToNode,
'VariableDeclarator'
)?.node
)
const primaryLine = varDecs[0]
const secondaryVarDecs = varDecs.slice(1)
const isOthersLinkedToPrimary = secondaryVarDecs.every((secondary) =>
isSketchVariablesLinked(secondary, primaryLine, ast)
)
const isAllTooltips = nodes.every(
(node) =>
node?.type === 'CallExpression' &&
toolTips.includes(node.callee.name as any)
)
const theTransforms = getTransformInfos( setTransformInfos(transforms)
{ setEnableEqual(enabled)
...selectionRanges,
codeBasedSelections: selectionRanges.codeBasedSelections.slice(1),
},
ast,
'equalLength'
)
setTransformInfos(theTransforms)
const _enableEqual =
!!secondaryVarDecs.length &&
isAllTooltips &&
isOthersLinkedToPrimary &&
theTransforms.every(Boolean)
setEnableEqual(_enableEqual)
}, [guiMode, selectionRanges]) }, [guiMode, selectionRanges])
if (guiMode.mode !== 'sketch') return null if (guiMode.mode !== 'sketch') return null
return ( return (
<button <button
onClick={() => { onClick={() => {
if (!(transformInfos && ast)) return if (!transformInfos) return
const { modifiedAst, pathToNodeMap } = const { modifiedAst, pathToNodeMap } =
transformSecondarySketchLinesTagFirst({ transformSecondarySketchLinesTagFirst({
ast, ast: kclManager.ast,
selectionRanges, selectionRanges,
transformInfos, transformInfos,
programMemory, programMemory: kclManager.programMemory,
}) })
updateAst(modifiedAst, true, { kclManager.updateAst(modifiedAst, true, {
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap), callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
}) })
}} }}
@ -103,3 +63,72 @@ export const EqualLength = () => {
</button> </button>
) )
} }
*/
export function setEqualLengthInfo({
selectionRanges,
}: {
selectionRanges: Selections
}) {
const paths = selectionRanges.codeBasedSelections.map(({ range }) =>
getNodePathFromSourceRange(kclManager.ast, range)
)
const nodes = paths.map(
(pathToNode) => getNodeFromPath<Value>(kclManager.ast, pathToNode).node
)
const varDecs = paths.map(
(pathToNode) =>
getNodeFromPath<VariableDeclarator>(
kclManager.ast,
pathToNode,
'VariableDeclarator'
)?.node
)
const primaryLine = varDecs[0]
const secondaryVarDecs = varDecs.slice(1)
const isOthersLinkedToPrimary = secondaryVarDecs.every((secondary) =>
isSketchVariablesLinked(secondary, primaryLine, kclManager.ast)
)
const isAllTooltips = nodes.every(
(node) =>
node?.type === 'CallExpression' &&
toolTips.includes(node.callee.name as any)
)
const transforms = getTransformInfos(
{
...selectionRanges,
codeBasedSelections: selectionRanges.codeBasedSelections.slice(1),
},
kclManager.ast,
'equalLength'
)
const enabled =
!!secondaryVarDecs.length &&
isAllTooltips &&
isOthersLinkedToPrimary &&
transforms.every(Boolean)
return { enabled, transforms }
}
export function applyConstraintEqualLength({
selectionRanges,
}: {
selectionRanges: Selections
}): {
modifiedAst: Program
pathToNodeMap: PathToNodeMap
} {
const { enabled, transforms } = setEqualLengthInfo({ selectionRanges })
const { modifiedAst, pathToNodeMap } = transformSecondarySketchLinesTagFirst({
ast: kclManager.ast,
selectionRanges,
transformInfos: transforms,
programMemory: kclManager.programMemory,
})
return { modifiedAst, pathToNodeMap }
// kclManager.updateAst(modifiedAst, true, {
// // callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
// })
}

View File

@ -1,69 +1,53 @@
import { useState, useEffect } from 'react' import { useState, useEffect } from 'react'
import { toolTips, useStore } from '../../useStore' import { toolTips, useStore } from '../../useStore'
import { Value } from '../../lang/wasm' import { Program, ProgramMemory, Value } from '../../lang/wasm'
import { import {
getNodePathFromSourceRange, getNodePathFromSourceRange,
getNodeFromPath, getNodeFromPath,
} from '../../lang/queryAst' } from '../../lang/queryAst'
import { import {
PathToNodeMap,
TransformInfo, TransformInfo,
getTransformInfos, getTransformInfos,
transformAstSketchLines, transformAstSketchLines,
} from '../../lang/std/sketchcombos' } from '../../lang/std/sketchcombos'
import { updateCursors } from '../../lang/util'
import { ActionIcon } from 'components/ActionIcon' import { ActionIcon } from 'components/ActionIcon'
import { sketchButtonClassnames } from 'Toolbar' import { sketchButtonClassnames } from 'Toolbar'
import { kclManager } from 'lang/KclSinglton'
import { Selections } from 'useStore'
/*
export const HorzVert = ({ export const HorzVert = ({
horOrVert, horOrVert,
}: { }: {
horOrVert: 'vertical' | 'horizontal' horOrVert: 'vertical' | 'horizontal'
}) => { }) => {
const { guiMode, selectionRanges, ast, programMemory, updateAst, setCursor } = const { guiMode, selectionRanges, setCursor } = useStore((s) => ({
useStore((s) => ({
guiMode: s.guiMode, guiMode: s.guiMode,
ast: s.ast,
updateAst: s.updateAst,
selectionRanges: s.selectionRanges, selectionRanges: s.selectionRanges,
programMemory: s.programMemory,
setCursor: s.setCursor, setCursor: s.setCursor,
})) }))
const [enableHorz, setEnableHorz] = useState(false) const [enableHorz, setEnableHorz] = useState(false)
const [transformInfos, setTransformInfos] = useState<TransformInfo[]>() const [transformInfos, setTransformInfos] = useState<TransformInfo[]>()
useEffect(() => { useEffect(() => {
if (!ast) return const { enabled, transforms } = horzVertInfo(selectionRanges, horOrVert)
const paths = selectionRanges.codeBasedSelections.map(({ range }) => setTransformInfos(transforms)
getNodePathFromSourceRange(ast, range) setEnableHorz(enabled)
)
const nodes = paths.map(
(pathToNode) => getNodeFromPath<Value>(ast, pathToNode).node
)
const isAllTooltips = nodes.every(
(node) =>
node?.type === 'CallExpression' &&
toolTips.includes(node.callee.name as any)
)
const theTransforms = getTransformInfos(selectionRanges, ast, horOrVert)
setTransformInfos(theTransforms)
const _enableHorz = isAllTooltips && theTransforms.every(Boolean)
setEnableHorz(_enableHorz)
}, [guiMode, selectionRanges]) }, [guiMode, selectionRanges])
if (guiMode.mode !== 'sketch') return null if (guiMode.mode !== 'sketch') return null
return ( return (
<button <button
onClick={() => { onClick={() => {
if (!transformInfos || !ast) return if (!transformInfos) return
const { modifiedAst, pathToNodeMap } = transformAstSketchLines({ const { modifiedAst, pathToNodeMap } = transformAstSketchLines({
ast, ast: kclManager.ast,
selectionRanges, selectionRanges,
transformInfos, transformInfos,
programMemory, programMemory: kclManager.programMemory,
referenceSegName: '', referenceSegName: '',
}) })
updateAst(modifiedAst, true, { kclManager.updateAst(modifiedAst, true, {
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap), callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
}) })
}} }}
@ -82,3 +66,51 @@ export const HorzVert = ({
</button> </button>
) )
} }
*/
export function horzVertInfo(
selectionRanges: Selections,
horOrVert: 'vertical' | 'horizontal'
) {
const paths = selectionRanges.codeBasedSelections.map(({ range }) =>
getNodePathFromSourceRange(kclManager.ast, range)
)
const nodes = paths.map(
(pathToNode) => getNodeFromPath<Value>(kclManager.ast, pathToNode).node
)
const isAllTooltips = nodes.every(
(node) =>
node?.type === 'CallExpression' &&
toolTips.includes(node.callee.name as any)
)
const theTransforms = getTransformInfos(
selectionRanges,
kclManager.ast,
horOrVert
)
const _enableHorz = isAllTooltips && theTransforms.every(Boolean)
return { enabled: _enableHorz, transforms: theTransforms }
}
export function applyConstraintHorzVert(
selectionRanges: Selections,
horOrVert: 'vertical' | 'horizontal',
ast: Program,
programMemory: ProgramMemory
): {
modifiedAst: Program
pathToNodeMap: PathToNodeMap
} {
const transformInfos = horzVertInfo(selectionRanges, horOrVert).transforms
return transformAstSketchLines({
ast,
selectionRanges,
transformInfos,
programMemory,
referenceSegName: '',
})
// kclManager.updateAst(modifiedAst, true, {
// callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
// })
}

View File

@ -16,18 +16,15 @@ import {
import { GetInfoModal } from '../SetHorVertDistanceModal' import { GetInfoModal } from '../SetHorVertDistanceModal'
import { createVariableDeclaration } from '../../lang/modifyAst' import { createVariableDeclaration } from '../../lang/modifyAst'
import { removeDoubleNegatives } from '../AvailableVarsHelpers' import { removeDoubleNegatives } from '../AvailableVarsHelpers'
import { updateCursors } from '../../lang/util' import { kclManager } from 'lang/KclSinglton'
const getModalInfo = create(GetInfoModal as any) const getModalInfo = create(GetInfoModal as any)
/*
export const Intersect = () => { export const Intersect = () => {
const { guiMode, selectionRanges, ast, programMemory, updateAst, setCursor } = const { guiMode, selectionRanges, setCursor } = useStore((s) => ({
useStore((s) => ({
guiMode: s.guiMode, guiMode: s.guiMode,
ast: s.ast,
updateAst: s.updateAst,
selectionRanges: s.selectionRanges, selectionRanges: s.selectionRanges,
programMemory: s.programMemory,
setCursor: s.setCursor, setCursor: s.setCursor,
})) }))
const [enable, setEnable] = useState(false) const [enable, setEnable] = useState(false)
@ -35,7 +32,6 @@ export const Intersect = () => {
const [forecdSelectionRanges, setForcedSelectionRanges] = const [forecdSelectionRanges, setForcedSelectionRanges] =
useState<typeof selectionRanges>() useState<typeof selectionRanges>()
useEffect(() => { useEffect(() => {
if (!ast) return
if (selectionRanges.codeBasedSelections.length < 2) { if (selectionRanges.codeBasedSelections.length < 2) {
setEnable(false) setEnable(false)
setForcedSelectionRanges({ ...selectionRanges }) setForcedSelectionRanges({ ...selectionRanges })
@ -45,8 +41,8 @@ export const Intersect = () => {
const previousSegment = const previousSegment =
selectionRanges.codeBasedSelections.length > 1 && selectionRanges.codeBasedSelections.length > 1 &&
isLinesParallelAndConstrained( isLinesParallelAndConstrained(
ast, kclManager.ast,
programMemory, kclManager.programMemory,
selectionRanges.codeBasedSelections[0], selectionRanges.codeBasedSelections[0],
selectionRanges.codeBasedSelections[1] selectionRanges.codeBasedSelections[1]
) )
@ -70,15 +66,15 @@ export const Intersect = () => {
setForcedSelectionRanges(_forcedSelectionRanges) setForcedSelectionRanges(_forcedSelectionRanges)
const paths = _forcedSelectionRanges.codeBasedSelections.map(({ range }) => const paths = _forcedSelectionRanges.codeBasedSelections.map(({ range }) =>
getNodePathFromSourceRange(ast, range) getNodePathFromSourceRange(kclManager.ast, range)
) )
const nodes = paths.map( const nodes = paths.map(
(pathToNode) => getNodeFromPath<Value>(ast, pathToNode).node (pathToNode) => getNodeFromPath<Value>(kclManager.ast, pathToNode).node
) )
const varDecs = paths.map( const varDecs = paths.map(
(pathToNode) => (pathToNode) =>
getNodeFromPath<VariableDeclarator>( getNodeFromPath<VariableDeclarator>(
ast, kclManager.ast,
pathToNode, pathToNode,
'VariableDeclarator' 'VariableDeclarator'
)?.node )?.node
@ -86,7 +82,7 @@ export const Intersect = () => {
const primaryLine = varDecs[0] const primaryLine = varDecs[0]
const secondaryVarDecs = varDecs.slice(1) const secondaryVarDecs = varDecs.slice(1)
const isOthersLinkedToPrimary = secondaryVarDecs.every((secondary) => const isOthersLinkedToPrimary = secondaryVarDecs.every((secondary) =>
isSketchVariablesLinked(secondary, primaryLine, ast) isSketchVariablesLinked(secondary, primaryLine, kclManager.ast)
) )
const isAllTooltips = nodes.every( const isAllTooltips = nodes.every(
(node) => (node) =>
@ -103,7 +99,7 @@ export const Intersect = () => {
codeBasedSelections: codeBasedSelections:
_forcedSelectionRanges.codeBasedSelections.slice(1), _forcedSelectionRanges.codeBasedSelections.slice(1),
}, },
ast, kclManager.ast,
'intersect' 'intersect'
) )
setTransformInfos(theTransforms) setTransformInfos(theTransforms)
@ -121,13 +117,13 @@ export const Intersect = () => {
return ( return (
<button <button
onClick={async () => { onClick={async () => {
if (!(transformInfos && ast && forecdSelectionRanges)) return if (!(transformInfos && forecdSelectionRanges)) return
const { modifiedAst, tagInfo, valueUsedInTransform, pathToNodeMap } = const { modifiedAst, tagInfo, valueUsedInTransform, pathToNodeMap } =
transformSecondarySketchLinesTagFirst({ transformSecondarySketchLinesTagFirst({
ast: JSON.parse(JSON.stringify(ast)), ast: JSON.parse(JSON.stringify(kclManager.ast)),
selectionRanges: forecdSelectionRanges, selectionRanges: forecdSelectionRanges,
transformInfos, transformInfos,
programMemory, programMemory: kclManager.programMemory,
}) })
const { const {
segName, segName,
@ -150,7 +146,7 @@ export const Intersect = () => {
initialVariableName: 'offset', initialVariableName: 'offset',
} as any) } as any)
if (segName === tagInfo?.tag && value === valueUsedInTransform) { if (segName === tagInfo?.tag && value === valueUsedInTransform) {
updateAst(modifiedAst, true, { kclManager.updateAst(modifiedAst, true, {
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap), callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
}) })
} else { } else {
@ -162,10 +158,10 @@ export const Intersect = () => {
) )
const { modifiedAst: _modifiedAst, pathToNodeMap } = const { modifiedAst: _modifiedAst, pathToNodeMap } =
transformSecondarySketchLinesTagFirst({ transformSecondarySketchLinesTagFirst({
ast, ast: kclManager.ast,
selectionRanges: forecdSelectionRanges, selectionRanges: forecdSelectionRanges,
transformInfos, transformInfos,
programMemory, programMemory: kclManager.programMemory,
forceSegName: segName, forceSegName: segName,
forceValueUsedInTransform: finalValue, forceValueUsedInTransform: finalValue,
}) })
@ -178,7 +174,7 @@ export const Intersect = () => {
) )
_modifiedAst.body = newBody _modifiedAst.body = newBody
} }
updateAst(_modifiedAst, true, { kclManager.updateAst(_modifiedAst, true, {
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap), callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
}) })
} }
@ -190,3 +186,4 @@ export const Intersect = () => {
</button> </button>
) )
} }
*/

View File

@ -10,27 +10,23 @@ import {
getRemoveConstraintsTransforms, getRemoveConstraintsTransforms,
transformAstSketchLines, transformAstSketchLines,
} from '../../lang/std/sketchcombos' } from '../../lang/std/sketchcombos'
import { updateCursors } from '../../lang/util' import { kclManager } from 'lang/KclSinglton'
/*
export const RemoveConstrainingValues = () => { export const RemoveConstrainingValues = () => {
const { guiMode, selectionRanges, ast, programMemory, updateAst, setCursor } = const { guiMode, selectionRanges, setCursor } = useStore((s) => ({
useStore((s) => ({
guiMode: s.guiMode, guiMode: s.guiMode,
ast: s.ast,
updateAst: s.updateAst,
selectionRanges: s.selectionRanges, selectionRanges: s.selectionRanges,
programMemory: s.programMemory,
setCursor: s.setCursor, setCursor: s.setCursor,
})) }))
const [enableHorz, setEnableHorz] = useState(false) const [enableHorz, setEnableHorz] = useState(false)
const [transformInfos, setTransformInfos] = useState<TransformInfo[]>() const [transformInfos, setTransformInfos] = useState<TransformInfo[]>()
useEffect(() => { useEffect(() => {
if (!ast) return
const paths = selectionRanges.codeBasedSelections.map(({ range }) => const paths = selectionRanges.codeBasedSelections.map(({ range }) =>
getNodePathFromSourceRange(ast, range) getNodePathFromSourceRange(kclManager.ast, range)
) )
const nodes = paths.map( const nodes = paths.map(
(pathToNode) => getNodeFromPath<Value>(ast, pathToNode).node (pathToNode) => getNodeFromPath<Value>(kclManager.ast, pathToNode).node
) )
const isAllTooltips = nodes.every( const isAllTooltips = nodes.every(
(node) => (node) =>
@ -41,7 +37,7 @@ export const RemoveConstrainingValues = () => {
try { try {
const theTransforms = getRemoveConstraintsTransforms( const theTransforms = getRemoveConstraintsTransforms(
selectionRanges, selectionRanges,
ast, kclManager.ast,
'removeConstrainingValues' 'removeConstrainingValues'
) )
setTransformInfos(theTransforms) setTransformInfos(theTransforms)
@ -57,15 +53,15 @@ export const RemoveConstrainingValues = () => {
return ( return (
<button <button
onClick={() => { onClick={() => {
if (!transformInfos || !ast) return if (!transformInfos) return
const { modifiedAst, pathToNodeMap } = transformAstSketchLines({ const { modifiedAst, pathToNodeMap } = transformAstSketchLines({
ast, ast: kclManager.ast,
selectionRanges, selectionRanges,
transformInfos, transformInfos,
programMemory, programMemory: kclManager.programMemory,
referenceSegName: '', referenceSegName: '',
}) })
updateAst(modifiedAst, true, { kclManager.updateAst(modifiedAst, true, {
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap), callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
}) })
}} }}
@ -76,3 +72,4 @@ export const RemoveConstrainingValues = () => {
</button> </button>
) )
} }
*/

View File

@ -18,7 +18,7 @@ import {
createVariableDeclaration, createVariableDeclaration,
} from '../../lang/modifyAst' } from '../../lang/modifyAst'
import { removeDoubleNegatives } from '../AvailableVarsHelpers' import { removeDoubleNegatives } from '../AvailableVarsHelpers'
import { updateCursors } from '../../lang/util' import { kclManager } from 'lang/KclSinglton'
const getModalInfo = create(SetAngleLengthModal as any) const getModalInfo = create(SetAngleLengthModal as any)
@ -31,14 +31,11 @@ const buttonLabels: Record<ButtonType, string> = {
snapToXAxis: 'Snap To X Axis', snapToXAxis: 'Snap To X Axis',
} }
/*
export const SetAbsDistance = ({ buttonType }: { buttonType: ButtonType }) => { export const SetAbsDistance = ({ buttonType }: { buttonType: ButtonType }) => {
const { guiMode, selectionRanges, ast, programMemory, updateAst, setCursor } = const { guiMode, selectionRanges, setCursor } = useStore((s) => ({
useStore((s) => ({
guiMode: s.guiMode, guiMode: s.guiMode,
ast: s.ast,
updateAst: s.updateAst,
selectionRanges: s.selectionRanges, selectionRanges: s.selectionRanges,
programMemory: s.programMemory,
setCursor: s.setCursor, setCursor: s.setCursor,
})) }))
const disType: ConstraintType = const disType: ConstraintType =
@ -50,13 +47,13 @@ export const SetAbsDistance = ({ buttonType }: { buttonType: ButtonType }) => {
const [enableAngLen, setEnableAngLen] = useState(false) const [enableAngLen, setEnableAngLen] = useState(false)
const [transformInfos, setTransformInfos] = useState<TransformInfo[]>() const [transformInfos, setTransformInfos] = useState<TransformInfo[]>()
useEffect(() => { useEffect(() => {
if (!ast) return
const paths = selectionRanges.codeBasedSelections.map(({ range }) => const paths = selectionRanges.codeBasedSelections.map(({ range }) =>
getNodePathFromSourceRange(ast, range) getNodePathFromSourceRange(kclManager.ast, range)
) )
const nodes = paths.map( const nodes = paths.map(
(pathToNode) => (pathToNode) =>
getNodeFromPath<Value>(ast, pathToNode, 'CallExpression').node getNodeFromPath<Value>(kclManager.ast, pathToNode, 'CallExpression')
.node
) )
const isAllTooltips = nodes.every( const isAllTooltips = nodes.every(
(node) => (node) =>
@ -64,7 +61,11 @@ export const SetAbsDistance = ({ buttonType }: { buttonType: ButtonType }) => {
toolTips.includes(node.callee.name as any) toolTips.includes(node.callee.name as any)
) )
const theTransforms = getTransformInfos(selectionRanges, ast, disType) const theTransforms = getTransformInfos(
selectionRanges,
kclManager.ast,
disType
)
setTransformInfos(theTransforms) setTransformInfos(theTransforms)
const enableY = const enableY =
@ -90,12 +91,12 @@ export const SetAbsDistance = ({ buttonType }: { buttonType: ButtonType }) => {
return ( return (
<button <button
onClick={async () => { onClick={async () => {
if (!(transformInfos && ast)) return if (!transformInfos) return
const { valueUsedInTransform } = transformAstSketchLines({ const { valueUsedInTransform } = transformAstSketchLines({
ast: JSON.parse(JSON.stringify(ast)), ast: JSON.parse(JSON.stringify(kclManager.ast)),
selectionRanges: selectionRanges, selectionRanges: selectionRanges,
transformInfos, transformInfos,
programMemory, programMemory: kclManager.programMemory,
referenceSegName: '', referenceSegName: '',
}) })
try { try {
@ -112,10 +113,10 @@ export const SetAbsDistance = ({ buttonType }: { buttonType: ButtonType }) => {
const { modifiedAst: _modifiedAst, pathToNodeMap } = const { modifiedAst: _modifiedAst, pathToNodeMap } =
transformAstSketchLines({ transformAstSketchLines({
ast: JSON.parse(JSON.stringify(ast)), ast: JSON.parse(JSON.stringify(kclManager.ast)),
selectionRanges: selectionRanges, selectionRanges: selectionRanges,
transformInfos, transformInfos,
programMemory, programMemory: kclManager.programMemory,
referenceSegName: '', referenceSegName: '',
forceValueUsedInTransform: finalValue, forceValueUsedInTransform: finalValue,
}) })
@ -129,7 +130,7 @@ export const SetAbsDistance = ({ buttonType }: { buttonType: ButtonType }) => {
_modifiedAst.body = newBody _modifiedAst.body = newBody
} }
updateAst(_modifiedAst, true, { kclManager.updateAst(_modifiedAst, true, {
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap), callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
}) })
} catch (e) { } catch (e) {
@ -143,3 +144,4 @@ export const SetAbsDistance = ({ buttonType }: { buttonType: ButtonType }) => {
</button> </button>
) )
} }
*/

View File

@ -1,7 +1,7 @@
import { useState, useEffect } from 'react' import { useState, useEffect } from 'react'
import { create } from 'react-modal-promise' import { create } from 'react-modal-promise'
import { toolTips, useStore } from '../../useStore' import { Selections, toolTips, useStore } from '../../useStore'
import { BinaryPart, Value, VariableDeclarator } from '../../lang/wasm' import { BinaryPart, Program, Value, VariableDeclarator } from '../../lang/wasm'
import { import {
getNodePathFromSourceRange, getNodePathFromSourceRange,
getNodeFromPath, getNodeFromPath,
@ -11,82 +11,41 @@ import {
TransformInfo, TransformInfo,
transformSecondarySketchLinesTagFirst, transformSecondarySketchLinesTagFirst,
getTransformInfos, getTransformInfos,
PathToNodeMap,
} from '../../lang/std/sketchcombos' } from '../../lang/std/sketchcombos'
import { GetInfoModal } from '../SetHorVertDistanceModal' import { GetInfoModal } from '../SetHorVertDistanceModal'
import { createVariableDeclaration } from '../../lang/modifyAst' import { createVariableDeclaration } from '../../lang/modifyAst'
import { removeDoubleNegatives } from '../AvailableVarsHelpers' import { removeDoubleNegatives } from '../AvailableVarsHelpers'
import { updateCursors } from '../../lang/util' import { kclManager } from 'lang/KclSinglton'
const getModalInfo = create(GetInfoModal as any) const getModalInfo = create(GetInfoModal as any)
/*
export const SetAngleBetween = () => { export const SetAngleBetween = () => {
const { guiMode, selectionRanges, ast, programMemory, updateAst, setCursor } = const { guiMode, selectionRanges, setCursor } = useStore((s) => ({
useStore((s) => ({
guiMode: s.guiMode, guiMode: s.guiMode,
ast: s.ast,
updateAst: s.updateAst,
selectionRanges: s.selectionRanges, selectionRanges: s.selectionRanges,
programMemory: s.programMemory,
setCursor: s.setCursor, setCursor: s.setCursor,
})) }))
const [enable, setEnable] = useState(false) const [enable, setEnable] = useState(false)
const [transformInfos, setTransformInfos] = useState<TransformInfo[]>() const [transformInfos, setTransformInfos] = useState<TransformInfo[]>()
useEffect(() => { useEffect(() => {
if (!ast) return const { enabled, transforms } = angleBetweenInfo({ selectionRanges })
const paths = selectionRanges.codeBasedSelections.map(({ range }) => setTransformInfos(transforms)
getNodePathFromSourceRange(ast, range) setEnable(enabled)
)
const nodes = paths.map(
(pathToNode) => getNodeFromPath<Value>(ast, pathToNode).node
)
const varDecs = paths.map(
(pathToNode) =>
getNodeFromPath<VariableDeclarator>(
ast,
pathToNode,
'VariableDeclarator'
)?.node
)
const primaryLine = varDecs[0]
const secondaryVarDecs = varDecs.slice(1)
const isOthersLinkedToPrimary = secondaryVarDecs.every((secondary) =>
isSketchVariablesLinked(secondary, primaryLine, ast)
)
const isAllTooltips = nodes.every(
(node) =>
node?.type === 'CallExpression' &&
toolTips.includes(node.callee.name as any)
)
const theTransforms = getTransformInfos(
{
...selectionRanges,
codeBasedSelections: selectionRanges.codeBasedSelections.slice(1),
},
ast,
'setAngleBetween'
)
setTransformInfos(theTransforms)
const _enableEqual =
secondaryVarDecs.length === 1 &&
isAllTooltips &&
isOthersLinkedToPrimary &&
theTransforms.every(Boolean)
setEnable(_enableEqual)
}, [guiMode, selectionRanges]) }, [guiMode, selectionRanges])
if (guiMode.mode !== 'sketch') return null if (guiMode.mode !== 'sketch') return null
return ( return (
<button <button
onClick={async () => { onClick={async () => {
if (!(transformInfos && ast)) return if (!transformInfos) return
const { modifiedAst, tagInfo, valueUsedInTransform, pathToNodeMap } = const { modifiedAst, tagInfo, valueUsedInTransform, pathToNodeMap } =
transformSecondarySketchLinesTagFirst({ transformSecondarySketchLinesTagFirst({
ast: JSON.parse(JSON.stringify(ast)), ast: JSON.parse(JSON.stringify(kclManager.ast)),
selectionRanges, selectionRanges,
transformInfos, transformInfos,
programMemory, programMemory: kclManager.programMemory,
}) })
const { const {
segName, segName,
@ -109,7 +68,7 @@ export const SetAngleBetween = () => {
initialVariableName: 'angle', initialVariableName: 'angle',
} as any) } as any)
if (segName === tagInfo?.tag && value === valueUsedInTransform) { if (segName === tagInfo?.tag && value === valueUsedInTransform) {
updateAst(modifiedAst, true, { kclManager.updateAst(modifiedAst, true, {
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap), callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
}) })
} else { } else {
@ -121,10 +80,10 @@ export const SetAngleBetween = () => {
// transform again but forcing certain values // transform again but forcing certain values
const { modifiedAst: _modifiedAst, pathToNodeMap } = const { modifiedAst: _modifiedAst, pathToNodeMap } =
transformSecondarySketchLinesTagFirst({ transformSecondarySketchLinesTagFirst({
ast, ast: kclManager.ast,
selectionRanges, selectionRanges,
transformInfos, transformInfos,
programMemory, programMemory: kclManager.programMemory,
forceSegName: segName, forceSegName: segName,
forceValueUsedInTransform: finalValue, forceValueUsedInTransform: finalValue,
}) })
@ -137,7 +96,7 @@ export const SetAngleBetween = () => {
) )
_modifiedAst.body = newBody _modifiedAst.body = newBody
} }
updateAst(_modifiedAst, true, { kclManager.updateAst(_modifiedAst, true, {
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap), callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
}) })
} }
@ -149,3 +108,135 @@ export const SetAngleBetween = () => {
</button> </button>
) )
} }
*/
export function angleBetweenInfo({
selectionRanges,
}: {
selectionRanges: Selections
}) {
const paths = selectionRanges.codeBasedSelections.map(({ range }) =>
getNodePathFromSourceRange(kclManager.ast, range)
)
const nodes = paths.map(
(pathToNode) => getNodeFromPath<Value>(kclManager.ast, pathToNode).node
)
const varDecs = paths.map(
(pathToNode) =>
getNodeFromPath<VariableDeclarator>(
kclManager.ast,
pathToNode,
'VariableDeclarator'
)?.node
)
const primaryLine = varDecs[0]
const secondaryVarDecs = varDecs.slice(1)
const isOthersLinkedToPrimary = secondaryVarDecs.every((secondary) =>
isSketchVariablesLinked(secondary, primaryLine, kclManager.ast)
)
const isAllTooltips = nodes.every(
(node) =>
node?.type === 'CallExpression' &&
toolTips.includes(node.callee.name as any)
)
const theTransforms = getTransformInfos(
{
...selectionRanges,
codeBasedSelections: selectionRanges.codeBasedSelections.slice(1),
},
kclManager.ast,
'setAngleBetween'
)
const _enableEqual =
secondaryVarDecs.length === 1 &&
isAllTooltips &&
isOthersLinkedToPrimary &&
theTransforms.every(Boolean)
return { enabled: _enableEqual, transforms: theTransforms }
}
export async function applyConstraintAngleBetween({
selectionRanges,
}: // constraint,
{
selectionRanges: Selections
// constraint: 'setHorzDistance' | 'setVertDistance'
}): Promise<{
modifiedAst: Program
pathToNodeMap: PathToNodeMap
}> {
const transformInfos = angleBetweenInfo({ selectionRanges }).transforms
const { modifiedAst, tagInfo, valueUsedInTransform, pathToNodeMap } =
transformSecondarySketchLinesTagFirst({
ast: JSON.parse(JSON.stringify(kclManager.ast)),
selectionRanges,
transformInfos,
programMemory: kclManager.programMemory,
})
const {
segName,
value,
valueNode,
variableName,
newVariableInsertIndex,
sign,
}: {
segName: string
value: number
valueNode: Value
variableName?: string
newVariableInsertIndex: number
sign: number
} = await getModalInfo({
segName: tagInfo?.tag,
isSegNameEditable: !tagInfo?.isTagExisting,
value: valueUsedInTransform,
initialVariableName: 'angle',
} as any)
if (segName === tagInfo?.tag && value === valueUsedInTransform) {
return {
modifiedAst,
pathToNodeMap,
}
// kclManager.updateAst(modifiedAst, true, {
// TODO handle cursor
// callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
// })
}
const finalValue = removeDoubleNegatives(
valueNode as BinaryPart,
sign,
variableName
)
// transform again but forcing certain values
const { modifiedAst: _modifiedAst, pathToNodeMap: _pathToNodeMap } =
transformSecondarySketchLinesTagFirst({
ast: kclManager.ast,
selectionRanges,
transformInfos,
programMemory: kclManager.programMemory,
forceSegName: segName,
forceValueUsedInTransform: finalValue,
})
if (variableName) {
const newBody = [..._modifiedAst.body]
newBody.splice(
newVariableInsertIndex,
0,
createVariableDeclaration(variableName, valueNode)
)
_modifiedAst.body = newBody
}
return {
modifiedAst: _modifiedAst,
pathToNodeMap: _pathToNodeMap,
}
// kclManager.updateAst(_modifiedAst, true, {
// TODO handle cursor
// callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
// })
}

View File

@ -1,7 +1,7 @@
import { useState, useEffect } from 'react' import { useState, useEffect } from 'react'
import { create } from 'react-modal-promise' import { create } from 'react-modal-promise'
import { toolTips, useStore } from '../../useStore' import { toolTips, useStore } from '../../useStore'
import { BinaryPart, Value, VariableDeclarator } from '../../lang/wasm' import { BinaryPart, Program, Value, VariableDeclarator } from '../../lang/wasm'
import { import {
getNodePathFromSourceRange, getNodePathFromSourceRange,
getNodeFromPath, getNodeFromPath,
@ -12,11 +12,13 @@ import {
transformSecondarySketchLinesTagFirst, transformSecondarySketchLinesTagFirst,
getTransformInfos, getTransformInfos,
ConstraintType, ConstraintType,
PathToNodeMap,
} from '../../lang/std/sketchcombos' } from '../../lang/std/sketchcombos'
import { GetInfoModal } from '../SetHorVertDistanceModal' import { GetInfoModal } from '../SetHorVertDistanceModal'
import { createLiteral, createVariableDeclaration } from '../../lang/modifyAst' import { createLiteral, createVariableDeclaration } from '../../lang/modifyAst'
import { removeDoubleNegatives } from '../AvailableVarsHelpers' import { removeDoubleNegatives } from '../AvailableVarsHelpers'
import { updateCursors } from '../../lang/util' import { kclManager } from 'lang/KclSinglton'
import { Selections } from 'useStore'
const getModalInfo = create(GetInfoModal as any) const getModalInfo = create(GetInfoModal as any)
@ -33,18 +35,15 @@ const buttonLabels: Record<ButtonType, string> = {
alignEndsVertically: 'Align Ends Vertically', alignEndsVertically: 'Align Ends Vertically',
} }
/*
export const SetHorzVertDistance = ({ export const SetHorzVertDistance = ({
buttonType, buttonType,
}: { }: {
buttonType: ButtonType buttonType: ButtonType
}) => { }) => {
const { guiMode, selectionRanges, ast, programMemory, updateAst, setCursor } = const { guiMode, selectionRanges, setCursor } = useStore((s) => ({
useStore((s) => ({
guiMode: s.guiMode, guiMode: s.guiMode,
ast: s.ast,
updateAst: s.updateAst,
selectionRanges: s.selectionRanges, selectionRanges: s.selectionRanges,
programMemory: s.programMemory,
setCursor: s.setCursor, setCursor: s.setCursor,
})) }))
const constraint: ConstraintType = const constraint: ConstraintType =
@ -56,51 +55,12 @@ export const SetHorzVertDistance = ({
const [enable, setEnable] = useState(false) const [enable, setEnable] = useState(false)
const [transformInfos, setTransformInfos] = useState<TransformInfo[]>() const [transformInfos, setTransformInfos] = useState<TransformInfo[]>()
useEffect(() => { useEffect(() => {
if (!ast) return const { transforms, enabled } = horzVertDistanceInfo({
const paths = selectionRanges.codeBasedSelections.map(({ range }) => selectionRanges,
getNodePathFromSourceRange(ast, range) constraint,
) })
const nodes = paths.map( setTransformInfos(transforms)
(pathToNode) => getNodeFromPath<Value>(ast, pathToNode).node setEnable(enabled)
)
const varDecs = paths.map(
(pathToNode) =>
getNodeFromPath<VariableDeclarator>(
ast,
pathToNode,
'VariableDeclarator'
)?.node
)
const primaryLine = varDecs[0]
const secondaryVarDecs = varDecs.slice(1)
const isOthersLinkedToPrimary = secondaryVarDecs.every((secondary) =>
isSketchVariablesLinked(secondary, primaryLine, ast)
)
const isAllTooltips = nodes.every(
(node) =>
node?.type === 'CallExpression' &&
[
...toolTips,
'startSketchAt', // TODO probably a better place for this to live
].includes(node.callee.name as any)
)
const theTransforms = getTransformInfos(
{
...selectionRanges,
codeBasedSelections: selectionRanges.codeBasedSelections.slice(1),
},
ast,
constraint
)
setTransformInfos(theTransforms)
const _enableEqual =
secondaryVarDecs.length === 1 &&
isAllTooltips &&
isOthersLinkedToPrimary &&
theTransforms.every(Boolean)
setEnable(_enableEqual)
}, [guiMode, selectionRanges]) }, [guiMode, selectionRanges])
if (guiMode.mode !== 'sketch') return null if (guiMode.mode !== 'sketch') return null
@ -111,13 +71,13 @@ export const SetHorzVertDistance = ({
return ( return (
<button <button
onClick={async () => { onClick={async () => {
if (!(transformInfos && ast)) return if (!transformInfos) return
const { modifiedAst, tagInfo, valueUsedInTransform, pathToNodeMap } = const { modifiedAst, tagInfo, valueUsedInTransform, pathToNodeMap } =
transformSecondarySketchLinesTagFirst({ transformSecondarySketchLinesTagFirst({
ast: JSON.parse(JSON.stringify(ast)), ast: JSON.parse(JSON.stringify(kclManager.ast)),
selectionRanges, selectionRanges,
transformInfos, transformInfos,
programMemory, programMemory: kclManager.programMemory,
}) })
const { const {
segName, segName,
@ -142,7 +102,7 @@ export const SetHorzVertDistance = ({
constraint === 'setHorzDistance' ? 'xDis' : 'yDis', constraint === 'setHorzDistance' ? 'xDis' : 'yDis',
} as any)) } as any))
if (segName === tagInfo?.tag && value === valueUsedInTransform) { if (segName === tagInfo?.tag && value === valueUsedInTransform) {
updateAst(modifiedAst, true, { kclManager.updateAst(modifiedAst, true, {
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap), callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
}) })
} else { } else {
@ -152,10 +112,10 @@ export const SetHorzVertDistance = ({
// transform again but forcing certain values // transform again but forcing certain values
const { modifiedAst: _modifiedAst, pathToNodeMap } = const { modifiedAst: _modifiedAst, pathToNodeMap } =
transformSecondarySketchLinesTagFirst({ transformSecondarySketchLinesTagFirst({
ast, ast: kclManager.ast,
selectionRanges, selectionRanges,
transformInfos, transformInfos,
programMemory, programMemory: kclManager.programMemory,
forceSegName: segName, forceSegName: segName,
forceValueUsedInTransform: finalValue, forceValueUsedInTransform: finalValue,
}) })
@ -168,7 +128,7 @@ export const SetHorzVertDistance = ({
) )
_modifiedAst.body = newBody _modifiedAst.body = newBody
} }
updateAst(_modifiedAst, true, { kclManager.updateAst(_modifiedAst, true, {
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap), callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
}) })
} }
@ -180,3 +140,175 @@ export const SetHorzVertDistance = ({
</button> </button>
) )
} }
*/
export function horzVertDistanceInfo({
selectionRanges,
constraint,
}: {
selectionRanges: Selections
constraint: 'setHorzDistance' | 'setVertDistance'
}) {
const paths = selectionRanges.codeBasedSelections.map(({ range }) =>
getNodePathFromSourceRange(kclManager.ast, range)
)
const nodes = paths.map(
(pathToNode) => getNodeFromPath<Value>(kclManager.ast, pathToNode).node
)
const varDecs = paths.map(
(pathToNode) =>
getNodeFromPath<VariableDeclarator>(
kclManager.ast,
pathToNode,
'VariableDeclarator'
)?.node
)
const primaryLine = varDecs[0]
const secondaryVarDecs = varDecs.slice(1)
const isOthersLinkedToPrimary = secondaryVarDecs.every((secondary) =>
isSketchVariablesLinked(secondary, primaryLine, kclManager.ast)
)
const isAllTooltips = nodes.every(
(node) =>
node?.type === 'CallExpression' &&
[
...toolTips,
'startSketchAt', // TODO probably a better place for this to live
].includes(node.callee.name as any)
)
const theTransforms = getTransformInfos(
{
...selectionRanges,
codeBasedSelections: selectionRanges.codeBasedSelections.slice(1),
},
kclManager.ast,
constraint
)
const _enableEqual =
secondaryVarDecs.length === 1 &&
isAllTooltips &&
isOthersLinkedToPrimary &&
theTransforms.every(Boolean)
return { enabled: _enableEqual, transforms: theTransforms }
}
export async function applyConstraintHorzVertDistance({
selectionRanges,
constraint,
// TODO align will always be false (covered by synconous applyConstraintHorzVertAlign), remove it
isAlign = false,
}: {
selectionRanges: Selections
constraint: 'setHorzDistance' | 'setVertDistance'
isAlign?: boolean
}): Promise<{
modifiedAst: Program
pathToNodeMap: PathToNodeMap
}> {
const transformInfos = horzVertDistanceInfo({
selectionRanges,
constraint,
}).transforms
const { modifiedAst, tagInfo, valueUsedInTransform, pathToNodeMap } =
transformSecondarySketchLinesTagFirst({
ast: JSON.parse(JSON.stringify(kclManager.ast)),
selectionRanges,
transformInfos,
programMemory: kclManager.programMemory,
})
const {
segName,
value,
valueNode,
variableName,
newVariableInsertIndex,
sign,
}: {
segName: string
value: number
valueNode: Value
variableName?: string
newVariableInsertIndex: number
sign: number
} = await (!isAlign &&
getModalInfo({
segName: tagInfo?.tag,
isSegNameEditable: !tagInfo?.isTagExisting,
value: valueUsedInTransform,
initialVariableName: constraint === 'setHorzDistance' ? 'xDis' : 'yDis',
} as any))
if (segName === tagInfo?.tag && value === valueUsedInTransform) {
return {
modifiedAst,
pathToNodeMap,
}
// TODO handle cursor stuff
// kclManager.updateAst(modifiedAst, true, {
// callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
// })
} else {
let finalValue = isAlign
? createLiteral(0)
: removeDoubleNegatives(valueNode as BinaryPart, sign, variableName)
// transform again but forcing certain values
const { modifiedAst: _modifiedAst, pathToNodeMap } =
transformSecondarySketchLinesTagFirst({
ast: kclManager.ast,
selectionRanges,
transformInfos,
programMemory: kclManager.programMemory,
forceSegName: segName,
forceValueUsedInTransform: finalValue,
})
if (variableName) {
const newBody = [..._modifiedAst.body]
newBody.splice(
newVariableInsertIndex,
0,
createVariableDeclaration(variableName, valueNode)
)
_modifiedAst.body = newBody
}
return {
modifiedAst: _modifiedAst,
pathToNodeMap,
}
// TODO handle cursor stuff
// kclManager.updateAst(_modifiedAst, true, {
// callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
// })
}
}
export function applyConstraintHorzVertAlign({
selectionRanges,
constraint,
}: {
selectionRanges: Selections
constraint: 'setHorzDistance' | 'setVertDistance'
}): {
modifiedAst: Program
pathToNodeMap: PathToNodeMap
} {
const transformInfos = horzVertDistanceInfo({
selectionRanges,
constraint,
}).transforms
let finalValue = createLiteral(0)
const { modifiedAst, pathToNodeMap } = transformSecondarySketchLinesTagFirst({
ast: kclManager.ast,
selectionRanges,
transformInfos,
programMemory: kclManager.programMemory,
forceValueUsedInTransform: finalValue,
})
return {
modifiedAst: modifiedAst,
pathToNodeMap,
}
// TODO handle cursor stuff
// kclManager.updateAst(_modifiedAst, true, {
// callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
// })
}

View File

@ -1,12 +1,13 @@
import { useState, useEffect } from 'react' import { useState, useEffect } from 'react'
import { create } from 'react-modal-promise' import { create } from 'react-modal-promise'
import { toolTips, useStore } from '../../useStore' import { Selections, toolTips, useStore } from '../../useStore'
import { Value } from '../../lang/wasm' import { Program, Value } from '../../lang/wasm'
import { import {
getNodePathFromSourceRange, getNodePathFromSourceRange,
getNodeFromPath, getNodeFromPath,
} from '../../lang/queryAst' } from '../../lang/queryAst'
import { import {
PathToNodeMap,
TransformInfo, TransformInfo,
getTransformInfos, getTransformInfos,
transformAstSketchLines, transformAstSketchLines,
@ -19,7 +20,7 @@ import {
} from '../../lang/modifyAst' } from '../../lang/modifyAst'
import { removeDoubleNegatives } from '../AvailableVarsHelpers' import { removeDoubleNegatives } from '../AvailableVarsHelpers'
import { normaliseAngle } from '../../lib/utils' import { normaliseAngle } from '../../lib/utils'
import { updateCursors } from '../../lang/util' import { kclManager } from 'lang/KclSinglton'
const getModalInfo = create(SetAngleLengthModal as any) const getModalInfo = create(SetAngleLengthModal as any)
@ -30,54 +31,39 @@ const buttonLabels: Record<ButtonType, string> = {
setLength: 'Set Length', setLength: 'Set Length',
} }
/*
export const SetAngleLength = ({ export const SetAngleLength = ({
angleOrLength, angleOrLength,
}: { }: {
angleOrLength: ButtonType angleOrLength: ButtonType
}) => { }) => {
const { guiMode, selectionRanges, ast, programMemory, updateAst, setCursor } = const { guiMode, selectionRanges, setCursor } = useStore((s) => ({
useStore((s) => ({
guiMode: s.guiMode, guiMode: s.guiMode,
ast: s.ast,
updateAst: s.updateAst,
selectionRanges: s.selectionRanges, selectionRanges: s.selectionRanges,
programMemory: s.programMemory,
setCursor: s.setCursor, setCursor: s.setCursor,
})) }))
const [enableAngLen, setEnableAngLen] = useState(false) const [enableAngLen, setEnableAngLen] = useState(false)
const [transformInfos, setTransformInfos] = useState<TransformInfo[]>() const [transformInfos, setTransformInfos] = useState<TransformInfo[]>()
useEffect(() => { useEffect(() => {
if (!ast) return const { enabled, transforms } = setAngleLengthInfo({
const paths = selectionRanges.codeBasedSelections.map(({ range }) => selectionRanges,
getNodePathFromSourceRange(ast, range) angleOrLength,
) })
const nodes = paths.map(
(pathToNode) =>
getNodeFromPath<Value>(ast, pathToNode, 'CallExpression').node
)
const isAllTooltips = nodes.every(
(node) =>
node?.type === 'CallExpression' &&
toolTips.includes(node.callee.name as any)
)
const theTransforms = getTransformInfos(selectionRanges, ast, angleOrLength) setTransformInfos(transforms)
setTransformInfos(theTransforms) setEnableAngLen(enabled)
const _enableHorz = isAllTooltips && theTransforms.every(Boolean)
setEnableAngLen(_enableHorz)
}, [guiMode, selectionRanges]) }, [guiMode, selectionRanges])
if (guiMode.mode !== 'sketch') return null if (guiMode.mode !== 'sketch') return null
return ( return (
<button <button
onClick={async () => { onClick={async () => {
if (!(transformInfos && ast)) return if (!transformInfos) return
const { valueUsedInTransform } = transformAstSketchLines({ const { valueUsedInTransform } = transformAstSketchLines({
ast: JSON.parse(JSON.stringify(ast)), ast: JSON.parse(JSON.stringify(kclManager.ast)),
selectionRanges, selectionRanges,
transformInfos, transformInfos,
programMemory, programMemory: kclManager.programMemory,
referenceSegName: '', referenceSegName: '',
}) })
try { try {
@ -126,10 +112,10 @@ export const SetAngleLength = ({
const { modifiedAst: _modifiedAst, pathToNodeMap } = const { modifiedAst: _modifiedAst, pathToNodeMap } =
transformAstSketchLines({ transformAstSketchLines({
ast: JSON.parse(JSON.stringify(ast)), ast: JSON.parse(JSON.stringify(kclManager.ast)),
selectionRanges, selectionRanges,
transformInfos, transformInfos,
programMemory, programMemory: kclManager.programMemory,
referenceSegName: '', referenceSegName: '',
forceValueUsedInTransform: finalValue, forceValueUsedInTransform: finalValue,
}) })
@ -143,7 +129,7 @@ export const SetAngleLength = ({
_modifiedAst.body = newBody _modifiedAst.body = newBody
} }
updateAst(_modifiedAst, true, { kclManager.updateAst(_modifiedAst, true, {
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap), callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
}) })
} catch (e) { } catch (e) {
@ -157,3 +143,119 @@ export const SetAngleLength = ({
</button> </button>
) )
} }
*/
export function setAngleLengthInfo({
selectionRanges,
angleOrLength = 'setLength',
}: {
selectionRanges: Selections
angleOrLength?: 'setLength' | 'setAngle'
}) {
const paths = selectionRanges.codeBasedSelections.map(({ range }) =>
getNodePathFromSourceRange(kclManager.ast, range)
)
const nodes = paths.map(
(pathToNode) =>
getNodeFromPath<Value>(kclManager.ast, pathToNode, 'CallExpression').node
)
const isAllTooltips = nodes.every(
(node) =>
node?.type === 'CallExpression' &&
toolTips.includes(node.callee.name as any)
)
const transforms = getTransformInfos(
selectionRanges,
kclManager.ast,
angleOrLength
)
const enabled = isAllTooltips && transforms.every(Boolean)
return { enabled, transforms }
}
export async function applyConstraintAngleLength({
selectionRanges,
angleOrLength = 'setLength',
}: {
selectionRanges: Selections
angleOrLength?: 'setLength' | 'setAngle'
}): Promise<{
modifiedAst: Program
pathToNodeMap: PathToNodeMap
}> {
const { transforms } = setAngleLengthInfo({ selectionRanges, angleOrLength })
const { valueUsedInTransform } = transformAstSketchLines({
ast: JSON.parse(JSON.stringify(kclManager.ast)),
selectionRanges,
transformInfos: transforms,
programMemory: kclManager.programMemory,
referenceSegName: '',
})
try {
const isReferencingYAxis =
selectionRanges.otherSelections.length === 1 &&
selectionRanges.otherSelections[0] === 'y-axis'
const isReferencingYAxisAngle =
isReferencingYAxis && angleOrLength === 'setAngle'
const isReferencingXAxis =
selectionRanges.otherSelections.length === 1 &&
selectionRanges.otherSelections[0] === 'x-axis'
const isReferencingXAxisAngle =
isReferencingXAxis && angleOrLength === 'setAngle'
let forceVal = valueUsedInTransform || 0
let calcIdentifier = createIdentifier('_0')
if (isReferencingYAxisAngle) {
calcIdentifier = createIdentifier(forceVal < 0 ? '_270' : '_90')
forceVal = normaliseAngle(forceVal + (forceVal < 0 ? 90 : -90))
} else if (isReferencingXAxisAngle) {
calcIdentifier = createIdentifier(Math.abs(forceVal) > 90 ? '_180' : '_0')
forceVal =
Math.abs(forceVal) > 90 ? normaliseAngle(forceVal - 180) : forceVal
}
const { valueNode, variableName, newVariableInsertIndex, sign } =
await getModalInfo({
value: forceVal,
valueName: angleOrLength === 'setAngle' ? 'angle' : 'length',
shouldCreateVariable: true,
} as any)
let finalValue = removeDoubleNegatives(valueNode, sign, variableName)
if (
isReferencingYAxisAngle ||
(isReferencingXAxisAngle && calcIdentifier.name !== '_0')
) {
finalValue = createBinaryExpressionWithUnary([calcIdentifier, finalValue])
}
const { modifiedAst: _modifiedAst, pathToNodeMap } =
transformAstSketchLines({
ast: JSON.parse(JSON.stringify(kclManager.ast)),
selectionRanges,
transformInfos: transforms,
programMemory: kclManager.programMemory,
referenceSegName: '',
forceValueUsedInTransform: finalValue,
})
if (variableName) {
const newBody = [..._modifiedAst.body]
newBody.splice(
newVariableInsertIndex,
0,
createVariableDeclaration(variableName, valueNode)
)
_modifiedAst.body = newBody
}
return {
modifiedAst: _modifiedAst,
pathToNodeMap,
}
// kclManager.updateAst(_modifiedAst, true, {
// callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
// })
} catch (e) {
console.log('erorr', e)
throw e
}
}

View File

@ -22,9 +22,7 @@ const UserSidebarMenu = ({ user }: { user?: User }) => {
const displayedName = getDisplayName(user) const displayedName = getDisplayName(user)
const [imageLoadFailed, setImageLoadFailed] = useState(false) const [imageLoadFailed, setImageLoadFailed] = useState(false)
const navigate = useNavigate() const navigate = useNavigate()
const { const send = useGlobalStateContext()?.auth?.send
auth: { send },
} = useGlobalStateContext()
// Fallback logic for displaying user's "name": // Fallback logic for displaying user's "name":
// 1. user.name // 1. user.name

View File

@ -1,8 +1,5 @@
// all web app environment variables are defined here, jest doesn't like import.meta.env so centralising them here // env vars were centralised so they could be mocked in jest
// allows us to mock them in one place, see src/setupTests.ts, it pulls the variable names and valuse from .env.development // but isn't needed anymore with vite, so is now just a convention
// note the exported variable name must match the env var name for the jest mocks to work
// i.e. const VITE_MY_VAR = import.meta.env.VITE_MY_VAR
// Maybe this file should be generated in a GHA from .env.development?
export const VITE_KC_API_WS_MODELING_URL = import.meta.env export const VITE_KC_API_WS_MODELING_URL = import.meta.env
.VITE_KC_API_WS_MODELING_URL .VITE_KC_API_WS_MODELING_URL
@ -12,3 +9,4 @@ export const VITE_KC_CONNECTION_TIMEOUT_MS = import.meta.env
.VITE_KC_CONNECTION_TIMEOUT_MS .VITE_KC_CONNECTION_TIMEOUT_MS
export const VITE_KC_SENTRY_DSN = import.meta.env.VITE_KC_SENTRY_DSN export const VITE_KC_SENTRY_DSN = import.meta.env.VITE_KC_SENTRY_DSN
export const TEST = import.meta.env.TEST export const TEST = import.meta.env.TEST
export const DEV = import.meta.env.DEV

View File

@ -1,336 +1,8 @@
// needed somewhere to dump this logic, import { Selections } from 'useStore'
// Once we have xState this should be removed
import { useStore, Selections } from 'useStore'
import { useEffect } from 'react'
import { v4 as uuidv4 } from 'uuid'
import { ArtifactMap, EngineCommandManager } from 'lang/std/engineConnection' 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 { isOverlap } from 'lib/utils'
import { engineCommandManager } from '../lang/std/engineConnection'
import { DefaultPlanes } from '../wasm-lib/kcl/bindings/DefaultPlanes'
import { getNodeFromPath } from '../lang/queryAst'
import { CallExpression, PipeExpression } from '../lang/wasm'
export function useAppMode() { export function isCursorInSketchCommandRange(
const {
guiMode,
setGuiMode,
selectionRanges,
selectionRangeTypeMap,
defaultPlanes,
setDefaultPlanes,
setCurrentPlane,
ast,
} = useStore((s) => ({
guiMode: s.guiMode,
setGuiMode: s.setGuiMode,
selectionRanges: s.selectionRanges,
selectionRangeTypeMap: s.selectionRangeTypeMap,
defaultPlanes: s.defaultPlanes,
setDefaultPlanes: s.setDefaultPlanes,
setCurrentPlane: s.setCurrentPlane,
ast: s.ast,
}))
useEffect(() => {
if (
guiMode.mode === 'sketch' &&
guiMode.sketchMode === 'selectFace' &&
engineCommandManager
) {
const createAndShowPlanes = async () => {
let localDefaultPlanes: DefaultPlanes
if (!defaultPlanes) {
const newDefaultPlanes = await initDefaultPlanes(engineCommandManager)
if (!newDefaultPlanes) return
setDefaultPlanes(newDefaultPlanes)
localDefaultPlanes = newDefaultPlanes
} else {
localDefaultPlanes = defaultPlanes
}
setDefaultPlanesHidden(engineCommandManager, localDefaultPlanes, false)
}
createAndShowPlanes()
}
if (
guiMode.mode === 'sketch' &&
guiMode.sketchMode === 'enterSketchEdit' &&
engineCommandManager
) {
const enableSketchMode = async () => {
let localDefaultPlanes: DefaultPlanes
if (!defaultPlanes) {
const newDefaultPlanes = await initDefaultPlanes(engineCommandManager)
if (!newDefaultPlanes) return
setDefaultPlanes(newDefaultPlanes)
localDefaultPlanes = newDefaultPlanes
} else {
localDefaultPlanes = defaultPlanes
}
setDefaultPlanesHidden(engineCommandManager, localDefaultPlanes, true)
const pipeExpression = getNodeFromPath<PipeExpression>(
ast,
guiMode.pathToNode,
'PipeExpression'
).node
if (pipeExpression.type !== 'PipeExpression') return /// bad bad bad
const sketchCallExpression = pipeExpression.body.find(
(e) =>
e.type === 'CallExpression' && e.callee.name === 'startSketchOn'
) as CallExpression
if (!sketchCallExpression) return // also bad bad bad
const firstArg = sketchCallExpression.arguments[0]
let planeId = ''
if (firstArg.type === 'Literal' && firstArg.value) {
const planeStrCleaned = firstArg.value
.toString()
.toLowerCase()
.replace('-', '')
if (
planeStrCleaned === 'xy' ||
planeStrCleaned === 'xz' ||
planeStrCleaned === 'yz'
) {
planeId = localDefaultPlanes[planeStrCleaned]
}
}
if (!planeId) return // they are on some non default plane, which we don't support yet
setCurrentPlane(planeId)
await engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'sketch_mode_enable',
plane_id: planeId,
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',
})
}
if (guiMode.mode !== 'sketch' && defaultPlanes) {
setDefaultPlanesHidden(engineCommandManager, defaultPlanes, true)
}
if (guiMode.mode === 'default') {
const pathId =
engineCommandManager &&
isCursorInSketchCommandRange(
engineCommandManager.artifactMap,
selectionRanges
)
if (pathId) {
setGuiMode({
mode: 'canEditSketch',
rotation: [0, 0, 0, 1],
position: [0, 0, 0],
pathToNode: [],
pathId,
})
}
} else if (guiMode.mode === 'canEditSketch') {
if (
!engineCommandManager ||
!isCursorInSketchCommandRange(
engineCommandManager.artifactMap,
selectionRanges
)
) {
setGuiMode({
mode: 'default',
})
}
}
}, [
guiMode,
guiMode.mode,
engineCommandManager,
selectionRanges,
selectionRangeTypeMap,
])
useEffect(() => {
const unSub = engineCommandManager.subscribeTo({
event: 'select_with_point',
callback: async ({ data }) => {
if (!data.entity_id) return
if (!defaultPlanes) return
if (!Object.values(defaultPlanes || {}).includes(data.entity_id)) {
// user clicked something else in the scene
return
}
setCurrentPlane(data.entity_id)
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(
engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: sketchUuid,
cmd: {
type: 'start_path',
},
})
)
proms.push(
engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'edit_mode_enter',
target: sketchUuid,
},
})
)
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)
},
})
return unSub
}, [engineCommandManager, defaultPlanes])
}
async function createPlane(
engineCommandManager: EngineCommandManager,
{
x_axis,
y_axis,
color,
hidden,
}: {
x_axis: Models['Point3d_type']
y_axis: Models['Point3d_type']
color: Models['Color_type']
hidden: boolean
}
) {
const planeId = uuidv4()
await engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd: {
type: 'make_plane',
size: 60,
origin: { x: 0, y: 0, z: 0 },
x_axis,
y_axis,
clobber: false,
hide: hidden,
},
cmd_id: planeId,
})
await engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd: {
type: 'plane_set_color',
plane_id: planeId,
color,
},
cmd_id: uuidv4(),
})
return planeId
}
export function setDefaultPlanesHidden(
engineCommandManager: EngineCommandManager,
defaultPlanes: DefaultPlanes,
hidden: boolean
) {
Object.values(defaultPlanes).forEach((planeId) => {
hidePlane(engineCommandManager, planeId, hidden)
})
}
function hidePlane(
engineCommandManager: EngineCommandManager,
planeId: string,
hidden: boolean
) {
engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'object_visible',
object_id: planeId,
hidden: hidden,
},
})
}
export async function initDefaultPlanes(
engineCommandManager: EngineCommandManager,
hidePlanes?: boolean
): Promise<DefaultPlanes | null> {
if (!engineCommandManager.engineConnection?.isReady()) {
return null
}
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 },
hidden: hidePlanes ? true : false,
})
if (hidePlanes) {
hidePlane(engineCommandManager, xy, true)
}
const yz = await 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 },
hidden: hidePlanes ? true : false,
})
if (hidePlanes) {
hidePlane(engineCommandManager, yz, true)
}
const xz = await 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 },
hidden: hidePlanes ? true : false,
})
return { xy, yz, xz }
}
function isCursorInSketchCommandRange(
artifactMap: ArtifactMap, artifactMap: ArtifactMap,
selectionRanges: Selections selectionRanges: Selections
): string | false { ): string | false {

View File

@ -1,13 +1,14 @@
import { useEffect } from 'react' import { useEffect } from 'react'
import { useStore } from 'useStore' import { useStore } from 'useStore'
import { engineCommandManager } from '../lang/std/engineConnection' import { engineCommandManager } from '../lang/std/engineConnection'
import { useModelingContext } from './useModelingContext'
export function useEngineConnectionSubscriptions() { export function useEngineConnectionSubscriptions() {
const { setCursor2, setHighlightRange, highlightRange } = useStore((s) => ({ const { setHighlightRange, highlightRange } = useStore((s) => ({
setCursor2: s.setCursor2,
setHighlightRange: s.setHighlightRange, setHighlightRange: s.setHighlightRange,
highlightRange: s.highlightRange, highlightRange: s.highlightRange,
})) }))
const { send } = useModelingContext()
useEffect(() => { useEffect(() => {
if (!engineCommandManager) return if (!engineCommandManager) return
@ -30,16 +31,25 @@ export function useEngineConnectionSubscriptions() {
event: 'select_with_point', event: 'select_with_point',
callback: ({ data }) => { callback: ({ data }) => {
if (!data?.entity_id) { if (!data?.entity_id) {
setCursor2() send({
type: 'Set selection',
data: { selectionType: 'singleCodeCursor' },
})
return return
} }
const sourceRange = engineCommandManager.sourceRangeMap[data.entity_id] const sourceRange = engineCommandManager.sourceRangeMap[data.entity_id]
setCursor2({ range: sourceRange, type: 'default' }) send({
type: 'Set selection',
data: {
selectionType: 'singleCodeCursor',
selection: { range: sourceRange, type: 'default' },
},
})
}, },
}) })
return () => { return () => {
unSubHover() unSubHover()
unSubClick() unSubClick()
} }
}, [engineCommandManager, setCursor2, setHighlightRange, highlightRange]) }, [engineCommandManager, setHighlightRange, highlightRange])
} }

View File

@ -0,0 +1,6 @@
import { ModelingMachineContext } from 'components/ModelingMachineProvider'
import { useContext } from 'react'
export const useModelingContext = () => {
return useContext(ModelingMachineContext)
}

View File

@ -1,8 +1,9 @@
import { useLayoutEffect, useEffect, useRef } from 'react' import { useLayoutEffect, useEffect, useRef } from 'react'
import { _executor } from '../lang/wasm' import { _executor, parse } from '../lang/wasm'
import { useStore } from '../useStore' import { useStore } from '../useStore'
import { engineCommandManager } from '../lang/std/engineConnection' import { engineCommandManager } from '../lang/std/engineConnection'
import { deferExecution } from 'lib/utils' import { deferExecution } from 'lib/utils'
import { kclManager } from 'lang/KclSinglton'
export function useSetupEngineManager( export function useSetupEngineManager(
streamRef: React.RefObject<HTMLDivElement>, streamRef: React.RefObject<HTMLDivElement>,
@ -13,13 +14,11 @@ export function useSetupEngineManager(
setIsStreamReady, setIsStreamReady,
setStreamDimensions, setStreamDimensions,
streamDimensions, streamDimensions,
executeCode,
} = useStore((s) => ({ } = useStore((s) => ({
setMediaStream: s.setMediaStream, setMediaStream: s.setMediaStream,
setIsStreamReady: s.setIsStreamReady, setIsStreamReady: s.setIsStreamReady,
setStreamDimensions: s.setStreamDimensions, setStreamDimensions: s.setStreamDimensions,
streamDimensions: s.streamDimensions, streamDimensions: s.streamDimensions,
executeCode: s.executeCode,
})) }))
const streamWidth = streamRef?.current?.offsetWidth const streamWidth = streamRef?.current?.offsetWidth
@ -28,7 +27,7 @@ export function useSetupEngineManager(
const hasSetNonZeroDimensions = useRef<boolean>(false) const hasSetNonZeroDimensions = useRef<boolean>(false)
useEffect(() => { useEffect(() => {
executeCode() kclManager.executeCode()
}, []) }, [])
useLayoutEffect(() => { useLayoutEffect(() => {
@ -44,7 +43,10 @@ export function useSetupEngineManager(
setIsStreamReady, setIsStreamReady,
width: quadWidth, width: quadWidth,
height: quadHeight, height: quadHeight,
executeCode, executeCode: (code?: string) => {
const _ast = parse(code || kclManager.code)
return kclManager.executeAst(_ast, true)
},
token, token,
}) })
setStreamDimensions({ setStreamDimensions({

View File

@ -1,52 +1,43 @@
import { SetVarNameModal } from 'components/SetVarNameModal' import { SetVarNameModal } from 'components/SetVarNameModal'
import { kclManager } from 'lang/KclSinglton'
import { moveValueIntoNewVariable } from 'lang/modifyAst' import { moveValueIntoNewVariable } from 'lang/modifyAst'
import { isNodeSafeToReplace } from 'lang/queryAst' import { isNodeSafeToReplace } from 'lang/queryAst'
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { create } from 'react-modal-promise' import { create } from 'react-modal-promise'
import { useStore } from 'useStore' import { useModelingContext } from './useModelingContext'
const getModalInfo = create(SetVarNameModal as any) const getModalInfo = create(SetVarNameModal as any)
export function useConvertToVariable() { export function useConvertToVariable() {
const { guiMode, selectionRanges, ast, programMemory, updateAst } = useStore( const { context } = useModelingContext()
(s) => ({
guiMode: s.guiMode,
ast: s.ast,
updateAst: s.updateAst,
selectionRanges: s.selectionRanges,
programMemory: s.programMemory,
})
)
const [enable, setEnabled] = useState(false) const [enable, setEnabled] = useState(false)
useEffect(() => { useEffect(() => {
if (!ast) return
const { isSafe, value } = isNodeSafeToReplace( const { isSafe, value } = isNodeSafeToReplace(
ast, kclManager.ast,
selectionRanges.codeBasedSelections?.[0]?.range || [] context.selectionRanges.codeBasedSelections?.[0]?.range || []
) )
const canReplace = isSafe && value.type !== 'Identifier' const canReplace = isSafe && value.type !== 'Identifier'
const isOnlyOneSelection = selectionRanges.codeBasedSelections.length === 1 const isOnlyOneSelection =
context.selectionRanges.codeBasedSelections.length === 1
const _enableHorz = canReplace && isOnlyOneSelection const _enableHorz = canReplace && isOnlyOneSelection
setEnabled(_enableHorz) setEnabled(_enableHorz)
}, [guiMode, selectionRanges]) }, [context.selectionRanges])
const handleClick = async () => { const handleClick = async () => {
if (!ast) return
try { try {
const { variableName } = await getModalInfo({ const { variableName } = await getModalInfo({
valueName: 'var', valueName: 'var',
} as any) } as any)
const { modifiedAst: _modifiedAst } = moveValueIntoNewVariable( const { modifiedAst: _modifiedAst } = moveValueIntoNewVariable(
ast, kclManager.ast,
programMemory, kclManager.programMemory,
selectionRanges.codeBasedSelections[0].range, context.selectionRanges.codeBasedSelections[0].range,
variableName variableName
) )
updateAst(_modifiedAst, true) kclManager.updateAst(_modifiedAst, true)
} catch (e) { } catch (e) {
console.log('error', e) console.log('error', e)
} }

View File

@ -4,6 +4,13 @@ import reportWebVitals from './reportWebVitals'
import { Toaster } from 'react-hot-toast' import { Toaster } from 'react-hot-toast'
import { Router } from './Router' import { Router } from './Router'
import { HotkeysProvider } from 'react-hotkeys-hook' import { HotkeysProvider } from 'react-hotkeys-hook'
import { inspect } from '@xstate/inspect'
import { DEV } from 'env'
if (DEV)
inspect({
iframe: false,
})
const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement) const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement)

347
src/lang/KclSinglton.tsx Normal file
View File

@ -0,0 +1,347 @@
import { Selections, executeAst, executeCode } from 'useStore'
import { KCLError } from './errors'
import {
EngineCommandManager,
engineCommandManager,
} from './std/engineConnection'
import { deferExecution } from 'lib/utils'
import {
initPromise,
parse,
PathToNode,
Program,
ProgramMemory,
recast,
} from 'lang/wasm'
import { bracket } from 'lib/exampleKcl'
import { createContext, useContext, useEffect, useState } from 'react'
import { getNodeFromPath } from './queryAst'
const PERSIST_CODE_TOKEN = 'persistCode'
class KclManager {
private _code = bracket
private _ast: Program = {
body: [],
start: 0,
end: 0,
nonCodeMeta: {
nonCodeNodes: {},
start: null,
},
}
private _programMemory: ProgramMemory = {
root: {},
return: null,
}
private _logs: string[] = []
private _kclErrors: KCLError[] = []
private _isExecuting = false
engineCommandManager: EngineCommandManager
private _defferer = deferExecution((code: string) => {
const ast = parse(code)
this.executeAst(ast)
}, 600)
private _isExecutingCallback: (a: boolean) => void = () => {}
private _codeCallBack: (arg: string) => void = () => {}
private _astCallBack: (arg: Program) => void = () => {}
private _programMemoryCallBack: (arg: ProgramMemory) => void = () => {}
private _logsCallBack: (arg: string[]) => void = () => {}
private _kclErrorsCallBack: (arg: KCLError[]) => void = () => {}
get ast() {
return this._ast
}
set ast(ast) {
this._ast = ast
this._astCallBack(ast)
}
get code() {
return this._code
}
set code(code) {
this._code = code
this._codeCallBack(code)
}
get programMemory() {
return this._programMemory
}
set programMemory(programMemory) {
this._programMemory = programMemory
this._programMemoryCallBack(programMemory)
}
get defaultPlanes() {
return this?.engineCommandManager?.defaultPlanes
}
get logs() {
return this._logs
}
set logs(logs) {
this._logs = logs
this._logsCallBack(logs)
}
get kclErrors() {
return this._kclErrors
}
set kclErrors(kclErrors) {
this._kclErrors = kclErrors
this._kclErrorsCallBack(kclErrors)
}
get isExecuting() {
return this._isExecuting
}
set isExecuting(isExecuting) {
this._isExecuting = isExecuting
this._isExecutingCallback(isExecuting)
}
constructor(engineCommandManager: EngineCommandManager) {
this.engineCommandManager = engineCommandManager
const storedCode = localStorage.getItem(PERSIST_CODE_TOKEN)
// TODO #819 remove zustand persistance logic in a few months
// short term migration, shouldn't make a difference for tauri app users
// anyway since that's filesystem based.
const zustandStore = JSON.parse(localStorage.getItem('store') || '{}')
if (storedCode === null && zustandStore?.state?.code) {
this.code = zustandStore.state.code
localStorage.setItem(PERSIST_CODE_TOKEN, this._code)
zustandStore.state.code = ''
localStorage.setItem('store', JSON.stringify(zustandStore))
} else if (storedCode === null) {
this.code = bracket
} else {
this.code = storedCode
}
}
registerCallBacks({
setCode,
setProgramMemory,
setAst,
setLogs,
setKclErrors,
setIsExecuting,
}: {
setCode: (arg: string) => void
setProgramMemory: (arg: ProgramMemory) => void
setAst: (arg: Program) => void
setLogs: (arg: string[]) => void
setKclErrors: (arg: KCLError[]) => void
setIsExecuting: (arg: boolean) => void
}) {
this._codeCallBack = setCode
this._programMemoryCallBack = setProgramMemory
this._astCallBack = setAst
this._logsCallBack = setLogs
this._kclErrorsCallBack = setKclErrors
this._isExecutingCallback = setIsExecuting
}
async executeAst(ast: Program = this._ast, updateCode = false) {
this.isExecuting = true
await initPromise
const { logs, errors, programMemory } = await executeAst({
ast,
engineCommandManager: this.engineCommandManager,
defaultPlanes: this.defaultPlanes,
})
this.isExecuting = false
this._logs = logs
this._kclErrors = errors
this._programMemory = programMemory
this._ast = { ...ast }
if (updateCode) {
this._code = recast(ast)
this._codeCallBack(this._code)
}
}
async executeAstMock(ast: Program = this._ast, updateCode = false) {
await initPromise
const newCode = recast(ast)
const newAst = parse(newCode)
await this?.engineCommandManager?.waitForReady
if (updateCode) {
this.setCode(recast(ast))
}
this._ast = { ...newAst }
const { logs, errors, programMemory } = await executeAst({
ast: newAst,
engineCommandManager: this.engineCommandManager,
defaultPlanes: this.defaultPlanes,
useFakeExecutor: true,
})
this._logs = logs
this._kclErrors = errors
this._programMemory = programMemory
}
async executeCode(code?: string) {
await initPromise
await this?.engineCommandManager?.waitForReady
const result = await executeCode({
engineCommandManager,
code: code || this._code,
lastAst: this._ast,
defaultPlanes: this.defaultPlanes,
force: false,
})
if (!result.isChange) return
const { logs, errors, programMemory, ast } = result
this.logs = logs
this.kclErrors = errors
this.programMemory = programMemory
this.ast = ast
if (code) this.code = code
}
setCode(code: string) {
this._code = code
this._codeCallBack(code)
localStorage.setItem(PERSIST_CODE_TOKEN, code)
}
setCodeAndExecute(code: string) {
this.setCode(code)
if (code.trim()) {
this._defferer(code)
return
}
this._ast = {
body: [],
start: 0,
end: 0,
nonCodeMeta: {
nonCodeNodes: {},
start: null,
},
}
this._programMemory = {
root: {},
return: null,
}
this.engineCommandManager.endSession()
}
format() {
this.code = recast(parse(kclManager.code))
}
// There's overlapping resposibility between updateAst and executeAst.
// updateAst was added as it was used a lot before xState migration so makes the port easier.
// but should probably have think about which of the function to keep
async updateAst(
ast: Program,
execute: boolean,
optionalParams?: {
focusPath?: PathToNode
callBack?: (ast: Program) => void
}
): Promise<Selections | null> {
const newCode = recast(ast)
const astWithUpdatedSource = parse(newCode)
optionalParams?.callBack?.(astWithUpdatedSource)
let returnVal: Selections | null = null
this.code = newCode
if (optionalParams?.focusPath) {
const { node } = getNodeFromPath<any>(
astWithUpdatedSource,
optionalParams?.focusPath
)
const { start, end } = node
if (!start || !end) return null
returnVal = {
codeBasedSelections: [
{
type: 'default',
range: [start, end],
},
],
otherSelections: [],
}
}
if (execute) {
// Call execute on the set ast.
await this.executeAst(astWithUpdatedSource)
} else {
// When we don't re-execute, we still want to update the program
// memory with the new ast. So we will hit the mock executor
// instead.
await this.executeAstMock(astWithUpdatedSource)
}
return returnVal
}
getPlaneId(axis: 'xy' | 'xz' | 'yz'): string {
return this.defaultPlanes[axis]
}
showPlanes() {
this.engineCommandManager.setPlaneHidden(this.defaultPlanes.xy, false)
this.engineCommandManager.setPlaneHidden(this.defaultPlanes.yz, false)
this.engineCommandManager.setPlaneHidden(this.defaultPlanes.xz, false)
}
hidePlanes() {
this.engineCommandManager.setPlaneHidden(this.defaultPlanes.xy, true)
this.engineCommandManager.setPlaneHidden(this.defaultPlanes.yz, true)
this.engineCommandManager.setPlaneHidden(this.defaultPlanes.xz, true)
}
}
export const kclManager = new KclManager(engineCommandManager)
const KclContext = createContext({
code: kclManager.code,
programMemory: kclManager.programMemory,
ast: kclManager.ast,
isExecuting: kclManager.isExecuting,
errors: kclManager.kclErrors,
logs: kclManager.logs,
})
export function useKclContext() {
return useContext(KclContext)
}
export function KclContextProvider({
children,
}: {
children: React.ReactNode
}) {
const [code, setCode] = useState(kclManager.code)
const [programMemory, setProgramMemory] = useState(kclManager.programMemory)
const [ast, setAst] = useState(kclManager.ast)
const [isExecuting, setIsExecuting] = useState(false)
const [errors, setErrors] = useState<KCLError[]>([])
const [logs, setLogs] = useState<string[]>([])
useEffect(() => {
kclManager.registerCallBacks({
setCode,
setProgramMemory,
setAst,
setLogs,
setKclErrors: setErrors,
setIsExecuting,
})
}, [])
return (
<KclContext.Provider
value={{
code,
programMemory,
ast,
isExecuting,
errors,
logs,
}}
>
{children}
</KclContext.Provider>
)
}

View File

@ -72,7 +72,7 @@ export class KCLUndefinedValueError extends KCLError {
* Currently the diagnostics are all errors, but in the future they could include lints. * Currently the diagnostics are all errors, but in the future they could include lints.
* */ * */
export function kclErrToDiagnostic(errors: KCLError[]): Diagnostic[] { export function kclErrToDiagnostic(errors: KCLError[]): Diagnostic[] {
return errors.flatMap((err) => { return errors?.flatMap((err) => {
return err.sourceRanges.map(([from, to]) => { return err.sourceRanges.map(([from, to]) => {
return { from, to, message: err.msg, severity: 'error' } return { from, to, message: err.msg, severity: 'error' }
}) })

View File

@ -4,6 +4,8 @@ import {
isNodeSafeToReplace, isNodeSafeToReplace,
isTypeInValue, isTypeInValue,
getNodePathFromSourceRange, getNodePathFromSourceRange,
doesPipeHaveCallExp,
hasExtrudeSketchGroup,
} from './queryAst' } from './queryAst'
import { enginelessExecutor } from '../lib/testHelpers' import { enginelessExecutor } from '../lib/testHelpers'
import { import {
@ -246,3 +248,114 @@ show(part001)`
expect(selectWholeThing).toEqual(expected) expect(selectWholeThing).toEqual(expected)
}) })
}) })
describe('testing doesPipeHave', () => {
it('finds close', () => {
const exampleCode = `const length001 = 2
const part001 = startSketchAt([-1.41, 3.46])
|> line({ to: [19.49, 1.16], tag: 'seg01' }, %)
|> angledLine([-35, length001], %)
|> line([-3.22, -7.36], %)
|> angledLine([-175, segLen('seg01', %)], %)
|> close(%)
`
const ast = parse(exampleCode)
const result = doesPipeHaveCallExp({
calleeName: 'close',
ast,
selection: { type: 'default', range: [100, 101] },
})
expect(result).toEqual(true)
})
it('finds extrude', () => {
const exampleCode = `const length001 = 2
const part001 = startSketchAt([-1.41, 3.46])
|> line({ to: [19.49, 1.16], tag: 'seg01' }, %)
|> angledLine([-35, length001], %)
|> line([-3.22, -7.36], %)
|> angledLine([-175, segLen('seg01', %)], %)
|> close(%)
|> extrude(1, %)
`
const ast = parse(exampleCode)
const result = doesPipeHaveCallExp({
calleeName: 'extrude',
ast,
selection: { type: 'default', range: [100, 101] },
})
expect(result).toEqual(true)
})
it('does NOT find close', () => {
const exampleCode = `const length001 = 2
const part001 = startSketchAt([-1.41, 3.46])
|> line({ to: [19.49, 1.16], tag: 'seg01' }, %)
|> angledLine([-35, length001], %)
|> line([-3.22, -7.36], %)
|> angledLine([-175, segLen('seg01', %)], %)
`
const ast = parse(exampleCode)
const result = doesPipeHaveCallExp({
calleeName: 'close',
ast,
selection: { type: 'default', range: [100, 101] },
})
expect(result).toEqual(false)
})
it('returns false if not a pipe', () => {
const exampleCode = `const length001 = 2`
const ast = parse(exampleCode)
const result = doesPipeHaveCallExp({
calleeName: 'close',
ast,
selection: { type: 'default', range: [9, 10] },
})
expect(result).toEqual(false)
})
})
describe('testing hasExtrudeSketchGroup', () => {
it('find sketch group', async () => {
const exampleCode = `const length001 = 2
const part001 = startSketchAt([-1.41, 3.46])
|> line({ to: [19.49, 1.16], tag: 'seg01' }, %)
|> angledLine([-35, length001], %)
|> line([-3.22, -7.36], %)
|> angledLine([-175, segLen('seg01', %)], %)`
const ast = parse(exampleCode)
const programMemory = await enginelessExecutor(ast)
const result = hasExtrudeSketchGroup({
ast,
selection: { type: 'default', range: [100, 101] },
programMemory,
})
expect(result).toEqual(true)
})
it('find extrude group', async () => {
const exampleCode = `const length001 = 2
const part001 = startSketchAt([-1.41, 3.46])
|> line({ to: [19.49, 1.16], tag: 'seg01' }, %)
|> angledLine([-35, length001], %)
|> line([-3.22, -7.36], %)
|> angledLine([-175, segLen('seg01', %)], %)
|> extrude(1, %)`
const ast = parse(exampleCode)
const programMemory = await enginelessExecutor(ast)
const result = hasExtrudeSketchGroup({
ast,
selection: { type: 'default', range: [100, 101] },
programMemory,
})
expect(result).toEqual(true)
})
it('finds nothing', async () => {
const exampleCode = `const length001 = 2`
const ast = parse(exampleCode)
const programMemory = await enginelessExecutor(ast)
const result = hasExtrudeSketchGroup({
ast,
selection: { type: 'default', range: [10, 11] },
programMemory,
})
expect(result).toEqual(false)
})
})

View File

@ -13,6 +13,7 @@ import {
ProgramMemory, ProgramMemory,
SketchGroup, SketchGroup,
SourceRange, SourceRange,
PipeExpression,
} from './wasm' } from './wasm'
import { createIdentifier, splitPathAtLastIndex } from './modifyAst' import { createIdentifier, splitPathAtLastIndex } from './modifyAst'
import { getSketchSegmentFromSourceRange } from './std/sketchConstraints' import { getSketchSegmentFromSourceRange } from './std/sketchConstraints'
@ -511,3 +512,47 @@ export function isLinesParallelAndConstrained(
} }
} }
} }
export function doesPipeHaveCallExp({
ast,
selection,
calleeName,
}: {
calleeName: string
ast: Program
selection: Selection
}): boolean {
const pathToNode = getNodePathFromSourceRange(ast, selection.range)
const pipeExpression = getNodeFromPath<PipeExpression>(
ast,
pathToNode,
'PipeExpression'
).node
if (pipeExpression.type !== 'PipeExpression') return false
return pipeExpression.body.some(
(expression) =>
expression.type === 'CallExpression' &&
expression.callee.name === calleeName
)
}
export function hasExtrudeSketchGroup({
ast,
selection,
programMemory,
}: {
ast: Program
selection: Selection
programMemory: ProgramMemory
}): boolean {
const pathToNode = getNodePathFromSourceRange(ast, selection.range)
const varDec = getNodeFromPath<VariableDeclaration>(
ast,
pathToNode,
'VariableDeclaration'
).node
if (varDec.type !== 'VariableDeclaration') return false
const varName = varDec.declarations[0].id.name
const varValue = programMemory?.root[varName]
return varValue?.type === 'ExtrudeGroup' || varValue?.type === 'SketchGroup'
}

View File

@ -11,6 +11,7 @@ import { exportSave } from 'lib/exportSave'
import { v4 as uuidv4 } from 'uuid' import { v4 as uuidv4 } from 'uuid'
import * as Sentry from '@sentry/react' import * as Sentry from '@sentry/react'
import { getNodeFromPath, getNodePathFromSourceRange } from 'lang/queryAst' import { getNodeFromPath, getNodePathFromSourceRange } from 'lang/queryAst'
import { DefaultPlanes } from 'wasm-lib/kcl/bindings/DefaultPlanes'
let lastMessage = '' let lastMessage = ''
@ -286,14 +287,18 @@ export class EngineConnection {
const message: Models['WebSocketResponse_type'] = JSON.parse(event.data) const message: Models['WebSocketResponse_type'] = JSON.parse(event.data)
if (!message.success) { if (!message.success) {
if (message.request_id) { const errorsString = message?.errors
console.error(`Error in response to request ${message.request_id}:`) ?.map((error) => {
} else { return ` - ${error.error_code}: ${error.message}`
console.error(`Error from server:`)
}
message?.errors?.forEach((error) => {
console.error(` - ${error.error_code}: ${error.message}`)
}) })
.join('\n')
if (message.request_id) {
console.error(
`Error in response to request ${message.request_id}:\n${errorsString}`
)
} else {
console.error(`Error from server:\n${errorsString}`)
}
return return
} }
@ -593,11 +598,14 @@ export class EngineCommandManager {
outSequence = 1 outSequence = 1
inSequence = 1 inSequence = 1
engineConnection?: EngineConnection engineConnection?: EngineConnection
defaultPlanes: DefaultPlanes = { xy: '', yz: '', xz: '' }
// Folks should realize that wait for ready does not get called _everytime_ // Folks should realize that wait for ready does not get called _everytime_
// the connection resets and restarts, it only gets called the first time. // the connection resets and restarts, it only gets called the first time.
// Be careful what you put here. // Be careful what you put here.
waitForReady: Promise<void> = new Promise(() => {})
private resolveReady = () => {} private resolveReady = () => {}
waitForReady: Promise<void> = new Promise((resolve) => {
this.resolveReady = resolve
})
subscriptions: { subscriptions: {
[event: string]: { [event: string]: {
@ -639,9 +647,6 @@ export class EngineCommandManager {
return return
} }
this.waitForReady = new Promise((resolve) => {
this.resolveReady = resolve
})
const url = `${VITE_KC_API_WS_MODELING_URL}?video_res_width=${width}&video_res_height=${height}` const url = `${VITE_KC_API_WS_MODELING_URL}?video_res_width=${width}&video_res_height=${height}`
this.engineConnection = new EngineConnection({ this.engineConnection = new EngineConnection({
url, url,
@ -669,12 +674,15 @@ export class EngineCommandManager {
}, },
}) })
// Inisialize the planes.
this.initPlanes().then(() => {
// We execute the code here to make sure if the stream was to // We execute the code here to make sure if the stream was to
// restart in a session, we want to make sure to execute the code. // restart in a session, we want to make sure to execute the code.
// We force it to re-execute the code because we want to make sure // We force it to re-execute the code because we want to make sure
// the code is executed everytime the stream is restarted. // the code is executed everytime the stream is restarted.
// We pass undefined for the code so it reads from the current state. // We pass undefined for the code so it reads from the current state.
executeCode(undefined, true) executeCode(undefined, true)
})
}, },
onClose: () => { onClose: () => {
setIsStreamReady(false) setIsStreamReady(false)
@ -1177,6 +1185,95 @@ export class EngineCommandManager {
} }
}) })
} }
private async initPlanes() {
const [xy, yz, xz] = [
await this.createPlane({
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 },
}),
await this.createPlane({
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 },
}),
await this.createPlane({
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 },
}),
]
this.defaultPlanes = { xy, yz, xz }
this.subscribeTo({
event: 'select_with_point',
callback: ({ data }) => {
if (!data?.entity_id) return
if (
![
this.defaultPlanes.xy,
this.defaultPlanes.yz,
this.defaultPlanes.xz,
].includes(data.entity_id)
)
return
this.onPlaneSelectCallback(data.entity_id)
},
})
}
onPlaneSelectCallback = (id: string) => {}
onPlaneSelected(callback: (id: string) => void) {
this.onPlaneSelectCallback = callback
}
async setPlaneHidden(id: string, hidden: boolean): Promise<string> {
return await this.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'object_visible',
object_id: id,
hidden: hidden,
},
})
}
private async createPlane({
x_axis,
y_axis,
color,
}: {
x_axis: Models['Point3d_type']
y_axis: Models['Point3d_type']
color: Models['Color_type']
}): Promise<string> {
const planeId = uuidv4()
await this.sendSceneCommand({
type: 'modeling_cmd_req',
cmd: {
type: 'make_plane',
size: 60,
origin: { x: 0, y: 0, z: 0 },
x_axis,
y_axis,
clobber: false,
hide: true,
},
cmd_id: planeId,
})
await this.sendSceneCommand({
type: 'modeling_cmd_req',
cmd: {
type: 'plane_set_color',
plane_id: planeId,
color,
},
cmd_id: uuidv4(),
})
await this.setPlaneHidden(planeId, true)
return planeId
}
} }
export const engineCommandManager = new EngineCommandManager() export const engineCommandManager = new EngineCommandManager()

View File

@ -113,20 +113,6 @@ show(mySketch001)
programMemory, programMemory,
[sourceStart, sourceStart + lineToChange.length], [sourceStart, sourceStart + lineToChange.length],
[2, 3], [2, 3],
{
mode: 'sketch',
sketchMode: 'sketchEdit',
pathId: '',
rotation: [0, 0, 0, 1],
position: [0, 0, 0],
pathToNode: [
['body', ''],
[0, 'index'],
['declarations', 'VariableDeclaration'],
[0, 'index'],
['init', 'VariableDeclarator'],
],
},
[0, 0] [0, 0]
) )
expect(recast(modifiedAst)).toBe(expectedCode) expect(recast(modifiedAst)).toBe(expectedCode)

View File

@ -18,7 +18,7 @@ import {
getNodePathFromSourceRange, getNodePathFromSourceRange,
} from '../queryAst' } from '../queryAst'
import { isLiteralArrayOrStatic } from './sketchcombos' import { isLiteralArrayOrStatic } from './sketchcombos'
import { GuiModes, toolTips, ToolTip } from '../../useStore' import { toolTips, ToolTip } from '../../useStore'
import { createPipeExpression, splitPathAtPipeExpression } from '../modifyAst' import { createPipeExpression, splitPathAtPipeExpression } from '../modifyAst'
import { generateUuidFromHashSeed } from '../../lib/uuid' import { generateUuidFromHashSeed } from '../../lib/uuid'
@ -908,7 +908,6 @@ export function changeSketchArguments(
programMemory: ProgramMemory, programMemory: ProgramMemory,
sourceRange: SourceRange, sourceRange: SourceRange,
args: [number, number], args: [number, number],
guiMode: GuiModes,
from: [number, number] from: [number, number]
): { modifiedAst: Program } { ): { modifiedAst: Program } {
const _node = { ...node } const _node = { ...node }
@ -917,7 +916,6 @@ export function changeSketchArguments(
_node, _node,
thePath thePath
) )
if (guiMode.mode !== 'sketch') throw new Error('not in sketch mode')
if (callExpression?.callee?.name in sketchLineHelperMap) { if (callExpression?.callee?.name in sketchLineHelperMap) {
const { updateArgs } = sketchLineHelperMap[callExpression.callee.name] const { updateArgs } = sketchLineHelperMap[callExpression.callee.name]

View File

@ -1306,7 +1306,7 @@ export function getRemoveConstraintsTransforms(
return theTransforms return theTransforms
} }
type PathToNodeMap = { [key: number]: PathToNode } export type PathToNodeMap = { [key: number]: PathToNode }
export function transformSecondarySketchLinesTagFirst({ export function transformSecondarySketchLinesTagFirst({
ast, ast,

View File

@ -2,19 +2,18 @@ import { Selections, StoreState } from '../useStore'
import { Program, PathToNode } from './wasm' import { Program, PathToNode } from './wasm'
import { getNodeFromPath } from './queryAst' import { getNodeFromPath } from './queryAst'
export function updateCursors( export function pathMapToSelections(
setCursor: StoreState['setCursor'], ast: Program,
selectionRanges: Selections, prevSelections: Selections,
pathToNodeMap: { [key: number]: PathToNode } pathToNodeMap: { [key: number]: PathToNode }
): (newAst: Program) => void { ): Selections {
return (newAst: Program) => {
const newSelections: Selections = { const newSelections: Selections = {
...selectionRanges, ...prevSelections,
codeBasedSelections: [], codeBasedSelections: [],
} }
Object.entries(pathToNodeMap).forEach(([index, path]) => { Object.entries(pathToNodeMap).forEach(([index, path]) => {
const node = getNodeFromPath(newAst, path).node as any const node = getNodeFromPath(ast, path).node as any
const type = selectionRanges.codeBasedSelections[Number(index)].type const type = prevSelections.codeBasedSelections[Number(index)].type
if (node) { if (node) {
newSelections.codeBasedSelections.push({ newSelections.codeBasedSelections.push({
range: [node.start, node.end], range: [node.start, node.end],
@ -22,8 +21,7 @@ export function updateCursors(
}) })
} }
}) })
setCursor(newSelections) return newSelections
}
} }
export function isReducedMotion(): boolean { export function isReducedMotion(): boolean {

View File

@ -0,0 +1,853 @@
import { PathToNode } from 'lang/wasm'
import { engineCommandManager } from 'lang/std/engineConnection'
import { isReducedMotion } from 'lang/util'
import { Axis, Selection, SelectionRangeTypeMap, Selections } from 'useStore'
import { assign, createMachine } from 'xstate'
import { v4 as uuidv4 } from 'uuid'
import { isCursorInSketchCommandRange } from 'hooks/useAppMode'
import {
doesPipeHaveCallExp,
getNodePathFromSourceRange,
hasExtrudeSketchGroup,
} from 'lang/queryAst'
import { kclManager } from 'lang/KclSinglton'
import {
horzVertInfo,
applyConstraintHorzVert,
} from 'components/Toolbar/HorzVert'
import {
applyConstraintHorzVertAlign,
horzVertDistanceInfo,
} from 'components/Toolbar/SetHorzVertDistance'
import { angleBetweenInfo } from 'components/Toolbar/SetAngleBetween'
import { setAngleLengthInfo } from 'components/Toolbar/setAngleLength'
import {
applyConstraintEqualLength,
setEqualLengthInfo,
} from 'components/Toolbar/EqualLength'
import { extrudeSketch } from 'lang/modifyAst'
import { getNodeFromPath } from '../lang/queryAst'
import { CallExpression, PipeExpression } from '../lang/wasm'
export const MODELING_PERSIST_KEY = 'MODELING_PERSIST_KEY'
export type SetSelections =
| {
selectionType: 'singleCodeCursor'
selection?: Selection
}
| {
selectionType: 'otherSelection'
selection: Axis
}
| {
selectionType: 'completeSelection'
selection: Selections
}
| {
selectionType: 'mirrorCodeMirrorSelections'
selection: Selections
}
export const modelingMachine = createMachine(
{
/** @xstate-layout N4IgpgJg5mDOIC5QFkD2EwBsCWA7KAxAMICGuAxlgNoAMAuoqAA6qzYAu2qujIAHogDMAVgDsAOmEiAjLMGCAHIIAsAJlHKANCACeiaQDZBEsctGrVATmmiaNQQF8H2tBhz5x2CJjAEAymDsAASwWGDknNy0DEggLGyRPLECCMrCCuL20qrCRpYagvnaegjZBtLiBqLploIGCtZVyk4u6Fh4UJ7evgAicGERQSx47NG88RxcSaApdcKSNKIKi8ppwsrLwsX60oriqgaWBgbKImpWqi0gru0eXj4EfaE+g5AwY7ETibyzx5lLu1sygM61ERV0+ho0mU4gURlOwihlis2SuN3cnXuvX6L2CJD42FgH2YrEm3B+QnM4mqdhoyPUILqBm2CAaFTS5houQUCMczmubQxXQeAVxQ1QI2JcVJ32SiGUXPEhjBtWklgaokMzIhqVUNHEG0WgjVqkEUN2aMFHWFvlF4WCbzAUq+UwpqRoGQ2pwacPW4JKJxhgYU0kWu3Wymklrc1qx-gGeIJRPo4xlrrlqUEqkqdMKOSRNEOLPWGTVhkswlU0O5fNaMbu3XjYoAZiRKM60+SMwqMgY7Go6mbalCWaIjLCjtJ0n36tUFNHbpjGwBRXDsMAAJxCAGtAuQABYdhLpmY7SPiSzAyOXwGFUQs01BtXVEEV9VSZr89Gxldrzc7vdD2kGISWPLtTwQepBGpEN8lDGhVBDLYdTUfVZzvYFQ3MS4vytBsHieBMglbdsU0+Ttpn4Sk0KzBR1EKdING1EozQqYxajyNQ6MOBchTjO1BhITBMCPMlKJSSNoIsEEllQrlhGQko9RhZEDFUL1vWEUMcLrRcbUeHF7SCISRLI0CxLdRR2ROJQwQQ2RmMQENJD1OxjR5aE+1rAV6yXB4wD4dgNwAVwwIIRjANdRNlCDlGRJU6kfapK0vdYWSnBQJEsfJMtODYrM-XS+MbAKgtCsBwr-KLgNTMDxPlawJ0DKtXNkLl0o2NjNULPMPQ2HSfL0vxd3YA9iDIShMGGwDopPKjSg0fUaDURFFoVbJ7x1S9oOSmQcl2CtRF461ptG-dxFOg8AElGwE4JhiiszpTqt1pB9C8NA8plynVFlyn1EMzVfdTrAU46PEu87IZukUiNCKAAFtItGJ6XXA+a3vVD6vV2Y41IQlkzAMakLCnD0K0jK9wc6SGLpG67G0IsUHpRkDnosjM3oUi91SsRYjmUywWRUDJRFEY0PXzdQrGpunALls6YexZ4jPhpHHrZtH6tKHkJHkLJ1AUGpsofQxxBEWpxayCXFFl2noZXABHYLsCYIJ2FQVBTM1ijLK5Njp20wtPMEUc+yVRC4vgpZlqjXDfIVg9E-3JWCGXZ3XaCBHUAANwqj2vdm9HZg9NjpPUZbIykFkKYNTD1nsGycjt+modb1OAmCFWIimIvtbVMwczF4wtXqRyEDHfVsiNwtahyTU46Kk7W+T1PkBIXcQjARHkaCPON04cghL716kKVdYjErEWFEy9LoXZKsQT7I3jWEFv5Ydh5183tXd-3VANzYAAF7cHYMfVGvtOZggyOkewqkjBqQrOlOBlQqjyCrEoacR145DRXp-XwRBuCwCCiQPAQR-6AJAWuISQQICEjARQJ0ECXpQOqJULm+QVArFvjqcsMIDg2UwRsdIhVBpCntu3RshDcDEI3KQ3Ae9NyHxoXQ4hE0mE+xYRBGwlh9QpQOp5awdR0piAqOLLS5g0HmlEd+CGeDJEPGkbI+Rxl8A+BPpzOEKk+wOT6nYHk6VNgGkOLkUJOQ1jvzOqvKRRCSFkJ8Pgdgh5mEc20UbDIN4GjHAKopfQjQLzLAVCIrkdE344PEfYwCqcnFxIURQ4BoCTI6GMjgKAuAPHaKWMTJQCFzBZIOEbdK6kYSRg0EsRCdhLyWEiUnfBxBYlyLIfvZRwlmlCWwG0jpGNljQQVMCKohZzBiHSiIHasg1DIhEFpOEMy25VJiTI2pQQwDOxoQkqASStkpDejYA0voqg5HKFOLQvC0gwK0sOPs1g3K3PEAAGTwBVAAKp7TAacM5u2znnd2qKvlCEUDA8ZCk1TpE8qOJQSouQ9SnMIfIGhYUItwMi1F4gAAKEo1xBAAIIQAwBAAgPKIDiklCkmK80zTAnNg0dIUg1JqFWMWKESoThQkyn6I2UgGWIqCCir2F1t7q2CIKyAAreXCo1rVVJ4rEIVEOJlLyZpCz1EJqaA0Bx1JPx+dJLVTKdUsoCDvTlxr+WEIRkwHw64gjuA0ZasVsxgQSBnsPMFyJcmpBvkqXKozlpWDij65leqA2Gu5byk16cXZuyZQAdxxYXUVc1ZgV32G9MeCEjhHGFm9JU2Uq5yGlTYvCNMV6MoLZgcQV1cAcAIHihA6k1T-CuZqXIVc-r2EyPUMQmokJ2AMPmv1eqJ1TqoDVciWj5qmmypkS5cClAghBSUSsxNCglJSosRYu7ynL3liO-dY6AByqAgjspGLAU1QqWYzoZJYC8G0wSvuRMWdSSoim6N0a-TKsKuVVtIcEepVCwGYFofQ9RlVmyoAIBAbgYBPC4BzqgXc4gYDsAALR4caZgZjeAyOQaWPMRCyJNTGmBccdKS7YRmmMG5fs0zP12PllhnD5CAENOoYR1RDDKCkfI5uDcADxDhpIOwMjG4EaMcCKx5T+GhKcdwNx+txdECG3mIdaWjQjaXnSlUDIqroTmHDOsTD2GOCKIPtgI+aniOMK0xRqjNG6MMaY8x5ZYXrNcdQDx3RkhzDytonCYwJjGqIUyvIWwiJnyBcU8l8LRG1FRbSwQHTemDNGYAaZxLVXUu2fS-Z7W6hMtiHUupXLRhNolGBTtG+4tjBwjpLUCrwWyBQB8NFyjTK4v0eo4lxbPgbN2c0ValIfXSynGylBKcVZAmVgvB6ZayxR4enm3iNxFV6uNY3PpzAhnjNtfM9tsAu3uv7bjY5lUkhgTqnbR6qcgTdhXtkDYeHxojiPajZFD5+4VuxbwPFzb5n3lJIBxl4m6hKwKQ0Ju1Q6V1QVGWh6E4Fh8wbBR-jjHr2Ny6fe8177ZmWMs8Jz1t0fXibbSrPkBUsc01qj2Cqxu8gn5KFuUEXAgGSK2iIhgVswVMD3U+0ymd2Q4qSArG9a8PJgShxQlOGDUI6hG2nuYbytjOgkCC5wfAW9cRTHjF3BMvcBec2DtSXRY4eTHEjND3hy7qR9g2Oqe1l55yyc6AecI24Oge-tF7-Xoz9jLo5AcXpJi1J-NWOaOosdZYp-IGn933dEjTpPeZYHpRKaVGNBXDQ6pwcdSkOwlQtLr3LBwvyZXGB4CxCd7GhtQg6SSBkHIRQKh1D3sQMx5zyxzRSCkLog4i8xE-h8FPhzqRi-b8OYcTU8ILdjcHBHXRdI3ohlOI7wdycj-90ONmHNq09lQnUCyZydzRQTqLSY4KcWFfBd-V6S5fYJkG3eQQoKoGuPUXPKQBAkMB3AdBOWmH9XVTAKAjMaSEwS5WlaoLMCwFfWdM0SoHtK+RCDBGWJPZOeFbVPA8QHoKjAgiCYwag3IcWG1VVBkRDaCZ9f6WlKwBXJgnA1gllYDINUtCALg89NDc2ThXKBAk4UbRASsIMFDG+EEdYCwPdNgotXeYNJQxtcOODCmfZIcRDXQrkOPAw84YwllQ9dgCwxzV+I3U0UgqQBnSg65fYC9FQFQU4OkD9JeOTKJXAllADIDDldgcfJvafWdE4YmU4H6U0aeOEewyoFDOkLIRiWFNAbFPAzwhAErLKQFMwBNOoaQGuagy8coVaeeKoGTKIodeTV3JTShdjGrDTF7LrCoi9JaGwARDBAfTzfrXIIrUJKEEEFHDrCLWrTTNLEYhUHaQcKEHNbIZaExDYWA0AjiCwI4RPTo5ghTBbZ7LTDY35PUIwIcZaEQJQQJRYTIEWBEewOoCsZnNHJJW409A7RzcPD6d8dBCFXIKnEQTINtZKaoPUAaJ3ZOJXFXNsMACos46kEQVVawCsQsSnFCd4woHkCItqKsWWF3HDdPOvOaLWN0XKC8A5dzG1PgkxTKHmJYAofKJ-SvfcVPGk33dGekzmBnSQE4anQ2OKQodk7MWQCxREFVOiJwJwIAA */
id: 'Modeling',
tsTypes: {} as import('./modelingMachine.typegen').Typegen0,
predictableActionArguments: true,
preserveActionOrder: true,
context: {
guiMode: 'default',
selection: [] as string[],
selectionRanges: {
otherSelections: [],
codeBasedSelections: [],
} as Selections,
selectionRangeTypeMap: {} as SelectionRangeTypeMap,
sketchPathToNode: null as PathToNode | null, // maybe too specific, and we should have a generic pathToNode, but being specific seems less risky when I'm not sure
sketchEnginePathId: '' as string,
sketchPlaneId: '' as string,
},
schema: {
events: {} as
| { type: 'Deselect all' }
| { type: 'Deselect edge'; data: Selection & { type: 'edge' } }
| { type: 'Deselect axis'; data: Axis }
| {
type: 'Deselect segment'
data: Selection & { type: 'line' | 'arc' }
}
| { type: 'Deselect face'; data: Selection & { type: 'face' } }
| {
type: 'Deselect point'
data: Selection & { type: 'point' | 'line-end' | 'line-mid' }
}
| { type: 'Enter sketch' }
| { type: 'Select all'; data: Selection & { type: 'all ' } }
| { type: 'Select edge'; data: Selection & { type: 'edge' } }
| { type: 'Select axis'; data: Axis }
| { type: 'Select segment'; data: Selection & { type: 'line' | 'arc' } }
| { type: 'Select face'; data: Selection & { type: 'face' } }
| { type: 'Select default plane'; data: { planeId: string } }
| { type: 'Set selection'; data: SetSelections }
| {
type: 'Select point'
data: Selection & { type: 'point' | 'line-end' | 'line-mid' }
}
| { type: 'Sketch no face' }
| { type: 'Toggle gui mode' }
| { type: 'Cancel' }
| { type: 'CancelSketch' }
| {
type: 'Add point'
data: {
coords: { x: number; y: number }[]
axis: 'xy' | 'xz' | 'yz' | '-xy' | '-xz' | '-yz' | null
}
}
| { type: 'Equip tool' }
| { type: 'Equip move tool' }
| { type: 'Set radius' }
| { type: 'Complete line' }
| { type: 'Set distance' }
| { type: 'Equip new tool' }
| { type: 'update_code'; data: string }
| { type: 'Make segment horizontal' }
| { type: 'Make segment vertical' }
| { type: 'Constrain horizontal distance' }
| { type: 'Constrain vertical distance' }
| { type: 'Constrain angle' }
| { type: 'Constrain horizontally align' }
| { type: 'Constrain vertically align' }
| { type: 'Constrain length' }
| { type: 'Constrain equal length' }
| { type: 'extrude intent' },
// ,
},
states: {
idle: {
on: {
'Set selection': {
target: 'idle',
internal: true,
actions: 'Set selection',
},
'Deselect point': {
target: 'idle',
internal: true,
actions: [
'Remove from code-based selection',
'Update code selection cursors',
// 'Engine: remove highlight',
],
cond: 'Selection contains point',
},
'Deselect edge': {
target: 'idle',
internal: true,
actions: [
'Remove from code-based selection',
'Update code selection cursors',
// 'Engine: remove highlight',
],
cond: 'Selection contains edge',
},
'Deselect axis': {
target: 'idle',
internal: true,
actions: [
'Remove from other selection',
'Update code selection cursors',
// 'Engine: remove highlight',
],
cond: 'Selection contains axis',
},
'Select point': {
target: 'idle',
internal: true,
actions: [
'Add to code-based selection',
'Update code selection cursors',
// 'Engine: add highlight',
],
},
'Select edge': {
target: 'idle',
internal: true,
actions: [
'Add to code-based selection',
'Update code selection cursors',
// 'Engine: add highlight',
],
},
'Select axis': {
target: 'idle',
internal: true,
actions: [
'Add to other selection',
// 'Engine: add highlight',
],
},
'Select face': {
target: 'idle',
internal: true,
actions: [
'Add to code-based selection',
'Update code selection cursors',
// 'Engine: add highlight',
],
},
'Enter sketch': [
{
target: 'Sketch',
cond: 'Selection is one face',
actions: [
'set sketch metadata',
'sketch mode enabled',
'edit mode enter',
],
},
'Sketch no face',
],
'Deselect face': {
target: 'idle',
internal: true,
actions: [
'Remove from code-based selection',
'Update code selection cursors',
// 'Engine: remove highlight',
],
cond: 'Selection contains face',
},
'Select all': {
target: 'idle',
internal: true,
actions: 'Add to code-based selection',
},
'Deselect all': {
target: 'idle',
internal: true,
actions: [
'Clear selection',
'Update code selection cursors',
// 'Engine: remove highlight',
],
cond: 'Selection is not empty',
},
'extrude intent': [
{
target: 'awaiting selection',
cond: 'has no selection',
},
{
target: 'idle',
cond: 'has valid extrude selection',
internal: true,
actions: 'AST extrude',
},
],
},
},
Sketch: {
states: {
SketchIdle: {
on: {
'Select point': {
target: 'SketchIdle',
internal: true,
actions: [
'Update code selection cursors',
'Add to code-based selection',
],
},
'Select segment': {
target: 'SketchIdle',
internal: true,
actions: [
'Update code selection cursors',
'Add to code-based selection',
],
},
'Deselect point': {
target: 'SketchIdle',
internal: true,
cond: 'Selection contains point',
actions: [
'Update code selection cursors',
'Add to code-based selection',
],
},
'Deselect segment': {
target: 'SketchIdle',
internal: true,
cond: 'Selection contains line',
actions: [
'Update code selection cursors',
'Add to code-based selection',
],
},
'Equip tool': {
target: 'Line Tool',
actions: 'set tool line',
},
'Equip move tool': 'Move Tool',
'Set selection': {
target: 'SketchIdle',
internal: true,
actions: 'Set selection',
},
'Make segment vertical': {
cond: 'Can make selection vertical',
target: 'SketchIdle',
internal: true,
actions: ['Make selection vertical'],
},
'Make segment horizontal': {
target: 'SketchIdle',
internal: true,
cond: 'Can make selection horizontal',
actions: ['Make selection horizontal'],
},
'Constrain horizontal distance': {
target: 'Await horizontal distance info',
cond: 'Can constrain horizontal distance',
},
'Constrain vertical distance': {
target: 'Await vertical distance info',
cond: 'Can constrain vertical distance',
},
'Constrain angle': {
target: 'Await angle info',
cond: 'Can constrain angle',
},
'Constrain length': {
target: 'Await length info',
cond: 'Can constrain length',
},
'Constrain horizontally align': {
cond: 'Can constrain horizontally align',
target: 'SketchIdle',
internal: true,
actions: ['Constrain horizontally align'],
},
'Constrain vertically align': {
cond: 'Can constrain vertically align',
target: 'SketchIdle',
internal: true,
actions: ['Constrain vertically align'],
},
'Constrain equal length': {
cond: 'Can constrain equal length',
target: 'SketchIdle',
internal: true,
actions: ['Constrain equal length'],
},
},
entry: 'equip select',
},
'Line Tool': {
states: {
Done: {
type: 'final',
},
'Point Added': {
on: {
'Add point': {
target: 'Segment Added',
actions: ['AST start new sketch'],
},
},
},
'Segment Added': {
on: {
'Add point': {
target: 'Segment Added',
internal: true,
actions: ['AST add line segment'],
},
'Complete line': {
target: 'Done',
actions: ['Modify AST', 'Update code selection cursors'],
},
'Equip new tool': {
target: 'Segment Added',
internal: true,
actions: 'set tool',
},
},
},
Init: {
always: [
{
target: 'Segment Added',
cond: 'is editing existing sketch',
},
'No Points',
],
},
'No Points': {
on: {
'Add point': 'Point Added',
},
},
},
// invoke: [
// {
// src: 'createLine',
// id: 'Create line',
// onDone: 'SketchIdle',
// },
// ],
initial: 'Init',
on: {
'Equip move tool': 'Move Tool',
},
},
'Move Tool': {
entry: 'set tool move',
},
'Await horizontal distance info': {
invoke: {
src: 'Get horizontal info',
id: 'get-horizontal-info',
onDone: {
target: 'SketchIdle',
actions: 'Set selection',
},
onError: 'SketchIdle',
},
},
'Await vertical distance info': {
invoke: {
src: 'Get vertical info',
id: 'get-vertical-info',
onDone: {
target: 'SketchIdle',
actions: 'Set selection',
},
onError: 'SketchIdle',
},
},
'Await angle info': {
invoke: {
src: 'Get angle info',
id: 'get-angle-info',
onDone: {
target: 'SketchIdle',
actions: 'Set selection',
},
onError: 'SketchIdle',
},
},
'Await length info': {
invoke: {
src: 'Get length info',
id: 'get-length-info',
onDone: {
target: 'SketchIdle',
actions: 'Set selection',
},
onError: 'SketchIdle',
},
},
},
initial: 'SketchIdle',
on: {
CancelSketch: '.SketchIdle',
},
exit: 'sketch exit execute',
},
'Sketch no face': {
entry: 'show default planes',
exit: 'hide default planes',
on: {
'Select default plane': {
target: 'Sketch',
actions: [
'reset sketch metadata',
'set default plane id',
'sketch mode enabled',
'create path',
],
},
},
},
'awaiting selection': {
on: {
'Set selection': {
target: 'checking selection',
actions: 'Set selection',
},
},
},
'checking selection': {
always: [
{
target: 'idle',
cond: 'has valid extrude selection',
actions: 'AST extrude',
},
{
target: 'idle',
actions: 'toast extrude failed',
},
],
},
},
initial: 'idle',
on: {
Cancel: {
target: 'idle',
// TODO what if we're existing extrude equiped, should these actions still be fired?
// mabye cancel needs to have a guard for if else logic?
actions: [
'edit_mode_exit',
'default_camera_disable_sketch_mode',
'reset sketch metadata',
],
},
},
},
{
guards: {
'is editing existing sketch': ({ sketchPathToNode }) =>
!!sketchPathToNode,
'Can make selection horizontal': ({ selectionRanges }) =>
horzVertInfo(selectionRanges, 'horizontal').enabled,
'Can make selection vertical': ({ selectionRanges }) =>
horzVertInfo(selectionRanges, 'vertical').enabled,
'Can constrain horizontal distance': ({ selectionRanges }) =>
horzVertDistanceInfo({ selectionRanges, constraint: 'setHorzDistance' })
.enabled,
'Can constrain vertical distance': ({ selectionRanges }) =>
horzVertDistanceInfo({ selectionRanges, constraint: 'setVertDistance' })
.enabled,
'Can constrain angle': ({ selectionRanges }) =>
angleBetweenInfo({ selectionRanges }).enabled,
'Can constrain length': ({ selectionRanges }) =>
setAngleLengthInfo({ selectionRanges }).enabled,
'Can constrain horizontally align': ({ selectionRanges }) =>
horzVertDistanceInfo({ selectionRanges, constraint: 'setHorzDistance' })
.enabled,
'Can constrain vertically align': ({ selectionRanges }) =>
horzVertDistanceInfo({ selectionRanges, constraint: 'setHorzDistance' })
.enabled,
'Can constrain equal length': ({ selectionRanges }) =>
setEqualLengthInfo({ selectionRanges }).enabled,
'has no selection': ({ selectionRanges }) => {
if (selectionRanges?.codeBasedSelections?.length < 1) return true
const selection = selectionRanges?.codeBasedSelections?.[0] || {}
return (
selectionRanges.codeBasedSelections.length === 1 &&
!hasExtrudeSketchGroup({
ast: kclManager.ast,
programMemory: kclManager.programMemory,
selection,
})
)
},
'has valid extrude selection': ({ selectionRanges }) => {
if (selectionRanges.codeBasedSelections.length !== 1) return false
const isSketchPipe = isCursorInSketchCommandRange(
engineCommandManager.artifactMap,
selectionRanges
)
const common = {
selection: selectionRanges.codeBasedSelections[0],
ast: kclManager.ast,
}
const hasClose = doesPipeHaveCallExp({ calleeName: 'close', ...common })
const hasExtrude = doesPipeHaveCallExp({
calleeName: 'extrude',
...common,
})
return !!isSketchPipe && hasClose && !hasExtrude
},
},
actions: {
'Add to code-based selection': assign({
selectionRanges: ({ selectionRanges }, event) => ({
...selectionRanges,
codeBasedSelections: [
...selectionRanges.codeBasedSelections,
event.data,
],
}),
}),
'Add to other selection': assign({
selectionRanges: ({ selectionRanges }, event) => ({
...selectionRanges,
otherSelections: [...selectionRanges.otherSelections, event.data],
}),
}),
'Remove from code-based selection': assign({
selectionRanges: ({ selectionRanges }, event) => ({
...selectionRanges,
codeBasedSelections: [
...selectionRanges.codeBasedSelections,
event.data,
],
}),
}),
'Remove from other selection': assign({
selectionRanges: ({ selectionRanges }, event) => ({
...selectionRanges,
otherSelections: [...selectionRanges.otherSelections, event.data],
}),
}),
'Clear selection': assign({
selectionRanges: () => ({
otherSelections: [],
codeBasedSelections: [],
}),
}),
'sketch mode enabled': ({ sketchPlaneId }) => {
engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'sketch_mode_enable',
plane_id: sketchPlaneId,
ortho: true,
animated: !isReducedMotion(),
},
})
},
'edit mode enter': ({ selectionRanges }) => {
const pathId = isCursorInSketchCommandRange(
engineCommandManager.artifactMap,
selectionRanges
)
pathId &&
engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'edit_mode_enter',
target: pathId,
},
})
},
'hide default planes': ({}) => {
kclManager.hidePlanes()
},
edit_mode_exit: () =>
engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: { type: 'edit_mode_exit' },
}),
default_camera_disable_sketch_mode: () =>
engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: { type: 'default_camera_disable_sketch_mode' },
}),
'reset sketch metadata': assign({
sketchPathToNode: null,
sketchEnginePathId: '',
sketchPlaneId: '',
}),
'set sketch metadata': assign(({ selectionRanges }) => {
const sourceRange = selectionRanges.codeBasedSelections[0].range
const sketchPathToNode = getNodePathFromSourceRange(
kclManager.ast,
sourceRange
)
const pipeExpression = getNodeFromPath<PipeExpression>(
kclManager.ast,
sketchPathToNode,
'PipeExpression'
).node
if (pipeExpression.type !== 'PipeExpression') return {}
const sketchCallExpression = pipeExpression.body.find(
(e) =>
e.type === 'CallExpression' && e.callee.name === 'startSketchOn'
) as CallExpression
if (!sketchCallExpression) return {}
const firstArg = sketchCallExpression.arguments[0]
let planeId = ''
if (firstArg.type === 'Literal' && firstArg.value) {
const planeStrCleaned = firstArg.value
.toString()
.toLowerCase()
.replace('-', '')
if (
planeStrCleaned === 'xy' ||
planeStrCleaned === 'xz' ||
planeStrCleaned === 'yz'
) {
planeId = kclManager.getPlaneId(planeStrCleaned)
}
}
console.log('planeId', planeId)
const sketchEnginePathId =
isCursorInSketchCommandRange(
engineCommandManager.artifactMap,
selectionRanges
) || ''
return {
sketchPathToNode,
sketchEnginePathId,
sketchPlaneId: planeId,
}
}),
'set tool line': () =>
engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'set_tool',
tool: 'sketch_line',
},
}),
'equip select': () =>
engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'set_tool',
tool: 'select',
},
}),
'set tool move': () =>
engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'set_tool',
tool: 'move',
},
}),
'Make selection horizontal': ({ selectionRanges }) => {
const { modifiedAst, pathToNodeMap } = applyConstraintHorzVert(
selectionRanges,
'horizontal',
kclManager.ast,
kclManager.programMemory
)
kclManager.updateAst(modifiedAst, true, {
// TODO re implement cursor shit
// callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
})
},
'Make selection vertical': ({ selectionRanges }) => {
const { modifiedAst, pathToNodeMap } = applyConstraintHorzVert(
selectionRanges,
'vertical',
kclManager.ast,
kclManager.programMemory
)
kclManager.updateAst(modifiedAst, true, {
// TODO re implement cursor shit
// callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
})
},
'Constrain horizontally align': ({ selectionRanges }) => {
const { modifiedAst, pathToNodeMap } = applyConstraintHorzVertAlign({
selectionRanges,
constraint: 'setVertDistance',
})
kclManager.updateAst(modifiedAst, true, {
// TODO re implement cursor shit
// callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
})
},
'Constrain vertically align': ({ selectionRanges }) => {
const { modifiedAst, pathToNodeMap } = applyConstraintHorzVertAlign({
selectionRanges,
constraint: 'setHorzDistance',
})
kclManager.updateAst(modifiedAst, true, {
// TODO re implement cursor shit
// callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
})
},
'Constrain equal length': ({ selectionRanges }) => {
const { modifiedAst, pathToNodeMap } = applyConstraintEqualLength({
selectionRanges,
})
kclManager.updateAst(modifiedAst, true, {
// TODO re implement cursor shit
// callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
})
},
'AST extrude': ({ selectionRanges }) => {
const pathToNode = getNodePathFromSourceRange(
kclManager.ast,
selectionRanges.codeBasedSelections[0].range
)
const { modifiedAst, pathToExtrudeArg } = extrudeSketch(
kclManager.ast,
pathToNode
)
// TODO not handling focusPath correctly I think
kclManager.updateAst(modifiedAst, true, {
focusPath: pathToExtrudeArg,
})
},
'set default plane id': assign({
sketchPlaneId: (_, { data }) => data.planeId,
}),
},
}
)

View File

@ -0,0 +1,98 @@
// This file was automatically generated. Edits will be overwritten
export interface Typegen0 {
'@@xstate/typegen': true;
internalEvents: {
"": { type: "" };
"done.invoke.get-angle-info": { type: "done.invoke.get-angle-info"; data: unknown; __tip: "See the XState TS docs to learn how to strongly type this." };
"done.invoke.get-horizontal-info": { type: "done.invoke.get-horizontal-info"; data: unknown; __tip: "See the XState TS docs to learn how to strongly type this." };
"done.invoke.get-length-info": { type: "done.invoke.get-length-info"; data: unknown; __tip: "See the XState TS docs to learn how to strongly type this." };
"done.invoke.get-vertical-info": { type: "done.invoke.get-vertical-info"; data: unknown; __tip: "See the XState TS docs to learn how to strongly type this." };
"error.platform.get-angle-info": { type: "error.platform.get-angle-info"; data: unknown };
"error.platform.get-horizontal-info": { type: "error.platform.get-horizontal-info"; data: unknown };
"error.platform.get-length-info": { type: "error.platform.get-length-info"; data: unknown };
"error.platform.get-vertical-info": { type: "error.platform.get-vertical-info"; data: unknown };
"xstate.init": { type: "xstate.init" };
"xstate.stop": { type: "xstate.stop" };
};
invokeSrcNameMap: {
"Get angle info": "done.invoke.get-angle-info";
"Get horizontal info": "done.invoke.get-horizontal-info";
"Get length info": "done.invoke.get-length-info";
"Get vertical info": "done.invoke.get-vertical-info";
};
missingImplementations: {
actions: "AST add line segment" | "AST start new sketch" | "Modify AST" | "Set selection" | "Update code selection cursors" | "create path" | "set tool" | "show default planes" | "sketch exit execute" | "toast extrude failed";
delays: never;
guards: "Selection contains axis" | "Selection contains edge" | "Selection contains face" | "Selection contains line" | "Selection contains point" | "Selection is not empty" | "Selection is one face";
services: "Get angle info" | "Get horizontal info" | "Get length info" | "Get vertical info";
};
eventsCausingActions: {
"AST add line segment": "Add point";
"AST extrude": "" | "extrude intent";
"AST start new sketch": "Add point";
"Add to code-based selection": "Deselect point" | "Deselect segment" | "Select all" | "Select edge" | "Select face" | "Select point" | "Select segment";
"Add to other selection": "Select axis";
"Clear selection": "Deselect all";
"Constrain equal length": "Constrain equal length";
"Constrain horizontally align": "Constrain horizontally align";
"Constrain vertically align": "Constrain vertically align";
"Make selection horizontal": "Make segment horizontal";
"Make selection vertical": "Make segment vertical";
"Modify AST": "Complete line";
"Remove from code-based selection": "Deselect edge" | "Deselect face" | "Deselect point";
"Remove from other selection": "Deselect axis";
"Set selection": "Set selection" | "done.invoke.get-angle-info" | "done.invoke.get-horizontal-info" | "done.invoke.get-length-info" | "done.invoke.get-vertical-info";
"Update code selection cursors": "Complete line" | "Deselect all" | "Deselect axis" | "Deselect edge" | "Deselect face" | "Deselect point" | "Deselect segment" | "Select edge" | "Select face" | "Select point" | "Select segment";
"create path": "Select default plane";
"default_camera_disable_sketch_mode": "Cancel";
"edit mode enter": "Enter sketch";
"edit_mode_exit": "Cancel";
"equip select": "CancelSketch" | "Constrain equal length" | "Constrain horizontally align" | "Constrain vertically align" | "Deselect point" | "Deselect segment" | "Enter sketch" | "Make segment horizontal" | "Make segment vertical" | "Select default plane" | "Select point" | "Select segment" | "Set selection" | "done.invoke.get-angle-info" | "done.invoke.get-horizontal-info" | "done.invoke.get-length-info" | "done.invoke.get-vertical-info" | "error.platform.get-angle-info" | "error.platform.get-horizontal-info" | "error.platform.get-length-info" | "error.platform.get-vertical-info";
"hide default planes": "Cancel" | "Select default plane" | "xstate.stop";
"reset sketch metadata": "Cancel" | "Select default plane";
"set default plane id": "Select default plane";
"set sketch metadata": "Enter sketch";
"set tool": "Equip new tool";
"set tool line": "Equip tool";
"set tool move": "Equip move tool";
"show default planes": "Enter sketch";
"sketch exit execute": "Cancel" | "Complete line" | "xstate.stop";
"sketch mode enabled": "Enter sketch" | "Select default plane";
"toast extrude failed": "";
};
eventsCausingDelays: {
};
eventsCausingGuards: {
"Can constrain angle": "Constrain angle";
"Can constrain equal length": "Constrain equal length";
"Can constrain horizontal distance": "Constrain horizontal distance";
"Can constrain horizontally align": "Constrain horizontally align";
"Can constrain length": "Constrain length";
"Can constrain vertical distance": "Constrain vertical distance";
"Can constrain vertically align": "Constrain vertically align";
"Can make selection horizontal": "Make segment horizontal";
"Can make selection vertical": "Make segment vertical";
"Selection contains axis": "Deselect axis";
"Selection contains edge": "Deselect edge";
"Selection contains face": "Deselect face";
"Selection contains line": "Deselect segment";
"Selection contains point": "Deselect point";
"Selection is not empty": "Deselect all";
"Selection is one face": "Enter sketch";
"has no selection": "extrude intent";
"has valid extrude selection": "" | "extrude intent";
"is editing existing sketch": "";
};
eventsCausingServices: {
"Get angle info": "Constrain angle";
"Get horizontal info": "Constrain horizontal distance";
"Get length info": "Constrain length";
"Get vertical info": "Constrain vertical distance";
};
matchesStates: "Sketch" | "Sketch no face" | "Sketch.Await angle info" | "Sketch.Await horizontal distance info" | "Sketch.Await length info" | "Sketch.Await vertical distance info" | "Sketch.Line Tool" | "Sketch.Line Tool.Done" | "Sketch.Line Tool.Init" | "Sketch.Line Tool.No Points" | "Sketch.Line Tool.Point Added" | "Sketch.Line Tool.Segment Added" | "Sketch.Move Tool" | "Sketch.SketchIdle" | "awaiting selection" | "checking selection" | "idle" | { "Sketch"?: "Await angle info" | "Await horizontal distance info" | "Await length info" | "Await vertical distance info" | "Line Tool" | "Move Tool" | "SketchIdle" | { "Line Tool"?: "Done" | "Init" | "No Points" | "Point Added" | "Segment Added"; }; };
tags: never;
}

View File

@ -2,18 +2,15 @@ import { faArrowRight, faXmark } from '@fortawesome/free-solid-svg-icons'
import { ActionButton } from '../../components/ActionButton' import { ActionButton } from '../../components/ActionButton'
import { useDismiss } from '.' import { useDismiss } from '.'
import { useEffect } from 'react' import { useEffect } from 'react'
import { useStore } from 'useStore'
import { bracket } from 'lib/exampleKcl' import { bracket } from 'lib/exampleKcl'
import { kclManager } from 'lang/KclSinglton'
export default function FutureWork() { export default function FutureWork() {
const dismiss = useDismiss() const dismiss = useDismiss()
const { deferredSetCode } = useStore((s) => ({
deferredSetCode: s.deferredSetCode,
}))
useEffect(() => { useEffect(() => {
deferredSetCode(bracket) kclManager.setCode(bracket)
}, [deferredSetCode]) }, [kclManager.setCode])
return ( return (
<div className="fixed grid justify-center items-center inset-0 bg-chalkboard-100/50 z-50"> <div className="fixed grid justify-center items-center inset-0 bg-chalkboard-100/50 z-50">

View File

@ -9,7 +9,6 @@ import {
import { useGlobalStateContext } from 'hooks/useGlobalStateContext' import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
import { Themes, getSystemTheme } from 'lib/theme' import { Themes, getSystemTheme } from 'lib/theme'
import { bracket } from 'lib/exampleKcl' import { bracket } from 'lib/exampleKcl'
import { useStore } from 'useStore'
import { import {
createNewProject, createNewProject,
getNextProjectIndex, getNextProjectIndex,
@ -20,14 +19,12 @@ import { isTauri } from 'lib/isTauri'
import { useNavigate } from 'react-router-dom' import { useNavigate } from 'react-router-dom'
import { paths } from 'Router' import { paths } from 'Router'
import { useEffect } from 'react' import { useEffect } from 'react'
import { kclManager } from 'lang/KclSinglton'
function OnboardingWithNewFile() { function OnboardingWithNewFile() {
const navigate = useNavigate() const navigate = useNavigate()
const dismiss = useDismiss() const dismiss = useDismiss()
const next = useNextClick(onboardingPaths.INDEX) const next = useNextClick(onboardingPaths.INDEX)
const { deferredSetCode } = useStore((s) => ({
deferredSetCode: s.deferredSetCode,
}))
const { const {
settings: { settings: {
context: { defaultDirectory }, context: { defaultDirectory },
@ -76,7 +73,7 @@ function OnboardingWithNewFile() {
<ActionButton <ActionButton
Element="button" Element="button"
onClick={() => { onClick={() => {
deferredSetCode(bracket) kclManager.setCode(bracket)
next() next()
}} }}
icon={{ icon: faArrowRight }} icon={{ icon: faArrowRight }}
@ -127,10 +124,6 @@ function OnboardingWithNewFile() {
} }
export default function Introduction() { export default function Introduction() {
const { deferredSetCode, code } = useStore((s) => ({
code: s.code,
deferredSetCode: s.deferredSetCode,
}))
const { const {
settings: { settings: {
state: { state: {
@ -147,10 +140,10 @@ export default function Introduction() {
const next = useNextClick(onboardingPaths.CAMERA) const next = useNextClick(onboardingPaths.CAMERA)
useEffect(() => { useEffect(() => {
if (code === '') deferredSetCode(bracket) if (kclManager.code === '') kclManager.setCode(bracket)
}, [code, deferredSetCode]) }, [kclManager.code, kclManager.setCode])
return !(code !== '' && code !== bracket) ? ( return !(kclManager.code !== '' && kclManager.code !== bracket) ? (
<div className="fixed grid place-content-center inset-0 bg-chalkboard-110/50 z-50"> <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"> <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"> <h1 className="text-2xl font-bold flex gap-4 flex-wrap items-center">

View File

@ -3,18 +3,16 @@ import { ActionButton } from '../../components/ActionButton'
import { onboardingPaths, useDismiss, useNextClick } from '.' import { onboardingPaths, useDismiss, useNextClick } from '.'
import { useStore } from 'useStore' import { useStore } from 'useStore'
import { useEffect } from 'react' import { useEffect } from 'react'
import { kclManager } from 'lang/KclSinglton'
export default function Sketching() { export default function Sketching() {
const { deferredSetCode, buttonDownInStream } = useStore((s) => ({ const buttonDownInStream = useStore((s) => s.buttonDownInStream)
deferredSetCode: s.deferredSetCode,
buttonDownInStream: s.buttonDownInStream,
}))
const dismiss = useDismiss() const dismiss = useDismiss()
const next = useNextClick(onboardingPaths.FUTURE_WORK) const next = useNextClick(onboardingPaths.FUTURE_WORK)
useEffect(() => { useEffect(() => {
deferredSetCode('') kclManager.setCode('')
}, [deferredSetCode]) }, [])
return ( return (
<div className="fixed grid justify-center items-end inset-0 z-50 pointer-events-none"> <div className="fixed grid justify-center items-end inset-0 z-50 pointer-events-none">

View File

@ -5,30 +5,36 @@ import {
parse, parse,
Program, Program,
_executor, _executor,
recast,
ProgramMemory, ProgramMemory,
Position, Position,
PathToNode, PathToNode,
Rotation, Rotation,
SourceRange, SourceRange,
} from './lang/wasm' } from './lang/wasm'
import { getNodeFromPath } from './lang/queryAst'
import { enginelessExecutor } from './lib/testHelpers' import { enginelessExecutor } from './lib/testHelpers'
import { EditorSelection } from '@codemirror/state' import { EditorSelection } from '@codemirror/state'
import { EngineCommandManager } from './lang/std/engineConnection' import { EngineCommandManager } from './lang/std/engineConnection'
import { KCLError } from './lang/errors' import { KCLError } from './lang/errors'
import { deferExecution } from 'lib/utils' import { kclManager } from 'lang/KclSinglton'
import { bracket } from 'lib/exampleKcl'
import { engineCommandManager } from './lang/std/engineConnection'
import { DefaultPlanes } from './wasm-lib/kcl/bindings/DefaultPlanes' import { DefaultPlanes } from './wasm-lib/kcl/bindings/DefaultPlanes'
import { initDefaultPlanes } from './hooks/useAppMode'
export type Axis = 'y-axis' | 'x-axis' | 'z-axis'
export type Selection = { export type Selection = {
type: 'default' | 'line-end' | 'line-mid' type:
| 'default'
| 'line-end'
| 'line-mid'
| 'face'
| 'point'
| 'edge'
| 'line'
| 'arc'
| 'all'
range: SourceRange range: SourceRange
} }
export type Selections = { export type Selections = {
otherSelections: ('y-axis' | 'x-axis' | 'z-axis')[] otherSelections: Axis[]
codeBasedSelections: Selection[] codeBasedSelections: Selection[]
} }
export type ToolTip = export type ToolTip =
@ -63,54 +69,6 @@ export const toolTips = [
'angledLineThatIntersects', 'angledLineThatIntersects',
] as any as ToolTip[] ] as any as ToolTip[]
export type GuiModes =
| {
mode: 'default'
}
| {
mode: 'sketch'
sketchMode: ToolTip
isTooltip: true
waitingFirstClick: boolean
rotation: Rotation
position: Position
pathId: string
pathToNode: PathToNode
}
| {
mode: 'sketch'
sketchMode: 'sketchEdit'
rotation: Rotation
position: Position
pathToNode: PathToNode
pathId: string
}
| {
mode: 'sketch'
sketchMode: 'enterSketchEdit'
rotation: Rotation
position: Position
pathToNode: PathToNode
pathId: string
}
| {
mode: 'sketch'
sketchMode: 'selectFace'
}
| {
mode: 'canEditSketch'
pathId: string
pathToNode: PathToNode
rotation: Rotation
position: Position
}
| {
mode: 'canEditExtrude'
pathToNode: PathToNode
rotation: Rotation
position: Position
}
export type PaneType = export type PaneType =
| 'code' | 'code'
| 'variables' | 'variables'
@ -119,50 +77,15 @@ export type PaneType =
| 'logs' | 'logs'
| 'lspMessages' | 'lspMessages'
export interface SelectionRangeTypeMap {
[key: number]: Selection['type']
}
export interface StoreState { export interface StoreState {
editorView: EditorView | null editorView: EditorView | null
setEditorView: (editorView: EditorView) => void setEditorView: (editorView: EditorView) => void
highlightRange: [number, number] highlightRange: [number, number]
setHighlightRange: (range: Selection['range']) => void setHighlightRange: (range: Selection['range']) => void
setCursor: (selections: Selections) => void
setCursor2: (a?: Selection) => void
selectionRanges: Selections
selectionRangeTypeMap: { [key: number]: Selection['type'] }
setSelectionRanges: (range: Selections) => void
guiMode: GuiModes
lastGuiMode: GuiModes
setGuiMode: (guiMode: GuiModes) => void
logs: string[]
addLog: (log: string) => void
setLogs: (logs: string[]) => void
kclErrors: KCLError[]
addKCLError: (err: KCLError) => void
setErrors: (errors: KCLError[]) => void
resetKCLErrors: () => void
ast: Program
setAst: (ast: Program) => void
executeAst: (ast?: Program) => void
executeAstMock: (ast?: Program) => void
updateAst: (
ast: Program,
execute: boolean,
optionalParams?: {
focusPath?: PathToNode
callBack?: (ast: Program) => void
}
) => void
updateAstAsync: (
ast: Program,
reexecute: boolean,
focusPath?: PathToNode
) => void
code: string
setCode: (code: string) => void
deferredSetCode: (code: string) => void
executeCode: (code?: string, force?: boolean) => void
formatCode: () => void
programMemory: ProgramMemory
setProgramMemory: (programMemory: ProgramMemory) => void
isShiftDown: boolean isShiftDown: boolean
setIsShiftDown: (isShiftDown: boolean) => void setIsShiftDown: (isShiftDown: boolean) => void
mediaStream?: MediaStream mediaStream?: MediaStream
@ -182,12 +105,6 @@ export interface StoreState {
streamWidth: number streamWidth: number
streamHeight: number streamHeight: number
}) => void }) => void
isExecuting: boolean
setIsExecuting: (isExecuting: boolean) => void
defaultPlanes: DefaultPlanes | null
setDefaultPlanes: (defaultPlanes: DefaultPlanes) => void
currentPlane: string | null
setCurrentPlane: (currentPlane: string) => void
showHomeMenu: boolean showHomeMenu: boolean
setHomeShowMenu: (showMenu: boolean) => void setHomeShowMenu: (showMenu: boolean) => void
@ -202,18 +119,9 @@ export interface StoreState {
setHomeMenuItems: (items: { name: string; path: string }[]) => void setHomeMenuItems: (items: { name: string; path: string }[]) => void
} }
let pendingAstUpdates: number[] = []
export const useStore = create<StoreState>()( export const useStore = create<StoreState>()(
persist( persist(
(set, get) => { (set, get) => {
// We defer this so that likely our ast has caught up to the code.
// If we are making changes that are not reflected in the ast, we
// should not be updating the ast.
const setDeferredCode = deferExecution((code: string) => {
set({ code })
get().executeCode(code)
}, 600)
return { return {
editorView: null, editorView: null,
setEditorView: (editorView) => { setEditorView: (editorView) => {
@ -227,243 +135,6 @@ export const useStore = create<StoreState>()(
editorView.dispatch({ effects: addLineHighlight.of(selection) }) editorView.dispatch({ effects: addLineHighlight.of(selection) })
} }
}, },
executeCode: async (code, force) => {
if (!get().defaultPlanes) {
let defaultPlanes = await initDefaultPlanes(
engineCommandManager,
true
)
if (!defaultPlanes) return
get().setDefaultPlanes(defaultPlanes)
}
const result = await executeCode({
code: code || get().code,
lastAst: get().ast,
engineCommandManager: engineCommandManager,
defaultPlanes: get().defaultPlanes!,
force,
})
if (!result.isChange) {
return
}
set({
ast: result.ast,
logs: result.logs,
kclErrors: result.errors,
programMemory: result.programMemory,
})
},
setCursor: (selections) => {
const { editorView } = get()
if (!editorView) return
const ranges: ReturnType<typeof EditorSelection.cursor>[] = []
const selectionRangeTypeMap: { [key: number]: Selection['type'] } = {}
set({ selectionRangeTypeMap })
selections.codeBasedSelections.forEach(({ range, type }) => {
if (range?.[1]) {
ranges.push(EditorSelection.cursor(range[1]))
selectionRangeTypeMap[range[1]] = type
}
})
setTimeout(() => {
ranges.length &&
editorView.dispatch({
selection: EditorSelection.create(
ranges,
selections.codeBasedSelections.length - 1
),
})
})
},
setCursor2: (codeSelections) => {
const currestSelections = get().selectionRanges
const code = get().code
if (!codeSelections) {
get().setCursor({
otherSelections: currestSelections.otherSelections,
codeBasedSelections: [
{
range: [0, code.length ? code.length - 1 : 0],
type: 'default',
},
],
})
return
}
const selections: Selections = {
...currestSelections,
codeBasedSelections: get().isShiftDown
? [...currestSelections.codeBasedSelections, codeSelections]
: [codeSelections],
}
get().setCursor(selections)
},
selectionRangeTypeMap: {},
selectionRanges: {
otherSelections: [],
codeBasedSelections: [],
},
setSelectionRanges: (selectionRanges) =>
set({ selectionRanges, selectionRangeTypeMap: {} }),
guiMode: { mode: 'default' },
lastGuiMode: { mode: 'default' },
setGuiMode: (guiMode) => {
set({ guiMode })
},
logs: [],
addLog: (log) => {
if (Array.isArray(log)) {
const cleanLog: any = log.map(({ __geoMeta, ...rest }) => rest)
set((state) => ({ logs: [...state.logs, cleanLog] }))
} else {
set((state) => ({ logs: [...state.logs, log] }))
}
},
setLogs: (logs) => {
set({ logs })
},
kclErrors: [],
addKCLError: (e) => {
set((state) => ({ kclErrors: [...state.kclErrors, e] }))
},
resetKCLErrors: () => {
set({ kclErrors: [] })
},
setErrors: (errors) => {
set({ kclErrors: errors })
},
ast: {
start: 0,
end: 0,
body: [],
nonCodeMeta: {
nonCodeNodes: {},
start: null,
},
},
setAst: (ast) => {
set({ ast })
},
executeAst: async (ast) => {
const _ast = ast || get().ast
if (!get().isStreamReady) return
if (!get().defaultPlanes) {
let defaultPlanes = await initDefaultPlanes(
engineCommandManager,
true
)
if (!defaultPlanes) return
get().setDefaultPlanes(defaultPlanes)
}
set({ isExecuting: true })
const { logs, errors, programMemory } = await executeAst({
ast: _ast,
engineCommandManager,
defaultPlanes: get().defaultPlanes!,
})
set({
programMemory,
logs,
kclErrors: errors,
isExecuting: false,
})
},
executeAstMock: async (ast) => {
const _ast = ast || get().ast
if (!get().isStreamReady) return
if (!get().defaultPlanes) {
let defaultPlanes = await initDefaultPlanes(
engineCommandManager,
true
)
if (!defaultPlanes) return
get().setDefaultPlanes(defaultPlanes)
}
const { logs, errors, programMemory } = await executeAst({
ast: _ast,
engineCommandManager,
useFakeExecutor: true,
defaultPlanes: get().defaultPlanes!,
})
set({
programMemory,
logs,
kclErrors: errors,
isExecuting: false,
})
},
updateAst: async (
ast,
reexecute,
{ focusPath, callBack = () => {} } = {}
) => {
const newCode = recast(ast)
const astWithUpdatedSource = parse(newCode)
callBack(astWithUpdatedSource)
set({
ast: astWithUpdatedSource,
code: newCode,
})
if (focusPath) {
const { node } = getNodeFromPath<any>(
astWithUpdatedSource,
focusPath
)
const { start, end } = node
if (!start || !end) return
setTimeout(() => {
get().setCursor({
codeBasedSelections: [
{
type: 'default',
range: [start, end],
},
],
otherSelections: [],
})
})
}
if (reexecute) {
// Call execute on the set ast.
get().executeAst(astWithUpdatedSource)
} else {
// When we don't re-execute, we still want to update the program
// memory with the new ast. So we will hit the mock executor
// instead.
get().executeAstMock(astWithUpdatedSource)
}
},
updateAstAsync: async (ast, reexecute, focusPath) => {
// clear any pending updates
pendingAstUpdates.forEach((id) => clearTimeout(id))
pendingAstUpdates = []
// setup a new update
pendingAstUpdates.push(
setTimeout(() => {
get().updateAst(ast, reexecute, { focusPath })
}, 100) as unknown as number
)
},
code: bracket,
setCode: (code) => set({ code }),
deferredSetCode: (code) => {
set({ code })
setDeferredCode(code)
},
formatCode: async () => {
const code = get().code
const ast = parse(code)
const newCode = recast(ast)
set({ code: newCode, ast })
},
programMemory: { root: {}, return: null },
setProgramMemory: (programMemory) => set({ programMemory }),
isShiftDown: false, isShiftDown: false,
setIsShiftDown: (isShiftDown) => set({ isShiftDown }), setIsShiftDown: (isShiftDown) => set({ isShiftDown }),
setMediaStream: (mediaStream) => set({ mediaStream }), setMediaStream: (mediaStream) => set({ mediaStream }),
@ -486,12 +157,6 @@ export const useStore = create<StoreState>()(
setStreamDimensions: (streamDimensions) => { setStreamDimensions: (streamDimensions) => {
set({ streamDimensions }) set({ streamDimensions })
}, },
isExecuting: false,
setIsExecuting: (isExecuting) => set({ isExecuting }),
defaultPlanes: null,
setDefaultPlanes: (defaultPlanes) => set({ defaultPlanes }),
currentPlane: null,
setCurrentPlane: (currentPlane) => set({ currentPlane }),
// tauri specific app settings // tauri specific app settings
defaultDir: { defaultDir: {
@ -511,9 +176,7 @@ export const useStore = create<StoreState>()(
name: 'store', name: 'store',
partialize: (state) => partialize: (state) =>
Object.fromEntries( Object.fromEntries(
Object.entries(state).filter(([key]) => Object.entries(state).filter(([key]) => ['openPanes'].includes(key))
['code', 'openPanes'].includes(key)
)
), ),
} }
) )
@ -547,7 +210,7 @@ const defaultProgramMemory: ProgramMemory['root'] = {
}, },
} }
async function executeCode({ export async function executeCode({
engineCommandManager, engineCommandManager,
code, code,
lastAst, lastAst,
@ -618,7 +281,7 @@ async function executeCode({
} }
} }
async function executeAst({ export async function executeAst({
ast, ast,
engineCommandManager, engineCommandManager,
defaultPlanes, defaultPlanes,
@ -682,3 +345,79 @@ async function executeAst({
} }
} }
} }
export function dispatchCodeMirrorCursor({
selections,
editorView,
}: {
selections: Selections
editorView: EditorView
}): {
selectionRangeTypeMap: SelectionRangeTypeMap
} {
const ranges: ReturnType<typeof EditorSelection.cursor>[] = []
const selectionRangeTypeMap: SelectionRangeTypeMap = {}
selections.codeBasedSelections.forEach(({ range, type }) => {
if (range?.[1]) {
ranges.push(EditorSelection.cursor(range[1]))
selectionRangeTypeMap[range[1]] = type
}
})
setTimeout(() => {
ranges.length &&
editorView.dispatch({
selection: EditorSelection.create(
ranges,
selections.codeBasedSelections.length - 1
),
})
})
return {
selectionRangeTypeMap,
}
}
export function setCodeMirrorCursor({
codeSelection,
currestSelections,
editorView,
isShiftDown,
}: {
codeSelection?: Selection
currestSelections: Selections
editorView: EditorView
isShiftDown: boolean
}): SelectionRangeTypeMap {
// This DOES NOT set the `selectionRanges` in xstate context
// instead it updates/dispatches to the editor, which in turn updates the xstate context
// I've found this the best way to deal with the editor without causing an infinite loop
// and really we want the editor to be in charge of cursor positions and for `selectionRanges` mirror it
// because we want to respect the user manually placing the cursor too.
const code = kclManager.code
if (!codeSelection) {
const selectionRangeTypeMap = dispatchCodeMirrorCursor({
editorView,
selections: {
otherSelections: currestSelections.otherSelections,
codeBasedSelections: [
{
range: [0, code.length ? code.length - 1 : 0],
type: 'default',
},
],
},
})
return selectionRangeTypeMap
}
const selections: Selections = {
...currestSelections,
codeBasedSelections: isShiftDown
? [...currestSelections.codeBasedSelections, codeSelection]
: [codeSelection],
}
const selectionRangeTypeMap = dispatchCodeMirrorCursor({
editorView,
selections,
})
return selectionRangeTypeMap
}

View File

@ -2218,6 +2218,13 @@
loupe "^2.3.6" loupe "^2.3.6"
pretty-format "^29.5.0" pretty-format "^29.5.0"
"@xstate/inspect@^0.8.0":
version "0.8.0"
resolved "https://registry.yarnpkg.com/@xstate/inspect/-/inspect-0.8.0.tgz#f99d3706cd823d4922c47ce4f4376eecac502cc7"
integrity sha512-wSkFeOnp+7dhn+zTThO0M4D2FEqZN9lGIWowJu5JLa2ojjtlzRwK8SkjcHZ4rLX8VnMev7kGjgQLrGs8kxy+hw==
dependencies:
fast-safe-stringify "^2.1.1"
"@xstate/react@^3.2.2": "@xstate/react@^3.2.2":
version "3.2.2" version "3.2.2"
resolved "https://registry.yarnpkg.com/@xstate/react/-/react-3.2.2.tgz#ddf0f9d75e2c19375b1e1b7335e72cb99762aed8" resolved "https://registry.yarnpkg.com/@xstate/react/-/react-3.2.2.tgz#ddf0f9d75e2c19375b1e1b7335e72cb99762aed8"
@ -3380,6 +3387,11 @@ fast-levenshtein@^2.0.6:
resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917"
integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==
fast-safe-stringify@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz#c406a83b6e70d9e35ce3b30a81141df30aeba884"
integrity sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==
fastq@^1.6.0: fastq@^1.6.0:
version "1.15.0" version "1.15.0"
resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.15.0.tgz#d04d07c6a2a68fe4599fea8d2e103a937fae6b3a" resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.15.0.tgz#d04d07c6a2a68fe4599fea8d2e103a937fae6b3a"