diff --git a/cura/CrashHandler.py b/cura/CrashHandler.py index 1df1ab773e..1771a5c548 100644 --- a/cura/CrashHandler.py +++ b/cura/CrashHandler.py @@ -3,10 +3,15 @@ import platform import traceback import webbrowser -from PyQt5.QtCore import QT_VERSION_STR, PYQT_VERSION_STR +from PyQt5.QtCore import QT_VERSION_STR, PYQT_VERSION_STR, QCoreApplication from PyQt5.QtWidgets import QDialog, QDialogButtonBox, QVBoxLayout, QLabel, QTextEdit -def show(): +def show(type, value, tb): + application = QCoreApplication.instance() + if not application: + traceback.print_exception(type, value, tb) + sys.exit(1) + dialog = QDialog() dialog.setWindowTitle("Oops!") @@ -25,7 +30,7 @@ def show(): except: version = "Unknown" - trace = "".join(traceback.format_exception(sys.exc_info()[0], sys.exc_info()[1], sys.exc_info()[2])) + trace = "".join(traceback.format_exception(type, value, tb)) crash_info = "Version: {0}\nPlatform: {1}\nQt: {2}\nPyQt: {3}\n\nException:\n{4}" crash_info = crash_info.format(version, platform.platform(), QT_VERSION_STR, PYQT_VERSION_STR, trace) @@ -39,3 +44,4 @@ def show(): buttons.helpRequested.connect(lambda: webbrowser.open("http://github.com/Ultimaker/Cura/issues")) dialog.exec_() + sys.exit(1) diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index 876bbeae3b..9da84163e6 100644 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -67,7 +67,7 @@ class CuraApplication(QtApplication): "SelectionTool", "CameraTool", "GCodeWriter", - "LocalFileStorage" + "LocalFileOutputDevice" ]) self._physics = None self._volume = None @@ -101,16 +101,12 @@ class CuraApplication(QtApplication): 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({ "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.loadPlugins() - self._plugin_registry.loadPlugin("CuraEngineBackend") + if self.getBackend() == None: + raise RuntimeError("Could not load the backend plugin!") def addCommandLineOptions(self, parser): super().addCommandLineOptions(parser) @@ -119,15 +115,6 @@ class CuraApplication(QtApplication): 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() @@ -167,8 +154,6 @@ class CuraApplication(QtApplication): 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: @@ -181,7 +166,6 @@ class CuraApplication(QtApplication): else: self.requestAddPrinter.emit() - self._removableDrivesChanged() if self._engine.rootObjects: self.closeSplash() @@ -401,16 +385,6 @@ class CuraApplication(QtApplication): 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(): @@ -479,82 +453,6 @@ class CuraApplication(QtApplication): 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: @@ -580,25 +478,6 @@ class CuraApplication(QtApplication): 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: diff --git a/cura_app.py b/cura_app.py index 35ea0375b6..4bd49dc081 100755 --- a/cura_app.py +++ b/cura_app.py @@ -3,12 +3,15 @@ # Copyright (c) 2015 Ultimaker B.V. # Cura is released under the terms of the AGPLv3 or higher. -try: - import cura.CuraApplication +import sys - app = cura.CuraApplication.CuraApplication.getInstance() - app.run() -except Exception as e: +def exceptHook(type, value, traceback): import cura.CrashHandler - cura.CrashHandler.show() + cura.CrashHandler.show(type, value, traceback) +sys.excepthook = exceptHook + +import cura.CuraApplication + +app = cura.CuraApplication.CuraApplication.getInstance() +app.run() diff --git a/plugins/CuraEngineBackend/__init__.py b/plugins/CuraEngineBackend/__init__.py index 0c9588b1e7..fc986c6f67 100644 --- a/plugins/CuraEngineBackend/__init__.py +++ b/plugins/CuraEngineBackend/__init__.py @@ -9,11 +9,11 @@ catalog = i18nCatalog("cura") def getMetaData(): return { - "type": "backend", "plugin": { "name": "CuraEngine Backend", "author": "Ultimaker", - "description": catalog.i18nc("CuraEngine backend plugin description", "Provides the link to the CuraEngine slicing backend") + "description": catalog.i18nc("CuraEngine backend plugin description", "Provides the link to the CuraEngine slicing backend"), + "api": 2 } } diff --git a/plugins/GCodeWriter/GCodeWriter.py b/plugins/GCodeWriter/GCodeWriter.py index f776c6f0c1..d3db35e762 100644 --- a/plugins/GCodeWriter/GCodeWriter.py +++ b/plugins/GCodeWriter/GCodeWriter.py @@ -10,18 +10,17 @@ import io class GCodeWriter(MeshWriter): def __init__(self): super().__init__() - self._gcode = None - def write(self, file_name, storage_device, mesh_data): - if "gcode" in file_name: - scene = Application.getInstance().getController().getScene() - gcode_list = getattr(scene, "gcode_list") - if gcode_list: - f = storage_device.openFile(file_name, "wt") - Logger.log("d", "Writing GCode to file %s", file_name) - for gcode in gcode_list: - f.write(gcode) - storage_device.closeFile(f) - return True + def write(self, stream, node, mode = MeshWriter.OutputMode.TextMode): + if mode != MeshWriter.OutputMode.TextMode: + Logger.log("e", "GCode Writer does not support non-text mode") + return False + + scene = Application.getInstance().getController().getScene() + gcode_list = getattr(scene, "gcode_list") + if gcode_list: + for gcode in gcode_list: + stream.write(gcode) + return True return False diff --git a/plugins/GCodeWriter/__init__.py b/plugins/GCodeWriter/__init__.py index 3897cc3f75..ecd63b02b5 100644 --- a/plugins/GCodeWriter/__init__.py +++ b/plugins/GCodeWriter/__init__.py @@ -8,17 +8,21 @@ catalog = i18nCatalog("cura") def getMetaData(): return { - "type": "mesh_writer", "plugin": { "name": "GCode Writer", "author": "Ultimaker", "version": "1.0", - "description": catalog.i18nc("GCode Writer Plugin Description", "Writes GCode to a file") + "description": catalog.i18nc("GCode Writer Plugin Description", "Writes GCode to a file"), + "api": 2 }, "mesh_writer": { - "extension": "gcode", - "description": catalog.i18nc("GCode Writer File Description", "GCode File") + "output": [{ + "extension": "gcode", + "description": catalog.i18nc("GCode Writer File Description", "GCode File"), + "mime_type": "text/x-gcode", + "mode": GCodeWriter.GCodeWriter.OutputMode.TextMode + }] } } diff --git a/plugins/LayerView/__init__.py b/plugins/LayerView/__init__.py index eb3ba4cdbe..4bd9a61fb0 100644 --- a/plugins/LayerView/__init__.py +++ b/plugins/LayerView/__init__.py @@ -9,12 +9,12 @@ catalog = i18nCatalog("cura") def getMetaData(): return { - "type": "view", "plugin": { "name": "Layer View", "author": "Ultimaker", "version": "1.0", - "description": catalog.i18nc("Layer View plugin description", "Provides the Layer view.") + "description": catalog.i18nc("Layer View plugin description", "Provides the Layer view."), + "api": 2 }, "view": { "name": catalog.i18nc("Layers View mode", "Layers"), diff --git a/plugins/RemovableDriveOutputDevice/LinuxRemovableDrivePlugin.py b/plugins/RemovableDriveOutputDevice/LinuxRemovableDrivePlugin.py new file mode 100644 index 0000000000..ce948c472b --- /dev/null +++ b/plugins/RemovableDriveOutputDevice/LinuxRemovableDrivePlugin.py @@ -0,0 +1,41 @@ +# Copyright (c) 2015 Ultimaker B.V. +# Copyright (c) 2013 David Braam +# Uranium is released under the terms of the AGPLv3 or higher. + +from . import RemovableDrivePlugin + +import glob +import os +import subprocess + +## Support for removable devices on Linux. +# +# TODO: This code uses the most basic interfaces for handling this. +# We should instead use UDisks2 to handle mount/unmount and hotplugging events. +# +class LinuxRemovableDrivePlugin(RemovableDrivePlugin.RemovableDrivePlugin): + def checkRemovableDrives(self): + drives = {} + for volume in glob.glob("/media/*"): + if os.path.ismount(volume): + drives[volume] = os.path.basename(volume) + elif volume == "/media/"+os.getenv("USER"): + for volume in glob.glob("/media/"+os.getenv("USER")+"/*"): + if os.path.ismount(volume): + drives[volume] = os.path.basename(volume) + + for volume in glob.glob("/run/media/" + os.getenv("USER") + "/*"): + if os.path.ismount(volume): + drives[volume] = os.path.basename(volume) + + return drives + + def performEjectDevice(self, device): + p = subprocess.Popen(["umount", device.getId()], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + output = p.communicate() + + return_code = p.wait() + if return_code != 0: + return False + else: + return True diff --git a/plugins/RemovableDriveOutputDevice/OSXRemovableDrivePlugin.py b/plugins/RemovableDriveOutputDevice/OSXRemovableDrivePlugin.py new file mode 100644 index 0000000000..e02e0d65b6 --- /dev/null +++ b/plugins/RemovableDriveOutputDevice/OSXRemovableDrivePlugin.py @@ -0,0 +1,66 @@ +# Copyright (c) 2015 Ultimaker B.V. +# Copyright (c) 2013 David Braam +# Uranium is released under the terms of the AGPLv3 or higher. + +from . import RemovableDrivePlugin + +import threading + +import subprocess +import time +import os + +import plistlib + +## Support for removable devices on Mac OSX +class OSXRemovableDrivePlugin(RemovableDrivePlugin.RemovableDrivePlugin): + def checkRemovableDrives(self): + drives = {} + p = subprocess.Popen(["system_profiler", "SPUSBDataType", "-xml"], stdout=subprocess.PIPE) + plist = plistlib.loads(p.communicate()[0]) + p.wait() + + for dev in self._findInTree(plist, "Mass Storage Device"): + if "removable_media" in dev and dev["removable_media"] == "yes" and "volumes" in dev and len(dev["volumes"]) > 0: + for vol in dev["volumes"]: + if "mount_point" in vol: + volume = vol["mount_point"] + drives[volume] = os.path.basename(volume) + + p = subprocess.Popen(["system_profiler", "SPCardReaderDataType", "-xml"], stdout=subprocess.PIPE) + plist = plistlib.loads(p.communicate()[0]) + p.wait() + + for entry in plist: + if "_items" in entry: + for item in entry["_items"]: + for dev in item["_items"]: + if "removable_media" in dev and dev["removable_media"] == "yes" and "volumes" in dev and len(dev["volumes"]) > 0: + for vol in dev["volumes"]: + if "mount_point" in vol: + volume = vol["mount_point"] + drives[volume] = os.path.basename(volume) + + return drives + + def performEjectDevice(self, device): + p = subprocess.Popen(["diskutil", "eject", path], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + output = p.communicate() + + return_code = p.wait() + if return_code != 0: + return False + else: + return True + + def _findInTree(self, t, n): + ret = [] + if type(t) is dict: + if "_name" in t and t["_name"] == n: + ret.append(t) + for k, v in t.items(): + ret += self._findInTree(v, n) + if type(t) is list: + for v in t: + ret += self._findInTree(v, n) + return ret diff --git a/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py b/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py new file mode 100644 index 0000000000..2728dfd90b --- /dev/null +++ b/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py @@ -0,0 +1,87 @@ +import os.path + +from UM.Application import Application +from UM.Logger import Logger +from UM.Message import Message +from UM.Mesh.WriteMeshJob import WriteMeshJob +from UM.Mesh.MeshWriter import MeshWriter +from UM.Scene.Iterator.BreadthFirstIterator import BreadthFirstIterator +from UM.OutputDevice.OutputDevice import OutputDevice +from UM.OutputDevice import OutputDeviceError + +from UM.i18n import i18nCatalog +catalog = i18nCatalog("uranium") + +class RemovableDriveOutputDevice(OutputDevice): + def __init__(self, device_id, device_name): + super().__init__(device_id) + + self.setName(device_name) + self.setShortDescription(catalog.i18nc("", "Save to Removable Drive")) + self.setDescription(catalog.i18nc("", "Save to Removable Drive {0}").format(device_name)) + self.setIconName("save_sd") + self.setPriority(1) + + def requestWrite(self, node): + gcode_writer = Application.getInstance().getMeshFileHandler().getWriterByMimeType("text/x-gcode") + if not gcode_writer: + Logger.log("e", "Could not find GCode writer, not writing to removable drive %s", self.getName()) + raise OutputDeviceError.WriteRequestFailedError() + + file_name = None + for n in BreadthFirstIterator(node): + if n.getMeshData(): + file_name = n.getName() + if file_name: + break + + if not file_name: + Logger.log("e", "Could not determine a proper file name when trying to write to %s, aborting", self.getName()) + raise OutputDeviceError.WriteRequestFailedError() + + file_name = os.path.join(self.getId(), os.path.splitext(file_name)[0] + ".gcode") + + try: + Logger.log("d", "Writing to %s", file_name) + stream = open(file_name, "wt") + job = WriteMeshJob(gcode_writer, stream, node, MeshWriter.OutputMode.TextMode) + job.setFileName(file_name) + job.progress.connect(self._onProgress) + job.finished.connect(self._onFinished) + + message = Message(catalog.i18nc("", "Saving to Removable Drive {0}").format(self.getName()), 0, False, -1) + message.show() + + job._message = message + job.start() + except PermissionError as e: + raise OutputDeviceError.PermissionDeniedError() from e + except OSError as e: + raise OutputDeviceError.WriteRequestFailedError() from e + + def _onProgress(self, job, progress): + if hasattr(job, "_message"): + job._message.setProgress(progress) + self.writeProgress.emit(self, progress) + + def _onFinished(self, job): + if hasattr(job, "_message"): + job._message.hide() + job._message = None + self.writeFinished.emit(self) + if job.getResult(): + message = Message(catalog.i18nc("", "Saved to Removable Drive {0} as {1}").format(self.getName(), os.path.basename(job.getFileName()))) + message.addAction("eject", catalog.i18nc("", "Eject"), "eject", catalog.i18nc("", "Eject removable device {0}").format(self.getName())) + message.actionTriggered.connect(self._onActionTriggered) + message.show() + self.writeSuccess.emit(self) + else: + message = Message(catalog.i18nc("", "Could not save to removable drive {0}: {1}").format(self.getName(), str(job.getError()))) + message.show() + self.writeError.emit(self) + job.getStream().close() + + def _onActionTriggered(self, message, action): + if action == "eject": + Application.getInstance().getOutputDeviceManager().getOutputDevicePlugin("RemovableDriveOutputDevice").ejectDevice(self) + diff --git a/plugins/RemovableDriveOutputDevice/RemovableDrivePlugin.py b/plugins/RemovableDriveOutputDevice/RemovableDrivePlugin.py new file mode 100644 index 0000000000..a4e5e4f3f9 --- /dev/null +++ b/plugins/RemovableDriveOutputDevice/RemovableDrivePlugin.py @@ -0,0 +1,73 @@ +# Copyright (c) 2015 Ultimaker B.V. +# Uranium is released under the terms of the AGPLv3 or higher. + +import threading +import time + +from UM.Signal import Signal +from UM.Message import Message +from UM.OutputDevice.OutputDevicePlugin import OutputDevicePlugin + +from . import RemovableDriveOutputDevice + +from UM.i18n import i18nCatalog +catalog = i18nCatalog("uranium") + +class RemovableDrivePlugin(OutputDevicePlugin): + def __init__(self): + super().__init__() + + self._update_thread = threading.Thread(target = self._updateThread) + self._update_thread.setDaemon(True) + + self._check_updates = True + + self._drives = {} + + def start(self): + self._update_thread.start() + + def stop(self): + self._check_updates = False + self._update_thread.join() + + self._addRemoveDrives({}) + + def checkRemovableDrives(self): + raise NotImplementedError() + + def ejectDevice(self, device): + result = self.performEjectDevice(device) + if result: + message = Message(catalog.i18n("Ejected {0}. You can now safely remove the drive.").format(device.getName())) + message.show() + else: + message = Message(catalog.i18n("Failed to eject {0}. Maybe it is still in use?").format(device.getName())) + message.show() + + def performEjectDevice(self, device): + raise NotImplementedError() + + def _updateThread(self): + while self._check_updates: + result = self.checkRemovableDrives() + self._addRemoveDrives(result) + time.sleep(5) + + def _addRemoveDrives(self, drives): + # First, find and add all new or changed keys + for key, value in drives.items(): + if key not in self._drives: + self.getOutputDeviceManager().addOutputDevice(RemovableDriveOutputDevice.RemovableDriveOutputDevice(key, value)) + continue + + if self._drives[key] != value: + self.getOutputDeviceManager().removeOutputDevice(key) + self.getOutputDeviceManager().addOutputDevice(RemovableDriveOutputDevice.RemovableDriveOutputDevice(key, value)) + + # Then check for keys that have been removed + for key in self._drives.keys(): + if key not in drives: + self.getOutputDeviceManager().removeOutputDevice(key) + + self._drives = drives diff --git a/plugins/RemovableDriveOutputDevice/WindowsRemovableDrivePlugin.py b/plugins/RemovableDriveOutputDevice/WindowsRemovableDrivePlugin.py new file mode 100644 index 0000000000..aa85db0c09 --- /dev/null +++ b/plugins/RemovableDriveOutputDevice/WindowsRemovableDrivePlugin.py @@ -0,0 +1,98 @@ +# Copyright (c) 2015 Ultimaker B.V. +# Copyright (c) 2013 David Braam +# Uranium is released under the terms of the AGPLv3 or higher. + +from . import RemovableDrivePlugin + +import threading +import string + +from ctypes import windll +from ctypes import wintypes + +import ctypes +import time +import os +import subprocess + +from UM.i18n import i18nCatalog +catalog = i18nCatalog("uranium") + +# WinAPI Constants that we need +# Hardcoded here due to stupid WinDLL stuff that does not give us access to these values. +DRIVE_REMOVABLE = 2 + +GENERIC_READ = 2147483648 +GENERIC_WRITE = 1073741824 + +FILE_SHARE_READ = 1 +FILE_SHARE_WRITE = 2 + +IOCTL_STORAGE_EJECT_MEDIA = 2967560 + +OPEN_EXISTING = 3 + +## Removable drive support for windows +class WindowsRemovableDrivePlugin(RemovableDrivePlugin.RemovableDrivePlugin): + def checkRemovableDrives(self): + drives = {} + + bitmask = windll.kernel32.GetLogicalDrives() + # Check possible drive letters, from A to Z + # Note: using ascii_uppercase because we do not want this to change with locale! + for letter in string.ascii_uppercase: + drive = "{0}:/".format(letter) + + # Do we really want to skip A and B? + # GetDriveTypeA explicitly wants a byte array of type ascii. It will accept a string, but this wont work + if bitmask & 1 and windll.kernel32.GetDriveTypeA(drive.encode("ascii")) == DRIVE_REMOVABLE: + volume_name = "" + name_buffer = ctypes.create_unicode_buffer(1024) + filesystem_buffer = ctypes.create_unicode_buffer(1024) + error = windll.kernel32.GetVolumeInformationW(ctypes.c_wchar_p(drive), name_buffer, ctypes.sizeof(name_buffer), None, None, None, filesystem_buffer, ctypes.sizeof(filesystem_buffer)) + + if error != 0: + volume_name = name_buffer.value + + if not volume_name: + volume_name = catalog.i18nc("Default name for removable device", "Removable Drive") + + # Certain readers will report themselves as a volume even when there is no card inserted, but will show an + # "No volume in drive" warning when trying to call GetDiskFreeSpace. However, they will not report a valid + # filesystem, so we can filter on that. In addition, this excludes other things with filesystems Windows + # does not support. + if filesystem_buffer.value == "": + continue + + # Check for the free space. Some card readers show up as a drive with 0 space free when there is no card inserted. + freeBytes = ctypes.c_longlong(0) + if windll.kernel32.GetDiskFreeSpaceExA(drive.encode("ascii"), ctypes.byref(freeBytes), None, None) == 0: + continue + + if freeBytes.value < 1: + continue + + drives[drive] = "{0} ({1}:)".format(volume_name, letter) + bitmask >>= 1 + + return drives + + def performEjectDevice(self, device): + # Magic WinAPI stuff + # First, open a handle to the Device + handle = windll.kernel32.CreateFileA("\\\\.\\{0}".format(device.getId()[:-1]).encode("ascii"), GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, None, OPEN_EXISTING, 0, None ) + + if handle == -1: + print(windll.kernel32.GetLastError()) + return + + result = None + # Then, try and tell it to eject + if not windll.kernel32.DeviceIoControl(handle, IOCTL_STORAGE_EJECT_MEDIA, None, None, None, None, None, None): + result = False + else: + result = True + + # Finally, close the handle + windll.kernel32.CloseHandle(handle) + return result diff --git a/plugins/RemovableDriveOutputDevice/__init__.py b/plugins/RemovableDriveOutputDevice/__init__.py new file mode 100644 index 0000000000..72ba10f01f --- /dev/null +++ b/plugins/RemovableDriveOutputDevice/__init__.py @@ -0,0 +1,32 @@ +# Copyright (c) 2015 Ultimaker B.V. +# Uranium is released under the terms of the AGPLv3 or higher. + +import platform + +from UM.i18n import i18nCatalog +catalog = i18nCatalog("uranium") + +def getMetaData(): + return { + "plugin": { + "name": catalog.i18nc("Removable Drive Output Device Plugin name", "Removable Drive Output Device Plugin"), + "author": "Ultimaker B.V.", + "description": catalog.i18nc("Removable Drive Output Device Plugin description", "Provides removable drive hotplugging and writing support"), + "version": "1.0", + "api": 2 + } + } + +def register(app): + if platform.system() == "Windows": + from . import WindowsRemovableDrivePlugin + return { "output_device": WindowsRemovableDrivePlugin.WindowsRemovableDrivePlugin() } + elif platform.system() == "Darwin": + from . import OSXRemovableDrivePlugin + return { "output_device": OSXRemovableDrivePlugin.OSXRemovableDrivePlugin() } + elif platform.system() == "Linux": + from . import LinuxRemovableDrivePlugin + return { "output_device": LinuxRemovableDrivePlugin.LinuxRemovableDrivePlugin() } + else: + Logger.log("e", "Unsupported system %s, no removable device hotplugging support available.", platform.system()) + return { } diff --git a/resources/qml/AddMachineWizard.qml b/resources/qml/AddMachineWizard.qml index 06341e140c..5d6608a7da 100644 --- a/resources/qml/AddMachineWizard.qml +++ b/resources/qml/AddMachineWizard.qml @@ -8,78 +8,9 @@ import QtQuick.Window 2.1 import UM 1.0 as UM -UM.Dialog -{ +UM.Wizard{ id: base - - //: Add Printer dialog title - title: qsTr("Add Printer"); - - ColumnLayout - { - anchors.fill: parent; - - Label - { - //: Add Printer wizard page title - text: qsTr("Add Printer"); - font.pointSize: 18; - } - - Label - { - //: Add Printer wizard page description - text: qsTr("Please select the type of printer:"); - } - - ScrollView - { - Layout.fillWidth: true; - - ListView - { - id: machineList; - model: UM.Models.availableMachinesModel - delegate: RadioButton - { - exclusiveGroup: printerGroup; - text: model.name; - onClicked: ListView.view.currentIndex = index; - } - } - } - - Label { - //: Add Printer wizard field label - text: qsTr("Printer Name:"); - } - - TextField { id: machineName; Layout.fillWidth: true; text: machineList.model.getItem(machineList.currentIndex).name } - - Item { Layout.fillWidth: true; Layout.fillHeight: true; } - - ExclusiveGroup { id: printerGroup; } - } - - rightButtons: [ - Button - { - //: Add Printer wizarad button - text: qsTr("Next"); - onClicked: - { - if(machineList.currentIndex != -1) - { - UM.Models.availableMachinesModel.createMachine(machineList.currentIndex, machineName.text) - base.visible = false - } - } - }, - Button - { - //: Add Printer wizarad button - text: qsTr("Cancel"); - onClicked: base.visible = false; - } - ] + property bool printer: true + file: "ultimaker2.json" + firstRun: printer ? false : true } diff --git a/resources/qml/Cura.qml b/resources/qml/Cura.qml index 35a1ad5e6c..637a4298eb 100644 --- a/resources/qml/Cura.qml +++ b/resources/qml/Cura.qml @@ -7,7 +7,7 @@ import QtQuick.Controls.Styles 1.1 import QtQuick.Layouts 1.1 import QtQuick.Dialogs 1.1 -import UM 1.0 as UM +import UM 1.1 as UM UM.MainWindow { id: base @@ -30,24 +30,52 @@ UM.MainWindow { title: qsTr("&File"); MenuItem { action: actions.open; } - MenuItem { action: actions.save; } + + Menu { + id: recentFilesMenu; + title: "Open Recent" + iconName: "document-open-recent"; + + enabled: Printer.recentFiles.length > 0; + + Instantiator { + model: Printer.recentFiles + MenuItem { + text: { + var path = modelData.toString() + return (index + 1) + ". " + path.slice(path.lastIndexOf("/") + 1); + } + onTriggered: UM.MeshFileHandler.readLocalFile(modelData); + } + onObjectAdded: recentFilesMenu.insertItem(index, object) + onObjectRemoved: recentFilesMenu.removeItem(object) + } + } MenuSeparator { } - Instantiator { - model: Printer.recentFiles - MenuItem { - text: { - var path = modelData.toString() - return (index + 1) + ". " + path.slice(path.lastIndexOf("/") + 1); - } - onTriggered: { - UM.MeshFileHandler.readLocalFile(modelData); - Printer.setPlatformActivity(true) + MenuItem { + text: "Save Selection to File"; + enabled: UM.Selection.hasSelection; + iconName: "document-save-as"; + onTriggered: UM.OutputDeviceManager.requestWriteSelectionToDevice("local_file"); + } + Menu { + id: saveAllMenu + title: "Save All" + iconName: "document-save"; + enabled: devicesModel.count > 0 && UM.Backend.progress > 0.99; + + Instantiator { + model: UM.OutputDevicesModel { id: devicesModel; } + + MenuItem { + text: model.description; + onTriggered: UM.OutputDeviceManager.requestWriteToDevice(model.id); } + onObjectAdded: saveAllMenu.insertItem(index, object) + onObjectRemoved: saveAllMenu.removeItem(object) } - onObjectAdded: fileMenu.insertItem(index, object) - onObjectRemoved: fileMenu.removeItem(object) } MenuSeparator { } @@ -65,7 +93,26 @@ UM.MainWindow { MenuItem { action: actions.deleteSelection; } MenuItem { action: actions.deleteAll; } } - + Menu + { + title: qsTr("&View"); + id: top_view_menu + Instantiator + { + model: UM.Models.viewModel + MenuItem + { + text: model.name; + checkable: true; + checked: model.active; + exclusiveGroup: view_menu_top_group; + onTriggered: UM.Controller.setActiveView(model.id); + } + onObjectAdded: top_view_menu.insertItem(index, object) + onObjectRemoved: top_view_menu.removeItem(object) + } + ExclusiveGroup { id: view_menu_top_group; } + } Menu { id: machineMenu; //: Machine menu @@ -281,7 +328,6 @@ UM.MainWindow { addMachineAction: actions.addMachine; configureMachinesAction: actions.configureMachines; - saveAction: actions.save; } Rectangle { @@ -368,7 +414,7 @@ UM.MainWindow { resetAll.onTriggered: Printer.resetAll() reloadAll.onTriggered: Printer.reloadAll() - addMachine.onTriggered: addMachine.visible = true; + addMachine.onTriggered: addMachineWizard.visible = true; preferences.onTriggered: preferences.visible = true; configureMachines.onTriggered: { preferences.visible = true; preferences.setPage(2); } @@ -439,38 +485,25 @@ UM.MainWindow { } } - FileDialog { - id: saveDialog; - //: File save dialog title - title: qsTr("Save File"); - selectExisting: false; - - modality: UM.Application.platform == "linux" ? Qt.NonModal : Qt.WindowModal; - - nameFilters: UM.MeshFileHandler.supportedWriteFileTypes - - onAccepted: - { - UM.MeshFileHandler.writeLocalFile(fileUrl); - } - } - EngineLog { id: engineLog; } AddMachineWizard { - id: addMachine; + id: addMachineWizard } + AboutDialog { id: aboutDialog } Connections { target: Printer - onRequestAddPrinter: addMachine.visible = true; - onWriteToLocalFileRequested: saveDialog.open(); + onRequestAddPrinter: { + addMachineWizard.visible = true + addMachineWizard.printer = false + } } Component.onCompleted: UM.Theme.load(UM.Resources.getPath(UM.Resources.ThemesLocation, "cura")) diff --git a/resources/qml/SaveButton.qml b/resources/qml/SaveButton.qml index 004b523336..ec2066713f 100644 --- a/resources/qml/SaveButton.qml +++ b/resources/qml/SaveButton.qml @@ -6,47 +6,18 @@ import QtQuick.Controls 1.1 import QtQuick.Controls.Styles 1.1 import QtQuick.Layouts 1.1 -import UM 1.0 as UM +import UM 1.1 as UM Rectangle { id: base; - property Action saveAction; - property real progress: UM.Backend.progress; property bool activity: Printer.getPlatformActivity; Behavior on progress { NumberAnimation { duration: 250; } } - property string currentDevice: "local_file" - property bool defaultOverride: false; - property bool defaultAmbiguous: false; - property variant printDuration: PrintInformation.currentPrintTime; property real printMaterialAmount: PrintInformation.materialAmount; - Connections { - target: Printer; - onOutputDevicesChanged: { - if(!base.defaultOverride) { - base.defaultAmbiguous = false; - var device = null; - for(var i in Printer.outputDevices) { - if(device == null) { - device = i; - } else if(Printer.outputDevices[i].priority > Printer.outputDevices[device].priority) { - device = i; - } else if(Printer.outputDevices[i].priority == Printer.outputDevices[device].priority) { - base.defaultAmbiguous = true; - } - } - - if(device != null) { - base.currentDevice = device; - } - } - } - } - Rectangle{ id: background implicitWidth: base.width; @@ -76,7 +47,7 @@ Rectangle { visible: base.progress >= 0 && base.progress < 0.99 ? false : true color: UM.Theme.colors.save_button_estimated_text; font: UM.Theme.fonts.small; - text: + text: { if(base.activity == false) { //: Save button label return qsTr("Please load a 3D model"); @@ -90,6 +61,8 @@ Rectangle { //: Save button label return qsTr("Estimated Print-time"); } + return ""; + } } Label { id: printDurationLabel @@ -113,7 +86,7 @@ Rectangle { elide: mediumLengthDuration ? Text.ElideRight : Text.ElideNone visible: base.activity == false || base.progress < 0.99 ? false : true //: Print material amount save button label - text: base.printMaterialAmount < 0 ? "" : qsTr("%1m material").arg(base.printMaterialAmount); + text: base.printMaterialAmount < 0 ? "" : qsTr("%1m of Material").arg(base.printMaterialAmount); } } Rectangle { @@ -134,29 +107,28 @@ Rectangle { anchors.topMargin: UM.Theme.sizes.save_button_text_margin.height; anchors.left: parent.left anchors.leftMargin: UM.Theme.sizes.default_margin.width; - tooltip: '' + tooltip: UM.OutputDeviceManager.activeDeviceDescription; enabled: progress > 0.99 && base.activity == true width: infoBox.width/6*4.5 height: UM.Theme.sizes.save_button_save_to_button.height + + text: UM.OutputDeviceManager.activeDeviceShortDescription; + style: ButtonStyle { background: Rectangle { color: !control.enabled ? UM.Theme.colors.save_button_inactive : control.hovered ? UM.Theme.colors.save_button_active_hover : UM.Theme.colors.save_button_active; + Label { - anchors.verticalCenter: parent.verticalCenter - anchors.horizontalCenter: parent.horizontalCenter + anchors.centerIn: parent color: UM.Theme.colors.save_button_safe_to_text; font: UM.Theme.fonts.sidebar_save_to; - text: Printer.outputDevices[base.currentDevice].shortDescription; + text: control.text; } } + label: Item { } } - onClicked: - if(base.defaultAmbiguous) { - devicesMenu.popup(); - } else { - Printer.writeToOutputDevice(base.currentDevice); - } + onClicked: UM.OutputDeviceManager.requestWriteToDevice(UM.OutputDeviceManager.activeDevice) } Button { @@ -165,16 +137,20 @@ Rectangle { anchors.topMargin: UM.Theme.sizes.save_button_text_margin.height anchors.right: parent.right anchors.rightMargin: UM.Theme.sizes.default_margin.width; - tooltip: '' + + tooltip: qsTr("Select the active output device"); width: infoBox.width/6*1.3 - UM.Theme.sizes.save_button_text_margin.height; height: UM.Theme.sizes.save_button_save_to_button.height + iconSource: UM.Theme.icons[UM.OutputDeviceManager.activeDeviceIconName]; + style: ButtonStyle { background: Rectangle { - color: UM.Theme.colors.save_button_background; - border.width: control.hovered ? UM.Theme.sizes.save_button_border.width : 0 - border.color: UM.Theme.colors.save_button_border + color: UM.Theme.colors.save_button_background; + border.width: control.hovered ? UM.Theme.sizes.save_button_border.width : 0 + border.color: UM.Theme.colors.save_button_border + Rectangle { id: deviceSelectionIcon color: UM.Theme.colors.save_button_background; @@ -183,14 +159,13 @@ Rectangle { anchors.verticalCenter: parent.verticalCenter; width: parent.height - UM.Theme.sizes.save_button_text_margin.width ; height: parent.height - UM.Theme.sizes.save_button_text_margin.width; + UM.RecolorImage { - anchors.centerIn: parent; - width: parent.width; - height: parent.height; + anchors.fill: parent; sourceSize.width: width; sourceSize.height: height; color: UM.Theme.colors.save_button_active - source: UM.Theme.icons[Printer.outputDevices[base.currentDevice].icon]; + source: control.iconSource; } } Label { @@ -203,24 +178,20 @@ Rectangle { color: UM.Theme.colors.save_button_active; } } + label: Item { } } menu: Menu { id: devicesMenu; Instantiator { - model: Printer.outputDeviceNames; + model: devicesModel; MenuItem { - text: Printer.outputDevices[modelData].description; + text: model.description checkable: true; - checked: base.defaultAmbiguous ? false : modelData == base.currentDevice; + checked: model.id == UM.OutputDeviceManager.activeDevice; exclusiveGroup: devicesMenuGroup; onTriggered: { - base.defaultOverride = true; - base.currentDevice = modelData; - if(base.defaultAmbiguous) { - base.defaultAmbiguous = false; - Printer.writeToOutputDevice(modelData); - } + UM.OutputDeviceManager.setActiveDevice(model.id); } } onObjectAdded: devicesMenu.insertItem(index, object) @@ -230,4 +201,8 @@ Rectangle { } } } -} \ No newline at end of file + + UM.OutputDevicesModel { + id: devicesModel; + } +} diff --git a/resources/qml/Sidebar.qml b/resources/qml/Sidebar.qml index 23ddfe4ed7..0c908fc789 100644 --- a/resources/qml/Sidebar.qml +++ b/resources/qml/Sidebar.qml @@ -13,7 +13,6 @@ Rectangle { property Action addMachineAction; property Action configureMachinesAction; - property alias saveAction: saveButton.saveAction; color: UM.Theme.colors.sidebar; diff --git a/resources/qml/WizardPages/AddMachine.qml b/resources/qml/WizardPages/AddMachine.qml new file mode 100644 index 0000000000..7e69c06366 --- /dev/null +++ b/resources/qml/WizardPages/AddMachine.qml @@ -0,0 +1,109 @@ +// Copyright (c) 2015 Ultimaker B.V. +// Cura is released under the terms of the AGPLv3 or higher. + +import QtQuick 2.2 +import QtQuick.Controls 1.1 +import QtQuick.Layouts 1.1 +import QtQuick.Window 2.1 + +import UM 1.0 as UM +import ".." + +ColumnLayout { + id: wizardPage + property string title + signal openFile(string fileName) + signal closeWizard() + + Connections { + target: rootElement + onFinalClicked: {//You can add functions here that get triggered when the final button is clicked in the wizard-element + saveMachine() + } + } + + Label { + text: parent.title + font.pointSize: 18; + } + + Label { + //: Add Printer wizard page description + text: qsTr("Please select the type of printer:"); + } + + ScrollView { + ListView { + id: machineList; + model: UM.Models.availableMachinesModel + delegate: RadioButton { + id:machine_button + exclusiveGroup: printerGroup; + checked: ListView.view.currentIndex == index ? true : false + text: model.name; + onClicked: { + ListView.view.currentIndex = index; + + } + } + } + } + + Label { + text: qsTr("Variation:"); + } + + ScrollView { + ListView { + id: variations_list + model: machineList.model.getItem(machineList.currentIndex).variations + delegate: RadioButton { + id: variation_radio_button + checked: ListView.view.currentIndex == index ? true : false + exclusiveGroup: variationGroup; + text: model.name; + onClicked: ListView.view.currentIndex = index; + } + } + } + + Label { + //: Add Printer wizard field label + text: qsTr("Printer Name:"); + } + + TextField { id: machineName; Layout.fillWidth: true; text: machineList.model.getItem(machineList.currentIndex).name } + Item { Layout.fillWidth: true; Layout.fillHeight: true; } + ExclusiveGroup { id: printerGroup; } + ExclusiveGroup { id: variationGroup; } + + function getSpecialMachineType(machineId){ + for (var i = 0; i < UM.Models.addMachinesModel.rowCount(); i++) { + if (UM.Models.addMachinesModel.getItem(i).name == machineId){ + return UM.Models.addMachinesModel.getItem(i).name + } + } + } + + function saveMachine(){ + if(machineList.currentIndex != -1) { + UM.Models.availableMachinesModel.createMachine(machineList.currentIndex, variations_list.currentIndex, machineName.text) + + var originalString = "Ultimaker Original" + var originalPlusString = "Ultimaker Original+" + var originalMachineType = getSpecialMachineType(originalString) + + if (UM.Models.availableMachinesModel.getItem(machineList.currentIndex).name == originalMachineType){ + var variation = UM.Models.availableMachinesModel.getItem(machineList.currentIndex).variations.getItem(variations_list.currentIndex).name + if (variation == originalString || variation == originalPlusString){ + console.log(UM.Models.availableMachinesModel.getItem(machineList.currentIndex).variations.getItem(variations_list.currentIndex).type) + wizardPage.openFile(UM.Models.availableMachinesModel.getItem(machineList.currentIndex).variations.getItem(variations_list.currentIndex).type) + } + } + else { + wizardPage.closeWizard() + } + } + } +} + diff --git a/resources/qml/WizardPages/Bedleveling.qml b/resources/qml/WizardPages/Bedleveling.qml new file mode 100644 index 0000000000..eb8207b37d --- /dev/null +++ b/resources/qml/WizardPages/Bedleveling.qml @@ -0,0 +1,52 @@ +// Copyright (c) 2015 Ultimaker B.V. +// Cura is released under the terms of the AGPLv3 or higher. + +import QtQuick 2.2 +import QtQuick.Controls 1.1 +import QtQuick.Layouts 1.1 +import QtQuick.Window 2.1 + +import UM 1.0 as UM + +ColumnLayout { + property string title + anchors.fill: parent; + + Label { + text: parent.title + font.pointSize: 18; + } + + Label { + //: Add Printer wizard page description + text: qsTr("Please select the type of printer:"); + } + + ScrollView { + Layout.fillWidth: true; + + ListView { + id: machineList; + model: UM.Models.availableMachinesModel + delegate: RadioButton { + exclusiveGroup: printerGroup; + text: model.name; + onClicked: { + ListView.view.currentIndex = index; + + } + } + } + } + + Label { + //: Add Printer wizard field label + text: qsTr("Printer Name:"); + } + + TextField { id: machineName; Layout.fillWidth: true; text: machineList.model.getItem(machineList.currentIndex).name } + + Item { Layout.fillWidth: true; Layout.fillHeight: true; } + + ExclusiveGroup { id: printerGroup; } +} \ No newline at end of file diff --git a/resources/qml/WizardPages/SelectUpgradedParts.qml b/resources/qml/WizardPages/SelectUpgradedParts.qml new file mode 100644 index 0000000000..eb8207b37d --- /dev/null +++ b/resources/qml/WizardPages/SelectUpgradedParts.qml @@ -0,0 +1,52 @@ +// Copyright (c) 2015 Ultimaker B.V. +// Cura is released under the terms of the AGPLv3 or higher. + +import QtQuick 2.2 +import QtQuick.Controls 1.1 +import QtQuick.Layouts 1.1 +import QtQuick.Window 2.1 + +import UM 1.0 as UM + +ColumnLayout { + property string title + anchors.fill: parent; + + Label { + text: parent.title + font.pointSize: 18; + } + + Label { + //: Add Printer wizard page description + text: qsTr("Please select the type of printer:"); + } + + ScrollView { + Layout.fillWidth: true; + + ListView { + id: machineList; + model: UM.Models.availableMachinesModel + delegate: RadioButton { + exclusiveGroup: printerGroup; + text: model.name; + onClicked: { + ListView.view.currentIndex = index; + + } + } + } + } + + Label { + //: Add Printer wizard field label + text: qsTr("Printer Name:"); + } + + TextField { id: machineName; Layout.fillWidth: true; text: machineList.model.getItem(machineList.currentIndex).name } + + Item { Layout.fillWidth: true; Layout.fillHeight: true; } + + ExclusiveGroup { id: printerGroup; } +} \ No newline at end of file diff --git a/resources/qml/WizardPages/UltimakerCheckup.qml b/resources/qml/WizardPages/UltimakerCheckup.qml new file mode 100644 index 0000000000..eb8207b37d --- /dev/null +++ b/resources/qml/WizardPages/UltimakerCheckup.qml @@ -0,0 +1,52 @@ +// Copyright (c) 2015 Ultimaker B.V. +// Cura is released under the terms of the AGPLv3 or higher. + +import QtQuick 2.2 +import QtQuick.Controls 1.1 +import QtQuick.Layouts 1.1 +import QtQuick.Window 2.1 + +import UM 1.0 as UM + +ColumnLayout { + property string title + anchors.fill: parent; + + Label { + text: parent.title + font.pointSize: 18; + } + + Label { + //: Add Printer wizard page description + text: qsTr("Please select the type of printer:"); + } + + ScrollView { + Layout.fillWidth: true; + + ListView { + id: machineList; + model: UM.Models.availableMachinesModel + delegate: RadioButton { + exclusiveGroup: printerGroup; + text: model.name; + onClicked: { + ListView.view.currentIndex = index; + + } + } + } + } + + Label { + //: Add Printer wizard field label + text: qsTr("Printer Name:"); + } + + TextField { id: machineName; Layout.fillWidth: true; text: machineList.model.getItem(machineList.currentIndex).name } + + Item { Layout.fillWidth: true; Layout.fillHeight: true; } + + ExclusiveGroup { id: printerGroup; } +} \ No newline at end of file diff --git a/resources/qml/WizardPages/UpgradeFirmware.qml b/resources/qml/WizardPages/UpgradeFirmware.qml new file mode 100644 index 0000000000..9369df6e42 --- /dev/null +++ b/resources/qml/WizardPages/UpgradeFirmware.qml @@ -0,0 +1,52 @@ +// Copyright (c) 2015 Ultimaker B.V. +// Cura is released under the terms of the AGPLv3 or higher. + +import QtQuick 2.2 +import QtQuick.Controls 1.1 +import QtQuick.Layouts 1.1 +import QtQuick.Window 2.1 + +import UM 1.0 as UM + +ColumnLayout { + property string title + anchors.fill: parent; + signal openFile(string fileName) + + Label { + text: parent.title + font.pointSize: 18; + } + + Label { + //: Add Printer wizard page description + text: qsTr("Please select the type of printer:"); + } + + ScrollView { + Layout.fillWidth: true; + + ListView { + id: machineList; + model: UM.Models.availableMachinesModel + delegate: RadioButton { + exclusiveGroup: printerGroup; + text: model.name; + onClicked: { + ListView.view.currentIndex = index; + } + } + } + } + + Label { + //: Add Printer wizard field label + text: qsTr("Printer Name:"); + } + + TextField { id: machineName; Layout.fillWidth: true; text: machineList.model.getItem(machineList.currentIndex).name } + + Item { Layout.fillWidth: true; Layout.fillHeight: true; } + + ExclusiveGroup { id: printerGroup; } +} \ No newline at end of file diff --git a/resources/settings/fdmprinter.json b/resources/settings/fdmprinter.json index 15efd866b5..63fc092932 100644 --- a/resources/settings/fdmprinter.json +++ b/resources/settings/fdmprinter.json @@ -1,5 +1,12 @@ { "visible": false, + "version": 1, + "author": "other", + "manufacturer": "other", + "author": "other", + + "add_pages": [{"page": "AddMachine", "title": "Add new printer"}], + "machine_settings": { "machine_start_gcode": { "default": "G28 ; Home\nG1 Z15.0 F6000 ;move the platform down 15mm\n;Prime the extruder\nG92 E0\nG1 F200 E3\nG92 E0" diff --git a/resources/settings/grr_neo.json b/resources/settings/grr_neo.json index fbec330adc..66b1248017 100644 --- a/resources/settings/grr_neo.json +++ b/resources/settings/grr_neo.json @@ -1,6 +1,9 @@ { - "id": "GRRneo", + "id": "grr_neo", + "version": 1, "name": "German RepRap Neo", + "manufacturer": "German RepRap", + "author": "other", "icon": "icon_ultimaker.png", "platform": "grr_neo_platform.stl", diff --git a/resources/settings/hephestos.json b/resources/settings/hephestos.json index ce97fc21a5..4158ddaf53 100644 --- a/resources/settings/hephestos.json +++ b/resources/settings/hephestos.json @@ -1,6 +1,9 @@ { "id": "hephestos", + "version": 1, "name": "BQ Prusa i3 Hephestos", + "manufacturer": "BQ", + "author": "other", "platform": "hephestos_platform.stl", "inherits": "fdmprinter.json", diff --git a/resources/settings/hephestos_XL.json b/resources/settings/hephestos_xl.json similarity index 98% rename from resources/settings/hephestos_XL.json rename to resources/settings/hephestos_xl.json index 5a308bab2b..74583d55d2 100644 --- a/resources/settings/hephestos_XL.json +++ b/resources/settings/hephestos_xl.json @@ -1,6 +1,9 @@ { - "id": "hephestos_XL", + "id": "hephestos_xl", + "version": 1, "name": "BQ Prusa i3 Hephestos XL", + "manufacturer": "BQ", + "author": "other", "platform": "hephestos_platform.stl", "inherits": "fdmprinter.json", diff --git a/resources/settings/prusai3.json b/resources/settings/prusa_i3.json similarity index 96% rename from resources/settings/prusai3.json rename to resources/settings/prusa_i3.json index d411a114e8..e0ff59c073 100644 --- a/resources/settings/prusai3.json +++ b/resources/settings/prusa_i3.json @@ -1,7 +1,10 @@ { "id": "prusa_i3", - "icon": "icon_ultimaker2.png", + "version": 1, "name": "Prusa i3", + "manufacturer": "Prusa", + "author": "other", + "icon": "icon_ultimaker2.png", "platform": "prusai3_platform.stl", "inherits": "fdmprinter.json", diff --git a/resources/settings/ultimaker2.json b/resources/settings/ultimaker2.json index f845573ec7..6810a9a3d6 100644 --- a/resources/settings/ultimaker2.json +++ b/resources/settings/ultimaker2.json @@ -1,6 +1,9 @@ { "id": "ultimaker2", + "version": 1, "name": "Ultimaker 2", + "manufacturer": "Ultimaker", + "author": "Ultimaker", "icon": "icon_ultimaker2.png", "platform": "ultimaker2_platform.obj", "platform_texture": "Ultimaker2backplate.png", diff --git a/resources/settings/ultimaker2extended.json b/resources/settings/ultimaker2_extended.json similarity index 76% rename from resources/settings/ultimaker2extended.json rename to resources/settings/ultimaker2_extended.json index 7ef1e35d3f..ef4f2d5141 100644 --- a/resources/settings/ultimaker2extended.json +++ b/resources/settings/ultimaker2_extended.json @@ -1,6 +1,9 @@ { - "id": "ultimaker2extended", + "id": "ultimaker2_extended", + "version": 1, "name": "Ultimaker 2 Extended", + "manufacturer": "Ultimaker", + "author": "Ultimaker", "icon": "icon_ultimaker2.png", "platform": "ultimaker2_platform.obj", "platform_texture": "Ultimaker2backplate.png", diff --git a/resources/settings/ultimaker2go.json b/resources/settings/ultimaker2_go.json similarity index 89% rename from resources/settings/ultimaker2go.json rename to resources/settings/ultimaker2_go.json index 6d102c4e7c..708863f70d 100644 --- a/resources/settings/ultimaker2go.json +++ b/resources/settings/ultimaker2_go.json @@ -1,6 +1,9 @@ { - "id": "ultimaker2go", + "id": "ultimaker2_go", + "version": 1, "name": "Ultimaker 2 Go", + "manufacturer": "Ultimaker", + "author": "Ultimaker", "icon": "icon_ultimaker2.png", "platform": "ultimaker2go_platform.obj", "platform_texture": "Ultimaker2backplate.png", diff --git a/resources/settings/ultimaker_original.json b/resources/settings/ultimaker_original.json index 360bbbe6b6..81bb2bf178 100644 --- a/resources/settings/ultimaker_original.json +++ b/resources/settings/ultimaker_original.json @@ -1,11 +1,21 @@ { "id": "ultimaker_original", + "version": 1, "name": "Ultimaker Original", + "manufacturer": "Ultimaker", + "author": "Ultimaker", "icon": "icon_ultimaker.png", "platform": "ultimaker_platform.stl", "inherits": "fdmprinter.json", + "add_pages": [ + {"page": "SelectUpgradedParts", "title": "Select Upgraded Parts"}, + {"page": "UpgradeFirmware", "title": "Upgrade Ultimaker Firmware"}, + {"page": "UltimakerCheckup", "title": "Ultimaker Checkup"}, + {"page": "Bedleveling", "title": "Bedleveling Wizard"} + ], + "machine_settings": { "machine_width": { "default": 205 }, "machine_height": { "default": 200 }, diff --git a/resources/settings/ultimaker_original_plus.json b/resources/settings/ultimaker_original_plus.json index 9e5cf6370d..73fdcc2370 100644 --- a/resources/settings/ultimaker_original_plus.json +++ b/resources/settings/ultimaker_original_plus.json @@ -1,6 +1,9 @@ { "id": "ultimaker_original_plus", + "version": 1, "name": "Ultimaker Original+", + "manufacturer": "Ultimaker", + "author": "Ultimaker", "icon": "icon_ultimaker.png", "platform": "ultimaker2_platform.obj", "platform_texture": "UltimakerPlusbackplate.png", diff --git a/resources/settings/witbox.json b/resources/settings/witbox.json index bafcceccdd..c79ee169b2 100644 --- a/resources/settings/witbox.json +++ b/resources/settings/witbox.json @@ -1,6 +1,9 @@ { - "id": "witbox", + "id": "bq_witbox", + "version": 1, "name": "BQ Witbox", + "manufacturer": "BQ", + "author": "other", "platform": "witbox_platform.stl", "inherits": "fdmprinter.json", diff --git a/src/__init__.py b/src/__init__.py deleted file mode 100644 index e69de29bb2..0000000000