123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449 |
- # Copyright (c) 2015 Ultimaker B.V.
- # Cura is released under the terms of the AGPLv3 or higher.
- from UM.Qt.QtApplication import QtApplication
- from UM.Scene.SceneNode import SceneNode
- from UM.Scene.Camera import Camera
- from UM.Scene.Platform import Platform
- from UM.Math.Vector import Vector
- from UM.Math.Matrix import Matrix
- from UM.Math.Quaternion import Quaternion
- from UM.Resources import Resources
- from UM.Scene.ToolHandle import ToolHandle
- from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
- from UM.Mesh.WriteMeshJob import WriteMeshJob
- from UM.Mesh.ReadMeshJob import ReadMeshJob
- from UM.Logger import Logger
- from UM.Preferences import Preferences
- from UM.Message import Message
- from UM.PluginRegistry import PluginRegistry
- from UM.Scene.BoxRenderer import BoxRenderer
- from UM.Scene.Selection import Selection
- from UM.Operations.AddSceneNodeOperation import AddSceneNodeOperation
- from UM.Operations.RemoveSceneNodeOperation import RemoveSceneNodeOperation
- from UM.Operations.GroupedOperation import GroupedOperation
- from UM.Operations.SetTransformOperation import SetTransformOperation
- from UM.i18n import i18nCatalog
- from . import PlatformPhysics
- from . import BuildVolume
- from . import CameraAnimation
- from . import PrintInformation
- from PyQt5.QtCore import pyqtSlot, QUrl, Qt, pyqtSignal, pyqtProperty
- from PyQt5.QtGui import QColor
- import platform
- import sys
- import os.path
- import numpy
- numpy.seterr(all="ignore")
- 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 = "15.05.90")
- 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.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)
|