Module pipettin-piper.piper.gcode_primitives

Classes

class GcodePrimitives (machine_limits: dict = None,
main_feedrate: float = 60000,
config=None,
before_callback=None,
after_callback=None)
Expand source code
class GcodePrimitives(MethodIntercept):
    def __init__(self,
                 machine_limits: dict = None,
                 main_feedrate: float = 60*1000,  # mm/min
                 config=None,
                 before_callback = None,
                 after_callback = None):

        self.gconfig = {}
        if config is not None:
            self.gconfig = config.get("gcode", {})

        self.machine_limits = self.gconfig.get("machine_limits", {})
        if machine_limits is not None:
            self.machine_limits.update(machine_limits)

        # Default feedrate (F parameter for G1) in mm/min, for XYZ moves.
        # Equivalences: 60000 mm/min = 1000 * 60 mm/min  = 1000 mm/s = 1 m/s
        self.feedrate = self.gconfig.get("main_feedrate_mm_min", main_feedrate)

        self.set_limits(self.machine_limits)

        self.tc_select_gcode = self.gconfig.get("tc_select_gcode", "T0")

        # Register callbacks.
        if before_callback is not None:
            self.before = before_callback
        if after_callback is not None:
            self.after = after_callback

        # Ignore some attributes.
        super().__init__(cb_ignore=["set_limits", "comment"])

    # These limits are meant to be overriden
    min_x: float = 0
    max_x: float = 99999999
    min_y: float = 0
    max_y: float = 99999999
    min_z: float = 0
    max_z: float = 99999999

    def gcode_set_origin(self):
        """Set the current position as the XYZ origin."""
        return ["SET_KINEMATIC_POSITION X=0 Y=0 Z=0"]

    def set_limits(self, machine_limits: dict):
        """Apply the provided limits.
        See 'make_limits' for input definition."""
        self.min_x = machine_limits.get("min_x", self.min_x)
        self.max_x = machine_limits.get("max_x", self.max_x)
        self.min_y = machine_limits.get("min_y", self.min_y)
        self.max_y = machine_limits.get("max_y", self.max_y)
        self.min_z = machine_limits.get("min_z", self.min_z)
        self.max_z = machine_limits.get("max_z", self.max_z)

    @staticmethod
    def make_limits(min_x, max_x, min_y, max_y, min_z, max_z) -> dict:
        """Generate the machine limits dictionary."""
        machine_limits = {
            "min_x": min_x,
            "max_x": max_x,
            "min_y": min_y,
            "max_y": max_y,
            "min_z": min_z,
            "max_z": max_z
        }
        return machine_limits

    # GCODE generator methods ####

    def comment(self, message: str, prefix=" ; ", suffix = ""):
        if message:
            return prefix + str(message) + suffix
        else:
            return ""

    def gcodeHuman(self, text, human_prefix = "PAUSE"):
        human_command = [human_prefix + self.comment(text)]
        return human_command

    def gcodeDwell(self, seconds, wait_prefix = "G4 P", comment_text=""):
        """Dwell for the provided number of seconds"""
        wait_command = [wait_prefix + str(seconds*1000) + self.comment(comment_text)]
        return wait_command

    def gcodeWait(self, comment_text=""):
        """Wait for current moves to finish"""
        wait_command = ["M400" + self.comment(comment_text)]
        return wait_command

    def G1(self,
           x: float = None,
           y: float = None,
           z: float = None,
           e: float = None,
           f: float = None,
           absolute=None,
           absolute_e=None,
           add_comment=""):
        """G1 move command
        Accepts a feedrate parameter in mm/min units.
        """

        commands = self.gcodeMove(
            x=x, y=y, z=z, e=e, f=f,
            _mode="G1",
            absolute=absolute,
            absolute_e=absolute_e,
            add_comment=add_comment)

        return commands

    def G0(self,
           x: float = None,
           y: float = None,
           z: float = None,
           e: float = None,
           absolute=None,
           absolute_e=None,
           add_comment=""):
        """Fast move
        G0 uses the default (fast) feedrate.
        """

        commands = self.gcodeMove(
            x=x, y=y, z=z, e=e, f=self.feedrate,
            _mode="G0",
            absolute=absolute,
            absolute_e=absolute_e,
            add_comment=add_comment)

        return commands

    def gcodeClearance(self, z: float = None, f: float = None, add_comment=""):
        """Uses a klipper macro to skip moving to clearance if the toolhead is already clear."""
        # Make command.
        clearance_command = "CLEARANCE"
        if z is not None:
            clearance_command += f" Z={z}"
        if f is not None:
            clearance_command += f" F={f}"
        # Add the comment if requested.
        if add_comment:
            clearance_command += self.comment(add_comment)
        # Return a list, as expected.
        return [clearance_command]

    def gcodeMove(self,
                  x: float = None,
                  y: float = None,
                  z: float = None,
                  e: float = None,
                  f: float = None,
                  _mode="G1",
                  absolute=None,
                  absolute_e=None,
                  add_comment="") -> list:  # True for absolute (G90), False for relative (G91).
        """
        gcode to move robot to xyz and extruder to e, by pasting values of xyzsef if specified.
        @param absolute: Use G90/G91 for XYZ move (abs/rel).
        TODO: this should not affect E moves, M82/M83 check pending!
        """

        if (x is None) and (y is None) and (z is None) and (e is None):
            msg = "No coordinate values specified for GCODE move command."
            logging.error(msg)
            raise ValueError(msg)

        # Coord parsing
        command_pars = []
        if x is not None:
            command_pars.append(f"X{x}")
        if y is not None:
            command_pars.append(f"Y{y}")
        if z is not None:
            command_pars.append(f"Z{z}")
        if e is not None:
            command_pars.append(f"E{e}")
        if f is not None:
            command_pars.append(f"F{f}")

        # Construct GCODE command as a string.
        str_command = ""
        if command_pars:
            # Build move command.
            str_command += _mode + " " + " ".join(command_pars)
        if add_comment:
            # Add the comment if requested.
            str_command += self.comment(add_comment)

        # Motion coord mode command.
        coord_mode = []
        if absolute is not None:
            coord_mode = self.gcodeCoordMode(absolute)
        if absolute_e is not None:
            coord_mode += self.gcodeCoordModeExtruder(absolute_e)
        # Motion coordinates command.
        move_command = []
        if str_command != "":
            move_command = [str_command]

        # Prepend coord mode to gcode if any.
        final_commands: list = coord_mode + move_command
        return final_commands

    def gcodeCoordMode(self, absolute=True, add_comment=""):
        # Motion coord mode
        if absolute:
            coord_mode = "G90" + self.comment(add_comment)
        else:
            coord_mode = "G91" + self.comment(add_comment)
        return [coord_mode]

    def gcodeCoordModeExtruder(self, absolute=True, add_comment=""):
        # Motion coord mode
        if absolute:
            coord_mode = "M82" + self.comment(add_comment)
        else:
            coord_mode = "M83" + self.comment(add_comment)
        return [coord_mode]

    def gcodeHomeXYZ(self, axes:str=None):
        """Homes all XYZ axes, unless X, Y, and/or Z are selected in 'axes'.
        Then sets absolute XYZ coordinates (G90), and relative E coordinates (M83).
        """

        # Default GRBL homing command
        home_command = "G28"

        # Append single axis specifier if required
        if axes:
            if axes.upper().find("X") >= 0:
                home_command += " X"
            if axes.upper().find("Y") >= 0:
                home_command += " Y"
            if axes.upper().find("Z") >= 0:
                home_command += " Z"

        return [home_command + self.comment("Homing command"),
                "G90" + self.comment("Absolute XYZ motion"),
                "M83" + self.comment("Relative E motion")]

    def gcodeHomeP(self, which_axis="all", retraction_mm=None, cmd_prefix="H_T_", cmd_all="ALL", cmd_suffix=""):
        """
        Make homing moves for tools, using custom Klipper macro commands.
        TODO: This is largely deprecated.
        """

        # Homing command prefix
        home_command = cmd_prefix

        if which_axis == "all":
            # A macro named "H_ALL" must be configured, which homes all tools in Klipper.
            home_command += cmd_all
        else:
            # Macros named "H_P200" (and similar) must be configured in Klipper.
            home_command += which_axis

        # Add the suffix
        home_command += cmd_suffix

        # Add homing GCODE command.
        home_commands = [home_command + self.comment(f"Home {which_axis} tool(s)")]

        # Check for retract move
        if retraction_mm is not None:
            # Ensure relative extruder motion
            home_commands += self.gcodeCoordMode(
                absolute=False, add_comment="Tool homing retraction.")
            home_commands += self.gcodeCoordModeExtruder(
                absolute=False, add_comment="Tool homing retraction.")
            home_commands += self.gcodeMove(
                e=retraction_mm,
                add_comment="Tool homing retraction."
            )

        # For testing
        # home_command = ["SET_ORIGIN X=0 Y=0 Z=0 E=0"]

        return home_commands

    def gcodeG92(self, x: float = None,
                y: float = None,
                z: float = None):
        """Returns G92 setup command"""
        command = ["G92"]  # Default is G90 G0,

        if x is None and y is None and z is None:
            print("gcodeG92 warning: no values specified.")
            return [self.comment("gcodeG92 warning: no values specified in gcodeG92")]

        if x is not None:
            command.append("X" + str(x))

        if y is not None:
            command.append("Y" + str(y))

        if z is not None:
            command.append("Z" + str(z))

        return [" ".join(command)]

    def probe_move(self, x=None, y=None, z=None, f=None,
                probe_towards=True, error_on_failure=False,
                command_prefix="G38"):
        """
        Example:

        gcode.probe_move(1,2,3)
        'G38.4 X1 Y2 Z3'

        For reference:
            - "G38.2" probe until probe is ON, error if probe does not activate during probing.
            - "G38.3" probe until probe is ON, without error.

        From LinuxCNC: https://linuxcnc.org/docs/2.6/html/gcode/gcode.html
            - G38.2 - (True/True) probe toward workpiece, stop on contact, signal error if failure.
            - G38.3 - (True/False) probe toward workpiece, stop on contact.
            - G38.4 - (False/True) probe away from workpiece, stop on loss of contact, signal error if failure.
            - G38.5 - (False/False) probe away from workpiece, stop on loss of contact.

        In the Klipper G38/probing module, this will use the probing endstop with a name matching the current tool.
        """

        if (x is None and y is None and z is None) or f is None:
            print("probe_move warning: insufficient values specified.")
            return [self.comment("probe_move warning: insufficient values specified in probe_move")]

        options = [[".2", ".3"],
                   [".4", ".5"]]

        # Get the "Xn Yn Zn" component of a GCODE move,
        # removing the abs/rel command and the G0/G1 mode prefix.
        coords = " " + self.gcodeMove(x=x, y=y, z=z, f=f, _mode="")[0].strip()

        command = command_prefix + options[not probe_towards][not error_on_failure] + coords

        return [command]

    def multi_probe_move(self, probe_name, f=10, x=None, y=None, z=None,
                         probe_towards=True, error_on_failure=False):
        """
        Klipper mux type command.

        Note that F is in mm/s units.
        """

        # Checks
        if (x is None and y is None and z is None):
            msg = "multi_probe_move warning: insufficient values specified."
            logging.error(msg)
            raise ValueError(msg)

        command_prefix = "MULTIPROBE"

        options = [["2", "3"],
                   ["4", "5"]]

        command = command_prefix + options[not probe_towards][not error_on_failure] + f" PROBE_NAME={probe_name}"

        # Build coordinate arguments
        if x is not None:
            command += " X=" + str(x)
        if y is not None:
            command += " Y=" + str(y)
        if z is not None:
            command += " Z=" + str(z)
        # Add feedrate
        command += " F=" + str(f)

        return [command]

    def gcodeProbeDown(self, z_scan=-5, feedrate=10):
        """
        Movimiento para hacer "probing" al colocar el tip.

        In the Klipper G38/probing module, this will use the probing endstop with a name matching the current tool.

        Note that "feedrate" here is actually "speed" in mm/s units.
        """
        # Build command
        command = self.probe_move(z=z_scan, f=feedrate, probe_towards=True, error_on_failure=True)[0]
        # return f"{prefix}{mode} {axis}{z_scan} F{feedrate};probingtime_{abs(z_scan)/feedrate}"

        # Add probing time comment
        command += self.comment(f"Probe move with max probing time={abs(z_scan)/feedrate}")

        return ["G91" + self.comment("gcodeProbeDown START"),
                command,
                "G90" + self.comment("gcodeProbeDown END")]

    def gcodeActivateTool(self, tool_name, prefix="", suffix=""):
        """
        Use the Klipper macros "P200" and "P20" to activate the corresponding tool.
        """
        return [prefix + tool_name + suffix + self.comment(f"Activating tool '{tool_name}'")]

    def gcodeT(self, tool, prefix="", suffix=""):
        """Alias for gcodeActivateTool"""
        return self.gcodeActivateTool(tool, prefix, suffix)

    def gcodeAtivateToolChanger(self):
        return [self.tc_select_gcode]

    def toolchange_probe(self, x, y, z, y_final, y_clearance, z_docking_offset, safe_z, safe_y,
                        probe_name,
                        extra_scan=1, closeup_dist=5, extra_undock_dist=0,
                        #mode_fwd="G38.2", mode_rev="G38.4"
                        feedrate=1000*60, safe_feedrate=600):
        """
        This will use regular GCODE and the spectial Klipper "MULTIPROBE" command from the probing module.
        Regular G38 cannot be used with the current implementation (it would require probing on two endstops simultaneously).

        Tool parking and docking use the same GCODE sequence.

        First align to the parked tool. Then, do a probing scan, looking for the parking post's limit switch.
        When it activates, back out up to the initial location.
        NOTE: The $6=0 GRBL setting is required to use a mechanical end-stop as a probe.
        For reference:
        - "G38.2" probe until probe is ON, error if probe does not activate during probing.
        - "G38.3" probe until probe is ON, without error.
        TODO: conseguir informacion del probe position con "$#".
        Ver: https://github.com/gnea/grbl/wiki/Grbl-v1.1-Commands#---view-gcode-parameters

        From LinuxCNC: https://linuxcnc.org/docs/2.6/html/gcode/gcode.html

        - G38.2 - probe toward workpiece, stop on contact, signal error if failure.
        - G38.3 - probe toward workpiece, stop on contact.
        - G38.4 - probe away from workpiece, stop on loss of contact, signal error if failure.
        - G38.5 - probe away from workpiece, stop on loss of contact.

        @param x: Carriage coordinate at the front of the parking.
        @param y: Carriage coordinate at the front of the parking.
        @param z: Carriage coordinate at the front of the parking.
        @param y_final:
        @param y_clearance: Length of the tool along the Y axis when it is loaded, measured from the front of the carriage.
        @param z_docking_offset: Difference between the Z coordinate for parking, relative to the Z coordinate for loading.
        @param safe_z: General Z clearance from platforms in the workspace.
        @param safe_y: General Y clearance from other parked tools.
        @param probe_name: Klipper config name for the probe to use in the MULTIPROBE command.
        @param extra_scan: Tool-change probing extra scan distance, relative to the exact Y position for docking.
        @param closeup_dist: A fast move will be made between "y" and "y_final-closeup_dist", the rest of the distance to the probe will be done slowly.
        @param extra_undock_dist: After clicking the latch and probing back, Y axis will move to "y-extra_undock_dist", to ensure undocking from the toolpost.
        @param mode_fwd: Forward probing mode.
        @param mode_rev: Reverse probing mode.
        @param feedrate: General feedrate.
        @param safe_feedrate: Undocking and probing feedrate.
        @return:
        """

        # f"{mode_fwd} Y{y_final + extra_scan} F{feedrate}" + self.comment("probe the remaining Y distance."),
        probe_fwd = self.multi_probe_move(probe_name=probe_name,
                                          y=y_final + extra_scan, # Probe towards the post to dock.
                                          f=safe_feedrate/60,
                                          probe_towards=True, error_on_failure=True)
        # f"{mode_rev} Y{y} F{feedrate}" + self.comment("probe back."),
        probe_rev = self.multi_probe_move(probe_name=probe_name,
                                          y=y,  # Probe away from the post to undock.
                                          f=safe_feedrate/60,
                                          probe_towards=False, error_on_failure=True)

        command = [self.comment("START tool-change macro.")]

        # Start sequence.
        command += [self.comment("Safety clearance moves."),
                    "G90" + self.comment("Set absolute motion mode.")]
        command += self.gcodeClearance(z=safe_z, f=feedrate, add_comment="move to the safe Z height.")
        command += self.gcodeMove(y=safe_y, add_comment="move to the Y coordinate clear of other parked tools.")
        command += [self.comment("Alignment moves.")]
        command += self.gcodeMove(x=x, add_comment="move to the X position in front of the parking post.")
        command += self.gcodeMove(y=y, z=z, add_comment="move to the Y-Z position in front of the parking post/tool, and align the rods.")
        # Initial docking moves.
        command += [self.comment("Docking moves.")]
        command += self.gcodeMove(y=y+closeup_dist, add_comment="Partial approach for initial alignment.")
        command += self.gcodeMove(z=z+z_docking_offset, add_comment="Compensate Z for a loosely parked tool.")
        # Dock/park: probe using G38 until the endstop triggers, and then back until it releases.
        command += [f"{probe_fwd[0]}" + self.comment("Probe the remaining Y distance."), f"{probe_rev[0]}" + self.comment("Probe back.")]
        # End sequence.
        command += self.gcodeMove(y=y-extra_undock_dist, f=safe_feedrate, add_comment="back away from the parking area, undocking the tool from the post.")
        command += [self.comment("Safety clearance moves.")]
        command += self.gcodeMove(y=y-y_clearance, f=feedrate, add_comment="back out of the way of other tools.")
        command += self.gcodeClearance(z=safe_z, add_comment="move to the safe Z height.")
        command += [self.comment("END tool-change macro.")]
        return command

