def get_cura_dir_path(): if Platform.isWindows(): return os.path.expanduser("~/AppData/Local/cura/") elif Platform.isLinux(): return os.path.expanduser("~/.local/share/cura") elif Platform.isOSX(): return os.path.expanduser("~/Library/Logs/cura")
def get_cura_dir_path(): if Platform.isWindows(): return os.path.expanduser("~/AppData/Roaming/" + CuraAppName) elif Platform.isLinux(): return os.path.expanduser("~/.local/share/" + CuraAppName) elif Platform.isOSX(): return os.path.expanduser("~/Library/Logs/" + CuraAppName)
def exportContainer(self, container_id: str, file_type: str, file_url_or_string: Union[QUrl, str]) -> Dict[str, str]: if not container_id or not file_type or not file_url_or_string: return { "status": "error", "message": "Invalid arguments"} if isinstance(file_url_or_string, QUrl): file_url = file_url_or_string.toLocalFile() else: file_url = file_url_or_string if not file_url: return { "status": "error", "message": "Invalid path"} mime_type = None if not file_type in self._container_name_filters: try: mime_type = MimeTypeDatabase.getMimeTypeForFile(file_url) except MimeTypeNotFoundError: return { "status": "error", "message": "Unknown File Type" } else: mime_type = self._container_name_filters[file_type]["mime"] containers = self._container_registry.findContainers(None, id = container_id) if not containers: return { "status": "error", "message": "Container not found"} container = containers[0] if Platform.isOSX() and "." in file_url: file_url = file_url[:file_url.rfind(".")] for suffix in mime_type.suffixes: if file_url.endswith(suffix): break else: file_url += "." + mime_type.preferredSuffix if not Platform.isWindows(): if os.path.exists(file_url): result = QMessageBox.question(None, catalog.i18nc("@title:window", "File Already Exists"), catalog.i18nc("@label", "The file <filename>{0}</filename> already exists. Are you sure you want to overwrite it?").format(file_url)) if result == QMessageBox.No: return { "status": "cancelled", "message": "User cancelled"} try: contents = container.serialize() except NotImplementedError: return { "status": "error", "message": "Unable to serialize container"} if contents is None: return {"status": "error", "message": "Serialization returned None. Unable to write to file"} with SaveFile(file_url, "w") as f: f.write(contents) return { "status": "success", "message": "Succesfully exported container", "path": file_url}
def _getPossibleConfigStorageRootPathList(cls): # Returns all possible root paths for storing app configurations (in old and new versions) config_root_list = [Resources._getConfigStorageRootPath()] if Platform.isWindows(): # it used to be in LOCALAPPDATA on Windows config_root_list.append(os.getenv("LOCALAPPDATA")) elif Platform.isOSX(): config_root_list.append(os.path.expanduser("~")) config_root_list = [os.path.join(n, cls.ApplicationIdentifier) for n in config_root_list] return config_root_list
def register(app): if Platform.isWindows(): from . import WindowsRemovableDrivePlugin return { "output_device": WindowsRemovableDrivePlugin.WindowsRemovableDrivePlugin() } elif Platform.isOSX(): from . import OSXRemovableDrivePlugin return { "output_device": OSXRemovableDrivePlugin.OSXRemovableDrivePlugin() } elif Platform.isLinux(): from . import LinuxRemovableDrivePlugin return { "output_device": LinuxRemovableDrivePlugin.LinuxRemovableDrivePlugin() } else: Logger.log("e", "Unsupported system, thus no removable device hotplugging support available.") return { }
def _getCacheStorageRootPath(cls): # Returns the path where we store different versions of app configurations cache_path = None if Platform.isWindows(): cache_path = os.getenv("LOCALAPPDATA") elif Platform.isOSX(): cache_path = None elif Platform.isLinux(): try: cache_path = os.environ["XDG_CACHE_HOME"] except KeyError: cache_path = os.path.expanduser("~/.cache") return cache_path
def _sendCrashReport(self): # Before sending data, the user comments are stored self.data["user_info"] = self.user_description_text_area.toPlainText() # Convert data to bytes binary_data = json.dumps(self.data).encode("utf-8") # Submit data kwoptions = {"data": binary_data, "timeout": 5} if Platform.isOSX(): kwoptions["context"] = ssl._create_unverified_context() Logger.log("i", "Sending crash report info to [%s]...", self.crash_url) if not self.has_started: print("Sending crash report info to [%s]...\n" % self.crash_url) try: f = urllib.request.urlopen(self.crash_url, **kwoptions) Logger.log("i", "Sent crash report info.") if not self.has_started: print("Sent crash report info.\n") f.close() except urllib.error.HTTPError as e: Logger.logException("e", "An HTTP error occurred while trying to send crash report") if not self.has_started: print("An HTTP error occurred while trying to send crash report: %s" % e) except Exception as e: # We don't want any exception to cause problems Logger.logException("e", "An exception occurred while trying to send crash report") if not self.has_started: print("An exception occurred while trying to send crash report: %s" % e) os._exit(1)
def restore(self) -> bool: if not self.zip_file or not self.meta_data or not self.meta_data.get("cura_release", None): # We can restore without the minimum required information. Logger.log("w", "Tried to restore a Cura backup without having proper data or meta data.") self._showMessage( self.catalog.i18nc("@info:backup_failed", "Tried to restore a Cura backup without having proper data or meta data.")) return False current_version = self._application.getVersion() version_to_restore = self.meta_data.get("cura_release", "master") if current_version < version_to_restore: # Cannot restore version newer than current because settings might have changed. Logger.log("d", "Tried to restore a Cura backup of version {version_to_restore} with cura version {current_version}".format(version_to_restore = version_to_restore, current_version = current_version)) self._showMessage( self.catalog.i18nc("@info:backup_failed", "Tried to restore a Cura backup that is higher than the current version.")) return False version_data_dir = Resources.getDataStoragePath() archive = ZipFile(io.BytesIO(self.zip_file), "r") extracted = self._extractArchive(archive, version_data_dir) # Under Linux, preferences are stored elsewhere, so we copy the file to there. if Platform.isLinux(): preferences_file_name = self._application.getApplicationName() preferences_file = Resources.getPath(Resources.Preferences, "{}.cfg".format(preferences_file_name)) backup_preferences_file = os.path.join(version_data_dir, "{}.cfg".format(preferences_file_name)) Logger.log("d", "Moving preferences file from %s to %s", backup_preferences_file, preferences_file) shutil.move(backup_preferences_file, preferences_file) return extracted
def _createSocket(self, protocol_file): if self._socket: Logger.log("d", "Previous socket existed. Closing that first.") # temp debug logging self._socket.stateChanged.disconnect(self._onSocketStateChanged) self._socket.messageReceived.disconnect(self._onMessageReceived) self._socket.error.disconnect(self._onSocketError) # Hack for (at least) Linux. If the socket is connecting, the close will deadlock. while self._socket.getState() == Arcus.SocketState.Opening: sleep(0.1) # If the error occurred due to parsing, both connections believe that connection is okay. # So we need to force a close. self._socket.close() self._socket = SignalSocket() self._socket.stateChanged.connect(self._onSocketStateChanged) self._socket.messageReceived.connect(self._onMessageReceived) self._socket.error.connect(self._onSocketError) if Platform.isWindows(): # On Windows, the Protobuf DiskSourceTree does stupid things with paths. # So convert to forward slashes here so it finds the proto file properly. # Using sys.getfilesystemencoding() avoid the application crashing if it is # installed on a path with non-ascii characters GitHub issue #3907 protocol_file = protocol_file.replace("\\", "/").encode(sys.getfilesystemencoding()) if not self._socket.registerAllMessageTypes(protocol_file): Logger.log("e", "Could not register Uranium protocol messages: %s", self._socket.getLastError()) if UM.Application.Application.getInstance().getUseExternalBackend(): Logger.log("i", "Listening for backend connections on %s", self._port) self._socket.listen("127.0.0.1", self._port)
def _getConfigStorageRootPath(cls): # Returns the path where we store different versions of app configurations config_path = None if Platform.isWindows(): config_path = os.getenv("APPDATA") elif Platform.isOSX(): config_path = os.path.expanduser("~/Library/Application Support") elif Platform.isLinux(): try: config_path = os.environ["XDG_CONFIG_HOME"] except KeyError: config_path = os.path.expanduser("~/.config") else: config_path = "." return config_path
def getMetaData(): # Workarround for osx not supporting double file extensions correctly. if Platform.isOSX(): workspace_extension = "3mf" else: workspace_extension = "curaproject.3mf" metaData = {} if "3MFWriter.ThreeMFWriter" in sys.modules: metaData["mesh_writer"] = { "output": [{ "extension": "3mf", "description": i18n_catalog.i18nc("@item:inlistbox", "3MF file"), "mime_type": "application/vnd.ms-package.3dmanufacturing-3dmodel+xml", "mode": ThreeMFWriter.ThreeMFWriter.OutputMode.BinaryMode }] } metaData["workspace_writer"] = { "output": [{ "extension": workspace_extension, "description": i18n_catalog.i18nc("@item:inlistbox", "Cura Project 3MF file"), "mime_type": "application/x-curaproject+xml", "mode": ThreeMFWorkspaceWriter.ThreeMFWorkspaceWriter.OutputMode.BinaryMode }] } return metaData
def getMetaData() -> Dict: # Workarround for osx not supporting double file extensions correclty. if Platform.isOSX(): workspace_extension = "3mf" else: workspace_extension = "curaproject.3mf" metaData = { "plugin": { "name": catalog.i18nc("@label", "3MF Reader"), "author": "Ultimaker", "version": "1.0", "description": catalog.i18nc("@info:whatsthis", "Provides support for reading 3MF files."), "api": 3 } } if "3MFReader.ThreeMFReader" in sys.modules: metaData["mesh_reader"] = [ { "extension": "3mf", "description": catalog.i18nc("@item:inlistbox", "3MF File") } ] metaData["workspace_reader"] = [ { "extension": workspace_extension, "description": catalog.i18nc("@item:inlistbox", "3MF File") } ] return metaData
def _createSocket(self, protocol_file): if self._socket: self._socket.stateChanged.disconnect(self._onSocketStateChanged) self._socket.messageReceived.disconnect(self._onMessageReceived) self._socket.error.disconnect(self._onSocketError) # If the error occured due to parsing, both connections believe that connection is okay. So we need to force a close. self._socket.close() self._socket = SignalSocket() self._socket.stateChanged.connect(self._onSocketStateChanged) self._socket.messageReceived.connect(self._onMessageReceived) self._socket.error.connect(self._onSocketError) if Platform.isWindows(): # On Windows, the Protobuf DiskSourceTree does stupid things with paths. # So convert to forward slashes here so it finds the proto file properly. protocol_file = protocol_file.replace("\\", "/") if not self._socket.registerAllMessageTypes(protocol_file): Logger.log("e", "Could not register Cura protocol messages: %s", self._socket.getLastError()) if Application.getInstance().getCommandLineOption("external-backend", False): Logger.log("i", "Listening for backend connections on %s", self._port) self._socket.listen("127.0.0.1", self._port)
def _getConfigStorageRootPath(cls) -> str: # Returns the path where we store different versions of app configurations if Platform.isWindows(): config_path = os.getenv("APPDATA") if not config_path: # Protect if the getenv function returns None (it should never happen) config_path = "." elif Platform.isOSX(): config_path = os.path.expanduser("~/Library/Application Support") elif Platform.isLinux(): try: config_path = os.environ["XDG_CONFIG_HOME"] except KeyError: config_path = os.path.expanduser("~/.config") else: config_path = "." return config_path
def _getDataStorageRootPath(cls): # Returns the path where we store different versions of app data data_path = None if Platform.isLinux(): try: data_path = os.environ["XDG_DATA_HOME"] except KeyError: data_path = os.path.expanduser("~/.local/share") return data_path
def _updateContainerNameFilters(self) -> None: self._container_name_filters = {} for plugin_id, container_type in self._container_registry.getContainerTypes(): # Ignore default container types since those are not plugins if container_type in (InstanceContainer, ContainerStack, DefinitionContainer): continue serialize_type = "" try: plugin_metadata = self._plugin_registry.getMetaData(plugin_id) if plugin_metadata: serialize_type = plugin_metadata["settings_container"]["type"] else: continue except KeyError as e: continue mime_type = self._container_registry.getMimeTypeForContainer(container_type) if mime_type is None: continue entry = { "type": serialize_type, "mime": mime_type, "container": container_type } suffix = mime_type.preferredSuffix if Platform.isOSX() and "." in suffix: # OSX's File dialog is stupid and does not allow selecting files with a . in its name suffix = suffix[suffix.index(".") + 1:] suffix_list = "*." + suffix for suffix in mime_type.suffixes: if suffix == mime_type.preferredSuffix: continue if Platform.isOSX() and "." in suffix: # OSX's File dialog is stupid and does not allow selecting files with a . in its name suffix = suffix[suffix.index("."):] suffix_list += ", *." + suffix name_filter = "{0} ({1})".format(mime_type.comment, suffix_list) self._container_name_filters[name_filter] = entry
def exportProfile(self, instance_ids, file_name, file_type): # Parse the fileType to deduce what plugin can save the file format. # fileType has the format "<description> (*.<extension>)" split = file_type.rfind(" (*.") # Find where the description ends and the extension starts. if split < 0: # Not found. Invalid format. Logger.log("e", "Invalid file format identifier %s", file_type) return description = file_type[:split] extension = file_type[split + 4:-1] # Leave out the " (*." and ")". if not file_name.endswith("." + extension): # Auto-fill the extension if the user did not provide any. file_name += "." + extension # On Windows, QML FileDialog properly asks for overwrite confirm, but not on other platforms, so handle those ourself. if not Platform.isWindows(): if os.path.exists(file_name): result = QMessageBox.question(None, catalog.i18nc("@title:window", "File Already Exists"), catalog.i18nc("@label", "The file <filename>{0}</filename> already exists. Are you sure you want to overwrite it?").format(file_name)) if result == QMessageBox.No: return found_containers = [] extruder_positions = [] for instance_id in instance_ids: containers = ContainerRegistry.getInstance().findInstanceContainers(id=instance_id) if containers: found_containers.append(containers[0]) # Determine the position of the extruder of this container extruder_id = containers[0].getMetaDataEntry("extruder", "") if extruder_id == "": # Global stack extruder_positions.append(-1) else: extruder_containers = ContainerRegistry.getInstance().findDefinitionContainers(id=extruder_id) if extruder_containers: extruder_positions.append(int(extruder_containers[0].getMetaDataEntry("position", 0))) else: extruder_positions.append(0) # Ensure the profiles are always exported in order (global, extruder 0, extruder 1, ...) found_containers = [containers for (positions, containers) in sorted(zip(extruder_positions, found_containers))] profile_writer = self._findProfileWriter(extension, description) try: success = profile_writer.write(file_name, found_containers) except Exception as e: Logger.log("e", "Failed to export profile to %s: %s", file_name, str(e)) m = Message(catalog.i18nc("@info:status", "Failed to export profile to <filename>{0}</filename>: <message>{1}</message>", file_name, str(e)), lifetime = 0) m.show() return if not success: Logger.log("w", "Failed to export profile to %s: Writer plugin reported failure.", file_name) m = Message(catalog.i18nc("@info:status", "Failed to export profile to <filename>{0}</filename>: Writer plugin reported failure.", file_name), lifetime = 0) m.show() return m = Message(catalog.i18nc("@info:status", "Exported profile to <filename>{0}</filename>", file_name)) m.show()
def getMetaData(): file_extension = "gz" if Platform.isOSX() else "gcode.gz" return { "mesh_reader": [ { "extension": file_extension, "description": i18n_catalog.i18nc("@item:inlistbox", "Compressed G-code File") } ] }
def _getPossibleDataStorageRootPathList(cls) -> List[str]: data_root_list = [] # Returns all possible root paths for storing app configurations (in old and new versions) if Platform.isLinux(): data_root_list.append(os.path.join(Resources._getDataStorageRootPath(), cls.ApplicationIdentifier)) else: # on Windows and Mac, data and config are saved in the same place data_root_list = Resources._getPossibleConfigStorageRootPathList() return data_root_list
def supportedPluginExtensions(self): file_types = [] all_types = [] if Platform.isLinux(): for ext, desc in self._supported_file_types.items(): file_types.append("{0} (*.{1} *.{2})".format(desc, ext.lower(), ext.upper())) all_types.append("*.{0} *.{1}".format(ext.lower(), ext.upper())) else: for ext, desc in self._supported_file_types.items(): file_types.append("{0} (*.{1})".format(desc, ext)) all_types.append("*.{0}".format(ext)) file_types.sort() file_types.insert(0, i18n_catalog.i18nc("@item:inlistbox", "All Supported Types ({0})", " ".join(all_types))) file_types.append(i18n_catalog.i18nc("@item:inlistbox", "All Files (*)")) return file_types
def supportedPluginExtensions(self) -> List[str]: file_types = [] all_types = [] if Platform.isLinux(): for ext, desc in self._supported_file_types.items(): file_types.append("{0} (*.{1} *.{2})".format(desc, ext.lower(), ext.upper())) all_types.append("*.{0} *.{1}".format(ext.lower(), ext.upper())) else: for ext, desc in self._supported_file_types.items(): file_types.append("{0} (*.{1})".format(desc, ext)) all_types.append("*.{0}".format(ext)) file_types.sort() file_types.insert(0, i18n_catalog.i18nc("@item:inlistbox", "All Supported Types ({0})", " ".join(all_types))) file_types.append(i18n_catalog.i18nc("@item:inlistbox", "All Files (*)")) return file_types
def __exit__( self, exc_type: type, exc_val: Exception, exc_tb: Any ) -> None: #exc_tb is actually a traceback object which is not exposed in Python. """Release the lock file so that other processes may use it. :param exc_type: The type of exception that was raised during the ``with`` block, if any. Use ``None`` if no exception was raised. :param exc_val: The exception instance that was raised during the ``with`` block, if any. Use ``None`` if no exception was raised. :param exc_tb: The traceback frames at the time the exception occurred, if any. Use ``None`` if no exception was raised. """ if Platform.isWindows(): self._deleteLockFileWindows() else: self._deleteLockFile()
def makeFromCurrent(self) -> None: steslicer_release = self._application.getVersion() version_data_dir = Resources.getDataStoragePath() Logger.log("d", "Creating backup for STE Slicer %s, using folder %s", steslicer_release, version_data_dir) # Ensure all current settings are saved. self._application.saveSettings() # We copy the preferences file to the user data directory in Linux as it's in a different location there. # When restoring a backup on Linux, we move it back. if Platform.isLinux(): preferences_file_name = self._application.getApplicationName() preferences_file = Resources.getPath( Resources.Preferences, "{}.cfg".format(preferences_file_name)) backup_preferences_file = os.path.join( version_data_dir, "{}.cfg".format(preferences_file_name)) Logger.log("d", "Copying preferences file from %s to %s", preferences_file, backup_preferences_file) shutil.copyfile(preferences_file, backup_preferences_file) # Create an empty buffer and write the archive to it. buffer = io.BytesIO() archive = self._makeArchive(buffer, version_data_dir) if archive is None: return files = archive.namelist() # Count the metadata items. We do this in a rather naive way at the moment. machine_count = len([s for s in files if "machine_instances/" in s]) - 1 material_count = len([s for s in files if "materials/" in s]) - 1 profile_count = len([s for s in files if "quality_changes/" in s]) - 1 plugin_count = len([s for s in files if "plugin.json" in s]) # Store the archive and metadata so the BackupManager can fetch them when needed. self.zip_file = buffer.getvalue() self.meta_data = { "steslicer_release": steslicer_release, "machine_count": str(machine_count), "material_count": str(material_count), "profile_count": str(profile_count), "plugin_count": str(plugin_count) }
def event(self, event): if event.type == Event.ViewActivateEvent: # FIX: on Max OS X, somehow QOpenGLContext.currentContext() can become None during View switching. # This can happen when you do the following steps: # 1. Start Cura # 2. Load a model # 3. Switch to Custom mode # 4. Select the model and click on the per-object tool icon # 5. Switch view to Layer view or X-Ray # 6. Cura will very likely crash # It seems to be a timing issue that the currentContext can somehow be empty, but I have no clue why. # This fix tries to reschedule the view changing event call on the Qt thread again if the current OpenGL # context is None. if Platform.isOSX(): if QOpenGLContext.currentContext() is None: Logger.log("d", "current context of OpenGL is empty on Mac OS X, will try to create shaders later") CuraApplication.getInstance().callLater(lambda e = event: self.event(e)) return if not self._xray_pass: # Currently the RenderPass constructor requires a size > 0 # This should be fixed in RenderPass's constructor. self._xray_pass = XRayPass.XRayPass(1, 1) self.getRenderer().addRenderPass(self._xray_pass) if not self._xray_composite_shader: self._xray_composite_shader = OpenGL.getInstance().createShaderProgram(os.path.join(PluginRegistry.getInstance().getPluginPath("XRayView"), "xray_composite.shader")) theme = Application.getInstance().getTheme() self._xray_composite_shader.setUniformValue("u_background_color", Color(*theme.getColor("viewport_background").getRgb())) self._xray_composite_shader.setUniformValue("u_error_color", Color(*theme.getColor("xray_error").getRgb())) self._xray_composite_shader.setUniformValue("u_outline_color", Color(*theme.getColor("model_selection_outline").getRgb())) if not self._composite_pass: self._composite_pass = self.getRenderer().getRenderPass("composite") self._old_layer_bindings = self._composite_pass.getLayerBindings() self._composite_pass.setLayerBindings(["default", "selection", "xray"]) self._old_composite_shader = self._composite_pass.getCompositeShader() self._composite_pass.setCompositeShader(self._xray_composite_shader) if event.type == Event.ViewDeactivateEvent: self.getRenderer().removeRenderPass(self._xray_pass) self._composite_pass.setLayerBindings(self._old_layer_bindings) self._composite_pass.setCompositeShader(self._old_composite_shader)
def __initializeStoragePaths(cls): Logger.log("d", "Initializing storage paths") # use nested structure: <app-name>/<version>/... if cls.ApplicationVersion == "master" or cls.ApplicationVersion == "unknown": storage_dir_name = os.path.join(cls.ApplicationIdentifier, cls.ApplicationVersion) else: from UM.Version import Version version = Version(cls.ApplicationVersion) storage_dir_name = os.path.join( cls.ApplicationIdentifier, "%s.%s" % (version.getMajor(), version.getMinor())) # config is saved in "<CONFIG_ROOT>/<storage_dir_name>" cls.__config_storage_path = os.path.join( Resources._getConfigStorageRootPath(), storage_dir_name) Logger.log("d", "Config storage path is %s", cls.__config_storage_path) # data is saved in # - on Linux: "<DATA_ROOT>/<storage_dir_name>" # - on other: "<CONFIG_DIR>" (in the config directory) data_root_path = Resources._getDataStorageRootPath() cls.__data_storage_path = cls.__config_storage_path if data_root_path is None else \ os.path.join(data_root_path, storage_dir_name) Logger.log("d", "Data storage path is %s", cls.__data_storage_path) # cache is saved in # - on Linux: "<CACHE_DIR>/<storage_dir_name>" # - on Windows: "<CACHE_DIR>/<storage_dir_name>/cache" # - on Mac: "<CONFIG_DIR>/cache" (in the config directory) cache_root_path = Resources._getCacheStorageRootPath() if cache_root_path is None: cls.__cache_storage_path = os.path.join(cls.__config_storage_path, "cache") else: cls.__cache_storage_path = os.path.join(cache_root_path, storage_dir_name) if Platform.isWindows(): cls.__cache_storage_path = os.path.join( cls.__cache_storage_path, "cache") Logger.log("d", "Cache storage path is %s", cls.__cache_storage_path) if not os.path.exists(cls.__config_storage_path) or not os.path.exists( cls.__data_storage_path): cls._copyLatestDirsIfPresent() cls.__paths.insert(0, cls.__data_storage_path)
def __init__(self, **kwargs): # These collections must be treated as immutable otherwise we lose thread safety. self.__functions = WeakImmutableList() self.__methods = WeakImmutablePairList() self.__signals = WeakImmutableList() self.__lock = threading.Lock() # Guards access to the fields above. self.__type = kwargs.get("type", Signal.Auto) if "URANIUM_TRACE_SIGNALS" in os.environ: try: if Platform.isWindows(): self.__name = inspect.stack()[1][0].f_locals["key"] else: self.__name = inspect.stack()[1].frame.f_locals["key"] except KeyError: self.__name = "Signal"
def isReadOnly(self, container_id: str) -> bool: if container_id in self._is_read_only_cache: return self._is_read_only_cache[container_id] storage_path = os.path.realpath(Resources.getDataStoragePath()) file_path = self._id_to_path[container_id] # If KeyError: We don't know this ID. # The container is read-only if file_path is not a subdirectory of storage_path. if Platform.isWindows(): # On Windows, if the paths provided to commonpath() don't come from the same drive, # a ValueError will be raised. try: result = os.path.commonpath([storage_path, os.path.realpath(file_path)]) != storage_path except ValueError: result = True else: result = os.path.commonpath([storage_path, os.path.realpath(file_path)]) != storage_path self._is_read_only_cache[container_id] = result return result
def restore(self) -> bool: if not self.zip_file or not self.meta_data or not self.meta_data.get( "cura_release", None): # We can restore without the minimum required information. Logger.log( "w", "Tried to restore a Cura backup without having proper data or meta data." ) self._showMessage( self.catalog.i18nc( "@info:backup_failed", "Tried to restore a Cura backup without having proper data or meta data." )) return False current_version = CuraApplication.getInstance().getVersion() version_to_restore = self.meta_data.get("cura_release", "master") if current_version != version_to_restore: # Cannot restore version older or newer than current because settings might have changed. # Restoring this will cause a lot of issues so we don't allow this for now. self._showMessage( self.catalog.i18nc( "@info:backup_failed", "Tried to restore a Cura backup that does not match your current version." )) return False version_data_dir = Resources.getDataStoragePath() archive = ZipFile(io.BytesIO(self.zip_file), "r") extracted = self._extractArchive(archive, version_data_dir) # Under Linux, preferences are stored elsewhere, so we copy the file to there. if Platform.isLinux(): preferences_file_name = CuraApplication.getInstance( ).getApplicationName() preferences_file = Resources.getPath( Resources.Preferences, "{}.cfg".format(preferences_file_name)) backup_preferences_file = os.path.join( version_data_dir, "{}.cfg".format(preferences_file_name)) Logger.log("d", "Moving preferences file from %s to %s", backup_preferences_file, preferences_file) shutil.move(backup_preferences_file, preferences_file) return extracted
def event(self, event): if event.type == Event.ViewActivateEvent: # FIX: on Max OS X, somehow QOpenGLContext.currentContext() can become None during View switching. # This can happen when you do the following steps: # 1. Start Cura # 2. Load a model # 3. Switch to Custom mode # 4. Select the model and click on the per-object tool icon # 5. Switch view to Layer view or X-Ray # 6. Cura will very likely crash # It seems to be a timing issue that the currentContext can somehow be empty, but I have no clue why. # This fix tries to reschedule the view changing event call on the Qt thread again if the current OpenGL # context is None. if Platform.isOSX(): if QOpenGLContext.currentContext() is None: Logger.log("d", "current context of OpenGL is empty on Mac OS X, will try to create shaders later") CuraApplication.getInstance().callLater(lambda e = event: self.event(e)) return if not self._xray_pass: # Currently the RenderPass constructor requires a size > 0 # This should be fixed in RenderPass's constructor. self._xray_pass = XRayPass.XRayPass(1, 1) self.getRenderer().addRenderPass(self._xray_pass) if not self._xray_composite_shader: self._xray_composite_shader = OpenGL.getInstance().createShaderProgram(Resources.getPath(Resources.Shaders, "xray_composite.shader")) self._xray_composite_shader.setUniformValue("u_background_color", Color(*theme.getColor("viewport_background").getRgb())) self._xray_composite_shader.setUniformValue("u_outline_color", Color(*theme.getColor("model_selection_outline").getRgb())) if not self._composite_pass: self._composite_pass = self.getRenderer().getRenderPass("composite") self._old_layer_bindings = self._composite_pass.getLayerBindings() self._composite_pass.setLayerBindings(["default", "selection", "xray"]) self._old_composite_shader = self._composite_pass.getCompositeShader() self._composite_pass.setCompositeShader(self._xray_composite_shader) if event.type == Event.ViewDeactivateEvent: self.getRenderer().removeRenderPass(self._xray_pass) self._composite_pass.setLayerBindings(self._old_layer_bindings) self._composite_pass.setCompositeShader(self._old_composite_shader)
def exportProfile(self, instance_id, file_name, file_type): Logger.log('d', 'exportProfile instance_id: '+str(instance_id)) # Parse the fileType to deduce what plugin can save the file format. # fileType has the format "<description> (*.<extension>)" split = file_type.rfind(" (*.") # Find where the description ends and the extension starts. if split < 0: # Not found. Invalid format. Logger.log("e", "Invalid file format identifier %s", file_type) return description = file_type[:split] extension = file_type[split + 4:-1] # Leave out the " (*." and ")". if not file_name.endswith("." + extension): # Auto-fill the extension if the user did not provide any. file_name += "." + extension # On Windows, QML FileDialog properly asks for overwrite confirm, but not on other platforms, so handle those ourself. if not Platform.isWindows(): if os.path.exists(file_name): result = QMessageBox.question(None, catalog.i18nc("@title:window", "File Already Exists"), catalog.i18nc("@label", "The file <filename>{0}</filename> already exists. Are you sure you want to overwrite it?").format(file_name)) if result == QMessageBox.No: return containers = ContainerRegistry.getInstance().findInstanceContainers(id=instance_id) if not containers: return container = containers[0] profile_writer = self._findProfileWriter(extension, description) try: success = profile_writer.write(file_name, container) except Exception as e: Logger.log("e", "Failed to export profile to %s: %s", file_name, str(e)) m = Message(catalog.i18nc("@info:status", "Failed to export profile to <filename>{0}</filename>: <message>{1}</message>", file_name, str(e)), lifetime = 0) m.show() return if not success: Logger.log("w", "Failed to export profile to %s: Writer plugin reported failure.", file_name) m = Message(catalog.i18nc("@info:status", "Failed to export profile to <filename>{0}</filename>: Writer plugin reported failure.", file_name), lifetime = 0) m.show() return m = Message(catalog.i18nc("@info:status", "Exported profile to <filename>{0}</filename>", file_name)) m.show()
def exportQualityProfile(self, container_list: List[InstanceContainer], file_name: str, file_type: str) -> bool: # Parse the fileType to deduce what plugin can save the file format. # fileType has the format "<description> (*.<extension>)" split = file_type.rfind(" (*.") # Find where the description ends and the extension starts. if split < 0: # Not found. Invalid format. Logger.log("e", "Invalid file format identifier %s", file_type) return False description = file_type[:split] extension = file_type[split + 4:-1] # Leave out the " (*." and ")". if not file_name.endswith("." + extension): # Auto-fill the extension if the user did not provide any. file_name += "." + extension # On Windows, QML FileDialog properly asks for overwrite confirm, but not on other platforms, so handle those ourself. if not Platform.isWindows(): if os.path.exists(file_name): result = QMessageBox.question(None, catalog.i18nc("@title:window", "File Already Exists"), catalog.i18nc("@label Don't translate the XML tag <filename>!", "The file <filename>{0}</filename> already exists. Are you sure you want to overwrite it?").format(file_name)) if result == QMessageBox.No: return False profile_writer = self._findProfileWriter(extension, description) try: if profile_writer is None: raise Exception("Unable to find a profile writer") success = profile_writer.write(file_name, container_list) except Exception as e: Logger.log("e", "Failed to export profile to %s: %s", file_name, str(e)) m = Message(catalog.i18nc("@info:status Don't translate the XML tags <filename> or <message>!", "Failed to export profile to <filename>{0}</filename>: <message>{1}</message>", file_name, str(e)), lifetime = 0, title = catalog.i18nc("@info:title", "Error")) m.show() return False if not success: Logger.log("w", "Failed to export profile to %s: Writer plugin reported failure.", file_name) m = Message(catalog.i18nc("@info:status Don't translate the XML tag <filename>!", "Failed to export profile to <filename>{0}</filename>: Writer plugin reported failure.", file_name), lifetime = 0, title = catalog.i18nc("@info:title", "Error")) m.show() return False m = Message(catalog.i18nc("@info:status Don't translate the XML tag <filename>!", "Exported profile to <filename>{0}</filename>", file_name), title = catalog.i18nc("@info:title", "Export succeeded")) m.show() return True
def exportQualityProfile(self, container_list, file_name, file_type) -> bool: # Parse the fileType to deduce what plugin can save the file format. # fileType has the format "<description> (*.<extension>)" split = file_type.rfind(" (*.") # Find where the description ends and the extension starts. if split < 0: # Not found. Invalid format. Logger.log("e", "Invalid file format identifier %s", file_type) return False description = file_type[:split] extension = file_type[split + 4:-1] # Leave out the " (*." and ")". if not file_name.endswith("." + extension): # Auto-fill the extension if the user did not provide any. file_name += "." + extension # On Windows, QML FileDialog properly asks for overwrite confirm, but not on other platforms, so handle those ourself. if not Platform.isWindows(): if os.path.exists(file_name): result = QMessageBox.question(None, catalog.i18nc("@title:window", "File Already Exists"), catalog.i18nc("@label Don't translate the XML tag <filename>!", "The file <filename>{0}</filename> already exists. Are you sure you want to overwrite it?").format(file_name)) if result == QMessageBox.No: return False profile_writer = self._findProfileWriter(extension, description) try: success = profile_writer.write(file_name, container_list) except Exception as e: Logger.log("e", "Failed to export profile to %s: %s", file_name, str(e)) m = Message(catalog.i18nc("@info:status Don't translate the XML tags <filename> or <message>!", "Failed to export profile to <filename>{0}</filename>: <message>{1}</message>", file_name, str(e)), lifetime = 0, title = catalog.i18nc("@info:title", "Error")) m.show() return False if not success: Logger.log("w", "Failed to export profile to %s: Writer plugin reported failure.", file_name) m = Message(catalog.i18nc("@info:status Don't translate the XML tag <filename>!", "Failed to export profile to <filename>{0}</filename>: Writer plugin reported failure.", file_name), lifetime = 0, title = catalog.i18nc("@info:title", "Error")) m.show() return False m = Message(catalog.i18nc("@info:status Don't translate the XML tag <filename>!", "Exported profile to <filename>{0}</filename>", file_name), title = catalog.i18nc("@info:title", "Export succeeded")) m.show() return True
def makeFromCurrent(self) -> None: cura_release = self._application.getVersion() version_data_dir = Resources.getDataStoragePath() Logger.log("d", "Creating backup for Cura %s, using folder %s", cura_release, version_data_dir) # Ensure all current settings are saved. self._application.saveSettings() # We copy the preferences file to the user data directory in Linux as it's in a different location there. # When restoring a backup on Linux, we move it back. if Platform.isLinux(): #TODO: This should check for the config directory not being the same as the data directory, rather than hard-coding that to Linux systems. preferences_file_name = self._application.getApplicationName() preferences_file = Resources.getPath(Resources.Preferences, "{}.cfg".format(preferences_file_name)) backup_preferences_file = os.path.join(version_data_dir, "{}.cfg".format(preferences_file_name)) if os.path.exists(preferences_file) and (not os.path.exists(backup_preferences_file) or not os.path.samefile(preferences_file, backup_preferences_file)): Logger.log("d", "Copying preferences file from %s to %s", preferences_file, backup_preferences_file) shutil.copyfile(preferences_file, backup_preferences_file) # Create an empty buffer and write the archive to it. buffer = io.BytesIO() archive = self._makeArchive(buffer, version_data_dir) if archive is None: return files = archive.namelist() # Count the metadata items. We do this in a rather naive way at the moment. machine_count = len([s for s in files if "machine_instances/" in s]) - 1 material_count = len([s for s in files if "materials/" in s]) - 1 profile_count = len([s for s in files if "quality_changes/" in s]) - 1 plugin_count = len([s for s in files if "plugin.json" in s]) # Store the archive and metadata so the BackupManager can fetch them when needed. self.zip_file = buffer.getvalue() self.meta_data = { "cura_release": cura_release, "machine_count": str(machine_count), "material_count": str(material_count), "profile_count": str(profile_count), "plugin_count": str(plugin_count) }
def run(self): if not self.url or not self.data: Logger.log("e", "URL or DATA for sending slice info was not set!") return # Submit data kwoptions = {"data" : self.data, "timeout" : 5 } if Platform.isOSX(): kwoptions["context"] = ssl._create_unverified_context() try: f = urllib.request.urlopen(self.url, **kwoptions) Logger.log("i", "Sent anonymous slice info to %s", self.url) f.close() except urllib.error.HTTPError as http_exception: Logger.log("e", "An HTTP error occurred while trying to send slice information: %s" % http_exception) except Exception as e: # We don't want any exception to cause problems Logger.log("e", "An exception occurred while trying to send slice information: %s" % e)
def _sendCrashReport(self): # Before sending data, the user comments are stored self.data["user_info"] = self.user_description_text_area.toPlainText() # Convert data to bytes binary_data = json.dumps(self.data).encode("utf-8") # Submit data kwoptions = {"data": binary_data, "timeout": 5} if Platform.isOSX(): kwoptions["context"] = ssl._create_unverified_context() Logger.log("i", "Sending crash report info to [%s]...", self.crash_url) if not self.has_started: print("Sending crash report info to [%s]...\n" % self.crash_url) try: f = urllib.request.urlopen(self.crash_url, **kwoptions) Logger.log("i", "Sent crash report info.") if not self.has_started: print("Sent crash report info.\n") f.close() except urllib.error.HTTPError as e: Logger.logException( "e", "An HTTP error occurred while trying to send crash report") if not self.has_started: print( "An HTTP error occurred while trying to send crash report: %s" % e) except Exception as e: # We don't want any exception to cause problems Logger.logException( "e", "An exception occurred while trying to send crash report") if not self.has_started: print( "An exception occurred while trying to send crash report: %s" % e) os._exit(1)
def isReadOnly(self, container_id: str) -> bool: """Returns whether a container is read-only or not. A container can only be modified if it is stored in the data directory. :return: Whether the specified container is read-only. """ if container_id in self._is_read_only_cache: return self._is_read_only_cache[container_id] if self._storage_path == "": try: self._storage_path = os.path.realpath( Resources.getDataStoragePath()) except OSError: # Directory can't be accessed. self._is_read_only_cache[container_id] = True return True storage_path = self._storage_path file_path = self._id_to_path[ container_id] # If KeyError: We don't know this ID. # The container is read-only if file_path is not a subdirectory of storage_path. if Platform.isWindows(): # On Windows, if the paths provided to commonpath() don't come from the same drive, # a ValueError will be raised. try: result = os.path.commonpath([ storage_path, os.path.realpath(file_path) ]) != storage_path except ValueError: result = True else: result = os.path.commonpath( [storage_path, os.path.realpath(file_path)]) != storage_path result |= ContainerRegistry.getInstance().isExplicitReadOnly( container_id) self._is_read_only_cache[container_id] = result return result
def _createSocket(self, protocol_file): """Creates a socket and attaches listeners.""" if self._socket: Logger.log("d", "Previous socket existed. Closing that first." ) # temp debug logging self._socket.stateChanged.disconnect(self._onSocketStateChanged) self._socket.messageReceived.disconnect(self._onMessageReceived) self._socket.error.disconnect(self._onSocketError) # Hack for (at least) Linux. If the socket is connecting, the close will deadlock. while self._socket.getState() == Arcus.SocketState.Opening: sleep(0.1) # If the error occurred due to parsing, both connections believe that connection is okay. # So we need to force a close. self._socket.close() self._socket = SignalSocket() self._socket.stateChanged.connect(self._onSocketStateChanged) self._socket.messageReceived.connect(self._onMessageReceived) self._socket.error.connect(self._onSocketError) if Platform.isWindows(): # On Windows, the Protobuf DiskSourceTree does stupid things with paths. # So convert to forward slashes here so it finds the proto file properly. # Using sys.getfilesystemencoding() avoid the application crashing if it is # installed on a path with non-ascii characters GitHub issue #3907 protocol_file = protocol_file.replace("\\", "/").encode( sys.getfilesystemencoding()) if not self._socket.registerAllMessageTypes(protocol_file): Logger.log("e", "Could not register Uranium protocol messages: %s", self._socket.getLastError()) if UM.Application.Application.getInstance().getUseExternalBackend(): Logger.log("i", "Listening for backend connections on %s", self._port) self._socket.listen("127.0.0.1", self._port)
def getDefaultFirmwareName(self) -> str: machine_has_heated_bed = self.getProperty("machine_heated_bed", "value") baudrate = 250000 if Platform.isLinux(): # Linux prefers a baudrate of 115200 here because older versions of # pySerial did not support a baudrate of 250000 baudrate = 115200 # If a firmware file is available, it should be specified in the definition for the printer hex_file = self.getMetaDataEntry("firmware_file", None) if machine_has_heated_bed: hex_file = self.getMetaDataEntry("firmware_hbk_file", hex_file) if not hex_file: Logger.log("w", "There is no firmware for machine %s.", self.getBottom().id) return "" try: return Resources.getPath(cura.CuraApplication.CuraApplication.ResourceTypes.Firmware, hex_file.format(baudrate=baudrate)) except FileNotFoundError: Logger.log("w", "Firmware file %s not found.", hex_file) return ""
def getMetaData() -> Dict: # Workaround for osx not supporting double file extensions correctly. if Platform.isOSX(): workspace_extension = "3mf" else: workspace_extension = "curaproject.3mf" metaData = {} if "3MFReader.ThreeMFReader" in sys.modules: metaData["mesh_reader"] = [ { "extension": "3mf", "description": catalog.i18nc("@item:inlistbox", "3MF File") } ] metaData["workspace_reader"] = [ { "extension": workspace_extension, "description": catalog.i18nc("@item:inlistbox", "3MF File") } ] return metaData
def getDefaultFirmwareName(self) -> str: machine_has_heated_bed = self.getProperty("machine_heated_bed", "value") machine_has_heated_chamber=self.getProperty("machine_heated_chamber", "value") baudrate = 250000 if Platform.isLinux(): # Linux prefers a baudrate of 115200 here because older versions of # pySerial did not support a baudrate of 250000 baudrate = 115200 # If a firmware file is available, it should be specified in the definition for the printer hex_file = self.getMetaDataEntry("firmware_file", None) if machine_has_heated_bed: hex_file = self.getMetaDataEntry("firmware_hbk_file", hex_file) if not hex_file: Logger.log("w", "There is no firmware for machine %s.", self.getBottom().id) return "" try: return Resources.getPath(cura.CuraApplication.CuraApplication.ResourceTypes.Firmware, hex_file.format(baudrate=baudrate)) except FileNotFoundError: Logger.log("w", "Firmware file %s not found.", hex_file) return ""
def getMetaData() -> Dict: # Workarround for osx not supporting double file extensions correctly. if Platform.isOSX(): workspace_extension = "3mf" else: workspace_extension = "curaproject.3mf" metaData = {} if "3MFReader.ThreeMFReader" in sys.modules: metaData["mesh_reader"] = [ { "extension": "3mf", "description": catalog.i18nc("@item:inlistbox", "3MF File") } ] metaData["workspace_reader"] = [ { "extension": workspace_extension, "description": catalog.i18nc("@item:inlistbox", "3MF File") } ] return metaData
def getMetaData() -> Dict: # Workarround for osx not supporting double file extensions correclty. if Platform.isOSX(): workspace_extension = "3mf" else: workspace_extension = "curaproject.3mf" metaData = { "plugin": { "name": catalog.i18nc("@label", "3MF Reader"), "author": "Ultimaker", "version": "1.0", "description": catalog.i18nc("@info:whatsthis", "Provides support for reading 3MF files."), "api": 3 } } if "3MFReader.ThreeMFReader" in sys.modules: metaData["mesh_reader"] = [{ "extension": "3mf", "description": catalog.i18nc("@item:inlistbox", "3MF File") }] metaData["workspace_reader"] = [{ "extension": workspace_extension, "description": catalog.i18nc("@item:inlistbox", "3MF File") }] return metaData
def getMetaData(): # Workarround for osx not supporting double file extensions correctly. if Platform.isOSX(): workspace_extension = "3mf" else: workspace_extension = "curaproject.3mf" metaData = {} if "3MFWriter.ThreeMFWriter" in sys.modules: metaData["mesh_writer"] = { "output": [{ "extension": "3mf", "description": i18n_catalog.i18nc("@item:inlistbox", "3MF file"), "mime_type": "application/vnd.ms-package.3dmanufacturing-3dmodel+xml", "mode": ThreeMFWriter.ThreeMFWriter.OutputMode.BinaryMode }] } metaData["workspace_writer"] = { "output": [{ "extension": workspace_extension, "description": i18n_catalog.i18nc("@item:inlistbox", "Cura Project 3MF file"), "mime_type": "application/x-curaproject+xml", "mode": ThreeMFWorkspaceWriter.ThreeMFWorkspaceWriter.OutputMode. BinaryMode }] } return metaData
def __initializeStoragePaths(cls): Logger.log("d", "Initializing storage paths") # use nested structure: <app-name>/<version>/... if cls.ApplicationVersion == "master" or cls.ApplicationVersion == "unknown": storage_dir_name = os.path.join(cls.ApplicationIdentifier, cls.ApplicationVersion) else: from UM.Version import Version version = Version(cls.ApplicationVersion) storage_dir_name = os.path.join(cls.ApplicationIdentifier, "%s.%s" % (version.getMajor(), version.getMinor())) # config is saved in "<CONFIG_ROOT>/<storage_dir_name>" cls.__config_storage_path = os.path.join(Resources._getConfigStorageRootPath(), storage_dir_name) Logger.log("d", "Config storage path is %s", cls.__config_storage_path) # data is saved in # - on Linux: "<DATA_ROOT>/<storage_dir_name>" # - on other: "<CONFIG_DIR>" (in the config directory) data_root_path = Resources._getDataStorageRootPath() cls.__data_storage_path = cls.__config_storage_path if data_root_path is None else \ os.path.join(data_root_path, storage_dir_name) Logger.log("d", "Data storage path is %s", cls.__data_storage_path) # cache is saved in # - on Linux: "<CACHE_DIR>/<storage_dir_name>" # - on Windows: "<CACHE_DIR>/<storage_dir_name>/cache" # - on Mac: "<CONFIG_DIR>/cache" (in the config directory) cache_root_path = Resources._getCacheStorageRootPath() if cache_root_path is None: cls.__cache_storage_path = os.path.join(cls.__config_storage_path, "cache") else: cls.__cache_storage_path = os.path.join(cache_root_path, storage_dir_name) if Platform.isWindows(): cls.__cache_storage_path = os.path.join(cls.__cache_storage_path, "cache") Logger.log("d", "Cache storage path is %s", cls.__cache_storage_path) if not os.path.exists(cls.__config_storage_path): cls._copyLatestDirsIfPresent() cls.__paths.insert(0, cls.__data_storage_path)
def __init__(self, type: int = Auto) -> None: # These collections must be treated as immutable otherwise we lose thread safety. self.__functions = WeakImmutableList() # type: "WeakImmutableList" self.__methods = WeakImmutablePairList() # type: "WeakImmutablePairList" self.__signals = WeakImmutableList() # type: "WeakImmutableList" self.__lock = threading.Lock() # Guards access to the fields above. self.__type = type self._postpone_emit = False self._postpone_thread = None # type: threading.Thread self._compress_postpone = False self._postponed_emits = None # type: Any if _recordSignalNames(): try: if Platform.isWindows(): self.__name = inspect.stack()[1][0].f_locals["key"] else: self.__name = inspect.stack()[1].frame.f_locals["key"] except KeyError: self.__name = "Signal" else: self.__name = "Anon"
def detectBestOpenGLVersion( cls, force_compatability: bool ) -> Tuple[Optional[int], Optional[int], Optional[int]]: """Return "best" OpenGL to use, 4.1 core or 2.0. result is <major_version>, <minor_version>, <profile> The version depends on what versions are supported in Qt (4.1 and 2.0) and what the GPU supports. If creating a context fails at all, (None, None, None) is returned Note that PyQt only supports 4.1, 2.1 and 2.0. Cura omits support for 2.1, so the only returned options are 4.1 and 2.0. """ cls.detect_ogl_context = None if not force_compatability: Logger.log("d", "Trying OpenGL context 4.1...") cls.detect_ogl_context = cls.setContext(4, 1, core=True) if cls.detect_ogl_context is not None: fmt = cls.detect_ogl_context.format() profile = fmt.profile() # First test: we hope for this if ((fmt.majorVersion() == 4 and fmt.minorVersion() >= 1) or (fmt.majorVersion() > 4)) and profile == QSurfaceFormat.CoreProfile: Logger.log( "d", "Yay, we got at least OpenGL 4.1 core: %s", cls.versionAsText(fmt.majorVersion(), fmt.minorVersion(), profile)) # https://riverbankcomputing.com/pipermail/pyqt/2017-January/038640.html # PyQt currently only implements 2.0, 2.1 and 4.1Core # If eg 4.5Core would be detected and used here, PyQt would not be able to handle it. major_version = 4 minor_version = 1 # CURA-6092: Check if we're not using software backed 4.1 context; A software 4.1 context # is much slower than a hardware backed 2.0 context # Check for OS, Since this only seems to happen on specific versions of Mac OSX and # the workaround (which involves the deletion of an OpenGL context) is a problem for some Intel drivers. if not Platform.isOSX(): return major_version, minor_version, QSurfaceFormat.CoreProfile gl_window = QWindow() gl_window.setSurfaceType(QWindow.OpenGLSurface) gl_window.showMinimized() cls.detect_ogl_context.makeCurrent(gl_window) gl_profile = QOpenGLVersionProfile() gl_profile.setVersion(major_version, minor_version) gl_profile.setProfile(profile) gl = cls.detect_ogl_context.versionFunctions( gl_profile ) # type: Any #It's actually a protected class in PyQt that depends on the requested profile and the implementation of your graphics card. gpu_type = "Unknown" # type: str result = None if gl: result = gl.initializeOpenGLFunctions() if not result: Logger.log("e", "Could not initialize OpenGL to get gpu type") else: # WORKAROUND: Cura/#1117 Cura-packaging/12 # Some Intel GPU chipsets return a string, which is not undecodable via PyQt5. # This workaround makes the code fall back to a "Unknown" renderer in these cases. try: gpu_type = gl.glGetString(gl.GL_RENDERER) except UnicodeDecodeError: Logger.log( "e", "DecodeError while getting GL_RENDERER via glGetString!" ) Logger.log("d", "OpenGL renderer type for this OpenGL version: %s", gpu_type) if "software" in gpu_type.lower(): Logger.log( "w", "Unfortunately OpenGL 4.1 uses software rendering") else: return major_version, minor_version, QSurfaceFormat.CoreProfile else: Logger.log("d", "Failed to create OpenGL context 4.1.") # Fallback: check min spec Logger.log("d", "Trying OpenGL context 2.0...") cls.detect_ogl_context = cls.setContext( 2, 0, profile=QSurfaceFormat.NoProfile) if cls.detect_ogl_context is not None: fmt = cls.detect_ogl_context.format() profile = fmt.profile() if fmt.majorVersion() >= 2 and fmt.minorVersion() >= 0: Logger.log( "d", "We got at least OpenGL context 2.0: %s", cls.versionAsText(fmt.majorVersion(), fmt.minorVersion(), profile)) return 2, 0, QSurfaceFormat.NoProfile else: Logger.log( "d", "Current OpenGL context is too low: %s" % cls.versionAsText(fmt.majorVersion(), fmt.minorVersion(), profile)) return None, None, None else: Logger.log("d", "Failed to create OpenGL context 2.0.") return None, None, None
def exportProfile(self, instance_ids, file_name, file_type): # Parse the fileType to deduce what plugin can save the file format. # fileType has the format "<description> (*.<extension>)" split = file_type.rfind( " (*." ) # Find where the description ends and the extension starts. if split < 0: # Not found. Invalid format. Logger.log("e", "Invalid file format identifier %s", file_type) return description = file_type[:split] extension = file_type[split + 4:-1] # Leave out the " (*." and ")". if not file_name.endswith( "." + extension ): # Auto-fill the extension if the user did not provide any. file_name += "." + extension # On Windows, QML FileDialog properly asks for overwrite confirm, but not on other platforms, so handle those ourself. if not Platform.isWindows(): if os.path.exists(file_name): result = QMessageBox.question( None, catalog.i18nc("@title:window", "File Already Exists"), catalog.i18nc( "@label", "The file <filename>{0}</filename> already exists. Are you sure you want to overwrite it?" ).format(file_name)) if result == QMessageBox.No: return found_containers = [] extruder_positions = [] for instance_id in instance_ids: containers = ContainerRegistry.getInstance( ).findInstanceContainers(id=instance_id) if containers: found_containers.append(containers[0]) # Determine the position of the extruder of this container extruder_id = containers[0].getMetaDataEntry("extruder", "") if extruder_id == "": # Global stack extruder_positions.append(-1) else: extruder_containers = ContainerRegistry.getInstance( ).findDefinitionContainers(id=extruder_id) if extruder_containers: extruder_positions.append( int(extruder_containers[0].getMetaDataEntry( "position", 0))) else: extruder_positions.append(0) # Ensure the profiles are always exported in order (global, extruder 0, extruder 1, ...) found_containers = [ containers for (positions, containers ) in sorted(zip(extruder_positions, found_containers)) ] profile_writer = self._findProfileWriter(extension, description) try: success = profile_writer.write(file_name, found_containers) except Exception as e: Logger.log("e", "Failed to export profile to %s: %s", file_name, str(e)) m = Message(catalog.i18nc( "@info:status", "Failed to export profile to <filename>{0}</filename>: <message>{1}</message>", file_name, str(e)), lifetime=0) m.show() return if not success: Logger.log( "w", "Failed to export profile to %s: Writer plugin reported failure.", file_name) m = Message(catalog.i18nc( "@info:status", "Failed to export profile to <filename>{0}</filename>: Writer plugin reported failure.", file_name), lifetime=0) m.show() return m = Message( catalog.i18nc("@info:status", "Exported profile to <filename>{0}</filename>", file_name)) m.show()
def __init__(self): super().__init__() # Find out where the engine is located, and how it is called. # This depends on how Cura is packaged and which OS we are running on. executable_name = "CuraEngine" if Platform.isWindows(): executable_name += ".exe" default_engine_location = executable_name if os.path.exists(os.path.join(Application.getInstallPrefix(), "bin", executable_name)): default_engine_location = os.path.join(Application.getInstallPrefix(), "bin", executable_name) if hasattr(sys, "frozen"): default_engine_location = os.path.join(os.path.dirname(os.path.abspath(sys.executable)), executable_name) if Platform.isLinux() and not default_engine_location: if not os.getenv("PATH"): raise OSError("There is something wrong with your Linux installation.") for pathdir in os.getenv("PATH").split(os.pathsep): execpath = os.path.join(pathdir, executable_name) if os.path.exists(execpath): default_engine_location = execpath break if not default_engine_location: raise EnvironmentError("Could not find CuraEngine") Logger.log("i", "Found CuraEngine at: %s" %(default_engine_location)) default_engine_location = os.path.abspath(default_engine_location) Preferences.getInstance().addPreference("backend/location", default_engine_location) self._scene = Application.getInstance().getController().getScene() self._scene.sceneChanged.connect(self._onSceneChanged) self._pause_slicing = False # Workaround to disable layer view processing if layer view is not active. self._layer_view_active = False Application.getInstance().getController().activeViewChanged.connect(self._onActiveViewChanged) self._onActiveViewChanged() self._stored_layer_data = [] self._stored_optimized_layer_data = [] # Triggers for when to (re)start slicing: self._global_container_stack = None Application.getInstance().globalContainerStackChanged.connect(self._onGlobalStackChanged) self._onGlobalStackChanged() self._active_extruder_stack = None cura.Settings.ExtruderManager.getInstance().activeExtruderChanged.connect(self._onActiveExtruderChanged) self._onActiveExtruderChanged() # When you update a setting and other settings get changed through inheritance, many propertyChanged signals are fired. # This timer will group them up, and only slice for the last setting changed signal. # TODO: Properly group propertyChanged signals by whether they are triggered by the same user interaction. self._change_timer = QTimer() self._change_timer.setInterval(500) self._change_timer.setSingleShot(True) self._change_timer.timeout.connect(self.slice) # Listeners for receiving messages from the back-end. self._message_handlers["cura.proto.Layer"] = self._onLayerMessage self._message_handlers["cura.proto.LayerOptimized"] = self._onOptimizedLayerMessage self._message_handlers["cura.proto.Progress"] = self._onProgressMessage self._message_handlers["cura.proto.GCodeLayer"] = self._onGCodeLayerMessage self._message_handlers["cura.proto.GCodePrefix"] = self._onGCodePrefixMessage self._message_handlers["cura.proto.PrintTimeMaterialEstimates"] = self._onPrintTimeMaterialEstimates self._message_handlers["cura.proto.SlicingFinished"] = self._onSlicingFinishedMessage self._start_slice_job = None self._slicing = False # Are we currently slicing? self._restart = False # Back-end is currently restarting? self._enabled = True # Should we be slicing? Slicing might be paused when, for instance, the user is dragging the mesh around. self._always_restart = True # Always restart the engine when starting a new slice. Don't keep the process running. TODO: Fix engine statelessness. self._process_layers_job = None # The currently active job to process layers, or None if it is not processing layers. self._backend_log_max_lines = 20000 # Maximum number of lines to buffer self._error_message = None # Pop-up message that shows errors. self.backendQuit.connect(self._onBackendQuit) self.backendConnected.connect(self._onBackendConnected) # When a tool operation is in progress, don't slice. So we need to listen for tool operations. Application.getInstance().getController().toolOperationStarted.connect(self._onToolOperationStarted) Application.getInstance().getController().toolOperationStopped.connect(self._onToolOperationStopped) self._slice_start_time = None
def removeWatchedFile(self, file_path: str) -> None: # The QT 5.10.0 issue, only on Windows. Cura crashes after loading a stl file from USB/sd-card/Cloud-based drive if not Platform.isWindows(): self._file_watcher.removePath(file_path)
def restore(self) -> bool: """Restore this back-up. :return: Whether we had success or not. """ if not self.zip_file or not self.meta_data or not self.meta_data.get( "cura_release", None): # We can restore without the minimum required information. Logger.log( "w", "Tried to restore a Cura backup without having proper data or meta data." ) self._showMessage( self.catalog.i18nc( "@info:backup_failed", "Tried to restore a Cura backup without having proper data or meta data." )) return False current_version = self._application.getVersion() version_to_restore = self.meta_data.get("cura_release", "master") if current_version < version_to_restore: # Cannot restore version newer than current because settings might have changed. Logger.log( "d", "Tried to restore a Cura backup of version {version_to_restore} with cura version {current_version}" .format(version_to_restore=version_to_restore, current_version=current_version)) self._showMessage( self.catalog.i18nc( "@info:backup_failed", "Tried to restore a Cura backup that is higher than the current version." )) return False # Get the current secrets and store since the back-up doesn't contain those secrets = self._obfuscate() version_data_dir = Resources.getDataStoragePath() try: archive = ZipFile(io.BytesIO(self.zip_file), "r") except LookupError as e: Logger.log( "d", f"The following error occurred while trying to restore a Cura backup: {str(e)}" ) self._showMessage( self.catalog.i18nc( "@info:backup_failed", "The following error occurred while trying to restore a Cura backup:" ) + str(e)) return False extracted = self._extractArchive(archive, version_data_dir) # Under Linux, preferences are stored elsewhere, so we copy the file to there. if Platform.isLinux(): preferences_file_name = self._application.getApplicationName() preferences_file = Resources.getPath( Resources.Preferences, "{}.cfg".format(preferences_file_name)) backup_preferences_file = os.path.join( version_data_dir, "{}.cfg".format(preferences_file_name)) Logger.log("d", "Moving preferences file from %s to %s", backup_preferences_file, preferences_file) shutil.move(backup_preferences_file, preferences_file) # Restore the obfuscated settings self._illuminate(**secrets) return extracted
def _onWriteStarted(self, output_device): try: if not Preferences.getInstance().getValue("info/send_slice_info"): Logger.log("d", "'info/send_slice_info' is turned off.") return # Do nothing, user does not want to send data global_container_stack = Application.getInstance( ).getGlobalContainerStack() # Get total material used (in mm^3) print_information = Application.getInstance().getPrintInformation() material_radius = 0.5 * global_container_stack.getProperty( "material_diameter", "value") # TODO: Send material per extruder instead of mashing it on a pile material_used = math.pi * material_radius * material_radius * sum( print_information.materialLengths ) #Volume of all materials used # Get model information (bounding boxes, hashes and transformation matrix) models_info = [] for node in DepthFirstIterator(Application.getInstance( ).getController().getScene().getRoot()): if type(node) is SceneNode and node.getMeshData( ) and node.getMeshData().getVertices() is not None: if not getattr(node, "_outside_buildarea", False): model_info = {} model_info["hash"] = node.getMeshData().getHash() model_info["bounding_box"] = {} model_info["bounding_box"]["minimum"] = {} model_info["bounding_box"]["minimum"][ "x"] = node.getBoundingBox().minimum.x model_info["bounding_box"]["minimum"][ "y"] = node.getBoundingBox().minimum.y model_info["bounding_box"]["minimum"][ "z"] = node.getBoundingBox().minimum.z model_info["bounding_box"]["maximum"] = {} model_info["bounding_box"]["maximum"][ "x"] = node.getBoundingBox().maximum.x model_info["bounding_box"]["maximum"][ "y"] = node.getBoundingBox().maximum.y model_info["bounding_box"]["maximum"][ "z"] = node.getBoundingBox().maximum.z model_info["transformation"] = str( node.getWorldTransformation().getData()) models_info.append(model_info) # Bundle the collected data submitted_data = { "processor": platform.processor(), "machine": platform.machine(), "platform": platform.platform(), "settings": global_container_stack.serialize( ), # global_container with references on used containers "version": Application.getInstance().getVersion(), "modelhash": "None", "printtime": print_information.currentPrintTime.getDisplayString( DurationFormat.Format.ISO8601), "filament": material_used, "language": Preferences.getInstance().getValue("general/language"), } for container in global_container_stack.getContainers(): container_id = container.getId() try: container_serialized = container.serialize() except NotImplementedError: Logger.log("w", "Container %s could not be serialized!", container_id) continue if container_serialized: submitted_data["settings_%s" % ( container_id )] = container_serialized # This can be anything, eg. INI, JSON, etc. else: Logger.log("i", "No data found in %s to be serialized!", container_id) # Convert data to bytes submitted_data = urllib.parse.urlencode(submitted_data) binary_data = submitted_data.encode("utf-8") # Submit data kwoptions = {"data": binary_data, "timeout": 1} if Platform.isOSX(): kwoptions["context"] = ssl._create_unverified_context() try: f = urllib.request.urlopen(self.info_url, **kwoptions) Logger.log("i", "Sent anonymous slice info to %s", self.info_url) f.close() except Exception as e: Logger.logException( "e", "An exception occurred while trying to send slice information" ) except: # We really can't afford to have a mistake here, as this would break the sending of g-code to a device # (Either saving or directly to a printer). The functionality of the slice data is not *that* important. pass
def __init__(self, parent=None): super().__init__(parent=parent) # Find out where the engine is located, and how it is called. # This depends on how Cura is packaged and which OS we are running on. executable_name = "CuraEngine" if Platform.isWindows(): executable_name += ".exe" default_engine_location = executable_name if os.path.exists( os.path.join(Application.getInstallPrefix(), "bin", executable_name)): default_engine_location = os.path.join( Application.getInstallPrefix(), "bin", executable_name) if hasattr(sys, "frozen"): default_engine_location = os.path.join( os.path.dirname(os.path.abspath(sys.executable)), executable_name) if Platform.isLinux() and not default_engine_location: if not os.getenv("PATH"): raise OSError( "There is something wrong with your Linux installation.") for pathdir in os.getenv("PATH").split(os.pathsep): execpath = os.path.join(pathdir, executable_name) if os.path.exists(execpath): default_engine_location = execpath break if not default_engine_location: raise EnvironmentError("Could not find CuraEngine") Logger.log("i", "Found CuraEngine at: %s" % (default_engine_location)) default_engine_location = os.path.abspath(default_engine_location) Preferences.getInstance().addPreference("backend/location", default_engine_location) # Workaround to disable layer view processing if layer view is not active. self._layer_view_active = False Application.getInstance().getController().activeViewChanged.connect( self._onActiveViewChanged) Application.getInstance().getBuildPlateModel( ).activeBuildPlateChanged.connect(self._onActiveViewChanged) self._onActiveViewChanged() self._stored_layer_data = [] self._stored_optimized_layer_data = { } # key is build plate number, then arrays are stored until they go to the ProcessSlicesLayersJob self._scene = Application.getInstance().getController().getScene() self._scene.sceneChanged.connect(self._onSceneChanged) # Triggers for auto-slicing. Auto-slicing is triggered as follows: # - auto-slicing is started with a timer # - whenever there is a value change, we start the timer # - sometimes an error check can get scheduled for a value change, in that case, we ONLY want to start the # auto-slicing timer when that error check is finished # If there is an error check, it will set the "_is_error_check_scheduled" flag, stop the auto-slicing timer, # and only wait for the error check to be finished to start the auto-slicing timer again. # self._global_container_stack = None Application.getInstance().globalContainerStackChanged.connect( self._onGlobalStackChanged) Application.getInstance().getExtruderManager().extrudersAdded.connect( self._onGlobalStackChanged) self._onGlobalStackChanged() Application.getInstance().stacksValidationFinished.connect( self._onStackErrorCheckFinished) # A flag indicating if an error check was scheduled # If so, we will stop the auto-slice timer and start upon the error check self._is_error_check_scheduled = False # Listeners for receiving messages from the back-end. self._message_handlers["cura.proto.Layer"] = self._onLayerMessage self._message_handlers[ "cura.proto.LayerOptimized"] = self._onOptimizedLayerMessage self._message_handlers["cura.proto.Progress"] = self._onProgressMessage self._message_handlers[ "cura.proto.GCodeLayer"] = self._onGCodeLayerMessage self._message_handlers[ "cura.proto.GCodePrefix"] = self._onGCodePrefixMessage self._message_handlers[ "cura.proto.PrintTimeMaterialEstimates"] = self._onPrintTimeMaterialEstimates self._message_handlers[ "cura.proto.SlicingFinished"] = self._onSlicingFinishedMessage self._start_slice_job = None self._start_slice_job_build_plate = None self._slicing = False # Are we currently slicing? self._restart = False # Back-end is currently restarting? self._tool_active = False # If a tool is active, some tasks do not have to do anything self._always_restart = True # Always restart the engine when starting a new slice. Don't keep the process running. TODO: Fix engine statelessness. self._process_layers_job = None # The currently active job to process layers, or None if it is not processing layers. self._build_plates_to_be_sliced = [] # what needs slicing? self._engine_is_fresh = True # Is the newly started engine used before or not? self._backend_log_max_lines = 20000 # Maximum number of lines to buffer self._error_message = None # Pop-up message that shows errors. self._last_num_objects = defaultdict( int ) # Count number of objects to see if there is something changed self._postponed_scene_change_sources = [ ] # scene change is postponed (by a tool) self.backendQuit.connect(self._onBackendQuit) self.backendConnected.connect(self._onBackendConnected) # When a tool operation is in progress, don't slice. So we need to listen for tool operations. Application.getInstance().getController().toolOperationStarted.connect( self._onToolOperationStarted) Application.getInstance().getController().toolOperationStopped.connect( self._onToolOperationStopped) self._slice_start_time = None Preferences.getInstance().addPreference("general/auto_slice", True) self._use_timer = False # When you update a setting and other settings get changed through inheritance, many propertyChanged signals are fired. # This timer will group them up, and only slice for the last setting changed signal. # TODO: Properly group propertyChanged signals by whether they are triggered by the same user interaction. self._change_timer = QTimer() self._change_timer.setSingleShot(True) self._change_timer.setInterval(500) self.determineAutoSlicing() Preferences.getInstance().preferenceChanged.connect( self._onPreferencesChanged)
#!/usr/bin/env python3 # Copyright (c) 2015 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. import os import sys import platform import faulthandler from UM.Platform import Platform #WORKAROUND: GITHUB-88 GITHUB-385 GITHUB-612 if Platform.isLinux( ): # Needed for platform.linux_distribution, which is not available on Windows and OSX # For Ubuntu: https://bugs.launchpad.net/ubuntu/+source/python-qt4/+bug/941826 if platform.linux_distribution()[0] in ( "debian", "Ubuntu", "LinuxMint" ): # TODO: Needs a "if X11_GFX == 'nvidia'" here. The workaround is only needed on Ubuntu+NVidia drivers. Other drivers are not affected, but fine with this fix. import ctypes from ctypes.util import find_library libGL = find_library("GL") ctypes.CDLL(libGL, ctypes.RTLD_GLOBAL) # When frozen, i.e. installer version, don't let PYTHONPATH mess up the search path for DLLs. if Platform.isWindows() and hasattr(sys, "frozen"): try: del os.environ["PYTHONPATH"] except KeyError: pass #WORKAROUND: GITHUB-704 GITHUB-708
import os.path # To watch files for changes. import threading from typing import Callable, List, Optional, Set, Any, Dict from PyQt6.QtCore import QFileSystemWatcher # To watch files for changes. from UM.Logger import Logger from UM.Mesh.ReadMeshJob import ReadMeshJob # To reload a mesh when its file was changed. from UM.Message import Message # To display a message for reloading files that were changed. from UM.Scene.Camera import Camera from UM.Scene.Iterator.BreadthFirstIterator import BreadthFirstIterator from UM.Scene.SceneNode import SceneNode from UM.Signal import Signal, signalemitter from UM.i18n import i18nCatalog from UM.Platform import Platform if Platform.isWindows(): from PyQt6.QtCore import QEventLoop # Windows fix for using file watcher on removable devices. i18n_catalog = i18nCatalog("uranium") @signalemitter class Scene: """Container object for the scene graph The main purpose of this class is to provide the root SceneNode. """ def __init__(self) -> None: super().__init__() from UM.Scene.SceneNode import SceneNode
def exportContainer( self, container_id: str, file_type: str, file_url_or_string: Union[QUrl, str]) -> Dict[str, str]: if not container_id or not file_type or not file_url_or_string: return {"status": "error", "message": "Invalid arguments"} if isinstance(file_url_or_string, QUrl): file_url = file_url_or_string.toLocalFile() else: file_url = file_url_or_string if not file_url: return {"status": "error", "message": "Invalid path"} if file_type not in self._container_name_filters: try: mime_type = MimeTypeDatabase.getMimeTypeForFile(file_url) except MimeTypeNotFoundError: return {"status": "error", "message": "Unknown File Type"} else: mime_type = self._container_name_filters[file_type]["mime"] containers = cura.CuraApplication.CuraApplication.getInstance( ).getContainerRegistry().findContainers(id=container_id) if not containers: return {"status": "error", "message": "Container not found"} container = containers[0] if Platform.isOSX() and "." in file_url: file_url = file_url[:file_url.rfind(".")] for suffix in mime_type.suffixes: if file_url.endswith(suffix): break else: file_url += "." + mime_type.preferredSuffix if not Platform.isWindows(): if os.path.exists(file_url): result = QMessageBox.question( None, catalog.i18nc("@title:window", "File Already Exists"), catalog.i18nc( "@label Don't translate the XML tag <filename>!", "The file <filename>{0}</filename> already exists. Are you sure you want to overwrite it?" ).format(file_url)) if result == QMessageBox.No: return {"status": "cancelled", "message": "User cancelled"} try: contents = container.serialize() except NotImplementedError: return { "status": "error", "message": "Unable to serialize container" } if contents is None: return { "status": "error", "message": "Serialization returned None. Unable to write to file" } with SaveFile(file_url, "w") as f: f.write(contents) return { "status": "success", "message": "Successfully exported container", "path": file_url }
def event(self, event): modifiers = QApplication.keyboardModifiers() ctrl_is_active = modifiers & Qt.ControlModifier shift_is_active = modifiers & Qt.ShiftModifier if event.type == Event.KeyPressEvent and ctrl_is_active: amount = 10 if shift_is_active else 1 if event.key == KeyEvent.UpKey: self.setLayer(self._current_layer_num + amount) return True if event.key == KeyEvent.DownKey: self.setLayer(self._current_layer_num - amount) return True if event.type == Event.ViewActivateEvent: # FIX: on Max OS X, somehow QOpenGLContext.currentContext() can become None during View switching. # This can happen when you do the following steps: # 1. Start Cura # 2. Load a model # 3. Switch to Custom mode # 4. Select the model and click on the per-object tool icon # 5. Switch view to Layer view or X-Ray # 6. Cura will very likely crash # It seems to be a timing issue that the currentContext can somehow be empty, but I have no clue why. # This fix tries to reschedule the view changing event call on the Qt thread again if the current OpenGL # context is None. if Platform.isOSX(): if QOpenGLContext.currentContext() is None: Logger.log("d", "current context of OpenGL is empty on Mac OS X, will try to create shaders later") CuraApplication.getInstance().callLater(lambda e=event: self.event(e)) return # Make sure the SimulationPass is created layer_pass = self.getSimulationPass() self.getRenderer().addRenderPass(layer_pass) # Make sure the NozzleNode is add to the root nozzle = self.getNozzleNode() nozzle.setParent(self.getController().getScene().getRoot()) nozzle.setVisible(False) Application.getInstance().globalContainerStackChanged.connect(self._onGlobalStackChanged) self._onGlobalStackChanged() if not self._simulationview_composite_shader: self._simulationview_composite_shader = OpenGL.getInstance().createShaderProgram(os.path.join(PluginRegistry.getInstance().getPluginPath("SimulationView"), "simulationview_composite.shader")) theme = Application.getInstance().getTheme() self._simulationview_composite_shader.setUniformValue("u_background_color", Color(*theme.getColor("viewport_background").getRgb())) self._simulationview_composite_shader.setUniformValue("u_outline_color", Color(*theme.getColor("model_selection_outline").getRgb())) if not self._composite_pass: self._composite_pass = self.getRenderer().getRenderPass("composite") self._old_layer_bindings = self._composite_pass.getLayerBindings()[:] # make a copy so we can restore to it later self._composite_pass.getLayerBindings().append("simulationview") self._old_composite_shader = self._composite_pass.getCompositeShader() self._composite_pass.setCompositeShader(self._simulationview_composite_shader) elif event.type == Event.ViewDeactivateEvent: self._wireprint_warning_message.hide() Application.getInstance().globalContainerStackChanged.disconnect(self._onGlobalStackChanged) if self._global_container_stack: self._global_container_stack.propertyChanged.disconnect(self._onPropertyChanged) self._nozzle_node.setParent(None) self.getRenderer().removeRenderPass(self._layer_pass) self._composite_pass.setLayerBindings(self._old_layer_bindings) self._composite_pass.setCompositeShader(self._old_composite_shader)
# Cura is released under the terms of the LGPLv3 or higher. from typing import Type, TYPE_CHECKING, Optional, List import keyring from keyring.backend import KeyringBackend from keyring.errors import NoKeyringError, PasswordSetError from UM.Logger import Logger if TYPE_CHECKING: from cura.OAuth2.Models import BaseModel # Need to do some extra workarounds on windows: import sys from UM.Platform import Platform if Platform.isWindows() and hasattr(sys, "frozen"): import win32timezone from keyring.backends.Windows import WinVaultKeyring keyring.set_keyring(WinVaultKeyring()) if Platform.isOSX() and hasattr(sys, "frozen"): from keyring.backends.macOS import Keyring keyring.set_keyring(Keyring()) # Even if errors happen, we don't want this stored locally: DONT_EVER_STORE_LOCALLY: List[str] = ["refresh_token"] class KeyringAttribute: """ Descriptor for attributes that need to be stored in the keyring. With Fallback behaviour to the preference cfg file """