simplified machine class

separated machine state to avoid circular dependency between gcodes.py and machine.py
This commit is contained in:
Peter Boin 2017-07-07 02:42:43 +10:00
parent 3d849667f5
commit c62ee74b78
4 changed files with 127 additions and 73 deletions

View File

@ -1,6 +1,6 @@
from .line import Line
from .machine import AbstractMachine
#from .machine import AbstractMachine
class GCodeFile(object):
def __init__(self, filename=None):
@ -14,10 +14,6 @@ class GCodeFile(object):
self.lines.append(line)
class GCodeWriterMachine(AbstractMachine):
def machine_init(self, *args, **kwargs):
pass
def parse(filename):
# FIXME: should be an iterator, and also not terrible
file = GCodeFile()

View File

@ -1,70 +1,24 @@
from collections import defaultdict
from .gcodes import MODAL_GROUP_MAP, GCode
from .line import Line
from .machinestate import MachineState
class MachineState(object):
def __init__(self, axes=('x', 'y', 'z')):
self.axes = axes
class Machine(object):
def __init__(self):
self.state = MachineState()
# initialize
self.position = {}
for axis in self.axes:
self.position[axis] = 0
def process(self, *gcode_list, **kwargs):
"""
Process gcodes
:param gcode_list: list of GCode instances
:param modal_params: list of Word instances to be applied to current movement mode
"""
modal_params = kwargs.get('modal_params', [])
for gcode in sorted(gcode_list):
self.state.set_mode(gcode) # if gcode is not modal, it's ignored
self.time = 0
class AbstractMachine(object):
"""Basis for a real / virtualized machine to process gcode"""
def __init__(self, *args, **kwargs):
self.axes = kwargs.get('axes', ('x', 'y', 'z'))
self.max_rate = kwargs.get('max_rate', {
'x': 500, # mm/min
'y': 500, # mm/min
'z': 500, # mm/min
})
self.max_travel = kwargs.get('max_travel', {
'x': 200, # mm
'y': 200, # mm
'z': 200, # mm
})
self.max_spindle_speed = kwargs.get('max_spindle_speed', 1000) # rpm
self.min_spindle_speed = kwargs.get('max_spindle_speed', 0) # rpm
# initialize
self.state = MachineState(self.axes)
# machine-specific initialization
self.machine_init(*args, **kwargs)
def machine_init(self, *args, **kwargs):
# Executed last in instances' __init__ call.
# Parameters are identical to that of __init__
pass
def process_line(self, line):
"""Change machine's state based on the given gcode line"""
pass # TODO
"""
class Axes(object):
pass
class MyMachineState(MachineState):
axes_state_class = AxesState
pass
class MyMachine(AbstractMachine):
available_axes = set('xyz')
state_class = MyMachineState
m = MyMachine(
state=MyMachineState(
absolute_position=
),
)
"""
# TODO: gcode instance to change machine's state

97
pygcode/machinestate.py Normal file
View File

@ -0,0 +1,97 @@
from .gcodes import GCode
from .line import Line
class State(object):
"""State of a Machine"""
# State is very forgiving:
# Anything possible in a machine's state may be changed & fetched.
# For example: x, y, z, a, b, c may all be set & requested.
# However, the machine for which this state is stored probably doesn't
# have all possible 6 axes.
# It is also possible to set an axis to an impossibly large distance.
# It is the responsibility of the Machine using this class to be
# discerning in these respects.
def __init__(self):
self.axes = defaultdict(lambda: 0.0) # aka: "machine coordinates"
self.work_offset = defaultdict(lambda: 0.0)
# TODO: how to manage work offsets? (probs not like the above)
# read up on:
# - G92: coordinate system offset
# - G54-G59: select coordinate system (offsets from machine coordinates set by G10 L2)
# TODO: Move this class into MachineState
class MachineState(object):
"""Machine's state, and mode"""
# Mode is defined by gcodes set by processed blocks:
# see modal_group in gcode.py module for details
def __init__(self):
self.modal_groups = defaultdict(lambda: None)
# populate with all groups
for modal_group in MODAL_GROUP_MAP.values():
self.modal_groups[modal_group] = None
# Default mode:
self.set_mode(*Line('''
G17 (plane_selection: X/Y plane)
G90 (distance: absolute position. ie: not "turtle" mode)
G91.1 (arc_ijk_distance: IJK sets arc center vertex relative to current position)
G94 (feed_rate_mode: feed-rate defined in units/min)
G21 (units: mm)
G40 (cutter_diameter_comp: no compensation)
G49 (tool_length_offset: no offset)
G61 (control_mode: exact path mode)
G97 (spindle_speed_mode: RPM Mode)
M0 (stopping: program paused)
M5 (spindle: off)
M9 (coolant: off)
F100 (feed_rate: 100 mm/min)
S1000 (tool: 1000 rpm, when it's switched on)
''').block.gcodes) # note: although this is not a single line
# '\n' is just treated like any other whitespace,
# so it behaves like a single line.
def set_mode(self, *gcode_list):
for g in sorted(gcode_list): # sorted by execution order
if g.modal_group is not None:
self.modal_groups[g.modal_group] = g
def __getattr__(self, key):
if key in MODAL_GROUP_MAP:
return self.modal_groups[MODAL_GROUP_MAP[key]]
raise AttributeError("'{cls}' object has no attribute '{key}'".format(
cls=self.__class__.__name__,
key=key
))
def __setattr__(self, key, value):
if key in MODAL_GROUP_MAP:
# Set/Clear modal group gcode
if value is None:
# clear mode group
self.modal_groups[MODAL_GROUP_MAP[key]] = None
else:
# set mode group explicitly
# (recommended to use self.set_mode(value) instead)
assert isinstance(value, GCode), "invalid value type: %r" % value
assert value.modal_group == MODAL_GROUP_MAP[key], \
"cannot set '%s' mode as %r, wrong group" % (key, value)
self.modal_groups[MODAL_GROUP_MAP[key]] = value
else:
self.__dict__[key] = value
def __str__(self):
gcode_list = []
for modal_group in sorted(MODAL_GROUP_MAP.values()):
if self.modal_groups[modal_group]:
gcode_list.append(self.modal_groups[modal_group])
return ' '.join(str(g) for g in gcode_list)
def __repr__(self):
return "<{class_name}: {gcodes}>".format(
class_name=self.__class__.__name__, gcodes=str(self)
)

View File

@ -4,7 +4,6 @@ import six
from .exceptions import GCodeBlockFormatError
REGEX_FLOAT = re.compile(r'^-?(\d+\.?\d*|\.\d+)') # testcase: ..tests.test_words.WordValueMatchTests.test_float
REGEX_INT = re.compile(r'^-?\d+')
REGEX_POSITIVEINT = re.compile(r'^\d+')
@ -201,7 +200,15 @@ WORD_MAP = {
class Word(object):
def __init__(self, letter, value):
def __init__(self, *args):
assert len(args) in [1, 2], "input arguments either: (letter, value) or (word_str)"
if len(args) == 2:
(letter, value) = args
else:
word_str = args[0]
letter = word_str[0] # first letter
value = word_str[1:] # rest of string
self.letter = letter.upper()
self._value_str = None