Exemplo n.º 1
0
    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()
Exemplo n.º 2
0
    def _createView(self):
        ## Load all scripts in the scripts folder
        self.loadAllScripts(os.path.join(PluginRegistry.getInstance().getPluginPath("PostProcessingPlugin"), "scripts"))
        
        path = QUrl.fromLocalFile(os.path.join(PluginRegistry.getInstance().getPluginPath("PostProcessingPlugin"), "PostProcessingPlugin.qml"))
        self._component = QQmlComponent(Application.getInstance()._engine, path)

        self._context = QQmlContext(Application.getInstance()._engine.rootContext())
        self._context.setContextProperty("manager", self)
        self._view = self._component.create(self._context)
Exemplo n.º 3
0
 def createChangelogWindow(self):
     path = QUrl.fromLocalFile(
         os.path.join(
             PluginRegistry.getInstance().getPluginPath("ChangeLogPlugin"),
             "ChangeLog.qml"))
     component = QQmlComponent(Application.getInstance()._engine, path)
     self._changelog_context = QQmlContext(
         Application.getInstance()._engine.rootContext())
     self._changelog_context.setContextProperty("manager", self)
     self._changelog_window = component.create(self._changelog_context)
Exemplo n.º 4
0
 def createControlInterface(self):
     if self._control_view is None:
         path = QUrl.fromLocalFile(
             os.path.join(
                 PluginRegistry.getInstance().getPluginPath("USBPrinting"),
                 "ControlWindow.qml"))
         component = QQmlComponent(Application.getInstance()._engine, path)
         self._control_context = QQmlContext(
             Application.getInstance()._engine.rootContext())
         self._control_context.setContextProperty("manager", self)
         self._control_view = component.create(self._control_context)
Exemplo n.º 5
0
    def _createCostView(self):
        path = QUrl.fromLocalFile(
            os.path.join(
                PluginRegistry.getInstance().getPluginPath(
                    "PrintCostCalculator"), "PrintCostCalculator.qml"))
        self._component = QQmlComponent(Application.getInstance()._engine,
                                        path)

        self._context = QQmlContext(
            Application.getInstance()._engine.rootContext())
        self._context.setContextProperty("manager", self)
        self._cost_view = self._component.create(self._context)
class PauseBackend(QObject, Extension):
    def __init__(self, parent=None):
        super().__init__(parent=parent)

        self._additional_component = None
        self._additional_components_view = None

        Application.getInstance().engineCreatedSignal.connect(
            self._createAdditionalComponentsView)

    def _createAdditionalComponentsView(self):
        Logger.log(
            "d", "Creating additional ui components for Pause Backend plugin.")

        path = QUrl.fromLocalFile(
            os.path.join(
                PluginRegistry.getInstance().getPluginPath(
                    "PauseBackendPlugin"), "PauseBackend.qml"))
        self._additional_component = QQmlComponent(
            Application.getInstance()._engine, path)

        # We need access to engine (although technically we can't)
        self._additional_components_context = QQmlContext(
            Application.getInstance()._engine.rootContext())
        self._additional_components_context.setContextProperty("manager", self)

        self._additional_components_view = self._additional_component.create(
            self._additional_components_context)
        if not self._additional_components_view:
            Logger.log(
                "w",
                "Could not create additional components for Pause Backend plugin."
            )
            return

        Application.getInstance().addAdditionalComponent(
            "saveButton",
            self._additional_components_view.findChild(QObject,
                                                       "pauseResumeButton"))

    @pyqtSlot()
    def pauseBackend(self):
        backend = Application.getInstance().getBackend()
        backend._change_timer.timeout.disconnect(backend.slice)
        backend._terminate()

        backend.backendStateChange.emit(BackendState.Error)

    @pyqtSlot()
    def resumeBackend(self):
        backend = Application.getInstance().getBackend()
        backend._change_timer.timeout.connect(backend.slice)
        backend.forceSlice()
Exemplo n.º 7
0
    def createUserAgreementWindow(self):
        path = QUrl.fromLocalFile(
            os.path.join(
                PluginRegistry.getInstance().getPluginPath(self.getPluginId()),
                "UserAgreement.qml"))

        component = QQmlComponent(Application.getInstance()._engine, path)
        self._user_agreement_context = QQmlContext(
            Application.getInstance()._engine.rootContext())
        self._user_agreement_context.setContextProperty("manager", self)
        self._user_agreement_window = component.create(
            self._user_agreement_context)
Exemplo n.º 8
0
    def _createConfigUI(self):
        if self._ui_view is None:
            Logger.log("d", "Creating ImageReader config UI")
            path = QUrl.fromLocalFile(os.path.join(PluginRegistry.getInstance().getPluginPath("ImageReader"), "ConfigUI.qml"))
            component = QQmlComponent(Application.getInstance()._engine, path)
            self._ui_context = QQmlContext(Application.getInstance()._engine.rootContext())
            self._ui_context.setContextProperty("manager", self)
            self._ui_view = component.create(self._ui_context)

            self._ui_view.setFlags(self._ui_view.flags() & ~Qt.WindowCloseButtonHint & ~Qt.WindowMinimizeButtonHint & ~Qt.WindowMaximizeButtonHint);

            self._disable_size_callbacks = False
Exemplo n.º 9
0
    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())
Exemplo n.º 10
0
    def _createMonitorViewFromQML(self):
        path = QUrl.fromLocalFile(self._monitor_view_qml_path)

        # Because of garbage collection we need to keep this referenced by python.
        self._monitor_component = QQmlComponent(Application.getInstance()._engine, path)

        # Check if the context was already requested before (Printer output device might have multiple items in the future)
        if self._qml_context is None:
            self._qml_context = QQmlContext(Application.getInstance()._engine.rootContext())
            self._qml_context.setContextProperty("OutputDevice", self)

        self._monitor_item = self._monitor_component.create(self._qml_context)
        if self._monitor_item is None:
            Logger.log("e", "QQmlComponent status %s", self._monitor_component.status())
            Logger.log("e", "QQmlComponent error string %s", self._monitor_component.errorString())
Exemplo n.º 11
0
    def spawnPrintView(self):
        if self._print_view is None:
            path = QUrl.fromLocalFile(os.path.join(self._plugin_path, "PrintWindow.qml"))
            component = QQmlComponent(Application.getInstance()._engine, path)

            self._print_context = QQmlContext(Application.getInstance()._engine.rootContext())
            self._print_context.setContextProperty("OutputDevice", self)
            self._print_view = component.create(self._print_context)

            if component.isError():
                Logger.log("e", " Errors creating component: \n%s", "\n".join(
                    [e.toString() for e in component.errors()]))

        if self._print_view is not None:
            self._print_view.show()
