diff --git a/.codespellrc b/.codespellrc index e6e67c013..0650c36b1 100644 --- a/.codespellrc +++ b/.codespellrc @@ -1,3 +1,3 @@ [codespell] ignore-words-list: crate,everytime,inout,co-ordinate,ot,nwo,absolutey,atleast,ue,afterall -skip: **/target,node_modules,build,**/Cargo.lock,./docs/kcl/*.md,.yarn.lock,**/yarn.lock +skip: **/target,node_modules,build,**/Cargo.lock,./docs/kcl/*.md,.yarn.lock,**/yarn.lock,./openapi/*.json diff --git a/interface.d.ts b/interface.d.ts index b9e776922..77d5e07b7 100644 --- a/interface.d.ts +++ b/interface.d.ts @@ -67,7 +67,7 @@ export interface IElectronAPI { } } kittycad: (access: string, args: any) => any - listMachines: () => Promise + listMachines: (machineApiIp: string) => Promise getMachineApiIp: () => Promise onUpdateDownloadStart: ( callback: (value: { version: string }) => void diff --git a/openapi/machine-api.json b/openapi/machine-api.json index 8b7295b13..d5a9238ec 100644 --- a/openapi/machine-api.json +++ b/openapi/machine-api.json @@ -36,38 +36,178 @@ "description": "Extra machine-specific information regarding a connected machine.", "oneOf": [ { - "additionalProperties": false, "properties": { - "Moonraker": { - "type": "object" + "type": { + "enum": [ + "moonraker" + ], + "type": "string" } }, "required": [ - "Moonraker" + "type" ], "type": "object" }, { - "additionalProperties": false, "properties": { - "Usb": { - "type": "object" + "type": { + "enum": [ + "usb" + ], + "type": "string" } }, "required": [ - "Usb" + "type" ], "type": "object" }, { - "additionalProperties": false, "properties": { - "Bambu": { - "type": "object" + "current_stage": { + "allOf": [ + { + "$ref": "#/components/schemas/Stage" + } + ], + "description": "The current stage of the machine as defined by Bambu which can include errors, etc.", + "nullable": true + }, + "nozzle_diameter": { + "allOf": [ + { + "$ref": "#/components/schemas/NozzleDiameter" + } + ], + "description": "The nozzle diameter of the machine." + }, + "type": { + "enum": [ + "bambu" + ], + "type": "string" } }, "required": [ - "Bambu" + "nozzle_diameter", + "type" + ], + "type": "object" + } + ] + }, + "FdmHardwareConfiguration": { + "description": "Configuration for a FDM-based printer.", + "properties": { + "filament_material": { + "allOf": [ + { + "$ref": "#/components/schemas/FilamentMaterial" + } + ], + "description": "type of material being extruded" + }, + "nozzle_diameter": { + "description": "Diameter of the extrusion nozzle, in mm.", + "format": "double", + "type": "number" + } + }, + "required": [ + "filament_material", + "nozzle_diameter" + ], + "type": "object" + }, + "FilamentMaterial": { + "description": "The material that the filament is made of.", + "oneOf": [ + { + "description": "polylactic acid based plastics", + "enum": [ + "Pla" + ], + "type": "string" + }, + { + "description": "acrylonitrile butadiene styrene based plastics", + "enum": [ + "Abs" + ], + "type": "string" + }, + { + "description": "polyethylene terephthalate glycol based plastics", + "enum": [ + "Petg" + ], + "type": "string" + }, + { + "description": "unsuprisingly, nylon based", + "enum": [ + "Nylon" + ], + "type": "string" + }, + { + "description": "thermoplastic polyurethane based urethane material", + "enum": [ + "Tpu" + ], + "type": "string" + }, + { + "description": "polyvinyl alcohol based material", + "enum": [ + "Pva" + ], + "type": "string" + }, + { + "description": "high impact polystyrene based material", + "enum": [ + "Hips" + ], + "type": "string" + }, + { + "description": "composite material with stuff in other stuff, something like PLA mixed with carbon fiber, kevlar, or fiberglass", + "enum": [ + "Composite" + ], + "type": "string" + }, + { + "description": "none of the above, buyer beware", + "enum": [ + "Other" + ], + "type": "string" + } + ] + }, + "HardwareConfiguration": { + "description": "The hardware configuration of a machine.", + "oneOf": [ + { + "description": "No configuration is possible. This isn't the same conceptually as an `Option`, because this indicates we positively know there is no possible configuration changes that are possible with this method of manufcture.", + "enum": [ + "None" + ], + "type": "string" + }, + { + "additionalProperties": false, + "description": "Hardware configuration specific to FDM based printers", + "properties": { + "Fdm": { + "$ref": "#/components/schemas/FdmHardwareConfiguration" + } + }, + "required": [ + "Fdm" ], "type": "object" } @@ -85,6 +225,14 @@ "description": "Additional, per-machine information which is specific to the underlying machine type.", "nullable": true }, + "hardware_configuration": { + "allOf": [ + { + "$ref": "#/components/schemas/HardwareConfiguration" + } + ], + "description": "Information about how the Machine is currently configured." + }, "id": { "description": "Machine Identifier (ID) for the specific Machine.", "type": "string" @@ -124,6 +272,7 @@ } }, "required": [ + "hardware_configuration", "id", "machine_type", "make_model", @@ -157,57 +306,111 @@ "oneOf": [ { "description": "If a print state can not be resolved at this time, an Unknown may be returned.", - "enum": [ - "Unknown" - ], - "type": "string" - }, - { - "description": "Idle, and ready for another job.", - "enum": [ - "Idle" - ], - "type": "string" - }, - { - "description": "Running a job -- 3D printing or CNC-ing a part.", - "enum": [ - "Running" - ], - "type": "string" - }, - { - "description": "Machine is currently offline or unreachable.", - "enum": [ - "Offline" - ], - "type": "string" - }, - { - "description": "Job is underway but halted, waiting for some action to take place.", - "enum": [ - "Paused" - ], - "type": "string" - }, - { - "description": "Job is finished, but waiting manual action to move back to Idle.", - "enum": [ - "Complete" - ], - "type": "string" - }, - { - "additionalProperties": false, - "description": "The printer has failed and is in an unknown state that may require manual attention to resolve. The inner value is a human readable description of what specifically has failed.", "properties": { - "Failed": { - "nullable": true, + "state": { + "enum": [ + "unknown" + ], "type": "string" } }, "required": [ - "Failed" + "state" + ], + "type": "object" + }, + { + "description": "Idle, and ready for another job.", + "properties": { + "state": { + "enum": [ + "idle" + ], + "type": "string" + } + }, + "required": [ + "state" + ], + "type": "object" + }, + { + "description": "Running a job -- 3D printing or CNC-ing a part.", + "properties": { + "state": { + "enum": [ + "running" + ], + "type": "string" + } + }, + "required": [ + "state" + ], + "type": "object" + }, + { + "description": "Machine is currently offline or unreachable.", + "properties": { + "state": { + "enum": [ + "offline" + ], + "type": "string" + } + }, + "required": [ + "state" + ], + "type": "object" + }, + { + "description": "Job is underway but halted, waiting for some action to take place.", + "properties": { + "state": { + "enum": [ + "paused" + ], + "type": "string" + } + }, + "required": [ + "state" + ], + "type": "object" + }, + { + "description": "Job is finished, but waiting manual action to move back to Idle.", + "properties": { + "state": { + "enum": [ + "complete" + ], + "type": "string" + } + }, + "required": [ + "state" + ], + "type": "object" + }, + { + "description": "The printer has failed and is in an unknown state that may require manual attention to resolve. The inner value is a human readable description of what specifically has failed.", + "properties": { + "message": { + "description": "A human-readable message describing the failure.", + "nullable": true, + "type": "string" + }, + "state": { + "enum": [ + "failed" + ], + "type": "string" + } + }, + "required": [ + "state" ], "type": "object" } @@ -239,6 +442,39 @@ } ] }, + "NozzleDiameter": { + "description": "A nozzle diameter.", + "oneOf": [ + { + "description": "0.2mm.", + "enum": [ + "0.2" + ], + "type": "string" + }, + { + "description": "0.4mm.", + "enum": [ + "0.4" + ], + "type": "string" + }, + { + "description": "0.6mm.", + "enum": [ + "0.6" + ], + "type": "string" + }, + { + "description": "0.8mm.", + "enum": [ + "0.8" + ], + "type": "string" + } + ] + }, "Pong": { "description": "The response from the `/ping` endpoint.", "properties": { @@ -284,6 +520,15 @@ "machine_id": { "description": "The machine id to print to.", "type": "string" + }, + "slicer_configuration": { + "allOf": [ + { + "$ref": "#/components/schemas/SlicerConfiguration" + } + ], + "description": "Requested design-specific slicer configurations.", + "nullable": true } }, "required": [ @@ -292,6 +537,274 @@ ], "type": "object" }, + "SlicerConfiguration": { + "description": "The slicer configuration is a set of parameters that are passed to the slicer to control how the gcode is generated.", + "type": "object" + }, + "Stage": { + "description": "The print stage. These come from: https://github.com/SoftFever/OrcaSlicer/blob/431978baf17961df90f0d01871b0ad1d839d7f5d/src/slic3r/GUI/DeviceManager.cpp#L78", + "oneOf": [ + { + "description": "Nothing.", + "enum": [ + "nothing" + ], + "type": "string" + }, + { + "description": "Empty.", + "enum": [ + "empty" + ], + "type": "string" + }, + { + "description": "Auto bed leveling.", + "enum": [ + "auto_bed_leveling" + ], + "type": "string" + }, + { + "description": "Heatbed preheating.", + "enum": [ + "heatbed_preheating" + ], + "type": "string" + }, + { + "description": "Sweeping XY mech mode.", + "enum": [ + "sweeping_xy_mech_mode" + ], + "type": "string" + }, + { + "description": "Changing filament.", + "enum": [ + "changing_filament" + ], + "type": "string" + }, + { + "description": "M400 pause.", + "enum": [ + "m400_pause" + ], + "type": "string" + }, + { + "description": "Paused due to filament runout.", + "enum": [ + "paused_due_to_filament_runout" + ], + "type": "string" + }, + { + "description": "Heating hotend.", + "enum": [ + "heating_hotend" + ], + "type": "string" + }, + { + "description": "Calibrating extrusion.", + "enum": [ + "calibrating_extrusion" + ], + "type": "string" + }, + { + "description": "Scanning bed surface.", + "enum": [ + "scanning_bed_surface" + ], + "type": "string" + }, + { + "description": "Inspecting first layer.", + "enum": [ + "inspecting_first_layer" + ], + "type": "string" + }, + { + "description": "Identifying build plate type.", + "enum": [ + "identifying_build_plate_type" + ], + "type": "string" + }, + { + "description": "Calibrating micro lidar.", + "enum": [ + "calibrating_micro_lidar" + ], + "type": "string" + }, + { + "description": "Homing toolhead.", + "enum": [ + "homing_toolhead" + ], + "type": "string" + }, + { + "description": "Cleaning nozzle tip.", + "enum": [ + "cleaning_nozzle_tip" + ], + "type": "string" + }, + { + "description": "Checking extruder temperature.", + "enum": [ + "checking_extruder_temperature" + ], + "type": "string" + }, + { + "description": "Printing was paused by the user.", + "enum": [ + "printing_was_paused_by_the_user" + ], + "type": "string" + }, + { + "description": "Pause of front cover falling.", + "enum": [ + "pause_of_front_cover_falling" + ], + "type": "string" + }, + { + "description": "Calibrating micro lidar.", + "enum": [ + "calibrating_micro_lidar2" + ], + "type": "string" + }, + { + "description": "Calibrating extrusion flow.", + "enum": [ + "calibrating_extrusion_flow" + ], + "type": "string" + }, + { + "description": "Paused due to nozzle temperature malfunction.", + "enum": [ + "paused_due_to_nozzle_temperature_malfunction" + ], + "type": "string" + }, + { + "description": "Paused due to heat bed temperature malfunction.", + "enum": [ + "paused_due_to_heat_bed_temperature_malfunction" + ], + "type": "string" + }, + { + "description": "Filament unloading.", + "enum": [ + "filament_unloading" + ], + "type": "string" + }, + { + "description": "Skip step pause.", + "enum": [ + "skip_step_pause" + ], + "type": "string" + }, + { + "description": "Filament loading.", + "enum": [ + "filament_loading" + ], + "type": "string" + }, + { + "description": "Motor noise calibration.", + "enum": [ + "motor_noise_calibration" + ], + "type": "string" + }, + { + "description": "Paused due to AMS lost.", + "enum": [ + "paused_due_to_ams_lost" + ], + "type": "string" + }, + { + "description": "Paused due to low speed of the heat break fan.", + "enum": [ + "paused_due_to_low_speed_of_the_heat_break_fan" + ], + "type": "string" + }, + { + "description": "Paused due to chamber temperature control error.", + "enum": [ + "paused_due_to_chamber_temperature_control_error" + ], + "type": "string" + }, + { + "description": "Cooling chamber.", + "enum": [ + "cooling_chamber" + ], + "type": "string" + }, + { + "description": "Paused by the Gcode inserted by the user.", + "enum": [ + "paused_by_the_gcode_inserted_by_the_user" + ], + "type": "string" + }, + { + "description": "Motor noise showoff.", + "enum": [ + "motor_noise_showoff" + ], + "type": "string" + }, + { + "description": "Nozzle filament covered detected pause.", + "enum": [ + "nozzle_filament_covered_detected_pause" + ], + "type": "string" + }, + { + "description": "Cutter error pause.", + "enum": [ + "cutter_error_pause" + ], + "type": "string" + }, + { + "description": "First layer error pause.", + "enum": [ + "first_layer_error_pause" + ], + "type": "string" + }, + { + "description": "Nozzle clog pause.", + "enum": [ + "nozzle_clog_pause" + ], + "type": "string" + } + ] + }, "Volume": { "description": "Set of three values to represent the extent of a 3-D Volume. This contains the width, depth, and height values, generally used to represent some maximum or minimum.\n\nAll measurements are in millimeters.", "properties": { diff --git a/src/components/NetworkMachineIndicator.tsx b/src/components/NetworkMachineIndicator.tsx index c80a0a8c6..a2f42509d 100644 --- a/src/components/NetworkMachineIndicator.tsx +++ b/src/components/NetworkMachineIndicator.tsx @@ -11,6 +11,8 @@ export const NetworkMachineIndicator = ({ }) => { const machineCount = machineManager.machineCount() const reason = machineManager.noMachinesReason() + const machines = machineManager.machines + console.log('react machines', machines) return isDesktop() ? ( @@ -46,20 +48,29 @@ export const NetworkMachineIndicator = ({ {machineCount > 0 && (
    - {Object.entries(machineManager.machines).map( - ([hostname, machine]) => ( -
  • -

    - {machine.make_model.model || - machine.make_model.manufacturer || - 'Unknown Machine'} + {machines.map((machine) => { + return ( +

  • +

    {machine.id.toUpperCase()}

    +

    + {machine.make_model.model}

    - Hostname {hostname} + {machine.state.state.toUpperCase()} + {machine.state.state === 'failed' && machine.state.message + ? ': ' + machine.state.message + : ''}

    + {machine.extra && + machine.extra.type === 'bambu' && + machine.extra.nozzle_diameter && ( +

    + Nozzle Diameter: {machine.extra.nozzle_diameter} +

    + )}
  • ) - )} + })}
)} diff --git a/src/lib/commandBarConfigs/modelingCommandConfig.ts b/src/lib/commandBarConfigs/modelingCommandConfig.ts index 0826b28aa..93ec4a8eb 100644 --- a/src/lib/commandBarConfigs/modelingCommandConfig.ts +++ b/src/lib/commandBarConfigs/modelingCommandConfig.ts @@ -190,10 +190,17 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig< options: () => { return Object.entries(machineManager.machines).map( ([_, machine]) => ({ - name: `${machine.id} (${ - machine.make_model.model || machine.make_model.manufacturer - }) via ${machineManager.machineApiIp || 'the local network'}`, + name: + `${machine.id} (${ + machine.make_model.model || machine.make_model.manufacturer + }) (${machine.state.state})` + + (machine.extra && + machine.extra.type === 'bambu' && + machine.extra.nozzle_diameter + ? ` - Nozzle Diameter: ${machine.extra.nozzle_diameter}` + : ''), isCurrent: false, + disabled: machine.state.state !== 'idle', value: machine as components['schemas']['MachineInfoResponse'], }) ) diff --git a/src/lib/machine-api.d.ts b/src/lib/machine-api.d.ts index c692e3b90..5ab0ceec2 100644 --- a/src/lib/machine-api.d.ts +++ b/src/lib/machine-api.d.ts @@ -119,18 +119,54 @@ export interface components { /** @description Extra machine-specific information regarding a connected machine. */ ExtraMachineInfoResponse: | { - Moonraker: Record + /** @enum {string} */ + type: 'moonraker' } | { - Usb: Record + /** @enum {string} */ + type: 'usb' } | { - Bambu: Record + /** @description The current stage of the machine as defined by Bambu which can include errors, etc. */ + current_stage?: components['schemas']['Stage'] | null + /** @description The nozzle diameter of the machine. */ + nozzle_diameter: components['schemas']['NozzleDiameter'] + /** @enum {string} */ + type: 'bambu' + } + /** @description Configuration for a FDM-based printer. */ + FdmHardwareConfiguration: { + /** @description type of material being extruded */ + filament_material: components['schemas']['FilamentMaterial'] + /** + * Format: double + * @description Diameter of the extrusion nozzle, in mm. + */ + nozzle_diameter: number + } + /** @description The material that the filament is made of. */ + FilamentMaterial: + | 'Pla' + | 'Abs' + | 'Petg' + | 'Nylon' + | 'Tpu' + | 'Pva' + | 'Hips' + | 'Composite' + | 'Other' + /** @description The hardware configuration of a machine. */ + HardwareConfiguration: + | 'None' + | { + Fdm: components['schemas']['FdmHardwareConfiguration'] } /** @description Information regarding a connected machine. */ MachineInfoResponse: { /** @description Additional, per-machine information which is specific to the underlying machine type. */ extra?: components['schemas']['ExtraMachineInfoResponse'] | null + /** @description Information about how the Machine is currently configured. */ + hardware_configuration: components['schemas']['HardwareConfiguration'] /** @description Machine Identifier (ID) for the specific Machine. */ id: string /** @description Information regarding the method of manufacture. */ @@ -157,17 +193,40 @@ export interface components { } /** @description Current state of the machine -- be it printing, idle or offline. This can be used to determine if a printer is in the correct state to take a new job. */ MachineState: - | 'Unknown' - | 'Idle' - | 'Running' - | 'Offline' - | 'Paused' - | 'Complete' | { - Failed: string | null + /** @enum {string} */ + state: 'unknown' + } + | { + /** @enum {string} */ + state: 'idle' + } + | { + /** @enum {string} */ + state: 'running' + } + | { + /** @enum {string} */ + state: 'offline' + } + | { + /** @enum {string} */ + state: 'paused' + } + | { + /** @enum {string} */ + state: 'complete' + } + | { + /** @description A human-readable message describing the failure. */ + message?: string | null + /** @enum {string} */ + state: 'failed' } /** @description Specific technique by which this Machine takes a design, and produces a real-world 3D object. */ MachineType: 'Stereolithography' | 'FusedDeposition' | 'Cnc' + /** @description A nozzle diameter. */ + NozzleDiameter: '0.2' | '0.4' | '0.6' | '0.8' /** @description The response from the `/ping` endpoint. */ Pong: { /** @description The pong response. */ @@ -186,7 +245,50 @@ export interface components { job_name: string /** @description The machine id to print to. */ machine_id: string + /** @description Requested design-specific slicer configurations. */ + slicer_configuration?: components['schemas']['SlicerConfiguration'] | null } + /** @description The slicer configuration is a set of parameters that are passed to the slicer to control how the gcode is generated. */ + SlicerConfiguration: Record + /** @description The print stage. These come from: https://github.com/SoftFever/OrcaSlicer/blob/431978baf17961df90f0d01871b0ad1d839d7f5d/src/slic3r/GUI/DeviceManager.cpp#L78 */ + Stage: + | 'nothing' + | 'empty' + | 'auto_bed_leveling' + | 'heatbed_preheating' + | 'sweeping_xy_mech_mode' + | 'changing_filament' + | 'm400_pause' + | 'paused_due_to_filament_runout' + | 'heating_hotend' + | 'calibrating_extrusion' + | 'scanning_bed_surface' + | 'inspecting_first_layer' + | 'identifying_build_plate_type' + | 'calibrating_micro_lidar' + | 'homing_toolhead' + | 'cleaning_nozzle_tip' + | 'checking_extruder_temperature' + | 'printing_was_paused_by_the_user' + | 'pause_of_front_cover_falling' + | 'calibrating_micro_lidar2' + | 'calibrating_extrusion_flow' + | 'paused_due_to_nozzle_temperature_malfunction' + | 'paused_due_to_heat_bed_temperature_malfunction' + | 'filament_unloading' + | 'skip_step_pause' + | 'filament_loading' + | 'motor_noise_calibration' + | 'paused_due_to_ams_lost' + | 'paused_due_to_low_speed_of_the_heat_break_fan' + | 'paused_due_to_chamber_temperature_control_error' + | 'cooling_chamber' + | 'paused_by_the_gcode_inserted_by_the_user' + | 'motor_noise_showoff' + | 'nozzle_filament_covered_detected_pause' + | 'cutter_error_pause' + | 'first_layer_error_pause' + | 'nozzle_clog_pause' /** @description Set of three values to represent the extent of a 3-D Volume. This contains the width, depth, and height values, generally used to represent some maximum or minimum. * * All measurements are in millimeters. */ diff --git a/src/lib/machineManager.ts b/src/lib/machineManager.ts index d9a8ce09e..912204f4f 100644 --- a/src/lib/machineManager.ts +++ b/src/lib/machineManager.ts @@ -85,7 +85,11 @@ export class MachineManager { return } - this._machines = await window.electron.listMachines() + if (this._machineApiIp === null) { + return + } + + this._machines = await window.electron.listMachines(this._machineApiIp) } private async updateMachineApiIp(): Promise { diff --git a/src/main.ts b/src/main.ts index 06b73a581..9abe516d2 100644 --- a/src/main.ts +++ b/src/main.ts @@ -238,6 +238,7 @@ ipcMain.handle('find_machine_api', () => { const ip = service.addresses[0] const port = service.port // We want to return the ip address of the machine API. + console.log(`Machine API found at ${ip}:${port}`) resolve(`${ip}:${port}`) } ) diff --git a/src/preload.ts b/src/preload.ts index bed9cfca6..841eb9976 100644 --- a/src/preload.ts +++ b/src/preload.ts @@ -77,11 +77,12 @@ const kittycad = (access: string, args: any) => // We could probably do this from the renderer side, but I fear CORS will // bite our butts. -const listMachines = async (): Promise => { - const machineApi = await ipcRenderer.invoke('find_machine_api') - if (!machineApi) return [] - - return fetch(`http://${machineApi}/machines`).then((resp) => resp.json()) +const listMachines = async ( + machineApiAddr: string +): Promise => { + return fetch(`http://${machineApiAddr}/machines`).then((resp) => { + return resp.json() + }) } const getMachineApiIp = async (): Promise =>