Skip to content
Ast.py 16.3 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/>.
#
####################################################################################################

"""Module to implement an AST for RS-274 G-code.
"""

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

__all__ = [
    'Program',
Fabrice Salvaire's avatar
Fabrice Salvaire committed
    # 'LineItem',
Fabrice Salvaire's avatar
Fabrice Salvaire committed
    'Line',
    'Comment',
    'Word',
Fabrice Salvaire's avatar
Fabrice Salvaire committed
    # 'RealValue',
Fabrice Salvaire's avatar
Fabrice Salvaire committed
    'ParameterMixin',
    'ParameterSetting',
    'Parameter',
Fabrice Salvaire's avatar
Fabrice Salvaire committed

    # 'UnaryOperation',
Fabrice Salvaire's avatar
Fabrice Salvaire committed
    'AbsoluteValue',
    'ArcCosine',
    'ArcSine',
    'ArcTangent',
    'Cosine',
    'ERaisedTo',
    'FixDown',
    'FixUp',
    'NaturalLogOf',
    'Round',
    'Sine',
    'SquareRoot',
    'Tangent',
Fabrice Salvaire's avatar
Fabrice Salvaire committed

    # 'BinaryOperation',
Fabrice Salvaire's avatar
Fabrice Salvaire committed
    'Power',
    'DividedBy',
    'Modulo',
Fabrice Salvaire's avatar
Fabrice Salvaire committed
    'Multiply',
Fabrice Salvaire's avatar
Fabrice Salvaire committed
    'And',
    'ExclusiveOr',
Fabrice Salvaire's avatar
Fabrice Salvaire committed
    'Subtraction',
    'Or',
    'Addition',
Fabrice Salvaire's avatar
Fabrice Salvaire committed
]

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

import math

Fabrice Salvaire's avatar
Fabrice Salvaire committed
import colors

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

class Program:

    """Class to implement a G-code program

    Usage::

        program = Program()
        program += line
Fabrice Salvaire's avatar
Fabrice Salvaire committed

        # Array interface
        for line in programs:
            print(line)

Fabrice Salvaire's avatar
Fabrice Salvaire committed
        str(program)

    """

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

    def __init__(self):
        self._lines = []

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

    def push(self, line):
        self._lines.append(line)

    def __iadd__(self, item):
        self.push(item)
        return self

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

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

    def __iter__(self):
        return iter(self._lines)

    def __getitem__(self, _slice):
        return self._lines[_slice]

    def iter_on_not_deleted(self):
        for line in self._lines:
            if line:
                yield line

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

    def __repr__(self):

        text = 'Program(\n'
        for line in self:
            text += repr(line) + '\n'
        text += ')\n'

        return text

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

    def __str__(self):
        return '\n'.join(map(str, self))

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

class LineItem:
Fabrice Salvaire's avatar
Fabrice Salvaire committed

    def ansi_str(self):
        return str(self)
Fabrice Salvaire's avatar
Fabrice Salvaire committed

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

