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()
Esempio n. 2
0
	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()
Esempio n. 3
0
 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()
Esempio n. 6
0
 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
Esempio n. 7
0
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 "-"
Esempio n. 8
0
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()
Esempio n. 9
0
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')
Esempio n. 10
0
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")
Esempio n. 13
0
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?).")