Skip to content

Commit

Permalink
host post processor
Browse files Browse the repository at this point in the history
  • Loading branch information
HelgeKeck committed May 3, 2024
1 parent 0a585f6 commit 9d707c5
Show file tree
Hide file tree
Showing 9 changed files with 392 additions and 2 deletions.
361 changes: 361 additions & 0 deletions klippy/ratos_post_processor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,361 @@
# RatOS IDEX and RMMU Gcode Post Processor
#
# Copyright (C) 2024 Helge Magnus Keck <[email protected]>
#
# This file may be distributed under the terms of the GNU GPLv3 license.
from math import fabs
from shutil import ReadError, copy2
from os import path, remove, getenv
import os, logging, io

#####
# RMMU Hub
#####
class RatOS_Post_Processor:

#####
# Initialize
#####
def __init__(self, config):
self.printer = config.get_printer()
self.name = config.get_name()
self.gcode = self.printer.lookup_object('gcode')
self.reactor = self.printer.get_reactor()

self.register_commands()
self.register_handler()

def get_status(self, eventtime):
return {'name': self.name}

#####
# Handler
#####
def register_handler(self):
self.printer.register_event_handler("klippy:connect", self._connect)

def _connect(self):
self.v_sd = self.printer.lookup_object('virtual_sdcard', None)
self.sdcard_dirname = self.v_sd.sdcard_dirname
self.dual_carriage = self.printer.lookup_object("dual_carriage", None)
self.rmmu_hub = self.printer.lookup_object("rmmu_hub", None)

#####
# Gcode commands
#####
def register_commands(self):
self.gcode.register_command('RATOS_POST_PROCESSOR', self.cmd_RATOS_POST_PROCESSOR, desc=(self.desc_RATOS_POST_PROCESSOR))

desc_RATOS_POST_PROCESSOR = ""
def cmd_RATOS_POST_PROCESSOR(self, gcmd):
if self.dual_carriage == None and self.rmmu_hub == None:
self.v_sd.cmd_SDCARD_PRINT_FILE(gcmd)
else:
filename = gcmd.get('FILENAME', "")
if filename[0] == '/':
filename = filename[1:]
if self.process_file(filename):
self.v_sd.cmd_SDCARD_PRINT_FILE(gcmd)
else:
raise self.printer.command_error("Could not process gcode file")

#####
# Post Processor
#####
def process_file(self, filename):
path = self.get_file_path(filename)
lines = self.get_file_lines(path)

if self.already_processed(path):
return True

self.ratos_echo("processing...")

slicer = self.get_slicer(lines[0].rstrip())

if slicer["Name"] != "PrusaSlicer" and slicer["Name"] != "SuperSlicer":
raise self.printer.command_error("Unsupported Slicer")

min_x = 1000
max_x = 0
first_x = -1
first_y = -1
toolshift_count = 0
tower_line = -1
start_print_line = 0
file_has_changed = False
wipe_accel = 0
used_tools = []
pause_counter = 0
for line in range(len(lines)):
# give the cpu some time
pause_counter += 1
if pause_counter == 1000:
pause_counter = 0
self.reactor.pause(.001)

# get slicer profile settings
if slicer["Name"] == "PrusaSlicer":
if wipe_accel == 0:
if lines[line].rstrip().startswith("; wipe_tower_acceleration = "):
wipe_accel = int(lines[line].rstrip().replace("; wipe_tower_acceleration = ", ""))

# get the start_print line number
if start_print_line == 0:
if lines[line].rstrip().startswith("START_PRINT") or lines[line].rstrip().startswith("RMMU_START_PRINT"):
lines[line] = lines[line].replace("#", "") # fix color variable format
start_print_line = line

# count toolshifts
if start_print_line > 0:
if lines[line].rstrip().startswith("T") and lines[line].rstrip()[1:].isdigit():
if toolshift_count == 0:
lines[line] = '' # remove first toolchange
toolshift_count += 1

# get first tools usage in order
if start_print_line > 0:
if len(used_tools) == 0:
index = lines[start_print_line].rstrip().find("INITIAL_TOOL=")
if index != -1:
used_tools.append(lines[start_print_line].rstrip()[index + len("INITIAL_TOOL="):].split()[0])
if lines[line].rstrip().startswith("T") and lines[line].rstrip()[1:].isdigit():
# add tool to the list if not already added
t = lines[line].rstrip()[1:]
if t not in used_tools:
used_tools.append(t)

