示例#1
0
    def __init__(self, parent=None):
        super().__init__(parent)

        self._width = 0
        self._height = 0
        self._depth = 0

        self._shader = None

        self._grid_mesh = None
        self._grid_shader = None

        self._disallowed_areas = []
        self._disallowed_area_mesh = None

        self.setCalculateBoundingBox(False)
        self._volume_aabb = None

        self._raft_thickness = 0.0
        self._adhesion_type = None
        self._platform = Platform(self)

        self._global_container_stack = None
        Application.getInstance().globalContainerStackChanged.connect(
            self._onGlobalContainerStackChanged)
        self._onGlobalContainerStackChanged()

        self._active_extruder_stack = None
        ExtruderManager.getInstance().activeExtruderChanged.connect(
            self._onActiveExtruderStackChanged)
        self._onActiveExtruderStackChanged()
示例#2
0
    def __init__(self, parent=None):
        super().__init__(parent)

        self._width = 0
        self._height = 0
        self._depth = 0

        self._shader = None

        self._grid_mesh = None
        self._grid_shader = None

        self._disallowed_areas = []
        self._disallowed_area_mesh = None

        self._error_areas = []
        self._error_mesh = None

        self.setCalculateBoundingBox(False)
        self._volume_aabb = None

        self._raft_thickness = 0.0
        self._adhesion_type = None
        self._platform = Platform(self)

        self._global_container_stack = None
        Application.getInstance().globalContainerStackChanged.connect(
            self._onStackChanged)
        self._onStackChanged()

        self._has_errors = False
        Application.getInstance().getController().getScene(
        ).sceneChanged.connect(self._onSceneChanged)

        # Number of objects loaded at the moment.
        self._number_of_objects = 0

        self._change_timer = QTimer()
        self._change_timer.setInterval(100)
        self._change_timer.setSingleShot(True)
        self._change_timer.timeout.connect(self._onChangeTimerFinished)

        self._build_volume_message = Message(
            catalog.i18nc(
                "@info:status",
                "The build volume height has been reduced due to the value of the"
                " \"Print Sequence\" setting to prevent the gantry from colliding"
                " with printed models."))

        # Must be after setting _build_volume_message, apparently that is used in getMachineManager.
        # activeQualityChanged is always emitted after setActiveVariant, setActiveMaterial and setActiveQuality.
        # Therefore this works.
        Application.getInstance().getMachineManager(
        ).activeQualityChanged.connect(self._onStackChanged)
        # This should also ways work, and it is semantically more correct,
        # but it does not update the disallowed areas after material change
        Application.getInstance().getMachineManager(
        ).activeStackChanged.connect(self._onStackChanged)
示例#3
0
    def run(self):
        if "PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION" not in os.environ or os.environ["PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION"] != "cpp":
            Logger.log("w", "Using Python implementation of Protobuf, expect bad performance!")

        self._i18n_catalog = i18nCatalog("cura");

        i18nCatalog.setTagReplacements({
            "filename": "font color=\"black\"",
            "message": "font color=UM.Theme.colors.message_text;",
        })

        self.showSplashMessage(self._i18n_catalog.i18nc("@info:progress", "Setting up scene..."))

        controller = self.getController()

        controller.setActiveView("SolidView")
        controller.setCameraTool("CameraTool")
        controller.setSelectionTool("SelectionTool")

        t = controller.getTool("TranslateTool")
        if t:
            t.setEnabledAxis([ToolHandle.XAxis, ToolHandle.YAxis,ToolHandle.ZAxis])

        Selection.selectionChanged.connect(self.onSelectionChanged)

        root = controller.getScene().getRoot()
        self._platform = Platform(root)

        self._volume = BuildVolume.BuildVolume(root)

        self.getRenderer().setBackgroundColor(QColor(245, 245, 245))

        self._physics = PlatformPhysics.PlatformPhysics(controller, self._volume)

        camera = Camera("3d", root)
        camera.setPosition(Vector(-80, 250, 700))
        camera.setPerspective(True)
        camera.lookAt(Vector(0, 0, 0))
        controller.getScene().setActiveCamera("3d")

        self.getController().getTool("CameraTool").setOrigin(Vector(0, 100, 0))

        self._camera_animation = CameraAnimation.CameraAnimation()
        self._camera_animation.setCameraTool(self.getController().getTool("CameraTool"))

        self.showSplashMessage(self._i18n_catalog.i18nc("@info:progress", "Loading interface..."))

        self.setMainQml(Resources.getPath(self.ResourceTypes.QmlFiles, "Cura.qml"))
        self.initializeEngine()

        if self._engine.rootObjects:
            self.closeSplash()

            for file in self.getCommandLineOption("file", []):
                self._openFile(file)

            self.exec_()
示例#4
0
class CuraApplication(QtApplication):
    def __init__(self):
        Resources.addResourcePath(os.path.join(QtApplication.getInstallPrefix(), "share", "cura"))
        if not hasattr(sys, "frozen"):
            Resources.addResourcePath(os.path.join(os.path.abspath(os.path.dirname(__file__)), ".."))

        super().__init__(name = "cura", version = "master")

        self.setWindowIcon(QIcon(Resources.getPath(Resources.ImagesLocation, "cura-icon.png")))

        self.setRequiredPlugins([
            "CuraEngineBackend",
            "MeshView",
            "LayerView",
            "STLReader",
            "SelectionTool",
            "CameraTool",
            "GCodeWriter",
            "LocalFileOutputDevice"
        ])
        self._physics = None
        self._volume = None
        self._platform = None
        self._output_devices = {}
        self._print_information = None
        self._i18n_catalog = None
        self._previous_active_tool = None
        self._platform_activity = False

        self.activeMachineChanged.connect(self._onActiveMachineChanged)
        self.getController().getScene().sceneChanged.connect(self.updatePlatformActivity)

        Preferences.getInstance().addPreference("cura/active_machine", "")
        Preferences.getInstance().addPreference("cura/active_mode", "simple")
        Preferences.getInstance().addPreference("cura/recent_files", "")
        Preferences.getInstance().addPreference("cura/categories_expanded", "")
        Preferences.getInstance().addPreference("view/center_on_select", True)

        JobQueue.getInstance().jobFinished.connect(self._onJobFinished)

        self._recent_files = []
        files = Preferences.getInstance().getValue("cura/recent_files").split(";")
        for f in files:
            if not os.path.isfile(f):
                continue

            self._recent_files.append(QUrl.fromLocalFile(f))
    
    ##  Handle loading of all plugin types (and the backend explicitly)
    #   \sa PluginRegistery
    def _loadPlugins(self):
        self._plugin_registry.addPluginLocation(os.path.join(QtApplication.getInstallPrefix(), "lib", "cura"))
        if not hasattr(sys, "frozen"):
            self._plugin_registry.addPluginLocation(os.path.join(os.path.abspath(os.path.dirname(__file__)), "..", "plugins"))
            self._plugin_registry.loadPlugin("ConsoleLogger")

        self._plugin_registry.loadPlugins()

        if self.getBackend() == None:
            raise RuntimeError("Could not load the backend plugin!")

    def addCommandLineOptions(self, parser):
        super().addCommandLineOptions(parser)
        parser.add_argument("file", nargs="*", help="Files to load after starting the application.")

    def run(self):
        self._i18n_catalog = i18nCatalog("cura");

        i18nCatalog.setTagReplacements({
            "filename": "font color=\"black\"",
            "message": "font color=UM.Theme.colors.message_text;",
        })

        self.showSplashMessage(self._i18n_catalog.i18nc("Splash screen message", "Setting up scene..."))

        controller = self.getController()

        controller.setActiveView("MeshView")
        controller.setCameraTool("CameraTool")
        controller.setSelectionTool("SelectionTool")

        t = controller.getTool("TranslateTool")
        if t:
            t.setEnabledAxis([ToolHandle.XAxis, ToolHandle.YAxis,ToolHandle.ZAxis])

        Selection.selectionChanged.connect(self.onSelectionChanged)

        root = controller.getScene().getRoot()
        self._platform = Platform(root)

        self._volume = BuildVolume.BuildVolume(root)

        self.getRenderer().setLightPosition(Vector(0, 150, 0))
        self.getRenderer().setBackgroundColor(QColor(245, 245, 245))

        self._physics = PlatformPhysics.PlatformPhysics(controller, self._volume)

        camera = Camera("3d", root)
        camera.setPosition(Vector(-150, 150, 300))
        camera.setPerspective(True)
        camera.lookAt(Vector(0, 0, 0))

        self._camera_animation = CameraAnimation.CameraAnimation()
        self._camera_animation.setCameraTool(self.getController().getTool("CameraTool"))

        controller.getScene().setActiveCamera("3d")

        self.showSplashMessage(self._i18n_catalog.i18nc("Splash screen message", "Loading interface..."))

        self.setMainQml(Resources.getPath(Resources.QmlFilesLocation, "Cura.qml"))
        self.initializeEngine()

        if self.getMachines():
            active_machine_pref = Preferences.getInstance().getValue("cura/active_machine")
            if active_machine_pref:
                for machine in self.getMachines():
                    if machine.getName() == active_machine_pref:
                        self.setActiveMachine(machine)

            if not self.getActiveMachine():
                self.setActiveMachine(self.getMachines()[0])
        else:
            self.requestAddPrinter.emit()

        if self._engine.rootObjects:
            self.closeSplash()

            for file in self.getCommandLineOption("file", []):
                self._openFile(file)

            self.exec_()

    #   Handle Qt events
    def event(self, event):
        if event.type() == QEvent.FileOpen:
            self._openFile(event.file())

        return super().event(event)

    def getPrintInformation(self):
        return self._print_information

    def registerObjects(self, engine):
        engine.rootContext().setContextProperty("Printer", self)
        self._print_information = PrintInformation.PrintInformation()
        engine.rootContext().setContextProperty("PrintInformation", self._print_information)
        self._cura_actions = CuraActions.CuraActions(self)
        engine.rootContext().setContextProperty("CuraActions", self._cura_actions)

    def onSelectionChanged(self):
        if Selection.hasSelection():
            if not self.getController().getActiveTool():
                if self._previous_active_tool:
                    self.getController().setActiveTool(self._previous_active_tool)
                    self._previous_active_tool = None
                else:
                    self.getController().setActiveTool("TranslateTool")
            if Preferences.getInstance().getValue("view/center_on_select"):
                self._camera_animation.setStart(self.getController().getTool("CameraTool").getOrigin())
                self._camera_animation.setTarget(Selection.getSelectedObject(0).getWorldPosition())
                self._camera_animation.start()
        else:
            if self.getController().getActiveTool():
                self._previous_active_tool = self.getController().getActiveTool().getPluginId()
                self.getController().setActiveTool(None)
            else:
                self._previous_active_tool = None

    requestAddPrinter = pyqtSignal()
    activityChanged = pyqtSignal()

    @pyqtProperty(bool, notify = activityChanged)
    def getPlatformActivity(self):
        return self._platform_activity

    def updatePlatformActivity(self, node = None):
        count = 0
        for node in DepthFirstIterator(self.getController().getScene().getRoot()):
            if type(node) is not SceneNode or not node.getMeshData():
                continue

            count += 1

        self._platform_activity = True if count > 0 else False
        self.activityChanged.emit()

    ##  Remove an object from the scene
    @pyqtSlot("quint64")
    def deleteObject(self, object_id):
        object = self.getController().getScene().findObject(object_id)

        if not object and object_id != 0: #Workaround for tool handles overlapping the selected object
            object = Selection.getSelectedObject(0)
        
        if object:
            if object.getParent():
                group_node = object.getParent()
                if not group_node.callDecoration("isGroup"):
                    op = RemoveSceneNodeOperation(object)
                else:
                    while group_node.getParent().callDecoration("isGroup"):
                        group_node = group_node.getParent()
                    op = RemoveSceneNodeOperation(group_node)
            op.push()

    ##  Create a number of copies of existing object.
    @pyqtSlot("quint64", int)
    def multiplyObject(self, object_id, count):
        node = self.getController().getScene().findObject(object_id)

        if not node and object_id != 0: #Workaround for tool handles overlapping the selected object
            node = Selection.getSelectedObject(0)

        if node:
            op = GroupedOperation()
            for i in range(count):
                new_node = SceneNode()
                new_node.setMeshData(node.getMeshData())

                new_node.translate(Vector((i + 1) * node.getBoundingBox().width, node.getPosition().y, 0))
                new_node.setOrientation(node.getOrientation())
                new_node.setScale(node.getScale())
                new_node.setSelectable(True)
                op.addOperation(AddSceneNodeOperation(new_node, node.getParent()))
            op.push()
    
    ##  Center object on platform.
    @pyqtSlot("quint64")
    def centerObject(self, object_id):
        node = self.getController().getScene().findObject(object_id)

        if not node and object_id != 0: #Workaround for tool handles overlapping the selected object
            node = Selection.getSelectedObject(0)

        if node:
            op = SetTransformOperation(node, Vector())
            op.push()
    
    ##  Delete all mesh data on the scene.
    @pyqtSlot()
    def deleteAll(self):
        nodes = []
        for node in DepthFirstIterator(self.getController().getScene().getRoot()):
            if type(node) is not SceneNode or not node.getMeshData():
                continue
            nodes.append(node)
        if nodes:
            op = GroupedOperation()

            for node in nodes:
                op.addOperation(RemoveSceneNodeOperation(node))

            op.push()

    ## Reset all translation on nodes with mesh data. 
    @pyqtSlot()
    def resetAllTranslation(self):
        nodes = []
        for node in DepthFirstIterator(self.getController().getScene().getRoot()):
            if type(node) is not SceneNode or not node.getMeshData():
                continue
            nodes.append(node)

        if nodes:
            op = GroupedOperation()

            for node in nodes:
                op.addOperation(SetTransformOperation(node, Vector()))

            op.push()
    
    ## Reset all transformations on nodes with mesh data. 
    @pyqtSlot()
    def resetAll(self):
        nodes = []
        for node in DepthFirstIterator(self.getController().getScene().getRoot()):
            if type(node) is not SceneNode or not node.getMeshData():
                continue
            nodes.append(node)

        if nodes:
            op = GroupedOperation()

            for node in nodes:
                op.addOperation(SetTransformOperation(node, Vector(), Quaternion(), Vector(1, 1, 1)))

            op.push()
            
    ##  Reload all mesh data on the screen from file.
    @pyqtSlot()
    def reloadAll(self):
        nodes = []
        for node in DepthFirstIterator(self.getController().getScene().getRoot()):
            if type(node) is not SceneNode or not node.getMeshData():
                continue

            nodes.append(node)

        if not nodes:
            return

        for node in nodes:
            if not node.getMeshData():
                continue

            file_name = node.getMeshData().getFileName()
            if file_name:
                job = ReadMeshJob(file_name)
                job._node = node
                job.finished.connect(self._reloadMeshFinished)
                job.start()
    
    ##  Get logging data of the backend engine
    #   \returns \type{string} Logging data
    @pyqtSlot(result=str)
    def getEngineLog(self):
        log = ""

        for entry in self.getBackend().getLog():
            log += entry.decode()

        return log

    recentFilesChanged = pyqtSignal()
    @pyqtProperty("QVariantList", notify = recentFilesChanged)
    def recentFiles(self):
        return self._recent_files

    @pyqtSlot("QStringList")
    def setExpandedCategories(self, categories):
        categories = list(set(categories))
        categories.sort()
        joined = ";".join(categories)
        if joined != Preferences.getInstance().getValue("cura/categories_expanded"):
            Preferences.getInstance().setValue("cura/categories_expanded", joined)
            self.expandedCategoriesChanged.emit()

    expandedCategoriesChanged = pyqtSignal()
    @pyqtProperty("QStringList", notify = expandedCategoriesChanged)
    def expandedCategories(self):
        return Preferences.getInstance().getValue("cura/categories_expanded").split(";")

    @pyqtSlot(str, result = "QVariant")
    def getSettingValue(self, key):
        if not self.getActiveMachine():
            return None

        return self.getActiveMachine().getSettingValueByKey(key)
    
    ##  Change setting by key value pair
    @pyqtSlot(str, "QVariant")
    def setSettingValue(self, key, value):
        if not self.getActiveMachine():
            return

        self.getActiveMachine().setSettingValueByKey(key, value)
        
    @pyqtSlot()
    def mergeSelected(self):
        self.groupSelected()
        try:
            group_node = Selection.getAllSelectedObjects()[0]
        except Exception as e:
            return
        multi_material_decorator = MultiMaterialDecorator.MultiMaterialDecorator()
        group_node.addDecorator(multi_material_decorator)
        # Reset the position of each node
        for node in group_node.getChildren():
            new_position = node.getMeshData().getCenterPosition()
            new_position.setY(0)
            node.setPosition(new_position)
        
        # Use the previously found center of the group bounding box as the new location of the group
        group_node.setPosition(group_node.getBoundingBox().center)
    
    @pyqtSlot()
    def groupSelected(self):
        group_node = SceneNode()
        group_decorator = GroupDecorator()
        group_node.addDecorator(group_decorator)
        group_node.setParent(self.getController().getScene().getRoot())
        
        for node in Selection.getAllSelectedObjects():
            node.setParent(group_node)
        
        for node in group_node.getChildren():
            Selection.remove(node)
        
        Selection.add(group_node)
    
    @pyqtSlot()
    def ungroupSelected(self):
        ungrouped_nodes = []
        selected_objects = Selection.getAllSelectedObjects()[:] #clone the list
        for node in selected_objects:
            if node.callDecoration("isGroup" ):
                children_to_move = []
                for child in node.getChildren():
                    if type(child) is SceneNode:
                        children_to_move.append(child)
                       
                for child in children_to_move:
                    child.setParent(node.getParent())
                    Selection.add(child)
                    child.callDecoration("setConvexHull",None)
                node.setParent(None)
                ungrouped_nodes.append(node)
        for node in ungrouped_nodes:
            Selection.remove(node)

    def _onActiveMachineChanged(self):
        machine = self.getActiveMachine()
        if machine:
            Preferences.getInstance().setValue("cura/active_machine", machine.getName())

            self._volume.setWidth(machine.getSettingValueByKey("machine_width"))
            self._volume.setHeight(machine.getSettingValueByKey("machine_height"))
            self._volume.setDepth(machine.getSettingValueByKey("machine_depth"))

            disallowed_areas = machine.getSettingValueByKey("machine_disallowed_areas")
            areas = []
            if disallowed_areas:
                for area in disallowed_areas:
                    areas.append(Polygon(numpy.array(area, numpy.float32)))

            self._volume.setDisallowedAreas(areas)

            self._volume.rebuild()

            offset = machine.getSettingValueByKey("machine_platform_offset")
            if offset:
                self._platform.setPosition(Vector(offset[0], offset[1], offset[2]))
            else:
                self._platform.setPosition(Vector(0.0, 0.0, 0.0))

    def _onFileLoaded(self, job):
        node = job.getResult()
        if node != None:
            node.setSelectable(True)
            node.setName(os.path.basename(job.getFileName()))

            op = AddSceneNodeOperation(node, self.getController().getScene().getRoot())
            op.push()

    def _onJobFinished(self, job):
        if type(job) is not ReadMeshJob or not job.getResult():
            return

        f = QUrl.fromLocalFile(job.getFileName())
        if f in self._recent_files:
            self._recent_files.remove(f)

        self._recent_files.insert(0, f)
        if len(self._recent_files) > 10:
            del self._recent_files[10]

        pref = ""
        for path in self._recent_files:
            pref += path.toLocalFile() + ";"

        Preferences.getInstance().setValue("cura/recent_files", pref)
        self.recentFilesChanged.emit()

    def _reloadMeshFinished(self, job):
        job._node = job.getResult()

    def _openFile(self, file):
        job = ReadMeshJob(os.path.abspath(file))
        job.finished.connect(self._onFileLoaded)
        job.start()
示例#5
0
    def run(self):
        self._i18n_catalog = i18nCatalog("cura");

        i18nCatalog.setTagReplacements({
            "filename": "font color=\"black\"",
            "message": "font color=UM.Theme.colors.message_text;",
        })

        self.showSplashMessage(self._i18n_catalog.i18nc("Splash screen message", "Setting up scene..."))

        controller = self.getController()

        controller.setActiveView("MeshView")
        controller.setCameraTool("CameraTool")
        controller.setSelectionTool("SelectionTool")

        t = controller.getTool("TranslateTool")
        if t:
            t.setEnabledAxis([ToolHandle.XAxis, ToolHandle.YAxis,ToolHandle.ZAxis])

        Selection.selectionChanged.connect(self.onSelectionChanged)

        root = controller.getScene().getRoot()
        self._platform = Platform(root)

        self._volume = BuildVolume.BuildVolume(root)

        self.getRenderer().setLightPosition(Vector(0, 150, 0))
        self.getRenderer().setBackgroundColor(QColor(245, 245, 245))

        self._physics = PlatformPhysics.PlatformPhysics(controller, self._volume)

        camera = Camera("3d", root)
        camera.setPosition(Vector(-150, 150, 300))
        camera.setPerspective(True)
        camera.lookAt(Vector(0, 0, 0))

        self._camera_animation = CameraAnimation.CameraAnimation()
        self._camera_animation.setCameraTool(self.getController().getTool("CameraTool"))

        controller.getScene().setActiveCamera("3d")

        self.showSplashMessage(self._i18n_catalog.i18nc("Splash screen message", "Loading interface..."))

        self.setMainQml(Resources.getPath(Resources.QmlFilesLocation, "Cura.qml"))
        self.initializeEngine()

        if self.getMachines():
            active_machine_pref = Preferences.getInstance().getValue("cura/active_machine")
            if active_machine_pref:
                for machine in self.getMachines():
                    if machine.getName() == active_machine_pref:
                        self.setActiveMachine(machine)

            if not self.getActiveMachine():
                self.setActiveMachine(self.getMachines()[0])
        else:
            self.requestAddPrinter.emit()

        if self._engine.rootObjects:
            self.closeSplash()

            for file in self.getCommandLineOption("file", []):
                self._openFile(file)

            self.exec_()
示例#6
0
class CuraApplication(QtApplication):
    def __init__(self):
        Resources.addResourcePath(os.path.join(QtApplication.getInstallPrefix(), "share", "cura"))
        if not hasattr(sys, "frozen"):
            Resources.addResourcePath(os.path.join(os.path.abspath(os.path.dirname(__file__)), ".."))

        super().__init__(name = "cura", version = "master")

        self.setWindowIcon(QIcon(Resources.getPath(Resources.ImagesLocation, "cura-icon.png")))

        self.setRequiredPlugins([
            "CuraEngineBackend",
            "MeshView",
            "LayerView",
            "STLReader",
            "SelectionTool",
            "CameraTool",
            "GCodeWriter",
            "LocalFileStorage"
        ])
        self._physics = None
        self._volume = None
        self._platform = None
        self._output_devices = {}
        self._print_information = None
        self._i18n_catalog = None
        self._previous_active_tool = None
        self._platform_activity = False

        self.activeMachineChanged.connect(self._onActiveMachineChanged)

        Preferences.getInstance().addPreference("cura/active_machine", "")
        Preferences.getInstance().addPreference("cura/active_mode", "simple")
        Preferences.getInstance().addPreference("cura/recent_files", "")
        Preferences.getInstance().addPreference("cura/categories_expanded", "")

        JobQueue.getInstance().jobFinished.connect(self._onJobFinished)

        self._recent_files = []
        files = Preferences.getInstance().getValue("cura/recent_files").split(";")
        for f in files:
            if not os.path.isfile(f):
                continue

            self._recent_files.append(QUrl.fromLocalFile(f))
    
    ##  Handle loading of all plugin types (and the backend explicitly)
    #   \sa PluginRegistery
    def _loadPlugins(self):
        self._plugin_registry.addPluginLocation(os.path.join(QtApplication.getInstallPrefix(), "lib", "cura"))
        if not hasattr(sys, "frozen"):
            self._plugin_registry.addPluginLocation(os.path.join(os.path.abspath(os.path.dirname(__file__)), "..", "plugins"))

        self._plugin_registry.loadPlugins({ "type": "logger"})
        self._plugin_registry.loadPlugins({ "type": "storage_device" })
        self._plugin_registry.loadPlugins({ "type": "view" })
        self._plugin_registry.loadPlugins({ "type": "mesh_reader" })
        self._plugin_registry.loadPlugins({ "type": "mesh_writer" })
        self._plugin_registry.loadPlugins({ "type": "tool" })
        self._plugin_registry.loadPlugins({ "type": "extension" })

        self._plugin_registry.loadPlugin("CuraEngineBackend")

    def addCommandLineOptions(self, parser):
        super().addCommandLineOptions(parser)
        parser.add_argument("file", nargs="*", help="Files to load after starting the application.")

    def run(self):
        self._i18n_catalog = i18nCatalog("cura");

        self.addOutputDevice("local_file", {
            "id": "local_file",
            "function": self._writeToLocalFile,
            "description": self._i18n_catalog.i18nc("Save button tooltip", "Save to Disk"),
            "shortDescription": self._i18n_catalog.i18nc("Save button tooltip", "Save to Disk"),
            "icon": "save",
            "priority": 0
        })

        self.showSplashMessage(self._i18n_catalog.i18nc("Splash screen message", "Setting up scene..."))

        controller = self.getController()

        controller.setActiveView("MeshView")
        controller.setCameraTool("CameraTool")
        controller.setSelectionTool("SelectionTool")

        t = controller.getTool("TranslateTool")
        if t:
            t.setEnabledAxis([ToolHandle.XAxis, ToolHandle.ZAxis])

        Selection.selectionChanged.connect(self.onSelectionChanged)

        root = controller.getScene().getRoot()
        self._platform = Platform(root)

        self._volume = BuildVolume.BuildVolume(root)

        self.getRenderer().setLightPosition(Vector(0, 150, 0))
        self.getRenderer().setBackgroundColor(QColor(245, 245, 245))

        self._physics = PlatformPhysics.PlatformPhysics(controller, self._volume)

        camera = Camera("3d", root)
        camera.setPosition(Vector(-150, 150, 300))
        camera.setPerspective(True)
        camera.lookAt(Vector(0, 0, 0))

        self._camera_animation = CameraAnimation.CameraAnimation()
        self._camera_animation.setCameraTool(self.getController().getTool("CameraTool"))

        controller.getScene().setActiveCamera("3d")

        self.showSplashMessage(self._i18n_catalog.i18nc("Splash screen message", "Loading interface..."))

        self.setMainQml(Resources.getPath(Resources.QmlFilesLocation, "Cura.qml"))
        self.initializeEngine()

        self.getStorageDevice("LocalFileStorage").removableDrivesChanged.connect(self._removableDrivesChanged)

        if self.getMachines():
            active_machine_pref = Preferences.getInstance().getValue("cura/active_machine")
            if active_machine_pref:
                for machine in self.getMachines():
                    if machine.getName() == active_machine_pref:
                        self.setActiveMachine(machine)

            if not self.getActiveMachine():
                self.setActiveMachine(self.getMachines()[0])
        else:
            self.requestAddPrinter.emit()

        self._removableDrivesChanged()
        if self._engine.rootObjects:
            self.closeSplash()

            for file in self.getCommandLineOption("file", []):
                job = ReadMeshJob(os.path.abspath(file))
                job.finished.connect(self._onFileLoaded)
                job.start()

            self.exec_()

    def registerObjects(self, engine):
        engine.rootContext().setContextProperty("Printer", self)
        self._print_information = PrintInformation.PrintInformation()
        engine.rootContext().setContextProperty("PrintInformation", self._print_information)
        self._cura_actions = CuraActions.CuraActions(self)
        engine.rootContext().setContextProperty("CuraActions", self._cura_actions)

    def onSelectionChanged(self):
        if Selection.hasSelection():
            if not self.getController().getActiveTool():
                if self._previous_active_tool:
                    self.getController().setActiveTool(self._previous_active_tool)
                    self._previous_active_tool = None
                else:
                    self.getController().setActiveTool("TranslateTool")

            self._camera_animation.setStart(self.getController().getTool("CameraTool").getOrigin())
            self._camera_animation.setTarget(Selection.getSelectedObject(0).getWorldPosition())
            self._camera_animation.start()
        else:
            if self.getController().getActiveTool():
                self._previous_active_tool = self.getController().getActiveTool().getPluginId()
                self.getController().setActiveTool(None)
            else:
                self._previous_active_tool = None

    requestAddPrinter = pyqtSignal()
    activityChanged = pyqtSignal()

    @pyqtProperty(bool, notify = activityChanged)
    def getPlatformActivity(self):
        return self._platform_activity

    @pyqtSlot(bool)
    def setPlatformActivity(self, activity):
        ##Sets the _platform_activity variable on true or false depending on whether there is a mesh on the platform
        if activity == True:
            self._platform_activity = activity
        elif activity == False:
            nodes = []
            for node in DepthFirstIterator(self.getController().getScene().getRoot()):
                if type(node) is not SceneNode or not node.getMeshData():
                    continue
                nodes.append(node)
            i = 0
            for node in nodes:
                if not node.getMeshData():
                    continue
                i += 1
            if i <= 1: ## i == 0 when the meshes are removed using the deleteAll function; i == 1 when the last remaining mesh is removed using the deleteObject function
                self._platform_activity = activity
        self.activityChanged.emit()

    ##  Remove an object from the scene
    @pyqtSlot("quint64")
    def deleteObject(self, object_id):
        object = self.getController().getScene().findObject(object_id)

        if not object and object_id != 0: #Workaround for tool handles overlapping the selected object
            object = Selection.getSelectedObject(0)
        
        if object:
            if object.getParent():
                group_node = object.getParent()
                if not group_node.callDecoration("isGroup"):
                    op = RemoveSceneNodeOperation(object)
                else:
                    while group_node.getParent().callDecoration("isGroup"):
                        group_node = group_node.getParent()
                    op = RemoveSceneNodeOperation(group_node)
            op.push()
            self.setPlatformActivity(False)
    
    ##  Create a number of copies of existing object.
    @pyqtSlot("quint64", int)
    def multiplyObject(self, object_id, count):
        node = self.getController().getScene().findObject(object_id)

        if not node and object_id != 0: #Workaround for tool handles overlapping the selected object
            node = Selection.getSelectedObject(0)

        if node:
            op = GroupedOperation()
            for i in range(count):
                new_node = SceneNode()
                new_node.setMeshData(node.getMeshData())
                new_node.setScale(node.getScale())
                new_node.translate(Vector((i + 1) * node.getBoundingBox().width, 0, 0))
                new_node.setSelectable(True)
                op.addOperation(AddSceneNodeOperation(new_node, node.getParent()))
            op.push()
    
    ##  Center object on platform.
    @pyqtSlot("quint64")
    def centerObject(self, object_id):
        node = self.getController().getScene().findObject(object_id)

        if not node and object_id != 0: #Workaround for tool handles overlapping the selected object
            node = Selection.getSelectedObject(0)

        if node:
            op = SetTransformOperation(node, Vector())
            op.push()
    
    ##  Delete all mesh data on the scene.
    @pyqtSlot()
    def deleteAll(self):
        nodes = []
        for node in DepthFirstIterator(self.getController().getScene().getRoot()):
            if type(node) is not SceneNode or not node.getMeshData():
                continue
            nodes.append(node)
        if nodes:
            op = GroupedOperation()

            for node in nodes:
                op.addOperation(RemoveSceneNodeOperation(node))

            op.push()
        self.setPlatformActivity(False)
    
    ## Reset all translation on nodes with mesh data. 
    @pyqtSlot()
    def resetAllTranslation(self):
        nodes = []
        for node in DepthFirstIterator(self.getController().getScene().getRoot()):
            if type(node) is not SceneNode or not node.getMeshData():
                continue
            nodes.append(node)

        if nodes:
            op = GroupedOperation()

            for node in nodes:
                op.addOperation(SetTransformOperation(node, Vector()))

            op.push()
    
    ## Reset all transformations on nodes with mesh data. 
    @pyqtSlot()
    def resetAll(self):
        nodes = []
        for node in DepthFirstIterator(self.getController().getScene().getRoot()):
            if type(node) is not SceneNode or not node.getMeshData():
                continue
            nodes.append(node)

        if nodes:
            op = GroupedOperation()

            for node in nodes:
                op.addOperation(SetTransformOperation(node, Vector(), Quaternion(), Vector(1, 1, 1)))

            op.push()
            
    ##  Reload all mesh data on the screen from file.
    @pyqtSlot()
    def reloadAll(self):
        nodes = []
        for node in DepthFirstIterator(self.getController().getScene().getRoot()):
            if type(node) is not SceneNode or not node.getMeshData():
                continue

            nodes.append(node)

        if not nodes:
            return

        for node in nodes:
            if not node.getMeshData():
                continue

            file_name = node.getMeshData().getFileName()
            if file_name:
                job = ReadMeshJob(file_name)
                job._node = node
                job.finished.connect(self._reloadMeshFinished)
                job.start()
    
    ##  Get logging data of the backend engine
    #   \returns \type{string} Logging data
    @pyqtSlot(result=str)
    def getEngineLog(self):
        log = ""

        for entry in self.getBackend().getLog():
            log += entry.decode()

        return log

    recentFilesChanged = pyqtSignal()
    @pyqtProperty("QVariantList", notify = recentFilesChanged)
    def recentFiles(self):
        return self._recent_files

    @pyqtSlot("QStringList")
    def setExpandedCategories(self, categories):
        categories = list(set(categories))
        categories.sort()
        joined = ";".join(categories)
        if joined != Preferences.getInstance().getValue("cura/categories_expanded"):
            Preferences.getInstance().setValue("cura/categories_expanded", joined)
            self.expandedCategoriesChanged.emit()

    expandedCategoriesChanged = pyqtSignal()
    @pyqtProperty("QStringList", notify = expandedCategoriesChanged)
    def expandedCategories(self):
        return Preferences.getInstance().getValue("cura/categories_expanded").split(";")

    outputDevicesChanged = pyqtSignal()
    
    @pyqtProperty("QVariantMap", notify = outputDevicesChanged)
    def outputDevices(self):
        return self._output_devices

    @pyqtProperty("QStringList", notify = outputDevicesChanged)
    def outputDeviceNames(self):
        return self._output_devices.keys()

    @pyqtSlot(str, result = "QVariant")
    def getSettingValue(self, key):
        if not self.getActiveMachine():
            return None

        return self.getActiveMachine().getSettingValueByKey(key)
    
    ##  Change setting by key value pair
    @pyqtSlot(str, "QVariant")
    def setSettingValue(self, key, value):
        if not self.getActiveMachine():
            return

        self.getActiveMachine().setSettingValueByKey(key, value)
        
    @pyqtSlot()
    def mergeSelected(self):
        self.groupSelected()
        try:
            group_node = Selection.getAllSelectedObjects()[0]
        except Exception as e:
            return
        multi_material_decorator = MultiMaterialDecorator.MultiMaterialDecorator()
        group_node.addDecorator(multi_material_decorator)
        # Reset the position of each node
        for node in group_node.getChildren():
            new_position = node.getMeshData().getCenterPosition()
            new_position.setY(0)
            node.setPosition(new_position)
        
        # Use the previously found center of the group bounding box as the new location of the group
        group_node.setPosition(group_node.getBoundingBox().center)
    
    @pyqtSlot()
    def groupSelected(self):
        group_node = SceneNode()
        group_decorator = GroupDecorator()
        group_node.addDecorator(group_decorator)
        group_node.setParent(self.getController().getScene().getRoot())
        
        for node in Selection.getAllSelectedObjects():
            node.setParent(group_node)
        
        for node in group_node.getChildren():
            Selection.remove(node)
        
        Selection.add(group_node)
    
    @pyqtSlot()
    def ungroupSelected(self):
        ungrouped_nodes = []
        selected_objects = Selection.getAllSelectedObjects()[:] #clone the list
        for node in selected_objects:
            if node.callDecoration("isGroup" ):
                children_to_move = []
                for child in node.getChildren():
                    if type(child) is SceneNode:
                        children_to_move.append(child)
                       
                for child in children_to_move:
                    child.setParent(node.getParent())
                    Selection.add(child)
                    child.callDecoration("setConvexHull",None)
                node.setParent(None)
                ungrouped_nodes.append(node)
        for node in ungrouped_nodes:
            Selection.remove(node)

    ##  Add an output device that can be written to.
    #
    #   \param id \type{string} The identifier used to identify the device.
    #   \param device \type{StorageDevice} A dictionary of device information.
    #                 It should contains the following:
    #                 - function: A function to be called when trying to write to the device. Will be passed the device id as first parameter.
    #                 - description: A translated string containing a description of what happens when writing to the device.
    #                 - icon: The icon to use to represent the device.
    #                 - priority: The priority of the device. The device with the highest priority will be used as the default device.
    def addOutputDevice(self, id, device):
        self._output_devices[id] = device
        self.outputDevicesChanged.emit()
    
    ##  Remove output device
    #   \param id \type{string} The identifier used to identify the device.
    #   \sa PrinterApplication::addOutputDevice()
    def removeOutputDevice(self, id):
        if id in self._output_devices:
            del self._output_devices[id]
            self.outputDevicesChanged.emit()

    @pyqtSlot(str)
    def writeToOutputDevice(self, device):
        self._output_devices[device]["function"](device)

    writeToLocalFileRequested = pyqtSignal()
    
    def _writeToLocalFile(self, device):
        self.writeToLocalFileRequested.emit()

    def _writeToSD(self, device):
        for node in DepthFirstIterator(self.getController().getScene().getRoot()):
            if type(node) is not SceneNode or not node.getMeshData():
                continue

            try:
                path = self.getStorageDevice("LocalFileStorage").getRemovableDrives()[device]
            except KeyError:
                Logger.log("e", "Tried to write to unknown SD card %s", device)
                return
    
            filename = os.path.join(path, node.getName()[0:node.getName().rfind(".")] + ".gcode")

            message = Message(self._output_devices[device]["description"], 0, False, -1)
            message.show()

            job = WriteMeshJob(filename, node.getMeshData())
            job._sdcard = device
            job._message = message
            job.start()
            job.finished.connect(self._onWriteToSDFinished)

            return

    def _removableDrivesChanged(self):
        drives = self.getStorageDevice("LocalFileStorage").getRemovableDrives()
        for drive in drives:
            if drive not in self._output_devices:
                self.addOutputDevice(drive, {
                    "id": drive,
                    "function": self._writeToSD,
                    "description": self._i18n_catalog.i18nc("Save button tooltip. {0} is sd card name", "Save to SD Card {0}").format(drive),
                    "shortDescription": self._i18n_catalog.i18nc("Save button tooltip. {0} is sd card name", "Save to SD Card {0}").format(""),
                    "icon": "save_sd",
                    "priority": 1
                })

        drives_to_remove = []
        for device in self._output_devices:
            if device not in drives:
                if self._output_devices[device]["function"] == self._writeToSD:
                    drives_to_remove.append(device)

        for drive in drives_to_remove:
            self.removeOutputDevice(drive)

    def _onActiveMachineChanged(self):
        machine = self.getActiveMachine()
        if machine:
            Preferences.getInstance().setValue("cura/active_machine", machine.getName())

            self._volume.setWidth(machine.getSettingValueByKey("machine_width"))
            self._volume.setHeight(machine.getSettingValueByKey("machine_height"))
            self._volume.setDepth(machine.getSettingValueByKey("machine_depth"))

            disallowed_areas = machine.getSettingValueByKey("machine_disallowed_areas")
            areas = []
            if disallowed_areas:
                for area in disallowed_areas:
                    areas.append(Polygon(numpy.array(area, numpy.float32)))

            self._volume.setDisallowedAreas(areas)

            self._volume.rebuild()

            offset = machine.getSettingValueByKey("machine_platform_offset")
            if offset:
                self._platform.setPosition(Vector(offset[0], offset[1], offset[2]))
            else:
                self._platform.setPosition(Vector(0.0, 0.0, 0.0))

    def _onWriteToSDFinished(self, job):
        message = Message(self._i18n_catalog.i18nc("Saved to SD message, {0} is sdcard, {1} is filename", "Saved to SD Card {0} as {1}").format(job._sdcard, job.getFileName()))
        message.addAction(
            "eject",
            self._i18n_catalog.i18nc("Message action", "Eject"),
            "eject",
            self._i18n_catalog.i18nc("Message action tooltip, {0} is sdcard", "Eject SD Card {0}").format(job._sdcard)
        )

        job._message.hide()

        message._sdcard = job._sdcard
        message.actionTriggered.connect(self._onMessageActionTriggered)
        message.show()

    def _onMessageActionTriggered(self, message, action):
        if action == "eject":
            self.getStorageDevice("LocalFileStorage").ejectRemovableDrive(message._sdcard)

    def _onFileLoaded(self, job):
        mesh = job.getResult()
        if mesh != None:
            node = SceneNode()

            node.setSelectable(True)
            node.setMeshData(mesh)
            node.setName(os.path.basename(job.getFileName()))

            op = AddSceneNodeOperation(node, self.getController().getScene().getRoot())
            op.push()

    def _onJobFinished(self, job):
        if type(job) is not ReadMeshJob or not job.getResult():
            return

        f = QUrl.fromLocalFile(job.getFileName())
        if f in self._recent_files:
            self._recent_files.remove(f)

        self._recent_files.insert(0, f)
        if len(self._recent_files) > 10:
            del self._recent_files[10]

        pref = ""
        for path in self._recent_files:
            pref += path.toLocalFile() + ";"

        Preferences.getInstance().setValue("cura/recent_files", pref)
        self.recentFilesChanged.emit()

    def _reloadMeshFinished(self, job):
        job._node.setMeshData(job.getResult())
示例#7
0
    def run(self):
        self._i18n_catalog = i18nCatalog("cura");

        self.addOutputDevice("local_file", {
            "id": "local_file",
            "function": self._writeToLocalFile,
            "description": self._i18n_catalog.i18nc("Save button tooltip", "Save to Disk"),
            "shortDescription": self._i18n_catalog.i18nc("Save button tooltip", "Save to Disk"),
            "icon": "save",
            "priority": 0
        })

        self.showSplashMessage(self._i18n_catalog.i18nc("Splash screen message", "Setting up scene..."))

        controller = self.getController()

        controller.setActiveView("MeshView")
        controller.setCameraTool("CameraTool")
        controller.setSelectionTool("SelectionTool")

        t = controller.getTool("TranslateTool")
        if t:
            t.setEnabledAxis([ToolHandle.XAxis, ToolHandle.ZAxis])

        Selection.selectionChanged.connect(self.onSelectionChanged)

        root = controller.getScene().getRoot()
        self._platform = Platform(root)

        self._volume = BuildVolume.BuildVolume(root)

        self.getRenderer().setLightPosition(Vector(0, 150, 0))
        self.getRenderer().setBackgroundColor(QColor(245, 245, 245))

        self._physics = PlatformPhysics.PlatformPhysics(controller, self._volume)

        camera = Camera("3d", root)
        camera.setPosition(Vector(-150, 150, 300))
        camera.setPerspective(True)
        camera.lookAt(Vector(0, 0, 0))

        self._camera_animation = CameraAnimation.CameraAnimation()
        self._camera_animation.setCameraTool(self.getController().getTool("CameraTool"))

        controller.getScene().setActiveCamera("3d")

        self.showSplashMessage(self._i18n_catalog.i18nc("Splash screen message", "Loading interface..."))

        self.setMainQml(Resources.getPath(Resources.QmlFilesLocation, "Cura.qml"))
        self.initializeEngine()

        self.getStorageDevice("LocalFileStorage").removableDrivesChanged.connect(self._removableDrivesChanged)

        if self.getMachines():
            active_machine_pref = Preferences.getInstance().getValue("cura/active_machine")
            if active_machine_pref:
                for machine in self.getMachines():
                    if machine.getName() == active_machine_pref:
                        self.setActiveMachine(machine)

            if not self.getActiveMachine():
                self.setActiveMachine(self.getMachines()[0])
        else:
            self.requestAddPrinter.emit()

        self._removableDrivesChanged()
        if self._engine.rootObjects:
            self.closeSplash()

            for file in self.getCommandLineOption("file", []):
                job = ReadMeshJob(os.path.abspath(file))
                job.finished.connect(self._onFileLoaded)
                job.start()

            self.exec_()
示例#8
0
class CuraApplication(QtApplication):
    def __init__(self):
        Resources.addResourcePath(
            os.path.join(QtApplication.getInstallPrefix(), "share", "cura"))
        if not hasattr(sys, "frozen"):
            Resources.addResourcePath(
                os.path.join(os.path.abspath(os.path.dirname(__file__)), ".."))

        super().__init__(name="cura", version="master")

        self.setWindowIcon(
            QIcon(Resources.getPath(Resources.ImagesLocation,
                                    "cura-icon.png")))

        self.setRequiredPlugins([
            "CuraEngineBackend", "MeshView", "LayerView", "STLReader",
            "SelectionTool", "CameraTool", "GCodeWriter",
            "LocalFileOutputDevice"
        ])
        self._physics = None
        self._volume = None
        self._platform = None
        self._output_devices = {}
        self._print_information = None
        self._i18n_catalog = None
        self._previous_active_tool = None
        self._platform_activity = False

        self.activeMachineChanged.connect(self._onActiveMachineChanged)
        self.getController().getScene().sceneChanged.connect(
            self.updatePlatformActivity)

        Preferences.getInstance().addPreference("cura/active_machine", "")
        Preferences.getInstance().addPreference("cura/active_mode", "simple")
        Preferences.getInstance().addPreference("cura/recent_files", "")
        Preferences.getInstance().addPreference("cura/categories_expanded", "")
        Preferences.getInstance().addPreference("view/center_on_select", True)

        JobQueue.getInstance().jobFinished.connect(self._onJobFinished)

        self._recent_files = []
        files = Preferences.getInstance().getValue("cura/recent_files").split(
            ";")
        for f in files:
            if not os.path.isfile(f):
                continue

            self._recent_files.append(QUrl.fromLocalFile(f))

    ##  Handle loading of all plugin types (and the backend explicitly)
    #   \sa PluginRegistery
    def _loadPlugins(self):
        self._plugin_registry.addPluginLocation(
            os.path.join(QtApplication.getInstallPrefix(), "lib", "cura"))
        if not hasattr(sys, "frozen"):
            self._plugin_registry.addPluginLocation(
                os.path.join(os.path.abspath(os.path.dirname(__file__)), "..",
                             "plugins"))
            self._plugin_registry.loadPlugin("ConsoleLogger")

        self._plugin_registry.loadPlugins()

        if self.getBackend() == None:
            raise RuntimeError("Could not load the backend plugin!")

    def addCommandLineOptions(self, parser):
        super().addCommandLineOptions(parser)
        parser.add_argument(
            "file",
            nargs="*",
            help="Files to load after starting the application.")

    def run(self):
        self._i18n_catalog = i18nCatalog("cura")

        i18nCatalog.setTagReplacements({
            "filename":
            "font color=\"black\"",
            "message":
            "font color=UM.Theme.colors.message_text;",
        })

        self.showSplashMessage(
            self._i18n_catalog.i18nc("Splash screen message",
                                     "Setting up scene..."))

        controller = self.getController()

        controller.setActiveView("MeshView")
        controller.setCameraTool("CameraTool")
        controller.setSelectionTool("SelectionTool")

        t = controller.getTool("TranslateTool")
        if t:
            t.setEnabledAxis(
                [ToolHandle.XAxis, ToolHandle.YAxis, ToolHandle.ZAxis])

        Selection.selectionChanged.connect(self.onSelectionChanged)

        root = controller.getScene().getRoot()
        self._platform = Platform(root)

        self._volume = BuildVolume.BuildVolume(root)

        self.getRenderer().setLightPosition(Vector(0, 150, 0))
        self.getRenderer().setBackgroundColor(QColor(245, 245, 245))

        self._physics = PlatformPhysics.PlatformPhysics(
            controller, self._volume)

        camera = Camera("3d", root)
        camera.setPosition(Vector(-150, 150, 300))
        camera.setPerspective(True)
        camera.lookAt(Vector(0, 0, 0))

        self._camera_animation = CameraAnimation.CameraAnimation()
        self._camera_animation.setCameraTool(
            self.getController().getTool("CameraTool"))

        controller.getScene().setActiveCamera("3d")

        self.showSplashMessage(
            self._i18n_catalog.i18nc("Splash screen message",
                                     "Loading interface..."))

        self.setMainQml(
            Resources.getPath(Resources.QmlFilesLocation, "Cura.qml"))
        self.initializeEngine()

        if self.getMachines():
            active_machine_pref = Preferences.getInstance().getValue(
                "cura/active_machine")
            if active_machine_pref:
                for machine in self.getMachines():
                    if machine.getName() == active_machine_pref:
                        self.setActiveMachine(machine)

            if not self.getActiveMachine():
                self.setActiveMachine(self.getMachines()[0])
        else:
            self.requestAddPrinter.emit()

        if self._engine.rootObjects:
            self.closeSplash()

            for file in self.getCommandLineOption("file", []):
                self._openFile(file)

            self.exec_()

    #   Handle Qt events
    def event(self, event):
        if event.type() == QEvent.FileOpen:
            self._openFile(event.file())

        return super().event(event)

    def registerObjects(self, engine):
        engine.rootContext().setContextProperty("Printer", self)
        self._print_information = PrintInformation.PrintInformation()
        engine.rootContext().setContextProperty("PrintInformation",
                                                self._print_information)
        self._cura_actions = CuraActions.CuraActions(self)
        engine.rootContext().setContextProperty("CuraActions",
                                                self._cura_actions)

    def onSelectionChanged(self):
        if Selection.hasSelection():
            if not self.getController().getActiveTool():
                if self._previous_active_tool:
                    self.getController().setActiveTool(
                        self._previous_active_tool)
                    self._previous_active_tool = None
                else:
                    self.getController().setActiveTool("TranslateTool")
            if Preferences.getInstance().getValue("view/center_on_select"):
                self._camera_animation.setStart(
                    self.getController().getTool("CameraTool").getOrigin())
                self._camera_animation.setTarget(
                    Selection.getSelectedObject(0).getWorldPosition())
                self._camera_animation.start()
        else:
            if self.getController().getActiveTool():
                self._previous_active_tool = self.getController(
                ).getActiveTool().getPluginId()
                self.getController().setActiveTool(None)
            else:
                self._previous_active_tool = None

    requestAddPrinter = pyqtSignal()
    activityChanged = pyqtSignal()

    @pyqtProperty(bool, notify=activityChanged)
    def getPlatformActivity(self):
        return self._platform_activity

    def updatePlatformActivity(self, node=None):
        count = 0
        for node in DepthFirstIterator(
                self.getController().getScene().getRoot()):
            if type(node) is not SceneNode or not node.getMeshData():
                continue

            count += 1

        self._platform_activity = True if count > 0 else False
        self.activityChanged.emit()

    ##  Remove an object from the scene
    @pyqtSlot("quint64")
    def deleteObject(self, object_id):
        object = self.getController().getScene().findObject(object_id)

        if not object and object_id != 0:  #Workaround for tool handles overlapping the selected object
            object = Selection.getSelectedObject(0)

        if object:
            if object.getParent():
                group_node = object.getParent()
                if not group_node.callDecoration("isGroup"):
                    op = RemoveSceneNodeOperation(object)
                else:
                    while group_node.getParent().callDecoration("isGroup"):
                        group_node = group_node.getParent()
                    op = RemoveSceneNodeOperation(group_node)
            op.push()

    ##  Create a number of copies of existing object.
    @pyqtSlot("quint64", int)
    def multiplyObject(self, object_id, count):
        node = self.getController().getScene().findObject(object_id)

        if not node and object_id != 0:  #Workaround for tool handles overlapping the selected object
            node = Selection.getSelectedObject(0)

        if node:
            op = GroupedOperation()
            for i in range(count):
                new_node = SceneNode()
                new_node.setMeshData(node.getMeshData())

                new_node.translate(
                    Vector((i + 1) * node.getBoundingBox().width,
                           node.getPosition().y, 0))
                new_node.setOrientation(node.getOrientation())
                new_node.setScale(node.getScale())
                new_node.setSelectable(True)
                op.addOperation(
                    AddSceneNodeOperation(new_node, node.getParent()))
            op.push()

    ##  Center object on platform.
    @pyqtSlot("quint64")
    def centerObject(self, object_id):
        node = self.getController().getScene().findObject(object_id)

        if not node and object_id != 0:  #Workaround for tool handles overlapping the selected object
            node = Selection.getSelectedObject(0)

        if node:
            op = SetTransformOperation(node, Vector())
            op.push()

    ##  Delete all mesh data on the scene.
    @pyqtSlot()
    def deleteAll(self):
        nodes = []
        for node in DepthFirstIterator(
                self.getController().getScene().getRoot()):
            if type(node) is not SceneNode or not node.getMeshData():
                continue
            nodes.append(node)
        if nodes:
            op = GroupedOperation()

            for node in nodes:
                op.addOperation(RemoveSceneNodeOperation(node))

            op.push()

    ## Reset all translation on nodes with mesh data.
    @pyqtSlot()
    def resetAllTranslation(self):
        nodes = []
        for node in DepthFirstIterator(
                self.getController().getScene().getRoot()):
            if type(node) is not SceneNode or not node.getMeshData():
                continue
            nodes.append(node)

        if nodes:
            op = GroupedOperation()

            for node in nodes:
                op.addOperation(SetTransformOperation(node, Vector()))

            op.push()

    ## Reset all transformations on nodes with mesh data.
    @pyqtSlot()
    def resetAll(self):
        nodes = []
        for node in DepthFirstIterator(
                self.getController().getScene().getRoot()):
            if type(node) is not SceneNode or not node.getMeshData():
                continue
            nodes.append(node)

        if nodes:
            op = GroupedOperation()

            for node in nodes:
                op.addOperation(
                    SetTransformOperation(node, Vector(), Quaternion(),
                                          Vector(1, 1, 1)))

            op.push()

    ##  Reload all mesh data on the screen from file.
    @pyqtSlot()
    def reloadAll(self):
        nodes = []
        for node in DepthFirstIterator(
                self.getController().getScene().getRoot()):
            if type(node) is not SceneNode or not node.getMeshData():
                continue

            nodes.append(node)

        if not nodes:
            return

        for node in nodes:
            if not node.getMeshData():
                continue

            file_name = node.getMeshData().getFileName()
            if file_name:
                job = ReadMeshJob(file_name)
                job._node = node
                job.finished.connect(self._reloadMeshFinished)
                job.start()

    ##  Get logging data of the backend engine
    #   \returns \type{string} Logging data
    @pyqtSlot(result=str)
    def getEngineLog(self):
        log = ""

        for entry in self.getBackend().getLog():
            log += entry.decode()

        return log

    recentFilesChanged = pyqtSignal()

    @pyqtProperty("QVariantList", notify=recentFilesChanged)
    def recentFiles(self):
        return self._recent_files

    @pyqtSlot("QStringList")
    def setExpandedCategories(self, categories):
        categories = list(set(categories))
        categories.sort()
        joined = ";".join(categories)
        if joined != Preferences.getInstance().getValue(
                "cura/categories_expanded"):
            Preferences.getInstance().setValue("cura/categories_expanded",
                                               joined)
            self.expandedCategoriesChanged.emit()

    expandedCategoriesChanged = pyqtSignal()

    @pyqtProperty("QStringList", notify=expandedCategoriesChanged)
    def expandedCategories(self):
        return Preferences.getInstance().getValue(
            "cura/categories_expanded").split(";")

    @pyqtSlot(str, result="QVariant")
    def getSettingValue(self, key):
        if not self.getActiveMachine():
            return None

        return self.getActiveMachine().getSettingValueByKey(key)

    ##  Change setting by key value pair
    @pyqtSlot(str, "QVariant")
    def setSettingValue(self, key, value):
        if not self.getActiveMachine():
            return

        self.getActiveMachine().setSettingValueByKey(key, value)

    @pyqtSlot()
    def mergeSelected(self):
        self.groupSelected()
        try:
            group_node = Selection.getAllSelectedObjects()[0]
        except Exception as e:
            return
        multi_material_decorator = MultiMaterialDecorator.MultiMaterialDecorator(
        )
        group_node.addDecorator(multi_material_decorator)
        # Reset the position of each node
        for node in group_node.getChildren():
            new_position = node.getMeshData().getCenterPosition()
            new_position.setY(0)
            node.setPosition(new_position)

        # Use the previously found center of the group bounding box as the new location of the group
        group_node.setPosition(group_node.getBoundingBox().center)

    @pyqtSlot()
    def groupSelected(self):
        group_node = SceneNode()
        group_decorator = GroupDecorator()
        group_node.addDecorator(group_decorator)
        group_node.setParent(self.getController().getScene().getRoot())

        for node in Selection.getAllSelectedObjects():
            node.setParent(group_node)

        for node in group_node.getChildren():
            Selection.remove(node)

        Selection.add(group_node)

    @pyqtSlot()
    def ungroupSelected(self):
        ungrouped_nodes = []
        selected_objects = Selection.getAllSelectedObjects(
        )[:]  #clone the list
        for node in selected_objects:
            if node.callDecoration("isGroup"):
                children_to_move = []
                for child in node.getChildren():
                    if type(child) is SceneNode:
                        children_to_move.append(child)

                for child in children_to_move:
                    child.setParent(node.getParent())
                    Selection.add(child)
                    child.callDecoration("setConvexHull", None)
                node.setParent(None)
                ungrouped_nodes.append(node)
        for node in ungrouped_nodes:
            Selection.remove(node)

    def _onActiveMachineChanged(self):
        machine = self.getActiveMachine()
        if machine:
            Preferences.getInstance().setValue("cura/active_machine",
                                               machine.getName())

            self._volume.setWidth(
                machine.getSettingValueByKey("machine_width"))
            self._volume.setHeight(
                machine.getSettingValueByKey("machine_height"))
            self._volume.setDepth(
                machine.getSettingValueByKey("machine_depth"))

            disallowed_areas = machine.getSettingValueByKey(
                "machine_disallowed_areas")
            areas = []
            if disallowed_areas:
                for area in disallowed_areas:
                    areas.append(Polygon(numpy.array(area, numpy.float32)))

            self._volume.setDisallowedAreas(areas)

            self._volume.rebuild()

            offset = machine.getSettingValueByKey("machine_platform_offset")
            if offset:
                self._platform.setPosition(
                    Vector(offset[0], offset[1], offset[2]))
            else:
                self._platform.setPosition(Vector(0.0, 0.0, 0.0))

    def _onFileLoaded(self, job):
        node = job.getResult()
        if node != None:
            node.setSelectable(True)
            node.setName(os.path.basename(job.getFileName()))

            op = AddSceneNodeOperation(
                node,
                self.getController().getScene().getRoot())
            op.push()

    def _onJobFinished(self, job):
        if type(job) is not ReadMeshJob or not job.getResult():
            return

        f = QUrl.fromLocalFile(job.getFileName())
        if f in self._recent_files:
            self._recent_files.remove(f)

        self._recent_files.insert(0, f)
        if len(self._recent_files) > 10:
            del self._recent_files[10]

        pref = ""
        for path in self._recent_files:
            pref += path.toLocalFile() + ";"

        Preferences.getInstance().setValue("cura/recent_files", pref)
        self.recentFilesChanged.emit()

    def _reloadMeshFinished(self, job):
        job._node = job.getResult()

    def _openFile(self, file):
        job = ReadMeshJob(os.path.abspath(file))
        job.finished.connect(self._onFileLoaded)
        job.start()
