mirror of
https://git.mirrors.martin98.com/https://github.com/Ultimaker/Cura
synced 2025-08-13 09:59:01 +08:00
Merge branch 'feature_firmware_updater' of https://github.com/fieldOfView/Cura into fieldOfView-feature_firmware_updater
This commit is contained in:
commit
7c6970bec3
78
cura/PrinterOutput/FirmwareUpdater.py
Normal file
78
cura/PrinterOutput/FirmwareUpdater.py
Normal file
@ -0,0 +1,78 @@
|
||||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from PyQt5.QtCore import QObject, QUrl, pyqtSignal, pyqtProperty
|
||||
|
||||
from enum import IntEnum
|
||||
from threading import Thread
|
||||
from typing import Union
|
||||
|
||||
MYPY = False
|
||||
if MYPY:
|
||||
from cura.PrinterOutputDevice import PrinterOutputDevice
|
||||
|
||||
class FirmwareUpdater(QObject):
|
||||
firmwareProgressChanged = pyqtSignal()
|
||||
firmwareUpdateStateChanged = pyqtSignal()
|
||||
|
||||
def __init__(self, output_device: "PrinterOutputDevice") -> None:
|
||||
super().__init__()
|
||||
|
||||
self._output_device = output_device
|
||||
|
||||
self._update_firmware_thread = Thread(target=self._updateFirmware, daemon=True)
|
||||
|
||||
self._firmware_file = ""
|
||||
self._firmware_progress = 0
|
||||
self._firmware_update_state = FirmwareUpdateState.idle
|
||||
|
||||
def updateFirmware(self, firmware_file: Union[str, QUrl]) -> None:
|
||||
# the file path could be url-encoded.
|
||||
if firmware_file.startswith("file://"):
|
||||
self._firmware_file = QUrl(firmware_file).toLocalFile()
|
||||
else:
|
||||
self._firmware_file = firmware_file
|
||||
|
||||
self._setFirmwareUpdateState(FirmwareUpdateState.updating)
|
||||
|
||||
self._update_firmware_thread.start()
|
||||
|
||||
def _updateFirmware(self) -> None:
|
||||
raise NotImplementedError("_updateFirmware needs to be implemented")
|
||||
|
||||
## Cleanup after a succesful update
|
||||
def _cleanupAfterUpdate(self) -> None:
|
||||
# Clean up for next attempt.
|
||||
self._update_firmware_thread = Thread(target=self._updateFirmware, daemon=True)
|
||||
self._firmware_file = ""
|
||||
self._onFirmwareProgress(100)
|
||||
self._setFirmwareUpdateState(FirmwareUpdateState.completed)
|
||||
|
||||
@pyqtProperty(int, notify = firmwareProgressChanged)
|
||||
def firmwareProgress(self) -> int:
|
||||
return self._firmware_progress
|
||||
|
||||
@pyqtProperty(int, notify=firmwareUpdateStateChanged)
|
||||
def firmwareUpdateState(self) -> "FirmwareUpdateState":
|
||||
return self._firmware_update_state
|
||||
|
||||
def _setFirmwareUpdateState(self, state: "FirmwareUpdateState") -> None:
|
||||
if self._firmware_update_state != state:
|
||||
self._firmware_update_state = state
|
||||
self.firmwareUpdateStateChanged.emit()
|
||||
|
||||
# Callback function for firmware update progress.
|
||||
def _onFirmwareProgress(self, progress: int, max_progress: int = 100) -> None:
|
||||
self._firmware_progress = int(progress * 100 / max_progress) # Convert to scale of 0-100
|
||||
self.firmwareProgressChanged.emit()
|
||||
|
||||
|
||||
class FirmwareUpdateState(IntEnum):
|
||||
idle = 0
|
||||
updating = 1
|
||||
completed = 2
|
||||
unknown_error = 3
|
||||
communication_error = 4
|
||||
io_error = 5
|
||||
firmware_not_found_error = 6
|
||||
|
@ -1,7 +1,7 @@
|
||||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import TYPE_CHECKING, Set, Union, Optional
|
||||
|
||||
from cura.PrinterOutput.PrinterOutputController import PrinterOutputController
|
||||
from PyQt5.QtCore import QTimer
|
||||
@ -9,27 +9,28 @@ from PyQt5.QtCore import QTimer
|
||||
if TYPE_CHECKING:
|
||||
from cura.PrinterOutput.PrintJobOutputModel import PrintJobOutputModel
|
||||
from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel
|
||||
from cura.PrinterOutput.PrinterOutputDevice import PrinterOutputDevice
|
||||
from cura.PrinterOutput.ExtruderOutputModel import ExtruderOutputModel
|
||||
|
||||
|
||||
class GenericOutputController(PrinterOutputController):
|
||||
def __init__(self, output_device):
|
||||
def __init__(self, output_device: "PrinterOutputDevice") -> None:
|
||||
super().__init__(output_device)
|
||||
|
||||
self._preheat_bed_timer = QTimer()
|
||||
self._preheat_bed_timer.setSingleShot(True)
|
||||
self._preheat_bed_timer.timeout.connect(self._onPreheatBedTimerFinished)
|
||||
self._preheat_printer = None
|
||||
self._preheat_printer = None # type: Optional[PrinterOutputModel]
|
||||
|
||||
self._preheat_hotends_timer = QTimer()
|
||||
self._preheat_hotends_timer.setSingleShot(True)
|
||||
self._preheat_hotends_timer.timeout.connect(self._onPreheatHotendsTimerFinished)
|
||||
self._preheat_hotends = set()
|
||||
self._preheat_hotends = set() # type: Set[ExtruderOutputModel]
|
||||
|
||||
self._output_device.printersChanged.connect(self._onPrintersChanged)
|
||||
self._active_printer = None
|
||||
self._active_printer = None # type: Optional[PrinterOutputModel]
|
||||
|
||||
def _onPrintersChanged(self):
|
||||
def _onPrintersChanged(self) -> None:
|
||||
if self._active_printer:
|
||||
self._active_printer.stateChanged.disconnect(self._onPrinterStateChanged)
|
||||
self._active_printer.targetBedTemperatureChanged.disconnect(self._onTargetBedTemperatureChanged)
|
||||
@ -43,32 +44,33 @@ class GenericOutputController(PrinterOutputController):
|
||||
for extruder in self._active_printer.extruders:
|
||||
extruder.targetHotendTemperatureChanged.connect(self._onTargetHotendTemperatureChanged)
|
||||
|
||||
def _onPrinterStateChanged(self):
|
||||
if self._active_printer.state != "idle":
|
||||
def _onPrinterStateChanged(self) -> None:
|
||||
if self._active_printer and self._active_printer.state != "idle":
|
||||
if self._preheat_bed_timer.isActive():
|
||||
self._preheat_bed_timer.stop()
|
||||
self._preheat_printer.updateIsPreheating(False)
|
||||
if self._preheat_printer:
|
||||
self._preheat_printer.updateIsPreheating(False)
|
||||
if self._preheat_hotends_timer.isActive():
|
||||
self._preheat_hotends_timer.stop()
|
||||
for extruder in self._preheat_hotends:
|
||||
extruder.updateIsPreheating(False)
|
||||
self._preheat_hotends = set()
|
||||
self._preheat_hotends = set() # type: Set[ExtruderOutputModel]
|
||||
|
||||
def moveHead(self, printer: "PrinterOutputModel", x, y, z, speed):
|
||||
def moveHead(self, printer: "PrinterOutputModel", x, y, z, speed) -> None:
|
||||
self._output_device.sendCommand("G91")
|
||||
self._output_device.sendCommand("G0 X%s Y%s Z%s F%s" % (x, y, z, speed))
|
||||
self._output_device.sendCommand("G90")
|
||||
|
||||
def homeHead(self, printer):
|
||||
def homeHead(self, printer: "PrinterOutputModel") -> None:
|
||||
self._output_device.sendCommand("G28 X Y")
|
||||
|
||||
def homeBed(self, printer):
|
||||
def homeBed(self, printer: "PrinterOutputModel") -> None:
|
||||
self._output_device.sendCommand("G28 Z")
|
||||
|
||||
def sendRawCommand(self, printer: "PrinterOutputModel", command: str):
|
||||
def sendRawCommand(self, printer: "PrinterOutputModel", command: str) -> None:
|
||||
self._output_device.sendCommand(command.upper()) #Most printers only understand uppercase g-code commands.
|
||||
|
||||
def setJobState(self, job: "PrintJobOutputModel", state: str):
|
||||
def setJobState(self, job: "PrintJobOutputModel", state: str) -> None:
|
||||
if state == "pause":
|
||||
self._output_device.pausePrint()
|
||||
job.updateState("paused")
|
||||
@ -79,15 +81,15 @@ class GenericOutputController(PrinterOutputController):
|
||||
self._output_device.cancelPrint()
|
||||
pass
|
||||
|
||||
def setTargetBedTemperature(self, printer: "PrinterOutputModel", temperature: int):
|
||||
def setTargetBedTemperature(self, printer: "PrinterOutputModel", temperature: int) -> None:
|
||||
self._output_device.sendCommand("M140 S%s" % temperature)
|
||||
|
||||
def _onTargetBedTemperatureChanged(self):
|
||||
if self._preheat_bed_timer.isActive() and self._preheat_printer.targetBedTemperature == 0:
|
||||
def _onTargetBedTemperatureChanged(self) -> None:
|
||||
if self._preheat_bed_timer.isActive() and self._preheat_printer and self._preheat_printer.targetBedTemperature == 0:
|
||||
self._preheat_bed_timer.stop()
|
||||
self._preheat_printer.updateIsPreheating(False)
|
||||
|
||||
def preheatBed(self, printer: "PrinterOutputModel", temperature, duration):
|
||||
def preheatBed(self, printer: "PrinterOutputModel", temperature, duration) -> None:
|
||||
try:
|
||||
temperature = round(temperature) # The API doesn't allow floating point.
|
||||
duration = round(duration)
|
||||
@ -100,21 +102,25 @@ class GenericOutputController(PrinterOutputController):
|
||||
self._preheat_printer = printer
|
||||
printer.updateIsPreheating(True)
|
||||
|
||||
def cancelPreheatBed(self, printer: "PrinterOutputModel"):
|
||||
def cancelPreheatBed(self, printer: "PrinterOutputModel") -> None:
|
||||
self.setTargetBedTemperature(printer, temperature=0)
|
||||
self._preheat_bed_timer.stop()
|
||||
printer.updateIsPreheating(False)
|
||||
|
||||
def _onPreheatBedTimerFinished(self):
|
||||
def _onPreheatBedTimerFinished(self) -> None:
|
||||
if not self._preheat_printer:
|
||||
return
|
||||
self.setTargetBedTemperature(self._preheat_printer, 0)
|
||||
self._preheat_printer.updateIsPreheating(False)
|
||||
|
||||
def setTargetHotendTemperature(self, printer: "PrinterOutputModel", position: int, temperature: int):
|
||||
def setTargetHotendTemperature(self, printer: "PrinterOutputModel", position: int, temperature: Union[int, float]) -> None:
|
||||
self._output_device.sendCommand("M104 S%s T%s" % (temperature, position))
|
||||
|
||||
def _onTargetHotendTemperatureChanged(self):
|
||||
def _onTargetHotendTemperatureChanged(self) -> None:
|
||||
if not self._preheat_hotends_timer.isActive():
|
||||
return
|
||||
if not self._active_printer:
|
||||
return
|
||||
|
||||
for extruder in self._active_printer.extruders:
|
||||
if extruder in self._preheat_hotends and extruder.targetHotendTemperature == 0:
|
||||
@ -123,7 +129,7 @@ class GenericOutputController(PrinterOutputController):
|
||||
if not self._preheat_hotends:
|
||||
self._preheat_hotends_timer.stop()
|
||||
|
||||
def preheatHotend(self, extruder: "ExtruderOutputModel", temperature, duration):
|
||||
def preheatHotend(self, extruder: "ExtruderOutputModel", temperature, duration) -> None:
|
||||
position = extruder.getPosition()
|
||||
number_of_extruders = len(extruder.getPrinter().extruders)
|
||||
if position >= number_of_extruders:
|
||||
@ -141,7 +147,7 @@ class GenericOutputController(PrinterOutputController):
|
||||
self._preheat_hotends.add(extruder)
|
||||
extruder.updateIsPreheating(True)
|
||||
|
||||
def cancelPreheatHotend(self, extruder: "ExtruderOutputModel"):
|
||||
def cancelPreheatHotend(self, extruder: "ExtruderOutputModel") -> None:
|
||||
self.setTargetHotendTemperature(extruder.getPrinter(), extruder.getPosition(), temperature=0)
|
||||
if extruder in self._preheat_hotends:
|
||||
extruder.updateIsPreheating(False)
|
||||
@ -149,21 +155,22 @@ class GenericOutputController(PrinterOutputController):
|
||||
if not self._preheat_hotends and self._preheat_hotends_timer.isActive():
|
||||
self._preheat_hotends_timer.stop()
|
||||
|
||||
def _onPreheatHotendsTimerFinished(self):
|
||||
def _onPreheatHotendsTimerFinished(self) -> None:
|
||||
for extruder in self._preheat_hotends:
|
||||
self.setTargetHotendTemperature(extruder.getPrinter(), extruder.getPosition(), 0)
|
||||
self._preheat_hotends = set()
|
||||
self._preheat_hotends = set() #type: Set[ExtruderOutputModel]
|
||||
|
||||
# Cancel any ongoing preheating timers, without setting back the temperature to 0
|
||||
# This can be used eg at the start of a print
|
||||
def stopPreheatTimers(self):
|
||||
def stopPreheatTimers(self) -> None:
|
||||
if self._preheat_hotends_timer.isActive():
|
||||
for extruder in self._preheat_hotends:
|
||||
extruder.updateIsPreheating(False)
|
||||
self._preheat_hotends = set()
|
||||
self._preheat_hotends = set() #type: Set[ExtruderOutputModel]
|
||||
|
||||
self._preheat_hotends_timer.stop()
|
||||
|
||||
if self._preheat_bed_timer.isActive():
|
||||
self._preheat_printer.updateIsPreheating(False)
|
||||
if self._preheat_printer:
|
||||
self._preheat_printer.updateIsPreheating(False)
|
||||
self._preheat_bed_timer.stop()
|
||||
|
@ -91,7 +91,7 @@ class PrintJobOutputModel(QObject):
|
||||
def assignedPrinter(self):
|
||||
return self._assigned_printer
|
||||
|
||||
def updateAssignedPrinter(self, assigned_printer: "PrinterOutputModel"):
|
||||
def updateAssignedPrinter(self, assigned_printer: Optional["PrinterOutputModel"]) -> None:
|
||||
if self._assigned_printer != assigned_printer:
|
||||
old_printer = self._assigned_printer
|
||||
self._assigned_printer = assigned_printer
|
||||
|
@ -1,57 +1,68 @@
|
||||
# Copyright (c) 2017 Ultimaker B.V.
|
||||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from UM.Logger import Logger
|
||||
from UM.Signal import Signal
|
||||
|
||||
from typing import Union
|
||||
|
||||
MYPY = False
|
||||
if MYPY:
|
||||
from cura.PrinterOutput.PrintJobOutputModel import PrintJobOutputModel
|
||||
from cura.PrinterOutput.ExtruderOutputModel import ExtruderOutputModel
|
||||
from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel
|
||||
from cura.PrinterOutput.PrinterOutputDevice import PrinterOutputDevice
|
||||
|
||||
|
||||
class PrinterOutputController:
|
||||
def __init__(self, output_device):
|
||||
def __init__(self, output_device: "PrinterOutputDevice") -> None:
|
||||
self.can_pause = True
|
||||
self.can_abort = True
|
||||
self.can_pre_heat_bed = True
|
||||
self.can_pre_heat_hotends = True
|
||||
self.can_send_raw_gcode = True
|
||||
self.can_control_manually = True
|
||||
self.can_update_firmware = False
|
||||
self._output_device = output_device
|
||||
|
||||
def setTargetHotendTemperature(self, printer: "PrinterOutputModel", extruder: "ExtruderOutputModel", temperature: int):
|
||||
def setTargetHotendTemperature(self, printer: "PrinterOutputModel", position: int, temperature: Union[int, float]) -> None:
|
||||
Logger.log("w", "Set target hotend temperature not implemented in controller")
|
||||
|
||||
def setTargetBedTemperature(self, printer: "PrinterOutputModel", temperature: int):
|
||||
def setTargetBedTemperature(self, printer: "PrinterOutputModel", temperature: int) -> None:
|
||||
Logger.log("w", "Set target bed temperature not implemented in controller")
|
||||
|
||||
def setJobState(self, job: "PrintJobOutputModel", state: str):
|
||||
def setJobState(self, job: "PrintJobOutputModel", state: str) -> None:
|
||||
Logger.log("w", "Set job state not implemented in controller")
|
||||
|
||||
def cancelPreheatBed(self, printer: "PrinterOutputModel"):
|
||||
def cancelPreheatBed(self, printer: "PrinterOutputModel") -> None:
|
||||
Logger.log("w", "Cancel preheat bed not implemented in controller")
|
||||
|
||||
def preheatBed(self, printer: "PrinterOutputModel", temperature, duration):
|
||||
def preheatBed(self, printer: "PrinterOutputModel", temperature, duration) -> None:
|
||||
Logger.log("w", "Preheat bed not implemented in controller")
|
||||
|
||||
def cancelPreheatHotend(self, extruder: "ExtruderOutputModel"):
|
||||
def cancelPreheatHotend(self, extruder: "ExtruderOutputModel") -> None:
|
||||
Logger.log("w", "Cancel preheat hotend not implemented in controller")
|
||||
|
||||
def preheatHotend(self, extruder: "ExtruderOutputModel", temperature, duration):
|
||||
def preheatHotend(self, extruder: "ExtruderOutputModel", temperature, duration) -> None:
|
||||
Logger.log("w", "Preheat hotend not implemented in controller")
|
||||
|
||||
def setHeadPosition(self, printer: "PrinterOutputModel", x, y, z, speed):
|
||||
def setHeadPosition(self, printer: "PrinterOutputModel", x, y, z, speed) -> None:
|
||||
Logger.log("w", "Set head position not implemented in controller")
|
||||
|
||||
def moveHead(self, printer: "PrinterOutputModel", x, y, z, speed):
|
||||
def moveHead(self, printer: "PrinterOutputModel", x, y, z, speed) -> None:
|
||||
Logger.log("w", "Move head not implemented in controller")
|
||||
|
||||
def homeBed(self, printer: "PrinterOutputModel"):
|
||||
def homeBed(self, printer: "PrinterOutputModel") -> None:
|
||||
Logger.log("w", "Home bed not implemented in controller")
|
||||
|
||||
def homeHead(self, printer: "PrinterOutputModel"):
|
||||
def homeHead(self, printer: "PrinterOutputModel") -> None:
|
||||
Logger.log("w", "Home head not implemented in controller")
|
||||
|
||||
def sendRawCommand(self, printer: "PrinterOutputModel", command: str):
|
||||
def sendRawCommand(self, printer: "PrinterOutputModel", command: str) -> None:
|
||||
Logger.log("w", "Custom command not implemented in controller")
|
||||
|
||||
canUpdateFirmwareChanged = Signal()
|
||||
def setCanUpdateFirmware(self, can_update_firmware: bool) -> None:
|
||||
if can_update_firmware != self.can_update_firmware:
|
||||
self.can_update_firmware = can_update_firmware
|
||||
self.canUpdateFirmwareChanged.emit()
|
@ -2,7 +2,7 @@
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from PyQt5.QtCore import pyqtSignal, pyqtProperty, QObject, QVariant, pyqtSlot
|
||||
from typing import Optional
|
||||
from typing import List, Dict, Optional
|
||||
from UM.Math.Vector import Vector
|
||||
from cura.PrinterOutput.ConfigurationModel import ConfigurationModel
|
||||
from cura.PrinterOutput.ExtruderOutputModel import ExtruderOutputModel
|
||||
@ -11,6 +11,7 @@ MYPY = False
|
||||
if MYPY:
|
||||
from cura.PrinterOutput.PrintJobOutputModel import PrintJobOutputModel
|
||||
from cura.PrinterOutput.PrinterOutputController import PrinterOutputController
|
||||
from cura.PrinterOutput.NetworkCamera import NetworkCamera
|
||||
|
||||
|
||||
class PrinterOutputModel(QObject):
|
||||
@ -26,6 +27,7 @@ class PrinterOutputModel(QObject):
|
||||
buildplateChanged = pyqtSignal()
|
||||
cameraChanged = pyqtSignal()
|
||||
configurationChanged = pyqtSignal()
|
||||
canUpdateFirmwareChanged = pyqtSignal()
|
||||
|
||||
def __init__(self, output_controller: "PrinterOutputController", number_of_extruders: int = 1, parent=None, firmware_version = "") -> None:
|
||||
super().__init__(parent)
|
||||
@ -34,6 +36,7 @@ class PrinterOutputModel(QObject):
|
||||
self._name = ""
|
||||
self._key = "" # Unique identifier
|
||||
self._controller = output_controller
|
||||
self._controller.canUpdateFirmwareChanged.connect(self._onControllerCanUpdateFirmwareChanged)
|
||||
self._extruders = [ExtruderOutputModel(printer = self, position = i) for i in range(number_of_extruders)]
|
||||
self._printer_configuration = ConfigurationModel() # Indicates the current configuration setup in this printer
|
||||
self._head_position = Vector(0, 0, 0)
|
||||
@ -42,7 +45,7 @@ class PrinterOutputModel(QObject):
|
||||
self._printer_state = "unknown"
|
||||
self._is_preheating = False
|
||||
self._printer_type = ""
|
||||
self._buildplate_name = None
|
||||
self._buildplate_name = ""
|
||||
|
||||
self._printer_configuration.extruderConfigurations = [extruder.extruderConfiguration for extruder in
|
||||
self._extruders]
|
||||
@ -50,32 +53,32 @@ class PrinterOutputModel(QObject):
|
||||
self._camera = None
|
||||
|
||||
@pyqtProperty(str, constant = True)
|
||||
def firmwareVersion(self):
|
||||
def firmwareVersion(self) -> str:
|
||||
return self._firmware_version
|
||||
|
||||
def setCamera(self, camera):
|
||||
def setCamera(self, camera: Optional["NetworkCamera"]) -> None:
|
||||
if self._camera is not camera:
|
||||
self._camera = camera
|
||||
self.cameraChanged.emit()
|
||||
|
||||
def updateIsPreheating(self, pre_heating):
|
||||
def updateIsPreheating(self, pre_heating: bool) -> None:
|
||||
if self._is_preheating != pre_heating:
|
||||
self._is_preheating = pre_heating
|
||||
self.isPreheatingChanged.emit()
|
||||
|
||||
@pyqtProperty(bool, notify=isPreheatingChanged)
|
||||
def isPreheating(self):
|
||||
def isPreheating(self) -> bool:
|
||||
return self._is_preheating
|
||||
|
||||
@pyqtProperty(QObject, notify=cameraChanged)
|
||||
def camera(self):
|
||||
def camera(self) -> Optional["NetworkCamera"]:
|
||||
return self._camera
|
||||
|
||||
@pyqtProperty(str, notify = printerTypeChanged)
|
||||
def type(self):
|
||||
def type(self) -> str:
|
||||
return self._printer_type
|
||||
|
||||
def updateType(self, printer_type):
|
||||
def updateType(self, printer_type: str) -> None:
|
||||
if self._printer_type != printer_type:
|
||||
self._printer_type = printer_type
|
||||
self._printer_configuration.printerType = self._printer_type
|
||||
@ -83,10 +86,10 @@ class PrinterOutputModel(QObject):
|
||||
self.configurationChanged.emit()
|
||||
|
||||
@pyqtProperty(str, notify = buildplateChanged)
|
||||
def buildplate(self):
|
||||
def buildplate(self) -> str:
|
||||
return self._buildplate_name
|
||||
|
||||
def updateBuildplateName(self, buildplate_name):
|
||||
def updateBuildplateName(self, buildplate_name: str) -> None:
|
||||
if self._buildplate_name != buildplate_name:
|
||||
self._buildplate_name = buildplate_name
|
||||
self._printer_configuration.buildplateConfiguration = self._buildplate_name
|
||||
@ -94,66 +97,66 @@ class PrinterOutputModel(QObject):
|
||||
self.configurationChanged.emit()
|
||||
|
||||
@pyqtProperty(str, notify=keyChanged)
|
||||
def key(self):
|
||||
def key(self) -> str:
|
||||
return self._key
|
||||
|
||||
def updateKey(self, key: str):
|
||||
def updateKey(self, key: str) -> None:
|
||||
if self._key != key:
|
||||
self._key = key
|
||||
self.keyChanged.emit()
|
||||
|
||||
@pyqtSlot()
|
||||
def homeHead(self):
|
||||
def homeHead(self) -> None:
|
||||
self._controller.homeHead(self)
|
||||
|
||||
@pyqtSlot()
|
||||
def homeBed(self):
|
||||
def homeBed(self) -> None:
|
||||
self._controller.homeBed(self)
|
||||
|
||||
@pyqtSlot(str)
|
||||
def sendRawCommand(self, command: str):
|
||||
def sendRawCommand(self, command: str) -> None:
|
||||
self._controller.sendRawCommand(self, command)
|
||||
|
||||
@pyqtProperty("QVariantList", constant = True)
|
||||
def extruders(self):
|
||||
def extruders(self) -> List["ExtruderOutputModel"]:
|
||||
return self._extruders
|
||||
|
||||
@pyqtProperty(QVariant, notify = headPositionChanged)
|
||||
def headPosition(self):
|
||||
def headPosition(self) -> Dict[str, float]:
|
||||
return {"x": self._head_position.x, "y": self._head_position.y, "z": self.head_position.z}
|
||||
|
||||
def updateHeadPosition(self, x, y, z):
|
||||
def updateHeadPosition(self, x: float, y: float, z: float) -> None:
|
||||
if self._head_position.x != x or self._head_position.y != y or self._head_position.z != z:
|
||||
self._head_position = Vector(x, y, z)
|
||||
self.headPositionChanged.emit()
|
||||
|
||||
@pyqtProperty(float, float, float)
|
||||
@pyqtProperty(float, float, float, float)
|
||||
def setHeadPosition(self, x, y, z, speed = 3000):
|
||||
def setHeadPosition(self, x: float, y: float, z: float, speed: float = 3000) -> None:
|
||||
self.updateHeadPosition(x, y, z)
|
||||
self._controller.setHeadPosition(self, x, y, z, speed)
|
||||
|
||||
@pyqtProperty(float)
|
||||
@pyqtProperty(float, float)
|
||||
def setHeadX(self, x, speed = 3000):
|
||||
def setHeadX(self, x: float, speed: float = 3000) -> None:
|
||||
self.updateHeadPosition(x, self._head_position.y, self._head_position.z)
|
||||
self._controller.setHeadPosition(self, x, self._head_position.y, self._head_position.z, speed)
|
||||
|
||||
@pyqtProperty(float)
|
||||
@pyqtProperty(float, float)
|
||||
def setHeadY(self, y, speed = 3000):
|
||||
def setHeadY(self, y: float, speed: float = 3000) -> None:
|
||||
self.updateHeadPosition(self._head_position.x, y, self._head_position.z)
|
||||
self._controller.setHeadPosition(self, self._head_position.x, y, self._head_position.z, speed)
|
||||
|
||||
@pyqtProperty(float)
|
||||
@pyqtProperty(float, float)
|
||||
def setHeadZ(self, z, speed = 3000):
|
||||
def setHeadZ(self, z: float, speed:float = 3000) -> None:
|
||||
self.updateHeadPosition(self._head_position.x, self._head_position.y, z)
|
||||
self._controller.setHeadPosition(self, self._head_position.x, self._head_position.y, z, speed)
|
||||
|
||||
@pyqtSlot(float, float, float)
|
||||
@pyqtSlot(float, float, float, float)
|
||||
def moveHead(self, x = 0, y = 0, z = 0, speed = 3000):
|
||||
def moveHead(self, x: float = 0, y: float = 0, z: float = 0, speed: float = 3000) -> None:
|
||||
self._controller.moveHead(self, x, y, z, speed)
|
||||
|
||||
## Pre-heats the heated bed of the printer.
|
||||
@ -162,47 +165,47 @@ class PrinterOutputModel(QObject):
|
||||
# Celsius.
|
||||
# \param duration How long the bed should stay warm, in seconds.
|
||||
@pyqtSlot(float, float)
|
||||
def preheatBed(self, temperature, duration):
|
||||
def preheatBed(self, temperature: float, duration: float) -> None:
|
||||
self._controller.preheatBed(self, temperature, duration)
|
||||
|
||||
@pyqtSlot()
|
||||
def cancelPreheatBed(self):
|
||||
def cancelPreheatBed(self) -> None:
|
||||
self._controller.cancelPreheatBed(self)
|
||||
|
||||
def getController(self):
|
||||
def getController(self) -> "PrinterOutputController":
|
||||
return self._controller
|
||||
|
||||
@pyqtProperty(str, notify=nameChanged)
|
||||
def name(self):
|
||||
def name(self) -> str:
|
||||
return self._name
|
||||
|
||||
def setName(self, name):
|
||||
def setName(self, name: str) -> None:
|
||||
self._setName(name)
|
||||
self.updateName(name)
|
||||
|
||||
def updateName(self, name):
|
||||
def updateName(self, name: str) -> None:
|
||||
if self._name != name:
|
||||
self._name = name
|
||||
self.nameChanged.emit()
|
||||
|
||||
## Update the bed temperature. This only changes it locally.
|
||||
def updateBedTemperature(self, temperature):
|
||||
def updateBedTemperature(self, temperature: int) -> None:
|
||||
if self._bed_temperature != temperature:
|
||||
self._bed_temperature = temperature
|
||||
self.bedTemperatureChanged.emit()
|
||||
|
||||
def updateTargetBedTemperature(self, temperature):
|
||||
def updateTargetBedTemperature(self, temperature: int) -> None:
|
||||
if self._target_bed_temperature != temperature:
|
||||
self._target_bed_temperature = temperature
|
||||
self.targetBedTemperatureChanged.emit()
|
||||
|
||||
## Set the target bed temperature. This ensures that it's actually sent to the remote.
|
||||
@pyqtSlot(int)
|
||||
def setTargetBedTemperature(self, temperature):
|
||||
def setTargetBedTemperature(self, temperature: int) -> None:
|
||||
self._controller.setTargetBedTemperature(self, temperature)
|
||||
self.updateTargetBedTemperature(temperature)
|
||||
|
||||
def updateActivePrintJob(self, print_job):
|
||||
def updateActivePrintJob(self, print_job: Optional["PrintJobOutputModel"]) -> None:
|
||||
if self._active_print_job != print_job:
|
||||
old_print_job = self._active_print_job
|
||||
|
||||
@ -214,72 +217,83 @@ class PrinterOutputModel(QObject):
|
||||
old_print_job.updateAssignedPrinter(None)
|
||||
self.activePrintJobChanged.emit()
|
||||
|
||||
def updateState(self, printer_state):
|
||||
def updateState(self, printer_state: str) -> None:
|
||||
if self._printer_state != printer_state:
|
||||
self._printer_state = printer_state
|
||||
self.stateChanged.emit()
|
||||
|
||||
@pyqtProperty(QObject, notify = activePrintJobChanged)
|
||||
def activePrintJob(self):
|
||||
def activePrintJob(self) -> Optional["PrintJobOutputModel"]:
|
||||
return self._active_print_job
|
||||
|
||||
@pyqtProperty(str, notify=stateChanged)
|
||||
def state(self):
|
||||
def state(self) -> str:
|
||||
return self._printer_state
|
||||
|
||||
@pyqtProperty(int, notify = bedTemperatureChanged)
|
||||
def bedTemperature(self):
|
||||
@pyqtProperty(int, notify=bedTemperatureChanged)
|
||||
def bedTemperature(self) -> int:
|
||||
return self._bed_temperature
|
||||
|
||||
@pyqtProperty(int, notify=targetBedTemperatureChanged)
|
||||
def targetBedTemperature(self):
|
||||
def targetBedTemperature(self) -> int:
|
||||
return self._target_bed_temperature
|
||||
|
||||
# Does the printer support pre-heating the bed at all
|
||||
@pyqtProperty(bool, constant=True)
|
||||
def canPreHeatBed(self):
|
||||
def canPreHeatBed(self) -> bool:
|
||||
if self._controller:
|
||||
return self._controller.can_pre_heat_bed
|
||||
return False
|
||||
|
||||
# Does the printer support pre-heating the bed at all
|
||||
@pyqtProperty(bool, constant=True)
|
||||
def canPreHeatHotends(self):
|
||||
def canPreHeatHotends(self) -> bool:
|
||||
if self._controller:
|
||||
return self._controller.can_pre_heat_hotends
|
||||
return False
|
||||
|
||||
# Does the printer support sending raw G-code at all
|
||||
@pyqtProperty(bool, constant=True)
|
||||
def canSendRawGcode(self):
|
||||
def canSendRawGcode(self) -> bool:
|
||||
if self._controller:
|
||||
return self._controller.can_send_raw_gcode
|
||||
return False
|
||||
|
||||
# Does the printer support pause at all
|
||||
@pyqtProperty(bool, constant=True)
|
||||
def canPause(self):
|
||||
def canPause(self) -> bool:
|
||||
if self._controller:
|
||||
return self._controller.can_pause
|
||||
return False
|
||||
|
||||
# Does the printer support abort at all
|
||||
@pyqtProperty(bool, constant=True)
|
||||
def canAbort(self):
|
||||
def canAbort(self) -> bool:
|
||||
if self._controller:
|
||||
return self._controller.can_abort
|
||||
return False
|
||||
|
||||
# Does the printer support manual control at all
|
||||
@pyqtProperty(bool, constant=True)
|
||||
def canControlManually(self):
|
||||
def canControlManually(self) -> bool:
|
||||
if self._controller:
|
||||
return self._controller.can_control_manually
|
||||
return False
|
||||
|
||||
# Does the printer support upgrading firmware
|
||||
@pyqtProperty(bool, notify = canUpdateFirmwareChanged)
|
||||
def canUpdateFirmware(self) -> bool:
|
||||
if self._controller:
|
||||
return self._controller.can_update_firmware
|
||||
return False
|
||||
|
||||
# Stub to connect UM.Signal to pyqtSignal
|
||||
def _onControllerCanUpdateFirmwareChanged(self) -> None:
|
||||
self.canUpdateFirmwareChanged.emit()
|
||||
|
||||
# Returns the configuration (material, variant and buildplate) of the current printer
|
||||
@pyqtProperty(QObject, notify = configurationChanged)
|
||||
def printerConfiguration(self):
|
||||
def printerConfiguration(self) -> Optional[ConfigurationModel]:
|
||||
if self._printer_configuration.isValid():
|
||||
return self._printer_configuration
|
||||
return None
|
@ -4,22 +4,24 @@
|
||||
from UM.Decorators import deprecated
|
||||
from UM.i18n import i18nCatalog
|
||||
from UM.OutputDevice.OutputDevice import OutputDevice
|
||||
from PyQt5.QtCore import pyqtProperty, QObject, QTimer, pyqtSignal
|
||||
from PyQt5.QtCore import pyqtProperty, pyqtSignal, QObject, QTimer, QUrl
|
||||
from PyQt5.QtWidgets import QMessageBox
|
||||
|
||||
from UM.Logger import Logger
|
||||
from UM.FileHandler.FileHandler import FileHandler #For typing.
|
||||
from UM.Scene.SceneNode import SceneNode #For typing.
|
||||
from UM.Signal import signalemitter
|
||||
from UM.Qt.QtApplication import QtApplication
|
||||
from UM.FlameProfiler import pyqtSlot
|
||||
|
||||
from enum import IntEnum # For the connection state tracking.
|
||||
from typing import Callable, List, Optional
|
||||
from typing import Callable, List, Optional, Union
|
||||
|
||||
MYPY = False
|
||||
if MYPY:
|
||||
from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel
|
||||
from cura.PrinterOutput.ConfigurationModel import ConfigurationModel
|
||||
from cura.PrinterOutput.FirmwareUpdater import FirmwareUpdater
|
||||
from UM.FileHandler.FileHandler import FileHandler
|
||||
from UM.Scene.SceneNode import SceneNode
|
||||
|
||||
i18n_catalog = i18nCatalog("cura")
|
||||
|
||||
@ -83,6 +85,7 @@ class PrinterOutputDevice(QObject, OutputDevice):
|
||||
|
||||
self._connection_state = ConnectionState.closed #type: ConnectionState
|
||||
|
||||
self._firmware_updater = None #type: Optional[FirmwareUpdater]
|
||||
self._firmware_name = None #type: Optional[str]
|
||||
self._address = "" #type: str
|
||||
self._connection_text = "" #type: str
|
||||
@ -128,7 +131,7 @@ class PrinterOutputDevice(QObject, OutputDevice):
|
||||
|
||||
return None
|
||||
|
||||
def requestWrite(self, nodes: List[SceneNode], file_name: Optional[str] = None, limit_mimetypes: bool = False, file_handler: Optional[FileHandler] = None, **kwargs: str) -> None:
|
||||
def requestWrite(self, nodes: List["SceneNode"], file_name: Optional[str] = None, limit_mimetypes: bool = False, file_handler: Optional["FileHandler"] = None, **kwargs: str) -> None:
|
||||
raise NotImplementedError("requestWrite needs to be implemented")
|
||||
|
||||
@pyqtProperty(QObject, notify = printersChanged)
|
||||
@ -225,4 +228,14 @@ class PrinterOutputDevice(QObject, OutputDevice):
|
||||
#
|
||||
# This name can be used to define device type
|
||||
def getFirmwareName(self) -> Optional[str]:
|
||||
return self._firmware_name
|
||||
return self._firmware_name
|
||||
|
||||
def getFirmwareUpdater(self) -> Optional["FirmwareUpdater"]:
|
||||
return self._firmware_updater
|
||||
|
||||
@pyqtSlot(str)
|
||||
def updateFirmware(self, firmware_file: Union[str, QUrl]) -> None:
|
||||
if not self._firmware_updater:
|
||||
return
|
||||
|
||||
self._firmware_updater.updateFirmware(firmware_file)
|
@ -4,7 +4,7 @@
|
||||
from collections import defaultdict
|
||||
import threading
|
||||
from typing import Any, Dict, Optional, Set, TYPE_CHECKING
|
||||
from PyQt5.QtCore import pyqtProperty
|
||||
from PyQt5.QtCore import pyqtProperty, pyqtSlot
|
||||
|
||||
from UM.Decorators import override
|
||||
from UM.MimeTypeDatabase import MimeType, MimeTypeDatabase
|
||||
@ -13,6 +13,8 @@ from UM.Settings.SettingInstance import InstanceState
|
||||
from UM.Settings.ContainerRegistry import ContainerRegistry
|
||||
from UM.Settings.Interfaces import PropertyEvaluationContext
|
||||
from UM.Logger import Logger
|
||||
from UM.Resources import Resources
|
||||
from UM.Platform import Platform
|
||||
from UM.Util import parseBool
|
||||
|
||||
import cura.CuraApplication
|
||||
@ -200,6 +202,31 @@ class GlobalStack(CuraContainerStack):
|
||||
def getHasMachineQuality(self) -> bool:
|
||||
return parseBool(self.getMetaDataEntry("has_machine_quality", False))
|
||||
|
||||
## Get default firmware file name if one is specified in the firmware
|
||||
@pyqtSlot(result = str)
|
||||
def getDefaultFirmwareName(self) -> str:
|
||||
machine_has_heated_bed = self.getProperty("machine_heated_bed", "value")
|
||||
|
||||
baudrate = 250000
|
||||
if Platform.isLinux():
|
||||
# Linux prefers a baudrate of 115200 here because older versions of
|
||||
# pySerial did not support a baudrate of 250000
|
||||
baudrate = 115200
|
||||
|
||||
# If a firmware file is available, it should be specified in the definition for the printer
|
||||
hex_file = self.getMetaDataEntry("firmware_file", None)
|
||||
if machine_has_heated_bed:
|
||||
hex_file = self.getMetaDataEntry("firmware_hbk_file", hex_file)
|
||||
|
||||
if not hex_file:
|
||||
Logger.log("w", "There is no firmware for machine %s.", self.getBottom().id)
|
||||
return ""
|
||||
|
||||
try:
|
||||
return Resources.getPath(cura.CuraApplication.CuraApplication.ResourceTypes.Firmware, hex_file.format(baudrate=baudrate))
|
||||
except FileNotFoundError:
|
||||
Logger.log("w", "Firmware file %s not found.", hex_file)
|
||||
return ""
|
||||
|
||||
## private:
|
||||
global_stack_mime = MimeType(
|
||||
|
69
plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.py
Normal file
69
plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.py
Normal file
@ -0,0 +1,69 @@
|
||||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from cura.CuraApplication import CuraApplication
|
||||
from UM.Settings.DefinitionContainer import DefinitionContainer
|
||||
from cura.MachineAction import MachineAction
|
||||
from UM.i18n import i18nCatalog
|
||||
from UM.Settings.ContainerRegistry import ContainerRegistry
|
||||
from cura.PrinterOutput.FirmwareUpdater import FirmwareUpdateState
|
||||
|
||||
from PyQt5.QtCore import pyqtSignal, pyqtProperty, QObject
|
||||
from typing import Optional
|
||||
|
||||
MYPY = False
|
||||
if MYPY:
|
||||
from cura.PrinterOutput.FirmwareUpdater import FirmwareUpdater
|
||||
from cura.PrinterOutput.PrinterOutputDevice import PrinterOutputDevice
|
||||
from UM.Settings.ContainerInterface import ContainerInterface
|
||||
|
||||
catalog = i18nCatalog("cura")
|
||||
|
||||
## Upgrade the firmware of a machine by USB with this action.
|
||||
class FirmwareUpdaterMachineAction(MachineAction):
|
||||
def __init__(self) -> None:
|
||||
super().__init__("UpgradeFirmware", catalog.i18nc("@action", "Update Firmware"))
|
||||
self._qml_url = "FirmwareUpdaterMachineAction.qml"
|
||||
ContainerRegistry.getInstance().containerAdded.connect(self._onContainerAdded)
|
||||
|
||||
self._active_output_device = None # type: Optional[PrinterOutputDevice]
|
||||
self._active_firmware_updater = None # type: Optional[FirmwareUpdater]
|
||||
|
||||
CuraApplication.getInstance().engineCreatedSignal.connect(self._onEngineCreated)
|
||||
|
||||
def _onEngineCreated(self) -> None:
|
||||
CuraApplication.getInstance().getMachineManager().outputDevicesChanged.connect(self._onOutputDevicesChanged)
|
||||
|
||||
def _onContainerAdded(self, container: "ContainerInterface") -> None:
|
||||
# Add this action as a supported action to all machine definitions if they support USB connection
|
||||
if isinstance(container, DefinitionContainer) and container.getMetaDataEntry("type") == "machine" and container.getMetaDataEntry("supports_usb_connection"):
|
||||
CuraApplication.getInstance().getMachineActionManager().addSupportedAction(container.getId(), self.getKey())
|
||||
|
||||
def _onOutputDevicesChanged(self) -> None:
|
||||
if self._active_output_device and self._active_output_device.activePrinter:
|
||||
self._active_output_device.activePrinter.getController().canUpdateFirmwareChanged.disconnect(self._onControllerCanUpdateFirmwareChanged)
|
||||
|
||||
output_devices = CuraApplication.getInstance().getMachineManager().printerOutputDevices
|
||||
self._active_output_device = output_devices[0] if output_devices else None
|
||||
|
||||
if self._active_output_device and self._active_output_device.activePrinter:
|
||||
self._active_output_device.activePrinter.getController().canUpdateFirmwareChanged.connect(self._onControllerCanUpdateFirmwareChanged)
|
||||
|
||||
self.outputDeviceCanUpdateFirmwareChanged.emit()
|
||||
|
||||
def _onControllerCanUpdateFirmwareChanged(self) -> None:
|
||||
self.outputDeviceCanUpdateFirmwareChanged.emit()
|
||||
|
||||
outputDeviceCanUpdateFirmwareChanged = pyqtSignal()
|
||||
@pyqtProperty(QObject, notify = outputDeviceCanUpdateFirmwareChanged)
|
||||
def firmwareUpdater(self) -> Optional["FirmwareUpdater"]:
|
||||
if self._active_output_device and self._active_output_device.activePrinter.getController().can_update_firmware:
|
||||
self._active_firmware_updater = self._active_output_device.getFirmwareUpdater()
|
||||
return self._active_firmware_updater
|
||||
|
||||
elif self._active_firmware_updater and self._active_firmware_updater.firmwareUpdateState not in [FirmwareUpdateState.idle, FirmwareUpdateState.completed]:
|
||||
# During a firmware update, the PrinterOutputDevice is disconnected but the FirmwareUpdater is still there
|
||||
return self._active_firmware_updater
|
||||
|
||||
self._active_firmware_updater = None
|
||||
return None
|
191
plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.qml
Normal file
191
plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.qml
Normal file
@ -0,0 +1,191 @@
|
||||
// Copyright (c) 2018 Ultimaker B.V.
|
||||
// Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import QtQuick 2.2
|
||||
import QtQuick.Controls 1.1
|
||||
import QtQuick.Layouts 1.1
|
||||
import QtQuick.Window 2.1
|
||||
import QtQuick.Dialogs 1.2 // For filedialog
|
||||
|
||||
import UM 1.2 as UM
|
||||
import Cura 1.0 as Cura
|
||||
|
||||
|
||||
Cura.MachineAction
|
||||
{
|
||||
anchors.fill: parent;
|
||||
property bool printerConnected: Cura.MachineManager.printerConnected
|
||||
property var activeOutputDevice: printerConnected ? Cura.MachineManager.printerOutputDevices[0] : null
|
||||
property bool canUpdateFirmware: activeOutputDevice ? activeOutputDevice.activePrinter.canUpdateFirmware : false
|
||||
|
||||
Column
|
||||
{
|
||||
id: firmwareUpdaterMachineAction
|
||||
anchors.fill: parent;
|
||||
UM.I18nCatalog { id: catalog; name:"cura"}
|
||||
spacing: UM.Theme.getSize("default_margin").height
|
||||
|
||||
Label
|
||||
{
|
||||
width: parent.width
|
||||
text: catalog.i18nc("@title", "Update Firmware")
|
||||
wrapMode: Text.WordWrap
|
||||
font.pointSize: 18
|
||||
}
|
||||
Label
|
||||
{
|
||||
width: parent.width
|
||||
wrapMode: Text.WordWrap
|
||||
text: catalog.i18nc("@label", "Firmware is the piece of software running directly on your 3D printer. This firmware controls the step motors, regulates the temperature and ultimately makes your printer work.")
|
||||
}
|
||||
|
||||
Label
|
||||
{
|
||||
width: parent.width
|
||||
wrapMode: Text.WordWrap
|
||||
text: catalog.i18nc("@label", "The firmware shipping with new printers works, but new versions tend to have more features and improvements.");
|
||||
}
|
||||
|
||||
Row
|
||||
{
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
width: childrenRect.width
|
||||
spacing: UM.Theme.getSize("default_margin").width
|
||||
property string firmwareName: Cura.MachineManager.activeMachine.getDefaultFirmwareName()
|
||||
Button
|
||||
{
|
||||
id: autoUpgradeButton
|
||||
text: catalog.i18nc("@action:button", "Automatically upgrade Firmware");
|
||||
enabled: parent.firmwareName != "" && canUpdateFirmware
|
||||
onClicked:
|
||||
{
|
||||
updateProgressDialog.visible = true;
|
||||
activeOutputDevice.updateFirmware(parent.firmwareName);
|
||||
}
|
||||
}
|
||||
Button
|
||||
{
|
||||
id: manualUpgradeButton
|
||||
text: catalog.i18nc("@action:button", "Upload custom Firmware");
|
||||
enabled: canUpdateFirmware
|
||||
onClicked:
|
||||
{
|
||||
customFirmwareDialog.open()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Label
|
||||
{
|
||||
width: parent.width
|
||||
wrapMode: Text.WordWrap
|
||||
visible: !printerConnected && !updateProgressDialog.visible
|
||||
text: catalog.i18nc("@label", "Firmware can not be updated because there is no connection with the printer.");
|
||||
}
|
||||
|
||||
Label
|
||||
{
|
||||
width: parent.width
|
||||
wrapMode: Text.WordWrap
|
||||
visible: printerConnected && !canUpdateFirmware
|
||||
text: catalog.i18nc("@label", "Firmware can not be updated because the connection with the printer does not support upgrading firmware.");
|
||||
}
|
||||
}
|
||||
|
||||
FileDialog
|
||||
{
|
||||
id: customFirmwareDialog
|
||||
title: catalog.i18nc("@title:window", "Select custom firmware")
|
||||
nameFilters: "Firmware image files (*.hex)"
|
||||
selectExisting: true
|
||||
onAccepted:
|
||||
{
|
||||
updateProgressDialog.visible = true;
|
||||
activeOutputDevice.updateFirmware(fileUrl);
|
||||
}
|
||||
}
|
||||
|
||||
UM.Dialog
|
||||
{
|
||||
id: updateProgressDialog
|
||||
|
||||
width: minimumWidth
|
||||
minimumWidth: 500 * screenScaleFactor
|
||||
height: minimumHeight
|
||||
minimumHeight: 100 * screenScaleFactor
|
||||
|
||||
modality: Qt.ApplicationModal
|
||||
|
||||
title: catalog.i18nc("@title:window","Firmware Update")
|
||||
|
||||
Column
|
||||
{
|
||||
anchors.fill: parent
|
||||
|
||||
Label
|
||||
{
|
||||
anchors
|
||||
{
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
}
|
||||
|
||||
text: {
|
||||
if(manager.firmwareUpdater == null)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
switch (manager.firmwareUpdater.firmwareUpdateState)
|
||||
{
|
||||
case 0:
|
||||
return ""; //Not doing anything (eg; idling)
|
||||
case 1:
|
||||
return catalog.i18nc("@label","Updating firmware.");
|
||||
case 2:
|
||||
return catalog.i18nc("@label","Firmware update completed.");
|
||||
case 3:
|
||||
return catalog.i18nc("@label","Firmware update failed due to an unknown error.");
|
||||
case 4:
|
||||
return catalog.i18nc("@label","Firmware update failed due to an communication error.");
|
||||
case 5:
|
||||
return catalog.i18nc("@label","Firmware update failed due to an input/output error.");
|
||||
case 6:
|
||||
return catalog.i18nc("@label","Firmware update failed due to missing firmware.");
|
||||
}
|
||||
}
|
||||
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
|
||||
ProgressBar
|
||||
{
|
||||
id: prog
|
||||
value: (manager.firmwareUpdater != null) ? manager.firmwareUpdater.firmwareProgress : 0
|
||||
minimumValue: 0
|
||||
maximumValue: 100
|
||||
indeterminate:
|
||||
{
|
||||
if(manager.firmwareUpdater == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return manager.firmwareUpdater.firmwareProgress < 1 && manager.firmwareUpdater.firmwareProgress > 0;
|
||||
}
|
||||
anchors
|
||||
{
|
||||
left: parent.left;
|
||||
right: parent.right;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rightButtons: [
|
||||
Button
|
||||
{
|
||||
text: catalog.i18nc("@action:button","Close");
|
||||
enabled: (manager.firmwareUpdater != null) ? manager.firmwareUpdater.firmwareUpdateState != 1 : true;
|
||||
onClicked: updateProgressDialog.visible = false;
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
12
plugins/FirmwareUpdater/__init__.py
Normal file
12
plugins/FirmwareUpdater/__init__.py
Normal file
@ -0,0 +1,12 @@
|
||||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from . import FirmwareUpdaterMachineAction
|
||||
|
||||
def getMetaData():
|
||||
return {}
|
||||
|
||||
def register(app):
|
||||
return { "machine_action": [
|
||||
FirmwareUpdaterMachineAction.FirmwareUpdaterMachineAction()
|
||||
]}
|
8
plugins/FirmwareUpdater/plugin.json
Normal file
8
plugins/FirmwareUpdater/plugin.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"name": "Firmware Updater",
|
||||
"author": "Ultimaker B.V.",
|
||||
"version": "1.0.0",
|
||||
"description": "Provides a machine actions for updating firmware.",
|
||||
"api": 5,
|
||||
"i18n-catalog": "cura"
|
||||
}
|
68
plugins/USBPrinting/AvrFirmwareUpdater.py
Normal file
68
plugins/USBPrinting/AvrFirmwareUpdater.py
Normal file
@ -0,0 +1,68 @@
|
||||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from UM.Logger import Logger
|
||||
|
||||
from cura.CuraApplication import CuraApplication
|
||||
from cura.PrinterOutput.FirmwareUpdater import FirmwareUpdater, FirmwareUpdateState
|
||||
|
||||
from .avr_isp import stk500v2, intelHex
|
||||
from serial import SerialException
|
||||
|
||||
from time import sleep
|
||||
|
||||
MYPY = False
|
||||
if MYPY:
|
||||
from cura.PrinterOutputDevice import PrinterOutputDevice
|
||||
|
||||
|
||||
class AvrFirmwareUpdater(FirmwareUpdater):
|
||||
def __init__(self, output_device: "PrinterOutputDevice") -> None:
|
||||
super().__init__(output_device)
|
||||
|
||||
def _updateFirmware(self) -> None:
|
||||
try:
|
||||
hex_file = intelHex.readHex(self._firmware_location)
|
||||
assert len(hex_file) > 0
|
||||
except (FileNotFoundError, AssertionError):
|
||||
Logger.log("e", "Unable to read provided hex file. Could not update firmware.")
|
||||
self._setFirmwareUpdateState(FirmwareUpdateState.firmware_not_found_error)
|
||||
return
|
||||
|
||||
programmer = stk500v2.Stk500v2()
|
||||
programmer.progress_callback = self._onFirmwareProgress
|
||||
|
||||
# Ensure that other connections are closed.
|
||||
if self._output_device.isConnected():
|
||||
self._output_device.close()
|
||||
|
||||
try:
|
||||
programmer.connect(self._output_device._serial_port)
|
||||
except:
|
||||
programmer.close()
|
||||
Logger.logException("e", "Failed to update firmware")
|
||||
self._setFirmwareUpdateState(FirmwareUpdateState.communication_error)
|
||||
return
|
||||
|
||||
# Give programmer some time to connect. Might need more in some cases, but this worked in all tested cases.
|
||||
sleep(1)
|
||||
if not programmer.isConnected():
|
||||
Logger.log("e", "Unable to connect with serial. Could not update firmware")
|
||||
self._setFirmwareUpdateState(FirmwareUpdateState.communication_error)
|
||||
try:
|
||||
programmer.programChip(hex_file)
|
||||
except SerialException as e:
|
||||
Logger.log("e", "A serial port exception occured during firmware update: %s" % e)
|
||||
self._setFirmwareUpdateState(FirmwareUpdateState.io_error)
|
||||
return
|
||||
except Exception as e:
|
||||
Logger.log("e", "An unknown exception occured during firmware update: %s" % e)
|
||||
self._setFirmwareUpdateState(FirmwareUpdateState.unknown_error)
|
||||
return
|
||||
|
||||
programmer.close()
|
||||
|
||||
# Try to re-connect with the machine again, which must be done on the Qt thread, so we use call later.
|
||||
CuraApplication.getInstance().callLater(self._output_device.connect)
|
||||
|
||||
self._cleanupAfterUpdate()
|
@ -1,89 +0,0 @@
|
||||
// Copyright (c) 2017 Ultimaker B.V.
|
||||
// Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import QtQuick 2.2
|
||||
import QtQuick.Window 2.2
|
||||
import QtQuick.Controls 1.2
|
||||
|
||||
import UM 1.1 as UM
|
||||
|
||||
UM.Dialog
|
||||
{
|
||||
id: base;
|
||||
|
||||
width: minimumWidth;
|
||||
minimumWidth: 500 * screenScaleFactor;
|
||||
height: minimumHeight;
|
||||
minimumHeight: 100 * screenScaleFactor;
|
||||
|
||||
visible: true;
|
||||
modality: Qt.ApplicationModal;
|
||||
|
||||
title: catalog.i18nc("@title:window","Firmware Update");
|
||||
|
||||
Column
|
||||
{
|
||||
anchors.fill: parent;
|
||||
|
||||
Label
|
||||
{
|
||||
anchors
|
||||
{
|
||||
left: parent.left;
|
||||
right: parent.right;
|
||||
}
|
||||
|
||||
text: {
|
||||
switch (manager.firmwareUpdateState)
|
||||
{
|
||||
case 0:
|
||||
return "" //Not doing anything (eg; idling)
|
||||
case 1:
|
||||
return catalog.i18nc("@label","Updating firmware.")
|
||||
case 2:
|
||||
return catalog.i18nc("@label","Firmware update completed.")
|
||||
case 3:
|
||||
return catalog.i18nc("@label","Firmware update failed due to an unknown error.")
|
||||
case 4:
|
||||
return catalog.i18nc("@label","Firmware update failed due to an communication error.")
|
||||
case 5:
|
||||
return catalog.i18nc("@label","Firmware update failed due to an input/output error.")
|
||||
case 6:
|
||||
return catalog.i18nc("@label","Firmware update failed due to missing firmware.")
|
||||
}
|
||||
}
|
||||
|
||||
wrapMode: Text.Wrap;
|
||||
}
|
||||
|
||||
ProgressBar
|
||||
{
|
||||
id: prog
|
||||
value: manager.firmwareProgress
|
||||
minimumValue: 0
|
||||
maximumValue: 100
|
||||
indeterminate: manager.firmwareProgress < 1 && manager.firmwareProgress > 0
|
||||
anchors
|
||||
{
|
||||
left: parent.left;
|
||||
right: parent.right;
|
||||
}
|
||||
}
|
||||
|
||||
SystemPalette
|
||||
{
|
||||
id: palette;
|
||||
}
|
||||
|
||||
UM.I18nCatalog { id: catalog; name: "cura"; }
|
||||
}
|
||||
|
||||
rightButtons: [
|
||||
Button
|
||||
{
|
||||
text: catalog.i18nc("@action:button","Close");
|
||||
enabled: manager.firmwareUpdateCompleteStatus;
|
||||
onClicked: base.visible = false;
|
||||
}
|
||||
]
|
||||
}
|
@ -4,7 +4,6 @@
|
||||
from UM.Logger import Logger
|
||||
from UM.i18n import i18nCatalog
|
||||
from UM.Qt.Duration import DurationFormat
|
||||
from UM.PluginRegistry import PluginRegistry
|
||||
|
||||
from cura.CuraApplication import CuraApplication
|
||||
from cura.PrinterOutputDevice import PrinterOutputDevice, ConnectionState
|
||||
@ -13,28 +12,21 @@ from cura.PrinterOutput.PrintJobOutputModel import PrintJobOutputModel
|
||||
from cura.PrinterOutput.GenericOutputController import GenericOutputController
|
||||
|
||||
from .AutoDetectBaudJob import AutoDetectBaudJob
|
||||
from .avr_isp import stk500v2, intelHex
|
||||
|
||||
from PyQt5.QtCore import pyqtSlot, pyqtSignal, pyqtProperty, QUrl
|
||||
from .AvrFirmwareUpdater import AvrFirmwareUpdater
|
||||
|
||||
from serial import Serial, SerialException, SerialTimeoutException
|
||||
from threading import Thread, Event
|
||||
from time import time, sleep
|
||||
from time import time
|
||||
from queue import Queue
|
||||
from enum import IntEnum
|
||||
from typing import Union, Optional, List, cast
|
||||
|
||||
import re
|
||||
import functools # Used for reduce
|
||||
import os
|
||||
|
||||
catalog = i18nCatalog("cura")
|
||||
|
||||
|
||||
class USBPrinterOutputDevice(PrinterOutputDevice):
|
||||
firmwareProgressChanged = pyqtSignal()
|
||||
firmwareUpdateStateChanged = pyqtSignal()
|
||||
|
||||
def __init__(self, serial_port: str, baud_rate: Optional[int] = None) -> None:
|
||||
super().__init__(serial_port)
|
||||
self.setName(catalog.i18nc("@item:inmenu", "USB printing"))
|
||||
@ -59,9 +51,7 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
|
||||
self._all_baud_rates = [115200, 250000, 230400, 57600, 38400, 19200, 9600]
|
||||
|
||||
# Instead of using a timer, we really need the update to be as a thread, as reading from serial can block.
|
||||
self._update_thread = Thread(target=self._update, daemon = True)
|
||||
|
||||
self._update_firmware_thread = Thread(target=self._updateFirmware, daemon = True)
|
||||
self._update_thread = Thread(target=self._update, daemon=True)
|
||||
|
||||
self._last_temperature_request = None # type: Optional[int]
|
||||
|
||||
@ -75,11 +65,6 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
|
||||
|
||||
self._paused = False
|
||||
|
||||
self._firmware_view = None
|
||||
self._firmware_location = None
|
||||
self._firmware_progress = 0
|
||||
self._firmware_update_state = FirmwareUpdateState.idle
|
||||
|
||||
self.setConnectionText(catalog.i18nc("@info:status", "Connected via USB"))
|
||||
|
||||
# Queue for commands that need to be sent.
|
||||
@ -88,6 +73,8 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
|
||||
self._command_received = Event()
|
||||
self._command_received.set()
|
||||
|
||||
self._firmware_updater = AvrFirmwareUpdater(self)
|
||||
|
||||
CuraApplication.getInstance().getOnExitCallbackManager().addCallback(self._checkActivePrintingUponAppExit)
|
||||
|
||||
# This is a callback function that checks if there is any printing in progress via USB when the application tries
|
||||
@ -109,7 +96,7 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
|
||||
|
||||
## Reset USB device settings
|
||||
#
|
||||
def resetDeviceSettings(self):
|
||||
def resetDeviceSettings(self) -> None:
|
||||
self._firmware_name = None
|
||||
|
||||
## Request the current scene to be sent to a USB-connected printer.
|
||||
@ -135,93 +122,6 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
|
||||
|
||||
self._printGCode(gcode_list)
|
||||
|
||||
## Show firmware interface.
|
||||
# This will create the view if its not already created.
|
||||
def showFirmwareInterface(self):
|
||||
if self._firmware_view is None:
|
||||
path = os.path.join(PluginRegistry.getInstance().getPluginPath("USBPrinting"), "FirmwareUpdateWindow.qml")
|
||||
self._firmware_view = CuraApplication.getInstance().createQmlComponent(path, {"manager": self})
|
||||
|
||||
self._firmware_view.show()
|
||||
|
||||
@pyqtSlot(str)
|
||||
def updateFirmware(self, file):
|
||||
# the file path could be url-encoded.
|
||||
if file.startswith("file://"):
|
||||
self._firmware_location = QUrl(file).toLocalFile()
|
||||
else:
|
||||
self._firmware_location = file
|
||||
self.showFirmwareInterface()
|
||||
self.setFirmwareUpdateState(FirmwareUpdateState.updating)
|
||||
self._update_firmware_thread.start()
|
||||
|
||||
def _updateFirmware(self):
|
||||
# Ensure that other connections are closed.
|
||||
if self._connection_state != ConnectionState.closed:
|
||||
self.close()
|
||||
|
||||
try:
|
||||
hex_file = intelHex.readHex(self._firmware_location)
|
||||
assert len(hex_file) > 0
|
||||
except (FileNotFoundError, AssertionError):
|
||||
Logger.log("e", "Unable to read provided hex file. Could not update firmware.")
|
||||
self.setFirmwareUpdateState(FirmwareUpdateState.firmware_not_found_error)
|
||||
return
|
||||
|
||||
programmer = stk500v2.Stk500v2()
|
||||
programmer.progress_callback = self._onFirmwareProgress
|
||||
|
||||
try:
|
||||
programmer.connect(self._serial_port)
|
||||
except:
|
||||
programmer.close()
|
||||
Logger.logException("e", "Failed to update firmware")
|
||||
self.setFirmwareUpdateState(FirmwareUpdateState.communication_error)
|
||||
return
|
||||
|
||||
# Give programmer some time to connect. Might need more in some cases, but this worked in all tested cases.
|
||||
sleep(1)
|
||||
if not programmer.isConnected():
|
||||
Logger.log("e", "Unable to connect with serial. Could not update firmware")
|
||||
self.setFirmwareUpdateState(FirmwareUpdateState.communication_error)
|
||||
try:
|
||||
programmer.programChip(hex_file)
|
||||
except SerialException:
|
||||
self.setFirmwareUpdateState(FirmwareUpdateState.io_error)
|
||||
return
|
||||
except:
|
||||
self.setFirmwareUpdateState(FirmwareUpdateState.unknown_error)
|
||||
return
|
||||
|
||||
programmer.close()
|
||||
|
||||
# Clean up for next attempt.
|
||||
self._update_firmware_thread = Thread(target=self._updateFirmware, daemon=True)
|
||||
self._firmware_location = ""
|
||||
self._onFirmwareProgress(100)
|
||||
self.setFirmwareUpdateState(FirmwareUpdateState.completed)
|
||||
|
||||
# Try to re-connect with the machine again, which must be done on the Qt thread, so we use call later.
|
||||
CuraApplication.getInstance().callLater(self.connect)
|
||||
|
||||
@pyqtProperty(float, notify = firmwareProgressChanged)
|
||||
def firmwareProgress(self):
|
||||
return self._firmware_progress
|
||||
|
||||
@pyqtProperty(int, notify=firmwareUpdateStateChanged)
|
||||
def firmwareUpdateState(self):
|
||||
return self._firmware_update_state
|
||||
|
||||
def setFirmwareUpdateState(self, state):
|
||||
if self._firmware_update_state != state:
|
||||
self._firmware_update_state = state
|
||||
self.firmwareUpdateStateChanged.emit()
|
||||
|
||||
# Callback function for firmware update progress.
|
||||
def _onFirmwareProgress(self, progress, max_progress = 100):
|
||||
self._firmware_progress = (progress / max_progress) * 100 # Convert to scale of 0-100
|
||||
self.firmwareProgressChanged.emit()
|
||||
|
||||
## Start a print based on a g-code.
|
||||
# \param gcode_list List with gcode (strings).
|
||||
def _printGCode(self, gcode_list: List[str]):
|
||||
@ -258,7 +158,7 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
|
||||
self._baud_rate = baud_rate
|
||||
|
||||
def connect(self):
|
||||
self._firmware_name = None # after each connection ensure that the firmware name is removed
|
||||
self._firmware_name = None # after each connection ensure that the firmware name is removed
|
||||
|
||||
if self._baud_rate is None:
|
||||
if self._use_auto_detect:
|
||||
@ -275,7 +175,9 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
|
||||
container_stack = CuraApplication.getInstance().getGlobalContainerStack()
|
||||
num_extruders = container_stack.getProperty("machine_extruder_count", "value")
|
||||
# Ensure that a printer is created.
|
||||
self._printers = [PrinterOutputModel(output_controller=GenericOutputController(self), number_of_extruders=num_extruders)]
|
||||
controller = GenericOutputController(self)
|
||||
controller.setCanUpdateFirmware(True)
|
||||
self._printers = [PrinterOutputModel(output_controller=controller, number_of_extruders=num_extruders)]
|
||||
self._printers[0].updateName(container_stack.getName())
|
||||
self.setConnectionState(ConnectionState.connected)
|
||||
self._update_thread.start()
|
||||
@ -295,6 +197,7 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
|
||||
self._command_queue.put(command)
|
||||
else:
|
||||
self._sendCommand(command)
|
||||
|
||||
def _sendCommand(self, command: Union[str, bytes]):
|
||||
if self._serial is None or self._connection_state != ConnectionState.connected:
|
||||
return
|
||||
@ -445,7 +348,9 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
|
||||
elapsed_time = int(time() - self._print_start_time)
|
||||
print_job = self._printers[0].activePrintJob
|
||||
if print_job is None:
|
||||
print_job = PrintJobOutputModel(output_controller = GenericOutputController(self), name= CuraApplication.getInstance().getPrintInformation().jobName)
|
||||
controller = GenericOutputController(self)
|
||||
controller.setCanUpdateFirmware(True)
|
||||
print_job = PrintJobOutputModel(output_controller=controller, name=CuraApplication.getInstance().getPrintInformation().jobName)
|
||||
print_job.updateState("printing")
|
||||
self._printers[0].updateActivePrintJob(print_job)
|
||||
|
||||
@ -456,13 +361,3 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
|
||||
print_job.updateTimeTotal(estimated_time)
|
||||
|
||||
self._gcode_position += 1
|
||||
|
||||
|
||||
class FirmwareUpdateState(IntEnum):
|
||||
idle = 0
|
||||
updating = 1
|
||||
completed = 2
|
||||
unknown_error = 3
|
||||
communication_error = 4
|
||||
io_error = 5
|
||||
firmware_not_found_error = 6
|
||||
|
@ -2,14 +2,12 @@
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import threading
|
||||
import platform
|
||||
import time
|
||||
import serial.tools.list_ports
|
||||
|
||||
from PyQt5.QtCore import QObject, pyqtSlot, pyqtProperty, pyqtSignal
|
||||
|
||||
from UM.Logger import Logger
|
||||
from UM.Resources import Resources
|
||||
from UM.Signal import Signal, signalemitter
|
||||
from UM.OutputDevice.OutputDevicePlugin import OutputDevicePlugin
|
||||
from UM.i18n import i18nCatalog
|
||||
@ -87,65 +85,6 @@ class USBPrinterOutputDeviceManager(QObject, OutputDevicePlugin):
|
||||
self._addRemovePorts(port_list)
|
||||
time.sleep(5)
|
||||
|
||||
@pyqtSlot(result = str)
|
||||
def getDefaultFirmwareName(self):
|
||||
# Check if there is a valid global container stack
|
||||
global_container_stack = self._application.getGlobalContainerStack()
|
||||
if not global_container_stack:
|
||||
Logger.log("e", "There is no global container stack. Can not update firmware.")
|
||||
self._firmware_view.close()
|
||||
return ""
|
||||
|
||||
# The bottom of the containerstack is the machine definition
|
||||
machine_id = global_container_stack.getBottom().id
|
||||
|
||||
machine_has_heated_bed = global_container_stack.getProperty("machine_heated_bed", "value")
|
||||
|
||||
if platform.system() == "Linux":
|
||||
baudrate = 115200
|
||||
else:
|
||||
baudrate = 250000
|
||||
|
||||
# NOTE: The keyword used here is the id of the machine. You can find the id of your machine in the *.json file, eg.
|
||||
# https://github.com/Ultimaker/Cura/blob/master/resources/machines/ultimaker_original.json#L2
|
||||
# The *.hex files are stored at a seperate repository:
|
||||
# https://github.com/Ultimaker/cura-binary-data/tree/master/cura/resources/firmware
|
||||
machine_without_extras = {"bq_witbox" : "MarlinWitbox.hex",
|
||||
"bq_hephestos_2" : "MarlinHephestos2.hex",
|
||||
"ultimaker_original" : "MarlinUltimaker-{baudrate}.hex",
|
||||
"ultimaker_original_plus" : "MarlinUltimaker-UMOP-{baudrate}.hex",
|
||||
"ultimaker_original_dual" : "MarlinUltimaker-{baudrate}-dual.hex",
|
||||
"ultimaker2" : "MarlinUltimaker2.hex",
|
||||
"ultimaker2_go" : "MarlinUltimaker2go.hex",
|
||||
"ultimaker2_plus" : "MarlinUltimaker2plus.hex",
|
||||
"ultimaker2_extended" : "MarlinUltimaker2extended.hex",
|
||||
"ultimaker2_extended_plus" : "MarlinUltimaker2extended-plus.hex",
|
||||
}
|
||||
machine_with_heated_bed = {"ultimaker_original" : "MarlinUltimaker-HBK-{baudrate}.hex",
|
||||
"ultimaker_original_dual" : "MarlinUltimaker-HBK-{baudrate}-dual.hex",
|
||||
}
|
||||
##TODO: Add check for multiple extruders
|
||||
hex_file = None
|
||||
if machine_id in machine_without_extras.keys(): # The machine needs to be defined here!
|
||||
if machine_id in machine_with_heated_bed.keys() and machine_has_heated_bed:
|
||||
Logger.log("d", "Choosing firmware with heated bed enabled for machine %s.", machine_id)
|
||||
hex_file = machine_with_heated_bed[machine_id] # Return firmware with heated bed enabled
|
||||
else:
|
||||
Logger.log("d", "Choosing basic firmware for machine %s.", machine_id)
|
||||
hex_file = machine_without_extras[machine_id] # Return "basic" firmware
|
||||
else:
|
||||
Logger.log("w", "There is no firmware for machine %s.", machine_id)
|
||||
|
||||
if hex_file:
|
||||
try:
|
||||
return Resources.getPath(CuraApplication.ResourceTypes.Firmware, hex_file.format(baudrate=baudrate))
|
||||
except FileNotFoundError:
|
||||
Logger.log("w", "Could not find any firmware for machine %s.", machine_id)
|
||||
return ""
|
||||
else:
|
||||
Logger.log("w", "Could not find any firmware for machine %s.", machine_id)
|
||||
return ""
|
||||
|
||||
## Helper to identify serial ports (and scan for them)
|
||||
def _addRemovePorts(self, serial_ports):
|
||||
# First, find and add all new or changed keys
|
||||
|
@ -2,9 +2,6 @@
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from . import USBPrinterOutputDeviceManager
|
||||
from PyQt5.QtQml import qmlRegisterSingletonType
|
||||
from UM.i18n import i18nCatalog
|
||||
i18n_catalog = i18nCatalog("cura")
|
||||
|
||||
|
||||
def getMetaData():
|
||||
@ -14,5 +11,4 @@ def getMetaData():
|
||||
def register(app):
|
||||
# We are violating the QT API here (as we use a factory, which is technically not allowed).
|
||||
# but we don't really have another means for doing this (and it seems to you know -work-)
|
||||
qmlRegisterSingletonType(USBPrinterOutputDeviceManager.USBPrinterOutputDeviceManager, "Cura", 1, 0, "USBPrinterManager", USBPrinterOutputDeviceManager.USBPrinterOutputDeviceManager.getInstance)
|
||||
return {"output_device": USBPrinterOutputDeviceManager.USBPrinterOutputDeviceManager(app)}
|
||||
|
@ -17,7 +17,7 @@ Cura.MachineAction
|
||||
property int rightRow: (checkupMachineAction.width * 0.60) | 0
|
||||
property bool heatupHotendStarted: false
|
||||
property bool heatupBedStarted: false
|
||||
property bool usbConnected: Cura.USBPrinterManager.connectedPrinterList.rowCount() > 0
|
||||
property bool printerConnected: Cura.MachineManager.printerConnected
|
||||
|
||||
UM.I18nCatalog { id: catalog; name:"cura"}
|
||||
Label
|
||||
@ -86,7 +86,7 @@ Cura.MachineAction
|
||||
anchors.left: connectionLabel.right
|
||||
anchors.top: parent.top
|
||||
wrapMode: Text.WordWrap
|
||||
text: checkupMachineAction.usbConnected ? catalog.i18nc("@info:status","Connected"): catalog.i18nc("@info:status","Not connected")
|
||||
text: checkupMachineAction.printerConnected ? catalog.i18nc("@info:status","Connected"): catalog.i18nc("@info:status","Not connected")
|
||||
}
|
||||
//////////////////////////////////////////////////////////
|
||||
Label
|
||||
@ -97,7 +97,7 @@ Cura.MachineAction
|
||||
anchors.top: connectionLabel.bottom
|
||||
wrapMode: Text.WordWrap
|
||||
text: catalog.i18nc("@label","Min endstop X: ")
|
||||
visible: checkupMachineAction.usbConnected
|
||||
visible: checkupMachineAction.printerConnected
|
||||
}
|
||||
Label
|
||||
{
|
||||
@ -107,7 +107,7 @@ Cura.MachineAction
|
||||
anchors.top: connectionLabel.bottom
|
||||
wrapMode: Text.WordWrap
|
||||
text: manager.xMinEndstopTestCompleted ? catalog.i18nc("@info:status","Works") : catalog.i18nc("@info:status","Not checked")
|
||||
visible: checkupMachineAction.usbConnected
|
||||
visible: checkupMachineAction.printerConnected
|
||||
}
|
||||
//////////////////////////////////////////////////////////////
|
||||
Label
|
||||
@ -118,7 +118,7 @@ Cura.MachineAction
|
||||
anchors.top: endstopXLabel.bottom
|
||||
wrapMode: Text.WordWrap
|
||||
text: catalog.i18nc("@label","Min endstop Y: ")
|
||||
visible: checkupMachineAction.usbConnected
|
||||
visible: checkupMachineAction.printerConnected
|
||||
}
|
||||
Label
|
||||
{
|
||||
@ -128,7 +128,7 @@ Cura.MachineAction
|
||||
anchors.top: endstopXLabel.bottom
|
||||
wrapMode: Text.WordWrap
|
||||
text: manager.yMinEndstopTestCompleted ? catalog.i18nc("@info:status","Works") : catalog.i18nc("@info:status","Not checked")
|
||||
visible: checkupMachineAction.usbConnected
|
||||
visible: checkupMachineAction.printerConnected
|
||||
}
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
Label
|
||||
@ -139,7 +139,7 @@ Cura.MachineAction
|
||||
anchors.top: endstopYLabel.bottom
|
||||
wrapMode: Text.WordWrap
|
||||
text: catalog.i18nc("@label","Min endstop Z: ")
|
||||
visible: checkupMachineAction.usbConnected
|
||||
visible: checkupMachineAction.printerConnected
|
||||
}
|
||||
Label
|
||||
{
|
||||
@ -149,7 +149,7 @@ Cura.MachineAction
|
||||
anchors.top: endstopYLabel.bottom
|
||||
wrapMode: Text.WordWrap
|
||||
text: manager.zMinEndstopTestCompleted ? catalog.i18nc("@info:status","Works") : catalog.i18nc("@info:status","Not checked")
|
||||
visible: checkupMachineAction.usbConnected
|
||||
visible: checkupMachineAction.printerConnected
|
||||
}
|
||||
////////////////////////////////////////////////////////////
|
||||
Label
|
||||
@ -161,7 +161,7 @@ Cura.MachineAction
|
||||
anchors.top: endstopZLabel.bottom
|
||||
wrapMode: Text.WordWrap
|
||||
text: catalog.i18nc("@label","Nozzle temperature check: ")
|
||||
visible: checkupMachineAction.usbConnected
|
||||
visible: checkupMachineAction.printerConnected
|
||||
}
|
||||
Label
|
||||
{
|
||||
@ -171,7 +171,7 @@ Cura.MachineAction
|
||||
anchors.left: nozzleTempLabel.right
|
||||
wrapMode: Text.WordWrap
|
||||
text: catalog.i18nc("@info:status","Not checked")
|
||||
visible: checkupMachineAction.usbConnected
|
||||
visible: checkupMachineAction.printerConnected
|
||||
}
|
||||
Item
|
||||
{
|
||||
@ -181,7 +181,7 @@ Cura.MachineAction
|
||||
anchors.top: nozzleTempLabel.top
|
||||
anchors.left: bedTempStatus.right
|
||||
anchors.leftMargin: Math.round(UM.Theme.getSize("default_margin").width/2)
|
||||
visible: checkupMachineAction.usbConnected
|
||||
visible: checkupMachineAction.printerConnected
|
||||
Button
|
||||
{
|
||||
text: checkupMachineAction.heatupHotendStarted ? catalog.i18nc("@action:button","Stop Heating") : catalog.i18nc("@action:button","Start Heating")
|
||||
@ -209,7 +209,7 @@ Cura.MachineAction
|
||||
wrapMode: Text.WordWrap
|
||||
text: manager.hotendTemperature + "°C"
|
||||
font.bold: true
|
||||
visible: checkupMachineAction.usbConnected
|
||||
visible: checkupMachineAction.printerConnected
|
||||
}
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
Label
|
||||
@ -221,7 +221,7 @@ Cura.MachineAction
|
||||
anchors.top: nozzleTempLabel.bottom
|
||||
wrapMode: Text.WordWrap
|
||||
text: catalog.i18nc("@label","Build plate temperature check:")
|
||||
visible: checkupMachineAction.usbConnected && manager.hasHeatedBed
|
||||
visible: checkupMachineAction.printerConnected && manager.hasHeatedBed
|
||||
}
|
||||
|
||||
Label
|
||||
@ -232,7 +232,7 @@ Cura.MachineAction
|
||||
anchors.left: bedTempLabel.right
|
||||
wrapMode: Text.WordWrap
|
||||
text: manager.bedTestCompleted ? catalog.i18nc("@info:status","Not checked"): catalog.i18nc("@info:status","Checked")
|
||||
visible: checkupMachineAction.usbConnected && manager.hasHeatedBed
|
||||
visible: checkupMachineAction.printerConnected && manager.hasHeatedBed
|
||||
}
|
||||
Item
|
||||
{
|
||||
@ -242,7 +242,7 @@ Cura.MachineAction
|
||||
anchors.top: bedTempLabel.top
|
||||
anchors.left: bedTempStatus.right
|
||||
anchors.leftMargin: Math.round(UM.Theme.getSize("default_margin").width/2)
|
||||
visible: checkupMachineAction.usbConnected && manager.hasHeatedBed
|
||||
visible: checkupMachineAction.printerConnected && manager.hasHeatedBed
|
||||
Button
|
||||
{
|
||||
text: checkupMachineAction.heatupBedStarted ?catalog.i18nc("@action:button","Stop Heating") : catalog.i18nc("@action:button","Start Heating")
|
||||
@ -270,7 +270,7 @@ Cura.MachineAction
|
||||
wrapMode: Text.WordWrap
|
||||
text: manager.bedTemperature + "°C"
|
||||
font.bold: true
|
||||
visible: checkupMachineAction.usbConnected && manager.hasHeatedBed
|
||||
visible: checkupMachineAction.printerConnected && manager.hasHeatedBed
|
||||
}
|
||||
Label
|
||||
{
|
||||
|
@ -1,19 +0,0 @@
|
||||
from UM.Application import Application
|
||||
from UM.Settings.DefinitionContainer import DefinitionContainer
|
||||
from cura.MachineAction import MachineAction
|
||||
from UM.i18n import i18nCatalog
|
||||
from UM.Settings.ContainerRegistry import ContainerRegistry
|
||||
|
||||
catalog = i18nCatalog("cura")
|
||||
|
||||
## Upgrade the firmware of a machine by USB with this action.
|
||||
class UpgradeFirmwareMachineAction(MachineAction):
|
||||
def __init__(self):
|
||||
super().__init__("UpgradeFirmware", catalog.i18nc("@action", "Upgrade Firmware"))
|
||||
self._qml_url = "UpgradeFirmwareMachineAction.qml"
|
||||
ContainerRegistry.getInstance().containerAdded.connect(self._onContainerAdded)
|
||||
|
||||
def _onContainerAdded(self, container):
|
||||
# Add this action as a supported action to all machine definitions if they support USB connection
|
||||
if isinstance(container, DefinitionContainer) and container.getMetaDataEntry("type") == "machine" and container.getMetaDataEntry("supports_usb_connection"):
|
||||
Application.getInstance().getMachineActionManager().addSupportedAction(container.getId(), self.getKey())
|
@ -1,93 +0,0 @@
|
||||
// Copyright (c) 2016 Ultimaker B.V.
|
||||
// Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import QtQuick 2.2
|
||||
import QtQuick.Controls 1.1
|
||||
import QtQuick.Layouts 1.1
|
||||
import QtQuick.Window 2.1
|
||||
import QtQuick.Dialogs 1.2 // For filedialog
|
||||
|
||||
import UM 1.2 as UM
|
||||
import Cura 1.0 as Cura
|
||||
|
||||
|
||||
Cura.MachineAction
|
||||
{
|
||||
anchors.fill: parent;
|
||||
property bool printerConnected: Cura.MachineManager.printerConnected
|
||||
property var activeOutputDevice: printerConnected ? Cura.MachineManager.printerOutputDevices[0] : null
|
||||
|
||||
Item
|
||||
{
|
||||
id: upgradeFirmwareMachineAction
|
||||
anchors.fill: parent;
|
||||
UM.I18nCatalog { id: catalog; name:"cura"}
|
||||
|
||||
Label
|
||||
{
|
||||
id: pageTitle
|
||||
width: parent.width
|
||||
text: catalog.i18nc("@title", "Upgrade Firmware")
|
||||
wrapMode: Text.WordWrap
|
||||
font.pointSize: 18
|
||||
}
|
||||
Label
|
||||
{
|
||||
id: pageDescription
|
||||
anchors.top: pageTitle.bottom
|
||||
anchors.topMargin: UM.Theme.getSize("default_margin").height
|
||||
width: parent.width
|
||||
wrapMode: Text.WordWrap
|
||||
text: catalog.i18nc("@label", "Firmware is the piece of software running directly on your 3D printer. This firmware controls the step motors, regulates the temperature and ultimately makes your printer work.")
|
||||
}
|
||||
|
||||
Label
|
||||
{
|
||||
id: upgradeText1
|
||||
anchors.top: pageDescription.bottom
|
||||
anchors.topMargin: UM.Theme.getSize("default_margin").height
|
||||
width: parent.width
|
||||
wrapMode: Text.WordWrap
|
||||
text: catalog.i18nc("@label", "The firmware shipping with new printers works, but new versions tend to have more features and improvements.");
|
||||
}
|
||||
|
||||
Row
|
||||
{
|
||||
anchors.top: upgradeText1.bottom
|
||||
anchors.topMargin: UM.Theme.getSize("default_margin").height
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
width: childrenRect.width
|
||||
spacing: UM.Theme.getSize("default_margin").width
|
||||
property var firmwareName: Cura.USBPrinterManager.getDefaultFirmwareName()
|
||||
Button
|
||||
{
|
||||
id: autoUpgradeButton
|
||||
text: catalog.i18nc("@action:button", "Automatically upgrade Firmware");
|
||||
enabled: parent.firmwareName != "" && activeOutputDevice
|
||||
onClicked:
|
||||
{
|
||||
activeOutputDevice.updateFirmware(parent.firmwareName)
|
||||
}
|
||||
}
|
||||
Button
|
||||
{
|
||||
id: manualUpgradeButton
|
||||
text: catalog.i18nc("@action:button", "Upload custom Firmware");
|
||||
enabled: activeOutputDevice != null
|
||||
onClicked:
|
||||
{
|
||||
customFirmwareDialog.open()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FileDialog
|
||||
{
|
||||
id: customFirmwareDialog
|
||||
title: catalog.i18nc("@title:window", "Select custom firmware")
|
||||
nameFilters: "Firmware image files (*.hex)"
|
||||
selectExisting: true
|
||||
onAccepted: activeOutputDevice.updateFirmware(fileUrl)
|
||||
}
|
||||
}
|
||||
}
|
@ -1,22 +1,16 @@
|
||||
# Copyright (c) 2016 Ultimaker B.V.
|
||||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from . import BedLevelMachineAction
|
||||
from . import UpgradeFirmwareMachineAction
|
||||
from . import UMOUpgradeSelection
|
||||
from . import UM2UpgradeSelection
|
||||
|
||||
from UM.i18n import i18nCatalog
|
||||
catalog = i18nCatalog("cura")
|
||||
|
||||
def getMetaData():
|
||||
return {
|
||||
}
|
||||
return {}
|
||||
|
||||
def register(app):
|
||||
return { "machine_action": [
|
||||
BedLevelMachineAction.BedLevelMachineAction(),
|
||||
UpgradeFirmwareMachineAction.UpgradeFirmwareMachineAction(),
|
||||
UMOUpgradeSelection.UMOUpgradeSelection(),
|
||||
UM2UpgradeSelection.UM2UpgradeSelection()
|
||||
]}
|
||||
|
@ -118,6 +118,23 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"FirmwareUpdater": {
|
||||
"package_info": {
|
||||
"package_id": "FirmwareUpdater",
|
||||
"package_type": "plugin",
|
||||
"display_name": "Firmware Updater",
|
||||
"description": "Provides a machine actions for updating firmware.",
|
||||
"package_version": "1.0.0",
|
||||
"sdk_version": 5,
|
||||
"website": "https://ultimaker.com",
|
||||
"author": {
|
||||
"author_id": "Ultimaker",
|
||||
"display_name": "Ultimaker B.V.",
|
||||
"email": "plugins@ultimaker.com",
|
||||
"website": "https://ultimaker.com"
|
||||
}
|
||||
}
|
||||
},
|
||||
"GCodeGzReader": {
|
||||
"package_info": {
|
||||
"package_id": "GCodeGzReader",
|
||||
|
@ -12,7 +12,8 @@
|
||||
"machine_extruder_trains":
|
||||
{
|
||||
"0": "bq_hephestos_extruder_0"
|
||||
}
|
||||
},
|
||||
"firmware_file": "MarlinHephestos2.hex"
|
||||
},
|
||||
|
||||
"overrides": {
|
||||
|
@ -12,7 +12,8 @@
|
||||
"machine_extruder_trains":
|
||||
{
|
||||
"0": "bq_witbox_extruder_0"
|
||||
}
|
||||
},
|
||||
"firmware_file": "MarlinWitbox.hex"
|
||||
},
|
||||
|
||||
"overrides": {
|
||||
|
@ -8,7 +8,6 @@
|
||||
"manufacturer": "NA",
|
||||
"file_formats": "text/x-gcode",
|
||||
"has_materials": false,
|
||||
"supported_actions": [ "MachineSettingsAction", "UpgradeFirmware" ],
|
||||
"machine_extruder_trains":
|
||||
{
|
||||
"0": "makeit_l_dual_1st",
|
||||
|
@ -8,7 +8,6 @@
|
||||
"manufacturer": "NA",
|
||||
"file_formats": "text/x-gcode",
|
||||
"has_materials": false,
|
||||
"supported_actions": [ "MachineSettingsAction", "UpgradeFirmware" ],
|
||||
"machine_extruder_trains":
|
||||
{
|
||||
"0": "makeit_dual_1st",
|
||||
|
@ -10,7 +10,6 @@
|
||||
"platform": "tam_series1.stl",
|
||||
"platform_offset": [-580.0, -6.23, 253.5],
|
||||
"has_materials": false,
|
||||
"supported_actions": ["UpgradeFirmware"],
|
||||
"machine_extruder_trains":
|
||||
{
|
||||
"0": "tam_extruder_0"
|
||||
|
@ -17,11 +17,12 @@
|
||||
"preferred_variant_name": "0.4 mm",
|
||||
"exclude_materials": ["generic_hips", "generic_petg", "generic_bam", "ultimaker_bam", "generic_pva", "ultimaker_pva", "generic_tough_pla", "ultimaker_tough_pla_black", "ultimaker_tough_pla_green", "ultimaker_tough_pla_red", "ultimaker_tough_pla_white"],
|
||||
"first_start_actions": ["UM2UpgradeSelection"],
|
||||
"supported_actions":["UM2UpgradeSelection", "UpgradeFirmware"],
|
||||
"supported_actions":["UM2UpgradeSelection"],
|
||||
"machine_extruder_trains":
|
||||
{
|
||||
"0": "ultimaker2_extruder_0"
|
||||
}
|
||||
},
|
||||
"firmware_file": "MarlinUltimaker2.hex"
|
||||
},
|
||||
"overrides": {
|
||||
"machine_name": { "default_value": "Ultimaker 2" },
|
||||
|
@ -14,7 +14,8 @@
|
||||
"machine_extruder_trains":
|
||||
{
|
||||
"0": "ultimaker2_extended_extruder_0"
|
||||
}
|
||||
},
|
||||
"firmware_file": "MarlinUltimaker2extended.hex"
|
||||
},
|
||||
|
||||
"overrides": {
|
||||
|
@ -10,11 +10,11 @@
|
||||
"file_formats": "text/x-gcode",
|
||||
"platform": "ultimaker2_platform.obj",
|
||||
"platform_texture": "Ultimaker2ExtendedPlusbackplate.png",
|
||||
"supported_actions": ["UpgradeFirmware"],
|
||||
"machine_extruder_trains":
|
||||
{
|
||||
"0": "ultimaker2_extended_plus_extruder_0"
|
||||
}
|
||||
},
|
||||
"firmware_file": "MarlinUltimaker2extended-plus.hex"
|
||||
},
|
||||
|
||||
"overrides": {
|
||||
|
@ -13,11 +13,11 @@
|
||||
"platform_texture": "Ultimaker2Gobackplate.png",
|
||||
"platform_offset": [0, 0, 0],
|
||||
"first_start_actions": [],
|
||||
"supported_actions": ["UpgradeFirmware"],
|
||||
"machine_extruder_trains":
|
||||
{
|
||||
"0": "ultimaker2_go_extruder_0"
|
||||
}
|
||||
},
|
||||
"firmware_file": "MarlinUltimaker2go.hex"
|
||||
},
|
||||
|
||||
"overrides": {
|
||||
|
@ -15,11 +15,11 @@
|
||||
"has_machine_materials": true,
|
||||
"has_machine_quality": true,
|
||||
"first_start_actions": [],
|
||||
"supported_actions": ["UpgradeFirmware"],
|
||||
"machine_extruder_trains":
|
||||
{
|
||||
"0": "ultimaker2_plus_extruder_0"
|
||||
}
|
||||
},
|
||||
"firmware_file": "MarlinUltimaker2plus.hex"
|
||||
},
|
||||
|
||||
"overrides": {
|
||||
|
@ -14,11 +14,13 @@
|
||||
"has_machine_quality": true,
|
||||
"exclude_materials": ["generic_hips", "generic_petg", "generic_bam", "ultimaker_bam", "generic_pva", "ultimaker_pva", "generic_tough_pla", "ultimaker_tough_pla_black", "ultimaker_tough_pla_green", "ultimaker_tough_pla_red", "ultimaker_tough_pla_white"],
|
||||
"first_start_actions": ["UMOUpgradeSelection", "UMOCheckup", "BedLevel"],
|
||||
"supported_actions": ["UMOUpgradeSelection", "UMOCheckup", "BedLevel", "UpgradeFirmware"],
|
||||
"supported_actions": ["UMOUpgradeSelection", "UMOCheckup", "BedLevel"],
|
||||
"machine_extruder_trains":
|
||||
{
|
||||
"0": "ultimaker_original_extruder_0"
|
||||
}
|
||||
},
|
||||
"firmware_file": "MarlinUltimaker-{baudrate}.hex",
|
||||
"firmware_hbk_file": "MarlinUltimaker-HBK-{baudrate}.hex"
|
||||
},
|
||||
|
||||
"overrides": {
|
||||
|
@ -19,8 +19,10 @@
|
||||
"0": "ultimaker_original_dual_1st",
|
||||
"1": "ultimaker_original_dual_2nd"
|
||||
},
|
||||
"firmware_file": "MarlinUltimaker-{baudrate}-dual.hex",
|
||||
"firmware_hbk_file": "MarlinUltimaker-HBK-{baudrate}-dual.hex",
|
||||
"first_start_actions": ["UMOUpgradeSelection", "UMOCheckup", "BedLevel"],
|
||||
"supported_actions": ["UMOUpgradeSelection", "UMOCheckup", "BedLevel", "UpgradeFirmware"]
|
||||
"supported_actions": ["UMOUpgradeSelection", "UMOCheckup", "BedLevel"]
|
||||
},
|
||||
|
||||
"overrides": {
|
||||
|
@ -12,11 +12,12 @@
|
||||
"platform_texture": "UltimakerPlusbackplate.png",
|
||||
"quality_definition": "ultimaker_original",
|
||||
"first_start_actions": ["UMOCheckup", "BedLevel"],
|
||||
"supported_actions": ["UMOCheckup", "BedLevel", "UpgradeFirmware"],
|
||||
"supported_actions": ["UMOCheckup", "BedLevel"],
|
||||
"machine_extruder_trains":
|
||||
{
|
||||
"0": "ultimaker_original_plus_extruder_0"
|
||||
}
|
||||
},
|
||||
"firmware_file": "MarlinUltimaker-UMOP-{baudrate}.hex"
|
||||
},
|
||||
|
||||
"overrides": {
|
||||
|
@ -18,9 +18,6 @@
|
||||
0,
|
||||
-28,
|
||||
0
|
||||
],
|
||||
"supported_actions": [
|
||||
"UpgradeFirmware"
|
||||
]
|
||||
},
|
||||
"overrides": {
|
||||
|
Loading…
x
Reference in New Issue
Block a user