In the course of running off some Superformula plots, I found what must be my original stash of B-size plotter paper. Although it wasn’t archival paper and has yellowed a bit with age, it’s the smoothest and creamiest paper I’ve touched in quite some time: far nicer than the cheap stuff I picked up while reconditioning the HP 7475A plotter & its assorted pens.

Once in a while, all my errors and omissions cancel out enough to produce interesting results on that historic paper, hereby documented for future reference…

A triangle starburst:

A symmetric starburst:

Complex meshed ovals:

They look better in person, of course. Although inkjet printers produce more accurate results in less time, those old pen plots definitely *look better* in some sense.

The demo program lets you jam a fixed set of parameters into the plot, so (at least in principle) one could reproduce a plot from the parameters in the lower right corner. Here you go:

The triangle starburst:

The symmetric starburst:

The meshed ovals:

The current Python / Chiplotle source code as a GitHub gist:

from chiplotle import * | |

from math import * | |

from datetime import * | |

from time import * | |

from types import * | |

import random | |

def superformula_polar(a, b, m, n1, n2, n3, phi): | |

''' Computes the position of the point on a | |

superformula curve. | |

Superformula has first been proposed by Johan Gielis | |

and is a generalization of superellipse. | |

see: http://en.wikipedia.org/wiki/Superformula | |

Tweaked to return polar coordinates | |

''' | |

t1 = cos(m * phi / 4.0) / a | |

t1 = abs(t1) | |

t1 = pow(t1, n2) | |

t2 = sin(m * phi / 4.0) / b | |

t2 = abs(t2) | |

t2 = pow(t2, n3) | |

t3 = -1 / float(n1) | |

r = pow(t1 + t2, t3) | |

if abs(r) == 0: | |

return (0, 0) | |

else: | |

# return (r * cos(phi), r * sin(phi)) | |

return (r, phi) | |

def supershape(width, height, m, n1, n2, n3, | |

point_count=10 * 1000, percentage=1.0, a=1.0, b=1.0, travel=None): | |

'''Supershape, generated using the superformula first proposed | |

by Johan Gielis. | |

- `points_count` is the total number of points to compute. | |

- `travel` is the length of the outline drawn in radians. | |

3.1416 * 2 is a complete cycle. | |

''' | |

travel = travel or (10 * 2 * pi) | |

# compute points... | |

phis = [i * travel / point_count | |

for i in range(1 + int(point_count * percentage))] | |

points = [superformula_polar(a, b, m, n1, n2, n3, x) for x in phis] | |

# scale and transpose... | |

path = [] | |

for r, a in points: | |

x = width * r * cos(a) | |

y = height * r * sin(a) | |

path.append(Coordinate(x, y)) | |

return Path(path) | |

# RUN DEMO CODE | |

if __name__ == '__main__': | |

override = False | |

plt = instantiate_plotters()[0] | |

# plt.write('IN;') | |

if plt.margins.soft.width < 11000: # A=10365 B=16640 | |

maxplotx = (plt.margins.soft.width / 2) - 100 | |

maxploty = (plt.margins.soft.height / 2) - 150 | |

legendx = maxplotx - 2900 | |

legendy = -(maxploty - 750) | |

tscale = 0.45 | |

numpens = 4 | |

# prime/10 = number of spikes | |

m_values = [n / 10.0 for n in [11, 13, 17, 19, 23]] | |

# ring-ness 0.1 to 2.0, higher is larger | |

n1_values = [ | |

n / 100.0 for n in range(55, 75, 2) + range(80, 120, 5) + range(120, 200, 10)] | |

else: | |

maxplotx = plt.margins.soft.width / 2 | |

maxploty = plt.margins.soft.height / 2 | |

legendx = maxplotx - 3000 | |

legendy = -(maxploty - 900) | |

tscale = 0.45 | |

numpens = 6 | |

m_values = [n / 10.0 for n in [11, 13, 17, 19, 23, 29, 31, | |

37, 41, 43, 47, 53, 59]] # prime/10 = number of spikes | |

# ring-ness 0.1 to 2.0, higher is larger | |

n1_values = [ | |

n / 100.0 for n in range(15, 75, 2) + range(80, 120, 5) + range(120, 200, 10)] | |

print " Max: ({},{})".format(maxplotx, maxploty) | |

# spiky-ness 0.1 to 2.0, higher is spiky-er (mostly) | |

n2_values = [ | |

n / 100.0 for n in range(10, 60, 2) + range(65, 100, 5) + range(110, 200, 10)] | |

plt.write(chr(27) + '.H200:') # set hardware handshake block size | |

plt.set_origin_center() | |

# scale based on B size characters | |

plt.write(hpgl.SI(tscale * 0.285, tscale * 0.375)) | |

# slow speed for those abrupt spikes | |

plt.write(hpgl.VS(10)) | |

while True: | |

# standard loadout has pen 1 = fine black | |

plt.write(hpgl.PA([(legendx, legendy)])) | |

pen = 1 | |

plt.select_pen(pen) | |

plt.write(hpgl.PA([(legendx, legendy)])) | |

plt.write(hpgl.LB("Started " + str(datetime.today()))) | |

if override: | |

m = 4.1 | |

n1_list = [1.15, 0.90, 0.25, 0.59, 0.51, 0.23] | |

n2_list = [0.70, 0.58, 0.32, 0.28, 0.56, 0.26] | |

else: | |

m = random.choice(m_values) | |

n1_list = random.sample(n1_values, numpens) | |

n2_list = random.sample(n2_values, numpens) | |

pen = 1 | |

for n1, n2 in zip(n1_list, n2_list): | |

n3 = n2 | |

print "{0} - m: {1:.1f}, n1: {2:.2f}, n2=n3: {3:.2f}".format(pen, m, n1, n2) | |

plt.select_pen(pen) | |

plt.write(hpgl.PA([(legendx, legendy - 100 * pen)])) | |

plt.write( | |

hpgl.LB("Pen {0}: m={1:.1f} n1={2:.2f} n2=n3={3:.2f}".format(pen, m, n1, n2))) | |

e = supershape(maxplotx, maxploty, m, n1, n2, n3) | |

plt.write(e) | |

pen = pen + 1 if (pen % numpens) else 1 | |

pen = 1 | |

plt.select_pen(pen) | |

plt.write(hpgl.PA([(legendx, legendy - 100 * (numpens + 1))])) | |

plt.write(hpgl.LB("Ended " + str(datetime.today()))) | |

plt.write(hpgl.PA([(legendx, legendy - 100 * (numpens + 2))])) | |

plt.write(hpgl.LB("More at https://softsolder.com/?s=7475a")) | |

plt.select_pen(0) | |

plt.write(hpgl.PA([(-maxplotx,maxploty)])) | |

print "Waiting for plotter... ignore timeout errors!" | |

sleep(40) | |

while NoneType is type(plt.status): | |

sleep(5) | |

print "Load more paper, then ..." | |

print " ... Press ENTER on the plotter to continue" | |

plt.clear_digitizer() | |

plt.digitize_point() | |

plotstatus = plt.status | |

while (NoneType is type(plotstatus)) or (0 == int(plotstatus) & 0x04): | |

plotstatus = plt.status | |

print "Digitized: " + str(plt.digitized_point) |