Skip to content

Commit

Permalink
Altered SearchAndReplace, InsertAtLayerChange and TimeLapse
Browse files Browse the repository at this point in the history
All three files get new options:
SearchAndReplace - Added range of layers, and options to skip the StartUp and Ending Gcode.
InsertAtLayerChange - Added Insert Frequency and layer range.  Added multi-line input.
TimeLapse - Added Insert Frequency
  • Loading branch information
GregValiant committed Jun 22, 2023
1 parent 7f62744 commit 44c6cf1
Show file tree
Hide file tree
Showing 3 changed files with 388 additions and 105 deletions.
140 changes: 119 additions & 21 deletions plugins/PostProcessingPlugin/scripts/InsertAtLayerChange.py
Original file line number Diff line number Diff line change
@@ -1,52 +1,150 @@
# Copyright (c) 2020 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
# Created by Wayne Porter
# Created by Wayne Porter.
# Altered January of 2023 by GregValiant (Greg Foresi)
# Support for multi-line insertions
# Insertion start and end layers. Numbers are consistent with the Cura Preview (base1)
# Frequency of Insertion (one time, every layer, every 2nd, 3rd, 5th, 10th, 25th, 50th, 100th)

from ..Script import Script
import re

class InsertAtLayerChange(Script):
def __init__(self):
super().__init__()

def getSettingDataString(self):
return """{
"name": "Insert at layer change",
"name": "Insert at Layer Change",
"key": "InsertAtLayerChange",
"metadata": {},
"version": 2,
"settings":
{
"insert_location":
"insert_frequency":
{
"label": "When to insert",
"description": "Whether to insert code before or after layer change.",
"label": "How often to insert",
"description": "Every so many layers starting with the Start Layer OR as single insertion at a specific layer.",
"type": "enum",
"options": {"before": "Before", "after": "After"},
"default_value": "before"
"options": {
"once_only": "One insertion only",
"every_layer": "Every Layer",
"every_2nd": "Every 2nd",
"every_3rd": "Every 3rd",
"every_5th": "Every 5th",
"every_10th": "Every 10th",
"every_25th": "Every 25th",
"every_50th": "Every 50th",
"every_100th": "Every 100th"},
"default_value": "every_layer"
},
"start_layer":
{
"label": "Starting Layer",
"description": "Layer to start the insertion at. Use layer numbers from the Cura Preview. Enter '1' to start at gcode LAYER:0. If you need to start from the beginning of a raft enter '-5'.",
"type": "int",
"default_value": 1,
"minimum_value": -5,
"enabled": "insert_frequency != 'once_only'"
},
"end_layer_enabled":
{
"label": "Enable End Layer",
"description": "Check to use an ending layer for the insertion. Use layer numbers from the Cura Preview.",
"type": "bool",
"default_value": false,
"enabled": "insert_frequency != 'once_only'"
},
"end_layer":
{
"label": "Ending Layer",
"description": "Layer to end the insertion at. Enter '-1' for entire file (or disable this setting). Use layer numbers from the Cura Preview.",
"type": "str",
"default_value": "-1",
"enabled": "end_layer_enabled and insert_frequency != 'once_only'"
},
"single_end_layer":
{
"label": "Layer # for Single Insertion.",
"description": "Layer for a single insertion of the Gcode. Use the layer numbers from the Cura Preview.",
"type": "str",
"default_value": "",
"enabled": "insert_frequency == 'once_only'"
},
"gcode_to_add":
{
"label": "G-code to insert.",
"description": "G-code to add before or after layer change.",
"description": "G-code to add at start of the layer. Use a comma to delimit multi-line commands. EX: G28 X Y,M220 S100,M117 HELL0. NOTE: All inserted text will be converted to upper-case as some firmwares don't understand lower-case.",
"type": "str",
"default_value": ""
}
}
}"""

