Skip to content

Instantly share code, notes, and snippets.

@scruss
Last active December 19, 2022 21:29
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save scruss/529a8ad86ea4906cd7db999b285574d0 to your computer and use it in GitHub Desktop.
Save scruss/529a8ad86ea4906cd7db999b285574d0 to your computer and use it in GitHub Desktop.
Slightly imperfect Python simulation of the "HOOT-NANNY" (or Magic Designer) drawing toy
#!/usr/bin/env python3
# hootnanny.py - simulate Hoot-Nanny / Magic Designer drawing toy
# Currently hard-coded to output figure "25KM" in HP-GL,
# a simple but somewhat obsolete plotting language.
# Output figures as close to actual sizes as I could manage.
# All dimensions in inches, unfortunately, but HP-GL is bilingual.
# scruss, 2022-02 - code cleanup - 2022-12
# Licence: CC-BY-SA - share freely, but credit me and make your
# improvements freely available for all
# -*- coding: utf-8 -*-
from math import sin, cos, sqrt, radians, degrees, atan2
def arm_length(letter):
"""
each metal arm has pivot holes marked A - R, at 1/4" spacing
from 5.75 to 1.5". The perpendicular distance from the pivot holes
to the pencil is 5/16". This doesn't add much to the overall arm
length, but it's easy to take into account
"""
if len(letter) > 1 or letter > "R" or letter < "A":
# wrong input
return None
return sqrt(
(5.75 - (ord(letter) - ord("A")) / 4.0) ** 2 + (5 / 16) ** 2
)
def angle_setting(deg):
"""
the hoot-nanny has two smaller gears: one fixed, the other on a movable
track with a scale 10 to 70 degrees. The indicator isn't the same as the
angle between the gears: when 40 degrees is indicated, the gears are
60 degrees apart. So the actual range is 30 to 90 degrees.
The smaller gears are 1" in diameter (32 teeth) on a 7" PCD
Each smaller gear has a pivot point for the arms at 3/8" radius
The large gear is 6" in diameter (192 teeth)
"""
return 20 + deg
def distance(p1, p2):
"""
return Euclidean distance between two points in 2d space
Note that I'm using a two element list to represent [x, y]
"""
return sqrt((p1[0] - p2[0]) ** 2 + (p1[1] - p2[1]) ** 2)
def epitrochoid(r1, r2, d, theta):
"""
If you held the paper on the Hoot-Nanny fixed, the smaller
gears would describe epitrochoids around the larger
"""
return [
(r1 + r2) * cos(theta) - d * cos(((r1 + r2) / r2) * theta),
(r1 + r2) * sin(theta) - d * sin(((r1 + r2) / r2) * theta),
]
def nearest_circle_intersection(p0, p1, r0, r1):
"""
return the intersection point of two circles nearer the origin
if equidistant, may not return the point you expect
"""
d = distance(p0, p1)
a = (r0**2 - r1**2 + d**2) / (2 * d)
h = sqrt(r0**2 - a**2)
x2 = p0[0] + a * (p1[0] - p0[0]) / d
y2 = p0[1] + a * (p1[1] - p0[1]) / d
p3 = [x2 + h * (p1[1] - p0[1]) / d, y2 - h * (p1[0] - p0[0]) / d]
p4 = [x2 - h * (p1[1] - p0[1]) / d, y2 + h * (p1[0] - p0[0]) / d]
if distance(p3, [0, 0]) <= distance(p4, [0, 0]):
return p3
else:
return p4
# this is where I hard-code the "25KM" setting
arm1 = arm_length("K")
arm2 = arm_length("M")
t = angle_setting(25)
# output the figure in HP-GL: it may be old, but it's very simple
# the path steps around a circle once in full degrees
for i in range(360):
"""
p is the fixed smaller gear path:
* large radius = 3"
* small radius = 1/2"
* cam arm length = 3/8"
"""
p = epitrochoid(3, 1 / 2, 3 / 8, radians(i))
# q is the movable smaller gear path
q = epitrochoid(3, 1 / 2, 3 / 8, radians(i - t))
# pencil holder is where the two arms intersect
# coordinates of ph are [x, y] in inches (blecch)
ph = nearest_circle_intersection(p, q, arm1, arm2)
# scale to HP-GL units: 40 units / mm == 1016 units / inch
# thanks, Carl Edvard Johansson!
x = int(1016 * ph[0])
y = int(1016 * ph[1])
if i == 0:
# initialize and plot first point
print("IN;") # HP-GL INitialize plotter
print("SP1;") # HP-GL Select Pen 1
print("PU", x, ",", y, ";") # HP-GL Pen Up (= move) to x, y
print("PD;") # HP-GL Pen Down (= plot)
# save first point to close figure
first = [x, y]
else:
print("PD", x, ",", y, ";") # HP-GL Pen Down (= plot) to x, y
# close figure
print("PD", first[0], ",", first[1], ";")
# plot is now finished, so pick up pen and put it away
print("PU;") # HP-GL Pen Up (= pick up pen, if no coordinates)
print("SP0;") # HP-GL Select Pen 0 (= put the pen away)
IN;
SP1;
PU 29 , -1052 ;
PD;
PD 55 , -1107 ;
PD 87 , -1158 ;
PD 124 , -1203 ;
PD 164 , -1244 ;
PD 209 , -1278 ;
PD 256 , -1305 ;
PD 306 , -1325 ;
PD 358 , -1338 ;
PD 411 , -1342 ;
PD 464 , -1337 ;
PD 516 , -1324 ;
PD 568 , -1302 ;
PD 617 , -1272 ;
PD 664 , -1233 ;
PD 708 , -1186 ;
PD 748 , -1131 ;
PD 783 , -1069 ;
PD 814 , -1001 ;
PD 839 , -927 ;
PD 859 , -848 ;
PD 874 , -765 ;
PD 883 , -680 ;
PD 886 , -593 ;
PD 883 , -506 ;
PD 875 , -420 ;
PD 863 , -335 ;
PD 845 , -254 ;
PD 823 , -177 ;
PD 797 , -105 ;
PD 768 , -38 ;
PD 736 , 20 ;
PD 702 , 72 ;
PD 667 , 116 ;
PD 630 , 153 ;
PD 594 , 181 ;
PD 558 , 200 ;
PD 523 , 212 ;
PD 490 , 215 ;
PD 460 , 211 ;
PD 433 , 200 ;
PD 410 , 181 ;
PD 391 , 156 ;
PD 376 , 126 ;
PD 367 , 91 ;
PD 363 , 51 ;
PD 365 , 9 ;
PD 372 , -36 ;
PD 386 , -83 ;
PD 406 , -131 ;
PD 431 , -178 ;
PD 462 , -226 ;
PD 498 , -271 ;
PD 540 , -315 ;
PD 586 , -355 ;
PD 636 , -392 ;
PD 689 , -424 ;
PD 746 , -451 ;
PD 805 , -474 ;
PD 865 , -490 ;
PD 926 , -501 ;
PD 986 , -505 ;
PD 1046 , -503 ;
PD 1104 , -494 ;
PD 1160 , -479 ;
PD 1211 , -458 ;
PD 1259 , -430 ;
PD 1301 , -397 ;
PD 1338 , -358 ;
PD 1367 , -314 ;
PD 1390 , -266 ;
PD 1405 , -214 ;
PD 1412 , -159 ;
PD 1410 , -101 ;
PD 1400 , -41 ;
PD 1381 , 20 ;
PD 1354 , 82 ;
PD 1318 , 143 ;
PD 1274 , 204 ;
PD 1222 , 263 ;
PD 1164 , 320 ;
PD 1100 , 374 ;
PD 1030 , 424 ;
PD 957 , 470 ;
PD 880 , 512 ;
PD 801 , 548 ;
PD 722 , 579 ;
PD 643 , 605 ;
PD 565 , 624 ;
PD 489 , 638 ;
PD 418 , 646 ;
PD 350 , 648 ;
PD 288 , 644 ;
PD 232 , 636 ;
PD 182 , 623 ;
PD 140 , 605 ;
PD 105 , 584 ;
PD 77 , 559 ;
PD 58 , 533 ;
PD 47 , 504 ;
PD 43 , 475 ;
PD 47 , 445 ;
PD 59 , 417 ;
PD 78 , 389 ;
PD 104 , 363 ;
PD 136 , 340 ;
PD 174 , 320 ;
PD 217 , 304 ;
PD 265 , 293 ;
PD 316 , 286 ;
PD 370 , 284 ;
PD 427 , 287 ;
PD 484 , 296 ;
PD 542 , 310 ;
PD 600 , 329 ;
PD 657 , 354 ;
PD 712 , 385 ;
PD 764 , 420 ;
PD 813 , 460 ;
PD 857 , 504 ;
PD 896 , 551 ;
PD 931 , 602 ;
PD 959 , 654 ;
PD 980 , 709 ;
PD 995 , 764 ;
PD 1002 , 820 ;
PD 1002 , 875 ;
PD 994 , 928 ;
PD 979 , 979 ;
PD 956 , 1027 ;
PD 926 , 1070 ;
PD 888 , 1109 ;
PD 844 , 1143 ;
PD 793 , 1171 ;
PD 735 , 1192 ;
PD 673 , 1206 ;
PD 605 , 1213 ;
PD 534 , 1213 ;
PD 460 , 1205 ;
PD 383 , 1190 ;
PD 304 , 1168 ;
PD 226 , 1140 ;
PD 147 , 1105 ;
PD 71 , 1064 ;
PD -3 , 1018 ;
PD -74 , 968 ;
PD -140 , 915 ;
PD -202 , 859 ;
PD -258 , 801 ;
PD -307 , 743 ;
PD -350 , 685 ;
PD -386 , 627 ;
PD -414 , 572 ;
PD -434 , 519 ;
PD -448 , 469 ;
PD -454 , 424 ;
PD -453 , 383 ;
PD -445 , 347 ;
PD -432 , 317 ;
PD -413 , 293 ;
PD -389 , 275 ;
PD -362 , 264 ;
PD -331 , 260 ;
PD -297 , 262 ;
PD -262 , 272 ;
PD -226 , 288 ;
PD -190 , 311 ;
PD -155 , 341 ;
PD -121 , 376 ;
PD -89 , 417 ;
PD -60 , 463 ;
PD -35 , 513 ;
PD -13 , 567 ;
PD 2 , 625 ;
PD 14 , 685 ;
PD 21 , 746 ;
PD 22 , 809 ;
PD 18 , 872 ;
PD 7 , 934 ;
PD -7 , 994 ;
PD -29 , 1052 ;
PD -55 , 1107 ;
PD -87 , 1158 ;
PD -124 , 1203 ;
PD -164 , 1244 ;
PD -209 , 1278 ;
PD -256 , 1305 ;
PD -306 , 1325 ;
PD -358 , 1338 ;
PD -411 , 1342 ;
PD -464 , 1337 ;
PD -516 , 1324 ;
PD -568 , 1302 ;
PD -617 , 1272 ;
PD -664 , 1233 ;
PD -708 , 1186 ;
PD -748 , 1131 ;
PD -783 , 1069 ;
PD -814 , 1001 ;
PD -839 , 927 ;
PD -859 , 848 ;
PD -874 , 765 ;
PD -883 , 680 ;
PD -886 , 593 ;
PD -883 , 506 ;
PD -875 , 420 ;
PD -863 , 335 ;
PD -845 , 254 ;
PD -823 , 177 ;
PD -797 , 105 ;
PD -768 , 38 ;
PD -736 , -20 ;
PD -702 , -72 ;
PD -667 , -116 ;
PD -630 , -153 ;
PD -594 , -181 ;
PD -558 , -200 ;
PD -523 , -212 ;
PD -490 , -215 ;
PD -460 , -211 ;
PD -433 , -200 ;
PD -410 , -181 ;
PD -391 , -156 ;
PD -376 , -126 ;
PD -367 , -91 ;
PD -363 , -51 ;
PD -365 , -9 ;
PD -372 , 36 ;
PD -386 , 83 ;
PD -406 , 131 ;
PD -431 , 178 ;
PD -462 , 226 ;
PD -498 , 271 ;
PD -540 , 315 ;
PD -586 , 355 ;
PD -636 , 392 ;
PD -689 , 424 ;
PD -746 , 451 ;
PD -805 , 474 ;
PD -865 , 490 ;
PD -926 , 501 ;
PD -986 , 505 ;
PD -1046 , 503 ;
PD -1104 , 494 ;
PD -1160 , 479 ;
PD -1211 , 458 ;
PD -1259 , 430 ;
PD -1301 , 397 ;
PD -1338 , 358 ;
PD -1367 , 314 ;
PD -1390 , 266 ;
PD -1405 , 214 ;
PD -1412 , 159 ;
PD -1410 , 101 ;
PD -1400 , 41 ;
PD -1381 , -20 ;
PD -1354 , -82 ;
PD -1318 , -143 ;
PD -1274 , -204 ;
PD -1222 , -263 ;
PD -1164 , -320 ;
PD -1100 , -374 ;
PD -1030 , -424 ;
PD -957 , -470 ;
PD -880 , -512 ;
PD -801 , -548 ;
PD -722 , -579 ;
PD -643 , -605 ;
PD -565 , -624 ;
PD -489 , -638 ;
PD -418 , -646 ;
PD -350 , -648 ;
PD -288 , -644 ;
PD -232 , -636 ;
PD -182 , -623 ;
PD -140 , -605 ;
PD -105 , -584 ;
PD -77 , -559 ;
PD -58 , -533 ;
PD -47 , -504 ;
PD -43 , -475 ;
PD -47 , -445 ;
PD -59 , -417 ;
PD -78 , -389 ;
PD -104 , -363 ;
PD -136 , -340 ;
PD -174 , -320 ;
PD -217 , -304 ;
PD -265 , -293 ;
PD -316 , -286 ;
PD -370 , -284 ;
PD -427 , -287 ;
PD -484 , -296 ;
PD -542 , -310 ;
PD -600 , -329 ;
PD -657 , -354 ;
PD -712 , -385 ;
PD -764 , -420 ;
PD -813 , -460 ;
PD -857 , -504 ;
PD -896 , -551 ;
PD -931 , -602 ;
PD -959 , -654 ;
PD -980 , -709 ;
PD -995 , -764 ;
PD -1002 , -820 ;
PD -1002 , -875 ;
PD -994 , -928 ;
PD -979 , -979 ;
PD -956 , -1027 ;
PD -926 , -1070 ;
PD -888 , -1109 ;
PD -844 , -1143 ;
PD -793 , -1171 ;
PD -735 , -1192 ;
PD -673 , -1206 ;
PD -605 , -1213 ;
PD -534 , -1213 ;
PD -460 , -1205 ;
PD -383 , -1190 ;
PD -304 , -1168 ;
PD -226 , -1140 ;
PD -147 , -1105 ;
PD -71 , -1064 ;
PD 3 , -1018 ;
PD 74 , -968 ;
PD 140 , -915 ;
PD 202 , -859 ;
PD 258 , -801 ;
PD 307 , -743 ;
PD 350 , -685 ;
PD 386 , -627 ;
PD 414 , -572 ;
PD 434 , -519 ;
PD 448 , -469 ;
PD 454 , -424 ;
PD 453 , -383 ;
PD 445 , -347 ;
PD 432 , -317 ;
PD 413 , -293 ;
PD 389 , -275 ;
PD 362 , -264 ;
PD 331 , -260 ;
PD 297 , -262 ;
PD 262 , -272 ;
PD 226 , -288 ;
PD 190 , -311 ;
PD 155 , -341 ;
PD 121 , -376 ;
PD 89 , -417 ;
PD 60 , -463 ;
PD 35 , -513 ;
PD 13 , -567 ;
PD -2 , -625 ;
PD -14 , -685 ;
PD -21 , -746 ;
PD -22 , -809 ;
PD -18 , -872 ;
PD -7 , -934 ;
PD 7 , -994 ;
PD 29 , -1052 ;
PU;
SP0;
Display the source blob
Display the rendered blob
Raw
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
@scruss
Copy link
Author

scruss commented Dec 19, 2022

code to go with Slightly imperfect Hoot-Nanny/Magic Designer simulation – We Saw a Chicken …, as requested by Sandra Z. Keith

SVG created by importing km25.hpgl into Inkscape

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment