PrintBuddy/main.py

533 lines
13 KiB
Python

import serial
import time
import threading
import math
print("PrintBuddy V0.1")
print("""
Copyright (C) 2021 Thomas Van Acker
This program comes with ABSOLUTELY NO WARRANTY.
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 <https://www.gnu.org/licenses/>.""")
print("")
# Static var init
COMMANDS = {
"help":"List all commands with explanation",
"exit":"Disconnect from printer and exit",
"raw":"Send raw gcode commands",
"home":"Auto-home the machine",
"load":"Load a gcode file",
"add":"Manipulate the loaded gcode",
"print":"Print the loaded gcode",
"level":"Start bed leveling wizard",
"mesh":"Start mesh bed leveling wizard"
}
GCODE_FILCH_LAYER = ["G28 X Y", "M84", "M300 P3000", "M0", "M600", "M400", "G28 X Y", "G0 F1500"]
# Function declarations
def send(c, silent=False):
global s
if not silent:
print("%s"%c)
c += "\n"
s.write(c.encode("utf-8"))
while True:
r = s.readline().decode("utf-8").strip()
if not silent:
print(" > %s"%r)
if r[0:2] == "ok":
break
def threadPrintInput():
# Checks for input from user during printing process
global abortPrint
while printing:
i = input()
if i == "stop" or i == "exit":
abortPrint = True
break
def meshZ(X, Y):
# Interpolates values of mesh
x = X/300*(MESH_SIZE-1)
y = Y/300*(MESH_SIZE-1)
xmin = math.floor(x)
xmax = math.ceil(x)
ymin = math.floor(y)
ymax = math.ceil(y)
z0 = mesh[ymin][xmin] + (mesh[ymin][xmax]-mesh[ymin][xmin])*(x-xmin)
z1 = mesh[ymax][xmin] + (mesh[ymax][xmax]-mesh[ymax][xmin])*(x-xmin)
z = z0 + (z1-z0)*(y-ymin)
return z
# Var init
serialPort = "/dev/ttyUSB0"
serialBaud = 115200
gcodeRaw = ""
gcodeLines = []
gcodeLayers = 1
printing = False
abortPrint = False
MESH_SIZE = 9 # Number of points of a side in the mesh.
MESH_OFFSET = 1.00 # The distance above Z=0 where printbed is located. Mesh needs to be added to this number to get the absolute Z height.
mesh = [[0 for _ in range(MESH_SIZE)] for __ in range(MESH_SIZE)]
meshPositions = [["X%d Y%d"%(280/(MESH_SIZE-1)*x+10, 280/(MESH_SIZE-1)*y+10) for x in range(MESH_SIZE)] for y in range(MESH_SIZE)]
levelingCornersPos = {0:"X10 Y45",1:"X10 Y260",2:"X280 Y260",3:"X280 Y45"}
# Read mesh file
print("Reading mesh file...")
try:
f = open("mesh.txt", "r")
t = f.read()[:-1]
f.close()
lines = t.split("\n")
MESH_SIZE = int(lines[0])
mesh = [[0 for _ in range(MESH_SIZE)] for __ in range(MESH_SIZE)]
meshPositions = [["X%d Y%d"%(280/(MESH_SIZE-1)*x+10, 280/(MESH_SIZE-1)*y+10) for x in range(MESH_SIZE)] for y in range(MESH_SIZE)]
for i in range(MESH_SIZE*MESH_SIZE):
mesh[int(i/MESH_SIZE)][int(i%MESH_SIZE)] = float(lines[i+1])
print("Mesh loaded.")
except Exception as e:
print("Couldn't read mesh file. Using default mesh.")
print(e)
print("Current mesh:")
for i in mesh:
for j in i:
print("{:5.2f}".format(j), end=" ")
print()
# Connect to printer
print("\nConnecting to printer...")
s = serial.Serial(serialPort, serialBaud)
time.sleep(1)
s.write(b"\r\n\r\n") # Wake printer
time.sleep(2)
readBytes = s.inWaiting()
r = s.read(readBytes).decode("utf-8")
print(" > %s"%r)
time.sleep(5)
print("Connected!")
# Start main loop
while True:
print("\n# Please enter a command ('help' for command list)")
cmd = input()
if cmd == "help":
# List all commands
print("\nCOMMAND\t\tACTION")
for k in COMMANDS.keys():
print("%s\t\t%s"%(k, COMMANDS[k]))
elif cmd == "home":
# Home machine
send("G28", silent=True) #Auto-home
send("M84", silent=True) #Disable steppers
elif cmd == "raw":
# Send raw gcode commands
print("Entering raw mode. Proceed at your own risk. Type 'exit' to exit raw mode.")
while True:
print("\n# RAW: Please enter a gcode command (or 'exit' to exit)");
r = input()
if r == "exit":
break
send(r)
print("Exiting raw mode.")
elif cmd == "load":
# Load gcode file
print("# Please enter the path to the gcode file you want to load")
path = input()
# Try to open and parse file
try:
print("Trying to open file...")
f = open(path, "r")
print("Reading...")
gcodeRaw = f.read()[:-1]
f.close()
print("Parsing...")
gcodeLines = gcodeRaw.split("\n")
# Iterate through all lines to get info
gcodeLines = 0
for l in range(len(gcodeLines)):
line = gcodeLines[l]
if line[0] == ";":
# When comment
if line.startswith(";LAYER:"):
l = int(line[line.index(":") +1:])
if l > gcodeLayers:
gcodeLayers = l
print("#Layers: %d"%gcodeLayers)
print("%d lines"%len(gcodeLines))
print("File loaded! Use 'add' to manipulate this gcode.")
except Exception as e:
print("Couldn't read file:")
print(e)
# Reset gcode vars
gcodeRaw = ""
gcodeLines = []
gcodeLayers = 1
elif cmd == "add":
# Manipulate gcode
# Check if gcode present
if gcodeRaw == "" or len(gcodeLines) == 0:
print("No gcode loaded. Use 'load' to load gcode.")
continue
# Display all commands
print("COMMAND\t\tEXPLANATION")
print("exit\t\tReturn")
print("mesh\t\tEnable Mesh Bed Leveling")
print("filch\t\tFilament change at specific layer")
print("")
print("# Please enter one of the above actions.")
# Getting input
act = input()
if act == "exit":
print("Exiting.")
pass
elif act == "mesh":
# Add Mesh Bed Leveling
print("Recalculating Z with Mesh Bed Leveling...")
layerZ = 0
layerIndex = 0
for l in range(len(gcodeLines)):
line = gcodeLines[l]
# Check if comment
if line[0] == ";":
# Check if new layer
if line.startswith(";LAYER:"):
layerIndex = int(line[line.index(":") +1:])
if not (line.startswith("G0") or line.startswith("G1")):
continue
# When this is a movement line
tokens = line.split(" ")
indexX = -1
indexY = -1
indexZ = -1
for t in range(len(tokens)):
if tokens[t][0] == "X":
indexX = t
elif tokens[t][0] == "Y":
indexY = t
elif tokens[t][0] == "Z":
indexZ = t
# Check if Z is given
if not indexZ == -1:
# When Z is set -> save as layer height
layerZ = float(tokens[indexZ][1:])
# Add Z if X or Y are set
if indexX == -1 or indexY == -1:
continue
X = float(tokens[indexX][1:])
Y = float(tokens[indexY][1:])
Z = layerZ + MESH_OFFSET + (meshZ(X, Y)*max(0, float(10-layerIndex)/10.0) if layerIndex < 10 else 0)
# Add Z coordinate
tokens.append("Z{:.3f}".format(Z))
newLine = " ".join(tokens)
gcodeLines[l] = newLine
print("Done!")
elif act == "filch":
# Filament change at layer
layer = 0
while True:
# Ask for layer
print("# FILCH: At which layer do you want to change filament?")
l = input()
# Check if valid
try:
layer = int(l)
if layer > 0 and layer <= gcodeLayers:
break
print("Invalid.")
except:
print("Invalid.")
if layer == 0:
continue # exit
# Search for start of this layer
print("Searching for layer...")
success = False
for l in range(len(gcodeLines)):
line = gcodeLines[l]
if line[0] == ";":
if line.startswith(";LAYER:"):
thisLayer = int(line[line.find(":")+1:])
# Check if right layer
if thisLayer == layer:
# Add filch gcode
print("Adding gcode for filch...")
gcodeLines[l:l] = GCODE_FILCH_LAYER
success = True
break
print("Done." if success else "Couldn't find layer.")
else:
# When add action not found
print("Command not found.")
elif cmd == "print":
# Print loaded gcode
if len(gcodeLines) == 0:
print("No file loaded yet. Use 'load' to load gcode.")
else:
# Start print
printing = True
abortPrint = False
print("Print started. Type 'stop' at any time to abort the print.")
send("M75", silent=True) # Start timer
# Start thread to check for commands
threadInput = threading.Thread(target=threadPrintInput, daemon=True)
threadInput.start()
# Send gcode line by line to printer
for l in range(len(gcodeLines)):
line = gcodeLines[l]
# Check if needs to abort print
if abortPrint:
print("Aborting print!")
break
# Check if comment
if line[0] == ";":
# Check if new layer
if line.startswith(";LAYER:"):
layer = int(line[line.index(":") +1:])
print("Printing layer %d/%d"%(layer, gcodeLayers))
else:
# Send line to printer
send(line, silent=True)
# Stop input thread
printing = False
# Stop print
send("M104 S0", silent=True) # Hotend off
send("M140 S0", silent=True) # Bed off
send("M107", silent=True) # Fan off
send("M77", silent=True) # Stop timer
send("M31", silent=True) # Display time on lcd
send("G28 X Y", silent=True) # Home X and Y
send("G0 Y300 F1500", silent=True) # Move platform to user
send("M84", silent=True) # Disable steppers
send("M400", silent=True) # Wait for previous commands to finish
send("M300 P3000", silent=True) # Beep
print("Print done!")
elif cmd == "level":
# Normal leveling wizard
print("Starting leveling wizard...")
send("G28", silent=True) #Home
send("G0 F7500", silent=True) # Set feedrate
print("Please adjust the knob under the nozzle so that a piece of paper can slide under the nozzle with just a little bit of friction.")
# Start corner loop
i = 0
while True:
# Move to corner
print("Moving to corner %d..."%i)
send("G0 Z10", silent=True)
send("G0 %s"%levelingCornersPos[i], silent=True)
send("G0 Z0", silent=True)
send("M84", silent=True) #Disable steppers
# Prompt user
print("Press ENTER to move to next corner. Type 'exit' to exit wizard.")
r = input()
if r == "exit":
break
# Next corner
i = (i+1)%4
# End wizard
send("G0 F1500", silent=True) #Set slower feedrate
send("G28", silent=True) #Home
send("M84", silent=True) #Disable steppers
elif cmd == "mesh":
# Mesh leveling wizard
print("Starting mesh leveling wizard...")
send("G28", silent=True) #Home
send("G0 F7500", silent=True) # Set feedrate
print("\nSTEP 1: Normal leveling")
print("Please adjust the knob under the nozzle so that a piece of paper can slide under the nozzle with just a little bit of friction.")
# Start corner loop
i = 0
stop = False
while True:
# Move to corner
print("Moving to corner %d..."%i)
send("G0 Z10", silent=True)
send("G0 %s"%levelingCornersPos[i], silent=True)
send("G0 Z{:.2f}".format(MESH_OFFSET), silent=True)
# Prompt user
print("Press ENTER to move to next corner. Type 'next' to go to step 2. Type 'exit' to exit wizard.")
r = input()
if r == "exit":
stop = True
break
elif r == "next":
break
# Next corner
i = (i+1)%4
# Step 2: get mesh deltas
if not stop:
print("\nSTEP 2: Mesh leveling")
print("For every mesh point, lower the nozzle using the '+' and '-' buttons until the nozzle is at the right height. Then, enter 'next' to go to the next mesh point. Type 'exit' to exit the wizard.")
# Move through mesh
for y in range(MESH_SIZE):
for x in range(MESH_SIZE):
Z = 1.00 # Offset on top of MESH_OFFSET
print("\nMoving to mesh point x=%d, y=%d"%(x,y))
send("G0 Z10", silent=True)
send("G0 %s"%meshPositions[y][x], silent=True)
send("G0 Z{:.3f}".format(Z+MESH_OFFSET), silent=True)
# Input loop
stop = False
while True:
i = input()
if i == "+":
Z += 0.01
send("G0 Z{:.3f}".format(Z+MESH_OFFSET), silent=True)
elif i == "-":
Z -= 0.01
send("G0 Z{:.3f}".format(Z+MESH_OFFSET), silent=True)
elif i == "++":
Z += 0.1
send("G0 Z{:.3f}".format(Z+MESH_OFFSET), silent=True)
elif i == "--":
Z -= 0.1
send("G0 Z{:.3f}".format(Z+MESH_OFFSET), silent=True)
elif i == "+++":
Z += 0.5
send("G0 Z{:.3f}".format(Z+MESH_OFFSET), silent=True)
elif i == "---":
Z -= 0.5
send("G0 Z{:.3f}".format(Z+MESH_OFFSET), silent=True)
elif i == "exit":
stop = True
break
elif i == "next":
mesh[y][x] = Z
print("Mesh value saved (Z={:.3f})".format(Z))
break
# Check if needs to exit
if stop:
break
# Check if needs to exit
if stop:
break
# Print mesh
print("\nCurrent Mesh:")
for i in mesh:
for j in i:
print("{:6.3f}".format(j), end=" ")
print()
# Save mesh to file
print("Writing mesh to file...")
try:
t = ""
t += "%d\n"%MESH_SIZE
for y in range(MESH_SIZE):
for x in range(MESH_SIZE):
t += "{:.3f}\n".format(mesh[y][x])
f = open("mesh.txt", "w")
f.write(t)
f.close()
except Exception as e:
print("Couldn't write mesh to file.")
print(e)
# End wizard
send("G0 F1500", silent=True) #Set slower feedrate
send("G28", silent=True) #Home
send("M84", silent=True) #Disable steppers
elif cmd == "exit":
break
else:
print("Command not found. Enter 'help' to list all commands.")
# Close serial
print("Disconnecting from printer...")
s.close()
# End of script
print("Thank you for using PrintBuddy!")