diff --git a/cura/ConvexHullDecorator.py b/cura/ConvexHullDecorator.py index d864f4288b..5185579633 100644 --- a/cura/ConvexHullDecorator.py +++ b/cura/ConvexHullDecorator.py @@ -30,9 +30,10 @@ class ConvexHullDecorator(SceneNodeDecorator): def setNode(self, node): previous_node = self._node + # Disconnect from previous node signals if previous_node is not None and node is not previous_node: - previous_node.transformationChanged.connect(self._onChanged) - previous_node.parentChanged.connect(self._onChanged) + previous_node.transformationChanged.disconnect(self._onChanged) + previous_node.parentChanged.disconnect(self._onChanged) super().setNode(node) @@ -286,5 +287,5 @@ class ConvexHullDecorator(SceneNodeDecorator): _affected_settings = [ "adhesion_type", "raft_base_thickness", "raft_interface_thickness", "raft_surface_layers", - "raft_surface_thickness", "raft_airgap", "print_sequence", + "raft_surface_thickness", "raft_airgap", "raft_margin", "print_sequence", "skirt_gap", "skirt_line_count", "skirt_brim_line_width", "skirt_distance"] diff --git a/cura/ConvexHullNode.py b/cura/ConvexHullNode.py index 703dfb0bed..8e5acf9518 100644 --- a/cura/ConvexHullNode.py +++ b/cura/ConvexHullNode.py @@ -23,7 +23,7 @@ class ConvexHullNode(SceneNode): self._original_parent = parent # Color of the drawn convex hull - self._color = Color(35, 35, 35, 192) + self._color = Color(0.4, 0.4, 0.4, 1.0) # The y-coordinate of the convex hull mesh. Must not be 0, to prevent z-fighting. self._mesh_height = 0.1 @@ -77,7 +77,7 @@ class ConvexHullNode(SceneNode): convex_hull_head = self._node.callDecoration("getConvexHullHead") if convex_hull_head: convex_hull_head_builder = MeshBuilder() - convex_hull_head_builder.addConvexPolygon(convex_hull_head.getPoints(), self._mesh_height-self._thickness) + convex_hull_head_builder.addConvexPolygon(convex_hull_head.getPoints(), self._mesh_height - self._thickness) self._convex_hull_head_mesh = convex_hull_head_builder.build() if not node: diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index a6d0a3b827..53d509b71c 100644 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -98,7 +98,7 @@ class CuraApplication(QtApplication): SettingDefinition.addSupportedProperty("settable_per_meshgroup", DefinitionPropertyType.Any, default = True) SettingDefinition.addSupportedProperty("settable_globally", DefinitionPropertyType.Any, default = True) SettingDefinition.addSupportedProperty("global_inherits_stack", DefinitionPropertyType.Function, default = "-1") - SettingDefinition.addSettingType("extruder", int, str, Validator) + SettingDefinition.addSettingType("extruder", None, str, Validator) ## Add the 4 types of profiles to storage. Resources.addStorageType(self.ResourceTypes.QualityInstanceContainer, "quality") @@ -128,6 +128,8 @@ class CuraApplication(QtApplication): self._machine_action_manager = MachineActionManager.MachineActionManager() self._machine_manager = None # This is initialized on demand. + self._additional_components = {} # Components to add to certain areas in the interface + super().__init__(name = "cura", version = CuraVersion, buildtype = CuraBuildType) self.setWindowIcon(QIcon(Resources.getPath(Resources.Images, "cura-icon.png"))) @@ -201,7 +203,7 @@ class CuraApplication(QtApplication): "dialog_profile_path", "dialog_material_path"]: - Preferences.getInstance().addPreference("local_file/%s" % key, "~/") + Preferences.getInstance().addPreference("local_file/%s" % key, os.path.expanduser("~/")) Preferences.getInstance().setDefault("local_file/last_used_type", "text/x-gcode") @@ -553,12 +555,18 @@ class CuraApplication(QtApplication): def deleteSelection(self): if not self.getController().getToolsEnabled(): return - + removed_group_nodes = [] op = GroupedOperation() nodes = Selection.getAllSelectedObjects() for node in nodes: op.addOperation(RemoveSceneNodeOperation(node)) - + group_node = node.getParent() + if group_node and group_node.callDecoration("isGroup") and group_node not in removed_group_nodes: + remaining_nodes_in_group = list(set(group_node.getChildren()) - set(nodes)) + if len(remaining_nodes_in_group) == 1: + removed_group_nodes.append(group_node) + op.addOperation(SetParentOperation(remaining_nodes_in_group[0], group_node.getParent())) + op.addOperation(RemoveSceneNodeOperation(group_node)) op.push() pass @@ -584,8 +592,7 @@ class CuraApplication(QtApplication): op.push() if group_node: if len(group_node.getChildren()) == 1 and group_node.callDecoration("isGroup"): - group_node.getChildren()[0].translate(group_node.getPosition()) - group_node.getChildren()[0].setParent(group_node.getParent()) + op.addOperation(SetParentOperation(group_node.getChildren()[0], group_node.getParent())) op = RemoveSceneNodeOperation(group_node) op.push() @@ -626,7 +633,23 @@ class CuraApplication(QtApplication): if node: op = SetTransformOperation(node, Vector()) op.push() - + + ## Select all nodes containing mesh data in the scene. + @pyqtSlot() + def selectAll(self): + if not self.getController().getToolsEnabled(): + return + + Selection.clear() + for node in DepthFirstIterator(self.getController().getScene().getRoot()): + if type(node) is not SceneNode: + continue + if not node.getMeshData() and not node.callDecoration("isGroup"): + continue # Node that doesnt have a mesh and is not a group. + if node.getParent() and node.getParent().callDecoration("isGroup"): + continue # Grouped nodes don't need resetting as their parent (the group) is resetted) + Selection.add(node) + ## Delete all nodes containing mesh data in the scene. @pyqtSlot() def deleteAll(self): @@ -650,6 +673,7 @@ class CuraApplication(QtApplication): op.addOperation(RemoveSceneNodeOperation(node)) op.push() + Selection.clear() ## Reset all translation on nodes with mesh data. @pyqtSlot() @@ -876,4 +900,22 @@ class CuraApplication(QtApplication): self.getMainWindow().setMinimumSize(size) def getBuildVolume(self): - return self._volume \ No newline at end of file + return self._volume + + additionalComponentsChanged = pyqtSignal(str, arguments = ["areaId"]) + + @pyqtProperty("QVariantMap", notify = additionalComponentsChanged) + def additionalComponents(self): + return self._additional_components + + ## Add a component to a list of components to be reparented to another area in the GUI. + # The actual reparenting is done by the area itself. + # \param area_id \type{str} Identifying name of the area to which the component should be reparented + # \param component \type{QQuickComponent} The component that should be reparented + @pyqtSlot(str, "QVariant") + def addAdditionalComponent(self, area_id, component): + if area_id not in self._additional_components: + self._additional_components[area_id] = [] + self._additional_components[area_id].append(component) + + self.additionalComponentsChanged.emit(area_id) diff --git a/cura/MachineActionManager.py b/cura/MachineActionManager.py index b50bb95e7f..6061a2e49b 100644 --- a/cura/MachineActionManager.py +++ b/cura/MachineActionManager.py @@ -57,9 +57,10 @@ class MachineActionManager(QObject): def addRequiredAction(self, definition_id, action_key): if action_key in self._machine_actions: if definition_id in self._required_actions: - self._required_actions[definition_id] |= {self._machine_actions[action_key]} + if self._machine_actions[action_key] not in self._required_actions[definition_id]: + self._required_actions[definition_id].append(self._machine_actions[action_key]) else: - self._required_actions[definition_id] = {self._machine_actions[action_key]} + self._required_actions[definition_id] = [self._machine_actions[action_key]] else: raise UnknownMachineActionError("Action %s, which is required for %s is not known." % (action_key, definition_id)) @@ -67,9 +68,10 @@ class MachineActionManager(QObject): def addSupportedAction(self, definition_id, action_key): if action_key in self._machine_actions: if definition_id in self._supported_actions: - self._supported_actions[definition_id] |= {self._machine_actions[action_key]} + if self._machine_actions[action_key] not in self._supported_actions[definition_id]: + self._supported_actions[definition_id].append(self._machine_actions[action_key]) else: - self._supported_actions[definition_id] = {self._machine_actions[action_key]} + self._supported_actions[definition_id] = [self._machine_actions[action_key]] else: Logger.log("w", "Unable to add %s to %s, as the action is not recognised", action_key, definition_id) diff --git a/cura/PlatformPhysics.py b/cura/PlatformPhysics.py index d2a848dd72..56daaddc18 100644 --- a/cura/PlatformPhysics.py +++ b/cura/PlatformPhysics.py @@ -71,7 +71,6 @@ class PlatformPhysics: # If there is no convex hull for the node, start calculating it and continue. if not node.getDecorator(ConvexHullDecorator): node.addDecorator(ConvexHullDecorator()) - node.callDecoration("recomputeConvexHull") if Preferences.getInstance().getValue("physics/automatic_push_free"): # Check for collisions between convex hulls @@ -95,11 +94,11 @@ class PlatformPhysics: # Get the overlap distance for both convex hulls. If this returns None, there is no intersection. head_hull = node.callDecoration("getConvexHullHead") if head_hull: - overlap = head_hull.intersectsPolygon(other_node.callDecoration("getConvexHull")) + overlap = head_hull.intersectsPolygon(other_node.callDecoration("getConvexHullHead")) if not overlap: other_head_hull = other_node.callDecoration("getConvexHullHead") if other_head_hull: - overlap = node.callDecoration("getConvexHull").intersectsPolygon(other_head_hull) + overlap = node.callDecoration("getConvexHullHead").intersectsPolygon(other_head_hull) else: own_convex_hull = node.callDecoration("getConvexHull") other_convex_hull = other_node.callDecoration("getConvexHull") diff --git a/cura/Settings/ContainerManager.py b/cura/Settings/ContainerManager.py index 9184db109a..82be7c480f 100644 --- a/cura/Settings/ContainerManager.py +++ b/cura/Settings/ContainerManager.py @@ -244,6 +244,7 @@ class ContainerManager(QObject): if not type_name or entry["type"] == type_name: filters.append(filter_string) + filters.append("All Files (*)") return filters ## Export a container to a file @@ -280,6 +281,9 @@ class ContainerManager(QObject): return { "status": "error", "message": "Container not found"} container = containers[0] + if UM.Platform.isOSX() and "." in file_url: + file_url = file_url[:file_url.rfind(".")] + for suffix in mime_type.suffixes: if file_url.endswith(suffix): break @@ -301,7 +305,7 @@ class ContainerManager(QObject): with UM.SaveFile(file_url, "w") as f: f.write(contents) - return { "status": "success", "message": "Succesfully exported container"} + return { "status": "success", "message": "Succesfully exported container", "path": file_url} ## Imports a profile from a file # @@ -371,11 +375,20 @@ class ContainerManager(QObject): "container": container_type } - suffix_list = "*." + mime_type.preferredSuffix + suffix = mime_type.preferredSuffix + if UM.Platform.isOSX() and "." in suffix: + # OSX's File dialog is stupid and does not allow selecting files with a . in its name + suffix = suffix[suffix.index(".") + 1:] + + suffix_list = "*." + suffix for suffix in mime_type.suffixes: if suffix == mime_type.preferredSuffix: continue + if UM.Platform.isOSX() and "." in suffix: + # OSX's File dialog is stupid and does not allow selecting files with a . in its name + suffix = suffix[suffix.index("."):] + suffix_list += ", *." + suffix name_filter = "{0} ({1})".format(mime_type.comment, suffix_list) diff --git a/cura/Settings/MachineManager.py b/cura/Settings/MachineManager.py index ca09cbc5dc..9e09ad83be 100644 --- a/cura/Settings/MachineManager.py +++ b/cura/Settings/MachineManager.py @@ -26,7 +26,7 @@ class MachineManager(QObject): self._global_container_stack = None Application.getInstance().globalContainerStackChanged.connect(self._onGlobalContainerChanged) - self._global_stack_valid = None + self._active_stack_valid = None self._onGlobalContainerChanged() ExtruderManager.getInstance().activeExtruderChanged.connect(self._onActiveExtruderStackChanged) @@ -74,7 +74,7 @@ class MachineManager(QObject): activeStackChanged = pyqtSignal() globalValueChanged = pyqtSignal() # Emitted whenever a value inside global container is changed. - globalValidationChanged = pyqtSignal() # Emitted whenever a validation inside global container is changed + activeValidationChanged = pyqtSignal() # Emitted whenever a validation inside active container is changed blurSettings = pyqtSignal() # Emitted to force fields in the advanced sidebar to un-focus, so they update properly @@ -269,25 +269,29 @@ class MachineManager(QObject): if property_name == "global_inherits_stack": if self._active_container_stack and self._active_container_stack != self._global_container_stack: # Update the global user value when the "global_inherits_stack" function points to a different stack - stack_index = int(self._active_container_stack.getProperty(key, property_name)) - extruder_stacks = [stack for stack in ExtruderManager.getInstance().getMachineExtruders(self._global_container_stack.getId())] + extruder_stacks = list(ExtruderManager.getInstance().getMachineExtruders(self._global_container_stack.getId())) + target_stack_position = int(self._active_container_stack.getProperty(key, "global_inherits_stack")) + if target_stack_position == -1: # Prevent -1 from selecting wrong stack. + target_stack = self._active_container_stack + else: + target_stack = extruder_stacks[target_stack_position] - if len(extruder_stacks) > stack_index: - new_value = extruder_stacks[stack_index].getProperty(key, "value") - if self._global_container_stack.getProperty(key, "value") != new_value: - self._global_container_stack.getTop().setProperty(key, "value", new_value) + new_value = target_stack.getProperty(key, "value") + if self._global_container_stack.getProperty(key, "value") != new_value: + self._global_container_stack.getTop().setProperty(key, "value", new_value) if property_name == "validationState": - if self._global_stack_valid: + if self._active_stack_valid: changed_validation_state = self._active_container_stack.getProperty(key, property_name) if changed_validation_state in (UM.Settings.ValidatorState.Exception, UM.Settings.ValidatorState.MaximumError, UM.Settings.ValidatorState.MinimumError): - self._global_stack_valid = False - self.globalValidationChanged.emit() + self._active_stack_valid = False + self.activeValidationChanged.emit() else: has_errors = self._checkStackForErrors(self._active_container_stack) if not has_errors: - self._global_stack_valid = True - self.globalValidationChanged.emit() + self._active_stack_valid = True + self.activeValidationChanged.emit() + def _onGlobalContainerChanged(self): if self._global_container_stack: self._global_container_stack.nameChanged.disconnect(self._onMachineNameChanged) @@ -310,8 +314,6 @@ class MachineManager(QObject): self._global_container_stack.nameChanged.connect(self._onMachineNameChanged) self._global_container_stack.containersChanged.connect(self._onInstanceContainersChanged) self._global_container_stack.propertyChanged.connect(self._onGlobalPropertyChanged) - self._global_stack_valid = not self._checkStackForErrors(self._global_container_stack) - self.globalValidationChanged.emit() material = self._global_container_stack.findContainer({"type": "material"}) material.nameChanged.connect(self._onMaterialNameChanged) @@ -329,6 +331,8 @@ class MachineManager(QObject): self._active_container_stack.propertyChanged.connect(self._onGlobalPropertyChanged) else: self._active_container_stack = self._global_container_stack + self._active_stack_valid = not self._checkStackForErrors(self._active_container_stack) + self.activeValidationChanged.emit() def _onInstanceContainersChanged(self, container): container_type = container.getMetaDataEntry("type") @@ -433,11 +437,11 @@ class MachineManager(QObject): return len(user_settings) != 0 ## Check if the global profile does not contain error states - # Note that the _global_stack_valid is cached due to performance issues + # Note that the _active_stack_valid is cached due to performance issues # Calling _checkStackForErrors on every change is simply too expensive - @pyqtProperty(bool, notify = globalValidationChanged) - def isGlobalStackValid(self): - return bool(self._global_stack_valid) + @pyqtProperty(bool, notify = activeValidationChanged) + def isActiveStackValid(self): + return bool(self._active_stack_valid) @pyqtProperty(str, notify = activeStackChanged) def activeUserProfileId(self): @@ -549,7 +553,7 @@ class MachineManager(QObject): return "" @pyqtSlot(str, str) - def renameQualityContainer(self, container_id, new_name): + def renameQualityContainer(self, container_id, nbalew_name): containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(id = container_id, type = "quality") if containers: new_name = self._createUniqueName("quality", containers[0].getName(), new_name, diff --git a/cura/Settings/SettingOverrideDecorator.py b/cura/Settings/SettingOverrideDecorator.py index 24360ed992..f1e34a939a 100644 --- a/cura/Settings/SettingOverrideDecorator.py +++ b/cura/Settings/SettingOverrideDecorator.py @@ -75,6 +75,7 @@ class SettingOverrideDecorator(SceneNodeDecorator): # \param extruder_stack_id The new extruder stack to print with. def setActiveExtruder(self, extruder_stack_id): self._extruder_stack = extruder_stack_id + self._updateNextStack() self.activeExtruderChanged.emit() def getStack(self): diff --git a/plugins/3MFReader/ThreeMFReader.py b/plugins/3MFReader/ThreeMFReader.py index 57d76b2783..ea3ca30118 100644 --- a/plugins/3MFReader/ThreeMFReader.py +++ b/plugins/3MFReader/ThreeMFReader.py @@ -9,18 +9,20 @@ from UM.Math.Vector import Vector from UM.Scene.SceneNode import SceneNode from UM.Scene.GroupDecorator import GroupDecorator from UM.Math.Quaternion import Quaternion - from UM.Job import Job import math import zipfile -import xml.etree.ElementTree as ET +try: + import xml.etree.cElementTree as ET +except ImportError: + import xml.etree.ElementTree as ET ## Base implementation for reading 3MF files. Has no support for textures. Only loads meshes! class ThreeMFReader(MeshReader): def __init__(self): - super(ThreeMFReader, self).__init__() + super().__init__() self._supported_extensions = [".3mf"] self._namespaces = { @@ -116,4 +118,10 @@ class ThreeMFReader(MeshReader): except Exception as e: Logger.log("e", "exception occured in 3mf reader: %s", e) - return result + try: # Selftest - There might be more functions that should fail + boundingBox = result.getBoundingBox() + boundingBox.isValid() + except: + return None + + return result diff --git a/plugins/CuraEngineBackend/Cura.proto b/plugins/CuraEngineBackend/Cura.proto index 0c4803cc19..289c0a98a0 100644 --- a/plugins/CuraEngineBackend/Cura.proto +++ b/plugins/CuraEngineBackend/Cura.proto @@ -13,6 +13,7 @@ message Slice repeated ObjectList object_lists = 1; // The meshgroups to be printed one after another SettingList global_settings = 2; // The global settings used for the whole print job repeated Extruder extruders = 3; // The settings sent to each extruder object + repeated SettingExtruder global_inherits_stack = 4; //From which stack the setting would inherit if not defined in a stack. } message Extruder @@ -108,8 +109,14 @@ message Setting { bytes value = 2; // The value of the setting } +message SettingExtruder { + string name = 1; //The setting key. + + int32 extruder = 2; //From which extruder stack the setting should inherit. +} + message GCodePrefix { - bytes data = 2; // Header string to be prenpended before the rest of the gcode sent from the engine + bytes data = 2; //Header string to be prepended before the rest of the g-code sent from the engine. } message SlicingFinished { diff --git a/plugins/CuraEngineBackend/CuraEngineBackend.py b/plugins/CuraEngineBackend/CuraEngineBackend.py index aedc91f130..a822512218 100644 --- a/plugins/CuraEngineBackend/CuraEngineBackend.py +++ b/plugins/CuraEngineBackend/CuraEngineBackend.py @@ -13,9 +13,11 @@ from UM.Resources import Resources from UM.Settings.Validator import ValidatorState #To find if a setting is in an error state. We can't slice then. from UM.Platform import Platform + import cura.Settings from cura.OneAtATimeIterator import OneAtATimeIterator +from cura.Settings.ExtruderManager import ExtruderManager from . import ProcessSlicedLayersJob from . import ProcessGCodeJob from . import StartSliceJob @@ -40,7 +42,8 @@ class CuraEngineBackend(Backend): def __init__(self): super().__init__() - # Find out where the engine is located, and how it is called. This depends on how Cura is packaged and which OS we are running on. + # Find out where the engine is located, and how it is called. + # This depends on how Cura is packaged and which OS we are running on. default_engine_location = os.path.join(Application.getInstallPrefix(), "bin", "CuraEngine") if hasattr(sys, "frozen"): default_engine_location = os.path.join(os.path.dirname(os.path.abspath(sys.executable)), "CuraEngine") @@ -59,7 +62,7 @@ class CuraEngineBackend(Backend): self._stored_layer_data = [] self._stored_optimized_layer_data = [] - #Triggers for when to (re)start slicing: + # Triggers for when to (re)start slicing: self._global_container_stack = None Application.getInstance().globalContainerStackChanged.connect(self._onGlobalStackChanged) self._onGlobalStackChanged() @@ -68,15 +71,15 @@ class CuraEngineBackend(Backend): cura.Settings.ExtruderManager.getInstance().activeExtruderChanged.connect(self._onActiveExtruderChanged) self._onActiveExtruderChanged() - #When you update a setting and other settings get changed through inheritance, many propertyChanged signals are fired. - #This timer will group them up, and only slice for the last setting changed signal. - #TODO: Properly group propertyChanged signals by whether they are triggered by the same user interaction. + # When you update a setting and other settings get changed through inheritance, many propertyChanged signals are fired. + # This timer will group them up, and only slice for the last setting changed signal. + # TODO: Properly group propertyChanged signals by whether they are triggered by the same user interaction. self._change_timer = QTimer() self._change_timer.setInterval(500) self._change_timer.setSingleShot(True) self._change_timer.timeout.connect(self.slice) - #Listeners for receiving messages from the back-end. + # Listeners for receiving messages from the back-end. self._message_handlers["cura.proto.Layer"] = self._onLayerMessage self._message_handlers["cura.proto.LayerOptimized"] = self._onOptimizedLayerMessage self._message_handlers["cura.proto.Progress"] = self._onProgressMessage @@ -86,19 +89,19 @@ class CuraEngineBackend(Backend): self._message_handlers["cura.proto.SlicingFinished"] = self._onSlicingFinishedMessage self._start_slice_job = None - self._slicing = False #Are we currently slicing? - self._restart = False #Back-end is currently restarting? - self._enabled = True #Should we be slicing? Slicing might be paused when, for instance, the user is dragging the mesh around. - self._always_restart = True #Always restart the engine when starting a new slice. Don't keep the process running. TODO: Fix engine statelessness. - self._process_layers_job = None #The currently active job to process layers, or None if it is not processing layers. + self._slicing = False # Are we currently slicing? + self._restart = False # Back-end is currently restarting? + self._enabled = True # Should we be slicing? Slicing might be paused when, for instance, the user is dragging the mesh around. + self._always_restart = True # Always restart the engine when starting a new slice. Don't keep the process running. TODO: Fix engine statelessness. + self._process_layers_job = None # The currently active job to process layers, or None if it is not processing layers. - self._backend_log_max_lines = 200 # Maximal count of lines to buffer - self._error_message = None #Pop-up message that shows errors. + self._backend_log_max_lines = 20000 # Maximum number of lines to buffer + self._error_message = None # Pop-up message that shows errors. self.backendQuit.connect(self._onBackendQuit) self.backendConnected.connect(self._onBackendConnected) - #When a tool operation is in progress, don't slice. So we need to listen for tool operations. + # When a tool operation is in progress, don't slice. So we need to listen for tool operations. Application.getInstance().getController().toolOperationStarted.connect(self._onToolOperationStarted) Application.getInstance().getController().toolOperationStopped.connect(self._onToolOperationStopped) @@ -117,9 +120,10 @@ class CuraEngineBackend(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, "-vv"] + return [Preferences.getInstance().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. + ## Emitted when we get a message containing print duration and material amount. + # This also implies the slicing has finished. # \param time The amount of time the print will take. # \param material_amount The amount of material the print will use. printDurationMessage = Signal() @@ -133,7 +137,7 @@ class CuraEngineBackend(Backend): ## Perform a slice of the scene. def slice(self): self._slice_start_time = time() - if not self._enabled or not self._global_container_stack: #We shouldn't be slicing. + if not self._enabled or not self._global_container_stack: # We shouldn't be slicing. # try again in a short time self._change_timer.start() return @@ -143,10 +147,10 @@ class CuraEngineBackend(Backend): self._stored_layer_data = [] self._stored_optimized_layer_data = [] - if self._slicing: #We were already slicing. Stop the old job. + if self._slicing: # We were already slicing. Stop the old job. self._terminate() - if self._process_layers_job: #We were processing layers. Stop that, the layers are going to change soon. + if self._process_layers_job: # We were processing layers. Stop that, the layers are going to change soon. self._process_layers_job.abort() self._process_layers_job = None @@ -183,7 +187,7 @@ class CuraEngineBackend(Backend): self._process.terminate() Logger.log("d", "Engine process is killed. Received return code %s", self._process.wait()) self._process = None - except Exception as e: # terminating a process that is already terminating causes an exception, silently ignore this. + except Exception as e: # terminating a process that is already terminating causes an exception, silently ignore this. Logger.log("d", "Exception occurred while trying to kill the engine %s", str(e)) ## Event handler to call when the job to initiate the slicing process is @@ -204,7 +208,7 @@ class CuraEngineBackend(Backend): if job.getResult() == StartSliceJob.StartJobResult.SettingError: if Application.getInstance().getPlatformActivity: - self._error_message = Message(catalog.i18nc("@info:status", "Unable to slice. Please check your setting values for errors."), lifetime = 10) + self._error_message = Message(catalog.i18nc("@info:status", "Unable to slice. Please check your setting values for errors.")) self._error_message.show() self.backendStateChange.emit(BackendState.Error) else: @@ -213,7 +217,7 @@ class CuraEngineBackend(Backend): if job.getResult() == StartSliceJob.StartJobResult.NothingToSlice: if Application.getInstance().getPlatformActivity: - self._error_message = Message(catalog.i18nc("@info:status", "Unable to slice. No suitable objects found."), lifetime = 10) + self._error_message = Message(catalog.i18nc("@info:status", "Unable to slice. No suitable objects found.")) self._error_message.show() self.backendStateChange.emit(BackendState.Error) else: @@ -252,6 +256,9 @@ class CuraEngineBackend(Backend): return super()._onSocketError(error) + if error.getErrorCode() == Arcus.ErrorCode.Debug: + return + self._terminate() if error.getErrorCode() not in [Arcus.ErrorCode.BindFailedError, Arcus.ErrorCode.ConnectionResetError, Arcus.ErrorCode.Debug]: @@ -262,7 +269,7 @@ class CuraEngineBackend(Backend): # \param instance The setting instance that has changed. # \param property The property of the setting instance that has changed. def _onSettingChanged(self, instance, property): - if property == "value": #Only reslice if the value has changed. + if property == "value": # Only reslice if the value has changed. self._onChanged() ## Called when a sliced layer data message is received from the engine. @@ -313,7 +320,7 @@ class CuraEngineBackend(Backend): ## Called when a print time message is received from the engine. # - # \param message The protobuf message containing the print time and + # \param message The protobuff message containing the print time and # material amount per extruder def _onPrintTimeMaterialEstimates(self, message): material_amounts = [] @@ -348,8 +355,8 @@ class CuraEngineBackend(Backend): # # \param tool The tool that the user is using. def _onToolOperationStarted(self, tool): - self._terminate() # Do not continue slicing once a tool has started - self._enabled = False # Do not reslice when a tool is doing it's 'thing' + self._terminate() # Do not continue slicing once a tool has started + self._enabled = False # Do not reslice when a tool is doing it's 'thing' ## Called when the user stops using some tool. # @@ -357,13 +364,13 @@ class CuraEngineBackend(Backend): # # \param tool The tool that the user was using. def _onToolOperationStopped(self, tool): - self._enabled = True # Tool stop, start listening for changes again. + self._enabled = True # Tool stop, start listening for changes again. ## Called when the user changes the active view mode. def _onActiveViewChanged(self): if Application.getInstance().getController().getActiveView(): view = Application.getInstance().getController().getActiveView() - if view.getPluginId() == "LayerView": #If switching to layer view, we should process the layers if that hasn't been done yet. + if view.getPluginId() == "LayerView": # If switching to layer view, we should process the layers if that hasn't been done yet. self._layer_view_active = True # There is data and we're not slicing at the moment # if we are slicing, there is no need to re-calculate the data as it will be invalid in a moment. @@ -388,22 +395,35 @@ class CuraEngineBackend(Backend): if self._global_container_stack: self._global_container_stack.propertyChanged.disconnect(self._onSettingChanged) self._global_container_stack.containersChanged.disconnect(self._onChanged) + extruders = list(ExtruderManager.getInstance().getMachineExtruders(self._global_container_stack.getId())) + if extruders: + for extruder in extruders: + extruder.propertyChanged.disconnect(self._onSettingChanged) self._global_container_stack = Application.getInstance().getGlobalContainerStack() if self._global_container_stack: - self._global_container_stack.propertyChanged.connect(self._onSettingChanged) #Note: Only starts slicing when the value changed. + self._global_container_stack.propertyChanged.connect(self._onSettingChanged) # Note: Only starts slicing when the value changed. self._global_container_stack.containersChanged.connect(self._onChanged) + extruders = list(ExtruderManager.getInstance().getMachineExtruders(self._global_container_stack.getId())) + if extruders: + for extruder in extruders: + extruder.propertyChanged.connect(self._onSettingChanged) self._onActiveExtruderChanged() self._onChanged() def _onActiveExtruderChanged(self): + if self._global_container_stack: + # Connect all extruders of the active machine. This might cause a few connects that have already happend, + # but that shouldn't cause issues as only new / unique connections are added. + extruders = list(ExtruderManager.getInstance().getMachineExtruders(self._global_container_stack.getId())) + if extruders: + for extruder in extruders: + extruder.propertyChanged.connect(self._onSettingChanged) if self._active_extruder_stack: - self._active_extruder_stack.propertyChanged.disconnect(self._onSettingChanged) self._active_extruder_stack.containersChanged.disconnect(self._onChanged) self._active_extruder_stack = cura.Settings.ExtruderManager.getInstance().getActiveExtruderStack() if self._active_extruder_stack: - self._active_extruder_stack.propertyChanged.connect(self._onSettingChanged) # Note: Only starts slicing when the value changed. self._active_extruder_stack.containersChanged.connect(self._onChanged) diff --git a/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py b/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py index c2f73cf5b7..7443340c5b 100644 --- a/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py +++ b/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py @@ -56,10 +56,9 @@ class ProcessSlicedLayersJob(Job): ## Remove old layer data (if any) for node in DepthFirstIterator(self._scene.getRoot()): - if type(node) is SceneNode and node.getMeshData(): - if node.callDecoration("getLayerData"): - self._scene.getRoot().removeChild(node) - Job.yieldThread() + if node.callDecoration("getLayerData"): + node.getParent().removeChild(node) + break if self._abort_requested: if self._progress: self._progress.hide() @@ -74,7 +73,7 @@ class ProcessSlicedLayersJob(Job): # instead simply offset all other layers so the lowest layer is always 0. min_layer_number = 0 for layer in self._layers: - if(layer.id < min_layer_number): + if layer.id < min_layer_number: min_layer_number = layer.id current_layer = 0 @@ -98,7 +97,7 @@ class ProcessSlicedLayersJob(Job): points = numpy.fromstring(polygon.points, dtype="f4") # Convert bytearray to numpy array if polygon.point_type == 0: # Point2D points = points.reshape((-1,2)) # We get a linear list of pairs that make up the points, so make numpy interpret them correctly. - else: # Point3D + else: # Point3D points = points.reshape((-1,3)) line_widths = numpy.fromstring(polygon.line_width, dtype="f4") # Convert bytearray to numpy array @@ -108,15 +107,14 @@ class ProcessSlicedLayersJob(Job): # This uses manual array creation + copy rather than numpy.insert since this is # faster. new_points = numpy.empty((len(points), 3), numpy.float32) - if polygon.point_type == 0: # Point2D - new_points[:,0] = points[:,0] - new_points[:,1] = layer.height/1000 # layer height value is in backend representation - new_points[:,2] = -points[:,1] + if polygon.point_type == 0: # Point2D + new_points[:, 0] = points[:, 0] + new_points[:, 1] = layer.height / 1000 # layer height value is in backend representation + new_points[:, 2] = -points[:, 1] else: # Point3D - new_points[:,0] = points[:,0] - new_points[:,1] = points[:,2] - new_points[:,2] = -points[:,1] - + new_points[:, 0] = points[:, 0] + new_points[:, 1] = points[:, 2] + new_points[:, 2] = -points[:, 1] this_poly = LayerPolygon.LayerPolygon(layer_data, extruder, line_types, new_points, line_widths) this_poly.buildCache() @@ -185,4 +183,3 @@ class ProcessSlicedLayersJob(Job): else: if self._progress: self._progress.hide() - diff --git a/plugins/CuraEngineBackend/StartSliceJob.py b/plugins/CuraEngineBackend/StartSliceJob.py index a726e239e0..5b948c90ab 100644 --- a/plugins/CuraEngineBackend/StartSliceJob.py +++ b/plugins/CuraEngineBackend/StartSliceJob.py @@ -13,6 +13,7 @@ from UM.Scene.SceneNode import SceneNode from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator from UM.Settings.Validator import ValidatorState +from UM.Settings.SettingRelation import RelationType from cura.OneAtATimeIterator import OneAtATimeIterator @@ -24,6 +25,7 @@ class StartJobResult(IntEnum): SettingError = 3 NothingToSlice = 4 + ## Formatter class that handles token expansion in start/end gcod class GcodeStartEndFormatter(Formatter): def get_value(self, key, args, kwargs): # [CodeStyle: get_value is an overridden function from the Formatter class] @@ -37,6 +39,7 @@ class GcodeStartEndFormatter(Formatter): Logger.log("w", "Incorrectly formatted placeholder '%s' in start/end gcode", key) return "{" + str(key) + "}" + ## Job class that builds up the message of scene data to send to CuraEngine. class StartSliceJob(Job): def __init__(self, slice_message): @@ -71,7 +74,7 @@ class StartSliceJob(Job): return # Don't slice if there is a setting with an error value. - if self._checkStackForErrors(stack): + if not Application.getInstance().getMachineManager().isActiveStackValid: self.setResult(StartJobResult.SettingError) return @@ -123,11 +126,15 @@ class StartSliceJob(Job): if temp_list: object_groups.append(temp_list) + # There are cases when there is nothing to slice. This can happen due to one at a time slicing not being + # able to find a possible sequence or because there are no objects on the build plate (or they are outside + # the build volume) if not object_groups: self.setResult(StartJobResult.NothingToSlice) return self._buildGlobalSettingsMessage(stack) + self._buildGlobalInheritsStackMessage(stack) for extruder_stack in cura.Settings.ExtruderManager.getInstance().getMachineExtruders(stack.getId()): self._buildExtruderMessage(extruder_stack) @@ -171,6 +178,7 @@ class StartSliceJob(Job): Logger.logException("w", "Unable to do token replacement on start/end gcode") return str(value).encode("utf-8") + ## Create extruder message from stack def _buildExtruderMessage(self, stack): message = self._slice_message.addRepeatedMessage("extruders") message.id = int(stack.getMetaDataEntry("position")) @@ -209,11 +217,54 @@ class StartSliceJob(Job): else: setting_message.value = str(value).encode("utf-8") + ## Sends for some settings which extruder they should fallback to if not + # set. + # + # This is only set for settings that have the global_inherits_stack + # property. + # + # \param stack The global stack with all settings, from which to read the + # global_inherits_stack property. + def _buildGlobalInheritsStackMessage(self, stack): + for key in stack.getAllKeys(): + extruder = int(stack.getProperty(key, "global_inherits_stack")) + if extruder >= 0: #Set to a specific extruder. + setting_extruder = self._slice_message.addRepeatedMessage("global_inherits_stack") + setting_extruder.name = key + setting_extruder.extruder = extruder + + ## Check if a node has per object settings and ensure that they are set correctly in the message + # \param node \type{SceneNode} Node to check. + # \param message object_lists message to put the per object settings in def _handlePerObjectSettings(self, node, message): stack = node.callDecoration("getStack") + # Check if the node has a stack attached to it and the stack has any settings in the top container. if stack: - for key in stack.getAllKeys(): + # Check all settings for relations, so we can also calculate the correct values for dependant settings. + changed_setting_keys = set(stack.getTop().getAllKeys()) + for key in stack.getTop().getAllKeys(): + instance = stack.getTop().getInstance(key) + self._addRelations(changed_setting_keys, instance.definition.relations) + Job.yieldThread() + + # Ensure that the engine is aware what the build extruder is + if stack.getProperty("machine_extruder_count", "value") > 1: + changed_setting_keys.add("extruder_nr") + + # Get values for all changed settings + for key in changed_setting_keys: setting = message.addRepeatedMessage("settings") setting.name = key setting.value = str(stack.getProperty(key, "value")).encode("utf-8") Job.yieldThread() + + ## Recursive function to put all settings that require eachother for value changes in a list + # \param relations_set \type{set} Set of keys (strings) of settings that are influenced + # \param relations list of relation objects that need to be checked. + def _addRelations(self, relations_set, relations): + for relation in filter(lambda r: r.role == "value", relations): + if relation.type == RelationType.RequiresTarget: + continue + + relations_set.add(relation.target.key) + self._addRelations(relations_set, relation.target.relations) \ No newline at end of file diff --git a/plugins/GCodeWriter/GCodeWriter.py b/plugins/GCodeWriter/GCodeWriter.py index 11c5dc5f84..8e3be15ffe 100644 --- a/plugins/GCodeWriter/GCodeWriter.py +++ b/plugins/GCodeWriter/GCodeWriter.py @@ -9,6 +9,8 @@ import UM.Settings.ContainerRegistry from cura.CuraApplication import CuraApplication from cura.Settings.ExtruderManager import ExtruderManager +from UM.Settings.InstanceContainer import InstanceContainer + import re #For escaping characters in the settings. import json @@ -61,6 +63,20 @@ class GCodeWriter(MeshWriter): return False + ## Create a new container with container 2 as base and container 1 written over it. + def _createFlattenedContainerInstance(self, instance_container1, instance_container2): + flat_container = InstanceContainer(instance_container2.getName()) + flat_container.setDefinition(instance_container2.getDefinition()) + flat_container.setMetaData(instance_container2.getMetaData()) + + for key in instance_container2.getAllKeys(): + flat_container.setProperty(key, "value", instance_container2.getProperty(key, "value")) + + for key in instance_container1.getAllKeys(): + flat_container.setProperty(key, "value", instance_container1.getProperty(key, "value")) + return flat_container + + ## Serialises a container stack to prepare it for writing at the end of the # g-code. # @@ -74,38 +90,13 @@ class GCodeWriter(MeshWriter): prefix_length = len(prefix) container_with_profile = stack.findContainer({"type": "quality"}) - machine_manager = CuraApplication.getInstance().getMachineManager() - - # Duplicate the current quality profile and update it with any user settings. - flat_quality_id = machine_manager.duplicateContainer(container_with_profile.getId()) - - flat_quality = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(id = flat_quality_id)[0] - flat_quality._dirty = False - user_settings = stack.getTop() - - # We don't want to send out any signals, so disconnect them. - flat_quality.propertyChanged.disconnectAll() - - for key in user_settings.getAllKeys(): - flat_quality.setProperty(key, "value", user_settings.getProperty(key, "value")) - - serialized = flat_quality.serialize() - + flat_global_container = self._createFlattenedContainerInstance(stack.getTop(),container_with_profile) + serialized = flat_global_container.serialize() data = {"global_quality": serialized} - manager = ExtruderManager.getInstance() - for extruder in manager.getMachineExtruders(stack.getId()): + + for extruder in ExtruderManager.getInstance().getMachineExtruders(stack.getId()): extruder_quality = extruder.findContainer({"type": "quality"}) - - flat_extruder_quality_id = machine_manager.duplicateContainer(extruder_quality.getId()) - flat_extruder_quality = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(id=flat_extruder_quality_id)[0] - flat_extruder_quality._dirty = False - extruder_user_settings = extruder.getTop() - - # We don't want to send out any signals, so disconnect them. - flat_extruder_quality.propertyChanged.disconnectAll() - - for key in extruder_user_settings.getAllKeys(): - flat_extruder_quality.setProperty(key, "value", extruder_user_settings.getProperty(key, "value")) + flat_extruder_quality = self._createFlattenedContainerInstance(extruder.getTop(), extruder_quality) extruder_serialized = flat_extruder_quality.serialize() data.setdefault("extruder_quality", []).append(extruder_serialized) diff --git a/plugins/LayerView/LayerView.py b/plugins/LayerView/LayerView.py index 87e1a30834..f60d492ec7 100644 --- a/plugins/LayerView/LayerView.py +++ b/plugins/LayerView/LayerView.py @@ -11,6 +11,7 @@ from UM.Math.Color import Color from UM.Mesh.MeshBuilder import MeshBuilder from UM.Job import Job from UM.Preferences import Preferences +from UM.Logger import Logger from UM.View.RenderBatch import RenderBatch from UM.View.GL.OpenGL import OpenGL @@ -34,7 +35,7 @@ class LayerView(View): self._shader = None self._selection_shader = None self._num_layers = 0 - self._layer_percentage = 0 # what percentage of layers need to be shown (SLider gives value between 0 - 100) + self._layer_percentage = 0 # what percentage of layers need to be shown (Slider gives value between 0 - 100) self._proxy = LayerViewProxy.LayerViewProxy() self._controller.getScene().getRoot().childrenChanged.connect(self._onSceneChanged) self._max_layers = 0 @@ -43,12 +44,14 @@ class LayerView(View): self._current_layer_jumps = None self._top_layers_job = None self._activity = False + self._old_max_layers = 0 Preferences.getInstance().addPreference("view/top_layer_count", 5) + Preferences.getInstance().addPreference("view/only_show_top_layers", False) Preferences.getInstance().preferenceChanged.connect(self._onPreferencesChanged) 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._busy = False def getActivity(self): @@ -100,7 +103,7 @@ class LayerView(View): continue # Render all layers below a certain number as line mesh instead of vertices. - if self._current_layer_num - self._solid_layers > -1: + if self._current_layer_num - self._solid_layers > -1 and not self._only_show_top_layers: start = 0 end = 0 element_counts = layer_data.getElementCounts() @@ -126,14 +129,12 @@ class LayerView(View): if self._current_layer_num > self._max_layers: self._current_layer_num = self._max_layers - self.resetLayerData() self._startUpdateTopLayers() self.currentLayerNumChanged.emit() def calculateMaxLayers(self): scene = self.getController().getScene() - renderer = self.getRenderer() # TODO: @UnusedVariable self._activity = True self._old_max_layers = self._max_layers @@ -199,7 +200,7 @@ class LayerView(View): if not job.getResult(): return - + self.resetLayerData() # Reset the layer data only when job is done. Doing it now prevents "blinking" data. self._current_layer_mesh = job.getResult().get("layers") self._current_layer_jumps = job.getResult().get("jumps") self._controller.getScene().sceneChanged.emit(self._controller.getScene().getRoot()) @@ -207,14 +208,15 @@ class LayerView(View): self._top_layers_job = None def _onPreferencesChanged(self, preference): - if preference != "view/top_layer_count": + if preference != "view/top_layer_count" and preference != "view/only_show_top_layers": return 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.resetLayerData() self._startUpdateTopLayers() + class _CreateTopLayersJob(Job): def __init__(self, scene, layer_number, solid_layers): super().__init__() @@ -242,20 +244,20 @@ class _CreateTopLayersJob(Job): try: layer = layer_data.getLayer(layer_number).createMesh() - except Exception as e: - print(e) + except Exception: + Logger.logException("w", "An exception occurred while creating layer mesh.") return if not layer or layer.getVertices() is None: continue - layer_mesh.addIndices(layer_mesh._vertex_count+layer.getIndices()) + layer_mesh.addIndices(layer_mesh.getVertexCount() + layer.getIndices()) layer_mesh.addVertices(layer.getVertices()) # Scale layer color by a brightness factor based on the current layer number # This will result in a range of 0.5 - 1.0 to multiply colors by. - brightness = numpy.ones((1,4), dtype=numpy.float32) * (2.0 - (i / self._solid_layers)) / 2.0 - brightness[0, 3] = 1.0; + brightness = numpy.ones((1, 4), dtype=numpy.float32) * (2.0 - (i / self._solid_layers)) / 2.0 + brightness[0, 3] = 1.0 layer_mesh.addColors(layer.getColors() * brightness) if self._cancel: @@ -271,7 +273,7 @@ class _CreateTopLayersJob(Job): if not jump_mesh or jump_mesh.getVertices() is None: jump_mesh = None - self.setResult({ "layers": layer_mesh.build(), "jumps": jump_mesh }) + self.setResult({"layers": layer_mesh.build(), "jumps": jump_mesh}) def cancel(self): self._cancel = True diff --git a/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml b/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml index 79f10f508f..482fb73b0f 100644 --- a/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml +++ b/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml @@ -47,7 +47,9 @@ Item { id: extruders_model onRowsInserted: extruderSelector.visible = extruders_model.rowCount() > 1 onModelReset: extruderSelector.visible = extruders_model.rowCount() > 1 + onModelChanged: extruderSelector.color = extruders_model.getItem(extruderSelector.currentIndex).colour } + property string color: extruders_model.getItem(extruderSelector.currentIndex).colour visible: extruders_model.rowCount() > 1 textRole: "name" width: UM.Theme.getSize("setting_control").width @@ -88,7 +90,7 @@ Item { anchors.leftMargin: UM.Theme.getSize("default_lining").width anchors.verticalCenter: parent.verticalCenter - color: extruders_model.getItem(extruderSelector.currentIndex).colour + color: extruderSelector.color border.width: UM.Theme.getSize("default_lining").width border.color: !enabled ? UM.Theme.getColor("setting_control_disabled_border") : UM.Theme.getColor("setting_control_border") } @@ -125,7 +127,11 @@ Item { } } } - + Component.onCompleted: + { + // Ensure primary extruder is set as active + UM.ActiveTool.setProperty("SelectedActiveExtruder", extruders_model.getItem(0).id) + } onActivated: UM.ActiveTool.setProperty("SelectedActiveExtruder", extruders_model.getItem(index).id); onModelChanged: updateCurrentIndex(); @@ -136,6 +142,7 @@ Item { if(extruders_model.getItem(i).id == UM.ActiveTool.properties.getValue("SelectedActiveExtruder")) { extruderSelector.currentIndex = i; + extruderSelector.color = extruders_model.getItem(i).colour; return; } } @@ -243,6 +250,7 @@ Item { key: model.key watchedProperties: [ "value", "enabled", "validationState" ] storeIndex: 0 + removeUnusedValue: false } } } diff --git a/plugins/SolidView/SolidView.py b/plugins/SolidView/SolidView.py index 2182a0748a..0329fb53e2 100644 --- a/plugins/SolidView/SolidView.py +++ b/plugins/SolidView/SolidView.py @@ -3,6 +3,7 @@ from UM.View.View import View 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 @@ -57,25 +58,28 @@ class SolidView(View): # renderer.queueNode(scene.getRoot(), mesh = node.getBoundingBoxMesh(),mode = Renderer.RenderLines) uniforms = {} - if self._extruders_model.rowCount() > 0: + if self._extruders_model.rowCount() == 0: + material = Application.getInstance().getGlobalContainerStack().findContainer({ "type": "material" }) + material_color = material.getMetaDataEntry("color_code", default = self._extruders_model.defaultColours[0]) if material else self._extruders_model.defaultColours[0] + else: # Get color to render this mesh in from ExtrudersModel extruder_index = 0 extruder_id = node.callDecoration("getActiveExtruder") if extruder_id: extruder_index = max(0, self._extruders_model.find("id", extruder_id)) - extruder_color = self._extruders_model.getItem(extruder_index)["colour"] - try: - # Colors are passed as rgb hex strings (eg "#ffffff"), and the shader needs - # an rgba list of floats (eg [1.0, 1.0, 1.0, 1.0]) - uniforms["diffuse_color"] = [ - int(extruder_color[1:3], 16) / 255, - int(extruder_color[3:5], 16) / 255, - int(extruder_color[5:7], 16) / 255, - 1.0 - ] - except ValueError: - pass + material_color = self._extruders_model.getItem(extruder_index)["colour"] + try: + # Colors are passed as rgb hex strings (eg "#ffffff"), and the shader needs + # an rgba list of floats (eg [1.0, 1.0, 1.0, 1.0]) + uniforms["diffuse_color"] = [ + int(material_color[1:3], 16) / 255, + int(material_color[3:5], 16) / 255, + int(material_color[5:7], 16) / 255, + 1.0 + ] + except ValueError: + pass if hasattr(node, "_outside_buildarea"): if node._outside_buildarea: @@ -84,7 +88,7 @@ class SolidView(View): renderer.queueNode(node, shader = self._enabled_shader, uniforms = uniforms) else: renderer.queueNode(node, material = self._enabled_shader, uniforms = uniforms) - if node.callDecoration("isGroup"): + if node.callDecoration("isGroup") and Selection.isSelected(node): renderer.queueNode(scene.getRoot(), mesh = node.getBoundingBoxMesh(), mode = Renderer.RenderLines) def endRendering(self): diff --git a/plugins/UltimakerMachineActions/BedLevelMachineAction.py b/plugins/UltimakerMachineActions/BedLevelMachineAction.py index 7dad841340..12df7d6843 100644 --- a/plugins/UltimakerMachineActions/BedLevelMachineAction.py +++ b/plugins/UltimakerMachineActions/BedLevelMachineAction.py @@ -18,6 +18,11 @@ class BedLevelMachineAction(MachineAction): pass def _reset(self): + self._bed_level_position = 0 + pass + + @pyqtSlot() + def startBedLeveling(self): self._bed_level_position = 0 printer_output_devices = self._getPrinterOutputDevices() if printer_output_devices: @@ -52,4 +57,5 @@ class BedLevelMachineAction(MachineAction): output_device.moveHead(0, 0, -3) self._bed_level_position += 1 elif self._bed_level_position >= 3: + output_device.sendCommand("M18") # Turn off all motors so the user can move the axes self.setFinished() \ No newline at end of file diff --git a/plugins/UltimakerMachineActions/BedLevelMachineAction.qml b/plugins/UltimakerMachineActions/BedLevelMachineAction.qml index d043c20df5..c7c4074120 100644 --- a/plugins/UltimakerMachineActions/BedLevelMachineAction.qml +++ b/plugins/UltimakerMachineActions/BedLevelMachineAction.qml @@ -47,37 +47,37 @@ Cura.MachineAction text: catalog.i18nc("@label", "For every position; insert a piece of paper under the nozzle and adjust the print bed height. The print bed height is right when the paper is slightly gripped by the tip of the nozzle.") } - Item + Row { id: bedlevelingWrapper anchors.top: bedlevelingText.bottom anchors.topMargin: UM.Theme.getSize("default_margin").height anchors.horizontalCenter: parent.horizontalCenter - height: skipBedlevelingButton.height - width: bedlevelingButton.width + skipBedlevelingButton.width + UM.Theme.getSize("default_margin").height < bedLevelMachineAction.width ? bedlevelingButton.width + skipBedlevelingButton.width + UM.Theme.getSize("default_margin").height : bedLevelMachineAction.width + width: childrenRect.width + spacing: UM.Theme.getSize("default_margin").width + Button { - id: bedlevelingButton - anchors.top: parent.top - anchors.left: parent.left - text: catalog.i18nc("@action:button","Move to Next Position"); + id: startBedLevelingButton + text: catalog.i18nc("@action:button","Start Bed Leveling") onClicked: { - manager.moveToNextLevelPosition() + startBedLevelingButton.visible = false; + bedlevelingButton.visible = true; + checkupMachineAction.heatupHotendStarted = false; + checkupMachineAction.heatupBedStarted = false; + manager.startCheck(); } } Button { - id: skipBedlevelingButton - anchors.top: parent.width < bedLevelMachineAction.width ? parent.top : bedlevelingButton.bottom - anchors.topMargin: parent.width < bedLevelMachineAction.width ? 0 : UM.Theme.getSize("default_margin").height/2 - anchors.left: parent.width < bedLevelMachineAction.width ? bedlevelingButton.right : parent.left - anchors.leftMargin: parent.width < bedLevelMachineAction.width ? UM.Theme.getSize("default_margin").width : 0 - text: catalog.i18nc("@action:button","Skip bed leveling"); + id: bedlevelingButton + text: catalog.i18nc("@action:button","Move to Next Position") + visible: false onClicked: { - manager.setFinished() + manager.moveToNextLevelPosition(); } } } diff --git a/plugins/UltimakerMachineActions/UMOCheckupMachineAction.py b/plugins/UltimakerMachineActions/UMOCheckupMachineAction.py index b46c92a82c..355b4dd0c1 100644 --- a/plugins/UltimakerMachineActions/UMOCheckupMachineAction.py +++ b/plugins/UltimakerMachineActions/UMOCheckupMachineAction.py @@ -155,6 +155,7 @@ class UMOCheckupMachineAction(MachineAction): if output_devices: self._output_device = output_devices[0] try: + self._output_device.sendCommand("M18") # Turn off all motors so the user can move the axes self._output_device.startPollEndstop() self._output_device.bedTemperatureChanged.connect(self.bedTemperatureChanged) self._output_device.hotendTemperaturesChanged.connect(self.hotendTemperatureChanged) diff --git a/plugins/UltimakerMachineActions/UMOCheckupMachineAction.qml b/plugins/UltimakerMachineActions/UMOCheckupMachineAction.qml index 14ed1e2c51..0bdbc27527 100644 --- a/plugins/UltimakerMachineActions/UMOCheckupMachineAction.qml +++ b/plugins/UltimakerMachineActions/UMOCheckupMachineAction.qml @@ -39,38 +39,26 @@ Cura.MachineAction text: catalog.i18nc("@label", "It's a good idea to do a few sanity checks on your Ultimaker. You can skip this step if you know your machine is functional"); } - Item + Row { id: startStopButtons anchors.top: pageDescription.bottom anchors.topMargin: UM.Theme.getSize("default_margin").height anchors.horizontalCenter: parent.horizontalCenter - height: childrenRect.height - width: startCheckButton.width + skipCheckButton.width + UM.Theme.getSize("default_margin").height < checkupMachineAction.width ? startCheckButton.width + skipCheckButton.width + UM.Theme.getSize("default_margin").height : checkupMachineAction.width + width: childrenRect.width + spacing: UM.Theme.getSize("default_margin").width Button { id: startCheckButton - anchors.top: parent.top - anchors.left: parent.left text: catalog.i18nc("@action:button","Start Printer Check"); onClicked: { - checkupMachineAction.heatupHotendStarted = false - checkupMachineAction.heatupBedStarted = false - manager.startCheck() + checkupMachineAction.heatupHotendStarted = false; + checkupMachineAction.heatupBedStarted = false; + manager.startCheck(); + startCheckButton.visible = false; } } - - Button - { - id: skipCheckButton - anchors.top: parent.width < checkupMachineAction.width ? parent.top : startCheckButton.bottom - anchors.topMargin: parent.width < checkupMachineAction.width ? 0 : UM.Theme.getSize("default_margin").height/2 - anchors.left: parent.width < checkupMachineAction.width ? startCheckButton.right : parent.left - anchors.leftMargin: parent.width < checkupMachineAction.width ? UM.Theme.getSize("default_margin").width : 0 - text: catalog.i18nc("@action:button", "Skip Printer Check"); - onClicked: manager.setFinished() - } } Item diff --git a/plugins/UltimakerMachineActions/UMOUpgradeSelection.py b/plugins/UltimakerMachineActions/UMOUpgradeSelection.py index 2e13ec50d5..5d00fd5e3a 100644 --- a/plugins/UltimakerMachineActions/UMOUpgradeSelection.py +++ b/plugins/UltimakerMachineActions/UMOUpgradeSelection.py @@ -12,6 +12,9 @@ class UMOUpgradeSelection(MachineAction): super().__init__("UMOUpgradeSelection", catalog.i18nc("@action", "Select upgrades")) self._qml_url = "UMOUpgradeSelectionMachineAction.qml" + def _reset(self): + self.heatedBedChanged.emit() + heatedBedChanged = pyqtSignal() @pyqtProperty(bool, notify = heatedBedChanged) diff --git a/plugins/UltimakerMachineActions/UMOUpgradeSelectionMachineAction.qml b/plugins/UltimakerMachineActions/UMOUpgradeSelectionMachineAction.qml index d960bcf976..234e53c0a6 100644 --- a/plugins/UltimakerMachineActions/UMOUpgradeSelectionMachineAction.qml +++ b/plugins/UltimakerMachineActions/UMOUpgradeSelectionMachineAction.qml @@ -44,7 +44,7 @@ Cura.MachineAction text: catalog.i18nc("@label", "Heated bed (official kit or self-built)") checked: manager.hasHeatedBed - onClicked: manager.hasHeatedBed ? manager.removeHeatedBed() : manager.addHeatedBed() + onClicked: checked ? manager.addHeatedBed() : manager.removeHeatedBed() } UM.I18nCatalog { id: catalog; name: "cura"; } diff --git a/plugins/UltimakerMachineActions/UpgradeFirmwareMachineAction.qml b/plugins/UltimakerMachineActions/UpgradeFirmwareMachineAction.qml index 37e4eae2d3..0c9b80c010 100644 --- a/plugins/UltimakerMachineActions/UpgradeFirmwareMachineAction.qml +++ b/plugins/UltimakerMachineActions/UpgradeFirmwareMachineAction.qml @@ -56,29 +56,21 @@ Cura.MachineAction wrapMode: Text.WordWrap text: catalog.i18nc("@label", "Cura requires these new features and thus your firmware will most likely need to be upgraded. You can do so now."); } - Item + Row { anchors.top: upgradeText2.bottom anchors.topMargin: UM.Theme.getSize("default_margin").height anchors.horizontalCenter: parent.horizontalCenter - width: upgradeButton.width + skipUpgradeButton.width + UM.Theme.getSize("default_margin").height < upgradeFirmwareMachineAction.width ? upgradeButton.width + skipUpgradeButton.width + UM.Theme.getSize("default_margin").height : upgradeFirmwareMachineAction.width + width: childrenRect.width + spacing: UM.Theme.getSize("default_margin").width Button { id: upgradeButton - anchors.top: parent.top - anchors.left: parent.left text: catalog.i18nc("@action:button","Upgrade to Marlin Firmware"); - onClicked: Cura.USBPrinterManager.updateAllFirmware() - } - Button - { - id: skipUpgradeButton - anchors.top: parent.width < upgradeFirmwareMachineAction.width ? parent.top : upgradeButton.bottom - anchors.topMargin: parent.width < upgradeFirmwareMachineAction.width ? 0 : UM.Theme.getSize("default_margin").height / 2 - anchors.left: parent.width < upgradeFirmwareMachineAction.width ? upgradeButton.right : parent.left - anchors.leftMargin: parent.width < upgradeFirmwareMachineAction.width ? UM.Theme.getSize("default_margin").width : 0 - text: catalog.i18nc("@action:button", "Skip Upgrade"); - onClicked: manager.setFinished() + onClicked: + { + Cura.USBPrinterManager.updateAllFirmware() + } } } } diff --git a/resources/definitions/fdmprinter.def.json b/resources/definitions/fdmprinter.def.json index 950c856a6e..e7445b6061 100644 --- a/resources/definitions/fdmprinter.def.json +++ b/resources/definitions/fdmprinter.def.json @@ -150,6 +150,8 @@ "label": "Number extruders", "description": "Number of extruder trains. An extruder train is the combination of a feeder, bowden tube, and nozzle.", "default_value": 1, + "minimum_value": "1", + "maximum_value": "16", "type": "int", "settable_per_mesh": false, "settable_per_extruder": false, @@ -630,9 +632,8 @@ "type": "float", "enabled": "support_enable", "value": "line_width", - "global_inherits_stack": "support_extruder_nr", "settable_per_mesh": false, - "settable_per_extruder": false + "settable_per_extruder": true }, "support_roof_line_width": { @@ -645,9 +646,8 @@ "type": "float", "enabled": "support_roof_enable", "value": "line_width", - "global_inherits_stack": "support_extruder_nr", "settable_per_mesh": false, - "settable_per_extruder": false + "settable_per_extruder": true }, "prime_tower_line_width": { @@ -1238,30 +1238,28 @@ "default_value": false, "enabled": "retraction_enable", "settable_per_mesh": false, - "settable_per_extruder": true, - "children": { - "retraction_hop_only_when_collides": { - "label": "Z Hop Only Over Printed Parts", - "description": "Only perform a Z Hop when moving over printed parts which cannot be avoided by horizontal motion by Avoid Printed Parts when Traveling.", - "type": "bool", - "default_value": false, - "enabled": "retraction_enable and retraction_hop_enabled and travel_avoid_other_parts", - "settable_per_mesh": false, - "settable_per_extruder": true - }, - "retraction_hop": { - "label": "Z Hop Height", - "description": "The height difference when performing a Z Hop.", - "unit": "mm", - "type": "float", - "default_value": 1, - "minimum_value_warning": "-0.0001", - "maximum_value_warning": "10", - "enabled": "retraction_enable and retraction_hop_enabled", - "settable_per_mesh": false, - "settable_per_extruder": true - } - } + "settable_per_extruder": true + }, + "retraction_hop_only_when_collides": { + "label": "Z Hop Only Over Printed Parts", + "description": "Only perform a Z Hop when moving over printed parts which cannot be avoided by horizontal motion by Avoid Printed Parts when Traveling.", + "type": "bool", + "default_value": false, + "enabled": "retraction_enable and retraction_hop_enabled and travel_avoid_other_parts", + "settable_per_mesh": false, + "settable_per_extruder": true + }, + "retraction_hop": { + "label": "Z Hop Height", + "description": "The height difference when performing a Z Hop.", + "unit": "mm", + "type": "float", + "default_value": 1, + "minimum_value_warning": "-0.0001", + "maximum_value_warning": "10", + "enabled": "retraction_enable and retraction_hop_enabled", + "settable_per_mesh": false, + "settable_per_extruder": true }, "material_standby_temperature": { @@ -1444,10 +1442,9 @@ "maximum_value_warning": "150", "default_value": 60, "value": "speed_print", - "global_inherits_stack": "support_extruder_nr", "enabled": "support_enable", "settable_per_mesh": false, - "settable_per_extruder": false, + "settable_per_extruder": true, "children": { "speed_support_infill": @@ -1461,10 +1458,9 @@ "maximum_value": "299792458000", "maximum_value_warning": "150", "value": "speed_support", - "global_inherits_stack": "support_extruder_nr", "enabled": "support_enable", "settable_per_mesh": false, - "settable_per_extruder": false + "settable_per_extruder": true }, "speed_support_roof": { @@ -1478,9 +1474,8 @@ "maximum_value_warning": "150", "enabled": "support_roof_enable and support_enable", "value": "speed_support / 1.5", - "global_inherits_stack": "support_extruder_nr", "settable_per_mesh": false, - "settable_per_extruder": false + "settable_per_extruder": true } } }, @@ -1516,14 +1511,44 @@ }, "speed_layer_0": { "label": "Initial Layer Speed", - "description": "The print speed for the initial layer. A lower value is advised to improve adhesion to the build plate.", + "description": "The speed for the initial layer. A lower value is advised to improve adhesion to the build plate.", "unit": "mm/s", "type": "float", "default_value": 30, "minimum_value": "0.1", "maximum_value": "299792458000", "maximum_value_warning": "300", - "settable_per_mesh": true + "settable_per_mesh": true, + "children": + { + "speed_print_layer_0": + { + "label": "Initial Layer Print Speed", + "description": "The speed of printing for the initial layer. A lower value is advised to improve adhesion to the build plate.", + "unit": "mm/s", + "type": "float", + "default_value": 30, + "value": "speed_layer_0", + "minimum_value": "0.1", + "maximum_value": "299792458000", + "maximum_value_warning": "300", + "settable_per_mesh": true + }, + "speed_travel_layer_0": + { + "label": "Initial Layer Travel Speed", + "description": "The speed of travel moves in the initial layer. A lower value is advised to prevent pulling previously printed parts away from the build plate.", + "unit": "mm/s", + "type": "float", + "default_value": 60, + "value": "speed_layer_0 * speed_travel / speed_print", + "minimum_value": "0.1", + "maximum_value": "299792458000", + "maximum_value_warning": "300", + "settable_per_mesh": true, + "settable_per_extruder": true + } + } }, "skirt_brim_speed": { "label": "Skirt/Brim Speed", @@ -1551,8 +1576,8 @@ "settable_per_mesh": false, "settable_per_extruder": false }, - - + + "acceleration_enabled": { "label": "Enable Acceleration Control", "description": "Enables adjusting the print head acceleration. Increasing the accelerations can reduce printing time at the cost of print quality.", @@ -1650,10 +1675,9 @@ "maximum_value_warning": "10000", "default_value": 3000, "value": "acceleration_print", - "global_inherits_stack": "support_extruder_nr", "enabled": "acceleration_enabled and support_enable", "settable_per_mesh": false, - "settable_per_extruder": false, + "settable_per_extruder": true, "children": { "acceleration_support_infill": { "label": "Support Infill Acceleration", @@ -1662,13 +1686,12 @@ "type": "float", "default_value": 3000, "value": "acceleration_support", - "global_inherits_stack": "support_extruder_nr", "minimum_value": "0.1", "minimum_value_warning": "100", "maximum_value_warning": "10000", "enabled": "acceleration_enabled and support_enable", "settable_per_mesh": false, - "settable_per_extruder": false + "settable_per_extruder": true }, "acceleration_support_roof": { "label": "Support Roof Acceleration", @@ -1677,13 +1700,12 @@ "type": "float", "default_value": 3000, "value": "acceleration_support", - "global_inherits_stack": "support_extruder_nr", "minimum_value": "0.1", "minimum_value_warning": "100", "maximum_value_warning": "10000", "enabled": "acceleration_enabled and support_roof_enable and support_enable", "settable_per_mesh": false, - "settable_per_extruder": false + "settable_per_extruder": true } } }, @@ -1742,8 +1764,8 @@ "settable_per_mesh": false }, - - + + "jerk_enabled": { "label": "Enable Jerk Control", "description": "Enables adjusting the jerk of print head when the velocity in the X or Y axis changes. Increasing the jerk can reduce printing time at the cost of print quality.", @@ -1841,10 +1863,9 @@ "maximum_value_warning": "50", "default_value": 20, "value": "jerk_print", - "global_inherits_stack": "support_extruder_nr", "enabled": "jerk_enabled and support_enable", "settable_per_mesh": false, - "settable_per_extruder": false, + "settable_per_extruder": true, "children": { "jerk_support_infill": { "label": "Support Infill Jerk", @@ -1853,13 +1874,12 @@ "type": "float", "default_value": 20, "value": "jerk_support", - "global_inherits_stack": "support_extruder_nr", "minimum_value": "0.1", "minimum_value_warning": "5", "maximum_value_warning": "50", "enabled": "jerk_enabled and support_enable", "settable_per_mesh": false, - "settable_per_extruder": false + "settable_per_extruder": true }, "jerk_support_roof": { "label": "Support Roof Jerk", @@ -1868,13 +1888,12 @@ "type": "float", "default_value": 20, "value": "jerk_support", - "global_inherits_stack": "support_extruder_nr", "minimum_value": "0.1", "minimum_value_warning": "5", "maximum_value_warning": "50", "enabled": "jerk_enabled and support_roof_enable and support_enable", "settable_per_mesh": false, - "settable_per_extruder": false + "settable_per_extruder": true } } }, @@ -2180,7 +2199,7 @@ "default_value": "zigzag", "enabled": "support_enable", "settable_per_mesh": false, - "settable_per_extruder": false + "settable_per_extruder": true }, "support_connect_zigzags": { @@ -2190,7 +2209,7 @@ "default_value": true, "enabled": "support_enable and (support_pattern == \"zigzag\")", "settable_per_mesh": false, - "settable_per_extruder": false + "settable_per_extruder": true }, "support_infill_rate": { @@ -2203,7 +2222,7 @@ "default_value": 15, "enabled": "support_enable", "settable_per_mesh": false, - "settable_per_extruder": false, + "settable_per_extruder": true, "children": { "support_line_distance": { @@ -2216,7 +2235,7 @@ "enabled": "support_enable", "value": "(support_line_width * 100) / support_infill_rate * (2 if support_pattern == \"grid\" else (3 if support_pattern == \"triangles\" else 1))", "settable_per_mesh": false, - "settable_per_extruder": false + "settable_per_extruder": true } } }, @@ -2389,7 +2408,7 @@ "maximum_value_warning": "100", "enabled":"support_roof_enable and support_enable", "settable_per_mesh": false, - "settable_per_extruder": false, + "settable_per_extruder": true, "children": { "support_roof_line_distance": @@ -2403,7 +2422,7 @@ "value": "0 if support_roof_density == 0 else (support_roof_line_width * 100) / support_roof_density * (2 if support_roof_pattern == \"grid\" else (3 if support_roof_pattern == \"triangles\" else 1))", "enabled": "support_roof_enable and support_enable", "settable_per_mesh": false, - "settable_per_extruder": false + "settable_per_extruder": true } } }, @@ -2423,7 +2442,7 @@ "default_value": "concentric", "enabled": "support_roof_enable and support_enable", "settable_per_mesh": false, - "settable_per_extruder": false + "settable_per_extruder": true }, "support_use_towers": { @@ -3357,9 +3376,9 @@ "type": "float", "minimum_value": "0", "maximum_value_warning": "9999", - "default_value": 0, - "value": "9999 if draft_shield_height_limitation == 'full' and draft_shield_enabled else 0.0", - "enabled": "draft_shield_height_limitation == \"limited\"", + "default_value": 10, + "value": "10", + "enabled": "draft_shield_enabled and draft_shield_height_limitation == \"limited\"", "settable_per_mesh": false, "settable_per_extruder": false }, diff --git a/resources/definitions/printrbot_simple.def.json b/resources/definitions/printrbot_simple.def.json index 0116ba6244..a1963fe20e 100644 --- a/resources/definitions/printrbot_simple.def.json +++ b/resources/definitions/printrbot_simple.def.json @@ -7,6 +7,7 @@ "visible": true, "author": "Calvindog717", "manufacturer": "PrintrBot", + "category": "Other", "file_formats": "text/x-gcode" }, diff --git a/resources/definitions/ultimaker2.def.json b/resources/definitions/ultimaker2.def.json index 3712963f23..d8e73a7e14 100644 --- a/resources/definitions/ultimaker2.def.json +++ b/resources/definitions/ultimaker2.def.json @@ -57,7 +57,7 @@ "default_value": 2 }, "gantry_height": { - "default_value": 55 + "default_value": 48 }, "machine_use_extruder_offset_to_offset_coords": { "default_value": true diff --git a/resources/definitions/ultimaker_original.def.json b/resources/definitions/ultimaker_original.def.json index d2174ece80..0815aeee02 100644 --- a/resources/definitions/ultimaker_original.def.json +++ b/resources/definitions/ultimaker_original.def.json @@ -15,7 +15,7 @@ "preferred_material": "*pla*", "preferred_quality": "*normal*", "first_start_actions": ["UMOUpgradeSelection", "UMOCheckup", "BedLevel"], - "supported_actions": ["UMOCheckup", "UpgradeFirmware", "BedLevel", "UMOUpgradeSelection"] + "supported_actions": ["UMOUpgradeSelection", "UMOCheckup", "BedLevel", "UpgradeFirmware"] }, "overrides": { diff --git a/resources/qml/Actions.qml b/resources/qml/Actions.qml index 67bc5fe149..ed88433e33 100644 --- a/resources/qml/Actions.qml +++ b/resources/qml/Actions.qml @@ -27,6 +27,7 @@ Item property alias multiplyObject: multiplyObjectAction; + property alias selectAll: selectAllAction; property alias deleteAll: deleteAllAction; property alias reloadAll: reloadAllAction; property alias resetAllTranslation: resetAllTranslationAction; @@ -119,7 +120,7 @@ Item Action { id: updateProfileAction; - enabled: Cura.MachineManager.isGlobalStackValid && Cura.MachineManager.hasUserSettings && !Cura.MachineManager.isReadOnly(Cura.MachineManager.activeQualityId) + enabled: Cura.MachineManager.isActiveStackValid && Cura.MachineManager.hasUserSettings && !Cura.MachineManager.isReadOnly(Cura.MachineManager.activeQualityId) text: catalog.i18nc("@action:inmenu menubar:profile","&Update profile with current settings"); onTriggered: Cura.MachineManager.updateQualityContainerFromUserContainer() } @@ -135,7 +136,7 @@ Item Action { id: addProfileAction; - enabled: Cura.MachineManager.isGlobalStackValid && Cura.MachineManager.hasUserSettings + enabled: Cura.MachineManager.isActiveStackValid && Cura.MachineManager.hasUserSettings text: catalog.i18nc("@action:inmenu menubar:profile","&Create profile from current settings..."); } @@ -230,6 +231,16 @@ Item iconName: "edit-duplicate" } + Action + { + id: selectAllAction; + text: catalog.i18nc("@action:inmenu menubar:edit","&Select All Objects"); + enabled: UM.Controller.toolsEnabled; + iconName: "edit-select-all"; + shortcut: "Ctrl+A"; + onTriggered: Printer.selectAll(); + } + Action { id: deleteAllAction; diff --git a/resources/qml/AddMachineDialog.qml b/resources/qml/AddMachineDialog.qml index 38221030ea..350435d1ca 100644 --- a/resources/qml/AddMachineDialog.qml +++ b/resources/qml/AddMachineDialog.qml @@ -16,12 +16,23 @@ UM.Dialog { id: base title: catalog.i18nc("@title:window", "Add Printer") - property string activeManufacturer: "Ultimaker"; + property string preferredCategory: "Ultimaker" + property string activeCategory: preferredCategory + + onVisibilityChanged: + { + // Reset selection and machine name + if (visible) { + activeCategory = preferredCategory; + machineList.currentIndex = 0; + machineName.text = getMachineName(); + } + } signal machineAdded(string id) function getMachineName() { - var name = machineList.model.getItem(machineList.currentIndex).name + var name = machineList.model.get(machineList.currentIndex).name return name } @@ -36,16 +47,32 @@ UM.Dialog right: parent.right; bottom: parent.bottom; } + ListView { id: machineList - model: UM.DefinitionContainersModel + model: ListModel { - id: machineDefinitionsModel - filter: {"visible":true} + id: sortedMachineDefinitionsModel + Component.onCompleted: { + // DefinitionContainersModel is sorted alphabetically, but we want the preferred + // category on top so we create a custom-sorted ListModel from it. + var items = []; + for(var i in machineDefinitionsModel.items) { + var item = machineDefinitionsModel.getItem(i); + if (item["category"] == preferredCategory) + sortedMachineDefinitionsModel.append(item); + else + items.push(item); + } + for(var i in items) { + sortedMachineDefinitionsModel.append(items[i]); + } + } } - section.property: "manufacturer" + + section.property: "category" section.delegate: Button { text: section @@ -76,16 +103,25 @@ UM.Dialog sourceSize.width: width sourceSize.height: width color: palette.windowText - source: base.activeManufacturer == section ? UM.Theme.getIcon("arrow_bottom") : UM.Theme.getIcon("arrow_right") + source: base.activeCategory == section ? UM.Theme.getIcon("arrow_bottom") : UM.Theme.getIcon("arrow_right") } } } onClicked: { - base.activeManufacturer = section; - machineList.currentIndex = machineList.model.find("manufacturer", section) - machineName.text = getMachineName() + base.activeCategory = section; + if (machineList.model.get(machineList.currentIndex).category != section) { + // Find the first machine from this category + for(var i = 0; i < sortedMachineDefinitionsModel.count; i++) { + var item = sortedMachineDefinitionsModel.get(i); + if (item.category == section) { + machineList.currentIndex = i; + break; + } + } + } + machineName.text = getMachineName(); } } @@ -114,7 +150,7 @@ UM.Dialog states: State { name: "collapsed"; - when: base.activeManufacturer != model.manufacturer; + when: base.activeCategory != model.category; PropertyChanges { target: machineButton; opacity: 0; height: 0; } } @@ -161,7 +197,7 @@ UM.Dialog onClicked: { base.visible = false - var item = machineList.model.getItem(machineList.currentIndex); + var item = machineList.model.get(machineList.currentIndex); Cura.MachineManager.addMachine(machineName.text, item.id) base.machineAdded(item.id) // Emit signal that the user added a machine. } @@ -174,6 +210,11 @@ UM.Dialog id: catalog; name: "cura"; } + UM.DefinitionContainersModel + { + id: machineDefinitionsModel + filter: { "visible": true } + } SystemPalette { id: palette } ExclusiveGroup { id: printerGroup; } } diff --git a/resources/qml/Cura.qml b/resources/qml/Cura.qml index d0870991d2..ae72353e0e 100644 --- a/resources/qml/Cura.qml +++ b/resources/qml/Cura.qml @@ -54,10 +54,7 @@ UM.MainWindow Keys.onPressed: { if (event.key == Qt.Key_Backspace) { - if(objectContextMenu.objectId != 0) - { - Printer.deleteObject(objectContextMenu.objectId); - } + Cura.Actions.deleteSelection.trigger() } } @@ -121,6 +118,7 @@ UM.MainWindow MenuItem { action: Cura.Actions.undo; } MenuItem { action: Cura.Actions.redo; } MenuSeparator { } + MenuItem { action: Cura.Actions.selectAll; } MenuItem { action: Cura.Actions.deleteSelection; } MenuItem { action: Cura.Actions.deleteAll; } MenuItem { action: Cura.Actions.resetAllTranslation; } @@ -540,6 +538,7 @@ UM.MainWindow MenuItem { action: Cura.Actions.deleteObject; } MenuItem { action: Cura.Actions.multiplyObject; } MenuSeparator { } + MenuItem { action: Cura.Actions.selectAll; } MenuItem { action: Cura.Actions.deleteAll; } MenuItem { action: Cura.Actions.reloadAll; } MenuItem { action: Cura.Actions.resetAllTranslation; } @@ -592,6 +591,7 @@ UM.MainWindow Menu { id: contextMenu; + MenuItem { action: Cura.Actions.selectAll; } MenuItem { action: Cura.Actions.deleteAll; } MenuItem { action: Cura.Actions.reloadAll; } MenuItem { action: Cura.Actions.resetAllTranslation; } diff --git a/resources/qml/MonitorButton.qml b/resources/qml/MonitorButton.qml index af163a39e2..4a33b347d3 100644 --- a/resources/qml/MonitorButton.qml +++ b/resources/qml/MonitorButton.qml @@ -22,7 +22,7 @@ Rectangle { if(!printerConnected) return UM.Theme.getColor("status_offline") - else if(Cura.MachineManager.printerOutputDevices[0].jobState == "printing" || Cura.MachineManager.printerOutputDevices[0].jobState == "pre_print") + else if(Cura.MachineManager.printerOutputDevices[0].jobState == "printing" || Cura.MachineManager.printerOutputDevices[0].jobState == "pre_print" || Cura.MachineManager.printerOutputDevices[0].jobState == "wait_cleanup" ) return UM.Theme.getColor("status_busy") else if(Cura.MachineManager.printerOutputDevices[0].jobState == "ready" || Cura.MachineManager.printerOutputDevices[0].jobState == "") return UM.Theme.getColor("status_ready") @@ -30,6 +30,8 @@ Rectangle return UM.Theme.getColor("status_paused") else if (Cura.MachineManager.printerOutputDevices[0].jobState == "error") return UM.Theme.getColor("status_stopped") + else if (Cura.MachineManager.printerOutputDevices[0].jobState == "offline") + return UM.Theme.getColor("status_offline") else return UM.Theme.getColor("text") } @@ -41,7 +43,10 @@ Rectangle { if(!printerConnected) { - return catalog.i18nc("@label:", "Please check your printer connections") + return catalog.i18nc("@label:", "Not connected to a printer") + } else if(Cura.MachineManager.printerOutputDevices[0].jobState == "offline") + { + return catalog.i18nc("@label:", "Lost connection with the printer") } else if(Cura.MachineManager.printerOutputDevices[0].jobState == "printing") { return catalog.i18nc("@label:", "Printing...") @@ -53,6 +58,10 @@ Rectangle { return catalog.i18nc("@label:", "Preparing...") } + else if(Cura.MachineManager.printerOutputDevices[0].jobState == "wait_cleanup") + { + return catalog.i18nc("@label:", "Waiting for cleanup...") + } else { return " " diff --git a/resources/qml/Preferences/GeneralPage.qml b/resources/qml/Preferences/GeneralPage.qml index 7e5b4efadc..223b7eda85 100644 --- a/resources/qml/Preferences/GeneralPage.qml +++ b/resources/qml/Preferences/GeneralPage.qml @@ -192,6 +192,7 @@ UM.PreferencesPage } } + UM.TooltipArea { width: childrenRect.width; height: childrenRect.height; @@ -215,6 +216,19 @@ UM.PreferencesPage } } } + UM.TooltipArea { + width: childrenRect.width + height: childrenRect.height + text: catalog.i18nc("@info:tooltip", "Should only the top layers be displayed in layerview?") + + CheckBox + { + id: topLayersOnlyCheckbox + text: catalog.i18nc("@option:check", "Only display top layer(s) in layer view") + checked: boolCheck(UM.Preferences.getValue("view/only_show_top_layers")) + onCheckedChanged: UM.Preferences.setValue("view/only_show_top_layers", checked) + } + } Item { diff --git a/resources/qml/Preferences/MachinesPage.qml b/resources/qml/Preferences/MachinesPage.qml index 0dc3bb32eb..b057a33d52 100644 --- a/resources/qml/Preferences/MachinesPage.qml +++ b/resources/qml/Preferences/MachinesPage.qml @@ -107,6 +107,12 @@ UM.ManagementPage contents = content; content.onCompleted.connect(hide) } + rightButtons: Button + { + text: catalog.i18nc("@action:button", "Close") + iconName: "dialog-close" + onClicked: actionDialog.accept() + } } Row diff --git a/resources/qml/Preferences/MaterialsPage.qml b/resources/qml/Preferences/MaterialsPage.qml index aaaa4f5e9d..f4a8df1dcf 100644 --- a/resources/qml/Preferences/MaterialsPage.qml +++ b/resources/qml/Preferences/MaterialsPage.qml @@ -67,6 +67,8 @@ UM.ManagementPage enabled: base.currentItem != null && base.currentItem.id != Cura.MachineManager.activeMaterialId onClicked: Cura.MachineManager.setActiveMaterial(base.currentItem.id) }, + /* + // disabled because it has a lot of issues Button { text: catalog.i18nc("@action:button", "Duplicate"); @@ -89,7 +91,7 @@ UM.ManagementPage Cura.MachineManager.setActiveMaterial(material_id) } - }, + }, */ Button { text: catalog.i18nc("@action:button", "Remove"); @@ -253,7 +255,7 @@ UM.ManagementPage else if(result.status == "success") { messageDialog.icon = StandardIcon.Information - messageDialog.text = catalog.i18nc("@info:status", "Successfully exported material to %1").arg(fileUrl) + messageDialog.text = catalog.i18nc("@info:status", "Successfully exported material to %1").arg(result.path) messageDialog.open() } CuraApplication.setDefaultPath("dialog_material_path", folder) diff --git a/resources/qml/SaveButton.qml b/resources/qml/SaveButton.qml index 8b95de15ee..9ea6e181bb 100644 --- a/resources/qml/SaveButton.qml +++ b/resources/qml/SaveButton.qml @@ -84,6 +84,27 @@ Rectangle { anchors.topMargin: UM.Theme.getSize("default_margin").height anchors.left: parent.left + Row { + id: additionalComponentsRow + anchors.top: parent.top + anchors.right: saveToButton.visible ? saveToButton.left : parent.right + anchors.rightMargin: UM.Theme.getSize("default_margin").width + + spacing: UM.Theme.getSize("default_margin").width + } + + Connections { + target: Printer + onAdditionalComponentsChanged: + { + if(areaId == "saveButton") { + for (var component in Printer.additionalComponents["saveButton"]) { + Printer.additionalComponents["saveButton"][component].parent = additionalComponentsRow + } + } + } + } + Button { id: saveToButton @@ -102,8 +123,7 @@ Rectangle { } style: ButtonStyle { - background: - Rectangle + background: Rectangle { border.width: UM.Theme.getSize("default_lining").width border.color: !control.enabled ? UM.Theme.getColor("action_button_disabled_border") : @@ -126,7 +146,7 @@ Rectangle { text: control.text; } } - label: Item { } + label: Item { } } } diff --git a/resources/qml/Settings/SettingComboBox.qml b/resources/qml/Settings/SettingComboBox.qml index 5308e45f5a..283e309e6a 100644 --- a/resources/qml/Settings/SettingComboBox.qml +++ b/resources/qml/Settings/SettingComboBox.qml @@ -86,18 +86,18 @@ SettingItem } } - onActivated: { forceActiveFocus(); provider.setPropertyValue("value", definition.options[index].key) } + onActivated: { forceActiveFocus(); propertyProvider.setPropertyValue("value", definition.options[index].key) } onModelChanged: updateCurrentIndex(); Connections { - target: provider + target: propertyProvider onPropertiesChanged: control.updateCurrentIndex() } function updateCurrentIndex() { for(var i = 0; i < definition.options.length; ++i) { - if(definition.options[i].key == provider.properties.value) { + if(definition.options[i].key == propertyProvider.properties.value) { currentIndex = i; return; } diff --git a/resources/qml/Settings/SettingExtruder.qml b/resources/qml/Settings/SettingExtruder.qml index 72c5299b15..d88fc9c457 100644 --- a/resources/qml/Settings/SettingExtruder.qml +++ b/resources/qml/Settings/SettingExtruder.qml @@ -105,13 +105,13 @@ SettingItem onActivated: { forceActiveFocus(); - provider.setPropertyValue("value", extruders_model.getItem(index).index) + propertyProvider.setPropertyValue("value", extruders_model.getItem(index).index) } onModelChanged: updateCurrentIndex(); Connections { - target: provider + target: propertyProvider onPropertiesChanged: control.updateCurrentIndex(); } @@ -119,7 +119,7 @@ SettingItem { for(var i = 0; i < extruders_model.rowCount(); ++i) { - if(extruders_model.getItem(i).index == provider.properties.value) + if(extruders_model.getItem(i).index == propertyProvider.properties.value) { currentIndex = i; return; diff --git a/resources/qml/Settings/SettingItem.qml b/resources/qml/Settings/SettingItem.qml index eb6c37f73c..dae9953690 100644 --- a/resources/qml/Settings/SettingItem.qml +++ b/resources/qml/Settings/SettingItem.qml @@ -138,7 +138,7 @@ Item { { id: linkedSettingIcon; - visible: base.settablePerExtruder != "True" && base.showLinkedSettingIcon + visible: Cura.MachineManager.activeStackId != Cura.MachineManager.activeMachineId && base.settablePerExtruder != "True" && base.showLinkedSettingIcon height: parent.height; width: height; diff --git a/resources/qml/Sidebar.qml b/resources/qml/Sidebar.qml index 53a2375394..8ab776af6a 100644 --- a/resources/qml/Sidebar.qml +++ b/resources/qml/Sidebar.qml @@ -108,7 +108,7 @@ Rectangle iconSource: { if(!printerConnected) return UM.Theme.getIcon("tab_monitor") - else if(Cura.MachineManager.printerOutputDevices[0].jobState == "printing" || Cura.MachineManager.printerOutputDevices[0].jobState == "pre_print") + else if(Cura.MachineManager.printerOutputDevices[0].jobState == "printing" || Cura.MachineManager.printerOutputDevices[0].jobState == "pre_print" || Cura.MachineManager.printerOutputDevices[0].jobState == "wait_cleanup" ) return UM.Theme.getIcon("tab_monitor_busy") else if(Cura.MachineManager.printerOutputDevices[0].jobState == "ready" || Cura.MachineManager.printerOutputDevices[0].jobState == "") return UM.Theme.getIcon("tab_monitor_connected") @@ -116,6 +116,8 @@ Rectangle return UM.Theme.getIcon("tab_monitor_paused") else if (Cura.MachineManager.printerOutputDevices[0].jobState == "error") return UM.Theme.getIcon("tab_monitor_stopped") + else if (Cura.MachineManager.printerOutputDevices[0].jobState == "offline") + return UM.Theme.getIcon("tab_monitor_offline") else return UM.Theme.getIcon("tab_monitor") } diff --git a/resources/shaders/transparent_object.shader b/resources/shaders/transparent_object.shader index a3790901bc..cd27a40769 100644 --- a/resources/shaders/transparent_object.shader +++ b/resources/shaders/transparent_object.shader @@ -1,7 +1,7 @@ [shaders] vertex = - uniform highp mat4 u_viewProjectionMatrix; uniform highp mat4 u_modelMatrix; + uniform highp mat4 u_viewProjectionMatrix; uniform highp mat4 u_normalMatrix; attribute highp vec4 a_vertex; @@ -10,7 +10,6 @@ vertex = varying highp vec3 v_vertex; varying highp vec3 v_normal; - varying highp vec2 v_uvs; void main() { @@ -19,56 +18,47 @@ vertex = v_vertex = world_space_vert.xyz; v_normal = (u_normalMatrix * normalize(a_normal)).xyz; - - v_uvs = a_uvs; } fragment = uniform mediump vec4 u_ambientColor; uniform mediump vec4 u_diffuseColor; uniform highp vec3 u_lightPosition; - uniform highp vec3 u_viewPosition; + uniform mediump float u_opacity; - uniform sampler2D u_texture; varying highp vec3 v_vertex; varying highp vec3 v_normal; - varying highp vec2 v_uvs; void main() { - // Copied from platform.shader, removed texture - mediump vec4 final_color = vec4(0.0); + mediump vec4 finalColor = vec4(0.0); /* Ambient Component */ - final_color += u_ambientColor; + finalColor += u_ambientColor; highp vec3 normal = normalize(v_normal); - highp vec3 light_dir = normalize(u_lightPosition - v_vertex); + highp vec3 lightDir = normalize(u_lightPosition - v_vertex); /* Diffuse Component */ - highp float n_dot_l = clamp(dot(normal, light_dir), 0.0, 1.0); - final_color += (n_dot_l * u_diffuseColor); + highp float NdotL = clamp(abs(dot(normal, lightDir)), 0.0, 1.0); + finalColor += (NdotL * u_diffuseColor); - final_color.a = u_opacity; - - gl_FragColor = final_color; + gl_FragColor = finalColor; + gl_FragColor.a = u_opacity; } [defaults] -u_ambientColor = [0.3, 0.3, 0.3, 1.0] -u_diffuseColor = [1.0, 1.0, 1.0, 1.0] +u_ambientColor = [0.1, 0.1, 0.1, 1.0] +u_diffuseColor = [0.4, 0.4, 0.4, 1.0] u_opacity = 0.5 -u_texture = 0 [bindings] -u_viewProjectionMatrix = view_projection_matrix u_modelMatrix = model_matrix +u_viewProjectionMatrix = view_projection_matrix u_normalMatrix = normal_matrix u_lightPosition = light_0_position -u_viewPosition = camera_position [attributes] a_vertex = vertex a_normal = normal -a_uvs = uv0