move export to the rust side to make the interface way more clean (#5855)
* move export Signed-off-by: Jess Frazelle <github@jessfraz.com> testing Signed-off-by: Jess Frazelle <github@jessfraz.com> remove debugs Signed-off-by: Jess Frazelle <github@jessfraz.com> fix main Signed-off-by: Jess Frazelle <github@jessfraz.com> updates Signed-off-by: Jess Frazelle <github@jessfraz.com> fices Signed-off-by: Jess Frazelle <github@jessfraz.com> get rid of logs Signed-off-by: Jess Frazelle <github@jessfraz.com> * Convert async actions anti-pattern to fromPromise actors * Fix tsc by removing a generic type * updates Signed-off-by: Jess Frazelle <github@jessfraz.com> * updates Signed-off-by: Jess Frazelle <github@jessfraz.com> * cleanup Signed-off-by: Jess Frazelle <github@jessfraz.com> * cleanup Signed-off-by: Jess Frazelle <github@jessfraz.com> * Update rustContext.ts * fix Signed-off-by: Jess Frazelle <github@jessfraz.com> * fix; Signed-off-by: Jess Frazelle <github@jessfraz.com> * cleanup Signed-off-by: Jess Frazelle <github@jessfraz.com> * remove weird file Signed-off-by: Jess Frazelle <github@jessfraz.com> * fix Signed-off-by: Jess Frazelle <github@jessfraz.com> * updates Signed-off-by: Jess Frazelle <github@jessfraz.com> --------- Signed-off-by: Jess Frazelle <github@jessfraz.com> Co-authored-by: Frank Noirot <frankjohnson1993@gmail.com>
This commit is contained in:
@ -405,8 +405,9 @@ extrude001 = extrude(sketch001, length = 50)
|
|||||||
await expect(successToastMessage).toBeVisible()
|
await expect(successToastMessage).toBeVisible()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
// We updated this test such that you can have multiple exports going at once.
|
||||||
test(
|
test(
|
||||||
'ensure you can not export while an export is already going',
|
'ensure you CAN export while an export is already going',
|
||||||
{ tag: ['@skipLinux', '@skipWin'] },
|
{ tag: ['@skipLinux', '@skipWin'] },
|
||||||
async ({ page, homePage }) => {
|
async ({ page, homePage }) => {
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
@ -441,22 +442,13 @@ extrude001 = extrude(sketch001, length = 50)
|
|||||||
const alreadyExportingToastMessage = page.getByText(`Already exporting`)
|
const alreadyExportingToastMessage = page.getByText(`Already exporting`)
|
||||||
const successToastMessage = page.getByText(`Exported successfully`)
|
const successToastMessage = page.getByText(`Exported successfully`)
|
||||||
|
|
||||||
await test.step('Blocked second export', async () => {
|
await test.step('second export', async () => {
|
||||||
await clickExportButton(page)
|
await clickExportButton(page)
|
||||||
|
|
||||||
await expect(exportingToastMessage).toBeVisible()
|
await expect(exportingToastMessage).toBeVisible()
|
||||||
|
|
||||||
await clickExportButton(page)
|
await clickExportButton(page)
|
||||||
|
|
||||||
await test.step('The second export is blocked', async () => {
|
|
||||||
// Find the toast.
|
|
||||||
// Look out for the toast message
|
|
||||||
await Promise.all([
|
|
||||||
expect(exportingToastMessage.first()).toBeVisible(),
|
|
||||||
expect(alreadyExportingToastMessage).toBeVisible(),
|
|
||||||
])
|
|
||||||
})
|
|
||||||
|
|
||||||
await test.step('The first export still succeeds', async () => {
|
await test.step('The first export still succeeds', async () => {
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
expect(exportingToastMessage).not.toBeVisible({ timeout: 15_000 }),
|
expect(exportingToastMessage).not.toBeVisible({ timeout: 15_000 }),
|
||||||
@ -486,7 +478,7 @@ extrude001 = extrude(sketch001, length = 50)
|
|||||||
expect(alreadyExportingToastMessage).not.toBeVisible(),
|
expect(alreadyExportingToastMessage).not.toBeVisible(),
|
||||||
])
|
])
|
||||||
|
|
||||||
await expect(successToastMessage).toBeVisible()
|
await expect(successToastMessage).toHaveCount(2)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ -36,6 +36,7 @@
|
|||||||
"@xstate/inspect": "^0.8.0",
|
"@xstate/inspect": "^0.8.0",
|
||||||
"@xstate/react": "^4.1.1",
|
"@xstate/react": "^4.1.1",
|
||||||
"bonjour-service": "^1.3.0",
|
"bonjour-service": "^1.3.0",
|
||||||
|
"bson": "^6.10.3",
|
||||||
"chokidar": "^4.0.1",
|
"chokidar": "^4.0.1",
|
||||||
"codemirror": "^6.0.1",
|
"codemirror": "^6.0.1",
|
||||||
"decamelize": "^6.0.0",
|
"decamelize": "^6.0.0",
|
||||||
@ -212,6 +213,7 @@
|
|||||||
"typescript-eslint": "^8.26.1",
|
"typescript-eslint": "^8.26.1",
|
||||||
"vite": "^5.4.12",
|
"vite": "^5.4.12",
|
||||||
"vite-plugin-package-version": "^1.1.0",
|
"vite-plugin-package-version": "^1.1.0",
|
||||||
|
"vite-plugin-top-level-await": "^1.5.0",
|
||||||
"vite-tsconfig-paths": "^4.3.2",
|
"vite-tsconfig-paths": "^4.3.2",
|
||||||
"vitest": "^1.6.1",
|
"vitest": "^1.6.1",
|
||||||
"vitest-webgl-canvas-mock": "^1.1.0",
|
"vitest-webgl-canvas-mock": "^1.1.0",
|
||||||
|
1
rust/Cargo.lock
generated
1
rust/Cargo.lock
generated
@ -1982,6 +1982,7 @@ dependencies = [
|
|||||||
"js-sys",
|
"js-sys",
|
||||||
"kcl-lib",
|
"kcl-lib",
|
||||||
"kittycad",
|
"kittycad",
|
||||||
|
"kittycad-modeling-cmds",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"tokio",
|
"tokio",
|
||||||
"toml",
|
"toml",
|
||||||
|
@ -30,6 +30,7 @@ debug = "line-tables-only"
|
|||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
async-trait = "0.1.85"
|
async-trait = "0.1.85"
|
||||||
anyhow = { version = "1" }
|
anyhow = { version = "1" }
|
||||||
|
bson = { version = "2.13.0", features = ["uuid-1", "chrono"] }
|
||||||
clap = { version = "4.5.31", features = ["derive"] }
|
clap = { version = "4.5.31", features = ["derive"] }
|
||||||
dashmap = { version = "6.1.0" }
|
dashmap = { version = "6.1.0" }
|
||||||
http = "1"
|
http = "1"
|
||||||
|
@ -24,6 +24,7 @@ anyhow = { workspace = true, features = ["backtrace"] }
|
|||||||
async-recursion = "1.1.1"
|
async-recursion = "1.1.1"
|
||||||
async-trait = { workspace = true }
|
async-trait = { workspace = true }
|
||||||
base64 = "0.22.1"
|
base64 = "0.22.1"
|
||||||
|
bson = { workspace = true }
|
||||||
chrono = "0.4.38"
|
chrono = "0.4.38"
|
||||||
clap = { version = "4.5.27", default-features = false, optional = true, features = [
|
clap = { version = "4.5.27", default-features = false, optional = true, features = [
|
||||||
"std",
|
"std",
|
||||||
@ -95,7 +96,6 @@ wasm-bindgen-futures = "0.4.49"
|
|||||||
web-sys = { version = "0.3.76", features = ["console"] }
|
web-sys = { version = "0.3.76", features = ["console"] }
|
||||||
|
|
||||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||||
bson = { version = "2.13.0", features = ["uuid-1", "chrono"] }
|
|
||||||
tokio = { workspace = true, features = ["full"] }
|
tokio = { workspace = true, features = ["full"] }
|
||||||
tokio-tungstenite = { version = "0.24.0", features = [
|
tokio-tungstenite = { version = "0.24.0", features = [
|
||||||
"rustls-tls-native-roots",
|
"rustls-tls-native-roots",
|
||||||
|
@ -104,23 +104,29 @@ impl EngineConnection {
|
|||||||
})?;
|
})?;
|
||||||
|
|
||||||
let value = crate::wasm::JsFuture::from(promise).await.map_err(|e| {
|
let value = crate::wasm::JsFuture::from(promise).await.map_err(|e| {
|
||||||
|
// Try to parse the error as an engine error.
|
||||||
|
let err_str = e.as_string().unwrap_or_default();
|
||||||
|
if let Ok(kittycad_modeling_cmds::websocket::FailureWebSocketResponse { errors, .. }) =
|
||||||
|
serde_json::from_str(&err_str)
|
||||||
|
{
|
||||||
KclError::Engine(KclErrorDetails {
|
KclError::Engine(KclErrorDetails {
|
||||||
message: format!("Failed to wait for promise from engine: {:?}", e),
|
message: errors.iter().map(|e| e.message.clone()).collect::<Vec<_>>().join("\n"),
|
||||||
source_ranges: vec![source_range],
|
source_ranges: vec![source_range],
|
||||||
})
|
})
|
||||||
})?;
|
} else {
|
||||||
|
|
||||||
// Parse the value as a string.
|
|
||||||
let s = value.as_string().ok_or_else(|| {
|
|
||||||
KclError::Engine(KclErrorDetails {
|
KclError::Engine(KclErrorDetails {
|
||||||
message: format!("Failed to get string from response from engine: `{:?}`", value),
|
message: format!("Failed to wait for promise from send modeling command: {:?}", e),
|
||||||
source_ranges: vec![source_range],
|
source_ranges: vec![source_range],
|
||||||
})
|
})
|
||||||
|
}
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let ws_result: WebSocketResponse = serde_json::from_str(&s).map_err(|e| {
|
// Convert JsValue to a Uint8Array
|
||||||
|
let data = js_sys::Uint8Array::from(value);
|
||||||
|
|
||||||
|
let ws_result: WebSocketResponse = bson::from_slice(&data.to_vec()).map_err(|e| {
|
||||||
KclError::Engine(KclErrorDetails {
|
KclError::Engine(KclErrorDetails {
|
||||||
message: format!("Failed to deserialize response from engine: {:?}", e),
|
message: format!("Failed to deserialize bson response from engine: {:?}", e),
|
||||||
source_ranges: vec![source_range],
|
source_ranges: vec![source_range],
|
||||||
})
|
})
|
||||||
})?;
|
})?;
|
||||||
|
@ -11,7 +11,7 @@ crate-type = ["cdylib"]
|
|||||||
bench = false
|
bench = false
|
||||||
|
|
||||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||||
bson = { version = "2.13.0", features = ["uuid-1", "chrono"] }
|
bson = { workspace = true, features = ["uuid-1", "chrono"] }
|
||||||
console_error_panic_hook = "0.1.7"
|
console_error_panic_hook = "0.1.7"
|
||||||
data-encoding = "2.6.0"
|
data-encoding = "2.6.0"
|
||||||
futures = "0.3.31"
|
futures = "0.3.31"
|
||||||
@ -22,6 +22,7 @@ gloo-utils = "0.2.0"
|
|||||||
js-sys = "0.3.72"
|
js-sys = "0.3.72"
|
||||||
kcl-lib = { path = "../kcl-lib" }
|
kcl-lib = { path = "../kcl-lib" }
|
||||||
kittycad = { workspace = true }
|
kittycad = { workspace = true }
|
||||||
|
kittycad-modeling-cmds = { workspace = true }
|
||||||
serde_json = { workspace = true }
|
serde_json = { workspace = true }
|
||||||
tokio = { workspace = true, features = ["sync"] }
|
tokio = { workspace = true, features = ["sync"] }
|
||||||
toml = "0.8.19"
|
toml = "0.8.19"
|
||||||
|
@ -106,4 +106,22 @@ impl Context {
|
|||||||
Err(err) => Err(serde_json::to_string(&err).map_err(|serde_err| serde_err.to_string())?),
|
Err(err) => Err(serde_json::to_string(&err).map_err(|serde_err| serde_err.to_string())?),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Export a scene to a file.
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub async fn export(&self, format_json: &str, settings: &str) -> Result<JsValue, String> {
|
||||||
|
console_error_panic_hook::set_once();
|
||||||
|
|
||||||
|
let format: kittycad_modeling_cmds::format::OutputFormat3d =
|
||||||
|
serde_json::from_str(format_json).map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
|
let ctx = self.create_executor_ctx(settings, None, false)?;
|
||||||
|
|
||||||
|
match ctx.export(format).await {
|
||||||
|
// The serde-wasm-bindgen does not work here because of weird HashMap issues.
|
||||||
|
// DO NOT USE serde_wasm_bindgen::to_value it will break the frontend.
|
||||||
|
Ok(outcome) => JsValue::from_serde(&outcome).map_err(|e| e.to_string()),
|
||||||
|
Err(err) => Err(serde_json::to_string(&err).map_err(|serde_err| serde_err.to_string())?),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,25 +18,6 @@ pub async fn kcl_lint(program_ast_json: &str) -> Result<JsValue, JsValue> {
|
|||||||
Ok(JsValue::from_serde(&findings).map_err(|e| e.to_string())?)
|
Ok(JsValue::from_serde(&findings).map_err(|e| e.to_string())?)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[wasm_bindgen]
|
|
||||||
pub fn deserialize_files(data: &[u8]) -> Result<JsValue, JsError> {
|
|
||||||
console_error_panic_hook::set_once();
|
|
||||||
|
|
||||||
let ws_resp: kittycad::types::WebSocketResponse = bson::from_slice(data)?;
|
|
||||||
|
|
||||||
if let Some(success) = ws_resp.success {
|
|
||||||
if !success {
|
|
||||||
return Err(JsError::new(&format!("Server returned error: {:?}", ws_resp.errors)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(kittycad::types::OkWebSocketResponseData::Export { files }) = ws_resp.resp {
|
|
||||||
return Ok(JsValue::from_serde(&files)?);
|
|
||||||
}
|
|
||||||
|
|
||||||
Err(JsError::new(&format!("Invalid response type, got: {:?}", ws_resp)))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub fn parse_wasm(kcl_program_source: &str) -> Result<JsValue, String> {
|
pub fn parse_wasm(kcl_program_source: &str) -> Result<JsValue, String> {
|
||||||
console_error_panic_hook::set_once();
|
console_error_panic_hook::set_once();
|
||||||
|
@ -27,12 +27,6 @@ export const ModelStateIndicator = () => {
|
|||||||
name="checkmark"
|
name="checkmark"
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
} else if (lastCommandType === 'export-done') {
|
|
||||||
className +=
|
|
||||||
'border-6 border border-solid border-chalkboard-60 dark:border-chalkboard-80 bg-chalkboard-20 dark:bg-chalkboard-80 !group-disabled:bg-chalkboard-30 !dark:group-disabled:bg-chalkboard-80 rounded-sm bg-succeed-10/30 dark:bg-succeed'
|
|
||||||
icon = (
|
|
||||||
<CustomIcon data-testid={dataTestId + '-export-done'} name="checkmark" />
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -8,7 +8,6 @@ import React, {
|
|||||||
} from 'react'
|
} from 'react'
|
||||||
import {
|
import {
|
||||||
Actor,
|
Actor,
|
||||||
AnyStateMachine,
|
|
||||||
ContextFrom,
|
ContextFrom,
|
||||||
Prop,
|
Prop,
|
||||||
SnapshotFrom,
|
SnapshotFrom,
|
||||||
@ -33,8 +32,12 @@ import {
|
|||||||
codeManager,
|
codeManager,
|
||||||
editorManager,
|
editorManager,
|
||||||
sceneEntitiesManager,
|
sceneEntitiesManager,
|
||||||
|
rustContext,
|
||||||
} from 'lib/singletons'
|
} from 'lib/singletons'
|
||||||
import { MachineManagerContext } from 'components/MachineManagerProvider'
|
import {
|
||||||
|
MachineManager,
|
||||||
|
MachineManagerContext,
|
||||||
|
} from 'components/MachineManagerProvider'
|
||||||
import { useHotkeys } from 'react-hotkeys-hook'
|
import { useHotkeys } from 'react-hotkeys-hook'
|
||||||
import { applyConstraintHorzVertDistance } from './Toolbar/SetHorzVertDistance'
|
import { applyConstraintHorzVertDistance } from './Toolbar/SetHorzVertDistance'
|
||||||
import {
|
import {
|
||||||
@ -53,7 +56,10 @@ import {
|
|||||||
import { applyConstraintIntersect } from './Toolbar/Intersect'
|
import { applyConstraintIntersect } from './Toolbar/Intersect'
|
||||||
import { applyConstraintAbsDistance } from './Toolbar/SetAbsDistance'
|
import { applyConstraintAbsDistance } from './Toolbar/SetAbsDistance'
|
||||||
import useStateMachineCommands from 'hooks/useStateMachineCommands'
|
import useStateMachineCommands from 'hooks/useStateMachineCommands'
|
||||||
import { modelingMachineCommandConfig } from 'lib/commandBarConfigs/modelingCommandConfig'
|
import {
|
||||||
|
ModelingCommandSchema,
|
||||||
|
modelingMachineCommandConfig,
|
||||||
|
} from 'lib/commandBarConfigs/modelingCommandConfig'
|
||||||
import {
|
import {
|
||||||
SEGMENT_BODIES,
|
SEGMENT_BODIES,
|
||||||
getParentGroup,
|
getParentGroup,
|
||||||
@ -84,21 +90,17 @@ import {
|
|||||||
isCursorInFunctionDefinition,
|
isCursorInFunctionDefinition,
|
||||||
traverse,
|
traverse,
|
||||||
} from 'lang/queryAst'
|
} from 'lang/queryAst'
|
||||||
import { exportFromEngine } from 'lib/exportFromEngine'
|
|
||||||
import { Models } from '@kittycad/lib/dist/types/src'
|
|
||||||
import toast from 'react-hot-toast'
|
import toast from 'react-hot-toast'
|
||||||
import { useLoaderData, useNavigate, useSearchParams } from 'react-router-dom'
|
import { useLoaderData, useNavigate, useSearchParams } from 'react-router-dom'
|
||||||
import { letEngineAnimateAndSyncCamAfter } from 'clientSideScene/CameraControls'
|
import { letEngineAnimateAndSyncCamAfter } from 'clientSideScene/CameraControls'
|
||||||
import { err, reportRejection, trap, reject } from 'lib/trap'
|
import { err, reportRejection, trap, reject } from 'lib/trap'
|
||||||
import {
|
import {
|
||||||
ExportIntent,
|
|
||||||
EngineConnectionStateType,
|
EngineConnectionStateType,
|
||||||
EngineConnectionEvents,
|
EngineConnectionEvents,
|
||||||
} from 'lang/std/engineConnection'
|
} from 'lang/std/engineConnection'
|
||||||
import { submitAndAwaitTextToKcl } from 'lib/textToCad'
|
import { submitAndAwaitTextToKcl } from 'lib/textToCad'
|
||||||
import { useFileContext } from 'hooks/useFileContext'
|
import { useFileContext } from 'hooks/useFileContext'
|
||||||
import { platform, uuidv4 } from 'lib/utils'
|
import { platform, uuidv4 } from 'lib/utils'
|
||||||
import { IndexLoaderData } from 'lib/types'
|
|
||||||
import { Node } from '@rust/kcl-lib/bindings/Node'
|
import { Node } from '@rust/kcl-lib/bindings/Node'
|
||||||
import {
|
import {
|
||||||
getFaceCodeRef,
|
getFaceCodeRef,
|
||||||
@ -111,15 +113,18 @@ import { commandBarActor } from 'machines/commandBarMachine'
|
|||||||
import { useToken } from 'machines/appMachine'
|
import { useToken } from 'machines/appMachine'
|
||||||
import { getNodePathFromSourceRange } from 'lang/queryAstNodePathUtils'
|
import { getNodePathFromSourceRange } from 'lang/queryAstNodePathUtils'
|
||||||
import { useSettings } from 'machines/appMachine'
|
import { useSettings } from 'machines/appMachine'
|
||||||
|
import { IndexLoaderData } from 'lib/types'
|
||||||
type MachineContext<T extends AnyStateMachine> = {
|
import { OutputFormat3d } from '@rust/kcl-lib/bindings/ModelingCmd'
|
||||||
state: StateFrom<T>
|
import { EXPORT_TOAST_MESSAGES, MAKE_TOAST_MESSAGES } from 'lib/constants'
|
||||||
context: ContextFrom<T>
|
import { exportMake } from 'lib/exportMake'
|
||||||
send: Prop<Actor<T>, 'send'>
|
import { exportSave } from 'lib/exportSave'
|
||||||
}
|
|
||||||
|
|
||||||
export const ModelingMachineContext = createContext(
|
export const ModelingMachineContext = createContext(
|
||||||
{} as MachineContext<typeof modelingMachine>
|
{} as {
|
||||||
|
state: StateFrom<typeof modelingMachine>
|
||||||
|
context: ContextFrom<typeof modelingMachine>
|
||||||
|
send: Prop<Actor<typeof modelingMachine>, 'send'>
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
const commandBarIsClosedSelector = (
|
const commandBarIsClosedSelector = (
|
||||||
@ -524,118 +529,6 @@ export const ModelingMachineProvider = ({
|
|||||||
return {}
|
return {}
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
Make: ({ context, event }) => {
|
|
||||||
if (event.type !== 'Make') return
|
|
||||||
// Check if we already have an export intent.
|
|
||||||
if (engineCommandManager.exportInfo) {
|
|
||||||
toast.error('Already exporting')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// Set the export intent.
|
|
||||||
engineCommandManager.exportInfo = {
|
|
||||||
intent: ExportIntent.Make,
|
|
||||||
name: file?.name || '',
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the current machine.
|
|
||||||
// Due to our use of singeton pattern, we need to do this to reliably
|
|
||||||
// update this object across React and non-React boundary.
|
|
||||||
// We need to do this eagerly because of the exportToEngine call below.
|
|
||||||
if (engineCommandManager.machineManager === null) {
|
|
||||||
console.warn(
|
|
||||||
"engineCommandManager.machineManager is null. It shouldn't be at this point. Aborting operation."
|
|
||||||
)
|
|
||||||
return
|
|
||||||
} else {
|
|
||||||
engineCommandManager.machineManager.currentMachine =
|
|
||||||
event.data.machine
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update the rest of the UI that needs to know the current machine
|
|
||||||
context.machineManager.setCurrentMachine(event.data.machine)
|
|
||||||
|
|
||||||
const format: Models['OutputFormat_type'] = {
|
|
||||||
type: 'stl',
|
|
||||||
coords: {
|
|
||||||
forward: {
|
|
||||||
axis: 'y',
|
|
||||||
direction: 'negative',
|
|
||||||
},
|
|
||||||
up: {
|
|
||||||
axis: 'z',
|
|
||||||
direction: 'positive',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
storage: 'ascii',
|
|
||||||
// Convert all units to mm since that is what the slicer expects.
|
|
||||||
units: 'mm',
|
|
||||||
selection: { type: 'default_scene' },
|
|
||||||
}
|
|
||||||
|
|
||||||
exportFromEngine({
|
|
||||||
format: format,
|
|
||||||
}).catch(reportRejection)
|
|
||||||
},
|
|
||||||
'Engine export': ({ event }) => {
|
|
||||||
if (event.type !== 'Export') return
|
|
||||||
if (engineCommandManager.exportInfo) {
|
|
||||||
toast.error('Already exporting')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// Set the export intent.
|
|
||||||
engineCommandManager.exportInfo = {
|
|
||||||
intent: ExportIntent.Save,
|
|
||||||
// This never gets used its only for make.
|
|
||||||
name: file?.name?.replace('.kcl', `.${event.data.type}`) || '',
|
|
||||||
}
|
|
||||||
|
|
||||||
const format = {
|
|
||||||
...event.data,
|
|
||||||
} as Partial<Models['OutputFormat_type']>
|
|
||||||
|
|
||||||
// Set all the un-configurable defaults here.
|
|
||||||
if (format.type === 'gltf') {
|
|
||||||
format.presentation = 'pretty'
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
format.type === 'obj' ||
|
|
||||||
format.type === 'ply' ||
|
|
||||||
format.type === 'step' ||
|
|
||||||
format.type === 'stl'
|
|
||||||
) {
|
|
||||||
// Set the default coords.
|
|
||||||
// In the future we can make this configurable.
|
|
||||||
// But for now, its probably best to keep it consistent with the
|
|
||||||
// UI.
|
|
||||||
format.coords = {
|
|
||||||
forward: {
|
|
||||||
axis: 'y',
|
|
||||||
direction: 'negative',
|
|
||||||
},
|
|
||||||
up: {
|
|
||||||
axis: 'z',
|
|
||||||
direction: 'positive',
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
format.type === 'obj' ||
|
|
||||||
format.type === 'stl' ||
|
|
||||||
format.type === 'ply'
|
|
||||||
) {
|
|
||||||
format.units = defaultUnit.current
|
|
||||||
}
|
|
||||||
|
|
||||||
if (format.type === 'ply' || format.type === 'stl') {
|
|
||||||
format.selection = { type: 'default_scene' }
|
|
||||||
}
|
|
||||||
|
|
||||||
exportFromEngine({
|
|
||||||
format: format as Models['OutputFormat_type'],
|
|
||||||
}).catch(reportRejection)
|
|
||||||
},
|
|
||||||
'Submit to Text-to-CAD API': ({ event }) => {
|
'Submit to Text-to-CAD API': ({ event }) => {
|
||||||
if (event.type !== 'Text-to-CAD') return
|
if (event.type !== 'Text-to-CAD') return
|
||||||
const trimmedPrompt = event.data.prompt.trim()
|
const trimmedPrompt = event.data.prompt.trim()
|
||||||
@ -696,14 +589,155 @@ export const ModelingMachineProvider = ({
|
|||||||
else if (kclManager.ast.body.length === 0)
|
else if (kclManager.ast.body.length === 0)
|
||||||
errorMessage += 'due to Empty Scene'
|
errorMessage += 'due to Empty Scene'
|
||||||
console.error(errorMessage)
|
console.error(errorMessage)
|
||||||
toast.error(errorMessage, {
|
toast.error(errorMessage)
|
||||||
id: kclManager.engineCommandManager.pendingExport?.toastId,
|
|
||||||
})
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
actors: {
|
actors: {
|
||||||
|
exportFromEngine: fromPromise(
|
||||||
|
async ({ input }: { input?: ModelingCommandSchema['Export'] }) => {
|
||||||
|
if (!input) {
|
||||||
|
return new Error('No input provided')
|
||||||
|
}
|
||||||
|
|
||||||
|
let fileName = file?.name?.replace('.kcl', `.${input.type}`) || ''
|
||||||
|
console.log('fileName', fileName)
|
||||||
|
// Ensure the file has an extension.
|
||||||
|
if (!fileName.includes('.')) {
|
||||||
|
fileName += `.${input.type}`
|
||||||
|
}
|
||||||
|
|
||||||
|
const format = {
|
||||||
|
...input,
|
||||||
|
} as Partial<OutputFormat3d>
|
||||||
|
|
||||||
|
// Set all the un-configurable defaults here.
|
||||||
|
if (format.type === 'gltf') {
|
||||||
|
format.presentation = 'pretty'
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
format.type === 'obj' ||
|
||||||
|
format.type === 'ply' ||
|
||||||
|
format.type === 'step' ||
|
||||||
|
format.type === 'stl'
|
||||||
|
) {
|
||||||
|
// Set the default coords.
|
||||||
|
// In the future we can make this configurable.
|
||||||
|
// But for now, its probably best to keep it consistent with the
|
||||||
|
// UI.
|
||||||
|
format.coords = {
|
||||||
|
forward: {
|
||||||
|
axis: 'y',
|
||||||
|
direction: 'negative',
|
||||||
|
},
|
||||||
|
up: {
|
||||||
|
axis: 'z',
|
||||||
|
direction: 'positive',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
format.type === 'obj' ||
|
||||||
|
format.type === 'stl' ||
|
||||||
|
format.type === 'ply'
|
||||||
|
) {
|
||||||
|
format.units = defaultUnit.current
|
||||||
|
}
|
||||||
|
|
||||||
|
if (format.type === 'ply' || format.type === 'stl') {
|
||||||
|
format.selection = { type: 'default_scene' }
|
||||||
|
}
|
||||||
|
|
||||||
|
const toastId = toast.loading(EXPORT_TOAST_MESSAGES.START)
|
||||||
|
const files = await rustContext.export(
|
||||||
|
format,
|
||||||
|
{
|
||||||
|
settings: { modeling: { base_unit: defaultUnit.current } },
|
||||||
|
},
|
||||||
|
toastId
|
||||||
|
)
|
||||||
|
|
||||||
|
if (files === undefined) {
|
||||||
|
// We already sent the toast message in the export function.
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
await exportSave({ files, toastId, fileName })
|
||||||
|
}
|
||||||
|
),
|
||||||
|
makeFromEngine: fromPromise(
|
||||||
|
async ({
|
||||||
|
input,
|
||||||
|
}: {
|
||||||
|
input?: {
|
||||||
|
machineManager: MachineManager
|
||||||
|
} & ModelingCommandSchema['Make']
|
||||||
|
}) => {
|
||||||
|
if (input === undefined) {
|
||||||
|
return new Error('No input provided')
|
||||||
|
}
|
||||||
|
|
||||||
|
const name = file?.name || ''
|
||||||
|
|
||||||
|
// Set the current machine.
|
||||||
|
// Due to our use of singeton pattern, we need to do this to reliably
|
||||||
|
// update this object across React and non-React boundary.
|
||||||
|
// We need to do this eagerly because of the exportToEngine call below.
|
||||||
|
if (engineCommandManager.machineManager === null) {
|
||||||
|
console.warn(
|
||||||
|
"engineCommandManager.machineManager is null. It shouldn't be at this point. Aborting operation."
|
||||||
|
)
|
||||||
|
return new Error('Machine manager is not set')
|
||||||
|
} else {
|
||||||
|
engineCommandManager.machineManager.currentMachine = input.machine
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the rest of the UI that needs to know the current machine
|
||||||
|
input.machineManager.setCurrentMachine(input.machine)
|
||||||
|
|
||||||
|
const format: OutputFormat3d = {
|
||||||
|
type: 'stl',
|
||||||
|
coords: {
|
||||||
|
forward: {
|
||||||
|
axis: 'y',
|
||||||
|
direction: 'negative',
|
||||||
|
},
|
||||||
|
up: {
|
||||||
|
axis: 'z',
|
||||||
|
direction: 'positive',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
storage: 'ascii',
|
||||||
|
// Convert all units to mm since that is what the slicer expects.
|
||||||
|
units: 'mm',
|
||||||
|
selection: { type: 'default_scene' },
|
||||||
|
}
|
||||||
|
|
||||||
|
const toastId = toast.loading(MAKE_TOAST_MESSAGES.START)
|
||||||
|
const files = await rustContext.export(
|
||||||
|
format,
|
||||||
|
{
|
||||||
|
settings: { modeling: { base_unit: 'mm' } },
|
||||||
|
},
|
||||||
|
toastId
|
||||||
|
)
|
||||||
|
|
||||||
|
if (files === undefined) {
|
||||||
|
// We already sent the toast message in the export function.
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
await exportMake({
|
||||||
|
files,
|
||||||
|
toastId,
|
||||||
|
name,
|
||||||
|
machineManager: engineCommandManager.machineManager,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
),
|
||||||
'AST-undo-startSketchOn': fromPromise(
|
'AST-undo-startSketchOn': fromPromise(
|
||||||
async ({ input: { sketchDetails } }) => {
|
async ({ input: { sketchDetails } }) => {
|
||||||
if (!sketchDetails) return
|
if (!sketchDetails) return
|
||||||
|
@ -6,8 +6,8 @@ import {
|
|||||||
} from 'lang/wasm'
|
} from 'lang/wasm'
|
||||||
import { VITE_KC_API_WS_MODELING_URL, VITE_KC_DEV_TOKEN } from 'env'
|
import { VITE_KC_API_WS_MODELING_URL, VITE_KC_DEV_TOKEN } from 'env'
|
||||||
import { Models } from '@kittycad/lib'
|
import { Models } from '@kittycad/lib'
|
||||||
import { exportSave } from 'lib/exportSave'
|
|
||||||
import { deferExecution, isOverlap, uuidv4 } from 'lib/utils'
|
import { deferExecution, isOverlap, uuidv4 } from 'lib/utils'
|
||||||
|
import { BSON, Binary as BSONBinary } from 'bson'
|
||||||
import {
|
import {
|
||||||
Themes,
|
Themes,
|
||||||
getThemeColorForEngine,
|
getThemeColorForEngine,
|
||||||
@ -16,14 +16,8 @@ import {
|
|||||||
} from 'lib/theme'
|
} from 'lib/theme'
|
||||||
import { EngineCommand, ResponseMap } from 'lang/std/artifactGraph'
|
import { EngineCommand, ResponseMap } from 'lang/std/artifactGraph'
|
||||||
import { useModelingContext } from 'hooks/useModelingContext'
|
import { useModelingContext } from 'hooks/useModelingContext'
|
||||||
import { exportMake } from 'lib/exportMake'
|
|
||||||
import toast from 'react-hot-toast'
|
|
||||||
import { SettingsViaQueryString } from 'lib/settings/settingsTypes'
|
import { SettingsViaQueryString } from 'lib/settings/settingsTypes'
|
||||||
import {
|
import { EXECUTE_AST_INTERRUPT_ERROR_MESSAGE } from 'lib/constants'
|
||||||
EXECUTE_AST_INTERRUPT_ERROR_MESSAGE,
|
|
||||||
EXPORT_TOAST_MESSAGES,
|
|
||||||
MAKE_TOAST_MESSAGES,
|
|
||||||
} from 'lib/constants'
|
|
||||||
import { KclManager } from 'lang/KclSingleton'
|
import { KclManager } from 'lang/KclSingleton'
|
||||||
import { reportRejection } from 'lib/trap'
|
import { reportRejection } from 'lib/trap'
|
||||||
import { markOnce } from 'lib/performance'
|
import { markOnce } from 'lib/performance'
|
||||||
@ -47,16 +41,6 @@ interface NewTrackArgs {
|
|||||||
mediaStream: MediaStream
|
mediaStream: MediaStream
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum ExportIntent {
|
|
||||||
Save = 'save',
|
|
||||||
Make = 'make',
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ExportInfo {
|
|
||||||
intent: ExportIntent
|
|
||||||
name: string
|
|
||||||
}
|
|
||||||
|
|
||||||
type ClientMetrics = Models['ClientMetrics_type']
|
type ClientMetrics = Models['ClientMetrics_type']
|
||||||
|
|
||||||
interface WebRTCClientMetrics extends ClientMetrics {
|
interface WebRTCClientMetrics extends ClientMetrics {
|
||||||
@ -1069,18 +1053,6 @@ class EngineConnection extends EventTarget {
|
|||||||
`Error in response to request ${message.request_id}:\n${errorsString}
|
`Error in response to request ${message.request_id}:\n${errorsString}
|
||||||
failed cmd type was ${artifactThatFailed?.type}`
|
failed cmd type was ${artifactThatFailed?.type}`
|
||||||
)
|
)
|
||||||
// Check if this was a pending export command.
|
|
||||||
if (
|
|
||||||
this.engineCommandManager.pendingExport?.commandId ===
|
|
||||||
message.request_id
|
|
||||||
) {
|
|
||||||
// Reject the promise with the error.
|
|
||||||
this.engineCommandManager.pendingExport.reject(errorsString)
|
|
||||||
toast.error(errorsString, {
|
|
||||||
id: this.engineCommandManager.pendingExport.toastId,
|
|
||||||
})
|
|
||||||
this.engineCommandManager.pendingExport = undefined
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
console.error(`Error from server:\n${errorsString}`)
|
console.error(`Error from server:\n${errorsString}`)
|
||||||
}
|
}
|
||||||
@ -1365,10 +1337,6 @@ export type CommandLog =
|
|||||||
type: 'execution-done'
|
type: 'execution-done'
|
||||||
data: null
|
data: null
|
||||||
}
|
}
|
||||||
| {
|
|
||||||
type: 'export-done'
|
|
||||||
data: null
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum EngineCommandManagerEvents {
|
export enum EngineCommandManagerEvents {
|
||||||
// engineConnection is available but scene setup may not have run
|
// engineConnection is available but scene setup may not have run
|
||||||
@ -1432,16 +1400,6 @@ export class EngineCommandManager extends EventTarget {
|
|||||||
inSequence = 1
|
inSequence = 1
|
||||||
engineConnection?: EngineConnection
|
engineConnection?: EngineConnection
|
||||||
commandLogs: CommandLog[] = []
|
commandLogs: CommandLog[] = []
|
||||||
pendingExport?: {
|
|
||||||
/** The id of the shared loading/success/error toast for export */
|
|
||||||
toastId: string
|
|
||||||
/** An on-success callback */
|
|
||||||
resolve: (a: null) => void
|
|
||||||
/** An on-error callback */
|
|
||||||
reject: (reason: string) => void
|
|
||||||
/** The engine command uuid */
|
|
||||||
commandId: string
|
|
||||||
}
|
|
||||||
settings: SettingsViaQueryString
|
settings: SettingsViaQueryString
|
||||||
|
|
||||||
streamDimensions = {
|
streamDimensions = {
|
||||||
@ -1452,12 +1410,6 @@ export class EngineCommandManager extends EventTarget {
|
|||||||
|
|
||||||
elVideo: HTMLVideoElement | null = null
|
elVideo: HTMLVideoElement | null = null
|
||||||
|
|
||||||
/**
|
|
||||||
* Export intent tracks the intent of the export. If it is null there is no
|
|
||||||
* export in progress. Otherwise it is an enum value of the intent.
|
|
||||||
* Another export cannot be started if one is already in progress.
|
|
||||||
*/
|
|
||||||
private _exportInfo: ExportInfo | null = null
|
|
||||||
_commandLogCallBack: (command: CommandLog[]) => void = () => {}
|
_commandLogCallBack: (command: CommandLog[]) => void = () => {}
|
||||||
|
|
||||||
subscriptions: {
|
subscriptions: {
|
||||||
@ -1509,14 +1461,6 @@ export class EngineCommandManager extends EventTarget {
|
|||||||
// The current "manufacturing machine" aka 3D printer, CNC, etc.
|
// The current "manufacturing machine" aka 3D printer, CNC, etc.
|
||||||
public machineManager: MachineManager | null = null
|
public machineManager: MachineManager | null = null
|
||||||
|
|
||||||
set exportInfo(info: ExportInfo | null) {
|
|
||||||
this._exportInfo = info
|
|
||||||
}
|
|
||||||
|
|
||||||
get exportInfo() {
|
|
||||||
return this._exportInfo
|
|
||||||
}
|
|
||||||
|
|
||||||
start({
|
start({
|
||||||
setMediaStream,
|
setMediaStream,
|
||||||
setIsStreamReady,
|
setIsStreamReady,
|
||||||
@ -1691,64 +1635,33 @@ export class EngineCommandManager extends EventTarget {
|
|||||||
engineConnection.websocket?.addEventListener('message', ((
|
engineConnection.websocket?.addEventListener('message', ((
|
||||||
event: MessageEvent
|
event: MessageEvent
|
||||||
) => {
|
) => {
|
||||||
|
let message: Models['WebSocketResponse_type'] | null = null
|
||||||
|
|
||||||
if (event.data instanceof ArrayBuffer) {
|
if (event.data instanceof ArrayBuffer) {
|
||||||
// If the data is an ArrayBuffer, it's the result of an export command,
|
// BSON deserialize the command.
|
||||||
// because in all other cases we send JSON strings. But in the case of
|
message = BSON.deserialize(
|
||||||
// export we send a binary blob.
|
new Uint8Array(event.data)
|
||||||
// Pass this to our export function.
|
) as Models['WebSocketResponse_type']
|
||||||
if (this.exportInfo === null || this.pendingExport === undefined) {
|
// The request id comes back as binary and we want to get the uuid
|
||||||
toast.error(
|
// string from that.
|
||||||
'Export intent was not set, but export data was received'
|
if (message.request_id) {
|
||||||
)
|
message.request_id = binaryToUuid(message.request_id)
|
||||||
console.error(
|
|
||||||
'Export intent was not set, but export data was received'
|
|
||||||
)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (this.exportInfo.intent) {
|
|
||||||
case ExportIntent.Save: {
|
|
||||||
exportSave({
|
|
||||||
data: event.data,
|
|
||||||
fileName: this.exportInfo.name,
|
|
||||||
toastId: this.pendingExport.toastId,
|
|
||||||
}).then(() => {
|
|
||||||
this.pendingExport?.resolve(null)
|
|
||||||
}, this.pendingExport?.reject)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
case ExportIntent.Make: {
|
|
||||||
if (!this.machineManager) {
|
|
||||||
console.warn('Some how, no manufacturing machine is selected.')
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
exportMake(
|
|
||||||
event.data,
|
|
||||||
this.exportInfo.name,
|
|
||||||
this.pendingExport.toastId,
|
|
||||||
this.machineManager
|
|
||||||
).then((result) => {
|
|
||||||
if (result) {
|
|
||||||
this.pendingExport?.resolve(null)
|
|
||||||
} else {
|
} else {
|
||||||
this.pendingExport?.reject('Failed to make export')
|
message = JSON.parse(event.data)
|
||||||
}
|
}
|
||||||
}, this.pendingExport?.reject)
|
|
||||||
break
|
if (message === null) {
|
||||||
}
|
// We should never get here.
|
||||||
}
|
console.error('Received a null message from the engine', event)
|
||||||
// Set the export intent back to null.
|
|
||||||
this.exportInfo = null
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const message: Models['WebSocketResponse_type'] = JSON.parse(event.data)
|
|
||||||
const pending = this.pendingCommands[message.request_id || '']
|
const pending = this.pendingCommands[message.request_id || '']
|
||||||
|
|
||||||
if (pending && !message.success) {
|
if (pending && !message.success) {
|
||||||
// handle bad case
|
// handle bad case
|
||||||
pending.reject(`engine error: ${JSON.stringify(message.errors)}`)
|
pending.reject(JSON.stringify(message))
|
||||||
delete this.pendingCommands[message.request_id || '']
|
delete this.pendingCommands[message.request_id || '']
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
@ -1756,12 +1669,15 @@ export class EngineCommandManager extends EventTarget {
|
|||||||
pending &&
|
pending &&
|
||||||
message.success &&
|
message.success &&
|
||||||
(message.resp.type === 'modeling' ||
|
(message.resp.type === 'modeling' ||
|
||||||
message.resp.type === 'modeling_batch')
|
message.resp.type === 'modeling_batch' ||
|
||||||
|
message.resp.type === 'export')
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
if (
|
if (message.resp.type === 'export' && message.request_id) {
|
||||||
|
this.responseMap[message.request_id] = message.resp
|
||||||
|
} else if (
|
||||||
message.resp.type === 'modeling' &&
|
message.resp.type === 'modeling' &&
|
||||||
pending.command.type === 'modeling_cmd_req' &&
|
pending.command.type === 'modeling_cmd_req' &&
|
||||||
message.request_id
|
message.request_id
|
||||||
@ -2026,38 +1942,6 @@ export class EngineCommandManager extends EventTarget {
|
|||||||
this.outSequence++
|
this.outSequence++
|
||||||
this.engineConnection?.unreliableSend(command)
|
this.engineConnection?.unreliableSend(command)
|
||||||
return Promise.resolve(null)
|
return Promise.resolve(null)
|
||||||
} else if (cmd.type === 'export') {
|
|
||||||
const promise = new Promise<null>((resolve, reject) => {
|
|
||||||
if (this.exportInfo === null) {
|
|
||||||
if (this.exportInfo === null) {
|
|
||||||
toast.error('Export intent was not set, but export is being sent')
|
|
||||||
console.error('Export intent was not set, but export is being sent')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const toastId = toast.loading(
|
|
||||||
this.exportInfo.intent === ExportIntent.Save
|
|
||||||
? EXPORT_TOAST_MESSAGES.START
|
|
||||||
: MAKE_TOAST_MESSAGES.START
|
|
||||||
)
|
|
||||||
this.pendingExport = {
|
|
||||||
toastId,
|
|
||||||
resolve: (passThrough) => {
|
|
||||||
this.addCommandLog({
|
|
||||||
type: 'export-done',
|
|
||||||
data: null,
|
|
||||||
})
|
|
||||||
resolve(passThrough)
|
|
||||||
},
|
|
||||||
reject: (reason: string) => {
|
|
||||||
this.exportInfo = null
|
|
||||||
reject(reason)
|
|
||||||
},
|
|
||||||
commandId: command.cmd_id,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
this.engineConnection?.send(command)
|
|
||||||
return promise
|
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
command.cmd.type === 'default_camera_look_at' ||
|
command.cmd.type === 'default_camera_look_at' ||
|
||||||
@ -2090,7 +1974,7 @@ export class EngineCommandManager extends EventTarget {
|
|||||||
rangeStr: string,
|
rangeStr: string,
|
||||||
commandStr: string,
|
commandStr: string,
|
||||||
idToRangeStr: string
|
idToRangeStr: string
|
||||||
): Promise<string | void> {
|
): Promise<Uint8Array | void> {
|
||||||
if (this.engineConnection === undefined) return Promise.resolve()
|
if (this.engineConnection === undefined) return Promise.resolve()
|
||||||
if (
|
if (
|
||||||
!this.engineConnection?.isReady() &&
|
!this.engineConnection?.isReady() &&
|
||||||
@ -2118,7 +2002,7 @@ export class EngineCommandManager extends EventTarget {
|
|||||||
range,
|
range,
|
||||||
idToRangeMap,
|
idToRangeMap,
|
||||||
})
|
})
|
||||||
return JSON.stringify(resp[0])
|
return BSON.serialize(resp[0])
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Common send command function used for both modeling and scene commands
|
* Common send command function used for both modeling and scene commands
|
||||||
@ -2266,3 +2150,65 @@ function promiseFactory<T>() {
|
|||||||
})
|
})
|
||||||
return { promise, resolve, reject }
|
return { promise, resolve, reject }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a binary buffer to a UUID string.
|
||||||
|
*
|
||||||
|
* @param buffer - The binary buffer containing the UUID bytes.
|
||||||
|
* @returns A string representation of the UUID in the format 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'.
|
||||||
|
*/
|
||||||
|
function binaryToUuid(
|
||||||
|
binaryData: Buffer | Uint8Array | BSONBinary | string
|
||||||
|
): string {
|
||||||
|
if (typeof binaryData === 'string') {
|
||||||
|
return binaryData
|
||||||
|
}
|
||||||
|
|
||||||
|
let buffer: Uint8Array
|
||||||
|
|
||||||
|
// Handle MongoDB BSON Binary object
|
||||||
|
if (
|
||||||
|
binaryData &&
|
||||||
|
'_bsontype' in binaryData &&
|
||||||
|
binaryData._bsontype === 'Binary'
|
||||||
|
) {
|
||||||
|
// Extract the buffer from the BSON Binary object
|
||||||
|
buffer = binaryData.buffer
|
||||||
|
}
|
||||||
|
// Handle case where buffer property exists (some MongoDB drivers structure)
|
||||||
|
else if (binaryData && binaryData.buffer instanceof Uint8Array) {
|
||||||
|
buffer = binaryData.buffer
|
||||||
|
}
|
||||||
|
// Handle direct Buffer or Uint8Array
|
||||||
|
else if (binaryData instanceof Uint8Array || Buffer.isBuffer(binaryData)) {
|
||||||
|
buffer = binaryData
|
||||||
|
} else {
|
||||||
|
console.error(
|
||||||
|
'Invalid input type: expected MongoDB BSON Binary, Buffer, or Uint8Array'
|
||||||
|
)
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure we have exactly 16 bytes (128 bits) for a UUID
|
||||||
|
if (buffer.length !== 16) {
|
||||||
|
// For debugging
|
||||||
|
console.log('Buffer length:', buffer.length)
|
||||||
|
console.log('Buffer content:', Array.from(buffer))
|
||||||
|
console.error('UUID must be exactly 16 bytes')
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert each byte to a hex string and pad with zeros if needed
|
||||||
|
const hexValues = Array.from(buffer).map((byte) =>
|
||||||
|
byte.toString(16).padStart(2, '0')
|
||||||
|
)
|
||||||
|
|
||||||
|
// Format into UUID structure (8-4-4-4-12 characters)
|
||||||
|
return [
|
||||||
|
hexValues.slice(0, 4).join(''),
|
||||||
|
hexValues.slice(4, 6).join(''),
|
||||||
|
hexValues.slice(6, 8).join(''),
|
||||||
|
hexValues.slice(8, 10).join(''),
|
||||||
|
hexValues.slice(10, 16).join(''),
|
||||||
|
].join('-')
|
||||||
|
}
|
||||||
|
@ -1,26 +0,0 @@
|
|||||||
import { engineCommandManager } from 'lib/singletons'
|
|
||||||
import { type Models } from '@kittycad/lib'
|
|
||||||
import { uuidv4 } from 'lib/utils'
|
|
||||||
|
|
||||||
// Isolating a function to call the engine to export the current scene.
|
|
||||||
// Because it has given us trouble in automated testing environments.
|
|
||||||
export async function exportFromEngine({
|
|
||||||
format,
|
|
||||||
}: {
|
|
||||||
format: Models['OutputFormat_type']
|
|
||||||
}): Promise<Models['WebSocketResponse_type'] | null> {
|
|
||||||
let exportPromise = engineCommandManager.sendSceneCommand({
|
|
||||||
type: 'modeling_cmd_req',
|
|
||||||
cmd: {
|
|
||||||
type: 'export',
|
|
||||||
// By default let's leave this blank to export the whole scene.
|
|
||||||
// In the future we might want to let the user choose which entities
|
|
||||||
// in the scene to export. In that case, you'd pass the IDs thru here.
|
|
||||||
entity_ids: [],
|
|
||||||
format,
|
|
||||||
},
|
|
||||||
cmd_id: uuidv4(),
|
|
||||||
})
|
|
||||||
|
|
||||||
return exportPromise
|
|
||||||
}
|
|
@ -1,4 +1,3 @@
|
|||||||
import { deserialize_files } from '@rust/kcl-wasm-lib/pkg/kcl_wasm_lib'
|
|
||||||
import { MachineManager } from 'components/MachineManagerProvider'
|
import { MachineManager } from 'components/MachineManagerProvider'
|
||||||
import toast from 'react-hot-toast'
|
import toast from 'react-hot-toast'
|
||||||
import { components } from './machine-api'
|
import { components } from './machine-api'
|
||||||
@ -6,12 +5,17 @@ import ModelingAppFile from './modelingAppFile'
|
|||||||
import { MAKE_TOAST_MESSAGES } from './constants'
|
import { MAKE_TOAST_MESSAGES } from './constants'
|
||||||
|
|
||||||
// Make files locally from an export call.
|
// Make files locally from an export call.
|
||||||
export async function exportMake(
|
export async function exportMake({
|
||||||
data: ArrayBuffer,
|
files,
|
||||||
name: string,
|
name,
|
||||||
toastId: string,
|
toastId,
|
||||||
|
machineManager,
|
||||||
|
}: {
|
||||||
|
files: ModelingAppFile[]
|
||||||
|
name: string
|
||||||
|
toastId: string
|
||||||
machineManager: MachineManager
|
machineManager: MachineManager
|
||||||
): Promise<Response | null> {
|
}): Promise<Response | null> {
|
||||||
if (name === '') {
|
if (name === '') {
|
||||||
console.error(MAKE_TOAST_MESSAGES.NO_NAME)
|
console.error(MAKE_TOAST_MESSAGES.NO_NAME)
|
||||||
toast.error(MAKE_TOAST_MESSAGES.NO_NAME, { id: toastId })
|
toast.error(MAKE_TOAST_MESSAGES.NO_NAME, { id: toastId })
|
||||||
@ -50,10 +54,8 @@ export async function exportMake(
|
|||||||
job_name: name,
|
job_name: name,
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
console.log('params', params)
|
|
||||||
const formData = new FormData()
|
const formData = new FormData()
|
||||||
formData.append('params', JSON.stringify(params))
|
formData.append('params', JSON.stringify(params))
|
||||||
let files: ModelingAppFile[] = deserialize_files(new Uint8Array(data))
|
|
||||||
let file = files[0]
|
let file = files[0]
|
||||||
const fileBlob = new Blob([new Uint8Array(file.contents)], {
|
const fileBlob = new Blob([new Uint8Array(file.contents)], {
|
||||||
type: 'text/plain',
|
type: 'text/plain',
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import { isDesktop } from './isDesktop'
|
import { isDesktop } from './isDesktop'
|
||||||
import { deserialize_files } from '@rust/kcl-wasm-lib/pkg/kcl_wasm_lib'
|
|
||||||
import { browserSaveFile } from './browserSaveFile'
|
import { browserSaveFile } from './browserSaveFile'
|
||||||
|
|
||||||
import JSZip from 'jszip'
|
import JSZip from 'jszip'
|
||||||
@ -78,19 +77,14 @@ const save_ = async (file: ModelingAppFile, toastId: string) => {
|
|||||||
// Saves files locally from an export call.
|
// Saves files locally from an export call.
|
||||||
// We override the file's name with one passed in from the client side.
|
// We override the file's name with one passed in from the client side.
|
||||||
export async function exportSave({
|
export async function exportSave({
|
||||||
data,
|
files,
|
||||||
fileName,
|
fileName,
|
||||||
toastId,
|
toastId,
|
||||||
}: {
|
}: {
|
||||||
data: ArrayBuffer
|
files: ModelingAppFile[]
|
||||||
fileName: string
|
fileName: string
|
||||||
toastId: string
|
toastId: string
|
||||||
}) {
|
}) {
|
||||||
// This converts the ArrayBuffer to a Rust equivalent Vec<u8>.
|
|
||||||
let uintArray = new Uint8Array(data)
|
|
||||||
|
|
||||||
let files: ModelingAppFile[] = deserialize_files(uintArray)
|
|
||||||
|
|
||||||
if (files.length > 1) {
|
if (files.length > 1) {
|
||||||
let zip = new JSZip()
|
let zip = new JSZip()
|
||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import {
|
import {
|
||||||
emptyExecState,
|
|
||||||
errFromErrWithOutputs,
|
errFromErrWithOutputs,
|
||||||
ExecState,
|
ExecState,
|
||||||
execStateFromRust,
|
execStateFromRust,
|
||||||
@ -17,6 +16,10 @@ import { DefaultPlanes } from '@rust/kcl-lib/bindings/DefaultPlanes'
|
|||||||
import { DefaultPlaneStr, defaultPlaneStrToKey } from 'lib/planes'
|
import { DefaultPlaneStr, defaultPlaneStrToKey } from 'lib/planes'
|
||||||
import { err } from 'lib/trap'
|
import { err } from 'lib/trap'
|
||||||
import { EngineCommandManager } from 'lang/std/engineConnection'
|
import { EngineCommandManager } from 'lang/std/engineConnection'
|
||||||
|
import { OutputFormat3d } from '@rust/kcl-lib/bindings/ModelingCmd'
|
||||||
|
import ModelingAppFile from './modelingAppFile'
|
||||||
|
import toast from 'react-hot-toast'
|
||||||
|
import { KclError as RustKclError } from '@rust/kcl-lib/bindings/KclError'
|
||||||
|
|
||||||
export default class RustContext {
|
export default class RustContext {
|
||||||
private wasmInitFailed: boolean = true
|
private wasmInitFailed: boolean = true
|
||||||
@ -43,20 +46,22 @@ export default class RustContext {
|
|||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||||
this.ensureWasmInit().then(async () => {
|
this.ensureWasmInit().then(async () => {
|
||||||
await this.create()
|
this.ctxInstance = await this.create()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a new context instance
|
// Create a new context instance
|
||||||
async create() {
|
async create(): Promise<Context> {
|
||||||
this.rustInstance = getModule()
|
this.rustInstance = getModule()
|
||||||
// We need this await here, DO NOT REMOVE it even if your editor says it's
|
// We need this await here, DO NOT REMOVE it even if your editor says it's
|
||||||
// unnecessary. The constructor of the module is async and it will not
|
// unnecessary. The constructor of the module is async and it will not
|
||||||
// resolve if you don't await it.
|
// resolve if you don't await it.
|
||||||
this.ctxInstance = await new this.rustInstance.Context(
|
const ctxInstance = await new this.rustInstance.Context(
|
||||||
this.engineCommandManager,
|
this.engineCommandManager,
|
||||||
fileSystemManager
|
fileSystemManager
|
||||||
)
|
)
|
||||||
|
|
||||||
|
return ctxInstance
|
||||||
}
|
}
|
||||||
|
|
||||||
// Execute a program.
|
// Execute a program.
|
||||||
@ -65,11 +70,10 @@ export default class RustContext {
|
|||||||
settings: DeepPartial<Configuration>,
|
settings: DeepPartial<Configuration>,
|
||||||
path?: string
|
path?: string
|
||||||
): Promise<ExecState> {
|
): Promise<ExecState> {
|
||||||
await this._checkInstance()
|
const instance = await this._checkInstance()
|
||||||
|
|
||||||
if (this.ctxInstance) {
|
|
||||||
try {
|
try {
|
||||||
const result = await this.ctxInstance.execute(
|
const result = await instance.execute(
|
||||||
JSON.stringify(node),
|
JSON.stringify(node),
|
||||||
path,
|
path,
|
||||||
JSON.stringify(settings)
|
JSON.stringify(settings)
|
||||||
@ -88,10 +92,6 @@ export default class RustContext {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// You will never get here.
|
|
||||||
return Promise.reject(emptyExecState())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Execute a program with in mock mode.
|
// Execute a program with in mock mode.
|
||||||
async executeMock(
|
async executeMock(
|
||||||
node: Node<Program>,
|
node: Node<Program>,
|
||||||
@ -99,15 +99,14 @@ export default class RustContext {
|
|||||||
path?: string,
|
path?: string,
|
||||||
usePrevMemory?: boolean
|
usePrevMemory?: boolean
|
||||||
): Promise<ExecState> {
|
): Promise<ExecState> {
|
||||||
await this._checkInstance()
|
const instance = await this._checkInstance()
|
||||||
|
|
||||||
if (this.ctxInstance) {
|
|
||||||
try {
|
|
||||||
if (usePrevMemory === undefined) {
|
if (usePrevMemory === undefined) {
|
||||||
usePrevMemory = true
|
usePrevMemory = true
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await this.ctxInstance.executeMock(
|
try {
|
||||||
|
const result = await instance.executeMock(
|
||||||
JSON.stringify(node),
|
JSON.stringify(node),
|
||||||
path,
|
path,
|
||||||
JSON.stringify(settings),
|
JSON.stringify(settings),
|
||||||
@ -119,8 +118,24 @@ export default class RustContext {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// You will never get here.
|
// Export a scene to a file.
|
||||||
return Promise.reject(emptyExecState())
|
async export(
|
||||||
|
format: DeepPartial<OutputFormat3d>,
|
||||||
|
settings: DeepPartial<Configuration>,
|
||||||
|
toastId: string
|
||||||
|
): Promise<ModelingAppFile[] | undefined> {
|
||||||
|
const instance = await this._checkInstance()
|
||||||
|
|
||||||
|
try {
|
||||||
|
return await instance.export(
|
||||||
|
JSON.stringify(format),
|
||||||
|
JSON.stringify(settings)
|
||||||
|
)
|
||||||
|
} catch (e: any) {
|
||||||
|
const parsed: RustKclError = JSON.parse(e.toString())
|
||||||
|
toast.error(parsed.msg, { id: toastId })
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async waitForAllEngineCommands() {
|
async waitForAllEngineCommands() {
|
||||||
@ -169,11 +184,13 @@ export default class RustContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Helper to check if context instance exists
|
// Helper to check if context instance exists
|
||||||
private async _checkInstance() {
|
private async _checkInstance(): Promise<Context> {
|
||||||
if (!this.ctxInstance) {
|
if (!this.ctxInstance) {
|
||||||
// Create the context instance.
|
// Create the context instance.
|
||||||
await this.create()
|
this.ctxInstance = await this.create()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return this.ctxInstance
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clean up resources
|
// Clean up resources
|
||||||
|
@ -1283,14 +1283,12 @@ export const modelingMachine = setup({
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
Make: () => {},
|
|
||||||
'enable copilot': () => {},
|
'enable copilot': () => {},
|
||||||
'disable copilot': () => {},
|
'disable copilot': () => {},
|
||||||
'Set selection': () => {},
|
'Set selection': () => {},
|
||||||
'Set mouse state': () => {},
|
'Set mouse state': () => {},
|
||||||
'Set Segment Overlays': () => {},
|
'Set Segment Overlays': () => {},
|
||||||
'Center camera on selection': () => {},
|
'Center camera on selection': () => {},
|
||||||
'Engine export': () => {},
|
|
||||||
'Submit to Text-to-CAD API': () => {},
|
'Submit to Text-to-CAD API': () => {},
|
||||||
'Set sketchDetails': () => {},
|
'Set sketchDetails': () => {},
|
||||||
'sketch exit execute': () => {},
|
'sketch exit execute': () => {},
|
||||||
@ -2469,6 +2467,20 @@ export const modelingMachine = setup({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
|
exportFromEngine: fromPromise(
|
||||||
|
async ({}: { input?: ModelingCommandSchema['Export'] }) => {
|
||||||
|
return undefined as Error | undefined
|
||||||
|
}
|
||||||
|
),
|
||||||
|
makeFromEngine: fromPromise(
|
||||||
|
async ({}: {
|
||||||
|
input?: {
|
||||||
|
machineManager: MachineManager
|
||||||
|
} & ModelingCommandSchema['Make']
|
||||||
|
}) => {
|
||||||
|
return undefined as Error | undefined
|
||||||
|
}
|
||||||
|
),
|
||||||
},
|
},
|
||||||
// end actors
|
// end actors
|
||||||
}).createMachine({
|
}).createMachine({
|
||||||
@ -2528,17 +2540,13 @@ export const modelingMachine = setup({
|
|||||||
},
|
},
|
||||||
|
|
||||||
Export: {
|
Export: {
|
||||||
target: 'idle',
|
target: 'Exporting',
|
||||||
reenter: false,
|
|
||||||
guard: 'Has exportable geometry',
|
guard: 'Has exportable geometry',
|
||||||
actions: 'Engine export',
|
|
||||||
},
|
},
|
||||||
|
|
||||||
Make: {
|
Make: {
|
||||||
target: 'idle',
|
target: 'Making',
|
||||||
reenter: false,
|
|
||||||
guard: 'Has exportable geometry',
|
guard: 'Has exportable geometry',
|
||||||
actions: 'Make',
|
|
||||||
},
|
},
|
||||||
|
|
||||||
'Delete selection': {
|
'Delete selection': {
|
||||||
@ -3885,6 +3893,35 @@ export const modelingMachine = setup({
|
|||||||
onError: ['idle'],
|
onError: ['idle'],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
Exporting: {
|
||||||
|
invoke: {
|
||||||
|
src: 'exportFromEngine',
|
||||||
|
id: 'exportFromEngine',
|
||||||
|
input: ({ event }) => {
|
||||||
|
if (event.type !== 'Export') return undefined
|
||||||
|
return event.data
|
||||||
|
},
|
||||||
|
onDone: ['idle'],
|
||||||
|
onError: ['idle'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
Making: {
|
||||||
|
invoke: {
|
||||||
|
src: 'makeFromEngine',
|
||||||
|
id: 'makeFromEngine',
|
||||||
|
input: ({ event, context }) => {
|
||||||
|
if (event.type !== 'Make' || !context.machineManager) return undefined
|
||||||
|
return {
|
||||||
|
machineManager: context.machineManager,
|
||||||
|
...event.data,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onDone: ['idle'],
|
||||||
|
onError: ['idle'],
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
initial: 'idle',
|
initial: 'idle',
|
||||||
|
@ -3,6 +3,7 @@ import viteTsconfigPaths from 'vite-tsconfig-paths'
|
|||||||
import eslint from '@nabla/vite-plugin-eslint'
|
import eslint from '@nabla/vite-plugin-eslint'
|
||||||
import { defineConfig, configDefaults } from 'vitest/config'
|
import { defineConfig, configDefaults } from 'vitest/config'
|
||||||
import version from 'vite-plugin-package-version'
|
import version from 'vite-plugin-package-version'
|
||||||
|
import topLevelAwait from 'vite-plugin-top-level-await'
|
||||||
// @ts-ignore: No types available
|
// @ts-ignore: No types available
|
||||||
import { lezer } from '@lezer/generator/rollup'
|
import { lezer } from '@lezer/generator/rollup'
|
||||||
|
|
||||||
@ -62,7 +63,19 @@ const config = defineConfig({
|
|||||||
'@rust': '/rust',
|
'@rust': '/rust',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
plugins: [react(), viteTsconfigPaths(), eslint(), version(), lezer()],
|
plugins: [
|
||||||
|
react(),
|
||||||
|
viteTsconfigPaths(),
|
||||||
|
eslint(),
|
||||||
|
version(),
|
||||||
|
lezer(),
|
||||||
|
topLevelAwait({
|
||||||
|
// The export name of top-level await promise for each chunk module
|
||||||
|
promiseExportName: '__tla',
|
||||||
|
// The function to generate import names of top-level await promise in each chunk module
|
||||||
|
promiseImportName: (i) => `__tla_${i}`,
|
||||||
|
}),
|
||||||
|
],
|
||||||
worker: {
|
worker: {
|
||||||
plugins: () => [viteTsconfigPaths()],
|
plugins: () => [viteTsconfigPaths()],
|
||||||
},
|
},
|
||||||
|
@ -2,6 +2,7 @@ import type { ConfigEnv, UserConfig } from 'vite'
|
|||||||
import { defineConfig } from 'vite'
|
import { defineConfig } from 'vite'
|
||||||
import { pluginExposeRenderer } from './vite.base.config'
|
import { pluginExposeRenderer } from './vite.base.config'
|
||||||
import viteTsconfigPaths from 'vite-tsconfig-paths'
|
import viteTsconfigPaths from 'vite-tsconfig-paths'
|
||||||
|
import topLevelAwait from 'vite-plugin-top-level-await'
|
||||||
// @ts-ignore: No types available
|
// @ts-ignore: No types available
|
||||||
import { lezer } from '@lezer/generator/rollup'
|
import { lezer } from '@lezer/generator/rollup'
|
||||||
|
|
||||||
@ -18,7 +19,17 @@ export default defineConfig((env) => {
|
|||||||
build: {
|
build: {
|
||||||
outDir: `.vite/renderer/${name}`,
|
outDir: `.vite/renderer/${name}`,
|
||||||
},
|
},
|
||||||
plugins: [pluginExposeRenderer(name), viteTsconfigPaths(), lezer()],
|
plugins: [
|
||||||
|
pluginExposeRenderer(name),
|
||||||
|
viteTsconfigPaths(),
|
||||||
|
lezer(),
|
||||||
|
topLevelAwait({
|
||||||
|
// The export name of top-level await promise for each chunk module
|
||||||
|
promiseExportName: '__tla',
|
||||||
|
// The function to generate import names of top-level await promise in each chunk module
|
||||||
|
promiseImportName: (i) => `__tla_${i}`,
|
||||||
|
}),
|
||||||
|
],
|
||||||
worker: {
|
worker: {
|
||||||
plugins: () => [viteTsconfigPaths()],
|
plugins: () => [viteTsconfigPaths()],
|
||||||
},
|
},
|
||||||
|
105
yarn.lock
105
yarn.lock
@ -1995,6 +1995,11 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@codemirror/state" "^6.2.1"
|
"@codemirror/state" "^6.2.1"
|
||||||
|
|
||||||
|
"@rollup/plugin-virtual@^3.0.2":
|
||||||
|
version "3.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@rollup/plugin-virtual/-/plugin-virtual-3.0.2.tgz#17e17eeecb4c9fa1c0a6e72c9e5f66382fddbb82"
|
||||||
|
integrity sha512-10monEYsBp3scM4/ND4LNH5Rxvh3e/cVeL3jWTgZ2SrQ+BmUoQcopVQvnaMcOnykb1VkxUFuDAN+0FnpTFRy2A==
|
||||||
|
|
||||||
"@rollup/rollup-android-arm-eabi@4.29.1":
|
"@rollup/rollup-android-arm-eabi@4.29.1":
|
||||||
version "4.29.1"
|
version "4.29.1"
|
||||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.29.1.tgz#9bd38df6a29afb7f0336d988bc8112af0c8816c0"
|
resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.29.1.tgz#9bd38df6a29afb7f0336d988bc8112af0c8816c0"
|
||||||
@ -2105,6 +2110,87 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-4.6.0.tgz#3c7c9c46e678feefe7a2e5bb609d3dbd665ffb3f"
|
resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-4.6.0.tgz#3c7c9c46e678feefe7a2e5bb609d3dbd665ffb3f"
|
||||||
integrity sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==
|
integrity sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==
|
||||||
|
|
||||||
|
"@swc/core-darwin-arm64@1.11.11":
|
||||||
|
version "1.11.11"
|
||||||
|
resolved "https://registry.yarnpkg.com/@swc/core-darwin-arm64/-/core-darwin-arm64-1.11.11.tgz#e4b5fc99bab657f8f72217fd4976956faf4132b3"
|
||||||
|
integrity sha512-vJcjGVDB8cZH7zyOkC0AfpFYI/7GHKG0NSsH3tpuKrmoAXJyCYspKPGid7FT53EAlWreN7+Pew+bukYf5j+Fmg==
|
||||||
|
|
||||||
|
"@swc/core-darwin-x64@1.11.11":
|
||||||
|
version "1.11.11"
|
||||||
|
resolved "https://registry.yarnpkg.com/@swc/core-darwin-x64/-/core-darwin-x64-1.11.11.tgz#0f4e810a2cd9c2993a7ccc3b38d1f92ef49894d8"
|
||||||
|
integrity sha512-/N4dGdqEYvD48mCF3QBSycAbbQd3yoZ2YHSzYesQf8usNc2YpIhYqEH3sql02UsxTjEFOJSf1bxZABDdhbSl6A==
|
||||||
|
|
||||||
|
"@swc/core-linux-arm-gnueabihf@1.11.11":
|
||||||
|
version "1.11.11"
|
||||||
|
resolved "https://registry.yarnpkg.com/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.11.11.tgz#72b4b1e403bca37f051fd194eb0518cda83fad9f"
|
||||||
|
integrity sha512-hsBhKK+wVXdN3x9MrL5GW0yT8o9GxteE5zHAI2HJjRQel3HtW7m5Nvwaq+q8rwMf4YQRd8ydbvwl4iUOZx7i2Q==
|
||||||
|
|
||||||
|
"@swc/core-linux-arm64-gnu@1.11.11":
|
||||||
|
version "1.11.11"
|
||||||
|
resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.11.11.tgz#ea87e183ec53db9e121cca581cef538e9652193f"
|
||||||
|
integrity sha512-YOCdxsqbnn/HMPCNM6nrXUpSndLXMUssGTtzT7ffXqr7WuzRg2e170FVDVQFIkb08E7Ku5uOnnUVAChAJQbMOQ==
|
||||||
|
|
||||||
|
"@swc/core-linux-arm64-musl@1.11.11":
|
||||||
|
version "1.11.11"
|
||||||
|
resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.11.11.tgz#33db0f45b2286bbca9baf2ed84d1f2405c657600"
|
||||||
|
integrity sha512-nR2tfdQRRzwqR2XYw9NnBk9Fdvff/b8IiJzDL28gRR2QiJWLaE8LsRovtWrzCOYq6o5Uu9cJ3WbabWthLo4jLw==
|
||||||
|
|
||||||
|
"@swc/core-linux-x64-gnu@1.11.11":
|
||||||
|
version "1.11.11"
|
||||||
|
resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.11.11.tgz#4a1fe41baa968008bb0fffc7754fd6ee824e76e1"
|
||||||
|
integrity sha512-b4gBp5HA9xNWNC5gsYbdzGBJWx4vKSGybGMGOVWWuF+ynx10+0sA/o4XJGuNHm8TEDuNh9YLKf6QkIO8+GPJ1g==
|
||||||
|
|
||||||
|
"@swc/core-linux-x64-musl@1.11.11":
|
||||||
|
version "1.11.11"
|
||||||
|
resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.11.11.tgz#972d3530d740b3681191590ee08bb9ab7bb6706d"
|
||||||
|
integrity sha512-dEvqmQVswjNvMBwXNb8q5uSvhWrJLdttBSef3s6UC5oDSwOr00t3RQPzyS3n5qmGJ8UMTdPRmsopxmqaODISdg==
|
||||||
|
|
||||||
|
"@swc/core-win32-arm64-msvc@1.11.11":
|
||||||
|
version "1.11.11"
|
||||||
|
resolved "https://registry.yarnpkg.com/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.11.11.tgz#179846f1f9e3e806a4bf6d8f35af97f577c1a0b3"
|
||||||
|
integrity sha512-aZNZznem9WRnw2FbTqVpnclvl8Q2apOBW2B316gZK+qxbe+ktjOUnYaMhdCG3+BYggyIBDOnaJeQrXbKIMmNdw==
|
||||||
|
|
||||||
|
"@swc/core-win32-ia32-msvc@1.11.11":
|
||||||
|
version "1.11.11"
|
||||||
|
resolved "https://registry.yarnpkg.com/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.11.11.tgz#b098b72c1b45e237a9598b7b5e83e6c5ecb9ac69"
|
||||||
|
integrity sha512-DjeJn/IfjgOddmJ8IBbWuDK53Fqw7UvOz7kyI/728CSdDYC3LXigzj3ZYs4VvyeOt+ZcQZUB2HA27edOifomGw==
|
||||||
|
|
||||||
|
"@swc/core-win32-x64-msvc@1.11.11":
|
||||||
|
version "1.11.11"
|
||||||
|
resolved "https://registry.yarnpkg.com/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.11.11.tgz#1d5610c585b903b8c1f4a452725d77ac96f27e84"
|
||||||
|
integrity sha512-Gp/SLoeMtsU4n0uRoKDOlGrRC6wCfifq7bqLwSlAG8u8MyJYJCcwjg7ggm0rhLdC2vbiZ+lLVl3kkETp+JUvKg==
|
||||||
|
|
||||||
|
"@swc/core@^1.10.16":
|
||||||
|
version "1.11.11"
|
||||||
|
resolved "https://registry.yarnpkg.com/@swc/core/-/core-1.11.11.tgz#bac3256d7a113f0dd6965206cf428e826981cf0d"
|
||||||
|
integrity sha512-pCVY2Wn6dV/labNvssk9b3Owi4WOYsapcbWm90XkIj4xH/56Z6gzja9fsU+4MdPuEfC2Smw835nZHcdCFGyX6A==
|
||||||
|
dependencies:
|
||||||
|
"@swc/counter" "^0.1.3"
|
||||||
|
"@swc/types" "^0.1.19"
|
||||||
|
optionalDependencies:
|
||||||
|
"@swc/core-darwin-arm64" "1.11.11"
|
||||||
|
"@swc/core-darwin-x64" "1.11.11"
|
||||||
|
"@swc/core-linux-arm-gnueabihf" "1.11.11"
|
||||||
|
"@swc/core-linux-arm64-gnu" "1.11.11"
|
||||||
|
"@swc/core-linux-arm64-musl" "1.11.11"
|
||||||
|
"@swc/core-linux-x64-gnu" "1.11.11"
|
||||||
|
"@swc/core-linux-x64-musl" "1.11.11"
|
||||||
|
"@swc/core-win32-arm64-msvc" "1.11.11"
|
||||||
|
"@swc/core-win32-ia32-msvc" "1.11.11"
|
||||||
|
"@swc/core-win32-x64-msvc" "1.11.11"
|
||||||
|
|
||||||
|
"@swc/counter@^0.1.3":
|
||||||
|
version "0.1.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/@swc/counter/-/counter-0.1.3.tgz#cc7463bd02949611c6329596fccd2b0ec782b0e9"
|
||||||
|
integrity sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==
|
||||||
|
|
||||||
|
"@swc/types@^0.1.19":
|
||||||
|
version "0.1.19"
|
||||||
|
resolved "https://registry.yarnpkg.com/@swc/types/-/types-0.1.19.tgz#65d9fe81e0a1dc7e861ad698dd581abe3703a2d2"
|
||||||
|
integrity sha512-WkAZaAfj44kh/UFdAQcrMP1I0nwRqpt27u+08LMBYMqmQfwwMofYoMh/48NGkMMRfC4ynpfwRbJuu8ErfNloeA==
|
||||||
|
dependencies:
|
||||||
|
"@swc/counter" "^0.1.3"
|
||||||
|
|
||||||
"@szmarczak/http-timer@^4.0.5":
|
"@szmarczak/http-timer@^4.0.5":
|
||||||
version "4.0.6"
|
version "4.0.6"
|
||||||
resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-4.0.6.tgz#b4a914bb62e7c272d4e5989fe4440f812ab1d807"
|
resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-4.0.6.tgz#b4a914bb62e7c272d4e5989fe4440f812ab1d807"
|
||||||
@ -3284,6 +3370,11 @@ browserslist@^4.24.0, browserslist@^4.24.4:
|
|||||||
node-releases "^2.0.19"
|
node-releases "^2.0.19"
|
||||||
update-browserslist-db "^1.1.1"
|
update-browserslist-db "^1.1.1"
|
||||||
|
|
||||||
|
bson@^6.10.3:
|
||||||
|
version "6.10.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/bson/-/bson-6.10.3.tgz#5f9a463af6b83e264bedd08b236d1356a30eda47"
|
||||||
|
integrity sha512-MTxGsqgYTwfshYWTRdmZRC+M7FnG1b4y7RO7p2k3X24Wq0yv1m77Wsj0BzlPzd/IowgESfsruQCUToa7vbOpPQ==
|
||||||
|
|
||||||
buffer-crc32@~0.2.3:
|
buffer-crc32@~0.2.3:
|
||||||
version "0.2.13"
|
version "0.2.13"
|
||||||
resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242"
|
resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242"
|
||||||
@ -9249,6 +9340,11 @@ utrie@^1.0.2:
|
|||||||
dependencies:
|
dependencies:
|
||||||
base64-arraybuffer "^1.0.2"
|
base64-arraybuffer "^1.0.2"
|
||||||
|
|
||||||
|
uuid@^10.0.0:
|
||||||
|
version "10.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/uuid/-/uuid-10.0.0.tgz#5a95aa454e6e002725c79055fd42aaba30ca6294"
|
||||||
|
integrity sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==
|
||||||
|
|
||||||
uuid@^11.1.0:
|
uuid@^11.1.0:
|
||||||
version "11.1.0"
|
version "11.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/uuid/-/uuid-11.1.0.tgz#9549028be1753bb934fc96e2bca09bb4105ae912"
|
resolved "https://registry.yarnpkg.com/uuid/-/uuid-11.1.0.tgz#9549028be1753bb934fc96e2bca09bb4105ae912"
|
||||||
@ -9297,6 +9393,15 @@ vite-plugin-package-version@^1.1.0:
|
|||||||
resolved "https://registry.yarnpkg.com/vite-plugin-package-version/-/vite-plugin-package-version-1.1.0.tgz#7d8088955aa21e4ec93353c98992b3f58c4bf13c"
|
resolved "https://registry.yarnpkg.com/vite-plugin-package-version/-/vite-plugin-package-version-1.1.0.tgz#7d8088955aa21e4ec93353c98992b3f58c4bf13c"
|
||||||
integrity sha512-TPoFZXNanzcaKCIrC3e2L/TVRkkRLB6l4RPN/S7KbG7rWfyLcCEGsnXvxn6qR7fyZwXalnnSN/I9d6pSFjHpEA==
|
integrity sha512-TPoFZXNanzcaKCIrC3e2L/TVRkkRLB6l4RPN/S7KbG7rWfyLcCEGsnXvxn6qR7fyZwXalnnSN/I9d6pSFjHpEA==
|
||||||
|
|
||||||
|
vite-plugin-top-level-await@^1.5.0:
|
||||||
|
version "1.5.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/vite-plugin-top-level-await/-/vite-plugin-top-level-await-1.5.0.tgz#e3f76302921152bf29d1658f169d168f8937e78b"
|
||||||
|
integrity sha512-r/DtuvHrSqUVk23XpG2cl8gjt1aATMG5cjExXL1BUTcSNab6CzkcPua9BPEc9fuTP5UpwClCxUe3+dNGL0yrgQ==
|
||||||
|
dependencies:
|
||||||
|
"@rollup/plugin-virtual" "^3.0.2"
|
||||||
|
"@swc/core" "^1.10.16"
|
||||||
|
uuid "^10.0.0"
|
||||||
|
|
||||||
vite-tsconfig-paths@^4.3.2:
|
vite-tsconfig-paths@^4.3.2:
|
||||||
version "4.3.2"
|
version "4.3.2"
|
||||||
resolved "https://registry.yarnpkg.com/vite-tsconfig-paths/-/vite-tsconfig-paths-4.3.2.tgz#321f02e4b736a90ff62f9086467faf4e2da857a9"
|
resolved "https://registry.yarnpkg.com/vite-tsconfig-paths/-/vite-tsconfig-paths-4.3.2.tgz#321f02e4b736a90ff62f9086467faf4e2da857a9"
|
||||||
|
Reference in New Issue
Block a user