Exemplo n.º 1
0
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")
Exemplo n.º 2
0
 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)
Exemplo n.º 3
0
    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}
Exemplo n.º 4
0
    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
Exemplo n.º 5
0
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 { }
Exemplo n.º 6
0
    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
Exemplo n.º 7
0
    def _sendCrashReport(self):
        # Before sending data, the user comments are stored
        self.data["user_info"] = self.user_description_text_area.toPlainText()

        # Convert data to bytes
        binary_data = json.dumps(self.data).encode("utf-8")

        # Submit data
        kwoptions = {"data": binary_data, "timeout": 5}

        if Platform.isOSX():
            kwoptions["context"] = ssl._create_unverified_context()

        Logger.log("i", "Sending crash report info to [%s]...", self.crash_url)
        if not self.has_started:
            print("Sending crash report info to [%s]...\n" % self.crash_url)

        try:
            f = urllib.request.urlopen(self.crash_url, **kwoptions)
            Logger.log("i", "Sent crash report info.")
            if not self.has_started:
                print("Sent crash report info.\n")
            f.close()
        except urllib.error.HTTPError as e:
            Logger.logException("e", "An HTTP error occurred while trying to send crash report")
            if not self.has_started:
                print("An HTTP error occurred while trying to send crash report: %s" % e)
        except Exception as e:  # We don't want any exception to cause problems
            Logger.logException("e", "An exception occurred while trying to send crash report")
            if not self.has_started:
                print("An exception occurred while trying to send crash report: %s" % e)

        os._exit(1)
Exemplo n.º 8
0
    def restore(self) -> bool:
        if not self.zip_file or not self.meta_data or not self.meta_data.get("cura_release", None):
            # We can restore without the minimum required information.
            Logger.log("w", "Tried to restore a Cura backup without having proper data or meta data.")
            self._showMessage(
                self.catalog.i18nc("@info:backup_failed",
                                   "Tried to restore a Cura backup without having proper data or meta data."))
            return False

        current_version = self._application.getVersion()
        version_to_restore = self.meta_data.get("cura_release", "master")

        if current_version < version_to_restore:
            # Cannot restore version newer than current because settings might have changed.
            Logger.log("d", "Tried to restore a Cura backup of version {version_to_restore} with cura version {current_version}".format(version_to_restore = version_to_restore, current_version = current_version))
            self._showMessage(
                self.catalog.i18nc("@info:backup_failed",
                                   "Tried to restore a Cura backup that is higher than the current version."))
            return False

        version_data_dir = Resources.getDataStoragePath()
        archive = ZipFile(io.BytesIO(self.zip_file), "r")
        extracted = self._extractArchive(archive, version_data_dir)

        # Under Linux, preferences are stored elsewhere, so we copy the file to there.
        if Platform.isLinux():
            preferences_file_name = self._application.getApplicationName()
            preferences_file = Resources.getPath(Resources.Preferences, "{}.cfg".format(preferences_file_name))
            backup_preferences_file = os.path.join(version_data_dir, "{}.cfg".format(preferences_file_name))
            Logger.log("d", "Moving preferences file from %s to %s", backup_preferences_file, preferences_file)
            shutil.move(backup_preferences_file, preferences_file)

        return extracted
Exemplo n.º 9
0
    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)
Exemplo n.º 10
0
    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
Exemplo n.º 11
0
def getMetaData():
    # Workarround for osx not supporting double file extensions correctly.
    if Platform.isOSX():
        workspace_extension = "3mf"
    else:
        workspace_extension = "curaproject.3mf"

    metaData = {}

    if "3MFWriter.ThreeMFWriter" in sys.modules:
        metaData["mesh_writer"] = {
            "output": [{
                "extension": "3mf",
                "description": i18n_catalog.i18nc("@item:inlistbox", "3MF file"),
                "mime_type": "application/vnd.ms-package.3dmanufacturing-3dmodel+xml",
                "mode": ThreeMFWriter.ThreeMFWriter.OutputMode.BinaryMode
            }]
        }
        metaData["workspace_writer"] = {
            "output": [{
                "extension": workspace_extension,
                "description": i18n_catalog.i18nc("@item:inlistbox", "Cura Project 3MF file"),
                "mime_type": "application/x-curaproject+xml",
                "mode": ThreeMFWorkspaceWriter.ThreeMFWorkspaceWriter.OutputMode.BinaryMode
            }]
        }

    return metaData
Exemplo n.º 12
0
def getMetaData() -> Dict:
    # Workarround for osx not supporting double file extensions correclty.
    if Platform.isOSX():
        workspace_extension = "3mf"
    else:
        workspace_extension = "curaproject.3mf"

    metaData = {
        "plugin": {
            "name": catalog.i18nc("@label", "3MF Reader"),
            "author": "Ultimaker",
            "version": "1.0",
            "description": catalog.i18nc("@info:whatsthis", "Provides support for reading 3MF files."),
            "api": 3
        }
    }
    if "3MFReader.ThreeMFReader" in sys.modules:
        metaData["mesh_reader"] = [
            {
                "extension": "3mf",
                "description": catalog.i18nc("@item:inlistbox", "3MF File")
            }
        ]
        metaData["workspace_reader"] = [
            {
                "extension": workspace_extension,
                "description": catalog.i18nc("@item:inlistbox", "3MF File")
            }
        ]
    
    return metaData
Exemplo n.º 13
0
    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)
Exemplo n.º 14
0
    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
Exemplo n.º 15
0
 def _getDataStorageRootPath(cls):
     # Returns the path where we store different versions of app data
     data_path = None
     if Platform.isLinux():
         try:
             data_path = os.environ["XDG_DATA_HOME"]
         except KeyError:
             data_path = os.path.expanduser("~/.local/share")
     return data_path
