From 82f91d1372b681aff442939982ccaabf7a25a96e Mon Sep 17 00:00:00 2001 From: Fabrice Salvaire Date: Tue, 25 Dec 2018 00:58:15 +0100 Subject: [PATCH] implemented machine --- PythonicGcodeMachine/Gcode/Rs274/Ast.py | 129 +++++++++++++++---- PythonicGcodeMachine/Gcode/Rs274/Config.py | 27 +++- PythonicGcodeMachine/Gcode/Rs274/Machine.py | 59 ++++++++- PythonicGcodeMachine/Gcode/Rs274/Parser.py | 22 +++- PythonicGcodeMachine/Gcode/Rs274/__init__.py | 17 +-- examples/gcode/annotate-gcode.py | 31 +++-- examples/gcode/generate-gcode.py | 2 +- 7 files changed, 225 insertions(+), 62 deletions(-) diff --git a/PythonicGcodeMachine/Gcode/Rs274/Ast.py b/PythonicGcodeMachine/Gcode/Rs274/Ast.py index 238ca3b..963964c 100644 --- a/PythonicGcodeMachine/Gcode/Rs274/Ast.py +++ b/PythonicGcodeMachine/Gcode/Rs274/Ast.py @@ -72,7 +72,28 @@ import colors #################################################################################################### -class Program: +class MachineMixin: + + """Mixin to define the target machine for the AST node""" + + ############################################## + + def __init__(self, machine=None): + self._machine = machine + + ############################################## + + @property + def machine(self): + return self._machine + + @machine.setter + def machine(self, value): + self._machine = value + +#################################################################################################### + +class Program(MachineMixin): """Class to implement a G-code program @@ -93,14 +114,15 @@ class Program: ############################################## - def __init__(self): + def __init__(self, machine=None): + super().__init__(machine) self._lines = [] ############################################## def clone(self): - program = self.__class__() + program = self.__class__(machine=self._machine) for line in self: program += line.clone() @@ -151,6 +173,8 @@ class Program: class CloneMixin: + """Mixin to provide a method to clone value""" + @staticmethod def _clone_value(value): if hasattr(value, 'clone'): @@ -160,7 +184,9 @@ class CloneMixin: #################################################################################################### -class LineItem(CloneMixin): +class LineItem(MachineMixin, CloneMixin): + + """Base class for line item""" ############################################## @@ -191,7 +217,7 @@ class LineItem(CloneMixin): #################################################################################################### -class Line: +class Line(MachineMixin): """Class to implement a G-code line @@ -254,7 +280,9 @@ class Line: ############################################## - def __init__(self, deleted=False, line_number=None, comment=None): + def __init__(self, deleted=False, line_number=None, comment=None, machine=None): + + super().__init__(machine) self.deleted = deleted self.line_number = line_number @@ -266,7 +294,7 @@ class Line: def clone(self): - line = self.__class__(self._deleted, self._line_number, self._comment) + line = self.__class__(self._deleted, self._line_number, self._comment, self._machine) for item in self: line += item.clone() @@ -428,13 +456,14 @@ class Comment(LineItem): ############################################## - def __init__(self, text): + def __init__(self, text, machine=None): + super().__init__(machine) self.set(text) ############################################## def clone(self): - return self.__class__(self._text) + return self.__class__(self._text, self._machine) ############################################## @@ -493,14 +522,15 @@ class Word(LineItem): ############################################## - def __init__(self, letter, value): + def __init__(self, letter, value, machine=None): + super().__init__(machine) self.letter = letter self.value = value ############################################## def clone(self): - return self.__class__(self._letter, self._clone_value(self._value)) + return self.__class__(self._letter, self._clone_value(self._value), self._machine) ############################################## @@ -538,15 +568,58 @@ class Word(LineItem): else: return Line.ANSI_X(self._letter) + Line.ANSI_VALUE(str(self._value)) + ############################################## + + def _check_machine(self): + if self._machine is None: + raise NameError('Machine is not defined') + + ############################################## + + @property + def is_valid_gcode(self): + self._check_machine() + return str(self) in self._machine.config.gcodes + + ############################################## + + @property + def meaning(self): + if self.is_valid_gcode: + return self._machine.config.gcodes[str(self)].meaning + else: + return self._machine.config.letters[self.letter].meaning + + ############################################## + + @property + def modal_group(self): + if self.is_valid_gcode: + return self._machine.config.modal_groups[str(self)] + else: + return None + + ############################################## + + @property + def execution_order(self): + if self.is_valid_gcode: + return self._machine.config.execution_order[str(self)] + else: + return None + #################################################################################################### -class RealValue(CloneMixin): +class RealValue(MachineMixin, CloneMixin): + """Base class for real value""" pass #################################################################################################### class ParameterMixin: + """Mixin for parameter""" + ############################################## def __init__(self, parameter): @@ -554,11 +627,6 @@ class ParameterMixin: ############################################## - def clone(self): - return self.__class__(self._parameter) - - ############################################## - @property def parameter(self): return self._parameter @@ -579,14 +647,15 @@ class ParameterSetting(LineItem, ParameterMixin): ############################################## - def __init__(self, parameter, value): + def __init__(self, parameter, value, machine=None): + super().__init__(machine) ParameterMixin.__init__(self, parameter) self.value = value ############################################## def clone(self): - return self.__class__(self._parameter, self._clone_value(self._value)) + return self.__class__(self._parameter, self._clone_value(self._value), self._machine) ############################################## @property @@ -616,11 +685,17 @@ class Parameter(RealValue, ParameterMixin): ############################################## - def __init__(self, parameter): + def __init__(self, parameter, machine=None): + super().__init__(machine) ParameterMixin.__init__(self, parameter) ############################################## + def clone(self): + return self.__class__(self._parameter, self._machine) + + ############################################## + def __repr__(self): return 'Parameter({0._parameter})'.format(self) @@ -636,18 +711,21 @@ class Parameter(RealValue, ParameterMixin): class UnaryOperation(RealValue): + """Base class for unary operation""" + __function__ = None __gcode__ = None ############################################## - def __init__(self, arg): + def __init__(self, arg, machine=None): + super().__init__(machine) self.arg = arg ############################################## def clone(self): - return self.__class__(self._clone_value(self._arg)) + return self.__class__(self._clone_value(self._arg), self._machine) ############################################## @@ -732,19 +810,22 @@ class Tangent(UnaryOperation): class BinaryOperation(RealValue): + """Base class for binary operation""" + __function__ = None __gcode__ = None ############################################## - def __init__(self, arg1, arg2): + def __init__(self, arg1, arg2, machine=None): + super().__init__(machine) self.arg1 = arg1 self.arg2 = arg2 ############################################## def clone(self): - return self.__class__(self._clone_value(self._arg1), self._clone_value(self.arg2)) + return self.__class__(self._clone_value(self._arg1), self._clone_value(self.arg2), self._machine) ############################################## diff --git a/PythonicGcodeMachine/Gcode/Rs274/Config.py b/PythonicGcodeMachine/Gcode/Rs274/Config.py index 3ab33de..0987cac 100644 --- a/PythonicGcodeMachine/Gcode/Rs274/Config.py +++ b/PythonicGcodeMachine/Gcode/Rs274/Config.py @@ -283,6 +283,9 @@ class Gcodes(YamlMixin, RstMixin): def __getitem__(self, code): return self._gcodes[code] + def __contains__(self, code): + return code in self._gcodes + ############################################## def sorted_iter(self): @@ -336,6 +339,7 @@ class ExecutionOrder(YamlMixin, RstMixin): data = self._load_yaml(yaml_path) self._order = [] + self._gcode_map = {} count = 1 for index, d in data.items(): if index != count: @@ -346,6 +350,14 @@ class ExecutionOrder(YamlMixin, RstMixin): gcodes = [gcodes] group = ExecutionGroup(index, gcodes, d['meaning']) self._order.append(group) + for gcode in gcodes: + if '-' in gcode: + start, stop = [int(code[1:]) for code in gcode.split('-')] + letter = gcode[0] + for i in range(start, stop+1): + self._gcode_map['{}{}'.format(letter, i)] = group + else: + self._gcode_map[gcode] = group ############################################## @@ -355,8 +367,11 @@ class ExecutionOrder(YamlMixin, RstMixin): def __iter__(self): return iter(self._order) - def __getitem__(self, slice_): - return self._order[slice_] + def __getitem__(self, index): + if isinstance(index, int): + return self._order[index] + else: + return self._gcode_map[index] ############################################## @@ -404,12 +419,15 @@ class ModalGroups(YamlMixin, RstMixin): data = self._load_yaml(yaml_path) self._groups = {} + self._gcode_map = {} 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 + for gcode in gcodes: + self._gcode_map[gcode] = group ############################################## @@ -420,7 +438,10 @@ class ModalGroups(YamlMixin, RstMixin): return iter(self._groups.values()) def __getitem__(self, index): - return self._groups[index] + if isinstance(index, int): + return self._groups[index] + else: + return self._gcode_map[index] ############################################## diff --git a/PythonicGcodeMachine/Gcode/Rs274/Machine.py b/PythonicGcodeMachine/Gcode/Rs274/Machine.py index 940faab..96e6939 100644 --- a/PythonicGcodeMachine/Gcode/Rs274/Machine.py +++ b/PythonicGcodeMachine/Gcode/Rs274/Machine.py @@ -18,19 +18,70 @@ # #################################################################################################### -""" +"""Module to implement a basic G-code machine. """ #################################################################################################### __all__ = [ + 'GcodeMachine', ] #################################################################################################### -# class Config: +from pathlib import Path as Path + +from .Config import Config +from .Parser import GcodeParser, GcodeParserError + +#################################################################################################### + +class GcodeMachine: + + PARSER_CLS = GcodeParser ############################################## - # def __init__(self, - # ): + def __init__(self): + + self._config = None + self.load_config() + + self._parser = None + self.setup_parser() + + ############################################## + + def load_config(self): + + data_path = Path(__file__).parent.joinpath('data') + self._config = Config( + execution_order=data_path.joinpath('rs274-execution-order.yaml'), + gcodes=data_path.joinpath('rs274-gcodes.yaml'), + letters=data_path.joinpath('rs274-word-starting-letter.yaml'), + modal_groups=data_path.joinpath('rs274-modal-groups.yaml'), + parameters=data_path.joinpath('rs274-default-parameter-file.yaml'), + ) + + ############################################## + + def setup_parser(self): + + self._parser = self.PARSER_CLS(machine=self) + + ############################################## + + @property + def config(self): + return self._config + + @property + def parser(self): + return self._parser + + ############################################## + + def reset(): + pass + + ############################################## diff --git a/PythonicGcodeMachine/Gcode/Rs274/Parser.py b/PythonicGcodeMachine/Gcode/Rs274/Parser.py index 8d3edf8..f35b280 100644 --- a/PythonicGcodeMachine/Gcode/Rs274/Parser.py +++ b/PythonicGcodeMachine/Gcode/Rs274/Parser.py @@ -186,7 +186,7 @@ class GcodeGrammarMixin: def p_ordinary_comment(self, p): 'ordinary_comment : INLINE_COMMENT' - p[0] = Ast.Comment(p[1]) + p[0] = Ast.Comment(p[1], self._machine) # def p_message(self, p): # 'message : left_parenthesis + {white_space} + letter_m + {white_space} + letter_s + @@ -197,7 +197,7 @@ class GcodeGrammarMixin: def p_mid_line_word(self, p): 'mid_line_word : mid_line_letter real_value' - p[0] = Ast.Word(p[1], p[2]) + p[0] = Ast.Word(p[1], p[2], self._machine) def p_mid_line_letter(self, p): # LETTER @@ -226,11 +226,11 @@ class GcodeGrammarMixin: def p_parameter_setting(self, p): 'parameter_setting : PARAMETER_SIGN parameter_index EQUAL_SIGN real_value' - p[0] = Ast.ParameterSetting(p[2], p[4]) + p[0] = Ast.ParameterSetting(p[2], p[4], self._machine) def p_parameter_value(self, p): 'parameter_value : PARAMETER_SIGN parameter_index' - p[0] = Ast.Parameter(p[2]) + p[0] = Ast.Parameter(p[2], self._machine) def p_parameter_index(self, p): 'parameter_index : real_value' @@ -340,12 +340,20 @@ class GcodeParserMixin: ############################################## - def __init__(self): + def __init__(self, machine=None): + + self._machine = machine self._build() self._reset() ############################################## + @property + def machine(self): + return self._machine + + ############################################## + def _reset(self): self._line = None @@ -374,7 +382,7 @@ class GcodeParserMixin: line = line.strip() - self._line = Ast.Line() + self._line = Ast.Line(machine=self._machine) ast = self._parser.parse( line, lexer=self._lexer._lexer, @@ -398,7 +406,7 @@ class GcodeParserMixin: if not isinstance(lines, (list, tuple)): lines = lines.split('\n') - program = Ast.Program() + program = Ast.Program(machine=self._machine) for line in lines: try: program += self.parse(line) diff --git a/PythonicGcodeMachine/Gcode/Rs274/__init__.py b/PythonicGcodeMachine/Gcode/Rs274/__init__.py index 88bb30a..c3792e7 100644 --- a/PythonicGcodeMachine/Gcode/Rs274/__init__.py +++ b/PythonicGcodeMachine/Gcode/Rs274/__init__.py @@ -25,17 +25,6 @@ See :ref:`rs-274-reference-page` for more details about the RS-274 specification #################################################################################################### -from pathlib import Path as _Path - -from .Config import Config as _Config -from .Parser import GcodeParser, GcodeParserError - -_data_path = _Path(__file__).parent.joinpath('data') - -config = _Config( - execution_order=_data_path.joinpath('rs274-execution-order.yaml'), - gcodes=_data_path.joinpath('rs274-gcodes.yaml'), - letters=_data_path.joinpath('rs274-word-starting-letter.yaml'), - modal_groups=_data_path.joinpath('rs274-modal-groups.yaml'), - parameters=_data_path.joinpath('rs274-default-parameter-file.yaml'), -) +# from .Config import Config as _Config +# from .Parser import GcodeParser, GcodeParserError +from .Machine import GcodeMachine diff --git a/examples/gcode/annotate-gcode.py b/examples/gcode/annotate-gcode.py index 23a39e6..ab393e7 100644 --- a/examples/gcode/annotate-gcode.py +++ b/examples/gcode/annotate-gcode.py @@ -34,10 +34,18 @@ from pathlib import Path -from PythonicGcodeMachine.Gcode.Rs274 import GcodeParser, config +from PythonicGcodeMachine.Gcode.Rs274.Machine import GcodeMachine #################################################################################################### +#r# We build a RS-274 G-code Machine + +machine = GcodeMachine() + +#################################################################################################### + +#r# We load a G-code program + program_filename = 'mill-example-1.ngc' programs_directory = Path(__file__).parents[1].joinpath('programs') @@ -47,8 +55,13 @@ with open(program_path, 'r') as fh: if lines[0].startswith(';'): lines = lines[1:] -parser = GcodeParser() -program = parser.parse_lines(lines) +#################################################################################################### + +#r# We parse the program + +program = machine.parser.parse_lines(lines) + +#r# We dump the annotated program meaning_format = ' {:5}: {}' for line in program: @@ -56,11 +69,11 @@ for line in program: # print(line.ansi_str()) # Fixme: pyterate print(str(line)) for word in line.iter_on_word(): - if word.letter in 'GM': - meaning = config.gcodes[str(word)].meaning - print(meaning_format.format(str(word), meaning)) + if word.is_valid_gcode: + margin = ' '*9 + print(meaning_format.format(str(word), word.meaning)) + print(margin + 'Modal group: {}'.format(word.modal_group.meaning)) + print(margin + 'Execution order: {}'.format(word.execution_order.index)) else: - letter = word.letter - meaning = config.letters[letter].meaning - print(meaning_format.format(letter, meaning)) + print(meaning_format.format(word.letter, word.meaning)) #o# diff --git a/examples/gcode/generate-gcode.py b/examples/gcode/generate-gcode.py index 99afeb9..5555281 100644 --- a/examples/gcode/generate-gcode.py +++ b/examples/gcode/generate-gcode.py @@ -32,8 +32,8 @@ #################################################################################################### -from PythonicGcodeMachine.Gcode.Rs274 import * from PythonicGcodeMachine.Gcode.Rs274.Ast import * +from PythonicGcodeMachine.Gcode.Rs274.Parser import GcodeParser #################################################################################################### -- GitLab