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"""
param_letters = GCodeCannedCycle.param_letters | set('RLP')
word_key = Word('G', 81)
modal_param_letters = GCodeCannedCycle.param_letters | set('RLP')
modal_param_letters = GCodeCannedCycle.param_letters | set('RP')
class GCodeDrillingCycleDwell(GCodeCannedCycle):
"""G82: Drilling Cycle, Dwell"""
param_letters = GCodeCannedCycle.param_letters | set('RLP')
word_key = Word('G', 82)
modal_param_letters = GCodeCannedCycle.param_letters | set('RLP')
modal_param_letters = GCodeCannedCycle.param_letters | set('RP')
class GCodeDrillingCyclePeck(GCodeCannedCycle):
"""G83: Drilling Cycle, Peck"""
param_letters = GCodeCannedCycle.param_letters | set('RLQ')
word_key = Word('G', 83)
modal_param_letters = GCodeCannedCycle.param_letters | set('RLQ')
modal_param_letters = GCodeCannedCycle.param_letters | set('RQ')
class GCodeDrillingCycleChipBreaking(GCodeCannedCycle):
"""G73: Drilling Cycle, ChipBreaking"""
param_letters = GCodeCannedCycle.param_letters | set('RLQ')
word_key = Word('G', 73)
modal_param_letters = GCodeCannedCycle.param_letters | set('RLQ')
modal_param_letters = GCodeCannedCycle.param_letters | set('RQ')
class GCodeBoringCycleFeedOut(GCodeCannedCycle):
"""G85: Boring Cycle, Feed Out"""
param_letters = GCodeCannedCycle.param_letters | set('RLP')
word_key = Word('G', 85)
modal_param_letters = GCodeCannedCycle.param_letters | set('RLP')
modal_param_letters = GCodeCannedCycle.param_letters | set('RP')
class GCodeBoringCycleDwellFeedOut(GCodeCannedCycle):
"""G89: Boring Cycle, Dwell, Feed Out"""
param_letters = GCodeCannedCycle.param_letters | set('RLP')
word_key = Word('G', 89)
modal_param_letters = GCodeCannedCycle.param_letters | set('RLP')
modal_param_letters = GCodeCannedCycle.param_letters | set('RP')
class GCodeThreadingCycle(GCodeCannedCycle):
@ -895,8 +895,8 @@ class GCodePathBlendingMode(GCodePathControlMode):
# ======================= Return Mode in Canned Cycles =======================
# 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):
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:]
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 .gcodes import GCodeLinearMove
from .gcodes import GCodeLinearMove, GCodeRapidMove
from .gcodes import GCodeArcMove, GCodeArcMoveCW, GCodeArcMoveCCW
from .gcodes import GCodePlaneSelect, GCodeSelectXYPlane, GCodeSelectYZPlane, GCodeSelectZXPlane
from .gcodes import GCodeAbsoluteDistanceMode, GCodeIncrementalDistanceMode
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 .exceptions import GCodeParameterError
@ -97,7 +101,7 @@ class ArcLinearizeMethod(object):
self._outer_radius = self.get_outer_radius()
return self._outer_radius
# Iter
# Vertex Generator
def iter_vertices(self):
"""Yield absolute (<start vertex>, <end vertex>) for each line for the arc"""
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,
dist_mode=None, arc_dist_mode=None,
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
if method_class is None:
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
# ==================== 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()
return vect - (n * vect.dot(n))
# ==================== GCode Utilities ====================
# ==================== GCode Utilities ====================
def omit_redundant_modes(gcode_iter):
"""
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 split_gcodes
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.gcodes import _subclasses
from pygcode.utils import omit_redundant_modes
@ -89,13 +89,13 @@ group = parser.add_argument_group(
"interpolation (G1), and pauses (or 'dwells', G4)"
)
group.add_argument(
'--canned_simplify', '-cs', dest='canned_simplify',
'--canned_expand', '-ce', dest='canned_expand',
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(
'---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(
@ -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):
with split_and_process(effective_gcodes, GCodeArcMove, line.comment) as arc:
print(Comment("linearized arc: %r" % arc))
linearize_params = {
'arc_gcode': arc,
'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)):
print(linear_gcode)
elif args.canned_simplify and any((g.word in args.canned_codes) for g in effective_gcodes):
(befores, (canned,), afters) = split_gcodes(effective_gcodes, GCodeCannedCycle)
print(Comment('canning simplified: %r' % canned))
# TODO: simplify canned things
print(str(line))
machine.process_block(line.block)
elif args.canned_expand and any((g.word in args.canned_codes) for g in effective_gcodes):
with split_and_process(effective_gcodes, GCodeCannedCycle, line.comment) as canned:
print(Comment("expanded: %r" % canned))
simplify_canned_params = {
'canned_gcode': canned,
'start_pos': machine.pos,
'plane': machine.mode.plane_selection,
'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:
print(str(line))

View File

@ -11,6 +11,7 @@ add_pygcode_to_path()
# Units under test
from pygcode import gcodes
from pygcode import words
from pygcode import machine
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(isinstance(split[1][0], gcodes.GCodeStartSpindle))
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
# 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 os
import inspect