Skip to content
Config.py 14.1 KiB
Newer Older
Fabrice Salvaire's avatar
Fabrice Salvaire committed
####################################################################################################
#
# PythonicGcodeMachine - @licence_header_description@
# 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',
    'Gcodes',
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',
    'Parameters',
Fabrice Salvaire's avatar
Fabrice Salvaire committed
]

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

import yaml

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

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 Parameters(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):
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

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

    @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

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

Fabrice Salvaire's avatar
Fabrice Salvaire committed
class Gcodes(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 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):

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

    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
        """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
####################################################################################################

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
    ##############################################

    def __init__(self, yaml_path):

        data = self._load_yaml(yaml_path)
        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
            gcodes = d['gcodes']
Fabrice Salvaire's avatar
Fabrice Salvaire committed
            if not isinstance(gcodes, list):
Fabrice Salvaire's avatar
Fabrice Salvaire committed
                gcodes = [gcodes]
            group = ExecutionGroup(index, gcodes, d['meaning'])
            self._order.append(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, slice_):
        return self._order[slice_]

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
    ##############################################

    def __init__(self, yaml_path):

        data = self._load_yaml(yaml_path)
        self._groups = {}
Fabrice Salvaire's avatar
Fabrice Salvaire committed
        for index, d in data.items():
            gcodes = d['gcodes']
            if not isinstance(gcodes, list):
                gcodes = [gcodes]
            group = ExecutionGroup(index, gcodes, d['meaning'])
            self._groups[index] = 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]

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

    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).
        """

        self._execution_order = str(execution_order)
        self._gcodes = str(gcodes)
        self._letters = str(letters)
        self._modal_groups = str(modal_groups)
        self._parameters = str(parameters)

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

    @property
    def execution_order(self):
Fabrice Salvaire's avatar
Fabrice Salvaire committed
        """:class:`ExecutionOrder` instance"""
        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:`Gcodes` instance"""
        if isinstance(self._gcodes, str):
            self._gcodes = Gcodes(self._gcodes)
        return self._gcodes

    @property
    def letters(self):
Fabrice Salvaire's avatar
Fabrice Salvaire committed
        """:class:`Letters` instance"""
        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"""
        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:`Parameters` instance"""
        if isinstance(self._parameters, str):
            self._parameters = Parameters(self._parameters)
        return self._parameters