diff --git a/cura/CuraActions.py b/cura/CuraActions.py index 2474e218e8..f5aace805b 100644 --- a/cura/CuraActions.py +++ b/cura/CuraActions.py @@ -73,7 +73,7 @@ class CuraActions(QObject): # \param count The number of times to multiply the selection. @pyqtSlot(int) def multiplySelection(self, count: int) -> None: - job = MultiplyObjectsJob(Selection.getAllSelectedObjects(), count, 8) + job = MultiplyObjectsJob(Selection.getAllSelectedObjects(), count, min_offset = 8) job.start() ## Delete all selected objects. diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index 30fd461868..0ac50c9e5e 100755 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -266,6 +266,7 @@ class CuraApplication(QtApplication): self.getController().getScene().sceneChanged.connect(self.updatePlatformActivity) self.getController().toolOperationStopped.connect(self._onToolOperationStopped) self.getController().contextMenuRequested.connect(self._onContextMenuRequested) + self.getCuraSceneController().activeBuildPlateChanged.connect(self.updatePlatformActivity) Resources.addType(self.ResourceTypes.QmlFiles, "qml") Resources.addType(self.ResourceTypes.Firmware, "firmware") @@ -298,6 +299,7 @@ class CuraApplication(QtApplication): empty_quality_changes_container = copy.deepcopy(empty_container) empty_quality_changes_container.setMetaDataEntry("id", "empty_quality_changes") empty_quality_changes_container.addMetaDataEntry("type", "quality_changes") + empty_quality_changes_container.addMetaDataEntry("quality_type", "not_supported") ContainerRegistry.getInstance().addContainer(empty_quality_changes_container) with ContainerRegistry.getInstance().lockFile(): @@ -646,10 +648,10 @@ class CuraApplication(QtApplication): if parsed_args["help"]: parser.print_help() sys.exit(0) - + def run(self): self.preRun() - + self.showSplashMessage(self._i18n_catalog.i18nc("@info:progress", "Setting up scene...")) self._setUpSingleInstanceServer() @@ -804,6 +806,7 @@ class CuraApplication(QtApplication): qmlRegisterUncreatableType(CuraApplication, "Cura", 1, 0, "ResourceTypes", "Just an Enum type") + qmlRegisterType(InstanceContainer, "Cura", 1, 0, "InstanceContainer") qmlRegisterType(ExtrudersModel, "Cura", 1, 0, "ExtrudersModel") qmlRegisterType(ContainerSettingsModel, "Cura", 1, 0, "ContainerSettingsModel") qmlRegisterSingletonType(ProfilesModel, "Cura", 1, 0, "ProfilesModel", ProfilesModel.createProfilesModel) @@ -890,12 +893,18 @@ class CuraApplication(QtApplication): def getSceneBoundingBoxString(self): return self._i18n_catalog.i18nc("@info 'width', 'depth' and 'height' are variable names that must NOT be translated; just translate the format of ##x##x## mm.", "%(width).1f x %(depth).1f x %(height).1f mm") % {'width' : self._scene_bounding_box.width.item(), 'depth': self._scene_bounding_box.depth.item(), 'height' : self._scene_bounding_box.height.item()} + ## Update scene bounding box for current build plate def updatePlatformActivity(self, node = None): count = 0 scene_bounding_box = None is_block_slicing_node = False + active_build_plate = self.getBuildPlateModel().activeBuildPlate for node in DepthFirstIterator(self.getController().getScene().getRoot()): - if not issubclass(type(node), SceneNode) or (not node.getMeshData() and not node.callDecoration("getLayerData")): + if ( + not issubclass(type(node), CuraSceneNode) or + (not node.getMeshData() and not node.callDecoration("getLayerData")) or + (node.callDecoration("getBuildPlateNumber") != active_build_plate)): + continue if node.callDecoration("isBlockSlicing"): is_block_slicing_node = True @@ -915,7 +924,7 @@ class CuraApplication(QtApplication): if not scene_bounding_box: scene_bounding_box = AxisAlignedBox.Null - if repr(self._scene_bounding_box) != repr(scene_bounding_box) and scene_bounding_box.isValid(): + if repr(self._scene_bounding_box) != repr(scene_bounding_box): self._scene_bounding_box = scene_bounding_box self.sceneBoundingBoxChanged.emit() @@ -1012,7 +1021,7 @@ class CuraApplication(QtApplication): Selection.clear() for node in DepthFirstIterator(self.getController().getScene().getRoot()): - if not issubclass(type(node), SceneNode): + if not isinstance(node, SceneNode): continue if not node.getMeshData() and not node.callDecoration("isGroup"): continue # Node that doesnt have a mesh and is not a group. @@ -1034,10 +1043,12 @@ class CuraApplication(QtApplication): nodes = [] for node in DepthFirstIterator(self.getController().getScene().getRoot()): - if type(node) not in {SceneNode, CuraSceneNode}: + if not isinstance(node, SceneNode): continue if (not node.getMeshData() and not node.callDecoration("getLayerData")) and not node.callDecoration("isGroup"): continue # Node that doesnt have a mesh and is not a group. + if not node.isSelectable(): + continue # Only remove nodes that are selectable. if node.getParent() and node.getParent().callDecoration("isGroup"): continue # Grouped nodes don't need resetting as their parent (the group) is resetted) nodes.append(node) @@ -1050,7 +1061,12 @@ class CuraApplication(QtApplication): op.push() Selection.clear() - self.getCuraSceneController().setActiveBuildPlate(0) # Select first build plate + # Reset the print information: + self.getController().getScene().sceneChanged.emit(node) + # self._print_information.setToZeroPrintInformation(self.getBuildPlateModel().activeBuildPlate) + + # stay on the same build plate + #self.getCuraSceneController().setActiveBuildPlate(0) # Select first build plate ## Reset all translation on nodes with mesh data. @pyqtSlot() @@ -1058,7 +1074,7 @@ class CuraApplication(QtApplication): Logger.log("i", "Resetting all scene translations") nodes = [] for node in DepthFirstIterator(self.getController().getScene().getRoot()): - if not issubclass(type(node), SceneNode): + if not isinstance(node, SceneNode): continue if not node.getMeshData() and not node.callDecoration("isGroup"): continue # Node that doesnt have a mesh and is not a group. @@ -1086,7 +1102,7 @@ class CuraApplication(QtApplication): Logger.log("i", "Resetting all scene transformations") nodes = [] for node in DepthFirstIterator(self.getController().getScene().getRoot()): - if not issubclass(type(node), SceneNode): + if not isinstance(node, SceneNode): continue if not node.getMeshData() and not node.callDecoration("isGroup"): continue # Node that doesnt have a mesh and is not a group. @@ -1113,7 +1129,7 @@ class CuraApplication(QtApplication): def arrangeObjectsToAllBuildPlates(self): nodes = [] for node in DepthFirstIterator(self.getController().getScene().getRoot()): - if not issubclass(type(node), SceneNode): + if not isinstance(node, SceneNode): continue if not node.getMeshData() and not node.callDecoration("isGroup"): continue # Node that doesnt have a mesh and is not a group. @@ -1134,7 +1150,7 @@ class CuraApplication(QtApplication): nodes = [] active_build_plate = self.getBuildPlateModel().activeBuildPlate for node in DepthFirstIterator(self.getController().getScene().getRoot()): - if not issubclass(type(node), SceneNode): + if not isinstance(node, SceneNode): continue if not node.getMeshData() and not node.callDecoration("isGroup"): continue # Node that doesnt have a mesh and is not a group. @@ -1158,7 +1174,7 @@ class CuraApplication(QtApplication): # What nodes are on the build plate and are not being moved fixed_nodes = [] for node in DepthFirstIterator(self.getController().getScene().getRoot()): - if not issubclass(type(node), SceneNode): + if not isinstance(node, SceneNode): continue if not node.getMeshData() and not node.callDecoration("isGroup"): continue # Node that doesnt have a mesh and is not a group. @@ -1186,7 +1202,7 @@ class CuraApplication(QtApplication): Logger.log("i", "Reloading all loaded mesh data.") nodes = [] for node in DepthFirstIterator(self.getController().getScene().getRoot()): - if not issubclass(type(node), SceneNode) or not node.getMeshData(): + if not isinstance(node, SceneNode) or not node.getMeshData(): continue nodes.append(node) @@ -1421,16 +1437,20 @@ class CuraApplication(QtApplication): filename = job.getFileName() self._currently_loading_files.remove(filename) - root = self.getController().getScene().getRoot() - arranger = Arrange.create(scene_root = root) - min_offset = 8 - self.fileLoaded.emit(filename) arrange_objects_on_load = ( not Preferences.getInstance().getValue("cura/use_multi_build_plate") or not Preferences.getInstance().getValue("cura/not_arrange_objects_on_load")) target_build_plate = self.getBuildPlateModel().activeBuildPlate if arrange_objects_on_load else -1 + root = self.getController().getScene().getRoot() + fixed_nodes = [] + for node_ in DepthFirstIterator(root): + if node_.callDecoration("isSliceable") and node_.callDecoration("getBuildPlateNumber") == target_build_plate: + fixed_nodes.append(node_) + arranger = Arrange.create(fixed_nodes = fixed_nodes) + min_offset = 8 + for original_node in nodes: # Create a CuraSceneNode just if the original node is not that type @@ -1442,11 +1462,7 @@ class CuraApplication(QtApplication): extension = os.path.splitext(filename)[1] if extension.lower() in self._non_sliceable_extensions: - self.getController().setActiveView("SimulationView") - view = self.getController().getActiveView() - view.resetLayerData() - view.setLayer(9999999) - view.calculateMaxLayers() + self.callLater(lambda: self.getController().setActiveView("SimulationView")) block_slicing_decorator = BlockSlicingDecorator() node.addDecorator(block_slicing_decorator) @@ -1500,12 +1516,11 @@ class CuraApplication(QtApplication): """ Checks if the given file URL is a valid project file. """ + file_path = QUrl(file_url).toLocalFile() + workspace_reader = self.getWorkspaceFileHandler().getReaderForFile(file_path) + if workspace_reader is None: + return False # non-project files won't get a reader try: - file_path = QUrl(file_url).toLocalFile() - workspace_reader = self.getWorkspaceFileHandler().getReaderForFile(file_path) - if workspace_reader is None: - return False # non-project files won't get a reader - result = workspace_reader.preRead(file_path, show_dialog=False) return result == WorkspaceReader.PreReadResult.accepted except Exception as e: diff --git a/cura/ObjectsModel.py b/cura/ObjectsModel.py index 5218127fc5..1516b3ee33 100644 --- a/cura/ObjectsModel.py +++ b/cura/ObjectsModel.py @@ -28,7 +28,7 @@ class ObjectsModel(ListModel): active_build_plate_number = self._build_plate_number group_nr = 1 for node in DepthFirstIterator(Application.getInstance().getController().getScene().getRoot()): - if not issubclass(type(node), SceneNode): + if not isinstance(node, SceneNode): continue if (not node.getMeshData() and not node.callDecoration("getLayerData")) and not node.callDecoration("isGroup"): continue diff --git a/cura/PlatformPhysics.py b/cura/PlatformPhysics.py index 0ad3cf1328..69890178e4 100755 --- a/cura/PlatformPhysics.py +++ b/cura/PlatformPhysics.py @@ -61,7 +61,7 @@ class PlatformPhysics: random.shuffle(nodes) for node in nodes: - if node is root or not issubclass(type(node), SceneNode) or node.getBoundingBox() is None: + if node is root or not isinstance(node, SceneNode) or node.getBoundingBox() is None: continue bbox = node.getBoundingBox() diff --git a/cura/PrintInformation.py b/cura/PrintInformation.py index 838628e37c..5d5d59ed3b 100644 --- a/cura/PrintInformation.py +++ b/cura/PrintInformation.py @@ -1,4 +1,4 @@ -# Copyright (c) 2017 Ultimaker B.V. +# Copyright (c) 2018 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. from PyQt5.QtCore import QObject, pyqtSignal, pyqtProperty @@ -65,6 +65,7 @@ class PrintInformation(QObject): self._backend = Application.getInstance().getBackend() if self._backend: self._backend.printDurationMessage.connect(self._onPrintDurationMessage) + Application.getInstance().getController().getScene().sceneChanged.connect(self.setToZeroPrintInformation) self._base_name = "" self._abbr_machine = "" @@ -171,7 +172,7 @@ class PrintInformation(QObject): def printTimes(self): return self._print_time_message_values[self._active_build_plate] - def _onPrintDurationMessage(self, build_plate_number, print_time, material_amounts): + def _onPrintDurationMessage(self, build_plate_number, print_time: Dict[str, int], material_amounts: list): self._updateTotalPrintTimePerFeature(build_plate_number, print_time) self.currentPrintTimeChanged.emit() diff --git a/cura/PrinterOutputDevice.py b/cura/PrinterOutputDevice.py index 2aa6fb382e..9e603b83ae 100644 --- a/cura/PrinterOutputDevice.py +++ b/cura/PrinterOutputDevice.py @@ -41,6 +41,9 @@ class PrinterOutputDevice(QObject, OutputDevice): # # Signal to indicate that the hotend of the active printer on the remote changed. hotendIdChanged = pyqtSignal() + # Signal to indicate that the info text about the connection has changed. + connectionTextChanged = pyqtSignal() + def __init__(self, device_id, parent = None): super().__init__(device_id = device_id, parent = parent) @@ -65,11 +68,21 @@ class PrinterOutputDevice(QObject, OutputDevice): self._connection_state = ConnectionState.closed self._address = "" + self._connection_text = "" - @pyqtProperty(str, constant = True) + @pyqtProperty(str, notify = connectionTextChanged) def address(self): return self._address + def setConnectionText(self, connection_text): + if self._connection_text != connection_text: + self._connection_text = connection_text + self.connectionTextChanged.emit() + + @pyqtProperty(str, constant=True) + def connectionText(self): + return self._connection_text + def materialHotendChangedMessage(self, callback): Logger.log("w", "materialHotendChangedMessage needs to be implemented, returning 'Yes'") callback(QMessageBox.Yes) @@ -122,7 +135,6 @@ class PrinterOutputDevice(QObject, OutputDevice): def controlItem(self): if not self._control_component: self._createControlViewFromQML() - return self._control_item def _createControlViewFromQML(self): diff --git a/cura/Scene/BuildPlateDecorator.py b/cura/Scene/BuildPlateDecorator.py index 44372976f0..c2fd3145dd 100644 --- a/cura/Scene/BuildPlateDecorator.py +++ b/cura/Scene/BuildPlateDecorator.py @@ -13,7 +13,7 @@ class BuildPlateDecorator(SceneNodeDecorator): # Make sure that groups are set correctly # setBuildPlateForSelection in CuraActions makes sure that no single childs are set. self._build_plate_number = nr - if issubclass(type(self._node), CuraSceneNode): + if isinstance(self._node, CuraSceneNode): self._node.transformChanged() # trigger refresh node without introducing a new signal if self._node and self._node.callDecoration("isGroup"): for child in self._node.getChildren(): diff --git a/cura/Scene/ConvexHullNode.py b/cura/Scene/ConvexHullNode.py index 02d8ed833c..c8106b5d15 100644 --- a/cura/Scene/ConvexHullNode.py +++ b/cura/Scene/ConvexHullNode.py @@ -65,7 +65,7 @@ class ConvexHullNode(SceneNode): ConvexHullNode.shader.setUniformValue("u_opacity", 0.6) if self.getParent(): - if self.getMeshData() and issubclass(type(self._node), SceneNode) and self._node.callDecoration("getBuildPlateNumber") == Application.getInstance().getBuildPlateModel().activeBuildPlate: + if self.getMeshData() and isinstance(self._node, SceneNode) and self._node.callDecoration("getBuildPlateNumber") == Application.getInstance().getBuildPlateModel().activeBuildPlate: renderer.queueNode(self, transparent = True, shader = ConvexHullNode.shader, backface_cull = True, sort = -8) if self._convex_hull_head_mesh: renderer.queueNode(self, shader = ConvexHullNode.shader, transparent = True, mesh = self._convex_hull_head_mesh, backface_cull = True, sort = -8) diff --git a/cura/Scene/CuraSceneController.py b/cura/Scene/CuraSceneController.py index c3e27ca3dd..a93a8769d0 100644 --- a/cura/Scene/CuraSceneController.py +++ b/cura/Scene/CuraSceneController.py @@ -10,9 +10,12 @@ from UM.Application import Application from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator from UM.Scene.SceneNode import SceneNode from UM.Scene.Selection import Selection +from UM.Signal import Signal class CuraSceneController(QObject): + activeBuildPlateChanged = Signal() + def __init__(self, objects_model: ObjectsModel, build_plate_model: BuildPlateModel): super().__init__() @@ -30,7 +33,7 @@ class CuraSceneController(QObject): source = args[0] else: source = None - if not issubclass(type(source), SceneNode): + if not isinstance(source, SceneNode): return max_build_plate = self._calcMaxBuildPlate() changed = False @@ -101,6 +104,7 @@ class CuraSceneController(QObject): self._build_plate_model.setActiveBuildPlate(nr) self._objects_model.setActiveBuildPlate(nr) + self.activeBuildPlateChanged.emit() @staticmethod def createCuraSceneController(): diff --git a/cura/Settings/CuraContainerRegistry.py b/cura/Settings/CuraContainerRegistry.py index 9202e57285..b945ec0609 100644 --- a/cura/Settings/CuraContainerRegistry.py +++ b/cura/Settings/CuraContainerRegistry.py @@ -94,7 +94,7 @@ class CuraContainerRegistry(ContainerRegistry): def _containerExists(self, container_type, container_name): container_class = ContainerStack if container_type == "machine" else InstanceContainer - return self.findContainersMetadata(id = container_name, type = container_type, ignore_case = True) or \ + return self.findContainersMetadata(container_type = container_class, id = container_name, type = container_type, ignore_case = True) or \ self.findContainersMetadata(container_type = container_class, name = container_name, type = container_type) ## Exports an profile to a file diff --git a/cura/Settings/CuraContainerStack.py b/cura/Settings/CuraContainerStack.py index 8e13b24358..b97bb3314e 100755 --- a/cura/Settings/CuraContainerStack.py +++ b/cura/Settings/CuraContainerStack.py @@ -144,7 +144,7 @@ class CuraContainerStack(ContainerStack): ## Set the material container. # - # \param new_quality_changes The new material container. It is expected to have a "type" metadata entry with the value "quality_changes". + # \param new_material The new material container. It is expected to have a "type" metadata entry with the value "material". def setMaterial(self, new_material: InstanceContainer, postpone_emit = False) -> None: self.replaceContainer(_ContainerIndexes.Material, new_material, postpone_emit = postpone_emit) @@ -155,7 +155,7 @@ class CuraContainerStack(ContainerStack): # to whatever the machine definition specifies as "preferred" container, or a fallback value. See findDefaultMaterial # for details. # - # \param new_quality_changes_id The ID of the new material container. + # \param new_material_id The ID of the new material container. # # \throws Exceptions.InvalidContainerError Raised when no container could be found with the specified ID. def setMaterialById(self, new_material_id: str) -> None: @@ -182,7 +182,7 @@ class CuraContainerStack(ContainerStack): ## Set the variant container. # - # \param new_quality_changes The new variant container. It is expected to have a "type" metadata entry with the value "quality_changes". + # \param new_variant The new variant container. It is expected to have a "type" metadata entry with the value "variant". def setVariant(self, new_variant: InstanceContainer) -> None: self.replaceContainer(_ContainerIndexes.Variant, new_variant) @@ -193,13 +193,13 @@ class CuraContainerStack(ContainerStack): # to whatever the machine definition specifies as "preferred" container, or a fallback value. See findDefaultVariant # for details. # - # \param new_quality_changes_id The ID of the new variant container. + # \param new_variant_id The ID of the new variant container. # # \throws Exceptions.InvalidContainerError Raised when no container could be found with the specified ID. def setVariantById(self, new_variant_id: str) -> None: variant = self._empty_variant if new_variant_id == "default": - new_variant = self.findDefaultVariant() + new_variant = self.findDefaultVariantBuildplate() if self.getMetaDataEntry("type") == "machine" else self.findDefaultVariant() if new_variant: variant = new_variant else: @@ -220,13 +220,13 @@ class CuraContainerStack(ContainerStack): ## Set the definition changes container. # - # \param new_quality_changes The new definition changes container. It is expected to have a "type" metadata entry with the value "quality_changes". + # \param new_definition_changes The new definition changes container. It is expected to have a "type" metadata entry with the value "definition_changes". def setDefinitionChanges(self, new_definition_changes: InstanceContainer) -> None: self.replaceContainer(_ContainerIndexes.DefinitionChanges, new_definition_changes) ## Set the definition changes container by an ID. # - # \param new_quality_changes_id The ID of the new definition changes container. + # \param new_definition_changes_id The ID of the new definition changes container. # # \throws Exceptions.InvalidContainerError Raised when no container could be found with the specified ID. def setDefinitionChangesById(self, new_definition_changes_id: str) -> None: @@ -245,13 +245,13 @@ class CuraContainerStack(ContainerStack): ## Set the definition container. # - # \param new_quality_changes The new definition container. It is expected to have a "type" metadata entry with the value "quality_changes". + # \param new_definition The new definition container. It is expected to have a "type" metadata entry with the value "definition". def setDefinition(self, new_definition: DefinitionContainerInterface) -> None: self.replaceContainer(_ContainerIndexes.Definition, new_definition) ## Set the definition container by an ID. # - # \param new_quality_changes_id The ID of the new definition container. + # \param new_definition_id The ID of the new definition container. # # \throws Exceptions.InvalidContainerError Raised when no container could be found with the specified ID. def setDefinitionById(self, new_definition_id: str) -> None: @@ -435,6 +435,51 @@ class CuraContainerStack(ContainerStack): Logger.log("w", "Could not find a valid default variant for stack {stack}", stack = self.id) return None + ## Find the global variant that should be used as "default". This is used for the buildplates. + # + # This will search for variants that match the current definition and pick the preferred one, + # if specified by the machine definition. + # + # The following criteria are used to find the default global variant: + # - If the machine definition does not have a metadata entry "has_variant_buildplates" set to True, return None + # - The definition of the variant should be the same as the machine definition for this stack. + # - The container should have a metadata entry "type" with value "variant" and "hardware_type" with value "buildplate". + # - If the machine definition has a metadata entry "preferred_variant_buildplate", filter the variant IDs based on that. + # + # \return The container that should be used as default, or None if nothing was found or the machine does not use variants. + # + # \note This method assumes the stack has a valid machine definition. + def findDefaultVariantBuildplate(self) -> Optional[ContainerInterface]: + definition = self._getMachineDefinition() + # has_variant_buildplates can be overridden in other containers and stacks. + # In the case of UM2, it is overridden in the GlobalStack + if not self.getMetaDataEntry("has_variant_buildplates"): + # If the machine does not use variants, we should never set a variant. + return None + + # First add any variant. Later, overwrite with preference if the preference is valid. + variant = None + definition_id = self._findInstanceContainerDefinitionId(definition) + variants = ContainerRegistry.getInstance().findInstanceContainers(definition = definition_id, type = "variant", hardware_type = "buildplate") + if variants: + variant = variants[0] + + preferred_variant_buildplate_id = definition.getMetaDataEntry("preferred_variant_buildplate") + if preferred_variant_buildplate_id: + preferred_variant_buildplates = ContainerRegistry.getInstance().findInstanceContainers(id = preferred_variant_buildplate_id, definition = definition_id, type = "variant") + if preferred_variant_buildplates: + variant = preferred_variant_buildplates[0] + else: + Logger.log("w", "The preferred variant buildplate \"{variant}\" of stack {stack} does not exist or is not a variant.", + variant = preferred_variant_buildplate_id, stack = self.id) + # And leave it at the default variant. + + if variant: + return variant + + Logger.log("w", "Could not find a valid default buildplate variant for stack {stack}", stack = self.id) + return None + ## Find the material that should be used as "default" material. # # This will search for materials that match the current definition and pick the preferred one, diff --git a/cura/Settings/ExtruderManager.py b/cura/Settings/ExtruderManager.py index 351843ae14..b5f9a35914 100755 --- a/cura/Settings/ExtruderManager.py +++ b/cura/Settings/ExtruderManager.py @@ -49,6 +49,9 @@ class ExtruderManager(QObject): ## Notify when the user switches the currently active extruder. activeExtruderChanged = pyqtSignal() + ## The signal notifies subscribers if extruders are added + extrudersAdded = pyqtSignal() + ## Gets the unique identifier of the currently active extruder stack. # # The currently active extruder stack is the stack that is currently being @@ -406,6 +409,7 @@ class ExtruderManager(QObject): if extruders_changed: self.extrudersChanged.emit(global_stack_id) + self.extrudersAdded.emit() self.setActiveExtruderIndex(0) ## Get all extruder values for a certain setting. diff --git a/cura/Settings/MachineManager.py b/cura/Settings/MachineManager.py index 50ab26f9df..06ccd300ab 100755 --- a/cura/Settings/MachineManager.py +++ b/cura/Settings/MachineManager.py @@ -50,6 +50,7 @@ class MachineManager(QObject): # Used to store the new containers until after confirming the dialog self._new_variant_container = None + self._new_buildplate_container = None self._new_material_container = None self._new_quality_containers = [] @@ -157,6 +158,10 @@ class MachineManager(QObject): def newVariant(self): return self._new_variant_container + @property + def newBuildplate(self): + return self._new_buildplate_container + @property def newMaterial(self): return self._new_material_container @@ -309,10 +314,11 @@ class MachineManager(QObject): self._global_container_stack.containersChanged.connect(self._onInstanceContainersChanged) self._global_container_stack.propertyChanged.connect(self._onPropertyChanged) - # set the global variant to empty as we now use the extruder stack at all times - CURA-4482 + # Global stack can have only a variant if it is a buildplate global_variant = self._global_container_stack.variant if global_variant != self._empty_variant_container: - self._global_container_stack.setVariant(self._empty_variant_container) + if global_variant.getMetaDataEntry("hardware_type") != "buildplate": + self._global_container_stack.setVariant(self._empty_variant_container) # set the global material to empty as we now use the extruder stack at all times - CURA-4482 global_material = self._global_container_stack.material @@ -675,6 +681,14 @@ class MachineManager(QObject): return quality.getId() return "" + @pyqtProperty(str, notify=activeVariantChanged) + def globalVariantId(self) -> str: + if self._global_container_stack: + variant = self._global_container_stack.variant + if variant and not isinstance(variant, type(self._empty_variant_container)): + return variant.getId() + return "" + @pyqtProperty(str, notify = activeQualityChanged) def activeQualityType(self) -> str: if self._active_container_stack: @@ -855,6 +869,24 @@ class MachineManager(QObject): else: Logger.log("w", "While trying to set the active variant, no variant was found to replace.") + @pyqtSlot(str) + def setActiveVariantBuildplate(self, variant_buildplate_id: str): + with postponeSignals(*self._getContainerChangedSignals(), compress = CompressTechnique.CompressPerParameterValue): + containers = ContainerRegistry.getInstance().findInstanceContainers(id = variant_buildplate_id) + if not containers or not self._global_container_stack: + return + Logger.log("d", "Attempting to change the active buildplate to %s", variant_buildplate_id) + old_buildplate = self._global_container_stack.variant + if old_buildplate: + self.blurSettings.emit() + self._new_buildplate_container = containers[0] # self._active_container_stack will be updated with a delay + Logger.log("d", "Active buildplate changed to {active_variant_buildplate_id}".format(active_variant_buildplate_id = containers[0].getId())) + + # Force set the active quality as it is so the values are updated + self.setActiveMaterial(self._active_container_stack.material.getId()) + else: + Logger.log("w", "While trying to set the active buildplate, no buildplate was found to replace.") + ## set the active quality # \param quality_id The quality_id of either a quality or a quality_changes @pyqtSlot(str) @@ -932,6 +964,10 @@ class MachineManager(QObject): self._active_container_stack.variant = self._new_variant_container self._new_variant_container = None + if self._new_buildplate_container is not None: + self._global_container_stack.variant = self._new_buildplate_container + self._new_buildplate_container = None + if self._new_material_container is not None: self._active_container_stack.material = self._new_material_container self._new_material_container = None @@ -952,6 +988,7 @@ class MachineManager(QObject): # Used for ignoring any changes when switching between printers (setActiveMachine) def _cancelDelayedActiveContainerStackChanges(self): self._new_material_container = None + self._new_buildplate_container = None self._new_variant_container = None ## Determine the quality and quality changes settings for the current machine for a quality name. @@ -1116,6 +1153,15 @@ class MachineManager(QObject): return "" + @pyqtProperty(str, notify = activeVariantChanged) + def activeVariantBuildplateName(self) -> str: + if self._global_container_stack: + variant = self._global_container_stack.variant + if variant: + return variant.getName() + + return "" + @pyqtProperty(str, notify = globalContainerChanged) def activeDefinitionId(self) -> str: if self._global_container_stack: @@ -1213,7 +1259,6 @@ class MachineManager(QObject): def hasMaterials(self) -> bool: if self._global_container_stack: return Util.parseBool(self._global_container_stack.getMetaDataEntry("has_materials", False)) - return False @pyqtProperty(bool, notify = globalContainerChanged) @@ -1222,6 +1267,53 @@ class MachineManager(QObject): return Util.parseBool(self._global_container_stack.getMetaDataEntry("has_variants", False)) return False + @pyqtProperty(bool, notify = globalContainerChanged) + def hasVariantBuildplates(self) -> bool: + if self._global_container_stack: + return Util.parseBool(self._global_container_stack.getMetaDataEntry("has_variant_buildplates", False)) + return False + + ## The selected buildplate is compatible if it is compatible with all the materials in all the extruders + @pyqtProperty(bool, notify = activeMaterialChanged) + def variantBuildplateCompatible(self) -> bool: + if not self._global_container_stack: + return True + + buildplate_compatible = True # It is compatible by default + extruder_stacks = self._global_container_stack.extruders.values() + for stack in extruder_stacks: + material_container = stack.material + if material_container == self._empty_material_container: + continue + if material_container.getMetaDataEntry("buildplate_compatible"): + buildplate_compatible = buildplate_compatible and material_container.getMetaDataEntry("buildplate_compatible")[self.activeVariantBuildplateName] + + return buildplate_compatible + + ## The selected buildplate is usable if it is usable for all materials OR it is compatible for one but not compatible + # for the other material but the buildplate is still usable + @pyqtProperty(bool, notify = activeMaterialChanged) + def variantBuildplateUsable(self) -> bool: + if not self._global_container_stack: + return True + + # Here the next formula is being calculated: + # result = (not (material_left_compatible and material_right_compatible)) and + # (material_left_compatible or material_left_usable) and + # (material_right_compatible or material_right_usable) + result = not self.variantBuildplateCompatible + extruder_stacks = self._global_container_stack.extruders.values() + for stack in extruder_stacks: + material_container = stack.material + if material_container == self._empty_material_container: + continue + buildplate_compatible = material_container.getMetaDataEntry("buildplate_compatible")[self.activeVariantBuildplateName] if material_container.getMetaDataEntry("buildplate_compatible") else True + buildplate_usable = material_container.getMetaDataEntry("buildplate_recommended")[self.activeVariantBuildplateName] if material_container.getMetaDataEntry("buildplate_recommended") else True + + result = result and (buildplate_compatible or buildplate_usable) + + return result + ## Property to indicate if a machine has "specialized" material profiles. # Some machines have their own material profiles that "override" the default catch all profiles. @pyqtProperty(bool, notify = globalContainerChanged) diff --git a/cura/Settings/ProfilesModel.py b/cura/Settings/ProfilesModel.py index 3b43e2740a..77cd407457 100644 --- a/cura/Settings/ProfilesModel.py +++ b/cura/Settings/ProfilesModel.py @@ -36,6 +36,8 @@ class ProfilesModel(InstanceContainersModel): Application.getInstance().getMachineManager().activeStackChanged.connect(self._update) Application.getInstance().getMachineManager().activeMaterialChanged.connect(self._update) + self._empty_quality = ContainerRegistry.getInstance().findContainers(id = "empty_quality")[0] + # Factory function, used by QML @staticmethod def createProfilesModel(engine, js_engine): @@ -85,13 +87,10 @@ class ProfilesModel(InstanceContainersModel): if quality.getMetaDataEntry("quality_type") not in quality_type_set: result.append(quality) - # if still profiles are found, add a single empty_quality ("Not supported") instance to the drop down list - if len(result) == 0: - # If not qualities are found we dynamically create a not supported container for this machine + material combination - not_supported_container = ContainerRegistry.getInstance().findContainers(id = "empty_quality")[0] - result.append(not_supported_container) + if len(result) > 1 and self._empty_quality in result: + result.remove(self._empty_quality) - return {item.getId():item for item in result}, {} #Only return true profiles for now, no metadata. The quality manager is not able to get only metadata yet. + return {item.getId(): item for item in result}, {} #Only return true profiles for now, no metadata. The quality manager is not able to get only metadata yet. ## Re-computes the items in this model, and adds the layer height role. def _recomputeItems(self): @@ -114,7 +113,6 @@ class ProfilesModel(InstanceContainersModel): # active machine and material, and later yield the right ones. tmp_all_quality_items = OrderedDict() for item in super()._recomputeItems(): - profiles = container_registry.findContainersMetadata(id = item["id"]) if not profiles or "quality_type" not in profiles[0]: quality_type = "" diff --git a/cura/Settings/QualityAndUserProfilesModel.py b/cura/Settings/QualityAndUserProfilesModel.py index bc81df976b..645e63acdb 100644 --- a/cura/Settings/QualityAndUserProfilesModel.py +++ b/cura/Settings/QualityAndUserProfilesModel.py @@ -1,17 +1,21 @@ # Copyright (c) 2016 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. from UM.Application import Application +from UM.Settings.ContainerRegistry import ContainerRegistry from cura.QualityManager import QualityManager from cura.Settings.ProfilesModel import ProfilesModel from cura.Settings.ExtruderManager import ExtruderManager + ## QML Model for listing the current list of valid quality and quality changes profiles. # class QualityAndUserProfilesModel(ProfilesModel): def __init__(self, parent = None): super().__init__(parent) + self._empty_quality = ContainerRegistry.getInstance().findInstanceContainers(id = "empty_quality")[0] + ## Fetch the list of containers to display. # # See UM.Settings.Models.InstanceContainersModel._fetchInstanceContainers(). @@ -35,12 +39,16 @@ class QualityAndUserProfilesModel(ProfilesModel): # Filter the quality_change by the list of available quality_types quality_type_set = set([x.getMetaDataEntry("quality_type") for x in quality_list]) - filtered_quality_changes = {qc.getId():qc for qc in quality_changes_list if + # Also show custom profiles based on "Not Supported" quality profile + quality_type_set.add(self._empty_quality.getMetaDataEntry("quality_type")) + filtered_quality_changes = {qc.getId(): qc for qc in quality_changes_list if qc.getMetaDataEntry("quality_type") in quality_type_set and qc.getMetaDataEntry("extruder") is not None and (qc.getMetaDataEntry("extruder") == active_extruder.definition.getMetaDataEntry("quality_definition") or qc.getMetaDataEntry("extruder") == active_extruder.definition.getId())} result = filtered_quality_changes - result.update({q.getId():q for q in quality_list}) - return result, {} #Only return true profiles for now, no metadata. The quality manager is not able to get only metadata yet. \ No newline at end of file + for q in quality_list: + if q.getId() != "empty_quality": + result[q.getId()] = q + return result, {} #Only return true profiles for now, no metadata. The quality manager is not able to get only metadata yet. diff --git a/cura/Settings/QualitySettingsModel.py b/cura/Settings/QualitySettingsModel.py index 243fd146dc..fb1aa9a6b2 100644 --- a/cura/Settings/QualitySettingsModel.py +++ b/cura/Settings/QualitySettingsModel.py @@ -1,8 +1,6 @@ # Copyright (c) 2017 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. -import collections - from PyQt5.QtCore import pyqtProperty, pyqtSignal, Qt from UM.Logger import Logger @@ -42,6 +40,8 @@ class QualitySettingsModel(UM.Qt.ListModel.ListModel): self.addRoleName(self.UserValueRole, "user_value") self.addRoleName(self.CategoryRole, "category") + self._empty_quality = self._container_registry.findInstanceContainers(id = "empty_quality")[0] + def setExtruderId(self, extruder_id): if extruder_id != self._extruder_id: self._extruder_id = extruder_id @@ -107,77 +107,87 @@ class QualitySettingsModel(UM.Qt.ListModel.ListModel): else: quality_changes_container = containers[0] - criteria = { - "type": "quality", - "quality_type": quality_changes_container.getMetaDataEntry("quality_type"), - "definition": quality_changes_container.getDefinition().getId() - } + if quality_changes_container.getMetaDataEntry("quality_type") == self._empty_quality.getMetaDataEntry("quality_type"): + quality_container = self._empty_quality + else: + criteria = { + "type": "quality", + "quality_type": quality_changes_container.getMetaDataEntry("quality_type"), + "definition": quality_changes_container.getDefinition().getId() + } - quality_container = self._container_registry.findInstanceContainers(**criteria) - if not quality_container: - Logger.log("w", "Could not find a quality container matching quality changes %s", quality_changes_container.getId()) - return - quality_container = quality_container[0] + quality_container = self._container_registry.findInstanceContainers(**criteria) + if not quality_container: + Logger.log("w", "Could not find a quality container matching quality changes %s", quality_changes_container.getId()) + return + + quality_container = quality_container[0] quality_type = quality_container.getMetaDataEntry("quality_type") - definition_id = Application.getInstance().getMachineManager().getQualityDefinitionId(quality_container.getDefinition()) - definition = quality_container.getDefinition() - # Check if the definition container has a translation file. - definition_suffix = ContainerRegistry.getMimeTypeForContainer(type(definition)).preferredSuffix - catalog = i18nCatalog(os.path.basename(definition_id + "." + definition_suffix)) - if catalog.hasTranslationLoaded(): - self._i18n_catalog = catalog + if quality_type == "not_supported": + containers = [] + else: + definition_id = Application.getInstance().getMachineManager().getQualityDefinitionId(quality_container.getDefinition()) + definition = quality_container.getDefinition() - for file_name in quality_container.getDefinition().getInheritedFiles(): - catalog = i18nCatalog(os.path.basename(file_name)) + # Check if the definition container has a translation file. + definition_suffix = ContainerRegistry.getMimeTypeForContainer(type(definition)).preferredSuffix + catalog = i18nCatalog(os.path.basename(definition_id + "." + definition_suffix)) if catalog.hasTranslationLoaded(): self._i18n_catalog = catalog - criteria = {"type": "quality", "quality_type": quality_type, "definition": definition_id} + for file_name in quality_container.getDefinition().getInheritedFiles(): + catalog = i18nCatalog(os.path.basename(file_name)) + if catalog.hasTranslationLoaded(): + self._i18n_catalog = catalog - if self._material_id and self._material_id != "empty_material": - criteria["material"] = self._material_id + criteria = {"type": "quality", "quality_type": quality_type, "definition": definition_id} - criteria["extruder"] = self._extruder_id + if self._material_id and self._material_id != "empty_material": + criteria["material"] = self._material_id - containers = self._container_registry.findInstanceContainers(**criteria) - if not containers: - # Try again, this time without extruder - new_criteria = criteria.copy() - new_criteria.pop("extruder") - containers = self._container_registry.findInstanceContainers(**new_criteria) + criteria["extruder"] = self._extruder_id - if not containers and "material" in criteria: - # Try again, this time without material - criteria.pop("material", None) containers = self._container_registry.findInstanceContainers(**criteria) + if not containers: + # Try again, this time without extruder + new_criteria = criteria.copy() + new_criteria.pop("extruder") + containers = self._container_registry.findInstanceContainers(**new_criteria) - if not containers: - # Try again, this time without material or extruder - criteria.pop("extruder") # "material" has already been popped - containers = self._container_registry.findInstanceContainers(**criteria) + if not containers and "material" in criteria: + # Try again, this time without material + criteria.pop("material", None) + containers = self._container_registry.findInstanceContainers(**criteria) - if not containers: - Logger.log("w", "Could not find any quality containers matching the search criteria %s" % str(criteria)) - return + if not containers: + # Try again, this time without material or extruder + criteria.pop("extruder") # "material" has already been popped + containers = self._container_registry.findInstanceContainers(**criteria) + + if not containers: + Logger.log("w", "Could not find any quality containers matching the search criteria %s" % str(criteria)) + return if quality_changes_container: - criteria = {"type": "quality_changes", "quality_type": quality_type, "definition": definition_id, "name": quality_changes_container.getName()} - if self._extruder_definition_id != "": - extruder_definitions = self._container_registry.findDefinitionContainers(id = self._extruder_definition_id) - if extruder_definitions: - criteria["extruder"] = Application.getInstance().getMachineManager().getQualityDefinitionId(extruder_definitions[0]) - criteria["name"] = quality_changes_container.getName() + if quality_type == "not_supported": + criteria = {"type": "quality_changes", "quality_type": quality_type, "name": quality_changes_container.getName()} else: - criteria["extruder"] = None + criteria = {"type": "quality_changes", "quality_type": quality_type, "definition": definition_id, "name": quality_changes_container.getName()} + if self._extruder_definition_id != "": + extruder_definitions = self._container_registry.findDefinitionContainers(id = self._extruder_definition_id) + if extruder_definitions: + criteria["extruder"] = Application.getInstance().getMachineManager().getQualityDefinitionId(extruder_definitions[0]) + criteria["name"] = quality_changes_container.getName() + else: + criteria["extruder"] = None changes = self._container_registry.findInstanceContainers(**criteria) if changes: containers.extend(changes) global_container_stack = Application.getInstance().getGlobalContainerStack() - is_multi_extrusion = global_container_stack.getProperty("machine_extruder_count", "value") > 1 current_category = "" for definition in definition_container.findDefinitions(): @@ -213,15 +223,14 @@ class QualitySettingsModel(UM.Qt.ListModel.ListModel): if profile_value is None and user_value is None: continue - if is_multi_extrusion: - settable_per_extruder = global_container_stack.getProperty(definition.key, "settable_per_extruder") - # If a setting is not settable per extruder (global) and we're looking at an extruder tab, don't show this value. - if self._extruder_id != "" and not settable_per_extruder: - continue + settable_per_extruder = global_container_stack.getProperty(definition.key, "settable_per_extruder") + # If a setting is not settable per extruder (global) and we're looking at an extruder tab, don't show this value. + if self._extruder_id != "" and not settable_per_extruder: + continue - # If a setting is settable per extruder (not global) and we're looking at global tab, don't show this value. - if self._extruder_id == "" and settable_per_extruder: - continue + # If a setting is settable per extruder (not global) and we're looking at global tab, don't show this value. + if self._extruder_id == "" and settable_per_extruder: + continue label = definition.label if self._i18n_catalog: diff --git a/cura/Settings/UserProfilesModel.py b/cura/Settings/UserProfilesModel.py index e093c6c132..6605f52f8a 100644 --- a/cura/Settings/UserProfilesModel.py +++ b/cura/Settings/UserProfilesModel.py @@ -2,6 +2,8 @@ # Cura is released under the terms of the LGPLv3 or higher. from UM.Application import Application +from UM.Settings.ContainerRegistry import ContainerRegistry + from cura.QualityManager import QualityManager from cura.Settings.ProfilesModel import ProfilesModel from cura.Settings.ExtruderManager import ExtruderManager @@ -22,6 +24,8 @@ class UserProfilesModel(ProfilesModel): for material in self.__current_materials: material.metaDataChanged.connect(self._onContainerChanged) + self._empty_quality = ContainerRegistry.getInstance().findContainers(id = "empty_quality")[0] + ## Fetch the list of containers to display. # # See UM.Settings.Models.InstanceContainersModel._fetchInstanceContainers(). @@ -45,6 +49,7 @@ class UserProfilesModel(ProfilesModel): # Filter the quality_change by the list of available quality_types quality_type_set = set([x.getMetaDataEntry("quality_type") for x in quality_list]) + quality_type_set.add(self._empty_quality.getMetaDataEntry("quality_type")) filtered_quality_changes = {qc.getId():qc for qc in quality_changes_list if qc.getMetaDataEntry("quality_type") in quality_type_set and diff --git a/plugins/3MFReader/ThreeMFReader.py b/plugins/3MFReader/ThreeMFReader.py index 727bce2112..09ed1e126d 100755 --- a/plugins/3MFReader/ThreeMFReader.py +++ b/plugins/3MFReader/ThreeMFReader.py @@ -81,8 +81,10 @@ class ThreeMFReader(MeshReader): self._object_count += 1 node_name = "Object %s" % self._object_count + active_build_plate = Application.getInstance().getBuildPlateModel().activeBuildPlate + um_node = CuraSceneNode() - um_node.addDecorator(BuildPlateDecorator(0)) + um_node.addDecorator(BuildPlateDecorator(active_build_plate)) um_node.setName(node_name) transformation = self._createMatrixFromTransformationString(savitar_node.getTransformation()) um_node.setTransformation(transformation) diff --git a/plugins/3MFReader/ThreeMFWorkspaceReader.py b/plugins/3MFReader/ThreeMFWorkspaceReader.py index 40d64590f5..77a7da8b6a 100755 --- a/plugins/3MFReader/ThreeMFWorkspaceReader.py +++ b/plugins/3MFReader/ThreeMFWorkspaceReader.py @@ -168,11 +168,9 @@ class ThreeMFWorkspaceReader(WorkspaceReader): Logger.log("w", "Unknown definition container type %s for %s", definition_container_type, each_definition_container_file) Job.yieldThread() - # sanity check + if machine_definition_container_count != 1: - msg = "Expecting one machine definition container but got %s" % machine_definition_container_count - Logger.log("e", msg) - raise RuntimeError(msg) + return WorkspaceReader.PreReadResult.failed #Not a workspace file but ordinary 3MF. material_labels = [] material_conflict = False @@ -271,7 +269,6 @@ class ThreeMFWorkspaceReader(WorkspaceReader): # if the global stack is found, we check if there are conflicts in the extruder stacks if containers_found_dict["machine"] and not machine_conflict: for extruder_stack_file in extruder_stack_files: - container_id = self._stripFileToId(extruder_stack_file) serialized = archive.open(extruder_stack_file).read().decode("utf-8") parser = configparser.ConfigParser() parser.read_string(serialized) @@ -303,7 +300,6 @@ class ThreeMFWorkspaceReader(WorkspaceReader): break num_visible_settings = 0 - has_visible_settings_string = False try: temp_preferences = Preferences() serialized = archive.open("Cura/preferences.cfg").read().decode("utf-8") @@ -609,7 +605,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader): instance_container.setName(self._container_registry.uniqueName(instance_container.getName())) new_changes_container_id = self.getNewId(instance_container.getId()) - instance_container._id = new_changes_container_id + instance_container.setMetaDataEntry("id", new_changes_container_id) # TODO: we don't know the following is correct or not, need to verify # AND REFACTOR!!! diff --git a/plugins/3MFWriter/ThreeMFWriter.py b/plugins/3MFWriter/ThreeMFWriter.py index 79b821fe31..c4b7035cf1 100644 --- a/plugins/3MFWriter/ThreeMFWriter.py +++ b/plugins/3MFWriter/ThreeMFWriter.py @@ -6,8 +6,9 @@ from UM.Math.Vector import Vector from UM.Logger import Logger from UM.Math.Matrix import Matrix from UM.Application import Application -import UM.Scene.SceneNode -from cura.Scene.CuraSceneNode import CuraSceneNode +from UM.Scene.SceneNode import SceneNode + +from cura.CuraApplication import CuraApplication import Savitar @@ -62,11 +63,15 @@ class ThreeMFWriter(MeshWriter): self._store_archive = store_archive ## Convenience function that converts an Uranium SceneNode object to a SavitarSceneNode - # \returns Uranium Scenen node. + # \returns Uranium Scene node. def _convertUMNodeToSavitarNode(self, um_node, transformation = Matrix()): - if type(um_node) not in [UM.Scene.SceneNode.SceneNode, CuraSceneNode]: + if not isinstance(um_node, SceneNode): return None + active_build_plate_nr = CuraApplication.getInstance().getBuildPlateModel().activeBuildPlate + if um_node.callDecoration("getBuildPlateNumber") != active_build_plate_nr: + return + savitar_node = Savitar.SceneNode() node_matrix = um_node.getLocalTransformation() @@ -97,6 +102,9 @@ class ThreeMFWriter(MeshWriter): savitar_node.setSetting(key, str(stack.getProperty(key, "value"))) for child_node in um_node.getChildren(): + # only save the nodes on the active build plate + if child_node.callDecoration("getBuildPlateNumber") != active_build_plate_nr: + continue savitar_child_node = self._convertUMNodeToSavitarNode(child_node) if savitar_child_node is not None: savitar_node.addChild(savitar_child_node) diff --git a/plugins/CuraEngineBackend/CuraEngineBackend.py b/plugins/CuraEngineBackend/CuraEngineBackend.py index e8c830b901..9713211ad3 100755 --- a/plugins/CuraEngineBackend/CuraEngineBackend.py +++ b/plugins/CuraEngineBackend/CuraEngineBackend.py @@ -88,7 +88,7 @@ class CuraEngineBackend(QObject, Backend): # self._global_container_stack = None Application.getInstance().globalContainerStackChanged.connect(self._onGlobalStackChanged) - Application.getInstance().getExtruderManager().activeExtruderChanged.connect(self._onGlobalStackChanged) + Application.getInstance().getExtruderManager().extrudersAdded.connect(self._onGlobalStackChanged) self._onGlobalStackChanged() Application.getInstance().stacksValidationFinished.connect(self._onStackErrorCheckFinished) @@ -423,7 +423,7 @@ class CuraEngineBackend(QObject, Backend): # # \param source The scene node that was changed. def _onSceneChanged(self, source): - if not issubclass(type(source), SceneNode): + if not isinstance(source, SceneNode): return build_plate_changed = set() @@ -722,7 +722,7 @@ class CuraEngineBackend(QObject, 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())) + extruders = list(self._global_container_stack.extruders.values()) for extruder in extruders: extruder.propertyChanged.disconnect(self._onSettingChanged) @@ -733,7 +733,7 @@ class CuraEngineBackend(QObject, Backend): 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.containersChanged.connect(self._onChanged) - extruders = list(ExtruderManager.getInstance().getMachineExtruders(self._global_container_stack.getId())) + extruders = list(self._global_container_stack.extruders.values()) for extruder in extruders: extruder.propertyChanged.connect(self._onSettingChanged) extruder.containersChanged.connect(self._onChanged) diff --git a/plugins/CuraEngineBackend/StartSliceJob.py b/plugins/CuraEngineBackend/StartSliceJob.py index b0e19e7f39..fa5473ba38 100644 --- a/plugins/CuraEngineBackend/StartSliceJob.py +++ b/plugins/CuraEngineBackend/StartSliceJob.py @@ -122,6 +122,12 @@ class StartSliceJob(Job): self.setResult(StartJobResult.BuildPlateError) return + # Don't slice if the buildplate or the nozzle type is incompatible with the materials + if not Application.getInstance().getMachineManager().variantBuildplateCompatible and \ + not Application.getInstance().getMachineManager().variantBuildplateUsable: + self.setResult(StartJobResult.MaterialIncompatible) + return + for extruder_stack in ExtruderManager.getInstance().getMachineExtruders(stack.getId()): material = extruder_stack.findContainer({"type": "material"}) if material: diff --git a/plugins/MachineSettingsAction/MachineSettingsAction.py b/plugins/MachineSettingsAction/MachineSettingsAction.py index a939d033fc..101ba54ed0 100755 --- a/plugins/MachineSettingsAction/MachineSettingsAction.py +++ b/plugins/MachineSettingsAction/MachineSettingsAction.py @@ -225,7 +225,10 @@ class MachineSettingsAction(MachineAction): material_approximate_diameter = str(round(material_diameter)) machine_diameter = extruder_stack.definitionChanges.getProperty("material_diameter", "value") if not machine_diameter: - machine_diameter = extruder_stack.definition.getProperty("material_diameter", "value") + if extruder_stack.definition.hasProperty("material_diameter", "value"): + machine_diameter = extruder_stack.definition.getProperty("material_diameter", "value") + else: + machine_diameter = self._global_container_stack.definition.getProperty("material_diameter", "value") machine_approximate_diameter = str(round(machine_diameter)) if material_approximate_diameter != machine_approximate_diameter: diff --git a/plugins/PostProcessingPlugin/PostProcessingPlugin.py b/plugins/PostProcessingPlugin/PostProcessingPlugin.py index 657e5c5387..fa519f48be 100644 --- a/plugins/PostProcessingPlugin/PostProcessingPlugin.py +++ b/plugins/PostProcessingPlugin/PostProcessingPlugin.py @@ -54,7 +54,11 @@ class PostProcessingPlugin(QObject, Extension): ## Execute all post-processing scripts on the gcode. def execute(self, output_device): scene = Application.getInstance().getController().getScene() - gcode_dict = getattr(scene, "gcode_dict") + gcode_dict = None + + if hasattr(scene, "gcode_dict"): + gcode_dict = getattr(scene, "gcode_dict") + if not gcode_dict: return diff --git a/plugins/SimulationView/SimulationPass.py b/plugins/SimulationView/SimulationPass.py index c9c1443bfe..76d7127534 100644 --- a/plugins/SimulationView/SimulationPass.py +++ b/plugins/SimulationView/SimulationPass.py @@ -106,7 +106,7 @@ class SimulationPass(RenderPass): nozzle_node = node nozzle_node.setVisible(False) - elif issubclass(type(node), SceneNode) and (node.getMeshData() or node.callDecoration("isBlockSlicing")) and node.isVisible(): + elif isinstance(node, SceneNode) and (node.getMeshData() or node.callDecoration("isBlockSlicing")) and node.isVisible(): layer_data = node.callDecoration("getLayerData") if not layer_data: continue diff --git a/plugins/SimulationView/SimulationView.py b/plugins/SimulationView/SimulationView.py index f667aff998..dfecda06bb 100644 --- a/plugins/SimulationView/SimulationView.py +++ b/plugins/SimulationView/SimulationView.py @@ -1,4 +1,4 @@ -# Copyright (c) 2017 Ultimaker B.V. +# Copyright (c) 2018 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. import sys @@ -344,7 +344,12 @@ class SimulationView(View): self._max_feedrate = max(float(p.lineFeedrates.max()), self._max_feedrate) self._min_feedrate = min(float(p.lineFeedrates.min()), self._min_feedrate) self._max_thickness = max(float(p.lineThicknesses.max()), self._max_thickness) - self._min_thickness = min(float(p.lineThicknesses.min()), self._min_thickness) + try: + self._min_thickness = min(float(p.lineThicknesses[numpy.nonzero(p.lineThicknesses)].min()), self._min_thickness) + except: + # Sometimes, when importing a GCode the line thicknesses are zero and so the minimum (avoiding + # the zero) can't be calculated + Logger.log("i", "Min thickness can't be calculated because all the values are zero") if max_layer_number < layer_id: max_layer_number = layer_id if min_layer_number > layer_id: diff --git a/plugins/SimulationView/SimulationViewProxy.py b/plugins/SimulationView/SimulationViewProxy.py index e144b841e6..a84b151983 100644 --- a/plugins/SimulationView/SimulationViewProxy.py +++ b/plugins/SimulationView/SimulationViewProxy.py @@ -1,4 +1,4 @@ -# Copyright (c) 2017 Ultimaker B.V. +# Copyright (c) 2018 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. from PyQt5.QtCore import QObject, pyqtSignal, pyqtProperty @@ -117,7 +117,7 @@ class SimulationViewProxy(QObject): def setSimulationViewType(self, layer_view_type): active_view = self._controller.getActiveView() if isinstance(active_view, SimulationView.SimulationView.SimulationView): - active_view.setSimulationViewisinstance(layer_view_type) + active_view.setSimulationViewType(layer_view_type) @pyqtSlot(result=int) def getSimulationViewType(self): diff --git a/plugins/SimulationView/__init__.py b/plugins/SimulationView/__init__.py index 15e113bd8e..360fdc1de9 100644 --- a/plugins/SimulationView/__init__.py +++ b/plugins/SimulationView/__init__.py @@ -1,4 +1,4 @@ -# Copyright (c) 2017 Ultimaker B.V. +# Copyright (c) 2018 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. from PyQt5.QtQml import qmlRegisterSingletonType @@ -18,7 +18,7 @@ def getMetaData(): } def createSimulationViewProxy(engine, script_engine): - return SimulationViewProxy.SimulatorViewProxy() + return SimulationViewProxy.SimulationViewProxy() def register(app): simulation_view = SimulationView.SimulationView() diff --git a/plugins/UM3NetworkPrinting/ClusterMonitorItem.qml b/plugins/UM3NetworkPrinting/ClusterMonitorItem.qml index df102915ff..86bdaae0a5 100644 --- a/plugins/UM3NetworkPrinting/ClusterMonitorItem.qml +++ b/plugins/UM3NetworkPrinting/ClusterMonitorItem.qml @@ -52,13 +52,19 @@ Component { id: addRemovePrintersLabel anchors.right: parent.right - text: "Add / remove printers" + text: catalog.i18nc("@label link to connect manager", "Add/Remove printers") + font: UM.Theme.getFont("default") + color: UM.Theme.getColor("text") + linkColor: UM.Theme.getColor("text_link") } MouseArea { anchors.fill: addRemovePrintersLabel + hoverEnabled: true onClicked: Cura.MachineManager.printerOutputDevices[0].openPrinterControlPanel() + onEntered: addRemovePrintersLabel.font.underline = true + onExited: addRemovePrintersLabel.font.underline = false } } diff --git a/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py b/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py index 4283042bf2..b5cbc33d51 100644 --- a/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py +++ b/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py @@ -69,6 +69,8 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice): self.setShortDescription(i18n_catalog.i18nc("@action:button Preceded by 'Ready to'.", "Print over network")) self.setDescription(i18n_catalog.i18nc("@properties:tooltip", "Print over network")) + self.setConnectionText(i18n_catalog.i18nc("@info:status", "Connected over the network")) + self._printer_uuid_to_unique_name_mapping = {} self._finished_jobs = [] @@ -76,20 +78,26 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice): self._cluster_size = int(properties.get(b"cluster_size", 0)) def requestWrite(self, nodes, file_name=None, filter_by_machine=False, file_handler=None, **kwargs): - # Notify the UI that a switch to the print monitor should happen - Application.getInstance().getController().setActiveStage("MonitorStage") self.writeStarted.emit(self) - self._gcode = getattr(Application.getInstance().getController().getScene(), "gcode_list", []) - if not self._gcode: + gcode_dict = getattr(Application.getInstance().getController().getScene(), "gcode_dict", []) + active_build_plate_id = Application.getInstance().getBuildPlateModel().activeBuildPlate + gcode_list = gcode_dict[active_build_plate_id] + + if not gcode_list: # Unable to find g-code. Nothing to send return + self._gcode = gcode_list + if len(self._printers) > 1: self._spawnPrinterSelectionDialog() else: self.sendPrintJob() + # Notify the UI that a switch to the print monitor should happen + Application.getInstance().getController().setActiveStage("MonitorStage") + def _spawnPrinterSelectionDialog(self): if self._printer_selection_dialog is None: path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "PrintWindow.qml") @@ -240,7 +248,10 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice): newly_finished_jobs = [job for job in finished_jobs if job not in self._finished_jobs and job.owner == username] for job in newly_finished_jobs: - job_completed_text = i18n_catalog.i18nc("@info:status", "Printer '{printer_name}' has finished printing '{job_name}'.".format(printer_name=job.assignedPrinter.name, job_name = job.name)) + if job.assignedPrinter: + job_completed_text = i18n_catalog.i18nc("@info:status", "Printer '{printer_name}' has finished printing '{job_name}'.".format(printer_name=job.assignedPrinter.name, job_name = job.name)) + else: + job_completed_text = i18n_catalog.i18nc("@info:status", "The print job '{job_name}' was finished.".format(job_name = job.name)) job_completed_message = Message(text=job_completed_text, title = i18n_catalog.i18nc("@info:status", "Print finished")) job_completed_message.show() diff --git a/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py b/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py index 786b97d034..647a7f822c 100644 --- a/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py +++ b/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py @@ -78,10 +78,16 @@ class LegacyUM3OutputDevice(NetworkedPrinterOutputDevice): def _onAuthenticationStateChanged(self): # We only accept commands if we are authenticated. + self._setAcceptsCommands(self._authentication_state == AuthState.Authenticated) + if self._authentication_state == AuthState.Authenticated: - self._setAcceptsCommands(True) - else: - self._setAcceptsCommands(False) + self.setConnectionText(i18n_catalog.i18nc("@info:status", "Connected over the network.")) + elif self._authentication_state == AuthState.AuthenticationRequested: + self.setConnectionText(i18n_catalog.i18nc("@info:status", + "Connected over the network. Please approve the access request on the printer.")) + elif self._authentication_state == AuthState.AuthenticationDenied: + self.setConnectionText(i18n_catalog.i18nc("@info:status", "Connected over the network. No access to control the printer.")) + def _setupMessages(self): self._authentication_requested_message = Message(i18n_catalog.i18nc("@info:status", @@ -175,15 +181,18 @@ class LegacyUM3OutputDevice(NetworkedPrinterOutputDevice): # Not authenticated, so unable to send job. return - # Notify the UI that a switch to the print monitor should happen - Application.getInstance().getController().setActiveStage("MonitorStage") self.writeStarted.emit(self) - self._gcode = getattr(Application.getInstance().getController().getScene(), "gcode_list", []) - if not self._gcode: + gcode_dict = getattr(Application.getInstance().getController().getScene(), "gcode_dict", []) + active_build_plate_id = Application.getInstance().getBuildPlateModel().activeBuildPlate + gcode_list = gcode_dict[active_build_plate_id] + + if not gcode_list: # Unable to find g-code. Nothing to send return + self._gcode = gcode_list + errors = self._checkForErrors() if errors: text = i18n_catalog.i18nc("@label", "Unable to start a new print job.") @@ -229,6 +238,9 @@ class LegacyUM3OutputDevice(NetworkedPrinterOutputDevice): # No warnings or errors, so we're good to go. self._startPrint() + # Notify the UI that a switch to the print monitor should happen + Application.getInstance().getController().setActiveStage("MonitorStage") + def _startPrint(self): Logger.log("i", "Sending print job to printer.") if self._sending_gcode: diff --git a/plugins/UM3NetworkPrinting/PrintWindow.qml b/plugins/UM3NetworkPrinting/PrintWindow.qml index 13d087f930..d84b0f30ec 100644 --- a/plugins/UM3NetworkPrinting/PrintWindow.qml +++ b/plugins/UM3NetworkPrinting/PrintWindow.qml @@ -31,6 +31,7 @@ UM.Dialog property var printersModel: ListModel{} function resetPrintersModel() { + printersModel.clear() printersModel.append({ name: "Automatic", key: ""}) for (var index in OutputDevice.printers) diff --git a/plugins/UM3NetworkPrinting/UM3InfoComponents.qml b/plugins/UM3NetworkPrinting/UM3InfoComponents.qml index 939c6bcb39..18b481a6ed 100644 --- a/plugins/UM3NetworkPrinting/UM3InfoComponents.qml +++ b/plugins/UM3NetworkPrinting/UM3InfoComponents.qml @@ -115,24 +115,8 @@ Item { tooltip: catalog.i18nc("@info:tooltip", "Load the configuration of the printer into Cura") text: catalog.i18nc("@action:button", "Activate Configuration") - visible: printerConnected && !isClusterPrinter() + visible: false // printerConnected && !isClusterPrinter() onClicked: manager.loadConfigurationFromPrinter() - - function isClusterPrinter() { - return false - //TODO: Hardcoded this for the moment now. These info components might also need to move. - /*if(Cura.MachineManager.printerOutputDevices.length == 0) - { - return false; - } - var clusterSize = Cura.MachineManager.printerOutputDevices[0].clusterSize; - // This is not a cluster printer or the cluster it is just one printer - if(clusterSize == undefined || clusterSize == 1) - { - return false; - } - return true;*/ - } } } diff --git a/plugins/USBPrinting/USBPrinterOutputDevice.py b/plugins/USBPrinting/USBPrinterOutputDevice.py index 6c03450a88..d372b54c38 100644 --- a/plugins/USBPrinting/USBPrinterOutputDevice.py +++ b/plugins/USBPrinting/USBPrinterOutputDevice.py @@ -80,6 +80,8 @@ class USBPrinterOutputDevice(PrinterOutputDevice): self._firmware_progress = 0 self._firmware_update_state = FirmwareUpdateState.idle + self.setConnectionText(catalog.i18nc("@info:status", "Connected via USB")) + # Queue for commands that need to be send. Used when command is sent when a print is active. self._command_queue = Queue() diff --git a/plugins/XmlMaterialProfile/XmlMaterialProfile.py b/plugins/XmlMaterialProfile/XmlMaterialProfile.py index 125fe1e344..8767377db0 100644 --- a/plugins/XmlMaterialProfile/XmlMaterialProfile.py +++ b/plugins/XmlMaterialProfile/XmlMaterialProfile.py @@ -576,6 +576,42 @@ class XmlMaterialProfile(InstanceContainer): if is_new_material: containers_to_add.append(new_material) + # Find the buildplates compatibility + buildplates = machine.iterfind("./um:buildplate", self.__namespaces) + buildplate_map = {} + buildplate_map["buildplate_compatible"] = {} + buildplate_map["buildplate_recommended"] = {} + for buildplate in buildplates: + buildplate_id = buildplate.get("id") + if buildplate_id is None: + continue + + variant_containers = ContainerRegistry.getInstance().findInstanceContainersMetadata( + id = buildplate_id) + if not variant_containers: + # It is not really properly defined what "ID" is so also search for variants by name. + variant_containers = ContainerRegistry.getInstance().findInstanceContainersMetadata( + definition = machine_id, name = buildplate_id) + + if not variant_containers: + continue + + buildplate_compatibility = machine_compatibility + buildplate_recommended = machine_compatibility + settings = buildplate.iterfind("./um:setting", self.__namespaces) + for entry in settings: + key = entry.get("key") + if key in self.__unmapped_settings: + if key == "hardware compatible": + buildplate_compatibility = self._parseCompatibleValue(entry.text) + elif key == "hardware recommended": + buildplate_recommended = self._parseCompatibleValue(entry.text) + else: + Logger.log("d", "Unsupported material setting %s", key) + + buildplate_map["buildplate_compatible"][buildplate_id] = buildplate_compatibility + buildplate_map["buildplate_recommended"][buildplate_id] = buildplate_recommended + hotends = machine.iterfind("./um:hotend", self.__namespaces) for hotend in hotends: hotend_id = hotend.get("id") @@ -605,8 +641,7 @@ class XmlMaterialProfile(InstanceContainer): new_hotend_id = self.getId() + "_" + machine_id + "_" + hotend_id.replace(" ", "_") - # Same as machine compatibility, keep the derived material containers consistent with the parent - # material + # Same as machine compatibility, keep the derived material containers consistent with the parent material if ContainerRegistry.getInstance().isLoaded(new_hotend_id): new_hotend_material = ContainerRegistry.getInstance().findContainers(id = new_hotend_id)[0] is_new_material = False @@ -623,6 +658,9 @@ class XmlMaterialProfile(InstanceContainer): new_hotend_material.getMetaData()["compatible"] = hotend_compatibility new_hotend_material.getMetaData()["machine_manufacturer"] = machine_manufacturer new_hotend_material.getMetaData()["definition"] = machine_id + if buildplate_map["buildplate_compatible"]: + new_hotend_material.getMetaData()["buildplate_compatible"] = buildplate_map["buildplate_compatible"] + new_hotend_material.getMetaData()["buildplate_recommended"] = buildplate_map["buildplate_recommended"] cached_hotend_setting_properties = cached_machine_setting_properties.copy() cached_hotend_setting_properties.update(hotend_setting_values) @@ -763,6 +801,34 @@ class XmlMaterialProfile(InstanceContainer): if len(found_materials) == 0: #This is a new material. result_metadata.append(new_material_metadata) + buildplates = machine.iterfind("./um:buildplate", cls.__namespaces) + buildplate_map = {} + buildplate_map["buildplate_compatible"] = {} + buildplate_map["buildplate_recommended"] = {} + for buildplate in buildplates: + buildplate_id = buildplate.get("id") + if buildplate_id is None: + continue + + variant_containers = ContainerRegistry.getInstance().findInstanceContainersMetadata(id = buildplate_id) + if not variant_containers: + # It is not really properly defined what "ID" is so also search for variants by name. + variant_containers = ContainerRegistry.getInstance().findInstanceContainersMetadata(definition = machine_id, name = buildplate_id) + + if not variant_containers: + continue + + settings = buildplate.iterfind("./um:setting", cls.__namespaces) + for entry in settings: + key = entry.get("key") + if key == "hardware compatible": + buildplate_compatibility = cls._parseCompatibleValue(entry.text) + elif key == "hardware recommended": + buildplate_recommended = cls._parseCompatibleValue(entry.text) + + buildplate_map["buildplate_compatible"][buildplate_id] = buildplate_map["buildplate_compatible"] + buildplate_map["buildplate_recommended"][buildplate_id] = buildplate_map["buildplate_recommended"] + for hotend in machine.iterfind("./um:hotend", cls.__namespaces): hotend_id = hotend.get("id") if hotend_id is None: @@ -781,8 +847,7 @@ class XmlMaterialProfile(InstanceContainer): new_hotend_id = container_id + "_" + machine_id + "_" + hotend_id.replace(" ", "_") - # Same as machine compatibility, keep the derived material containers consistent with the parent - # material + # Same as machine compatibility, keep the derived material containers consistent with the parent material found_materials = ContainerRegistry.getInstance().findInstanceContainersMetadata(id = new_hotend_id) if found_materials: new_hotend_material_metadata = found_materials[0] @@ -799,6 +864,9 @@ class XmlMaterialProfile(InstanceContainer): new_hotend_material_metadata["machine_manufacturer"] = machine_manufacturer new_hotend_material_metadata["id"] = new_hotend_id new_hotend_material_metadata["definition"] = machine_id + if buildplate_map["buildplate_compatible"]: + new_hotend_material_metadata["buildplate_compatible"] = buildplate_map["buildplate_compatible"] + new_hotend_material_metadata["buildplate_recommended"] = buildplate_map["buildplate_recommended"] if len(found_materials) == 0: result_metadata.append(new_hotend_material_metadata) @@ -874,7 +942,7 @@ class XmlMaterialProfile(InstanceContainer): # Map XML file setting names to internal names __material_settings_setting_map = { "print temperature": "default_material_print_temperature", - "heated bed temperature": "material_bed_temperature", + "heated bed temperature": "default_material_bed_temperature", "standby temperature": "material_standby_temperature", "processing temperature graph": "material_flow_temp_graph", "print cooling": "cool_fan_speed", @@ -884,7 +952,8 @@ class XmlMaterialProfile(InstanceContainer): "surface energy": "material_surface_energy" } __unmapped_settings = [ - "hardware compatible" + "hardware compatible", + "hardware recommended" ] __material_properties_setting_map = { "diameter": "material_diameter" diff --git a/resources/definitions/3dator.def.json b/resources/definitions/3dator.def.json index 2ec7119656..19307bfddd 100644 --- a/resources/definitions/3dator.def.json +++ b/resources/definitions/3dator.def.json @@ -47,9 +47,9 @@ "default_value": 30 }, "machine_start_gcode": { - "default_value": ";Sliced at: {day} {date} {time}\nM104 S{material_print_temperature} ;set temperatures\nM140 S{material_bed_temperature}\nM109 S{material_print_temperature} ;wait for temperatures\nM190 S{material_bed_temperature}\nG21 ;metric values\nG90 ;absolute positioning\nM82 ;set extruder to absolute mode\nM107 ;start with the fan off\nG28 Z0 ;move Z to min endstops\nG28 X0 Y0 ;move X/Y to min endstops\nG29 ;Auto Level\nG1 Z0.6 F{travel_speed} ;move the Nozzle near the Bed\nG92 E0\nG1 Y0 ;zero the extruded length\nG1 X10 E30 F500 ;printing a Line from right to left\nG92 E0 ;zero the extruded length again\nG1 Z2\nG1 F{travel_speed}\nM117 Printing...;Put printing message on LCD screen\nM150 R255 U255 B255 P4 ;Change LED Color to white" }, + "default_value": ";Sliced at: {day} {date} {time}\nM104 S{material_print_temperature} ;set temperatures\nM140 S{material_bed_temperature}\nM109 S{material_print_temperature} ;wait for temperatures\nM190 S{material_bed_temperature}\nG21 ;metric values\nG90 ;absolute positioning\nM82 ;set extruder to absolute mode\nM107 ;start with the fan off\nG28 Z0 ;move Z to min endstops\nG28 X0 Y0 ;move X/Y to min endstops\nG29 ;Auto Level\nG1 Z0.6 F{speed_travel} ;move the Nozzle near the Bed\nG92 E0\nG1 Y0 ;zero the extruded length\nG1 X10 E30 F500 ;printing a Line from right to left\nG92 E0 ;zero the extruded length again\nG1 Z2\nG1 F{speed_travel}\nM117 Printing...;Put printing message on LCD screen\nM150 R255 U255 B255 P4 ;Change LED Color to white" }, "machine_end_gcode": { - "default_value": "M104 S0 ;extruder heater off\nM140 S0 ;heated bed heater off (if you have it)\nG91 ;relative positioning\nG1 E-1 F300 ;retract the filament a bit before lifting the nozzle, to release some of the pressure\nG1 Z+0.5 E-5 X-20 Y-20 F{travel_speed} ;move Z up a bit and retract filament even more\nG28 ;move X/Y to min endstops, so the head is out of the way\nM84 ;steppers off\nG90 ;absolute positioning" + "default_value": "M104 S0 ;extruder heater off\nM140 S0 ;heated bed heater off (if you have it)\nG91 ;relative positioning\nG1 E-1 F300 ;retract the filament a bit before lifting the nozzle, to release some of the pressure\nG1 Z+0.5 E-5 X-20 Y-20 F{speed_travel} ;move Z up a bit and retract filament even more\nG28 ;move X/Y to min endstops, so the head is out of the way\nM84 ;steppers off\nG90 ;absolute positioning" } } } diff --git a/resources/definitions/alya3dp.def.json b/resources/definitions/alya3dp.def.json index 2fda102249..6bf5d89a95 100644 --- a/resources/definitions/alya3dp.def.json +++ b/resources/definitions/alya3dp.def.json @@ -40,10 +40,10 @@ "default_value": "RepRap" }, "machine_start_gcode": { - "default_value": ";Sliced at: {day} {date} {time}\n;Basic settings: Layer height: {layer_height} Walls: {wall_thickness} Fill: {fill_density}\n;Print time: {print_time}\n;Filament used: {filament_amount}m {filament_weight}g\n;Filament cost: {filament_cost}\n;M190 S{print_bed_temperature} ;Uncomment to add your own bed temperature line\n;M109 S{print_temperature} ;Uncomment to add your own temperature line\nG21 ;metric values\nG90 ;absolute positioning\nM82 ;set extruder to absolute mode\nM107 ;start with the fan off\nG28 X0 Y0 ;move X/Y to min endstops\nG28 Z0 ;move Z to max endstops\nG1 Z115.0 F{travel_speed} ;move th e platform up 20mm\nG28 Z0 ;move Z to max endstop\nG1 Z15.0 F{travel_speed} ;move the platform down 15mm\nG92 E0 ;zero the extruded length\nG1 F200 E3 ;extrude 3mm of feed stock\nG92 E0 ;zero the extruded length again\nG1 F{travel_speed}\nM301 H1 P26.38 I2.57 D67.78\n;Put printing message on LCD screen\nM117 Printing..." + "default_value": ";Sliced at: {day} {date} {time}\n;Basic settings: Layer height: {layer_height} Walls: {wall_thickness} Fill: {fill_density}\n;Print time: {print_time}\n;Filament used: {filament_amount}m {filament_weight}g\n;Filament cost: {filament_cost}\n;M190 S{print_bed_temperature} ;Uncomment to add your own bed temperature line\n;M109 S{print_temperature} ;Uncomment to add your own temperature line\nG21 ;metric values\nG90 ;absolute positioning\nM82 ;set extruder to absolute mode\nM107 ;start with the fan off\nG28 X0 Y0 ;move X/Y to min endstops\nG28 Z0 ;move Z to max endstops\nG1 Z115.0 F{speed_travel} ;move th e platform up 20mm\nG28 Z0 ;move Z to max endstop\nG1 Z15.0 F{speed_travel} ;move the platform down 15mm\nG92 E0 ;zero the extruded length\nG1 F200 E3 ;extrude 3mm of feed stock\nG92 E0 ;zero the extruded length again\nG1 F{speed_travel}\nM301 H1 P26.38 I2.57 D67.78\n;Put printing message on LCD screen\nM117 Printing..." }, "machine_end_gcode": { - "default_value": ";End GCode\nM104 S0 ;extruder heater off\nM140 S0 ;heated bed heater off (if you have it)\nG91 ;relative positioning\nG1 E-1 F300 ;retract the filament a bit before lifting the nozzle, to release some of the pressure\nG1 Z+0.5 E-5 X-20 Y-20 F{travel_speed} ;move Z up a bit and retract filament even more\nG28 X0 Y0 ;move X/Y to min endstops, so the head is out of the way\nG28 Z0\nM84 ;steppers off\nG90 ;absolute positioning\n;{profile_string}" + "default_value": ";End GCode\nM104 S0 ;extruder heater off\nM140 S0 ;heated bed heater off (if you have it)\nG91 ;relative positioning\nG1 E-1 F300 ;retract the filament a bit before lifting the nozzle, to release some of the pressure\nG1 Z+0.5 E-5 X-20 Y-20 F{speed_travel} ;move Z up a bit and retract filament even more\nG28 X0 Y0 ;move X/Y to min endstops, so the head is out of the way\nG28 Z0\nM84 ;steppers off\nG90 ;absolute positioning\n;{profile_string}" } } } \ No newline at end of file diff --git a/resources/definitions/anycubic_i3_mega.def.json b/resources/definitions/anycubic_i3_mega.def.json index a0bd0efb7c..cba868900c 100644 --- a/resources/definitions/anycubic_i3_mega.def.json +++ b/resources/definitions/anycubic_i3_mega.def.json @@ -1,55 +1,69 @@ { - "version":2, - "name":"Anycubic i3 Mega", - "inherits":"fdmprinter", - "metadata":{ - "visible":true, - "author":"TheTobby", - "manufacturer":"Anycubic", - "file_formats":"text/x-gcode", - "icon":"icon_ultimaker2", - "platform":"anycubic_i3_mega_platform.stl", - "has_materials": false, - "has_machine_quality": true, - "preferred_quality": "*normal*" + "version": 2, + "name": "Anycubic i3 Mega", + "inherits": "fdmprinter", + "metadata": + { + "visible": true, + "author": "TheTobby", + "manufacturer": "Anycubic", + "file_formats": "text/x-gcode", + "icon": "icon_ultimaker2", + "platform": "anycubic_i3_mega_platform.stl", + "has_materials": false, + "has_machine_quality": true, + "preferred_quality": "*normal*" }, - - "overrides":{ - "machine_name":{ - "default_value":"Anycubic i3 Mega" + + "overrides": + { + "machine_name": + { + "default_value": "Anycubic i3 Mega" }, - "machine_heated_bed":{ - "default_value":true + "machine_heated_bed": + { + "default_value": true }, - "machine_width":{ - "default_value":210 + "machine_width": + { + "default_value": 210 }, - "machine_height":{ - "default_value":205 + "machine_height": + { + "default_value": 205 }, - "machine_depth":{ - "default_value":210 + "machine_depth": + { + "default_value": 210 }, - "machine_center_is_zero":{ - "default_value":false + "machine_center_is_zero": + { + "default_value": false }, - "machine_nozzle_size":{ - "default_value":0.4 + "machine_nozzle_size": + { + "default_value": 0.4 }, - "material_diameter":{ - "default_value":1.75 + "material_diameter": + { + "default_value": 1.75 }, - "gantry_height":{ - "default_value":0 + "gantry_height": + { + "default_value": 0 }, - "machine_gcode_flavor":{ - "default_value":"RepRap (Marlin/Sprinter)" + "machine_gcode_flavor": + { + "default_value": "RepRap (Marlin/Sprinter)" }, - "machine_start_gcode":{ - "default_value":"G21 ;metric values\nG90 ;absolute positioning\nM82 ;set extruder to absolute mode\nM107 ;start with the fan off\nG28 X0 Y0 ;move X/Y to min endstops\nG28 Z0 ;move Z to min endstops\nG1 Z15.0 F{travel_speed} ;move the platform down 15mm\nG92 E0 ;zero the extruded length\nG1 F200 E3 ;extrude 3mm of feed stock\nG92 E0 ;zero the extruded length again\nG1 F{travel_speed}\nM117 Printing...\nG5" + "machine_start_gcode": + { + "default_value": "G21 ;metric values\nG90 ;absolute positioning\nM82 ;set extruder to absolute mode\nM107 ;start with the fan off\nG28 X0 Y0 ;move X/Y to min endstops\nG28 Z0 ;move Z to min endstops\nG1 Z15.0 F{speed_travel} ;move the platform down 15mm\nG92 E0 ;zero the extruded length\nG1 F200 E3 ;extrude 3mm of feed stock\nG92 E0 ;zero the extruded length again\nG1 F{speed_travel}\nM117 Printing...\nG5" }, - "machine_end_gcode":{ - "default_value":"M104 S0 ; turn off extruder\nM140 S0 ; turn off bed\nM84 ; disable motors\nM107\nG91 ;relative positioning\nG1 E-1 F300 ;retract the filament a bit before lifting the nozzle\nto release some of the pressure\nG1 Z+0.5 E-5 ;X-20 Y-20 F{travel_speed} ;move Z up a bit and retract filament even more\nG28 X0 ;Y0 ;move X/Y to min endstops\nso the head is out of the way\nG1 Y180 F2000\nM84 ;steppers off\nG90\nM300 P300 S4000" + "machine_end_gcode": + { + "default_value": "M104 S0 ; turn off extruder\nM140 S0 ; turn off bed\nM84 ; disable motors\nM107\nG91 ;relative positioning\nG1 E-1 F300 ;retract the filament a bit before lifting the nozzle\nto release some of the pressure\nG1 Z+0.5 E-5 ;X-20 Y-20 F{speed_travel} ;move Z up a bit and retract filament even more\nG28 X0 ;Y0 ;move X/Y to min endstops\nso the head is out of the way\nG1 Y180 F2000\nM84 ;steppers off\nG90\nM300 P300 S4000" } } } diff --git a/resources/definitions/easyarts_ares.def.json b/resources/definitions/easyarts_ares.def.json index 982496de4c..689ac63625 100644 --- a/resources/definitions/easyarts_ares.def.json +++ b/resources/definitions/easyarts_ares.def.json @@ -10,7 +10,7 @@ }, "overrides": { "machine_start_gcode": { - "default_value": "; -- START GCODE --\nG21 ;metric values\nG90 ;absolute positioning\nM82 ;set extruder to absolute mode\nM107 ;start with the fan off\nG28 \nG29 Z0.12 ;Auto-bedleveling with Z offset \nG92 E0 ;zero the extruded length \nG1 F2000 E3 ;extrude 3mm of feed stock\nG92 E0 ;zero the extruded length again\nG1 F{travel_speed}\nM117 Printing...\n; -- end of START GCODE --" + "default_value": "; -- START GCODE --\nG21 ;metric values\nG90 ;absolute positioning\nM82 ;set extruder to absolute mode\nM107 ;start with the fan off\nG28 \nG29 Z0.12 ;Auto-bedleveling with Z offset \nG92 E0 ;zero the extruded length \nG1 F2000 E3 ;extrude 3mm of feed stock\nG92 E0 ;zero the extruded length again\nG1 F{speed_travel}\nM117 Printing...\n; -- end of START GCODE --" }, "machine_end_gcode": { "default_value": "; -- START GCODE --\nG28 ; Home all axes\nM104 S0 ;extruder heater off\n;M140 S0 ;heated bed heater off (if you have it)\nG91 ;relative positioning\nG1 E-1 F300 ;retract the filament a bit before lifting the nozzle, to release some of the pressure\n;M84 ;steppers off\nG90 ;absolute positioning\n; -- end of START GCODE --" diff --git a/resources/definitions/fdmextruder.def.json b/resources/definitions/fdmextruder.def.json index 3a59e7df1e..2b314cd6a5 100644 --- a/resources/definitions/fdmextruder.def.json +++ b/resources/definitions/fdmextruder.def.json @@ -181,27 +181,6 @@ } } }, - "material": { - "label": "Material", - "icon": "category_material", - "description": "Material", - "type": "category", - "children": { - "material_diameter": { - "label": "Diameter", - "description": "Adjusts the diameter of the filament used. Match this value with the diameter of the used filament.", - "unit": "mm", - "type": "float", - "default_value": 2.85, - "minimum_value": "0.0001", - "minimum_value_warning": "0.4", - "maximum_value_warning": "3.5", - "enabled": "machine_gcode_flavor != \"UltiGCode\"", - "settable_per_mesh": false, - "settable_per_extruder": true - } - } - }, "platform_adhesion": { "label": "Build Plate Adhesion", diff --git a/resources/definitions/fdmprinter.def.json b/resources/definitions/fdmprinter.def.json index 87b72928ca..c7f80666ff 100644 --- a/resources/definitions/fdmprinter.def.json +++ b/resources/definitions/fdmprinter.def.json @@ -154,6 +154,21 @@ "settable_per_extruder": false, "settable_per_meshgroup": false }, + "machine_buildplate_type": + { + "label": "Build Plate Material", + "description": "The material of the build plate installed on the printer.", + "default_value": "glass", + "type": "enum", + "options": + { + "glass": "Glass", + "aluminium": "Aluminium" + }, + "settable_per_mesh": false, + "settable_per_extruder": false, + "settable_per_meshgroup": false + }, "machine_height": { "label": "Machine Height", @@ -1981,14 +1996,30 @@ "settable_per_mesh": false, "settable_per_extruder": true }, + "default_material_bed_temperature": + { + "label": "Default Build Plate Temperature", + "description": "The default temperature used for the heated build plate. This should be the \"base\" temperature of a build plate. All other print temperatures should use offsets based on this value", + "unit": "°C", + "type": "float", + "resolve": "max(extruderValues('default_material_bed_temperature'))", + "default_value": 60, + "minimum_value": "-273.15", + "minimum_value_warning": "0", + "maximum_value_warning": "130", + "enabled": "machine_heated_bed and machine_gcode_flavor != \"UltiGCode\"", + "settable_per_mesh": false, + "settable_per_extruder": false, + "settable_per_meshgroup": false + }, "material_bed_temperature": { "label": "Build Plate Temperature", "description": "The temperature used for the heated build plate. If this is 0, the bed temperature will not be adjusted.", "unit": "°C", "type": "float", - "resolve": "max(extruderValues('material_bed_temperature'))", "default_value": 60, + "value": "default_material_bed_temperature", "minimum_value": "-273.15", "minimum_value_warning": "0", "maximum_value_warning": "130", diff --git a/resources/definitions/tevo_blackwidow.def.json b/resources/definitions/tevo_blackwidow.def.json index 9d7166f4a2..04cadfb160 100644 --- a/resources/definitions/tevo_blackwidow.def.json +++ b/resources/definitions/tevo_blackwidow.def.json @@ -8,46 +8,59 @@ "manufacturer": "Tevo", "file_formats": "text/x-gcode", "icon": "icon_ultimaker2", - "has_materials": false, + "has_materials": false, "has_machine_quality": true, - "platform": "prusai3_platform.stl", - "preferred_quality": "*normal*" + "platform": "tevo_blackwidow.stl", + "preferred_quality": "*normal*" }, - "overrides": { - "machine_name": { + "overrides": + { + "machine_name": + { "default_value": "Tevo Black Widow" }, - "machine_heated_bed": { + "machine_heated_bed": + { "default_value": true }, - "machine_width": { + "machine_width": + { "default_value": 350 }, - "machine_height": { + "machine_height": + { "default_value": 250 }, - "machine_depth": { + "machine_depth": + { "default_value": 250 }, - "machine_center_is_zero": { + "machine_center_is_zero": + { "default_value": false }, - "machine_nozzle_size": { + "machine_nozzle_size": + { "default_value": 0.4 }, - "material_diameter": { - "default_value": 1.75 + "material_diameter": + { + "default_value": 1.75 }, - "gantry_height": { + "gantry_height": + { "default_value": 0 }, - "machine_gcode_flavor": { + "machine_gcode_flavor": + { "default_value": "RepRap (Marlin/Sprinter)" }, - "machine_start_gcode": { + "machine_start_gcode": + { "default_value": "M280 P0 S160 ; release BLTouch alarm (OK to send for Non BLTouch)\nM420 Z2 ; set fade leveling at 2mm for BLTouch (OK to send for Non BLTouch)\nG28 ; home all\nG29 ; probe bed\nG92 E0 ;zero the extruded length\nG1 X0.0 Y50.0 Z10.0 F3600\n; perform wipe and prime\nG1 Z0.0 F1000\nG1 Z0.2 Y70.0 E9.0 F1000.0 ; prime\nG1 Y100.0 E12.5 F1000.0 ; prime\nG92 E0 ; zero extruder again\nM117 Printing..." }, - "machine_end_gcode": { + "machine_end_gcode": + { "default_value": "G92 E0 ; zero the extruded length again\nG1 E-1.5 F500 ; retract the filament to release some of the pressure\nM104 S0 ; turn off extruder\nM140 S0 ; turn off bed\nG28 X0 ; home X axis\nG1 Y245 ; move Y axis to end position\nM84 ; disable motors\nM107 ; turn off fan" } } diff --git a/resources/meshes/tevo_blackwidow.stl b/resources/meshes/tevo_blackwidow.stl index ef45dd1621..36b52381d5 100644 Binary files a/resources/meshes/tevo_blackwidow.stl and b/resources/meshes/tevo_blackwidow.stl differ diff --git a/resources/qml/Cura.qml b/resources/qml/Cura.qml index 86f46ef7a1..6f649a7273 100644 --- a/resources/qml/Cura.qml +++ b/resources/qml/Cura.qml @@ -204,6 +204,7 @@ UM.MainWindow onObjectRemoved: settingsMenu.removeItem(object) } + BuildplateMenu { title: catalog.i18nc("@title:menu", "&Build plate"); visible: Cura.MachineManager.hasVariantBuildplates } NozzleMenu { title: Cura.MachineManager.activeDefinitionVariantsName; visible: machineExtruderCount.properties.value <= 1 && Cura.MachineManager.hasVariants } MaterialMenu { title: catalog.i18nc("@title:menu", "&Material"); visible: machineExtruderCount.properties.value <= 1 && Cura.MachineManager.hasMaterials } ProfileMenu { title: catalog.i18nc("@title:menu", "&Profile"); visible: machineExtruderCount.properties.value <= 1 } diff --git a/resources/qml/Menus/BuildplateMenu.qml b/resources/qml/Menus/BuildplateMenu.qml new file mode 100644 index 0000000000..908cccf1c8 --- /dev/null +++ b/resources/qml/Menus/BuildplateMenu.qml @@ -0,0 +1,87 @@ +// Copyright (c) 2018 Ultimaker B.V. +// Cura is released under the terms of the LGPLv3 or higher. + +import QtQuick 2.2 +import QtQuick.Controls 1.1 + +import UM 1.2 as UM +import Cura 1.0 as Cura + +Menu +{ + id: menu + title: "Build plate" + + property bool printerConnected: Cura.MachineManager.printerOutputDevices.length != 0 + property bool isClusterPrinter: + { + if(Cura.MachineManager.printerOutputDevices.length == 0) + { + return false; + } + var clusterSize = Cura.MachineManager.printerOutputDevices[0].clusterSize; + // This is not a cluster printer or the cluster it is just one printer + if(clusterSize == undefined || clusterSize == 1) + { + return false; + } + return true; + } + + MenuItem + { + id: automaticBuildplate + text: + { + if(printerConnected && Cura.MachineManager.printerOutputDevices[0].buildplateId != "" && !isClusterPrinter) + { + var buildplateName = Cura.MachineManager.printerOutputDevices[0].buildplateId + return catalog.i18nc("@title:menuitem %1 is the buildplate currently loaded in the printer", "Automatic: %1").arg(buildplateName) + } + return "" + } + visible: printerConnected && Cura.MachineManager.printerOutputDevices[0].buildplateId != "" && !isClusterPrinter + onTriggered: + { + var buildplateId = Cura.MachineManager.printerOutputDevices[0].buildplateId + var itemIndex = buildplateInstantiator.model.find("name", buildplateId) + if(itemIndex > -1) + { + Cura.MachineManager.setActiveVariantBuildplate(buildplateInstantiator.model.getItem(itemIndex).id) + } + } + } + + MenuSeparator + { + visible: automaticBuildplate.visible + } + + Instantiator + { + id: buildplateInstantiator + model: UM.InstanceContainersModel + { + filter: + { + "type": "variant", + "hardware_type": "buildplate", + "definition": Cura.MachineManager.activeDefinitionId //Only show variants of this machine + } + } + MenuItem { + text: model.name + checkable: true + checked: model.id == Cura.MachineManager.globalVariantId + exclusiveGroup: group + onTriggered: + { + Cura.MachineManager.setActiveVariantBuildplate(model.id); + } + } + onObjectAdded: menu.insertItem(index, object) + onObjectRemoved: menu.removeItem(object) + } + + ExclusiveGroup { id: group } +} diff --git a/resources/qml/Menus/NozzleMenu.qml b/resources/qml/Menus/NozzleMenu.qml index f70e639872..cc3ea66b07 100644 --- a/resources/qml/Menus/NozzleMenu.qml +++ b/resources/qml/Menus/NozzleMenu.qml @@ -68,8 +68,17 @@ Menu { filter: { - "type": "variant", - "definition": Cura.MachineManager.activeQualityDefinitionId //Only show variants of this machine + var filter_dict = + { + "type": "variant", + "definition": Cura.MachineManager.activeQualityDefinitionId //Only show variants of this machine + } + if (Cura.MachineManager.hasVariantBuildplates) + { + filter_dict["hardware_type"] = "nozzle" + } + + return filter_dict } } MenuItem { diff --git a/resources/qml/Preferences/MachinesPage.qml b/resources/qml/Preferences/MachinesPage.qml index a8e25155e1..889dfa8d5b 100644 --- a/resources/qml/Preferences/MachinesPage.qml +++ b/resources/qml/Preferences/MachinesPage.qml @@ -143,7 +143,7 @@ UM.ManagementPage property bool printerConnected: Cura.MachineManager.printerOutputDevices.length != 0 property var connectedPrinter: printerConnected ? Cura.MachineManager.printerOutputDevices[0] : null property bool printerAcceptsCommands: printerConnected && Cura.MachineManager.printerOutputDevices[0].acceptsCommands - + property var printJob: connectedPrinter != null ? connectedPrinter.activePrintJob: null Label { text: catalog.i18nc("@label", "Printer type:") @@ -178,7 +178,12 @@ UM.ManagementPage return ""; } - switch(Cura.MachineManager.printerOutputDevices[0].jobState) + if (machineInfo.printJob == null) + { + return catalog.i18nc("@label:MonitorStatus", "Waiting for a printjob"); + } + + switch(machineInfo.printJob.state) { case "printing": return catalog.i18nc("@label:MonitorStatus", "Printing..."); @@ -194,10 +199,9 @@ UM.ManagementPage return catalog.i18nc("@label:MonitorStatus", "In maintenance. Please check the printer"); case "abort": // note sure if this jobState actually occurs in the wild return catalog.i18nc("@label:MonitorStatus", "Aborting print..."); - case "ready": // ready to print or getting ready - case "": // ready to print or getting ready - return catalog.i18nc("@label:MonitorStatus", "Waiting for a printjob"); + } + return "" } visible: base.currentItem && base.currentItem.id == Cura.MachineManager.activeMachineId && machineInfo.printerAcceptsCommands wrapMode: Text.WordWrap diff --git a/resources/qml/Preferences/MaterialView.qml b/resources/qml/Preferences/MaterialView.qml index c3f36f5125..b2307fe4f6 100644 --- a/resources/qml/Preferences/MaterialView.qml +++ b/resources/qml/Preferences/MaterialView.qml @@ -72,7 +72,7 @@ TabView width: scrollView.columnWidth; text: properties.name; readOnly: !base.editingEnabled; - onEditingFinished: base.setName(properties.name, text) + onEditingFinished: base.updateMaterialDisplayName(properties.name, text) } Label { width: scrollView.columnWidth; height: parent.rowHeight; verticalAlignment: Qt.AlignVCenter; text: catalog.i18nc("@label", "Brand") } @@ -82,11 +82,7 @@ TabView width: scrollView.columnWidth; text: properties.supplier; readOnly: !base.editingEnabled; - onEditingFinished: - { - base.setMetaDataEntry("brand", properties.supplier, text); - pane.objectList.currentIndex = pane.getIndexById(base.containerId); - } + onEditingFinished: base.updateMaterialSupplier(properties.supplier, text) } Label { width: scrollView.columnWidth; height: parent.rowHeight; verticalAlignment: Qt.AlignVCenter; text: catalog.i18nc("@label", "Material Type") } @@ -95,15 +91,10 @@ TabView width: scrollView.columnWidth; text: properties.material_type; readOnly: !base.editingEnabled; - onEditingFinished: - { - base.setMetaDataEntry("material", properties.material_type, text); - pane.objectList.currentIndex = pane.getIndexById(base.containerId) - } + onEditingFinished: base.updateMaterialType(properties.material_type, text) } Label { width: scrollView.columnWidth; height: parent.rowHeight; verticalAlignment: Qt.AlignVCenter; text: catalog.i18nc("@label", "Color") } - Row { width: scrollView.columnWidth height: parent.rowHeight @@ -128,13 +119,6 @@ TabView } } - // make sure the color stays connected after changing the color - Binding { - target: colorSelector - property: "color" - value: properties.color_code - } - // pretty color name text field ReadOnlyTextField { id: colorLabel; @@ -453,14 +437,28 @@ TabView return 0; } - function setName(old_value, new_value) - { - if(old_value != new_value) - { - Cura.ContainerManager.setContainerName(base.containerId, new_value); - // update material name label. not so pretty, but it works - materialProperties.name = new_value; - pane.objectList.currentIndex = pane.getIndexById(base.containerId) + // update the display name of the material + function updateMaterialDisplayName (old_name, new_name) { + + // don't change when new name is the same + if (old_name == new_name) { + return } + + // update the values + Cura.ContainerManager.setContainerName(base.containerId, new_name) + materialProperties.name = new_name + } + + // update the type of the material + function updateMaterialType (old_type, new_type) { + base.setMetaDataEntry("material", old_type, new_type) + materialProperties.material_type = new_type + } + + // update the supplier of the material + function updateMaterialSupplier (old_supplier, new_supplier) { + base.setMetaDataEntry("brand", old_supplier, new_supplier) + materialProperties.supplier = new_supplier } } diff --git a/resources/qml/Preferences/MaterialsPage.qml b/resources/qml/Preferences/MaterialsPage.qml index 6b041b895a..228f9c8ea2 100644 --- a/resources/qml/Preferences/MaterialsPage.qml +++ b/resources/qml/Preferences/MaterialsPage.qml @@ -153,15 +153,6 @@ UM.ManagementPage forceActiveFocus() Cura.ContainerManager.createMaterial() } - - Connections - { - target: base.objectList.model - onItemsChanged: - { - base.objectList.currentIndex = base.getIndexById(Cura.MachineManager.activeMaterialId); - } - } }, // Duplicate button diff --git a/resources/qml/Preferences/ProfilesPage.qml b/resources/qml/Preferences/ProfilesPage.qml index e3ba9b23a4..5e040cdba2 100644 --- a/resources/qml/Preferences/ProfilesPage.qml +++ b/resources/qml/Preferences/ProfilesPage.qml @@ -213,8 +213,8 @@ UM.ManagementPage ProfileTab { title: catalog.i18nc("@title:tab", "Global Settings"); - quality: base.currentItem != null ? base.currentItem.id : ""; - material: Cura.MachineManager.allActiveMaterialIds[Cura.MachineManager.activeMachineId] + quality: Cura.MachineManager.activeMachine.qualityChanges.id + material: Cura.MachineManager.activeMachine.material.id } Repeater diff --git a/resources/qml/SaveButton.qml b/resources/qml/SaveButton.qml index dc24cc4700..bf44a29cf5 100644 --- a/resources/qml/SaveButton.qml +++ b/resources/qml/SaveButton.qml @@ -170,8 +170,8 @@ Item { tooltip: [1, 5].indexOf(base.backendState) != -1 ? catalog.i18nc("@info:tooltip","Slice current printjob") : catalog.i18nc("@info:tooltip","Cancel slicing process") // 1 = not started, 2 = Processing - enabled: base.backendState != "undefined" && (base.backendState == 1 || base.backendState == 2) && base.activity == true - visible: base.backendState != "undefined" && !autoSlice && (base.backendState == 1 || base.backendState == 2) && base.activity == true + enabled: base.backendState != "undefined" && ([1, 2].indexOf(base.backendState) != -1) && base.activity + visible: base.backendState != "undefined" && !autoSlice && ([1, 2, 4].indexOf(base.backendState) != -1) && base.activity property bool autoSlice height: UM.Theme.getSize("save_button_save_to_button").height @@ -179,8 +179,8 @@ Item { anchors.right: parent.right anchors.rightMargin: UM.Theme.getSize("sidebar_margin").width - // 1 = not started, 5 = disabled - text: [1, 5].indexOf(base.backendState) != -1 ? catalog.i18nc("@label:Printjob", "Prepare") : catalog.i18nc("@label:Printjob", "Cancel") + // 1 = not started, 4 = error, 5 = disabled + text: [1, 4, 5].indexOf(base.backendState) != -1 ? catalog.i18nc("@label:Printjob", "Prepare") : catalog.i18nc("@label:Printjob", "Cancel") onClicked: { sliceOrStopSlicing(); diff --git a/resources/qml/Settings/SettingTextField.qml b/resources/qml/Settings/SettingTextField.qml index 176a4e23e6..489f23c6d1 100644 --- a/resources/qml/Settings/SettingTextField.qml +++ b/resources/qml/Settings/SettingTextField.qml @@ -135,14 +135,6 @@ SettingItem } } - onEditingFinished: - { - if (textHasChanged) - { - propertyProvider.setPropertyValue("value", text) - } - } - onActiveFocusChanged: { if(activeFocus) diff --git a/resources/qml/Settings/SettingView.qml b/resources/qml/Settings/SettingView.qml index 08f4ec5a68..21e79ee967 100644 --- a/resources/qml/Settings/SettingView.qml +++ b/resources/qml/Settings/SettingView.qml @@ -63,11 +63,9 @@ Item menu: ProfileMenu { } function generateActiveQualityText () { - var result = catalog.i18nc("@", "No Profile Available") // default text - - if (Cura.MachineManager.isActiveQualitySupported ) { - result = Cura.MachineManager.activeQualityName + var result = Cura.MachineManager.activeQualityName; + if (Cura.MachineManager.isActiveQualitySupported) { if (Cura.MachineManager.activeQualityLayerHeight > 0) { result += " " result += " - " diff --git a/resources/qml/Sidebar.qml b/resources/qml/Sidebar.qml index 6a860fe32d..236256266c 100644 --- a/resources/qml/Sidebar.qml +++ b/resources/qml/Sidebar.qml @@ -96,7 +96,7 @@ Rectangle SidebarHeader { id: header width: parent.width - visible: (machineExtruderCount.properties.value > 1 || Cura.MachineManager.hasMaterials || Cura.MachineManager.hasVariants) && !monitoringPrint + visible: !hideSettings && (machineExtruderCount.properties.value > 1 || Cura.MachineManager.hasMaterials || Cura.MachineManager.hasVariants) && !monitoringPrint anchors.top: machineSelection.bottom onShowTooltip: base.showTooltip(item, location, text) @@ -128,7 +128,7 @@ Rectangle text: !hideSettings ? catalog.i18nc("@label:listbox", "Print Setup") : catalog.i18nc("@label:listbox", "Print Setup disabled\nG-code files cannot be modified") anchors.left: parent.left anchors.leftMargin: UM.Theme.getSize("sidebar_margin").width - anchors.top: headerSeparator.bottom + anchors.top: hideSettings ? machineSelection.bottom : headerSeparator.bottom anchors.topMargin: UM.Theme.getSize("sidebar_margin").height width: Math.floor(parent.width * 0.45) font: UM.Theme.getFont("large") diff --git a/resources/qml/SidebarHeader.qml b/resources/qml/SidebarHeader.qml index 3e1e85824a..bc45d9ddac 100644 --- a/resources/qml/SidebarHeader.qml +++ b/resources/qml/SidebarHeader.qml @@ -242,7 +242,7 @@ Column Label { id: materialLabel - text: catalog.i18nc("@label","Material"); + text: catalog.i18nc("@label", "Material"); width: Math.floor(parent.width * 0.45 - UM.Theme.getSize("default_margin").width) font: UM.Theme.getFont("default"); color: UM.Theme.getColor("text"); @@ -314,6 +314,62 @@ Column } } + //Buildplate row separator + Rectangle { + id: separator + + anchors.leftMargin: UM.Theme.getSize("sidebar_margin").width + anchors.rightMargin: UM.Theme.getSize("sidebar_margin").width + anchors.horizontalCenter: parent.horizontalCenter + visible: buildplateRow.visible + width: parent.width - UM.Theme.getSize("sidebar_margin").width * 2 + height: visible ? UM.Theme.getSize("sidebar_lining_thin").height / 2 : 0 + color: UM.Theme.getColor("sidebar_lining_thin") + } + + //Buildplate row + Item + { + id: buildplateRow + height: UM.Theme.getSize("sidebar_setup").height + visible: Cura.MachineManager.hasVariantBuildplates && !sidebar.monitoringPrint && !sidebar.hideSettings + + anchors + { + left: parent.left + leftMargin: UM.Theme.getSize("sidebar_margin").width + right: parent.right + rightMargin: UM.Theme.getSize("sidebar_margin").width + } + + Label + { + id: bulidplateLabel + text: catalog.i18nc("@label", "Build plate"); + width: Math.floor(parent.width * 0.45 - UM.Theme.getSize("default_margin").width) + font: UM.Theme.getFont("default"); + color: UM.Theme.getColor("text"); + } + + ToolButton { + id: buildplateSelection + text: Cura.MachineManager.activeVariantBuildplateName + tooltip: Cura.MachineManager.activeVariantBuildplateName + visible: Cura.MachineManager.hasVariantBuildplates + + height: UM.Theme.getSize("setting_control").height + width: Math.floor(parent.width * 0.7 + UM.Theme.getSize("sidebar_margin").width) + anchors.right: parent.right + style: UM.Theme.styles.sidebar_header_button + activeFocusOnPress: true; + + menu: BuildplateMenu {} + + property var valueError: !Cura.MachineManager.variantBuildplateCompatible && !Cura.MachineManager.variantBuildplateUsable + property var valueWarning: Cura.MachineManager.variantBuildplateUsable + } + } + // Material info row Item { diff --git a/resources/themes/cura-dark/theme.json b/resources/themes/cura-dark/theme.json index 9e99945d3d..80a5eec09c 100644 --- a/resources/themes/cura-dark/theme.json +++ b/resources/themes/cura-dark/theme.json @@ -43,6 +43,7 @@ "sidebar_header_text_hover": [255, 255, 255, 255], "sidebar_header_text_inactive": [255, 255, 255, 127], "sidebar_lining": [31, 36, 39, 255], + "sidebar_lining_thin": [255, 255, 255, 30], "button": [39, 44, 48, 255], "button_hover": [39, 44, 48, 255], diff --git a/resources/themes/cura-light/styles.qml b/resources/themes/cura-light/styles.qml index 7532f0dfaf..20f858c238 100755 --- a/resources/themes/cura-light/styles.qml +++ b/resources/themes/cura-light/styles.qml @@ -864,9 +864,10 @@ QtObject { } } label: Label { - text: control.text; - color: Theme.getColor("checkbox_text"); - font: Theme.getFont("default"); + text: control.text + color: Theme.getColor("checkbox_text") + font: Theme.getFont("default") + elide: Text.ElideRight } } } diff --git a/resources/themes/cura-light/theme.json b/resources/themes/cura-light/theme.json index 53bef1e7d9..51c96a5f82 100644 --- a/resources/themes/cura-light/theme.json +++ b/resources/themes/cura-light/theme.json @@ -91,6 +91,7 @@ "sidebar_header_text_active": [255, 255, 255, 255], "sidebar_header_text_hover": [255, 255, 255, 255], "sidebar_lining": [245, 245, 245, 255], + "sidebar_lining_thin": [127, 127, 127, 255], "button": [31, 36, 39, 255], "button_hover": [68, 72, 75, 255],