Alexandre B A Villares 2022-07-04 15:29:36 -03:00
rodzic 30cfa07045
commit 9b4ec61767
7 zmienionych plików z 738 dodań i 0 usunięć

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 381 KiB

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 262 KiB

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 300 KiB

Wyświetl plik

@ -0,0 +1,653 @@
# -*- coding: utf-8 -*-
"""
From https://github.com/villares/villares/blob/main/arcs.py
2020-09-22 Merges/renames several versions of the arc related functions
2020-09-24 Updates arc_filleted_poly and arc_augmented_poly
2020-09-25 Added bar() and var_bar()
2020-09-26 Moved code from bar() to var_bar() and added several new kwargs
2020-09-27 Revising arc_filleted_poly, added kwargs. Revised circle_arc and related functions
2020-11 Improving compatibility with pyp5js, not using PVector anymore
2021-07-26 Added auto-flip option to arc_augmented_poly
2022-03-02 Make it work with py5
2022-03-13 On arc_filleted_poly, add radius keyword argument to be used.
2022-06-10 Added arc_pts() & var_bar_pts(). Also some p_arc and var_bar clean up.
2022-06-11 Fixing arc_pts bug. Making arc_filleted_poly return points with arc_pts
Added a radius keywarg to arc_augmented_poly, and a py5 compatibilty fix.
2022_06_13 Attempt at arc_augmented_points(), changing some behaviour of arc_augmente_poly()
2022_07_03 Adding alternative resolution control to arc_pts (@introscopia's suggestion)
"""
from warnings import warn
try:
from line_geometry import is_poly_self_intersecting, triangle_area
except ModuleNotFoundError:
from villares.line_geometry import is_poly_self_intersecting, triangle_area
# The following block makes this compatible with py5.ixora.io
try:
EPSILON
remap = map
except NameError:
from py5 import *
beginShape = begin_shape
endShape = end_shape
bezierVertex = bezier_vertex
textSize = text_size
DEBUG, TEXT_HEIGHT = False, 12 # For debug
# For use with half_circle and quarter_circle functions
ROTATION = {0: 0,
BOTTOM: 0,
DOWN: 0,
1: HALF_PI,
LEFT: HALF_PI,
2: PI,
TOP: PI,
UP: PI,
3: PI + HALF_PI,
RIGHT: PI + HALF_PI,
BOTTOM + RIGHT: 0,
BOTTOM + LEFT: HALF_PI,
TOP + LEFT: PI,
TOP + RIGHT: PI + HALF_PI,
}
def circle_arc(x, y, radius, start_ang, sweep_ang, *args, **kwargs):
arc_func = kwargs.pop('arc_func', arc)
arc_func(x, y, radius * 2, radius * 2, start_ang,
start_ang + sweep_ang, *args, **kwargs)
def quarter_circle(x, y, radius, quadrant, *args, **kwargs):
circle_arc(x, y, radius, ROTATION[quadrant], HALF_PI, *args, **kwargs)
def half_circle(x, y, radius, quadrant, *args, **kwargs):
circle_arc(x, y, radius, ROTATION[quadrant], PI, *args, **kwargs)
def b_circle_arc(x, y, radius, start_ang, sweep_ang, mode=0):
b_arc(x, y, radius * 2, radius * 2, start_ang, start_ang + sweep_ang,
mode=mode)
def b_arc(cx, cy, w, h, start_angle, end_angle, mode=0):
"""
Draw a bezier approximation of an arc using the same
signature as the original Processing arc().
mode: 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.
"""
# Based on ideas from Richard DeVeneza via code by Golan Levin:
# http://www.flong.com/blog/2009/bezier-approximation-of-a-circular-arc-in-processing/
theta = end_angle - start_angle
# Compute raw Bezier coordinates.
if mode != 1 or abs(theta) < HALF_PI:
x0 = cos(theta / 2.0)
y0 = sin(theta / 2.0)
x3 = x0
y3 = 0 - y0
x1 = (4.0 - x0) / 3.0
y1 = ((1.0 - x0) * (3.0 - x0)) / (3.0 * y0) if y0 != 0 else 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 = start_angle + 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
if DEBUG:
ellipse(px3, py3, 3, 3)
ellipse(px0, py0, 5, 5)
# Drawing
if mode == 0: # 'normal' arc (not 'middle' nor 'naked')
beginShape()
if mode != 1: # if not 'middle'
vertex(px3, py3)
if abs(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, start_angle, end_angle - theta / 2.0, mode=1)
b_arc(cx, cy, w, h, start_angle + theta / 2.0, end_angle, mode=1)
if mode == 0: # end of a 'normal' arc
endShape()
def p_circle_arc(x, y, radius, start_ang, sweep_ang, mode=0, **kwargs):
p_arc(x, y, radius * 2, radius * 2, start_ang, start_ang + sweep_ang,
mode=mode, **kwargs)
def circle_arc_pts(x, y, radius, start_ang, sweep_ang, **kwargs):
arc_pts(x, y, radius * 2, radius * 2, start_ang, start_ang + sweep_ang,
**kwargs)
def p_arc(cx, cy, w, h, start_angle, end_angle, mode=0,
num_points=24, vertex_func=None):
"""
A poly approximation of an arc using the same
signature as the original Processing arc().
mode: 0 "normal" arc-like, using beginShape() and endShape()
1 "middle" not implemented on p_arc, used on recursive b_arc
2 "naked" like normal, but without beginShape() and
endShape() for use inside a larger PShape.
"""
vertex_func = vertex_func or vertex
if mode == 0:
beginShape()
vertex_pts = arc_pts(cx, cy, w, h, start_angle, end_angle, num_points)
for vx, vy in vertex_pts:
vertex_func(vx, vy)
if mode == 0:
endShape()
def arc_pts(cx, cy, w, h, start_angle, end_angle, num_points=None, seg_len=None):
"""
Returns points approximating an arc using the same
signature as the original Processing arc().
"""
sweep_angle = end_angle - start_angle
if abs(sweep_angle) < 0.0001:
vx = cx + cos(start_angle) * w / 2.0
vy = cy + sin(start_angle) * h / 2.0
return [(vx, vy)]
if num_points is None and seg_len is None:
num_points = 24
elif num_points is None:
num_points = abs(sweep_angle * (w + h) / 4) / seg_len
pts_list = []
step_angle = float(sweep_angle) / num_points
va = start_angle
side = 1 if sweep_angle > 0 else -1
while va * side < end_angle * side or abs(va - end_angle) < 0.0001:
vx = cx + cos(va) * w / 2.0
vy = cy + sin(va) * h / 2.0
pts_list.append((vx, vy))
va += step_angle
return pts_list
def arc_filleted_poly(p_list, r_list=None, **kwargs):
"""
Draws a 'filleted' polygon with variable radius, depends on arc_corner()
2020-09-24 Rewritten from poly_rounded2 to be a continous PShape
2020-09-27 Moved default args to kwargs, added kwargs support for custom arc_func
2020-11-10 Moving vertex_func=vertex inside body to make this more compatible with pyp5js
2020-11-11 Removing use of PVector to improve compatibility with pyp5js
2022-03-13 Allows a radius keyword argument to be used when no r_list is suplied
2022-06-11 Refactoring and added arc_pts non-drawing feature that returns points.
"""
arc_func = kwargs.pop('arc_func', b_arc) # draws with bezier aprox. arc by default
open_poly = kwargs.pop('open_poly', False) # assumes a closed poly by default
assert p_list, 'No points were provided.'
assert not ('radius' in kwargs and r_list),\
"You can't use a radii list and a radius kwarg together."
if r_list is None:
r_list = [kwargs.pop('radius', 0)] * len(p_list)
p_list, r_list = list(p_list), list(r_list)
draw_shape = False if arc_func == arc_pts else True
def mid(p0, p1):
return (p0[0] + p1[0]) * 0.5, (p0[1] + p1[1]) * 0.5
if open_poly:
p0_p1_p2_r_sequence = zip(p_list[:-1],
[p_list[-1]] + p_list[:-2],
[p_list[-2]] + [p_list[-1]] + p_list[:-3],
[r_list[-1]] + r_list[:-2])
else:
p0_p1_p2_r_sequence = zip(p_list,
[p_list[-1]] + p_list[:-1],
[p_list[-2]] + [p_list[-1]] + p_list[:-2],
[r_list[-1]] + r_list[:-1])
if draw_shape:
beginShape()
for p0, p1, p2, r in p0_p1_p2_r_sequence:
arc_corner(p1, mid(p0, p1), mid(p1, p2), r,
arc_func=arc_func, **kwargs)
else:
pts_list = []
for p0, p1, p2, r in p0_p1_p2_r_sequence:
pts_list.extend(arc_corner(p1, mid(p0, p1), mid(p1, p2), r,
arc_func=arc_func, **kwargs))
return pts_list
# then, if draw_shape:
if open_poly:
endShape()
else:
endShape(CLOSE)
def arc_corner(pc, p1, p2, r, **kwargs):
"""
Draw an arc that 'rounds' the point pc between p1 and p2 using arc_func
Based on '...rounded corners in a polygon' from https://stackoverflow.com/questions/24771828/
2020-09-27 Added support for custom arc_func & kwargs
2020-11-11 Avoiding the use of PVector
2022-06-11 Now returns the result of arc_pts
"""
arc_func = kwargs.pop('arc_func', b_arc) # draws with bezier aprox. arc by default
def proportion_point(pt, segment, L, dx, dy):
factor = float(segment) / L if L != 0 else segment
return (pt[0] - dx * factor), (pt[1] - dy * factor)
# Vectors 1 and 2
dx1, dy1 = pc[0] - p1[0], pc[1] - p1[1]
dx2, dy2 = pc[0] - p2[0], 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 = proportion_point(pc, segment, length1, dx1, dy1)
p2Cross = proportion_point(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)
arc_center = proportion_point(pc, d, L, dx, dy)
# start_angle and end_angle of arc
start_angle = atan2(p1Cross[1] - arc_center[1], p1Cross[0] - arc_center[0])
end_angle = atan2(p2Cross[1] - arc_center[1], p2Cross[0] - arc_center[0])
# Sweep angle
sweep_angle = end_angle - start_angle
# Some additional checks
nsa = False # negative sweep angle
if sweep_angle < 0:
start_angle, end_angle = end_angle, start_angle
sweep_angle = -sweep_angle
nsa = True
if DEBUG:
circle(arc_center[0], arc_center[1], max_r / 2)
lsa = False # large sweep angle
if sweep_angle > PI:
start_angle, end_angle = end_angle, start_angle
sweep_angle = TWO_PI - sweep_angle
lsa = True
if DEBUG:
circle(arc_center[0], arc_center[1], max_r)
if (lsa and nsa) or (not lsa and not nsa):
# reverse sweep direction
start_angle, end_angle = end_angle, start_angle
sweep_angle = -sweep_angle
if arc_func == arc_pts:
return arc_pts(arc_center[0], arc_center[1], 2 * max_r, 2 * max_r,
start_angle, start_angle + sweep_angle, **kwargs)
# else, draw "naked" arc (without beginShape & endShape)
arc_func(arc_center[0], arc_center[1], 2 * max_r, 2 * max_r,
start_angle, start_angle + sweep_angle, mode=2, **kwargs)
def arc_augmented_poly(op_list, or_list=None, **kwargs):
"""
Draw a continous PShape "Polyline" as if around pins of various diameters.
Has an ugly check_intersection mode that does not draw and "roughly" checks
for self intersections using slow polygon aproximations.
2020-09-22 Renamed from b_poly_arc_augmented
2020-09-24 Removed Bezier mode in favour of arc_func + any keyword arguments.
2020-09-26 Moved arc_func to kwargs, updates exceptions
2021-07-26 Added auto-flip switch/option (when concave vertex radius = -radius)
2022-06-11 Added remap py5 compatibility alias & radius kwarg for or_list=None
2022-06-14 Connected to arc_augmented_points. Added reduce_both kwarg.
"""
arc_func = kwargs.pop('arc_func', b_arc)
if arc_func == arc_pts:
return arc_augmented_points(op_list, or_list, **kwargs)
assert op_list, 'No points were provided.'
assert not ('radius' in kwargs and or_list),\
"You can't use a radii list and a radius kwarg together."
if 'radius' in kwargs and or_list == None:
or_list = [kwargs.pop('radius')] * len(op_list)
r2_list = list(or_list)
assert len(op_list) == len(r2_list),\
'Number of points and radii provided not the same.'
check_intersection = kwargs.pop('check_intersection', False)
auto_flip = kwargs.pop('auto_flip', True)
gradual_flip = kwargs.pop('gradual_flip', False)
reduce_both = kwargs.pop('reduce_both', True)
if check_intersection and arc_func:
warn("check_intersection mode overrides arc_func (arc_func ignored).")
if check_intersection:
global _points, vertex_func
_points = []
vertex_func = lambda x, y: _points.append((x, y))
arc_func = p_arc
kwargs = {"num_points": 4, "vertex_func": vertex_func}
else:
vertex_func = vertex
# remove overlapping adjacent points
p_list, r_list = [], []
for i1, p1 in enumerate(op_list):
i2 = (i1 - 1)
p2, r2, r1 = op_list[i2], r2_list[i2], r2_list[i1]
if dist(p1[0], p1[1], p2[0], p2[1]) > 1: # or p1 != p2:
p_list.append(p1)
r_list.append(r1)
else:
r2_list[i2] = min(r1, r2)
# invert radius
for i1, p1 in enumerate(p_list):
i0 = (i1 - 1)
p0 = p_list[i0]
i2 = (i1 + 1) % len(p_list)
p2 = p_list[i2]
a = triangle_area(p0, p1, p2) / 1000.
if auto_flip and a < 0:
r_list[i1] = -r_list[i1]
if gradual_flip:
r_list[i1] = r_list[i1] * min(1, abs(a))
# reduce radius that won't fit
for i1, p1 in enumerate(p_list):
i2 = (i1 + 1) % len(p_list)
p2, r2, r1 = p_list[i2], r_list[i2], r_list[i1]
r_list[i1], r_list[i2] = reduce_radius(p1, p2, r1, r2,
reduce_both=reduce_both)
# calculate the tangents
a_list = []
for i1, p1 in enumerate(p_list):
i2 = (i1 + 1) % len(p_list)
p2, r2, r1 = p_list[i2], r_list[i2], r_list[i1]
cct = circ_circ_tangent(p1, p2, r1, r2)
a_list.append(cct)
# check basic "skeleton poly" intersection (whithout the p_arc aprox.)
if check_intersection:
skeleton_points = []
for ang, p1, p2 in a_list:
skeleton_points.append(p1)
skeleton_points.append(p2)
if is_poly_self_intersecting(skeleton_points):
return True
# now draw it!
beginShape()
for i1, ia in enumerate(a_list):
i2 = (i1 + 1) % len(a_list)
p1, p2, r1, r2 = p_list[i1], p_list[i2], r_list[i1], r_list[i2]
a1, p11, p12 = ia
a2, p21, p22 = a_list[i2]
if DEBUG:
circle(p1[0], p1[1], 10)
if a1 != None and a2 != None:
start = a1 if a1 < a2 else a1 - TWO_PI
if r2 <= 0:
a2 = a2 - TWO_PI
abs_angle = abs(a2 - start)
if abs_angle > TWO_PI:
if a2 < 0:
a2 += TWO_PI
else:
a2 -= TWO_PI
if abs(a2 - start) != TWO_PI:
arc_func(p2[0], p2[1], r2 * 2, r2 * 2, start, a2, mode=2,
**kwargs)
if DEBUG:
textSize(TEXT_HEIGHT)
text(str(int(degrees(start - a2))), p2[0], p2[1])
else:
# when the the segment is smaller than the diference between
# radius, circ_circ_tangent won't renturn the angle
if DEBUG:
ellipse(p2[0], p2[1], r2 * 2, r2 * 2)
if a1:
vertex_func(p12[0], p12[1])
if a2:
vertex_func(p21[0], p21[1])
endShape(CLOSE)
# check augmented poly aproximation instersection
if check_intersection:
return is_poly_self_intersecting(_points)
def arc_augmented_points(op_list, or_list=None, **kwargs):
"""
A version of arc_augmented_poly that returns the points
of a poly-approximation with arc_pts
"""
def mid(p0, p1):
return (p0[0] + p1[0]) * 0.5, (p0[1] + p1[1]) * 0.5
assert op_list, 'No points were provided.'
assert not ('radius' in kwargs and or_list),\
"You can't use a radii list and a radius kwarg together."
if 'radius' in kwargs and or_list == None:
or_list = [kwargs.pop('radius')] * len(op_list)
r2_list = list(or_list)
assert len(op_list) == len(r2_list),\
'Number of points and radii provided not the same.'
auto_flip = kwargs.pop('auto_flip', True)
gradual_flip = kwargs.pop('gradual_flip', False) # experimentas
pts_list = []
# remove overlapping adjacent points
p_list, r_list = [], []
p2_list = list(op_list)
for i1, p1 in enumerate(p2_list):
i2 = (i1 + 1) % len(p2_list)
p2, r2, r1 = p2_list[i2], r2_list[i2], r2_list[i1]
d = dist(p1[0], p1[1], p2[0], p2[1])
if d > abs(r1 - r2):
p_list.append(p1)
r_list.append(r1)
else:
p2_list[i2] = mid(p1, p2)
r2_list[i2] = max(r1, r2)
# invert radius
for i1, p1 in enumerate(p_list):
i0 = (i1 - 1)
p0 = p_list[i0]
i2 = (i1 + 1) % len(p_list)
p2 = p_list[i2]
a = triangle_area(p0, p1, p2) / 1000
if auto_flip and a < 0:
r_list[i1] = -r_list[i1]
if gradual_flip:
r_list[i1] = r_list[i1] * min(1, abs(a))
# reduce radius that won't fit
for i1, p1 in enumerate(p_list):
i2 = (i1 + 1) % len(p_list)
p2, r2, r1 = p_list[i2], r_list[i2], r_list[i1]
r_list[i1], r_list[i2] = reduce_radius(p1, p2, r1, r2)
# calculate the tangents
a_list = []
for i1, p1 in enumerate(p_list):
i2 = (i1 + 1) % len(p_list)
p2, r2, r1 = p_list[i2], r_list[i2], r_list[i1]
cct = circ_circ_tangent(p1, p2, r1, r2)
a_list.append(cct)
# now draw it!
for i1, ia in enumerate(a_list):
i2 = (i1 + 1) % len(a_list)
p1, p2, r1, r2 = p_list[i1], p_list[i2], r_list[i1], r_list[i2]
a1, p11, p12 = ia
a2, p21, p22 = a_list[i2]
if DEBUG:
circle(p1[0], p1[1], r1 * 2)
if a1 != None and a2 != None:
start = a1 if a1 < a2 else a1 - TWO_PI # was <
if r2 < 0: # was <=
a2 = a2 - TWO_PI
abs_angle = abs(a2 - start)
if abs_angle > TWO_PI:
if a2 < 0:
a2 += TWO_PI
else:
a2 -= TWO_PI
if abs(a2 - start) != TWO_PI:
pts_list.extend(arc_pts(p2[0], p2[1], r2 * 2, r2 * 2, start, a2,
**kwargs))
if DEBUG:
textSize(TEXT_HEIGHT)
text(' {:0.2f} {:0.2f}'.format(r2, degrees(abs_angle)), p2[0], p2[1])
else:
# when the the segment is smaller than the diference between
# radius, circ_circ_tangent won't renturn the angle
if DEBUG:
ellipse(p2[0], p2[1], r1, r1)
if a1:
pts_list.append((p12[0], p12[1]))
if a2:
pts_list.append((p21[0], p21[1]))
return pts_list
def reduce_radius(p1, p2, r1, r2, reduce_both=True):
d = dist(p1[0], p1[1], p2[0], p2[1])
ri = abs(r1 - r2)
if d - ri <= 0:
if reduce_both:
r1, r2 = (remap(d, ri + 1, 0, r1, (r1 + r2) / 2),
remap(d, ri + 1, 0, r2, (r1 + r2) / 2))
elif abs(r1) > abs(r2):
r1 = remap(d, ri + 1, 0, r1, r2)
else:
r2 = remap(d, ri + 1, 0, r2, r1)
return r1, r2
def circ_circ_tangent(p1, p2, r1, r2):
d = dist(p1[0], p1[1], p2[0], p2[1])
ri = r1 - r2
line_angle = atan2(p1[0] - p2[0], p2[1] - p1[1])
if d - abs(ri) >= 0:
theta = asin(ri / float(d))
x1 = -cos(line_angle + theta) * r1
y1 = -sin(line_angle + theta) * r1
x2 = -cos(line_angle + theta) * r2
y2 = -sin(line_angle + theta) * r2
return (line_angle + theta,
(p1[0] - x1, p1[1] - y1),
(p2[0] - x2, p2[1] - y2))
else:
return (None,
(p1[0], p1[1]),
(p2[0], p2[1]))
def bar(x1, y1, x2, y2, thickness, **kwargs):
"""
Draw a thick strip with rounded ends.
It can be shorter than the supporting (axial) line segment.
# 2020-9-25 First rewrite attempt based on var_bar + arc_func + **kwargs
# 2020-9-26 Let's do everything in var_bar()!
"""
var_bar(x1, y1, x2, y2, thickness / 2, **kwargs)
def var_bar(p1x, p1y, p2x, p2y, r1, r2=None, **kwargs):
"""
Tangent/tangent shape on 2 circles of arbitrary radius
Expected keyword arguments:
shorter: Will draw a shorter "bar" (only if r1 == r2)
internal: When too short draws circle from smaller radius end
inside circle from larger radius end (default is True)
arc_func: Allows choosing the arc funcio, like p_arc (default is b_arc)
num_points: Will be passed to the arc_func (works with p_arc)
# 2020-9-25 Added **kwargs, now one can use arc_func=p_arc & num_points=N
# 2020-9-26 Added treatment to shorter=N so as to incorporate bar() use.
Added a keyword argument, internal=True is the default,
internal=False disables drawing internal circle.
Minor cleanups, and removed "with" for pushMatrix().
# 2022-6-10 Removed unused variables & changed behaviour for small distances,
when internal=False, draw a circle from the larger radius.
"""
r2 = r2 if r2 is not None else r1
draw_internal_circle = kwargs.pop('internal', True)
arc_func = kwargs.pop('arc_func', b_arc)
shorter = kwargs.pop('shorter', 0)
assert not (shorter and r1 != r2),\
"Can't draw shorter var_bar with different radii. r1={} r2={}".format(r1, r2)
assert not (kwargs and arc_func == b_arc),\
"Can't use keyword arguments with b_arc. {}".format(kwargs)
d = dist(p1x, p1y, p2x, p2y)
ri = r1 - r2
if d > abs(ri):
clipped_ri_over_d = min(1, max(-1, ri / d))
beta = asin(clipped_ri_over_d) + HALF_PI
push()
translate(p1x, p1y)
angle = atan2(p1x - p2x, p2y - p1y)
rotate(angle + HALF_PI)
beginShape()
offset = shorter / 2.0 if shorter < d else d / 2.0
arc_func(offset, 0, r1 * 2, r1 * 2,
-beta - PI, beta - PI, mode=2, **kwargs)
arc_func(d - offset, 0, r2 * 2, r2 * 2,
beta - PI, PI - beta, mode=2, **kwargs)
endShape(CLOSE)
pop()
else: # draw a circle with the bigger radius if distance is too small
r = max(r1, r2)
x, y = (p1x, p1y) if r1 > r2 else (p2x, p2y)
arc_func(x, y, r * 2, r * 2, 0, TWO_PI, **kwargs)
if draw_internal_circle:
r = min(r1, r2)
x, y = (p1x, p1y) if r1 < r2 else (p2x, p2y)
arc_func(x, y, r * 2, r * 2, 0, TWO_PI, **kwargs)
def var_bar_pts(p1x, p1y, p2x, p2y, r1, r2=None, **kwargs):
"""
Tangent/tangent shape on 2 circles of arbitrary radius
Expected keyword arguments:
shorter: will make a shorter "bar" (only if r1 == r2)
num_points: will be passed to arc_pts (default there is 24)
internal: unavailable
"""
r2 = r2 if r2 is not None else r1
shorter = kwargs.pop('shorter', 0)
assert not (shorter and r1 != r2),\
"Can't draw shorter var_bar with different radii"
d = dist(p1x, p1y, p2x, p2y)
ri = r1 - r2
result = []
if d > abs(ri):
clipped_ri_over_d = min(1, max(-1, ri / d))
beta = asin(clipped_ri_over_d) + HALF_PI
angle = atan2(p1x - p2x, p2y - p1y) + HALF_PI
offset = shorter / 2.0 if shorter < d else d / 2.0
result.extend(arc_pts(offset, 0, r1 * 2, r1 * 2,
-beta - PI, beta - PI, **kwargs))
result.extend(arc_pts(d - offset, 0, r2 * 2, r2 * 2,
beta - PI, PI - beta, **kwargs))
return rotate_offset_points(result, angle, p1x, p1y)
else:
r = max(r1, r2)
x, y = (p1x, p1y) if r1 > r2 else (p2x, p2y)
return arc_pts(x, y, r * 2, r * 2, 0, TWO_PI, **kwargs)
def rotate_offset_points(pts, angle, offx, offy, y0=0, x0=0):
return [(((xp - x0) * cos(angle) - (yp - y0) * sin(angle)) + x0 + offx,
((yp - y0) * cos(angle) + (xp - x0) * sin(angle)) + y0 + offy)
for xp, yp in pts]

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 285 KiB

