Просмотр исходного кода

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

Diego Prado Gesto 7 лет назад
Родитель
Сommit
b11b2def3e

+ 0 - 1
.gitignore

@@ -47,7 +47,6 @@ plugins/Doodle3D-cura-plugin
 plugins/FlatProfileExporter
 plugins/GodMode
 plugins/OctoPrintPlugin
-plugins/PostProcessingPlugin
 plugins/ProfileFlattener
 plugins/X3GWriter
 

+ 9 - 0
plugins/PerObjectSettingsTool/PerObjectSettingVisibilityHandler.py

@@ -2,6 +2,7 @@
 # Cura is released under the terms of the LGPLv3 or higher.
 
 from PyQt5.QtCore import QObject, pyqtProperty, pyqtSignal
+from UM.FlameProfiler import pyqtSlot
 
 from UM.Application import Application
 from UM.Settings.ContainerRegistry import ContainerRegistry
@@ -21,6 +22,7 @@ class PerObjectSettingVisibilityHandler(UM.Settings.Models.SettingVisibilityHand
         self._selected_object_id = None
         self._node = None
         self._stack = None
+        self._skip_setting = None
 
     def setSelectedObjectId(self, id):
         if id != self._selected_object_id:
@@ -36,6 +38,10 @@ class PerObjectSettingVisibilityHandler(UM.Settings.Models.SettingVisibilityHand
     def selectedObjectId(self):
         return self._selected_object_id
 
+    @pyqtSlot(str)
+    def setSkipSetting(self, setting_name):
+        self._skip_setting = setting_name
+
     def setVisible(self, visible):
         if not self._node:
             return
@@ -50,6 +56,9 @@ class PerObjectSettingVisibilityHandler(UM.Settings.Models.SettingVisibilityHand
 
         # Remove all instances that are not in visibility list
         for instance in all_instances:
+            # exceptionally skip setting
+            if self._skip_setting is not None and self._skip_setting == instance.definition.key:
+                continue
             if instance.definition.key not in visible:
                 settings.removeInstance(instance.definition.key)
                 visibility_changed = True

+ 2 - 0
plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml

@@ -324,6 +324,8 @@ Item {
             // force updating the model to sync it with addedSettingsModel
             if(visible)
             {
+                // Set skip setting, it will prevent from restting selected mesh_type
+                contents.model.visibilityHandler.setSkipSetting(meshTypeSelection.model.get(meshTypeSelection.currentIndex).type)
                 listview.model.forceUpdate()
             }
         }

+ 206 - 0
plugins/PostProcessingPlugin/PostProcessingPlugin.py

@@ -0,0 +1,206 @@
+# Copyright (c) 2015 Jaime van Kessel, Ultimaker B.V.
+# The PostProcessingPlugin is released under the terms of the AGPLv3 or higher.
+from PyQt5.QtCore import QObject, pyqtProperty, pyqtSignal, pyqtSlot
+
+from UM.PluginRegistry import PluginRegistry
+from UM.Resources import Resources
+from UM.Application import Application
+from UM.Extension import Extension
+from UM.Logger import Logger
+
+import os.path
+import pkgutil
+import sys
+import importlib.util
+
+from UM.i18n import i18nCatalog
+i18n_catalog = i18nCatalog("cura")
+
+
+##  The post processing plugin is an Extension type plugin that enables pre-written scripts to post process generated
+#   g-code files.
+class PostProcessingPlugin(QObject, Extension):
+    def __init__(self, parent = None):
+        super().__init__(parent)
+        self.addMenuItem(i18n_catalog.i18n("Modify G-Code"), self.showPopup)
+        self._view = None
+
+        # Loaded scripts are all scripts that can be used
+        self._loaded_scripts = {}
+        self._script_labels = {}
+
+        # Script list contains instances of scripts in loaded_scripts.
+        # There can be duplicates, which will be executed in sequence.
+        self._script_list = []
+        self._selected_script_index = -1
+
+        Application.getInstance().getOutputDeviceManager().writeStarted.connect(self.execute)
+
+    selectedIndexChanged = pyqtSignal()
+    @pyqtProperty("QVariant", notify = selectedIndexChanged)
+    def selectedScriptDefinitionId(self):
+        try:
+            return self._script_list[self._selected_script_index].getDefinitionId()
+        except:
+            return ""
+
+    @pyqtProperty("QVariant", notify=selectedIndexChanged)
+    def selectedScriptStackId(self):
+        try:
+            return self._script_list[self._selected_script_index].getStackId()
+        except:
+            return ""
+
+    ##  Execute all post-processing scripts on the gcode.
+    def execute(self, output_device):
+        scene = Application.getInstance().getController().getScene()
+        gcode_dict = getattr(scene, "gcode_dict")
+        if not gcode_dict:
+            return
+
+        # get gcode list for the active build plate
+        active_build_plate_id = Application.getInstance().getBuildPlateModel().activeBuildPlate
+        gcode_list = gcode_dict[active_build_plate_id]
+        if not gcode_list:
+            return
+
+        if ";POSTPROCESSED" not in gcode_list[0]:
+            for script in self._script_list:
+                try:
+                    gcode_list = script.execute(gcode_list)
+                except Exception:
+                    Logger.logException("e", "Exception in post-processing script.")
+            if len(self._script_list):  # Add comment to g-code if any changes were made.
+                gcode_list[0] += ";POSTPROCESSED\n"
+            gcode_dict[active_build_plate_id] = gcode_list
+            setattr(scene, "gcode_dict", gcode_dict)
+        else:
+            Logger.log("e", "Already post processed")
+
+    @pyqtSlot(int)
+    def setSelectedScriptIndex(self, index):
+        self._selected_script_index = index
+        self.selectedIndexChanged.emit()
+
+    @pyqtProperty(int, notify = selectedIndexChanged)
+    def selectedScriptIndex(self):
+        return self._selected_script_index
+
+    @pyqtSlot(int, int)
+    def moveScript(self, index, new_index):
+        if new_index < 0 or new_index > len(self._script_list) - 1:
+            return  # nothing needs to be done
+        else:
+            # Magical switch code.
+            self._script_list[new_index], self._script_list[index] = self._script_list[index], self._script_list[new_index]
+            self.scriptListChanged.emit()
+            self.selectedIndexChanged.emit() #Ensure that settings are updated
+            self._propertyChanged()
+
+    ##  Remove a script from the active script list by index.
+    @pyqtSlot(int)
+    def removeScriptByIndex(self, index):
+        self._script_list.pop(index)
+        if len(self._script_list) - 1 < self._selected_script_index:
+            self._selected_script_index = len(self._script_list) - 1
+        self.scriptListChanged.emit()
+        self.selectedIndexChanged.emit()  # Ensure that settings are updated
+        self._propertyChanged()
+
+    ##  Load all scripts from provided path.
+    #   This should probably only be done on init.
+    #   \param path Path to check for scripts.
+    def loadAllScripts(self, path):
+        scripts = pkgutil.iter_modules(path = [path])
+        for loader, script_name, ispkg in scripts:
+            # Iterate over all scripts.
+            if script_name not in sys.modules:
+                spec = importlib.util.spec_from_file_location(__name__ + "." + script_name, os.path.join(path, script_name + ".py"))
+                loaded_script = importlib.util.module_from_spec(spec)
+                spec.loader.exec_module(loaded_script)
+                sys.modules[script_name] = loaded_script
+
+                loaded_class = getattr(loaded_script, script_name)
+                temp_object = loaded_class()
+                Logger.log("d", "Begin loading of script: %s", script_name)
+                try:
+                    setting_data = temp_object.getSettingData()
+                    if "name" in setting_data and "key" in setting_data:
+                        self._script_labels[setting_data["key"]] = setting_data["name"]
+                        self._loaded_scripts[setting_data["key"]] = loaded_class
+                    else:
+                        Logger.log("w", "Script %s.py has no name or key", script_name)
+                        self._script_labels[script_name] = script_name
+                        self._loaded_scripts[script_name] = loaded_class
+                except AttributeError:
+                    Logger.log("e", "Script %s.py is not a recognised script type. Ensure it inherits Script", script_name)
+                except NotImplementedError:
+                    Logger.log("e", "Script %s.py has no implemented settings", script_name)
+        self.loadedScriptListChanged.emit()
+
+    loadedScriptListChanged = pyqtSignal()
+    @pyqtProperty("QVariantList", notify = loadedScriptListChanged)
+    def loadedScriptList(self):
+        return sorted(list(self._loaded_scripts.keys()))
+
+    @pyqtSlot(str, result = str)
+    def getScriptLabelByKey(self, key):
+        return self._script_labels[key]
+
+    scriptListChanged = pyqtSignal()
+    @pyqtProperty("QVariantList", notify = scriptListChanged)
+    def scriptList(self):
+        script_list = [script.getSettingData()["key"] for script in self._script_list]
+        return script_list
+
+    @pyqtSlot(str)
+    def addScriptToList(self, key):
+        Logger.log("d", "Adding script %s to list.", key)
+        new_script = self._loaded_scripts[key]()
+        self._script_list.append(new_script)
+        self.setSelectedScriptIndex(len(self._script_list) - 1)
+        self.scriptListChanged.emit()
+        self._propertyChanged()
+
+    ##  Creates the view used by show popup. The view is saved because of the fairly aggressive garbage collection.
+    def _createView(self):
+        Logger.log("d", "Creating post processing plugin view.")
+
+        ## Load all scripts in the scripts folders
+        for root in [PluginRegistry.getInstance().getPluginPath("PostProcessingPlugin"), Resources.getStoragePath(Resources.Preferences)]:
+            try:
+                path = os.path.join(root, "scripts")
+                if not os.path.isdir(path):
+                    try:
+                        os.makedirs(path)
+                    except OSError:
+                        Logger.log("w", "Unable to create a folder for scripts: " + path)
+                        continue
+
+                self.loadAllScripts(path)
+            except Exception as e:
+                Logger.logException("e", "Exception occurred while loading post processing plugin: {error_msg}".format(error_msg = str(e)))
+
+        # Create the plugin dialog component
+        path = os.path.join(PluginRegistry.getInstance().getPluginPath("PostProcessingPlugin"), "PostProcessingPlugin.qml")
+        self._view = Application.getInstance().createQmlComponent(path, {"manager": self})
+        Logger.log("d", "Post processing view created.")
+
+        # Create the save button component
+        Application.getInstance().addAdditionalComponent("saveButton", self._view.findChild(QObject, "postProcessingSaveAreaButton"))
+
+    ##  Show the (GUI) popup of the post processing plugin.
+    def showPopup(self):
+        if self._view is None:
+            self._createView()
+        self._view.show()
+
+    ##  Property changed: trigger re-slice
+    #   To do this we use the global container stack propertyChanged.
+    #   Re-slicing is necessary for setting changes in this plugin, because the changes
+    #   are applied only once per "fresh" gcode
+    def _propertyChanged(self):
+        global_container_stack = Application.getInstance().getGlobalContainerStack()
+        global_container_stack.propertyChanged.emit("post_processing_plugin", "value")
+
+

+ 501 - 0
plugins/PostProcessingPlugin/PostProcessingPlugin.qml

@@ -0,0 +1,501 @@
+// Copyright (c) 2015 Jaime van Kessel, Ultimaker B.V.
+// The PostProcessingPlugin is released under the terms of the AGPLv3 or higher.
+
+import QtQuick 2.2
+import QtQuick.Controls 1.1
+import QtQuick.Controls.Styles 1.1
+import QtQuick.Layouts 1.1
+import QtQuick.Dialogs 1.1
+import QtQuick.Window 2.2
+
+import UM 1.2 as UM
+import Cura 1.0 as Cura
+
+UM.Dialog
+{
+    id: dialog
+
+    title: catalog.i18nc("@title:window", "Post Processing Plugin")
+    width: 700 * screenScaleFactor;
+    height: 500 * screenScaleFactor;
+    minimumWidth: 400 * screenScaleFactor;
+    minimumHeight: 250 * screenScaleFactor;
+
+    Item
+    {
+        UM.I18nCatalog{id: catalog; name:"cura"}
+        id: base
+        property int columnWidth: Math.floor((base.width / 2) - UM.Theme.getSize("default_margin").width)
+        property int textMargin: Math.floor(UM.Theme.getSize("default_margin").width / 2)
+        property string activeScriptName
+        SystemPalette{ id: palette }
+        SystemPalette{ id: disabledPalette; colorGroup: SystemPalette.Disabled }
+        anchors.fill: parent
+
+        ExclusiveGroup
+        {
+            id: selectedScriptGroup
+        }
+        Item
+        {
+            id: activeScripts
+            anchors.left: parent.left
+            width: base.columnWidth
+            height: parent.height
+
+            Label
+            {
+                id: activeScriptsHeader
+                text: catalog.i18nc("@label", "Post Processing Scripts")
+                anchors.top: parent.top
+                anchors.topMargin: base.textMargin
+                anchors.left: parent.left
+                anchors.leftMargin: base.textMargin
+                anchors.right: parent.right
+                anchors.rightMargin: base.textMargin
+                font: UM.Theme.getFont("large")
+            }
+            ListView
+            {
+                id: activeScriptsList
+                anchors.top: activeScriptsHeader.bottom
+                anchors.topMargin: base.textMargin
+                anchors.left: parent.left
+                anchors.leftMargin: UM.Theme.getSize("default_margin").width
+                anchors.right: parent.right
+                anchors.rightMargin: base.textMargin
+                height: childrenRect.height
+                model: manager.scriptList
+                delegate: Item
+                {
+                    width: parent.width
+                    height: activeScriptButton.height
+                    Button
+                    {
+                        id: activeScriptButton
+                        text: manager.getScriptLabelByKey(modelData.toString())
+                        exclusiveGroup: selectedScriptGroup
+                        checkable: true
+                        checked: {
+                            if (manager.selectedScriptIndex == index)
+                            {
+                                base.activeScriptName = manager.getScriptLabelByKey(modelData.toString())
+                                return true
+                            }
+                            else
+                            {
+                                return false
+                            }
+                        }
+                        onClicked:
+                        {
+                            forceActiveFocus()
+                            manager.setSelectedScriptIndex(index)
+                            base.activeScriptName = manager.getScriptLabelByKey(modelData.toString())
+                        }
+                        width: parent.width
+                        height: UM.Theme.getSize("setting").height
+                        style: ButtonStyle
+                        {
+                            background: Rectangle
+                            {
+                                color: activeScriptButton.checked ? palette.highlight : "transparent"
+                                width: parent.width
+                                height: parent.height
+                            }
+                            label: Label
+                            {
+                                wrapMode: Text.Wrap
+                                text: control.text
+                                color: activeScriptButton.checked ? palette.highlightedText : palette.text
+                            }
+                        }
+                    }
+                    Button
+                    {
+                        id: removeButton
+                        text: "x"
+                        width: 20 * screenScaleFactor
+                        height: 20 * screenScaleFactor
+                        anchors.right:parent.right
+                        anchors.rightMargin: base.textMargin
+                        anchors.verticalCenter: parent.verticalCenter
+                        onClicked: manager.removeScriptByIndex(index)
+                        style: ButtonStyle
+                        {
+                            label: Item
+                            {
+                                UM.RecolorImage
+                                {
+                                    anchors.verticalCenter: parent.verticalCenter
+                                    anchors.horizontalCenter: parent.horizontalCenter
+                                    width: Math.floor(control.width / 2.7)
+                                    height: Math.floor(control.height / 2.7)
+                                    sourceSize.width: width
+                                    sourceSize.height: width
+                                    color: palette.text
+                                    source: UM.Theme.getIcon("cross1")
+                                }
+                            }
+                        }
+                    }
+                    Button
+                    {
+                        id: downButton
+                        text: ""
+                        anchors.right: removeButton.left
+                        anchors.verticalCenter: parent.verticalCenter
+                        enabled: index != manager.scriptList.length - 1
+                        width: 20 * screenScaleFactor
+                        height: 20 * screenScaleFactor
+                        onClicked:
+                        {
+                            if (manager.selectedScriptIndex == index)
+                            {
+                                manager.setSelectedScriptIndex(index + 1)
+                            }
+                            return manager.moveScript(index, index + 1)
+                        }
+                        style: ButtonStyle
+                        {
+                            label: Item
+                            {
+                                UM.RecolorImage
+                                {
+                                    anchors.verticalCenter: parent.verticalCenter
+                                    anchors.horizontalCenter: parent.horizontalCenter
+                                    width: Math.floor(control.width / 2.5)
+                                    height: Math.floor(control.height / 2.5)
+                                    sourceSize.width: width
+                                    sourceSize.height: width
+                                    color: control.enabled ? palette.text : disabledPalette.text
+                                    source: UM.Theme.getIcon("arrow_bottom")
+                                }
+                            }
+                        }
+                    }
+                    Button
+                    {
+                        id: upButton
+                        text: ""
+                        enabled: index != 0
+                        width: 20 * screenScaleFactor
+                        height: 20 * screenScaleFactor
+                        anchors.right: downButton.left
+                        anchors.verticalCenter: parent.verticalCenter
+                        onClicked:
+                        {
+                            if (manager.selectedScriptIndex == index)
+                            {
+                                manager.setSelectedScriptIndex(index - 1)
+                            }
+                            return manager.moveScript(index, index - 1)
+                        }
+                        style: ButtonStyle
+                        {
+                            label: Item
+                             {
+                                UM.RecolorImage
+                                {
+                                    anchors.verticalCenter: parent.verticalCenter
+                                    anchors.horizontalCenter: parent.horizontalCenter
+                                    width: Math.floor(control.width / 2.5)
+                                    height: Math.floor(control.height / 2.5)
+                                    sourceSize.width: width
+                                    sourceSize.height: width
+                                    color: control.enabled ? palette.text : disabledPalette.text
+                                    source: UM.Theme.getIcon("arrow_top")
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+            Button
+            {
+                id: addButton
+                text: catalog.i18nc("@action", "Add a script")
+                anchors.left: parent.left
+                anchors.leftMargin: base.textMargin
+                anchors.top: activeScriptsList.bottom
+                anchors.topMargin: base.textMargin
+                menu: scriptsMenu
+                style: ButtonStyle
+                {
+                    label: Label
+                    {
+                        text: control.text
+                    }
+                }
+            }
+            Menu
+            {
+                id: scriptsMenu
+
+                Instantiator
+                {
+                    model: manager.loadedScriptList
+
+                    MenuItem
+                    {
+                        text: manager.getScriptLabelByKey(modelData.toString())
+                        onTriggered: manager.addScriptToList(modelData.toString())
+                    }
+
+                    onObjectAdded: scriptsMenu.insertItem(index, object);
+                    onObjectRemoved: scriptsMenu.removeItem(object);
+                }
+            }
+        }
+
+        Rectangle
+        {
+            color: UM.Theme.getColor("sidebar")
+            anchors.left: activeScripts.right
+            anchors.leftMargin: UM.Theme.getSize("default_margin").width
+            anchors.right: parent.right
+            height: parent.height
+            id: settingsPanel
+
+            Label
+            {
+                id: scriptSpecsHeader
+                text: manager.selectedScriptIndex == -1 ? catalog.i18nc("@label", "Settings") : base.activeScriptName
+                anchors.top: parent.top
+                anchors.topMargin: base.textMargin
+                anchors.left: parent.left
+                anchors.leftMargin: base.textMargin
+                anchors.right: parent.right
+                anchors.rightMargin: base.textMargin
+                height: 20 * screenScaleFactor
+                font: UM.Theme.getFont("large")
+                color: UM.Theme.getColor("text")
+            }
+
+            ScrollView
+            {
+                id: scrollView
+                anchors.top: scriptSpecsHeader.bottom
+                anchors.topMargin: settingsPanel.textMargin
+                anchors.left: parent.left
+                anchors.right: parent.right
+                anchors.bottom: parent.bottom
+                visible: manager.selectedScriptDefinitionId != ""
+                style: UM.Theme.styles.scrollview;
+
+                ListView
+                {
+                    id: listview
+                    spacing: UM.Theme.getSize("default_lining").height
+                    model: UM.SettingDefinitionsModel
+                    {
+                        id: definitionsModel;
+                        containerId: manager.selectedScriptDefinitionId
+                        showAll: true
+                    }
+                    delegate:Loader
+                    {
+                        id: settingLoader
+
+                        width: parent.width
+                        height:
+                        {
+                            if(provider.properties.enabled == "True")
+                            {
+                                if(model.type != undefined)
+                                {
+                                    return UM.Theme.getSize("section").height;
+                                }
+                                else
+                                {
+                                    return 0;
+                                }
+                            }
+                            else
+                            {
+                                return 0;
+                            }
+
+                        }
+                        Behavior on height { NumberAnimation { duration: 100 } }
+                        opacity: provider.properties.enabled == "True" ? 1 : 0
+                        Behavior on opacity { NumberAnimation { duration: 100 } }
+                        enabled: opacity > 0
+                        property var definition: model
+                        property var settingDefinitionsModel: definitionsModel
+                        property var propertyProvider: provider
+                        property var globalPropertyProvider: inheritStackProvider
+
+                        //Qt5.4.2 and earlier has a bug where this causes a crash: https://bugreports.qt.io/browse/QTBUG-35989
+                        //In addition, while it works for 5.5 and higher, the ordering of the actual combo box drop down changes,
+                        //causing nasty issues when selecting different options. So disable asynchronous loading of enum type completely.
+                        asynchronous: model.type != "enum" && model.type != "extruder"
+
+                        onLoaded: {
+                            settingLoader.item.showRevertButton = false
+                            settingLoader.item.showInheritButton = false
+                            settingLoader.item.showLinkedSettingIcon = false
+                            settingLoader.item.doDepthIndentation = true
+                            settingLoader.item.doQualityUserSettingEmphasis = false
+                        }
+
+                        sourceComponent:
+                        {
+                            switch(model.type)
+                            {
+                                case "int":
+                                    return settingTextField
+                                case "float":
+                                    return settingTextField
+                                case "enum":
+                                    return settingComboBox
+                                case "extruder":
+                                    return settingExtruder
+                                case "bool":
+                                    return settingCheckBox
+                                case "str":
+                                    return settingTextField
+                                case "category":
+                                    return settingCategory
+                                default:
+                                    return settingUnknown
+                            }
+                        }
+
+                        UM.SettingPropertyProvider
+                        {
+                            id: provider
+                            containerStackId: manager.selectedScriptStackId
+                            key: model.key ? model.key : "None"
+                            watchedProperties: [ "value", "enabled", "state", "validationState" ]
+                            storeIndex: 0
+                        }
+
+                        // Specialty provider that only watches global_inherits (we cant filter on what property changed we get events
+                        // so we bypass that to make a dedicated provider).
+                        UM.SettingPropertyProvider
+                        {
+                            id: inheritStackProvider
+                            containerStackId: Cura.MachineManager.activeMachineId
+                            key: model.key ? model.key : "None"
+                            watchedProperties: [ "limit_to_extruder" ]
+                        }
+
+                        Connections
+                        {
+                            target: item
+
+                            onShowTooltip:
+                            {
+                                tooltip.text = text;
+                                var position = settingLoader.mapToItem(settingsPanel, settingsPanel.x, 0);
+                                tooltip.show(position);
+                                tooltip.target.x = position.x + 1
+                            }
+
+                            onHideTooltip:
+                            {
+                                tooltip.hide();
+                            }
+                        }
+
+                    }
+                }
+            }
+        }
+
+        Cura.SidebarTooltip
+        {
+            id: tooltip
+        }
+
+        Component
+        {
+            id: settingTextField;
+
+            Cura.SettingTextField { }
+        }
+
+        Component
+        {
+            id: settingComboBox;
+
+            Cura.SettingComboBox { }
+        }
+
+        Component
+        {
+            id: settingExtruder;
+
+            Cura.SettingExtruder { }
+        }
+
+        Component
+        {
+            id: settingCheckBox;
+
+            Cura.SettingCheckBox { }
+        }
+
+        Component
+        {
+            id: settingCategory;
+
+            Cura.SettingCategory { }
+        }
+
+        Component
+        {
+            id: settingUnknown;
+
+            Cura.SettingUnknown { }
+        }
+    }
+    rightButtons: Button
+    {
+        text: catalog.i18nc("@action:button", "Close")
+        iconName: "dialog-close"
+        onClicked: dialog.accept()
+    }
+
+    Button {
+        objectName: "postProcessingSaveAreaButton"
+        visible: activeScriptsList.count > 0
+        height: UM.Theme.getSize("save_button_save_to_button").height
+        width: height
+        tooltip: catalog.i18nc("@info:tooltip", "Change active post-processing scripts")
+        onClicked: dialog.show()
+
+        style: ButtonStyle {
+            background: Rectangle {
+                id: deviceSelectionIcon
+                border.width: UM.Theme.getSize("default_lining").width
+                border.color: !control.enabled ? UM.Theme.getColor("action_button_disabled_border") :
+                                  control.pressed ? UM.Theme.getColor("action_button_active_border") :
+                                  control.hovered ? UM.Theme.getColor("action_button_hovered_border") : UM.Theme.getColor("action_button_border")
+                color: !control.enabled ? UM.Theme.getColor("action_button_disabled") :
+                           control.pressed ? UM.Theme.getColor("action_button_active") :
+                           control.hovered ? UM.Theme.getColor("action_button_hovered") : UM.Theme.getColor("action_button")
+                Behavior on color { ColorAnimation { duration: 50; } }
+                anchors.left: parent.left
+                anchors.leftMargin: Math.floor(UM.Theme.getSize("save_button_text_margin").width / 2);
+                width: parent.height
+                height: parent.height
+
+                UM.RecolorImage {
+                    anchors.verticalCenter: parent.verticalCenter
+                    anchors.horizontalCenter: parent.horizontalCenter
+                    width: Math.floor(parent.width / 2)
+                    height: Math.floor(parent.height / 2)
+                    sourceSize.width: width
+                    sourceSize.height: height
+                    color: !control.enabled ? UM.Theme.getColor("action_button_disabled_text") :
+                               control.pressed ? UM.Theme.getColor("action_button_active_text") :
+                               control.hovered ? UM.Theme.getColor("action_button_hovered_text") : UM.Theme.getColor("action_button_text");
+                    source: "postprocessing.svg"
+                }
+            }
+            label: Label{ }
+        }
+    }
+}

+ 2 - 0
plugins/PostProcessingPlugin/README.md

@@ -0,0 +1,2 @@
+# PostProcessingPlugin
+A post processing plugin for Cura 

+ 111 - 0
plugins/PostProcessingPlugin/Script.py

@@ -0,0 +1,111 @@
+# Copyright (c) 2015 Jaime van Kessel
+# Copyright (c) 2017 Ultimaker B.V.
+# The PostProcessingPlugin is released under the terms of the AGPLv3 or higher.
+from UM.Logger import Logger
+from UM.Signal import Signal, signalemitter
+from UM.i18n import i18nCatalog
+
+# Setting stuff import
+from UM.Application import Application
+from UM.Settings.ContainerStack import ContainerStack
+from UM.Settings.InstanceContainer import InstanceContainer
+from UM.Settings.DefinitionContainer import DefinitionContainer
+from UM.Settings.ContainerRegistry import ContainerRegistry
+
+import re
+import json
+import collections
+i18n_catalog = i18nCatalog("cura")
+
+
+## Base class for scripts. All scripts should inherit the script class.
+@signalemitter
+class Script:
+    def __init__(self):
+        super().__init__()
+        self._settings = None
+        self._stack = None
+
+        setting_data = self.getSettingData()
+        self._stack = ContainerStack(stack_id = str(id(self)))
+        self._stack.setDirty(False)  # This stack does not need to be saved.
+
+
+        ## Check if the definition of this script already exists. If not, add it to the registry.
+        if "key" in setting_data:
+            definitions = ContainerRegistry.getInstance().findDefinitionContainers(id = setting_data["key"])
+            if definitions:
+                # Definition was found
+                self._definition = definitions[0]
+            else:
+                self._definition = DefinitionContainer(setting_data["key"])
+                self._definition.deserialize(json.dumps(setting_data))
+                ContainerRegistry.getInstance().addContainer(self._definition)
+        self._stack.addContainer(self._definition)
+        self._instance = InstanceContainer(container_id="ScriptInstanceContainer")
+        self._instance.setDefinition(self._definition.getId())
+        self._instance.addMetaDataEntry("setting_version", self._definition.getMetaDataEntry("setting_version", default = 0))
+        self._stack.addContainer(self._instance)
+        self._stack.propertyChanged.connect(self._onPropertyChanged)
+
+        ContainerRegistry.getInstance().addContainer(self._stack)
+
+    settingsLoaded = Signal()
+    valueChanged = Signal()  # Signal emitted whenever a value of a setting is changed
+
+    def _onPropertyChanged(self, key, property_name):
+        if property_name == "value":
+            self.valueChanged.emit()
+
+            # Property changed: trigger reslice
+            # To do this we use the global container stack propertyChanged.
+            # Reslicing is necessary for setting changes in this plugin, because the changes
+            # are applied only once per "fresh" gcode
+            global_container_stack = Application.getInstance().getGlobalContainerStack()
+            global_container_stack.propertyChanged.emit(key, property_name)
+
+    ##  Needs to return a dict that can be used to construct a settingcategory file.
+    #   See the example script for an example.
+    #   It follows the same style / guides as the Uranium settings.
+    #   Scripts can either override getSettingData directly, or use getSettingDataString
+    #   to return a string that will be parsed as json. The latter has the benefit over
+    #   returning a dict in that the order of settings is maintained.
+    def getSettingData(self):
+        setting_data = self.getSettingDataString()
+        if type(setting_data) == str:
+            setting_data = json.loads(setting_data, object_pairs_hook = collections.OrderedDict)
+        return setting_data
+
+    def getSettingDataString(self):
+        raise NotImplementedError()
+
+    def getDefinitionId(self):
+        if self._stack:
+            return self._stack.getBottom().getId()
+
+    def getStackId(self):
+        if self._stack:
+            return self._stack.getId()
+
+    ##  Convenience function that retrieves value of a setting from the stack.
+    def getSettingValueByKey(self, key):
+        return self._stack.getProperty(key, "value")
+
+    ##  Convenience function that finds the value in a line of g-code.
+    #   When requesting key = x from line "G1 X100" the value 100 is returned.
+    def getValue(self, line, key, default = None):
+        if not key in line or (';' in line and line.find(key) > line.find(';')):
+            return default
+        sub_part = line[line.find(key) + 1:]
+        m = re.search('^-?[0-9]+\.?[0-9]*', sub_part)
+        if m is None:
+            return default
+        try:
+            return float(m.group(0))
+        except:
+            return default
+
+    ##  This is called when the script is executed. 
+    #   It gets a list of g-code strings and needs to return a (modified) list.
+    def execute(self, data):
+        raise NotImplementedError()

+ 11 - 0
plugins/PostProcessingPlugin/__init__.py

@@ -0,0 +1,11 @@
+# Copyright (c) 2015 Jaime van Kessel, Ultimaker B.V.
+# The PostProcessingPlugin is released under the terms of the AGPLv3 or higher.
+
+from . import PostProcessingPlugin
+from UM.i18n import i18nCatalog
+catalog = i18nCatalog("cura")
+def getMetaData():
+    return {}
+        
+def register(app):
+    return {"extension": PostProcessingPlugin.PostProcessingPlugin()}

+ 8 - 0
plugins/PostProcessingPlugin/plugin.json

@@ -0,0 +1,8 @@
+{
+    "name": "Post Processing",
+    "author": "Ultimaker",
+    "version": "2.2",
+    "api": 4,
+    "description": "Extension that allows for user created scripts for post processing",
+    "catalog": "cura"
+}

Разница между файлами не показана из-за своего большого размера
+ 45 - 0
plugins/PostProcessingPlugin/postprocessing.svg


Некоторые файлы не были показаны из-за большого количества измененных файлов