Browse Source

Merge branch 'master' of github.com:Ultimaker/Cura

Jack Ha 7 years ago
parent
commit
5a55c1817d

+ 7 - 16
cura/CuraApplication.py

@@ -451,27 +451,18 @@ class CuraApplication(QtApplication):
 
     @pyqtSlot(str)
     def discardOrKeepProfileChangesClosed(self, option):
+        global_stack = self.getGlobalContainerStack()
         if option == "discard":
-            global_stack = self.getGlobalContainerStack()
-            for extruder in self._extruder_manager.getMachineExtruders(global_stack.getId()):
-                extruder.getTop().clear()
-            global_stack.getTop().clear()
+            for extruder in global_stack.extruders.values():
+                extruder.userChanges.clear()
+            global_stack.userChanges.clear()
 
         # if the user decided to keep settings then the user settings should be re-calculated and validated for errors
         # before slicing. To ensure that slicer uses right settings values
         elif option == "keep":
-            global_stack = self.getGlobalContainerStack()
-            for extruder in self._extruder_manager.getMachineExtruders(global_stack.getId()):
-                user_extruder_container = extruder.getTop()
-                if user_extruder_container:
-                    user_extruder_container.update()
-
-            user_global_container = global_stack.getTop()
-            if user_global_container:
-                user_global_container.update()
-
-        # notify listeners that quality has changed (after user selected discard or keep)
-        self.getMachineManager().activeQualityChanged.emit()
+            for extruder in global_stack.extruders.values():
+                extruder.userChanges.update()
+            global_stack.userChanges.update()
 
     @pyqtSlot(int)
     def messageBoxClosed(self, button):

+ 38 - 0
cura/PrinterOutput/ExtruderOutputModel.py

@@ -18,6 +18,7 @@ class ExtruderOutputModel(QObject):
     hotendTemperatureChanged = pyqtSignal()
     activeMaterialChanged = pyqtSignal()
     extruderConfigurationChanged = pyqtSignal()
+    isPreheatingChanged = pyqtSignal()
 
     def __init__(self, printer: "PrinterOutputModel", position, parent=None):
         super().__init__(parent)
@@ -30,6 +31,21 @@ class ExtruderOutputModel(QObject):
         self._extruder_configuration = ExtruderConfigurationModel()
         self._extruder_configuration.position = self._position
 
+        self._is_preheating = False
+
+    def getPrinter(self):
+        return self._printer
+
+    def getPosition(self):
+        return self._position
+
+    # Does the printer support pre-heating the bed at all
+    @pyqtProperty(bool, constant=True)
+    def canPreHeatHotends(self):
+        if self._printer:
+            return self._printer.canPreHeatHotends
+        return False
+
     @pyqtProperty(QObject, notify = activeMaterialChanged)
     def activeMaterial(self) -> "MaterialOutputModel":
         return self._active_material
@@ -82,3 +98,25 @@ class ExtruderOutputModel(QObject):
         if self._extruder_configuration.isValid():
             return self._extruder_configuration
         return None
+
+    def updateIsPreheating(self, pre_heating):
+        if self._is_preheating != pre_heating:
+            self._is_preheating = pre_heating
+            self.isPreheatingChanged.emit()
+
+    @pyqtProperty(bool, notify=isPreheatingChanged)
+    def isPreheating(self):
+        return self._is_preheating
+
+    ##  Pre-heats the extruder before printer.
+    #
+    #   \param temperature The temperature to heat the extruder to, in degrees
+    #   Celsius.
+    #   \param duration How long the bed should stay warm, in seconds.
+    @pyqtSlot(float, float)
+    def preheatHotend(self, temperature, duration):
+        self._printer._controller.preheatHotend(self, temperature, duration)
+
+    @pyqtSlot()
+    def cancelPreheatHotend(self):
+        self._printer._controller.cancelPreheatHotend(self)

+ 151 - 0
cura/PrinterOutput/GenericOutputController.py

