Script.py 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  1. # Copyright (c) 2015 Jaime van Kessel
  2. # Copyright (c) 2018 Ultimaker B.V.
  3. # The PostProcessingPlugin is released under the terms of the AGPLv3 or higher.
  4. from typing import Optional, Any, Dict, TYPE_CHECKING, List
  5. from UM.Signal import Signal, signalemitter
  6. from UM.i18n import i18nCatalog
  7. # Setting stuff import
  8. from UM.Application import Application
  9. from UM.Settings.ContainerFormatError import ContainerFormatError
  10. from UM.Settings.ContainerStack import ContainerStack
  11. from UM.Settings.InstanceContainer import InstanceContainer
  12. from UM.Settings.DefinitionContainer import DefinitionContainer
  13. from UM.Settings.ContainerRegistry import ContainerRegistry
  14. import re
  15. import json
  16. import collections
  17. i18n_catalog = i18nCatalog("cura")
  18. if TYPE_CHECKING:
  19. from UM.Settings.Interfaces import DefinitionContainerInterface
  20. @signalemitter
  21. class Script:
  22. """Base class for scripts. All scripts should inherit the script class."""
  23. def __init__(self) -> None:
  24. super().__init__()
  25. self._stack = None # type: Optional[ContainerStack]
  26. self._definition = None # type: Optional[DefinitionContainerInterface]
  27. self._instance = None # type: Optional[InstanceContainer]
  28. def initialize(self) -> None:
  29. setting_data = self.getSettingData()
  30. self._stack = ContainerStack(stack_id=str(id(self)))
  31. self._stack.setDirty(False) # This stack does not need to be saved.
  32. ## Check if the definition of this script already exists. If not, add it to the registry.
  33. if "key" in setting_data:
  34. definitions = ContainerRegistry.getInstance().findDefinitionContainers(id=setting_data["key"])
  35. if definitions:
  36. # Definition was found
  37. self._definition = definitions[0]
  38. else:
  39. self._definition = DefinitionContainer(setting_data["key"])
  40. try:
  41. self._definition.deserialize(json.dumps(setting_data))
  42. ContainerRegistry.getInstance().addContainer(self._definition)
  43. except ContainerFormatError:
  44. self._definition = None
  45. return
  46. if self._definition is None:
  47. return
  48. self._stack.addContainer(self._definition)
  49. self._instance = InstanceContainer(container_id="ScriptInstanceContainer")
  50. self._instance.setDefinition(self._definition.getId())
  51. self._instance.setMetaDataEntry("setting_version",
  52. self._definition.getMetaDataEntry("setting_version", default=0))
  53. self._stack.addContainer(self._instance)
  54. self._stack.propertyChanged.connect(self._onPropertyChanged)
  55. ContainerRegistry.getInstance().addContainer(self._stack)
  56. settingsLoaded = Signal()
  57. valueChanged = Signal() # Signal emitted whenever a value of a setting is changed
  58. def _onPropertyChanged(self, key: str, property_name: str) -> None:
  59. if property_name == "value":
  60. self.valueChanged.emit()
  61. # Property changed: trigger reslice
  62. # To do this we use the global container stack propertyChanged.
  63. # Re-slicing is necessary for setting changes in this plugin, because the changes
  64. # are applied only once per "fresh" gcode
  65. global_container_stack = Application.getInstance().getGlobalContainerStack()
  66. if global_container_stack is not None:
  67. global_container_stack.propertyChanged.emit(key, property_name)
  68. def getSettingData(self) -> Dict[str, Any]:
  69. """Needs to return a dict that can be used to construct a settingcategory file.
  70. See the example script for an example.
  71. It follows the same style / guides as the Uranium settings.
  72. Scripts can either override getSettingData directly, or use getSettingDataString
  73. to return a string that will be parsed as json. The latter has the benefit over
  74. returning a dict in that the order of settings is maintained.
  75. """
  76. setting_data_as_string = self.getSettingDataString()
  77. setting_data = json.loads(setting_data_as_string, object_pairs_hook = collections.OrderedDict)
  78. return setting_data
  79. def getSettingDataString(self) -> str:
  80. raise NotImplementedError()
  81. def getDefinitionId(self) -> Optional[str]:
  82. if self._stack:
  83. bottom = self._stack.getBottom()
  84. if bottom is not None:
  85. return bottom.getId()
  86. return None
  87. def getStackId(self) -> Optional[str]:
  88. if self._stack:
  89. return self._stack.getId()
  90. return None
  91. def getSettingValueByKey(self, key: str) -> Any:
  92. """Convenience function that retrieves value of a setting from the stack."""
  93. if self._stack is not None:
  94. return self._stack.getProperty(key, "value")
  95. return None
  96. def getValue(self, line: str, key: str, default = None) -> Any:
  97. """Convenience function that finds the value in a line of g-code.
  98. When requesting key = x from line "G1 X100" the value 100 is returned.
  99. """
  100. if not key in line or (';' in line and line.find(key) > line.find(';')):
  101. return default
  102. sub_part = line[line.find(key) + 1:]
  103. m = re.search('^-?[0-9]+\.?[0-9]*', sub_part)
  104. if m is None:
  105. return default
  106. try:
  107. return int(m.group(0))
  108. except ValueError: #Not an integer.
  109. try:
  110. return float(m.group(0))
  111. except ValueError: #Not a number at all.
  112. return default
  113. def putValue(self, line: str = "", **kwargs) -> str:
  114. """Convenience function to produce a line of g-code.
  115. You can put in an original g-code line and it'll re-use all the values
  116. in that line.
  117. All other keyword parameters are put in the result in g-code's format.
  118. For instance, if you put ``G=1`` in the parameters, it will output
  119. ``G1``. If you put ``G=1, X=100`` in the parameters, it will output
  120. ``G1 X100``. The parameters G and M will always be put first. The
  121. parameters T and S will be put second (or first if there is no G or M).
  122. The rest of the parameters will be put in arbitrary order.
  123. :param line: The original g-code line that must be modified. If not
  124. provided, an entirely new g-code line will be produced.
  125. :return: A line of g-code with the desired parameters filled in.
  126. """
  127. #Strip the comment.
  128. comment = ""
  129. if ";" in line:
  130. comment = line[line.find(";"):]
  131. line = line[:line.find(";")] #Strip the comment.
  132. #Parse the original g-code line.
  133. for part in line.split(" "):
  134. if part == "":
  135. continue
  136. parameter = part[0]
  137. if parameter in kwargs:
  138. continue #Skip this one. The user-provided parameter overwrites the one in the line.
  139. value = part[1:]
  140. kwargs[parameter] = value
  141. #Write the new g-code line.
  142. result = ""
  143. priority_parameters = ["G", "M", "T", "S", "F", "X", "Y", "Z", "E"] #First some parameters that get priority. In order of priority!
  144. for priority_key in priority_parameters:
  145. if priority_key in kwargs:
  146. if result != "":
  147. result += " "
  148. result += priority_key + str(kwargs[priority_key])
  149. del kwargs[priority_key]
  150. for key, value in kwargs.items():
  151. if result != "":
  152. result += " "
  153. result += key + str(value)
  154. #Put the comment back in.
  155. if comment != "":
  156. if result != "":
  157. result += " "
  158. result += ";" + comment
  159. return result
  160. def execute(self, data: List[str]) -> List[str]:
  161. """This is called when the script is executed.
  162. It gets a list of g-code strings and needs to return a (modified) list.
  163. """
  164. raise NotImplementedError()