diff --git a/MMU-Testobject.scad b/MMU-Testobject.scad new file mode 100644 index 0000000..654d192 --- /dev/null +++ b/MMU-Testobject.scad @@ -0,0 +1,164 @@ +// +// Multi Material Test Object +// +// Nikolai Rinas - 22/12/2018 +// + +$fn = 180; + +nozzle = 0.4; +thinWall = nozzle*2; + +print = "all"; +//print = "base"; +//print = "windows"; +//print = "tubes"; + +baseWidth = 38; +baseHeight = 28; +baseDepth = thinWall*3; + +windowHeightOffset = 4; +windowWidthOffset = 4; +windowWidth = 10; +windowHeight = 10; +windowDepth = thinWall; +windowInlay = 3; +firstWindowX = 8; +firstWindowY = 3; +firstWindowDepth = nozzle; +secondWindowX = firstWindowX * 2 + windowWidthOffset; +secondWindowY = firstWindowY; +thirdWindowX = firstWindowX; +thirdWindowY = firstWindowY+ windowHeight/1.3 + windowHeightOffset; +thirdWindowDepth = firstWindowDepth; +fourthWindowX = secondWindowX; +fourthWindowY = thirdWindowY; + +towerDiameter = 10; +towerYOffset = 2; +towerHeight = baseHeight - 6; + +leftTowerX = 0; +leftTowerY = towerYOffset; +rightTowerX = baseWidth; +rightTowerY = towerYOffset; + +towerBaseHeight = towerYOffset + 2; +towerBaseDiameter = towerDiameter*1.5; +leftTowerBaseX = 0; +leftTowerBaseY = 0; +rightTowerBaseX = baseWidth; +rightTowerBaseY = 0; +leftTowerTopX = 0; +leftTowerTopY = towerHeight + towerYOffset; +rightTowerTopX = baseWidth; +rightTowerTopY = towerHeight + towerYOffset; + +barWidth = 2.5; +barHeight = baseWidth - towerDiameter; + +module windowFrame(x,y,width=thinWall) { + translate([x,thinWall,y]){ + cube([windowWidth,width,windowHeight], center=false); + } +} + +module placeWindow(x,y,width=thinWall) { + windowFrame(x,y,width); + // Cut for the first window + translate([x+(windowInlay/2),-1,y+(windowInlay/2)]) { + cube([windowWidth-windowInlay,baseDepth+2,windowHeight-windowInlay], center=false); + } +} + +module placeTower(x,y) { + translate([x,0,y]) { + cylinder(towerHeight, d=towerDiameter, center=false); + } +} + +module placeCrossBeam(d,height) { + translate([height/5.5,-2,baseHeight-d/1.9]) { + rotate([0,90,0]) { + //cylinder(height,d=d,center=false); + cube([d/1.5,d/2,height], center=false); + } + } +} + +module placeTowerFrame(x,y) { + translate([x,0,y]) { + cylinder(towerBaseHeight, d=towerBaseDiameter, center=false); + } + if ( y == 0 ) { + translate([x,0,y+towerBaseHeight]) { + cylinder(towerBaseHeight*2,towerBaseDiameter/2,0, center=false); + } + }else{ + translate([x,0,y-towerBaseHeight*2]) { + cylinder(towerBaseHeight*2,0,towerBaseDiameter/2, center=false); + } + } +} + +module plaBase() { + union() { + difference(){ + cube([baseWidth,baseDepth,baseHeight], center=false); + placeWindow(firstWindowX, firstWindowY, firstWindowDepth); + placeWindow(secondWindowX, secondWindowY); + placeWindow(thirdWindowX, thirdWindowY, thirdWindowDepth); + placeWindow(fourthWindowX, fourthWindowY); + placeTower(leftTowerX, leftTowerY); + placeTower(rightTowerX, rightTowerY); + } + difference() { + placeTowerFrame(leftTowerBaseX, leftTowerBaseY); + placeTower(leftTowerX, leftTowerY); + } + difference() { + placeTowerFrame(rightTowerBaseX, rightTowerBaseY); + placeTower(rightTowerX, rightTowerY); + } + difference() { + placeTowerFrame(leftTowerTopX, leftTowerTopY); + placeTower(leftTowerX, leftTowerY); + placeCrossBeam(barWidth,barHeight); + } + difference() { + placeTowerFrame(rightTowerTopX, rightTowerTopY); + placeTower(rightTowerX, rightTowerY); + placeCrossBeam(barWidth,barHeight); + } + } +} + +module petgWindows() { + windowFrame(firstWindowX, firstWindowY, firstWindowDepth); + windowFrame(secondWindowX, secondWindowY); + windowFrame(thirdWindowX, thirdWindowY, thirdWindowDepth); + windowFrame(fourthWindowX, fourthWindowY); +} + +module absTower() { + placeTower(leftTowerX, leftTowerY); + placeTower(rightTowerX, rightTowerY); +} + +if ( print == "base" || print == "all" ) { + color("lime"){ + plaBase(); + } +} +if ( print == "windows" || print == "all" ) { + color("LightBlue", alpha = 0.5){ + petgWindows(); + } +} +if ( print == "tubes" || print == "all" ) { + color("red") { + absTower(); + placeCrossBeam(barWidth,barHeight); + } +} \ No newline at end of file diff --git a/mmuGcodeParser.bat b/mmuGcodeParser.bat new file mode 100644 index 0000000..0572e12 --- /dev/null +++ b/mmuGcodeParser.bat @@ -0,0 +1,9 @@ +@ECHO OFF +SET filepath=%~f1 + +IF NOT EXIST "%filepath%" ( + ECHO %~n0: file not found - %filepath% >&2 + EXIT /B 1 +) +ECHO Adjusting the gcode file. Please wait ... +C:\Users\nikol01\AppData\Local\Continuum\anaconda3\python.exe C:\Users\nikol01\ownCloud\PycharmProjects\MMUGcodeParser\mmuGcodeParser.py %filepath% \ No newline at end of file diff --git a/mmuGcodeParser.py b/mmuGcodeParser.py new file mode 100644 index 0000000..9398fcc --- /dev/null +++ b/mmuGcodeParser.py @@ -0,0 +1,381 @@ +#!/usr/local/bin/python3 +# encoding: utf-8 + +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.1" +MYGCODEMARK = " ; MMUGCODEPARSER " + VERSION +UNLOAD_START_LINE = "unloadStartLine" +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" + +debug_set = False +ram_temp_diff = 10 + +# get the input file specified, and turn it into a path variable for the current OS +# inpath = os.path.normpath(args.input) +# inpath = os.path.normpath("Yang_0.2mm_PLA_PETG_MK3MMU2.gcode") +# inpath = os.path.normpath("KobayashiFidgetCube-dual-CubesOnly_0.15mm_PLA_MK3MMU2.gcode") +inpath = sys.argv[1] + +# if there is no output specified, call it "..._ramcool.gcode" based off the input filename +# if args.output == "none": +outpath = os.path.normpath(os.path.splitext(inpath)[0] + "_adjusted.gcode") +# else: +# outpath = os.path.normpath(args.output) +# print(inpath) +# print(outpath) +# wait = input("PRESS ENTER TO CONTINUE.") + +# 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 +beforeUnload = r"^; CP TOOLCHANGE UNLOAD" +# 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) +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 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: + # 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 loading' 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 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 + 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 + +""" ------------------------- +### 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 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] + +""" ---------------------- +### 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