Exemplo n.º 16
0
    def _updateContainerNameFilters(self) -> None:
        self._container_name_filters = {}
        for plugin_id, container_type in self._container_registry.getContainerTypes():
            # Ignore default container types since those are not plugins
            if container_type in (InstanceContainer, ContainerStack, DefinitionContainer):
                continue

            serialize_type = ""
            try:
                plugin_metadata = self._plugin_registry.getMetaData(plugin_id)
                if plugin_metadata:
                    serialize_type = plugin_metadata["settings_container"]["type"]
                else:
                    continue
            except KeyError as e:
                continue

            mime_type = self._container_registry.getMimeTypeForContainer(container_type)
            if mime_type is None:
                continue
            entry = {
                "type": serialize_type,
                "mime": mime_type,
                "container": container_type
            }

            suffix = mime_type.preferredSuffix
            if Platform.isOSX() and "." in suffix:
                # OSX's File dialog is stupid and does not allow selecting files with a . in its name
                suffix = suffix[suffix.index(".") + 1:]

            suffix_list = "*." + suffix
            for suffix in mime_type.suffixes:
                if suffix == mime_type.preferredSuffix:
                    continue

                if Platform.isOSX() and "." in suffix:
                    # OSX's File dialog is stupid and does not allow selecting files with a . in its name
                    suffix = suffix[suffix.index("."):]

                suffix_list += ", *." + suffix

            name_filter = "{0} ({1})".format(mime_type.comment, suffix_list)
            self._container_name_filters[name_filter] = entry
Exemplo n.º 17
0
    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()
Exemplo n.º 18
0
def getMetaData():
    file_extension = "gz" if Platform.isOSX() else "gcode.gz"
    return {
        "mesh_reader": [
            {
                "extension": file_extension,
                "description": i18n_catalog.i18nc("@item:inlistbox", "Compressed G-code File")
            }
        ]
    }
Exemplo n.º 19
0
    def _getPossibleDataStorageRootPathList(cls) -> List[str]:
        data_root_list = []

        # Returns all possible root paths for storing app configurations (in old and new versions)
        if Platform.isLinux():
            data_root_list.append(os.path.join(Resources._getDataStorageRootPath(), cls.ApplicationIdentifier))
        else:
            # on Windows and Mac, data and config are saved in the same place
            data_root_list = Resources._getPossibleConfigStorageRootPathList()

        return data_root_list
Exemplo n.º 20
0
    def supportedPluginExtensions(self):
        file_types = []
        all_types = []

        if Platform.isLinux():
            for ext, desc in self._supported_file_types.items():
                file_types.append("{0} (*.{1} *.{2})".format(desc, ext.lower(), ext.upper()))
                all_types.append("*.{0} *.{1}".format(ext.lower(), ext.upper()))
        else:
            for ext, desc in self._supported_file_types.items():
                file_types.append("{0} (*.{1})".format(desc, ext))
                all_types.append("*.{0}".format(ext))

        file_types.sort()
        file_types.insert(0, i18n_catalog.i18nc("@item:inlistbox", "All Supported Types ({0})", " ".join(all_types)))
        file_types.append(i18n_catalog.i18nc("@item:inlistbox", "All Files (*)"))
        return file_types
Exemplo n.º 21
0
    def supportedPluginExtensions(self) -> List[str]:
        file_types = []
        all_types = []

        if Platform.isLinux():
            for ext, desc in self._supported_file_types.items():
                file_types.append("{0} (*.{1} *.{2})".format(desc, ext.lower(), ext.upper()))
                all_types.append("*.{0} *.{1}".format(ext.lower(), ext.upper()))
        else:
            for ext, desc in self._supported_file_types.items():
                file_types.append("{0} (*.{1})".format(desc, ext))
                all_types.append("*.{0}".format(ext))

        file_types.sort()
        file_types.insert(0, i18n_catalog.i18nc("@item:inlistbox", "All Supported Types ({0})", " ".join(all_types)))
        file_types.append(i18n_catalog.i18nc("@item:inlistbox", "All Files (*)"))
        return file_types
Exemplo n.º 22
0
    def __exit__(
        self, exc_type: type, exc_val: Exception, exc_tb: Any
    ) -> None:  #exc_tb is actually a traceback object which is not exposed in Python.
        """Release the lock file so that other processes may use it.
        
        :param exc_type: The type of exception that was raised during the
            ``with`` block, if any. Use ``None`` if no exception was raised.
        :param exc_val: The exception instance that was raised during the
            ``with`` block, if any. Use ``None`` if no exception was raised.
        :param exc_tb: The traceback frames at the time the exception occurred,
            if any. Use ``None`` if no exception was raised.
        """

        if Platform.isWindows():
            self._deleteLockFileWindows()
        else:
            self._deleteLockFile()
Exemplo n.º 23
0
    def makeFromCurrent(self) -> None:
        steslicer_release = self._application.getVersion()
        version_data_dir = Resources.getDataStoragePath()

        Logger.log("d", "Creating backup for STE Slicer %s, using folder %s",
                   steslicer_release, version_data_dir)

        # Ensure all current settings are saved.
        self._application.saveSettings()

        # We copy the preferences file to the user data directory in Linux as it's in a different location there.
        # When restoring a backup on Linux, we move it back.
        if Platform.isLinux():
            preferences_file_name = self._application.getApplicationName()
            preferences_file = Resources.getPath(
                Resources.Preferences, "{}.cfg".format(preferences_file_name))
            backup_preferences_file = os.path.join(
                version_data_dir, "{}.cfg".format(preferences_file_name))
            Logger.log("d", "Copying preferences file from %s to %s",
                       preferences_file, backup_preferences_file)
            shutil.copyfile(preferences_file, backup_preferences_file)

        # Create an empty buffer and write the archive to it.
        buffer = io.BytesIO()
        archive = self._makeArchive(buffer, version_data_dir)
        if archive is None:
            return
        files = archive.namelist()

        # Count the metadata items. We do this in a rather naive way at the moment.
        machine_count = len([s
                             for s in files if "machine_instances/" in s]) - 1
        material_count = len([s for s in files if "materials/" in s]) - 1
        profile_count = len([s for s in files if "quality_changes/" in s]) - 1
        plugin_count = len([s for s in files if "plugin.json" in s])

        # Store the archive and metadata so the BackupManager can fetch them when needed.
        self.zip_file = buffer.getvalue()
        self.meta_data = {
            "steslicer_release": steslicer_release,
            "machine_count": str(machine_count),
            "material_count": str(material_count),
            "profile_count": str(profile_count),
            "plugin_count": str(plugin_count)
        }
