def pluginsModel(self): if self._plugins_model is None: self._plugins_model = ListModel() self._plugins_model.addRoleName(Qt.UserRole + 1, "name") self._plugins_model.addRoleName(Qt.UserRole + 2, "version") self._plugins_model.addRoleName(Qt.UserRole + 3, "short_description") self._plugins_model.addRoleName(Qt.UserRole + 4, "author") self._plugins_model.addRoleName(Qt.UserRole + 5, "already_installed") self._plugins_model.addRoleName(Qt.UserRole + 6, "file_location") self._plugins_model.addRoleName(Qt.UserRole + 7, "can_upgrade") else: self._plugins_model.clear() items = [] for metadata in self._plugins_metadata: items.append({ "name": metadata["label"], "version": metadata["version"], "short_description": metadata["short_description"], "author": metadata["author"], "already_installed": self._checkAlreadyInstalled(metadata["id"]), "file_location": metadata["file_location"], "can_upgrade": self._checkCanUpgrade(metadata["id"], metadata["version"]) }) self._plugins_model.setItems(items) return self._plugins_model
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
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 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
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
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)}) model.sort(lambda t: t["name"]) 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 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 _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 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
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
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))
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 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}
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 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 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)
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 PluginBrowser(QObject, Extension): def __init__(self, parent=None): super().__init__(parent) self.addMenuItem(i18n_catalog.i18n("Browse plugins"), self.browsePlugins) self._api_version = 1 self._api_url = "http://software.ultimaker.com/cura/v%s/" % self._api_version self._plugin_list_request = None self._download_plugin_request = None self._download_plugin_reply = None self._network_manager = None self._plugins_metadata = [] self._plugins_model = None self._qml_component = None self._qml_context = None self._dialog = None pluginsMetadataChanged = pyqtSignal() def browsePlugins(self): self._createNetworkManager() self.requestPluginList() if not self._dialog: self._createDialog() self._dialog.show() def requestPluginList(self): url = QUrl(self._api_url + "plugins") self._plugin_list_request = QNetworkRequest(url) self._network_manager.get(self._plugin_list_request) def _createDialog(self): Logger.log("d", "PluginBrowser") path = QUrl.fromLocalFile( os.path.join( PluginRegistry.getInstance().getPluginPath(self.getPluginId()), "PluginBrowser.qml")) self._qml_component = QQmlComponent(Application.getInstance()._engine, path) # We need access to engine (although technically we can't) self._qml_context = QQmlContext( Application.getInstance()._engine.rootContext()) self._qml_context.setContextProperty("manager", self) self._dialog = self._qml_component.create(self._qml_context) if self._dialog is None: Logger.log("e", "QQmlComponent status %s", self._qml_component.status()) Logger.log("e", "QQmlComponent errorString %s", self._qml_component.errorString()) def _onDownloadPluginProgress(self, bytes_sent, bytes_total): if bytes_total > 0: new_progress = bytes_sent / bytes_total * 100 if new_progress == 100.0: self._download_plugin_reply.downloadProgress.disconnect( self._onDownloadPluginProgress) self._temp_plugin_file = tempfile.NamedTemporaryFile( suffix=".curaplugin") self._temp_plugin_file.write( self._download_plugin_reply.readAll()) result = PluginRegistry.getInstance().installPlugin( "file://" + self._temp_plugin_file.name) self._temp_plugin_file.close( ) # Plugin was installed, delete temp file @pyqtSlot(str) def downloadAndInstallPlugin(self, url): Logger.log("i", "Attempting to download & install plugin from %s", url) url = QUrl(url) self._download_plugin_request = QNetworkRequest(url) self._download_plugin_reply = self._network_manager.get( self._download_plugin_request) self._download_plugin_reply.downloadProgress.connect( self._onDownloadPluginProgress) @pyqtProperty(QObject, notify=pluginsMetadataChanged) def pluginsModel(self): if self._plugins_model is None: self._plugins_model = ListModel() self._plugins_model.addRoleName(Qt.UserRole + 1, "name") self._plugins_model.addRoleName(Qt.UserRole + 2, "version") self._plugins_model.addRoleName(Qt.UserRole + 3, "short_description") self._plugins_model.addRoleName(Qt.UserRole + 4, "author") self._plugins_model.addRoleName(Qt.UserRole + 5, "already_installed") self._plugins_model.addRoleName(Qt.UserRole + 6, "file_location") else: self._plugins_model.clear() items = [] for metadata in self._plugins_metadata: items.append({ "name": metadata["label"], "version": metadata["version"], "short_description": metadata["short_description"], "author": metadata["author"], "already_installed": self._checkAlreadyInstalled(metadata["id"], metadata["version"]), "file_location": metadata["file_location"] }) self._plugins_model.setItems(items) return self._plugins_model def _checkAlreadyInstalled(self, id, version): plugin_registry = PluginRegistry.getInstance() metadata = plugin_registry.getMetaData(id) if metadata != {}: current_version = Version(metadata["plugin"]["version"]) new_version = Version(version) if new_version > current_version: return False return True def _onRequestFinished(self, reply): reply_url = reply.url().toString() if reply.operation() == QNetworkAccessManager.GetOperation: if reply_url == self._api_url + "plugins": try: json_data = json.loads( bytes(reply.readAll()).decode("utf-8")) self._plugins_metadata = json_data self.pluginsMetadataChanged.emit() except json.decoder.JSONDecodeError: Logger.log( "w", "Received an invalid print job state message: Not valid JSON." ) return else: # Ignore any operation that is not a get operation pass def _createNetworkManager(self): if self._network_manager: self._network_manager.finished.disconnect(self._onRequestFinished) self._network_manager = QNetworkAccessManager() self._network_manager.finished.connect(self._onRequestFinished)
class PluginBrowser(QObject, Extension): def __init__(self, parent = None): super().__init__(parent) self.addMenuItem(i18n_catalog.i18n("Browse plugins"), self.browsePlugins) self._api_version = 1 self._api_url = "http://software.ultimaker.com/cura/v%s/" % self._api_version self._plugin_list_request = None self._download_plugin_request = None self._download_plugin_reply = None self._network_manager = None self._plugins_metadata = [] self._plugins_model = None self._qml_component = None self._qml_context = None self._dialog = None self._download_progress = 0 self._is_downloading = False self._request_header = [b"User-Agent", str.encode("%s - %s" % (Application.getInstance().getApplicationName(), Application.getInstance().getVersion()))] # Installed plugins are really installed after reboot. In order to prevent the user from downloading the # same file over and over again, we keep track of the upgraded plugins. self._newly_installed_plugin_ids = [] pluginsMetadataChanged = pyqtSignal() onDownloadProgressChanged = pyqtSignal() onIsDownloadingChanged = pyqtSignal() @pyqtProperty(bool, notify = onIsDownloadingChanged) def isDownloading(self): return self._is_downloading def browsePlugins(self): self._createNetworkManager() self.requestPluginList() if not self._dialog: self._createDialog() self._dialog.show() def requestPluginList(self): url = QUrl(self._api_url + "plugins") self._plugin_list_request = QNetworkRequest(url) self._plugin_list_request.setRawHeader(*self._request_header) self._network_manager.get(self._plugin_list_request) def _createDialog(self): Logger.log("d", "PluginBrowser") path = QUrl.fromLocalFile(os.path.join(PluginRegistry.getInstance().getPluginPath(self.getPluginId()), "PluginBrowser.qml")) self._qml_component = QQmlComponent(Application.getInstance()._engine, path) # We need access to engine (although technically we can't) self._qml_context = QQmlContext(Application.getInstance()._engine.rootContext()) self._qml_context.setContextProperty("manager", self) self._dialog = self._qml_component.create(self._qml_context) if self._dialog is None: Logger.log("e", "QQmlComponent status %s", self._qml_component.status()) Logger.log("e", "QQmlComponent errorString %s", self._qml_component.errorString()) def setIsDownloading(self, is_downloading): if self._is_downloading != is_downloading: self._is_downloading = is_downloading self.onIsDownloadingChanged.emit() def _onDownloadPluginProgress(self, bytes_sent, bytes_total): if bytes_total > 0: new_progress = bytes_sent / bytes_total * 100 if new_progress > self._download_progress: self._download_progress = new_progress self.onDownloadProgressChanged.emit() self._download_progress = new_progress if new_progress == 100.0: self.setIsDownloading(False) self._download_plugin_reply.downloadProgress.disconnect(self._onDownloadPluginProgress) self._temp_plugin_file = tempfile.NamedTemporaryFile(suffix = ".curaplugin") self._temp_plugin_file.write(self._download_plugin_reply.readAll()) result = PluginRegistry.getInstance().installPlugin("file://" + self._temp_plugin_file.name) self._newly_installed_plugin_ids.append(result["id"]) self.pluginsMetadataChanged.emit() Application.getInstance().messageBox(i18n_catalog.i18nc("@window:title", "Plugin browser"), result["message"]) self._temp_plugin_file.close() # Plugin was installed, delete temp file @pyqtProperty(int, notify = onDownloadProgressChanged) def downloadProgress(self): return self._download_progress @pyqtSlot(str) def downloadAndInstallPlugin(self, url): Logger.log("i", "Attempting to download & install plugin from %s", url) url = QUrl(url) self._download_plugin_request = QNetworkRequest(url) self._download_plugin_request.setRawHeader(*self._request_header) self._download_plugin_reply = self._network_manager.get(self._download_plugin_request) self._download_progress = 0 self.setIsDownloading(True) self.onDownloadProgressChanged.emit() self._download_plugin_reply.downloadProgress.connect(self._onDownloadPluginProgress) @pyqtProperty(QObject, notify=pluginsMetadataChanged) def pluginsModel(self): if self._plugins_model is None: self._plugins_model = ListModel() self._plugins_model.addRoleName(Qt.UserRole + 1, "name") self._plugins_model.addRoleName(Qt.UserRole + 2, "version") self._plugins_model.addRoleName(Qt.UserRole + 3, "short_description") self._plugins_model.addRoleName(Qt.UserRole + 4, "author") self._plugins_model.addRoleName(Qt.UserRole + 5, "already_installed") self._plugins_model.addRoleName(Qt.UserRole + 6, "file_location") self._plugins_model.addRoleName(Qt.UserRole + 7, "can_upgrade") else: self._plugins_model.clear() items = [] for metadata in self._plugins_metadata: items.append({ "name": metadata["label"], "version": metadata["version"], "short_description": metadata["short_description"], "author": metadata["author"], "already_installed": self._checkAlreadyInstalled(metadata["id"]), "file_location": metadata["file_location"], "can_upgrade": self._checkCanUpgrade(metadata["id"], metadata["version"]) }) self._plugins_model.setItems(items) return self._plugins_model def _checkCanUpgrade(self, id, version): plugin_registry = PluginRegistry.getInstance() metadata = plugin_registry.getMetaData(id) if metadata != {}: if id in self._newly_installed_plugin_ids: return False # We already updated this plugin. current_version = Version(metadata["plugin"]["version"]) new_version = Version(version) if new_version > current_version: return True return False def _checkAlreadyInstalled(self, id): plugin_registry = PluginRegistry.getInstance() metadata = plugin_registry.getMetaData(id) if metadata != {}: return True else: if id in self._newly_installed_plugin_ids: return True # We already installed this plugin, but the registry just doesn't know it yet. return False def _onRequestFinished(self, reply): reply_url = reply.url().toString() if reply.operation() == QNetworkAccessManager.GetOperation: if reply_url == self._api_url + "plugins": try: json_data = json.loads(bytes(reply.readAll()).decode("utf-8")) self._plugins_metadata = json_data self.pluginsMetadataChanged.emit() except json.decoder.JSONDecodeError: Logger.log("w", "Received an invalid print job state message: Not valid JSON.") return else: # Ignore any operation that is not a get operation pass def _createNetworkManager(self): if self._network_manager: self._network_manager.finished.disconnect(self._onRequestFinished) self._network_manager = QNetworkAccessManager() self._network_manager.finished.connect(self._onRequestFinished)
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 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
def createOptionsModel(self, options): model = ListModel() model.addRoleName(self.NameRole,"text") for option in options: model.appendItem({"text": str(option)}) return model
class PluginBrowser(QObject, Extension): def __init__(self, parent=None): super().__init__(parent) self.addMenuItem(i18n_catalog.i18n("Browse plugins"), self.browsePlugins) self._api_version = 1 self._api_url = "http://software.ultimaker.com/cura/v%s/" % self._api_version self._plugin_list_request = None self._download_plugin_request = None self._download_plugin_reply = None self._network_manager = None self._plugins_metadata = [] self._plugins_model = None self._qml_component = None self._qml_context = None self._dialog = None self._download_progress = 0 self._is_downloading = False self._request_header = [ b"User-Agent", str.encode("%s - %s" % (Application.getInstance().getApplicationName(), Application.getInstance().getVersion())) ] # Installed plugins are really installed after reboot. In order to prevent the user from downloading the # same file over and over again, we keep track of the upgraded plugins. self._newly_installed_plugin_ids = [] pluginsMetadataChanged = pyqtSignal() onDownloadProgressChanged = pyqtSignal() onIsDownloadingChanged = pyqtSignal() @pyqtProperty(bool, notify=onIsDownloadingChanged) def isDownloading(self): return self._is_downloading def browsePlugins(self): self._createNetworkManager() self.requestPluginList() if not self._dialog: self._createDialog() self._dialog.show() @pyqtSlot() def requestPluginList(self): Logger.log("i", "Requesting plugin list") url = QUrl(self._api_url + "plugins") self._plugin_list_request = QNetworkRequest(url) self._plugin_list_request.setRawHeader(*self._request_header) self._network_manager.get(self._plugin_list_request) def _createDialog(self): Logger.log("d", "PluginBrowser") path = QUrl.fromLocalFile( os.path.join( PluginRegistry.getInstance().getPluginPath(self.getPluginId()), "PluginBrowser.qml")) self._qml_component = QQmlComponent(Application.getInstance()._engine, path) # We need access to engine (although technically we can't) self._qml_context = QQmlContext( Application.getInstance()._engine.rootContext()) self._qml_context.setContextProperty("manager", self) self._dialog = self._qml_component.create(self._qml_context) if self._dialog is None: Logger.log("e", "QQmlComponent status %s", self._qml_component.status()) Logger.log("e", "QQmlComponent errorString %s", self._qml_component.errorString()) def setIsDownloading(self, is_downloading): if self._is_downloading != is_downloading: self._is_downloading = is_downloading self.onIsDownloadingChanged.emit() def _onDownloadPluginProgress(self, bytes_sent, bytes_total): if bytes_total > 0: new_progress = bytes_sent / bytes_total * 100 self.setDownloadProgress(new_progress) if new_progress == 100.0: self.setIsDownloading(False) self._download_plugin_reply.downloadProgress.disconnect( self._onDownloadPluginProgress) self._temp_plugin_file = tempfile.NamedTemporaryFile( suffix=".curaplugin") self._temp_plugin_file.write( self._download_plugin_reply.readAll()) result = PluginRegistry.getInstance().installPlugin( "file://" + self._temp_plugin_file.name) self._newly_installed_plugin_ids.append(result["id"]) self.pluginsMetadataChanged.emit() Application.getInstance().messageBox( i18n_catalog.i18nc("@window:title", "Plugin browser"), result["message"]) self._temp_plugin_file.close( ) # Plugin was installed, delete temp file @pyqtProperty(int, notify=onDownloadProgressChanged) def downloadProgress(self): return self._download_progress def setDownloadProgress(self, progress): if progress != self._download_progress: self._download_progress = progress self.onDownloadProgressChanged.emit() @pyqtSlot(str) def downloadAndInstallPlugin(self, url): Logger.log("i", "Attempting to download & install plugin from %s", url) url = QUrl(url) self._download_plugin_request = QNetworkRequest(url) self._download_plugin_request.setRawHeader(*self._request_header) self._download_plugin_reply = self._network_manager.get( self._download_plugin_request) self.setDownloadProgress(0) self.setIsDownloading(True) self._download_plugin_reply.downloadProgress.connect( self._onDownloadPluginProgress) @pyqtProperty(QObject, notify=pluginsMetadataChanged) def pluginsModel(self): if self._plugins_model is None: self._plugins_model = ListModel() self._plugins_model.addRoleName(Qt.UserRole + 1, "name") self._plugins_model.addRoleName(Qt.UserRole + 2, "version") self._plugins_model.addRoleName(Qt.UserRole + 3, "short_description") self._plugins_model.addRoleName(Qt.UserRole + 4, "author") self._plugins_model.addRoleName(Qt.UserRole + 5, "already_installed") self._plugins_model.addRoleName(Qt.UserRole + 6, "file_location") self._plugins_model.addRoleName(Qt.UserRole + 7, "can_upgrade") else: self._plugins_model.clear() items = [] for metadata in self._plugins_metadata: items.append({ "name": metadata["label"], "version": metadata["version"], "short_description": metadata["short_description"], "author": metadata["author"], "already_installed": self._checkAlreadyInstalled(metadata["id"]), "file_location": metadata["file_location"], "can_upgrade": self._checkCanUpgrade(metadata["id"], metadata["version"]) }) self._plugins_model.setItems(items) return self._plugins_model def _checkCanUpgrade(self, id, version): plugin_registry = PluginRegistry.getInstance() metadata = plugin_registry.getMetaData(id) if metadata != {}: if id in self._newly_installed_plugin_ids: return False # We already updated this plugin. current_version = Version(metadata["plugin"]["version"]) new_version = Version(version) if new_version > current_version: return True return False def _checkAlreadyInstalled(self, id): plugin_registry = PluginRegistry.getInstance() metadata = plugin_registry.getMetaData(id) if metadata != {}: return True else: if id in self._newly_installed_plugin_ids: return True # We already installed this plugin, but the registry just doesn't know it yet. return False def _onRequestFinished(self, reply): reply_url = reply.url().toString() if reply.error() == QNetworkReply.TimeoutError: Logger.log("w", "Got a timeout.") # Reset everything. self.setDownloadProgress(0) self.setIsDownloading(False) if self._download_plugin_reply: self._download_plugin_reply.downloadProgress.disconnect( self._onDownloadPluginProgress) self._download_plugin_reply.abort() self._download_plugin_reply = None return elif reply.error() == QNetworkReply.HostNotFoundError: Logger.log("w", "Unable to reach server.") return if reply.operation() == QNetworkAccessManager.GetOperation: if reply_url == self._api_url + "plugins": try: json_data = json.loads( bytes(reply.readAll()).decode("utf-8")) self._plugins_metadata = json_data self.pluginsMetadataChanged.emit() except json.decoder.JSONDecodeError: Logger.log( "w", "Received an invalid print job state message: Not valid JSON." ) return else: # Ignore any operation that is not a get operation pass def _onNetworkAccesibleChanged(self, accessible): if accessible == 0: self.setDownloadProgress(0) self.setIsDownloading(False) if self._download_plugin_reply: self._download_plugin_reply.downloadProgress.disconnect( self._onDownloadPluginProgress) self._download_plugin_reply.abort() self._download_plugin_reply = None def _createNetworkManager(self): if self._network_manager: self._network_manager.finished.disconnect(self._onRequestFinished) self._network_manager.networkAccessibleChanged.disconnect( self._onNetworkAccesibleChanged) self._network_manager = QNetworkAccessManager() self._network_manager.finished.connect(self._onRequestFinished) self._network_manager.networkAccessibleChanged.connect( self._onNetworkAccesibleChanged)
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"
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
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 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"
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 PluginBrowser(QObject, Extension): def __init__(self, parent=None): super().__init__(parent) self._api_version = 3 self._api_url = "http://software.ultimaker.com/cura/v%s/" % self._api_version self._plugin_list_request = None self._download_plugin_request = None self._download_plugin_reply = None self._network_manager = None self._plugins_metadata = [] self._plugins_model = None self._dialog = None self._download_progress = 0 self._is_downloading = False self._request_header = [b"User-Agent", str.encode("%s/%s (%s %s)" % (Application.getInstance().getApplicationName(), Application.getInstance().getVersion(), platform.system(), platform.machine(), ) ) ] # Installed plugins are really installed after reboot. In order to prevent the user from downloading the # same file over and over again, we keep track of the upgraded plugins. self._newly_installed_plugin_ids = [] # variables for the license agreement dialog self._license_dialog_plugin_name = "" self._license_dialog_license_content = "" self._license_dialog_plugin_file_location = "" showLicenseDialog = pyqtSignal() @pyqtSlot(result = str) def getLicenseDialogPluginName(self): return self._license_dialog_plugin_name @pyqtSlot(result = str) def getLicenseDialogPluginFileLocation(self): return self._license_dialog_plugin_file_location @pyqtSlot(result = str) def getLicenseDialogLicenseContent(self): return self._license_dialog_license_content def openLicenseDialog(self, plugin_name, license_content, plugin_file_location): self._license_dialog_plugin_name = plugin_name self._license_dialog_license_content = license_content self._license_dialog_plugin_file_location = plugin_file_location self.showLicenseDialog.emit() pluginsMetadataChanged = pyqtSignal() onDownloadProgressChanged = pyqtSignal() onIsDownloadingChanged = pyqtSignal() @pyqtProperty(bool, notify = onIsDownloadingChanged) def isDownloading(self): return self._is_downloading @pyqtSlot() def browsePlugins(self): self._createNetworkManager() self.requestPluginList() if not self._dialog: self._dialog = self._createDialog("PluginBrowser.qml") self._dialog.show() @pyqtSlot() def requestPluginList(self): Logger.log("i", "Requesting plugin list") url = QUrl(self._api_url + "plugins") self._plugin_list_request = QNetworkRequest(url) self._plugin_list_request.setRawHeader(*self._request_header) self._network_manager.get(self._plugin_list_request) def _createDialog(self, qml_name): Logger.log("d", "Creating dialog [%s]", qml_name) path = os.path.join(PluginRegistry.getInstance().getPluginPath(self.getPluginId()), qml_name) dialog = Application.getInstance().createQmlComponent(path, {"manager": self}) return dialog def setIsDownloading(self, is_downloading): if self._is_downloading != is_downloading: self._is_downloading = is_downloading self.onIsDownloadingChanged.emit() def _onDownloadPluginProgress(self, bytes_sent, bytes_total): if bytes_total > 0: new_progress = bytes_sent / bytes_total * 100 self.setDownloadProgress(new_progress) if new_progress == 100.0: self.setIsDownloading(False) self._download_plugin_reply.downloadProgress.disconnect(self._onDownloadPluginProgress) # must not delete the temporary file on Windows self._temp_plugin_file = tempfile.NamedTemporaryFile(mode = "w+b", suffix = ".curaplugin", delete = False) location = self._temp_plugin_file.name # write first and close, otherwise on Windows, it cannot read the file self._temp_plugin_file.write(self._download_plugin_reply.readAll()) self._temp_plugin_file.close() self._checkPluginLicenseOrInstall(location) return ## Checks if the downloaded plugin ZIP file contains a license file or not. # If it does, it will show a popup dialog displaying the license to the user. The plugin will be installed if the # user accepts the license. # If there is no license file, the plugin will be directory installed. def _checkPluginLicenseOrInstall(self, file_path): with zipfile.ZipFile(file_path, "r") as zip_ref: plugin_id = None for file in zip_ref.infolist(): if file.filename.endswith("/"): plugin_id = file.filename.strip("/") break if plugin_id is None: msg = i18n_catalog.i18nc("@info:status", "Failed to get plugin ID from <filename>{0}</filename>", file_path) msg_title = i18n_catalog.i18nc("@info:tile", "Warning") self._progress_message = Message(msg, lifetime=0, dismissable=False, title = msg_title) return # find a potential license file plugin_root_dir = plugin_id + "/" license_file = None for f in zip_ref.infolist(): # skip directories (with file_size = 0) and files not in the plugin directory if f.file_size == 0 or not f.filename.startswith(plugin_root_dir): continue file_name = os.path.basename(f.filename).lower() file_base_name, file_ext = os.path.splitext(file_name) if file_base_name in ["license", "licence"]: license_file = f.filename break # show a dialog for user to read and accept/decline the license if license_file is not None: Logger.log("i", "Found license file for plugin [%s], showing the license dialog to the user", plugin_id) license_content = zip_ref.read(license_file).decode('utf-8') self.openLicenseDialog(plugin_id, license_content, file_path) return # there is no license file, directly install the plugin self.installPlugin(file_path) @pyqtSlot(str) def installPlugin(self, file_path): if not file_path.startswith("/"): location = "/" + file_path # Ensure that it starts with a /, as otherwise it doesn't work on windows. else: location = file_path result = PluginRegistry.getInstance().installPlugin("file://" + location) self._newly_installed_plugin_ids.append(result["id"]) self.pluginsMetadataChanged.emit() Application.getInstance().messageBox(i18n_catalog.i18nc("@window:title", "Plugin browser"), result["message"]) @pyqtProperty(int, notify = onDownloadProgressChanged) def downloadProgress(self): return self._download_progress def setDownloadProgress(self, progress): if progress != self._download_progress: self._download_progress = progress self.onDownloadProgressChanged.emit() @pyqtSlot(str) def downloadAndInstallPlugin(self, url): Logger.log("i", "Attempting to download & install plugin from %s", url) url = QUrl(url) self._download_plugin_request = QNetworkRequest(url) self._download_plugin_request.setRawHeader(*self._request_header) self._download_plugin_reply = self._network_manager.get(self._download_plugin_request) self.setDownloadProgress(0) self.setIsDownloading(True) self._download_plugin_reply.downloadProgress.connect(self._onDownloadPluginProgress) @pyqtSlot() def cancelDownload(self): Logger.log("i", "user cancelled the download of a plugin") self._download_plugin_reply.abort() self._download_plugin_reply.downloadProgress.disconnect(self._onDownloadPluginProgress) self._download_plugin_reply = None self._download_plugin_request = None self.setDownloadProgress(0) self.setIsDownloading(False) @pyqtProperty(QObject, notify=pluginsMetadataChanged) def pluginsModel(self): if self._plugins_model is None: self._plugins_model = ListModel() self._plugins_model.addRoleName(Qt.UserRole + 1, "name") self._plugins_model.addRoleName(Qt.UserRole + 2, "version") self._plugins_model.addRoleName(Qt.UserRole + 3, "short_description") self._plugins_model.addRoleName(Qt.UserRole + 4, "author") self._plugins_model.addRoleName(Qt.UserRole + 5, "already_installed") self._plugins_model.addRoleName(Qt.UserRole + 6, "file_location") self._plugins_model.addRoleName(Qt.UserRole + 7, "can_upgrade") else: self._plugins_model.clear() items = [] for metadata in self._plugins_metadata: items.append({ "name": metadata["label"], "version": metadata["version"], "short_description": metadata["short_description"], "author": metadata["author"], "already_installed": self._checkAlreadyInstalled(metadata["id"]), "file_location": metadata["file_location"], "can_upgrade": self._checkCanUpgrade(metadata["id"], metadata["version"]) }) self._plugins_model.setItems(items) return self._plugins_model def _checkCanUpgrade(self, id, version): plugin_registry = PluginRegistry.getInstance() metadata = plugin_registry.getMetaData(id) if metadata != {}: if id in self._newly_installed_plugin_ids: return False # We already updated this plugin. current_version = Version(metadata["plugin"]["version"]) new_version = Version(version) if new_version > current_version: return True return False def _checkAlreadyInstalled(self, id): plugin_registry = PluginRegistry.getInstance() metadata = plugin_registry.getMetaData(id) if metadata != {}: return True else: if id in self._newly_installed_plugin_ids: return True # We already installed this plugin, but the registry just doesn't know it yet. return False def _onRequestFinished(self, reply): reply_url = reply.url().toString() if reply.error() == QNetworkReply.TimeoutError: Logger.log("w", "Got a timeout.") # Reset everything. self.setDownloadProgress(0) self.setIsDownloading(False) if self._download_plugin_reply: self._download_plugin_reply.downloadProgress.disconnect(self._onDownloadPluginProgress) self._download_plugin_reply.abort() self._download_plugin_reply = None return elif reply.error() == QNetworkReply.HostNotFoundError: Logger.log("w", "Unable to reach server.") return if reply.operation() == QNetworkAccessManager.GetOperation: if reply_url == self._api_url + "plugins": try: json_data = json.loads(bytes(reply.readAll()).decode("utf-8")) self._plugins_metadata = json_data self.pluginsMetadataChanged.emit() except json.decoder.JSONDecodeError: Logger.log("w", "Received an invalid print job state message: Not valid JSON.") return else: # Ignore any operation that is not a get operation pass def _onNetworkAccesibleChanged(self, accessible): if accessible == 0: self.setDownloadProgress(0) self.setIsDownloading(False) if self._download_plugin_reply: self._download_plugin_reply.downloadProgress.disconnect(self._onDownloadPluginProgress) self._download_plugin_reply.abort() self._download_plugin_reply = None def _createNetworkManager(self): if self._network_manager: self._network_manager.finished.disconnect(self._onRequestFinished) self._network_manager.networkAccessibleChanged.disconnect(self._onNetworkAccesibleChanged) self._network_manager = QNetworkAccessManager() self._network_manager.finished.connect(self._onRequestFinished) self._network_manager.networkAccessibleChanged.connect(self._onNetworkAccesibleChanged)