Compare commits
18 Commits
v0.7.1
...
franknoiro
Author | SHA1 | Date | |
---|---|---|---|
977e566ae4 | |||
7aaf923529 | |||
bcb05d02b4 | |||
ef451b70b6 | |||
c33107aa28 | |||
26737e055a | |||
c01590b49b | |||
1d656d68c6 | |||
e180b73c9d | |||
738b1a7c21 | |||
62aebaf523 | |||
2095375b37 | |||
7a9a33c656 | |||
a4a393fc45 | |||
10884fd0b0 | |||
bcf83dc7ee | |||
32f79c98f8 | |||
c11149e909 |
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "untitled-app",
|
||||
"version": "0.7.1",
|
||||
"version": "0.7.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@codemirror/autocomplete": "^6.9.0",
|
||||
|
@ -1,42 +0,0 @@
|
||||
## Alpha Users Expectations
|
||||
|
||||
### Welcome
|
||||
|
||||
First off, thank you so much for your interest in being a part of the closed Alpha program! We are thrilled to have others use our product and see what you build with it (and truthfully, how you break it too).
|
||||
|
||||
### KittyCAD Modeling App (KCMA)
|
||||
|
||||
What we are introducing to you is our KittyCAD Modeling App (KCMA). KCMA is a CAD application that expresses a hybrid style of traditional CAD interface along with a code-CAD interface. KCMA is a great way for us to test our own APIs as well as inspire others to develop their own applications.
|
||||
|
||||
### Why Code?
|
||||
|
||||
Plenty of you have professional CAD experience, and may not understand why coding your model would be helpful. The "code-CAD" paradigm isn’t as popular as traditional CAD programs (SolidWorks, NX, CREO, OnShape, etc.), but it certainly has its benefits. Some benefits include:
|
||||
|
||||
- Automation and parametric design
|
||||
- Customization and flexibility
|
||||
- Algorithmic and generative design
|
||||
- Reproducibility
|
||||
- Easier integration with other tools
|
||||
|
||||
### Before You Use KCMA
|
||||
|
||||
Before you dive straight into the app, we wanted to lay some expectations out for you.
|
||||
|
||||
- KCMA is in early development. Kurt pitched the idea back in January, and the team has been working hard on it since then. KCMA has really basic CAD features for now, but we have plenty of features on our roadmap. Most of the features that you may be currently used to in your CAD workflow today will be available down the road.
|
||||
- For a list of all scripting functions, please reference our [documentation](https://github.com/KittyCAD/modeling-app/blob/main/docs/kcl/std.md). For a basic rundown of our types, please reference [this document](https://github.com/KittyCAD/modeling-app/blob/main/docs/kcl/types.md).
|
||||
- With that being said, we have created an external new features list in [GH Discussions](https://github.com/KittyCAD/modeling-app/discussions). For our current priority list, please click [here](https://github.com/KittyCAD/modeling-app/blob/main/public/roadmap.md). Please upvote any features in the GH Discussions page that you would like to see implemented first. We will prioritize the highest upvoted items or items that are foundational for other features on the list. You can also add your own, but we will review it to make sure it’s not a duplicate or it’s feasible for the current state of the app.
|
||||
- Please report any and all bugs/issues you find. Even the smallest bugs are important! You can report them in a GH Issue [here](https://github.com/KittyCAD/modeling-app/issues/new). You are more than welcome to link your GH Issue in the **bugs** section of our Discord, but if you want to discuss the bug further, please keep that in the GH Issue thread. Please include the severity of the bug in your GH Issue ticket (High, Medium, or Low). If you are having trouble deciding what severity the bug is, use this guideline:
|
||||
- **High:** The bug is blocking you from continuing.
|
||||
- Example: Every time I click the extrude button with two faces selected, the app crashes.
|
||||
- **Medium:** You can find a workaround to the problem, but it increases your time spent working or makes it unenjoyable.
|
||||
- Example: When the app is full screen on Mac, the settings are not showing properly. It works if I have the app windowed.
|
||||
- **Low:** The bug is annoying but doesn’t affect workflow or block you from continuing (usually you can say “It would be nice if ___, but it’s not needed”)
|
||||
- Example: It would be nice if the camera would orient normal to the sketching surface when I select a face/plane and click “sketch”.
|
||||
- We want you all to be aware that we may reach out to you in regard to issues, bugs, problems, and satisfaction. This will typically be for further clarification so we can really nail things down.
|
||||
|
||||
### Discord
|
||||
We will be using Discord a lot more now that the Alpha has been released to people outside of the company. Please feel free to discuss and talk with us in the **alpha users** section of the server. We highly encourage you to engage with us on Discord!
|
||||
|
||||
### Thank You!
|
||||
|
||||
Once again, from all of us to you, thank you for being a part of the closed Alpha. We are happy to chat with you all, hear your feedback, and see some of your projects!
|
712
src-tauri/Cargo.lock
generated
712
src-tauri/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -16,14 +16,13 @@ tauri-build = { version = "1.4.0", features = [] }
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1"
|
||||
kittycad = "0.2.25"
|
||||
oauth2 = "4.4.2"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
tauri = { version = "1.4.1", features = ["dialog-all", "fs-all", "http-request", "path-all", "shell-open", "shell-open-api", "updater", "devtools"] }
|
||||
tauri-plugin-fs-extra = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" }
|
||||
tokio = { version = "1.32.0", features = ["time"] }
|
||||
toml = "0.8.0"
|
||||
tauri-plugin-fs-extra = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" }
|
||||
|
||||
[features]
|
||||
# this feature is used for production builds or when `devPath` points to the filesystem and the built-in dev server is disabled.
|
||||
|
@ -85,24 +85,6 @@ async fn login(app: tauri::AppHandle, host: &str) -> Result<String, InvokeError>
|
||||
Ok(token)
|
||||
}
|
||||
|
||||
///This command returns the KittyCAD user info given a token.
|
||||
/// The string returned from this method is the user info as a json string.
|
||||
#[tauri::command]
|
||||
async fn get_user(token: Option<String>) -> Result<kittycad::types::User, InvokeError> {
|
||||
println!("Getting user info...");
|
||||
|
||||
// use kittycad library to fetch the user info from /user/me
|
||||
let client = kittycad::Client::new(token.unwrap());
|
||||
|
||||
let user_info: kittycad::types::User = client
|
||||
.users()
|
||||
.get_self()
|
||||
.await
|
||||
.map_err(|e| InvokeError::from_anyhow(e.into()))?;
|
||||
|
||||
Ok(user_info)
|
||||
}
|
||||
|
||||
fn main() {
|
||||
tauri::Builder::default()
|
||||
.setup(|app| {
|
||||
@ -115,12 +97,7 @@ fn main() {
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
get_user,
|
||||
login,
|
||||
read_toml,
|
||||
read_txt_file
|
||||
])
|
||||
.invoke_handler(tauri::generate_handler![login, read_toml, read_txt_file])
|
||||
.plugin(tauri_plugin_fs_extra::init())
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
|
@ -8,7 +8,7 @@
|
||||
},
|
||||
"package": {
|
||||
"productName": "kittycad-modeling",
|
||||
"version": "0.7.1"
|
||||
"version": "0.7.0"
|
||||
},
|
||||
"tauri": {
|
||||
"allowlist": {
|
||||
|
49
src/App.tsx
49
src/App.tsx
@ -1,7 +1,6 @@
|
||||
import { useRef, useEffect, useCallback, MouseEventHandler } from 'react'
|
||||
import { useEffect, useCallback, MouseEventHandler } from 'react'
|
||||
import { DebugPanel } from './components/DebugPanel'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import { _executor } from './lang/executor'
|
||||
import { PaneType, useStore } from './useStore'
|
||||
import { Logs, KCLErrors } from './components/Logs'
|
||||
import { CollapsiblePanel } from './components/CollapsiblePanel'
|
||||
@ -30,13 +29,10 @@ import { CameraDragInteractionType_type } from '@kittycad/lib/dist/types/src/mod
|
||||
import { CodeMenu } from 'components/CodeMenu'
|
||||
import { TextEditor } from 'components/TextEditor'
|
||||
import { Themes, getSystemTheme } from 'lib/theme'
|
||||
import { useSetupEngineManager } from 'hooks/useSetupEngineManager'
|
||||
import { useEngineConnectionSubscriptions } from 'hooks/useEngineConnectionSubscriptions'
|
||||
|
||||
export function App() {
|
||||
const { code: loadedCode, project } = useLoaderData() as IndexLoaderData
|
||||
|
||||
const streamRef = useRef<HTMLDivElement>(null)
|
||||
useHotKeyListener()
|
||||
const {
|
||||
setCode,
|
||||
@ -47,10 +43,8 @@ export function App() {
|
||||
didDragInStream,
|
||||
streamDimensions,
|
||||
guiMode,
|
||||
setGuiMode,
|
||||
} = useStore((s) => ({
|
||||
guiMode: s.guiMode,
|
||||
setGuiMode: s.setGuiMode,
|
||||
setCode: s.setCode,
|
||||
engineCommandManager: s.engineCommandManager,
|
||||
buttonDownInStream: s.buttonDownInStream,
|
||||
@ -61,9 +55,6 @@ export function App() {
|
||||
}))
|
||||
|
||||
const {
|
||||
auth: {
|
||||
context: { token },
|
||||
},
|
||||
settings: {
|
||||
context: { showDebugPanel, onboardingStatus, cameraControls, theme },
|
||||
},
|
||||
@ -84,38 +75,6 @@ export function App() {
|
||||
useHotkeys('shift + l', () => togglePane('logs'))
|
||||
useHotkeys('shift + e', () => togglePane('kclErrors'))
|
||||
useHotkeys('shift + d', () => togglePane('debug'))
|
||||
useHotkeys('esc', () => {
|
||||
if (guiMode.mode === 'sketch') {
|
||||
if (guiMode.sketchMode === 'selectFace') return
|
||||
if (guiMode.sketchMode === 'sketchEdit') {
|
||||
engineCommandManager?.sendSceneCommand({
|
||||
type: 'modeling_cmd_req',
|
||||
cmd_id: uuidv4(),
|
||||
cmd: { type: 'edit_mode_exit' },
|
||||
})
|
||||
setGuiMode({ mode: 'default' })
|
||||
} 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,
|
||||
// todo: ...guiMod is adding isTooltip: true, will probably just fix with xstate migtaion
|
||||
})
|
||||
}
|
||||
} else {
|
||||
setGuiMode({ mode: 'default' })
|
||||
}
|
||||
})
|
||||
|
||||
const paneOpacity =
|
||||
onboardingStatus === onboardingPaths.CAMERA
|
||||
@ -138,9 +97,6 @@ export function App() {
|
||||
}
|
||||
}, [loadedCode, setCode])
|
||||
|
||||
useSetupEngineManager(streamRef, token)
|
||||
useEngineConnectionSubscriptions()
|
||||
|
||||
const debounceSocketSend = throttle<EngineCommand>((message) => {
|
||||
engineCommandManager?.sendSceneCommand(message)
|
||||
}, 16)
|
||||
@ -220,9 +176,8 @@ export function App() {
|
||||
|
||||
return (
|
||||
<div
|
||||
className="h-screen overflow-hidden relative flex flex-col cursor-pointer select-none"
|
||||
className="relative h-full flex flex-col"
|
||||
onMouseMove={handleMouseMove}
|
||||
ref={streamRef}
|
||||
>
|
||||
<AppHeader
|
||||
className={
|
||||
|
@ -40,6 +40,7 @@ import { ContextFrom } from 'xstate'
|
||||
import CommandBarProvider from 'components/CommandBar'
|
||||
import { TEST, VITE_KC_SENTRY_DSN } from './env'
|
||||
import * as Sentry from '@sentry/react'
|
||||
import ModelingMachineProvider from 'components/ModelingMachineProvider'
|
||||
|
||||
if (VITE_KC_SENTRY_DSN && !TEST) {
|
||||
Sentry.init({
|
||||
@ -136,7 +137,9 @@ const router = createBrowserRouter(
|
||||
element: (
|
||||
<Auth>
|
||||
<Outlet />
|
||||
<App />
|
||||
<ModelingMachineProvider>
|
||||
<App />
|
||||
</ModelingMachineProvider>
|
||||
{!isTauri() && import.meta.env.PROD && <DownloadAppBanner />}
|
||||
</Auth>
|
||||
),
|
||||
|
@ -47,15 +47,6 @@
|
||||
@apply hover:bg-cool-20;
|
||||
}
|
||||
|
||||
.smallScrollbar::-webkit-scrollbar {
|
||||
@apply h-0.5;
|
||||
}
|
||||
|
||||
.smallScrollbar {
|
||||
@apply overflow-x-auto;
|
||||
scrollbar-width: thin;
|
||||
}
|
||||
|
||||
:global(.dark) .popoverToggle {
|
||||
@apply hover:bg-cool-90;
|
||||
}
|
||||
|
@ -27,7 +27,6 @@ export const Toolbar = () => {
|
||||
updateAst,
|
||||
programMemory,
|
||||
engineCommandManager,
|
||||
executeAst,
|
||||
} = useStore((s) => ({
|
||||
guiMode: s.guiMode,
|
||||
setGuiMode: s.setGuiMode,
|
||||
@ -36,7 +35,6 @@ export const Toolbar = () => {
|
||||
updateAst: s.updateAst,
|
||||
programMemory: s.programMemory,
|
||||
engineCommandManager: s.engineCommandManager,
|
||||
executeAst: s.executeAst,
|
||||
}))
|
||||
useAppMode()
|
||||
|
||||
@ -46,7 +44,7 @@ export const Toolbar = () => {
|
||||
|
||||
function ToolbarButtons() {
|
||||
return (
|
||||
<span className={styles.smallScrollbar}>
|
||||
<span className="overflow-x-auto">
|
||||
{guiMode.mode === 'default' && (
|
||||
<button
|
||||
onClick={() => {
|
||||
@ -72,7 +70,7 @@ export const Toolbar = () => {
|
||||
pathToNode,
|
||||
programMemory
|
||||
)
|
||||
updateAst(modifiedAst, true)
|
||||
updateAst(modifiedAst)
|
||||
}}
|
||||
>
|
||||
SketchOnFace
|
||||
@ -115,7 +113,7 @@ export const Toolbar = () => {
|
||||
ast,
|
||||
pathToNode
|
||||
)
|
||||
updateAst(modifiedAst, true, { focusPath: pathToExtrudeArg })
|
||||
updateAst(modifiedAst, { focusPath: pathToExtrudeArg })
|
||||
}}
|
||||
>
|
||||
ExtrudeSketch
|
||||
@ -132,7 +130,7 @@ export const Toolbar = () => {
|
||||
pathToNode,
|
||||
false
|
||||
)
|
||||
updateAst(modifiedAst, true, { focusPath: pathToExtrudeArg })
|
||||
updateAst(modifiedAst, { focusPath: pathToExtrudeArg })
|
||||
}}
|
||||
>
|
||||
ExtrudeSketch (w/o pipe)
|
||||
@ -148,14 +146,7 @@ export const Toolbar = () => {
|
||||
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()
|
||||
}}
|
||||
>
|
||||
Exit sketch
|
||||
|
@ -1,15 +1,11 @@
|
||||
import { Menu } from '@headlessui/react'
|
||||
import { PropsWithChildren } from 'react'
|
||||
import {
|
||||
faArrowUpRightFromSquare,
|
||||
faEllipsis,
|
||||
} from '@fortawesome/free-solid-svg-icons'
|
||||
import { faEllipsis } from '@fortawesome/free-solid-svg-icons'
|
||||
import { ActionIcon } from './ActionIcon'
|
||||
import { useStore } from 'useStore'
|
||||
import styles from './CodeMenu.module.css'
|
||||
import { useConvertToVariable } from 'hooks/useToolbarGuards'
|
||||
import { editorShortcutMeta } from './TextEditor'
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
|
||||
export const CodeMenu = ({ children }: PropsWithChildren) => {
|
||||
const { formatCode } = useStore((s) => ({
|
||||
@ -56,24 +52,6 @@ export const CodeMenu = ({ children }: PropsWithChildren) => {
|
||||
</button>
|
||||
</Menu.Item>
|
||||
)}
|
||||
<Menu.Item>
|
||||
<a
|
||||
className={styles.button}
|
||||
href="https://github.com/KittyCAD/modeling-app/blob/main/docs/kcl/std.md"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<span>Read the KCL docs</span>
|
||||
<small>
|
||||
On GitHub
|
||||
<FontAwesomeIcon
|
||||
icon={faArrowUpRightFromSquare}
|
||||
className="ml-1 align-text-top"
|
||||
width={12}
|
||||
/>
|
||||
</small>
|
||||
</a>
|
||||
</Menu.Item>
|
||||
</Menu.Items>
|
||||
</div>
|
||||
</Menu>
|
||||
|
@ -24,9 +24,6 @@ import {
|
||||
StateFrom,
|
||||
} from 'xstate'
|
||||
import { useCommandsContext } from 'hooks/useCommandsContext'
|
||||
import { invoke } from '@tauri-apps/api'
|
||||
import { isTauri } from 'lib/isTauri'
|
||||
import { VITE_KC_API_BASE_URL } from 'env'
|
||||
|
||||
type MachineContext<T extends AnyStateMachine> = {
|
||||
state: StateFrom<T>
|
||||
@ -111,7 +108,6 @@ export const GlobalStateProvider = ({
|
||||
actions: {
|
||||
goToSignInPage: () => {
|
||||
navigate(paths.SIGN_IN)
|
||||
|
||||
logout()
|
||||
},
|
||||
goToIndexPage: () => {
|
||||
@ -153,12 +149,10 @@ export const GlobalStateProvider = ({
|
||||
export default GlobalStateProvider
|
||||
|
||||
export function logout() {
|
||||
const url = withBaseUrl('/logout')
|
||||
localStorage.removeItem(TOKEN_PERSIST_KEY)
|
||||
return (
|
||||
!isTauri() &&
|
||||
fetch(withBaseUrl('/logout'), {
|
||||
method: 'POST',
|
||||
credentials: 'include',
|
||||
})
|
||||
)
|
||||
return fetch(url, {
|
||||
method: 'POST',
|
||||
credentials: 'include',
|
||||
})
|
||||
}
|
||||
|
106
src/components/ModelingMachineProvider.tsx
Normal file
106
src/components/ModelingMachineProvider.tsx
Normal file
@ -0,0 +1,106 @@
|
||||
import { useMachine } from '@xstate/react'
|
||||
import React, { createContext, useRef } from 'react'
|
||||
import {
|
||||
AnyStateMachine,
|
||||
ContextFrom,
|
||||
InterpreterFrom,
|
||||
Prop,
|
||||
StateFrom,
|
||||
} from 'xstate'
|
||||
import { modelingMachine } from 'machines/modelingMachine'
|
||||
import { useSetupEngineManager } from 'hooks/useSetupEngineManager'
|
||||
import { useCodeEval } from 'hooks/useCodeEval'
|
||||
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
|
||||
|
||||
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: {
|
||||
context: { token },
|
||||
},
|
||||
} = useGlobalStateContext()
|
||||
const streamRef = useRef<HTMLDivElement>(null)
|
||||
useSetupEngineManager(streamRef, token)
|
||||
useCodeEval()
|
||||
|
||||
// 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': () => {},
|
||||
'Make selection horizontal': () => {},
|
||||
'Make selection vertical': () => {},
|
||||
'Update code selection cursors': () => {},
|
||||
},
|
||||
guards: {
|
||||
'Can make selection horizontal': () => true,
|
||||
'Can make selection vertical': () => true,
|
||||
'Selection contains axis': () => true,
|
||||
'Selection contains edge': () => true,
|
||||
'Selection contains face': () => true,
|
||||
'Selection contains line': () => true,
|
||||
'Selection contains point': () => true,
|
||||
'Selection is empty': () => true,
|
||||
'Selection is not empty': () => true,
|
||||
'Selection is one face': () => true,
|
||||
'Selection is one or more edges': () => true,
|
||||
},
|
||||
services: {
|
||||
createSketch: async () => {},
|
||||
createLine: async () => {},
|
||||
createExtrude: async () => {},
|
||||
createFillet: async () => {},
|
||||
},
|
||||
})
|
||||
|
||||
// useStateMachineCommands({
|
||||
// state: settingsState,
|
||||
// send: settingsSend,
|
||||
// commands,
|
||||
// owner: 'settings',
|
||||
// commandBarMeta: settingsCommandBarMeta,
|
||||
// })
|
||||
|
||||
return (
|
||||
<ModelingMachineContext.Provider
|
||||
value={{
|
||||
state: modelingState,
|
||||
context: modelingState.context,
|
||||
send: modelingSend,
|
||||
}}
|
||||
>
|
||||
<div className="h-screen overflow-hidden select-none" ref={streamRef}>
|
||||
{children}
|
||||
</div>
|
||||
</ModelingMachineContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export default ModelingMachineProvider
|
@ -220,21 +220,9 @@ export const Stream = ({ className = '' }) => {
|
||||
)
|
||||
return
|
||||
|
||||
// Check if the sketch group already exists.
|
||||
const varDec = getNodeFromPath<VariableDeclarator>(
|
||||
ast,
|
||||
guiMode.pathToNode,
|
||||
'VariableDeclarator'
|
||||
).node
|
||||
const variableName = varDec?.id?.name
|
||||
const sketchGroup = programMemory.root[variableName]
|
||||
const isEditingExistingSketch =
|
||||
sketchGroup?.type === 'SketchGroup' && sketchGroup.value.length
|
||||
|
||||
if (
|
||||
resp?.data?.data?.entities_modified?.length &&
|
||||
guiMode.waitingFirstClick &&
|
||||
!isEditingExistingSketch
|
||||
guiMode.waitingFirstClick
|
||||
) {
|
||||
const curve = await engineCommandManager?.sendSceneCommand({
|
||||
type: 'modeling_cmd_req',
|
||||
@ -262,10 +250,10 @@ export const Stream = ({ className = '' }) => {
|
||||
pathToNode: _pathToNode,
|
||||
waitingFirstClick: false,
|
||||
})
|
||||
updateAst(_modifiedAst, false)
|
||||
updateAst(_modifiedAst)
|
||||
} else if (
|
||||
resp?.data?.data?.entities_modified?.length &&
|
||||
(!guiMode.waitingFirstClick || isEditingExistingSketch)
|
||||
!guiMode.waitingFirstClick
|
||||
) {
|
||||
const curve = await engineCommandManager?.sendSceneCommand({
|
||||
type: 'modeling_cmd_req',
|
||||
@ -302,7 +290,6 @@ export const Stream = ({ className = '' }) => {
|
||||
fnName: 'line',
|
||||
pathToNode: guiMode.pathToNode,
|
||||
}).modifiedAst
|
||||
updateAst(_modifiedAst, false)
|
||||
} else {
|
||||
_modifiedAst = addCloseToPipe({
|
||||
node: ast,
|
||||
@ -312,15 +299,8 @@ export const Stream = ({ className = '' }) => {
|
||||
setGuiMode({
|
||||
mode: 'default',
|
||||
})
|
||||
engineCommandManager?.sendSceneCommand({
|
||||
type: 'modeling_cmd_req',
|
||||
cmd_id: uuidv4(),
|
||||
cmd: {
|
||||
type: 'sketch_mode_disable',
|
||||
},
|
||||
})
|
||||
updateAst(_modifiedAst, true)
|
||||
}
|
||||
updateAst(_modifiedAst)
|
||||
}
|
||||
})
|
||||
setDidDragInStream(false)
|
||||
@ -353,7 +333,7 @@ export const Stream = ({ className = '' }) => {
|
||||
onWheel={handleScroll}
|
||||
onPlay={() => setIsLoading(false)}
|
||||
onMouseMoveCapture={handleMouseMove}
|
||||
className={`w-full h-full ${isExecuting && 'blur-md'}`}
|
||||
className={`w-full cursor-pointer h-full ${isExecuting && 'blur-md'}`}
|
||||
style={{ transitionDuration: '200ms', transitionProperty: 'filter' }}
|
||||
/>
|
||||
{isLoading && (
|
||||
|
@ -29,6 +29,7 @@ import {
|
||||
import { isOverlap, roundOff } from 'lib/utils'
|
||||
import { kclErrToDiagnostic } from 'lang/errors'
|
||||
import { CSSRuleObject } from 'tailwindcss/types/config'
|
||||
import { useModelingContext } from 'hooks/useModelingContext'
|
||||
import interact from '@replit/codemirror-interact'
|
||||
|
||||
export const editorShortcutMeta = {
|
||||
@ -50,7 +51,7 @@ export const TextEditor = ({
|
||||
const pathParams = useParams()
|
||||
const {
|
||||
code,
|
||||
deferredSetCode,
|
||||
defferedSetCode,
|
||||
editorView,
|
||||
engineCommandManager,
|
||||
formatCode,
|
||||
@ -60,9 +61,10 @@ export const TextEditor = ({
|
||||
setEditorView,
|
||||
setIsLSPServerReady,
|
||||
setSelectionRanges,
|
||||
sourceRangeMap,
|
||||
} = useStore((s) => ({
|
||||
code: s.code,
|
||||
deferredSetCode: s.deferredSetCode,
|
||||
defferedSetCode: s.defferedSetCode,
|
||||
editorView: s.editorView,
|
||||
engineCommandManager: s.engineCommandManager,
|
||||
formatCode: s.formatCode,
|
||||
@ -72,8 +74,14 @@ export const TextEditor = ({
|
||||
setEditorView: s.setEditorView,
|
||||
setIsLSPServerReady: s.setIsLSPServerReady,
|
||||
setSelectionRanges: s.setSelectionRanges,
|
||||
sourceRangeMap: s.sourceRangeMap,
|
||||
}))
|
||||
|
||||
const {
|
||||
context: { selectionRanges: machineSelectionRanges },
|
||||
send,
|
||||
} = useModelingContext()
|
||||
|
||||
const {
|
||||
settings: {
|
||||
context: { textWrapping },
|
||||
@ -124,7 +132,7 @@ export const TextEditor = ({
|
||||
|
||||
// const onChange = React.useCallback((value: string, viewUpdate: ViewUpdate) => {
|
||||
const onChange = (value: string, viewUpdate: ViewUpdate) => {
|
||||
deferredSetCode(value)
|
||||
defferedSetCode(value)
|
||||
if (isTauri() && pathParams.id) {
|
||||
// Save the file to disk
|
||||
// Note that PROJECT_ENTRYPOINT is hardcoded until we support multiple files
|
||||
@ -172,11 +180,11 @@ export const TextEditor = ({
|
||||
)
|
||||
const idBasedSelections = codeBasedSelections
|
||||
.map(({ type, range }) => {
|
||||
const hasOverlap = Object.entries(
|
||||
engineCommandManager?.sourceRangeMap || {}
|
||||
).filter(([_, sourceRange]) => {
|
||||
return isOverlap(sourceRange, range)
|
||||
})
|
||||
const hasOverlap = Object.entries(sourceRangeMap).filter(
|
||||
([_, sourceRange]) => {
|
||||
return isOverlap(sourceRange, range)
|
||||
}
|
||||
)
|
||||
if (hasOverlap.length) {
|
||||
return {
|
||||
type,
|
||||
@ -195,6 +203,14 @@ export const TextEditor = ({
|
||||
otherSelections: [],
|
||||
codeBasedSelections,
|
||||
})
|
||||
|
||||
send({
|
||||
type: 'Set selection',
|
||||
data: {
|
||||
...machineSelectionRanges,
|
||||
codeBasedSelections,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const editorExtensions = useMemo(() => {
|
||||
|
@ -82,7 +82,7 @@ export const EqualAngle = () => {
|
||||
transformInfos,
|
||||
programMemory,
|
||||
})
|
||||
updateAst(modifiedAst, true, {
|
||||
updateAst(modifiedAst, {
|
||||
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
|
||||
})
|
||||
}}
|
||||
|
@ -82,7 +82,7 @@ export const EqualLength = () => {
|
||||
transformInfos,
|
||||
programMemory,
|
||||
})
|
||||
updateAst(modifiedAst, true, {
|
||||
updateAst(modifiedAst, {
|
||||
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
|
||||
})
|
||||
}}
|
||||
|
@ -61,7 +61,7 @@ export const HorzVert = ({
|
||||
programMemory,
|
||||
referenceSegName: '',
|
||||
})
|
||||
updateAst(modifiedAst, true, {
|
||||
updateAst(modifiedAst, {
|
||||
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
|
||||
})
|
||||
}}
|
||||
|
@ -154,7 +154,7 @@ export const Intersect = () => {
|
||||
initialVariableName: 'offset',
|
||||
} as any)
|
||||
if (segName === tagInfo?.tag && value === valueUsedInTransform) {
|
||||
updateAst(modifiedAst, true, {
|
||||
updateAst(modifiedAst, {
|
||||
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
|
||||
})
|
||||
} else {
|
||||
@ -182,7 +182,7 @@ export const Intersect = () => {
|
||||
)
|
||||
_modifiedAst.body = newBody
|
||||
}
|
||||
updateAst(_modifiedAst, true, {
|
||||
updateAst(_modifiedAst, {
|
||||
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
|
||||
})
|
||||
}
|
||||
|
@ -65,7 +65,7 @@ export const RemoveConstrainingValues = () => {
|
||||
programMemory,
|
||||
referenceSegName: '',
|
||||
})
|
||||
updateAst(modifiedAst, true, {
|
||||
updateAst(modifiedAst, {
|
||||
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
|
||||
})
|
||||
}}
|
||||
|
@ -124,7 +124,7 @@ export const SetAbsDistance = ({
|
||||
_modifiedAst.body = newBody
|
||||
}
|
||||
|
||||
updateAst(_modifiedAst, true, {
|
||||
updateAst(_modifiedAst, {
|
||||
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
|
||||
})
|
||||
} catch (e) {
|
||||
|
@ -113,7 +113,7 @@ export const SetAngleBetween = () => {
|
||||
initialVariableName: 'angle',
|
||||
} as any)
|
||||
if (segName === tagInfo?.tag && value === valueUsedInTransform) {
|
||||
updateAst(modifiedAst, true, {
|
||||
updateAst(modifiedAst, {
|
||||
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
|
||||
})
|
||||
} else {
|
||||
@ -141,7 +141,7 @@ export const SetAngleBetween = () => {
|
||||
)
|
||||
_modifiedAst.body = newBody
|
||||
}
|
||||
updateAst(_modifiedAst, true, {
|
||||
updateAst(_modifiedAst, {
|
||||
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
|
||||
})
|
||||
}
|
||||
|
@ -137,7 +137,7 @@ export const SetHorzVertDistance = ({
|
||||
constraint === 'setHorzDistance' ? 'xDis' : 'yDis',
|
||||
} as any))
|
||||
if (segName === tagInfo?.tag && value === valueUsedInTransform) {
|
||||
updateAst(modifiedAst, true, {
|
||||
updateAst(modifiedAst, {
|
||||
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
|
||||
})
|
||||
} else {
|
||||
@ -163,7 +163,7 @@ export const SetHorzVertDistance = ({
|
||||
)
|
||||
_modifiedAst.body = newBody
|
||||
}
|
||||
updateAst(_modifiedAst, true, {
|
||||
updateAst(_modifiedAst, {
|
||||
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
|
||||
})
|
||||
}
|
||||
|
@ -136,7 +136,7 @@ export const SetAngleLength = ({
|
||||
_modifiedAst.body = newBody
|
||||
}
|
||||
|
||||
updateAst(_modifiedAst, true, {
|
||||
updateAst(_modifiedAst, {
|
||||
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
|
||||
})
|
||||
} catch (e) {
|
||||
|
@ -11,9 +11,8 @@ import { isOverlap } from 'lib/utils'
|
||||
|
||||
interface DefaultPlanes {
|
||||
xy: string
|
||||
// TODO re-enable
|
||||
// yz: string
|
||||
// xz: string
|
||||
yz: string
|
||||
xz: string
|
||||
}
|
||||
|
||||
export function useAppMode() {
|
||||
@ -43,26 +42,34 @@ export function useAppMode() {
|
||||
y_axis: { x: 0, y: 1, z: 0 },
|
||||
color: { r: 0.7, g: 0.28, b: 0.28, a: 0.4 },
|
||||
})
|
||||
// TODO re-enable
|
||||
// const yz = createPlane(engineCommandManager, {
|
||||
// x_axis: { x: 0, y: 1, z: 0 },
|
||||
// y_axis: { x: 0, y: 0, z: 1 },
|
||||
// color: { r: 0.28, g: 0.7, b: 0.28, a: 0.4 },
|
||||
// })
|
||||
// const xz = createPlane(engineCommandManager, {
|
||||
// x_axis: { x: 1, y: 0, z: 0 },
|
||||
// y_axis: { x: 0, y: 0, z: 1 },
|
||||
// color: { r: 0.28, g: 0.28, b: 0.7, a: 0.4 },
|
||||
// })
|
||||
setDefaultPlanes({ xy })
|
||||
const yz = createPlane(engineCommandManager, {
|
||||
x_axis: { x: 0, y: 1, z: 0 },
|
||||
y_axis: { x: 0, y: 0, z: 1 },
|
||||
color: { r: 0.28, g: 0.7, b: 0.28, a: 0.4 },
|
||||
})
|
||||
const xz = createPlane(engineCommandManager, {
|
||||
x_axis: { x: 1, y: 0, z: 0 },
|
||||
y_axis: { x: 0, y: 0, z: 1 },
|
||||
color: { r: 0.28, g: 0.28, b: 0.7, a: 0.4 },
|
||||
})
|
||||
setDefaultPlanes({ xy, yz, xz })
|
||||
} else {
|
||||
setDefaultPlanesHidden(engineCommandManager, defaultPlanes, false)
|
||||
hideDefaultPlanes(engineCommandManager, defaultPlanes)
|
||||
}
|
||||
}
|
||||
if (guiMode.mode !== 'sketch' && defaultPlanes) {
|
||||
setDefaultPlanesHidden(engineCommandManager, defaultPlanes, true)
|
||||
}
|
||||
if (guiMode.mode === 'default') {
|
||||
Object.values(defaultPlanes).forEach((planeId) => {
|
||||
engineCommandManager?.sendSceneCommand({
|
||||
type: 'modeling_cmd_req',
|
||||
cmd_id: uuidv4(),
|
||||
cmd: {
|
||||
type: 'object_visible',
|
||||
object_id: planeId,
|
||||
hidden: true,
|
||||
},
|
||||
})
|
||||
})
|
||||
} else if (guiMode.mode === 'default') {
|
||||
const pathId =
|
||||
engineCommandManager &&
|
||||
isCursorInSketchCommandRange(
|
||||
@ -121,7 +128,7 @@ export function useAppMode() {
|
||||
},
|
||||
}
|
||||
)
|
||||
setDefaultPlanesHidden(engineCommandManager, defaultPlanes, true)
|
||||
hideDefaultPlanes(engineCommandManager, defaultPlanes)
|
||||
const sketchUuid = uuidv4()
|
||||
const proms: any[] = []
|
||||
proms.push(
|
||||
@ -197,10 +204,9 @@ function createPlane(
|
||||
return planeId
|
||||
}
|
||||
|
||||
function setDefaultPlanesHidden(
|
||||
engineCommandManager: EngineCommandManager | undefined,
|
||||
defaultPlanes: DefaultPlanes,
|
||||
hidden: boolean
|
||||
function hideDefaultPlanes(
|
||||
engineCommandManager: EngineCommandManager,
|
||||
defaultPlanes: DefaultPlanes
|
||||
) {
|
||||
Object.values(defaultPlanes).forEach((planeId) => {
|
||||
engineCommandManager?.sendSceneCommand({
|
||||
@ -209,7 +215,7 @@ function setDefaultPlanesHidden(
|
||||
cmd: {
|
||||
type: 'object_visible',
|
||||
object_id: planeId,
|
||||
hidden: hidden,
|
||||
hidden: true,
|
||||
},
|
||||
})
|
||||
})
|
||||
@ -223,17 +229,15 @@ function isCursorInSketchCommandRange(
|
||||
([id, artifact]) =>
|
||||
selectionRanges.codeBasedSelections.some(
|
||||
(selection) =>
|
||||
Array.isArray(selection?.range) &&
|
||||
Array.isArray(artifact?.range) &&
|
||||
Array.isArray(selection.range) &&
|
||||
Array.isArray(artifact.range) &&
|
||||
isOverlap(selection.range, artifact.range) &&
|
||||
(artifact.commandType === 'start_path' ||
|
||||
artifact.commandType === 'extend_path' ||
|
||||
artifact.commandType === 'close_path')
|
||||
'close_path')
|
||||
)
|
||||
)
|
||||
return overlapingEntries.length && overlapingEntries[0][1].parentId
|
||||
return overlapingEntries.length === 1 && overlapingEntries[0][1].parentId
|
||||
? overlapingEntries[0][1].parentId
|
||||
: overlapingEntries.find(
|
||||
([, artifact]) => artifact.commandType === 'start_path'
|
||||
)?.[0] || false
|
||||
: false
|
||||
}
|
||||
|
156
src/hooks/useCodeEval.ts
Normal file
156
src/hooks/useCodeEval.ts
Normal file
@ -0,0 +1,156 @@
|
||||
import { useEffect } from 'react'
|
||||
import { asyncParser } from '../lang/abstractSyntaxTree'
|
||||
import { _executor } from '../lang/executor'
|
||||
import { useStore } from '../useStore'
|
||||
import { KCLError } from '../lang/errors'
|
||||
|
||||
// This recently moved out of app.tsx
|
||||
// and is our old way of thinking that whenever the code changes we need to re-execute, instead of
|
||||
// being more decisive about when and where we execute, its likey this custom hook will be
|
||||
// refactored away entirely at some point
|
||||
|
||||
export function useCodeEval() {
|
||||
const {
|
||||
addLog,
|
||||
addKCLError,
|
||||
setAst,
|
||||
setError,
|
||||
setProgramMemory,
|
||||
resetLogs,
|
||||
resetKCLErrors,
|
||||
setArtifactMap,
|
||||
engineCommandManager,
|
||||
highlightRange,
|
||||
setHighlightRange,
|
||||
setCursor2,
|
||||
isStreamReady,
|
||||
setIsExecuting,
|
||||
defferedCode,
|
||||
} = useStore((s) => ({
|
||||
addLog: s.addLog,
|
||||
defferedCode: s.defferedCode,
|
||||
setAst: s.setAst,
|
||||
setError: s.setError,
|
||||
setProgramMemory: s.setProgramMemory,
|
||||
resetLogs: s.resetLogs,
|
||||
resetKCLErrors: s.resetKCLErrors,
|
||||
setArtifactMap: s.setArtifactNSourceRangeMaps,
|
||||
engineCommandManager: s.engineCommandManager,
|
||||
highlightRange: s.highlightRange,
|
||||
setHighlightRange: s.setHighlightRange,
|
||||
setCursor2: s.setCursor2,
|
||||
isStreamReady: s.isStreamReady,
|
||||
addKCLError: s.addKCLError,
|
||||
setIsExecuting: s.setIsExecuting,
|
||||
}))
|
||||
useEffect(() => {
|
||||
if (!isStreamReady) return
|
||||
if (!engineCommandManager) return
|
||||
let unsubFn: any[] = []
|
||||
const asyncWrap = async () => {
|
||||
try {
|
||||
if (!defferedCode) {
|
||||
setAst({
|
||||
start: 0,
|
||||
end: 0,
|
||||
body: [],
|
||||
nonCodeMeta: {
|
||||
noneCodeNodes: {},
|
||||
start: null,
|
||||
},
|
||||
})
|
||||
setProgramMemory({ root: {}, return: null })
|
||||
engineCommandManager.endSession()
|
||||
engineCommandManager.startNewSession()
|
||||
return
|
||||
}
|
||||
const _ast = await asyncParser(defferedCode)
|
||||
setAst(_ast)
|
||||
resetLogs()
|
||||
resetKCLErrors()
|
||||
engineCommandManager.endSession()
|
||||
engineCommandManager.startNewSession()
|
||||
setIsExecuting(true)
|
||||
const programMemory = await _executor(
|
||||
_ast,
|
||||
{
|
||||
root: {
|
||||
_0: {
|
||||
type: 'UserVal',
|
||||
value: 0,
|
||||
__meta: [],
|
||||
},
|
||||
_90: {
|
||||
type: 'UserVal',
|
||||
value: 90,
|
||||
__meta: [],
|
||||
},
|
||||
_180: {
|
||||
type: 'UserVal',
|
||||
value: 180,
|
||||
__meta: [],
|
||||
},
|
||||
_270: {
|
||||
type: 'UserVal',
|
||||
value: 270,
|
||||
__meta: [],
|
||||
},
|
||||
},
|
||||
return: null,
|
||||
},
|
||||
engineCommandManager
|
||||
)
|
||||
|
||||
const { artifactMap, sourceRangeMap } =
|
||||
await engineCommandManager.waitForAllCommands(_ast, programMemory)
|
||||
setIsExecuting(false)
|
||||
if (programMemory !== undefined) {
|
||||
setProgramMemory(programMemory)
|
||||
}
|
||||
|
||||
setArtifactMap({ artifactMap, sourceRangeMap })
|
||||
const unSubHover = engineCommandManager.subscribeToUnreliable({
|
||||
event: 'highlight_set_entity',
|
||||
callback: ({ data }) => {
|
||||
if (data?.entity_id) {
|
||||
const sourceRange = sourceRangeMap[data.entity_id]
|
||||
setHighlightRange(sourceRange)
|
||||
} else if (
|
||||
!highlightRange ||
|
||||
(highlightRange[0] !== 0 && highlightRange[1] !== 0)
|
||||
) {
|
||||
setHighlightRange([0, 0])
|
||||
}
|
||||
},
|
||||
})
|
||||
const unSubClick = engineCommandManager.subscribeTo({
|
||||
event: 'select_with_point',
|
||||
callback: ({ data }) => {
|
||||
if (!data?.entity_id) {
|
||||
setCursor2()
|
||||
return
|
||||
}
|
||||
const sourceRange = sourceRangeMap[data.entity_id]
|
||||
setCursor2({ range: sourceRange, type: 'default' })
|
||||
},
|
||||
})
|
||||
unsubFn.push(unSubHover, unSubClick)
|
||||
|
||||
setError()
|
||||
} catch (e: any) {
|
||||
setIsExecuting(false)
|
||||
if (e instanceof KCLError) {
|
||||
addKCLError(e)
|
||||
} else {
|
||||
setError('problem')
|
||||
console.log(e)
|
||||
addLog(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
asyncWrap()
|
||||
return () => {
|
||||
unsubFn.forEach((fn) => fn())
|
||||
}
|
||||
}, [defferedCode, isStreamReady, engineCommandManager])
|
||||
}
|
@ -1,50 +0,0 @@
|
||||
import { useEffect } from 'react'
|
||||
import { useStore } from 'useStore'
|
||||
|
||||
export function useEngineConnectionSubscriptions() {
|
||||
const {
|
||||
engineCommandManager,
|
||||
setCursor2,
|
||||
setHighlightRange,
|
||||
highlightRange,
|
||||
} = useStore((s) => ({
|
||||
engineCommandManager: s.engineCommandManager,
|
||||
setCursor2: s.setCursor2,
|
||||
setHighlightRange: s.setHighlightRange,
|
||||
highlightRange: s.highlightRange,
|
||||
}))
|
||||
useEffect(() => {
|
||||
if (!engineCommandManager) return
|
||||
|
||||
const unSubHover = engineCommandManager.subscribeToUnreliable({
|
||||
event: 'highlight_set_entity',
|
||||
callback: ({ data }) => {
|
||||
if (data?.entity_id) {
|
||||
const sourceRange =
|
||||
engineCommandManager.sourceRangeMap[data.entity_id]
|
||||
setHighlightRange(sourceRange)
|
||||
} else if (
|
||||
!highlightRange ||
|
||||
(highlightRange[0] !== 0 && highlightRange[1] !== 0)
|
||||
) {
|
||||
setHighlightRange([0, 0])
|
||||
}
|
||||
},
|
||||
})
|
||||
const unSubClick = engineCommandManager.subscribeTo({
|
||||
event: 'select_with_point',
|
||||
callback: ({ data }) => {
|
||||
if (!data?.entity_id) {
|
||||
setCursor2()
|
||||
return
|
||||
}
|
||||
const sourceRange = engineCommandManager.sourceRangeMap[data.entity_id]
|
||||
setCursor2({ range: sourceRange, type: 'default' })
|
||||
},
|
||||
})
|
||||
return () => {
|
||||
unSubHover()
|
||||
unSubClick()
|
||||
}
|
||||
}, [engineCommandManager, setCursor2, setHighlightRange, highlightRange])
|
||||
}
|
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)
|
||||
}
|
@ -12,13 +12,11 @@ export function useSetupEngineManager(
|
||||
setMediaStream,
|
||||
setIsStreamReady,
|
||||
setStreamDimensions,
|
||||
executeCode,
|
||||
} = useStore((s) => ({
|
||||
setEngineCommandManager: s.setEngineCommandManager,
|
||||
setMediaStream: s.setMediaStream,
|
||||
setIsStreamReady: s.setIsStreamReady,
|
||||
setStreamDimensions: s.setStreamDimensions,
|
||||
executeCode: s.executeCode,
|
||||
}))
|
||||
|
||||
const streamWidth = streamRef?.current?.offsetWidth
|
||||
@ -43,9 +41,6 @@ export function useSetupEngineManager(
|
||||
token,
|
||||
})
|
||||
setEngineCommandManager(eng)
|
||||
eng.waitForReady.then(() => {
|
||||
executeCode()
|
||||
})
|
||||
return () => {
|
||||
eng?.tearDown()
|
||||
}
|
||||
|
@ -46,7 +46,7 @@ export function useConvertToVariable() {
|
||||
variableName
|
||||
)
|
||||
|
||||
updateAst(_modifiedAst, true)
|
||||
updateAst(_modifiedAst)
|
||||
} catch (e) {
|
||||
console.log('e', e)
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Selection, ToolTip } from '../useStore'
|
||||
import { Selection, TooTip } from '../useStore'
|
||||
import {
|
||||
Program,
|
||||
CallExpression,
|
||||
@ -305,11 +305,7 @@ export function extrudeSketch(
|
||||
}
|
||||
const name = findUniqueName(node, 'part')
|
||||
const VariableDeclaration = createVariableDeclaration(name, extrudeCall)
|
||||
let showCallIndex = getShowIndex(_node)
|
||||
if (showCallIndex == -1) {
|
||||
// We didn't find a show, so let's just append everything
|
||||
showCallIndex = _node.body.length
|
||||
}
|
||||
const showCallIndex = getShowIndex(_node)
|
||||
_node.body.splice(showCallIndex, 0, VariableDeclaration)
|
||||
const pathToExtrudeArg: PathToNode = [
|
||||
['body', ''],
|
||||
@ -639,7 +635,7 @@ export function giveSketchFnCallTag(
|
||||
createLiteral(tag || findUniqueName(ast, 'seg', 2))) as Literal
|
||||
const tagStr = String(tagValue.value)
|
||||
const newFirstArg = createFirstArg(
|
||||
primaryCallExp.callee.name as ToolTip,
|
||||
primaryCallExp.callee.name as TooTip,
|
||||
firstArg.val,
|
||||
tagValue
|
||||
)
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { PathToNode, ProgramMemory, SketchGroup, SourceRange } from './executor'
|
||||
import { Selection, ToolTip } from '../useStore'
|
||||
import { Selection, TooTip } from '../useStore'
|
||||
import {
|
||||
BinaryExpression,
|
||||
Program,
|
||||
@ -457,7 +457,7 @@ export function isLinesParallelAndConstrained(
|
||||
const secondaryFirstArg = getFirstArg(secondaryNode)
|
||||
const constraintType = getConstraintType(
|
||||
secondaryFirstArg.val,
|
||||
secondaryNode.callee.name as ToolTip
|
||||
secondaryNode.callee.name as TooTip
|
||||
)
|
||||
const constraintLevel = getConstraintLevelFromSourceRange(
|
||||
secondaryLine.range,
|
||||
|
@ -395,6 +395,8 @@ export class EngineConnection {
|
||||
|
||||
let videoTrack = mediaStream.getVideoTracks()[0]
|
||||
this.pc?.getStats(videoTrack).then((videoTrackStats) => {
|
||||
// TODO(paultag): this needs type information from the KittyCAD typescript
|
||||
// library once it's updated
|
||||
let client_metrics: ClientMetrics = {
|
||||
rtc_frames_decoded: 0,
|
||||
rtc_frames_dropped: 0,
|
||||
@ -422,13 +424,12 @@ export class EngineConnection {
|
||||
videoTrackReport.framesReceived
|
||||
client_metrics.rtc_frames_per_second =
|
||||
videoTrackReport.framesPerSecond || 0
|
||||
client_metrics.rtc_freeze_count =
|
||||
videoTrackReport.freezeCount || 0
|
||||
client_metrics.rtc_freeze_count = videoTrackReport.freezeCount
|
||||
client_metrics.rtc_jitter_sec = videoTrackReport.jitter
|
||||
client_metrics.rtc_keyframes_decoded =
|
||||
videoTrackReport.keyFramesDecoded
|
||||
client_metrics.rtc_total_freezes_duration_sec =
|
||||
videoTrackReport.totalFreezesDuration || 0
|
||||
videoTrackReport.totalFreezesDuration
|
||||
} else if (videoTrackReport.type === 'transport') {
|
||||
// videoTrackReport.bytesReceived,
|
||||
// videoTrackReport.bytesSent,
|
||||
|
@ -21,7 +21,7 @@ import {
|
||||
getNodePathFromSourceRange,
|
||||
} from '../queryAst'
|
||||
import { isLiteralArrayOrStatic } from './sketchcombos'
|
||||
import { GuiModes, toolTips, ToolTip } from '../../useStore'
|
||||
import { GuiModes, toolTips, TooTip } from '../../useStore'
|
||||
import { createPipeExpression, splitPathAtPipeExpression } from '../modifyAst'
|
||||
import { generateUuidFromHashSeed } from '../../lib/uuid'
|
||||
|
||||
@ -57,7 +57,7 @@ export function getCoordsFromPaths(skGroup: SketchGroup, index = 0): Coords2d {
|
||||
}
|
||||
|
||||
export function createFirstArg(
|
||||
sketchFn: ToolTip,
|
||||
sketchFn: TooTip,
|
||||
val: Value | [Value, Value] | [Value, Value, Value],
|
||||
tag?: Value
|
||||
): Value {
|
||||
@ -943,7 +943,7 @@ interface CreateLineFnCallArgs {
|
||||
programMemory: ProgramMemory
|
||||
to: [number, number]
|
||||
from: [number, number]
|
||||
fnName: ToolTip
|
||||
fnName: TooTip
|
||||
pathToNode: PathToNode
|
||||
}
|
||||
|
||||
@ -1029,7 +1029,7 @@ export function replaceSketchLine({
|
||||
node: Program
|
||||
programMemory: ProgramMemory
|
||||
sourceRange: SourceRange
|
||||
fnName: ToolTip
|
||||
fnName: TooTip
|
||||
to: [number, number]
|
||||
from: [number, number]
|
||||
createCallback: TransformCallback
|
||||
@ -1208,7 +1208,7 @@ function getFirstArgValuesForAngleFns(callExpression: CallExpression): {
|
||||
const tag = firstArg.properties.find((p) => p.key.name === 'tag')?.value
|
||||
const angle = firstArg.properties.find((p) => p.key.name === 'angle')?.value
|
||||
const secondArgName = ['angledLineToX', 'angledLineToY'].includes(
|
||||
callExpression?.callee?.name as ToolTip
|
||||
callExpression?.callee?.name as TooTip
|
||||
)
|
||||
? 'to'
|
||||
: 'length'
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { ToolTip, toolTips } from '../../useStore'
|
||||
import { TooTip, toolTips } from '../../useStore'
|
||||
import {
|
||||
Program,
|
||||
VariableDeclarator,
|
||||
@ -67,10 +67,7 @@ export function isSketchVariablesLinked(
|
||||
return false
|
||||
const firstCallExp = // first in pipe expression or just the call expression
|
||||
init?.type === 'CallExpression' ? init : (init?.body[0] as CallExpression)
|
||||
if (
|
||||
!firstCallExp ||
|
||||
!toolTips.includes(firstCallExp?.callee?.name as ToolTip)
|
||||
)
|
||||
if (!firstCallExp || !toolTips.includes(firstCallExp?.callee?.name as TooTip))
|
||||
return false
|
||||
// convention for sketch fns is that the second argument is the sketch group
|
||||
const secondArg = firstCallExp?.arguments[1]
|
||||
|
@ -9,7 +9,7 @@ import {
|
||||
getConstraintLevelFromSourceRange,
|
||||
} from './sketchcombos'
|
||||
import { initPromise } from '../rust'
|
||||
import { Selections, ToolTip } from '../../useStore'
|
||||
import { Selections, TooTip } from '../../useStore'
|
||||
import { enginelessExecutor } from '../../lib/testHelpers'
|
||||
import { recast } from '../../lang/recast'
|
||||
|
||||
@ -68,7 +68,7 @@ function getConstraintTypeFromSourceHelper(
|
||||
Value,
|
||||
Value
|
||||
]
|
||||
const fnName = (ast.body[0] as any).expression.callee.name as ToolTip
|
||||
const fnName = (ast.body[0] as any).expression.callee.name as TooTip
|
||||
return getConstraintType(args, fnName)
|
||||
}
|
||||
function getConstraintTypeFromSourceHelper2(
|
||||
@ -76,7 +76,7 @@ function getConstraintTypeFromSourceHelper2(
|
||||
): ReturnType<typeof getConstraintType> {
|
||||
const ast = parser_wasm(code)
|
||||
const arg = (ast.body[0] as any).expression.arguments[0] as Value
|
||||
const fnName = (ast.body[0] as any).expression.callee.name as ToolTip
|
||||
const fnName = (ast.body[0] as any).expression.callee.name as TooTip
|
||||
return getConstraintType(arg, fnName)
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { TransformCallback } from './stdTypes'
|
||||
import { Selections, toolTips, ToolTip, Selection } from '../../useStore'
|
||||
import { Selections, toolTips, TooTip, Selection } from '../../useStore'
|
||||
import {
|
||||
CallExpression,
|
||||
Program,
|
||||
@ -54,7 +54,7 @@ export type ConstraintType =
|
||||
| 'setAngleBetween'
|
||||
|
||||
function createCallWrapper(
|
||||
a: ToolTip,
|
||||
a: TooTip,
|
||||
val: [Value, Value] | Value,
|
||||
tag?: Value,
|
||||
valueUsedInTransform?: number
|
||||
@ -101,7 +101,7 @@ function intersectCallWrapper({
|
||||
}
|
||||
|
||||
export type TransformInfo = {
|
||||
tooltip: ToolTip
|
||||
tooltip: TooTip
|
||||
createNode: (a: {
|
||||
varValA: Value // x / angle
|
||||
varValB: Value // y / length or x y for angledLineOfXlength etc
|
||||
@ -112,7 +112,7 @@ export type TransformInfo = {
|
||||
}
|
||||
|
||||
type TransformMap = {
|
||||
[key in ToolTip]?: {
|
||||
[key in TooTip]?: {
|
||||
[key in LineInputsType | 'free']?: {
|
||||
[key in ConstraintType]?: TransformInfo
|
||||
}
|
||||
@ -1095,12 +1095,12 @@ export function getRemoveConstraintsTransform(
|
||||
sketchFnExp: CallExpression,
|
||||
constraintType: ConstraintType
|
||||
): TransformInfo | false {
|
||||
let name = sketchFnExp.callee.name as ToolTip
|
||||
let name = sketchFnExp.callee.name as TooTip
|
||||
if (!toolTips.includes(name)) {
|
||||
return false
|
||||
}
|
||||
const xyLineMap: {
|
||||
[key in ToolTip]?: ToolTip
|
||||
[key in TooTip]?: TooTip
|
||||
} = {
|
||||
xLine: 'line',
|
||||
yLine: 'line',
|
||||
@ -1167,12 +1167,12 @@ function getTransformMapPath(
|
||||
constraintType: ConstraintType
|
||||
):
|
||||
| {
|
||||
toolTip: ToolTip
|
||||
toolTip: TooTip
|
||||
lineInputType: LineInputsType | 'free'
|
||||
constraintType: ConstraintType
|
||||
}
|
||||
| false {
|
||||
const name = sketchFnExp.callee.name as ToolTip
|
||||
const name = sketchFnExp.callee.name as TooTip
|
||||
if (!toolTips.includes(name)) {
|
||||
return false
|
||||
}
|
||||
@ -1225,7 +1225,7 @@ export function getTransformInfo(
|
||||
|
||||
export function getConstraintType(
|
||||
val: Value | [Value, Value] | [Value, Value, Value],
|
||||
fnName: ToolTip
|
||||
fnName: TooTip
|
||||
): LineInputsType | null {
|
||||
// this function assumes that for two val sketch functions that one arg is locked down not both
|
||||
// and for one val sketch functions that the arg is NOT locked down
|
||||
@ -1445,7 +1445,7 @@ export function transformAstSketchLines({
|
||||
programMemory,
|
||||
sourceRange: range,
|
||||
referencedSegment,
|
||||
fnName: transformTo || (callExp.callee.name as ToolTip),
|
||||
fnName: transformTo || (callExp.callee.name as TooTip),
|
||||
to,
|
||||
from,
|
||||
createCallback: callBack({
|
||||
@ -1511,7 +1511,7 @@ export function getConstraintLevelFromSourceRange(
|
||||
getNodePathFromSourceRange(ast, cursorRange),
|
||||
'CallExpression'
|
||||
)
|
||||
const name = sketchFnExp?.callee?.name as ToolTip
|
||||
const name = sketchFnExp?.callee?.name as TooTip
|
||||
if (!toolTips.includes(name)) return 'free'
|
||||
|
||||
const firstArg = getFirstArg(sketchFnExp)
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { ProgramMemory, Path, SourceRange } from '../executor'
|
||||
import { Program, Value } from '../abstractSyntaxTreeTypes'
|
||||
import { ToolTip } from '../../useStore'
|
||||
import { TooTip } from '../../useStore'
|
||||
import { PathToNode } from '../executor'
|
||||
import { EngineCommandManager } from './engineConnection'
|
||||
|
||||
@ -45,7 +45,7 @@ export type TransformCallback = (
|
||||
}
|
||||
|
||||
export type SketchCallTransfromMap = {
|
||||
[key in ToolTip]: TransformCallback
|
||||
[key in TooTip]: TransformCallback
|
||||
}
|
||||
|
||||
export interface SketchLineHelper {
|
||||
|
@ -57,7 +57,7 @@ export function throttle<T>(
|
||||
}
|
||||
|
||||
// takes a function and executes it after the wait time, if the function is called again before the wait time is up, the timer is reset
|
||||
export function deferExecution<T>(func: (args: T) => any, wait: number) {
|
||||
export function defferExecution<T>(func: (args: T) => any, wait: number) {
|
||||
let timeout: ReturnType<typeof setTimeout> | null
|
||||
let latestArgs: T
|
||||
|
||||
@ -66,7 +66,7 @@ export function deferExecution<T>(func: (args: T) => any, wait: number) {
|
||||
func(latestArgs)
|
||||
}
|
||||
|
||||
function deferred(args: T) {
|
||||
function deffered(args: T) {
|
||||
latestArgs = args
|
||||
if (timeout) {
|
||||
clearTimeout(timeout)
|
||||
@ -74,7 +74,7 @@ export function deferExecution<T>(func: (args: T) => any, wait: number) {
|
||||
timeout = setTimeout(later, wait)
|
||||
}
|
||||
|
||||
return deferred
|
||||
return deffered
|
||||
}
|
||||
|
||||
export function getNormalisedCoordinates({
|
||||
|
@ -2,8 +2,6 @@ import { createMachine, assign } from 'xstate'
|
||||
import { Models } from '@kittycad/lib'
|
||||
import withBaseURL from '../lib/withBaseURL'
|
||||
import { CommandBarMeta } from '../lib/commands'
|
||||
import { isTauri } from 'lib/isTauri'
|
||||
import { invoke } from '@tauri-apps/api'
|
||||
|
||||
const SKIP_AUTH =
|
||||
import.meta.env.VITE_KC_SKIP_AUTH === 'true' && import.meta.env.DEV
|
||||
@ -117,25 +115,16 @@ async function getUser(context: UserContext) {
|
||||
const headers: { [key: string]: string } = {
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
|
||||
if (!context.token && isTauri()) throw new Error('No token found')
|
||||
if (!context.token && '__TAURI__' in window) throw 'not log in'
|
||||
if (context.token) headers['Authorization'] = `Bearer ${context.token}`
|
||||
if (SKIP_AUTH) return LOCAL_USER
|
||||
const response = await fetch(url, {
|
||||
method: 'GET',
|
||||
credentials: 'include',
|
||||
headers,
|
||||
})
|
||||
|
||||
const userPromise = !isTauri()
|
||||
? fetch(url, {
|
||||
method: 'GET',
|
||||
credentials: 'include',
|
||||
headers,
|
||||
})
|
||||
.then((res) => res.json())
|
||||
.catch((err) => console.error('error from Browser getUser', err))
|
||||
: invoke<Models['User_type'] | Record<'error_code', unknown>>('get_user', {
|
||||
token: context.token,
|
||||
}).catch((err) => console.error('error from Tauri getUser', err))
|
||||
|
||||
const user = await userPromise
|
||||
|
||||
const user = await response.json()
|
||||
if ('error_code' in user) throw new Error(user.message)
|
||||
|
||||
return user
|
||||
|
466
src/machines/modelingMachine.ts
Normal file
466
src/machines/modelingMachine.ts
Normal file
@ -0,0 +1,466 @@
|
||||
import { Program } from 'lang/abstractSyntaxTreeTypes'
|
||||
import { ProgramMemory } from 'lang/executor'
|
||||
import { Axis, Selection, Selections } from 'useStore'
|
||||
import { assign, createMachine } from 'xstate'
|
||||
|
||||
export const MODELING_PERSIST_KEY = 'MODELING_PERSIST_KEY'
|
||||
|
||||
export const modelingMachine = createMachine(
|
||||
{
|
||||
/** @xstate-layout N4IgpgJg5mDOIC5QFkD2EwBsCWA7KAxAMICGuAxlgNoAMAuoqAA6qzYAu2qujIAHogC0AFgCMAOgBMogOwyAzAE5hNABw0ZAVlGKANCACeQyQDZh4zTMWj5GmqfnDN8gL4v9aDDnzjsETGAEAMpg7AAEsFhg5JzctAxIICxssTyJAgiimuZOkqqSMjRqipKKcvpGCIKiqibiwrVqkjSiwpJiCm4e6Fh4UL7+gQAicFExYSx47PG8yRxcaaAZgvKqivUaJquywtYyohWIojbiqqr7NZprps2u7iCevT5+AQQjkQHjkDAziXOpvGWSnkUk0JhMVkkmmkbRMmkOVWE5mkighNHk7WaZzu3S8fQGr3eY3CJD42Fgv2YrHm3EBQhkpVBpm0q2EMi2qgRImROjRGLaahkqi6Dx63n6L0CIU+4UmuGm9Fm1IB6SE8kK4hku1MilUlgh4JkCNWIJMzQhThKiiukhFj3FBKlxLC3zAlKSyoWdKq8hMqnEinRtgaohoELB8MMiE0zikpUkpnR1sDijtYvxkuCztJ5Pd-y9qqq4PWKgjZ2yUJ0COcdXRNEUvvyMYxqfu9ozgyzMrCADMSJQ857aYWVpp1udRAVA1kTMckQiGTRxGHWtaYyYaA1NGm8c9OwBReVgABOEQA1qFyAALQcpAtLRBagNOIW7RuqRxcmz+hob-ItNoOhxUVdwlA8j1PWAL3Ya8qFEBIqTvYcHwQBll1sDRdhMZRRD9eQuTWKQVDkK5Wm0GMZB3J4wNefcAEcAFdsCYF0+HYY8GIwW8aUWfhEATGQ42tWRClwxRUWrfJ6jkK1hHkMckyoh1M3opiWLANiOK4+ClSQ3iMmkCRagxGpZDWdQJKjBAtiXBtBTBUw2VKJSO0JUZuz7AdFT+Id9LVBtxHBMQwTKLUCjZL8ynEWx1CFPYrEott0z3V5pWiElMEwbiVRQlZwWXMMxBKVZ1FKBE1kEqEtm0JxWlKEwXJS4Z3PSsISEy7L7z4n0oq0JErmyXZWiNKzRyXHQ1AafYqukRqaMCVTmN7bBMtCTrkO6yRHGi0ydAaTc1xGyprHWXVQ0sesdVtJLQMdAhFpYnsVoCaYdJ8vTvT204N1kbCajEYyEXE00NFw2dVDEDdEtxajxCCaDrwICBuDAXxcAAN1QC9xCIY8wBIdgwHPS8b28xCeO9VZBODaxVCyPIIS1BE5zjFQij9cSamUOa4YRq9iDISgsrJj0PsLSczlBeRhuaY5wq5K46jKetCnkdUIfOHn4ZJ8QAEkD0YpbvCJ9hUFQYWENFinxehSRxEnMR1QxZ2si5dVTvky65FacF0S1vm9c7NLxjlBVLfzDaMkDEEwS50plAKNQv0IrZarMaWkTphqbth7WYKvQPUudSIoAAWzAeV1r8hBrAkTc5JoaFZFaedRr9TVoRjc4rgaNpRH9nX9bcj5WtDqvvUaCxxLhfrrSFTlRvd6LwTNVY4V5LUB-zwvmpH8YS-LyuRYj6u9QkEo5F9JxG9WSQFZOaXShaWxZG3HOHTz68d4IZASAvCIwBlwruEdGJ5ODkHauPG2Y56hTk3CFRw+ErJWFOI3doatSiOH1FvL+Q9Ai-3-gfYBYQryoGPNgAAXtwdgkDj6+U+hWU4VwGxOA3C0D8bsQRq3TuqS4vpZw4ILgAGTwETAAKmbTASMUZo0xtjXG+NCZhGNlAlCWRcKBWUNfMGrJIyVBEFCAMZxlAtD9LOZwgjxAiNwOIyR4gAByqAwgAAVUBTFgAQAAghACAEw3FH3DvQ8W1ochrAbCvNQSgFb1ntrYEJF97BIksdY2x5txCuKmGEbxGAIBeJ8X4qYqjNrMgDFCa0RQtS1AhNE9YNh6zZASe0YQyTRFhAkWkkIQD5RZJ8ZAPJvix50LFihMpMdLDtE0PUjmR0hCTNqXEhpicmktJsW0uxnTD7hGyX0ogqBS5MBekTFRQzrYjPZBILIYZnDqA1iYN2ux7YxiuEUQMqdubv3xPuTSnFAjIxsbIrGqMFEEyJhpdiPyikZFZMua0pRmxjlREzKyvoQR6gSscBkvpSjQxArDL54KMD3UNupb5XETk5W6rqU6BRsiCg-HLBEEIJBQjkpMhQ0htDXRhg6fFWkFrEtYgSt0b1yYUoyAaU4id0QzgTGCRl+xQRyTkAUdodNgLth8Lyn539g7hE8m6clXUMh6lqZYMc3sFnCAXLUAMjgwZlPREkj5mrSWo11QsMIAAlfGEADBZnCBAckNCKAGsCcM7qDsQStD1LKjc8ZrW1gcHqX09ZsI4o1f0T+V4wi4CcfqrsrV9WQsQMIDcy5WiCmOM3TcMyfR232B0P6ZwV5gh5gAMWeqEaR-y8ByKBXjEFy1Vph10qcza4l6hjkcNYByeorVWVDDaxcJRSoRi0O2zt7AiVqSHS9YtNchSBQGkKWcWCWjMw0OOcxg0-RFG0Bu4d26lpPWHXBMNY7xU2taMNV8H5rB3wXZe76WQb3qBrQ+l6OrnSun3RVIizaDQlHBAvSoxwIbluOGGBkepNbOv6B24dcNiQeu9SQX1-qwjHjI9gBiFJDWRyOAUJWkylA2CxUiO5C7-oYdDBCPIVx03JXw5uojMoSM+r9bql00BQ2jrFUcWQ6wlCTOkJifYKHGPWlteoEySg41v3uLmjA8BEgZrk0a+kcgmTr1ZOyVYBEzCaj2uEtNuE37ctcmAczDGqiMh0BhSZ0IPxyHOAiUoyImVaPULbZpeHeYk286fdEpTL5mEC7fLkchqaomqhoLUVLLF4MS59DCUh9jTrEKWyw879EsvqBx9klgdCJlix5nwWarGtPaZgYr4sY322tKsRu6nKxcnTlIDkpRJzsmTCs1JmAHFOIyfKEzoqLOZEDONQb0WRvaDdicJwEZS2Z2lrhObay0lDBRr1kZ1glynYcE0PYSD9FIkEuoNoDIk1FDO3FjrKSLsLeW1s3pEAbubVwiCVE0tVjTlMXotUB31zZDMA0U72c2uZoDgD7rRGukg5yeDgypaJBqzDGY15DYEc+iR0d1H9LfuY-EFqjARPEBmn9PkQo0qHJVWrOqe20hwZ5AxB+VruKeWup3mz1CCrxnZGcFaW21rNCBXOHZ3CawbAY4l58qX7ruBeokzL0MGotSTjNGYD88kauPhQSasMRQ0dFHVUJ5nUvSO+pN0VJzWRxLZFwgoPQyC9Rq7kFsTX1hfSCJzXm-sXn3ofv4jGKeZQ0tyXMuUReII0EMjKPyBO7ndc+AIy9GXHJ7aVZC2j-9QMnCajyPkWWUfsIQdCNLxP8nULYSIsoYLNVsLU+OMoJhlopqlqhEXjN4hS-t4N7gI3ZHKhrZ86buodMGwYjy6nIf3G71olkO0FWbf2DiE98vq2XfWhKCkDUDEWosIJlt5kPfjcD8MhUGUNwbggA */
|
||||
id: 'Modeling',
|
||||
|
||||
tsTypes: {} as import('./modelingMachine.typegen').Typegen0,
|
||||
predictableActionArguments: true,
|
||||
|
||||
context: {
|
||||
guiMode: 'default',
|
||||
selection: [] as string[],
|
||||
ast: null as Program | null,
|
||||
selectionRanges: {
|
||||
otherSelections: [],
|
||||
codeBasedSelections: [],
|
||||
} as Selections,
|
||||
programMemory: { root: {}, pendingMemory: {} } as ProgramMemory,
|
||||
// TODO: migrate engineCommandManager from useStore
|
||||
// engineCommandManager?: EngineCommandManager
|
||||
},
|
||||
|
||||
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: 'Equip extrude' }
|
||||
| { type: 'Equip fillet' }
|
||||
| { 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: 'Set selection'; data: Selections }
|
||||
| {
|
||||
type: 'Select point'
|
||||
data: Selection & { type: 'point' | 'line-end' | 'line-mid' }
|
||||
}
|
||||
| { type: 'Sketch no face' }
|
||||
| { type: 'Toggle gui mode' }
|
||||
| { type: 'Cancel' }
|
||||
| { type: 'Add point' }
|
||||
| { type: 'Equip line tool' }
|
||||
| { type: 'Set radius' }
|
||||
| { type: 'Make segment horizontal' }
|
||||
| { type: 'Make segment vertical' }
|
||||
| { type: 'Complete line' }
|
||||
| { type: 'Set distance' },
|
||||
},
|
||||
|
||||
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',
|
||||
},
|
||||
'Sketch no face',
|
||||
],
|
||||
|
||||
'Equip extrude': [
|
||||
{
|
||||
target: 'Extrude',
|
||||
cond: 'Selection is empty',
|
||||
},
|
||||
{
|
||||
target: 'Extrude',
|
||||
cond: 'Selection is one 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',
|
||||
},
|
||||
|
||||
'Equip fillet': [
|
||||
{
|
||||
target: 'Fillet',
|
||||
cond: 'Selection is empty',
|
||||
},
|
||||
{
|
||||
target: 'Fillet',
|
||||
cond: 'Selection is one or more edges',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
|
||||
Sketch: {
|
||||
states: {
|
||||
Idle: {
|
||||
on: {
|
||||
'Equip line tool': 'Line Tool',
|
||||
|
||||
'Select point': {
|
||||
target: 'Idle',
|
||||
internal: true,
|
||||
actions: [
|
||||
'Update code selection cursors',
|
||||
'Add to code-based selection',
|
||||
],
|
||||
},
|
||||
|
||||
'Select segment': {
|
||||
target: 'Idle',
|
||||
internal: true,
|
||||
actions: [
|
||||
'Update code selection cursors',
|
||||
'Add to code-based selection',
|
||||
],
|
||||
},
|
||||
|
||||
'Deselect point': {
|
||||
target: 'Idle',
|
||||
internal: true,
|
||||
cond: 'Selection contains point',
|
||||
actions: [
|
||||
'Update code selection cursors',
|
||||
'Add to code-based selection',
|
||||
],
|
||||
},
|
||||
|
||||
'Deselect segment': {
|
||||
target: 'Idle',
|
||||
internal: true,
|
||||
cond: 'Selection contains line',
|
||||
actions: [
|
||||
'Update code selection cursors',
|
||||
'Add to code-based selection',
|
||||
],
|
||||
},
|
||||
|
||||
'Make segment vertical': {
|
||||
cond: 'Can make selection vertical',
|
||||
target: 'Idle',
|
||||
internal: true,
|
||||
actions: ['Make selection vertical'],
|
||||
},
|
||||
|
||||
'Make segment horizontal': {
|
||||
target: 'Idle',
|
||||
internal: true,
|
||||
cond: 'Can make selection horizontal',
|
||||
actions: ['Make selection horizontal'],
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
'Line Tool': {
|
||||
states: {
|
||||
'No Points': {
|
||||
on: {
|
||||
'Add point': {
|
||||
target: 'Point Added',
|
||||
actions: ['Modify AST', 'Update code selection cursors'],
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
Done: {
|
||||
type: 'final',
|
||||
},
|
||||
|
||||
'Point Added': {
|
||||
on: {
|
||||
'Add point': {
|
||||
target: 'Segment Added',
|
||||
actions: ['Modify AST', 'Update code selection cursors'],
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
'Segment Added': {
|
||||
on: {
|
||||
'Add point': {
|
||||
target: 'Segment Added',
|
||||
internal: true,
|
||||
actions: ['Modify AST', 'Update code selection cursors'],
|
||||
},
|
||||
|
||||
'Complete line': {
|
||||
target: 'Done',
|
||||
actions: ['Modify AST', 'Update code selection cursors'],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
initial: 'No Points',
|
||||
|
||||
invoke: {
|
||||
src: 'createLine',
|
||||
id: 'Create line',
|
||||
onDone: 'Idle',
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
initial: 'Idle',
|
||||
|
||||
on: {
|
||||
Cancel: '.Idle',
|
||||
},
|
||||
|
||||
invoke: {
|
||||
src: 'createSketch',
|
||||
id: 'Create sketch',
|
||||
onDone: 'idle',
|
||||
},
|
||||
},
|
||||
|
||||
Extrude: {
|
||||
states: {
|
||||
Idle: {
|
||||
on: {
|
||||
'Select face': 'Selection Ready',
|
||||
},
|
||||
},
|
||||
'Selection Ready': {
|
||||
on: {
|
||||
'Set distance': 'Ready',
|
||||
},
|
||||
},
|
||||
Ready: {},
|
||||
},
|
||||
|
||||
initial: 'Idle',
|
||||
|
||||
on: {
|
||||
'Equip extrude': [
|
||||
{
|
||||
target: '.Selection Ready',
|
||||
cond: 'Selection is one face',
|
||||
},
|
||||
'.Idle',
|
||||
],
|
||||
},
|
||||
|
||||
invoke: {
|
||||
src: 'createExtrude',
|
||||
id: 'Create extrude',
|
||||
onDone: {
|
||||
target: 'idle',
|
||||
actions: ['Modify AST', 'Clear selection'],
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
'Sketch no face': {
|
||||
on: {
|
||||
'Select face': 'Sketch',
|
||||
},
|
||||
},
|
||||
|
||||
Fillet: {
|
||||
states: {
|
||||
Idle: {
|
||||
on: {
|
||||
'Select edge': 'Selection Ready',
|
||||
},
|
||||
},
|
||||
'Selection Ready': {
|
||||
on: {
|
||||
'Set radius': 'Ready',
|
||||
|
||||
'Select edge': {
|
||||
target: 'Selection Ready',
|
||||
internal: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
Ready: {},
|
||||
},
|
||||
|
||||
initial: 'Ready',
|
||||
|
||||
on: {
|
||||
'Equip fillet': [
|
||||
{
|
||||
target: '.Selection Ready',
|
||||
cond: 'Selection is one or more edges',
|
||||
},
|
||||
'.Idle',
|
||||
],
|
||||
},
|
||||
|
||||
invoke: {
|
||||
src: 'createFillet',
|
||||
id: 'Create fillet',
|
||||
onDone: {
|
||||
target: 'idle',
|
||||
actions: ['Modify AST', 'Clear selection'],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
initial: 'idle',
|
||||
|
||||
on: {
|
||||
Cancel: '.idle',
|
||||
},
|
||||
},
|
||||
{
|
||||
actions: {
|
||||
'Set selection': assign({
|
||||
selectionRanges: (_, event) => event.data,
|
||||
}),
|
||||
'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: [],
|
||||
}),
|
||||
}),
|
||||
},
|
||||
}
|
||||
)
|
165
src/machines/modelingMachine.typegen.ts
Normal file
165
src/machines/modelingMachine.typegen.ts
Normal file
@ -0,0 +1,165 @@
|
||||
// This file was automatically generated. Edits will be overwritten
|
||||
|
||||
export interface Typegen0 {
|
||||
'@@xstate/typegen': true
|
||||
internalEvents: {
|
||||
'done.invoke.Create extrude': {
|
||||
type: 'done.invoke.Create extrude'
|
||||
data: unknown
|
||||
__tip: 'See the XState TS docs to learn how to strongly type this.'
|
||||
}
|
||||
'done.invoke.Create fillet': {
|
||||
type: 'done.invoke.Create fillet'
|
||||
data: unknown
|
||||
__tip: 'See the XState TS docs to learn how to strongly type this.'
|
||||
}
|
||||
'done.invoke.Create line': {
|
||||
type: 'done.invoke.Create line'
|
||||
data: unknown
|
||||
__tip: 'See the XState TS docs to learn how to strongly type this.'
|
||||
}
|
||||
'done.invoke.Create sketch': {
|
||||
type: 'done.invoke.Create sketch'
|
||||
data: unknown
|
||||
__tip: 'See the XState TS docs to learn how to strongly type this.'
|
||||
}
|
||||
'error.platform.Create extrude': {
|
||||
type: 'error.platform.Create extrude'
|
||||
data: unknown
|
||||
}
|
||||
'error.platform.Create fillet': {
|
||||
type: 'error.platform.Create fillet'
|
||||
data: unknown
|
||||
}
|
||||
'error.platform.Create line': {
|
||||
type: 'error.platform.Create line'
|
||||
data: unknown
|
||||
}
|
||||
'error.platform.Create sketch': {
|
||||
type: 'error.platform.Create sketch'
|
||||
data: unknown
|
||||
}
|
||||
'xstate.init': { type: 'xstate.init' }
|
||||
}
|
||||
invokeSrcNameMap: {
|
||||
createExtrude: 'done.invoke.Create extrude'
|
||||
createFillet: 'done.invoke.Create fillet'
|
||||
createLine: 'done.invoke.Create line'
|
||||
createSketch: 'done.invoke.Create sketch'
|
||||
}
|
||||
missingImplementations: {
|
||||
actions:
|
||||
| 'Make selection horizontal'
|
||||
| 'Make selection vertical'
|
||||
| 'Modify AST'
|
||||
| 'Update code selection cursors'
|
||||
delays: never
|
||||
guards:
|
||||
| 'Can make selection horizontal'
|
||||
| 'Can make selection vertical'
|
||||
| 'Selection contains axis'
|
||||
| 'Selection contains edge'
|
||||
| 'Selection contains face'
|
||||
| 'Selection contains line'
|
||||
| 'Selection contains point'
|
||||
| 'Selection is empty'
|
||||
| 'Selection is not empty'
|
||||
| 'Selection is one face'
|
||||
| 'Selection is one or more edges'
|
||||
services: 'createExtrude' | 'createFillet' | 'createLine' | 'createSketch'
|
||||
}
|
||||
eventsCausingActions: {
|
||||
'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'
|
||||
| 'done.invoke.Create extrude'
|
||||
| 'done.invoke.Create fillet'
|
||||
'Make selection horizontal': 'Make segment horizontal'
|
||||
'Make selection vertical': 'Make segment vertical'
|
||||
'Modify AST':
|
||||
| 'Add point'
|
||||
| 'Complete line'
|
||||
| 'done.invoke.Create extrude'
|
||||
| 'done.invoke.Create fillet'
|
||||
'Remove from code-based selection':
|
||||
| 'Deselect edge'
|
||||
| 'Deselect face'
|
||||
| 'Deselect point'
|
||||
'Remove from other selection': 'Deselect axis'
|
||||
'Set selection': 'Set selection'
|
||||
'Update code selection cursors':
|
||||
| 'Add point'
|
||||
| 'Complete line'
|
||||
| 'Deselect all'
|
||||
| 'Deselect axis'
|
||||
| 'Deselect edge'
|
||||
| 'Deselect face'
|
||||
| 'Deselect point'
|
||||
| 'Deselect segment'
|
||||
| 'Select edge'
|
||||
| 'Select face'
|
||||
| 'Select point'
|
||||
| 'Select segment'
|
||||
}
|
||||
eventsCausingDelays: {}
|
||||
eventsCausingGuards: {
|
||||
'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 empty': 'Equip extrude' | 'Equip fillet'
|
||||
'Selection is not empty': 'Deselect all'
|
||||
'Selection is one face': 'Enter sketch' | 'Equip extrude'
|
||||
'Selection is one or more edges': 'Equip fillet'
|
||||
}
|
||||
eventsCausingServices: {
|
||||
createExtrude: 'Equip extrude'
|
||||
createFillet: 'Equip fillet'
|
||||
createLine: 'Equip line tool'
|
||||
createSketch: 'Enter sketch' | 'Select face'
|
||||
}
|
||||
matchesStates:
|
||||
| 'Extrude'
|
||||
| 'Extrude.Idle'
|
||||
| 'Extrude.Ready'
|
||||
| 'Extrude.Selection Ready'
|
||||
| 'Fillet'
|
||||
| 'Fillet.Idle'
|
||||
| 'Fillet.Ready'
|
||||
| 'Fillet.Selection Ready'
|
||||
| 'Sketch'
|
||||
| 'Sketch no face'
|
||||
| 'Sketch.Idle'
|
||||
| 'Sketch.Line Tool'
|
||||
| 'Sketch.Line Tool.Done'
|
||||
| 'Sketch.Line Tool.No Points'
|
||||
| 'Sketch.Line Tool.Point Added'
|
||||
| 'Sketch.Line Tool.Segment Added'
|
||||
| 'idle'
|
||||
| {
|
||||
Extrude?: 'Idle' | 'Ready' | 'Selection Ready'
|
||||
Fillet?: 'Idle' | 'Ready' | 'Selection Ready'
|
||||
Sketch?:
|
||||
| 'Idle'
|
||||
| 'Line Tool'
|
||||
| {
|
||||
'Line Tool'?:
|
||||
| 'Done'
|
||||
| 'No Points'
|
||||
| 'Point Added'
|
||||
| 'Segment Added'
|
||||
}
|
||||
}
|
||||
tags: never
|
||||
}
|
329
src/useStore.ts
329
src/useStore.ts
@ -4,7 +4,6 @@ import { addLineHighlight, EditorView } from './editor/highlightextension'
|
||||
import { parser_wasm } from './lang/abstractSyntaxTree'
|
||||
import { Program } from './lang/abstractSyntaxTreeTypes'
|
||||
import { getNodeFromPath } from './lang/queryAst'
|
||||
import { enginelessExecutor } from './lib/testHelpers'
|
||||
import {
|
||||
ProgramMemory,
|
||||
Position,
|
||||
@ -14,20 +13,34 @@ import {
|
||||
} from './lang/executor'
|
||||
import { recast } from './lang/recast'
|
||||
import { EditorSelection } from '@codemirror/state'
|
||||
import { EngineCommandManager } from './lang/std/engineConnection'
|
||||
import {
|
||||
ArtifactMap,
|
||||
SourceRangeMap,
|
||||
EngineCommandManager,
|
||||
} from './lang/std/engineConnection'
|
||||
import { KCLError } from './lang/errors'
|
||||
import { deferExecution } from 'lib/utils'
|
||||
import { _executor } from './lang/executor'
|
||||
import { defferExecution } from 'lib/utils'
|
||||
|
||||
export type Axis = 'y-axis' | 'x-axis' | 'z-axis'
|
||||
|
||||
export type Selection = {
|
||||
type: 'default' | 'line-end' | 'line-mid'
|
||||
type:
|
||||
| 'default'
|
||||
| 'line-end'
|
||||
| 'line-mid'
|
||||
| 'face'
|
||||
| 'point'
|
||||
| 'edge'
|
||||
| 'line'
|
||||
| 'arc'
|
||||
| 'all'
|
||||
range: SourceRange
|
||||
}
|
||||
export type Selections = {
|
||||
otherSelections: ('y-axis' | 'x-axis' | 'z-axis')[]
|
||||
otherSelections: Axis[]
|
||||
codeBasedSelections: Selection[]
|
||||
}
|
||||
export type ToolTip =
|
||||
export type TooTip =
|
||||
| 'lineTo'
|
||||
| 'line'
|
||||
| 'angledLine'
|
||||
@ -57,7 +70,7 @@ export const toolTips = [
|
||||
'xLineTo',
|
||||
'yLineTo',
|
||||
'angledLineThatIntersects',
|
||||
] as any as ToolTip[]
|
||||
] as any as TooTip[]
|
||||
|
||||
export type GuiModes =
|
||||
| {
|
||||
@ -65,7 +78,7 @@ export type GuiModes =
|
||||
}
|
||||
| {
|
||||
mode: 'sketch'
|
||||
sketchMode: ToolTip
|
||||
sketchMode: TooTip
|
||||
isTooltip: true
|
||||
waitingFirstClick: boolean
|
||||
rotation: Rotation
|
||||
@ -121,37 +134,40 @@ export interface StoreState {
|
||||
setGuiMode: (guiMode: GuiModes) => void
|
||||
logs: string[]
|
||||
addLog: (log: string) => void
|
||||
setLogs: (logs: string[]) => void
|
||||
resetLogs: () => 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
|
||||
updateAstAsync: (ast: Program, focusPath?: PathToNode) => void
|
||||
code: string
|
||||
defferedCode: string
|
||||
setCode: (code: string) => void
|
||||
deferredSetCode: (code: string) => void
|
||||
executeCode: (code?: string) => void
|
||||
defferedSetCode: (code: string) => void
|
||||
formatCode: () => void
|
||||
errorState: {
|
||||
isError: boolean
|
||||
error: string
|
||||
}
|
||||
setError: (error?: string) => void
|
||||
programMemory: ProgramMemory
|
||||
setProgramMemory: (programMemory: ProgramMemory) => void
|
||||
isShiftDown: boolean
|
||||
setIsShiftDown: (isShiftDown: boolean) => void
|
||||
artifactMap: ArtifactMap
|
||||
sourceRangeMap: SourceRangeMap
|
||||
setArtifactNSourceRangeMaps: (a: {
|
||||
artifactMap: ArtifactMap
|
||||
sourceRangeMap: SourceRangeMap
|
||||
}) => void
|
||||
engineCommandManager?: EngineCommandManager
|
||||
setEngineCommandManager: (engineCommandManager: EngineCommandManager) => void
|
||||
mediaStream?: MediaStream
|
||||
@ -192,13 +208,10 @@ let pendingAstUpdates: number[] = []
|
||||
export const useStore = create<StoreState>()(
|
||||
persist(
|
||||
(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)
|
||||
const setDefferedCode = defferExecution(
|
||||
(code: string) => set({ defferedCode: code }),
|
||||
600
|
||||
)
|
||||
return {
|
||||
editorView: null,
|
||||
setEditorView: (editorView) => {
|
||||
@ -212,22 +225,6 @@ export const useStore = create<StoreState>()(
|
||||
editorView.dispatch({ effects: addLineHighlight.of(selection) })
|
||||
}
|
||||
},
|
||||
executeCode: async (code) => {
|
||||
const result = await executeCode({
|
||||
code: code || get().code,
|
||||
lastAst: get().ast,
|
||||
engineCommandManager: get().engineCommandManager,
|
||||
})
|
||||
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
|
||||
@ -257,10 +254,7 @@ export const useStore = create<StoreState>()(
|
||||
get().setCursor({
|
||||
otherSelections: currestSelections.otherSelections,
|
||||
codeBasedSelections: [
|
||||
{
|
||||
range: [0, code.length ? code.length - 1 : 0],
|
||||
type: 'default',
|
||||
},
|
||||
{ range: [0, code.length - 1], type: 'default' },
|
||||
],
|
||||
})
|
||||
return
|
||||
@ -294,8 +288,8 @@ export const useStore = create<StoreState>()(
|
||||
set((state) => ({ logs: [...state.logs, log] }))
|
||||
}
|
||||
},
|
||||
setLogs: (logs) => {
|
||||
set({ logs })
|
||||
resetLogs: () => {
|
||||
set({ logs: [] })
|
||||
},
|
||||
kclErrors: [],
|
||||
addKCLError: (e) => {
|
||||
@ -304,9 +298,6 @@ export const useStore = create<StoreState>()(
|
||||
resetKCLErrors: () => {
|
||||
set({ kclErrors: [] })
|
||||
},
|
||||
setErrors: (errors) => {
|
||||
set({ kclErrors: errors })
|
||||
},
|
||||
ast: {
|
||||
start: 0,
|
||||
end: 0,
|
||||
@ -319,47 +310,7 @@ export const useStore = create<StoreState>()(
|
||||
setAst: (ast) => {
|
||||
set({ ast })
|
||||
},
|
||||
executeAst: async (ast) => {
|
||||
const _ast = ast || get().ast
|
||||
if (!get().isStreamReady) return
|
||||
const engineCommandManager = get().engineCommandManager!
|
||||
if (!engineCommandManager) return
|
||||
|
||||
set({ isExecuting: true })
|
||||
const { logs, errors, programMemory } = await executeAst({
|
||||
ast: _ast,
|
||||
engineCommandManager,
|
||||
})
|
||||
set({
|
||||
programMemory,
|
||||
logs,
|
||||
kclErrors: errors,
|
||||
isExecuting: false,
|
||||
})
|
||||
},
|
||||
executeAstMock: async (ast) => {
|
||||
const _ast = ast || get().ast
|
||||
if (!get().isStreamReady) return
|
||||
const engineCommandManager = get().engineCommandManager!
|
||||
if (!engineCommandManager) return
|
||||
|
||||
const { logs, errors, programMemory } = await executeAst({
|
||||
ast: _ast,
|
||||
engineCommandManager,
|
||||
useFakeExecutor: true,
|
||||
})
|
||||
set({
|
||||
programMemory,
|
||||
logs,
|
||||
kclErrors: errors,
|
||||
isExecuting: false,
|
||||
})
|
||||
},
|
||||
updateAst: async (
|
||||
ast,
|
||||
reexecute,
|
||||
{ focusPath, callBack = () => {} } = {}
|
||||
) => {
|
||||
updateAst: async (ast, { focusPath, callBack = () => {} } = {}) => {
|
||||
const newCode = recast(ast)
|
||||
const astWithUpdatedSource = parser_wasm(newCode)
|
||||
callBack(astWithUpdatedSource)
|
||||
@ -367,6 +318,7 @@ export const useStore = create<StoreState>()(
|
||||
set({
|
||||
ast: astWithUpdatedSource,
|
||||
code: newCode,
|
||||
defferedCode: newCode,
|
||||
})
|
||||
if (focusPath) {
|
||||
const { node } = getNodeFromPath<any>(
|
||||
@ -387,33 +339,24 @@ export const useStore = create<StoreState>()(
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
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) => {
|
||||
updateAstAsync: async (ast, focusPath) => {
|
||||
// clear any pending updates
|
||||
pendingAstUpdates.forEach((id) => clearTimeout(id))
|
||||
pendingAstUpdates = []
|
||||
// setup a new update
|
||||
pendingAstUpdates.push(
|
||||
setTimeout(() => {
|
||||
get().updateAst(ast, reexecute, { focusPath })
|
||||
get().updateAst(ast, { focusPath })
|
||||
}, 100) as unknown as number
|
||||
)
|
||||
},
|
||||
code: '',
|
||||
setCode: (code) => set({ code }),
|
||||
deferredSetCode: (code) => {
|
||||
defferedCode: '',
|
||||
setCode: (code) => set({ code, defferedCode: code }),
|
||||
defferedSetCode: (code) => {
|
||||
set({ code })
|
||||
setDeferredCode(code)
|
||||
setDefferedCode(code)
|
||||
},
|
||||
formatCode: async () => {
|
||||
const code = get().code
|
||||
@ -421,10 +364,20 @@ export const useStore = create<StoreState>()(
|
||||
const newCode = recast(ast)
|
||||
set({ code: newCode, ast })
|
||||
},
|
||||
errorState: {
|
||||
isError: false,
|
||||
error: '',
|
||||
},
|
||||
setError: (error = '') => {
|
||||
set({ errorState: { isError: !!error, error } })
|
||||
},
|
||||
programMemory: { root: {}, return: null },
|
||||
setProgramMemory: (programMemory) => set({ programMemory }),
|
||||
isShiftDown: false,
|
||||
setIsShiftDown: (isShiftDown) => set({ isShiftDown }),
|
||||
artifactMap: {},
|
||||
sourceRangeMap: {},
|
||||
setArtifactNSourceRangeMaps: (maps) => set({ ...maps }),
|
||||
setEngineCommandManager: (engineCommandManager) =>
|
||||
set({ engineCommandManager }),
|
||||
setMediaStream: (mediaStream) => set({ mediaStream }),
|
||||
@ -467,165 +420,9 @@ export const useStore = create<StoreState>()(
|
||||
partialize: (state) =>
|
||||
Object.fromEntries(
|
||||
Object.entries(state).filter(([key]) =>
|
||||
['code', 'openPanes'].includes(key)
|
||||
['code', 'defferedCode', 'openPanes'].includes(key)
|
||||
)
|
||||
),
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
const defaultProgramMemory: ProgramMemory['root'] = {
|
||||
_0: {
|
||||
type: 'UserVal',
|
||||
value: 0,
|
||||
__meta: [],
|
||||
},
|
||||
_90: {
|
||||
type: 'UserVal',
|
||||
value: 90,
|
||||
__meta: [],
|
||||
},
|
||||
_180: {
|
||||
type: 'UserVal',
|
||||
value: 180,
|
||||
__meta: [],
|
||||
},
|
||||
_270: {
|
||||
type: 'UserVal',
|
||||
value: 270,
|
||||
__meta: [],
|
||||
},
|
||||
PI: {
|
||||
type: 'UserVal',
|
||||
value: Math.PI,
|
||||
__meta: [],
|
||||
},
|
||||
}
|
||||
|
||||
async function executeCode({
|
||||
engineCommandManager,
|
||||
code,
|
||||
lastAst,
|
||||
}: {
|
||||
code: string
|
||||
lastAst: Program
|
||||
engineCommandManager?: EngineCommandManager
|
||||
}): Promise<
|
||||
| {
|
||||
logs: string[]
|
||||
errors: KCLError[]
|
||||
programMemory: ProgramMemory
|
||||
ast: Program
|
||||
isChange: true
|
||||
}
|
||||
| { isChange: false }
|
||||
> {
|
||||
let ast: Program
|
||||
try {
|
||||
ast = parser_wasm(code)
|
||||
} catch (e) {
|
||||
let errors: KCLError[] = []
|
||||
let logs: string[] = [JSON.stringify(e)]
|
||||
if (e instanceof KCLError) {
|
||||
errors = [e]
|
||||
logs = []
|
||||
if (e.msg === 'file is empty') engineCommandManager?.endSession()
|
||||
}
|
||||
return {
|
||||
isChange: true,
|
||||
logs,
|
||||
errors,
|
||||
programMemory: {
|
||||
root: {},
|
||||
return: null,
|
||||
},
|
||||
ast: {
|
||||
start: 0,
|
||||
end: 0,
|
||||
body: [],
|
||||
nonCodeMeta: {
|
||||
noneCodeNodes: {},
|
||||
start: null,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
// Check if the ast we have is equal to the ast in the storage.
|
||||
// If it is, we don't need to update the ast.
|
||||
if (!engineCommandManager || JSON.stringify(ast) === JSON.stringify(lastAst))
|
||||
return { isChange: false }
|
||||
|
||||
const { logs, errors, programMemory } = await executeAst({
|
||||
ast,
|
||||
engineCommandManager,
|
||||
})
|
||||
return {
|
||||
ast,
|
||||
logs,
|
||||
errors,
|
||||
programMemory,
|
||||
isChange: true,
|
||||
}
|
||||
}
|
||||
|
||||
async function executeAst({
|
||||
ast,
|
||||
engineCommandManager,
|
||||
useFakeExecutor = false,
|
||||
}: {
|
||||
ast: Program
|
||||
engineCommandManager: EngineCommandManager
|
||||
useFakeExecutor?: boolean
|
||||
}): Promise<{
|
||||
logs: string[]
|
||||
errors: KCLError[]
|
||||
programMemory: ProgramMemory
|
||||
}> {
|
||||
try {
|
||||
if (!useFakeExecutor) {
|
||||
engineCommandManager.endSession()
|
||||
engineCommandManager.startNewSession()
|
||||
}
|
||||
const programMemory = await (useFakeExecutor
|
||||
? enginelessExecutor(ast, {
|
||||
root: defaultProgramMemory,
|
||||
return: null,
|
||||
})
|
||||
: _executor(
|
||||
ast,
|
||||
{
|
||||
root: defaultProgramMemory,
|
||||
return: null,
|
||||
},
|
||||
engineCommandManager
|
||||
))
|
||||
|
||||
await engineCommandManager.waitForAllCommands(ast, programMemory)
|
||||
return {
|
||||
logs: [],
|
||||
errors: [],
|
||||
programMemory,
|
||||
}
|
||||
} catch (e: any) {
|
||||
if (e instanceof KCLError) {
|
||||
return {
|
||||
errors: [e],
|
||||
logs: [],
|
||||
programMemory: {
|
||||
root: {},
|
||||
return: null,
|
||||
},
|
||||
}
|
||||
} else {
|
||||
console.log(e)
|
||||
return {
|
||||
logs: [e],
|
||||
errors: [],
|
||||
programMemory: {
|
||||
root: {},
|
||||
return: null,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
180
src/wasm-lib/Cargo.lock
generated
180
src/wasm-lib/Cargo.lock
generated
@ -306,7 +306,7 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_bytes",
|
||||
"serde_json",
|
||||
"time",
|
||||
"time 0.3.27",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
@ -363,15 +363,18 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.30"
|
||||
version = "0.4.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "defd4e7873dbddba6c7c91e199c7fcb946abc4a6a4ac3195400bcfb01b5de877"
|
||||
checksum = "ec837a71355b28f6556dbd569b37b3f363091c0bd4b2e735674521b4c5fd9bc5"
|
||||
dependencies = [
|
||||
"android-tzdata",
|
||||
"iana-time-zone",
|
||||
"js-sys",
|
||||
"num-traits",
|
||||
"serde",
|
||||
"windows-targets 0.48.5",
|
||||
"time 0.1.45",
|
||||
"wasm-bindgen",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -385,6 +388,24 @@ dependencies = [
|
||||
"libloading",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "3.2.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123"
|
||||
dependencies = [
|
||||
"atty",
|
||||
"bitflags 1.3.2",
|
||||
"clap_derive 3.2.25",
|
||||
"clap_lex 0.2.4",
|
||||
"indexmap 1.9.3",
|
||||
"once_cell",
|
||||
"strsim",
|
||||
"termcolor",
|
||||
"textwrap",
|
||||
"unicase",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.4.3"
|
||||
@ -392,7 +413,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "84ed82781cea27b43c9b106a979fe450a13a31aab0500595fb3fc06616de08e6"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
"clap_derive 4.4.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -403,13 +424,25 @@ checksum = "2bb9faaa7c2ef94b2743a21f5a29e6f0010dff4caa69ac8e9d6cf8b6fa74da08"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
"clap_lex",
|
||||
"clap_lex 0.5.1",
|
||||
"strsim",
|
||||
"terminal_size",
|
||||
"unicase",
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "3.2.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ae6371b8bdc8b7d3959e9cf7b22d4435ef3e79e138688421ec654acf8c81b008"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro-error",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "4.4.2"
|
||||
@ -422,6 +455,15 @@ dependencies = [
|
||||
"syn 2.0.33",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_lex"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5"
|
||||
dependencies = [
|
||||
"os_str_bytes",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_lex"
|
||||
version = "0.5.1"
|
||||
@ -943,7 +985,7 @@ dependencies = [
|
||||
"cfg-if",
|
||||
"js-sys",
|
||||
"libc",
|
||||
"wasi",
|
||||
"wasi 0.11.0+wasi-snapshot-preview1",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
@ -1202,6 +1244,7 @@ checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"hashbrown 0.12.3",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1212,7 +1255,6 @@ checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown 0.14.0",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1227,17 +1269,6 @@ dependencies = [
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "io-lifetimes"
|
||||
version = "1.0.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2"
|
||||
dependencies = [
|
||||
"hermit-abi 0.3.2",
|
||||
"libc",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ipnet"
|
||||
version = "2.8.0"
|
||||
@ -1251,7 +1282,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b"
|
||||
dependencies = [
|
||||
"hermit-abi 0.3.2",
|
||||
"rustix 0.38.9",
|
||||
"rustix",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
@ -1314,7 +1345,7 @@ version = "0.1.26"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bson",
|
||||
"clap",
|
||||
"clap 4.4.3",
|
||||
"dashmap",
|
||||
"derive-docs 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"expectorate",
|
||||
@ -1362,7 +1393,7 @@ dependencies = [
|
||||
"rand",
|
||||
"reqwest",
|
||||
"reqwest-conditional-middleware",
|
||||
"reqwest-middleware",
|
||||
"reqwest-middleware 0.2.3",
|
||||
"reqwest-retry",
|
||||
"reqwest-tracing",
|
||||
"schemars",
|
||||
@ -1416,12 +1447,6 @@ version = "0.5.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.3.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519"
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.4.5"
|
||||
@ -1529,7 +1554,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"wasi",
|
||||
"wasi 0.11.0+wasi-snapshot-preview1",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
@ -1650,22 +1675,22 @@ checksum = "44d11de466f4a3006fe8a5e7ec84e93b79c70cb992ae0aa0eb631ad2df8abfe2"
|
||||
|
||||
[[package]]
|
||||
name = "openapitor"
|
||||
version = "0.0.9"
|
||||
source = "git+https://github.com/KittyCAD/kittycad.rs?branch=main#3d74c1dfb41146a268a644e9fde2c19a8cd66895"
|
||||
version = "0.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "120168eae5b6485690af708bd1030547df62cca10a643763d416ab0e6831decb"
|
||||
dependencies = [
|
||||
"Inflector",
|
||||
"anyhow",
|
||||
"chrono",
|
||||
"clap",
|
||||
"clap 3.2.25",
|
||||
"data-encoding",
|
||||
"format_serde_error",
|
||||
"futures-util",
|
||||
"http",
|
||||
"indexmap 2.0.0",
|
||||
"indexmap 1.9.3",
|
||||
"json-patch",
|
||||
"log",
|
||||
"numeral",
|
||||
"once_cell",
|
||||
"openapiv3",
|
||||
"phonenumber",
|
||||
"proc-macro2",
|
||||
@ -1673,7 +1698,7 @@ dependencies = [
|
||||
"rand",
|
||||
"regex",
|
||||
"reqwest",
|
||||
"reqwest-middleware",
|
||||
"reqwest-middleware 0.1.6",
|
||||
"rustfmt-wrapper",
|
||||
"schemars",
|
||||
"serde",
|
||||
@ -1686,17 +1711,18 @@ dependencies = [
|
||||
"slog-stdlog",
|
||||
"slog-term",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"url",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "openapiv3"
|
||||
version = "1.0.3"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "75e56d5c441965b6425165b7e3223cc933ca469834f4a8b4786817a1f9dc4f13"
|
||||
checksum = "7b1a9f106eb0a780abd17ba9fca8e0843e3461630bcbe2af0ad4d5d3ba4e9aa4"
|
||||
dependencies = [
|
||||
"indexmap 2.0.0",
|
||||
"indexmap 1.9.3",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
@ -1726,6 +1752,12 @@ dependencies = [
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "os_str_bytes"
|
||||
version = "6.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4d5d9eb14b174ee9aa2ef96dc2b94637a2d4b6e7cb873c7e171f0c20c6cf3eac"
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
version = "0.11.2"
|
||||
@ -2151,10 +2183,26 @@ checksum = "59e50a2e70970896c99d1b8f20ddc30a70b30d3ac6e619a03a8353b64a49b277"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"reqwest",
|
||||
"reqwest-middleware",
|
||||
"reqwest-middleware 0.2.3",
|
||||
"task-local-extensions",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "reqwest-middleware"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "69539cea4148dce683bec9dc95be3f0397a9bb2c248a49c8296a9d21659a8cdd"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
"futures",
|
||||
"http",
|
||||
"reqwest",
|
||||
"serde",
|
||||
"task-local-extensions",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "reqwest-middleware"
|
||||
version = "0.2.3"
|
||||
@ -2185,7 +2233,7 @@ dependencies = [
|
||||
"hyper",
|
||||
"parking_lot 0.11.2",
|
||||
"reqwest",
|
||||
"reqwest-middleware",
|
||||
"reqwest-middleware 0.2.3",
|
||||
"retry-policies",
|
||||
"task-local-extensions",
|
||||
"tokio",
|
||||
@ -2205,7 +2253,7 @@ dependencies = [
|
||||
"matchit",
|
||||
"opentelemetry",
|
||||
"reqwest",
|
||||
"reqwest-middleware",
|
||||
"reqwest-middleware 0.2.3",
|
||||
"task-local-extensions",
|
||||
"tracing",
|
||||
"tracing-opentelemetry",
|
||||
@ -2262,20 +2310,6 @@ dependencies = [
|
||||
"toolchain_find",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "0.37.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4d69718bf81c6127a49dc64e44a742e8bb9213c0ff8869a22c308f84c1d4ab06"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"errno",
|
||||
"io-lifetimes",
|
||||
"libc",
|
||||
"linux-raw-sys 0.3.8",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "0.38.9"
|
||||
@ -2285,7 +2319,7 @@ dependencies = [
|
||||
"bitflags 2.4.0",
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys 0.4.5",
|
||||
"linux-raw-sys",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
@ -2643,7 +2677,7 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"slog",
|
||||
"time",
|
||||
"time 0.3.27",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2678,7 +2712,7 @@ dependencies = [
|
||||
"slog",
|
||||
"term",
|
||||
"thread_local",
|
||||
"time",
|
||||
"time 0.3.27",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2803,7 +2837,7 @@ dependencies = [
|
||||
"cfg-if",
|
||||
"fastrand",
|
||||
"redox_syscall 0.3.5",
|
||||
"rustix 0.38.9",
|
||||
"rustix",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
@ -2828,13 +2862,12 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "terminal_size"
|
||||
version = "0.2.6"
|
||||
name = "textwrap"
|
||||
version = "0.16.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e6bf6f19e9f8ed8d4048dc22981458ebcf406d67e94cd422e5ecd73d63b3237"
|
||||
checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d"
|
||||
dependencies = [
|
||||
"rustix 0.37.23",
|
||||
"windows-sys 0.48.0",
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2878,6 +2911,17 @@ dependencies = [
|
||||
"weezl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.1.45"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"wasi 0.10.0+wasi-snapshot-preview1",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.3.27"
|
||||
@ -3356,6 +3400,12 @@ dependencies = [
|
||||
"try-lock",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.10.0+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.0+wasi-snapshot-preview1"
|
||||
|
@ -20,5 +20,5 @@ syn = { version = "2.0.33", features = ["full"] }
|
||||
|
||||
[dev-dependencies]
|
||||
expectorate = "1.0.7"
|
||||
openapitor = { git = "https://github.com/KittyCAD/kittycad.rs", branch = "main" }
|
||||
openapitor = "0.0.5"
|
||||
pretty_assertions = "1.4.0"
|
||||
|
@ -348,7 +348,7 @@ impl SourceRange {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, PartialEq, Clone, Copy, ts_rs::TS, JsonSchema)]
|
||||
#[derive(Debug, Deserialize, Serialize, PartialEq, Clone, ts_rs::TS, JsonSchema)]
|
||||
#[ts(export)]
|
||||
pub struct Point2d {
|
||||
pub x: f64,
|
||||
@ -379,16 +379,6 @@ impl From<Point2d> for kittycad::types::Point2D {
|
||||
}
|
||||
}
|
||||
|
||||
impl Point2d {
|
||||
pub const ZERO: Self = Self { x: 0.0, y: 0.0 };
|
||||
pub fn scale(self, scalar: f64) -> Self {
|
||||
Self {
|
||||
x: self.x * scalar,
|
||||
y: self.y * scalar,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, PartialEq, Clone, ts_rs::TS, JsonSchema)]
|
||||
#[ts(export)]
|
||||
pub struct Point3d {
|
||||
|
@ -7,7 +7,7 @@ use schemars::JsonSchema;
|
||||
use crate::{
|
||||
errors::{KclError, KclErrorDetails},
|
||||
executor::{MemoryItem, SketchGroup},
|
||||
std::{utils::Angle, Args},
|
||||
std::{utils::get_angle, Args},
|
||||
};
|
||||
|
||||
/// Returns the segment end of x.
|
||||
@ -174,9 +174,9 @@ fn inner_segment_angle(segment_name: &str, sketch_group: SketchGroup, args: &mut
|
||||
})?;
|
||||
let line = path.get_base();
|
||||
|
||||
let result = Angle::between(line.from.into(), line.to.into());
|
||||
let result = get_angle(&line.from, &line.to);
|
||||
|
||||
Ok(result.degrees())
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
/// Returns the angle to match the given length for x.
|
||||
|
@ -15,8 +15,6 @@ use crate::{
|
||||
},
|
||||
};
|
||||
|
||||
use super::utils::Angle;
|
||||
|
||||
/// Data to draw a line to a point.
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||
#[ts(export)]
|
||||
@ -394,13 +392,13 @@ fn inner_angled_line_of_x_length(
|
||||
AngledLineData::AngleAndLength(angle_and_length) => (angle_and_length[0], angle_and_length[1]),
|
||||
};
|
||||
|
||||
let to = get_y_component(Angle::from_degrees(angle), length);
|
||||
let to = get_y_component(angle, length);
|
||||
|
||||
let new_sketch_group = inner_line(
|
||||
if let AngledLineData::AngleWithTag { tag, .. } = data {
|
||||
LineData::PointWithTag { to: to.into(), tag }
|
||||
LineData::PointWithTag { to, tag }
|
||||
} else {
|
||||
LineData::Point(to.into())
|
||||
LineData::Point(to)
|
||||
},
|
||||
sketch_group,
|
||||
args,
|
||||
@ -489,13 +487,13 @@ fn inner_angled_line_of_y_length(
|
||||
AngledLineData::AngleAndLength(angle_and_length) => (angle_and_length[0], angle_and_length[1]),
|
||||
};
|
||||
|
||||
let to = get_x_component(Angle::from_degrees(angle), length);
|
||||
let to = get_x_component(angle, length);
|
||||
|
||||
let new_sketch_group = inner_line(
|
||||
if let AngledLineData::AngleWithTag { tag, .. } = data {
|
||||
LineData::PointWithTag { to: to.into(), tag }
|
||||
LineData::PointWithTag { to, tag }
|
||||
} else {
|
||||
LineData::Point(to.into())
|
||||
LineData::Point(to)
|
||||
},
|
||||
sketch_group,
|
||||
args,
|
||||
@ -590,16 +588,16 @@ fn inner_angled_line_that_intersects(
|
||||
|
||||
let from = sketch_group.get_coords_from_paths()?;
|
||||
let to = intersection_with_parallel_line(
|
||||
&[intersect_path.from.into(), intersect_path.to.into()],
|
||||
&[intersect_path.from, intersect_path.to],
|
||||
data.offset.unwrap_or_default(),
|
||||
data.angle,
|
||||
from,
|
||||
from.into(),
|
||||
);
|
||||
|
||||
let line_to_data = if let Some(tag) = data.tag {
|
||||
LineToData::PointWithTag { to: to.into(), tag }
|
||||
LineToData::PointWithTag { to, tag }
|
||||
} else {
|
||||
LineToData::Point(to.into())
|
||||
LineToData::Point(to)
|
||||
};
|
||||
|
||||
let new_sketch_group = inner_line_to(line_to_data, sketch_group, args)?;
|
||||
@ -768,7 +766,7 @@ pub fn arc(args: &mut Args) -> Result<MemoryItem, KclError> {
|
||||
name = "arc",
|
||||
}]
|
||||
fn inner_arc(data: ArcData, sketch_group: SketchGroup, args: &mut Args) -> Result<SketchGroup, KclError> {
|
||||
let from: Point2d = sketch_group.get_coords_from_paths()?;
|
||||
let from = sketch_group.get_coords_from_paths()?;
|
||||
|
||||
let (center, angle_start, angle_end, radius, end) = match &data {
|
||||
ArcData::AnglesAndRadiusWithTag {
|
||||
@ -777,27 +775,23 @@ fn inner_arc(data: ArcData, sketch_group: SketchGroup, args: &mut Args) -> Resul
|
||||
radius,
|
||||
..
|
||||
} => {
|
||||
let a_start = Angle::from_degrees(*angle_start);
|
||||
let a_end = Angle::from_degrees(*angle_end);
|
||||
let (center, end) = arc_center_and_end(from, a_start, a_end, *radius);
|
||||
(center, a_start, a_end, *radius, end)
|
||||
let (center, end) = arc_center_and_end(&from, *angle_start, *angle_end, *radius);
|
||||
(center, *angle_start, *angle_end, *radius, end)
|
||||
}
|
||||
ArcData::AnglesAndRadius {
|
||||
angle_start,
|
||||
angle_end,
|
||||
radius,
|
||||
} => {
|
||||
let a_start = Angle::from_degrees(*angle_start);
|
||||
let a_end = Angle::from_degrees(*angle_end);
|
||||
let (center, end) = arc_center_and_end(from, a_start, a_end, *radius);
|
||||
(center, a_start, a_end, *radius, end)
|
||||
let (center, end) = arc_center_and_end(&from, *angle_start, *angle_end, *radius);
|
||||
(center, *angle_start, *angle_end, *radius, end)
|
||||
}
|
||||
ArcData::CenterToRadiusWithTag { center, to, radius, .. } => {
|
||||
let (angle_start, angle_end) = arc_angles(from, center.into(), to.into(), *radius, args.source_range)?;
|
||||
let (angle_start, angle_end) = arc_angles(&from, ¢er.into(), &to.into(), *radius, args.source_range)?;
|
||||
(center.into(), angle_start, angle_end, *radius, to.into())
|
||||
}
|
||||
ArcData::CenterToRadius { center, to, radius } => {
|
||||
let (angle_start, angle_end) = arc_angles(from, center.into(), to.into(), *radius, args.source_range)?;
|
||||
let (angle_start, angle_end) = arc_angles(&from, ¢er.into(), &to.into(), *radius, args.source_range)?;
|
||||
(center.into(), angle_start, angle_end, *radius, to.into())
|
||||
}
|
||||
};
|
||||
@ -809,8 +803,8 @@ fn inner_arc(data: ArcData, sketch_group: SketchGroup, args: &mut Args) -> Resul
|
||||
ModelingCmd::ExtendPath {
|
||||
path: sketch_group.id,
|
||||
segment: kittycad::types::PathSegment::Arc {
|
||||
angle_start: angle_start.degrees(),
|
||||
angle_end: angle_end.degrees(),
|
||||
angle_start,
|
||||
angle_end,
|
||||
center: center.into(),
|
||||
radius,
|
||||
},
|
||||
|
@ -1,85 +1,30 @@
|
||||
use std::f64::consts::PI;
|
||||
|
||||
use crate::{
|
||||
errors::{KclError, KclErrorDetails},
|
||||
executor::{Point2d, SourceRange},
|
||||
};
|
||||
|
||||
#[derive(Clone, Copy, Default, PartialOrd, PartialEq, Debug)]
|
||||
pub struct Angle {
|
||||
degrees: f64,
|
||||
pub fn get_angle(a: &[f64; 2], b: &[f64; 2]) -> f64 {
|
||||
let x = b[0] - a[0];
|
||||
let y = b[1] - a[1];
|
||||
normalise_angle(y.atan2(x).to_degrees())
|
||||
}
|
||||
|
||||
impl Angle {
|
||||
const ZERO: Self = Self { degrees: 0.0 };
|
||||
/// Make an angle of the given degrees.
|
||||
pub fn from_degrees(degrees: f64) -> Self {
|
||||
Self { degrees }
|
||||
}
|
||||
/// Make an angle of the given radians.
|
||||
pub fn from_radians(radians: f64) -> Self {
|
||||
Self::from_degrees(radians.to_degrees())
|
||||
}
|
||||
/// Get the angle in degrees
|
||||
pub fn degrees(&self) -> f64 {
|
||||
self.degrees
|
||||
}
|
||||
/// Get the angle in radians
|
||||
pub fn radians(&self) -> f64 {
|
||||
self.degrees.to_radians()
|
||||
}
|
||||
/// Get the angle between these points
|
||||
pub fn between(a: Point2d, b: Point2d) -> Self {
|
||||
let x = b.x - a.x;
|
||||
let y = b.y - a.y;
|
||||
Self::from_radians(y.atan2(x)).normalize()
|
||||
}
|
||||
/// Normalize the angle
|
||||
pub fn normalize(self) -> Self {
|
||||
let angle = self.degrees();
|
||||
let result = ((angle % 360.0) + 360.0) % 360.0;
|
||||
Self::from_degrees(if result > 180.0 { result - 360.0 } else { result })
|
||||
}
|
||||
/// Gives the ▲-angle between from and to angles (shortest path), use radians.
|
||||
///
|
||||
/// Sign of the returned angle denotes direction, positive means counterClockwise 🔄
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use std::f64::consts::PI;
|
||||
/// use kcl_lib::std::utils::Angle;
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// Angle::delta(Angle::from_radians(PI / 8.0), Angle::from_radians(PI / 4.0)),
|
||||
/// Angle::from_radians(PI / 8.0)
|
||||
/// );
|
||||
/// ```
|
||||
#[allow(dead_code)]
|
||||
pub fn delta(from_angle: Self, to_angle: Self) -> Self {
|
||||
let norm_from_angle = normalize_rad(from_angle.radians());
|
||||
let norm_to_angle = normalize_rad(to_angle.radians());
|
||||
let provisional = norm_to_angle - norm_from_angle;
|
||||
|
||||
if provisional > -PI && provisional <= PI {
|
||||
return Angle::from_radians(provisional);
|
||||
}
|
||||
if provisional > PI {
|
||||
return Angle::from_radians(provisional - 2.0 * PI);
|
||||
}
|
||||
if provisional < -PI {
|
||||
return Angle::from_radians(provisional + 2.0 * PI);
|
||||
}
|
||||
Angle::ZERO
|
||||
pub fn normalise_angle(angle: f64) -> f64 {
|
||||
let result = ((angle % 360.0) + 360.0) % 360.0;
|
||||
if result > 180.0 {
|
||||
result - 360.0
|
||||
} else {
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn clockwise_sign(points: &[Point2d]) -> i32 {
|
||||
pub fn clockwise_sign(points: &[[f64; 2]]) -> i32 {
|
||||
let mut sum = 0.0;
|
||||
for i in 0..points.len() {
|
||||
let current_point = points[i];
|
||||
let next_point = points[(i + 1) % points.len()];
|
||||
sum += (next_point.x - current_point.x) * (next_point.y + current_point.y);
|
||||
sum += (next_point[0] - current_point[0]) * (next_point[1] + current_point[1]);
|
||||
}
|
||||
if sum >= 0.0 {
|
||||
1
|
||||
@ -90,145 +35,139 @@ pub fn clockwise_sign(points: &[Point2d]) -> i32 {
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn normalize_rad(angle: f64) -> f64 {
|
||||
let draft = angle % (2.0 * PI);
|
||||
let draft = angle % (2.0 * std::f64::consts::PI);
|
||||
if draft < 0.0 {
|
||||
draft + 2.0 * PI
|
||||
draft + 2.0 * std::f64::consts::PI
|
||||
} else {
|
||||
draft
|
||||
}
|
||||
}
|
||||
|
||||
/// Gives the ▲-angle between from and to angles (shortest path), use radians.
|
||||
///
|
||||
/// Sign of the returned angle denotes direction, positive means counterClockwise 🔄
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// assert_eq!(
|
||||
/// kcl_lib::std::utils::delta_angle(std::f64::consts::PI / 8.0, std::f64::consts::PI / 4.0),
|
||||
/// std::f64::consts::PI / 8.0
|
||||
/// );
|
||||
/// ```
|
||||
#[allow(dead_code)]
|
||||
pub fn delta_angle(from_angle: f64, to_angle: f64) -> f64 {
|
||||
let norm_from_angle = normalize_rad(from_angle);
|
||||
let norm_to_angle = normalize_rad(to_angle);
|
||||
let provisional = norm_to_angle - norm_from_angle;
|
||||
|
||||
if provisional > -std::f64::consts::PI && provisional <= std::f64::consts::PI {
|
||||
return provisional;
|
||||
}
|
||||
if provisional > std::f64::consts::PI {
|
||||
return provisional - 2.0 * std::f64::consts::PI;
|
||||
}
|
||||
if provisional < -std::f64::consts::PI {
|
||||
return provisional + 2.0 * std::f64::consts::PI;
|
||||
}
|
||||
0.0
|
||||
}
|
||||
|
||||
/// Calculates the distance between two points.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use kcl_lib::executor::Point2d;
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// kcl_lib::std::utils::distance_between_points(Point2d::ZERO, Point2d{x: 0.0, y: 5.0}),
|
||||
/// kcl_lib::std::utils::distance_between_points(&[0.0, 0.0], &[0.0, 5.0]),
|
||||
/// 5.0
|
||||
/// );
|
||||
/// assert_eq!(
|
||||
/// kcl_lib::std::utils::distance_between_points(Point2d::ZERO, Point2d{x: 3.0, y: 4.0}),
|
||||
/// kcl_lib::std::utils::distance_between_points(&[0.0, 0.0], &[3.0, 4.0]),
|
||||
/// 5.0
|
||||
/// );
|
||||
/// ```
|
||||
#[allow(dead_code)]
|
||||
pub fn distance_between_points(point_a: Point2d, point_b: Point2d) -> f64 {
|
||||
let x1 = point_a.x;
|
||||
let y1 = point_a.y;
|
||||
let x2 = point_b.x;
|
||||
let y2 = point_b.y;
|
||||
pub fn distance_between_points(point_a: &[f64; 2], point_b: &[f64; 2]) -> f64 {
|
||||
let x1 = point_a[0];
|
||||
let y1 = point_a[1];
|
||||
let x2 = point_b[0];
|
||||
let y2 = point_b[1];
|
||||
|
||||
((y2 - y1).powi(2) + (x2 - x1).powi(2)).sqrt()
|
||||
}
|
||||
|
||||
pub fn calculate_intersection_of_two_lines(line1: &[Point2d; 2], line2_angle: f64, line2_point: Point2d) -> Point2d {
|
||||
let line2_point_b = Point2d {
|
||||
x: line2_point.x + f64::cos(line2_angle.to_radians()) * 10.0,
|
||||
y: line2_point.y + f64::sin(line2_angle.to_radians()) * 10.0,
|
||||
};
|
||||
pub fn calculate_intersection_of_two_lines(line1: &[[f64; 2]; 2], line2_angle: f64, line2_point: [f64; 2]) -> [f64; 2] {
|
||||
let line2_point_b = [
|
||||
line2_point[0] + f64::cos(line2_angle.to_radians()) * 10.0,
|
||||
line2_point[1] + f64::sin(line2_angle.to_radians()) * 10.0,
|
||||
];
|
||||
intersect(line1[0], line1[1], line2_point, line2_point_b)
|
||||
}
|
||||
|
||||
pub fn intersect(p1: Point2d, p2: Point2d, p3: Point2d, p4: Point2d) -> Point2d {
|
||||
let slope = |p1: Point2d, p2: Point2d| (p1.y - p2.y) / (p1.x - p2.x);
|
||||
let constant = |p1: Point2d, p2: Point2d| p1.y - slope(p1, p2) * p1.x;
|
||||
let get_y = |for_x: f64, p1: Point2d, p2: Point2d| slope(p1, p2) * for_x + constant(p1, p2);
|
||||
pub fn intersect(p1: [f64; 2], p2: [f64; 2], p3: [f64; 2], p4: [f64; 2]) -> [f64; 2] {
|
||||
let slope = |p1: [f64; 2], p2: [f64; 2]| (p1[1] - p2[1]) / (p1[0] - p2[0]);
|
||||
let constant = |p1: [f64; 2], p2: [f64; 2]| p1[1] - slope(p1, p2) * p1[0];
|
||||
let get_y = |for_x: f64, p1: [f64; 2], p2: [f64; 2]| slope(p1, p2) * for_x + constant(p1, p2);
|
||||
|
||||
if p1.x == p2.x {
|
||||
return Point2d {
|
||||
x: p1.x,
|
||||
y: get_y(p1.x, p3, p4),
|
||||
};
|
||||
if p1[0] == p2[0] {
|
||||
return [p1[0], get_y(p1[0], p3, p4)];
|
||||
}
|
||||
if p3.x == p4.x {
|
||||
return Point2d {
|
||||
x: p3.x,
|
||||
y: get_y(p3.x, p1, p2),
|
||||
};
|
||||
if p3[0] == p4[0] {
|
||||
return [p3[0], get_y(p3[0], p1, p2)];
|
||||
}
|
||||
|
||||
let x = (constant(p3, p4) - constant(p1, p2)) / (slope(p1, p2) - slope(p3, p4));
|
||||
let y = get_y(x, p1, p2);
|
||||
Point2d { x, y }
|
||||
[x, y]
|
||||
}
|
||||
|
||||
pub fn intersection_with_parallel_line(
|
||||
line1: &[Point2d; 2],
|
||||
line1: &[[f64; 2]; 2],
|
||||
line1_offset: f64,
|
||||
line2_angle: f64,
|
||||
line2_point: Point2d,
|
||||
) -> Point2d {
|
||||
line2_point: [f64; 2],
|
||||
) -> [f64; 2] {
|
||||
calculate_intersection_of_two_lines(&offset_line(line1_offset, line1[0], line1[1]), line2_angle, line2_point)
|
||||
}
|
||||
|
||||
fn offset_line(offset: f64, p1: Point2d, p2: Point2d) -> [Point2d; 2] {
|
||||
if p1.x == p2.x {
|
||||
let direction = (p1.y - p2.y).signum();
|
||||
return [
|
||||
Point2d {
|
||||
x: p1.x + offset * direction,
|
||||
y: p1.y,
|
||||
},
|
||||
Point2d {
|
||||
x: p2.x + offset * direction,
|
||||
y: p2.y,
|
||||
},
|
||||
];
|
||||
fn offset_line(offset: f64, p1: [f64; 2], p2: [f64; 2]) -> [[f64; 2]; 2] {
|
||||
if p1[0] == p2[0] {
|
||||
let direction = (p1[1] - p2[1]).signum();
|
||||
return [[p1[0] + offset * direction, p1[1]], [p2[0] + offset * direction, p2[1]]];
|
||||
}
|
||||
if p1.y == p2.y {
|
||||
let direction = (p2.x - p1.x).signum();
|
||||
return [
|
||||
Point2d {
|
||||
x: p1.x,
|
||||
y: p1.y + offset * direction,
|
||||
},
|
||||
Point2d {
|
||||
x: p2.x,
|
||||
y: p2.y + offset * direction,
|
||||
},
|
||||
];
|
||||
if p1[1] == p2[1] {
|
||||
let direction = (p2[0] - p1[0]).signum();
|
||||
return [[p1[0], p1[1] + offset * direction], [p2[0], p2[1] + offset * direction]];
|
||||
}
|
||||
let x_offset = offset / f64::sin(f64::atan2(p1.y - p2.y, p1.x - p2.x));
|
||||
[
|
||||
Point2d {
|
||||
x: p1.x + x_offset,
|
||||
y: p1.y,
|
||||
},
|
||||
Point2d {
|
||||
x: p2.x + x_offset,
|
||||
y: p2.y,
|
||||
},
|
||||
]
|
||||
let x_offset = offset / f64::sin(f64::atan2(p1[1] - p2[1], p1[0] - p2[0]));
|
||||
[[p1[0] + x_offset, p1[1]], [p2[0] + x_offset, p2[1]]]
|
||||
}
|
||||
|
||||
pub fn get_y_component(angle: Angle, x: f64) -> Point2d {
|
||||
let normalised_angle = ((angle.degrees() % 360.0) + 360.0) % 360.0; // between 0 and 360
|
||||
let y = x * f64::tan(normalised_angle.to_radians());
|
||||
pub fn get_y_component(angle_degree: f64, x_component: f64) -> [f64; 2] {
|
||||
let normalised_angle = ((angle_degree % 360.0) + 360.0) % 360.0; // between 0 and 360
|
||||
let y_component = x_component * f64::tan(normalised_angle.to_radians());
|
||||
let sign = if normalised_angle > 90.0 && normalised_angle <= 270.0 {
|
||||
-1.0
|
||||
} else {
|
||||
1.0
|
||||
};
|
||||
Point2d { x, y }.scale(sign)
|
||||
[sign * x_component, sign * y_component]
|
||||
}
|
||||
|
||||
pub fn get_x_component(angle: Angle, y: f64) -> Point2d {
|
||||
let normalised_angle = ((angle.degrees() % 360.0) + 360.0) % 360.0; // between 0 and 360
|
||||
let x = y / f64::tan(normalised_angle.to_radians());
|
||||
pub fn get_x_component(angle_degree: f64, y_component: f64) -> [f64; 2] {
|
||||
let normalised_angle = ((angle_degree % 360.0) + 360.0) % 360.0; // between 0 and 360
|
||||
let x_component = y_component / f64::tan(normalised_angle.to_radians());
|
||||
let sign = if normalised_angle > 180.0 && normalised_angle <= 360.0 {
|
||||
-1.0
|
||||
} else {
|
||||
1.0
|
||||
};
|
||||
Point2d { x, y }.scale(sign)
|
||||
[sign * x_component, sign * y_component]
|
||||
}
|
||||
|
||||
pub fn arc_center_and_end(from: Point2d, start_angle: Angle, end_angle: Angle, radius: f64) -> (Point2d, Point2d) {
|
||||
let start_angle = start_angle.radians();
|
||||
let end_angle = end_angle.radians();
|
||||
pub fn arc_center_and_end(from: &Point2d, start_angle_deg: f64, end_angle_deg: f64, radius: f64) -> (Point2d, Point2d) {
|
||||
let start_angle = start_angle_deg.to_radians();
|
||||
let end_angle = end_angle_deg.to_radians();
|
||||
|
||||
let center = Point2d {
|
||||
x: -1.0 * (radius * start_angle.cos() - from.x),
|
||||
@ -244,12 +183,12 @@ pub fn arc_center_and_end(from: Point2d, start_angle: Angle, end_angle: Angle, r
|
||||
}
|
||||
|
||||
pub fn arc_angles(
|
||||
from: Point2d,
|
||||
to: Point2d,
|
||||
center: Point2d,
|
||||
from: &Point2d,
|
||||
to: &Point2d,
|
||||
center: &Point2d,
|
||||
radius: f64,
|
||||
source_range: SourceRange,
|
||||
) -> Result<(Angle, Angle), KclError> {
|
||||
) -> Result<(f64, f64), KclError> {
|
||||
// First make sure that the points are on the circumference of the circle.
|
||||
// If not, we'll return an error.
|
||||
if !is_on_circumference(center, from, radius) {
|
||||
@ -275,10 +214,13 @@ pub fn arc_angles(
|
||||
let start_angle = (from.y - center.y).atan2(from.x - center.x);
|
||||
let end_angle = (to.y - center.y).atan2(to.x - center.x);
|
||||
|
||||
Ok((Angle::from_radians(start_angle), Angle::from_radians(end_angle)))
|
||||
let start_angle_deg = start_angle.to_degrees();
|
||||
let end_angle_deg = end_angle.to_degrees();
|
||||
|
||||
Ok((start_angle_deg, end_angle_deg))
|
||||
}
|
||||
|
||||
pub fn is_on_circumference(center: Point2d, point: Point2d, radius: f64) -> bool {
|
||||
pub fn is_on_circumference(center: &Point2d, point: &Point2d, radius: f64) -> bool {
|
||||
let dx = point.x - center.x;
|
||||
let dy = point.y - center.y;
|
||||
|
||||
@ -295,7 +237,7 @@ mod tests {
|
||||
// Here you can bring your functions into scope
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
use super::{get_x_component, get_y_component, Angle};
|
||||
use super::{get_x_component, get_y_component};
|
||||
use crate::executor::SourceRange;
|
||||
|
||||
static EACH_QUAD: [(i32, [i32; 2]); 12] = [
|
||||
@ -319,28 +261,28 @@ mod tests {
|
||||
let mut results = Vec::new();
|
||||
|
||||
for &(angle, expected_result) in EACH_QUAD.iter() {
|
||||
let res = get_y_component(Angle::from_degrees(angle as f64), 1.0);
|
||||
results.push([res.x.round() as i32, res.y.round() as i32]);
|
||||
let res = get_y_component(angle as f64, 1.0);
|
||||
results.push([res[0].round() as i32, res[1].round() as i32]);
|
||||
expected.push(expected_result);
|
||||
}
|
||||
|
||||
assert_eq!(results, expected);
|
||||
|
||||
let result = get_y_component(Angle::ZERO, 1.0);
|
||||
assert_eq!(result.x as i32, 1);
|
||||
assert_eq!(result.y as i32, 0);
|
||||
let result = get_y_component(0.0, 1.0);
|
||||
assert_eq!(result[0] as i32, 1);
|
||||
assert_eq!(result[1] as i32, 0);
|
||||
|
||||
let result = get_y_component(Angle::from_degrees(90.0), 1.0);
|
||||
assert_eq!(result.x as i32, 1);
|
||||
assert!(result.y > 100000.0);
|
||||
let result = get_y_component(90.0, 1.0);
|
||||
assert_eq!(result[0] as i32, 1);
|
||||
assert!(result[1] > 100000.0);
|
||||
|
||||
let result = get_y_component(Angle::from_degrees(180.0), 1.0);
|
||||
assert_eq!(result.x as i32, -1);
|
||||
assert!((result.y - 0.0).abs() < f64::EPSILON);
|
||||
let result = get_y_component(180.0, 1.0);
|
||||
assert_eq!(result[0] as i32, -1);
|
||||
assert!((result[1] - 0.0).abs() < f64::EPSILON);
|
||||
|
||||
let result = get_y_component(Angle::from_degrees(270.0), 1.0);
|
||||
assert_eq!(result.x as i32, -1);
|
||||
assert!(result.y < -100000.0);
|
||||
let result = get_y_component(270.0, 1.0);
|
||||
assert_eq!(result[0] as i32, -1);
|
||||
assert!(result[1] < -100000.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -349,60 +291,45 @@ mod tests {
|
||||
let mut results = Vec::new();
|
||||
|
||||
for &(angle, expected_result) in EACH_QUAD.iter() {
|
||||
let res = get_x_component(Angle::from_degrees(angle as f64), 1.0);
|
||||
results.push([res.x.round() as i32, res.y.round() as i32]);
|
||||
let res = get_x_component(angle as f64, 1.0);
|
||||
results.push([res[0].round() as i32, res[1].round() as i32]);
|
||||
expected.push(expected_result);
|
||||
}
|
||||
|
||||
assert_eq!(results, expected);
|
||||
|
||||
let result = get_x_component(Angle::ZERO, 1.0);
|
||||
assert!(result.x > 100000.0);
|
||||
assert_eq!(result.y as i32, 1);
|
||||
let result = get_x_component(0.0, 1.0);
|
||||
assert!(result[0] > 100000.0);
|
||||
assert_eq!(result[1] as i32, 1);
|
||||
|
||||
let result = get_x_component(Angle::from_degrees(90.0), 1.0);
|
||||
assert!((result.x - 0.0).abs() < f64::EPSILON);
|
||||
assert_eq!(result.y as i32, 1);
|
||||
let result = get_x_component(90.0, 1.0);
|
||||
assert!((result[0] - 0.0).abs() < f64::EPSILON);
|
||||
assert_eq!(result[1] as i32, 1);
|
||||
|
||||
let result = get_x_component(Angle::from_degrees(180.0), 1.0);
|
||||
assert!(result.x < -100000.0);
|
||||
assert_eq!(result.y as i32, 1);
|
||||
let result = get_x_component(180.0, 1.0);
|
||||
assert!(result[0] < -100000.0);
|
||||
assert_eq!(result[1] as i32, 1);
|
||||
|
||||
let result = get_x_component(Angle::from_degrees(270.0), 1.0);
|
||||
assert!((result.x - 0.0).abs() < f64::EPSILON);
|
||||
assert_eq!(result.y as i32, -1);
|
||||
let result = get_x_component(270.0, 1.0);
|
||||
assert!((result[0] - 0.0).abs() < f64::EPSILON);
|
||||
assert_eq!(result[1] as i32, -1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_arc_center_and_end() {
|
||||
let (center, end) = super::arc_center_and_end(
|
||||
super::Point2d { x: 0.0, y: 0.0 },
|
||||
Angle::ZERO,
|
||||
Angle::from_degrees(90.0),
|
||||
1.0,
|
||||
);
|
||||
let (center, end) = super::arc_center_and_end(&super::Point2d { x: 0.0, y: 0.0 }, 0.0, 90.0, 1.0);
|
||||
assert_eq!(center.x.round(), -1.0);
|
||||
assert_eq!(center.y, 0.0);
|
||||
assert_eq!(end.x.round(), -1.0);
|
||||
assert_eq!(end.y, 1.0);
|
||||
|
||||
let (center, end) = super::arc_center_and_end(
|
||||
super::Point2d { x: 0.0, y: 0.0 },
|
||||
Angle::ZERO,
|
||||
Angle::from_degrees(180.0),
|
||||
1.0,
|
||||
);
|
||||
let (center, end) = super::arc_center_and_end(&super::Point2d { x: 0.0, y: 0.0 }, 0.0, 180.0, 1.0);
|
||||
assert_eq!(center.x.round(), -1.0);
|
||||
assert_eq!(center.y, 0.0);
|
||||
assert_eq!(end.x.round(), -2.0);
|
||||
assert_eq!(end.y.round(), 0.0);
|
||||
|
||||
let (center, end) = super::arc_center_and_end(
|
||||
super::Point2d { x: 0.0, y: 0.0 },
|
||||
Angle::ZERO,
|
||||
Angle::from_degrees(180.0),
|
||||
10.0,
|
||||
);
|
||||
let (center, end) = super::arc_center_and_end(&super::Point2d { x: 0.0, y: 0.0 }, 0.0, 180.0, 10.0);
|
||||
assert_eq!(center.x.round(), -10.0);
|
||||
assert_eq!(center.y, 0.0);
|
||||
assert_eq!(end.x.round(), -20.0);
|
||||
@ -412,42 +339,42 @@ mod tests {
|
||||
#[test]
|
||||
fn test_arc_angles() {
|
||||
let (angle_start, angle_end) = super::arc_angles(
|
||||
super::Point2d { x: 0.0, y: 0.0 },
|
||||
super::Point2d { x: -1.0, y: 1.0 },
|
||||
super::Point2d { x: -1.0, y: 0.0 },
|
||||
&super::Point2d { x: 0.0, y: 0.0 },
|
||||
&super::Point2d { x: -1.0, y: 1.0 },
|
||||
&super::Point2d { x: -1.0, y: 0.0 },
|
||||
1.0,
|
||||
SourceRange(Default::default()),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(angle_start.degrees().round(), 0.0);
|
||||
assert_eq!(angle_end.degrees().round(), 90.0);
|
||||
assert_eq!(angle_start.round(), 0.0);
|
||||
assert_eq!(angle_end.round(), 90.0);
|
||||
|
||||
let (angle_start, angle_end) = super::arc_angles(
|
||||
super::Point2d { x: 0.0, y: 0.0 },
|
||||
super::Point2d { x: -2.0, y: 0.0 },
|
||||
super::Point2d { x: -1.0, y: 0.0 },
|
||||
&super::Point2d { x: 0.0, y: 0.0 },
|
||||
&super::Point2d { x: -2.0, y: 0.0 },
|
||||
&super::Point2d { x: -1.0, y: 0.0 },
|
||||
1.0,
|
||||
SourceRange(Default::default()),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(angle_start.degrees().round(), 0.0);
|
||||
assert_eq!(angle_end.degrees().round(), 180.0);
|
||||
assert_eq!(angle_start.round(), 0.0);
|
||||
assert_eq!(angle_end.round(), 180.0);
|
||||
|
||||
let (angle_start, angle_end) = super::arc_angles(
|
||||
super::Point2d { x: 0.0, y: 0.0 },
|
||||
super::Point2d { x: -20.0, y: 0.0 },
|
||||
super::Point2d { x: -10.0, y: 0.0 },
|
||||
&super::Point2d { x: 0.0, y: 0.0 },
|
||||
&super::Point2d { x: -20.0, y: 0.0 },
|
||||
&super::Point2d { x: -10.0, y: 0.0 },
|
||||
10.0,
|
||||
SourceRange(Default::default()),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(angle_start.degrees().round(), 0.0);
|
||||
assert_eq!(angle_end.degrees().round(), 180.0);
|
||||
assert_eq!(angle_start.round(), 0.0);
|
||||
assert_eq!(angle_end.round(), 180.0);
|
||||
|
||||
let result = super::arc_angles(
|
||||
super::Point2d { x: 0.0, y: 5.0 },
|
||||
super::Point2d { x: 5.0, y: 5.0 },
|
||||
super::Point2d { x: 10.0, y: -10.0 },
|
||||
&super::Point2d { x: 0.0, y: 5.0 },
|
||||
&super::Point2d { x: 5.0, y: 5.0 },
|
||||
&super::Point2d { x: 10.0, y: -10.0 },
|
||||
10.0,
|
||||
SourceRange(Default::default()),
|
||||
);
|
||||
@ -457,7 +384,7 @@ mod tests {
|
||||
} else {
|
||||
panic!("Expected error");
|
||||
}
|
||||
assert_eq!(angle_start.degrees().round(), 0.0);
|
||||
assert_eq!(angle_end.degrees().round(), 180.0);
|
||||
assert_eq!(angle_start.round(), 0.0);
|
||||
assert_eq!(angle_end.round(), 180.0);
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user