diff --git a/2019/sketch_190606a/0399.png b/2019/sketch_190606a/0399.png new file mode 100644 index 00000000..fd00ec85 Binary files /dev/null and b/2019/sketch_190606a/0399.png differ diff --git a/2019/sketch_190606a/0410.png b/2019/sketch_190606a/0410.png new file mode 100644 index 00000000..fd00ec85 Binary files /dev/null and b/2019/sketch_190606a/0410.png differ diff --git a/2019/sketch_190606a/0411.png b/2019/sketch_190606a/0411.png new file mode 100644 index 00000000..fd00ec85 Binary files /dev/null and b/2019/sketch_190606a/0411.png differ diff --git a/2019/sketch_190609a/poly.py b/2019/sketch_190609a/poly.py index b8ff9213..480a97af 100644 --- a/2019/sketch_190609a/poly.py +++ b/2019/sketch_190609a/poly.py @@ -1,3 +1,4 @@ +from copy import deepcopy from arcs import * class Poly(): diff --git a/2019/sketch_190609a/sketch_190609a.pyde b/2019/sketch_190609a/sketch_190609a.pyde index 114179c5..1d240dd5 100644 --- a/2019/sketch_190609a/sketch_190609a.pyde +++ b/2019/sketch_190609a/sketch_190609a.pyde @@ -4,7 +4,6 @@ A minimal poly editor - Add points """ import pickle -from copy import deepcopy from poly import Poly # add_library('GifAnimation') # from gif_exporter import gif_export diff --git a/2019/sketch_190610a/arcs.py b/2019/sketch_190610a/arcs.py new file mode 100644 index 00000000..f0c58374 --- /dev/null +++ b/2019/sketch_190610a/arcs.py @@ -0,0 +1,153 @@ +def b_poly_filleted(p_list, r_list=None, open_poly=False): + """ + draws a 'filleted' polygon with variable radius + dependent on roundedCorner() + """ + if not r_list: + r_list = [0] * len(p_list) + assert len(p_list) == len(r_list), \ + "Number of points and radii not the same" + strokeJoin(ROUND) + beginShape() + for p0, p1, p2, r in zip(p_list, + [p_list[-1]] + p_list[:-1], + [p_list[-2]] + [p_list[-1]] + p_list[:-2], + [r_list[-1]] + r_list[:-1] + ): + m1 = (p0[0] + p1[0]) / 2, (p0[1] + p1[1]) / 2 + m2 = (p2[0] + p1[0]) / 2, (p2[1] + p1[1]) / 2 + b_roundedCorner(p1, m1, m2, r) + endShape(CLOSE) + +def b_roundedCorner(pc, p2, p1, r): + """ + Based on Stackoverflow C# rounded corner post + https://stackoverflow.com/questions/24771828/algorithm-for-creating-rounded-corners-in-a-polygon + """ + def GetProportionPoint(pt, segment, L, dx, dy): + factor = float(segment) / L if L != 0 else segment + return PVector((pt[0] - dx * factor), (pt[1] - dy * factor)) + + # Vector 1 + dx1 = pc[0] - p1[0] + dy1 = pc[1] - p1[1] + # Vector 2 + dx2 = pc[0] - p2[0] + dy2 = pc[1] - p2[1] + # Angle between vector 1 and vector 2 divided by 2 + angle = (atan2(dy1, dx1) - atan2(dy2, dx2)) / 2 + # The length of segment between angular point and the + # points of intersection with the circle of a given radius + tng = abs(tan(angle)) + segment = r / tng if tng != 0 else r + # Check the segment + length1 = sqrt(dx1 * dx1 + dy1 * dy1) + length2 = sqrt(dx2 * dx2 + dy2 * dy2) + min_len = min(length1, length2) + if segment > min_len: + segment = min_len + max_r = min_len * abs(tan(angle)) + else: + max_r = r + # Points of intersection are calculated by the proportion between + # length of vector and the length of the segment. + p1Cross = GetProportionPoint(pc, segment, length1, dx1, dy1) + p2Cross = GetProportionPoint(pc, segment, length2, dx2, dy2) + # Calculation of the coordinates of the circle + # center by the addition of angular vectors. + dx = pc[0] * 2 - p1Cross[0] - p2Cross[0] + dy = pc[1] * 2 - p1Cross[1] - p2Cross[1] + L = sqrt(dx * dx + dy * dy) + d = sqrt(segment * segment + max_r * max_r) + circlePoint = GetProportionPoint(pc, d, L, dx, dy) + # StartAngle and EndAngle of arc + startAngle = atan2(p1Cross[1] - circlePoint[1], + p1Cross[0] - circlePoint[0]) + endAngle = atan2(p2Cross[1] - circlePoint[1], + p2Cross[0] - circlePoint[0]) + # Sweep angle + sweepAngle = endAngle - startAngle + # Some additional checks + A, B = False, False + if sweepAngle < 0: + A = True + startAngle, endAngle = endAngle, startAngle + sweepAngle = -sweepAngle + # ellipse(pc[0], pc[1], 15, 15) # debug + if sweepAngle > PI: + B = True + startAngle, endAngle = endAngle, startAngle + sweepAngle = TWO_PI - sweepAngle + # ellipse(pc[0], pc[1], 25, 25) # debug + if (A and not B) or (B and not A): + startAngle, endAngle = endAngle, startAngle + sweepAngle = -sweepAngle + # ellipse(pc[0], pc[1], 5, 5) # debug + b_arc(circlePoint[0], circlePoint[1], 2 * max_r, 2 * max_r, + startAngle, startAngle + sweepAngle, arc_type=2) + + +def b_arc(cx, cy, w, h, startAngle, endAngle, arc_type=0): + """ + A bezier approximation of an arc + using the same signature as the original Processing arc() + arc_type: 0 "normal" arc, using beginShape() and endShape() + 1 "middle" used in recursive call of smaller arcs + 2 "naked" like normal, but without beginShape() and endShape() + for use inside a larger PShape + """ + theta = endAngle - startAngle + # Compute raw Bezier coordinates. + if arc_type != 1 or theta < HALF_PI: + x0 = cos(theta / 2.0) + y0 = sin(theta / 2.0) + x3 = x0 + y3 = 0 - y0 + x1 = (4.0 - x0) / 3.0 + if y0 != 0: + y1 = ((1.0 - x0) * (3.0 - x0)) / (3.0 * y0) # y0 != 0... + else: + y1 = 0 + x2 = x1 + y2 = 0 - y1 + # Compute rotationally-offset Bezier coordinates, using: + # x' = cos(angle) * x - sin(angle) * y + # y' = sin(angle) * x + cos(angle) * y + bezAng = startAngle + theta / 2.0 + cBezAng = cos(bezAng) + sBezAng = sin(bezAng) + rx0 = cBezAng * x0 - sBezAng * y0 + ry0 = sBezAng * x0 + cBezAng * y0 + rx1 = cBezAng * x1 - sBezAng * y1 + ry1 = sBezAng * x1 + cBezAng * y1 + rx2 = cBezAng * x2 - sBezAng * y2 + ry2 = sBezAng * x2 + cBezAng * y2 + rx3 = cBezAng * x3 - sBezAng * y3 + ry3 = sBezAng * x3 + cBezAng * y3 + # Compute scaled and translated Bezier coordinates. + rx, ry = w / 2.0, h / 2.0 + px0 = cx + rx * rx0 + py0 = cy + ry * ry0 + px1 = cx + rx * rx1 + py1 = cy + ry * ry1 + px2 = cx + rx * rx2 + py2 = cy + ry * ry2 + px3 = cx + rx * rx3 + py3 = cy + ry * ry3 + # Debug points... comment this out! + # stroke(0) + # ellipse(px3, py3, 15, 15) + # ellipse(px0, py0, 5, 5) + # Drawing + if arc_type == 0: # 'normal' arc (not 'middle' nor 'naked') + beginShape() + if arc_type != 1: # if not 'middle' + vertex(px3, py3) + if theta < HALF_PI: + bezierVertex(px2, py2, px1, py1, px0, py0) + else: + # to avoid distortion, break into 2 smaller arcs + b_arc(cx, cy, w, h, startAngle, endAngle - theta / 2.0, arc_type=1) + b_arc(cx, cy, w, h, startAngle + theta / 2.0, endAngle, arc_type=1) + if arc_type == 0: # end of a 'normal' arc + endShape() diff --git a/2019/sketch_190610a/data/project.data b/2019/sketch_190610a/data/project.data new file mode 100644 index 00000000..97b886d5 --- /dev/null +++ b/2019/sketch_190610a/data/project.data @@ -0,0 +1,86 @@ +(lp0 +(ipoly +Poly +p1 +(dp2 +S'closed' +p3 +I01 +sS'lw' +p4 +I1 +sS'id' +p5 +I0 +sS'pts' +p6 +(lp7 +(I0 +I0 +I-1 +tp8 +a(I6 +I0 +I1 +tp9 +a(I6 +I6 +I1 +tp10 +a(I0 +I6 +I0 +tp11 +asS'holes' +p12 +(lp13 +(lp14 +asba(ipoly +Poly +p15 +(dp16 +S'closed' +p17 +I01 +sS'lw' +p18 +I1 +sS'id' +p19 +I1 +sS'pts' +p20 +(lp21 +(I-1 +I-1 +I0 +tp22 +a(I-6 +I-1 +I0 +tp23 +a(I-6 +I-6 +I0 +tp24 +a(I-1 +I-6 +I0 +tp25 +asS'holes' +p26 +(lp27 +(lp28 +(I-2 +I-3 +I0 +tp29 +a(I-3 +I-3 +I1 +tp30 +a(I-2 +I-2 +I0 +tp31 +aasba. \ No newline at end of file diff --git a/2019/sketch_190610a/gif_exporter.py b/2019/sketch_190610a/gif_exporter.py new file mode 100644 index 00000000..51c431df --- /dev/null +++ b/2019/sketch_190610a/gif_exporter.py @@ -0,0 +1,40 @@ +""" +Alexandre B A Villares http://abav.lugaralgum.com - GPL v3 + +A helper for the Processing gifAnimation library https://github.com/extrapixel/gif-animation/tree/3.0 +Download from https://github.com/villares/processing-play/blob/master/export_GIF/unzip_and_move_to_libraries_GifAnimation.zip +This helper was inspired by an example by Art Simon https://github.com/APCSPrinciples/AnimatedGIF/ + +# add at the start of your sketch: + add_library('gifAnimation') + from gif_exporter import gif_export +# add 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=255, # quality range 0 - 255 + delay=200, # this is quick + frames=0, # 0 will stop on keyPressed or frameCount >= 100000 + finish=False): # force stop + global gifExporter + try: + gifExporter + except NameError: + gifExporter = GifMaker(this, filename + ".gif") + gifExporter.setRepeat(repeat) + gifExporter.setQuality(quality) + gifExporter.setDelay(delay) + + gifExporter.addFrame() + + if frames == 0: + if keyPressed and key == "e": + finish = True + + if finish: + gifExporter.finish() + print("gif saved") + exit() diff --git a/2019/sketch_190610a/poly.py b/2019/sketch_190610a/poly.py new file mode 100644 index 00000000..d8128625 --- /dev/null +++ b/2019/sketch_190610a/poly.py @@ -0,0 +1,232 @@ +from copy import deepcopy +from arcs import * + +class Poly(): + + selected = -1 + text_on = False + selected_drag = -1 + drag_hole = -1 + drag_pt = -1 + id = 0 + + def __init__(self, pts, holes=None, closed=True, lw=1): + self.pts = pts + self.holes = holes if holes else [[]] + self.closed = closed + self.lw = lw + + @classmethod + def setup_grid(cls, cell_size, order, x_offset, y_offset): + cls.cell_size = cell_size + cls.order = order + cls.x_offset, cls.y_offset = x_offset, y_offset + cls.polys = [] + cls.text_on = False + + def plot(self): + for i, p in enumerate(self.polys): + self.id = i if self == p else self.id + pushStyle() + strokeJoin(ROUND) + strokeWeight(self.lw) + if self.selected_drag == self.id: + stroke(200, 0, 0) + else: + stroke(0) + if len(self.pts) >= 2: + if self.closed: + fill(100) + else: + noFill() + beginShape() + Poly.draw_pts(self.pts) + for hole in self.holes: + beginContour() + Poly.draw_pts(hole) + endContour() + if self.closed: + endShape(CLOSE) + else: + endShape() + if self.text_on: + self.annotate_pts(self.id, self.pts, color(200, 0, 0), 5) + self.annotate_pts(self.id, self.holes[0], color(0, 0, 200), 5) + popStyle() + + @classmethod + def draw_pts(cls, pts): + for i, pt in enumerate(pts): + x, y, corner = pt + sx, sy = cls.grid_to_screen(x, y) + if corner == 0: + vertex(sx, sy) + elif corner > 0: + pp = cls.grid_to_screen(pts[i - 1]) + np = cls.grid_to_screen(pts[(i + 1) % len(pts)]) + r = corner * cls.cell_size + b_roundedCorner((sx, sy), np, pp, r) # pt[2]) + else: + if keyPressed: + vertex(sx, sy) + + + def remove_pt(self): + snap = self.mouse_snap() + if snap: + for pt in self.pts: + if pt[:2] == snap: + self.pts.remove(pt) + return True + for h in self.holes: + for pt in h: + if pt[:2] == snap: + h.remove(pt) + return True + + def set_drag(self): # , io, jo): + snap = Poly.mouse_snap() + if snap: + for ipt, pt in enumerate(self.pts): + if pt[:2] == snap: # (io, jo): + Poly.drag_pt = ipt + return True + for ih, h in enumerate(self.holes): + for ipt, pt in enumerate(h): + if pt[:2] == snap: # (io, jo): + Poly.drag_hole = ih + Poly.drag_pt = ipt + return True + return False + + @classmethod + def annotate_pts(cls, id, pts, c, scale_m=1): + strokeWeight(5) + textSize(12) + fill(c) + stroke(c) + for pt in pts: + i, j = pt[0], pt[1] + sx, sy = cls.grid_to_screen(i, j) + point(sx, sy) + text(str(id) + ":" + str((i * scale_m, j * scale_m)), sx, sy) + + @classmethod + def draw_grid(cls): + stroke(128) + noFill() + for x in range(cls.order): + for y in range(cls.order): + rect(x * cls.cell_size, y * cls.cell_size, + cls.cell_size, cls.cell_size) + + @staticmethod + def clockwise_sort(xy_pairs): + # https://stackoverflow.com/questions/51074984/sorting-according-to-clockwise-point-coordinates + data_len = len(xy_pairs) + if data_len > 2: + x, y = zip(*xy_pairs) + else: + return xy_pairs + centroid_x, centroid_y = sum(x) / data_len, sum(y) / data_len + xy_sorted = sorted(xy_pairs, + key=lambda p: atan2((p[1] - centroid_y), (p[0] - centroid_x))) + xy_sorted_xy = [ + coord for pair in list(zip(*xy_sorted)) for coord in pair] + half_len = int(len(xy_sorted_xy) / 2) + return list(zip(xy_sorted_xy[:half_len], xy_sorted_xy[half_len:])) + + @classmethod + def mouse_snap(cls): + for i in range(cls.order): + x = i * cls.cell_size + for j in range(cls.order): + y = j * cls.cell_size + # grid origin correction + io, jo = i - cls.x_offset, j - cls.y_offset + if dist(mouseX, mouseY, x, y) < cls.cell_size / 2: + return (io, jo) + return None + + @classmethod + def mouse_pressed(cls): + if keyPressed and keyCode == CONTROL: + for p in cls.polys: + if p.remove_pt(): # io, jo): + return + else: + for ip, p in enumerate(cls.polys): + if p.set_drag(): # io, jo): + cls.selected_drag = ip + return + cls.selected_drag = -1 # click outside known vertices deselects + + @classmethod + def mouse_dragged(cls): + if cls.selected_drag >= 0 and not keyPressed: + # a Poly point has been selected to be dragged + # and no modifier key is pressed... + if cls.drag_hole == -1: # if no hole was selected + poly = cls.polys[cls.selected_drag] + i, j = cls.screen_to_grid(mouseX, mouseY) + poly.pts[cls.drag_pt] = (i, j, poly.pts[cls.drag_pt][2]) + else: + poly = cls.polys[cls.selected_drag] + hole = poly.holes[cls.drag_hole] + i, j = cls.screen_to_grid(mouseX, mouseY) + hole[cls.drag_pt] = (i, j, hole[cls.drag_pt][2]) + + elif cls.selected_drag >= 0 and key == "m": + poly = cls.polys[cls.selected_drag] + dragged_pt = poly.pts[cls.drag_pt] + mx, my = cls.screen_to_grid(mouseX, mouseY) + dx, dy = mx - dragged_pt[0], my - dragged_pt[1] + pts = poly.pts + for i, pt in enumerate(pts): + pts[i] = (pt[0] + dx, pt[1] + dy, pt[2] ) + for hole in poly.holes: + for i, pt in enumerate(hole): + hole[i] = (pt[0] + dx, pt[1] + dy, pt[2]) + + @classmethod + def grid_to_screen(cls, *args): + if len(args) == 1: + x, y = args[0][0], args[0][1] + else: + x, y = args + return ((x + cls.x_offset) * cls.cell_size, + (y + cls.y_offset) * cls.cell_size) + + @classmethod + def screen_to_grid(cls, x, y): + return (int(x / cls.cell_size) - cls.x_offset, + int(y / cls.cell_size) - cls.y_offset) + + @classmethod + def mouse_released(cls): + if cls.selected_drag >= 0 and keyPressed and keyCode == SHIFT: + # a Poly point has been selected to be dragged + # and SHIFT key is pressed... + if cls.drag_hole == -1: # if no hole wase selected + poly = cls.polys[cls.selected_drag] + i, j = cls.screen_to_grid(mouseX, mouseY ) + poly.pts.insert(cls.drag_pt, (i, j, 0)) + else: + poly = cls.polys[cls.selected_drag] + hole = poly.holes[Poly.drag_hole] + i, j = cls.screen_to_grid(mouseX, mouseY ) + hole.insert(cls.drag_pt, (i, j, 0)) + # Poly.selected_drag = -1 # No poly selected + Poly.drag_hole = -1 # No hole selected + Poly.drag_pt = -1 # No point selected + + @classmethod + def duplicate_selected(cls, off=1): + if Poly.selected_drag >= 0: + new_poly = deepcopy(cls.polys[cls.selected_drag]) + for i, pt in enumerate(new_poly.pts): + new_poly.pts[i] = (pt[0] + off, pt[1] + off, pt[2]) + for h in new_poly.holes: + for i, pt in enumerate(h): + h[i] = (pt[0] + off, pt[1] + off, pt[2]) + cls.polys.append(new_poly) diff --git a/2019/sketch_190610a/sketch_190609a.png b/2019/sketch_190610a/sketch_190609a.png new file mode 100644 index 00000000..a3e5abcc Binary files /dev/null and b/2019/sketch_190610a/sketch_190609a.png differ diff --git a/2019/sketch_190610a/sketch_190610a.pyde b/2019/sketch_190610a/sketch_190610a.pyde new file mode 100644 index 00000000..47939d0b --- /dev/null +++ b/2019/sketch_190610a/sketch_190610a.pyde @@ -0,0 +1,86 @@ +# Alexandre B A Villares - https://abav.lugaralgum.com/sketch-a-day +""" +A minimal poly editor +- Add points +""" +import pickle +from poly import Poly +# add_library('GifAnimation') +# from gif_exporter import gif_export + + +def setup(): + size(500, 500, P2D) + + f = createFont("Fira Mono", 16) + textFont(f) + + CELL_SIZE = 15 + order = width // CELL_SIZE + x_offset = y_offset = int(order // 2) + Poly.setup_grid(CELL_SIZE, order, x_offset, y_offset) + p1 = Poly([(0, 0, -1), (6, 0, 1), (6, 6, 1), (0, 6, 0)]) + Poly.polys.append(p1) + p2 = Poly([(-1, -1, 0), (-6, -1, 0), (-6, -6, 0), (-1, -6, 0)], + holes=[[(-2, -3, 0), (-3, -3, 1), (-2, -2, 0)]]) + Poly.polys.append(p2) + +def draw(): + background(230) + # grade + Poly.draw_grid() + # polĂ­gonos + for p in Poly.polys: + p.plot() + +def mousePressed(): + Poly.mouse_pressed() + +def mouseDragged(): + Poly.mouse_dragged() + +def mouseReleased(): + Poly.mouse_released() + +def keyPressed(): + if key == "=": + Poly.selected_drag += 1 + if Poly.selected_drag >= len(Poly.polys): + Poly.selected_drag = -1 + if key == "d": + Poly.duplicate_selected() + + if key == " " and Poly.selected_drag >= 0: + p = Poly.polys[Poly.selected_drag] + p.pts[:] = Poly.clockwise_sort(p.pts) + for h in p.holes: + h[:] = Poly.clockwise_sort(h)[::-1] + # if key == "g": + # gif_export(GifMaker, filename=SKETCH_NAME) + if key == "p": + saveFrame(SKETCH_NAME + ".png") + if key == "t": + Poly.text_on = not Poly.text_on + + if key == "s": + with open("data/project.data", "wb") as file_out: + pickle.dump(Poly.polys, file_out) + println("project saved") + + if key == "r": + with open("data/project.data", "rb") as file_in: + Poly.polys = pickle.load(file_in) + println("project loaded") + +def settings(): + from os import path + global SKETCH_NAME + SKETCH_NAME = path.basename(sketchPath()) + OUTPUT = ".png" + println( + """ +![{0}](2019/{0}/{0}{1}) + +[{0}](https://github.com/villares/sketch-a-day/tree/master/2019/{0}) [[Py.Processing](https://villares.github.io/como-instalar-o-processing-modo-python/index-EN)] +""".format(SKETCH_NAME, OUTPUT) + )