示例#9
0
    def run(self):
        self._i18n_catalog = i18nCatalog("cura")

        i18nCatalog.setTagReplacements({
            "filename":
            "font color=\"black\"",
            "message":
            "font color=UM.Theme.colors.message_text;",
        })

        self.showSplashMessage(
            self._i18n_catalog.i18nc("Splash screen message",
                                     "Setting up scene..."))

        controller = self.getController()

        controller.setActiveView("MeshView")
        controller.setCameraTool("CameraTool")
        controller.setSelectionTool("SelectionTool")

        t = controller.getTool("TranslateTool")
        if t:
            t.setEnabledAxis(
                [ToolHandle.XAxis, ToolHandle.YAxis, ToolHandle.ZAxis])

        Selection.selectionChanged.connect(self.onSelectionChanged)

        root = controller.getScene().getRoot()
        self._platform = Platform(root)

        self._volume = BuildVolume.BuildVolume(root)

        self.getRenderer().setLightPosition(Vector(0, 150, 0))
        self.getRenderer().setBackgroundColor(QColor(245, 245, 245))

        self._physics = PlatformPhysics.PlatformPhysics(
            controller, self._volume)

        camera = Camera("3d", root)
        camera.setPosition(Vector(-150, 150, 300))
        camera.setPerspective(True)
        camera.lookAt(Vector(0, 0, 0))

        self._camera_animation = CameraAnimation.CameraAnimation()
        self._camera_animation.setCameraTool(
            self.getController().getTool("CameraTool"))

        controller.getScene().setActiveCamera("3d")

        self.showSplashMessage(
            self._i18n_catalog.i18nc("Splash screen message",
                                     "Loading interface..."))

        self.setMainQml(
            Resources.getPath(Resources.QmlFilesLocation, "Cura.qml"))
        self.initializeEngine()

        if self.getMachines():
            active_machine_pref = Preferences.getInstance().getValue(
                "cura/active_machine")
            if active_machine_pref:
                for machine in self.getMachines():
                    if machine.getName() == active_machine_pref:
                        self.setActiveMachine(machine)

            if not self.getActiveMachine():
                self.setActiveMachine(self.getMachines()[0])
        else:
            self.requestAddPrinter.emit()

        if self._engine.rootObjects:
            self.closeSplash()

            for file in self.getCommandLineOption("file", []):
                self._openFile(file)

            self.exec_()
示例#10
0
    def run(self):
        self._i18n_catalog = i18nCatalog("cura")

        i18nCatalog.setTagReplacements({
            "filename":
            "font color=\"black\"",
            "message":
            "font color=UM.Theme.colors.message_text;",
        })

        self.showSplashMessage(
            self._i18n_catalog.i18nc("@info:progress", "Setting up scene..."))

        controller = self.getController()

        controller.setActiveView("SolidView")
        controller.setCameraTool("CameraTool")
        controller.setSelectionTool("SelectionTool")

        t = controller.getTool("TranslateTool")
        if t:
            t.setEnabledAxis(
                [ToolHandle.XAxis, ToolHandle.YAxis, ToolHandle.ZAxis])

        Selection.selectionChanged.connect(self.onSelectionChanged)

        root = controller.getScene().getRoot()
        self._platform = Platform(root)

        self._volume = BuildVolume.BuildVolume(root)

        self.getRenderer().setBackgroundColor(QColor(245, 245, 245))

        self._physics = PlatformPhysics.PlatformPhysics(
            controller, self._volume)

        camera = Camera("3d", root)
        camera.setPosition(Vector(-80, 250, 700))
        camera.setPerspective(True)
        camera.lookAt(Vector(0, 0, 0))
        controller.getScene().setActiveCamera("3d")

        self.getController().getTool("CameraTool").setOrigin(Vector(0, 100, 0))

        self._camera_animation = CameraAnimation.CameraAnimation()
        self._camera_animation.setCameraTool(
            self.getController().getTool("CameraTool"))

        self.showSplashMessage(
            self._i18n_catalog.i18nc("@info:progress", "Loading interface..."))

        qmlRegisterSingletonType(MachineManagerModel.MachineManagerModel,
                                 "Cura", 1, 0, "MachineManager",
                                 MachineManagerModel.createMachineManagerModel)

        self.setMainQml(
            Resources.getPath(self.ResourceTypes.QmlFiles, "Cura.qml"))
        self._qml_import_paths.append(
            Resources.getPath(self.ResourceTypes.QmlFiles))
        self.initializeEngine()

        if self._engine.rootObjects:
            self.closeSplash()

            for file in self.getCommandLineOption("file", []):
                self._openFile(file)
            for file_name in self._open_file_queue:  #Open all the files that were queued up while plug-ins were loading.
                self._openFile(file_name)

            self._started = True

            self.exec_()
示例#11
0
class CuraApplication(QtApplication):
    def __init__(self):
        Resources.addResourcePath(os.path.join(QtApplication.getInstallPrefix(), "share", "cura"))
        if not hasattr(sys, "frozen"):
            Resources.addResourcePath(os.path.join(os.path.abspath(os.path.dirname(__file__)), ".."))

        super().__init__(name = "cura", version = "master")

        self.setWindowIcon(QIcon(Resources.getPath(Resources.ImagesLocation, "cura-icon.png")))

        self.setRequiredPlugins([
            "CuraEngineBackend",
            "MeshView",
            "LayerView",
            "STLReader",
            "SelectionTool",
            "CameraTool",
            "GCodeWriter",
            "LocalFileStorage"
        ])
        self._physics = None
        self._volume = None
        self._platform = None
        self._output_devices = {}
        self._print_information = None
        self._i18n_catalog = None
        self._previous_active_tool = None

        self.activeMachineChanged.connect(self._onActiveMachineChanged)

        Preferences.getInstance().addPreference("cura/active_machine", "")
        Preferences.getInstance().addPreference("cura/active_mode", "simple")
        Preferences.getInstance().addPreference("cura/recent_files", "")
        Preferences.getInstance().addPreference("cura/categories_expanded", "")

        JobQueue.getInstance().jobFinished.connect(self._onJobFinished)

        self._recent_files = []
        files = Preferences.getInstance().getValue("cura/recent_files").split(";")
        for f in files:
            if not os.path.isfile(f):
                continue

            self._recent_files.append(QUrl.fromLocalFile(f))
    
    ##  Handle loading of all plugin types (and the backend explicitly)
    #   \sa PluginRegistery
    def _loadPlugins(self):
        self._plugin_registry.addPluginLocation(os.path.join(QtApplication.getInstallPrefix(), "lib", "cura"))
        if not hasattr(sys, "frozen"):
            self._plugin_registry.addPluginLocation(os.path.join(os.path.abspath(os.path.dirname(__file__)), "..", "plugins"))

        self._plugin_registry.loadPlugins({ "type": "logger"})
        self._plugin_registry.loadPlugins({ "type": "storage_device" })
        self._plugin_registry.loadPlugins({ "type": "view" })
        self._plugin_registry.loadPlugins({ "type": "mesh_reader" })
        self._plugin_registry.loadPlugins({ "type": "mesh_writer" })
        self._plugin_registry.loadPlugins({ "type": "tool" })
        self._plugin_registry.loadPlugins({ "type": "extension" })

        self._plugin_registry.loadPlugin("CuraEngineBackend")

    def addCommandLineOptions(self, parser):
        parser.add_argument("file", nargs="*", help="Files to load after starting the application.")

    def run(self):
        self._i18n_catalog = i18nCatalog("cura");

        self.addOutputDevice("local_file", {
            "id": "local_file",
            "function": self._writeToLocalFile,
            "description": self._i18n_catalog.i18nc("Save button tooltip", "Save to Disk"),
            "icon": "save",
            "priority": 0
        })

        self.showSplashMessage(self._i18n_catalog.i18nc("Splash screen message", "Setting up scene..."))

        controller = self.getController()

        controller.setActiveView("MeshView")
        controller.setCameraTool("CameraTool")
        controller.setSelectionTool("SelectionTool")

        t = controller.getTool("TranslateTool")
        if t:
            t.setEnabledAxis([ToolHandle.XAxis, ToolHandle.ZAxis])

        Selection.selectionChanged.connect(self.onSelectionChanged)

        root = controller.getScene().getRoot()
        self._platform = Platform(root)

        self._volume = BuildVolume.BuildVolume(root)

        self.getRenderer().setLightPosition(Vector(0, 150, 0))
        self.getRenderer().setBackgroundColor(QColor(245, 245, 245))

        self._physics = PlatformPhysics.PlatformPhysics(controller, self._volume)

        camera = Camera("3d", root)
        camera.setPosition(Vector(-150, 150, 300))
        camera.setPerspective(True)
        camera.lookAt(Vector(0, 0, 0))

        self._camera_animation = CameraAnimation.CameraAnimation()
        self._camera_animation.setCameraTool(self.getController().getTool("CameraTool"))

        controller.getScene().setActiveCamera("3d")

        self.showSplashMessage(self._i18n_catalog.i18nc("Splash screen message", "Loading interface..."))

        self.setMainQml(Resources.getPath(Resources.QmlFilesLocation, "Cura.qml"))
        self.initializeEngine()

        self.getStorageDevice("LocalFileStorage").removableDrivesChanged.connect(self._removableDrivesChanged)

        if self.getMachines():
            active_machine_pref = Preferences.getInstance().getValue("cura/active_machine")
            if active_machine_pref:
                for machine in self.getMachines():
                    if machine.getName() == active_machine_pref:
                        self.setActiveMachine(machine)

            if not self.getActiveMachine():
                self.setActiveMachine(self.getMachines()[0])
        else:
            self.requestAddPrinter.emit()

        self._removableDrivesChanged()
        if self._engine.rootObjects:
            self.closeSplash()

            for file in self.getCommandLineOption("file", []):
                job = ReadMeshJob(os.path.abspath(file))
                job.finished.connect(self._onFileLoaded)
                job.start()

            self.exec_()

    def registerObjects(self, engine):
        engine.rootContext().setContextProperty("Printer", self)
        self._print_information = PrintInformation.PrintInformation()
        engine.rootContext().setContextProperty("PrintInformation", self._print_information)
        self._cura_actions = CuraActions.CuraActions(self)
        engine.rootContext().setContextProperty("CuraActions", self._cura_actions)

    def onSelectionChanged(self):
        if Selection.hasSelection():
            if not self.getController().getActiveTool():
                if self._previous_active_tool:
                    self.getController().setActiveTool(self._previous_active_tool)
                    self._previous_active_tool = None
                else:
                    self.getController().setActiveTool("TranslateTool")

            self._camera_animation.setStart(self.getController().getTool("CameraTool").getOrigin())
            self._camera_animation.setTarget(Selection.getSelectedObject(0).getWorldPosition())
            self._camera_animation.start()
        else:
            if self.getController().getActiveTool():
                self._previous_active_tool = self.getController().getActiveTool().getPluginId()
                self.getController().setActiveTool(None)
            else:
                self._previous_active_tool = None

    requestAddPrinter = pyqtSignal()

    ##  Remove an object from the scene
    @pyqtSlot("quint64")
    def deleteObject(self, object_id):
        object = self.getController().getScene().findObject(object_id)

        if not object and object_id != 0: #Workaround for tool handles overlapping the selected object
            object = Selection.getSelectedObject(0)

        if object:
            op = RemoveSceneNodeOperation(object)
            op.push()
    
    ##  Create a number of copies of existing object.
    @pyqtSlot("quint64", int)
    def multiplyObject(self, object_id, count):
        node = self.getController().getScene().findObject(object_id)

        if not node and object_id != 0: #Workaround for tool handles overlapping the selected object
            node = Selection.getSelectedObject(0)

        if node:
            op = GroupedOperation()
            for i in range(count):
                new_node = SceneNode()
                new_node.setMeshData(node.getMeshData())
                new_node.setScale(node.getScale())
                new_node.translate(Vector((i + 1) * node.getBoundingBox().width, 0, 0))
                new_node.setSelectable(True)
                op.addOperation(AddSceneNodeOperation(new_node, node.getParent()))
            op.push()
    
    ##  Center object on platform.
    @pyqtSlot("quint64")
    def centerObject(self, object_id):
        node = self.getController().getScene().findObject(object_id)

        if not node and object_id != 0: #Workaround for tool handles overlapping the selected object
            node = Selection.getSelectedObject(0)

        if node:
            op = SetTransformOperation(node, Vector())
            op.push()
    
    ##  Delete all mesh data on the scene.
    @pyqtSlot()
    def deleteAll(self):
        nodes = []
        for node in DepthFirstIterator(self.getController().getScene().getRoot()):
            if type(node) is not SceneNode or not node.getMeshData():
                continue
            nodes.append(node)

        if nodes:
            op = GroupedOperation()

            for node in nodes:
                op.addOperation(RemoveSceneNodeOperation(node))

            op.push()
    
    ## Reset all translation on nodes with mesh data. 
    @pyqtSlot()
    def resetAllTranslation(self):
        nodes = []
        for node in DepthFirstIterator(self.getController().getScene().getRoot()):
            if type(node) is not SceneNode or not node.getMeshData():
                continue
            nodes.append(node)

        if nodes:
            op = GroupedOperation()

            for node in nodes:
                op.addOperation(SetTransformOperation(node, Vector()))

            op.push()
    
    ## Reset all transformations on nodes with mesh data. 
    @pyqtSlot()
    def resetAll(self):
        nodes = []
        for node in DepthFirstIterator(self.getController().getScene().getRoot()):
            if type(node) is not SceneNode or not node.getMeshData():
                continue
            nodes.append(node)

        if nodes:
            op = GroupedOperation()

            for node in nodes:
                op.addOperation(SetTransformOperation(node, Vector(), Quaternion(), Vector(1, 1, 1)))

            op.push()
            
    ##  Reload all mesh data on the screen from file.
    @pyqtSlot()
    def reloadAll(self):
        nodes = []
        for node in DepthFirstIterator(self.getController().getScene().getRoot()):
            if type(node) is not SceneNode or not node.getMeshData():
                continue

            nodes.append(node)

        if not nodes:
            return

        for node in nodes:
            if not node.getMeshData():
                continue

            file_name = node.getMeshData().getFileName()
            if file_name:
                job = ReadMeshJob(file_name)
                job.finished.connect(lambda j: node.setMeshData(j.getResult()))
                job.start()
    
    ##  Get logging data of the backend engine
    #   \returns \type{string} Logging data
    @pyqtSlot(result=str)
    def getEngineLog(self):
        log = ""

        for entry in self.getBackend().getLog():
            log += entry.decode()

        return log

    recentFilesChanged = pyqtSignal()
    @pyqtProperty("QVariantList", notify = recentFilesChanged)
    def recentFiles(self):
        return self._recent_files

    @pyqtSlot("QStringList")
    def setExpandedCategories(self, categories):
        categories = list(set(categories))
        categories.sort()
        joined = ";".join(categories)
        if joined != Preferences.getInstance().getValue("cura/categories_expanded"):
            Preferences.getInstance().setValue("cura/categories_expanded", joined)
            self.expandedCategoriesChanged.emit()

    expandedCategoriesChanged = pyqtSignal()
    @pyqtProperty("QStringList", notify = expandedCategoriesChanged)
    def expandedCategories(self):
        return Preferences.getInstance().getValue("cura/categories_expanded").split(";")

    outputDevicesChanged = pyqtSignal()
    
    @pyqtProperty("QVariantMap", notify = outputDevicesChanged)
    def outputDevices(self):
        return self._output_devices

    @pyqtProperty("QStringList", notify = outputDevicesChanged)
    def outputDeviceNames(self):
        return self._output_devices.keys()

    @pyqtSlot(str, result = "QVariant")
    def getSettingValue(self, key):
        if not self.getActiveMachine():
            return None

        return self.getActiveMachine().getSettingValueByKey(key)
    
    ##  Change setting by key value pair
    @pyqtSlot(str, "QVariant")
    def setSettingValue(self, key, value):
        if not self.getActiveMachine():
            return

        self.getActiveMachine().setSettingValueByKey(key, value)

    ##  Add an output device that can be written to.
    #
    #   \param id \type{string} The identifier used to identify the device.
    #   \param device \type{StorageDevice} A dictionary of device information.
    #                 It should contains the following:
    #                 - function: A function to be called when trying to write to the device. Will be passed the device id as first parameter.
    #                 - description: A translated string containing a description of what happens when writing to the device.
    #                 - icon: The icon to use to represent the device.
    #                 - priority: The priority of the device. The device with the highest priority will be used as the default device.
    def addOutputDevice(self, id, device):
        self._output_devices[id] = device
        self.outputDevicesChanged.emit()
    
    ##  Remove output device
    #   \param id \type{string} The identifier used to identify the device.
    #   \sa PrinterApplication::addOutputDevice()
    def removeOutputDevice(self, id):
        if id in self._output_devices:
            del self._output_devices[id]
            self.outputDevicesChanged.emit()

    @pyqtSlot(str)
    def writeToOutputDevice(self, device):
        self._output_devices[device]["function"](device)

    writeToLocalFileRequested = pyqtSignal()
    
    def _writeToLocalFile(self, device):
        self.writeToLocalFileRequested.emit()

    def _writeToSD(self, device):
        for node in DepthFirstIterator(self.getController().getScene().getRoot()):
            if type(node) is not SceneNode or not node.getMeshData():
                continue

            try:
                path = self.getStorageDevice("LocalFileStorage").getRemovableDrives()[device]
            except KeyError:
                Logger.log("e", "Tried to write to unknown SD card %s", device)
                return
    
            filename = os.path.join(path, node.getName()[0:node.getName().rfind(".")] + ".gcode")

            job = WriteMeshJob(filename, node.getMeshData())
            job._sdcard = device
            job.start()
            job.finished.connect(self._onWriteToSDFinished)
            return

    def _removableDrivesChanged(self):
        drives = self.getStorageDevice("LocalFileStorage").getRemovableDrives()
        for drive in drives:
            if drive not in self._output_devices:
                self.addOutputDevice(drive, {
                    "id": drive,
                    "function": self._writeToSD,
                    "description": self._i18n_catalog.i18nc("Save button tooltip. {0} is sd card name", "Save to SD Card {0}").format(drive),
                    "icon": "save_sd",
                    "priority": 1
                })

        drives_to_remove = []
        for device in self._output_devices:
            if device not in drives:
                if self._output_devices[device]["function"] == self._writeToSD:
                    drives_to_remove.append(device)

        for drive in drives_to_remove:
            self.removeOutputDevice(drive)

    def _onActiveMachineChanged(self):
        machine = self.getActiveMachine()
        if machine:
            Preferences.getInstance().setValue("cura/active_machine", machine.getName())

            self._volume.setWidth(machine.getSettingValueByKey("machine_width"))
            self._volume.setHeight(machine.getSettingValueByKey("machine_height"))
            self._volume.setDepth(machine.getSettingValueByKey("machine_depth"))

            disallowed_areas = machine.getSettingValueByKey("machine_disallowed_areas")
            areas = []
            if disallowed_areas:
                for area in disallowed_areas:
                    areas.append(Polygon(numpy.array(area, numpy.float32)))

            self._volume.setDisallowedAreas(areas)

            self._volume.rebuild()

            offset = machine.getSettingValueByKey("machine_platform_offset")
            if offset:
                self._platform.setPosition(Vector(offset[0], offset[1], offset[2]))
            else:
                self._platform.setPosition(Vector(0.0, 0.0, 0.0))

    def _onWriteToSDFinished(self, job):
        message = Message(self._i18n_catalog.i18nc("Saved to SD message, {0} is sdcard, {1} is filename", "Saved to SD Card {0} as {1}").format(job._sdcard, job.getFileName()))
        message.addAction(
            "eject",
            self._i18n_catalog.i18nc("Message action", "Eject"),
            "eject",
            self._i18n_catalog.i18nc("Message action tooltip, {0} is sdcard", "Eject SD Card {0}").format(job._sdcard)
        )
        message._sdcard = job._sdcard
        message.actionTriggered.connect(self._onMessageActionTriggered)
        message.show()

    def _onMessageActionTriggered(self, message, action):
        if action == "eject":
            self.getStorageDevice("LocalFileStorage").ejectRemovableDrive(message._sdcard)

    def _onFileLoaded(self, job):
        mesh = job.getResult()
        if mesh != None:
            node = SceneNode()

            node.setSelectable(True)
            node.setMeshData(mesh)
            node.setName(os.path.basename(job.getFileName()))

            op = AddSceneNodeOperation(node, self.getController().getScene().getRoot())
            op.push()

    def _onJobFinished(self, job):
        if type(job) is not ReadMeshJob:
            return

        f = QUrl.fromLocalFile(job.getFileName())
        if f in self._recent_files:
            self._recent_files.remove(f)

        self._recent_files.insert(0, f)
        if len(self._recent_files) > 10:
            del self._recent_files[10]

        pref = ""
        for path in self._recent_files:
            pref += path.toLocalFile() + ";"

        Preferences.getInstance().setValue("cura/recent_files", pref)
        self.recentFilesChanged.emit()
