Esempio n. 1
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
Esempio n. 2
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))