Skip to content
Commits on Source (39)
*~
__pycache__
doc/sphinx/build/
doc/sphinx/source/api/
# Build files
__pycache__
_lextab.py
_parsetab.py
lextab.py
parser.out
parsetab.py
doc/sphinx/build/
# Generated files
doc/sphinx/source/api/
doc/sphinx/source/examples/
doc/sphinx/source/gcode-reference/rs-274/execution_order.rst
doc/sphinx/source/gcode-reference/rs-274/gcodes.rst
doc/sphinx/source/gcode-reference/rs-274/letters.rst
doc/sphinx/source/gcode-reference/rs-274/modal_groups.rst
doc/sphinx/source/gcode-reference/rs-274/parameters.rst
doc/sphinx/source/gcode-reference/linuxcnc/images/*_fr.png
doc/sphinx/source/gcode-reference/linuxcnc/images/*_fr.svg
doc/sphinx/source/gcode-reference/linuxcnc/tmp/
pandoc
PythonicGcodeMachine/PythonLexYacc/yacc.py
ressources
......
####################################################################################################
#
# PythonicGcodeMachine - @licence_header_description@
# PythonicGcodeMachine - A Python G-code Toolkit
# Copyright (C) 2018 Fabrice Salvaire
#
# This program is free software: you can redistribute it and/or modify
......@@ -19,6 +19,8 @@
####################################################################################################
"""Module to implement an AST for RS-274 G-code.
All classes are clonable.
"""
####################################################################################################
......@@ -64,12 +66,34 @@ __all__ = [
####################################################################################################
import math
import re
import colors
####################################################################################################
class Program:
class MachineMixin:
"""Mixin to define the target machine for the AST node"""
##############################################
def __init__(self, machine=None):
self._machine = machine
##############################################
@property
def machine(self):
return self._machine
@machine.setter
def machine(self, value):
self._machine = value
####################################################################################################
class Program(MachineMixin):
"""Class to implement a G-code program
......@@ -84,15 +108,28 @@ class Program:
str(program)
program2 = program.clone()
"""
##############################################
def __init__(self):
def __init__(self, machine=None):
super().__init__(machine)
self._lines = []
##############################################
def clone(self):
program = self.__class__(machine=self._machine)
for line in self:
program += line.clone()
return program
##############################################
def push(self, line):
self._lines.append(line)
......@@ -134,14 +171,53 @@ class Program:
####################################################################################################
class LineItem:
class CloneMixin:
"""Mixin to provide a method to clone value"""
@staticmethod
def _clone_value(value):
if hasattr(value, 'clone'):
return value.clone()
else:
return value
####################################################################################################
class LineItem(MachineMixin, CloneMixin):
"""Base class for line item"""
##############################################
def _check_value(self, value):
if (isinstance(value, (int, float)) or
isinstance(value, RealValue)):
return value
else:
try:
str_value = str(value)
except:
raise ValueError("Invalid value {}".format(value))
# Fixme:
from .Parser import GcodeParser, GcodeParserError
parser = GcodeParser()
try:
# Fixme: parser hack
ast = parser.parse('X' + value)
return ast[0].value
except GcodeParserError:
raise ValueError("Invalid G-code value {}".format(value))
##############################################
def ansi_str(self):
return str(self)
####################################################################################################
class Line:
class Line(MachineMixin):
"""Class to implement a G-code line
......@@ -162,11 +238,13 @@ class Line:
line += Comment('move')
line += Word('X', 10)
line += Comment('Y value')
line += Word('Y', 20)
line += Word('Y', 20.)
line += ParameterSetting('1', 1.2)
# using expression
# using expression, AST way
line += Word('Z', Addition(30, Multiply(Parameter(100), Cosine(30))))
# string way
line += Word('Z', '[30 + [#100 * cos[30]]]')
# Array interface
for item in line:
......@@ -175,6 +253,18 @@ class Line:
str(line)
print(line.ansi_str()) # use ANSI colors, see Line.ANSI_... attributes
a_line = line.clone()
Values can be passed as:
* int or float,
* AST for expression,
* any object that "str" evaluate to a valid G-code expression.
As a shortcut, a G/M-code operation can be passed as string::
line += 'G0'
Expression can be evaluated using :code:`float(obj.value)`, excepted when we must access a parameter
value.
......@@ -190,7 +280,9 @@ class Line:
##############################################
def __init__(self, deleted=False, line_number=None, comment=None):
def __init__(self, deleted=False, line_number=None, comment=None, machine=None):
super().__init__(machine)
self.deleted = deleted
self.line_number = line_number
......@@ -200,6 +292,16 @@ class Line:
##############################################
def clone(self):
line = self.__class__(self._deleted, self._line_number, self._comment, self._machine)
for item in self:
line += item.clone()
return line
##############################################
@property
def deleted(self):
return self._deleted
......@@ -233,13 +335,26 @@ class Line:
##############################################
def _push_item(self, item):
if not isinstance(item, LineItem):
item = Word.from_str(item)
# Fixme: try to parse ???
self._items.append(item)
def push_items(self, iterable):
"""Method to push an iterable"""
for item in iterable:
self.push(item)
def push(self, item):
if isinstance(item, LineItem):
self._items.append(item)
"""Method to push a valid item, a 'G/Mxxx' shortcut string, a list or tuple"""
if isinstance(item, (list, tuple, Line)):
self.push_items(item)
else:
raise ValueError
self._push_item(item)
def __iadd__(self, item):
"""push shortcut"""
self.push(item)
return self
......@@ -261,6 +376,75 @@ class Line:
##############################################
def iter_on_word(self):
for item in self:
if isinstance(item, Word):
yield item
def iter_on_gm_word(self):
for item in self.iter_on_word():
if item.is_gm_gcode:
yield item
def iter_on_x_word(self):
for item in self.iter_on_word():
if not item.is_gm_gcode:
yield item
def iter_on_letter(self, letters):
for item in self.iter_on_word():
if item.letter in letters:
yield item
def iter_on_g_word(self):
return self.iter_on_letter('G')
def iter_on_m_word(self):
return self.iter_on_letter('M')
def iter_on_setting(self):
for item in self:
if isinstance(item, ParameterSetting):
yield item
def iter_in_order(self):
words = [word for word in self.iter_on_gm_word()]
words.sort(key=lambda word: word.execution_order.index)
return words
##############################################
def toggle(self):
"""Toggle deleted flag"""
self._deleted = not self._deleted
def remove_line_number(self):
self._line_number = None
##############################################
def remove_comment(self):
self._comment = None
for i, item in enumerate(self):
if isinstance(item, Comment):
# self._items.pop(i)
del self._items[i]
##############################################
def check_modal_group(self):
modal_groups = {}
for item in self.iter_on_gm_word():
group = item.modal_group
modal_groups.setdefault(group, 0)
modal_groups[group] += 1
errors = [modal_group for modal_group, count in modal_groups.items() if count > 1]
if errors:
raise ValueError("Modal group errors {}".format(errors))
##############################################
def __repr__(self):
items = []
......@@ -311,11 +495,17 @@ class Comment(LineItem):
##############################################
def __init__(self, text):
def __init__(self, text, machine=None):
super().__init__(machine)
self.set(text)
##############################################
def clone(self):
return self.__class__(self._text, self._machine)
##############################################
def set(self, text):
if '(' in text:
raise ValueError('Comment cannot contains a "("')
......@@ -348,6 +538,7 @@ class Word(LineItem):
"""Class to implement word"""
# Fixme: config ???
LETTERS = (
'A', 'B', 'C', 'D',
'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', # 'N',
......@@ -355,14 +546,34 @@ class Word(LineItem):
'X', 'Y', 'Z',
)
WORD_RE = re.compile('(G|M)(\d+)')
##############################################
@classmethod
def from_str(cls, obj):
str_obj = str(obj)
match = cls.WORD_RE.match(str_obj)
if match is not None:
return cls(*match.groups())
else:
raise ValueError(obj)
##############################################
def __init__(self, letter, value):
def __init__(self, letter, value, machine=None):
super().__init__(machine)
self.letter = letter
self.value = value
##############################################
def clone(self):
return self.__class__(self._letter, self._clone_value(self._value), self._machine)
##############################################
@property
def letter(self):
return self._letter
......@@ -380,33 +591,110 @@ class Word(LineItem):
@value.setter
def value(self, value):
# float expression ...
self._value = value
self._value = self._check_value(value)
##############################################
def __repr__(self):
return 'Word({0._letter} {0._value})'.format(self)
return 'Word({0._letter}, {0._value})'.format(self)
def __str__(self):
return '{0._letter}{0._value}'.format(self)
def ansi_str(self):
if self._letter in 'GM':
if self._machine:
gm = self._machine.GM_LETTERS
else:
gm = 'GM'
if self._letter in gm:
return Line.ANSI_G(str(self))
else:
return Line.ANSI_X(self._letter) + Line.ANSI_VALUE(str(self._value))
##############################################
def _check_machine(self):
if self._machine is None:
raise NameError('Machine is not defined')
@property
def _machine_config(self):
self._check_machine()
return self._machine.config
##############################################
@property
def is_gm_gcode(self):
return self._machine_config.letters.is_gm_word(self)
@property
def is_axis_gcode(self):
return self._machine_config.letters.is_axis_word(self)
##############################################
@property
def is_valid_gcode(self):
if self.is_gm_gcode:
return str(self) in self._machine.config.gcodes
else:
return True
##############################################
@property
def _gcode_info(self):
return self._machine.config.gcodes[str(self)]
##############################################
@property
def gcode_info(self):
if self.is_gm_gcode:
return self._gcode_info
##############################################
@property
def meaning(self):
if self.is_gm_gcode:
return self._gcode_info.meaning
else:
return self._machine.config.letters[self.letter].meaning
##############################################
@property
def modal_group(self):
if self.is_gm_gcode:
return self._gcode_info.modal_group
else:
return None
##############################################
@property
def execution_order(self):
if self.is_gm_gcode:
return self._gcode_info.execution_order
else:
return None
####################################################################################################
class RealValue:
class RealValue(MachineMixin, CloneMixin):
"""Base class for real value"""
pass
####################################################################################################
class ParameterMixin:
"""Mixin for parameter"""
##############################################
def __init__(self, parameter):
......@@ -434,10 +722,16 @@ class ParameterSetting(LineItem, ParameterMixin):
##############################################
def __init__(self, parameter, value):
def __init__(self, parameter, value, machine=None):
super().__init__(machine)
ParameterMixin.__init__(self, parameter)
self.value = value
##############################################
def clone(self):
return self.__class__(self._parameter, self._clone_value(self._value), self._machine)
##############################################
@property
def value(self):
......@@ -445,8 +739,7 @@ class ParameterSetting(LineItem, ParameterMixin):
@value.setter
def value(self, value):
# float expression ...
self._value = value
self._value = self._check_value(value)
##############################################
......@@ -467,11 +760,17 @@ class Parameter(RealValue, ParameterMixin):
##############################################
def __init__(self, parameter):
def __init__(self, parameter, machine=None):
super().__init__(machine)
ParameterMixin.__init__(self, parameter)
##############################################
def clone(self):
return self.__class__(self._parameter, self._machine)
##############################################
def __repr__(self):
return 'Parameter({0._parameter})'.format(self)
......@@ -487,16 +786,24 @@ class Parameter(RealValue, ParameterMixin):
class UnaryOperation(RealValue):
"""Base class for unary operation"""
__function__ = None
__gcode__ = None
##############################################
def __init__(self, arg):
def __init__(self, arg, machine=None):
super().__init__(machine)
self.arg = arg
##############################################
def clone(self):
return self.__class__(self._clone_value(self._arg), self._machine)
##############################################
@property
def arg(self):
return self._arg
......@@ -578,17 +885,25 @@ class Tangent(UnaryOperation):
class BinaryOperation(RealValue):
"""Base class for binary operation"""
__function__ = None
__gcode__ = None
##############################################
def __init__(self, arg1, arg2):
def __init__(self, arg1, arg2, machine=None):
super().__init__(machine)
self.arg1 = arg1
self.arg2 = arg2
##############################################
def clone(self):
return self.__class__(self._clone_value(self._arg1), self._clone_value(self.arg2), self._machine)
##############################################
@property
def arg1(self):
return self._arg1
......
####################################################################################################
#
# PythonicGcodeMachine - A Python G-code Toolkit
# Copyright (C) 2018 Fabrice Salvaire
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
####################################################################################################
"""Module to implement a G-code implementation configuration and an Oriented Object API for the YAML
configuration files.
See YAML files for examples and :ref:`rs-274-reference-page`.
API implements an array interface or a dictionary interface for a table.
"""
####################################################################################################
__all__ = [
'Config',
'MeaningMixin',
'ExecutionGroup',
'ExecutionOrder',
'Gcode',
'GcodeSet',
'LetterSet',
'ModalGroup',
'ModalGroupSet',
'Parameter',
'ParameterSet',
]
####################################################################################################
import yaml
####################################################################################################
def ensure_list(obj):
if not isinstance(obj, list):
obj = [obj]
return obj
def format_gcode_list(gcodes):
return ' '.join([str(gcode) for gcode in gcodes])
####################################################################################################
class YamlMixin:
def _load_yaml(self, yaml_path):
with open(yaml_path, 'r') as fh:
data = yaml.load(fh.read())
return data
####################################################################################################
class MeaningMixin:
##############################################
def __init__(self, meaning):
self._meaning = str(meaning)
##############################################
@property
def meaning(self):
"""A comment"""
return self._meaning
####################################################################################################
class RstMixin:
##############################################
def _make_rst(self, headers, columns, **kwargs):
number_of_columns = len(headers)
if len(columns) != number_of_columns:
raise ValueError('Number of columns mismatch')
number_of_lines = len(self)
table = []
rule = ''
line_format = ''
for c, title in enumerate(headers):
if rule:
rule += ' '
line_format += ' '
length = len(title)
column = columns[c]
str_columns = []
if hasattr(self, 'sorted_iter'):
it = self.sorted_iter()
else:
it = self
for line_number, item in enumerate(it):
formater = kwargs.get('str_' + column, str)
field = getattr(item, column)
text = formater(field)
length = max(len(text), length)
str_columns.append(text)
rule += '='*length
line_format += '{:' + str(length) + '}'
table.append(str_columns)
rst = ''
rst += rule + '\n'
rst += line_format.format(*headers) + '\n'
rst += rule + '\n'
for line_number in range(number_of_lines):
fields = [table[c][line_number] for c in range(number_of_columns)]
rst += line_format.format(*fields) + '\n'
rst += rule + '\n'
return rst
##############################################
def _write_rst(self, path, *args, **kwargs):
print('Write {}'.format(path))
with open(path, 'w') as fh:
fh.write(self._make_rst(*args, **kwargs))
####################################################################################################
class Parameter(MeaningMixin):
##############################################
def __init__(self, index, meaning, value):
MeaningMixin.__init__(self, meaning)
self._index = int(index)
self._value = float(value)
##############################################
@property
def index(self):
"""Parameter's index (table key)"""
return self._index
@property
def default_value(self):
return self._value
##############################################
def __repr__(self):
return '#{0._index}: {0._meaning}'.format(self)
####################################################################################################
class ParameterSet(YamlMixin, RstMixin):
"""Class for the table of parameters."""
##############################################
def __init__(self, yaml_path):
data = self._load_yaml(yaml_path)
self._parameters = {}
for index, d in data.items():
parameter = Parameter(index, d['meaning'], d['value'])
self._parameters[index] = parameter
##############################################
def __len__(self):
return len(self._parameters)
def __iter__(self):
return iter(self._parameters.values())
def __getitem__(self, index):
return self._parameters[index]
##############################################
def to_rst(self, path):
self._write_rst(
path,
headers=('Parameter Number', 'Parameter Value', 'Comment'),
columns=('index', 'default_value', 'meaning'),
)
####################################################################################################
class Letter(MeaningMixin):
##############################################
def __init__(self, letter, meaning):
MeaningMixin.__init__(self, meaning)
self._letter = str(letter)
##############################################
@property
def letter(self):
"""G-code letter (table key)"""
return self._letter
##############################################
def __repr__(self):
return '#{0._letter}: {0._meaning}'.format(self)
####################################################################################################
class LetterSet(YamlMixin, RstMixin):
"""Class for the table of letters."""
GM_LETTERS = 'GM'
AXIS_LETTERS = 'XYZABC' # 6-axis
##############################################
def is_gm_letter(self, letter):
return letter in self.GM_LETTERS
def is_axis_letter(self, letter):
return letter in self.AXIS_LETTERS
##############################################
def is_gm_word(self, word):
return self.is_gm_letter(word.letter)
def is_axis_word(self, word):
return self.is_axis_letter(word.letter)
##############################################
def __init__(self, yaml_path):
data = self._load_yaml(yaml_path)
self._letters = {}
for letter, d in data.items():
self._letters[letter] = Letter(letter, d['meaning'])
##############################################
def __len__(self):
return len(self._letters)
def __iter__(self):
return iter(self._letters.values())
def __getitem__(self, letter):
return self._letters[letter]
##############################################
def to_rst(self, path):
self._write_rst(
path,
headers=('Letter', 'Meaning'),
columns=('letter', 'meaning'),
)
####################################################################################################
class Gcode(MeaningMixin):
##############################################
def __init__(self, gcode, meaning,
modal_group=None,
execution_order=None,
doc=None,
):
MeaningMixin.__init__(self, meaning)
self._gcode = str(gcode)
# Those are set later due to the initialisation process
self._modal_group = modal_group
self._execution_order = execution_order
self._doc = doc
##############################################
@property
def gcode(self):
"""G-code (table key)"""
return self._gcode
@property
def modal_group(self):
return self._modal_group
@property
def execution_order(self):
return self._execution_order
@property
def execution_order_index(self):
return self._execution_order.index
@property
def doc(self):
return self._doc
##############################################
def __str__(self):
return self._gcode
##############################################
def convert_doc(self, format):
import pypandoc
return pypandoc.convert_text(self.doc, 'rst', format=format)
####################################################################################################
class GcodeSet(YamlMixin, RstMixin):
"""Class for the table of G-codes."""
##############################################
def __init__(self, yaml_path):
data = self._load_yaml(yaml_path)
self._gcodes = {}
for gcode_txt, d in data.items():
gcode = Gcode(gcode_txt, d['meaning'])
self._gcodes[gcode_txt] = gcode
self._sorted_gcodes = None
##############################################
def __len__(self):
return len(self._gcodes)
def __iter__(self):
return iter(self._gcodes.values())
def __getitem__(self, code):
return self._gcodes[code]
def __contains__(self, code):
return code in self._gcodes
##############################################
def _sort(self):
if self._sorted_gcodes is None:
items = list(self)
items.sort(key=lambda item: str(ord(item.gcode[0])*1000) + item.gcode[1:])
self._sorted_gcodes = items
return self._sorted_gcodes
##############################################
def sorted_iter(self):
return iter(self._sort())
##############################################
def iter_on_slice(self, start, stop):
start_index = None
stop_index = None
for i, item in enumerate(self._sort()):
if item.gcode == start:
start_index = i
elif item.gcode == stop:
stop_index = i
if start_index > stop_index:
raise ValueError('{} > {}'.format(start, stop))
return iter(self._sorted_gcodes[start_index:stop_index+1])
##############################################
def to_rst(self, path):
self._write_rst(
path,
headers=('G-code', 'Meaning'),
columns=('gcode', 'meaning'),
)
####################################################################################################
class ExecutionGroup(MeaningMixin):
##############################################
def __init__(self, index, gcodes, raw_gcodes, meaning):
MeaningMixin.__init__(self, meaning)
self._index = int(index)
self._gcodes = list(gcodes)
self._raw_gcodes = list(raw_gcodes)
##############################################
@property
def index(self):
"""Order index (table key)"""
return self._index
@property
def gcodes(self):
"""G-Codes list"""
return self._gcodes
@property
def raw_gcodes(self):
"""Raw G-Codes list"""
return self._raw_gcodes
##############################################
def __str__(self):
return '#{0._index} Meaning: {0._meaning}'.format(self)
####################################################################################################
class ExecutionOrder(YamlMixin, RstMixin):
"""Class for the execution order table."""
##############################################
def __init__(self, yaml_path, gcode_set):
data = self._load_yaml(yaml_path)
self._order = []
count = 1
for index, d in data.items():
if index != count:
raise ValueError('Unexpected index {} versus {}'.format(index, count))
count += 1
raw_gcodes = ensure_list(d['gcodes'])
gcodes = []
for gcode in raw_gcodes:
if '-' in gcode:
start, stop = [int(code[1:]) for code in gcode.split('-')]
letter = gcode[0]
for i in range(start, stop+1):
_gcode = '{}{}'.format(letter, i)
gcodes.append(gcode_set[_gcode])
else:
try:
gcode = gcode_set[gcode]
except KeyError:
if gcode != 'COMMENT':
raise ValueError('Invalid G-code {}'.format(gcode))
gcodes.append(gcode)
group = ExecutionGroup(index, gcodes, raw_gcodes, d['meaning'])
self._order.append(group)
for gcode in gcodes:
if isinstance(gcode, Gcode):
gcode._execution_order = group
##############################################
def __len__(self):
return len(self._order)
def __iter__(self):
return iter(self._order)
def __getitem__(self, index):
return self._order[index]
##############################################
def to_rst(self, path):
self._write_rst(
path,
headers=('Order', 'G-codes', 'Comment'),
columns=('index', 'raw_gcodes', 'meaning'),
str_raw_gcodes=lambda gcodes: format_gcode_list(gcodes),
)
####################################################################################################
class ModalGroup(MeaningMixin):
##############################################
def __init__(self, index, gcodes, meaning):
MeaningMixin.__init__(self, meaning)
self._index = int(index)
self._gcodes = list(gcodes)
##############################################
@property
def index(self):
"""Group id (table key)"""
return self._index
@property
def gcodes(self):
"""G-Codes list"""
return self._gcodes
##############################################
def __repr__(self):
return '#{0._index}: ({1}) Meaning: {0._meaning}'.format(self, format_gcode_list(self._gcodes))
####################################################################################################
class ModalGroupSet(YamlMixin, RstMixin):
"""Class for the table of modal groups."""
##############################################
def __init__(self, yaml_path, gcode_set):
data = self._load_yaml(yaml_path)
self._groups = {}
for index, d in data.items():
gcodes = ensure_list(d['gcodes'])
gcodes = [gcode_set[gcode] for gcode in gcodes]
group = ModalGroup(index, gcodes, d['meaning'])
self._groups[index] = group
for gcode in gcodes:
gcode._modal_group = group
##############################################
def __len__(self):
return len(self._groups)
def __iter__(self):
return iter(self._groups.values())
def __getitem__(self, index):
return self._groups[index]
##############################################
def sorted_iter(self):
items = list(self)
items.sort(key=lambda item: item.index)
return items
##############################################
def to_rst(self, path):
self._write_rst(
path,
headers=('Group', 'G-codes', 'Comment'),
columns=('index', 'gcodes', 'meaning'),
str_gcodes=lambda gcodes: format_gcode_list(gcodes),
)
####################################################################################################
class Config:
"""Class to register a G-code implementation configuration.
An instance is build from a set of YAML files.
"""
##############################################
def __init__(self,
execution_order,
gcodes,
letters,
modal_groups,
parameters,
):
"""Each argument is a path to the corresponding YAML file.
"""
self._gcodes = GcodeSet(gcodes)
self._execution_order = ExecutionOrder(execution_order, self._gcodes)
self._modal_groups = ModalGroupSet(modal_groups, self._gcodes)
# self._letters = str(letters)
# self._parameters = str(parameters)
self._letters = LetterSet(letters)
self._parameters = ParameterSet(parameters)
self._load_doc()
##############################################
def _load_doc(self):
from . import GcodeDoc as gcode_doc
for obj in gcode_doc.__dict__.values():
if isinstance(obj, type):
self._load_gcode_doc_cls(obj)
##############################################
def _set_gcode_doc(self, gcode, cls):
rst_doc = cls.__doc__
rst_doc = rst_doc.replace('\n' + ' '*4, '\n')
self._gcodes[gcode]._doc = rst_doc
##############################################
def _load_gcode_doc_cls(self, cls):
cls_name = cls.__name__
for letter in self._letters.GM_LETTERS:
cls_name = cls_name.replace('_' + letter, ' ' + letter)
cls_name = cls_name.replace('_to', '-')
cls_name = cls_name.replace('_', '.')
gcodes = cls_name.split(' ')
i = 0
while i < len(gcodes):
gcode = gcodes[i]
if gcode.endswith('-'):
start = gcode[:-1]
i += 1
stop = gcodes[i]
for _gcode in self._gcodes.iter_on_slice(start, stop):
self._set_gcode_doc(str(_gcode), cls)
else:
self._set_gcode_doc(gcode, cls)
i += 1
##############################################
@property
def execution_order(self):
""":class:`ExecutionOrder` instance"""
return self._execution_order
@property
def gcodes(self):
""":class:`GcodeSet` instance"""
return self._gcodes
@property
def letters(self):
""":class:`LetterSet` instance"""
# if isinstance(self._letters, str):
# self._letters = LetterSet(self._letters)
return self._letters
@property
def modal_groups(self):
""":class:`ModalGroupSet` instance"""
return self._modal_groups
@property
def parameters(self):
""":class:`ParameterSet` instance"""
# if isinstance(self._parameters, str):
# self._parameters = ParameterSet(self._parameters)
return self._parameters
####################################################################################################
#
# PythonicGcodeMachine - A Python G-code Toolkit
# Copyright (C) 2018 Fabrice Salvaire
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
####################################################################################################
"""Module to implement a machine coordinate.
"""
####################################################################################################
__all__ = [
'Coordinate',
]
####################################################################################################
import numpy as np
####################################################################################################
class Coordinate:
##############################################
def __init__(self, *args, dimension=None):
if args:
self._v = numpy.array(args)
else:
self._v = numpy.zeros(dimension)
##############################################
def clone(self):
return self.__class__(self._v)
##############################################
@property
def dimension(self):
return self._v.shape[0]
##############################################
def __len__(self):
return self.dimension
def __iter__(self):
return iter(self._v)
def __getitem__(self, slice_):
return self._v[slice_]
##############################################
def set(self, v):
if isintance(self, Coordinate):
self._v = self._v
else:
self._v = v
##############################################
def __eq__(self, v):
return self._v == self._v
##############################################
def __iadd__(self, v):
self._v += self._v
return self
##############################################
def __isub__(self, v):
self._v -= self._v
return self
This diff is collapsed.
####################################################################################################
#
# PythonicGcodeMachine - @licence_header_description@
# PythonicGcodeMachine - A Python G-code Toolkit
# Copyright (C) 2018 Fabrice Salvaire
#
# This program is free software: you can redistribute it and/or modify
......@@ -18,7 +18,16 @@
#
####################################################################################################
__all__ = ['GcodeLexerError', 'GcodeLexer']
"""Module to implement a RS-274 G-code lexer."""
####################################################################################################
__all__ = [
'GcodeLexerError',
'GcodeLexer',
'GcodeLexerMixin',
'GcodeTokenMixin',
]
####################################################################################################
......@@ -36,11 +45,9 @@ class GcodeLexerError(ValueError):
####################################################################################################
class GcodeLexer:
class GcodeTokenMixin:
"""Class to implement a RS-274 G-code lexer.
"""
"""Mixin to define RS-274 G-code tokens. """
# List of token names.
tokens = (
......@@ -196,6 +203,12 @@ class GcodeLexer:
# raise GcodeLexerError("Illegal character @{} '{}'".format(t.lexpos, t.value))
raise GcodeLexerError(t.lexpos)
####################################################################################################
class GcodeLexerMixin:
"""Class to implement a RS-274 G-code lexer."""
##############################################
def __init__(self):
......@@ -209,6 +222,7 @@ class GcodeLexer:
module=self,
reflags=int(re.VERBOSE + re.IGNORECASE),
optimize=1,
lextab='_lextab',
**kwargs,
)
......@@ -226,3 +240,8 @@ class GcodeLexer:
if not token:
break
yield token
####################################################################################################
class GcodeLexer(GcodeLexerMixin, GcodeTokenMixin):
pass
####################################################################################################
#
# PythonicGcodeMachine - A Python G-code Toolkit
# Copyright (C) 2018 Fabrice Salvaire
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
####################################################################################################
"""Module to implement a basic G-code machine.
"""
####################################################################################################
__all__ = [
'GcodeMachine',
]
####################################################################################################
from pathlib import Path as Path
from .Config import Config
from .Parser import GcodeParser, GcodeParserError
####################################################################################################
class GcodeMachine:
PARSER_CLS = GcodeParser
##############################################
def __init__(self):
self._config = None
self.load_config()
self._parser = None
self.setup_parser()
##############################################
def load_config(self):
data_path = Path(__file__).parent.joinpath('data')
self._config = Config(
execution_order=data_path.joinpath('rs274-execution-order.yaml'),
gcodes=data_path.joinpath('rs274-gcodes.yaml'),
letters=data_path.joinpath('rs274-word-starting-letter.yaml'),
modal_groups=data_path.joinpath('rs274-modal-groups.yaml'),
parameters=data_path.joinpath('rs274-default-parameter-file.yaml'),
)
##############################################
def setup_parser(self):
self._parser = self.PARSER_CLS(machine=self)
##############################################
@property
def config(self):
return self._config
@property
def parser(self):
return self._parser
##############################################
def reset():
pass
####################################################################################################
#
# PythonicGcodeMachine - A Python G-code Toolkit
# Copyright (C) 2018 Fabrice Salvaire
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
####################################################################################################
"""Module to implement a basic G-code machine.
"""
####################################################################################################
__all__ = [
'MachineState',
]
####################################################################################################
from enum import Enum, auto
from .Coordinate import Coordinate
from .ToolSet import ToolSet
####################################################################################################
class PlaneSelection:
XY = auto()
XZ = auto()
YZ = auto()
class FeedRateMode:
UNITS_PER_MINUTE = auto()
INVERSE_TIME = auto()
####################################################################################################
class MachineState:
NUMBER_OF_COORDINATE_SYSTEMS = 9
##############################################
def __init__(self,
number_of_axes,
is_metric=True,
):
self._number_of_axes = int(number_of_axes)
self._coordinate = Coordinate(dimension=self._number_of_axes)
self._tool_set = ToolSet()
self._tool = None # T
self._is_metric = bool(is_metric)
self._use_metric = self._is_metric # G20 inch / G21 mm
self._use_absolut = True # G90 absolut / G91 incremental
# G17 XY-plane selection
# G18 XZ-plane selection
# G19 YZ-plane selection
self._plane = None
# G54 G55 G56 G57 G58 G59 G59.1 G59.2 G59.3
self._coordinate_system = None
self._feed_rate = 0 # F
# G93 units per minute
# G94 inverse time
self._feed_rate_mode = FeedRateMode.UNITS_PER_MINUTE
self._spindle_rate = 0 # S
### 4 M0 M1 M2 M30 M60 stopping
### 6 M6 tool change
### 7 M3 M4 M5 spindle turning
### 8 M7 M8 M9 coolant (special case: M7 and M8 may be active at the same time)
### 9 M48 M49 enable/disable feed and speed override switches
### 10 G98 G99 return mode in canned cycles
### 13 G61 G61.1 G64 path control mode
##############################################
@property
def number_of_axes(self):
return self._number_of_axes
@property
def coordinate(self):
return self._coordinate
##############################################
@property
def is_metric(self):
return self._is_metric
@property
def use_metric(self):
return self._use_metric
@use_metric.setter
def use_metric(self, value):
self._use_metric = bool(value)
##############################################
@property
def use_absolut(self):
return self._use_absolut
@use_absolut.setter
def use_absolut(self, value):
self._use_absolut = bool(value)
##############################################
@property
def plane(self):
return self._plane
@plane.setter
def plane(self, value):
self._plane = PlaneSelection(value)
##############################################
@property
def coordinate_system(self):
return self._coordinate_system
@coordinate_system.setter
def coordinate_system(self, value):
_value = int(value)
if not(1 <= _value <= self.NUMBER_OF_COORDINATE_SYSTEMS):
raise ValueError('Invalid coordinate system {}'.format(value))
self._coordinate_system = _value
##############################################
@property
def tool_set(self):
return self._tool_set
def load_tool(self, pocket):
"""Load the tool at the given carousel pocket.
Raise ValueError if KeyError.
"""
self._tool.toggle_loaded()
try:
self._tool = self._tool_set[pocket].toggle_loaded()
except KeyError:
raise ValueError('Invalid carousel pocket {}'.format(pocket))
##############################################
@property
def feed_rate(self):
return self._feed_rate
@feed_rate.setter
def feed_rate(self, value):
# negative feedback ?
self._feed_rate = float(value)
@property
def feed_rate_mode(self):
return self._feed_rate_mode
@feed_rate_mode.setter
def feed_rate_mode(self, value):
self._feed_rate_mode = FeedRateMode(value)
##############################################
@property
def spindle_rate(self):
return self._spindle_rate
@spindle_rate.setter
def spindle_rate(self, value):
_value = float(value)
if _value < 0:
raise ValueError('Negative spindle rate {}'.format(value))
self._spindle_rate = _value
####################################################################################################
#
# PythonicGcodeMachine - @licence_header_description@
# PythonicGcodeMachine - A Python G-code Toolkit
# Copyright (C) 2018 Fabrice Salvaire
#
# This program is free software: you can redistribute it and/or modify
......@@ -54,6 +54,8 @@ __all__ = [
####################################################################################################
from pathlib import Path
# https://rply.readthedocs.io/en/latest/
from ply import yacc
......@@ -186,7 +188,7 @@ class GcodeGrammarMixin:
def p_ordinary_comment(self, p):
'ordinary_comment : INLINE_COMMENT'
p[0] = Ast.Comment(p[1])
p[0] = Ast.Comment(p[1], self._machine)
# def p_message(self, p):
# 'message : left_parenthesis + {white_space} + letter_m + {white_space} + letter_s +
......@@ -197,7 +199,7 @@ class GcodeGrammarMixin:
def p_mid_line_word(self, p):
'mid_line_word : mid_line_letter real_value'
p[0] = Ast.Word(p[1], p[2])
p[0] = Ast.Word(p[1], p[2], self._machine)
def p_mid_line_letter(self, p):
# LETTER
......@@ -226,11 +228,11 @@ class GcodeGrammarMixin:
def p_parameter_setting(self, p):
'parameter_setting : PARAMETER_SIGN parameter_index EQUAL_SIGN real_value'
p[0] = Ast.ParameterSetting(p[2], p[4])
p[0] = Ast.ParameterSetting(p[2], p[4], self._machine)
def p_parameter_value(self, p):
'parameter_value : PARAMETER_SIGN parameter_index'
p[0] = Ast.Parameter(p[2])
p[0] = Ast.Parameter(p[2], self._machine)
def p_parameter_index(self, p):
'parameter_index : real_value'
......@@ -340,12 +342,20 @@ class GcodeParserMixin:
##############################################
def __init__(self):
def __init__(self, machine=None):
self._machine = machine
self._build()
self._reset()
##############################################
@property
def machine(self):
return self._machine
##############################################
def _reset(self):
self._line = None
......@@ -359,8 +369,10 @@ class GcodeParserMixin:
self.tokens = self._lexer.tokens
self._parser = yacc.yacc(
module=self,
# debug=True,
optimize=0,
debug=False,
optimize=1,
tabmodule='_parsetab',
# outputdir=Path(__file__).parent.joinpath('ply'),
)
##############################################
......@@ -374,7 +386,7 @@ class GcodeParserMixin:
line = line.strip()
self._line = Ast.Line()
self._line = Ast.Line(machine=self._machine)
ast = self._parser.parse(
line,
lexer=self._lexer._lexer,
......@@ -395,9 +407,16 @@ class GcodeParserMixin:
Return a :class:`PythonicGcodeMachine.Gcode.Rs274.Ast.Program` instance.
"""
program = Ast.Program()
for line in lines.split('\n'):
program += self.parse(line)
if not isinstance(lines, (list, tuple)):
lines = lines.split('\n')
program = Ast.Program(machine=self._machine)
for line in lines:
try:
program += self.parse(line)
except GcodeParserError as exception:
print('Parse Error:', line)
raise exception
return program
......
####################################################################################################
#
# PythonicGcodeMachine - A Python G-code Toolkit
# Copyright (C) 2018 Fabrice Salvaire
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
####################################################################################################
"""Module to implement a basic tool set.
"""
####################################################################################################
__all__ = [
'Tool',
'LatheTool',
'ToolSet',
]
####################################################################################################
import yaml
####################################################################################################
class Tool:
"""Class to define a tool"""
##############################################
def __init__(self, tool_id, offset, diameter=None, comment=None):
self._id = tool_id
self._tool_offset = offset
self._tool_diameter = diameter
self._comment = comment
self._tool_set = None
self._pocket = None
self._loaded = False
##############################################
@property
def id(self):
return self._id
@tool_id.setter
def id(self, value):
self._id = str(value)
@property
def offset(self):
return self._offset
@offset.setter
def offset(self, value):
self._offset = value
@property
def diameter(self):
return self._diameter
@diameter.setter
def diameter(self, value):
self._diameter = float(value)
@property
def comment(self):
return self._comment
@comment.setter
def comment(self, value):
self._comment = str(value)
##############################################
@property
def tool_set(self):
return self._tool_set
@tool_set.setter
def tool_set(self, value):
self._tool_set = value
@property
def pocket(self):
return self._pocket
@pocket.setter
def pocket(self, value):
self._pocket = int(value)
##############################################
@property
def loaded(self):
return self._loaded
@loaded.setter
def loaded(self, value):
self._loaded = bool(value)
def toggle_loaded(self):
self._loaded = not self._loaded
return self
##############################################
def _to_dict(self, d, keys):
for key in keys:
d[key] = geattr(self, key)
##############################################
def to_dict(self):
keys = (
'id',
'tool_offset',
'tool_diameter',
'comment',
'pocket',
)
return self._to_dict({}, keys)
####################################################################################################
class LatheTool(Tool):
"""Class to define a lathe tool"""
##############################################
def __init__(self, tool_id, offset, front_angle, back_angle, orientation,
diameter=None, comment=None):
super(). __init__(tool_id, offset, diameter, comment)
self._front_angle = front_angle
self._back_angle = back_angle
self._orientation = orientation
##############################################
@property
def front_angle(self):
return self._front_angle
@front_angle.setter
def front_angle(self, value):
self._front_angle = float(value)
@property
def back_angle(self):
return self._back_angle
@back_angle.setter
def back_angle(self, value):
self._back_angle = float(value)
@property
def orientation(self):
return self._orientation
@orientation.setter
def orientation(self, value):
self._orientation = int(value)
##############################################
def to_dict(self):
d = super().to_dict()
keys = (
'front_angle',
'back_angle',
'orientation',
)
self._to_dict(d, keys)
return d
####################################################################################################
class ToolSet:
"""Class to define a tool set"""
##############################################
def __init__(self):
self._tools = {} # Free pocket implementation
##############################################
def __len__(self):
return len(self._tools)
def __iter__(self):
return iter(self._tools.values())
def __getitem__(self, pocket):
return self._tools[pocket]
##############################################
def remove_tool(self, pocket):
if isinstance(pocket, Tool):
pocket = pocket.pocket
tool = self._tools.pop(pocket)
tool.tool_set = None
tool.pocket = None
return tool
##############################################
def add_tool(self, tool, pocket):
old_tool = self.remove_tool(pocket)
self._tools[pocket] = tool
tool.tool_set = self
tool.pocket = pocket
return old_tool
##############################################
def load_yaml(self, path):
with (open(path, 'r')) as fh:
yaml_data = yaml.load(fh.read())
for pocket, d in yaml_data.items():
if 'front_angle' in d:
cls = LatheTool
else:
cls = Tool
tool = cls(*d)
self.add_tool(tool, pocket)
##############################################
def write_yaml(self, path):
data = {}
for tool in self:
d = tool.to_dict()
del d['pocket']
data[tool.pocket] = d
yaml_data = yaml.dump(data, default_flow_style=False)
with (open(path, 'w')) as fh:
fh.write(yaml_data)
#! /usr/bin/env python3
####################################################################################################
#
# PythonicGcodeMachine - A Python G-code Toolkit
# Copyright (C) 2018 Fabrice Salvaire
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
####################################################################################################
"""Script to generate rST files.
"""
####################################################################################################
import pathlib
from PythonicGcodeMachine.Gcode.Rs274.Machine import GcodeMachine
####################################################################################################
machine = GcodeMachine()
config = machine.config
source_path = pathlib.Path(__file__).absolute().parents[4]
print('Source:', source_path)
rst_path = source_path.joinpath('doc', 'sphinx', 'source', 'gcode-reference', 'rs-274')
print('rST:', rst_path)
config.execution_order.to_rst(rst_path.joinpath('execution_order.rst'))
config.gcodes.to_rst(rst_path.joinpath('gcodes.rst'))
config.letters.to_rst(rst_path.joinpath('letters.rst'))
config.modal_groups.to_rst(rst_path.joinpath('modal_groups.rst'))
config.parameters.to_rst(rst_path.joinpath('parameters.rst'))
#! /usr/bin/env python3
####################################################################################################
#
# PythonicGcodeMachine - A Python G-code Toolkit
# Copyright (C) 2018 Fabrice Salvaire
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
####################################################################################################
"""Script to generate YAML files from texts copied from the NIST PDF.
"""
####################################################################################################
import pathlib
......@@ -295,25 +319,18 @@ B-axis of machine
C-axis of machine
tool radius compensation number
feedrate
general function (see Table 5)
general function (See table G codes)
tool length offset index
X-axis offset for arcs
X offset in G87 canned cycle
Y-axis offset for arcs
Y offset in G87 canned cycle
Z-axis offset for arcs
Z offset in G87 canned cycle
number of repetitions in canned cycles
key used with G10
miscellaneous function (see Table 7)
X-axis offset for arcs / X offset in G87 canned cycle
Y-axis offset for arcs / Y offset in G87 canned cycle
Z-axis offset for arcs / Z offset in G87 canned cycle
number of repetitions in canned cycles / key used with G10
miscellaneous function (see Table M codes)
line number
dwell time in canned cycles
dwell time with G4
key used with G10
dwell time in canned cycles / dwell time with G4 / key used with G10
feed increment in G83 canned cycle
arc radius
canned cycle plane
spindle speed
canned cycle plane / spindle speed
tool selection
X-axis of machine
Y-axis of machine
......
# Table 8. Order of Execution
1: COMMENT # (includes message]
2: [G93, G94] # set feed rate mode (inverse time or per minute]
3: F # set feed rate
4: S # set spindle speed
5: T # select tool
6: M6 # change tool
7: [M3, M4, M5] # spindle on or off
8: [M7, M8, M9] # coolant on or off
9: [M48, M49] # enable or disable overrides
10: G4 # dwell
11: [G17, G18, G19] # set active plane
12: [G20, G21] # set length units
13: [G40, G41, G42] # cutter radius compensation on or off
14: [G43, G49] # cutter length compensation on or off
15: [G54, G55, G56, G57, G58, G59, G59.1, G59.2, G59.3] # coordinate system selection
16: [G61, G61.1, G64] # set path control mode
17: [G90, G91] # set distance mode
18: [G98, G99] # set retract mode
19: [G28, G30, G10, G92, G92.1, G92.2, G94] # home or change coordinate system data or set axis offsets
20: ['G0-G3', 'G80-G89', G53] # perform motion, as modified (possibly) by G53
21: [M0, M1, M2, M30, M60] # stop
1:
gcodes: COMMENT
meaning: includes message
2:
gcodes: [G93, G94]
meaning: set feed rate mode (inverse time or per minute)
3:
gcodes: F
meaning: set feed rate
4:
gcodes: S
meaning: set spindle speed
5:
gcodes: T
meaning: select tool
6:
gcodes: M6
meaning: change tool
7:
gcodes: [M3, M4, M5]
meaning: spindle on or off
8:
gcodes: [M7, M8, M9]
meaning: coolant on or off
9:
gcodes: [M48, M49]
meaning: enable or disable overrides
10:
gcodes: G4
meaning: dwell
11:
gcodes: [G17, G18, G19]
meaning: set active plane
12:
gcodes: [G20, G21]
meaning: set length units
13:
gcodes: [G40, G41, G42]
meaning: cutter radius compensation on or off
14:
gcodes: [G43, G49]
meaning: cutter length compensation on or off
15:
gcodes: [G54, G55, G56, G57, G58, G59, G59.1, G59.2, G59.3]
meaning: coordinate system selection
16:
gcodes: [G61, G61.1, G64]
meaning: set path control mode
17:
gcodes: [G90, G91]
meaning: set distance mode
18:
gcodes: [G98, G99]
meaning: set retract mode
19:
gcodes: [G28, G30, G10, G92, G92.1, G92.2, G94]
meaning: home or change coordinate system data or set axis offsets
20:
gcodes: ['G0-G3', 'G80-G89', G53]
meaning: perform motion, as modified (possibly) by G53
21:
gcodes: [M0, M1, M2, M30, M60]
meaning: stop
# Table 4. Modal Groups
# The modal groups for G codes are
1: [G0, G1, G2, G3, G38.2, G80, G81, G82, G83, G84, G85, G86, G87, G88, G89]
2 : [G17, G18, G19] # plane selection
3 : [G90, G91] # distance mode
5 : [G93, G94] # feed rate mode
6 : [G20, G21] # units
7 : [G40, G41, G42] # cutter radius compensation
8 : [G43, G49] # tool length offset
10 : [G98, G99] # return mode in canned cycles
12 : [G54, G55, G56, G57, G58, G59, G59.1, G59.2, G59.3] # coordinate system selection
13 : [G61, G61.1, G64] # path control mode
1:
gcodes: [G0, G1, G2, G3, G38.2, G80, G81, G82, G83, G84, G85, G86, G87, G88, G89]
meaning:
2 :
gcodes: [G17, G18, G19]
meaning: plane selection
3 :
gcodes: [G90, G91]
meaning: distance mode
5 :
gcodes: [G93, G94]
meaning: feed rate mode
6 :
gcodes: [G20, G21]
meaning: units
7 :
gcodes: [G40, G41, G42]
meaning: cutter radius compensation
8 :
gcodes: [G43, G49]
meaning: tool length offset
10 :
gcodes: [G98, G99]
meaning: return mode in canned cycles
12 :
gcodes: [G54, G55, G56, G57, G58, G59, G59.1, G59.2, G59.3]
meaning: coordinate system selection
13 :
gcodes: [G61, G61.1, G64]
meaning: path control mode
# The modal groups for M codes are
4 : [M0, M1, M2, M30, M60] # stopping
6 : [M6] # tool change
7 : [M3, M4, M5] # spindle turning
8 : [M7, M8, M9] # coolant (special case: M7 and M8 may be active at the same time)
9 : [M48, M49] # enable/disable feed and speed override switches
4 :
gcodes: [M0, M1, M2, M30, M60]
meaning: stopping
6 :
gcodes: [M6]
meaning: tool change
7 :
gcodes: [M3, M4, M5]
meaning: spindle turning
8 :
gcodes: [M7, M8, M9]
meaning: 'coolant (special case: M7 and M8 may be active at the same time)'
9 :
gcodes: [M48, M49]
meaning: enable/disable feed and speed override switches
# In addition to the above modal groups, there is a group for non-modal G codes
0 : [G4, G10, G28, G30, G53, G92, G92.1, G92.2, G92.3]
0 :
gcodes: [G4, G10, G28, G30, G53, G92, G92.1, G92.2, G92.3]
meaning: group for non-modal G codes
......@@ -9,34 +9,34 @@ D:
F:
meaning: feedrate
G:
meaning: general function (see Table 5)
meaning: general function (See table G codes)
H:
meaning: tool length offset index
I:
meaning: X-axis offset for arcs
meaning: X-axis offset for arcs / X offset in G87 canned cycle
J:
meaning: X offset in G87 canned cycle
meaning: Y-axis offset for arcs / Y offset in G87 canned cycle
K:
meaning: Y-axis offset for arcs
meaning: Z-axis offset for arcs / Z offset in G87 canned cycle
L:
meaning: Y offset in G87 canned cycle
meaning: number of repetitions in canned cycles / key used with G10
M:
meaning: Z-axis offset for arcs
meaning: miscellaneous function (see Table M codes)
N:
meaning: Z offset in G87 canned cycle
meaning: line number
P:
meaning: number of repetitions in canned cycles
meaning: dwell time in canned cycles / dwell time with G4 / key used with G10
Q:
meaning: key used with G10
meaning: feed increment in G83 canned cycle
R:
meaning: miscellaneous function (see Table 7)
meaning: arc radius
S:
meaning: line number
meaning: canned cycle plane / spindle speed
T:
meaning: dwell time in canned cycles
meaning: tool selection
X:
meaning: dwell time with G4
meaning: X-axis of machine
Y:
meaning: key used with G10
meaning: Y-axis of machine
Z:
meaning: feed increment in G83 canned cycle
meaning: Z-axis of machine
This directory contains PLY code, should be removed in future.
......@@ -4,7 +4,7 @@
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="generator" content="Docutils 0.14: http://docutils.sourceforge.net/" />
<title>The Pythonic Gcode Machine</title>
<title>The Pythonic G-code Machine</title>
<style type="text/css">
/*
......@@ -360,8 +360,8 @@ ul.auto-toc {
</style>
</head>
<body>
<div class="document" id="the-pythonic-gcode-machine">
<h1 class="title">The Pythonic Gcode Machine</h1>
<div class="document" id="the-pythonic-g-code-machine">
<h1 class="title">The Pythonic G-code Machine</h1>
<!-- -*- Mode: rst -*- -->
<!-- -*- Mode: rst -*- -->
......@@ -391,6 +391,23 @@ ul.auto-toc {
<h2>What is PythonicGcodeMachine ?</h2>
<!-- free and open source -->
<p>PythonicGcodeMachine is a Python toolkit to work with RS-274 / ISO G-Code.</p>
<!-- -*- mode: rst -*- -->
<p>PythonicGcodeMachine features:</p>
<ul class="simple">
<li>a compliant RS-274 / ISO G-code parser which is automatically generated from grammar and easy to
derivate to support other flavours,</li>
<li>an abstract syntax tree (AST) API,</li>
<li>some G-code flavour aspects are handled by YAML files for maximum flexibility,</li>
<li>tools to manipulate and validate G-code,</li>
<li>and more ..</li>
</ul>
<p>PythonicGcodeMachine supports these G-code flavours:</p>
<ul class="simple">
<li>RS-274 <strong>(full support)</strong></li>
<li>Fanuc <em>(partially)</em></li>
<li>Heidenhain <em>(partially)</em></li>
<li>LinuxCNC <em>(partially)</em></li>
</ul>
</div>
<div class="section" id="where-is-the-documentation">
<h2>Where is the Documentation ?</h2>
......
......@@ -79,9 +79,9 @@
.. |YAML| replace:: YAML
.. _YAML: https://yaml.org
============================
The Pythonic Gcode Machine
============================
=============================
The Pythonic G-code Machine
=============================
|Pypi License|
|Pypi Python Version|
......@@ -101,6 +101,24 @@ What is PythonicGcodeMachine ?
PythonicGcodeMachine is a Python toolkit to work with RS-274 / ISO G-Code.
.. -*- mode: rst -*-
PythonicGcodeMachine features:
* a compliant RS-274 / ISO G-code parser which is automatically generated from grammar and easy to
derivate to support other flavours,
* an abstract syntax tree (AST) API,
* some G-code flavour aspects are handled by YAML files for maximum flexibility,
* tools to manipulate and validate G-code,
* and more ..
PythonicGcodeMachine supports these G-code flavours:
* RS-274 **(full support)**
* Fanuc *(partially)*
* Heidenhain *(partially)*
* LinuxCNC *(partially)*
Where is the Documentation ?
----------------------------
......
......@@ -3,9 +3,9 @@
.. include:: project-links.txt
.. include:: abbreviation.txt
============================
The Pythonic Gcode Machine
============================
=============================
The Pythonic G-code Machine
=============================
|Pypi License|
|Pypi Python Version|
......@@ -25,6 +25,8 @@ What is PythonicGcodeMachine ?
PythonicGcodeMachine is a Python toolkit to work with RS-274 / ISO G-Code.
.. include:: features.txt
Where is the Documentation ?
----------------------------
......