diff --git a/cura/PrinterOutputDevice.py b/cura/PrinterOutputDevice.py index 0141e74ba4..9e603b83ae 100644 --- a/cura/PrinterOutputDevice.py +++ b/cura/PrinterOutputDevice.py @@ -135,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/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/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/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/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/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/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/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/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],