diff --git a/.gitignore b/.gitignore index 066b83d..6f9c9cf 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,5 @@ # build build/* +src/pygcode.egg-info/* +dist/pygcode-*.egg diff --git a/src/pygcode.egg-info/PKG-INFO b/src/pygcode.egg-info/PKG-INFO deleted file mode 100644 index 577db9d..0000000 --- a/src/pygcode.egg-info/PKG-INFO +++ /dev/null @@ -1,46 +0,0 @@ -Metadata-Version: 1.1 -Name: pygcode -Version: 0.2.1 -Summary: Basic g-code parser, interpreter, and encoder library. -Home-page: https://github.com/fragmuffin/pygcode -Author: Peter Boin -Author-email: peter.boin@gmail.com -License: GPLv3 -Description-Content-Type: UNKNOWN -Description: ======= - pygcode - ======= - - GCODE Parser for Python - - Currently in development, ``pygcode`` is a low-level GCode interpreter - for python. - - - Installation - ============ - - Install using ``pip`` - - ``pip install pygcode`` - - or `download directly from PyPi `__ - - - Documentation - ============= - - `Check out the wiki `__ for documentation. - -Keywords: gcode,cnc,parser,interpreter -Platform: UNKNOWN -Classifier: Development Status :: 3 - Alpha -Classifier: Intended Audience :: Developers -Classifier: Intended Audience :: Manufacturing -Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3) -Classifier: Natural Language :: English -Classifier: Operating System :: OS Independent -Classifier: Programming Language :: Python -Classifier: Programming Language :: Python :: 2 -Classifier: Programming Language :: Python :: 3 -Classifier: Topic :: Scientific/Engineering diff --git a/src/pygcode.egg-info/SOURCES.txt b/src/pygcode.egg-info/SOURCES.txt deleted file mode 100644 index 3429030..0000000 --- a/src/pygcode.egg-info/SOURCES.txt +++ /dev/null @@ -1,21 +0,0 @@ -README.rst -setup.cfg -setup.py -scripts/pygcode-crop -scripts/pygcode-norm -src/pygcode/__init__.py -src/pygcode/block.py -src/pygcode/comment.py -src/pygcode/exceptions.py -src/pygcode/gcodes.py -src/pygcode/line.py -src/pygcode/machine.py -src/pygcode/transform.py -src/pygcode/utils.py -src/pygcode/words.py -src/pygcode.egg-info/PKG-INFO -src/pygcode.egg-info/SOURCES.txt -src/pygcode.egg-info/dependency_links.txt -src/pygcode.egg-info/not-zip-safe -src/pygcode.egg-info/requires.txt -src/pygcode.egg-info/top_level.txt \ No newline at end of file diff --git a/src/pygcode.egg-info/dependency_links.txt b/src/pygcode.egg-info/dependency_links.txt deleted file mode 100644 index 8b13789..0000000 --- a/src/pygcode.egg-info/dependency_links.txt +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/pygcode.egg-info/not-zip-safe b/src/pygcode.egg-info/not-zip-safe deleted file mode 100644 index 8b13789..0000000 --- a/src/pygcode.egg-info/not-zip-safe +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/pygcode.egg-info/requires.txt b/src/pygcode.egg-info/requires.txt deleted file mode 100644 index e0978f0..0000000 --- a/src/pygcode.egg-info/requires.txt +++ /dev/null @@ -1,3 +0,0 @@ -argparse -euclid3 -six diff --git a/src/pygcode.egg-info/top_level.txt b/src/pygcode.egg-info/top_level.txt deleted file mode 100644 index d560b01..0000000 --- a/src/pygcode.egg-info/top_level.txt +++ /dev/null @@ -1 +0,0 @@ -pygcode diff --git a/src/pygcode/dialects/linuxcnc.py b/src/pygcode/dialects/linuxcnc.py index 7dbe096..50c907c 100644 --- a/src/pygcode/dialects/linuxcnc.py +++ b/src/pygcode/dialects/linuxcnc.py @@ -18,20 +18,28 @@ from .utils import WordType # ======================== WORDS ======================== -REGEX_FLOAT = re.compile(r'^\s*-?(\d+\.?\d*|\.\d+)') # testcase: ..tests.test_words.WordValueMatchTests.test_float -REGEX_INT = re.compile(r'^\s*-?\d+') +REGEX_NUMBER = re.compile(r'^\s*-?(\d+\.?\d*|\.\d+)') # testcase: ..tests.test_words.WordValueMatchTests.test_float REGEX_POSITIVEINT = re.compile(r'^\s*\d+') REGEX_CODE = re.compile(r'^\s*\d+(\.\d)?') # float, but can't be negative -# Value cleaning functions -def _clean_codestr(value): - if value < 10: - return "0%g" % value - return "%g" % value +def CLASS_NUMBER(value): + if isinstance(value, (int, float)): + return value + if '.' in value: + return float(value) + return int(value) +# Value cleaning functions CLEAN_NONE = lambda v: v -CLEAN_FLOAT = lambda v: "{0:g}".format(round(v, 3)) -CLEAN_CODE = _clean_codestr + +def CLEAN_NUMBER(v): + if isinstance(v, int): + return str(v) + fstr = "{0:g}".format(round(v, 3)) # FIXME bad practice for inches + if '.' not in fstr: + return fstr + '.' + return fstr +CLEAN_CODE = lambda v: '{:02}'.format(v) CLEAN_INT = lambda v: "%g" % v WORD_MAP = { @@ -40,74 +48,74 @@ WORD_MAP = { # Rotational Axes 'A': WordType( - cls=float, - value_regex=REGEX_FLOAT, + cls=CLASS_NUMBER, + value_regex=REGEX_NUMBER, description="Absolute or incremental position of A axis (rotational axis around X axis)", - clean_value=CLEAN_FLOAT, + clean_value=CLEAN_NUMBER, ), 'B': WordType( - cls=float, - value_regex=REGEX_FLOAT, + cls=CLASS_NUMBER, + value_regex=REGEX_NUMBER, description="Absolute or incremental position of B axis (rotational axis around Y axis)", - clean_value=CLEAN_FLOAT, + clean_value=CLEAN_NUMBER, ), 'C': WordType( - cls=float, - value_regex=REGEX_FLOAT, + cls=CLASS_NUMBER, + value_regex=REGEX_NUMBER, description="Absolute or incremental position of C axis (rotational axis around Z axis)", - clean_value=CLEAN_FLOAT, + clean_value=CLEAN_NUMBER, ), 'D': WordType( - cls=float, - value_regex=REGEX_FLOAT, + cls=CLASS_NUMBER, + value_regex=REGEX_NUMBER, 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, + clean_value=CLEAN_NUMBER, ), # Feed Rates 'E': WordType( - cls=float, - value_regex=REGEX_FLOAT, + cls=CLASS_NUMBER, + value_regex=REGEX_NUMBER, description="Precision feedrate for threading on lathes", - clean_value=CLEAN_FLOAT, + clean_value=CLEAN_NUMBER, ), 'F': WordType( - cls=float, - value_regex=REGEX_FLOAT, + cls=CLASS_NUMBER, + value_regex=REGEX_NUMBER, description="Feedrate", - clean_value=CLEAN_FLOAT, + clean_value=CLEAN_NUMBER, ), # G-Codes 'G': WordType( - cls=float, + cls=CLASS_NUMBER, value_regex=REGEX_CODE, description="Address for preparatory commands", - clean_value=CLEAN_CODE, + clean_value=CLEAN_NONE, ), # Tool Offsets 'H': WordType( - cls=float, - value_regex=REGEX_FLOAT, + cls=CLASS_NUMBER, + value_regex=REGEX_NUMBER, description="Defines tool length offset; Incremental axis corresponding to C axis (e.g., on a turn-mill)", - clean_value=CLEAN_FLOAT, + clean_value=CLEAN_NUMBER, ), # Arc radius center coords 'I': WordType( - cls=float, - value_regex=REGEX_FLOAT, + cls=CLASS_NUMBER, + value_regex=REGEX_NUMBER, 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, + clean_value=CLEAN_NUMBER, ), 'J': WordType( - cls=float, - value_regex=REGEX_FLOAT, + cls=CLASS_NUMBER, + value_regex=REGEX_NUMBER, 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, + clean_value=CLEAN_NUMBER, ), 'K': WordType( - cls=float, - value_regex=REGEX_FLOAT, + cls=CLASS_NUMBER, + value_regex=REGEX_NUMBER, 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, + clean_value=CLEAN_NUMBER, ), # Loop Count 'L': WordType( @@ -118,10 +126,10 @@ WORD_MAP = { ), # Miscellaneous Function 'M': WordType( - cls=float, + cls=CLASS_NUMBER, value_regex=REGEX_CODE, description="Miscellaneous function", - clean_value=CLEAN_CODE, + clean_value=CLEAN_NONE, ), # Line Number 'N': WordType( @@ -139,31 +147,31 @@ WORD_MAP = { ), # Parameter (arbitrary parameter) 'P': WordType( - cls=float, # parameter is often an integer, but can be a float - value_regex=REGEX_FLOAT, + cls=CLASS_NUMBER, # parameter is often an integer, but can be a float + value_regex=REGEX_NUMBER, description="Serves as parameter address for various G and M codes", - clean_value=CLEAN_FLOAT, + clean_value=CLEAN_NUMBER, ), # Peck increment 'Q': WordType( - cls=float, - value_regex=REGEX_FLOAT, + cls=CLASS_NUMBER, + value_regex=REGEX_NUMBER, description="Depth to increase on each peck; Peck increment in canned cycles", - clean_value=CLEAN_FLOAT, + clean_value=CLEAN_NUMBER, ), # Arc Radius 'R': WordType( - cls=float, - value_regex=REGEX_FLOAT, + cls=CLASS_NUMBER, + value_regex=REGEX_NUMBER, description="Defines size of arc radius, or defines retract height in milling canned cycles", - clean_value=CLEAN_FLOAT, + clean_value=CLEAN_NUMBER, ), # Spindle speed 'S': WordType( - cls=float, - value_regex=REGEX_FLOAT, + cls=CLASS_NUMBER, + value_regex=REGEX_NUMBER, description="Defines speed, either spindle speed or surface speed depending on mode", - clean_value=CLEAN_FLOAT, + clean_value=CLEAN_NUMBER, ), # Tool Selecton 'T': WordType( @@ -174,41 +182,41 @@ WORD_MAP = { ), # Incremental axes 'U': WordType( - cls=float, - value_regex=REGEX_FLOAT, + cls=CLASS_NUMBER, + value_regex=REGEX_NUMBER, 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, + clean_value=CLEAN_NUMBER, ), 'V': WordType( - cls=float, - value_regex=REGEX_FLOAT, + cls=CLASS_NUMBER, + value_regex=REGEX_NUMBER, description="Incremental axis corresponding to Y axis", - clean_value=CLEAN_FLOAT, + clean_value=CLEAN_NUMBER, ), 'W': WordType( - cls=float, - value_regex=REGEX_FLOAT, + cls=CLASS_NUMBER, + value_regex=REGEX_NUMBER, description="Incremental axis corresponding to Z axis (typically only lathe group A controls)", - clean_value=CLEAN_FLOAT, + clean_value=CLEAN_NUMBER, ), # Linear Axes 'X': WordType( - cls=float, - value_regex=REGEX_FLOAT, + cls=CLASS_NUMBER, + value_regex=REGEX_NUMBER, description="Absolute or incremental position of X axis.", - clean_value=CLEAN_FLOAT, + clean_value=CLEAN_NUMBER, ), 'Y': WordType( - cls=float, - value_regex=REGEX_FLOAT, + cls=CLASS_NUMBER, + value_regex=REGEX_NUMBER, description="Absolute or incremental position of Y axis.", - clean_value=CLEAN_FLOAT, + clean_value=CLEAN_NUMBER, ), 'Z': WordType( - cls=float, - value_regex=REGEX_FLOAT, + cls=CLASS_NUMBER, + value_regex=REGEX_NUMBER, description="Absolute or incremental position of Z axis.", - clean_value=CLEAN_FLOAT, + clean_value=CLEAN_NUMBER, ), } diff --git a/src/pygcode/gcodes.py b/src/pygcode/gcodes.py index a2e5f34..fead76d 100644 --- a/src/pygcode/gcodes.py +++ b/src/pygcode/gcodes.py @@ -42,12 +42,12 @@ from .exceptions import GCodeParameterError, GCodeWordStrError # # There are 15 groups: # ref: http://linuxcnc.org/docs/html/gcode/overview.html#_modal_groups +# ref: https://www.haascnc.com/content/dam/haascnc/en/service/manual/operator/english---mill-ngc---operator's-manual---2017.pdf # # 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 +# Motion (Group 1) G0, G1, G2, G3, G33, G38.x, # 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 @@ -55,6 +55,8 @@ from .exceptions import GCodeParameterError, GCodeWordStrError # 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 +# Can Cycles (Group 9) G73, G76, G80, G81, G82, G83, +# G84, G85, G86, G87, G88, G89 # Canned Cycles Return Mode (Group 10) G98, G99 # Coordinate System (Group 12) G54, G55, G56, G57, G58, G59, # G59.1, G59.2, G59.3 @@ -81,6 +83,7 @@ MODAL_GROUP_MAP = { 'units': 6, 'cutter_diameter_comp': 7, 'tool_length_offset': 8, + 'canned_cycle': 9, 'canned_cycles_return': 10, 'coordinate_system': 12, 'control_mode': 13, @@ -393,7 +396,6 @@ class GCodeProgramName(GCodeDefinition): # G38.2 - G38.5 Straight Probe # G33 K Spindle Synchronized Motion # G33.1 K Rigid Tapping -# G80 Cancel Canned Cycle class GCodeMotion(GCode): param_letters = set('XYZABCUVW') @@ -509,21 +511,6 @@ class GCodeRigidTapping(GCodeMotion): word_key = Word('G', 33.1) -class GCodeCancelCannedCycle(GCodeMotion): - """G80: Cancel Canned Cycle""" - word_key = Word('G', 80) - # Modal Group - # Technically G80 belongs to the motion modal group, however it's often - # expressed in the same line as another motion command. - # This is alowed, but executed just prior to any other motion command - # eg: G00 G80 - # will leave the machine in rapid motion mode - # Just running G80 will leave machine with no motion mode. - modal_group = None - exec_order = 241 - - def _process(self, machine): - machine.mode.motion = None # ======================= Canned Cycles ======================= @@ -536,10 +523,11 @@ class GCodeCancelCannedCycle(GCodeMotion): # G85 R L (P) Boring Cycle, Feed Out # G89 R L (P) Boring Cycle, Dwell, Feed Out # G76 P Z I J R K Q H L E Threading Cycle +# G80 Cancel Canned Cycle class GCodeCannedCycle(GCode): param_letters = set('XYZUVW') - modal_group = MODAL_GROUP_MAP['motion'] + modal_group = MODAL_GROUP_MAP['canned_cycle'] exec_order = 242 def _process(self, machine): @@ -554,11 +542,12 @@ class GCodeCannedCycle(GCode): moveto_coords.pop(machine.mode.plane_selection.normal_axis, None) # 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) + if hasattr(self, 'L'): + 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): @@ -577,7 +566,7 @@ class GCodeDrillingCycleDwell(GCodeCannedCycle): class GCodeDrillingCyclePeck(GCodeCannedCycle): """G83: Drilling Cycle, Peck""" - param_letters = GCodeCannedCycle.param_letters | set('RLQ') + param_letters = GCodeCannedCycle.param_letters | set('RLQ') | set('IJK') word_key = Word('G', 83) modal_param_letters = GCodeCannedCycle.param_letters | set('RQ') @@ -608,6 +597,19 @@ class GCodeThreadingCycle(GCodeCannedCycle): param_letters = GCodeCannedCycle.param_letters | set('PZIJRKQHLE') word_key = Word('G', 76) +class GCodeCancelCannedCycle(GCodeCannedCycle): + """ G80: Cancel Canned Cycle """ + param_letters = set() + word_key = Word('G', 80) + # Modal Group + # Technically G80 belongs to the motion modal group, however it's often + # expressed in the same line as another motion command. + # This is alowed, but executed just prior to any other motion command + # eg: G00 G80 + # will leave the machine in rapid motion mode + # Just running G80 will leave machine with no motion mode. + exec_order = 241 + # ======================= Distance Mode ======================= # CODE PARAMETERS DESCRIPTION diff --git a/src/pygcode/machine.py b/src/pygcode/machine.py index 46c2ad5..a946f3c 100644 --- a/src/pygcode/machine.py +++ b/src/pygcode/machine.py @@ -268,6 +268,14 @@ class Mode(object): # > $G # > [GC:G0 G54 G17 G21 G90 G94 M5 M9 T0 F0 S0] # ref: https://github.com/gnea/grbl/wiki/Grbl-v1.1-Commands#g---view-gcode-parser-state + # NOTE : there is no default mode for some machines (eg haas), so if the + # previous program leaves the machine in G91 it will remain in G91 at the + # beginning of the next program.. it is good practice to start programs + # with a few "safe startup" blocks for example + # G21 ( metric ) + # G0 G17 G40 G49 G80 G90 + # G54 ( set wcs ) + default_mode = ''' G0 (movement: rapid) G17 (plane_selection: X/Y plane) @@ -407,6 +415,10 @@ class Machine(object): self.state.cur_coord_sys = coord_sys_mode.coord_system_id # TODO: convert coord systems between inches/mm, G20/G21 respectively + # NOTE : on at least a Haas -- this cannot be changed when running a + # program -- the G20 / G21 codes don't actually change units, but if + # a G20 / G21 appears in the code and the machine settings do not match + # an error will be thrown. def modal_gcode(self, modal_params): """