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
Exemple #2
0
 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
Exemple #3
0
 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
Exemple #5
0
    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
Exemple #6
0
    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 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
Exemple #8
0
 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 _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 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 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
Exemple #13
0
 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
Exemple #15
0
    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))
Exemple #16
0
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
Exemple #17
0
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}
Exemple #18
0
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
Exemple #20
0
    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 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"
Exemple #23
0
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)
Exemple #24
0
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)
Exemple #25
0
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
Exemple #26
0
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
Exemple #28
0
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)
Exemple #29
0
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   
Exemple #31
0
 def createOptionsModel(self, options):
     model = ListModel()
     model.addRoleName(self.NameRole, "text")
     for option in options:
         model.appendItem({"text": str(option)})
     return model
Exemple #32
0
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"
Exemple #33
0
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)