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.
This commit is contained in:
Lipu Fei 2018-04-30 16:47:14 +02:00
parent c8f73d303e
commit 051dd7a6e9
14 changed files with 521 additions and 471 deletions

View file

@ -1,14 +1,16 @@
# Copyright (c) 2018 Ultimaker B.V. # Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher. # 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.i18n import i18nCatalog
from UM.Scene.Platform import Platform from UM.Scene.Platform import Platform
from UM.Scene.Iterator.BreadthFirstIterator import BreadthFirstIterator from UM.Scene.Iterator.BreadthFirstIterator import BreadthFirstIterator
from UM.Scene.SceneNode import SceneNode from UM.Scene.SceneNode import SceneNode
from UM.Application import Application
from UM.Resources import Resources from UM.Resources import Resources
from UM.Mesh.MeshBuilder import MeshBuilder from UM.Mesh.MeshBuilder import MeshBuilder
from UM.Math.Vector import Vector from UM.Math.Vector import Vector
@ -18,16 +20,14 @@ from UM.Math.AxisAlignedBox import AxisAlignedBox
from UM.Math.Polygon import Polygon from UM.Math.Polygon import Polygon
from UM.Message import Message from UM.Message import Message
from UM.Signal import Signal from UM.Signal import Signal
from PyQt5.QtCore import QTimer
from UM.View.RenderBatch import RenderBatch from UM.View.RenderBatch import RenderBatch
from UM.View.GL.OpenGL import OpenGL from UM.View.GL.OpenGL import OpenGL
from cura.Scene.CuraSceneNode import CuraSceneNode
from cura.Settings.ExtruderManager import ExtruderManager
catalog = i18nCatalog("cura") catalog = i18nCatalog("cura")
import numpy
import math
from typing import List, Optional
# Setting for clearance around the prime # Setting for clearance around the prime
PRIME_CLEARANCE = 6.5 PRIME_CLEARANCE = 6.5
@ -36,8 +36,10 @@ PRIME_CLEARANCE = 6.5
class BuildVolume(SceneNode): class BuildVolume(SceneNode):
raftThicknessChanged = Signal() raftThicknessChanged = Signal()
def __init__(self, parent = None): def __init__(self, application, parent = None):
super().__init__(parent) super().__init__(parent)
self._application = application
self._machine_manager = self._application.getMachineManager()
self._volume_outline_color = None self._volume_outline_color = None
self._x_axis_color = None self._x_axis_color = None
@ -80,14 +82,14 @@ class BuildVolume(SceneNode):
" with printed models."), title = catalog.i18nc("@info:title", "Build Volume")) " with printed models."), title = catalog.i18nc("@info:title", "Build Volume"))
self._global_container_stack = None self._global_container_stack = None
Application.getInstance().globalContainerStackChanged.connect(self._onStackChanged) self._application.globalContainerStackChanged.connect(self._onStackChanged)
self._onStackChanged() self._onStackChanged()
self._engine_ready = False self._engine_ready = False
Application.getInstance().engineCreatedSignal.connect(self._onEngineCreated) self._application.engineCreatedSignal.connect(self._onEngineCreated)
self._has_errors = False 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. #Objects loaded at the moment. We are connected to the property changed events of these objects.
self._scene_objects = set() self._scene_objects = set()
@ -105,14 +107,14 @@ class BuildVolume(SceneNode):
# Must be after setting _build_volume_message, apparently that is used in getMachineManager. # Must be after setting _build_volume_message, apparently that is used in getMachineManager.
# activeQualityChanged is always emitted after setActiveVariant, setActiveMaterial and setActiveQuality. # activeQualityChanged is always emitted after setActiveVariant, setActiveMaterial and setActiveQuality.
# Therefore this works. # 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, # This should also ways work, and it is semantically more correct,
# but it does not update the disallowed areas after material change # 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 # Enable and disable extruder
Application.getInstance().getMachineManager().extruderChanged.connect(self.updateNodeBoundaryCheck) self._machine_manager.extruderChanged.connect(self.updateNodeBoundaryCheck)
# list of settings which were updated # list of settings which were updated
self._changed_settings_since_last_rebuild = [] self._changed_settings_since_last_rebuild = []
@ -122,7 +124,7 @@ class BuildVolume(SceneNode):
self._scene_change_timer.start() self._scene_change_timer.start()
def _onSceneChangeTimerFinished(self): 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")) new_scene_objects = set(node for node in BreadthFirstIterator(root) if node.callDecoration("isSliceable"))
if new_scene_objects != self._scene_objects: if new_scene_objects != self._scene_objects:
for node in new_scene_objects - self._scene_objects: #Nodes that were added to the scene. 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: if not self._shader:
self._shader = OpenGL.getInstance().createShaderProgram(Resources.getPath(Resources.Shaders, "default.shader")) self._shader = OpenGL.getInstance().createShaderProgram(Resources.getPath(Resources.Shaders, "default.shader"))
self._grid_shader = OpenGL.getInstance().createShaderProgram(Resources.getPath(Resources.Shaders, "grid.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_plateColor", Color(*theme.getColor("buildplate").getRgb()))
self._grid_shader.setUniformValue("u_gridColor0", Color(*theme.getColor("buildplate_grid").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())) 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 ## For every sliceable node, update node._outside_buildarea
# #
def updateNodeBoundaryCheck(self): def updateNodeBoundaryCheck(self):
root = Application.getInstance().getController().getScene().getRoot() root = self._application.getController().getScene().getRoot()
nodes = list(BreadthFirstIterator(root)) nodes = list(BreadthFirstIterator(root))
group_nodes = [] group_nodes = []
@ -289,11 +291,11 @@ class BuildVolume(SceneNode):
if not self._width or not self._height or not self._depth: if not self._width or not self._height or not self._depth:
return return
if not Application.getInstance()._engine: if not self._application._qml_engine:
return return
if not self._volume_outline_color: 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._volume_outline_color = Color(*theme.getColor("volume_outline").getRgb())
self._x_axis_color = Color(*theme.getColor("x_axis").getRgb()) self._x_axis_color = Color(*theme.getColor("x_axis").getRgb())
self._y_axis_color = Color(*theme.getColor("y_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) 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() self.updateNodeBoundaryCheck()
@ -518,7 +520,7 @@ class BuildVolume(SceneNode):
for extruder in extruders: for extruder in extruders:
extruder.propertyChanged.disconnect(self._onSettingPropertyChanged) extruder.propertyChanged.disconnect(self._onSettingPropertyChanged)
self._global_container_stack = Application.getInstance().getGlobalContainerStack() self._global_container_stack = self._application.getGlobalContainerStack()
if self._global_container_stack: if self._global_container_stack:
self._global_container_stack.propertyChanged.connect(self._onSettingPropertyChanged) self._global_container_stack.propertyChanged.connect(self._onSettingPropertyChanged)
@ -561,7 +563,7 @@ class BuildVolume(SceneNode):
if setting_key == "print_sequence": if setting_key == "print_sequence":
machine_height = self._global_container_stack.getProperty("machine_height", "value") 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) self._height = min(self._global_container_stack.getProperty("gantry_height", "value"), machine_height)
if self._height < machine_height: if self._height < machine_height:
self._build_volume_message.show() self._build_volume_message.show()

View file

@ -12,7 +12,6 @@ from UM.Scene.Selection import Selection
from UM.Scene.Iterator.BreadthFirstIterator import BreadthFirstIterator from UM.Scene.Iterator.BreadthFirstIterator import BreadthFirstIterator
from UM.Operations.GroupedOperation import GroupedOperation from UM.Operations.GroupedOperation import GroupedOperation
from UM.Operations.RemoveSceneNodeOperation import RemoveSceneNodeOperation from UM.Operations.RemoveSceneNodeOperation import RemoveSceneNodeOperation
from UM.Operations.SetTransformOperation import SetTransformOperation
from UM.Operations.TranslateOperation import TranslateOperation from UM.Operations.TranslateOperation import TranslateOperation
from cura.Operations.SetParentOperation import SetParentOperation from cura.Operations.SetParentOperation import SetParentOperation

View file

@ -1,9 +1,19 @@
# Copyright (c) 2018 Ultimaker B.V. # Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher. # 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 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.Qt.QtApplication import QtApplication
from UM.Scene.SceneNode import SceneNode from UM.Scene.SceneNode import SceneNode
@ -74,6 +84,7 @@ from cura.Settings.SimpleModeSettingsManager import SimpleModeSettingsManager
from cura.Machines.VariantManager import VariantManager from cura.Machines.VariantManager import VariantManager
from .SingleInstance import SingleInstance
from . import PlatformPhysics from . import PlatformPhysics
from . import BuildVolume from . import BuildVolume
from . import CameraAnimation from . import CameraAnimation
@ -93,22 +104,10 @@ from cura.Settings.ContainerManager import ContainerManager
from cura.ObjectsModel import ObjectsModel from cura.ObjectsModel import ObjectsModel
from PyQt5.QtCore import QUrl, pyqtSignal, pyqtProperty, QEvent, Q_ENUMS
from UM.FlameProfiler import pyqtSlot 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 MYPY = False
if not MYPY: if not MYPY:
@ -143,19 +142,164 @@ class CuraApplication(QtApplication):
Q_ENUMS(ResourceTypes) 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._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 # 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"]: for dir_name in ["extruders", "machine_instances", "materials", "plugins", "quality", "quality_changes", "user", "variants"]:
Resources.addExpectedDirNameInData(dir_name) 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"): if not hasattr(sys, "frozen"):
Resources.addSearchPath(os.path.join(os.path.abspath(os.path.dirname(__file__)), "..", "resources")) Resources.addSearchPath(os.path.join(os.path.abspath(os.path.dirname(__file__)), "..", "resources"))
self._use_gui = True # Adds custom property types, settings types, and extra operators (functions) that need to be registered in
self._open_file_queue = [] # Files to open when plug-ins are loaded. # SettingDefinition and SettingFunction.
def __initializeSettingDefinitionsAndFunctions(self):
# Need to do this before ContainerRegistry tries to load the machines # 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_mesh", DefinitionPropertyType.Any, default = True, read_only = True)
SettingDefinition.addSupportedProperty("settable_per_extruder", 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("extruderValue", ExtruderManager.getExtruderValue)
SettingFunction.registerOperator("resolveOrValue", ExtruderManager.getResolveOrValue) 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.QualityInstanceContainer, "quality")
Resources.addStorageType(self.ResourceTypes.QualityChangesInstanceContainer, "quality_changes") Resources.addStorageType(self.ResourceTypes.QualityChangesInstanceContainer, "quality_changes")
Resources.addStorageType(self.ResourceTypes.VariantInstanceContainer, "variants") Resources.addStorageType(self.ResourceTypes.VariantInstanceContainer, "variants")
@ -191,20 +336,64 @@ class CuraApplication(QtApplication):
Resources.addStorageType(self.ResourceTypes.DefinitionChangesContainer, "definition_changes") Resources.addStorageType(self.ResourceTypes.DefinitionChangesContainer, "definition_changes")
Resources.addStorageType(self.ResourceTypes.SettingVisibilityPreset, "setting_visibility") Resources.addStorageType(self.ResourceTypes.SettingVisibilityPreset, "setting_visibility")
ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.QualityInstanceContainer, "quality") self._container_registry.addResourceType(self.ResourceTypes.QualityInstanceContainer, "quality")
ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.QualityChangesInstanceContainer, "quality_changes") self._container_registry.addResourceType(self.ResourceTypes.QualityChangesInstanceContainer, "quality_changes")
ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.VariantInstanceContainer, "variant") self._container_registry.addResourceType(self.ResourceTypes.VariantInstanceContainer, "variant")
ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.MaterialInstanceContainer, "material") self._container_registry.addResourceType(self.ResourceTypes.MaterialInstanceContainer, "material")
ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.UserInstanceContainer, "user") self._container_registry.addResourceType(self.ResourceTypes.UserInstanceContainer, "user")
ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.ExtruderStack, "extruder_train") self._container_registry.addResourceType(self.ResourceTypes.ExtruderStack, "extruder_train")
ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.MachineStack, "machine") self._container_registry.addResourceType(self.ResourceTypes.MachineStack, "machine")
ContainerRegistry.getInstance().addResourceType(self.ResourceTypes.DefinitionChangesContainer, "definition_changes") self._container_registry.addResourceType(self.ResourceTypes.DefinitionChangesContainer, "definition_changes")
## Initialise the version upgrade manager with Cura's storage paths. Resources.addType(self.ResourceTypes.QmlFiles, "qml")
# Needs to be here to prevent circular dependencies. Resources.addType(self.ResourceTypes.Firmware, "firmware")
import UM.VersionUpgradeManager
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"), ("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"), ("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 = [] # Runs preparations that needs to be done before the starting process.
self._non_sliceable_extensions = [] def startSlashWindowPhase(self):
super().startSlashWindowPhase()
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"
self.setWindowIcon(QIcon(Resources.getPath(Resources.Images, "cura-icon.png"))) self.setWindowIcon(QIcon(Resources.getPath(Resources.Images, "cura-icon.png")))
@ -289,23 +441,6 @@ class CuraApplication(QtApplication):
"SelectionTool", "SelectionTool",
"TranslateTool" "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._i18n_catalog = i18nCatalog("cura")
self._update_platform_activity_timer = QTimer() self._update_platform_activity_timer = QTimer()
@ -318,53 +453,10 @@ class CuraApplication(QtApplication):
self.getController().contextMenuRequested.connect(self._onContextMenuRequested) self.getController().contextMenuRequested.connect(self._onContextMenuRequested)
self.getCuraSceneController().activeBuildPlateChanged.connect(self.updatePlatformActivityDelayed) 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...")) self.showSplashMessage(self._i18n_catalog.i18nc("@info:progress", "Loading machines..."))
# Add empty variant, material and quality containers. with self._container_registry.lockFile():
# Since they are empty, they should never be serialized and instead just programmatically created. self._container_registry.loadAllMetadata()
# 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()
# set the setting version for Preferences # set the setting version for Preferences
preferences = Preferences.getInstance() preferences = Preferences.getInstance()
@ -411,13 +503,10 @@ class CuraApplication(QtApplication):
self.getCuraSceneController().setActiveBuildPlate(0) # Initialize 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 CuraApplication.Created = True
def _onEngineCreated(self): def _onEngineCreated(self):
self._engine.addImageProvider("camera", CameraImageProvider.CameraImageProvider()) self._qml_engine.addImageProvider("camera", CameraImageProvider.CameraImageProvider())
@pyqtProperty(bool) @pyqtProperty(bool)
def needToShowUserAgreement(self): def needToShowUserAgreement(self):
@ -515,10 +604,6 @@ class CuraApplication(QtApplication):
def setDefaultPath(self, key, default_path): def setDefaultPath(self, key, default_path):
Preferences.getInstance().setValue("local_file/%s" % key, QUrl(default_path).toLocalFile()) 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) ## Handle loading of all plugin types (and the backend explicitly)
# \sa PluginRegistry # \sa PluginRegistry
def _loadPlugins(self): def _loadPlugins(self):
@ -543,127 +628,8 @@ class CuraApplication(QtApplication):
self._plugins_loaded = True 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): def run(self):
self.preRun() container_registry = self._container_registry
container_registry = ContainerRegistry.getInstance()
Logger.log("i", "Initializing variant manager") Logger.log("i", "Initializing variant manager")
self._variant_manager = VariantManager(container_registry) self._variant_manager = VariantManager(container_registry)
@ -682,20 +648,25 @@ class CuraApplication(QtApplication):
Logger.log("i", "Initializing machine manager") Logger.log("i", "Initializing machine manager")
self._machine_manager = MachineManager(self) self._machine_manager = MachineManager(self)
Logger.log("i", "Initializing container manager")
self._container_manager = ContainerManager(self)
Logger.log("i", "Initializing machine error checker") Logger.log("i", "Initializing machine error checker")
self._machine_error_checker = MachineErrorChecker(self) self._machine_error_checker = MachineErrorChecker(self)
self._machine_error_checker.initialize() self._machine_error_checker.initialize()
# Check if we should run as single instance or not # Check if we should run as single instance or not. If so, set up a local socket server which listener which
self._setUpSingleInstanceServer() # coordinates multiple Cura instances and accepts commands.
if self._use_single_instance:
self.__setUpSingleInstanceServer()
# Setup scene and build volume # Setup scene and build volume
root = self.getController().getScene().getRoot() root = self.getController().getScene().getRoot()
self._volume = BuildVolume.BuildVolume(self.getController().getScene().getRoot()) self._volume = BuildVolume.BuildVolume(self, root)
Arrange.build_volume = self._volume Arrange.build_volume = self._volume
# initialize info objects # initialize info objects
self._print_information = PrintInformation.PrintInformation() self._print_information = PrintInformation.PrintInformation(self)
self._cura_actions = CuraActions.CuraActions(self) self._cura_actions = CuraActions.CuraActions(self)
# Initialize setting visibility presets model # Initialize setting visibility presets model
@ -704,7 +675,7 @@ class CuraApplication(QtApplication):
Preferences.getInstance().setDefault("general/visible_settings", ";".join(default_visibility_profile["settings"])) Preferences.getInstance().setDefault("general/visible_settings", ";".join(default_visibility_profile["settings"]))
# Detect in which mode to run and execute that mode # Detect in which mode to run and execute that mode
if self.getCommandLineOption("headless", False): if self._is_headless:
self.runWithoutGUI() self.runWithoutGUI()
else: else:
self.runWithGUI() self.runWithGUI()
@ -713,7 +684,6 @@ class CuraApplication(QtApplication):
self.initializationFinished.emit() self.initializationFinished.emit()
Logger.log("d", "Booting Cura took %s seconds", time.time() - self._boot_loading_time) 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 # 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 # initialized, for example opening files because they may show dialogs which can be closed due to incomplete
# GUI initialization. # GUI initialization.
@ -725,8 +695,12 @@ class CuraApplication(QtApplication):
self.exec_() self.exec_()
def __setUpSingleInstanceServer(self):
if self._use_single_instance:
self._single_instance.startServer()
def _onPostStart(self): def _onPostStart(self):
for file_name in self.getCommandLineOption("file", []): for file_name in self._files_to_open:
self.callLater(self._openFile, file_name) 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. 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) self.callLater(self._openFile, file_name)
@ -735,13 +709,10 @@ class CuraApplication(QtApplication):
## Run Cura without GUI elements and interaction (server mode). ## Run Cura without GUI elements and interaction (server mode).
def runWithoutGUI(self): def runWithoutGUI(self):
self._use_gui = False
self.closeSplash() self.closeSplash()
## Run Cura with GUI (desktop mode). ## Run Cura with GUI (desktop mode).
def runWithGUI(self): def runWithGUI(self):
self._use_gui = True
self.showSplashMessage(self._i18n_catalog.i18nc("@info:progress", "Setting up scene...")) self.showSplashMessage(self._i18n_catalog.i18nc("@info:progress", "Setting up scene..."))
controller = self.getController() controller = self.getController()
@ -791,9 +762,6 @@ class CuraApplication(QtApplication):
# Hide the splash screen # Hide the splash screen
self.closeSplash() self.closeSplash()
def hasGui(self):
return self._use_gui
@pyqtSlot(result = QObject) @pyqtSlot(result = QObject)
def getSettingVisibilityPresetsModel(self, *args) -> SettingVisibilityPresetsModel: def getSettingVisibilityPresetsModel(self, *args) -> SettingVisibilityPresetsModel:
return self._setting_visibility_presets_model return self._setting_visibility_presets_model
@ -808,7 +776,7 @@ class CuraApplication(QtApplication):
def getExtruderManager(self, *args): def getExtruderManager(self, *args):
if self._extruder_manager is None: if self._extruder_manager is None:
self._extruder_manager = ExtruderManager.createExtruderManager() self._extruder_manager = ExtruderManager()
return self._extruder_manager return self._extruder_manager
@pyqtSlot(result = QObject) @pyqtSlot(result = QObject)
@ -932,7 +900,7 @@ class CuraApplication(QtApplication):
qmlRegisterType(QualitySettingsModel, "Cura", 1, 0, "QualitySettingsModel") qmlRegisterType(QualitySettingsModel, "Cura", 1, 0, "QualitySettingsModel")
qmlRegisterType(MachineNameValidator, "Cura", 1, 0, "MachineNameValidator") qmlRegisterType(MachineNameValidator, "Cura", 1, 0, "MachineNameValidator")
qmlRegisterType(UserChangesModel, "Cura", 1, 0, "UserChangesModel") 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. # 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"))) actions_url = QUrl.fromLocalFile(os.path.abspath(Resources.getPath(CuraApplication.ResourceTypes.QmlFiles, "Actions.qml")))
@ -1520,8 +1488,7 @@ class CuraApplication(QtApplication):
# see GroupDecorator._onChildrenChanged # see GroupDecorator._onChildrenChanged
def _createSplashScreen(self): def _createSplashScreen(self):
run_headless = self.getCommandLineOption("headless", False) if self._is_headless:
if run_headless:
return None return None
return CuraSplashScreen.CuraSplashScreen() return CuraSplashScreen.CuraSplashScreen()

