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 commit629f326f4c
. * 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:
@ -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
|
||||||
|
@ -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",
|
||||||
|
@ -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>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
88
src/App.tsx
88
src/App.tsx
@ -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={
|
||||||
|
@ -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>
|
||||||
|
@ -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>
|
||||||
),
|
),
|
||||||
|
298
src/Toolbar.tsx
298
src/Toolbar.tsx
@ -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" />
|
||||||
|
@ -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
|
||||||
|
@ -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()
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
@ -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)
|
||||||
|
@ -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>
|
||||||
|
@ -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}
|
||||||
|
@ -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]
|
||||||
|
355
src/components/ModelingMachineProvider.tsx
Normal file
355
src/components/ModelingMachineProvider.tsx
Normal 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
|
@ -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 && (
|
||||||
|
@ -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: [
|
||||||
|
@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
@ -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),
|
||||||
|
// })
|
||||||
|
}
|
||||||
|
@ -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),
|
||||||
|
// })
|
||||||
|
}
|
||||||
|
@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
@ -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),
|
||||||
|
// })
|
||||||
|
}
|
||||||
|
@ -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),
|
||||||
|
// })
|
||||||
|
}
|
||||||
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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 {
|
||||||
|
@ -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])
|
||||||
}
|
}
|
||||||
|
6
src/hooks/useModelingContext.ts
Normal file
6
src/hooks/useModelingContext.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import { ModelingMachineContext } from 'components/ModelingMachineProvider'
|
||||||
|
import { useContext } from 'react'
|
||||||
|
|
||||||
|
export const useModelingContext = () => {
|
||||||
|
return useContext(ModelingMachineContext)
|
||||||
|
}
|
@ -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({
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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
347
src/lang/KclSinglton.tsx
Normal 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>
|
||||||
|
)
|
||||||
|
}
|
@ -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' }
|
||||||
})
|
})
|
||||||
|
@ -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)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
@ -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'
|
||||||
|
}
|
||||||
|
@ -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()
|
||||||
|
@ -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)
|
||||||
|
@ -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]
|
||||||
|
@ -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,
|
||||||
|
@ -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 {
|
||||||
|
853
src/machines/modelingMachine.ts
Normal file
853
src/machines/modelingMachine.ts
Normal 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,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
98
src/machines/modelingMachine.typegen.ts
Normal file
98
src/machines/modelingMachine.typegen.ts
Normal 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;
|
||||||
|
}
|
||||||
|
|
@ -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">
|
||||||
|
@ -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">
|
||||||
|
@ -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">
|
||||||
|
455
src/useStore.ts
455
src/useStore.ts
@ -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
|
||||||
|
}
|
||||||
|
12
yarn.lock
12
yarn.lock
@ -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"
|
||||||
|
Reference in New Issue
Block a user