simplify canned drilling processes

This commit is contained in:
Peter Boin 2017-07-18 12:59:15 +10:00
parent b741603655
commit 9ca819d8c6
7 changed files with 272 additions and 28 deletions

View File

@ -475,42 +475,42 @@ class GCodeDrillingCycle(GCodeCannedCycle):
"""G81: Drilling Cycle""" """G81: Drilling Cycle"""
param_letters = GCodeCannedCycle.param_letters | set('RLP') param_letters = GCodeCannedCycle.param_letters | set('RLP')
word_key = Word('G', 81) word_key = Word('G', 81)
modal_param_letters = GCodeCannedCycle.param_letters | set('RLP') modal_param_letters = GCodeCannedCycle.param_letters | set('RP')
class GCodeDrillingCycleDwell(GCodeCannedCycle): class GCodeDrillingCycleDwell(GCodeCannedCycle):
"""G82: Drilling Cycle, Dwell""" """G82: Drilling Cycle, Dwell"""
param_letters = GCodeCannedCycle.param_letters | set('RLP') param_letters = GCodeCannedCycle.param_letters | set('RLP')
word_key = Word('G', 82) word_key = Word('G', 82)
modal_param_letters = GCodeCannedCycle.param_letters | set('RLP') modal_param_letters = GCodeCannedCycle.param_letters | set('RP')
class GCodeDrillingCyclePeck(GCodeCannedCycle): class GCodeDrillingCyclePeck(GCodeCannedCycle):
"""G83: Drilling Cycle, Peck""" """G83: Drilling Cycle, Peck"""
param_letters = GCodeCannedCycle.param_letters | set('RLQ') param_letters = GCodeCannedCycle.param_letters | set('RLQ')
word_key = Word('G', 83) word_key = Word('G', 83)
modal_param_letters = GCodeCannedCycle.param_letters | set('RLQ') modal_param_letters = GCodeCannedCycle.param_letters | set('RQ')
class GCodeDrillingCycleChipBreaking(GCodeCannedCycle): class GCodeDrillingCycleChipBreaking(GCodeCannedCycle):
"""G73: Drilling Cycle, ChipBreaking""" """G73: Drilling Cycle, ChipBreaking"""
param_letters = GCodeCannedCycle.param_letters | set('RLQ') param_letters = GCodeCannedCycle.param_letters | set('RLQ')
word_key = Word('G', 73) word_key = Word('G', 73)
modal_param_letters = GCodeCannedCycle.param_letters | set('RLQ') modal_param_letters = GCodeCannedCycle.param_letters | set('RQ')
class GCodeBoringCycleFeedOut(GCodeCannedCycle): class GCodeBoringCycleFeedOut(GCodeCannedCycle):
"""G85: Boring Cycle, Feed Out""" """G85: Boring Cycle, Feed Out"""
param_letters = GCodeCannedCycle.param_letters | set('RLP') param_letters = GCodeCannedCycle.param_letters | set('RLP')
word_key = Word('G', 85) word_key = Word('G', 85)
modal_param_letters = GCodeCannedCycle.param_letters | set('RLP') modal_param_letters = GCodeCannedCycle.param_letters | set('RP')
class GCodeBoringCycleDwellFeedOut(GCodeCannedCycle): class GCodeBoringCycleDwellFeedOut(GCodeCannedCycle):
"""G89: Boring Cycle, Dwell, Feed Out""" """G89: Boring Cycle, Dwell, Feed Out"""
param_letters = GCodeCannedCycle.param_letters | set('RLP') param_letters = GCodeCannedCycle.param_letters | set('RLP')
word_key = Word('G', 89) word_key = Word('G', 89)
modal_param_letters = GCodeCannedCycle.param_letters | set('RLP') modal_param_letters = GCodeCannedCycle.param_letters | set('RP')
class GCodeThreadingCycle(GCodeCannedCycle): class GCodeThreadingCycle(GCodeCannedCycle):
@ -895,8 +895,8 @@ class GCodePathBlendingMode(GCodePathControlMode):
# ======================= Return Mode in Canned Cycles ======================= # ======================= Return Mode in Canned Cycles =======================
# CODE PARAMETERS DESCRIPTION # CODE PARAMETERS DESCRIPTION
# G98 Canned Cycle Return Level # G98 Canned Cycle Return Level to previous
# G99 Canned Cycle Return to the level set by R
class GCodeCannedReturnMode(GCode): class GCodeCannedReturnMode(GCode):
modal_group = MODAL_GROUP_MAP['canned_cycles_return'] modal_group = MODAL_GROUP_MAP['canned_cycles_return']
@ -1444,3 +1444,73 @@ def split_gcodes(gcode_list, splitter_class, sort_list=True):
split[2] = gcode_list[split_index+1:] split[2] = gcode_list[split_index+1:]
return split return split
def _gcodes_abs2rel(start_pos, dist_mode=None, axes='XYZ'):
"""
Decorator to convert returned motion gcode coordinates to incremental.
Intended to be used internally (mainly because it's a little shonky)
Decorated function is only expected to return GCodeRapidMove or GCodeLinearMove
instances
:param start_pos: starting machine position (Position)
:param dist_mode: machine's distance mode (GCodeAbsoluteDistanceMode or GCodeIncrementalDistanceMode)
:param axes: axes machine accepts (set)
"""
# Usage:
# m = Machine() # defaults to absolute distance mode
# m.process_gcodes(GCodeRapidMove(X=10, Y=20, Z=3))
# m.process_gcodes(GCodeIncrementalDistanceMode())
#
# @_gcodes_abs2rel(start_pos=m.pos, dist_mode=m.mode.distance, axes=m.axes)
# def do_stuff():
# yield GCodeRapidMove(X=0, Y=30, Z=3)
# yield GCodeLinearMove(X=0, Y=30, Z=-5)
#
# gcode_list = do_stuff()
# gocde_list[0] # == GCodeRapidMove(X=-10, Y=10)
# gocde_list[1] # == GCodeLinearMove(Z=-8)
SUPPORTED_MOTIONS = (
GCodeRapidMove, GCodeLinearMove,
)
def wrapper(func):
def inner(*largs, **kwargs):
# Create Machine (with minimal information)
from .machine import Machine, Mode, Position
m = type('AbsoluteCoordMachine', (Machine,), {
'MODE_CLASS': type('NullMode', (Mode,), {'default_mode': 'G90'}),
'axes': axes,
})()
m.pos = start_pos
for gcode in func(*largs, **kwargs):
# Verification & passthrough's
if not isinstance(gcode, GCode):
yield gcode # whatever this thing is
else:
# Process gcode
pos_from = m.pos
m.process_gcodes(gcode)
pos_to = m.pos
if gcode.modal_group != MODAL_GROUP_MAP['motion']:
yield gcode # only deal with motion gcodes
continue
elif not isinstance(gcode, SUPPORTED_MOTIONS):
raise NotImplementedError("%r to iterative coords is not supported (this is only a very simple function)" % gcode)
# Convert coordinates to iterative
rel_pos = pos_to - pos_from
coord_words = [w for w in rel_pos.words if w.value]
if coord_words: # else relative coords are all zero; do nothing
yield words2gcodes([gcode.word] + coord_words)[0].pop()
# Return apropriate function
if (dist_mode is None) or isinstance(dist_mode, GCodeIncrementalDistanceMode):
return inner
else:
return func # bypass decorator entirely; nothing to be done
return wrapper

