Last active
December 19, 2022 21:29
-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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