-
Notifications
You must be signed in to change notification settings - Fork 60
Open
Description
Made with AI.. but AI isn't so intelligent..... :
#!/usr/bin/env python3
PenColorizer.py - Adapted for Orca Slicer / Bambu Studio post-processing
Adds pen changing & offset drawing logic for multi-pen plotting on 3D printer
import re
import sys
import os
class PenColorizer:
def init(self):
# These were your settings — you can make them command-line args later if needed
self.penstartx = 28.0
self.penstarty = 238.0 # note: this was called FirstPenZPosition, but used as Y!
self.xoffset = 36.1
self.yoffset = 45.8
self.zoffset = 3.2
self.extra_retraction = 5.5
self.interlace = True # only draw every second "color"/tool
self.last_z = 0.2
def get_pen(self, pen):
penside = pen % 2
penoffset = float(pen // 2) * 68.0
if penside == 0:
return [
f";Get pen {pen}",
"G0 F5000 ; set speed fast",
f"G0 X{self.penstartx + penoffset:.3f} Y{self.penstarty - 152.0:.3f} ;go under pen",
"G0 F2000 ; set speed slow",
f"G0 X{self.penstartx + penoffset:.3f} Y{self.penstarty:.3f} ; lift pen",
f"G0 X{self.penstartx + 20.5 + penoffset:.3f} Y{self.penstarty:.3f} ; move pen right",
"G0 F5000 ; set speed fast",
f"G0 X{self.penstartx + 20.5 + penoffset:.3f} Y{self.penstarty - 152.0:.3f} ; lower pen"
]
else:
return [
f";Get pen {pen}",
"G0 F5000 ; set speed fast",
f"G0 X{self.penstartx + 41.0 + penoffset:.3f} Y{self.penstarty - 152.0:.3f} ;go under pen",
"G0 F2000 ; set speed slow",
f"G0 X{self.penstartx + 41.0 + penoffset:.3f} Y{self.penstarty:.3f} ; lift pen",
f"G0 X{self.penstartx + 20.5 + penoffset:.3f} Y{self.penstarty:.3f} ; move pen right",
"G0 F5000 ; set speed fast",
f"G0 X{self.penstartx + 20.5 + penoffset:.3f} Y{self.penstarty - 152.0:.3f} ; lower pen"
]
def put_pen(self, pen):
penside = pen % 2
penoffset = float(pen // 2) * 68.0
if penside == 0:
return [
f";put pen {pen}",
"G0 F5000 ; set speed fast",
f"G0 X{self.penstartx + 20.5 + penoffset:.3f} Y{self.penstarty - 152.0:.3f} ; go under pen",
f"G0 X{self.penstartx + 20.5 + penoffset:.3f} Y{self.penstarty:.3f} ; lift pen",
"G0 F2000 ; set speed slow",
f"G0 X{self.penstartx + penoffset:.3f} Y{self.penstarty:.3f} ; move pen left",
f"G0 X{self.penstartx + penoffset:.3f} Y{self.penstarty - 152.0:.3f} ; lower pen",
"G0 F5000 ; set speed fast",
""
]
else:
return [
f";put pen {pen}",
"G0 F5000 ; set speed fast",
f"G0 X{self.penstartx + 20.5 + penoffset:.3f} Y{self.penstarty - 152.0:.3f} ; go under pen",
f"G0 X{self.penstartx + 20.5 + penoffset:.3f} Y{self.penstarty:.3f} ; lift pen",
"G0 F2000 ; set speed slow",
f"G0 X{self.penstartx + 41.0 + penoffset:.3f} Y{self.penstarty:.3f} ; move pen left",
f"G0 X{self.penstartx + 41.0 + penoffset:.3f} Y{self.penstarty - 152.0:.3f} ; lower pen",
"G0 F5000 ; set speed fast",
""
]
def offset_gcode(self, line, extra_z=0.0):
if not (line.startswith('G0') or line.startswith('G1')):
return line
parts = []
cmd = line.split(' ', 1)[0]
parts.append(cmd)
coords = re.findall(r'([XYZF])(-?\d*\.?\d+)', line)
f_found = False
for axis, value in coords:
val = float(value)
if axis == 'F':
parts.append(f"F3600") # hard-coded as in original
f_found = True
elif axis == 'X':
parts.append(f"X{val + self.xoffset:.3f}")
elif axis == 'Y':
parts.append(f"Y{val + self.yoffset:.3f}")
elif axis == 'Z':
parts.append(f"Z{val + self.zoffset + extra_z:.3f}")
if not f_found and 'F' in line: # fallback
parts.append("F3600")
return ' '.join(parts)
def lift_gcode(self, line, extra_z=0.0):
if not (line.startswith('G0') or line.startswith('G1')):
return line
parts = []
cmd = line.split(' ', 1)[0]
parts.append(cmd)
coords = re.findall(r'([XYZF])(-?\d*\.?\d+)', line)
for axis, value in coords:
val = float(value)
if axis == 'F':
parts.append(f"F{val:.0f}")
elif axis == 'X':
parts.append(f"X{val:.3f}")
elif axis == 'Y':
parts.append(f"Y{val:.3f}")
elif axis == 'Z':
parts.append(f"Z{val + extra_z:.3f}")
return ' '.join(parts)
def get_first_position_line(self, draw_block):
for ln in draw_block:
if 'G0' in ln or 'G1' in ln:
if 'X' in ln and 'Y' in ln and 'Z' in ln:
return ln
return None
def add_explicit_z(self, lines):
new_lines = []
for line in lines:
if ('G0' in line or 'G1' in line) and 'Z' not in line:
line += f" Z{self.last_z:.3f}"
elif 'Z' in line:
m = re.search(r'Z(-?\d*\.?\d+)', line)
if m:
self.last_z = float(m.group(1))
new_lines.append(line)
return new_lines
def process(self, gcode_text):
data = gcode_text.splitlines(keepends=True)
# We'll process in-place; collect modified lines
output = []
layer_num = -1
cur_tool = 0
last_x, last_y, last_z = 100.0, 100.0, 0.3
is_layer = False
is_prime_tower = False
is_skirt_support_fill = False
draw_buffers = [[] for _ in range(8)] # per "tool"/pen
i = 0
while i < len(data):
line = data[i].rstrip('\r\n')
orig_line = line
if line.startswith(';LAYER:'):
layer_num += 1
is_layer = True
output.append(orig_line)
i += 1
continue
if ';layer_height =' in line:
try:
lh = float(line.split('=')[1].strip())
# you can use lh if needed
except:
pass
output.append(orig_line)
i += 1
continue
if ';TYPE:' in line:
if 'PRIME-TOWER' in line:
is_prime_tower = True
is_skirt_support_fill = False
elif any(t in line for t in ['SKIRT', 'SUPPORT', 'SUPPORT-INTERFACE', 'FILL']):
is_skirt_support_fill = True
is_prime_tower = False
else:
is_skirt_support_fill = False
is_prime_tower = False
output.append(orig_line)
i += 1
continue
draw_this = (cur_tool >= 0 and
not is_skirt_support_fill and
not is_prime_tower and
((layer_num + cur_tool) % 2 == 0 or not self.interlace))
if ('G0' in line or 'G1' in line):
# update last known pos
if 'X' in line:
m = re.search(r'X(-?\d*\.?\d+)', line)
if m: last_x = float(m.group(1))
if 'Y' in line:
m = re.search(r'Y(-?\d*\.?\d+)', line)
if m: last_y = float(m.group(1))
if 'Z' in line:
m = re.search(r'Z(-?\d*\.?\d+)', line)
if m: last_z = float(m.group(1))
e_match = re.search(r'E(-?\d*\.?\d+)', line)
if e_match:
e_val = float(e_match.group(1))
if e_val < 0:
# retract → assume z-hop possible
extra_z_offset = 3.0
else:
extra_z_offset = 0.0
else:
extra_z_offset = 0.0
if draw_this:
draw_buffers[cur_tool].append(self.offset_gcode(line, extra_z_offset))
else:
output.append(orig_line)
elif line.startswith('T'):
try:
cur_tool = int(line[1:]) - 1
except:
cur_tool = 0
output.append(';' + orig_line) # comment out tool change
i += 1
continue
elif any(t in line for t in ['T1','T2','T3','T4','T5','T6','T7','T8']):
output.append(';' + orig_line)
i += 1
continue
# Skip heating in object layers if desired
elif is_layer and any(cmd in line for cmd in ['M104','M109','M105']):
# optionally comment or remove
pass
else:
output.append(orig_line)
i += 1
# Now insert drawing blocks per layer (simplified – you may need to adjust insertion point)
# For simplicity we append at end of each ;LAYER block, but ideally insert before next layer or after inner walls
# This is a simplification – test & adjust insertion logic
final_output = []
in_layer = False
for ln in output:
final_output.append(ln)
if ln.startswith(';LAYER:') and layer_num >= 0:
in_layer = True
# retract before drawing
final_output.append(f"G1 F1500 E-{self.extra_retraction}\n")
for pen in range(8):
block = draw_buffers[pen]
if len(block) < 2: # almost empty
continue
first_pos = self.get_first_position_line(block)
if first_pos:
final_output.append(self.lift_gcode(first_pos, 3.0) + " ; lower pen from above first spot\n")
final_output.extend(self.get_pen(pen))
final_output.extend(block)
if block:
final_output.append(self.lift_gcode(block[-1], 3.0) + " ; raise pen after last spot\n")
final_output.extend(self.put_pen(pen))
# return after drawing
final_output.append(f"G0 X{last_x:.3f} Y{last_y:.3f} Z{last_z + 2.0:.3f}\n")
final_output.append(f"G0 X{last_x:.3f} Y{last_y:.3f} Z{last_z:.3f} ; return to last pos after drawing\n")
final_output.append(f"G1 F1500 E{self.extra_retraction}\n\n")
return ''.join(final_output)
if name == "main":
if len(sys.argv) < 2:
print("Usage: python PenColorizer.py <gcode_file>")
print(" The script modifies the file in place.")
sys.exit(1)
input_path = sys.argv[1]
if not os.path.isfile(input_path):
print(f"File not found: {input_path}")
sys.exit(1)
with open(input_path, 'r', encoding='utf-8') as f:
gcode = f.read()
processor = PenColorizer()
modified = processor.process(gcode)
with open(input_path, 'w', encoding='utf-8') as f:
f.write(modified)
print(f"Processed and overwritten: {input_path}")
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
No labels