View file

@ -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. # 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 PyQt5.QtCore import QObject
from UM.FlameProfiler import pyqtSlot 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 ## Raised when trying to add an unknown machine action as a required action
class UnknownMachineActionError(Exception): class UnknownMachineActionError(Exception):
@ -20,23 +20,27 @@ class NotUniqueMachineActionError(Exception):
class MachineActionManager(QObject): class MachineActionManager(QObject):
def __init__(self, parent = None): def __init__(self, application, parent = None):
super().__init__(parent) super().__init__(parent)
self._application = application
self._machine_actions = {} # Dict of all known machine actions self._machine_actions = {} # Dict of all known machine actions
self._required_actions = {} # Dict of all required actions by definition ID self._required_actions = {} # Dict of all required actions by definition ID
self._supported_actions = {} # Dict of all supported 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 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 # Add machine_action as plugin type
PluginRegistry.addType("machine_action", self.addMachineAction) PluginRegistry.addType("machine_action", self.addMachineAction)
# Ensure that all containers that were registered before creation of this registry are also handled. # 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. # 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) self._onContainerAdded(container)
ContainerRegistry.getInstance().containerAdded.connect(self._onContainerAdded) container_registry.containerAdded.connect(self._onContainerAdded)
def _onContainerAdded(self, container): def _onContainerAdded(self, container):
## Ensure that the actions are added to this manager ## Ensure that the actions are added to this manager

