kopia lustrzana https://gitlab.com/markol/embroiderino
Added job auto pausing options, fixed job time estimation.
rodzic
1d638e1f1d
commit
73ef2c792a
|
@ -1,6 +1,6 @@
|
|||
#!/usr/bin/python3
|
||||
|
||||
from tkinter import Tk, Label, Button, Entry, Menu, filedialog, messagebox, colorchooser, Canvas, Frame, LabelFrame, Scale, Toplevel, PhotoImage
|
||||
from tkinter import Tk, Label, Button, Checkbutton, Entry, Menu, filedialog, messagebox, colorchooser, Canvas, Frame, LabelFrame, Scale, Toplevel, PhotoImage, IntVar
|
||||
from tkinter.font import Font
|
||||
import tkinter.ttk as ttk
|
||||
from tkinter import LEFT, TOP, BOTTOM, N, YES, W,SUNKEN,X, HORIZONTAL, DISABLED, NORMAL, RAISED, FLAT, RIDGE, END
|
||||
|
@ -8,6 +8,7 @@ from path_preview import ResizingCanvas, load_gcode_file, save_gcode_file, load_
|
|||
from collections import namedtuple
|
||||
import copy, re, math, time, pickle
|
||||
|
||||
#import control_serial_mockup as serial
|
||||
import control_serial as serial
|
||||
|
||||
class ControlAppGUI:
|
||||
|
@ -88,6 +89,15 @@ class ControlAppGUI:
|
|||
self.stopButton = Button(tab1, text="STOP", command=self.StopAll, state=DISABLED)
|
||||
self.stopButton.grid(row=4,column=0)
|
||||
|
||||
self.pauseOnToolChange = IntVar()
|
||||
self.pauseOnTrim = IntVar()
|
||||
self.toolChangeCheck = Checkbutton(tab1, variable=self.pauseOnToolChange, onvalue=1, offvalue=0, text="Pause on tool change")
|
||||
self.toolChangeCheck.grid(row=4,column=1)
|
||||
self.toolChangeCheck.select()
|
||||
self.trimCheck = Checkbutton(tab1, variable=self.pauseOnTrim, onvalue=1, offvalue=0, text="Pause on trim")
|
||||
self.trimCheck.grid(row=4,column=2)
|
||||
self.trimCheck.select()
|
||||
|
||||
progressFrame = Frame(tab1)
|
||||
Label(progressFrame, text="Tool changes: ", bd=1).grid(row=0,column=0)
|
||||
self.toolChangesLabel = Label(progressFrame, text="0/0", bd=1, relief=SUNKEN)
|
||||
|
@ -211,11 +221,11 @@ class ControlAppGUI:
|
|||
self.toolPointsTotal, self.toolChangesTotal, self.distancesList = toolpath_info(self.commands)
|
||||
self.toolPointsLabel.config(text="%d/%d" % (self.currentToolPoint, self.toolPointsTotal))
|
||||
self.toolChangesLabel.config(text="%d/%d" % (self.currentToolChange, self.toolChangesTotal))
|
||||
self.timeLabel.config(text="%d/%d" % (self.distancesList[self.currentToolChange]- self.distanceTraveled, self.distancesList[-1]-self.distanceTraveled))
|
||||
self.UpdateTimeEstLabel()
|
||||
self.canvas.draw_toolpath(self.commands)
|
||||
|
||||
def About(self):
|
||||
#self.ToolChangePopup()
|
||||
#self.PausePopup()
|
||||
messagebox.showinfo('About this software', '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 any later version.\n\nThis 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.\n\nWritten in 2018 by markol.')
|
||||
def Settings(self):
|
||||
tl = Toplevel(root)
|
||||
|
@ -267,7 +277,7 @@ class ControlAppGUI:
|
|||
data = {"workAreaSize": self.workAreaSize}
|
||||
pickle.dump(data, f)
|
||||
except Exception as e:
|
||||
print ("error while saving settings:", str(e))
|
||||
print ("Error while saving settings:", str(e))
|
||||
|
||||
def ToggleConnect(self):
|
||||
if self.isConnected:
|
||||
|
@ -294,6 +304,7 @@ class ControlAppGUI:
|
|||
rectangle = toolpath_border_points(self.commands[1:])
|
||||
for point in rectangle:
|
||||
serial.queue_command("G0 X%f Y%f F5000\n" % point)
|
||||
|
||||
def ToggleStart(self):
|
||||
if self.isJobPaused:
|
||||
serial.queue.clear()
|
||||
|
@ -308,51 +319,83 @@ class ControlAppGUI:
|
|||
self.canvas.clear()
|
||||
startInstructionIndex = 0
|
||||
self.start = time.time()
|
||||
# after every move command being sent, this callback is executed
|
||||
def progressCallback(instruction_index):
|
||||
point = self.commands[instruction_index]
|
||||
if self.lastMove:
|
||||
coord = (self.lastMove[1], self.lastMove[2], point[1], point[2])
|
||||
color = self.currentColor
|
||||
# set color for jump move
|
||||
if "G0" in point[0]:
|
||||
color = "snow2"
|
||||
else:
|
||||
self.currentToolPoint += 1
|
||||
self.toolPointsLabel.config(text="%d/%d" % (self.currentToolPoint, self.toolPointsTotal))
|
||||
self.distanceTraveled += math.hypot(coord[0] - coord[2], coord[1] - coord[3])
|
||||
line = self.canvas.create_line(self.canvas.calc_coords(coord), fill=color)
|
||||
self.canvas.lift(self.canvas.pointer, line)
|
||||
self.timeLabel.config(text="%d/%d" % (self.distancesList[self.currentToolChange]- self.distanceTraveled, self.distancesList[-1]-self.distanceTraveled))
|
||||
self.lastSendCommandIndex = instruction_index
|
||||
self.lastMove = point
|
||||
|
||||
# this callback pauses
|
||||
def progressToolChangeCallback(instruction_index):
|
||||
point = self.commands[instruction_index]
|
||||
self.lastSendCommandIndex = instruction_index
|
||||
self.currentColor = _from_rgb((point[1], point[2], point[3]))
|
||||
self.ToggleStart()
|
||||
self.currentToolChange += 1
|
||||
self.toolChangesLabel.config(text="%d/%d" % (self.currentToolChange, self.toolChangesTotal))
|
||||
self.ToolChangePopup(self.currentColor)
|
||||
|
||||
self.isJobRunning = True
|
||||
commandsCount = len(self.commands)
|
||||
# all the commands until tool change command, are queued at once
|
||||
for i in range(startInstructionIndex, commandsCount):
|
||||
point = self.commands[i]
|
||||
# pause on color change
|
||||
if "M6" in point[0]:
|
||||
serial.queue_command("G0 F25000\n", lambda _, index = i: progressToolChangeCallback(index))
|
||||
break
|
||||
else:
|
||||
serial.queue_command("%s X%f Y%f\n" % (point[0],point[1], point[2]), lambda _, index = i: progressCallback(index))
|
||||
# queue job finish callback, it is unnecessary added after every pause but is cleaned in pause callback
|
||||
serial.queue_command("M114\n", self.JobFinished)
|
||||
self.QueueCommandsBlock(startInstructionIndex)
|
||||
|
||||
self.isJobPaused = not self.isJobPaused
|
||||
|
||||
def QueueCommandsBlock(self, startInstructionIndex):
|
||||
commandsCount = len(self.commands)
|
||||
|
||||
def progressCallback(instruction_index):
|
||||
''' after every move G0 or G1 or G28 command being sent, this callback is executed '''
|
||||
point = self.commands[instruction_index]
|
||||
if self.lastMove:
|
||||
coord = (self.lastMove[1], self.lastMove[2], point[1], point[2])
|
||||
color = self.currentColor
|
||||
# set color for jump move
|
||||
if "G0" == point[0] or "G28" == point[0]:
|
||||
color = "snow2"
|
||||
else:
|
||||
self.currentToolPoint += 1
|
||||
self.toolPointsLabel.config(text="%d/%d" % (self.currentToolPoint, self.toolPointsTotal))
|
||||
# calculate distance for material usage
|
||||
self.distanceTraveled += math.hypot(coord[0] - coord[2], coord[1] - coord[3])
|
||||
# draw new line on canvas
|
||||
line = self.canvas.create_line(self.canvas.calc_coords(coord), fill=color)
|
||||
self.canvas.lift(self.canvas.pointer, line)
|
||||
self.UpdateTimeEstLabel()
|
||||
# store next start point and instruction index
|
||||
self.lastSendCommandIndex = instruction_index
|
||||
self.lastMove = point
|
||||
|
||||
def progressPauseCallback(instruction_index, trim = False):
|
||||
''' this callback pauses the job '''
|
||||
point = self.commands[instruction_index]
|
||||
self.lastSendCommandIndex = instruction_index
|
||||
# pause on color change
|
||||
if not trim:
|
||||
self.currentColor = _from_rgb((point[1], point[2], point[3]))
|
||||
self.currentToolChange += 1
|
||||
self.toolChangesLabel.config(text="%d/%d" % (self.currentToolChange, self.toolChangesTotal))
|
||||
# pause enabled or not
|
||||
if self.pauseOnToolChange.get() == 1:
|
||||
self.ToggleStart()
|
||||
self.PausePopup(self.currentColor)
|
||||
else:
|
||||
self.QueueCommandsBlock(self.lastSendCommandIndex + 1)
|
||||
# pause on trim
|
||||
else:
|
||||
# pause enabled or not
|
||||
if self.pauseOnTrim.get() == 1:
|
||||
self.ToggleStart()
|
||||
self.PausePopup(self.currentColor, trim)
|
||||
else:
|
||||
self.QueueCommandsBlock(self.lastSendCommandIndex + 1)
|
||||
|
||||
# all the commands until next tool change command, are queued at once
|
||||
# unsupported commands are ignored
|
||||
for i in range(startInstructionIndex, commandsCount):
|
||||
point = self.commands[i]
|
||||
# pause on color change
|
||||
if "M6" == point[0]:
|
||||
serial.queue_command("M6\n", lambda _, index = i: progressPauseCallback(index))
|
||||
break
|
||||
# pause on trim
|
||||
elif "G12" == point[0]:
|
||||
# if next command is a color change, there is no need to pause now
|
||||
if i < commandsCount + 1 and self.commands[i+1][0] == "M6" and self.pauseOnToolChange.get() == 1:
|
||||
serial.queue_command("G12\n")
|
||||
else:
|
||||
serial.queue_command("G12\n", lambda _, index = i: progressPauseCallback(index, trim = True))
|
||||
break
|
||||
elif "G1" == point[0] or "G0" == point[0] or "G28" == point[0]:
|
||||
serial.queue_command("%s X%f Y%f\n" % (point[0],point[1], point[2]), lambda _, index = i: progressCallback(index))
|
||||
# queue job finish callback
|
||||
if i + 1 >= commandsCount:
|
||||
serial.queue_command("M114\n", self.JobFinished)
|
||||
|
||||
def SetNavButtonsState(self, enabled = False):
|
||||
newState = NORMAL if enabled else DISABLED
|
||||
for b in self.navigationButtons:
|
||||
|
@ -416,14 +459,17 @@ class ControlAppGUI:
|
|||
self.currentColor = 'black'
|
||||
self.toolPointsLabel.config(text="%d/%d" % (self.currentToolPoint, self.toolPointsTotal))
|
||||
self.toolChangesLabel.config(text="%d/%d" % (self.currentToolChange, self.toolChangesTotal))
|
||||
self.timeLabel.config(text="%d/%d" % (self.distancesList[self.currentToolChange]- self.distanceTraveled, self.distancesList[-1]-self.distanceTraveled))
|
||||
self.UpdateTimeEstLabel()
|
||||
self.startButton.config(text="Start job")
|
||||
self.status.config(text="Job finished")
|
||||
timeTaken = time.time() - self.start
|
||||
# non blocking popup messagebox
|
||||
if messagePopup:
|
||||
tl = Toplevel(root)
|
||||
# this pop-up is always on top and other windows are deactivated
|
||||
tl.attributes('-topmost', 'true')
|
||||
tl.title("Job finished")
|
||||
tl.grab_set()
|
||||
frame = Frame(tl)
|
||||
frame.grid()
|
||||
Label(frame, text='Current job is finished and took %s.' % time.strftime("%H hours, %M minutes, %S seconds", time.gmtime(timeTaken)) ).grid(row=0, column=0, sticky=N)
|
||||
|
@ -476,6 +522,12 @@ class ControlAppGUI:
|
|||
if self.isJobRunning:
|
||||
return
|
||||
self.canvas.draw_toolpath(self.commands[0:int(val)])
|
||||
def UpdateTimeEstLabel(self):
|
||||
# avg milimeters per second factor
|
||||
factor = 11.0
|
||||
time_to_toolchange = (self.distancesList[self.currentToolChange]- self.distanceTraveled) / factor
|
||||
time_total = (self.distancesList[-1]-self.distanceTraveled) / factor
|
||||
self.timeLabel.config(text="%d m %d s/%d m %d s" % (time_to_toolchange / 60, time_to_toolchange % 60, time_total / 60, time_total % 60))
|
||||
def GetPositionTimerTaks(self):
|
||||
if self.isConnected:
|
||||
def TimerCallback(response):
|
||||
|
@ -490,28 +542,28 @@ class ControlAppGUI:
|
|||
def CleanUp(self):
|
||||
serial.close_serial()
|
||||
|
||||
def ToolChangePopup(self, newColor = "black"):
|
||||
def PausePopup(self, newColor = "black", trim = False):
|
||||
tl = Toplevel(root)
|
||||
tl.attributes('-topmost', 'true')
|
||||
tl.grab_set()
|
||||
|
||||
tl.title("Tool change")
|
||||
msg = "change the tool for a next color"
|
||||
if trim:
|
||||
tl.title("Thread trim")
|
||||
msg = "cut the thread"
|
||||
|
||||
frame = Frame(tl)
|
||||
frame.grid()
|
||||
|
||||
canvas = Canvas(frame, width=100, height=130)
|
||||
canvas.grid(row=1, column=0)
|
||||
#imgvar = PhotoImage(file="pyrocket.png")
|
||||
#canvas.create_image(50,70, image=imgvar)
|
||||
#canvas.image = imgvar
|
||||
|
||||
msgbody1 = Label(frame, text="There is time to change tool for a " )
|
||||
msgbody1.grid(row=1, column=1, sticky=N)
|
||||
lang = Label(frame, text="next color", font=Font(size=20, weight="bold"), fg=newColor)
|
||||
lang.grid(row=1, column=2, sticky=N)
|
||||
msgbody2 = Label(frame, text="Resume the current job after change.")
|
||||
msgbody2.grid(row=1, column=3, sticky=N)
|
||||
canvas = Canvas(frame, width=64, height=64)
|
||||
canvas.grid(row=2, column=0)
|
||||
canvas.create_rectangle(0, 0, 65, 65, fill=newColor)
|
||||
msgbody = Label(frame, text="There is the moment to %s. Resume the current job after change." % msg)
|
||||
msgbody.grid(row=1, column=0, sticky=N)
|
||||
|
||||
okbttn = Button(frame, text="OK", command=lambda: tl.destroy(), width=10)
|
||||
okbttn.grid(row=2, column=4)
|
||||
okbttn.grid(row=2, column=2)
|
||||
|
||||
root = Tk()
|
||||
my_gui = ControlAppGUI(root)
|
||||
|
|
|
@ -60,7 +60,7 @@ def send_serial(msg, responseCallback = None):
|
|||
global last_pos
|
||||
response = "ok\n"
|
||||
if "M114" in msg:
|
||||
response = "X%f Y%f\nok\n" % (last_pos[0],last_pos[1])
|
||||
response = "X:%f,Y:%f\nok\n" % (last_pos[0],last_pos[1])
|
||||
if "G0" in msg or "G1" in msg:
|
||||
regex_result = gcode_regexX.search(msg)
|
||||
if regex_result:
|
||||
|
@ -113,4 +113,4 @@ class SendingThread(threading.Thread):
|
|||
queue.task_done()
|
||||
|
||||
def terminate(self):
|
||||
self.running = False
|
||||
self.running = False
|
||||
|
|
|
@ -83,10 +83,14 @@ class ResizingCanvas(Canvas):
|
|||
current_color = "black"
|
||||
color = current_color
|
||||
for point in points[1:]:
|
||||
if "G0" in point[0]:
|
||||
if "G0" == point[0]:
|
||||
color = "snow2"
|
||||
elif "M6" in point[0]:
|
||||
elif "M6" == point[0]:
|
||||
current_color = _from_rgb((point[1], point[2], point[3]))
|
||||
color = current_color
|
||||
continue
|
||||
# commands other than G0, G1, G28 or M6 are ignored for drawing a preview
|
||||
elif "G1" != point[0] and "G28" != point[0]:
|
||||
continue
|
||||
coord = (last_point[1], last_point[2], point[1], point[2])
|
||||
last_point = point
|
||||
|
@ -99,7 +103,7 @@ class ResizingCanvas(Canvas):
|
|||
def translate_toolpath(points, translate=(0,0)):
|
||||
#new_points = copy.deepcopy(points)
|
||||
for point in points:
|
||||
if not ("G0" in point[0] or "G1" in point[0]):
|
||||
if not ("G0" == point[0] or "G1" == point[0]):
|
||||
continue
|
||||
point[1] += translate[0]
|
||||
point[2] += translate[1]
|
||||
|
@ -107,7 +111,7 @@ def translate_toolpath(points, translate=(0,0)):
|
|||
|
||||
def rotate_toolpath(points, origin, theta):
|
||||
for point in points:
|
||||
if not ("G0" in point[0] or "G1" in point[0]):
|
||||
if not ("G0" == point[0] or "G1" == point[0]):
|
||||
continue
|
||||
point[1], point[2] = rotate(point[1:3], origin, theta)
|
||||
return points
|
||||
|
@ -127,21 +131,21 @@ def rotate(point, origin, angle):
|
|||
|
||||
def reflect_toolpath(points, d):
|
||||
for point in points:
|
||||
if not ("G0" in point[0] or "G1" in point[0]):
|
||||
if not ("G0" == point[0] or "G1" == point[0]):
|
||||
continue
|
||||
point[1] = 2*d - point[1]
|
||||
return points
|
||||
|
||||
def scale_toolpath(points, f):
|
||||
for point in points:
|
||||
if not ("G0" in point[0] or "G1" in point[0]):
|
||||
if not ("G0" == point[0] or "G1" == point[0]):
|
||||
continue
|
||||
point[1] += point[1]*f
|
||||
point[2] += point[2]*f
|
||||
return points
|
||||
|
||||
def toolpath_border_points(points):
|
||||
points = [elem for elem in points if "G0" in elem[0] or "G1" in elem[0]]
|
||||
points = [elem for elem in points if "G0" == elem[0] or "G1" == elem[0]]
|
||||
x_max = max(points,key=itemgetter(1))[1]
|
||||
x_min = min(points,key=itemgetter(1))[1]
|
||||
y_max = max(points,key=itemgetter(2))[2]
|
||||
|
@ -155,12 +159,12 @@ def toolpath_info(points):
|
|||
tool_changes = 0
|
||||
distances = []
|
||||
for point in points:
|
||||
if not ("G0" in point[0] or "G1" in point[0]):
|
||||
if "M6" in point[0]:
|
||||
if not ("G0" == point[0] or "G1" == point[0]):
|
||||
if "M6" == point[0]:
|
||||
tool_changes += 1
|
||||
distances.append(total_distance)
|
||||
continue
|
||||
if last_point:
|
||||
if last_point and "G1" == point[0]:
|
||||
total_distance += math.hypot(point[1] - last_point[1], point[2] - last_point[2])
|
||||
last_point = point
|
||||
distances.append(total_distance)
|
||||
|
@ -178,6 +182,8 @@ def load_csv_file(csvfile, offset=(0,0)):
|
|||
command = "G0"
|
||||
commands = [("G28",0,0)]
|
||||
colors = []
|
||||
last_color = None
|
||||
current_color = None
|
||||
# load each point into commands list
|
||||
for row in reader:
|
||||
# empyt row
|
||||
|
@ -191,12 +197,15 @@ def load_csv_file(csvfile, offset=(0,0)):
|
|||
elif 'COLOR' in row[1]:
|
||||
command = "M6"
|
||||
if len(colors) > 0:
|
||||
r, g, b = colors.pop(0)
|
||||
current_color = colors.pop(0)
|
||||
else:
|
||||
r = g = b = 0
|
||||
commands.append([command, r, g, b])
|
||||
current_color = (0,0,0)
|
||||
# colors are truly different
|
||||
if current_color != last_color:
|
||||
commands.append([command, *current_color])
|
||||
last_color = current_color
|
||||
continue
|
||||
elif 'TRIMM' in row[1]:
|
||||
elif 'TRIM' in row[1]:
|
||||
command = "G12"
|
||||
commands.append([command, ])
|
||||
continue
|
||||
|
@ -247,9 +256,9 @@ def load_gcode_file(f):
|
|||
|
||||
def save_gcode_file(f, commands):
|
||||
for command in commands:
|
||||
if "M6" in command[0]:
|
||||
if "M6" == command[0]:
|
||||
command = "%s R%d G%d B%d\n" % (command[0], command[1], command[2], command[3])
|
||||
elif "G0" in command[0] or "G1" in command[0]:
|
||||
elif "G0" == command[0] or "G1" == command[0]:
|
||||
if len(command) > 3:
|
||||
command = "%s X%f Y%f F%f\n" % (command[0], command[1], command[2], command[3])
|
||||
else:
|
||||
|
|
Ładowanie…
Reference in New Issue