Skip to content
Config.py 16.1 KiB
Newer Older
Fabrice Salvaire's avatar
Fabrice Salvaire committed
####################################################################################################
#
Fabrice Salvaire's avatar
Fabrice Salvaire committed
# PythonicGcodeMachine - A Python G-code Toolkit
Fabrice Salvaire's avatar
Fabrice Salvaire committed
# 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/>.
#
####################################################################################################

Fabrice Salvaire's avatar
Fabrice Salvaire committed
"""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.

Fabrice Salvaire's avatar
Fabrice Salvaire committed
"""

####################################################################################################

__all__ = [
Fabrice Salvaire's avatar
Fabrice Salvaire committed
    'MeaningMixin',
    'ExecutionGroup',
    'ExecutionOrder',
Fabrice Salvaire's avatar
Fabrice Salvaire committed
    'Gcode',
Fabrice Salvaire's avatar
Fabrice Salvaire committed
    'GcodeSet',
Fabrice Salvaire's avatar
Fabrice Salvaire committed
    'Letters',
Fabrice Salvaire's avatar
Fabrice Salvaire committed
    'ModalGroup',
Fabrice Salvaire's avatar
Fabrice Salvaire committed
    'ModalGroups',
Fabrice Salvaire's avatar
Fabrice Salvaire committed
    'Parameter',
Fabrice Salvaire's avatar
Fabrice Salvaire committed
    'ParameterSet',
Fabrice Salvaire's avatar
Fabrice Salvaire committed
]

####################################################################################################

import yaml

####################################################################################################

Fabrice Salvaire's avatar
Fabrice Salvaire committed
def ensure_list(obj):
    if not isinstance(obj, list):
        obj = [obj]
    return obj

####################################################################################################

Fabrice Salvaire's avatar
Fabrice Salvaire committed
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):
Fabrice Salvaire's avatar
Fabrice Salvaire committed
        """A comment"""
Fabrice Salvaire's avatar
Fabrice Salvaire committed
        return self._meaning

####################################################################################################

Fabrice Salvaire's avatar
Fabrice Salvaire committed
class RstMixin:

    ##############################################

Fabrice Salvaire's avatar
Fabrice Salvaire committed
    def _make_rst(self, headers, columns, **kwargs):
Fabrice Salvaire's avatar
Fabrice Salvaire committed

Fabrice Salvaire's avatar
Fabrice Salvaire committed
        number_of_columns = len(headers)
        if len(columns) != number_of_columns:
            raise ValueError('Number of columns mismatch')
        number_of_lines = len(self)

        table = []
Fabrice Salvaire's avatar
Fabrice Salvaire committed
        rule = ''
        line_format = ''
        for c, title in enumerate(headers):
            if rule:
                rule += ' '
                line_format += ' '
            length = len(title)
Fabrice Salvaire's avatar
Fabrice Salvaire committed
            column = columns[c]
            str_columns = []
            if hasattr(self, 'sorted_iter'):
                it = self.sorted_iter()
Fabrice Salvaire's avatar
Fabrice Salvaire committed
            else:
Fabrice Salvaire's avatar
Fabrice Salvaire committed
                it = self
            for line_number, item in enumerate(it):
                formater = kwargs.get('str_' + column, str)
                field = getattr(item, column)
                text = formater(field)
Fabrice Salvaire's avatar
Fabrice Salvaire committed
                length = max(len(text), length)
Fabrice Salvaire's avatar
Fabrice Salvaire committed
                str_columns.append(text)
Fabrice Salvaire's avatar
Fabrice Salvaire committed
            rule += '='*length
            line_format += '{:' + str(length) + '}'
Fabrice Salvaire's avatar
Fabrice Salvaire committed
            table.append(str_columns)
Fabrice Salvaire's avatar
Fabrice Salvaire committed

        rst = ''
        rst += rule + '\n'
        rst += line_format.format(*headers) + '\n'
        rst += rule + '\n'
Fabrice Salvaire's avatar
Fabrice Salvaire committed
        for line_number in range(number_of_lines):
            fields = [table[c][line_number] for c in range(number_of_columns)]
Fabrice Salvaire's avatar
Fabrice Salvaire committed
            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))

####################################################################################################

Fabrice Salvaire's avatar
Fabrice Salvaire committed
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):
Fabrice Salvaire's avatar
Fabrice Salvaire committed
        """Parameter's index (table key)"""
Fabrice Salvaire's avatar
Fabrice Salvaire committed
        return self._index

    @property
    def default_value(self):
        return self._value

####################################################################################################

Fabrice Salvaire's avatar
Fabrice Salvaire committed
class ParameterSet(YamlMixin, RstMixin):
Fabrice Salvaire's avatar
Fabrice Salvaire committed
    """Class for the table of parameters."""

Fabrice Salvaire's avatar
Fabrice Salvaire committed
    ##############################################

    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]

Fabrice Salvaire's avatar
Fabrice Salvaire committed
    ##############################################

    def to_rst(self, path):
        self._write_rst(
            path,
            headers=('Parameter Number', 'Parameter Value', 'Comment'),
            columns=('index', 'default_value', 'meaning'),
        )

Fabrice Salvaire's avatar
Fabrice Salvaire committed
####################################################################################################

class Letter(MeaningMixin):

    ##############################################

    def __init__(self, letter, meaning):

        MeaningMixin.__init__(self, meaning)
        self._letter = str(letter)

    ##############################################

    @property
    def letter(self):
Fabrice Salvaire's avatar
Fabrice Salvaire committed
        """G-code letter (table key)"""
Fabrice Salvaire's avatar
Fabrice Salvaire committed
        return self._letter

####################################################################################################

Fabrice Salvaire's avatar
Fabrice Salvaire committed
class Letters(YamlMixin, RstMixin):
Fabrice Salvaire's avatar
Fabrice Salvaire committed
    """Class for the table of letters."""

Fabrice Salvaire's avatar
Fabrice Salvaire committed
    ##############################################

    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'])
Fabrice Salvaire's avatar
Fabrice Salvaire committed

    ##############################################

    def __len__(self):
        return len(self._letters)

    def __iter__(self):
        return iter(self._letters.values())

    def __getitem__(self, letter):
        return self._letters[letter]

Fabrice Salvaire's avatar
Fabrice Salvaire committed
    ##############################################

    def to_rst(self, path):
        self._write_rst(
            path,
            headers=('Letter', 'Meaning'),
            columns=('letter', 'meaning'),
        )

Fabrice Salvaire's avatar
Fabrice Salvaire committed
####################################################################################################

class Gcode(MeaningMixin):

    ##############################################

Fabrice Salvaire's avatar
Fabrice Salvaire committed
    def __init__(self, gcode, meaning, modal_group=None, execution_order=None):
Fabrice Salvaire's avatar
Fabrice Salvaire committed

        MeaningMixin.__init__(self, meaning)
Fabrice Salvaire's avatar
Fabrice Salvaire committed
        self._gcode = str(gcode)
Fabrice Salvaire's avatar
Fabrice Salvaire committed
        # Those are set later due to the initialisation process
        self._modal_group = modal_group
        self._execution_order = execution_order

Fabrice Salvaire's avatar
Fabrice Salvaire committed
    ##############################################

    @property
Fabrice Salvaire's avatar
Fabrice Salvaire committed
    def gcode(self):
        """G-code (table key)"""
        return self._gcode
Fabrice Salvaire's avatar
Fabrice Salvaire committed
    @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

Fabrice Salvaire's avatar
Fabrice Salvaire committed
####################################################################################################

Fabrice Salvaire's avatar
Fabrice Salvaire committed
class GcodeSet(YamlMixin, RstMixin):
Fabrice Salvaire's avatar
Fabrice Salvaire committed
    """Class for the table of G-codes."""

Fabrice Salvaire's avatar
Fabrice Salvaire committed
    ##############################################

    def __init__(self, yaml_path):

        data = self._load_yaml(yaml_path)
        self._gcodes = {}
Fabrice Salvaire's avatar
Fabrice Salvaire committed
        for gcode_txt, d in data.items():
            gcode = Gcode(gcode_txt, d['meaning'])
            self._gcodes[gcode_txt] = gcode
Fabrice Salvaire's avatar
Fabrice Salvaire committed

    ##############################################

    def __len__(self):
        return len(self._gcodes)

    def __iter__(self):
        return iter(self._gcodes.values())

    def __getitem__(self, code):
        return self._gcodes[code]

Fabrice Salvaire's avatar
Fabrice Salvaire committed
    def __contains__(self, code):
        return code in self._gcodes

Fabrice Salvaire's avatar
Fabrice Salvaire committed
    ##############################################

    def sorted_iter(self):

        items = list(self)
Fabrice Salvaire's avatar
Fabrice Salvaire committed
        items.sort(key=lambda item: str(ord(item.gcode[0])*1000) + item.gcode[1:])
Fabrice Salvaire's avatar
Fabrice Salvaire committed
        return items
Fabrice Salvaire's avatar
Fabrice Salvaire committed

    ##############################################

    def to_rst(self, path):
        self._write_rst(
            path,
            headers=('G-code', 'Meaning'),
Fabrice Salvaire's avatar
Fabrice Salvaire committed
            columns=('gcode', 'meaning'),
Fabrice Salvaire's avatar
Fabrice Salvaire committed
        )
