From aef2728b0da351a8c7f059652f78a318a65311c4 Mon Sep 17 00:00:00 2001 From: Peter Boin Date: Tue, 25 Jul 2017 01:52:55 +1000 Subject: [PATCH] updated readme fixed canned drilling cycle machine processing bugs improved tests for linear movement --- README.rst | 10 +-- src/pygcode/gcodes.py | 34 ++++++-- tests/test_machine.py | 180 +++++++++++++++++++++++++++++++++--------- 3 files changed, 176 insertions(+), 48 deletions(-) diff --git a/README.rst b/README.rst index 2aef061..a649a90 100644 --- a/README.rst +++ b/README.rst @@ -111,15 +111,13 @@ To elaborate, here are some line examples G01 X1 Y2 F100 S1000 ; blah >>> print(line.block) G01 X1 Y2 F100 S1000 + >>> sorted(line.block.gcodes) + [, + , + ] >>> print(line.comment) ; blah - >>> line = Line('G0 x1 y2 (foo) f100 (bar) s1000') - >>> print(line) - G00 X1 Y2 F100 S1000 (foo. bar) - >>> print(line.comment) - (foo. bar) - Interpreting what a line of gcode does depends on the machine it's running on, and also that machine's state (or 'mode') diff --git a/src/pygcode/gcodes.py b/src/pygcode/gcodes.py index 00465c4..83f3735 100644 --- a/src/pygcode/gcodes.py +++ b/src/pygcode/gcodes.py @@ -218,15 +218,29 @@ class GCode(object): return copy(self.word_key) raise AssertionError("class %r has no default word" % self.__class__) - # Comparisons + # Equality + def __eq__(self, other): + return ( + (self.word == other.word) and + (self.params == other.params) + ) + + def __ne__(self, other): + return not self.__eq__(other) + + # Sort by execution order def __lt__(self, other): - """Sort by execution order""" return self.exec_order < other.exec_order + def __le__(self, other): + return self.exec_order <= other.exec_order + def __gt__(self, other): - """Sort by execution order""" return self.exec_order > other.exec_order + def __ge__(self, other): + return self.exec_order >= other.exec_order + # Parameters def add_parameter(self, word): """ @@ -483,10 +497,18 @@ class GCodeCannedCycle(GCode): if isinstance(machine.mode.canned_cycles_return, GCodeCannedCycleReturnToR): # canned return is to this.R, not this.Z (plane dependent) moveto_coords.update({ - machine.mode.plane_selection.normal_axis: this.R, + machine.mode.plane_selection.normal_axis: self.R, }) + else: # default: GCodeCannedCycleReturnPrevLevel + # Remove this.Z (plane dependent) value (ie: no machine movement on this axis) + moveto_coords.pop(machine.mode.plane_selection.normal_axis, None) - machine.move_to(**moveto_coords) + # Process action 'L' times + loop_count = self.L + if (loop_count is None) or (loop_count <= 0): + loop_count = 1 + for i in range(loop_count): + machine.move_to(**moveto_coords) class GCodeDrillingCycle(GCodeCannedCycle): @@ -929,7 +951,7 @@ class GCodeCannedReturnMode(GCode): exec_order = 220 -class GCodeCannedCycleReturnLevel(GCodeCannedReturnMode): +class GCodeCannedCycleReturnPrevLevel(GCodeCannedReturnMode): """G98: Canned Cycle Return to the level set prior to cycle start""" # "retract to the position that axis was in just before this series of one or more contiguous canned cycles was started" word_key = Word('G', 98) diff --git a/tests/test_machine.py b/tests/test_machine.py index 5f52aa6..17ccf07 100644 --- a/tests/test_machine.py +++ b/tests/test_machine.py @@ -8,6 +8,11 @@ add_pygcode_to_path() from pygcode.machine import Position, Machine from pygcode.line import Line from pygcode.exceptions import MachineInvalidAxis +from pygcode.gcodes import ( + GCodeAbsoluteDistanceMode, GCodeIncrementalDistanceMode, + GCodeAbsoluteArcDistanceMode, GCodeIncrementalArcDistanceMode, + GCodeCannedCycleReturnPrevLevel, GCodeCannedCycleReturnToR, +) class PositionTests(unittest.TestCase): @@ -81,43 +86,146 @@ class PositionTests(unittest.TestCase): self.assertEqual(p / 2, Position(axes='XYZ', X=1, Y=5)) - class MachineGCodeProcessingTests(unittest.TestCase): - def test_linear_movement(self): - m = Machine() - test_str = '''; move in a 10mm square - F100 M3 S1000 ; 0 - g1 x0 y10 ; 1 - g1 x10 y10 ; 2 - g1 x10 y0 ; 3 - g1 x0 y0 ; 4 - ''' - expected_pos = { - '0': m.Position(), - '1': m.Position(X=0, Y=10), - '2': m.Position(X=10, Y=10), - '3': m.Position(X=10, Y=0), - '4': m.Position(X=0, Y=0), - } - #print("\n%r\n%r" % (m.mode, m.state)) - for line_text in str_lines(test_str): - line = Line(line_text) + def assert_processed_lines(self, line_data, machine): + """ + Process lines & assert machine's position + :param line_data: list of tuples [('g1 x2', {'X':2}), ... ] + """ + for (i, (line_str, expected_pos)) in enumerate(line_data): + line = Line(line_str) if line.block: - #print("\n%s" % line.block) - m.process_block(line.block) - # Assert possition change correct - comment = line.comment.text - if comment in expected_pos: - self.assertEqual(m.pos, expected_pos[comment]) - #print("%r\n%r\npos=%r" % (m.mode, m.state, m.pos)) + machine.process_block(line.block) + # Assert possition change correct + if expected_pos is not None: + p1 = machine.pos + p2 = machine.Position(**expected_pos) + self.assertEqual(p1, p2, "index:%i '%s': %r != %r" % (i, line_str, p1, p2)) + # Rapid Movement + def test_rapid_abs(self): + m = Machine() + m.process_gcodes(GCodeAbsoluteDistanceMode()) + line_data = [ + ('', {}), # start @ 0,0,0 + ('g0 x0 y10', {'X':0, 'Y':10}), + (' x10 y10', {'X':10, 'Y':10}), + (' x10 y0', {'X':10, 'Y':0}), + (' x0 y0', {'X':0, 'Y':0}), + ] + self.assert_processed_lines(line_data, m) -#m = Machine() -# -#file = GCodeParser('part1.gcode') -#for line in file.iterlines(): -# for (i, gcode) in enumerate(line.block.gcode): -# if isinstance(gcode, GCodeArcMove): -# arc = gcode -# line_params = arc.line_segments(precision=0.0005) -# for + def test_rapid_inc(self): + m = Machine() + m.process_gcodes(GCodeIncrementalDistanceMode()) + line_data = [ + ('', {}), # start @ 0,0,0 + ('g0 y10', {'X':0, 'Y':10}), + (' x10', {'X':10, 'Y':10}), + (' y-10', {'X':10, 'Y':0}), + (' x-10', {'X':0, 'Y':0}), + ] + self.assert_processed_lines(line_data, m) + + # Linearly Interpolated Movement + def test_linear_abs(self): + m = Machine() + m.process_gcodes(GCodeAbsoluteDistanceMode()) + line_data = [ + ('g1 x0 y10', {'X':0, 'Y':10}), + (' x10 y10', {'X':10, 'Y':10}), + (' x10 y0', {'X':10, 'Y':0}), + (' x0 y0', {'X':0, 'Y':0}), + ] + self.assert_processed_lines(line_data, m) + + def test_linear_inc(self): + m = Machine() + m.process_gcodes(GCodeIncrementalDistanceMode()) + line_data = [ + ('g1 y10', {'X':0, 'Y':10}), + (' x10', {'X':10, 'Y':10}), + (' y-10', {'X':10, 'Y':0}), + (' x-10', {'X':0, 'Y':0}), + ] + self.assert_processed_lines(line_data, m) + + # Arc Movement + def test_arc_abs(self): + m = Machine() + m.process_gcodes( + GCodeAbsoluteDistanceMode(), + GCodeIncrementalArcDistanceMode(), + ) + line_data = [ + # Clockwise circle in 4 segments + ('g2 x0 y10 i5 j5', {'X':0, 'Y':10}), + (' x10 y10 i5 j-5', {'X':10, 'Y':10}), + (' x10 y0 i-5 j-5', {'X':10, 'Y':0}), + (' x0 y0 i-5 j5', {'X':0, 'Y':0}), + # Counter-clockwise circle in 4 segments + ('g3 x10 y0 i5 j5', {'X':10, 'Y':0}), + (' x10 y10 i-5 j5', {'X':10, 'Y':10}), + (' x0 y10 i-5 j-5', {'X':0, 'Y':10}), + (' x0 y0 i5 j-5', {'X':0, 'Y':0}), + ] + self.assert_processed_lines(line_data, m) + + def test_arc_inc(self): + m = Machine() + m.process_gcodes( + GCodeIncrementalDistanceMode(), + GCodeIncrementalArcDistanceMode(), + ) + line_data = [ + # Clockwise circle in 4 segments + ('g2 y10 i5 j5', {'X':0, 'Y':10}), + (' x10 i5 j-5', {'X':10, 'Y':10}), + (' y-10 i-5 j-5', {'X':10, 'Y':0}), + (' x-10 i-5 j5', {'X':0, 'Y':0}), + # Counter-clockwise circle in 4 segments + ('g3 x10 i5 j5', {'X':10, 'Y':0}), + (' y10 i-5 j5', {'X':10, 'Y':10}), + (' x-10 i-5 j-5', {'X':0, 'Y':10}), + (' y-10 i5 j-5', {'X':0, 'Y':0}), + ] + self.assert_processed_lines(line_data, m) + + # Canned Drilling Cycles + def test_canned_return2oldz(self): + m = Machine() + m.process_gcodes( + GCodeAbsoluteDistanceMode(), + GCodeCannedCycleReturnPrevLevel(), + ) + line_data = [ + ('g0 z5', {'Z':5}), + ('g81 x10 y20 z-2 r1', {'X':10, 'Y':20, 'Z':5}), + ] + self.assert_processed_lines(line_data, m) + + def test_canned_return2r(self): + m = Machine() + m.process_gcodes( + GCodeAbsoluteDistanceMode(), + GCodeCannedCycleReturnToR(), + ) + line_data = [ + ('g0 z5', {'Z':5}), + ('g81 x10 y20 z-2 r1', {'X':10, 'Y':20, 'Z':1}), + ] + self.assert_processed_lines(line_data, m) + + def test_canned_loops(self): + m = Machine() + m.process_gcodes( + GCodeAbsoluteDistanceMode(), + GCodeCannedCycleReturnPrevLevel(), + ) + line_data = [ + ('g0 z5', None), + ('g81 x10 y20 z-2 r1 l2', {'X':10, 'Y':20, 'Z':5}), + ('g91', None), # switch to incremental mode + ('g81 x10 y20 z-2 r1 l2', {'X':30, 'Y':60, 'Z':5}), + ] + self.assert_processed_lines(line_data, m)