@@ -0,0 +1,151 @@
+# Copyright (c) 2018 Ultimaker B.V.
+# Cura is released under the terms of the LGPLv3 or higher.
+
+from cura.PrinterOutput.PrinterOutputController import PrinterOutputController
+from PyQt5.QtCore import QTimer
+
+MYPY = False
+if MYPY:
+    from cura.PrinterOutput.PrintJobOutputModel import PrintJobOutputModel
+    from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel
+
+
+class GenericOutputController(PrinterOutputController):
+    def __init__(self, output_device):
+        super().__init__(output_device)
+
+        self._preheat_bed_timer = QTimer()
+        self._preheat_bed_timer.setSingleShot(True)
+        self._preheat_bed_timer.timeout.connect(self._onPreheatBedTimerFinished)
+        self._preheat_printer = None
+
+        self._preheat_hotends_timer = QTimer()
+        self._preheat_hotends_timer.setSingleShot(True)
+        self._preheat_hotends_timer.timeout.connect(self._onPreheatHotendsTimerFinished)
+        self._preheat_hotends = set()
+
+        self._output_device.printersChanged.connect(self._onPrintersChanged)
+        self._active_printer = None
+
+    def _onPrintersChanged(self):
+        if self._active_printer:
+            self._active_printer.stateChanged.disconnect(self._onPrinterStateChanged)
+            self._active_printer.targetBedTemperatureChanged.disconnect(self._onTargetBedTemperatureChanged)
+            for extruder in self._active_printer.extruders:
+                extruder.targetHotendTemperatureChanged.disconnect(self._onTargetHotendTemperatureChanged)
+
+        self._active_printer = self._output_device.activePrinter
+        if self._active_printer:
+            self._active_printer.stateChanged.connect(self._onPrinterStateChanged)
+            self._active_printer.targetBedTemperatureChanged.connect(self._onTargetBedTemperatureChanged)
+            for extruder in self._active_printer.extruders:
+                extruder.targetHotendTemperatureChanged.connect(self._onTargetHotendTemperatureChanged)
+
+    def _onPrinterStateChanged(self):
+        if self._active_printer.state != "idle":
+            if self._preheat_bed_timer.isActive():
+                self._preheat_bed_timer.stop()
+                self._preheat_printer.updateIsPreheating(False)
+            if self._preheat_hotends_timer.isActive():
+                self._preheat_hotends_timer.stop()
+                for extruder in self._preheat_hotends:
+                    extruder.updateIsPreheating(False)
+                self._preheat_hotends = set()
+
+    def moveHead(self, printer: "PrinterOutputModel", x, y, z, speed):
+        self._output_device.sendCommand("G91")
+        self._output_device.sendCommand("G0 X%s Y%s Z%s F%s" % (x, y, z, speed))
+        self._output_device.sendCommand("G90")
+
+    def homeHead(self, printer):
+        self._output_device.sendCommand("G28 X")
+        self._output_device.sendCommand("G28 Y")
+
+    def homeBed(self, printer):
+        self._output_device.sendCommand("G28 Z")
+
+    def setJobState(self, job: "PrintJobOutputModel", state: str):
+        if state == "pause":
+            self._output_device.pausePrint()
+            job.updateState("paused")
+        elif state == "print":
+            self._output_device.resumePrint()
+            job.updateState("printing")
+        elif state == "abort":
+            self._output_device.cancelPrint()
+            pass
+
+    def setTargetBedTemperature(self, printer: "PrinterOutputModel", temperature: int):
+        self._output_device.sendCommand("M140 S%s" % temperature)
+
+    def _onTargetBedTemperatureChanged(self):
+        if self._preheat_bed_timer.isActive() and self._preheat_printer.targetBedTemperature == 0:
+            self._preheat_bed_timer.stop()
+            self._preheat_printer.updateIsPreheating(False)
+
+    def preheatBed(self, printer: "PrinterOutputModel", temperature, duration):
+        try:
+            temperature = round(temperature)  # The API doesn't allow floating point.
+            duration = round(duration)
+        except ValueError:
+            return  # Got invalid values, can't pre-heat.
+
+        self.setTargetBedTemperature(printer, temperature=temperature)
+        self._preheat_bed_timer.setInterval(duration * 1000)
+        self._preheat_bed_timer.start()
+        self._preheat_printer = printer
+        printer.updateIsPreheating(True)
+
+    def cancelPreheatBed(self, printer: "PrinterOutputModel"):
+        self.setTargetBedTemperature(printer, temperature=0)
+        self._preheat_bed_timer.stop()
+        printer.updateIsPreheating(False)
+
+    def _onPreheatBedTimerFinished(self):
+        self.setTargetBedTemperature(self._preheat_printer, 0)
+        self._preheat_printer.updateIsPreheating(False)
+
+    def setTargetHotendTemperature(self, printer: "PrinterOutputModel", position: int, temperature: int):
+        self._output_device.sendCommand("M104 S%s T%s" % (temperature, position))
+
+    def _onTargetHotendTemperatureChanged(self):
+        if not self._preheat_hotends_timer.isActive():
+            return
+
+        for extruder in self._active_printer.extruders:
+            if extruder in self._preheat_hotends and extruder.targetHotendTemperature == 0:
+                extruder.updateIsPreheating(False)
+                self._preheat_hotends.remove(extruder)
+        if not self._preheat_hotends:
+            self._preheat_hotends_timer.stop()
+
+    def preheatHotend(self, extruder: "ExtruderOutputModel", temperature, duration):
+        position = extruder.getPosition()
+        number_of_extruders = len(extruder.getPrinter().extruders)
+        if position >= number_of_extruders:
+            return  # Got invalid extruder nr, can't pre-heat.
+
+        try:
+            temperature = round(temperature)  # The API doesn't allow floating point.
+            duration = round(duration)
+        except ValueError:
+            return  # Got invalid values, can't pre-heat.
+
+        self.setTargetHotendTemperature(extruder.getPrinter(), position, temperature=temperature)
+        self._preheat_hotends_timer.setInterval(duration * 1000)
+        self._preheat_hotends_timer.start()
+        self._preheat_hotends.add(extruder)
+        extruder.updateIsPreheating(True)
+
+    def cancelPreheatHotend(self, extruder: "ExtruderOutputModel"):
+        self.setTargetHotendTemperature(extruder.getPrinter(), extruder.getPosition(), temperature=0)
+        if extruder in self._preheat_hotends:
+            extruder.updateIsPreheating(False)
+            self._preheat_hotends.remove(extruder)
+        if not self._preheat_hotends and self._preheat_hotends_timer.isActive():
+            self._preheat_hotends_timer.stop()
+
+    def _onPreheatHotendsTimerFinished(self):
+        for extruder in self._preheat_hotends:
+            self.setTargetHotendTemperature(extruder.getPrinter(), extruder.getPosition(), 0)
+        self._preheat_hotends = set()

+ 7 - 0
cura/PrinterOutput/PrinterOutputController.py

@@ -15,6 +15,7 @@ class PrinterOutputController:
         self.can_pause = True
         self.can_abort = True
         self.can_pre_heat_bed = True
+        self.can_pre_heat_hotends = True
         self.can_control_manually = True
         self._output_device = output_device
 
@@ -33,6 +34,12 @@ class PrinterOutputController:
     def preheatBed(self, printer: "PrinterOutputModel", temperature, duration):
         Logger.log("w", "Preheat bed not implemented in controller")
 
+    def cancelPreheatHotend(self, extruder: "ExtruderOutputModel"):
+        Logger.log("w", "Cancel preheat hotend not implemented in controller")
+
+    def preheatHotend(self, extruder: "ExtruderOutputModel", temperature, duration):
+        Logger.log("w", "Preheat hotend not implemented in controller")
+
     def setHeadPosition(self, printer: "PrinterOutputModel", x, y, z, speed):
         Logger.log("w", "Set head position not implemented in controller")
 

+ 7 - 0
cura/PrinterOutput/PrinterOutputModel.py

@@ -238,6 +238,13 @@ class PrinterOutputModel(QObject):
             return self._controller.can_pre_heat_bed
         return False
 
+    # Does the printer support pre-heating the bed at all
+    @pyqtProperty(bool, constant=True)
+    def canPreHeatHotends(self):
+        if self._controller:
+            return self._controller.can_pre_heat_hotends
+        return False
+
     # Does the printer support pause at all
     @pyqtProperty(bool, constant=True)
     def canPause(self):

