reimplemented shifting
added crop action (not tested) added rotate action (not implemented)
This commit is contained in:
parent
e4b7b7d00a
commit
3dfd860a2d
1 changed files with 179 additions and 33 deletions
|
@ -6,45 +6,175 @@ import re
|
|||
import sys
|
||||
from decimal import Decimal as d
|
||||
|
||||
AXIS = "XYZ"
|
||||
AXIS_REGEX = r"\b([%s][\+\-0-9\.]+)\b" % AXIS
|
||||
|
||||
class GCodePosition(object):
|
||||
|
||||
def __init__(self, lower, upper, shift):
|
||||
self.pos = [d(0), d(0), d(0)]
|
||||
class GCodeFilter(object):
|
||||
|
||||
def __init__(self, lower, upper, func=None):
|
||||
self.pos = tuple([d(0)] * len(AXIS))
|
||||
self.lower = lower
|
||||
self.upper = upper
|
||||
self.shift = shift
|
||||
self.recalc_func = func
|
||||
self.destination_position = PositionHandler()
|
||||
|
||||
def is_inside(self):
|
||||
for i in range(len(self.pos)):
|
||||
if not ((self.lower[i] is None or (self.lower[i] <= self.pos[i])) and \
|
||||
(self.upper[i] is None or (self.upper[i] >= self.pos[i]))):
|
||||
def is_inside(self, position):
|
||||
for i in range(len(position)):
|
||||
if not ((self.lower[i] is None or (self.lower[i] <= position[i])) and \
|
||||
(self.upper[i] is None or (self.upper[i] >= position[i]))):
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def get_shifted_line(self, line):
|
||||
""" parse the line, update current position replace shifted values
|
||||
"""
|
||||
axis_pattern = r"\b([XYZ][\+\-0-9\.]+)\b"
|
||||
result = []
|
||||
# first: update the x/y/z position (make sure that we are in the box)
|
||||
for token in re.findall(axis_pattern, line, flags=re.I):
|
||||
for i, axis in enumerate("XYZ"):
|
||||
def update_position(self, line):
|
||||
for comment_sep in ";(":
|
||||
if comment_sep in line:
|
||||
line = line.split(comment_sep)[0]
|
||||
new_pos = list(self.pos)
|
||||
for token in re.findall(AXIS_REGEX, line, flags=re.I):
|
||||
for i, axis in enumerate(AXIS):
|
||||
if token.upper().startswith(axis):
|
||||
self.pos[i] = d(token[1:])
|
||||
# second: replace values
|
||||
if self.is_inside():
|
||||
def replace_value(match):
|
||||
token = match.group(0).upper()
|
||||
for i, axis in enumerate("XYZ"):
|
||||
if token.startswith(axis):
|
||||
value = d(token[1:])
|
||||
return token[0] + str(value + self.shift[i])
|
||||
return token
|
||||
return re.sub(axis_pattern, replace_value, line, re.I)
|
||||
new_pos[i] = d(token[1:])
|
||||
new_pos = tuple(new_pos)
|
||||
if self.pos != new_pos:
|
||||
self.pos = new_pos
|
||||
return True
|
||||
else:
|
||||
return line
|
||||
return False
|
||||
|
||||
def get_processed_lines(self, line):
|
||||
""" parse the line, update current position and replace line string
|
||||
"""
|
||||
if self.update_position(line):
|
||||
result = []
|
||||
handler = LineHandler(line)
|
||||
positions = self.recalc_func(self.pos, is_inside=self.is_inside)
|
||||
for pos in positions:
|
||||
changed_axes = self.destination_position.get_changed_axes(pos)
|
||||
yield handler.get_line(pos, changed_axes)
|
||||
self.destination_position.update(pos)
|
||||
else:
|
||||
# no coordinate given
|
||||
yield line
|
||||
|
||||
|
||||
class PositionHandler(object):
|
||||
|
||||
def __init__(self):
|
||||
self.pos = tuple([0] * len(AXIS))
|
||||
|
||||
def update(self, position):
|
||||
new_pos = tuple(position)
|
||||
if self.pos != new_pos:
|
||||
self.pos = new_pos
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def get_changed_axes(self, position):
|
||||
for i, axis in enumerate(AXIS):
|
||||
if position[i] != self.pos[i]:
|
||||
yield (i, axis)
|
||||
|
||||
|
||||
class LineHandler(object):
|
||||
|
||||
def __init__(self, line):
|
||||
self._digits = 0
|
||||
self._processed = []
|
||||
# remove trailing whitespace (and linebreak)
|
||||
self.line = line.rstrip()
|
||||
# all missing whitespace
|
||||
self._suffix = line[len(self.line):]
|
||||
|
||||
def _get_digits(self, text):
|
||||
if "." in text:
|
||||
return len(text.split(".", 1))
|
||||
else:
|
||||
return 0
|
||||
|
||||
def _get_number_string(self, value):
|
||||
return str(value)
|
||||
return ("%%.%df" % self._digits) % value
|
||||
|
||||
def replace_value(self, match):
|
||||
token = match.group(0).upper()
|
||||
for i, axis in enumerate(AXIS):
|
||||
if token.startswith(axis):
|
||||
self._processed.append(i)
|
||||
self._digits = max(self._digits, self._get_digits(token[1:]))
|
||||
if d(token[1:]) != self._position[i]:
|
||||
return token[0] + self._get_number_string(self._position[i])
|
||||
else:
|
||||
return token
|
||||
|
||||
def get_line(self, position, axes):
|
||||
result = []
|
||||
self._processed = []
|
||||
self._position = position
|
||||
line = re.sub(AXIS_REGEX, self.replace_value, self.line, re.I)
|
||||
if not self._processed:
|
||||
# no axis definition
|
||||
line = self.line
|
||||
else:
|
||||
# at least one axis was defined
|
||||
for i, axis in axes:
|
||||
if not i in self._processed:
|
||||
line += " %s%s" % (axis, self._get_number_string(position[i]))
|
||||
return line + self._suffix
|
||||
|
||||
|
||||
def shift_position(pos, shift, inside_func):
|
||||
if inside_func(pos):
|
||||
new_pos = [(axis + shift_axis) for axis, shift_axis in zip(pos, shift)]
|
||||
else:
|
||||
new_pos = pos
|
||||
return (tuple(new_pos), )
|
||||
|
||||
|
||||
class CropFilter(object):
|
||||
|
||||
def __init__(self):
|
||||
self._was_inside = True
|
||||
self._previous_position = None
|
||||
self._recurse_counter = 0
|
||||
self._max_recurse = 20
|
||||
|
||||
def crop_bounds(self, pos, inside_func):
|
||||
is_inside = inside_func(pos)
|
||||
result = None
|
||||
if is_inside and self._was_inside:
|
||||
# inside -> inside
|
||||
result = (pos, )
|
||||
elif not is_inside and not self._was_inside:
|
||||
# outside -> outside
|
||||
result = []
|
||||
else:
|
||||
# outside -> inside OR inside -> outside
|
||||
border_pos = self._recurse_border_position(self._previous_position,
|
||||
pos, inside_func)
|
||||
self._was_inside = is_inside
|
||||
if is_inside:
|
||||
result = (border_pos, pos)
|
||||
else:
|
||||
result = (pos, )
|
||||
self._previous_position = pos
|
||||
return result
|
||||
|
||||
def _recurse_border_position(self, p1, p2, inside_func):
|
||||
""" simple and stupid: bisections between p1 and p2
|
||||
"""
|
||||
p_middle = tuple([0.5 * (axis1 + axis2)
|
||||
for axis1, axis2 in zip(p1, p2)])
|
||||
if self._recurse_counter >= self._max_recurse:
|
||||
self._recurse_counter = 0
|
||||
return p_middle
|
||||
else:
|
||||
if inside_func(p_middle) == inside_func(p1):
|
||||
p1_new, p2_new = p_middle, p2
|
||||
else:
|
||||
p1_new, p2_new = p1, p_middle
|
||||
return self._recurse_border_position(p1_new, p2_new, inside_func)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
@ -58,12 +188,28 @@ if __name__ == "__main__":
|
|||
parser.add_argument('--shiftx', dest="shiftx", type=d, default=d(0))
|
||||
parser.add_argument('--shifty', dest="shifty", type=d, default=d(0))
|
||||
parser.add_argument('--shiftz', dest="shiftz", type=d, default=d(0))
|
||||
parser.add_argument('--rotatex', dest="rotatex", type=d, default=d(0))
|
||||
parser.add_argument('--rotatey', dest="rotatey", type=d, default=d(0))
|
||||
parser.add_argument('--rotatez', dest="rotatez", type=d, default=d(0))
|
||||
parser.add_argument('action', choices=("shift", "crop", "rotate"))
|
||||
infile = sys.stdin
|
||||
outfile = sys.stdout
|
||||
options = parser.parse_args()
|
||||
shifter = GCodePosition((options.minx, options.miny, options.minz),
|
||||
(options.maxx, options.maxy, options.maxz),
|
||||
(options.shiftx, options.shifty, options.shiftz))
|
||||
if options.action == "shift":
|
||||
shift = (options.shiftx, options.shifty, options.shiftz)
|
||||
func = lambda pos, is_inside: shift_position(pos, shift, is_inside)
|
||||
elif options.action == "crop":
|
||||
crop_filter = CropFilter()
|
||||
func = crop_filter.crop_bounds
|
||||
elif options.action == "rotate":
|
||||
rotate = (options.rotatex, options.rotatey, options.rotatez)
|
||||
func = lambda pos, is_inside: rotate_position(pos, shift, is_inside)
|
||||
else:
|
||||
print >>sys.stderr, "No valid action choosen"
|
||||
sys.exit(1)
|
||||
gcode_filter = GCodeFilter((options.minx, options.miny, options.minz),
|
||||
(options.maxx, options.maxy, options.maxz), func=func)
|
||||
for line in infile.readlines():
|
||||
outfile.write(shifter.get_shifted_line(line))
|
||||
for out_line in gcode_filter.get_processed_lines(line):
|
||||
outfile.write(out_line)
|
||||
|
||||
|
|
Loading…
Reference in a new issue