View file

@ -1,24 +1,24 @@
# Copyright (c) 2018 Ultimaker B.V. # Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher. # 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 json
import math
import os
import unicodedata
import re # To create abbreviations for printer names. import re # To create abbreviations for printer names.
from typing import Dict
from PyQt5.QtCore import QObject, pyqtSignal, pyqtProperty, pyqtSlot 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.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") catalog = i18nCatalog("cura")
## A class for processing and calculating minimum, current and maximum print time as well as managing the job name ## 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 # This class contains all the logic relating to calculation and slicing for the
@ -47,8 +47,9 @@ class PrintInformation(QObject):
ActiveMachineChanged = 3 ActiveMachineChanged = 3
Other = 4 Other = 4
def __init__(self, parent = None): def __init__(self, application, parent = None):
super().__init__(parent) super().__init__(parent)
self._application = application
self.initializeCuraMessagePrintTimeProperties() self.initializeCuraMessagePrintTimeProperties()
@ -59,10 +60,10 @@ class PrintInformation(QObject):
self._pre_sliced = False self._pre_sliced = False
self._backend = Application.getInstance().getBackend() self._backend = self._application.getBackend()
if self._backend: if self._backend:
self._backend.printDurationMessage.connect(self._onPrintDurationMessage) 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._base_name = ""
self._abbr_machine = "" self._abbr_machine = ""
@ -71,7 +72,6 @@ class PrintInformation(QObject):
self._active_build_plate = 0 self._active_build_plate = 0
self._initVariablesWithBuildPlate(self._active_build_plate) self._initVariablesWithBuildPlate(self._active_build_plate)
self._application = Application.getInstance()
self._multi_build_plate_model = self._application.getMultiBuildPlateModel() self._multi_build_plate_model = self._application.getMultiBuildPlateModel()
self._application.globalContainerStackChanged.connect(self._updateJobName) self._application.globalContainerStackChanged.connect(self._updateJobName)
@ -199,7 +199,7 @@ class PrintInformation(QObject):
self._current_print_time[build_plate_number].setDuration(total_estimated_time) self._current_print_time[build_plate_number].setDuration(total_estimated_time)
def _calculateInformation(self, build_plate_number): def _calculateInformation(self, build_plate_number):
global_stack = Application.getInstance().getGlobalContainerStack() global_stack = self._application.getGlobalContainerStack()
if global_stack is None: if global_stack is None:
return return
@ -358,7 +358,7 @@ class PrintInformation(QObject):
## Created an acronymn-like abbreviated machine name from the currently active machine name ## Created an acronymn-like abbreviated machine name from the currently active machine name
# Called each time the global stack is switched # Called each time the global stack is switched
def _setAbbreviatedMachineName(self): def _setAbbreviatedMachineName(self):
global_container_stack = Application.getInstance().getGlobalContainerStack() global_container_stack = self._application.getGlobalContainerStack()
if not global_container_stack: if not global_container_stack:
self._abbr_machine = "" self._abbr_machine = ""
return return