Fabrice Salvaire's avatar
Fabrice Salvaire committed

Fabrice Salvaire's avatar
Fabrice Salvaire committed
####################################################################################################

class ExecutionGroup(MeaningMixin):

    ##############################################

Fabrice Salvaire's avatar
Fabrice Salvaire committed
    def __init__(self, index, gcodes, raw_gcodes, meaning):
Fabrice Salvaire's avatar
Fabrice Salvaire committed

        MeaningMixin.__init__(self, meaning)
        self._index = int(index)
        self._gcodes = list(gcodes)
Fabrice Salvaire's avatar
Fabrice Salvaire committed
        self._raw_gcodes = list(raw_gcodes)
Fabrice Salvaire's avatar
Fabrice Salvaire committed

    ##############################################

    @property
    def index(self):
Fabrice Salvaire's avatar
Fabrice Salvaire committed
        """Order index (table key)"""
Fabrice Salvaire's avatar
Fabrice Salvaire committed
        return self._index

    @property
    def gcodes(self):
Fabrice Salvaire's avatar
Fabrice Salvaire committed
        """G-Codes list"""
Fabrice Salvaire's avatar
Fabrice Salvaire committed
        return self._gcodes
Fabrice Salvaire's avatar
Fabrice Salvaire committed

Fabrice Salvaire's avatar
Fabrice Salvaire committed
    @property
    def raw_gcodes(self):
        """Raw G-Codes list"""
        return self._raw_gcodes

Fabrice Salvaire's avatar
Fabrice Salvaire committed
####################################################################################################

Fabrice Salvaire's avatar
Fabrice Salvaire committed
class ExecutionOrder(YamlMixin, RstMixin):
Fabrice Salvaire's avatar
Fabrice Salvaire committed
    """Class for the execution order table."""

Fabrice Salvaire's avatar
Fabrice Salvaire committed
    ##############################################

Fabrice Salvaire's avatar
Fabrice Salvaire committed
    def __init__(self, yaml_path, gcode_set):
Fabrice Salvaire's avatar
Fabrice Salvaire committed

        data = self._load_yaml(yaml_path)
Fabrice Salvaire's avatar
Fabrice Salvaire committed

Fabrice Salvaire's avatar
Fabrice Salvaire committed
        self._order = []
        count = 1
Fabrice Salvaire's avatar
Fabrice Salvaire committed
        for index, d in data.items():
Fabrice Salvaire's avatar
Fabrice Salvaire committed
            if index != count:
                raise ValueError('Unexpected index {} versus {}'.format(index, count))
            count += 1
Fabrice Salvaire's avatar
Fabrice Salvaire committed

            raw_gcodes = ensure_list(d['gcodes'])

            gcodes = []
            for gcode in raw_gcodes:
Fabrice Salvaire's avatar
Fabrice Salvaire committed
                if '-' in gcode:
                    start, stop = [int(code[1:]) for code in gcode.split('-')]
                    letter = gcode[0]
                    for i in range(start, stop+1):
Fabrice Salvaire's avatar
Fabrice Salvaire committed
                        _gcode = '{}{}'.format(letter, i)
                        gcodes.append(gcode_set[_gcode])
Fabrice Salvaire's avatar
Fabrice Salvaire committed
                else:
Fabrice Salvaire's avatar
Fabrice Salvaire committed
                    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
Fabrice Salvaire's avatar
Fabrice Salvaire committed

    ##############################################

    def __len__(self):
        return len(self._order)

    def __iter__(self):
Fabrice Salvaire's avatar
Fabrice Salvaire committed
        return iter(self._order)
Fabrice Salvaire's avatar
Fabrice Salvaire committed
    def __getitem__(self, index):
Fabrice Salvaire's avatar
Fabrice Salvaire committed
        return self._order[index]
Fabrice Salvaire's avatar
Fabrice Salvaire committed
    ##############################################

    def to_rst(self, path):
        self._write_rst(
            path,
Fabrice Salvaire's avatar
Fabrice Salvaire committed
            headers=('Order', 'G-codes', 'Comment'),
            columns=('index', 'gcodes', 'meaning'),
Fabrice Salvaire's avatar
Fabrice Salvaire committed
            str_gcodes=lambda item: ' '.join(item),
Fabrice Salvaire's avatar
Fabrice Salvaire committed
        )

Fabrice Salvaire's avatar
Fabrice Salvaire committed
####################################################################################################

Fabrice Salvaire's avatar
Fabrice Salvaire committed
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):
Fabrice Salvaire's avatar
Fabrice Salvaire committed
        """Group id (table key)"""
Fabrice Salvaire's avatar
Fabrice Salvaire committed
        return self._index

    @property
    def gcodes(self):
Fabrice Salvaire's avatar
Fabrice Salvaire committed
        """G-Codes list"""
Fabrice Salvaire's avatar
Fabrice Salvaire committed
        return self._gcodes

####################################################################################################

Fabrice Salvaire's avatar
Fabrice Salvaire committed
class ModalGroups(YamlMixin, RstMixin):
Fabrice Salvaire's avatar
Fabrice Salvaire committed
    """Class for the table of modal groups."""

Fabrice Salvaire's avatar
Fabrice Salvaire committed
    ##############################################

Fabrice Salvaire's avatar
Fabrice Salvaire committed
    def __init__(self, yaml_path, gcode_set):
Fabrice Salvaire's avatar
Fabrice Salvaire committed

        data = self._load_yaml(yaml_path)
Fabrice Salvaire's avatar
Fabrice Salvaire committed

Fabrice Salvaire's avatar
Fabrice Salvaire committed
        self._groups = {}
Fabrice Salvaire's avatar
Fabrice Salvaire committed
        for index, d in data.items():
Fabrice Salvaire's avatar
Fabrice Salvaire committed
            gcodes = ensure_list(d['gcodes'])
            gcodes = [gcode_set[gcode] for gcode in gcodes]
            group = ModalGroup(index, gcodes, d['meaning'])
Fabrice Salvaire's avatar
Fabrice Salvaire committed
            self._groups[index] = group
Fabrice Salvaire's avatar
Fabrice Salvaire committed
            for gcode in gcodes:
Fabrice Salvaire's avatar
Fabrice Salvaire committed
                gcode._modal_group = group
Fabrice Salvaire's avatar
Fabrice Salvaire committed

    ##############################################

    def __len__(self):
        return len(self._groups)

    def __iter__(self):
        return iter(self._groups.values())

    def __getitem__(self, index):
Fabrice Salvaire's avatar
Fabrice Salvaire committed
        return self._groups[index]
Fabrice Salvaire's avatar
Fabrice Salvaire committed

    ##############################################

    def sorted_iter(self):

        items = list(self)
        items.sort(key=lambda item: item.index)
        return items
Fabrice Salvaire's avatar
Fabrice Salvaire committed
    ##############################################

    def to_rst(self, path):
        self._write_rst(
            path,
Fabrice Salvaire's avatar
Fabrice Salvaire committed
            headers=('Group', 'G-codes', 'Comment'),
            columns=('index', 'gcodes', 'meaning'),
Fabrice Salvaire's avatar
Fabrice Salvaire committed
            str_gcodes=lambda item: ' '.join(item),
Fabrice Salvaire's avatar
Fabrice Salvaire committed
        )

####################################################################################################

class Config:

Fabrice Salvaire's avatar
Fabrice Salvaire committed
    """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,
    ):

Fabrice Salvaire's avatar
Fabrice Salvaire committed
        """Each argument is a path to the corresponding YAML file. Files are loaded on demand (lazy loading).
        """

Fabrice Salvaire's avatar
Fabrice Salvaire committed
        # 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):
Fabrice Salvaire's avatar
Fabrice Salvaire committed
        """:class:`ExecutionOrder` instance"""
Fabrice Salvaire's avatar
Fabrice Salvaire committed
        # if isinstance(self._execution_order, str):
        #     self._execution_order = ExecutionOrder(self._execution_order)
        return self._execution_order

    @property
    def gcodes(self):
Fabrice Salvaire's avatar
Fabrice Salvaire committed
        """:class:`GcodeSet` instance"""
        # if isinstance(self._gcodes, str):
        #     self._gcodes = GcodeSet(self._gcodes)
        return self._gcodes

    @property
    def letters(self):
Fabrice Salvaire's avatar
Fabrice Salvaire committed
        """:class:`Letters` instance"""
Fabrice Salvaire's avatar
Fabrice Salvaire committed
        # if isinstance(self._letters, str):
        #     self._letters = Letters(self._letters)
        return self._letters

    @property
    def modal_groups(self):
Fabrice Salvaire's avatar
Fabrice Salvaire committed
        """:class:`ModalGroups` instance"""
Fabrice Salvaire's avatar
Fabrice Salvaire committed
        # if isinstance(self._modal_groups, str):
        #     self._modal_groups = ModalGroups(self._modal_groups)
        return self._modal_groups

    @property
    def parameters(self):
Fabrice Salvaire's avatar
Fabrice Salvaire committed
        """:class:`ParameterSet` instance"""
        # if isinstance(self._parameters, str):
        #     self._parameters = ParameterSet(self._parameters)
        return self._parameters