# get first XY coordinates
if start_print_line > 0 and first_x < 0 and first_y < 0:
if lines[line].rstrip().startswith("G1") or lines[line].rstrip().startswith("G0"):
split = lines[line].rstrip().replace(" ", " ").split(" ")
for s in range(len(split)):
if split[s].lower().startswith("x"):
try:
x = float(split[s].lower().replace("x", ""))
if x > first_x:
first_x = x
except Exception as exc:
self.ratos_echo("Can not get first x coordinate. " + str(exc))
return False
if split[s].lower().startswith("y"):
try:
y = float(split[s].lower().replace("y", ""))
if y > first_y:
first_y = y
except Exception as exc:
self.ratos_echo("Can not get first y coordinate. " + str(exc))
return False

# get x boundaries
if start_print_line > 0:
if lines[line].rstrip().startswith("G1") or lines[line].rstrip().startswith("G0"):
split = lines[line].rstrip().replace(" ", " ").split(" ")
for s in range(len(split)):
if split[s].lower().startswith("x"):
try:
x = float(split[s].lower().replace("x", ""))
if x < min_x:
min_x = x
if x > max_x:
max_x = x
except Exception as exc:
self.ratos_echo("Can not get x boundaries . " + str(exc))
return False

# toolshift processing
if start_print_line > 0:
if lines[line].rstrip().startswith("T") and lines[line].rstrip()[1:].isdigit():

# purge tower
if tower_line == -1:
tower_line = 0
for i2 in range(20):
if lines[line-i2].rstrip().startswith("; CP TOOLCHANGE START"):
tower_line = line-i2
break

# z-hop before toolchange
zhop = 0
zhop_line = 0
if tower_line == 0:
for i2 in range(20):
if lines[line-i2].rstrip().startswith("; custom gcode: end_filament_gcode"):
if lines[line-i2-1].rstrip().startswith("G1 Z"):
split = lines[line-i2-1].rstrip().split(" ")
if split[1].startswith("Z"):
zhop = float(split[1].replace("Z", ""))
if zhop > 0.0:
zhop_line = line-i2-1

# toolchange line
toolchange_line = 0
for i2 in range(20):
if lines[line + i2].rstrip().startswith("T") and lines[line].rstrip()[1:].isdigit():
toolchange_line = line + i2
break

# retraction after toolchange
retraction_line = 0
if tower_line == 0 and toolchange_line > 0:
for i2 in range(20):
if lines[toolchange_line + i2].rstrip().startswith("G1 E-"):
retraction_line = toolchange_line + i2
break

# move after toolchange
move_x = ''
move_y = ''
move_line = 0
if toolchange_line > 0:
for i2 in range(20):
if lines[toolchange_line + i2].rstrip().replace(" ", " ").startswith("G1 X"):
splittedstring = lines[toolchange_line + i2].rstrip().replace(" ", " ").split(" ")
if splittedstring[1].startswith("X"):
if splittedstring[2].startswith("Y"):
move_x = splittedstring[1].rstrip()
move_y = splittedstring[2].rstrip()
move_line = toolchange_line + i2
break

# z-drop after toolchange
move_z = ''
zdrop_line = 0
if tower_line == 0:
if lines[move_line + 1].rstrip().startswith("G1 Z"):
zdrop_line = move_line + 1
elif lines[move_line + 2].rstrip().startswith("G1 Z"):
zdrop_line = move_line + 2
if zdrop_line > 0:
split = lines[zdrop_line].rstrip().split(" ")
if split[1].startswith("Z"):
move_z = split[1].rstrip()

# extrusion after move
extrusion_line = 0
if tower_line == 0 and move_line > 0:
for i2 in range(5):
if lines[move_line + i2].rstrip().startswith("G1 E"):
extrusion_line = move_line + i2
break

# make toolshift changes
if toolshift_count > 0 and toolchange_line > 0 and move_line > 0:
file_has_changed = True

if zhop_line > 0:
lines[zhop_line] = '; Z-Hop removed by RatOS IDEX Postprocessor: ' + lines[zhop_line].rstrip() + '\n'

if zdrop_line > 0:
lines[zdrop_line] = '; Z-Drop removed by RatOS IDEX Postprocessor: ' + lines[zdrop_line].rstrip() + '\n'

new_toolchange_gcode = ('TOOL T=' + lines[toolchange_line].rstrip().replace("T", "") + ' ' + move_x.replace("X", "X=") + ' ' + move_y.replace("Y", "Y=") + ' ' + move_z.replace("Z", "Z=")).rstrip()
lines[toolchange_line] = new_toolchange_gcode + '\n'
lines[move_line] = '; Horizontal move removed by RatOS IDEX Postprocessor: ' + lines[move_line].rstrip().replace(" ", " ") + '\n'

if retraction_line > 0 and extrusion_line > 0:
lines[retraction_line] = '; Retraction removed by RatOS IDEX Postprocessor: ' + lines[retraction_line].rstrip() + '\n'
lines[extrusion_line] = '; Deretraction removed by RatOS IDEX Postprocessor: ' + lines[extrusion_line].rstrip() + '\n'