Wyświetl plik

@ -0,0 +1,77 @@
from random import sample
from itertools import product
from villares.helpers import lerp_tuple, save_png_with_src
import arcs
BORDER = 200
SIZE = 150
NUM_POINTS = 5
drag = None
gestures = ([], [])
rs = 1
def setup():
size(850, 850)
def draw():
background(200)
# stroke(255)
# stroke_weight(2)
# if gestures[0]:
# with begin_closed_shape():
# vertices(gestures[0])
# if gestures[1]:
# with begin_closed_shape():
# vertices(gestures[1])
no_stroke()
fill(0, 0, 100)
for x, y in gestures[0]:
circle(x, y, 4)
fill(0, 100, 0)
for x, y in gestures[1]:
circle(x, y, 4)
no_fill()
for i, p1 in list(enumerate(gestures[1])): # [::10]:
j = floor(remap(i, 0, len(gestures[1]),
0, len(gestures[0])))
p0 = gestures[0][j]
stroke_weight(0.5)
stroke(0, 100)
line(*p0, *p1)
stroke_weight(2)
stroke(100, 0, 0)
lpts = [lerp_tuple(p0, p1, k / 10) for k in range(1, 10)]
points(lpts)
def key_pressed():
if key == ' ':
generate()
elif key == 's':
save_png_with_src()
def split(pts, d=20):
if len(pts) == 1:
return pts
elif len(pts) > 2:
return split(pts[:2], d) + split(pts[1:], d)[1:]
elif dist(*pts[0], *pts[1]) > d:
return split([pts[0], lerp_tuple(pts[0], pts[1], 0.5), pts[1]], d)
else:
return pts
def generate():
grid = list(product(range(BORDER, width - BORDER + 1, SIZE),
range(BORDER, height - BORDER + 1, SIZE)))
p_list = sample(grid, NUM_POINTS * 2)
g0 = arcs.arc_augmented_points(p_list[:NUM_POINTS], radius=100, seg_len=20)
gestures[0][:] = split(g0 + g0[:1], 22)
g1 = arcs.arc_augmented_points(p_list[NUM_POINTS:], radius=100, seg_len=12)
gestures[1][:] = split(g1 + g1[:1], 16)
if len(gestures[1]) < len(gestures[0]):
g0 = gestures[0][:]
g1 = gestures[1][:]
gestures[1][:], gestures[0][:] = g0, g1

Wyświetl plik

@ -27,6 +27,14 @@ Here are listed some of the tools I have been using:
---
### sketch_2022_07_04
![sketch_2022_07_04](2022/sketch_2022_07_04/sketch_2022_07_04.png)
[sketch_2022_07_04](https://github.com/villares/sketch-a-day/tree/main/2022/sketch_2022_07_04) [[py5](https://py5.ixora.io/)]
---
### sketch_2022_07_03
![sketch_2022_07_03](2022/sketch_2022_07_03/sketch_2022_07_03.gif)