From 728172136a5038ad76eba20236bda03283d793ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Fri, 21 Oct 2016 20:36:55 +0200 Subject: [PATCH 1/3] Record position on pause & track F --- src/octoprint/util/comm.py | 115 ++++++++++++++++++++++++++++++------- 1 file changed, 94 insertions(+), 21 deletions(-) diff --git a/src/octoprint/util/comm.py b/src/octoprint/util/comm.py index 74399cb4..793dd9ae 100644 --- a/src/octoprint/util/comm.py +++ b/src/octoprint/util/comm.py @@ -50,9 +50,13 @@ regex_float = re.compile(regex_float_pattern) """Regex for a float value.""" regexes_parameters = dict( + floatE=re.compile("(^|[^A-Za-z])[Ee](?P%s)" % regex_float_pattern), + floatF=re.compile("(^|[^A-Za-z])[Ff](?P%s)" % regex_float_pattern), floatP=re.compile("(^|[^A-Za-z])[Pp](?P%s)" % regex_float_pattern), floatR=re.compile("(^|[^A-Za-z])[Rr](?P%s)" % regex_float_pattern), floatS=re.compile("(^|[^A-Za-z])[Ss](?P%s)" % regex_float_pattern), + floatX=re.compile("(^|[^A-Za-z])[Xx](?P%s)" % regex_float_pattern), + floatY=re.compile("(^|[^A-Za-z])[Yy](?P%s)" % regex_float_pattern), floatZ=re.compile("(^|[^A-Za-z])[Zz](?P%s)" % regex_float_pattern), intN=re.compile("(^|[^A-Za-z])[Nn](?P%s)" % regex_int_pattern), intT=re.compile("(^|[^A-Za-z])[Tt](?P%s)" % regex_int_pattern) @@ -109,6 +113,17 @@ Groups will be as follows: * ``target``: new target temperature (float) """ +regex_position = re.compile("X:(?P{float})\s+Y:(?P{float})\s+Z:(?P{float})\s+E:(?P{float})".format(float=regex_float_pattern)) +"""Regex for matching position reporting. + +Groups will be as follows: + + * ``x``: X coordinate + * ``y``: Y coordinate + * ``z``: Z coordinate + * ``e``: E coordinate +""" + def serialList(): baselist=[] if os.name=="nt": @@ -188,6 +203,23 @@ gcodeToEvent = { "M81": Events.POWER_OFF, } +class PositionRecord(object): + def __init__(self, *args, **kwargs): + self.x = kwargs.get("x") + self.y = kwargs.get("y") + self.z = kwargs.get("z") + self.e = kwargs.get("e") + self.f = kwargs.get("f") + self.t = kwargs.get("t") + + def copy_from(self, other): + self.x = other.x + self.y = other.y + self.z = other.z + self.e = other.e + self.f = other.f + self.t = other.t + class MachineCom(object): STATE_NONE = 0 STATE_OPEN_SERIAL = 1 @@ -231,6 +263,7 @@ class MachineCom(object): self._tempOffsets = dict() self._command_queue = TypedQueue() self._currentZ = None + self._currentF = None self._heatupWaitStartTime = None self._heatupWaitTimeLost = 0.0 self._pauseWaitStartTime = None @@ -329,6 +362,10 @@ class MachineCom(object): self._ignore_select = False self._manualStreaming = False + self._last_position = PositionRecord() + self._pause_position = PositionRecord() + self._record_pause_position = False + # print job self._currentFile = None @@ -601,7 +638,7 @@ class MachineCom(object): def fakeOk(self): self._handle_ok() - def sendCommand(self, cmd, cmd_type=None, processed=False, force=False): + def sendCommand(self, cmd, cmd_type=None, processed=False, force=False, on_sent=None): cmd = to_unicode(cmd, errors="replace") if not processed: cmd = process_gcode_line(cmd) @@ -610,20 +647,22 @@ class MachineCom(object): if self.isPrinting() and not self.isSdFileSelected(): try: - self._command_queue.put((cmd, cmd_type), item_type=cmd_type) + self._command_queue.put((cmd, cmd_type, on_sent), item_type=cmd_type) return True except TypeAlreadyInQueue as e: self._logger.debug("Type already in command queue: " + e.type) return False elif self.isOperational() or force: - return self._sendCommand(cmd, cmd_type=cmd_type) + return self._sendCommand(cmd, cmd_type=cmd_type, on_sent=on_sent) def sendGcodeScript(self, scriptName, replacements=None): context = dict() if replacements is not None and isinstance(replacements, dict): context.update(replacements) context.update(dict( - printer_profile=self._printerProfileManager.get_current_or_default() + printer_profile=self._printerProfileManager.get_current_or_default(), + last_position=self._last_position, + pause_position=self._pause_position )) template = settings().loadScript("gcode", scriptName, context=context) @@ -781,6 +820,9 @@ class MachineCom(object): self._recordFilePosition() self._callback.on_comm_print_job_cancelled() + def _pause_preparation_done(self): + self._callback.on_comm_print_job_paused() + def setPause(self, pause): if self.isStreaming(): return @@ -815,8 +857,14 @@ class MachineCom(object): if self.isSdFileSelected(): self.sendCommand("M25") # pause print - self._callback.on_comm_print_job_paused() + def _on_M400_sent(): + # we don't call on_print_job_paused on our callback here + # because we do this only after our M114 has been answered + # by the firmware + self._record_pause_position = True + self.sendCommand("M114") + self.sendCommand("M400", on_sent=_on_M400_sent) def getSdFiles(self): return self._sdFiles @@ -1064,12 +1112,23 @@ class MachineCom(object): # position report processing if 'X:' in line and 'Y:' in line and 'Z:' in line: - # currently only here to prevent any "B:"s in a position report from - # triggering temperature processing and being interpreted as a bed - # temperature - can happen with COREXY in current Marlin - # - # see also issue #1373 - pass + match = regex_position.search(line) + if match: + # we don't know T or F when printing from SD since + # there's no way to query it from the firmware and + # no way to track it ourselves when not streaming + # the file - this all sucks sooo much + self._last_position.x = float(match.group("x")) + self._last_position.y = float(match.group("y")) + self._last_position.z = float(match.group("z")) + self._last_position.e = float(match.group("e")) + self._last_position.t = self._currentTool if not self.isSdFileSelected() else None + self._last_position.f = self._currentF if not self.isSdFileSelected() else None + + if self._record_pause_position: + self._record_pause_position = False + self._pause_position.copy_from(self._last_position) + self._pause_preparation_done() # temperature processing elif ' T:' in line or line.startswith('T:') or ' T0:' in line or line.startswith('T0:') or ((' B:' in line or line.startswith('B:')) and not 'A:' in line): @@ -1472,16 +1531,17 @@ class MachineCom(object): try: if isinstance(entry, tuple): - if not len(entry) == 2: + if not len(entry) == 3: # something with that entry is broken, ignore it and fetch # the next one continue - cmd, cmd_type = entry + cmd, cmd_type, callback = entry else: cmd = entry cmd_type = None + callback = None - if self._sendCommand(cmd, cmd_type=cmd_type): + if self._sendCommand(cmd, cmd_type=cmd_type, on_sent=callback): # we actually did add this cmd to the send queue, so let's # return, we are done here return True @@ -1809,7 +1869,7 @@ class MachineCom(object): return result - def _sendCommand(self, cmd, cmd_type=None): + def _sendCommand(self, cmd, cmd_type=None, on_sent=None): # Make sure we are only handling one sending job at a time with self._sendingLock: if self._serial is None: @@ -1829,7 +1889,7 @@ class MachineCom(object): eventManager().fire(gcodeToEvent[gcode]) # actually enqueue the command for sending - if self._enqueue_for_sending(cmd, command_type=cmd_type): + if self._enqueue_for_sending(cmd, command_type=cmd_type, on_sent=on_sent): self._process_command_phase("queued", cmd, cmd_type, gcode=gcode) return True else: @@ -1837,7 +1897,7 @@ class MachineCom(object): ##~~ send loop handling - def _enqueue_for_sending(self, command, linenumber=None, command_type=None): + def _enqueue_for_sending(self, command, linenumber=None, command_type=None, on_sent=None): """ Enqueues a command an optional linenumber to use for it in the send queue. @@ -1848,7 +1908,7 @@ class MachineCom(object): """ try: - self._send_queue.put((command, linenumber, command_type), item_type=command_type) + self._send_queue.put((command, linenumber, command_type, on_sent), item_type=command_type) return True except TypeAlreadyInQueue as e: self._logger.debug("Type already in send queue: " + e.type) @@ -1872,8 +1932,8 @@ class MachineCom(object): if not self._send_queue_active: break - # fetch command and optional linenumber from queue - command, linenumber, command_type = entry + # fetch command, command type and optional linenumber and sent callback from queue + command, linenumber, command_type, on_sent = entry # some firmwares (e.g. Smoothie) might support additional in-band communication that will not # stick to the acknowledgement behaviour of GCODE, so we check here if we have a GCODE command @@ -1921,6 +1981,9 @@ class MachineCom(object): self._do_send_without_checksum(command_to_send) # trigger "sent" phase and use up one "ok" + if on_sent is not None and callable(on_sent): + # we have a sent callback for this specific command, let's execute it now + on_sent() self._process_command_phase("sent", command, command_type, gcode=gcode) # we only need to use up a clear if the command we just sent was either a gcode command or if we also @@ -2093,7 +2156,8 @@ class MachineCom(object): self._currentTool = int(toolMatch.group("value")) def _gcode_G0_sent(self, cmd, cmd_type=None): - if 'Z' in cmd: + if "Z" in cmd or "F" in cmd: + # track Z match = regexes_parameters["floatZ"].search(cmd) if match: try: @@ -2103,6 +2167,15 @@ class MachineCom(object): self._callback.on_comm_z_change(z) except ValueError: pass + + # track F + match = regexes_parameters["floatF"].search(cmd) + if match: + try: + f = float(match.group("value")) + self._currentF = f + except ValueError: + pass _gcode_G1_sent = _gcode_G0_sent def _gcode_M0_queuing(self, cmd, cmd_type=None): From 3f0bfa0fc8d8d1ee6e4b0ddde12fb7ed81d3559e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Wed, 26 Oct 2016 18:11:00 +0200 Subject: [PATCH 2/3] New POSITION_UPDATE event for responses to M114 --- src/octoprint/events.py | 1 + src/octoprint/printer/standard.py | 3 +++ src/octoprint/util/comm.py | 13 +++++++++++++ 3 files changed, 17 insertions(+) diff --git a/src/octoprint/events.py b/src/octoprint/events.py index 1791d5cb..b8f12ad0 100644 --- a/src/octoprint/events.py +++ b/src/octoprint/events.py @@ -78,6 +78,7 @@ class Events(object): CONVEYOR = "Conveyor" EJECT = "Eject" E_STOP = "EStop" + POSITION_UPDATE = "PositionUpdate" REGISTERED_MESSAGE_RECEIVED = "RegisteredMessageReceived" # Timelapse diff --git a/src/octoprint/printer/standard.py b/src/octoprint/printer/standard.py index 082c4321..5a9719bc 100644 --- a/src/octoprint/printer/standard.py +++ b/src/octoprint/printer/standard.py @@ -956,6 +956,9 @@ class Printer(PrinterInterface, comm.MachineComPrintCallback): def on_comm_temperature_update(self, temp, bedTemp): self._addTemperatureData(copy.deepcopy(temp), copy.deepcopy(bedTemp)) + def on_comm_position_update(self, position): + eventManager().fire(Events.POSITION_UPDATE, position) + def on_comm_state_change(self, state): """ Callback method for the comm object, called if the connection state changes. diff --git a/src/octoprint/util/comm.py b/src/octoprint/util/comm.py index 793dd9ae..1648ae85 100644 --- a/src/octoprint/util/comm.py +++ b/src/octoprint/util/comm.py @@ -220,6 +220,14 @@ class PositionRecord(object): self.f = other.f self.t = other.t + def as_dict(self): + return dict(x=self.x, + y=self.y, + z=self.z, + e=self.e, + t=self.t, + f=self.f) + class MachineCom(object): STATE_NONE = 0 STATE_OPEN_SERIAL = 1 @@ -1130,6 +1138,8 @@ class MachineCom(object): self._pause_position.copy_from(self._last_position) self._pause_preparation_done() + self._callback.on_comm_position_update(self._last_position.as_dict()) + # temperature processing elif ' T:' in line or line.startswith('T:') or ' T0:' in line or line.startswith('T0:') or ((' B:' in line or line.startswith('B:')) and not 'A:' in line): if not disable_external_heatup_detection and not line.strip().startswith("ok") and not self._heating: @@ -2341,6 +2351,9 @@ class MachineComPrintCallback(object): def on_comm_temperature_update(self, temp, bedTemp): pass + def on_comm_position_update(self, position): + pass + def on_comm_state_change(self, state): pass From fae27c77b5b719bd2aab594d27796b6bf4110b7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Fri, 4 Nov 2016 12:12:30 +0100 Subject: [PATCH 3/3] Have virtual printer coordinates start at 0.0 --- src/octoprint/plugins/virtual_printer/virtual.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/octoprint/plugins/virtual_printer/virtual.py b/src/octoprint/plugins/virtual_printer/virtual.py index 9bde9949..2ae3ba86 100644 --- a/src/octoprint/plugins/virtual_printer/virtual.py +++ b/src/octoprint/plugins/virtual_printer/virtual.py @@ -62,10 +62,10 @@ class VirtualPrinter(object): self.speeds = settings().get(["devel", "virtualPrinter", "movementSpeed"]) self._relative = True - self._lastX = None - self._lastY = None - self._lastZ = None - self._lastE = None + self._lastX = 0.0 + self._lastY = 0.0 + self._lastZ = 0.0 + self._lastE = 0.0 self._unitModifier = 1