mirror of
https://github.com/Ultimaker/Cura.git
synced 2025-07-06 14:37:29 -06:00
Typing fixes
Since I was stupid enough to touch it, I was also forced to boyscout the code.
This commit is contained in:
parent
2e529452dd
commit
adf8285d20
2 changed files with 115 additions and 80 deletions
|
@ -2,6 +2,7 @@
|
||||||
# The PostProcessingPlugin is released under the terms of the AGPLv3 or higher.
|
# The PostProcessingPlugin is released under the terms of the AGPLv3 or higher.
|
||||||
|
|
||||||
from PyQt5.QtCore import QObject, pyqtProperty, pyqtSignal, pyqtSlot
|
from PyQt5.QtCore import QObject, pyqtProperty, pyqtSignal, pyqtSlot
|
||||||
|
from typing import Dict, Type, TYPE_CHECKING, List, Optional, cast
|
||||||
|
|
||||||
from UM.PluginRegistry import PluginRegistry
|
from UM.PluginRegistry import PluginRegistry
|
||||||
from UM.Resources import Resources
|
from UM.Resources import Resources
|
||||||
|
@ -9,55 +10,62 @@ from UM.Application import Application
|
||||||
from UM.Extension import Extension
|
from UM.Extension import Extension
|
||||||
from UM.Logger import Logger
|
from UM.Logger import Logger
|
||||||
|
|
||||||
import configparser #The script lists are stored in metadata as serialised config files.
|
import configparser # The script lists are stored in metadata as serialised config files.
|
||||||
import io #To allow configparser to write to a string.
|
import io # To allow configparser to write to a string.
|
||||||
import os.path
|
import os.path
|
||||||
import pkgutil
|
import pkgutil
|
||||||
import sys
|
import sys
|
||||||
import importlib.util
|
import importlib.util
|
||||||
|
|
||||||
from UM.i18n import i18nCatalog
|
from UM.i18n import i18nCatalog
|
||||||
|
from cura.CuraApplication import CuraApplication
|
||||||
|
|
||||||
i18n_catalog = i18nCatalog("cura")
|
i18n_catalog = i18nCatalog("cura")
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from .Script import Script
|
||||||
|
|
||||||
|
|
||||||
## The post processing plugin is an Extension type plugin that enables pre-written scripts to post process generated
|
## The post processing plugin is an Extension type plugin that enables pre-written scripts to post process generated
|
||||||
# g-code files.
|
# g-code files.
|
||||||
class PostProcessingPlugin(QObject, Extension):
|
class PostProcessingPlugin(QObject, Extension):
|
||||||
def __init__(self, parent = None):
|
def __init__(self, parent = None) -> None:
|
||||||
super().__init__(parent)
|
QObject.__init__(self, parent)
|
||||||
|
Extension.__init__(self)
|
||||||
self.addMenuItem(i18n_catalog.i18n("Modify G-Code"), self.showPopup)
|
self.addMenuItem(i18n_catalog.i18n("Modify G-Code"), self.showPopup)
|
||||||
self._view = None
|
self._view = None
|
||||||
|
|
||||||
# Loaded scripts are all scripts that can be used
|
# Loaded scripts are all scripts that can be used
|
||||||
self._loaded_scripts = {}
|
self._loaded_scripts = {} # type: Dict[str, Type[Script]]
|
||||||
self._script_labels = {}
|
self._script_labels = {} # type: Dict[str, str]
|
||||||
|
|
||||||
# Script list contains instances of scripts in loaded_scripts.
|
# Script list contains instances of scripts in loaded_scripts.
|
||||||
# There can be duplicates, which will be executed in sequence.
|
# There can be duplicates, which will be executed in sequence.
|
||||||
self._script_list = []
|
self._script_list = [] # type: List[Script]
|
||||||
self._selected_script_index = -1
|
self._selected_script_index = -1
|
||||||
|
|
||||||
Application.getInstance().getOutputDeviceManager().writeStarted.connect(self.execute)
|
Application.getInstance().getOutputDeviceManager().writeStarted.connect(self.execute)
|
||||||
Application.getInstance().globalContainerStackChanged.connect(self._onGlobalContainerStackChanged) #When the current printer changes, update the list of scripts.
|
Application.getInstance().globalContainerStackChanged.connect(self._onGlobalContainerStackChanged) # When the current printer changes, update the list of scripts.
|
||||||
Application.getInstance().mainWindowChanged.connect(self._createView) #When the main window is created, create the view so that we can display the post-processing icon if necessary.
|
CuraApplication.getInstance().mainWindowChanged.connect(self._createView) # When the main window is created, create the view so that we can display the post-processing icon if necessary.
|
||||||
|
|
||||||
selectedIndexChanged = pyqtSignal()
|
selectedIndexChanged = pyqtSignal()
|
||||||
@pyqtProperty("QVariant", notify = selectedIndexChanged)
|
|
||||||
def selectedScriptDefinitionId(self):
|
@pyqtProperty(str, notify = selectedIndexChanged)
|
||||||
|
def selectedScriptDefinitionId(self) -> Optional[str]:
|
||||||
try:
|
try:
|
||||||
return self._script_list[self._selected_script_index].getDefinitionId()
|
return self._script_list[self._selected_script_index].getDefinitionId()
|
||||||
except:
|
except:
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
@pyqtProperty("QVariant", notify=selectedIndexChanged)
|
@pyqtProperty(str, notify=selectedIndexChanged)
|
||||||
def selectedScriptStackId(self):
|
def selectedScriptStackId(self) -> Optional[str]:
|
||||||
try:
|
try:
|
||||||
return self._script_list[self._selected_script_index].getStackId()
|
return self._script_list[self._selected_script_index].getStackId()
|
||||||
except:
|
except:
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
## Execute all post-processing scripts on the gcode.
|
## Execute all post-processing scripts on the gcode.
|
||||||
def execute(self, output_device):
|
def execute(self, output_device) -> None:
|
||||||
scene = Application.getInstance().getController().getScene()
|
scene = Application.getInstance().getController().getScene()
|
||||||
# If the scene does not have a gcode, do nothing
|
# If the scene does not have a gcode, do nothing
|
||||||
if not hasattr(scene, "gcode_dict"):
|
if not hasattr(scene, "gcode_dict"):
|
||||||
|
@ -67,7 +75,7 @@ class PostProcessingPlugin(QObject, Extension):
|
||||||
return
|
return
|
||||||
|
|
||||||
# get gcode list for the active build plate
|
# get gcode list for the active build plate
|
||||||
active_build_plate_id = Application.getInstance().getMultiBuildPlateModel().activeBuildPlate
|
active_build_plate_id = CuraApplication.getInstance().getMultiBuildPlateModel().activeBuildPlate
|
||||||
gcode_list = gcode_dict[active_build_plate_id]
|
gcode_list = gcode_dict[active_build_plate_id]
|
||||||
if not gcode_list:
|
if not gcode_list:
|
||||||
return
|
return
|
||||||
|
@ -86,16 +94,17 @@ class PostProcessingPlugin(QObject, Extension):
|
||||||
Logger.log("e", "Already post processed")
|
Logger.log("e", "Already post processed")
|
||||||
|
|
||||||
@pyqtSlot(int)
|
@pyqtSlot(int)
|
||||||
def setSelectedScriptIndex(self, index):
|
def setSelectedScriptIndex(self, index: int) -> None:
|
||||||
|
if self._selected_script_index != index:
|
||||||
self._selected_script_index = index
|
self._selected_script_index = index
|
||||||
self.selectedIndexChanged.emit()
|
self.selectedIndexChanged.emit()
|
||||||
|
|
||||||
@pyqtProperty(int, notify = selectedIndexChanged)
|
@pyqtProperty(int, notify = selectedIndexChanged)
|
||||||
def selectedScriptIndex(self):
|
def selectedScriptIndex(self) -> int:
|
||||||
return self._selected_script_index
|
return self._selected_script_index
|
||||||
|
|
||||||
@pyqtSlot(int, int)
|
@pyqtSlot(int, int)
|
||||||
def moveScript(self, index, new_index):
|
def moveScript(self, index: int, new_index: int) -> None:
|
||||||
if new_index < 0 or new_index > len(self._script_list) - 1:
|
if new_index < 0 or new_index > len(self._script_list) - 1:
|
||||||
return # nothing needs to be done
|
return # nothing needs to be done
|
||||||
else:
|
else:
|
||||||
|
@ -107,7 +116,7 @@ class PostProcessingPlugin(QObject, Extension):
|
||||||
|
|
||||||
## Remove a script from the active script list by index.
|
## Remove a script from the active script list by index.
|
||||||
@pyqtSlot(int)
|
@pyqtSlot(int)
|
||||||
def removeScriptByIndex(self, index):
|
def removeScriptByIndex(self, index: int) -> None:
|
||||||
self._script_list.pop(index)
|
self._script_list.pop(index)
|
||||||
if len(self._script_list) - 1 < self._selected_script_index:
|
if len(self._script_list) - 1 < self._selected_script_index:
|
||||||
self._selected_script_index = len(self._script_list) - 1
|
self._selected_script_index = len(self._script_list) - 1
|
||||||
|
@ -118,14 +127,16 @@ class PostProcessingPlugin(QObject, Extension):
|
||||||
## Load all scripts from all paths where scripts can be found.
|
## Load all scripts from all paths where scripts can be found.
|
||||||
#
|
#
|
||||||
# This should probably only be done on init.
|
# This should probably only be done on init.
|
||||||
def loadAllScripts(self):
|
def loadAllScripts(self) -> None:
|
||||||
if self._loaded_scripts: #Already loaded.
|
if self._loaded_scripts: # Already loaded.
|
||||||
return
|
return
|
||||||
|
|
||||||
#The PostProcessingPlugin path is for built-in scripts.
|
# The PostProcessingPlugin path is for built-in scripts.
|
||||||
#The Resources path is where the user should store custom scripts.
|
# The Resources path is where the user should store custom scripts.
|
||||||
#The Preferences path is legacy, where the user may previously have stored scripts.
|
# The Preferences path is legacy, where the user may previously have stored scripts.
|
||||||
for root in [PluginRegistry.getInstance().getPluginPath("PostProcessingPlugin"), Resources.getStoragePath(Resources.Resources), Resources.getStoragePath(Resources.Preferences)]:
|
for root in [PluginRegistry.getInstance().getPluginPath("PostProcessingPlugin"), Resources.getStoragePath(Resources.Resources), Resources.getStoragePath(Resources.Preferences)]:
|
||||||
|
if root is None:
|
||||||
|
continue
|
||||||
path = os.path.join(root, "scripts")
|
path = os.path.join(root, "scripts")
|
||||||
if not os.path.isdir(path):
|
if not os.path.isdir(path):
|
||||||
try:
|
try:
|
||||||
|
@ -139,7 +150,7 @@ class PostProcessingPlugin(QObject, Extension):
|
||||||
## Load all scripts from provided path.
|
## Load all scripts from provided path.
|
||||||
# This should probably only be done on init.
|
# This should probably only be done on init.
|
||||||
# \param path Path to check for scripts.
|
# \param path Path to check for scripts.
|
||||||
def loadScripts(self, path):
|
def loadScripts(self, path: str) -> None:
|
||||||
## Load all scripts in the scripts folders
|
## Load all scripts in the scripts folders
|
||||||
scripts = pkgutil.iter_modules(path = [path])
|
scripts = pkgutil.iter_modules(path = [path])
|
||||||
for loader, script_name, ispkg in scripts:
|
for loader, script_name, ispkg in scripts:
|
||||||
|
@ -148,6 +159,8 @@ class PostProcessingPlugin(QObject, Extension):
|
||||||
try:
|
try:
|
||||||
spec = importlib.util.spec_from_file_location(__name__ + "." + script_name, os.path.join(path, script_name + ".py"))
|
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)
|
loaded_script = importlib.util.module_from_spec(spec)
|
||||||
|
if spec.loader is None:
|
||||||
|
continue
|
||||||
spec.loader.exec_module(loaded_script)
|
spec.loader.exec_module(loaded_script)
|
||||||
sys.modules[script_name] = loaded_script #TODO: This could be a security risk. Overwrite any module with a user-provided name?
|
sys.modules[script_name] = loaded_script #TODO: This could be a security risk. Overwrite any module with a user-provided name?
|
||||||
|
|
||||||
|
@ -172,21 +185,21 @@ class PostProcessingPlugin(QObject, Extension):
|
||||||
|
|
||||||
loadedScriptListChanged = pyqtSignal()
|
loadedScriptListChanged = pyqtSignal()
|
||||||
@pyqtProperty("QVariantList", notify = loadedScriptListChanged)
|
@pyqtProperty("QVariantList", notify = loadedScriptListChanged)
|
||||||
def loadedScriptList(self):
|
def loadedScriptList(self) -> List[str]:
|
||||||
return sorted(list(self._loaded_scripts.keys()))
|
return sorted(list(self._loaded_scripts.keys()))
|
||||||
|
|
||||||
@pyqtSlot(str, result = str)
|
@pyqtSlot(str, result = str)
|
||||||
def getScriptLabelByKey(self, key):
|
def getScriptLabelByKey(self, key: str) -> Optional[str]:
|
||||||
return self._script_labels[key]
|
return self._script_labels.get(key)
|
||||||
|
|
||||||
scriptListChanged = pyqtSignal()
|
scriptListChanged = pyqtSignal()
|
||||||
@pyqtProperty("QVariantList", notify = scriptListChanged)
|
@pyqtProperty("QStringList", notify = scriptListChanged)
|
||||||
def scriptList(self):
|
def scriptList(self) -> List[str]:
|
||||||
script_list = [script.getSettingData()["key"] for script in self._script_list]
|
script_list = [script.getSettingData()["key"] for script in self._script_list]
|
||||||
return script_list
|
return script_list
|
||||||
|
|
||||||
@pyqtSlot(str)
|
@pyqtSlot(str)
|
||||||
def addScriptToList(self, key):
|
def addScriptToList(self, key: str) -> None:
|
||||||
Logger.log("d", "Adding script %s to list.", key)
|
Logger.log("d", "Adding script %s to list.", key)
|
||||||
new_script = self._loaded_scripts[key]()
|
new_script = self._loaded_scripts[key]()
|
||||||
new_script.initialize()
|
new_script.initialize()
|
||||||
|
@ -197,32 +210,35 @@ class PostProcessingPlugin(QObject, Extension):
|
||||||
|
|
||||||
## When the global container stack is changed, swap out the list of active
|
## When the global container stack is changed, swap out the list of active
|
||||||
# scripts.
|
# scripts.
|
||||||
def _onGlobalContainerStackChanged(self):
|
def _onGlobalContainerStackChanged(self) -> None:
|
||||||
self.loadAllScripts()
|
self.loadAllScripts()
|
||||||
new_stack = Application.getInstance().getGlobalContainerStack()
|
new_stack = Application.getInstance().getGlobalContainerStack()
|
||||||
|
if new_stack is None:
|
||||||
|
return
|
||||||
self._script_list.clear()
|
self._script_list.clear()
|
||||||
if not new_stack.getMetaDataEntry("post_processing_scripts"): #Missing or empty.
|
if not new_stack.getMetaDataEntry("post_processing_scripts"): # Missing or empty.
|
||||||
self.scriptListChanged.emit() #Even emit this if it didn't change. We want it to write the empty list to the stack's metadata.
|
self.scriptListChanged.emit() # Even emit this if it didn't change. We want it to write the empty list to the stack's metadata.
|
||||||
return
|
return
|
||||||
|
|
||||||
self._script_list.clear()
|
self._script_list.clear()
|
||||||
scripts_list_strs = new_stack.getMetaDataEntry("post_processing_scripts")
|
scripts_list_strs = new_stack.getMetaDataEntry("post_processing_scripts")
|
||||||
for script_str in scripts_list_strs.split("\n"): #Encoded config files should never contain three newlines in a row. At most 2, just before section headers.
|
for script_str in scripts_list_strs.split("\n"): # Encoded config files should never contain three newlines in a row. At most 2, just before section headers.
|
||||||
if not script_str: #There were no scripts in this one (or a corrupt file caused more than 3 consecutive newlines here).
|
if not script_str: # There were no scripts in this one (or a corrupt file caused more than 3 consecutive newlines here).
|
||||||
continue
|
continue
|
||||||
script_str = script_str.replace(r"\\\n", "\n").replace(r"\\\\", "\\\\") #Unescape escape sequences.
|
script_str = script_str.replace(r"\\\n", "\n").replace(r"\\\\", "\\\\") # Unescape escape sequences.
|
||||||
script_parser = configparser.ConfigParser(interpolation = None)
|
script_parser = configparser.ConfigParser(interpolation = None)
|
||||||
script_parser.optionxform = str #Don't transform the setting keys as they are case-sensitive.
|
script_parser.optionxform = str # type: ignore # Don't transform the setting keys as they are case-sensitive.
|
||||||
script_parser.read_string(script_str)
|
script_parser.read_string(script_str)
|
||||||
for script_name, settings in script_parser.items(): #There should only be one, really! Otherwise we can't guarantee the order or allow multiple uses of the same script.
|
for script_name, settings in script_parser.items(): # There should only be one, really! Otherwise we can't guarantee the order or allow multiple uses of the same script.
|
||||||
if script_name == "DEFAULT": #ConfigParser always has a DEFAULT section, but we don't fill it. Ignore this one.
|
if script_name == "DEFAULT": # ConfigParser always has a DEFAULT section, but we don't fill it. Ignore this one.
|
||||||
continue
|
continue
|
||||||
if script_name not in self._loaded_scripts: #Don't know this post-processing plug-in.
|
if script_name not in self._loaded_scripts: # Don't know this post-processing plug-in.
|
||||||
Logger.log("e", "Unknown post-processing script {script_name} was encountered in this global stack.".format(script_name = script_name))
|
Logger.log("e", "Unknown post-processing script {script_name} was encountered in this global stack.".format(script_name = script_name))
|
||||||
continue
|
continue
|
||||||
new_script = self._loaded_scripts[script_name]()
|
new_script = self._loaded_scripts[script_name]()
|
||||||
new_script.initialize()
|
new_script.initialize()
|
||||||
for setting_key, setting_value in settings.items(): #Put all setting values into the script.
|
for setting_key, setting_value in settings.items(): # Put all setting values into the script.
|
||||||
|
if new_script._instance is not None:
|
||||||
new_script._instance.setProperty(setting_key, "value", setting_value)
|
new_script._instance.setProperty(setting_key, "value", setting_value)
|
||||||
self._script_list.append(new_script)
|
self._script_list.append(new_script)
|
||||||
|
|
||||||
|
@ -230,49 +246,53 @@ class PostProcessingPlugin(QObject, Extension):
|
||||||
self.scriptListChanged.emit()
|
self.scriptListChanged.emit()
|
||||||
|
|
||||||
@pyqtSlot()
|
@pyqtSlot()
|
||||||
def writeScriptsToStack(self):
|
def writeScriptsToStack(self) -> None:
|
||||||
script_list_strs = []
|
script_list_strs = [] # type: List[str]
|
||||||
for script in self._script_list:
|
for script in self._script_list:
|
||||||
parser = configparser.ConfigParser(interpolation = None) #We'll encode the script as a config with one section. The section header is the key and its values are the settings.
|
parser = configparser.ConfigParser(interpolation = None) # We'll encode the script as a config with one section. The section header is the key and its values are the settings.
|
||||||
parser.optionxform = str #Don't transform the setting keys as they are case-sensitive.
|
parser.optionxform = str # type: ignore # Don't transform the setting keys as they are case-sensitive.
|
||||||
script_name = script.getSettingData()["key"]
|
script_name = script.getSettingData()["key"]
|
||||||
parser.add_section(script_name)
|
parser.add_section(script_name)
|
||||||
for key in script.getSettingData()["settings"]:
|
for key in script.getSettingData()["settings"]:
|
||||||
value = script.getSettingValueByKey(key)
|
value = script.getSettingValueByKey(key)
|
||||||
parser[script_name][key] = str(value)
|
parser[script_name][key] = str(value)
|
||||||
serialized = io.StringIO() #ConfigParser can only write to streams. Fine.
|
serialized = io.StringIO() # ConfigParser can only write to streams. Fine.
|
||||||
parser.write(serialized)
|
parser.write(serialized)
|
||||||
serialized.seek(0)
|
serialized.seek(0)
|
||||||
script_str = serialized.read()
|
script_str = serialized.read()
|
||||||
script_str = script_str.replace("\\\\", r"\\\\").replace("\n", r"\\\n") #Escape newlines because configparser sees those as section delimiters.
|
script_str = script_str.replace("\\\\", r"\\\\").replace("\n", r"\\\n") # Escape newlines because configparser sees those as section delimiters.
|
||||||
script_list_strs.append(script_str)
|
script_list_strs.append(script_str)
|
||||||
|
|
||||||
script_list_strs = "\n".join(script_list_strs) #ConfigParser should never output three newlines in a row when serialised, so it's a safe delimiter.
|
script_list_string = "\n".join(script_list_strs) # ConfigParser should never output three newlines in a row when serialised, so it's a safe delimiter.
|
||||||
|
|
||||||
global_stack = Application.getInstance().getGlobalContainerStack()
|
global_stack = Application.getInstance().getGlobalContainerStack()
|
||||||
|
if global_stack is None:
|
||||||
|
return
|
||||||
|
|
||||||
if "post_processing_scripts" not in global_stack.getMetaData():
|
if "post_processing_scripts" not in global_stack.getMetaData():
|
||||||
global_stack.setMetaDataEntry("post_processing_scripts", "")
|
global_stack.setMetaDataEntry("post_processing_scripts", "")
|
||||||
Application.getInstance().getGlobalContainerStack().setMetaDataEntry("post_processing_scripts", script_list_strs)
|
|
||||||
|
global_stack.setMetaDataEntry("post_processing_scripts", script_list_string)
|
||||||
|
|
||||||
## Creates the view used by show popup. The view is saved because of the fairly aggressive garbage collection.
|
## Creates the view used by show popup. The view is saved because of the fairly aggressive garbage collection.
|
||||||
def _createView(self):
|
def _createView(self) -> None:
|
||||||
Logger.log("d", "Creating post processing plugin view.")
|
Logger.log("d", "Creating post processing plugin view.")
|
||||||
|
|
||||||
self.loadAllScripts()
|
self.loadAllScripts()
|
||||||
|
|
||||||
# Create the plugin dialog component
|
# Create the plugin dialog component
|
||||||
path = os.path.join(PluginRegistry.getInstance().getPluginPath("PostProcessingPlugin"), "PostProcessingPlugin.qml")
|
path = os.path.join(cast(str, PluginRegistry.getInstance().getPluginPath("PostProcessingPlugin")), "PostProcessingPlugin.qml")
|
||||||
self._view = Application.getInstance().createQmlComponent(path, {"manager": self})
|
self._view = CuraApplication.getInstance().createQmlComponent(path, {"manager": self})
|
||||||
if self._view is None:
|
if self._view is None:
|
||||||
Logger.log("e", "Not creating PostProcessing button near save button because the QML component failed to be created.")
|
Logger.log("e", "Not creating PostProcessing button near save button because the QML component failed to be created.")
|
||||||
return
|
return
|
||||||
Logger.log("d", "Post processing view created.")
|
Logger.log("d", "Post processing view created.")
|
||||||
|
|
||||||
# Create the save button component
|
# Create the save button component
|
||||||
Application.getInstance().addAdditionalComponent("saveButton", self._view.findChild(QObject, "postProcessingSaveAreaButton"))
|
CuraApplication.getInstance().addAdditionalComponent("saveButton", self._view.findChild(QObject, "postProcessingSaveAreaButton"))
|
||||||
|
|
||||||
## Show the (GUI) popup of the post processing plugin.
|
## Show the (GUI) popup of the post processing plugin.
|
||||||
def showPopup(self):
|
def showPopup(self) -> None:
|
||||||
if self._view is None:
|
if self._view is None:
|
||||||
self._createView()
|
self._createView()
|
||||||
if self._view is None:
|
if self._view is None:
|
||||||
|
@ -284,8 +304,9 @@ class PostProcessingPlugin(QObject, Extension):
|
||||||
# To do this we use the global container stack propertyChanged.
|
# To do this we use the global container stack propertyChanged.
|
||||||
# Re-slicing is necessary for setting changes in this plugin, because the changes
|
# Re-slicing is necessary for setting changes in this plugin, because the changes
|
||||||
# are applied only once per "fresh" gcode
|
# are applied only once per "fresh" gcode
|
||||||
def _propertyChanged(self):
|
def _propertyChanged(self) -> None:
|
||||||
global_container_stack = Application.getInstance().getGlobalContainerStack()
|
global_container_stack = Application.getInstance().getGlobalContainerStack()
|
||||||
|
if global_container_stack is not None:
|
||||||
global_container_stack.propertyChanged.emit("post_processing_plugin", "value")
|
global_container_stack.propertyChanged.emit("post_processing_plugin", "value")
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
# Copyright (c) 2015 Jaime van Kessel
|
# Copyright (c) 2015 Jaime van Kessel
|
||||||
# Copyright (c) 2018 Ultimaker B.V.
|
# Copyright (c) 2018 Ultimaker B.V.
|
||||||
# The PostProcessingPlugin is released under the terms of the AGPLv3 or higher.
|
# The PostProcessingPlugin is released under the terms of the AGPLv3 or higher.
|
||||||
|
from typing import Optional, Any, Dict, TYPE_CHECKING, List
|
||||||
|
|
||||||
from UM.Signal import Signal, signalemitter
|
from UM.Signal import Signal, signalemitter
|
||||||
from UM.i18n import i18nCatalog
|
from UM.i18n import i18nCatalog
|
||||||
|
|
||||||
|
@ -17,16 +19,20 @@ import json
|
||||||
import collections
|
import collections
|
||||||
i18n_catalog = i18nCatalog("cura")
|
i18n_catalog = i18nCatalog("cura")
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from UM.Settings.Interfaces import DefinitionContainerInterface
|
||||||
|
|
||||||
|
|
||||||
## Base class for scripts. All scripts should inherit the script class.
|
## Base class for scripts. All scripts should inherit the script class.
|
||||||
@signalemitter
|
@signalemitter
|
||||||
class Script:
|
class Script:
|
||||||
def __init__(self):
|
def __init__(self) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self._settings = None
|
self._stack = None # type: Optional[ContainerStack]
|
||||||
self._stack = None
|
self._definition = None # type: Optional[DefinitionContainerInterface]
|
||||||
|
self._instance = None # type: Optional[InstanceContainer]
|
||||||
|
|
||||||
def initialize(self):
|
def initialize(self) -> None:
|
||||||
setting_data = self.getSettingData()
|
setting_data = self.getSettingData()
|
||||||
self._stack = ContainerStack(stack_id=str(id(self)))
|
self._stack = ContainerStack(stack_id=str(id(self)))
|
||||||
self._stack.setDirty(False) # This stack does not need to be saved.
|
self._stack.setDirty(False) # This stack does not need to be saved.
|
||||||
|
@ -45,6 +51,8 @@ class Script:
|
||||||
except ContainerFormatError:
|
except ContainerFormatError:
|
||||||
self._definition = None
|
self._definition = None
|
||||||
return
|
return
|
||||||
|
if self._definition is None:
|
||||||
|
return
|
||||||
self._stack.addContainer(self._definition)
|
self._stack.addContainer(self._definition)
|
||||||
self._instance = InstanceContainer(container_id="ScriptInstanceContainer")
|
self._instance = InstanceContainer(container_id="ScriptInstanceContainer")
|
||||||
self._instance.setDefinition(self._definition.getId())
|
self._instance.setDefinition(self._definition.getId())
|
||||||
|
@ -58,15 +66,16 @@ class Script:
|
||||||
settingsLoaded = Signal()
|
settingsLoaded = Signal()
|
||||||
valueChanged = Signal() # Signal emitted whenever a value of a setting is changed
|
valueChanged = Signal() # Signal emitted whenever a value of a setting is changed
|
||||||
|
|
||||||
def _onPropertyChanged(self, key, property_name):
|
def _onPropertyChanged(self, key: str, property_name: str) -> None:
|
||||||
if property_name == "value":
|
if property_name == "value":
|
||||||
self.valueChanged.emit()
|
self.valueChanged.emit()
|
||||||
|
|
||||||
# Property changed: trigger reslice
|
# Property changed: trigger reslice
|
||||||
# To do this we use the global container stack propertyChanged.
|
# To do this we use the global container stack propertyChanged.
|
||||||
# Reslicing is necessary for setting changes in this plugin, because the changes
|
# Re-slicing is necessary for setting changes in this plugin, because the changes
|
||||||
# are applied only once per "fresh" gcode
|
# are applied only once per "fresh" gcode
|
||||||
global_container_stack = Application.getInstance().getGlobalContainerStack()
|
global_container_stack = Application.getInstance().getGlobalContainerStack()
|
||||||
|
if global_container_stack is not None:
|
||||||
global_container_stack.propertyChanged.emit(key, property_name)
|
global_container_stack.propertyChanged.emit(key, property_name)
|
||||||
|
|
||||||
## Needs to return a dict that can be used to construct a settingcategory file.
|
## Needs to return a dict that can be used to construct a settingcategory file.
|
||||||
|
@ -75,30 +84,35 @@ class Script:
|
||||||
# Scripts can either override getSettingData directly, or use getSettingDataString
|
# 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
|
# 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.
|
# returning a dict in that the order of settings is maintained.
|
||||||
def getSettingData(self):
|
def getSettingData(self) -> Dict[str, Any]:
|
||||||
setting_data = self.getSettingDataString()
|
setting_data_as_string = self.getSettingDataString()
|
||||||
if type(setting_data) == str:
|
setting_data = json.loads(setting_data_as_string, object_pairs_hook = collections.OrderedDict)
|
||||||
setting_data = json.loads(setting_data, object_pairs_hook = collections.OrderedDict)
|
|
||||||
return setting_data
|
return setting_data
|
||||||
|
|
||||||
def getSettingDataString(self):
|
def getSettingDataString(self) -> str:
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
def getDefinitionId(self):
|
def getDefinitionId(self) -> Optional[str]:
|
||||||
if self._stack:
|
if self._stack:
|
||||||
return self._stack.getBottom().getId()
|
bottom = self._stack.getBottom()
|
||||||
|
if bottom is not None:
|
||||||
|
return bottom.getId()
|
||||||
|
return None
|
||||||
|
|
||||||
def getStackId(self):
|
def getStackId(self) -> Optional[str]:
|
||||||
if self._stack:
|
if self._stack:
|
||||||
return self._stack.getId()
|
return self._stack.getId()
|
||||||
|
return None
|
||||||
|
|
||||||
## Convenience function that retrieves value of a setting from the stack.
|
## Convenience function that retrieves value of a setting from the stack.
|
||||||
def getSettingValueByKey(self, key):
|
def getSettingValueByKey(self, key: str) -> Any:
|
||||||
|
if self._stack is not None:
|
||||||
return self._stack.getProperty(key, "value")
|
return self._stack.getProperty(key, "value")
|
||||||
|
return None
|
||||||
|
|
||||||
## Convenience function that finds the value in a line of g-code.
|
## 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.
|
# When requesting key = x from line "G1 X100" the value 100 is returned.
|
||||||
def getValue(self, line, key, default = None):
|
def getValue(self, line: str, key: str, default = None) -> Any:
|
||||||
if not key in line or (';' in line and line.find(key) > line.find(';')):
|
if not key in line or (';' in line and line.find(key) > line.find(';')):
|
||||||
return default
|
return default
|
||||||
sub_part = line[line.find(key) + 1:]
|
sub_part = line[line.find(key) + 1:]
|
||||||
|
@ -126,7 +140,7 @@ class Script:
|
||||||
# \param line The original g-code line that must be modified. If not
|
# \param line The original g-code line that must be modified. If not
|
||||||
# provided, an entirely new g-code line will be produced.
|
# provided, an entirely new g-code line will be produced.
|
||||||
# \return A line of g-code with the desired parameters filled in.
|
# \return A line of g-code with the desired parameters filled in.
|
||||||
def putValue(self, line = "", **kwargs):
|
def putValue(self, line: str = "", **kwargs) -> str:
|
||||||
#Strip the comment.
|
#Strip the comment.
|
||||||
comment = ""
|
comment = ""
|
||||||
if ";" in line:
|
if ";" in line:
|
||||||
|
@ -167,5 +181,5 @@ class Script:
|
||||||
|
|
||||||
## This is called when the script is executed.
|
## This is called when the script is executed.
|
||||||
# It gets a list of g-code strings and needs to return a (modified) list.
|
# It gets a list of g-code strings and needs to return a (modified) list.
|
||||||
def execute(self, data):
|
def execute(self, data: List[str]) -> List[str]:
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue