Add print button (#3133)
* add print button Signed-off-by: Jess Frazelle <github@jessfraz.com> * cleanup Signed-off-by: Jess Frazelle <github@jessfraz.com> * generate more types Signed-off-by: Jess Frazelle <github@jessfraz.com> * add a github action to generate machine api-types Signed-off-by: Jess Frazelle <github@jessfraz.com> * fix Signed-off-by: Jess Frazelle <github@jessfraz.com> * New machine-api types * actually print on the real machine Signed-off-by: Jess Frazelle <github@jessfraz.com> * A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu) * add more Signed-off-by: Jess Frazelle <github@jessfraz.com> * New machine-api types * get the current machine Signed-off-by: Jess Frazelle <github@jessfraz.com> * New machine-api types * know when error Signed-off-by: Jess Frazelle <github@jessfraz.com> * updates Signed-off-by: Jess Frazelle <github@jessfraz.com> * updates Signed-off-by: Jess Frazelle <github@jessfraz.com> * fmt Signed-off-by: Jess Frazelle <github@jessfraz.com> * add fmt Signed-off-by: Jess Frazelle <github@jessfraz.com> * New machine-api types * empty * empty * update machine api Signed-off-by: Jess Frazelle <github@jessfraz.com> * New machine-api types * empty * New machine-api types * emptuy * no circular deps Signed-off-by: Jess Frazelle <github@jessfraz.com> * New machine-api types * remove recursive dep Signed-off-by: Jess Frazelle <github@jessfraz.com> --------- Signed-off-by: Jess Frazelle <github@jessfraz.com> Co-authored-by: Jess Frazelle <github@jessfraz.com> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: Jess Frazelle <jessfraz@users.noreply.github.com>
This commit is contained in:
49
.github/workflows/generate-machine-api-types.yml
vendored
Normal file
49
.github/workflows/generate-machine-api-types.yml
vendored
Normal file
@ -0,0 +1,49 @@
|
||||
name: generate machine-api types
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- 'openapi/machine-api.json'
|
||||
- '.github/workflows/generate-machine-api-types.yml'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
jobs:
|
||||
generate:
|
||||
runs-on: 'ubuntu-latest'
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'yarn'
|
||||
- run: yarn install
|
||||
- run: yarn generate:machine-api
|
||||
- run: yarn fmt
|
||||
- name: check for changes
|
||||
id: git-check
|
||||
run: |
|
||||
git add .
|
||||
if git status | grep -q "Changes to be committed"
|
||||
then echo "modified=true" >> $GITHUB_OUTPUT
|
||||
else echo "modified=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
- name: Commit changes, if any
|
||||
if: steps.git-check.outputs.modified == 'true'
|
||||
run: |
|
||||
git add .
|
||||
git config --local user.email "github-actions[bot]@users.noreply.github.com"
|
||||
git config --local user.name "github-actions[bot]"
|
||||
git remote set-url origin https://${{ github.actor }}:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}.git
|
||||
git fetch origin
|
||||
echo ${{ github.head_ref }}
|
||||
git checkout ${{ github.head_ref }}
|
||||
git commit -am "New machine-api types" || true
|
||||
git push
|
||||
git push origin ${{ github.head_ref }}
|
||||
|
12
flake.lock
generated
12
flake.lock
generated
@ -2,11 +2,11 @@
|
||||
"nodes": {
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1718470082,
|
||||
"narHash": "sha256-u2F0MMYE+Efc+ocruTbtU/wWHuYHWcJafp5zJ++n/YE=",
|
||||
"lastModified": 1721933792,
|
||||
"narHash": "sha256-zYVwABlQnxpbaHMfX6Wt9jhyQstFYwN2XjleOJV3VVg=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "3027ba73dfef68eb555fc2fa97aed4e999e74f97",
|
||||
"rev": "2122a9b35b35719ad9a395fe783eabb092df01b1",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@ -43,11 +43,11 @@
|
||||
"nixpkgs": "nixpkgs_2"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1718681902,
|
||||
"narHash": "sha256-E/T7Ge6ayEQe7FVKMJqDBoHyLhRhjc6u9CmU8MyYfy0=",
|
||||
"lastModified": 1721960387,
|
||||
"narHash": "sha256-o21ax+745ETGXrcgc/yUuLw1SI77ymp3xEpJt+w/kks=",
|
||||
"owner": "oxalica",
|
||||
"repo": "rust-overlay",
|
||||
"rev": "16c8ad83297c278eebe740dea5491c1708960dd1",
|
||||
"rev": "9cbf831c5b20a53354fc12758abd05966f9f1699",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@ -57,6 +57,7 @@
|
||||
pkg-config
|
||||
|
||||
nodejs_22
|
||||
yarn
|
||||
]) ++ pkgs.lib.optionals pkgs.stdenv.isDarwin (with pkgs; [
|
||||
libiconv
|
||||
darwin.apple_sdk.frameworks.Security
|
||||
|
@ -87,7 +87,8 @@
|
||||
"bump-jsons": "echo \"$(jq --arg v \"$VERSION\" '.version=$v' package.json --indent 2)\" > package.json && echo \"$(jq --arg v \"$VERSION\" '.version=$v' src-tauri/tauri.conf.json --indent 2)\" > src-tauri/tauri.conf.json",
|
||||
"postinstall": "yarn xstate:typegen",
|
||||
"xstate:typegen": "yarn xstate typegen \"src/**/*.ts?(x)\"",
|
||||
"make:dev": "make dev"
|
||||
"make:dev": "make dev",
|
||||
"generate:machine-api": "npx openapi-typescript ./openapi/machine-api.json -o src/lib/machine-api.d.ts"
|
||||
},
|
||||
"prettier": {
|
||||
"trailingComma": "es5",
|
||||
|
59
src-tauri/Cargo.lock
generated
59
src-tauri/Cargo.lock
generated
@ -172,7 +172,9 @@ dependencies = [
|
||||
"kcl-lib",
|
||||
"kittycad",
|
||||
"log",
|
||||
"mdns-sd",
|
||||
"oauth2",
|
||||
"reqwest 0.12.4",
|
||||
"serde_json",
|
||||
"tauri",
|
||||
"tauri-build",
|
||||
@ -286,7 +288,7 @@ dependencies = [
|
||||
"futures-io",
|
||||
"futures-lite",
|
||||
"parking",
|
||||
"polling",
|
||||
"polling 3.7.0",
|
||||
"rustix",
|
||||
"slab",
|
||||
"tracing",
|
||||
@ -1570,6 +1572,17 @@ dependencies = [
|
||||
"miniz_oxide",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "flume"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "55ac459de2512911e4b674ce33cf20befaba382d05b62b008afc1c8b57cbf181"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
"spin",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fnv"
|
||||
version = "1.0.7"
|
||||
@ -2405,6 +2418,16 @@ dependencies = [
|
||||
"unicode-normalization",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "if-addrs"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cabb0019d51a643781ff15c9c8a3e5dedc365c47211270f4e8f82812fedd8f0a"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "image"
|
||||
version = "0.25.2"
|
||||
@ -2752,7 +2775,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0c2a198fb6b0eada2a8df47933734e6d35d350665a33a3593d7164fa52c75c19"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"windows-targets 0.52.5",
|
||||
"windows-targets 0.48.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2896,6 +2919,19 @@ version = "0.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94"
|
||||
|
||||
[[package]]
|
||||
name = "mdns-sd"
|
||||
version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "807457e493076539ff8f202806f9dc2eaa9f13f69701da7ed38eec7a9afd1616"
|
||||
dependencies = [
|
||||
"flume",
|
||||
"if-addrs",
|
||||
"log",
|
||||
"polling 2.8.0",
|
||||
"socket2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.7.2"
|
||||
@ -3635,6 +3671,22 @@ dependencies = [
|
||||
"miniz_oxide",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "polling"
|
||||
version = "2.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"bitflags 1.3.2",
|
||||
"cfg-if",
|
||||
"concurrent-queue",
|
||||
"libc",
|
||||
"log",
|
||||
"pin-project-lite",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "polling"
|
||||
version = "3.7.0"
|
||||
@ -4871,6 +4923,9 @@ name = "spin"
|
||||
version = "0.9.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
|
||||
dependencies = [
|
||||
"lock_api",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "stable_deref_trait"
|
||||
|
@ -18,7 +18,9 @@ anyhow = "1"
|
||||
kcl-lib = { version = "0.2", path = "../src/wasm-lib/kcl" }
|
||||
kittycad = "0.3.7"
|
||||
log = "0.4.21"
|
||||
mdns-sd = "0.11.1"
|
||||
oauth2 = "4.4.2"
|
||||
reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls"] }
|
||||
serde_json = "1.0"
|
||||
tauri = { version = "2.0.0-beta.23", features = [ "devtools", "unstable"] }
|
||||
tauri-plugin-cli = { version = "2.0.0-beta.7" }
|
||||
|
@ -370,6 +370,70 @@ fn show_in_folder(app: tauri::AppHandle, path: &str) -> Result<(), InvokeError>
|
||||
Ok(())
|
||||
}
|
||||
|
||||
const SERVICE_NAME: &str = "_machine-api._tcp.local.";
|
||||
|
||||
async fn find_machine_api() -> Result<Option<String>> {
|
||||
println!("Looking for machine API...");
|
||||
// Timeout if no response is received after 5 seconds.
|
||||
let timeout_duration = std::time::Duration::from_secs(5);
|
||||
|
||||
let mdns = mdns_sd::ServiceDaemon::new()?;
|
||||
|
||||
// Browse for a service type.
|
||||
let receiver = mdns.browse(SERVICE_NAME)?;
|
||||
let resp = tokio::time::timeout(
|
||||
timeout_duration,
|
||||
tokio::spawn(async move {
|
||||
while let Ok(event) = receiver.recv() {
|
||||
if let mdns_sd::ServiceEvent::ServiceResolved(info) = event {
|
||||
if let Some(addr) = info.get_addresses().iter().next() {
|
||||
return Some(format!("{}:{}", addr, info.get_port()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
|
||||
// Shut down.
|
||||
mdns.shutdown()?;
|
||||
|
||||
let Ok(Ok(Some(addr))) = resp else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
Ok(Some(addr))
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn get_machine_api_ip() -> Result<Option<String>, InvokeError> {
|
||||
let machine_api = find_machine_api().await.map_err(InvokeError::from_anyhow)?;
|
||||
|
||||
Ok(machine_api)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn list_machines() -> Result<String, InvokeError> {
|
||||
let machine_api = find_machine_api().await.map_err(InvokeError::from_anyhow)?;
|
||||
|
||||
let Some(machine_api) = machine_api else {
|
||||
// Empty array.
|
||||
return Ok("[]".to_string());
|
||||
};
|
||||
|
||||
let client = reqwest::Client::new();
|
||||
let response = client
|
||||
.get(format!("http://{}/machines", machine_api))
|
||||
.send()
|
||||
.await
|
||||
.map_err(|e| InvokeError::from_anyhow(e.into()))?;
|
||||
|
||||
let text = response.text().await.map_err(|e| InvokeError::from_anyhow(e.into()))?;
|
||||
Ok(text)
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn open_url_sync(app: &tauri::AppHandle, url: &url::Url) {
|
||||
log::debug!("Opening URL: {:?}", url);
|
||||
@ -417,6 +481,8 @@ fn main() -> Result<()> {
|
||||
read_project_settings_file,
|
||||
write_project_settings_file,
|
||||
rename_project_directory,
|
||||
get_machine_api_ip,
|
||||
list_machines
|
||||
])
|
||||
.plugin(tauri_plugin_cli::init())
|
||||
.plugin(tauri_plugin_deep_link::init())
|
||||
|
@ -124,7 +124,11 @@ function CommandBarHeader({ children }: React.PropsWithChildren<{}>) {
|
||||
4
|
||||
)
|
||||
) : typeof argValue === 'object' ? (
|
||||
JSON.stringify(argValue)
|
||||
arg.valueSummary ? (
|
||||
arg.valueSummary(argValue)
|
||||
) : (
|
||||
JSON.stringify(argValue)
|
||||
)
|
||||
) : (
|
||||
<em>{argValue}</em>
|
||||
)
|
||||
|
@ -541,6 +541,16 @@ const CustomIconMap = {
|
||||
/>
|
||||
</svg>
|
||||
),
|
||||
printer3d: (
|
||||
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M16 5H4V7.5H7V7V6H8H9H10V7V7.5H16V5ZM17 7.5V8.5V15V16V17H16V16H15H14H6H5H4V17H3V16V15V8.5V7.5V5V4H4H16H17V5V7.5ZM4 8.5V15H5V13.5V13H5.5H14.5H15V13.5V15H16V8.5H10V9H9V10L8.5 10.5L8 10V9H7V8.5H4ZM14 14V15H6V14H14ZM8 7H9V8H8V7Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
),
|
||||
polygon: (
|
||||
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
|
@ -10,6 +10,7 @@ import { coreDump } from 'lang/wasm'
|
||||
import toast from 'react-hot-toast'
|
||||
import { CoreDumpManager } from 'lib/coredump'
|
||||
import openWindow from 'lib/openWindow'
|
||||
import { NetworkMachineIndicator } from './NetworkMachineIndicator'
|
||||
|
||||
export function LowerRightControls({
|
||||
children,
|
||||
@ -100,6 +101,7 @@ export function LowerRightControls({
|
||||
Settings
|
||||
</Tooltip>
|
||||
</Link>
|
||||
<NetworkMachineIndicator className={linkOverrideClassName} />
|
||||
<NetworkHealthIndicator />
|
||||
<HelpMenu />
|
||||
</menu>
|
||||
|
@ -28,6 +28,7 @@ import {
|
||||
editorManager,
|
||||
sceneEntitiesManager,
|
||||
} from 'lib/singletons'
|
||||
import { machineManager } from 'lib/machineManager'
|
||||
import { useHotkeys } from 'react-hotkeys-hook'
|
||||
import { applyConstraintHorzVertDistance } from './Toolbar/SetHorzVertDistance'
|
||||
import {
|
||||
@ -77,6 +78,7 @@ import { err, trap } from 'lib/trap'
|
||||
import { useCommandsContext } from 'hooks/useCommandsContext'
|
||||
import { modelingMachineEvent } from 'editor/manager'
|
||||
import { hasValidFilletSelection } from 'lang/modifyAst/addFillet'
|
||||
import { ExportIntent } from 'lang/std/engineConnection'
|
||||
|
||||
type MachineContext<T extends AnyStateMachine> = {
|
||||
state: StateFrom<T>
|
||||
@ -351,8 +353,57 @@ export const ModelingMachineProvider = ({
|
||||
|
||||
return {}
|
||||
}),
|
||||
Make: async (_, event) => {
|
||||
if (event.type !== 'Make' || TEST) return
|
||||
// Check if we already have an export intent.
|
||||
if (engineCommandManager.exportIntent) {
|
||||
toast.error('Already exporting')
|
||||
return
|
||||
}
|
||||
// Set the export intent.
|
||||
engineCommandManager.exportIntent = ExportIntent.Make
|
||||
|
||||
console.log('making', event.data)
|
||||
// Set the current machine.
|
||||
machineManager.currentMachine = event.data.machine
|
||||
|
||||
const format: Models['OutputFormat_type'] = {
|
||||
type: 'stl',
|
||||
coords: {
|
||||
forward: {
|
||||
axis: 'y',
|
||||
direction: 'negative',
|
||||
},
|
||||
up: {
|
||||
axis: 'z',
|
||||
direction: 'positive',
|
||||
},
|
||||
},
|
||||
storage: 'ascii',
|
||||
units: defaultUnit.current,
|
||||
selection: { type: 'default_scene' },
|
||||
}
|
||||
|
||||
toast.promise(
|
||||
exportFromEngine({
|
||||
format: format,
|
||||
}),
|
||||
{
|
||||
loading: 'Starting print...',
|
||||
success: 'Started print successfully',
|
||||
error: 'Error while starting print',
|
||||
}
|
||||
)
|
||||
},
|
||||
'Engine export': async (_, event) => {
|
||||
if (event.type !== 'Export' || TEST) return
|
||||
if (engineCommandManager.exportIntent) {
|
||||
toast.error('Already exporting')
|
||||
return
|
||||
}
|
||||
// Set the export intent.
|
||||
engineCommandManager.exportIntent = ExportIntent.Save
|
||||
|
||||
console.log('exporting', event.data)
|
||||
const format = {
|
||||
...event.data,
|
||||
|
@ -13,6 +13,7 @@ import { CustomIconName } from 'components/CustomIcon'
|
||||
import { useCommandsContext } from 'hooks/useCommandsContext'
|
||||
import { IconDefinition } from '@fortawesome/free-solid-svg-icons'
|
||||
import { useKclContext } from 'lang/KclProvider'
|
||||
import { machineManager } from 'lib/machineManager'
|
||||
|
||||
interface ModelingSidebarProps {
|
||||
paneOpacity: '' | 'opacity-20' | 'opacity-40'
|
||||
@ -45,7 +46,30 @@ export function ModelingSidebar({ paneOpacity }: ModelingSidebarProps) {
|
||||
data: { name: 'Export', groupId: 'modeling' },
|
||||
}),
|
||||
},
|
||||
{
|
||||
id: 'make',
|
||||
title: 'Make part',
|
||||
icon: 'printer3d',
|
||||
iconClassName: '!p-0',
|
||||
keybinding: 'Ctrl + Shift + M',
|
||||
action: async () => {
|
||||
commandBarSend({
|
||||
type: 'Find and select command',
|
||||
data: { name: 'Make', groupId: 'modeling' },
|
||||
})
|
||||
},
|
||||
hide: () => machineManager.machineCount() === 0,
|
||||
hideOnPlatform: 'web',
|
||||
},
|
||||
]
|
||||
const filteredActions: SidebarAction[] = sidebarActions.filter(
|
||||
(action) =>
|
||||
(!action.hide || (action.hide instanceof Function && !action.hide())) &&
|
||||
(!action.hideOnPlatform ||
|
||||
(isTauri()
|
||||
? action.hideOnPlatform === 'web'
|
||||
: action.hideOnPlatform === 'desktop'))
|
||||
)
|
||||
|
||||
// // Filter out the debug panel if it's not supposed to be shown
|
||||
// // TODO: abstract out for allowing user to configure which panes to show
|
||||
@ -135,23 +159,30 @@ export function ModelingSidebar({ paneOpacity }: ModelingSidebarProps) {
|
||||
/>
|
||||
))}
|
||||
</ul>
|
||||
<hr className="w-full border-chalkboard-20 dark:border-chalkboard-80" />
|
||||
<ul id="sidebar-actions" className="w-fit p-2 flex flex-col gap-2">
|
||||
{sidebarActions.map((action) => (
|
||||
<ModelingPaneButton
|
||||
key={action.id}
|
||||
paneConfig={{
|
||||
id: action.id,
|
||||
title: action.title,
|
||||
icon: action.icon,
|
||||
keybinding: action.keybinding,
|
||||
iconClassName: action.iconClassName,
|
||||
iconSize: 'md',
|
||||
}}
|
||||
onClick={action.action}
|
||||
/>
|
||||
))}
|
||||
</ul>
|
||||
{filteredActions.length > 0 && (
|
||||
<>
|
||||
<hr className="w-full border-chalkboard-20 dark:border-chalkboard-80" />
|
||||
<ul
|
||||
id="sidebar-actions"
|
||||
className="w-fit p-2 flex flex-col gap-2"
|
||||
>
|
||||
{filteredActions.map((action) => (
|
||||
<ModelingPaneButton
|
||||
key={action.id}
|
||||
paneConfig={{
|
||||
id: action.id,
|
||||
title: action.title,
|
||||
icon: action.icon,
|
||||
keybinding: action.keybinding,
|
||||
iconClassName: action.iconClassName,
|
||||
iconSize: 'md',
|
||||
}}
|
||||
onClick={action.action}
|
||||
/>
|
||||
))}
|
||||
</ul>
|
||||
</>
|
||||
)}
|
||||
</ul>
|
||||
<ul
|
||||
id="pane-section"
|
||||
@ -277,4 +308,5 @@ export type SidebarAction = {
|
||||
keybinding: string
|
||||
action: () => void
|
||||
hideOnPlatform?: 'desktop' | 'web'
|
||||
hide?: boolean | (() => boolean)
|
||||
}
|
||||
|
@ -103,8 +103,8 @@ export const NetworkHealthIndicator = () => {
|
||||
'rounded-sm ' + overallConnectionStateColor[overallState].bg
|
||||
}
|
||||
/>
|
||||
<Tooltip position="top-right">
|
||||
Network Health ({NETWORK_HEALTH_TEXT[overallState]})
|
||||
<Tooltip position="top-right" wrapperClassName="ui-open:hidden">
|
||||
Network health ({NETWORK_HEALTH_TEXT[overallState]})
|
||||
</Tooltip>
|
||||
</Popover.Button>
|
||||
<Popover.Panel
|
||||
|
62
src/components/NetworkMachineIndicator.tsx
Normal file
62
src/components/NetworkMachineIndicator.tsx
Normal file
@ -0,0 +1,62 @@
|
||||
import { Popover } from '@headlessui/react'
|
||||
import Tooltip from './Tooltip'
|
||||
import { machineManager } from 'lib/machineManager'
|
||||
import { isTauri } from 'lib/isTauri'
|
||||
import { CustomIcon } from './CustomIcon'
|
||||
|
||||
export const NetworkMachineIndicator = ({
|
||||
className,
|
||||
}: {
|
||||
className?: string
|
||||
}) => {
|
||||
const machineCount = Object.keys(machineManager.machines).length
|
||||
return isTauri() ? (
|
||||
<Popover className="relative">
|
||||
<Popover.Button
|
||||
className={
|
||||
'flex items-center p-0 border-none bg-transparent dark:bg-transparent relative ' +
|
||||
(className || '')
|
||||
}
|
||||
data-testid="network-machine-toggle"
|
||||
>
|
||||
<CustomIcon name="printer3d" className="w-5 h-5" />
|
||||
{machineCount > 0 && (
|
||||
<p aria-hidden className="flex items-center justify-center text-xs">
|
||||
{machineCount}
|
||||
</p>
|
||||
)}
|
||||
<Tooltip position="top-right" wrapperClassName="ui-open:hidden">
|
||||
Network machines ({machineCount})
|
||||
</Tooltip>
|
||||
</Popover.Button>
|
||||
<Popover.Panel
|
||||
className="absolute right-0 left-auto bottom-full mb-1 w-64 flex flex-col gap-1 align-stretch bg-chalkboard-10 dark:bg-chalkboard-90 rounded shadow-lg border border-solid border-chalkboard-20/50 dark:border-chalkboard-80/50 text-sm"
|
||||
data-testid="network-popover"
|
||||
>
|
||||
<div className="flex items-center justify-between p-2 rounded-t-sm bg-chalkboard-20 dark:bg-chalkboard-80">
|
||||
<h2 className="text-sm font-sans font-normal">Network machines</h2>
|
||||
<p
|
||||
data-testid="network"
|
||||
className="font-bold text-xs uppercase px-2 py-1 rounded-sm"
|
||||
>
|
||||
{machineCount}
|
||||
</p>
|
||||
</div>
|
||||
{machineCount > 0 && (
|
||||
<ul className="divide-y divide-chalkboard-20 dark:divide-chalkboard-80">
|
||||
{Object.entries(machineManager.machines).map(
|
||||
([hostname, machine]) => (
|
||||
<li key={hostname} className={'px-2 py-4 gap-1 last:mb-0 '}>
|
||||
<p className="">{machine.model || machine.manufacturer}</p>
|
||||
<p className="text-chalkboard-60 dark:text-chalkboard-50 text-xs">
|
||||
Hostname {hostname}
|
||||
</p>
|
||||
</li>
|
||||
)
|
||||
)}
|
||||
</ul>
|
||||
)}
|
||||
</Popover.Panel>
|
||||
</Popover>
|
||||
) : null
|
||||
}
|
@ -12,6 +12,7 @@ import { useCommandsContext } from 'hooks/useCommandsContext'
|
||||
import { CustomIcon } from './CustomIcon'
|
||||
import { useLspContext } from './LspProvider'
|
||||
import { engineCommandManager } from 'lib/singletons'
|
||||
import { machineManager } from 'lib/machineManager'
|
||||
import usePlatform from 'hooks/usePlatform'
|
||||
import { useAbsoluteFilePath } from 'hooks/useAbsoluteFilePath'
|
||||
import Tooltip from './Tooltip'
|
||||
@ -90,12 +91,14 @@ function ProjectMenuPopover({
|
||||
const { commandBarState, commandBarSend } = useCommandsContext()
|
||||
const { onProjectClose } = useLspContext()
|
||||
const exportCommandInfo = { name: 'Export', groupId: 'modeling' }
|
||||
const makeCommandInfo = { name: 'Make', groupId: 'modeling' }
|
||||
const findCommand = (obj: { name: string; groupId: string }) =>
|
||||
Boolean(
|
||||
commandBarState.context.commands.find(
|
||||
(c) => c.name === obj.name && c.groupId === obj.groupId
|
||||
)
|
||||
)
|
||||
const machineCount = machineManager.machineCount()
|
||||
|
||||
// We filter this memoized list so that no orphan "break" elements are rendered.
|
||||
const projectMenuItems = useMemo<(ActionButtonProps | 'break')[]>(
|
||||
@ -144,6 +147,32 @@ function ProjectMenuPopover({
|
||||
}),
|
||||
},
|
||||
'break',
|
||||
{
|
||||
id: 'make',
|
||||
Element: 'button',
|
||||
className: !isTauri() ? 'hidden' : '',
|
||||
children: (
|
||||
<>
|
||||
<span>Make current part</span>
|
||||
{!findCommand(makeCommandInfo) && (
|
||||
<Tooltip
|
||||
position="right"
|
||||
wrapperClassName="!max-w-none min-w-fit"
|
||||
>
|
||||
Awaiting engine connection
|
||||
</Tooltip>
|
||||
)}
|
||||
</>
|
||||
),
|
||||
disabled: !findCommand(makeCommandInfo) || machineCount === 0,
|
||||
onClick: () => {
|
||||
commandBarSend({
|
||||
type: 'Find and select command',
|
||||
data: makeCommandInfo,
|
||||
})
|
||||
},
|
||||
},
|
||||
'break',
|
||||
{
|
||||
id: 'go-home',
|
||||
Element: 'button',
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 373 KiB After Width: | Height: | Size: 374 KiB |
@ -13,6 +13,8 @@ import {
|
||||
createArtifactGraph,
|
||||
} from 'lang/std/artifactGraph'
|
||||
import { useModelingContext } from 'hooks/useModelingContext'
|
||||
import { exportMake } from 'lib/exportMake'
|
||||
import toast from 'react-hot-toast'
|
||||
|
||||
// TODO(paultag): This ought to be tweakable.
|
||||
const pingIntervalMs = 10000
|
||||
@ -30,6 +32,11 @@ interface NewTrackArgs {
|
||||
mediaStream: MediaStream
|
||||
}
|
||||
|
||||
export enum ExportIntent {
|
||||
Save = 'save',
|
||||
Make = 'make',
|
||||
}
|
||||
|
||||
type ClientMetrics = Models['ClientMetrics_type']
|
||||
|
||||
interface WebRTCClientMetrics extends ClientMetrics {
|
||||
@ -1153,6 +1160,12 @@ export class EngineCommandManager extends EventTarget {
|
||||
reject: (reason: any) => void
|
||||
commandId: string
|
||||
}
|
||||
/**
|
||||
* Export intent traxcks 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 _exportIntent: ExportIntent | null = null
|
||||
_commandLogCallBack: (command: CommandLog[]) => void = () => {}
|
||||
resolveReady = () => {}
|
||||
/** Folks should realize that wait for ready does not get called _everytime_
|
||||
@ -1205,6 +1218,14 @@ export class EngineCommandManager extends EventTarget {
|
||||
modelingSend: ReturnType<typeof useModelingContext>['send'] =
|
||||
(() => {}) as any
|
||||
|
||||
set exportIntent(intent: ExportIntent | null) {
|
||||
this._exportIntent = intent
|
||||
}
|
||||
|
||||
get exportIntent() {
|
||||
return this._exportIntent
|
||||
}
|
||||
|
||||
start({
|
||||
disableWebRTC = false,
|
||||
setMediaStream,
|
||||
@ -1382,9 +1403,36 @@ export class EngineCommandManager extends EventTarget {
|
||||
// because in all other cases we send JSON strings. But in the case of
|
||||
// export we send a binary blob.
|
||||
// Pass this to our export function.
|
||||
exportSave(event.data).then(() => {
|
||||
this.pendingExport?.resolve(null)
|
||||
}, this.pendingExport?.reject)
|
||||
if (this.exportIntent === null) {
|
||||
toast.error(
|
||||
'Export intent was not set, but export data was received'
|
||||
)
|
||||
console.error(
|
||||
'Export intent was not set, but export data was received'
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
switch (this.exportIntent) {
|
||||
case ExportIntent.Save: {
|
||||
exportSave(event.data).then(() => {
|
||||
this.pendingExport?.resolve(null)
|
||||
}, this.pendingExport?.reject)
|
||||
break
|
||||
}
|
||||
case ExportIntent.Make: {
|
||||
exportMake(event.data).then((result) => {
|
||||
if (result) {
|
||||
this.pendingExport?.resolve(null)
|
||||
} else {
|
||||
this.pendingExport?.reject('Failed to make export')
|
||||
}
|
||||
}, this.pendingExport?.reject)
|
||||
break
|
||||
}
|
||||
}
|
||||
// Set the export intent back to null.
|
||||
this.exportIntent = null
|
||||
return
|
||||
}
|
||||
|
||||
@ -1688,7 +1736,13 @@ export class EngineCommandManager extends EventTarget {
|
||||
return Promise.resolve(null)
|
||||
} else if (cmd.type === 'export') {
|
||||
const promise = new Promise<null>((resolve, reject) => {
|
||||
this.pendingExport = { resolve, reject, commandId: command.cmd_id }
|
||||
this.pendingExport = {
|
||||
resolve,
|
||||
reject: () => {
|
||||
this.exportIntent = null
|
||||
},
|
||||
commandId: command.cmd_id,
|
||||
}
|
||||
})
|
||||
this.engineConnection?.send(command)
|
||||
return promise
|
||||
|
@ -1,7 +1,9 @@
|
||||
import { Models } from '@kittycad/lib'
|
||||
import { StateMachineCommandSetConfig, KclCommandValue } from 'lib/commandTypes'
|
||||
import { KCL_DEFAULT_LENGTH } from 'lib/constants'
|
||||
import { components } from 'lib/machine-api'
|
||||
import { Selections } from 'lib/selections'
|
||||
import { machineManager } from 'lib/machineManager'
|
||||
import { modelingMachine, SketchTool } from 'machines/modelingMachine'
|
||||
|
||||
type OutputFormat = Models['OutputFormat_type']
|
||||
@ -22,6 +24,9 @@ export type ModelingCommandSchema = {
|
||||
type: OutputTypeKey
|
||||
storage?: StorageUnion
|
||||
}
|
||||
Make: {
|
||||
machine: components['schemas']['Machine']
|
||||
}
|
||||
Extrude: {
|
||||
selection: Selections // & { type: 'face' } would be cool to lock that down
|
||||
// result: (typeof EXTRUSION_RESULTS)[number]
|
||||
@ -160,6 +165,36 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
|
||||
},
|
||||
},
|
||||
},
|
||||
Make: {
|
||||
hide: 'web',
|
||||
displayName: 'Make',
|
||||
description:
|
||||
'Export the current part and send to a 3D printer on the network.',
|
||||
icon: 'printer3d',
|
||||
needsReview: true,
|
||||
args: {
|
||||
machine: {
|
||||
inputType: 'options',
|
||||
required: true,
|
||||
valueSummary: (machine: components['schemas']['Machine']) =>
|
||||
machine.model || machine.manufacturer,
|
||||
options: () => {
|
||||
return Object.entries(machineManager.machines).map(
|
||||
([hostname, machine]) => ({
|
||||
name: `${machine.model || machine.manufacturer}, ${hostname}`,
|
||||
isCurrent: false,
|
||||
value: machine as components['schemas']['Machine'],
|
||||
})
|
||||
)
|
||||
},
|
||||
defaultValue: () => {
|
||||
return Object.values(
|
||||
machineManager.machines
|
||||
)[0] as components['schemas']['Machine']
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Extrude: {
|
||||
description: 'Pull a sketch into 3D along its normal or perpendicular.',
|
||||
icon: 'extrude',
|
||||
|
@ -111,6 +111,10 @@ export type CommandArgumentConfig<
|
||||
machineContext?: C
|
||||
) => boolean)
|
||||
skip?: boolean
|
||||
/** For showing a summary display of the current value, such as in
|
||||
* the command bar's header
|
||||
*/
|
||||
valueSummary?: (value: OutputType) => string
|
||||
} & (
|
||||
| {
|
||||
inputType: 'options'
|
||||
@ -172,6 +176,10 @@ export type CommandArgument<
|
||||
) => boolean)
|
||||
skip?: boolean
|
||||
machineActor: InterpreterFrom<T>
|
||||
/** For showing a summary display of the current value, such as in
|
||||
* the command bar's header
|
||||
*/
|
||||
valueSummary?: (value: OutputType) => string
|
||||
} & (
|
||||
| {
|
||||
inputType: Extract<CommandInputType, 'options'>
|
||||
|
@ -52,17 +52,22 @@ export function createMachineCommand<
|
||||
return null
|
||||
} else if (commandConfig instanceof Array) {
|
||||
return commandConfig
|
||||
.map((config) =>
|
||||
createMachineCommand({
|
||||
.map((config) => {
|
||||
const recursiveCommandBarConfig: Partial<
|
||||
StateMachineCommandSetConfig<T, S>
|
||||
> = {
|
||||
[type]: config,
|
||||
}
|
||||
return createMachineCommand({
|
||||
groupId,
|
||||
type,
|
||||
state,
|
||||
send,
|
||||
actor,
|
||||
commandBarConfig: { [type]: config },
|
||||
commandBarConfig: recursiveCommandBarConfig,
|
||||
onCancel,
|
||||
})
|
||||
)
|
||||
})
|
||||
.filter((c) => c !== null) as Command<T, typeof type, S[typeof type]>[]
|
||||
}
|
||||
|
||||
@ -145,6 +150,7 @@ export function buildCommandArgument<
|
||||
required: arg.required,
|
||||
skip: arg.skip,
|
||||
machineActor,
|
||||
valueSummary: arg.valueSummary,
|
||||
} satisfies Omit<CommandArgument<O, T>, 'inputType'>
|
||||
|
||||
if (arg.inputType === 'options') {
|
||||
|
74
src/lib/exportMake.ts
Normal file
74
src/lib/exportMake.ts
Normal file
@ -0,0 +1,74 @@
|
||||
import { deserialize_files } from 'wasm-lib/pkg/wasm_lib'
|
||||
import { machineManager } from './machineManager'
|
||||
import toast from 'react-hot-toast'
|
||||
import { components } from './machine-api'
|
||||
import ModelingAppFile from './modelingAppFile'
|
||||
|
||||
// Make files locally from an export call.
|
||||
export async function exportMake(data: ArrayBuffer): Promise<Response | null> {
|
||||
if (machineManager.machineCount() === 0) {
|
||||
console.error('No machines available')
|
||||
toast.error('No machines available')
|
||||
return null
|
||||
}
|
||||
|
||||
const machineApiIp = machineManager.machineApiIp
|
||||
if (!machineApiIp) {
|
||||
console.error('No machine api ip available')
|
||||
toast.error('No machine api ip available')
|
||||
return null
|
||||
}
|
||||
|
||||
const currentMachine = machineManager.currentMachine
|
||||
if (!currentMachine) {
|
||||
console.error('No current machine available')
|
||||
toast.error('No current machine available')
|
||||
return null
|
||||
}
|
||||
|
||||
let machineId = null
|
||||
if ('id' in currentMachine) {
|
||||
machineId = currentMachine.id
|
||||
} else if ('hostname' in currentMachine && currentMachine.hostname) {
|
||||
machineId = currentMachine.hostname
|
||||
} else if ('ip' in currentMachine && currentMachine.ip) {
|
||||
machineId = currentMachine.ip
|
||||
}
|
||||
|
||||
if (!machineId) {
|
||||
console.error('No machine id available', currentMachine)
|
||||
toast.error('No machine id available')
|
||||
return null
|
||||
}
|
||||
|
||||
const params: components['schemas']['PrintParameters'] = {
|
||||
machine_id: machineId,
|
||||
job_name: 'Exported Job', // TODO: make this the project name.
|
||||
}
|
||||
try {
|
||||
console.log('params', params)
|
||||
const formData = new FormData()
|
||||
formData.append('params', JSON.stringify(params))
|
||||
let files: ModelingAppFile[] = deserialize_files(new Uint8Array(data))
|
||||
let file = files[0]
|
||||
const fileBlob = new Blob([new Uint8Array(file.contents)], {
|
||||
type: 'text/plain',
|
||||
})
|
||||
formData.append('file', fileBlob, file.name)
|
||||
console.log('formData', formData)
|
||||
|
||||
const response = await fetch('http://' + machineApiIp + '/print', {
|
||||
mode: 'no-cors',
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
})
|
||||
|
||||
console.log('response', response)
|
||||
|
||||
return response
|
||||
} catch (error) {
|
||||
console.error('Error exporting', error)
|
||||
toast.error('Error exporting')
|
||||
return null
|
||||
}
|
||||
}
|
@ -5,11 +5,7 @@ import { save } from '@tauri-apps/plugin-dialog'
|
||||
import { writeFile } from '@tauri-apps/plugin-fs'
|
||||
|
||||
import JSZip from 'jszip'
|
||||
|
||||
interface ModelingAppFile {
|
||||
name: string
|
||||
contents: number[]
|
||||
}
|
||||
import ModelingAppFile from './modelingAppFile'
|
||||
|
||||
const save_ = async (file: ModelingAppFile) => {
|
||||
try {
|
||||
@ -51,7 +47,7 @@ const save_ = async (file: ModelingAppFile) => {
|
||||
}
|
||||
} catch (e) {
|
||||
// TODO: do something real with the error.
|
||||
console.log('export error', e)
|
||||
console.error('export error', e)
|
||||
}
|
||||
}
|
||||
|
||||
|
925
src/lib/machine-api.d.ts
vendored
Normal file
925
src/lib/machine-api.d.ts
vendored
Normal file
@ -0,0 +1,925 @@
|
||||
/**
|
||||
* This file was auto-generated by openapi-typescript.
|
||||
* Do not make direct changes to the file.
|
||||
*/
|
||||
|
||||
export interface paths {
|
||||
'/': {
|
||||
parameters: {
|
||||
query?: never
|
||||
header?: never
|
||||
path?: never
|
||||
cookie?: never
|
||||
}
|
||||
/** Return the OpenAPI schema in JSON format. */
|
||||
get: operations['api_get_schema']
|
||||
put?: never
|
||||
post?: never
|
||||
delete?: never
|
||||
options?: never
|
||||
head?: never
|
||||
patch?: never
|
||||
trace?: never
|
||||
}
|
||||
'/machines': {
|
||||
parameters: {
|
||||
query?: never
|
||||
header?: never
|
||||
path?: never
|
||||
cookie?: never
|
||||
}
|
||||
/** List available machines and their statuses */
|
||||
get: operations['get_machines']
|
||||
put?: never
|
||||
post?: never
|
||||
delete?: never
|
||||
options?: never
|
||||
head?: never
|
||||
patch?: never
|
||||
trace?: never
|
||||
}
|
||||
'/machines/{id}': {
|
||||
parameters: {
|
||||
query?: never
|
||||
header?: never
|
||||
path?: never
|
||||
cookie?: never
|
||||
}
|
||||
/** Get the status of a specific machine */
|
||||
get: operations['get_machine']
|
||||
put?: never
|
||||
post?: never
|
||||
delete?: never
|
||||
options?: never
|
||||
head?: never
|
||||
patch?: never
|
||||
trace?: never
|
||||
}
|
||||
'/ping': {
|
||||
parameters: {
|
||||
query?: never
|
||||
header?: never
|
||||
path?: never
|
||||
cookie?: never
|
||||
}
|
||||
/** Return pong. */
|
||||
get: operations['ping']
|
||||
put?: never
|
||||
post?: never
|
||||
delete?: never
|
||||
options?: never
|
||||
head?: never
|
||||
patch?: never
|
||||
trace?: never
|
||||
}
|
||||
'/print': {
|
||||
parameters: {
|
||||
query?: never
|
||||
header?: never
|
||||
path?: never
|
||||
cookie?: never
|
||||
}
|
||||
get?: never
|
||||
put?: never
|
||||
/** Print a given file. File must be a sliceable 3D model. */
|
||||
post: operations['print_file']
|
||||
delete?: never
|
||||
options?: never
|
||||
head?: never
|
||||
patch?: never
|
||||
trace?: never
|
||||
}
|
||||
}
|
||||
export type webhooks = Record<string, never>
|
||||
export interface components {
|
||||
schemas: {
|
||||
/** @description The type of accessory. */
|
||||
AccessoryType: 'none'
|
||||
/** @description Error information from a response. */
|
||||
Error: {
|
||||
error_code?: string
|
||||
message: string
|
||||
request_id: string
|
||||
}
|
||||
/** @description An info command. */
|
||||
Info: {
|
||||
/** @enum {string} */
|
||||
command: 'get_version'
|
||||
/** @description The info module. */
|
||||
module: components['schemas']['InfoModule'][]
|
||||
/** @description The reason of the info command. */
|
||||
reason?: components['schemas']['Reason'] | null
|
||||
/** @description The result of the info command. */
|
||||
result: components['schemas']['Result']
|
||||
/** @description The sequence id. */
|
||||
sequence_id: components['schemas']['SequenceId']
|
||||
} & {
|
||||
[key: string]: unknown
|
||||
}
|
||||
/** @description An info module. */
|
||||
InfoModule: {
|
||||
/** @description The hardware version. */
|
||||
hw_ver: string
|
||||
/** @description The loader version. */
|
||||
loader_ver?: string | null
|
||||
/** @description The module name. */
|
||||
name: string
|
||||
/** @description The ota version. */
|
||||
ota_ver?: string | null
|
||||
/** @description The project name. */
|
||||
project_name?: string | null
|
||||
/** @description The serial number. */
|
||||
sn: string
|
||||
/** @description The software version. */
|
||||
sw_ver: string
|
||||
}
|
||||
/** @description The mode for the led. */
|
||||
LedMode: 'on' | 'off' | 'flashing'
|
||||
/** @description The node for the led. */
|
||||
LedNode: 'chamber_light' | 'work_light'
|
||||
/** @description Details for a 3d printer connected over USB. */
|
||||
Machine:
|
||||
| {
|
||||
id: string
|
||||
manufacturer: string
|
||||
model: string
|
||||
port: string
|
||||
/** @enum {string} */
|
||||
type: 'UsbPrinter'
|
||||
}
|
||||
| {
|
||||
/** @description The hostname of the printer. */
|
||||
hostname?: string | null
|
||||
/**
|
||||
* Format: ip
|
||||
* @description The IP address of the printer.
|
||||
*/
|
||||
ip: string
|
||||
/** @description The manufacturer of the printer. */
|
||||
manufacturer: components['schemas']['NetworkPrinterManufacturer']
|
||||
/** @description The model of the printer. */
|
||||
model?: string | null
|
||||
/**
|
||||
* Format: uint16
|
||||
* @description The port of the printer.
|
||||
*/
|
||||
port?: number | null
|
||||
/** @description The serial number of the printer. */
|
||||
serial?: string | null
|
||||
/** @enum {string} */
|
||||
type: 'NetworkPrinter'
|
||||
}
|
||||
/** @description A message from a machine. */
|
||||
Message:
|
||||
| {
|
||||
UsbPrinter: components['schemas']['Message2']
|
||||
}
|
||||
| {
|
||||
NetworkPrinter: components['schemas']['Message3']
|
||||
}
|
||||
/**
|
||||
* @description A message from the printer.
|
||||
* @enum {string}
|
||||
*/
|
||||
Message2: 'ok'
|
||||
/** @description A message from the printer. */
|
||||
Message3:
|
||||
| {
|
||||
Bambu: components['schemas']['Message4']
|
||||
}
|
||||
| {
|
||||
Formlabs: Record<string, never>
|
||||
}
|
||||
/** @description A message from/to the printer. */
|
||||
Message4:
|
||||
| {
|
||||
print: components['schemas']['Print']
|
||||
}
|
||||
| {
|
||||
info: components['schemas']['Info']
|
||||
}
|
||||
| {
|
||||
system: components['schemas']['System']
|
||||
}
|
||||
| {
|
||||
json: unknown
|
||||
}
|
||||
| {
|
||||
unknown: string | null
|
||||
}
|
||||
/** @description Network printer manufacturer. */
|
||||
NetworkPrinterManufacturer: 'Bambu' | 'Formlabs'
|
||||
/** @description A nozzle type. */
|
||||
NozzleType: 'hardened_steel' | 'stainless_steel'
|
||||
/** @description The response from the `/ping` endpoint. */
|
||||
Pong: {
|
||||
/** @description The pong response. */
|
||||
message: string
|
||||
}
|
||||
/** @description A print command. */
|
||||
Print:
|
||||
| ({
|
||||
/** @enum {string} */
|
||||
command: 'ams_control'
|
||||
/** @description The param. */
|
||||
param?: string | null
|
||||
/** @description The reason for the message. */
|
||||
reason: components['schemas']['Reason']
|
||||
/** @description The result of the command. */
|
||||
result: components['schemas']['Result']
|
||||
/** @description The sequence id. */
|
||||
sequence_id: components['schemas']['SequenceId']
|
||||
} & {
|
||||
[key: string]: unknown
|
||||
})
|
||||
| ({
|
||||
/** @description The ams. */
|
||||
ams?: components['schemas']['PrintAms'] | null
|
||||
/**
|
||||
* Format: int64
|
||||
* @description The ams rfid status.
|
||||
*/
|
||||
ams_rfid_status?: number | null
|
||||
/**
|
||||
* Format: int64
|
||||
* @description The ams status.
|
||||
*/
|
||||
ams_status?: number | null
|
||||
/** @description The aux part fan. */
|
||||
aux_part_fan?: boolean | null
|
||||
/**
|
||||
* Format: double
|
||||
* @description The target bed temperature.
|
||||
*/
|
||||
bed_target_temper?: number | null
|
||||
/**
|
||||
* Format: double
|
||||
* @description The bed temperature.
|
||||
*/
|
||||
bed_temper?: number | null
|
||||
/** @description The big fan 1 speed. */
|
||||
big_fan1_speed?: string | null
|
||||
/** @description The big fan 2 speed. */
|
||||
big_fan2_speed?: string | null
|
||||
/**
|
||||
* Format: double
|
||||
* @description The chamber temperature.
|
||||
*/
|
||||
chamber_temper?: number | null
|
||||
/** @enum {string} */
|
||||
command: 'push_status'
|
||||
/** @description The cooling fan speed. */
|
||||
cooling_fan_speed?: string | null
|
||||
/**
|
||||
* Format: int64
|
||||
* @description The fan gear.
|
||||
*/
|
||||
fan_gear?: number | null
|
||||
/** @description Force upgrade? */
|
||||
force_upgrade?: boolean | null
|
||||
/** @description The gcode file. */
|
||||
gcode_file?: string | null
|
||||
/** @description The gcode file prepare percent. */
|
||||
gcode_file_prepare_percent?: string | null
|
||||
/** @description The gcode state. */
|
||||
gcode_state?: string | null
|
||||
/** @description The heatbreak fan speed. */
|
||||
heatbreak_fan_speed?: string | null
|
||||
/** @description The hms. */
|
||||
hms?: unknown[] | null
|
||||
/**
|
||||
* Format: int64
|
||||
* @description The home flag.
|
||||
*/
|
||||
home_flag?: number | null
|
||||
/**
|
||||
* Format: int64
|
||||
* @description The hw switch state.
|
||||
*/
|
||||
hw_switch_state?: number | null
|
||||
/** @description The ipcam. */
|
||||
ipcam?: components['schemas']['PrintIpcam'] | null
|
||||
/**
|
||||
* Format: int64
|
||||
* @description The layer num.
|
||||
*/
|
||||
layer_num?: number | null
|
||||
/** @description The lifecycle. */
|
||||
lifecycle?: string | null
|
||||
/** @description The lights report. */
|
||||
lights_report?: components['schemas']['PrintLightsReport'][] | null
|
||||
/**
|
||||
* Format: int64
|
||||
* @description The percentage of the print completed.
|
||||
*/
|
||||
mc_percent?: number | null
|
||||
/** @description The mc print line number. */
|
||||
mc_print_line_number?: string | null
|
||||
/** @description The print stage. */
|
||||
mc_print_stage?: string | null
|
||||
/**
|
||||
* Format: int64
|
||||
* @description The mc print sub stage.
|
||||
*/
|
||||
mc_print_sub_stage?: number | null
|
||||
/**
|
||||
* Format: int64
|
||||
* @description The remaining time of the print.
|
||||
*/
|
||||
mc_remaining_time?: number | null
|
||||
/** @description The mess production state. */
|
||||
mess_production_state?: string | null
|
||||
/**
|
||||
* Format: int64
|
||||
* @description The message.
|
||||
*/
|
||||
msg?: number | null
|
||||
/** @description The nozzle diameter. */
|
||||
nozzle_diameter?: string | null
|
||||
/**
|
||||
* Format: double
|
||||
* @description The target nozzle temperature.
|
||||
*/
|
||||
nozzle_target_temper?: number | null
|
||||
/**
|
||||
* Format: double
|
||||
* @description The nozzle temperature.
|
||||
*/
|
||||
nozzle_temper?: number | null
|
||||
/** @description The nozzle type. */
|
||||
nozzle_type?: components['schemas']['NozzleType'] | null
|
||||
/** @description Online status. */
|
||||
online?: components['schemas']['PrintOnline'] | null
|
||||
/**
|
||||
* Format: int64
|
||||
* @description The print error.
|
||||
*/
|
||||
print_error?: number | null
|
||||
/** @description The print type. */
|
||||
print_type?: string | null
|
||||
/** @description The profile id. */
|
||||
profile_id?: string | null
|
||||
/** @description The project id. */
|
||||
project_id?: string | null
|
||||
/**
|
||||
* Format: int64
|
||||
* @description The queue est.
|
||||
*/
|
||||
queue_est?: number | null
|
||||
/**
|
||||
* Format: int64
|
||||
* @description The queue number.
|
||||
*/
|
||||
queue_number?: number | null
|
||||
/**
|
||||
* Format: int64
|
||||
* @description The queue sts.
|
||||
*/
|
||||
queue_sts?: number | null
|
||||
/**
|
||||
* Format: int64
|
||||
* @description The queue total.
|
||||
*/
|
||||
queue_total?: number | null
|
||||
/** @description The s obj. */
|
||||
s_obj?: unknown[] | null
|
||||
/** @description Sdcard? */
|
||||
sdcard?: boolean | null
|
||||
/** @description The sequence id. */
|
||||
sequence_id: components['schemas']['SequenceId']
|
||||
/**
|
||||
* Format: int64
|
||||
* @description The spd lvl.
|
||||
*/
|
||||
spd_lvl?: number | null
|
||||
/**
|
||||
* Format: int64
|
||||
* @description The spd mag.
|
||||
*/
|
||||
spd_mag?: number | null
|
||||
/** @description The stg. */
|
||||
stg?: unknown[] | null
|
||||
/**
|
||||
* Format: int64
|
||||
* @description The stg cur.
|
||||
*/
|
||||
stg_cur?: number | null
|
||||
/** @description The subtask id. */
|
||||
subtask_id?: string | null
|
||||
/** @description The subtask name. */
|
||||
subtask_name?: string | null
|
||||
/** @description The task id. */
|
||||
task_id?: string | null
|
||||
/**
|
||||
* Format: int64
|
||||
* @description The total layer num.
|
||||
*/
|
||||
total_layer_num?: number | null
|
||||
/** @description The upgrade state. */
|
||||
upgrade_state?: components['schemas']['PrintUpgradeState'] | null
|
||||
/** @description The upload. */
|
||||
upload?: components['schemas']['PrintUpload'] | null
|
||||
/** @description The tray. */
|
||||
vt_tray?: components['schemas']['PrintTray'] | null
|
||||
/** @description The wifi signal. */
|
||||
wifi_signal?: string | null
|
||||
} & {
|
||||
[key: string]: unknown
|
||||
})
|
||||
| ({
|
||||
/** @enum {string} */
|
||||
command: 'gcode_line'
|
||||
/** @description The gcode line. */
|
||||
param?: string | null
|
||||
/** @description The reason for the message. */
|
||||
reason: components['schemas']['Reason']
|
||||
/** @description The result of the command. */
|
||||
result: components['schemas']['Result']
|
||||
/** @description The return code. */
|
||||
return_code?: string | null
|
||||
/** @description The sequence id. */
|
||||
sequence_id: components['schemas']['SequenceId']
|
||||
/**
|
||||
* Format: int64
|
||||
* @description The source.
|
||||
*/
|
||||
source?: number | null
|
||||
} & {
|
||||
[key: string]: unknown
|
||||
})
|
||||
| ({
|
||||
/** @enum {string} */
|
||||
command: 'project_file'
|
||||
/** @description The gcode file. */
|
||||
gcode_file?: string | null
|
||||
/** @description The profile id. */
|
||||
profile_id: string
|
||||
/** @description The project id. */
|
||||
project_id: string
|
||||
/** @description The sequence id. */
|
||||
sequence_id: components['schemas']['SequenceId']
|
||||
/** @description The subtask id. */
|
||||
subtask_id: string
|
||||
/** @description The subtask name. */
|
||||
subtask_name: string
|
||||
/** @description The task id. */
|
||||
task_id: string
|
||||
} & {
|
||||
[key: string]: unknown
|
||||
})
|
||||
| ({
|
||||
/** @enum {string} */
|
||||
command: 'pause'
|
||||
/** @description The reason for the message. */
|
||||
reason: components['schemas']['Reason']
|
||||
/** @description The result of the command. */
|
||||
result: components['schemas']['Result']
|
||||
/** @description The sequence id. */
|
||||
sequence_id: components['schemas']['SequenceId']
|
||||
} & {
|
||||
[key: string]: unknown
|
||||
})
|
||||
| ({
|
||||
/** @enum {string} */
|
||||
command: 'resume'
|
||||
/** @description The reason for the message. */
|
||||
reason: components['schemas']['Reason']
|
||||
/** @description The result of the command. */
|
||||
result: components['schemas']['Result']
|
||||
/** @description The sequence id. */
|
||||
sequence_id: components['schemas']['SequenceId']
|
||||
} & {
|
||||
[key: string]: unknown
|
||||
})
|
||||
| ({
|
||||
/** @enum {string} */
|
||||
command: 'stop'
|
||||
/** @description The sequence id. */
|
||||
sequence_id: components['schemas']['SequenceId']
|
||||
} & {
|
||||
[key: string]: unknown
|
||||
})
|
||||
| ({
|
||||
/** @enum {string} */
|
||||
command: 'extrusion_cali_get'
|
||||
/** @description The sequence id. */
|
||||
sequence_id: components['schemas']['SequenceId']
|
||||
} & {
|
||||
[key: string]: unknown
|
||||
})
|
||||
/** @description The print ams. */
|
||||
PrintAms: {
|
||||
/** @description The ams. */
|
||||
ams?: components['schemas']['PrintAmsData'][] | null
|
||||
/** @description The ams exist bits. */
|
||||
ams_exist_bits?: string | null
|
||||
/** @description The insert flag. */
|
||||
insert_flag?: boolean | null
|
||||
/** @description The power on flag. */
|
||||
power_on_flag?: boolean | null
|
||||
/** @description The tray exist bits. */
|
||||
tray_exist_bits?: string | null
|
||||
/** @description The tray is bbl bits. */
|
||||
tray_is_bbl_bits?: string | null
|
||||
/** @description The tray now. */
|
||||
tray_now?: string | null
|
||||
/** @description The tray pre. */
|
||||
tray_pre?: string | null
|
||||
/** @description The tray read done bits. */
|
||||
tray_read_done_bits?: string | null
|
||||
/** @description The tray reading bits. */
|
||||
tray_reading_bits?: string | null
|
||||
/** @description The tray tar. */
|
||||
tray_tar?: string | null
|
||||
/**
|
||||
* Format: int64
|
||||
* @description The version.
|
||||
*/
|
||||
version?: number | null
|
||||
} & {
|
||||
[key: string]: unknown
|
||||
}
|
||||
/** @description The print ams data. */
|
||||
PrintAmsData: {
|
||||
/** @description The humidity. */
|
||||
humidity: string
|
||||
/** @description The id. */
|
||||
id: string
|
||||
/** @description The temperature. */
|
||||
temp: string
|
||||
/** @description The tray. */
|
||||
tray: components['schemas']['PrintTray'][]
|
||||
} & {
|
||||
[key: string]: unknown
|
||||
}
|
||||
/** @description The print ipcam. */
|
||||
PrintIpcam: {
|
||||
/** @description The ipcam dev. */
|
||||
ipcam_dev?: string | null
|
||||
/** @description The ipcam record. */
|
||||
ipcam_record?: string | null
|
||||
/**
|
||||
* Format: int64
|
||||
* @description The mode bits.
|
||||
*/
|
||||
mode_bits?: number | null
|
||||
/** @description The timelapse. */
|
||||
timelapse?: string | null
|
||||
} & {
|
||||
[key: string]: unknown
|
||||
}
|
||||
/** @description The response from the `/print` endpoint. */
|
||||
PrintJobResponse: {
|
||||
/** @description The job id used for this print. */
|
||||
job_id: string
|
||||
/** @description The parameters used for this print. */
|
||||
parameters: components['schemas']['PrintParameters']
|
||||
}
|
||||
/** @description A print lights report. */
|
||||
PrintLightsReport: {
|
||||
/** @description The mode. */
|
||||
mode: components['schemas']['LedMode']
|
||||
/** @description The node. */
|
||||
node: components['schemas']['LedNode']
|
||||
} & {
|
||||
[key: string]: unknown
|
||||
}
|
||||
/** @description The print online. */
|
||||
PrintOnline: {
|
||||
/** @description The ahb. */
|
||||
ahb: boolean
|
||||
/** @description The rfid. */
|
||||
rfid?: boolean | null
|
||||
/**
|
||||
* Format: int64
|
||||
* @description The version.
|
||||
*/
|
||||
version: number
|
||||
} & {
|
||||
[key: string]: unknown
|
||||
}
|
||||
/** @description Parameters for printing. */
|
||||
PrintParameters: {
|
||||
/** @description The name for the job. */
|
||||
job_name: string
|
||||
/** @description The machine id to print to. */
|
||||
machine_id: string
|
||||
}
|
||||
/** @description The print tray. */
|
||||
PrintTray: {
|
||||
/** @description The bed temperature. */
|
||||
bed_temp?: string | null
|
||||
/** @description The bed temperature type. */
|
||||
bed_temp_type?: string | null
|
||||
/** @description The id. */
|
||||
id: string
|
||||
/**
|
||||
* Format: double
|
||||
* @description The tray k.
|
||||
*/
|
||||
k?: number | null
|
||||
/**
|
||||
* Format: int64
|
||||
* @description The tray n.
|
||||
*/
|
||||
n?: number | null
|
||||
/** @description The nozzle temperature max. */
|
||||
nozzle_temp_max?: string | null
|
||||
/** @description The nozzle temperature min. */
|
||||
nozzle_temp_min?: string | null
|
||||
/**
|
||||
* Format: int64
|
||||
* @description The tray remain.
|
||||
*/
|
||||
remain?: number | null
|
||||
/** @description The tag uid. */
|
||||
tag_uid?: string | null
|
||||
/** @description The tray color. */
|
||||
tray_color?: string | null
|
||||
/** @description The tray diameter. */
|
||||
tray_diameter?: string | null
|
||||
/** @description The tray id name. */
|
||||
tray_id_name?: string | null
|
||||
/** @description The tray info index. */
|
||||
tray_info_idx?: string | null
|
||||
/** @description The tray sub brands. */
|
||||
tray_sub_brands?: string | null
|
||||
/** @description The tray temperature. */
|
||||
tray_temp?: string | null
|
||||
/** @description The tray time. */
|
||||
tray_time?: string | null
|
||||
/** @description The tray type. */
|
||||
tray_type?: string | null
|
||||
/** @description The tray uuid. */
|
||||
tray_uuid?: string | null
|
||||
/** @description The tray weight. */
|
||||
tray_weight?: string | null
|
||||
/** @description The xcam info. */
|
||||
xcam_info?: string | null
|
||||
} & {
|
||||
[key: string]: unknown
|
||||
}
|
||||
/** @description A print upgrade state. */
|
||||
PrintUpgradeState: {
|
||||
/** @description The consistency request. */
|
||||
consistency_request?: boolean | null
|
||||
/**
|
||||
* Format: int64
|
||||
* @description The dis state.
|
||||
*/
|
||||
dis_state?: number | null
|
||||
/**
|
||||
* Format: int64
|
||||
* @description The error code.
|
||||
*/
|
||||
err_code?: number | null
|
||||
/** @description Force upgrade? */
|
||||
force_upgrade?: boolean | null
|
||||
/** @description The message. */
|
||||
message?: string | null
|
||||
/** @description The module. */
|
||||
module?: string | null
|
||||
/** @description The new version list. */
|
||||
new_ver_list?: unknown[] | null
|
||||
/**
|
||||
* Format: int64
|
||||
* @description The new version state.
|
||||
*/
|
||||
new_version_state?: number | null
|
||||
/** @description The progress. */
|
||||
progress?: string | null
|
||||
/**
|
||||
* Format: int64
|
||||
* @description The sequence id.
|
||||
*/
|
||||
sequence_id?: number | null
|
||||
/** @description The status. */
|
||||
status?: string | null
|
||||
} & {
|
||||
[key: string]: unknown
|
||||
}
|
||||
/** @description The print upload. */
|
||||
PrintUpload: {
|
||||
/** @description The message. */
|
||||
message: string
|
||||
/**
|
||||
* Format: int64
|
||||
* @description The progress.
|
||||
*/
|
||||
progress: number
|
||||
/** @description The status. */
|
||||
status: string
|
||||
} & {
|
||||
[key: string]: unknown
|
||||
}
|
||||
/** @description A reason for a message. */
|
||||
Reason:
|
||||
| 'SUCCESS'
|
||||
| 'FAIL'
|
||||
| {
|
||||
UNKNOWN: string
|
||||
}
|
||||
/** @description The result of a message. */
|
||||
Result: 'SUCCESS' | 'FAIL'
|
||||
/** @description The sequence id type. */
|
||||
SequenceId: string | number
|
||||
/** @description A system command. */
|
||||
System:
|
||||
| ({
|
||||
/** @enum {string} */
|
||||
command: 'ledctrl'
|
||||
/**
|
||||
* Format: uint32
|
||||
* @description The interval time.
|
||||
*/
|
||||
interval_time: number
|
||||
/** @description The LED mode. */
|
||||
led_mode: components['schemas']['LedMode']
|
||||
/** @description The LED node. */
|
||||
led_node: components['schemas']['LedNode']
|
||||
/**
|
||||
* Format: uint32
|
||||
* @description The LED off time.
|
||||
*/
|
||||
led_off_time: number
|
||||
/**
|
||||
* Format: uint32
|
||||
* @description The LED on time.
|
||||
*/
|
||||
led_on_time: number
|
||||
/**
|
||||
* Format: uint32
|
||||
* @description The loop times.
|
||||
*/
|
||||
loop_times: number
|
||||
/** @description The reason for the message. */
|
||||
reason?: components['schemas']['Reason'] | null
|
||||
/** @description The result of the command. */
|
||||
result: components['schemas']['Result']
|
||||
/** @description The sequence id. */
|
||||
sequence_id: components['schemas']['SequenceId']
|
||||
} & {
|
||||
[key: string]: unknown
|
||||
})
|
||||
| ({
|
||||
/** @description The accessory type. */
|
||||
accessory_type: components['schemas']['AccessoryType']
|
||||
/** @description The aux part fan. */
|
||||
aux_part_fan: boolean
|
||||
/** @enum {string} */
|
||||
command: 'get_accessories'
|
||||
/**
|
||||
* Format: double
|
||||
* @description The nozzle diameter.
|
||||
*/
|
||||
nozzle_diameter: number
|
||||
/** @description The nozzle type. */
|
||||
nozzle_type: components['schemas']['NozzleType']
|
||||
/** @description The reason for the message. */
|
||||
reason?: components['schemas']['Reason'] | null
|
||||
/** @description The result of the command. */
|
||||
result: components['schemas']['Result']
|
||||
/** @description The sequence id. */
|
||||
sequence_id: components['schemas']['SequenceId']
|
||||
} & {
|
||||
[key: string]: unknown
|
||||
})
|
||||
}
|
||||
responses: {
|
||||
/** @description Error */
|
||||
Error: {
|
||||
headers: {
|
||||
[name: string]: unknown
|
||||
}
|
||||
content: {
|
||||
'application/json': components['schemas']['Error']
|
||||
}
|
||||
}
|
||||
}
|
||||
parameters: never
|
||||
requestBodies: never
|
||||
headers: never
|
||||
pathItems: never
|
||||
}
|
||||
export type $defs = Record<string, never>
|
||||
export interface operations {
|
||||
api_get_schema: {
|
||||
parameters: {
|
||||
query?: never
|
||||
header?: never
|
||||
path?: never
|
||||
cookie?: never
|
||||
}
|
||||
requestBody?: never
|
||||
responses: {
|
||||
/** @description successful operation */
|
||||
200: {
|
||||
headers: {
|
||||
[name: string]: unknown
|
||||
}
|
||||
content: {
|
||||
'application/json': unknown
|
||||
}
|
||||
}
|
||||
'4XX': components['responses']['Error']
|
||||
'5XX': components['responses']['Error']
|
||||
}
|
||||
}
|
||||
get_machines: {
|
||||
parameters: {
|
||||
query?: never
|
||||
header?: never
|
||||
path?: never
|
||||
cookie?: never
|
||||
}
|
||||
requestBody?: never
|
||||
responses: {
|
||||
/** @description successful operation */
|
||||
200: {
|
||||
headers: {
|
||||
[name: string]: unknown
|
||||
}
|
||||
content: {
|
||||
'application/json': {
|
||||
[key: string]: components['schemas']['Machine']
|
||||
}
|
||||
}
|
||||
}
|
||||
'4XX': components['responses']['Error']
|
||||
'5XX': components['responses']['Error']
|
||||
}
|
||||
}
|
||||
get_machine: {
|
||||
parameters: {
|
||||
query?: never
|
||||
header?: never
|
||||
path: {
|
||||
/** @description The machine ID. */
|
||||
id: string
|
||||
}
|
||||
cookie?: never
|
||||
}
|
||||
requestBody?: never
|
||||
responses: {
|
||||
/** @description successful operation */
|
||||
200: {
|
||||
headers: {
|
||||
[name: string]: unknown
|
||||
}
|
||||
content: {
|
||||
'application/json': components['schemas']['Message']
|
||||
}
|
||||
}
|
||||
'4XX': components['responses']['Error']
|
||||
'5XX': components['responses']['Error']
|
||||
}
|
||||
}
|
||||
ping: {
|
||||
parameters: {
|
||||
query?: never
|
||||
header?: never
|
||||
path?: never
|
||||
cookie?: never
|
||||
}
|
||||
requestBody?: never
|
||||
responses: {
|
||||
/** @description successful operation */
|
||||
200: {
|
||||
headers: {
|
||||
[name: string]: unknown
|
||||
}
|
||||
content: {
|
||||
'application/json': components['schemas']['Pong']
|
||||
}
|
||||
}
|
||||
'4XX': components['responses']['Error']
|
||||
'5XX': components['responses']['Error']
|
||||
}
|
||||
}
|
||||
print_file: {
|
||||
parameters: {
|
||||
query?: never
|
||||
header?: never
|
||||
path?: never
|
||||
cookie?: never
|
||||
}
|
||||
requestBody: {
|
||||
content: {
|
||||
'multipart/form-data': string
|
||||
}
|
||||
}
|
||||
responses: {
|
||||
/** @description successful operation */
|
||||
200: {
|
||||
headers: {
|
||||
[name: string]: unknown
|
||||
}
|
||||
content: {
|
||||
'application/json': components['schemas']['PrintJobResponse']
|
||||
}
|
||||
}
|
||||
'4XX': components['responses']['Error']
|
||||
'5XX': components['responses']['Error']
|
||||
}
|
||||
}
|
||||
}
|
74
src/lib/machineManager.ts
Normal file
74
src/lib/machineManager.ts
Normal file
@ -0,0 +1,74 @@
|
||||
import { isTauri } from './isTauri'
|
||||
import { components } from './machine-api'
|
||||
import { getMachineApiIp, listMachines } from './tauri'
|
||||
|
||||
export class MachineManager {
|
||||
private _isTauri: boolean = isTauri()
|
||||
private _machines: {
|
||||
[key: string]: components['schemas']['Machine']
|
||||
} = {}
|
||||
private _machineApiIp: string | null = null
|
||||
private _currentMachine: components['schemas']['Machine'] | null = null
|
||||
|
||||
constructor() {
|
||||
if (!this._isTauri) {
|
||||
return
|
||||
}
|
||||
|
||||
this.updateMachines()
|
||||
}
|
||||
|
||||
start() {
|
||||
if (!this._isTauri) {
|
||||
return
|
||||
}
|
||||
|
||||
// Start a background job to update the machines every ten seconds.
|
||||
setInterval(() => {
|
||||
this.updateMachineApiIp()
|
||||
this.updateMachines()
|
||||
}, 10000)
|
||||
}
|
||||
|
||||
get machines(): {
|
||||
[key: string]: components['schemas']['Machine']
|
||||
} {
|
||||
return this._machines
|
||||
}
|
||||
|
||||
machineCount(): number {
|
||||
return Object.keys(this._machines).length
|
||||
}
|
||||
|
||||
get machineApiIp(): string | null {
|
||||
return this._machineApiIp
|
||||
}
|
||||
|
||||
get currentMachine(): components['schemas']['Machine'] | null {
|
||||
return this._currentMachine
|
||||
}
|
||||
|
||||
set currentMachine(machine: components['schemas']['Machine'] | null) {
|
||||
this._currentMachine = machine
|
||||
}
|
||||
|
||||
private async updateMachines(): Promise<void> {
|
||||
if (!this._isTauri) {
|
||||
return
|
||||
}
|
||||
|
||||
this._machines = await listMachines()
|
||||
console.log('Machines:', this._machines)
|
||||
}
|
||||
|
||||
private async updateMachineApiIp(): Promise<void> {
|
||||
if (!this._isTauri) {
|
||||
return
|
||||
}
|
||||
|
||||
this._machineApiIp = await getMachineApiIp()
|
||||
}
|
||||
}
|
||||
|
||||
export const machineManager = new MachineManager()
|
||||
machineManager.start()
|
4
src/lib/modelingAppFile.ts
Normal file
4
src/lib/modelingAppFile.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export default interface ModelingAppFile {
|
||||
name: string
|
||||
contents: number[]
|
||||
}
|
@ -9,6 +9,7 @@ import { FileEntry } from 'wasm-lib/kcl/bindings/FileEntry'
|
||||
import { ProjectState } from 'wasm-lib/kcl/bindings/ProjectState'
|
||||
import { ProjectRoute } from 'wasm-lib/kcl/bindings/ProjectRoute'
|
||||
import { isTauri } from './isTauri'
|
||||
import { components } from './machine-api'
|
||||
|
||||
// Get the app state from tauri.
|
||||
export async function getState(): Promise<ProjectState | undefined> {
|
||||
@ -26,6 +27,19 @@ export async function setState(state: ProjectState | undefined): Promise<void> {
|
||||
return await invoke('set_state', { state })
|
||||
}
|
||||
|
||||
// List machines on the local network.
|
||||
export async function listMachines(): Promise<{
|
||||
[key: string]: components['schemas']['Machine']
|
||||
}> {
|
||||
let machines: string = await invoke<string>('list_machines')
|
||||
return JSON.parse(machines)
|
||||
}
|
||||
|
||||
// Get the machine-api ip address.
|
||||
export async function getMachineApiIp(): Promise<string | null> {
|
||||
return await invoke<string | null>('get_machine_api_ip')
|
||||
}
|
||||
|
||||
export async function renameProjectDirectory(
|
||||
projectPath: string,
|
||||
newName: string
|
||||
|
@ -202,6 +202,7 @@ export type ModelingMachineEvent =
|
||||
| { type: 'Constrain remove constraints'; data?: PathToNode }
|
||||
| { type: 'Re-execute' }
|
||||
| { type: 'Export'; data: ModelingCommandSchema['Export'] }
|
||||
| { type: 'Make'; data: ModelingCommandSchema['Make'] }
|
||||
| { type: 'Extrude'; data?: ModelingCommandSchema['Extrude'] }
|
||||
| { type: 'Fillet'; data?: ModelingCommandSchema['Fillet'] }
|
||||
| {
|
||||
@ -334,6 +335,13 @@ export const modelingMachine = createMachine(
|
||||
actions: 'Engine export',
|
||||
},
|
||||
|
||||
Make: {
|
||||
target: 'idle',
|
||||
internal: true,
|
||||
cond: 'Has exportable geometry',
|
||||
actions: 'Make',
|
||||
},
|
||||
|
||||
'Delete selection': {
|
||||
target: 'idle',
|
||||
cond: 'has valid selection for deletion',
|
||||
|
12
src/wasm-lib/Cargo.lock
generated
12
src/wasm-lib/Cargo.lock
generated
@ -335,6 +335,12 @@ version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
||||
|
||||
[[package]]
|
||||
name = "byteorder-lite"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495"
|
||||
|
||||
[[package]]
|
||||
name = "bytes"
|
||||
version = "1.5.0"
|
||||
@ -1251,12 +1257,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "image"
|
||||
version = "0.25.1"
|
||||
version = "0.25.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fd54d660e773627692c524beaad361aca785a4f9f5730ce91f42aabe5bce3d11"
|
||||
checksum = "99314c8a2152b8ddb211f924cdae532d8c5e4c8bb54728e12fff1b0cd5963a10"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"byteorder",
|
||||
"byteorder-lite",
|
||||
"num-traits",
|
||||
"png",
|
||||
]
|
||||
|
@ -27,7 +27,7 @@ pub async fn execute_and_snapshot(code: &str, units: UnitLength) -> anyhow::Resu
|
||||
// Save the snapshot locally, to that temporary file.
|
||||
std::fs::write(&output_file, snapshot.contents.0)?;
|
||||
// Decode the snapshot, return it.
|
||||
let img = image::io::Reader::open(output_file).unwrap().decode()?;
|
||||
let img = image::ImageReader::open(output_file).unwrap().decode()?;
|
||||
Ok(img)
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user