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 _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 _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 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 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 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 _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 _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 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 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, 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 exportQualityProfile(self, container_list, 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 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 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 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 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()
def __compress_gcode(self): exePath = None if Platform.isWindows(): exePath = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'VC_compress_gcode.exe') elif Platform.isOSX(): exePath = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'VC_compress_gcode_MAC') else: self.__log("w", "Could not find gcode compression tool") if exePath is not None and os.path.exists(exePath): cmd = '"' + exePath + '"' + ' "' + self._localTempGcode + '" ' + self._config["x_mm_per_step"] + ' ' + self._config["y_mm_per_step"] + ' ' + self._config["z_mm_per_step"] + ' ' + \ self._config["e_mm_per_step"] + ' "' + os.path.dirname(self._localTempGcode) + '" ' \ + self._config["s_x_max"] + ' ' + self._config["s_y_max"] + ' ' + self._config["s_z_max"] + ' ' + self._config["s_machine_type"] self.__log("d", cmd) ret = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True) self.__log("d", ret.stdout.read().decode('utf-8', 'ignore').rstrip()) if os.path.exists(self._localTempGcode + '.tz'): # check whether the compression succedded return True else: return False
def __init__(self): super().__init__() self._application = Application.getInstance() self._i18n_catalog = None settings_definition_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "arcwelder_settings.def.json") try: with open(settings_definition_path, "r", encoding = "utf-8") as f: self._settings_dict = json.load(f, object_pairs_hook = OrderedDict) except: Logger.logException("e", "Could not load arc welder settings definition") return if Platform.isWindows(): arcwelder_executable = "bin/win64/ArcWelder.exe" elif Platform.isLinux(): arcwelder_executable = "bin/linux/ArcWelder" elif Platform.isOSX(): arcwelder_executable = "bin/osx/ArcWelder" self._arcwelder_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), arcwelder_executable) try: os.chmod(self._arcwelder_path, stat.S_IXUSR | stat.S_IRUSR | stat.S_IRGRP | stat.S_IROTH | stat.S_IWUSR) # Make sure we have the rights to run this. except: Logger.logException("e", "Could modify rights of ArcWelder executable") return version_output = subprocess.check_output([self._arcwelder_path, "--version"]).decode() match = re.search("version: (.*)", version_output) if match: Logger.log("d", "Using ArcWelder %s" % match.group(1)) else: Logger.log("w", "Could not determine ArcWelder version") self._application.getPreferences().addPreference("arcwelderplugin/settings_made_visible", False) ContainerRegistry.getInstance().containerLoadComplete.connect(self._onContainerLoadComplete) self._application.getOutputDeviceManager().writeStarted.connect(self._filterGcode)
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 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 __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())) Logger.log("d", "...StorageRootPath is %s", Resources._getConfigStorageRootPath()) Logger.log("d", "...StorageDirName is %s", storage_dir_name ) # 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 _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 __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 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 __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 __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. if Platform.isWindows(): self._deleteLockFileWindows() else: self._deleteLockFile()
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 _filterGcode(self, output_device): scene = self._application.getController().getScene() global_container_stack = self._application.getGlobalContainerStack() if not global_container_stack: return arcwelder_enable = global_container_stack.getProperty( "arcwelder_enable", "value") if not arcwelder_enable: Logger.log("d", "ArcWelder is not enabled") return maximum_radius = global_container_stack.getProperty( "arcwelder_maximum_radius", "value") path_tolerance = (global_container_stack.getProperty( "arcwelder_tolerance", "value") / 100) resolution = global_container_stack.getProperty( "arcwelder_resolution", "value") firmware_compensation = global_container_stack.getProperty( "arcwelder_firmware_compensation", "value") min_arc_segment = int( global_container_stack.getProperty("arcwelder_min_arc_segment", "value")) mm_per_arc_segment = global_container_stack.getProperty( "arcwelder_mm_per_arc_segment", "value") allow_3d_arcs = global_container_stack.getProperty( "arcwelder_allow_3d_arcs", "value") allow_dynamic_precision = global_container_stack.getProperty( "arcwelder_allow_dynamic_precision", "value") allow_travel_arcs = global_container_stack.getProperty( "arcwelder_allow_travel_arcs", "value") default_xyz_precision = int( global_container_stack.getProperty( "arcwelder_default_xyz_precision", "value")) default_e_precision = int( global_container_stack.getProperty("arcwelder_default_e_precision", "value")) g90_influences_extruder = global_container_stack.getProperty( "arcwelder_g90_influences_extruder", "value") extrusion_rate_variance = (global_container_stack.getProperty( "arcwelder_extrusion_rate_variance", "value") / 100) max_gcode_length = int( global_container_stack.getProperty("arcwelder_max_gcode_length", "value")) # If the scene does not have a gcode, do nothing gcode_dict = getattr(scene, "gcode_dict", {}) if not gcode_dict: # this also checks for an empty dict Logger.log("w", "Scene has no gcode to process") return dict_changed = False layer_separator = ";ARCWELDERPLUGIN_GCODELIST_SEPARATOR\n" processed_marker = ";ARCWELDERPROCESSED\n" for plate_id in gcode_dict: gcode_list = gcode_dict[plate_id] if len(gcode_list) < 2: Logger.log("w", "Plate %s does not contain any layers", plate_id) continue if processed_marker in gcode_list[0]: Logger.log("d", "Plate %s has already been processed", plate_id) continue if len(gcode_list) > 0: # remove header from gcode, so we can put it back in front after processing header = gcode_list.pop(0) else: header = "" joined_gcode = layer_separator.join(gcode_list) file_descriptor, temporary_path = tempfile.mkstemp() Logger.log("d", "Using temporary file %s", temporary_path) with os.fdopen(file_descriptor, "w", encoding="utf-8") as temporary_file: temporary_file.write(joined_gcode) command_arguments = [ self._arcwelder_path, "-m=%f" % maximum_radius, "-t=%f" % path_tolerance, "-r=%f" % resolution, "-x=%d" % default_xyz_precision, "-e=%d" % default_e_precision, "-v=%f" % extrusion_rate_variance, "-c=%d" % max_gcode_length ] if firmware_compensation: command_arguments.extend( ["-s=%f" % mm_per_arc_segment, "-a=%d" % min_arc_segment]) if allow_3d_arcs: command_arguments.append("-z") if allow_dynamic_precision: command_arguments.append("-d") if allow_travel_arcs: command_arguments.append("-y") if g90_influences_extruder: command_arguments.append("-g") command_arguments.append(temporary_path) Logger.log( "d", "Running ArcWelder with the following options: %s" % command_arguments, ) if Platform.isWindows(): startupinfo = subprocess.STARTUPINFO() startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW else: startupinfo = None process_output = subprocess.check_output( command_arguments, startupinfo=startupinfo).decode(locale.getpreferredencoding()) Logger.log("d", process_output) with open(temporary_path, "r", encoding="utf-8") as temporary_file: result_gcode = temporary_file.read() os.remove(temporary_path) gcode_list = result_gcode.split(layer_separator) if header != "": gcode_list.insert(0, header) # add header back in front gcode_list[0] += processed_marker gcode_dict[plate_id] = gcode_list dict_changed = True if dict_changed: setattr(scene, "gcode_dict", gcode_dict)
# 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 # The workaround is only needed on Ubuntu+NVidia drivers. Other drivers are not affected, but fine with this fix. try: import ctypes from ctypes.util import find_library libGL = find_library("GL") ctypes.CDLL(libGL, ctypes.RTLD_GLOBAL) except: # GLES-only systems (e.g. ARM Mali) do not have libGL, ignore error pass # 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 # It looks like setuptools creates a .pth file in # the default /usr/lib which causes the default site-packages # to be inserted into sys.path before PYTHONPATH. # This can cause issues such as having libsip loaded from # the system instead of the one provided with Cura, which causes # incompatibility issues with libArcus if "PYTHONPATH" in os.environ.keys(): # If PYTHONPATH is used PYTHONPATH = os.environ["PYTHONPATH"].split(os.pathsep) # Get the value, split it.. PYTHONPATH.reverse() # and reverse it, because we always insert at 1
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 # It looks like setuptools creates a .pth file in # the default /usr/lib which causes the default site-packages # to be inserted into sys.path before PYTHONPATH. # This can cause issues such as having libsip loaded from # the system instead of the one provided with Cura, which causes # incompatibility issues with libArcus if "PYTHONPATH" in os.environ.keys(): # If PYTHONPATH is used PYTHONPATH = os.environ["PYTHONPATH"].split( os.pathsep) # Get the value, split it..
def __init__(self) -> None: 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(CuraApplication.getInstallPrefix(), "bin", executable_name)): default_engine_location = os.path.join(CuraApplication.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 cast(str, os.getenv("PATH")).split(os.pathsep): execpath = os.path.join(pathdir, executable_name) if os.path.exists(execpath): default_engine_location = execpath break self._application = CuraApplication.getInstance() #type: CuraApplication self._multi_build_plate_model = None #type: Optional[MultiBuildPlateModel] self._machine_error_checker = None #type: Optional[MachineErrorChecker] 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) self._application.getPreferences().addPreference("backend/location", default_engine_location) # Workaround to disable layer view processing if layer view is not active. self._layer_view_active = False #type: bool self._onActiveViewChanged() self._stored_layer_data = [] # type: List[Arcus.PythonMessage] self._stored_optimized_layer_data = {} # type: Dict[int, List[Arcus.PythonMessage]] # key is build plate number, then arrays are stored until they go to the ProcessSlicesLayersJob self._scene = self._application.getController().getScene() #type: Scene 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, 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 #type: Optional[ContainerStack] # 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 #type: Optional[StartSliceJob] self._start_slice_job_build_plate = None #type: Optional[int] self._slicing = False #type: bool # Are we currently slicing? self._restart = False #type: bool # Back-end is currently restarting? self._tool_active = False #type: bool # If a tool is active, some tasks do not have to do anything self._always_restart = True #type: bool # Always restart the engine when starting a new slice. Don't keep the process running. TODO: Fix engine statelessness. self._process_layers_job = None #type: Optional[ProcessSlicedLayersJob] # The currently active job to process layers, or None if it is not processing layers. self._build_plates_to_be_sliced = [] #type: List[int] # what needs slicing? self._engine_is_fresh = True #type: bool # Is the newly started engine used before or not? self._backend_log_max_lines = 20000 #type: int # Maximum number of lines to buffer self._error_message = None #type: Optional[Message] # Pop-up message that shows errors. self._last_num_objects = defaultdict(int) #type: Dict[int, int] # Count number of objects to see if there is something changed self._postponed_scene_change_sources = [] #type: List[SceneNode] # scene change is postponed (by a tool) self._slice_start_time = None #type: Optional[float] self._is_disabled = False #type: bool self._application.getPreferences().addPreference("general/auto_slice", False) self._use_timer = False #type: bool # 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() #type: QTimer self._change_timer.setSingleShot(True) self._change_timer.setInterval(500) self.determineAutoSlicing() self._application.getPreferences().preferenceChanged.connect(self._onPreferencesChanged) self._application.initializationFinished.connect(self.initialize)
def __init__(self) -> None: """Starts the back-end plug-in. This registers all the signal listeners and prepares for communication with the back-end in general. CuraEngineBackend is exposed to qml as well. """ 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 search_path = [ os.path.abspath(os.path.dirname(sys.executable)), os.path.abspath( os.path.join(os.path.dirname(sys.executable), "bin")), os.path.abspath(os.path.join(os.path.dirname(sys.executable), "..")), os.path.join(CuraApplication.getInstallPrefix(), "bin"), os.path.dirname(os.path.abspath(sys.executable)), ] for path in search_path: engine_path = os.path.join(path, executable_name) if os.path.isfile(engine_path): default_engine_location = engine_path break 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 cast(str, os.getenv("PATH")).split(os.pathsep): execpath = os.path.join(pathdir, executable_name) if os.path.exists(execpath): default_engine_location = execpath break application = CuraApplication.getInstance() #type: CuraApplication self._multi_build_plate_model = None #type: Optional[MultiBuildPlateModel] self._machine_error_checker = None #type: Optional[MachineErrorChecker] 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) application.getPreferences().addPreference("backend/location", default_engine_location) # Workaround to disable layer view processing if layer view is not active. self._layer_view_active = False #type: bool self._onActiveViewChanged() self._stored_layer_data = [] # type: List[Arcus.PythonMessage] self._stored_optimized_layer_data = { } # type: Dict[int, List[Arcus.PythonMessage]] # key is build plate number, then arrays are stored until they go to the ProcessSlicesLayersJob self._scene = application.getController().getScene() #type: Scene 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, 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 #type: Optional[ContainerStack] # 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 #type: Optional[StartSliceJob] self._start_slice_job_build_plate = None #type: Optional[int] self._slicing = False #type: bool # Are we currently slicing? self._restart = False #type: bool # Back-end is currently restarting? self._tool_active = False #type: bool # If a tool is active, some tasks do not have to do anything self._always_restart = True #type: bool # Always restart the engine when starting a new slice. Don't keep the process running. TODO: Fix engine statelessness. self._process_layers_job = None #type: Optional[ProcessSlicedLayersJob] # The currently active job to process layers, or None if it is not processing layers. self._build_plates_to_be_sliced = [ ] #type: List[int] # what needs slicing? self._engine_is_fresh = True #type: bool # Is the newly started engine used before or not? self._backend_log_max_lines = 20000 #type: int # Maximum number of lines to buffer self._error_message = None #type: Optional[Message] # Pop-up message that shows errors. self._last_num_objects = defaultdict( int ) #type: Dict[int, int] # Count number of objects to see if there is something changed self._postponed_scene_change_sources = [ ] #type: List[SceneNode] # scene change is postponed (by a tool) self._slice_start_time = None #type: Optional[float] self._is_disabled = False #type: bool application.getPreferences().addPreference("general/auto_slice", False) self._use_timer = False #type: bool # 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() #type: QTimer self._change_timer.setSingleShot(True) self._change_timer.setInterval(500) self.determineAutoSlicing() application.getPreferences().preferenceChanged.connect( self._onPreferencesChanged) application.initializationFinished.connect(self.initialize)
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 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 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( ).findDefinitionContainersMetadata(id=extruder_id) if extruder_containers: extruder_positions.append( int(extruder_containers[0].get("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)) ] base_containers = [] quality_manager = QualityManager.getInstance() global_container_stack = Application.getInstance( ).getGlobalContainerStack() global_machine_definition = quality_manager.getParentMachineDefinition( global_container_stack.getBottom()) for container in found_containers: if container.getMetaDataEntry("type") == "quality_changes": material = ContainerRegistry.getInstance( ).findInstanceContainers( id=container.getMetaDataEntry("material")) if len(material) > 0: base_container = quality_manager.findQualityByQualityType( container.getMetaDataEntry("quality_type"), global_machine_definition, [material[0].getMetaData()]) if base_container: c = copy.deepcopy(base_container) c.setMetaDataEntry("id", "base/" + container.id) base_containers.append(c) found_containers.extend(base_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 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 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 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()
def __init__(self) -> None: super().__init__() executable_name = "CliParser" if Platform.isWindows(): executable_name += ".exe" default_engine_location = executable_name if os.path.exists(os.path.join(SteSlicerApplication.getInstallPrefix(), "bin", executable_name)): default_engine_location = os.path.join(SteSlicerApplication.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 cast(str, os.getenv("PATH")).split(os.pathsep): execpath = os.path.join(pathdir, executable_name) if os.path.exists(execpath): default_engine_location = execpath break self._application = SteSlicerApplication.getInstance() #type: SteSlicerApplication self._multi_build_plate_model = None #type: Optional[MultiBuildPlateModel] self._machine_error_checker = None #type: Optional[MachineErrorChecker] if not default_engine_location: raise EnvironmentError("Could not find CliParser") Logger.log("i", "Found CliParser at: %s", default_engine_location) default_engine_location = os.path.abspath(default_engine_location) self._application.getPreferences().addPreference("cli_backend/location", default_engine_location) # Workaround to disable layer view processing if layer view is not active. self._layer_view_active = False #type: bool self._onActiveViewChanged() self._stored_optimized_layer_data = {} #type: Dict[int, List[Arcus.PythonMessage]] # key is build plate number, then arrays are stored until they go to the ProcessSlicesLayersJob self._scene = self._application.getController().getScene() #type: Scene self._scene.sceneChanged.connect(self._onSceneChanged) self._global_container_stack = None # type: Optional[ContainerStack] self._start_slice_job = None # type: Optional[StartSliceJob] self._process_cli_job = None # type: Optional[ProcessCliJob] self._start_slice_job_build_plate = None # type: Optional[int] self._glicer_process = None self._slicing = False # type: bool # Are we currently slicing? self._restart = False # type: bool # Back-end is currently restarting? self._tool_active = False # type: bool # If a tool is active, some tasks do not have to do anything self._always_restart = True # type: bool # Always restart the engine when starting a new slice. Don't keep the process running. TODO: Fix engine statelessness. self._process_layers_job = None # type: Optional[ProcessSlicedLayersJob] # The currently active job to process layers, or None if it is not processing layers. self._build_plates_to_be_sliced = [] # type: List[int] # what needs slicing? self._engine_is_fresh = True # type: bool # Is the newly started engine used before or not? self._backend_log_max_lines = 20000 # type: int # Maximum number of lines to buffer self._error_message = None # type: Optional[Message] # Pop-up message that shows errors. self._last_num_objects = defaultdict( int) # type: Dict[int, int] # Count number of objects to see if there is something changed self._postponed_scene_change_sources = [] # type: List[SceneNode] # scene change is postponed (by a tool) self._slice_start_time = None # type: Optional[float] self._is_disabled = False # type: bool self._application.getPreferences().addPreference("general/auto_slice", False) self._use_timer = False # type: bool # 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() # type: QTimer self._change_timer.setSingleShot(True) self._change_timer.setInterval(500) self.determineAutoSlicing() self._application.getPreferences().preferenceChanged.connect(self._onPreferencesChanged) self._application.initializationFinished.connect(self.initialize)
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 __enter__(self) -> None: if Platform.isWindows(): self._createLockFileWindows() else: self._createLockFile()
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) # 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 __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)
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 exportProfile(self, id, name, url, fileType): #Input checking. path = url.toLocalFile() if not path: error_str = "Not a valid path. If this problem persists, please report a bug." error_str = i18nCatalog.i18nc("@info:status", error_str) return {"status": "error", "message": error_str} # 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(path): 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(path)) if result == QMessageBox.No: return if id == -1: #id -1 references the "Current settings"/working profile profile = copy.deepcopy(self._working_profile) profile.setType(None) profile.setMachineTypeId(self._manager.getActiveMachineInstance( ).getMachineDefinition().getProfilesMachineId()) else: profile = self._manager.findProfile( name, instance=self._manager.getActiveMachineInstance()) if not profile: error_str = "Profile not found. If this problem persists, please report a bug." error_str = i18nCatalog.i18nc("@info:status", error_str) return {"status": "error", "message": error_str} #Parse the fileType to deduce what plugin can save the file format. #TODO: This parsing can be made unnecessary by storing for each plugin what the fileType string is in complete (in addition to the {(description,extension)} dict). #fileType has the format "<description> (*.<extension>)" split = fileType.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", fileType) error_str = catalog.i18nc("@info:status", "Invalid file format: {0}", fileType) return {"status": "error", "message": error_str} description = fileType[:split] extension = fileType[split + 4:-1] #Leave out the " (*." and ")". if not path.endswith( "." + extension ): #Auto-fill the extension if the user did not provide any. path += "." + extension good_profile_writer = None for profile_writer_id, profile_writer in self._manager.getProfileWriters( ): #Find which profile writer can write this file type. meta_data = PluginRegistry.getInstance().getMetaData( profile_writer_id) if "profile_writer" in meta_data: for supported_type in meta_data[ "profile_writer"]: #All file types this plugin can supposedly write. supported_extension = supported_type.get("extension", None) if supported_extension == extension: #This plugin supports a file type with the same extension. supported_description = supported_type.get( "description", None) if supported_description == description: #The description is also identical. Assume it's the same file type. good_profile_writer = profile_writer break if good_profile_writer: #If we found a writer in this iteration, break the loop. break success = False try: success = good_profile_writer.write(path, profile) except Exception as e: Logger.log("e", "Failed to export profile to %s: %s", path, str(e)) error_str = catalog.i18nc( "@info:status", "Failed to export profile to <filename>{0}</filename>: <message>{1}</message>", path, str(e)) return {"status": "error", "message": error_str} if not success: Logger.log( "w", "Failed to export profile to %s: Writer plugin reported failure.", path) error_str = catalog.i18nc( "@info:status", "Failed to export profile to <filename>{0}</filename>: Writer plugin reported failure.", path) return {"status": "error", "message": error_str} m = Message( catalog.i18nc("@info:status", "Exported profile to <filename>{0}</filename>", path)) 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. default_engine_location = os.path.join(Application.getInstallPrefix(), "bin", "CuraEngine") if hasattr(sys, "frozen"): default_engine_location = os.path.join(os.path.dirname(os.path.abspath(sys.executable)), "CuraEngine") if Platform.isWindows(): default_engine_location += ".exe" 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) # 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 = [] #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 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.Progress"] = self._onProgressMessage self._message_handlers["cura.proto.GCodeLayer"] = self._onGCodeLayerMessage self._message_handlers["cura.proto.GCodePrefix"] = self._onGCodePrefixMessage self._message_handlers["cura.proto.ObjectPrintTime"] = self._onObjectPrintTimeMessage 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._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)
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) self._onActiveViewChanged() self._stored_layer_data = [] self._stored_optimized_layer_data = [] 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) 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._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._need_slicing = False 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 = 0 # 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)
import numpy import math #from . import trimesh from UM.Platform import Platform from UM.Extension import Extension from UM.Application import Application from UM.Logger import Logger from UM.Mesh.MeshData import MeshData, calculateNormalsFromIndexedVertices from UM.Math.Vector import Vector from UM.i18n import i18nCatalog catalog = i18nCatalog("cura") if (Platform.isWindows()): from .ThirdParty.win import trimesh elif (Platform.isOSX()): from .ThirdParty.mac import trimesh class SupportMeshCreator(): def __init__(self, support_angle=None, filter_upwards_facing_faces=True, down_vector=numpy.array([0, -1, 0]), bottom_cut_off=0, minimum_island_area=0): self._support_angle = support_angle if self._support_angle is None: global_container_stack = Application.getInstance(
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 __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. default_engine_location = os.path.join(Application.getInstallPrefix(), "bin", "CuraEngine") if hasattr(sys, "frozen"): default_engine_location = os.path.join( os.path.dirname(os.path.abspath(sys.executable)), "CuraEngine") if Platform.isWindows(): default_engine_location += ".exe" 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) # 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 = [] #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 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.Progress"] = self._onProgressMessage self._message_handlers[ "cura.proto.GCodeLayer"] = self._onGCodeLayerMessage self._message_handlers[ "cura.proto.GCodePrefix"] = self._onGCodePrefixMessage self._message_handlers[ "cura.proto.ObjectPrintTime"] = self._onObjectPrintTimeMessage 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._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)
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 exportContainer( self, container_id: str, file_type: str, file_url_or_string: Union[QUrl, str]) -> Dict[str, str]: """Export a container to a file :param container_id: The ID of the container to export :param file_type: The type of file to save as. Should be in the form of "description (*.extension, *.ext)" :param file_url_or_string: The URL where to save the file. :return: A dictionary containing a key "status" with a status code and a key "message" with a message explaining the status. The status code can be one of "error", "cancelled", "success" """ 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" } try: with SaveFile(file_url, "w") as f: f.write(contents) except OSError: return { "status": "error", "message": "Unable to write to this location.", "path": file_url } Logger.info( "Successfully exported container to {path}".format(path=file_url)) return { "status": "success", "message": "Successfully exported container", "path": file_url }