mirror of
https://github.com/Ultimaker/Cura.git
synced 2025-07-19 04:37:51 -06:00
Refactoring & documentation
CURA-1339
This commit is contained in:
parent
9ee6323177
commit
4b5c118ed2
3 changed files with 28 additions and 270 deletions
|
@ -1,244 +0,0 @@
|
|||
# Copyright (c) 2015 Ultimaker B.V.
|
||||
# Cura is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
from UM.Signal import Signal, SignalEmitter
|
||||
from . import USBPrinterOutputDevice
|
||||
from UM.Application import Application
|
||||
from UM.Resources import Resources
|
||||
from UM.Logger import Logger
|
||||
from UM.PluginRegistry import PluginRegistry
|
||||
from UM.OutputDevice.OutputDevicePlugin import OutputDevicePlugin
|
||||
from cura.PrinterOutputDevice import ConnectionState
|
||||
from UM.Qt.ListModel import ListModel
|
||||
from UM.Message import Message
|
||||
|
||||
from cura.CuraApplication import CuraApplication
|
||||
|
||||
import threading
|
||||
import platform
|
||||
import glob
|
||||
import time
|
||||
import os.path
|
||||
from UM.Extension import Extension
|
||||
|
||||
from PyQt5.QtQml import QQmlComponent, QQmlContext
|
||||
from PyQt5.QtCore import QUrl, QObject, pyqtSlot, pyqtProperty, pyqtSignal, Qt
|
||||
from UM.i18n import i18nCatalog
|
||||
i18n_catalog = i18nCatalog("cura")
|
||||
|
||||
class USBPrinterManager(QObject, SignalEmitter, OutputDevicePlugin, Extension):
|
||||
def __init__(self, parent = None):
|
||||
QObject.__init__(self, parent)
|
||||
SignalEmitter.__init__(self)
|
||||
OutputDevicePlugin.__init__(self)
|
||||
Extension.__init__(self)
|
||||
self._serial_port_list = []
|
||||
self._printer_connections = {}
|
||||
self._printer_connections_model = None
|
||||
self._update_thread = threading.Thread(target = self._updateThread)
|
||||
self._update_thread.setDaemon(True)
|
||||
|
||||
self._check_updates = True
|
||||
self._firmware_view = None
|
||||
|
||||
## Add menu item to top menu of the application.
|
||||
self.setMenuName(i18n_catalog.i18nc("@title:menu","Firmware"))
|
||||
self.addMenuItem(i18n_catalog.i18nc("@item:inmenu", "Update Firmware"), self.updateAllFirmware)
|
||||
|
||||
Application.getInstance().applicationShuttingDown.connect(self.stop)
|
||||
self.addConnectionSignal.connect(self.addConnection) #Because the model needs to be created in the same thread as the QMLEngine, we use a signal.
|
||||
|
||||
addConnectionSignal = Signal()
|
||||
printerConnectionStateChanged = pyqtSignal()
|
||||
|
||||
progressChanged = pyqtSignal()
|
||||
|
||||
@pyqtProperty(float, notify = progressChanged)
|
||||
def progress(self):
|
||||
progress = 0
|
||||
for printer_name, connection in self._printer_connections.items(): # TODO: @UnusedVariable "printer_name"
|
||||
progress += connection.progress
|
||||
|
||||
return progress / len(self._printer_connections)
|
||||
|
||||
def start(self):
|
||||
self._check_updates = True
|
||||
self._update_thread.start()
|
||||
|
||||
def stop(self):
|
||||
self._check_updates = False
|
||||
try:
|
||||
self._update_thread.join()
|
||||
except RuntimeError:
|
||||
pass
|
||||
|
||||
def _updateThread(self):
|
||||
while self._check_updates:
|
||||
result = self.getSerialPortList(only_list_usb = True)
|
||||
self._addRemovePorts(result)
|
||||
time.sleep(5)
|
||||
|
||||
## Show firmware interface.
|
||||
# This will create the view if its not already created.
|
||||
def spawnFirmwareInterface(self, serial_port):
|
||||
if self._firmware_view is None:
|
||||
path = QUrl.fromLocalFile(os.path.join(PluginRegistry.getInstance().getPluginPath("USBPrinting"), "FirmwareUpdateWindow.qml"))
|
||||
component = QQmlComponent(Application.getInstance()._engine, path)
|
||||
|
||||
self._firmware_context = QQmlContext(Application.getInstance()._engine.rootContext())
|
||||
self._firmware_context.setContextProperty("manager", self)
|
||||
self._firmware_view = component.create(self._firmware_context)
|
||||
|
||||
self._firmware_view.show()
|
||||
|
||||
@pyqtSlot()
|
||||
def updateAllFirmware(self):
|
||||
if not self._printer_connections:
|
||||
Message(i18n_catalog.i18nc("@info","Cannot update firmware, there were no connected printers found.")).show()
|
||||
return
|
||||
|
||||
self.spawnFirmwareInterface("")
|
||||
for printer_connection in self._printer_connections:
|
||||
try:
|
||||
self._printer_connections[printer_connection].updateFirmware(Resources.getPath(CuraApplication.ResourceTypes.Firmware, self._getDefaultFirmwareName()))
|
||||
except FileNotFoundError:
|
||||
self._printer_connections[printer_connection].setProgress(100, 100)
|
||||
Logger.log("w", "No firmware found for printer %s", printer_connection)
|
||||
continue
|
||||
|
||||
@pyqtSlot(str, result = bool)
|
||||
def updateFirmwareBySerial(self, serial_port):
|
||||
if serial_port in self._printer_connections:
|
||||
self.spawnFirmwareInterface(self._printer_connections[serial_port].getSerialPort())
|
||||
try:
|
||||
self._printer_connections[serial_port].updateFirmware(Resources.getPath(CuraApplication.ResourceTypes.Firmware, self._getDefaultFirmwareName()))
|
||||
except FileNotFoundError:
|
||||
self._firmware_view.close()
|
||||
Logger.log("e", "Could not find firmware required for this machine")
|
||||
return False
|
||||
return True
|
||||
return False
|
||||
|
||||
## Return the singleton instance of the USBPrinterManager
|
||||
@classmethod
|
||||
def getInstance(cls, engine = None, script_engine = None):
|
||||
# Note: Explicit use of class name to prevent issues with inheritance.
|
||||
if USBPrinterManager._instance is None:
|
||||
USBPrinterManager._instance = cls()
|
||||
|
||||
return USBPrinterManager._instance
|
||||
|
||||
def _getDefaultFirmwareName(self):
|
||||
machine_instance = Application.getInstance().getMachineManager().getActiveMachineInstance()
|
||||
machine_type = machine_instance.getMachineDefinition().getId()
|
||||
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",
|
||||
"ultimaker_original" : "MarlinUltimaker-{baudrate}.hex",
|
||||
"ultimaker_original_plus" : "MarlinUltimaker-UMOP-{baudrate}.hex",
|
||||
"ultimaker2" : "MarlinUltimaker2.hex",
|
||||
"ultimaker2_go" : "MarlinUltimaker2go.hex",
|
||||
"ultimaker2plus" : "MarlinUltimaker2plus.hex",
|
||||
"ultimaker2_extended" : "MarlinUltimaker2extended.hex",
|
||||
"ultimaker2_extended_plus" : "MarlinUltimaker2extended-plus.hex",
|
||||
}
|
||||
machine_with_heated_bed = {"ultimaker_original" : "MarlinUltimaker-HBK-{baudrate}.hex",
|
||||
}
|
||||
|
||||
##TODO: Add check for multiple extruders
|
||||
hex_file = None
|
||||
if machine_type in machine_without_extras.keys(): # The machine needs to be defined here!
|
||||
if machine_type in machine_with_heated_bed.keys() and machine_instance.getMachineSettingValue("machine_heated_bed"):
|
||||
Logger.log("d", "Choosing firmware with heated bed enabled for machine %s.", machine_type)
|
||||
hex_file = machine_with_heated_bed[machine_type] # Return firmware with heated bed enabled
|
||||
else:
|
||||
Logger.log("d", "Choosing basic firmware for machine %s.", machine_type)
|
||||
hex_file = machine_without_extras[machine_type] # Return "basic" firmware
|
||||
else:
|
||||
Logger.log("e", "There is no firmware for machine %s.", machine_type)
|
||||
|
||||
if hex_file:
|
||||
return hex_file.format(baudrate=baudrate)
|
||||
else:
|
||||
Logger.log("e", "Could not find any firmware for machine %s.", machine_type)
|
||||
raise FileNotFoundError()
|
||||
|
||||
def _addRemovePorts(self, serial_ports):
|
||||
# First, find and add all new or changed keys
|
||||
for serial_port in list(serial_ports):
|
||||
if serial_port not in self._serial_port_list:
|
||||
self.addConnectionSignal.emit(serial_port) #Hack to ensure its created in main thread
|
||||
continue
|
||||
self._serial_port_list = list(serial_ports)
|
||||
|
||||
connections_to_remove = []
|
||||
for port, connection in self._printer_connections.items():
|
||||
if port not in self._serial_port_list:
|
||||
connection.close()
|
||||
connections_to_remove.append(port)
|
||||
|
||||
for port in connections_to_remove:
|
||||
del self._printer_connections[port]
|
||||
|
||||
|
||||
## Because the model needs to be created in the same thread as the QMLEngine, we use a signal.
|
||||
def addConnection(self, serial_port):
|
||||
connection = USBPrinterOutputDevice.USBPrinterOutputDevice(serial_port)
|
||||
connection.connect()
|
||||
connection.connectionStateChanged.connect(self._onPrinterConnectionStateChanged)
|
||||
connection.progressChanged.connect(self.progressChanged)
|
||||
self._printer_connections[serial_port] = connection
|
||||
|
||||
def _onPrinterConnectionStateChanged(self, serial_port):
|
||||
try:
|
||||
if self._printer_connections[serial_port].connectionState == ConnectionState.CONNECTED:
|
||||
self.getOutputDeviceManager().addOutputDevice(self._printer_connections[serial_port])
|
||||
else:
|
||||
self.getOutputDeviceManager().removeOutputDevice(serial_port)
|
||||
self.printerConnectionStateChanged.emit()
|
||||
except KeyError:
|
||||
pass # no output device by this device_id found in connection list.
|
||||
|
||||
|
||||
@pyqtProperty(QObject , notify = printerConnectionStateChanged)
|
||||
def connectedPrinterList(self):
|
||||
self._printer_connections_model = ListModel()
|
||||
self._printer_connections_model.addRoleName(Qt.UserRole + 1,"name")
|
||||
self._printer_connections_model.addRoleName(Qt.UserRole + 2, "printer")
|
||||
for connection in self._printer_connections:
|
||||
if self._printer_connections[connection].connectionState == ConnectionState.CONNECTED:
|
||||
self._printer_connections_model.appendItem({"name":connection, "printer": self._printer_connections[connection]})
|
||||
return self._printer_connections_model
|
||||
|
||||
## Create a list of serial ports on the system.
|
||||
# \param only_list_usb If true, only usb ports are listed
|
||||
def getSerialPortList(self, only_list_usb = False):
|
||||
base_list = []
|
||||
if platform.system() == "Windows":
|
||||
import winreg
|
||||
try:
|
||||
key = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE,"HARDWARE\\DEVICEMAP\\SERIALCOMM")
|
||||
i = 0
|
||||
while True:
|
||||
values = winreg.EnumValue(key, i)
|
||||
if not only_list_usb or "USBSER" in values[0]:
|
||||
base_list += [values[1]]
|
||||
i += 1
|
||||
except Exception as e:
|
||||
pass
|
||||
else:
|
||||
if only_list_usb:
|
||||
base_list = base_list + glob.glob("/dev/ttyUSB*") + glob.glob("/dev/ttyACM*") + glob.glob("/dev/cu.usb*")
|
||||
base_list = filter(lambda s: "Bluetooth" not in s, base_list) # Filter because mac sometimes puts them in the list
|
||||
else:
|
||||
base_list = base_list + glob.glob("/dev/ttyUSB*") + glob.glob("/dev/ttyACM*") + glob.glob("/dev/cu.*") + glob.glob("/dev/tty.usb*") + glob.glob("/dev/rfcomm*") + glob.glob("/dev/serial/by-id/*")
|
||||
return list(base_list)
|
||||
|
||||
_instance = None
|
|
@ -72,6 +72,7 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
|
|||
# Current Z stage location
|
||||
self._current_z = 0
|
||||
|
||||
# Check if endstops are ever pressed (used for first run)
|
||||
self._x_min_endstop_pressed = False
|
||||
self._y_min_endstop_pressed = False
|
||||
self._z_min_endstop_pressed = False
|
||||
|
@ -140,14 +141,14 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
|
|||
for layer in gcode_list:
|
||||
self._gcode.extend(layer.split("\n"))
|
||||
|
||||
#Reset line number. If this is not done, first line is sometimes ignored
|
||||
# Reset line number. If this is not done, first line is sometimes ignored
|
||||
self._gcode.insert(0, "M110")
|
||||
self._gcode_position = 0
|
||||
self._print_start_time_100 = None
|
||||
self._is_printing = True
|
||||
self._print_start_time = time.time()
|
||||
|
||||
for i in range(0, 4): #Push first 4 entries before accepting other inputs
|
||||
for i in range(0, 4): # Push first 4 entries before accepting other inputs
|
||||
self._sendNextGcodeLine()
|
||||
|
||||
self.writeFinished.emit(self)
|
||||
|
@ -162,7 +163,7 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
|
|||
if not self._updating_firmware and not self._connect_thread.isAlive():
|
||||
self._connect_thread.start()
|
||||
|
||||
## Private fuction (threaded) that actually uploads the firmware.
|
||||
## Private function (threaded) that actually uploads the firmware.
|
||||
def _updateFirmware(self):
|
||||
self.setProgress(0, 100)
|
||||
|
||||
|
@ -182,7 +183,8 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
|
|||
except Exception:
|
||||
pass
|
||||
|
||||
time.sleep(1) # Give programmer some time to connect. Might need more in some cases, but this worked in all tested cases.
|
||||
# Give programmer some time to connect. Might need more in some cases, but this worked in all tested cases.
|
||||
time.sleep(1)
|
||||
|
||||
if not programmer.isConnected():
|
||||
Logger.log("e", "Unable to connect with serial. Could not update firmware")
|
||||
|
@ -238,7 +240,8 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
|
|||
except Exception as e:
|
||||
Logger.log("i", "Could not establish connection on %s, unknown reasons. Device is not arduino based." % self._serial_port)
|
||||
|
||||
# If the programmer connected, we know its an atmega based version. Not all that useful, but it does give some debugging information.
|
||||
# If the programmer connected, we know its an atmega based version.
|
||||
# Not all that useful, but it does give some debugging information.
|
||||
for baud_rate in self._getBaudrateList(): # Cycle all baud rates (auto detect)
|
||||
Logger.log("d","Attempting to connect to printer with serial %s on baud rate %s", self._serial_port, baud_rate)
|
||||
if self._serial is None:
|
||||
|
@ -312,7 +315,6 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
|
|||
self._listen_thread.daemon = True
|
||||
self._serial = None
|
||||
|
||||
|
||||
## Directly send the command, withouth checking connection state (eg; printing).
|
||||
# \param cmd string with g-code
|
||||
def _sendCommand(self, cmd):
|
||||
|
@ -418,7 +420,7 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
|
|||
if not self.hasError():
|
||||
self._setErrorState(line[6:])
|
||||
|
||||
elif b" T:" in line or line.startswith(b"T:"): #Temperature message
|
||||
elif b" T:" in line or line.startswith(b"T:"): # Temperature message
|
||||
try:
|
||||
self._setHotendTemperature(self._temperature_requested_extruder_index, float(re.search(b"T: *([0-9\.]*)", line).group(1)))
|
||||
except:
|
||||
|
@ -435,7 +437,7 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
|
|||
|
||||
if self._is_printing:
|
||||
if line == b"" and time.time() > ok_timeout:
|
||||
line = b"ok" # Force a timeout (basicly, send next command)
|
||||
line = b"ok" # Force a timeout (basically, send next command)
|
||||
|
||||
if b"ok" in line:
|
||||
ok_timeout = time.time() + 5
|
||||
|
@ -472,7 +474,7 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
|
|||
line = line.strip()
|
||||
try:
|
||||
if line == "M0" or line == "M1":
|
||||
line = "M105" #Don't send the M0 or M1 to the machine, as M0 and M1 are handled as an LCD menu pause.
|
||||
line = "M105" # Don't send the M0 or M1 to the machine, as M0 and M1 are handled as an LCD menu pause.
|
||||
if ("G0" in line or "G1" in line) and "Z" in line:
|
||||
z = float(re.search("Z([0-9\.]*)", line).group(1))
|
||||
if self._current_z != z:
|
||||
|
@ -484,13 +486,13 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
|
|||
|
||||
self._sendCommand("N%d%s*%d" % (self._gcode_position, line, checksum))
|
||||
self._gcode_position += 1
|
||||
self.setProgress(( self._gcode_position / len(self._gcode)) * 100)
|
||||
self.setProgress((self._gcode_position / len(self._gcode)) * 100)
|
||||
self.progressChanged.emit()
|
||||
|
||||
## Set the progress of the print.
|
||||
# It will be normalized (based on max_progress) to range 0 - 100
|
||||
def setProgress(self, progress, max_progress = 100):
|
||||
self._progress = (progress / max_progress) * 100 #Convert to scale of 0-100
|
||||
self._progress = (progress / max_progress) * 100 # Convert to scale of 0-100
|
||||
self.progressChanged.emit()
|
||||
|
||||
## Cancel the current print. Printer connection wil continue to listen.
|
||||
|
@ -507,7 +509,7 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
|
|||
|
||||
## Check if the process did not encounter an error yet.
|
||||
def hasError(self):
|
||||
return self._error_state != None
|
||||
return self._error_state is not None
|
||||
|
||||
## private read line used by printer connection to listen for data on serial port.
|
||||
def _readline(self):
|
||||
|
@ -516,7 +518,7 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
|
|||
try:
|
||||
ret = self._serial.readline()
|
||||
except Exception as e:
|
||||
Logger.log("e","Unexpected error while reading serial port. %s" %e)
|
||||
Logger.log("e", "Unexpected error while reading serial port. %s" % e)
|
||||
self._setErrorState("Printer has been disconnected")
|
||||
self.close()
|
||||
return None
|
||||
|
@ -530,7 +532,7 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
|
|||
|
||||
def _onFirmwareUpdateComplete(self):
|
||||
self._update_firmware_thread.join()
|
||||
self._update_firmware_thread = threading.Thread(target= self._updateFirmware)
|
||||
self._update_firmware_thread = threading.Thread(target = self._updateFirmware)
|
||||
self._update_firmware_thread.daemon = True
|
||||
|
||||
self.connect()
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# Copyright (c) 2015 Ultimaker B.V.
|
||||
# Cura is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
from . import USBPrinterManager
|
||||
from . import USBPrinterOutputDeviceManager
|
||||
from PyQt5.QtQml import qmlRegisterType, qmlRegisterSingletonType
|
||||
from UM.i18n import i18nCatalog
|
||||
i18n_catalog = i18nCatalog("cura")
|
||||
|
@ -19,5 +19,5 @@ def getMetaData():
|
|||
}
|
||||
|
||||
def register(app):
|
||||
qmlRegisterSingletonType(USBPrinterManager.USBPrinterManager, "UM", 1, 0, "USBPrinterManager", USBPrinterManager.USBPrinterManager.getInstance)
|
||||
return {"extension":USBPrinterManager.USBPrinterManager.getInstance(),"output_device": USBPrinterManager.USBPrinterManager.getInstance() }
|
||||
qmlRegisterSingletonType(USBPrinterOutputDeviceManager.USBPrinterOutputDeviceManager, "UM", 1, 0, "USBPrinterManager", USBPrinterOutputDeviceManager.USBPrinterOutputDeviceManager.getInstance)
|
||||
return {"extension":USBPrinterOutputDeviceManager.USBPrinterOutputDeviceManager.getInstance(), "output_device": USBPrinterOutputDeviceManager.USBPrinterOutputDeviceManager.getInstance()}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue