mirror of
https://git.mirrors.martin98.com/https://github.com/petaflot/pygcode
synced 2025-08-12 08:48:57 +08:00
simplify canned drilling processes
This commit is contained in:
parent
b741603655
commit
9ca819d8c6
@ -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
|
||||
|
@ -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()
|
||||
|
@ -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,
|
||||
|
@ -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))
|
||||
|
@ -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)
|
||||
|
@ -1,7 +1,3 @@
|
||||
import sys
|
||||
import os
|
||||
import inspect
|
||||
|
||||
import unittest
|
||||
|
||||
# Add relative pygcode to path
|
||||
|
@ -1,3 +1,4 @@
|
||||
# utilities for the testing suite (as opposed to the tests for utils.py)
|
||||
import sys
|
||||
import os
|
||||
import inspect
|
||||
|
Loading…
x
Reference in New Issue
Block a user