#!/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 */ """ 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