mirror of
https://git.mirrors.martin98.com/https://github.com/petaflot/pygcode
synced 2025-04-23 22:30:19 +08:00
updated readme
made S, F, and T gcodes instantiatable
This commit is contained in:
parent
a59c41a282
commit
549f82f203
264
README.rst
264
README.rst
@ -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
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user