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 <andrew@mirsky.net>
This commit is contained in:
Andrew Mirsky 2022-11-22 23:22:46 -05:00 committed by KevinOConnor
parent c7f323e863
commit 0c5c87d7c0
3 changed files with 98 additions and 31 deletions

View File

@ -549,8 +549,9 @@ clears any error state from the micro-controller.
The following standard G-Code commands are available if a The following standard G-Code commands are available if a
[gcode_arcs config section](Config_Reference.md#gcode_arcs) is [gcode_arcs config section](Config_Reference.md#gcode_arcs) is
enabled: enabled:
- Controlled Arc Move (G2 or G3): `G2 [X<pos>] [Y<pos>] [Z<pos>] - Arc Move Clockwise (G2), Arc Move Counter-clockwise (G3): `G2|G3 [X<pos>] [Y<pos>] [Z<pos>]
[E<pos>] [F<speed>] I<value> J<value>` [E<pos>] [F<speed>] I<value> J<value>|I<value> K<value>|J<value> K<value>`
- Arc Plane Select: G17 (XY plane), G18 (XZ plane), G19 (YZ plane)
### [gcode_macro] ### [gcode_macro]

View File

@ -7,12 +7,25 @@
# #
# This file may be distributed under the terms of the GNU GPLv3 license. # This file may be distributed under the terms of the GNU GPLv3 license.
import math import math
from gcode import Coord
# Coordinates created by this are converted into G1 commands. # 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: class ArcSupport:
def __init__(self, config): def __init__(self, config):
self.printer = config.get_printer() self.printer = config.get_printer()
self.mm_per_arc_segment = config.getfloat('resolution', 1., above=0.0) 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("G2", self.cmd_G2)
self.gcode.register_command("G3", self.cmd_G3) 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): def cmd_G2(self, gcmd):
self._cmd_inner(gcmd, True) self._cmd_inner(gcmd, True)
def cmd_G3(self, gcmd): def cmd_G3(self, gcmd):
self._cmd_inner(gcmd, False) 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): def _cmd_inner(self, gcmd, clockwise):
gcodestatus = self.gcode_move.get_status() gcodestatus = self.gcode_move.get_status()
if not gcodestatus['absolute_coordinates']: if not gcodestatus['absolute_coordinates']:
@ -35,21 +64,33 @@ class ArcSupport:
currentPos = gcodestatus['gcode_position'] currentPos = gcodestatus['gcode_position']
# Parse parameters # Parse parameters
asX = gcmd.get_float("X", currentPos[0]) asTarget = Coord(x=gcmd.get_float("X", currentPos[0]),
asY = gcmd.get_float("Y", currentPos[1]) y=gcmd.get_float("Y", currentPos[1]),
asZ = gcmd.get_float("Z", currentPos[2]) z=gcmd.get_float("Z", currentPos[2]),
e=None)
if gcmd.get_float("R", None) is not None: if gcmd.get_float("R", None) is not None:
raise gcmd.error("G2/G3 does not support R moves") raise gcmd.error("G2/G3 does not support R moves")
asI = gcmd.get_float("I", 0.)
asJ = gcmd.get_float("J", 0.) # determine the plane coordinates and the helical axis
if not asI and not asJ: asPlanar = [ gcmd.get_float(a, 0.) for i,a in enumerate('IJ') ]
raise gcmd.error("G2/G3 neither I nor J given") 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) asE = gcmd.get_float("E", None)
asF = gcmd.get_float("F", None) asF = gcmd.get_float("F", None)
# Build list of linear coordinates to move to # Build list of linear coordinates to move
coords = self.planArc(currentPos, [asX, asY, asZ], [asI, asJ], coords = self.planArc(currentPos, asTarget, asPlanar,
clockwise) clockwise, *axes)
e_per_move = e_base = 0. e_per_move = e_base = 0.
if asE is not None: if asE is not None:
if gcodestatus['absolute_extrude']: if gcodestatus['absolute_extrude']:
@ -74,37 +115,37 @@ class ArcSupport:
# The arc is approximated by generating many small linear segments. # The arc is approximated by generating many small linear segments.
# The length of each segment is configured in MM_PER_ARC_SEGMENT # The length of each segment is configured in MM_PER_ARC_SEGMENT
# Arcs smaller then this value, will be a Line only # 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 # todo: sometimes produces full circles
X_AXIS = 0
Y_AXIS = 1
Z_AXIS = 2
# Radius vector from center to current location # Radius vector from center to current location
r_P = -offset[0] r_P = -offset[0]
r_Q = -offset[1] r_Q = -offset[1]
# Determine angular travel # Determine angular travel
center_P = currentPos[X_AXIS] - r_P center_P = currentPos[alpha_axis] - r_P
center_Q = currentPos[Y_AXIS] - r_Q center_Q = currentPos[beta_axis] - r_Q
rt_X = targetPos[X_AXIS] - center_P rt_Alpha = targetPos[alpha_axis] - center_P
rt_Y = targetPos[Y_AXIS] - center_Q rt_Beta = targetPos[beta_axis] - center_Q
angular_travel = math.atan2(r_P * rt_Y - r_Q * rt_X, angular_travel = math.atan2(r_P * rt_Beta - r_Q * rt_Alpha,
r_P * rt_X + r_Q * rt_Y) r_P * rt_Alpha + r_Q * rt_Beta)
if angular_travel < 0.: if angular_travel < 0.:
angular_travel += 2. * math.pi angular_travel += 2. * math.pi
if clockwise: if clockwise:
angular_travel -= 2. * math.pi angular_travel -= 2. * math.pi
if (angular_travel == 0. if (angular_travel == 0.
and currentPos[X_AXIS] == targetPos[X_AXIS] and currentPos[alpha_axis] == targetPos[alpha_axis]
and currentPos[Y_AXIS] == targetPos[Y_AXIS]): and currentPos[beta_axis] == targetPos[beta_axis]):
# Make a circle if the angular rotation is 0 and the # Make a circle if the angular rotation is 0 and the
# target is current position # target is current position
angular_travel = 2. * math.pi angular_travel = 2. * math.pi
# Determine number of segments # 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) radius = math.hypot(r_P, r_Q)
flat_mm = radius * angular_travel flat_mm = radius * angular_travel
if linear_travel: if linear_travel:
@ -118,14 +159,18 @@ class ArcSupport:
linear_per_segment = linear_travel / segments linear_per_segment = linear_travel / segments
coords = [] coords = []
for i in range(1, int(segments)): 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) cos_Ti = math.cos(i * theta_per_segment)
sin_Ti = math.sin(i * theta_per_segment) sin_Ti = math.sin(i * theta_per_segment)
r_P = -offset[0] * cos_Ti + offset[1] * sin_Ti r_P = -offset[0] * cos_Ti + offset[1] * sin_Ti
r_Q = -offset[0] * sin_Ti - offset[1] * cos_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] # Coord doesn't support index assignment, create list
coords.append(c) 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) coords.append(targetPos)
return coords return coords

View File

@ -2,10 +2,31 @@
DICTIONARY atmega2560.dict DICTIONARY atmega2560.dict
CONFIG gcode_arcs.cfg CONFIG gcode_arcs.cfg
# Home and move in arcs # Home and move in XY arc
G28 G28
G90
G1 X20 Y20 Z20 G1 X20 Y20 Z20
G2 X125 Y32 Z20 E1 I10.5 J10.5 G2 X125 Y32 Z20 E1 I10.5 J10.5
# XY+Z arc move # XY+Z arc move
G2 X20 Y20 Z10 E1 I10.5 J10.5 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