updated readme

made S, F, and T gcodes instantiatable
This commit is contained in:
Peter Boin 2017-07-18 21:55:48 +10:00
parent a59c41a282
commit 549f82f203
2 changed files with 230 additions and 59 deletions

View File

@ -1,86 +1,232 @@
=======
pygcode pygcode
======= =======
GCODE Parser for Python GCODE Parser for Python
Currently in development, this is planned to be a pythonic interpreter Currently in development, ``pygcode`` is a low-level GCode interpreter
and encoder for g-code. I'll be learning along the way, but the plan is for python.
to follow the lead of `GRBL <https://github.com/gnea/grbl>`__.
Installation Installation
------------ ============
Using `PyPi <https://pypi.python.org/pypi/pydemia>`__:
``pip install pygcode`` ``pip install pygcode``
FIXME: well, that's the plan... give me some time to get it going
though.
Usage Usage
----- =====
Just brainstorming here... Just brainstorming here...
Writing GCode
-------------
Writing gcode from python object instances to text
:: ::
import pygcode >>> from pygcode import *
import math >>> gcodes = [
import euclid ... GCodeRapidMove(Z=5),
... GCodeStartSpindleCW(),
... GCodeRapidMove(X=10, Y=20),
... GCodeFeedRate(200),
... GCodeLinearMove(Z=-1.5),
... GCodeRapidMove(Z=5),
... GCodeStopSpindle(),
... ]
>>> print('\n'.join(str(g) for g in gcodes))
gfile_in = pygcode.parse('part1.gcode') # G00 Z5
gfile_out = pygcode.GCodeFile('part2.gcode') M03
G00 X10 Y20
total_travel = 0 F200
total_time = 0 G01 Z-1.5
G00 Z5
machine = pygcode.Machine() M05
for line in gfile_in.iterlines():
block = line.block
if block is None:
continue
# validation
if isinstance(block, pygcode.GCodeArc):
error = block.r2 - block.r1
if error > 0.0005:
raise pygcode.GCodeValidationError("arc points are not on the same circle")
#block.set_precision(0.0005, method=pygcode.GCodeArc.EFFECT_ENDPOINT)
block.set_precision(0.0005, method=pygcode.GCodeArc.EFFECT_RADIUS)
# random metrics
travel_vector = block.position - machine.state.position # euclid.Vector3 instance
distance = travel_vector.magnitude()
travel = block.travel_distance(position=machine.state.position) # eg: distance != travel for G02 & G03
total_travel += travel
#total_time += block.time(feed_rate=machine.state.feed_rate) # doesn't consider the feedrate being changed in this block
total_time += block.time(state=machine.state)
# rotate : entire file 90deg CCW
block.rotate(euclid.Quaternion.new_rotate_axis(
math.pi / 2, euclid.Vector3(0, 0, 1)
))
# translate : entire file x += 1, y += 2 mm (after rotation)
block.translate(euclid.Vector3(1, 2, 0), unit=pygcode.UNIT_MM)
To plot along a lines of vectors, you could write...
# TODO: then do something like write it to another file ::
gfile_out.write(block)
>>> from pygcode import *
>>> from euclid import Vector3
>>> vectors = [
... Vector3(0, 0, 0),
... Vector3(10, 0, 0),
... Vector3(10, 20, 0),
... Vector3(10, 20, 3),
... Vector3(0, 20, 3),
... Vector3(0, 0, 3),
... Vector3(0, 0, 0)
... ]
>>> to_coords = lambda v: {'X': v.x, 'Y': v.y, 'Z': v.z}
>>> for v in vectors:
... print("%s" % GCodeLinearMove(**to_coords(v)))
G01 X0 Y0 Z0
G01 X10 Y0 Z0
G01 X10 Y20 Z0
G01 X10 Y20 Z3
G01 X0 Y20 Z3
G01 X0 Y0 Z3
G01 X0 Y0 Z0
Reading / Interpreting GCode
----------------------------
To read gcode from a file, utilise the ``Line`` class.
Each ``Line`` instance contains a ``Block`` and an optional ``Comment``.
The ``Block`` contains a list of gcodes you're after.
::
from pygcode import Line
with open('part.gcode', 'r') as fh:
for line_text in fh.readlines():
line = Line(line_text)
print(line) # will print the line (with cosmetic changes)
line.block.gcodes # is your list of gcodes
line.block.modal_params # are all parameters not assigned to a gcode, assumed to be motion modal parameters
if line.comment:
line.comment.text # your comment text
To elaborate, here are some line examples
::
>>> from pygcode import Line
>>> line = Line('G01 x1 y2 f100 s1000 ; blah')
>>> print(line)
G01 X1 Y2 F100 S1000 ; blah
>>> print(line.block)
G01 X1 Y2 F100 S1000
>>> 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')
The simple line of a rapid move to ``x=10, y=10`` may be ``G00 X10 Y10``.
However, if the machine in question is in "Incremental Motion" mode ``G91`` then
the machine will only end up at ``x=10, y=10`` if it started at ``x=0, y=0``
So, GCode interpretation is done via a virtual machine:
::
>>> from pygcode import Machine, GCodeRapidMove
>>> m = Machine()
>>> m.pos
<Position: X0 Y0 Z0>
>>> g = GCodeRapidMove(X=10, Y=20)
>>> m.process_gcodes(g)
>>> m.pos
<Position: X10 Y20 Z0>
>>> m.process_gcodes(g)
>>> m.pos
<Position: X10 Y20 Z0> # same position; machine in absolute mode
>>> m.mode.distance
<GCodeAbsoluteDistanceMode: G90> # see
>>> m.process_gcodes(GCodeIncrementalDistanceMode())
>>> m.process_gcodes(g) # same gcode as above
>>> m.pos
<Position: X20 Y40 Z0>
all valid ``m.mode`` attributes can be found with ``from pygcode.gcodes import MODAL_GROUP_MAP; MODAL_GROUP_MAP.keys()``
Also note that the order codes are interpreted is important.
For example, the following code is WRONG
::
from pygcode import Machine, Line
m = Machine()
line = Line('G0 x10 y10 G91')
m.process_gcodes(*line.block.gcodes) # WRONG!
This will process the movement to ``x=10, y=10``, and **then** it will change the
distance mode to *Incremental*... there are 2 ways to do this correctly.
- ``m.process_gcodes(*sorted(line.block.gcodes))``, or simply
- ``m.process_block(line.block)``
sorting a list of gcodes will sort them in execution order (as specified by
`LinuxCNC's order of execution <http://linuxcnc.org/docs/html/gcode/overview.html#_g_code_order_of_execution>`__).
``process_block`` does this automatically.
If you need to process & change one type of gcode (usually a movement),
you must split a list of gcodes into those executed before, and after the one
in question.
::
from pygcode import GCodeRapidMove, GCodeLinearMove
from pygcode import Machine, Line, split_gcodes
m = Machine()
line = Line('M0 G0 x10 y10 G91')
(befores, (g,), afters) = split_gcodes(line.block.gcodes, (GCodeRapidMove, GCodeLinearMove))
m.process_gcodes(*sorted(befores))
if g.X is not None:
g.X += 100 # shift linear movements (rapid or otherwise)
m.process_gcodes(g)
m.process_gcodes(*sorted(afters))
For a more practical use of machines & interpreting gcode, have a look at
`pygcode-normalize.py <https://github.com/fragmuffin/pygcode/blob/master/scripts/pygcode-normalize.py>`__
At the time of writing this, that script converts arcs to linear codes, and
expands drilling cycles to basic movements (so my
`GRBL <https://github.com/gnea/grbl>`__ machine can understand them)
Development
===========
This library came from my own needs to interpret and convert erroneous
arcs to linear segments, and to expand canned drilling cycles, but also
as a means to *learn* GCode.
As such there is no direct plan for further development, however I'm
interested in what you'd like to use it for, and cater for that.
Generally, in terms of what to support, I'm following the lead of:
- `GRBL <https://github.com/gnea/grbl>`__ and
- `LinuxCNC <http://linuxcnc.org/>`__
More support will come with increased interest.
So that is... if you don't like what it does, or how it's documented, make some
noise in the `issue section <https://github.com/fragmuffin/pygcode/issues>`__.
if you get in early, you may get some free labour out of me ;)
gfile_in.close()
gfile_out.close()
Supported G-Codes Supported G-Codes
----------------- -----------------
GCode support is planned to follow that of All GCodes supported by `LinuxCNC <http://linuxcnc.org>`__ can be written, and
`GRBL <https://github.com/gnea/grbl>`__ which follows parsed by ``pygcode``.
`LinuxCNC <http://linuxcnc.org>`__ (list of gcodes documented
`here <http://linuxcnc.org/docs/html/gcode.html>`__).
But anything pre v1.0 will be a sub-set, focusing on the issues I'm Few GCodes are accurately interpreted by a virtual CNC ``Machine`` instance.
having... I'm selfish that way. Supported movements are currently;
TBD: list of gcodes (also as a TODO list) - linear movements
- arc movements
- canned drilling cycles

View File

@ -1,6 +1,7 @@
import sys import sys
from collections import defaultdict from collections import defaultdict
from copy import copy from copy import copy
import six
from .utils import Vector3, Quaternion, quat2coord_system from .utils import Vector3, Quaternion, quat2coord_system
from .words import Word, text2words from .words import Word, text2words
@ -141,6 +142,8 @@ class GCode(object):
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)
default_word = None default_word = None
word_letter = 'G'
word_value_configurable = False # if set, word value can be the first parameter
# Parameters associated to this gcode # Parameters associated to this gcode
param_letters = set() param_letters = set()
@ -161,6 +164,8 @@ class GCode(object):
param_words = words[1:] param_words = words[1:]
if gcode_word_list: if gcode_word_list:
gcode_word = gcode_word_list[0] gcode_word = gcode_word_list[0]
if self.word_value_configurable and isinstance(gcode_word, six.integer_types + (float,)):
gcode_word = Word(self.word_letter, gcode_word) # cast to Word()
else: else:
gcode_word = self._default_word() gcode_word = self._default_word()
assert isinstance(gcode_word, Word), "invalid gcode word %r" % gcode_word assert isinstance(gcode_word, Word), "invalid gcode word %r" % gcode_word
@ -610,6 +615,7 @@ class GCodeUnitsPerRevolution(GCodeFeedRateMode):
# G96, G97 S D Spindle Control Mode # G96, G97 S D Spindle Control Mode
class GCodeSpindle(GCode): class GCodeSpindle(GCode):
word_letter = 'M'
exec_order = 90 exec_order = 90
@ -642,6 +648,7 @@ class GCodeOrientSpindle(GCodeSpindle):
class GCodeSpindleSpeedMode(GCodeSpindle): class GCodeSpindleSpeedMode(GCodeSpindle):
word_letter = 'G'
modal_group = MODAL_GROUP_MAP['spindle_speed_mode'] modal_group = MODAL_GROUP_MAP['spindle_speed_mode']
@ -663,6 +670,7 @@ class GCodeSpindleRPMMode(GCodeSpindleSpeedMode):
# M7, M8, M9 Coolant Control # M7, M8, M9 Coolant Control
class GCodeCoolant(GCode): class GCodeCoolant(GCode):
word_letter = 'M'
modal_group = MODAL_GROUP_MAP['coolant'] modal_group = MODAL_GROUP_MAP['coolant']
exec_order = 110 exec_order = 110
@ -723,6 +731,7 @@ class GCodeCancelToolLengthOffset(GCodeToolLength):
# M60 Pallet Change Pause # M60 Pallet Change Pause
class GCodeProgramControl(GCode): class GCodeProgramControl(GCode):
word_letter = 'M'
modal_group = MODAL_GROUP_MAP['stopping'] modal_group = MODAL_GROUP_MAP['stopping']
exec_order = 250 exec_order = 250
@ -950,6 +959,8 @@ class GCodeOtherModal(GCode):
class GCodeFeedRate(GCodeOtherModal): class GCodeFeedRate(GCodeOtherModal):
"""F: Set Feed Rate""" """F: Set Feed Rate"""
word_letter = 'F'
word_value_configurable = True
@classmethod @classmethod
def word_matches(cls, w): def word_matches(cls, w):
return w.letter == 'F' return w.letter == 'F'
@ -960,6 +971,8 @@ class GCodeFeedRate(GCodeOtherModal):
class GCodeSpindleSpeed(GCodeOtherModal): class GCodeSpindleSpeed(GCodeOtherModal):
"""S: Set Spindle Speed""" """S: Set Spindle Speed"""
word_letter = 'S'
word_value_configurable = True
@classmethod @classmethod
def word_matches(cls, w): def word_matches(cls, w):
return w.letter == 'S' return w.letter == 'S'
@ -971,6 +984,8 @@ class GCodeSpindleSpeed(GCodeOtherModal):
class GCodeSelectTool(GCodeOtherModal): class GCodeSelectTool(GCodeOtherModal):
"""T: Select Tool""" """T: Select Tool"""
word_letter = 'T'
word_value_configurable = True
@classmethod @classmethod
def word_matches(cls, w): def word_matches(cls, w):
return w.letter == 'T' return w.letter == 'T'
@ -982,6 +997,7 @@ class GCodeSelectTool(GCodeOtherModal):
class GCodeSpeedAndFeedOverrideOn(GCodeOtherModal): class GCodeSpeedAndFeedOverrideOn(GCodeOtherModal):
"""M48: Speed and Feed Override Control On""" """M48: Speed and Feed Override Control On"""
word_letter = 'M'
word_key = Word('M', 48) word_key = Word('M', 48)
modal_group = MODAL_GROUP_MAP['override_switches'] modal_group = MODAL_GROUP_MAP['override_switches']
exec_order = 120 exec_order = 120
@ -989,6 +1005,7 @@ class GCodeSpeedAndFeedOverrideOn(GCodeOtherModal):
class GCodeSpeedAndFeedOverrideOff(GCodeOtherModal): class GCodeSpeedAndFeedOverrideOff(GCodeOtherModal):
"""M49: Speed and Feed Override Control Off""" """M49: Speed and Feed Override Control Off"""
word_letter = 'M'
word_key = Word('M', 49) word_key = Word('M', 49)
modal_group = MODAL_GROUP_MAP['override_switches'] modal_group = MODAL_GROUP_MAP['override_switches']
exec_order = 120 exec_order = 120
@ -996,6 +1013,7 @@ class GCodeSpeedAndFeedOverrideOff(GCodeOtherModal):
class GCodeFeedOverride(GCodeOtherModal): class GCodeFeedOverride(GCodeOtherModal):
"""M50: Feed Override Control""" """M50: Feed Override Control"""
word_letter = 'M'
param_letters = set('P') param_letters = set('P')
word_key = Word('M', 50) word_key = Word('M', 50)
exec_order = 120 exec_order = 120
@ -1003,6 +1021,7 @@ class GCodeFeedOverride(GCodeOtherModal):
class GCodeSpindleSpeedOverride(GCodeOtherModal): class GCodeSpindleSpeedOverride(GCodeOtherModal):
"""M51: Spindle Speed Override Control""" """M51: Spindle Speed Override Control"""
word_letter = 'M'
param_letters = set('P') param_letters = set('P')
word_key = Word('M', 51) word_key = Word('M', 51)
exec_order = 120 exec_order = 120
@ -1010,6 +1029,7 @@ class GCodeSpindleSpeedOverride(GCodeOtherModal):
class GCodeAdaptiveFeed(GCodeOtherModal): class GCodeAdaptiveFeed(GCodeOtherModal):
"""M52: Adaptive Feed Control""" """M52: Adaptive Feed Control"""
word_letter = 'M'
param_letters = set('P') param_letters = set('P')
word_key = Word('M', 52) word_key = Word('M', 52)
exec_order = 120 exec_order = 120
@ -1017,6 +1037,7 @@ class GCodeAdaptiveFeed(GCodeOtherModal):
class GCodeFeedStop(GCodeOtherModal): class GCodeFeedStop(GCodeOtherModal):
"""M53: Feed Stop Control""" """M53: Feed Stop Control"""
word_letter = 'M'
param_letters = set('P') param_letters = set('P')
word_key = Word('M', 53) word_key = Word('M', 53)
exec_order = 120 exec_order = 120
@ -1105,6 +1126,7 @@ class GCodeSelectCoordinateSystem9(GCodeSelectCoordinateSystem):
# M68 T Analog Output, Immediate # M68 T Analog Output, Immediate
class GCodeIO(GCode): class GCodeIO(GCode):
word_letter = 'M'
exec_order = 70 exec_order = 70
@ -1179,6 +1201,7 @@ class GCodeToolChange(GCodeNonModal):
"""M6: Tool Change""" """M6: Tool Change"""
param_letters = set('T') param_letters = set('T')
word_key = Word('M', 6) word_key = Word('M', 6)
word_letter = 'M'
exec_order = 80 exec_order = 80
@ -1186,6 +1209,7 @@ class GCodeToolSetCurrent(GCodeNonModal):
"""M61: Set Current Tool""" """M61: Set Current Tool"""
param_letters = set('Q') param_letters = set('Q')
word_key = Word('M', 61) word_key = Word('M', 61)
word_letter = 'M'
exec_order = 80 exec_order = 80
@ -1245,6 +1269,7 @@ class GCodeRestoreCoordSystemOffset(GCodeNonModal):
class GCodeUserDefined(GCodeNonModal): class GCodeUserDefined(GCodeNonModal):
"""M101-M199: User Defined Commands""" """M101-M199: User Defined Commands"""
word_letter = 'M'
# To create user g-codes, inherit from this class # To create user g-codes, inherit from this class
param_letters = set('PQ') param_letters = set('PQ')
#@classmethod #@classmethod