Exemplo n.º 24
0
    def event(self, event):
        if event.type == Event.ViewActivateEvent:
            # FIX: on Max OS X, somehow QOpenGLContext.currentContext() can become None during View switching.
            # This can happen when you do the following steps:
            #   1. Start Cura
            #   2. Load a model
            #   3. Switch to Custom mode
            #   4. Select the model and click on the per-object tool icon
            #   5. Switch view to Layer view or X-Ray
            #   6. Cura will very likely crash
            # It seems to be a timing issue that the currentContext can somehow be empty, but I have no clue why.
            # This fix tries to reschedule the view changing event call on the Qt thread again if the current OpenGL
            # context is None.
            if Platform.isOSX():
                if QOpenGLContext.currentContext() is None:
                    Logger.log("d", "current context of OpenGL is empty on Mac OS X, will try to create shaders later")
                    CuraApplication.getInstance().callLater(lambda e = event: self.event(e))
                    return

            if not self._xray_pass:
                # Currently the RenderPass constructor requires a size > 0
                # This should be fixed in RenderPass's constructor.
                self._xray_pass = XRayPass.XRayPass(1, 1)

            self.getRenderer().addRenderPass(self._xray_pass)

            if not self._xray_composite_shader:
                self._xray_composite_shader = OpenGL.getInstance().createShaderProgram(os.path.join(PluginRegistry.getInstance().getPluginPath("XRayView"), "xray_composite.shader"))
                theme = Application.getInstance().getTheme()
                self._xray_composite_shader.setUniformValue("u_background_color", Color(*theme.getColor("viewport_background").getRgb()))
                self._xray_composite_shader.setUniformValue("u_error_color", Color(*theme.getColor("xray_error").getRgb()))
                self._xray_composite_shader.setUniformValue("u_outline_color", Color(*theme.getColor("model_selection_outline").getRgb()))

            if not self._composite_pass:
                self._composite_pass = self.getRenderer().getRenderPass("composite")

            self._old_layer_bindings = self._composite_pass.getLayerBindings()
            self._composite_pass.setLayerBindings(["default", "selection", "xray"])
            self._old_composite_shader = self._composite_pass.getCompositeShader()
            self._composite_pass.setCompositeShader(self._xray_composite_shader)

        if event.type == Event.ViewDeactivateEvent:
            self.getRenderer().removeRenderPass(self._xray_pass)
            self._composite_pass.setLayerBindings(self._old_layer_bindings)
            self._composite_pass.setCompositeShader(self._old_composite_shader)
Exemplo n.º 25
0
    def __initializeStoragePaths(cls):
        Logger.log("d", "Initializing storage paths")
        # use nested structure: <app-name>/<version>/...
        if cls.ApplicationVersion == "master" or cls.ApplicationVersion == "unknown":
            storage_dir_name = os.path.join(cls.ApplicationIdentifier,
                                            cls.ApplicationVersion)
        else:
            from UM.Version import Version
            version = Version(cls.ApplicationVersion)
            storage_dir_name = os.path.join(
                cls.ApplicationIdentifier,
                "%s.%s" % (version.getMajor(), version.getMinor()))

        # config is saved in "<CONFIG_ROOT>/<storage_dir_name>"
        cls.__config_storage_path = os.path.join(
            Resources._getConfigStorageRootPath(), storage_dir_name)
        Logger.log("d", "Config storage path is %s", cls.__config_storage_path)

        # data is saved in
        #  - on Linux: "<DATA_ROOT>/<storage_dir_name>"
        #  - on other: "<CONFIG_DIR>" (in the config directory)
        data_root_path = Resources._getDataStorageRootPath()
        cls.__data_storage_path = cls.__config_storage_path if data_root_path is None else \
            os.path.join(data_root_path, storage_dir_name)
        Logger.log("d", "Data storage path is %s", cls.__data_storage_path)
        # cache is saved in
        #  - on Linux:   "<CACHE_DIR>/<storage_dir_name>"
        #  - on Windows: "<CACHE_DIR>/<storage_dir_name>/cache"
        #  - on Mac:     "<CONFIG_DIR>/cache" (in the config directory)
        cache_root_path = Resources._getCacheStorageRootPath()
        if cache_root_path is None:
            cls.__cache_storage_path = os.path.join(cls.__config_storage_path,
                                                    "cache")
        else:
            cls.__cache_storage_path = os.path.join(cache_root_path,
                                                    storage_dir_name)
            if Platform.isWindows():
                cls.__cache_storage_path = os.path.join(
                    cls.__cache_storage_path, "cache")
        Logger.log("d", "Cache storage path is %s", cls.__cache_storage_path)
        if not os.path.exists(cls.__config_storage_path) or not os.path.exists(
                cls.__data_storage_path):
            cls._copyLatestDirsIfPresent()

        cls.__paths.insert(0, cls.__data_storage_path)
Exemplo n.º 26
0
    def __init__(self, **kwargs):
        # These collections must be treated as immutable otherwise we lose thread safety.
        self.__functions = WeakImmutableList()
        self.__methods = WeakImmutablePairList()
        self.__signals = WeakImmutableList()

        self.__lock = threading.Lock()  # Guards access to the fields above.

        self.__type = kwargs.get("type", Signal.Auto)

        if "URANIUM_TRACE_SIGNALS" in os.environ:
            try:
                if Platform.isWindows():
                    self.__name = inspect.stack()[1][0].f_locals["key"]
                else:
                    self.__name = inspect.stack()[1].frame.f_locals["key"]
            except KeyError:
                self.__name = "Signal"
Exemplo n.º 27
0
    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
Exemplo n.º 28
0
    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
Exemplo n.º 29
0
    def restore(self) -> bool:
        if not self.zip_file or not self.meta_data or not self.meta_data.get(
                "cura_release", None):
            # We can restore without the minimum required information.
            Logger.log(
                "w",
                "Tried to restore a Cura backup without having proper data or meta data."
            )
            self._showMessage(
                self.catalog.i18nc(
                    "@info:backup_failed",
                    "Tried to restore a Cura backup without having proper data or meta data."
                ))
            return False

        current_version = CuraApplication.getInstance().getVersion()
        version_to_restore = self.meta_data.get("cura_release", "master")
        if current_version != version_to_restore:
            # Cannot restore version older or newer than current because settings might have changed.
            # Restoring this will cause a lot of issues so we don't allow this for now.
            self._showMessage(
                self.catalog.i18nc(
                    "@info:backup_failed",
                    "Tried to restore a Cura backup that does not match your current version."
                ))
            return False

        version_data_dir = Resources.getDataStoragePath()
        archive = ZipFile(io.BytesIO(self.zip_file), "r")
        extracted = self._extractArchive(archive, version_data_dir)

        # Under Linux, preferences are stored elsewhere, so we copy the file to there.
        if Platform.isLinux():
            preferences_file_name = CuraApplication.getInstance(
            ).getApplicationName()
            preferences_file = Resources.getPath(
                Resources.Preferences, "{}.cfg".format(preferences_file_name))
            backup_preferences_file = os.path.join(
                version_data_dir, "{}.cfg".format(preferences_file_name))
            Logger.log("d", "Moving preferences file from %s to %s",
                       backup_preferences_file, preferences_file)
            shutil.move(backup_preferences_file, preferences_file)

        return extracted