示例#12
0
    def run(self):
        self._i18n_catalog = i18nCatalog("cura");

        self.addOutputDevice("local_file", {
            "id": "local_file",
            "function": self._writeToLocalFile,
            "description": self._i18n_catalog.i18nc("Save button tooltip", "Save to Disk"),
            "icon": "save",
            "priority": 0
        })

        self.showSplashMessage(self._i18n_catalog.i18nc("Splash screen message", "Setting up scene..."))

        controller = self.getController()

        controller.setActiveView("MeshView")
        controller.setCameraTool("CameraTool")
        controller.setSelectionTool("SelectionTool")

        t = controller.getTool("TranslateTool")
        if t:
            t.setEnabledAxis([ToolHandle.XAxis, ToolHandle.ZAxis])

        Selection.selectionChanged.connect(self.onSelectionChanged)

        root = controller.getScene().getRoot()
        self._platform = Platform(root)

        self._volume = BuildVolume.BuildVolume(root)

        self.getRenderer().setLightPosition(Vector(0, 150, 0))
        self.getRenderer().setBackgroundColor(QColor(245, 245, 245))

        self._physics = PlatformPhysics.PlatformPhysics(controller, self._volume)

        camera = Camera("3d", root)
        camera.setPosition(Vector(-150, 150, 300))
        camera.setPerspective(True)
        camera.lookAt(Vector(0, 0, 0))

        self._camera_animation = CameraAnimation.CameraAnimation()
        self._camera_animation.setCameraTool(self.getController().getTool("CameraTool"))

        controller.getScene().setActiveCamera("3d")

        self.showSplashMessage(self._i18n_catalog.i18nc("Splash screen message", "Loading interface..."))

        self.setMainQml(Resources.getPath(Resources.QmlFilesLocation, "Cura.qml"))
        self.initializeEngine()

        self.getStorageDevice("LocalFileStorage").removableDrivesChanged.connect(self._removableDrivesChanged)

        if self.getMachines():
            active_machine_pref = Preferences.getInstance().getValue("cura/active_machine")
            if active_machine_pref:
                for machine in self.getMachines():
                    if machine.getName() == active_machine_pref:
                        self.setActiveMachine(machine)

            if not self.getActiveMachine():
                self.setActiveMachine(self.getMachines()[0])
        else:
            self.requestAddPrinter.emit()

        self._removableDrivesChanged()
        if self._engine.rootObjects:
            self.closeSplash()

            for file in self.getCommandLineOption("file", []):
                job = ReadMeshJob(os.path.abspath(file))
                job.finished.connect(self._onFileLoaded)
                job.start()

            self.exec_()