class DialogHandler(QObject, Extension):
    def __init__(self, parent=None):
        super().__init__(parent)
        self._config_dialog = None
        self._tutorial_dialog = None
        self.addMenuItem(i18n_catalog.i18n("Configure"),
                         self._openConfigDialog)
        self.addMenuItem(
            i18n_catalog.i18n("How to install Cura SolidWorks macro"),
            self._openTutorialDialog)

    def _openConfigDialog(self):
        if not self._config_dialog:
            self._config_dialog = self._createDialog("ConfigDialog.qml")
        self._config_dialog.show()

    def _openTutorialDialog(self):
        if not self._tutorial_dialog:
            self._tutorial_dialog = self._createDialog(
                "MacroTutorialDialog.qml")
        self._tutorial_dialog.show()

    def _createDialog(self, dialog_qml):
        path = QUrl.fromLocalFile(
            os.path.join(
                PluginRegistry.getInstance().getPluginPath(self.getPluginId()),
                dialog_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)
        dialog = self._qml_component.create(self._qml_context)
        if dialog is None:
            Logger.log("e", "QQmlComponent status %s",
                       self._qml_component.status())
            Logger.log("e", "QQmlComponent errorString %s",
                       self._qml_component.errorString())
        return dialog

    @pyqtSlot()
    def openMacroAndIconDirectory(self):
        plugin_dir = os.path.join(PluginRegistry.getInstance().getPluginPath(
            self.getPluginId()))
        macro_dir = os.path.join(plugin_dir, "macro")
        os.system("explorer.exe \"%s\"" % macro_dir)
Exemplo n.º 13
0
 def _createViewFromQML(self):
     path = QUrl.fromLocalFile(
         os.path.join(PluginRegistry.getInstance().getPluginPath(self.getPluginId()), self._qml_url))
     self._component = QQmlComponent(Application.getInstance()._engine, path)
     self._context = QQmlContext(Application.getInstance()._engine.rootContext())
     self._context.setContextProperty("manager", self)
     self._view = self._component.create(self._context)
Exemplo n.º 14
0
    def createChangelogWindow(self):
        path = QUrl.fromLocalFile(os.path.join(PluginRegistry.getInstance().getPluginPath(self.getPluginId()), "ChangeLog.qml"))

        component = QQmlComponent(Application.getInstance()._engine, path)
        self._changelog_context = QQmlContext(Application.getInstance()._engine.rootContext())
        self._changelog_context.setContextProperty("manager", self)
        self._changelog_window = component.create(self._changelog_context)
Exemplo n.º 15
0
    def _createAdditionalComponentsView(self):
        Logger.log("d", "Creating additional ui components for OctoPrint-connected printers.")

        path = QUrl.fromLocalFile(os.path.join(PluginRegistry.getInstance().getPluginPath("OctoPrintPlugin"), "OctoPrintComponents.qml"))
        self._additional_component = QQmlComponent(Application.getInstance()._engine, path)

        # We need access to engine (although technically we can't)
        self._additional_components_context = QQmlContext(Application.getInstance()._engine.rootContext())
        self._additional_components_context.setContextProperty("manager", self)

        self._additional_components_view = self._additional_component.create(self._additional_components_context)
        if not self._additional_components_view:
            Logger.log("w", "Could not create additional components for OctoPrint-connected printers.")
            return

        Application.getInstance().addAdditionalComponent("monitorButtons", self._additional_components_view.findChild(QObject, "openOctoPrintButton"))
Exemplo n.º 16
0
 def _createDialog(self, qml):
     path = QUrl.fromLocalFile(
         os.path.join(os.path.dirname(os.path.abspath(__file__)), qml))
     self._component = QQmlComponent(Application.getInstance()._engine,
                                     path)
     self._context = QQmlContext(
         Application.getInstance()._engine.rootContext())
     self._context.setContextProperty("manager", self)
     dialog = self._component.create(self._context)
     if dialog is None:
         Logger.log("e", "QQmlComponent status %s",
                    self._component.status())
         Logger.log("e", "QQmlComponent errorString %s",
                    self._component.errorString())
         raise RuntimeError(self._component.errorString())
     return dialog
Exemplo n.º 17
0
    def __init__(self, app):
        super().__init__()
        self.app = app
        selection = libdice.dice.languages1(app, self, QUrl)
        self.languagerelevant()
        context = self.rootContext()
        self.scrollmodel = model2.PersonModel()
        context.setContextProperty("scrollmodel", self.scrollmodel)
        #self.libdice_strlist = [self.tr('lin'), self.tr('log'), self.tr('root'), self.tr('poly'), self.tr('exp'), self.tr('kombi'), self.tr('logistic'), self.tr('rand'), self.tr('gewicht'), self.tr('add'), self.tr('mul'), self.tr("Wuerfelwurf: "),self.tr(" (Wuerfelaugen ")]
        blub = [self.tr('test')]
        #print(str(blub[0]))
        #print(blub)
        #print(str(libdice.dice.randfkt2.values()))

        self.load(':/main.qml')

        langimg = self.rootObjects()[0].findChild(QObject, "langimg")
        # print("UrL: "+str(selection[1].fileName()))
        #langimg.setProperty("source",(":/"+selection[1].fileName()))
        langimg.setProperty("source", selection[1])
        #rado = self.rootObjects()[0].findChild(QObject, "radios")
        #rado.setProperty("onClicked", self.radu() )

        #layout = QVBoxLayout()
        #layout.addWidget(QPushButton('Top'))
        #layout.addWidget(QPushButton('Bottom'))
        context = QQmlContext(self.rootContext())
Exemplo n.º 18
0
 def _createViewFromQML(self):
     path = QUrl.fromLocalFile(
         os.path.join(
             PluginRegistry.getInstance().getPluginPath(self.getPluginId()),
             self._qml_url))
     self._component = QQmlComponent(Application.getInstance()._engine,
                                     path)
     self._context = QQmlContext(
         Application.getInstance()._engine.rootContext())
     self._context.setContextProperty("manager", self)
     self._view = self._component.create(self._context)
     if self._view is None:
         Logger.log("c", "QQmlComponent status %s",
                    self._component.status())
         Logger.log("c", "QQmlComponent error string %s",
                    self._component.errorString())
Exemplo n.º 19
0
def run():
    directory = os.path.dirname(os.path.abspath(__file__))

    appIcon = QIcon()
    appIcon.addFile(os.path.join(directory, "python.png"), QSize(64, 64))
    app.setWindowIcon(appIcon)

    from . import registerQmlTypes

    registerQmlTypes()

    engine = QQmlApplicationEngine()
    context = QQmlContext(engine)

    mainQml = QUrl.fromLocalFile(os.path.join(directory, "Main.qml"))

    component = QQmlComponent(engine)
    component.loadUrl(mainQml)

    if component.isError():
        for error in component.errors():
            print("Error: ", error.toString())

    dialog = component.create(context)

    return app.exec_()
Exemplo n.º 20
0
class UserAgreement(QObject, Extension):
    def __init__(self, parent=None):
        super(UserAgreement, self).__init__()
        self._user_agreement_window = None
        self._user_agreement_context = None
        Application.getInstance().engineCreatedSignal.connect(
            self._onEngineCreated)
        Preferences.getInstance().addPreference(
            "general/accepted_user_agreement", False)

    def _onEngineCreated(self):
        if not Preferences.getInstance().getValue(
                "general/accepted_user_agreement"):
            self.showUserAgreement()

    def showUserAgreement(self):
        if not self._user_agreement_window:
            self.createUserAgreementWindow()

        self._user_agreement_window.show()

    @pyqtSlot(bool)
    def didAgree(self, userChoice):
        if userChoice:
            Logger.log("i", "User agreed to the user agreement")
            Preferences.getInstance().setValue(
                "general/accepted_user_agreement", True)
            self._user_agreement_window.hide()
        else:
            Logger.log("i", "User did NOT agree to the user agreement")
            Preferences.getInstance().setValue(
                "general/accepted_user_agreement", False)
            CuraApplication.getInstance().quit()
        CuraApplication.getInstance().setNeedToShowUserAgreement(False)

    def createUserAgreementWindow(self):
        path = QUrl.fromLocalFile(
            os.path.join(
                PluginRegistry.getInstance().getPluginPath(self.getPluginId()),
                "UserAgreement.qml"))

        component = QQmlComponent(Application.getInstance()._engine, path)
        self._user_agreement_context = QQmlContext(
            Application.getInstance()._engine.rootContext())
        self._user_agreement_context.setContextProperty("manager", self)
        self._user_agreement_window = component.create(
            self._user_agreement_context)
Exemplo n.º 21
0
 def createQmlComponent(self, qml_file_path: str, context_properties: Dict[str, "QObject"]=None) -> Optional["QObject"]:
     path = QUrl.fromLocalFile(qml_file_path)
     component = QQmlComponent(self._engine, path)
     result_context = QQmlContext(self._engine.rootContext())
     if context_properties is not None:
         for name, value in context_properties.items():
             result_context.setContextProperty(name, value)
     result = component.create(result_context)
     for err in component.errors():
         Logger.log("e", str(err.toString()))
     if result is None:
         return None
     
     # We need to store the context with the qml object, else the context gets garbage collected and the qml objects
     # no longer function correctly/application crashes.
     result.attached_context = result_context
     return result
Exemplo n.º 22
0
 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()
Exemplo n.º 23
0
 def createControlInterface(self):
     if self._control_view is None:
         Logger.log("d", "Creating control interface for printer connection")
         path = QUrl.fromLocalFile(os.path.join(PluginRegistry.getInstance().getPluginPath("USBPrinting"), "ControlWindow.qml"))
         component = QQmlComponent(Application.getInstance()._engine, path)
         self._control_context = QQmlContext(Application.getInstance()._engine.rootContext())
         self._control_context.setContextProperty("manager", self)
         self._control_view = component.create(self._control_context)
Exemplo n.º 24
0
 def _createViewFromQML(self):
     path = QUrl.fromLocalFile(os.path.join(PluginRegistry.getInstance().getPluginPath("3MFReader"), self._qml_url))
     self._component = QQmlComponent(Application.getInstance()._engine, path)
     self._context = QQmlContext(Application.getInstance()._engine.rootContext())
     self._context.setContextProperty("manager", self)
     self._view = self._component.create(self._context)
     if self._view is None:
         Logger.log("c", "QQmlComponent status %s", self._component.status())
         Logger.log("c", "QQmlComponent error string %s", self._component.errorString())
Exemplo n.º 25
0
 def _createConfigUI(self):
     if self._ui_view is None:
         Logger.log('d', 'Creating SVGReader CDT UI')
         path = QUrl.fromLocalFile(
             os.path.join(
                 PluginRegistry.getInstance().getPluginPath('SVGReader'),
                 'CDTUI.qml'))
         component = QQmlComponent(Application.getInstance()._qml_engine,
                                   path)
         self._ui_context = QQmlContext(
             Application.getInstance()._qml_engine.rootContext())
         self._ui_context.setContextProperty('manager', self)
         self._ui_view = component.create(self._ui_context)
         self._ui_view.setFlags(self._ui_view.flags()
                                & ~(Qt.WindowCloseButtonHint)
                                & ~(Qt.WindowMinimizeButtonHint)
                                & ~(Qt.WindowMaximizeButtonHint))
         self._disable_size_callbacks = False
Exemplo n.º 26
0
    def _createDialog(self, dialog_qml, directory=None):
        if directory is None:
            directory = PluginRegistry.getInstance().getPluginPath(
                self.getPluginId())
        path = QUrl.fromLocalFile(os.path.join(directory, dialog_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)
        dialog = self._qml_component.create(self._qml_context)
        if dialog is None:
            Logger.log("e", "QQmlComponent status %s",
                       self._qml_component.status())
            Logger.log("e", "QQmlComponent errorString %s",
                       self._qml_component.errorString())
        return dialog
Exemplo n.º 27
0
class SolidWorksUiCommons():
    def _createDialog(self, dialog_qml, directory=None):
        if directory is None:
            directory = PluginRegistry.getInstance().getPluginPath(
                self.getPluginId())
        path = QUrl.fromLocalFile(os.path.join(directory, dialog_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)
        dialog = self._qml_component.create(self._qml_context)
        if dialog is None:
            Logger.log("e", "QQmlComponent status %s",
                       self._qml_component.status())
            Logger.log("e", "QQmlComponent errorString %s",
                       self._qml_component.errorString())
        return dialog

    @pyqtSlot(int, result=bool)
    def isVersionOperational(self, major_version):
        return major_version in self.reader.operational_versions

    @pyqtSlot(int, str, result=bool)
    def getTechnicalInfoPerVersion(self, revision, name):
        return bool(self.reader.technical_infos_per_version[revision][name])

    @pyqtSlot(result=list)
    def getVersionsList(self):
        versions = list(self.reader.technical_infos_per_version.keys())
        versions.sort()
        versions.reverse()
        return versions

    @pyqtSlot(result=int)
    def getVersionsCount(self):
        return int(len(list(self.reader.technical_infos_per_version.keys())))

    @pyqtSlot(int, result=str)
    def getFriendlyName(self, major_revision):
        return self.reader.getFriendlyName(major_revision)
Exemplo n.º 28
0
    def _createView(self):
        Logger.log("d", "Creating post processing plugin view.")

        ## Load all scripts in the scripts folder
        try:
            self.loadAllScripts(os.path.join(PluginRegistry.getInstance().getPluginPath("PostProcessingPlugin"), "scripts"))
        except Exception as e:
            print("Exception occured", e)  # TODO: Debug code (far to general catch. Remove this once done testing)

        path = QUrl.fromLocalFile(os.path.join(PluginRegistry.getInstance().getPluginPath("PostProcessingPlugin"), "PostProcessingPlugin.qml"))
        self._component = QQmlComponent(Application.getInstance()._engine, path)

        # We need access to engine (although technically we can't)
        self._context = QQmlContext(Application.getInstance()._engine.rootContext())
        self._context.setContextProperty("manager", self)
        self._view = self._component.create(self._context)
        Logger.log("d", "Post processing view created.")

        Application.getInstance().addAdditionalComponent("saveButton", self._view.findChild(QObject, "postProcessingSaveAreaButton"))
    def _createView(self):
        ## Load all scripts in the scripts folder
        self.loadAllScripts(os.path.join(PluginRegistry.getInstance().getPluginPath("PostProcessingPlugin"), "scripts"))
        
        path = QUrl.fromLocalFile(os.path.join(PluginRegistry.getInstance().getPluginPath("PostProcessingPlugin"), "PostProcessingPlugin.qml"))
        self._component = QQmlComponent(Application.getInstance()._engine, path)

        self._context = QQmlContext(Application.getInstance()._engine.rootContext())
        self._context.setContextProperty("manager", self)
        self._view = self._component.create(self._context)
Exemplo n.º 30
0
    def createQmlComponent(self, qml_file_path: str, context_properties: Dict[str, "QObject"] = None) -> Optional["QObject"]:
        if self._qml_engine is None: # Protect in case the engine was not initialized yet
            return None
        path = QUrl.fromLocalFile(qml_file_path)
        component = QQmlComponent(self._qml_engine, path)
        result_context = QQmlContext(self._qml_engine.rootContext()) #type: ignore #MyPy doens't realise that self._qml_engine can't be None here.
        if context_properties is not None:
            for name, value in context_properties.items():
                result_context.setContextProperty(name, value)
        result = component.create(result_context)
        for err in component.errors():
            Logger.log("e", str(err.toString()))
        if result is None:
            return None

        # We need to store the context with the qml object, else the context gets garbage collected and the qml objects
        # no longer function correctly/application crashes.
        result.attached_context = result_context
        return result
Exemplo n.º 31
0
    def _createDialogue(self):
        #Create a QML component from the Hello.qml file.
        qml_file = QUrl.fromLocalFile(
            os.path.join(
                PluginRegistry.getInstance().getPluginPath(self.getPluginId()),
                "SelectionInfo.qml"))
        component = QQmlComponent(Application.getInstance()._engine, qml_file)
        qml_context = QQmlContext(
            Application.getInstance()._engine.rootContext(
            ))  #List the QML component as subcomponent of the main window.

        return component.create(qml_context)
Exemplo n.º 32
0
    def _createConfigUI(self):
        if self._ui_view is None:
            Logger.log("d", "Creating ImageReader config UI")
            path = QUrl.fromLocalFile(os.path.join(PluginRegistry.getInstance().getPluginPath("ImageReader"), "ConfigUI.qml"))
            component = QQmlComponent(Application.getInstance()._engine, path)
            self._ui_context = QQmlContext(Application.getInstance()._engine.rootContext())
            self._ui_context.setContextProperty("manager", self)
            self._ui_view = component.create(self._ui_context)

            self._ui_view.setFlags(self._ui_view.flags() & ~Qt.WindowCloseButtonHint & ~Qt.WindowMinimizeButtonHint & ~Qt.WindowMaximizeButtonHint);

            self._disable_size_callbacks = False
Exemplo n.º 33
0
class PauseBackend(QObject, Extension):
    def __init__(self, parent = None):
        super().__init__(parent = parent)

        self._additional_component = None
        self._additional_components_view = None

        Application.getInstance().engineCreatedSignal.connect(self._createAdditionalComponentsView)

    def _createAdditionalComponentsView(self):
        Logger.log("d", "Creating additional ui components for Pause Backend plugin.")

        path = QUrl.fromLocalFile(os.path.join(PluginRegistry.getInstance().getPluginPath("PauseBackendPlugin"), "PauseBackend.qml"))
        self._additional_component = QQmlComponent(Application.getInstance()._engine, path)

        # We need access to engine (although technically we can't)
        self._additional_components_context = QQmlContext(Application.getInstance()._engine.rootContext())
        self._additional_components_context.setContextProperty("manager", self)

        self._additional_components_view = self._additional_component.create(self._additional_components_context)
        if not self._additional_components_view:
            Logger.log("w", "Could not create additional components for Pause Backend plugin.")
            return

        Application.getInstance().addAdditionalComponent("saveButton", self._additional_components_view.findChild(QObject, "pauseResumeButton"))

    @pyqtSlot()
    def pauseBackend(self):
        backend = Application.getInstance().getBackend()
        backend._change_timer.timeout.disconnect(backend.slice)
        backend._terminate()

        backend.backendStateChange.emit(BackendState.Error)

    @pyqtSlot()
    def resumeBackend(self):
        backend = Application.getInstance().getBackend()
        backend._change_timer.timeout.connect(backend.slice)
        backend.forceSlice()
Exemplo n.º 34
0
    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())
Exemplo n.º 35
0
    def _createMonitorViewFromQML(self):
        path = QUrl.fromLocalFile(self._monitor_view_qml_path)

        # Because of garbage collection we need to keep this referenced by python.
        self._monitor_component = QQmlComponent(Application.getInstance()._engine, path)

        # Check if the context was already requested before (Printer output device might have multiple items in the future)
        if self._qml_context is None:
            self._qml_context = QQmlContext(Application.getInstance()._engine.rootContext())
            self._qml_context.setContextProperty("OutputDevice", self)

        self._monitor_item = self._monitor_component.create(self._qml_context)
        if self._monitor_item is None:
            Logger.log("e", "QQmlComponent status %s", self._monitor_component.status())
            Logger.log("e", "QQmlComponent error string %s", self._monitor_component.errorString())
Exemplo n.º 36
0
    def _createAdditionalComponentsView(self):
        Logger.log("d", "Creating additional ui components for Pause Backend plugin.")

        path = QUrl.fromLocalFile(os.path.join(PluginRegistry.getInstance().getPluginPath("PauseBackendPlugin"), "PauseBackend.qml"))
        self._additional_component = QQmlComponent(Application.getInstance()._engine, path)

        # We need access to engine (although technically we can't)
        self._additional_components_context = QQmlContext(Application.getInstance()._engine.rootContext())
        self._additional_components_context.setContextProperty("manager", self)

        self._additional_components_view = self._additional_component.create(self._additional_components_context)
        if not self._additional_components_view:
            Logger.log("w", "Could not create additional components for Pause Backend plugin.")
            return

        Application.getInstance().addAdditionalComponent("saveButton", self._additional_components_view.findChild(QObject, "pauseResumeButton"))
    def _createView(self):
        Logger.log("d", "Creating post processing plugin view.")

        ## Load all scripts in the scripts folder
        try:
            self.loadAllScripts(os.path.join(PluginRegistry.getInstance().getPluginPath("PostProcessingPlugin"), "scripts"))
        except Exception as e:
            print("Exception occured", e)  # TODO: Debug code (far to general catch. Remove this once done testing)

        path = QUrl.fromLocalFile(os.path.join(PluginRegistry.getInstance().getPluginPath("PostProcessingPlugin"), "PostProcessingPlugin.qml"))
        self._component = QQmlComponent(Application.getInstance()._engine, path)

        # We need access to engine (although technically we can't)
        self._context = QQmlContext(Application.getInstance()._engine.rootContext())
        self._context.setContextProperty("manager", self)
        self._view = self._component.create(self._context)
        Logger.log("d", "Post processing view created.")
Exemplo n.º 38
0
    def _createAdditionalComponentsView(self):
        Logger.log("d", "Creating additional ui components for UM3.")
        path = QUrl.fromLocalFile(
            os.path.join(PluginRegistry.getInstance().getPluginPath("UM3NetworkPrinting"), "UM3InfoComponents.qml")
        )
        self.__additional_component = QQmlComponent(Application.getInstance()._engine, path)

        # We need access to engine (although technically we can't)
        self.__additional_components_context = QQmlContext(Application.getInstance()._engine.rootContext())
        self.__additional_components_context.setContextProperty("manager", self)

        self.__additional_components_view = self.__additional_component.create(self.__additional_components_context)
        if not self.__additional_components_view:
            Logger.log("w", "Could not create ui components for UM3.")
            return

        Application.getInstance().addAdditionalComponent(
            "monitorButtons", self.__additional_components_view.findChild(QObject, "networkPrinterConnectButton")
        )
        Application.getInstance().addAdditionalComponent(
            "machinesDetailPane", self.__additional_components_view.findChild(QObject, "networkPrinterConnectionInfo")
        )
Exemplo n.º 39
0
class ImageReaderUI(QObject):
    show_config_ui_trigger = pyqtSignal()

    def __init__(self, image_reader):
        super(ImageReaderUI, self).__init__()
        self.image_reader = image_reader
        self._ui_view = None
        self.show_config_ui_trigger.connect(self._actualShowConfigUI)

        self.defaultWidth = 120
        self.defaultDepth = 120

        self._aspect = 1
        self._width = self.defaultWidth
        self._depth = self.defaultDepth

        self.base_height = 1
        self.peak_height = 10
        self.smoothing = 1
        self.image_color_invert = False;

        self._ui_lock = threading.Lock()
        self._cancelled = False
        self._disable_size_callbacks = False

    def setWidthAndDepth(self, width, depth):
        self._aspect = width / depth
        self._width = width
        self._depth = depth

    def getWidth(self):
        return self._width

    def getDepth(self):
        return self._depth

    def getCancelled(self):
        return self._cancelled

    def waitForUIToClose(self):
        self._ui_lock.acquire()
        self._ui_lock.release()

    def showConfigUI(self):
        self._ui_lock.acquire()
        self._cancelled = False
        self.show_config_ui_trigger.emit()

    def _actualShowConfigUI(self):
        self._disable_size_callbacks = True

        if self._ui_view is None:
            self._createConfigUI()
        self._ui_view.show()

        self._ui_view.findChild(QObject, "Width").setProperty("text", str(self._width))
        self._ui_view.findChild(QObject, "Depth").setProperty("text", str(self._depth))
        self._disable_size_callbacks = False

        self._ui_view.findChild(QObject, "Base_Height").setProperty("text", str(self.base_height))
        self._ui_view.findChild(QObject, "Peak_Height").setProperty("text", str(self.peak_height))
        self._ui_view.findChild(QObject, "Smoothing").setProperty("value", self.smoothing)

    def _createConfigUI(self):
        if self._ui_view is None:
            Logger.log("d", "Creating ImageReader config UI")
            path = QUrl.fromLocalFile(os.path.join(PluginRegistry.getInstance().getPluginPath("ImageReader"), "ConfigUI.qml"))
            component = QQmlComponent(Application.getInstance()._engine, path)
            self._ui_context = QQmlContext(Application.getInstance()._engine.rootContext())
            self._ui_context.setContextProperty("manager", self)
            self._ui_view = component.create(self._ui_context)

            self._ui_view.setFlags(self._ui_view.flags() & ~Qt.WindowCloseButtonHint & ~Qt.WindowMinimizeButtonHint & ~Qt.WindowMaximizeButtonHint);

            self._disable_size_callbacks = False

    @pyqtSlot()
    def onOkButtonClicked(self):
        self._cancelled = False
        self._ui_view.close()
        self._ui_lock.release()

    @pyqtSlot()
    def onCancelButtonClicked(self):
        self._cancelled = True
        self._ui_view.close()
        self._ui_lock.release()

    @pyqtSlot(str)
    def onWidthChanged(self, value):
        if self._ui_view and not self._disable_size_callbacks:
            if len(value) > 0:
                self._width = float(value)
            else:
                self._width = 0

            self._depth = self._width / self._aspect
            self._disable_size_callbacks = True
            self._ui_view.findChild(QObject, "Depth").setProperty("text", str(self._depth))
            self._disable_size_callbacks = False

    @pyqtSlot(str)
    def onDepthChanged(self, value):
        if self._ui_view and not self._disable_size_callbacks:
            if len(value) > 0:
                self._depth = float(value)
            else:
                self._depth = 0

            self._width = self._depth * self._aspect
            self._disable_size_callbacks = True
            self._ui_view.findChild(QObject, "Width").setProperty("text", str(self._width))
            self._disable_size_callbacks = False

    @pyqtSlot(str)
    def onBaseHeightChanged(self, value):
        if (len(value) > 0):
            self.base_height = float(value)
        else:
            self.base_height = 0

    @pyqtSlot(str)
    def onPeakHeightChanged(self, value):
        if (len(value) > 0):
            self.peak_height = float(value)
        else:
            self.peak_height = 0

    @pyqtSlot(float)
    def onSmoothingChanged(self, value):
        self.smoothing = int(value)

    @pyqtSlot(int)
    def onImageColorInvertChanged(self, value):
        if (value == 1):
            self.image_color_invert = True
        else:
            self.image_color_invert = False
Exemplo n.º 40
0
class PrinterConnection(OutputDevice, QObject, SignalEmitter):
    def __init__(self, serial_port, parent = None):
        QObject.__init__(self, parent)
        OutputDevice.__init__(self, serial_port)
        SignalEmitter.__init__(self)
        #super().__init__(serial_port)
        self.setName(catalog.i18nc("@item:inmenu", "USB printing"))
        self.setShortDescription(catalog.i18nc("@action:button", "Print with USB"))
        self.setDescription(catalog.i18nc("@info:tooltip", "Print with USB"))
        self.setIconName("print")

        self._serial = None
        self._serial_port = serial_port
        self._error_state = None

        self._connect_thread = threading.Thread(target = self._connect)
        self._connect_thread.daemon = True

        self._end_stop_thread = threading.Thread(target = self._pollEndStop)
        self._end_stop_thread.deamon = True
        self._poll_endstop = -1

        # Printer is connected
        self._is_connected = False

        # Printer is in the process of connecting
        self._is_connecting = False

        # The baud checking is done by sending a number of m105 commands to the printer and waiting for a readable
        # response. If the baudrate is correct, this should make sense, else we get giberish.
        self._required_responses_auto_baud = 3

        self._progress = 0

        self._listen_thread = threading.Thread(target=self._listen)
        self._listen_thread.daemon = True

        self._update_firmware_thread = threading.Thread(target= self._updateFirmware)
        self._update_firmware_thread.daemon = True
        
        self._heatup_wait_start_time = time.time()

        ## Queue for commands that need to be send. Used when command is sent when a print is active.
        self._command_queue = queue.Queue()

        self._is_printing = False

        ## Set when print is started in order to check running time.
        self._print_start_time = None
        self._print_start_time_100 = None

        ## Keep track where in the provided g-code the print is
        self._gcode_position = 0

        # List of gcode lines to be printed
        self._gcode = []

        # Number of extruders
        self._extruder_count = 1

        # Temperatures of all extruders
        self._extruder_temperatures = [0] * self._extruder_count

        # Target temperatures of all extruders
        self._target_extruder_temperatures = [0] * self._extruder_count

        #Target temperature of the bed
        self._target_bed_temperature = 0 

        # Temperature of the bed
        self._bed_temperature = 0

        # Current Z stage location 
        self._current_z = 0

        self._x_min_endstop_pressed = False
        self._y_min_endstop_pressed = False
        self._z_min_endstop_pressed = False

        self._x_max_endstop_pressed = False
        self._y_max_endstop_pressed = False
        self._z_max_endstop_pressed = False

        # In order to keep the connection alive we request the temperature every so often from a different extruder.
        # This index is the extruder we requested data from the last time.
        self._temperature_requested_extruder_index = 0 

        self._updating_firmware = False

        self._firmware_file_name = None

        self._control_view = None

    onError = pyqtSignal()
    progressChanged = pyqtSignal()
    extruderTemperatureChanged = pyqtSignal()
    bedTemperatureChanged = pyqtSignal()
    firmwareUpdateComplete = pyqtSignal()

    endstopStateChanged = pyqtSignal(str ,bool, arguments = ["key","state"])

    @pyqtProperty(float, notify = progressChanged)
    def progress(self):
        return self._progress

    @pyqtProperty(float, notify = extruderTemperatureChanged)
    def extruderTemperature(self):
        return self._extruder_temperatures[0]

    @pyqtProperty(float, notify = bedTemperatureChanged)
    def bedTemperature(self):
        return self._bed_temperature

    @pyqtProperty(str, notify = onError)
    def error(self):
        return self._error_state

    # TODO: Might need to add check that extruders can not be changed when it started printing or loading these settings from settings object    
    def setNumExtuders(self, num):
        self._extruder_count = num
        self._extruder_temperatures = [0] * self._extruder_count
        self._target_extruder_temperatures = [0] * self._extruder_count

    ##  Is the printer actively printing
    def isPrinting(self):
        if not self._is_connected or self._serial is None:
            return False
        return self._is_printing

    @pyqtSlot()
    def startPrint(self):
        self.writeStarted.emit(self)
        gcode_list = getattr( Application.getInstance().getController().getScene(), "gcode_list")
        self.printGCode(gcode_list)

    ##  Start a print based on a g-code.
    #   \param gcode_list List with gcode (strings).
    def printGCode(self, gcode_list):
        if self.isPrinting() or not self._is_connected:
            Logger.log("d", "Printer is busy or not connected, aborting print")
            self.writeError.emit(self)
            return

        self._gcode.clear()
        for layer in gcode_list:
            self._gcode.extend(layer.split("\n"))

        #Reset line number. If this is not done, first line is sometimes ignored
        self._gcode.insert(0, "M110")
        self._gcode_position = 0
        self._print_start_time_100 = None
        self._is_printing = True
        self._print_start_time = time.time()

        for i in range(0, 4): #Push first 4 entries before accepting other inputs
            self._sendNextGcodeLine()

        self.writeFinished.emit(self)

    ##  Get the serial port string of this connection.
    #   \return serial port
    def getSerialPort(self):
        return self._serial_port

    ##  Try to connect the serial. This simply starts the thread, which runs _connect.
    def connect(self):
        if not self._updating_firmware and not self._connect_thread.isAlive():
            self._connect_thread.start()

    ##  Private fuction (threaded) that actually uploads the firmware.
    def _updateFirmware(self):
        if self._is_connecting or  self._is_connected:
            self.close()
        hex_file = intelHex.readHex(self._firmware_file_name)

        if len(hex_file) == 0:
            Logger.log("e", "Unable to read provided hex file. Could not update firmware")
            return 

        programmer = stk500v2.Stk500v2()
        programmer.progressCallback = self.setProgress 
        programmer.connect(self._serial_port)

        time.sleep(1) # Give programmer some time to connect. Might need more in some cases, but this worked in all tested cases.

        if not programmer.isConnected():
            Logger.log("e", "Unable to connect with serial. Could not update firmware")
            return 

        self._updating_firmware = True

        try:
            programmer.programChip(hex_file)
            self._updating_firmware = False
        except Exception as e:
            Logger.log("e", "Exception while trying to update firmware %s" %e)
            self._updating_firmware = False
            return
        programmer.close()

        self.setProgress(100, 100)

        self.firmwareUpdateComplete.emit()

    ##  Upload new firmware to machine
    #   \param filename full path of firmware file to be uploaded
    def updateFirmware(self, file_name):
        Logger.log("i", "Updating firmware of %s using %s", self._serial_port, file_name)
        self._firmware_file_name = file_name
        self._update_firmware_thread.start()

    @pyqtSlot()
    def startPollEndstop(self):
        if self._poll_endstop == -1:
            self._poll_endstop = True
            self._end_stop_thread.start()

    @pyqtSlot()
    def stopPollEndstop(self):
        self._poll_endstop = False

    def _pollEndStop(self):
        while self._is_connected and self._poll_endstop:
            self.sendCommand("M119")
            time.sleep(0.5)

    ##  Private connect function run by thread. Can be started by calling connect.
    def _connect(self):
        Logger.log("d", "Attempting to connect to %s", self._serial_port)
        self._is_connecting = True
        programmer = stk500v2.Stk500v2()
        try:
            programmer.connect(self._serial_port) # Connect with the serial, if this succeeds, it"s an arduino based usb device.
            self._serial = programmer.leaveISP()
        except ispBase.IspError as e:
            Logger.log("i", "Could not establish connection on %s: %s. Device is not arduino based." %(self._serial_port,str(e)))
        except Exception as e:
            Logger.log("i", "Could not establish connection on %s, unknown reasons.  Device is not arduino based." % self._serial_port)

        # If the programmer connected, we know its an atmega based version. Not all that usefull, but it does give some debugging information.
        for baud_rate in self._getBaudrateList(): # Cycle all baud rates (auto detect)
            Logger.log("d","Attempting to connect to printer with serial %s on baud rate %s", self._serial_port, baud_rate)
            if self._serial is None:
                try:
                    self._serial = serial.Serial(str(self._serial_port), baud_rate, timeout = 3, writeTimeout = 10000)
                except serial.SerialException:
                    #Logger.log("i", "Could not open port %s" % self._serial_port)
                    continue
            else:
                if not self.setBaudRate(baud_rate):
                    continue # Could not set the baud rate, go to the next

            time.sleep(1.5) # Ensure that we are not talking to the bootloader. 1.5 sec seems to be the magic number
            sucesfull_responses = 0
            timeout_time = time.time() + 5
            self._serial.write(b"\n")
            self._sendCommand("M105")  # Request temperature, as this should (if baudrate is correct) result in a command with "T:" in it
            while timeout_time > time.time():
                line = self._readline()
                if line is None:
                    self.setIsConnected(False) # Something went wrong with reading, could be that close was called.
                    return
                
                if b"T:" in line:
                    self._serial.timeout = 0.5
                    sucesfull_responses += 1
                    if sucesfull_responses >= self._required_responses_auto_baud:
                        self._serial.timeout = 2 #Reset serial timeout
                        self.setIsConnected(True)
                        Logger.log("i", "Established printer connection on port %s" % self._serial_port)
                        return 

                self._sendCommand("M105") # Send M105 as long as we are listening, otherwise we end up in an undefined state

        Logger.log("e", "Baud rate detection for %s failed", self._serial_port)
        self.close() # Unable to connect, wrap up.
        self.setIsConnected(False)

    ##  Set the baud rate of the serial. This can cause exceptions, but we simply want to ignore those.
    def setBaudRate(self, baud_rate):
        try:
            self._serial.baudrate = baud_rate
            return True
        except Exception as e:
            return False

    def setIsConnected(self, state):
        self._is_connecting = False
        if self._is_connected != state:
            self._is_connected = state
            self.connectionStateChanged.emit(self._serial_port)
            if self._is_connected: 
                self._listen_thread.start() #Start listening
        else:
            Logger.log("w", "Printer connection state was not changed")

    connectionStateChanged = Signal()

    ##  Close the printer connection
    def close(self):
        Logger.log("d", "Closing the printer connection.")
        if self._connect_thread.isAlive():
            try:
                self._connect_thread.join()
            except Exception as e:
                pass # This should work, but it does fail sometimes for some reason

        self._connect_thread = threading.Thread(target=self._connect)
        self._connect_thread.daemon = True
        
        if self._serial is not None:
            self.setIsConnected(False)
            try:
                self._listen_thread.join()
            except:
                pass
            self._serial.close()

        self._listen_thread = threading.Thread(target=self._listen)
        self._listen_thread.daemon = True
        self._serial = None

    def isConnected(self):
        return self._is_connected

    @pyqtSlot(int)
    def heatupNozzle(self, temperature):
        Logger.log("d", "Setting nozzle temperature to %s", temperature)
        self._sendCommand("M104 S%s" % temperature)

    @pyqtSlot(int)
    def heatupBed(self, temperature):
        Logger.log("d", "Setting bed temperature to %s", temperature)
        self._sendCommand("M140 S%s" % temperature)

    @pyqtSlot("long", "long","long")
    def moveHead(self, x, y, z):
        Logger.log("d","Moving head to %s, %s , %s", x, y, z)
        self._sendCommand("G0 X%s Y%s Z%s"%(x,y,z))

    @pyqtSlot()
    def homeHead(self):
       self._sendCommand("G28")

    ##  Directly send the command, withouth checking connection state (eg; printing).
    #   \param cmd string with g-code
    def _sendCommand(self, cmd):
        if self._serial is None:
            return

        if "M109" in cmd or "M190" in cmd:
            self._heatup_wait_start_time = time.time()
        if "M104" in cmd or "M109" in cmd:
            try:
                t = 0
                if "T" in cmd:
                    t = int(re.search("T([0-9]+)", cmd).group(1))
                self._target_extruder_temperatures[t] = float(re.search("S([0-9]+)", cmd).group(1))
            except:
                pass
        if "M140" in cmd or "M190" in cmd:
            try:
                self._target_bed_temperature = float(re.search("S([0-9]+)", cmd).group(1))
            except:
                pass
        try:
            command = (cmd + "\n").encode()
            self._serial.write(b"\n")
            self._serial.write(command)
        except serial.SerialTimeoutException:
            Logger.log("w","Serial timeout while writing to serial port, trying again.")
            try:
                time.sleep(0.5)
                self._serial.write((cmd + "\n").encode())
            except Exception as e:
                Logger.log("e","Unexpected error while writing serial port %s " % e)
                self._setErrorState("Unexpected error while writing serial port %s " % e)
                self.close()
        except Exception as e:
            Logger.log("e","Unexpected error while writing serial port %s" % e)
            self._setErrorState("Unexpected error while writing serial port %s " % e)
            self.close()

    ##  Ensure that close gets called when object is destroyed
    def __del__(self):
        self.close()

    def createControlInterface(self):
        if self._control_view is None:
            Logger.log("d", "Creating control interface for printer connection")
            path = QUrl.fromLocalFile(os.path.join(PluginRegistry.getInstance().getPluginPath("USBPrinting"), "ControlWindow.qml"))
            component = QQmlComponent(Application.getInstance()._engine, path)
            self._control_context = QQmlContext(Application.getInstance()._engine.rootContext())
            self._control_context.setContextProperty("manager", self)
            self._control_view = component.create(self._control_context)

    ##  Show control interface.
    #   This will create the view if its not already created.
    def showControlInterface(self):
        if self._control_view is None:
            self.createControlInterface()
        self._control_view.show()

    ##  Send a command to printer. 
    #   \param cmd string with g-code
    def sendCommand(self, cmd):
        if self.isPrinting():
            self._command_queue.put(cmd)
        elif self.isConnected():
            self._sendCommand(cmd)

    ##  Set the error state with a message.
    #   \param error String with the error message.
    def _setErrorState(self, error):
        self._error_state = error
        self.onError.emit()

    ##  Private function to set the temperature of an extruder
    #   \param index index of the extruder
    #   \param temperature recieved temperature
    def _setExtruderTemperature(self, index, temperature):
        try: 
            self._extruder_temperatures[index] = temperature
            self.extruderTemperatureChanged.emit()
        except Exception as e:
            pass

    ##  Private function to set the temperature of the bed.
    #   As all printers (as of time of writing) only support a single heated bed,
    #   these are not indexed as with extruders.
    def _setBedTemperature(self, temperature):
        self._bed_temperature = temperature
        self.bedTemperatureChanged.emit()

    def requestWrite(self, node, file_name = None):
        self.showControlInterface()

    def _setEndstopState(self, endstop_key, value):
        if endstop_key == b'x_min':
            if self._x_min_endstop_pressed != value:
                self.endstopStateChanged.emit('x_min', value)
            self._x_min_endstop_pressed = value
        elif endstop_key == b'y_min':
            if self._y_min_endstop_pressed != value:
                self.endstopStateChanged.emit('y_min', value)
            self._y_min_endstop_pressed = value
        elif endstop_key == b'z_min':
            if self._z_min_endstop_pressed != value:
                self.endstopStateChanged.emit('z_min', value)
            self._z_min_endstop_pressed = value

    ##  Listen thread function. 
    def _listen(self):
        Logger.log("i", "Printer connection listen thread started for %s" % self._serial_port)
        temperature_request_timeout = time.time()
        ok_timeout = time.time()
        while self._is_connected:
            line = self._readline()

            if line is None: 
                break # None is only returned when something went wrong. Stop listening

            if time.time() > temperature_request_timeout:
                if self._extruder_count > 0:
                    self._temperature_requested_extruder_index = (self._temperature_requested_extruder_index + 1) % self._extruder_count
                    self.sendCommand("M105 T%d" % (self._temperature_requested_extruder_index))
                else:
                    self.sendCommand("M105")
                temperature_request_timeout = time.time() + 5

            if line.startswith(b"Error:"):
                # Oh YEAH, consistency.
                # Marlin reports an MIN/MAX temp error as "Error:x\n: Extruder switched off. MAXTEMP triggered !\n"
                #       But a bed temp error is reported as "Error: Temperature heated bed switched off. MAXTEMP triggered !!"
                #       So we can have an extra newline in the most common case. Awesome work people.
                if re.match(b"Error:[0-9]\n", line):
                    line = line.rstrip() + self._readline()

                # Skip the communication errors, as those get corrected.
                if b"Extruder switched off" in line or b"Temperature heated bed switched off" in line or b"Something is wrong, please turn off the printer." in line:
                    if not self.hasError():
                        self._setErrorState(line[6:])

            elif b" T:" in line or line.startswith(b"T:"): #Temperature message
                try: 
                    self._setExtruderTemperature(self._temperature_requested_extruder_index,float(re.search(b"T: *([0-9\.]*)", line).group(1)))
                except:
                    pass
                if b"B:" in line: # Check if it"s a bed temperature
                    try:
                        self._setBedTemperature(float(re.search(b"B: *([0-9\.]*)", line).group(1)))
                    except Exception as e:
                        pass
                #TODO: temperature changed callback
            elif b"_min" in line or b"_max" in line:
                tag, value = line.split(b':', 1)
                self._setEndstopState(tag,(b'H' in value or b'TRIGGERED' in value))

            if self._is_printing:
                if line == b"" and time.time() > ok_timeout:
                    line = b"ok" # Force a timeout (basicly, send next command)

                if b"ok" in line:
                    ok_timeout = time.time() + 5
                    if not self._command_queue.empty():
                        self._sendCommand(self._command_queue.get())
                    else:
                        self._sendNextGcodeLine()
                elif b"resend" in line.lower() or b"rs" in line: # Because a resend can be asked with "resend" and "rs"
                    try:
                        self._gcode_position = int(line.replace(b"N:",b" ").replace(b"N",b" ").replace(b":",b" ").split()[-1])
                    except:
                        if b"rs" in line:
                            self._gcode_position = int(line.split()[1])

            else: # Request the temperature on comm timeout (every 2 seconds) when we are not printing.)
                if line == b"":
                    if self._extruder_count > 0:
                        self._temperature_requested_extruder_index = (self._temperature_requested_extruder_index + 1) % self._extruder_count
                        self.sendCommand("M105 T%d" % self._temperature_requested_extruder_index)
                    else:
                        self.sendCommand("M105")
        Logger.log("i", "Printer connection listen thread stopped for %s" % self._serial_port)

    ##  Send next Gcode in the gcode list
    def _sendNextGcodeLine(self):
        if self._gcode_position >= len(self._gcode):
            return
        if self._gcode_position == 100:
            self._print_start_time_100 = time.time()
        line = self._gcode[self._gcode_position]

        if ";" in line:
            line = line[:line.find(";")]
        line = line.strip()
        try:
            if line == "M0" or line == "M1":
                line = "M105"   #Don"t send the M0 or M1 to the machine, as M0 and M1 are handled as an LCD menu pause.
            if ("G0" in line or "G1" in line) and "Z" in line:
                z = float(re.search("Z([0-9\.]*)", line).group(1))
                if self._current_z != z:
                    self._current_z = z
        except Exception as e:
            Logger.log("e", "Unexpected error with printer connection: %s" % e)
            self._setErrorState("Unexpected error: %s" %e)
        checksum = functools.reduce(lambda x,y: x^y, map(ord, "N%d%s" % (self._gcode_position, line)))

        self._sendCommand("N%d%s*%d" % (self._gcode_position, line, checksum))
        self._gcode_position += 1 
        self.setProgress(( self._gcode_position / len(self._gcode)) * 100)
        self.progressChanged.emit()

    ##  Set the progress of the print. 
    #   It will be normalized (based on max_progress) to range 0 - 100
    def setProgress(self, progress, max_progress = 100):
        self._progress  = (progress / max_progress) * 100 #Convert to scale of 0-100
        self.progressChanged.emit()

    ##  Cancel the current print. Printer connection wil continue to listen.
    @pyqtSlot()
    def cancelPrint(self):
        self._gcode_position = 0
        self.setProgress(0)
        self._gcode = []

        # Turn of temperatures
        self._sendCommand("M140 S0")
        self._sendCommand("M104 S0")
        self._is_printing = False

    ##  Check if the process did not encounter an error yet.
    def hasError(self):
        return self._error_state != None

    ##  private read line used by printer connection to listen for data on serial port.
    def _readline(self):
        if self._serial is None:
            return None
        try:
            ret = self._serial.readline()
        except Exception as e:
            Logger.log("e","Unexpected error while reading serial port. %s" %e)
            self._setErrorState("Printer has been disconnected") 
            self.close()
            return None
        return ret

    ##  Create a list of baud rates at which we can communicate.
    #   \return list of int
    def _getBaudrateList(self):
        ret = [115200, 250000, 230400, 57600, 38400, 19200, 9600]
        return ret

    def _onFirmwareUpdateComplete(self):
        self._update_firmware_thread.join()
        self._update_firmware_thread = threading.Thread(target= self._updateFirmware)
        self._update_firmware_thread.daemon = True

        self.connect()
Exemplo n.º 41
0
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
Exemplo n.º 42
0
class MachineAction(QObject, PluginObject):

    ##  Create a new Machine action.
    #   \param key unique key of the machine action
    #   \param label Human readable label used to identify the machine action.
    def __init__(self, key, label=""):
        super().__init__()
        self._key = key
        self._label = label
        self._qml_url = ""

        self._component = None
        self._context = None
        self._view = None
        self._finished = False

    labelChanged = pyqtSignal()
    onFinished = pyqtSignal()

    def getKey(self):
        return self._key

    @pyqtProperty(str, notify=labelChanged)
    def label(self):
        return self._label

    def setLabel(self, label):
        if self._label != label:
            self._label = label
            self.labelChanged.emit()

    ##  Reset the action to it's default state.
    #   This should not be re-implemented by child classes, instead re-implement _reset.
    #   /sa _reset
    @pyqtSlot()
    def reset(self):
        self._component = None
        self._finished = False
        self._reset()

    ##  Protected implementation of reset.
    #   /sa reset()
    def _reset(self):
        pass

    @pyqtSlot()
    def setFinished(self):
        self._finished = True
        self._reset()
        self.onFinished.emit()

    @pyqtProperty(bool, notify=onFinished)
    def finished(self):
        return self._finished

    ##  Protected helper to create a view object based on provided QML.
    def _createViewFromQML(self):
        path = QUrl.fromLocalFile(
            os.path.join(
                PluginRegistry.getInstance().getPluginPath(self.getPluginId()),
                self._qml_url))
        self._component = QQmlComponent(Application.getInstance()._engine,
                                        path)
        self._context = QQmlContext(
            Application.getInstance()._engine.rootContext())
        self._context.setContextProperty("manager", self)
        self._view = self._component.create(self._context)
        if self._view is None:
            Logger.log("c", "QQmlComponent status %s",
                       self._component.status())
            Logger.log("c", "QQmlComponent error string %s",
                       self._component.errorString())

    @pyqtProperty(QObject, constant=True)
    def displayItem(self):
        if not self._component:
            self._createViewFromQML()

        return self._view
Exemplo n.º 43
0
class MachineAction(QObject, PluginObject):

    ##  Create a new Machine action.
    #   \param key unique key of the machine action
    #   \param label Human readable label used to identify the machine action.
    def __init__(self, key, label = ""):
        super().__init__()
        self._key = key
        self._label = label
        self._qml_url = ""

        self._component = None
        self._context = None
        self._view = None
        self._finished = False

    labelChanged = pyqtSignal()
    onFinished = pyqtSignal()

    def getKey(self):
        return self._key

    @pyqtProperty(str, notify = labelChanged)
    def label(self):
        return self._label

    def setLabel(self, label):
        if self._label != label:
            self._label = label
            self.labelChanged.emit()

    ##  Reset the action to it's default state.
    #   This should not be re-implemented by child classes, instead re-implement _reset.
    #   /sa _reset
    @pyqtSlot()
    def reset(self):
        self._component = None
        self._finished = False
        self._reset()

    ##  Protected implementation of reset.
    #   /sa reset()
    def _reset(self):
        pass

    @pyqtSlot()
    def setFinished(self):
        self._finished = True
        self._reset()
        self.onFinished.emit()

    @pyqtProperty(bool, notify = onFinished)
    def finished(self):
        return self._finished

    ##  Protected helper to create a view object based on provided QML.
    def _createViewFromQML(self):
        path = QUrl.fromLocalFile(os.path.join(PluginRegistry.getInstance().getPluginPath(self.getPluginId()), self._qml_url))
        self._component = QQmlComponent(Application.getInstance()._engine, path)
        self._context = QQmlContext(Application.getInstance()._engine.rootContext())
        self._context.setContextProperty("manager", self)
        self._view = self._component.create(self._context)
        if self._view is None:
            Logger.log("c", "QQmlComponent status %s", self._component.status())
            Logger.log("c", "QQmlComponent error string %s", self._component.errorString())

    @pyqtProperty(QObject, constant = True)
    def displayItem(self):
        if not self._component:
            self._createViewFromQML()

        return self._view
Exemplo n.º 44
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
Exemplo n.º 45
0
class DiscoverUM3Action(MachineAction):
    def __init__(self):
        super().__init__("DiscoverUM3Action", catalog.i18nc("@action", "Connect via Network"))
        self._qml_url = "DiscoverUM3Action.qml"

        self._network_plugin = None

        self.__additional_components_context = None
        self.__additional_component = None
        self.__additional_components_view = None

        Application.getInstance().engineCreatedSignal.connect(self._createAdditionalComponentsView)

        self._last_zeroconf_event_time = time.time()
        self._zeroconf_change_grace_period = (
            0.25
        )  # Time to wait after a zeroconf service change before allowing a zeroconf reset

    printersChanged = pyqtSignal()

    @pyqtSlot()
    def startDiscovery(self):
        if not self._network_plugin:
            self._network_plugin = (
                Application.getInstance().getOutputDeviceManager().getOutputDevicePlugin("UM3NetworkPrinting")
            )
            self._network_plugin.printerListChanged.connect(self._onPrinterDiscoveryChanged)
            self.printersChanged.emit()

    ##  Re-filters the list of printers.
    @pyqtSlot()
    def reset(self):
        self.printersChanged.emit()

    @pyqtSlot()
    def restartDiscovery(self):
        # Ensure that there is a bit of time after a printer has been discovered.
        # This is a work around for an issue with Qt 5.5.1 up to Qt 5.7 which can segfault if we do this too often.
        # It's most likely that the QML engine is still creating delegates, where the python side already deleted or
        # garbage collected the data.
        # Whatever the case, waiting a bit ensures that it doesn't crash.
        if time.time() - self._last_zeroconf_event_time > self._zeroconf_change_grace_period:
            if not self._network_plugin:
                self.startDiscovery()
            else:
                self._network_plugin.startDiscovery()

    @pyqtSlot(str, str)
    def removeManualPrinter(self, key, address):
        if not self._network_plugin:
            return

        self._network_plugin.removeManualPrinter(key, address)

    @pyqtSlot(str, str)
    def setManualPrinter(self, key, address):
        if key != "":
            # This manual printer replaces a current manual printer
            self._network_plugin.removeManualPrinter(key)

        if address != "":
            self._network_plugin.addManualPrinter(address)

    def _onPrinterDiscoveryChanged(self, *args):
        self._last_zeroconf_event_time = time.time()
        self.printersChanged.emit()

    @pyqtProperty("QVariantList", notify=printersChanged)
    def foundDevices(self):
        if self._network_plugin:
            if Application.getInstance().getGlobalContainerStack():
                global_printer_type = Application.getInstance().getGlobalContainerStack().getBottom().getId()
            else:
                global_printer_type = "unknown"

            printers = list(self._network_plugin.getPrinters().values())
            # TODO; There are still some testing printers that don't have a correct printer type, so don't filter out unkown ones just yet.
            printers = [
                printer
                for printer in printers
                if printer.printerType == global_printer_type or printer.printerType == "unknown"
            ]
            printers.sort(key=lambda k: k.name)
            return printers
        else:
            return []

    @pyqtSlot(str)
    def setKey(self, key):
        global_container_stack = Application.getInstance().getGlobalContainerStack()
        if global_container_stack:
            meta_data = global_container_stack.getMetaData()
            if "um_network_key" in meta_data:
                global_container_stack.setMetaDataEntry("um_network_key", key)
                # Delete old authentication data.
                global_container_stack.removeMetaDataEntry("network_authentication_id")
                global_container_stack.removeMetaDataEntry("network_authentication_key")
            else:
                global_container_stack.addMetaDataEntry("um_network_key", key)

        if self._network_plugin:
            # Ensure that the connection states are refreshed.
            self._network_plugin.reCheckConnections()

    @pyqtSlot(result=str)
    def getStoredKey(self):
        global_container_stack = Application.getInstance().getGlobalContainerStack()
        if global_container_stack:
            meta_data = global_container_stack.getMetaData()
            if "um_network_key" in meta_data:
                return global_container_stack.getMetaDataEntry("um_network_key")

        return ""

    @pyqtSlot()
    def loadConfigurationFromPrinter(self):
        machine_manager = Application.getInstance().getMachineManager()
        hotend_ids = machine_manager.printerOutputDevices[0].hotendIds
        for index in range(len(hotend_ids)):
            machine_manager.printerOutputDevices[0].hotendIdChanged.emit(index, hotend_ids[index])
        material_ids = machine_manager.printerOutputDevices[0].materialIds
        for index in range(len(material_ids)):
            machine_manager.printerOutputDevices[0].materialIdChanged.emit(index, material_ids[index])

    def _createAdditionalComponentsView(self):
        Logger.log("d", "Creating additional ui components for UM3.")
        path = QUrl.fromLocalFile(
            os.path.join(PluginRegistry.getInstance().getPluginPath("UM3NetworkPrinting"), "UM3InfoComponents.qml")
        )
        self.__additional_component = QQmlComponent(Application.getInstance()._engine, path)

        # We need access to engine (although technically we can't)
        self.__additional_components_context = QQmlContext(Application.getInstance()._engine.rootContext())
        self.__additional_components_context.setContextProperty("manager", self)

        self.__additional_components_view = self.__additional_component.create(self.__additional_components_context)
        if not self.__additional_components_view:
            Logger.log("w", "Could not create ui components for UM3.")
            return

        Application.getInstance().addAdditionalComponent(
            "monitorButtons", self.__additional_components_view.findChild(QObject, "networkPrinterConnectButton")
        )
        Application.getInstance().addAdditionalComponent(
            "machinesDetailPane", self.__additional_components_view.findChild(QObject, "networkPrinterConnectionInfo")
        )
Exemplo n.º 46
0
    def load(self, path: str, is_first_call: bool = True) -> None:
        if path == self._path:
            return

        with open(os.path.join(path, "theme.json"), encoding = "utf-8") as f:
            Logger.log("d", "Loading theme file: %s", os.path.join(path, "theme.json"))
            data = json.load(f)

        # Iteratively load inherited themes
        try:
            theme_id = data["metadata"]["inherits"]
            self.load(Resources.getPath(Resources.Themes, theme_id), is_first_call = False)
        except FileNotFoundError:
            Logger.log("e", "Could not find inherited theme %s", theme_id)
        except KeyError:
            pass  # No metadata or no inherits keyword in the theme.json file

        if "colors" in data:
            for name, color in data["colors"].items():
                c = QColor(color[0], color[1], color[2], color[3])
                self._colors[name] = c

        fonts_dir = os.path.join(path, "fonts")
        if os.path.isdir(fonts_dir):
            for file in os.listdir(fonts_dir):
                if "ttf" in file:
                    QFontDatabase.addApplicationFont(os.path.join(fonts_dir, file))

        if "fonts" in data:
            system_font_size = QCoreApplication.instance().font().pointSize()
            for name, font in data["fonts"].items():
                q_font = QFont()
                q_font.setFamily(font.get("family", QCoreApplication.instance().font().family()))

                if font.get("bold"):
                    q_font.setBold(font.get("bold", False))
                else:
                    q_font.setWeight(font.get("weight", 50))

                q_font.setLetterSpacing(QFont.AbsoluteSpacing, font.get("letterSpacing", 0))
                q_font.setItalic(font.get("italic", False))
                q_font.setPointSize(int(font.get("size", 1) * system_font_size))
                q_font.setCapitalization(QFont.AllUppercase if font.get("capitalize", False) else QFont.MixedCase)

                self._fonts[name] = q_font

        if "sizes" in data:
            for name, size in data["sizes"].items():
                s = QSizeF()
                s.setWidth(round(size[0] * self._em_width))
                s.setHeight(round(size[1] * self._em_height))

                self._sizes[name] = s

        iconsdir = os.path.join(path, "icons")
        if os.path.isdir(iconsdir):
            for icon in os.listdir(iconsdir):
                name = os.path.splitext(icon)[0]
                self._icons[name] = QUrl.fromLocalFile(os.path.join(iconsdir, icon))

        imagesdir = os.path.join(path, "images")
        if os.path.isdir(imagesdir):
            for image in os.listdir(imagesdir):
                name = os.path.splitext(image)[0]
                self._images[name] = QUrl.fromLocalFile(os.path.join(imagesdir, image))

        styles = os.path.join(path, "styles.qml")
        if os.path.isfile(styles):
            c = QQmlComponent(self._engine, styles)
            context = QQmlContext(self._engine, self._engine)
            context.setContextProperty("Theme", self)
            self._styles = c.create(context)

            if c.isError():
                for error in c.errors():
                    Logger.log("e", error.toString())

        Logger.log("d", "Loaded theme %s", path)
        self._path = path

        # only emit the theme loaded signal once after all the themes in the inheritance chain have been loaded
        if is_first_call:
            self.themeLoaded.emit()
Exemplo n.º 47
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

        ## 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
Exemplo n.º 48
0
class MachineAction(QObject, PluginObject):
    def __init__(self, key, label = ""):
        super().__init__()
        self._key = key
        self._label = label
        self._qml_url = ""

        self._component = None
        self._context = None
        self._view = None
        self._finished = False

    labelChanged = pyqtSignal()
    onFinished = pyqtSignal()

    def getKey(self):
        return self._key

    @pyqtProperty(str, notify = labelChanged)
    def label(self):
        return self._label

    def setLabel(self, label):
        if self._label != label:
            self._label = label
            self.labelChanged.emit()

    ##  Reset the action to it's default state.
    #   This should not be re-implemented by child classes, instead re-implement _reset.
    #   /sa _reset
    @pyqtSlot()
    def reset(self):
        self._finished = False
        self._reset()

    ##  Protected implementation of reset.
    #   /sa reset()
    def _reset(self):
        pass

    @pyqtSlot()
    def setFinished(self):
        self._finished = True
        self._reset()
        self.onFinished.emit()

    @pyqtProperty(bool, notify = onFinished)
    def finished(self):
        return self._finished

    def _createViewFromQML(self):
        path = QUrl.fromLocalFile(
            os.path.join(PluginRegistry.getInstance().getPluginPath(self.getPluginId()), self._qml_url))
        self._component = QQmlComponent(Application.getInstance()._engine, path)
        self._context = QQmlContext(Application.getInstance()._engine.rootContext())
        self._context.setContextProperty("manager", self)
        self._view = self._component.create(self._context)

    @pyqtProperty(QObject, constant = True)
    def displayItem(self):
        if not self._component:
            self._createViewFromQML()

        return self._view
Exemplo n.º 49
0
class PrinterOutputDevice(QObject, OutputDevice):
    def __init__(self, device_id, parent = None):
        super().__init__(device_id = device_id, parent = parent)

        self._container_registry = ContainerRegistry.getInstance()
        self._target_bed_temperature = 0
        self._bed_temperature = 0
        self._num_extruders = 1
        self._hotend_temperatures = [0] * self._num_extruders
        self._target_hotend_temperatures = [0] * self._num_extruders
        self._material_ids = [""] * self._num_extruders
        self._hotend_ids = [""] * self._num_extruders
        self._progress = 0
        self._head_x = 0
        self._head_y = 0
        self._head_z = 0
        self._connection_state = ConnectionState.closed
        self._connection_text = ""
        self._time_elapsed = 0
        self._time_total = 0
        self._job_state = ""
        self._job_name = ""
        self._error_text = ""
        self._accepts_commands = True
        self._preheat_bed_timeout = 900  # Default time-out for pre-heating the bed, in seconds.
        self._preheat_bed_timer = QTimer()  # Timer that tracks how long to preheat still.
        self._preheat_bed_timer.setSingleShot(True)
        self._preheat_bed_timer.timeout.connect(self.cancelPreheatBed)

        self._printer_state = ""
        self._printer_type = "unknown"

        self._camera_active = False

        self._monitor_view_qml_path = ""
        self._monitor_component = None
        self._monitor_item = None
        self._qml_context = None

    def requestWrite(self, nodes, file_name = None, filter_by_machine = False, file_handler = None):
        raise NotImplementedError("requestWrite needs to be implemented")

    ## Signals

    # Signal to be emitted when bed temp is changed
    bedTemperatureChanged = pyqtSignal()

    # Signal to be emitted when target bed temp is changed
    targetBedTemperatureChanged = pyqtSignal()

    # Signal when the progress is changed (usually when this output device is printing / sending lots of data)
    progressChanged = pyqtSignal()

    # Signal to be emitted when hotend temp is changed
    hotendTemperaturesChanged = pyqtSignal()

    # Signal to be emitted when target hotend temp is changed
    targetHotendTemperaturesChanged = pyqtSignal()

    # Signal to be emitted when head position is changed (x,y,z)
    headPositionChanged = pyqtSignal()

    # Signal to be emitted when either of the material ids is changed
    materialIdChanged = pyqtSignal(int, str, arguments = ["index", "id"])

    # Signal to be emitted when either of the hotend ids is changed
    hotendIdChanged = pyqtSignal(int, str, arguments = ["index", "id"])

    # Signal that is emitted every time connection state is changed.
    # it also sends it's own device_id (for convenience sake)
    connectionStateChanged = pyqtSignal(str)

    connectionTextChanged = pyqtSignal()

    timeElapsedChanged = pyqtSignal()

    timeTotalChanged = pyqtSignal()

    jobStateChanged = pyqtSignal()

    jobNameChanged = pyqtSignal()

    errorTextChanged = pyqtSignal()

    acceptsCommandsChanged = pyqtSignal()

    printerStateChanged = pyqtSignal()

    printerTypeChanged = pyqtSignal()

    # Signal to be emitted when some drastic change occurs in the remaining time (not when the time just passes on normally).
    preheatBedRemainingTimeChanged = pyqtSignal()

    @pyqtProperty(QObject, constant=True)
    def monitorItem(self):
        # Note that we specifically only check if the monitor component is created.
        # It could be that it failed to actually create the qml item! If we check if the item was created, it will try to
        # create the item (and fail) every time.
        if not self._monitor_component:
            self._createMonitorViewFromQML()

        return self._monitor_item

    def _createMonitorViewFromQML(self):
        path = QUrl.fromLocalFile(self._monitor_view_qml_path)

        # Because of garbage collection we need to keep this referenced by python.
        self._monitor_component = QQmlComponent(Application.getInstance()._engine, path)

        # Check if the context was already requested before (Printer output device might have multiple items in the future)
        if self._qml_context is None:
            self._qml_context = QQmlContext(Application.getInstance()._engine.rootContext())
            self._qml_context.setContextProperty("OutputDevice", self)

        self._monitor_item = self._monitor_component.create(self._qml_context)
        if self._monitor_item is None:
            Logger.log("e", "QQmlComponent status %s", self._monitor_component.status())
            Logger.log("e", "QQmlComponent error string %s", self._monitor_component.errorString())

    @pyqtProperty(str, notify=printerTypeChanged)
    def printerType(self):
        return self._printer_type

    @pyqtProperty(str, notify=printerStateChanged)
    def printerState(self):
        return self._printer_state

    @pyqtProperty(str, notify = jobStateChanged)
    def jobState(self):
        return self._job_state

    def _updatePrinterType(self, printer_type):
        if self._printer_type != printer_type:
            self._printer_type = printer_type
            self.printerTypeChanged.emit()

    def _updatePrinterState(self, printer_state):
        if self._printer_state != printer_state:
            self._printer_state = printer_state
            self.printerStateChanged.emit()

    def _updateJobState(self, job_state):
        if self._job_state != job_state:
            self._job_state = job_state
            self.jobStateChanged.emit()

    @pyqtSlot(str)
    def setJobState(self, job_state):
        self._setJobState(job_state)

    def _setJobState(self, job_state):
        Logger.log("w", "_setJobState is not implemented by this output device")

    @pyqtSlot()
    def startCamera(self):
        self._camera_active = True
        self._startCamera()

    def _startCamera(self):
        Logger.log("w", "_startCamera is not implemented by this output device")

    @pyqtSlot()
    def stopCamera(self):
        self._camera_active = False
        self._stopCamera()

    def _stopCamera(self):
        Logger.log("w", "_stopCamera is not implemented by this output device")

    @pyqtProperty(str, notify = jobNameChanged)
    def jobName(self):
        return self._job_name

    def setJobName(self, name):
        if self._job_name != name:
            self._job_name = name
            self.jobNameChanged.emit()

    ##  Gives a human-readable address where the device can be found.
    @pyqtProperty(str, constant = True)
    def address(self):
        Logger.log("w", "address is not implemented by this output device.")

    ##  A human-readable name for the device.
    @pyqtProperty(str, constant = True)
    def name(self):
        Logger.log("w", "name is not implemented by this output device.")
        return ""

    @pyqtProperty(str, notify = errorTextChanged)
    def errorText(self):
        return self._error_text

    ##  Set the error-text that is shown in the print monitor in case of an error
    def setErrorText(self, error_text):
        if self._error_text != error_text:
            self._error_text = error_text
            self.errorTextChanged.emit()

    @pyqtProperty(bool, notify = acceptsCommandsChanged)
    def acceptsCommands(self):
        return self._accepts_commands

    ##  Set a flag to signal the UI that the printer is not (yet) ready to receive commands
    def setAcceptsCommands(self, accepts_commands):
        if self._accepts_commands != accepts_commands:
            self._accepts_commands = accepts_commands
            self.acceptsCommandsChanged.emit()

    ##  Get the bed temperature of the bed (if any)
    #   This function is "final" (do not re-implement)
    #   /sa _getBedTemperature implementation function
    @pyqtProperty(float, notify = bedTemperatureChanged)
    def bedTemperature(self):
        return self._bed_temperature

    ##  Set the (target) bed temperature
    #   This function is "final" (do not re-implement)
    #   /param temperature new target temperature of the bed (in deg C)
    #   /sa _setTargetBedTemperature implementation function
    @pyqtSlot(int)
    def setTargetBedTemperature(self, temperature):
        self._setTargetBedTemperature(temperature)
        if self._target_bed_temperature != temperature:
            self._target_bed_temperature = temperature
            self.targetBedTemperatureChanged.emit()

    ##  The total duration of the time-out to pre-heat the bed, in seconds.
    #
    #   \return The duration of the time-out to pre-heat the bed, in seconds.
    @pyqtProperty(int, constant = True)
    def preheatBedTimeout(self):
        return self._preheat_bed_timeout

    ##  The remaining duration of the pre-heating of the bed.
    #
    #   This is formatted in M:SS format.
    #   \return The duration of the time-out to pre-heat the bed, formatted.
    @pyqtProperty(str, notify = preheatBedRemainingTimeChanged)
    def preheatBedRemainingTime(self):
        if not self._preheat_bed_timer.isActive():
            return ""
        period = self._preheat_bed_timer.remainingTime()
        if period <= 0:
            return ""
        minutes, period = divmod(period, 60000) #60000 milliseconds in a minute.
        seconds, _ = divmod(period, 1000) #1000 milliseconds in a second.
        if minutes <= 0 and seconds <= 0:
            return ""
        return "%d:%02d" % (minutes, seconds)

    ## Time the print has been printing.
    #  Note that timeTotal - timeElapsed should give time remaining.
    @pyqtProperty(float, notify = timeElapsedChanged)
    def timeElapsed(self):
        return self._time_elapsed

    ## Total time of the print
    #  Note that timeTotal - timeElapsed should give time remaining.
    @pyqtProperty(float, notify=timeTotalChanged)
    def timeTotal(self):
        return self._time_total

    @pyqtSlot(float)
    def setTimeTotal(self, new_total):
        if self._time_total != new_total:
            self._time_total = new_total
            self.timeTotalChanged.emit()

    @pyqtSlot(float)
    def setTimeElapsed(self, time_elapsed):
        if self._time_elapsed != time_elapsed:
            self._time_elapsed = time_elapsed
            self.timeElapsedChanged.emit()

    ##  Home the head of the connected printer
    #   This function is "final" (do not re-implement)
    #   /sa _homeHead implementation function
    @pyqtSlot()
    def homeHead(self):
        self._homeHead()

    ##  Home the head of the connected printer
    #   This is an implementation function and should be overriden by children.
    def _homeHead(self):
        Logger.log("w", "_homeHead is not implemented by this output device")

    ##  Home the bed of the connected printer
    #   This function is "final" (do not re-implement)
    #   /sa _homeBed implementation function
    @pyqtSlot()
    def homeBed(self):
        self._homeBed()

    ##  Home the bed of the connected printer
    #   This is an implementation function and should be overriden by children.
    #   /sa homeBed
    def _homeBed(self):
        Logger.log("w", "_homeBed is not implemented by this output device")

    ##  Protected setter for the bed temperature of the connected printer (if any).
    #   /parameter temperature Temperature bed needs to go to (in deg celsius)
    #   /sa setTargetBedTemperature
    def _setTargetBedTemperature(self, temperature):
        Logger.log("w", "_setTargetBedTemperature is not implemented by this output device")

    ##  Pre-heats the heated bed of the printer.
    #
    #   \param temperature The temperature to heat the bed to, in degrees
    #   Celsius.
    #   \param duration How long the bed should stay warm, in seconds.
    @pyqtSlot(float, float)
    def preheatBed(self, temperature, duration):
        Logger.log("w", "preheatBed is not implemented by this output device.")

    ##  Cancels pre-heating the heated bed of the printer.
    #
    #   If the bed is not pre-heated, nothing happens.
    @pyqtSlot()
    def cancelPreheatBed(self):
        Logger.log("w", "cancelPreheatBed is not implemented by this output device.")

    ##  Protected setter for the current bed temperature.
    #   This simply sets the bed temperature, but ensures that a signal is emitted.
    #   /param temperature temperature of the bed.
    def _setBedTemperature(self, temperature):
        if self._bed_temperature != temperature:
            self._bed_temperature = temperature
            self.bedTemperatureChanged.emit()

    ##  Get the target bed temperature if connected printer (if any)
    @pyqtProperty(int, notify = targetBedTemperatureChanged)
    def targetBedTemperature(self):
        return self._target_bed_temperature

    ##  Set the (target) hotend temperature
    #   This function is "final" (do not re-implement)
    #   /param index the index of the hotend that needs to change temperature
    #   /param temperature The temperature it needs to change to (in deg celsius).
    #   /sa _setTargetHotendTemperature implementation function
    @pyqtSlot(int, int)
    def setTargetHotendTemperature(self, index, temperature):
        self._setTargetHotendTemperature(index, temperature)

        if self._target_hotend_temperatures[index] != temperature:
            self._target_hotend_temperatures[index] = temperature
            self.targetHotendTemperaturesChanged.emit()

    ##  Implementation function of setTargetHotendTemperature.
    #   /param index Index of the hotend to set the temperature of
    #   /param temperature Temperature to set the hotend to (in deg C)
    #   /sa setTargetHotendTemperature
    def _setTargetHotendTemperature(self, index, temperature):
        Logger.log("w", "_setTargetHotendTemperature is not implemented by this output device")

    @pyqtProperty("QVariantList", notify = targetHotendTemperaturesChanged)
    def targetHotendTemperatures(self):
        return self._target_hotend_temperatures

    @pyqtProperty("QVariantList", notify = hotendTemperaturesChanged)
    def hotendTemperatures(self):
        return self._hotend_temperatures

    ##  Protected setter for the current hotend temperature.
    #   This simply sets the hotend temperature, but ensures that a signal is emitted.
    #   /param index Index of the hotend
    #   /param temperature temperature of the hotend (in deg C)
    def _setHotendTemperature(self, index, temperature):
        if self._hotend_temperatures[index] != temperature:
            self._hotend_temperatures[index] = temperature
            self.hotendTemperaturesChanged.emit()

    @pyqtProperty("QVariantList", notify = materialIdChanged)
    def materialIds(self):
        return self._material_ids

    @pyqtProperty("QVariantList", notify = materialIdChanged)
    def materialNames(self):
        result = []
        for material_id in self._material_ids:
            if material_id is None:
                result.append(i18n_catalog.i18nc("@item:material", "No material loaded"))
                continue

            containers = self._container_registry.findInstanceContainers(type = "material", GUID = material_id)
            if containers:
                result.append(containers[0].getName())
            else:
                result.append(i18n_catalog.i18nc("@item:material", "Unknown material"))
        return result

    ##  List of the colours of the currently loaded materials.
    #
    #   The list is in order of extruders. If there is no material in an
    #   extruder, the colour is shown as transparent.
    #
    #   The colours are returned in hex-format AARRGGBB or RRGGBB
    #   (e.g. #800000ff for transparent blue or #00ff00 for pure green).
    @pyqtProperty("QVariantList", notify = materialIdChanged)
    def materialColors(self):
        result = []
        for material_id in self._material_ids:
            if material_id is None:
                result.append("#00000000") #No material.
                continue

            containers = self._container_registry.findInstanceContainers(type = "material", GUID = material_id)
            if containers:
                result.append(containers[0].getMetaDataEntry("color_code"))
            else:
                result.append("#00000000") #Unknown material.
        return result

    ##  Protected setter for the current material id.
    #   /param index Index of the extruder
    #   /param material_id id of the material
    def _setMaterialId(self, index, material_id):
        if material_id and material_id != "" and material_id != self._material_ids[index]:
            Logger.log("d", "Setting material id of hotend %d to %s" % (index, material_id))
            self._material_ids[index] = material_id
            self.materialIdChanged.emit(index, material_id)

    @pyqtProperty("QVariantList", notify = hotendIdChanged)
    def hotendIds(self):
        return self._hotend_ids

    ##  Protected setter for the current hotend id.
    #   /param index Index of the extruder
    #   /param hotend_id id of the hotend
    def _setHotendId(self, index, hotend_id):
        if hotend_id and hotend_id != self._hotend_ids[index]:
            Logger.log("d", "Setting hotend id of hotend %d to %s" % (index, hotend_id))
            self._hotend_ids[index] = hotend_id
            self.hotendIdChanged.emit(index, hotend_id)
        elif not hotend_id:
            Logger.log("d", "Removing hotend id of hotend %d.", index)
            self._hotend_ids[index] = None
            self.hotendIdChanged.emit(index, None)

    ##  Let the user decide if the hotends and/or material should be synced with the printer
    #   NB: the UX needs to be implemented by the plugin
    def materialHotendChangedMessage(self, callback):
        Logger.log("w", "materialHotendChangedMessage needs to be implemented, returning 'Yes'")
        callback(QMessageBox.Yes)

    ##  Attempt to establish connection
    def connect(self):
        raise NotImplementedError("connect needs to be implemented")

    ##  Attempt to close the connection
    def close(self):
        raise NotImplementedError("close needs to be implemented")

    @pyqtProperty(bool, notify = connectionStateChanged)
    def connectionState(self):
        return self._connection_state

    ##  Set the connection state of this output device.
    #   /param connection_state ConnectionState enum.
    def setConnectionState(self, connection_state):
        if self._connection_state != connection_state:
            self._connection_state = connection_state
            self.connectionStateChanged.emit(self._id)

    @pyqtProperty(str, notify = connectionTextChanged)
    def connectionText(self):
        return self._connection_text

    ##  Set a text that is shown on top of the print monitor tab
    def setConnectionText(self, connection_text):
        if self._connection_text != connection_text:
            self._connection_text = connection_text
            self.connectionTextChanged.emit()

    ##  Ensure that close gets called when object is destroyed
    def __del__(self):
        self.close()

    ##  Get the x position of the head.
    #   This function is "final" (do not re-implement)
    @pyqtProperty(float, notify = headPositionChanged)
    def headX(self):
        return self._head_x

    ##  Get the y position of the head.
    #   This function is "final" (do not re-implement)
    @pyqtProperty(float, notify = headPositionChanged)
    def headY(self):
        return self._head_y

    ##  Get the z position of the head.
    #   In some machines it's actually the bed that moves. For convenience sake we simply see it all as head movements.
    #   This function is "final" (do not re-implement)
    @pyqtProperty(float, notify = headPositionChanged)
    def headZ(self):
        return self._head_z

    ##  Update the saved position of the head
    #   This function should be called when a new position for the head is received.
    def _updateHeadPosition(self, x, y ,z):
        position_changed = False
        if self._head_x != x:
            self._head_x = x
            position_changed = True
        if self._head_y != y:
            self._head_y = y
            position_changed = True
        if self._head_z != z:
            self._head_z = z
            position_changed = True

        if position_changed:
            self.headPositionChanged.emit()

    ##  Set the position of the head.
    #   In some machines it's actually the bed that moves. For convenience sake we simply see it all as head movements.
    #   This function is "final" (do not re-implement)
    #   /param x new x location of the head.
    #   /param y new y location of the head.
    #   /param z new z location of the head.
    #   /param speed Speed by which it needs to move (in mm/minute)
    #   /sa _setHeadPosition implementation function
    @pyqtSlot("long", "long", "long")
    @pyqtSlot("long", "long", "long", "long")
    def setHeadPosition(self, x, y, z, speed = 3000):
        self._setHeadPosition(x, y , z, speed)

    ##  Set the X position of the head.
    #   This function is "final" (do not re-implement)
    #   /param x x position head needs to move to.
    #   /param speed Speed by which it needs to move (in mm/minute)
    #   /sa _setHeadx implementation function
    @pyqtSlot("long")
    @pyqtSlot("long", "long")
    def setHeadX(self, x, speed = 3000):
        self._setHeadX(x, speed)

    ##  Set the Y position of the head.
    #   This function is "final" (do not re-implement)
    #   /param y y position head needs to move to.
    #   /param speed Speed by which it needs to move (in mm/minute)
    #   /sa _setHeadY implementation function
    @pyqtSlot("long")
    @pyqtSlot("long", "long")
    def setHeadY(self, y, speed = 3000):
        self._setHeadY(y, speed)

    ##  Set the Z position of the head.
    #   In some machines it's actually the bed that moves. For convenience sake we simply see it all as head movements.
    #   This function is "final" (do not re-implement)
    #   /param z z position head needs to move to.
    #   /param speed Speed by which it needs to move (in mm/minute)
    #   /sa _setHeadZ implementation function
    @pyqtSlot("long")
    @pyqtSlot("long", "long")
    def setHeadZ(self, z, speed = 3000):
        self._setHeadY(z, speed)

    ##  Move the head of the printer.
    #   Note that this is a relative move. If you want to move the head to a specific position you can use
    #   setHeadPosition
    #   This function is "final" (do not re-implement)
    #   /param x distance in x to move
    #   /param y distance in y to move
    #   /param z distance in z to move
    #   /param speed Speed by which it needs to move (in mm/minute)
    #   /sa _moveHead implementation function
    @pyqtSlot("long", "long", "long")
    @pyqtSlot("long", "long", "long", "long")
    def moveHead(self, x = 0, y = 0, z = 0, speed = 3000):
        self._moveHead(x, y, z, speed)

    ##  Implementation function of moveHead.
    #   /param x distance in x to move
    #   /param y distance in y to move
    #   /param z distance in z to move
    #   /param speed Speed by which it needs to move (in mm/minute)
    #   /sa moveHead
    def _moveHead(self, x, y, z, speed):
        Logger.log("w", "_moveHead is not implemented by this output device")

    ##  Implementation function of setHeadPosition.
    #   /param x new x location of the head.
    #   /param y new y location of the head.
    #   /param z new z location of the head.
    #   /param speed Speed by which it needs to move (in mm/minute)
    #   /sa setHeadPosition
    def _setHeadPosition(self, x, y, z, speed):
        Logger.log("w", "_setHeadPosition is not implemented by this output device")

    ##  Implementation function of setHeadX.
    #   /param x new x location of the head.
    #   /param speed Speed by which it needs to move (in mm/minute)
    #   /sa setHeadX
    def _setHeadX(self, x, speed):
        Logger.log("w", "_setHeadX is not implemented by this output device")

    ##  Implementation function of setHeadY.
    #   /param y new y location of the head.
    #   /param speed Speed by which it needs to move (in mm/minute)
    #   /sa _setHeadY
    def _setHeadY(self, y, speed):
        Logger.log("w", "_setHeadY is not implemented by this output device")

    ##  Implementation function of setHeadZ.
    #   /param z new z location of the head.
    #   /param speed Speed by which it needs to move (in mm/minute)
    #   /sa _setHeadZ
    def _setHeadZ(self, z, speed):
        Logger.log("w", "_setHeadZ is not implemented by this output device")

    ##  Get the progress of any currently active process.
    #   This function is "final" (do not re-implement)
    #   /sa _getProgress
    #   /returns float progress of the process. -1 indicates that there is no process.
    @pyqtProperty(float, notify = progressChanged)
    def progress(self):
        return self._progress

    ##  Set the progress of any currently active process
    #   /param progress Progress of the process.
    def setProgress(self, progress):
        if self._progress != progress:
            self._progress = progress
            self.progressChanged.emit()
Exemplo n.º 50
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
Exemplo n.º 51
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:
            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"
Exemplo n.º 52
0
class WorkspaceDialog(QObject):
    showDialogSignal = pyqtSignal()

    def __init__(self, parent = None):
        super().__init__(parent)
        self._component = None
        self._context = None
        self._view = None
        self._qml_url = "WorkspaceDialog.qml"
        self._lock = threading.Lock()
        self._default_strategy = "override"
        self._result = {"machine": self._default_strategy,
                        "quality_changes": self._default_strategy,
                        "material": self._default_strategy}
        self._visible = False
        self.showDialogSignal.connect(self.__show)

        self._has_quality_changes_conflict = False
        self._has_machine_conflict = False
        self._has_material_conflict = False

    machineConflictChanged = pyqtSignal()
    qualityChangesConflictChanged = pyqtSignal()
    materialConflictChanged = pyqtSignal()

    @pyqtProperty(bool, notify = machineConflictChanged)
    def machineConflict(self):
        return self._has_machine_conflict

    @pyqtProperty(bool, notify=qualityChangesConflictChanged)
    def qualityChangesConflict(self):
        return self._has_quality_changes_conflict

    @pyqtProperty(bool, notify=materialConflictChanged)
    def materialConflict(self):
        return self._has_material_conflict

    @pyqtSlot(str, str)
    def setResolveStrategy(self, key, strategy):
        if key in self._result:
            self._result[key] = strategy

    def setMaterialConflict(self, material_conflict):
        self._has_material_conflict = material_conflict
        self.materialConflictChanged.emit()

    def setMachineConflict(self, machine_conflict):
        self._has_machine_conflict = machine_conflict
        self.machineConflictChanged.emit()

    def setQualityChangesConflict(self, quality_changes_conflict):
        self._has_quality_changes_conflict = quality_changes_conflict
        self.qualityChangesConflictChanged.emit()

    def getResult(self):
        if "machine" in self._result and not self._has_machine_conflict:
            self._result["machine"] = None
        if "quality_changes" in self._result and not self._has_quality_changes_conflict:
            self._result["quality_changes"] = None
        if "material" in self._result and not self._has_material_conflict:
            self._result["material"] = None
        return self._result

    def _createViewFromQML(self):
        path = QUrl.fromLocalFile(os.path.join(PluginRegistry.getInstance().getPluginPath("3MFReader"), self._qml_url))
        self._component = QQmlComponent(Application.getInstance()._engine, path)
        self._context = QQmlContext(Application.getInstance()._engine.rootContext())
        self._context.setContextProperty("manager", self)
        self._view = self._component.create(self._context)
        if self._view is None:
            Logger.log("c", "QQmlComponent status %s", self._component.status())
            Logger.log("c", "QQmlComponent error string %s", self._component.errorString())

    def show(self):
        # Emit signal so the right thread actually shows the view.
        if threading.current_thread() != threading.main_thread():
            self._lock.acquire()
        # Reset the result
        self._result = {"machine": self._default_strategy,
                        "quality_changes": self._default_strategy,
                        "material": self._default_strategy}
        self._visible = True
        self.showDialogSignal.emit()

    @pyqtSlot()
    ##  Used to notify the dialog so the lock can be released.
    def notifyClosed(self):
        if self._result is None:
            self._result = {}
        self._lock.release()

    def hide(self):
        self._visible = False
        self._lock.release()
        self._view.hide()

    @pyqtSlot()
    def onOkButtonClicked(self):
        self._view.hide()
        self.hide()

    @pyqtSlot()
    def onCancelButtonClicked(self):
        self._view.hide()
        self.hide()
        self._result = {}

    ##  Block thread until the dialog is closed.
    def waitForClose(self):
        if self._visible:
            if threading.current_thread() != threading.main_thread():
                self._lock.acquire()
                self._lock.release()
            else:
                # If this is not run from a separate thread, we need to ensure that the events are still processed.
                while self._visible:
                    time.sleep(1 / 50)
                    QCoreApplication.processEvents()  # Ensure that the GUI does not freeze.

    def __show(self):
        if self._view is None:
            self._createViewFromQML()
        if self._view:
            self._view.show()
Exemplo n.º 53
0
class NetworkClusterPrinterOutputDevice(NetworkPrinterOutputDevice.NetworkPrinterOutputDevice):
    printJobsChanged = pyqtSignal()
    printersChanged = pyqtSignal()
    selectedPrinterChanged = pyqtSignal()

    def __init__(self, key, address, properties, api_prefix):
        super().__init__(key, address, properties, api_prefix)
        # Store the address of the master.
        self._master_address = address
        name_property = properties.get(b"name", b"")
        if name_property:
            name = name_property.decode("utf-8")
        else:
            name = key

        self._authentication_state = NetworkPrinterOutputDevice.AuthState.Authenticated  # The printer is always authenticated

        self.setName(name)
        description = i18n_catalog.i18nc("@action:button Preceded by 'Ready to'.", "Print over network")
        self.setShortDescription(description)
        self.setDescription(description)

        self._stage = OutputStage.ready
        host_override = os.environ.get("CLUSTER_OVERRIDE_HOST", "")
        if host_override:
            Logger.log(
                "w",
                "Environment variable CLUSTER_OVERRIDE_HOST is set to [%s], cluster hosts are now set to this host",
                host_override)
            self._host = "http://" + host_override
        else:
            self._host = "http://" + address

        # is the same as in NetworkPrinterOutputDevicePlugin
        self._cluster_api_version = "1"
        self._cluster_api_prefix = "/cluster-api/v" + self._cluster_api_version + "/"
        self._api_base_uri = self._host + self._cluster_api_prefix

        self._file_name = None
        self._progress_message = None
        self._request = None
        self._reply = None

        # The main reason to keep the 'multipart' form data on the object
        # is to prevent the Python GC from claiming it too early.
        self._multipart = None

        self._print_view = None
        self._request_job = []

        self._monitor_view_qml_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "ClusterMonitorItem.qml")
        self._control_view_qml_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "ClusterControlItem.qml")

        self._print_jobs = []
        self._print_job_by_printer_uuid = {}
        self._print_job_by_uuid = {} # Print jobs by their own uuid
        self._printers = []
        self._printers_dict = {}  # by unique_name

        self._connected_printers_type_count = []
        self._automatic_printer = {"unique_name": "", "friendly_name": "Automatic"}  # empty unique_name IS automatic selection
        self._selected_printer = self._automatic_printer

        self._cluster_status_update_timer = QTimer()
        self._cluster_status_update_timer.setInterval(5000)
        self._cluster_status_update_timer.setSingleShot(False)
        self._cluster_status_update_timer.timeout.connect(self._requestClusterStatus)

        self._can_pause = True
        self._can_abort = True
        self._can_pre_heat_bed = False
        self._can_control_manually = False
        self._cluster_size = int(properties.get(b"cluster_size", 0))

        self._cleanupRequest()

        #These are texts that are to be translated for future features.
        temporary_translation = i18n_catalog.i18n("This printer is not set up to host a group of connected Ultimaker 3 printers.")
        temporary_translation2 = i18n_catalog.i18nc("Count is number of printers.", "This printer is the host for a group of {count} connected Ultimaker 3 printers.").format(count = 3)
        temporary_translation3 = i18n_catalog.i18n("{printer_name} has finished printing '{job_name}'. Please collect the print and confirm clearing the build plate.") #When finished.
        temporary_translation4 = i18n_catalog.i18n("{printer_name} is reserved to print '{job_name}'. Please change the printer's configuration to match the job, for it to start printing.") #When configuration changed.

    ##  No authentication, so requestAuthentication should do exactly nothing
    @pyqtSlot()
    def requestAuthentication(self, message_id = None, action_id = "Retry"):
        pass    # Cura Connect doesn't do any authorization

    def setAuthenticationState(self, auth_state):
        self._authentication_state = NetworkPrinterOutputDevice.AuthState.Authenticated  # The printer is always authenticated

    def _verifyAuthentication(self):
        pass

    def _checkAuthentication(self):
        Logger.log("d", "_checkAuthentication Cura Connect - nothing to be done")

    @pyqtProperty(QObject, notify=selectedPrinterChanged)
    def controlItem(self):
        # TODO: Probably not the nicest way to do this. This needs to be done better at some point in time.
        if not self._control_component:
            self._createControlViewFromQML()
        name = self._selected_printer.get("friendly_name")
        if name == self._automatic_printer.get("friendly_name") or name == "":
            return self._control_item
        # Let cura use the default.
        return None

    @pyqtSlot(int, result = str)
    def getTimeCompleted(self, time_remaining):
        current_time = time.time()
        datetime_completed = datetime.datetime.fromtimestamp(current_time + time_remaining)
        return "{hour:02d}:{minute:02d}".format(hour = datetime_completed.hour, minute = datetime_completed.minute)

    @pyqtSlot(int, result = str)
    def getDateCompleted(self, time_remaining):
        current_time = time.time()
        datetime_completed = datetime.datetime.fromtimestamp(current_time + time_remaining)
        return (datetime_completed.strftime("%a %b ") + "{day}".format(day=datetime_completed.day)).upper()

    @pyqtProperty(int, constant = True)
    def clusterSize(self):
        return self._cluster_size

    @pyqtProperty(str, notify=selectedPrinterChanged)
    def name(self):
        # Show the name of the selected printer.
        # This is not the nicest way to do this, but changes to the Cura UI are required otherwise.
        name = self._selected_printer.get("friendly_name")
        if name != self._automatic_printer.get("friendly_name"):
            return name
        # Return name of cluster master.
        return self._properties.get(b"name", b"").decode("utf-8")

    def connect(self):
        super().connect()
        self._cluster_status_update_timer.start()

    def close(self):
        super().close()
        self._cluster_status_update_timer.stop()

    def _setJobState(self, job_state):
        if not self._selected_printer:
            return

        selected_printer_uuid = self._printers_dict[self._selected_printer["unique_name"]]["uuid"]
        if selected_printer_uuid not in self._print_job_by_printer_uuid:
            return

        print_job_uuid = self._print_job_by_printer_uuid[selected_printer_uuid]["uuid"]

        url = QUrl(self._api_base_uri + "print_jobs/" + print_job_uuid + "/action")
        put_request = QNetworkRequest(url)
        put_request.setHeader(QNetworkRequest.ContentTypeHeader, "application/json")
        data = '{"action": "' + job_state + '"}'
        self._manager.put(put_request, data.encode())

    def _requestClusterStatus(self):
        # TODO: Handle timeout. We probably want to know if the cluster is still reachable or not.
        url = QUrl(self._api_base_uri + "printers/")
        printers_request = QNetworkRequest(url)
        self._addUserAgentHeader(printers_request)
        self._manager.get(printers_request)
        # See _finishedPrintersRequest()

        if self._printers:  # if printers is not empty
            url = QUrl(self._api_base_uri + "print_jobs/")
            print_jobs_request = QNetworkRequest(url)
            self._addUserAgentHeader(print_jobs_request)
            self._manager.get(print_jobs_request)
            # See _finishedPrintJobsRequest()

    def _finishedPrintJobsRequest(self, reply):
        try:
            json_data = json.loads(bytes(reply.readAll()).decode("utf-8"))
        except json.decoder.JSONDecodeError:
            Logger.log("w", "Received an invalid print job state message: Not valid JSON.")
            return
        self.setPrintJobs(json_data)

    def _finishedPrintersRequest(self, reply):
        try:
            json_data = json.loads(bytes(reply.readAll()).decode("utf-8"))
        except json.decoder.JSONDecodeError:
            Logger.log("w", "Received an invalid print job state message: Not valid JSON.")
            return
        self.setPrinters(json_data)

    def materialHotendChangedMessage(self, callback):
        # When there is just one printer, the activate configuration option is enabled
        if (self._cluster_size == 1):
            super().materialHotendChangedMessage(callback = callback)

    def _startCameraStream(self):
        ## Request new image
        url = QUrl("http://" + self._printers_dict[self._selected_printer["unique_name"]]["ip_address"] + ":8080/?action=stream")
        self._image_request = QNetworkRequest(url)
        self._addUserAgentHeader(self._image_request)
        self._image_reply = self._manager.get(self._image_request)
        self._image_reply.downloadProgress.connect(self._onStreamDownloadProgress)

    def spawnPrintView(self):
        if self._print_view is None:
            path = QUrl.fromLocalFile(os.path.join(self._plugin_path, "PrintWindow.qml"))
            component = QQmlComponent(Application.getInstance()._engine, path)

            self._print_context = QQmlContext(Application.getInstance()._engine.rootContext())
            self._print_context.setContextProperty("OutputDevice", self)
            self._print_view = component.create(self._print_context)

            if component.isError():
                Logger.log("e", " Errors creating component: \n%s", "\n".join(
                    [e.toString() for e in component.errors()]))

        if self._print_view is not None:
            self._print_view.show()

    ##  Store job info, show Print view for settings
    def requestWrite(self, nodes, file_name=None, filter_by_machine=False, file_handler=None, **kwargs):
        self._selected_printer = self._automatic_printer  # reset to default option
        self._request_job = [nodes, file_name, filter_by_machine, file_handler, kwargs]

        if self._stage != OutputStage.ready:
            if self._error_message:
                self._error_message.hide()
            self._error_message = Message(
                i18n_catalog.i18nc("@info:status",
                                   "Sending new jobs (temporarily) blocked, still sending the previous print job."))
            self._error_message.show()
            return

        if len(self._printers) > 1:
            self.spawnPrintView()  # Ask user how to print it.
        elif len(self._printers) == 1:
            # If there is only one printer, don't bother asking.
            self.selectAutomaticPrinter()
            self.sendPrintJob()
        else:
            # Cluster has no printers, warn the user of this.
            if self._error_message:
                self._error_message.hide()
            self._error_message = Message(
                i18n_catalog.i18nc("@info:status",
                                   "Unable to send new print job: this 3D printer is not (yet) set up to host a group of connected Ultimaker 3 printers."))
            self._error_message.show()

    ##  Actually send the print job, called from the dialog
    #   :param: require_printer_name: name of printer, or ""
    @pyqtSlot()
    def sendPrintJob(self):
        nodes, file_name, filter_by_machine, file_handler, kwargs = self._request_job
        require_printer_name = self._selected_printer["unique_name"]

        self._send_gcode_start = time.time()
        Logger.log("d", "Sending print job [%s] to host..." % file_name)

        if self._stage != OutputStage.ready:
            Logger.log("d", "Unable to send print job as the state is %s", self._stage)
            raise OutputDeviceError.DeviceBusyError()
        self._stage = OutputStage.uploading

        self._file_name = "%s.gcode.gz" % file_name
        self._showProgressMessage()

        new_request = self._buildSendPrintJobHttpRequest(require_printer_name)
        if new_request is None or self._stage != OutputStage.uploading:
            return
        self._request = new_request
        self._reply = self._manager.post(self._request, self._multipart)
        self._reply.uploadProgress.connect(self._onUploadProgress)
        # See _finishedPostPrintJobRequest()

    def _buildSendPrintJobHttpRequest(self, require_printer_name):
        api_url = QUrl(self._api_base_uri + "print_jobs/")
        request = QNetworkRequest(api_url)
        # Create multipart request and add the g-code.
        self._multipart = QHttpMultiPart(QHttpMultiPart.FormDataType)

        # Add gcode
        part = QHttpPart()
        part.setHeader(QNetworkRequest.ContentDispositionHeader,
                       'form-data; name="file"; filename="%s"' % self._file_name)

        gcode = getattr(Application.getInstance().getController().getScene(), "gcode_list")
        compressed_gcode = self._compressGcode(gcode)
        if compressed_gcode is None:
            return None     # User aborted print, so stop trying.

        part.setBody(compressed_gcode)
        self._multipart.append(part)

        # require_printer_name "" means automatic
        if require_printer_name:
            self._multipart.append(self.__createKeyValueHttpPart("require_printer_name", require_printer_name))
        user_name = self.__get_username()
        if user_name is None:
            user_name = "unknown"
        self._multipart.append(self.__createKeyValueHttpPart("owner", user_name))

        self._addUserAgentHeader(request)
        return request

    def _compressGcode(self, gcode):
        self._compressing_print = True
        batched_line = ""
        max_chars_per_line = int(1024 * 1024 / 4)  # 1 / 4  MB

        byte_array_file_data = b""

        def _compressDataAndNotifyQt(data_to_append):
            compressed_data = gzip.compress(data_to_append.encode("utf-8"))
            self._progress_message.setProgress(-1)  # Tickle the message so that it's clear that it's still being used.
            QCoreApplication.processEvents()  # Ensure that the GUI does not freeze.
            # Pretend that this is a response, as zipping might take a bit of time.
            self._last_response_time = time.time()
            return compressed_data

        if gcode is None:
            Logger.log("e", "Unable to find sliced gcode, returning empty.")
            return byte_array_file_data

        for line in gcode:
            if not self._compressing_print:
                self._progress_message.hide()
                return None     # Stop trying to zip, abort was called.
            batched_line += line
            # if the gcode was read from a gcode file, self._gcode will be a list of all lines in that file.
            # Compressing line by line in this case is extremely slow, so we need to batch them.
            if len(batched_line) < max_chars_per_line:
                continue
            byte_array_file_data += _compressDataAndNotifyQt(batched_line)
            batched_line = ""

        # Also compress the leftovers.
        if batched_line:
            byte_array_file_data += _compressDataAndNotifyQt(batched_line)

        return byte_array_file_data

    def __createKeyValueHttpPart(self, key, value):
        metadata_part = QHttpPart()
        metadata_part.setHeader(QNetworkRequest.ContentTypeHeader, 'text/plain')
        metadata_part.setHeader(QNetworkRequest.ContentDispositionHeader, 'form-data; name="%s"' % (key))
        metadata_part.setBody(bytearray(value, "utf8"))
        return metadata_part

    def __get_username(self):
        try:
            return getpass.getuser()
        except:
            Logger.log("d", "Could not get the system user name, returning 'unknown' instead.")
            return None

    def _finishedPrintJobPostRequest(self, reply):
        self._stage = OutputStage.ready
        if self._progress_message:
            self._progress_message.hide()
        self._progress_message = None
        self.writeFinished.emit(self)

        if reply.error():
            self._showRequestFailedMessage(reply)
            self.writeError.emit(self)
        else:
            self._showRequestSucceededMessage()
            self.writeSuccess.emit(self)

        self._cleanupRequest()

    def _showRequestFailedMessage(self, reply):
        if reply is not None:
            Logger.log("w", "Unable to send print job to group {cluster_name}: {error_string} ({error})".format(
                cluster_name = self.getName(),
                error_string = str(reply.errorString()),
                error = str(reply.error())))
            error_message_template = i18n_catalog.i18nc("@info:status", "Unable to send print job to group {cluster_name}.")
            message = Message(text=error_message_template.format(
                cluster_name = self.getName()))
            message.show()

    def _showRequestSucceededMessage(self):
        confirmation_message_template = i18n_catalog.i18nc(
            "@info:status",
            "Sent {file_name} to group {cluster_name}."
        )
        file_name = os.path.basename(self._file_name).split(".")[0]
        message_text = confirmation_message_template.format(cluster_name = self.getName(), file_name = file_name)
        message = Message(text=message_text)
        button_text = i18n_catalog.i18nc("@action:button", "Show print jobs")
        button_tooltip = i18n_catalog.i18nc("@info:tooltip", "Opens the print jobs interface in your browser.")
        message.addAction("open_browser", button_text, "globe", button_tooltip)
        message.actionTriggered.connect(self._onMessageActionTriggered)
        message.show()

    def setPrintJobs(self, print_jobs):
        #TODO: hack, last seen messes up the check, so drop it.
        for job in print_jobs:
            del job["last_seen"]
            # Strip any extensions
            job["name"] = self._removeGcodeExtension(job["name"])

        if self._print_jobs != print_jobs:
            old_print_jobs = self._print_jobs
            self._print_jobs = print_jobs

            self._notifyFinishedPrintJobs(old_print_jobs, print_jobs)
            self._notifyConfigurationChangeRequired(old_print_jobs, print_jobs)

            # Yes, this is a hacky way of doing it, but it's quick and the API doesn't give the print job per printer
            # for some reason. ugh.
            self._print_job_by_printer_uuid = {}
            self._print_job_by_uuid = {}
            for print_job in print_jobs:
                if "printer_uuid" in print_job and print_job["printer_uuid"] is not None:
                    self._print_job_by_printer_uuid[print_job["printer_uuid"]] = print_job
                self._print_job_by_uuid[print_job["uuid"]] = print_job
            self.printJobsChanged.emit()

    def _removeGcodeExtension(self, name):
        parts = name.split(".")
        if parts[-1].upper() == "GZ":
            parts = parts[:-1]
        if parts[-1].upper() == "GCODE":
            parts = parts[:-1]
        return ".".join(parts)

    def _notifyFinishedPrintJobs(self, old_print_jobs, new_print_jobs):
        """Notify the user when any of their print jobs have just completed.

        Arguments:

        old_print_jobs -- the previous list of print job status information as returned by the cluster REST API.
        new_print_jobs -- the current list of print job status information as returned by the cluster REST API.
        """
        if old_print_jobs is None:
            return

        username = self.__get_username()
        if username is None:
            return

        our_old_print_jobs = self.__filterOurPrintJobs(old_print_jobs)
        our_old_not_finished_print_jobs = [pj for pj in our_old_print_jobs if pj["status"] != "wait_cleanup"]

        our_new_print_jobs = self.__filterOurPrintJobs(new_print_jobs)
        our_new_finished_print_jobs = [pj for pj in our_new_print_jobs if pj["status"] == "wait_cleanup"]

        old_not_finished_print_job_uuids = set([pj["uuid"] for pj in our_old_not_finished_print_jobs])

        for print_job in our_new_finished_print_jobs:
            if print_job["uuid"] in old_not_finished_print_job_uuids:

                printer_name = self.__getPrinterNameFromUuid(print_job["printer_uuid"])
                if printer_name is None:
                    printer_name = i18n_catalog.i18nc("@label Printer name", "Unknown")

                message_text = (i18n_catalog.i18nc("@info:status",
                                "Printer '{printer_name}' has finished printing '{job_name}'.")
                                .format(printer_name=printer_name, job_name=print_job["name"]))
                message = Message(text=message_text, title=i18n_catalog.i18nc("@info:status", "Print finished"))
                Application.getInstance().showMessage(message)
                Application.getInstance().showToastMessage(
                    i18n_catalog.i18nc("@info:status", "Print finished"),
                    message_text)

    def __filterOurPrintJobs(self, print_jobs):
        username = self.__get_username()
        return [print_job for print_job in print_jobs if print_job["owner"] == username]

    def _notifyConfigurationChangeRequired(self, old_print_jobs, new_print_jobs):
        if old_print_jobs is None:
            return

        old_change_required_print_jobs = self.__filterConfigChangePrintJobs(self.__filterOurPrintJobs(old_print_jobs))
        new_change_required_print_jobs = self.__filterConfigChangePrintJobs(self.__filterOurPrintJobs(new_print_jobs))
        old_change_required_print_job_uuids = set([pj["uuid"] for pj in old_change_required_print_jobs])

        for print_job in new_change_required_print_jobs:
            if print_job["uuid"] not in old_change_required_print_job_uuids:

                printer_name = self.__getPrinterNameFromUuid(print_job["assigned_to"])
                if printer_name is None:
                    # don't report on yet unknown printers
                    continue

                message_text = (i18n_catalog.i18n("{printer_name} is reserved to print '{job_name}'. Please change the printer's configuration to match the job, for it to start printing.")
                                .format(printer_name=printer_name, job_name=print_job["name"]))
                message = Message(text=message_text, title=i18n_catalog.i18nc("@label:status", "Action required"))
                Application.getInstance().showMessage(message)
                Application.getInstance().showToastMessage(
                    i18n_catalog.i18nc("@label:status", "Action required"),
                    message_text)

    def __filterConfigChangePrintJobs(self, print_jobs):
        return filter(self.__isConfigurationChangeRequiredPrintJob, print_jobs)

    def __isConfigurationChangeRequiredPrintJob(self, print_job):
        if print_job["status"] == "queued":
            changes_required = print_job.get("configuration_changes_required", [])
            return len(changes_required) != 0
        return False

    def __getPrinterNameFromUuid(self, printer_uuid):
        for printer in self._printers:
            if printer["uuid"] == printer_uuid:
                return printer["friendly_name"]
        return None

    def setPrinters(self, printers):
        if self._printers != printers:
            self._connected_printers_type_count = []
            printers_count = {}
            self._printers = printers
            self._printers_dict = dict((p["unique_name"], p) for p in printers)  # for easy lookup by unique_name

            for printer in printers:
                variant = printer["machine_variant"]
                if variant in printers_count:
                    printers_count[variant] += 1
                else:
                    printers_count[variant] = 1
            for type in printers_count:
                self._connected_printers_type_count.append({"machine_type": type, "count": printers_count[type]})
            self.printersChanged.emit()

    @pyqtProperty("QVariantList", notify=printersChanged)
    def connectedPrintersTypeCount(self):
        return self._connected_printers_type_count

    @pyqtProperty("QVariantList", notify=printersChanged)
    def connectedPrinters(self):
        return self._printers

    @pyqtProperty(int, notify=printJobsChanged)
    def numJobsPrinting(self):
        num_jobs_printing = 0
        for job in self._print_jobs:
            if job["status"] in ["printing", "wait_cleanup", "sent_to_printer", "pre_print", "post_print"]:
                num_jobs_printing += 1
        return num_jobs_printing

    @pyqtProperty(int, notify=printJobsChanged)
    def numJobsQueued(self):
        num_jobs_queued = 0
        for job in self._print_jobs:
            if job["status"] == "queued":
                num_jobs_queued += 1
        return num_jobs_queued

    @pyqtProperty("QVariantMap", notify=printJobsChanged)
    def printJobsByUUID(self):
        return self._print_job_by_uuid

    @pyqtProperty("QVariantMap", notify=printJobsChanged)
    def printJobsByPrinterUUID(self):
        return self._print_job_by_printer_uuid

    @pyqtProperty("QVariantList", notify=printJobsChanged)
    def printJobs(self):
        return self._print_jobs

    @pyqtProperty("QVariantList", notify=printersChanged)
    def printers(self):
        return [self._automatic_printer, ] + self._printers

    @pyqtSlot(str, str)
    def selectPrinter(self, unique_name, friendly_name):
        self.stopCamera()
        self._selected_printer = {"unique_name": unique_name, "friendly_name": friendly_name}
        Logger.log("d", "Selected printer: %s %s", friendly_name, unique_name)
        # TODO: Probably not the nicest way to do this. This needs to be done better at some point in time.
        if unique_name == "":
            self._address = self._master_address
        else:
            self._address = self._printers_dict[self._selected_printer["unique_name"]]["ip_address"]

        self.selectedPrinterChanged.emit()

    def _updateJobState(self, job_state):
        name = self._selected_printer.get("friendly_name")
        if name == "" or name == "Automatic":
            # TODO: This is now a bit hacked; If no printer is selected, don't show job state.
            if self._job_state != "":
                self._job_state = ""
                self.jobStateChanged.emit()
        else:
            if self._job_state != job_state:
                self._job_state = job_state
                self.jobStateChanged.emit()

    @pyqtSlot()
    def selectAutomaticPrinter(self):
        self.stopCamera()
        self._selected_printer = self._automatic_printer
        self.selectedPrinterChanged.emit()

    @pyqtProperty("QVariant", notify=selectedPrinterChanged)
    def selectedPrinterName(self):
        return self._selected_printer.get("unique_name", "")

    def getPrintJobsUrl(self):
        return self._host + "/print_jobs"

    def getPrintersUrl(self):
        return self._host + "/printers"

    def _showProgressMessage(self):
        progress_message_template = i18n_catalog.i18nc("@info:progress",
                                               "Sending <filename>{file_name}</filename> to group {cluster_name}")
        file_name = os.path.basename(self._file_name).split(".")[0]
        self._progress_message = Message(progress_message_template.format(file_name = file_name, cluster_name = self.getName()), 0, False, -1)
        self._progress_message.addAction("Abort", i18n_catalog.i18nc("@action:button", "Cancel"), None, "")
        self._progress_message.actionTriggered.connect(self._onMessageActionTriggered)
        self._progress_message.show()

    def _addUserAgentHeader(self, request):
        request.setRawHeader(b"User-agent", b"CuraPrintClusterOutputDevice Plugin")

    def _cleanupRequest(self):
        self._request = None
        self._stage = OutputStage.ready
        self._file_name = None

    def _onFinished(self, reply):
        super()._onFinished(reply)
        reply_url = reply.url().toString()
        status_code = reply.attribute(QNetworkRequest.HttpStatusCodeAttribute)
        if status_code == 500:
            Logger.log("w", "Request to {url} returned a 500.".format(url = reply_url))
            return
        if reply.error() == QNetworkReply.ContentOperationNotPermittedError:
            # It was probably "/api/v1/materials" for legacy UM3
            return
        if reply.error() == QNetworkReply.ContentNotFoundError:
            # It was probably "/api/v1/print_job" for legacy UM3
            return

        if reply.operation() == QNetworkAccessManager.PostOperation:
            if self._cluster_api_prefix + "print_jobs" in reply_url:
                self._finishedPrintJobPostRequest(reply)
                return

        # We need to do this check *after* we process the post operation!
        # If the sending of g-code is cancelled by the user it will result in an error, but we do need to handle this.
        if reply.error() != QNetworkReply.NoError:
            Logger.log("e", "After requesting [%s] we got a network error [%s]. Not processing anything...", reply_url, reply.error())
            return

        elif reply.operation() == QNetworkAccessManager.GetOperation:
            if self._cluster_api_prefix + "print_jobs" in reply_url:
                self._finishedPrintJobsRequest(reply)
            elif self._cluster_api_prefix + "printers" in reply_url:
                self._finishedPrintersRequest(reply)

    @pyqtSlot()
    def openPrintJobControlPanel(self):
        Logger.log("d", "Opening print job control panel...")
        QDesktopServices.openUrl(QUrl(self.getPrintJobsUrl()))

    @pyqtSlot()
    def openPrinterControlPanel(self):
        Logger.log("d", "Opening printer control panel...")
        QDesktopServices.openUrl(QUrl(self.getPrintersUrl()))

    def _onMessageActionTriggered(self, message, action):
        if action == "open_browser":
            QDesktopServices.openUrl(QUrl(self.getPrintJobsUrl()))

        if action == "Abort":
            Logger.log("d", "User aborted sending print to remote.")
            self._progress_message.hide()
            self._compressing_print = False
            if self._reply:
                self._reply.abort()
            self._stage = OutputStage.ready
            Application.getInstance().showPrintMonitor.emit(False)

    @pyqtSlot(int, result=str)
    def formatDuration(self, seconds):
        return Duration(seconds).getDisplayString(DurationFormat.Format.Short)

    ##  For cluster below
    def _get_plugin_directory_name(self):
        current_file_absolute_path = os.path.realpath(__file__)
        directory_path = os.path.dirname(current_file_absolute_path)
        _, directory_name = os.path.split(directory_path)
        return directory_name

    @property
    def _plugin_path(self):
        return PluginRegistry.getInstance().getPluginPath(self._get_plugin_directory_name())
Exemplo n.º 54
0
class WorkspaceDialog(QObject):
    showDialogSignal = pyqtSignal()

    def __init__(self, parent = None):
        super().__init__(parent)
        self._component = None
        self._context = None
        self._view = None
        self._qml_url = "WorkspaceDialog.qml"
        self._lock = threading.Lock()
        self._default_strategy = "override"
        self._result = {"machine": self._default_strategy,
                        "quality_changes": self._default_strategy,
                        "definition_changes": self._default_strategy,
                        "material": self._default_strategy}
        self._visible = False
        self.showDialogSignal.connect(self.__show)

        self._has_quality_changes_conflict = False
        self._has_definition_changes_conflict = False
        self._has_machine_conflict = False
        self._has_material_conflict = False
        self._num_visible_settings = 0
        self._num_user_settings = 0
        self._active_mode = ""
        self._quality_name = ""
        self._num_settings_overriden_by_quality_changes = 0
        self._quality_type = ""
        self._machine_name = ""
        self._machine_type = ""
        self._variant_type = ""
        self._material_labels = []
        self._extruders = []
        self._objects_on_plate = False

    machineConflictChanged = pyqtSignal()
    qualityChangesConflictChanged = pyqtSignal()
    definitionChangesConflictChanged = pyqtSignal()
    materialConflictChanged = pyqtSignal()
    numVisibleSettingsChanged = pyqtSignal()
    activeModeChanged = pyqtSignal()
    qualityNameChanged = pyqtSignal()
    numSettingsOverridenByQualityChangesChanged = pyqtSignal()
    qualityTypeChanged = pyqtSignal()
    machineNameChanged = pyqtSignal()
    materialLabelsChanged = pyqtSignal()
    objectsOnPlateChanged = pyqtSignal()
    numUserSettingsChanged = pyqtSignal()
    machineTypeChanged = pyqtSignal()
    variantTypeChanged = pyqtSignal()
    extrudersChanged = pyqtSignal()

    @pyqtProperty(str, notify=variantTypeChanged)
    def variantType(self):
        return self._variant_type

    def setVariantType(self, variant_type):
        if self._variant_type != variant_type:
            self._variant_type = variant_type
            self.variantTypeChanged.emit()

    @pyqtProperty(str, notify=machineTypeChanged)
    def machineType(self):
        return self._machine_type

    def setMachineType(self, machine_type):
        self._machine_type = machine_type
        self.machineTypeChanged.emit()

    def setNumUserSettings(self, num_user_settings):
        if self._num_user_settings != num_user_settings:
            self._num_user_settings = num_user_settings
            self.numVisibleSettingsChanged.emit()

    @pyqtProperty(int, notify=numUserSettingsChanged)
    def numUserSettings(self):
        return self._num_user_settings

    @pyqtProperty(bool, notify=objectsOnPlateChanged)
    def hasObjectsOnPlate(self):
        return self._objects_on_plate

    def setHasObjectsOnPlate(self, objects_on_plate):
        if self._objects_on_plate != objects_on_plate:
            self._objects_on_plate = objects_on_plate
            self.objectsOnPlateChanged.emit()

    @pyqtProperty("QVariantList", notify = materialLabelsChanged)
    def materialLabels(self):
        return self._material_labels

    def setMaterialLabels(self, material_labels):
        if self._material_labels != material_labels:
            self._material_labels = material_labels
            self.materialLabelsChanged.emit()

    @pyqtProperty("QVariantList", notify=extrudersChanged)
    def extruders(self):
        return self._extruders

    def setExtruders(self, extruders):
        if self._extruders != extruders:
            self._extruders = extruders
            self.extrudersChanged.emit()

    @pyqtProperty(str, notify = machineNameChanged)
    def machineName(self):
        return self._machine_name

    def setMachineName(self, machine_name):
        if self._machine_name != machine_name:
            self._machine_name = machine_name
            self.machineNameChanged.emit()

    @pyqtProperty(str, notify=qualityTypeChanged)
    def qualityType(self):
        return self._quality_type

    def setQualityType(self, quality_type):
        if self._quality_type != quality_type:
            self._quality_type = quality_type
            self.qualityTypeChanged.emit()

    @pyqtProperty(int, notify=numSettingsOverridenByQualityChangesChanged)
    def numSettingsOverridenByQualityChanges(self):
        return self._num_settings_overriden_by_quality_changes

    def setNumSettingsOverridenByQualityChanges(self, num_settings_overriden_by_quality_changes):
        self._num_settings_overriden_by_quality_changes = num_settings_overriden_by_quality_changes
        self.numSettingsOverridenByQualityChangesChanged.emit()

    @pyqtProperty(str, notify=qualityNameChanged)
    def qualityName(self):
        return self._quality_name

    def setQualityName(self, quality_name):
        if self._quality_name != quality_name:
            self._quality_name = quality_name
            self.qualityNameChanged.emit()

    @pyqtProperty(str, notify=activeModeChanged)
    def activeMode(self):
        return self._active_mode

    def setActiveMode(self, active_mode):
        if active_mode == 0:
            self._active_mode = i18n_catalog.i18nc("@title:tab", "Recommended")
        else:
            self._active_mode = i18n_catalog.i18nc("@title:tab", "Custom")
        self.activeModeChanged.emit()

    @pyqtProperty(int, constant = True)
    def totalNumberOfSettings(self):
        return len(ContainerRegistry.getInstance().findDefinitionContainers(id="fdmprinter")[0].getAllKeys())

    @pyqtProperty(int, notify = numVisibleSettingsChanged)
    def numVisibleSettings(self):
        return self._num_visible_settings

    def setNumVisibleSettings(self, num_visible_settings):
        if self._num_visible_settings != num_visible_settings:
            self._num_visible_settings = num_visible_settings
            self.numVisibleSettingsChanged.emit()

    @pyqtProperty(bool, notify = machineConflictChanged)
    def machineConflict(self):
        return self._has_machine_conflict

    @pyqtProperty(bool, notify=qualityChangesConflictChanged)
    def qualityChangesConflict(self):
        return self._has_quality_changes_conflict

    @pyqtProperty(bool, notify=definitionChangesConflictChanged)
    def definitionChangesConflict(self):
        return self._has_definition_changes_conflict

    @pyqtProperty(bool, notify=materialConflictChanged)
    def materialConflict(self):
        return self._has_material_conflict

    @pyqtSlot(str, str)
    def setResolveStrategy(self, key, strategy):
        if key in self._result:
            self._result[key] = strategy

    ##  Close the backend: otherwise one could end up with "Slicing..."
    @pyqtSlot()
    def closeBackend(self):
        Application.getInstance().getBackend().close()

    def setMaterialConflict(self, material_conflict):
        if self._has_material_conflict != material_conflict:
            self._has_material_conflict = material_conflict
            self.materialConflictChanged.emit()

    def setMachineConflict(self, machine_conflict):
        if self._has_machine_conflict != machine_conflict:
            self._has_machine_conflict = machine_conflict
            self.machineConflictChanged.emit()

    def setQualityChangesConflict(self, quality_changes_conflict):
        if self._has_quality_changes_conflict != quality_changes_conflict:
            self._has_quality_changes_conflict = quality_changes_conflict
            self.qualityChangesConflictChanged.emit()

    def setDefinitionChangesConflict(self, definition_changes_conflict):
        if self._has_definition_changes_conflict != definition_changes_conflict:
            self._has_definition_changes_conflict = definition_changes_conflict
            self.definitionChangesConflictChanged.emit()

    def getResult(self):
        if "machine" in self._result and not self._has_machine_conflict:
            self._result["machine"] = None
        if "quality_changes" in self._result and not self._has_quality_changes_conflict:
            self._result["quality_changes"] = None
        if "definition_changes" in self._result and not self._has_definition_changes_conflict:
            self._result["definition_changes"] = None
        if "material" in self._result and not self._has_material_conflict:
            self._result["material"] = None
        return self._result

    def _createViewFromQML(self):
        path = QUrl.fromLocalFile(os.path.join(PluginRegistry.getInstance().getPluginPath("3MFReader"), self._qml_url))
        self._component = QQmlComponent(Application.getInstance()._engine, path)
        self._context = QQmlContext(Application.getInstance()._engine.rootContext())
        self._context.setContextProperty("manager", self)
        self._view = self._component.create(self._context)
        if self._view is None:
            Logger.log("c", "QQmlComponent status %s", self._component.status())
            Logger.log("c", "QQmlComponent error string %s", self._component.errorString())

    def show(self):
        # Emit signal so the right thread actually shows the view.
        if threading.current_thread() != threading.main_thread():
            self._lock.acquire()
        # Reset the result
        self._result = {"machine": self._default_strategy,
                        "quality_changes": self._default_strategy,
                        "definition_changes": self._default_strategy,
                        "material": self._default_strategy}
        self._visible = True
        self.showDialogSignal.emit()

    @pyqtSlot()
    ##  Used to notify the dialog so the lock can be released.
    def notifyClosed(self):
        self._result = {}
        self._visible = False
        self._lock.release()

    def hide(self):
        self._visible = False
        self._lock.release()
        self._view.hide()

    @pyqtSlot()
    def onOkButtonClicked(self):
        self._view.hide()
        self.hide()

    @pyqtSlot()
    def onCancelButtonClicked(self):
        self._view.hide()
        self.hide()
        self._result = {}

    ##  Block thread until the dialog is closed.
    def waitForClose(self):
        if self._visible:
            if threading.current_thread() != threading.main_thread():
                self._lock.acquire()
                self._lock.release()
            else:
                # If this is not run from a separate thread, we need to ensure that the events are still processed.
                while self._visible:
                    time.sleep(1 / 50)
                    QCoreApplication.processEvents()  # Ensure that the GUI does not freeze.

    def __show(self):
        if self._view is None:
            self._createViewFromQML()
        if self._view:
            self._view.show()
class PostProcessingPlugin(QObject,  Extension):
    def __init__(self, parent = None):
        super().__init__(parent)
        self.addMenuItem(i18n_catalog.i18n("Modify G-Code"), self.showPopup)
        self._view = None
        
        # Loaded scripts are all scripts that can be used
        self._loaded_scripts = {} 
        self._script_labels = {}
        
        # Script list contains instances of scripts in loaded_scripts. There can be duplicates and they will be executed in sequence.
        self._script_list = [] 
        self._selected_script_index = 0
        
    @pyqtSlot(int, result = "QVariant")
    def getSettingModel(self, index):
        return self._script_list[index].getSettingsModel()
    
    @pyqtSlot(str, "QVariant")
    ## Called when the setting is changed.
    def setSettingValue(self, key, value):
        setting = self._script_list[self._selected_script_index].getSettings().getSettingByKey(key)
        if setting:
            setting.setValue(value)
        #self._script_list[self._selected_script_index].getSettings().setSettingValue
    
    selectedIndexChanged = pyqtSignal()
    @pyqtProperty("QVariant", notify = selectedIndexChanged)
    def selectedScriptSettingsModel(self):
        try:
            return self._script_list[self._selected_script_index].getSettingsModel()
        except:
            return None
    
    @pyqtSlot()
    def execute(self):
        scene = Application.getInstance().getController().getScene()
        if hasattr(scene, "gcode_list"):
            gcode_list = getattr(scene, "gcode_list")
            if gcode_list:
                for script in self._script_list:
                    try:
                        gcode_list = script.execute(gcode_list)
                    except Exception as e:
                        print(e)
                        pass
                setattr(scene, "gcode_list", gcode_list)

    @pyqtSlot(int)
    def setSelectedScriptIndex(self, index):
        self._selected_script_index = index
        self.selectedIndexChanged.emit()
    
    @pyqtProperty(int, notify = selectedIndexChanged)
    def selectedScriptIndex(self):
        return self._selected_script_index
    
    @pyqtSlot(int, int)
    def moveScript(self, index, new_index):
        if new_index < 0 or new_index > len(self._script_list)-1:
            return #nothing needs to be done
        else:
            # Magical switch code.
            self._script_list[new_index], self._script_list[index] = self._script_list[index], self._script_list[new_index]
            self.scriptListChanged.emit()
            self.selectedIndexChanged.emit() #Ensure that settings are updated
    
    ##  Remove a script from the active script list by index.
    @pyqtSlot(int)
    def removeScriptByIndex(self, index):
        self._script_list.pop(index)
        if len(self._script_list) - 1 < self._selected_script_index:
            self._selected_script_index = len(self._script_list) - 1
        self.scriptListChanged.emit()
        self.selectedIndexChanged.emit() #Ensure that settings are updated
    
    ##  Load all scripts from provided path. This should probably only be done on init.
    def loadAllScripts(self, path):
        scripts = pkgutil.iter_modules(path = [path])
        for loader, script_name, ispkg in scripts: 
            if script_name not in sys.modules:
                # Import module
                loaded_script = __import__("PostProcessingPlugin.scripts."+ script_name, fromlist = [script_name])
                loaded_class = getattr(loaded_script, script_name)
                temp_object = loaded_class()
                try: 
                    setting_data = temp_object.getSettingData()
                    if "label" in setting_data and "key" in setting_data:
                        self._script_labels[setting_data["key"]] = setting_data["label"]
                        self._loaded_scripts[setting_data["key"]] = loaded_class
                    else:
                        Logger.log("w", "Script %s.py has no label or key", script_name)
                        self._script_labels[script_name] = script_name
                        self._loaded_scripts[script_name] = loaded_class
                    #self._script_list.append(loaded_class())
                    self.loadedScriptListChanged.emit()
                except AttributeError:
                    Logger.log("e", "Script %s.py is not a recognised script type. Ensure it inherits Script", script_name)
                except NotImplementedError:
                    Logger.log("e", "Script %s.py has no implemented settings",script_name)

    loadedScriptListChanged = pyqtSignal()
    @pyqtProperty("QVariantList", notify = loadedScriptListChanged)
    def loadedScriptList(self):
        return list(self._loaded_scripts.keys())
    
    @pyqtSlot(str, result = str)
    def getScriptLabelByKey(self, key):
        return self._script_labels[key]
    
    scriptListChanged = pyqtSignal()
    @pyqtProperty("QVariantList", notify = scriptListChanged)
    def scriptList(self):
        script_list = [script.getSettingData()["key"] for script in self._script_list]
        return script_list
    
    @pyqtSlot(str)
    def addScriptToList(self, key):
        self._script_list.append(self._loaded_scripts[key]())
        self.setSelectedScriptIndex(len(self._script_list) - 1)
        self.scriptListChanged.emit()
    
    ##  Creates the view used by show popup. The view is saved because of the fairly aggressive garbage collection.
    def _createView(self):
        ## Load all scripts in the scripts folder
        self.loadAllScripts(os.path.join(PluginRegistry.getInstance().getPluginPath("PostProcessingPlugin"), "scripts"))
        
        path = QUrl.fromLocalFile(os.path.join(PluginRegistry.getInstance().getPluginPath("PostProcessingPlugin"), "PostProcessingPlugin.qml"))
        self._component = QQmlComponent(Application.getInstance()._engine, path)

        self._context = QQmlContext(Application.getInstance()._engine.rootContext())
        self._context.setContextProperty("manager", self)
        self._view = self._component.create(self._context)
    
    ##  Show the (GUI) popup of the post processing plugin.
    def showPopup(self):
        if self._view is None:
            self._createView()
        self._view.show()
Exemplo n.º 56
0
class ChangeLog(Extension, QObject,):
    def __init__(self, parent = None):
        QObject.__init__(self, parent)
        Extension.__init__(self)
        self._changelog_window = None
        self._changelog_context = None
        version_string = Application.getInstance().getVersion()
        if version_string is not "master":
            self._version = Version(version_string)
        else:
            self._version = None
        self._change_logs = None
        Application.getInstance().engineCreatedSignal.connect(self._onEngineCreated)
        Preferences.getInstance().addPreference("general/latest_version_changelog_shown", "15.05.90") #First version of CURA with uranium
        #self.showChangelog()

    def getChangeLogs(self):
        if not self._change_logs:
            self.loadChangeLogs()
        return self._change_logs

    @pyqtSlot(result = str)
    def getChangeLogString(self):
        logs = self.getChangeLogs()
        latest_version = Version(Preferences.getInstance().getValue("general/latest_version_changelog_shown"))
        result = ""
        for version in logs:
            result += "<h1>" + str(version) + "</h1><br>"
            result += ""
            for change in logs[version]:
                result += "<b>" + str(change) + "</b><br>"
                for line in logs[version][change]:
                    result += str(line) + "<br>"
                result += "<br>"

        pass
        return result

    def loadChangeLogs(self):
        self._change_logs = {}
        with open(os.path.join(PluginRegistry.getInstance().getPluginPath("ChangeLogPlugin"), "ChangeLog.txt"), 'r') as f:
            open_version = None
            open_header = None
            for line in f:
                line = line.replace("\n","")
                if "[" in line and "]" in line:
                    line = line.replace("[","")
                    line = line.replace("]","")
                    open_version = Version(line)
                    self._change_logs[Version(line)] = {}
                elif line.startswith("*"):
                    open_header = line.replace("*","")
                    self._change_logs[open_version][open_header] = []
                else:
                    if line != "":
                        self._change_logs[open_version][open_header].append(line)

    def _onEngineCreated(self):
        if not self._version:
            return #We're on dev branch.
        if self._version > Preferences.getInstance().getValue("general/latest_version_changelog_shown"):
            self.showChangelog()

    def showChangelog(self):
        if not self._changelog_window:
            self.createChangelogWindow()
        self._changelog_window.show()
        Preferences.getInstance().setValue("general/latest_version_changelog_shown", Application.getInstance().getVersion())

    def hideChangelog(self):
        if self._changelog_window:
            self._changelog_window.hide()

    def createChangelogWindow(self):
        path = QUrl.fromLocalFile(os.path.join(PluginRegistry.getInstance().getPluginPath("ChangeLogPlugin"), "ChangeLog.qml"))
        component = QQmlComponent(Application.getInstance()._engine, path)
        self._changelog_context = QQmlContext(Application.getInstance()._engine.rootContext())
        self._changelog_context.setContextProperty("manager", self)
        self._changelog_window = component.create(self._changelog_context)
