Skip to content

Instantly share code, notes, and snippets.

@PardhavMaradani
Last active August 2, 2020 16:37
Show Gist options
  • Select an option

  • Save PardhavMaradani/881d79991484829cde0536657767faf2 to your computer and use it in GitHub Desktop.

Select an option

Save PardhavMaradani/881d79991484829cde0536657767faf2 to your computer and use it in GitHub Desktop.
Blog: Sliders
# https://pardhav-m.blogspot.com/2020/07/sliders.html
import turtle
# register square thumb shape
thumb_size = 7
screen = turtle.Screen()
screen.register_shape('thumb', ((-thumb_size, -thumb_size), (thumb_size, -thumb_size), (thumb_size, thumb_size), (-thumb_size, thumb_size)))
# Slider Class
class Slider(turtle.Turtle):
def __init__(self, id, x, y, length, min, max, step, initial_value, label, callback):
turtle.Turtle.__init__(self)
self.shape('thumb')
self.speed(0)
self.id = id
self.x = x
self.y = y
self.length = length
self.min = min
self.step = step
self.label = label
self.callback = callback
self.clicked = False
self.dragging = False
self.steps = (max - min) / step
# draw slider line
self.pu()
self.goto(x, y)
self.pd()
self.fd(length)
# turtle to write label text and value
self.lt = turtle.Turtle()
self.lt.speed(0)
self.lt.pu()
self.lt.goto(self.pos())
self.lt.fd(20)
self.lt.right(90)
self.lt.fd(thumb_size/2)
self.lt.ht()
# move thumb to initial position
self.bk(length)
initial_length = length * ((initial_value - min) / (max - min))
self.fd(initial_length)
self.value = initial_value
# update label
self.update_label()
# register mouse handlers
self.onclick(self.onclick_handler)
self.onrelease(self.onrelease_handler)
self.ondrag(self.ondrag_handler)
# write label text and value
def update_label(self):
self.lt.clear()
self.lt.write(self.label + ' = ' + str(self.value), font=("Arial", 10, "normal"))
# get value based on slider position
def get_value(self, x):
unit_value = (x - self.x) / self.length
v1 = unit_value * self.steps * self.step
v1 = int(v1 / self.step) * self.step
return self.min + v1
# onclick handler
def onclick_handler(self, x, y):
self.clicked = True
# onrelease handler
def onrelease_handler(self, x, y):
self.clicked = False
# ondrag handler
def ondrag_handler(self, x, y):
if not self.clicked:
return
if self.dragging:
return
# stop drag if mouse moves away in y direction
if abs(y - self.y) > 20:
self.clicked = False
self.dragging = False
self.callback(self.id, self.value)
return
self.dragging = True
# limit drag within the slider
if x < self.x:
x = self.x
if x > self.x + self.length:
x = self.x + self.length
# move thumb to new position
self.goto(x, self.y)
new_value = self.get_value(x)
# call the callback function if value changes
if new_value != self.value:
self.value = new_value
self.update_label()
self.callback(self.id, self.value)
self.update()
self.dragging = False
# https://pardhav-m.blogspot.com/2020/07/sliders.html
# https://trinket.io/embed/python/a46520b84c
# sliders
# Basic Slider
import turtle
from slider import Slider
# screen setup
screen = turtle.Screen()
wh = screen.window_height() # window height
ww = 1.5 * wh # window width
# trinket returns the same window width and height (hence square)
# set it to a rectangular area
screen.setup(ww, wh)
screen.tracer(0)
screen_radius = wh / 2
def handle_slider_update(id, value):
print(id, value)
x = -ww/2 + 20
y = wh/2 - 20
slider_length = screen_radius * 0.8
Slider(0, x, y, slider_length, 5, 50, 5, 25, 'value 1', handle_slider_update)
Slider(1, x, y - 30, slider_length, 0, 1, 0.05, 0.25, 'value 2', handle_slider_update)
screen.update()
# https://pardhav-m.blogspot.com/2020/07/sliders.html
import turtle
import random
import math
# screen setup
g_screen = turtle.Screen()
g_wh = g_screen.window_height()
g_ww = 1.5 * g_wh
g_screen.setup(g_ww, g_wh)
g_screen_radius = g_wh / 2
# other globals
g_b_side = g_screen_radius * 0.1
g_b_side2 = g_b_side / 2
g_pen_colors = ['black', 'blue', 'firebrick', 'green', 'saddle brown', 'orange red', 'indian red', 'medium violet red']
g_n_cps = 3
g_control_points = []
g_control_points_coords = []
g_n_steps = 50 # number of steps of t between [0, 1]
g_t = 0
g_t_step = 1 / g_n_steps
# turtle to draw connecting lines
g_dt = turtle.Turtle()
g_dt.speed(0)
g_dt.ht()
# turtle to draw bezier points
g_bt = turtle.Turtle()
g_bt.speed(0)
g_bt.ht()
g_bt.pensize(3)
g_bt.color('red')
# draw border
def draw_border():
t = turtle.Turtle()
t.speed(0)
t.pu()
t.goto(-g_ww/2, -g_wh/2)
t.pd()
for i in range(4):
t.fd(g_ww if i % 2 == 0 else g_wh)
t.left(90)
# t slider update
def update_t(t):
global g_t
g_t = t
draw_frame()
# n control points slider update
def update_n_control_points(n):
global g_n_cps
if n == g_n_cps:
return
diff = n - g_n_cps
if diff > 0:
for i in range(diff):
create_control_point(g_n_cps)
g_n_cps += 1
else:
for i in range(abs(diff)):
g_n_cps -= 1
cp = g_control_points.pop()
cp.clear()
cp.ht()
del cp
update_control_points()
# control point class
class ControlPoint(turtle.Turtle):
def __init__(self, num = 0, x = 0, y = 0):
turtle.Turtle.__init__(self)
self.num = num
self.clicked = False
self.dragging = False
self.is_playing = None
self.speed(0)
self.pu()
self.goto(x, y)
self.fd(10)
self.write("p" + str(self.num))
self.bk(10)
# click/release/drag handlers
self.onclick(self.onclick_handler)
self.onrelease(self.onrelease_handler)
self.ondrag(self.ondrag_handler)
# handle click
def onclick_handler(self, x, y):
self.clicked = True
# handle release
def onrelease_handler(self, x, y):
self.clicked = False
# handle drag
def ondrag_handler(self, x, y):
if not self.clicked or self.dragging:
return
self.dragging = True
self.clear()
self.goto(x, y)
self.fd(10)
self.write("p" + str(self.num))
self.bk(10)
update_control_points()
self.dragging = False
# create a control point
# i - control point number
def create_control_point(i):
mh = int(g_wh * 0.8)
x = random.randrange(0, mh) - g_wh/2
y = random.randrange(0, mh) - g_wh/2
cp = ControlPoint(i, x, y)
cp.shape('cp')
g_control_points.append(cp)
# create initial control points
def create_initial_control_points():
global g_control_points
# create control point shape
s = g_screen_radius * 0.025
g_screen.register_shape("cp", ((-s, -s), (s, -s), (s, s), (-s, s)))
# create control points
initial_cps = [(-g_ww/4, -g_wh/4), (0, g_wh/4), (g_ww/4, -g_wh/4)]
for i in range(len(initial_cps)):
x, y = initial_cps[i]
cp = ControlPoint(i, x, y)
cp.shape('cp')
g_control_points.append(cp)
# update control point coordinates (after drag)
def update_control_points():
global g_control_points_coords
g_control_points_coords = []
for cp in g_control_points:
g_control_points_coords.append(cp.pos())
draw_frame()
# recursive functiont to get bezier point
# cps - control points
# t - t value [0, 1]
# level - recursion level
# draw - whether to draw connecting lines
# dt - turtle to draw connecting lines
def get_bezier_point(cps, t, level, draw = False, dt = None):
# terminating condition
if len(cps) == 1:
return cps[0]
# create new control points (n - 1)
n_cps = []
for i, p0 in enumerate(cps):
if i == len(cps) - 1:
break
p1 = cps[i + 1]
x0, y0 = p0
x1, y1 = p1
x2 = x0 + t * (x1 - x0)
y2 = y0 + t * (y1 - y0)
n_cps.append((x2, y2))
# draw connecting lines if asked
if draw:
dt.pensize(2 - (0.5*level))
for i, v in enumerate(cps):
if i == 0:
dt.pu()
dt.color(g_pen_colors[level % len(g_pen_colors)])
dt.goto(v)
dt.dot(5)
if i == 0:
dt.pd()
# recursively call for n - 1 control points
return get_bezier_point(n_cps, t, level + 1, draw, dt)
# draw a frame
def draw_frame():
g_dt.clear()
bp = get_bezier_point(g_control_points_coords, g_t, 0, True, g_dt)
g_bt.clear()
t = 0
for i in range(g_n_steps + 1):
if t > g_t:
break
bp = get_bezier_point(g_control_points_coords, t, 0)
if t == 0:
g_bt.pu()
g_bt.goto(bp)
if t == 0:
g_bt.pd()
t += g_t_step
if t < 1:
g_bt.dot(10)
# update screen
g_screen.update()
# initial setup
def setup():
g_screen.tracer(0)
draw_border()
create_initial_control_points()
update_control_points()
# main
setup()
# https://pardhav-m.blogspot.com/2020/07/sliders.html
# https://trinket.io/embed/python/2572eb9d97
# Sliders
# Bézier curve animations
import turtle
from slider import Slider
from bezier_curve_animation import update_t, update_n_control_points
# handle slider updates
def handle_slider_update(id, value):
if id == 0:
update_t(value)
elif id == 1:
update_n_control_points(value)
screen = turtle.Screen()
wh = screen.window_height()
ww = 1.5 * wh
x = -ww/2 + 20
y = wh/2 - 20
screen_radius = wh / 2
default_t = 0.5
default_n_cps = 3
slider_length = screen_radius * 0.6
# create sliders
Slider(0, x, y, slider_length, 0, 1, 0.05, default_t, 't', handle_slider_update)
Slider(1, x, y - 30, slider_length, 3, 8, 1, default_n_cps, 'control_points', handle_slider_update)
# update with default slider values
update_t(default_t)
update_n_control_points(default_n_cps)
# https://pardhav-m.blogspot.com/2020/07/sliders.html
import turtle
import random
import math
# screen setup
g_screen = turtle.Screen()
g_wh = g_screen.window_height()
g_ww = 1.5 * g_wh
g_screen.setup(g_ww, g_wh)
g_screen_radius = g_wh / 2
# other globals
g_b_side = g_screen_radius * 0.1
g_b_side2 = g_b_side / 2
g_playing = True
g_pen_colors = ['black', 'blue', 'firebrick', 'green', 'saddle brown', 'orange red', 'indian red', 'medium violet red']
g_min_cps = 2
g_max_cps = 8
g_n_cps = 3
g_control_points = []
g_control_points_coords = []
g_n_steps = 50 # number of steps of t between [0, 1]
g_t = 0
g_t_step = 1 / g_n_steps
g_dt = [None] * g_max_cps
# draw border
def draw_border():
t = turtle.Turtle()
t.speed(0)
t.pu()
t.goto(-g_ww/2, -g_wh/2)
t.pd()
for i in range(4):
t.fd(g_ww if i % 2 == 0 else g_wh)
t.left(90)
# t slider update
def update_t(t):
global g_t
g_t = t
draw_frame()
# n control points slider update
def update_n_control_points(n):
global g_n_cps
if n == g_n_cps:
return
diff = n - g_n_cps
if diff > 0:
for i in range(diff):
create_control_point(g_n_cps)
g_n_cps += 1
else:
for i in range(abs(diff)):
g_n_cps -= 1
cp = g_control_points.pop()
cp.clear()
cp.ht()
del cp
update_control_points()
# clear, hide and penup a turtle
def clear_turtle(t):
t.clear()
t.ht()
t.pu()
# control point class
class ControlPoint(turtle.Turtle):
def __init__(self, num = 0, x = 0, y = 0):
turtle.Turtle.__init__(self)
self.num = num
self.clicked = False
self.dragging = False
self.is_playing = None
self.speed(0)
self.pu()
self.goto(x, y)
self.fd(10)
self.write("p" + str(self.num))
self.bk(10)
# click/release/drag handlers
self.onclick(self.onclick_handler)
self.onrelease(self.onrelease_handler)
self.ondrag(self.ondrag_handler)
# handle click
def onclick_handler(self, x, y):
self.clicked = True
# handle release
def onrelease_handler(self, x, y):
self.clicked = False
# handle drag
def ondrag_handler(self, x, y):
if not self.clicked or self.dragging:
return
self.dragging = True
self.clear()
self.goto(x, y)
self.fd(10)
self.write("p" + str(self.num))
self.bk(10)
update_control_points()
self.dragging = False
# create a control point
# i - control point number
def create_control_point(i):
mh = int(g_wh * 0.8)
x = random.randrange(0, mh) - g_wh/2
y = random.randrange(0, mh) - g_wh/2
cp = ControlPoint(i, x, y)
cp.shape('cp')
g_control_points.append(cp)
# create initial control points
def create_initial_control_points():
global g_control_points
# create control point shape
s = g_screen_radius * 0.025
g_screen.register_shape("cp", ((-s, -s), (s, -s), (s, s), (-s, s)))
# create control points
initial_cps = [(-g_ww/4, -g_wh/4), (0, g_wh/4), (g_ww/4, -g_wh/4)]
for i in range(len(initial_cps)):
x, y = initial_cps[i]
cp = ControlPoint(i, x, y)
cp.shape('cp')
g_control_points.append(cp)
# update control point coordinates (after drag)
def update_control_points():
global g_control_points_coords
g_control_points_coords = []
for cp in g_control_points:
g_control_points_coords.append(cp.pos())
draw_frame()
# recursive functiont to get bezier point
# cps - control points
# t - t value [0, 1]
# level - recursion level
def get_bezier_point(cps, t, level):
# terminating condition
if len(cps) == 1:
return cps[0]
# create new control points (n - 1)
n_cps = []
for i, p0 in enumerate(cps):
if i == len(cps) - 1:
break
p1 = cps[i + 1]
x0, y0 = p0
x1, y1 = p1
x2 = x0 + t * (x1 - x0)
y2 = y0 + t * (y1 - y0)
n_cps.append((x2, y2))
# recursively call for n - 1 control points
bp = get_bezier_point(n_cps, t, level + 1)
pt = cps[len(cps) - 1]
if level == 0:
pt = bp
dt = g_dt[0]
else:
dt = g_dt[g_n_cps - 1 - level]
dt.seth(dt.towards(pt))
dt.goto(pt)
if not dt.isvisible():
dt.st()
dt.pd()
return bp
# draw a frame
def draw_frame():
for i in range(g_max_cps):
clear_turtle(g_dt[i])
# redraw bezier curve if needed
t = 0
for i in range(g_n_steps + 1):
if t > g_t + g_t_step:
break
get_bezier_point(g_control_points_coords, t, 0)
t += g_t_step
# update screen
g_screen.update()
# initial setup
def setup():
global g_dt
g_screen.tracer(0)
draw_border()
for i in range(g_max_cps):
g_dt[i] = turtle.Turtle()
g_dt[i].speed(0)
if i == 0:
g_dt[i].color('red')
g_dt[i].pensize(3)
else:
g_dt[i].color(g_pen_colors[i % len(g_pen_colors)])
g_dt[i].pensize(2 - (0.5*i))
create_initial_control_points()
update_control_points()
# main
setup()
# https://pardhav-m.blogspot.com/2020/07/sliders.html
# https://trinket.io/embed/python/6a58cb2e48
# Sliders
# Bezier curves in pursuit
import turtle
from slider import Slider
from bezier_pursuit_curve_animation import update_t, update_n_control_points
# handle slider updates
def handle_slider_update(id, value):
if id == 0:
update_t(value)
elif id == 1:
update_n_control_points(value)
screen = turtle.Screen()
wh = screen.window_height()
ww = 1.5 * wh
x = -ww/2 + 20
y = wh/2 - 20
screen_radius = wh / 2
default_t = 0.5
default_n_cps = 3
slider_length = screen_radius * 0.6
# create sliders
Slider(0, x, y, slider_length, 0, 1, 0.05, default_t, 't', handle_slider_update)
Slider(1, x, y - 30, slider_length, 3, 8, 1, default_n_cps, 'control_points', handle_slider_update)
# update with default slider values
update_t(default_t)
update_n_control_points(default_n_cps)
# https://pardhav-m.blogspot.com/2020/07/sliders.html
import turtle
import math
import time
# screen setup
screen = turtle.Screen()
wh = screen.window_height() # window height
ww = 1.5 * wh # window width
# trinket returns the same window width and height (hence square)
# set it to a rectangular area
screen.setup(ww, wh)
screen_radius = wh / 2
top_clip_window = [(-ww/2, wh/2), (-ww/2, 0), (ww/2, 0), (ww/2, wh/2)] # top half
bottom_clip_window = [(-ww/2, 0), (-ww/2, -wh/2), (ww/2, -wh/2), (ww/2, 0)] # bottom half
# turtle setup
t = turtle.Turtle()
t.ht()
t.speed(0)
t.tracer(0)
# draw a border
def draw_border():
t = turtle.Turtle()
t.ht()
t.speed(0)
t.pu()
t.goto(-ww/2, -wh/2)
t.pd()
t.goto(ww/2, -wh/2)
t.goto(ww/2, wh/2)
t.goto(-ww/2, wh/2)
t.goto(-ww/2, -wh/2)
# regular polygon shape functions from https://www.mathsisfun.com/geometry/regular-polygons.html
def shape_radius(sides, length):
return length / (2 * math.sin(math.pi / sides))
def shape_apothem(sides, length):
return shape_radius(sides, length) * math.cos(math.pi / sides)
def shape_length(sides, radius):
return 2 * radius * math.sin(math.pi / sides)
# get inner polygon to draw
# sides - number of sides
# cx, cy - center
# length - length of side
# xangle - angle wrt x axis
def get_polygon(sides, cx, cy, length, xangle):
radius = shape_radius(sides, length)
t.pu()
t.goto(cx, cy)
t.seth(270) # point down
t.right(math.degrees(math.pi / sides))
t.fd(radius)
sx, sy = t.pos() # left most starting point
# get the rotated starting point
# see https://stackoverflow.com/questions/2259476/rotating-a-point-about-another-point-2d
sx -= cx
sy -= cy
s = math.sin(math.radians(xangle))
c = math.cos(math.radians(xangle))
rsx = cx + (sx * c - sy * s)
rsy = cy + (sx * s + sy * c)
t.goto(rsx, rsy) # goto rotated starting point
t.seth(xangle) # rotate turtle to start angle
# draw each side
polygon = []
for s in range(sides):
polygon.append(t.pos())
t.fd(length)
t.left(360 / sides)
t.goto(cx, cy)
t.pd()
return polygon
# fill a polygon
# polygon - vertices of polygon to fill
# color - fill color
# pen_size - pen size to draw outline
# index - polygon index
# checkerboard - whether to fill alternatively
def fill_polygon(polygon, color, pen_size):
# first fill white
t.pensize(1)
t.color(color)
for i, v in enumerate(polygon):
if i == 0:
t.pu()
t.goto(v)
t.pd()
t.begin_fill()
else:
t.goto(v)
t.end_fill()
# now draw outline except intersecting line on x axis
t.pensize(pen_size)
t.color('black')
for i, v1 in enumerate(polygon):
v2 = polygon[(i + 1) % len(polygon)]
if i == 0:
t.pu()
t.goto(v1)
t.pd()
_, y1 = v1
_, y2 = v2
if y1 == 0 and y2 == 0:
t.pu()
t.goto(v1)
t.goto(v2)
if y1 == 0 and y2 == 0:
t.pd()
# get intersecting polygon
# Code from: https://rosettacode.org/wiki/Sutherland-Hodgman_polygon_clipping#Python
# Referred from: https://stackoverflow.com/questions/37555770/control-shape-overlap-in-pythons-turtle
# subjectPolygon - polygon to be clipped
# clipPolygon - clipping window
def clip(subjectPolygon, clipPolygon):
def inside(p):
return(cp2[0]-cp1[0])*(p[1]-cp1[1]) > (cp2[1]-cp1[1])*(p[0]-cp1[0])
def computeIntersection():
dc = [ cp1[0] - cp2[0], cp1[1] - cp2[1] ]
dp = [ s[0] - e[0], s[1] - e[1] ]
n1 = cp1[0] * cp2[1] - cp1[1] * cp2[0]
n2 = s[0] * e[1] - s[1] * e[0]
n3 = 1.0 / (dc[0] * dp[1] - dc[1] * dp[0])
return ((n1*dp[0] - n2*dc[0]) * n3, (n1*dp[1] - n2*dc[1]) * n3)
outputList = subjectPolygon
cp1 = clipPolygon[-1]
for clipVertex in clipPolygon:
cp2 = clipVertex
inputList = outputList
outputList = []
s = inputList[-1]
for subjectVertex in inputList:
e = subjectVertex
if inside(e):
if not inside(s):
outputList.append(computeIntersection())
outputList.append(e)
elif inside(s):
outputList.append(computeIntersection())
s = e
cp1 = cp2
return(outputList)
# draw on outer polygon
# half - which half of the screen
# o_sides - outer sides
# o_length - outer side length
# i_sides - inner sides
# i_length - inner side length
# i_p_m - inner polygons multiple
# twists - number of twists
# ra - base rotate angle wrt x axis
# pen_size - pen size
# checkerboard - whether to fill alternatively
def draw_on_outer_polygon(half, o_sides, o_length, i_sides, i_length, i_p_m, twists, ra, pen_size, checkerboard):
o_radius = shape_radius(o_sides, o_length)
i_radius = shape_radius(i_sides, i_length)
t.pu()
t.goto(ww/6, 0)
t.seth(270) # point down
t.right(math.degrees(math.pi / o_sides))
t.fd(o_radius)
t.seth(0) # rotate turtle to start angle
# skip so we start on correct side
skip = 0 if half == 'top' else math.ceil(o_sides / 2)
for i in range(skip):
t.fd(o_length)
t.left(360 / o_sides)
t.pd()
# draw along each side of outer polygon
for s in range(o_sides):
delta = o_length / i_p_m
delta_angle = ((360 / i_sides) / i_p_m) * twists
# draw each inner polygon
for i in range(i_p_m):
t.pu()
t.fd(delta)
t.pd()
fillcolor = 'white'
if checkerboard:
fillcolor = 'white' if i % 2 == 0 else 'gray'
# save starting center
cx, cy = t.pos()
sh = t.heading()
# check if this polygon will be visible or needs intersection
visible = False
should_check_intersection = False
if half == 'top':
if cy > i_radius:
visible = True
elif cy >= -i_radius:
should_check_intersection = True
else:
if cy < -i_radius:
visible = True
elif cy <= i_radius:
should_check_intersection = True
# get the polygon only if will be drawn
if visible or should_check_intersection:
polygon = get_polygon(i_sides, cx, cy, i_length, ra + (delta_angle * i))
else:
continue
# draw and fill inner polygon
if visible:
fill_polygon(polygon, fillcolor, pen_size)
elif should_check_intersection:
ip = None
try:
clip_window = top_clip_window if half == 'top' else bottom_clip_window
ip = clip(polygon, clip_window)
except (IndexError, ZeroDivisionError):
pass
if ip != None:
fill_polygon(ip, fillcolor, pen_size)
# go back to starting center
t.pu()
t.goto(cx, cy)
t.seth(sh)
t.pd()
t.left(360 / o_sides)
# draw polygonal stack
def draw_polygonal_stack(
outer_sides,
inner_sides,
inner_polygons_multiple,
inner_length_factor,
twists,
pen_size,
screen_radius_factor,
checkerboard):
# calculate radius and lengths wrt screen size
outer_radius = (screen_radius * screen_radius_factor) / (1 + inner_length_factor * math.cos(math.pi / outer_sides))
outer_length = shape_length(outer_sides, outer_radius)
inner_radius = shape_apothem(outer_sides, outer_length)
inner_length = shape_length(inner_sides, inner_radius) * inner_length_factor
# draw stack
t.clear()
# draw top half
draw_on_outer_polygon('top', outer_sides, outer_length, inner_sides, inner_length, inner_polygons_multiple, twists, 0, pen_size, checkerboard)
# draw bottom half
draw_on_outer_polygon('bottom', outer_sides, outer_length, inner_sides, inner_length, inner_polygons_multiple, twists, 0, pen_size, checkerboard)
t.update()
# main
draw_border()
# https://pardhav-m.blogspot.com/2020/07/sliders.html
# https://trinket.io/embed/python/dc8c7ec91b
# Sliders
# Polygonal Stacks
import turtle
from slider import Slider
from polygonal_stack import draw_polygonal_stack
outer_sides = 6
inner_sides = 4
inner_polygons_multiple = 30
inner_length_factor = 0.2
twists = 1
pen_size = 1
screen_radius_factor = 0.85
checkerboard = False
animate = False
step_angle = 2
# draw polygonal stack
def draw():
draw_polygonal_stack(outer_sides, inner_sides, inner_polygons_multiple, inner_length_factor, twists, pen_size, screen_radius_factor, checkerboard)
# handle slider updates
def handle_slider_update(id, value):
global outer_sides
global inner_sides
global inner_polygons_multiple
global inner_length_factor
global twists
global pen_size
global screen_radius_factor
global checkerboard
# set variable based on slider id
if id == 0:
outer_sides = value
elif id == 1:
inner_sides = value
elif id == 2:
inner_polygons_multiple = value
elif id == 3:
inner_length_factor = value
elif id == 4:
twists = value
elif id == 5:
pen_size = value
elif id == 6:
screen_radius_factor = value
elif id == 7:
checkerboard = True if value == 1 else False
# draw stack
draw()
screen = turtle.Screen()
wh = screen.window_height()
ww = 1.5 * wh
x = -ww/2 + 20
y = wh/2 - 20
screen_radius = wh / 2
# create sliders
slider_length = screen_radius * 0.6
Slider(0, x, y, slider_length, 3, 20, 1, outer_sides, 'outer_sides', handle_slider_update)
Slider(1, x, y - 30, slider_length, 2, 10, 1, inner_sides, 'inner_sides', handle_slider_update)
Slider(2, x, y - 60, slider_length, 1, 100, 1, inner_polygons_multiple, 'n_polygons', handle_slider_update)
Slider(3, x, y - 90, slider_length, 0.1, 1, 0.1, inner_length_factor, 'aperture', handle_slider_update)
Slider(4, x, y - 120, slider_length, 1, 100, 1, twists, 'twists', handle_slider_update)
Slider(5, x, y - 150, slider_length, 1, 5, 1, pen_size, 'pen_size', handle_slider_update)
Slider(6, x, y - 180, slider_length, 0.1, 1, 0.05, screen_radius_factor, 'size', handle_slider_update)
Slider(7, x, y - 210, screen_radius * 0.15, 0, 1, 1, 0, 'checkerboard', handle_slider_update)
draw()
# https://pardhav-m.blogspot.com/2020/07/sliders.html
import turtle
import math
import time
# screen setup
screen = turtle.Screen()
wh = screen.window_height() # window height
ww = 1.5 * wh # window width
# trinket returns the same window width and height (hence square)
# set it to a rectangular area
screen.setup(ww, wh)
screen_radius = wh / 2
screen_radius_factor = 0.95
top_clip_window = [(-ww/2, wh/2), (-ww/2, 0), (ww/2, 0), (ww/2, wh/2)] # top half
bottom_clip_window = [(-ww/2, 0), (-ww/2, -wh/2), (ww/2, -wh/2), (ww/2, 0)] # bottom half
# turtle setup
t = turtle.Turtle()
t.ht()
t.speed(0)
t.tracer(0)
# draw a border
def draw_border():
t = turtle.Turtle()
t.pu()
t.goto(-ww/2, -wh/2)
t.pd()
t.goto(ww/2, -wh/2)
t.goto(ww/2, wh/2)
t.goto(-ww/2, wh/2)
t.goto(-ww/2, -wh/2)
# regular polygon shape functions from https://www.mathsisfun.com/geometry/regular-polygons.html
def shape_radius(sides, length):
return length / (2 * math.sin(math.pi / sides))
def shape_apothem(sides, length):
return shape_radius(sides, length) * math.cos(math.pi / sides)
def shape_length(sides, radius):
return 2 * radius * math.sin(math.pi / sides)
# get inner polygon to draw
# sides - number of sides
# cx, cy - center
# length - length of side
# xangle - angle wrt x axis
def get_polygon(sides, cx, cy, length, xangle):
radius = shape_radius(sides, length)
t.pu()
t.goto(cx, cy)
t.seth(270) # point down
t.right(math.degrees(math.pi / sides))
t.fd(radius)
sx, sy = t.pos() # left most starting point
# get the rotated starting point
# see https://stackoverflow.com/questions/2259476/rotating-a-point-about-another-point-2d
sx -= cx
sy -= cy
s = math.sin(math.radians(xangle))
c = math.cos(math.radians(xangle))
rsx = cx + (sx * c - sy * s)
rsy = cy + (sx * s + sy * c)
t.goto(rsx, rsy) # goto rotated starting point
t.seth(xangle) # rotate turtle to start angle
# draw each side
polygon = []
for s in range(sides):
polygon.append(t.pos())
t.fd(length)
t.left(360 / sides)
t.goto(cx, cy)
t.pd()
return polygon
# fill a polygon
# polygon - vertices of polygon to fill
# color - fill color
# pen_size - pen size to draw outline
def fill_polygon(polygon, color, pen_size):
# first fill white
t.pensize(1)
t.color(color)
for i, v in enumerate(polygon):
if i == 0:
t.pu()
t.goto(v)
t.pd()
t.begin_fill()
else:
t.goto(v)
t.end_fill()
# now draw outline except intersecting line on x axis
t.pensize(pen_size)
t.color('black')
for i, v1 in enumerate(polygon):
v2 = polygon[(i + 1) % len(polygon)]
if i == 0:
t.pu()
t.goto(v1)
t.pd()
_, y1 = v1
_, y2 = v2
if y1 == 0 and y2 == 0:
t.pu()
t.goto(v1)
t.goto(v2)
if y1 == 0 and y2 == 0:
t.pd()
# get intersecting polygon
# Code from: https://rosettacode.org/wiki/Sutherland-Hodgman_polygon_clipping#Python
# Referred from: https://stackoverflow.com/questions/37555770/control-shape-overlap-in-pythons-turtle
# subjectPolygon - polygon to be clipped
# clipPolygon - clipping window
def clip(subjectPolygon, clipPolygon):
def inside(p):
return(cp2[0]-cp1[0])*(p[1]-cp1[1]) > (cp2[1]-cp1[1])*(p[0]-cp1[0])
def computeIntersection():
dc = [ cp1[0] - cp2[0], cp1[1] - cp2[1] ]
dp = [ s[0] - e[0], s[1] - e[1] ]
n1 = cp1[0] * cp2[1] - cp1[1] * cp2[0]
n2 = s[0] * e[1] - s[1] * e[0]
n3 = 1.0 / (dc[0] * dp[1] - dc[1] * dp[0])
return ((n1*dp[0] - n2*dc[0]) * n3, (n1*dp[1] - n2*dc[1]) * n3)
outputList = subjectPolygon
cp1 = clipPolygon[-1]
for clipVertex in clipPolygon:
cp2 = clipVertex
inputList = outputList
outputList = []
s = inputList[-1]
for subjectVertex in inputList:
e = subjectVertex
if inside(e):
if not inside(s):
outputList.append(computeIntersection())
outputList.append(e)
elif inside(s):
outputList.append(computeIntersection())
s = e
cp1 = cp2
return(outputList)
# draw on outer circle
# half - which half of the screen
# o_radius - outer circle radius
# i_sides - inner sides
# i_length - inner side length
# i_p_m - inner polygons multiple
# twists - number of twists
# ra - base rotate angle wrt x axis
# pen_size - pen size
def draw_on_outer_circle(half, o_radius, i_sides, i_length, i_p_m, twists, ra, pen_size, checkerboard):
i_radius = shape_radius(i_sides, i_length)
t.pu()
t.goto(ww/6, 0)
t.seth(270) # point down
t.fd(o_radius)
t.left(90)
# skip so we start on correct side
skip = 0 if half == 'top' else 1
for i in range(skip):
t.circle(o_radius, 180)
t.pd()
n = 4 * i_p_m
fixed_angle = (90 / i_sides) / i_p_m
# draw each inner polygon
for i in range(n):
t.pu()
t.circle(o_radius, -(360 / n))
t.pd()
fillcolor = 'white'
if checkerboard:
fillcolor = 'white' if i % 2 == 0 else 'gray'
# save starting center
cx, cy = t.pos()
sh = t.heading()
# check if this polygon will be visible or needs intersection
visible = False
should_check_intersection = False
if half == 'top':
if cy > i_radius:
visible = True
elif cy >= -i_radius:
should_check_intersection = True
else:
if cy < -i_radius:
visible = True
elif cy <= i_radius:
should_check_intersection = True
# get the polygon only if will be drawn
if visible or should_check_intersection:
ii = i
if half == 'bottom':
ii += (i_p_m * 2)
delta_angle = math.degrees(ii / n * 2 * math.pi) + (fixed_angle * ii * twists)
polygon = get_polygon(i_sides, cx, cy, i_length, -(ra + delta_angle))
else:
continue
# draw and fill inner polygon
if visible:
fill_polygon(polygon, fillcolor, pen_size)
elif should_check_intersection:
ip = None
try:
clip_window = top_clip_window if half == 'top' else bottom_clip_window
ip = clip(polygon, clip_window)
except (IndexError, ZeroDivisionError):
pass
if ip != None:
fill_polygon(ip, fillcolor, pen_size)
# go back to starting center
t.pu()
t.goto(cx, cy)
t.seth(sh)
t.pd()
# draw circular stack
def draw_circular_stack(
inner_sides,
inner_polygons_multiple,
inner_length_factor,
twists,
pen_size,
screen_radius_factor,
checkerboard):
# calculate radius and lengths wrt screen size
outer_radius = (screen_radius * screen_radius_factor) / (1 + inner_length_factor)
inner_length = shape_length(inner_sides, outer_radius) * inner_length_factor
# draw stack and animate if asked
t.clear()
# draw top half
draw_on_outer_circle('top', outer_radius, inner_sides, inner_length, inner_polygons_multiple, twists, 0, pen_size, checkerboard)
# draw bottom half
draw_on_outer_circle('bottom', outer_radius, inner_sides, inner_length, inner_polygons_multiple, twists, 0, pen_size, checkerboard)
t.update()
# main
draw_border()
# https://pardhav-m.blogspot.com/2020/07/sliders.html
# https://trinket.io/embed/python/f56dd6d317
# Sliders
# Circular Stacks
import turtle
from slider import Slider
from circular_stack import draw_circular_stack
inner_sides = 4
inner_polygons_multiple = 30
inner_length_factor = 0.2
twists = 6
pen_size = 1
screen_radius_factor = 0.85
checkerboard = False
# draw circular stack
def draw():
draw_circular_stack(inner_sides, inner_polygons_multiple, inner_length_factor, twists, pen_size, screen_radius_factor, checkerboard)
# handle slider updates
def handle_slider_update(id, value):
global inner_sides
global inner_polygons_multiple
global inner_length_factor
global twists
global pen_size
global screen_radius_factor
global checkerboard
# set variable based on slider id
if id == 0:
inner_sides = value
elif id == 1:
inner_polygons_multiple = value
elif id == 2:
inner_length_factor = value
elif id == 3:
twists = value
elif id == 4:
pen_size = value
elif id == 5:
screen_radius_factor = value
elif id == 6:
checkerboard = True if value == 1 else False
# draw stack
draw()
screen = turtle.Screen()
wh = screen.window_height()
ww = 1.5 * wh
x = -ww/2 + 20
y = wh/2 - 20
screen_radius = wh / 2
# create sliders
slider_length = screen_radius * 0.6
Slider(0, x, y, slider_length, 2, 10, 1, inner_sides, 'inner_sides', handle_slider_update)
Slider(1, x, y - 30, slider_length, 1, 100, 1, inner_polygons_multiple, 'n_polygons', handle_slider_update)
Slider(2, x, y - 60, slider_length, 0.1, 1, 0.1, inner_length_factor, 'aperture', handle_slider_update)
Slider(3, x, y - 90, slider_length, 1, 100, 1, twists, 'twists', handle_slider_update)
Slider(4, x, y - 120, slider_length, 1, 5, 1, pen_size, 'pen_size', handle_slider_update)
Slider(5, x, y - 150, slider_length, 0.1, 1, 0.05, screen_radius_factor, 'size', handle_slider_update)
Slider(6, x, y - 180, screen_radius * 0.15, 0, 1, 1, 0, 'checkerboard', handle_slider_update)
draw()
# https://pardhav-m.blogspot.com/2020/07/sliders.html
import turtle
import math
screen = turtle.Screen()
wh = screen.window_height() # window height
ww = 1.5 * wh # window width
# trinket returns the same window width and height (hence square)
# set it to a rectangular area
screen.setup(ww, wh)
screen_radius = wh / 2
t = turtle.Turtle()
t.ht()
t.speed(0)
t.tracer(0)
# draw a border
def draw_border():
t = turtle.Turtle()
t.pu()
t.goto(-ww/2, -wh/2)
t.pd()
t.goto(ww/2, -wh/2)
t.goto(ww/2, wh/2)
t.goto(-ww/2, wh/2)
t.goto(-ww/2, -wh/2)
# shape radius from https://www.mathsisfun.com/geometry/regular-polygons.html
def shape_radius(sides, length):
return length / (2 * math.sin(math.pi / sides))
# shape side from https://www.mathsisfun.com/geometry/regular-polygons.html
def shape_side(sides, radius):
return 2 * radius * math.sin(math.pi / sides)
# get current position and heading
def get_position():
return t.pos(), t.heading()
# restore a saved position
def restore_position(pos, h):
t.goto(pos)
t.seth(h)
# draw polygon checkerboard
# sides - number of sides
# cx, cy - center around which to draw this polygon
# length - length of each side
# rows - number of inside polygons
# cols - number of divisions
# draw_cols - whether to draw the dividing lines
# cb - whether to fill checkerboard pattern
# curved - whether to draw straight or curved side
def draw_polygon_cb(sides, cx, cy, length, rows, cols, draw_cols = True, cb = True, curved = False):
ea = 360 / sides
radius = shape_radius(sides, length)
angle = math.degrees(math.pi / sides)
# get left most starting point
t.clear()
t.pu()
t.goto(cx, cy)
t.seth(270) # point down
t.right(angle)
t.fd(radius)
t.seth(0)
sx, sy = t.pos() # left most starting point
t.pd()
# radius delta
rd = radius / rows
# For each side
for s in range(sides):
for r in range(rows):
# go to left most starting point for each r
t.pu()
t.goto(cx, cy)
t.seth(270 + (s * ea))
t.right(angle)
t.fd(r * rd)
spos, sh = get_position()
t.left(90 + angle)
t.pd()
# calculate short and long side lengths and deltas
s_side_length = shape_side(sides, r * rd)
s_d = s_side_length / cols
l_side_length = shape_side(sides, (r + 1) * rd)
l_d = l_side_length / cols
# angle for each division
a = 180 / cols
# calculate four positions of the quad
for c in range(cols):
t.pu()
restore_position(spos, sh)
# pos1
if curved:
t.left(angle)
t.circle((s_side_length / 2), c * a)
else:
t.left(90 + angle)
t.fd(c * s_d)
pos1, h1 = get_position()
# pos4
if curved:
t.circle((s_side_length / 2), a)
else:
t.fd(s_d)
pos4, h4 = get_position()
# pos2
restore_position(spos, sh)
t.fd(rd)
if curved:
t.left(angle)
t.circle((l_side_length / 2), c * a)
else:
t.left(90 + angle)
t.fd(c * l_d)
pos2, h2 = get_position()
# pos3
if curved:
t.circle((l_side_length / 2), a)
else:
t.fd(l_d)
pos3, h3 = get_position()
t.setpos(pos1)
t.pd()
# Determine whether to fill
fill = False
if (r % 2 == 0 and c % 2 == 0) or (r % 2 == 1 and c % 2 == 1):
fill = True
if cb and fill:
t.begin_fill()
t.fillcolor('gray')
# Connect the positions of the quad
# pos1 -> pos2
if not draw_cols:
t.pu()
t.goto(pos2)
t.pd()
# pos2 -> pos3
if curved:
t.seth(h2)
t.circle(l_side_length / 2, a)
else:
t.goto(pos3)
# pos3 -> pos4
if not draw_cols:
t.pu()
t.goto(pos4)
t.pd()
# pos4 -> pos1
if curved:
t.seth(h4)
t.right(180)
t.circle(-s_side_length / 2, a)
else:
t.goto(pos1)
if fill:
t.end_fill()
t.update()
# main
draw_border()
# https://pardhav-m.blogspot.com/2020/07/sliders.html
# https://trinket.io/embed/python/4cc6051797
# Sliders
# Polygonal Checkerboards
import turtle
from slider import Slider
from checkerboards import draw_polygon_cb
screen = turtle.Screen()
wh = screen.window_height()
ww = 1.5 * wh
screen_radius = wh / 2
length_factor = 0.4
sides = 6
rows = 6
cols = 6
draw_cols = True
checkerboard = True
curved = True
# draw polygon checkerboard
def draw():
draw_polygon_cb(sides, ww/6, 0, length_factor * screen_radius, rows, cols, draw_cols, checkerboard, curved)
# handle slider updates
def handle_slider_update(id, value):
global length_factor
global sides
global rows
global cols
global draw_cols
global checkerboard
global curved
# set variables based on slider id
if id == 0:
length_factor = value
elif id == 1:
sides = value
elif id == 2:
rows = value
elif id == 3:
cols = value
elif id == 4:
draw_cols = True if value == 1 else False
elif id == 5:
checkerboard = True if value == 1 else False
elif id == 6:
curved = True if value == 1 else False
# draw polygon
draw()
x = -ww/2 + 20
y = wh/2 - 20
# create sliders
slider_length = screen_radius * 0.6
Slider(0, x, y, slider_length, 0.1, 1, 0.05, length_factor, 'size', handle_slider_update)
Slider(1, x, y - 30, slider_length, 2, 30, 1, sides, 'sides', handle_slider_update)
Slider(2, x, y - 60, slider_length, 2, 30, 1, rows, 'rows', handle_slider_update)
Slider(3, x, y - 90, slider_length, 2, 30, 2, cols, 'cols', handle_slider_update)
Slider(4, x, y - 120, screen_radius * 0.15, 0, 1, 1, 1, 'draw_cols', handle_slider_update)
Slider(5, x, y - 150, screen_radius * 0.15, 0, 1, 1, 1, 'checkerboard', handle_slider_update)
Slider(6, x, y - 180, screen_radius * 0.15, 0, 1, 1, 1, 'curved', handle_slider_update)
draw()
# https://pardhav-m.blogspot.com/2020/07/sliders.html
import turtle
import random
import math
# screen setup
g_screen = turtle.Screen()
g_wh = g_screen.window_height()
g_ww = 1.5 * g_wh
g_screen.setup(g_ww, g_wh)
g_screen_radius = g_wh / 2
# other globals
g_b_side = g_screen_radius * 0.1
g_b_side2 = g_b_side / 2
g_pen_colors = ['black', 'blue', 'firebrick', 'green', 'saddle brown', 'orange red', 'indian red', 'medium violet red']
g_n_cps = 3
g_control_points = []
g_control_points_coords = []
g_n_steps = 50 # number of steps of t between [0, 1]
g_t = 0
g_t_step = 1 / g_n_steps
g_levels = 5
# turtle to draw connecting lines
g_dt = turtle.Turtle()
g_dt.speed(0)
g_dt.ht()
# draw border
def draw_border():
t = turtle.Turtle()
t.speed(0)
t.pu()
t.goto(-g_ww/2, -g_wh/2)
t.pd()
for i in range(4):
t.fd(g_ww if i % 2 == 0 else g_wh)
t.left(90)
# t slider update
def update_t(t):
global g_t
g_t = t
draw_frame()
# n control points slider update
def update_n_control_points(n):
global g_n_cps
if n == g_n_cps:
return
diff = n - g_n_cps
if diff > 0:
for i in range(diff):
create_control_point(g_n_cps)
g_n_cps += 1
else:
for i in range(abs(diff)):
g_n_cps -= 1
cp = g_control_points.pop()
cp.clear()
cp.ht()
del cp
update_control_points()
# level update
def update_levels(levels):
global g_levels
g_levels = levels
draw_frame()
# control point class
class ControlPoint(turtle.Turtle):
def __init__(self, num = 0, x = 0, y = 0):
turtle.Turtle.__init__(self)
self.num = num
self.clicked = False
self.dragging = False
self.is_playing = None
self.speed(0)
self.pu()
self.goto(x, y)
self.fd(10)
self.write("p" + str(self.num))
self.bk(10)
# click/release/drag handlers
self.onclick(self.onclick_handler)
self.onrelease(self.onrelease_handler)
self.ondrag(self.ondrag_handler)
# handle click
def onclick_handler(self, x, y):
self.clicked = True
# handle release
def onrelease_handler(self, x, y):
self.clicked = False
# handle drag
def ondrag_handler(self, x, y):
if not self.clicked or self.dragging:
return
self.dragging = True
self.clear()
self.goto(x, y)
self.fd(10)
self.write("p" + str(self.num))
self.bk(10)
update_control_points()
self.dragging = False
# create a control point
# i - control point number
def create_control_point(i):
mh = int(g_wh * 0.8)
x = random.randrange(0, mh) - g_wh/2
y = random.randrange(0, mh) - g_wh/2
cp = ControlPoint(i, x, y)
cp.shape('cp')
g_control_points.append(cp)
# create initial control points
def create_initial_control_points():
global g_control_points
# create control point shape
s = g_screen_radius * 0.025
g_screen.register_shape("cp", ((-s, -s), (s, -s), (s, s), (-s, s)))
# create control points
initial_cps = [(-g_ww/4, -g_wh/4), (0, g_wh/4), (g_ww/4, -g_wh/4)]
for i in range(len(initial_cps)):
x, y = initial_cps[i]
cp = ControlPoint(i, x, y)
cp.shape('cp')
g_control_points.append(cp)
# update control point coordinates (after drag)
def update_control_points():
global g_control_points_coords
g_control_points_coords = []
for cp in g_control_points:
g_control_points_coords.append(cp.pos())
g_control_points_coords.append(g_control_points_coords[0])
draw_frame()
# recursive functiont to get bezier point
# cps - control points
# t - t value [0, 1]
# level - recursion level
# draw - whether to draw connecting lines
# dt - turtle to draw connecting lines
def get_bezier_point(cps, t, level, draw = False, dt = None):
# terminating condition
if level > g_levels:
return
# create new control points (n - 1)
n_cps = []
for i, p0 in enumerate(cps):
if i == len(cps) - 1:
i = 0
p1 = cps[i + 1]
x0, y0 = p0
x1, y1 = p1
x2 = x0 + t * (x1 - x0)
y2 = y0 + t * (y1 - y0)
n_cps.append((x2, y2))
# draw connecting lines if asked
if draw:
dt.pensize(2 - (0.5*level))
for i, v in enumerate(cps):
if i == 0:
dt.pu()
dt.color(g_pen_colors[level % len(g_pen_colors)])
dt.goto(v)
# dt.dot(5)
if i == 0:
dt.pd()
# recursively call for n - 1 control points
get_bezier_point(n_cps, t, level + 1, draw, dt)
# draw a frame
# redraw_bt - whether to redraw bezier curve up to current t
def draw_frame():
g_dt.clear()
get_bezier_point(g_control_points_coords, g_t, 0, True, g_dt)
# update screen
g_screen.update()
# initial setup
def setup():
g_screen.tracer(0)
draw_border()
create_initial_control_points()
update_control_points()
# main
setup()
# https://pardhav-m.blogspot.com/2020/07/sliders.html
# https://trinket.io/embed/python/d62f5a5c93
# Sliders
# Polygon Whirls
import turtle
from slider import Slider
from polygon_whirls import update_t, update_n_control_points, update_levels
# handle slider updates
def handle_slider_update(id, value):
if id == 0:
update_t(value)
elif id == 1:
update_n_control_points(value)
elif id == 2:
update_levels(value)
screen = turtle.Screen()
wh = screen.window_height()
ww = 1.5 * wh
x = -ww/2 + 20
y = wh/2 - 20
screen_radius = wh / 2
default_t = 0.05
default_n_cps = 3
default_levels = 25
slider_length = screen_radius * 0.6
# create sliders
Slider(0, x, y, slider_length, 0, 1, 0.05, default_t, 't', handle_slider_update)
Slider(1, x, y - 30, slider_length, 3, 8, 1, default_n_cps, 'control_points', handle_slider_update)
Slider(2, x, y - 60, slider_length, 1, 100, 1, default_levels, 'levels', handle_slider_update)
# update with default slider values
update_t(default_t)
update_n_control_points(default_n_cps)
update_levels(default_levels)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment