import sys
import os
import inspect
import re
import unittest
# Add relative pygcode to path
from testutils import add_pygcode_to_path, str_lines
add_pygcode_to_path()
# Units under test
from pygcode import gcodes
from pygcode import words
from pygcode import machine
from pygcode.exceptions import GCodeWordStrError
class GCodeWordMappingTests(unittest.TestCase):
def test_word_map_integrity(self):
gcodes.build_maps()
for (word_maches, fn_class) in gcodes._gcode_function_list:
for (word, key_class) in gcodes._gcode_word_map.items():
# Verify that no mapped word will yield a True result
# from any of the 'word_maches' functions
self.assertFalse(
word_maches(word),
"conflict with %s and %s" % (fn_class, key_class)
)
class GCodeModalGroupTests(unittest.TestCase):
def test_modal_groups(self):
# Modal groups taken (and slightly modified) from LinuxCNC documentation:
# link: http://linuxcnc.org/docs/html/gcode/overview.html#_modal_groups
table_rows = ''
# Table 5. G-Code Modal Groups
# MODAL GROUP MEANING MEMBER WORDS
table_rows += '''
Non-modal codes (Group 0) G4,G10,G28,G30,G53,G92,G92.1,G92.2,G92.3
Motion (Group 1) G0,G1,G2,G3,G33,G38.2,G38.3,G38.4
Motion (Group 1) G38.5,G73,G76,G81,G82,G83,G85,G89
Plane selection (Group 2) G17, G18, G19, G17.1, G18.1, G19.1
Distance Mode (Group 3) G90, G91
Arc IJK Distance Mode (Group 4) G90.1, G91.1
Feed Rate Mode (Group 5) G93, G94, G95
Units (Group 6) G20, G21
Cutter Diameter Compensation (Group 7) G40, G41, G42, G41.1, G42.1
Tool Length Offset (Group 8) G43, G43.1, G49
Canned Cycles Return Mode (Group 10) G98
Coordinate System (Group 12) G54,G55,G56,G57,G58,G59,G59.1,G59.2,G59.3
Control Mode (Group 13) G61, G61.1, G64
Spindle Speed Mode (Group 14) G96, G97
Lathe Diameter Mode (Group 15) G7,G8
'''
# Table 6. M-Code Modal Groups
# MODAL GROUP MEANING MEMBER WORDS
table_rows += re.sub(r'\(Group (\d+)\)', r'(Group 10\1)', '''
Stopping (Group 4) M0, M1, M2, M30, M60
Spindle (Group 7) M3, M4, M5
Coolant (Group 8) M7, M8, M9
Override Switches (Group 9) M48, M49
''') # groups += 100 (to distinguish "M" GCodes from "G" GCodes)
for row in table_rows.split('\n'):
match = re.search(r'^\s*(?P
.*)\s*\(Group (?P\d+)\)\s*(?P.*)$', row, re.I)
if match:
for word_str in re.split(r'\s*,\s*', match.group('words')):
word = list(words.text2words(word_str))[0]
gcode_class = gcodes.word_gcode_class(word)
# GCode class found for each word in the table
self.assertIsNotNone(gcode_class)
# GCode's modal group equals that defined in the table
expected_group = int(match.group('group'))
if expected_group == 0:
self.assertIsNone(
gcode_class.modal_group,
"%s modal_group: %s is not None" % (gcode_class, gcode_class.modal_group)
)
else:
self.assertEqual(
gcode_class.modal_group, expected_group,
"%s != %s (%r)" % (gcode_class.modal_group, expected_group, word)
)
class Words2GCodesTests(unittest.TestCase):
def test_stuff(self): # FIXME: function name
line = 'G1 X82.6892 Y-38.6339 F1500'
word_list = list(words.text2words(line))
result = gcodes.words2gcodes(word_list)
# result form
self.assertIsInstance(result, tuple)
self.assertEqual(len(result), 2)
# result content
(gcode_list, unused_words) = result
self.assertEqual(len(gcode_list), 2)
self.assertEqual(unused_words, [])
# Parsed GCodes
# G1
self.assertEqual(gcode_list[0].word, words.Word('G', 1))
self.assertEqual(gcode_list[0].X, 82.6892)
self.assertEqual(gcode_list[0].Y, -38.6339)
# F1500
self.assertEqual(gcode_list[1].word, words.Word('F', 1500))
class Text2GCodesTests(unittest.TestCase):
def test_basic(self):
gcs = gcodes.text2gcodes('G1 X1 Y2 G90')
self.assertEqual(len(gcs), 2)
# G1 X1 Y2
self.assertEqual(gcs[0].word, words.Word('G', 1))
self.assertEqual(gcs[0].X, 1)
self.assertEqual(gcs[0].Y, 2)
# G90
self.assertEqual(gcs[1].word, words.Word('G', 90))
def test_modal_params(self):
with self.assertRaises(GCodeWordStrError):
gcodes.text2gcodes('X1 Y2')
class GCodeSplitTests(unittest.TestCase):
def test_split(self):
g_list = gcodes.text2gcodes('G91 S1000 G1 X1 Y2 M3')
split = gcodes.split_gcodes(g_list, gcodes.GCodeStartSpindle)
self.assertEqual([len(x) for x in split], [1, 1, 2])
self.assertTrue(any(isinstance(g, gcodes.GCodeSpindleSpeed) for g in split[0]))
self.assertTrue(isinstance(split[1][0], gcodes.GCodeStartSpindle))
self.assertTrue(any(isinstance(g, gcodes.GCodeDistanceMode) for g in split[2]))
self.assertTrue(any(isinstance(g, gcodes.GCodeMotion) for g in split[2]))
def test_split_unsorted(self):
g_list = gcodes.text2gcodes('G91 G1 X1 Y2 M3 S1000')
split = gcodes.split_gcodes(g_list, gcodes.GCodeStartSpindle, sort_list=False)
self.assertEqual([len(x) for x in split], [2, 1, 1])
self.assertTrue(any(isinstance(g, gcodes.GCodeDistanceMode) 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(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)