def run(self): self.loaded = False self.gcode = [] self.times = [] self.bBox = None self.totalTime = 0 self.busy = True analyzer = GCodeAnalyzer() analyzer.fastf = self.g0_feed try: with open(self.file) as f: for line in f: analyzer.Analyze(line) self.gcode.append(line) self.times.append(analyzer.getTravelTime()*60) # time returned is in minutes: convert to seconds except: self.busy = False e = sys.exc_info()[0] self.load_error.emit("%s" % e) return self.busy = False self.loaded = True self.totalTime = self.times[-1] self.bBox = analyzer.getBoundingBox() self.load_finished.emit()
def _start_analysis(self, file_path): self._gcode_analyzer = GCodeAnalyzer() self._gcode_analyzer.prepare_for_job() def do_analysis(): gcode = file(file_path, "r") fileSize = os.path.getsize(file_path) self.layer_change_info = self._gcode_analyzer.get_print_job_layer_information(gcode, self.file_size) self.on_analysis_complete.invoke() analysis_thread = Thread(target=do_analysis) analysis_thread.start()
def __init__(self): QObject.__init__(self) self.analyzer = GCodeAnalyzer(False) self.serial = None self.config = {} self.g0_feed = pycnc_config.G0_FEED self.waitAck = 0 self.storedPos = None self.zCompensation = None self.doZCompensation = False self.restoreWorkCoords = False self.checkMode = False self.resetting = False
def __init__(self): QObject.__init__(self) self.analyzer = GCodeAnalyzer(False) self.serial = None self.config = {} self.g0_feed = pycnc_config.G0_FEED self.waitAck = False self.storedPos = None
def run(self): self.loaded = False self.gcode = [] self.times = [] self.bBox = None self.totalTime = 0 self.busy = True analyzer = GCodeAnalyzer() analyzer.fastf = self.g0_feed try: with open(self.file) as f: for line in f: analyzer.Analyze(line) self.gcode.append(line) self.times.append( analyzer.getTravelTime() * 60) # time returned is in minutes: convert to seconds except: self.busy = False e = sys.exc_info()[0] self.load_error.emit("%s" % e) return self.busy = False self.loaded = True self.totalTime = self.times[-1] self.bBox = analyzer.getBoundingBox() self.load_finished.emit()
def __init__(self, port=None, baud=None): """Initializes a printcore instance. Pass the port and baud rate to connect immediately """ self.baud = None self.port = None self.analyzer = GCodeAnalyzer() self.printer = None #Serial instance connected to the printer, None when disconnected self.clear = 0 #clear to send, enabled after responses self.online = False #The printer has responded to the initial command and is active self.printing = False #is a print currently running, true if printing, false if paused self.mainqueue = [] self.priqueue = [] self.queueindex = 0 self.lineno = 0 self.resendfrom = -1 self.paused = False self.sentlines = {} self.log = [] self.sent = [] self.tempcb = None #impl (wholeline) self.recvcb = None #impl (wholeline) self.sendcb = None #impl (wholeline) self.errorcb = None #impl (wholeline) self.startcb = None #impl () self.endcb = None #impl () self.onlinecb = None #impl () self.loud = False #emit sent and received lines to terminal self.greetings = ['start', 'Grbl '] self.wait = 0 # default wait period for send(), send_now() self.read_thread = None self.stop_read_thread = False self.print_thread = None if port is not None and baud is not None: self.connect(port, baud) self.xy_feedrate = None self.z_feedrate = None self.pronterface = None
class PrintJob: layer_change_info = None on_layer_change = Event() on_analysis_complete = Event() printing = False file_size = -1 def __init__(self, file_selected_payload): self.file_name = file_selected_payload['name'] self.local = file_selected_payload['origin'] == 'local' self.file_path = file_selected_payload['file'] if self.local: self.file_size = os.path.getsize(self.file_path) self._start_analysis(file_selected_payload['file']) self.current_layer = 0 def started(self): self.current_layer = 0 self.printing = True def stopped(self): self.printing = False def get_layer_count(self): return self.layer_change_info.get_layer_count() def is_analysing_gcode(self): return self._gcode_analyzer and self._gcode_analyzer.is_working() def set_progress(self, progress): # Verify printing from octoprint and layer change info is available if self.local and self.layer_change_info != None: # Take note of the current layer, used layer to trigger the on_layer_change event. prev_layer = self.current_layer layer_count = self.layer_change_info.get_layer_count() # Only proceed if not already on final layer if self.current_layer < layer_count: next_layer_progress = self.layer_change_info.get_layer_change_position( self.current_layer) # If our progress is greater than the next layer point, increment the current layer while progress >= next_layer_progress and self.current_layer < layer_count: self.current_layer += 1 next_layer_progress = self.layer_change_info.get_layer_change_position( self.current_layer) # if the layer has changed notify listeners if self.current_layer != prev_layer: self.on_layer_change.invoke() def _start_analysis(self, file_path): self._gcode_analyzer = GCodeAnalyzer() self._gcode_analyzer.prepare_for_job() def do_analysis(): gcode = file(file_path, "r") fileSize = os.path.getsize(file_path) self.layer_change_info = self._gcode_analyzer.get_print_job_layer_information( gcode, self.file_size) self.on_analysis_complete.invoke() analysis_thread = Thread(target=do_analysis) analysis_thread.start() def to_string(self): if self._gcode_analyzer and self._gcode_analyzer.is_working(): file_pos = float(self._gcode_analyzer.get_current_file_position()) return "Analysing: %%%.1f" % (file_pos / self.file_size * 100) elif self.layer_change_info: if self.printing: return "%d / %d" % (self.current_layer + 1, self.get_layer_count()) else: return "- / %d" % self.get_layer_count() return "-"
from GCodeAnalyzer import GCodeAnalyzer import time import os import threading file_name = r"C:\Users\chatr\AppData\Roaming\OctoPrint\uploads\20m_sphere.gcode" print("Starting gcode analyzation") gCodeAnalyzer = GCodeAnalyzer() gcode = file(file_name, "r") file_size = os.path.getsize(file_name) def analyze_gcode(): start = time.time() layer_info = gCodeAnalyzer.get_print_job_layer_information( gcode, file_size) end = time.time() print("Analyzation complete") print(end - start) print("%d layers in gcode" % layer_info.get_layer_count()) gcode.close() gCodeAnalyzer.prepare_for_job() thread = threading.Thread(target=analyze_gcode) thread.start()
class GrblWriter(QObject): position_updated = Signal(object) probe_error = Signal() grbl_error = Signal(object) def __init__(self): QObject.__init__(self) self.analyzer = GCodeAnalyzer(False) self.serial = None self.config = {} self.g0_feed = pycnc_config.G0_FEED self.waitAck = 0 self.storedPos = None self.zCompensation = None self.doZCompensation = False self.restoreWorkCoords = False self.checkMode = False self.resetting = False # this will actually connect to Grbl def open(self): self.waitAck = 0 grbl_paths = glob.glob(pycnc_config.SERIAL_PATTERN) if not grbl_paths: return False # Device not existing self.checkMode = False try: self.serial = serial.Serial(grbl_paths[0], pycnc_config.BAUD, timeout=5, dsrdtr=True) if pycnc_config.SERIAL_DEBUG: redefineSerialRW(self.serial) # this is to debug communication! self.serial.flushInput() time.sleep(0.1) self.serial.write("\r\n") time.sleep(0.1) self.serial.write("\x18") time.sleep(0.1) grblLine = self.serial.readline() while 'Grbl' not in grblLine: grblLine = self.serial.readline() time.sleep(0.1) time.sleep(0.5) self.serial.flushInput() self.load_config(grblLine) except: # serial port could not be opened return False if self.config[22] == 1: # homing is enabled. A homing cycle needs to be performed. self.do_homing() else: self.analyzer.Reset() self.analyzer.fastf = self.g0_feed # everything OK return True def do_homing(self): res = QMessageBox.information(None, "Homing", "Press OK to start the homing cycle", QMessageBox.Ok) if res != QMessageBox.Ok: return oldMachineCoords = self.analyzer.getMachineXYZ() oldWorkCoords = self.analyzer.getWorkXYZ() self.do_command("$H") self.analyzer.Reset() self.analyzer.fastf = self.g0_feed machinePos, workPos = self.get_status(True) print machinePos, workPos self.analyzer.syncStatusWithGrbl(machinePos, workPos) # read the actual positions from grbl if any([coord != 0 for coord in oldMachineCoords]): res = QMessageBox.information(None, "Restore coords", "Move tool to previous position?", QMessageBox.Yes | QMessageBox.No) if res == QMessageBox.Yes: self.do_command("G53 G0 X%.3f Y%.3f" % (oldMachineCoords[0], oldMachineCoords[1])) self.do_command("G53 G0 Z%.3f" % (oldMachineCoords[2])) self.do_command("G10 P0 L20 X%.3f Y%.3f Z%.3f" % oldWorkCoords) self.position_updated.emit(self.analyzer.getPosition()) def set_check_mode(self, checkMode): if self.checkMode != checkMode: self.do_command('$C') self.checkMode = checkMode time.sleep(0.5) self.serial.flushInput() def check_gcode_line(self, line): line = suppressAllInvalidGCodes(line) if not self.checkMode: self.set_check_mode(True) self.serial.flushInput() self.serial.write(line + '\n') while True: res = self.serial.readline() r = res.lower() if 'ok' in r: return True, None if 'error' in r or 'alarm' in r: return False, res def close(self): if self.serial is None: return self.serial.close() self.serial = None self.position_updated.emit([0, 0, 0]) def reset(self): self.resetting = True try: self.serial.write("\x18") except: pass print "Resetting!" self.close() res = self.open() self.resetting = False return res def read_response(self, until="ok", ignoreInitialize = False): """ read lines from the grbl until the expected matching line appears (usually "ok"), or just the first line if until is None. """ result = [] while True: while not self.serial.inWaiting > 0: QApplication.processEvents() line = self.serial.readline().strip() #print "Received line:", line if pycnc_config.SERIAL_DEBUG: if line == "": self.serial.write("\n") if line.startswith("error:") or line.startswith("ALARM:"): self.analyzer.undo() self.grbl_error.emit(line) break if not ignoreInitialize and line.startswith("Grbl"): # a spontaneous reset is detected? # restore work coordinates self.do_command("G10 P0 L20 X%.4f Y%.4f Z%.4f" % (self.analyzer.x, self.analyzer.y, self.analyzer.z)) break result.append(line) if line == until or until == None: break time.sleep(0.1) return '\n'.join(result) def do_compensated_move(self, lastMoveCommand): if lastMoveCommand.isArc(): # arcs don't have a z movement: add one in the end lastMoveCommand.z += self.zCompensation.getZValue(lastMoveCommand.x, lastMoveCommand.y) self.serial.write(lastMoveCommand.getCommand() + '\n') self.read_response() # wait for previous command to be acknowledged self.serial.write("G1 Z%.4f F100" % (lastMoveCommand.z)) response = self.read_response() else: #print "Z compensation: original command ", lastMoveCommand.getCommand() for splitted_cmd in lastMoveCommand.splitMovement(self.zCompensation.spacing): #print "Z compensation: splitted command ", splitted_cmd.getCommand() #print "Z compensation: ", self.zCompensation.getZValue(splitted_cmd.x, splitted_cmd.y) #print "Z compensation: ", self.zCompensation.getZValue(splitted_cmd.x, splitted_cmd.y) splitted_cmd.z += self.zCompensation.getZValue(splitted_cmd.x, splitted_cmd.y) #print "Z compensation: new command ", splitted_cmd.getCommand() self.serial.write(splitted_cmd.getCommand() + '\n') response = self.read_response() return response def do_command(self, gcode, wait=False, initCommand=False): """ send the command to grbl, read the response and return it. if wait=True, wait for the stepper motion to complete before returning. """ # self.waitAck = 0 # only for nonblocking commands, so it should be false, but if we run a nonblocking command, and then a blocking one, the blocking might catch the previous ok command = suppressAllInvalidGCodes(gcode.strip()) if not command or command[0] == '(': return self.analyzer.Analyze(command) lastMoveCommand = self.analyzer.lastMovementGCode if (self.doZCompensation and self.zCompensation and not self.analyzer.relative and lastMoveCommand is not None): # z compensation only works in absolute coords response = self.do_compensated_move(lastMoveCommand) else: #business as usual self.serial.write(command) if pycnc_config.SERIAL_DEBUG: time.sleep(0.1) self.serial.write('\n') response = self.read_response(ignoreInitialize=initCommand) if wait: self.wait_motion() self.position_updated.emit(self.analyzer.getPosition()) return response def do_command_nonblock(self, gcode): # run a command but don't wait command = suppressAllInvalidGCodes(gcode.strip()) if not command or command[0] == '(': return self.waitAck += 1 self.analyzer.Analyze(command) lastMoveCommand = self.analyzer.lastMovementGCode if (self.doZCompensation and self.zCompensation and not self.analyzer.relative and lastMoveCommand is not None): # z compensation only works in absolute coords self.do_compensated_move(lastMoveCommand) self.waitAck -= 1 # the do_compensated_move is blocking because it has to execute multiple commands. So remove the waitack. else: #business as usual self.serial.write(command + '\n') self.position_updated.emit(self.analyzer.getPosition()) #print "Nonblock: wait ack status", self.waitAck def ack_received(self): if self.waitAck == 0: # waitAck is an integer because there can be more commands in the queue to be executed. TODO: test! return True, None # if waitAck is 0 it means that there are no commands in the pipeline. Can we send more than one command before ack? Maybe not... # there is no serial to be received, return false if not self.serial.inWaiting() > 0: return False, None line = self.serial.readline().strip() if line.startswith("Grbl"): # coordinates were reset self.restoreWorkCoords = True return False, line if line.startswith("error:") or line.startswith("ALARM:"): self.analyzer.undo() self.grbl_error.emit(line) self.waitAck -= 1 if self.waitAck == 0: return True, line else: return False, line if line == "ok": self.waitAck -= 1 if self.waitAck == 0: if self.restoreWorkCoords: # coordinates need to be restored print "Restoring work coordinates" self.do_command("G10 P0 L20 X%.4f Y%.4f Z%.4f" % (self.analyzer.x, self.analyzer.y, self.analyzer.z)) self.restoreWorkCoords = False return True, line else: return False, line # something was received, but was not error or ok, so no ack return False, line def wait_motion(self): """ force grbl to wait until all motion is complete. """ # # the gcode dwell command as implemented by grbl includes a # stepper-motor sync prior to beginning the dwell countdown. # use it to force a pause-until-caught-up. self.serial.flushInput() self.do_command("G4 P0") def wait_motion_nonblock(self): self.do_command_nonblock("G4 P0") # self.motionWaiting = True # self.serial.flushInput() # self.serial.write("G4 P0\n") def load_config(self, grblLine=None): # query GRBL for the configuration conf = self.do_command("$$", initCommand=True) self.config = {} if grblLine is not None: # this is the Grbl welcome message m = re.search('Grbl ([0-9]+)\.([0-9]+)(.?)', grblLine) self.config['Major'] = int(m.group(1)) self.config['Minor'] = int(m.group(2)) self.config['Revision'] = m.group(3) for confLine in conf.split("\n"): key,val = readConfigLine(confLine) if key is not None: self.config[key] = val try: self.g0_feed = self.config[110] # $110 in grbl is max x rate except: pass # if it's not there, it's ok def store_pos(self): self.storedPos={} self.storedPos['Position'] = self.analyzer.getPosition() self.storedPos['f'] = self.analyzer.f self.storedPos['relative'] = self.analyzer.relative self.storedPos['metric'] = self.analyzer.metric def resume_pos(self): # go back to the stored position safeZ = self.analyzer.maxZ xyz = self.storedPos['Position'] self.do_command("G90") # go to abs positioning self.do_command("G21") # go to metric self.do_command("G0 Z%.4f" % safeZ) # move to safe Z self.do_command("G0 X%.4f Y%.4f" % (xyz[0], xyz[1])) # move to XY self.do_command("G1 Z%.4f F%.4f" % (xyz[2], self.storedPos['f'])) # move to Z using previous F # we are now in absolute metric: convert to relative/imperial if needed if self.storedPos['relative']: self.do_command("G91") if not self.storedPos['metric']: self.do_command("G20") def get_status(self, getBothStatuses = False): self.serial.write('?') # no newline needed res = self.read_response(None) # read one line # status is: <Idle,MPos:10.000,-5.000,2.000,WPos:0.000,0.000,0.000,Buf:0,RX:0,Ln:0,F:0.> #get machine and work pos # Grbl 1.1 only gives either machine or work pos! machineStatus = None workStatus = None m = re.search('MPos:([-.0-9]+),([-.0-9]+),([-.0-9]+)', res) if m is not None: machineStatus = { 'status': res, 'type': 'Machine', 'position': ( float(m.group(1)), float(m.group(2)), float(m.group(3)) ) } if not getBothStatuses: return machineStatus m = re.search('WPos:([-.0-9]+),([-.0-9]+),([-.0-9]+)', res) if m is not None: workStatus = { 'status': res, 'type': 'Work', 'position': (float(m.group(1)), float(m.group(2)), float(m.group(3))) } if not getBothStatuses: return workStatus # if both statuses are none here, then we had an error if machineStatus is None and workStatus is None: if getBothStatuses: return None, None # we wanted both positions, so send two return values else: return None # only executed if getBothPositions is True!! This makes the following recursion safe # here it means that we want both positions. Find out which one we miss (if any). We are sure that we have at least one if machineStatus is None: # this means that we have Grbl1.1 and the status setting is "Work": switch to Machine self.do_command("$10=1") # get the other status machineStatus = self.get_status(False) self.do_command("$10=0") if machineStatus is None: return None, None # error else: return machineStatus, workStatus elif workStatus is None: # this means that we have Grbl1.1 and the status setting is "Work": switch to Machine self.do_command("$10=0") # get the other status workStatus = self.get_status(False) self.do_command("$10=1") if workStatus is None: return None, None # error else: return machineStatus, workStatus else: return machineStatus, workStatus def do_probe(self): initial_status = self.get_status() probeResponse = self.do_command("G38.2 Z%.3f F%.3f" % (-math.fabs(pycnc_config.PROBING_DISTANCE), pycnc_config.PROBING_FEED)) # probe z if 'ALARM' in probeResponse: self.probe_error.emit() return None, None, None end_status = self.get_status() x = end_status['position'][0] - initial_status['position'][0] y = end_status['position'][1] - initial_status['position'][1] z = end_status['position'][2] - initial_status['position'][2] print "Probe position:",x,y,z return x,y,z # this is not needed as it's not 100% Grbl1.1-compatible, as it relies on getting both the work and machine positions at the same time # def parse_probe_response(self, response): # status = self.get_status() # machineOffsetX = status['work'][0] - status['machine'][0] # machineOffsetY = status['work'][1] - status['machine'][1] # machineOffsetZ = status['work'][2] - status['machine'][2] # lines = response.split('\n') # for line in lines: # print "Parsing probe response: ",line # m = re.match("\[PRB:([-0-9.]+),([-0-9.]+),([-0-9.]+):[01]+\]", line) # if m: # return float(m.group(1))+machineOffsetX, float(m.group(2))+machineOffsetY, float(m.group(3))+machineOffsetZ # return None, None, None def probe_z_offset(self): self.wait_motion() wasZComp = self.doZCompensation self.doZCompensation = False self.do_command("G90") # absolute positioning self.do_command("G10 P0 L20 Z0") # set current z to 0 x,y,z = self.do_probe() self.do_command("G10 P0 L20 Z0") # set Z=0 on piece self.doZCompensation = wasZComp # restore Z compensation return z def probe_grid(self, xRange, yRange, spacing): self.zCompensation = ZCompensation(xRange, yRange, spacing) self.do_command("G90") probePoints = self.zCompensation.getProbePoints() self.do_command("G0 X" + str(probePoints[0][0]) + " Y" + str(probePoints[0][1])) # move to first probe point safeZ = -self.probe_z_offset() self.zCompensation.setZValue(0, 0.0) # set first point to z=0 by default for pointIndex in range(1, len(probePoints)): # move to next probe point self.do_command("G0 Z" + str(safeZ)) # move to safe height self.do_command("G0 X" + str(probePoints[pointIndex][0]) + " Y" + str(probePoints[pointIndex][1])) # move to probe point x, y, z = self.do_probe() if z is None: self.zCompensation = None return False self.zCompensation.setZValue(pointIndex, z) self.do_command("G0 Z" + str(safeZ)) # move to safe height return True def compensate_z(self, status = True): self.doZCompensation = status def update_position(self): pos = self.get_status() if pos == None: return self.analyzer.syncStatusWithGrbl(pos) self.position_updated.emit(self.analyzer.getPosition()) def cancelJog(self): self.serial.write('\x85')
class GrblWriter(QObject): position_updated = Signal(object) def __init__(self): QObject.__init__(self) self.analyzer = GCodeAnalyzer(False) self.serial = None self.config = {} self.g0_feed = pycnc_config.G0_FEED self.waitAck = False self.storedPos = None # this will actually connect to Grbl def open(self): self.waitAck = False grbl_paths = glob.glob(pycnc_config.SERIAL_PATTERN) if not grbl_paths: return False # Device not existing try: self.serial = serial.Serial(grbl_paths[0], pycnc_config.BAUD, timeout=5, dsrdtr=True) self.serial.write("\r\n") time.sleep(2) # wake up! self.serial.flushInput() self.load_config() except: # serial port could not be opened return False self.analyzer.Reset() self.analyzer.fastf = self.g0_feed # everything OK return True def reset(self): self.serial.close() self.position_updated.emit([0,0,0]) return self.open() def read_response(self, until="ok"): """ read lines from the grbl until the expected matching line appears (usually "ok"), or just the first line if until is None. """ result = [] while True: line = self.serial.readline().strip() if line.startswith("error:"): break result.append(line) if line == until or until == None: break time.sleep(0.1) return '\n'.join(result) def do_command(self, gcode, wait=False): """ send the command to grbl, read the response and return it. if wait=True, wait for the stepper motion to complete before returning. """ # self.waitAck = False # only for nonblocking commands, so it should be false, but if we run a nonblocking command, and then a blocking one, the blocking might catch the previous ok command = gcode.strip() if not command or command[0] == '(': return self.serial.write(command + '\n') response = self.read_response() if wait: self.wait_motion() self.analyzer.Analyze(command) self.position_updated.emit(self.analyzer.getPosition()) return response def do_command_nonblock(self, gcode): # run a command but don't wait command = gcode.strip() if not command or command[0] == '(': return self.waitAck = True self.serial.write(command + '\n') self.analyzer.Analyze(command) self.position_updated.emit(self.analyzer.getPosition()) def ack_received(self): if not self.waitAck: return True, None # there is no serial to be received, return false if not self.serial.inWaiting() > 0: return False, None line = self.serial.readline().strip() if line.startswith("error:"): self.waitAck = False return True, line if line == "ok": self.waitAck = False return True, line # something was received, but was not error or ok, so no ack return False, line def wait_motion(self): """ force grbl to wait until all motion is complete. """ # # the gcode dwell command as implemented by grbl includes a # stepper-motor sync prior to beginning the dwell countdown. # use it to force a pause-until-caught-up. self.do_command("G4 P0") def wait_motion_nonblock(self): self.do_command_nonblock("G4 P0") # self.motionWaiting = True # self.serial.flushInput() # self.serial.write("G4 P0\n") def load_config(self): # query GRBL for the configuration conf = self.do_command("$$") self.config = {} for confLine in conf.split("\n"): key,val = readConfigLine(confLine) if key is not None: self.config[key] = val try: self.g0_feed = self.config[110] # $110 in grbl is max x rate except: pass # if it's not there, it's ok def store_pos(self): self.storedPos={} self.storedPos['Position'] = self.analyzer.getPosition() self.storedPos['f'] = self.analyzer.f self.storedPos['relative'] = self.analyzer.relative self.storedPos['metric'] = self.analyzer.metric def resume_pos(self): # go back to the stored position safeZ = self.analyzer.maxZ xyz = self.storedPos['Position'] self.do_command("G90") # go to abs positioning self.do_command("G21") # go to metric self.do_command("G0 Z%.4f" % safeZ) # move to safe Z self.do_command("G0 X%.4f Y%.4f" % (xyz[0], xyz[1])) # move to XY self.do_command("G1 Z%.4f F%.4f" % (xyz[2], self.storedPos['f'])) # move to Z using previous F # we are now in absolute metric: convert to relative/imperial if needed if self.storedPos['relative']: self.do_command("G91") if not self.storedPos['metric']: self.do_command("G20")
def __init__(self): QObject.__init__(self) self.analyzer = GCodeAnalyzer() self.g0_feed = 5000 self.config = {}
class GrblWriterBasic(QObject): position_updated = Signal(object) def __init__(self): QObject.__init__(self) self.analyzer = GCodeAnalyzer() self.g0_feed = 5000 self.config = {} # this will actually connect to Grbl def open(self): return True def reset(self): self.position_updated.emit([0,0,0]) self.analyzer.Reset() def do_command(self, gcode, wait=False): print(gcode) self.analyzer.Analyze(gcode) self.position_updated.emit(self.analyzer.getPosition()) time.sleep(0.1) def wait_motion(self): pass def do_command_nonblock(self, gcode): print gcode self.analyzer.Analyze(gcode) self.position_updated.emit(self.analyzer.getPosition()) time.sleep(0.01) def ack_received(self): return True, None def wait_motion_nonblock(self): pass def load_config(self): pass def store_pos(self): self.storedPos = {} self.storedPos['Position'] = self.analyzer.getPosition() self.storedPos['f'] = self.analyzer.f self.storedPos['relative'] = self.analyzer.relative self.storedPos['metric'] = self.analyzer.metric def resume_pos(self): # go back to the stored position safeZ = self.analyzer.maxZ xyz = self.storedPos['Position'] self.do_command("G90") # go to abs positioning self.do_command("G21") # go to metric self.do_command("G0 Z%.4f" % safeZ) # move to safe Z self.do_command("G0 X%.4f Y%.4f" % (xyz[0], xyz[1])) # move to XY self.do_command("G1 Z%.4f F%.4f" % (xyz[2], self.storedPos['f'])) # move to Z using previous F # we are now in absolute metric: convert to relative/imperial if needed if self.storedPos['relative']: self.do_command("G91") if not self.storedPos['metric']: self.do_command("G20")
class printcore(): def __init__(self, port=None, baud=None): """Initializes a printcore instance. Pass the port and baud rate to connect immediately """ self.baud = None self.port = None self.analyzer = GCodeAnalyzer() self.printer = None #Serial instance connected to the printer, None when disconnected self.clear = 0 #clear to send, enabled after responses self.online = False #The printer has responded to the initial command and is active self.printing = False #is a print currently running, true if printing, false if paused self.mainqueue = [] self.priqueue = [] self.queueindex = 0 self.lineno = 0 self.resendfrom = -1 self.paused = False self.sentlines = {} self.log = [] self.sent = [] self.tempcb = None #impl (wholeline) self.recvcb = None #impl (wholeline) self.sendcb = None #impl (wholeline) self.errorcb = None #impl (wholeline) self.startcb = None #impl () self.endcb = None #impl () self.onlinecb = None #impl () self.loud = False #emit sent and received lines to terminal self.greetings = ['start', 'Grbl '] self.wait = 0 # default wait period for send(), send_now() self.read_thread = None self.stop_read_thread = False self.print_thread = None if port is not None and baud is not None: self.connect(port, baud) self.xy_feedrate = None self.z_feedrate = None self.pronterface = None def disconnect(self): """Disconnects from printer and pauses the print """ if self.printer: if self.read_thread: self.stop_read_thread = True self.read_thread.join() self.read_thread = None self.printer.close() self.printer = None self.online = False self.printing = False def connect(self, port=None, baud=None): """Set port and baudrate if given, then connect to printer """ if self.printer: self.disconnect() if port is not None: self.port = port if baud is not None: self.baud = baud if self.port is not None and self.baud is not None: disable_hup(self.port) self.printer = Serial(port=self.port, baudrate=self.baud, timeout=0.25) self.stop_read_thread = False self.read_thread = Thread(target=self._listen) self.read_thread.start() def reset(self): """Reset the printer """ if self.printer: self.printer.setDTR(1) time.sleep(0.2) self.printer.setDTR(0) def _readline(self): try: line = self.printer.readline() if len(line) > 1: self.log.append(line) if self.recvcb: try: self.recvcb(line) except: pass if self.loud: print("RECV: ", line.rstrip()) return line except SelectError as e: if 'Bad file descriptor' in e.args[1]: print("Can't read from printer (disconnected?).") return None else: raise except SerialException as e: print("Can't read from printer (disconnected?).") return None except OSError as e: print("Can't read from printer (disconnected?).") return None def _listen_can_continue(self): return not self.stop_read_thread and self.printer and self.printer.isOpen( ) def _listen_until_online(self): while not self.online and self._listen_can_continue(): self._send("M105") empty_lines = 0 while self._listen_can_continue(): line = self._readline() if line == None: break # connection problem # workaround cases where M105 was sent before printer Serial # was online an empty line means read timeout was reached, # meaning no data was received thus we count those empty lines, # and once we have seen 5 in a row, we just break and send a # new M105 if not line: empty_lines += 1 else: empty_lines = 0 if empty_lines == 5: break if line.startswith(tuple( self.greetings)) or line.startswith('ok'): if self.onlinecb: try: self.onlinecb() except: pass self.online = True return time.sleep(0.25) def _listen(self): """This function acts on messages from the firmware """ self.clear = True if not self.printing: self._listen_until_online() while self._listen_can_continue(): line = self._readline() if line == None: break if line.startswith('DEBUG_'): continue if line.startswith(tuple(self.greetings)) or line.startswith('ok'): self.clear = True if line.startswith('ok') and "T:" in line and self.tempcb: #callback for temp, status, whatever try: self.tempcb(line) except: pass elif line.startswith('Error'): if self.errorcb: #callback for errors try: self.errorcb(line) except: pass # Teststrings for resend parsing # Firmware exp. result # line="rs N2 Expected checksum 67" # Teacup 2 if line.lower().startswith("resend") or line.startswith("rs"): line = line.replace("N:", " ").replace("N", " ").replace(":", " ") linewords = line.split() while len(linewords) != 0: try: toresend = int(linewords.pop(0)) self.resendfrom = toresend #print str(toresend) break except: pass self.clear = True self.clear = True def _checksum(self, command): return reduce(lambda x, y: x ^ y, list(map(ord, command))) def startprint(self, data, startindex=0): """Start a print, data is an array of gcode commands. returns True on success, False if already printing. The print queue will be replaced with the contents of the data array, the next line will be set to 0 and the firmware notified. Printing will then start in a parallel thread. """ if self.printing or not self.online or not self.printer: return False self.printing = True self.mainqueue = [] + data self.lineno = 0 self.queueindex = startindex self.resendfrom = -1 self._send("M110", -1, True) if len(data) == 0: return True self.clear = False self.print_thread = Thread(target=self._print) self.print_thread.start() return True # run a simple script if it exists, no multithreading def runSmallScript(self, filename): if filename == None: return f = None try: f = open(filename) except: pass if f != None: for i in f: l = i.replace("\n", "") l = l[:l.find(";")] #remove comment self.send_now(l) f.close() def pause(self): """Pauses the print, saving the current position. """ if not self.printing: return False self.paused = True self.printing = False # try joining the print thread: enclose it in try/except because we might be calling it from the thread itself try: self.print_thread.join() except: pass self.print_thread = None # saves the status self.pauseX = self.analyzer.x - self.analyzer.xOffset self.pauseY = self.analyzer.y - self.analyzer.yOffset self.pauseZ = self.analyzer.z - self.analyzer.zOffset self.pauseE = self.analyzer.e - self.analyzer.eOffset self.pauseF = self.analyzer.f self.pauseRelative = self.analyzer.relative def resume(self): """Resumes a paused print. """ if not self.paused: return False if self.paused: #restores the status self.send_now("G90") # go to absolute coordinates xyFeedString = "" zFeedString = "" if self.xy_feedrate != None: xyFeedString = " F" + str(self.xy_feedrate) if self.z_feedrate != None: zFeedString = " F" + str(self.z_feedrate) self.send_now("G1 X" + str(self.pauseX) + " Y" + str(self.pauseY) + xyFeedString) self.send_now("G1 Z" + str(self.pauseZ) + zFeedString) self.send_now("G92 E" + str(self.pauseE)) if self.pauseRelative: self.send_now("G91") # go back to relative if needed #reset old feed rate self.send_now("G1 F" + str(self.pauseF)) self.paused = False self.printing = True self.print_thread = Thread(target=self._print) self.print_thread.start() def send(self, command, wait=0): """Adds a command to the checksummed main command queue if printing, or sends the command immediately if not printing """ if self.online: if self.printing: self.mainqueue.append(command) else: while self.printer and self.printing and not self.clear: time.sleep(0.001) if wait == 0 and self.wait > 0: wait = self.wait if wait > 0: self.clear = False self._send(command, self.lineno, True) self.lineno += 1 while wait > 0 and self.printer and self.printing and not self.clear: time.sleep(0.001) wait -= 1 else: print("Not connected to printer.") def send_now(self, command, wait=0): """Sends a command to the printer ahead of the command queue, without a checksum """ if self.online or force: if self.printing: self.priqueue.append(command) else: while self.printer and self.printing and not self.clear: time.sleep(0.001) if wait == 0 and self.wait > 0: wait = self.wait if wait > 0: self.clear = False self._send(command) while (wait > 0 ) and self.printer and self.printing and not self.clear: time.sleep(0.001) wait -= 1 else: print("Not connected to printer.") def _print(self): if self.startcb: #callback for printing started try: self.startcb() except: pass while self.printing and self.printer and self.online: self._sendnext() self.sentlines = {} self.log = [] self.sent = [] try: self.print_thread.join() except: pass self.print_thread = None if self.endcb: #callback for printing done try: self.endcb() except: pass #now only "pause" is implemented as host command def processHostCommand(self, command): command = command.lstrip() if command.startswith(";@pause"): if self.pronterface != None: self.pronterface.pause(None) else: self.pause() def _sendnext(self): if not self.printer: return while self.printer and self.printing and not self.clear: time.sleep(0.001) self.clear = False if not (self.printing and self.printer and self.online): self.clear = True return if self.resendfrom < self.lineno and self.resendfrom > -1: self._send(self.sentlines[self.resendfrom], self.resendfrom, False) self.resendfrom += 1 return self.resendfrom = -1 for i in self.priqueue[:]: self._send(i) del self.priqueue[0] return if self.printing and self.queueindex < len(self.mainqueue): tline = self.mainqueue[self.queueindex] #check for host command if tline.lstrip().startswith(";@"): #it is a host command: pop it from the list self.mainqueue.pop(self.queueindex) self.processHostCommand(tline) return tline = tline.split(";")[0] if len(tline) > 0: self._send(tline, self.lineno, True) self.lineno += 1 else: self.clear = True self.queueindex += 1 else: self.printing = False self.clear = True if not self.paused: self.queueindex = 0 self.lineno = 0 self._send("M110", -1, True) def _send(self, command, lineno=0, calcchecksum=False): if calcchecksum: prefix = "N" + str(lineno) + " " + command command = prefix + "*" + str(self._checksum(prefix)) if "M110" not in command: self.sentlines[lineno] = command if self.printer: self.sent.append(command) self.analyzer.Analyze( command) # run the command through the analyzer if self.loud: print("SENT: ", command) if self.sendcb: try: self.sendcb(command) except: pass try: self.printer.write(str(command + "\n")) except SerialException as e: print("Can't write to printer (disconnected?).")