mirror of
https://git.mirrors.martin98.com/https://github.com/petaflot/pygcode
synced 2025-07-28 23:32:04 +08:00
simplified machine class
separated machine state to avoid circular dependency between gcodes.py and machine.py
This commit is contained in:
parent
3d849667f5
commit
c62ee74b78
@ -1,6 +1,6 @@
|
|||||||
from .line import Line
|
from .line import Line
|
||||||
|
|
||||||
from .machine import AbstractMachine
|
#from .machine import AbstractMachine
|
||||||
|
|
||||||
class GCodeFile(object):
|
class GCodeFile(object):
|
||||||
def __init__(self, filename=None):
|
def __init__(self, filename=None):
|
||||||
@ -14,10 +14,6 @@ class GCodeFile(object):
|
|||||||
self.lines.append(line)
|
self.lines.append(line)
|
||||||
|
|
||||||
|
|
||||||
class GCodeWriterMachine(AbstractMachine):
|
|
||||||
def machine_init(self, *args, **kwargs):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def parse(filename):
|
def parse(filename):
|
||||||
# FIXME: should be an iterator, and also not terrible
|
# FIXME: should be an iterator, and also not terrible
|
||||||
file = GCodeFile()
|
file = GCodeFile()
|
||||||
|
@ -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):
|
class Machine(object):
|
||||||
def __init__(self, axes=('x', 'y', 'z')):
|
def __init__(self):
|
||||||
self.axes = axes
|
self.state = MachineState()
|
||||||
|
|
||||||
# initialize
|
def process(self, *gcode_list, **kwargs):
|
||||||
self.position = {}
|
"""
|
||||||
for axis in self.axes:
|
Process gcodes
|
||||||
self.position[axis] = 0
|
: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
|
# TODO: gcode instance to change machine's state
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
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=
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
97
pygcode/machinestate.py
Normal file
97
pygcode/machinestate.py
Normal 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)
|
||||||
|
)
|
@ -4,7 +4,6 @@ import six
|
|||||||
|
|
||||||
from .exceptions import GCodeBlockFormatError
|
from .exceptions import GCodeBlockFormatError
|
||||||
|
|
||||||
|
|
||||||
REGEX_FLOAT = re.compile(r'^-?(\d+\.?\d*|\.\d+)') # testcase: ..tests.test_words.WordValueMatchTests.test_float
|
REGEX_FLOAT = re.compile(r'^-?(\d+\.?\d*|\.\d+)') # testcase: ..tests.test_words.WordValueMatchTests.test_float
|
||||||
REGEX_INT = re.compile(r'^-?\d+')
|
REGEX_INT = re.compile(r'^-?\d+')
|
||||||
REGEX_POSITIVEINT = re.compile(r'^\d+')
|
REGEX_POSITIVEINT = re.compile(r'^\d+')
|
||||||
@ -201,7 +200,15 @@ WORD_MAP = {
|
|||||||
|
|
||||||
|
|
||||||
class Word(object):
|
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.letter = letter.upper()
|
||||||
|
|
||||||
self._value_str = None
|
self._value_str = None
|
||||||
|
Loading…
x
Reference in New Issue
Block a user