Exemplo n.º 30
0
    def event(self, event):
        if event.type == Event.ViewActivateEvent:
            # FIX: on Max OS X, somehow QOpenGLContext.currentContext() can become None during View switching.
            # This can happen when you do the following steps:
            #   1. Start Cura
            #   2. Load a model
            #   3. Switch to Custom mode
            #   4. Select the model and click on the per-object tool icon
            #   5. Switch view to Layer view or X-Ray
            #   6. Cura will very likely crash
            # It seems to be a timing issue that the currentContext can somehow be empty, but I have no clue why.
            # This fix tries to reschedule the view changing event call on the Qt thread again if the current OpenGL
            # context is None.
            if Platform.isOSX():
                if QOpenGLContext.currentContext() is None:
                    Logger.log("d", "current context of OpenGL is empty on Mac OS X, will try to create shaders later")
                    CuraApplication.getInstance().callLater(lambda e = event: self.event(e))
                    return

            if not self._xray_pass:
                # Currently the RenderPass constructor requires a size > 0
                # This should be fixed in RenderPass's constructor.
                self._xray_pass = XRayPass.XRayPass(1, 1)

            self.getRenderer().addRenderPass(self._xray_pass)

            if not self._xray_composite_shader:
                self._xray_composite_shader = OpenGL.getInstance().createShaderProgram(Resources.getPath(Resources.Shaders, "xray_composite.shader"))
                self._xray_composite_shader.setUniformValue("u_background_color", Color(*theme.getColor("viewport_background").getRgb()))
                self._xray_composite_shader.setUniformValue("u_outline_color", Color(*theme.getColor("model_selection_outline").getRgb()))

            if not self._composite_pass:
                self._composite_pass = self.getRenderer().getRenderPass("composite")

            self._old_layer_bindings = self._composite_pass.getLayerBindings()
            self._composite_pass.setLayerBindings(["default", "selection", "xray"])
            self._old_composite_shader = self._composite_pass.getCompositeShader()
            self._composite_pass.setCompositeShader(self._xray_composite_shader)

        if event.type == Event.ViewDeactivateEvent:
            self.getRenderer().removeRenderPass(self._xray_pass)
            self._composite_pass.setLayerBindings(self._old_layer_bindings)
            self._composite_pass.setCompositeShader(self._old_composite_shader)
Exemplo n.º 31
0
    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()
Exemplo n.º 32
0
    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()
Exemplo n.º 33
0
    def exportQualityProfile(self, container_list: List[InstanceContainer], file_name: str, file_type: str) -> bool:
        # Parse the fileType to deduce what plugin can save the file format.
        # fileType has the format "<description> (*.<extension>)"
        split = file_type.rfind(" (*.")  # Find where the description ends and the extension starts.
        if split < 0:  # Not found. Invalid format.
            Logger.log("e", "Invalid file format identifier %s", file_type)
            return False
        description = file_type[:split]
        extension = file_type[split + 4:-1]  # Leave out the " (*." and ")".
        if not file_name.endswith("." + extension):  # Auto-fill the extension if the user did not provide any.
            file_name += "." + extension

        # On Windows, QML FileDialog properly asks for overwrite confirm, but not on other platforms, so handle those ourself.
        if not Platform.isWindows():
            if os.path.exists(file_name):
                result = QMessageBox.question(None, catalog.i18nc("@title:window", "File Already Exists"),
                                              catalog.i18nc("@label Don't translate the XML tag <filename>!", "The file <filename>{0}</filename> already exists. Are you sure you want to overwrite it?").format(file_name))
                if result == QMessageBox.No:
                    return False

        profile_writer = self._findProfileWriter(extension, description)
        try:
            if profile_writer is None:
                raise Exception("Unable to find a profile writer")
            success = profile_writer.write(file_name, container_list)
        except Exception as e:
            Logger.log("e", "Failed to export profile to %s: %s", file_name, str(e))
            m = Message(catalog.i18nc("@info:status Don't translate the XML tags <filename> or <message>!", "Failed to export profile to <filename>{0}</filename>: <message>{1}</message>", file_name, str(e)),
                        lifetime = 0,
                        title = catalog.i18nc("@info:title", "Error"))
            m.show()
            return False
        if not success:
            Logger.log("w", "Failed to export profile to %s: Writer plugin reported failure.", file_name)
            m = Message(catalog.i18nc("@info:status Don't translate the XML tag <filename>!", "Failed to export profile to <filename>{0}</filename>: Writer plugin reported failure.", file_name),
                        lifetime = 0,
                        title = catalog.i18nc("@info:title", "Error"))
            m.show()
            return False
        m = Message(catalog.i18nc("@info:status Don't translate the XML tag <filename>!", "Exported profile to <filename>{0}</filename>", file_name),
                    title = catalog.i18nc("@info:title", "Export succeeded"))
        m.show()
        return True
Exemplo n.º 34
0
    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
Exemplo n.º 35
0
    def makeFromCurrent(self) -> None:
        cura_release = self._application.getVersion()
        version_data_dir = Resources.getDataStoragePath()

        Logger.log("d", "Creating backup for Cura %s, using folder %s", cura_release, version_data_dir)

        # Ensure all current settings are saved.
        self._application.saveSettings()

        # We copy the preferences file to the user data directory in Linux as it's in a different location there.
        # When restoring a backup on Linux, we move it back.
        if Platform.isLinux(): #TODO: This should check for the config directory not being the same as the data directory, rather than hard-coding that to Linux systems.
            preferences_file_name = self._application.getApplicationName()
            preferences_file = Resources.getPath(Resources.Preferences, "{}.cfg".format(preferences_file_name))
            backup_preferences_file = os.path.join(version_data_dir, "{}.cfg".format(preferences_file_name))
            if os.path.exists(preferences_file) and (not os.path.exists(backup_preferences_file) or not os.path.samefile(preferences_file, backup_preferences_file)):
                Logger.log("d", "Copying preferences file from %s to %s", preferences_file, backup_preferences_file)
                shutil.copyfile(preferences_file, backup_preferences_file)

        # Create an empty buffer and write the archive to it.
        buffer = io.BytesIO()
        archive = self._makeArchive(buffer, version_data_dir)
        if archive is None:
            return
        files = archive.namelist()

        # Count the metadata items. We do this in a rather naive way at the moment.
        machine_count = len([s for s in files if "machine_instances/" in s]) - 1
        material_count = len([s for s in files if "materials/" in s]) - 1
        profile_count = len([s for s in files if "quality_changes/" in s]) - 1
        plugin_count = len([s for s in files if "plugin.json" in s])
        
        # Store the archive and metadata so the BackupManager can fetch them when needed.
        self.zip_file = buffer.getvalue()
        self.meta_data = {
            "cura_release": cura_release,
            "machine_count": str(machine_count),
            "material_count": str(material_count),
            "profile_count": str(profile_count),
            "plugin_count": str(plugin_count)
        }
Exemplo n.º 36
0
    def run(self):
        if not self.url or not self.data:
            Logger.log("e", "URL or DATA for sending slice info was not set!")
            return

        # Submit data
        kwoptions = {"data" : self.data,
                     "timeout" : 5
                     }

        if Platform.isOSX():
            kwoptions["context"] = ssl._create_unverified_context()

        try:
            f = urllib.request.urlopen(self.url, **kwoptions)
            Logger.log("i", "Sent anonymous slice info to %s", self.url)
            f.close()
        except urllib.error.HTTPError as http_exception:
            Logger.log("e", "An HTTP error occurred while trying to send slice information: %s" % http_exception)
        except Exception as e: # We don't want any exception to cause problems
            Logger.log("e", "An exception occurred while trying to send slice information: %s" % e)
Exemplo n.º 37
0
    def _sendCrashReport(self):
        # Before sending data, the user comments are stored
        self.data["user_info"] = self.user_description_text_area.toPlainText()

        # Convert data to bytes
        binary_data = json.dumps(self.data).encode("utf-8")

        # Submit data
        kwoptions = {"data": binary_data, "timeout": 5}

        if Platform.isOSX():
            kwoptions["context"] = ssl._create_unverified_context()

        Logger.log("i", "Sending crash report info to [%s]...", self.crash_url)
        if not self.has_started:
            print("Sending crash report info to [%s]...\n" % self.crash_url)

        try:
            f = urllib.request.urlopen(self.crash_url, **kwoptions)
            Logger.log("i", "Sent crash report info.")
            if not self.has_started:
                print("Sent crash report info.\n")
            f.close()
        except urllib.error.HTTPError as e:
            Logger.logException(
                "e",
                "An HTTP error occurred while trying to send crash report")
            if not self.has_started:
                print(
                    "An HTTP error occurred while trying to send crash report: %s"
                    % e)
        except Exception as e:  # We don't want any exception to cause problems
            Logger.logException(
                "e", "An exception occurred while trying to send crash report")
            if not self.has_started:
                print(
                    "An exception occurred while trying to send crash report: %s"
                    % e)

        os._exit(1)
Exemplo n.º 38
0
    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
Exemplo n.º 39
0
    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)
Exemplo n.º 40
0
    def getDefaultFirmwareName(self) -> str:
        machine_has_heated_bed = self.getProperty("machine_heated_bed", "value")

        baudrate = 250000
        if Platform.isLinux():
            # Linux prefers a baudrate of 115200 here because older versions of
            # pySerial did not support a baudrate of 250000
            baudrate = 115200

        # If a firmware file is available, it should be specified in the definition for the printer
        hex_file = self.getMetaDataEntry("firmware_file", None)
        if machine_has_heated_bed:
            hex_file = self.getMetaDataEntry("firmware_hbk_file", hex_file)

        if not hex_file:
            Logger.log("w", "There is no firmware for machine %s.", self.getBottom().id)
            return ""

        try:
            return Resources.getPath(cura.CuraApplication.CuraApplication.ResourceTypes.Firmware, hex_file.format(baudrate=baudrate))
        except FileNotFoundError:
            Logger.log("w", "Firmware file %s not found.", hex_file)
            return ""
Exemplo n.º 41
0
def getMetaData() -> Dict:
    # Workaround for osx not supporting double file extensions correctly.
    if Platform.isOSX():
        workspace_extension = "3mf"
    else:
        workspace_extension = "curaproject.3mf"

    metaData = {}
    if "3MFReader.ThreeMFReader" in sys.modules:
        metaData["mesh_reader"] = [
            {
                "extension": "3mf",
                "description": catalog.i18nc("@item:inlistbox", "3MF File")
            }
        ]
        metaData["workspace_reader"] = [
            {
                "extension": workspace_extension,
                "description": catalog.i18nc("@item:inlistbox", "3MF File")
            }
        ]
    
    return metaData
Exemplo n.º 42
0
    def getDefaultFirmwareName(self) -> str:
        machine_has_heated_bed = self.getProperty("machine_heated_bed", "value")
        machine_has_heated_chamber=self.getProperty("machine_heated_chamber", "value")
        baudrate = 250000
        if Platform.isLinux():
            # Linux prefers a baudrate of 115200 here because older versions of
            # pySerial did not support a baudrate of 250000
            baudrate = 115200

        # If a firmware file is available, it should be specified in the definition for the printer
        hex_file = self.getMetaDataEntry("firmware_file", None)
        if machine_has_heated_bed:
            hex_file = self.getMetaDataEntry("firmware_hbk_file", hex_file)

        if not hex_file:
            Logger.log("w", "There is no firmware for machine %s.", self.getBottom().id)
            return ""

        try:
            return Resources.getPath(cura.CuraApplication.CuraApplication.ResourceTypes.Firmware, hex_file.format(baudrate=baudrate))
        except FileNotFoundError:
            Logger.log("w", "Firmware file %s not found.", hex_file)
            return ""
Exemplo n.º 43
0
def getMetaData() -> Dict:
    # Workarround for osx not supporting double file extensions correctly.
    if Platform.isOSX():
        workspace_extension = "3mf"
    else:
        workspace_extension = "curaproject.3mf"

    metaData = {}
    if "3MFReader.ThreeMFReader" in sys.modules:
        metaData["mesh_reader"] = [
            {
                "extension": "3mf",
                "description": catalog.i18nc("@item:inlistbox", "3MF File")
            }
        ]
        metaData["workspace_reader"] = [
            {
                "extension": workspace_extension,
                "description": catalog.i18nc("@item:inlistbox", "3MF File")
            }
        ]
    
    return metaData
