Skip to content
Commits on Source (39)
*~ *~
__pycache__
doc/sphinx/build/
doc/sphinx/source/api/
# Build files
__pycache__
_lextab.py
_parsetab.py
lextab.py lextab.py
parser.out parser.out
parsetab.py 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 PythonicGcodeMachine/PythonLexYacc/yacc.py
ressources ressources
......
#################################################################################################### ####################################################################################################
# #
# PythonicGcodeMachine - @licence_header_description@ # PythonicGcodeMachine - A Python G-code Toolkit
# Copyright (C) 2018 Fabrice Salvaire # Copyright (C) 2018 Fabrice Salvaire
# #
# This program is free software: you can redistribute it and/or modify # This program is free software: you can redistribute it and/or modify
...@@ -19,6 +19,8 @@ ...@@ -19,6 +19,8 @@
#################################################################################################### ####################################################################################################
"""Module to implement an AST for RS-274 G-code. """Module to implement an AST for RS-274 G-code.
All classes are clonable.
""" """
#################################################################################################### ####################################################################################################
...@@ -64,12 +66,34 @@ __all__ = [ ...@@ -64,12 +66,34 @@ __all__ = [
#################################################################################################### ####################################################################################################
import math import math
import re
import colors 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 """Class to implement a G-code program
...@@ -84,15 +108,28 @@ class Program: ...@@ -84,15 +108,28 @@ class Program:
str(program) str(program)
program2 = program.clone()
""" """
############################################## ##############################################
def __init__(self): def __init__(self, machine=None):
super().__init__(machine)
self._lines = [] self._lines = []
############################################## ##############################################
def clone(self):
program = self.__class__(machine=self._machine)
for line in self:
program += line.clone()
return program
##############################################
def push(self, line): def push(self, line):
self._lines.append(line) self._lines.append(line)
...@@ -134,14 +171,53 @@ class Program: ...@@ -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): def ansi_str(self):
return str(self) return str(self)
#################################################################################################### ####################################################################################################
class Line: class Line(MachineMixin):
"""Class to implement a G-code line """Class to implement a G-code line
...@@ -162,11 +238,13 @@ class Line: ...@@ -162,11 +238,13 @@ class Line:
line += Comment('move') line += Comment('move')
line += Word('X', 10) line += Word('X', 10)
line += Comment('Y value') line += Comment('Y value')
line += Word('Y', 20) line += Word('Y', 20.)
line += ParameterSetting('1', 1.2) line += ParameterSetting('1', 1.2)
# using expression # using expression, AST way
line += Word('Z', Addition(30, Multiply(Parameter(100), Cosine(30)))) line += Word('Z', Addition(30, Multiply(Parameter(100), Cosine(30))))
# string way
line += Word('Z', '[30 + [#100 * cos[30]]]')
# Array interface # Array interface
for item in line: for item in line:
...@@ -175,6 +253,18 @@ class Line: ...@@ -175,6 +253,18 @@ class Line:
str(line) str(line)
print(line.ansi_str()) # use ANSI colors, see Line.ANSI_... attributes 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 Expression can be evaluated using :code:`float(obj.value)`, excepted when we must access a parameter
value. value.
...@@ -190,7 +280,9 @@ class Line: ...@@ -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.deleted = deleted
self.line_number = line_number self.line_number = line_number
...@@ -200,6 +292,16 @@ class Line: ...@@ -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 @property
def deleted(self): def deleted(self):
return self._deleted return self._deleted
...@@ -233,13 +335,26 @@ class Line: ...@@ -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): def push(self, item):
if isinstance(item, LineItem): """Method to push a valid item, a 'G/Mxxx' shortcut string, a list or tuple"""
self._items.append(item) if isinstance(item, (list, tuple, Line)):
self.push_items(item)
else: else:
raise ValueError self._push_item(item)
def __iadd__(self, item): def __iadd__(self, item):
"""push shortcut"""
self.push(item) self.push(item)
return self return self
...@@ -261,6 +376,75 @@ class Line: ...@@ -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): def __repr__(self):
items = [] items = []
...@@ -311,11 +495,17 @@ class Comment(LineItem): ...@@ -311,11 +495,17 @@ class Comment(LineItem):
############################################## ##############################################
def __init__(self, text): def __init__(self, text, machine=None):
super().__init__(machine)
self.set(text) self.set(text)
############################################## ##############################################
def clone(self):
return self.__class__(self._text, self._machine)
##############################################
def set(self, text): def set(self, text):
if '(' in text: if '(' in text:
raise ValueError('Comment cannot contains a "("') raise ValueError('Comment cannot contains a "("')
...@@ -348,6 +538,7 @@ class Word(LineItem): ...@@ -348,6 +538,7 @@ class Word(LineItem):
"""Class to implement word""" """Class to implement word"""
# Fixme: config ???
LETTERS = ( LETTERS = (
'A', 'B', 'C', 'D', 'A', 'B', 'C', 'D',
'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', # 'N', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', # 'N',
...@@ -355,14 +546,34 @@ class Word(LineItem): ...@@ -355,14 +546,34 @@ class Word(LineItem):
'X', 'Y', 'Z', '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.letter = letter
self.value = value self.value = value
############################################## ##############################################
def clone(self):
return self.__class__(self._letter, self._clone_value(self._value), self._machine)
##############################################
@property @property
def letter(self): def letter(self):
return self._letter return self._letter
...@@ -380,33 +591,110 @@ class Word(LineItem): ...@@ -380,33 +591,110 @@ class Word(LineItem):
@value.setter @value.setter
def value(self, value): def value(self, value):
# float expression ... self._value = self._check_value(value)
self._value = value
############################################## ##############################################
def __repr__(self): def __repr__(self):
return 'Word({0._letter} {0._value})'.format(self) return 'Word({0._letter}, {0._value})'.format(self)
def __str__(self): def __str__(self):
return '{0._letter}{0._value}'.format(self) return '{0._letter}{0._value}'.format(self)
def ansi_str(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)) return Line.ANSI_G(str(self))
else: else:
return Line.ANSI_X(self._letter) + Line.ANSI_VALUE(str(self._value)) 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 pass
#################################################################################################### ####################################################################################################
class ParameterMixin: class ParameterMixin:
"""Mixin for parameter"""
############################################## ##############################################
def __init__(self, parameter): def __init__(self, parameter):
...@@ -434,10 +722,16 @@ class ParameterSetting(LineItem, ParameterMixin): ...@@ -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) ParameterMixin.__init__(self, parameter)
self.value = value self.value = value
##############################################
def clone(self):
return self.__class__(self._parameter, self._clone_value(self._value), self._machine)
############################################## ##############################################
@property @property
def value(self): def value(self):
...@@ -445,8 +739,7 @@ class ParameterSetting(LineItem, ParameterMixin): ...@@ -445,8 +739,7 @@ class ParameterSetting(LineItem, ParameterMixin):
@value.setter @value.setter
def value(self, value): def value(self, value):
# float expression ... self._value = self._check_value(value)
self._value = value
############################################## ##############################################
...@@ -467,11 +760,17 @@ class Parameter(RealValue, ParameterMixin): ...@@ -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) ParameterMixin.__init__(self, parameter)
############################################## ##############################################
def clone(self):
return self.__class__(self._parameter, self._machine)
##############################################
def __repr__(self): def __repr__(self):
return 'Parameter({0._parameter})'.format(self) return 'Parameter({0._parameter})'.format(self)
...@@ -487,16 +786,24 @@ class Parameter(RealValue, ParameterMixin): ...@@ -487,16 +786,24 @@ class Parameter(RealValue, ParameterMixin):
class UnaryOperation(RealValue): class UnaryOperation(RealValue):
"""Base class for unary operation"""
__function__ = None __function__ = None
__gcode__ = None __gcode__ = None
############################################## ##############################################
def __init__(self, arg): def __init__(self, arg, machine=None):
super().__init__(machine)
self.arg = arg self.arg = arg
############################################## ##############################################
def clone(self):
return self.__class__(self._clone_value(self._arg), self._machine)
##############################################
@property @property
def arg(self): def arg(self):
return self._arg return self._arg
...@@ -578,17 +885,25 @@ class Tangent(UnaryOperation): ...@@ -578,17 +885,25 @@ class Tangent(UnaryOperation):
class BinaryOperation(RealValue): class BinaryOperation(RealValue):
"""Base class for binary operation"""
__function__ = None __function__ = None
__gcode__ = None __gcode__ = None
############################################## ##############################################
def __init__(self, arg1, arg2): def __init__(self, arg1, arg2, machine=None):
super().__init__(machine)
self.arg1 = arg1 self.arg1 = arg1
self.arg2 = arg2 self.arg2 = arg2
############################################## ##############################################
def clone(self):
return self.__class__(self._clone_value(self._arg1), self._clone_value(self.arg2), self._machine)
##############################################
@property @property
def arg1(self): def arg1(self):
return self._arg1 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 # Copyright (C) 2018 Fabrice Salvaire
# #
# This program is free software: you can redistribute it and/or modify # This program is free software: you can redistribute it and/or modify
...@@ -18,7 +18,16 @@ ...@@ -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): ...@@ -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. # List of token names.
tokens = ( tokens = (
...@@ -196,6 +203,12 @@ class GcodeLexer: ...@@ -196,6 +203,12 @@ class GcodeLexer:
# raise GcodeLexerError("Illegal character @{} '{}'".format(t.lexpos, t.value)) # raise GcodeLexerError("Illegal character @{} '{}'".format(t.lexpos, t.value))
raise GcodeLexerError(t.lexpos) raise GcodeLexerError(t.lexpos)
####################################################################################################
class GcodeLexerMixin:
"""Class to implement a RS-274 G-code lexer."""
############################################## ##############################################
def __init__(self): def __init__(self):
...@@ -209,6 +222,7 @@ class GcodeLexer: ...@@ -209,6 +222,7 @@ class GcodeLexer:
module=self, module=self,
reflags=int(re.VERBOSE + re.IGNORECASE), reflags=int(re.VERBOSE + re.IGNORECASE),
optimize=1, optimize=1,
lextab='_lextab',
**kwargs, **kwargs,
) )
...@@ -226,3 +240,8 @@ class GcodeLexer: ...@@ -226,3 +240,8 @@ class GcodeLexer:
if not token: if not token:
break break
yield token 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 # Copyright (C) 2018 Fabrice Salvaire
# #
# This program is free software: you can redistribute it and/or modify # This program is free software: you can redistribute it and/or modify
...@@ -54,6 +54,8 @@ __all__ = [ ...@@ -54,6 +54,8 @@ __all__ = [
#################################################################################################### ####################################################################################################
from pathlib import Path
# https://rply.readthedocs.io/en/latest/ # https://rply.readthedocs.io/en/latest/
from ply import yacc from ply import yacc
...@@ -186,7 +188,7 @@ class GcodeGrammarMixin: ...@@ -186,7 +188,7 @@ class GcodeGrammarMixin:
def p_ordinary_comment(self, p): def p_ordinary_comment(self, p):
'ordinary_comment : INLINE_COMMENT' 'ordinary_comment : INLINE_COMMENT'
p[0] = Ast.Comment(p[1]) p[0] = Ast.Comment(p[1], self._machine)
# def p_message(self, p): # def p_message(self, p):
# 'message : left_parenthesis + {white_space} + letter_m + {white_space} + letter_s + # 'message : left_parenthesis + {white_space} + letter_m + {white_space} + letter_s +
...@@ -197,7 +199,7 @@ class GcodeGrammarMixin: ...@@ -197,7 +199,7 @@ class GcodeGrammarMixin:
def p_mid_line_word(self, p): def p_mid_line_word(self, p):
'mid_line_word : mid_line_letter real_value' '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): def p_mid_line_letter(self, p):
# LETTER # LETTER
...@@ -226,11 +228,11 @@ class GcodeGrammarMixin: ...@@ -226,11 +228,11 @@ class GcodeGrammarMixin:
def p_parameter_setting(self, p): def p_parameter_setting(self, p):
'parameter_setting : PARAMETER_SIGN parameter_index EQUAL_SIGN real_value' '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): def p_parameter_value(self, p):
'parameter_value : PARAMETER_SIGN parameter_index' '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): def p_parameter_index(self, p):
'parameter_index : real_value' 'parameter_index : real_value'
...@@ -340,12 +342,20 @@ class GcodeParserMixin: ...@@ -340,12 +342,20 @@ class GcodeParserMixin:
############################################## ##############################################
def __init__(self): def __init__(self, machine=None):
self._machine = machine
self._build() self._build()
self._reset() self._reset()
############################################## ##############################################
@property
def machine(self):
return self._machine
##############################################
def _reset(self): def _reset(self):
self._line = None self._line = None
...@@ -359,8 +369,10 @@ class GcodeParserMixin: ...@@ -359,8 +369,10 @@ class GcodeParserMixin:
self.tokens = self._lexer.tokens self.tokens = self._lexer.tokens
self._parser = yacc.yacc( self._parser = yacc.yacc(
module=self, module=self,
# debug=True, debug=False,
optimize=0, optimize=1,
tabmodule='_parsetab',
# outputdir=Path(__file__).parent.joinpath('ply'),
) )
############################################## ##############################################
...@@ -374,7 +386,7 @@ class GcodeParserMixin: ...@@ -374,7 +386,7 @@ class GcodeParserMixin:
line = line.strip() line = line.strip()
self._line = Ast.Line() self._line = Ast.Line(machine=self._machine)
ast = self._parser.parse( ast = self._parser.parse(
line, line,
lexer=self._lexer._lexer, lexer=self._lexer._lexer,
...@@ -395,9 +407,16 @@ class GcodeParserMixin: ...@@ -395,9 +407,16 @@ class GcodeParserMixin:
Return a :class:`PythonicGcodeMachine.Gcode.Rs274.Ast.Program` instance. Return a :class:`PythonicGcodeMachine.Gcode.Rs274.Ast.Program` instance.
""" """
program = Ast.Program() if not isinstance(lines, (list, tuple)):
for line in lines.split('\n'): lines = lines.split('\n')
program += self.parse(line)
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 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 #! /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 import pathlib
...@@ -295,25 +319,18 @@ B-axis of machine ...@@ -295,25 +319,18 @@ B-axis of machine
C-axis of machine C-axis of machine
tool radius compensation number tool radius compensation number
feedrate feedrate
general function (see Table 5) general function (See table G codes)
tool length offset index tool length offset index
X-axis offset for arcs X-axis offset for arcs / X offset in G87 canned cycle
X offset in G87 canned cycle Y-axis offset for arcs / Y offset in G87 canned cycle
Y-axis offset for arcs Z-axis offset for arcs / Z offset in G87 canned cycle
Y offset in G87 canned cycle number of repetitions in canned cycles / key used with G10
Z-axis offset for arcs miscellaneous function (see Table M codes)
Z offset in G87 canned cycle
number of repetitions in canned cycles
key used with G10
miscellaneous function (see Table 7)
line number line number
dwell time in canned cycles dwell time in canned cycles / dwell time with G4 / key used with G10
dwell time with G4
key used with G10
feed increment in G83 canned cycle feed increment in G83 canned cycle
arc radius arc radius
canned cycle plane canned cycle plane / spindle speed
spindle speed
tool selection tool selection
X-axis of machine X-axis of machine
Y-axis of machine Y-axis of machine
......
# Table 8. Order of Execution # Table 8. Order of Execution
1: COMMENT # (includes message] 1:
2: [G93, G94] # set feed rate mode (inverse time or per minute] gcodes: COMMENT
3: F # set feed rate meaning: includes message
4: S # set spindle speed 2:
5: T # select tool gcodes: [G93, G94]
6: M6 # change tool meaning: set feed rate mode (inverse time or per minute)
7: [M3, M4, M5] # spindle on or off 3:
8: [M7, M8, M9] # coolant on or off gcodes: F
9: [M48, M49] # enable or disable overrides meaning: set feed rate
10: G4 # dwell 4:
11: [G17, G18, G19] # set active plane gcodes: S
12: [G20, G21] # set length units meaning: set spindle speed
13: [G40, G41, G42] # cutter radius compensation on or off 5:
14: [G43, G49] # cutter length compensation on or off gcodes: T
15: [G54, G55, G56, G57, G58, G59, G59.1, G59.2, G59.3] # coordinate system selection meaning: select tool
16: [G61, G61.1, G64] # set path control mode 6:
17: [G90, G91] # set distance mode gcodes: M6
18: [G98, G99] # set retract mode meaning: change tool
19: [G28, G30, G10, G92, G92.1, G92.2, G94] # home or change coordinate system data or set axis offsets 7:
20: ['G0-G3', 'G80-G89', G53] # perform motion, as modified (possibly) by G53 gcodes: [M3, M4, M5]
21: [M0, M1, M2, M30, M60] # stop 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 # Table 4. Modal Groups
# The modal groups for G codes are # The modal groups for G codes are
1: [G0, G1, G2, G3, G38.2, G80, G81, G82, G83, G84, G85, G86, G87, G88, G89] 1:
2 : [G17, G18, G19] # plane selection gcodes: [G0, G1, G2, G3, G38.2, G80, G81, G82, G83, G84, G85, G86, G87, G88, G89]
3 : [G90, G91] # distance mode meaning:
5 : [G93, G94] # feed rate mode 2 :
6 : [G20, G21] # units gcodes: [G17, G18, G19]
7 : [G40, G41, G42] # cutter radius compensation meaning: plane selection
8 : [G43, G49] # tool length offset 3 :
10 : [G98, G99] # return mode in canned cycles gcodes: [G90, G91]
12 : [G54, G55, G56, G57, G58, G59, G59.1, G59.2, G59.3] # coordinate system selection meaning: distance mode
13 : [G61, G61.1, G64] # path control 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 # The modal groups for M codes are
4 : [M0, M1, M2, M30, M60] # stopping 4 :
6 : [M6] # tool change gcodes: [M0, M1, M2, M30, M60]
7 : [M3, M4, M5] # spindle turning meaning: stopping
8 : [M7, M8, M9] # coolant (special case: M7 and M8 may be active at the same time) 6 :
9 : [M48, M49] # enable/disable feed and speed override switches 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 # 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: ...@@ -9,34 +9,34 @@ D:
F: F:
meaning: feedrate meaning: feedrate
G: G:
meaning: general function (see Table 5) meaning: general function (See table G codes)
H: H:
meaning: tool length offset index meaning: tool length offset index
I: I:
meaning: X-axis offset for arcs meaning: X-axis offset for arcs / X offset in G87 canned cycle
J: J:
meaning: X offset in G87 canned cycle meaning: Y-axis offset for arcs / Y offset in G87 canned cycle
K: K:
meaning: Y-axis offset for arcs meaning: Z-axis offset for arcs / Z offset in G87 canned cycle
L: L:
meaning: Y offset in G87 canned cycle meaning: number of repetitions in canned cycles / key used with G10
M: M:
meaning: Z-axis offset for arcs meaning: miscellaneous function (see Table M codes)
N: N:
meaning: Z offset in G87 canned cycle meaning: line number
P: P:
meaning: number of repetitions in canned cycles meaning: dwell time in canned cycles / dwell time with G4 / key used with G10
Q: Q:
meaning: key used with G10 meaning: feed increment in G83 canned cycle
R: R:
meaning: miscellaneous function (see Table 7) meaning: arc radius
S: S:
meaning: line number meaning: canned cycle plane / spindle speed
T: T:
meaning: dwell time in canned cycles meaning: tool selection
X: X:
meaning: dwell time with G4 meaning: X-axis of machine
Y: Y:
meaning: key used with G10 meaning: Y-axis of machine
Z: 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 @@ ...@@ -4,7 +4,7 @@
<head> <head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="generator" content="Docutils 0.14: http://docutils.sourceforge.net/" /> <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"> <style type="text/css">
/* /*
...@@ -360,8 +360,8 @@ ul.auto-toc { ...@@ -360,8 +360,8 @@ ul.auto-toc {
</style> </style>
</head> </head>
<body> <body>
<div class="document" id="the-pythonic-gcode-machine"> <div class="document" id="the-pythonic-g-code-machine">
<h1 class="title">The Pythonic Gcode Machine</h1> <h1 class="title">The Pythonic G-code Machine</h1>
<!-- -*- Mode: rst -*- --> <!-- -*- Mode: rst -*- -->
<!-- -*- Mode: rst -*- --> <!-- -*- Mode: rst -*- -->
...@@ -391,6 +391,23 @@ ul.auto-toc { ...@@ -391,6 +391,23 @@ ul.auto-toc {
<h2>What is PythonicGcodeMachine ?</h2> <h2>What is PythonicGcodeMachine ?</h2>
<!-- free and open source --> <!-- free and open source -->
<p>PythonicGcodeMachine is a Python toolkit to work with RS-274 / ISO G-Code.</p> <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>
<div class="section" id="where-is-the-documentation"> <div class="section" id="where-is-the-documentation">
<h2>Where is the Documentation ?</h2> <h2>Where is the Documentation ?</h2>
......
...@@ -79,9 +79,9 @@ ...@@ -79,9 +79,9 @@
.. |YAML| replace:: YAML .. |YAML| replace:: YAML
.. _YAML: https://yaml.org .. _YAML: https://yaml.org
============================ =============================
The Pythonic Gcode Machine The Pythonic G-code Machine
============================ =============================
|Pypi License| |Pypi License|
|Pypi Python Version| |Pypi Python Version|
...@@ -101,6 +101,24 @@ What is PythonicGcodeMachine ? ...@@ -101,6 +101,24 @@ What is PythonicGcodeMachine ?
PythonicGcodeMachine is a Python toolkit to work with RS-274 / ISO G-Code. 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 ? Where is the Documentation ?
---------------------------- ----------------------------
......
...@@ -3,9 +3,9 @@ ...@@ -3,9 +3,9 @@
.. include:: project-links.txt .. include:: project-links.txt
.. include:: abbreviation.txt .. include:: abbreviation.txt
============================ =============================
The Pythonic Gcode Machine The Pythonic G-code Machine
============================ =============================
|Pypi License| |Pypi License|
|Pypi Python Version| |Pypi Python Version|
...@@ -25,6 +25,8 @@ What is PythonicGcodeMachine ? ...@@ -25,6 +25,8 @@ What is PythonicGcodeMachine ?
PythonicGcodeMachine is a Python toolkit to work with RS-274 / ISO G-Code. PythonicGcodeMachine is a Python toolkit to work with RS-274 / ISO G-Code.
.. include:: features.txt
Where is the Documentation ? Where is the Documentation ?
---------------------------- ----------------------------
......