diff --git a/2019/sketch_190509a/gif_exporter.py b/2019/sketch_190509a/gif_exporter.py new file mode 100644 index 00000000..dbc50ae7 --- /dev/null +++ b/2019/sketch_190509a/gif_exporter.py @@ -0,0 +1,43 @@ +""" +Alexandre B A Villares http://abav.lugaralgum.com - GPL v3 + +A helper for the Processing gifAnimation library (https://github.com/jordanorelli) +ported to Processing 3 by 01010101 (https://github.com/01010101) +Download the library from https://github.com/01010101/GifAnimation/archive/master.zip +This helper was inspired by an example by Art Simon https://github.com/APCSPrinciples/AnimatedGIF/ + +Put at the start of your sketch: + add_library('gifAnimation') + from gif_exporter import gif_export +and at the end of draw(): + gif_export(GifMaker) +""" + +def gif_export(GifMaker, # gets a reference to the library + filename="exported", # .gif will be added + repeat=0, # 0 makes it an "endless" animation + quality=182, # quality range 0 - 255 + delay=170, # this is quick + frames=0): # 0 will stop on keyPressed or frameCount >= 100000 + global gifExporter + try: + gifExporter + except NameError: + gifExporter = GifMaker(this, filename + ".gif") + gifExporter.setRepeat(repeat) + gifExporter.setQuality(quality) + gifExporter.setDelay(delay) + gif_export._frame = frameCount + print("gif start") + + gifExporter.addFrame() + + if (frames == 0 and keyPressed or frameCount - gif_export._frame >= 100000) \ + or (frames != 0 and frameCount - gif_export._frame >= frames): + gifExporter.finish() + background(255) + print("gif saved") + del(gifExporter) + return False + else: + return True diff --git a/2019/sketch_190509a/sketch_190509a.gif b/2019/sketch_190509a/sketch_190509a.gif new file mode 100644 index 00000000..49cd720e Binary files /dev/null and b/2019/sketch_190509a/sketch_190509a.gif differ diff --git a/2019/sketch_190509a/sketch_190509a.pyde b/2019/sketch_190509a/sketch_190509a.pyde new file mode 100644 index 00000000..9cc17e7f --- /dev/null +++ b/2019/sketch_190509a/sketch_190509a.pyde @@ -0,0 +1,195 @@ +""" +Alexandre B A Villares - https://abav.lugaralgum.com/sketch-a-day + +- Unfolding solid.... +""" + +add_library('GifAnimation') +from gif_exporter import gif_export +from unfold_face import * + +CUT_STROKE = color(255, 0, 0) +FOLD_STROKE = color(0, 0, 255) + +p_height, base_radius, top_radius = 100, 50, 50 +sides = 5 + +def setup(): + size(600, 600, P3D) + hint(ENABLE_DEPTH_TEST) + hint(ENABLE_DEPTH_SORT) + +def draw(): + background(240) + pushMatrix() + translate(width / 2, height / 4 + 50) + rotateX(radians(45)) + rotateZ(radians(frameCount / 3.)) + fill(255, 200) + stroke(0) + strokeWeight(2) + # draw 3D piramid and get points + points, face = prism_3D(sides, p_height, base_radius, top_radius) + popMatrix() + # draw unfolded 2D + translate(width / 2, height * 3 / 4 - 50) + prism_2D(points, face) + # triangulated_face(*face) + +def prism_3D(np, h, base_r, top_r): + # calculando os points + base_points = [] + for i in range(np): + ang = radians(i * 360. / np) + x = sin(ang) * base_r + y = cos(ang) * base_r + base_points.append((x, y)) + # edges da base + o_base_points = base_points[1:] + [base_points[0]] + base_edges = zip(base_points, o_base_points) + top_points = [] + for i in range(np): + ang = radians(i * 360. / np) + x = sin(ang) * top_r + y = cos(ang) * top_r + top_points.append((x, y)) + # edges da base + o_top_points = top_points[1:] + [top_points[0]] + top_edges = zip(top_points, o_top_points) + # edges + for base_edge, top_edge in zip(base_edges, top_edges): + (p1x, p1y), (p2x, p2y) = base_edge + (p1tx, p1ty), (p2tx, p2ty) = top_edge + beginShape() + vertex(p1x, p1y, 0) + vertex(p1tx, p1ty, h) + vertex(p2tx, p2ty, h) + vertex(p2x, p2y, 0) + endShape(CLOSE) + #line(p1x, p1y, 0, p2tx, p2ty, h) + # one face + (p1x, p1y), (p2x, p2y) = base_edges[0] + (p1tx, p1ty), (p2tx, p2ty) = top_edges[0] + face = [(p2x, p2y, 0), + (p1x, p1y, 0), + (p1tx, p1ty, h), + (p2tx, p2ty, h), + ] + # always draws base + beginShape() + for bpt in base_points: + vertex(bpt[0], bpt[1], 0) + endShape(CLOSE) + beginShape() + for tpt in top_points: + vertex(tpt[0], tpt[1], h) + endShape(CLOSE) + # return points for 2D! + return (base_points, top_points), face + +def prism_2D(top_bot, face): + with pushMatrix(): + translate(150, -300) + poly_draw(top_bot[1]) + with pushMatrix(): + translate(-150, -300) + poly_draw(top_bot[0]) + x0, y0, z0 = face[1] + x2, y2, z2 = face[2] + d = dist(x0, y0, z0, x2, y2, z2) + side = ((150, d - 150), (150, -150)) + for i in range(sides): + side = unfold_tri_face(side, face[::-1]) + stroke(CUT_STROKE) + glue_tab((150, -150), (150, d - 150), 10) + + # for points in all_points: + # ang = radians(360. / len(points)) + # with pushMatrix(): + # translate(-width / 4, 0) + # rotate(ang / 2) + # noFill() + # base fold lines + # stroke(FOLD_STROKE) + # beginShape() + # for pt in points: + # vertex(*pt) + # endShape(CLOSE) + # lateral edges + # o_points = points[1:] + [points[0]] + # edges = zip(points, o_points) + # for i, edge in enumerate(edges): # edges[1:] to skip one + # p1, p2 = edge + # stroke(CUT_STROKE) + # abas de cola + # glue_tab(p2, p1, 10, ) + # FOLD_STROKE + # stroke(FOLD_STROKE) + # line(p2[0], p2[1], p1[0], p1[1]) + # translate(width / 2, 0) + +def glue_tab(p1, p2, tab_w, cut_ang=QUARTER_PI / 3): + """ + draws a trapezoidal or triangular glue tab along edge defined by p1 and p2, + with width tab_w and cut angle a + """ + al = atan2(p1[0] - p2[0], p1[1] - p2[1]) + a1 = al + cut_ang + PI + a2 = al - cut_ang + # calculate cut_len to get the base_rght tab width + cut_len = tab_w / sin(cut_ang) + f1 = (p1[0] + cut_len * sin(a1), + p1[1] + cut_len * cos(a1)) + f2 = (p2[0] + cut_len * sin(a2), + p2[1] + cut_len * cos(a2)) + edge_len = dist(p1[0], p1[1], p2[0], p2[1]) + + if edge_len > 2 * cut_len * cos(cut_ang): # 'normal' trapezoidal tab + beginShape() + vertex(*p1) # vertex(p1[0], p1[1]) + vertex(*f1) + vertex(*f2) + vertex(*p2) + endShape() + else: # short triangular tab + fm = ((f1[0] + f2[0]) / 2, (f1[1] + f2[1]) / 2) + beginShape() + vertex(*p1) + vertex(*fm) # middle way of f1 and f2 + vertex(*p2) + endShape() + +def keyPressed(): + global base_radius, top_radius, p_height, sides + if keyCode == UP: + p_height += 5 + if keyCode == DOWN: + p_height -= 5 + if keyCode == LEFT: + base_radius += 5 + if keyCode == RIGHT: + base_radius -= 5 + if key == "w": + sides += 1 + if key == "s" and sides > 3: + sides -= 1 + if key == "a" and top_radius > 0: + top_radius -= 5 + if key == "d": + top_radius += 5 + if key == "g": + # saveFrame(SKETCH_NAME + ".gif") + gif_export(GifMaker, filename=SKETCH_NAME) + +def settings(): + from os import path + global SKETCH_NAME + SKETCH_NAME = path.basename(sketchPath()) + OUTPUT = ".gif" + println( + """ +![{0}]({2}/{0}/{0}{1}) + +[{0}](https://github.com/villares/sketch-a-day/tree/master/{2}/{0}) [[Py.Processing](https://villares.github.io/como-instalar-o-processing-modo-python/index-EN)] +""".format(SKETCH_NAME, OUTPUT, year()) + ) diff --git a/2019/sketch_190509a/unfold_face.py b/2019/sketch_190509a/unfold_face.py new file mode 100644 index 00000000..a4dddea7 --- /dev/null +++ b/2019/sketch_190509a/unfold_face.py @@ -0,0 +1,158 @@ +CUT_STROKE = color(255, 0, 0) + +def test(): + #size(600, 400, P3D) + p3D = [(50, 100, 0), (200, 100, 0), (200, 200, 0), (100, 300, -100)] + debug_text("ABCD", p3D) + beginShape() + for p in p3D: + vertex(*p) + endShape(CLOSE) + x0, y0, z0 = p3D[1] + x2, y2, z2 = p3D[3] + line(x0, y0, z0, x2, y2, z2) + println(dist(x0, y0, z0, x2, y2, z2)) + + p2D = [(250, 100), (250, 200)] + bx, by = p2D[0] + debug_text("BC", p2D) + for i in range(1): + p2D = unfold_tri_face(p2D, p3D) + println(p2D) + debug_text("AD", p2D) + dx, dy, _ = p2D[1] + println(dist(bx, by, dx, dy)) + + +def unfold_tri_face(pts_2D, pts_3D): + """ + gets a collection of 2 (B, C) starting 2D points (PVectors or tuples) + Gets a collection of 4 (A, B, C, D) 3D points (PVectors or tuples) + Draws the unfolded face and returns (A, D) 2D positions. + """ + b2D, c2D = pts_2D + a3D, b3D, c3D, d3D = pts_3D + bd_len = dist(b3D[0], b3D[1], b3D[2], d3D[0], d3D[1], d3D[2]) + cd_len = dist(c3D[0], c3D[1], c3D[2], d3D[0], d3D[1], d3D[2]) + # lower triangle + d2D = third_point(b2D, c2D, bd_len, cd_len)[0] # gets the first solution + line_draw(b2D, c2D) + #line_draw(b2D, d2D) + line_draw(d2D, c2D, tab=True) + # upper triangle (fixed from 190408a) + ab_len = dist(b3D[0], b3D[1], b3D[2], a3D[0], a3D[1], a3D[2]) + ad_len = dist(a3D[0], a3D[1], a3D[2], d3D[0], d3D[1], d3D[2]) + # gets the 1st solution too! + a2D = third_point(b2D, d2D, ab_len, ad_len)[0] + line_draw(b2D, a2D, tab=True) + line_draw(d2D, a2D) + return (a2D, d2D) + +def third_point(a, b, ac_len, bc_len): + """ + Adapted from code by Monkut https://stackoverflow.com/users/24718/monkut + at https://stackoverflow.com/questions/4001948/drawing-a-triangle-in-a-coordinate-plane-given-its-three-sides + for use with Processing Python Mode - using PVectors + + Returns two point c options given: + point a, point b, ac length, bc length + """ + class NoTrianglePossible(BaseException): + pass + + # To allow use of tuples, creates or recreates PVectors + a, b = PVector(*a), PVector(*b) + # check if a triangle is possible + ab_len = a.dist(b) + if ab_len > (ac_len + bc_len) or ab_len < abs(ac_len - bc_len): + raise NoTrianglePossible("The sides do not form a triangle") + + # get the length to the vertex of the right triangle formed, + # by the intersection formed by circles a and b + ad_len = (ab_len ** 2 + ac_len ** 2 - bc_len ** 2) / (2.0 * ab_len) + # get the height of the line at a right angle from a_len + h = sqrt(abs(ac_len ** 2 - ad_len ** 2)) + + # Calculate the mid point d, needed to calculate point c(1|2) + d = PVector(a.x + ad_len * (b.x - a.x) / ab_len, + a.y + ad_len * (b.y - a.y) / ab_len) + # get point c locations + c1 = PVector(d.x + h * (b.y - a.y) / ab_len, + d.y - h * (b.x - a.x) / ab_len) + c2 = PVector(d.y + h * (b.x - a.x) / ab_len, + d.x - h * (b.y - a.y) / ab_len) + return c1, c2 + +def line_draw(p1, p2, tab=False): + """ + sugar for drawing lines from 2 "points" (tuples or PVectors) + may also draw a glue tab suitably marked for cutting. + """ + line(p1[0], p1[1], p2[0], p2[1]) + if tab: + with pushStyle(): + stroke(CUT_STROKE) + glue_tab(p1, p2) + +def glue_tab(p1, p2, tab_w=10, cut_ang=QUARTER_PI): + """ + draws a trapezoidal or triangular glue tab + along edge defined by p1 and p2, with provided + width (tab_w) and cut angle (cut_ang) + """ + a1 = atan2(p1[0] - p2[0], p1[1] - p2[1]) + cut_ang + PI + a2 = atan2(p1[0] - p2[0], p1[1] - p2[1]) - cut_ang + # calculate cut_len to get the right tab width + cut_len = tab_w / sin(cut_ang) + f1 = (p1[0] + cut_len * sin(a1), + p1[1] + cut_len * cos(a1)) + f2 = (p2[0] + cut_len * sin(a2), + p2[1] + cut_len * cos(a2)) + edge_len = dist(p1[0], p1[1], p2[0], p2[1]) + + if edge_len > 2 * cut_len * cos(cut_ang): # 'normal' trapezoidal tab + line_draw(p1, f1) + line_draw(f1, f2) + line_draw(f2, p2) + else: # short triangular tab + fm = ((f1[0] + f2[0]) / 2, (f1[1] + f2[1]) / 2) + line_draw(p1, fm) + line_draw(fm, p2) + + +DEBUG = True + +def debug_text(name, points, enum=False): + if DEBUG: + for i, p in enumerate(points): + with push(): + + fill(255, 0, 0) + if enum: + translate(0, -5, 10) + text(name + "-" + str(i), *p) + else: + translate(10, 10, 10) + text(name[i], *p) + +def poly_draw(points, closed=True): + """ sugar for face drawing """ + beginShape() + for p in points: + vertex(*p) + if closed: + endShape(CLOSE) + else: + endShape() + +def triangulated_face(*args): + if len(args) == 4: + a, b, c, d = args + println("face") + else: + a, b, c, d = args[0] + # two triangles - could be with a diferent diagonal! + # TODO: let one choose diagonal orientation + stroke(0) + poly_draw((a, b, d)) + poly_draw((b, d, c))