mirror of
https://git.mirrors.martin98.com/https://github.com/petaflot/pygcode
synced 2025-09-16 01:13:15 +08:00
named modal groups, clean words to str
This commit is contained in:
parent
cf5f9a17cd
commit
c7578de0d7
@ -1,6 +1,6 @@
|
|||||||
import re
|
import re
|
||||||
from .words import iter_words, WORD_MAP
|
from .words import iter_words, WORD_MAP
|
||||||
from .gcodes import words_to_gcodes
|
from .gcodes import words2gcodes
|
||||||
|
|
||||||
class Block(object):
|
class Block(object):
|
||||||
"""GCode block (effectively any gcode file line that defines any <word><value>)"""
|
"""GCode block (effectively any gcode file line that defines any <word><value>)"""
|
||||||
@ -21,7 +21,7 @@ class Block(object):
|
|||||||
self.text = text
|
self.text = text
|
||||||
|
|
||||||
self.words = list(iter_words(self.text))
|
self.words = list(iter_words(self.text))
|
||||||
(self.gcodes, self.modal_params) = words_to_gcodes(self.words)
|
(self.gcodes, self.modal_params) = words2gcodes(self.words)
|
||||||
|
|
||||||
self._assert_gcodes()
|
self._assert_gcodes()
|
||||||
|
|
||||||
@ -31,7 +31,9 @@ class Block(object):
|
|||||||
def _assert_gcodes(self):
|
def _assert_gcodes(self):
|
||||||
modal_groups = set()
|
modal_groups = set()
|
||||||
code_words = set()
|
code_words = set()
|
||||||
|
|
||||||
for gc in self.gcodes:
|
for gc in self.gcodes:
|
||||||
|
|
||||||
# Assert all gcodes are not repeated in the same block
|
# Assert all gcodes are not repeated in the same block
|
||||||
if gc.word in code_words:
|
if gc.word in code_words:
|
||||||
raise AssertionError("%s cannot be in the same block" % ([
|
raise AssertionError("%s cannot be in the same block" % ([
|
||||||
@ -39,6 +41,7 @@ class Block(object):
|
|||||||
if x.modal_group == gc.modal_group
|
if x.modal_group == gc.modal_group
|
||||||
]))
|
]))
|
||||||
code_words.add(gc.word)
|
code_words.add(gc.word)
|
||||||
|
|
||||||
# Assert all gcodes are from different modal groups
|
# Assert all gcodes are from different modal groups
|
||||||
if gc.modal_group is not None:
|
if gc.modal_group is not None:
|
||||||
if gc.modal_group in modal_groups:
|
if gc.modal_group in modal_groups:
|
||||||
@ -61,3 +64,6 @@ class Block(object):
|
|||||||
cls=self.__class__.__name__,
|
cls=self.__class__.__name__,
|
||||||
key=k
|
key=k
|
||||||
))
|
))
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return ' '.join(str(x) for x in (self.gcodes + [p.clean_str for p in self.modal_params]))
|
||||||
|
@ -24,5 +24,6 @@ def parse(filename):
|
|||||||
with open(filename, 'r') as fh:
|
with open(filename, 'r') as fh:
|
||||||
for line in fh.readlines():
|
for line in fh.readlines():
|
||||||
line_obj = Line(line)
|
line_obj = Line(line)
|
||||||
|
# FIXME: don't dump entire file into RAM; change to generator model
|
||||||
file.append(line_obj)
|
file.append(line_obj)
|
||||||
return file
|
return file
|
||||||
|
@ -64,6 +64,44 @@ from .words import Word
|
|||||||
# Override Switches (Group 9) M48, M49
|
# Override Switches (Group 9) M48, M49
|
||||||
# User Defined (Group 10) M100-M199
|
# User Defined (Group 10) M100-M199
|
||||||
#
|
#
|
||||||
|
|
||||||
|
MODAL_GROUP_NUM = {
|
||||||
|
# "G" codes
|
||||||
|
'motion': 1
|
||||||
|
'plane_selection': 2,
|
||||||
|
'distance': 3,
|
||||||
|
'arc_ijk_distance': 4,
|
||||||
|
'feed_rate_mode': 5,
|
||||||
|
'units': 6,
|
||||||
|
'cutter_diameter_comp': 7,
|
||||||
|
'tool_length_offset': 8,
|
||||||
|
'canned_cycles_return': 10,
|
||||||
|
'coordinate_system': 12,
|
||||||
|
'control_mode': 13,
|
||||||
|
'spindle_speed': 14,
|
||||||
|
'lathe_diameter': 15,
|
||||||
|
|
||||||
|
# "M" codes
|
||||||
|
'stopping': 104,
|
||||||
|
'spindle': 107,
|
||||||
|
'coolant': 108,
|
||||||
|
'override_switches': 109,
|
||||||
|
'user_defined': 110,
|
||||||
|
|
||||||
|
# Traditionally Non-grouped:
|
||||||
|
# Although these GCodes set the machine's mode, there are no other GCodes to
|
||||||
|
# group with them. So although they're modal, they doesn't have a defined
|
||||||
|
# modal group.
|
||||||
|
# However, having a modal group assists with:
|
||||||
|
# - validating gcode blocks for conflicting commands
|
||||||
|
# - remembering machine's state with consistency across gcodes
|
||||||
|
# Therefore, I've added F, S, and T GCodes to custom group numbers (> 200)
|
||||||
|
'feed_rate': 201,
|
||||||
|
'spindle_speed': 202,
|
||||||
|
'tool': 203,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
# Execution Order
|
# Execution Order
|
||||||
# Order taken http://linuxcnc.org/docs/html/gcode/overview.html#_g_code_order_of_execution
|
# Order taken http://linuxcnc.org/docs/html/gcode/overview.html#_g_code_order_of_execution
|
||||||
# (as of 2017-07-03)
|
# (as of 2017-07-03)
|
||||||
@ -132,12 +170,15 @@ class GCode(object):
|
|||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
"""String representation of gcode, as it would be seen in a .gcode file"""
|
"""String representation of gcode, as it would be seen in a .gcode file"""
|
||||||
return "{gcode} {parameters}".format(
|
param_str = ''
|
||||||
gcode=self.word,
|
if self.params:
|
||||||
parameters=' '.join([
|
param_str += ' ' + ' '.join([
|
||||||
"{}".format(self.params[k])
|
"{}".format(self.params[k].clean_str)
|
||||||
for k in sorted(self.params.keys())
|
for k in sorted(self.params.keys())
|
||||||
]),
|
])
|
||||||
|
return "{gcode}{parameters}".format(
|
||||||
|
gcode=self.word.clean_str,
|
||||||
|
parameters=param_str,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Sort by exec_order
|
# Sort by exec_order
|
||||||
@ -167,6 +208,16 @@ class GCode(object):
|
|||||||
def description(self):
|
def description(self):
|
||||||
return self.__doc__
|
return self.__doc__
|
||||||
|
|
||||||
|
@property
|
||||||
|
def mode(self):
|
||||||
|
"""
|
||||||
|
Mode word for modal GCodes
|
||||||
|
:return: Word to save machine's state, or None if GCode is not modal
|
||||||
|
"""
|
||||||
|
if self.modal_group is None:
|
||||||
|
return None
|
||||||
|
return self.word
|
||||||
|
|
||||||
|
|
||||||
# ======================= Motion =======================
|
# ======================= Motion =======================
|
||||||
# (X Y Z A B C U V W apply to all motions)
|
# (X Y Z A B C U V W apply to all motions)
|
||||||
@ -185,7 +236,7 @@ class GCode(object):
|
|||||||
|
|
||||||
class GCodeMotion(GCode):
|
class GCodeMotion(GCode):
|
||||||
param_letters = set('XYZABCUVW')
|
param_letters = set('XYZABCUVW')
|
||||||
modal_group = 1
|
modal_group = MODAL_GROUP_NUM['motion']
|
||||||
exec_order = 241
|
exec_order = 241
|
||||||
|
|
||||||
class GCodeRapidMove(GCodeMotion):
|
class GCodeRapidMove(GCodeMotion):
|
||||||
@ -281,7 +332,7 @@ class GCodeCancelCannedCycle(GCodeMotion):
|
|||||||
|
|
||||||
class GCodeCannedCycle(GCode):
|
class GCodeCannedCycle(GCode):
|
||||||
param_letters = set('XYZUVW')
|
param_letters = set('XYZUVW')
|
||||||
modal_group = 1
|
modal_group = MODAL_GROUP_NUM['motion']
|
||||||
exec_order = 241
|
exec_order = 241
|
||||||
|
|
||||||
class GCodeDrillingCycle(GCodeCannedCycle):
|
class GCodeDrillingCycle(GCodeCannedCycle):
|
||||||
@ -340,36 +391,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
|
modal_group = MODAL_GROUP_NUM['distance']
|
||||||
|
|
||||||
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
|
modal_group = MODAL_GROUP_NUM['distance']
|
||||||
|
|
||||||
|
|
||||||
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
|
modal_group = MODAL_GROUP_NUM['arc_ijk_distance']
|
||||||
|
|
||||||
|
|
||||||
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
|
modal_group = MODAL_GROUP_NUM['arc_ijk_distance']
|
||||||
|
|
||||||
|
|
||||||
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
|
modal_group = MODAL_GROUP_NUM['lathe_diameter']
|
||||||
|
|
||||||
|
|
||||||
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
|
modal_group = MODAL_GROUP_NUM['lathe_diameter']
|
||||||
|
|
||||||
|
|
||||||
# ======================= Feed Rate Mode =======================
|
# ======================= Feed Rate Mode =======================
|
||||||
@ -377,7 +428,7 @@ class GCodeLatheRadiusMode(GCodeDistanceMode):
|
|||||||
# G93, G94, G95 Feed Rate Mode
|
# G93, G94, G95 Feed Rate Mode
|
||||||
|
|
||||||
class GCodeFeedRateMode(GCode):
|
class GCodeFeedRateMode(GCode):
|
||||||
modal_group = 5
|
modal_group = MODAL_GROUP_NUM['feed_rate_mode']
|
||||||
exec_order = 30
|
exec_order = 30
|
||||||
|
|
||||||
class GCodeInverseTimeMode(GCodeFeedRateMode):
|
class GCodeInverseTimeMode(GCodeFeedRateMode):
|
||||||
@ -409,20 +460,20 @@ class GCodeStartSpindleCW(GCodeSpindle):
|
|||||||
"""M3: Start Spindle Clockwise"""
|
"""M3: Start Spindle Clockwise"""
|
||||||
#param_letters = set('S') # S is it's own gcode, makes no sense to be here
|
#param_letters = set('S') # S is it's own gcode, makes no sense to be here
|
||||||
word_key = Word('M', 3)
|
word_key = Word('M', 3)
|
||||||
modal_group = 7
|
modal_group = MODAL_GROUP_NUM['spindle']
|
||||||
|
|
||||||
class GCodeStartSpindleCCW(GCodeSpindle):
|
class GCodeStartSpindleCCW(GCodeSpindle):
|
||||||
"""M4: Start Spindle Counter-Clockwise"""
|
"""M4: Start Spindle Counter-Clockwise"""
|
||||||
#param_letters = set('S') # S is it's own gcode, makes no sense to be here
|
#param_letters = set('S') # S is it's own gcode, makes no sense to be here
|
||||||
word_key = Word('M', 4)
|
word_key = Word('M', 4)
|
||||||
modal_group = 7
|
modal_group = MODAL_GROUP_NUM['spindle']
|
||||||
|
|
||||||
|
|
||||||
class GCodeStopSpindle(GCodeSpindle):
|
class GCodeStopSpindle(GCodeSpindle):
|
||||||
"""M5: Stop Spindle"""
|
"""M5: Stop Spindle"""
|
||||||
#param_letters = set('S') # S is it's own gcode, makes no sense to be here
|
#param_letters = set('S') # S is it's own gcode, makes no sense to be here
|
||||||
word_key = Word('M', 5)
|
word_key = Word('M', 5)
|
||||||
modal_group = 7
|
modal_group = MODAL_GROUP_NUM['spindle']
|
||||||
|
|
||||||
|
|
||||||
class GCodeOrientSpindle(GCodeSpindle):
|
class GCodeOrientSpindle(GCodeSpindle):
|
||||||
@ -434,14 +485,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
|
modal_group = MODAL_GROUP_NUM['spindle_speed']
|
||||||
|
|
||||||
|
|
||||||
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
|
modal_group = MODAL_GROUP_NUM['spindle_speed']
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -450,7 +501,7 @@ class GCodeSpindleRPMMode(GCodeSpindle):
|
|||||||
# M7, M8, M9 Coolant Control
|
# M7, M8, M9 Coolant Control
|
||||||
|
|
||||||
class GCodeCoolant(GCode):
|
class GCodeCoolant(GCode):
|
||||||
modal_group = 8
|
modal_group = MODAL_GROUP_NUM['coolant']
|
||||||
exec_order = 110
|
exec_order = 110
|
||||||
|
|
||||||
|
|
||||||
@ -477,7 +528,7 @@ class GCodeCoolantOff(GCodeCoolant):
|
|||||||
# G49 Cancel Tool Length Compensation
|
# G49 Cancel Tool Length Compensation
|
||||||
|
|
||||||
class GCodeToolLength(GCode):
|
class GCodeToolLength(GCode):
|
||||||
modal_group = 8
|
modal_group = MODAL_GROUP_NUM['tool_length_offset']
|
||||||
exec_order = 180
|
exec_order = 180
|
||||||
|
|
||||||
|
|
||||||
@ -510,7 +561,7 @@ class GCodeCancelToolLengthOffset(GCodeToolLength):
|
|||||||
# M60 Pallet Change Pause
|
# M60 Pallet Change Pause
|
||||||
|
|
||||||
class GCodeProgramControl(GCode):
|
class GCodeProgramControl(GCode):
|
||||||
modal_group = 4
|
modal_group = MODAL_GROUP_NUM['stopping']
|
||||||
exec_order = 250
|
exec_order = 250
|
||||||
|
|
||||||
class GCodePauseProgram(GCodeProgramControl):
|
class GCodePauseProgram(GCodeProgramControl):
|
||||||
@ -543,7 +594,7 @@ class GCodePalletChangePause(GCodeProgramControl):
|
|||||||
# G20, G21 Units
|
# G20, G21 Units
|
||||||
|
|
||||||
class GCodeUnit(GCode):
|
class GCodeUnit(GCode):
|
||||||
modal_group = 6
|
modal_group = MODAL_GROUP_NUM['units']
|
||||||
exec_order = 160
|
exec_order = 160
|
||||||
|
|
||||||
|
|
||||||
@ -563,7 +614,7 @@ class GCodeUseMillimeters(GCodeUnit):
|
|||||||
# G17 - G19.1 Plane Select
|
# G17 - G19.1 Plane Select
|
||||||
|
|
||||||
class GCodePlaneSelect(GCode):
|
class GCodePlaneSelect(GCode):
|
||||||
modal_group = 2
|
modal_group = MODAL_GROUP_NUM['plane_selection']
|
||||||
exec_order = 150
|
exec_order = 150
|
||||||
|
|
||||||
|
|
||||||
@ -604,7 +655,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):
|
||||||
modal_group = 7
|
modal_group = MODAL_GROUP_NUM['cutter_diameter_comp']
|
||||||
exec_order = 170
|
exec_order = 170
|
||||||
|
|
||||||
|
|
||||||
@ -643,7 +694,7 @@ class GCodeDynamicCutterCompRight(GCodeCutterRadiusComp):
|
|||||||
# G64 P Q Path Blending
|
# G64 P Q Path Blending
|
||||||
|
|
||||||
class GCodePathControlMode(GCode):
|
class GCodePathControlMode(GCode):
|
||||||
modal_group = 13
|
modal_group = MODAL_GROUP_NUM['control_mode']
|
||||||
exec_order = 200
|
exec_order = 200
|
||||||
|
|
||||||
|
|
||||||
@ -668,7 +719,7 @@ class GCodePathBlendingMode(GCodePathControlMode):
|
|||||||
# G98 Canned Cycle Return Level
|
# G98 Canned Cycle Return Level
|
||||||
|
|
||||||
class GCodeCannedReturnMode(GCode):
|
class GCodeCannedReturnMode(GCode):
|
||||||
modal_group = 10
|
modal_group = MODAL_GROUP_NUM['canned_cycles_return']
|
||||||
exec_order = 220
|
exec_order = 220
|
||||||
|
|
||||||
|
|
||||||
@ -698,11 +749,7 @@ 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 :
|
modal_group = MODAL_GROUP_NUM['feed_rate']
|
||||||
# although this sets the machine's state, there are no other codes to
|
|
||||||
# group with this one, so although it's modal, it usually has no group.
|
|
||||||
# However,
|
|
||||||
modal_group = 101
|
|
||||||
exec_order = 40
|
exec_order = 40
|
||||||
|
|
||||||
|
|
||||||
@ -712,7 +759,7 @@ class GCodeSpindleSpeed(GCodeOtherModal):
|
|||||||
def word_matches(cls, w):
|
def word_matches(cls, w):
|
||||||
return w.letter == 'S'
|
return w.letter == 'S'
|
||||||
# Modal Group: (see description in GCodeFeedRate)
|
# Modal Group: (see description in GCodeFeedRate)
|
||||||
modal_group = 102
|
modal_group = MODAL_GROUP_NUM['spindle_speed']
|
||||||
exec_order = 50
|
exec_order = 50
|
||||||
|
|
||||||
|
|
||||||
@ -722,21 +769,21 @@ class GCodeSelectTool(GCodeOtherModal):
|
|||||||
def word_matches(cls, w):
|
def word_matches(cls, w):
|
||||||
return w.letter == 'T'
|
return w.letter == 'T'
|
||||||
# Modal Group: (see description in GCodeFeedRate)
|
# Modal Group: (see description in GCodeFeedRate)
|
||||||
modal_group = 103
|
modal_group = MODAL_GROUP_NUM['tool']
|
||||||
exec_order = 60
|
exec_order = 60
|
||||||
|
|
||||||
|
|
||||||
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
|
modal_group = MODAL_GROUP_NUM['override_switches']
|
||||||
exec_order = 120
|
exec_order = 120
|
||||||
|
|
||||||
|
|
||||||
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
|
modal_group = MODAL_GROUP_NUM['override_switches']
|
||||||
exec_order = 120
|
exec_order = 120
|
||||||
|
|
||||||
|
|
||||||
@ -783,7 +830,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
|
modal_group = MODAL_GROUP_NUM['coordinate_system']
|
||||||
exec_order = 190
|
exec_order = 190
|
||||||
|
|
||||||
|
|
||||||
@ -950,6 +997,7 @@ class GCodeUserDefined(GCodeNonModal):
|
|||||||
#def word_matches(cls, w):
|
#def word_matches(cls, w):
|
||||||
# return (w.letter == 'M') and (101 <= w.value <= 199)
|
# return (w.letter == 'M') and (101 <= w.value <= 199)
|
||||||
exec_order = 130
|
exec_order = 130
|
||||||
|
modal_group = MODAL_GROUP_NUM['user_defined']
|
||||||
|
|
||||||
|
|
||||||
# ======================= Utilities =======================
|
# ======================= Utilities =======================
|
||||||
@ -997,7 +1045,7 @@ _gcode_function_list = [] # of the form: [(lambda w: w.letter == 'F', GCodeFeedR
|
|||||||
|
|
||||||
def build_maps():
|
def build_maps():
|
||||||
"""Populate _gcode_word_map and _gcode_function_list"""
|
"""Populate _gcode_word_map and _gcode_function_list"""
|
||||||
# Ensure lists are clear
|
# Ensure Word maps / lists are clear
|
||||||
global _gcode_word_map
|
global _gcode_word_map
|
||||||
global _gcode_function_list
|
global _gcode_function_list
|
||||||
_gcode_word_map = {}
|
_gcode_word_map = {}
|
||||||
@ -1043,8 +1091,7 @@ def word_gcode_class(word, exhaustive=False):
|
|||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def words2gcodes(words):
|
||||||
def words_to_gcodes(words):
|
|
||||||
"""
|
"""
|
||||||
Group words into g-codes (includes both G & M codes)
|
Group words into g-codes (includes both G & M codes)
|
||||||
:param words: list of Word instances
|
:param words: list of Word instances
|
||||||
|
@ -20,9 +20,8 @@ class Line(object):
|
|||||||
@property
|
@property
|
||||||
def text(self):
|
def text(self):
|
||||||
if self._text is None:
|
if self._text is None:
|
||||||
return self.build_line_text()
|
return str(self)
|
||||||
return self._text
|
return self._text
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
def build_line_text(self):
|
return ' '.join([str(x) for x in [self.block, self.comment] if x])
|
||||||
return ' '.join([str(x) for x in [self.block, self.comment] if x]) + '\n'
|
|
||||||
|
132
pygcode/words.py
132
pygcode/words.py
@ -5,10 +5,21 @@ import six
|
|||||||
from .exceptions import GCodeBlockFormatError
|
from .exceptions import GCodeBlockFormatError
|
||||||
|
|
||||||
|
|
||||||
FLOAT_REGEX = re.compile(r'^-?(\d+\.?\d*|\.\d+)') # testcase: ..tests.test_words.WordValueMatchTests.test_float
|
REGEX_FLOAT = re.compile(r'^-?(\d+\.?\d*|\.\d+)') # testcase: ..tests.test_words.WordValueMatchTests.test_float
|
||||||
INT_REGEX = re.compile(r'^-?\d+')
|
REGEX_INT = re.compile(r'^-?\d+')
|
||||||
POSITIVEINT_REGEX = re.compile(r'^\d+')
|
REGEX_POSITIVEINT = re.compile(r'^\d+')
|
||||||
CODE_REGEX = re.compile(r'^\d+(\.\d)?') # similar
|
REGEX_CODE = re.compile(r'^\d+(\.\d)?') # similar
|
||||||
|
|
||||||
|
# Value cleaning functions
|
||||||
|
def _clean_codestr(value):
|
||||||
|
if value < 10:
|
||||||
|
return "0%g" % value
|
||||||
|
return "%g" % value
|
||||||
|
|
||||||
|
CLEAN_NONE = lambda v: v
|
||||||
|
CLEAN_FLOAT = lambda v: "%g" % v
|
||||||
|
CLEAN_CODE = _clean_codestr
|
||||||
|
CLEAN_INT = lambda v: "%g" % v
|
||||||
|
|
||||||
WORD_MAP = {
|
WORD_MAP = {
|
||||||
# Descriptions copied from wikipedia:
|
# Descriptions copied from wikipedia:
|
||||||
@ -17,148 +28,174 @@ WORD_MAP = {
|
|||||||
# Rotational Axes
|
# Rotational Axes
|
||||||
'A': {
|
'A': {
|
||||||
'class': float,
|
'class': float,
|
||||||
'value_regex': FLOAT_REGEX,
|
'value_regex': REGEX_FLOAT,
|
||||||
'description': "Absolute or incremental position of A axis (rotational axis around X axis)",
|
'description': "Absolute or incremental position of A axis (rotational axis around X axis)",
|
||||||
|
'clean_value': CLEAN_FLOAT,
|
||||||
},
|
},
|
||||||
'B': {
|
'B': {
|
||||||
'class': float,
|
'class': float,
|
||||||
'value_regex': FLOAT_REGEX,
|
'value_regex': REGEX_FLOAT,
|
||||||
'description': "Absolute or incremental position of B axis (rotational axis around Y axis)",
|
'description': "Absolute or incremental position of B axis (rotational axis around Y axis)",
|
||||||
|
'clean_value': CLEAN_FLOAT,
|
||||||
},
|
},
|
||||||
'C': {
|
'C': {
|
||||||
'class': float,
|
'class': float,
|
||||||
'value_regex': FLOAT_REGEX,
|
'value_regex': REGEX_FLOAT,
|
||||||
'description': "Absolute or incremental position of C axis (rotational axis around Z axis)",
|
'description': "Absolute or incremental position of C axis (rotational axis around Z axis)",
|
||||||
|
'clean_value': CLEAN_FLOAT,
|
||||||
},
|
},
|
||||||
'D': {
|
'D': {
|
||||||
'class': float,
|
'class': float,
|
||||||
'value_regex': FLOAT_REGEX,
|
'value_regex': REGEX_FLOAT,
|
||||||
'description': "Defines diameter or radial offset used for cutter compensation. D is used for depth of cut on lathes. It is used for aperture selection and commands on photoplotters.",
|
'description': "Defines diameter or radial offset used for cutter compensation. D is used for depth of cut on lathes. It is used for aperture selection and commands on photoplotters.",
|
||||||
|
'clean_value': CLEAN_FLOAT,
|
||||||
},
|
},
|
||||||
# Feed Rates
|
# Feed Rates
|
||||||
'E': {
|
'E': {
|
||||||
'class': float,
|
'class': float,
|
||||||
'value_regex': FLOAT_REGEX,
|
'value_regex': REGEX_FLOAT,
|
||||||
'description': "Precision feedrate for threading on lathes",
|
'description': "Precision feedrate for threading on lathes",
|
||||||
|
'clean_value': CLEAN_FLOAT,
|
||||||
},
|
},
|
||||||
'F': {
|
'F': {
|
||||||
'class': float,
|
'class': float,
|
||||||
'value_regex': FLOAT_REGEX,
|
'value_regex': REGEX_FLOAT,
|
||||||
'description': "Feedrate",
|
'description': "Feedrate",
|
||||||
|
'clean_value': CLEAN_FLOAT,
|
||||||
},
|
},
|
||||||
# G-Codes
|
# G-Codes
|
||||||
'G': {
|
'G': {
|
||||||
'class': float,
|
'class': float,
|
||||||
'value_regex': CODE_REGEX,
|
'value_regex': REGEX_CODE,
|
||||||
'description': "Address for preparatory commands",
|
'description': "Address for preparatory commands",
|
||||||
|
'clean_value': CLEAN_CODE,
|
||||||
},
|
},
|
||||||
# Tool Offsets
|
# Tool Offsets
|
||||||
'H': {
|
'H': {
|
||||||
'class': float,
|
'class': float,
|
||||||
'value_regex': FLOAT_REGEX,
|
'value_regex': REGEX_FLOAT,
|
||||||
'description': "Defines tool length offset; Incremental axis corresponding to C axis (e.g., on a turn-mill)",
|
'description': "Defines tool length offset; Incremental axis corresponding to C axis (e.g., on a turn-mill)",
|
||||||
|
'clean_value': CLEAN_FLOAT,
|
||||||
},
|
},
|
||||||
# Arc radius center coords
|
# Arc radius center coords
|
||||||
'I': {
|
'I': {
|
||||||
'class': float,
|
'class': float,
|
||||||
'value_regex': FLOAT_REGEX,
|
'value_regex': REGEX_FLOAT,
|
||||||
'description': "Defines arc center in X axis for G02 or G03 arc commands. Also used as a parameter within some fixed cycles.",
|
'description': "Defines arc center in X axis for G02 or G03 arc commands. Also used as a parameter within some fixed cycles.",
|
||||||
|
'clean_value': CLEAN_FLOAT,
|
||||||
},
|
},
|
||||||
'J': {
|
'J': {
|
||||||
'class': float,
|
'class': float,
|
||||||
'value_regex': FLOAT_REGEX,
|
'value_regex': REGEX_FLOAT,
|
||||||
'description': "Defines arc center in Y axis for G02 or G03 arc commands. Also used as a parameter within some fixed cycles.",
|
'description': "Defines arc center in Y axis for G02 or G03 arc commands. Also used as a parameter within some fixed cycles.",
|
||||||
|
'clean_value': CLEAN_FLOAT,
|
||||||
},
|
},
|
||||||
'K': {
|
'K': {
|
||||||
'class': float,
|
'class': float,
|
||||||
'value_regex': FLOAT_REGEX,
|
'value_regex': REGEX_FLOAT,
|
||||||
'description': "Defines arc center in Z axis for G02 or G03 arc commands. Also used as a parameter within some fixed cycles, equal to L address.",
|
'description': "Defines arc center in Z axis for G02 or G03 arc commands. Also used as a parameter within some fixed cycles, equal to L address.",
|
||||||
|
'clean_value': CLEAN_FLOAT,
|
||||||
},
|
},
|
||||||
# Loop Count
|
# Loop Count
|
||||||
'L': {
|
'L': {
|
||||||
'class': int,
|
'class': int,
|
||||||
'value_regex': POSITIVEINT_REGEX,
|
'value_regex': REGEX_POSITIVEINT,
|
||||||
'description': "Fixed cycle loop count; Specification of what register to edit using G10",
|
'description': "Fixed cycle loop count; Specification of what register to edit using G10",
|
||||||
|
'clean_value': CLEAN_INT,
|
||||||
},
|
},
|
||||||
# Miscellaneous Function
|
# Miscellaneous Function
|
||||||
'M': {
|
'M': {
|
||||||
'class': float,
|
'class': float,
|
||||||
'value_regex': CODE_REGEX,
|
'value_regex': REGEX_CODE,
|
||||||
'description': "Miscellaneous function",
|
'description': "Miscellaneous function",
|
||||||
|
'clean_value': CLEAN_CODE,
|
||||||
},
|
},
|
||||||
# Line Number
|
# Line Number
|
||||||
'N': {
|
'N': {
|
||||||
'class': int,
|
'class': int,
|
||||||
'value_regex': POSITIVEINT_REGEX,
|
'value_regex': REGEX_POSITIVEINT,
|
||||||
'description': "Line (block) number in program; System parameter number to change using G10",
|
'description': "Line (block) number in program; System parameter number to change using G10",
|
||||||
|
'clean_value': CLEAN_INT,
|
||||||
},
|
},
|
||||||
# Program Name
|
# Program Name
|
||||||
'O': {
|
'O': {
|
||||||
'class': str,
|
'class': str,
|
||||||
'value_regex': re.compile(r'^.+$'), # all the way to the end
|
'value_regex': re.compile(r'^.+$'), # all the way to the end
|
||||||
'description': "Program name",
|
'description': "Program name",
|
||||||
|
'clean_value': CLEAN_NONE,
|
||||||
},
|
},
|
||||||
# Parameter (arbitrary parameter)
|
# Parameter (arbitrary parameter)
|
||||||
'P': {
|
'P': {
|
||||||
'class': float, # parameter is often an integer, but can be a float
|
'class': float, # parameter is often an integer, but can be a float
|
||||||
'value_regex': FLOAT_REGEX,
|
'value_regex': REGEX_FLOAT,
|
||||||
'description': "Serves as parameter address for various G and M codes",
|
'description': "Serves as parameter address for various G and M codes",
|
||||||
|
'clean_value': CLEAN_FLOAT,
|
||||||
},
|
},
|
||||||
# Peck increment
|
# Peck increment
|
||||||
'Q': {
|
'Q': {
|
||||||
'class': float,
|
'class': float,
|
||||||
'value_regex': FLOAT_REGEX,
|
'value_regex': REGEX_FLOAT,
|
||||||
'description': "Depth to increase on each peck; Peck increment in canned cycles",
|
'description': "Depth to increase on each peck; Peck increment in canned cycles",
|
||||||
|
'clean_value': CLEAN_FLOAT,
|
||||||
},
|
},
|
||||||
# Arc Radius
|
# Arc Radius
|
||||||
'R': {
|
'R': {
|
||||||
'class': float,
|
'class': float,
|
||||||
'value_regex': FLOAT_REGEX,
|
'value_regex': REGEX_FLOAT,
|
||||||
'description': "Defines size of arc radius, or defines retract height in milling canned cycles",
|
'description': "Defines size of arc radius, or defines retract height in milling canned cycles",
|
||||||
|
'clean_value': CLEAN_FLOAT,
|
||||||
},
|
},
|
||||||
# Spindle speed
|
# Spindle speed
|
||||||
'S': {
|
'S': {
|
||||||
'class': float,
|
'class': float,
|
||||||
'value_regex': FLOAT_REGEX,
|
'value_regex': REGEX_FLOAT,
|
||||||
'description': "Defines speed, either spindle speed or surface speed depending on mode",
|
'description': "Defines speed, either spindle speed or surface speed depending on mode",
|
||||||
|
'clean_value': CLEAN_FLOAT,
|
||||||
},
|
},
|
||||||
# Tool Selecton
|
# Tool Selecton
|
||||||
'T': {
|
'T': {
|
||||||
'class': str,
|
'class': str,
|
||||||
'value_regex': POSITIVEINT_REGEX, # tool string may have leading '0's, but is effectively an index (integer)
|
'value_regex': REGEX_POSITIVEINT, # tool string may have leading '0's, but is effectively an index (integer)
|
||||||
'description': "Tool selection",
|
'description': "Tool selection",
|
||||||
|
'clean_value': CLEAN_NONE,
|
||||||
},
|
},
|
||||||
# Incremental axes
|
# Incremental axes
|
||||||
'U': {
|
'U': {
|
||||||
'class': float,
|
'class': float,
|
||||||
'value_regex': FLOAT_REGEX,
|
'value_regex': REGEX_FLOAT,
|
||||||
'description': "Incremental axis corresponding to X axis (typically only lathe group A controls) Also defines dwell time on some machines (instead of 'P' or 'X').",
|
'description': "Incremental axis corresponding to X axis (typically only lathe group A controls) Also defines dwell time on some machines (instead of 'P' or 'X').",
|
||||||
|
'clean_value': CLEAN_FLOAT,
|
||||||
},
|
},
|
||||||
'V': {
|
'V': {
|
||||||
'class': float,
|
'class': float,
|
||||||
'value_regex': FLOAT_REGEX,
|
'value_regex': REGEX_FLOAT,
|
||||||
'description': "Incremental axis corresponding to Y axis",
|
'description': "Incremental axis corresponding to Y axis",
|
||||||
|
'clean_value': CLEAN_FLOAT,
|
||||||
},
|
},
|
||||||
'W': {
|
'W': {
|
||||||
'class': float,
|
'class': float,
|
||||||
'value_regex': FLOAT_REGEX,
|
'value_regex': REGEX_FLOAT,
|
||||||
'description': "Incremental axis corresponding to Z axis (typically only lathe group A controls)",
|
'description': "Incremental axis corresponding to Z axis (typically only lathe group A controls)",
|
||||||
|
'clean_value': CLEAN_FLOAT,
|
||||||
},
|
},
|
||||||
# Linear Axes
|
# Linear Axes
|
||||||
'X': {
|
'X': {
|
||||||
'class': float,
|
'class': float,
|
||||||
'value_regex': FLOAT_REGEX,
|
'value_regex': REGEX_FLOAT,
|
||||||
'description': "Absolute or incremental position of X axis.",
|
'description': "Absolute or incremental position of X axis.",
|
||||||
|
'clean_value': CLEAN_FLOAT,
|
||||||
},
|
},
|
||||||
'Y': {
|
'Y': {
|
||||||
'class': float,
|
'class': float,
|
||||||
'value_regex': FLOAT_REGEX,
|
'value_regex': REGEX_FLOAT,
|
||||||
'description': "Absolute or incremental position of Y axis.",
|
'description': "Absolute or incremental position of Y axis.",
|
||||||
|
'clean_value': CLEAN_FLOAT,
|
||||||
},
|
},
|
||||||
'Z': {
|
'Z': {
|
||||||
'class': float,
|
'class': float,
|
||||||
'value_regex': FLOAT_REGEX,
|
'value_regex': REGEX_FLOAT,
|
||||||
'description': "Absolute or incremental position of Z axis.",
|
'description': "Absolute or incremental position of Z axis.",
|
||||||
|
'clean_value': CLEAN_FLOAT,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -174,8 +211,8 @@ class Word(object):
|
|||||||
else:
|
else:
|
||||||
self._value = value
|
self._value = value
|
||||||
|
|
||||||
# Sorting Order
|
self._value_class = WORD_MAP[self.letter]['class']
|
||||||
self._order_linuxcnc = None
|
self._value_clean = WORD_MAP[self.letter]['clean_value']
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "{letter}{value}".format(
|
return "{letter}{value}".format(
|
||||||
@ -183,6 +220,14 @@ class Word(object):
|
|||||||
value=self.value_str,
|
value=self.value_str,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def clean_str(self):
|
||||||
|
"""same as __str__ but with a cleaned value (eg: X.4 is X0.4)"""
|
||||||
|
return "{letter}{value}".format(
|
||||||
|
letter=self.letter,
|
||||||
|
value=self.value_cleanstr,
|
||||||
|
)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "<{class_name}: {string}>".format(
|
return "<{class_name}: {string}>".format(
|
||||||
class_name=self.__class__.__name__,
|
class_name=self.__class__.__name__,
|
||||||
@ -190,6 +235,8 @@ class Word(object):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def __eq__(self, other):
|
def __eq__(self, other):
|
||||||
|
if isinstance(other, six.string_types):
|
||||||
|
other = str2word(other)
|
||||||
return (self.letter == other.letter) and (self.value == other.value)
|
return (self.letter == other.letter) and (self.value == other.value)
|
||||||
|
|
||||||
def __ne__(self, other):
|
def __ne__(self, other):
|
||||||
@ -198,42 +245,45 @@ class Word(object):
|
|||||||
def __hash__(self):
|
def __hash__(self):
|
||||||
return hash((self.letter, self.value))
|
return hash((self.letter, self.value))
|
||||||
|
|
||||||
|
|
||||||
# Value Properties
|
# Value Properties
|
||||||
@property
|
@property
|
||||||
def value_str(self):
|
def value_str(self):
|
||||||
"""Value string, or """
|
"""Value string, or string representation of value"""
|
||||||
if self._value_str is None:
|
if self._value_str is None:
|
||||||
return str(self._value)
|
return str(self._value)
|
||||||
return self._value_str
|
return self._value_str
|
||||||
|
|
||||||
|
@property
|
||||||
|
def value_cleanstr(self):
|
||||||
|
"""Clean string representation, for consistent file output"""
|
||||||
|
return self._value_clean(self.value)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def value(self):
|
def value(self):
|
||||||
if self._value is None:
|
if self._value is None:
|
||||||
return WORD_MAP[self.letter]['class'](self._value_str)
|
return self._value_class(self._value_str)
|
||||||
return self._value
|
return self._value
|
||||||
|
|
||||||
# Order
|
# Order
|
||||||
@property
|
def __lt__(self, other):
|
||||||
def orderval_linuxcnc(self):
|
return self.letter < other.letter
|
||||||
if self._order_linuxcnc is None:
|
|
||||||
self._order_linuxcnc = _word_order_linuxcnc(self)
|
|
||||||
return self._order_linuxcnc
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def description(self):
|
def description(self):
|
||||||
return "%s: %s" % (self.letter, WORD_MAP[self.letter]['description'])
|
return "%s: %s" % (self.letter, WORD_MAP[self.letter]['description'])
|
||||||
|
|
||||||
NEXT_WORD = re.compile(r'^.*?(?P<letter>[%s])' % ''.join(WORD_MAP.keys()), re.IGNORECASE)
|
|
||||||
|
|
||||||
def iter_words(block_text):
|
def iter_words(block_text):
|
||||||
"""
|
"""
|
||||||
Iterate through block text yielding Word instances
|
Iterate through block text yielding Word instances
|
||||||
:param block_text: text for given block with comments removed
|
:param block_text: text for given block with comments removed
|
||||||
"""
|
"""
|
||||||
index = 0
|
next_word = re.compile(r'^.*?(?P<letter>[%s])' % ''.join(WORD_MAP.keys()), re.IGNORECASE)
|
||||||
|
|
||||||
|
index = 0
|
||||||
while True:
|
while True:
|
||||||
letter_match = NEXT_WORD.search(block_text[index:])
|
letter_match = next_word.search(block_text[index:])
|
||||||
if letter_match:
|
if letter_match:
|
||||||
# Letter
|
# Letter
|
||||||
letter = letter_match.group('letter').upper()
|
letter = letter_match.group('letter').upper()
|
||||||
|
@ -17,4 +17,4 @@ class FileParseTest(unittest.TestCase):
|
|||||||
self.assertEqual(len(file.lines), 26)
|
self.assertEqual(len(file.lines), 26)
|
||||||
# FIXME: just verifying content visually
|
# FIXME: just verifying content visually
|
||||||
#for line in file.lines:
|
#for line in file.lines:
|
||||||
# print(' '.join(["%s%s" % (w.letter, w.value_str) for w in line.block.words]))
|
# print(line)
|
||||||
|
@ -82,7 +82,7 @@ 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'
|
||||||
word_list = list(words.iter_words(line))
|
word_list = list(words.iter_words(line))
|
||||||
result = gcodes.words_to_gcodes(word_list)
|
result = gcodes.words2gcodes(word_list)
|
||||||
# result form
|
# result form
|
||||||
self.assertIsInstance(result, tuple)
|
self.assertIsInstance(result, tuple)
|
||||||
self.assertEqual(len(result), 2)
|
self.assertEqual(len(result), 2)
|
||||||
|
@ -58,7 +58,7 @@ class WordValueMatchTests(unittest.TestCase):
|
|||||||
|
|
||||||
def test_float(self):
|
def test_float(self):
|
||||||
self.regex_assertions(
|
self.regex_assertions(
|
||||||
regex=words.FLOAT_REGEX,
|
regex=words.REGEX_FLOAT,
|
||||||
positive_list=[
|
positive_list=[
|
||||||
('1.2', '1.2'), ('1', '1'), ('200', '200'), ('0092', '0092'),
|
('1.2', '1.2'), ('1', '1'), ('200', '200'), ('0092', '0092'),
|
||||||
('1.', '1.'), ('.2', '.2'), ('-1.234', '-1.234'),
|
('1.', '1.'), ('.2', '.2'), ('-1.234', '-1.234'),
|
||||||
@ -71,7 +71,7 @@ class WordValueMatchTests(unittest.TestCase):
|
|||||||
|
|
||||||
def test_code(self):
|
def test_code(self):
|
||||||
self.regex_assertions(
|
self.regex_assertions(
|
||||||
regex=words.CODE_REGEX,
|
regex=words.REGEX_CODE,
|
||||||
positive_list=[
|
positive_list=[
|
||||||
('1.2', '1.2'), ('1', '1'), ('10', '10'),
|
('1.2', '1.2'), ('1', '1'), ('10', '10'),
|
||||||
('02', '02'), ('02.3', '02.3'),
|
('02', '02'), ('02.3', '02.3'),
|
||||||
|
Loading…
x
Reference in New Issue
Block a user