Last active
August 2, 2020 16:37
-
-
Save PardhavMaradani/881d79991484829cde0536657767faf2 to your computer and use it in GitHub Desktop.
Blog: Sliders
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # 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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # 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() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # 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() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # 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) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # 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() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # 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) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # 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() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # 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() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # 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() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # 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() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # 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() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # 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() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # 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() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # 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