moved to class-based filters
added "DensityFilter" (additional parallel lines)
This commit is contained in:
parent
3dfd860a2d
commit
9447c747b5
1 changed files with 131 additions and 56 deletions
|
@ -17,7 +17,8 @@ class GCodeFilter(object):
|
||||||
self.lower = lower
|
self.lower = lower
|
||||||
self.upper = upper
|
self.upper = upper
|
||||||
self.recalc_func = func
|
self.recalc_func = func
|
||||||
self.destination_position = PositionHandler()
|
self.source_pos = PositionHandler()
|
||||||
|
self.target_pos = PositionHandler()
|
||||||
|
|
||||||
def is_inside(self, position):
|
def is_inside(self, position):
|
||||||
for i in range(len(position)):
|
for i in range(len(position)):
|
||||||
|
@ -37,6 +38,7 @@ class GCodeFilter(object):
|
||||||
new_pos[i] = d(token[1:])
|
new_pos[i] = d(token[1:])
|
||||||
new_pos = tuple(new_pos)
|
new_pos = tuple(new_pos)
|
||||||
if self.pos != new_pos:
|
if self.pos != new_pos:
|
||||||
|
self.source_pos.update(new_pos)
|
||||||
self.pos = new_pos
|
self.pos = new_pos
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
|
@ -48,11 +50,10 @@ class GCodeFilter(object):
|
||||||
if self.update_position(line):
|
if self.update_position(line):
|
||||||
result = []
|
result = []
|
||||||
handler = LineHandler(line)
|
handler = LineHandler(line)
|
||||||
positions = self.recalc_func(self.pos, is_inside=self.is_inside)
|
for pos in self.transform_position():
|
||||||
for pos in positions:
|
changed_axes = self.target_pos.get_changed_axes(pos)
|
||||||
changed_axes = self.destination_position.get_changed_axes(pos)
|
|
||||||
yield handler.get_line(pos, changed_axes)
|
yield handler.get_line(pos, changed_axes)
|
||||||
self.destination_position.update(pos)
|
self.target_pos.update(pos)
|
||||||
else:
|
else:
|
||||||
# no coordinate given
|
# no coordinate given
|
||||||
yield line
|
yield line
|
||||||
|
@ -62,10 +63,17 @@ class PositionHandler(object):
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.pos = tuple([0] * len(AXIS))
|
self.pos = tuple([0] * len(AXIS))
|
||||||
|
self.pos_stack = [tuple(self.pos)]
|
||||||
|
|
||||||
|
def push(self, position):
|
||||||
|
self.pos_stack.insert(0, tuple(position))
|
||||||
|
if len(self.pos_stack) > 10:
|
||||||
|
self.pos_stack.pop(-1)
|
||||||
|
|
||||||
def update(self, position):
|
def update(self, position):
|
||||||
new_pos = tuple(position)
|
new_pos = tuple(position)
|
||||||
if self.pos != new_pos:
|
if self.pos != new_pos:
|
||||||
|
self.push(new_pos)
|
||||||
self.pos = new_pos
|
self.pos = new_pos
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
|
@ -80,31 +88,20 @@ class PositionHandler(object):
|
||||||
class LineHandler(object):
|
class LineHandler(object):
|
||||||
|
|
||||||
def __init__(self, line):
|
def __init__(self, line):
|
||||||
self._digits = 0
|
|
||||||
self._processed = []
|
self._processed = []
|
||||||
# remove trailing whitespace (and linebreak)
|
# remove trailing whitespace (and linebreak)
|
||||||
self.line = line.rstrip()
|
self.line = line.rstrip()
|
||||||
# all missing whitespace
|
# all missing whitespace
|
||||||
self._suffix = line[len(self.line):]
|
self._suffix = line[len(self.line):]
|
||||||
|
self._axes = []
|
||||||
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):
|
def replace_value(self, match):
|
||||||
token = match.group(0).upper()
|
token = match.group(0).upper()
|
||||||
for i, axis in enumerate(AXIS):
|
for i, axis in enumerate(AXIS):
|
||||||
if token.startswith(axis):
|
if token.startswith(axis):
|
||||||
self._processed.append(i)
|
self._processed.append(i)
|
||||||
self._digits = max(self._digits, self._get_digits(token[1:]))
|
|
||||||
if d(token[1:]) != self._position[i]:
|
if d(token[1:]) != self._position[i]:
|
||||||
return token[0] + self._get_number_string(self._position[i])
|
return token[0] + str(self._position[i])
|
||||||
else:
|
else:
|
||||||
return token
|
return token
|
||||||
|
|
||||||
|
@ -112,6 +109,7 @@ class LineHandler(object):
|
||||||
result = []
|
result = []
|
||||||
self._processed = []
|
self._processed = []
|
||||||
self._position = position
|
self._position = position
|
||||||
|
self._axes = axes
|
||||||
line = re.sub(AXIS_REGEX, self.replace_value, self.line, re.I)
|
line = re.sub(AXIS_REGEX, self.replace_value, self.line, re.I)
|
||||||
if not self._processed:
|
if not self._processed:
|
||||||
# no axis definition
|
# no axis definition
|
||||||
|
@ -120,61 +118,128 @@ class LineHandler(object):
|
||||||
# at least one axis was defined
|
# at least one axis was defined
|
||||||
for i, axis in axes:
|
for i, axis in axes:
|
||||||
if not i in self._processed:
|
if not i in self._processed:
|
||||||
line += " %s%s" % (axis, self._get_number_string(position[i]))
|
line += " %s%s" % (axis, str(position[i]))
|
||||||
return line + self._suffix
|
return line + self._suffix
|
||||||
|
|
||||||
|
|
||||||
def shift_position(pos, shift, inside_func):
|
class ShiftFilter(GCodeFilter):
|
||||||
if inside_func(pos):
|
|
||||||
new_pos = [(axis + shift_axis) for axis, shift_axis in zip(pos, shift)]
|
def __init__(self, *args, **kwargs):
|
||||||
|
self.shift = kwargs.pop("shift", tuple([0] * len(AXIS)))
|
||||||
|
super(ShiftFilter, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
def transform_position(self):
|
||||||
|
if self.is_inside(self.pos):
|
||||||
|
yield [(axis + shift_axis)
|
||||||
|
for axis, shift_axis in zip(self.pos, self.shift)]
|
||||||
else:
|
else:
|
||||||
new_pos = pos
|
yield self.pos
|
||||||
return (tuple(new_pos), )
|
|
||||||
|
|
||||||
|
|
||||||
class CropFilter(object):
|
class DensifyFilter(GCodeFilter):
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self, *args, **kwargs):
|
||||||
self._was_inside = True
|
self.loops = kwargs.pop("densify_loops")
|
||||||
self._previous_position = None
|
self.minimum_step = kwargs.pop("densify_minimum_step")
|
||||||
self._recurse_counter = 0
|
self.direction = kwargs.pop("densify_dir").upper()
|
||||||
self._max_recurse = 20
|
self.direction_index = AXIS.index(self.direction)
|
||||||
|
super(DensifyFilter, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
def crop_bounds(self, pos, inside_func):
|
def _densify_is_valid(self, p1, p2):
|
||||||
is_inside = inside_func(pos)
|
for index, (v1, v2) in enumerate(zip(p1, p2)):
|
||||||
|
if index == self.direction_index:
|
||||||
|
if v1 == v2:
|
||||||
|
return False
|
||||||
|
elif abs(v1 - v2) < self.minimum_step:
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
pass
|
||||||
|
elif v1 != v2:
|
||||||
|
# all other direction vectors must be zero
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
return True
|
||||||
|
|
||||||
|
def _shift_point(self, pos, diff, reverse=False):
|
||||||
|
if reverse:
|
||||||
|
target = [p - d for p, d in zip(pos, diff)]
|
||||||
|
else:
|
||||||
|
target = [p + d for p, d in zip(pos, diff)]
|
||||||
|
return tuple(target)
|
||||||
|
|
||||||
|
def transform_position(self):
|
||||||
|
stack = self.source_pos.pos_stack
|
||||||
|
if (len(stack) > 2) and self.is_inside(stack[0]) and \
|
||||||
|
self.is_inside(stack[1]) and \
|
||||||
|
self._densify_is_valid(stack[0], stack[1]):
|
||||||
|
# move sideways
|
||||||
|
divider = d(float(self.loops * 2 + 1))
|
||||||
|
dir_step = []
|
||||||
|
for now, prev in zip(stack[0], stack[1]):
|
||||||
|
value = (now - prev)/divider
|
||||||
|
# adjust accuracy
|
||||||
|
value = value.quantize(now)
|
||||||
|
dir_step.append(value)
|
||||||
|
# move forward and backward
|
||||||
|
line_diff = [prev - prevprev
|
||||||
|
for prev, prevprev in zip(stack[1], stack[2])]
|
||||||
|
current = stack[1]
|
||||||
|
# run some loops
|
||||||
|
for step in range(self.loops):
|
||||||
|
# move to the side
|
||||||
|
current = self._shift_point(current, dir_step)
|
||||||
|
yield current
|
||||||
|
# move back against line_diff
|
||||||
|
current = self._shift_point(current, line_diff, reverse=True)
|
||||||
|
yield current
|
||||||
|
# move to the side
|
||||||
|
current = self._shift_point(current, dir_step)
|
||||||
|
yield current
|
||||||
|
# move up against line_diff
|
||||||
|
current = self._shift_point(current, line_diff, reverse=False)
|
||||||
|
yield current
|
||||||
|
# always move to the final point
|
||||||
|
yield stack[0]
|
||||||
|
|
||||||
|
|
||||||
|
class CropFilter(GCodeFilter):
|
||||||
|
|
||||||
|
def transform_position(self):
|
||||||
|
is_inside = self.is_inside(self.pos)
|
||||||
|
was_inside = self.pos_stack[1]
|
||||||
result = None
|
result = None
|
||||||
if is_inside and self._was_inside:
|
if is_inside and was_inside:
|
||||||
# inside -> inside
|
# inside -> inside
|
||||||
result = (pos, )
|
yield self.pos
|
||||||
elif not is_inside and not self._was_inside:
|
elif not is_inside and not was_inside:
|
||||||
# outside -> outside
|
# outside -> outside
|
||||||
result = []
|
pass
|
||||||
else:
|
else:
|
||||||
# outside -> inside OR inside -> outside
|
# outside -> inside OR inside -> outside
|
||||||
border_pos = self._recurse_border_position(self._previous_position,
|
border_pos = self._recurse_border_position(self._previous_position,
|
||||||
pos, inside_func)
|
pos, self.is_inside)
|
||||||
self._was_inside = is_inside
|
# the border position is always the first step
|
||||||
|
yield border_pos
|
||||||
|
# omit the current position if we are outside
|
||||||
if is_inside:
|
if is_inside:
|
||||||
result = (border_pos, pos)
|
yield self.pos
|
||||||
else:
|
|
||||||
result = (pos, )
|
|
||||||
self._previous_position = pos
|
|
||||||
return result
|
|
||||||
|
|
||||||
def _recurse_border_position(self, p1, p2, inside_func):
|
def _recurse_border_position(self, p1, p2, depth_limit=20):
|
||||||
""" simple and stupid: bisections between p1 and p2
|
""" simple and stupid: bisections between p1 and p2
|
||||||
"""
|
"""
|
||||||
p_middle = tuple([0.5 * (axis1 + axis2)
|
p_middle = tuple([0.5 * (axis1 + axis2)
|
||||||
for axis1, axis2 in zip(p1, p2)])
|
for axis1, axis2 in zip(p1, p2)])
|
||||||
if self._recurse_counter >= self._max_recurse:
|
if depth_limit < 0:
|
||||||
self._recurse_counter = 0
|
|
||||||
return p_middle
|
return p_middle
|
||||||
else:
|
else:
|
||||||
if inside_func(p_middle) == inside_func(p1):
|
if self.is_inside(p_middle) == self.is_inside(p1):
|
||||||
p1_new, p2_new = p_middle, p2
|
p1_new, p2_new = p_middle, p2
|
||||||
else:
|
else:
|
||||||
p1_new, p2_new = p1, p_middle
|
p1_new, p2_new = p1, p_middle
|
||||||
return self._recurse_border_position(p1_new, p2_new, inside_func)
|
return self._recurse_border_position(p1_new, p2_new,
|
||||||
|
depth_limit=depth_limit-1)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
@ -191,24 +256,34 @@ if __name__ == "__main__":
|
||||||
parser.add_argument('--rotatex', dest="rotatex", 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('--rotatey', dest="rotatey", type=d, default=d(0))
|
||||||
parser.add_argument('--rotatez', dest="rotatez", type=d, default=d(0))
|
parser.add_argument('--rotatez', dest="rotatez", type=d, default=d(0))
|
||||||
parser.add_argument('action', choices=("shift", "crop", "rotate"))
|
parser.add_argument('--densify-dir', dest="densify_dir",
|
||||||
|
choices=("x", "y", "z"), default="x")
|
||||||
|
parser.add_argument('--densify-loops', dest="densify_loops", type=int,
|
||||||
|
default=1)
|
||||||
|
parser.add_argument('--densify-minimum-step', dest="densify_minimum_step",
|
||||||
|
type=d, default=d(0))
|
||||||
|
parser.add_argument('action', \
|
||||||
|
choices=("shift", "crop", "rotate", "densify"))
|
||||||
infile = sys.stdin
|
infile = sys.stdin
|
||||||
outfile = sys.stdout
|
outfile = sys.stdout
|
||||||
options = parser.parse_args()
|
options = parser.parse_args()
|
||||||
|
low = (options.minx, options.miny, options.minz)
|
||||||
|
high = (options.maxx, options.maxy, options.maxz)
|
||||||
if options.action == "shift":
|
if options.action == "shift":
|
||||||
shift = (options.shiftx, options.shifty, options.shiftz)
|
shift = (options.shiftx, options.shifty, options.shiftz)
|
||||||
func = lambda pos, is_inside: shift_position(pos, shift, is_inside)
|
gcode_filter = ShiftFilter(low, high, shift=shift)
|
||||||
elif options.action == "crop":
|
elif options.action == "crop":
|
||||||
crop_filter = CropFilter()
|
gcode_filter = CropFilter(low, high)
|
||||||
func = crop_filter.crop_bounds
|
|
||||||
elif options.action == "rotate":
|
elif options.action == "rotate":
|
||||||
rotate = (options.rotatex, options.rotatey, options.rotatez)
|
rotate = (options.rotatex, options.rotatey, options.rotatez)
|
||||||
func = lambda pos, is_inside: rotate_position(pos, shift, is_inside)
|
gcode_filter = RotateFilter(low, high, rotate=rotate)
|
||||||
|
elif options.action == "densify":
|
||||||
|
gcode_filter = DensifyFilter(low, high, densify_dir=options.densify_dir,
|
||||||
|
densify_loops=options.densify_loops,
|
||||||
|
densify_minimum_step=options.densify_minimum_step)
|
||||||
else:
|
else:
|
||||||
print >>sys.stderr, "No valid action choosen"
|
print >>sys.stderr, "No valid action choosen"
|
||||||
sys.exit(1)
|
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():
|
for line in infile.readlines():
|
||||||
for out_line in gcode_filter.get_processed_lines(line):
|
for out_line in gcode_filter.get_processed_lines(line):
|
||||||
outfile.write(out_line)
|
outfile.write(out_line)
|
||||||
|
|
Loading…
Reference in a new issue