Example #1
0
    def __init__(self, octolapse_settings, octoprint_printer_profile,
                 g90_influences_extruder):
        self.Settings = octolapse_settings
        self.Printer = Printer(self.Settings.current_printer())
        self.Snapshot = Snapshot(self.Settings.current_snapshot())
        self.OctoprintPrinterProfile = octoprint_printer_profile
        self.Origin = {
            "X": self.Printer.origin_x,
            "Y": self.Printer.origin_y,
            "Z": self.Printer.origin_z
        }

        self.BoundingBox = utility.get_bounding_box(self.Printer,
                                                    octoprint_printer_profile)
        self.PrinterTolerance = self.Printer.printer_position_confirmation_tolerance
        self.Positions = deque(maxlen=5)
        self.SavedPosition = None
        self.HasRestrictedPosition = len(
            self.Snapshot.position_restrictions) > 0

        self.reset()

        self.Extruder = Extruder(octolapse_settings)
        if self.Printer.g90_influences_extruder in ['true', 'false']:
            self.G90InfluencesExtruder = True if self.Printer.g90_influences_extruder == 'true' else False
        else:
            self.G90InfluencesExtruder = g90_influences_extruder

        if self.Printer.z_hop is None:
            self.Printer.z_hop = 0

        self.Commands = command.Commands()
        self.LocationDetectionCommands = []
        self.create_location_detection_commands()
Example #2
0
    def __init__(self, octolapseSettings, octoprintPrinterProfile,
                 g90InfluencesExtruder):
        self.Settings = octolapseSettings
        self.Printer = self.Settings.CurrentPrinter()
        self.OctoprintPrinterProfile = octoprintPrinterProfile
        self.PrinterTolerance = self.Printer.printer_position_confirmation_tolerance
        self.Positions = []
        self.Reset()

        self.Extruder = Extruder(octolapseSettings)
        self.G90InfluencesExtruder = g90InfluencesExtruder

        if (self.Printer.z_hop is None):
            self.Printer.z_hop = 0

        self.Commands = Commands()
Example #3
0
 def setUp(self):
     self.Settings = OctolapseSettings(NamedTemporaryFile().name)
     self.OctoprintPrinterProfile = self.create_octoprint_printer_profile()
     printer = get_printer_profile()
     self.Settings.printers.update({printer["guid"]: Printer(printer=printer)})
     self.Settings.profiles.current_printer_profile_guid = printer["guid"]
     self.Extruder = Extruder(self.Settings)
     self.Position = Position(self.Settings, self.OctoprintPrinterProfile, False)
Example #4
0
    def __init__(self, octolapseSettings, octoprintPrinterProfile,
                 g90InfluencesExtruder):
        self.Settings = octolapseSettings
        self.Printer = self.Settings.CurrentPrinter()
        self.OctoprintPrinterProfile = octoprintPrinterProfile
        self.Origin = {
            "X": self.Printer.origin_x,
            "Y": self.Printer.origin_y,
            "Z": self.Printer.origin_z
        }

        self.BoundingBox = utility.GetBoundingBox(self.Printer,
                                                  octoprintPrinterProfile)
        self.PrinterTolerance = self.Printer.printer_position_confirmation_tolerance
        self.Positions = []
        self.Reset()

        self.Extruder = Extruder(octolapseSettings)
        self.G90InfluencesExtruder = g90InfluencesExtruder

        if (self.Printer.z_hop is None):
            self.Printer.z_hop = 0

        self.Commands = Commands()
Example #5
0
class Position(object):
    def __init__(self, octolapse_settings, octoprint_printer_profile,
                 g90_influences_extruder):
        self.Settings = octolapse_settings
        self.Printer = Printer(self.Settings.current_printer())
        self.Snapshot = Snapshot(self.Settings.current_snapshot())
        self.OctoprintPrinterProfile = octoprint_printer_profile
        self.Origin = {
            "X": self.Printer.origin_x,
            "Y": self.Printer.origin_y,
            "Z": self.Printer.origin_z
        }

        self.BoundingBox = utility.get_bounding_box(self.Printer,
                                                    octoprint_printer_profile)
        self.PrinterTolerance = self.Printer.printer_position_confirmation_tolerance
        self.Positions = deque(maxlen=5)
        self.SavedPosition = None
        self.HasRestrictedPosition = len(
            self.Snapshot.position_restrictions) > 0

        self.reset()

        self.Extruder = Extruder(octolapse_settings)
        if self.Printer.g90_influences_extruder in ['true', 'false']:
            self.G90InfluencesExtruder = True if self.Printer.g90_influences_extruder == 'true' else False
        else:
            self.G90InfluencesExtruder = g90_influences_extruder

        if self.Printer.z_hop is None:
            self.Printer.z_hop = 0

        self.Commands = command.Commands()
        self.LocationDetectionCommands = []
        self.create_location_detection_commands()

    def create_location_detection_commands(self):

        if self.Printer.auto_position_detection_commands is not None:
            trimmed_commands = self.Printer.auto_position_detection_commands.strip(
            )
            if len(trimmed_commands) > 0:
                self.LocationDetectionCommands = [
                    x.strip().upper() for x in
                    self.Printer.auto_position_detection_commands.split(',')
                ]
        if "G28" not in self.LocationDetectionCommands:
            self.LocationDetectionCommands.append("G28")
        if "G29" not in self.LocationDetectionCommands:
            self.LocationDetectionCommands.append("G29")

    def reset(self):
        # todo: This reset function doesn't seem to reset everything.
        self.Positions.clear()
        self.SavedPosition = None

    def update_position(self,
                        x=None,
                        y=None,
                        z=None,
                        e=None,
                        f=None,
                        force=False,
                        calculate_changes=False):
        num_positions = len(self.Positions)
        if num_positions == 0:
            return
        pos = self.Positions[0]
        pos.update_position(self.BoundingBox, x, y, z, e, f, force)
        if calculate_changes and num_positions > 1:
            previous_pos = self.Positions[1]
            pos.HasPositionChanged = not pos.is_position_equal(
                previous_pos, self.PrinterTolerance)
            pos.HasStateChanged = not pos.is_state_equal(
                previous_pos, self.PrinterTolerance)

    def to_dict(self):
        if len(self.Positions) > 0:
            previous_pos = self.Positions[0]
            return previous_pos.to_dict()
        return None

    def to_position_dict(self):
        if len(self.Positions) > 0:
            previous_pos = self.Positions[0]
            return previous_pos.to_position_dict()
        return None

    def to_state_dict(self):
        if len(self.Positions) > 0:
            previous_pos = self.Positions[0]
            return previous_pos.to_state_dict()
        return None

    def z_delta(self, pos, index=0):
        previous_pos = self.get_position(index)
        if previous_pos is not None:
            # calculate ZDelta
            if pos.Height is not None:
                if previous_pos.Height is None:
                    return pos.Height
                else:
                    return pos.Height - previous_pos.Height
        return 0

    def distance_to_zlift(self, index=0):
        pos = self.get_position(index)
        assert (isinstance(pos, Pos))

        if pos is None:
            return None

        # get the lift amount, but don't restrict it so we can log properly
        amount_to_lift = pos.distance_to_zlift(self.Printer.z_hop, False)

        if amount_to_lift < 0:
            # the current lift is negative
            self.Settings.current_debug_profile().log_warning(
                "position.py - A 'distance_to_zlift' was requested, "
                "but the current lift is already above the z_hop height.")
            return 0
        elif amount_to_lift > self.Printer.z_hop:
            # For some reason we're lower than we expected
            self.Settings.current_debug_profile().log_warning(
                "position.py - A 'distance_to_zlift' was requested, "
                "but was found to be more than the z_hop height.")
            return self.Printer.z_hop
        else:
            # we are in-between 0 and z_hop, calculate lift
            return amount_to_lift

    def has_state_changed(self, index=0):
        pos = self.get_position(index)
        if pos is None:
            return None
        return pos.HasStateChanged

    def is_in_position(self, index=0):
        pos = self.get_position(index)
        if pos is None:
            return None
        return pos.IsInPosition

    def in_path_position(self, index=0):
        pos = self.get_position(index)
        if pos is None:
            return None
        return pos.InPathPosition

    def has_position_changed(self, index=0):
        pos = self.get_position(index)
        if pos is None:
            return None
        return pos.HasPositionChanged

    def has_position_error(self, index=0):
        pos = self.get_position(index)
        if pos is None:
            return None
        return pos.HasPositionError

    def position_error(self, index=0):
        pos = self.get_position(index)
        if pos is None:
            return None
        return pos.PositionError

    def x(self, index=0):
        pos = self.get_position(index)
        if pos is None:
            return None
        return pos.X

    def x_offset(self, index=0):
        pos = self.get_position(index)
        if pos is None:
            return None
        return pos.XOffset

    def y(self, index=0):
        pos = self.get_position(index)
        if pos is None:
            return None
        return pos.Y

    def y_offset(self, index=0):
        pos = self.get_position(index)
        if pos is None:
            return None
        return pos.YOffset

    def z(self, index=0):
        pos = self.get_position(index)
        if pos is None:
            return None
        return pos.Z

    def z_offset(self, index=0):
        pos = self.get_position(index)
        if pos is None:
            return None
        return pos.ZOffset

    def e(self, index=0):
        pos = self.get_position(index)
        if pos is None:
            return None
        return pos.E

    def e_offset(self, index=0):
        pos = self.get_position(index)
        if pos is None:
            return None
        return pos.EOffset

    def f(self, index=0):
        pos = self.get_position(index)
        if pos is None:
            return None
        return pos.F

    def is_zhop(self, index=0):
        pos = self.get_position(index)
        if pos is None:
            return None
        return pos.IsZHop

    def is_layer_change(self, index=0):
        pos = self.get_position(index)
        if pos is None:
            return None
        return pos.IsLayerChange

    def layer(self, index=0):
        pos = self.get_position(index)
        if pos is None:
            return None
        return pos.Layer

    def height(self, index=0):
        pos = self.get_position(index)
        if pos is None:
            return None
        return pos.Height

    def is_relative(self, index=0):
        pos = self.get_position(index)
        if pos is None:
            return None
        return pos.IsRelative

    def is_extruder_relative(self, index=0):
        pos = self.get_position(index)
        if pos is None:
            return None
        return pos.IsExtruderRelative

    def is_metric(self, index=0):
        pos = self.get_position(index)
        if pos is None:
            return None
        return pos.IsMetric

    def has_received_home_command(self, index=0):
        pos = self.get_position(index)
        if pos is None:
            return False
        return pos.HasReceivedHomeCommand and self.has_homed_axes(index)

    def command_requires_location_detection(self, cmd):
        if self.Printer.auto_detect_position:
            gcode = command.get_gcode_from_string(cmd)
            if gcode in self.LocationDetectionCommands:
                return True
        return False

    def requires_location_detection(self, index=0):
        pos = self.get_position(index)
        if pos is None:
            return False

        if self.command_requires_location_detection(pos.GCode):
            return True
        return False

    def undo_update(self):
        pos = self.get_position(0)
        if pos is not None:
            self.Positions.popleft()
        self.Extruder.undo_update()

    def get_position(self, index=0):
        if len(self.Positions) > index:
            return self.Positions[index]
        return None

    def update(self, gcode):
        cmd = self.Commands.get_command(gcode)
        # a new position

        pos = None
        previous_pos = None
        num_positions = len(self.Positions)
        if num_positions > 0:
            pos = Pos(self.Printer, self.OctoprintPrinterProfile,
                      self.Positions[0])
            previous_pos = Pos(self.Printer, self.OctoprintPrinterProfile,
                               self.Positions[0])
        if pos is None:
            pos = Pos(self.Printer, self.OctoprintPrinterProfile)
        if previous_pos is None:
            previous_pos = Pos(self.Printer, self.OctoprintPrinterProfile)

        # reset the current position state (copied from the previous position,
        # or a
        # new position)
        pos.reset_state()
        # set the pos gcode cmd
        pos.GCode = gcode

        # apply the cmd to the position tracker
        # TODO: this should NOT be an else/if structure anymore..  Simplify
        if cmd is not None:

            if cmd.Command in self.Commands.CommandsRequireMetric and not pos.IsMetric:
                pos.HasPositionError = True
                pos.PositionError = "Units are not metric.  Unable to continue print."
            elif cmd.Command in ["G0", "G1"]:
                # Movement
                if cmd.parse():
                    self.Settings.current_debug_profile(
                    ).log_position_command_received("Received {0}".format(
                        cmd.Name))
                    x = cmd.Parameters["X"].Value
                    y = cmd.Parameters["Y"].Value
                    z = cmd.Parameters["Z"].Value
                    e = cmd.Parameters["E"].Value
                    f = cmd.Parameters["F"].Value

                    if x is not None or y is not None or z is not None or f is not None:
                        if pos.IsRelative is not None:
                            if pos.HasPositionError and not pos.IsRelative:
                                pos.HasPositionError = False
                                pos.PositionError = ""
                            pos.update_position(self.BoundingBox,
                                                x,
                                                y,
                                                z,
                                                e=None,
                                                f=f)
                        else:
                            self.Settings.current_debug_profile(
                            ).log_position_command_received(
                                "Position - Unable to update the X/Y/Z axis position, the axis mode ("
                                "relative/absolute) has not been explicitly set via G90/G91. "
                            )
                    if e is not None:
                        if pos.IsExtruderRelative is not None:
                            if pos.HasPositionError and not pos.IsExtruderRelative:
                                pos.HasPositionError = False
                                pos.PositionError = ""
                            pos.update_position(self.BoundingBox,
                                                x=None,
                                                y=None,
                                                z=None,
                                                e=e,
                                                f=None)
                        else:
                            self.Settings.current_debug_profile().log_error(
                                "Position - Unable to update the extruder position, the extruder mode ("
                                "relative/absolute) has been selected (absolute/relative). "
                            )
                    message = "Position Change - {0} - {1} Move From(X:{2},Y:{3},Z:{4},E:{5}) - To(X:{6},Y:{7},Z:{8}," \
                              "E:{9}) "
                    if previous_pos is None:
                        message = message.format(
                            gcode,
                            "Relative" if pos.IsRelative else "Absolute",
                            "None", "None", "None", "None", pos.X, pos.Y,
                            pos.Z, pos.E)
                    else:
                        message = message.format(
                            gcode,
                            "Relative" if pos.IsRelative else "Absolute",
                            previous_pos.X, previous_pos.Y, previous_pos.Z,
                            previous_pos.E, pos.X, pos.Y, pos.Z, pos.E)
                    self.Settings.current_debug_profile().log_position_change(
                        message)
                else:
                    error_message = "Unable to parse the gcode command: {0}".format(
                        gcode)
                    pos.HasPositionError = True
                    # Todo:  We need to end the timelapse if we get here
                    #pos.X = None
                    #pos.Y = None
                    #pos.Z = None
                    #pos.E = None#
                    #pos.F = None
                    pos.PositionError = "Unable to parse the gcode command"
                    self.Settings.current_debug_profile().log_error(
                        "Position - {0}".format(error_message))
            elif cmd.Command == "G20":
                # change units to inches
                if pos.IsMetric is None or pos.IsMetric:
                    self.Settings.current_debug_profile(
                    ).log_position_command_received(
                        "Received G20 - Switching units to inches.")
                    pos.IsMetric = False
                else:
                    self.Settings.current_debug_profile(
                    ).log_position_command_received(
                        "Received G20 - Already in inches.")
            elif cmd.Command == "G21":
                # change units to millimeters
                if pos.IsMetric is None or not pos.IsMetric:
                    self.Settings.current_debug_profile(
                    ).log_position_command_received(
                        "Received G21 - Switching units to millimeters.")
                    pos.IsMetric = True
                else:
                    self.Settings.current_debug_profile(
                    ).log_position_command_received(
                        "Received G21 - Already in millimeters.")
            elif cmd.Command == "G28":
                # Home
                if cmd.parse():
                    pos.HasReceivedHomeCommand = True
                    x = cmd.Parameters["X"].Value
                    y = cmd.Parameters["Y"].Value
                    z = cmd.Parameters["Z"].Value
                    # ignore the W parameter, it's used in Prusa firmware to indicate a home without mesh bed leveling
                    # w = cmd.Parameters["W"].Value
                    x_homed = False
                    y_homed = False
                    z_homed = False
                    if x is not None:
                        x_homed = True
                    if y is not None:
                        y_homed = True
                    if z is not None:
                        z_homed = True

                    # if there are no x,y or z parameters, we're homing all axes
                    if x is None and y is None and z is None:
                        x_homed = True
                        y_homed = True
                        z_homed = True

                    home_strings = []
                    if x_homed:
                        pos.XHomed = True
                        pos.X = self.Origin[
                            "X"] if not self.Printer.auto_detect_position else None
                        if pos.X is None:
                            home_strings.append("Homing X to Unknown Origin.")
                        else:
                            home_strings.append("Homing X to {0}.".format(
                                get_formatted_coordinate(pos.X)))
                    if y_homed:
                        pos.YHomed = True
                        pos.Y = self.Origin[
                            "Y"] if not self.Printer.auto_detect_position else None
                        if pos.Y is None:
                            home_strings.append("Homing Y to Unknown Origin.")
                        else:
                            home_strings.append("Homing Y to {0}.".format(
                                get_formatted_coordinate(pos.Y)))
                    if z_homed:
                        pos.ZHomed = True
                        pos.Z = self.Origin[
                            "Z"] if not self.Printer.auto_detect_position else None
                        if pos.Z is None:
                            home_strings.append("Homing Z to Unknown Origin.")
                        else:
                            home_strings.append("Homing Z to {0}.".format(
                                get_formatted_coordinate(pos.Z)))

                    self.Settings.current_debug_profile(
                    ).log_position_command_received("Received G28 - ".format(
                        " ".join(home_strings)))
                    pos.HasPositionError = False
                    pos.PositionError = None
                    # we must do this in case we have more than one home command
                    previous_pos = Pos(self.Printer,
                                       self.OctoprintPrinterProfile, pos)
                else:
                    self.Settings.current_debug_profile().log_error(
                        "Position - Unable to parse the Gcode:{0}".format(
                            gcode))
            elif cmd.Command == "G90":
                # change x,y,z to absolute
                if pos.IsRelative is None or pos.IsRelative:
                    self.Settings.current_debug_profile(
                    ).log_position_command_received(
                        "Received G90 - Switching to absolute x,y,z coordinates."
                    )
                    pos.IsRelative = False
                else:
                    self.Settings.current_debug_profile(
                    ).log_position_command_received(
                        "Received G90 - Already using absolute x,y,z coordinates."
                    )

                # for some firmwares we need to switch the extruder to
                # absolute
                # coordinates
                # as well
                if self.G90InfluencesExtruder:
                    if pos.IsExtruderRelative is None or pos.IsExtruderRelative:
                        self.Settings.current_debug_profile(
                        ).log_position_command_received(
                            "Received G90 - Switching to absolute extruder coordinates"
                        )
                        pos.IsExtruderRelative = False
                    else:
                        self.Settings.current_debug_profile(
                        ).log_position_command_received(
                            "Received G90 - Already using absolute extruder coordinates"
                        )
            elif cmd.Command == "G91":
                # change x,y,z to relative
                if pos.IsRelative is None or not pos.IsRelative:
                    self.Settings.current_debug_profile(
                    ).log_position_command_received(
                        "Received G91 - Switching to relative x,y,z coordinates"
                    )
                    pos.IsRelative = True
                else:
                    self.Settings.current_debug_profile(
                    ).log_position_command_received(
                        "Received G91 - Already using relative x,y,z coordinates"
                    )

                # for some firmwares we need to switch the extruder to
                # absolute
                # coordinates
                # as well
                if self.G90InfluencesExtruder:
                    if pos.IsExtruderRelative is None or not pos.IsExtruderRelative:
                        self.Settings.current_debug_profile(
                        ).log_position_command_received(
                            "Received G91 - Switching to relative extruder coordinates"
                        )
                        pos.IsExtruderRelative = True
                    else:
                        self.Settings.current_debug_profile(
                        ).log_position_command_received(
                            "Received G91 - Already using relative extruder coordinates"
                        )
            elif cmd.Command == "M83":
                # Extruder - Set Relative
                if pos.IsExtruderRelative is None or not pos.IsExtruderRelative:
                    self.Settings.current_debug_profile(
                    ).log_position_command_received(
                        "Received M83 - Switching Extruder to Relative Coordinates"
                    )
                    pos.IsExtruderRelative = True
            elif cmd.Command == "M82":
                # Extruder - Set Absolute
                if pos.IsExtruderRelative is None or pos.IsExtruderRelative:
                    self.Settings.current_debug_profile(
                    ).log_position_command_received(
                        "Received M82 - Switching Extruder to Absolute Coordinates"
                    )
                    pos.IsExtruderRelative = False
            elif cmd.Command == "G92":
                # Set Position (offset)
                if cmd.parse():
                    x = cmd.Parameters["X"].Value
                    y = cmd.Parameters["Y"].Value
                    z = cmd.Parameters["Z"].Value
                    e = cmd.Parameters["E"].Value
                    if x is None and y is None and z is None and e is None:
                        pos.XOffset = pos.X
                        pos.YOffset = pos.Y
                        pos.ZOffset = pos.Z
                        pos.EOffset = pos.E
                    # set the offsets if they are provided
                    if x is not None and pos.X is not None and pos.XHomed:
                        pos.XOffset = pos.X - utility.get_float(x, 0)
                    if y is not None and pos.Y is not None and pos.YHomed:
                        pos.YOffset = pos.Y - utility.get_float(y, 0)
                    if z is not None and pos.Z is not None and pos.ZHomed:
                        pos.ZOffset = pos.Z - utility.get_float(z, 0)
                    if e is not None and pos.E is not None:
                        pos.EOffset = pos.E - utility.get_float(e, 0)
                    self.Settings.current_debug_profile(
                    ).log_position_command_received(
                        "Received G92 - Set Position.  Command:{0}, XOffset:{1}, "
                        + "YOffset:{2}, ZOffset:{3}, EOffset:{4}".format(
                            gcode, pos.XOffset, pos.YOffset, pos.ZOffset,
                            pos.EOffset))
                else:
                    self.Settings.current_debug_profile().log_error(
                        "Position - Unable to parse the Gcode:{0}".format(
                            gcode))

        ########################################
        # Update the extruder monitor.
        self.Extruder.update(self.e_relative_pos(pos))

        ########################################
        # If we have a homed axis, detect changes.
        ########################################
        # for now we're going to ignore extruder changes and just run calculations every time.
        # has_extruder_changed = self.Extruder.HasChanged()

        pos.HasPositionChanged = not pos.is_position_equal(previous_pos, 0)
        pos.HasStateChanged = not pos.is_state_equal(previous_pos,
                                                     self.PrinterTolerance)

        if pos.has_homed_position() and previous_pos.has_homed_position():

            # if (hasExtruderChanged or pos.HasPositionChanged):
            if pos.HasPositionChanged:
                if self.HasRestrictedPosition:
                    _is_in_position, _intersections = self.calculate_path_intersections(
                        self.Snapshot.position_restrictions, pos.X, pos.Y,
                        previous_pos.X, previous_pos.Y)
                    if _is_in_position:
                        pos.IsInPosition = _is_in_position

                    else:
                        pos.IsInPosition = False
                        pos.InPathPosition = _intersections
                else:
                    pos.IsInPosition = True

            # calculate LastExtrusionHeight and Height
            if self.Extruder.is_extruding():
                pos.LastExtrusionHeight = pos.Z

                # see if we have primed yet
                if self.Printer.priming_height > 0:
                    if not pos.IsPrimed and pos.LastExtrusionHeight < self.Printer.priming_height:
                        pos.IsPrimed = True
                else:
                    # if we have no priming height set, just set IsPrimed = true.
                    pos.IsPrimed = True

                # make sure we are primed before calculating height/layers
                if pos.IsPrimed:
                    if pos.Height is None or utility.round_to(
                            pos.Z,
                            self.PrinterTolerance) > previous_pos.Height:
                        pos.Height = utility.round_to(pos.Z,
                                                      self.PrinterTolerance)
                        self.Settings.current_debug_profile(
                        ).log_position_height_change(
                            "Position - Reached New Height:{0}.".format(
                                pos.Height))

                    # calculate layer change
                    if (utility.round_to(self.z_delta(pos),
                                         self.PrinterTolerance) > 0
                            or pos.Layer == 0):
                        pos.IsLayerChange = True
                        pos.Layer += 1
                        self.Settings.current_debug_profile(
                        ).log_position_layer_change(
                            "Position - Layer:{0}.".format(pos.Layer))
                    else:
                        pos.IsLayerChange = False

            # Calculate ZHop based on last extrusion height
            if pos.LastExtrusionHeight is not None:
                # calculate lift, taking into account floating point
                # rounding
                lift = pos.distance_to_zlift(self.Printer.z_hop)

                # todo:  replace rounding with a call to is close or greater than utility function
                lift = utility.round_to(lift, self.PrinterTolerance)
                is_lifted = lift >= self.Printer.z_hop and (
                    not self.Extruder.is_extruding()
                    or self.Extruder.is_extruding_start())

                if is_lifted or self.Printer.z_hop == 0:
                    pos.IsZHop = True

            if pos.IsZHop and self.Printer.z_hop > 0:
                self.Settings.current_debug_profile().log_position_zhop(
                    "Position - Zhop:{0}".format(self.Printer.z_hop))

        self.Positions.appendleft(pos)

    def has_homed_position(self, index=0):
        if len(self.Positions) <= index:
            return None
        pos = self.Positions[index]
        return pos.has_homed_position()

    def has_homed_axes(self, index=0):
        if len(self.Positions) <= index:
            return None
        pos = self.Positions[index]
        return pos.has_homed_axes()

    def x_relative(self, index=0, x=None):

        if x:
            if len(self.Positions) <= index:
                return None
            pos = self.Positions[index]
            return x - pos.X + pos.XOffset

        else:
            if len(self.Positions) <= index + 1:
                return None
            pos = self.Positions[index]
            previous_pos = self.Positions[index + 1]
            return pos.X - previous_pos.X

    def y_relative(self, index=0, y=None):

        if y:
            if len(self.Positions) <= index:
                return None
            pos = self.Positions[index]
            return y - pos.Y + pos.YOffset

        else:
            if len(self.Positions) <= index + 1:
                return None
            pos = self.Positions[index]
            previous_pos = self.Positions[index + 1]
            return pos.Y - previous_pos.Y

    def z_relative(self, index=0, z=None):

        if z:
            if len(self.Positions) <= index:
                return None
            pos = self.Positions[index]
            return z - pos.Z + pos.ZOffset

        else:
            if len(self.Positions) <= index + 1:
                return None
            pos = self.Positions[index]
            previous_pos = self.Positions[index + 1]
            return pos.Z - previous_pos.Z

    def e_relative(self, index=0, e=None):

        if e:
            if len(self.Positions) <= index:
                return None
            pos = self.Positions[index]
            return e - pos.E + pos.EOffset

        else:
            if len(self.Positions) <= index + 1:
                return None
            pos = self.Positions[index]
            previous_pos = self.Positions[index + 1]
            return pos.E - previous_pos.E

    def e_relative_pos(self, pos):
        if len(self.Positions) < 1:
            return None
        previous_pos = self.Positions[0]
        return pos.E - previous_pos.E

    @staticmethod
    def is_at_position(x, y, z, pos, tolerance, apply_offsets):
        if apply_offsets:
            x = x + pos.XOffset
            y = y + pos.YOffset
            if z is not None:
                z = z + pos.ZOffset

        if ((pos.X is None or utility.is_close(pos.X, x, abs_tol=tolerance))
                and
            (pos.Y is None or utility.is_close(pos.Y, y, abs_tol=tolerance))
                and (z is None or pos.Z is None
                     or utility.is_close(pos.Z, z, abs_tol=tolerance))):
            return True
        return False

    def is_at_previous_position(self, x, y, z=None):
        if len(self.Positions) < 2:
            return False
        return self.is_at_position(
            x, y, z, self.Positions[1],
            self.Printer.printer_position_confirmation_tolerance, True)

    def is_at_current_position(self, x, y, z=None):
        if len(self.Positions) < 1:
            return False
        return self.is_at_position(
            x, y, z, self.Positions[0],
            self.Printer.printer_position_confirmation_tolerance, True)

    def get_position_string(self, index=0):
        if len(self.Positions) < 1:
            return get_formatted_coordinates(None, None, None, None)
        current_position = self.Positions[index]
        return get_formatted_coordinates(current_position.X,
                                         current_position.Y,
                                         current_position.Z,
                                         current_position.E)

    def calculate_path_intersections(self, restrictions, x, y, previous_x,
                                     previous_y):

        if self.calculate_is_in_position(
                restrictions, x, y,
                self.Printer.printer_position_confirmation_tolerance):
            return True, None

        if previous_x is None or previous_y is None:
            return False, False

        return False, self.calculate_in_position_intersection(
            restrictions, x, y, previous_x, previous_y,
            self.Printer.printer_position_confirmation_tolerance)

    @staticmethod
    def calculate_in_position_intersection(restrictions, x, y, previous_x,
                                           previous_y, tolerance):
        intersections = []
        for restriction in restrictions:
            cur_intersections = restriction.get_intersections(
                x, y, previous_x, previous_y)
            if cur_intersections:
                for cur_intersection in cur_intersections:
                    intersections.append(cur_intersection)

        if len(intersections) == 0:
            return False

        for intersection in intersections:
            if Position.calculate_is_in_position(restrictions, intersection[0],
                                                 intersection[1], tolerance):
                # calculate the distance from x/y previous to the intersection
                distance_to_intersection = math.sqrt(
                    math.pow(previous_x - intersection[0], 2) +
                    math.pow(previous_y - intersection[1], 2))
                # calculate the length of the lin x,y to previous_x, previous_y
                total_distance = math.sqrt(
                    math.pow(previous_x - x, 2) + math.pow(previous_y - y, 2))
                if total_distance > 0:
                    path_ratio_1 = distance_to_intersection / total_distance
                    path_ratio_2 = 1.0 - path_ratio_1
                else:
                    path_ratio_1 = 0
                    path_ratio_2 = 0

                return {
                    'intersection': intersection,
                    'path_ratio_1': path_ratio_1,
                    'path_ratio_2': path_ratio_2
                }
        return False

    @staticmethod
    def calculate_is_in_position(restrictions, x, y, tolerance):
        # we need to know if there is at least one required position
        has_required_position = False
        # isInPosition will be used to determine if we return
        # true where we have at least one required type
        in_position = False

        # loop through each restriction
        for restriction in restrictions:
            if restriction.Type == "required":
                # we have at least on required position, so at least one point must be in
                # position for us to return true
                has_required_position = True
            if restriction.is_in_position(x, y, tolerance):
                if restriction.Type == "forbidden":
                    # if we're in a forbidden position, return false now
                    return False
                else:
                    # we're in position in at least one required position restriction
                    in_position = True

        if has_required_position:
            # if at least one position restriction is required
            return in_position

        # if we're here then we only have forbidden restrictions, but the point
        # was not within the restricted area(s)
        return True