View file

@ -24,7 +24,7 @@ class ConvexHullNode(SceneNode):
self._original_parent = parent self._original_parent = parent
# Color of the drawn convex hull # 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()) self._color = Color(*Application.getInstance().getTheme().getColor("convex_hull").getRgb())
else: else:
self._color = Color(0, 0, 0) self._color = Color(0, 0, 0)

View file

@ -1,32 +1,25 @@
# Copyright (c) 2018 Ultimaker B.V. # Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher. # Cura is released under the terms of the LGPLv3 or higher.
import os.path import os
import urllib.parse import urllib.parse
import uuid import uuid
from typing import Dict, Union from typing import Dict, Union
from PyQt5.QtCore import QObject, QUrl, QVariant from PyQt5.QtCore import QObject, QUrl, QVariant
from UM.FlameProfiler import pyqtSlot
from PyQt5.QtWidgets import QMessageBox from PyQt5.QtWidgets import QMessageBox
from UM.PluginRegistry import PluginRegistry from UM.i18n import i18nCatalog
from UM.SaveFile import SaveFile from UM.FlameProfiler import pyqtSlot
from UM.Platform import Platform
from UM.MimeTypeDatabase import MimeTypeDatabase
from UM.Logger import Logger 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.ContainerStack import ContainerStack
from UM.Settings.DefinitionContainer import DefinitionContainer from UM.Settings.DefinitionContainer import DefinitionContainer
from UM.Settings.InstanceContainer import InstanceContainer 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") 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 # 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. # when a certain action happens. This can be done through this class.
class ContainerManager(QObject): class ContainerManager(QObject):
def __init__(self, parent = None):
super().__init__(parent)
self._application = Application.getInstance() def __init__(self, application):
self._container_registry = ContainerRegistry.getInstance() 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._machine_manager = self._application.getMachineManager()
self._material_manager = self._application.getMaterialManager() self._material_manager = self._application.getMaterialManager()
self._container_name_filters = {} self._container_name_filters = {}
@ -129,7 +128,7 @@ class ContainerManager(QObject):
container.setProperty(setting_key, property_name, property_value) container.setProperty(setting_key, property_name, property_value)
basefile = container.getMetaDataEntry("base_file", container_id) 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: if sibbling_container != container:
sibbling_container.setProperty(setting_key, property_name, property_value) 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. # \return \type{bool} True if successful, False if not.
@pyqtSlot(result = bool) @pyqtSlot(result = bool)
def updateQualityChanges(self): def updateQualityChanges(self):
global_stack = Application.getInstance().getGlobalContainerStack() global_stack = self._machine_manager.activeMachine
if not global_stack: if not global_stack:
return False return False
self._machine_manager.blurSettings.emit() 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. # Find the quality_changes container for this stack and merge the contents of the top container into it.
quality_changes = stack.qualityChanges quality_changes = stack.qualityChanges
if not quality_changes or self._container_registry.isReadOnly(quality_changes.getId()): if not quality_changes or self._container_registry.isReadOnly(quality_changes.getId()):
@ -334,13 +335,15 @@ class ContainerManager(QObject):
send_emits_containers = [] send_emits_containers = []
# Go through global and extruder stacks and clear their topmost container (the user settings). # 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 = stack.userChanges
container.clear() container.clear()
send_emits_containers.append(container) send_emits_containers.append(container)
# user changes are possibly added to make the current setup match the current enabled extruders # 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: for container in send_emits_containers:
container.sendPostponedEmits() container.sendPostponedEmits()
@ -381,21 +384,6 @@ class ContainerManager(QObject):
if container is not None: if container is not None:
container.setMetaDataEntry("GUID", new_guid) 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): def _performMerge(self, merge_into, merge, clear_settings = True):
if merge == merge_into: if merge == merge_into:
return return
@ -415,7 +403,7 @@ class ContainerManager(QObject):
serialize_type = "" serialize_type = ""
try: try:
plugin_metadata = PluginRegistry.getInstance().getMetaData(plugin_id) plugin_metadata = self._plugin_registry.getMetaData(plugin_id)
if plugin_metadata: if plugin_metadata:
serialize_type = plugin_metadata["settings_container"]["type"] serialize_type = plugin_metadata["settings_container"]["type"]
else: 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] 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) self._container_registry.exportQualityProfile(container_list, path, file_type)
__instance = None
@classmethod
def getInstance(cls, *args, **kwargs) -> "ContainerManager":
return cls.__instance

View file

@ -7,7 +7,6 @@ from UM.ConfigurationErrorMessage import ConfigurationErrorMessage
from UM.Logger import Logger from UM.Logger import Logger
from UM.Settings.Interfaces import DefinitionContainerInterface from UM.Settings.Interfaces import DefinitionContainerInterface
from UM.Settings.InstanceContainer import InstanceContainer from UM.Settings.InstanceContainer import InstanceContainer
from UM.Settings.ContainerRegistry import ContainerRegistry
from cura.Machines.VariantManager import VariantType from cura.Machines.VariantManager import VariantType
from .GlobalStack import GlobalStack from .GlobalStack import GlobalStack
@ -29,7 +28,7 @@ class CuraStackBuilder:
variant_manager = application.getVariantManager() variant_manager = application.getVariantManager()
material_manager = application.getMaterialManager() material_manager = application.getMaterialManager()
quality_manager = application.getQualityManager() quality_manager = application.getQualityManager()
registry = ContainerRegistry.getInstance() registry = application.getContainerRegistry()
definitions = registry.findDefinitionContainers(id = definition_id) definitions = registry.findDefinitionContainers(id = definition_id)
if not definitions: if not definitions:
@ -142,6 +141,7 @@ class CuraStackBuilder:
variant_container, material_container, quality_container, global_stack) -> ExtruderStack: variant_container, material_container, quality_container, global_stack) -> ExtruderStack:
from cura.CuraApplication import CuraApplication from cura.CuraApplication import CuraApplication
application = CuraApplication.getInstance() application = CuraApplication.getInstance()
registry = application.getContainerRegistry()
stack = ExtruderStack(new_stack_id, parent = global_stack) stack = ExtruderStack(new_stack_id, parent = global_stack)
stack.setName(extruder_definition.getName()) 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 # 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 # properties. This makes the create operation more transactional, since any problems
# setting properties will not result in incomplete containers being added. # setting properties will not result in incomplete containers being added.
ContainerRegistry.getInstance().addContainer(user_container) registry.addContainer(user_container)
return stack return stack
@ -178,6 +178,7 @@ class CuraStackBuilder:
variant_container, material_container, quality_container) -> GlobalStack: variant_container, material_container, quality_container) -> GlobalStack:
from cura.CuraApplication import CuraApplication from cura.CuraApplication import CuraApplication
application = CuraApplication.getInstance() application = CuraApplication.getInstance()
registry = application.getContainerRegistry()
stack = GlobalStack(new_stack_id) stack = GlobalStack(new_stack_id)
stack.setDefinition(definition) stack.setDefinition(definition)
@ -193,7 +194,7 @@ class CuraStackBuilder:
stack.qualityChanges = application.empty_quality_changes_container stack.qualityChanges = application.empty_quality_changes_container
stack.userChanges = user_container stack.userChanges = user_container
ContainerRegistry.getInstance().addContainer(user_container) registry.addContainer(user_container)
return stack return stack
@ -201,8 +202,10 @@ class CuraStackBuilder:
def createUserChangesContainer(cls, container_name: str, definition_id: str, stack_id: str, def createUserChangesContainer(cls, container_name: str, definition_id: str, stack_id: str,
is_global_stack: bool) -> "InstanceContainer": is_global_stack: bool) -> "InstanceContainer":
from cura.CuraApplication import CuraApplication 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 = InstanceContainer(unique_container_name)
container.setDefinition(definition_id) container.setDefinition(definition_id)
@ -217,15 +220,17 @@ class CuraStackBuilder:
@classmethod @classmethod
def createDefinitionChangesContainer(cls, container_stack, container_name): def createDefinitionChangesContainer(cls, container_stack, container_name):
from cura.CuraApplication import CuraApplication 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 = InstanceContainer(unique_container_name)
definition_changes_container.setDefinition(container_stack.getBottom().getId()) definition_changes_container.setDefinition(container_stack.getBottom().getId())
definition_changes_container.addMetaDataEntry("type", "definition_changes") definition_changes_container.addMetaDataEntry("type", "definition_changes")
definition_changes_container.addMetaDataEntry("setting_version", CuraApplication.SettingVersion) 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 container_stack.definitionChanges = definition_changes_container
return definition_changes_container return definition_changes_container

