I'm trying to generalize a method for rounding the corners of outlines I'm drawing around a heartline—I already have a solution for when the radius is 1/2 the outline thickness (or stroke width, if you like). I'm using arcTo() to draw the curve.

In the diagram here, the control point for the arcTo() curve is D, defined by the nodes in the heartline path and the change in angle in that path (and constant for all rounding radii).

Given node A, radius r1, and the negative angle A (in this case –45deg to keep the drawing easy), the target for the arcTo() curve, B1, would be defined (Ax + r1 * cos(angle A + 90), Ay + r1 * sin(angleA + 90)).

So I would call arcTo(D, B1, r1) to get a curve that starts at E1 and ends at B1. So far, so good.

But let's say I want the rounding to have the radius r2. How would I define B2 so that I could call arcTo(D, B2, r2), and the function will give me E2 as a starting point? How I get Ax – length(A → C2), Ay + length(C2 → B2)?

]]>If you want to jump directly into the code, here's the source:

```
##############################
# Draw Wiggles using Drawbot #
##############################
"""
Script by Roberto Arista, you can find the related tutorial here: https://medium.com/@roberto_arista/how-to-draw-a-wiggle-between-two-points-with-python-and-drawbot-788006c18fb0
You can find drawbot here: http://www.drawbot.com/
Code distributed with no guarantee, use at your own risk
"""
### Modules
from math import radians, atan2, sqrt, sin, cos
from collections import namedtuple
### Constants
BLACK = (0, 0, 0)
Point = namedtuple('Point', ['x', 'y'])
### Function & procedures
def calcAngle(pt1, pt2):
return atan2((pt2.y - pt1.y), (pt2.x - pt1.x))
def calcDistance(pt1, pt2):
return sqrt((pt1.x - pt2.x)**2 + (pt1.y - pt2.y)**2)
def calcWiggle(pt1, pt2, waveLength, waveHeight, curveSquaring=.57):
assert 0 <= curveSquaring <= 1, 'curveSquaring should be a value between 0 and 1: {}'.format(curveSquaring)
assert waveLength > 0, 'waveLength smaller or equal to zero: {}'.format(waveLength)
diagonal = calcDistance(pt1, pt2)
angleRad = calcAngle(pt1, pt2)
howManyWaves = diagonal//int(waveLength)
waveInterval = diagonal/float(howManyWaves)
maxBcpLength = sqrt((waveInterval/4.)**2+(waveHeight/2.)**2)
bcpLength = maxBcpLength*curveSquaring
bcpInclination = calcAngle(Point(0,0), Point(waveInterval/4., waveHeight/2.))
wigglePoints = [pt1]
prevFlexPt = pt1
polarity = 1
for waveIndex in range(0, int(howManyWaves*2)):
bcpOutAngle = angleRad+bcpInclination*polarity
bcpOut = Point(prevFlexPt.x+cos(bcpOutAngle)*bcpLength, prevFlexPt.y+sin(bcpOutAngle)*bcpLength)
flexPt = Point(prevFlexPt.x+cos(angleRad)*waveInterval/2., prevFlexPt.y+sin(angleRad)*waveInterval/2.)
bcpInAngle = angleRad+(radians(180)-bcpInclination)*polarity
bcpIn = Point(flexPt.x+cos(bcpInAngle)*bcpLength, flexPt.y+sin(bcpInAngle)*bcpLength)
wigglePoints.append((bcpOut, bcpIn, flexPt))
polarity *= -1
prevFlexPt = flexPt
return wigglePoints
def drawCurvesSequence(wigglePoints):
myBez = BezierPath()
myBez.moveTo(wigglePoints[0])
for eachBcpOut, eachBcpIn, eachAnchor in wigglePoints[1:]:
myBez.curveTo(eachBcpOut, eachBcpIn, eachAnchor)
myBez.endPath()
drawPath(myBez)
### Variables
pt1 = Point(50, 50)
pt2 = Point(150, 60)
### Instructions
size(400, 400)
oval(pt1.x-1, pt1.y-1, 2, 2)
oval(pt2.x-1, pt2.y-1, 2, 2)
stroke(*BLACK)
strokeWidth(.5)
fill(None)
wigglePoints = calcWiggle(pt1, pt2, 16, 36, .7)
drawCurvesSequence(wigglePoints)
```

]]>