This implictly adds a decorator to your method. Source: KillianDS (https://stackoverflow.com/a/2704528).

Ancestors

Class variables

var max_x : float
var max_y : float
var max_z : float
var min_x : float
var min_y : float
var min_z : float

Static methods

def make_limits(min_x, max_x, min_y, max_y, min_z, max_z) ‑> dict
Expand source code
@staticmethod
def make_limits(min_x, max_x, min_y, max_y, min_z, max_z) -> dict:
    """Generate the machine limits dictionary."""
    machine_limits = {
        "min_x": min_x,
        "max_x": max_x,
        "min_y": min_y,
        "max_y": max_y,
        "min_z": min_z,
        "max_z": max_z
    }
    return machine_limits

Generate the machine limits dictionary.

Methods

def G0(self,
x: float = None,
y: float = None,
z: float = None,
e: float = None,
absolute=None,
absolute_e=None,
add_comment='')
Expand source code
def G0(self,
       x: float = None,
       y: float = None,
       z: float = None,
       e: float = None,
       absolute=None,
       absolute_e=None,
       add_comment=""):
    """Fast move
    G0 uses the default (fast) feedrate.
    """

    commands = self.gcodeMove(
        x=x, y=y, z=z, e=e, f=self.feedrate,
        _mode="G0",
        absolute=absolute,
        absolute_e=absolute_e,
        add_comment=add_comment)

    return commands

Fast move G0 uses the default (fast) feedrate.

def G1(self,
x: float = None,
y: float = None,
z: float = None,
e: float = None,
f: float = None,
absolute=None,
absolute_e=None,
add_comment='')
Expand source code
def G1(self,
       x: float = None,
       y: float = None,
       z: float = None,
       e: float = None,
       f: float = None,
       absolute=None,
       absolute_e=None,
       add_comment=""):
    """G1 move command
    Accepts a feedrate parameter in mm/min units.
    """

    commands = self.gcodeMove(
        x=x, y=y, z=z, e=e, f=f,
        _mode="G1",
        absolute=absolute,
        absolute_e=absolute_e,
        add_comment=add_comment)

    return commands

G1 move command Accepts a feedrate parameter in mm/min units.

def comment(self, message: str, prefix=' ; ', suffix='')
Expand source code
def comment(self, message: str, prefix=" ; ", suffix = ""):
    if message:
        return prefix + str(message) + suffix
    else:
        return ""
def gcodeActivateTool(self, tool_name, prefix='', suffix='')
Expand source code
def gcodeActivateTool(self, tool_name, prefix="", suffix=""):
    """
    Use the Klipper macros "P200" and "P20" to activate the corresponding tool.
    """
    return [prefix + tool_name + suffix + self.comment(f"Activating tool '{tool_name}'")]

Use the Klipper macros "P200" and "P20" to activate the corresponding tool.

def gcodeAtivateToolChanger(self)
Expand source code
def gcodeAtivateToolChanger(self):
    return [self.tc_select_gcode]
def gcodeClearance(self, z: float = None, f: float = None, add_comment='')
Expand source code
def gcodeClearance(self, z: float = None, f: float = None, add_comment=""):
    """Uses a klipper macro to skip moving to clearance if the toolhead is already clear."""
    # Make command.
    clearance_command = "CLEARANCE"
    if z is not None:
        clearance_command += f" Z={z}"
    if f is not None:
        clearance_command += f" F={f}"
    # Add the comment if requested.
    if add_comment:
        clearance_command += self.comment(add_comment)
    # Return a list, as expected.
    return [clearance_command]

Uses a klipper macro to skip moving to clearance if the toolhead is already clear.

def gcodeCoordMode(self, absolute=True, add_comment='')
Expand source code
def gcodeCoordMode(self, absolute=True, add_comment=""):
    # Motion coord mode
    if absolute:
        coord_mode = "G90" + self.comment(add_comment)
    else:
        coord_mode = "G91" + self.comment(add_comment)
    return [coord_mode]
def gcodeCoordModeExtruder(self, absolute=True, add_comment='')
Expand source code
def gcodeCoordModeExtruder(self, absolute=True, add_comment=""):
    # Motion coord mode
    if absolute:
        coord_mode = "M82" + self.comment(add_comment)
    else:
        coord_mode = "M83" + self.comment(add_comment)
    return [coord_mode]
def gcodeDwell(self, seconds, wait_prefix='G4 P', comment_text='')
Expand source code
def gcodeDwell(self, seconds, wait_prefix = "G4 P", comment_text=""):
    """Dwell for the provided number of seconds"""
    wait_command = [wait_prefix + str(seconds*1000) + self.comment(comment_text)]
    return wait_command

Dwell for the provided number of seconds

def gcodeG92(self, x: float = None, y: float = None, z: float = None)
Expand source code
def gcodeG92(self, x: float = None,
            y: float = None,
            z: float = None):
    """Returns G92 setup command"""
    command = ["G92"]  # Default is G90 G0,

    if x is None and y is None and z is None:
        print("gcodeG92 warning: no values specified.")
        return [self.comment("gcodeG92 warning: no values specified in gcodeG92")]

    if x is not None:
        command.append("X" + str(x))

    if y is not None:
        command.append("Y" + str(y))

    if z is not None:
        command.append("Z" + str(z))

    return [" ".join(command)]

Returns G92 setup command

def gcodeHomeP(self,
which_axis='all',
retraction_mm=None,
cmd_prefix='H_T_',
cmd_all='ALL',
cmd_suffix='')
Expand source code
def gcodeHomeP(self, which_axis="all", retraction_mm=None, cmd_prefix="H_T_", cmd_all="ALL", cmd_suffix=""):
    """
    Make homing moves for tools, using custom Klipper macro commands.
    TODO: This is largely deprecated.
    """

    # Homing command prefix
    home_command = cmd_prefix

    if which_axis == "all":
        # A macro named "H_ALL" must be configured, which homes all tools in Klipper.
        home_command += cmd_all
    else:
        # Macros named "H_P200" (and similar) must be configured in Klipper.
        home_command += which_axis

    # Add the suffix
    home_command += cmd_suffix

    # Add homing GCODE command.
    home_commands = [home_command + self.comment(f"Home {which_axis} tool(s)")]

    # Check for retract move
    if retraction_mm is not None:
        # Ensure relative extruder motion
        home_commands += self.gcodeCoordMode(
            absolute=False, add_comment="Tool homing retraction.")
        home_commands += self.gcodeCoordModeExtruder(
            absolute=False, add_comment="Tool homing retraction.")
        home_commands += self.gcodeMove(
            e=retraction_mm,
            add_comment="Tool homing retraction."
        )

    # For testing
    # home_command = ["SET_ORIGIN X=0 Y=0 Z=0 E=0"]

    return home_commands

Make homing moves for tools, using custom Klipper macro commands. TODO: This is largely deprecated.

def gcodeHomeXYZ(self, axes: str = None)
Expand source code
def gcodeHomeXYZ(self, axes:str=None):
    """Homes all XYZ axes, unless X, Y, and/or Z are selected in 'axes'.
    Then sets absolute XYZ coordinates (G90), and relative E coordinates (M83).
    """

    # Default GRBL homing command
    home_command = "G28"

    # Append single axis specifier if required
    if axes:
        if axes.upper().find("X") >= 0:
            home_command += " X"
        if axes.upper().find("Y") >= 0:
            home_command += " Y"
        if axes.upper().find("Z") >= 0:
            home_command += " Z"

    return [home_command + self.comment("Homing command"),
            "G90" + self.comment("Absolute XYZ motion"),
            "M83" + self.comment("Relative E motion")]

Homes all XYZ axes, unless X, Y, and/or Z are selected in 'axes'. Then sets absolute XYZ coordinates (G90), and relative E coordinates (M83).

def gcodeHuman(self, text, human_prefix='PAUSE')
Expand source code
def gcodeHuman(self, text, human_prefix = "PAUSE"):
    human_command = [human_prefix + self.comment(text)]
    return human_command
def gcodeMove(self,
x: float = None,
y: float = None,
z: float = None,
e: float = None,
f: float = None,
absolute=None,
absolute_e=None,
add_comment='') ‑> list
Expand source code
def gcodeMove(self,
              x: float = None,
              y: float = None,
              z: float = None,
              e: float = None,
              f: float = None,
              _mode="G1",
              absolute=None,
              absolute_e=None,
              add_comment="") -> list:  # True for absolute (G90), False for relative (G91).
    """
    gcode to move robot to xyz and extruder to e, by pasting values of xyzsef if specified.
    @param absolute: Use G90/G91 for XYZ move (abs/rel).
    TODO: this should not affect E moves, M82/M83 check pending!
    """

    if (x is None) and (y is None) and (z is None) and (e is None):
        msg = "No coordinate values specified for GCODE move command."
        logging.error(msg)
        raise ValueError(msg)

    # Coord parsing
    command_pars = []
    if x is not None:
        command_pars.append(f"X{x}")
    if y is not None:
        command_pars.append(f"Y{y}")
    if z is not None:
        command_pars.append(f"Z{z}")
    if e is not None:
        command_pars.append(f"E{e}")
    if f is not None:
        command_pars.append(f"F{f}")

    # Construct GCODE command as a string.
    str_command = ""
    if command_pars:
        # Build move command.
        str_command += _mode + " " + " ".join(command_pars)
    if add_comment:
        # Add the comment if requested.
        str_command += self.comment(add_comment)

    # Motion coord mode command.
    coord_mode = []
    if absolute is not None:
        coord_mode = self.gcodeCoordMode(absolute)
    if absolute_e is not None:
        coord_mode += self.gcodeCoordModeExtruder(absolute_e)
    # Motion coordinates command.
    move_command = []
    if str_command != "":
        move_command = [str_command]

    # Prepend coord mode to gcode if any.
    final_commands: list = coord_mode + move_command
    return final_commands

gcode to move robot to xyz and extruder to e, by pasting values of xyzsef if specified. @param absolute: Use G90/G91 for XYZ move (abs/rel). TODO: this should not affect E moves, M82/M83 check pending!

def gcodeProbeDown(self, z_scan=-5, feedrate=10)
Expand source code
def gcodeProbeDown(self, z_scan=-5, feedrate=10):
    """
    Movimiento para hacer "probing" al colocar el tip.

    In the Klipper G38/probing module, this will use the probing endstop with a name matching the current tool.

    Note that "feedrate" here is actually "speed" in mm/s units.
    """
    # Build command
    command = self.probe_move(z=z_scan, f=feedrate, probe_towards=True, error_on_failure=True)[0]
    # return f"{prefix}{mode} {axis}{z_scan} F{feedrate};probingtime_{abs(z_scan)/feedrate}"

    # Add probing time comment
    command += self.comment(f"Probe move with max probing time={abs(z_scan)/feedrate}")

    return ["G91" + self.comment("gcodeProbeDown START"),
            command,
            "G90" + self.comment("gcodeProbeDown END")]

Movimiento para hacer "probing" al colocar el tip.

In the Klipper G38/probing module, this will use the probing endstop with a name matching the current tool.

Note that "feedrate" here is actually "speed" in mm/s units.

def gcodeT(self, tool, prefix='', suffix='')
Expand source code
def gcodeT(self, tool, prefix="", suffix=""):
    """Alias for gcodeActivateTool"""
    return self.gcodeActivateTool(tool, prefix, suffix)

Alias for gcodeActivateTool

def gcodeWait(self, comment_text='')
Expand source code
def gcodeWait(self, comment_text=""):
    """Wait for current moves to finish"""
    wait_command = ["M400" + self.comment(comment_text)]
    return wait_command

Wait for current moves to finish

def gcode_set_origin(self)
Expand source code
def gcode_set_origin(self):
    """Set the current position as the XYZ origin."""
    return ["SET_KINEMATIC_POSITION X=0 Y=0 Z=0"]

Set the current position as the XYZ origin.

def multi_probe_move(self,
probe_name,
f=10,
x=None,
y=None,
z=None,
probe_towards=True,
error_on_failure=False)
Expand source code
def multi_probe_move(self, probe_name, f=10, x=None, y=None, z=None,
                     probe_towards=True, error_on_failure=False):
    """
    Klipper mux type command.

    Note that F is in mm/s units.
    """

    # Checks
    if (x is None and y is None and z is None):
        msg = "multi_probe_move warning: insufficient values specified."
        logging.error(msg)
        raise ValueError(msg)

    command_prefix = "MULTIPROBE"

    options = [["2", "3"],
               ["4", "5"]]

    command = command_prefix + options[not probe_towards][not error_on_failure] + f" PROBE_NAME={probe_name}"

    # Build coordinate arguments
    if x is not None:
        command += " X=" + str(x)
    if y is not None:
        command += " Y=" + str(y)
    if z is not None:
        command += " Z=" + str(z)
    # Add feedrate
    command += " F=" + str(f)

    return [command]

Klipper mux type command.

Note that F is in mm/s units.

def probe_move(self,
x=None,
y=None,
z=None,
f=None,
probe_towards=True,
error_on_failure=False,
command_prefix='G38')
Expand source code
def probe_move(self, x=None, y=None, z=None, f=None,
            probe_towards=True, error_on_failure=False,
            command_prefix="G38"):
    """
    Example:

    gcode.probe_move(1,2,3)
    'G38.4 X1 Y2 Z3'

    For reference:
        - "G38.2" probe until probe is ON, error if probe does not activate during probing.
        - "G38.3" probe until probe is ON, without error.

    From LinuxCNC: https://linuxcnc.org/docs/2.6/html/gcode/gcode.html
        - G38.2 - (True/True) probe toward workpiece, stop on contact, signal error if failure.
        - G38.3 - (True/False) probe toward workpiece, stop on contact.
        - G38.4 - (False/True) probe away from workpiece, stop on loss of contact, signal error if failure.
        - G38.5 - (False/False) probe away from workpiece, stop on loss of contact.

    In the Klipper G38/probing module, this will use the probing endstop with a name matching the current tool.
    """

    if (x is None and y is None and z is None) or f is None:
        print("probe_move warning: insufficient values specified.")
        return [self.comment("probe_move warning: insufficient values specified in probe_move")]

    options = [[".2", ".3"],
               [".4", ".5"]]

    # Get the "Xn Yn Zn" component of a GCODE move,
    # removing the abs/rel command and the G0/G1 mode prefix.
    coords = " " + self.gcodeMove(x=x, y=y, z=z, f=f, _mode="")[0].strip()

    command = command_prefix + options[not probe_towards][not error_on_failure] + coords

    return [command]

Example:

gcode.probe_move(1,2,3) 'G38.4 X1 Y2 Z3'

For reference: - "G38.2" probe until probe is ON, error if probe does not activate during probing. - "G38.3" probe until probe is ON, without error.

From LinuxCNC: https://linuxcnc.org/docs/2.6/html/gcode/gcode.html - G38.2 - (True/True) probe toward workpiece, stop on contact, signal error if failure. - G38.3 - (True/False) probe toward workpiece, stop on contact. - G38.4 - (False/True) probe away from workpiece, stop on loss of contact, signal error if failure. - G38.5 - (False/False) probe away from workpiece, stop on loss of contact.

In the Klipper G38/probing module, this will use the probing endstop with a name matching the current tool.

def set_limits(self, machine_limits: dict)
Expand source code
def set_limits(self, machine_limits: dict):
    """Apply the provided limits.
    See 'make_limits' for input definition."""
    self.min_x = machine_limits.get("min_x", self.min_x)
    self.max_x = machine_limits.get("max_x", self.max_x)
    self.min_y = machine_limits.get("min_y", self.min_y)
    self.max_y = machine_limits.get("max_y", self.max_y)
    self.min_z = machine_limits.get("min_z", self.min_z)
    self.max_z = machine_limits.get("max_z", self.max_z)

Apply the provided limits. See 'make_limits' for input definition.

def toolchange_probe(self,
x,
y,
z,
y_final,
y_clearance,
z_docking_offset,
safe_z,
safe_y,
probe_name,
extra_scan=1,
closeup_dist=5,
extra_undock_dist=0,
feedrate=60000,
safe_feedrate=600)
Expand source code
def toolchange_probe(self, x, y, z, y_final, y_clearance, z_docking_offset, safe_z, safe_y,
                    probe_name,
                    extra_scan=1, closeup_dist=5, extra_undock_dist=0,
                    #mode_fwd="G38.2", mode_rev="G38.4"
                    feedrate=1000*60, safe_feedrate=600):
    """
    This will use regular GCODE and the spectial Klipper "MULTIPROBE" command from the probing module.
    Regular G38 cannot be used with the current implementation (it would require probing on two endstops simultaneously).

    Tool parking and docking use the same GCODE sequence.

    First align to the parked tool. Then, do a probing scan, looking for the parking post's limit switch.
    When it activates, back out up to the initial location.
    NOTE: The $6=0 GRBL setting is required to use a mechanical end-stop as a probe.
    For reference:
    - "G38.2" probe until probe is ON, error if probe does not activate during probing.
    - "G38.3" probe until probe is ON, without error.
    TODO: conseguir informacion del probe position con "$#".
    Ver: https://github.com/gnea/grbl/wiki/Grbl-v1.1-Commands#---view-gcode-parameters

    From LinuxCNC: https://linuxcnc.org/docs/2.6/html/gcode/gcode.html

    - G38.2 - probe toward workpiece, stop on contact, signal error if failure.
    - G38.3 - probe toward workpiece, stop on contact.
    - G38.4 - probe away from workpiece, stop on loss of contact, signal error if failure.
    - G38.5 - probe away from workpiece, stop on loss of contact.

    @param x: Carriage coordinate at the front of the parking.
    @param y: Carriage coordinate at the front of the parking.
    @param z: Carriage coordinate at the front of the parking.
    @param y_final:
    @param y_clearance: Length of the tool along the Y axis when it is loaded, measured from the front of the carriage.
    @param z_docking_offset: Difference between the Z coordinate for parking, relative to the Z coordinate for loading.
    @param safe_z: General Z clearance from platforms in the workspace.
    @param safe_y: General Y clearance from other parked tools.
    @param probe_name: Klipper config name for the probe to use in the MULTIPROBE command.
    @param extra_scan: Tool-change probing extra scan distance, relative to the exact Y position for docking.
    @param closeup_dist: A fast move will be made between "y" and "y_final-closeup_dist", the rest of the distance to the probe will be done slowly.
    @param extra_undock_dist: After clicking the latch and probing back, Y axis will move to "y-extra_undock_dist", to ensure undocking from the toolpost.
    @param mode_fwd: Forward probing mode.
    @param mode_rev: Reverse probing mode.
    @param feedrate: General feedrate.
    @param safe_feedrate: Undocking and probing feedrate.
    @return:
    """

    # f"{mode_fwd} Y{y_final + extra_scan} F{feedrate}" + self.comment("probe the remaining Y distance."),
    probe_fwd = self.multi_probe_move(probe_name=probe_name,
                                      y=y_final + extra_scan, # Probe towards the post to dock.
                                      f=safe_feedrate/60,
                                      probe_towards=True, error_on_failure=True)
    # f"{mode_rev} Y{y} F{feedrate}" + self.comment("probe back."),
    probe_rev = self.multi_probe_move(probe_name=probe_name,
                                      y=y,  # Probe away from the post to undock.
                                      f=safe_feedrate/60,
                                      probe_towards=False, error_on_failure=True)

    command = [self.comment("START tool-change macro.")]

    # Start sequence.
    command += [self.comment("Safety clearance moves."),
                "G90" + self.comment("Set absolute motion mode.")]
    command += self.gcodeClearance(z=safe_z, f=feedrate, add_comment="move to the safe Z height.")
    command += self.gcodeMove(y=safe_y, add_comment="move to the Y coordinate clear of other parked tools.")
    command += [self.comment("Alignment moves.")]
    command += self.gcodeMove(x=x, add_comment="move to the X position in front of the parking post.")
    command += self.gcodeMove(y=y, z=z, add_comment="move to the Y-Z position in front of the parking post/tool, and align the rods.")
    # Initial docking moves.
    command += [self.comment("Docking moves.")]
    command += self.gcodeMove(y=y+closeup_dist, add_comment="Partial approach for initial alignment.")
    command += self.gcodeMove(z=z+z_docking_offset, add_comment="Compensate Z for a loosely parked tool.")
    # Dock/park: probe using G38 until the endstop triggers, and then back until it releases.
    command += [f"{probe_fwd[0]}" + self.comment("Probe the remaining Y distance."), f"{probe_rev[0]}" + self.comment("Probe back.")]
    # End sequence.
    command += self.gcodeMove(y=y-extra_undock_dist, f=safe_feedrate, add_comment="back away from the parking area, undocking the tool from the post.")
    command += [self.comment("Safety clearance moves.")]
    command += self.gcodeMove(y=y-y_clearance, f=feedrate, add_comment="back out of the way of other tools.")
    command += self.gcodeClearance(z=safe_z, add_comment="move to the safe Z height.")
    command += [self.comment("END tool-change macro.")]
    return command

This will use regular GCODE and the spectial Klipper "MULTIPROBE" command from the probing module. Regular G38 cannot be used with the current implementation (it would require probing on two endstops simultaneously).

Tool parking and docking use the same GCODE sequence.

First align to the parked tool. Then, do a probing scan, looking for the parking post's limit switch. When it activates, back out up to the initial location. NOTE: The $6=0 GRBL setting is required to use a mechanical end-stop as a probe. For reference: - "G38.2" probe until probe is ON, error if probe does not activate during probing. - "G38.3" probe until probe is ON, without error. TODO: conseguir informacion del probe position con "$#". Ver: https://github.com/gnea/grbl/wiki/Grbl-v1.1-Commands#---view-gcode-parameters

From LinuxCNC: https://linuxcnc.org/docs/2.6/html/gcode/gcode.html

  • G38.2 - probe toward workpiece, stop on contact, signal error if failure.
  • G38.3 - probe toward workpiece, stop on contact.
  • G38.4 - probe away from workpiece, stop on loss of contact, signal error if failure.
  • G38.5 - probe away from workpiece, stop on loss of contact.

@param x: Carriage coordinate at the front of the parking. @param y: Carriage coordinate at the front of the parking. @param z: Carriage coordinate at the front of the parking. @param y_final: @param y_clearance: Length of the tool along the Y axis when it is loaded, measured from the front of the carriage. @param z_docking_offset: Difference between the Z coordinate for parking, relative to the Z coordinate for loading. @param safe_z: General Z clearance from platforms in the workspace. @param safe_y: General Y clearance from other parked tools. @param probe_name: Klipper config name for the probe to use in the MULTIPROBE command. @param extra_scan: Tool-change probing extra scan distance, relative to the exact Y position for docking. @param closeup_dist: A fast move will be made between "y" and "y_final-closeup_dist", the rest of the distance to the probe will be done slowly. @param extra_undock_dist: After clicking the latch and probing back, Y axis will move to "y-extra_undock_dist", to ensure undocking from the toolpost. @param mode_fwd: Forward probing mode. @param mode_rev: Reverse probing mode. @param feedrate: General feedrate. @param safe_feedrate: Undocking and probing feedrate. @return:

Inherited members

class MethodIntercept (cb_ignore: list = None)
Expand source code
class MethodIntercept(object):
    """This implictly adds a decorator to your method.
    Source: KillianDS (https://stackoverflow.com/a/2704528).
    """
    init = False
    def __init__(self, cb_ignore: list=None):
        self.cb_ignore = ["cb_ignore", "before", "after"]
        if cb_ignore is not None:
            self.cb_ignore += cb_ignore
        self.init = True

    def __getattribute__(self, name):
        attr = object.__getattribute__(self, name)
        if not object.__getattribute__(self, "init"):
            # Return immediately if the class has not been initialized with super() by the parent.
            return attr
            # Error-out immediately if the class has not been initialized with super() by the parent.
            #raise Exception("MethodIntercept has not been initialized. Have you called super() in your class?")
        elif hasattr(attr, '__call__') and (attr.__name__ not in self.cb_ignore):
            # Define a new function with the original
            # sandwiched between the callbacks.
            def newfunc(*args, **kwargs):
                self.before(*args, **kwargs)
                result = attr(*args, **kwargs)
                self.after(*args, **kwargs)
                return result
            # Return the new function instead of the
            # original, effectively intercepting it.
            return newfunc
        else:
            return attr

    @staticmethod
    def before(*args, **kwargs):
        """Override this method to intercept method calls in parent classes."""
        pass

    @staticmethod
    def after(*args, **kwargs):
        """Override this method to intercept results of method calls in parent classes."""
        pass

This implictly adds a decorator to your method. Source: KillianDS (https://stackoverflow.com/a/2704528).

Subclasses

Class variables

var init

Static methods

def after(*args, **kwargs)
Expand source code
@staticmethod
def after(*args, **kwargs):
    """Override this method to intercept results of method calls in parent classes."""
    pass

Override this method to intercept results of method calls in parent classes.

def before(*args, **kwargs)
Expand source code
@staticmethod
def before(*args, **kwargs):
    """Override this method to intercept method calls in parent classes."""
    pass

Override this method to intercept method calls in parent classes.