Exemplo n.º 57
0
    def load(self, path, is_first_call = True):
        if path == self._path:
            return

        with open(os.path.join(path, "theme.json"), encoding = "utf-8") as f:
            Logger.log("d", "Loading theme file: %s", os.path.join(path, "theme.json"))
            data = json.load(f)

        # Iteratively load inherited themes
        try:
            theme_id = data["metadata"]["inherits"]
            self.load(Resources.getPath(Resources.Themes, theme_id), is_first_call = False)
        except FileNotFoundError:
            Logger.log("e", "Could not find inherited theme %s", theme_id)
        except KeyError:
            pass # No metadata or no inherits keyword in the theme.json file

        if "colors" in data:
            for name, color in data["colors"].items():
                c = QColor(color[0], color[1], color[2], color[3])
                self._colors[name] = c

        fontsdir = os.path.join(path, "fonts")
        if os.path.isdir(fontsdir):
            for file in os.listdir(fontsdir):
                if "ttf" in file:
                    QFontDatabase.addApplicationFont(os.path.join(fontsdir, file))

        if "fonts" in data:
            system_font_size = QCoreApplication.instance().font().pointSize()
            for name, font in data["fonts"].items():
                f = QFont()
                f.setFamily(font.get("family", QCoreApplication.instance().font().family()))

                if font.get("bold"):
                    f.setBold(font.get("bold", False))
                else:
                    f.setWeight(font.get("weight", 50))

                f.setLetterSpacing(QFont.AbsoluteSpacing, font.get("letterSpacing", 0))
                f.setItalic(font.get("italic", False))
                f.setPointSize(int(font.get("size", 1) * system_font_size))
                f.setCapitalization(QFont.AllUppercase if font.get("capitalize", False) else QFont.MixedCase)

                self._fonts[name] = f

        if "sizes" in data:
            for name, size in data["sizes"].items():
                s = QSizeF()
                s.setWidth(round(size[0] * self._em_width))
                s.setHeight(round(size[1] * self._em_height))

                self._sizes[name] = s

        iconsdir = os.path.join(path, "icons")
        if os.path.isdir(iconsdir):
            for icon in os.listdir(iconsdir):
                name = os.path.splitext(icon)[0]
                self._icons[name] = QUrl.fromLocalFile(os.path.join(iconsdir, icon))

        imagesdir = os.path.join(path, "images")
        if os.path.isdir(imagesdir):
            for image in os.listdir(imagesdir):
                name = os.path.splitext(image)[0]
                self._images[name] = QUrl.fromLocalFile(os.path.join(imagesdir, image))

        styles = os.path.join(path, "styles.qml")
        if os.path.isfile(styles):
            c = QQmlComponent(self._engine, styles)
            context = QQmlContext(self._engine, self._engine)
            context.setContextProperty("Theme", self)
            self._styles = c.create(context)

            if c.isError():
                for error in c.errors():
                    Logger.log("e", error.toString())

        Logger.log("d", "Loaded theme %s", path)
        self._path = path

        # only emit the theme loaded signal once after all the themes in the inheritance chain have been loaded
        if is_first_call:
            self.themeLoaded.emit()