class Line:

    """Class to implement a G-code line

    Usage::

Fabrice Salvaire's avatar
Fabrice Salvaire committed
        line = Line(deleted=False, line_number=1, comment='a comment')

        line.deleted = True
        print(line.deleted)
        # same apply for line_number and comment

        # Is line not deleted ?
        bool(line)

        # Push some items
        # Note: order doesn't matter, see RS-274 for details
Fabrice Salvaire's avatar
Fabrice Salvaire committed
        line += Word('G', 0)
        line += Comment('move')
        line += Word('X', 10)
        line += Comment('Y value')
        line += Word('Y', 20)
        line += ParameterSetting('1', 1.2)
Fabrice Salvaire's avatar
Fabrice Salvaire committed

        # using expression
        line += Word('Z', Addition(30, Multiply(Parameter(100), Cosine(30))))

        # Array interface
        for item in line:
            print(item)

Fabrice Salvaire's avatar
Fabrice Salvaire committed
        str(line)
Fabrice Salvaire's avatar
Fabrice Salvaire committed
        print(line.ansi_str()) # use ANSI colors, see Line.ANSI_... attributes

    Expression can be evaluated using :code:`float(obj.value)`, excepted when we must access a parameter
    value.
Fabrice Salvaire's avatar
Fabrice Salvaire committed
    ANSI_DELETED = colors.red
    ANSI_LINE_NUMBER = colors.blue
    ANSI_COMMENT = colors.green
    ANSI_SETTING = colors.blue
    ANSI_G = colors.red
    ANSI_X = colors.blue
    ANSI_VALUE = colors.black

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

    def __init__(self, deleted=False, line_number=None, comment=None):

        self.deleted = deleted
        self.line_number = line_number
        self.comment = comment

        self._items = []

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

    @property
    def deleted(self):
        return self._deleted

    @deleted.setter
    def deleted(self, value):
        self._deleted = bool(value)

    @property
    def line_number(self):
        return self._line_number

    @line_number.setter
    def line_number(self, value):
        if value is not None:
            value = float(value)
            if value.is_integer():
                value = int(value)
        self._line_number = value

    @property
    def comment(self):
        return self._comment

    @comment.setter
    def comment(self, value):
        if value is not None:
            self._comment = str(value)
        else:
            self._comment = None

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

    def push(self, item):
        if isinstance(item, LineItem):
            self._items.append(item)
        else:
            raise ValueError

    def __iadd__(self, item):
        self.push(item)
        return self

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

    def __bool__(self):
        return not self._deleted

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

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

    def __iter__(self):
        return iter(self._items)

    def __getitem__(self, _slice):
        return self._items[_slice]

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

    def iter_on_word(self):
        for item in self:
            if isinstance(item, Word):
                yield item

    def iter_on_setting(self):
        for item in self:
            if isinstance(item, ParameterSetting):
                yield item

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

Fabrice Salvaire's avatar
Fabrice Salvaire committed
    def __repr__(self):

        items = []
        if not self:
            items.append('Deleted')
        items += list(map(repr, self))
        if self._comment:
            items.append(self._comment)

        return 'Line{}'.format(self, items)

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

    def __str__(self):

        line = ''
        if not self:
Fabrice Salvaire's avatar
Fabrice Salvaire committed
            line += '/ '
Fabrice Salvaire's avatar
Fabrice Salvaire committed
        if self._line_number:
            line += 'N{} '.format(self._line_number)
        line += ' '.join(map(str, self))
        if self._comment:
            line += ' ; ' + self._comment

        return line

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

    def ansi_str(self):

        line = ''
        if not self:
            # line += self.ANSI_DELETED('/ ')
            return self.ANSI_DELETED(str(self))
        if self._line_number:
            line += self.ANSI_LINE_NUMBER('N{} '.format(self._line_number))
        line += ' '.join([item.ansi_str() for item in self])
        if self._comment:
            line += ' ' + self.ANSI_COMMENT('; ' + self._comment)

        return line

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

class Comment(LineItem):

Fabrice Salvaire's avatar
Fabrice Salvaire committed
    """Class to implement comment"""

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

    def __init__(self, text):
        self.set(text)

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

    def set(self, text):
        if '(' in text:
            raise ValueError('Comment cannot contains a "("')
        self._text = str(text)

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

    @property
    def text(self):
        return self._text

    @text.setter
    def text(self, value):
        self.set(value)

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

    def __repr__(self):
        return 'Comment({0._text})'.format(self)

    def __str__(self):
        return '({0._text})'.format(self)

Fabrice Salvaire's avatar
Fabrice Salvaire committed
    def ansi_str(self):
        return Line.ANSI_COMMENT(str(self))

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

class Word(LineItem):

Fabrice Salvaire's avatar
Fabrice Salvaire committed
    """Class to implement word"""

Fabrice Salvaire's avatar
Fabrice Salvaire committed
    LETTERS = (
        'A', 'B', 'C', 'D',
        'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', # 'N',
        'P', 'Q', 'R', 'S', 'T',
        'X', 'Y', 'Z',
    )

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

    def __init__(self, letter, value):
        self.letter = letter
        self.value = value

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

    @property
    def letter(self):
        return self._letter

    @letter.setter
    def letter(self, value):
        value = str(value).upper()
        if value not in self.LETTERS:
            raise ValueError
        self._letter = value

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

    @value.setter
    def value(self, value):
        # float expression ...
        self._value = value

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

    def __repr__(self):
        return 'Word({0._letter} {0._value})'.format(self)

    def __str__(self):
        return '{0._letter}{0._value}'.format(self)

Fabrice Salvaire's avatar
Fabrice Salvaire committed
    def ansi_str(self):

        if self._letter in 'GM':
            return Line.ANSI_G(str(self))
        else:
            return Line.ANSI_X(self._letter) + Line.ANSI_VALUE(str(self._value))

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

class RealValue:
    pass

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

class ParameterMixin:

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

    def __init__(self, parameter):
        self.parameter = parameter

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

    @property
    def parameter(self):
        return self._parameter

    @parameter.setter
    def parameter(self, value):
        try:
            value = int(value)
        except ValueError:
            value = str(value)
        self._parameter = value

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

class ParameterSetting(LineItem, ParameterMixin):

Fabrice Salvaire's avatar
Fabrice Salvaire committed
    """Class to implement parameter setting"""

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

    def __init__(self, parameter, value):
        ParameterMixin.__init__(self, parameter)
        self.value = value

    ##############################################
    @property
    def value(self):
        return self._value

    @value.setter
    def value(self, value):
        # float expression ...
        self._value = value

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

    def __repr__(self):
        return 'ParameterSetting({0._parameter} = {0._value})'.format(self)

    def __str__(self):
        return '#{0._parameter}={0._value}'.format(self)

Fabrice Salvaire's avatar
Fabrice Salvaire committed
    def ansi_str(self):
        return Line.ANSI_SETTING('#{0._parameter}='.format(self)) + Line.ANSI_VALUE(str(self._value))

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

class Parameter(RealValue, ParameterMixin):

Fabrice Salvaire's avatar
Fabrice Salvaire committed
    """Class to implement parameter"""

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

    def __init__(self, parameter):
        ParameterMixin.__init__(self, parameter)

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

    def __repr__(self):
        return 'Parameter({0._parameter})'.format(self)

    def __str__(self):
        return '#{0._parameter}'.format(self)

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

    def __float__(self):
        raise NotImplementedError

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

class UnaryOperation(RealValue):

    __function__ = None
    __gcode__ = None

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

    def __init__(self, arg):
        self.arg = arg

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

    @property
    def arg(self):
        return self._arg

    @arg.setter
    def arg(self, value):
        self._arg = value

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

Fabrice Salvaire's avatar
Fabrice Salvaire committed
    def __float__(self):
Fabrice Salvaire's avatar
Fabrice Salvaire committed
        return self.__function__(float(self._arg))

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

    def __repr__(self):
        return '{}({})'.format(self.__class__.__name__, repr(self._arg))

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

    def __str__(self):
        return '{0.__gcode__}[{0._arg}]'.format(self)

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

class AbsoluteValue(UnaryOperation):
Fabrice Salvaire's avatar
Fabrice Salvaire committed
    __function__ = staticmethod(abs)
Fabrice Salvaire's avatar
Fabrice Salvaire committed
    __gcode__ = 'abs'

class ArcCosine(UnaryOperation):
Fabrice Salvaire's avatar
Fabrice Salvaire committed
    __function__ = staticmethod(lambda x: math.acos(math.radians(x)))
Fabrice Salvaire's avatar
Fabrice Salvaire committed
    __gcode__ = 'acos'

class ArcSine(UnaryOperation):
Fabrice Salvaire's avatar
Fabrice Salvaire committed
    __function__ = staticmethod(lambda x: math.degrees(math.asin(x)))
Fabrice Salvaire's avatar
Fabrice Salvaire committed
    __gcode__ = 'asin'

class ArcTangent(UnaryOperation):
Fabrice Salvaire's avatar
Fabrice Salvaire committed
    __function__ = staticmethod(lambda x: math.degrees(math.atan(x)))
Fabrice Salvaire's avatar
Fabrice Salvaire committed
    __gcode__ = 'atan'

class Cosine(UnaryOperation):
Fabrice Salvaire's avatar
Fabrice Salvaire committed
    __function__ = staticmethod(lambda x: math.cos(math.radians(x)))
Fabrice Salvaire's avatar
Fabrice Salvaire committed
    __gcode__ =  'cos'

class ERaisedTo(UnaryOperation):
Fabrice Salvaire's avatar
Fabrice Salvaire committed
    __function__ = staticmethod(math.exp)
Fabrice Salvaire's avatar
Fabrice Salvaire committed
    __gcode__ = 'exp'

class FixDown(UnaryOperation):
Fabrice Salvaire's avatar
Fabrice Salvaire committed
    __function__ = staticmethod(math.ceil)
Fabrice Salvaire's avatar
Fabrice Salvaire committed
    __gcode__ = 'fix'

class FixUp(UnaryOperation):
Fabrice Salvaire's avatar
Fabrice Salvaire committed
    __function__ = staticmethod(math.floor)
Fabrice Salvaire's avatar
Fabrice Salvaire committed
    __gcode__ = 'fup'

class NaturalLogOf(UnaryOperation):
Fabrice Salvaire's avatar
Fabrice Salvaire committed
    __function__ = staticmethod(math.log)
Fabrice Salvaire's avatar
Fabrice Salvaire committed
    __gcode__ = 'ln'

class Round(UnaryOperation):
Fabrice Salvaire's avatar
Fabrice Salvaire committed
    __function__ = staticmethod(round)
Fabrice Salvaire's avatar
Fabrice Salvaire committed
    __gcode__ =  'round'

class Sine(UnaryOperation):
Fabrice Salvaire's avatar
Fabrice Salvaire committed
    __function__ = staticmethod(lambda x: math.sin(math.radians(x)))
Fabrice Salvaire's avatar
Fabrice Salvaire committed
    __gcode__ = 'sin'

class SquareRoot(UnaryOperation):
Fabrice Salvaire's avatar
Fabrice Salvaire committed
    __function__ = staticmethod(math.sqrt)
Fabrice Salvaire's avatar
Fabrice Salvaire committed
    __gcode__ = 'sqrt'

class Tangent(UnaryOperation):
Fabrice Salvaire's avatar
Fabrice Salvaire committed
    __function__ = staticmethod(lambda x: ath.tan(math.radians(x)))
Fabrice Salvaire's avatar
Fabrice Salvaire committed
    __gcode__ = 'tan'

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

class BinaryOperation(RealValue):

    __function__ = None
    __gcode__ = None

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

    def __init__(self, arg1, arg2):
        self.arg1 = arg1
        self.arg2 = arg2

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

    @property
    def arg1(self):
        return self._arg1

    @arg1.setter
    def arg1(self, value):
        self._arg1 = value

    @property
    def arg2(self):
        return self._arg2

    @arg2.setter
    def arg2(self, value):
        self._arg2 = value

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

Fabrice Salvaire's avatar
Fabrice Salvaire committed
    def __float__(self):
Fabrice Salvaire's avatar
Fabrice Salvaire committed
        return self.__function__(float(self._arg1), float(self._arg2))

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

    def __repr__(self):
        return '{}({})'.format(self.__class__.__name__, repr(self._arg))

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

    def __str__(self):
        return '[{0._arg1} {0.__gcode__} {0._arg2}]'.format(self)

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

class Power(BinaryOperation):
Fabrice Salvaire's avatar
Fabrice Salvaire committed
    __function__ = staticmethod(lambda a, b: a**b)
Fabrice Salvaire's avatar
Fabrice Salvaire committed
    __gcode__ = '**'

class DividedBy(BinaryOperation):
Fabrice Salvaire's avatar
Fabrice Salvaire committed
    __function__ = staticmethod(lambda a, b: a / b)
Fabrice Salvaire's avatar
Fabrice Salvaire committed
    __gcode__ = '/'

class Modulo(BinaryOperation):
Fabrice Salvaire's avatar
Fabrice Salvaire committed
    __function__ = staticmethod(lambda a, b: a % b)
Fabrice Salvaire's avatar
Fabrice Salvaire committed
    __gcode__ = 'mod'

Fabrice Salvaire's avatar
Fabrice Salvaire committed
class Multiply(BinaryOperation):
Fabrice Salvaire's avatar
Fabrice Salvaire committed
    __function__ = staticmethod(lambda a, b: a * b)
Fabrice Salvaire's avatar
Fabrice Salvaire committed
    __gcode__ =  '*'

class And(BinaryOperation):
Fabrice Salvaire's avatar
Fabrice Salvaire committed
    __function__ = staticmethod(lambda a, b: a & b)
Fabrice Salvaire's avatar
Fabrice Salvaire committed
    __gcode__ = 'and'

class ExclusiveOr(BinaryOperation):
Fabrice Salvaire's avatar
Fabrice Salvaire committed
    __function__ = staticmethod(lambda a, b: a ^ b)
Fabrice Salvaire's avatar
Fabrice Salvaire committed
    __gcode__ = 'xor'

Fabrice Salvaire's avatar
Fabrice Salvaire committed
class Subtraction(BinaryOperation):
    __function__ = staticmethod(lambda a, b: a - b)
Fabrice Salvaire's avatar
Fabrice Salvaire committed
    __gcode__ = '-'

Fabrice Salvaire's avatar
Fabrice Salvaire committed
class Or(BinaryOperation):
    __function__ = staticmethod(lambda a, b: a | b)
Fabrice Salvaire's avatar
Fabrice Salvaire committed
    __gcode__ = 'or'

Fabrice Salvaire's avatar
Fabrice Salvaire committed
class Addition(BinaryOperation):
    __function__ = staticmethod(lambda a, b: a + b)
Fabrice Salvaire's avatar
Fabrice Salvaire committed
    __gcode__ = '+'