SearchAndReplace.py 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  1. # Copyright (c) 2017 Ghostkeeper
  2. # The PostProcessingPlugin is released under the terms of the LGPLv3 or higher.
  3. # Altered by GregValiant (Greg Foresi) February, 2025.
  4. # Added option for "first instance only"
  5. # Added option for a layer search with a Start Layer and an End layer.
  6. # Added 'Ignore StartUp G-code' and 'Ignore Ending G-code' options
  7. import re
  8. from ..Script import Script
  9. from UM.Application import Application
  10. class SearchAndReplace(Script):
  11. """Performs a search-and-replace on the g-code.
  12. """
  13. def getSettingDataString(self):
  14. return r"""{
  15. "name": "Search and Replace",
  16. "key": "SearchAndReplace",
  17. "metadata": {},
  18. "version": 2,
  19. "settings":
  20. {
  21. "search":
  22. {
  23. "label": "Search for:",
  24. "description": "All occurrences of this text (within the search range) will be replaced by the 'Replace with' string. The search string is 'Case Sensitive' and 'Layer' is not the same as 'layer'.",
  25. "type": "str",
  26. "default_value": ""
  27. },
  28. "replace":
  29. {
  30. "label": "Replace with:",
  31. "description": "The 'Search For' text will get replaced by this text. For MultiLine insertions use the newline character '\\n' as the delimiter. If your Search term ends with a '\\n' remember to add '\\n' to the end of this Replace term.",
  32. "type": "str",
  33. "default_value": ""
  34. },
  35. "is_regex":
  36. {
  37. "label": "Use Regular Expressions",
  38. "description": "When disabled the search string is treated as a simple text string. When enabled, the search text will be interpreted as a Python regular expression.",
  39. "type": "bool",
  40. "default_value": false
  41. },
  42. "enable_layer_search":
  43. {
  44. "label": "Enable search within a Layer Range:",
  45. "description": "When enabled, You can choose a Start and End layer for the search. When 'Layer Search' is enabled the StartUp and Ending gcodes are always ignored.",
  46. "type": "bool",
  47. "default_value": false,
  48. "enabled": true
  49. },
  50. "search_start":
  51. {
  52. "label": "Start S&R at Layer:",
  53. "description": "Use the Cura Preview layer numbering.",
  54. "type": "int",
  55. "default_value": 1,
  56. "minimum_value": 1,
  57. "enabled": "enable_layer_search"
  58. },
  59. "search_end":
  60. {
  61. "label": "Stop S&R at end of Layer:",
  62. "description": "Use the Cura Preview layer numbering. The replacements will conclude at the end of this layer. If the End Layer is equal to the Start Layer then only that single layer is searched.",
  63. "type": "int",
  64. "default_value": 2,
  65. "minimum_value": 1,
  66. "enabled": "enable_layer_search"
  67. },
  68. "first_instance_only":
  69. {
  70. "label": "Replace first instance only:",
  71. "description": "When enabled only the first instance is replaced.",
  72. "type": "bool",
  73. "default_value": false,
  74. "enabled": true
  75. },
  76. "ignore_start":
  77. {
  78. "label": "Ignore StartUp G-code:",
  79. "description": "When enabled the StartUp Gcode is unaffected. The StartUp Gcode is everything from ';generated with Cura...' to ';LAYER_COUNT:' inclusive.",
  80. "type": "bool",
  81. "default_value": true,
  82. "enabled": "not enable_layer_search"
  83. },
  84. "ignore_end":
  85. {
  86. "label": "Ignore Ending G-code:",
  87. "description": "When enabled the Ending Gcode is unaffected.",
  88. "type": "bool",
  89. "default_value": true,
  90. "enabled": "not enable_layer_search"
  91. }
  92. }
  93. }"""
  94. def execute(self, data):
  95. global_stack = Application.getInstance().getGlobalContainerStack()
  96. extruder = global_stack.extruderList
  97. retract_enabled = bool(extruder[0].getProperty("retraction_enable", "value"))
  98. search_string = self.getSettingValueByKey("search")
  99. replace_string = self.getSettingValueByKey("replace")
  100. is_regex = self.getSettingValueByKey("is_regex")
  101. enable_layer_search = self.getSettingValueByKey("enable_layer_search")
  102. start_layer = self.getSettingValueByKey("search_start")
  103. end_layer = self.getSettingValueByKey("search_end")
  104. ignore_start = self.getSettingValueByKey("ignore_start")
  105. ignore_end = self.getSettingValueByKey("ignore_end")
  106. if enable_layer_search:
  107. ignore_start = True
  108. ignore_end = True
  109. first_instance_only = bool(self.getSettingValueByKey("first_instance_only"))
  110. # Account for missing layer numbers when a raft is used
  111. start_index = 1
  112. end_index = len(data) - 1
  113. data_list = [0,1]
  114. layer_list = [-1,0]
  115. lay_num = 1
  116. for index, layer in enumerate(data):
  117. if re.search(r";LAYER:(-?\d+)", layer):
  118. data_list.append(index)
  119. layer_list.append(lay_num)
  120. lay_num += 1
  121. # Get the start and end indexes within the data
  122. if not enable_layer_search:
  123. if ignore_start:
  124. start_index = 2
  125. else:
  126. start_index = 1
  127. if ignore_end:
  128. end_index = data_list[len(data_list) - 1]
  129. else:
  130. # Account for the extra data item when retraction is enabled
  131. end_index = data_list[len(data_list) - 1] + (2 if retract_enabled else 1)
  132. elif enable_layer_search:
  133. for index, num in enumerate(layer_list):
  134. if num == start_layer:
  135. start_index = data_list[index]
  136. if num == end_layer:
  137. end_index = data_list[index]
  138. # Make replacements
  139. replace_one = False
  140. if not is_regex:
  141. search_string = re.escape(search_string)
  142. search_regex = re.compile(search_string)
  143. for num in range(start_index, end_index + 1, 1):
  144. layer = data[num]
  145. # First_instance only
  146. if first_instance_only:
  147. if re.search(search_regex, layer) and replace_one == False:
  148. data[num] = re.sub(search_regex, replace_string, data[num], 1)
  149. replace_one = True
  150. break
  151. # All instances
  152. else:
  153. if end_index > start_index:
  154. data[num] = re.sub(search_regex, replace_string, layer)
  155. elif end_index == start_index:
  156. layer = data[start_index]
  157. data[start_index] = re.sub(search_regex, replace_string, layer)
  158. return data