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
=======
GCODE Parser for Python
Currently in development, this is planned to be a pythonic interpreter
and encoder for g-code. I'll be learning along the way, but the plan is
to follow the lead of `GRBL <https://github.com/gnea/grbl>`__.
Currently in development, ``pygcode`` is a low-level GCode interpreter
for python.
Installation
------------
============
Using `PyPi <https://pypi.python.org/pypi/pydemia>`__:
``pip install pygcode``
FIXME: well, that's the plan... give me some time to get it going
though.
Usage
-----
=====
Just brainstorming here...
Writing GCode
-------------
Writing gcode from python object instances to text
::
import pygcode
import math
import euclid
>>> from pygcode import *
>>> gcodes = [
... 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') #
gfile_out = pygcode.GCodeFile('part2.gcode')
total_travel = 0
total_time = 0
machine = pygcode.Machine()
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)
G00 Z5
M03
G00 X10 Y20
F200
G01 Z-1.5
G00 Z5
M05
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
-----------------
GCode support is planned to follow that of
`GRBL <https://github.com/gnea/grbl>`__ which follows
`LinuxCNC <http://linuxcnc.org>`__ (list of gcodes documented
`here <http://linuxcnc.org/docs/html/gcode.html>`__).
All GCodes supported by `LinuxCNC <http://linuxcnc.org>`__ can be written, and
parsed by ``pygcode``.
But anything pre v1.0 will be a sub-set, focusing on the issues I'm
having... I'm selfish that way.
Few GCodes are accurately interpreted by a virtual CNC ``Machine`` instance.
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
from collections import defaultdict
from copy import copy
import six
from .utils import Vector3, Quaternion, quat2coord_system
from .words import Word, text2words
@ -141,6 +142,8 @@ class GCode(object):
word_key = None # Word instance to use in lookup
word_matches = None # function (secondary)
default_word = None
word_letter = 'G'
word_value_configurable = False # if set, word value can be the first parameter
# Parameters associated to this gcode
param_letters = set()
@ -161,6 +164,8 @@ class GCode(object):
param_words = words[1:]
if gcode_word_list:
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:
gcode_word = self._default_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
class GCodeSpindle(GCode):
word_letter = 'M'
exec_order = 90
@ -642,6 +648,7 @@ class GCodeOrientSpindle(GCodeSpindle):
class GCodeSpindleSpeedMode(GCodeSpindle):
word_letter = 'G'
modal_group = MODAL_GROUP_MAP['spindle_speed_mode']
@ -663,6 +670,7 @@ class GCodeSpindleRPMMode(GCodeSpindleSpeedMode):
# M7, M8, M9 Coolant Control
class GCodeCoolant(GCode):
word_letter = 'M'
modal_group = MODAL_GROUP_MAP['coolant']
exec_order = 110
@ -723,6 +731,7 @@ class GCodeCancelToolLengthOffset(GCodeToolLength):
# M60 Pallet Change Pause
class GCodeProgramControl(GCode):
word_letter = 'M'
modal_group = MODAL_GROUP_MAP['stopping']
exec_order = 250
@ -950,6 +959,8 @@ class GCodeOtherModal(GCode):
class GCodeFeedRate(GCodeOtherModal):
"""F: Set Feed Rate"""
word_letter = 'F'
word_value_configurable = True
@classmethod
def word_matches(cls, w):
return w.letter == 'F'
@ -960,6 +971,8 @@ class GCodeFeedRate(GCodeOtherModal):
class GCodeSpindleSpeed(GCodeOtherModal):
"""S: Set Spindle Speed"""
word_letter = 'S'
word_value_configurable = True
@classmethod
def word_matches(cls, w):
return w.letter == 'S'
@ -971,6 +984,8 @@ class GCodeSpindleSpeed(GCodeOtherModal):
class GCodeSelectTool(GCodeOtherModal):
"""T: Select Tool"""
word_letter = 'T'
word_value_configurable = True
@classmethod
def word_matches(cls, w):
return w.letter == 'T'
@ -982,6 +997,7 @@ class GCodeSelectTool(GCodeOtherModal):
class GCodeSpeedAndFeedOverrideOn(GCodeOtherModal):
"""M48: Speed and Feed Override Control On"""
word_letter = 'M'
word_key = Word('M', 48)
modal_group = MODAL_GROUP_MAP['override_switches']
exec_order = 120
@ -989,6 +1005,7 @@ class GCodeSpeedAndFeedOverrideOn(GCodeOtherModal):
class GCodeSpeedAndFeedOverrideOff(GCodeOtherModal):
"""M49: Speed and Feed Override Control Off"""
word_letter = 'M'
word_key = Word('M', 49)
modal_group = MODAL_GROUP_MAP['override_switches']
exec_order = 120
@ -996,6 +1013,7 @@ class GCodeSpeedAndFeedOverrideOff(GCodeOtherModal):
class GCodeFeedOverride(GCodeOtherModal):
"""M50: Feed Override Control"""
word_letter = 'M'
param_letters = set('P')
word_key = Word('M', 50)
exec_order = 120
@ -1003,6 +1021,7 @@ class GCodeFeedOverride(GCodeOtherModal):
class GCodeSpindleSpeedOverride(GCodeOtherModal):
"""M51: Spindle Speed Override Control"""
word_letter = 'M'
param_letters = set('P')
word_key = Word('M', 51)
exec_order = 120
@ -1010,6 +1029,7 @@ class GCodeSpindleSpeedOverride(GCodeOtherModal):
class GCodeAdaptiveFeed(GCodeOtherModal):
"""M52: Adaptive Feed Control"""
word_letter = 'M'
param_letters = set('P')
word_key = Word('M', 52)
exec_order = 120
@ -1017,6 +1037,7 @@ class GCodeAdaptiveFeed(GCodeOtherModal):
class GCodeFeedStop(GCodeOtherModal):
"""M53: Feed Stop Control"""
word_letter = 'M'
param_letters = set('P')
word_key = Word('M', 53)
exec_order = 120
@ -1105,6 +1126,7 @@ class GCodeSelectCoordinateSystem9(GCodeSelectCoordinateSystem):
# M68 T Analog Output, Immediate
class GCodeIO(GCode):
word_letter = 'M'
exec_order = 70
@ -1179,6 +1201,7 @@ class GCodeToolChange(GCodeNonModal):
"""M6: Tool Change"""
param_letters = set('T')
word_key = Word('M', 6)
word_letter = 'M'
exec_order = 80
@ -1186,6 +1209,7 @@ class GCodeToolSetCurrent(GCodeNonModal):
"""M61: Set Current Tool"""
param_letters = set('Q')
word_key = Word('M', 61)
word_letter = 'M'
exec_order = 80
@ -1245,6 +1269,7 @@ class GCodeRestoreCoordSystemOffset(GCodeNonModal):
class GCodeUserDefined(GCodeNonModal):
"""M101-M199: User Defined Commands"""
word_letter = 'M'
# To create user g-codes, inherit from this class
param_letters = set('PQ')
#@classmethod