def draw_polygon_arc(self, center_point, radius, edge_count, start_angle, end_angle, direction): """Draw a polygon.""" if isinstance(edge_count, tuple): edge_count, arc_angle = edge_count else: arc_angle = end_angle - start_angle print 'draw_polygon_arc with {} edges, centered on {} with radius of {}, between {} and {}'.format( edge_count, center_point, radius, start_angle, end_angle) edge_count = int(math.ceil(edge_count)) if edge_count < 3: raise ValueError('draw_polygon needs a minimum of 3 edges.') Display.debug_circle(center_point, radius) Display.debug_point(center_point, 'green') delta_step = Unit(arc_angle / float(edge_count)) if direction == CW: delta_step = -abs(delta_step) elif direction == CCW: delta_step = abs(delta_step) polar = Polar(radius, start_angle) turtle.up() self.setpos(center_point + polar.cartesian()) Display.debug_point(center_point + polar.cartesian(), 'pink') turtle.down() # direction = sign(delta_step) for i in range(int(edge_count)): polar.t += delta_step self.setpos(center_point + polar.cartesian())
def step(self, steps): """Step the motors in sync.""" biggest = int(max(abs(steps.r), abs(steps.t))) step_size = Steps(float(steps.r) / biggest, float(steps.t) / biggest) self.r_motor.set_direction(sign(steps.r)) self.t_motor.set_direction(sign(steps.t)) accumulation = Steps(0.0, 0.0) for i in range(biggest): accumulation += step_size while abs(accumulation.r) >= 1.0 or abs(accumulation.t) >= 1.0: this_step = Polar(0.0, 0.0) if abs(accumulation.r) >= 1.0: r_step = self.r_motor.step() this_step.r = r_step if r_step.is_boundry: accumulation.r = 0.0 else: accumulation.r = accumulation.r - sign(steps.r) if abs(accumulation.t) >= 1.0: this_step.t = self.t_motor.step() accumulation.t = accumulation.t - sign(steps.t) yield this_step
def __init__(self, controller): """Init.""" self.display = Display() self.c = controller self.polar = Polar(0.0, 0.0) self.polar_error = Polar(0.0, 0.0) self.cart = Cartesian(0.0, 0.0) self.cart_error = Cartesian(0.0, 0.0) self.grid_size = self.c.grid_size self.direction = CCW
def __init__(self, radius_start, radius_end, turns, start_angle=None): super(Spiral, self).__init__(Polar(radius_start, start_angle)) self.radius_start = radius_start self.radius_end = radius_end self.turns = turns radius_range = radius_end - radius_start self.radius_step = float(radius_range) / turns self.whole_step = Polar(self.radius_step, math.tau) if start_angle is None: start_angle = Radians(0.0)
def move(self, polar): """Move relative to our current position.""" # print 'PolarPlot.move({})'.format(polar) delta = polar if delta == Polar(0, 0): return steps_number = (delta / self.grid_size).max_abs_value() delta_step = delta / float(steps_number) current = self.polar_error for i in range(int(steps_number)): current += delta_step whole = Steps(current // self.grid_size) current = current % self.grid_size if whole.r == 0 and whole.t == 0: continue for movement in self.c.step(whole): if movement.r.is_boundry: delta_step.r = 0.0 self.polar = self.polar + movement self.display.setpos(self.polar) self.polar_error = current
def step(self, steps): """Step the motors in sync.""" biggest = int(max(abs(steps.r), abs(steps.t))) step_size = Steps(float(steps.r) / biggest, float(steps.t) / biggest) accumulation = Steps(0.0, 0.0) for i in range(biggest): accumulation += step_size while abs(accumulation.r) >= 1.0 or abs(accumulation.t) >= 1.0: this_step = Polar(0.0, 0.0) if abs(accumulation.r) >= 1.0: r_step = self.r_motor.effective_step_distance * (self.r_motor.step_size * sign(steps.r)) this_step.r = r_step accumulation.r = accumulation.r - sign(steps.r) if abs(accumulation.t) >= 1.0: this_step.t = self.t_motor.effective_step_distance * (self.t_motor.step_size * sign(steps.t)) accumulation.t = accumulation.t - sign(steps.t) yield this_step
def spiral(self, start_r, end_r, pitch_count): """A constant spiral.""" turtle.up() start = Polar(start_r, self.polar_error.t) self.setpos(start) turtle.down() for step in Spiral(start_r, end_r, pitch_count, self.polar_error.t).functor(): self.setpos(step) turtle.up()
def functor(self): stem_b = 50.0 target_theta = Radians(math.tau * 1.5) theta_step = math.tau / 90.0 # self.grid_size.t while abs(self.internal_position.t) <= abs(target_theta): theta = self.internal_position.t + theta_step stem = self.stem_func(stem_b, theta) self.internal_position = Polar(stem, theta) yield self.internal_position stem_b = 30.0 target_theta = Radians(0.0) self.internal_position.t -= math.tau while abs(self.internal_position.t) >= abs(target_theta): theta = self.internal_position.t - theta_step stem = self.stem_func(stem_b, theta) self.internal_position = Polar(stem, theta) yield self.internal_position
def flower(self, petals, amplitude): """Flower.""" n = float(petals) / 2.0 if petals % 2 == 0: d = 1 else: d = 0.5 turtle.up() self.setpos(Polar(0.0, 0.0)) turtle.down() self.rhodonea(n, d, amplitude)
def koru(self): """A constant spiral.""" turtle.up() start = Polar(0.0, 0.0) self.setpos(start) print 'koru' koru = Transform(Koru(), Cartesian(0.0, 150.0)) turtle.down() for step in koru: self.setpos(step) turtle.up()
def rhodonea(self, n, d, amplitude): """Drawing for the full set of rhodonea curves.""" # amplitude *= 100 # target_step_size = Millimeters(0.1) # circumference = Millimeters(math.tau * amplitude) # step_count = int(math.ceil(circumference / target_step_size)) # # step_size_mm = circumference / step_count # step_size_rad = Radiansmath.tau / step_count step_count = 100 k = float(n) / float(d) theta_range = Radians(math.tau) * d turtle.up() self.setpos(Polar(0.0, self.polar_error.t)) turtle.down() step_size_rad = theta_range / float(step_count) for step in range(step_count + 1): t = step * step_size_rad r = amplitude * math.sin(k * t) self.setpos(Polar(r, t)) turtle.up()
def functor(self): func = Spiral.archimedean a, b, target_theta = self.to_archimedean_parameters( self.radius_start, self.radius_end, self.turns) func = Spiral.logarithmic a, b, target_theta = self.to_logarithmic_parameters( self.radius_start, self.radius_end, self.turns) while abs(self.internal_position.t) < abs(target_theta): delta = Polar(float(func(a, b, self.grid_size.t)), self.grid_size.t) for rel_step in self.step_relative(delta, self.grid_size): self.internal_position += rel_step yield self.internal_position
def setpos(self, polar): """Set to an absolute position.""" # print 'PolarPlot.setpos({})'.format(polar) # Display.debug_line(self.polar.cartesian(), polar.cartesian(), 'yellow') r = polar.r - self.polar.r def shortest_angle(a, b): delta = (b - a) % math.tau if delta < 0: if abs(delta + math.tau) < abs(delta): delta = delta + math.tau else: if abs(delta - math.tau) < abs(delta): delta = delta - math.tau return delta delta_t = shortest_angle(self.polar.t, polar.t) self.move(Polar(r, delta_t))
def __init__(self, radius, speed=1.0): """Init. radius of the cog speed multiplies the turning rate relative to the parent. """ self.radius = Millimeters(radius) self.offset = None self.children = list() self.track = Polar(0.0, 0.0) self.theta = Radians(0.0) self.speed_boost = speed self.base_speed = 1.0 self.center = Cartesian(0.0, 0.0) self.direction = 1.0 self.parent_radius = None self.id = uuid.uuid4()
class Display(object): """Abstraction that converts our polar motor signals to pixels on the screen.""" c = DisplayPosition(0, 0) p = Polar(0, 0) c_pixels = DisplayPosition(0, 0) c_pixels_whole = DisplayPosition(0, 0) offset = Cartesian(0, 0) hud = None canvas = None debug_enabled = True debug_pen = None class HUD(threading.Thread): """HUD manager.""" def __init__(self): """Setup the HUD elements.""" super(Display.HUD, self).__init__() self.canvas = turtle.screen.getcanvas() self.hud_lines = dict() self.queues = dict() self.next_pos = TkSpace(-102.0, -98.0) self.stop = False self.daemon = True def __setitem__(self, queue_name, value): """Set text on HUD item. Use a queue to make the value avaiable when we refresh the HUD. Passing new keys into here will create new HUD lines. """ if queue_name not in self.queues: self.queues[queue_name] = LifoQueue() if queue_name not in self.hud_lines: self.hud_lines[queue_name] = self.canvas.create_text(*self.next_pos, text='', fill='grey', anchor=tk.SW) self.next_pos.y += 5.0 try: while True: self.queues[queue_name].get_nowait() except Empty: pass self.queues[queue_name].put_nowait(value) def run(self): """Update the HUD text.""" while not self.stop: for queue_name, q in self.queues.iteritems(): try: self.canvas.itemconfig(self.hud_lines[queue_name], text=q.get_nowait()) except Empty: pass time.sleep(0.5) def __init__(self): """Set the HUD elements.""" if Display.hud is None: Display.hud = Display.HUD() Display.hud.start() if Display.canvas is None: Display.canvas = turtle.screen.getcanvas() Display.debug_pen = Pen() self.pen = Pen() def setpos(self, polar): """Set the position of of the polar coordinates to pixels on the screen.""" self.p = polar self.c = self.p.cartesian() new_pixel_position = self.c.pixels().trunc(factor=10) if self.c_pixels != new_pixel_position: # a = new_pixel_position * 0.86666666666666666666666 # self.pen.down() # self.pen.setpos(self.c) # self.pen.up() # TODO: What is going on here?!? # Some how my converion from mm to pixels that works everywhere else doesn't here. turtle.setpos(new_pixel_position * 0.86666666666666666 * 0.86666666666666666666666) if not FAST: turtle.setheading(polar.t.degrees()) self.c_pixels = new_pixel_position # Post values to the HUD self.hud['hud_polar'] = self.p self.hud['hud_cartesian'] = self.c @classmethod def set_offset(cls, offset): """Apply an offset to align with the real drawings.""" cls.offset = offset @classmethod def debug_point(cls, point, color): """Draw a colored dot.""" if cls.debug_enabled: pen = Display.debug_pen pen.color = color if cls.offset: point = point + cls.offset pen.setpos(point) pen.down() pen.dot(5, color) pen.up() @classmethod def debug_line(cls, start_point, end_point, color='grey'): """Draw a line.""" if cls.debug_enabled: pen = Display.debug_pen if cls.offset: start_point = start_point + cls.offset end_point = end_point + cls.offset pen.down() pen.line(start_point, end_point) pen.up() @classmethod def debug_circle(cls, center_point, radius, color='grey'): """Draw a line.""" if cls.debug_enabled: pen = Display.debug_pen if cls.offset: center_point = center_point + cls.offset pen.down() pen.circle(center_point, radius, color) pen.up() @classmethod def debug_text(cls, point, text): """Write some text.""" pass # if cls.debug_enabled: # with DebugPen('grey', 1) as pen: # pen.setpos(point.pixels() * 0.86666666666666666 * 0.86666666666666666) # pen.write(text) @classmethod def debug_clear(cls): """Clear all debug drawing.""" if cls.debug_enabled: debug_pen.clear()
def set_track(self, radius, angle): """The pen's track can be anywhere.""" self.track = Polar(self.offset, angle)
def motor_step_distance(self): """Return the step size of the motors.""" return Polar(self.r_motor.step_distance, self.t_motor.step_distance)
def __init__(self): super(Koru, self).__init__(Polar(0.0, 0.0))
def __iter__(self): """Iterable steps.""" delta = Polar(self.radius_end - self.radius_start, math.tau * self.turns) for blah in self.step_absolute(delta, self.grid_size): yield blah
def set_track(self, radius, angle): """Save the track.""" self.track = Polar(radius, angle)