From baf7d3dd9dd30d68665b4c7b972eddc62b29cd11 Mon Sep 17 00:00:00 2001 From: Adam Sunderland Date: Sun, 4 Aug 2024 00:51:30 -0400 Subject: [PATCH] Add print button (#3133) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * add print button Signed-off-by: Jess Frazelle * cleanup Signed-off-by: Jess Frazelle * generate more types Signed-off-by: Jess Frazelle * add a github action to generate machine api-types Signed-off-by: Jess Frazelle * fix Signed-off-by: Jess Frazelle * New machine-api types * actually print on the real machine Signed-off-by: Jess Frazelle * A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu) * add more Signed-off-by: Jess Frazelle * New machine-api types * get the current machine Signed-off-by: Jess Frazelle * New machine-api types * know when error Signed-off-by: Jess Frazelle * updates Signed-off-by: Jess Frazelle * updates Signed-off-by: Jess Frazelle * fmt Signed-off-by: Jess Frazelle * add fmt Signed-off-by: Jess Frazelle * New machine-api types * empty * empty * update machine api Signed-off-by: Jess Frazelle * New machine-api types * empty * New machine-api types * emptuy * no circular deps Signed-off-by: Jess Frazelle * New machine-api types * remove recursive dep Signed-off-by: Jess Frazelle --------- Signed-off-by: Jess Frazelle Co-authored-by: Jess Frazelle Co-authored-by: github-actions[bot] Co-authored-by: Jess Frazelle --- .../workflows/generate-machine-api-types.yml | 49 + flake.lock | 12 +- flake.nix | 1 + package.json | 3 +- src-tauri/Cargo.lock | 59 +- src-tauri/Cargo.toml | 2 + src-tauri/src/main.rs | 66 ++ .../CommandBar/CommandBarHeader.tsx | 6 +- src/components/CustomIcon.tsx | 10 + src/components/LowerRightControls.tsx | 2 + src/components/ModelingMachineProvider.tsx | 51 + .../ModelingSidebar/ModelingSidebar.tsx | 66 +- src/components/NetworkHealthIndicator.tsx | 4 +- src/components/NetworkMachineIndicator.tsx | 62 ++ src/components/ProjectSidebarMenu.tsx | 29 + .../sketchOnFaceOnFaceEtc.png | Bin 381903 -> 382546 bytes src/lang/std/engineConnection.ts | 62 +- .../modelingCommandConfig.ts | 35 + src/lib/commandTypes.ts | 8 + src/lib/createMachineCommand.ts | 14 +- src/lib/exportMake.ts | 74 ++ src/lib/exportSave.ts | 8 +- src/lib/machine-api.d.ts | 925 ++++++++++++++++++ src/lib/machineManager.ts | 74 ++ src/lib/modelingAppFile.ts | 4 + src/lib/tauri.ts | 14 + src/machines/modelingMachine.ts | 8 + src/wasm-lib/Cargo.lock | 12 +- src/wasm-lib/kcl/src/test_server.rs | 2 +- 29 files changed, 1615 insertions(+), 47 deletions(-) create mode 100644 .github/workflows/generate-machine-api-types.yml create mode 100644 src/components/NetworkMachineIndicator.tsx create mode 100644 src/lib/exportMake.ts create mode 100644 src/lib/machine-api.d.ts create mode 100644 src/lib/machineManager.ts create mode 100644 src/lib/modelingAppFile.ts diff --git a/.github/workflows/generate-machine-api-types.yml b/.github/workflows/generate-machine-api-types.yml new file mode 100644 index 000000000..3433dd713 --- /dev/null +++ b/.github/workflows/generate-machine-api-types.yml @@ -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 }} + diff --git a/flake.lock b/flake.lock index 42f2ac436..dff03418b 100644 --- a/flake.lock +++ b/flake.lock @@ -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": { diff --git a/flake.nix b/flake.nix index 79cfa1bbb..cbca77842 100644 --- a/flake.nix +++ b/flake.nix @@ -57,6 +57,7 @@ pkg-config nodejs_22 + yarn ]) ++ pkgs.lib.optionals pkgs.stdenv.isDarwin (with pkgs; [ libiconv darwin.apple_sdk.frameworks.Security diff --git a/package.json b/package.json index 73eed50a8..628181310 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 7a4b4f5ae..f54da0591 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -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" diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 419fe6fdf..63ff87664 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -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" } diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 8ec83645a..d19ae7fd8 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -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> { + 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, InvokeError> { + let machine_api = find_machine_api().await.map_err(InvokeError::from_anyhow)?; + + Ok(machine_api) +} + +#[tauri::command] +async fn list_machines() -> Result { + 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()) diff --git a/src/components/CommandBar/CommandBarHeader.tsx b/src/components/CommandBar/CommandBarHeader.tsx index 84093a7e9..6cbde3cd1 100644 --- a/src/components/CommandBar/CommandBarHeader.tsx +++ b/src/components/CommandBar/CommandBarHeader.tsx @@ -124,7 +124,11 @@ function CommandBarHeader({ children }: React.PropsWithChildren<{}>) { 4 ) ) : typeof argValue === 'object' ? ( - JSON.stringify(argValue) + arg.valueSummary ? ( + arg.valueSummary(argValue) + ) : ( + JSON.stringify(argValue) + ) ) : ( {argValue} ) diff --git a/src/components/CustomIcon.tsx b/src/components/CustomIcon.tsx index 2c9eee93b..672ee17f7 100644 --- a/src/components/CustomIcon.tsx +++ b/src/components/CustomIcon.tsx @@ -541,6 +541,16 @@ const CustomIconMap = { /> ), + printer3d: ( + + + + ), polygon: ( + diff --git a/src/components/ModelingMachineProvider.tsx b/src/components/ModelingMachineProvider.tsx index 37ccdcf26..76dbdb89d 100644 --- a/src/components/ModelingMachineProvider.tsx +++ b/src/components/ModelingMachineProvider.tsx @@ -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 = { state: StateFrom @@ -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, diff --git a/src/components/ModelingSidebar/ModelingSidebar.tsx b/src/components/ModelingSidebar/ModelingSidebar.tsx index abc58ba71..a728b6950 100644 --- a/src/components/ModelingSidebar/ModelingSidebar.tsx +++ b/src/components/ModelingSidebar/ModelingSidebar.tsx @@ -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) { /> ))} -
- + {filteredActions.length > 0 && ( + <> +
+ + + )}