From 9447c747b5a079778b6b9e102115f8daeefc817d Mon Sep 17 00:00:00 2001 From: lars Date: Fri, 16 Nov 2012 03:05:34 +0000 Subject: [PATCH] moved to class-based filters added "DensityFilter" (additional parallel lines) --- gcode-tools/gcode-shifter/gcode_shift.py | 187 ++++++++++++++++------- 1 file changed, 131 insertions(+), 56 deletions(-) diff --git a/gcode-tools/gcode-shifter/gcode_shift.py b/gcode-tools/gcode-shifter/gcode_shift.py index c5c0982..d6ed31c 100755 --- a/gcode-tools/gcode-shifter/gcode_shift.py +++ b/gcode-tools/gcode-shifter/gcode_shift.py @@ -17,7 +17,8 @@ class GCodeFilter(object): self.lower = lower self.upper = upper self.recalc_func = func - self.destination_position = PositionHandler() + self.source_pos = PositionHandler() + self.target_pos = PositionHandler() def is_inside(self, position): for i in range(len(position)): @@ -37,6 +38,7 @@ class GCodeFilter(object): new_pos[i] = d(token[1:]) new_pos = tuple(new_pos) if self.pos != new_pos: + self.source_pos.update(new_pos) self.pos = new_pos return True else: @@ -48,11 +50,10 @@ class GCodeFilter(object): 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) + for pos in self.transform_position(): + changed_axes = self.target_pos.get_changed_axes(pos) yield handler.get_line(pos, changed_axes) - self.destination_position.update(pos) + self.target_pos.update(pos) else: # no coordinate given yield line @@ -62,10 +63,17 @@ class PositionHandler(object): def __init__(self): 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): new_pos = tuple(position) if self.pos != new_pos: + self.push(new_pos) self.pos = new_pos return True else: @@ -80,31 +88,20 @@ class PositionHandler(object): 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 + self._axes = [] 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]) + return token[0] + str(self._position[i]) else: return token @@ -112,6 +109,7 @@ class LineHandler(object): result = [] self._processed = [] self._position = position + self._axes = axes line = re.sub(AXIS_REGEX, self.replace_value, self.line, re.I) if not self._processed: # no axis definition @@ -120,61 +118,128 @@ class LineHandler(object): # 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])) + line += " %s%s" % (axis, str(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 ShiftFilter(GCodeFilter): + + 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: + yield self.pos -class CropFilter(object): +class DensifyFilter(GCodeFilter): - def __init__(self): - self._was_inside = True - self._previous_position = None - self._recurse_counter = 0 - self._max_recurse = 20 + def __init__(self, *args, **kwargs): + self.loops = kwargs.pop("densify_loops") + self.minimum_step = kwargs.pop("densify_minimum_step") + self.direction = kwargs.pop("densify_dir").upper() + self.direction_index = AXIS.index(self.direction) + super(DensifyFilter, self).__init__(*args, **kwargs) - def crop_bounds(self, pos, inside_func): - is_inside = inside_func(pos) + def _densify_is_valid(self, p1, p2): + 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 - if is_inside and self._was_inside: + if is_inside and was_inside: # inside -> inside - result = (pos, ) - elif not is_inside and not self._was_inside: + yield self.pos + elif not is_inside and not was_inside: # outside -> outside - result = [] + pass else: # outside -> inside OR inside -> outside border_pos = self._recurse_border_position(self._previous_position, - pos, inside_func) - self._was_inside = is_inside + pos, self.is_inside) + # the border position is always the first step + yield border_pos + # omit the current position if we are outside if is_inside: - result = (border_pos, pos) - else: - result = (pos, ) - self._previous_position = pos - return result + yield self.pos - 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 """ 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 + if depth_limit < 0: return p_middle 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 else: 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__": @@ -191,24 +256,34 @@ if __name__ == "__main__": 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")) + 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 outfile = sys.stdout options = parser.parse_args() + low = (options.minx, options.miny, options.minz) + high = (options.maxx, options.maxy, options.maxz) if options.action == "shift": 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": - crop_filter = CropFilter() - func = crop_filter.crop_bounds + gcode_filter = CropFilter(low, high) elif options.action == "rotate": 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: 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(): for out_line in gcode_filter.get_processed_lines(line): outfile.write(out_line)