From 0c5c87d7c08c2b8aa2f248d81dd1003c5c7d5a7c Mon Sep 17 00:00:00 2001 From: Andrew Mirsky Date: Tue, 22 Nov 2022 23:22:46 -0500 Subject: [PATCH] gcode_arcs: support XY, XZ and YZ planes add G17, G18 and G19 commands to select arc planes enhance G2/G3 to support arc moves in XY, XZ and YZ planes Signed-off-by: Andrew Mirsky --- docs/G-Codes.md | 5 +- klippy/extras/gcode_arcs.py | 101 ++++++++++++++++++++++++++---------- test/klippy/gcode_arcs.test | 23 +++++++- 3 files changed, 98 insertions(+), 31 deletions(-) diff --git a/docs/G-Codes.md b/docs/G-Codes.md index d9cefd80..137d14ba 100644 --- a/docs/G-Codes.md +++ b/docs/G-Codes.md @@ -549,8 +549,9 @@ clears any error state from the micro-controller. The following standard G-Code commands are available if a [gcode_arcs config section](Config_Reference.md#gcode_arcs) is enabled: -- Controlled Arc Move (G2 or G3): `G2 [X] [Y] [Z] - [E] [F] I J` +- Arc Move Clockwise (G2), Arc Move Counter-clockwise (G3): `G2|G3 [X] [Y] [Z] + [E] [F] I J|I K|J K` +- Arc Plane Select: G17 (XY plane), G18 (XZ plane), G19 (YZ plane) ### [gcode_macro] diff --git a/klippy/extras/gcode_arcs.py b/klippy/extras/gcode_arcs.py index 61fa7234..eac89050 100644 --- a/klippy/extras/gcode_arcs.py +++ b/klippy/extras/gcode_arcs.py @@ -7,12 +7,25 @@ # # This file may be distributed under the terms of the GNU GPLv3 license. import math - +from gcode import Coord # Coordinates created by this are converted into G1 commands. # -# note: only IJ version available +# supports XY, XZ & YZ planes with remaining axis as helical + +# Enum +ARC_PLANE_X_Y = 0 +ARC_PLANE_X_Z = 1 +ARC_PLANE_Y_Z = 2 + +# Enum +X_AXIS = 0 +Y_AXIS = 1 +Z_AXIS = 2 +E_AXIS = 3 + class ArcSupport: + def __init__(self, config): self.printer = config.get_printer() self.mm_per_arc_segment = config.getfloat('resolution', 1., above=0.0) @@ -22,12 +35,28 @@ class ArcSupport: self.gcode.register_command("G2", self.cmd_G2) self.gcode.register_command("G3", self.cmd_G3) + self.gcode.register_command("G17", self.cmd_G17) + self.gcode.register_command("G18", self.cmd_G18) + self.gcode.register_command("G19", self.cmd_G19) + + # backwards compatibility, prior implementation only supported XY + self.plane = ARC_PLANE_X_Y + def cmd_G2(self, gcmd): self._cmd_inner(gcmd, True) def cmd_G3(self, gcmd): self._cmd_inner(gcmd, False) + def cmd_G17(self, gcmd): + self.plane = ARC_PLANE_X_Y + + def cmd_G18(self, gcmd): + self.plane = ARC_PLANE_X_Z + + def cmd_G19(self, gcmd): + self.plane = ARC_PLANE_Y_Z + def _cmd_inner(self, gcmd, clockwise): gcodestatus = self.gcode_move.get_status() if not gcodestatus['absolute_coordinates']: @@ -35,21 +64,33 @@ class ArcSupport: currentPos = gcodestatus['gcode_position'] # Parse parameters - asX = gcmd.get_float("X", currentPos[0]) - asY = gcmd.get_float("Y", currentPos[1]) - asZ = gcmd.get_float("Z", currentPos[2]) + asTarget = Coord(x=gcmd.get_float("X", currentPos[0]), + y=gcmd.get_float("Y", currentPos[1]), + z=gcmd.get_float("Z", currentPos[2]), + e=None) + if gcmd.get_float("R", None) is not None: raise gcmd.error("G2/G3 does not support R moves") - asI = gcmd.get_float("I", 0.) - asJ = gcmd.get_float("J", 0.) - if not asI and not asJ: - raise gcmd.error("G2/G3 neither I nor J given") + + # determine the plane coordinates and the helical axis + asPlanar = [ gcmd.get_float(a, 0.) for i,a in enumerate('IJ') ] + axes = (X_AXIS, Y_AXIS, Z_AXIS) + if self.plane == ARC_PLANE_X_Z: + asPlanar = [ gcmd.get_float(a, 0.) for i,a in enumerate('IK') ] + axes = (X_AXIS, Z_AXIS, Y_AXIS) + elif self.plane == ARC_PLANE_Y_Z: + asPlanar = [ gcmd.get_float(a, 0.) for i,a in enumerate('JK') ] + axes = (Y_AXIS, Z_AXIS, X_AXIS) + + if not asPlanar[0] or not asPlanar[1]: + raise gcmd.error("G2/G3 requires IJ, IK or JK parameters") + asE = gcmd.get_float("E", None) asF = gcmd.get_float("F", None) - # Build list of linear coordinates to move to - coords = self.planArc(currentPos, [asX, asY, asZ], [asI, asJ], - clockwise) + # Build list of linear coordinates to move + coords = self.planArc(currentPos, asTarget, asPlanar, + clockwise, *axes) e_per_move = e_base = 0. if asE is not None: if gcodestatus['absolute_extrude']: @@ -74,37 +115,37 @@ class ArcSupport: # The arc is approximated by generating many small linear segments. # The length of each segment is configured in MM_PER_ARC_SEGMENT # Arcs smaller then this value, will be a Line only - def planArc(self, currentPos, targetPos, offset, clockwise): + # + # alpha and beta axes are the current plane, helical axis is linear travel + def planArc(self, currentPos, targetPos, offset, clockwise, + alpha_axis, beta_axis, helical_axis): # todo: sometimes produces full circles - X_AXIS = 0 - Y_AXIS = 1 - Z_AXIS = 2 # Radius vector from center to current location r_P = -offset[0] r_Q = -offset[1] # Determine angular travel - center_P = currentPos[X_AXIS] - r_P - center_Q = currentPos[Y_AXIS] - r_Q - rt_X = targetPos[X_AXIS] - center_P - rt_Y = targetPos[Y_AXIS] - center_Q - angular_travel = math.atan2(r_P * rt_Y - r_Q * rt_X, - r_P * rt_X + r_Q * rt_Y) + center_P = currentPos[alpha_axis] - r_P + center_Q = currentPos[beta_axis] - r_Q + rt_Alpha = targetPos[alpha_axis] - center_P + rt_Beta = targetPos[beta_axis] - center_Q + angular_travel = math.atan2(r_P * rt_Beta - r_Q * rt_Alpha, + r_P * rt_Alpha + r_Q * rt_Beta) if angular_travel < 0.: angular_travel += 2. * math.pi if clockwise: angular_travel -= 2. * math.pi if (angular_travel == 0. - and currentPos[X_AXIS] == targetPos[X_AXIS] - and currentPos[Y_AXIS] == targetPos[Y_AXIS]): + and currentPos[alpha_axis] == targetPos[alpha_axis] + and currentPos[beta_axis] == targetPos[beta_axis]): # Make a circle if the angular rotation is 0 and the # target is current position angular_travel = 2. * math.pi # Determine number of segments - linear_travel = targetPos[Z_AXIS] - currentPos[Z_AXIS] + linear_travel = targetPos[helical_axis] - currentPos[helical_axis] radius = math.hypot(r_P, r_Q) flat_mm = radius * angular_travel if linear_travel: @@ -118,14 +159,18 @@ class ArcSupport: linear_per_segment = linear_travel / segments coords = [] for i in range(1, int(segments)): - dist_Z = i * linear_per_segment + dist_Helical = i * linear_per_segment cos_Ti = math.cos(i * theta_per_segment) sin_Ti = math.sin(i * theta_per_segment) r_P = -offset[0] * cos_Ti + offset[1] * sin_Ti r_Q = -offset[0] * sin_Ti - offset[1] * cos_Ti - c = [center_P + r_P, center_Q + r_Q, currentPos[Z_AXIS] + dist_Z] - coords.append(c) + # Coord doesn't support index assignment, create list + c = [None, None, None, None] + c[alpha_axis] = center_P + r_P + c[beta_axis] = center_Q + r_Q + c[helical_axis] = currentPos[helical_axis] + dist_Helical + coords.append(Coord(*c)) coords.append(targetPos) return coords diff --git a/test/klippy/gcode_arcs.test b/test/klippy/gcode_arcs.test index 2be4efc9..6f9a7e3a 100644 --- a/test/klippy/gcode_arcs.test +++ b/test/klippy/gcode_arcs.test @@ -2,10 +2,31 @@ DICTIONARY atmega2560.dict CONFIG gcode_arcs.cfg -# Home and move in arcs +# Home and move in XY arc G28 +G90 G1 X20 Y20 Z20 G2 X125 Y32 Z20 E1 I10.5 J10.5 # XY+Z arc move G2 X20 Y20 Z10 E1 I10.5 J10.5 + +# Home and move in XZ arc +G28 +G90 +G1 X20 Y20 Z20 +G18 +G2 X125 Y20 Z32 E1 I10.5 K10.5 + +# XZ+Y arc move +G2 X20 Y10 Z20 E1 I10.5 K10.5 + +# Home and move in YZ arc +G28 +G90 +G1 X20 Y20 Z20 +G19 +G2 X20 Y125 Z32 E1 J10.5 K10.5 + +# YZ+X arc move +G2 X10 Y20 Z20 E1 J10.5 K10.5