View file

@ -15,6 +15,7 @@ from UM.Settings.SettingFunction import SettingFunction
from UM.Settings.SettingInstance import SettingInstance from UM.Settings.SettingInstance import SettingInstance
from UM.Settings.ContainerStack import ContainerStack from UM.Settings.ContainerStack import ContainerStack
from UM.Settings.PropertyEvaluationContext import PropertyEvaluationContext from UM.Settings.PropertyEvaluationContext import PropertyEvaluationContext
from typing import Optional, List, TYPE_CHECKING, Union from typing import Optional, List, TYPE_CHECKING, Union
if TYPE_CHECKING: if TYPE_CHECKING:
@ -29,6 +30,10 @@ class ExtruderManager(QObject):
## Registers listeners and such to listen to changes to the extruders. ## Registers listeners and such to listen to changes to the extruders.
def __init__(self, parent = None): 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) super().__init__(parent)
self._application = Application.getInstance() self._application = Application.getInstance()
@ -92,28 +97,6 @@ class ExtruderManager(QObject):
if extruder.getId() == extruder_stack_id: if extruder.getId() == extruder_stack_id:
return extruder.qualityChanges.getId() 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. ## Changes the active extruder by index.
# #
# \param index The index of the new active extruder. # \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) resolved_value = global_stack.getProperty(key, "value", context = context)
return resolved_value return resolved_value
__instance = None
@classmethod
def getInstance(cls, *args, **kwargs) -> "ExtruderManager":
return cls.__instance

