diff --git a/PythonicGcodeMachine/Gcode/Rs274/Ast.py b/PythonicGcodeMachine/Gcode/Rs274/Ast.py index 6cc4493e35cf1d3e493c1a13ed41b3368b05fb1c..e3df3e62bd0d49ff57123bd151e1f3d912ed235e 100644 --- a/PythonicGcodeMachine/Gcode/Rs274/Ast.py +++ b/PythonicGcodeMachine/Gcode/Rs274/Ast.py @@ -382,15 +382,26 @@ class Line(MachineMixin): yield item def iter_on_gm_word(self): - for item in self: - if isinstance(item, Word) and item.is_gm_gcode: + for item in self.iter_on_word(): + if item.is_gm_gcode: yield item def iter_on_x_word(self): - for item in self: - if isinstance(item, Word) and not item.is_gm_gcode: + for item in self.iter_on_word(): + if not item.is_gm_gcode: + yield item + + def iter_on_letter(self, letters): + for item in self.iter_on_word(): + if item.letter in letters: yield item + def iter_on_g_word(self): + return self.iter_on_letter('G') + + def iter_on_m_word(self): + return self.iter_on_letter('M') + def iter_on_setting(self): for item in self: if isinstance(item, ParameterSetting): @@ -421,6 +432,19 @@ class Line(MachineMixin): ############################################## + def check_modal_group(self): + + modal_groups = {} + for item in self.iter_on_gm_word(): + group = item.modal_group + modal_groups.setdefault(group, 0) + modal_groups[group] += 1 + errors = [modal_group for modal_group, count in modal_groups.items() if count > 1] + if errors: + raise ValueError("Modal group errors {}".format(errors)) + + ############################################## + def __repr__(self): items = [] @@ -514,6 +538,7 @@ class Word(LineItem): """Class to implement word""" + # Fixme: config ??? LETTERS = ( 'A', 'B', 'C', 'D', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', # 'N', @@ -594,12 +619,20 @@ class Word(LineItem): if self._machine is None: raise NameError('Machine is not defined') + @property + def _machine_config(self): + self._check_machine() + return self._machine.config + ############################################## @property def is_gm_gcode(self): - self._check_machine() - return self._machine.is_gm_word(self) + return self._machine_config.letters.is_gm_word(self) + + @property + def is_axis_gcode(self): + return self._machine_config.letters.is_axis_word(self) ############################################## diff --git a/PythonicGcodeMachine/Gcode/Rs274/Config.py b/PythonicGcodeMachine/Gcode/Rs274/Config.py index 84c64a41af51684df9b00f2ee96d8640d245acf1..2663fc9daf243064b2599eb026e58ae281145180 100644 --- a/PythonicGcodeMachine/Gcode/Rs274/Config.py +++ b/PythonicGcodeMachine/Gcode/Rs274/Config.py @@ -36,9 +36,9 @@ __all__ = [ 'ExecutionOrder', 'Gcode', 'GcodeSet', - 'Letters', + 'LetterSet', 'ModalGroup', - 'ModalGroups', + 'ModalGroupSet', 'Parameter', 'ParameterSet', ] @@ -156,6 +156,11 @@ class Parameter(MeaningMixin): def default_value(self): return self._value + ############################################## + + def __repr__(self): + return '#{0._index}: {0._meaning}'.format(self) + #################################################################################################### class ParameterSet(YamlMixin, RstMixin): @@ -210,12 +215,36 @@ class Letter(MeaningMixin): """G-code letter (table key)""" return self._letter + ############################################## + + def __repr__(self): + return '#{0._letter}: {0._meaning}'.format(self) + #################################################################################################### -class Letters(YamlMixin, RstMixin): +class LetterSet(YamlMixin, RstMixin): """Class for the table of letters.""" + GM_LETTERS = 'GM' + AXIS_LETTERS = 'XYZABC' # 6-axis + + ############################################## + + def is_gm_letter(self, letter): + return letter in self.GM_LETTERS + + def is_axis_letter(self, letter): + return letter in self.AXIS_LETTERS + + ############################################## + + def is_gm_word(self, word): + return self.is_gm_letter(word.letter) + + def is_axis_word(self, word): + return self.is_axis_letter(word.letter) + ############################################## def __init__(self, yaml_path): @@ -251,7 +280,11 @@ class Gcode(MeaningMixin): ############################################## - def __init__(self, gcode, meaning, modal_group=None, execution_order=None): + def __init__(self, gcode, meaning, + modal_group=None, + execution_order=None, + doc=None, + ): MeaningMixin.__init__(self, meaning) self._gcode = str(gcode) @@ -259,6 +292,7 @@ class Gcode(MeaningMixin): # Those are set later due to the initialisation process self._modal_group = modal_group self._execution_order = execution_order + self._doc = doc ############################################## @@ -279,6 +313,21 @@ class Gcode(MeaningMixin): def execution_order_index(self): return self._execution_order.index + @property + def doc(self): + return self._doc + + ############################################## + + def __str__(self): + return self._gcode + + ############################################## + + def convert_doc(self, format): + import pypandoc + return pypandoc.convert_text(self.doc, 'rst', format=format) + #################################################################################################### class GcodeSet(YamlMixin, RstMixin): @@ -295,6 +344,8 @@ class GcodeSet(YamlMixin, RstMixin): gcode = Gcode(gcode_txt, d['meaning']) self._gcodes[gcode_txt] = gcode + self._sorted_gcodes = None + ############################################## def __len__(self): @@ -311,11 +362,34 @@ class GcodeSet(YamlMixin, RstMixin): ############################################## + def _sort(self): + + if self._sorted_gcodes is None: + items = list(self) + items.sort(key=lambda item: str(ord(item.gcode[0])*1000) + item.gcode[1:]) + self._sorted_gcodes = items + return self._sorted_gcodes + + ############################################## + def sorted_iter(self): + return iter(self._sort()) - items = list(self) - items.sort(key=lambda item: str(ord(item.gcode[0])*1000) + item.gcode[1:]) - return items + ############################################## + + def iter_on_slice(self, start, stop): + + start_index = None + stop_index = None + for i, item in enumerate(self._sort()): + if item.gcode == start: + start_index = i + elif item.gcode == stop: + stop_index = i + if start_index > stop_index: + raise ValueError('{} > {}'.format(start, stop)) + + return iter(self._sorted_gcodes[start_index:stop_index+1]) ############################################## @@ -356,6 +430,11 @@ class ExecutionGroup(MeaningMixin): """Raw G-Codes list""" return self._raw_gcodes + ############################################## + + def __str__(self): + return '#{0._index} Meaning: {0._meaning}'.format(self) + #################################################################################################### class ExecutionOrder(YamlMixin, RstMixin): @@ -445,9 +524,14 @@ class ModalGroup(MeaningMixin): """G-Codes list""" return self._gcodes + ############################################## + + def __repr__(self): + return '#{0._index}: ({1}) Meaning: {0._meaning}'.format(self, ' '.join([str(gcode) for gcode in self._gcodes])) + #################################################################################################### -class ModalGroups(YamlMixin, RstMixin): +class ModalGroupSet(YamlMixin, RstMixin): """Class for the table of modal groups.""" @@ -521,13 +605,54 @@ class Config: self._gcodes = GcodeSet(gcodes) self._execution_order = ExecutionOrder(execution_order, self._gcodes) - self._modal_groups = ModalGroups(modal_groups, self._gcodes) + self._modal_groups = ModalGroupSet(modal_groups, self._gcodes) # self._letters = str(letters) # self._parameters = str(parameters) - self._letters = Letters(letters) + self._letters = LetterSet(letters) self._parameters = ParameterSet(parameters) + self._load_doc() + + ############################################## + + def _load_doc(self): + + from . import GcodeDoc as gcode_doc + for obj in gcode_doc.__dict__.values(): + if isinstance(obj, type): + self._load_gcode_doc_cls(obj) + + ############################################## + + def _set_gcode_doc(self, gcode, cls): + rst_doc = cls.__doc__ + rst_doc = rst_doc.replace('\n' + ' '*4, '\n') + self._gcodes[gcode]._doc = rst_doc + + ############################################## + + def _load_gcode_doc_cls(self, cls): + + cls_name = cls.__name__ + for letter in self._letters.GM_LETTERS: + cls_name = cls_name.replace('_' + letter, ' ' + letter) + cls_name = cls_name.replace('_to', '-') + cls_name = cls_name.replace('_', '.') + gcodes = cls_name.split(' ') + i = 0 + while i < len(gcodes): + gcode = gcodes[i] + if gcode.endswith('-'): + start = gcode[:-1] + i += 1 + stop = gcodes[i] + for _gcode in self._gcodes.iter_on_slice(start, stop): + self._set_gcode_doc(str(_gcode), cls) + else: + self._set_gcode_doc(gcode, cls) + i += 1 + ############################################## @property @@ -542,14 +667,14 @@ class Config: @property def letters(self): - """:class:`Letters` instance""" + """:class:`LetterSet` instance""" # if isinstance(self._letters, str): - # self._letters = Letters(self._letters) + # self._letters = LetterSet(self._letters) return self._letters @property def modal_groups(self): - """:class:`ModalGroups` instance""" + """:class:`ModalGroupSet` instance""" return self._modal_groups @property diff --git a/PythonicGcodeMachine/Gcode/Rs274/Machine.py b/PythonicGcodeMachine/Gcode/Rs274/Machine.py index 1864685cb6050496468cb011df905d26b7bbde0b..8e8f4f50521ae64d3c3acc74bba21de6586180a6 100644 --- a/PythonicGcodeMachine/Gcode/Rs274/Machine.py +++ b/PythonicGcodeMachine/Gcode/Rs274/Machine.py @@ -40,8 +40,6 @@ class GcodeMachine: PARSER_CLS = GcodeParser - GM_LETTERS = 'GM' - ############################################## def __init__(self): @@ -63,7 +61,7 @@ class GcodeMachine: 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'), - ) + ) ############################################## @@ -85,13 +83,3 @@ class GcodeMachine: def reset(): pass - - ############################################## - - def is_gm_letter(self, letter): - return letter in self.GM_LETTERS - - ############################################## - - def is_gm_word(self, word): - return self.is_gm_letter(word.letter) diff --git a/examples/gcode/annotate-gcode.py b/examples/gcode/annotate-gcode.py index 0fbf7504963406188d4dd5f97394c1e6bb0b4c63..08a5dc1514a34ee60ea35a78dd62d5bcf56c9bc1 100644 --- a/examples/gcode/annotate-gcode.py +++ b/examples/gcode/annotate-gcode.py @@ -28,6 +28,8 @@ #r# #r# * :mod:`PythonicGcodeMachine.Gcode.Rs274` #r# * :mod:`PythonicGcodeMachine.Gcode.Rs274.Ast` +#r# * :mod:`PythonicGcodeMachine.Gcode.Rs274.Condig` +#r# * :mod:`PythonicGcodeMachine.Gcode.Rs274.Machine` #r# * :mod:`PythonicGcodeMachine.Gcode.Rs274.Parser` #################################################################################################### @@ -71,6 +73,7 @@ for line in program: print() # print(line.ansi_str()) # Fixme: pyterate print(str(line)) + line.check_modal_group() for word in line.iter_on_word(): if word.is_gm_gcode: margin = ' '*9 diff --git a/examples/gcode/query-gcode.py b/examples/gcode/query-gcode.py new file mode 100644 index 0000000000000000000000000000000000000000..76d39474d8b58b5a5498371251e1fb2fe979f6bc --- /dev/null +++ b/examples/gcode/query-gcode.py @@ -0,0 +1,74 @@ +#?################################################################################################## +#?# +#?# PythonicGcodeMachine - A Python G-code Toolkit +#?# 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 . +#?# +#?################################################################################################## + +#################################################################################################### + +#r# =============== +#r# Query G-codes +#r# =============== +#r# +#r# For API see +#r# +#r# * :mod:`PythonicGcodeMachine.Gcode.Rs274` +#r# * :mod:`PythonicGcodeMachine.Gcode.Rs274.Config` +#r# * :mod:`PythonicGcodeMachine.Gcode.Rs274.Machine` + +#################################################################################################### + +from pathlib import Path + +from PythonicGcodeMachine.Gcode.Rs274.Machine import GcodeMachine + +#################################################################################################### + +#r# We build a RS-274 G-code Machine + +machine = GcodeMachine() + +#################################################################################################### + +#r# We get G-code information + +gcode = machine.config.gcodes['G0'] + +print('Modal group:', gcode.modal_group) +print('Execution order:', gcode.execution_order) +#o# + +print('\nreStructuredText doc:\n') +print(gcode.doc) +#o# + +#r# Convert the reStructuredText doc using `pypandoc `_ and +#r# `Pandoc `_ + +try: + print('\nMarkdown doc:') + print(gcode.convert_doc('md')) +except (ImportError, RuntimeError): + pass +#o# + +try: + print('\nHTML doc:') + print(gcode.convert_doc('html5')) +except (ImportError, RuntimeError): + pass +#o#