2021-03-07 13:43:20 +00:00
#
# Copyright (C) 2021 Thomas Van Acker
#
# 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/>.""")
#
2021-03-07 13:32:47 +00:00
import serial
import time
import threading
import math
print ( " PrintBuddy V0.1 " )
2021-03-07 13:38:05 +00:00
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 / > . """ )
2021-03-07 13:32:47 +00:00
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 ( " \n Connecting 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 ( " \n COMMAND \t \t ACTION " )
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
2021-03-07 13:38:05 +00:00
gcodeLines = 0
2021-03-07 13:32:47 +00:00
for l in range ( len ( gcodeLines ) ) :
line = gcodeLines [ l ]
if line [ 0 ] == " ; " :
# When comment
if line . startswith ( " ;LAYER: " ) :
l = int ( line [ line . index ( " : " ) + 1 : ] )
2021-03-07 13:38:05 +00:00
if l > gcodeLayers :
gcodeLayers = l
2021-03-07 13:32:47 +00:00
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 \t EXPLANATION " )
print ( " exit \t \t Return " )
print ( " mesh \t \t Enable Mesh Bed Leveling " )
print ( " filch \t \t Filament 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 ( " \n STEP 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 ( " \n STEP 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 ( " \n Moving 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 ( " \n Current 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! " )