diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index 32c5191c7c..7651cad930 100644 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -322,6 +322,7 @@ class CuraApplication(QtApplication): path = Resources.getStoragePath(self.ResourceTypes.VariantInstanceContainer, file_name) if path: + instance.setPath(path) with SaveFile(path, "wt", -1, "utf-8") as f: f.write(data) @@ -346,6 +347,7 @@ class CuraApplication(QtApplication): elif stack_type == "extruder_train": path = Resources.getStoragePath(self.ResourceTypes.ExtruderStack, file_name) if path: + stack.setPath(path) with SaveFile(path, "wt", -1, "utf-8") as f: f.write(data) diff --git a/cura/PrinterOutputDevice.py b/cura/PrinterOutputDevice.py index c376bb496c..b828e16daf 100644 --- a/cura/PrinterOutputDevice.py +++ b/cura/PrinterOutputDevice.py @@ -150,8 +150,9 @@ class PrinterOutputDevice(QObject, OutputDevice): @pyqtSlot(int) def setTargetBedTemperature(self, temperature): self._setTargetBedTemperature(temperature) - self._target_bed_temperature = temperature - self.targetBedTemperatureChanged.emit() + if self._target_bed_temperature != temperature: + self._target_bed_temperature = temperature + self.targetBedTemperatureChanged.emit() ## Time the print has been printing. # Note that timeTotal - timeElapsed should give time remaining. @@ -212,8 +213,9 @@ class PrinterOutputDevice(QObject, OutputDevice): # This simply sets the bed temperature, but ensures that a signal is emitted. # /param temperature temperature of the bed. def _setBedTemperature(self, temperature): - self._bed_temperature = temperature - self.bedTemperatureChanged.emit() + if self._bed_temperature != temperature: + self._bed_temperature = temperature + self.bedTemperatureChanged.emit() ## Get the target bed temperature if connected printer (if any) @pyqtProperty(int, notify = targetBedTemperatureChanged) @@ -228,8 +230,10 @@ class PrinterOutputDevice(QObject, OutputDevice): @pyqtSlot(int, int) def setTargetHotendTemperature(self, index, temperature): self._setTargetHotendTemperature(index, temperature) - self._target_hotend_temperatures[index] = temperature - self.targetHotendTemperaturesChanged.emit() + + if self._target_hotend_temperatures[index] != temperature: + self._target_hotend_temperatures[index] = temperature + self.targetHotendTemperaturesChanged.emit() ## Implementation function of setTargetHotendTemperature. # /param index Index of the hotend to set the temperature of @@ -251,8 +255,9 @@ class PrinterOutputDevice(QObject, OutputDevice): # /param index Index of the hotend # /param temperature temperature of the hotend (in deg C) def _setHotendTemperature(self, index, temperature): - self._hotend_temperatures[index] = temperature - self.hotendTemperaturesChanged.emit() + if self._hotend_temperatures[index] != temperature: + self._hotend_temperatures[index] = temperature + self.hotendTemperaturesChanged.emit() @pyqtProperty("QVariantList", notify = materialIdChanged) def materialIds(self): @@ -267,7 +272,6 @@ class PrinterOutputDevice(QObject, OutputDevice): self._material_ids[index] = material_id self.materialIdChanged.emit(index, material_id) - @pyqtProperty("QVariantList", notify = hotendIdChanged) def hotendIds(self): return self._hotend_ids @@ -302,8 +306,9 @@ class PrinterOutputDevice(QObject, OutputDevice): ## Set the connection state of this output device. # /param connection_state ConnectionState enum. def setConnectionState(self, connection_state): - self._connection_state = connection_state - self.connectionStateChanged.emit(self._id) + if self._connection_state != connection_state: + self._connection_state = connection_state + self.connectionStateChanged.emit(self._id) @pyqtProperty(str, notify = connectionTextChanged) def connectionText(self): @@ -351,6 +356,7 @@ class PrinterOutputDevice(QObject, OutputDevice): if self._head_z != z: self._head_z = z position_changed = True + if position_changed: self.headPositionChanged.emit() diff --git a/cura/Settings/ContainerManager.py b/cura/Settings/ContainerManager.py index c082c64da1..5be134c74b 100644 --- a/cura/Settings/ContainerManager.py +++ b/cura/Settings/ContainerManager.py @@ -167,6 +167,19 @@ class ContainerManager(QObject): return True + @pyqtSlot(str, str, result=str) + def getContainerMetaDataEntry(self, container_id, entry_name): + containers = self._container_registry.findContainers(None, id=container_id) + if not containers: + UM.Logger.log("w", "Could not get metadata of container %s because it was not found.", container_id) + return False + + result = containers[0].getMetaDataEntry(entry_name) + if result: + return result + else: + return "" + ## Set a metadata entry of the specified container. # # This will set the specified entry of the container's metadata to the specified @@ -577,6 +590,29 @@ class ContainerManager(QObject): return new_name + @pyqtSlot(str, result = str) + def duplicateMaterial(self, material_id): + containers = self._container_registry.findInstanceContainers(id=material_id) + if not containers: + UM.Logger.log("d", "Unable to duplicate the material with id %s, because it doesn't exist.", material_id) + return "" + + # Ensure all settings are saved. + UM.Application.getInstance().saveSettings() + + # Create a new ID & container to hold the data. + new_id = self._container_registry.uniqueName(material_id) + container_type = type(containers[0]) # Could be either a XMLMaterialProfile or a InstanceContainer + duplicated_container = container_type(new_id) + + # Instead of duplicating we load the data from the basefile again. + # This ensures that the inheritance goes well and all "cut up" subclasses of the xmlMaterial profile + # are also correctly created. + with open(containers[0].getPath(), encoding="utf-8") as f: + duplicated_container.deserialize(f.read()) + duplicated_container.setDirty(True) + self._container_registry.addContainer(duplicated_container) + # Factory function, used by QML @staticmethod def createContainerManager(engine, js_engine): diff --git a/plugins/CuraProfileReader/CuraProfileReader.py b/plugins/CuraProfileReader/CuraProfileReader.py index c8d39b7a78..2bccb8e3cb 100644 --- a/plugins/CuraProfileReader/CuraProfileReader.py +++ b/plugins/CuraProfileReader/CuraProfileReader.py @@ -27,15 +27,15 @@ class CuraProfileReader(ProfileReader): # returned. def read(self, file_name): try: - archive = zipfile.ZipFile(file_name, "r") - results = [] - for profile_id in archive.namelist(): - with archive.open(profile_id) as f: - serialized = f.read() - profile = self._loadProfile(serialized.decode("utf-8"), profile_id) - if profile is not None: - results.append(profile) - return results + with zipfile.ZipFile(file_name, "r") as archive: + results = [] + for profile_id in archive.namelist(): + with archive.open(profile_id) as f: + serialized = f.read() + profile = self._loadProfile(serialized.decode("utf-8"), profile_id) + if profile is not None: + results.append(profile) + return results except zipfile.BadZipFile: # It must be an older profile from Cura 2.1. diff --git a/plugins/XmlMaterialProfile/XmlMaterialProfile.py b/plugins/XmlMaterialProfile/XmlMaterialProfile.py index cd10348f2f..be8f5146c9 100644 --- a/plugins/XmlMaterialProfile/XmlMaterialProfile.py +++ b/plugins/XmlMaterialProfile/XmlMaterialProfile.py @@ -22,35 +22,6 @@ class XmlMaterialProfile(UM.Settings.InstanceContainer): super().__init__(container_id, *args, **kwargs) self._inherited_files = [] - ## Overridden from InstanceContainer - def duplicate(self, new_id, new_name = None): - base_file = self.getMetaDataEntry("base_file", None) - - if base_file != self.id: - containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(id = base_file) - if containers: - new_basefile = containers[0].duplicate(self.getMetaDataEntry("brand") + "_" + new_id, new_name) - base_file = new_basefile.id - UM.Settings.ContainerRegistry.getInstance().addContainer(new_basefile) - - new_id = self.getMetaDataEntry("brand") + "_" + new_id + "_" + self.getDefinition().getId() - variant = self.getMetaDataEntry("variant") - if variant: - variant_containers = UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(id = variant) - if variant_containers: - new_id += "_" + variant_containers[0].getName().replace(" ", "_") - has_base_file = True - else: - has_base_file = False - - new_id = UM.Settings.ContainerRegistry.getInstance().createUniqueName("material", self._id, new_id, "") - result = super().duplicate(new_id, new_name) - if has_base_file: - result.setMetaDataEntry("base_file", base_file) - else: - result.setMetaDataEntry("base_file", result.id) - return result - def getInheritedFiles(self): return self._inherited_files @@ -63,6 +34,7 @@ class XmlMaterialProfile(UM.Settings.InstanceContainer): container._read_only = read_only # prevent loop instead of calling setReadOnly ## Overridden from InstanceContainer + # set the meta data for all machine / variant combinations def setMetaDataEntry(self, key, value): if self.isReadOnly(): return @@ -103,10 +75,17 @@ class XmlMaterialProfile(UM.Settings.InstanceContainer): # # basefile = self.getMetaDataEntry("base_file", self._id) #if basefile is self.id, this is a basefile. # for container in UM.Settings.ContainerRegistry.getInstance().findInstanceContainers(base_file = basefile): - # container._dirty = True + # if not container.isReadOnly(): + # container.setDirty(True) ## Overridden from InstanceContainer + # base file: global settings + supported machines + # machine / variant combination: only changes for itself. def serialize(self): + if self._read_only: + Logger.log("w", "Serializing read-only container [%s], probably a programming error." % self.id) + return + registry = UM.Settings.ContainerRegistry.getInstance() base_file = self.getMetaDataEntry("base_file", "") @@ -114,7 +93,7 @@ class XmlMaterialProfile(UM.Settings.InstanceContainer): # Since we create an instance of XmlMaterialProfile for each machine and nozzle in the profile, # we should only serialize the "base" material definition, since that can then take care of # serializing the machine/nozzle specific profiles. - raise NotImplementedError("Cannot serialize non-root XML materials") + raise NotImplementedError("Ignoring serializing non-root XML materials, the data is contained in the base material") builder = ET.TreeBuilder() diff --git a/resources/definitions/fdmprinter.def.json b/resources/definitions/fdmprinter.def.json index ab1915bde1..b1cad3eb68 100644 --- a/resources/definitions/fdmprinter.def.json +++ b/resources/definitions/fdmprinter.def.json @@ -2478,19 +2478,6 @@ "enabled": "support_enable", "settable_per_mesh": true }, - "support_area_smoothing": - { - "label": "Support Area Smoothing", - "description": "Maximum distance in the X/Y directions of a line segment which is to be smoothed out. Ragged lines are introduced by the join distance and support bridge, which cause the machine to resonate. Smoothing the support areas won't cause them to break with the constraints, except it might change the overhang.", - "unit": "mm", - "type": "float", - "default_value": 0.6, - "global_inherits_stack": "support_extruder_nr", - "minimum_value": "0", - "maximum_value_warning": "1.0", - "enabled": "support_enable", - "settable_per_mesh": true - }, "support_interface_enable": { "label": "Enable Support Interface", @@ -2546,6 +2533,19 @@ } } }, + "support_interface_skip_height": + { + "label": "Support Interface Resolution", + "description": "When checking where there's model above the support, take steps of the given height. Lower values will slice slower, while higher values may cause normal support to be printed in some places where there should have been support interface.", + "unit": "mm", + "type": "float", + "default_value": 0.3, + "minimum_value": "0", + "global_inherits_stack": "support_extruder_nr", + "maximum_value_warning": "support_interface_height", + "enabled": "extruderValue(support_extruder_nr, 'support_interface_enable') and support_enable", + "settable_per_mesh": true + }, "support_interface_density": { "label": "Support Interface Density", diff --git a/resources/qml/Preferences/MaterialsPage.qml b/resources/qml/Preferences/MaterialsPage.qml index d2d7c1b0a2..ef57b5af58 100644 --- a/resources/qml/Preferences/MaterialsPage.qml +++ b/resources/qml/Preferences/MaterialsPage.qml @@ -129,7 +129,6 @@ UM.ManagementPage enabled: base.currentItem != null && base.currentItem.id != Cura.MachineManager.activeMaterialId onClicked: Cura.MachineManager.setActiveMaterial(base.currentItem.id) }, - /* // apparently visible does not work on OS X Button { text: catalog.i18nc("@action:button", "Duplicate"); @@ -137,24 +136,17 @@ UM.ManagementPage enabled: base.currentItem != null onClicked: { - var material_id = Cura.ContainerManager.duplicateContainer(base.currentItem.id) + var base_file = Cura.ContainerManager.getContainerMetaDataEntry(base.currentItem.id, "base_file") + // We need to copy the base container instead of the specific variant. + var material_id = base_file == "" ? Cura.ContainerManager.duplicateMaterial(base.currentItem.id): Cura.ContainerManager.duplicateMaterial(base_file) if(material_id == "") { return } - if(Cura.MachineManager.filterQualityByMachine) - { - var quality_id = Cura.ContainerManager.duplicateContainer(Cura.MachineManager.activeQualityId) - Cura.ContainerManager.setContainerMetaDataEntry(quality_id, "material", material_id) - Cura.MachineManager.setActiveQuality(quality_id) - } - Cura.MachineManager.setActiveMaterial(material_id) } - visible: false; }, - */ Button { text: catalog.i18nc("@action:button", "Remove"); @@ -162,15 +154,13 @@ UM.ManagementPage enabled: base.currentItem != null && !base.currentItem.readOnly && !Cura.ContainerManager.isContainerUsed(base.currentItem.id) onClicked: confirmDialog.open() }, - /* // apparently visible does not work on OS X Button { text: catalog.i18nc("@action:button", "Import"); iconName: "document-import"; onClicked: importDialog.open(); - visible: false; + visible: true; }, - */ Button { text: catalog.i18nc("@action:button", "Export")