View File

@ -1,10 +1,14 @@
from math import sin, cos, tan, asin, acos, atan2, pi, sqrt, ceil from math import sin, cos, tan, asin, acos, atan2, pi, sqrt, ceil
from .gcodes import GCodeLinearMove from .gcodes import GCodeLinearMove, GCodeRapidMove
from .gcodes import GCodeArcMove, GCodeArcMoveCW, GCodeArcMoveCCW from .gcodes import GCodeArcMove, GCodeArcMoveCW, GCodeArcMoveCCW
from .gcodes import GCodePlaneSelect, GCodeSelectXYPlane, GCodeSelectYZPlane, GCodeSelectZXPlane from .gcodes import GCodePlaneSelect, GCodeSelectXYPlane, GCodeSelectYZPlane, GCodeSelectZXPlane
from .gcodes import GCodeAbsoluteDistanceMode, GCodeIncrementalDistanceMode from .gcodes import GCodeAbsoluteDistanceMode, GCodeIncrementalDistanceMode
from .gcodes import GCodeAbsoluteArcDistanceMode, GCodeIncrementalArcDistanceMode from .gcodes import GCodeAbsoluteArcDistanceMode, GCodeIncrementalArcDistanceMode
from .gcodes import GCodeCannedCycle
from .gcodes import GCodeDrillingCyclePeck, GCodeDrillingCycleDwell, GCodeDrillingCycleChipBreaking
from .gcodes import GCodeCannedReturnMode, GCodeCannedCycleReturnLevel, GCodeCannedCycleReturnToR
from .gcodes import _gcodes_abs2rel
from .machine import Position from .machine import Position
from .exceptions import GCodeParameterError from .exceptions import GCodeParameterError
@ -97,7 +101,7 @@ class ArcLinearizeMethod(object):
self._outer_radius = self.get_outer_radius() self._outer_radius = self.get_outer_radius()
return self._outer_radius return self._outer_radius
# Iter # Vertex Generator
def iter_vertices(self): def iter_vertices(self):
"""Yield absolute (<start vertex>, <end vertex>) for each line for the arc""" """Yield absolute (<start vertex>, <end vertex>) for each line for the arc"""
start_vertex = self.arc_p_start - self.arc_p_center start_vertex = self.arc_p_start - self.arc_p_center
@ -214,6 +218,17 @@ DEFAULT_LA_ARCDISTMODE = GCodeIncrementalArcDistanceMode
def linearize_arc(arc_gcode, start_pos, plane=None, method_class=None, def linearize_arc(arc_gcode, start_pos, plane=None, method_class=None,
dist_mode=None, arc_dist_mode=None, dist_mode=None, arc_dist_mode=None,
max_error=0.01, decimal_places=3): max_error=0.01, decimal_places=3):
"""
Convert a G2,G3 arc into a series of approsimation G1 codes
:param arc_gcode: arc gcode to approximate (GCodeArcMove)
:param start_pos: current machine position (Position)
:param plane: machine's active plane (GCodePlaneSelect)
:param method_class: method of linear approximation (ArcLinearizeMethod)
:param dist_mode: machine's distance mode (GCodeAbsoluteDistanceMode or GCodeIncrementalDistanceMode)
:param arc_dist_mode: machine's arc distance mode (GCodeAbsoluteArcDistanceMode or GCodeIncrementalArcDistanceMode)
:param max_error: maximum distance approximation arcs can stray from original arc (float)
:param decimal_places: number of decimal places gocde will be rounded to, used to mitigate risks of accumulated eror when in incremental distance mode (int)
"""
# set defaults # set defaults
if method_class is None: if method_class is None:
method_class = DEFAULT_LA_method_class method_class = DEFAULT_LA_method_class
@ -362,4 +377,109 @@ def linearize_arc(arc_gcode, start_pos, plane=None, method_class=None,
cur_pos += l_delta # mitigate errors by also adding them the accumulated cur_pos cur_pos += l_delta # mitigate errors by also adding them the accumulated cur_pos
# ==================== Arc Precision Adjustment ==================== # ==================== Un-Canning ====================
DEFAULT_SCC_PLANE = GCodeSelectXYPlane
DEFAULT_SCC_DISTMODE = GCodeAbsoluteDistanceMode
DEFAULT_SCC_RETRACTMODE = GCodeCannedCycleReturnLevel
def simplify_canned_cycle(canned_gcode, start_pos,
plane=None, dist_mode=None, retract_mode=None,
axes='XYZ'):
"""
Simplify canned cycle into it's basic linear components
:param canned_gcode: canned gcode to be simplified (GCodeCannedCycle)
:param start_pos: current machine position (Position)
:param plane: machine's active plane (GCodePlaneSelect)
:param dist_mode: machine's distance mode (GCodeAbsoluteDistanceMode or GCodeIncrementalDistanceMode)
:param axes: axes machine accepts (set)
"""
# set defaults
if plane is None:
plane = DEFAULT_SCC_PLANE()
if dist_mode is None:
dist_mode = DEFAULT_SCC_DISTMODE()
if retract_mode is None:
retract_mode = DEFAULT_SCC_RETRACTMODE()
# Parameter Type Assertions
assert isinstance(canned_gcode, GCodeCannedCycle), "bad canned_gcode type: %r" % canned_gcode
assert isinstance(start_pos, Position), "bad start_pos type: %r" % start_pos
assert isinstance(plane, GCodePlaneSelect), "bad plane type: %r" % plane
assert isinstance(dist_mode, (GCodeAbsoluteDistanceMode, GCodeIncrementalDistanceMode)), "bad dist_mode type: %r" % dist_mode
assert isinstance(retract_mode, GCodeCannedReturnMode), "bad retract_mode type: %r" % retract_mode
# TODO: implement for planes other than XY
if not isinstance(plane, GCodeSelectXYPlane):
raise NotImplementedError("simplifying canned cycles for planes other than X/Y has not been implemented")
@_gcodes_abs2rel(start_pos=start_pos, dist_mode=dist_mode, axes=axes)
def inner():
cycle_count = 1 if (canned_gcode.L is None) else canned_gcode.L
cur_hole_p_axis = start_pos.vector
for i in range(cycle_count):
# Calculate Depths
if isinstance(dist_mode, GCodeAbsoluteDistanceMode):
retract_depth = canned_gcode.R
drill_depth = canned_gcode.Z
cur_hole_p_axis = Vector3(x=canned_gcode.X, y=canned_gcode.Y)
else: # incremental
retract_depth = start_pos.Z + canned_gcode.R
drill_depth = retract_depth + canned_gcode.Z
cur_hole_p_axis += Vector3(x=canned_gcode.X, y=canned_gcode.Y)
if retract_depth < drill_depth:
raise NotImplementedError("drilling upward is not supported")
if isinstance(retract_mode, GCodeCannedCycleReturnToR):
final_depth = retract_depth
else:
final_depth = start_pos.Z
# Move above hole (height of retract_depth)
if retract_depth > start_pos.Z:
yield GCodeRapidMove(Z=retract_depth)
yield GCodeRapidMove(X=cur_hole_p_axis.x, Y=cur_hole_p_axis.y)
if retract_depth < start_pos.Z:
yield GCodeRapidMove(Z=retract_depth)
# Drill hole
delta = drill_depth - retract_depth # full depth
if isinstance(canned_gcode, (GCodeDrillingCyclePeck, GCodeDrillingCycleChipBreaking)):
delta = -abs(canned_gcode.Q)
cur_depth = retract_depth
last_depth = cur_depth
while True:
# Determine new depth
cur_depth += delta
if cur_depth < drill_depth:
cur_depth = drill_depth
# Rapid to just above, then slowly drill through delta
just_above_base = last_depth + 0.1
if just_above_base < retract_depth:
yield GCodeRapidMove(Z=just_above_base)
yield GCodeLinearMove(Z=cur_depth)
if cur_depth <= drill_depth:
break # loop stops at the bottom of the hole
else:
# back up
if isinstance(canned_gcode, GCodeDrillingCycleChipBreaking):
# retract "a bit"
yield GCodeRapidMove(Z=cur_depth + 0.5) # TODO: configurable retraction
else:
# default behaviour: GCodeDrillingCyclePeck
yield GCodeRapidMove(Z=retract_depth)
last_depth = cur_depth
# Dwell
if isinstance(canned_gcode, GCodeDrillingCycleDwell):
yield GCodeDwell(P=0.5) # TODO: configurable pause
# Return
yield GCodeRapidMove(Z=final_depth)
return inner()

View File

@ -66,8 +66,8 @@ def plane_projection(vect, normal):
n = normal.normalized() n = normal.normalized()
return vect - (n * vect.dot(n)) return vect - (n * vect.dot(n))
# ==================== GCode Utilities ====================
# ==================== GCode Utilities ====================
def omit_redundant_modes(gcode_iter): def omit_redundant_modes(gcode_iter):
""" """
Replace redundant machine motion modes with whitespace, Replace redundant machine motion modes with whitespace,

View File

@ -20,7 +20,7 @@ for pygcode_lib_type in ('installed_lib', 'relative_lib'):
from pygcode import GCodeCannedCycle from pygcode import GCodeCannedCycle
from pygcode import split_gcodes from pygcode import split_gcodes
from pygcode import Comment from pygcode import Comment
from pygcode.transform import linearize_arc from pygcode.transform import linearize_arc, simplify_canned_cycle
from pygcode.transform import ArcLinearizeInside, ArcLinearizeOutside, ArcLinearizeMid from pygcode.transform import ArcLinearizeInside, ArcLinearizeOutside, ArcLinearizeMid
from pygcode.gcodes import _subclasses from pygcode.gcodes import _subclasses
from pygcode.utils import omit_redundant_modes from pygcode.utils import omit_redundant_modes
@ -89,13 +89,13 @@ group = parser.add_argument_group(
"interpolation (G1), and pauses (or 'dwells', G4)" "interpolation (G1), and pauses (or 'dwells', G4)"
) )
group.add_argument( group.add_argument(
'--canned_simplify', '-cs', dest='canned_simplify', '--canned_expand', '-ce', dest='canned_expand',
action='store_const', const=True, default=False, action='store_const', const=True, default=False,
help="Convert canned cycles into basic linear movements", help="Expand canned cycles into basic linear movements, and pauses",
) )
group.add_argument( group.add_argument(
'---canned_codes', '-cc', dest='canned_codes', default=DEFAULT_CANNED_CODES, '---canned_codes', '-cc', dest='canned_codes', default=DEFAULT_CANNED_CODES,
help="List of canned gcodes to simplify, (default is '%s')" % DEFAULT_CANNED_CODES, help="List of canned gcodes to expand, (default is '%s')" % DEFAULT_CANNED_CODES,
) )
#parser.add_argument( #parser.add_argument(
@ -194,6 +194,7 @@ for line_str in args.infile[0].readlines():
if args.arc_linearize and any(isinstance(g, GCodeArcMove) for g in effective_gcodes): if args.arc_linearize and any(isinstance(g, GCodeArcMove) for g in effective_gcodes):
with split_and_process(effective_gcodes, GCodeArcMove, line.comment) as arc: with split_and_process(effective_gcodes, GCodeArcMove, line.comment) as arc:
print(Comment("linearized arc: %r" % arc))
linearize_params = { linearize_params = {
'arc_gcode': arc, 'arc_gcode': arc,
'start_pos': machine.pos, 'start_pos': machine.pos,
@ -207,14 +208,18 @@ for line_str in args.infile[0].readlines():
for linear_gcode in omit_redundant_modes(linearize_arc(**linearize_params)): for linear_gcode in omit_redundant_modes(linearize_arc(**linearize_params)):
print(linear_gcode) print(linear_gcode)
elif args.canned_simplify and any((g.word in args.canned_codes) for g in effective_gcodes): elif args.canned_expand and any((g.word in args.canned_codes) for g in effective_gcodes):
(befores, (canned,), afters) = split_gcodes(effective_gcodes, GCodeCannedCycle) with split_and_process(effective_gcodes, GCodeCannedCycle, line.comment) as canned:
print(Comment('canning simplified: %r' % canned)) print(Comment("expanded: %r" % canned))
simplify_canned_params = {
# TODO: simplify canned things 'canned_gcode': canned,
'start_pos': machine.pos,
print(str(line)) 'plane': machine.mode.plane_selection,
machine.process_block(line.block) 'dist_mode': machine.mode.distance,
'axes': machine.axes,
}
for simplified_gcode in omit_redundant_modes(simplify_canned_cycle(**simplify_canned_params)):
print(simplified_gcode)
else: else:
print(str(line)) print(str(line))

View File

@ -11,6 +11,7 @@ add_pygcode_to_path()
# Units under test # Units under test
from pygcode import gcodes from pygcode import gcodes
from pygcode import words from pygcode import words
from pygcode import machine
from pygcode.exceptions import GCodeWordStrError from pygcode.exceptions import GCodeWordStrError
@ -139,3 +140,54 @@ class GCodeSplitTests(unittest.TestCase):
self.assertTrue(any(isinstance(g, gcodes.GCodeMotion) for g in split[0])) self.assertTrue(any(isinstance(g, gcodes.GCodeMotion) for g in split[0]))
self.assertTrue(isinstance(split[1][0], gcodes.GCodeStartSpindle)) self.assertTrue(isinstance(split[1][0], gcodes.GCodeStartSpindle))
self.assertTrue(any(isinstance(g, gcodes.GCodeSpindleSpeed) for g in split[2])) self.assertTrue(any(isinstance(g, gcodes.GCodeSpindleSpeed) for g in split[2]))
class GCodeAbsoluteToRelativeDecoratorTests(unittest.TestCase):
def test_gcodes_abs2rel(self):
# setup gcode testlist
L = gcodes.GCodeLinearMove
R = gcodes.GCodeRapidMove
args = lambda x, y, z: dict(a for a in zip('XYZ', [x,y,z]) if a[1] is not None)
gcode_list = [
# GCode instances Expected incremental output
(L(**args(0, 0, 0)), L(**args(-10, -20, -30))),
(L(**args(1, 2, 0)), L(**args(1, 2, None))),
(L(**args(3, 4, 0)), L(**args(2, 2, None))),
(R(**args(1, 2, 0)), R(**args(-2, -2, None))),
(R(**args(3, 4, 0)), R(**args(2, 2, None))),
(L(**args(3, 4, 0)), None),
(L(**args(3, 4, 8)), L(**args(None, None, 8))),
]
m = machine.Machine()
# Incremental Output
m.set_mode(gcodes.GCodeAbsoluteDistanceMode())
m.move_to(X=10, Y=20, Z=30) # initial position (absolute)
m.set_mode(gcodes.GCodeIncrementalDistanceMode())
@gcodes._gcodes_abs2rel(m.pos, dist_mode=m.mode.distance, axes=m.axes)
def expecting_rel():
return [g[0] for g in gcode_list]
trimmed_expecting_list = [x[1] for x in gcode_list if x[1] is not None]
for (i, g) in enumerate(expecting_rel()):
expected = trimmed_expecting_list[i]
self.assertEqual(type(g), type(expected))
self.assertEqual(g.word, expected.word)
self.assertEqual(g.params, expected.params)
# Absolute Output
m.set_mode(gcodes.GCodeAbsoluteDistanceMode())
m.move_to(X=10, Y=20, Z=30) # initial position
@gcodes._gcodes_abs2rel(m.pos, dist_mode=m.mode.distance, axes=m.axes)
def expecting_abs():
return [g[0] for g in gcode_list]
for (i, g) in enumerate(expecting_abs()):
expected = gcode_list[i][0] # expecting passthrough
self.assertEqual(type(g), type(expected))
self.assertEqual(g.word, expected.word)
self.assertEqual(g.params, expected.params)

View File

@ -1,7 +1,3 @@
import sys
import os
import inspect
import unittest import unittest
# Add relative pygcode to path # Add relative pygcode to path

View File

@ -1,3 +1,4 @@
# utilities for the testing suite (as opposed to the tests for utils.py)
import sys import sys
import os import os
import inspect import inspect