Compare commits

..

9 Commits

Author SHA1 Message Date
openhands
79e5e5b7bd Fix stop command to reset block state 2025-07-22 18:08:06 +00:00
Thomas
0308d47967 - Updated readme 2023-12-30 19:03:31 +01:00
Thomas
c11e5e1ba4 - added settings externalized into settings.py (not for the executable version yet)
-Changed depency to the pip package version from submodule
-Updated requirements.txt
2023-12-30 17:34:06 +01:00
Thomas Herrmann
9f798791c5 - Changed Screenshot 2023-12-28 20:55:03 +01:00
Thomas Herrmann
1f20d7c79e - Changed Screenshot 2023-12-28 20:50:51 +01:00
Thomas Herrmann
63af178adf - Changed Screenshot 2023-12-28 20:49:21 +01:00
Thomas Herrmann
05f3b1dc45 - Added executable
- Removed old files
- changed readme
- Added old color scheme
- added requirements.txt
2023-12-28 20:43:03 +01:00
Thomas Herrmann
ce7b331b1e Merge remote-tracking branch 'origin/main'
# Conflicts:
#	README.md
2023-12-28 20:39:24 +01:00
Thomas Herrmann
f1ee9b8419 - Added executable
- Removed old files
- changed readme
- Added old color scheme
- added requirements.txt
2023-12-28 20:38:53 +01:00
14 changed files with 131 additions and 1025 deletions

View File

@@ -1,28 +1,73 @@
# touchCNC
GRBL 1.1 CNC Controller for ODROID C2 with VU PLus Touch Screen
# touchCNC 1.0
GRBL 1.1 CNC Controller for ODROID C2 with VU PLus Touch Screen or Linux Desktop
Should run on any System wit at least 1024x600 Screen Resolution.
- Jog
- Zero positions
- Job commands
- Spindle Coolant, Tool
- Gcode milling envelope preview (low cpu usage),
- Spindle Coolant, Tool and Macro commands
- Gcode milling envelope preview
- G28 Position
- Feed override (soon)
- Feed override (Not yet working)
- terminal
- Laser status and switch
![Screenshot_20231222_205706](https://github.com/BKLronin/touchCNC/assets/6392076/b57899df-8c59-4353-a41a-548273e79a59)
![Screen.png](Screen.png)
-
- Tested on latest Armbian stable https://www.armbian.com/odroid-c2/#kernels-archive-all
- Tested on Manjaro
- Using cncpro v3 with grbl1.1f
## Clone to your PC
`git clone https://github.com/BKLronin/touchCNC.git`
## Install
- (Create env)
- In folder enter:
`pip install requirements.txt`
## Run
`python cnc_gerbil.py`
or
`cd dist`
`./cnc_gerbil` (Bundled pyinstaller executable)
## Settings
`nano settings.py`
or any other editor to setup to your specific usecase, the options are:
- The resolution of your SBC-screen
`resolution = '1024x600+0+0'`
Tested on latest Armbian stable https://www.armbian.com/odroid-c2/#kernels-archive-all
- When running on SBC with touch set to: True
`set_fullscreen = False`
Based on
- Platform dependent com ports
`portlist = ['/dev/ttyUSB0', '/dev/ttyACM0', '/dev/ttyUSB1', '/dev/ttyACM1', '/dev/ttyACM2', '/dev/ttyACM3',
'/dev/ttyS0', '/dev/ttyS1', '/dev/ttyS2', '/dev/ttyS3']`
- Where the file dialog points to. Ideally some Nextcloud folder or Samba share etc.
`basepath = '/home/'`
- Machine commands
`spindle_on = 'M3S1000' `
`spindle_off = 'M5'`
`cooling_on = 'M8' `
`cooling_off = 'M9' `
`toolchange = 'G10 P0 L20 X0 Y0 Z0' `
- Table Info Text
`table_text = 'Fräsbereich 300mm x 300mm'`
### Based on:
Python3
tkinter
pyserial
gerbil
gcodemachine
In some cases you have to manually install tkinter via apt and pyserial via pip.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 67 KiB

474
cnc.py
View File

@@ -1,474 +0,0 @@
import serial
import time
from tkinter import *
import serial.tools.list_ports
from tkinter import filedialog as fd
import os
import threading
import grbl_gcode_send
import grbl_stream
grbl = 0
port = None
i = 10
GCODE = 0
gcode_to_stream = []
countbuf = 0
writebuffer_byPass = []
writebuffer = []
readbuffer = []
AXIS = 'X'
states = {'M3':'0', 'M8':'0', 'M6':'0', 'G10': '0'} #Spindle, Coolant, Toolchange
dict_GCODE = {'G':'0',
'X':'0',
'Y':'0',
'Z':'0',
'I':'0',
'J':'0',
'F':'0'
}
#GUI Main
buttonsize_x = 5
buttonsize_y = 3
increments = 0
BORDER = 2
freetosend = 1
#GUI Color Scheme
attention = 'red'
loaded = 'green'
cooling = 'blue'
toolchange = 'yellow'
standard = '#17223B'
feed = '#283B67'
def grblConnect2():
global grbl
global port
#Serial Connection
locations=['/dev/ttyACM0','/dev/ttyUSB0','/dev/ttyUSB1','/dev/ttyACM1','/dev/ttyACM2','/dev/ttyACM3',
'/dev/ttyS0','/dev/ttyS1','/dev/ttyS2','/dev/ttyS3']
for device in locations:
try:
#print([comport.device for comport in serial.tools.list_ports.comports()])
print ("Trying...",device)
grbl = serial.Serial(port= device, baudrate= 115200, timeout =.5) #dsrdtr= True)
port = device
#grbl.open()
#print(grbl.readline())
grbl.write(str.encode("\r\n\r\n"))
time.sleep(2) # Wait for grbl to initialize
grbl.flushInput() # Flush startup text in serial input
connect_ser.config(bg= loaded)
#print("connected")
break
except:
#print ("Failed to connect on",device)
grbl = 0
# Stream g-code to grbl
#Stream GCODE from -https://onehossshay.wordpress.com/2011/08/26/grbl-a-simple-python-interface/-
def jogWrite(AXIS, CMD, scale): #Schreiben von manuellen Positionierungsbefehlen
global freetosend
DECIMAL = [0.1,1,10,100]
scale = increments.get()
MOVE = int(CMD) * DECIMAL[scale -1]
grbl_command = ('$J=G91' + 'G21' + AXIS + str(MOVE)+ 'F1000')
#print(grbl_command) $J=G91G21X10F185
grbl_gcode_send.send_gcode(grbl, grbl_command)
def switchButtonState(button): #Umschalter für Knopfstatus
if button["state"] == DISABLED:
button["state"] = NORMAL
else:
button["state"] = DISABLED
def directWrite(CMD): #Direktes schreiben eines Befehls
global freetosend
#print(freetosend)
grbl_command = CMD
grbl_gcode_send.send_gcode(grbl, grbl_command)
def latchWrite(CMD):
global states
global freetosend
if states[CMD] == '0':
states[CMD] = '1'
if CMD == 'M3':
spindle.config(bg= attention) #A31621
if CMD == 'M6':
tool.config(bg = toolchange)#E0CA3C
if CMD == 'G10':
zero_all.config(bg = loaded)
else:
states[CMD] ='0'
if CMD == 'M3':
spindle.config(bg=loaded)#A2D729
if CMD == 'M6':
tool.config(bg='grey')
#if CMD == 'G10':
# zero_all.config(bg= attention)
if CMD == 'M3':
if states['M3'] == '1':
grbl_command = 'M3 S1000'
else:
grbl_command = 'M3 S0'
elif CMD == 'M8':
if states['M8'] == '1':
grbl_command = (CMD)
coolant.config(bg = cooling)#1F7A8C
else:
grbl_command = 'M9'
coolant.config(bg ='grey')
elif CMD == 'G10':
grbl_command = 'G10 P0 L20 X0 Y0 Z0'
else:
grbl_command = (CMD)
#grbl_command = (CMD * int(states[CMD]) )
#print(grbl_command)
#print(states)
grbl_gcode_send.send_gcode(grbl, grbl_command)
def terminalWrite(): #Holt Zeichenstring von Editfeld und sendet es
grbl_command = terminal.get()
#print(grbl_command)
grbl_gcode_send.send_gcode(grbl, grbl_command)
def infoScreen(data): #Anzeigecanvas für GRBL Rückmeldungen
global i
terminalFrame = Frame(terminal_recv, bg = 'white')
terminal_recv.create_window(10,i, window = terminalFrame, anchor = 'nw')
Label(terminalFrame, text = data, font = ('Calibri',10), bg ='white', fg ='black').pack()
i += 22
if i >=400:
i=10
terminal_recv.delete("all")
def openGCODE(): #Dialog zur Gcode Auswahl und öffnen der Datei als GCODE Objekt
global gcode_to_stream
filetypes = (('GCODE', '*.nc'),('All files', '*.*'))
GCODE = fd.askopenfile(title='Open a file', initialdir='/home/thomas/Nextcloud/CAM/', filetypes=filetypes)
if GCODE != 0:
fopen.config(bg= loaded)
extracted = extract_GCODE(GCODE)
draw_GCODE(extracted)
gcode_to_stream = GCODE
else:
fopen.config(bg = 'grey')
#build_xy = findEnvelope() #Aufruf PLatz im Bauraum
#mill_table.create_rectangle(build_xy[0],build_xy[1], fill = 'blue', stipple = 'gray75') # Zeichnen des Objekts im Bauraum
def extract_GCODE(gcode: list): #Aufschlüsseln der enthaltenen Koordinaten in ein per Schlüssel zugängiges Dictionary
list_dict_GCODE = []
for line in gcode:
l = line.split() #Elemente trennen und in Liste konvertieren
for i in range(0,len(l)):
#print (l)
if 'G' in l[i]:
dict_GCODE['G'] = l[i].replace('G','') #Wert einfügen und gleichzeitig G CODE befehl entfernen
if 'X' in l[i]:
dict_GCODE['X'] = l[i].replace('X','')
if 'Y' in l[i]:
dict_GCODE['Y'] = l[i] .replace('Y','')
if 'Z' in l[i]:
dict_GCODE['Z'] = l[i].replace('Z','')
if 'I' in l[i] and not 'ZMIN':
dict_GCODE['I'] = l[i].replace('I','')
if 'J' in l[i]:
dict_GCODE['J'] = l[i].replace('J','')
if 'F' in l[i] and not 'Fusion':
dict_GCODE['F'] = l[i].replace('F','')
#print(dict_GCODE)
list_dict_GCODE.append(dict_GCODE.copy()) #Copy notwendig da es sich nur um einen "Pointer" handelt der immer auf die zuletzt aktualisierte dict Zeile zeigt.
print(list_dict_GCODE)
return list_dict_GCODE
def draw_GCODE(glist): #Zeichnen des GCodes zur Beurteilung des Bauraums
for i in range(0,len(glist)-1):
x_y_current = 50 + float(glist[i]['X']), 350 - float(glist[i]['Y'])
x_y_next = 50 + float(glist[i+1]['X']), 350 - float(glist[i+1]['Y'])
mill_table.create_line(x_y_current, x_y_next)
def writeToFileLog(log): #Log für Debugzwecke
with open("log.txt", 'a') as out:
out.write(log)
def displayPosition_request(grbl_pos):
if grbl != 0 :
try:
position = str(grbl_pos)
#print (readbuffer)
position = position.replace('Idle|', ',')
position = position.replace('Run|', ',')
position = position.replace('WPos:', '')
position = position.replace('MPos:', '')
position = position.replace('>', ',')
position = position.replace('|', ',')
position.strip()
coordinates_list = position.split(',')
#print(coordinates_list)
show_ctrl_x.config(text = coordinates_list[1])
show_ctrl_y.config(text = coordinates_list[2])
show_ctrl_z.config(text = coordinates_list[3])
mill_table.create_line(coordinates_list[1],coordinates_list[2],coordinates_list[1],coordinates_list[2]+50, arrow = FIRST )
#show_ctrl_x_w.config(text = coordinates_list[4])
#show_ctrl_y_w.config(text = coordinates_list[5])
#show_ctrl_z_w.config(text = coordinates_list[6])
except:
pass
#print("Listerror")
else:
print("Serial Busy")
#root.after(1000,displayPosition)
def displayPosition():
global readbuffer
if grbl != 0 :
try:
position = str(readbuffer[2])
#print (readbuffer)
position = position.replace('Idle|', ',')
position = position.replace('Run|', ',')
position = position.replace('WPos:', '')
position = position.replace('MPos:', '')
position = position.replace('>', ',')
position = position.replace('|', ',')
position.strip()
coordinates_list = position.split(',')
#print(coordinates_list)
show_ctrl_x.config(text = coordinates_list[1])
show_ctrl_y.config(text = coordinates_list[2])
show_ctrl_z.config(text = coordinates_list[3])
mill_table.create_line(coordinates_list[1],coordinates_list[2],coordinates_list[1]+10,coordinates_list[2]+20 )
mill_table.create_line(coordinates_list[1],coordinates_list[2],coordinates_list[1]-10,coordinates_list[2]+20 )
mill_table.create_line(coordinates_list[1]-10,coordinates_list[2]+20,coordinates_list[1]+10,coordinates_list[2]+20 )
#show_ctrl_x_w.config(text = coordinates_list[4])
#show_ctrl_y_w.config(text = coordinates_list[5])
#show_ctrl_z_w.config(text = coordinates_list[6])
except:
pass
#print("Listerror")
else:
print("Serial Busy")
#root.after(1000,displayPosition)
def grblWrite():
if gcode_to_stream != None:
print("Stream", gcode_to_stream)
grbl_gcode_send.send_gcode(grbl, gcode_to_stream)
#fdbk = grbl_gcode_send.send_gcode(grbl, line)
#print(fdbk)
grbl_gcode_send.wait_for_buffer_empty()
def grblClose():
# Close file and serial port
#f.close()
try:
grbl.close()
print("closed")
connect_ser.config(bg='grey')
except:
print("Connection still open")
root = Tk()
root.title('touchCNC')
root.geometry('1024x600+0+0')
root.geometry('1024x600+0+0')
root.resizable(False,False)#17203b
root.attributes('-fullscreen', False)
root.tk_setPalette(background='#11192C', foreground='white',activeBackground='#283867', activeForeground='white' )
increments = IntVar()
movement = Frame(root, relief = 'ridge', bd = BORDER)
left = Button(root, text="-X", width = buttonsize_x, height = buttonsize_y, command = lambda:jogWrite('X', '-1', increments),bd = BORDER, bg = standard)
right = Button(root, text="+X",width = buttonsize_x, height = buttonsize_y,command = lambda:jogWrite('X', '1', increments),bd = BORDER, bg = standard)
up = Button(root, text="+Y", width = buttonsize_x, height = buttonsize_y,command = lambda:jogWrite('Y', '1', increments),bd = BORDER, bg = standard)
down = Button(root, text="-Y",width = buttonsize_x, height = buttonsize_y,command = lambda:jogWrite('Y', '-1', increments),bd = BORDER, bg = standard)
z_up = Button(root, text="+Z",width = buttonsize_x, height = buttonsize_y,command = lambda:jogWrite('Z', '1', increments) ,bd = BORDER, bg = standard)
z_down = Button(root, text="-Z",width = buttonsize_x, height = buttonsize_y,command = lambda:jogWrite('Z', '-1', increments),bd = BORDER, bg = standard)
zero_x = Button(root, text="zero X",width = buttonsize_x, height = 1, command = lambda:directWrite('G10 P0 L20 X0'),bd = BORDER)
zero_y = Button(root, text="zero Y",width = buttonsize_x, height = 1, command = lambda:directWrite('G10 P0 L20 Y0'),bd = BORDER)
zero_z = Button(root, text="zero Z",width = buttonsize_x, height = 1, command = lambda:directWrite('G10 P0 L20 Z0'),bd = BORDER)
zero_all=Button(root, text="zeroAll",width = buttonsize_x, height = 3, command = lambda:latchWrite('G10'),bd = BORDER, bg= 'magenta')
setzero =Button(root, text="SetPOS",width = buttonsize_x, height = buttonsize_y, command = lambda:directWrite('G28.1'),bd = BORDER)
gozero =Button(root, text="GoPOS",width = buttonsize_x, height = buttonsize_y, command = lambda:directWrite('G28'),bd = BORDER)
connect_ser = Button(root, text="Cnnct",width = buttonsize_x, height = buttonsize_y, command = grblConnect2, bg = 'grey',bd = BORDER)
discon_ser = Button(root, text="Dsconct",width = buttonsize_x, height = buttonsize_y, command = lambda:grblClose(),bd = BORDER)
unlock = Button(root, text="Unlock",width = buttonsize_x, height = buttonsize_y, command = lambda:directWrite('$X'),bd = BORDER)
start = Button(root, text="START",width = buttonsize_x, height = buttonsize_y, bg = attention, command = lambda: threading.Thread(target = grblWrite).start(),bd = BORDER)
stop = Button(root, text="STOP",width = buttonsize_x, height = buttonsize_y,bd = BORDER, command = lambda: directWrite('') )
pause = Button(root, text="PAUSE",width = buttonsize_x, height = buttonsize_y, bg = cooling,bd = BORDER,command = lambda: directWrite('!') )
resume = Button(root, text="RESUME",width = buttonsize_x, height = buttonsize_y,bd = BORDER,command = lambda: directWrite('~'))
fopen = Button(root, text="GCODE",width = buttonsize_x , height = buttonsize_y, bg = 'grey',fg = 'black', command = openGCODE,bd = BORDER)
spindle = Button(root, text="Spindle",width = buttonsize_x, height = buttonsize_y,command = lambda:latchWrite('M3'))
coolant = Button(root, text="Coolant",width = buttonsize_x, height = buttonsize_y,command = lambda:latchWrite('M8') )
tool = Button(root, text="Tool",width = buttonsize_x, height = buttonsize_y,command = lambda:latchWrite('M6') )
macro = Button(root, text="Macro1",width = buttonsize_x, height = buttonsize_y,command = lambda:directWrite(' G91 G0 X10 Y10 Z50 F1000') )
inc1 = Button(root, text="Inc 1%",width = buttonsize_x, height = buttonsize_y,command = lambda:directWrite(''),bg= feed)
inc10 = Button(root,text="Inc 10%",width = buttonsize_x, height = buttonsize_y,command = lambda:directWrite(''),bg= feed )
dec1 = Button(root, text="Dec 1%",width = buttonsize_x, height = buttonsize_y,command = lambda:directWrite(''),bg= feed )
dec10 = Button(root,text="Dec 10%",width = buttonsize_x, height = buttonsize_y,command = lambda:directWrite(''),bg= feed )
reset = Button(root,text="<RESET",width = buttonsize_x, height = buttonsize_y,command = lambda:directWrite(''),bg= 'grey' )
reboot= Button(root,text="REBOOT",width = buttonsize_x, height = buttonsize_y,command = lambda: os.system('reboot'),bg= 'grey' )
step_incr1 = Radiobutton(root, text= '0,1', value = 1 , variable = increments,width = buttonsize_x, height = buttonsize_y, indicatoron = 0 )
step_incr2 = Radiobutton(root, text= '1', value = 2 , variable = increments,width = buttonsize_x, height = buttonsize_y, indicatoron = 0 )
step_incr3 = Radiobutton(root, text= '10', value = 3 , variable = increments,width = buttonsize_x, height = buttonsize_y, indicatoron = 0 )
step_incr4 = Radiobutton(root, text= '100', value = 4 , variable = increments,width = buttonsize_x, height = buttonsize_y, indicatoron = 0 )
step_incr2.select()
terminal = Entry(root, width =8, text="GCODE")
terminal_send = Button(root, text="SEND",width = buttonsize_x, height = buttonsize_y, bd= 3, command = lambda: terminalWrite())
terminal_recv = Canvas(root, width = 200, height =400, bg = 'white')
show_ctrl_x_label = Label(root,text = "X")
show_ctrl_y_label = Label(root,text = "Y")
show_ctrl_z_label = Label(root,text = "Z")
show_ctrl_x =Label(root, text = "X_POS", width = 8, height = 2, bg ='white', relief = SUNKEN, fg= 'black')
show_ctrl_y =Label(root, text = "Y_POS", width = 8, height = 2, bg ='white', relief = SUNKEN, fg= 'black')
show_ctrl_z =Label(root, text = "Z_POS", width = 8, height = 2, bg ='white', relief = SUNKEN, fg= 'black')
show_ctrl_x_w =Label(root, text = "X_POS_W", width = 8, height = 2, bg ='white', relief = SUNKEN, fg= 'black')
show_ctrl_y_w =Label(root, text = "Y_POS_W", width = 8, height = 2, bg ='white', relief = SUNKEN, fg= 'black')
show_ctrl_z_w =Label(root, text = "Z_POS_W", width = 8, height = 2, bg ='white', relief = SUNKEN, fg= 'black')
#feed_control = Scale(root, orient = HORIZONTAL, length = 400, label = "Feedrate",tickinterval = 20)
#Milling Area and Gcode preview with grid generation
mill_table= Canvas(root, width= 400, height = 400, bg = 'grey')
mill_table.create_rectangle(50,50,350,350, fill ='white')
mill_table.create_text(200,25,text = 'Fräsbereich 300mm x 300mm')
for x in range(50,350,50):
mill_table.create_text(x,400- x, text = x-50)
for x in range(0,400,50):
for y in range(0,400,50):
gitter_x = mill_table.create_line(x,0,x,400)
gitter_y = mill_table.create_line(0,y,400,y)
movement.grid(row = 0, column = 0, columnspan = 3, rowspan = 1)
left.grid(row=1, column=0, padx=3, pady=2)
right.grid(row=1, column=2,padx=3, pady=2)
up.grid(row=0, column=1, padx=3, pady=10)
down.grid(row=1, column=1,padx=3, pady=2)
z_up.grid(row=0, column=3,padx=10, pady=10)
z_down.grid(row=1, column=3,padx=10, pady=2)
step_incr2.select()
step_incr1.grid(row=2, column=0,padx=3, pady=10)
step_incr2.grid(row=2, column=1,padx=3, pady=10)
step_incr3.grid(row=2, column=2,padx=3, pady=10)
step_incr4.grid(row=2, column=3,padx=3, pady=10)
show_ctrl_x_label.grid(row=3, column=0,padx=3, pady=10)
show_ctrl_y_label.grid(row=4, column=0,padx=3, pady=10)
show_ctrl_z_label.grid(row=5, column=0,padx=3, pady=10)
show_ctrl_x.grid(row=3, column=1,padx=0, pady=0, columnspan =1)
show_ctrl_y.grid(row=4, column=1,padx=0, pady=0, columnspan =1)
show_ctrl_z.grid(row=5, column=1,padx=0, pady=0, columnspan =1)
show_ctrl_x_w.grid(row=3, column=2,padx=0, pady=0, columnspan =1)
show_ctrl_y_w.grid(row=4, column=2,padx=0, pady=0, columnspan =1)
show_ctrl_z_w.grid(row=5, column=2,padx=0, pady=0, columnspan =1)
zero_x.grid(row=3, column=3)
zero_y.grid(row=4, column=3)
zero_z.grid(row=5, column=3)
zero_all.grid(row=6, column=3,padx=10, pady=10)
setzero.grid(row=6, column=0,padx=10, pady=10)
gozero.grid(row=6, column=1,padx=10, pady=10)
connect_ser.grid(row=7, column=0,padx=10, pady=10)
discon_ser.grid(row=7, column=1,padx=10, pady=10)
unlock.grid(row=8, column=1,padx=10, pady=10)
start.grid(row=7, column=2,padx=10, pady=10)
stop.grid(row=7, column=3,padx=10, pady=10)
pause.grid(row=8, column=2,padx=10, pady=10)
resume.grid(row=8, column=3,padx=10, pady=10)
fopen.grid(row=8, column=0,padx=10, pady=10)
spindle.grid(row=7, column=4,padx=1, pady=10)
coolant.grid(row=7, column=5,padx=1, pady=10)
tool.grid(row=7, column=6,padx=1, pady=10)
macro.grid(row=7, column=7,padx=1, pady=10)
dec10.grid(row=8, column=4,padx=1, pady=10)
dec1.grid(row=8, column=5,padx=1, pady=10)
inc1.grid(row=8, column=6,padx=1, pady=10)
inc10.grid(row=8, column=7,padx=1, pady=10)
reset.grid(row=8, column=8,padx=1, pady=10)
reboot.grid(row=8, column=9,padx=1, pady=10)
terminal.grid(row = 7, column = 8, padx =2, pady =10)
terminal_send.grid(row = 7, column = 9, padx =2, pady =10)
terminal_recv.grid(row = 0, column = 8, padx =10, pady =10,rowspan = 7, columnspan =2)
#feed_control.grid(row = 8, column = 4, columnspan =4)
mill_table.grid(row=0, column=4,padx=10, pady=10,columnspan = 4, rowspan = 7)
#sendGRBL()
#BlockedButtons
blkbuttons = (up,down,left,right,z_up,z_down, zero_x, zero_y, zero_z, zero_all, setzero, gozero, spindle)
#timedPositionRequest()
root.mainloop()

View File

@@ -3,7 +3,8 @@ import time
from tkinter import Button, Label, Variable, IntVar, Canvas, Frame, Listbox, Entry, Radiobutton, Tk, constants, LEFT
from tkinter import filedialog as fd
import os
from gerbil.gerbil import Gerbil
from grbl_streamer import GrblStreamer
import settings
class touchCNC:
def __init__(self, root):
@@ -21,7 +22,7 @@ class touchCNC:
self.increments = 0
self.BORDER = 2
self.feedspeed = None
self.states = {'M3': '0', 'M8': '0', 'M6': '0', 'G10': '0', '32' :0} # self.spindle, Coolant, Toolchange
self.states = {'M3': '0', 'M8': '0', 'M6': '0', 'G10': '0', '32' :'0'} # self.spindle, Coolant, Toolchange
self.dict_GCODE = {'G': '0',
'X': '0',
@@ -43,6 +44,14 @@ class touchCNC:
self.feed = self.secondary
self.transport = '#FA7921'
# Classic Scheme
attention = 'red'
loaded = 'green'
cooling = 'blue'
toolchange = 'yellow'
standard = '#17223B'
feed = '#283B67'
self.increments = IntVar()
self.movement = Frame(root, relief='ridge', bd=self.BORDER, padx=10, pady=10)
@@ -81,7 +90,7 @@ class touchCNC:
bd=self.BORDER)
self.start = Button(root, text="START", width=self.buttonsize_x, height=self.buttonsize_y, bg=self.attention,
command=self.grblWrite, bd=self.BORDER)
self.stop = Button(root, text="STOP", width=self.buttonsize_x, height=self.buttonsize_y, bd=self.BORDER, bg=self.transport,
self.stop = Button(root, text="STOP", width=self.buttonsize_x, height=self.buttonsize_y, bd=self.BORDER, bg=self.transport,\n command=self.reset_block_state)
command=self.grblStop)
self.pause = Button(root, text="PAUSE", width=self.buttonsize_x, height=self.buttonsize_y, bd=self.BORDER, bg=self.transport,
command=self.grblPause)
@@ -305,8 +314,7 @@ class touchCNC:
def grblConnect2(self, baudrate=115200, max_retries=5, retry_interval=3):
retry_count = 0
locations = ['/dev/ttyUSB0', '/dev/ttyACM0', '/dev/ttyUSB1', '/dev/ttyACM1', '/dev/ttyACM2', '/dev/ttyACM3',
'/dev/ttyS0', '/dev/ttyS1', '/dev/ttyS2', '/dev/ttyS3']
locations = settings.portlist
# Configure logging
logging.basicConfig(level=logging.DEBUG)
@@ -343,6 +351,7 @@ class touchCNC:
def grblClose(self):
grbl.softreset()
print(grbl.connected)
grbl.disconnect()
self.connect_ser.config(bg=self.secondary)
@@ -400,13 +409,13 @@ class touchCNC:
def get_grbl_command(self, CMD):
if CMD == 'M3':
return 'M3 S1000' if self.states['M3'] == '1' else 'M5'
return settings.spindle_on if self.states['M3'] == '1' else settings.spindle_off
elif CMD == 'M8':
return CMD if self.states[CMD] == '1' else 'M9'
return settings.cooling_on if self.states[CMD] == '1' else settings.cooling_off
elif CMD == 'G10':
return 'G10 P0 L20 X0 Y0 Z0'
return settings.toolchange
elif CMD == '32':
return '$32=0' if self.states['32'] == '1' else '$32=1'
@@ -419,7 +428,7 @@ class touchCNC:
def openGCODE(self):
filetypes = (('GCODE', '*.nc'), ('All files', '*.*'))
if not self.file_list:
GCODE = fd.askopenfilename(title='Open a file', initialdir='/home/', filetypes=filetypes)
GCODE = fd.askopenfilename(title='Open a file', initialdir=settings.basepath, filetypes=filetypes)
else:
GCODE = self.load_gcode_from_listbox()
@@ -462,7 +471,7 @@ class touchCNC:
def openDir(self):
self.file_list = []
self.path_list = []
directory = fd.askdirectory(title='Open a Folder', initialdir='/home/')
directory = fd.askdirectory(title='Open a Folder', initialdir=settings.basepath)
allowed_extensions = {'nc', 'GCODE'} # Use a set for efficient membership testing
if directory:
@@ -593,7 +602,7 @@ class DrawonTable:
self.mill_table.delete('all')
self.mill_table.create_rectangle(50, 50, 350, 350, fill='white')
self.mill_table.create_text(200, 25, text='Fräsbereich 300mm x 300mm')
self.mill_table.create_text(200, 25, text=settings.table_text)
for x in range(50, 350, 50):
self.mill_table.create_text(x, 400 - x, text=x - 50)
@@ -607,16 +616,15 @@ class DrawonTable:
if __name__ == "__main__":
root = Tk()
root.title('touchCNC')
root.geometry('1024x600+0+0')
root.geometry(settings.resolution)
root.grid_propagate(True)
root.resizable(False, False) # 17203b
root.attributes('-fullscreen', False)
root.attributes('-fullscreen', settings.set_fullscreen)
root.tk_setPalette(background='#4B4A67', foreground='black', activeBackground='#F99417',
activeForeground='lightgrey')
app = touchCNC(root)
grbl = Gerbil(app.gui_callback)
grbl = GrblStreamer(app.gui_callback)
grbl.hash_state_requested = True
grbl.gcode_parser_state_requested = True

BIN
dist/cnc vendored

Binary file not shown.

BIN
dist/cnc_gerbil vendored Executable file

Binary file not shown.

22
dist/settings.py vendored Normal file
View File

@@ -0,0 +1,22 @@
# The resolution of your SBC-screen
resolution = '1024x600+0+0'
# When running on SBC with touch set to: True
set_fullscreen = False
# Platform dependent
portlist = ['/dev/ttyUSB0', '/dev/ttyACM0', '/dev/ttyUSB1', '/dev/ttyACM1', '/dev/ttyACM2', '/dev/ttyACM3',
'/dev/ttyS0', '/dev/ttyS1', '/dev/ttyS2', '/dev/ttyS3']
# Where the file dialog points to. Ideally some Nextcloud folder or Samba share etc.
basepath = '/home/'
# Machine commands
spindle_on = 'M3S1000'
spindle_off = 'M5'
cooling_on = 'M8'
cooling_off = 'M9'
toolchange = 'G10 P0 L20 X0 Y0 Z0'
# Table Info Text
table_text = 'Fräsbereich 300mm x 300mm'

Submodule gcode_machine deleted from 624f71ae15

1
gerbil

Submodule gerbil deleted from 78aa148f0a

View File

@@ -1,55 +0,0 @@
import serial
import time
# Define the serial port and baud rate for communication
ser = serial.Serial('/dev/ttyUSB0', 115200, timeout=1)
# Function to send G-code commands
def send_gcode(ser, command):
# Split the command into chunks of 120 characters or less
chunks = [command[i:i + 120] for i in range(0, len(command), 120)]
for chunk in chunks:
ser.write((chunk + '\n').encode())
response = ser.readline().decode().strip()
if response != 'ok':
# Handle errors or unexpected responses here
print(f"GRBL response: {response}")
# Function to wait until the buffer is empty
def wait_for_buffer_empty():
while True:
status = send_gcode('?')
if status.startswith('<Idle'):
break
time.sleep(0.1)
if __name__ == "__main__":
# Your G-code commands
gcode_commands = [
'G21', # Set units to millimeters
'G90', # Set to absolute positioning
'G1 X10 Y10 F100', # Move to X10 Y10 at a feed rate of 100 mm/min
'G1 X20 Y20 F100',
]
try:
# Initialize communication
#ser.open()
ser.flushInput()
ser.flushOutput()
# Send G-code commands
for command in gcode_commands:
send_gcode(command)
# Wait for the buffer to empty
wait_for_buffer_empty()
except Exception as e:
print(f"An error occurred: {str(e)}")
finally:
ser.close()

View File

@@ -1,150 +0,0 @@
import serial
import re
import time
import threading
RX_BUFFER_SIZE = 128
BAUD_RATE = 115200
ENABLE_STATUS_REPORTS = True
REPORT_INTERVAL = 1.0 # seconds
is_run = True # Controls query timer
class GrblController:
def __init__(self, device_file, verbose=True, settings_mode=False, check_mode=False):
self.ser = serial.Serial(device_file, BAUD_RATE)
self.verbose = verbose
self.settings_mode = settings_mode
self.check_mode = check_mode
self.timerThread = None
def open(self):
self.ser.open()
self.ser.flushInput()
self.ser.flushOutput()
time.sleep(2)
self.ser.flushInput()
if self.check_mode:
self.set_check_mode()
def close(self):
self.ser.close()
def set_check_mode(self):
self.send_command("$C\n")
while True:
grbl_out = self.ser.readline().strip()
if grbl_out.find('error') >= 0:
print("REC:", grbl_out)
print(" Failed to set Grbl check-mode. Aborting...")
quit()
elif grbl_out.find('ok') >= 0:
if self.verbose:
print('REC:', grbl_out)
break
def send_command(self, command):
self.ser.write((command + '\n').encode())
def read_response(self):
return self.ser.readline().strip()
def send_gcode(self, gcode_file):
l_count = 0
error_count = 0
start_time = time.time()
self.start_status_report_timer()
for line in gcode_file:
l_count += 1
l_block = line.strip()
if self.settings_mode:
self.send_command(l_block)
while True:
grbl_out = self.read_response()
if grbl_out.find('ok') >= 0:
if self.verbose:
print(" REC<{}: \"{}\"".format(l_count, grbl_out))
break
elif grbl_out.find('error') >= 0:
if self.verbose:
print(" REC<{}: \"{}\"".format(l_count, grbl_out))
error_count += 1
break
else:
print(" MSG: \"{}\"".format(grbl_out))
else:
c_line = []
for char in l_block:
c_line.append(len(char) + 1)
grbl_out = ''
while sum(c_line) >= RX_BUFFER_SIZE - 1 or self.ser.inWaiting():
out_temp = self.read_response()
if out_temp.find('ok') < 0 and out_temp.find('error') < 0:
print(" MSG: \"{}\"".format(out_temp))
else:
if out_temp.find('error') >= 0:
error_count += 1
del c_line[0]
if self.verbose:
print(" REC<{}: \"{}\"".format(l_count, out_temp))
self.send_command(char)
if self.verbose:
print("SND>{}: \"{}\"".format(l_count, char))
while l_count > 0:
out_temp = self.read_response()
if out_temp.find('ok') < 0 and out_temp.find('error') < 0:
print(" MSG: \"{}\"".format(out_temp))
else:
if out_temp.find('error') >= 0:
error_count += 1
l_count -= 1
del c_line[0]
if self.verbose:
print(" REC<{}: \"{}\"".format(l_count, out_temp))
self.stop_status_report_timer()
end_time = time.time()
is_run = False
print("\nG-code streaming finished!")
print("Time elapsed: {}\n".format(end_time - start_time))
if self.check_mode:
if error_count > 0:
print("CHECK FAILED: {} errors found! See output for details.\n".format(error_count))
else:
print("CHECK PASSED: No errors found in g-code program.\n")
else:
print("WARNING: Wait until Grbl completes buffered g-code blocks before exiting.")
def start_status_report_timer(self):
if ENABLE_STATUS_REPORTS:
self.timerThread = threading.Thread(target=self.periodic_timer)
self.timerThread.daemon = True
self.timerThread.start()
def stop_status_report_timer(self):
self.timerThread.join()
def periodic_timer(self):
while is_run:
self.send_command('?')
time.sleep(REPORT_INTERVAL)
if __name__ == '__main__':
import argparse
parser = argparse.ArgumentParser(description='Stream g-code file to grbl.')
parser.add_argument('gcode_file', type=argparse.FileType('r'), help='g-code filename to be streamed')
parser.add_argument('device_file', help='serial device path')
parser.add_argument('-q', '--quiet', action='store_true', default=False, help='suppress output text')
parser.add_argument('-s', '--settings', action='store_true', default=False, help='settings write mode')
parser.add_argument('-c', '--check', action='store_true', default=False, help='stream in check mode')
args = parser.parse_args()
grbl_controller = GrblController(args.device_file, not args.quiet, args.settings, args.check)
grbl_controller.open()
grbl_controller.send_gcode(args.gcode_file)
grbl_controller.close()

View File

@@ -1,315 +0,0 @@
grbl = 0
port = None
i = 10
GCODE = 0
gcode_to_stream = []
countbuf = 0
writebuffer_byPass = []
writebuffer = []
readbuffer = []
AXIS = 'X'
states = {'M3': '0', 'M8': '0', 'M6': '0', 'G10': '0'} # Spindle, Coolant, Toolchange
dict_GCODE = {'G': '0',
'X': '0',
'Y': '0',
'Z': '0',
'I': '0',
'J': '0',
'F': '0'
}
# GUI Main
buttonsize_x = 5
buttonsize_y = 3
increments = 0
BORDER = 2
freetosend = 1
# GUI Color Scheme
attention = 'red'
loaded = 'green'
cooling = 'blue'
toolchange = 'yellow'
standard = '#17223B'
feed = '#283B67'
def grblConnect2():
global grbl
global port
# Serial Connection
locations = ['/dev/ttyACM0', '/dev/ttyUSB0', '/dev/ttyUSB1', '/dev/ttyACM1', '/dev/ttyACM2', '/dev/ttyACM3',
'/dev/ttyS0', '/dev/ttyS1', '/dev/ttyS2', '/dev/ttyS3']
for device in locations:
try:
# print([comport.device for comport in serial.tools.list_ports.comports()])
print("Trying...", device)
grbl = serial.Serial(port=device, baudrate=115200, timeout=.5) # dsrdtr= True)
port = device
# grbl.open()
# print(grbl.readline())
grbl.write(str.encode("\r\n\r\n"))
time.sleep(2) # Wait for grbl to initialize
grbl.flushInput() # Flush startup text in serial input
connect_ser.config(bg=loaded)
# print("connected")
break
except:
# print ("Failed to connect on",device)
grbl = 0
# Stream g-code to grbl
# Stream GCODE from -https://onehossshay.wordpress.com/2011/08/26/grbl-a-simple-python-interface/-
def jogWrite(AXIS, CMD, scale): # Schreiben von manuellen Positionierungsbefehlen
global freetosend
DECIMAL = [0.1, 1, 10, 100]
scale = increments.get()
MOVE = int(CMD) * DECIMAL[scale - 1]
grbl_command = ('$J=G91' + 'G21' + AXIS + str(MOVE) + 'F1000')
# print(grbl_command) $J=G91G21X10F185
grbl_gcode_send.send_gcode(grbl, grbl_command)
def switchButtonState(button): # Umschalter für Knopfstatus
if button["state"] == DISABLED:
button["state"] = NORMAL
else:
button["state"] = DISABLED
def directWrite(CMD): # Direktes schreiben eines Befehls
global freetosend
# print(freetosend)
grbl_command = CMD
grbl_gcode_send.send_gcode(grbl, grbl_command)
def latchWrite(CMD):
global states
global freetosend
if states[CMD] == '0':
states[CMD] = '1'
if CMD == 'M3':
spindle.config(bg=attention) # A31621
if CMD == 'M6':
tool.config(bg=toolchange) # E0CA3C
if CMD == 'G10':
zero_all.config(bg=loaded)
else:
states[CMD] = '0'
if CMD == 'M3':
spindle.config(bg=loaded) # A2D729
if CMD == 'M6':
tool.config(bg='grey')
# if CMD == 'G10':
# zero_all.config(bg= attention)
if CMD == 'M3':
if states['M3'] == '1':
grbl_command = 'M3 S1000'
else:
grbl_command = 'M3 S0'
elif CMD == 'M8':
if states['M8'] == '1':
grbl_command = (CMD)
coolant.config(bg=cooling) # 1F7A8C
else:
grbl_command = 'M9'
coolant.config(bg='grey')
elif CMD == 'G10':
grbl_command = 'G10 P0 L20 X0 Y0 Z0'
else:
grbl_command = (CMD)
# grbl_command = (CMD * int(states[CMD]) )
# print(grbl_command)
# print(states)
grbl_gcode_send.send_gcode(grbl, grbl_command)
def terminalWrite(): # Holt Zeichenstring von Editfeld und sendet es
grbl_command = terminal.get()
# print(grbl_command)
grbl_gcode_send.send_gcode(grbl, grbl_command)
def infoScreen(data): # Anzeigecanvas für GRBL Rückmeldungen
global i
terminalFrame = Frame(terminal_recv, bg='white')
terminal_recv.create_window(10, i, window=terminalFrame, anchor='nw')
Label(terminalFrame, text=data, font=('Calibri', 10), bg='white', fg='black').pack()
i += 22
if i >= 400:
i = 10
terminal_recv.delete("all")
def openGCODE(): # Dialog zur Gcode Auswahl und öffnen der Datei als GCODE Objekt
global gcode_to_stream
filetypes = (('GCODE', '*.nc'), ('All files', '*.*'))
GCODE = fd.askopenfile(title='Open a file', initialdir='/home/thomas/Nextcloud/CAM/', filetypes=filetypes)
if GCODE != 0:
fopen.config(bg=loaded)
extracted = extract_GCODE(GCODE)
draw_GCODE(extracted)
gcode_to_stream = GCODE
else:
fopen.config(bg='grey')
# build_xy = findEnvelope() #Aufruf PLatz im Bauraum
# mill_table.create_rectangle(build_xy[0],build_xy[1], fill = 'blue', stipple = 'gray75') # Zeichnen des Objekts im Bauraum
def extract_GCODE(gcode: list): # Aufschlüsseln der enthaltenen Koordinaten in ein per Schlüssel zugängiges Dictionary
list_dict_GCODE = []
for line in gcode:
l = line.split() # Elemente trennen und in Liste konvertieren
for i in range(0, len(l)):
# print (l)
if 'G' in l[i]:
dict_GCODE['G'] = l[i].replace('G', '') # Wert einfügen und gleichzeitig G CODE befehl entfernen
if 'X' in l[i]:
dict_GCODE['X'] = l[i].replace('X', '')
if 'Y' in l[i]:
dict_GCODE['Y'] = l[i].replace('Y', '')
if 'Z' in l[i]:
dict_GCODE['Z'] = l[i].replace('Z', '')
if 'I' in l[i] and not 'ZMIN':
dict_GCODE['I'] = l[i].replace('I', '')
if 'J' in l[i]:
dict_GCODE['J'] = l[i].replace('J', '')
if 'F' in l[i] and not 'Fusion':
dict_GCODE['F'] = l[i].replace('F', '')
# print(dict_GCODE)
list_dict_GCODE.append(
dict_GCODE.copy()) # Copy notwendig da es sich nur um einen "Pointer" handelt der immer auf die zuletzt aktualisierte dict Zeile zeigt.
print(list_dict_GCODE)
return list_dict_GCODE
def draw_GCODE(glist): # Zeichnen des GCodes zur Beurteilung des Bauraums
for i in range(0, len(glist) - 1):
x_y_current = 50 + float(glist[i]['X']), 350 - float(glist[i]['Y'])
x_y_next = 50 + float(glist[i + 1]['X']), 350 - float(glist[i + 1]['Y'])
mill_table.create_line(x_y_current, x_y_next)
def writeToFileLog(log): # Log für Debugzwecke
with open("log.txt", 'a') as out:
out.write(log)
def displayPosition_request(grbl_pos):
if grbl != 0:
try:
position = str(grbl_pos)
# print (readbuffer)
position = position.replace('Idle|', ',')
position = position.replace('Run|', ',')
position = position.replace('WPos:', '')
position = position.replace('MPos:', '')
position = position.replace('>', ',')
position = position.replace('|', ',')
position.strip()
coordinates_list = position.split(',')
# print(coordinates_list)
show_ctrl_x.config(text=coordinates_list[1])
show_ctrl_y.config(text=coordinates_list[2])
show_ctrl_z.config(text=coordinates_list[3])
mill_table.create_line(coordinates_list[1], coordinates_list[2], coordinates_list[1],
coordinates_list[2] + 50, arrow=FIRST)
# show_ctrl_x_w.config(text = coordinates_list[4])
# show_ctrl_y_w.config(text = coordinates_list[5])
# show_ctrl_z_w.config(text = coordinates_list[6])
except:
pass
# print("Listerror")
else:
print("Serial Busy")
# root.after(1000,displayPosition)
def displayPosition():
global readbuffer
if grbl != 0:
try:
position = str(readbuffer[2])
# print (readbuffer)
position = position.replace('Idle|', ',')
position = position.replace('Run|', ',')
position = position.replace('WPos:', '')
position = position.replace('MPos:', '')
position = position.replace('>', ',')
position = position.replace('|', ',')
position.strip()
coordinates_list = position.split(',')
# print(coordinates_list)
show_ctrl_x.config(text=coordinates_list[1])
show_ctrl_y.config(text=coordinates_list[2])
show_ctrl_z.config(text=coordinates_list[3])
mill_table.create_line(coordinates_list[1], coordinates_list[2], coordinates_list[1] + 10,
coordinates_list[2] + 20)
mill_table.create_line(coordinates_list[1], coordinates_list[2], coordinates_list[1] - 10,
coordinates_list[2] + 20)
mill_table.create_line(coordinates_list[1] - 10, coordinates_list[2] + 20, coordinates_list[1] + 10,
coordinates_list[2] + 20)
# show_ctrl_x_w.config(text = coordinates_list[4])
# show_ctrl_y_w.config(text = coordinates_list[5])
# show_ctrl_z_w.config(text = coordinates_list[6])
except:
pass
# print("Listerror")
else:
print("Serial Busy")
# root.after(1000,displayPosition)
def grblWrite():
if gcode_to_stream != None:
print("Stream", gcode_to_stream)
grbl_gcode_send.send_gcode(grbl, gcode_to_stream)
# fdbk = grbl_gcode_send.send_gcode(grbl, line)
# print(fdbk)
grbl_gcode_send.wait_for_buffer_empty()
def grblClose():
# Close file and serial port
# f.close()
try:
grbl.close()
print("closed")
connect_ser.config(bg='grey')
except:
print("Connection still open")

5
requirements.txt Normal file
View File

@@ -0,0 +1,5 @@
altgraph==0.17.4
gcode-machine==1.0.3
grbl-streamer==1.0.3
numpy==1.26.2
pyserial==3.5

22
settings.py Normal file
View File

@@ -0,0 +1,22 @@
# The resolution of your SBC-screen
resolution = '1024x600+0+0'
# When running on SBC with touch set to: True
set_fullscreen = False
# Platform dependent
portlist = ['/dev/ttyUSB0', '/dev/ttyACM0', '/dev/ttyUSB1', '/dev/ttyACM1', '/dev/ttyACM2', '/dev/ttyACM3',
'/dev/ttyS0', '/dev/ttyS1', '/dev/ttyS2', '/dev/ttyS3']
# Where the file dialog points to. Ideally some Nextcloud folder or Samba share etc.
basepath = '/home/'
# Machine commands
spindle_on = 'M3S1000'
spindle_off = 'M5'
cooling_on = 'M8'
cooling_off = 'M9'
toolchange = 'G10 P0 L20 X0 Y0 Z0'
# Table Info Text
table_text = 'Fräsbereich 300mm x 300mm'