def execute(self, data):
gcode_to_add = self.getSettingValueByKey("gcode_to_add") + "\n"
for layer in data:
# Check that a layer is being printed
lines = layer.split("\n")
for line in lines:
if ";LAYER:" in line:
index = data.index(layer)
if self.getSettingValueByKey("insert_location") == "before":
layer = gcode_to_add + layer
else:
layer = layer + gcode_to_add
#Initialize variables
mycode = self.getSettingValueByKey("gcode_to_add").upper()
the_start_layer = int(self.getSettingValueByKey("start_layer"))-1
the_end_layer = self.getSettingValueByKey("end_layer").lower()
the_end_is_enabled = self.getSettingValueByKey("end_layer_enabled")
when_to_insert = self.getSettingValueByKey("insert_frequency")
start_here = False
real_num = 0
if the_end_layer == "-1" or not the_end_is_enabled:
the_end_layer = "9999999999"
#If the gcode_to_enter is multi-line then replace the commas with newline characters
if mycode != "":
if "," in mycode:
mycode = re.sub(",", "\n",mycode)
gcode_to_add = mycode + "\n"
#Get the insertion frequency
if when_to_insert == "every_layer":
freq = 1
if when_to_insert == "every_2nd":
freq = 2
if when_to_insert == "every_3rd":
freq = 3
if when_to_insert == "every_5th":
freq = 5
if when_to_insert == "every_10th":
freq = 10
if when_to_insert == "every_25th":
freq = 25
if when_to_insert == "every_50th":
freq = 50
if when_to_insert == "every_100th":
freq = 100
if when_to_insert == "once_only":
the_search_layer = int(self.getSettingValueByKey("single_end_layer"))-1

#Add the post processor name to the gcode file
data[0] += "; Insert at Layer Change (Insert; " + str(mycode) + " Insert Frequency; " + when_to_insert + " layer)" + "\n"

data[index] = layer
break
#Single insertion
index = 0
if when_to_insert == "once_only":
for layer in data:
lines = layer.split("\n")
for line in lines:
if ";LAYER:" in line:
layer_number = int(line.split(":")[1])
if layer_number == int(the_search_layer):
index = data.index(layer)
lines.insert(1,gcode_to_add[0:-1])
data[index] = "\n".join(lines)
break
#Multiple insertions
if when_to_insert != "once_only":
for layer in data:
lines = layer.split("\n")
for line in lines:
if ";LAYER:" in line:
layer_number = int(line.split(":")[1])
if layer_number >= int(the_start_layer)-1 and layer_number <= int(the_end_layer)-1:
index = data.index(layer)
real_num = layer_number - int(the_start_layer)
if int(real_num / freq) - (real_num / freq) == 0:
lines.insert(1,gcode_to_add[0:-1])
data[index] = "\n".join(lines)
break
return data
172 changes: 153 additions & 19 deletions plugins/PostProcessingPlugin/scripts/SearchAndReplace.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
# Copyright (c) 2017 Ghostkeeper
# The PostProcessingPlugin is released under the terms of the AGPLv3 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


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 +21,173 @@ 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 Multi-Line insertions use the newline character 'backslash plus n' as the delimiter. Also for multi-line insertions the last character must be 'backslash plus n'",
"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 re-compiled 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 g-codes 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 ''end'' to replace to the end of the file. Enter any other layer number and the replacements will conclude at the end of that layer.",
"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 G-code is unaffected. The StartUp G-code 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 G-code is unaffected.",
"type": "bool",
"default_value": true,
"enabled": "not enable_layer_search"
}
}
}"""

def execute(self, data):
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")
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--------------------------------------------------------------------------
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:
all

#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) - 1
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)-1
else:
end_index = raft_start_index + int(end_layer)
if end_index > len(data)-1: end_index = len(data)-1 #For possible Input error
if int(end_layer) < int(start_layer): end_index = start_index #For possible Input error
except:
start_index = 2
end_index = len(data) -1

return data
#if "first_instance_only" is enabled:
replaceone = False
new_string = search_string
if first_instance_only:
if not is_regex:
new_string = re.escape(search_string)
search_regex = re.compile(new_string)
for num in range(start_index, end_index,1):
layer = data[num]
lines = layer.split("\n")
for index, line in enumerate(lines):
if re.match(search_regex, line) and replaceone == False:
lines[index] = re.sub(search_regex, replace_string, line)
data[num] = "\n".join(lines)
replaceone = True
break
if replaceone: break
data[0] += "; Search and Replace (Search: " + str(self.getSettingValueByKey("search")) + " | Replace: " + str(self.getSettingValueByKey("replace")) + ")\n"
return data

#Do 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)

#Add the post-processor name and settings to the gcode----------------------------------------------------
data[0] += "; Search and Replace (Search: " + str(self.getSettingValueByKey("search")) + " | Replace: " + str(self.getSettingValueByKey("replace")) + ")\n"
return data

Loading

0 comments on commit 44c6cf1

Please sign in to comment.