Example #6
0
class Position(object):
    def __init__(self, octolapseSettings, octoprintPrinterProfile,
                 g90InfluencesExtruder):
        self.Settings = octolapseSettings
        self.Printer = self.Settings.CurrentPrinter()
        self.OctoprintPrinterProfile = octoprintPrinterProfile
        self.Origin = {
            "X": self.Printer.origin_x,
            "Y": self.Printer.origin_y,
            "Z": self.Printer.origin_z
        }

        self.BoundingBox = utility.GetBoundingBox(self.Printer,
                                                  octoprintPrinterProfile)
        self.PrinterTolerance = self.Printer.printer_position_confirmation_tolerance
        self.Positions = []
        self.Reset()

        self.Extruder = Extruder(octolapseSettings)
        self.G90InfluencesExtruder = g90InfluencesExtruder

        if (self.Printer.z_hop is None):
            self.Printer.z_hop = 0

        self.Commands = Commands()

    def Reset(self):
        # todo:  This reset function doesn't seem to reset everything.
        self.Positions = []

        self.SavedPosition = None

    def UpdatePosition(self,
                       x=None,
                       y=None,
                       z=None,
                       e=None,
                       f=None,
                       force=False,
                       calculateChanges=False):
        numPositions = len(self.Positions)
        if (numPositions == 0):
            return
        pos = self.Positions[0]
        pos.UpdatePosition(self.BoundingBox, x, y, z, e, f, force)
        if (calculateChanges and numPositions > 1):
            previousPos = self.Positions[1]
            pos.HasPositionChanged = not pos.IsPositionEqual(
                previousPos, self.PrinterTolerance)
            pos.HasStateChanged = not pos.IsStateEqual(previousPos,
                                                       self.PrinterTolerance)

    def SavePosition(self,
                     x=None,
                     y=None,
                     z=None,
                     e=None,
                     f=None,
                     force=False):
        if (len(self.Positions) == 0):
            return
        self.SavedPosition = Pos(self.Positions[0])

    def ToDict(self):
        positionDict = None
        if (len(self.Positions) > 0):
            previousPos = self.Positions[0]
            return previousPos.ToDict()
        return None

    def ToPositionDict(self):
        positionDict = None
        if (len(self.Positions) > 0):
            previousPos = self.Positions[0]
            return previousPos.ToPositionDict()
        return None

    def ToStateDict(self):
        positionDict = None
        if (len(self.Positions) > 0):
            previousPos = self.Positions[0]
            return previousPos.ToStateDict()
        return None

    def ZDelta(self, pos):
        if (len(self.Positions) > 0):
            previousPos = self.Positions[0]
            # calculate ZDelta
            if (pos.Height is not None):
                if (previousPos.Height is None):
                    return pos.Height
                else:
                    return pos.Height - previousPos.Height
        return 0

    def HasStateChanged(self, index=0):
        if (len(self.Positions) > index):
            return self.Positions[index].HasStateChanged
        return None

    def HasPositionChanged(self, index=0):
        if (len(self.Positions) > index):
            return self.Positions[index].HasPositionChanged
        return None

    def HasPositionError(self, index=0):
        if (len(self.Positions) > index):
            return self.Positions[index].HasPositionError
        return None

    def DistanceToZLift(self, index=0):
        if (len(self.Positions) > index):
            pos = self.Positions[index]
            currentLift = utility.round_to(pos.Z - pos.Height,
                                           self.PrinterTolerance)
            if (currentLift < self.Printer.z_hop):
                return self.Printer.z_hop - currentLift
            return 0
        return None

    def PositionError(self, index=0):
        if (len(self.Positions) > index):
            return self.Positions[index].PositionError
        return None

    def X(self, index=0):
        if (len(self.Positions) > index):
            return self.Positions[index].X
        return None

    def Y(self, index=0):
        if (len(self.Positions) > index):
            return self.Positions[index].Y
        return None

    def Z(self, index=0):
        if (len(self.Positions) > index):
            return self.Positions[index].Z
        return None

    def E(self, index=0):
        if (len(self.Positions) > index):
            return self.Positions[index].E
        return None

    def F(self, index=0):
        if (len(self.Positions) > index):
            return self.Positions[index].F
        return None

    def IsZHop(self, index=0):
        if (len(self.Positions) > index):
            return self.Positions[index].IsZHop
        return None

    def IsLayerChange(self, index=0):
        if (len(self.Positions) > index):
            return self.Positions[index].IsLayerChange
        return None

    def Layer(self, index=0):
        if (len(self.Positions) > index):
            return self.Positions[index].Layer
        return None

    def Height(self, index=0):
        if (len(self.Positions) > index):
            return self.Positions[index].Height
        return None

    def IsRelative(self, index=0):
        if (len(self.Positions) > index):
            return self.Positions[index].IsRelative
        return None

    def IsExtruderRelative(self, index=0):
        if (len(self.Positions) > index):
            return self.Positions[index].IsExtruderRelative
        return None

    def HasReceivedHomeCommand(self, index=0):
        if (len(self.Positions) > index):
            return self.Positions[
                index].HasReceivedHomeCommand and self.HasHomedAxis(index)
        return False

    def UndoUpdate(self):
        if (len(self.Positions) > 0):
            del self.Positions[0]
        self.Extruder.UndoUpdate()

    def Update(self, gcode):
        command = self.Commands.GetCommand(gcode)
        # a new position

        pos = None
        previousPos = None
        numPositions = len(self.Positions)
        if (numPositions > 0):
            pos = Pos(self.OctoprintPrinterProfile, self.Positions[0])
            if (numPositions > 1):
                previousPos = Pos(self.OctoprintPrinterProfile,
                                  self.Positions[1])
        if (pos is None):
            pos = Pos(self.OctoprintPrinterProfile)
        if (previousPos is None):
            previousPos = Pos(self.OctoprintPrinterProfile)

        # reset the current position state (copied from the previous position, or a new position)
        pos.ResetState()
        # set the pos gcode command
        pos.GCode = gcode

        # apply the command to the position tracker
        if (command is not None):
            # I'm currently too lazy to keep this DRY
            # TODO:  Make DRY
            if (command.Command in ["G0", "G1"]):
                #Movement
                if (command.Parse()):
                    self.Settings.CurrentDebugProfile(
                    ).LogPositionCommandReceived("Received {0}".format(
                        command.Name))
                    x = command.Parameters["X"].Value
                    y = command.Parameters["Y"].Value
                    z = command.Parameters["Z"].Value
                    e = command.Parameters["E"].Value
                    f = command.Parameters["F"].Value

                    if (x is not None or y is not None or z is not None
                            or f is not None):

                        if (pos.HasPositionError and not pos.IsRelative):
                            pos.HasPositionError = False
                            pos.PositionError = ""
                        pos.UpdatePosition(self.BoundingBox,
                                           x,
                                           y,
                                           z,
                                           e=None,
                                           f=f)

                    if (e is not None):
                        if (pos.IsExtruderRelative is not None):
                            if (pos.HasPositionError
                                    and not pos.IsExtruderRelative):
                                pos.HasPositionError = False
                                pos.PositionError = ""
                            pos.UpdatePosition(self.BoundingBox,
                                               x=None,
                                               y=None,
                                               z=None,
                                               e=e,
                                               f=None)
                        else:
                            self.Settings.CurrentDebugProfile().LogError(
                                "Position - Unable to update the extruder position, no extruder coordinate system has been selected (absolute/relative)."
                            )
                    message = "Position Change - {0} - {1} Move From(X:{2},Y:{3},Z:{4},E:{5}) - To(X:{6},Y:{7},Z:{8},E:{9})"
                    if (previousPos is None):
                        message = message.format(
                            gcode,
                            "Relative" if pos.IsRelative else "Absolute",
                            "None", "None", "None", "None", pos.X, pos.Y,
                            pos.Z, pos.E)
                    else:
                        message = message.format(
                            gcode,
                            "Relative" if pos.IsRelative else "Absolute",
                            previousPos.X, previousPos.Y, previousPos.Z,
                            previousPos.E, pos.X, pos.Y, pos.Z, pos.E)
                    self.Settings.CurrentDebugProfile().LogPositionChange(
                        message)

                else:
                    self.Settings.CurrentDebugProfile().LogError(
                        "Position - Unable to parse the gcode command: {0}".
                        format(gcode))
            elif (command.Command == "G28"):
                # Home
                if (command.Parse()):
                    pos.HasReceivedHomeCommand = True
                    x = command.Parameters["X"].Value
                    y = command.Parameters["Y"].Value
                    z = command.Parameters["Z"].Value
                    xHomed = False
                    yHomed = False
                    zHomed = False
                    if (x is not None):
                        xHomed = True
                    if (y is not None):
                        yHomed = True
                    if (z is not None):
                        zHomed = True
                    if (x is None and y is None and z is None):
                        xHomed = True
                        yHomed = True
                        zHomed = True

                    homeStrings = []
                    if (xHomed):
                        pos.XHomed = True
                        pos.X = self.Origin[
                            "X"] if not self.Printer.auto_detect_origin else None
                        if (pos.X is None):
                            homeStrings.append("Homing X to Unknown Origin.")
                        else:
                            homeStrings.append("Homing X to {0}.".format(
                                GetFormattedCoordinate(pos.X)))
                    zHomedString = ""
                    if (yHomed):
                        pos.YHomed = True
                        pos.Y = self.Origin[
                            "Y"] if not self.Printer.auto_detect_origin else None
                        if (pos.Y is None):
                            homeStrings.append("Homing Y to Unknown Origin.")
                        else:
                            homeStrings.append("Homing Y to {0}.".format(
                                GetFormattedCoordinate(pos.Y)))
                    xHomedString = ""
                    if (zHomed):
                        pos.ZHomed = True
                        pos.Z = self.Origin[
                            "Z"] if not self.Printer.auto_detect_origin else None
                        if (pos.Z is None):
                            homeStrings.append("Homing Z to Unknown Origin.")
                        else:
                            homeStrings.append("Homing Z to {0}.".format(
                                GetFormattedCoordinate(pos.Z)))

                    self.Settings.CurrentDebugProfile(
                    ).LogPositionCommandReceived("Received G28 - ".format(
                        " ".join(homeStrings)))
                    pos.HasPositionError = False
                    pos.PositionError = None
                else:
                    self.Settings.CurrentDebugProfile().LogError(
                        "Position - Unable to parse the Gcode:{0}".format(
                            gcode))
            elif (command.Command == "G90"):
                # change x,y,z to absolute
                if (pos.IsRelative):
                    self.Settings.CurrentDebugProfile(
                    ).LogPositionCommandReceived(
                        "Received G90 - Switching to absolute x,y,z coordinates."
                    )
                    pos.IsRelative = False
                else:
                    self.Settings.CurrentDebugProfile(
                    ).LogPositionCommandReceived(
                        "Received G90 - Already using absolute x,y,z coordinates."
                    )

                # for some firmwares we need to switch the extruder to absolute coordinates as well
                if (self.G90InfluencesExtruder):
                    if (pos.IsExtruderRelative):
                        self.Settings.CurrentDebugProfile(
                        ).LogPositionCommandReceived(
                            "Received G90 - Switching to absolute extruder coordinates"
                        )
                        pos.IsExtruderRelative = False
                    else:
                        self.Settings.CurrentDebugProfile(
                        ).LogPositionCommandReceived(
                            "Received G90 - Already using absolute extruder coordinates"
                        )
            elif (command.Command == "G91"):
                # change x,y,z to relative
                if (not pos.IsRelative):
                    self.Settings.CurrentDebugProfile(
                    ).LogPositionCommandReceived(
                        "Received G91 - Switching to relative x,y,z coordinates"
                    )
                    pos.IsRelative = True
                else:
                    self.Settings.CurrentDebugProfile(
                    ).LogPositionCommandReceived(
                        "Received G91 - Already using relative x,y,z coordinates"
                    )

                # for some firmwares we need to switch the extruder to absolute coordinates as well
                if (self.G90InfluencesExtruder):
                    if (not pos.IsExtruderRelative):
                        self.Settings.CurrentDebugProfile(
                        ).LogPositionCommandReceived(
                            "Received G91 - Switching to relative extruder coordinates"
                        )
                        pos.IsExtruderRelative = True
                    else:
                        self.Settings.CurrentDebugProfile(
                        ).LogPositionCommandReceived(
                            "Received G91 - Already using relative extruder coordinates"
                        )
            elif (command.Command == "M83"):
                # Extruder - Set Relative
                if (pos.IsExtruderRelative is None
                        or not pos.IsExtruderRelative):
                    self.Settings.CurrentDebugProfile(
                    ).LogPositionCommandReceived(
                        "Received M83 - Switching Extruder to Relative Coordinates"
                    )
                    pos.IsExtruderRelative = True
            elif (command.Command == "M82"):
                # Extruder - Set Absolute
                if (pos.IsExtruderRelative is None or pos.IsExtruderRelative):
                    self.Settings.CurrentDebugProfile(
                    ).LogPositionCommandReceived(
                        "Received M82 - Switching Extruder to Absolute Coordinates"
                    )
                    pos.IsExtruderRelative = False
            elif (command.Command == "G92"):
                # Set Position (offset)
                if (command.Parse()):
                    previousRelativeValue = pos.IsRelative
                    previousExtruderRelativeValue = pos.IsExtruderRelative
                    x = command.Parameters["X"].Value
                    y = command.Parameters["Y"].Value
                    z = command.Parameters["Z"].Value
                    e = command.Parameters["E"].Value
                    resetAll = False
                    if (x is None and y is None and z is None and e is None):
                        pos.XOffset = pos.X
                        pos.YOffset = pos.Y
                        pos.ZOffset = pos.Z
                        pos.EOffset = pos.E
                    # set the offsets if they are provided
                    if (x is not None and pos.X is not None and pos.XHomed):
                        pos.XOffset = pos.X - utility.getfloat(x, 0)
                    if (y is not None and pos.Y is not None and pos.YHomed):
                        pos.YOffset = pos.Y - utility.getfloat(y, 0)
                    if (z is not None and pos.Z is not None and pos.ZHomed):
                        pos.ZOffset = pos.Z - utility.getfloat(z, 0)
                    if (e is not None and pos.E is not None):
                        pos.EOffset = pos.E - utility.getfloat(e, 0)
                    self.Settings.CurrentDebugProfile(
                    ).LogPositionCommandReceived(
                        "Received G92 - Set Position.  Command:{0}, XOffset:{1}, YOffset:{2}, ZOffset:{3}, EOffset:{4}"
                        .format(gcode, pos.XOffset, pos.YOffset, pos.ZOffset,
                                pos.EOffset))
                else:
                    self.Settings.CurrentDebugProfile().LogError(
                        "Position - Unable to parse the Gcode:{0}".format(
                            gcode))

            ########################################
            # Update the extruder monitor.
            self.Extruder.Update(self.ERelative(pos))
            ########################################
            # If we have a homed axis, detect changes.
            ########################################
            hasExtruderChanged = self.Extruder.HasChanged()
            pos.HasPositionChanged = not pos.IsPositionEqual(
                previousPos, self.PrinterTolerance)
            pos.HasStateChanged = not pos.IsStateEqual(previousPos,
                                                       self.PrinterTolerance)

            if (self.HasHomedPosition()):

                if (hasExtruderChanged or pos.HasPositionChanged):

                    # calculate LastExtrusionHeight and Height
                    if (self.Extruder.IsExtruding()):
                        pos.LastExtrusionHeight = pos.Z
                        if (pos.Height is None or
                                utility.round_to(pos.Z, self.PrinterTolerance)
                                > previousPos.Height):
                            pos.Height = utility.round_to(
                                pos.Z, self.PrinterTolerance)
                            self.Settings.CurrentDebugProfile(
                            ).LogPositionHeightChange(
                                "Position - Reached New Height:{0}.".format(
                                    pos.Height))

                        # calculate layer change
                        if (utility.round_to(self.ZDelta(pos),
                                             self.PrinterTolerance) > 0
                                or pos.Layer == 0):
                            pos.IsLayerChange = True
                            pos.Layer += 1
                            self.Settings.CurrentDebugProfile(
                            ).LogPositionLayerChange(
                                "Position - Layer:{0}.".format(pos.Layer))
                        else:
                            pos.IsLayerChange = False

                    # Calculate ZHop based on last extrusion height
                    if (pos.LastExtrusionHeight is not None):
                        # calculate lift, taking into account floating point rounding
                        lift = utility.round_to(
                            pos.Z - pos.LastExtrusionHeight,
                            self.PrinterTolerance)
                        if (lift >= self.Printer.z_hop):
                            lift = self.Printer.z_hop
                        isLifted = self.Printer.z_hop > 0.0 and lift >= self.Printer.z_hop and (
                            not self.Extruder.IsExtruding()
                            or self.Extruder.IsExtrudingStart())

                        if (isLifted):
                            pos.IsZHop = True

                    if (pos.IsZHop):
                        self.Settings.CurrentDebugProfile().LogPositionZHop(
                            "Position - Zhop:{0}".format(self.Printer.z_hop))

        # Add the current position, remove positions if we have more than 5 from the end
        self.Positions.insert(0, pos)
        while (len(self.Positions) > 5):
            del self.Positions[5]

    def HasHomedPosition(self, index=0):
        if (len(self.Positions) <= index):
            return None
        pos = self.Positions[index]
        return (self.HasHomedAxis(index) and pos.X is not None
                and pos.Y is not None and pos.Z is not None)

    def HasHomedAxis(self, index=0):
        if (len(self.Positions) <= index):
            return None
        pos = self.Positions[index]
        return (pos.XHomed and pos.YHomed and pos.ZHomed)

    def XRelative(self):
        if (len(self.Positions) < 2):
            return None
        pos = self.Positions[0]
        prevoiusPos = self.Positions[1]
        return pos.X - previousPos.X

    def YRelative(self):
        if (len(self.Positions) < 2):
            return None
        pos = self.Positions[0]
        prevoiusPos = self.Positions[1]
        return pos.Y - previousPos.Y

    def ZRelative(self):
        if (len(self.Positions) < 2):
            return None
        pos = self.Positions[0]
        prevoiusPos = self.Positions[1]
        return pos.Z - previousPos.Z

    def ERelative(self, pos):
        if (len(self.Positions) < 1):
            return None
        previousPos = self.Positions[0]
        return pos.E - previousPos.E

    def IsAtPosition(self, x, y, z, pos, tolerance, applyOffset):
        if (applyOffset):
            x = x + pos.XOffset
            y = y + pos.YOffset
            if (z is not None):
                z = z + pos.ZOffset

        if ((pos.X is None or utility.isclose(pos.X, x, abs_tol=tolerance)) and
            (pos.Y is None or utility.isclose(pos.Y, y, abs_tol=tolerance))
                and (z is None or pos.Z is None
                     or utility.isclose(pos.Z, z, abs_tol=tolerance))):
            return True
        return False

    def IsAtPreviousPosition(self, x, y, z=None, applyOffset=True):
        if (len(self.Positions) < 2):
            return False
        return self.IsAtPosition(
            x, y, z, self.Positions[1],
            self.Printer.printer_position_confirmation_tolerance, True)

    def IsAtCurrentPosition(self, x, y, z=None, applyOffset=True):
        if (len(self.Positions) < 1):
            return False
        return self.IsAtPosition(
            x, y, z, self.Positions[0],
            self.Printer.printer_position_confirmation_tolerance, True)

    def IsAtSavedPosition(self, x, y, z=None, applyOffset=True):
        if (self.SavedPosition is None):
            return False
        return self.IsAtPosition(
            x, y, z, self.SavedPosition,
            self.Printer.printer_position_confirmation_tolerance, True)
Example #7
0
 def setUp(self):
     self.Settings = OctolapseSettings("c:\\temp\\octolapse.log")
     self.Extruder = Extruder(self.Settings)
Example #8
0
 def setUp(self):
     self.Settings = OctolapseSettings(NamedTemporaryFile().name)
     self.Extruder = Extruder(self.Settings)
     # set the retraction distance
     self.Extruder.Printerretraction_length = 4
Example #9
0
class TestExtruder(unittest.TestCase):
    def setUp(self):
        self.Settings = OctolapseSettings(NamedTemporaryFile().name)
        self.Extruder = Extruder(self.Settings)
        # set the retraction distance
        self.Extruder.Printerretraction_length = 4

    @staticmethod
    def create_octoprint_printer_profile():
        return {
            "volume": {
                "custom_box": False,
                "width": 250,
                "depth": 200,
                "height": 200
            }
        }

    def test_ResetInitialState(self):
        """Test the initial extruder state, change all values, reset and check again"""
        # Check the initial state
        self.assertEquals(len(self.Extruder.StateHistory), 1)

        # add some states
        state1 = ExtruderState()
        self.Extruder.add_state(state1)

        state2 = ExtruderState()
        self.Extruder.add_state(state2)

        state3 = ExtruderState()
        self.Extruder.add_state(state3)

        # check the length of StateHistory
        self.assertEquals(len(self.Extruder.StateHistory), 4)

        # reset the state and check again
        self.Extruder.reset()
        self.assertEquals(len(self.Extruder.StateHistory), 0)

    def test_ExtruderState_InitialValues(self):
        # create a new state
        state = ExtruderState()

        # verify the initial values
        self.assertEquals(state.E, 0)
        self.assertEquals(state.extrusion_length, 0.0)
        self.assertEquals(state.extruder_length_total, 0.0)
        self.assertEquals(state.retraction_length, 0.0)
        self.assertEquals(state.deretraction_length, 0.0)
        self.assertFalse(state.is_extruding_start)
        self.assertFalse(state.is_extruding)
        self.assertFalse(state.is_primed)
        self.assertFalse(state.is_retracting_start)
        self.assertFalse(state.is_retracting)
        self.assertFalse(state.is_partially_retracted)
        self.assertFalse(state.is_retracted)
        self.assertFalse(state.is_deretracting_start)
        self.assertFalse(state.is_deretracting)
        self.assertFalse(state.is_deretracted)
        self.assertFalse(state.has_changed)

    def test_ExtruderStateCopy(self):
        # create a new state
        state = ExtruderState()
        # change all the default values

        state.E = 1
        state.extrusion_length = 100
        state.extruder_length_total = 200
        state.retraction_length = 300
        state.deretraction_length = 400
        state.is_extruding_start = True
        state.is_extruding = True
        state.is_primed = True
        state.is_retracting_start = True
        state.is_retracting = True
        state.is_partially_retracted = True
        state.is_retracted = True
        state.is_deretracting_start = True
        state.is_deretracting = True
        state.is_deretracted = True
        state.has_changed = True

        # copy to a new state
        new_state = ExtruderState(state)
        # verify the state was copied correctly
        self.assertEquals(new_state.E, 1)
        self.assertEquals(new_state.extrusion_length, 100)
        self.assertEquals(new_state.extruder_length_total, 200)
        self.assertEquals(new_state.retraction_length, 300)
        self.assertEquals(new_state.deretraction_length, 400)
        self.assertTrue(new_state.is_extruding_start)
        self.assertTrue(new_state.is_extruding)
        self.assertTrue(new_state.is_primed)
        self.assertTrue(new_state.is_retracting_start)
        self.assertTrue(new_state.is_retracting)
        self.assertTrue(new_state.is_partially_retracted)
        self.assertTrue(new_state.is_retracted)
        self.assertTrue(new_state.is_deretracting_start)
        self.assertTrue(new_state.is_deretracting)
        self.assertTrue(new_state.is_deretracted)
        self.assertTrue(new_state.has_changed)

    def test_has_changed(self):
        """Test the has_changed flag"""
        # test the initial state
        self.assertFalse(self.Extruder.has_changed())
        # test updating with no movement - Change to primed
        self.Extruder.update(0)
        self.assertTrue(self.Extruder.has_changed())

        # test updating with no movement
        self.Extruder.update(0)
        self.assertFalse(self.Extruder.has_changed())

        # test updating with movement
        self.Extruder.update(1)
        self.assertTrue(self.Extruder.has_changed())
        # test updating with no movement
        self.Extruder.update(0)
        self.assertTrue(self.Extruder.has_changed())
        # test updating with slight movement
        self.Extruder.update(.0001)
        self.assertTrue(self.Extruder.has_changed())
        # test updating with slight movement
        self.Extruder.update(.0001)
        self.assertTrue(self.Extruder.has_changed())
        # test updating with slight movement
        self.Extruder.update(.01)
        self.assertFalse(self.Extruder.has_changed())
        # test updating with slight movement
        self.Extruder.update(.01)
        self.assertFalse(self.Extruder.has_changed())
        # test updating with no movement
        self.Extruder.update(0)
        self.assertTrue(self.Extruder.has_changed())
        # test updating with no movement, is_primed changed from 0 to 1
        self.Extruder.update(0)
        self.assertFalse(self.Extruder.has_changed())
        # test updating with no movement
        self.Extruder.update(0)
        self.assertFalse(self.Extruder.has_changed())
        # test updating with slight negative movement
        self.Extruder.update(-0.01)
        self.assertTrue(self.Extruder.has_changed())

    def test_ExtruderStates_Initial(self):
        """Test the All Extruder States"""

        # test the initial state
        self.assertFalse(self.Extruder.is_extruding_start())
        self.assertFalse(self.Extruder.is_extruding())
        self.assertFalse(self.Extruder.is_primed())
        self.assertFalse(self.Extruder.is_retracting_start())
        self.assertFalse(self.Extruder.is_retracting())
        self.assertFalse(self.Extruder.is_partially_retracted())
        self.assertFalse(self.Extruder.is_retracted())
        self.assertFalse(self.Extruder.is_deretracting_start())
        self.assertFalse(self.Extruder.is_deretracting())
        self.assertFalse(self.Extruder.is_deretracted())

    def test_ExtruderStates_FromExtruding(self):
        # 1, 0 - From extruding to primed
        self.Extruder.reset()
        self.Extruder.update(1)
        self.Extruder.update(0)
        self.assertFalse(self.Extruder.is_extruding_start())
        self.assertFalse(self.Extruder.is_extruding())
        self.assertTrue(self.Extruder.is_primed())
        self.assertFalse(self.Extruder.is_retracting_start())
        self.assertFalse(self.Extruder.is_retracting())
        self.assertFalse(self.Extruder.is_partially_retracted())
        self.assertFalse(self.Extruder.is_retracted())
        self.assertFalse(self.Extruder.is_deretracting_start())
        self.assertFalse(self.Extruder.is_deretracting())
        self.assertFalse(self.Extruder.is_deretracted())
        # 1, 1 - keep extruding
        self.Extruder.reset()
        self.Extruder.update(1)
        self.Extruder.update(1)
        self.assertFalse(self.Extruder.is_extruding_start())
        self.assertTrue(self.Extruder.is_extruding())
        self.assertFalse(self.Extruder.is_primed())
        self.assertFalse(self.Extruder.is_retracting_start())
        self.assertFalse(self.Extruder.is_retracting())
        self.assertFalse(self.Extruder.is_partially_retracted())
        self.assertFalse(self.Extruder.is_retracted())
        self.assertFalse(self.Extruder.is_deretracting_start())
        self.assertFalse(self.Extruder.is_deretracting())
        self.assertFalse(self.Extruder.is_deretracted())
        # 1, -1 - from extruding to partially retracted
        self.Extruder.reset()
        self.Extruder.update(1)
        self.Extruder.update(-1)
        self.assertFalse(self.Extruder.is_extruding_start())
        self.assertFalse(self.Extruder.is_extruding())
        self.assertFalse(self.Extruder.is_primed())
        self.assertTrue(self.Extruder.is_retracting_start())
        self.assertTrue(self.Extruder.is_retracting())
        self.assertTrue(self.Extruder.is_partially_retracted())
        self.assertFalse(self.Extruder.is_retracted())
        self.assertFalse(self.Extruder.is_deretracting_start())
        self.assertFalse(self.Extruder.is_deretracting())
        self.assertFalse(self.Extruder.is_deretracted())

        # 1, -4 - from extruding to fully retracted
        self.Extruder.reset()
        self.Extruder.update(1)
        self.Extruder.update(-4)
        self.assertFalse(self.Extruder.is_extruding_start())
        self.assertFalse(self.Extruder.is_extruding())
        self.assertFalse(self.Extruder.is_primed())
        self.assertTrue(self.Extruder.is_retracting_start())
        self.assertTrue(self.Extruder.is_retracting())
        self.assertFalse(self.Extruder.is_partially_retracted())
        self.assertTrue(self.Extruder.is_retracted())
        self.assertFalse(self.Extruder.is_deretracting_start())
        self.assertFalse(self.Extruder.is_deretracting())
        self.assertFalse(self.Extruder.is_deretracted())

        # 1, -5 - from extruding to beyond fully retracted
        self.Extruder.reset()
        self.Extruder.update(1)
        self.Extruder.update(-5)
        self.assertFalse(self.Extruder.is_extruding_start())
        self.assertFalse(self.Extruder.is_extruding())
        self.assertFalse(self.Extruder.is_primed())
        self.assertTrue(self.Extruder.is_retracting_start())
        self.assertTrue(self.Extruder.is_retracting())
        self.assertFalse(self.Extruder.is_partially_retracted())
        self.assertTrue(self.Extruder.is_retracted())
        self.assertFalse(self.Extruder.is_deretracting_start())
        self.assertFalse(self.Extruder.is_deretracting())
        self.assertFalse(self.Extruder.is_deretracted())

    def test_ExtruderStates_FromPrimed(self):
        # 0, 0 - remain primed
        self.Extruder.reset()
        self.Extruder.update(0)
        self.Extruder.update(0)
        self.assertFalse(self.Extruder.is_extruding_start())
        self.assertFalse(self.Extruder.is_extruding())
        self.assertTrue(self.Extruder.is_primed())
        self.assertFalse(self.Extruder.is_retracting_start())
        self.assertFalse(self.Extruder.is_retracting())
        self.assertFalse(self.Extruder.is_partially_retracted())
        self.assertFalse(self.Extruder.is_retracted())
        self.assertFalse(self.Extruder.is_deretracting_start())
        self.assertFalse(self.Extruder.is_deretracting())
        self.assertFalse(self.Extruder.is_deretracted())

        # 0, 1 - from primed to extruding
        self.Extruder.reset()
        self.Extruder.update(0)
        self.Extruder.update(1)
        self.assertTrue(self.Extruder.is_extruding_start())
        self.assertTrue(self.Extruder.is_extruding())
        self.assertFalse(self.Extruder.is_primed())
        self.assertFalse(self.Extruder.is_retracting_start())
        self.assertFalse(self.Extruder.is_retracting())
        self.assertFalse(self.Extruder.is_partially_retracted())
        self.assertFalse(self.Extruder.is_retracted())
        self.assertFalse(self.Extruder.is_deretracting_start())
        self.assertFalse(self.Extruder.is_deretracting())
        self.assertFalse(self.Extruder.is_deretracted())

        # 0, -1 - from primed to partially retracted
        self.Extruder.reset()
        self.Extruder.update(0)
        self.Extruder.update(-1)
        self.assertFalse(self.Extruder.is_extruding_start())
        self.assertFalse(self.Extruder.is_extruding())
        self.assertFalse(self.Extruder.is_primed())
        self.assertTrue(self.Extruder.is_retracting_start())
        self.assertTrue(self.Extruder.is_retracting())
        self.assertTrue(self.Extruder.is_partially_retracted())
        self.assertFalse(self.Extruder.is_retracted())
        self.assertFalse(self.Extruder.is_deretracting_start())
        self.assertFalse(self.Extruder.is_deretracting())
        self.assertFalse(self.Extruder.is_deretracted())

        # 0, -4 - from primed to fully retracted
        self.Extruder.reset()
        self.Extruder.update(0)
        self.Extruder.update(-4)
        self.assertFalse(self.Extruder.is_extruding_start())
        self.assertFalse(self.Extruder.is_extruding())
        self.assertFalse(self.Extruder.is_primed())
        self.assertTrue(self.Extruder.is_retracting_start())
        self.assertTrue(self.Extruder.is_retracting())
        self.assertFalse(self.Extruder.is_partially_retracted())
        self.assertTrue(self.Extruder.is_retracted())
        self.assertFalse(self.Extruder.is_deretracting_start())
        self.assertFalse(self.Extruder.is_deretracting())
        self.assertFalse(self.Extruder.is_deretracted())

        # 0, -5 - from primed to beyond fully retracted
        self.Extruder.reset()
        self.Extruder.update(0)
        self.Extruder.update(-5)
        self.assertFalse(self.Extruder.is_extruding_start())
        self.assertFalse(self.Extruder.is_extruding())
        self.assertFalse(self.Extruder.is_primed())
        self.assertTrue(self.Extruder.is_retracting_start())
        self.assertTrue(self.Extruder.is_retracting())
        self.assertFalse(self.Extruder.is_partially_retracted())
        self.assertTrue(self.Extruder.is_retracted())
        self.assertFalse(self.Extruder.is_deretracting_start())
        self.assertFalse(self.Extruder.is_deretracting())
        self.assertFalse(self.Extruder.is_deretracted())

    def test_ExtruderStates_FromPartiallyRetracted(self):
        # -2,3 - from partially retracted to extruding
        self.Extruder.reset()
        self.Extruder.update(-2)
        self.Extruder.update(3)
        self.assertTrue(self.Extruder.is_extruding_start())
        self.assertTrue(self.Extruder.is_extruding())
        self.assertFalse(self.Extruder.is_primed())
        self.assertFalse(self.Extruder.is_retracting_start())
        self.assertFalse(self.Extruder.is_retracting())
        self.assertFalse(self.Extruder.is_partially_retracted())
        self.assertFalse(self.Extruder.is_retracted())
        self.assertTrue(self.Extruder.is_deretracting_start())
        self.assertTrue(self.Extruder.is_deretracting())
        self.assertTrue(self.Extruder.is_deretracted())
        # -2,2 - deretract
        self.Extruder.reset()
        self.Extruder.update(-2)
        self.Extruder.update(2)
        self.assertFalse(self.Extruder.is_extruding_start())
        self.assertFalse(self.Extruder.is_extruding())
        self.assertTrue(self.Extruder.is_primed())
        self.assertFalse(self.Extruder.is_retracting_start())
        self.assertFalse(self.Extruder.is_retracting())
        self.assertFalse(self.Extruder.is_partially_retracted())
        self.assertFalse(self.Extruder.is_retracted())
        self.assertTrue(self.Extruder.is_deretracting_start())
        self.assertTrue(self.Extruder.is_deretracting())
        self.assertTrue(self.Extruder.is_deretracted())
        # -2,1 - deretract from partially retracted to partially retracted
        self.Extruder.reset()
        self.Extruder.update(-2)
        self.Extruder.update(1)
        self.assertFalse(self.Extruder.is_extruding_start())
        self.assertFalse(self.Extruder.is_extruding())
        self.assertFalse(self.Extruder.is_primed())
        self.assertFalse(self.Extruder.is_retracting_start())
        self.assertFalse(self.Extruder.is_retracting())
        self.assertTrue(self.Extruder.is_partially_retracted())
        self.assertFalse(self.Extruder.is_retracted())
        self.assertTrue(self.Extruder.is_deretracting_start())
        self.assertTrue(self.Extruder.is_deretracting())
        self.assertFalse(self.Extruder.is_deretracted())

        # -2, 0 - remain partially retracted
        self.Extruder.reset()
        self.Extruder.update(-2)
        self.Extruder.update(0)
        self.assertFalse(self.Extruder.is_extruding_start())
        self.assertFalse(self.Extruder.is_extruding())
        self.assertFalse(self.Extruder.is_primed())
        self.assertFalse(self.Extruder.is_retracting_start())
        self.assertFalse(self.Extruder.is_retracting())
        self.assertTrue(self.Extruder.is_partially_retracted())
        self.assertFalse(self.Extruder.is_retracted())
        self.assertFalse(self.Extruder.is_deretracting_start())
        self.assertFalse(self.Extruder.is_deretracting())
        self.assertFalse(self.Extruder.is_deretracted())

        # -2,-1 - retract from partially retracted to partially retracted
        self.Extruder.reset()
        self.Extruder.update(-2)
        self.Extruder.update(-1)
        self.assertFalse(self.Extruder.is_extruding_start())
        self.assertFalse(self.Extruder.is_extruding())
        self.assertFalse(self.Extruder.is_primed())
        self.assertFalse(self.Extruder.is_retracting_start())
        self.assertTrue(self.Extruder.is_retracting())
        self.assertTrue(self.Extruder.is_partially_retracted())
        self.assertFalse(self.Extruder.is_retracted())
        self.assertFalse(self.Extruder.is_deretracting_start())
        self.assertFalse(self.Extruder.is_deretracting())
        self.assertFalse(self.Extruder.is_deretracted())

        # -2,-2 - from partially retracted to fully retracted
        self.Extruder.reset()
        self.Extruder.update(-2)
        self.Extruder.update(-2)
        self.assertFalse(self.Extruder.is_extruding_start())
        self.assertFalse(self.Extruder.is_extruding())
        self.assertFalse(self.Extruder.is_primed())
        self.assertFalse(self.Extruder.is_retracting_start())
        self.assertTrue(self.Extruder.is_retracting())
        self.assertFalse(self.Extruder.is_partially_retracted())
        self.assertTrue(self.Extruder.is_retracted())
        self.assertFalse(self.Extruder.is_deretracting_start())
        self.assertFalse(self.Extruder.is_deretracting())
        self.assertFalse(self.Extruder.is_deretracted())

        # -2,-3 - from partially retracted to beyond partially retracted
        self.Extruder.reset()
        self.Extruder.update(-2)
        self.Extruder.update(-3)
        self.assertFalse(self.Extruder.is_extruding_start())
        self.assertFalse(self.Extruder.is_extruding())
        self.assertFalse(self.Extruder.is_primed())
        self.assertFalse(self.Extruder.is_retracting_start())
        self.assertTrue(self.Extruder.is_retracting())
        self.assertFalse(self.Extruder.is_partially_retracted())
        self.assertTrue(self.Extruder.is_retracted())
        self.assertFalse(self.Extruder.is_deretracting_start())
        self.assertFalse(self.Extruder.is_deretracting())
        self.assertFalse(self.Extruder.is_deretracted())

    def test_ExtruderStates_FromFullyRetracted(self):
        # -4,5 - Fully Retracted To Extruding
        self.Extruder.reset()
        self.Extruder.update(-4)
        self.Extruder.update(5)
        self.assertTrue(self.Extruder.is_extruding_start())
        self.assertTrue(self.Extruder.is_extruding())
        self.assertFalse(self.Extruder.is_primed())
        self.assertFalse(self.Extruder.is_retracting_start())
        self.assertFalse(self.Extruder.is_retracting())
        self.assertFalse(self.Extruder.is_partially_retracted())
        self.assertFalse(self.Extruder.is_retracted())
        self.assertTrue(self.Extruder.is_deretracting_start())
        self.assertTrue(self.Extruder.is_deretracting())
        self.assertTrue(self.Extruder.is_deretracted())

        # -4,4 - Fully Retracted to Primed/Deretracted
        self.Extruder.reset()
        self.Extruder.update(-4)
        self.Extruder.update(4)
        self.assertFalse(self.Extruder.is_extruding_start())
        self.assertFalse(self.Extruder.is_extruding())
        self.assertTrue(self.Extruder.is_primed())
        self.assertFalse(self.Extruder.is_retracting_start())
        self.assertFalse(self.Extruder.is_retracting())
        self.assertFalse(self.Extruder.is_partially_retracted())
        self.assertFalse(self.Extruder.is_retracted())
        self.assertTrue(self.Extruder.is_deretracting_start())
        self.assertTrue(self.Extruder.is_deretracting())
        self.assertTrue(self.Extruder.is_deretracted())

        # -4,1 - Fully Retracted to Partially Retracted
        self.Extruder.reset()
        self.Extruder.update(-4)
        self.Extruder.update(1)
        self.assertFalse(self.Extruder.is_extruding_start())
        self.assertFalse(self.Extruder.is_extruding())
        self.assertFalse(self.Extruder.is_primed())
        self.assertFalse(self.Extruder.is_retracting_start())
        self.assertFalse(self.Extruder.is_retracting())
        self.assertTrue(self.Extruder.is_partially_retracted())
        self.assertFalse(self.Extruder.is_retracted())
        self.assertTrue(self.Extruder.is_deretracting_start())
        self.assertTrue(self.Extruder.is_deretracting())
        self.assertFalse(self.Extruder.is_deretracted())

        # -4,0 - Remain Fully Retracted
        self.Extruder.reset()
        self.Extruder.update(-4)
        self.Extruder.update(0)
        self.assertFalse(self.Extruder.is_extruding_start())
        self.assertFalse(self.Extruder.is_extruding())
        self.assertFalse(self.Extruder.is_primed())
        self.assertFalse(self.Extruder.is_retracting_start())
        self.assertFalse(self.Extruder.is_retracting())
        self.assertFalse(self.Extruder.is_partially_retracted())
        self.assertTrue(self.Extruder.is_retracted())
        self.assertFalse(self.Extruder.is_deretracting_start())
        self.assertFalse(self.Extruder.is_deretracting())
        self.assertFalse(self.Extruder.is_deretracted())

        # -4,-1 - Fully Retracted, Continue Retracting
        self.Extruder.reset()
        self.Extruder.update(-4)
        self.Extruder.update(-1)
        self.assertFalse(self.Extruder.is_extruding_start())
        self.assertFalse(self.Extruder.is_extruding())
        self.assertFalse(self.Extruder.is_primed())
        self.assertFalse(self.Extruder.is_retracting_start())
        self.assertTrue(self.Extruder.is_retracting())
        self.assertFalse(self.Extruder.is_partially_retracted())
        self.assertTrue(self.Extruder.is_retracted())
        self.assertFalse(self.Extruder.is_deretracting_start())
        self.assertFalse(self.Extruder.is_deretracting())
        self.assertFalse(self.Extruder.is_deretracted())

    def test_ExtruderStates_FromBeyondFullyRetracted(self):
        # -5,6 - Beyond fully retracted to extruding
        self.Extruder.reset()
        self.Extruder.update(-5)
        self.Extruder.update(6)
        self.assertTrue(self.Extruder.is_extruding_start())
        self.assertTrue(self.Extruder.is_extruding())
        self.assertFalse(self.Extruder.is_primed())
        self.assertFalse(self.Extruder.is_retracting_start())
        self.assertFalse(self.Extruder.is_retracting())
        self.assertFalse(self.Extruder.is_partially_retracted())
        self.assertFalse(self.Extruder.is_retracted())
        self.assertTrue(self.Extruder.is_deretracting_start())
        self.assertTrue(self.Extruder.is_deretracting())
        self.assertTrue(self.Extruder.is_deretracted())

        # -5,5 - Beyond fully retracted to primed/deretracted
        self.Extruder.reset()
        self.Extruder.update(-5)
        self.Extruder.update(5)
        self.assertFalse(self.Extruder.is_extruding_start())
        self.assertFalse(self.Extruder.is_extruding())
        self.assertTrue(self.Extruder.is_primed())
        self.assertFalse(self.Extruder.is_retracting_start())
        self.assertFalse(self.Extruder.is_retracting())
        self.assertFalse(self.Extruder.is_partially_retracted())
        self.assertFalse(self.Extruder.is_retracted())
        self.assertTrue(self.Extruder.is_deretracting_start())
        self.assertTrue(self.Extruder.is_deretracting())
        self.assertTrue(self.Extruder.is_deretracted())

        # -5,4 - Beyond fully retracted to partially retracted
        self.Extruder.reset()
        self.Extruder.update(-5)
        self.Extruder.update(4)
        self.assertFalse(self.Extruder.is_extruding_start())
        self.assertFalse(self.Extruder.is_extruding())
        self.assertFalse(self.Extruder.is_primed())
        self.assertFalse(self.Extruder.is_retracting_start())
        self.assertFalse(self.Extruder.is_retracting())
        self.assertTrue(self.Extruder.is_partially_retracted())
        self.assertFalse(self.Extruder.is_retracted())
        self.assertTrue(self.Extruder.is_deretracting_start())
        self.assertTrue(self.Extruder.is_deretracting())
        self.assertFalse(self.Extruder.is_deretracted())

        # -5,1 - Beyond fully retracted to fully retracted
        self.Extruder.reset()
        self.Extruder.update(-5)
        self.Extruder.update(1)
        self.assertFalse(self.Extruder.is_extruding_start())
        self.assertFalse(self.Extruder.is_extruding())
        self.assertFalse(self.Extruder.is_primed())
        self.assertFalse(self.Extruder.is_retracting_start())
        self.assertFalse(self.Extruder.is_retracting())
        self.assertFalse(self.Extruder.is_partially_retracted())
        self.assertTrue(self.Extruder.is_retracted())
        self.assertTrue(self.Extruder.is_deretracting_start())
        self.assertTrue(self.Extruder.is_deretracting())
        self.assertFalse(self.Extruder.is_deretracted())

        # -5,0 - Remain beyond fully retracted
        self.Extruder.reset()
        self.Extruder.update(-5)
        self.Extruder.update(0)
        self.assertFalse(self.Extruder.is_extruding_start())
        self.assertFalse(self.Extruder.is_extruding())
        self.assertFalse(self.Extruder.is_primed())
        self.assertFalse(self.Extruder.is_retracting_start())
        self.assertFalse(self.Extruder.is_retracting())
        self.assertFalse(self.Extruder.is_partially_retracted())
        self.assertTrue(self.Extruder.is_retracted())
        self.assertFalse(self.Extruder.is_deretracting_start())
        self.assertFalse(self.Extruder.is_deretracting())
        self.assertFalse(self.Extruder.is_deretracted())

        # -5, -1 - Beyond fully retracted, continuing to retract
        self.Extruder.reset()
        self.Extruder.update(-5)
        self.Extruder.update(-1)
        self.assertFalse(self.Extruder.is_extruding_start())
        self.assertFalse(self.Extruder.is_extruding())
        self.assertFalse(self.Extruder.is_primed())
        self.assertFalse(self.Extruder.is_retracting_start())
        self.assertTrue(self.Extruder.is_retracting())
        self.assertFalse(self.Extruder.is_partially_retracted())
        self.assertTrue(self.Extruder.is_retracted())
        self.assertFalse(self.Extruder.is_deretracting_start())
        self.assertFalse(self.Extruder.is_deretracting())
        self.assertFalse(self.Extruder.is_deretracted())

    def test_ExtruderStateTriggered(self):
        self.assertTrue(
            self.Extruder._extruder_state_triggered(None, False) is None)
        self.assertTrue(
            self.Extruder._extruder_state_triggered(None, True) is None)
        self.assertTrue(
            self.Extruder._extruder_state_triggered(True, False) is None)
        self.assertTrue(self.Extruder._extruder_state_triggered(True, True))
        self.assertTrue(
            self.Extruder._extruder_state_triggered(False, False) is None)
        self.assertFalse(self.Extruder._extruder_state_triggered(False, True))

    def test_extruderTriggers_NoFilter(self):
        """Test the extruder triggers"""

        # test with no filters - should trigger
        self.Extruder.reset()
        state = ExtruderState()
        self.Extruder.add_state(state)
        state.is_primed = False  # turn this off so we don't have to account for this default state
        triggers = ExtruderTriggers(None, None, None, None, None, None, None,
                                    None, None, None)
        self.assertTrue(self.Extruder.is_triggered(triggers))

    def test_extruderTriggers_on_extruding_start(self):
        self.Extruder.reset()
        state = ExtruderState()
        self.Extruder.add_state(state)
        # test on_extruding_start - True Filter
        triggers = ExtruderTriggers(True, None, None, None, None, None, None,
                                    None, None, None)
        # test True with true filter
        state.is_extruding_start = True
        state.is_primed = False  # turn this off so we don't have to account for this default state
        self.assertTrue(self.Extruder.is_triggered(triggers))
        # test False with True filter
        state.is_extruding_start = False
        self.assertTrue(not self.Extruder.is_triggered(triggers))
        # test on_extruding_start - False Filter
        triggers = ExtruderTriggers(False, None, None, None, None, None, None,
                                    None, None, None)
        # test True with False filter
        state.is_extruding_start = True
        self.assertTrue(not self.Extruder.is_triggered(triggers))
        # test False with False filter
        state.is_extruding_start = False
        self.assertTrue(not self.Extruder.is_triggered(triggers))

    def test_extruderTriggers_on_extruding(self):
        # test onExtruding
        self.Extruder.reset()
        state = ExtruderState()
        self.Extruder.add_state(state)
        triggers = ExtruderTriggers(None, True, None, None, None, None, None,
                                    None, None, None)
        # test True with true filter
        state.is_extruding = True
        state.is_primed = False  # turn this off so we don't have to account for this default state
        self.assertTrue(self.Extruder.is_triggered(triggers))
        # test False with True filter
        state.is_extruding = False
        self.assertFalse(self.Extruder.is_triggered(triggers))
        # test on_extruding - False Filter
        triggers = ExtruderTriggers(None, False, None, None, None, None, None,
                                    None, None, None)
        # test True with False filter
        state.is_extruding = True
        self.assertFalse(self.Extruder.is_triggered(triggers))
        # test False with False filter
        state.is_extruding = False
        self.assertFalse(self.Extruder.is_triggered(triggers))

    def test_extruderTriggers_on_primed(self):
        self.Extruder.reset()
        state = ExtruderState()
        self.Extruder.add_state(state)
        triggers = ExtruderTriggers(None, None, True, None, None, None, None,
                                    None, None, None)
        # test True with true filter
        state.is_primed = True
        self.assertTrue(self.Extruder.is_triggered(triggers))
        # test False with True filter
        state.is_primed = False
        self.assertTrue(not self.Extruder.is_triggered(triggers))
        # test on_primed - False Filter
        triggers = ExtruderTriggers(None, None, False, None, None, None, None,
                                    None, None, None)
        # test True with False filter
        state.is_primed = True
        self.assertTrue(not self.Extruder.is_triggered(triggers))
        # test False with False filter
        state.is_primed = False
        self.assertTrue(not self.Extruder.is_triggered(triggers))

    def test_extruderTriggers_on_retracting_start(self):
        # test on_retracting_start
        self.Extruder.reset()
        state = ExtruderState()
        self.Extruder.add_state(state)
        triggers = ExtruderTriggers(None, None, None, True, None, None, None,
                                    None, None, None)
        # test True with true filter
        state.is_retracting_start = True
        state.is_primed = False  # turn this off so we don't have to account for this default state
        self.assertTrue(self.Extruder.is_triggered(triggers))
        # test False with True filter
        state.is_retracting_start = False
        self.assertTrue(not self.Extruder.is_triggered(triggers))
        # test on_retracting_start - False Filter
        triggers = ExtruderTriggers(None, None, None, False, None, None, None,
                                    None, None, None)
        # test True with False filter
        state.is_retracting_start = True
        self.assertTrue(not self.Extruder.is_triggered(triggers))
        # test False with False filter
        state.is_retracting_start = False
        self.assertTrue(not self.Extruder.is_triggered(triggers))

    def test_extruderTriggers_on_retracting(self):
        # test on_retracting
        self.Extruder.reset()
        state = ExtruderState()
        self.Extruder.add_state(state)
        triggers = ExtruderTriggers(None, None, None, None, True, None, None,
                                    None, None, None)
        # test True with true filter
        state.is_retracting = True
        state.is_primed = False  # turn this off so we don't have to account for this default state
        self.assertTrue(self.Extruder.is_triggered(triggers))
        # test False with True filter
        state.is_retracting = False
        self.assertTrue(not self.Extruder.is_triggered(triggers))
        # test on_retracting - False Filter
        triggers = ExtruderTriggers(None, None, None, None, False, None, None,
                                    None, None, None)
        # test True with False filter
        state.is_retracting = True
        self.assertTrue(not self.Extruder.is_triggered(triggers))
        # test False with False filter
        state.is_retracting = False
        self.assertTrue(not self.Extruder.is_triggered(triggers))

    def test_extruderTriggers_on_partially_retracted(self):
        # test on_partially_retracted
        self.Extruder.reset()
        state = ExtruderState()
        self.Extruder.add_state(state)
        triggers = ExtruderTriggers(None, None, None, None, None, True, None,
                                    None, None, None)
        # test True with true filter
        state.is_partially_retracted = True
        state.is_primed = False  # turn this off so we don't have to account for this default state
        self.assertTrue(self.Extruder.is_triggered(triggers))
        # test False with True filter
        state.is_partially_retracted = False
        self.assertTrue(not self.Extruder.is_triggered(triggers))
        # test on_partially_retracted - False Filter
        triggers = ExtruderTriggers(None, None, None, None, None, False, None,
                                    None, None, None)
        # test True with False filter
        state.is_partially_retracted = True
        self.assertTrue(not self.Extruder.is_triggered(triggers))
        # test False with False filter
        state.is_partially_retracted = False
        self.assertTrue(not self.Extruder.is_triggered(triggers))

    def test_extruderTriggers_on_retracted(self):
        # test on_retracted
        self.Extruder.reset()
        state = ExtruderState()
        self.Extruder.add_state(state)
        triggers = ExtruderTriggers(None, None, None, None, None, None, True,
                                    None, None, None)
        # test True with true filter
        state.is_retracted = True
        state.is_primed = False  # turn this off so we don't have to account for this default state
        self.assertTrue(self.Extruder.is_triggered(triggers))
        # test False with True filter
        state.is_retracted = False
        self.assertTrue(not self.Extruder.is_triggered(triggers))
        # test on_retracted - False Filter
        triggers = ExtruderTriggers(None, None, None, None, None, None, False,
                                    None, None, None)
        # test True with False filter
        state.is_retracted = True
        self.assertTrue(not self.Extruder.is_triggered(triggers))
        # test False with False filter
        state.is_retracted = False
        self.assertTrue(not self.Extruder.is_triggered(triggers))

    def test_extruderTriggers_on_deretracting_start(self):
        # test on_deretracting_start
        self.Extruder.reset()
        state = ExtruderState()
        self.Extruder.add_state(state)
        triggers = ExtruderTriggers(None, None, None, None, None, None, None,
                                    True, None, None)
        # test True with true filter
        state.is_deretracting_start = True
        state.is_primed = False  # turn this off so we don't have to account for this default state
        self.assertTrue(self.Extruder.is_triggered(triggers))
        # test False with True filter
        state.is_deretracting_start = False
        self.assertTrue(not self.Extruder.is_triggered(triggers))
        # test on_deretracting_start - False Filter
        triggers = ExtruderTriggers(None, None, None, None, None, None, None,
                                    False, None, None)
        # test True with False filter
        state.is_deretracting_start = True
        self.assertTrue(not self.Extruder.is_triggered(triggers))
        # test False with False filter
        state.is_deretracting_start = False
        self.assertTrue(not self.Extruder.is_triggered(triggers))

    def test_extruderTriggers_on_deretracting(self):
        # test on_deretracting
        self.Extruder.reset()
        state = ExtruderState()
        self.Extruder.add_state(state)
        triggers = ExtruderTriggers(None, None, None, None, None, None, None,
                                    None, True, None)
        # test True with true filter
        state.is_deretracting = True
        state.is_primed = False  # turn this off so we don't have to account for this default state
        self.assertTrue(self.Extruder.is_triggered(triggers))
        # test False with True filter
        state.is_deretracting = False
        self.assertTrue(not self.Extruder.is_triggered(triggers))
        # test on_deretracting - False Filter
        triggers = ExtruderTriggers(None, None, None, None, None, None, None,
                                    None, False, None)
        # test True with False filter
        state.is_deretracting = True
        self.assertTrue(not self.Extruder.is_triggered(triggers))
        # test False with False filter
        state.is_deretracting = False
        self.assertTrue(not self.Extruder.is_triggered(triggers))

    def test_extruderTriggers_on_deretracted(self):
        # test on_deretracted
        self.Extruder.reset()
        state = ExtruderState()
        self.Extruder.add_state(state)
        triggers = ExtruderTriggers(None, None, None, None, None, None, None,
                                    None, None, True)
        # test True with true filter
        state.is_deretracted = True
        state.is_primed = False  # turn this off so we don't have to account for this default state
        self.assertTrue(self.Extruder.is_triggered(triggers))
        # test False with True filter
        state.is_deretracted = False
        self.assertTrue(not self.Extruder.is_triggered(triggers))
        # test on_deretracted - False Filter
        triggers = ExtruderTriggers(None, None, None, None, None, None, None,
                                    None, None, False)
        # test True with False filter
        state.is_deretracted = True
        self.assertTrue(not self.Extruder.is_triggered(triggers))
        # test False with False filter
        state.is_deretracted = False
        self.assertTrue(not self.Extruder.is_triggered(triggers))

    def test_extruderTriggers_Mixed(self):
        # Test mixed nones, trues and falses
        self.Extruder.reset()
        state = ExtruderState()
        self.Extruder.add_state(state)
        triggers = ExtruderTriggers(None, True, False, None, True, False, None,
                                    True, False, None)
        # Forbidden Due to is_primed
        state.is_extruding_start = True
        state.is_extruding = True
        state.is_primed = True
        state.is_retracting_start = True
        state.is_retracting = True
        state.is_partially_retracted = False
        state.is_retracted = True
        state.is_deretracting_start = True
        state.is_deretracting = False
        state.is_deretracted = True
        self.assertTrue(not self.Extruder.is_triggered(triggers))
        # True - is extruding
        state.is_extruding_start = False
        state.is_extruding = True
        state.is_primed = False
        state.is_retracting_start = True
        state.is_retracting = False
        state.is_partially_retracted = False
        state.is_retracted = True
        state.is_deretracting_start = False
        state.is_deretracting = False
        state.is_deretracted = True
        self.assertTrue(self.Extruder.is_triggered(triggers))

        # Test all false states and all Nones
        state.is_extruding_start = True
        state.is_extruding = True
        state.is_primed = True
        state.is_retracting_start = True
        state.is_retracting = True
        state.is_partially_retracted = True
        state.is_retracted = True
        state.is_deretracting_start = True
        state.is_deretracting = True
        state.is_deretracted = True
        triggers = ExtruderTriggers(None, None, None, None, None, None, None,
                                    None, None, None)
        self.assertTrue(self.Extruder.is_triggered(triggers))
        triggers = ExtruderTriggers(False, True, True, True, True, True, True,
                                    True, True, True)
        self.assertFalse(self.Extruder.is_triggered(triggers))
        triggers = ExtruderTriggers(True, False, True, True, True, True, True,
                                    True, True, True)
        self.assertFalse(self.Extruder.is_triggered(triggers))
        triggers = ExtruderTriggers(True, True, False, True, True, True, True,
                                    True, True, True)
        self.assertFalse(self.Extruder.is_triggered(triggers))
        triggers = ExtruderTriggers(True, True, True, False, True, True, True,
                                    True, True, True)
        self.assertFalse(self.Extruder.is_triggered(triggers))
        triggers = ExtruderTriggers(True, True, True, True, False, True, True,
                                    True, True, True)
        self.assertFalse(self.Extruder.is_triggered(triggers))
        triggers = ExtruderTriggers(True, True, True, True, True, False, True,
                                    True, True, True)
        self.assertFalse(self.Extruder.is_triggered(triggers))
        triggers = ExtruderTriggers(True, True, True, True, True, True, False,
                                    True, True, True)
        self.assertFalse(self.Extruder.is_triggered(triggers))
        triggers = ExtruderTriggers(True, True, True, True, True, True, True,
                                    False, True, True)
        self.assertFalse(self.Extruder.is_triggered(triggers))
        triggers = ExtruderTriggers(True, True, True, True, True, True, True,
                                    True, False, True)
        self.assertFalse(self.Extruder.is_triggered(triggers))
        triggers = ExtruderTriggers(True, True, True, True, True, True, True,
                                    True, True, False)
        self.assertFalse(self.Extruder.is_triggered(triggers))
Example #10
0
 def setUp(self):
     self.Settings = OctolapseSettings(NamedTemporaryFile().name)
     self.Extruder = Extruder(self.Settings)
Example #11
0
class Position(object):
    def __init__(self, octolapseSettings, octoprintPrinterProfile,
                 g90InfluencesExtruder):
        self.Settings = octolapseSettings
        self.Printer = self.Settings.CurrentPrinter()
        self.OctoprintPrinterProfile = octoprintPrinterProfile
        self.PrinterTolerance = self.Printer.printer_position_confirmation_tolerance
        self.Positions = []
        self.Reset()

        self.Extruder = Extruder(octolapseSettings)
        self.G90InfluencesExtruder = g90InfluencesExtruder

        if (self.Printer.z_hop is None):
            self.Printer.z_hop = 0

        self.Commands = Commands()

    def Reset(self):

        self.Positions = []
        self.HasPositionError = False
        self.PositionError = None
        self.IsLayerChange = False
        self.HasPositionChanged = False
        self.Layer = 0
        self.Height = 0
        self.SavedPosition = None

    def UpdatePosition(self,
                       x=None,
                       y=None,
                       z=None,
                       e=None,
                       f=None,
                       force=False):
        if (len(self.Positions) == 0):
            return
        pos = self.Positions[0]
        pos.UpdatePosition(x, y, z, e, f, force)

    def SavePosition(self,
                     x=None,
                     y=None,
                     z=None,
                     e=None,
                     f=None,
                     force=False):
        if (len(self.Positions) == 0):
            return
        self.SavedPosition = Pos(self.Positions[0])

    def ZDelta(self, pos):
        if (len(self.Positions) > 0):
            previousPos = self.Positions[0]
            # calculate ZDelta
            if (pos.Height is not None):
                if (previousPos.Height is None):
                    return pos.Height
                else:
                    return pos.Height - previousPos.Height
        return 0

    def X(self):
        if (len(self.Positions) > 0):
            return self.Positions[0].X
        return None

    def Y(self):
        if (len(self.Positions) > 0):
            return self.Positions[0].Y
        return None

    def Z(self):
        if (len(self.Positions) > 0):
            return self.Positions[0].Z
        return None

    def E(self):
        if (len(self.Positions) > 0):
            return self.Positions[0].E
        return None

    def F(self):
        if (len(self.Positions) > 0):
            return self.Positions[0].F
        return None

    def IsZHop(self):
        if (len(self.Positions) > 0):
            return self.Positions[0].IsZHop
        return None

    def IsRelative(self):
        if (len(self.Positions) > 0):
            return self.Positions[0].IsRelative
        return None

    def IsExtruderRelative(self):
        if (len(self.Positions) > 0):
            return self.Positions[0].IsExtruderRelative
        return None

    def UndoUpdate(self):
        if (len(self.Positions) > 0):
            del self.Positions[0]
        self.Extruder.UndoUpdate()

    def Update(self, gcode):
        # reset state variables
        self.IsLayerChange = False
        self.HasPositionChanged = False

        command = self.Commands.GetCommand(gcode)
        # a new position

        pos = None
        previousPos = None
        numPositions = len(self.Positions)
        if (numPositions > 0):
            pos = Pos(self.OctoprintPrinterProfile, self.Positions[0])
            if (numPositions > 1):
                previousPos = Pos(self.OctoprintPrinterProfile,
                                  self.Positions[1])
        if (pos is None):
            pos = Pos(self.OctoprintPrinterProfile)
        if (previousPos is None):
            previousPos = Pos(self.OctoprintPrinterProfile)

        # Movement detected, set the previous values
        # disect the gcode and use it to update our position

        if (pos.IsZHopStart):
            pos.IsZHop = True

        elif (pos.IsZHopCompleting):
            pos.IsZHop = False
            pos.IsZHopCompleting = False

        pos.IsZHopStart = False

        # apply the command to the position tracker
        if (command is not None):
            if (command.Command in ["G0", "G1"]):
                #Movement
                if (command.Parse()):
                    self.Settings.CurrentDebugProfile(
                    ).LogPositionCommandReceived("Received {0}".format(
                        command.Name))
                    x = command.Parameters["X"].Value
                    y = command.Parameters["Y"].Value
                    z = command.Parameters["Z"].Value
                    e = command.Parameters["E"].Value
                    f = command.Parameters["F"].Value

                    if (x is not None or y is not None or z is not None
                            or f is not None):

                        if (self.HasPositionError and not pos.IsRelative):
                            self.HasPositionError = False
                            self.PositionError = ""
                        pos.UpdatePosition(x, y, z, e=None, f=f)

                    if (e is not None):
                        if (pos.IsExtruderRelative is not None):
                            if (self.HasPositionError
                                    and not pos.IsExtruderRelative):
                                self.HasPositionError = False
                                self.PositionError = ""
                            pos.UpdatePosition(x=None,
                                               y=None,
                                               z=None,
                                               e=e,
                                               f=None)
                        else:
                            self.Settings.CurrentDebugProfile().LogError(
                                "Position - Unable to update the extruder position, no extruder coordinate system has been selected (absolute/relative)."
                            )
                    message = "Position Change - {0} - {1} Move From(X:{2},Y:{3},Z:{4},E:{5}) - To(X:{6},Y:{7},Z:{8},E:{9})"
                    if (previousPos is None):
                        message = message.format(
                            gcode,
                            "Relative" if pos.IsRelative else "Absolute",
                            "None", "None", "None", "None", pos.X, pos.Y,
                            pos.Z, pos.E)
                    else:
                        message = message.format(
                            gcode,
                            "Relative" if pos.IsRelative else "Absolute",
                            previousPos.X, previousPos.Y, previousPos.Z,
                            previousPos.E, pos.X, pos.Y, pos.Z, pos.E)
                    self.Settings.CurrentDebugProfile().LogPositionChange(
                        message)

                else:
                    self.Settings.CurrentDebugProfile().LogError(
                        "Position - Unable to parse the gcode command: {0}".
                        format(gcode))

                # If we've not yet homed the axis

                #########################################################
            elif (command.Command == "G28"):
                # test homing of only X,Y or Z

                if (command.Parse()):
                    x = command.Parameters["X"].Value
                    y = command.Parameters["Y"].Value
                    z = command.Parameters["Z"].Value
                    if (x is not None):
                        pos.XHomed = True
                    if (y is not None):
                        pos.YHomed = True
                    if (z is not None):
                        pos.ZHomed = True
                    if (x is None and y is None and z is None):
                        pos.XHomed = True
                        pos.YHomed = True
                        pos.ZHomed = True

                    self.Settings.CurrentDebugProfile(
                    ).LogPositionCommandReceived(
                        "Received G28 - Homing to {0}".format(
                            GetFormattedCoordinates(x, y, z, pos.E)))
                    self.HasPositionError = False
                    self.PositionError = None
                else:
                    self.Settings.CurrentDebugProfile().LogError(
                        "Position - Unable to parse the Gcode:{0}".format(
                            gcode))
            elif (command.Command == "G90"):
                # change x,y,z to absolute
                if (pos.IsRelative):
                    self.Settings.CurrentDebugProfile(
                    ).LogPositionCommandReceived(
                        "Received G90 - Switching to absolute x,y,z coordinates."
                    )
                    pos.IsRelative = False
                else:
                    self.Settings.CurrentDebugProfile(
                    ).LogPositionCommandReceived(
                        "Received G90 - Already using absolute x,y,z coordinates."
                    )

                # for some firmwares we need to switch the extruder to absolute coordinates as well
                if (self.G90InfluencesExtruder):
                    if (pos.IsExtruderRelative):
                        self.Settings.CurrentDebugProfile(
                        ).LogPositionCommandReceived(
                            "Received G90 - Switching to absolute extruder coordinates"
                        )
                        pos.IsExtruderRelative = False
                    else:
                        self.Settings.CurrentDebugProfile(
                        ).LogPositionCommandReceived(
                            "Received G90 - Already using absolute extruder coordinates"
                        )
            elif (command.Command == "G91"):
                # change x,y,z to relative
                if (not pos.IsRelative):
                    self.Settings.CurrentDebugProfile(
                    ).LogPositionCommandReceived(
                        "Received G91 - Switching to relative x,y,z coordinates"
                    )
                    pos.IsRelative = True
                else:
                    self.Settings.CurrentDebugProfile(
                    ).LogPositionCommandReceived(
                        "Received G91 - Already using relative x,y,z coordinates"
                    )

                # for some firmwares we need to switch the extruder to absolute coordinates as well
                if (self.G90InfluencesExtruder):
                    if (not pos.IsExtruderRelative):
                        self.Settings.CurrentDebugProfile(
                        ).LogPositionCommandReceived(
                            "Received G91 - Switching to relative extruder coordinates"
                        )
                        pos.IsExtruderRelative = True
                    else:
                        self.Settings.CurrentDebugProfile(
                        ).LogPositionCommandReceived(
                            "Received G91 - Already using relative extruder coordinates"
                        )
            elif (command.Command == "M83"):
                if (pos.IsExtruderRelative is None
                        or not pos.IsExtruderRelative):
                    self.Settings.CurrentDebugProfile(
                    ).LogPositionCommandReceived(
                        "Received M83 - Switching Extruder to Relative Coordinates"
                    )
                    pos.IsExtruderRelative = True
            elif (command.Command == "M82"):

                if (pos.IsExtruderRelative is None or pos.IsExtruderRelative):
                    self.Settings.CurrentDebugProfile(
                    ).LogPositionCommandReceived(
                        "Received M82 - Switching Extruder to Absolute Coordinates"
                    )
                    pos.IsExtruderRelative = False
            elif (command.Command == "G92"):
                if (command.Parse()):
                    previousRelativeValue = self.IsRelative
                    previousExtruderRelativeValue = self.IsExtruderRelative
                    x = command.Parameters["X"].Value
                    y = command.Parameters["Y"].Value
                    z = command.Parameters["Z"].Value
                    e = command.Parameters["E"].Value
                    resetAll = False
                    if (x is None and y is None and z is None and e is None):
                        pos.XOffset = pos.X
                        pos.YOffset = pos.Y
                        pos.ZOffset = pos.Z
                        pos.EOffset = pos.E
                    # set the offsets if they are provided
                    if (x is not None and pos.X is not None and pos.XHomed):
                        pos.XOffset = pos.X - utility.getfloat(x, 0)
                    if (y is not None and pos.Y is not None and pos.YHomed):
                        pos.YOffset = pos.Y - utility.getfloat(y, 0)
                    if (z is not None and pos.Z is not None and pos.ZHomed):
                        pos.ZOffset = pos.Z - utility.getfloat(z, 0)
                    if (e is not None and pos.E is not None):
                        pos.EOffset = pos.E - utility.getfloat(e, 0)
                    self.Settings.CurrentDebugProfile(
                    ).LogPositionCommandReceived(
                        "Received G92 - Set Position.  Command:{0}, XOffset:{1}, YOffset:{2}, ZOffset:{3}, EOffset:{4}"
                        .format(gcode, pos.XOffset, pos.YOffset, pos.ZOffset,
                                pos.EOffset))
                else:
                    self.Settings.CurrentDebugProfile().LogError(
                        "Position - Unable to parse the Gcode:{0}".format(
                            gcode))

            #########################################################
            # Update the extruder monitor if there was movement
            self.Extruder.Update(self.ERelative(pos))

            if (self.HasHomedAxis()):
                hasExtruderChanged = (utility.round_to(
                    self.ERelative(pos), self.PrinterTolerance) != 0)
                hasXYZChanged = (
                    utility.round_to(pos.X, self.PrinterTolerance) !=
                    utility.round_to(previousPos.X, self.PrinterTolerance)
                    or utility.round_to(pos.Y, self.PrinterTolerance) !=
                    utility.round_to(previousPos.Y, self.PrinterTolerance)
                    or utility.round_to(pos.Z, self.PrinterTolerance) !=
                    utility.round_to(previousPos.Z, self.PrinterTolerance))

                if (hasExtruderChanged or hasXYZChanged):
                    self.HasPositionChanged = True

                    # calculate LastExtrusionHeight and Height
                    if (self.Extruder.IsExtruding()
                            or self.Extruder.IsExtrudingStart()):
                        pos.LastExtrusionHeight = pos.Z
                        if (pos.Height is None or utility.round_to(
                                pos.Z, self.PrinterTolerance) > self.Height):
                            self.Height = utility.round_to(
                                pos.Z, self.PrinterTolerance)
                            pos.Height = self.Height
                            self.Settings.CurrentDebugProfile(
                            ).LogPositionHeightChange(
                                "Position - Reached New Height:{0}.".format(
                                    pos.Height))

                    # calculate layer change
                    if (utility.round_to(self.ZDelta(pos),
                                         self.PrinterTolerance) > 0
                            or self.Layer == 0):
                        self.IsLayerChange = True
                        self.Layer += 1
                        self.Settings.CurrentDebugProfile(
                        ).LogPositionLayerChange(
                            "Position - Layer:{0}.".format(self.Layer))
                    else:
                        self.IsLayerChange = False

                    # Calculate ZHop based on last extrusion height
                    if (pos.LastExtrusionHeight is not None):
                        # calculate lift, taking into account floating point rounding
                        lift = utility.round_to(
                            pos.Z - pos.LastExtrusionHeight,
                            self.PrinterTolerance)
                        if (lift >= self.Printer.z_hop):
                            lift = self.Printer.z_hop
                        isLifted = self.Printer.z_hop > 0.0 and lift >= self.Printer.z_hop and (
                            not self.Extruder.IsExtruding()
                            or self.Extruder.IsExtrudingStart())

                        if (isLifted):
                            if (not pos.IsZHop):
                                pos.IsZHopStart = True
                        else:
                            if (pos.IsZHop):
                                pos.IsZHopCompleting = True

                    if (pos.IsZHopStart):
                        self.Settings.CurrentDebugProfile().LogPositionZHop(
                            "Position - ZhopStart:{0}".format(
                                self.Printer.z_hop))
                    if (pos.IsZHop):
                        self.Settings.CurrentDebugProfile().LogPositionZHop(
                            "Position - Zhop:{0}".format(self.Printer.z_hop))
                    if (pos.IsZHopCompleting):
                        self.Settings.CurrentDebugProfile().LogPositionZHop(
                            "Position - IsZHopCompleting:{0}".format(
                                self.Printer.z_hop))

        # Add the current position, remove positions if we have more than 5 from the end
        self.Positions.insert(0, pos)
        while (len(self.Positions) > 5):
            del self.Positions[5]

    def HasHomedAxis(self):
        if (len(self.Positions) < 2):
            return False
        pos = self.Positions[0]
        previousPos = self.Positions[1]

        return (pos.XHomed and pos.YHomed and pos.ZHomed and pos.X is not None
                and pos.Y is not None and pos.Z is not None
                and previousPos.X is not None and previousPos.Y is not None
                and previousPos.Z is not None)

    def XRelative(self):
        if (len(self.Positions) < 2):
            return None
        pos = self.Positions[0]
        prevoiusPos = self.Positions[1]
        return pos.X - previousPos.X

    def YRelative(self):
        if (len(self.Positions) < 2):
            return None
        pos = self.Positions[0]
        prevoiusPos = self.Positions[1]
        return pos.Y - previousPos.Y

    def ZRelative(self):
        if (len(self.Positions) < 2):
            return None
        pos = self.Positions[0]
        prevoiusPos = self.Positions[1]
        return pos.Z - previousPos.Z

    def ERelative(self, pos):
        if (len(self.Positions) < 1):
            return None
        previousPos = self.Positions[0]
        return pos.E - previousPos.E

    def IsAtPosition(self, x, y, z, pos, tolerance, applyOffset):
        if (applyOffset):
            x = x + pos.XOffset
            y = y + pos.YOffset
            if (z is not None):
                z = z + pos.ZOffset

        if ((pos.X is None or utility.isclose(pos.X, x, abs_tol=tolerance)) and
            (pos.Y is None or utility.isclose(pos.Y, y, abs_tol=tolerance))
                and (z is None or pos.Z is None
                     or utility.isclose(pos.Z, z, abs_tol=tolerance))):
            return True
        return False

    def IsAtPreviousPosition(self, x, y, z=None, applyOffset=True):
        if (len(self.Positions) < 2):
            return False
        return self.IsAtPosition(
            x, y, z, self.Positions[1],
            self.Printer.printer_position_confirmation_tolerance, True)

    def IsAtCurrentPosition(self, x, y, z=None, applyOffset=True):
        if (len(self.Positions) < 1):
            return False
        return self.IsAtPosition(
            x, y, z, self.Positions[0],
            self.Printer.printer_position_confirmation_tolerance, True)

    def IsAtSavedPosition(self, x, y, z=None, applyOffset=True):
        if (self.SavedPosition is None):
            return False
        return self.IsAtPosition(
            x, y, z, self.SavedPosition,
            self.Printer.printer_position_confirmation_tolerance, True)