+ 9 - 0
cura/Settings/MachineManager.py

@@ -1269,6 +1269,15 @@ class MachineManager(QObject):
         if not no_dialog and self.hasUserSettings and Preferences.getInstance().getValue("cura/active_mode") == 1:
             self._application.discardOrKeepProfileChanges()
 
+    @pyqtSlot()
+    def clearQualityChangesGroup(self):
+        with postponeSignals(*self._getContainerChangedSignals(), compress = CompressTechnique.CompressPerParameterValue):
+            self._setQualityGroup(self._current_quality_group)
+
+        # See if we need to show the Discard or Keep changes screen
+        if self.hasUserSettings and Preferences.getInstance().getValue("cura/active_mode") == 1:
+            self._application.discardOrKeepProfileChanges()
+
     @pyqtProperty(QObject, fset = setQualityChangesGroup, notify = activeQualityChangesGroupChanged)
     def activeQualityChangesGroup(self):
         return self._current_quality_changes_group

+ 38 - 25
plugins/GCodeWriter/GCodeWriter.py

@@ -1,17 +1,17 @@
 # Copyright (c) 2017 Ultimaker B.V.
 # Cura is released under the terms of the LGPLv3 or higher.
 
+import re  # For escaping characters in the settings.
+import json
+import copy
+
 from UM.Mesh.MeshWriter import MeshWriter
 from UM.Logger import Logger
 from UM.Application import Application
 from UM.Settings.InstanceContainer import InstanceContainer
-from UM.Util import parseBool
 
-from cura.Settings.ExtruderManager import ExtruderManager
+from cura.Machines.QualityManager import getMachineDefinitionIDForQualitySearch
 
-import re #For escaping characters in the settings.
-import json
-import copy
 
 ##  Writes g-code to a file.
 #
@@ -45,6 +45,8 @@ class GCodeWriter(MeshWriter):
     def __init__(self):
         super().__init__()
 
+        self._application = Application.getInstance()
+
     ##  Writes the g-code for the entire scene to a stream.
     #
     #   Note that even though the function accepts a collection of nodes, the
@@ -94,7 +96,6 @@ class GCodeWriter(MeshWriter):
 
         return flat_container
 
-
     ##  Serialises a container stack to prepare it for writing at the end of the
     #   g-code.
     #
@@ -104,15 +105,21 @@ class GCodeWriter(MeshWriter):
     #   \param settings A container stack to serialise.
     #   \return A serialised string of the settings.
     def _serialiseSettings(self, stack):
+        container_registry = self._application.getContainerRegistry()
+        quality_manager = self._application.getQualityManager()
+
         prefix = ";SETTING_" + str(GCodeWriter.version) + " "  # The prefix to put before each line.
         prefix_length = len(prefix)
 
+        quality_name = stack.qualityChanges.getName()
+        quality_type = stack.quality.getMetaDataEntry("quality_type")
         container_with_profile = stack.qualityChanges
         if container_with_profile.getId() == "empty_quality_changes":
-            Logger.log("e", "No valid quality profile found, not writing settings to g-code!")
-            return ""
+            # If the global quality changes is empty, create a new one
+            quality_name = container_registry.uniqueName(stack.quality.getName())
+            container_with_profile = quality_manager._createQualityChanges(quality_type, quality_name, stack, None)
 
-        flat_global_container = self._createFlattenedContainerInstance(stack.getTop(), container_with_profile)
+        flat_global_container = self._createFlattenedContainerInstance(stack.userChanges, container_with_profile)
         # If the quality changes is not set, we need to set type manually
         if flat_global_container.getMetaDataEntry("type", None) is None:
             flat_global_container.addMetaDataEntry("type", "quality_changes")
@@ -121,41 +128,47 @@ class GCodeWriter(MeshWriter):
         if flat_global_container.getMetaDataEntry("quality_type", None) is None:
             flat_global_container.addMetaDataEntry("quality_type", stack.quality.getMetaDataEntry("quality_type", "normal"))
 
-        # Change the default defintion
-        default_machine_definition = "fdmprinter"
-        if parseBool(stack.getMetaDataEntry("has_machine_quality", "False")):
-            default_machine_definition = stack.getMetaDataEntry("quality_definition")
-            if not default_machine_definition:
-                default_machine_definition = stack.definition.getId()
-        flat_global_container.setMetaDataEntry("definition", default_machine_definition)
+        # Get the machine definition ID for quality profiles
+        machine_definition_id_for_quality = getMachineDefinitionIDForQualitySearch(stack.definition)
+        flat_global_container.setMetaDataEntry("definition", machine_definition_id_for_quality)
 
         serialized = flat_global_container.serialize()
         data = {"global_quality": serialized}
 
-        for extruder in sorted(stack.extruders.values(), key = lambda k: k.getMetaDataEntry("position")):
+        all_setting_keys = set(flat_global_container.getAllKeys())
+        for extruder in sorted(stack.extruders.values(), key = lambda k: int(k.getMetaDataEntry("position"))):
             extruder_quality = extruder.qualityChanges
             if extruder_quality.getId() == "empty_quality_changes":
-                Logger.log("w", "No extruder quality profile found, not writing quality for extruder %s to file!", extruder.getId())
-                continue
-            flat_extruder_quality = self._createFlattenedContainerInstance(extruder.getTop(), extruder_quality)
+                # Same story, if quality changes is empty, create a new one
+                quality_name = container_registry.uniqueName(stack.quality.getName())
+                extruder_quality = quality_manager._createQualityChanges(quality_type, quality_name, stack, None)
+
+            flat_extruder_quality = self._createFlattenedContainerInstance(extruder.userChanges, extruder_quality)
             # If the quality changes is not set, we need to set type manually
             if flat_extruder_quality.getMetaDataEntry("type", None) is None:
                 flat_extruder_quality.addMetaDataEntry("type", "quality_changes")
 
             # Ensure that extruder is set. (Can happen if we have empty quality changes).
-            if flat_extruder_quality.getMetaDataEntry("extruder", None) is None:
-                flat_extruder_quality.addMetaDataEntry("extruder", extruder.getBottom().getId())
+            if flat_extruder_quality.getMetaDataEntry("position", None) is None:
+                flat_extruder_quality.addMetaDataEntry("position", extruder.getMetaDataEntry("position"))
 
             # Ensure that quality_type is set. (Can happen if we have empty quality changes).
             if flat_extruder_quality.getMetaDataEntry("quality_type", None) is None:
                 flat_extruder_quality.addMetaDataEntry("quality_type", extruder.quality.getMetaDataEntry("quality_type", "normal"))
 
-            # Change the default defintion
-            flat_extruder_quality.setMetaDataEntry("definition", default_machine_definition)
+            # Change the default definition
+            flat_extruder_quality.setMetaDataEntry("definition", machine_definition_id_for_quality)
 
             extruder_serialized = flat_extruder_quality.serialize()
             data.setdefault("extruder_quality", []).append(extruder_serialized)
 
+            all_setting_keys.update(set(flat_extruder_quality.getAllKeys()))
+
+        # Check if there is any profiles
+        if not all_setting_keys:
+            Logger.log("i", "No custom settings found, not writing settings to g-code.")
+            return ""
+
         json_string = json.dumps(data)
 
         # Escape characters that have a special meaning in g-code comments.
@@ -169,5 +182,5 @@ class GCodeWriter(MeshWriter):
 
         # Lines have 80 characters, so the payload of each line is 80 - prefix.
         for pos in range(0, len(escaped_string), 80 - prefix_length):
-            result += prefix + escaped_string[pos : pos + 80 - prefix_length] + "\n"
+            result += prefix + escaped_string[pos: pos + 80 - prefix_length] + "\n"
         return result

+ 46 - 19
plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml

@@ -163,7 +163,16 @@ Item {
                         id: addedSettingsModel;
                         containerId: Cura.MachineManager.activeDefinitionId
                         expanded: [ "*" ]
-                        exclude: {
+                        filter:
+                        {
+                            if (printSequencePropertyProvider.properties.value == "one_at_a_time")
+                            {
+                                return {"settable_per_meshgroup": true};
+                            }
+                            return {"settable_per_mesh": true};
+                        }
+                        exclude:
+                        {
                             var excluded_settings = [ "support_mesh", "anti_overhang_mesh", "cutting_mesh", "infill_mesh" ];
 
                             if(meshTypeSelection.model.get(meshTypeSelection.currentIndex).type == "support_mesh")
@@ -375,7 +384,6 @@ Item {
         title: catalog.i18nc("@title:window", "Select Settings to Customize for this model")
         width: screenScaleFactor * 360
 
-        property string labelFilter: ""
         property var additional_excluded_settings
 
         onVisibilityChanged:
@@ -386,11 +394,33 @@ Item {
                 // Set skip setting, it will prevent from resetting selected mesh_type
                 contents.model.visibilityHandler.addSkipResetSetting(meshTypeSelection.model.get(meshTypeSelection.currentIndex).type)
                 listview.model.forceUpdate()
+
+                updateFilter()
             }
         }
 
+        function updateFilter()
+        {
+            var new_filter = {};
+            if (printSequencePropertyProvider.properties.value == "one_at_a_time")
+            {
+                new_filter["settable_per_meshgroup"] = true;
+            }
+            else
+            {
+                new_filter["settable_per_mesh"] = true;
+            }
+
+            if(filterInput.text != "")
+            {
+                new_filter["i18n_label"] = "*" + filterInput.text;
+            }
+
+            listview.model.filter = new_filter;
+        }
+
         TextField {
-            id: filter
+            id: filterInput
 
             anchors {
                 top: parent.top
@@ -401,17 +431,7 @@ Item {
 
             placeholderText: catalog.i18nc("@label:textbox", "Filter...");
 
-            onTextChanged:
-            {
-                if(text != "")
-                {
-                    listview.model.filter = {"settable_per_mesh": true, "i18n_label": "*" + text}
-                }
-                else
-                {
-                    listview.model.filter = {"settable_per_mesh": true}
-                }
-            }
+            onTextChanged: settingPickDialog.updateFilter()
         }
 
         CheckBox
@@ -437,7 +457,7 @@ Item {
 
             anchors
             {
-                top: filter.bottom;
+                top: filterInput.bottom;
                 left: parent.left;
                 right: parent.right;
                 bottom: parent.bottom;
@@ -449,10 +469,6 @@ Item {
                 {
                     id: definitionsModel;
                     containerId: Cura.MachineManager.activeDefinitionId
-                    filter:
-                    {
-                        "settable_per_mesh": true
-                    }
                     visibilityHandler: UM.SettingPreferenceVisibilityHandler {}
                     expanded: [ "*" ]
                     exclude:
@@ -484,6 +500,7 @@ Item {
                         }
                     }
                 }
+                Component.onCompleted: settingPickDialog.updateFilter()
             }
         }
 
@@ -507,6 +524,16 @@ Item {
         storeIndex: 0
     }
 
+    UM.SettingPropertyProvider
+    {
+        id: printSequencePropertyProvider
+
+        containerStackId: Cura.MachineManager.activeMachineId
+        key: "print_sequence"
+        watchedProperties: [ "value" ]
+        storeIndex: 0
+    }
+
     SystemPalette { id: palette; }
 
     Component

+ 1 - 1
plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py

@@ -60,7 +60,7 @@ class RemovableDriveOutputDevice(OutputDevice):
 
         if len(file_formats) == 0:
             Logger.log("e", "There are no file formats available to write with!")
-            raise OutputDeviceError.WriteRequestFailedError(catalog.i18nc("There are no file formats available to write with!"))
+            raise OutputDeviceError.WriteRequestFailedError(catalog.i18nc("@info:status", "There are no file formats available to write with!"))
 
         # Just take the first file format available.
         if file_handler is not None:

+ 1 - 0
plugins/UM3NetworkPrinting/ClusterUM3PrinterOutputController.py

@@ -13,6 +13,7 @@ class ClusterUM3PrinterOutputController(PrinterOutputController):
     def __init__(self, output_device):
         super().__init__(output_device)
         self.can_pre_heat_bed = False
+        self.can_pre_heat_hotends = False
         self.can_control_manually = False
 
     def setJobState(self, job: "PrintJobOutputModel", state: str):

Some files were not shown because too many files changed in this diff