modal groups

This commit is contained in:
Peter Boin 2017-07-06 01:04:37 +10:00
parent e84901eccc
commit 3a5dec14f0
2 changed files with 165 additions and 13 deletions

View File

@ -2,14 +2,81 @@ from collections import defaultdict
from .words import Word from .words import Word
# Terminology of a "G-Code"
# For the purposes of this library, so-called "G" codes do not necessarily
# use the letter "G" in their word; other letters include M, F, S, and T
# Why?
# I've seen documentation thrown around using the term "gcode" interchangably
# for any word that triggers an action, or mode change. The fact that the
# entire language is called "gcode" obfuscates any other convention.
# Considering it's much easier for me to call everything GCode, I'm taking
# the lazy route, so sue me (but seriously, please don't sue me).
#
# Modality groups
# Modal GCodes:
# A machine's "mode" can be changed by any gcode with a modal_group.
# This state is retained by the machine in the blocks to follow until
# the "mode" is revoked, or changed.
# A typical example of this is units:
# G20
# this will change the machine's "mode" to treat all positions as
# millimeters; G20 does not have to be on every line, thus the machine's
# "mode" is in millimeters for that, and every block after it until G21
# is specified (changing units to inches).
#
# Modal Groups:
# Only one mode of each modal group can be active. That is to say, a
# modal g-code can only change the sate of a previously set mode if
# they're in the same group.
# For example:
# G20 (mm), and G21 (inches) are in group 6
# G1 (linear movement), and G2 (arc movement) are in group 1
# A machine can't use mm and inches at the same time, just as it can't
# move in a straight line, and move in an arc simultaneously.
#
# There are 15 groups:
# ref: http://linuxcnc.org/docs/html/gcode/overview.html#_modal_groups
#
# Table 5. G-Code Modal Groups
# MODAL GROUP MEANING MEMBER WORDS
# 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.x, G73, G76, G80, G81
# G82, G83, G84, G85, G86, G87, G88,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, G99
# 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
# Stopping (Group 4) M0, M1, M2, M30, M60
# Spindle (Group 7) M3, M4, M5
# Coolant (Group 8) (M7 M8 can both be on), M9
# Override Switches (Group 9) M48, M49
# User Defined (Group 10) M100-M199
#
class GCode(object): class GCode(object):
# Defining Word # Defining Word
word_key = None # Word instance to use in lookup word_key = None # Word instance to use in lookup
word_matches = None # function (secondary) word_matches = None # function (secondary)
# Parameters associated to this gcode # Parameters associated to this gcode
param_letters = set() param_letters = set()
# Modal Group
modal_group = None
def __init__(self, word, *params): def __init__(self, word, *params):
assert isinstance(word, Word), "invalid gcode word %r" % code_word assert isinstance(word, Word), "invalid gcode word %r" % code_word
self.word = word self.word = word
@ -75,6 +142,7 @@ class GCode(object):
class GCodeMotion(GCode): class GCodeMotion(GCode):
param_letters = set('XYZABCUVW') param_letters = set('XYZABCUVW')
modal_group = 1
class GCodeRapidMove(GCodeMotion): class GCodeRapidMove(GCodeMotion):
@ -106,6 +174,7 @@ class GCodeDwell(GCodeMotion):
"""G4: Dwell""" """G4: Dwell"""
param_letters = GCodeMotion.param_letters | set('P') param_letters = GCodeMotion.param_letters | set('P')
word_key = Word('G', 4) word_key = Word('G', 4)
modal_group = None # one of the few motion commands that isn't modal
class GCodeCublcSpline(GCodeMotion): class GCodeCublcSpline(GCodeMotion):
@ -168,7 +237,7 @@ class GCodeCancelCannedCycle(GCodeMotion):
class GCodeCannedCycle(GCode): class GCodeCannedCycle(GCode):
param_letters = set('XYZUVW') param_letters = set('XYZUVW')
modal_group = 1
class GCodeDrillingCycle(GCodeCannedCycle): class GCodeDrillingCycle(GCodeCannedCycle):
"""G81: Drilling Cycle""" """G81: Drilling Cycle"""
@ -226,31 +295,36 @@ class GCodeDistanceMode(GCode):
class GCodeAbsoluteDistanceMode(GCodeDistanceMode): class GCodeAbsoluteDistanceMode(GCodeDistanceMode):
"""G90: Absolute Distance Mode""" """G90: Absolute Distance Mode"""
word_key = Word('G', 90) word_key = Word('G', 90)
modal_group = 3
class GCodeIncrementalDistanceMode(GCodeDistanceMode): class GCodeIncrementalDistanceMode(GCodeDistanceMode):
"""G91: Incremental Distance Mode""" """G91: Incremental Distance Mode"""
word_key = Word('G', 91) word_key = Word('G', 91)
modal_group = 3
class GCodeAbsoluteArcDistanceMode(GCodeDistanceMode): class GCodeAbsoluteArcDistanceMode(GCodeDistanceMode):
"""G90.1: Absolute Distance Mode for Arc IJK Parameters""" """G90.1: Absolute Distance Mode for Arc IJK Parameters"""
word_key = Word('G', 90.1) word_key = Word('G', 90.1)
modal_group = 4
class GCodeIncrementalArcDistanceMode(GCodeDistanceMode): class GCodeIncrementalArcDistanceMode(GCodeDistanceMode):
"""G91.1: Incremental Distance Mode for Arc IJK Parameters""" """G91.1: Incremental Distance Mode for Arc IJK Parameters"""
word_key = Word('G', 91.1) word_key = Word('G', 91.1)
modal_group = 4
class GCodeLatheDiameterMode(GCodeDistanceMode): class GCodeLatheDiameterMode(GCodeDistanceMode):
"""G7: Lathe Diameter Mode""" """G7: Lathe Diameter Mode"""
word_key = Word('G', 7) word_key = Word('G', 7)
modal_group = 15
class GCodeLatheRadiusMode(GCodeDistanceMode): class GCodeLatheRadiusMode(GCodeDistanceMode):
"""G8: Lathe Radius Mode""" """G8: Lathe Radius Mode"""
word_key = Word('G', 8) word_key = Word('G', 8)
modal_group = 15
# ======================= Feed Rate Mode ======================= # ======================= Feed Rate Mode =======================
@ -258,7 +332,7 @@ class GCodeLatheRadiusMode(GCodeDistanceMode):
# G93, G94, G95 Feed Rate Mode # G93, G94, G95 Feed Rate Mode
class GCodeFeedRateMode(GCode): class GCodeFeedRateMode(GCode):
pass modal_group = 5
class GCodeInverseTimeMode(GCodeFeedRateMode): class GCodeInverseTimeMode(GCodeFeedRateMode):
@ -290,18 +364,20 @@ class GCodeStartSpindleCW(GCodeSpindle):
"""M3: Start Spindle Clockwise""" """M3: Start Spindle Clockwise"""
param_letters = set('S') param_letters = set('S')
word_key = Word('M', 3) word_key = Word('M', 3)
modal_group = 7
class GCodeStartSpindleCCW(GCodeSpindle): class GCodeStartSpindleCCW(GCodeSpindle):
"""M4: Start Spindle Counter-Clockwise""" """M4: Start Spindle Counter-Clockwise"""
param_letters = set('S') param_letters = set('S')
word_key = Word('M', 4) word_key = Word('M', 4)
modal_group = 7
class GCodeStopSpindle(GCodeSpindle): class GCodeStopSpindle(GCodeSpindle):
"""M5: Stop Spindle""" """M5: Stop Spindle"""
param_letters = set('S') param_letters = set('S')
word_key = Word('M', 5) word_key = Word('M', 5)
modal_group = 7
class GCodeOrientSpindle(GCodeSpindle): class GCodeOrientSpindle(GCodeSpindle):
@ -313,12 +389,14 @@ class GCodeSpindleConstantSurfaceSpeedMode(GCodeSpindle):
"""G96: Spindle Constant Surface Speed""" """G96: Spindle Constant Surface Speed"""
param_letters = set('DS') param_letters = set('DS')
word_key = Word('G', 96) word_key = Word('G', 96)
modal_group = 14
class GCodeSpindleRPMMode(GCodeSpindle): class GCodeSpindleRPMMode(GCodeSpindle):
"""G97: Spindle RPM Speed""" """G97: Spindle RPM Speed"""
param_letters = set('D') param_letters = set('D')
word_key = Word('G', 97) word_key = Word('G', 97)
modal_group = 14
@ -327,7 +405,7 @@ class GCodeSpindleRPMMode(GCodeSpindle):
# M7, M8, M9 Coolant Control # M7, M8, M9 Coolant Control
class GCodeCoolant(GCode): class GCodeCoolant(GCode):
pass modal_group = 8
class GCodeCoolantMistOn(GCodeCoolant): class GCodeCoolantMistOn(GCodeCoolant):
@ -353,7 +431,7 @@ class GCodeCoolantOff(GCodeCoolant):
# G49 Cancel Tool Length Compensation # G49 Cancel Tool Length Compensation
class GCodeToolLength(GCode): class GCodeToolLength(GCode):
pass modal_group = 8
class GCodeToolLengthOffset(GCodeToolLength): class GCodeToolLengthOffset(GCodeToolLength):
@ -385,7 +463,7 @@ class GCodeCancelToolLengthOffset(GCodeToolLength):
# M60 Pallet Change Pause # M60 Pallet Change Pause
class GCodeProgramControl(GCode): class GCodeProgramControl(GCode):
pass modal_group = 4
class GCodePauseProgram(GCodeProgramControl): class GCodePauseProgram(GCodeProgramControl):
@ -418,12 +496,14 @@ class GCodePalletChangePause(GCodeProgramControl):
# G20, G21 Units # G20, G21 Units
class GCodeUnit(GCode): class GCodeUnit(GCode):
pass modal_group = 6
class GCodeUseInches(GCodeUnit): class GCodeUseInches(GCodeUnit):
"""G20: use inches for length units""" """G20: use inches for length units"""
word_key = Word('G', 20) word_key = Word('G', 20)
class GCodeUseMillimeters(GCodeUnit): class GCodeUseMillimeters(GCodeUnit):
"""G21: use millimeters for length units""" """G21: use millimeters for length units"""
word_key = Word('G', 21) word_key = Word('G', 21)
@ -435,7 +515,7 @@ class GCodeUseMillimeters(GCodeUnit):
# G17 - G19.1 Plane Select # G17 - G19.1 Plane Select
class GCodePlaneSelect(GCode): class GCodePlaneSelect(GCode):
pass modal_group = 2
class GCodeSelectZYPlane(GCodePlaneSelect): class GCodeSelectZYPlane(GCodePlaneSelect):
@ -475,7 +555,7 @@ class GCodeSelectVWPlane(GCodePlaneSelect):
# G41.1, G42.1 D L Dynamic Cutter Compensation # G41.1, G42.1 D L Dynamic Cutter Compensation
class GCodeCutterRadiusComp(GCode): class GCodeCutterRadiusComp(GCode):
pass modal_group = 7
class GCodeCutterRadiusCompOff(GCodeCutterRadiusComp): class GCodeCutterRadiusCompOff(GCodeCutterRadiusComp):
@ -513,7 +593,7 @@ class GCodeDynamicCutterCompRight(GCodeCutterRadiusComp):
# G64 P Q Path Blending # G64 P Q Path Blending
class GCodePathControlMode(GCode): class GCodePathControlMode(GCode):
pass modal_group = 13
class GCodeExactPathMode(GCodePathControlMode): class GCodeExactPathMode(GCodePathControlMode):
@ -537,7 +617,7 @@ class GCodePathBlendingMode(GCodePathControlMode):
# G98 Canned Cycle Return Level # G98 Canned Cycle Return Level
class GCodeCannedReturnMode(GCode): class GCodeCannedReturnMode(GCode):
pass modal_group = 10
class GCodeCannedCycleReturnLevel(GCodeCannedReturnMode): class GCodeCannedCycleReturnLevel(GCodeCannedReturnMode):
@ -566,12 +646,21 @@ class GCodeFeedRate(GCodeOtherModal):
@classmethod @classmethod
def word_matches(cls, w): def word_matches(cls, w):
return w.letter == 'F' return w.letter == 'F'
# Modal Group n/a:
# although this sets the machine's state, there are no other codes to
# group with this one, so although it's modal, it has no group.
#modal_group = n/a
class GCodeSpindleSpeed(GCodeOtherModal): class GCodeSpindleSpeed(GCodeOtherModal):
"""S: Set Spindle Speed""" """S: Set Spindle Speed"""
@classmethod @classmethod
def word_matches(cls, w): def word_matches(cls, w):
return w.letter == 'S' return w.letter == 'S'
# Modal Group n/a:
# although this sets the machine's state, there are no other codes to
# group with this one, so although it's modal, it has no group.
#modal_group = n/a
class GCodeSelectTool(GCodeOtherModal): class GCodeSelectTool(GCodeOtherModal):
@ -579,16 +668,22 @@ class GCodeSelectTool(GCodeOtherModal):
@classmethod @classmethod
def word_matches(cls, w): def word_matches(cls, w):
return w.letter == 'T' return w.letter == 'T'
# Modal Group n/a:
# although this sets the machine's state, there are no other codes to
# group with this one, so although it's modal, it has no group.
#modal_group = n/a
class GCodeSpeedAndFeedOverrideOn(GCodeOtherModal): class GCodeSpeedAndFeedOverrideOn(GCodeOtherModal):
"""M48: Speed and Feed Override Control On""" """M48: Speed and Feed Override Control On"""
word_key = Word('M', 48) word_key = Word('M', 48)
modal_group = 9
class GCodeSpeedAndFeedOverrideOff(GCodeOtherModal): class GCodeSpeedAndFeedOverrideOff(GCodeOtherModal):
"""M49: Speed and Feed Override Control Off""" """M49: Speed and Feed Override Control Off"""
word_key = Word('M', 49) word_key = Word('M', 49)
modal_group = 9
class GCodeFeedOverride(GCodeOtherModal): class GCodeFeedOverride(GCodeOtherModal):
@ -630,6 +725,7 @@ class GCodeSelectCoordinateSystem(GCodeOtherModal):
@classmethod @classmethod
def word_matches(cls, w): def word_matches(cls, w):
return (w.letter == 'G') and (w.value in [54, 55, 56, 57, 58, 59, 59.1, 59.2, 59.3]) return (w.letter == 'G') and (w.value in [54, 55, 56, 57, 58, 59, 59.1, 59.2, 59.3])
modal_group = 12
# ======================= Flow-control Codes ======================= # ======================= Flow-control Codes =======================

View File

@ -1,7 +1,7 @@
import sys import sys
import os import os
import inspect import inspect
import re
import unittest import unittest
# Units Under Test # Units Under Test
@ -22,6 +22,62 @@ class TestGCodeWordMapping(unittest.TestCase):
"conflict with %s and %s" % (fn_class, key_class) "conflict with %s and %s" % (fn_class, key_class)
) )
class TestGCodeModalGroups(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,G80,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 += '''
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
'''
for row in table_rows.split('\n'):
match = re.search(r'^\s*(?P<title>.*)\s*\(Group (?P<group>\d+)\)\s*(?P<words>.*)$', row, re.I)
if match:
for word_str in re.split(r'\s*,\s*', match.group('words')):
word = list(words.iter_words(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 TestWordsToGCodes(unittest.TestCase): class TestWordsToGCodes(unittest.TestCase):
def test_stuff(self): # FIXME: function name def test_stuff(self): # FIXME: function name
line = 'G1 X82.6892 Y-38.6339 F1500' line = 'G1 X82.6892 Y-38.6339 F1500'