Typing fixes

Since I was stupid enough to touch it, I was also forced to boyscout the code.
This commit is contained in:
Jaime van Kessel 2018-10-03 16:36:58 +02:00
parent 2e529452dd
commit adf8285d20
2 changed files with 115 additions and 80 deletions

View file

@ -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")

View file

@ -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()