class DuetRRFOutputDevice(OutputDevice):
    def __init__(self,
                 name="DuetRRF",
                 url="http://printer.local",
                 duet_password="******",
                 http_user=None,
                 http_password=None,
                 device_type=DeviceType.print):
        self._device_type = device_type
        if device_type == DeviceType.print:
            description = catalog.i18nc("@action:button",
                                        "Print on {0}").format(name)
            name_id = name + "-print"
            priority = 30
        elif device_type == DeviceType.simulate:
            description = catalog.i18nc("@action:button",
                                        "Simulate on {0}").format(name)
            name_id = name + "-simulate"
            priority = 20
        elif device_type == DeviceType.upload:
            description = catalog.i18nc("@action:button",
                                        "Upload to {0}").format(name)
            name_id = name + "-upload"
            priority = 10
        else:
            assert False

        super().__init__(name_id)
        self.setShortDescription(description)
        self.setDescription(description)
        self.setPriority(priority)

        self._stage = OutputStage.ready
        self._name = name

        if not url.endswith('/'):
            url += '/'
        self._url = url

        self._duet_password = duet_password
        self._http_user = http_user
        self._http_password = http_password

        self._qnam = QtNetwork.QNetworkAccessManager()

        self._stream = None
        self._cleanupRequest()

        if hasattr(self, '_message'):
            self._message.hide()
        self._message = None

    def _timestamp(self):
        return ("time", datetime.datetime.now().strftime('%Y-%m-%dT%H:%M:%S'))

    def _send(self, command, query=None, next_stage=None, data=None):
        enc_query = urllib.parse.urlencode(query or dict())
        if enc_query:
            command += '?' + enc_query
        self._request = QtNetwork.QNetworkRequest(
            QUrl(self._url + "rr_" + command))
        self._request.setRawHeader(b'User-Agent', b'Cura Plugin DuetRRF')
        self._request.setRawHeader(b'Accept',
                                   b'application/json, text/javascript')
        self._request.setRawHeader(b'Connection', b'keep-alive')

        if self._http_user and self._http_password:
            self._request.setRawHeader(
                b'Authorization', b'Basic ' + base64.b64encode("{}:{}".format(
                    self._http_user, self._http_password).encode()))

        if data:
            self._request.setRawHeader(b'Content-Type',
                                       b'application/octet-stream')
            self._reply = self._qnam.post(self._request, data)
            self._reply.uploadProgress.connect(self._onUploadProgress)
        else:
            self._reply = self._qnam.get(self._request)

        if next_stage:
            self._reply.finished.connect(next_stage)
        self._reply.error.connect(self._onNetworkError)

    def requestWrite(self, node, fileName=None, *args, **kwargs):
        if self._stage != OutputStage.ready:
            raise OutputDeviceError.DeviceBusyError()

        if fileName:
            fileName = os.path.splitext(fileName)[0] + '.gcode'
        else:
            fileName = "%s.gcode" % Application.getInstance(
            ).getPrintInformation().jobName
        self._fileName = fileName

        path = QUrl.fromLocalFile(
            os.path.join(os.path.dirname(os.path.abspath(__file__)),
                         'UploadFilename.qml'))
        self._component = QQmlComponent(Application.getInstance()._engine,
                                        path)
        Logger.log("d", "Errors:", self._component.errors())
        self._context = QQmlContext(
            Application.getInstance()._engine.rootContext())
        self._context.setContextProperty("manager", self)
        self._dialog = self._component.create(self._context)
        self._dialog.textChanged.connect(self.onFilenameChanged)
        self._dialog.accepted.connect(self.onFilenameAccepted)
        self._dialog.open()
        self._dialog.findChild(QObject, "nameField").setProperty(
            'text', self._fileName)
        self._dialog.findChild(QObject,
                               "nameField").select(0,
                                                   len(self._fileName) - 6)
        self._dialog.findChild(QObject, "nameField").setProperty('focus', True)

    def onFilenameChanged(self):
        fileName = self._dialog.findChild(QObject,
                                          "nameField").property('text')
        self._dialog.setProperty('validName', len(fileName) > 0)

    def onFilenameAccepted(self):
        self._fileName = self._dialog.findChild(QObject,
                                                "nameField").property('text')
        if not self._fileName.endswith('.gcode') and '.' not in self._fileName:
            self._fileName += '.gcode'
        Logger.log("d", "Filename set to: " + self._fileName)

        self._dialog.deleteLater()

        # create the temp file for the gcode
        self._stream = StringIO()
        self._stage = OutputStage.writing
        self.writeStarted.emit(self)

        # show a progress message
        self._message = Message(
            catalog.i18nc("@info:progress",
                          "Uploading to {}").format(self._name), 0, False, -1)
        self._message.show()

        Logger.log("d", "Loading gcode...")

        # find the G-code for the active build plate to print
        active_build_plate_id = Application.getInstance().getBuildPlateModel(
        ).activeBuildPlate
        gcode_dict = getattr(
            Application.getInstance().getController().getScene(), "gcode_dict")
        gcode = gcode_dict[active_build_plate_id]

        # send all the gcode to self._stream
        lines = len(gcode)
        nextYield = time() + 0.05
        i = 0
        for line in gcode:
            i += 1
            self._stream.write(line)
            if time() > nextYield:
                self._onProgress(i / lines)
                QCoreApplication.processEvents()
                nextYield = time() + 0.05

        # start
        Logger.log("d", "Connecting...")
        self._send('connect', [("password", self._duet_password),
                               self._timestamp()], self.onConnected)

    def onConnected(self):
        if self._stage != OutputStage.writing:
            return

        Logger.log("d", "Connected")
        Logger.log("d", "Uploading...")

        self._stream.seek(0)
        self._postData = QByteArray()
        self._postData.append(self._stream.getvalue().encode())
        self._send('upload', [("name", "0:/gcodes/" + self._fileName),
                              self._timestamp()], self.onUploadDone,
                   self._postData)

    def onUploadDone(self):
        if self._stage != OutputStage.writing:
            return

        Logger.log("d", "Upload done")

        self._stream.close()
        self.stream = None

        if self._device_type == DeviceType.simulate:
            Logger.log("d", "Simulating...")
            if self._message:
                self._message.hide()
            text = catalog.i18nc("@info:progress",
                                 "Simulating print on {}").format(self._name)
            self._message = Message(text, 0, False, -1)
            self._message.show()

            self._send('gcode', [("gcode", "M37 S1")], self.onReadyToPrint)
        elif self._device_type == DeviceType.print:
            self.onReadyToPrint()
        elif self._device_type == DeviceType.upload:
            self._send('disconnect')
            if self._message:
                self._message.hide()
            text = "Uploaded file {} to {}.".format(
                os.path.basename(self._fileName), self._name)
            self._message = Message(catalog.i18nc("@info:status", text))
            self._message.addAction(
                "open_browser", catalog.i18nc("@action:button",
                                              "Open Browser"), "globe",
                catalog.i18nc("@info:tooltip",
                              "Open browser to DuetWebControl."))
            self._message.actionTriggered.connect(
                self._onMessageActionTriggered)
            self._message.show()

            self.writeSuccess.emit(self)
            self._cleanupRequest()

    def onReadyToPrint(self):
        if self._stage != OutputStage.writing:
            return

        Logger.log("d", "Ready to print")
        self._send('gcode', [("gcode", "M32 /gcodes/" + self._fileName)],
                   self.onPrintStarted)

    def onPrintStarted(self):
        if self._stage != OutputStage.writing:
            return

        Logger.log("d", "Print started")

        if self._device_type == DeviceType.simulate:
            self.onCheckStatus()
        else:
            self._send('disconnect')
            if self._message:
                self._message.hide()
            text = "Print started on {} with file {}".format(
                self._name, self._fileName)
            self._message = Message(catalog.i18nc("@info:status", text))
            self._message.addAction(
                "open_browser", catalog.i18nc("@action:button",
                                              "Open Browser"), "globe",
                catalog.i18nc("@info:tooltip",
                              "Open browser to DuetWebControl."))
            self._message.actionTriggered.connect(
                self._onMessageActionTriggered)
            self._message.show()

            self.writeSuccess.emit(self)
            self._cleanupRequest()

    def onSimulatedPrintFinished(self):
        if self._stage != OutputStage.writing:
            return

        Logger.log("d", "Simulation print finished")

        self._send('gcode', [("gcode", "M37 S0")], self.onSimulationStopped)

    def onCheckStatus(self):
        if self._stage != OutputStage.writing:
            return

        Logger.log("d", "Check status")

        self._send('status', [("type", "3")], self.onStatusReceived)

    def onStatusReceived(self):
        if self._stage != OutputStage.writing:
            return

        Logger.log("d", "Status received")

        status_bytes = bytes(self._reply.readAll())
        Logger.log("d", status_bytes)

        status = json.loads(status_bytes.decode())
        if status["status"] in ['P', 'M']:
            # still simulating
            # RRF 1.21RC2 and earlier used P while simulating
            # RRF 1.21RC3 and later uses M while simulating
            if self._message and "fractionPrinted" in status:
                self._message.setProgress(float(status["fractionPrinted"]))
            QTimer.singleShot(1000, self.onCheckStatus)
        else:
            # not printing any more (or error?)
            self.onSimulatedPrintFinished()

    def onSimulationStopped(self):
        if self._stage != OutputStage.writing:
            return

        Logger.log("d", "Simulation stopped")

        self._send('gcode', [("gcode", "M37")], self.onReporting)

    def onReporting(self):
        if self._stage != OutputStage.writing:
            return

        Logger.log("d", "Reporting")

        self._send('reply', [], self.onReported)

    def onReported(self):
        if self._stage != OutputStage.writing:
            return

        Logger.log("d", "Reported")

        self._send('disconnect')

        if self._message:
            self._message.hide()

        reply_body = bytes(self._reply.readAll()).decode()
        text = "Simulation performed on {} with file {}:\n{}".format(
            self._name, self._fileName, reply_body)
        self._message = Message(catalog.i18nc("@info:status", text))
        self._message.addAction(
            "open_browser", catalog.i18nc("@action:button", "Open Browser"),
            "globe",
            catalog.i18nc("@info:tooltip", "Open browser to DuetWebControl."))
        self._message.actionTriggered.connect(self._onMessageActionTriggered)
        self._message.show()

        self.writeSuccess.emit(self)
        self._cleanupRequest()

    def _onProgress(self, progress):
        if self._message:
            self._message.setProgress(progress)
        self.writeProgress.emit(self, progress)

    def _cleanupRequest(self):
        self._reply = None
        self._request = None
        if self._stream:
            self._stream.close()
        self._stream = None
        self._stage = OutputStage.ready
        self._fileName = None

    def _onMessageActionTriggered(self, message, action):
        if action == "open_browser":
            QDesktopServices.openUrl(QUrl(self._url))
            if self._message:
                self._message.hide()
            self._message = None

    def _onUploadProgress(self, bytesSent, bytesTotal):
        if bytesTotal > 0:
            self._onProgress(int(bytesSent * 100 / bytesTotal))

    def _onNetworkError(self, errorCode):
        Logger.log("e", "_onNetworkError: %s", repr(errorCode))
        if self._message:
            self._message.hide()
        self._message = None

        if self._reply:
            errorString = self._reply.errorString()
        else:
            errorString = ''
        message = Message(
            catalog.i18nc("@info:status",
                          "There was a network error: {} {}").format(
                              errorCode, errorString))
        message.show()

        self.writeError.emit(self)
        self._cleanupRequest()