示例#13
0
class CuraApplication(QtApplication):
    def __init__(self):
        Resources.addResourcePath(os.path.join(QtApplication.getInstallPrefix(), "share", "cura"))
        if not hasattr(sys, "frozen"):
            Resources.addResourcePath(os.path.join(os.path.abspath(os.path.dirname(__file__)), ".."))

        super().__init__(name = "cura", version = "master")

        self.setRequiredPlugins([
            "CuraEngineBackend",
            "MeshView",
            "LayerView",
            "STLReader",
            "SelectionTool",
            "CameraTool",
            "GCodeWriter",
            "LocalFileStorage"
        ])
        self._physics = None
        self._volume = None
        self._platform = None
        self._output_devices = {}
        self._print_information = None
        self._i18n_catalog = None

        self.activeMachineChanged.connect(self._onActiveMachineChanged)

        Preferences.getInstance().addPreference("cura/active_machine", "")
        Preferences.getInstance().addPreference("cura/active_mode", "simple")
    
    ##  Handle loading of all plugin types (and the backend explicitly)
    #   \sa PluginRegistery
    def _loadPlugins(self):
        self._plugin_registry.addPluginLocation(os.path.join(QtApplication.getInstallPrefix(), "lib", "cura"))
        if not hasattr(sys, "frozen"):
            self._plugin_registry.addPluginLocation(os.path.join(os.path.abspath(os.path.dirname(__file__)), "..", "plugins"))

        self._plugin_registry.loadPlugins({ "type": "logger"})
        self._plugin_registry.loadPlugins({ "type": "storage_device" })
        self._plugin_registry.loadPlugins({ "type": "view" })
        self._plugin_registry.loadPlugins({ "type": "mesh_reader" })
        self._plugin_registry.loadPlugins({ "type": "mesh_writer" })
        self._plugin_registry.loadPlugins({ "type": "tool" })
        self._plugin_registry.loadPlugins({ "type": "extension" })

        self._plugin_registry.loadPlugin("CuraEngineBackend")

    def addCommandLineOptions(self, parser):
        parser.add_argument("file", nargs="*", help="Files to load after starting the application.")

    def run(self):
        self._i18n_catalog = i18nCatalog("cura");

        self.addOutputDevice("local_file", {
            "id": "local_file",
            "function": self._writeToLocalFile,
            "description": self._i18n_catalog.i18nc("Save button tooltip", "Save to Disk"),
            "icon": "save",
            "priority": 0
        })

        self.showSplashMessage(self._i18n_catalog.i18nc("Splash screen message", "Setting up scene..."))

        controller = self.getController()

        controller.setActiveView("MeshView")
        controller.setCameraTool("CameraTool")
        controller.setSelectionTool("SelectionTool")

        t = controller.getTool("TranslateTool")
        if t:
            t.setEnabledAxis([ToolHandle.XAxis, ToolHandle.ZAxis])

        Selection.selectionChanged.connect(self.onSelectionChanged)

        root = controller.getScene().getRoot()
        self._platform = Platform(root)

        self._volume = BuildVolume.BuildVolume(root)

        self.getRenderer().setLightPosition(Vector(0, 150, 0))
        self.getRenderer().setBackgroundColor(QColor(245, 245, 245))

        self._physics = PlatformPhysics.PlatformPhysics(controller, self._volume)

        camera = Camera("3d", root)
        camera.setPosition(Vector(-150, 150, 300))
        camera.setPerspective(True)
        camera.lookAt(Vector(0, 0, 0))

        self._camera_animation = CameraAnimation.CameraAnimation()
        self._camera_animation.setCameraTool(self.getController().getTool("CameraTool"))

        controller.getScene().setActiveCamera("3d")

        self.showSplashMessage(self._i18n_catalog.i18nc("Splash screen message", "Loading interface..."))

        self.setMainQml(Resources.getPath(Resources.QmlFilesLocation, "Cura.qml"))
        self.initializeEngine()

        self.getStorageDevice("LocalFileStorage").removableDrivesChanged.connect(self._removableDrivesChanged)

        if self.getMachines():
            active_machine_pref = Preferences.getInstance().getValue("cura/active_machine")
            if active_machine_pref:
                for machine in self.getMachines():
                    if machine.getName() == active_machine_pref:
                        self.setActiveMachine(machine)

            if not self.getActiveMachine():
                self.setActiveMachine(self.getMachines()[0])
        else:
            self.requestAddPrinter.emit()

        self._removableDrivesChanged()
        if self._engine.rootObjects:
            self.closeSplash()

            for file in self.getCommandLineOption("file", []):
                job = ReadMeshJob(os.path.abspath(file))
                job.finished.connect(self._onFileLoaded)
                job.start()

            self.exec_()

    def registerObjects(self, engine):
        engine.rootContext().setContextProperty("Printer", self)
        self._print_information = PrintInformation.PrintInformation()
        engine.rootContext().setContextProperty("PrintInformation", self._print_information)

    def onSelectionChanged(self):
        if Selection.hasSelection():
            if not self.getController().getActiveTool():
                self.getController().setActiveTool("TranslateTool")

            self._camera_animation.setStart(self.getController().getTool("CameraTool").getOrigin())
            self._camera_animation.setTarget(Selection.getSelectedObject(0).getWorldPosition())
            self._camera_animation.start()
        else:
            if self.getController().getActiveTool():
                self.getController().setActiveTool(None)

    requestAddPrinter = pyqtSignal()

    ##  Remove an object from the scene
    @pyqtSlot("quint64")
    def deleteObject(self, object_id):
        object = self.getController().getScene().findObject(object_id)

        if object:
            op = RemoveSceneNodeOperation(object)
            op.push()
    
    ##  Create a number of copies of existing object.
    @pyqtSlot("quint64", int)
    def multiplyObject(self, object_id, count):
        node = self.getController().getScene().findObject(object_id)

        if node:
            op = GroupedOperation()
            for i in range(count):
                new_node = SceneNode()
                new_node.setMeshData(node.getMeshData())
                new_node.setScale(node.getScale())
                new_node.translate(Vector((i + 1) * node.getBoundingBox().width, 0, 0))
                new_node.setSelectable(True)
                op.addOperation(AddSceneNodeOperation(new_node, node.getParent()))
            op.push()
    
    ##  Center object on platform.
    @pyqtSlot("quint64")
    def centerObject(self, object_id):
        node = self.getController().getScene().findObject(object_id)

        if node:
            op = SetTransformOperation(node, Vector())
            op.push()
    
    ##  Delete all mesh data on the scene.
    @pyqtSlot()
    def deleteAll(self):
        nodes = []
        for node in DepthFirstIterator(self.getController().getScene().getRoot()):
            if type(node) is not SceneNode or not node.getMeshData():
                continue
            nodes.append(node)

        if nodes:
            op = GroupedOperation()

            for node in nodes:
                op.addOperation(RemoveSceneNodeOperation(node))

            op.push()
    
    ## Reset all translation on nodes with mesh data. 
    @pyqtSlot()
    def resetAllTranslation(self):
        nodes = []
        for node in DepthFirstIterator(self.getController().getScene().getRoot()):
            if type(node) is not SceneNode or not node.getMeshData():
                continue
            nodes.append(node)

        if nodes:
            op = GroupedOperation()

            for node in nodes:
                op.addOperation(SetTransformOperation(node, Vector()))

            op.push()
    
    ## Reset all transformations on nodes with mesh data. 
    @pyqtSlot()
    def resetAll(self):
        nodes = []
        for node in DepthFirstIterator(self.getController().getScene().getRoot()):
            if type(node) is not SceneNode or not node.getMeshData():
                continue
            nodes.append(node)

        if nodes:
            op = GroupedOperation()

            for node in nodes:
                op.addOperation(SetTransformOperation(node, Vector(), Quaternion(), Vector(1, 1, 1)))

            op.push()
            
    ##  Reload all mesh data on the screen from file.
    @pyqtSlot()
    def reloadAll(self):
        nodes = []
        for node in DepthFirstIterator(self.getController().getScene().getRoot()):
            if type(node) is not SceneNode or not node.getMeshData():
                continue

            nodes.append(node)

        if not nodes:
            return

        for node in nodes:
            if not node.getMeshData():
                continue

            file_name = node.getMeshData().getFileName()
            if file_name:
                job = ReadMeshJob(file_name)
                job.finished.connect(lambda j: node.setMeshData(j.getResult()))
                job.start()
    
    ##  Get logging data of the backend engine
    #   \returns \type{string} Logging data
    @pyqtSlot(result=str)
    def getEngineLog(self):
        log = ""

        for entry in self.getBackend().getLog():
            log += entry.decode()

        return log

    outputDevicesChanged = pyqtSignal()
    
    @pyqtProperty("QVariantMap", notify = outputDevicesChanged)
    def outputDevices(self):
        return self._output_devices

    @pyqtProperty("QStringList", notify = outputDevicesChanged)
    def outputDeviceNames(self):
        return self._output_devices.keys()

    @pyqtSlot(str, result = "QVariant")
    def getSettingValue(self, key):
        if not self.getActiveMachine():
            return None

        return self.getActiveMachine().getSettingValueByKey(key)
    
    ##  Change setting by key value pair
    @pyqtSlot(str, "QVariant")
    def setSettingValue(self, key, value):
        if not self.getActiveMachine():
            return

        self.getActiveMachine().setSettingValueByKey(key, value)

    ##  Add an output device that can be written to.
    #
    #   \param id \type{string} The identifier used to identify the device.
    #   \param device \type{StorageDevice} A dictionary of device information.
    #                 It should contains the following:
    #                 - function: A function to be called when trying to write to the device. Will be passed the device id as first parameter.
    #                 - description: A translated string containing a description of what happens when writing to the device.
    #                 - icon: The icon to use to represent the device.
    #                 - priority: The priority of the device. The device with the highest priority will be used as the default device.
    def addOutputDevice(self, id, device):
        self._output_devices[id] = device
        self.outputDevicesChanged.emit()
    
    ##  Remove output device
    #   \param id \type{string} The identifier used to identify the device.
    #   \sa PrinterApplication::addOutputDevice()
    def removeOutputDevice(self, id):
        if id in self._output_devices:
            del self._output_devices[id]
            self.outputDevicesChanged.emit()

    @pyqtSlot(str)
    def writeToOutputDevice(self, device):
        self._output_devices[device]["function"](device)

    writeToLocalFileRequested = pyqtSignal()
    
    def _writeToLocalFile(self, device):
        self.writeToLocalFileRequested.emit()

    def _writeToSD(self, device):
        for node in DepthFirstIterator(self.getController().getScene().getRoot()):
            if type(node) is not SceneNode or not node.getMeshData():
                continue

            try:
                path = self.getStorageDevice("LocalFileStorage").getRemovableDrives()[device]
            except KeyError:
                Logger.log("e", "Tried to write to unknown SD card %s", device)
                return
    
            filename = os.path.join(path, node.getName()[0:node.getName().rfind(".")] + ".gcode")

            job = WriteMeshJob(filename, node.getMeshData())
            job._sdcard = device
            job.start()
            job.finished.connect(self._onWriteToSDFinished)
            return

    def _removableDrivesChanged(self):
        drives = self.getStorageDevice("LocalFileStorage").getRemovableDrives()
        for drive in drives:
            if drive not in self._output_devices:
                self.addOutputDevice(drive, {
                    "id": drive,
                    "function": self._writeToSD,
                    "description": self._i18n_catalog.i18nc("Save button tooltip. {0} is sd card name", "Save to SD Card {0}".format(drive)),
                    "icon": "save_sd",
                    "priority": 1
                })

        drives_to_remove = []
        for device in self._output_devices:
            if device not in drives:
                if self._output_devices[device]["function"] == self._writeToSD:
                    drives_to_remove.append(device)

        for drive in drives_to_remove:
            self.removeOutputDevice(drive)

    def _onActiveMachineChanged(self):
        machine = self.getActiveMachine()
        if machine:
            Preferences.getInstance().setValue("cura/active_machine", machine.getName())

            self._volume.setWidth(machine.getSettingValueByKey("machine_width"))
            self._volume.setHeight(machine.getSettingValueByKey("machine_height"))
            self._volume.setDepth(machine.getSettingValueByKey("machine_depth"))

            disallowed_areas = machine.getSettingValueByKey("machine_disallowed_areas")
            areas = []
            if disallowed_areas:

                for area in disallowed_areas:
                    polygon = []
                    polygon.append(Vector(area[0][0], 0.2, area[0][1]))
                    polygon.append(Vector(area[1][0], 0.2, area[1][1]))
                    polygon.append(Vector(area[2][0], 0.2, area[2][1]))
                    polygon.append(Vector(area[3][0], 0.2, area[3][1]))
                    areas.append(polygon)
            self._volume.setDisallowedAreas(areas)

            self._volume.rebuild()

            if self.getController().getTool("ScaleTool"):
                self.getController().getTool("ScaleTool").setMaximumBounds(self._volume.getBoundingBox())

            offset = machine.getSettingValueByKey("machine_platform_offset")
            if offset:
                self._platform.setPosition(Vector(offset[0], offset[1], offset[2]))
            else:
                self._platform.setPosition(Vector(0.0, 0.0, 0.0))

    def _onWriteToSDFinished(self, job):
        message = Message(self._i18n_catalog.i18nc("Saved to SD message, {0} is sdcard, {1} is filename", "Saved to SD Card {0} as {1}").format(job._sdcard, job.getFileName()))
        message.addAction(
            "eject",
            self._i18n_catalog.i18nc("Message action", "Eject"),
            "eject",
            self._i18n_catalog.i18nc("Message action tooltip, {0} is sdcard", "Eject SD Card {0}".format(job._sdcard))
        )
        message._sdcard = job._sdcard
        message.actionTriggered.connect(self._onMessageActionTriggered)
        message.show()

    def _onMessageActionTriggered(self, message, action):
        if action == "eject":
            self.getStorageDevice("LocalFileStorage").ejectRemovableDrive(message._sdcard)

    def _onFileLoaded(self, job):
        mesh = job.getResult()
        if mesh != None:
            node = SceneNode()

            node.setSelectable(True)
            node.setMeshData(mesh)
            node.setName(os.path.basename(job.getFileName()))

            op = AddSceneNodeOperation(node, self.getController().getScene().getRoot())
            op.push()