From 0d9c258db2220979d9ec818cf4a32fc20d4dbd44 Mon Sep 17 00:00:00 2001 From: Marius-450 Date: Thu, 5 Mar 2020 15:34:51 -0500 Subject: [PATCH 1/6] multiple improvements : memory, functionnality, consistency with documentation --- adafruit_turtle.py | 1296 +++++++++++++---------------- examples/turtle_hilbert.py | 2 +- examples/turtle_overlayed_koch.py | 2 + examples/turtle_simpletest.py | 1 + examples/turtle_star.py | 1 + 5 files changed, 605 insertions(+), 697 deletions(-) diff --git a/adafruit_turtle.py b/adafruit_turtle.py index 0f1a8a5..4bcb5b6 100644 --- a/adafruit_turtle.py +++ b/adafruit_turtle.py @@ -28,10 +28,7 @@ `adafruit_turtle` ================================================================================ -Turtle graphics library for CircuitPython and displayio - - -* Author(s): LadyAda and Dave Astels +* Originals Author(s): LadyAda and Dave Astels Implementation Notes -------------------- @@ -43,7 +40,8 @@ * Adafruit CircuitPython firmware for the supported boards: https://github.com/adafruit/circuitpython/releases -* Adafruit's Bus Device library: https://github.com/adafruit/Adafruit_CircuitPython_BusDevice +* Adafruit's Bus Device library: + https://github.com/adafruit/Adafruit_CircuitPython_BusDevice """ #pylint:disable=too-many-public-methods, too-many-instance-attributes, invalid-name @@ -54,7 +52,6 @@ import time import board import displayio -import adafruit_logging as logging __version__ = "0.0.0-auto.0" __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_turtle.git" @@ -65,13 +62,21 @@ class Color(object): BLACK = 0x000000 RED = 0xFF0000 ORANGE = 0xFFA500 - YELLOW = 0xFFFF00 - GREEN = 0x00FF00 + YELLOW = 0xFFEE00 + GREEN = 0x00C000 BLUE = 0x0000FF - PURPLE = 0x800080 - PINK = 0xFFC0CB - - colors = (WHITE, BLACK, RED, ORANGE, YELLOW, GREEN, BLUE, PURPLE, PINK) + PURPLE = 0x8040C0 + PINK = 0xFF40C0 + LIGHT_GRAY = 0xAAAAAA + GRAY = 0x444444 + BROWN = 0xCA801D + DARK_GREEN = 0x008700 + TURQUOISE = 0x00C0C0 + DARK_BLUE = 0x0000AA + DARK_RED = 0x800000 + + colors = (BLACK, WHITE, RED, YELLOW, GREEN, ORANGE, BLUE, PURPLE, PINK, GRAY, + LIGHT_GRAY, BROWN, DARK_GREEN, TURQUOISE, DARK_BLUE, DARK_RED) def __init__(self): pass @@ -135,7 +140,8 @@ def __repr__(self): class turtle(object): """A Turtle that can be given commands to draw.""" - def __init__(self, display=None): + def __init__(self, display=None, scale=1): + if display: self._display = display else: @@ -143,43 +149,66 @@ def __init__(self, display=None): self._display = board.DISPLAY except AttributeError: raise RuntimeError("No display available. One must be provided.") - self._logger = logging.getLogger("Turtle") - self._logger.setLevel(logging.INFO) + self._w = self._display.width self._h = self._display.height - self._x = self._w // 2 - self._y = self._h // 2 + self._x = self._w // (2 * scale) + self._y = self._h // (2 * scale) self._speed = 6 - self._heading = 90 - self._logomode = False + self._heading = 0 + self._logomode = True self._fullcircle = 360.0 self._degreesPerAU = 1.0 - self._mode = "standard" + self._angleOrient = 1 self._angleOffset = 0 - - self._splash = displayio.Group(max_size=3) - - self._bg_bitmap = displayio.Bitmap(self._w, self._h, 1) + self._bg_color = 0 + + self._splash = displayio.Group(max_size=5) + self._bgscale = 1 + if self._w == self._h: + i = 1 + while self._bgscale == 1: + if self._w/i < 128: + self._bg_bitmap = displayio.Bitmap(i, i, 1) + self._bgscale = self._w//i + i += 1 + else: + self._bgscale = self._GCD(self._w, self._h) + self._bg_bitmap = displayio.Bitmap(self._w//self._bgscale, self._h//self._bgscale , 1) self._bg_palette = displayio.Palette(1) - self._bg_palette[0] = Color.BLACK + self._bg_palette[0] = Color.colors[self._bg_color] self._bg_sprite = displayio.TileGrid(self._bg_bitmap, pixel_shader=self._bg_palette, x=0, y=0) - self._splash.append(self._bg_sprite) - - self._fg_bitmap = displayio.Bitmap(self._w, self._h, 5) - self._fg_palette = displayio.Palette(len(Color.colors) + 1) - self._fg_palette.make_transparent(0) + self._bg_group = displayio.Group(scale=self._bgscale, max_size=1) + self._bg_group.append(self._bg_sprite) + self._splash.append(self._bg_group) + # group to add background pictures (and/or user-defined stuff) + self._bg_addon_group = displayio.Group(max_size=6) + self._splash.append(self._bg_addon_group) + self._fg_scale = scale + self._w = self._w // self._fg_scale + self._h = self._h // self._fg_scale + self._fg_bitmap = displayio.Bitmap(self._w, self._h, len(Color.colors)) + + self._fg_palette = displayio.Palette(len(Color.colors)) + self._fg_palette.make_transparent(self._bg_color) for i, c in enumerate(Color.colors): - self._fg_palette[i + 1] = c + self._fg_palette[i] = c self._fg_sprite = displayio.TileGrid(self._fg_bitmap, pixel_shader=self._fg_palette, x=0, y=0) - self._splash.append(self._fg_sprite) + self._fg_group = displayio.Group(scale=self._fg_scale, max_size=1) + self._fg_group.append(self._fg_sprite) + self._splash.append(self._fg_group) + # group to add text and/or user defined stuff + self._fg_addon_group = displayio.Group(max_size=6) + self._splash.append(self._fg_addon_group) self._turtle_bitmap = displayio.Bitmap(9, 9, 2) self._turtle_palette = displayio.Palette(2) self._turtle_palette.make_transparent(0) + self._turtle_palette[1] = Color.WHITE for i in range(4): self._turtle_bitmap[4 - i, i] = 1 @@ -189,23 +218,37 @@ def __init__(self, display=None): self._turtle_sprite = displayio.TileGrid(self._turtle_bitmap, pixel_shader=self._turtle_palette, x=-100, y=-100) - self._drawturtle() - self._splash.append(self._turtle_sprite) + self._turtle_group = displayio.Group(scale=self._fg_scale, max_size=2) + self._turtle_group.append(self._turtle_sprite) + self._splash.append(self._turtle_group) self._penstate = False - self._pencolor = None self._pensize = 1 self.pencolor(Color.WHITE) - - self._display.show(self._splash) + self._bg_pic = None + self._turtle_pic = None + self._turtle_odb = None + self._turtle_alt_sprite = None + self._drawturtle() + self._stamps = {} + self._turtle_odb_use = 0 + self._turtle_odb_file = None gc.collect() + self._display.show(self._splash) def _drawturtle(self): - self._turtle_sprite.x = int(self._x - 4) - self._turtle_sprite.y = int(self._y - 4) - #self._logger.debug("pos (%d, %d)", self._x, self._y) + if self._turtle_pic is None: + self._turtle_sprite.x = int(self._x - 4) + self._turtle_sprite.y = int(self._y - 4) + else: + if self._turtle_odb is not None: + self._turtle_alt_sprite.x = int(self._x - self._turtle_odb.width//2) + self._turtle_alt_sprite.y = int(self._y - self._turtle_odb.height//2) + else: + self._turtle_alt_sprite.x = int(self._x - self._turtle_pic[0]//2) + self._turtle_alt_sprite.y = int(self._y - self._turtle_pic[1]//2) - ############################################################################ + ########################################################################### # Move and draw def forward(self, distance): @@ -214,8 +257,10 @@ def forward(self, distance): :param distance: how far to move (integer or float) """ p = self.pos() - x1 = p[0] + math.sin(math.radians(self._heading)) * distance - y1 = p[1] + math.cos(math.radians(self._heading)) * distance + # works only for degrees. + # TODO implement for radians + x1 = p[0] + math.sin(math.radians((self._angleOffset + self._angleOrient*self._heading) % self._fullcircle)) * distance + y1 = p[1] + math.cos(math.radians((self._angleOffset + self._angleOrient*self._heading) % self._fullcircle)) * distance self.goto(x1, y1) fd = forward @@ -237,7 +282,10 @@ def right(self, angle): :param angle: how much to rotate to the right (integer or float) """ - self._turn(angle) + if self._logomode: + self._turn(angle) + else: + self._turn(-angle) rt = right def left(self, angle): @@ -247,7 +295,10 @@ def left(self, angle): :param angle: how much to rotate to the left (integer or float) """ - self._turn(-angle) + if self._logomode: + self._turn(-angle) + else: + self._turn(angle) lt = left #pylint:disable=too-many-branches,too-many-statements @@ -267,7 +318,6 @@ def goto(self, x1, y1=None): y1 = self._h // 2 - y1 x0 = self._x y0 = self._y - self._logger.debug("* GoTo from (%d, %d) to (%d, %d)", x0, y0, x1, y1) if not self.isdown(): self._x = x1 # woot, we just skip ahead self._y = y1 @@ -291,6 +341,11 @@ def goto(self, x1, y1=None): ystep = -1 if y0 < y1: ystep = 1 + step = 1 + if self._speed > 0: + ts = (((11-self._speed)*0.00020)*(self._speed+0.5)) + else: + ts = 0 while (not rev and x0 <= x1) or (rev and x1 <= x0): if steep: @@ -300,7 +355,6 @@ def goto(self, x1, y1=None): pass self._x = y0 self._y = x0 - self._drawturtle() else: try: self._plot(int(x0), int(y0), self._pencolor) @@ -308,7 +362,14 @@ def goto(self, x1, y1=None): pass self._x = x0 self._y = y0 - self._drawturtle() + if self._speed > 0: + if step >= self._speed : + # mark the step + step = 1 + self._drawturtle() + time.sleep(ts) + else: + step += 1 err -= dy if err < 0: y0 += ystep @@ -317,6 +378,7 @@ def goto(self, x1, y1=None): x0 -= 1 else: x0 += 1 + self._drawturtle() setpos = goto setposition = goto @@ -351,23 +413,91 @@ def setheading(self, to_angle): :param to_angle: the new turtle heading """ - - self._heading = to_angle + self._turn(to_angle - self._heading) seth = setheading def home(self): - """Move turtle to the origin - coordinates (0,0) - and set its heading to - its start-orientation + """Move turtle to the origin - coordinates (0,0) - and set its heading + to its start-orientation (which depends on the mode, see mode()). """ self.setheading(90) self.goto(0, 0) def _plot(self, x, y, c): - try: - self._fg_bitmap[int(x), int(y)] = c - except IndexError: - pass + if self._pensize == 1: + try: + self._fg_bitmap[int(x), int(y)] = c + return + except IndexError: + pass + r = self._pensize // 2 + 1 + sin = math.sin(math.radians((self._angleOffset + self._angleOrient*self._heading - 90) % self._fullcircle)) + cos = math.cos(math.radians((self._angleOffset + self._angleOrient*self._heading - 90) % self._fullcircle)) + x0 = x + sin * r + x1 = x - sin * (self._pensize - r) + y0 = y - cos * r + y1 = y + cos * (self._pensize - r) + + coords = [x0, x1, y0, y1] + for i, v in enumerate(coords): + if v >= 0: + coords[i] = math.ceil(v) + else: + coords[i] = math.floor(v) + x0, x1, y0, y1 = coords + + steep = abs(y1 - y0) > abs(x1 - x0) + rev = False + dx = x1 - x0 + if steep: + x0, y0 = y0, x0 + x1, y1 = y1, x1 + dx = x1 - x0 + + if x0 > x1: + rev = True + dx = x0 - x1 + + dy = abs(y1 - y0) + err = dx / 2 + ystep = -1 + if y0 < y1: + ystep = 1 + + while (not rev and x0 <= x1) or (rev and x1 <= x0): + # first row + if steep: + try: + self._fg_bitmap[int(y0), int(x0)] = c + except IndexError: + pass + else: + try: + self._fg_bitmap[int(x0), int(y0)] = c + except IndexError: + pass + if y0 != y1 and self._heading % 90 != 0: + # need a second row to fill the cracks + j = -1 if y1 < y0 else 1 + if steep: + try: + self._fg_bitmap[int(y0+j), int(x0)] = c + except IndexError: + pass + else: + try: + self._fg_bitmap[int(x0), int(y0+j)] = c + except IndexError: + pass + err -= dy + if err < 0: + y0 += ystep + err += dx + if rev: + x0 -= 1 + else: + x0 += 1 def circle(self, radius, extent=None, steps=None): """Draw a circle with given radius. The center is radius units left of @@ -390,72 +520,58 @@ def circle(self, radius, extent=None, steps=None): # --or: circle(radius, extent) # arc # --or: circle(radius, extent, steps) # --or: circle(radius, steps=6) # 6-sided polygon - + pos = self.pos() + h = self._heading if extent is None: extent = self._fullcircle if steps is None: frac = abs(extent)/self._fullcircle - steps = 1+int(min(11+abs(radius)/6.0, 59.0)*frac) - w = 1.0 * extent / steps + steps = int(min(3+abs(radius)/4.0, 12.0)*frac)*4 + w = extent / steps w2 = 0.5 * w - l = 2.0 * radius * math.sin(w2*math.pi/180.0*self._degreesPerAU) + l = radius * math.sin(w*math.pi/180.0*self._degreesPerAU) if radius < 0: l, w, w2 = -l, -w, -w2 self.left(w2) - for _ in range(steps): + for _ in range(steps-1): self.forward(l) self.left(w) - self.right(w2) - - def _draw_disk(self, x, y, width, height, r, color, fill=True, outline=True, stroke=1): - """Draw a filled and/or outlined circle""" - if fill: - self._helper(x+r, y+r, r, color=color, fill=True, - x_offset=width-2*r-1, y_offset=height-2*r-1) - if outline: - self._helper(x+r, y+r, r, color=color, stroke=stroke, - x_offset=width-2*r-1, y_offset=height-2*r-1) - - # pylint: disable=too-many-locals, too-many-branches - def _helper(self, x0, y0, r, color, x_offset=0, y_offset=0, - stroke=1, fill=False): - """Draw quandrant wedges filled or outlined""" - f = 1 - r - ddF_x = 1 - ddF_y = -2 * r - x = -1 - y = r - - while x < y: - if f >= 0: - y -= 1 - ddF_y += 2 - f += ddF_y - x += 1 - ddF_x += 2 - f += ddF_x - if fill: - for w in range(x0-y, x0+y+x_offset): - self._plot(w, y0 + x + y_offset, color) - self._plot(w, y0 - x, color) - for w in range(x0-x, x0+x+x_offset): - self._plot(w, y0 + y + y_offset, color) - self._plot(w, y0 - y, color) - else: - for line in range(stroke): - self._plot(x0 - y + line, y0 + x + y_offset, color) - self._plot(x0 - x, y0 + y + y_offset - line, color) - self._plot(x0 - y + line, y0 - x, color) - self._plot(x0 - x, y0 - y + line, color) - for line in range(stroke): - self._plot(x0 + x + x_offset, y0 + y + y_offset - line, color) - self._plot(x0 + y + x_offset - line, y0 + x + y_offset, color) - self._plot(x0 + x + x_offset, y0 - y + line, color) - self._plot(x0 + y + x_offset - line, y0 - x, color) - - # pylint: enable=too-many-locals, too-many-branches - -#pylint:disable=keyword-arg-before-vararg + # rounding error correction on the last step + self.setheading(self.towards(pos)) + # get back to exact same position and heading + self.goto(pos) + self.setheading(h) + + def speed(self, speed=None): + """ + + Set the turtle's speed to an integer value in the range 0..10. If no + argument is given, return current speed. + + If input is a number greater than 10 or smaller than 1, speed is set + to 0. Speedstrings are mapped to speedvalues as follows: + + "fastest": 0 + "fast": 10 + "normal": 6 + "slow": 3 + "slowest": 1 + Speeds from 1 to 10 enforce increasingly faster animation of line + drawing and turtle turning. + + Attention: speed = 0 means that no animation takes place. + forward/back makes turtle jump and likewise left/right make the + turtle turn instantly. + + :param speed: the new turtle speed (0..10) or None + """ + if speed is None: + return self._speed + elif speed > 10 or speed < 1: + self._speed = 0 + else: + self._speed = speed + def dot(self, size=None, color=None): """Draw a circular dot with diameter size, using color. If size is not given, the maximum of pensize+4 and @@ -471,31 +587,88 @@ def dot(self, size=None, color=None): color = self._pencolor else: color = self._color_to_pencolor(color) - self._logger.debug('dot(%d)', size) - self._draw_disk(self._x - size, self._y - size, 2 * size + 1, 2 * size + 1, size, color) - self._fg_sprite[0, 0] = 0 - - def stamp(self): - """Not implemented + pensize = self._pensize + pencolor = self._pencolor + down = self.isdown() + if size > 1: + self._pensize = size + self._pencolor = color + self.pendown() + self.right(180) + self.right(180) + if not down: + self.penup() + self._pensize = pensize + self._pencolor = pencolor + else: + self._pensize = 1 + self._plot(self._x, self._y, color) + self._pensize = pensize + def stamp(self, bitmap=None, palette=None): + """ Stamp a copy of the turtle shape onto the canvas at the current turtle position. Return a stamp_id for that stamp, which can be used to delete it by calling clearstamp(stamp_id). """ - raise NotImplementedError + if len(self._fg_addon_group) >= 6: + print("Addon group full") + return + s_id = len(self._stamps) + if self._turtle_pic is None: + # easy. + new_stamp = displayio.TileGrid(self._turtle_bitmap, + pixel_shader=self._turtle_palette, + x=int(self._x-self._turtle_bitmap.width//2), + y=int(self._y - self._turtle_bitmap.height // 2)) + elif self._turtle_odb is not None: + # odb bitmap + new_stamp = displayio.TileGrid(self._turtle_odb, + pixel_shader=displayio.ColorConverter(), + x=int(self._x-self._turtle_odb.width//2), + y=int(self._y - self._turtle_odb.height // 2)) + self._turtle_odb_use += 1 + else: + if bitmap == None: + raise RuntimeError("a bitmap must be provided") + if palette == None: + raise RuntimeError("a palette must be provided") + new_stamp = displayio.TileGrid(bitmap, pixel_shader=palette, + x=int(self._x-bitmap.width//2), + y=int(self._y - bitmap.height // 2)) + self._fg_addon_group.append(new_stamp) + if self._turtle_odb is not None: + self._stamps[s_id] = (new_stamp, self._turtle_odb_file) + else: + self._stamps[s_id] = new_stamp + + return s_id def clearstamp(self, stampid): - """Not implemented + """ Delete stamp with given stampid. :param stampid: the id of the stamp to be deleted """ - raise NotImplementedError + if isinstance(stampid, int): + if stampid in self._stamps and self._stamps[stampid] is not None: + if isinstance(self._stamps[stampid], tuple): + self._fg_addon_group.remove(self._stamps[stampid][0]) + self._turtle_odb_use -= 1 + if self._turtle_odb_use == 0: + self._stamps[stampid][1].close() + else: + self._fg_addon_group.remove(self._stamps[stampid]) + self._stamps[stampid] = None + else: + return + else: + raise TypeError("Stamp id must be an int") def clearstamps(self, n=None): - """Not implemented + """ Delete all or first/last n of turtle's stamps. If n is None, delete all stamps, if n > 0 delete first n stamps, else if n < 0 delete last @@ -504,43 +677,15 @@ def clearstamps(self, n=None): :param n: how many stamps to delete (None means delete them all) """ - raise NotImplementedError - - def undo(self): - """Not implemented - - Undo (repeatedly) the last turtle action(s). Number of available undo - actions is determined by the size of the undobuffer. - """ - raise NotImplementedError - - def speed(self, speed=None): - """Not implemented - - Set the turtle's speed to an integer value in the range 0..10. If no - argument is given, return current speed. - - If input is a number greater than 10 or smaller than 0.5, speed is set - to 0. Speedstrings are mapped to speedvalues as follows: - - "fastest": 0 - "fast": 10 - "normal": 6 - "slow": 3 - "slowest": 1 - Speeds from 1 to 10 enforce increasingly faster animation of line - drawing and turtle turning. - - Attention: speed = 0 means that no animation takes place. - forward/back makes turtle jump and likewise left/right make the - turtle turn instantly. - - :param speed: the new turtle speed (0..10) or None - """ - raise NotImplementedError - - - ############################################################################ + i = 1 + for sid in self._stamps: + if self._stamps[sid] is not None: + self.clearstamp(sid) + if n is not None and i >= n: + return + i += 1 + + ########################################################################### # Tell turtle's state def pos(self): @@ -549,8 +694,7 @@ def pos(self): position = pos def towards(self, x1, y1=None): - """Not implemented - + """ Return the angle between the line from turtle position to position specified by (x,y) or the vector. This depends on the turtle's start orientation which depends on the mode - "standard" or "logo"). @@ -559,7 +703,21 @@ def towards(self, x1, y1=None): :param y: a number if x is a number, else None """ - raise NotImplementedError + if y1 is None: + y1 = x1[1] + x1 = x1[0] + x0, y0 = self.pos() + + result = math.degrees(math.atan2(x1-x0, y1-y0)) + result /= self._degreesPerAU + return (self._angleOffset + self._angleOrient*result) % self._fullcircle + if self._logomode : + print("logo mode") + return(math.degrees(math.atan2(x0-x1, y0-y1))) + else: + # not used yet + print("standard mode") + return(math.degrees(math.atan2(y0-y1, x0-x1))) def xcor(self): """Return the turtle's x coordinate.""" @@ -576,8 +734,7 @@ def heading(self): return self._heading def distance(self, x1, y1=None): - """Not implemented - + """ Return the distance from the turtle to (x,y) or the vector, in turtle step units. @@ -585,23 +742,27 @@ def distance(self, x1, y1=None): :param y: a number if x is a number, else None """ - raise NotImplementedError + if y1 is None: + y1 = x1[1] + x1 = x1[0] + x0, y0 = self.pos() + return(math.sqrt((x0-x1)**2+(y0-y1)**2)) - ############################################################################ + ########################################################################### # Setting and measurement def _setDegreesPerAU(self, fullcircle): """Helper function for degrees() and radians()""" self._fullcircle = fullcircle self._degreesPerAU = 360/fullcircle - if self._mode == "standard": + if self._mode == "logo": self._angleOffset = 0 else: - self._angleOffset = fullcircle/4. - + self._angleOffset = -fullcircle/4 def degrees(self, fullcircle=360): - """Set angle measurement units, i.e. set number of "degrees" for a full circle. + """Set angle measurement units, i.e. set number of "degrees" for + a full circle. Default value is 360 degrees. :param fullcircle: the number of degrees in a full circle @@ -609,11 +770,48 @@ def degrees(self, fullcircle=360): self._setDegreesPerAU(fullcircle) def radians(self): - """Set the angle measurement units to radians. Equivalent to degrees(2*math.pi).""" + """Set the angle measurement units to radians. + Equivalent to degrees(2*math.pi).""" self._setDegreesPerAU(2*math.pi) + def mode(self, mode=None): + """ + + Set turtle mode ("standard" or "logo") and perform reset. + If mode is not given, current mode is returned. + + Mode "standard" is compatible with old turtle. + Mode "logo" is compatible with most Logo turtle graphics. + + :param mode: one of the strings "standard" or "logo" + """ + if mode == "standard": + self._logomode = False + self._angleOrient = -1 + self._angleOffset = self._fullcircle/4 + elif mode == "logo": + self._logomode = True + self._angleOrient = 1 + self._angleOffset = 0 + elif mode is None: + if self._logomode: + return "logo" + return "standard" + else: + raise RuntimeError("Mode must be 'logo', 'standard', or None") + return None + + def window_height(self): + """ + Return the height of the turtle window.""" + return self._h + + def window_width(self): + """ + Return the width of the turtle window.""" + return self._w - ############################################################################ + ########################################################################### # Drawing state def pendown(self): @@ -628,12 +826,14 @@ def penup(self): pu = penup up = penup - def pensize(self, width=None): - """Not implemented + def isdown(self): + """Return True if pen is down, False if it's up.""" + return self._penstate - Set the line thickness to width or return it. If resizemode is set to - "auto" and turtleshape is a polygon, that polygon is drawn with the same - line thickness. If no argument is given, the current pensize is returned. + def pensize(self, width=None): + """ + Set the line thickness to width or return it. + If no argument is given, the current pensize is returned. :param width: - a positive number @@ -643,80 +843,18 @@ def pensize(self, width=None): return self._pensize width = pensize - def pen(self, pen=None, **pendict): - """Not implemented - - Not implemented - - Return or set the pen's attributes in a "pen-dictionary" with - the following key/value pairs: - - "shown": True/False - "pendown": True/False - "pencolor": color-string or color-tuple - "fillcolor": color-string or color-tuple - "pensize": positive number - "speed": number in range 0..10 - "resizemode": "auto" or "user" or "noresize" - "stretchfactor": (positive number, positive number) - "outline": positive number - "tilt": number - - This dictionary can be used as argument for a subsequent call to pen() - to restore the former pen-state. Moreover one or more of these - attributes can be provided as keyword-arguments. This can be used to - set several pen attributes in one statement. - - :param pen: a dictionary with some or all of the above listed keys - :param pendict: ne or more keyword-arguments with the above listed keys - as keywords - """ - - raise NotImplementedError - - def isdown(self): - """Return True if pen is down, False if it's up.""" - return self._penstate - - ############################################################################ + ########################################################################### # Color control #pylint:disable=no-self-use - def _color_to_pencolor(self, c): - return 1 + Color.colors.index(c) -#pylint:enable=no-self-use - - def color(self, *args): - """Not implemented - - Return or set pencolor and fillcolor. - - Several input formats are allowed. They use 0 to 3 arguments as follows: - - color() - Return the current pencolor and the current fillcolor as a pair of - color specification strings or tuples as returned by pencolor() and - fillcolor(). - color(colorstring), color((r, g, b)), color(r, g, b) - Inputs as in pencolor(), set both, fillcolor and pencolor, to the - given value. - - color(colorstring1, colorstring2), color((r1, g1, b1), (r2, g2, b2)) - Equivalent to pencolor(colorstring1) and fillcolor(colorstring2) - and analogously if the other input format is used. - - If turtleshape is a polygon, outline and interior of that polygon is - drawn with the newly set colors. - """ - raise NotImplementedError + def _color_to_pencolor(self, c): + return Color.colors.index(c) def pencolor(self, c=None): """ Return or set the pencolor. - Four input formats are allowed: - pencolor() Return the current pencolor as color specification string or as a tuple (see example). May be used as input to another color/ @@ -725,504 +863,270 @@ def pencolor(self, c=None): pencolor(colorvalue) Set pencolor to colorvalue, which is a 24-bit integer such as 0xFF0000. The Color class provides the available values: - WHITE, BLACK, RED, ORANGE, YELLOW, GREEN, BLUE, PURPLE, PINK + BLACK, WHITE, RED, YELLOW, ORANGE, GREEN, BLUE, PURPLE, PINK + GRAY, LIGHT_GRAY, BROWN, DARK_GREEN, TURQUOISE, DARK_BLUE, DARK_RED - If turtleshape is a polygon, the outline of that polygon is drawn with - the newly set pencolor. """ if c is None: - return Color.colors[self._pencolor - 1] - if not c in Color.colors: + return Color.colors[self._pencolor] + if c not in Color.colors: raise RuntimeError("Color must be one of the 'Color' class items") - self._pencolor = 1 + Color.colors.index(c) + self._pencolor = Color.colors.index(c) + self._turtle_palette[1] = c + if self._bg_color == self._pencolor: + self._turtle_palette.make_transparent(1) + else: + self._turtle_palette.make_opaque(1) return c - def fillcolor(self, c=None): - """Not implemented - - Return or set the fillcolor. - - Four input formats are allowed: - - fillcolor() - Return the current fillcolor as color specification string, possibly - in tuple format (see example). May be used as input to another - color/pencolor/fillcolor call. - - fillcolor(colorstring) - Set fillcolor to colorstring, which is a Tk color specification - string, such as "red", "yellow", or "#33cc8c". - - fillcolor((r, g, b)) - Set fillcolor to the RGB color represented by the tuple of r, g, and - b. Each of r, g, and b must be in the range 0..colormode, where - colormode is either 1.0 or 255 (see colormode()). - - fillcolor(r, g, b) - Set fillcolor to the RGB color represented by r, g, and b. Each of - r, g, and b must be in the range 0..colormode. - - If turtleshape is a polygon, the interior of that polygon is drawn with - the newly set fillcolor. + def bgcolor(self, c=None): """ - raise NotImplementedError - - ############################################################################ - # Filling + Return or set the background color. - def filling(self): - """Not implemented + bgcolor() + Return the current backgroud color as color specification string. + May be used as input to another color/ pencolor/fillcolor call. - Return fillstate (True if filling, False else).""" - raise NotImplementedError - - def begin_fill(self): - """Not implemented + bgcolor(colorvalue) + Set backgroud color to colorvalue, which is a 24-bit integer such as 0xFF0000. + The Color class provides the available values: + WHITE, BLACK, RED, ORANGE, YELLOW, GREEN, BLUE, PURPLE, PINK + """ + if c is None: + return Color.colors[self._bg_color] + if c not in Color.colors: + raise RuntimeError("Color must be one of the 'Color' class items") + old_color = self._bg_color + self._fg_palette.make_opaque(old_color) + self._bg_color = Color.colors.index(c) + self._bg_palette[0] = c + self._fg_palette.make_transparent(self._bg_color) + self._turtle_palette[0] = c + if self._bg_color == self._pencolor: + self._turtle_palette.make_transparent(1) + else: + self._turtle_palette.make_opaque(1) + for h in range(self._h): + for w in range(self._w): + if self._fg_bitmap[w, h] == old_color : + self._fg_bitmap[w, h] = self._bg_color - To be called just before drawing a shape to be filled.""" - raise NotImplementedError + def set_bgpic(self, file): + """ + Set a picture as background. - def end_fill(self): - """Not implemented + set_bgpic(filename) + Set backgroud picture using OnDiskBitmap. + """ + self._bg_pic = open(file, 'rb') + odb = displayio.OnDiskBitmap(self._bg_pic) + self._odb_tilegrid = displayio.TileGrid(odb, pixel_shader=displayio.ColorConverter()) + self._bg_addon_group.append(self._odb_tilegrid) + # centered + self._odb_tilegrid.y = ((self._h*self._fg_scale)//2) - (odb.height//2) + self._odb_tilegrid.x = ((self._w*self._fg_scale)//2) - (odb.width//2) + + def del_bgpic(self): + """ + Remove the background picture, if any - Fill the shape drawn after the last call to begin_fill().""" - raise NotImplementedError + del_bgpic() + Remove the picture and close the file + """ + if self._bg_pic is not None: + self._bg_addon_group.remove(self._odb_tilegrid) + self._odb_tilegrid = None + self._bg_pic.close() + self._bg_pic = None - ############################################################################ + ########################################################################### # More drawing control def reset(self): - """Not implemented - + """ Delete the turtle's drawings from the screen, re-center the turtle and set variables to the default values.""" - raise NotImplementedError + self.changeturtle() + self.del_bgpic() + self.bgcolor(Color.BLACK) + self.clear() + self.penup() + self.goto(0, 0) + self.setheading(0) + self.pensize(1) + self.pencolor(Color.WHITE) + + def clear(self): """Delete the turtle's drawings from the screen. Do not move turtle.""" + self.clearstamps() for w in range(self._w): for h in range(self._h): - self._fg_bitmap[w, h] = 0 + self._fg_bitmap[w, h] = self._bg_color for i, c in enumerate(Color.colors): - self._fg_palette[i + 1] = c ^ 0xFFFFFF + self._fg_palette[i] = c ^ 0xFFFFFF for i, c in enumerate(Color.colors): - self._fg_palette[i + 1] = c + self._fg_palette[i] = c time.sleep(0.1) - def write(self, arg, move=False, align="left", font=("Arial", 8, "normal")): - """Not implemented - - Write text - the string representation of arg - at the current turtle - position according to align ("left", "center" or "right") and with the - given font. If move is true, the pen is moved to the bottom-right corner - of the text. By default, move is False. - - :param arg: object to be written to the TurtleScreen - :param move": True/False - :param align: one of the strings "left", "center" or "right" - :param font: a triple (fontname, fontsize, fonttype) - - """ - raise NotImplementedError - - ############################################################################ + ########################################################################### # Visibility def showturtle(self): - """Not implemented - + """ Make the turtle visible.""" - raise NotImplementedError + if len(self._turtle_group) == 1: + return + else: + if self._turtle_pic is None: + self._turtle_group.append(self._turtle_sprite) + else: + self._turtle_group.append(self._turtle_alt_sprite) + return st = showturtle def hideturtle(self): - """Not implemented - + """ Make the turtle invisible.""" - raise NotImplementedError + if len(self._turtle_group) == 0: + return + else: + self._turtle_group.pop() + return ht = hideturtle def isvisible(self): - """Not implemented - - Return True if the Turtle is shown, False if it's hidden.""" - raise NotImplementedError - - ############################################################################ - # Appearance - - def shape(self, name=None): - """Not implemented - - Set turtle shape to shape with given name or, if name is not - given, return name of current shape. Shape with name must exist - in the TurtleScreen's shape dictionary. Initially there are the - following polygon shapes: "arrow", "turtle", "circle", "square", - "triangle", "classic". To learn about how to deal with shapes - see Screen method register_shape(). - - :param name: a string which is a valid shapename - - """ - raise NotImplementedError - - def resizemode(self, rmode=None): - """Not implemented - - Set resizemode to one of the values: "auto", "user", - - "noresize". If rmode is not given, return current - resizemode. Different resizemodes have the following effects: - - "auto": adapts the appearance of the turtle corresponding to the value - of pensize. - - "user": adapts the appearance of the turtle according to the values of - stretchfactor and outlinewidth (outline), which are set by shapesize(). - - "noresize": no adaption of the turtle's appearance takes place. - - resizemode("user") is called by shapesize() when used with arguments. - - :param rmode: one of the strings "auto", "user", or "noresize" - - """ - raise NotImplementedError - - def shapesize(self, stretch_wid=None, stretch_len=None, outline=None): - """Not implemented - - Return or set the pen's attributes x/y-stretchfactors and/or - outline. Set resizemode to "user". If and only if resizemode is - set to "user", the turtle will be displayed stretched according - to its stretchfactors: stretch_wid is stretchfactor - perpendicular to its orientation, stretch_len is stretchfactor - in direction of its orientation, outline determines the width of - the shapes's outline. - - :param stretch_wid: positive number - :param stretch_len: positive number - :param outline: positive number - - """ - raise NotImplementedError - turtlesize = shapesize - - def sheerfactor(self, shear=None): - """Not implemented - - Set or return the current shearfactor. Shear the turtleshape - according to the given shearfactor shear, which is the tangent - of the shear angle. Do not change the turtle's heading - (direction of movement). If shear is not given: return the - current shearfactor, i. e. the tangent of the shear angle, by - which lines parallel to the heading of the turtle are sheared. - - :param shear: number (optional) - - """ - raise NotImplementedError - - def settiltangle(self, angle): - """Not implemented - - Rotate the turtleshape to point in the direction specified by - angle, regardless of its current tilt-angle. Do not change the - turtle's heading (direction of movement). - - :param angle: a number - - """ - raise NotImplementedError - - def tiltangle(self, angle=None): - """Not implemented - - Set or return the current tilt-angle. If angle is given, - rotate the turtleshape to point in the direction specified by - angle, regardless of its current tilt-angle. Do not change the - turtle's heading (direction of movement). If angle is not given: - return the current tilt-angle, i. e. the angle between the - orientation of the turtleshape and the heading of the turtle - (its direction of movement). - - :param angle: a number (optional) - - """ - raise NotImplementedError - - def tilt(self, angle): - """Not implemented - - Rotate the turtleshape by angle from its current tilt-angle, - but do not change the turtle's heading (direction of movement). - - :param angle: a number - """ - raise NotImplementedError - - def shapetransform(self, t11=None, t12=None, t21=None, t22=None): - """Not implemented - - Set or return the current transformation matrix of the turtle shape. - - If none of the matrix elements are given, return the transformation - matrix as a tuple of 4 elements. Otherwise set the given elements and - transform the turtleshape according to the matrix consisting of first - row t11, t12 and second row t21, 22. The determinant t11 * t22 - t12 * - t21 must not be zero, otherwise an error is raised. Modify - stretchfactor, shearfactor and tiltangle according to the given matrix. - - :param t11: a number (optional) - :param t12: a number (optional) - :param t21: a number (optional) - :param t12: a number (optional) - - """ - raise NotImplementedError - - def get_shapepoly(self): - """Not implemented - - Return the current shape polygon as tuple of coordinate - pairs. This can be used to define a new shape or components of a - compound shape. - """ - raise NotImplementedError - - ############################################################################ - # Using events - - def onclick(self, fun, btn=1, add=None): - """Not implemented - - Bind fun to mouse-click events on this turtle. If fun is - - None, existing bindings are removed. - - :param fun: a function with two arguments which will be called with the - coordinates of the clicked point on the canvas - - :param btn: number of the mouse-button, defaults to 1 (left mouse button) - - :param add: True or False - if True, a new binding will be added, - otherwise it will replace a former binding - - """ - raise NotImplementedError - - def onrelease(self, fun, btn=1, add=None): - """Not implemented - - Bind fun to mouse-button-release events on this turtle. If - fun is None, existing bindings are removed. - - :param fun: a function with two arguments which will be called with the - coordinates of the clicked point on the canvas - - :param btn: number of the mouse-button, defaults to 1 (left mouse button) - - :param add: True or False - if True, a new binding will be added, - otherwise it will replace a former binding - - """ - raise NotImplementedError - - def ondrag(self, fun, btn=1, add=None): - """Not implemented - - Bind fun to mouse-move events on this turtle. If fun is None, - existing bindings are removed. - - Remark: Every sequence of mouse-move-events on a turtle is - preceded by a mouse-click event on that turtle. - - :param fun: a function with two arguments which will be called with the - coordinates of the clicked point on the canvas - - :param btn: number of the mouse-button, defaults to 1 (left mouse button) - - :param add: True or False - if True, a new binding will be added, - otherwise it will replace a former binding - - """ - raise NotImplementedError - - ############################################################################ - # Special turtle methods - - def begin_poly(self): - """Not implemented - - Start recording the vertices of a polygon. Current turtle - position is first vertex of polygon. - """ - raise NotImplementedError - - def end_poly(self): - """Not implemented - - Stop recording the vertices of a polygon. Current turtle - position is last vertex of polygon. This will be connected with - the first vertex. - """ - raise NotImplementedError - - - def get_poly(self): - """Not implemented - - Return the last recorded polygon.""" - raise NotImplementedError - - def clone(self): - """Not implemented - - Create and return a clone of the turtle with same position, - heading and turtle properties. - """ - raise NotImplementedError - - def getturtle(self): - """Not implemented - - Return the Turtle object itself. Only reasonable use: as a - function to return the "anonymous turtle": - """ - raise NotImplementedError - getpen = getturtle - - def getscreen(self): - """Not implemented - - Return the TurtleScreen object the turtle is drawing - on. TurtleScreen methods can then be called for that object. - """ - raise NotImplementedError - - def setundobuffer(self, size): - """Not implemented - - Set or disable undobuffer. If size is an integer an empty - undobuffer of given size is installed. size gives the maximum - number of turtle actions that can be undone by the undo() - method/function. If size is None, the undobuffer is disabled. - - :param size: an integer or None - - """ - raise NotImplementedError - - def undobufferentries(self): - """Not implemented - - Return number of entries in the undobuffer.""" - raise NotImplementedError - - ############################################################################ - # Settings and special methods - - def mode(self, mode=None): - """Not implemented - - Set turtle mode ("standard" or "logo") and perform reset. - If mode is not given, current mode is returned. - - Mode "standard" is compatible with old turtle. - Mode "logo" is compatible with most Logo turtle graphics. - - :param mode: one of the strings "standard" or "logo" - """ - raise NotImplementedError - # if mode == "standard": - # self._logomode = False - # elif mode == "logo": - # self._logomode = True - # elif mode is None: - # if self._logomode: - # return "logo" - # return "standard" - # else: - # raise RuntimeError("Mode must be 'logo', 'standard!', or None") - # return None - - def colormode(self, cmode=None): - """Not implemented - - Return the colormode or set it to 1.0 or 255. Subsequently r, - g, b values of color triples have to be in the range 0..cmode. - - :param cmode: one of the valkues 1.0 or 255 - """ - raise NotImplementedError - - def getcanvas(self): - """Not implemented - - Return the Canvas of this TurtleScreen. Useful for insiders - who know what to do with a Tkinter Canvas. """ - raise NotImplementedError - - def getshapes(self): - """Not implemented + Return True if the Turtle is shown, False if it's hidden.""" + if len(self._turtle_group) == 0: + return False + else: + return True - Return a list of names of all currently available turtle - shapes. + def changeturtle(self, source=None, dimensions=(12, 12)): """ - raise NotImplementedError - - def register_shape(self, name, shape=None): - """Not implemented - - There are three different ways to call this function: - - 1. name is the name of a gif-file and shape is None: Install the - corresponding image shape. - - >>> screen.register_shape("turtle.gif") - - Note: Image shapes do not rotate when turning the turtle, so - they do not display the heading of the turtle! - - 2. name is an arbitrary string and shape is a tuple of pairs of - coordinates: Install the corresponding polygon shape. - - >>> screen.register_shape("triangle", ((5,-3), (0,5), (-5,-3))) - - 3. name is an arbitrary string and shape is a (compound) Shape - object: Install the corresponding compound shape. - - Add a turtle shape to TurtleScreen's shapelist. Only thusly registered - shapes can be used by issuing the command shape(shapename). + Change the turtle. + if a string is provided, its a path to an image opened via OnDiskBitmap + if a tilegrid is provided, it replace the default one for the turtle shape. + if no argument is provided, the default shape will be restored """ - raise NotImplementedError - addshape = register_shape - - def turtles(self): - """Not implemented - - Return the list of turtles on the screen.""" - raise NotImplementedError - - def window_height(self): - """Not implemented - - Return the height of the turtle window.""" - raise NotImplementedError - - def window_width(self): - """Not implemented - - Return the width of the turtle window.""" - raise NotImplementedError + if source is None: + if self._turtle_pic is None: + return + else: + if len(self._turtle_group) == 1: + self._turtle_group.remove(self._turtle_alt_sprite) + self._turtle_group.append(self._turtle_sprite) + self._turtle_alt_sprite = None + if self._turtle_odb is not None: + self._turtle_odb_use -= 1 + self._turtle_odb = None + if self._turtle_odb_file is not None : + if self._turtle_odb_use == 0: + self._turtle_odb_file.close() + self._turtle_odb_file = None + self._turtle_pic = None + self._drawturtle() + return + elif isinstance(source, str): + visible = self.isvisible() + if self._turtle_pic is not None: + if len(self._turtle_group) == 1: + self._turtle_group.remove(self._turtle_alt_sprite) + self._turtle_alt_sprite = None + self._turtle_odb = None + if not isinstance(self._turtle_pic, tuple): + self._turtle_odb_file.close() + self._turtle_odb_file = None + self._turtle_odb_use -= 1 + self._turtle_pic = None + self._turtle_odb_file = open(source, 'rb') + try: + self._turtle_odb = displayio.OnDiskBitmap(self._turtle_odb_file) + except: + self._turtle_odb_file.close() + self._turtle_odb_file = None + self._turtle_pic = None + if visible: + self._turtle_group.append(self._turtle_sprite) + raise + self._turtle_odb_use += 1 + self._turtle_pic = True + self._turtle_alt_sprite = displayio.TileGrid(self._turtle_odb, pixel_shader=displayio.ColorConverter()) + + if len(self._turtle_group) == 1: + self._turtle_group.pop() + if visible: + self._turtle_group.append(self._turtle_alt_sprite) + self._drawturtle() + elif isinstance(source, displayio.TileGrid): + if self._turtle_pic is not None: + if self._turtle_odb_file is not None: + self._turtle_odb_use -= 1 + if self._turtle_odb_use == 0: + self._turtle_odb_file.close() + self._turtle_pic = dimensions + self._turtle_alt_sprite = source + if len(self._turtle_group) == 1: + self._turtle_group.pop() + self._turtle_group.append(self._turtle_alt_sprite) + self._drawturtle() + else: + raise TypeError('Argument must be "str", a "displayio.TileGrid" or nothing.') - ############################################################################ + ########################################################################### # Other def _turn(self, angle): - if self._logomode: - self._heading -= angle - else: + if angle % self._fullcircle == 0: + return + if not self.isdown() or self._pensize == 1: self._heading += angle - self._heading %= 360 # wrap around + self._heading %= self._fullcircle # wrap + return + start_angle = self._heading + steps = math.ceil((self._pensize*2)*3.1415*(abs(angle)/self._fullcircle)) + if steps < 1: + d_angle = angle + steps = 1 + else: + d_angle = angle/steps + + if d_angle > 0: + d_angle = math.ceil(d_angle) + elif d_angle < 0: + d_angle = math.floor(d_angle) + else: + print("d_angle = 0 !", d_angle, angle, steps) + if self._logomode: + self._heading += angle + else: + self._heading -= angle + self._heading %= self._fullcircle # wrap + return + + if abs(angle-steps*d_angle) >= abs(d_angle): + steps += abs(angle-steps*d_angle) // abs(d_angle) + + self._plot(self._x, self._y, self._pencolor) + for i in range(steps): + self._heading += d_angle + self._heading %= self._fullcircle # wrap + self._plot(self._x, self._y, self._pencolor) + + # error correction + if self._heading != (start_angle + angle) % self._fullcircle : + self._heading = start_angle + angle + self._heading %= self._fullcircle + self._plot(self._x, self._y, self._pencolor) + + def _GCD(self, a, b): + """GCD(a,b): + recursive 'Greatest common divisor' calculus for int numbers a and b""" + if b == 0: + return a + else: + r = a % b + return self._GCD(b, r) + diff --git a/examples/turtle_hilbert.py b/examples/turtle_hilbert.py index c39d58a..c98b7f1 100644 --- a/examples/turtle_hilbert.py +++ b/examples/turtle_hilbert.py @@ -35,7 +35,7 @@ def hilbert2(step, rule, angle, depth, t): turtle = turtle(board.DISPLAY) turtle.penup() - +turtle.heading(90) turtle.goto(-80, -80) turtle.pendown() hilbert2(5, "a", 90, 5, turtle) diff --git a/examples/turtle_overlayed_koch.py b/examples/turtle_overlayed_koch.py index 440f641..d8f481a 100644 --- a/examples/turtle_overlayed_koch.py +++ b/examples/turtle_overlayed_koch.py @@ -31,6 +31,8 @@ def snowflake(num_generations, generation_color): unit= min(board.DISPLAY.width / 3, board.DISPLAY.height / 4) top_len = unit * 3 print(top_len) +turtle.heading(90) + turtle.penup() turtle.goto(-1.5 * unit, unit) turtle.pendown() diff --git a/examples/turtle_simpletest.py b/examples/turtle_simpletest.py index 2b8587c..608966f 100644 --- a/examples/turtle_simpletest.py +++ b/examples/turtle_simpletest.py @@ -7,6 +7,7 @@ print("Turtle time! Lets draw a star") turtle.pencolor(Color.BLUE) +turtle.heading(90) turtle.penup() turtle.goto(-starsize/2, 0) diff --git a/examples/turtle_star.py b/examples/turtle_star.py index 2b8587c..608966f 100644 --- a/examples/turtle_star.py +++ b/examples/turtle_star.py @@ -7,6 +7,7 @@ print("Turtle time! Lets draw a star") turtle.pencolor(Color.BLUE) +turtle.heading(90) turtle.penup() turtle.goto(-starsize/2, 0) From 0c19bd06b3ba14965166857c636e908291c0dac2 Mon Sep 17 00:00:00 2001 From: Marius-450 Date: Fri, 6 Mar 2020 07:06:53 -0500 Subject: [PATCH 2/6] pylint compliance --- adafruit_turtle.py | 75 +++++++++++++++++++++++----------------------- 1 file changed, 37 insertions(+), 38 deletions(-) diff --git a/adafruit_turtle.py b/adafruit_turtle.py index 4bcb5b6..f6800be 100644 --- a/adafruit_turtle.py +++ b/adafruit_turtle.py @@ -139,7 +139,7 @@ def __repr__(self): class turtle(object): """A Turtle that can be given commands to draw.""" - + # pylint:disable=too-many-statements def __init__(self, display=None, scale=1): if display: @@ -224,6 +224,7 @@ def __init__(self, display=None, scale=1): self._splash.append(self._turtle_group) self._penstate = False self._pensize = 1 + self._pencolor = 1 self.pencolor(Color.WHITE) self._bg_pic = None self._turtle_pic = None @@ -233,8 +234,10 @@ def __init__(self, display=None, scale=1): self._stamps = {} self._turtle_odb_use = 0 self._turtle_odb_file = None + self._odb_tilegrid = None gc.collect() self._display.show(self._splash) + # pylint:enable=too-many-statements def _drawturtle(self): if self._turtle_pic is None: @@ -257,8 +260,6 @@ def forward(self, distance): :param distance: how far to move (integer or float) """ p = self.pos() - # works only for degrees. - # TODO implement for radians x1 = p[0] + math.sin(math.radians((self._angleOffset + self._angleOrient*self._heading) % self._fullcircle)) * distance y1 = p[1] + math.cos(math.radians((self._angleOffset + self._angleOrient*self._heading) % self._fullcircle)) * distance self.goto(x1, y1) @@ -301,7 +302,7 @@ def left(self, angle): self._turn(angle) lt = left - #pylint:disable=too-many-branches,too-many-statements + # pylint:disable=too-many-branches,too-many-statements def goto(self, x1, y1=None): """If y1 is None, x1 must be a pair of coordinates or an (x, y) tuple @@ -363,7 +364,7 @@ def goto(self, x1, y1=None): self._x = x0 self._y = y0 if self._speed > 0: - if step >= self._speed : + if step >= self._speed: # mark the step step = 1 self._drawturtle() @@ -381,6 +382,7 @@ def goto(self, x1, y1=None): self._drawturtle() setpos = goto setposition = goto + # pylint:enable=too-many-branches,too-many-statements def setx(self, x): """Set the turtle's first coordinate to x, leave second coordinate @@ -424,6 +426,7 @@ def home(self): self.setheading(90) self.goto(0, 0) + # pylint:disable=too-many-locals def _plot(self, x, y, c): if self._pensize == 1: try: @@ -498,6 +501,7 @@ def _plot(self, x, y, c): x0 -= 1 else: x0 += 1 + # pylint:enable=too-many-locals def circle(self, radius, extent=None, steps=None): """Draw a circle with given radius. The center is radius units left of @@ -542,6 +546,7 @@ def circle(self, radius, extent=None, steps=None): self.goto(pos) self.setheading(h) + # pylint:disable=inconsistent-return-statements def speed(self, speed=None): """ @@ -571,6 +576,7 @@ def speed(self, speed=None): self._speed = 0 else: self._speed = speed + # pylint:enable=inconsistent-return-statements def dot(self, size=None, color=None): """Draw a circular dot with diameter size, using color. @@ -605,6 +611,7 @@ def dot(self, size=None, color=None): self._plot(self._x, self._y, color) self._pensize = pensize + def stamp(self, bitmap=None, palette=None): """ Stamp a copy of the turtle shape onto the canvas at the current @@ -613,7 +620,7 @@ def stamp(self, bitmap=None, palette=None): """ if len(self._fg_addon_group) >= 6: print("Addon group full") - return + return -1 s_id = len(self._stamps) if self._turtle_pic is None: # easy. @@ -629,9 +636,9 @@ def stamp(self, bitmap=None, palette=None): y=int(self._y - self._turtle_odb.height // 2)) self._turtle_odb_use += 1 else: - if bitmap == None: + if bitmap is None: raise RuntimeError("a bitmap must be provided") - if palette == None: + if palette is None: raise RuntimeError("a palette must be provided") new_stamp = displayio.TileGrid(bitmap, pixel_shader=palette, x=int(self._x-bitmap.width//2), @@ -711,13 +718,9 @@ def towards(self, x1, y1=None): result = math.degrees(math.atan2(x1-x0, y1-y0)) result /= self._degreesPerAU return (self._angleOffset + self._angleOrient*result) % self._fullcircle - if self._logomode : - print("logo mode") + if self._logomode: return(math.degrees(math.atan2(x0-x1, y0-y1))) - else: - # not used yet - print("standard mode") - return(math.degrees(math.atan2(y0-y1, x0-x1))) + return(math.degrees(math.atan2(y0-y1, x0-x1))) def xcor(self): """Return the turtle's x coordinate.""" @@ -755,7 +758,7 @@ def _setDegreesPerAU(self, fullcircle): """Helper function for degrees() and radians()""" self._fullcircle = fullcircle self._degreesPerAU = 360/fullcircle - if self._mode == "logo": + if self._logomode: self._angleOffset = 0 else: self._angleOffset = -fullcircle/4 @@ -846,10 +849,11 @@ def pensize(self, width=None): ########################################################################### # Color control -#pylint:disable=no-self-use + # pylint:disable=no-self-use def _color_to_pencolor(self, c): return Color.colors.index(c) + # pylint:enable=no-self-use def pencolor(self, c=None): """ @@ -908,8 +912,9 @@ def bgcolor(self, c=None): self._turtle_palette.make_opaque(1) for h in range(self._h): for w in range(self._w): - if self._fg_bitmap[w, h] == old_color : + if self._fg_bitmap[w, h] == old_color: self._fg_bitmap[w, h] = self._bg_color + return Color.colors[self._bg_color] def set_bgpic(self, file): """ @@ -976,33 +981,28 @@ def clear(self): def showturtle(self): """ Make the turtle visible.""" - if len(self._turtle_group) == 1: + if self._turtle_group: return + if self._turtle_pic is None: + self._turtle_group.append(self._turtle_sprite) else: - if self._turtle_pic is None: - self._turtle_group.append(self._turtle_sprite) - else: - self._turtle_group.append(self._turtle_alt_sprite) - return + self._turtle_group.append(self._turtle_alt_sprite) st = showturtle def hideturtle(self): """ Make the turtle invisible.""" - if len(self._turtle_group) == 0: - return - else: - self._turtle_group.pop() + if not self._turtle_group: return + self._turtle_group.pop() ht = hideturtle def isvisible(self): """ Return True if the Turtle is shown, False if it's hidden.""" - if len(self._turtle_group) == 0: - return False - else: + if self._turtle_group: return True + return False def changeturtle(self, source=None, dimensions=(12, 12)): """ @@ -1015,14 +1015,14 @@ def changeturtle(self, source=None, dimensions=(12, 12)): if self._turtle_pic is None: return else: - if len(self._turtle_group) == 1: + if self._turtle_group: self._turtle_group.remove(self._turtle_alt_sprite) self._turtle_group.append(self._turtle_sprite) self._turtle_alt_sprite = None if self._turtle_odb is not None: self._turtle_odb_use -= 1 self._turtle_odb = None - if self._turtle_odb_file is not None : + if self._turtle_odb_file is not None: if self._turtle_odb_use == 0: self._turtle_odb_file.close() self._turtle_odb_file = None @@ -1032,7 +1032,7 @@ def changeturtle(self, source=None, dimensions=(12, 12)): elif isinstance(source, str): visible = self.isvisible() if self._turtle_pic is not None: - if len(self._turtle_group) == 1: + if self._turtle_group: self._turtle_group.remove(self._turtle_alt_sprite) self._turtle_alt_sprite = None self._turtle_odb = None @@ -1055,7 +1055,7 @@ def changeturtle(self, source=None, dimensions=(12, 12)): self._turtle_pic = True self._turtle_alt_sprite = displayio.TileGrid(self._turtle_odb, pixel_shader=displayio.ColorConverter()) - if len(self._turtle_group) == 1: + if self._turtle_group: self._turtle_group.pop() if visible: self._turtle_group.append(self._turtle_alt_sprite) @@ -1068,7 +1068,7 @@ def changeturtle(self, source=None, dimensions=(12, 12)): self._turtle_odb_file.close() self._turtle_pic = dimensions self._turtle_alt_sprite = source - if len(self._turtle_group) == 1: + if self._turtle_group: self._turtle_group.pop() self._turtle_group.append(self._turtle_alt_sprite) self._drawturtle() @@ -1110,13 +1110,13 @@ def _turn(self, angle): steps += abs(angle-steps*d_angle) // abs(d_angle) self._plot(self._x, self._y, self._pencolor) - for i in range(steps): + for _ in range(steps): self._heading += d_angle self._heading %= self._fullcircle # wrap self._plot(self._x, self._y, self._pencolor) # error correction - if self._heading != (start_angle + angle) % self._fullcircle : + if self._heading != (start_angle + angle) % self._fullcircle: self._heading = start_angle + angle self._heading %= self._fullcircle self._plot(self._x, self._y, self._pencolor) @@ -1129,4 +1129,3 @@ def _GCD(self, a, b): else: r = a % b return self._GCD(b, r) - From a979fcfe4755328477547c8a6c971151adaa805f Mon Sep 17 00:00:00 2001 From: Marius-450 Date: Fri, 6 Mar 2020 07:46:44 -0500 Subject: [PATCH 3/6] pylint compliance (bis) --- adafruit_turtle.py | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/adafruit_turtle.py b/adafruit_turtle.py index f6800be..30e9371 100644 --- a/adafruit_turtle.py +++ b/adafruit_turtle.py @@ -174,7 +174,7 @@ def __init__(self, display=None, scale=1): i += 1 else: self._bgscale = self._GCD(self._w, self._h) - self._bg_bitmap = displayio.Bitmap(self._w//self._bgscale, self._h//self._bgscale , 1) + self._bg_bitmap = displayio.Bitmap(self._w//self._bgscale, self._h//self._bgscale, 1) self._bg_palette = displayio.Palette(1) self._bg_palette[0] = Color.colors[self._bg_color] self._bg_sprite = displayio.TileGrid(self._bg_bitmap, @@ -260,8 +260,9 @@ def forward(self, distance): :param distance: how far to move (integer or float) """ p = self.pos() - x1 = p[0] + math.sin(math.radians((self._angleOffset + self._angleOrient*self._heading) % self._fullcircle)) * distance - y1 = p[1] + math.cos(math.radians((self._angleOffset + self._angleOrient*self._heading) % self._fullcircle)) * distance + angle = (self._angleOffset + self._angleOrient*self._heading) % self._fullcircle + x1 = p[0] + math.sin(math.radians(angle)) * distance + y1 = p[1] + math.cos(math.radians(angle)) * distance self.goto(x1, y1) fd = forward @@ -426,7 +427,7 @@ def home(self): self.setheading(90) self.goto(0, 0) - # pylint:disable=too-many-locals + # pylint:disable=too-many-locals, too-many-statements, too-many-branches def _plot(self, x, y, c): if self._pensize == 1: try: @@ -435,8 +436,9 @@ def _plot(self, x, y, c): except IndexError: pass r = self._pensize // 2 + 1 - sin = math.sin(math.radians((self._angleOffset + self._angleOrient*self._heading - 90) % self._fullcircle)) - cos = math.cos(math.radians((self._angleOffset + self._angleOrient*self._heading - 90) % self._fullcircle)) + angle = (self._angleOffset + self._angleOrient*self._heading - 90) % self._fullcircle + sin = math.sin(math.radians(angle)) + cos = math.cos(math.radians(angle)) x0 = x + sin * r x1 = x - sin * (self._pensize - r) y0 = y - cos * r @@ -501,7 +503,7 @@ def _plot(self, x, y, c): x0 -= 1 else: x0 += 1 - # pylint:enable=too-many-locals + # pylint:enable=too-many-locals, too-many-statements, too-many-branches def circle(self, radius, extent=None, steps=None): """Draw a circle with given radius. The center is radius units left of @@ -718,9 +720,6 @@ def towards(self, x1, y1=None): result = math.degrees(math.atan2(x1-x0, y1-y0)) result /= self._degreesPerAU return (self._angleOffset + self._angleOrient*result) % self._fullcircle - if self._logomode: - return(math.degrees(math.atan2(x0-x1, y0-y1))) - return(math.degrees(math.atan2(y0-y1, x0-x1))) def xcor(self): """Return the turtle's x coordinate.""" @@ -749,7 +748,7 @@ def distance(self, x1, y1=None): y1 = x1[1] x1 = x1[0] x0, y0 = self.pos() - return(math.sqrt((x0-x1)**2+(y0-y1)**2)) + return math.sqrt((x0-x1) ** 2 + (y0 - y1) ** 2) ########################################################################### # Setting and measurement @@ -1004,6 +1003,7 @@ def isvisible(self): return True return False + # pylint:disable=too-many-statements, too-many-branches def changeturtle(self, source=None, dimensions=(12, 12)): """ Change the turtle. @@ -1053,7 +1053,8 @@ def changeturtle(self, source=None, dimensions=(12, 12)): raise self._turtle_odb_use += 1 self._turtle_pic = True - self._turtle_alt_sprite = displayio.TileGrid(self._turtle_odb, pixel_shader=displayio.ColorConverter()) + self._turtle_alt_sprite = displayio.TileGrid(self._turtle_odb, + pixel_shader=displayio.ColorConverter()) if self._turtle_group: self._turtle_group.pop() @@ -1074,6 +1075,7 @@ def changeturtle(self, source=None, dimensions=(12, 12)): self._drawturtle() else: raise TypeError('Argument must be "str", a "displayio.TileGrid" or nothing.') + # pylint:enable=too-many-statements, too-many-branches ########################################################################### # Other @@ -1126,6 +1128,5 @@ def _GCD(self, a, b): recursive 'Greatest common divisor' calculus for int numbers a and b""" if b == 0: return a - else: - r = a % b - return self._GCD(b, r) + r = a % b + return self._GCD(b, r) From 210cb3bdb86dd529dee8a9b108c6719ca94f2b64 Mon Sep 17 00:00:00 2001 From: Marius-450 Date: Fri, 6 Mar 2020 07:52:46 -0500 Subject: [PATCH 4/6] heading -> setheading --- examples/turtle_hilbert.py | 2 +- examples/turtle_overlayed_koch.py | 2 +- examples/turtle_simpletest.py | 2 +- examples/turtle_star.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/turtle_hilbert.py b/examples/turtle_hilbert.py index c98b7f1..e164287 100644 --- a/examples/turtle_hilbert.py +++ b/examples/turtle_hilbert.py @@ -35,7 +35,7 @@ def hilbert2(step, rule, angle, depth, t): turtle = turtle(board.DISPLAY) turtle.penup() -turtle.heading(90) +turtle.setheading(90) turtle.goto(-80, -80) turtle.pendown() hilbert2(5, "a", 90, 5, turtle) diff --git a/examples/turtle_overlayed_koch.py b/examples/turtle_overlayed_koch.py index d8f481a..4fb2332 100644 --- a/examples/turtle_overlayed_koch.py +++ b/examples/turtle_overlayed_koch.py @@ -31,7 +31,7 @@ def snowflake(num_generations, generation_color): unit= min(board.DISPLAY.width / 3, board.DISPLAY.height / 4) top_len = unit * 3 print(top_len) -turtle.heading(90) +turtle.setheading(90) turtle.penup() turtle.goto(-1.5 * unit, unit) diff --git a/examples/turtle_simpletest.py b/examples/turtle_simpletest.py index 608966f..d2c3521 100644 --- a/examples/turtle_simpletest.py +++ b/examples/turtle_simpletest.py @@ -7,7 +7,7 @@ print("Turtle time! Lets draw a star") turtle.pencolor(Color.BLUE) -turtle.heading(90) +turtle.setheading(90) turtle.penup() turtle.goto(-starsize/2, 0) diff --git a/examples/turtle_star.py b/examples/turtle_star.py index 608966f..d2c3521 100644 --- a/examples/turtle_star.py +++ b/examples/turtle_star.py @@ -7,7 +7,7 @@ print("Turtle time! Lets draw a star") turtle.pencolor(Color.BLUE) -turtle.heading(90) +turtle.setheading(90) turtle.penup() turtle.goto(-starsize/2, 0) From c6a5bcab14e995c5da0be2aff4f91e96737676ad Mon Sep 17 00:00:00 2001 From: Marius-450 Date: Fri, 6 Mar 2020 09:01:13 -0500 Subject: [PATCH 5/6] merged set_bgpic and del_bgpic in bgpic function --- adafruit_turtle.py | 54 ++++++++++++++++++++++++---------------------- 1 file changed, 28 insertions(+), 26 deletions(-) diff --git a/adafruit_turtle.py b/adafruit_turtle.py index 30e9371..c62a314 100644 --- a/adafruit_turtle.py +++ b/adafruit_turtle.py @@ -227,6 +227,7 @@ def __init__(self, display=None, scale=1): self._pencolor = 1 self.pencolor(Color.WHITE) self._bg_pic = None + self._bg_pic_filename = "" self._turtle_pic = None self._turtle_odb = None self._turtle_alt_sprite = None @@ -915,33 +916,34 @@ def bgcolor(self, c=None): self._fg_bitmap[w, h] = self._bg_color return Color.colors[self._bg_color] - def set_bgpic(self, file): - """ - Set a picture as background. - - set_bgpic(filename) - Set backgroud picture using OnDiskBitmap. - """ - self._bg_pic = open(file, 'rb') - odb = displayio.OnDiskBitmap(self._bg_pic) - self._odb_tilegrid = displayio.TileGrid(odb, pixel_shader=displayio.ColorConverter()) - self._bg_addon_group.append(self._odb_tilegrid) - # centered - self._odb_tilegrid.y = ((self._h*self._fg_scale)//2) - (odb.height//2) - self._odb_tilegrid.x = ((self._w*self._fg_scale)//2) - (odb.width//2) - - def del_bgpic(self): - """ - Remove the background picture, if any - - del_bgpic() - Remove the picture and close the file + # pylint:disable=inconsistent-return-statements + def bgpic(self, picname=None): + """Set background image or return name of current backgroundimage. + Optional argument: + picname -- a string, name of an image file or "nopic". + If picname is a filename, set the corresponding image as background. + If picname is "nopic", delete backgroundimage, if present. + If picname is None, return the filename of the current backgroundimage. """ - if self._bg_pic is not None: - self._bg_addon_group.remove(self._odb_tilegrid) - self._odb_tilegrid = None - self._bg_pic.close() - self._bg_pic = None + if picname is None: + return self._bg_pic_filename + if picname == "nopic": + if self._bg_pic is not None: + self._bg_addon_group.remove(self._odb_tilegrid) + self._odb_tilegrid = None + self._bg_pic.close() + self._bg_pic = None + self._bg_pic_filename = "" + else: + self._bg_pic = open(picname, 'rb') + odb = displayio.OnDiskBitmap(self._bg_pic) + self._odb_tilegrid = displayio.TileGrid(odb, pixel_shader=displayio.ColorConverter()) + self._bg_addon_group.append(self._odb_tilegrid) + self._bg_pic_filename = picname + # centered + self._odb_tilegrid.y = ((self._h*self._fg_scale)//2) - (odb.height//2) + self._odb_tilegrid.x = ((self._w*self._fg_scale)//2) - (odb.width//2) + # pylint:enable=inconsistent-return-statements ########################################################################### # More drawing control From c842228a0379ebeca7053b0dcf8abc0bc382e082 Mon Sep 17 00:00:00 2001 From: Marius-450 Date: Fri, 6 Mar 2020 09:08:25 -0500 Subject: [PATCH 6/6] I missed a del_bgpic in reset() --- adafruit_turtle.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adafruit_turtle.py b/adafruit_turtle.py index c62a314..ed30d78 100644 --- a/adafruit_turtle.py +++ b/adafruit_turtle.py @@ -953,7 +953,7 @@ def reset(self): Delete the turtle's drawings from the screen, re-center the turtle and set variables to the default values.""" self.changeturtle() - self.del_bgpic() + self.bgpic("nopic") self.bgcolor(Color.BLACK) self.clear() self.penup()