103
cura/SingleInstance.py Normal file
View file

@ -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()

View file

@ -1,51 +1,36 @@
#!/usr/bin/env python3 #!/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. # Cura is released under the terms of the LGPLv3 or higher.
import argparse import faulthandler
import os import os
import sys import sys
from UM.Platform import Platform 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"]: # Gets the directory for stdout and stderr
def get_cura_dir_path(): def get_cura_dir_for_stdoutputs() -> str:
if Platform.isWindows(): if Platform.isWindows():
return os.path.expanduser("~/AppData/Roaming/cura/") return os.path.expanduser("~/AppData/Roaming/cura/")
elif Platform.isLinux(): elif Platform.isLinux():
return os.path.expanduser("~/.local/share/cura") return os.path.expanduser("~/.local/share/cura")
elif Platform.isOSX(): elif Platform.isOSX():
return os.path.expanduser("~/Library/Logs/cura") 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 # Change stdout and stderr to files if Cura is running as a packaged application.
import faulthandler 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 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 # 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. # The workaround is only needed on Ubuntu+NVidia drivers. Other drivers are not affected, but fine with this fix.
try: try:
import ctypes import ctypes
@ -79,6 +64,7 @@ if "PYTHONPATH" in os.environ.keys(): # If PYTHONPATH is u
sys.path.remove(PATH_real) sys.path.remove(PATH_real)
sys.path.insert(1, PATH_real) # Insert it at 1 after os.curdir, which is 0. sys.path.insert(1, PATH_real) # Insert it at 1 after os.curdir, which is 0.
def exceptHook(hook_type, value, traceback): def exceptHook(hook_type, value, traceback):
from cura.CrashHandler import CrashHandler from cura.CrashHandler import CrashHandler
from cura.CuraApplication import CuraApplication from cura.CuraApplication import CuraApplication
@ -121,25 +107,25 @@ def exceptHook(hook_type, value, traceback):
_crash_handler.early_crash_dialog.show() _crash_handler.early_crash_dialog.show()
sys.exit(application.exec_()) 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 # Workaround for a race condition on certain systems where there
# is a race condition between Arcus and PyQt. Importing Arcus # is a race condition between Arcus and PyQt. Importing Arcus
# first seems to prevent Sip from going into a state where it # first seems to prevent Sip from going into a state where it
# tries to create PyQt objects on a non-main thread. # tries to create PyQt objects on a non-main thread.
import Arcus #@UnusedImport import Arcus #@UnusedImport
import cura.CuraApplication from cura.CuraApplication import CuraApplication
import cura.Settings.CuraContainerRegistry
faulthandler.enable() app = CuraApplication()
app.addCommandLineOptions()
app.parseCliOptions()
app.initialize()
# Force an instance of CuraContainerRegistry to be created and reused later. app.startSlashWindowPhase()
cura.Settings.CuraContainerRegistry.CuraContainerRegistry.getInstance() 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() app.run()

