|
#! python3 |
|
from sys import argv |
|
from pathlib import Path |
|
import re |
|
from time import sleep |
|
try: |
|
from tqdm import tqdm |
|
from tqdm import write |
|
except ImportError: |
|
tqdm = None |
|
write = print |
|
|
|
# clean up some excess comments. set to False if you want to chain other scripts which rely on comments |
|
# removes additional in-line feature comments after G1 commands as well as ";WIDTH:" comments |
|
REDUCE_FILESIZE = False |
|
|
|
# Set your fan speed overrides for any feature here! |
|
# types: ['Support material', 'Perimeter', 'Custom', 'Top solid infill', 'Internal infill', 'Support material interface', 'Bridge infill', 'External perimeter', 'Solid infill', 'Overhang perimeter', 'Skirt/Brim'] |
|
OVERRIDES = { |
|
"Support material interface": 255, |
|
#"Support material": 255, # If this (mostly) freezes the material fast enough, it may reduce curling... If not, cooling boost on supports probably makes curling worse... |
|
} |
|
|
|
# TODO: Add min/max range overrides which clamp values to between these values during a feature? Per-feature fan speed multiplier? |
|
|
|
# Common type comments: |
|
# {';TYPE:Skirt/Brim', ';TYPE:External perimeter', ';TYPE:Top solid infill', ';TYPE:Overhang perimeter', ';TYPE:Internal infill', ';TYPE:Support material', ';TYPE:Support material interface', ';TYPE:Bridge infill', ';TYPE:Perimeter', ';TYPE:Custom', ';TYPE:Solid infill'} |
|
|
|
# for a fan speed command (M106), return the requested speed value |
|
def getSpeed(line): |
|
try: |
|
return float(re.search("S\\d+\\.?\\d*",line).group(0).replace("S","")) |
|
except Exception as e: |
|
write(f"failed to parse speed from line '{line}' - {e}") |
|
return 255.0 # no match will also yield an exception (M106 without any S parameter sets full speed) |
|
|
|
# for a given fan speed, yield the correct gcode command (M107 if speed 0, M106 with speed otherwise). Add a comment to the line. |
|
def getFanCommand(speed:float, marker:str="OVERRIDE"): |
|
return "M107 ; {marker}: fan off" if (not isinstance(speed, (float,int))) or speed <= 0 else f"M106 S{speed:.1f} ; {marker}: set speed" |
|
|
|
# get the gcode file to process |
|
in_file = Path(argv[1]) |
|
assert in_file.is_file() and in_file.exists(), f"Input arg '{argv[1]}' does not resolve to a valid file!" |
|
|
|
set_speed = 0.0 |
|
out_lines = [] |
|
override_fan = False |
|
overridden_command_count = 0 |
|
overridden_feature_count = 0 |
|
fan_commands_kept_count = 0 |
|
|
|
for line in in_file.read_text().splitlines() if tqdm is None else tqdm(in_file.read_text().splitlines()): |
|
# process line input |
|
line = line.strip() |
|
# process feature type changes |
|
if line.startswith(";TYPE:"): |
|
out_lines.append(line) # pass the type comment to the output |
|
feature_type = line[len(";TYPE:"):] |
|
if feature_type in OVERRIDES: # if this type has an override, enter override mode and emit the requested override fan speed command (M106) |
|
override_fan = True |
|
out_lines.append(getFanCommand(OVERRIDES[feature_type], "START OVERRIDE")) |
|
overridden_feature_count += 1 |
|
elif override_fan: # when exiting from an override type, return to the last requested fan speed from the original gcode. Set override mode off. |
|
out_lines.append(getFanCommand(set_speed, "END OVERRIDE")) |
|
override_fan = False |
|
# process fan commands |
|
elif line.startswith("M106"): |
|
set_speed = getSpeed(line) # store the last fan speed requested by the original gcode |
|
if not override_fan: # do not emit fan speed changes while fan override is active |
|
out_lines.append(line) |
|
fan_commands_kept_count += 1 |
|
else: |
|
overridden_command_count += 1 |
|
if not REDUCE_FILESIZE: |
|
out_lines.append(";{line}") |
|
elif line.startswith("M107"): # interpret fan off command as fan speed 0 |
|
set_speed = 0 |
|
if not override_fan: # do not emit fan speed changes while fan override is active |
|
out_lines.append(line) |
|
fan_commands_kept_count += 1 |
|
else: |
|
overridden_command_count += 1 |
|
if not REDUCE_FILESIZE: |
|
out_lines.append(";{line}") |
|
elif line.startswith("G1") and REDUCE_FILESIZE: # most lines are G1 commands. remove verbose comments, save some space |
|
out_lines.append(line.split(";")[0].strip()) |
|
elif line.startswith(";WIDTH:") and REDUCE_FILESIZE: |
|
pass # some comments must be kept (e.g. thumbnail) but there can be a lot of these which we won't need |
|
else: # if the line does not match any pattern we want to change, pass it on as-is. |
|
out_lines.append(line) |
|
|
|
# if we end the gcode in an override state, finally return to the last requested fan speed, ususally off. |
|
if override_fan: |
|
out_lines.append("M107 ; END OVERRIDE: return to fan off" if set_speed <= 0 else f"M106 S{set_speed:.1f} ; END OVERRIDE: return to requested speed") |
|
|
|
# overwrite the file with our new lines |
|
in_file.write_text("\n".join(out_lines)) |
|
# some metrics to observe the fact that something has happened (for the one second they are visible) |
|
print(f"done! {overridden_command_count} fan speed commands removed, for {overridden_feature_count} local feature overrides. {fan_commands_kept_count} fan commands unchanged.") |
|
sleep(1.0) # wait a second to display the "done" message in the terminal. Optional. |