Skip to content

Instantly share code, notes, and snippets.

@HB-Stratos
Created July 2, 2024 00:36
Show Gist options
  • Save HB-Stratos/288777eaa019f8488e4804fa30b764e7 to your computer and use it in GitHub Desktop.
Save HB-Stratos/288777eaa019f8488e4804fa30b764e7 to your computer and use it in GitHub Desktop.
A gcode post processor to increase the extra restart distance based on travel time. currently quite messy, and does not account for acceleration of the printherad
from dataclasses import dataclass
import dataclasses
from collections import namedtuple
import math
import re
#I apologize for the mess of camelcase and underscores
gcode_file_name = "Soldering Jig_0.2mm_LW-PLA_I3MEGA_1h37m.gcode"
gcode_file = open(gcode_file_name, "r")
#Globals:
@dataclass
class Globals:
is_metric:bool
is_movement_absolute:bool
is_extruder_absolute:bool
globals = Globals(None, None, None)
@dataclass
class Position:
x:float
y:float
z:float
@dataclass
class MoveData:
position:Position
velocity:float
acceleration:float
@dataclass
class CorrectionCurve:
time_points:list[float]
corr_points:list[float]
correctionCurve = CorrectionCurve( [0.0, 0.1, 0.5, 1.0], \
[0.0, 0.5, 1.0, 9.0] )
def bisection(array,value): #https://stackoverflow.com/a/41856629
'''Given an ``array`` , and given a ``value`` , returns an index j such that ``value`` is between array[j]
and array[j+1]. ``array`` must be monotonic increasing. j=-1 or j=len(array) is returned
to indicate that ``value`` is out of range below and above respectively.'''
n = len(array)
if (value < array[0]):
return -1
elif (value > array[n-1]):
return n
jl = 0# Initialize lower
ju = n-1# and upper limits.
while (ju-jl > 1):# If we are not yet done,
jm=(ju+jl) >> 1# compute a midpoint with a bitshift
if (value >= array[jm]):
jl=jm# and replace either the lower limit
else:
ju=jm# or the upper limit, as appropriate.
# Repeat until the test condition is satisfied.
if (value == array[0]):# edge cases at bottom
return 0
elif (value == array[n-1]):# and top
return n-1
else:
return jl
def lerp(a: float, b: float, t: float) -> float: #https://gist.github.com/laundmo/b224b1f4c8ef6ca5fe47e132c8deab56
"""Linear interpolate on the scale given by a to b, using t as the point on that scale.
Examples
--------
50 == lerp(0, 100, 0.5)
4.2 == lerp(1, 5, 0.8)
"""
return (1 - t) * a + t * b
def interpolate(curve:CorrectionCurve, travel_time:float) -> float:
idx = bisection(curve.time_points, travel_time)
if idx == 0: return curve.corr_points[0]
if idx == len(curve.time_points): return curve.corr_points[len(curve.time_points)-1]
return lerp(curve.corr_points[idx], curve.corr_points[idx+1], travel_time-curve.time_points[idx])
def calcDistance (pos1:Position, pos2:Position) -> float:
return math.sqrt( (pos1.x - pos2.x)**2 + (pos1.y - pos2.y)**2 + (pos1.z - pos2.z)**2)
def calcSingleMoveTime (initialPos:Position, finalPos:Position, initialVelocity:float, acceleration:float) -> float:
distance = calcDistance(initialPos, finalPos)
if acceleration == 0:
time = distance / initialVelocity
else: # this is flawed, plain wrong at the moment
raise Exception("acceleration other than 0 is bad math")
time1 = -((math.sqrt(2*acceleration*distance + initialVelocity**2)+initialVelocity)/(acceleration))
time2 = +((math.sqrt(2*acceleration*distance + initialVelocity**2)-initialVelocity)/(acceleration))
time = max(time1, time2)
return time
def time_travel_move_list (travel_move_list:list[MoveData], globals:Globals) -> float:
if not globals.is_metric: raise Exception("this program currently can not work with imperial units in gcode (G20)")
if globals.is_extruder_absolute: raise Exception("this program currently can not work with absolute extruder values (G91)")
travel_time = 0
previous_moveData:MoveData = None
for moveData in travel_move_list:
if previous_moveData:
travel_time += calcSingleMoveTime(previous_moveData.position, moveData.position, previous_moveData.velocity, 0)
previous_moveData = moveData
return travel_time
def grab_value_from_string(string:str, target:str):
if (target_pos := string.find(target)+1) >= 1:
return string.split(maxsplit=1)[0][1:]
#This needs some fixing, currently imples the target is passed with a leading space
previous_line:str = ""
has_retracted:bool = False
travel_move_list:list = []
correction_dict:dict = {}
total_travel_time:float = 0
last_feedrate:float = 0
last_acceleration:float = 0
last_position:Position = Position(None, None, None) #namedtuple("last_position", ["x", "y", "z"])
for line_nr, line in enumerate(gcode_file, start=1):
for _ in (True,): # this merely exists so I can break; and still execute the last line
if line.startswith("\n"): break
if line.startswith(";"): break
if previous_line.startswith(";WIPE_"): previous_line_was_wipe = True;
else: previous_line_was_wipe = False
split_line = line.split(";")[0] #discard everything after comment
split_line = split_line.split()
for pos, element in enumerate(split_line):
if element.startswith(";"):
split_line = split_line[0:pos]
break
if split_line[0] == "G90": globals.is_movement_absolute = True; globals.is_extruder_absolute = True; break
if split_line[0] == "G91": globals.is_movement_absolute = False; globals.is_extruder_absolute = False; break
if split_line[0] == "G21": globals.is_metric = True; break
if split_line[0] == "G20": globals.is_metric = False; break
if split_line[0] == "M83": globals.is_extruder_absolute = False; break
if split_line[0] == "M204":
for element in split_line:
if element.startswith("S"):
last_acceleration = float(element[1:])
break
if not split_line[0] == "G1": break
for element in split_line[1:]:
if element.startswith("X"):
last_position.x = float(element[1:])
elif element.startswith("Y"):
last_position.y = float(element[1:])
elif element.startswith("Z"):
last_position.z = float(element[1:])
elif element.startswith("F"):
last_feedrate = float(element[1:])
elif element.startswith("E"):
if element[1] == "-":
if not previous_line_was_wipe: has_retracted = True
continue
if has_retracted:
has_retracted = False
travel_time = time_travel_move_list(travel_move_list, globals)
travel_move_list.clear()
correction_amount = interpolate(correctionCurve, travel_time)
correction_dict[line_nr] = [correction_amount, line]
#if retracted and position is different or no positions present
if has_retracted and ((travel_move_list and not last_position == travel_move_list[-1].position) or not travel_move_list):
moveData = MoveData(dataclasses.replace(last_position), float(last_feedrate)/60, float(last_acceleration)) # specified in units of mm and s
travel_move_list.append(moveData)
previous_line = line
def write_lines_from_dict(lines_to_write_dict:dict, file_name:str): # https://ask.replit.com/t/writing-to-a-specific-line-in-at-txt-file/83327/8
with open(file_name, "r") as file: # Open the file in read mode
lines = file.readlines() # Assign the file as a list to a variable
for line, content in lines_to_write_dict.items():
lines[line-1] = content # Replace the proper line with the provided content
with open(file_name.replace(".gcode", "_w.gcode"), "w") as file: # Open the file in write mode
file.write("".join(lines)) # Write the modified content to the file
lines_to_write_dict:dict = {}
for line_nr, [correction_amount, line_content] in correction_dict.items():
re_pattern = re.compile("(?<=E)[.|\d]*")
insert = float(re.search(re_pattern, line_content).group())
insert += correction_amount
line_content = re.sub(re_pattern, str(insert), line_content )
lines_to_write_dict[line_nr] = line_content
write_lines_from_dict(lines_to_write_dict, gcode_file_name)
print(line_nr)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment