From 051dd7a6e94c2dc1af7b1ccd9c04b8407be010e4 Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Mon, 30 Apr 2018 16:47:14 +0200 Subject: [PATCH 01/12] WIP: Make application initialization and start up more clear - Create SingleInstance class to handling single instance stuff. - Instead of calling getInstance() everywhere, initialize each object explicitly in order when application starts and getInstance()s do not create instances any more and they merely return the created instances. - Only set initial values in construtor functions __init__(). Move the initialization of context-aware (i.e. things that depend on other things) to separate functions. - Split application creation and initialziation into several steps and them should be called explicitly in the correct order. --- cura/BuildVolume.py | 52 +- cura/CuraActions.py | 1 - cura/CuraApplication.py | 527 ++++++++---------- cura/MachineActionManager.py | 22 +- cura/PrintInformation.py | 30 +- cura/Scene/ConvexHullNode.py | 2 +- cura/Settings/ContainerManager.py | 72 ++- cura/Settings/CuraStackBuilder.py | 19 +- cura/Settings/ExtruderManager.py | 33 +- cura/SingleInstance.py | 103 ++++ cura_app.py | 76 ++- .../CuraEngineBackend/CuraEngineBackend.py | 2 +- .../USBPrinterOutputDeviceManager.py | 51 +- plugins/USBPrinting/__init__.py | 2 +- 14 files changed, 521 insertions(+), 471 deletions(-) create mode 100644 cura/SingleInstance.py diff --git a/cura/BuildVolume.py b/cura/BuildVolume.py index 98e087707a..f249c3513d 100755 --- a/cura/BuildVolume.py +++ b/cura/BuildVolume.py @@ -1,14 +1,16 @@ # Copyright (c) 2018 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. +import math +from typing import List, Optional + +import numpy + +from PyQt5.QtCore import QTimer -from cura.Scene.CuraSceneNode import CuraSceneNode -from cura.Settings.ExtruderManager import ExtruderManager -from UM.Settings.ContainerRegistry import ContainerRegistry from UM.i18n import i18nCatalog from UM.Scene.Platform import Platform from UM.Scene.Iterator.BreadthFirstIterator import BreadthFirstIterator from UM.Scene.SceneNode import SceneNode -from UM.Application import Application from UM.Resources import Resources from UM.Mesh.MeshBuilder import MeshBuilder from UM.Math.Vector import Vector @@ -18,16 +20,14 @@ from UM.Math.AxisAlignedBox import AxisAlignedBox from UM.Math.Polygon import Polygon from UM.Message import Message from UM.Signal import Signal -from PyQt5.QtCore import QTimer from UM.View.RenderBatch import RenderBatch from UM.View.GL.OpenGL import OpenGL + +from cura.Scene.CuraSceneNode import CuraSceneNode +from cura.Settings.ExtruderManager import ExtruderManager + catalog = i18nCatalog("cura") -import numpy -import math - -from typing import List, Optional - # Setting for clearance around the prime PRIME_CLEARANCE = 6.5 @@ -36,8 +36,10 @@ PRIME_CLEARANCE = 6.5 class BuildVolume(SceneNode): raftThicknessChanged = Signal() - def __init__(self, parent = None): + def __init__(self, application, parent = None): super().__init__(parent) + self._application = application + self._machine_manager = self._application.getMachineManager() self._volume_outline_color = None self._x_axis_color = None @@ -80,14 +82,14 @@ class BuildVolume(SceneNode): " with printed models."), title = catalog.i18nc("@info:title", "Build Volume")) self._global_container_stack = None - Application.getInstance().globalContainerStackChanged.connect(self._onStackChanged) + self._application.globalContainerStackChanged.connect(self._onStackChanged) self._onStackChanged() self._engine_ready = False - Application.getInstance().engineCreatedSignal.connect(self._onEngineCreated) + self._application.engineCreatedSignal.connect(self._onEngineCreated) self._has_errors = False - Application.getInstance().getController().getScene().sceneChanged.connect(self._onSceneChanged) + self._application.getController().getScene().sceneChanged.connect(self._onSceneChanged) #Objects loaded at the moment. We are connected to the property changed events of these objects. self._scene_objects = set() @@ -105,14 +107,14 @@ class BuildVolume(SceneNode): # Must be after setting _build_volume_message, apparently that is used in getMachineManager. # activeQualityChanged is always emitted after setActiveVariant, setActiveMaterial and setActiveQuality. # Therefore this works. - Application.getInstance().getMachineManager().activeQualityChanged.connect(self._onStackChanged) + self._machine_manager.activeQualityChanged.connect(self._onStackChanged) # This should also ways work, and it is semantically more correct, # but it does not update the disallowed areas after material change - Application.getInstance().getMachineManager().activeStackChanged.connect(self._onStackChanged) + self._machine_manager.activeStackChanged.connect(self._onStackChanged) # Enable and disable extruder - Application.getInstance().getMachineManager().extruderChanged.connect(self.updateNodeBoundaryCheck) + self._machine_manager.extruderChanged.connect(self.updateNodeBoundaryCheck) # list of settings which were updated self._changed_settings_since_last_rebuild = [] @@ -122,7 +124,7 @@ class BuildVolume(SceneNode): self._scene_change_timer.start() def _onSceneChangeTimerFinished(self): - root = Application.getInstance().getController().getScene().getRoot() + root = self._application.getController().getScene().getRoot() new_scene_objects = set(node for node in BreadthFirstIterator(root) if node.callDecoration("isSliceable")) if new_scene_objects != self._scene_objects: for node in new_scene_objects - self._scene_objects: #Nodes that were added to the scene. @@ -181,7 +183,7 @@ class BuildVolume(SceneNode): if not self._shader: self._shader = OpenGL.getInstance().createShaderProgram(Resources.getPath(Resources.Shaders, "default.shader")) self._grid_shader = OpenGL.getInstance().createShaderProgram(Resources.getPath(Resources.Shaders, "grid.shader")) - theme = Application.getInstance().getTheme() + theme = self._application.getTheme() self._grid_shader.setUniformValue("u_plateColor", Color(*theme.getColor("buildplate").getRgb())) self._grid_shader.setUniformValue("u_gridColor0", Color(*theme.getColor("buildplate_grid").getRgb())) self._grid_shader.setUniformValue("u_gridColor1", Color(*theme.getColor("buildplate_grid_minor").getRgb())) @@ -201,7 +203,7 @@ class BuildVolume(SceneNode): ## For every sliceable node, update node._outside_buildarea # def updateNodeBoundaryCheck(self): - root = Application.getInstance().getController().getScene().getRoot() + root = self._application.getController().getScene().getRoot() nodes = list(BreadthFirstIterator(root)) group_nodes = [] @@ -289,11 +291,11 @@ class BuildVolume(SceneNode): if not self._width or not self._height or not self._depth: return - if not Application.getInstance()._engine: + if not self._application._qml_engine: return if not self._volume_outline_color: - theme = Application.getInstance().getTheme() + theme = self._application.getTheme() self._volume_outline_color = Color(*theme.getColor("volume_outline").getRgb()) self._x_axis_color = Color(*theme.getColor("x_axis").getRgb()) self._y_axis_color = Color(*theme.getColor("y_axis").getRgb()) @@ -465,7 +467,7 @@ class BuildVolume(SceneNode): maximum = Vector(max_w - bed_adhesion_size - 1, max_h - self._raft_thickness - self._extra_z_clearance, max_d - disallowed_area_size + bed_adhesion_size - 1) ) - Application.getInstance().getController().getScene()._maximum_bounds = scale_to_max_bounds + self._application.getController().getScene()._maximum_bounds = scale_to_max_bounds self.updateNodeBoundaryCheck() @@ -518,7 +520,7 @@ class BuildVolume(SceneNode): for extruder in extruders: extruder.propertyChanged.disconnect(self._onSettingPropertyChanged) - self._global_container_stack = Application.getInstance().getGlobalContainerStack() + self._global_container_stack = self._application.getGlobalContainerStack() if self._global_container_stack: self._global_container_stack.propertyChanged.connect(self._onSettingPropertyChanged) @@ -561,7 +563,7 @@ class BuildVolume(SceneNode): if setting_key == "print_sequence": machine_height = self._global_container_stack.getProperty("machine_height", "value") - if Application.getInstance().getGlobalContainerStack().getProperty("print_sequence", "value") == "one_at_a_time" and len(self._scene_objects) > 1: + if self._application.getGlobalContainerStack().getProperty("print_sequence", "value") == "one_at_a_time" and len(self._scene_objects) > 1: self._height = min(self._global_container_stack.getProperty("gantry_height", "value"), machine_height) if self._height < machine_height: self._build_volume_message.show() diff --git a/cura/CuraActions.py b/cura/CuraActions.py index 75338f17b6..019893957f 100644 --- a/cura/CuraActions.py +++ b/cura/CuraActions.py @@ -12,7 +12,6 @@ from UM.Scene.Selection import Selection from UM.Scene.Iterator.BreadthFirstIterator import BreadthFirstIterator from UM.Operations.GroupedOperation import GroupedOperation from UM.Operations.RemoveSceneNodeOperation import RemoveSceneNodeOperation -from UM.Operations.SetTransformOperation import SetTransformOperation from UM.Operations.TranslateOperation import TranslateOperation from cura.Operations.SetParentOperation import SetParentOperation diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index d490b1cfbf..47b2dd25bf 100755 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -1,9 +1,19 @@ # Copyright (c) 2018 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. -from PyQt5.QtCore import QObject, QTimer +import copy +import json +import os +import sys +import time + +import numpy + +from PyQt5.QtCore import QObject, QTimer, QUrl, pyqtSignal, pyqtProperty, QEvent, Q_ENUMS from PyQt5.QtNetwork import QLocalServer -from PyQt5.QtNetwork import QLocalSocket +from PyQt5.QtGui import QColor, QIcon +from PyQt5.QtWidgets import QMessageBox +from PyQt5.QtQml import qmlRegisterUncreatableType, qmlRegisterSingletonType, qmlRegisterType from UM.Qt.QtApplication import QtApplication from UM.Scene.SceneNode import SceneNode @@ -74,6 +84,7 @@ from cura.Settings.SimpleModeSettingsManager import SimpleModeSettingsManager from cura.Machines.VariantManager import VariantManager +from .SingleInstance import SingleInstance from . import PlatformPhysics from . import BuildVolume from . import CameraAnimation @@ -93,22 +104,10 @@ from cura.Settings.ContainerManager import ContainerManager from cura.ObjectsModel import ObjectsModel -from PyQt5.QtCore import QUrl, pyqtSignal, pyqtProperty, QEvent, Q_ENUMS from UM.FlameProfiler import pyqtSlot -from PyQt5.QtGui import QColor, QIcon -from PyQt5.QtWidgets import QMessageBox -from PyQt5.QtQml import qmlRegisterUncreatableType, qmlRegisterSingletonType, qmlRegisterType - -import sys -import numpy -import copy -import os -import argparse -import json -import time -numpy.seterr(all="ignore") +numpy.seterr(all = "ignore") MYPY = False if not MYPY: @@ -143,19 +142,164 @@ class CuraApplication(QtApplication): Q_ENUMS(ResourceTypes) - def __init__(self, **kwargs): + def __init__(self, *args, **kwargs): + super().__init__(name = "cura", + version = CuraVersion, + buildtype = CuraBuildType, + is_debug_mode = CuraDebugMode, + tray_icon_name = "cura-icon-32.png", + **kwargs) + + self.default_theme = "cura-light" + self._boot_loading_time = time.time() + + self._currently_loading_files = [] + self._non_sliceable_extensions = [] + + # Variables set from CLI + self._files_to_open = [] + self._use_single_instance = False + self._trigger_early_crash = False # For debug only + + self._single_instance = None + + self._cura_package_manager = None + + self._machine_action_manager = None + + self.empty_container = None + self.empty_definition_changes_container = None + self.empty_variant_container = None + self.empty_material_container = None + self.empty_quality_container = None + self.empty_quality_changes_container = None + + self._variant_manager = None + self._material_manager = None + self._quality_manager = None + self._machine_manager = None + self._extruder_manager = None + self._container_manager = None + + self._object_manager = None + self._build_plate_model = None + self._multi_build_plate_model = None + self._setting_visibility_presets_model = None + self._setting_inheritance_manager = None + self._simple_mode_settings_manager = None + self._cura_scene_controller = None + self._machine_error_checker = None + + self._quality_profile_drop_down_menu_model = None + self._custom_quality_profile_drop_down_menu_model = None + + self._physics = None + self._volume = None + self._output_devices = {} + self._print_information = None + self._previous_active_tool = None + self._platform_activity = False + self._scene_bounding_box = AxisAlignedBox.Null + + self._job_name = None + self._center_after_select = False + self._camera_animation = None + self._cura_actions = None + self.started = False + + self._message_box_callback = None + self._message_box_callback_arguments = [] + self._preferred_mimetype = "" + self._i18n_catalog = None + + self._currently_loading_files = [] + self._non_sliceable_extensions = [] + self._additional_components = {} # Components to add to certain areas in the interface + + self._open_file_queue = [] # A list of files to open (after the application has started) + + self._update_platform_activity_timer = None + + self._need_to_show_user_agreement = True + + from cura.Settings.CuraContainerRegistry import CuraContainerRegistry + self._container_registry_class = CuraContainerRegistry + + # Adds command line options to the command line parser. This should be called after the application is created and + # before the pre-start. + def addCommandLineOptions(self): + super().addCommandLineOptions() + self._cli_parser.add_argument("--help", "-h", + action = "store_true", + default = False, + help = "Show this help message and exit.") + self._cli_parser.add_argument("--single-instance", + dest = "single_instance", + action = "store_true", + default = False) + # >> For debugging + # Trigger an early crash, i.e. a crash that happens before the application enters its event loop. + self._cli_parser.add_argument("--trigger-early-crash", + dest = "trigger_early_crash", + action = "store_true", + default = False, + help = "FOR TESTING ONLY. Trigger an early crash to show the crash dialog.") + self._cli_parser.add_argument("file", nargs = "*", help = "Files to load after starting the application.") + + def parseCliOptions(self): + super().parseCliOptions() + + if self._cli_args.help: + self._cli_parser.print_help() + sys.exit(0) + + self._use_single_instance = self._cli_args.single_instance + self._trigger_early_crash = self._cli_args.trigger_early_crash + for filename in self._cli_args.file: + self._files_to_open.append(os.path.abspath(filename)) + + def initialize(self) -> None: + super().initialize() + + # Initialize the package manager to remove and install scheduled packages. + from cura.CuraPackageManager import CuraPackageManager + self._cura_package_manager = CuraPackageManager(self) + self._cura_package_manager.initialize() + + self.__sendCommandToSingleInstance() + self.__addExpectedResourceDirsAndSearchPaths() + self.__initializeSettingDefinitionsAndFunctions() + self.__addAllResourcesAndContainerResources() + self.__addAllEmptyContainers() + self.__setLatestResouceVersionsForVersionUpgrade() + + self._machine_action_manager = MachineActionManager.MachineActionManager(self) + self._machine_action_manager.initialize() + + def __sendCommandToSingleInstance(self): + self._single_instance = SingleInstance(self, self._files_to_open) + + # If we use single instance, try to connect to the single instance server, send commands, and then exit. + # If we cannot find an existing single instance server, this is the only instance, so just keep going. + if self._use_single_instance: + if self._single_instance.startClient(): + Logger.log("i", "Single instance commands were sent, exiting") + sys.exit(0) + + # Adds expected directory names and search paths for Resources. + def __addExpectedResourceDirsAndSearchPaths(self): # this list of dir names will be used by UM to detect an old cura directory for dir_name in ["extruders", "machine_instances", "materials", "plugins", "quality", "quality_changes", "user", "variants"]: Resources.addExpectedDirNameInData(dir_name) - Resources.addSearchPath(os.path.join(QtApplication.getInstallPrefix(), "share", "cura", "resources")) + Resources.addSearchPath(os.path.join(self._app_install_dir, "share", "cura", "resources")) if not hasattr(sys, "frozen"): Resources.addSearchPath(os.path.join(os.path.abspath(os.path.dirname(__file__)), "..", "resources")) - self._use_gui = True - self._open_file_queue = [] # Files to open when plug-ins are loaded. - + # Adds custom property types, settings types, and extra operators (functions) that need to be registered in + # SettingDefinition and SettingFunction. + def __initializeSettingDefinitionsAndFunctions(self): # Need to do this before ContainerRegistry tries to load the machines SettingDefinition.addSupportedProperty("settable_per_mesh", DefinitionPropertyType.Any, default = True, read_only = True) SettingDefinition.addSupportedProperty("settable_per_extruder", DefinitionPropertyType.Any, default = True, read_only = True) @@ -180,7 +324,8 @@ class CuraApplication(QtApplication): SettingFunction.registerOperator("extruderValue", ExtruderManager.getExtruderValue) SettingFunction.registerOperator("resolveOrValue", ExtruderManager.getResolveOrValue) - ## Add the 4 types of profiles to storage. + # Adds all resources and container related resources. + def __addAllResourcesAndContainerResources(self) -> None: Resources.addStorageType(self.ResourceTypes.QualityInstanceContainer, "quality") Resources.addStorageType(self.ResourceTypes.QualityChangesInstanceContainer, "quality_changes") Resources.addStorageType(self.ResourceTypes.VariantInstanceContainer, "variants") @@ -191,20 +336,64 @@ class CuraApplication(QtApplication): Resources.addStorageType(self.ResourceTypes.DefinitionChangesContainer, "definition_changes") Resources.addStorageType(self.ResourceTypes.SettingVisibilityPreset, "setting_visibility") - ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.QualityInstanceContainer, "quality") - ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.QualityChangesInstanceContainer, "quality_changes") - ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.VariantInstanceContainer, "variant") - ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.MaterialInstanceContainer, "material") - ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.UserInstanceContainer, "user") - ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.ExtruderStack, "extruder_train") - ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.MachineStack, "machine") - ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.DefinitionChangesContainer, "definition_changes") + self._container_registry.addResourceType(self.ResourceTypes.QualityInstanceContainer, "quality") + self._container_registry.addResourceType(self.ResourceTypes.QualityChangesInstanceContainer, "quality_changes") + self._container_registry.addResourceType(self.ResourceTypes.VariantInstanceContainer, "variant") + self._container_registry.addResourceType(self.ResourceTypes.MaterialInstanceContainer, "material") + self._container_registry.addResourceType(self.ResourceTypes.UserInstanceContainer, "user") + self._container_registry.addResourceType(self.ResourceTypes.ExtruderStack, "extruder_train") + self._container_registry.addResourceType(self.ResourceTypes.MachineStack, "machine") + self._container_registry.addResourceType(self.ResourceTypes.DefinitionChangesContainer, "definition_changes") - ## Initialise the version upgrade manager with Cura's storage paths. - # Needs to be here to prevent circular dependencies. - import UM.VersionUpgradeManager + Resources.addType(self.ResourceTypes.QmlFiles, "qml") + Resources.addType(self.ResourceTypes.Firmware, "firmware") - UM.VersionUpgradeManager.VersionUpgradeManager.getInstance().setCurrentVersions( + # Adds all empty containers. + def __addAllEmptyContainers(self) -> None: + # Add empty variant, material and quality containers. + # Since they are empty, they should never be serialized and instead just programmatically created. + # We need them to simplify the switching between materials. + empty_container = self._container_registry.getEmptyInstanceContainer() + self.empty_container = empty_container + + empty_definition_changes_container = copy.deepcopy(empty_container) + empty_definition_changes_container.setMetaDataEntry("id", "empty_definition_changes") + empty_definition_changes_container.addMetaDataEntry("type", "definition_changes") + self._container_registry.addContainer(empty_definition_changes_container) + self.empty_definition_changes_container = empty_definition_changes_container + + empty_variant_container = copy.deepcopy(empty_container) + empty_variant_container.setMetaDataEntry("id", "empty_variant") + empty_variant_container.addMetaDataEntry("type", "variant") + self._container_registry.addContainer(empty_variant_container) + self.empty_variant_container = empty_variant_container + + empty_material_container = copy.deepcopy(empty_container) + empty_material_container.setMetaDataEntry("id", "empty_material") + empty_material_container.addMetaDataEntry("type", "material") + self._container_registry.addContainer(empty_material_container) + self.empty_material_container = empty_material_container + + empty_quality_container = copy.deepcopy(empty_container) + empty_quality_container.setMetaDataEntry("id", "empty_quality") + empty_quality_container.setName("Not Supported") + empty_quality_container.addMetaDataEntry("quality_type", "not_supported") + empty_quality_container.addMetaDataEntry("type", "quality") + empty_quality_container.addMetaDataEntry("supported", False) + self._container_registry.addContainer(empty_quality_container) + self.empty_quality_container = empty_quality_container + + empty_quality_changes_container = copy.deepcopy(empty_container) + empty_quality_changes_container.setMetaDataEntry("id", "empty_quality_changes") + empty_quality_changes_container.addMetaDataEntry("type", "quality_changes") + empty_quality_changes_container.addMetaDataEntry("quality_type", "not_supported") + self._container_registry.addContainer(empty_quality_changes_container) + self.empty_quality_changes_container = empty_quality_changes_container + + # Initializes the version upgrade manager with by providing the paths for each resource type and the latest + # versions. + def __setLatestResouceVersionsForVersionUpgrade(self): + self._version_upgrade_manager.setCurrentVersions( { ("quality_changes", InstanceContainer.Version * 1000000 + self.SettingVersion): (self.ResourceTypes.QualityChangesInstanceContainer, "application/x-uranium-instancecontainer"), ("machine_stack", ContainerStack.Version * 1000000 + self.SettingVersion): (self.ResourceTypes.MachineStack, "application/x-cura-globalstack"), @@ -216,46 +405,9 @@ class CuraApplication(QtApplication): } ) - self._currently_loading_files = [] - self._non_sliceable_extensions = [] - - self._machine_action_manager = MachineActionManager.MachineActionManager() - self._machine_manager = None # This is initialized on demand. - self._extruder_manager = None - self._material_manager = None - self._quality_manager = None - self._object_manager = None - self._build_plate_model = None - self._multi_build_plate_model = None - self._setting_visibility_presets_model = None - self._setting_inheritance_manager = None - self._simple_mode_settings_manager = None - self._cura_scene_controller = None - self._machine_error_checker = None - - self._additional_components = {} # Components to add to certain areas in the interface - - super().__init__(name = "cura", - version = CuraVersion, - buildtype = CuraBuildType, - is_debug_mode = CuraDebugMode, - tray_icon_name = "cura-icon-32.png", - **kwargs) - - # Initialize the package manager to remove and install scheduled packages. - from cura.CuraPackageManager import CuraPackageManager - self._cura_package_manager = CuraPackageManager(self) - self._cura_package_manager.initialize() - - self.initialize() - - # FOR TESTING ONLY - if kwargs["parsed_command_line"].get("trigger_early_crash", False): - assert not "This crash is triggered by the trigger_early_crash command line argument." - - self._variant_manager = None - - self.default_theme = "cura-light" + # Runs preparations that needs to be done before the starting process. + def startSlashWindowPhase(self): + super().startSlashWindowPhase() self.setWindowIcon(QIcon(Resources.getPath(Resources.Images, "cura-icon.png"))) @@ -289,23 +441,6 @@ class CuraApplication(QtApplication): "SelectionTool", "TranslateTool" ]) - self._physics = None - self._volume = None - self._output_devices = {} - self._print_information = None - self._previous_active_tool = None - self._platform_activity = False - self._scene_bounding_box = AxisAlignedBox.Null - - self._job_name = None - self._center_after_select = False - self._camera_animation = None - self._cura_actions = None - self.started = False - - self._message_box_callback = None - self._message_box_callback_arguments = [] - self._preferred_mimetype = "" self._i18n_catalog = i18nCatalog("cura") self._update_platform_activity_timer = QTimer() @@ -318,53 +453,10 @@ class CuraApplication(QtApplication): self.getController().contextMenuRequested.connect(self._onContextMenuRequested) self.getCuraSceneController().activeBuildPlateChanged.connect(self.updatePlatformActivityDelayed) - Resources.addType(self.ResourceTypes.QmlFiles, "qml") - Resources.addType(self.ResourceTypes.Firmware, "firmware") - self.showSplashMessage(self._i18n_catalog.i18nc("@info:progress", "Loading machines...")) - # Add empty variant, material and quality containers. - # Since they are empty, they should never be serialized and instead just programmatically created. - # We need them to simplify the switching between materials. - empty_container = ContainerRegistry.getInstance().getEmptyInstanceContainer() - self.empty_container = empty_container - - empty_definition_changes_container = copy.deepcopy(empty_container) - empty_definition_changes_container.setMetaDataEntry("id", "empty_definition_changes") - empty_definition_changes_container.addMetaDataEntry("type", "definition_changes") - ContainerRegistry.getInstance().addContainer(empty_definition_changes_container) - self.empty_definition_changes_container = empty_definition_changes_container - - empty_variant_container = copy.deepcopy(empty_container) - empty_variant_container.setMetaDataEntry("id", "empty_variant") - empty_variant_container.addMetaDataEntry("type", "variant") - ContainerRegistry.getInstance().addContainer(empty_variant_container) - self.empty_variant_container = empty_variant_container - - empty_material_container = copy.deepcopy(empty_container) - empty_material_container.setMetaDataEntry("id", "empty_material") - empty_material_container.addMetaDataEntry("type", "material") - ContainerRegistry.getInstance().addContainer(empty_material_container) - self.empty_material_container = empty_material_container - - empty_quality_container = copy.deepcopy(empty_container) - empty_quality_container.setMetaDataEntry("id", "empty_quality") - empty_quality_container.setName("Not Supported") - empty_quality_container.addMetaDataEntry("quality_type", "not_supported") - empty_quality_container.addMetaDataEntry("type", "quality") - empty_quality_container.addMetaDataEntry("supported", False) - ContainerRegistry.getInstance().addContainer(empty_quality_container) - self.empty_quality_container = empty_quality_container - - empty_quality_changes_container = copy.deepcopy(empty_container) - empty_quality_changes_container.setMetaDataEntry("id", "empty_quality_changes") - empty_quality_changes_container.addMetaDataEntry("type", "quality_changes") - empty_quality_changes_container.addMetaDataEntry("quality_type", "not_supported") - ContainerRegistry.getInstance().addContainer(empty_quality_changes_container) - self.empty_quality_changes_container = empty_quality_changes_container - - with ContainerRegistry.getInstance().lockFile(): - ContainerRegistry.getInstance().loadAllMetadata() + with self._container_registry.lockFile(): + self._container_registry.loadAllMetadata() # set the setting version for Preferences preferences = Preferences.getInstance() @@ -411,13 +503,10 @@ class CuraApplication(QtApplication): self.getCuraSceneController().setActiveBuildPlate(0) # Initialize - self._quality_profile_drop_down_menu_model = None - self._custom_quality_profile_drop_down_menu_model = None - CuraApplication.Created = True def _onEngineCreated(self): - self._engine.addImageProvider("camera", CameraImageProvider.CameraImageProvider()) + self._qml_engine.addImageProvider("camera", CameraImageProvider.CameraImageProvider()) @pyqtProperty(bool) def needToShowUserAgreement(self): @@ -515,10 +604,6 @@ class CuraApplication(QtApplication): def setDefaultPath(self, key, default_path): Preferences.getInstance().setValue("local_file/%s" % key, QUrl(default_path).toLocalFile()) - @classmethod - def getStaticVersion(cls): - return CuraVersion - ## Handle loading of all plugin types (and the backend explicitly) # \sa PluginRegistry def _loadPlugins(self): @@ -543,127 +628,8 @@ class CuraApplication(QtApplication): self._plugins_loaded = True - @classmethod - def addCommandLineOptions(cls, parser, parsed_command_line = None): - if parsed_command_line is None: - parsed_command_line = {} - super().addCommandLineOptions(parser, parsed_command_line = parsed_command_line) - parser.add_argument("file", nargs="*", help="Files to load after starting the application.") - parser.add_argument("--single-instance", action="store_true", default=False) - - # Set up a local socket server which listener which coordinates single instances Curas and accepts commands. - def _setUpSingleInstanceServer(self): - if self.getCommandLineOption("single_instance", False): - self.__single_instance_server = QLocalServer() - self.__single_instance_server.newConnection.connect(self._singleInstanceServerNewConnection) - self.__single_instance_server.listen("ultimaker-cura") - - def _singleInstanceServerNewConnection(self): - Logger.log("i", "New connection recevied on our single-instance server") - remote_cura_connection = self.__single_instance_server.nextPendingConnection() - - if remote_cura_connection is not None: - def readCommands(): - line = remote_cura_connection.readLine() - while len(line) != 0: # There is also a .canReadLine() - try: - payload = json.loads(str(line, encoding="ASCII").strip()) - command = payload["command"] - - # Command: Remove all models from the build plate. - if command == "clear-all": - self.deleteAll() - - # Command: Load a model file - elif command == "open": - self._openFile(payload["filePath"]) - # WARNING ^ this method is async and we really should wait until - # the file load is complete before processing more commands. - - # Command: Activate the window and bring it to the top. - elif command == "focus": - # Operating systems these days prevent windows from moving around by themselves. - # 'alert' or flashing the icon in the taskbar is the best thing we do now. - self.getMainWindow().alert(0) - - # Command: Close the socket connection. We're done. - elif command == "close-connection": - remote_cura_connection.close() - - else: - Logger.log("w", "Received an unrecognized command " + str(command)) - except json.decoder.JSONDecodeError as ex: - Logger.log("w", "Unable to parse JSON command in _singleInstanceServerNewConnection(): " + repr(ex)) - line = remote_cura_connection.readLine() - - remote_cura_connection.readyRead.connect(readCommands) - - ## Perform any checks before creating the main application. - # - # This should be called directly before creating an instance of CuraApplication. - # \returns \type{bool} True if the whole Cura app should continue running. - @classmethod - def preStartUp(cls, parser = None, parsed_command_line = None): - if parsed_command_line is None: - parsed_command_line = {} - - # Peek the arguments and look for the 'single-instance' flag. - if not parser: - parser = argparse.ArgumentParser(prog = "cura", add_help = False) # pylint: disable=bad-whitespace - CuraApplication.addCommandLineOptions(parser, parsed_command_line = parsed_command_line) - # Important: It is important to keep this line here! - # In Uranium we allow to pass unknown arguments to the final executable or script. - parsed_command_line.update(vars(parser.parse_known_args()[0])) - - if parsed_command_line["single_instance"]: - Logger.log("i", "Checking for the presence of an ready running Cura instance.") - single_instance_socket = QLocalSocket() - Logger.log("d", "preStartUp(): full server name: " + single_instance_socket.fullServerName()) - single_instance_socket.connectToServer("ultimaker-cura") - single_instance_socket.waitForConnected() - if single_instance_socket.state() == QLocalSocket.ConnectedState: - Logger.log("i", "Connection has been made to the single-instance Cura socket.") - - # Protocol is one line of JSON terminated with a carriage return. - # "command" field is required and holds the name of the command to execute. - # Other fields depend on the command. - - payload = {"command": "clear-all"} - single_instance_socket.write(bytes(json.dumps(payload) + "\n", encoding="ASCII")) - - payload = {"command": "focus"} - single_instance_socket.write(bytes(json.dumps(payload) + "\n", encoding="ASCII")) - - if len(parsed_command_line["file"]) != 0: - for filename in parsed_command_line["file"]: - payload = {"command": "open", "filePath": filename} - single_instance_socket.write(bytes(json.dumps(payload) + "\n", encoding="ASCII")) - - payload = {"command": "close-connection"} - single_instance_socket.write(bytes(json.dumps(payload) + "\n", encoding="ASCII")) - - single_instance_socket.flush() - single_instance_socket.waitForDisconnected() - return False - return True - - def preRun(self): - # Last check for unknown commandline arguments - parser = self.getCommandlineParser() - parser.add_argument("--help", "-h", - action='store_true', - default = False, - help = "Show this help message and exit." - ) - parsed_args = vars(parser.parse_args()) # This won't allow unknown arguments - if parsed_args["help"]: - parser.print_help() - sys.exit(0) - def run(self): - self.preRun() - - container_registry = ContainerRegistry.getInstance() + container_registry = self._container_registry Logger.log("i", "Initializing variant manager") self._variant_manager = VariantManager(container_registry) @@ -682,20 +648,25 @@ class CuraApplication(QtApplication): Logger.log("i", "Initializing machine manager") self._machine_manager = MachineManager(self) + Logger.log("i", "Initializing container manager") + self._container_manager = ContainerManager(self) + Logger.log("i", "Initializing machine error checker") self._machine_error_checker = MachineErrorChecker(self) self._machine_error_checker.initialize() - # Check if we should run as single instance or not - self._setUpSingleInstanceServer() + # Check if we should run as single instance or not. If so, set up a local socket server which listener which + # coordinates multiple Cura instances and accepts commands. + if self._use_single_instance: + self.__setUpSingleInstanceServer() # Setup scene and build volume root = self.getController().getScene().getRoot() - self._volume = BuildVolume.BuildVolume(self.getController().getScene().getRoot()) + self._volume = BuildVolume.BuildVolume(self, root) Arrange.build_volume = self._volume # initialize info objects - self._print_information = PrintInformation.PrintInformation() + self._print_information = PrintInformation.PrintInformation(self) self._cura_actions = CuraActions.CuraActions(self) # Initialize setting visibility presets model @@ -704,7 +675,7 @@ class CuraApplication(QtApplication): Preferences.getInstance().setDefault("general/visible_settings", ";".join(default_visibility_profile["settings"])) # Detect in which mode to run and execute that mode - if self.getCommandLineOption("headless", False): + if self._is_headless: self.runWithoutGUI() else: self.runWithGUI() @@ -713,7 +684,6 @@ class CuraApplication(QtApplication): self.initializationFinished.emit() Logger.log("d", "Booting Cura took %s seconds", time.time() - self._boot_loading_time) - # For now use a timer to postpone some things that need to be done after the application and GUI are # initialized, for example opening files because they may show dialogs which can be closed due to incomplete # GUI initialization. @@ -725,8 +695,12 @@ class CuraApplication(QtApplication): self.exec_() + def __setUpSingleInstanceServer(self): + if self._use_single_instance: + self._single_instance.startServer() + def _onPostStart(self): - for file_name in self.getCommandLineOption("file", []): + for file_name in self._files_to_open: self.callLater(self._openFile, file_name) for file_name in self._open_file_queue: # Open all the files that were queued up while plug-ins were loading. self.callLater(self._openFile, file_name) @@ -735,13 +709,10 @@ class CuraApplication(QtApplication): ## Run Cura without GUI elements and interaction (server mode). def runWithoutGUI(self): - self._use_gui = False self.closeSplash() ## Run Cura with GUI (desktop mode). def runWithGUI(self): - self._use_gui = True - self.showSplashMessage(self._i18n_catalog.i18nc("@info:progress", "Setting up scene...")) controller = self.getController() @@ -791,9 +762,6 @@ class CuraApplication(QtApplication): # Hide the splash screen self.closeSplash() - def hasGui(self): - return self._use_gui - @pyqtSlot(result = QObject) def getSettingVisibilityPresetsModel(self, *args) -> SettingVisibilityPresetsModel: return self._setting_visibility_presets_model @@ -808,7 +776,7 @@ class CuraApplication(QtApplication): def getExtruderManager(self, *args): if self._extruder_manager is None: - self._extruder_manager = ExtruderManager.createExtruderManager() + self._extruder_manager = ExtruderManager() return self._extruder_manager @pyqtSlot(result = QObject) @@ -932,7 +900,7 @@ class CuraApplication(QtApplication): qmlRegisterType(QualitySettingsModel, "Cura", 1, 0, "QualitySettingsModel") qmlRegisterType(MachineNameValidator, "Cura", 1, 0, "MachineNameValidator") qmlRegisterType(UserChangesModel, "Cura", 1, 0, "UserChangesModel") - qmlRegisterSingletonType(ContainerManager, "Cura", 1, 0, "ContainerManager", ContainerManager.createContainerManager) + qmlRegisterSingletonType(ContainerManager, "Cura", 1, 0, "ContainerManager", ContainerManager.getInstance) # As of Qt5.7, it is necessary to get rid of any ".." in the path for the singleton to work. actions_url = QUrl.fromLocalFile(os.path.abspath(Resources.getPath(CuraApplication.ResourceTypes.QmlFiles, "Actions.qml"))) @@ -1520,8 +1488,7 @@ class CuraApplication(QtApplication): # see GroupDecorator._onChildrenChanged def _createSplashScreen(self): - run_headless = self.getCommandLineOption("headless", False) - if run_headless: + if self._is_headless: return None return CuraSplashScreen.CuraSplashScreen() diff --git a/cura/MachineActionManager.py b/cura/MachineActionManager.py index 27b08ba8a1..65eb33b54c 100644 --- a/cura/MachineActionManager.py +++ b/cura/MachineActionManager.py @@ -1,13 +1,13 @@ -# Copyright (c) 2016 Ultimaker B.V. +# Copyright (c) 2018 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. -from UM.Logger import Logger -from UM.PluginRegistry import PluginRegistry # So MachineAction can be added as plugin type - -from UM.Settings.ContainerRegistry import ContainerRegistry -from UM.Settings.DefinitionContainer import DefinitionContainer from PyQt5.QtCore import QObject + from UM.FlameProfiler import pyqtSlot +from UM.Logger import Logger +from UM.PluginRegistry import PluginRegistry # So MachineAction can be added as plugin type +from UM.Settings.DefinitionContainer import DefinitionContainer + ## Raised when trying to add an unknown machine action as a required action class UnknownMachineActionError(Exception): @@ -20,23 +20,27 @@ class NotUniqueMachineActionError(Exception): class MachineActionManager(QObject): - def __init__(self, parent = None): + def __init__(self, application, parent = None): super().__init__(parent) + self._application = application self._machine_actions = {} # Dict of all known machine actions self._required_actions = {} # Dict of all required actions by definition ID self._supported_actions = {} # Dict of all supported actions by definition ID self._first_start_actions = {} # Dict of all actions that need to be done when first added by definition ID + def initialize(self): + container_registry = self._application.getContainerRegistry() + # Add machine_action as plugin type PluginRegistry.addType("machine_action", self.addMachineAction) # Ensure that all containers that were registered before creation of this registry are also handled. # This should not have any effect, but it makes it safer if we ever refactor the order of things. - for container in ContainerRegistry.getInstance().findDefinitionContainers(): + for container in container_registry.findDefinitionContainers(): self._onContainerAdded(container) - ContainerRegistry.getInstance().containerAdded.connect(self._onContainerAdded) + container_registry.containerAdded.connect(self._onContainerAdded) def _onContainerAdded(self, container): ## Ensure that the actions are added to this manager diff --git a/cura/PrintInformation.py b/cura/PrintInformation.py index 22f7ea5407..8c897230d5 100644 --- a/cura/PrintInformation.py +++ b/cura/PrintInformation.py @@ -1,24 +1,24 @@ # Copyright (c) 2018 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. -from typing import Dict -import math -import os.path -import unicodedata import json +import math +import os +import unicodedata import re # To create abbreviations for printer names. +from typing import Dict from PyQt5.QtCore import QObject, pyqtSignal, pyqtProperty, pyqtSlot -from UM.Application import Application -from UM.Logger import Logger -from UM.Qt.Duration import Duration -from UM.Preferences import Preferences -from UM.Scene.SceneNode import SceneNode from UM.i18n import i18nCatalog +from UM.Logger import Logger +from UM.Preferences import Preferences +from UM.Qt.Duration import Duration +from UM.Scene.SceneNode import SceneNode catalog = i18nCatalog("cura") + ## A class for processing and calculating minimum, current and maximum print time as well as managing the job name # # This class contains all the logic relating to calculation and slicing for the @@ -47,8 +47,9 @@ class PrintInformation(QObject): ActiveMachineChanged = 3 Other = 4 - def __init__(self, parent = None): + def __init__(self, application, parent = None): super().__init__(parent) + self._application = application self.initializeCuraMessagePrintTimeProperties() @@ -59,10 +60,10 @@ class PrintInformation(QObject): self._pre_sliced = False - self._backend = Application.getInstance().getBackend() + self._backend = self._application.getBackend() if self._backend: self._backend.printDurationMessage.connect(self._onPrintDurationMessage) - Application.getInstance().getController().getScene().sceneChanged.connect(self._onSceneChanged) + self._application.getController().getScene().sceneChanged.connect(self._onSceneChanged) self._base_name = "" self._abbr_machine = "" @@ -71,7 +72,6 @@ class PrintInformation(QObject): self._active_build_plate = 0 self._initVariablesWithBuildPlate(self._active_build_plate) - self._application = Application.getInstance() self._multi_build_plate_model = self._application.getMultiBuildPlateModel() self._application.globalContainerStackChanged.connect(self._updateJobName) @@ -199,7 +199,7 @@ class PrintInformation(QObject): self._current_print_time[build_plate_number].setDuration(total_estimated_time) def _calculateInformation(self, build_plate_number): - global_stack = Application.getInstance().getGlobalContainerStack() + global_stack = self._application.getGlobalContainerStack() if global_stack is None: return @@ -358,7 +358,7 @@ class PrintInformation(QObject): ## Created an acronymn-like abbreviated machine name from the currently active machine name # Called each time the global stack is switched def _setAbbreviatedMachineName(self): - global_container_stack = Application.getInstance().getGlobalContainerStack() + global_container_stack = self._application.getGlobalContainerStack() if not global_container_stack: self._abbr_machine = "" return diff --git a/cura/Scene/ConvexHullNode.py b/cura/Scene/ConvexHullNode.py index 1131958627..4c79c7d5dc 100644 --- a/cura/Scene/ConvexHullNode.py +++ b/cura/Scene/ConvexHullNode.py @@ -24,7 +24,7 @@ class ConvexHullNode(SceneNode): self._original_parent = parent # Color of the drawn convex hull - if Application.getInstance().hasGui(): + if not Application.getInstance().getIsHeadLess(): self._color = Color(*Application.getInstance().getTheme().getColor("convex_hull").getRgb()) else: self._color = Color(0, 0, 0) diff --git a/cura/Settings/ContainerManager.py b/cura/Settings/ContainerManager.py index 0dc26207df..ea2821ce25 100644 --- a/cura/Settings/ContainerManager.py +++ b/cura/Settings/ContainerManager.py @@ -1,32 +1,25 @@ # Copyright (c) 2018 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. -import os.path +import os import urllib.parse import uuid from typing import Dict, Union from PyQt5.QtCore import QObject, QUrl, QVariant -from UM.FlameProfiler import pyqtSlot from PyQt5.QtWidgets import QMessageBox -from UM.PluginRegistry import PluginRegistry -from UM.SaveFile import SaveFile -from UM.Platform import Platform -from UM.MimeTypeDatabase import MimeTypeDatabase - +from UM.i18n import i18nCatalog +from UM.FlameProfiler import pyqtSlot from UM.Logger import Logger -from UM.Application import Application +from UM.MimeTypeDatabase import MimeTypeDatabase, MimeTypeNotFoundError +from UM.Platform import Platform +from UM.SaveFile import SaveFile +from UM.Settings.ContainerFormatError import ContainerFormatError from UM.Settings.ContainerStack import ContainerStack from UM.Settings.DefinitionContainer import DefinitionContainer from UM.Settings.InstanceContainer import InstanceContainer -from UM.MimeTypeDatabase import MimeTypeNotFoundError -from UM.Settings.ContainerFormatError import ContainerFormatError -from UM.Settings.ContainerRegistry import ContainerRegistry -from cura.Settings.ExtruderManager import ExtruderManager -from UM.i18n import i18nCatalog - catalog = i18nCatalog("cura") @@ -36,11 +29,17 @@ catalog = i18nCatalog("cura") # from within QML. We want to be able to trigger things like removing a container # when a certain action happens. This can be done through this class. class ContainerManager(QObject): - def __init__(self, parent = None): - super().__init__(parent) - self._application = Application.getInstance() - self._container_registry = ContainerRegistry.getInstance() + def __init__(self, application): + if ContainerManager.__instance is not None: + raise RuntimeError("Try to create singleton '%s' more than once" % self.__class__.__name__) + ContainerManager.__instance = self + + super().__init__(parent = application) + + self._application = application + self._plugin_registry = self._application.getPluginRegistry() + self._container_registry = self._application.getContainerRegistry() self._machine_manager = self._application.getMachineManager() self._material_manager = self._application.getMaterialManager() self._container_name_filters = {} @@ -129,7 +128,7 @@ class ContainerManager(QObject): container.setProperty(setting_key, property_name, property_value) basefile = container.getMetaDataEntry("base_file", container_id) - for sibbling_container in ContainerRegistry.getInstance().findInstanceContainers(base_file = basefile): + for sibbling_container in self._container_registry.findInstanceContainers(base_file = basefile): if sibbling_container != container: sibbling_container.setProperty(setting_key, property_name, property_value) @@ -307,13 +306,15 @@ class ContainerManager(QObject): # \return \type{bool} True if successful, False if not. @pyqtSlot(result = bool) def updateQualityChanges(self): - global_stack = Application.getInstance().getGlobalContainerStack() + global_stack = self._machine_manager.activeMachine if not global_stack: return False self._machine_manager.blurSettings.emit() - for stack in ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks(): + global_stack = self._machine_manager.activeMachine + extruder_stacks = list(global_stack.extruders.values()) + for stack in [global_stack] + extruder_stacks: # Find the quality_changes container for this stack and merge the contents of the top container into it. quality_changes = stack.qualityChanges if not quality_changes or self._container_registry.isReadOnly(quality_changes.getId()): @@ -334,13 +335,15 @@ class ContainerManager(QObject): send_emits_containers = [] # Go through global and extruder stacks and clear their topmost container (the user settings). - for stack in ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks(): + global_stack = self._machine_manager.activeMachine + extruder_stacks = list(global_stack.extruders.values()) + for stack in [global_stack] + extruder_stacks: container = stack.userChanges container.clear() send_emits_containers.append(container) # user changes are possibly added to make the current setup match the current enabled extruders - Application.getInstance().getMachineManager().correctExtruderSettings() + self._machine_manager.correctExtruderSettings() for container in send_emits_containers: container.sendPostponedEmits() @@ -381,21 +384,6 @@ class ContainerManager(QObject): if container is not None: container.setMetaDataEntry("GUID", new_guid) - ## Get the singleton instance for this class. - @classmethod - def getInstance(cls) -> "ContainerManager": - # Note: Explicit use of class name to prevent issues with inheritance. - if ContainerManager.__instance is None: - ContainerManager.__instance = cls() - return ContainerManager.__instance - - __instance = None # type: "ContainerManager" - - # Factory function, used by QML - @staticmethod - def createContainerManager(engine, js_engine): - return ContainerManager.getInstance() - def _performMerge(self, merge_into, merge, clear_settings = True): if merge == merge_into: return @@ -415,7 +403,7 @@ class ContainerManager(QObject): serialize_type = "" try: - plugin_metadata = PluginRegistry.getInstance().getMetaData(plugin_id) + plugin_metadata = self._plugin_registry.getMetaData(plugin_id) if plugin_metadata: serialize_type = plugin_metadata["settings_container"]["type"] else: @@ -470,3 +458,9 @@ class ContainerManager(QObject): container_list = [n.getContainer() for n in quality_changes_group.getAllNodes() if n.getContainer() is not None] self._container_registry.exportQualityProfile(container_list, path, file_type) + + __instance = None + + @classmethod + def getInstance(cls, *args, **kwargs) -> "ContainerManager": + return cls.__instance diff --git a/cura/Settings/CuraStackBuilder.py b/cura/Settings/CuraStackBuilder.py index 640489adb3..e401cad1ae 100644 --- a/cura/Settings/CuraStackBuilder.py +++ b/cura/Settings/CuraStackBuilder.py @@ -7,7 +7,6 @@ from UM.ConfigurationErrorMessage import ConfigurationErrorMessage from UM.Logger import Logger from UM.Settings.Interfaces import DefinitionContainerInterface from UM.Settings.InstanceContainer import InstanceContainer -from UM.Settings.ContainerRegistry import ContainerRegistry from cura.Machines.VariantManager import VariantType from .GlobalStack import GlobalStack @@ -29,7 +28,7 @@ class CuraStackBuilder: variant_manager = application.getVariantManager() material_manager = application.getMaterialManager() quality_manager = application.getQualityManager() - registry = ContainerRegistry.getInstance() + registry = application.getContainerRegistry() definitions = registry.findDefinitionContainers(id = definition_id) if not definitions: @@ -142,6 +141,7 @@ class CuraStackBuilder: variant_container, material_container, quality_container, global_stack) -> ExtruderStack: from cura.CuraApplication import CuraApplication application = CuraApplication.getInstance() + registry = application.getContainerRegistry() stack = ExtruderStack(new_stack_id, parent = global_stack) stack.setName(extruder_definition.getName()) @@ -162,7 +162,7 @@ class CuraStackBuilder: # Only add the created containers to the registry after we have set all the other # properties. This makes the create operation more transactional, since any problems # setting properties will not result in incomplete containers being added. - ContainerRegistry.getInstance().addContainer(user_container) + registry.addContainer(user_container) return stack @@ -178,6 +178,7 @@ class CuraStackBuilder: variant_container, material_container, quality_container) -> GlobalStack: from cura.CuraApplication import CuraApplication application = CuraApplication.getInstance() + registry = application.getContainerRegistry() stack = GlobalStack(new_stack_id) stack.setDefinition(definition) @@ -193,7 +194,7 @@ class CuraStackBuilder: stack.qualityChanges = application.empty_quality_changes_container stack.userChanges = user_container - ContainerRegistry.getInstance().addContainer(user_container) + registry.addContainer(user_container) return stack @@ -201,8 +202,10 @@ class CuraStackBuilder: def createUserChangesContainer(cls, container_name: str, definition_id: str, stack_id: str, is_global_stack: bool) -> "InstanceContainer": from cura.CuraApplication import CuraApplication + application = CuraApplication.getInstance() + registry = application.getContainerRegistry() - unique_container_name = ContainerRegistry.getInstance().uniqueName(container_name) + unique_container_name = registry.uniqueName(container_name) container = InstanceContainer(unique_container_name) container.setDefinition(definition_id) @@ -217,15 +220,17 @@ class CuraStackBuilder: @classmethod def createDefinitionChangesContainer(cls, container_stack, container_name): from cura.CuraApplication import CuraApplication + application = CuraApplication.getInstance() + registry = application.getContainerRegistry() - unique_container_name = ContainerRegistry.getInstance().uniqueName(container_name) + unique_container_name = registry.uniqueName(container_name) definition_changes_container = InstanceContainer(unique_container_name) definition_changes_container.setDefinition(container_stack.getBottom().getId()) definition_changes_container.addMetaDataEntry("type", "definition_changes") definition_changes_container.addMetaDataEntry("setting_version", CuraApplication.SettingVersion) - ContainerRegistry.getInstance().addContainer(definition_changes_container) + registry.addContainer(definition_changes_container) container_stack.definitionChanges = definition_changes_container return definition_changes_container diff --git a/cura/Settings/ExtruderManager.py b/cura/Settings/ExtruderManager.py index c2fe929957..5fecb7aa17 100755 --- a/cura/Settings/ExtruderManager.py +++ b/cura/Settings/ExtruderManager.py @@ -15,6 +15,7 @@ from UM.Settings.SettingFunction import SettingFunction from UM.Settings.SettingInstance import SettingInstance from UM.Settings.ContainerStack import ContainerStack from UM.Settings.PropertyEvaluationContext import PropertyEvaluationContext + from typing import Optional, List, TYPE_CHECKING, Union if TYPE_CHECKING: @@ -29,6 +30,10 @@ class ExtruderManager(QObject): ## Registers listeners and such to listen to changes to the extruders. def __init__(self, parent = None): + if ExtruderManager.__instance is not None: + raise RuntimeError("Try to create singleton '%s' more than once" % self.__class__.__name__) + ExtruderManager.__instance = self + super().__init__(parent) self._application = Application.getInstance() @@ -92,28 +97,6 @@ class ExtruderManager(QObject): if extruder.getId() == extruder_stack_id: return extruder.qualityChanges.getId() - ## The instance of the singleton pattern. - # - # It's None if the extruder manager hasn't been created yet. - __instance = None - - @staticmethod - def createExtruderManager(): - return ExtruderManager().getInstance() - - ## Gets an instance of the extruder manager, or creates one if no instance - # exists yet. - # - # This is an implementation of singleton. If an extruder manager already - # exists, it is re-used. - # - # \return The extruder manager. - @classmethod - def getInstance(cls) -> "ExtruderManager": - if not cls.__instance: - cls.__instance = ExtruderManager() - return cls.__instance - ## Changes the active extruder by index. # # \param index The index of the new active extruder. @@ -746,3 +729,9 @@ class ExtruderManager(QObject): resolved_value = global_stack.getProperty(key, "value", context = context) return resolved_value + + __instance = None + + @classmethod + def getInstance(cls, *args, **kwargs) -> "ExtruderManager": + return cls.__instance diff --git a/cura/SingleInstance.py b/cura/SingleInstance.py new file mode 100644 index 0000000000..a664204d79 --- /dev/null +++ b/cura/SingleInstance.py @@ -0,0 +1,103 @@ +# Copyright (c) 2018 Ultimaker B.V. +# Cura is released under the terms of the LGPLv3 or higher. + +import json +import os +from typing import List, Optional + +from PyQt5.QtNetwork import QLocalServer, QLocalSocket + +from UM.Logger import Logger + + +class SingleInstance: + + def __init__(self, application, files_to_open: Optional[List[str]]): + self._application = application + self._files_to_open = files_to_open + + self._single_instance_server = None + + # Starts a client that checks for a single instance server and sends the files that need to opened if the server + # exists. Returns True if the single instance server is found, otherwise False. + def startClient(self) -> bool: + Logger.log("i", "Checking for the presence of an ready running Cura instance.") + single_instance_socket = QLocalSocket(self._application) + Logger.log("d", "Full single instance server name: %s", single_instance_socket.fullServerName()) + single_instance_socket.connectToServer("ultimaker-cura") + single_instance_socket.waitForConnected(msecs = 3000) # wait for 3 seconds + + if single_instance_socket.state() != QLocalSocket.ConnectedState: + return False + + # We only send the files that need to be opened. + if not self._files_to_open: + Logger.log("i", "No file need to be opened, do nothing.") + return True + + if single_instance_socket.state() == QLocalSocket.ConnectedState: + Logger.log("i", "Connection has been made to the single-instance Cura socket.") + + # Protocol is one line of JSON terminated with a carriage return. + # "command" field is required and holds the name of the command to execute. + # Other fields depend on the command. + + payload = {"command": "clear-all"} + single_instance_socket.write(bytes(json.dumps(payload) + "\n", encoding = "ascii")) + + payload = {"command": "focus"} + single_instance_socket.write(bytes(json.dumps(payload) + "\n", encoding = "ascii")) + + for filename in self._files_to_open: + payload = {"command": "open", "filePath": os.path.abspath(filename)} + single_instance_socket.write(bytes(json.dumps(payload) + "\n", encoding = "ascii")) + + payload = {"command": "close-connection"} + single_instance_socket.write(bytes(json.dumps(payload) + "\n", encoding = "ascii")) + + single_instance_socket.flush() + single_instance_socket.waitForDisconnected() + return True + + def startServer(self) -> None: + self._single_instance_server = QLocalServer() + self._single_instance_server.newConnection.connect(self._onClientConnected) + self._single_instance_server.listen("ultimaker-cura") + + def _onClientConnected(self): + Logger.log("i", "New connection recevied on our single-instance server") + connection = self._single_instance_server.nextPendingConnection() + + if connection is not None: + connection.readyRead.connect(lambda c = connection: self.__readCommands(c)) + + def __readCommands(self, connection): + line = connection.readLine() + while len(line) != 0: # There is also a .canReadLine() + try: + payload = json.loads(str(line, encoding = "ascii").strip()) + command = payload["command"] + + # Command: Remove all models from the build plate. + if command == "clear-all": + self._application.callLater(lambda: self._application.deleteAll()) + + # Command: Load a model file + elif command == "open": + self._application.callLater(lambda f = payload["filePath"]: self._application._openFile(f)) + + # Command: Activate the window and bring it to the top. + elif command == "focus": + # Operating systems these days prevent windows from moving around by themselves. + # 'alert' or flashing the icon in the taskbar is the best thing we do now. + self._application.callLater(lambda: self._application.getMainWindow().alert(0)) + + # Command: Close the socket connection. We're done. + elif command == "close-connection": + connection.close() + + else: + Logger.log("w", "Received an unrecognized command " + str(command)) + except json.decoder.JSONDecodeError as ex: + Logger.log("w", "Unable to parse JSON command '%s': %s", line, repr(ex)) + line = connection.readLine() diff --git a/cura_app.py b/cura_app.py index 8a04b8fe09..18f9b6365f 100755 --- a/cura_app.py +++ b/cura_app.py @@ -1,51 +1,36 @@ #!/usr/bin/env python3 -# Copyright (c) 2015 Ultimaker B.V. +# Copyright (c) 2018 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. -import argparse +import faulthandler import os import sys from UM.Platform import Platform -parser = argparse.ArgumentParser(prog = "cura", - add_help = False) -parser.add_argument('--debug', - action='store_true', - default = False, - help = "Turn on the debug mode by setting this option." - ) -parser.add_argument('--trigger-early-crash', - dest = 'trigger_early_crash', - action = 'store_true', - default = False, - help = "FOR TESTING ONLY. Trigger an early crash to show the crash dialog." - ) -known_args = vars(parser.parse_known_args()[0]) -if not known_args["debug"]: - def get_cura_dir_path(): - if Platform.isWindows(): - return os.path.expanduser("~/AppData/Roaming/cura/") - elif Platform.isLinux(): - return os.path.expanduser("~/.local/share/cura") - elif Platform.isOSX(): - return os.path.expanduser("~/Library/Logs/cura") +# Gets the directory for stdout and stderr +def get_cura_dir_for_stdoutputs() -> str: + if Platform.isWindows(): + return os.path.expanduser("~/AppData/Roaming/cura/") + elif Platform.isLinux(): + return os.path.expanduser("~/.local/share/cura") + elif Platform.isOSX(): + return os.path.expanduser("~/Library/Logs/cura") - if hasattr(sys, "frozen"): - dirpath = get_cura_dir_path() - os.makedirs(dirpath, exist_ok = True) - sys.stdout = open(os.path.join(dirpath, "stdout.log"), "w", encoding = "utf-8") - sys.stderr = open(os.path.join(dirpath, "stderr.log"), "w", encoding = "utf-8") -import platform -import faulthandler +# Change stdout and stderr to files if Cura is running as a packaged application. +if hasattr(sys, "frozen"): + dir_path = get_cura_dir_for_stdoutputs() + os.makedirs(dir_path, exist_ok = True) + sys.stdout = open(os.path.join(dir_path, "stdout.log"), "w", encoding = "utf-8") + sys.stderr = open(os.path.join(dir_path, "stderr.log"), "w", encoding = "utf-8") -#WORKAROUND: GITHUB-88 GITHUB-385 GITHUB-612 + +# 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 - linux_distro_name = platform.linux_distribution()[0].lower() # The workaround is only needed on Ubuntu+NVidia drivers. Other drivers are not affected, but fine with this fix. try: import ctypes @@ -79,6 +64,7 @@ if "PYTHONPATH" in os.environ.keys(): # If PYTHONPATH is u sys.path.remove(PATH_real) sys.path.insert(1, PATH_real) # Insert it at 1 after os.curdir, which is 0. + def exceptHook(hook_type, value, traceback): from cura.CrashHandler import CrashHandler from cura.CuraApplication import CuraApplication @@ -121,25 +107,25 @@ def exceptHook(hook_type, value, traceback): _crash_handler.early_crash_dialog.show() sys.exit(application.exec_()) -if not known_args["debug"]: - sys.excepthook = exceptHook + +# Set exception hook to use the crash dialog handler +sys.excepthook = exceptHook +# Enable dumping traceback for all threads +faulthandler.enable(all_threads = True) # Workaround for a race condition on certain systems where there # is a race condition between Arcus and PyQt. Importing Arcus # first seems to prevent Sip from going into a state where it # tries to create PyQt objects on a non-main thread. import Arcus #@UnusedImport -import cura.CuraApplication -import cura.Settings.CuraContainerRegistry +from cura.CuraApplication import CuraApplication -faulthandler.enable() +app = CuraApplication() +app.addCommandLineOptions() +app.parseCliOptions() +app.initialize() -# Force an instance of CuraContainerRegistry to be created and reused later. -cura.Settings.CuraContainerRegistry.CuraContainerRegistry.getInstance() +app.startSlashWindowPhase() +app.startPostSlashWindowPhase() -# This pre-start up check is needed to determine if we should start the application at all. -if not cura.CuraApplication.CuraApplication.preStartUp(parser = parser, parsed_command_line = known_args): - sys.exit(0) - -app = cura.CuraApplication.CuraApplication.getInstance(parser = parser, parsed_command_line = known_args) app.run() diff --git a/plugins/CuraEngineBackend/CuraEngineBackend.py b/plugins/CuraEngineBackend/CuraEngineBackend.py index 654c1024bb..111b253dd8 100755 --- a/plugins/CuraEngineBackend/CuraEngineBackend.py +++ b/plugins/CuraEngineBackend/CuraEngineBackend.py @@ -275,7 +275,7 @@ class CuraEngineBackend(QObject, Backend): self.processingProgress.emit(0) Logger.log("d", "Attempting to kill the engine process") - if Application.getInstance().getCommandLineOption("external-backend", False): + if Application.getInstance().getUseExternalBackend(): return if self._process is not None: diff --git a/plugins/USBPrinting/USBPrinterOutputDeviceManager.py b/plugins/USBPrinting/USBPrinterOutputDeviceManager.py index 6375c92879..5fc657e50f 100644 --- a/plugins/USBPrinting/USBPrinterOutputDeviceManager.py +++ b/plugins/USBPrinting/USBPrinterOutputDeviceManager.py @@ -1,10 +1,16 @@ -# Copyright (c) 2017 Ultimaker B.V. +# Copyright (c) 2018 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. -from UM.Signal import Signal, signalemitter -from UM.Application import Application -from UM.Resources import Resources +import threading +import platform +import time +import serial.tools.list_ports + +from PyQt5.QtCore import QObject, pyqtSlot, pyqtProperty, pyqtSignal + from UM.Logger import Logger +from UM.Resources import Resources +from UM.Signal import Signal, signalemitter from UM.OutputDevice.OutputDevicePlugin import OutputDevicePlugin from UM.i18n import i18nCatalog @@ -12,12 +18,6 @@ from cura.PrinterOutputDevice import ConnectionState from cura.CuraApplication import CuraApplication from . import USBPrinterOutputDevice -from PyQt5.QtCore import QObject, pyqtSlot, pyqtProperty, pyqtSignal - -import threading -import platform -import time -import serial.tools.list_ports i18n_catalog = i18nCatalog("cura") @@ -28,8 +28,14 @@ class USBPrinterOutputDeviceManager(QObject, OutputDevicePlugin): addUSBOutputDeviceSignal = Signal() progressChanged = pyqtSignal() - def __init__(self, parent = None): + def __init__(self, application, parent = None): + if USBPrinterOutputDeviceManager.__instance is not None: + raise RuntimeError("Try to create singleton '%s' more than once" % self.__class__.__name__) + USBPrinterOutputDeviceManager.__instance = self + super().__init__(parent = parent) + self._application = application + self._serial_port_list = [] self._usb_output_devices = {} self._usb_output_devices_model = None @@ -38,11 +44,11 @@ class USBPrinterOutputDeviceManager(QObject, OutputDevicePlugin): self._check_updates = True - Application.getInstance().applicationShuttingDown.connect(self.stop) + self._application.applicationShuttingDown.connect(self.stop) # Because the model needs to be created in the same thread as the QMLEngine, we use a signal. self.addUSBOutputDeviceSignal.connect(self.addOutputDevice) - Application.getInstance().globalContainerStackChanged.connect(self.updateUSBPrinterOutputDevices) + self._application.globalContainerStackChanged.connect(self.updateUSBPrinterOutputDevices) # The method updates/reset the USB settings for all connected USB devices def updateUSBPrinterOutputDevices(self): @@ -69,7 +75,7 @@ class USBPrinterOutputDeviceManager(QObject, OutputDevicePlugin): def _updateThread(self): while self._check_updates: - container_stack = Application.getInstance().getGlobalContainerStack() + container_stack = self._application.getGlobalContainerStack() if container_stack is None: time.sleep(5) continue @@ -80,19 +86,10 @@ class USBPrinterOutputDeviceManager(QObject, OutputDevicePlugin): self._addRemovePorts(port_list) time.sleep(5) - ## Return the singleton instance of the USBPrinterManager - @classmethod - def getInstance(cls, engine = None, script_engine = None): - # Note: Explicit use of class name to prevent issues with inheritance. - if USBPrinterOutputDeviceManager._instance is None: - USBPrinterOutputDeviceManager._instance = cls() - - return USBPrinterOutputDeviceManager._instance - @pyqtSlot(result = str) def getDefaultFirmwareName(self): # Check if there is a valid global container stack - global_container_stack = Application.getInstance().getGlobalContainerStack() + global_container_stack = self._application.getGlobalContainerStack() if not global_container_stack: Logger.log("e", "There is no global container stack. Can not update firmware.") self._firmware_view.close() @@ -181,4 +178,8 @@ class USBPrinterOutputDeviceManager(QObject, OutputDevicePlugin): return list(base_list) - _instance = None # type: "USBPrinterOutputDeviceManager" + __instance = None + + @classmethod + def getInstance(cls, *args, **kwargs) -> "USBPrinterOutputDeviceManager": + return cls.__instance diff --git a/plugins/USBPrinting/__init__.py b/plugins/USBPrinting/__init__.py index 7bf5853c10..fd5488eead 100644 --- a/plugins/USBPrinting/__init__.py +++ b/plugins/USBPrinting/__init__.py @@ -15,4 +15,4 @@ def register(app): # We are violating the QT API here (as we use a factory, which is technically not allowed). # but we don't really have another means for doing this (and it seems to you know -work-) qmlRegisterSingletonType(USBPrinterOutputDeviceManager.USBPrinterOutputDeviceManager, "Cura", 1, 0, "USBPrinterManager", USBPrinterOutputDeviceManager.USBPrinterOutputDeviceManager.getInstance) - return {"output_device": USBPrinterOutputDeviceManager.USBPrinterOutputDeviceManager.getInstance()} + return {"output_device": USBPrinterOutputDeviceManager.USBPrinterOutputDeviceManager(app)} From 42ecb126145eddebe0ebe7f5ae5ea303ef40ac05 Mon Sep 17 00:00:00 2001 From: Diego Prado Gesto Date: Tue, 8 May 2018 11:16:00 +0200 Subject: [PATCH 02/12] CURA-5164 Fix typo in the method signature --- cura/CuraApplication.py | 4 ++-- cura_app.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index 47b2dd25bf..b8c417d8d3 100755 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -406,8 +406,8 @@ class CuraApplication(QtApplication): ) # Runs preparations that needs to be done before the starting process. - def startSlashWindowPhase(self): - super().startSlashWindowPhase() + def startSplashWindowPhase(self): + super().startSplashWindowPhase() self.setWindowIcon(QIcon(Resources.getPath(Resources.Images, "cura-icon.png"))) diff --git a/cura_app.py b/cura_app.py index 18f9b6365f..1495a9e526 100755 --- a/cura_app.py +++ b/cura_app.py @@ -125,7 +125,7 @@ app.addCommandLineOptions() app.parseCliOptions() app.initialize() -app.startSlashWindowPhase() -app.startPostSlashWindowPhase() +app.startSplashWindowPhase() +app.startPostSplashWindowPhase() app.run() From 43657010bae9de322b1d1b8a28df1714d10a54da Mon Sep 17 00:00:00 2001 From: Diego Prado Gesto Date: Fri, 11 May 2018 08:42:03 +0200 Subject: [PATCH 03/12] CURA-5164 The Preferences is not a singleton class anymore since in some point several instances need to be created. - In the ThreeMFWorkspaceReader we need to create some temporal instances of Preferences that makes it not singleton anymore. - The current preferences are kept in the Application class and so all the calls to the preferences are changed to get the preferences from Application. - The method getInstance in Preferences is kept as deprecated since some external plugins. --- cura/CuraApplication.py | 24 +++++------ .../Models/SettingVisibilityPresetsModel.py | 4 +- cura/ObjectsModel.py | 5 +-- cura/PlatformPhysics.py | 9 ++-- cura/PrintInformation.py | 7 ++- cura/Settings/MachineManager.py | 29 ++++++------- plugins/3MFReader/ThreeMFWorkspaceReader.py | 4 +- plugins/3MFWriter/ThreeMFWorkspaceWriter.py | 2 +- plugins/AutoSave/AutoSave.py | 9 ++-- plugins/ChangeLogPlugin/ChangeLog.py | 9 ++-- .../CuraEngineBackend/CuraEngineBackend.py | 11 +++-- .../ProcessSlicedLayersJob.py | 3 +- .../FirmwareUpdateChecker.py | 9 ++-- .../FirmwareUpdateCheckerJob.py | 5 +-- plugins/GCodeReader/FlavorParser.py | 5 +-- plugins/GCodeReader/GCodeReader.py | 4 +- .../PerObjectSettingsTool.py | 5 +-- plugins/SimulationView/SimulationView.py | 43 +++++++++---------- plugins/SliceInfoPlugin/SliceInfo.py | 17 ++++---- plugins/SolidView/SolidView.py | 5 +-- .../UM3OutputDevicePlugin.py | 3 +- plugins/UserAgreement/UserAgreement.py | 9 ++-- 22 files changed, 102 insertions(+), 119 deletions(-) diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index b8c417d8d3..72f2d0af09 100755 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -459,7 +459,7 @@ class CuraApplication(QtApplication): self._container_registry.loadAllMetadata() # set the setting version for Preferences - preferences = Preferences.getInstance() + preferences = self.getPreferences() preferences.addPreference("metadata/setting_version", 0) preferences.setValue("metadata/setting_version", self.SettingVersion) #Don't make it equal to the default so that the setting version always gets written to the file. @@ -484,7 +484,7 @@ class CuraApplication(QtApplication): preferences.addPreference("view/filter_current_build_plate", False) preferences.addPreference("cura/sidebar_collapsed", False) - self._need_to_show_user_agreement = not Preferences.getInstance().getValue("general/accepted_user_agreement") + self._need_to_show_user_agreement = not self.getPreferences().getValue("general/accepted_user_agreement") for key in [ "dialog_load_path", # dialog_save_path is in LocalFileOutputDevicePlugin @@ -546,7 +546,7 @@ class CuraApplication(QtApplication): def discardOrKeepProfileChanges(self): has_user_interaction = False - choice = Preferences.getInstance().getValue("cura/choice_on_profile_override") + choice = self.getPreferences().getValue("cura/choice_on_profile_override") if choice == "always_discard": # don't show dialog and DISCARD the profile self.discardOrKeepProfileChangesClosed("discard") @@ -597,12 +597,12 @@ class CuraApplication(QtApplication): @pyqtSlot(str, result = QUrl) def getDefaultPath(self, key): - default_path = Preferences.getInstance().getValue("local_file/%s" % key) + default_path = self.getPreferences().getValue("local_file/%s" % key) return QUrl.fromLocalFile(default_path) @pyqtSlot(str, str) def setDefaultPath(self, key, default_path): - Preferences.getInstance().setValue("local_file/%s" % key, QUrl(default_path).toLocalFile()) + self.getPreferences().setValue("local_file/%s" % key, QUrl(default_path).toLocalFile()) ## Handle loading of all plugin types (and the backend explicitly) # \sa PluginRegistry @@ -672,7 +672,7 @@ class CuraApplication(QtApplication): # Initialize setting visibility presets model self._setting_visibility_presets_model = SettingVisibilityPresetsModel(self) default_visibility_profile = self._setting_visibility_presets_model.getItem(0) - Preferences.getInstance().setDefault("general/visible_settings", ";".join(default_visibility_profile["settings"])) + self.getPreferences().setDefault("general/visible_settings", ";".join(default_visibility_profile["settings"])) # Detect in which mode to run and execute that mode if self._is_headless: @@ -934,7 +934,7 @@ class CuraApplication(QtApplication): # Default self.getController().setActiveTool("TranslateTool") - if Preferences.getInstance().getValue("view/center_on_select"): + if self.getPreferences().getValue("view/center_on_select"): self._center_after_select = True else: if self.getController().getActiveTool(): @@ -1329,15 +1329,15 @@ class CuraApplication(QtApplication): categories = list(set(categories)) categories.sort() joined = ";".join(categories) - if joined != Preferences.getInstance().getValue("cura/categories_expanded"): - Preferences.getInstance().setValue("cura/categories_expanded", joined) + if joined != self.getPreferences().getValue("cura/categories_expanded"): + self.getPreferences().setValue("cura/categories_expanded", joined) self.expandedCategoriesChanged.emit() expandedCategoriesChanged = pyqtSignal() @pyqtProperty("QStringList", notify = expandedCategoriesChanged) def expandedCategories(self): - return Preferences.getInstance().getValue("cura/categories_expanded").split(";") + return self.getPreferences().getValue("cura/categories_expanded").split(";") @pyqtSlot() def mergeSelected(self): @@ -1600,8 +1600,8 @@ class CuraApplication(QtApplication): self.fileLoaded.emit(filename) arrange_objects_on_load = ( - not Preferences.getInstance().getValue("cura/use_multi_build_plate") or - not Preferences.getInstance().getValue("cura/not_arrange_objects_on_load")) + not self.getPreferences().getValue("cura/use_multi_build_plate") or + not self.getPreferences().getValue("cura/not_arrange_objects_on_load")) target_build_plate = self.getMultiBuildPlateModel().activeBuildPlate if arrange_objects_on_load else -1 root = self.getController().getScene().getRoot() diff --git a/cura/Machines/Models/SettingVisibilityPresetsModel.py b/cura/Machines/Models/SettingVisibilityPresetsModel.py index 8880ac5ce1..3062e83889 100644 --- a/cura/Machines/Models/SettingVisibilityPresetsModel.py +++ b/cura/Machines/Models/SettingVisibilityPresetsModel.py @@ -8,9 +8,9 @@ from configparser import ConfigParser from PyQt5.QtCore import pyqtProperty, Qt, pyqtSignal, pyqtSlot +from UM.Application import Application from UM.Logger import Logger from UM.Qt.ListModel import ListModel -from UM.Preferences import Preferences from UM.Resources import Resources from UM.MimeTypeDatabase import MimeTypeDatabase, MimeTypeNotFoundError @@ -33,7 +33,7 @@ class SettingVisibilityPresetsModel(ListModel): basic_item = self.items[1] basic_visibile_settings = ";".join(basic_item["settings"]) - self._preferences = Preferences.getInstance() + self._preferences = Application.getInstance().getPreferences() # Preference to store which preset is currently selected self._preferences.addPreference("cura/active_setting_visibility_preset", "basic") # Preference that stores the "custom" set so it can always be restored (even after a restart) diff --git a/cura/ObjectsModel.py b/cura/ObjectsModel.py index cfe4320e28..f3c703d424 100644 --- a/cura/ObjectsModel.py +++ b/cura/ObjectsModel.py @@ -8,7 +8,6 @@ from UM.Qt.ListModel import ListModel from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator from UM.Scene.SceneNode import SceneNode from UM.Scene.Selection import Selection -from UM.Preferences import Preferences from UM.i18n import i18nCatalog catalog = i18nCatalog("cura") @@ -20,7 +19,7 @@ class ObjectsModel(ListModel): super().__init__() Application.getInstance().getController().getScene().sceneChanged.connect(self._updateDelayed) - Preferences.getInstance().preferenceChanged.connect(self._updateDelayed) + Application.getInstance().getPreferences().preferenceChanged.connect(self._updateDelayed) self._update_timer = QTimer() self._update_timer.setInterval(100) @@ -38,7 +37,7 @@ class ObjectsModel(ListModel): def _update(self, *args): nodes = [] - filter_current_build_plate = Preferences.getInstance().getValue("view/filter_current_build_plate") + filter_current_build_plate = Application.getInstance().getPreferences().getValue("view/filter_current_build_plate") active_build_plate_number = self._build_plate_number group_nr = 1 for node in DepthFirstIterator(Application.getInstance().getController().getScene().getRoot()): diff --git a/cura/PlatformPhysics.py b/cura/PlatformPhysics.py index 1a5d6ef837..db28478597 100755 --- a/cura/PlatformPhysics.py +++ b/cura/PlatformPhysics.py @@ -8,7 +8,6 @@ from UM.Scene.SceneNode import SceneNode from UM.Scene.Iterator.BreadthFirstIterator import BreadthFirstIterator from UM.Math.Vector import Vector from UM.Scene.Selection import Selection -from UM.Preferences import Preferences from cura.Scene.ConvexHullDecorator import ConvexHullDecorator @@ -36,8 +35,8 @@ class PlatformPhysics: self._max_overlap_checks = 10 # How many times should we try to find a new spot per tick? self._minimum_gap = 2 # It is a minimum distance (in mm) between two models, applicable for small models - Preferences.getInstance().addPreference("physics/automatic_push_free", True) - Preferences.getInstance().addPreference("physics/automatic_drop_down", True) + Application.getInstance().getPreferences().addPreference("physics/automatic_push_free", True) + Application.getInstance().getPreferences().addPreference("physics/automatic_drop_down", True) def _onSceneChanged(self, source): if not source.getMeshData(): @@ -71,7 +70,7 @@ class PlatformPhysics: # Move it downwards if bottom is above platform move_vector = Vector() - if Preferences.getInstance().getValue("physics/automatic_drop_down") and not (node.getParent() and node.getParent().callDecoration("isGroup") or node.getParent() != root) and node.isEnabled(): #If an object is grouped, don't move it down + if Application.getInstance().getPreferences().getValue("physics/automatic_drop_down") and not (node.getParent() and node.getParent().callDecoration("isGroup") or node.getParent() != root) and node.isEnabled(): #If an object is grouped, don't move it down z_offset = node.callDecoration("getZOffset") if node.getDecorator(ZOffsetDecorator.ZOffsetDecorator) else 0 move_vector = move_vector.set(y = -bbox.bottom + z_offset) @@ -80,7 +79,7 @@ class PlatformPhysics: node.addDecorator(ConvexHullDecorator()) # only push away objects if this node is a printing mesh - if not node.callDecoration("isNonPrintingMesh") and Preferences.getInstance().getValue("physics/automatic_push_free"): + if not node.callDecoration("isNonPrintingMesh") and Application.getInstance().getPreferences().getValue("physics/automatic_push_free"): # Check for collisions between convex hulls for other_node in BreadthFirstIterator(root): # Ignore root, ourselves and anything that is not a normal SceneNode. diff --git a/cura/PrintInformation.py b/cura/PrintInformation.py index 8c897230d5..debc765084 100644 --- a/cura/PrintInformation.py +++ b/cura/PrintInformation.py @@ -12,7 +12,6 @@ from PyQt5.QtCore import QObject, pyqtSignal, pyqtProperty, pyqtSlot from UM.i18n import i18nCatalog from UM.Logger import Logger -from UM.Preferences import Preferences from UM.Qt.Duration import Duration from UM.Scene.SceneNode import SceneNode @@ -80,7 +79,7 @@ class PrintInformation(QObject): self._application.workspaceLoaded.connect(self.setProjectName) self._multi_build_plate_model.activeBuildPlateChanged.connect(self._onActiveBuildPlateChanged) - Preferences.getInstance().preferenceChanged.connect(self._onPreferencesChanged) + self._application.getInstance().getPreferences().preferenceChanged.connect(self._onPreferencesChanged) self._application.getMachineManager().rootMaterialChanged.connect(self._onActiveMaterialsChanged) self._onActiveMaterialsChanged() @@ -208,7 +207,7 @@ class PrintInformation(QObject): self._material_costs[build_plate_number] = [] self._material_names[build_plate_number] = [] - material_preference_values = json.loads(Preferences.getInstance().getValue("cura/material_settings")) + material_preference_values = json.loads(self._application.getInstance().getPreferences().getValue("cura/material_settings")) extruder_stacks = global_stack.extruders for position, extruder_stack in extruder_stacks.items(): @@ -299,7 +298,7 @@ class PrintInformation(QObject): self._setAbbreviatedMachineName() if self._pre_sliced: self._job_name = catalog.i18nc("@label", "Pre-sliced file {0}", base_name) - elif Preferences.getInstance().getValue("cura/jobname_prefix"): + elif self._application.getInstance().getPreferences().getValue("cura/jobname_prefix"): # Don't add abbreviation if it already has the exact same abbreviation. if base_name.startswith(self._abbr_machine + "_"): self._job_name = base_name diff --git a/cura/Settings/MachineManager.py b/cura/Settings/MachineManager.py index 436f9814db..8943734f3e 100755 --- a/cura/Settings/MachineManager.py +++ b/cura/Settings/MachineManager.py @@ -16,7 +16,6 @@ from UM.FlameProfiler import pyqtSlot from UM import Util from UM.Application import Application -from UM.Preferences import Preferences from UM.Logger import Logger from UM.Message import Message @@ -97,12 +96,12 @@ class MachineManager(QObject): ExtruderManager.getInstance().activeExtruderChanged.connect(self.activeStackChanged) self.activeStackChanged.connect(self.activeStackValueChanged) - Preferences.getInstance().addPreference("cura/active_machine", "") + self._application.getPreferences().addPreference("cura/active_machine", "") self._global_event_keys = set() self._printer_output_devices = [] # type: List[PrinterOutputDevice] - Application.getInstance().getOutputDeviceManager().outputDevicesChanged.connect(self._onOutputDevicesChanged) + self._application.getOutputDeviceManager().outputDevicesChanged.connect(self._onOutputDevicesChanged) # There might already be some output devices by the time the signal is connected self._onOutputDevicesChanged() @@ -163,7 +162,7 @@ class MachineManager(QObject): rootMaterialChanged = pyqtSignal() def setInitialActiveMachine(self) -> None: - active_machine_id = Preferences.getInstance().getValue("cura/active_machine") + active_machine_id = self._application.getPreferences().getValue("cura/active_machine") if active_machine_id != "" and ContainerRegistry.getInstance().findContainerStacksMetadata(id = active_machine_id): # An active machine was saved, so restore it. self.setActiveMachine(active_machine_id) @@ -172,7 +171,7 @@ class MachineManager(QObject): def _onOutputDevicesChanged(self) -> None: self._printer_output_devices = [] - for printer_output_device in Application.getInstance().getOutputDeviceManager().getOutputDevices(): + for printer_output_device in self._application.getOutputDeviceManager().getOutputDevices(): if isinstance(printer_output_device, PrinterOutputDevice): self._printer_output_devices.append(printer_output_device) @@ -239,7 +238,7 @@ class MachineManager(QObject): extruder_stack.containersChanged.disconnect(self._onContainersChanged) # Update the local global container stack reference - self._global_container_stack = Application.getInstance().getGlobalContainerStack() + self._global_container_stack = self._application.getGlobalContainerStack() if self._global_container_stack: self.updateDefaultExtruder() self.updateNumberExtrudersEnabled() @@ -247,7 +246,7 @@ class MachineManager(QObject): # after switching the global stack we reconnect all the signals and set the variant and material references if self._global_container_stack: - Preferences.getInstance().setValue("cura/active_machine", self._global_container_stack.getId()) + self._application.getPreferences().setValue("cura/active_machine", self._global_container_stack.getId()) self._global_container_stack.nameChanged.connect(self._onMachineNameChanged) self._global_container_stack.containersChanged.connect(self._onContainersChanged) @@ -271,7 +270,7 @@ class MachineManager(QObject): if self._global_container_stack.getId() in self.machine_extruder_material_update_dict: for func in self.machine_extruder_material_update_dict[self._global_container_stack.getId()]: - Application.getInstance().callLater(func) + self._application.callLater(func) del self.machine_extruder_material_update_dict[self._global_container_stack.getId()] self.activeQualityGroupChanged.emit() @@ -361,7 +360,7 @@ class MachineManager(QObject): global_stack = containers[0] ExtruderManager.getInstance().setActiveExtruderIndex(0) # Switch to first extruder self._global_container_stack = global_stack - Application.getInstance().setGlobalContainerStack(global_stack) + self._application.setGlobalContainerStack(global_stack) ExtruderManager.getInstance()._globalContainerStackChanged() self._initMachineState(containers[0]) self._onGlobalContainerChanged() @@ -835,7 +834,7 @@ class MachineManager(QObject): ## Set the amount of extruders on the active machine (global stack) # \param extruder_count int the number of extruders to set def setActiveMachineExtruderCount(self, extruder_count: int) -> None: - extruder_manager = Application.getInstance().getExtruderManager() + extruder_manager = self._application.getExtruderManager() definition_changes_container = self._global_container_stack.definitionChanges if not self._global_container_stack or definition_changes_container == self._empty_definition_changes_container: @@ -852,7 +851,7 @@ class MachineManager(QObject): self.correctExtruderSettings() # Check to see if any objects are set to print with an extruder that will no longer exist - root_node = Application.getInstance().getController().getScene().getRoot() + root_node = self._application.getController().getScene().getRoot() for node in DepthFirstIterator(root_node): if node.getMeshData(): extruder_nr = node.callDecoration("getActiveExtruderPosition") @@ -885,7 +884,7 @@ class MachineManager(QObject): global_user_container.removeInstance(setting_key) # Signal that the global stack has changed - Application.getInstance().globalContainerStackChanged.emit() + self._application.globalContainerStackChanged.emit() self.forceUpdateAllSettings() @pyqtSlot(int, result = QObject) @@ -1117,7 +1116,7 @@ class MachineManager(QObject): def _setGlobalVariant(self, container_node): self._global_container_stack.variant = container_node.getContainer() if not self._global_container_stack.variant: - self._global_container_stack.variant = Application.getInstance().empty_variant_container + self._global_container_stack.variant = self._application.empty_variant_container def _setMaterial(self, position, container_node = None): if container_node and container_node.getContainer(): @@ -1359,7 +1358,7 @@ class MachineManager(QObject): self._setQualityGroup(quality_group) # See if we need to show the Discard or Keep changes screen - if not no_dialog and self.hasUserSettings and Preferences.getInstance().getValue("cura/active_mode") == 1: + if not no_dialog and self.hasUserSettings and self._application.getPreferences().getValue("cura/active_mode") == 1: self._application.discardOrKeepProfileChanges() @pyqtProperty(QObject, fset = setQualityGroup, notify = activeQualityGroupChanged) @@ -1373,7 +1372,7 @@ class MachineManager(QObject): self._setQualityChangesGroup(quality_changes_group) # See if we need to show the Discard or Keep changes screen - if not no_dialog and self.hasUserSettings and Preferences.getInstance().getValue("cura/active_mode") == 1: + if not no_dialog and self.hasUserSettings and self._application.getPreferences().getValue("cura/active_mode") == 1: self._application.discardOrKeepProfileChanges() @pyqtSlot() diff --git a/plugins/3MFReader/ThreeMFWorkspaceReader.py b/plugins/3MFReader/ThreeMFWorkspaceReader.py index 5cd0ef5ced..8eecc3b5d9 100755 --- a/plugins/3MFReader/ThreeMFWorkspaceReader.py +++ b/plugins/3MFReader/ThreeMFWorkspaceReader.py @@ -456,7 +456,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader): num_visible_settings = len(visible_settings_string.split(";")) active_mode = temp_preferences.getValue("cura/active_mode") if not active_mode: - active_mode = Preferences.getInstance().getValue("cura/active_mode") + active_mode = Application.getInstance().getPreferences().getValue("cura/active_mode") except KeyError: # If there is no preferences file, it's not a workspace, so notify user of failure. Logger.log("w", "File %s is not a valid workspace.", file_name) @@ -575,7 +575,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader): temp_preferences.deserialize(serialized) # Copy a number of settings from the temp preferences to the global - global_preferences = Preferences.getInstance() + global_preferences = application.getInstance().getPreferences() visible_settings = temp_preferences.getValue("general/visible_settings") if visible_settings is None: diff --git a/plugins/3MFWriter/ThreeMFWorkspaceWriter.py b/plugins/3MFWriter/ThreeMFWorkspaceWriter.py index e948f62337..33df0bfe90 100644 --- a/plugins/3MFWriter/ThreeMFWorkspaceWriter.py +++ b/plugins/3MFWriter/ThreeMFWorkspaceWriter.py @@ -51,7 +51,7 @@ class ThreeMFWorkspaceWriter(WorkspaceWriter): self._writeContainerToArchive(container, archive) # Write preferences to archive - original_preferences = Preferences.getInstance() #Copy only the preferences that we use to the workspace. + original_preferences = Application.getInstance().getPreferences() #Copy only the preferences that we use to the workspace. temp_preferences = Preferences() for preference in {"general/visible_settings", "cura/active_mode", "cura/categories_expanded"}: temp_preferences.addPreference(preference, None) diff --git a/plugins/AutoSave/AutoSave.py b/plugins/AutoSave/AutoSave.py index 5fdac502b5..5025cc563c 100644 --- a/plugins/AutoSave/AutoSave.py +++ b/plugins/AutoSave/AutoSave.py @@ -4,7 +4,6 @@ from PyQt5.QtCore import QTimer from UM.Extension import Extension -from UM.Preferences import Preferences from UM.Application import Application from UM.Resources import Resources from UM.Logger import Logger @@ -14,14 +13,14 @@ class AutoSave(Extension): def __init__(self): super().__init__() - Preferences.getInstance().preferenceChanged.connect(self._triggerTimer) + Application.getInstance().getPreferences().preferenceChanged.connect(self._triggerTimer) self._global_stack = None - Preferences.getInstance().addPreference("cura/autosave_delay", 1000 * 10) + Application.getInstance().getPreferences().addPreference("cura/autosave_delay", 1000 * 10) self._change_timer = QTimer() - self._change_timer.setInterval(Preferences.getInstance().getValue("cura/autosave_delay")) + self._change_timer.setInterval(Application.getInstance().getPreferences().getValue("cura/autosave_delay")) self._change_timer.setSingleShot(True) self._saving = False @@ -72,6 +71,6 @@ class AutoSave(Extension): Application.getInstance().saveSettings() - Preferences.getInstance().writeToFile(Resources.getStoragePath(Resources.Preferences, Application.getInstance().getApplicationName() + ".cfg")) + Application.getInstance().getPreferences().writeToFile(Resources.getStoragePath(Resources.Preferences, Application.getInstance().getApplicationName() + ".cfg")) self._saving = False diff --git a/plugins/ChangeLogPlugin/ChangeLog.py b/plugins/ChangeLogPlugin/ChangeLog.py index 030d854d3f..e93d5c4395 100644 --- a/plugins/ChangeLogPlugin/ChangeLog.py +++ b/plugins/ChangeLogPlugin/ChangeLog.py @@ -3,7 +3,6 @@ from UM.i18n import i18nCatalog from UM.Extension import Extension -from UM.Preferences import Preferences from UM.Application import Application from UM.PluginRegistry import PluginRegistry from UM.Version import Version @@ -29,7 +28,7 @@ class ChangeLog(Extension, QObject,): self._change_logs = None Application.getInstance().engineCreatedSignal.connect(self._onEngineCreated) - Preferences.getInstance().addPreference("general/latest_version_changelog_shown", "2.0.0") #First version of CURA with uranium + Application.getInstance().getPreferences().addPreference("general/latest_version_changelog_shown", "2.0.0") #First version of CURA with uranium self.addMenuItem(catalog.i18nc("@item:inmenu", "Show Changelog"), self.showChangelog) def getChangeLogs(self): @@ -79,12 +78,12 @@ class ChangeLog(Extension, QObject,): if not self._current_app_version: return #We're on dev branch. - if Preferences.getInstance().getValue("general/latest_version_changelog_shown") == "master": + if Application.getInstance().getPreferences().getValue("general/latest_version_changelog_shown") == "master": latest_version_shown = Version("0.0.0") else: - latest_version_shown = Version(Preferences.getInstance().getValue("general/latest_version_changelog_shown")) + latest_version_shown = Version(Application.getInstance().getPreferences().getValue("general/latest_version_changelog_shown")) - Preferences.getInstance().setValue("general/latest_version_changelog_shown", Application.getInstance().getVersion()) + Application.getInstance().getPreferences().setValue("general/latest_version_changelog_shown", Application.getInstance().getVersion()) # Do not show the changelog when there is no global container stack # This implies we are running Cura for the first time. diff --git a/plugins/CuraEngineBackend/CuraEngineBackend.py b/plugins/CuraEngineBackend/CuraEngineBackend.py index 111b253dd8..d298ff1aca 100755 --- a/plugins/CuraEngineBackend/CuraEngineBackend.py +++ b/plugins/CuraEngineBackend/CuraEngineBackend.py @@ -4,7 +4,6 @@ from UM.Backend.Backend import Backend, BackendState from UM.Application import Application from UM.Scene.SceneNode import SceneNode -from UM.Preferences import Preferences from UM.Signal import Signal from UM.Logger import Logger from UM.Message import Message @@ -72,7 +71,7 @@ class CuraEngineBackend(QObject, Backend): 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) + Application.getInstance().getPreferences().addPreference("backend/location", default_engine_location) # Workaround to disable layer view processing if layer view is not active. self._layer_view_active = False @@ -121,7 +120,7 @@ class CuraEngineBackend(QObject, Backend): self._slice_start_time = None self._is_disabled = False - Preferences.getInstance().addPreference("general/auto_slice", False) + Application.getInstance().getPreferences().addPreference("general/auto_slice", False) self._use_timer = False # When you update a setting and other settings get changed through inheritance, many propertyChanged signals are fired. @@ -131,7 +130,7 @@ class CuraEngineBackend(QObject, Backend): self._change_timer.setSingleShot(True) self._change_timer.setInterval(500) self.determineAutoSlicing() - Preferences.getInstance().preferenceChanged.connect(self._onPreferencesChanged) + Application.getInstance().getPreferences().preferenceChanged.connect(self._onPreferencesChanged) self._application.initializationFinished.connect(self.initialize) @@ -170,7 +169,7 @@ class CuraEngineBackend(QObject, Backend): # \return list of commands and args / parameters. def getEngineCommand(self): json_path = Resources.getPath(Resources.DefinitionContainers, "fdmprinter.def.json") - return [Preferences.getInstance().getValue("backend/location"), "connect", "127.0.0.1:{0}".format(self._port), "-j", json_path, ""] + return [Application.getInstance().getPreferences().getValue("backend/location"), "connect", "127.0.0.1:{0}".format(self._port), "-j", json_path, ""] ## Emitted when we get a message containing print duration and material amount. # This also implies the slicing has finished. @@ -408,7 +407,7 @@ class CuraEngineBackend(QObject, Backend): enable_timer = True self._is_disabled = False - if not Preferences.getInstance().getValue("general/auto_slice"): + if not Application.getInstance().getPreferences().getValue("general/auto_slice"): enable_timer = False for node in DepthFirstIterator(self._scene.getRoot()): if node.callDecoration("isBlockSlicing"): diff --git a/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py b/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py index cbc097bb33..3bd6d79198 100644 --- a/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py +++ b/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py @@ -6,7 +6,6 @@ import gc from UM.Job import Job from UM.Application import Application from UM.Mesh.MeshData import MeshData -from UM.Preferences import Preferences from UM.View.GL.OpenGLContext import OpenGLContext from UM.Message import Message @@ -199,7 +198,7 @@ class ProcessSlicedLayersJob(Job): material_color_map[0, :] = color # We have to scale the colors for compatibility mode - if OpenGLContext.isLegacyOpenGL() or bool(Preferences.getInstance().getValue("view/force_layer_view_compatibility_mode")): + if OpenGLContext.isLegacyOpenGL() or bool(Application.getInstance().getPreferences().getValue("view/force_layer_view_compatibility_mode")): line_type_brightness = 0.5 # for compatibility mode else: line_type_brightness = 1.0 diff --git a/plugins/FirmwareUpdateChecker/FirmwareUpdateChecker.py b/plugins/FirmwareUpdateChecker/FirmwareUpdateChecker.py index 23a040f2e2..982f9d0b24 100644 --- a/plugins/FirmwareUpdateChecker/FirmwareUpdateChecker.py +++ b/plugins/FirmwareUpdateChecker/FirmwareUpdateChecker.py @@ -5,8 +5,7 @@ from PyQt5.QtCore import QUrl from PyQt5.QtGui import QDesktopServices from UM.Extension import Extension -from UM.Preferences import Preferences -from UM.Logger import Logger +from UM.Application import Application from UM.i18n import i18nCatalog from cura.Settings.GlobalStack import GlobalStack @@ -27,12 +26,12 @@ class FirmwareUpdateChecker(Extension): # Initialize the Preference called `latest_checked_firmware` that stores the last version # checked for the UM3. In the future if we need to check other printers' firmware - Preferences.getInstance().addPreference("info/latest_checked_firmware", "") + Application.getInstance().getPreferences().addPreference("info/latest_checked_firmware", "") # Listen to a Signal that indicates a change in the list of printers, just if the user has enabled the # 'check for updates' option - Preferences.getInstance().addPreference("info/automatic_update_check", True) - if Preferences.getInstance().getValue("info/automatic_update_check"): + Application.getInstance().getPreferences().addPreference("info/automatic_update_check", True) + if Application.getInstance().getPreferences().getValue("info/automatic_update_check"): ContainerRegistry.getInstance().containerAdded.connect(self._onContainerAdded) self._download_url = None diff --git a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py index 66ee43209f..c3b766e7b9 100644 --- a/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py +++ b/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py @@ -1,7 +1,6 @@ # Copyright (c) 2017 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. -from UM.Preferences import Preferences from UM.Application import Application from UM.Message import Message from UM.Logger import Logger @@ -51,11 +50,11 @@ class FirmwareUpdateCheckerJob(Job): current_version = reader(current_version_file).readline().rstrip() # If it is the first time the version is checked, the checked_version is '' - checked_version = Preferences.getInstance().getValue("info/latest_checked_firmware") + checked_version = Application.getInstance().getPreferences().getValue("info/latest_checked_firmware") # If the checked_version is '', it's because is the first time we check firmware and in this case # we will not show the notification, but we will store it for the next time - Preferences.getInstance().setValue("info/latest_checked_firmware", current_version) + Application.getInstance().getPreferences().setValue("info/latest_checked_firmware", current_version) Logger.log("i", "Reading firmware version of %s: checked = %s - latest = %s", machine_name, checked_version, current_version) # The first time we want to store the current version, the notification will not be shown, diff --git a/plugins/GCodeReader/FlavorParser.py b/plugins/GCodeReader/FlavorParser.py index 2679cc23a4..ceb5fbdb5d 100644 --- a/plugins/GCodeReader/FlavorParser.py +++ b/plugins/GCodeReader/FlavorParser.py @@ -10,7 +10,6 @@ from UM.Math.Vector import Vector from UM.Message import Message from cura.Scene.CuraSceneNode import CuraSceneNode from UM.i18n import i18nCatalog -from UM.Preferences import Preferences catalog = i18nCatalog("cura") @@ -43,7 +42,7 @@ class FlavorParser: self._current_layer_thickness = 0.2 # default self._filament_diameter = 2.85 # default - Preferences.getInstance().addPreference("gcodereader/show_caution", True) + Application.getInstance().getPreferences().addPreference("gcodereader/show_caution", True) def _clearValues(self): self._extruder_number = 0 @@ -462,7 +461,7 @@ class FlavorParser: Logger.log("d", "GCode loading finished") - if Preferences.getInstance().getValue("gcodereader/show_caution"): + if Application.getInstance().getPreferences().getValue("gcodereader/show_caution"): caution_message = Message(catalog.i18nc( "@info:generic", "Make sure the g-code is suitable for your printer and printer configuration before sending the file to it. The g-code representation may not be accurate."), diff --git a/plugins/GCodeReader/GCodeReader.py b/plugins/GCodeReader/GCodeReader.py index 6f872bc6f7..b821fa2cb3 100755 --- a/plugins/GCodeReader/GCodeReader.py +++ b/plugins/GCodeReader/GCodeReader.py @@ -4,7 +4,7 @@ from UM.FileHandler.FileReader import FileReader from UM.Mesh.MeshReader import MeshReader from UM.i18n import i18nCatalog -from UM.Preferences import Preferences +from UM.Application import Application catalog = i18nCatalog("cura") from . import MarlinFlavorParser, RepRapFlavorParser @@ -22,7 +22,7 @@ class GCodeReader(MeshReader): self._supported_extensions = [".gcode", ".g"] self._flavor_reader = None - Preferences.getInstance().addPreference("gcodereader/show_caution", True) + Application.getInstance().getPreferences().addPreference("gcodereader/show_caution", True) def preReadFromStream(self, stream, *args, **kwargs): for line in stream.split("\n"): diff --git a/plugins/PerObjectSettingsTool/PerObjectSettingsTool.py b/plugins/PerObjectSettingsTool/PerObjectSettingsTool.py index d2db5ff420..baa700165c 100644 --- a/plugins/PerObjectSettingsTool/PerObjectSettingsTool.py +++ b/plugins/PerObjectSettingsTool/PerObjectSettingsTool.py @@ -5,7 +5,6 @@ from UM.Tool import Tool from UM.Scene.Selection import Selection from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator from UM.Application import Application -from UM.Preferences import Preferences from cura.Settings.SettingOverrideDecorator import SettingOverrideDecorator from cura.Settings.ExtruderManager import ExtruderManager from UM.Settings.SettingInstance import SettingInstance @@ -27,7 +26,7 @@ class PerObjectSettingsTool(Tool): Selection.selectionChanged.connect(self.propertyChanged) - Preferences.getInstance().preferenceChanged.connect(self._onPreferenceChanged) + Application.getInstance().getPreferences().preferenceChanged.connect(self._onPreferenceChanged) self._onPreferenceChanged("cura/active_mode") Application.getInstance().globalContainerStackChanged.connect(self._onGlobalContainerChanged) @@ -106,7 +105,7 @@ class PerObjectSettingsTool(Tool): def _onPreferenceChanged(self, preference): if preference == "cura/active_mode": - self._advanced_mode = Preferences.getInstance().getValue(preference) == 1 + self._advanced_mode = Application.getInstance().getPreferences().getValue(preference) == 1 self._updateEnabled() def _onGlobalContainerChanged(self): diff --git a/plugins/SimulationView/SimulationView.py b/plugins/SimulationView/SimulationView.py index 85849efb2f..44643dbf1c 100644 --- a/plugins/SimulationView/SimulationView.py +++ b/plugins/SimulationView/SimulationView.py @@ -16,7 +16,6 @@ from UM.Mesh.MeshBuilder import MeshBuilder from UM.Message import Message from UM.Platform import Platform from UM.PluginRegistry import PluginRegistry -from UM.Preferences import Preferences from UM.Resources import Resources from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator from UM.Scene.Selection import Selection @@ -81,30 +80,30 @@ class SimulationView(View): self._show_travel_moves = False self._nozzle_node = None - Preferences.getInstance().addPreference("view/top_layer_count", 5) - Preferences.getInstance().addPreference("view/only_show_top_layers", False) - Preferences.getInstance().addPreference("view/force_layer_view_compatibility_mode", False) + Application.getInstance().getPreferences().addPreference("view/top_layer_count", 5) + Application.getInstance().getPreferences().addPreference("view/only_show_top_layers", False) + Application.getInstance().getPreferences().addPreference("view/force_layer_view_compatibility_mode", False) - Preferences.getInstance().addPreference("layerview/layer_view_type", 0) - Preferences.getInstance().addPreference("layerview/extruder_opacities", "") + Application.getInstance().getPreferences().addPreference("layerview/layer_view_type", 0) + Application.getInstance().getPreferences().addPreference("layerview/extruder_opacities", "") - Preferences.getInstance().addPreference("layerview/show_travel_moves", False) - Preferences.getInstance().addPreference("layerview/show_helpers", True) - Preferences.getInstance().addPreference("layerview/show_skin", True) - Preferences.getInstance().addPreference("layerview/show_infill", True) + Application.getInstance().getPreferences().addPreference("layerview/show_travel_moves", False) + Application.getInstance().getPreferences().addPreference("layerview/show_helpers", True) + Application.getInstance().getPreferences().addPreference("layerview/show_skin", True) + Application.getInstance().getPreferences().addPreference("layerview/show_infill", True) - Preferences.getInstance().preferenceChanged.connect(self._onPreferencesChanged) + Application.getInstance().getPreferences().preferenceChanged.connect(self._onPreferencesChanged) self._updateWithPreferences() - self._solid_layers = int(Preferences.getInstance().getValue("view/top_layer_count")) - self._only_show_top_layers = bool(Preferences.getInstance().getValue("view/only_show_top_layers")) + self._solid_layers = int(Application.getInstance().getPreferences().getValue("view/top_layer_count")) + self._only_show_top_layers = bool(Application.getInstance().getPreferences().getValue("view/only_show_top_layers")) self._compatibility_mode = self._evaluateCompatibilityMode() self._wireprint_warning_message = Message(catalog.i18nc("@info:status", "Cura does not accurately display layers when Wire Printing is enabled"), title = catalog.i18nc("@info:title", "Simulation View")) def _evaluateCompatibilityMode(self): - return OpenGLContext.isLegacyOpenGL() or bool(Preferences.getInstance().getValue("view/force_layer_view_compatibility_mode")) + return OpenGLContext.isLegacyOpenGL() or bool(Application.getInstance().getPreferences().getValue("view/force_layer_view_compatibility_mode")) def _resetSettings(self): self._layer_view_type = 0 # 0 is material color, 1 is color by linetype, 2 is speed, 3 is layer thickness @@ -543,23 +542,23 @@ class SimulationView(View): self._top_layers_job = None def _updateWithPreferences(self): - self._solid_layers = int(Preferences.getInstance().getValue("view/top_layer_count")) - self._only_show_top_layers = bool(Preferences.getInstance().getValue("view/only_show_top_layers")) + self._solid_layers = int(Application.getInstance().getPreferences().getValue("view/top_layer_count")) + self._only_show_top_layers = bool(Application.getInstance().getPreferences().getValue("view/only_show_top_layers")) self._compatibility_mode = self._evaluateCompatibilityMode() - self.setSimulationViewType(int(float(Preferences.getInstance().getValue("layerview/layer_view_type")))); + self.setSimulationViewType(int(float(Application.getInstance().getPreferences().getValue("layerview/layer_view_type")))); - for extruder_nr, extruder_opacity in enumerate(Preferences.getInstance().getValue("layerview/extruder_opacities").split("|")): + for extruder_nr, extruder_opacity in enumerate(Application.getInstance().getPreferences().getValue("layerview/extruder_opacities").split("|")): try: opacity = float(extruder_opacity) except ValueError: opacity = 1.0 self.setExtruderOpacity(extruder_nr, opacity) - self.setShowTravelMoves(bool(Preferences.getInstance().getValue("layerview/show_travel_moves"))) - self.setShowHelpers(bool(Preferences.getInstance().getValue("layerview/show_helpers"))) - self.setShowSkin(bool(Preferences.getInstance().getValue("layerview/show_skin"))) - self.setShowInfill(bool(Preferences.getInstance().getValue("layerview/show_infill"))) + self.setShowTravelMoves(bool(Application.getInstance().getPreferences().getValue("layerview/show_travel_moves"))) + self.setShowHelpers(bool(Application.getInstance().getPreferences().getValue("layerview/show_helpers"))) + self.setShowSkin(bool(Application.getInstance().getPreferences().getValue("layerview/show_skin"))) + self.setShowInfill(bool(Application.getInstance().getPreferences().getValue("layerview/show_infill"))) self._startUpdateTopLayers() self.preferencesChanged.emit() diff --git a/plugins/SliceInfoPlugin/SliceInfo.py b/plugins/SliceInfoPlugin/SliceInfo.py index 82e07da464..d32e512e4f 100755 --- a/plugins/SliceInfoPlugin/SliceInfo.py +++ b/plugins/SliceInfoPlugin/SliceInfo.py @@ -10,7 +10,6 @@ from PyQt5.QtCore import pyqtSlot, QObject from UM.Extension import Extension from UM.Application import Application -from UM.Preferences import Preferences from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator from UM.Message import Message from UM.i18n import i18nCatalog @@ -34,13 +33,13 @@ class SliceInfo(QObject, Extension): QObject.__init__(self, parent) Extension.__init__(self) Application.getInstance().getOutputDeviceManager().writeStarted.connect(self._onWriteStarted) - Preferences.getInstance().addPreference("info/send_slice_info", True) - Preferences.getInstance().addPreference("info/asked_send_slice_info", False) + Application.getInstance().getPreferences().addPreference("info/send_slice_info", True) + Application.getInstance().getPreferences().addPreference("info/asked_send_slice_info", False) self._more_info_dialog = None self._example_data_content = None - if not Preferences.getInstance().getValue("info/asked_send_slice_info"): + if not Application.getInstance().getPreferences().getValue("info/asked_send_slice_info"): self.send_slice_info_message = Message(catalog.i18nc("@info", "Cura collects anonymized usage statistics."), lifetime = 0, dismissable = False, @@ -62,7 +61,7 @@ class SliceInfo(QObject, Extension): ## Perform action based on user input. # Note that clicking "Disable" won't actually disable the data sending, but rather take the user to preferences where they can disable it. def messageActionTriggered(self, message_id, action_id): - Preferences.getInstance().setValue("info/asked_send_slice_info", True) + Application.getInstance().getPreferences().setValue("info/asked_send_slice_info", True) if action_id == "MoreInfo": self.showMoreInfoDialog() self.send_slice_info_message.hide() @@ -88,11 +87,11 @@ class SliceInfo(QObject, Extension): @pyqtSlot(bool) def setSendSliceInfo(self, enabled: bool): - Preferences.getInstance().setValue("info/send_slice_info", enabled) + Application.getInstance().getPreferences().setValue("info/send_slice_info", enabled) def _onWriteStarted(self, output_device): try: - if not Preferences.getInstance().getValue("info/send_slice_info"): + if not Application.getInstance().getPreferences().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 @@ -107,7 +106,7 @@ class SliceInfo(QObject, Extension): data["schema_version"] = 0 data["cura_version"] = application.getVersion() - active_mode = Preferences.getInstance().getValue("cura/active_mode") + active_mode = Application.getInstance().getPreferences().getValue("cura/active_mode") if active_mode == 0: data["active_mode"] = "recommended" else: @@ -122,7 +121,7 @@ class SliceInfo(QObject, Extension): machine_settings_changed_by_user = True data["machine_settings_changed_by_user"] = machine_settings_changed_by_user - data["language"] = Preferences.getInstance().getValue("general/language") + data["language"] = Application.getInstance().getPreferences().getValue("general/language") data["os"] = {"type": platform.system(), "version": platform.version()} data["active_machine"] = {"definition_id": global_stack.definition.getId(), diff --git a/plugins/SolidView/SolidView.py b/plugins/SolidView/SolidView.py index 8892ddb138..b9ad5c8829 100644 --- a/plugins/SolidView/SolidView.py +++ b/plugins/SolidView/SolidView.py @@ -6,7 +6,6 @@ from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator from UM.Scene.Selection import Selection from UM.Resources import Resources from UM.Application import Application -from UM.Preferences import Preferences from UM.View.RenderBatch import RenderBatch from UM.Settings.Validator import ValidatorState from UM.Math.Color import Color @@ -23,7 +22,7 @@ class SolidView(View): def __init__(self): super().__init__() - Preferences.getInstance().addPreference("view/show_overhang", True) + Application.getInstance().getPreferences().addPreference("view/show_overhang", True) self._enabled_shader = None self._disabled_shader = None @@ -65,7 +64,7 @@ class SolidView(View): support_extruder_nr = global_container_stack.getExtruderPositionValueWithDefault("support_extruder_nr") support_angle_stack = Application.getInstance().getExtruderManager().getExtruderStack(support_extruder_nr) - if support_angle_stack is not None and Preferences.getInstance().getValue("view/show_overhang"): + if support_angle_stack is not None and Application.getInstance().getPreferences().getValue("view/show_overhang"): angle = support_angle_stack.getProperty("support_angle", "value") # Make sure the overhang angle is valid before passing it to the shader # Note: if the overhang angle is set to its default value, it does not need to get validated (validationState = None) diff --git a/plugins/UM3NetworkPrinting/UM3OutputDevicePlugin.py b/plugins/UM3NetworkPrinting/UM3OutputDevicePlugin.py index 2add388db6..ac011a5557 100644 --- a/plugins/UM3NetworkPrinting/UM3OutputDevicePlugin.py +++ b/plugins/UM3NetworkPrinting/UM3OutputDevicePlugin.py @@ -5,7 +5,6 @@ from UM.OutputDevice.OutputDevicePlugin import OutputDevicePlugin from UM.Logger import Logger from UM.Application import Application from UM.Signal import Signal, signalemitter -from UM.Preferences import Preferences from UM.Version import Version from . import ClusterUM3OutputDevice, LegacyUM3OutputDevice @@ -54,7 +53,7 @@ class UM3OutputDevicePlugin(OutputDevicePlugin): self._cluster_api_prefix = "/cluster-api/v" + self._cluster_api_version + "/" # Get list of manual instances from preferences - self._preferences = Preferences.getInstance() + self._preferences = Application.getInstance().getPreferences() self._preferences.addPreference("um3networkprinting/manual_instances", "") # A comma-separated list of ip adresses or hostnames diff --git a/plugins/UserAgreement/UserAgreement.py b/plugins/UserAgreement/UserAgreement.py index 915c1761fa..30b2c00f08 100644 --- a/plugins/UserAgreement/UserAgreement.py +++ b/plugins/UserAgreement/UserAgreement.py @@ -2,7 +2,6 @@ # Cura is released under the terms of the LGPLv3 or higher. from UM.Extension import Extension -from UM.Preferences import Preferences from UM.Application import Application from UM.PluginRegistry import PluginRegistry from UM.Logger import Logger @@ -19,10 +18,10 @@ class UserAgreement(QObject, Extension): self._user_agreement_window = None self._user_agreement_context = None Application.getInstance().engineCreatedSignal.connect(self._onEngineCreated) - Preferences.getInstance().addPreference("general/accepted_user_agreement", False) + Application.getInstance().getPreferences().addPreference("general/accepted_user_agreement", False) def _onEngineCreated(self): - if not Preferences.getInstance().getValue("general/accepted_user_agreement"): + if not Application.getInstance().getPreferences().getValue("general/accepted_user_agreement"): self.showUserAgreement() def showUserAgreement(self): @@ -35,11 +34,11 @@ class UserAgreement(QObject, Extension): def didAgree(self, user_choice): if user_choice: Logger.log("i", "User agreed to the user agreement") - Preferences.getInstance().setValue("general/accepted_user_agreement", True) + Application.getInstance().getPreferences().setValue("general/accepted_user_agreement", True) self._user_agreement_window.hide() else: Logger.log("i", "User did NOT agree to the user agreement") - Preferences.getInstance().setValue("general/accepted_user_agreement", False) + Application.getInstance().getPreferences().setValue("general/accepted_user_agreement", False) CuraApplication.getInstance().quit() CuraApplication.getInstance().setNeedToShowUserAgreement(False) From 67ce55049bc3f0cf6b06eb9a2162b787a38cb7fb Mon Sep 17 00:00:00 2001 From: Diego Prado Gesto Date: Wed, 16 May 2018 10:26:13 +0200 Subject: [PATCH 04/12] CURA-5164 Fix a bug when open the discard or keep dialog. It was using a non-existing variable. --- cura/CuraApplication.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index b629332f47..667390b946 100755 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -555,7 +555,7 @@ class CuraApplication(QtApplication): elif choice == "always_keep": # don't show dialog and KEEP the profile self.discardOrKeepProfileChangesClosed("keep") - elif self._use_gui: + elif not self._is_headless: # ALWAYS ask whether to keep or discard the profile self.showDiscardOrKeepProfileChanges.emit() has_user_interaction = True From 1bec6d7aa57ae91611d2387dc43e33a9659cb179 Mon Sep 17 00:00:00 2001 From: Diego Prado Gesto Date: Wed, 16 May 2018 17:26:56 +0200 Subject: [PATCH 05/12] CURA-5164 Fix types --- cura/CuraPackageManager.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cura/CuraPackageManager.py b/cura/CuraPackageManager.py index 0c2c438fcc..6fe3840679 100644 --- a/cura/CuraPackageManager.py +++ b/cura/CuraPackageManager.py @@ -26,8 +26,8 @@ class CuraPackageManager(QObject): self._plugin_registry = self._application.getPluginRegistry() #JSON files that keep track of all installed packages. - self._user_package_management_file_path = None - self._bundled_package_management_file_path = None + self._user_package_management_file_path = None #type: str + self._bundled_package_management_file_path = None #type: str for search_path in Resources.getSearchPaths(): candidate_bundled_path = os.path.join(search_path, "bundled_packages.json") if os.path.exists(candidate_bundled_path): From 39768e29cc10e09c1eb3fbb304739b1d4ef162ca Mon Sep 17 00:00:00 2001 From: Diego Prado Gesto Date: Wed, 23 May 2018 12:12:58 +0200 Subject: [PATCH 06/12] CURA-5164 First add the resources and then initialize the package manager. --- cura/CuraApplication.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index e40efceeb3..80cf7a3d74 100755 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -262,11 +262,6 @@ class CuraApplication(QtApplication): def initialize(self) -> None: super().initialize() - # Initialize the package manager to remove and install scheduled packages. - from cura.CuraPackageManager import CuraPackageManager - self._cura_package_manager = CuraPackageManager(self) - self._cura_package_manager.initialize() - self.__sendCommandToSingleInstance() self.__addExpectedResourceDirsAndSearchPaths() self.__initializeSettingDefinitionsAndFunctions() @@ -274,6 +269,11 @@ class CuraApplication(QtApplication): self.__addAllEmptyContainers() self.__setLatestResouceVersionsForVersionUpgrade() + # Initialize the package manager to remove and install scheduled packages. + from cura.CuraPackageManager import CuraPackageManager + self._cura_package_manager = CuraPackageManager(self) + self._cura_package_manager.initialize() + self._machine_action_manager = MachineActionManager.MachineActionManager(self) self._machine_action_manager.initialize() From ddd7b056f387c9bb1a68e4952deac5b783ef723e Mon Sep 17 00:00:00 2001 From: Diego Prado Gesto Date: Wed, 23 May 2018 17:17:41 +0200 Subject: [PATCH 07/12] CURA-5164 Make the mesh readers compliant with the new signature of the mesh reader. Also adapt the tests. --- plugins/3MFReader/ThreeMFReader.py | 6 +++--- plugins/3MFReader/__init__.py | 2 +- plugins/GCodeGzReader/GCodeGzReader.py | 6 +++--- plugins/GCodeGzReader/__init__.py | 2 +- plugins/GCodeReader/GCodeReader.py | 6 +++--- plugins/GCodeReader/__init__.py | 2 +- plugins/ImageReader/ImageReader.py | 6 +++--- plugins/ImageReader/__init__.py | 2 +- plugins/X3DReader/X3DReader.py | 6 +++--- plugins/X3DReader/__init__.py | 2 +- 10 files changed, 20 insertions(+), 20 deletions(-) diff --git a/plugins/3MFReader/ThreeMFReader.py b/plugins/3MFReader/ThreeMFReader.py index 9eec09b202..2ecea82691 100755 --- a/plugins/3MFReader/ThreeMFReader.py +++ b/plugins/3MFReader/ThreeMFReader.py @@ -44,8 +44,8 @@ except ImportError: ## Base implementation for reading 3MF files. Has no support for textures. Only loads meshes! class ThreeMFReader(MeshReader): - def __init__(self): - super().__init__() + def __init__(self, application): + super().__init__(application) self._supported_extensions = [".3mf"] self._root = None self._base_name = "" @@ -158,7 +158,7 @@ class ThreeMFReader(MeshReader): um_node.addDecorator(sliceable_decorator) return um_node - def read(self, file_name): + def _read(self, file_name): result = [] self._object_count = 0 # Used to name objects as there is no node name yet. # The base object of 3mf is a zipped archive. diff --git a/plugins/3MFReader/__init__.py b/plugins/3MFReader/__init__.py index 9da54586a3..ddaf76e01e 100644 --- a/plugins/3MFReader/__init__.py +++ b/plugins/3MFReader/__init__.py @@ -42,7 +42,7 @@ def getMetaData() -> Dict: def register(app): if "3MFReader.ThreeMFReader" in sys.modules: - return {"mesh_reader": ThreeMFReader.ThreeMFReader(), + return {"mesh_reader": ThreeMFReader.ThreeMFReader(app), "workspace_reader": ThreeMFWorkspaceReader.ThreeMFWorkspaceReader()} else: return {} diff --git a/plugins/GCodeGzReader/GCodeGzReader.py b/plugins/GCodeGzReader/GCodeGzReader.py index 86c0af89a2..7a6a76d4a5 100644 --- a/plugins/GCodeGzReader/GCodeGzReader.py +++ b/plugins/GCodeGzReader/GCodeGzReader.py @@ -12,11 +12,11 @@ from UM.PluginRegistry import PluginRegistry # If you're zipping g-code, you might as well use gzip! class GCodeGzReader(MeshReader): - def __init__(self): - super().__init__() + def __init__(self, application): + super().__init__(application) self._supported_extensions = [".gcode.gz"] - def read(self, file_name): + def _read(self, file_name): with open(file_name, "rb") as file: file_data = file.read() uncompressed_gcode = gzip.decompress(file_data).decode("utf-8") diff --git a/plugins/GCodeGzReader/__init__.py b/plugins/GCodeGzReader/__init__.py index 6e720b1ed1..e6bae6615e 100644 --- a/plugins/GCodeGzReader/__init__.py +++ b/plugins/GCodeGzReader/__init__.py @@ -21,4 +21,4 @@ def getMetaData(): def register(app): app.addNonSliceableExtension(".gz") - return { "mesh_reader": GCodeGzReader.GCodeGzReader() } + return { "mesh_reader": GCodeGzReader.GCodeGzReader(app) } diff --git a/plugins/GCodeReader/GCodeReader.py b/plugins/GCodeReader/GCodeReader.py index a576d6b49f..c51fc9c8a7 100755 --- a/plugins/GCodeReader/GCodeReader.py +++ b/plugins/GCodeReader/GCodeReader.py @@ -27,8 +27,8 @@ class GCodeReader(MeshReader): _flavor_readers_dict = {"RepRap" : RepRapFlavorParser.RepRapFlavorParser(), "Marlin" : MarlinFlavorParser.MarlinFlavorParser()} - def __init__(self): - super(GCodeReader, self).__init__() + def __init__(self, application): + super(GCodeReader, self).__init__(application) self._supported_extensions = [".gcode", ".g"] self._flavor_reader = None @@ -57,7 +57,7 @@ class GCodeReader(MeshReader): def readFromStream(self, stream): return self._flavor_reader.processGCodeStream(stream) - def read(self, file_name): + def _read(self, file_name): with open(file_name, "r", encoding = "utf-8") as file: file_data = file.read() return self.readFromStream(file_data) diff --git a/plugins/GCodeReader/__init__.py b/plugins/GCodeReader/__init__.py index 999dd37a37..399de8a90e 100644 --- a/plugins/GCodeReader/__init__.py +++ b/plugins/GCodeReader/__init__.py @@ -23,4 +23,4 @@ def getMetaData(): def register(app): app.addNonSliceableExtension(".gcode") app.addNonSliceableExtension(".g") - return { "mesh_reader": GCodeReader.GCodeReader() } + return { "mesh_reader": GCodeReader.GCodeReader(app) } diff --git a/plugins/ImageReader/ImageReader.py b/plugins/ImageReader/ImageReader.py index 3a98abccf5..89f163cfca 100644 --- a/plugins/ImageReader/ImageReader.py +++ b/plugins/ImageReader/ImageReader.py @@ -17,8 +17,8 @@ from cura.Scene.CuraSceneNode import CuraSceneNode as SceneNode class ImageReader(MeshReader): - def __init__(self): - super(ImageReader, self).__init__() + def __init__(self, application): + super(ImageReader, self).__init__(application) self._supported_extensions = [".jpg", ".jpeg", ".bmp", ".gif", ".png"] self._ui = ImageReaderUI(self) @@ -44,7 +44,7 @@ class ImageReader(MeshReader): return MeshReader.PreReadResult.cancelled return MeshReader.PreReadResult.accepted - def read(self, file_name): + def _read(self, file_name): size = max(self._ui.getWidth(), self._ui.getDepth()) return self._generateSceneNode(file_name, size, self._ui.peak_height, self._ui.base_height, self._ui.smoothing, 512, self._ui.image_color_invert) diff --git a/plugins/ImageReader/__init__.py b/plugins/ImageReader/__init__.py index ea6ff8cceb..f30fdb83b0 100644 --- a/plugins/ImageReader/__init__.py +++ b/plugins/ImageReader/__init__.py @@ -33,4 +33,4 @@ def getMetaData(): } def register(app): - return { "mesh_reader": ImageReader.ImageReader() } + return { "mesh_reader": ImageReader.ImageReader(app) } diff --git a/plugins/X3DReader/X3DReader.py b/plugins/X3DReader/X3DReader.py index b0b9e00a5b..e57ec524db 100644 --- a/plugins/X3DReader/X3DReader.py +++ b/plugins/X3DReader/X3DReader.py @@ -38,14 +38,14 @@ class Shape: self.name = name class X3DReader(MeshReader): - def __init__(self): - super().__init__() + def __init__(self, application): + super().__init__(application) self._supported_extensions = [".x3d"] self._namespaces = {} # Main entry point # Reads the file, returns a SceneNode (possibly with nested ones), or None - def read(self, file_name): + def _read(self, file_name): try: self.defs = {} self.shapes = [] diff --git a/plugins/X3DReader/__init__.py b/plugins/X3DReader/__init__.py index 9e0e2df91c..03ed4ae301 100644 --- a/plugins/X3DReader/__init__.py +++ b/plugins/X3DReader/__init__.py @@ -16,4 +16,4 @@ def getMetaData(): } def register(app): - return { "mesh_reader": X3DReader.X3DReader() } + return { "mesh_reader": X3DReader.X3DReader(app) } From 74ef90694957b4cb68de722342b98dd2b9ee878c Mon Sep 17 00:00:00 2001 From: Diego Prado Gesto Date: Fri, 25 May 2018 10:15:04 +0200 Subject: [PATCH 08/12] CURA-5164 Reuse method savePreferences --- cura/CuraApplication.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index 70c4d7727a..5b1d557f49 100755 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -10,7 +10,6 @@ import time import numpy from PyQt5.QtCore import QObject, QTimer, QUrl, pyqtSignal, pyqtProperty, QEvent, Q_ENUMS -from PyQt5.QtNetwork import QLocalServer from PyQt5.QtGui import QColor, QIcon from PyQt5.QtWidgets import QMessageBox from PyQt5.QtQml import qmlRegisterUncreatableType, qmlRegisterSingletonType, qmlRegisterType @@ -597,8 +596,7 @@ class CuraApplication(QtApplication): # Do not do saving during application start or when data should not be safed on quit. return ContainerRegistry.getInstance().saveDirtyContainers() - Preferences.getInstance().writeToFile(Resources.getStoragePath(Resources.Preferences, - self._application_name + ".cfg")) + self.savePreferences() def saveStack(self, stack): ContainerRegistry.getInstance().saveContainer(stack) From 86d4f0583cccb869978084a67c6093267c2356fc Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Wed, 30 May 2018 13:15:32 +0200 Subject: [PATCH 09/12] Make showing of support density image dependent on support pattern Because that's what it's actually depending on. Contributes to issue CURA-4513. --- resources/definitions/fdmprinter.def.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/definitions/fdmprinter.def.json b/resources/definitions/fdmprinter.def.json index 5ad478bfc3..25fe841ee4 100644 --- a/resources/definitions/fdmprinter.def.json +++ b/resources/definitions/fdmprinter.def.json @@ -5812,7 +5812,7 @@ "description": "The file location of an image of which the brightness values determine the minimal density at the corresponding location in the support.", "type": "str", "default_value": "", - "enabled": "infill_pattern == 'cross' or infill_pattern == 'cross_3d'", + "enabled": "support_pattern == 'cross' or support_pattern == 'cross_3d'", "limit_to_extruder": "support_infill_extruder_nr", "settable_per_mesh": false, "settable_per_extruder": true From 00fe18008586fb33aeed1df9dc684f5f5d35d113 Mon Sep 17 00:00:00 2001 From: Ian Paschal Date: Wed, 30 May 2018 16:29:55 +0200 Subject: [PATCH 10/12] Hide Toolbox Showcase & Materials for 3.4 --- plugins/Toolbox/resources/qml/ToolboxDownloadsPage.qml | 4 ++++ plugins/Toolbox/resources/qml/ToolboxHeader.qml | 3 +++ 2 files changed, 7 insertions(+) diff --git a/plugins/Toolbox/resources/qml/ToolboxDownloadsPage.qml b/plugins/Toolbox/resources/qml/ToolboxDownloadsPage.qml index 170fd10fc7..69e508cd55 100644 --- a/plugins/Toolbox/resources/qml/ToolboxDownloadsPage.qml +++ b/plugins/Toolbox/resources/qml/ToolboxDownloadsPage.qml @@ -18,6 +18,8 @@ ScrollView spacing: UM.Theme.getSize("default_margin").height padding: UM.Theme.getSize("wide_margin").height height: childrenRect.height + 2 * padding + + /* Hide for 3.4 ToolboxDownloadsShowcase { id: showcase @@ -29,6 +31,8 @@ ScrollView width: parent.width height: UM.Theme.getSize("default_lining").height } + */ + ToolboxDownloadsGrid { id: allPlugins diff --git a/plugins/Toolbox/resources/qml/ToolboxHeader.qml b/plugins/Toolbox/resources/qml/ToolboxHeader.qml index 88495e3f63..ee4241beaf 100644 --- a/plugins/Toolbox/resources/qml/ToolboxHeader.qml +++ b/plugins/Toolbox/resources/qml/ToolboxHeader.qml @@ -33,6 +33,8 @@ Item toolbox.viewPage = "overview" } } + + /* Hide for 3.4 ToolboxTabButton { text: catalog.i18nc("@title:tab", "Materials") @@ -45,6 +47,7 @@ Item toolbox.viewPage = "overview" } } + */ } ToolboxTabButton { From a468fc2cc4a42778c137b11029b8dfb3bfc8032c Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Thu, 31 May 2018 12:21:51 +0200 Subject: [PATCH 11/12] Only load loading.gif when needed Toolbox crashes on Mac OS X due to reaching the max limit of file handlers. This seems to be caused by the loading the "loading.gif" animation image. Probably because many widgets are created and each of them has an animation image, and Qt (5.8?) seems to load everything at the same time. --- plugins/Toolbox/resources/qml/ToolboxProgressButton.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/Toolbox/resources/qml/ToolboxProgressButton.qml b/plugins/Toolbox/resources/qml/ToolboxProgressButton.qml index b598bd96d0..2744e40ec9 100644 --- a/plugins/Toolbox/resources/qml/ToolboxProgressButton.qml +++ b/plugins/Toolbox/resources/qml/ToolboxProgressButton.qml @@ -150,7 +150,7 @@ Item { id: loader visible: active - source: "../images/loading.gif" + source: visible ? "../images/loading.gif" : "" width: UM.Theme.getSize("toolbox_loader").width height: UM.Theme.getSize("toolbox_loader").height anchors.right: button.left From b3ef1bfd51e33ee91dd8047b33050e8ec2dc8bef Mon Sep 17 00:00:00 2001 From: Lipu Fei Date: Thu, 31 May 2018 14:29:56 +0200 Subject: [PATCH 12/12] Simplify UserAgreement --- plugins/UserAgreement/UserAgreement.py | 33 +++++++++++++------------- plugins/UserAgreement/__init__.py | 2 +- 2 files changed, 17 insertions(+), 18 deletions(-) diff --git a/plugins/UserAgreement/UserAgreement.py b/plugins/UserAgreement/UserAgreement.py index 30b2c00f08..4ea1ccf9bb 100644 --- a/plugins/UserAgreement/UserAgreement.py +++ b/plugins/UserAgreement/UserAgreement.py @@ -1,27 +1,26 @@ # Copyright (c) 2017 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. -from UM.Extension import Extension -from UM.Application import Application -from UM.PluginRegistry import PluginRegistry -from UM.Logger import Logger - -from cura.CuraApplication import CuraApplication +import os from PyQt5.QtCore import QObject, pyqtSlot -import os.path +from UM.Extension import Extension +from UM.Logger import Logger + class UserAgreement(QObject, Extension): - def __init__(self): + def __init__(self, application): super(UserAgreement, self).__init__() + self._application = application self._user_agreement_window = None self._user_agreement_context = None - Application.getInstance().engineCreatedSignal.connect(self._onEngineCreated) - Application.getInstance().getPreferences().addPreference("general/accepted_user_agreement", False) + self._application.engineCreatedSignal.connect(self._onEngineCreated) + + self._application.getPreferences().addPreference("general/accepted_user_agreement", False) def _onEngineCreated(self): - if not Application.getInstance().getPreferences().getValue("general/accepted_user_agreement"): + if not self._application.getPreferences().getValue("general/accepted_user_agreement"): self.showUserAgreement() def showUserAgreement(self): @@ -34,14 +33,14 @@ class UserAgreement(QObject, Extension): def didAgree(self, user_choice): if user_choice: Logger.log("i", "User agreed to the user agreement") - Application.getInstance().getPreferences().setValue("general/accepted_user_agreement", True) + self._application.getPreferences().setValue("general/accepted_user_agreement", True) self._user_agreement_window.hide() else: Logger.log("i", "User did NOT agree to the user agreement") - Application.getInstance().getPreferences().setValue("general/accepted_user_agreement", False) - CuraApplication.getInstance().quit() - CuraApplication.getInstance().setNeedToShowUserAgreement(False) + self._application.getPreferences().setValue("general/accepted_user_agreement", False) + self._application.quit() + self._application.setNeedToShowUserAgreement(False) def createUserAgreementWindow(self): - path = os.path.join(PluginRegistry.getInstance().getPluginPath(self.getPluginId()), "UserAgreement.qml") - self._user_agreement_window = Application.getInstance().createQmlComponent(path, {"manager": self}) + path = os.path.join(self._application.getPluginRegistry().getPluginPath(self.getPluginId()), "UserAgreement.qml") + self._user_agreement_window = self._application.createQmlComponent(path, {"manager": self}) diff --git a/plugins/UserAgreement/__init__.py b/plugins/UserAgreement/__init__.py index 88cb151f9e..3cf81c64f4 100644 --- a/plugins/UserAgreement/__init__.py +++ b/plugins/UserAgreement/__init__.py @@ -7,4 +7,4 @@ def getMetaData(): return {} def register(app): - return {"extension": UserAgreement.UserAgreement()} + return {"extension": UserAgreement.UserAgreement(app)}