Reputation: 73
im currently playing around with pythons turtle module and im trying to make a grid of opaque square shapes, say 30x30 that can change color based on some property (doesn't matter what property) my question is, is there anyway to change the color of a shape once its already been drawn down on the canvas?
Ive tried adding all the square shapes to an array, both stamps and polygons, but it seems impossible to change the color of any of them once they have been drawn.
i know stamp doesn't work because its like a footprint of where the turtle was but is there any method at all that allows for this with polygons or some other method im not aware of?
I didn't add any snippets of code because its a pretty basic question and can be used for many things.
Upvotes: 1
Views: 19866
Reputation: 41872
Yes, you can do it. The key to this, and many complicated turtle problems, is using stamps. They are individually, or collectively, removable. And since they take on the shape of the turtle itself, they can be images or arbitrary polygons of any size or color you wish:
from turtle import Turtle, Screen
from random import randrange, choice
from collections import namedtuple
from math import ceil
GRID = 15 # GRID by GRID of squares
SIZE = 30 # each square is SIZE by SIZE
INCREASE = 1.5 # how much to lighten the square's color
WHITE = [255, 255, 255] # color at which we stop changing square
DELAY = 100 # time between calls to change() in milliseconds
DARK = 32 # range (ceil(INCREASE) .. DARK - 1) of dark colors
def change():
block = choice(blocks)
blocks.remove(block)
color = [min(int(primary * INCREASE), WHITE[i]) for i, primary in enumerate(block.color)] # lighten color
turtle.color(color)
turtle.setposition(block.position)
turtle.clearstamp(block.stamp)
stamp = turtle.stamp()
if color != WHITE:
blocks.append(Block(block.position, color, stamp)) # not white yet so keep changing this block
if blocks: # stop all changes if/when all blocks turn white
screen.ontimer(change, DELAY)
HALF_SIZE = SIZE // 2
screen = Screen()
screen.colormode(WHITE[0])
screen.register_shape("block", ((HALF_SIZE, -HALF_SIZE), (HALF_SIZE, HALF_SIZE), (-HALF_SIZE, HALF_SIZE), (-HALF_SIZE, -HALF_SIZE)))
screen.tracer(GRID ** 2) # ala @PyNuts
turtle = Turtle(shape="block", visible=False)
turtle.speed("fastest")
turtle.up()
Block = namedtuple('Block', ['position', 'color', 'stamp'])
blocks = list()
HALF_GRID = GRID // 2
for x in range(-HALF_GRID, HALF_GRID):
for y in range(-HALF_GRID, HALF_GRID):
turtle.goto(x * SIZE, y * SIZE)
color = [randrange(ceil(INCREASE), DARK) for primary in WHITE]
turtle.color(color)
blocks.append(Block(turtle.position(), color, turtle.stamp()))
screen.ontimer(change, DELAY)
screen.exitonclick()
Upvotes: 3
Reputation: 110218
The idea of the Turtle is that images, once drawn, become pixels, commited to the canvas - even though the Turtle commands themselves are "vector" commands, when one is talking about "vectorial" vs "raster" graphics.
That means the Turtle drawing context can't know about what is already drawn "by itself" - there is no way for you to reference an already drawn shape, or polygon and change its properties. (update: but for the stamps - the Turtle object does record information about those - thanks @cdlane)
What you'd have to do,in this case, is to annotate on your code data about any shapes you'd like to further modify - and them redraw them in someway, when you need it. In other words: you develop code for a vector graphics model -which allows you to do that.
However, you should note, that the tkinter Canvas widget, on top of which the Turtle runs (at least, I believe), is itself a Vector model - so it might be more appropriate for what you have in mind. Its documentation and way of working may be hard to grasp, though.
So, going back to "having your own Vector graphics implementation", you could take advantage of Python Object Orientation and data model to build "history-turtle" - which could record a log and replay blocks of commands within the same position context (but allowing for different color, width, etc, ... settings).
It is a bitty tricker done than said, but then, here is a sample one:
from turtle import Turtle
"""
Python Turtle with logging and replay capabilities
Author: João S. O. Bueno <[email protected]>
License: LGPL 3.0+
This implements HistoryTurtle - a subclass of
Python's turtle.Turtle wich features a simple
command history and new `replay` and `log`
methods -
`turtle.replay(<start>, <end>)` will reissue
the commands logged in those positions of the history,
but with the current Pen settings.
The optional kwarg `position_independent` can be passed
as `True` to `replay` so that the turtle's position
and heading are not restored to the ones recorded
along with the command.
https://gist.github.com/jsbueno/cb413e985747392c460f39cc138436bc
"""
class Command:
__slots__ = ( "method_name", "args", "kwargs", "pos", "heading")
def __init__(self, method_name, args=(),
kwargs=None, pos=None, heading=None):
self.method_name = method_name
self.args = args
self.kwargs = kwargs or {}
self.pos = pos
self.heading = heading
def __repr__(self):
return "<{0}> - {1}".format(self.pos, self.method_name)
class Instrumented:
def __init__(self, turtle, method):
self.turtle = turtle
self.method = method
def __call__(self, *args, **kwargs):
command = Command(self.method.__name__, args, kwargs,
self.pos(), self.heading())
result = self.method(*args, **kwargs)
if (self.method.__name__ not in self.turtle.methods_to_skip or
not kwargs.pop('skip_self', True)):
self.turtle.history.append(command)
return result
def pos(self):
return self.turtle._execute(Command('pos'), True)
def heading(self):
return self.turtle._execute(Command('heading'), True)
class HistoryTurtle(Turtle):
methods_to_skip = ('replay', '_execute', 'log')
def __init__(self, *args, **kw):
self._inited = False
super().__init__(*args, **kw)
self.history = []
self._replaying = [False]
self._inited = True
def __getattribute__(self, attr):
result = super().__getattribute__(attr)
if (not callable(result) or
attr.startswith('_') or
not self._inited or
self._replaying[-1]):
return result
return Instrumented(self, result)
def replay(self, start, end=None, *,
position_independent=False,
skip_self=True,
restore=True
):
results = []
self._replaying.append(True)
if restore:
pos, heading, state = self.pos(), self.heading(), self.pen()['pendown']
for command in self.history[start:end]:
results.append(
self._execute(command, position_independent))
if restore:
self.setpos(pos)
self.setheading(heading)
(self.pendown() if state else self.penup())
self._replaying.pop()
return results
def log(self, start=0, end=None):
for i, command in enumerate(self.history[start:end], start):
print(i, command)
def _execute(self, command, position_independent):
""" Execute a method without triggering the log
"""
# Warning: not thread-safe:
self._replaying.append(True)
method = getattr(self, command.method_name)
if not position_independent:
state = self.pen()['pendown']
self.setpos(command.pos)
self.setheading(command.heading)
(self.pendown() if state else self.penup())
result = method(*command.args, **command.kwargs)
self._replaying.pop()
return result
Upvotes: 0