def createActionsModel(self, options): model = ListModel() model.addRoleName(self.NameRole, "text") for option in options: model.appendItem({"text": str(option)}) if len(options) != 0: return model return None
def createActionsModel(self, options): model = ListModel() model.addRoleName(self.NameRole,"text") for option in options: model.appendItem({"text": str(option)}) if len(options) != 0: return model return None
def _createPagesModel(self, steps): model = ListModel() model.addRoleName(self.NameRole, "page") model.addRoleName(self.NameRole, "title") for step in steps: _page = step.get("page") _title = step.get("title") model.appendItem({"title": str(_title), "page": str(_page)}) return model
def _createPagesModel(self, steps): model = ListModel() model.addRoleName(self.NameRole,"page") model.addRoleName(self.NameRole,"title") for step in steps: _page = step.get("page") _title = step.get("title") model.appendItem({"title": str(_title), "page": str(_page)}) return model
def createOptionsModel(self, options): if not options: return None model = ListModel() model.addRoleName(Qt.UserRole + 1, "value") model.addRoleName(Qt.UserRole + 2, "name") for value, name in options.items(): model.appendItem({"value": str(value), "name": str(name)}) return model
def createActionsModel(self, actions): model = ListModel() model.addRoleName(self.IDRole, "action_id") model.addRoleName(self.TextRole,"name") model.addRoleName(self.IconRole, "icon") model.addRoleName(self.DescriptionRole, "description") for action in actions: model.appendItem(action) return model
class USBPrinterOutputDeviceManager(QObject, OutputDevicePlugin, Extension): def __init__(self, parent=None): super().__init__(parent=parent) self._serial_port_list = [] self._usb_output_devices = {} self._usb_output_devices_model = None self._update_thread = threading.Thread(target=self._updateThread) self._update_thread.setDaemon(True) self._check_updates = True self._firmware_view = None Application.getInstance().applicationShuttingDown.connect(self.stop) self.addUSBOutputDeviceSignal.connect( self.addOutputDevice ) #Because the model needs to be created in the same thread as the QMLEngine, we use a signal. addUSBOutputDeviceSignal = Signal() connectionStateChanged = pyqtSignal() progressChanged = pyqtSignal() firmwareUpdateChange = pyqtSignal() @pyqtProperty(float, notify=progressChanged) def progress(self): progress = 0 for printer_name, device in self._usb_output_devices.items( ): # TODO: @UnusedVariable "printer_name" progress += device.progress return progress / len(self._usb_output_devices) @pyqtProperty(int, notify=progressChanged) def errorCode(self): for printer_name, device in self._usb_output_devices.items( ): # TODO: @UnusedVariable "printer_name" if device._error_code: return device._error_code return 0 ## Return True if all printers finished firmware update @pyqtProperty(float, notify=firmwareUpdateChange) def firmwareUpdateCompleteStatus(self): complete = True for printer_name, device in self._usb_output_devices.items( ): # TODO: @UnusedVariable "printer_name" if not device.firmwareUpdateFinished: complete = False return complete def start(self): self._check_updates = True self._update_thread.start() def stop(self): self._check_updates = False 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(str) def updateAllFirmware(self, file_name): if not self._usb_output_devices: Message( i18n_catalog.i18nc( "@info", "Unable to update firmware because there are no printers connected." )).show() return if file_name.startswith("file://"): file_name = QUrl(file_name).toLocalFile( ) # File dialogs prepend the path with file://, which we don't need / want for printer_connection in self._usb_output_devices: if self._usb_output_devices[ printer_connection].connectionState != ConnectionState.connected: Message( i18n_catalog.i18nc( "@info", "Unable to update firmware because printer connection isn't established yet." )).show() return self._usb_output_devices[printer_connection].resetFirmwareUpdate() self.spawnFirmwareInterface("") for printer_connection in self._usb_output_devices: try: self._usb_output_devices[printer_connection].updateFirmware( file_name) except FileNotFoundError: # Should only happen in dev environments where the resources/firmware folder is absent. self._usb_output_devices[printer_connection].setProgress( 100, 100) Logger.log("w", "No firmware found for printer %s called '%s'", printer_connection, file_name) Message( i18n_catalog.i18nc( "@info", "Could not find firmware required for the printer at %s." ) % printer_connection).show() self._firmware_view.close() continue @pyqtSlot(str, str, result=bool) def updateFirmwareBySerial(self, serial_port, file_name): if serial_port in self._usb_output_devices: self.spawnFirmwareInterface( self._usb_output_devices[serial_port].getSerialPort()) try: self._usb_output_devices[serial_port].updateFirmware(file_name) except FileNotFoundError: self._firmware_view.close() Logger.log( "e", "Could not find firmware required for this machine called '%s'", file_name) 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 USBPrinterOutputDeviceManager._instance is None: USBPrinterOutputDeviceManager._instance = cls() return USBPrinterOutputDeviceManager._instance ## Helper to identify serial ports (and scan for them) 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.addUSBOutputDeviceSignal.emit( serial_port) # Hack to ensure its created in main thread continue self._serial_port_list = list(serial_ports) devices_to_remove = [] for port, device in self._usb_output_devices.items(): if port not in self._serial_port_list: device.close() devices_to_remove.append(port) for port in devices_to_remove: del self._usb_output_devices[port] ## Because the model needs to be created in the same thread as the QMLEngine, we use a signal. def addOutputDevice(self, serial_port): device = USBPrinterOutputDevice.USBPrinterOutputDevice(serial_port) device.connectionStateChanged.connect(self._onConnectionStateChanged) device.connect() device.progressChanged.connect(self.progressChanged) device.firmwareUpdateChange.connect(self.firmwareUpdateChange) self._usb_output_devices[serial_port] = device ## If one of the states of the connected devices change, we might need to add / remove them from the global list. def _onConnectionStateChanged(self, serial_port): try: if self._usb_output_devices[serial_port].connectionState in [ ConnectionState.connected, ConnectionState.connecting ]: self.getOutputDeviceManager().addOutputDevice( self._usb_output_devices[serial_port]) else: self.getOutputDeviceManager().removeOutputDevice(serial_port) self.connectionStateChanged.emit() except KeyError: Logger.log( "w", "Connection state of %s changed, but it was not found in the list", serial_port) @pyqtProperty(QObject, notify=connectionStateChanged) def connectedPrinterList(self): self._usb_output_devices_model = ListModel() self._usb_output_devices_model.addRoleName(Qt.UserRole + 1, "name") self._usb_output_devices_model.addRoleName(Qt.UserRole + 2, "printer") for connection in self._usb_output_devices: if self._usb_output_devices[ connection].connectionState == ConnectionState.connected: self._usb_output_devices_model.appendItem({ "name": connection, "printer": self._usb_output_devices[connection] }) return self._usb_output_devices_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 = [] for port in serial.tools.list_ports.comports(): if not isinstance(port, tuple): port = (port.device, port.description, port.hwid) if only_list_usb and not port[2].startswith("USB"): continue base_list += [port[0]] return list(base_list) _instance = None # type: "USBPrinterOutputDeviceManager"
def createOptionsModel(self, options): model = ListModel() model.addRoleName(self.NameRole,"text") for option in options: model.appendItem({"text": str(option)}) return model
class USBPrinterOutputDeviceManager(QObject, OutputDevicePlugin, Extension): def __init__(self, parent = None): super().__init__(parent = parent) self._serial_port_list = [] self._usb_output_devices = {} self._usb_output_devices_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.addUSBOutputDeviceSignal.connect(self.addOutputDevice) #Because the model needs to be created in the same thread as the QMLEngine, we use a signal. addUSBOutputDeviceSignal = Signal() connectionStateChanged = pyqtSignal() progressChanged = pyqtSignal() @pyqtProperty(float, notify = progressChanged) def progress(self): progress = 0 for printer_name, device in self._usb_output_devices.items(): # TODO: @UnusedVariable "printer_name" progress += device.progress return progress / len(self._usb_output_devices) 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._usb_output_devices: Message(i18n_catalog.i18nc("@info","Cannot update firmware, there were no connected printers found.")).show() return self.spawnFirmwareInterface("") for printer_connection in self._usb_output_devices: try: self._usb_output_devices[printer_connection].updateFirmware(Resources.getPath(CuraApplication.ResourceTypes.Firmware, self._getDefaultFirmwareName())) except FileNotFoundError: self._usb_output_devices[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._usb_output_devices: self.spawnFirmwareInterface(self._usb_output_devices[serial_port].getSerialPort()) try: self._usb_output_devices[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 USBPrinterOutputDeviceManager._instance is None: USBPrinterOutputDeviceManager._instance = cls() return USBPrinterOutputDeviceManager._instance def _getDefaultFirmwareName(self): # Check if there is a valid global container stack global_container_stack = Application.getInstance().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", "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_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("e", "There is no firmware for machine %s.", machine_id) if hex_file: return hex_file.format(baudrate=baudrate) else: Logger.log("e", "Could not find any firmware for machine %s.", machine_id) raise FileNotFoundError() ## Helper to identify serial ports (and scan for them) 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.addUSBOutputDeviceSignal.emit(serial_port) # Hack to ensure its created in main thread continue self._serial_port_list = list(serial_ports) devices_to_remove = [] for port, device in self._usb_output_devices.items(): if port not in self._serial_port_list: device.close() devices_to_remove.append(port) for port in devices_to_remove: del self._usb_output_devices[port] ## Because the model needs to be created in the same thread as the QMLEngine, we use a signal. def addOutputDevice(self, serial_port): device = USBPrinterOutputDevice.USBPrinterOutputDevice(serial_port) device.connectionStateChanged.connect(self._onConnectionStateChanged) device.connect() device.progressChanged.connect(self.progressChanged) self._usb_output_devices[serial_port] = device ## If one of the states of the connected devices change, we might need to add / remove them from the global list. def _onConnectionStateChanged(self, serial_port): try: if self._usb_output_devices[serial_port].connectionState == ConnectionState.connected: self.getOutputDeviceManager().addOutputDevice(self._usb_output_devices[serial_port]) else: self.getOutputDeviceManager().removeOutputDevice(serial_port) self.connectionStateChanged.emit() except KeyError: pass # no output device by this device_id found in connection list. @pyqtProperty(QObject , notify = connectionStateChanged) def connectedPrinterList(self): self._usb_output_devices_model = ListModel() self._usb_output_devices_model.addRoleName(Qt.UserRole + 1, "name") self._usb_output_devices_model.addRoleName(Qt.UserRole + 2, "printer") for connection in self._usb_output_devices: if self._usb_output_devices[connection].connectionState == ConnectionState.connected: self._usb_output_devices_model.appendItem({"name": connection, "printer": self._usb_output_devices[connection]}) return self._usb_output_devices_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 #@UnresolvedImport 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
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("Firmware") self.addMenuItem(i18n_catalog.i18n("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() 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() def updateAllFirmware(self): self.spawnFirmwareInterface("") for printer_connection in self._printer_connections: try: self._printer_connections[printer_connection].updateFirmware( Resources.getPath(Resources.FirmwareLocation, self._getDefaultFirmwareName())) except FileNotFoundError: 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(Resources.FirmwareLocation, 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_type = Application.getInstance().getActiveMachine().getTypeID() firmware_name = "" baudrate = 250000 if sys.platform.startswith("linux"): baudrate = 115200 if machine_type == "ultimaker_original": firmware_name = "MarlinUltimaker" firmware_name += "-%d" % (baudrate) elif machine_type == "ultimaker_original_plus": firmware_name = "MarlinUltimaker-UMOP-%d" % (baudrate) elif machine_type == "Witbox": return "MarlinWitbox.hex" elif machine_type == "ultimaker2go": return "MarlinUltimaker2go.hex" elif machine_type == "ultimaker2extended": return "MarlinUltimaker2extended.hex" elif machine_type == "ultimaker2": return "MarlinUltimaker2.hex" ##TODO: Add check for multiple extruders if firmware_name != "": firmware_name += ".hex" return firmware_name 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) ## Because the model needs to be created in the same thread as the QMLEngine, we use a signal. def addConnection(self, serial_port): connection = PrinterConnection.PrinterConnection(serial_port) connection.connect() connection.connectionStateChanged.connect( self._onPrinterConnectionStateChanged) self._printer_connections[serial_port] = connection def _onPrinterConnectionStateChanged(self, serial_port): if self._printer_connections[serial_port].isConnected(): self.getOutputDeviceManager().addOutputDevice( self._printer_connections[serial_port]) else: self.getOutputDeviceManager().removeOutputDevice(serial_port) self.printerConnectionStateChanged.emit() @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].isConnected(): 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
class Doodle3D(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 self.updatetrigger = False # Add menu item to top menu of the application. self.setMenuName(i18n_catalog.i18nc("@title:menu", "Doodle3D")) self.addMenuItem(i18n_catalog.i18nc("@item:inlistbox", "Enable Scan devices..."), 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() settingChanged = pyqtSignal() """ 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.updatetrigger==True: result = self.getSerialPortList() #Logger.log("d","Connected Boxes: %s" % result) thereturn = self._addRemovePorts(result) if thereturn == False: self.updatetrigger=False break 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("Doodle3D"), "SettingsWindow.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() def updateAllFirmware(self): self.updatetrigger = True try: self._update_thread.start() except RuntimeError: Logger.log("d","[Doodle3D] Thread already started") """ 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: 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 Doodle3D._instance is None: Doodle3D._instance = cls() return Doodle3D._instance def _getDefaultFirmwareName(self): machine_type = Application.getInstance().getMachineManager().getActiveMachineInstance().getMachineDefinition().getId() firmware_name = "" baudrate = 250000 if sys.platform.startswith("linux"): baudrate = 115200 if machine_type == "ultimaker_original": firmware_name = "MarlinUltimaker" firmware_name += "-%d" % (baudrate) elif machine_type == "ultimaker_original_plus": firmware_name = "MarlinUltimaker-UMOP-%d" % (baudrate) elif machine_type == "Witbox": return "MarlinWitbox.hex" elif machine_type == "ultimaker2go": return "MarlinUltimaker2go.hex" elif machine_type == "ultimaker2extended": return "MarlinUltimaker2extended.hex" elif machine_type == "ultimaker2": return "MarlinUltimaker2.hex" # TODO: Add check for multiple extruders if firmware_name != "": firmware_name += ".hex" return firmware_name def _addRemovePorts(self, serial_ports): # First, find and add all new or changed keys if serial_ports == None: return False for boxIP, boxIDENT in serial_ports.items(): if boxIP not in self._serial_port_list: self.addConnectionSignal.emit(boxIP,boxIDENT) continue self._serial_port_list = serial_ports # Because the model needs to be created in the same thread as the QMLEngine, we use a signal. def addConnection(self, serial_port, wifiboxid): connection = PrinterConnection.PrinterConnection(serial_port, wifiboxid) connection.connect() connection.connectionStateChanged.connect(self._onPrinterConnectionStateChanged) self._printer_connections[serial_port] = connection def _onPrinterConnectionStateChanged(self, serial_port): if self._printer_connections[serial_port].isConnected(): self.getOutputDeviceManager().addOutputDevice(self._printer_connections[serial_port]) else: self.getOutputDeviceManager().removeOutputDevice(serial_port) self.printerConnectionStateChanged.emit() @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].isConnected(): 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. def getSerialPortList(self): base_list = {} # Get response from api/list.php and retrieve local ip # from each individual boxes found on the local network boxesListResponse = self.get("connect.doodle3d.com", "/api/list.php") if (boxesListResponse == False): return boxes = boxesListResponse['data'] for index in range(len(boxes)): box = boxes[index] # Check if the boxes are alive try: self.get(box['localip'], "/d3dapi/network/alive") except: # Run this exception for the boxes that aren't alive (anymore) if box['localip'] in self._printer_connections: self._printer_connections[box['localip']]._is_connected = False self._printer_connections[box['localip']].close() del self._printer_connections[box['localip']] self.getOutputDeviceManager().removeOutputDevice(box['localip']) else: pass else: # Boxes that are alive will be formed together into the base_list base_list[box['localip']] = box['wifiboxid'] return base_list # Takes Domain and Path and returns decoded JSON response back def get(self, domain, path): try: #print('get: ', domain, path) connect = http.client.HTTPConnection(domain) connect.request("GET", path) response = connect.getresponse() #print(' response: ', response.status, response.reason) jsonresponse = response.read() #print(' ', jsonresponse) return json.loads(jsonresponse.decode()) except Exception as e: pass return False _instance = None
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 = PrinterConnection.PrinterConnection(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): if self._printer_connections[serial_port].isConnected(): self.getOutputDeviceManager().addOutputDevice( self._printer_connections[serial_port]) else: self.getOutputDeviceManager().removeOutputDevice(serial_port) self.printerConnectionStateChanged.emit() @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].isConnected(): 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
class USBPrinterOutputDeviceManager(QObject, OutputDevicePlugin, Extension): def __init__(self, parent=None): super().__init__(parent=parent) self._serial_port_list = [] self._usb_output_devices = {} self._usb_output_devices_model = None self._update_thread = threading.Thread(target=self._updateThread) self._update_thread.setDaemon(True) self._check_updates = True self._firmware_view = None Application.getInstance().applicationShuttingDown.connect(self.stop) self.addUSBOutputDeviceSignal.connect( self.addOutputDevice ) #Because the model needs to be created in the same thread as the QMLEngine, we use a signal. addUSBOutputDeviceSignal = Signal() connectionStateChanged = pyqtSignal() progressChanged = pyqtSignal() firmwareUpdateChange = pyqtSignal() @pyqtProperty(float, notify=progressChanged) def progress(self): progress = 0 for printer_name, device in self._usb_output_devices.items( ): # TODO: @UnusedVariable "printer_name" progress += device.progress return progress / len(self._usb_output_devices) @pyqtProperty(int, notify=progressChanged) def errorCode(self): for printer_name, device in self._usb_output_devices.items( ): # TODO: @UnusedVariable "printer_name" if device._error_code: return device._error_code return 0 ## Return True if all printers finished firmware update @pyqtProperty(float, notify=firmwareUpdateChange) def firmwareUpdateCompleteStatus(self): complete = True for printer_name, device in self._usb_output_devices.items( ): # TODO: @UnusedVariable "printer_name" if not device.firmwareUpdateFinished: complete = False return complete def start(self): self._check_updates = True self._update_thread.start() def stop(self): self._check_updates = False 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 = os.path.join( PluginRegistry.getInstance().getPluginPath("USBPrinting"), "FirmwareUpdateWindow.qml") self._firmware_view = Application.getInstance().createQmlComponent( path, {"manager": self}) self._firmware_view.show() @pyqtSlot(str, bool) def updateAllFirmware(self, file_name, update_eeprom): if file_name.startswith("file://"): file_name = QUrl(file_name).toLocalFile( ) # File dialogs prepend the path with file://, which we don't need / want if not self._usb_output_devices: Message(i18n_catalog.i18nc( "@info", "Unable to update firmware because there are no printers connected." ), title=i18n_catalog.i18nc("@info:title", "Warning")).show() return for printer_connection in self._usb_output_devices: self._usb_output_devices[printer_connection].resetFirmwareUpdate() self.spawnFirmwareInterface("") for printer_connection in self._usb_output_devices: try: self._usb_output_devices[printer_connection].updateFirmware( file_name, update_eeprom) except FileNotFoundError: # Should only happen in dev environments where the resources/firmware folder is absent. self._usb_output_devices[printer_connection].setProgress( 100, 100) Logger.log("w", "No firmware found for printer %s called '%s'", printer_connection, file_name) Message(i18n_catalog.i18nc( "@info", "Could not find firmware required for the printer at %s.") % printer_connection, title=i18n_catalog.i18nc("@info:title", "Printer Firmware")).show() self._firmware_view.close() continue @pyqtSlot(str, str, result=bool) def updateFirmwareBySerial(self, serial_port, file_name): if serial_port in self._usb_output_devices: self.spawnFirmwareInterface( self._usb_output_devices[serial_port].getSerialPort()) try: self._usb_output_devices[serial_port].updateFirmware(file_name) except FileNotFoundError: self._firmware_view.close() Logger.log( "e", "Could not find firmware required for this machine called '%s'", file_name) 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 USBPrinterOutputDeviceManager._instance is None: USBPrinterOutputDeviceManager._instance = cls() return USBPrinterOutputDeviceManager._instance @pyqtSlot(result=str) def getDefaultFirmwareName(self): # Check if there is a valid global container stack global_container_stack = Application.getInstance( ).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", } lulzbot_machines = { "lulzbot_mini": "Marlin_Mini_SingleExtruder_1.1.8.21_f21ad454a.hex", "lulzbot_mini_flexy": "Marlin_Mini_Flexystruder_1.1.8.21_f21ad454a.hex", "lulzbot_mini_aerostruder": "Marlin_Mini_Aerostruder_1.1.8.21_f21ad454a.hex", "lulzbot_taz5": "Marlin_TAZ5_SingleExtruder_1.1.8.21_f21ad454a.hex", "lulzbot_taz5_flexy_v2": "Marlin_TAZ5_Flexystruder_1.1.8.21_f21ad454a.hex", "lulzbot_taz5_moarstruder": "Marlin_TAZ5_Moarstruder_1.1.8.21_f21ad454a.hex", "lulzbot_taz5_dual_v2": "Marlin_TAZ5_DualExtruderV2_1.1.8.21_f21ad454a.hex", "lulzbot_taz5_flexy_dually_v2": "Marlin_TAZ5_FlexyDually_1.1.8.21_f21ad454a.hex", "lulzbot_taz5_dual_v3": "Marlin_TAZ5_DualExtruderV3_1.1.8.21_f21ad454a.hex", "lulzbot_taz5_aerostruder": "Marlin_TAZ5_Aerostruder_1.1.8.21_f21ad454a.hex", "lulzbot_taz6": "Marlin_TAZ6_SingleExtruder_1.1.8.21_f21ad454a.hex", "lulzbot_taz6_flexy_v2": "Marlin_TAZ6_Flexystruder_1.1.8.21_f21ad454a.hex", "lulzbot_taz6_moarstruder": "Marlin_TAZ6_Moarstruder_1.1.8.21_f21ad454a.hex", "lulzbot_taz6_dual_v2": "Marlin_TAZ6_DualExtruderV2_1.1.8.21_f21ad454a.hex", "lulzbot_taz6_flexy_dually_v2": "Marlin_TAZ6_FlexyDually_1.1.8.21_f21ad454a.hex", "lulzbot_taz6_dual_v3": "Marlin_TAZ6_DualExtruderV3_1.1.8.21_f21ad454a.hex", "lulzbot_taz6_aerostruder": "Marlin_TAZ6_Aerostruder_1.1.8.21_f21ad454a.hex", "lulzbot_hibiscus": "Marlin_Mini2_AerostruderV2_1.1.8.21_f21ad454a.hex", } lulzbot_lcd_machines = { "lulzbot_mini": "Marlin_MiniLCD_SingleExtruder_1.1.8.21_f21ad454a.hex", "lulzbot_mini_flexy": "Marlin_MiniLCD_Flexystruder_1.1.8.21_f21ad454a.hex", "lulzbot_mini_aerostruder": "Marlin_MiniLCD_Aerostruder_1.1.8.21_f21ad454a.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 elif machine_id in lulzbot_machines.keys(): machine_has_lcd = global_container_stack.getProperty( "machine_has_lcd", "value") if machine_id in lulzbot_lcd_machines.keys() and machine_has_lcd: Logger.log("d", "Found firmware with LCD for machine %s.", machine_id) hex_file = lulzbot_lcd_machines[machine_id] else: Logger.log("d", "Found firmware for machine %s.", machine_id) hex_file = lulzbot_machines[machine_id] 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: pass 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): if len(self._serial_port_list) == 0 and len(serial_ports) > 0: # Hack to ensure its created in main thread self.addUSBOutputDeviceSignal.emit( USBPrinterOutputDevice.SERIAL_AUTODETECT_PORT) elif len(serial_ports) == 0: for port, device in self._usb_output_devices.items(): device.close() self._usb_output_devices = {} self.getOutputDeviceManager().removeOutputDevice( USBPrinterOutputDevice.SERIAL_AUTODETECT_PORT) self._serial_port_list = list(serial_ports) ## Because the model needs to be created in the same thread as the QMLEngine, we use a signal. def addOutputDevice(self, serial_port): device = USBPrinterOutputDevice(serial_port) device.connectionStateChanged.connect(self._onConnectionStateChanged) #device.connect() device.progressChanged.connect(self.progressChanged) device.firmwareUpdateChange.connect(self.firmwareUpdateChange) self._usb_output_devices[serial_port] = device self.getOutputDeviceManager().addOutputDevice(device) ## If one of the states of the connected devices change, we might need to add / remove them from the global list. def _onConnectionStateChanged(self, serial_port): try: self.connectionStateChanged.emit() except KeyError: Logger.log( "w", "Connection state of %s changed, but it was not found in the list" ) @pyqtProperty(QObject, notify=connectionStateChanged) def connectedPrinterList(self): self._usb_output_devices_model = ListModel() self._usb_output_devices_model.addRoleName(Qt.UserRole + 1, "name") self._usb_output_devices_model.addRoleName(Qt.UserRole + 2, "printer") for connection in self._usb_output_devices: if self._usb_output_devices[ connection].connectionState == ConnectionState.connected: self._usb_output_devices_model.appendItem({ "name": connection, "printer": self._usb_output_devices[connection] }) return self._usb_output_devices_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 = [] for port in serial.tools.list_ports.comports(): if not isinstance(port, tuple): port = (port.device, port.description, port.hwid) if only_list_usb and not port[2].startswith("USB"): continue base_list += [port[0]] return list(base_list) @pyqtProperty("QVariantList", constant=True) def portList(self): return self.getSerialPortList() @pyqtSlot(str, result=bool) def sendCommandToCurrentPrinter(self, command): try: printer = Application.getInstance().getMachineManager( ).printerOutputDevices[0] except: return False if type(printer) != USBPrinterOutputDevice: return False printer.sendCommand(command) return True @pyqtSlot(result=bool) def connectToCurrentPrinter(self): try: printer = Application.getInstance().getMachineManager( ).printerOutputDevices[0] except: return False if type(printer) != USBPrinterOutputDevice: return False printer.connect() return True _instance = None # type: "USBPrinterOutputDeviceManager"
class USBPrinterOutputDeviceManager(QObject, OutputDevicePlugin, Extension): def __init__(self, parent = None): super().__init__(parent = parent) self._serial_port_list = [] self._usb_output_devices = {} self._usb_output_devices_model = None self._update_thread = threading.Thread(target = self._updateThread) self._update_thread.setDaemon(True) self._check_updates = True self._firmware_view = None Application.getInstance().applicationShuttingDown.connect(self.stop) self.addUSBOutputDeviceSignal.connect(self.addOutputDevice) #Because the model needs to be created in the same thread as the QMLEngine, we use a signal. addUSBOutputDeviceSignal = Signal() connectionStateChanged = pyqtSignal() progressChanged = pyqtSignal() firmwareUpdateChange = pyqtSignal() @pyqtProperty(float, notify = progressChanged) def progress(self): progress = 0 for printer_name, device in self._usb_output_devices.items(): # TODO: @UnusedVariable "printer_name" progress += device.progress return progress / len(self._usb_output_devices) @pyqtProperty(int, notify = progressChanged) def errorCode(self): for printer_name, device in self._usb_output_devices.items(): # TODO: @UnusedVariable "printer_name" if device._error_code: return device._error_code return 0 ## Return True if all printers finished firmware update @pyqtProperty(float, notify = firmwareUpdateChange) def firmwareUpdateCompleteStatus(self): complete = True for printer_name, device in self._usb_output_devices.items(): # TODO: @UnusedVariable "printer_name" if not device.firmwareUpdateFinished: complete = False return complete def start(self): self._check_updates = True self._update_thread.start() def stop(self): self._check_updates = False 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 = os.path.join(PluginRegistry.getInstance().getPluginPath("USBPrinting"), "FirmwareUpdateWindow.qml") self._firmware_view = Application.getInstance().createQmlComponent(path, {"manager": self}) self._firmware_view.show() @pyqtSlot(str) def updateAllFirmware(self, file_name): if file_name.startswith("file://"): file_name = QUrl(file_name).toLocalFile() # File dialogs prepend the path with file://, which we don't need / want if not self._usb_output_devices: Message(i18n_catalog.i18nc("@info", "Unable to update firmware because there are no printers connected."), title = i18n_catalog.i18nc("@info:title", "Warning")).show() return for printer_connection in self._usb_output_devices: self._usb_output_devices[printer_connection].resetFirmwareUpdate() self.spawnFirmwareInterface("") for printer_connection in self._usb_output_devices: try: self._usb_output_devices[printer_connection].updateFirmware(file_name) except FileNotFoundError: # Should only happen in dev environments where the resources/firmware folder is absent. self._usb_output_devices[printer_connection].setProgress(100, 100) Logger.log("w", "No firmware found for printer %s called '%s'", printer_connection, file_name) Message(i18n_catalog.i18nc("@info", "Could not find firmware required for the printer at %s.") % printer_connection, title = i18n_catalog.i18nc("@info:title", "Printer Firmware")).show() self._firmware_view.close() continue @pyqtSlot(str, str, result = bool) def updateFirmwareBySerial(self, serial_port, file_name): if serial_port in self._usb_output_devices: self.spawnFirmwareInterface(self._usb_output_devices[serial_port].getSerialPort()) try: self._usb_output_devices[serial_port].updateFirmware(file_name) except FileNotFoundError: self._firmware_view.close() Logger.log("e", "Could not find firmware required for this machine called '%s'", file_name) 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 USBPrinterOutputDeviceManager._instance is None: USBPrinterOutputDeviceManager._instance = cls() return USBPrinterOutputDeviceManager._instance @pyqtSlot(result = str) def getDefaultFirmwareName(self): # Check if there is a valid global container stack global_container_stack = Application.getInstance().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: return Resources.getPath(CuraApplication.ResourceTypes.Firmware, hex_file.format(baudrate=baudrate)) 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 for serial_port in list(serial_ports): if serial_port not in self._serial_port_list: self.addUSBOutputDeviceSignal.emit(serial_port) # Hack to ensure its created in main thread continue self._serial_port_list = list(serial_ports) devices_to_remove = [] for port, device in self._usb_output_devices.items(): if port not in self._serial_port_list: device.close() devices_to_remove.append(port) for port in devices_to_remove: del self._usb_output_devices[port] ## Because the model needs to be created in the same thread as the QMLEngine, we use a signal. def addOutputDevice(self, serial_port): device = USBPrinterOutputDevice.USBPrinterOutputDevice(serial_port) device.connectionStateChanged.connect(self._onConnectionStateChanged) device.connect() device.progressChanged.connect(self.progressChanged) device.firmwareUpdateChange.connect(self.firmwareUpdateChange) self._usb_output_devices[serial_port] = device ## If one of the states of the connected devices change, we might need to add / remove them from the global list. def _onConnectionStateChanged(self, serial_port): success = True try: if self._usb_output_devices[serial_port].connectionState == ConnectionState.connected: self.getOutputDeviceManager().addOutputDevice(self._usb_output_devices[serial_port]) else: success = success and self.getOutputDeviceManager().removeOutputDevice(serial_port) if success: self.connectionStateChanged.emit() except KeyError: Logger.log("w", "Connection state of %s changed, but it was not found in the list") @pyqtProperty(QObject , notify = connectionStateChanged) def connectedPrinterList(self): self._usb_output_devices_model = ListModel() self._usb_output_devices_model.addRoleName(Qt.UserRole + 1, "name") self._usb_output_devices_model.addRoleName(Qt.UserRole + 2, "printer") for connection in self._usb_output_devices: if self._usb_output_devices[connection].connectionState == ConnectionState.connected: self._usb_output_devices_model.appendItem({"name": connection, "printer": self._usb_output_devices[connection]}) return self._usb_output_devices_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 = [] for port in serial.tools.list_ports.comports(): if not isinstance(port, tuple): port = (port.device, port.description, port.hwid) if only_list_usb and not port[2].startswith("USB"): continue base_list += [port[0]] return list(base_list) _instance = None # type: "USBPrinterOutputDeviceManager"
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 name, connection in self._printer_connections.items(): 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() baudrate = 250000 if sys.platform.startswith("linux"): baudrate = 115200 if machine_type == "ultimaker_original": firmware_name = "MarlinUltimaker" if machine_instance.getMachineSettingValue("machine_heated_bed"): #Has heated bed upgrade kit? firmware_name += "-HBK" firmware_name += "-%d" % (baudrate) return firmware_name + ".hex" elif machine_type == "ultimaker_original_plus": firmware_name = "MarlinUltimaker-UMOP-%d" % (baudrate) return firmware_name + ".hex" elif machine_type == "bq_witbox": return "MarlinWitbox.hex" elif machine_type == "ultimaker2_go": return "MarlinUltimaker2go.hex" elif machine_type == "ultimaker2_extended": return "MarlinUltimaker2extended.hex" elif machine_type == "ultimaker2": return "MarlinUltimaker2.hex" elif machine_type == "ultimaker2plus": return "MarlinUltimaker2plus.hex" elif machine_type == "ultimaker2_extended_plus": return "MarlinUltimaker2extended-plus.hex" else: Logger.log("e", "I don't know of any firmware for machine %s.", machine_type) raise FileNotFoundError() ##TODO: Add check for multiple extruders 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 = PrinterConnection.PrinterConnection(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): if self._printer_connections[serial_port].isConnected(): self.getOutputDeviceManager().addOutputDevice(self._printer_connections[serial_port]) else: self.getOutputDeviceManager().removeOutputDevice(serial_port) self.printerConnectionStateChanged.emit() @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].isConnected(): 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
class CuraSnapmakerSenderPlugin(Extension, OutputDevicePlugin, QObject): autodiscoverychanged = pyqtSignal() machineschanged = pyqtSignal() def __init__(self, parent=None) -> None: #Logger.log("d","Initializing CuraSnapmakerSenderPlugin") Extension.__init__(self) QObject.__init__(self) self.setMenuName( i18n_catalog.i18nc("@item:inmenu", "CuraSnapmakerSender")) self.addMenuItem(i18n_catalog.i18nc("@item:inmenu", "Settings"), self.showSettings) self._view = None self.setPluginId("CuraSnapmakerSender") Application.getInstance().mainWindowChanged.connect(self.afterInit) Application.getInstance().applicationShuttingDown.connect(self.stop) self._settingsWindow = None self.settings = dict() self.settings["AutoDiscover"] = True self.settings["machines"] = list() self._active_added_Printers = list() self._active_discovered_Printers = list() self._tokenregistry = dict() self._stop_discovery_running = threading.Event() self._stop_discovery_running.clear() self._discoveryThread = None self._stop_discovery_event = threading.Event() @pyqtSlot() def autodiscoverchanged_exec(self): #Logger.log("d","autodiscoverchanged_exec" + str(self.settings["AutoDiscover"]) + " " + str(self._discoveryThread.is_alive())) if (self.settings["AutoDiscover"] and not self._discoveryThread.is_alive()): #Logger.log("d","Auto-Discovery Enabled") self._discoveryThread = threading.Thread( target=self.timedDiscovering) self._discoveryThread.start() elif (not self.settings["AutoDiscover"] and self._discoveryThread.is_alive()): #Logger.log("d","Auto-Discovery Disabled") self._stop_discovery_event.set() @pyqtProperty(bool) def autodiscover(self): #Logger.log("d","autodicover read") return self.settings["AutoDiscover"] @autodiscover.setter def autodiscover(self, autodiscover): #Logger.log("d","autodicover write") self.settings["AutoDiscover"] = autodiscover self.autodiscoverychanged.emit() #self._autodiscover = autodiscover @pyqtProperty(ListModel) def machines(self) -> ListModel: return self._manualprinters @machines.setter def machines(self, machines): self._manualprinters = machines self.machineschanged.emit() #Logger.log("d","machines set : "+ str(machines)) def stop(self): self._stop_discovery_event.set() #Logger.log("d","Stopping everything from CuraSnapmakerSender") for printer_remove in self._active_added_Printers: self.removePrinter(printer_remove) for printer_remove in self._active_discovered_Printers: self.removePrinter(printer_remove) self.saveSettings() self.SaveTokenRegistry() def afterInit(self): #Logger.log("d","Log Something") self.loadTokenRegistry() self.loadSettings() self.autodiscoverychanged.connect(self.autodiscoverchanged_exec) if (self.settings["AutoDiscover"]): #Logger.log("d","Auto-Discovery Enabled") self._discoveryThread = threading.Thread( target=self.timedDiscovering) self._discoveryThread.start() self._manualprinters = ListModel() self._manualprinters.addRoleName(Qt.UserRole + 1, "name") self._manualprinters.addRoleName(Qt.UserRole + 2, "address") for x in self.settings["machines"]: printer = x self._manualprinters.appendItem(printer) #Logger.log("d","Getting Item result : "+str(self._manualprinters.getItem(0))) self.managePrinters() def loadTokenRegistry(self): path = PluginRegistry.getInstance().getPluginPath(self.getPluginId()) path = os.path.join(path, 'tokens.cfg') if os.path.exists(path): self._tokenregistry = json.load(open(path, 'r')) else: with open(path, 'w') as file: json.dump(self._tokenregistry, file) Logger.debug("TokenRegistryLoaded") def SaveTokenRegistry(self): path = PluginRegistry.getInstance().getPluginPath(self.getPluginId()) path = os.path.join(path, 'tokens.cfg') with open(path, 'w') as file: json.dump(self._tokenregistry, file) Logger.debug("TokensSaved") def loadSettings(self): path = PluginRegistry.getInstance().getPluginPath(self.getPluginId()) path = os.path.join(path, 'settings.cfg') if os.path.exists(path): self.settings = json.load(open(path, 'r')) else: with open(path, 'w') as file: json.dump(self.settings, file) Logger.debug("SettingsLoaded") @pyqtSlot() def saveSettings(self): path = PluginRegistry.getInstance().getPluginPath(self.getPluginId()) arr = list() for x in self._manualprinters.items: arr.append(x) #Logger.log("d",arr) self.settings["machines"] = arr path = os.path.join(path, 'settings.cfg') with open(path, 'w') as file: json.dump(self.settings, file) # #Logger.log("d","SettingsSaved") def timedDiscovering(self): #Logger.log("d","Discovery thread started") while not self._stop_discovery_event.is_set(): if not self.settings["AutoDiscover"]: #Logger.log("d","Discovery thread stopped") break self.discoverAndManagePrinters() self._stop_discovery_event.wait(5) self._stop_discovery_event.clear() #Logger.log("d","Discovery thread stopped") def discoverAndManagePrinters(self): old_printers = [x for x in self._active_discovered_Printers] printers_dict = discover_Snapmaker() printers = [x for x in printers_dict] for printer in printers: try: in_list = False for old_printer in old_printers: if old_printer == printer: #Logger.log("d","Already in list " + str(printer)) old_printers.remove(old_printer) in_list = True if not in_list: raise ValueError except ValueError: if not self.addPrinter(printer): printers.remove(printer) for printer_remove in old_printers: self.removePrinter(printer_remove) self._active_discovered_Printers = [x for x in printers] @pyqtSlot() def managePrinters(self): #Logger.log("d","Managing manually added printers") old_printers = [x for x in self._active_added_Printers] printers = self._manualprinters.items for printer in printers: try: in_list = False for old_printer in old_printers: if old_printer == printer: #Logger.log("d","Already in list " + str(printer)) old_printers.remove(old_printer) in_list = True if not in_list: raise ValueError except ValueError: if not self.addPrinter( printer ): #manually added printers take priority, so throw the already added printer from discovery out self.removePrinter(printer) in_list = False for old_printer in self._active_discovered_Printers: if old_printer["address"] == printer["address"]: #Logger.log("d","Already in list " + str(printer)) self._active_discovered_Printers.remove( old_printer) in_list = True if not self.addPrinter( printer): #and add the manual version #raise Exception("Problem with manually added printer") self._manualprinters.removeItem( self._manualprinters.find('address', printer['address'])) #Logger.log("d","Added manually " + str(printer)) for printer_remove in old_printers: #Logger.log("d","Removed " + str(printer_remove)) self.removePrinter(printer_remove) self._active_added_Printers = [x for x in printers] def addPrinter(self, printer): #Logger.log("d","Adding "+printer['name']+printer['address']+ " OutputDevice") token = '' if printer['address'] in self._tokenregistry: token = self._tokenregistry[printer['address']] if self.getOutputDeviceManager().getOutputDevice(printer['address']): return False #already added else: self.getOutputDeviceManager().addOutputDevice( CuraSnapmakerSenderOutputDevice(printer['address'], printer['name'], token=token)) return True def removePrinter(self, printer_remove: Dict): printer = self.getOutputDeviceManager().getOutputDevice( printer_remove['address']) # STore the token in the tokenregistry, maybe we can reuse it try: self._tokenregistry[printer_remove['address']] = printer._token except AttributeError: pass printer.tearDown() self.getOutputDeviceManager().removeOutputDevice(printer.getId()) #Logger.log("d","Removing "+printer_remove['name']+printer_remove['address']+ " OutputDevice") def showSettings(self): if not self._settingsWindow: self._settingsWindow = self._createSettingsDialogue() self._settingsWindow.show() def _createSettingsDialogue(self) -> QQuickWindow: qml_file_path = os.path.join( PluginRegistry.getInstance().getPluginPath(self.getPluginId()), "CuraSnapmakerSenderSettings.qml") component = Application.getInstance().createQmlComponent( qml_file_path, {"manager": self}) return component @pyqtSlot() def _appendEmptyPrinter(self): self.machines.appendItem({ 'name': 'MySnapmaker' + str(self.machines.count + 1), 'address': '192.168.0.' + str(self.machines.count + 1) }) @pyqtSlot(int) def _removePrinterfromList(self, index: int): self.machines.removeItem(index)
def createOptionsModel(self, options): model = ListModel() model.addRoleName(self.NameRole, "text") for option in options: model.appendItem({"text": str(option)}) return model
class TestListModel(TestCase): list_model = None # type: ListModel test_data = [{"name": "yay", "data": 12}, {"name": "omg", "data": 13}, {"name":"zomg", "data": 14}] NameRole = Qt.UserRole + 1 DataRole = Qt.UserRole + 2 def setUp(self): self.list_model = ListModel() self.list_model.addRoleName(self.NameRole, "name") self.list_model.addRoleName(self.DataRole, "data") self.list_model.setItems(deepcopy(self.test_data)) def test_getItem(self): assert self.list_model.getItem(0) == {"name": "yay", "data": 12} assert self.list_model.getItem(9001) == {} def test_items(self): assert self.list_model.items == self.test_data def test_insertItem(self): self.list_model.insertItem(0, {"name": "zomg!", "data": "yay"}) assert self.list_model.getItem(0) == {"name": "zomg!", "data": "yay"} # Check if the previously first item is now the second one. assert self.list_model.getItem(1) == {"name": "yay", "data": 12} def test_removeItem(self): self.list_model.removeItem(1) print(self.list_model._items) assert self.list_model.getItem(1) == {"name":"zomg", "data": 14} def test_clear(self): assert self.list_model.count == 3 self.list_model.clear() assert self.list_model.count == 0 def test_appendItem(self): self.list_model.appendItem({"name":"!", "data": 9001}) assert self.list_model.count == 4 assert self.list_model.getItem(3) == {"name":"!", "data": 9001} def test_setProperty(self): self.list_model.setProperty(0, "name", "new_data") assert self.list_model.getItem(0)["name"] == "new_data" def test_find(self): assert self.list_model.find("name", "omg") == 1 assert self.list_model.find("data", 13) == 1 assert self.list_model.find("name", "zomg") == 2 assert self.list_model.find("name", "UNKNOWN") == -1 def test_setItems(self): self.list_model.setItems([{"name": "zomg!", "data": "yay"}]) assert self.list_model.items == [{"name": "zomg!", "data": "yay"}] def test_sort(self): self.list_model.sort(lambda i: -i["data"]) assert self.list_model.getItem(0) == {"name":"zomg", "data": 14} assert self.list_model.getItem(2) == {"name": "yay", "data": 12}
class USBPrinterOutputDeviceManager(QObject, OutputDevicePlugin, Extension): def __init__(self, parent=None): super().__init__(parent=parent) self._serial_port_list = [] self._usb_output_devices = {} self._usb_output_devices_model = None self._update_thread = threading.Thread(target=self._updateThread) self._update_thread.setDaemon(True) self._check_updates = True self._firmware_view = None Application.getInstance().applicationShuttingDown.connect(self.stop) self.addUSBOutputDeviceSignal.connect( self.addOutputDevice ) #Because the model needs to be created in the same thread as the QMLEngine, we use a signal. addUSBOutputDeviceSignal = Signal() connectionStateChanged = pyqtSignal() progressChanged = pyqtSignal() firmwareUpdateChange = pyqtSignal() @pyqtProperty(float, notify=progressChanged) def progress(self): progress = 0 for printer_name, device in self._usb_output_devices.items( ): # TODO: @UnusedVariable "printer_name" progress += device.progress return progress / len(self._usb_output_devices) @pyqtProperty(int, notify=progressChanged) def errorCode(self): for printer_name, device in self._usb_output_devices.items( ): # TODO: @UnusedVariable "printer_name" if device._error_code: return device._error_code return 0 ## Return True if all printers finished firmware update @pyqtProperty(float, notify=firmwareUpdateChange) def firmwareUpdateCompleteStatus(self): complete = True for printer_name, device in self._usb_output_devices.items( ): # TODO: @UnusedVariable "printer_name" if not device.firmwareUpdateFinished: complete = False return complete def start(self): self._check_updates = True self._update_thread.start() def stop(self): self._check_updates = False 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(str) def updateAllFirmware(self, file_name): if file_name.startswith("file://"): file_name = QUrl(file_name).toLocalFile( ) # File dialogs prepend the path with file://, which we don't need / want if not self._usb_output_devices: Message( i18n_catalog.i18nc( "@info", "Unable to update firmware because there are no printers connected." )).show() return for printer_connection in self._usb_output_devices: self._usb_output_devices[printer_connection].resetFirmwareUpdate() self.spawnFirmwareInterface("") for printer_connection in self._usb_output_devices: try: self._usb_output_devices[printer_connection].updateFirmware( file_name) except FileNotFoundError: # Should only happen in dev environments where the resources/firmware folder is absent. self._usb_output_devices[printer_connection].setProgress( 100, 100) Logger.log("w", "No firmware found for printer %s called '%s'", printer_connection, file_name) Message( i18n_catalog.i18nc( "@info", "Could not find firmware required for the printer at %s." ) % printer_connection).show() self._firmware_view.close() continue @pyqtSlot(str, str, result=bool) def updateFirmwareBySerial(self, serial_port, file_name): if serial_port in self._usb_output_devices: self.spawnFirmwareInterface( self._usb_output_devices[serial_port].getSerialPort()) try: self._usb_output_devices[serial_port].updateFirmware(file_name) except FileNotFoundError: self._firmware_view.close() Logger.log( "e", "Could not find firmware required for this machine called '%s'", file_name) 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 USBPrinterOutputDeviceManager._instance is None: USBPrinterOutputDeviceManager._instance = cls() return USBPrinterOutputDeviceManager._instance @pyqtSlot(result=str) def getDefaultFirmwareName(self): # Check if there is a valid global container stack global_container_stack = Application.getInstance( ).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: return Resources.getPath(CuraApplication.ResourceTypes.Firmware, hex_file.format(baudrate=baudrate)) 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 for serial_port in list(serial_ports): if serial_port not in self._serial_port_list: self.addUSBOutputDeviceSignal.emit( serial_port) # Hack to ensure its created in main thread continue self._serial_port_list = list(serial_ports) devices_to_remove = [] for port, device in self._usb_output_devices.items(): if port not in self._serial_port_list: device.close() devices_to_remove.append(port) for port in devices_to_remove: del self._usb_output_devices[port] ## Because the model needs to be created in the same thread as the QMLEngine, we use a signal. def addOutputDevice(self, serial_port): device = USBPrinterOutputDevice.USBPrinterOutputDevice(serial_port) device.connectionStateChanged.connect(self._onConnectionStateChanged) device.connect() device.progressChanged.connect(self.progressChanged) device.firmwareUpdateChange.connect(self.firmwareUpdateChange) self._usb_output_devices[serial_port] = device ## If one of the states of the connected devices change, we might need to add / remove them from the global list. def _onConnectionStateChanged(self, serial_port): try: if self._usb_output_devices[ serial_port].connectionState == ConnectionState.connected: self.getOutputDeviceManager().addOutputDevice( self._usb_output_devices[serial_port]) else: self.getOutputDeviceManager().removeOutputDevice(serial_port) self.connectionStateChanged.emit() except KeyError: pass # no output device by this device_id found in connection list. @pyqtProperty(QObject, notify=connectionStateChanged) def connectedPrinterList(self): self._usb_output_devices_model = ListModel() self._usb_output_devices_model.addRoleName(Qt.UserRole + 1, "name") self._usb_output_devices_model.addRoleName(Qt.UserRole + 2, "printer") for connection in self._usb_output_devices: if self._usb_output_devices[ connection].connectionState == ConnectionState.connected: self._usb_output_devices_model.appendItem({ "name": connection, "printer": self._usb_output_devices[connection] }) return self._usb_output_devices_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 # type: ignore @UnresolvedImport 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*") + glob.glob( "/dev/tty.wchusb*") + glob.glob("/dev/cu.wchusb*") 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/tty.wchusb*") + glob.glob( "/dev/cu.wchusb*") + glob.glob( "/dev/rfcomm*") + glob.glob("/dev/serial/by-id/*") return list(base_list) _instance = None # type: "USBPrinterOutputDeviceManager"
class TestListModel(TestCase): list_model = None # type: ListModel test_data = [{ "name": "yay", "data": 12 }, { "name": "omg", "data": 13 }, { "name": "zomg", "data": 14 }] NameRole = Qt.UserRole + 1 DataRole = Qt.UserRole + 2 def setUp(self): self.list_model = ListModel() self.list_model.addRoleName(self.NameRole, "name") self.list_model.addRoleName(self.DataRole, "data") self.list_model.setItems(deepcopy(self.test_data)) def test_getItem(self): assert self.list_model.getItem(0) == {"name": "yay", "data": 12} assert self.list_model.getItem(9001) == {} def test_items(self): assert self.list_model.items == self.test_data def test_insertItem(self): self.list_model.insertItem(0, {"name": "zomg!", "data": "yay"}) assert self.list_model.getItem(0) == {"name": "zomg!", "data": "yay"} # Check if the previously first item is now the second one. assert self.list_model.getItem(1) == {"name": "yay", "data": 12} def test_removeItem(self): self.list_model.removeItem(1) assert self.list_model.getItem(1) == {"name": "zomg", "data": 14} def test_clear(self): assert self.list_model.count == 3 self.list_model.clear() assert self.list_model.count == 0 def test_appendItem(self): self.list_model.appendItem({"name": "!", "data": 9001}) assert self.list_model.count == 4 assert self.list_model.getItem(3) == {"name": "!", "data": 9001} def test_setProperty(self): self.list_model.setProperty(0, "name", "new_data") assert self.list_model.getItem(0)["name"] == "new_data" def test_find(self): assert self.list_model.find("name", "omg") == 1 assert self.list_model.find("data", 13) == 1 assert self.list_model.find("name", "zomg") == 2 assert self.list_model.find("name", "UNKNOWN") == -1 def test_setItems(self): self.list_model.setItems([{"name": "zomg!", "data": "yay"}]) assert self.list_model.items == [{"name": "zomg!", "data": "yay"}] def test_sort(self): self.list_model.sort(lambda i: -i["data"]) assert self.list_model.getItem(0) == {"name": "zomg", "data": 14} assert self.list_model.getItem(2) == {"name": "yay", "data": 12}