# add START_PRINT parameters
if start_print_line > 0:
if toolshift_count > 0:
file_has_changed = True
lines[start_print_line] = lines[start_print_line].rstrip() + ' TOTAL_TOOLSHIFTS=' + str(toolshift_count - 1) + '\n'
if first_x >= 0 and first_y >= 0:
file_has_changed = True
lines[start_print_line] = lines[start_print_line].rstrip() + ' FIRST_X=' + str(first_x) + ' FIRST_Y=' + str(first_y) + '\n'
if min_x < 1000:
file_has_changed = True
lines[start_print_line] = lines[start_print_line].rstrip() + ' MIN_X=' + str(min_x) + ' MAX_X=' + str(max_x) + '\n'
if len(used_tools) > 0:
file_has_changed = True
lines[start_print_line] = lines[start_print_line].rstrip() + ' USED_TOOLS=' + ','.join(used_tools) + '\n'
lines[start_print_line] = lines[start_print_line].rstrip() + ' WIPE_ACCEL=' + str(wipe_accel) + '\n'

# console output
self.ratos_echo("USED TOOLS: " + ','.join(used_tools))
self.ratos_echo("TOOLSHIFTS: " + str(0 if toolshift_count == 0 else toolshift_count - 1))
self.ratos_echo("SLICER: " + slicer["Name"] + " " + slicer["Version"])

# save file if it has changed
if file_has_changed:
lines[1] = lines[1].rstrip()+ "; processed by RatOS\n"
self.save_file(path, lines)

self.ratos_echo("Done!")
return True

def already_processed(self, path):
readfile = None
try:
i = 0
with open(path, 'r', encoding='utf-8') as readfile:
for line in readfile:
i += 1
if i == 2:
return line.rstrip().lower().startswith("; processed by ratos")
except:
raise self.printer.command_error("Can not get processed state")
finally:
readfile.close()

def get_slicer(self, line):
try:
line = line.rstrip().replace("; generated by ", "")
split = line.split(" on ")[0]
return {"Name": split.split(" ")[0], "Version": split.split(" ")[1]}
except:
raise self.printer.command_error("Can not get slicer version")

def get_file_path(self, filename):
files = self.v_sd.get_file_list(True)
flist = [f[0] for f in files]
files_by_lower = { filepath.lower(): filepath for filepath, fsize in files }
filepath = filename
try:
if filepath not in flist:
filepath = files_by_lower[filepath.lower()]
return os.path.join(self.sdcard_dirname, filepath)
except:
raise self.printer.command_error("Can not get path for file " + filename)

def get_file_lines(self, filepath):
try:
with open(filepath, "r", encoding='UTF-8') as readfile:
return readfile.readlines()
except:
raise self.printer.command_error("Unable to open file")
finally:
readfile.close()

def save_file(self, path, lines):
writefile = None
try:
pause_counter = 0
with open(path, "w", newline='\n', encoding='UTF-8') as writefile:
for i, strline in enumerate(lines):
pause_counter += 1
if pause_counter == 1000:
pause_counter = 0
self.reactor.pause(.001)
writefile.write(strline)
except Exception as exc:
raise self.printer.command_error("FileWriteError: " + str(exc))
finally:
writefile.close()

#####
# Helper
#####
def ratos_echo(self, msg):
self.gcode.run_script_from_command("RATOS_ECHO PREFIX='POST_PROCESSOR' MSG='" + str(msg) + "'")

def ratos_debug_echo(self, msg):
self.gcode.run_script_from_command("DEBUG_ECHO PREFIX='POST_PROCESSOR' MSG='" + str(msg) + "'")

#####
# Loader
#####
def load_config(config):
return RatOS_Post_Processor(config)
12 changes: 12 additions & 0 deletions macros/idex/overrides.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ gcode:

{% endif %}


[gcode_macro M106]
# Only rename_existing if you have a sacrificial [fan] section
rename_existing: M106.1
Expand Down Expand Up @@ -114,8 +115,19 @@ gcode:
# Update core Klipper's fan speed
M106.1 S{s}


[gcode_macro M107]
rename_existing: M107.1
gcode:
{% set p = params.P|default(-1)|int %}
M106 S0 P{p}


[gcode_macro SDCARD_PRINT_FILE]
rename_existing: SDCARD_PRINT_FILE_BASE
gcode:
{% if printer["ratos_post_processor"] is defined %}
RATOS_POST_PROCESSOR { rawparams }
{% else %}
SDCARD_PRINT_FILE_BASE { rawparams }
{% endif %}
4 changes: 3 additions & 1 deletion printers/caramba-idex/caramba.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,6 @@ inverted: true

[dual_carriage]
axis: x
safe_distance: 70
safe_distance: 70

[ratos_post_processor]
Loading

0 comments on commit 9d707c5

Please sign in to comment.