Exemplo n.º 44
0
def getMetaData() -> Dict:
    # Workarround for osx not supporting double file extensions correclty.
    if Platform.isOSX():
        workspace_extension = "3mf"
    else:
        workspace_extension = "curaproject.3mf"

    metaData = {
        "plugin": {
            "name":
            catalog.i18nc("@label", "3MF Reader"),
            "author":
            "Ultimaker",
            "version":
            "1.0",
            "description":
            catalog.i18nc("@info:whatsthis",
                          "Provides support for reading 3MF files."),
            "api":
            3
        }
    }
    if "3MFReader.ThreeMFReader" in sys.modules:
        metaData["mesh_reader"] = [{
            "extension":
            "3mf",
            "description":
            catalog.i18nc("@item:inlistbox", "3MF File")
        }]
        metaData["workspace_reader"] = [{
            "extension":
            workspace_extension,
            "description":
            catalog.i18nc("@item:inlistbox", "3MF File")
        }]

    return metaData
Exemplo n.º 45
0
def getMetaData():
    # Workarround for osx not supporting double file extensions correctly.
    if Platform.isOSX():
        workspace_extension = "3mf"
    else:
        workspace_extension = "curaproject.3mf"

    metaData = {}

    if "3MFWriter.ThreeMFWriter" in sys.modules:
        metaData["mesh_writer"] = {
            "output": [{
                "extension":
                "3mf",
                "description":
                i18n_catalog.i18nc("@item:inlistbox", "3MF file"),
                "mime_type":
                "application/vnd.ms-package.3dmanufacturing-3dmodel+xml",
                "mode":
                ThreeMFWriter.ThreeMFWriter.OutputMode.BinaryMode
            }]
        }
        metaData["workspace_writer"] = {
            "output": [{
                "extension":
                workspace_extension,
                "description":
                i18n_catalog.i18nc("@item:inlistbox", "Cura Project 3MF file"),
                "mime_type":
                "application/x-curaproject+xml",
                "mode":
                ThreeMFWorkspaceWriter.ThreeMFWorkspaceWriter.OutputMode.
                BinaryMode
            }]
        }

    return metaData
Exemplo n.º 46
0
    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)
Exemplo n.º 47
0
    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"
Exemplo n.º 48
0
    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"
Exemplo n.º 49
0
    def detectBestOpenGLVersion(
        cls, force_compatability: bool
    ) -> Tuple[Optional[int], Optional[int], Optional[int]]:
        """Return "best" OpenGL to use, 4.1 core or 2.0.

        result is <major_version>, <minor_version>, <profile>
        The version depends on what versions are supported in Qt (4.1 and 2.0) and what
        the GPU supports. If creating a context fails at all, (None, None, None) is returned
        Note that PyQt only supports 4.1, 2.1 and 2.0. Cura omits support for 2.1, so the
        only returned options are 4.1 and 2.0.
        """
        cls.detect_ogl_context = None
        if not force_compatability:
            Logger.log("d", "Trying OpenGL context 4.1...")
            cls.detect_ogl_context = cls.setContext(4, 1, core=True)
        if cls.detect_ogl_context is not None:
            fmt = cls.detect_ogl_context.format()
            profile = fmt.profile()

            # First test: we hope for this
            if ((fmt.majorVersion() == 4 and fmt.minorVersion() >= 1) or
                (fmt.majorVersion() >
                 4)) and profile == QSurfaceFormat.CoreProfile:
                Logger.log(
                    "d", "Yay, we got at least OpenGL 4.1 core: %s",
                    cls.versionAsText(fmt.majorVersion(), fmt.minorVersion(),
                                      profile))

                # https://riverbankcomputing.com/pipermail/pyqt/2017-January/038640.html
                # PyQt currently only implements 2.0, 2.1 and 4.1Core
                # If eg 4.5Core would be detected and used here, PyQt would not be able to handle it.
                major_version = 4
                minor_version = 1

                # CURA-6092: Check if we're not using software backed 4.1 context; A software 4.1 context
                # is much slower than a hardware backed 2.0 context
                # Check for OS, Since this only seems to happen on specific versions of Mac OSX and
                # the workaround (which involves the deletion of an OpenGL context) is a problem for some Intel drivers.
                if not Platform.isOSX():
                    return major_version, minor_version, QSurfaceFormat.CoreProfile

                gl_window = QWindow()
                gl_window.setSurfaceType(QWindow.OpenGLSurface)
                gl_window.showMinimized()

                cls.detect_ogl_context.makeCurrent(gl_window)

                gl_profile = QOpenGLVersionProfile()
                gl_profile.setVersion(major_version, minor_version)
                gl_profile.setProfile(profile)

                gl = cls.detect_ogl_context.versionFunctions(
                    gl_profile
                )  # type: Any #It's actually a protected class in PyQt that depends on the requested profile and the implementation of your graphics card.

                gpu_type = "Unknown"  # type: str

                result = None
                if gl:
                    result = gl.initializeOpenGLFunctions()

                if not result:
                    Logger.log("e",
                               "Could not initialize OpenGL to get gpu type")
                else:
                    # WORKAROUND: Cura/#1117 Cura-packaging/12
                    # Some Intel GPU chipsets return a string, which is not undecodable via PyQt5.
                    # This workaround makes the code fall back to a "Unknown" renderer in these cases.
                    try:
                        gpu_type = gl.glGetString(gl.GL_RENDERER)
                    except UnicodeDecodeError:
                        Logger.log(
                            "e",
                            "DecodeError while getting GL_RENDERER via glGetString!"
                        )

                Logger.log("d",
                           "OpenGL renderer type for this OpenGL version: %s",
                           gpu_type)
                if "software" in gpu_type.lower():
                    Logger.log(
                        "w",
                        "Unfortunately OpenGL 4.1 uses software rendering")
                else:
                    return major_version, minor_version, QSurfaceFormat.CoreProfile
        else:
            Logger.log("d", "Failed to create OpenGL context 4.1.")

        # Fallback: check min spec
        Logger.log("d", "Trying OpenGL context 2.0...")
        cls.detect_ogl_context = cls.setContext(
            2, 0, profile=QSurfaceFormat.NoProfile)
        if cls.detect_ogl_context is not None:
            fmt = cls.detect_ogl_context.format()
            profile = fmt.profile()

            if fmt.majorVersion() >= 2 and fmt.minorVersion() >= 0:
                Logger.log(
                    "d", "We got at least OpenGL context 2.0: %s",
                    cls.versionAsText(fmt.majorVersion(), fmt.minorVersion(),
                                      profile))
                return 2, 0, QSurfaceFormat.NoProfile
            else:
                Logger.log(
                    "d", "Current OpenGL context is too low: %s" %
                    cls.versionAsText(fmt.majorVersion(), fmt.minorVersion(),
                                      profile))
                return None, None, None
        else:
            Logger.log("d", "Failed to create OpenGL context 2.0.")
            return None, None, None
