Newer
Older
####################################################################################################
#
# 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__ = [
]
####################################################################################################
import yaml
####################################################################################################
def ensure_list(obj):
if not isinstance(obj, list):
obj = [obj]
return obj
####################################################################################################
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):
return self._meaning
####################################################################################################
class RstMixin:
##############################################
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()
it = self
for line_number, item in enumerate(it):
formater = kwargs.get('str_' + column, str)
field = getattr(item, column)
text = formater(field)
rule += '='*length
line_format += '{:' + str(length) + '}'
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):
return self._index
@property
def default_value(self):
return self._value
##############################################
def __repr__(self):
return '#{0._index}: {0._meaning}'.format(self)
####################################################################################################
##############################################
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):
##############################################
def __repr__(self):
return '#{0._letter}: {0._meaning}'.format(self)
####################################################################################################
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,
):
# Those are set later due to the initialisation process
self._modal_group = modal_group
self._execution_order = execution_order
##############################################
@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)
####################################################################################################
##############################################
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
##############################################
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 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'),
####################################################################################################
class ExecutionGroup(MeaningMixin):
##############################################
def __init__(self, index, gcodes, raw_gcodes, meaning):
MeaningMixin.__init__(self, meaning)
self._index = int(index)
self._gcodes = list(gcodes)
##############################################
@property
def index(self):
return self._index
@property
def gcodes(self):
@property
def raw_gcodes(self):
"""Raw G-Codes list"""
return self._raw_gcodes
##############################################
def __str__(self):
return '#{0._index} Meaning: {0._meaning}'.format(self)
####################################################################################################
##############################################
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])
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):
##############################################
def to_rst(self, path):
self._write_rst(
path,
####################################################################################################
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):
return self._index
@property
def gcodes(self):
##############################################
def __repr__(self):
return '#{0._index}: ({1}) Meaning: {0._meaning}'.format(self, ' '.join([str(gcode) for gcode in self._gcodes]))
####################################################################################################
##############################################
gcodes = ensure_list(d['gcodes'])
gcodes = [gcode_set[gcode] for gcode in gcodes]
group = ModalGroup(index, gcodes, d['meaning'])
##############################################
def __len__(self):
return len(self._groups)
def __iter__(self):
return iter(self._groups.values())
def __getitem__(self, 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: ' '.join([str(gcode) for gcode in 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)
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
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):
return self._execution_order
@property
def gcodes(self):
return self._gcodes
@property
def letters(self):
return self._letters
@property
def modal_groups(self):
return self._modal_groups
@property
def parameters(self):
""":class:`ParameterSet` instance"""
# if isinstance(self._parameters, str):
# self._parameters = ParameterSet(self._parameters)