diff --git a/cura/ConvexHullDecorator.py b/cura/ConvexHullDecorator.py index 783d74e41b..5185579633 100644 --- a/cura/ConvexHullDecorator.py +++ b/cura/ConvexHullDecorator.py @@ -287,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 232b30e317..8e5acf9518 100644 --- a/cura/ConvexHullNode.py +++ b/cura/ConvexHullNode.py @@ -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 5daae559d6..62d5fb7ae7 100644 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -203,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") @@ -555,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 @@ -586,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() @@ -628,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): @@ -652,6 +673,7 @@ class CuraApplication(QtApplication): op.addOperation(RemoveSceneNodeOperation(node)) op.push() + Selection.clear() ## Reset all translation on nodes with mesh data. @pyqtSlot() @@ -896,4 +918,4 @@ class CuraApplication(QtApplication): self._additional_components[area_id] = [] self._additional_components[area_id].append(component) - self.additionalComponentsChanged.emit(area_id) \ No newline at end of file + 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/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 5bc0ff65fa..6a180593c5 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 @@ -281,16 +281,17 @@ class MachineManager(QObject): 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) @@ -313,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) @@ -332,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") @@ -436,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): @@ -552,7 +553,7 @@ class MachineManager(QObject): return "" @pyqtSlot(str, str) - def renameQualityContainer(self, container_id, nbalew_name): + def renameQualityContainer(self, container_id, new_name): containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(id = container_id, type = "quality") if containers: new_name = self._createUniqueName("quality", containers[0].getName(), new_name, @@ -743,10 +744,7 @@ class MachineManager(QObject): # If the machine that is being removed is the currently active machine, set another machine as the active machine. activate_new_machine = (self._global_container_stack and self._global_container_stack.getId() == machine_id) - stacks = UM.Settings.ContainerRegistry.getInstance().findContainerStacks(id = machine_id) - if not stacks: - return - ExtruderManager.getInstance().removeMachineExtruders(stacks[0].getBottom().getId()) + ExtruderManager.getInstance().removeMachineExtruders(machine_id) containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(type = "user", machine = machine_id) for container in containers: diff --git a/cura/Settings/SettingOverrideDecorator.py b/cura/Settings/SettingOverrideDecorator.py index 24360ed992..183d84d2f4 100644 --- a/cura/Settings/SettingOverrideDecorator.py +++ b/cura/Settings/SettingOverrideDecorator.py @@ -37,10 +37,15 @@ class SettingOverrideDecorator(SceneNodeDecorator): self._updateNextStack() def __deepcopy__(self, memo): + print("deepcopy settingoverridedecorator") ## Create a fresh decorator object deep_copy = SettingOverrideDecorator() ## Copy the instance deep_copy._instance = copy.deepcopy(self._instance, memo) + + # Properly set the right extruder on the copy + deep_copy.setActiveExtruder(self._extruder_stack) + ## Set the copied instance as the first (and only) instance container of the stack. deep_copy._stack.replaceContainer(0, deep_copy._instance) return deep_copy @@ -75,6 +80,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 aa6f2b0807..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 = 20000 # Maximum number 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: @@ -265,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. @@ -316,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 = [] @@ -351,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. # @@ -360,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. @@ -391,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..7c7246361e 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 @@ -32,9 +33,9 @@ class LayerView(View): def __init__(self): super().__init__() self._shader = None - self._selection_shader = None + self._ghost_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): @@ -81,10 +84,9 @@ class LayerView(View): scene = self.getController().getScene() renderer = self.getRenderer() - if not self._selection_shader: - self._selection_shader = OpenGL.getInstance().createShaderProgram(Resources.getPath(Resources.Shaders, "color.shader")) - self._selection_shader.setUniformValue("u_color", Color(32, 32, 32, 128)) - + if not self._ghost_shader: + self._ghost_shader = OpenGL.getInstance().createShaderProgram(Resources.getPath(Resources.Shaders, "color.shader")) + self._ghost_shader.setUniformValue("u_color", Color(32, 32, 32, 96)) for node in DepthFirstIterator(scene.getRoot()): # We do not want to render ConvexHullNode as it conflicts with the bottom layers. # However, it is somewhat relevant when the node is selected, so do render it then. @@ -93,14 +95,13 @@ class LayerView(View): if not node.render(renderer): if node.getMeshData() and node.isVisible(): - if Selection.isSelected(node): - renderer.queueNode(node, transparent = True, shader = self._selection_shader) + renderer.queueNode(node, transparent = True, shader = self._ghost_shader) layer_data = node.callDecoration("getLayerData") if not layer_data: 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() @@ -110,13 +111,13 @@ class LayerView(View): end += counts # This uses glDrawRangeElements internally to only draw a certain range of lines. - renderer.queueNode(node, mesh = layer_data, mode = RenderBatch.RenderMode.Lines, range = (start, end)) + renderer.queueNode(node, mesh = layer_data, mode = RenderBatch.RenderMode.Lines, overlay = True, range = (start, end)) if self._current_layer_mesh: - renderer.queueNode(node, mesh = self._current_layer_mesh) + renderer.queueNode(node, mesh = self._current_layer_mesh, overlay = True) if self._current_layer_jumps: - renderer.queueNode(node, mesh = self._current_layer_jumps) + renderer.queueNode(node, mesh = self._current_layer_jumps, overlay = True) def setLayer(self, value): if self._current_layer_num != value: @@ -126,14 +127,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 +198,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 +206,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 +242,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 +271,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/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/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 9866e1a35d..1c009877ac 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, @@ -1064,6 +1066,7 @@ "unit": "[[mm³,°C]]", "type": "str", "default_value": "[[3.5,200],[7.0,240]]", + "enabled": "False", "comments": "old enabled function: material_flow_dependent_temperature", "settable_per_mesh": false, "settable_per_extruder": true @@ -1236,30 +1239,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": { @@ -1511,14 +1512,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_extruder": true, + "settable_per_mesh": false + } + } }, "skirt_brim_speed": { "label": "Skirt/Brim Speed", @@ -1546,8 +1577,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.", @@ -1718,7 +1749,38 @@ "minimum_value_warning": "100", "maximum_value_warning": "10000", "enabled": "acceleration_enabled", - "settable_per_mesh": true + "settable_per_mesh": true, + "children": { + "acceleration_print_layer_0": + { + "label": "Initial Layer Print Acceleration", + "description": "The acceleration during the printing of the initial layer.", + "unit": "mm/s", + "type": "float", + "default_value": 3000, + "value": "acceleration_layer_0", + "minimum_value": "0.1", + "minimum_value_warning": "100", + "maximum_value_warning": "10000", + "enabled": "acceleration_enabled", + "settable_per_mesh": true + }, + "acceleration_travel_layer_0": + { + "label": "Initial Layer Travel Acceleration", + "description": "The acceleration for travel moves in the initial layer.", + "unit": "mm/s", + "type": "float", + "default_value": 3000, + "value": "acceleration_layer_0 * acceleration_travel / acceleration_print", + "minimum_value": "0.1", + "minimum_value_warning": "100", + "maximum_value_warning": "10000", + "enabled": "acceleration_enabled", + "settable_per_extruder": true, + "settable_per_mesh": false + } + } }, "acceleration_skirt_brim": { "label": "Skirt/Brim Acceleration", @@ -1734,8 +1796,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.", @@ -1906,7 +1968,38 @@ "minimum_value_warning": "5", "maximum_value_warning": "50", "enabled": "jerk_enabled", - "settable_per_mesh": true + "settable_per_mesh": true, + "children": { + "jerk_print_layer_0": + { + "label": "Initial Layer Print Jerk", + "description": "The maximum instantaneous velocity change during the printing of the initial layer.", + "unit": "mm/s", + "type": "float", + "default_value": 20, + "value": "jerk_layer_0", + "minimum_value": "0.1", + "minimum_value_warning": "5", + "maximum_value_warning": "50", + "enabled": "jerk_enabled", + "settable_per_mesh": true + }, + "jerk_travel_layer_0": + { + "label": "Initial Layer Travel Jerk", + "description": "The acceleration for travel moves in the initial layer.", + "unit": "mm/s", + "type": "float", + "default_value": 20, + "value": "jerk_layer_0 * jerk_travel / jerk_print", + "minimum_value": "0.1", + "minimum_value_warning": "5", + "maximum_value_warning": "50", + "enabled": "jerk_enabled", + "settable_per_extruder": true, + "settable_per_mesh": false + } + } }, "jerk_skirt_brim": { "label": "Skirt/Brim Jerk", @@ -3378,8 +3471,8 @@ "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", + "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/mankati_fullscale_xt_plus.def.json b/resources/definitions/mankati_fullscale_xt_plus.def.json index eb8b99aaf9..72c8ebdd86 100644 --- a/resources/definitions/mankati_fullscale_xt_plus.def.json +++ b/resources/definitions/mankati_fullscale_xt_plus.def.json @@ -54,13 +54,13 @@ "speed_travel": { "default_value": 150 }, "speed_layer_0": { "minimum_value": "0.1", - "default": 15.0 + "default_value": 15.0 }, - "infill_overlap": { "default": 10 }, - "cool_fan_enabled": { "default": false }, - "cool_fan_speed": { "default": 0 }, - "skirt_line_count": { "default": 3 }, - "skirt_gap": { "default": 4 }, - "skirt_brim_minimal_length": { "default": 200 } + "infill_overlap": { "default_value": 10 }, + "cool_fan_enabled": { "default_value": false }, + "cool_fan_speed": { "default_value": 0 }, + "skirt_line_count": { "default_value": 3 }, + "skirt_gap": { "default_value": 4 }, + "skirt_brim_minimal_length": { "default_value": 200 } } } 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 d8e73a7e14..9985dea764 100644 --- a/resources/definitions/ultimaker2.def.json +++ b/resources/definitions/ultimaker2.def.json @@ -8,6 +8,7 @@ "author": "Ultimaker", "manufacturer": "Ultimaker", "category": "Ultimaker", + "weight": 3, "file_formats": "text/x-gcode", "icon": "icon_ultimaker2.png", "platform": "ultimaker2_platform.obj", @@ -82,10 +83,10 @@ "default_value": 45 }, "material_print_temperature": { - "enabled": "False" + "enabled": "not (material_flow_dependent_temperature) and machine_gcode_flavor != \"UltiGCode\"" }, "material_bed_temperature": { - "enabled": "False" + "enabled": "machine_heated_bed and machine_gcode_flavor != \"UltiGCode\"" }, "machine_max_feedrate_x": { "default_value": 300 @@ -103,22 +104,22 @@ "default_value": 3000 }, "material_diameter": { - "enabled": "False" + "enabled": "machine_gcode_flavor != \"UltiGCode\"" }, "material_flow": { - "enabled": "False" + "enabled": "machine_gcode_flavor != \"UltiGCode\"" }, "retraction_amount": { - "enabled": "False" + "enabled": "retraction_enable and machine_gcode_flavor != \"UltiGCode\"" }, "retraction_speed": { - "enabled": "False" + "enabled": "retraction_enable and machine_gcode_flavor != \"UltiGCode\"" }, "retraction_retract_speed": { - "enabled": "False" + "enabled": "retraction_enable and machine_gcode_flavor != \"UltiGCode\"" }, "retraction_prime_speed": { - "enabled": "False" + "enabled": "retraction_enable and machine_gcode_flavor != \"UltiGCode\"" } } } diff --git a/resources/definitions/ultimaker2_extended.def.json b/resources/definitions/ultimaker2_extended.def.json index fb443365f2..889046694c 100644 --- a/resources/definitions/ultimaker2_extended.def.json +++ b/resources/definitions/ultimaker2_extended.def.json @@ -7,6 +7,7 @@ "author": "Ultimaker", "manufacturer": "Ultimaker", "category": "Ultimaker", + "weight": 3, "file_formats": "text/x-gcode", "icon": "icon_ultimaker2.png", "platform": "ultimaker2_platform.obj", diff --git a/resources/definitions/ultimaker2_extended_plus.def.json b/resources/definitions/ultimaker2_extended_plus.def.json index f4190be83d..ea97033878 100644 --- a/resources/definitions/ultimaker2_extended_plus.def.json +++ b/resources/definitions/ultimaker2_extended_plus.def.json @@ -7,6 +7,7 @@ "author": "Ultimaker", "manufacturer": "Ultimaker", "category": "Ultimaker", + "weight": 2, "file_formats": "text/x-gcode", "platform": "ultimaker2_platform.obj", "platform_texture": "Ultimaker2ExtendedPlusbackplate.png", diff --git a/resources/definitions/ultimaker2_go.def.json b/resources/definitions/ultimaker2_go.def.json index 27b179eef9..107d413296 100644 --- a/resources/definitions/ultimaker2_go.def.json +++ b/resources/definitions/ultimaker2_go.def.json @@ -7,6 +7,7 @@ "author": "Ultimaker", "manufacturer": "Ultimaker", "category": "Ultimaker", + "weight": 3, "file_formats": "text/x-gcode", "icon": "icon_ultimaker2.png", "platform": "ultimaker2go_platform.obj", diff --git a/resources/definitions/ultimaker2_plus.def.json b/resources/definitions/ultimaker2_plus.def.json index d5a7c9f4f1..4c7279b178 100644 --- a/resources/definitions/ultimaker2_plus.def.json +++ b/resources/definitions/ultimaker2_plus.def.json @@ -7,6 +7,7 @@ "author": "Ultimaker", "manufacturer": "Ultimaker", "category": "Ultimaker", + "weight": 1, "file_formats": "text/x-gcode", "platform": "ultimaker2_platform.obj", "platform_texture": "Ultimaker2Plusbackplate.png", diff --git a/resources/definitions/ultimaker_original.def.json b/resources/definitions/ultimaker_original.def.json index d2174ece80..3dd4766afa 100644 --- a/resources/definitions/ultimaker_original.def.json +++ b/resources/definitions/ultimaker_original.def.json @@ -8,6 +8,7 @@ "author": "Ultimaker", "manufacturer": "Ultimaker", "category": "Ultimaker", + "weight": 4, "file_formats": "text/x-gcode", "icon": "icon_ultimaker.png", "platform": "ultimaker_platform.stl", @@ -15,7 +16,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/definitions/ultimaker_original_plus.def.json b/resources/definitions/ultimaker_original_plus.def.json index 4d39a39a78..4f787896d0 100644 --- a/resources/definitions/ultimaker_original_plus.def.json +++ b/resources/definitions/ultimaker_original_plus.def.json @@ -7,6 +7,7 @@ "author": "Ultimaker", "manufacturer": "Ultimaker", "category": "Ultimaker", + "weight": 4, "file_formats": "text/x-gcode", "icon": "icon_ultimaker.png", "platform": "ultimaker2_platform.obj", 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..a2b7c1586f 100644 --- a/resources/qml/AddMachineDialog.qml +++ b/resources/qml/AddMachineDialog.qml @@ -16,12 +16,24 @@ UM.Dialog { id: base title: catalog.i18nc("@title:window", "Add Printer") - property string activeManufacturer: "Ultimaker"; + property bool firstRun: false + 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.getItem(machineList.currentIndex) != undefined ? machineList.model.getItem(machineList.currentIndex).name : "" return name } @@ -36,6 +48,7 @@ UM.Dialog right: parent.right; bottom: parent.bottom; } + ListView { id: machineList @@ -43,9 +56,12 @@ UM.Dialog model: UM.DefinitionContainersModel { id: machineDefinitionsModel - filter: {"visible":true} + filter: { "visible": true } + sectionProperty: "category" + preferredSectionValue: preferredCategory } - section.property: "manufacturer" + + section.property: "section" section.delegate: Button { text: section @@ -76,16 +92,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.getItem(machineList.currentIndex).section != section) { + // Find the first machine from this section + for(var i = 0; i < sortedMachineDefinitionsModel.count; i++) { + var item = sortedMachineDefinitionsModel.getItem(i); + if (item.section == section) { + machineList.currentIndex = i; + break; + } + } + } + machineName.text = getMachineName(); } } @@ -114,7 +139,7 @@ UM.Dialog states: State { name: "collapsed"; - when: base.activeManufacturer != model.manufacturer; + when: base.activeCategory != model.section; PropertyChanges { target: machineButton; opacity: 0; height: 0; } } diff --git a/resources/qml/AddMachineWizard.qml b/resources/qml/AddMachineWizard.qml deleted file mode 100644 index 1c743834de..0000000000 --- a/resources/qml/AddMachineWizard.qml +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) 2015 Ultimaker B.V. -// Cura is released under the terms of the AGPLv3 or higher. - -import QtQuick 2.2 -import QtQuick.Controls 1.1 -import QtQuick.Layouts 1.1 -import QtQuick.Window 2.1 - -import UM 1.1 as UM -import Cura 1.0 as Cura - -import "WizardPages" - -UM.Wizard -{ - id: base; - - title: catalog.i18nc("@title:window", "Add Printer") - - // This part is optional - // This part checks whether there is a printer -> if not: some of the functions (delete for example) are disabled - firstRun: false - - Component.onCompleted: { - base.appendPage(Qt.resolvedUrl("WizardPages/AddMachine.qml"), catalog.i18nc("@title", "Add Printer")); - base.currentPage = 0; - } - - Item { - UM.I18nCatalog { id: catalog; name: "cura"; } - } -} diff --git a/resources/qml/Cura.qml b/resources/qml/Cura.qml index d0870991d2..5f45a062b8 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; } @@ -677,6 +677,7 @@ UM.MainWindow id: addMachineDialog onMachineAdded: { + machineActionsWizard.firstRun = addMachineDialog.firstRun machineActionsWizard.start(id) } } diff --git a/resources/qml/MachineAction.qml b/resources/qml/MachineAction.qml index 59fb3946a3..2c43f9f96d 100644 --- a/resources/qml/MachineAction.qml +++ b/resources/qml/MachineAction.qml @@ -3,6 +3,10 @@ import QtQuick 2.2 Item { id: contentItem + + // Point to the dialog containing the displayItem + property var dialog + // Connect the finished property change to completed signal. property var finished: manager.finished onFinishedChanged: if(manager.finished) {completed()} diff --git a/resources/qml/MonitorButton.qml b/resources/qml/MonitorButton.qml index 7a87eb4f60..4a33b347d3 100644 --- a/resources/qml/MonitorButton.qml +++ b/resources/qml/MonitorButton.qml @@ -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...") 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..c149185808 100644 --- a/resources/qml/Preferences/MachinesPage.qml +++ b/resources/qml/Preferences/MachinesPage.qml @@ -83,16 +83,17 @@ UM.ManagementPage Repeater { id: machineActionRepeater - model: Cura.MachineActionManager.getSupportedActions(Cura.MachineManager.getDefinitionByMachineId(base.currentItem.id)) + model: base.currentItem ? Cura.MachineActionManager.getSupportedActions(Cura.MachineManager.getDefinitionByMachineId(base.currentItem.id)) : null Button { - text: machineActionRepeater.model[index].label; + text: machineActionRepeater.model[index].label onClicked: { - actionDialog.content = machineActionRepeater.model[index].displayItem - machineActionRepeater.model[index].displayItem.reset() - actionDialog.show() + actionDialog.content = machineActionRepeater.model[index].displayItem; + machineActionRepeater.model[index].displayItem.reset(); + actionDialog.title = machineActionRepeater.model[index].label; + actionDialog.show(); } } } @@ -106,6 +107,13 @@ UM.ManagementPage { contents = content; content.onCompleted.connect(hide) + content.dialog = actionDialog + } + rightButtons: Button + { + text: catalog.i18nc("@action:button", "Close") + iconName: "dialog-close" + onClicked: actionDialog.reject() } } @@ -118,8 +126,14 @@ UM.ManagementPage spacing: UM.Theme.getSize("default_margin").height - Label { text: catalog.i18nc("@label", "Type") } - Label { text: base.currentItem ? base.currentItem.metadata.definition_name : "" } + Label + { + text: catalog.i18nc("@label", "Type") + visible: base.currentItem && base.currentItem.metadata + } + Label { + text: (base.currentItem && base.currentItem.metadata) ? base.currentItem.metadata.definition_name : "" + } } UM.I18nCatalog { id: catalog; name: "uranium"; } diff --git a/resources/qml/Preferences/MaterialsPage.qml b/resources/qml/Preferences/MaterialsPage.qml index 9da5522762..f4a8df1dcf 100644 --- a/resources/qml/Preferences/MaterialsPage.qml +++ b/resources/qml/Preferences/MaterialsPage.qml @@ -255,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/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 1542e24f5d..8ab776af6a 100644 --- a/resources/qml/Sidebar.qml +++ b/resources/qml/Sidebar.qml @@ -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") }