Commit f2bafa75 authored by Fabrice Salvaire's avatar Fabrice Salvaire

improved G-code API

parent 6898bc98
......@@ -19,6 +19,8 @@
####################################################################################################
"""Module to implement an AST for RS-274 G-code.
All classes are clonable.
"""
####################################################################################################
......@@ -64,6 +66,7 @@ __all__ = [
####################################################################################################
import math
import re
import colors
......@@ -84,6 +87,8 @@ class Program:
str(program)
program2 = program.clone()
"""
##############################################
......@@ -93,6 +98,16 @@ class Program:
##############################################
def clone(self):
program = self.__class__()
for line in self:
program += line.clone()
return program
##############################################
def push(self, line):
self._lines.append(line)
......@@ -134,7 +149,42 @@ class Program:
####################################################################################################
class LineItem:
class CloneMixin:
@staticmethod
def _clone_value(value):
if hasattr(value, 'clone'):
return value.clone()
else:
return value
####################################################################################################
class LineItem(CloneMixin):
##############################################
def _check_value(self, value):
if (isinstance(value, (int, float)) or
isinstance(value, RealValue)):
return value
else:
try:
str_value = str(value)
except:
raise ValueError("Invalid value {}".format(value))
# Fixme:
from .Parser import GcodeParser, GcodeParserError
parser = GcodeParser()
try:
# Fixme: parser hack
ast = parser.parse('X' + value)
return ast[0].value
except GcodeParserError:
raise ValueError("Invalid G-code value {}".format(value))
##############################################
def ansi_str(self):
return str(self)
......@@ -162,11 +212,13 @@ class Line:
line += Comment('move')
line += Word('X', 10)
line += Comment('Y value')
line += Word('Y', 20)
line += Word('Y', 20.)
line += ParameterSetting('1', 1.2)
# using expression
# using expression, AST way
line += Word('Z', Addition(30, Multiply(Parameter(100), Cosine(30))))
# string way
line += Word('Z', '[30 + [#100 * cos[30]]]')
# Array interface
for item in line:
......@@ -175,6 +227,18 @@ class Line:
str(line)
print(line.ansi_str()) # use ANSI colors, see Line.ANSI_... attributes
a_line = line.clone()
Values can be passed as:
* int or float,
* AST for expression,
* any object that "str" evaluate to a valid G-code expression.
As a shortcut, a G/M-code operation can be passed as string::
line += 'G0'
Expression can be evaluated using :code:`float(obj.value)`, excepted when we must access a parameter
value.
......@@ -200,6 +264,16 @@ class Line:
##############################################
def clone(self):
line = self.__class__(self._deleted, self._line_number, self._comment)
for item in self:
line += item.clone()
return line
##############################################
@property
def deleted(self):
return self._deleted
......@@ -233,13 +307,26 @@ class Line:
##############################################
def _push_item(self, item):
if not isinstance(item, LineItem):
item = Word.from_str(item)
# Fixme: try to parse ???
self._items.append(item)
def push_items(self, iterable):
"""Method to push an iterable"""
for item in iterable:
self.push(item)
def push(self, item):
if isinstance(item, LineItem):
self._items.append(item)
"""Method to push a valid item, a 'G/Mxxx' shortcut string, a list or tuple"""
if isinstance(item, (list, tuple, Line)):
self.push_items(item)
else:
raise ValueError
self._push_item(item)
def __iadd__(self, item):
"""push shortcut"""
self.push(item)
return self
......@@ -273,6 +360,24 @@ class Line:
##############################################
def toggle(self):
"""Toggle deleted flag"""
self._deleted = not self._deleted
def remove_line_number(self):
self._line_number = None
##############################################
def remove_comment(self):
self._comment = None
for i, item in enumerate(self):
if isinstance(item, Comment):
# self._items.pop(i)
del self._items[i]
##############################################
def __repr__(self):
items = []
......@@ -328,6 +433,11 @@ class Comment(LineItem):
##############################################
def clone(self):
return self.__class__(self._text)
##############################################
def set(self, text):
if '(' in text:
raise ValueError('Comment cannot contains a "("')
......@@ -367,6 +477,20 @@ class Word(LineItem):
'X', 'Y', 'Z',
)
WORD_RE = re.compile('(G|M)(\d+)')
##############################################
@classmethod
def from_str(cls, obj):
str_obj = str(obj)
match = cls.WORD_RE.match(str_obj)
if match is not None:
return cls(*match.groups())
else:
raise ValueError(obj)
##############################################
def __init__(self, letter, value):
......@@ -375,6 +499,11 @@ class Word(LineItem):
##############################################
def clone(self):
return self.__class__(self._letter, self._clone_value(self._value))
##############################################
@property
def letter(self):
return self._letter
......@@ -392,13 +521,12 @@ class Word(LineItem):
@value.setter
def value(self, value):
# float expression ...
self._value = value
self._value = self._check_value(value)
##############################################
def __repr__(self):
return 'Word({0._letter} {0._value})'.format(self)
return 'Word({0._letter}, {0._value})'.format(self)
def __str__(self):
return '{0._letter}{0._value}'.format(self)
......@@ -412,7 +540,7 @@ class Word(LineItem):
####################################################################################################
class RealValue:
class RealValue(CloneMixin):
pass
####################################################################################################
......@@ -426,6 +554,11 @@ class ParameterMixin:
##############################################
def clone(self):
return self.__class__(self._parameter)
##############################################
@property
def parameter(self):
return self._parameter
......@@ -450,6 +583,11 @@ class ParameterSetting(LineItem, ParameterMixin):
ParameterMixin.__init__(self, parameter)
self.value = value
##############################################
def clone(self):
return self.__class__(self._parameter, self._clone_value(self._value))
##############################################
@property
def value(self):
......@@ -457,8 +595,7 @@ class ParameterSetting(LineItem, ParameterMixin):
@value.setter
def value(self, value):
# float expression ...
self._value = value
self._value = self._check_value(value)
##############################################
......@@ -509,6 +646,11 @@ class UnaryOperation(RealValue):
##############################################
def clone(self):
return self.__class__(self._clone_value(self._arg))
##############################################
@property
def arg(self):
return self._arg
......@@ -601,6 +743,11 @@ class BinaryOperation(RealValue):
##############################################
def clone(self):
return self.__class__(self._clone_value(self._arg1), self._clone_value(self.arg2))
##############################################
@property
def arg1(self):
return self._arg1
......
......@@ -20,11 +20,15 @@
####################################################################################################
#r# ===========================
#r# Annotate a G-code program
#r# ===========================
#r#
#r# ==================================================
#r# Example to show how to annotate a G-code program
#r# ==================================================
#r# For API see
#r#
#r# * :mod:`PythonicGcodeMachine.Gcode.Rs274`
#r# * :mod:`PythonicGcodeMachine.Gcode.Rs274.Ast`
#r# * :mod:`PythonicGcodeMachine.Gcode.Rs274.Parser`
####################################################################################################
......
#?##################################################################################################
#?#
#?# 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/>.
#?#
#?##################################################################################################
####################################################################################################
#r# ===========================
#r# Generate a G-code program
#r# ===========================
#r#
#r# For API see
#r#
#r# * :mod:`PythonicGcodeMachine.Gcode.Rs274`
#r# * :mod:`PythonicGcodeMachine.Gcode.Rs274.Ast`
#r# * :mod:`PythonicGcodeMachine.Gcode.Rs274.Parser`
####################################################################################################
from PythonicGcodeMachine.Gcode.Rs274 import *
from PythonicGcodeMachine.Gcode.Rs274.Ast import *
####################################################################################################
#r# Create a G-code line (block) using AST API
line = Line(deleted=False, line_number=1, comment='a G-code block')
# Push some items
# Note: order doesn't matter, see RS-274 for details
line += Word('G', 0)
line += Comment('fast move')
line += Word('X', 10)
line += Word('Y', 20)
print(line)
#o#
#r# More simpler way to pass G/M-code
a_line = Line()
a_line += 'G0'
a_line += Word('X', 10)
print(a_line)
#o#
#r# Using the G-code parser
parser = GcodeParser()
a_line = parser.parse('G0 X0 Y0')
a_line += Word('Z', 0)
print(a_line)
#o#
a_line = Line()
a_line += 'G0'
parsed_line = parser.parse('X1 Y2')
print(list(parsed_line))
first_item = parsed_line[0]
print(first_item)
a_line += parsed_line
print(a_line)
#o#
#r# Expression : the AST way
line2 = line.clone()
line2 += Word('Z', Addition(30, Multiply(Parameter(100), Cosine(30))))
print(line2)
#o#
#r# Expression : the literal way
line3 = line.clone()
line3 += Word('Z', '[30 + [#100 * cos[30]]]')
print(line3)
#o#
#r# Invalid expression
try:
line4 = line.clone()
line4 += Word('Z', '1 + 2]')
print(line4)
except ValueError:
pass
#r# Create a G-code program
program = Program()
program += line
line2.line_number = 2
line2.comment = 'using expression'
program += line2
line3.deleted = True
line3.line_number = 3
line3.comment = None
program += line3
print(program)
#o#
#r# Line cleanup tools
line = Line(deleted=False, line_number=1, comment='a G-code block')
line += 'G0'
line += Comment('fast move')
line += Word('X', 10)
line += Word('Y', 20)
print(line)
#o#
line.toggle()
print(line)
#o#
line.toggle()
line.remove_line_number()
print(line)
#o#
line.remove_comment()
print(line)
#o#
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment