Files
mmuGcodeParser/mmuGcodeParser.py
Nikolai 45140c1ed3 Update mmuGcodeParser.py
Bump up the version
2019-02-01 21:55:42 -08:00

436 lines
16 KiB
Python
Executable File

#!/usr/local/bin/python3
# encoding: utf-8
"""
*
* mmuGcodeParser
*
* Created by Nikolai Rinas on 12/28/2018
* Copyright (c) 2018 Nikolai Rinas. All rights reserved.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
*/
"""
import re # regular expression library for search/replace
import os # os routines for reading/writing files
import sys # system library for input/output files
from io import open
""" ---------------------------------------------------------------------
### Constants
"""
VERSION = "v0.2"
MYGCODEMARK = " ; MMUGCODEPARSER " + VERSION
UNLOAD_START_LINE = "unloadStartLine"
LOAD_START_LINE = "loadStartLine"
DEST_TEMP_LINE = "destTempLine"
DEST_TEMP = "destTemp"
UNLOAD_LINE = "unloadLine"
PURGE_LINE = "purgeLine"
PRINT_LINE = "printLine"
CURR_TEMP = "currTemp"
TRANSITION = "transition"
LOW2HIGH = "Low2High"
HIGH2LOW = "High2Low"
NOTRANSITION = "NoTrans"
ID_LINE = "idLine"
# For debugging purpose
debug_set = False
# Drop the temperature by 10C during the ramming process. Checking if it might help
ram_temp_diff = 10
# Set this to True if you want to drop the temperature even for the same filament
ram_temp_diff_wait_for_stabilize = False
# get the input file specified, and turn it into a path variable for the current OS
inpath = sys.argv[1]
outpath = os.path.normpath(os.path.splitext(inpath)[0] + "_adjusted.gcode")
# open the input and output files (one read only, one for writing)
infile = open(inpath, 'r', encoding="utf8")
outfile = open(outpath, 'w', encoding="utf8")
""" ---------------------------------------------------------------
### Compile the regular expressions
"""
# We have to find following spots
# 1. Unload procedure
unloading = r"^T[0-9]?"
# 2. Purge
purge = r"^; CP TOOLCHANGE WIPE"
# 3. Print
printLine = r"^; CP TOOLCHANGE END"
# 4. Before unload/load
beforeUnload = r"^; CP TOOLCHANGE UNLOAD"
beforeLoad = r"^; CP TOOLCHANGE LOAD"
# 5. Target temperature
targetTemp = r"^M104 S([0-9]*)"
# start at TOOLCHANGE START comment. "^" simply indicates "beginning of line"
start = r"^; toolchange #[0-9]*"
# turn those strings into compiled regular expressions so we can search
start_detect = re.compile(start)
purge_detect = re.compile(purge)
print_detect = re.compile(printLine)
unloading_detect = re.compile(unloading)
before_unload_detect = re.compile(beforeUnload)
before_load_detect = re.compile(beforeLoad)
target_temp_detect = re.compile(targetTemp)
"""----------------------------------------------------------------------
### Functions
"""
def file_write(file, string):
# print(string)
file.write(string)
def low2high_handler(p_tool_change, p_line_number):
""" Basic idea
Cold (200C) ==> Hot (255C)
==========================
-> Ram/cool
-> Stay cool, do nothing. If needed, set lower temp but don't wait
-> Unload filament
-> Set hot. We can heating up while loading. Save some time
-> Load filament
-> Nozzle might still warming up. Load to nozzle for smooth loading process
-> Purge
-> Before start purging, wait for destination temp
-> Print
-> We are printing with the stabilized temp. No further intervention required
"""
lv_output = ""
lv_insert = 0 # 0 = don't insert, +1 = after the line, -1 before the line, -9 = comment out
if p_tool_change[p_line_number] == UNLOAD_START_LINE:
# Add temp drop for better tip
if ram_temp_diff > 0: # Only if set
lv_lower_temp = int(p_tool_change[CURR_TEMP]) - ram_temp_diff
lv_output = "M104 S" + str(lv_lower_temp)
lv_insert = 1
if p_tool_change[p_line_number] == DEST_TEMP_LINE:
# We need to stay cool here
# remove/comment existing line
lv_insert = -9
if p_tool_change[p_line_number] == UNLOAD_LINE:
# set hot (to save some time)
# insert the destination temp
lv_output = "M104 S" + p_tool_change[DEST_TEMP]
lv_insert = -1 # insert before start unloading
if p_tool_change[p_line_number] == PURGE_LINE:
# We have to wait for destination temp
lv_output = "M109 S" + p_tool_change[DEST_TEMP]
lv_insert = 1 # insert after the purge line identificator
if p_tool_change[p_line_number] == PRINT_LINE:
# We are already hot. Nothing to do here
pass
# print(toolChange["id"])
return lv_output, lv_insert
def high2low_handler(p_tool_change, p_line_number):
""" Basic idea
Hot (255C) ==> Cold (200C)
==========================
-> Ram/cool
-> We need to stay hot because hot filament is still in the nozzle. If needed, set lower temp but don't wait
-> Unload
-> Stay hot, do nothing
-> Load filament
-> Stay hot, do nothing. Load to nozzle for smooth loading process
-> Purge
-> Before start purging, set cool temp. We can cool down during the purging process
-> Print
-> Before start to print, wait for destination temp. Most likely temp will bounce pretty hard
"""
lv_output = ""
lv_insert = 0 # 0 = don't insert, +1 = after the line, -1 before the line, -9 = comment out
if p_tool_change[p_line_number] == UNLOAD_START_LINE:
if ram_temp_diff > 0: # Only if set
# Add temp drop for better tip
lv_lower_temp = int(p_tool_change[CURR_TEMP]) - ram_temp_diff
lv_output = "M104 S" + str(lv_lower_temp)
lv_insert = 1 # after the line
if p_tool_change[p_line_number] == DEST_TEMP_LINE:
# remove/comment existing line
lv_insert = -9
if p_tool_change[p_line_number] == UNLOAD_LINE:
# During unloading there is nothing to do
# In case we are dropping the temp during ramming, we need to bump it up again
if ram_temp_diff > 0: # Only if set
lv_output = "M104 S" + p_tool_change[CURR_TEMP]
lv_insert = -1 # before the line
pass
if p_tool_change[p_line_number] == PURGE_LINE:
# set to cold. We will cool down faster during purging
lv_output = "M104 S" + p_tool_change[DEST_TEMP]
lv_insert = 1 # after the line
if p_tool_change[p_line_number] == PRINT_LINE:
# wait for stable nozzle temp
lv_output = "M109 S" + p_tool_change[DEST_TEMP]
lv_insert = -1 # before the line
# print(toolChange["id"])
return lv_output, lv_insert
def none_handler(p_tool_change, p_line_number):
# Just in case we need to do something at the end
lv_output = ""
lv_insert = 0 # 0 = don't insert, +1 = after the line, -1 before the line, -9 = comment out
if CURR_TEMP in p_tool_change:
if p_tool_change[p_line_number] == UNLOAD_START_LINE:
if ram_temp_diff > 0: # Only if set
# Add temp drop for better tip
lv_lower_temp = int(p_tool_change[CURR_TEMP]) - ram_temp_diff
if ram_temp_diff_wait_for_stabilize:
# wait for stable nozzle temp
lv_output = "M109 S" + str(lv_lower_temp)
else:
# set nozzle temp without wait
lv_output = "M104 S" + str(lv_lower_temp)
lv_insert = 1 # after the line
if p_tool_change[p_line_number] == LOAD_START_LINE:
if ram_temp_diff > 0: # Only if set
lv_restore_temp = int(p_tool_change[CURR_TEMP])
# don't wait for stable nozzle temperature
# (enough time for nozzle to reach correct temp)
lv_output = "M104 S" + str(lv_restore_temp)
lv_insert = 1
if p_tool_change[p_line_number] == DEST_TEMP_LINE:
# nothing to do
pass
if p_tool_change[p_line_number] == UNLOAD_LINE:
# nothing to do
pass
if p_tool_change[p_line_number] == PURGE_LINE:
# nothing to do
pass
if p_tool_change[p_line_number] == PRINT_LINE:
# nothing to do
pass
# print(toolChange["id"])
return lv_output, lv_insert
""" ------------------------------------------------------------------------------
### Main process
"""
""" ----------------------------------------------
### Scan the gcode for tool changes and the values
"""
# walk through each line in the file
myToolChanges = {} # dictionary with all tool changes
line_number = 1 # index in the loop
toolChangeID = 0 # required to track the current tool change
initTemp = 0 # required to track the current temp
for line in infile:
# Search for the tool change starts
start_match = start_detect.search(line)
if start_match is not None:
start_position_match = re.search(r"[0-9]*\Z", start_match.group(0))
if start_position_match is not None:
# create a dictionary
myToolChange = {}
switchID = start_position_match.group(0)
# remember the tool change start position
myToolChange["id"] = switchID
myToolChange[line_number] = ID_LINE
toolChangeID = switchID # Remember the last tool change ID for later reference
# create dictionary entry
myToolChanges[toolChangeID] = myToolChange
# Search for the 'before unloading' position
before_unload_match = before_unload_detect.search(line)
if before_unload_match is not None:
if len(myToolChanges) > 0: # we found at least the start tool change
# remember the line number
myToolChanges[toolChangeID][line_number] = UNLOAD_START_LINE
# Search for the 'before loading' position
before_load_match = before_load_detect.search(line)
if before_load_match is not None:
if len(myToolChanges) > 0: # we found at least the start tool change
# remember the line number
myToolChanges[toolChangeID][line_number] = LOAD_START_LINE
# Search for the target temperature
targetTemp_match = target_temp_detect.search(line)
if targetTemp_match is not None:
if len(myToolChanges) > 0: # we found at least the start tool change
# print(myToolChanges[toolChangeID])
if DEST_TEMP_LINE not in myToolChanges[toolChangeID]:
# determine the temperature value
tempMatch = re.search(r"S[0-9]*", line)
temp = tempMatch.group(0).replace("S", "")
if DEST_TEMP not in myToolChanges[toolChangeID]: # do not overwrite in case of temp changes
# Remember the temperature
myToolChanges[toolChangeID][DEST_TEMP] = temp
# remember the line number
myToolChanges[toolChangeID][line_number] = DEST_TEMP_LINE
else:
# Search for the initial Temperature
if initTemp == 0:
# We need this value later to determine the transition for the first tool change
tempMatch = re.search(r"S[0-9]*", line)
temp = tempMatch.group(0).replace("S", "")
initTemp = temp
# Search for the unloading command
unloading_match = unloading_detect.search(line)
if unloading_match is not None:
if len(myToolChanges) > 0: # we have already at least one entry
# remember the line number
myToolChanges[toolChangeID][line_number] = UNLOAD_LINE
# Search for the purge command
purge_match = purge_detect.search(line)
if purge_match is not None:
if len(myToolChanges) > 0: # we have already at least one entry
# remember the line number
myToolChanges[toolChangeID][line_number] = PURGE_LINE
# Search for the print command
print_match = print_detect.search(line)
if print_match is not None:
if len(myToolChanges) > 0: # we have already at least one entry
# remember the line number
myToolChanges[toolChangeID][line_number] = PRINT_LINE
# increment the line number
line_number = line_number + 1
"""
for toolChange in myToolChanges:
if DEST_TEMP in myToolChanges[toolChange]:
print(myToolChanges[toolChange])
else:
print("Key is missing")
print(myToolChanges[toolChange])
"""
""" -------------------------
### Determine the transitions
"""
# Determine the transitions for the tool changes
lastTemp = initTemp
for toolChange in myToolChanges:
# Last tool change is unloading only
# Special handler required in case we need to do something there
if DEST_TEMP in myToolChanges[toolChange]:
if myToolChanges[toolChange][DEST_TEMP] == "0":
myToolChanges[toolChange][TRANSITION] = NOTRANSITION
else:
if lastTemp > myToolChanges[toolChange][DEST_TEMP]:
# Transition from higher value to lower value
myToolChanges[toolChange][TRANSITION] = HIGH2LOW
else:
if lastTemp == myToolChanges[toolChange][DEST_TEMP]:
# If there is no difference in temperature, no transition is required
myToolChanges[toolChange][TRANSITION] = NOTRANSITION
else:
# Transition from lower to higher value
myToolChanges[toolChange][TRANSITION] = LOW2HIGH
# Save current temperature. Needed for first tool change
myToolChanges[toolChange][CURR_TEMP] = lastTemp
# Remember the last temperature
lastTemp = myToolChanges[toolChange][DEST_TEMP]
else:
myToolChanges[toolChange][TRANSITION] = NOTRANSITION
""" ----------------------
### Update the gcode file
"""
# Here we have all the data to make our decisions and updating the gcode file
# Modify the file
line_number = 1
# Go back to the fist position in the input file
infile.seek(0)
for line in infile:
output = "" # reset the output
action = 0 # reset the insert position
# Check our dictionary if we have an entry for this line
for toolChange in myToolChanges:
if line_number in myToolChanges[toolChange]:
if myToolChanges[toolChange][TRANSITION] == LOW2HIGH:
# Calling handler for the lower to higher value transition
output, action = low2high_handler(myToolChanges[toolChange], line_number)
if myToolChanges[toolChange][TRANSITION] == HIGH2LOW:
# Calling handler for the higher to lower value transition
output, action = high2low_handler(myToolChanges[toolChange], line_number)
if myToolChanges[toolChange][TRANSITION] == NOTRANSITION:
# Calling handler for no transition case
output, action = none_handler(myToolChanges[toolChange], line_number)
# Perform the action determined for this line
if action == 1: # insert after this line
file_write(outfile, line)
file_write(outfile, output + MYGCODEMARK + "\n")
if action == -1: # insert before this line
file_write(outfile, output + MYGCODEMARK + "\n")
file_write(outfile, line)
if action == -9: # comment out the line
file_write(outfile, ";" + line)
if action == 0: # leave the line untouched
file_write(outfile, line)
# pass
# increase line index
line_number = line_number + 1
# Print a nice summery at the end of the file
if debug_set is True:
file_write(outfile, "; Debug information: " + MYGCODEMARK + "\n")
for toolChange in myToolChanges:
file_write(outfile, "; " + str(myToolChanges[toolChange]) + "\n")
file_write(outfile, ';;;;"Prusa PLA MMU2";')
# print(myToolChanges)
# end