diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index bdef0f9f04..603ffb8bd4 100755 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -300,6 +300,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(): @@ -1082,9 +1083,10 @@ class CuraApplication(QtApplication): op.push() Selection.clear() - Logger.log("i", "Reseting print information") - self._print_information = PrintInformation.PrintInformation() - + # 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 @@ -1484,11 +1486,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) @@ -1544,12 +1542,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/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 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/QualityManager.py b/cura/QualityManager.py index d984bd17a1..76a0c86a5f 100644 --- a/cura/QualityManager.py +++ b/cura/QualityManager.py @@ -136,9 +136,6 @@ class QualityManager: if basic_materials: result = self._getFilteredContainersForStack(machine_definition, basic_materials, **criteria) - empty_quality = ContainerRegistry.getInstance().findInstanceContainers(id = "empty_quality")[0] - result.append(empty_quality) - return result ## Find all quality changes for a machine. 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 b27faa1c61..309f27b6d7 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) @@ -939,6 +971,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 @@ -961,6 +997,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. @@ -1125,6 +1162,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: @@ -1222,7 +1268,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) @@ -1231,6 +1276,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 15b123d12f..77cd407457 100644 --- a/cura/Settings/ProfilesModel.py +++ b/cura/Settings/ProfilesModel.py @@ -87,7 +87,7 @@ class ProfilesModel(InstanceContainersModel): if quality.getMetaDataEntry("quality_type") not in quality_type_set: result.append(quality) - if len(result) > 1: + 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. diff --git a/cura/Settings/QualityAndUserProfilesModel.py b/cura/Settings/QualityAndUserProfilesModel.py index 8396e62417..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,7 +39,9 @@ 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 diff --git a/cura/Settings/QualitySettingsModel.py b/cura/Settings/QualitySettingsModel.py index 0e17237ff7..fb1aa9a6b2 100644 --- a/cura/Settings/QualitySettingsModel.py +++ b/cura/Settings/QualitySettingsModel.py @@ -107,7 +107,7 @@ class QualitySettingsModel(UM.Qt.ListModel.ListModel): else: quality_changes_container = containers[0] - if quality_changes_container.getMetaDataEntry("quality_type") == "not_supported": + if quality_changes_container.getMetaDataEntry("quality_type") == self._empty_quality.getMetaDataEntry("quality_type"): quality_container = self._empty_quality else: criteria = { 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 5c62c361c3..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") diff --git a/plugins/CuraEngineBackend/CuraEngineBackend.py b/plugins/CuraEngineBackend/CuraEngineBackend.py index 2fbd10e05b..f0a902545e 100755 --- a/plugins/CuraEngineBackend/CuraEngineBackend.py +++ b/plugins/CuraEngineBackend/CuraEngineBackend.py @@ -88,6 +88,7 @@ class CuraEngineBackend(QObject, Backend): # self._global_container_stack = None Application.getInstance().globalContainerStackChanged.connect(self._onGlobalStackChanged) + Application.getInstance().getExtruderManager().extrudersAdded.connect(self._onGlobalStackChanged) self._onGlobalStackChanged() Application.getInstance().stacksValidationFinished.connect(self._onStackErrorCheckFinished) 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/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 6c0cc554e7..b5cbc33d51 100644 --- a/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py +++ b/plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py @@ -248,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/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/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/anycubic_i3_mega.def.json b/resources/definitions/anycubic_i3_mega.def.json index a373872de8..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{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_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{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" + "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/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/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/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/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],