diff --git a/__pycache__/shapes.cpython-310.pyc b/__pycache__/shapes.cpython-310.pyc index 701b84e..6cc0ac5 100644 Binary files a/__pycache__/shapes.cpython-310.pyc and b/__pycache__/shapes.cpython-310.pyc differ diff --git a/__pycache__/superturt.cpython-310.pyc b/__pycache__/superturt.cpython-310.pyc index 021c646..f9b5c5a 100644 Binary files a/__pycache__/superturt.cpython-310.pyc and b/__pycache__/superturt.cpython-310.pyc differ diff --git a/changingcolors.py b/changingcolors.py new file mode 100644 index 0000000..5255690 --- /dev/null +++ b/changingcolors.py @@ -0,0 +1,31 @@ +from turtle import * +from superturt import * +from shapes import * + +# I'm trying to figure out how to get the leaves to change colors. + +with no_delay(): + fillcolor('green') + begin_fill() + draw_leaf(20) + end_fill() + +with no_delay(): + fillcolor('yellow') + begin_fill() + draw_leaf(20) + end_fill() + +with no_delay(): + fillcolor('orange') + begin_fill() + draw_leaf(20) + end_fill() + +with no_delay(): + fillcolor('red') + begin_fill() + draw_leaf(20) + end_fill() + +input() \ No newline at end of file diff --git a/drawing.py b/drawing.py index 954c5d3..2780035 100644 --- a/drawing.py +++ b/drawing.py @@ -2,17 +2,15 @@ # ---------- # By Stacy S # -# (Drew a tree.) +# (Drew a tree. Trying to make it change colors.) from turtle import * -from math import sqrt from shapes import * from superturt import * with no_delay(): - with restore_state_when_finished(): - draw_tree_nl(20) - with restore_state_when_finished(): - draw_tree_wl(20) - -input() \ No newline at end of file + for frame in animate(frames=6, loop=True, debug=False): + with restore_state_when_finished(): + draw_tree_nl(20) + with restore_state_when_finished(): + draw_tree_wl(20) \ No newline at end of file diff --git a/shapes.py b/shapes.py index 0bbdc75..4c060ac 100644 --- a/shapes.py +++ b/shapes.py @@ -4,13 +4,13 @@ from superturt import * def draw_leaf(size): pencolor('black') - fillcolor('green') - begin_fill() +# fillcolor('green') +# begin_fill() circle(size,90) right(270) circle(.75*size,90) circle(-.25*size,90) - end_fill() +# end_fill() def draw_leaves(): with restore_state_when_finished(): @@ -48,13 +48,7 @@ def tip_nl(size): forward(size) def tip_wl(size): - forward(size) - right(45) - forward(size/10) - right(90) - forward(size/10) - right(45) - forward(size) + tip_nl(size) draw_leaves() def branching_nl(ang1, d1, ang2, d2, ang3): @@ -65,11 +59,7 @@ def branching_nl(ang1, d1, ang2, d2, ang3): right(ang3) def branching_wl(ang1, d1, ang2, d2, ang3): - right(ang1) - forward(d1) - right(ang2) - forward(d2) - right(ang3) + branching_nl(ang1, d1, ang2, d2, ang3) draw_leaves() def branch_end_wl(size): diff --git a/superturt.py b/superturt.py index 8270ebd..ef34422 100644 --- a/superturt.py +++ b/superturt.py @@ -1,6 +1,32 @@ +#from superturtle by Chris Proctor + from turtle import * from itertools import chain, cycle +from pathlib import Path +from shutil import rmtree +from subprocess import run +from turtle import ( + Turtle, + left, + right, + clear, + home, + heading, + setheading, + isdown, + hideturtle, + penup, + pendown, + forward, + getcanvas, +) + +from itertools import cycle +from time import time, sleep + +FRAMES_PATH = Path(".frames") + class no_delay: """A context manager which causes drawing code to run instantly. @@ -67,4 +93,254 @@ class restore_state_when_finished: penup() setposition(self.position) setheading(self.heading) - pendown() \ No newline at end of file + pendown() + + +def animate(frames=1, loop=False, debug=False, gif_filename=None): + """Runs an animation, frame by frame, at a fixed frame rate of 20 fps. + An animation consists of a bunch of frames shown one after another. + Before creating an animation, create a static image and then think about + how you would like for it to move. The simplest way to use `animate` is + with no arguments; this produces a static image. (Not much of an animation!):: + + for frame in animate(): + draw_my_picture(frame) + + Once you are happy with your static image, specify `frames` and `animate` + will run the provided code block over and over, drawing one frame at a time:: + + for frame in animate(frames=6, debug=True): + draw_my_picture(frame) + + Because we set `debug` to `True`, you will see the following output in the Terminal. + Additionally, since we are in debug mode, you need to press enter to advance one + frame at a time.:: + + Drawing frame 0 + Drawing frame 1 + Drawing frame 2 + Drawing frame 3 + Drawing frame 4 + Drawing frame 5 + + Arguments: + frames (int): The total number of frames in your animation. + loop (bool): When True, the animation will play in a loop. + debug (bool): When True, renders the animation in debug mode. + gif_filename (str): When provided, saves the animation as a gif, with 10 frames per second. + """ + start_time = time() + if frames <= 0: + raise AnimationError("frames must be a positive integer") + frame_delta = 0.05 + hideturtle() + if debug: + frame_iterator = debug_iter(frames) + elif loop and not gif_filename: + frame_iterator = cycle(range(frames)) + else: + frame_iterator = range(frames) + if gif_filename: + if FRAMES_PATH.exists(): + if FRAMES_PATH.is_dir(): + rmtree(FRAMES_PATH) + else: + FRAMES_PATH.unlink() + FRAMES_PATH.mkdir() + + for frame_number in frame_iterator: + frame = Frame(frames, frame_number, debug=debug) + if time() < start_time + frame_number * frame_delta: + sleep(start_time + frame_number * frame_delta - time()) + with no_delay(): + home() + clear() + yield frame + if gif_filename: + filename = FRAMES_PATH / f"frame_{frame_number:03d}.eps" + canvas = getcanvas() + canvas.postscript(file=filename) + canvas.delete("all") + + if gif_filename: + loopflag = "-loop 0 " if loop else "" + run(f"convert -delay 5 -dispose Background {loopflag}{FRAMES_PATH}/frame_*.eps {gif_filename}", shell=True, check=True) + rmtree(FRAMES_PATH) + + +class Frame: + """ Represents one frame in an animation. + When creating an animation, `animate` will yield one `Frame` for each + frame. The `Frame` can be used to check information, such as the current + frame index (the first frame's index is 0; the tenth frame's index is 9). + The `Frame` can also be used to create motion through the use of transformations. + A transformation is a change to the picture which gradually changes from frame + to frame. The three supported transformations are `rotate`, `scale`, and + `translate`. + """ + def __init__(self, num_frames, index=0, debug=False): + self.debug = debug + self.num_frames = num_frames + self.index = index + self.stack = [] + self.log("Drawing frame {}".format(self.index)) + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, traceback): + exit = self.stack.pop() + exit() + + def rotate(self, start, stop=None, first_frame=None, last_frame=None, cycles=1, mirror=False, easing=None): + """Runs the code block within a rotation:: + + for frame in animate(frames=30): + with frame.rotate(0, 90): + square(100) + + Arguments: + start (int): the initial value. + stop (int): (optional) the final value. If not provided, this will be a static + rotation of `start` on every frame. + first_frame (int): (optional) The first frame at which this rotation should be + interpolated. If given, the rotation will be `start` at `first_frame` + and all prior frames. If not given, interpolation starts at the beginning + of the animation. + last_frame(int): (optional) The last frame at which this rotation should be + interpolated. If given, the rotation will be `stop` at `last_frame` + and all later frames. If not given, interpolation ends at the end of the + animation. + cycles (int): (optional) Number of times the animation should be run. + mirror (bool): (optional) When True, the animation runs forward and then backwards + between `first_frame` and `last_frame`. + easing (function): (optional) An easing function to use. + """ + value = self.interpolate(start, stop, first_frame, last_frame, cycles, mirror, easing) + left(value) + self.stack.append(lambda: right(value)) + self.log("Rotating by {}".format(value)) + return self + + + def scale(self, start, stop=None, first_frame=None, last_frame=None, cycles=1, mirror=False, easing=None): + """Scales the code block. For this to work correctly, make sure you start from the + center of the drawing in the code block:: + + for frame in animate(frames=30): + with frame.scale(1, 2): + square(100) + + Arguments: + start (int): the initial value. + stop (int): (optional) the final value. If not provided, this will be a static + scaling of `start` on every frame. + first_frame (int): (optional) The first frame at which this scaling should be + interpolated. If given, the scaling will be `start` at `first_frame` + and all prior frames. If not given, interpolation starts at the beginning + of the animation. + last_frame (int): (optional) The last frame at which this scaling should be + interpolated. If given, the scaling will be `stop` at `last_frame` + and all later frames. If not given, interpolation ends at the end of the + animation. + cycles (int): (optional) Number of times the animation should be run. + mirror (bool): (optional) When True, the animation runs forward and then backwards + between `first_frame` and `last_frame`. + easing (function): (optional) An easing function to use. + """ + value = self.interpolate(start, stop, first_frame, last_frame, cycles, mirror, easing) + repair = self._scale_turtle_go(value) + self.stack.append(repair) + self.log("Scaling by {}".format(value)) + return self + + + def translate(self, start, stop=None, first_frame=None, last_frame=None, cycles=1, mirror=False, easing=None): + """Translates (moves) the code block in the current coordinate space:: + + for frame in animate(frames=30): + with frame.translate([0, 0], [100, 100]): + square(100) + + Arguments: + start (int): the initial value. + stop (int): (optional) the final value. If not provided, this will be a static + translation of `start` on every frame. + first_frame (int): (optional) The first frame at which this translation should be + interpolated. If given, the translation will be `start` at `first_frame` + and all prior frames. If not given, interpolation starts at the beginning + of the animation. + last_frame (int): (optional) The last frame at which this translation should be + interpolated. If given, the translation will be `stop` at `last_frame` + and all later frames. If not given, interpolation ends at the end of the + animation. + cycles (int): (optional) Number of times the animation should be run. + mirror (bool): (optional) When True, the animation runs forward and then backwards + between `first_frame` and `last_frame`. + easing (function): (optional) An easing function to use. + """ + if stop: + x0, y0 = start + x1, y1 = stop + dx = self.interpolate(x0, x1, first_frame, last_frame, cycles, mirror, easing) + dy = self.interpolate(y0, y1, first_frame, last_frame, cycles, mirror, easing) + else: + dx, dy = start + + def scoot(x, y): + pd = isdown() + penup() + forward(x) + left(90) + forward(y) + right(90) + if pd: + pendown() + + scoot(dx, dy) + self.stack.append(lambda: scoot(-dx, -dy)) + + self.log("Translating by ({}, {})".format(dx, dy)) + return self + + + def _scale_turtle_go(self, scale_factor): + """Patches `Turtle._go` with a version which scales all motion + by `scale_factor`. Returns a repair function which will restore + `Turtle._go` when called. + """ + prior_go = Turtle._go + def scaled_go(turtle_self, distance): + prior_go(turtle_self, distance * scale_factor) + Turtle._go = scaled_go + def repair(): + Turtle._go = prior_go + return repair + + def log(self, message): + if self.debug: + print(" " * len(self.stack) + message) + + +def debug_iter(max_val=None): + "An iterator which yields only when input is " + HELP = '?' + INCREMENT = ['', 'f'] + DECREMENT = ['b'] + value = 0 + print("In debug mode. Enter {} for help.".format(HELP)) + while True: + yield value % max_val if max_val else value + command = None + while command not in INCREMENT and command not in DECREMENT: + if command == HELP: + print("Debug mode moves one frame at a time.") + print("Enter 'f' or '' (blank) to move forward. Enter 'b' to move backward.") + command = input() + if command in INCREMENT: + value += 1 + else: + value -= 1 + +class AnimationError(Exception): + pass \ No newline at end of file