Module pipettin-piper.piper.plugins.tools.tool
Functions
def make_tool_param_accessor(controller: Controller, tool_name: str)-
Expand source code
def make_tool_param_accessor(controller: Controller, tool_name: str): """ Creates a partial function that accesses parameters of a specific tool in the controller's database. NOTE: I'm using "partial" here instead of just making a function because I am afraid of python. Args: controller (Controller): The controller instance containing the database of tools. tool_name (str): The name of the tool whose parameters will be accessed. Returns: function: A function that takes a key and returns the corresponding parameter value for the specified tool. The function will return a deep copy of the result, meant to block changes to the original data. """ def data_accessor(controller: Controller, tool_name: str): return controller.database_tools.getToolByName(tool_name)["parameters"] data_accessor = functools.partial(data_accessor, controller=controller, tool_name=tool_name) def accessor(key: str, controller: Controller, tool_name: str): params = controller.database_tools.getToolByName(tool_name)["parameters"] try: res = params[key] except Exception as e: raise KeyError(f"Parameter '{key}' not found in tool '{tool_name}'.") from e return deepcopy(res) params_accessor = functools.partial(accessor, controller=controller, tool_name=tool_name) return data_accessor, params_accessorCreates a partial function that accesses parameters of a specific tool in the controller's database.
NOTE: I'm using "partial" here instead of just making a function because I am afraid of python.
Args
controller:Controller- The controller instance containing the database of tools.
tool_name:str- The name of the tool whose parameters will be accessed.
Returns
function- A function that takes a key and returns the corresponding parameter value for the specified tool. The function will return a deep copy of the result, meant to block changes to the original data.
Classes
class Tool (tool_data: dict, gcode_builder: GcodeBuilder)-
Expand source code
class Tool(TrackedDict): """ Represents a tool in the system, encapsulating its parameters, offsets, and GCODE sequences for operations such as pickup, parking, and activation. A `Tool` object is responsible for generating GCODE commands required to interact with the physical toolhead, ensuring proper tool management and collision avoidance. The `Tool` class extends `TrackedDict` to provide dynamic access to tool parameters stored in a database or JSON file, while exposing commonly used offsets and clearances as properties. Attributes: name (str): The unique name of the tool. parameters (dict): Tool parameters as loaded from the database or JSON file. parked (bool): Used to track whether the tool is currently parked. builder (GcodeBuilder): GCODE builder instance for generating movement commands. controller (Controller): Controller instance managing tools and tool data. gcode (GcodePrimitives): Object providing basic GCODE commands. tool_data (dict): Complete data for the tool, including parameters and metadata. Properties: tool_offset (dict): Tool offset parameters for the tool's active center. x_offset (float): X-axis offset for the tool. y_offset (float): Y-axis offset for the tool. z_offset (float): Z-axis offset for the tool. clearance (dict): Clearance properties to avoid collisions. safe_x (float): X clearance added by the tool to the toolhead's sides. safe_y (float): Y clearance added by the tool to the toolhead's front. safe_y_parked (float): Minimum Y coordinate for the tool when parked. safe_y_post (float): Minimum Y coordinate for the tool's empty parking post. safe_y_loaded (float): Y coordinate when the tool clears its parking post. tool_post (dict): Coordinates and offsets of the tool parking post. Notes: - The class assumes that tools have parameters like "tool_post" and "docking_commands" for GCODE operations. - It ensures safe tool-changing by using predefined clearances and docking sequences to avoid collisions during operations. """ def __init__(self, tool_data: dict, gcode_builder: GcodeBuilder): # Save the entry from the "tools" collection in the DB, # including parameters and metadata (e.g. name, description). # TODO: Disabled "deepcopy" here because tool_data is now a # TrackedDict with an accesor function for data in the DB. self.tool_data = tool_data # Extract the tool's parameters. These contain the actual tool parameters, # which look like the entries in the PIPETTES dict above. self.parameters = tool_data["parameters"] # Save tool name. self.name = tool_data["name"] if gcode_builder: # Save controller and builder. self.builder: GcodeBuilder = gcode_builder # Get the gcode generator and main controller. self.gcode: GcodePrimitives = self.builder.gcode self.controller: Controller = self.builder.controller # Setup tool parameter access from the controller's database. self.get_data, get_param = make_tool_param_accessor(self.controller, self.name) super().__init__(accessor_func=get_param) else: # Use the default GCODE generator. self.gcode = GcodePrimitives() # Setup tool parameter access from the provided data. super().__init__(data=tool_data["parameters"]) self.get_data = lambda: self.tool_data # NOTE: Tools may register themselves on instantiation in the gcode builder. # Though this is usually the job of a plugin with a "tool loader" method. #self.builder.register_tool(self) def __repr__(self): return f"Tool {self.name}: {self.get_data()}" def __iter__(self): """ Returns an iterator over the keys of the dictionary. Returns: iterator: An iterator over the keys of the dictionary. """ # NOTE: Implementing this method allows updating other dicts with this one. # TODO: This may cause issues if the keys are used to update the database in between. params = self.get_data() return iter(params.keys()) name: str= None """Unique name for the tool.""" parameters: dict= None """Tool parameters as loaded from the DB or JSON file.""" parked: bool """Indicates wether the tool is parked or not.""" @property def tool_offset(self): """Tool offset parameters for the active center of the tool.""" return deepcopy(self["tool_offset"]) @property def x_offset(self): """X offset for the tool.""" x = float(self.tool_offset["x"]) return x @property def y_offset(self): """Y offset for the tool.""" y = float(self.tool_offset["y"]) return y @property def z_offset(self): """Z offset for the tool.""" z = float(self.tool_offset["z"]) return z @property def safe_y(self): """Distance along the Y direction that this tool adds to the front of the toolhead. Used to avoid collisions with the machine, parked tools, and such.""" return self.clearance["safe_y"] @property def safe_x(self): """Distance along the X direction that this tool adds to the sides of the toolhead. Used to avoid collisions with the machine, parked tools, and such. Use the largest value if the tool is not symmetric.""" return self.clearance["safe_x"] @property def clearance(self) -> dict: """Clearance properties for the tool.""" return deepcopy(self["clearance"]) @property def safe_y_parked(self): """Minimum Y coordinate at which this tool is found when properly parked. Used to avoid collisions with the machine, parked tools, and such. """ # NOTE: The post might protrude further than the tool itself. # Use the minimum of the two, if available. safe_y_post = self.safe_y_post safe_y_parked = self.clearance["safe_y_parked"] # Choose the most conservative clearance. if safe_y_post is not None and safe_y_parked is not None: # NOTE: Corrected for axis inversion. safe_y_parked = max(safe_y_parked, safe_y_post) return safe_y_parked @property def safe_y_post(self): """Minimum Y coordinate at which the parking post is found when empty. This will tipically depend on the length of pins in the parking post. Used to avoid collisions with the machine, parked tools, and such. """ return self.clearance["safe_y_post"] @property def safe_y_loaded(self): """Y coordinate at which the tool is barely clear of its (empty) parking post. Derived from "safe_y" and "safe_y_post". Useful for toolchanging. """ # NOTE: Corrected for axis inversion. return self.safe_y_post + self.safe_y @property def tool_post(self): """Coordinates of the tool-post. Example values: - x: 16.50, - y: 280, - z: 19 - y_docking_closeup: 2 - z_docking_offset: 0.0 - y_parking_closeup: 4 - z_parking_offset: 1.0 - feedrate: 500 """ return deepcopy(self["tool_post"]) # TODO: Add "active center" size, meant to avoid collisions with # workspace objects due to bulky tools at their active site. # tool_shape: dict = {"size": 10, "x": 10, "y": 2, "diameter": 10} def pickup(self, old_tool, new_tool) -> list: """Pickup a tool. Assumes an empty toolhead, and a tool in its parking post. """ commands = [self.gcode.comment(f"Picking up {self.name}.")] # Home tool axis. # NOTE: Homing should be overriden by child classes. # See the "Pipette" class. # Home the toolchanger remote. # TODO: "gcodeHomeP" is largely deprecated. Replace it. commands += self.gcode.gcodeHomeP(cmd_prefix="HOME_", which_axis="E0") # Generate commands for the pickup. commands += self.gcode_dock() # Mark the tool as not parked. self.parked = False # Mark a toolchange / activate the new tool. commands += self.gcode_activate() return commands def gcode_activate(self) -> list: """Signal the firmware to activate this tool. This usually means sending gcode commands equivalent to T0, T1, etc. which may switch the active extruder. The need for it is really up to the firmware and the machine. Example: - "activate": "T1" """ return deepcopy(self["activate"]) def gcode_deactivate(self) -> list: """Signal the firmware to deactivate this tool. Example: - "deactivate": "; dummy deactivate." """ return deepcopy(self["deactivate"]) def park(self, old_tool, new_tool) -> list: """Park a tool. Assumes that the tool is mounted, and that its parking post is empty. """ # Generate commands for the pickup. parking_commands = self.gcode_park() # Mark the tool as parked. self.parked = True # TODO: Is a "deactivate" function needed for the pipettes? parking_commands += self.gcode_deactivate() return parking_commands @property def docking_commands(self): """GCODE command list for the docking sequence This command is run after placing the empty toolhead at the parking post's coordinates. It starts at those coordinates and must end with a correctly docked tool. Example: >>> [ >>> "T0", >>> "G91", >>> "M83", >>> "G1 Y-1.0 E-1.0 F100", >>> "G1 Y-1.0 E-3.0 F50", >>> "G1 E-2.0 F50" >>> ] """ # Return a copy of the commands. return deepcopy(self["docking_commands"]) def gcode_post_front(self, commands: list = None) -> list: """Generate GCODE to approack a parking post (assuming none is loaded).""" if commands is None: commands = [] # Initialize commands list. commands += [self.gcode.comment("Moving in front of parking post.")] # Clearance moves. commands += self.gcode_safe_zy() # Parked tool coordinates. x, _, z = self.tool_post["x"], self.tool_post["y"], self.tool_post["z"] # Extra Z-axis distance used to help align the tool. z_offset = self.tool_post["z_docking_offset"] # Initial approach. commands += self.gcode_approach(x, self.safe_y_parked, z+z_offset) # Done. commands += [self.gcode.comment("Done moving in front of parking post.")] return commands # Custom GCODE section def gcode_dock(self, commands: list = None) -> list: """Generate GCODE to dock a tool (assuming none is loaded). Required tool parameters: - tool_post: coordinates and offsets of the tool post. - docking_commands: list of GCODE commands. - parking_commands: list of GCODE commands. Returns: list: List of GCODES for docking. """ if commands is None: commands = [] # Initialize commands list. commands += [self.gcode.comment("START tool-change pickup macro.")] # Clearance moves. commands += self.gcode_safe_zy() # Parked tool coordinates. x, y, z = self.tool_post["x"], self.tool_post["y"], self.tool_post["z"] # Extra Y-axis distance needed to approach and touch the tool. y_docking_closeup = self.tool_post["y_docking_closeup"] # Extra Z-axis distance used to help align the tool. z_offset = self.tool_post["z_docking_offset"] # Initial approach. commands += self.gcode_approach(x, self.safe_y_parked, z+z_offset) # Closeup move. commands += self.gcode_approach(x, y+y_docking_closeup, z+z_offset) # Final approach moves. commands += self.gcode_align(y=y, z=z+z_offset) # Tool-changer commands to lock the tool to the toolhead. commands += [self.gcode.comment("Locking moves.")] commands += self.docking_commands # End sequence. commands += self.gcode.G0( y=self.safe_y_loaded, absolute=True, add_comment="Move back to the Y coordinate clear of other parked tools.") # Done. commands += [self.gcode.comment("END tool-change macro.")] return commands @property def parking_commands(self): """GCODE command list for the undocking sequence This command is run after placing the tool at the parking post's coordinates. It starts at those coordinates and must end with a free and correctly parked tool. Example: >>> [ >>> "T0", >>> "G91", >>> "M83", >>> "G1 Y4 E6 F100", >>> "G1 Y5 F500" >>> ] """ # Return a copy of the commands. return deepcopy(self["parking_commands"]) def gcode_park(self, commands: list = None) -> list: """Generate GCODE to park a tool (assuming none is loaded). TODO: Document this function. See 'gcode_dock' for deatils for now. """ if commands is None: commands = [] # Initialize commands list. commands += [self.gcode.comment("START tool-change parking macro.")] # Clearance moves. commands += self.gcode_safe_zy() # Parking post coordinates. x, y, z = self.tool_post["x"], self.tool_post["y"], self.tool_post["z"] # Extra Y-axis distance needed to approach the post and hang the tool. y_parking_closeup = self.tool_post["y_parking_closeup"] # Extra Z-axis distance used to help align the tool. z_offset = self.tool_post["z_parking_offset"] # TODO: This clearance move may be trivial. Consider removing. commands += self.gcode_approach(x, self.safe_y_loaded, z+z_offset) # Initial approach. commands += self.gcode_approach(x, y, z+z_offset) # Final approach moves. # NOTE: Corrected for axis inversion. commands += self.gcode_align(y=y-y_parking_closeup, z=z+z_offset) # Tool-changer commands to release the tool from the toolhead. commands += [self.gcode.comment("Unlocking moves.")] commands += self.parking_commands # End sequence. commands += self.gcode.G0( y=self.safe_y_parked, absolute=True, add_comment="Move back to the Y coordinate clear of other parked tools.") # Done. commands += [self.gcode.comment("END tool-change macro.")] return commands def gcode_safe_zy(self, commands: list = None) -> list: """Make G1 GCODE commands to move to safe Z and Y coordinates, in that order.""" # Use a new command list if none was provided. if commands is None: commands = [] # Get the absolute clearance coordinates safe_z = self.builder.getSafeHeight() safe_y = self.builder.getSafeY() # Clearance moves. commands += [self.gcode.comment("Safety clearance moves."), "G90" + self.gcode.comment("Set absolute motion mode.")] commands += self.gcode.gcodeClearance( z=safe_z, f=self.builder.feedrate, add_comment="Move to the safe Z height.") commands += self.gcode.G0( y=safe_y, add_comment="Move to the Y coordinate clear of other parked tools.") return commands def gcode_approach(self, x, y, z) -> list: """Make G1 GCODE to move in front of a tool-post. Moves first in X, then in YZ. """ # Start sequence: approach the tool. commands = [self.gcode.comment("Alignment moves.")] commands += self.gcode.G0( x=x, add_comment="Move to the X position in front of the parking post.") commands += self.gcode.G0( y=y, z=z, add_comment="Move to the Y-Z position in front of the parking post/tool.") return commands def gcode_align(self, y, z) -> list: """Make G1 GCODE for fine alignment to a tool-post. Moves first in Y (fine and slow approach) and then in Z (wobble compensation). """ # Slow feedrates for the moves. tc_feedrate = self.tool_post["feedrate"] # Initial docking moves. commands = [self.gcode.comment("Alignment moves.")] commands += self.gcode.G1( y=y, f=tc_feedrate, add_comment="Partial approach for initial alignment.") commands += self.gcode.G1( z=z, f=tc_feedrate, add_comment="Compensate Z for looseness in the parking post.") return commands @property def homing(self): """Homing parameters and commands for the pipette. Example: { "commands": [ "T2", "HOME_EXTRUDER EXTRUDER=extruder2", "G90", "M82", "G1 E0 F6000; Total free motion minus 'travel_distance' below." ], "commands.desc": "Homing commands, leaving the pipette at the maximum volume.", "travel_distance": 30, "travel_distance.desc": "Free travel distance after homing, to the lowest volume position." } """ return deepcopy(self["homing"]) def home(self) -> list: """Generate GCODE commands needed to home this tool. Then update the internal state accordingly. """ logging.warning(f"Placeholder homing routine for tool '{self.name}'.") # Build the command if not self.controller.machine.dry: logging.info(f"Homing tool '{self.name}'.") homing_commands = self.homing['commands'] else: # Dry mode: msg = f"Simulating homed state for tool '{self.name}'. Dry mode enabled." logging.info(msg) homing_commands = [self.gcode.comment(msg)] return homing_commandsRepresents a tool in the system, encapsulating its parameters, offsets, and GCODE sequences for operations such as pickup, parking, and activation. A
Toolobject is responsible for generating GCODE commands required to interact with the physical toolhead, ensuring proper tool management and collision avoidance.The
Toolclass extendsTrackedDictto provide dynamic access to tool parameters stored in a database or JSON file, while exposing commonly used offsets and clearances as properties.Attributes
name:str- The unique name of the tool.
parameters:dict- Tool parameters as loaded from the database or JSON file.
parked:bool- Used to track whether the tool is currently parked.
builder:GcodeBuilder- GCODE builder instance for generating movement commands.
controller:Controller- Controller instance managing tools and tool data.
gcode:GcodePrimitives- Object providing basic GCODE commands.
tool_data:dict- Complete data for the tool, including parameters and metadata.
Properties
tool_offset (dict): Tool offset parameters for the tool's active center. x_offset (float): X-axis offset for the tool. y_offset (float): Y-axis offset for the tool. z_offset (float): Z-axis offset for the tool. clearance (dict): Clearance properties to avoid collisions. safe_x (float): X clearance added by the tool to the toolhead's sides. safe_y (float): Y clearance added by the tool to the toolhead's front. safe_y_parked (float): Minimum Y coordinate for the tool when parked. safe_y_post (float): Minimum Y coordinate for the tool's empty parking post. safe_y_loaded (float): Y coordinate when the tool clears its parking post. tool_post (dict): Coordinates and offsets of the tool parking post.
Notes
- The class assumes that tools have parameters like "tool_post" and "docking_commands" for GCODE operations.
- It ensures safe tool-changing by using predefined clearances and docking sequences to avoid collisions during operations.
Initializes the TrackedDict instance.
Args
file_path:str- The path to the YAML file.
data:dict- Altertative (and sufficient) data source. Used to update the YAML file if any.
Ancestors
- TrackedDict
- YAMLReader
- collections.UserDict
- collections.abc.MutableMapping
- collections.abc.Mapping
- collections.abc.Collection
- collections.abc.Sized
- collections.abc.Iterable
- collections.abc.Container
Subclasses
Class variables
var name : str-
Unique name for the tool.
var parameters : dict-
Tool parameters as loaded from the DB or JSON file.
var parked : bool-
Indicates wether the tool is parked or not.
Instance variables
prop clearance : dict-
Expand source code
@property def clearance(self) -> dict: """Clearance properties for the tool.""" return deepcopy(self["clearance"])Clearance properties for the tool.
prop docking_commands-
Expand source code
@property def docking_commands(self): """GCODE command list for the docking sequence This command is run after placing the empty toolhead at the parking post's coordinates. It starts at those coordinates and must end with a correctly docked tool. Example: >>> [ >>> "T0", >>> "G91", >>> "M83", >>> "G1 Y-1.0 E-1.0 F100", >>> "G1 Y-1.0 E-3.0 F50", >>> "G1 E-2.0 F50" >>> ] """ # Return a copy of the commands. return deepcopy(self["docking_commands"])GCODE command list for the docking sequence This command is run after placing the empty toolhead at the parking post's coordinates. It starts at those coordinates and must end with a correctly docked tool. Example:
>>> [ >>> "T0", >>> "G91", >>> "M83", >>> "G1 Y-1.0 E-1.0 F100", >>> "G1 Y-1.0 E-3.0 F50", >>> "G1 E-2.0 F50" >>> ] prop homing-
Expand source code
@property def homing(self): """Homing parameters and commands for the pipette. Example: { "commands": [ "T2", "HOME_EXTRUDER EXTRUDER=extruder2", "G90", "M82", "G1 E0 F6000; Total free motion minus 'travel_distance' below." ], "commands.desc": "Homing commands, leaving the pipette at the maximum volume.", "travel_distance": 30, "travel_distance.desc": "Free travel distance after homing, to the lowest volume position." } """ return deepcopy(self["homing"])Homing parameters and commands for the pipette. Example: { "commands": [ "T2", "HOME_EXTRUDER EXTRUDER=extruder2", "G90", "M82", "G1 E0 F6000; Total free motion minus 'travel_distance' below." ], "commands.desc": "Homing commands, leaving the pipette at the maximum volume.", "travel_distance": 30, "travel_distance.desc": "Free travel distance after homing, to the lowest volume position." }
prop parking_commands-
Expand source code
@property def parking_commands(self): """GCODE command list for the undocking sequence This command is run after placing the tool at the parking post's coordinates. It starts at those coordinates and must end with a free and correctly parked tool. Example: >>> [ >>> "T0", >>> "G91", >>> "M83", >>> "G1 Y4 E6 F100", >>> "G1 Y5 F500" >>> ] """ # Return a copy of the commands. return deepcopy(self["parking_commands"])GCODE command list for the undocking sequence This command is run after placing the tool at the parking post's coordinates. It starts at those coordinates and must end with a free and correctly parked tool. Example:
>>> [ >>> "T0", >>> "G91", >>> "M83", >>> "G1 Y4 E6 F100", >>> "G1 Y5 F500" >>> ] prop safe_x-
Expand source code
@property def safe_x(self): """Distance along the X direction that this tool adds to the sides of the toolhead. Used to avoid collisions with the machine, parked tools, and such. Use the largest value if the tool is not symmetric.""" return self.clearance["safe_x"]Distance along the X direction that this tool adds to the sides of the toolhead. Used to avoid collisions with the machine, parked tools, and such. Use the largest value if the tool is not symmetric.
prop safe_y-
Expand source code
@property def safe_y(self): """Distance along the Y direction that this tool adds to the front of the toolhead. Used to avoid collisions with the machine, parked tools, and such.""" return self.clearance["safe_y"]Distance along the Y direction that this tool adds to the front of the toolhead. Used to avoid collisions with the machine, parked tools, and such.
prop safe_y_loaded-
Expand source code
@property def safe_y_loaded(self): """Y coordinate at which the tool is barely clear of its (empty) parking post. Derived from "safe_y" and "safe_y_post". Useful for toolchanging. """ # NOTE: Corrected for axis inversion. return self.safe_y_post + self.safe_yY coordinate at which the tool is barely clear of its (empty) parking post. Derived from "safe_y" and "safe_y_post". Useful for toolchanging.
prop safe_y_parked-
Expand source code
@property def safe_y_parked(self): """Minimum Y coordinate at which this tool is found when properly parked. Used to avoid collisions with the machine, parked tools, and such. """ # NOTE: The post might protrude further than the tool itself. # Use the minimum of the two, if available. safe_y_post = self.safe_y_post safe_y_parked = self.clearance["safe_y_parked"] # Choose the most conservative clearance. if safe_y_post is not None and safe_y_parked is not None: # NOTE: Corrected for axis inversion. safe_y_parked = max(safe_y_parked, safe_y_post) return safe_y_parkedMinimum Y coordinate at which this tool is found when properly parked. Used to avoid collisions with the machine, parked tools, and such.
prop safe_y_post-
Expand source code
@property def safe_y_post(self): """Minimum Y coordinate at which the parking post is found when empty. This will tipically depend on the length of pins in the parking post. Used to avoid collisions with the machine, parked tools, and such. """ return self.clearance["safe_y_post"]Minimum Y coordinate at which the parking post is found when empty. This will tipically depend on the length of pins in the parking post. Used to avoid collisions with the machine, parked tools, and such.
prop tool_offset-
Expand source code
@property def tool_offset(self): """Tool offset parameters for the active center of the tool.""" return deepcopy(self["tool_offset"])Tool offset parameters for the active center of the tool.
prop tool_post-
Expand source code
@property def tool_post(self): """Coordinates of the tool-post. Example values: - x: 16.50, - y: 280, - z: 19 - y_docking_closeup: 2 - z_docking_offset: 0.0 - y_parking_closeup: 4 - z_parking_offset: 1.0 - feedrate: 500 """ return deepcopy(self["tool_post"])Coordinates of the tool-post. Example values: - x: 16.50, - y: 280, - z: 19 - y_docking_closeup: 2 - z_docking_offset: 0.0 - y_parking_closeup: 4 - z_parking_offset: 1.0 - feedrate: 500
prop x_offset-
Expand source code
@property def x_offset(self): """X offset for the tool.""" x = float(self.tool_offset["x"]) return xX offset for the tool.
prop y_offset-
Expand source code
@property def y_offset(self): """Y offset for the tool.""" y = float(self.tool_offset["y"]) return yY offset for the tool.
prop z_offset-
Expand source code
@property def z_offset(self): """Z offset for the tool.""" z = float(self.tool_offset["z"]) return zZ offset for the tool.
Methods
def gcode_activate(self) ‑> list-
Expand source code
def gcode_activate(self) -> list: """Signal the firmware to activate this tool. This usually means sending gcode commands equivalent to T0, T1, etc. which may switch the active extruder. The need for it is really up to the firmware and the machine. Example: - "activate": "T1" """ return deepcopy(self["activate"])Signal the firmware to activate this tool. This usually means sending gcode commands equivalent to T0, T1, etc. which may switch the active extruder. The need for it is really up to the firmware and the machine. Example: - "activate": "T1"
def gcode_align(self, y, z) ‑> list-
Expand source code
def gcode_align(self, y, z) -> list: """Make G1 GCODE for fine alignment to a tool-post. Moves first in Y (fine and slow approach) and then in Z (wobble compensation). """ # Slow feedrates for the moves. tc_feedrate = self.tool_post["feedrate"] # Initial docking moves. commands = [self.gcode.comment("Alignment moves.")] commands += self.gcode.G1( y=y, f=tc_feedrate, add_comment="Partial approach for initial alignment.") commands += self.gcode.G1( z=z, f=tc_feedrate, add_comment="Compensate Z for looseness in the parking post.") return commandsMake G1 GCODE for fine alignment to a tool-post. Moves first in Y (fine and slow approach) and then in Z (wobble compensation).
def gcode_approach(self, x, y, z) ‑> list-
Expand source code
def gcode_approach(self, x, y, z) -> list: """Make G1 GCODE to move in front of a tool-post. Moves first in X, then in YZ. """ # Start sequence: approach the tool. commands = [self.gcode.comment("Alignment moves.")] commands += self.gcode.G0( x=x, add_comment="Move to the X position in front of the parking post.") commands += self.gcode.G0( y=y, z=z, add_comment="Move to the Y-Z position in front of the parking post/tool.") return commandsMake G1 GCODE to move in front of a tool-post. Moves first in X, then in YZ.
def gcode_deactivate(self) ‑> list-
Expand source code
def gcode_deactivate(self) -> list: """Signal the firmware to deactivate this tool. Example: - "deactivate": "; dummy deactivate." """ return deepcopy(self["deactivate"])Signal the firmware to deactivate this tool. Example: - "deactivate": "; dummy deactivate."
def gcode_dock(self, commands: list = None) ‑> list-
Expand source code
def gcode_dock(self, commands: list = None) -> list: """Generate GCODE to dock a tool (assuming none is loaded). Required tool parameters: - tool_post: coordinates and offsets of the tool post. - docking_commands: list of GCODE commands. - parking_commands: list of GCODE commands. Returns: list: List of GCODES for docking. """ if commands is None: commands = [] # Initialize commands list. commands += [self.gcode.comment("START tool-change pickup macro.")] # Clearance moves. commands += self.gcode_safe_zy() # Parked tool coordinates. x, y, z = self.tool_post["x"], self.tool_post["y"], self.tool_post["z"] # Extra Y-axis distance needed to approach and touch the tool. y_docking_closeup = self.tool_post["y_docking_closeup"] # Extra Z-axis distance used to help align the tool. z_offset = self.tool_post["z_docking_offset"] # Initial approach. commands += self.gcode_approach(x, self.safe_y_parked, z+z_offset) # Closeup move. commands += self.gcode_approach(x, y+y_docking_closeup, z+z_offset) # Final approach moves. commands += self.gcode_align(y=y, z=z+z_offset) # Tool-changer commands to lock the tool to the toolhead. commands += [self.gcode.comment("Locking moves.")] commands += self.docking_commands # End sequence. commands += self.gcode.G0( y=self.safe_y_loaded, absolute=True, add_comment="Move back to the Y coordinate clear of other parked tools.") # Done. commands += [self.gcode.comment("END tool-change macro.")] return commandsGenerate GCODE to dock a tool (assuming none is loaded).
Required tool parameters: - tool_post: coordinates and offsets of the tool post. - docking_commands: list of GCODE commands. - parking_commands: list of GCODE commands.
Returns
list- List of GCODES for docking.
def gcode_park(self, commands: list = None) ‑> list-
Expand source code
def gcode_park(self, commands: list = None) -> list: """Generate GCODE to park a tool (assuming none is loaded). TODO: Document this function. See 'gcode_dock' for deatils for now. """ if commands is None: commands = [] # Initialize commands list. commands += [self.gcode.comment("START tool-change parking macro.")] # Clearance moves. commands += self.gcode_safe_zy() # Parking post coordinates. x, y, z = self.tool_post["x"], self.tool_post["y"], self.tool_post["z"] # Extra Y-axis distance needed to approach the post and hang the tool. y_parking_closeup = self.tool_post["y_parking_closeup"] # Extra Z-axis distance used to help align the tool. z_offset = self.tool_post["z_parking_offset"] # TODO: This clearance move may be trivial. Consider removing. commands += self.gcode_approach(x, self.safe_y_loaded, z+z_offset) # Initial approach. commands += self.gcode_approach(x, y, z+z_offset) # Final approach moves. # NOTE: Corrected for axis inversion. commands += self.gcode_align(y=y-y_parking_closeup, z=z+z_offset) # Tool-changer commands to release the tool from the toolhead. commands += [self.gcode.comment("Unlocking moves.")] commands += self.parking_commands # End sequence. commands += self.gcode.G0( y=self.safe_y_parked, absolute=True, add_comment="Move back to the Y coordinate clear of other parked tools.") # Done. commands += [self.gcode.comment("END tool-change macro.")] return commandsGenerate GCODE to park a tool (assuming none is loaded). TODO: Document this function. See 'gcode_dock' for deatils for now.
def gcode_post_front(self, commands: list = None) ‑> list-
Expand source code
def gcode_post_front(self, commands: list = None) -> list: """Generate GCODE to approack a parking post (assuming none is loaded).""" if commands is None: commands = [] # Initialize commands list. commands += [self.gcode.comment("Moving in front of parking post.")] # Clearance moves. commands += self.gcode_safe_zy() # Parked tool coordinates. x, _, z = self.tool_post["x"], self.tool_post["y"], self.tool_post["z"] # Extra Z-axis distance used to help align the tool. z_offset = self.tool_post["z_docking_offset"] # Initial approach. commands += self.gcode_approach(x, self.safe_y_parked, z+z_offset) # Done. commands += [self.gcode.comment("Done moving in front of parking post.")] return commandsGenerate GCODE to approack a parking post (assuming none is loaded).
def gcode_safe_zy(self, commands: list = None) ‑> list-
Expand source code
def gcode_safe_zy(self, commands: list = None) -> list: """Make G1 GCODE commands to move to safe Z and Y coordinates, in that order.""" # Use a new command list if none was provided. if commands is None: commands = [] # Get the absolute clearance coordinates safe_z = self.builder.getSafeHeight() safe_y = self.builder.getSafeY() # Clearance moves. commands += [self.gcode.comment("Safety clearance moves."), "G90" + self.gcode.comment("Set absolute motion mode.")] commands += self.gcode.gcodeClearance( z=safe_z, f=self.builder.feedrate, add_comment="Move to the safe Z height.") commands += self.gcode.G0( y=safe_y, add_comment="Move to the Y coordinate clear of other parked tools.") return commandsMake G1 GCODE commands to move to safe Z and Y coordinates, in that order.
def home(self) ‑> list-
Expand source code
def home(self) -> list: """Generate GCODE commands needed to home this tool. Then update the internal state accordingly. """ logging.warning(f"Placeholder homing routine for tool '{self.name}'.") # Build the command if not self.controller.machine.dry: logging.info(f"Homing tool '{self.name}'.") homing_commands = self.homing['commands'] else: # Dry mode: msg = f"Simulating homed state for tool '{self.name}'. Dry mode enabled." logging.info(msg) homing_commands = [self.gcode.comment(msg)] return homing_commandsGenerate GCODE commands needed to home this tool. Then update the internal state accordingly.
def park(self, old_tool, new_tool) ‑> list-
Expand source code
def park(self, old_tool, new_tool) -> list: """Park a tool. Assumes that the tool is mounted, and that its parking post is empty. """ # Generate commands for the pickup. parking_commands = self.gcode_park() # Mark the tool as parked. self.parked = True # TODO: Is a "deactivate" function needed for the pipettes? parking_commands += self.gcode_deactivate() return parking_commandsPark a tool. Assumes that the tool is mounted, and that its parking post is empty.
def pickup(self, old_tool, new_tool) ‑> list-
Expand source code
def pickup(self, old_tool, new_tool) -> list: """Pickup a tool. Assumes an empty toolhead, and a tool in its parking post. """ commands = [self.gcode.comment(f"Picking up {self.name}.")] # Home tool axis. # NOTE: Homing should be overriden by child classes. # See the "Pipette" class. # Home the toolchanger remote. # TODO: "gcodeHomeP" is largely deprecated. Replace it. commands += self.gcode.gcodeHomeP(cmd_prefix="HOME_", which_axis="E0") # Generate commands for the pickup. commands += self.gcode_dock() # Mark the tool as not parked. self.parked = False # Mark a toolchange / activate the new tool. commands += self.gcode_activate() return commandsPickup a tool. Assumes an empty toolhead, and a tool in its parking post.
Inherited members