Merge pull request #2 from fragmuffin/develop

v0.1.0 (the actual one)
This commit is contained in:
Peter Boin 2017-07-18 23:12:01 +10:00 committed by GitHub
commit 7f622ac445
16 changed files with 660 additions and 96 deletions

3
.gitignore vendored
View File

@ -4,3 +4,6 @@
# editor backups # editor backups
*.swp *.swp
# build
build/*

View File

@ -1,86 +1,233 @@
=======
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

102
deployment.md Normal file
View File

@ -0,0 +1,102 @@
# Notes on deployment
How I deployed this package (mainly just notes for myself)
Method based on the articles:
* http://peterdowns.com/posts/first-time-with-pypi.html and
* https://hynek.me/articles/sharing-your-labor-of-love-pypi-quick-and-dirty/
## Pre-requisites
```
pip install -U "pip>=1.4" "setuptools>=0.9" "wheel>=0.21" twine
```
## PyPi rc
`cat ~/.pypirc`
```
[distutils]
index-servers =
prod
test
[prod]
repository = https://upload.pypi.org/legacy/
username=FraggaMuffin
password=secret
[test]
repository=https://test.pypi.org/legacy/
username=FraggaMuffin
password=secret
```
`chmod 600 ~/.pypirc`
## Building
```
rm -rf build/
python setup.py sdist bdist_wheel
```
### Test Build (sdist)
**Python 2.x**
```
rmvirtualenv 27-test
mkvirtualenv 27-test
$WORKON_HOME/27-test/bin/pip install dist/pygcode-0.1.0.tar.gz
$WORKON_HOME/27-test/bin/python
>>> import pygcode
>>> pygcode.Line('g1 x2 y3 m3 s1000 f100').block.gcodes # or whatever
```
**Python 3.x**
```
rmvirtualenv 35-test
mkvirtualenv -p $(which python3) 35-test
$WORKON_HOME/35-test/bin/pip install dist/pygcode-0.1.0.tar.gz
$WORKON_HOME/35-test/bin/python
>>> import pygcode
>>> pygcode.Line('g1 x2 y3 m3 s1000 f100').block.gcodes # or whatever
```
### Test Build (wheel)
similar to above, but the `pip` call references `pygcode-0.1.0-py2.py3-none-any.whl` instead
make sure to `rmvirtualenv` to ensure `pygcode` is uninstalled from virtual environment
## Upload to PyPi Test server
`twine upload -r test dist/pygcode-0.1.0*`
Then another round of testing, where `pip` call is:
`$WORKON_HOME/<envname>/bin/pip install -i https://testpypi.python.org/pypi pygcode`
## Upload to PyPy server
all good!? sweet :+1: time to upload to 'production'
`twine upload -r prod dist/pygcode-0.1.0*`
and final tests with simply:
`$WORKON_HOME/<envname>/bin/pip install pygcode`

BIN
dist/pygcode-0.1.0-py2.py3-none-any.whl vendored Normal file

Binary file not shown.

BIN
dist/pygcode-0.1.0.tar.gz vendored Normal file

Binary file not shown.

View File

@ -2,5 +2,5 @@
universal = 1 universal = 1
[metadata] [metadata]
description-file = README.md description-file = README.rst
license_file = LICENSE license_file = LICENSE

View File

@ -5,30 +5,30 @@ import re
from setuptools import setup, find_packages from setuptools import setup, find_packages
# Setup template thanks to: Hynek Schlawack
# https://hynek.me/articles/sharing-your-labor-of-love-pypi-quick-and-dirty/
################################################################### ###################################################################
NAME = "attrs" NAME = "pygcode"
PACKAGES = find_packages(where="src") PACKAGES = find_packages(where="src")
META_PATH = os.path.join("src", "attr", "__init__.py") META_PATH = os.path.join("src", NAME, "__init__.py")
KEYWORDS = ["class", "attribute", "boilerplate"] KEYWORDS = ['gcode', 'cnc', 'parser', 'interpreter']
CLASSIFIERS = [ CLASSIFIERS = [
"Development Status :: 5 - Production/Stable", "Development Status :: 2 - Pre-Alpha", # see src/pygcode/__init__.py
"Intended Audience :: Developers", "Intended Audience :: Developers",
"Intended Audience :: Manufacturing",
"License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
"Natural Language :: English", "Natural Language :: English",
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent", "Operating System :: OS Independent",
"Programming Language :: Python", "Programming Language :: Python",
"Programming Language :: Python :: 2", "Programming Language :: Python :: 2",
"Programming Language :: Python :: 2.7",
"Programming Language :: Python :: 3", "Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.3", "Topic :: Scientific/Engineering",
"Programming Language :: Python :: 3.4", ]
"Programming Language :: Python :: 3.5", INSTALL_REQUIRES = [
"Programming Language :: Python :: Implementation :: CPython", 'euclid3', # 2D and 3D vector, matrix, quaternion and geometry module.
"Programming Language :: Python :: Implementation :: PyPy", 'six', # Python 2 and 3 compatibility utilities
"Topic :: Software Development :: Libraries :: Python Modules",
] ]
INSTALL_REQUIRES = []
################################################################### ###################################################################
@ -52,7 +52,7 @@ def find_meta(meta):
Extract __*meta*__ from META_FILE. Extract __*meta*__ from META_FILE.
""" """
meta_match = re.search( meta_match = re.search(
r"^(?P<name>__{meta}__)\s*=\s*['\"](?P<value>[^'\"]*)['\"]".format(meta=meta), r"^(?P<name>__{meta}__)\s*=\s*['\"](?P<value>[^'\"]*)['\"](\s*#.*)?$".format(meta=meta),
META_FILE, re.M META_FILE, re.M
) )
if meta_match: if meta_match:
@ -65,7 +65,7 @@ if __name__ == "__main__":
name=NAME, name=NAME,
description=find_meta("description"), description=find_meta("description"),
license=find_meta("license"), license=find_meta("license"),
url=find_meta("uri"), url=find_meta("url"),
version=find_meta("version"), version=find_meta("version"),
author=find_meta("author"), author=find_meta("author"),
author_email=find_meta("email"), author_email=find_meta("email"),
@ -79,18 +79,3 @@ if __name__ == "__main__":
classifiers=CLASSIFIERS, classifiers=CLASSIFIERS,
install_requires=INSTALL_REQUIRES, install_requires=INSTALL_REQUIRES,
) )
#VERSION = '0.1.dev' # *.dev = release candidate
#
#setup(
# name = 'pygcode',
# packages = ['pygcode'],
# version = VERSION,
# description = 'basic g-code parser, interpreter, and writer library',
# author = 'Peter Boin',
# author_email = 'peter.boin@gmail.com',
# url = 'https://github.com/fragmuffin/pygcode',
# download_url = 'https://github.com/fragmuffin/pygcode/archive/%s.tar.gz' % VERSION,
# keywords = ['gcode', 'cnc', 'parser', 'interpreter'],
# classifiers = [],
#)

View File

@ -0,0 +1,254 @@
Metadata-Version: 1.1
Name: pygcode
Version: 0.1.0
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: =======
pygcode
=======
GCODE Parser for Python
Currently in development, ``pygcode`` is a low-level GCode interpreter
for python.
Installation
============
Using `PyPi <https://pypi.python.org/pypi/pydemia>`__:
``pip install pygcode``
Usage
=====
Just brainstorming here...
Writing GCode
-------------
Writing gcode from python object instances to text
::
>>> 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))
G00 Z5
M03
G00 X10 Y20
F200
G01 Z-1.5
G00 Z5
M05
To plot along a lines of vectors, you could write...
::
>>> 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 ;)
Supported G-Codes
-----------------
All GCodes supported by `LinuxCNC <http://linuxcnc.org>`__ can be written, and
parsed by ``pygcode``.
Few GCodes are accurately interpreted by a virtual CNC ``Machine`` instance.
Supported movements are currently;
- linear movements
- arc movements
- canned drilling cycles
Keywords: gcode,cnc,parser,interpreter
Platform: UNKNOWN
Classifier: Development Status :: 2 - Pre-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

View File

@ -0,0 +1,19 @@
README.rst
setup.cfg
setup.py
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

View File

@ -0,0 +1 @@

View File

@ -0,0 +1 @@

View File

@ -0,0 +1,2 @@
euclid3
six

View File

@ -0,0 +1 @@
pygcode

View File

@ -1,3 +1,27 @@
# =========================== Package Information ===========================
# Version Planning:
# 0.1.x - Development Status :: 2 - Pre-Alpha
# 0.2.x - Development Status :: 3 - Alpha
# 0.3.x - Development Status :: 4 - Beta
# 1.x - Development Status :: 5 - Production/Stable
# <any above>.y - developments on that version (pre-release)
# <any above>*.dev* - development release (intended purely to test deployment)
__version__ = "0.1.0"
__title__ = "pygcode"
__description__ = "Basic g-code parser, interpreter, and encoder library."
__url__ = "https://github.com/fragmuffin/pygcode"
__author__ = "Peter Boin"
__email__ = "peter.boin@gmail.com"
__license__ = "GPLv3"
# not text-parsable
__copyright__ = "Copyright (c) 2017 {0}".format(__author__)
# =========================== Imports ===========================
__all__ = [ __all__ = [
# Machine # Machine
'Machine', 'Position', 'CoordinateSystem', 'State', 'Mode', 'Machine', 'Position', 'CoordinateSystem', 'State', 'Mode',
@ -12,7 +36,7 @@ __all__ = [
# GCodes # GCodes
'words2gcodes', 'text2gcodes', 'split_gcodes', 'words2gcodes', 'text2gcodes', 'split_gcodes',
# $ python -c "from pygcode.gcodes import GCode, _subclasses as sc; print(',\\n '.join(sorted('\\'%s\\'' % g.__name__ for g in sc(GCode))))"python -c "from pygcode.gcodes import GCode, _subclasses as sc; print(',\\n '.join(sorted('\\'%s\\'' % g.__name__ for g in sc(GCode))))" # $ python -c "from pygcode.gcodes import GCode, _subclasses as sc; print(',\\n '.join(sorted('\\'%s\\'' % g.__name__ for g in sc(GCode))))"
'GCode', 'GCode',
'GCodeAbsoluteArcDistanceMode', 'GCodeAbsoluteArcDistanceMode',
'GCodeAbsoluteDistanceMode', 'GCodeAbsoluteDistanceMode',
@ -30,6 +54,7 @@ __all__ = [
'GCodeCancelToolLengthOffset', 'GCodeCancelToolLengthOffset',
'GCodeCannedCycle', 'GCodeCannedCycle',
'GCodeCannedCycleReturnLevel', 'GCodeCannedCycleReturnLevel',
'GCodeCannedCycleReturnToR',
'GCodeCannedReturnMode', 'GCodeCannedReturnMode',
'GCodeCoolant', 'GCodeCoolant',
'GCodeCoolantFloodOn', 'GCodeCoolantFloodOn',
@ -174,7 +199,8 @@ from .gcodes import (
# G83 - GCodeDrillingCyclePeck: G83: Drilling Cycle, Peck # G83 - GCodeDrillingCyclePeck: G83: Drilling Cycle, Peck
# G76 - GCodeThreadingCycle: G76: Threading Cycle # G76 - GCodeThreadingCycle: G76: Threading Cycle
# - GCodeCannedReturnMode: # - GCodeCannedReturnMode:
# G98 - GCodeCannedCycleReturnLevel: G98: Canned Cycle Return Level # G98 - GCodeCannedCycleReturnLevel: G98: Canned Cycle Return to the level set prior to cycle start
# G99 - GCodeCannedCycleReturnToR: G99: Canned Cycle Return to the level set by R
# - GCodeCoolant: # - GCodeCoolant:
# M08 - GCodeCoolantFloodOn: M8: turn flood coolant on # M08 - GCodeCoolantFloodOn: M8: turn flood coolant on
# M07 - GCodeCoolantMistOn: M7: turn mist coolant on # M07 - GCodeCoolantMistOn: M7: turn mist coolant on
@ -305,6 +331,7 @@ from .gcodes import (
GCodeCancelToolLengthOffset, GCodeCancelToolLengthOffset,
GCodeCannedCycle, GCodeCannedCycle,
GCodeCannedCycleReturnLevel, GCodeCannedCycleReturnLevel,
GCodeCannedCycleReturnToR,
GCodeCannedReturnMode, GCodeCannedReturnMode,
GCodeCoolant, GCodeCoolant,
GCodeCoolantFloodOn, GCodeCoolantFloodOn,

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

View File

@ -1,10 +1,7 @@
import sys import sys
from copy import copy, deepcopy from copy import copy, deepcopy
if sys.version_info < (3, 0): from euclid3 import Vector3, Quaternion
from euclid import Vector3, Quaternion
else:
from euclid3 import Vector3, Quaternion
# ==================== Geometric Utilities ==================== # ==================== Geometric Utilities ====================