Exemplo n.º 59
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"
Exemplo n.º 60
0
class ChangeLog(Extension, QObject,):
    def __init__(self, parent = None):
        QObject.__init__(self, parent)
        Extension.__init__(self)
        self._changelog_window = None
        self._changelog_context = None
        version_string = Application.getInstance().getVersion()
        if version_string is not "master":
            self._version = Version(version_string)
        else:
            self._version = None

        self._change_logs = None
        Application.getInstance().engineCreatedSignal.connect(self._onEngineCreated)
        Preferences.getInstance().addPreference("general/latest_version_changelog_shown", "2.0.0") #First version of CURA with uranium
        self.addMenuItem(catalog.i18nc("@item:inmenu", "Show Changelog"), self.showChangelog)
        #self.showChangelog()

    def getChangeLogs(self):
        if not self._change_logs:
            self.loadChangeLogs()
        return self._change_logs

    @pyqtSlot(result = str)
    def getChangeLogString(self):
        logs = self.getChangeLogs()
        result = ""
        for version in logs:
            result += "<h1>" + str(version) + "</h1><br>"
            result += ""
            for change in logs[version]:
                if str(change) != "":
                    result += "<b>" + str(change) + "</b><br>"
                for line in logs[version][change]:
                    result += str(line) + "<br>"
                result += "<br>"

        pass
        return result

    def loadChangeLogs(self):
        self._change_logs = collections.OrderedDict()
        with open(os.path.join(PluginRegistry.getInstance().getPluginPath(self.getPluginId()), "ChangeLog.txt"), "r",-1, "utf-8") as f:
            open_version = None
            open_header = "" # Initialise to an empty header in case there is no "*" in the first line of the changelog
            for line in f:
                line = line.replace("\n","")
                if "[" in line and "]" in line:
                    line = line.replace("[","")
                    line = line.replace("]","")
                    open_version = Version(line)
                    open_header = ""
                    self._change_logs[open_version] = collections.OrderedDict()
                elif line.startswith("*"):
                    open_header = line.replace("*","")
                    self._change_logs[open_version][open_header] = []
                elif line != "":
                    if open_header not in self._change_logs[open_version]:
                        self._change_logs[open_version][open_header] = []
                    self._change_logs[open_version][open_header].append(line)

    def _onEngineCreated(self):
        if not self._version:
            return #We're on dev branch.

        if Preferences.getInstance().getValue("general/latest_version_changelog_shown") == "master":
            latest_version_shown = Version("0.0.0")
        else:
            latest_version_shown = Version(Preferences.getInstance().getValue("general/latest_version_changelog_shown"))

        if self._version > latest_version_shown:
            self.showChangelog()

    def showChangelog(self):
        if not self._changelog_window:
            self.createChangelogWindow()

        self._changelog_window.show()
        Preferences.getInstance().setValue("general/latest_version_changelog_shown", Application.getInstance().getVersion())

    def hideChangelog(self):
        if self._changelog_window:
            self._changelog_window.hide()

    def createChangelogWindow(self):
        path = QUrl.fromLocalFile(os.path.join(PluginRegistry.getInstance().getPluginPath(self.getPluginId()), "ChangeLog.qml"))

        component = QQmlComponent(Application.getInstance()._engine, path)
        self._changelog_context = QQmlContext(Application.getInstance()._engine.rootContext())
        self._changelog_context.setContextProperty("manager", self)
        self._changelog_window = component.create(self._changelog_context)