Exemplo n.º 50
0
    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()
Exemplo n.º 51
0
    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
Exemplo n.º 52
0
 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)
Exemplo n.º 53
0
    def restore(self) -> bool:
        """Restore this back-up.

        :return: Whether we had success or not.
        """

        if not self.zip_file or not self.meta_data or not self.meta_data.get(
                "cura_release", None):
            # We can restore without the minimum required information.
            Logger.log(
                "w",
                "Tried to restore a Cura backup without having proper data or meta data."
            )
            self._showMessage(
                self.catalog.i18nc(
                    "@info:backup_failed",
                    "Tried to restore a Cura backup without having proper data or meta data."
                ))
            return False

        current_version = self._application.getVersion()
        version_to_restore = self.meta_data.get("cura_release", "master")

        if current_version < version_to_restore:
            # Cannot restore version newer than current because settings might have changed.
            Logger.log(
                "d",
                "Tried to restore a Cura backup of version {version_to_restore} with cura version {current_version}"
                .format(version_to_restore=version_to_restore,
                        current_version=current_version))
            self._showMessage(
                self.catalog.i18nc(
                    "@info:backup_failed",
                    "Tried to restore a Cura backup that is higher than the current version."
                ))
            return False

        # Get the current secrets and store since the back-up doesn't contain those
        secrets = self._obfuscate()

        version_data_dir = Resources.getDataStoragePath()
        try:
            archive = ZipFile(io.BytesIO(self.zip_file), "r")
        except LookupError as e:
            Logger.log(
                "d",
                f"The following error occurred while trying to restore a Cura backup: {str(e)}"
            )
            self._showMessage(
                self.catalog.i18nc(
                    "@info:backup_failed",
                    "The following error occurred while trying to restore a Cura backup:"
                ) + str(e))
            return False
        extracted = self._extractArchive(archive, version_data_dir)

        # Under Linux, preferences are stored elsewhere, so we copy the file to there.
        if Platform.isLinux():
            preferences_file_name = self._application.getApplicationName()
            preferences_file = Resources.getPath(
                Resources.Preferences, "{}.cfg".format(preferences_file_name))
            backup_preferences_file = os.path.join(
                version_data_dir, "{}.cfg".format(preferences_file_name))
            Logger.log("d", "Moving preferences file from %s to %s",
                       backup_preferences_file, preferences_file)
            shutil.move(backup_preferences_file, preferences_file)

        # Restore the obfuscated settings
        self._illuminate(**secrets)

        return extracted
Exemplo n.º 54
0
    def _onWriteStarted(self, output_device):
        try:
            if not Preferences.getInstance().getValue("info/send_slice_info"):
                Logger.log("d", "'info/send_slice_info' is turned off.")
                return  # Do nothing, user does not want to send data

            global_container_stack = Application.getInstance(
            ).getGlobalContainerStack()

            # Get total material used (in mm^3)
            print_information = Application.getInstance().getPrintInformation()
            material_radius = 0.5 * global_container_stack.getProperty(
                "material_diameter", "value")

            # TODO: Send material per extruder instead of mashing it on a pile
            material_used = math.pi * material_radius * material_radius * sum(
                print_information.materialLengths
            )  #Volume of all materials used

            # Get model information (bounding boxes, hashes and transformation matrix)
            models_info = []
            for node in DepthFirstIterator(Application.getInstance(
            ).getController().getScene().getRoot()):
                if type(node) is SceneNode and node.getMeshData(
                ) and node.getMeshData().getVertices() is not None:
                    if not getattr(node, "_outside_buildarea", False):
                        model_info = {}
                        model_info["hash"] = node.getMeshData().getHash()
                        model_info["bounding_box"] = {}
                        model_info["bounding_box"]["minimum"] = {}
                        model_info["bounding_box"]["minimum"][
                            "x"] = node.getBoundingBox().minimum.x
                        model_info["bounding_box"]["minimum"][
                            "y"] = node.getBoundingBox().minimum.y
                        model_info["bounding_box"]["minimum"][
                            "z"] = node.getBoundingBox().minimum.z

                        model_info["bounding_box"]["maximum"] = {}
                        model_info["bounding_box"]["maximum"][
                            "x"] = node.getBoundingBox().maximum.x
                        model_info["bounding_box"]["maximum"][
                            "y"] = node.getBoundingBox().maximum.y
                        model_info["bounding_box"]["maximum"][
                            "z"] = node.getBoundingBox().maximum.z
                        model_info["transformation"] = str(
                            node.getWorldTransformation().getData())

                        models_info.append(model_info)

            # Bundle the collected data
            submitted_data = {
                "processor":
                platform.processor(),
                "machine":
                platform.machine(),
                "platform":
                platform.platform(),
                "settings":
                global_container_stack.serialize(
                ),  # global_container with references on used containers
                "version":
                Application.getInstance().getVersion(),
                "modelhash":
                "None",
                "printtime":
                print_information.currentPrintTime.getDisplayString(
                    DurationFormat.Format.ISO8601),
                "filament":
                material_used,
                "language":
                Preferences.getInstance().getValue("general/language"),
            }
            for container in global_container_stack.getContainers():
                container_id = container.getId()
                try:
                    container_serialized = container.serialize()
                except NotImplementedError:
                    Logger.log("w", "Container %s could not be serialized!",
                               container_id)
                    continue

                if container_serialized:
                    submitted_data["settings_%s" % (
                        container_id
                    )] = container_serialized  # This can be anything, eg. INI, JSON, etc.
                else:
                    Logger.log("i", "No data found in %s to be serialized!",
                               container_id)

            # Convert data to bytes
            submitted_data = urllib.parse.urlencode(submitted_data)
            binary_data = submitted_data.encode("utf-8")

            # Submit data
            kwoptions = {"data": binary_data, "timeout": 1}
            if Platform.isOSX():
                kwoptions["context"] = ssl._create_unverified_context()
            try:
                f = urllib.request.urlopen(self.info_url, **kwoptions)
                Logger.log("i", "Sent anonymous slice info to %s",
                           self.info_url)
                f.close()
            except Exception as e:
                Logger.logException(
                    "e",
                    "An exception occurred while trying to send slice information"
                )
        except:
            # We really can't afford to have a mistake here, as this would break the sending of g-code to a device
            # (Either saving or directly to a printer). The functionality of the slice data is not *that* important.
            pass
Exemplo n.º 55
0
    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)
Exemplo n.º 56
0
#!/usr/bin/env python3

# Copyright (c) 2015 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
import os
import sys
import platform
import faulthandler

from UM.Platform import Platform

#WORKAROUND: GITHUB-88 GITHUB-385 GITHUB-612
if Platform.isLinux(
):  # Needed for platform.linux_distribution, which is not available on Windows and OSX
    # For Ubuntu: https://bugs.launchpad.net/ubuntu/+source/python-qt4/+bug/941826
    if platform.linux_distribution()[0] in (
            "debian", "Ubuntu", "LinuxMint"
    ):  # TODO: Needs a "if X11_GFX == 'nvidia'" here. The workaround is only needed on Ubuntu+NVidia drivers. Other drivers are not affected, but fine with this fix.
        import ctypes
        from ctypes.util import find_library
        libGL = find_library("GL")
        ctypes.CDLL(libGL, ctypes.RTLD_GLOBAL)

# When frozen, i.e. installer version, don't let PYTHONPATH mess up the search path for DLLs.
if Platform.isWindows() and hasattr(sys, "frozen"):
    try:
        del os.environ["PYTHONPATH"]
    except KeyError:
        pass

#WORKAROUND: GITHUB-704 GITHUB-708
Exemplo n.º 57
0
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
Exemplo n.º 58
0
    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
        }
Exemplo n.º 59
0
    def event(self, event):
        modifiers = QApplication.keyboardModifiers()
        ctrl_is_active = modifiers & Qt.ControlModifier
        shift_is_active = modifiers & Qt.ShiftModifier
        if event.type == Event.KeyPressEvent and ctrl_is_active:
            amount = 10 if shift_is_active else 1
            if event.key == KeyEvent.UpKey:
                self.setLayer(self._current_layer_num + amount)
                return True
            if event.key == KeyEvent.DownKey:
                self.setLayer(self._current_layer_num - amount)
                return True

        if event.type == Event.ViewActivateEvent:
            # FIX: on Max OS X, somehow QOpenGLContext.currentContext() can become None during View switching.
            # This can happen when you do the following steps:
            #   1. Start Cura
            #   2. Load a model
            #   3. Switch to Custom mode
            #   4. Select the model and click on the per-object tool icon
            #   5. Switch view to Layer view or X-Ray
            #   6. Cura will very likely crash
            # It seems to be a timing issue that the currentContext can somehow be empty, but I have no clue why.
            # This fix tries to reschedule the view changing event call on the Qt thread again if the current OpenGL
            # context is None.
            if Platform.isOSX():
                if QOpenGLContext.currentContext() is None:
                    Logger.log("d", "current context of OpenGL is empty on Mac OS X, will try to create shaders later")
                    CuraApplication.getInstance().callLater(lambda e=event: self.event(e))
                    return

            # Make sure the SimulationPass is created
            layer_pass = self.getSimulationPass()
            self.getRenderer().addRenderPass(layer_pass)

            # Make sure the NozzleNode is add to the root
            nozzle = self.getNozzleNode()
            nozzle.setParent(self.getController().getScene().getRoot())
            nozzle.setVisible(False)

            Application.getInstance().globalContainerStackChanged.connect(self._onGlobalStackChanged)
            self._onGlobalStackChanged()

            if not self._simulationview_composite_shader:
                self._simulationview_composite_shader = OpenGL.getInstance().createShaderProgram(os.path.join(PluginRegistry.getInstance().getPluginPath("SimulationView"), "simulationview_composite.shader"))
                theme = Application.getInstance().getTheme()
                self._simulationview_composite_shader.setUniformValue("u_background_color", Color(*theme.getColor("viewport_background").getRgb()))
                self._simulationview_composite_shader.setUniformValue("u_outline_color", Color(*theme.getColor("model_selection_outline").getRgb()))

            if not self._composite_pass:
                self._composite_pass = self.getRenderer().getRenderPass("composite")

            self._old_layer_bindings = self._composite_pass.getLayerBindings()[:] # make a copy so we can restore to it later
            self._composite_pass.getLayerBindings().append("simulationview")
            self._old_composite_shader = self._composite_pass.getCompositeShader()
            self._composite_pass.setCompositeShader(self._simulationview_composite_shader)

        elif event.type == Event.ViewDeactivateEvent:
            self._wireprint_warning_message.hide()
            Application.getInstance().globalContainerStackChanged.disconnect(self._onGlobalStackChanged)
            if self._global_container_stack:
                self._global_container_stack.propertyChanged.disconnect(self._onPropertyChanged)

            self._nozzle_node.setParent(None)
            self.getRenderer().removeRenderPass(self._layer_pass)
            self._composite_pass.setLayerBindings(self._old_layer_bindings)
            self._composite_pass.setCompositeShader(self._old_composite_shader)
Exemplo n.º 60
0
# Cura is released under the terms of the LGPLv3 or higher.
from typing import Type, TYPE_CHECKING, Optional, List

import keyring
from keyring.backend import KeyringBackend
from keyring.errors import NoKeyringError, PasswordSetError

from UM.Logger import Logger

if TYPE_CHECKING:
    from cura.OAuth2.Models import BaseModel

# Need to do some extra workarounds on windows:
import sys
from UM.Platform import Platform
if Platform.isWindows() and hasattr(sys, "frozen"):
    import win32timezone
    from keyring.backends.Windows import WinVaultKeyring
    keyring.set_keyring(WinVaultKeyring())
if Platform.isOSX() and hasattr(sys, "frozen"):
    from keyring.backends.macOS import Keyring
    keyring.set_keyring(Keyring())

# Even if errors happen, we don't want this stored locally:
DONT_EVER_STORE_LOCALLY: List[str] = ["refresh_token"]


class KeyringAttribute:
    """
    Descriptor for attributes that need to be stored in the keyring. With Fallback behaviour to the preference cfg file
    """