Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Re-write of Search and Replace #19796

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
175 changes: 156 additions & 19 deletions plugins/PostProcessingPlugin/scripts/SearchAndReplace.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
# Copyright (c) 2017 Ghostkeeper
# The PostProcessingPlugin is released under the terms of the LGPLv3 or higher.
# Altered by GregValiant (Greg Foresi) February, 2023.
# Added option for a layer search with a Start Layer and an End layer.
# Added 'Ignore StartUp G-code' and 'Ignore Ending G-code' options

import re # To perform the search and replace.

import re
from ..Script import Script

from UM.Application import Application

class SearchAndReplace(Script):
"""Performs a search-and-replace on all g-code.

Due to technical limitations, the search can't cross the border between
layers.
"""Performs a search-and-replace on the g-code.
"""

def getSettingDataString(self):
Expand All @@ -23,37 +22,175 @@ def getSettingDataString(self):
{
"search":
{
"label": "Search",
"description": "All occurrences of this text will get replaced by the replacement text.",
"label": "Search for:",
"description": "All occurrences of this text (within the search range) will be replaced by the 'Replace with' text. The search string is CASE SPECIFIC so 'LAYER' is not the same as 'layer'.",
"type": "str",
"default_value": ""
},
"replace":
{
"label": "Replace",
"description": "The search text will get replaced by this text.",
"label": "Replace with:",
"description": "The 'Search For' text will get replaced by this text. For MultiLine insertions use the newline character 'backslash plus n' as the delimiter. If your Search term ends with a 'newline' remember to add 'newline' to the end of this Replace term.",
"type": "str",
"default_value": ""
},
"is_regex":
{
"label": "Use Regular Expressions",
"description": "When enabled, the search text will be interpreted as a regular expression.",
"description": "When disabled the search string is treated as a simple text string. When enabled, the search text will be recompiled as a 'regular' python expression.",
"type": "bool",
"default_value": false
},
"enable_layer_search":
{
"label": "Enable search within a Layer Range:",
"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.",
"type": "bool",
"default_value": false,
"enabled": true
},
"search_start":
{
"label": "Start S&R at Layer:",
"description": "Use the Cura Preview layer numbering. The Start Layer will be included. Enter '1' to start with gcode ';LAYER:0'. Enter ''-6'' to start with the first layer of a raft.",
"type": "int",
"default_value": 1,
"minimum_value": -6,
"enabled": "enable_layer_search"
},
"search_end":
{
"label": "Stop S&R at end of Layer:",
"description": "Use the Cura Preview layer numbering. Enter '-1' to search and replace to the end of the file. Enter any other layer number and the replacements will conclude at the end of that layer. If the End Layer is equal to the Start Layer then only that single layer is searched.",
"type": "int",
"default_value": -1,
"minimum_value": -1,
"enabled": "enable_layer_search"
},
"first_instance_only":
{
"label": "Replace first instance only:",
"description": "When enabled only the first instance is replaced.",
"type": "bool",
"default_value": false,
"enabled": true
},
"ignore_start":
{
"label": "Ignore StartUp G-code:",
"description": "When enabled the StartUp Gcode is unaffected. The StartUp Gcode is everything from ';generated with Cura...' to ';LAYER_COUNT:' inclusive.",
"type": "bool",
"default_value": true,
"enabled": "not enable_layer_search"
},
"ignore_end":
{
"label": "Ignore Ending G-code:",
"description": "When enabled the Ending Gcode is unaffected.",
"type": "bool",
"default_value": true,
"enabled": "not enable_layer_search"
}
}
}"""

def execute(self, data):
curaApp = Application.getInstance().getGlobalContainerStack()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code style: cura_app instead of curaApp.
Or better, since you are actually getting the global container stack: global_stack

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

extruder = curaApp.extruderList
retract_enabled = bool(extruder[0].getProperty("retraction_enable", "value"))
# If retractions are enabled then the CuraEngine inserts a single data item for the retraction at the end of the last layer
# 'top_layer' accounts for that
if retract_enabled:
top_layer = 2
else:
top_layer = 1
search_string = self.getSettingValueByKey("search")
if not self.getSettingValueByKey("is_regex"):
search_string = re.escape(search_string) #Need to search for the actual string, not as a regex.
search_regex = re.compile(search_string)

replace_string = self.getSettingValueByKey("replace")
is_regex = self.getSettingValueByKey("is_regex")
enable_layer_search = self.getSettingValueByKey("enable_layer_search")
start_layer = self.getSettingValueByKey("search_start")
end_layer = self.getSettingValueByKey("search_end")
ignore_start = self.getSettingValueByKey("ignore_start")
ignore_end = self.getSettingValueByKey("ignore_end")
if enable_layer_search:
ignore_start = True
ignore_end = True
first_instance_only = bool(self.getSettingValueByKey("first_instance_only"))

for layer_number, layer in enumerate(data):
data[layer_number] = re.sub(search_regex, replace_string, layer) #Replace all.
#Find the raft and layer:0 indexes--------------------------------------------------------------------------
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Strange comment style. Please use the same style of comments as the rest of Cura.
I am somewhat surprised Python does not complain about the indentation; strictly speaking this indentation ends the previous codeblock.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

raft_start_index = 0
layer_0_index = 0
start_index = 1
end_index = len(data)
try:
for l_num in range(2,12,1):
layer = data[l_num]
if ";LAYER:-" in layer and raft_start_index == 0:
raft_start_index = l_num
if ";LAYER:0" in layer:
layer_0_index = l_num
break
if raft_start_index == 0:
raft_start_index = layer_0_index
raft_layers = 0
elif raft_start_index < layer_0_index:
raft_layers = layer_0_index - raft_start_index
else:
raft_layers = 0
except:
pass
#Determine the actual start and end indexes of the data----------------------------------------------------
try:
if not enable_layer_search:
if ignore_start:
start_index = 2
else:
start_index = 1
if ignore_end:
end_index = len(data) - top_layer
else:
end_index = len(data)
elif enable_layer_search:
if start_layer < 1 and start_layer != -6:
start_index = layer_0_index - raft_layers
elif start_layer == -6:
start_index = 2
else:
start_index = raft_start_index + start_layer - 1
if end_layer == -1:
end_index = len(data) - top_layer
else:
end_index = raft_start_index + int(end_layer)
if end_index > len(data) - 1: end_index = len(data) - 1 #For possible user input error
if int(end_index) < int(start_index): end_index = start_index #For possible user input error
except:
start_index = 2
end_index = len(data) - top_layer

return data
# If "first_instance_only" is enabled:
replaceone = False
if first_instance_only:
if not is_regex:
search_string = re.escape(search_string)
search_regex = re.compile(search_string)
for num in range(start_index, end_index, 1):
layer = data[num]
if re.search(search_regex, layer) and replaceone == False:
data[num] = re.sub(search_regex, replace_string, data[num], 1)
replaceone = True
break
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As far as I can see, this condition is never met because we're already broken out of the for loop. As a matter of fact, it seems to me that the whole replaceone variable is useless.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that was vestigial code. There may have been a second "for" in there and I used the second 'break' to back all the way out.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

if replaceone: break
return data

# For all the replacements
if not is_regex:
search_string = re.escape(search_string)
search_regex = re.compile(search_string)
if end_index > start_index:
for index in range(start_index, end_index, 1):
layer = data[index]
data[index] = re.sub(search_regex, replace_string, layer)
elif end_index == start_index:
layer = data[start_index]
data[start_index] = re.sub(search_regex, replace_string, layer)
return data
Loading