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 __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):
return self._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):
# 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
####################################################################################################
##############################################
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 sorted_iter(self):
items = list(self)
items.sort(key=lambda item: str(ord(item.gcode[0])*1000) + item.gcode[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
####################################################################################################
##############################################
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,
headers=('Order', 'G-codes', 'Comment'),
columns=('index', 'gcodes', 'meaning'),
####################################################################################################
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):
return 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'),
####################################################################################################
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. Files are loaded on demand (lazy loading).
"""
# self._letters = str(letters)
# self._parameters = str(parameters)
# self._gcodes = str(gcodes)
# self._modal_groups = str(modal_groups)
# self._execution_order = str(execution_order)
self._letters = Letters(letters)
self._parameters = ParameterSet(parameters)
self._gcodes = GcodeSet(gcodes)
self._execution_order = ExecutionOrder(execution_order, self._gcodes)
self._modal_groups = ModalGroups(modal_groups, self._gcodes)
##############################################
@property
def execution_order(self):
# if isinstance(self._execution_order, str):
# self._execution_order = ExecutionOrder(self._execution_order)
return self._execution_order
@property
def gcodes(self):
""":class:`GcodeSet` instance"""
# if isinstance(self._gcodes, str):
# self._gcodes = GcodeSet(self._gcodes)
return self._gcodes
@property
def letters(self):
# if isinstance(self._letters, str):
# self._letters = Letters(self._letters)
return self._letters
@property
def modal_groups(self):
# if isinstance(self._modal_groups, str):
# self._modal_groups = ModalGroups(self._modal_groups)
return self._modal_groups
@property
def parameters(self):
""":class:`ParameterSet` instance"""
# if isinstance(self._parameters, str):
# self._parameters = ParameterSet(self._parameters)