View file

@ -275,7 +275,7 @@ class CuraEngineBackend(QObject, Backend):
self.processingProgress.emit(0) self.processingProgress.emit(0)
Logger.log("d", "Attempting to kill the engine process") Logger.log("d", "Attempting to kill the engine process")
if Application.getInstance().getCommandLineOption("external-backend", False): if Application.getInstance().getUseExternalBackend():
return return
if self._process is not None: if self._process is not None:

View file

@ -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. # Cura is released under the terms of the LGPLv3 or higher.
from UM.Signal import Signal, signalemitter import threading
from UM.Application import Application import platform
from UM.Resources import Resources import time
import serial.tools.list_ports
from PyQt5.QtCore import QObject, pyqtSlot, pyqtProperty, pyqtSignal
from UM.Logger import Logger from UM.Logger import Logger
from UM.Resources import Resources
from UM.Signal import Signal, signalemitter
from UM.OutputDevice.OutputDevicePlugin import OutputDevicePlugin from UM.OutputDevice.OutputDevicePlugin import OutputDevicePlugin
from UM.i18n import i18nCatalog from UM.i18n import i18nCatalog
@ -12,12 +18,6 @@ from cura.PrinterOutputDevice import ConnectionState
from cura.CuraApplication import CuraApplication from cura.CuraApplication import CuraApplication
from . import USBPrinterOutputDevice 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") i18n_catalog = i18nCatalog("cura")
@ -28,8 +28,14 @@ class USBPrinterOutputDeviceManager(QObject, OutputDevicePlugin):
addUSBOutputDeviceSignal = Signal() addUSBOutputDeviceSignal = Signal()
progressChanged = pyqtSignal() 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) super().__init__(parent = parent)
self._application = application
self._serial_port_list = [] self._serial_port_list = []
self._usb_output_devices = {} self._usb_output_devices = {}
self._usb_output_devices_model = None self._usb_output_devices_model = None
@ -38,11 +44,11 @@ class USBPrinterOutputDeviceManager(QObject, OutputDevicePlugin):
self._check_updates = True 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. # Because the model needs to be created in the same thread as the QMLEngine, we use a signal.
self.addUSBOutputDeviceSignal.connect(self.addOutputDevice) 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 # The method updates/reset the USB settings for all connected USB devices
def updateUSBPrinterOutputDevices(self): def updateUSBPrinterOutputDevices(self):
@ -69,7 +75,7 @@ class USBPrinterOutputDeviceManager(QObject, OutputDevicePlugin):
def _updateThread(self): def _updateThread(self):
while self._check_updates: while self._check_updates:
container_stack = Application.getInstance().getGlobalContainerStack() container_stack = self._application.getGlobalContainerStack()
if container_stack is None: if container_stack is None:
time.sleep(5) time.sleep(5)
continue continue
@ -80,19 +86,10 @@ class USBPrinterOutputDeviceManager(QObject, OutputDevicePlugin):
self._addRemovePorts(port_list) self._addRemovePorts(port_list)
time.sleep(5) 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) @pyqtSlot(result = str)
def getDefaultFirmwareName(self): def getDefaultFirmwareName(self):
# Check if there is a valid global container stack # 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: if not global_container_stack:
Logger.log("e", "There is no global container stack. Can not update firmware.") Logger.log("e", "There is no global container stack. Can not update firmware.")
self._firmware_view.close() self._firmware_view.close()
@ -181,4 +178,8 @@ class USBPrinterOutputDeviceManager(QObject, OutputDevicePlugin):
return list(base_list) return list(base_list)
_instance = None # type: "USBPrinterOutputDeviceManager" __instance = None
@classmethod
def getInstance(cls, *args, **kwargs) -> "USBPrinterOutputDeviceManager":
return cls.__instance

View file

@ -15,4 +15,4 @@ def register(app):
# We are violating the QT API here (as we use a factory, which is technically not allowed). # 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-) # 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) qmlRegisterSingletonType(USBPrinterOutputDeviceManager.USBPrinterOutputDeviceManager, "Cura", 1, 0, "USBPrinterManager", USBPrinterOutputDeviceManager.USBPrinterOutputDeviceManager.getInstance)
return {"output_device": USBPrinterOutputDeviceManager.USBPrinterOutputDeviceManager.getInstance()} return {"output_device": USBPrinterOutputDeviceManager.USBPrinterOutputDeviceManager(app)}