chiark / gitweb /
Merge commit '875be22af707972efae3359b08ec78a328c91f59'
authorIan Jackson <ijackson@chiark.greenend.org.uk>
Thu, 28 Dec 2023 11:14:32 +0000 (11:14 +0000)
committerIan Jackson <ijackson@chiark.greenend.org.uk>
Thu, 28 Dec 2023 11:14:32 +0000 (11:14 +0000)
Signed-off-by: Ian Jackson <ijackson@chiark.greenend.org.uk>
210 files changed:
.gitignore [new file with mode: 0644]
35mmjack-dummy.scad [new file with mode: 0644]
GPL-3 [new file with mode: 0644]
Huxley-INSTRUCTIONS [new file with mode: 0644]
Makefile [new file with mode: 0644]
README [new file with mode: 0644]
README.md
adafruit-powerboost-1000.scad [new file with mode: 0644]
adafruit-powerboost-500.scad [new file with mode: 0644]
adafruit-powerboost-common.scad [new file with mode: 0644]
anglepoise-neck.scad [new file with mode: 0644]
anglepoise-neck.slic3r [new file with mode: 0644]
anke-gps-bracket.scad [new file with mode: 0644]
atreic-piano-stand.scad [new file with mode: 0644]
axlepin.scad [new file with mode: 0644]
belt-cut-jig-common.scad [new file with mode: 0644]
belt-hole-cut-jig-simple.scad [new file with mode: 0644]
belt-hole-cut-jig.scad [new file with mode: 0644]
belt-slot-cut-jig,JigT.auto.slic3r [new file with mode: 0644]
belt-slot-cut-jig,Kit.auto.slic3r [new file with mode: 0644]
belt-slot-cut-jig.scad [new file with mode: 0644]
bike-lipo-box-gland.scad [new file with mode: 0644]
bike-lipo-box.scad [new file with mode: 0644]
bike-phone-mount.scad [new file with mode: 0644]
bike-stalk-led-mount.scad [new file with mode: 0644]
biscuits.scad [new file with mode: 0644]
brompton-computer-guard.scad [new file with mode: 0644]
cable-hole-trunking-cover.scad [new file with mode: 0644]
cable-splice-clamp.scad [new file with mode: 0644]
calib-fit.scad [new file with mode: 0644]
camera-mount.scad [new file with mode: 0644]
chimney-cable-retainer.scad [new file with mode: 0644]
clip-spring-holder-clip.scad [new file with mode: 0644]
cliphook.scad [new file with mode: 0644]
commitid-2d-test.scad [new file with mode: 0644]
commitid-best-test.scad.pl [new file with mode: 0755]
commitid-cube-test-X.scad [new file with mode: 0644]
commitid-cube-test-Y.scad [new file with mode: 0644]
commitid-cube-test.scad [new file with mode: 0644]
commitid-layering-test.scad [new file with mode: 0644]
crossbar-computer-led-mount.scad [new file with mode: 0644]
dell-psu-glow-lampshade.scad [new file with mode: 0644]
deore-crank-remover.scad [new file with mode: 0644]
digispark-with-cable.scad [new file with mode: 0644]
distort-stl [new file with mode: 0755]
diziet-utils/COPYING [new file with mode: 0644]
diziet-utils/README.md [new file with mode: 0644]
diziet-utils/commitid.scad.pl [moved from commitid.scad.pl with 100% similarity]
diziet-utils/funcs.scad.cpp [moved from funcs.scad.cpp with 100% similarity]
diziet-utils/reprap-objects.make [moved from reprap-objects.make with 100% similarity]
diziet-utils/threads.scad [new file with mode: 0644]
diziet-utils/toplevel-find [moved from toplevel-find with 100% similarity]
diziet-utils/toplevel-make [moved from toplevel-make with 100% similarity]
diziet-utils/utils.scad [new file with mode: 0644]
doveclip.scad [new file with mode: 0644]
dovecliptest.scad [new file with mode: 0644]
dungeonquest-cone.scad [new file with mode: 0644]
dungeonquest-cone.slic3r [new file with mode: 0644]
earring-stand.scad [new file with mode: 0644]
electron-token.scad.pl [new file with mode: 0755]
fairphone-battery-case.scad [new file with mode: 0644]
fairphone-case-mounted.scad [new file with mode: 0644]
fairphone-case.scad [new file with mode: 0644]
fairphone4-case-coarse.scad [new file with mode: 0644]
fairphone4-case-mounted.scad [new file with mode: 0644]
fairphone4-case-tripod.scad [new file with mode: 0644]
fairphone4-case.scad [new file with mode: 0644]
filament-test.scad [new file with mode: 0644]
filamentclip.scad [new file with mode: 0644]
filamentspool-lt.scad [new file with mode: 0644]
filamentspool-number.eps.pl [new file with mode: 0755]
filamentspool-sm.scad [new file with mode: 0644]
filamentspool-storarm3.scad [new file with mode: 0644]
filamentspool.scad [new file with mode: 0644]
filamentteeth.scad [new file with mode: 0644]
filamenttrestle.scad [new file with mode: 0644]
fire-blanket-wall-mushroom.scad [new file with mode: 0644]
fire-blanket-wall-mushroom.slic3r [new file with mode: 0644]
floating-phases.scad [new file with mode: 0644]
floating-test.scad [new file with mode: 0644]
flyscreen-handle.scad [new file with mode: 0644]
flyscreen-wall-spacer.scad [new file with mode: 0644]
fruit-bowl-stand.scad [new file with mode: 0644]
hole-repair-20191117.scad [new file with mode: 0644]
hole-transfer-punch.scad [new file with mode: 0644]
holetest.scad [new file with mode: 0644]
hotel-piece-model.scad [new file with mode: 0644]
itx-aperture-grommet.scad [new file with mode: 0644]
keyring-kay.scad [new file with mode: 0644]
knifeblock,BlockPrint.auto.slic3r [new file with mode: 0644]
knifeblock-knives-filter [new file with mode: 0755]
knifeblock-knives-photo.jpg [new file with mode: 0644]
knifeblock-knives-trace.fig [new file with mode: 0644]
knifeblock.scad [new file with mode: 0644]
ksafe-base.scad [new file with mode: 0644]
laptop-camera-tripod-bracket.scad [new file with mode: 0644]
laptop-sound-cable-hooks.scad [new file with mode: 0644]
led-panel-ceiling-bracket.scad [new file with mode: 0644]
led-panel-ceiling-bracket.slic3r [new file with mode: 0644]
lemon-stand.scad.pl [new file with mode: 0755]
light-bracket.scad [new file with mode: 0644]
lipo-flat-mount.scad [new file with mode: 0644]
lock-inframe-bracket.scad [new file with mode: 0644]
m8-thin-washer.scad [new file with mode: 0644]
maglite-holder-photo.jpg [new file with mode: 0644]
maglite-holder-torch.fig [new file with mode: 0644]
maglite-holder.scad [new file with mode: 0644]
makita-drill-handle-blivet.scad [new file with mode: 0644]
manual-gcode-generator [new file with mode: 0755]
mic-camera-adapter.scad [new file with mode: 0644]
mic-table-clamp.scad [new file with mode: 0644]
nook-case-test.scad [new file with mode: 0644]
nook-case.scad [new file with mode: 0644]
nutbox.scad.m4 [new file with mode: 0644]
osstest-arm-hub-bracket.scad [new file with mode: 0644]
osstest-arm-net-bracket.scad [new file with mode: 0644]
osstest-arm-psu-bracket.scad [new file with mode: 0644]
pandemic-counter-letters.fig [new file with mode: 0644]
pandemic-counter.scad [new file with mode: 0644]
pandemic-counter.slic3r [new file with mode: 0644]
pandemic-quarantine-numbers.fig [new file with mode: 0644]
pandemic-quarantines.scad [new file with mode: 0644]
pannierstay.scad [new file with mode: 0644]
pattress-boxes-3-cover.scad [new file with mode: 0644]
pawn.scad [new file with mode: 0644]
pawn.slic3r [new file with mode: 0644]
pin-hinge.scad [new file with mode: 0644]
poster-tube-lid-coarse.scad [new file with mode: 0644]
poster-tube-lid-parametric.scad.pl [new file with mode: 0755]
poster-tube-lid.scad [new file with mode: 0644]
powerbank-anker-10000.eps [new file with mode: 0644]
powerbank-anker-10000.svg [new file with mode: 0644]
powerbank-bike-clamp.scad [new file with mode: 0644]
pull-cord-keeper.scad [new file with mode: 0644]
quacks-ingredients-L1.scad [new file with mode: 0644]
quacks-ingredients-L2.scad [new file with mode: 0644]
quacks-ingredients-L3.scad [new file with mode: 0644]
quacks-ingredients-L4.scad [new file with mode: 0644]
quacks-ingredients-L5.scad [new file with mode: 0644]
quacks-ingredients-counts [new file with mode: 0755]
quacks-ingredients-counts.scad [new file with mode: 0644]
quacks-ingredients-demos.scad [new file with mode: 0644]
quacks-ingredients-make-copy-gcodes [new file with mode: 0755]
quacks-ingredients-update-levels [new file with mode: 0755]
quacks-ingredients.scad [new file with mode: 0644]
quacks.ini [new file with mode: 0644]
question-question.fig [new file with mode: 0644]
question-token.scad [new file with mode: 0644]
ring-tests.scad [new file with mode: 0644]
rope-adjuster.scad [new file with mode: 0644]
rpi-mount.scad [new file with mode: 0644]
salter-scale-hook.scad [new file with mode: 0644]
scaffold-clamp-cleat.scad [new file with mode: 0644]
scaffold-clamp-common.scad [new file with mode: 0644]
scaffold-clamp-linear-bracket.scad [new file with mode: 0644]
scaffold-clamp-straphook.scad [new file with mode: 0644]
scaffold-clamp-tensioner.scad [new file with mode: 0644]
screw-recess-test-number.fig.pl [new file with mode: 0755]
screw-recess-test.scad [new file with mode: 0644]
sealing-box.scad.m4 [new file with mode: 0644]
secateurs-clip.scad [new file with mode: 0644]
sewing-table-end-profile-photo.jpg [new file with mode: 0644]
sewing-table-end-profile.fig [new file with mode: 0644]
sewing-table-front-profile-photo.jpg [new file with mode: 0644]
sewing-table-front-profile.fig [new file with mode: 0644]
sewing-table-jig.scad [new file with mode: 0644]
sewing-table-rear-profile-photo.jpg [new file with mode: 0644]
sewing-table-rear-profile.fig [new file with mode: 0644]
sewing-table-test.scad [new file with mode: 0644]
sewing-table.scad.m4 [new file with mode: 0644]
shelf-label-holder.scad [new file with mode: 0644]
simplephone-case-test.scad [new file with mode: 0644]
simplephone-case.scad [new file with mode: 0644]
size-tests.m-g [new file with mode: 0644]
sleepphone-cable-box.scad [new file with mode: 0644]
slic3r-config.ini [new file with mode: 0644]
smallfilamentclip.scad [new file with mode: 0644]
splitpin.scad [new file with mode: 0644]
sprinkler-spike-receptacle.scad [new file with mode: 0644]
startech-dell-usb-cable-retainer.scad [new file with mode: 0644]
steamer-handle-clip.scad [new file with mode: 0644]
stringing-test.scad [new file with mode: 0644]
summit-lantern-hook.scad [new file with mode: 0644]
svg-prep-dxf [new file with mode: 0755]
tablet-case-corner-mount.scad [new file with mode: 0644]
tablet-stand.scad [new file with mode: 0644]
test-cup.scad [new file with mode: 0644]
test-object.scad [new file with mode: 0644]
thread-external-test.scad [new file with mode: 0644]
thread-internal-test.scad [new file with mode: 0644]
threads.scad [changed from file to symlink]
topeak-mtx-tortec-expeditionrack-adapter.scad [new file with mode: 0644]
topeak-seatstay-lock.scad [new file with mode: 0644]
tower-base.scad [new file with mode: 0644]
trackpump-mutlihead-clip.scad [new file with mode: 0644]
trailerhubcap.scad [new file with mode: 0644]
treefoil.scad.pl [new file with mode: 0755]
tube-crossdrill-jig.scad [new file with mode: 0644]
utils.scad [changed from file to symlink]
velux-window-grip.scad [new file with mode: 0644]
velux-window-grip.slic3r [new file with mode: 0644]
wall-cable-hook.scad [new file with mode: 0644]
wardrobe-hook.scad [new file with mode: 0644]
warptest.scad [new file with mode: 0644]
warptest2.scad [new file with mode: 0644]
warptest3.scad [new file with mode: 0644]
wine-vacuum-adapter.scad [new file with mode: 0644]
xeno-drivebay-bracket.scad [new file with mode: 0644]
y-large-axlebar-washer.scad [new file with mode: 0644]
yubikey-5c-nano-loop.scad [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..f328663
--- /dev/null
@@ -0,0 +1,40 @@
+*~
+*.gcode
+light-bracket.stl
+*.aside
+*.stl
+*,*.auto.scad
+.*.d
+*.tmp
+*.fig.bak
+funcs.scad
+nutbox.scad
+powerbank-anker-10000.dxf*
+knifeblock-knives-*.dxf*
+pandemic-counter-l*.dxf
+pandemic-counter-l*.eps
+pandemic-quarantine-l*.dxf
+pandemic-quarantine-l*.eps
+screw-recess-test-number-s*.*
+question-question.dxf
+question-question.eps
+lemon-stand.scad
+electron-token.scad
+commitid.scad
+commitid-best-test.scad
+filamentspool-number-n*.dxf
+filamentspool-number-n*.eps
+sealing-box.scad
+sewing-table.scad
+maglite-holder-torch-curve.dxf
+maglite-holder-torch-curve.eps
+sewing-table-rear-profile.dxf
+sewing-table-rear-profile.eps
+sewing-table-front-profile.dxf
+sewing-table-front-profile.eps
+sewing-table-end-profile.dxf
+sewing-table-end-profile.eps
+sewing-table,Demo-flat.png
+poster-tube-lid-parametric.scad
+treefoil.scad
+quacks-L*.auto.ini
diff --git a/35mmjack-dummy.scad b/35mmjack-dummy.scad
new file mode 100644 (file)
index 0000000..1a675a5
--- /dev/null
@@ -0,0 +1,32 @@
+// -*- C -*-
+
+p2 = [ 0, 3.0 /2 ];
+p1 = p2 + [ -1.0, -1.0 ];
+p3 = [ 3.0, 2.5 /2 ];
+p4 = [ p3[0] + (3.2-2.5)/2 , 3.2 /2 ];
+p8 = [ 13.0, 8.0 /2 ];
+p5 = [ p8[0] - 8.5, 3.2 /2 ];
+p6 = [ p5[0] + (3.5-3.2)/2, 3.5 /2];
+p7 = [ p8[0], p6[1] ];
+p9 = p8 + [ 10, 0 ];
+
+$fa = 1;
+$fs = 0.1;
+
+module Plan(){
+  polygon([[ p1[0], 0.1 ],
+          p1, p2, p3, p4, p5, p6, p7, p8, p9,
+          [ p9[0], 0.1 ]]);
+}
+
+module Dummy(){
+  rotate_extrude()
+    rotate([0,0,-90])
+    Plan();
+  translate([0,0, -p1[0]])
+    mirror([0,0,1])
+    cylinder(r= 0.2, h= p9[0] - p1[0]);
+}
+
+//Plan();
+Dummy();
diff --git a/GPL-3 b/GPL-3
new file mode 100644 (file)
index 0000000..94a9ed0
--- /dev/null
+++ b/GPL-3
@@ -0,0 +1,674 @@
+                    GNU GENERAL PUBLIC LICENSE
+                       Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                            Preamble
+
+  The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+  The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works.  By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users.  We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors.  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+  To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights.  Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received.  You must make sure that they, too, receive
+or can get the source code.  And you must show them these terms so they
+know their rights.
+
+  Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+  For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software.  For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+  Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so.  This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software.  The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable.  Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products.  If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+  Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary.  To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                       TERMS AND CONDITIONS
+
+  0. Definitions.
+
+  "This License" refers to version 3 of the GNU General Public License.
+
+  "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+  "The Program" refers to any copyrightable work licensed under this
+License.  Each licensee is addressed as "you".  "Licensees" and
+"recipients" may be individuals or organizations.
+
+  To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy.  The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+  A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+  To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy.  Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+  To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies.  Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+  An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License.  If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+  1. Source Code.
+
+  The "source code" for a work means the preferred form of the work
+for making modifications to it.  "Object code" means any non-source
+form of a work.
+
+  A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+  The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form.  A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+  The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities.  However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work.  For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+  The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+  The Corresponding Source for a work in source code form is that
+same work.
+
+  2. Basic Permissions.
+
+  All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met.  This License explicitly affirms your unlimited
+permission to run the unmodified Program.  The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work.  This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+  You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force.  You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright.  Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+  Conveying under any other circumstances is permitted solely under
+the conditions stated below.  Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+  3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+  No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+  When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+  4. Conveying Verbatim Copies.
+
+  You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+  You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+  5. Conveying Modified Source Versions.
+
+  You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+    a) The work must carry prominent notices stating that you modified
+    it, and giving a relevant date.
+
+    b) The work must carry prominent notices stating that it is
+    released under this License and any conditions added under section
+    7.  This requirement modifies the requirement in section 4 to
+    "keep intact all notices".
+
+    c) You must license the entire work, as a whole, under this
+    License to anyone who comes into possession of a copy.  This
+    License will therefore apply, along with any applicable section 7
+    additional terms, to the whole of the work, and all its parts,
+    regardless of how they are packaged.  This License gives no
+    permission to license the work in any other way, but it does not
+    invalidate such permission if you have separately received it.
+
+    d) If the work has interactive user interfaces, each must display
+    Appropriate Legal Notices; however, if the Program has interactive
+    interfaces that do not display Appropriate Legal Notices, your
+    work need not make them do so.
+
+  A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit.  Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+  6. Conveying Non-Source Forms.
+
+  You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+    a) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by the
+    Corresponding Source fixed on a durable physical medium
+    customarily used for software interchange.
+
+    b) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by a
+    written offer, valid for at least three years and valid for as
+    long as you offer spare parts or customer support for that product
+    model, to give anyone who possesses the object code either (1) a
+    copy of the Corresponding Source for all the software in the
+    product that is covered by this License, on a durable physical
+    medium customarily used for software interchange, for a price no
+    more than your reasonable cost of physically performing this
+    conveying of source, or (2) access to copy the
+    Corresponding Source from a network server at no charge.
+
+    c) Convey individual copies of the object code with a copy of the
+    written offer to provide the Corresponding Source.  This
+    alternative is allowed only occasionally and noncommercially, and
+    only if you received the object code with such an offer, in accord
+    with subsection 6b.
+
+    d) Convey the object code by offering access from a designated
+    place (gratis or for a charge), and offer equivalent access to the
+    Corresponding Source in the same way through the same place at no
+    further charge.  You need not require recipients to copy the
+    Corresponding Source along with the object code.  If the place to
+    copy the object code is a network server, the Corresponding Source
+    may be on a different server (operated by you or a third party)
+    that supports equivalent copying facilities, provided you maintain
+    clear directions next to the object code saying where to find the
+    Corresponding Source.  Regardless of what server hosts the
+    Corresponding Source, you remain obligated to ensure that it is
+    available for as long as needed to satisfy these requirements.
+
+    e) Convey the object code using peer-to-peer transmission, provided
+    you inform other peers where the object code and Corresponding
+    Source of the work are being offered to the general public at no
+    charge under subsection 6d.
+
+  A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+  A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling.  In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage.  For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product.  A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+  "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source.  The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+  If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information.  But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+  The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed.  Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+  Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+  7. Additional Terms.
+
+  "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law.  If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+  When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it.  (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.)  You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+  Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+    a) Disclaiming warranty or limiting liability differently from the
+    terms of sections 15 and 16 of this License; or
+
+    b) Requiring preservation of specified reasonable legal notices or
+    author attributions in that material or in the Appropriate Legal
+    Notices displayed by works containing it; or
+
+    c) Prohibiting misrepresentation of the origin of that material, or
+    requiring that modified versions of such material be marked in
+    reasonable ways as different from the original version; or
+
+    d) Limiting the use for publicity purposes of names of licensors or
+    authors of the material; or
+
+    e) Declining to grant rights under trademark law for use of some
+    trade names, trademarks, or service marks; or
+
+    f) Requiring indemnification of licensors and authors of that
+    material by anyone who conveys the material (or modified versions of
+    it) with contractual assumptions of liability to the recipient, for
+    any liability that these contractual assumptions directly impose on
+    those licensors and authors.
+
+  All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10.  If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term.  If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+  If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+  Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+  8. Termination.
+
+  You may not propagate or modify a covered work except as expressly
+provided under this License.  Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+  However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+  Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+  Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License.  If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+  9. Acceptance Not Required for Having Copies.
+
+  You are not required to accept this License in order to receive or
+run a copy of the Program.  Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance.  However,
+nothing other than this License grants you permission to propagate or
+modify any covered work.  These actions infringe copyright if you do
+not accept this License.  Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+  10. Automatic Licensing of Downstream Recipients.
+
+  Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License.  You are not responsible
+for enforcing compliance by third parties with this License.
+
+  An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations.  If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+  You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License.  For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+  11. Patents.
+
+  A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based.  The
+work thus licensed is called the contributor's "contributor version".
+
+  A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version.  For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+  Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+  In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement).  To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+  If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients.  "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+  If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+  A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License.  You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+  Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+  12. No Surrender of Others' Freedom.
+
+  If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all.  For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+  13. Use with the GNU Affero General Public License.
+
+  Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work.  The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+  14. Revised Versions of this License.
+
+  The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+  Each version is given a distinguishing version number.  If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation.  If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+  If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+  Later license versions may give you additional or different
+permissions.  However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+  15. Disclaimer of Warranty.
+
+  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+  16. Limitation of Liability.
+
+  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+  17. Interpretation of Sections 15 and 16.
+
+  If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+                     END OF TERMS AND CONDITIONS
+
+            How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+  If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+    <program>  Copyright (C) <year>  <name of author>
+    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+  You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+<http://www.gnu.org/licenses/>.
+
+  The GNU General Public License does not permit incorporating your program
+into proprietary programs.  If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.  But first, please read
+<http://www.gnu.org/philosophy/why-not-lgpl.html>.
diff --git a/Huxley-INSTRUCTIONS b/Huxley-INSTRUCTIONS
new file mode 100644 (file)
index 0000000..45f9b77
--- /dev/null
@@ -0,0 +1,3 @@
+Go back to revision e1cdc45 aka "last-with-huxley".
+
+The files for our old Huxley have been removed from this tree since then.
diff --git a/Makefile b/Makefile
new file mode 100644 (file)
index 0000000..f2302b6
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,150 @@
+# reprap-objects Makefile
+#
+# Build scripts for various 3D designs
+# Copyright 2012-2023 Ian Jackson
+#
+# This work is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This work is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this work.  If not, see <http://www.gnu.org/licenses/>.
+
+FILAMENTSPOOL_AUTOS = filamentspool filamentspool-lt filamentspool-sm
+FILAMENTSPOOL_AUTOS += filamentspool-storarm3
+
+QUACKSES = $(addprefix quacks-ingredients-L, 1 2 3 4 5)
+QUACKS_SCADS = $(addsuffix .scad, $(QUACKSES))
+
+USING_AUTOS ?= $(FILAMENTSPOOL_AUTOS) xeno-drivebay-bracket dungeonquest-cone anke-gps-bracket cable-hole-trunking-cover anglepoise-neck crossbar-computer-led-mount wardrobe-hook knifeblock pandemic-counter pattress-boxes-3-cover bike-lipo-box earring-stand bike-stalk-led-mount sewing-table sewing-table-test sewing-table-jig maglite-holder poster-tube-lid poster-tube-lid-coarse fairphone-case fairphone-battery-case fairphone4-case fairphone4-case-coarse lock-inframe-bracket ksafe-base $(QUACKSES) quacks-ingredients-demos mic-table-clamp nook-case nook-case-test scaffold-clamp-common scaffold-clamp-tensioner scaffold-clamp-linear-bracket scaffold-clamp-straphook powerbank-bike-clamp topeak-mtx-tortec-expeditionrack-adapter lipo-flat-mount laptop-sound-cable-hooks digispark-with-cable chimney-cable-retainer $(foreach x,500 1000,adafruit-powerboost-$x)
+
+AUTO_INCS += sealing-box.scad sewing-table.scad nutbox.scad \
+            powerbank-anker-10000.dxf \
+            poster-tube-lid-parametric.scad \
+            $(QUACKS_SCADS)
+
+AUTO_STLS_INCS += poster-tube-lid,CatchPostDistort-fa3.stl
+AUTO_STLS_INCS += poster-tube-lid,CatchPostDistort-fa20.stl
+
+include diziet-utils/reprap-objects.make
+
+dovecliptest.stl: doveclip.scad $(AUTO_INCS)
+
+KNIFEBLOCK_KNIVES= 0 1 2
+KNIFEBLOCK_TEMPLATES= bl hl
+KNIFEBLOCK_TEMPLATE_FILES=\
+       $(foreach k,$(KNIFEBLOCK_KNIVES), \
+       $(foreach t,$(KNIFEBLOCK_TEMPLATES), \
+       knifeblock-knives-t$k$t.dxf))
+
+knifeblock-knives-templates knifeblock.stl: $(KNIFEBLOCK_TEMPLATE_FILES)
+
+.PRECIOUS: knifeblock-knives-t%.dxf
+knifeblock-knives-t%.dxf: knifeblock-knives-filter knifeblock-knives-trace.fig
+               ./$< $(notdir $*) <$(filter %.fig, $^) >$@.tmp.fig
+               fig2dev -D -30 -L eps <$@.tmp.fig >$@.tmp.eps
+               pstoedit -dt -f "dxf: -polyaslines -mm" $@.tmp.eps $@
+
+PANDEMICCOUNTER_LETTERS=30 31 32 33 34 35
+PANDEMICCOUNTER_DXFS=$(foreach l,$(PANDEMICCOUNTER_LETTERS), \
+       pandemic-counter-l$l.dxf)
+
+pandemic-counter-letters: $(PANDEMICCOUNTER_DXFS)
+pandemic-counter%.stl: $(PANDEMICCOUNTER_DXFS)
+
+.PRECIOUS: pandemic-counter-l%.eps
+pandemic-counter-l%.eps: pandemic-counter-letters.fig
+               fig2dev -D +$(notdir $*) -L eps <$< >$@.tmp
+               @$i
+
+.PRECIOUS: maglite-holder-torch-curve.eps
+maglite-holder-torch-curve.eps: maglite-holder-torch.fig
+               fig2dev -D +1:70 -L eps <$< >$@.tmp
+               @$i
+
+maglite-holder-torch-curve.dxf: maglite-holder-torch-curve.eps
+               pstoedit -dt -flat 0.05 -f "dxf: -polyaslines -mm" $< $@
+
+powerbank-anker-10000.dxf: powerbank-anker-10000.eps
+               pstoedit -dt -f "dxf: -polyaslines -mm" $< $@
+
+PANDEMICQUARANTINES_NUMBERS=1 2
+PANDEMICQUARANTINES_DXFS=$(foreach l,$(PANDEMICQUARANTINES_NUMBERS), \
+       pandemic-quarantine-l$l.dxf)
+
+pandemic-quarantine-numbers: $(PANDEMICQUARANTINES_DXFS)
+pandemic-quarantine%.stl: $(PANDEMICQUARANTINES_DXFS)
+
+.PRECIOUS: pandemic-quarantine-l%.eps
+pandemic-quarantine-l%.eps: pandemic-quarantine-numbers.fig
+               fig2dev -D +$(notdir $*) -L eps <$< >$@.tmp
+               @$i
+
+FILAMENTSPOOL_NUMBERS=$(shell seq 300 100 1500)
+filamentspool-number-n%.eps:   filamentspool-number.eps.pl
+       ./$< $* $o
+
+FILAMENTSPOOL_DXFS=$(foreach n,$(FILAMENTSPOOL_NUMBERS), \
+       filamentspool-number-n$n.dxf)
+
+$(addsuffix .auto.stl, $(foreach f,$(FILAMENTSPOOL_AUTOS),$(shell \
+       $(DUTILS)/toplevel-find $(CWD)/$f))): $(FILAMENTSPOOL_DXFS)
+
+filamentspool-numbers filamentspool.stl: $(FILAMENTSPOOL_DXFS)
+
+SCREWRECESSTEST_SIZES= 2 3 4 5 6
+SCREWRECESSTEST_DXFS=$(foreach s,$(SCREWRECESSTEST_SIZES), \
+       screw-recess-test-number-s$s.dxf)
+
+screw-recess-test-number-s%.fig: screw-recess-test-number.fig.pl
+       ./$< $* $o
+
+screw-recess-test-number-s%.eps: screw-recess-test-number-s%.fig
+               fig2dev -L eps <$< >$@.tmp
+               @$i
+
+screw-recess-test-numbers screw-recess-test.stl: $(SCREWRECESSTEST_DXFS)
+
+question-question.eps: question-question.fig
+               fig2dev -L eps <$< >$@.tmp
+               @$i
+
+sewing-table%.stl: sewing-table-rear-profile.dxf
+sewing-table%.stl: sewing-table-front-profile.dxf
+sewing-table%.stl: sewing-table-end-profile.dxf
+
+sewing-table-%-profile.eps: sewing-table-%-profile.fig
+               fig2dev -L eps -D +40 <$< >$@.tmp
+               @$i
+
+question-token.stl: question-question.dxf
+
+lemon-stand.stl: lemon-stand.scad
+
+electron-token.stl: electron-token.scad
+
+quacks-scads: $(addsuffix .auto.scads, $(QUACKSES))
+quacks-scads: quacks-ingredients-demos.auto.scads
+
+quacks-stls: $(addsuffix .auto.stls, $(QUACKSES))
+
+.PRECIOUS: $(SCREWRECESSTEST_DXFS) $(SCREWRECESSTEST_DXFS) \
+       $(foreach s,$(SCREWRECESSTEST_SIZES), \
+               screw-recess-test-number-s$s.fig \
+               screw-recess-test-number-s$s.eps)
+
+poster-tube-lid,CatchAssembly.auto.stl: poster-tube-lid,CatchPostDistort-fa3.stl
+poster-tube-lid-coarse,CatchAssembly.auto.stl: poster-tube-lid,CatchPostDistort-fa20.stl
+
+poster-tube-lid,CatchPostDistort-fa%.stl: \
+               distort-stl poster-tube-lid,CatchPreDistort.auto.stl
+       ./distort-stl <poster-tube-lid,CatchPreDistort.auto.stl \
+               set-fa $(notdir $*) project-cylinder 100 >$@.tmp
+       $i
+
diff --git a/README b/README
new file mode 100644 (file)
index 0000000..5c5af41
--- /dev/null
+++ b/README
@@ -0,0 +1,27 @@
+# Diziet's (Ian Jackson)'s 3D models
+
+This directory contains source code a number of objects of various
+kinds, and a simple build system.
+
+In general for a simple file `FOO.scad` you can say `make FOO.stl'.
+
+More complicated files contain source code for multiple related parts.
+The actual objects which might be printed or previewed are indicated
+in the source code with `////toplevel`.  For a complicated file
+`BAR.scad` you can generate simple template openscad files, one for each
+such object, with `make BAR.auto.scads`.  And you can make all the
+corresponding `.stl` files with `make BAR.auto.stls`.
+
+Many objects have "slop" in them, which represents an amount by which
+holes or gaps are bigger, in order to make things fit well.  You will
+need to examine the openscad source or play around with previews to
+see which slop does what.
+
+# Copyright
+
+Unless otherwise noted every object and file here is:
+
+  Copyright Ian Jackson
+  Licenced under the GNU General Public Licence, version 3, or
+  (at your option) any later version.  There is NO WARRANTY.
+
index ec4c50d5f995c68cf23c37daa97df065e7c8f4e4..36ca447f5b52219105e774760a47e68e5ec48767 100644 (file)
--- a/README.md
+++ b/README.md
@@ -1,24 +1,26 @@
-# 3D printing utility files and build system
+This directory contains source code a number of objects of various
+kinds, and a simple build system.
 
-This is a set of files I use for making 3D objects.
+In general for a simple file FOO.scad you can say `make FOO.stl'.
 
-It mostly consists of my own work, but occasionally I include other
-people's work if that other work has a suitable licence.
 
-# How to use
+More complicated files contain source code for multiple related parts.
+The actual objects which might be printed or previewed are indicated
+in the source code with `///toplevel'.  For a complicated file
+BAR.scad you can generate simple template openscad files, one for each
+such object, with `make BAR.auto.scads'.  And you can make all the
+corresponding .stl files with `make BAR.auto.stls'.
 
-To add to your project:
 
-```
-git subtree add -P diziet-utils https://salsa.debian.org/iwj/3d-utils.git main
-```
+Many objects have `slop' in them, which represents an amount by which
+holes or gaps are bigger, in order to make things fit well.  You will
+need to examine the openscad source or play around with previews to
+see which slop does what.
 
-To update to a new version:
 
-```
-git subtree pull -P diziet-utils https://salsa.debian.org/iwj/3d-utils.git main
-```
+Unless otherwise noted every object and file here is:
 
-# Licence
+  Copyright Ian Jackson
+  Licenced under the GNU General Public Licence, version 3, or
+  (at your option) any later version.  There is NO WARRANTY.
 
-Everything is GPLv3+ (or compatible).
diff --git a/adafruit-powerboost-1000.scad b/adafruit-powerboost-1000.scad
new file mode 100644 (file)
index 0000000..683eb81
--- /dev/null
@@ -0,0 +1,175 @@
+// -*- C -*-
+
+psu_sz_nom = [ 11.43*2, 36.20 ];
+
+//// toplevels-from:
+include <adafruit-powerboost-common.scad>
+
+psu_hole_pos = [ 2.05, // from back edge of psu_sz[0]
+                0.55 * 0.5 * 25.4, // from centreline
+                ];
+
+psu_led_low_x = 4;
+
+psu_led_usbend_y_min = 4.5;
+psu_led_chrg_min_x = -1.0;
+psu_led_chrg_max_x = 3.0;
+psu_led_chrg_both_sz_y = 9.4;
+
+psu_led_low_sz_x = 4.5;
+psu_led_low_min_y = -1.5;
+psu_led_low_max_y = 3.25;
+
+psu_led_baffle_th = 0.8;
+psu_led_baffle_ends = 1.5;
+
+psu_baffle_th = [ 0.8, 3.5 ];
+psu_innerend_led_depth = 7;
+
+psu_led_legend_line = 0.75;
+
+psu_led_legend_battery_l = 6.0;
+psu_led_legend_battery_w = 4.0;
+psu_led_legend_battery_nub_l = 0.75;
+psu_led_legend_battery_nub_w = 1.5;
+
+psu_led_legend_power_dia = 5.0;
+
+psu_led_legend_gap = 1.25;
+
+// ----- calculated -----
+
+psu_led_legend_battery_edge = psu_led_legend_line;
+
+psu_led_legend_power_tick_l =
+  psu_led_legend_power_dia * 0.65;
+psu_led_legend_power_tick_dy = psu_led_legend_line;
+
+psu_led_legend_power_sz_y =
+  psu_led_legend_power_dia/2 + psu_led_legend_power_tick_l/2
+  - psu_led_legend_power_tick_dy;
+
+psu_innerend_led_x_midder =  - psu_hole_pos[1] - psu_hole_dia/2;
+
+module PsuLedBafflePlan(){
+  AtPsuMountCorner(0,0) {
+    translate([ (psu_led_chrg_min_x + psu_led_chrg_max_x)/2,
+              psu_led_usbend_y_min + psu_led_chrg_both_sz_y/2 ])
+      square(center=true,
+            [ psu_led_chrg_max_x - psu_led_chrg_min_x
+              + psu_led_baffle_ends*2,
+              psu_led_baffle_th ]);
+  }
+}
+
+module PsuLedWindowsPlanCore(){
+  difference(){
+    union(){
+      // Two LEDs one side of inlet connector
+      // "Full" (near edge) and "Chrg" (inner)
+      AtPsuMountCorner(0,0) {
+       translate([0, psu_led_usbend_y_min ])
+         rectfromto([ psu_led_chrg_min_x, 0 ],
+                    [ psu_led_chrg_max_x,
+                      psu_led_chrg_both_sz_y ]);
+      }
+
+      // One LED, "Low", other side of inlet connector
+      AtPsuMountCorner(1,0) {
+       translate([0, psu_led_usbend_y_min ])
+         rectfromto([ 0,
+                      psu_led_low_min_y ],
+                    [ psu_led_low_sz_x,
+                      psu_led_low_max_y ]);
+      }
+
+      // One LED, PWR, near outlet USB pads
+      AtPsuMountCorner(0,1){
+       rectfromto([0,0],
+                  [psu_sz[0]/2 + psu_innerend_led_x_midder,
+                   psu_innerend_led_depth]);
+      }
+    }
+  }
+}
+
+module PsuLedLegendBattery(percent=50){
+  e = psu_led_legend_battery_edge;
+  empty_l = (100-percent)/100 * (psu_led_legend_battery_l - e*2);
+  difference(){
+    union(){
+      square([psu_led_legend_battery_l,
+             psu_led_legend_battery_w], center=true);
+      translate([psu_led_legend_battery_l/2, 0])
+       square([psu_led_legend_battery_nub_l*2,
+               psu_led_legend_battery_nub_w], center=true);
+    }
+    if (empty_l > 0)
+      translate([-(psu_led_legend_battery_l/2-e),
+                -(psu_led_legend_battery_w/2-e)])
+       square([empty_l, psu_led_legend_battery_w - e*2]);
+  }
+}  
+
+module PsuLedLegendPowerSymbol(){
+  $fn=30;
+  tick_mid = [0, psu_led_legend_power_dia/2 - psu_led_legend_power_tick_dy];
+
+  cut_slope = ( psu_led_legend_gap + psu_led_legend_line/2 ) / tick_mid[1];
+  cut_y = psu_led_legend_power_dia + 1;
+
+  translate(tick_mid)
+    square([psu_led_legend_line, psu_led_legend_power_tick_l], center=true);
+  
+  difference(){
+    circle(r= psu_led_legend_power_dia/2);
+    circle(r= psu_led_legend_power_dia/2 - psu_led_legend_line);
+
+    polygon([[0, 0],
+            [-cut_y * cut_slope, cut_y],
+            [ cut_y * cut_slope, cut_y]]);
+
+    if(0) translate(tick_mid)
+      square([psu_led_legend_line, psu_led_legend_power_tick_l]
+            + [psu_led_legend_gap*2, 0.1],
+            center=true);
+  }
+}
+
+module PsuLedLegendsPlan(){
+  translate([psu_led_legend_power_dia/2
+            + psu_innerend_led_x_midder
+            + psu_led_legend_gap,
+            psu_sz[1]/2
+            - psu_innerend_led_depth/2
+            ])
+    rotate([0,0,180])
+    PsuLedLegendPowerSymbol();
+
+  for (full=[0,1]) {
+    translate([-psu_sz[0]/2
+              + psu_led_legend_battery_l/2
+              + psu_led_chrg_max_x
+              + psu_led_legend_gap,
+              -psu_sz[1]/2
+              + psu_led_usbend_y_min
+              + psu_led_chrg_both_sz_y * 0.5
+              + max(
+                    psu_led_legend_battery_w + psu_led_legend_gap,
+                    psu_led_chrg_both_sz_y * 0.5
+                    ) * (0.5 - full)
+              ])
+      PsuLedLegendBattery(full ? 100 : 50);
+  }
+
+  translate([psu_sz[0]/2
+            - psu_led_legend_battery_nub_l
+            - psu_led_legend_battery_l/2,
+            -psu_sz[1]/2
+            + psu_led_legend_gap
+            + psu_led_usbend_y_min
+            + psu_led_low_max_y
+            + psu_led_legend_battery_w/2
+            ])
+    PsuLedLegendBattery(0);
+}
diff --git a/adafruit-powerboost-500.scad b/adafruit-powerboost-500.scad
new file mode 100644 (file)
index 0000000..d10002e
--- /dev/null
@@ -0,0 +1,66 @@
+// -*- C -*-
+
+psu_sz_nom = [ 21.59, 35.56 ];
+
+//// toplevels-from:
+include <adafruit-powerboost-common.scad>
+
+psu_baffle_cnr_y = 7.45; // from connector end
+psu_baffle_th = [ 0.8, 3.5 ];
+psu_usbend_led_x = 4.5;
+psu_innerend_led_depth = 10;
+
+// ----- calculated -----
+
+psu_usbend_led_depth = psu_baffle_cnr_y*2 - psu_usbend_led_x;
+
+
+module PsuLedBafflePlan(){
+  baffle_tr = [0, psu_baffle_cnr_y]
+    + 0.5 * [psu_baffle_th[1], psu_baffle_th[0]];
+  translate([0, -psu_sz[1]/2]) {
+    mirror([1,0,0]) {
+      rectfromto([-psu_baffle_th[1]/2, 0],
+                baffle_tr);
+      rectfromto([-psu_sz[0]/2 - psu_board_support_wall *2,
+                 baffle_tr[1] - psu_baffle_th[0]],
+                baffle_tr);
+    }
+  }
+}
+
+module PsuLedLegendsPlan(){
+}
+
+module PsuLedWindowsPlanCore(){
+  difference(){
+    union(){
+      // Two LEDs incl "Chrg", one side of inlet connector
+      AtPsuMountCorner(1,0) {
+       rectfromto([ -(psu_board_support_wall + 0.1),
+                    +psu_usbend_led_x ],
+                  [ psu_sz[0]/2,
+                    +psu_usbend_led_depth ]);
+      }
+
+      // One LED, "Low", other side of inlet connector
+      AtPsuMountCorner(0,0) {
+       sz = psu_baffle_cnr_y - psu_board_support_wall - psu_baffle_th[0];
+       translate([0, psu_baffle_cnr_y])
+         rectfromto([ -(psu_board_support_wall + 0.1),
+                      -sz/2 ],
+                    [ psu_sz[0]/2,
+                      +sz/2 ]);
+      }
+
+      // One LED, PWR, near outlet USB pads
+      AtPsuMountCorner(0,1){
+       rectfromto([0,0],
+                  [psu_sz[0]/2 - psu_hole_pos[1] - psu_hole_dia/2,
+                    psu_innerend_led_depth]);
+      }
+    }
+    translate([0, -psu_sz[1]/2])
+      square(center=true, [psu_baffle_th[1], psu_sz[1]]);;
+  }
+}
diff --git a/adafruit-powerboost-common.scad b/adafruit-powerboost-common.scad
new file mode 100644 (file)
index 0000000..b62643a
--- /dev/null
@@ -0,0 +1,337 @@
+// -*- C -*-
+
+include <nutbox.scad>
+include <utils.scad>
+
+psu_sz  = psu_sz_nom + [ 0.11, 0.44 ] + [ 0.25, 0.25 ];
+
+psu_hole_pos = [ 2.05, // from back edge of psu_sz[0]
+                0.55 * 0.5 * 25.4, // from centreline
+                ];
+
+psu_th = 1.70 + 0.25;
+psu_th_for_clamp = 1.50;
+
+psu_hole_dia = 2.5 - 0.5;
+psu_connector_z = 2.9 + 0.1;
+psu_connector_z_overlap = 0.15;
+psu_connector_depth = 6.25;
+psu_connector_w = 8.0 + 0.5;
+psu_usb_protr = 0.6;
+
+psu_clamp_th = 4.0 + 0.75;
+psu_clamp_w = 8.0;
+psu_clamp_gap = 0.4;
+
+psu_board_clamp_ovlp = 4.5;
+psu_board_nutbox = nutbox_data_M3;
+
+psu_board_gap = 0.5;
+psu_board_support_wall = 2;
+psu_board_support_ovlp = 4.5;
+psu_board_support_ovlp_ceil = 2;
+psu_board_support_z = 2;
+
+psu_baffle_gap = 1.0 + 0.5;
+
+psu_y = +psu_sz[1]/2 + psu_usb_protr;
+
+psu_usba_v_apart = 7.0;
+psu_usba_v_from_edge = 4.86;
+psu_usba_v_space_below = 1.5;
+psu_usba_v_space_w = 1.7;
+psu_usba_v_space_l = 3.0;
+
+psu_test_ceil = 2.5;
+
+// ----- calculated -----
+
+psu_z = NutBox_h_base(psu_board_nutbox);
+psu_z_down = psu_z + 0.1;
+psu_fix_sz = NutBox_outer_size(psu_board_nutbox);
+psu_board_nutbox_y = psu_sz[1]/2 + psu_board_nutbox[0]/2;
+
+psu_mount_outer_sz_x = psu_sz[0] + psu_board_support_wall * 2; // centred
+psu_mount_outer_sz_y = psu_y + max(psu_board_support_wall, // at psu_y
+                                  psu_board_nutbox_y + psu_fix_sz/2);
+
+module PsuBoardRepresentation(){
+  linear_extrude(height= psu_th)
+    square(center=true, [psu_sz[0],psu_sz[1]]);
+}
+
+module PsuRepresentation(){
+  PsuBoardRepresentation();
+  translate([0, -psu_sz[1]/2, -psu_connector_z])
+    linear_extrude(height= psu_connector_z + psu_connector_z_overlap)
+    rectfromto([ -psu_connector_w/2, -10 ],
+              [ +psu_connector_w/2, psu_connector_depth ]);
+}
+
+module AtPsuMountCorner(mx,my){
+  mirror([mx,0,0])
+    mirror([0,my,0])
+      translate(-0.5 * [psu_sz[0], psu_sz[1], 0]
+               -1 * [0,0, psu_z_down])
+       children();
+}
+
+module PsuMountCornerExtrude(mx,my, plus_z=psu_board_support_z){
+  AtPsuMountCorner(mx,my){
+    linear_extrude(height= psu_z_down + plus_z, convexity=10) {
+      children();
+    }
+  }
+}
+
+module PsuUsbAVSpacePlan(){
+  for (x= [-1,+1] * psu_usba_v_apart/2) {
+    translate([x, -psu_usba_v_from_edge ]) {
+      hull(){
+       for (y= [-1,+1] * 0.5 * (psu_usba_v_space_l - psu_usba_v_space_w)) {
+         translate([0,y])
+           circle(r= psu_usba_v_space_w);
+       }
+      }
+    }
+  }
+}
+
+module PsuMountPositiveMain(){
+  for (mx=[0,1]) {
+    for (my=[0,1]) {
+      PsuMountCornerExtrude(mx,my){
+       rectfromto(-[1,1]*psu_board_support_wall,
+                  +[1,1]*psu_board_support_ovlp);
+      }
+    }
+    // mount above at plug end
+    PsuMountCornerExtrude(mx,0, psu_th + psu_board_support_wall){
+      rectfromto(-[1,1]*psu_board_support_wall,
+                [psu_board_support_ovlp,
+                 psu_board_support_ovlp_ceil]);
+    }
+  }
+  translate([0,0, -psu_z_down])
+    linear_extrude(psu_z_down - psu_baffle_gap, convexity=10)
+      PsuLedBafflePlan();
+}
+
+module PsuMountNegative(){
+  axis = [0, -psu_sz[1]/2, psu_th];
+  PsuRepresentation();
+  translate(axis)
+    rotate([atan(2 * psu_board_support_z / psu_sz[1]),
+           0,0])
+    translate(-axis)
+    PsuBoardRepresentation();
+}
+
+module PsuMountPositive(){
+  difference(){
+    intersection(){
+      PsuMountPositiveMain();
+      linextr_y_xz(-psu_y, psu_sz[1]*2) square(100, center=true);
+    }
+    PsuMountNegative();
+    intersection(){
+      hull(){
+       PsuBoardRepresentation();
+       translate([0,0,5]) PsuBoardRepresentation();
+      }
+      translate([-20,0,-20]) cube(40);
+    }
+  }
+  for (mx=[0,1]) {
+    PsuMountCornerExtrude(mx,1){
+      translate([psu_sz[0]/2 - psu_hole_pos[1],
+                psu_hole_pos[0]]
+               + psu_board_gap * [1,1] )
+       circle(r= psu_hole_dia/2);
+    }
+  }
+  difference(){
+    translate([0, psu_board_nutbox_y, 0])
+      rotate([0,0,180])
+      NutBox(psu_board_nutbox, psu_z_down);
+    translate([0, psu_sz[1]/2, 0])
+      linextr(-psu_usba_v_space_below, +10)
+      PsuUsbAVSpacePlan();
+  }
+}
+
+module PsuClamp(){ ////toplevel
+  rotate([180,0,0]) difference(){
+    linear_extrude(height=psu_clamp_th + psu_th_for_clamp, convexity=5) {
+      difference(){
+       hull(){
+         circle(r = psu_fix_sz/2);
+         translate([ -psu_board_nutbox[0]/2, 0])
+           square(center=true, [ psu_board_clamp_ovlp*2, psu_clamp_w ]);
+       }
+       circle(r = psu_board_nutbox[0]/2);
+      }
+    }
+    translate([0,0,-1]) linear_extrude(height=psu_th_for_clamp+1) {
+      translate([ -psu_board_nutbox[0]/2 + psu_clamp_gap, 0 ])
+       mirror([1,0])
+       translate([0,-20]) square(40);
+    }
+    linextr(-10,10) {
+      rotate(-90)
+       translate([0, -psu_board_nutbox[0]/2])
+       PsuUsbAVSpacePlan();
+    }
+  }
+}
+
+module PsuLedWindowsPlan(){
+  difference(){
+    PsuLedWindowsPlanCore();
+    PsuLedBafflePlan();
+  }
+}
+
+module PsuLedWindowsWindows(ceil){
+  translate([0,0, -psu_z - ceil])
+    linextr(0, psu_initial_layer_thick)
+    offset(delta=psu_window_ledge)
+    PsuLedWindowsPlan();
+}
+
+module PsuFirstLayerNegative(ceil){
+  translate([0, 0, -psu_z - ceil])
+    linextr(-1, psu_initial_layer_thick)
+    children();
+}
+
+module PsuMountWindowsNegative(ceil){
+  linextr(-10, 0.1)
+    PsuLedWindowsPlan();
+  PsuFirstLayerNegative(ceil)
+    offset(delta= psu_window_ledge + psu_multicolour_gap)
+    PsuLedWindowsPlan();
+}
+
+module PsuLedLegendsNegative(ceil){
+  PsuFirstLayerNegative(ceil)
+    offset(delta= psu_multicolour_gap)
+    PsuLedLegendsPlan();
+}
+
+module PsuMountDemo() { ////toplevel
+  ceil = psu_test_ceil;
+
+  translate([0, psu_y, psu_z]) {
+    difference(){
+      PsuMountPositive();
+      linextr(-20, 0.1)
+       PsuLedWindowsPlan();
+    }
+    %PsuMountNegative();
+
+    color("yellow") translate([0,0, -psu_z - ceil])
+      linear_extrude(height=0.4, convexity=10)
+      PsuLedWindowsPlan();
+
+    color("blue") translate([0,0, -psu_z - ceil])
+      linear_extrude(height=0.4, convexity=10)
+      PsuLedLegendsPlan();
+
+    translate([0, psu_board_nutbox_y, 10])
+      rotate([180,0,0])
+      rotate([0,0,-90])
+      PsuClamp();
+  }
+}
+
+module PsuMountTest() { ////toplevel
+  ceil = psu_test_ceil;
+  $fs = 0.1;
+  $fa = 3;
+  difference(){
+    union(){
+      translate([0, psu_y, psu_z])
+       PsuMountPositive();
+      difference(){
+
+       // rectangular box with wall
+       linextr_x_yz(-psu_mount_outer_sz_x/2,
+                    +psu_mount_outer_sz_x/2) {
+         difference(){
+           rectfromto([0, -ceil],
+                      [psu_mount_outer_sz_y, psu_z + 10]);
+           rectfromto([ceil,0], 400*[1,1]);
+         }
+       }
+
+       translate([0, psu_y, psu_z]) {
+         PsuMountNegative();
+       }
+      }
+    }
+    translate([0, psu_y, psu_z]) {
+      PsuMountWindowsNegative(ceil);
+      PsuLedLegendsNegative(ceil);
+    }
+  }
+}
+
+psu_multicolour_gap = 0.075;
+psu_initial_layer_thick = 0.400;
+psu_initial_layer_width = 0.750;
+psu_window_ledge = 0.50; // each side
+
+psu_frame_gap = 1.0;
+
+module PsuMountLayerFrame(bl, tr, ix) {
+  gap0 = [1,1] * (psu_frame_gap + psu_initial_layer_width*(ix+0));
+  gap1 = [1,1] * (psu_frame_gap + psu_initial_layer_width*(ix+1));
+  linextr(0, psu_initial_layer_thick) {
+    difference(){
+      rectfromto(bl-gap1, tr+gap1);
+      rectfromto(bl-gap0, tr+gap0);
+    }
+  }
+}
+
+module PsuMountTestFullLayerFrame(ix) {
+  PsuMountLayerFrame([-0.5 * psu_mount_outer_sz_x, 0],
+                    [+0.5 * psu_mount_outer_sz_x,
+                     psu_mount_outer_sz_y],
+                    ix);
+}
+
+module PsuMountTestFullMain() { ////toplevel
+  ceil = psu_test_ceil;
+
+  PsuMountTestFullLayerFrame(2);
+  
+  difference(){
+    translate([0,0, ceil])
+      PsuMountTest();
+  }
+}
+
+module PsuMountTestFullOneLayer(ix) {
+  PsuMountTestFullLayerFrame(ix);
+  linextr(0, psu_initial_layer_thick) {
+    translate([0, psu_y]) children();
+  }
+}
+
+module PsuMountTestFullText() { ////toplevel
+  PsuMountTestFullOneLayer(0)
+    PsuLedLegendsPlan();
+}
+module PsuMountTestFullWindows() { ////toplevel
+  PsuMountTestFullLayerFrame(1);
+  translate([0, psu_y, psu_z + psu_test_ceil])
+    PsuLedWindowsWindows(psu_test_ceil);
+}
+
+module PsuMountTestFullDemo() { ////toplevel
+  color("blue") PsuMountTestFullMain();
+  color("yellow") PsuMountTestFullText();
+  %PsuMountTestFullWindows();
+}
diff --git a/anglepoise-neck.scad b/anglepoise-neck.scad
new file mode 100644 (file)
index 0000000..aa39ce2
--- /dev/null
@@ -0,0 +1,83 @@
+// -*- C -*-
+
+arm_depth = 25;
+arm_innerwidth = 9.60 - 0.50;
+arm_innerheight = 8.90 - 0.50;
+arm_pin_depth = 18.50 + 1.0;
+arm_pin_dia = 1.5 + 0.7;
+
+armpart_hex_rad = 15;
+armpart_main_thick = 8;
+
+hingepin_dia = 3 + 1.0;
+hingenut_width = 6 + 1.0;
+hingenut_depth = 4;
+hingenut_clear = 5;
+
+headpart_main_dia = 15 + 0.3;
+headpart_main_len = 16 + 1.1;
+headpart_stub_protrude = 2;
+headpart_stub_width = 11.7 - 0.6;
+
+headpart_flatten_angle = 45;
+
+// computed
+
+armpart_hinge_height = arm_innerheight + hingenut_width/2 + hingenut_clear;
+armpart_main_height = armpart_hinge_height + headpart_stub_width / 2;
+armpart_main_width = headpart_stub_width;
+armpart_x_unit = armpart_hex_rad * tan(30);
+headpart_flatten_z = headpart_main_dia/2 * cos(headpart_flatten_angle);
+headpart_stub_support_x = headpart_stub_width * cos(59) / 2;
+headpart_stub_len = headpart_stub_protrude + headpart_main_dia/2;
+hingenut_depth_y =
+  sqrt(headpart_main_dia*headpart_main_dia/4 - hingenut_width*hingenut_width/4)
+  - hingenut_depth;
+
+module ArmPart(){ ////toplevel
+  difference(){
+    translate([-arm_innerwidth/2, 1, 0])
+      mirror([0,-1,0])
+      cube([arm_innerwidth, arm_depth+1, arm_innerheight]);
+    translate([0, -arm_pin_depth, -50])
+      cylinder(r=arm_pin_dia/2, h=100, $fn=20);
+  }
+  difference(){
+    translate([-armpart_main_width/2, 0, 0])
+      cube([armpart_main_width, armpart_main_thick, armpart_main_height]);
+    translate([0,50,armpart_hinge_height])
+      rotate([90,0,0])
+      cylinder(r=hingepin_dia/2, h=100, $fn=20);
+  }
+}
+
+module HeadPart(){ ////toplevel
+  difference(){
+    union(){
+      translate([-headpart_main_len/2, 0,0])
+       rotate([0,90,0])
+       cylinder(r=headpart_main_dia/2, h=headpart_main_len, $fn=40);
+      rotate([90,0,0])
+       cylinder(h = headpart_stub_len,
+                r = headpart_stub_width/2,
+                $fn = 6);
+      translate([-headpart_stub_support_x,
+                -headpart_stub_len,
+                -headpart_main_dia/2])
+       cube([headpart_stub_support_x*2,
+             headpart_stub_len,
+             headpart_main_dia/2]);
+    }
+    translate([-100,-100,-100])
+      cube([200,200, 100 - headpart_flatten_z]);
+    rotate([90,0,0])
+      translate([0,0, -100])
+      cylinder(r=hingepin_dia/2, h = 200, $fn=20);
+    translate([0,hingenut_depth_y,0])
+      rotate([90,0,180])
+      cylinder(r=hingenut_width/2/cos(30), h=20, $fn=6);
+  }
+}
+
+//ArmPart();
+//HeadPart();
diff --git a/anglepoise-neck.slic3r b/anglepoise-neck.slic3r
new file mode 100644 (file)
index 0000000..b5079ea
--- /dev/null
@@ -0,0 +1 @@
+perimeters = 4
diff --git a/anke-gps-bracket.scad b/anke-gps-bracket.scad
new file mode 100644 (file)
index 0000000..084eefd
--- /dev/null
@@ -0,0 +1,374 @@
+// -*- C -*-
+
+include <doveclip.scad>
+
+// Dimensions of the main GPS body
+outerw = 120;
+outerh =  75;
+outert =  15;
+outerbackbevel = 3;
+
+// Dimensions for the holder
+holder_outerw = outerw - 0.0;
+holder_outerh = outerh + 0.0;
+holder_outert = outert + 0.0;
+
+// Dimensions for the model
+model_outerw = outerw + 2.5;
+model_outerh = outerh - 0.2;
+model_outert = outert - 1.0;
+
+// Dimensions of the bezel area round the edges
+bezelw =    11 - 0.5;
+bezelboth = 11 - 0.5;
+bezeltoph =  7 - 0.5;
+
+// Dimensions of the speaker at the back
+spkrdia =  22;
+spkr2bot = 19;
+spkr2rhs = 25;
+
+// Dimensions of the plug and wire
+plugw =      12;
+plugh =       9;
+plug2bot =   11;
+plug2lhs =   11;
+plugtotald = 15;
+pluggapd =    5;
+
+// Dimensions of the hole in the tray
+//   width and height (vertical) at the top
+nestleh = 53;
+nestlew = 60.9;
+//   depths (back to front distance):
+nestledl = 40.2;
+nestledr = 43.9;
+//   differences in width, depth, at bottom:
+nestledwl = 2.1;
+nestledwr = 1.4;
+nestleddf = 4.0;
+nestleddbl = 5.7;
+nestleddbr = 5.2;
+
+// Adjustment for the GPS attitude and position
+gpsazimuth = 45;
+gpselevation = 40;
+gpsrightwardoffset = 5;
+gpsrearwardoffset = 2;
+gpsrightwardoffsetonbar = 0;
+
+// Amount of wire protrusion to allow for
+plugwiremoreh = 25;
+
+// Slops and steps etc.
+plugslop = 0.5;
+plughstep = 1.5;
+bodylhsrhsslop = 0.5;
+holderhgap = 5;
+holderbezelmore = 2;
+nestlebevel = 1;
+
+// Dimensions for strength only
+screent = 1.0;
+plugstrutw = 4;
+plugstrutt = min(model_outert, 5);
+nestledoveclipw = 20;
+holderh = model_outerh * 0.5;
+holderwallt = 2.5;
+holderbackt = 2.8;
+holderdccount = 2;
+holderdoveclipl = 15;
+chassish = 13;
+chassist = 13;
+nestlefloorh = 4.7;
+nestleceilh = 6.0;
+nestlewallmin = 10.0;
+nestlearchslope = 0.75 * sqrt(0.5);
+
+// Consequential values
+holderdcw = DoveClipPairSane_width(holderdccount);
+
+module GpsPlugPlug(slop){
+  effhslop = slop - plughstep;
+  effplugw = plugw + slop*2;
+  effplugh = plugh + effhslop*2;
+  translate([plug2lhs-slop, plug2bot-effhslop, -1])
+    cube([effplugw, effplugh, model_outert+2]);
+}
+
+module GpsBodyOuterBevel(len){
+  translate([0,-1,0]) {
+    rotate([-90,0,0]) {
+      linear_extrude(height=len+2) {
+       polygon([[-outerbackbevel, 0],
+                [ 0, outerbackbevel],
+                [outerbackbevel, 0],
+                [ 0, -outerbackbevel]]);
+      }
+    }
+  }
+}
+
+module GpsBody() { ////toplevel
+  difference(){
+    union(){
+      difference(){
+       cube([model_outerw, model_outerh, model_outert]);
+       translate([bezelw, bezelboth, screent])
+         cube([model_outerw-bezelw*2,
+               model_outerh-bezelboth-bezeltoph,
+               model_outert]);
+       translate([model_outerw-spkr2rhs, spkr2bot, -1])
+         cylinder(r=spkrdia/2, h=model_outert+2);
+      }
+      translate([plug2lhs+plugw/2, plug2bot+plugh/2, 0])
+       cylinder(r=(plugw+plugh)/2, h=model_outert);
+      for (x=[plug2lhs-plugstrutw, plug2lhs+plugw])
+       translate([x, 0.1, 0])
+         cube([plugstrutw, model_outerh-0.2, plugstrutt-0.10]);
+    }
+    GpsPlugPlug(0);
+    for (x=[0,model_outerw]) translate([x,0,0]) GpsBodyOuterBevel(model_outerh);
+    for (y=[0,model_outerh]) translate([0,y,0])
+      rotate([0,0,-90]) GpsBodyOuterBevel(model_outerw);
+  }
+}
+
+module GpsPlug() {
+  plugwireh = plug2bot + plugwiremoreh;
+  translate([-plugslop,0,0]) GpsPlugPlug(-plugslop);
+  mirror([0,0,1]) translate([plug2lhs, plug2bot, 0]) {
+    cube([plugw, plugh, plugtotald-0.05]);
+    translate([0, -plugwireh, pluggapd])
+      cube([plugw, plugwireh+0.05, plugtotald-pluggapd]);
+  }
+}
+
+lhsteethu = 2;
+
+module GpsLHSMask(xslop=0){
+  translate([plug2lhs + plugw+plugh+plugstrutw,
+            0,
+            -50]) {
+    for (iter=[-100/lhsteethu : 100/lhsteethu]) {
+      translate([0, iter*lhsteethu*2, 0]) {
+       linear_extrude(height=100) {
+         polygon([[-300,     0],
+                  [   0,     0],
+                  [lhsteethu,lhsteethu],
+                  [   0,     lhsteethu*2],
+                  [-300,     lhsteethu*2+0.1]]);
+       }
+      }
+    }
+  }
+}
+
+module GpsAssembled(){ ////toplevel
+  GpsBody();
+  GpsPlug();
+}
+
+module GpsBodyLT(){
+  intersection(){
+    GpsBody();
+    GpsLHSMask();
+  }
+}
+
+module GpsBodyRT(){
+  difference(){
+    GpsBody();
+    GpsLHSMask(bodylhsrhsslop);
+  }
+}
+
+module GpsPlugT(){ ////toplevel
+  rotate([0,-90,0]) GpsPlug();
+}
+
+module NestleCubeCutout(ca,cb,d){
+  dist = cb - ca;
+  cuth = -nestleh + nestlefloorh;
+  mirror([0,1,0]){
+    translate([0,1,0])
+    rotate([90,0,0]){
+      linear_extrude(height=d+2){
+       polygon([[ca+nestlebevel, cuth],
+                [ca, cuth+nestlebevel*2],
+                [ca, -dist/2/nestlearchslope-nestleceilh],
+                [(ca+cb)/2, -nestleceilh],
+                [cb, -dist/2/nestlearchslope-nestleceilh],
+                [cb, cuth+nestlebevel*2],
+                [cb-nestlebevel, cuth]]);
+      }
+    }
+  }
+}
+
+module NestleCube(){ ////toplevel
+  midw = nestlew/2;
+  midd = min(nestledl,nestledr);
+  midddb = max(nestleddbl,nestleddbr);
+
+  based0 = nestleddf;
+  based1 = midd - midddb;
+  basew0 = -nestledwr;
+  basew1 = +nestledwl-nestlew;
+
+  echo("wl,wr=", basew1, basew0);
+  echo("df,dbl,dbm,dbr",
+       based0, nestledl-nestleddbl, based1, nestledr-nestleddbr);
+
+  cutd0 = based0 + nestlewallmin;
+  cutd1 = based1 - nestlewallmin;
+  cutw0 = basew0 - nestlewallmin;
+  cutw1 = basew1 + nestlewallmin;
+
+  bevth = -nestleh + nestlebevel*2;
+  bevw = nestlebevel;
+  bevd = nestlebevel;
+
+  translate([-(basew0+basew1)/2, -(based0+based1)/2, 0]) {
+    difference(){
+      polyhedron
+       (points=
+        [[          +0      ,            +0,        0], // 0
+         [          +0      ,            +nestledr, 0], // 1
+         [          -midw   ,            +midd,     0], // 2
+         [          -nestlew,            +nestledl, 0], // 3
+         [          -nestlew,            +0,        0], // 4
+         [-nestledwr+0      , +nestleddf +0,        bevth], // 5
+         [-nestledwr+0      , -nestleddbr+nestledr, bevth], // 6
+         [          -midw   , -midddb    +midd,     bevth], // 7
+         [+nestledwl-nestlew, -nestleddbl+nestledl, bevth], // 8
+         [+nestledwl-nestlew, +nestleddf +0,        bevth], // 9
+         [-nestledwr+0      -bevw, +nestleddf +0       +bevd, -nestleh], // 10
+         [-nestledwr+0      -bevw, -nestleddbr+nestledr-bevd, -nestleh], // 11
+         [          -midw        , -midddb    +midd    -bevd, -nestleh], // 12
+         [+nestledwl-nestlew+bevw, -nestleddbl+nestledl-bevd, -nestleh], // 13
+         [+nestledwl-nestlew+bevw, +nestleddf +0       +bevd, -nestleh]], // 14
+        triangles=[// main side panels
+                   [0,1,6],[6,5,0],
+                   [1,2,7],[7,6,1],
+                   [2,3,8],[8,7,2],
+                   [3,4,9],[9,8,3],
+                   [4,0,5],[5,9,4],
+                   // bevels
+                   [6,7,12],[12,11,6],
+                   [7,8,13],[13,12,7],
+                   [8,9,14],[14,13,8],
+                   [9,5,10],[10,14,9],
+                   [5,6,11],[11,10,5],
+                   // top and bottom
+                   [4,3,2],[2,1,0],[0,4,2],
+                   [12,13,14],[10,11,12],[12,14,10]],
+        convexity=3);
+      union(){
+       #NestleCubeCutout(cutw1, cutw0, max(nestledl,nestledr));
+       #rotate([0,0,90]) NestleCubeCutout(cutd0, cutd1, nestlew);
+      }
+    }
+  }
+
+  translate([gpsrightwardoffset,-gpsrearwardoffset,0])
+    rotate([0,0,90+gpsazimuth])
+    translate([nestledoveclipw/2,0,DoveClip_depth()-0.5])
+    rotate([0,-90,0])
+    DoveClipPairSane(count=3, h=nestledoveclipw);
+}
+
+module NestleCubeBaseTest(){ ////toplevel
+  intersection(){
+    translate([0,0,nestleh]) NestleCube();
+    translate([-100,-100,0]) cube([200,200,nestlebevel*5]);
+  }
+  cube([5,5,10]);
+}
+
+module NestleCubeCeilTest(){ ////toplevel
+  intersection(){
+    translate([0,0,3]) NestleCube();
+    translate([-100,-100,0]) cube([200,200,5.5]);
+  }
+  cube([5,5,10]);
+}
+
+module NestleCubePin(){ ////toplevel
+  DoveClipPin(nestledoveclipw*0.4);
+}
+
+module HolderSideL(){ ////toplevel
+  minz = -(bezelw - holderbezelmore) - holderbackt;
+  holdert = holder_outert + holderwallt*2;
+  cylr = 0.5*sqrt(holderdcw*holderdcw + holderdoveclipl*holderdoveclipl);
+  difference(){
+    translate([-holderh,
+              -holderwallt,
+              minz]) {
+      cube([holderh + holderhgap + cylr,
+           holdert,
+           -minz]);
+      translate([holderh + holderhgap + cylr, holdert/2, 0]) {
+       cylinder(r=cylr, h=-minz);
+       rotate([0,0,gpselevation])
+         translate([0, -holderdoveclipl/2, -minz + DoveClip_depth()])
+         rotate([0,-90,-90])
+         DoveClipPairSane(count=holderdccount, h=holderdoveclipl);
+      }
+    }
+    translate([-holderh-1,
+              0,
+              minz + holderbackt])
+      cube([holderh+1,
+           holder_outert,
+           bezelw]);
+  }
+}
+
+module HolderSideR(){ ////toplevel
+  mirror([0,1,0]) HolderSideL();
+}
+
+module ChassisBar(){ ////toplevel
+  dist = holder_outerw - 2*((bezelw - holderbezelmore) + DoveClip_depth());
+  cliph = holderdcw;
+  for (mir=[0,1]) {
+    mirror([mir,0,0]) {
+      translate([dist/2, cliph/2, 0])
+       DoveClipPairSane(h=holderdoveclipl, count=holderdccount);
+      translate([-1, 0, 0])
+       cube([dist/2 - DoveClip_depth() + 1.1, chassish, chassist]);
+    }
+  }
+  translate([-gpsrightwardoffsetonbar, -DoveClip_depth(), 0])
+    rotate([0,0,-90])
+    DoveClipPairSane(h=nestledoveclipw, count=3,
+                    baseextend=chassist/2);
+}
+
+module HolderSidePin(){ ////toplevel
+  DoveClipPin(holderdoveclipl*0.5);
+}
+
+module Pins(){ ///toplevel
+  for (i=[1:4*holderdccount]) {
+    translate([i*10, 0, 0]) HolderSidePin();
+  }
+  for (i=[1:6]) {
+    translate([i*10, 20, 0]) NestleCubePin();
+  }
+}
+
+//GpsPlugT();
+//GpsAssembled();
+//GpsBody();
+//NestleCube();
+//NestleCubeBaseTest();
+//NestleCubeCeilTest();
+//NestleCubePin();
+//HolderSideL();
+//HolderSideR();
+//HolderSidePin();
+//ChassisBar();
+//Pins();
diff --git a/atreic-piano-stand.scad b/atreic-piano-stand.scad
new file mode 100644 (file)
index 0000000..be81606
--- /dev/null
@@ -0,0 +1,11 @@
+// -*- C -*-
+height = 40;
+depth = 20;
+thick = 3;
+width = 40;
+
+difference(){
+  cube([width, depth, height]);
+  translate([thick, -1, thick])
+    cube([width - thick*2, depth+2, height]);
+}
diff --git a/axlepin.scad b/axlepin.scad
new file mode 100644 (file)
index 0000000..8b47771
--- /dev/null
@@ -0,0 +1,47 @@
+// -*- C -*-
+//
+// axlepin.scad
+//
+// 3D designs for for securing things on axles
+// Copyright 2012,2016 Ian Jackson
+//
+// This work is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This work is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this work.  If not, see <http://www.gnu.org/licenses/>.
+
+
+
+function AxlePin_holerad() = 2;
+function AxlePin_zoffset(holerad=2, slop=0.5) = (holerad - slop)*0.7;
+
+module AxlePin(axlerad, pinlen, holerad=2, tabthick=5, slop=0.5){
+  pinr = holerad - slop;
+  intersection(){
+    union(){
+      translate([0, -pinlen/2, 0]) rotate([-90,0,0])
+       cylinder(r=pinr, h=pinlen, $fn=10);
+      translate([-tabthick, axlerad, -holerad])
+       cube([tabthick*2, holerad*2, holerad*2]);
+    }
+    translate([-50,-50,-AxlePin_zoffset(holerad,slop)])
+      cube([100,100,50]);
+  }
+}
+
+function Washer_thick() = 1.2;
+
+module Washer(axlerad, washerrad, thick=1.2, slop=0.5){
+  difference(){
+    cylinder(h=thick, r=washerrad);
+    translate([0,0,-1]) cylinder(h=thick+2, r=axlerad+slop);
+  }
+}
diff --git a/belt-cut-jig-common.scad b/belt-cut-jig-common.scad
new file mode 100644 (file)
index 0000000..5464678
--- /dev/null
@@ -0,0 +1,95 @@
+// -*- C -*-
+
+jig_max_len = 160; // print diagonally
+//jig_max_len = 30;
+
+registrationgroove_width = 0.8;
+registrationgroove_depth = 1.2;
+
+registrationprotrusion_poke = 3;
+registrationprotrusion_slope = 0.75;
+
+jig_overlap = 1;
+
+jig_ends_extra = 2;
+
+jig_iters = floor((jig_max_len - jig_ends_extra) / jig_interval);
+//jig_iters=2;
+echo(jig_iters);
+
+module RegistrationGroove(l){
+  // runs along the +ve X axis for length l but at correct z pos
+  translate([0, 0, jig_main_zsz + 0.1]) {
+    rotate([90,0,90])
+      linear_extrude(height=l)
+      polygon([[-registrationgroove_width/2, 0],
+              [ +registrationgroove_width/2, 0],
+              [ 0, -registrationgroove_depth ]]);
+  }
+}
+
+module OneJig(){
+  difference(){
+    translate([-(jig_interval/2 + jig_overlap),
+              jig_min_y,
+              -strap_thick])
+      cube([jig_interval + 2,
+           jig_max_y - jig_min_y,
+           jig_main_zsz + strap_thick]);
+    OneJigCutout();
+    translate([-100, -strap_width/2, -10])
+      cube([200, strap_width, 10]);
+   translate([-100,0,0])
+     RegistrationGroove(200);
+   for (xfrac=[-1/4,0,+1/4])
+     translate([jig_interval * xfrac, -100, 0])
+       rotate([0,0,90])
+       RegistrationGroove(200);
+  }
+}
+
+module RegistrationProtrusion(){
+  // points towards the positive x axis
+  xsz = registrationprotrusion_poke;
+  ysz = registrationprotrusion_poke;
+  diag_sz = xsz * sqrt(2);
+  zsz = diag_sz / registrationprotrusion_slope;
+  hull(){
+    translate([0, 0, 0.1]){
+      linear_extrude(height=0.1)
+       polygon([[   0, -ysz ],
+                [ xsz,    0 ],
+                [   0,  ysz ]]);
+      translate([-0.1, 0, zsz ])
+       rotate([0,0,45])
+       cube(0.1);
+    }
+  }
+}
+
+module Jig(){
+  for(end=[0,1]){
+    for(yfrac=[-1/2, 0, 1/2]){
+      translate([ end
+                 ? jig_interval * (jig_iters - 0.5)
+                 : -jig_interval/2,
+                yfrac * strap_width,
+                0])
+       rotate([0,0, end ? 0 : 180])
+       translate([ jig_overlap, 0, 0 ])
+       RegistrationProtrusion();
+    }
+  }
+  for (i=[0:jig_iters-1]) {
+    translate([jig_interval * i, 0, 0])
+      OneJig();
+  }
+}
+
+module JigPrint(){
+  rotate([0,0,-45])
+    translate([0,0,jig_main_zsz])
+    rotate([180,0,0])
+    Jig();
+}
+
diff --git a/belt-hole-cut-jig-simple.scad b/belt-hole-cut-jig-simple.scad
new file mode 100644 (file)
index 0000000..68ef720
--- /dev/null
@@ -0,0 +1,96 @@
+// -*- C -*-
+
+strap_thick = 3;
+strap_width = 26.75 + 0.7;
+
+punch_dia = 11.10;
+
+punch_slop = 0.5;
+
+jig_interval = 20;
+
+reg_blocks = 3;
+
+jig_iters = 7;
+
+roof_thick = 4;
+regblock_thick = 4;
+punchtube_thick = 1.8;
+
+total_h = 33;
+punchfree_h = 8;
+
+reg_prot_width = 4;
+
+// computed:
+
+punchhole_r = punch_dia/2 + punch_slop;
+mainframe_l = jig_interval * jig_iters;
+
+mainframe_w = strap_width + reg_prot_width*2;
+
+echo(mainframe_l);
+
+module RegBlockOutline(){
+  difference(){
+    translate([0, -mainframe_w/2])
+      mirror([1,0])
+      square([total_h, mainframe_w]);
+    translate([1, -strap_width/2])
+      mirror([1,0])
+      square([strap_thick+1, strap_width]);
+  }
+}
+
+module RegBlock(){
+  translate([regblock_thick/2,0,total_h])
+    rotate([0,-90,0])
+    linear_extrude(height=regblock_thick)
+    RegBlockOutline();
+}
+
+module MainFrame(){
+  translate([jig_interval/2, -mainframe_w/2, 0])
+    mirror([1,0,0])
+    cube([mainframe_l, mainframe_w, roof_thick]);
+  for (rbi=[0:reg_blocks-1]) {
+    translate([0 +
+              -(mainframe_l-jig_interval)/(reg_blocks-1) * rbi,
+              0,0])
+      RegBlock();
+  }
+}
+
+module PerHole(){
+  for (holei=[0:jig_iters-1]) {
+    translate([-jig_interval * holei, 0, 0])
+      child(0);
+  }
+}
+
+module Shells(){
+  PerHole(){
+    cylinder(r=punchhole_r+punchtube_thick, h=total_h-punchfree_h, $fn=50);
+  }
+}
+
+module Punches(){
+  PerHole(){
+    translate([0,0,-1]){
+      cylinder(r=punchhole_r, h=total_h+2, $fn=100);
+      %cylinder(r=punch_dia/2, h=total_h);
+    }
+  }
+}
+
+module Jig(){
+  difference(){
+    union(){
+      MainFrame();
+      Shells();
+    }
+    Punches();
+  }
+}
+
+Jig();
diff --git a/belt-hole-cut-jig.scad b/belt-hole-cut-jig.scad
new file mode 100644 (file)
index 0000000..36380a0
--- /dev/null
@@ -0,0 +1,38 @@
+// -*- C -*-
+
+strap_thick = 3;
+strap_width = 26.75 + 0.7;
+
+jig_interval = 20;
+
+edgewall_width = 3;
+
+jig_ywidth = 17;
+
+jig_min_y = -jig_ywidth;
+jig_max_y = +jig_ywidth;
+
+
+jig_main_zsz = 28;
+punch_dia = 12.75;
+
+punch_slop = 0.5;
+
+// common stuff
+
+include <belt-cut-jig-common.scad>
+
+module OneJigCutout(){
+  translate([0,0,-10])
+  cylinder(r= punch_dia/2 + punch_slop, h=100, $fn=50);
+}
+
+module Demo(){ ////toplevel
+  Jig();
+}
+
+module JigT(){ ////toplevel
+  JigPrint();
+}
+
+JigT();
diff --git a/belt-slot-cut-jig,JigT.auto.slic3r b/belt-slot-cut-jig,JigT.auto.slic3r
new file mode 100644 (file)
index 0000000..33b5b29
--- /dev/null
@@ -0,0 +1,2 @@
+fill_angle = 0
+skirt_distance = 1
diff --git a/belt-slot-cut-jig,Kit.auto.slic3r b/belt-slot-cut-jig,Kit.auto.slic3r
new file mode 100644 (file)
index 0000000..33b5b29
--- /dev/null
@@ -0,0 +1,2 @@
+fill_angle = 0
+skirt_distance = 1
diff --git a/belt-slot-cut-jig.scad b/belt-slot-cut-jig.scad
new file mode 100644 (file)
index 0000000..543a53f
--- /dev/null
@@ -0,0 +1,195 @@
+// -*- C -*-
+
+// todo
+//  various registration marks
+//   protrustions at ends at strap width and middle
+//   grooves on top face at 1/4,1/2,3/4 length and 1/2 width
+
+strap_thick = 3;
+strap_width = 26.75 + 0.7;
+
+jig_interval = 25;
+
+edgewall_width = 3;
+crewpunch_slop = 0.3;
+main_slop = 0.25;
+
+holder_min_wall = 2;
+holder_attach_near_wall = 5;
+
+holder_attach_xsz = 5;
+holder_ctie_width = 4.0 + 0.5;
+holder_ctie_thick = 3.0 + 0.5;
+holder_attach_walls = 3;
+holder_attach_roof = 2.5;
+
+holder_corner_round = 2.0;
+
+punch_travel = 8;
+
+// from careful measurement
+
+crewpunch_shape =
+  [[  6, [0.6, 6.0], [1.6, 12.3] ],
+   [  8, [1.1, 6.2], [1.9, 12.5] ],
+   [ 10, [1.6, 6.5], [2.1, 12.8] ],
+   [ 12, [1.8, 6.6], [2.3, 12.7] ],
+   [ 14, [2.1, 6.8], [2.6, 13.0] ],
+   [ 16, [2.4, 6.9], [2.7, 13.2] ],
+   [ 18, [2.5, 7.0], [2.9, 13.3] ],
+   [ 22, [3.1, 7.1], [3.2, 13.4] ],
+   [ 26, [3.3, 7.2], [3.5, 13.6] ], 
+   ];
+
+crewpunch_shaft_max_y = 7.5;
+
+crewpunch_systematic_size_error = +0.36;
+
+crewpunch_smallest_shape = crewpunch_shape[0];
+crewpunch_biggest_shape = crewpunch_shape[len(crewpunch_shape)-1];
+
+crewpunch_skew_angle = 3.5; //degrees
+crewpunch_skew_yoff = +1.1; //mm
+
+// computed
+
+punch_dx = 0.5 * (-crewpunch_biggest_shape[2][0]
+                 +crewpunch_biggest_shape[2][1]);
+punch_dy = 0.5 * (+crewpunch_biggest_shape[1][1]
+                 -crewpunch_biggest_shape[1][0]) + crewpunch_skew_yoff;
+
+attach_ysz = holder_attach_walls*2 + holder_ctie_width;
+
+holder_block_zsz = crewpunch_biggest_shape[0] - crewpunch_smallest_shape[0];
+holder_xsz = crewpunch_biggest_shape[2][0] + crewpunch_biggest_shape[2][1] +
+  holder_min_wall*2;
+
+holder_skewangle_yextra = holder_xsz/2 * sin(abs(crewpunch_skew_angle));
+
+holder_max_y = punch_dy + crewpunch_biggest_shape[1][0] + holder_min_wall
+  + crewpunch_systematic_size_error + holder_skewangle_yextra;
+
+holder_attach_max_y = punch_dy
+  - max(crewpunch_biggest_shape[1][1], crewpunch_shaft_max_y)
+  - crewpunch_systematic_size_error - holder_skewangle_yextra;
+
+holder_block_min_y = punch_dy
+  - crewpunch_biggest_shape[1][1] - holder_attach_near_wall +
+  - crewpunch_systematic_size_error - holder_skewangle_yextra;
+
+holder_all_min_y = holder_attach_max_y - attach_ysz;
+
+jig_max_y = max(holder_max_y + main_slop, strap_width/2) + edgewall_width;
+jig_min_y = min(holder_all_min_y - main_slop, -strap_width/2) - edgewall_width;
+
+jig_main_zsz = holder_block_zsz + punch_travel;
+
+// common stuff
+
+include <belt-cut-jig-common.scad>
+
+// objects
+
+module CrewPunch(){
+  ourslop = crewpunch_slop - crewpunch_systematic_size_error;
+  hull(){
+    for(layer=crewpunch_shape){
+      translate([0,0, layer[0]]){
+       for(xind=[0,1]) //translate([xind?0:1,0,0])
+         for(yind=[0,1]) //translate([0,yind?0.5:0,0])
+           mirror([xind?1:0,0,0]) mirror([0,yind?0:1,0]){
+             translate([-0.1,-0.1,-0.1])
+               cube([0.1 + layer[2][xind] + ourslop,
+                     0.1 + layer[1][1-yind] + ourslop,
+                     0.2]);
+           }
+      }
+    }
+  }
+}
+
+module MaybeRoundedCube(sizes, roundedness){
+  if (roundedness > 0) {
+    translate([roundedness, roundedness, 0]){
+      minkowski(){
+       cube([sizes[0] - roundedness*2,
+             sizes[1] - roundedness*2,
+             sizes[2]]);
+       cylinder(h=0.05, r=roundedness, $fn=20);
+      }
+    }
+  } else {
+    cube(sizes);
+  }
+}
+
+module PunchHolder(cutouts=true){
+  roundedness = cutouts ? holder_corner_round : 0;
+    difference(){
+      translate([-holder_xsz/2, holder_block_min_y, 0])
+       MaybeRoundedCube([holder_xsz,
+                         holder_max_y - holder_block_min_y,
+                         holder_block_zsz],
+                        roundedness);
+      if (cutouts)
+       rotate([0,0,-crewpunch_skew_angle])
+       translate([punch_dx,
+                  punch_dy,
+                  -crewpunch_smallest_shape[0]])
+         CrewPunch();
+    }
+    difference(){
+      translate([-holder_attach_xsz/2, holder_all_min_y, 0])
+       MaybeRoundedCube([holder_attach_xsz,
+                         attach_ysz,
+                         holder_block_zsz + punch_travel
+                         + holder_ctie_thick + holder_attach_roof + 1],
+                        roundedness);
+      if (cutouts)
+       translate([-30,
+                  holder_all_min_y + holder_attach_walls,
+                  holder_block_zsz + punch_travel])
+         cube([60, holder_ctie_width, holder_ctie_thick]);
+    }
+}
+
+module OneJigCutout(){
+  minkowski(){
+    cube([main_slop*2, main_slop*2, 50], center=true);
+    PunchHolder(false);
+  }
+}
+
+module JigT(){ ////toplevel
+  JigPrint();
+}
+
+module PunchHolderT(){ ////toplevel
+  PunchHolder(true);
+}
+
+module Demo(){ ////toplevel
+  %PunchHolder();
+  Jig();
+}
+
+module Kit(){ ////toplevel
+  JigT();
+  rotate([0,0,-45]){
+    translate([(jig_iters-1)*jig_interval/2,
+              jig_min_y - holder_max_y - 5,
+              0])
+      PunchHolder();
+  }
+}
+
+//CrewPunch();
+//PunchHolder();
+//PunchHolder(false);
+//OneJig();
+//Jig();
+Demo();
+//JigT();
+//RegistrationProtrusion();
+//PunchHolderT();
+//Kit();
diff --git a/bike-lipo-box-gland.scad b/bike-lipo-box-gland.scad
new file mode 100644 (file)
index 0000000..0e9228b
--- /dev/null
@@ -0,0 +1,59 @@
+// -*- C -*-
+
+include <commitid.scad>
+
+cable_dias = [6.5, 8.2];
+
+cd = cable_dias[1] + 0.5;
+wall = 2.5;
+
+function Gland_xlen(cabledia)    = cabledia * 1.5;
+function Gland_xdia(cabledia)    = cabledia * 2.0;
+function Gland_xoutdia(cabledia) = Gland_xdia(cabledia) * 1.1 + 0.5;
+
+// origin is centre, on outside
+// outside is in direction of positive X axies
+module GlandNegative(cabledia){
+  xlen = Gland_xlen(cabledia);
+  xdia = Gland_xdia(cabledia);
+
+  hull(){
+    rotate([0,90,0]) cylinder(r= cabledia/2, h=1);
+    translate([xdia,0,0]) rotate([0,90,0]) cylinder(r= xdia/2, h=1);
+  }
+  translate([-10,0,0])
+    rotate([0,90,0])
+    cylinder(r= cabledia/2, h=11);
+}
+
+module GlandPositive(cabledia){
+  translate([-0.1, 0,0])
+    rotate([0,90,0])
+    cylinder(r= Gland_xoutdia(cabledia)/2, h= Gland_xlen(cabledia) + 0.1);
+}  
+
+platesz = [wall, 24, 28];
+plateoff = [-platesz[0]/2, -platesz[1]/2, -platesz[2] + platesz[1]/2];
+
+module Plate(){
+  difference(){
+    union(){
+      GlandPositive(cd);
+      translate(plateoff)
+       cube(platesz);
+    }
+    GlandNegative(cd);
+  }
+}
+
+module Test(){ ////toplevel
+  Plate();
+  translate(plateoff){
+    difference(){
+      cube([15, 20, 1.2]);
+      Commitid_BestCount_M([15, 20]);
+    }
+  }
+}
+
+//Test();
diff --git a/bike-lipo-box.scad b/bike-lipo-box.scad
new file mode 100644 (file)
index 0000000..edd3701
--- /dev/null
@@ -0,0 +1,294 @@
+// -*- C -*-
+
+include <commitid.scad>
+include <utils.scad>
+include <sealing-box.scad>
+include <bike-lipo-box-gland.scad>
+
+pxp6012_rad = 22.5 / 2 + 0.5; // make circular hole this size in outer wall
+pxp6012_rad_outer = 32.0 / 2 - 0.5;
+
+s1930_y = 30.2 + 0.2;
+s1930_x =   22 + 0.2;
+s1930_y_outer = 36.4 + 0.2;
+s1930_x_outer = 27.6 + 0.2;
+
+s1930_recess = 3;
+s1930_around = 3;
+s1930_behind = 3;
+
+jdae12pa_rad = 12 / 2 + 0.5;
+jdae12pa_rad_outer = 19 / 2 + 0.5; // head of an "M12 bolt"
+
+totx_inner = 180;
+toty_outer = 95;
+totz_inner = 27.0;
+
+wallthick = 2.5;
+
+cabledia = 8.7;
+
+strap_w = 5 + 1;
+strap_th = 4 + 1;
+strap_pillar = 3;
+strap_pillard = 5;
+strap_over = 2;
+
+lipokeeper_w = 10;
+lipokeeper_h = 8;
+lipokeeper_d_min = 2;
+lipokeeper_slope = 0.75;
+lipokeeper_end_h = 12;
+lipokeeper_end_d_min = 15;
+
+straps_at_box = [45, 95, 125, 160];
+straps_every = 30;
+
+// calculated
+
+totx_outer = totx_inner + wallthick*2;
+toty_inner = toty_outer - wallthick*2;
+totz_outer = totz_inner + wallthick*2;
+
+sb_box_sz = [totx_outer, totz_outer, toty_inner];
+
+// origin is at centre on outer face wall
+// outside is towards positive x
+// mounting is vertical
+module S1930_Positive(){
+  d = s1930_recess + s1930_behind;
+  translate([-d/2, 0,0])
+    cube([d,
+         s1930_x_outer + s1930_around,
+         s1930_y_outer + s1930_around], center=true);
+}
+module S1930_Negative(){
+  cube([60, s1930_x, s1930_y],
+       center=true);
+  translate([1, 0,0])
+    cube([s1930_recess*2+2, s1930_x_outer, s1930_y_outer],
+        center=true);
+}
+
+module TestWall(){ ////toplevel
+  sw_ctr = [25, wallthick, 25];
+
+  rotate([0,0,-90]){
+    difference(){
+      union(){
+       cube([50, wallthick, 42]);
+      }
+
+      translate([30, -1, 20])
+       rotate([-90,0,0])
+       cylinder(r = pxp6012_rad, h=10, $fn=60);
+
+      rotate([90,0,0])
+       Commitid_BestCount([15,40]);
+    }
+  }
+
+  difference(){
+    union(){
+      cube([50, wallthick, 50]);
+      translate(sw_ctr)
+       rotate([0,0,90])
+       S1930_Positive();
+    }
+
+    translate(sw_ctr) {
+      rotate([0,0,90])
+       S1930_Negative();
+    }
+  }    
+}
+
+ts_totx = 30;
+ts_toty = 25;
+ts_totz_inner = 8;
+
+ts_box_sz = [ts_totx, ts_toty, ts_totz_inner];
+
+$sealingbox_wallth = wallthick;
+$sealingbox_floorth = wallthick;
+$sealingbox_ceilth = wallthick;
+
+module TestSealBox(){ ////toplevel
+  $sealingbox_sz = ts_box_sz;
+
+  SealingBox_RectBox();
+  ts_cidoff = ($sealingbox_cnrrad * (1-.7) + wallthick * .8) * [1,1];
+  translate(ts_cidoff)
+    Commitid_BestCount([ts_totx,ts_toty] - 2*ts_cidoff);
+}
+
+module TestSealLid(){ ////toplevel
+  $sealingbox_sz = ts_box_sz;
+
+  difference(){
+    SealingBox_RectLid();
+
+    translate([ts_totx * .75, ts_toty/2, 0])
+      cylinder(h=100, r=5);
+    
+    translate([-wallthick + $sealingbox_cnrrad*.5,
+              $sealingbox_cnrrad*.5 - wallthick,
+              ts_totz_inner + $sealingbox_ceilth])
+      Commitid_BestCount([ts_totx * .75 - 2.5 - ($sealingbox_cnrrad*.5),
+                         ts_toty - ($sealingbox_cnrrad*.5 - wallthick)*2]);
+  }
+}
+
+module TestSealLidPrint(){ ////toplevel
+  rotate([180,0,0]) TestSealLid();
+}
+
+module ProfileDemos(){ ////toplevel
+  $sealingbox_sz = ts_box_sz;
+
+  SealingBox_WallProfile();
+  color("blue") SealingBox_FloorProfile();
+  SealingBox_LidProfile();
+  color("blue") SealingBox_CeilProfile();
+  color("red") translate([-5,0]) square([1,ts_totz_inner]);
+}
+
+module AtGlands(){
+  for (dgy=[-15,-45]) {
+    translate([totx_inner + wallthick - $sealingbox_cnrrad * .3,
+              toty_inner + dgy,
+              totz_inner/2])
+      children();
+  }
+}
+
+module StrapKeepers(at){
+  strap_x_tot = strap_w + strap_pillar*2;
+
+  for (sx= at) {
+    echo("strapkeeper at ",sx);
+    translate([sx - strap_x_tot, 0, 0])
+      difference(){
+      translate([0,0, -0.1])
+       cube([strap_x_tot, strap_pillard, strap_th + strap_over]);
+      translate([strap_pillar, -1, 0])
+       cube([strap_w, strap_pillard+2, strap_th]);
+    }
+  }
+}
+
+chargingconn_x = pxp6012_rad_outer + 1 + $sealingbox_cnrrad;
+switch_x = chargingconn_x + pxp6012_rad_outer
+  + s1930_y_outer/2 + s1930_around;
+
+module AtSealingBox(){
+  rotate([90,0,0])
+    translate([-wallthick,-wallthick, -toty_inner])
+    children();
+}
+
+module Box(){ ////toplevel
+  $sealingbox_sz = sb_box_sz;
+
+  difference(){
+    union(){
+      AtSealingBox()
+       SealingBox_RectBox();
+
+      translate([switch_x, toty_inner, totz_inner/2])
+       rotate([90,0,90])
+       S1930_Positive();
+
+      // keepers for lipo
+      for (keepers= [[ 35, lipokeeper_d_min,     lipokeeper_h,
+                      [ 40, 80, 120, 150 ] ],
+                    [ 10, lipokeeper_end_d_min, lipokeeper_end_h,
+                      [ 25 ] ]
+                    // each entry: [ y, d_min, h, [ x, ...] ]
+                    ])
+       for (kx= keepers[3]) {
+         translate([kx, keepers[0], -1])
+           hull(){
+             cube([lipokeeper_w, keepers[1], keepers[2] +1]);
+             cube([lipokeeper_w,
+                   keepers[1] + keepers[2] / lipokeeper_slope,
+                   1]);
+           }
+      }
+
+      AtGlands()
+       GlandPositive(cabledia);
+
+      translate([0, toty_inner+wallthick, -wallthick])
+       rotate([180, 0,0])
+       StrapKeepers(straps_at_box);
+    }
+
+    // charging connector
+    translate([chargingconn_x,
+              toty_inner - (pxp6012_rad_outer + 5),
+              10])
+      cylinder(r= pxp6012_rad, h= totz_outer);
+
+    // vent connector
+    translate([chargingconn_x,
+              toty_inner - (pxp6012_rad_outer*2 + 5 + 15 +
+                            jdae12pa_rad_outer),
+              10])
+      cylinder(r= jdae12pa_rad, h= totz_outer);
+
+    translate([switch_x, toty_inner, totz_inner/2])
+      rotate([90,0,90])
+      S1930_Negative();
+
+    AtGlands()
+      GlandNegative(cabledia);
+
+    translate(-$sealingbox_cnrrad * [1,1,0] +
+             [totx_inner, toty_inner/2, -wallthick])
+      rotate([0,0,180])
+      scale([2,2,1])
+      Commitid_Full16_M();
+  }
+}
+
+module BoxPrint(){ ////toplevel
+  rotate([-90,0,-90])
+    Box();
+}
+
+module Lid(){ ////toplevel
+  $sealingbox_sz = sb_box_sz;
+  difference(){
+    union(){
+      AtSealingBox()
+       SealingBox_RectLid();
+      translate([0, -wallthick, -SealingBox_lidbigger()])
+       mirror([0,0,1])
+       StrapKeepers([ straps_every : straps_every
+                      : totx_inner-straps_every ]);
+    }
+
+    translate($sealingbox_cnrrad * [1,0,1])
+      rotate([90,0,0])
+      scale([1.5, 1.5, 1])
+      Commitid_Small16_M();
+  }
+}
+
+module LidPrint(){ ////toplevel
+  rotate([90,0,-90])
+    Lid();
+}
+
+module Demo(){ ////toplevel
+  color("blue") Box();
+  color("red") Lid();
+}
+
+//TestWall();
+//ProfileDemos();
+//TestSealBox();
+//TestSealLid();
+//FArcSegment_mask(350);
+//StrapKeepers();
diff --git a/bike-phone-mount.scad b/bike-phone-mount.scad
new file mode 100644 (file)
index 0000000..bb69726
--- /dev/null
@@ -0,0 +1,101 @@
+// -*- C -*-
+
+// should rename this to actual name of the product
+
+include <utils.scad>
+
+mount_lip_height = 2.0 - 0.15 - 0.15;
+mount_lip_depth = 2.5 /*?*/ - 0.30;
+mount_neck_width = 26.5 - 0.55 - 0.15;
+mount_neck_length = 1.5 + 0.50;
+
+mount_diag_outer = 34.8        - 0.50;
+mount_diag_inner = 34.6 - 0.20 - 0.50;
+
+mount_slope = .65;
+mount_extra_slope = 3;
+
+mount_demo_ceil = 4;
+
+// calculated
+
+mnep0 = [0,0];
+mnep1 = mnep0 + [0,1] * mount_neck_length;
+mnep7 = mnep0 + [1,0] * mount_lip_depth;
+mnep2 = [ mnep7[0] + mount_extra_slope, mnep1[1] + mount_slope * (mnep7[0] + mount_extra_slope - mnep1[0]) ];
+mnep3 = mnep2 + [0, 0.1];
+mnep4 = [ mnep0[0]-1, mnep3[1] ];
+mnep6 = mnep7 + [0,-1] * mount_lip_height;
+mnep5 = [ mnep4[0], mnep6[1] ];
+mnepm = [ mnep0[0], mnep3[1] ];
+
+mount_total_height = mnep2[1] - mnep6[1];
+mnep_z_offset = -mnep2[1];
+mnep_side_offset = [ mount_neck_width/2, mnep_z_offset ];
+
+module MountNeckEdgePlan() {
+  polygon([ mnep0,
+           mnep1,
+           mnep2,
+           mnep3,
+           mnep4,
+           mnep5,
+           mnep6,
+           mnep7 ]);
+}
+
+module MountNeckSquare() {
+  intersection_for (r=[0,90]) {
+    rotate([0,0,r]){
+      linextr_y_xz(-100,100,convexity=10){
+       for (m=[0,1]) {
+         mirror([m,0]) {
+           translate(mnep_side_offset) MountNeckEdgePlan();
+           rectfromto([-0.1, -mount_total_height],
+                      mnep_side_offset + mnepm);
+         }
+       }
+      }
+    }
+  }
+}
+
+module MountDiagonal() {
+  rotate([0,0,45]){
+    translate([0,0, -mount_total_height]){
+      linextr(0, mount_lip_height)
+       square(center=true, mount_diag_outer);
+      linextr(0, mount_total_height)
+       square(center=true, mount_diag_inner);
+      linextr(mount_lip_height + mount_neck_length,
+             mount_total_height + 1)
+       square(center=true, 100);
+    }
+  }
+}
+
+module MountDemoCeil() {
+  c = mount_demo_ceil + mount_extra_slope;
+  linextr(0, 0.8) {
+    square(mount_neck_width + 2*(mount_demo_ceil + mount_extra_slope),
+          center=true);
+  }
+}
+
+module Mount(){
+  intersection(){
+    MountNeckSquare();
+    MountDiagonal();
+  }
+}
+
+module MountDemo(){ ////toplevel
+  Mount();
+  MountDemoCeil();
+}
+
+//MountNeckEdgePlan();
+//MountNeck();
+//MountDemoCeil();
+//MountDiagonal();
+//MountDemo();
diff --git a/bike-stalk-led-mount.scad b/bike-stalk-led-mount.scad
new file mode 100644 (file)
index 0000000..a3c0c4e
--- /dev/null
@@ -0,0 +1,71 @@
+// -*- C -*-
+
+include <commitid.scad>
+
+stalk_dia = 6.4 + 0.25;
+
+length = 50;
+width = 12;
+
+strap_below = 2;
+strap_thick = 2;
+strap_width = 5;
+strap_above = 0.25;
+
+arch_above = 2;
+
+inside_gap = 0.5;
+
+// calculated
+
+height_base = stalk_dia/2 - inside_gap/2;
+above_height = height_base + arch_above;
+below_height = height_base + max(arch_above,
+                                strap_below + strap_thick + strap_above);
+
+module StalkCutout(){
+  translate([-length,0,0])
+    rotate([0,90,0])
+    cylinder(r= stalk_dia/2, h=length*2, $fn=40);
+}
+
+module SomeBlockBase(height){
+  translate([0,0, height/2 + inside_gap/2]) {
+    difference(){
+      cube([length, width, height], center=true);
+      translate([-length/2, 0, height/2])
+       Commitid_BestCount([length*.66, width/2]);
+    }
+  }
+}
+
+module BlockAbove(){ ////toplevel
+  difference(){
+    SomeBlockBase(above_height);
+    StalkCutout();
+  }
+}
+
+module BlockBelow(){ ////toplevel
+  difference(){
+    SomeBlockBase(below_height);
+    StalkCutout();
+    translate([0,0, inside_gap/2 + strap_above + stalk_dia/2 + strap_thick/2])
+      cube([strap_width, width*2, strap_thick], center=true);
+  }
+}
+
+module BlockAbovePrint(){ ////toplevel
+  rotate([180,0,0]) BlockAbove();
+}
+
+module BlockBelowPrint(){ ////toplevel
+  rotate([180,0,0]) BlockBelow();
+}
+
+module Demo(){ ////toplevel
+  BlockAbove();
+  rotate([180,0,0]) BlockBelow();
+}
+
+//Demo();
diff --git a/biscuits.scad b/biscuits.scad
new file mode 100644 (file)
index 0000000..5f6a42a
--- /dev/null
@@ -0,0 +1,30 @@
+
+scale=0.75;
+rad=30*scale;
+hbase=28.4*scale;
+voff=10*scale;
+height=70*scale;
+
+wallheight = 15;
+wallthick=0.8;
+
+module flatsolid() {
+       circle(r=rad,$fn=50);
+       polygon(points=[[-hbase,voff],[hbase,voff],[0,height]]);
+}
+
+module mink() {
+       minkowski() {
+               flatsolid();
+               circle(r=wallthick/2);
+       }
+}   
+
+module hollow() {
+       difference() {
+               mink();
+               flatsolid();
+       }
+}
+
+linear_extrude(height=wallheight) hollow();
diff --git a/brompton-computer-guard.scad b/brompton-computer-guard.scad
new file mode 100644 (file)
index 0000000..f0154c0
--- /dev/null
@@ -0,0 +1,107 @@
+// -*- C -*-
+
+arch_height = 18;
+arch_width = 75;
+end_width = 25;
+
+arch_thick = 4;
+
+arch_breadth = 25;
+
+hole_dia = 4 + 0.5;
+
+pbase_tab = 12;
+pbase_thick = 4;
+inner_pbase_thick = 12;
+inner_pbase_rad_mul = 3;
+
+// computed
+
+arch_alpha = atan(arch_height / (arch_width/2));
+arch_beta = 2*arch_alpha;
+echo(arch_alpha,arch_beta);
+arch_in_rad = arch_width/2 / sin(arch_beta);
+arch_to_chord = arch_in_rad * cos(arch_beta);
+
+echo(inner_pbase_thick);
+
+inner_pbase_rad = arch_in_rad * inner_pbase_rad_mul;
+
+end_thick = arch_thick;
+
+holes = [[[  5  , 5  ], [16  , 21]], // left
+        [[ 18.5, 4.5], [ 4.5, 21]]]; // right
+
+module ArchCircle(rad){
+  translate([0,-arch_to_chord])
+    circle(rad, $fa=0.1);
+}
+
+module ArchProfile(pbase){
+  intersection(){
+    translate([-200,0])
+      square([400,200]);
+    difference(){
+      union(){
+       ArchCircle(arch_in_rad + arch_thick);
+       for (m=[0,1])
+         mirror([m,0])
+           translate([arch_width/2,0])
+           multmatrix([[1,pbase ? -0.75 : 0,0,0],
+                       [0,1,0,0],
+                       [0,0,1,0],
+                       [0,0,0,1]])
+           square([end_width, pbase ? pbase_tab : end_thick]);
+      }
+    }
+  }
+}
+
+module Holes(){
+  for (m=[0,1]) {
+    mirror([1-m,0])
+      translate([arch_width/2, 50, 0])
+      rotate([90,0,0])
+      for (h=holes[m]) {
+       translate(h)
+         cylinder(r=hole_dia/2, h=100, $fn=20);
+      }
+  }
+}
+
+module MainCutout(){
+  ArchCircle(arch_in_rad);
+}
+
+module Arch(){
+  difference(){
+    rotate([0,0,180]){
+      linear_extrude(height=arch_breadth) {
+       difference(){
+         ArchProfile(false);
+         MainCutout();
+       }
+      }
+      difference(){
+       translate([0,0, arch_breadth - pbase_thick])
+       linear_extrude(height=pbase_thick){
+         difference(){
+           hull(){
+             ArchProfile(true);
+             ArchProfile(false);
+           }
+           intersection(){
+             MainCutout();
+             translate([0, -inner_pbase_thick
+                        - (inner_pbase_rad - arch_in_rad)])
+               ArchCircle(inner_pbase_rad);
+           }
+         }
+       }
+      }
+    }
+    Holes();
+  }
+}
+
+rotate([0,0,45]) translate([0,0,arch_breadth]) rotate([0,180,0]) Arch();
diff --git a/cable-hole-trunking-cover.scad b/cable-hole-trunking-cover.scad
new file mode 100644 (file)
index 0000000..ed2bc31
--- /dev/null
@@ -0,0 +1,119 @@
+// -*- C -*-
+
+holedia = 25;
+tapethick = 1.5;
+cutoutsz= 15;
+innerz = 11;
+
+sidesflatbase = 2;
+endsflatbase = 8;
+
+basex = holedia + endsflatbase*2;
+basey = holedia + sidesflatbase*2;
+
+bevely = 2.75;
+bevelslope = 0.75;
+bevelz = bevely / bevelslope;;
+basebevelt = 3;
+
+sideslop = 0.5;
+
+basebaset = 2;
+sidewallt = 2;
+
+lidt = 1.3;
+endwallt = 2;
+zslop = 0.75;
+endslop = 0.75;
+
+module sheared_cube(sz, xperz, yperz) {
+  multmatrix([[1,0,xperz,0],
+             [0,1,yperz,0],
+             [0,0,1,    0],
+             [0,0,0,    1]])
+    cube(sz);
+}
+
+module Base(cutouty){
+  echo(cutouty);
+  difference(){
+    union(){
+      for (mir=[0,1]) mirror([0,mir,0]) {
+       translate([0, basey/2 - basebevelt, 0])
+         sheared_cube([basex, basebevelt, bevelz], 0, bevelslope);
+       cube([basex, basey/2, basebaset]);
+      }
+    }
+    translate([basex/2, 0, -1])
+      cylinder(r=holedia/2, h=bevelz+2);
+  }
+  rotate([90, 0, 90]) {
+    linear_extrude(height=endwallt) {
+      difference(){
+       for (mir=[0,1]) mirror([mir,0,0]) {
+           polygon([[-0.1,             0],
+                    [basey/2,          0],
+                    [basey/2 + bevely, bevelz],
+                    [basey/2 + bevely, innerz],
+                    [-0.1,             innerz]]);
+       }
+       translate([cutouty, 0])
+         square(size=[cutoutsz, 3*innerz], center=true);
+      }
+    }
+  }
+}
+
+module Lid(){
+  lidx = basex + endslop + endwallt;
+  for (mir=[0,1]) mirror([0,mir,0]) {
+    translate([0, basey/2 + sideslop + bevely, 0])
+      rotate([90,0,90])
+      linear_extrude(height = lidx)
+      polygon([[0,         0],
+              [-bevely,   0],
+              [0,         bevelz],
+              [0,         innerz + lidt + zslop],
+              [sidewallt, innerz + lidt + zslop],
+              [sidewallt, -tapethick],
+              [0,         -tapethick]]);
+    translate([0, -1, innerz + zslop])
+      cube([lidx, 1 + basey/2 + sideslop + bevely + sidewallt, lidt]);
+    translate([basex + endslop, -1, -tapethick])
+      cube([endwallt, 1 + basey/2 + sideslop + bevely + sidewallt,
+           tapethick + innerz + zslop + 0.1]);
+  }
+}
+
+module LidT(){ ////toplevel
+  rotate([180,0,0]) Lid();
+}
+
+module BaseCMid(){ ////toplevel
+  Base(0);
+}
+
+module BaseCTop(){ ////toplevel
+  Base(basey/2 + bevely - cutoutsz/2);
+}
+
+module BaseCBot(){ ////toplevel
+  Base(-(basey/2 + bevely - cutoutsz/2));
+}
+
+module BaseCNone(){ ////toplevel
+  Base(basey);
+}
+
+module Demo(){ ////toplevel
+  BaseCTop();
+  %Lid();
+}
+
+//BaseCTop();
+//BaseCMid();
+//BaseCBot();
+//BaseCNone();
+//Lid();
+//LidT();
+//Demo();
diff --git a/cable-splice-clamp.scad b/cable-splice-clamp.scad
new file mode 100644 (file)
index 0000000..bd27d86
--- /dev/null
@@ -0,0 +1,138 @@
+// -*- C -*-
+
+include <commitid.scad>
+
+rnom = 3.5 / 2;
+
+// alpha is slope angle, which is half of inner concave angle that
+//  wire sits in
+alpha = 40; // degrees
+
+// mu is minimum number of cable radii that cable tangent point (line)
+//  with splint ought to be away from edge of split
+mu = 1/4;
+
+// wall thickness, and base width as fraction of cable size
+wall_r = 3.5 / 6.5;
+base_r = 0.75;
+
+total_len = 60;
+
+strap_width = 3.0 + 0.5;
+
+strap_count = 4;
+
+// for cross-section calculations:
+//
+// origin O is at intersection of straight line segments forming walls
+// C is centre of circle (wire x-section) (of radius r or radius 1)
+//       which is tangent to lines
+// T is said tangent points
+// B is inner base point, which is extension of line from B by mu*r
+
+sina = sin(alpha);
+cosa = cos(alpha);
+tana = sina/cosa;
+
+// blah_r is blah where r=1
+// d_AB is distance AB
+// dy_AB is " " " vertical component
+
+d_OT_r = tana;
+d_OB_r = tana + mu;
+
+d_OC_r = 1/cosa;
+
+dy_OB_r = d_OB_r * sina;
+
+// *0 and *1 relate to smallest and largest wire
+// r[01] is radius
+// r10 is radius ratio
+
+r10 = d_OC_r / dy_OB_r;
+
+r0 = rnom / sqrt(r10);
+r1 = rnom * sqrt(r10);
+
+x_B_r =   d_OB_r * cosa;
+y_B_r = -dy_OB_r;
+
+x_T_r =         sina;
+y_T_r = -tana * sina;
+
+wall_x_r = wall_r / tan(90-alpha);
+
+top = wall_r * r1 - (d_OC_r - 1) * r0;
+basew = base_r * rnom;
+
+echo("dias", r0*2, r1*2, "ratio",r1/r0);
+
+module CrossSectionHalf(plus=0) {
+  difference(){
+    polygon([[-0.1,                                y_T_r * r0],
+            [x_T_r * r0,                          y_T_r * r0],
+            [x_B_r * r1,                          y_B_r * r1],
+            [x_B_r * r1 + wall_x_r * rnom + plus, y_B_r * r1],
+            [basew                        + plus, top],
+            [-0.1,                                top]]);
+    translate([0, -d_OC_r * r0])
+      circle(r = r0, $fn=20);
+  }
+}
+
+module CrossSection(plus=0) {
+  for (m=[0,1]) {
+    mirror([m,0])
+      CrossSectionHalf(plus);
+  }
+}
+
+module CrossSectionDemo(){ ////toplevel
+  color("black") CrossSection(2);
+  CrossSection();
+  for (rc=[["red", r1],
+          ["blue",r0]]) {
+    color(rc[0]) translate([0, -d_OC_r * rc[1]]) circle(r = rc[1]);
+  }
+}
+
+strap_wall_h = 1.5;
+strap_wall_l = 2.0;
+
+writing_dx = total_len / 3;
+writing_dy = basew*2;
+
+module HalfClamp(){ ////toplevel
+  difference(){
+    rotate([90,0,0])rotate([0,90,0]){
+      linear_extrude(height=total_len)
+       CrossSection();
+
+      for (i=[0 : strap_count]){
+       if (i*2 != strap_count) {
+         translate([0, 0,
+                    total_len * (i + 0.5) / (strap_count + 1)])
+           for (m=[0,1]){
+             mirror([0,0,m])
+               translate([0,0, strap_width/2])
+               linear_extrude(height=strap_wall_l)
+               CrossSection(strap_wall_h);
+           }
+       }
+      }
+    }
+
+    translate([0, -basew, top])
+      Commitid_BestCount([writing_dx, writing_dy]);
+  }
+}
+
+module HalfClampPrint(){ ////toplevel
+  rotate([180,0,0])
+    HalfClamp();
+}
+
+//CrossSection();
+//CrossSectionDemo();
+//HalfClamp();
+HalfClampPrint();
diff --git a/calib-fit.scad b/calib-fit.scad
new file mode 100644 (file)
index 0000000..dbee918
--- /dev/null
@@ -0,0 +1,20 @@
+th=3;
+holesz=6;
+outsz=15;
+
+module small(sz=holesz,dsz=0,dz=0) {
+       cube([sz+dsz,sz+dsz,th+dz], center=true);
+}
+
+module osmall() {
+       translate([0,outsz/2 + 10,0]) small();
+}
+module obig() {
+       difference() {
+               cube([outsz,outsz,th], center=true);
+               small(dz=1);
+       }
+}
+
+osmall();
+obig();
diff --git a/camera-mount.scad b/camera-mount.scad
new file mode 100644 (file)
index 0000000..fec9096
--- /dev/null
@@ -0,0 +1,28 @@
+// -*- C -*-
+
+include <threads.scad>
+
+inch = 25.4;
+
+negative_dia = inch * 1/4. + 0.375;
+negative_default_l =   10.0;
+
+negative_tpi = 20;
+negative_pitch = inch/negative_tpi;
+negative_chamfer = negative_pitch/2;
+
+module CameraMountThread(l){
+  rotate([0,180,0])
+    english_thread(diameter=negative_dia/inch,
+                  threads_per_inch=negative_tpi,
+                  leadin=0, internal=true, test=$test,
+                  length= (l + inch/19) / inch);
+  hull(){
+    translate([0,0, negative_chamfer])
+      cylinder(r= negative_dia/2 + negative_chamfer*2,
+              h=1);
+    mirror([0,0,1])
+      cylinder(r= negative_dia/2 - negative_chamfer*2,
+                  h= negative_chamfer*3);
+  }
+}
diff --git a/chimney-cable-retainer.scad b/chimney-cable-retainer.scad
new file mode 100644 (file)
index 0000000..2f783d0
--- /dev/null
@@ -0,0 +1,71 @@
+// -*- C -*-
+
+include <utils.scad>
+
+inrear_d_real = 20;
+inrear_d = 15;
+bar_th = 5;
+general_th = 5;
+between_cables = 150;
+around_cables = 20;
+cable_dia = 15;
+
+total_d = 40;
+above_h = 40;
+
+// calculated
+
+cable_x = around_cables + cable_dia/2;
+total_x = cable_x * 2 + between_cables;
+
+below_h = above_h;
+
+sit_angle = atan2(inrear_d_real - inrear_d, below_h);
+
+module CoreElevation(){
+  rotate(-sit_angle)
+    rectfromto([ 0, 0 ],
+              [ total_d, general_th ]);
+  rectfromto([ 0, 0 ],
+            [ general_th, above_h ]);
+  translate([ inrear_d, -above_h ])
+    rectfromto([ 0,0 ],
+              [ -bar_th, bar_th ]);
+}
+
+module BarMountElevation(){
+  hull(){
+    rotate(-sit_angle)
+      rectfromto([ 0, 0 ],
+                [ inrear_d, general_th ]);
+    translate([ 0, -below_h ])
+      rectfromto([ 0,0 ],
+                [ inrear_d, bar_th ]);
+  }
+}
+
+module Retainer(){ ////toplevel
+  difference(){
+    union(){
+      linextr_x_yz(0, total_x)
+       mirror([1,0])
+       CoreElevation();
+
+      for (x = [0, 0.5, 1] * (total_x - general_th))
+       translate([ x, 0,0 ])
+       linextr_x_yz(0, general_th)
+       mirror([1,0])
+       BarMountElevation();
+    }
+
+    for (x = [cable_x, total_x - cable_x])
+      translate([x, 0, 0])
+      linextr(-below_h/2, 100)
+      hull(){
+        translate([ 0, -(general_th + cable_dia/2) ])
+         circle(r = cable_dia/2);
+       translate([ 0, -(total_d + 1)])
+         square([ cable_dia, 1], center=true);
+      }
+  }
+}
diff --git a/clip-spring-holder-clip.scad b/clip-spring-holder-clip.scad
new file mode 100644 (file)
index 0000000..11f395f
--- /dev/null
@@ -0,0 +1,48 @@
+// -*- C -*-
+//
+// For holding the spring while reassembling a candle holder.
+
+include <utils.scad>
+
+spring_body_w = 5.0;
+spring_body_l = 6.0;
+axle_dia = 2.0;
+recess_d = 13.0;
+total_len = 45.0;
+
+th_y = 1.5;
+th_x = 2;
+handle_th = 2.5;
+
+// calculated
+
+outer_sz = [spring_body_l + th_x*2, spring_body_w + th_y*2];
+handle_sz = [outer_sz[0], handle_th];
+th_z = th_x;
+
+echo(outer_sz);
+
+module OuterElevation(){
+  square(center=true, outer_sz);
+}
+
+module Elevation(){
+  difference(){
+    OuterElevation();
+
+    square(center=true, [spring_body_l, spring_body_w]);
+    square(center=true, [outer_sz[0] + 10, axle_dia]);
+  }
+}
+
+module Clip(){
+  linextr(-th_z, recess_d) Elevation();
+  linextr(-th_z, 0) OuterElevation();
+  linextr(recess_d - total_len, 0) square(center=true, handle_sz);
+}
+
+module Print(){
+  rotate([0, 90, 0]) Clip();
+}
+
+Print();
diff --git a/cliphook.scad b/cliphook.scad
new file mode 100644 (file)
index 0000000..22f08f5
--- /dev/null
@@ -0,0 +1,103 @@
+// -*- C -*-
+//
+// cliphook.scad
+//
+// 3D design for a small clippy hook
+// Copyright 2012,2016 Ian Jackson
+//
+// This work is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This work is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this work.  If not, see <http://www.gnu.org/licenses/>.
+
+function ClipHook_r2(w,g,l,ye,k) = w/2 + g + w/2;
+function ClipHook_r3(w,g,l,ye,k) = k * (ClipHook_r2(w,g,l,ye,k) + w + g);
+function ClipHook_yd(w,g,l,ye,k) = g/2 + w + g + w/2 - ClipHook_r3(w,g,l,ye,k);
+function ClipHook_xe(w,g,l,ye,k) =
+        l*1.5 + w +
+       sqrt(pow( ClipHook_r3(w,g,l,ye,k),      2) -
+            pow( ClipHook_yd(w,g,l,ye,k) - ye, 2));
+
+module FlatArc(cx,cy,r1,r2,a1,a2=361,$fn=$fn) {
+  astep = (a2-a1)/6;
+  size = 5*(r2/2);
+  translate([cx,cy,0]) {
+    intersection() {
+      difference() {
+       circle(r=r2);
+       translate([0,0,-1])
+         circle(r=r1);
+      }
+      scale(size) {
+       for (ai=[0:4]) {
+         //echo(" jarc ", a1,a2, astep, ai, a1 + astep*ai );
+         rotate(a1 + astep*ai) {
+           polygon([ [0,0], [1,0],
+                     [cos(astep*2),sin(astep*2)] ]);
+         }
+       }
+      }
+    }
+  }
+}
+
+module ClipHook_2D(w,g,l,ye,k,h) {
+  r2 =  ClipHook_r2(w,g,l,ye,k);
+  r3 =  ClipHook_r3(w,g,l,ye,k);
+  yd =  ClipHook_yd(w,g,l,ye,k);
+  xe =  ClipHook_xe(w,g,l,ye,k);
+
+  xd = l*1.5 + w;
+  xc = -l/2;
+  yc = g/2 + w/2;
+
+  alpha = atan2((xe-xd)/r3, (ye-yd)/r3);
+
+  echo("ClipHook(w g l ye k h) ", w, g, l, ye, k, h);
+  echo("ClipHook r2 r3 xd yd xe =", r2,r3, xd,yd, xe);
+
+  $fn = 20;
+
+  module jcirc(x,y) { translate([x,y,0]) circle(r=w/2); }
+  module jbox(y,x1,x2) { translate([x1,y-w/2,0]) square(size=[x2-x1, w]); }
+  module jarc(cx,cy,r,a1=0,a2=360) { FlatArc(cx,cy,r-w/2,r+w/2,a1,a2); }
+
+  jcirc(-xc, -yc);
+  jbox(-yc, xc, -xc);
+  jarc(xc, yc, r2, 90, 270);
+  jbox(yc+r2, xc, xd);
+  jarc(xd, yd, r3, 90-alpha, 90);
+  jcirc(xe,ye);
+}
+
+module ClipHook(w=1.2, g=0.2, l=0.0, ye=0, k=2.0, h=3.5, demo=false,
+               cupcaph=0, cupgapg=0) {
+  difference() {
+    linear_extrude(height=h)
+      ClipHook_2D(w,g,l,ye,k);
+    if (cupcapg != 0) {
+      translate([-g+0.01,-(w+g),h-cupcapg])
+      cube([w,(w+g),cupcaph+1]);
+    }
+  }
+  if (cupcaph != 0) {
+    translate([-l/2, g/2+w/2, h-0.01])
+      intersection() {
+        cylinder(r=ClipHook_r2(w,g,l,ye,k)+w*0.4, h=cupcaph, $fn=16);
+       translate([-50-g,-50,-1]) cube([50,100,h+2]);
+      }
+  }
+}
+
+if (ClipHook_demo) {
+  ClipHook(l=0, k=1, cupcaph=1, cupcapg=0.4);
+  %translate([l+w,0,0]) rotate(180) ClipHook(l=0, k=1, cupcaph=1, cupcapg=0.4);
+}
diff --git a/commitid-2d-test.scad b/commitid-2d-test.scad
new file mode 100644 (file)
index 0000000..a15de77
--- /dev/null
@@ -0,0 +1,3 @@
+// -*- C -*-
+include <commitid.scad>
+Commitid_2DDemo();
diff --git a/commitid-best-test.scad.pl b/commitid-best-test.scad.pl
new file mode 100755 (executable)
index 0000000..b0ca1ba
--- /dev/null
@@ -0,0 +1,26 @@
+#!/usr/bin/perl -w
+use strict;
+
+our @xm = qw(4.7 6.8 8.2 10 12 15 18 22 27 33 47 68 100);
+our @ym = qw(3.9 5.6 8.2 12 18 27 39 56);
+
+sub p { print @_ or die $!; }
+
+p "include <commitid.scad>\n";
+p "p = Commitid_pixelsz();\n";
+
+my $x = 0;
+foreach my $xm (@xm) {
+    my $y = 0;
+    foreach my $ym (@ym) {
+       p " translate([$x,$y] * p) {\n";
+       p "  difference(){\n";
+       p "   translate(-0.5*p*[1,1]) square([$xm+1,$ym+1]*p);\n";
+       p "   square([$xm,$ym]*p);\n";
+       p "  }\n";
+       p "  Commitid_BestCount_2D([$xm,$ym] * p, margin=0.2);\n";
+       p " }\n";
+       $y += $ym + 2;
+    }
+    $x += $xm + 2;
+}
diff --git a/commitid-cube-test-X.scad b/commitid-cube-test-X.scad
new file mode 100644 (file)
index 0000000..4f2f6fd
--- /dev/null
@@ -0,0 +1,76 @@
+// -*- C -*-
+
+include <commitid.scad>
+
+baseh= 1;
+
+sz = 20;
+
+fdo = [1, 3, 0];
+
+$Commitid_depth = 1.0;
+
+module FD () {
+    translate(fdo)
+      Commitid_FontDemo();
+}
+
+module TC () { ////toplevel
+  difference(){
+    cube([sz,sz,sz]);
+    translate([0,0, sz]) mirror([0,0,1]) FD();
+    rotate([90,0,0]) translate([0,0,0]) FD();
+    translate([sz,0,0]) mirror([1,0,0]) rotate([90,0,90]) FD();
+    translate([sz,sz,0]) rotate([0,0,180]) FD();
+  }
+  translate([sz,sz,0]) rotate([-90,0,0]) rotate([0,0,180]) FD();
+  translate([0,sz,0]) rotate([-90,0,90]) rotate([0,0,180]) FD();
+}
+
+w = 3;
+t = 4;
+
+fdsz = Commitid_FontDemo_sz();
+d = Commitid_depth();
+ru = Commitid_pixelsz();
+
+module TTWall () {
+  difference(){
+    translate([0, 0, -0.1])
+      cube([w, sz, sz - 2 + 0.1]);
+
+    translate([0,sz,0]) rotate([90,0,-90]) FD();
+    translate([0, sz, 0])
+      rotate([90, 0, -90])
+      translate(fdo + [0, -ru*2, -d]) cube([fdsz[0], ru, d*2]);
+  }
+  translate([w,0,0]) rotate([90,0,90]) FD();
+
+  translate([0, sz+d, 0])
+    rotate([90,0,0])
+    translate([0, fdo[1], 0]) cube([d*2, fdsz[1], ru]);
+}
+
+module TT () { ////toplevel
+  difference(){
+    translate([-sz, 0, -t])
+      cube([sz*2 + w, sz, t]);
+
+    translate([0,0,-t]) rotate([0,180,0]) FD();
+    translate([w,0,0]) rotate([0,0,0]) FD();
+
+    translate([(sz+w), 0, -t]) rotate([0,180,0])
+      Commitid_BestCount([sz+w, sz]);
+  }
+  translate([-sz,0,0]) rotate([0,0,0]) FD();
+
+  TTWall();
+  translate([0,0,-t]) rotate([90,0,0]) TTWall();
+}
+
+echo("pixelsz:", str(Commitid_pixelsz()),
+     "depth:", Commitid_depth(),
+     "sz:", Commitid_FontDemo_sz());
+
+//TC();
+TT();
diff --git a/commitid-cube-test-Y.scad b/commitid-cube-test-Y.scad
new file mode 100644 (file)
index 0000000..23aa076
--- /dev/null
@@ -0,0 +1,78 @@
+// -*- C -*-
+
+$Commitid_depth = 1.0;
+$Commitid_pixelsz = 1.5;
+
+include <commitid.scad>
+
+baseh= 1;
+
+fdo = [1, 3, 0];
+
+w = 3;
+t = 4;
+
+fdsz = Commitid_FontDemo_sz();
+d = Commitid_depth();
+ru = Commitid_pixelsz();
+echo($Commitid_pixelsz, ru, fdsz);
+
+sz = max( fdsz[0], fdsz[1] ) + ru;
+
+module FD () {
+    translate(fdo)
+      Commitid_FontDemo();
+}
+
+module TC () { ////toplevel
+  difference(){
+    cube([sz,sz,sz]);
+    translate([0,0, sz]) mirror([0,0,1]) FD();
+    rotate([90,0,0]) translate([0,0,0]) FD();
+    translate([sz,0,0]) mirror([1,0,0]) rotate([90,0,90]) FD();
+    translate([sz,sz,0]) rotate([0,0,180]) FD();
+  }
+  translate([sz,sz,0]) rotate([-90,0,0]) rotate([0,0,180]) FD();
+  translate([0,sz,0]) rotate([-90,0,90]) rotate([0,0,180]) FD();
+}
+
+module TTWall () {
+  difference(){
+    translate([0, 0, -0.1])
+      cube([w, sz, sz - 2 + 0.1]);
+
+    translate([0,sz,0]) rotate([90,0,-90]) FD();
+    translate([0, sz, 0])
+      rotate([90, 0, -90])
+      translate(fdo + [0, -ru*2, -d]) cube([fdsz[0], ru, d*2]);
+  }
+  translate([w,0,0]) rotate([90,0,90]) FD();
+
+  translate([0, sz+d, 0])
+    rotate([90,0,0])
+    translate([0, fdo[1], 0]) cube([d*2, fdsz[1], ru]);
+}
+
+module TT () { ////toplevel
+  difference(){
+    translate([-sz, 0, -t])
+      cube([sz*2 + w, sz, t]);
+
+    translate([0,0,-t]) rotate([0,180,0]) FD();
+    translate([w,0,0]) rotate([0,0,0]) FD();
+
+    translate([(sz+w), 0, -t]) rotate([0,180,0])
+      Commitid_BestCount([sz+w, sz]);
+  }
+  translate([-sz,0,0]) rotate([0,0,0]) FD();
+
+  TTWall();
+  translate([0,0,-t]) rotate([90,0,0]) TTWall();
+}
+
+echo("pixelsz:", str(Commitid_pixelsz()),
+     "depth:", Commitid_depth(),
+     "sz:", Commitid_FontDemo_sz());
+
+//TC();
+TT();
diff --git a/commitid-cube-test.scad b/commitid-cube-test.scad
new file mode 100644 (file)
index 0000000..0a5f21a
--- /dev/null
@@ -0,0 +1,74 @@
+// -*- C -*-
+
+include <commitid.scad>
+
+baseh= 1;
+
+sz = 20;
+
+fdo = [1, 3, 0];
+
+module FD () {
+    translate(fdo)
+      Commitid_FontDemo();
+}
+
+module TC () { ////toplevel
+  difference(){
+    cube([sz,sz,sz]);
+    translate([0,0, sz]) mirror([0,0,1]) FD();
+    rotate([90,0,0]) translate([0,0,0]) FD();
+    translate([sz,0,0]) mirror([1,0,0]) rotate([90,0,90]) FD();
+    translate([sz,sz,0]) rotate([0,0,180]) FD();
+  }
+  translate([sz,sz,0]) rotate([-90,0,0]) rotate([0,0,180]) FD();
+  translate([0,sz,0]) rotate([-90,0,90]) rotate([0,0,180]) FD();
+}
+
+w = 3;
+t = 2;
+
+fdsz = Commitid_FontDemo_sz();
+d = Commitid_depth();
+ru = Commitid_pixelsz();
+
+module TTWall () {
+  difference(){
+    translate([0, 0, -0.1])
+      cube([w, sz, sz - t + 0.1]);
+
+    translate([0,sz,0]) rotate([90,0,-90]) FD();
+    translate([0, sz, 0])
+      rotate([90, 0, -90])
+      translate(fdo + [0, -ru*2, -d]) cube([fdsz[0], ru, d*2]);
+  }
+  translate([w,0,0]) rotate([90,0,90]) FD();
+
+  translate([0, sz+d, 0])
+    rotate([90,0,0])
+    translate([0, fdo[1], 0]) cube([d*2, fdsz[1], ru]);
+}
+
+module TT () { ////toplevel
+  difference(){
+    translate([-sz, 0, -t])
+      cube([sz*2 + w, sz, t]);
+
+    translate([0,0,-t]) rotate([0,180,0]) FD();
+    translate([w,0,0]) rotate([0,0,0]) FD();
+
+    translate([(sz+w), 0, -t]) rotate([0,180,0])
+      Commitid_BestCount([sz+w, sz]);
+  }
+  translate([-sz,0,0]) rotate([0,0,0]) FD();
+
+  TTWall();
+  translate([0,0,-t]) rotate([90,0,0]) TTWall();
+}
+
+echo("pixelsz:", str(Commitid_pixelsz()),
+     "depth:", Commitid_depth(),
+     "sz:", Commitid_FontDemo_sz());
+
+//TC();
+TT();
diff --git a/commitid-layering-test.scad b/commitid-layering-test.scad
new file mode 100644 (file)
index 0000000..04d8c83
--- /dev/null
@@ -0,0 +1,31 @@
+// -*- C -*-
+
+include <commitid.scad>
+
+baseh= 1;
+basex = 31;
+basey= 20;
+basexpos = -12;
+baseypos = -4;
+
+module Body(){
+  mirror([0,0,1])
+    translate([basexpos, baseypos])
+    cube([basex, basey, baseh]);
+}
+
+difference(){
+  Body();
+  translate([basexpos, baseypos, -baseh])
+    Commitid_BestCount_M([basex,basey], margin=3);
+
+  translate([-6, 6, -0.4])
+    cylinder(r=5, h=3, $fn=50);
+}
+
+translate([-6, 6, -0.5])
+  cylinder(r=4, h=3.5, $fn=50);
+
+echo("pause height (mm)", baseh+0.01);
+
+Commitid_FontDemo();
diff --git a/crossbar-computer-led-mount.scad b/crossbar-computer-led-mount.scad
new file mode 100644 (file)
index 0000000..535c680
--- /dev/null
@@ -0,0 +1,340 @@
+// -*- C -*-
+
+led_dia = 5 + 0.6;
+led_depth = 5;
+
+led_tip_height_above_crossbar = 70;
+led_angle = -60;
+crossbar_dia = 25; // fixme
+
+vert_space_inside = 8;
+backfront_space_inside = 12;
+width_space_inside = 10;
+
+backfront_mate_size = 25;
+tower_frontheight = 10;
+tower_base_height = 20;
+tower_slot_width = 3;
+
+cableclamp_ctie_width = 4.0 + 1.0;
+cableclamp_ctie_thick = 2.5 + 0.5;
+
+lidclamp_ctie_width = 4.0 + 1.0;
+lidclamp_ctie_thick = 2.5 + 0.5;
+
+base_ctie_width = 4.0 + 1.0;
+base_ctie_thick = 2.5 + 0.5;
+
+tube_ctie_width = 4.0 + 1.0;
+tube_ctie_thick = 2.5 + 0.5;
+
+// tuning
+
+tower_over_angle = 45;
+tower_wall_thick = 1.6;
+tower_forehead_angle = 30;
+lid_wall_thick = 1.6;
+lid_slop = 0.75;
+//cableclamp_ctie_anchor = 5;
+lidclamp_cableclamp_ctie_between = 0;
+base_ctie_anchor = 5;
+tube_ctie_anchor = 5;
+protrusion_size = 2;
+protrusion_none_frontback = 10;
+protrusion_slop = 0.25;
+cableclamp_ctie_z = tower_frontheight/2;
+
+towerleg_backfront = 5;
+towerleg_width = 3;
+towerleg_foot_gap = 2;
+towerleg_foot_backfront = 20;
+towerleg_foot_width = 40;
+towerleg_foot_height = 10;
+towerleg_yslope = 0.7;
+towerleg_xslope = 0.3;
+echo(sqrt(towerleg_yslope*towerleg_yslope+towerleg_xslope*towerleg_xslope));
+
+//--- tests ---
+
+test_width = 24;
+test_height = 24;
+
+test_thicks = [9,14,21];
+
+module Tests(){ ////toplevel
+  for (thicki=[0:len(test_thicks)-1]) {
+    translate([thicki*test_width-0.5, 0, 0]) {
+      difference(){
+       cube([test_width,
+             test_thicks[thicki] + led_depth,
+             test_height]);
+       translate([test_width/2, -1, test_height/2])
+         rotate([-90,0,0])
+         cylinder(r=led_dia/2, h=led_depth+1, $fn=30);
+      }
+    }
+  }
+}
+
+//Tests();
+
+//--- real thing ---
+
+// calculated
+
+tower_overhang = led_dia * 2.5;
+tower_width = width_space_inside + tower_wall_thick*2;
+
+tower_over_max_y = tower_overhang * sin(tower_over_angle);
+tower_over_max_z = tower_frontheight + tower_overhang * cos(tower_over_angle);
+tower_total_max_z = tower_over_max_z + vert_space_inside + led_depth;
+tower_rearwall_y = -(backfront_space_inside + tower_wall_thick);
+led_head_y = tower_over_max_y/2;
+led_head_z = tower_frontheight + tower_overhang*sin(tower_over_angle)/2;
+backfront_mate_extra = (backfront_mate_size - (-tower_rearwall_y));
+
+tower_height_contribution = led_head_z + tower_base_height;
+
+base_ctie_anchor_eff = base_ctie_anchor+base_ctie_thick/2;
+tube_ctie_anchor_eff = tube_ctie_anchor+tube_ctie_thick/2;
+
+base_width = 0.7 * crossbar_dia;
+base_backfront = backfront_mate_extra - tower_rearwall_y;
+base_height = led_tip_height_above_crossbar - tower_height_contribution;
+
+protrusion_frontback = base_backfront - protrusion_none_frontback;
+
+echo(tower_height_contribution, base_height);
+
+module TowerWallCrossSection(){
+  // generates a 2D shape - a polygon
+  // x is what is going to be -y
+  // y is what is going to be z
+  polygon([[0,                  0],
+          [0,                  tower_frontheight],
+          [-tower_over_max_y,  tower_over_max_z],
+          [-tower_over_max_y
+           + tan(tower_forehead_angle) * (vert_space_inside + led_depth),
+           tower_total_max_z],
+          [-tower_rearwall_y,  tower_total_max_z],
+          [-tower_rearwall_y,  0],
+          [-tower_rearwall_y, -tower_base_height],
+          [-backfront_mate_extra, -tower_base_height]],
+         convexity=5);
+}
+
+module TowerWallSomeEdge(front){
+  minkowski(){
+    difference(){
+      TowerWallCrossSection();
+      translate([front ? 0.10 : -0.10, 0])
+       TowerWallCrossSection();
+    }
+    circle(r=tower_wall_thick, $fn=8);
+  }
+}
+
+module TowerBulkCrossSection(){
+  intersection(){
+    TowerWallCrossSection();
+    union(){
+      translate([-led_head_y, led_head_z])
+       circle(r = led_depth);
+      TowerWallSomeEdge(true);
+      translate([-50, -50])
+       square([100, 50]);
+    }
+  }
+}
+
+module TowerRearWallCrossSection(){
+  intersection(){
+    TowerWallCrossSection();
+    union(){
+      intersection(){
+       translate([0,-10]) square([100, 10+led_head_z]);
+       TowerWallSomeEdge(false);
+      }
+      TowerBulkCrossSection();
+    }
+  }
+}
+
+
+module TowerCrossSectionDemo(){
+  %TowerWallCrossSection();
+  //TowerBulkCrossSection();
+  TowerRearWallCrossSection();
+}
+
+module TowerMain(){
+  for (mir=[0,1])
+    mirror([mir,0,0]) rotate([90,0,-90]) {
+      translate([0,0, tower_width/2-tower_wall_thick])
+       linear_extrude(height=tower_wall_thick) {
+       TowerWallCrossSection();
+      }
+      translate([0,0,-1])
+       linear_extrude(height=tower_width/2+0.9)
+       union(){
+         TowerBulkCrossSection();
+         hull(){
+           intersection(){
+             TowerWallCrossSection();
+             translate([-30, -30])
+               square([30 + 0.1, 30 + tower_frontheight]);
+           }
+         }
+        }
+      translate([0,0, tower_slot_width/2])
+       linear_extrude(height=(tower_width - tower_slot_width)/2 - 0.2)
+       TowerRearWallCrossSection();
+    }
+}
+
+module LedHole(){
+  translate([0, led_head_y, led_head_z])
+    rotate([90 + led_angle, 0, 0])
+    translate([0,0,-10])
+    cylinder(r=led_dia/2, h=led_depth+1+10, $fn=26, $fa=10);
+}
+
+module TowerProper(){
+  difference(){
+    TowerMain();
+    LedHole();
+    // passages for cable ties
+    translate([0,
+              tower_rearwall_y/2,
+              cableclamp_ctie_z
+              + cableclamp_ctie_width/2 + lidclamp_ctie_thick/2
+              + lidclamp_cableclamp_ctie_between])
+      cube([50, lidclamp_ctie_width, lidclamp_ctie_thick], center=true);
+    translate([0,
+               (backfront_mate_extra+tower_rearwall_y)/2,
+               -tower_base_height
+              + max(protrusion_size + protrusion_slop + 0.1,
+                    base_ctie_anchor_eff)])
+      cube([50, base_ctie_width, base_ctie_thick], center=true);
+//    for (extra_y=[0, -(cableclamp_ctie_thick + cableclamp_ctie_anchor)]) {
+//      translate([-tower_width/2,
+//              -cableclamp_ctie_thick/2 - tower_wall_thick + extra_y,
+//              cableclamp_ctie_z])
+//     cube([tower_wall_thick+2,
+//           cableclamp_ctie_thick,
+//           cableclamp_ctie_width], center=true);
+//    }
+    for (mir=[0,1])
+     mirror([mir,0,0]) {
+       translate([tower_width/4, 20, cableclamp_ctie_z])
+         cube([cableclamp_ctie_thick,
+               tower_wall_thick*2+1+40,
+               cableclamp_ctie_width],
+              center=true);
+      }
+    translate([0, tower_rearwall_y, -tower_base_height])
+      BaseRegistrationProtrusion(protrusion_slop);
+  }
+}
+
+module Tower(){ ////toplevel
+  TowerProper();
+  for (mir=[0,1]) {
+    mirror([mir,0,0]){
+      translate([0,
+                tower_rearwall_y + 0.1,
+                -1])
+       mirror([0,0,1])
+       multmatrix([[1,0, towerleg_xslope,0],
+                   [0,1,-towerleg_yslope,0],
+                   [0,0,1,0],
+                   [0,0,0,1]])
+       cube([towerleg_width, towerleg_backfront, tower_base_height-2]);
+    }
+  }
+  translate([-towerleg_foot_width/2,
+            tower_rearwall_y - towerleg_foot_gap,
+            -tower_base_height])
+    mirror([0,1,0])
+    cube([towerleg_foot_width, towerleg_foot_backfront, towerleg_foot_height]);
+}
+
+module TowerMainHull(){
+  hull(){ TowerMain(); }
+}
+
+module Lid(){
+  intersection(){
+    difference(){
+      minkowski(){
+       TowerMainHull();
+       sphere(r=lid_wall_thick+lid_slop, $fn=8);
+      }
+      minkowski(){
+       TowerMainHull();
+       sphere(r=lid_slop, $fn=6);
+      }
+    }
+    translate([-50,-50,led_head_z]) cube([100,100,100]);
+  }
+}
+
+module LidT(){ ////toplevel
+  rotate([180,0,0]) Lid();
+}
+
+module BaseRegistrationProtrusion(extra){
+  size = protrusion_size + extra;
+  translate([0, base_backfront/2, 0]){
+    hull(){
+      translate([0,0, -0.5])
+       cube([protrusion_size*2, protrusion_frontback, 1.0], center=true);
+      translate([0, 0, protrusion_size-0.5])
+       cube([0.05, protrusion_frontback-protrusion_size*2, 1.0], center=true);
+    }
+  }
+}
+
+module Base(){
+  difference(){
+    mirror([0,0,1]){
+      hull(){
+       translate([-tower_width/2, 0, 0])
+         cube([tower_width, base_backfront, 0.1]);
+       translate([-base_width/2, 0, base_height])
+         cube([base_width, base_backfront, crossbar_dia/2]);
+      }
+    }
+    translate([0, base_backfront/2, -base_ctie_anchor_eff])
+      cube([100, base_ctie_width, base_ctie_thick], center=true);
+    translate([0, base_backfront/2, -base_height + tube_ctie_anchor_eff])
+      cube([100, tube_ctie_width, tube_ctie_thick], center=true);
+    translate([0, -1, -(base_height + crossbar_dia/2)])
+      rotate([-90,0,0])
+      cylinder(r=crossbar_dia/2, h=101);
+  }
+  BaseRegistrationProtrusion(0.0);
+}
+
+module BaseT(){ ////toplevel
+  rotate([90,0,0]) Base();
+}
+
+module Demo(){
+  Tower();
+  %Lid();
+  translate([0,0, 25]) Lid();
+  translate([0, tower_rearwall_y, -(tower_base_height+5)]) Base();
+}
+
+//TowerCrossSectionDemo();
+//TowerWallSomeEdge(false);
+//TowerWallFrontEdge();
+//TowerMainHull();
+//LidT();
+//Tower();
+//Lid();
+//BaseRegistrationProtrusion();
+//Base();
+//BaseT();
+//Demo();
diff --git a/dell-psu-glow-lampshade.scad b/dell-psu-glow-lampshade.scad
new file mode 100644 (file)
index 0000000..8733948
--- /dev/null
@@ -0,0 +1,16 @@
+// -*- C -*-
+
+india = 11.01 + 0.50;
+t = 0.9;
+l = 7.5;
+
+$fa = 3;
+$fs = 0.2;
+
+linear_extrude(height=l, convexity=10) {
+  difference(){
+    circle(r = india/2 + t);
+    circle(r = india/2);
+  }
+}
+
diff --git a/deore-crank-remover.scad b/deore-crank-remover.scad
new file mode 100644 (file)
index 0000000..1b0a738
--- /dev/null
@@ -0,0 +1,34 @@
+// -*- C -*-
+
+outdia=15.1;
+india=13.0;
+depth=10;
+
+eoutrad = outdia/2 + 1.0;
+einrad = india/2 - 1.0;
+edepth = depth + 3;
+
+handledepth = 5;
+handlewidth = 20;
+handlelength = 70;
+
+module FlatSplines(){
+  for (rot=[0:7]) {
+    rotate([0,0, rot*360/8])
+      for (m=[0,1]) {
+       mirror([m,0,0])
+         polygon([[-0.1, 0],
+                  [-0.01, eoutrad],
+                  [einrad * sin(22.5), einrad * cos(22.5)],
+                  [einrad * sin(22.5), einrad * cos(22.5) - 3],
+                  [1, 0]]);
+      }
+  }
+}
+
+translate([0,0,-1])
+  linear_extrude(height=edepth+1)
+  FlatSplines();
+
+translate([0,0,-handledepth/2])
+  cube([handlelength,handlewidth,handledepth], center=true);
diff --git a/digispark-with-cable.scad b/digispark-with-cable.scad
new file mode 100644 (file)
index 0000000..1ad978c
--- /dev/null
@@ -0,0 +1,329 @@
+// -*- C -*-
+//
+// Print (fine detail settings):
+//
+//   * Bottom
+//   * MiddlePrint
+//   * CoverPrint
+
+include <utils.scad>
+
+usb_w = 12.01 + 0.19;
+usb_wall_w = 0.51;
+usb_tongue_d = 8.97 - 0.2;
+usb_tongue_w_slop = +0.5;
+usb_wall_h = 4.54 - 2.04;
+usb_ceil_th = 0.425;
+
+wall_th = 1.5; // XXXX rename wall_th
+
+board_l = 17.56 + 0.2;
+board_w = 19.14 + 0.2;
+board_th = 1.92 + 0.1;
+
+sw_to_edge = board_w/2 + 0.1;
+
+front_wall_th = 0.75;
+// egress_w = 8.0;
+
+wall_y_min = -board_l - wall_th;
+ceil_y_min = wall_y_min - 5;;
+
+small_walls = [
+              [ [0, 0], [-sw_to_edge, -1.0] ],
+              [ [sw_to_edge-4.5, -4.5], [sw_to_edge, -5.7] ],
+//            [ [3.0, -11.72],              [sw_to_edge, -13.38] ],
+              [ [-sw_to_edge+3.85, -14.90], [sw_to_edge, -13.38] ],
+              ];
+chip_cutout = [[ -sw_to_edge + 4.20,    -3.75 ],
+              [ -sw_to_edge + 11.95,  -11.90 ]];
+
+strain_w = 3.5 + 0.5;
+strain_t = 1.5 + 0.5;
+strain_pitch_across = 5;
+strain_pitch_along = 10;
+strain_groove_d = 2;
+strain_groove_w = 4.5;
+strain_around = [2.25, 2.00];
+
+cover_strap_c_d_y = 5.5; // from front of board
+cover_registration_sz_y = 2;
+cover_registration_sz_z = 3;
+midbot_registraton_sz_x = 3;
+
+cable_space_z = 6;
+cable_dia = 6;
+bottom_floor_th = 1.5;
+
+fit_gap_z = 0.5;
+fit_gap_y = 0.25;
+side_x_gap = 0.5;
+
+cover_ceil_th = 0.9;
+
+cover_strap_sz_x = wall_th * 3.5;
+
+// calculated
+
+strap_w = strain_w;
+cover_strap_cutout_z = wall_th;
+
+middle_top_z = usb_wall_h;
+middle_base_z = -board_th;
+bottom_base_z = middle_base_z - cable_space_z - bottom_floor_th;;
+
+front_y_max = front_wall_th;
+main_y_min = -board_l - wall_th;
+
+strain_0_y_c = main_y_min - strain_w/2;
+strain_1_y_c = strain_0_y_c - strain_pitch_along;
+total_y_min = strain_1_y_c - strain_w/2 - wall_th;
+
+bottom_wall_top_z = (middle_top_z + middle_base_z) * 0.5 - fit_gap_z/2;
+cover_wall_bot_z  = (middle_top_z + middle_base_z) * 0.5 + fit_gap_z/2;
+cover_top_z  = middle_top_z + cover_ceil_th;
+
+middle_side_wall_x = +board_w/2 + wall_th;
+total_side_wall_x = middle_side_wall_x + wall_th + side_x_gap;
+
+cover_registration_c_dy = -cover_strap_c_d_y - strap_w/2
+  - wall_th - cover_registration_sz_y/2;
+
+midbot_registration_sz_y = cover_registration_sz_y;
+midbot_registration_sz_z = cover_registration_sz_z;
+midbot_registration_y_min = total_y_min + wall_th*2;
+midbot_registration_y_max = midbot_registration_y_min
+  + midbot_registration_sz_y;
+midbot_registration_y_around_max = midbot_registration_y_max
+  + wall_th*2;
+midbot_registration_bottom_x = board_w/2 - midbot_registraton_sz_x;
+
+midbot_strap_c_y = 0.5 * (strain_0_y_c + strain_1_y_c);
+
+module BothSides(){
+  for (m=[0,1]) {
+    mirror([m,0,0]) {
+      children();
+    }
+  }
+}
+
+module NormalStrapCutouts(y_c, z, rot){
+  BothSides(){
+    translate([ -total_side_wall_x, y_c, z ])
+      rotate([0, rot, 0])
+      cube([ wall_th,
+            strap_w,
+            10 ],
+          center=true);
+  }
+}
+module BottomStrapCutouts(y_c){
+  NormalStrapCutouts(y_c, bottom_base_z, -45);
+}
+module CoverStrapCutouts(){
+  BothSides(){
+    translate([ -total_side_wall_x, -cover_strap_c_d_y, cover_top_z ])
+      cube([ cover_strap_sz_x*2,
+            strap_w,
+            cover_strap_cutout_z*2 ],
+          center=true);
+  }
+}
+
+module FrontWallsPlan(slop) {
+  BothSides(){
+    rectfromto([ -board_w/2 - wall_th,    0             ],
+               [ -usb_w/2 - slop,         front_wall_th ]);
+  }
+}
+module MiddleSmallWallsPlan() {
+  for (m=[0,1]) {
+    mirror([m,0]) {
+      rectfromto([ -usb_w/2,              -0.01        ],
+                [ -usb_w/2 + usb_wall_w, usb_tongue_d ]);
+    }
+  }
+  FrontWallsPlan(0);
+  for (w=small_walls) {
+    rectfromto(w[0], w[1]);
+  }
+}
+module MiddleCeilPlan() {
+  difference(){
+    BothSides(){
+      rectfromto([ -usb_w/2,              -0.01        ],
+                [ 0.1,                   usb_tongue_d ]);
+      rectfromto([ -board_w/2 - wall_th, 0            ],
+                [ 0.1,                  ceil_y_min   ]);
+    }
+    rectfromto(chip_cutout[0], chip_cutout[1]);
+  }
+}
+module MiddleMainWallsPlan() {
+  BothSides(){
+    rectfromto([ -board_w/2 - wall_th, 0          ],
+              [ -board_w/2,           wall_y_min ]);
+  }
+  FrontWallsPlan(usb_tongue_w_slop);
+  rectfromto([ -board_w/2 - wall_th + 0, -      board_l   ],
+            [ +board_w/2 + wall_th,            total_y_min ]);
+}
+
+module RegistrationsMinkowski(){
+  minkowski(){
+    cube([ 1, fit_gap_y*2, fit_gap_z*2 ], center=true);
+    children();
+  }
+}
+module CoverRegistrations(){
+  linextr_y_xz(cover_registration_c_dy - strap_w/2,
+              cover_registration_c_dy + strap_w/2) {
+    difference(){
+      rectfromto([ -total_side_wall_x,
+                         cover_wall_bot_z - cover_registration_sz_z  ],
+                [ +total_side_wall_x, cover_top_z ]);
+      hull(){
+       MiddleElevationForCutout();
+       translate([0, -20]) MiddleElevationForCutout();
+      }
+    }
+  }
+}
+module MidBotRegistrations(){
+  linextr_y_xz(midbot_registration_y_min,
+              midbot_registration_y_max) {
+    BothSides(){
+      rectfromto([ midbot_registration_bottom_x, middle_base_z + 0.1 ],
+                [ middle_side_wall_x, middle_base_z
+                       - midbot_registration_sz_z ]);
+    }
+  }
+}
+
+module MiddleStrainHoles(){
+  BothSides(){
+    for (y_c = [strain_0_y_c, strain_1_y_c]) {
+      translate([strain_pitch_across/2, y_c, 0])
+       square([ strain_t, strain_w ], center=true);
+    }
+  }
+}
+module Middle(){ ////toplevel
+  difference(){
+    union(){
+      linextr(0, usb_wall_h)
+       MiddleSmallWallsPlan();
+      linextr(usb_wall_h - usb_ceil_th, usb_wall_h)
+       MiddleCeilPlan();
+      linextr(-board_th, usb_wall_h)
+       MiddleMainWallsPlan();
+      BothSides()
+       linextr(cover_wall_bot_z, middle_top_z)
+        rectfromto([ -(board_w/2 + 0.1),  total_y_min             ],
+                  [ -total_side_wall_x,  main_y_min - fit_gap_y  ]);
+      MidBotRegistrations();
+    }
+
+    linextr(-20, 20)
+      MiddleStrainHoles();
+    linextr_y_xz(total_y_min-1, main_y_min)
+      translate([0, middle_base_z])
+      scale([1, strain_groove_d/strain_groove_w])
+      circle(strain_groove_w/2, $fn = 8);
+    NormalStrapCutouts(midbot_strap_c_y,
+                      middle_top_z, 45);
+  }
+}
+module MiddlePrint(){ ////toplevel
+  rotate([180,0,0]) Middle();
+}
+
+module MiddleElevationForCutout(){
+    rectfromto([ -(middle_side_wall_x + side_x_gap),
+                   middle_base_z - fit_gap_z ],
+              [ +(middle_side_wall_x + side_x_gap), middle_top_z  ]);
+}
+module BottomMainElevation(){
+  difference(){
+    rectfromto([ -total_side_wall_x, bottom_base_z        ],
+              [ +total_side_wall_x, bottom_wall_top_z    ]);
+
+    MiddleElevationForCutout();
+  }
+}
+module Bottom(){ ////toplevel
+  difference(){
+    union(){
+      linextr_y_xz(total_y_min, front_y_max)
+       BottomMainElevation();
+    }
+
+    linextr_y_xz(midbot_registration_y_around_max,
+                front_y_max - wall_th)
+      rectfromto([ -board_w/2, bottom_base_z + bottom_floor_th ],
+                [ +board_w/2, 20         ]);
+
+    linextr_y_xz(total_y_min + wall_th,
+                front_y_max - wall_th)
+      rectfromto([ -midbot_registration_bottom_x,
+                    bottom_base_z + bottom_floor_th ],
+                [ +midbot_registration_bottom_x, 20         ]);
+
+    linextr_y_xz(total_y_min - 1,
+                total_y_min + wall_th + 1){
+      translate([ 0, middle_base_z ]){
+       hull(){
+         translate([ 0, -cable_dia/2 ])
+           circle(r = cable_dia/2, $fa = 10, $fs = 1);
+         square([ cable_dia, 0.1 ], center=true);
+       }
+      }
+    }
+    RegistrationsMinkowski()
+      CoverRegistrations();
+    RegistrationsMinkowski()
+      MidBotRegistrations();
+    BottomStrapCutouts(-cover_strap_c_d_y);
+    BottomStrapCutouts(midbot_strap_c_y);
+  }
+}
+
+module CoverMainElevation(){
+  difference(){
+    rectfromto([ -total_side_wall_x, cover_wall_bot_z        ],
+              [ +total_side_wall_x, cover_top_z    ]);
+
+    MiddleElevationForCutout();
+  }
+}
+module Cover(){ ////toplevel
+  difference(){
+    union(){
+      linextr_y_xz(main_y_min, front_y_max)
+       CoverMainElevation();
+      CoverRegistrations();
+    }
+    CoverStrapCutouts();
+    linextr(-20,20) {
+      minkowski(){
+       square(strain_around * 2, center=true);
+       hull() MiddleStrainHoles();
+      }
+    }
+  }
+}
+module CoverPrint(){ ////toplevel
+  rotate([180,0,0]) Cover();
+}
+
+module BottomDemo(){ ////toplevel
+  translate([0, 0, -0.25]) Bottom();
+  %Middle();
+  translate([0, 0, +0.25]) Cover();
+}
+module ImpressionDemo(){ ////toplevel
+  color("black") translate([0, 0, -0.25]) Bottom();
+  %Middle();
+  %translate([0, 0, +0.25]) Cover();
+}
diff --git a/distort-stl b/distort-stl
new file mode 100755 (executable)
index 0000000..68290c4
--- /dev/null
@@ -0,0 +1,296 @@
+#!/usr/bin/perl -w
+#
+# usage:
+#   ./distort-stl <INPUT >OUTPUT DISTORTION [PARAMS...] ...
+#
+# DISTORTIONs:
+#
+#   project-cylinder RADIUS
+#       projects the X-Z plane onto the cylinder of
+#           radius RADIUS with axis [0, 0, t]
+#       origin becomes [0, -RADIUS, 0]
+#       other planes of the input are projected onto smaller
+#           or larger cylinders accordingly
+#       probably a bad idea if
+#           object has any Y > RADIUS
+#           object has any |X| > tau / RADIUS
+#       technically, treats input as if it were
+#       polar-rectangular coords:
+#          Z' = Z; R' = Y + RADIUS; theta' = X / RADIUS
+#       and then converts back into cartesian
+#       honours fa but not fs or fn
+#
+#   set-fa $FA
+
+use strict;
+use autodie;
+
+use List::Util;
+use POSIX;
+use File::Temp ();
+use Data::Dumper;
+
+sub TAU () { M_PI * 2; }
+
+our $debug = $ENV{DISTORT_DEBUG} // 0 ;
+
+my $ps = $ENV{DISTORT_PS};
+if ($ps) {
+    open PS, "> $ps" or die $!;
+    print PS "%!\n";
+}
+
+our $fa = 10;
+
+our $triangles;
+our $output;
+
+sub shift_arg () {
+    die unless @ARGV;
+    scalar shift @ARGV;
+}
+
+#no warnings qw(recursion);
+
+sub sprintf_triangle ($) {
+    my ($t) = @_;
+
+    return '' unless $debug;
+
+    if ($ps && $t->[3] =~ m/$ENV{DISTORT_PS_RE}/) {
+       printf PS <<'END',
+ %20.16g %20.16g %20.16g moveto
+ %20.16g %20.16g %20.16g lineto
+ %20.16g %20.16g %20.16g lineto
+ closepath stroke
+END
+               $t->[0][0], $t->[0][1], $t->[0][2],
+               $t->[1][0], $t->[1][1], $t->[1][2],
+               $t->[2][0], $t->[2][1], $t->[2][2],
+               or die $!;
+       flush PS or die $!;
+    }
+
+    sprintf
+       "%11.6f,%11.6f,%11.6f / ".
+       "%11.6f,%11.6f,%11.6f / ".
+       "%11.6f,%11.6f,%11.6f  %-40s ",
+               $t->[0][0], $t->[0][1], $t->[0][2],
+               $t->[1][0], $t->[1][1], $t->[1][2],
+               $t->[2][0], $t->[2][1], $t->[2][2],
+               $t->[3];
+}
+
+sub maybe_subdivide_triangle ($$$$) {
+    my ($t, $ok, $changed, $edge_need_subdivide_fn) = @_;
+
+    print STDERR sprintf_triangle $t if $debug;
+
+    my (@longest) = qw(-1);
+
+    foreach my $ix (0..2) {
+       my $jx = ($ix+1) % 3;
+       next unless $edge_need_subdivide_fn->($t->[$ix], $t->[$jx]);
+       my $l2 = 0;
+       foreach my $ci (0..2) {
+           my $d = $t->[$ix][$ci] - $t->[$jx][$ci];
+           $l2 += $d*$d;
+       }
+       next unless $l2 > $longest[0];
+       @longest = ($l2, $ix, $jx);
+    }
+    if ($longest[0] < 0) {
+       push @$ok, $t;
+       printf STDERR "OK nok=%d nchanged=%d\n",
+           (scalar @$ok), (scalar @$changed)
+           if $debug;
+       print STDERR Dumper(\@$ok) if $debug>=2;
+       return;
+    }
+    my ($dummy,$ix,$jx) = @longest;
+    my $kx = ($ix+2) % 3;
+
+    printf STDERR
+       " S i=%d j=%d k=%d ",
+       $ix, $jx, $kx
+       if $debug;
+    my @midp;
+    foreach my $ci (0..2) {
+       push @midp, 0.5 * ($t->[$ix][$ci] + $t->[$jx][$ci]);
+    }
+
+    printf STDERR
+       " midp %11.6f,%11.6f,%11.6f\n",
+       @midp
+       if $debug;
+
+    # triangle i-j-k, splitting edge i-m
+    # gives    i-m-k, k-m-j
+    my $gensplit = sub {
+       my ($ixjx, $xwhat) = @_;
+       my $n = [ @$t ];
+       $n->[$ixjx] = \@midp;
+       $n->[3] = "$t->[3]$xwhat";
+       printf STDERR "%s\n", sprintf_triangle $n if $debug;
+       unshift @$changed, $n;
+    };
+    $gensplit->($ix, "a$ix$jx");
+    $gensplit->($jx, "b$ix$jx");
+    return;
+}
+
+sub maybe_subdivide ($) {
+    my ($edge_need_subdivide_fn) = @_;
+    
+    my @small_enough = ();
+    while (my $t = shift @$triangles) {
+       maybe_subdivide_triangle $t, \@small_enough, $triangles,
+           $edge_need_subdivide_fn;
+    }
+
+    $triangles = \@small_enough;
+}
+
+sub append_triangle ($) {
+    my ($t) = @_;
+    push @$output, $t;
+}
+
+#---------- set-fa ----------
+
+sub op__set_fa () {
+    $fa = shift_arg;
+}
+
+#---------- project-cylinder ----------
+
+our $project_cylinder_radius;
+our $project_cylinder_max_d_theta;
+
+sub project_cylinder_edge_need_subdivide ($$) {
+    my @thetas = map { $_->[0] / $project_cylinder_radius } @_;
+    return abs($thetas[0] - $thetas[1]) > $project_cylinder_max_d_theta;
+}
+
+sub project_cylinder_tri {
+    my ($t) = @_;
+
+    #print STDERR 'PROJECT', Dumper($t);
+
+    my $radius = $project_cylinder_radius;
+
+    my @ot;
+    foreach my $p (@$t[0..2]) {
+       my ($x,$y,$z) = @$p;
+       my $r = $radius - $y;
+       my $theta = $x / $radius;
+       push @ot, [ $r * sin($theta),
+                   -$r * cos($theta),
+                   $z ];
+    }
+    push @ot, $t->[3].'P';
+    append_triangle \@ot;
+}
+
+sub op__project_cylinder () {
+    $project_cylinder_radius = shift_arg;
+    $project_cylinder_max_d_theta = $fa * TAU/360;
+
+    maybe_subdivide \&project_cylinder_edge_need_subdivide;
+    
+    $output = [];
+    foreach my $t (@$triangles) {
+       project_cylinder_tri $t;
+    }
+    $triangles = $output;
+}
+
+#---------- main program ----------
+
+our $raw;
+
+while (@ARGV && $ARGV[0] =~ m/^-/) {
+    $_ = shift @ARGV;
+    last if m/^--$/;
+    if (s/^--raw$//) {
+       $raw = 1;
+    } else {
+       die "$_ ?";
+    }
+}
+
+my $itmp;
+my $otmp;
+
+my $admesh_stdout = '--write-ascii-stl /dev/fd/3 3>&1 >/dev/null';
+
+if ($raw) {
+    open I, "<& STDIN";
+    $otmp = *STDOUT;
+} else {
+    $itmp = new File::Temp;
+    $otmp = new File::Temp;
+
+    system "cat >$itmp";
+
+    open I, "admesh $admesh_stdout $itmp |";
+}
+
+my $triangle;
+
+while (<I>) {
+    s/^\s*//;
+    if (m/^outer\s+loop/) {
+       die if $triangle;
+       $triangle = [];
+    } elsif (s/^vertex\s+//) {
+       my $lhs = $&;
+       s/\s+$//;
+       my @xyz = split /\s+/, $_;
+       die unless $triangle;
+       push @$triangle, \@xyz;
+    } elsif (m/^endloop/) {
+       die unless @$triangle == 3;
+       push @$triangle, $.;
+       push @$triangles, $triangle;
+       undef $triangle;
+    } elsif (m/^(?:solid|facet\s+normal|endfacet|endsolid)\s/) {
+    } else {
+       die "$_ ?";
+    }
+}
+
+close I;
+<I> if 0; # suppresses Name "main::I" used only once
+
+while (@ARGV) {
+    my $op = shift_arg;
+    $op =~ y/-/_/;
+    &{ ${*::}{"op__$op"} };
+}
+
+select $otmp;
+
+print "solid distort-stl\n";
+
+foreach my $t (@$triangles) {
+    print "  facet normal 0 0 0\n";
+    print "    outer loop\n";
+    die unless @$t==4;
+    foreach my $p (@$t[0..2]) {
+       die unless @$p==3;
+       print "      vertex";
+       printf " %.18g", $_ foreach @$p;
+       print "\n";
+    }
+    print "    endloop\n";
+    print "  endfacet\n";
+}
+
+print "endsolid distort-stl\n";
+
+flush $otmp;
+
+if (!$raw) {
+    system "admesh --normal-values $admesh_stdout $otmp";
+}
diff --git a/diziet-utils/COPYING b/diziet-utils/COPYING
new file mode 100644 (file)
index 0000000..94a9ed0
--- /dev/null
@@ -0,0 +1,674 @@
+                    GNU GENERAL PUBLIC LICENSE
+                       Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                            Preamble
+
+  The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+  The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works.  By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users.  We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors.  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+  To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights.  Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received.  You must make sure that they, too, receive
+or can get the source code.  And you must show them these terms so they
+know their rights.
+
+  Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+  For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software.  For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+  Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so.  This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software.  The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable.  Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products.  If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+  Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary.  To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                       TERMS AND CONDITIONS
+
+  0. Definitions.
+
+  "This License" refers to version 3 of the GNU General Public License.
+
+  "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+  "The Program" refers to any copyrightable work licensed under this
+License.  Each licensee is addressed as "you".  "Licensees" and
+"recipients" may be individuals or organizations.
+
+  To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy.  The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+  A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+  To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy.  Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+  To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies.  Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+  An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License.  If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+  1. Source Code.
+
+  The "source code" for a work means the preferred form of the work
+for making modifications to it.  "Object code" means any non-source
+form of a work.
+
+  A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+  The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form.  A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+  The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities.  However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work.  For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+  The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+  The Corresponding Source for a work in source code form is that
+same work.
+
+  2. Basic Permissions.
+
+  All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met.  This License explicitly affirms your unlimited
+permission to run the unmodified Program.  The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work.  This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+  You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force.  You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright.  Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+  Conveying under any other circumstances is permitted solely under
+the conditions stated below.  Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+  3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+  No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+  When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+  4. Conveying Verbatim Copies.
+
+  You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+  You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+  5. Conveying Modified Source Versions.
+
+  You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+    a) The work must carry prominent notices stating that you modified
+    it, and giving a relevant date.
+
+    b) The work must carry prominent notices stating that it is
+    released under this License and any conditions added under section
+    7.  This requirement modifies the requirement in section 4 to
+    "keep intact all notices".
+
+    c) You must license the entire work, as a whole, under this
+    License to anyone who comes into possession of a copy.  This
+    License will therefore apply, along with any applicable section 7
+    additional terms, to the whole of the work, and all its parts,
+    regardless of how they are packaged.  This License gives no
+    permission to license the work in any other way, but it does not
+    invalidate such permission if you have separately received it.
+
+    d) If the work has interactive user interfaces, each must display
+    Appropriate Legal Notices; however, if the Program has interactive
+    interfaces that do not display Appropriate Legal Notices, your
+    work need not make them do so.
+
+  A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit.  Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+  6. Conveying Non-Source Forms.
+
+  You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+    a) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by the
+    Corresponding Source fixed on a durable physical medium
+    customarily used for software interchange.
+
+    b) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by a
+    written offer, valid for at least three years and valid for as
+    long as you offer spare parts or customer support for that product
+    model, to give anyone who possesses the object code either (1) a
+    copy of the Corresponding Source for all the software in the
+    product that is covered by this License, on a durable physical
+    medium customarily used for software interchange, for a price no
+    more than your reasonable cost of physically performing this
+    conveying of source, or (2) access to copy the
+    Corresponding Source from a network server at no charge.
+
+    c) Convey individual copies of the object code with a copy of the
+    written offer to provide the Corresponding Source.  This
+    alternative is allowed only occasionally and noncommercially, and
+    only if you received the object code with such an offer, in accord
+    with subsection 6b.
+
+    d) Convey the object code by offering access from a designated
+    place (gratis or for a charge), and offer equivalent access to the
+    Corresponding Source in the same way through the same place at no
+    further charge.  You need not require recipients to copy the
+    Corresponding Source along with the object code.  If the place to
+    copy the object code is a network server, the Corresponding Source
+    may be on a different server (operated by you or a third party)
+    that supports equivalent copying facilities, provided you maintain
+    clear directions next to the object code saying where to find the
+    Corresponding Source.  Regardless of what server hosts the
+    Corresponding Source, you remain obligated to ensure that it is
+    available for as long as needed to satisfy these requirements.
+
+    e) Convey the object code using peer-to-peer transmission, provided
+    you inform other peers where the object code and Corresponding
+    Source of the work are being offered to the general public at no
+    charge under subsection 6d.
+
+  A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+  A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling.  In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage.  For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product.  A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+  "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source.  The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+  If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information.  But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+  The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed.  Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+  Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+  7. Additional Terms.
+
+  "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law.  If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+  When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it.  (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.)  You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+  Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+    a) Disclaiming warranty or limiting liability differently from the
+    terms of sections 15 and 16 of this License; or
+
+    b) Requiring preservation of specified reasonable legal notices or
+    author attributions in that material or in the Appropriate Legal
+    Notices displayed by works containing it; or
+
+    c) Prohibiting misrepresentation of the origin of that material, or
+    requiring that modified versions of such material be marked in
+    reasonable ways as different from the original version; or
+
+    d) Limiting the use for publicity purposes of names of licensors or
+    authors of the material; or
+
+    e) Declining to grant rights under trademark law for use of some
+    trade names, trademarks, or service marks; or
+
+    f) Requiring indemnification of licensors and authors of that
+    material by anyone who conveys the material (or modified versions of
+    it) with contractual assumptions of liability to the recipient, for
+    any liability that these contractual assumptions directly impose on
+    those licensors and authors.
+
+  All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10.  If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term.  If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+  If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+  Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+  8. Termination.
+
+  You may not propagate or modify a covered work except as expressly
+provided under this License.  Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+  However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+  Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+  Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License.  If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+  9. Acceptance Not Required for Having Copies.
+
+  You are not required to accept this License in order to receive or
+run a copy of the Program.  Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance.  However,
+nothing other than this License grants you permission to propagate or
+modify any covered work.  These actions infringe copyright if you do
+not accept this License.  Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+  10. Automatic Licensing of Downstream Recipients.
+
+  Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License.  You are not responsible
+for enforcing compliance by third parties with this License.
+
+  An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations.  If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+  You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License.  For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+  11. Patents.
+
+  A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based.  The
+work thus licensed is called the contributor's "contributor version".
+
+  A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version.  For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+  Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+  In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement).  To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+  If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients.  "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+  If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+  A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License.  You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+  Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+  12. No Surrender of Others' Freedom.
+
+  If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all.  For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+  13. Use with the GNU Affero General Public License.
+
+  Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work.  The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+  14. Revised Versions of this License.
+
+  The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+  Each version is given a distinguishing version number.  If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation.  If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+  If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+  Later license versions may give you additional or different
+permissions.  However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+  15. Disclaimer of Warranty.
+
+  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+  16. Limitation of Liability.
+
+  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+  17. Interpretation of Sections 15 and 16.
+
+  If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+                     END OF TERMS AND CONDITIONS
+
+            How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+  If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+    <program>  Copyright (C) <year>  <name of author>
+    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+  You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+<http://www.gnu.org/licenses/>.
+
+  The GNU General Public License does not permit incorporating your program
+into proprietary programs.  If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.  But first, please read
+<http://www.gnu.org/philosophy/why-not-lgpl.html>.
diff --git a/diziet-utils/README.md b/diziet-utils/README.md
new file mode 100644 (file)
index 0000000..ec4c50d
--- /dev/null
@@ -0,0 +1,24 @@
+# 3D printing utility files and build system
+
+This is a set of files I use for making 3D objects.
+
+It mostly consists of my own work, but occasionally I include other
+people's work if that other work has a suitable licence.
+
+# How to use
+
+To add to your project:
+
+```
+git subtree add -P diziet-utils https://salsa.debian.org/iwj/3d-utils.git main
+```
+
+To update to a new version:
+
+```
+git subtree pull -P diziet-utils https://salsa.debian.org/iwj/3d-utils.git main
+```
+
+# Licence
+
+Everything is GPLv3+ (or compatible).
similarity index 100%
rename from funcs.scad.cpp
rename to diziet-utils/funcs.scad.cpp
diff --git a/diziet-utils/threads.scad b/diziet-utils/threads.scad
new file mode 100644 (file)
index 0000000..b2eee23
--- /dev/null
@@ -0,0 +1,407 @@
+/*\r
+ * ISO-standard metric threads, following this specification:\r
+ *          http://en.wikipedia.org/wiki/ISO_metric_screw_thread\r
+ *\r
+ * Copyright 2020 Dan Kirshner - dan_kirshner@yahoo.com\r
+ * This program is free software: you can redistribute it and/or modify\r
+ * it under the terms of the GNU General Public License as published by\r
+ * the Free Software Foundation, either version 3 of the License, or\r
+ * (at your option) any later version.\r
+ *\r
+ * This program is distributed in the hope that it will be useful,\r
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of\r
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
+ * GNU General Public License for more details.\r
+ *\r
+ * See <http://www.gnu.org/licenses/>.\r
+ *\r
+ * Version 2.5.  2020-04-11  Leadin option works for internal threads.\r
+ * Version 2.4.  2019-07-14  Add test option - do not render threads.\r
+ * Version 2.3.  2017-08-31  Default for leadin: 0 (best for internal threads).\r
+ * Version 2.2.  2017-01-01  Correction for angle; leadfac option.  (Thanks to\r
+ *                           Andrew Allen <a2intl@gmail.com>.)\r
+ * Version 2.1.  2016-12-04  Chamfer bottom end (low-z); leadin option.\r
+ * Version 2.0.  2016-11-05  Backwards compatibility (earlier OpenSCAD) fixes.\r
+ * Version 1.9.  2016-07-03  Option: tapered.\r
+ * Version 1.8.  2016-01-08  Option: (non-standard) angle.\r
+ * Version 1.7.  2015-11-28  Larger x-increment - for small-diameters.\r
+ * Version 1.6.  2015-09-01  Options: square threads, rectangular threads.\r
+ * Version 1.5.  2015-06-12  Options: thread_size, groove.\r
+ * Version 1.4.  2014-10-17  Use "faces" instead of "triangles" for polyhedron\r
+ * Version 1.3.  2013-12-01  Correct loop over turns -- don't have early cut-off\r
+ * Version 1.2.  2012-09-09  Use discrete polyhedra rather than linear_extrude ()\r
+ * Version 1.1.  2012-09-07  Corrected to right-hand threads!\r
+ */\r
+\r
+// Examples.\r
+//\r
+// Standard M8 x 1.\r
+// metric_thread (diameter=8, pitch=1, length=4);\r
+\r
+// Square thread.\r
+// metric_thread (diameter=8, pitch=1, length=4, square=true);\r
+\r
+// Non-standard: long pitch, same thread size.\r
+//metric_thread (diameter=8, pitch=4, length=4, thread_size=1, groove=true);\r
+\r
+// Non-standard: 20 mm diameter, long pitch, square "trough" width 3 mm,\r
+// depth 1 mm.\r
+//metric_thread (diameter=20, pitch=8, length=16, square=true, thread_size=6,\r
+//               groove=true, rectangle=0.333);\r
+\r
+// English: 1/4 x 20.\r
+//english_thread (diameter=1/4, threads_per_inch=20, length=1);\r
+\r
+// Tapered.  Example -- pipe size 3/4" -- per:\r
+// http://www.engineeringtoolbox.com/npt-national-pipe-taper-threads-d_750.html\r
+// english_thread (diameter=1.05, threads_per_inch=14, length=3/4, taper=1/16);\r
+\r
+// Thread for mounting on Rohloff hub.\r
+//difference () {\r
+//   cylinder (r=20, h=10, $fn=100);\r
+//\r
+//   metric_thread (diameter=34, pitch=1, length=10, internal=true, n_starts=6);\r
+//}\r
+\r
+\r
+// ----------------------------------------------------------------------------\r
+function segments (diameter) = min (50, max (ceil (diameter*6), 25));\r
+\r
+\r
+// ----------------------------------------------------------------------------\r
+// diameter -    outside diameter of threads in mm. Default: 8.\r
+// pitch    -    thread axial "travel" per turn in mm.  Default: 1.\r
+// length   -    overall axial length of thread in mm.  Default: 1.\r
+// internal -    true = clearances for internal thread (e.g., a nut).\r
+//               false = clearances for external thread (e.g., a bolt).\r
+//               (Internal threads should be "cut out" from a solid using\r
+//               difference ()).  Default: false.\r
+// n_starts -    Number of thread starts (e.g., DNA, a "double helix," has\r
+//               n_starts=2).  See wikipedia Screw_thread.  Default: 1.\r
+// thread_size - (non-standard) axial width of a single thread "V" - independent\r
+//               of pitch.  Default: same as pitch.\r
+// groove      - (non-standard) true = subtract inverted "V" from cylinder\r
+//                (rather thanadd protruding "V" to cylinder).  Default: false.\r
+// square      - true = square threads (per\r
+//               https://en.wikipedia.org/wiki/Square_thread_form).  Default:\r
+//               false.\r
+// rectangle   - (non-standard) "Rectangular" thread - ratio depth/(axial) width\r
+//               Default: 0 (standard "v" thread).\r
+// angle       - (non-standard) angle (deg) of thread side from perpendicular to\r
+//               axis (default = standard = 30 degrees).\r
+// taper       - diameter change per length (National Pipe Thread/ANSI B1.20.1\r
+//               is 1" diameter per 16" length). Taper decreases from 'diameter'\r
+//               as z increases.  Default: 0 (no taper).\r
+// leadin      - 0 (default): no chamfer; 1: chamfer (45 degree) at max-z end;\r
+//               2: chamfer at both ends, 3: chamfer at z=0 end.\r
+// leadfac     - scale of leadin chamfer length (default: 1.0 = 1/2 thread).\r
+// test        - true = do not render threads (just draw "blank" cylinder).\r
+//               Default: false (draw threads).\r
+module metric_thread (diameter=8, pitch=1, length=1, internal=false, n_starts=1,\r
+                      thread_size=-1, groove=false, square=false, rectangle=0,\r
+                      angle=30, taper=0, leadin=0, leadfac=1.0, test=false)\r
+{\r
+   // thread_size: size of thread "V" different than travel per turn (pitch).\r
+   // Default: same as pitch.\r
+   local_thread_size = thread_size == -1 ? pitch : thread_size;\r
+   local_rectangle = rectangle ? rectangle : 1;\r
+\r
+   n_segments = segments (diameter);\r
+   h = (test && ! internal) ? 0 : (square || rectangle) ? local_thread_size*local_rectangle/2 : local_thread_size / (2 * tan(angle));\r
+\r
+   h_fac1 = (square || rectangle) ? 0.90 : 0.625;\r
+\r
+   // External thread includes additional relief.\r
+   h_fac2 = (square || rectangle) ? 0.95 : 5.3/8;\r
+\r
+   tapered_diameter = diameter - length*taper;\r
+\r
+   difference () {\r
+      union () {\r
+         if (! groove) {\r
+            if (! test) {\r
+               metric_thread_turns (diameter, pitch, length, internal, n_starts,\r
+                                    local_thread_size, groove, square, rectangle, angle,\r
+                                    taper);\r
+            }\r
+         }\r
+\r
+         difference () {\r
+\r
+            // Solid center, including Dmin truncation.\r
+            if (groove) {\r
+               cylinder (r1=diameter/2, r2=tapered_diameter/2,\r
+                         h=length, $fn=n_segments);\r
+            } else if (internal) {\r
+               cylinder (r1=diameter/2 - h*h_fac1, r2=tapered_diameter/2 - h*h_fac1,\r
+                         h=length, $fn=n_segments);\r
+            } else {\r
+\r
+               // External thread.\r
+               cylinder (r1=diameter/2 - h*h_fac2, r2=tapered_diameter/2 - h*h_fac2,\r
+                         h=length, $fn=n_segments);\r
+            }\r
+\r
+            if (groove) {\r
+               if (! test) {\r
+                  metric_thread_turns (diameter, pitch, length, internal, n_starts,\r
+                                       local_thread_size, groove, square, rectangle,\r
+                                       angle, taper);\r
+               }\r
+            }\r
+         }\r
+\r
+         // Internal thread lead-in: take away from external solid.\r
+         if (internal) {\r
+\r
+            // "Negative chamfer" z=0 end if leadin is 2 or 3.\r
+            if (leadin == 2 || leadin == 3) {\r
+               cylinder (r1=diameter/2, r2=diameter/2 - h*h_fac1*leadfac, h=h*h_fac1*leadfac,\r
+                         $fn=n_segments);\r
+            }\r
+\r
+            // "Negative chamfer" z-max end if leadin is 1 or 2.\r
+            if (leadin == 1 || leadin == 2) {\r
+               translate ([0, 0, length + 0.05 - h*h_fac1*leadfac]) {\r
+                  cylinder (r1=tapered_diameter/2 - h*h_fac1*leadfac, h=h*h_fac1*leadfac,\r
+                            r2=tapered_diameter/2,\r
+                            $fn=n_segments);\r
+               }\r
+            }\r
+         }\r
+      }\r
+\r
+      if (! internal) {\r
+\r
+         // Chamfer z=0 end if leadin is 2 or 3.\r
+         if (leadin == 2 || leadin == 3) {\r
+            difference () {\r
+               cylinder (r=diameter/2 + 1, h=h*h_fac1*leadfac, $fn=n_segments);\r
+\r
+               cylinder (r2=diameter/2, r1=diameter/2 - h*h_fac1*leadfac, h=h*h_fac1*leadfac,\r
+                         $fn=n_segments);\r
+            }\r
+         }\r
+\r
+         // Chamfer z-max end if leadin is 1 or 2.\r
+         if (leadin == 1 || leadin == 2) {\r
+            translate ([0, 0, length + 0.05 - h*h_fac1*leadfac]) {\r
+               difference () {\r
+                  cylinder (r=diameter/2 + 1, h=h*h_fac1*leadfac, $fn=n_segments);\r
+\r
+                  cylinder (r1=tapered_diameter/2, r2=tapered_diameter/2 - h*h_fac1*leadfac, h=h*h_fac1*leadfac,\r
+                            $fn=n_segments);\r
+               }\r
+            }\r
+         }\r
+      }\r
+   }\r
+}\r
+\r
+\r
+// ----------------------------------------------------------------------------\r
+// Input units in inches.\r
+// Note: units of measure in drawing are mm!\r
+module english_thread (diameter=0.25, threads_per_inch=20, length=1,\r
+                      internal=false, n_starts=1, thread_size=-1, groove=false,\r
+                      square=false, rectangle=0, angle=30, taper=0, leadin=0,\r
+                      leadfac=1.0, test=false)\r
+{\r
+   // Convert to mm.\r
+   mm_diameter = diameter*25.4;\r
+   mm_pitch = (1.0/threads_per_inch)*25.4;\r
+   mm_length = length*25.4;\r
+\r
+   echo (str ("mm_diameter: ", mm_diameter));\r
+   echo (str ("mm_pitch: ", mm_pitch));\r
+   echo (str ("mm_length: ", mm_length));\r
+   metric_thread (mm_diameter, mm_pitch, mm_length, internal, n_starts,\r
+                  thread_size, groove, square, rectangle, angle, taper, leadin,\r
+                  leadfac, test);\r
+}\r
+\r
+// ----------------------------------------------------------------------------\r
+module metric_thread_turns (diameter, pitch, length, internal, n_starts,\r
+                            thread_size, groove, square, rectangle, angle,\r
+                            taper)\r
+{\r
+   // Number of turns needed.\r
+   n_turns = floor (length/pitch);\r
+\r
+   intersection () {\r
+\r
+      // Start one below z = 0.  Gives an extra turn at each end.\r
+      for (i=[-1*n_starts : n_turns+1]) {\r
+         translate ([0, 0, i*pitch]) {\r
+            metric_thread_turn (diameter, pitch, internal, n_starts,\r
+                                thread_size, groove, square, rectangle, angle,\r
+                                taper, i*pitch);\r
+         }\r
+      }\r
+\r
+      // Cut to length.\r
+      translate ([0, 0, length/2]) {\r
+         cube ([diameter*3, diameter*3, length], center=true);\r
+      }\r
+   }\r
+}\r
+\r
+\r
+// ----------------------------------------------------------------------------\r
+module metric_thread_turn (diameter, pitch, internal, n_starts, thread_size,\r
+                           groove, square, rectangle, angle, taper, z)\r
+{\r
+   n_segments = segments (diameter);\r
+   fraction_circle = 1.0/n_segments;\r
+   for (i=[0 : n_segments-1]) {\r
+      rotate ([0, 0, i*360*fraction_circle]) {\r
+         translate ([0, 0, i*n_starts*pitch*fraction_circle]) {\r
+            //current_diameter = diameter - taper*(z + i*n_starts*pitch*fraction_circle);\r
+            thread_polyhedron ((diameter - taper*(z + i*n_starts*pitch*fraction_circle))/2,\r
+                               pitch, internal, n_starts, thread_size, groove,\r
+                               square, rectangle, angle);\r
+         }\r
+      }\r
+   }\r
+}\r
+\r
+\r
+// ----------------------------------------------------------------------------\r
+module thread_polyhedron (radius, pitch, internal, n_starts, thread_size,\r
+                          groove, square, rectangle, angle)\r
+{\r
+   n_segments = segments (radius*2);\r
+   fraction_circle = 1.0/n_segments;\r
+\r
+   local_rectangle = rectangle ? rectangle : 1;\r
+\r
+   h = (square || rectangle) ? thread_size*local_rectangle/2 : thread_size / (2 * tan(angle));\r
+   outer_r = radius + (internal ? h/20 : 0); // Adds internal relief.\r
+   //echo (str ("outer_r: ", outer_r));\r
+\r
+   // A little extra on square thread -- make sure overlaps cylinder.\r
+   h_fac1 = (square || rectangle) ? 1.1 : 0.875;\r
+   inner_r = radius - h*h_fac1; // Does NOT do Dmin_truncation - do later with\r
+                                // cylinder.\r
+\r
+   translate_y = groove ? outer_r + inner_r : 0;\r
+   reflect_x   = groove ? 1 : 0;\r
+\r
+   // Make these just slightly bigger (keep in proportion) so polyhedra will\r
+   // overlap.\r
+   x_incr_outer = (! groove ? outer_r : inner_r) * fraction_circle * 2 * PI * 1.02;\r
+   x_incr_inner = (! groove ? inner_r : outer_r) * fraction_circle * 2 * PI * 1.02;\r
+   z_incr = n_starts * pitch * fraction_circle * 1.005;\r
+\r
+   /*\r
+    (angles x0 and x3 inner are actually 60 deg)\r
+\r
+                          /\  (x2_inner, z2_inner) [2]\r
+                         /  \\r
+   (x3_inner, z3_inner) /    \\r
+                  [3]   \     \\r
+                        |\     \ (x2_outer, z2_outer) [6]\r
+                        | \    /\r
+                        |  \  /|\r
+             z          |[7]\/ / (x1_outer, z1_outer) [5]\r
+             |          |   | /\r
+             |   x      |   |/\r
+             |  /       |   / (x0_outer, z0_outer) [4]\r
+             | /        |  /     (behind: (x1_inner, z1_inner) [1]\r
+             |/         | /\r
+    y________|          |/\r
+   (r)                  / (x0_inner, z0_inner) [0]\r
+\r
+   */\r
+\r
+   x1_outer = outer_r * fraction_circle * 2 * PI;\r
+\r
+   z0_outer = (outer_r - inner_r) * tan(angle);\r
+   //echo (str ("z0_outer: ", z0_outer));\r
+\r
+   //polygon ([[inner_r, 0], [outer_r, z0_outer],\r
+   //        [outer_r, 0.5*pitch], [inner_r, 0.5*pitch]]);\r
+   z1_outer = z0_outer + z_incr;\r
+\r
+   // Give internal square threads some clearance in the z direction, too.\r
+   bottom = internal ? 0.235 : 0.25;\r
+   top    = internal ? 0.765 : 0.75;\r
+\r
+   translate ([0, translate_y, 0]) {\r
+      mirror ([reflect_x, 0, 0]) {\r
+\r
+         if (square || rectangle) {\r
+\r
+            // Rule for face ordering: look at polyhedron from outside: points must\r
+            // be in clockwise order.\r
+            polyhedron (\r
+               points = [\r
+                         [-x_incr_inner/2, -inner_r, bottom*thread_size],         // [0]\r
+                         [x_incr_inner/2, -inner_r, bottom*thread_size + z_incr], // [1]\r
+                         [x_incr_inner/2, -inner_r, top*thread_size + z_incr],    // [2]\r
+                         [-x_incr_inner/2, -inner_r, top*thread_size],            // [3]\r
+\r
+                         [-x_incr_outer/2, -outer_r, bottom*thread_size],         // [4]\r
+                         [x_incr_outer/2, -outer_r, bottom*thread_size + z_incr], // [5]\r
+                         [x_incr_outer/2, -outer_r, top*thread_size + z_incr],    // [6]\r
+                         [-x_incr_outer/2, -outer_r, top*thread_size]             // [7]\r
+                        ],\r
+\r
+               faces = [\r
+                         [0, 3, 7, 4],  // This-side trapezoid\r
+\r
+                         [1, 5, 6, 2],  // Back-side trapezoid\r
+\r
+                         [0, 1, 2, 3],  // Inner rectangle\r
+\r
+                         [4, 7, 6, 5],  // Outer rectangle\r
+\r
+                         // These are not planar, so do with separate triangles.\r
+                         [7, 2, 6],     // Upper rectangle, bottom\r
+                         [7, 3, 2],     // Upper rectangle, top\r
+\r
+                         [0, 5, 1],     // Lower rectangle, bottom\r
+                         [0, 4, 5]      // Lower rectangle, top\r
+                        ]\r
+            );\r
+         } else {\r
+\r
+            // Rule for face ordering: look at polyhedron from outside: points must\r
+            // be in clockwise order.\r
+            polyhedron (\r
+               points = [\r
+                         [-x_incr_inner/2, -inner_r, 0],                        // [0]\r
+                         [x_incr_inner/2, -inner_r, z_incr],                    // [1]\r
+                         [x_incr_inner/2, -inner_r, thread_size + z_incr],      // [2]\r
+                         [-x_incr_inner/2, -inner_r, thread_size],              // [3]\r
+\r
+                         [-x_incr_outer/2, -outer_r, z0_outer],                 // [4]\r
+                         [x_incr_outer/2, -outer_r, z0_outer + z_incr],         // [5]\r
+                         [x_incr_outer/2, -outer_r, thread_size - z0_outer + z_incr], // [6]\r
+                         [-x_incr_outer/2, -outer_r, thread_size - z0_outer]    // [7]\r
+                        ],\r
+\r
+               faces = [\r
+                         [0, 3, 7, 4],  // This-side trapezoid\r
+\r
+                         [1, 5, 6, 2],  // Back-side trapezoid\r
+\r
+                         [0, 1, 2, 3],  // Inner rectangle\r
+\r
+                         [4, 7, 6, 5],  // Outer rectangle\r
+\r
+                         // These are not planar, so do with separate triangles.\r
+                         [7, 2, 6],     // Upper rectangle, bottom\r
+                         [7, 3, 2],     // Upper rectangle, top\r
+\r
+                         [0, 5, 1],     // Lower rectangle, bottom\r
+                         [0, 4, 5]      // Lower rectangle, top\r
+                        ]\r
+            );\r
+         }\r
+      }\r
+   }\r
+}\r
+\r
+\r
+\r
similarity index 100%
rename from toplevel-find
rename to diziet-utils/toplevel-find
similarity index 100%
rename from toplevel-make
rename to diziet-utils/toplevel-make
diff --git a/diziet-utils/utils.scad b/diziet-utils/utils.scad
new file mode 100644 (file)
index 0000000..e440b5f
--- /dev/null
@@ -0,0 +1,51 @@
+// -*- C -*-
+
+// suitable for masking things within radius sqrt(2) only
+module FArcSegment_mask(beta) {
+  for (i=[0 : 0.75 : 3]) {
+    rotate(i*beta/4)
+      polygon([[0, 0],
+              [1, 0],
+              [cos(beta/4), sin(beta/4)]]);
+  }
+}
+
+module FArcSegment(xc,yc,inrad,outrad,alpha,delta) {
+  translate([xc,yc]) {
+    intersection() {
+      difference() {
+       circle(r=outrad, $fn=70);
+       circle(r=inrad, $fn=70);
+      }
+      rotate(alpha) scale(outrad*2) {
+       FArcSegment_mask(delta);
+      }
+    }
+  }
+}
+
+module rectfromto(a,b) {
+  ab = b - a;
+  translate([min(a[0], b[0]), min(a[1], b[1])])
+    square([abs(ab[0]), abs(ab[1])]);
+}
+module circleat(c,r) { translate(c) circle(r); }
+module linextr(z0,z1, convexity=20) {
+  translate([0,0,z0])
+    linear_extrude(height=z1-z0, convexity=convexity)
+    children();
+}
+
+module linextr_x_yz(x0,x1, convexity=20) { // XY turn into YZ
+  rotate([90,0,0])
+    rotate([0,90,0])
+    linextr(x0,x1, convexity=convexity)
+    children();
+}
+
+module linextr_y_xz(y0,y1, convexity=20) { // XY turn into YZ
+  rotate([0,0,180])
+    rotate([90,0,0])
+    linextr(y0,y1, convexity=convexity)
+    children();
+}
diff --git a/doveclip.scad b/doveclip.scad
new file mode 100644 (file)
index 0000000..0536c1c
--- /dev/null
@@ -0,0 +1,161 @@
+// -*- C -*-
+//
+// doveclip.scad
+//
+// 3D design for a fastener suitable for Reprapss
+// Copyright 2012,2016 Ian Jackson
+//
+// This work is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This work is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this work.  If not, see <http://www.gnu.org/licenses/>.
+
+
+toothheight = 1.2;
+webthick = 1.8;
+height = 7;
+pinlengthfact = 1.2;
+nomrad = height/2 - toothheight;
+minrad = nomrad - 0.75;
+maxrad = nomrad + 0.25;
+jawthick = 1.5;
+
+webgap = 0.4;
+basepinclear = 1.0;
+
+toothgap = webthick + webgap*2;
+basethick = toothheight;
+
+module DoveClipPin(h=height) {
+  pinh = h * pinlengthfact;
+  pinheight = nomrad*2 + jawthick*2;
+  translate([0,0, pinheight/2]) intersection(){
+    union(){
+      for (m=[0,1]) {
+       mirror([0,0,m]) translate([0,0,pinheight/2]) rotate([90,0,0])
+         cylinder($fn=20, r1=minrad, r2=maxrad, h=pinh);
+      }
+      translate([-webthick/2, -pinh, -pinheight/2-1])
+       cube([webthick, pinh, pinheight+2]);
+    }
+    translate([-maxrad-1, -pinh-1, -pinheight/2])
+      cube([maxrad*2+2, pinh+2, pinheight]);
+  }
+}
+
+function DoveClip_depth() =
+  basethick + nomrad*2 + toothheight;
+
+module DoveClipEnd(baseextend=1, h=7) {
+  cubex = nomrad*2 + jawthick*2;
+  cube0y = -basethick-nomrad*2-toothheight;
+  centrey = -basethick-nomrad;
+  difference(){
+    translate([-cubex/2, cube0y, 0])
+      cube([cubex, -cube0y+baseextend, h]);
+    translate([0, centrey, -1])
+      cylinder($fn=20, r=nomrad, h=h+2);
+    translate([-toothgap/2, cube0y-1, -1])
+      cube([toothgap, toothheight+nomrad+1, h+2]);
+  }
+}
+
+module DoveClipPair(baseextend=1, h=7) {
+  delta =  nomrad*2 + jawthick*2 + toothgap;
+  for (x=[-delta/2,delta/2])
+    translate([x,0,0])
+      DoveClipEnd(baseextend=baseextend, h=h);
+}
+
+module DoveClipPairBase(baseextend=0.1, h=7, count=2) {
+  delta = nomrad*2 + jawthick;
+  intrude = nomrad + basethick - basepinclear;
+  for (i=[0:count-1]) {
+    translate([(i - (count-1)/2) * delta, 0, 0])
+      DoveClipEnd(baseextend=baseextend, h=h);
+  }
+  translate([-delta * count/2, -intrude, 0])
+    cube([delta * count, intrude+0.1, h]);
+}
+
+module DoveClipPairSane(baseextend=0, h=7, count=2) {
+  rotate([0,0,90])
+    translate([0, DoveClip_depth(), 0])
+    DoveClipPairBase(baseextend=baseextend, h=h, count=count);
+}
+
+function DoveClipPairSane_width(count=2) =
+  2 * (nomrad + jawthick + ((nomrad*2 + jawthick) * (count-1)/2));
+
+module ExtenderPillar(length, height,
+                     pillarw=3.5, pillarslope=0.75, webthick=1) {
+  pillarr=pillarw/2;
+  d = 0.25;
+
+  intangle = atan(pillarslope);
+  polyjx = sin(intangle)*pillarr;
+  polyjy = cos(intangle)*pillarr;
+  polyex = -tan(intangle+90)*pillarr;
+  webmidy = height/2+d;
+
+  for (xmir=[0,1])
+    translate([0,0,height/2]) mirror([0,0,xmir])
+      translate([0,0,-height/2]) {
+      intersection() {
+       translate([-1, -pillarr-5, 0.01])
+         cube([length+2, height+pillarr*2+10, height]);
+       mirror([1,0,0]) rotate([0,-90,0])
+         linear_extrude(height=length) union(){
+         circle(r=pillarr, $fn=20);
+         polygon([[polyjx,polyjy-0.1], [polyex, 0],
+                  [polyjx,-(polyjy-0.1)]]);
+         polygon([[0,-webthick/2], [0,webthick/2],
+                  [webmidy,webthick/2], [webmidy,-webthick/2]]);
+       }
+      }
+    }
+}
+
+module ExtenderPillars(length, width, height,
+                      pillarw=3.5, pillarslope=0.75, webthick=1,
+                      baseweb=false, basewebthick=1) {
+  pilesw = width - pillarw;
+
+  for (ymir=[0,1]) mirror([0,ymir,0]) translate([0,-pilesw/2,0]) {
+      ExtenderPillar(length, height, pillarw, pillarslope, webthick);
+    }
+
+  if (baseweb) {
+    translate([0, -pilesw/2, 0])
+      cube([length, pilesw, basewebthick]);
+  }
+}
+
+module DoveClipExtender(length, ha=7, hb=7, counta=2, countb=2,
+                       pillarw=3.5, pillarslope=0.75, webthick=1) {
+
+  mirror([1,0,0])
+    DoveClipPairSane(h=ha, count=counta);
+  translate([length,0,0])
+    DoveClipPairSane(h=hb, count=countb);
+  pillarlen = length - DoveClip_depth() * 2 + 2;
+
+  pilesw = min(DoveClipPairSane_width(counta), DoveClipPairSane_width(countb))
+    - 0.5;
+  pilesh = min(ha, hb) - 0.5;
+
+  translate([DoveClip_depth() - 1, 0, 0])
+    ExtenderPillars(pillarlen, pilesw, pilesh,
+                   pillarw=pillarw, pillarslope=pillarslope,
+                   webthick=webthick);
+}
+
+//DoveClipExtender(length=100, ha=16, hb=20, counta=3, countb=4);
diff --git a/dovecliptest.scad b/dovecliptest.scad
new file mode 100644 (file)
index 0000000..e7e075c
--- /dev/null
@@ -0,0 +1,15 @@
+// -*- C -*-
+
+include <doveclip.scad>
+
+for (y=[0,-15]) translate([0,y,0]) {
+  DoveClipPair();
+
+  translate([-8,0,0])
+    cube([16,5,7]);
+  translate([15,0,0])
+    DoveClipPin();
+}
+
+translate([0,20,0])
+  DoveClipPairBase();
diff --git a/dungeonquest-cone.scad b/dungeonquest-cone.scad
new file mode 100644 (file)
index 0000000..b56ff91
--- /dev/null
@@ -0,0 +1,19 @@
+// -*- C -*-
+
+basesz=12;
+height=14.7;
+topsz=0.5;
+dsz=0;
+
+module One(){ ////toplevel
+  cylinder(h=height, r1=basesz/2-dsz, r2=topsz/2-dsz, $fn=50);
+}
+
+module Four(){ ////toplevel
+  for (x=[0,1]) {
+    for (y=[0,1]) {
+      translate([x*(basesz+3), y*(basesz+3), 0])
+       One();
+    }
+  }
+}
diff --git a/dungeonquest-cone.slic3r b/dungeonquest-cone.slic3r
new file mode 100644 (file)
index 0000000..b7d882f
--- /dev/null
@@ -0,0 +1,2 @@
+bed_temperature = 80
+temperature = 205
diff --git a/earring-stand.scad b/earring-stand.scad
new file mode 100644 (file)
index 0000000..85c28a8
--- /dev/null
@@ -0,0 +1,345 @@
+// -*- C -*-
+
+include <commitid.scad>
+
+front_height = 80;
+front_width = 120;
+front_setback = 30;
+front_thick = 2.4;
+
+front_hex_stride = 12.5;
+front_hex_dia = 9.5;
+
+front_hex_y_fudge = -0.65;
+
+front_surround_lr =3;
+
+back_thick = 3;
+back_pillarw = 6;
+
+base_thick = 2.4;
+
+eclip_inner_rad = 2.5;
+eclip_gap_rad = 0.1;
+eclip_prong_th = 2.25;
+eclip_outer_strt = 0.5;
+eclip_inner_xstrt = 0.5;
+
+eclip_ult_angle = 44;
+eclip_base_epsilon = 0.5;
+
+eclip_each_len = 6;
+eclip_each_every = 29;
+
+test_alpha = 10;
+test_main_th = 1.5;
+test_eclips = 5;
+test_base_th = 2.5;
+test_len = eclip_each_len + eclip_each_every*(test_eclips-1);
+
+num_eclips = 5;
+
+// calculated
+
+include <utils.scad>
+
+eclip_inner_strt = eclip_outer_strt + eclip_inner_xstrt;
+
+r0 = eclip_inner_rad;
+r1 = r0 + eclip_gap_rad;
+r2 = r1 + eclip_prong_th;
+r2e = r1 + eclip_base_epsilon;
+
+ppxl = -(r0 / sqrt(2)) + (eclip_inner_strt / sqrt(2));
+
+rgap = eclip_gap_rad;
+
+eclip_base_offset = r1;
+eclip_wall_offset = -ppxl;
+
+eclip_ra_offset = r2 - 0.1;
+
+eclip_recept_height = r2;
+
+eclip_rhs_offset = ppxl + rgap + eclip_prong_th;
+// does not include main_th
+
+$fn=70;
+
+module EclipLPlanCore(alpha){
+  FArcSegment(0,0, r1,r2,
+             180-eclip_ult_angle, eclip_ult_angle-alpha +1);
+
+  difference(){
+    hull(){
+      intersection(){
+       circle(r2);
+       rotate(-alpha) mirror([1,1]) square([r2e, 50]);
+     }
+      rotate(-alpha) mirror([1,1]) square([r2e, r2]);
+    }
+    circle(r1);
+  }
+}
+
+module EclipRPlan(alpha, main_th){
+  intersection(){
+    rotate(alpha)
+      translate([ppxl + main_th + rgap, -r2*2])
+      square([eclip_prong_th, r2*(2 + 1/sqrt(2))]);
+    translate([-r2, -r2e])
+      square([r2*3, eclip_base_epsilon + r2*4]);
+  }
+}
+
+module EclipLPlan(alpha){
+  rotate(alpha) EclipLPlanCore(alpha);
+}
+
+module EclipPPlan(main_th){
+  intersection(){
+    hull(){
+      circle(r0);
+      rotate(90-eclip_ult_angle) square([r0,r0]);
+    }
+    translate([-(r0+.1), -(r0+.1)])
+      square([(r0+.1) + main_th + ppxl, r2*2]);
+  }
+  translate([ppxl, 0]) square([main_th, r2]);
+}
+
+module TestBase(){ ////toplevel
+  translate([0,0, eclip_base_offset]){
+    for (i=[1 : 2: test_eclips-2]) {
+      translate([0, i*eclip_each_every])
+       rotate([90,0,0])
+       linear_extrude(height=eclip_each_len)
+       EclipLPlan(test_alpha);
+    }
+    for (j=[0 : 2: test_eclips-1]) {
+      translate([0, j*eclip_each_every])
+       rotate([90,0,0])
+       linear_extrude(height=eclip_each_len)
+       EclipRPlan(test_alpha, test_main_th);
+    }
+  }
+  translate([-r2, -eclip_each_len, -test_base_th]){
+    difference(){
+      cube([r2*2,
+           test_len,
+           test_base_th]);
+      mirror([0,0,1]) Commitid_BestCount_M([r2*2, test_len]);
+    }
+  }
+}
+
+module TestProtr(){ ////toplevel
+  difference(){
+    translate([0,0, test_main_th - eclip_wall_offset])
+      rotate([0,90,0])
+      linear_extrude(height=test_len)
+      EclipPPlan(test_main_th);
+    mirror([0,0,1]) Commitid_BestCount_M([test_len, r2]);
+  }
+}
+
+module TestRAProtr(){ ////toplevel
+  rotate([-90,0,0]) TestProtr();
+  mirror([1,0,0])
+    translate([-test_len,
+              -r2,
+              -(eclip_ra_offset + test_base_th)])
+    cube([test_len,
+         r2*2,
+         test_base_th]);
+}
+
+module TestPlanDemo(){
+  color("red") EclipLPlan(test_alpha);
+  color("blue") rotate(test_alpha) EclipPPlan(test_main_th);
+  color("green") EclipRPlan(test_alpha, test_main_th);
+}
+
+beta = asin(front_setback / front_height);
+
+uf = [-sin(beta), cos(beta)];
+ur = [ -uf[1], uf[0]];
+
+pp = [0, 0];
+pq = pp + uf*front_height + ur*eclip_ra_offset;
+pr = [ pq[0] - eclip_base_offset - eclip_wall_offset,
+       0 ];
+
+echo("uf ur P Q R", uf, ur, pp, pq, pr);
+
+module Sketch(){
+  polygon([pq, pp, pr]);
+}
+
+thicks = [ base_thick, front_thick, back_thick ];
+
+module Joins(alpha, objnum, objnum_f, objnum_m) {
+  pitch = (front_width - eclip_each_len) / (num_eclips-1);
+  
+  thm = thicks[objnum_m];
+  stride = (front_width - eclip_each_len) / (num_eclips-1);
+
+  if (objnum==objnum_f) {
+    for (i=[ 1 : 2 : num_eclips-1 ]) {
+      translate([0, i*stride + eclip_each_len, 0]) {
+       rotate([90,0,0])
+       linear_extrude(height=eclip_each_len)
+         EclipLPlan(alpha);
+      }
+    }
+    for (i=[ 0 : 2 : num_eclips-1 ]) {
+      translate([0, i*stride + eclip_each_len, 0]) {
+       rotate([90,0,0])
+       linear_extrude(height=eclip_each_len)
+         EclipRPlan(alpha, thm);
+      }
+    }
+  }
+  if (objnum==objnum_m)
+    mirror([0,1,0])
+      rotate([90,0,0])
+      linear_extrude(height=front_width)
+      rotate(alpha)
+      EclipPPlan(thm);
+}
+
+function r3(pc) = [ pc[0], 0, pc[1] ];
+
+module ObjectJoins(objnum){
+  translate(r3(pp))                   Joins(beta, objnum, 0,1);
+  translate(r3(pr)) mirror([1,0,0])   Joins(0,    objnum, 0,2);
+  translate(r3(pq)) rotate([0,90,0]) mirror([1,0,0]) Joins(-beta, objnum, 2,1);
+}
+
+module Base(){
+  xmin = pr[0] - eclip_rhs_offset - thicks[2];
+  xmax = pp[0] + eclip_rhs_offset + thicks[1]
+    + eclip_prong_th * (1/cos(beta) - 1)
+    + eclip_base_offset * tan(beta);
+  intersection(){
+    ObjectJoins(0);
+    translate([xmin,
+              -1,
+              -50])
+      cube([xmax - xmin,
+           front_width + 2,
+           300]);
+  }
+  translate([xmin,
+            0,
+            -eclip_base_offset - thicks[0]]){
+    difference(){
+      cube([xmax - xmin,
+           front_width,
+         thicks[0]]);
+      translate([xmax-xmin, front_width]/2)
+       rotate([0,0,270])
+       Commitid_Full16_M();
+    }
+  }
+}
+
+module FrontPattern(){
+  totalh = front_height - eclip_wall_offset + thicks[1];
+
+  ystride = front_hex_stride;
+  xstride = front_hex_stride * cos(30) * 2;
+
+  difference(){
+    square([front_width, totalh]);
+    translate([ front_surround_lr,
+               eclip_recept_height ])
+      square([ front_width - front_surround_lr*2,
+              totalh - eclip_recept_height*2
+              ]);
+  }
+    
+  difference(){
+    square([front_width, totalh]);
+    for (xi=[ -5 : 5 ]) {
+      translate([front_width/2 +
+                xi * xstride,
+                0]) {
+       for (yi=[ 0 : 10 ]) {
+         //echo(yi);
+         translate([0, yi * ystride +
+                    front_hex_dia*front_hex_y_fudge]) {
+           for (dv=[ [0,0],
+                     [-xstride/2, -ystride/2]
+                     ])
+             translate(dv)
+               circle(r= front_hex_dia/2, $fn=6);
+         }
+       }
+      }
+    }
+  }
+}
+
+module Front(){
+  ObjectJoins(1);
+  rotate([0, 90-beta, 0])
+    translate([0, 0, ppxl])
+    rotate([0,0,90]) {
+    linear_extrude(height=thicks[1])
+      FrontPattern();
+  }
+}
+
+module Back(){
+  ObjectJoins(2);
+
+  zmin = pr[1];
+  zmax = pq[1] + eclip_prong_th;
+  height = zmax - zmin;
+
+  translate([pr[0] + eclip_wall_offset - thicks[2],
+            0, 0])
+    rotate([0,90,0])
+    rotate([0,0,90]) {
+    difference(){
+      cube([front_width,
+           height,
+           thicks[2]]);
+      translate([back_pillarw,
+                eclip_recept_height,
+                -10])
+       cube([front_width - back_pillarw*2,
+             height - eclip_recept_height*2 - eclip_prong_th,
+             20]);
+    }
+  }
+}
+
+module BackPrint(){ ////toplevel
+  rotate([0,-90,0]) Back();
+}
+
+module FrontPrint(){ ////toplevel
+  rotate([0, 90+beta, 0]) Front();
+}
+
+module BasePrint(){ ////toplevel
+  Base();
+}
+
+module Demo(){ ////toplevel
+  color("red") Base();
+  color("blue") Front();
+  color("black") Back();
+}
+
+//PlanDemo();
+//TestBase();
+//TestProtr();
+//TestRAProtr();
+//Sketch();
+//Demo();
+//BackPrint();
+//FrontPrint();
+//BasePrint();
diff --git a/electron-token.scad.pl b/electron-token.scad.pl
new file mode 100755 (executable)
index 0000000..dc24c5a
--- /dev/null
@@ -0,0 +1,140 @@
+#!/usr/bin/perl -w
+
+use strict;
+use Math::Trig;
+use Math::Vector::Real;
+use IO::File;
+use Data::Dumper;
+use constant tau => pi*2;
+
+my $ellipse = 60 / 2;
+my $circle = 3.5 / 2;
+my $xscale = 33 / 100;
+my $N = 180; # around ellipse
+my $M = 80; # around each circle
+
+my $NMdiv = $ENV{'ELECTRONTOKEN_COARSE'} || 1;
+
+$M /= $NMdiv;
+$N /= $NMdiv;
+
+print <<END;
+// -*- C -*-
+// *** AUTOGENERATED - DO NOT EDIT ***
+END
+
+print "torusyup = ", ($circle / sqrt(2)), ";\n";
+
+our @ellipse = map {
+    my $theta = tau * $_ / $N;
+    V( cos($theta) * $ellipse * $xscale, sin($theta) * $ellipse, 0 )
+} 0..($N-1);
+
+#print Dumper(\@ellipse);
+
+our @alongs = map {
+    my $i = $_;
+    $ellipse[ ($i+1) % $N ] - $ellipse[ ($i-1) % $N];
+} 0..($N-1);
+
+our @circles = map {
+    my $i = $_;
+    my $centre = $ellipse[$i];
+    my $axis = $alongs[$i]->versor();
+    my $rad0 = $axis x V(0,0,1);
+    my $rad1 = $rad0 x $axis;
+    [ map {
+       my $theta = tau * $_ / $M;
+       $centre + $circle * ($rad0 * cos($theta) + $rad1 * sin($theta));
+    } 0..($M-1) ];
+} 0..($N-1);
+
+sub scadvec ($) {
+    my ($v) = @_;
+    return "[ ".(join ", ", @$v)." ]"
+}
+
+sub torusy () {
+    print "module Torusy(){ polyhedron(points=[";
+    my $ptix = 0;
+    my @cirpt;
+    foreach my $i (0..$N-1) {
+       foreach my $j (0..$M-1) {
+           print "," if $ptix;
+           print "\n";
+           print "    ",(scadvec $circles[$i][$j]);
+           $cirpt[$i][$j] = $ptix++;
+       }
+    }
+    print "\n  ],\n";
+
+    print "  faces=[";
+    foreach my $i (0..$N-1) {
+       my $i2 = ($i+1) % $N;
+       foreach my $j (0..$M-1) {
+           my $j2 = ($j+1) % $M;
+           print "," if $i || $j;
+           print "\n";
+           print "   [ ", (join ", ",
+                           $cirpt[ $i  ][ $j  ],
+                           $cirpt[ $i  ][ $j2 ],
+                           $cirpt[ $i2 ][ $j  ],
+                          ), " ],";
+           print "   [ ", (join ", ",
+                           $cirpt[ $i  ][ $j2 ],
+                           $cirpt[ $i2 ][ $j2 ],
+                           $cirpt[ $i2 ][ $j  ],
+                          ), " ]";
+       }
+    }
+    print "\n  ]);\n}\n";
+}
+
+torusy();
+
+
+our @distances;
+push @distances, 0;
+foreach my $i (1..$N) {
+    my $dist = $distances[ $i-1 ];
+    $dist += abs($ellipse[$i % $N] - $ellipse[$i-1]);
+    $distances[$i] = $dist;
+}
+
+sub infodistprop ($) {
+    my ($distprop) = @_;
+    # returns
+    #   ( $ellipse_centreline_point,
+    #     $along_vector )
+    my $dist = $distprop * $distances[$N];
+    foreach my $i (0..$N-1) {
+       my $prorata =
+           ($dist - $distances[$i]) /
+           ($distances[$i+1] - $distances[$i]);
+       next unless 0 <= $prorata && $prorata <= 1;
+       print "// infodistprop $distprop => #$i=$ellipse[$i] $prorata $ellipse[$i+1]\n";
+       return (
+               (1-$prorata) * $ellipse[$i] + ($prorata) * $ellipse[$i+1],
+               $alongs[$i],
+              );
+    }
+    die "$distprop ?";
+}
+
+while (<DATA>) { print };
+
+STDOUT->error and die $!;
+STDOUT->flush or die $!;
+
+__DATA__
+module Token(){
+    difference(){
+       for (rot=[ 0,120,240 ])
+           rotate([0,0, rot])
+           translate([0,0,torusyup])
+           Torusy();
+       translate([-200,-200,-50])
+           cube([400,400,50]);
+    }
+}
+Token();
diff --git a/fairphone-battery-case.scad b/fairphone-battery-case.scad
new file mode 100644 (file)
index 0000000..557271d
--- /dev/null
@@ -0,0 +1,190 @@
+// -*- C -*-
+
+include <utils.scad>
+
+mainwall_th = 3.0;
+smallwall_th = 2.0;
+
+seal_th = 0.3 + 0.6 + 0.6 - 0.4 - 0.4 + 0.2; // total gap for seal etc.
+behind_recess = 1.5;
+
+recess_gap_end = 0.4;
+
+lid_edge_th = 0.5;
+
+battery_len = 66.55 + 1.25 -.55;
+battery_th = 6.55 + 0.75 - .60;
+battery_wdth = 44.38 + 0.75 -.55;
+
+battery_base_indent = 0.94 + 0.50;
+battery_base_indent_fromside_outside = 4;
+battery_base_indent_fromside_inside = 10;
+
+handle_height = 3.5;
+handle_inward = 10;
+handle_len = 5;
+
+pushhole_ell_sz = 4.75;
+pushhole_ell_th = 1.75;
+pushhole_circle_dia = 4.0;
+
+// for testing:
+//battery_len = 3;
+//battery_wdth = 15;
+//battery_base_indent_fromside_inside = 6;
+
+// calculated
+
+bpp0 = [0,0];
+bpp1 = bpp0 + [ 0, mainwall_th - behind_recess ];
+lppA = bpp1 + [ seal_th, -recess_gap_end ];
+lppB = lppA + [ lid_edge_th, 0 ];
+bpp2 = [ lppB[0], bpp1[1] ];
+bpp3 = [ bpp2[0] + (bpp1 - bpp0)[1], bpp0[1] ];
+bpp4 = [ bpp3[0], bpp0[1] + mainwall_th ];
+lppC = bpp3 + [ 0, -recess_gap_end ];
+
+lppF = lppC + [ handle_height, 0 ];
+
+s0 = battery_wdth/2;
+s0i = s0 - battery_th/2;
+s1 = s0 + smallwall_th;
+
+l1 = s1 - handle_inward;
+l0 = l1 - handle_len;
+
+echo(
+     bpp0,
+     bpp1,
+     bpp2,
+     bpp3,
+     bpp4,
+     bpp5,
+     bpp6,
+     bpp7,
+     bpp8
+);
+
+echo(
+     lppA,
+     lppB,
+     lppC,
+     lppD,
+     lppE
+);
+
+bpp8 = bpp0 + [ -battery_len,0 ];
+bpp5 = [ bpp8[0] - smallwall_th, bpp4[1] ];
+bpp9 = [ bpp0[0], bpp0[1] - battery_th/2 - 1.0 ];
+bpp7 = [ bpp8[0], bpp9[1] ];
+bpp6 = [ bpp5[0], bpp9[1] ];
+lppE = [ lppA[0], bpp9[1] ];
+lppD = [ lppC[0], bpp9[1] ];
+
+module BaseHalfPlan(indent=0){
+  polygon([ bpp0,
+           bpp1,
+           bpp2,
+           bpp3,
+           bpp4,
+           bpp5,
+           bpp6,
+           bpp7 + indent * [1,0],
+           bpp8 + indent * [1,0]
+           ]);
+}
+
+module SideHalfPlan(){
+  polygon([ bpp5,
+           bpp6,
+           bpp9,
+           bpp1
+           ]);
+}
+
+module LidHalfPlan(){
+  polygon([ lppA,
+           lppE,
+           lppD,
+           lppC,
+           lppB
+           ]);
+}
+
+module HandleHalfPlan(){
+  translate(lppE)
+    square(lppF - lppE);
+}
+
+module ExtrudePlan(from,to){
+  rotate([0,-90,0])
+  for (mj=[0,1]) {
+    mirror([0,0,mj]) translate([0,0,from]){
+      linear_extrude(height= to-from, convexity=5){
+       for (mi=[0,1]) {
+         mirror([0,mi])
+           translate([0, battery_th/2])
+           children(0);
+       }
+      }
+    }
+  }
+}
+
+module PushHolePlan(){ ////toplevel
+  translate(-(pushhole_ell_th * 0.10 +
+             pushhole_ell_sz * 0.10) * [1,1]) {
+    for (r=[0,90])
+      rotate(r)
+       translate(-pushhole_ell_th * 0.5 * [1,1])
+       square([ pushhole_ell_sz, pushhole_ell_th ]);
+  }
+  circle(pushhole_circle_dia/2, $fn=40);
+}
+
+module PlanDemo(){ ////toplevel
+  color("blue") BaseHalfPlan();
+  color("red") LidHalfPlan();
+  translate([0,0,-1]) color("lightblue") SideHalfPlan();
+}
+
+module Base(){ ////toplevel
+  difference(){
+    ExtrudePlan(0,s1) BaseHalfPlan();
+    linextr(-(10+battery_len), battery_len+10, convexity=5) PushHolePlan();
+  }
+  difference(){
+    union(){
+      ExtrudePlan(s0i, s1) SideHalfPlan();
+      ExtrudePlan(s0 - battery_base_indent_fromside_inside,
+                 s0 - battery_base_indent_fromside_outside
+                 ) BaseHalfPlan(indent = battery_base_indent);
+    }
+    for (m=[0,1])
+      mirror([m,0,0])
+       translate([s0i, 0, bpp7[0] - 0.1])
+       cylinder(r= battery_th/2, h=100, $fs=0.5);
+  }
+}
+
+module BaseHalfTest(){ ////toplevel
+  intersection(){
+    Base();
+    translate([-100,0,-100])
+      cube([200,200,200]);
+  }
+}
+
+module Lid(){ ////toplevel
+  ExtrudePlan(0,s1) LidHalfPlan();
+  ExtrudePlan(l0,l1) HandleHalfPlan();
+}
+
+module Demo(){ ////toplevel
+  %Base();
+  Lid();
+}
+
+//PlanDemo();
+//Demo();
+//Base();
diff --git a/fairphone-case-mounted.scad b/fairphone-case-mounted.scad
new file mode 100644 (file)
index 0000000..8999348
--- /dev/null
@@ -0,0 +1,14 @@
+// -*- C -*-
+
+include <bike-phone-mount.scad>
+
+module CaseMounted(){ ////toplevel
+  Case();
+  translate([ phone_width/2,
+             -phone_height/2, epp3[1] - case_th_bottom ])
+    Mount();
+}
+
+//// toplevels-from:
+include <fairphone-case.scad>
+$suppress_forward_holes = true;
diff --git a/fairphone-case.scad b/fairphone-case.scad
new file mode 100644 (file)
index 0000000..2998b54
--- /dev/null
@@ -0,0 +1,1755 @@
+// -*- C -*-
+
+// Hard case for Fairphone 2
+//
+//  Copyright 2018 Ian Jackson.  There is NO WARRANTY.
+//  See below for full licensing and disclaimer.
+//
+// Instructions
+//
+//  1. You will want to git clone this repository.
+//
+//  2. Decide about the notification LED aperture. See the variable
+//     led_window_style, below.  The default here is "ad-hoc
+//     multi-colour", which can produces a translucent (clear-ish)
+//     window set into the lid, even on a single-nozzle printer.
+//     See "Ad-hoc multi-colour", below.
+//
+//  3. use "make" to generate the necessary files:
+//
+//     make -j8 fairphone-case.auto.scads `for f in   \
+//        HingePrint        \
+//        LidWindowPrint    \
+//        LidPrint          \
+//        OneKeeperPrint    \
+//        Case              \
+//     ; do echo fairphone-case,$f.auto.stl; done`
+//
+//  4. Print them.  Case and OneKeeperPrint should probably be
+//     the same colour.
+//
+//     For Lid and LidWindowPrint, if you are doing ad-hoc
+//     multi-colour:
+//        i.   Set up for clear filament
+//        ii.  Print LidWindowPrint.  Wait for it to finish.
+//             It won't take long.  As soon as it finishes, tell
+//             your printer to warm up (so that in fact it does
+//             not cool down).
+//        iii. Leaving the output so far on the printbed, reload
+//             your printer with the main lid colour.
+//        iv.  Print LidPrint.  You can let this go unattended.
+//
+//  5. Assemble the hinge.  You will need 4x M2 12mm machine screws
+//     and 8x M2 full nuts.
+//
+//     Make sure you get the hinge the right way round.  If you're not
+//     sure, run
+//         openscad fairphone-case,DemoHinge.auto.scad
+//     to see an assembly diagram.
+//
+//     The nuts recess into the hinge.  You will want very fine
+//     pliers.  As you screw each screw in, add the second nut when
+//     the screw thread emerges from the first - this will be a
+//     locknut.  Screw each screw to an appropriate tightness for the
+//     hinge stiffness.  You want the lid-side hinge to be stiffer as
+//     that makes closing the case work better.
+//
+//     When you have the stiffness right, tighten the locknuts onto
+//     each first nut.
+//
+//  6. In use:
+// 
+//      - To put the phone in, drop its RH side into the RH side of
+//        the case.  Then feed the keeper through the small hole.
+//        Feed it right through.
+//
+//      - The optional prop can be used to prop the phone up (in
+//        portrait orientation only right now).  See
+//            openscad fairphone-case,DemoPropAngles.auto.scad
+//
+// Ad-hoc multi-colour
+//     
+//  This file is set up to let you make a translucent window using a
+//  single-extruder printer, using a "two print run" technique.  This
+//  works well with our Lulzbot TAZ 5 and Aleph Objects' version of
+//  Cura.  If you are using a different printer, you may need to
+//  adjust the parameters or try a different technique.  In particular,
+//      initial_layer_thick
+//         set so that the window is one layer thick
+//      initial_layer_width
+//         set so that the slicer draws a rectangle around the whole
+//         object, rather than putting a "skirt" or anything inside
+//
+//  If you have a dual-extruder printer, you can set led_window_style
+//  to 2 and do a single print of LidPrint and LidWindowPrint.
+//
+//  Alternatively you can set it to 1 (just a hole) or 0 (no hole).
+//
+//  Thanks to Clare Boothby for the ad-hoc multi-colour technique (and
+//  the parameters for our Lulzbot TAZ 5 and Aleph Objects's Cura).
+//
+// Other phones
+//
+//  It might well be possible to adapt this file for other phones.
+//  If you do, let me know how you get on.
+//
+//
+// AUTHORSHIP, COPYRIGHT, LICENCE, AND LACK OF WARRANTY
+//
+//   Copyright (C)2018 Ian Jackson.
+//
+//    This program for generating a 3D model is free software: you can
+//    redistribute it and/or modify it under the terms of the GNU
+//    General Public License as published by the Free Software
+//    Foundation, either version 3 of the License, or (at your option)
+//    any later version.
+//
+//    This program is distributed in the hope that it will be useful,
+//    but WITHOUT ANY WARRANTY; without even the implied warranty of
+//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//    GNU General Public License for more details.
+//
+//    You should have received a copy of the GNU General Public
+//    License along with this program.  If not, see
+//    <http://www.gnu.org/licenses/>.
+//
+//  In particular DO NOT BLAME ME IF THIS CASE DOES NOT ADEQUATELY
+//  PROTECT YOUR PHONE !  It is your responsibility to decide whether
+//  this case will meet your needs.
+
+include <utils.scad>
+include <funcs.scad>
+
+phone = [ 75.0, 145.0 ];
+
+prop_buildout_less = 3;
+
+prop_angles = [ 15, 30, 45, 60 ];
+
+bumper = [ 0.250, -0.025 ];
+// ^ One side.  Overall size is increased by twice this.
+// If no bumpers, is the gap around the phone.
+
+enable_support = 1;
+
+led_window_style = 3;
+// 0: no window
+// 1: simply an opening
+// 2: opening with separate cover model, for printing in clear (two colour)
+// 3: like 2 but one-layer window for ad-hoc multi-colour
+
+initial_layer_thick = 0.400; // ^ needed for mode 3 only
+initial_layer_width = 0.750; // ^ needed for mode 3 only
+multicolour_gap = 0.15; // each side
+
+phone_cnr_rad = 6.0;
+phone_rim_depth = 0.80; // includes allowance for a screen protector
+
+button_cutout_depth = 9;
+
+phone_edge_thick = 9.0;
+phone_total_thick = 12.0;
+phone_backside_slope_inner = 1.5; // larger means shallower
+phone_backside_slope_outer = 1.0; // larger means shallower
+
+camera_pos_tl = [  6.450, 12.750 ]; // measured from tl corner
+camera_pos_br = [ 22.300, 37.600 ]; // tl/br as seen from back
+
+jack_pos = [ 13.83, 8.485 ];
+jack_dia = 10.64 + .5; // some jack I had lying around
+
+led_pos = [ 13.98, 10.00 ];
+led_aperture = 9;
+led_window_ledge = 0.75; // each side
+
+noisecancelmic_pos = [ 19.54, 7.37 ];   // from rhs
+noisecancelmic_dia = 4.00;
+
+//fingerpushhole_dias = [ 15, 18 ];
+fingerpushhole_dias = [];
+
+lanyard_half_dia = 1.15;
+lanyard_entry_rel_breadth = 2;
+lanyard_channel_len = 15;
+rearspeaker_pos_bl = [ 12.64, 18.72 ];
+rearspeaker_size   = [  3.76,  7.36 ];
+
+microusb_above = 3.27 - 0.25;
+microusb_below = 0.0;
+microusb_width = 16.12 + 1.25;
+
+case_th_bottom = 2.5;
+case_th_lid = 3.0;
+case_th_side = 2.3;
+case_th_lip = 1.2;
+
+lid_screen_gap_extra = .66;
+
+case_struts_count = 6;
+case_struts_solid_below = 1.00;
+case_struts_solid_above = 0.75;
+case_struts_width = 0.10;
+
+keeper_th_z = 0.75;
+keeper_th_x = 0.75;
+keeper_inner_width = 2.75;
+keeper_inner_height = 2.75;
+keeper_slant_slope = 2; // larger means steeper
+
+keeper_gap_z_top = 0.25;
+keeper_gap_z_bot = 0.75;
+keeper_gap_x     = 0.25;
+keeper_gap_x_holes = 0.75;
+
+keeper_side = 0; // 0 = lhs; 1 = rhs
+
+case_lip = 1.25;
+
+lid_gap_x = 0.25;
+lid_gap_z = 0.25;
+lid_lip = 1.75;
+lid_edgepart_width = 5.0;
+lid_buttoncover_thick = 1.3;
+lid_buttoncover_reinf = 0.65;
+
+foldover_gap = 0.50;
+foldover_lever_gap = 0.50;
+
+// properties of the hinge fasteners
+hingescrew_shaft_dia = 2.0 + 0.25; // M2 x 12mm machine screw
+hingescrew_shaft_len = 12;
+hingescrew_fasteners_extra_thick = 0.40;
+// ^ amount of thread protruding if everything was completely nominal
+//   and we are using two nuts
+hingescrew_nut_access_dia = 4.72 + 0.50;
+// ^ washer is 4.72 dia
+//   also, want to get pliers or tiny spanner in to do up locknut
+hingescrew_nut_across = 3.92 + 0.25; // incl. slop around recess slop
+hingescrew_nut_thick = 1.93;
+hingescrew_head_th = 1.38 + 0.75;
+hingescrew_head_dia = 3.92;
+
+hingescrew_nut_recess_portion = 2/3; // portion of nut in recess
+
+lever_cover_th = 0.75;
+hingemount_th = 2.5;
+hingemount_wd = 4.8725;
+
+$fa = 5;
+$fs = 0.1;
+
+button_l_fudge = 4.4;
+buttonishleg_default_l_is_fudge = 10;
+
+hinge_base_slope = 1.5; // bigger is steeper
+
+strut_min_at_end = 1.5;
+
+hinge_x_gap = 0.125;
+hinge_x_postscrew_gap = 0.75;
+hinge_x_arms_gap = 0.35;
+hinge_r_arms_gap = 0.55;
+
+rearspeaker_gap    = [ 2.0, 2.0 ]; // each side
+
+thumbrecess_depth = 1.3;
+thumbrecess_width = 16.5;
+thumbrecess_topcurve_r = 5.0;
+
+prop_recess_under = 0.50;
+prop_recess_slop = 0.200; // each side
+prop_end_dia = 0.5;
+prop_main_th = 3;
+prop_taper_len = 6;
+prop_main_width = 4;
+prop_side_gap = 0.75; // each side
+prop_lidrecess_behind = 0.75;
+prop_caserecess_behind = 0.75;
+prop_caserecess_taper = 0.45; // one side only
+prop_prop_gap = 0.5;
+prop_prong_heel_slope = 0.5;
+
+lid_fold_clearance_antislop = 0.5;
+
+$button_leg_only = false;
+$suppress_forward_holes = false;
+
+// ---------- calculated ----------
+
+phone_width =  (phone + bumper*2)[0];
+phone_height = (phone + bumper*2)[1];
+
+inside_br = [phone_width, -phone_height];
+
+prop_prong_h = prop_main_th;
+
+//echo(camera_pos_tl + bumper,
+//     camera_pos_br + bumper);
+
+// ----- could be changed -----
+lid_buttoncover_gap = lid_gap_x;
+lid_buttoncover_overlap = case_th_lip + keeper_gap_z_top;
+
+phone_backside_slope_thick = phone_total_thick - phone_edge_thick;
+
+//prop_lidrecess_depth = case_th_lid - prop_recess_under;
+
+//prop_nose_len = case_th_lid - prop_recess_under;
+//prop_recess_slope = tan(prop_max_angle); // bigger means steeper
+//prop_recess_width = prop_main_th / cos(prop_max_angle) + prop_backfwd_gap;
+
+
+//lid_lip_overlap_width xxx bad name = ;
+//lid_lip_inner_slope = [ 5, 5 ]; // xxx
+
+epp0 = [0,0];
+epp1 = [0, -phone_edge_thick];
+epp2i = epp1 + phone_backside_slope_thick * [ phone_backside_slope_inner, -1 ];
+epp2o = epp1 + phone_backside_slope_thick * [ phone_backside_slope_outer, -1 ];
+epp3 = epp2i + [10, 0];
+epp5 = epp0 + [0,1] * (keeper_th_z + keeper_gap_z_top + case_lip);
+epp4 = epp5 + [-1,0] * case_th_side;
+
+kppe = [0,0];
+kppd = kppe + [1,0] * keeper_inner_width;
+kppc = kppd + [0,1] * keeper_th_z;
+kppb = [ kppe[0] - keeper_th_x, kppc[1] ];
+kppf = kppe - [0,1] * keeper_inner_height;
+kppa = [ kppb[0], kppf[1] ];
+
+lpp10 = [ epp5[0] + lid_gap_x, kppc[1] + lid_gap_z ];
+lpp11 = [ lpp10[0],            epp5[1] + lid_gap_z ];
+
+lpp14 = lpp10 + [1,0] * max(keeper_inner_width, lid_edgepart_width);
+// exact x posn not very important; must extend past end of keeper
+
+lpp15 = [ lpp14[0],
+         epp0[1] - phone_rim_depth + 1/2.5 * case_th_lid
+         + lid_screen_gap_extra ];
+// ^ beam theory says to maximise force before contact,
+//   the gap below the `beam' (the lid) must be 1/3
+//   the thickness (ie the lid thickness) if the beam
+//   is solid, or 1/2 if it has a top and bottom only.
+//   ours is mostly solid.
+
+lp_r12 = max(case_th_lid - (lpp11[1] - lpp15[1]),
+            case_th_lip);
+
+lpp12 = [ epp4[0] + lp_r12,    lpp11[1] ];
+lpp13 = [ lpp12[0],            lpp12[1] + lp_r12 ];
+
+case_bottom_z = epp2o[1] - case_th_bottom;
+
+// button profile
+bppM = epp4 + [0,5];
+bppN = [ bppM[0] + lid_buttoncover_thick, bppM[1] ];
+bppR = [ bppN[0] + lid_buttoncover_gap, -button_cutout_depth ];
+bppS = [ epp1[0], bppR[1] ];
+bppQ = [ bppM[0], bppR[1] - lid_buttoncover_overlap ];
+bppP = bppQ + [0,1] * lid_buttoncover_gap;
+bppO = [ bppN[0], bppP[1] ];
+bppL = lpp10 + [5,0];
+bppK = [ bppL[0], bppN[1] ];
+bppJ = [ bppN[0], bppL[1] ];
+bppU = [ bppJ[0], lpp12[1] ];
+bppV = lpp11;
+bppW = lpp10;
+
+echo("BUTTON COVER TH", bppO[0] - bppP[0]);
+
+// notification led aperture
+
+nla_r0 = led_aperture/2;
+nla_r1 = nla_r0 + led_window_ledge;
+nla_r2 = nla_r1 + multicolour_gap;
+nla_t =
+  led_window_style >= 3 ? initial_layer_thick :
+  led_window_style >= 2 ? led_window_ledge : 0;
+
+
+// hinge plan
+hp_rn = hingescrew_nut_access_dia/2;
+hp_r2_min = hp_rn + lever_cover_th;
+hp_rs = hingescrew_shaft_dia/2;
+hp_r1_min = hp_rs + hingemount_th;
+
+hp_r1 = max(hp_r1_min, hp_r2_min);
+hp_r2 = hp_r1;
+
+hppU = lpp13;
+hppS = epp2o + [0,-1] * case_th_bottom;
+hp_k = 0.5 * (hppU[1] - hppS[1] + foldover_gap);
+
+hppM = [ epp4[0] - foldover_lever_gap - hp_r2,
+        0.5 * (hppU + hppS)[1] ];
+hppT = [ hppM[0], hppU[1] - hp_r1 ];
+hppB = hppT + [0,-1] * hp_k;
+
+hppE_y = epp2o[1] - case_th_bottom + hp_r1;
+hppE_x = hppB[0] + (hppB[1] - hppE_y) * hinge_base_slope;
+hppE = [ hppE_x, hppE_y ];
+
+// hinge elevation x coords
+
+hex20 = max(epp2o[0],
+           phone_cnr_rad,
+           kppd[0] + hingescrew_head_th + keeper_gap_x_holes);
+hex21 = hex20 + hingemount_wd;
+hex22 = hex21 + hinge_x_gap;
+hex27 = hex20 + hingescrew_shaft_len;
+hex24 = hex27 + hinge_x_postscrew_gap;
+hex23 = hex27 - (hingescrew_nut_thick*2
+                + hingescrew_fasteners_extra_thick);
+hex26 = hex23 + hingescrew_nut_thick * 2/3;
+
+echo(hex20, hex21, hex22, hex23, hex24);
+//  6, 10.8725, 10.9975, 13.74, 18.75
+module chk(act,exp) {
+  if (abs(act-exp) > 1e-9) echo("WRONG", act, exp);
+  else echo("ok", act);
+}
+chk(hex20, 6);
+chk(hex21, 10.8725);
+chk(hex22, 10.9975);
+chk(hex23, 13.74);
+chk(hex24, 18.75);
+
+lid_fold_clearance_skew =
+  (lpp10[1] - hppB[1]) /
+  (lpp10[0] - hppB[0]);
+
+echo("SK",lid_fold_clearance_skew);
+
+// thumb recess (used to be "catch" hence cpp*
+
+cppA = epp4 + [thumbrecess_depth, 0];
+cppB = [ cppA[0], epp1[1] ];
+
+// lanyard
+
+ly_r = lanyard_half_dia / 2;
+ly_rc = ly_r * 2;
+
+ly_theta = -atan2vector(epp2i - epp1);
+ly_o = epp2i + 3 * ly_r * unitvector2d(epp1 - epp2i);
+
+max_case_bottom_edge_thickness =
+  case_th_bottom
+  + sin(ly_theta) * (epp2i-epp2o)[0];
+
+ly_q_z = -(ly_rc + ly_r);
+ly_re = max_case_bottom_edge_thickness - (-ly_q_z);
+
+ly_oec_y = lanyard_entry_rel_breadth * ly_r;
+
+// prop recess in case
+
+prop_x_pos = phone_width/2;
+
+prop_recess_hw = 0.5 * prop_main_width + prop_side_gap;
+
+prc_r1 = prop_end_dia/2;
+prc_r3 = prc_r1 + prop_recess_slop;
+
+prcp2 = [ epp4[0] + prop_buildout_less,
+         case_bottom_z ];
+
+prop_caserecess_buildout_r = -1; // prcp2[0] - epp2o[0];
+
+prcp1 = [ epp2o[0] + prc_r3 + prop_caserecess_behind,
+         epp2i[1] - prc_r3 - prop_recess_under];
+
+// prop recess in lid
+
+prl_r10 = prop_end_dia/2;
+prl_r10o = prl_r10 + prop_recess_slop;
+
+prlp10 = lpp10 + [1,1] * prl_r10o
+  + [1,0] * prop_lidrecess_behind
+  + [0,1] * prop_recess_under;
+
+// prop
+
+$prpp10 = [0,0];
+$prpp11 = [0, prop_taper_len];
+
+$prp_r10 = prl_r10;
+
+// ---------- modules ----------
+
+module AdhocMultiprintFrame(phase, z0, zs) {
+  // from z0 to z0 + zs*layer
+  extra = phase * (initial_layer_width + multicolour_gap) + 5;
+  xextra = extra + -epp4[0];
+  xrange = [ 0, phone_width ] + [-1,+1] * xextra;
+  yextra = extra + -epp4[0];
+  yrange = [ -phone_height + +hppB[0] - hp_r2, 0 ] + [-1,+1] * yextra;
+  p0 = [ xrange[0], yrange[0] ];
+  p1 = [ xrange[1], yrange[1] ];
+  echo(p0, p1);
+  translate([0,0, z0])
+    mirror([0,0, zs<0 ? 1 : 0])
+    linear_extrude(height= initial_layer_thick)
+    difference(){
+      rectfromto(p0 - [1,1] * initial_layer_width,
+                p1 + [1,1] * initial_layer_width);
+      rectfromto(p0, p1);
+    }
+}
+
+module KeeperProfile(slant=0){
+  use_e = kppe + [0,-1] * slant * keeper_inner_width / keeper_slant_slope;
+  polygon([use_e, kppd, kppc, kppb, kppa, kppf]);
+}
+
+module EdgeProfile(){
+  difference(){
+    hull(){
+      translate(epp3) square(case_th_bottom*2, center=true);
+      circleat(epp2o, r=case_th_bottom);
+      circleat(epp1, r=case_th_side);
+      rectfromto(epp0, epp4);
+    }
+    polygon([ epp5 + [0,10],
+             epp1,
+             epp2i,
+             epp3 + [10,0] ]);
+  }
+}
+
+module LanyardLanyardProfile(entry=false){
+  hull(){
+    for (xs=[-1,+1] * (entry ? lanyard_entry_rel_breadth : 1))
+      translate(xs * 0.5 * lanyard_half_dia * [1,0])
+       circle(r= lanyard_half_dia/2);
+  }
+}
+
+module LanyardCurveChannelProfile(){
+  translate([0, -ly_r])
+    LanyardLanyardProfile();
+}  
+
+module LanyardEntryChannelProfile(){
+  LanyardLanyardProfile(true);
+}  
+
+module LanyardMainChannelProfile(){
+  LanyardCurveChannelProfile();
+  difference(){
+    square(center=true, ly_r * [6, 2]);
+    for (xs=[-1,+1])
+      translate(ly_r * [3 * xs, -1])
+       circle(r = ly_r);
+  }
+}
+
+module LanyardEntryOuterProfile(){
+  circleat([ly_re + ly_r, 0], ly_re);
+}
+
+module LanyardEntry(){
+  q_z = ly_q_z;
+  oec_y = ly_oec_y;
+
+  d_x = -ly_rc;
+
+  translate([d_x, 0, q_z]) {
+    intersection(){
+      rotate([90,0,0])
+       rotate_extrude(convexity=10)
+       rotate(90)
+       translate([0, -q_z])
+       LanyardCurveChannelProfile();
+      translate([0,-10,0])
+       cube([20,20,20]);
+    }
+  }
+
+  mirror([0,0,1])
+    translate([0,0,-1])
+    linear_extrude(height=20)
+    rotate(-90)
+    LanyardEntryChannelProfile();
+
+  translate([0, ly_r*2, 0])
+    rotate([90,0,0])
+    linear_extrude(height = ly_r*4){
+    difference(){
+      rectfromto([d_x, q_z], [ly_r, 0]);
+      circleat([d_x, q_z], ly_rc);
+    }
+  }
+
+  translate([0,0,q_z]){
+    for (my=[0,1])
+      mirror([0,my,0]){
+       translate([0, oec_y, 0]){
+         difference(){
+           translate(ly_re * [-1,0,-2])
+             cube(ly_re * [2,1,2]);
+           rotate_extrude(convexity=10)
+             LanyardEntryOuterProfile();
+         }
+       }
+      }
+    difference(){
+      translate([-ly_re, -(oec_y + 0.01), -2*ly_re])
+       cube([ly_re*2, 2*(oec_y + 0.01), 2*ly_re]);
+      for (mx=[0,1])
+       mirror([mx,0,0])
+         rotate([90,0,0])
+         translate([0,0,-10])
+         linear_extrude(height=20)
+         LanyardEntryOuterProfile();
+    }
+  }
+}
+
+module LanyardCutout(l){
+  rotate([0,-90,0])
+    linear_extrude(height=l)
+    rotate(-90)
+    LanyardMainChannelProfile();
+
+  for (ee=[0,1]){
+    translate(ee * l * [-1,0])
+      mirror([ee,0,0])
+      LanyardEntry();
+  }
+}
+
+module LidEdgeProfile(){
+  polygon([ lpp10,
+           lpp11,
+           lpp12,
+           lpp13,
+           lpp13 + [10, 0],
+           lpp15 + [10, 0],
+           lpp15,
+           lpp14,
+           ]);
+  intersection(){
+    circleat(lpp12, r=lp_r12);
+    rectfromto( lpp12 + [-10,   0],
+               lpp12 + [+10, +10] );
+  }
+}
+
+module LidEdgeFoldClearanceProfile(){
+  translate([-lid_fold_clearance_antislop, 0])
+    polygon([ lpp10,
+             lpp11,
+             lpp11 + [-20,  0],
+             lpp11 + [-20, 20],
+             lpp11 + [+20, 20],
+             lpp10 + [+20,  0] ]);
+}
+
+module ButtonCoverProfile(){
+  intersection(){
+    polygon(concat([ bppM, bppP, bppO, bppJ ],
+                  (enable_support && !$button_suppress_over_keeper
+                   ? [ bppU, bppV, bppW ] : []),
+                  [ bppL, bppK ]));
+    hull(){
+      EdgeProfile();
+      LidEdgeProfile();
+    }
+  }
+}
+
+module ButtonPlan(l, deep, cut){
+  epsilon =
+    (cut  ? 0 : lid_buttoncover_gap);
+
+  delta =
+    (deep ? lid_buttoncover_overlap : 0);
+
+  C = [0,0]; // by definition
+  T = [ 0, epp4[1] ];
+  G = T + [0,10];
+
+  B0 = C + [0,-1] * button_cutout_depth;
+  B1 = B0 + [0,1] * epsilon;
+
+  r0 = 0.5 * (T[1] - B0[1]);
+  A = [  -(l + button_l_fudge)/2 + r0, 0.5 * (T[1] + B0[1]) ];
+  H = A + [0,-1] * delta;
+
+  D = A + [-2,0] * r0;
+  F = D + [0,10];
+
+  E0 = 0.5 * (D + A);
+  E1 = E0 + [1,0] * epsilon;
+
+  I0 = [ E0[0], H[1] ];
+  I1 = [ E1[0], H[1] ];
+
+  hull(){
+    for (m=[0,1]) mirror([m,0])
+      circleat(H, r0 - epsilon);
+  }
+  for (m=[0,1]) mirror([m,0]) {
+    difference(){
+      polygon([ E1,
+               I1,
+               H,
+               B1,
+               G,
+               F,
+               D
+               ]);
+      circleat(D, r0 + epsilon);
+    }
+  }
+}
+
+module ButtonCoverReinf(){ ////toplevel
+  minkowski(){
+    rotate([90,0,0])
+      linear_extrude(height=0.01)
+      intersection(){
+        ButtonCoverProfile();
+       translate([bppJ[0] + 0.1, -50]) mirror([1,0])
+         square([100,100]);
+    }
+    mirror([0,0,1]) linear_extrude(height=0.01) intersection(){
+      circle(r= lid_buttoncover_reinf);
+      translate([-20,0]) square(40, center=true);
+    }
+  }
+}
+
+module ThumbRecessCutProfile(){
+  difference(){
+    polygon([ cppA + [-10,0],
+             cppB + [-10,0],
+             cppB,
+             cppA ]);
+    circleat(epp1, r=case_th_side);
+  }
+}
+
+module Flip_rhs(yn=[0,1]) {
+  for ($rhsflip=yn) {
+    translate([phone_width/2, 0, 0])
+      mirror([$rhsflip,0,0])
+      translate([-phone_width/2, 0, 0])
+      children();
+  }
+}
+
+module Flip_bot(yn=[0,1]) {
+  for ($botflip=yn) {
+    translate([0, -phone_height/2, 0])
+      mirror([0, $botflip, 0])
+      translate([0, phone_height/2, 0])
+      children();
+  }
+}  
+
+module AroundEdges(fill_zstart, fill_th, fill_downwards=0){
+  // sides
+  Flip_rhs(){
+    translate([0, -phone_cnr_rad, 0])
+      rotate([90,0,0])
+      linear_extrude(height = phone_height - phone_cnr_rad*2)
+      children(0);
+  }
+  // corners
+  Flip_rhs() Flip_bot() {
+    translate([+1,-1] * phone_cnr_rad)
+      intersection(){
+       rotate_extrude()
+         intersection(){
+           mirror([1,0,0])
+             translate([-1,0] * phone_cnr_rad)
+             children(0);
+           rectfromto([0,-20],[10,20]);
+         }
+       translate([-10, 0, -20] + 0.01 * [+1,-1, 0] )
+         cube([10,10,40]);
+      }
+  }
+  // top and bottom
+  Flip_bot(){
+    translate([ phone_width - phone_cnr_rad, 0,0 ])
+      rotate([90,0,-90])
+      linear_extrude(height = phone_width - phone_cnr_rad*2)
+      children(0);
+  }
+  // fill
+  translate([0,0, fill_zstart])
+    mirror([0,0, fill_downwards])
+    linear_extrude(height = fill_th)
+    rectfromto([+1,-1] * phone_cnr_rad,
+              [phone_width, -phone_height] + [-1,+1] * phone_cnr_rad);
+}
+
+module CaseAperture(pos, dia, $fn) {
+  theta = 180/$fn;
+  translate([ pos[0] + bumper[0],
+             -epp2i[0],
+             -pos[1] ])
+    rotate([-90, theta, 0])
+    cylinder(r = dia/2 / cos(theta),
+            h = 60);
+}
+
+module SideButton(y, y_ref_sign, l, suppress_over_keeper=0){
+  // y_ref_sign:
+  //   +1  measured from top    of actual phone to top    of button
+  //   -1  measured from bottom of actual phone to bottom of button
+  //    0  y is centre of button in coordinate system
+  $button_l= l;
+  $button_suppress_over_keeper= suppress_over_keeper;
+  eff_y = y_ref_sign > 0 ?         -bumper [1] -y -l/2 :
+         y_ref_sign < 0 ? (-phone -bumper)[1] +y +l/2 :
+         y;
+  //echo(eff_y);
+  translate([0, eff_y, 0])
+    children();
+}
+
+module LidButtonishLeg(y, y_ref_sign, l=buttonishleg_default_l_is_fudge) {
+  $button_leg_only = true;
+  SideButton(y, y_ref_sign, l) children();
+}
+
+module Buttons(){
+  Flip_rhs(1) SideButton(15.580, +1, 8.9     ) children(); // power
+  Flip_rhs(1) SideButton(48.700, -1, 8.920   ) children(); // camera
+  Flip_rhs(0) SideButton(30.800, +1, 21.96, 1) children(); // volume
+  Flip_rhs(   ) LidButtonishLeg(14, -1) children();
+//  Flip_rhs(0) LidButtonishLeg(20, +1, 20) children();
+}
+
+module Struts(x_start, z_min, th){
+  // if th is negative, starts at z_min and works towards -ve z
+  // and object should then be printed other way up
+  for (i= [1 : 1 : case_struts_count]) {
+    translate([0,
+              0,
+              z_min])
+      mirror([0,0, th<0 ? 1 : 0])
+      translate([0,
+                -phone_height * i / (case_struts_count+1),
+                case_struts_solid_below])
+      linear_extrude(height= abs(th)
+                    -(case_struts_solid_below+case_struts_solid_above))
+      rectfromto([               x_start, -0.5 * case_struts_width ],
+                [ phone_width - x_start, +0.5 * case_struts_width ]);
+  }
+}
+
+module OrdinaryRearAperture(rhs,bot, pos){
+  Flip_rhs(rhs) Flip_bot(bot)
+    linextr(-20, 20)
+    mirror([0,1])
+    translate(pos + bumper)
+    children();
+}
+
+module MicroUSB(){
+  Flip_bot(1){
+    rotate([90,0,0])
+      mirror([0,0,1])
+      linextr(-epp2i[0], 60)
+      translate([0.5 * phone_width, 0, 0])
+      rectfromto([-microusb_width/2, epp2i[1] + microusb_below],
+                [+microusb_width/2, epp0[1] + -microusb_above]);
+  }
+}
+
+module OrdinaryRearApertures(){
+  // rear speaker
+  OrdinaryRearAperture(1,1, rearspeaker_pos_bl)
+    rectfromto(-rearspeaker_gap,
+              rearspeaker_size + rearspeaker_gap);
+
+  // finger hole to remove phone
+  if (len(fingerpushhole_dias))
+    OrdinaryRearAperture(1,0, [ fingerpushhole_dias[0]/2 + epp2i[0],
+                               phone[1]/2 ])
+    scale(fingerpushhole_dias)
+    circle(r= 0.5 );
+}
+
+module RearCameraAperture(){
+  Flip_rhs(1)
+    mirror([0, 0, 1])
+    linear_extrude(height = 20)
+    mirror([0, 1, 0])
+    translate(bumper)
+    rectfromto(camera_pos_tl, camera_pos_br);
+}
+
+module HingeLidProfile(){
+  hull(){
+    circleat(hppT, hp_r1);
+    circleat(lpp12, lp_r12);
+    polygon([lpp10,
+            lpp13 + [2,0],
+            lpp12,
+            hppT]);
+  }
+}
+
+module HingeBaseProfile(){
+  difference(){
+    hull(){
+      circleat(hppB, hp_r1);
+      circleat(hppE, hp_r1);
+      circleat(epp2o, case_th_bottom);
+      circleat(hppB + [10,0], hp_r1);
+    }
+    polygon([epp5, epp1, epp2i, epp3, bppL]);
+  }
+}
+
+module HingeLeverOuterProfile(){
+  hull(){
+    circleat(hppT, hp_r2);
+    circleat(hppB, hp_r2);
+  }
+}
+
+module HingeLeverInnerProfile(){
+  for (s = [-1,+1]) {
+    c = s > 0 ? hppT : hppB;
+    translate(c)
+      mirror([0,0,s>0])
+      rotate(s<0 ? -40 : 0)
+      hull()
+      for (x=[-20,20])
+       for (y=[0, s * 10])
+         translate([x,y])
+           circle(hp_rn);
+  }
+}
+
+module HingeLeverNutProfile(){
+  for (c= [hppB, hppT]) {
+    translate(c)
+      circle($fn=6, r= 0.5 * hingescrew_nut_across / cos(30));
+  }
+}
+
+module Flip_hinge(doflip=1){
+  hinge_origin = [0, -(phone_height - hppB[0]), hppB[1]];
+  translate(hinge_origin)
+    rotate([doflip*180,0,0])
+    translate(-hinge_origin)
+    children();
+}
+
+module HingePortion(x0,x1){
+  Flip_rhs() Flip_bot(1)
+    translate([x0,0,0])
+    mirror([1,0,0])
+    rotate([90,0,-90])
+    linear_extrude(height=x1-x0)
+    children();
+}
+
+module ThumbRecessApply(ztop){
+  width = thumbrecess_width;
+  w = width + thumbrecess_topcurve_r*2 + 1;
+  translate([phone_width/2, 0,0]){
+    difference(){
+      rotate([90,0,-90])
+       linextr(-w/2, w/2)
+       children(0);
+      translate([0, 50, 0])
+       rotate([90,0,0])
+       linear_extrude(height=100){
+       for (m=[0,1]) mirror([m,0,0]) {
+         hull(){
+           translate([w/2, ztop - thumbrecess_topcurve_r])
+             circle(thumbrecess_topcurve_r);
+           translate([w/2, -50])
+             square(thumbrecess_topcurve_r*2, center=true);
+         }
+       }
+      }
+    }
+  }
+}
+
+module CaseBase(){
+  AroundEdges(epp3[1], case_th_bottom, 1)
+    EdgeProfile();
+}
+
+function prop_x(gamma) = hp_k / (2 * sin(gamma/2)) - hppT[0];
+
+module PropProfileAssignments(gamma){
+  // https://en.wikipedia.org/wiki/Solution_of_triangles#Two_sides_and_the_included_angle_given_(SAS)
+  x = prop_x(gamma);
+  p = phone_height + prlp10[0] - hppB[0];
+  b = p + x;
+
+  q = phone_height - hppT[0] - prcp1[0]; // $prpp7[0] is 0 by definition
+  a = q + x;
+  c = sqrt(a*a + b*b - 2*a*b*cos(gamma));
+  $prp_alpha = acos( (b*b + c*c - a*a) / (2*b*c) );
+
+  $prp_theta = 90 - $prp_alpha;
+  beta = 180 - $prp_alpha - gamma;
+  psi = 90 - beta;
+
+  //echo("abc", a,b,c);
+
+  v1 = [ [ cos(psi), -sin(psi) ],    // x
+        [ sin(psi),  cos(psi) ] ];  // y
+
+  $prpp7 = [0, c + (lpp13[1] - $prpp10[1] - hp_k) ];
+
+  $prp_r1 = prc_r1;
+  $prp_r11 = prop_main_th/2;
+
+  $prpp1 = $prpp7 + [1,0] *
+    // this is approximate, but will do
+    (prop_main_th/2 + prop_prop_gap + prcp1[0] - cppA[0]);
+  $prpp3 = $prpp1 +
+    v1[0] * -$prp_r1 +
+    v1[1] * ((prcp2[1] - prcp1[1]) - prop_prop_gap);
+  $prpp12 = $prpp3 + v1[0] *
+    (prop_end_dia + prop_caserecess_taper * ($prpp1[1] - $prpp3[1]));
+  $prp_r8 = prop_main_th;
+  $prpp4 = [ prop_main_th/2, $prpp3[1] ];
+  $prp_r5 = $prp_r8;
+  $prpp5 = [ $prpp12[0] - $prp_r5,
+           $prpp3[1] - prop_prong_h + $prp_r5 ];
+  $prpp6 = $prpp4 + [0,-1] * (prop_prong_h +
+         prop_prong_heel_slope * ($prpp5[0] - $prpp4[0]));
+  $prpp8 = $prpp4 + [0,-1] * $prp_r8;
+  $prpp9 = $prpp8 + [-1,0] * $prp_r8;
+
+  children();
+}
+
+module PropProfile(gamma, cut=0, rot=0){
+  PropProfileAssignments(gamma){
+
+    //#circleat($prpp3,1);
+    //#circleat($prpp12,1);
+
+    if (!cut) {
+      hull(){
+       translate($prpp8)
+         intersection(){
+           circle($prp_r8);
+           polygon([[-20,-0], [20,20], [0,0]]);
+         }
+       rectfromto($prpp6, $prpp9);
+       translate($prpp5) intersection(){
+         circle($prp_r5);
+         polygon([[-10,-10], [0,0], [10,0]]);
+       }
+       rectfromto($prpp12 + [0,-0.1], $prpp3);
+      }
+      hull(){
+       circleat($prpp1, $prp_r1);
+       rectfromto($prpp12 + [0,-0.1], $prpp3);
+      }
+    }
+    // main shaft
+    rotate([0,0, rot*-$prp_theta]){
+      hull(){
+       extra = cut ? prop_recess_slop : 0;
+       rectfromto($prpp6, $prpp9);
+       circleat($prpp11, $prp_r11 + extra);
+       circleat($prpp10, $prp_r10 + extra);
+      }
+    }
+  }
+}
+
+module PropAggregateProfile(){
+  for (angle = prop_angles)
+    PropProfile(angle, 0,0);
+}
+
+module Prop(){ ////toplevel
+  hw = prop_main_width/2;
+  linextr(-hw, +hw)
+    PropAggregateProfile();
+}
+
+module Case(){ ////toplevel
+  difference(){
+    union(){
+      CaseBase();
+
+      // ledge (fixed keeper)
+      Flip_rhs(1-keeper_side) intersection(){
+       rotate([90, 0, 0])
+         linear_extrude(height = phone_height + phone_cnr_rad * 2)
+         KeeperProfile(1);
+
+       // outline of the whole case, to stop it protruding
+       translate([0,0, -25])
+         linear_extrude(height = 50)
+         hull()
+         Flip_bot()
+         circleat([+1,-1] * phone_cnr_rad, phone_cnr_rad + case_th_side/2);
+      }
+
+      // hinge
+      HingePortion(hex20, hex21) HingeBaseProfile();
+
+      // buildout for prop recess
+      if (prop_caserecess_buildout_r > 0) Flip_rhs(1)
+       linextr(case_bottom_z, epp2i[1])
+       hull() {
+         for (dxs = [-1,+1])
+           circleat([ prop_x_pos + dxs * prop_caserecess_buildout_r,
+                      -epp2o[0] ],
+                    r = epp2o[0] - prcp2[0]);
+        }
+    }
+
+    // slot for keeper
+    Flip_rhs(keeper_side)
+      translate([0, -phone_cnr_rad, 0])
+      rotate([90, 0, 0])
+      linear_extrude(height = phone_height + phone_cnr_rad * 2)
+      minkowski(){
+        KeeperProfile();
+       rectfromto([ -keeper_gap_x,    -keeper_gap_z_bot ],
+                  [ keeper_gap_x_holes,    +keeper_gap_z_top ]);
+      }
+
+    // front camera
+    RearCameraAperture();
+
+    // struts (invisible, because they're buried in the case)
+    Struts(epp2i[0], epp2i[1] - case_th_bottom, case_th_bottom);
+
+    Buttons(){
+      mirror([1,0,0])
+       rotate([90,0,90]) {
+         if (!($button_leg_only && enable_support))
+         intersection(){
+           translate([0,0,-10])
+             linear_extrude(height= 20)
+             ButtonPlan($button_l, 0,1);
+           if ($button_leg_only)
+             rotate([-90,90,0])
+               translate([phone_width/2, -400, kppe[1]])
+               mirror([1-abs($rhsflip - keeper_side),0,0])
+               cube([400, 800, 50]);
+           if (enable_support && !$button_suppress_over_keeper)
+             rotate([-90,90,0])
+             translate([-400, -400, kppd[1]])
+               mirror([0,0,1])
+               cube([800,800,100]);
+         }
+         translate([0,0, -bppR[0]])
+           linear_extrude(height= 20)
+           ButtonPlan($button_l, 1,1);
+        }
+      
+    }
+
+    // apertures along top edge
+    if (!$suppress_forward_holes) {
+      CaseAperture(jack_pos, jack_dia, 8);
+      Flip_rhs(1)
+       CaseAperture(noisecancelmic_pos, noisecancelmic_dia, 8);
+    }
+
+    OrdinaryRearApertures();
+
+    MicroUSB();
+
+    // gaps for the lid's hinge arms
+    HingePortion(hex20 - hinge_x_arms_gap,
+                hex21 + hinge_x_arms_gap)
+      minkowski(){
+        HingeLidProfile();
+       circle(r= hinge_r_arms_gap, $fn= 8);
+      }
+
+    // screw holes in the hinge arms
+    HingeScrews();
+
+    // thumb recess
+    ThumbRecessApply(epp4[1])
+      ThumbRecessCutProfile();
+
+    // lanyard
+    Flip_bot(1)
+      translate([ly_o[0], -(phone_cnr_rad + ly_re), ly_o[1]])
+      rotate([0, ly_theta, 0])
+      rotate([0,0,90])
+      LanyardCutout(lanyard_channel_len);
+
+    // prop recess
+    Flip_rhs(1)
+      translate([prop_x_pos,0,0])
+      mirror([0,1,0])
+      rotate([90,0,90])
+      linextr(-prop_recess_hw, +prop_recess_hw)
+      hull(){
+        for (d=[ [0,0], [0,-1], [+1,-1/prop_caserecess_taper] ])
+         circleat(prcp1 + 20*d,
+                  prc_r3);
+      }
+  }
+}
+
+module LidAdhocMultiprintFrame(phase){
+  if (led_window_style >= 3) {
+    AdhocMultiprintFrame(phase, lpp13[1], -1);
+  }
+}
+
+module LidAroundEdges(){
+  AroundEdges(lpp15[1], lpp13[1] - lpp15[1], 0)
+    children();
+}
+
+module Lid(){ ////toplevel
+  skew_centre = [0, lpp11[0], lpp11[1]];
+  difference(){
+    union(){
+      intersection(){
+       LidAroundEdges()
+         LidEdgeProfile();
+
+       translate(skew_centre)
+         multmatrix([[ 1, 0, 0, 0 ],
+                     [ 0, 1, -lid_fold_clearance_skew, 0 ],
+                     [ 0, 0, 1, 0 ],
+                     [ 0, 0, 0, 1 ]])
+         translate(-skew_centre)
+         LidAroundEdges()
+         LidEdgeFoldClearanceProfile();
+      }
+
+      // button covers
+      Buttons(){
+       intersection(){
+         rotate([90,0,90])
+           translate([0,0,-10])
+           linear_extrude(height= 20)
+           ButtonPlan($button_l, 1,0);
+         union(){
+           rotate([90,0,0])
+             translate([0,0,-100])
+             linear_extrude(height= 200)
+             ButtonCoverProfile();
+           hull()
+             for (y= [-1,+1] * (($button_l + button_l_fudge)/2
+                                - lid_buttoncover_reinf))
+               translate([0,y,0])
+                 ButtonCoverReinf();
+         }
+       }
+      }
+
+      // hinge arms
+      HingePortion(hex20, hex21) {
+       LidEdgeProfile();
+       HingeLidProfile();
+      }
+    }
+    Struts(lpp10[0] + strut_min_at_end, lpp13[1], -case_th_lid);
+
+    // screw holes in the hinge arms
+    HingeScrews();
+
+    // prop recess
+    translate([prop_x_pos, -prlp10[0], prlp10[1]])
+      mirror([0,1,0])
+      rotate([90,0,90])
+      linextr(-prop_recess_hw, +prop_recess_hw)
+      hull()
+      for (pa = prop_angles)
+       PropProfile(pa, 1,1);
+
+    // notification led aperture
+    if (led_window_style)
+      translate([led_pos[0], -led_pos[1], lpp13[1]]) {
+       translate([0,0,-10])
+         cylinder(r=nla_r0, h=20);
+       if (led_window_style >= 2)
+         translate([0,0, -nla_t])
+           cylinder(r=nla_r2, height=20);
+      }
+
+    }
+
+  LidAdhocMultiprintFrame(1);
+}
+
+module HingeLever(){ ////toplevel
+  difference() {
+    // outer body, positive
+    HingePortion(hex22, hex22 + phone_width/2)
+      HingeLeverOuterProfile();
+
+    // space for the screws
+    HingePortion(hex26, hex24)
+      HingeLeverInnerProfile();
+
+    // recesses for the nuts
+    HingePortion(hex23, hex26+1)
+      HingeLeverNutProfile();
+
+    // bores for the screws
+    HingeScrews();
+
+    // space for the charging cable
+    MicroUSB();
+    Flip_hinge() MicroUSB();
+  }
+}
+
+module LidWindow(){ ////toplevel
+  translate([led_pos[0], -led_pos[1], lpp13[1]])
+    mirror([0,0,1])
+    cylinder(r= nla_r1, h=nla_t);
+  LidAdhocMultiprintFrame(0);
+}
+
+module LidWindowPrint(){ ////toplevel
+  rotate([0,180,0])
+    LidWindow();
+}
+
+module DemoLidWindowSelect(){
+  translate([led_pos[0], led_pos[1], -100]) {
+    translate([0, -30, 0]) cube([400, 400, 200]);
+  }
+}
+
+module DemoLidWindow(){ ////toplevel
+  %Lid();
+  LidWindow();
+  translate([0,40,0]){
+    color("blue") intersection(){ Lid(); DemoLidWindowSelect(); }
+    color("red") intersection(){ LidWindow(); DemoLidWindowSelect(); }
+  }
+}
+
+module HingeLeverPrint(){ ////toplevel
+  rotate([-90,0,0])
+    translate([-phone_width/2, phone_height, 0])
+    HingeLever();
+}
+
+module TestSelectLength(){
+  translate([-30, -200, -20])
+    cube([30 + 15, 250, 40]);
+}
+
+module TestLength(){ ////toplevel
+  intersection(){
+    Case();
+    TestSelectLength();
+  }
+}
+
+module TestLengthRight(){ ////toplevel
+  intersection(){
+    Case();
+    Flip_rhs(1)
+      TestSelectLength();
+  }
+}
+
+module TestSelectWidth(){
+  translate([-30, -(phone_height - 25), -20])
+    mirror([0, 1, 0])
+    cube([200, 50, 40]);
+}
+
+module TestWidth(){ ////toplevel
+  intersection(){
+    Case();
+    TestSelectWidth();
+  }
+}
+
+module TestLidWidthPrint(){ ////toplevel
+  rotate([0,180.0]) intersection(){
+    Lid();
+    TestSelectWidth();
+  }
+}
+
+module TestSelectRearAperture(){
+  minkowski(){
+    union() children();
+    translate([20, 0,0])
+      cube([42, 2, 1], center=true);
+  }
+}
+
+module TestSelectCamera(){
+  minkowski(){
+    TestSelectRearAperture()
+      RearCameraAperture();
+    cube([0.1, 50, 0.1]);
+  }
+}
+
+module TestSelectOrdinaryRearApertures(){
+  TestSelectRearAperture()
+    OrdinaryRearApertures();
+}
+
+module TestCamera(){ ////toplevel
+  intersection(){
+    Case();
+    TestSelectCamera();
+  }
+}
+
+module TestLidByCamera(){ ////toplevel
+  intersection(){
+    Lid();
+    TestSelectCamera();
+  }
+}
+
+module TestLidByCameraPrint(){ ////toplevel
+  rotate([180,0,0]) TestLidByCamera();
+}
+
+module DemoByCamera(){ ////toplevel
+  color("blue") TestLidByCamera();
+  color("red")  TestCamera();
+}
+
+module OneKeeper(){ ////toplevel
+  translate([0, -phone_cnr_rad, 0])
+    rotate([90, 0, 0])
+    linear_extrude(height = phone_height - phone_cnr_rad * 2)
+    KeeperProfile();
+}
+
+module OneKeeperPrint(){ ////toplevel
+  rotate([0,180,0])
+    OneKeeper();
+}
+
+module LidPrint(){ ////toplevel
+  rotate([0,180,0])
+    Lid();
+}
+
+module TestSelectFrame(){
+  include = [1,-1] * (epp2i[0] + 4);
+
+  difference(){
+    cube(1000, center=true);
+    translate([0,0, -100])
+      linear_extrude(height=200)
+      rectfromto(include,  inside_br - include);
+  }
+}
+
+module TestSelectLidFrame(){
+  TestSelectFrame();
+  translate([led_pos[0], -led_pos[1], -50])
+    cylinder(r= nla_r2+3, h=100);
+}
+
+module TestFrameCase(){ ////toplevel
+  intersection(){
+    Case();
+    union(){
+      TestSelectFrame();
+      TestSelectCamera();
+      TestSelectOrdinaryRearApertures();
+    }
+  }
+}
+
+module TestSelectTopApertures(){
+  translate([-100, -35, -100])
+    cube([400, 100, 200]);
+  LidAdhocMultiprintFrame(0);
+  LidAdhocMultiprintFrame(1);
+}
+
+module TestTopApertures(){ ////toplevel
+  intersection(){
+    Case();
+    TestSelectFrame();
+    TestSelectTopApertures();
+  }
+}
+
+module TestLidTopAperturesPrint(){ ////toplevel
+  rotate([0,180,0]) intersection(){
+    Lid();
+    TestSelectLidFrame();
+    TestSelectTopApertures();
+  }
+}
+
+module TestLidWindowTopAperturesPrint(){ ////toplevel
+  rotate([0,180,0]) intersection(){
+    LidWindow();
+    TestSelectTopApertures();
+  }
+}
+
+module TestFrameLidPrint(){ ////toplevel
+  rotate([0,180,0]) intersection(){
+    Lid();
+    TestSelectLidFrame();
+  }
+}
+
+module ButtonPlanForDemo(z, deep, cut){
+  translate([0,0,z])
+    ButtonPlan(8, deep, cut);
+}
+
+module HingeScrews(){
+  Flip_rhs() Flip_bot(1){
+    for (c= [ hppT, hppB ])
+      translate([ hex20,
+                 -c[0],
+                 c[1] ]){
+       rotate([0,90,0])
+         translate([0,0,-.2])
+         cylinder( r= hingescrew_shaft_dia/2,
+                   h = hingescrew_shaft_len+0.2 );
+       rotate([0,-90,0])
+         translate([0,0,+.1])
+         cylinder( r= hingescrew_head_dia/2, h = hingescrew_head_th );
+      }
+  }
+}
+
+module DemoPropAngleSelect(c){
+  color(c) difference(){
+    union(){ children(); }
+    translate([ prop_x_pos, -400, -200 ])
+      cube([ 400,800,400 ]);
+  }
+}
+
+module DemoPropAngle(ang){
+  hL = [0, -(phone_height - hppT[0]), hppT[1] - hp_k*2];
+  hC = [0, -(phone_height - hppB[0]), hppB[1]];
+
+  translate(hL)
+    rotate([ang/2,0,0])
+    translate(-hL)
+    translate(hC)
+    rotate([ang/2,0,0])
+    translate(-hC) {
+      DemoPropAngleSelect("red") Case();
+
+      color("orange")
+       translate([prop_x_pos, -prcp1[0], prcp1[1]])
+       PropProfileAssignments(ang) {
+          echo($prpp1);
+         rotate([-$prp_theta, 0, 0])
+         translate([0, $prpp1[0], -$prpp1[1]])
+         rotate([90,0,-90])
+         Prop();
+        }
+    }
+
+  translate([0,0, -hp_k*2])
+    DemoPropAngleSelect("blue")
+    Lid();
+}
+
+module DemoPropAngles(){ ////toplevel
+  for (i=[0 : len(prop_angles)-1])
+    translate(i * [0, -100, 100])
+    DemoPropAngle(prop_angles[i]);
+}
+
+module DemoHingeAngle(ang1,ang2){
+  hL = [0, -(phone_height - hppT[0]), hppT[1]];
+  hC = [0, -(phone_height - hppB[0]), hppB[1]];
+
+  translate(hL)
+    rotate([ang2,0,0])
+    translate(-hL)
+    translate(hC)
+    rotate([ang1,0,0])
+    translate(-hC) {
+      color("red") Lid();
+    }
+
+  color("blue") intersection(){
+    Case();
+    union(){
+      translate([bppJ[0], -400, -200])
+       mirror([1,0,0])
+       cube([400, 800, 400]);
+      translate([10, -400, -200])
+       cube([10, 800, 400]);
+    }
+  }
+}
+
+module DemoHingeAngles(){ ////toplevel
+  angles = [ 0, 4, 8, 12 ];
+  echo("angles",angles);
+  for (i=[0 : len(angles)-1]) {
+    translate(i * [0, 0, 30]) {
+      DemoHingeAngle(0,angles[i]);
+      translate([0, 200, 0])
+       DemoHingeAngle(angles[i],0);
+    }
+  }
+}
+
+module DemoSelectAdhocLeftRight(right=0) {
+  translate([phone_width/2, -400, -100]) // , -15, -100  to cross-section
+    mirror([1-right, 0,0])
+    cube([400, 800, 200]);
+}
+
+module DemoLeft(){ ////toplevel
+  color("red")  intersection(){ Case(); DemoSelectAdhocLeftRight(); }
+  color("blue") intersection(){ Lid();  DemoSelectAdhocLeftRight(); }
+}
+
+module DemoFrame(){ ////toplevel
+  color("red") TestFrameCase();
+  color("blue") intersection(){ Lid(); TestSelectLidFrame(); }
+  color("black") HingeScrews();
+  %HingeLever();
+}
+
+module DemoLanyardCutout(){ ////toplevel
+  LanyardCutout(25);
+}
+
+module DemoHingedFrame(){ ///toplevel
+  color("red") TestFrameCase();
+  translate([0,0, -2*hp_k])
+  color("blue") intersection(){ Lid(); TestSelectLidFrame(); }
+
+  Flip_hinge(){
+    color("orange") HingeLever();
+    color("black") HingeScrews();
+  }
+}
+
+module DemoHinge(){ ////toplevel
+  translate([ -0.5*phone_width, phone_height, hp_k*3 ]) {
+    DemoFrame();
+    translate([0,0, -hp_k*3])
+      DemoHingedFrame();
+  }
+}
+
+module DemoProfiles(){ ////toplevel
+  LidEdgeProfile();
+  %EdgeProfile();
+  KeeperProfile();
+  translate([0,0,-1]) color("black") KeeperProfile(1);
+  translate(ly_o){
+    rotate(-ly_theta){
+      translate([0,0,+1]) color("purple") LanyardMainChannelProfile();
+      translate([0,0,+2]) color("red") LanyardCurveChannelProfile();
+      translate([0, ly_q_z]){
+       translate([0,0,-1]) color("blue") LanyardEntryChannelProfile();
+       translate([ly_oec_y,0,-2]) color("black") LanyardEntryOuterProfile();
+      }
+    }
+  }
+  translate([0,0,-5]) color("white") translate(epp2i)
+    rotate(-ly_theta)
+    rectfromto([-15, 0],
+              [+15, -max_case_bottom_edge_thickness]);
+
+  translate([0,20]) {
+    LanyardMainChannelProfile();
+    translate([0,0,1]) color("purple") LanyardCurveChannelProfile();
+    translate([0,0,-1]) color("red") LanyardEntryChannelProfile();
+  }
+
+  translate([20,0]) {
+    LidEdgeProfile();
+    %EdgeProfile();
+
+    demopoint_QR = [ bppS[0], bppQ[1] - 0.1];
+  
+    color("blue") ButtonCoverProfile();
+    color("red") {
+      rectfromto(bppQ, demopoint_QR);
+      rectfromto(bppR, demopoint_QR);
+    }
+  }
+
+  translate([-20,0]) {
+    color("black") ButtonPlanForDemo(-2, 0,1);
+    color("red" )  ButtonPlanForDemo(-4, 1,1);
+    color("blue")  ButtonPlanForDemo(-6, 1,0);
+  }
+
+  translate([0, -30]) {
+    %LidEdgeProfile();
+    %EdgeProfile();
+    color("blue") HingeLidProfile();
+    color("red")  HingeBaseProfile();
+    color("black") translate([0,0,-2]) HingeLeverOuterProfile();
+  }
+
+  for (f=[0,1]) {
+    translate([-30, -60 + 30*f]) {
+      translate([0,0,-4]) EdgeProfile();
+      %translate([0,0,-10]) HingeBaseProfile();
+      translate([0,-2] * f * hp_k) {
+       translate([0,0,-4]) LidEdgeProfile();
+       %translate([0,0,-10]) %HingeLidProfile();
+      }
+      translate(+hppB) rotate([0,0,180*f]) translate(-hppB) {
+       translate([0,0,-2]) color("black") HingeLeverOuterProfile(); 
+       translate([0,0,0]) color("red") difference(){
+         HingeLeverOuterProfile();
+         HingeLeverInnerProfile();
+       }
+       translate([0,0,3]) color("yellow") HingeLeverNutProfile();
+      }
+    }
+  }
+
+  translate([20,-30]) {
+    %EdgeProfile();
+    %LidEdgeProfile();
+    //translate([0,0,1]) ThumbRecessCutProfile();
+    translate([0,0,+1]) color("red")
+      difference(){ EdgeProfile(); ThumbRecessCutProfile(); }
+  }
+
+  translate([40,-30]) {
+    difference(){
+      LidEdgeProfile();
+      translate(prlp10)
+       PropProfile(10, 1, 0);
+    }
+    translate(prlp10)
+      PropProfile(15, 0);
+  }
+  translate([60,-30]) {
+    PropAggregateProfile();
+  }
+}
+
+//EdgeProfile();
+//KeeperProfile();
+//CaseBase();
+//%Case();
+//Keeper();
+//LidEdgeProfile();
+//KeeperProfile();
+//DemoProfiles();
+//PropRecess();
diff --git a/fairphone4-case-coarse.scad b/fairphone4-case-coarse.scad
new file mode 100644 (file)
index 0000000..e5ea9a1
--- /dev/null
@@ -0,0 +1,7 @@
+// -*- C -*-
+
+//// toplevels-from:
+include <fairphone4-case.scad>
+
+$fa = 20;
+$fs = 2;
diff --git a/fairphone4-case-mounted.scad b/fairphone4-case-mounted.scad
new file mode 100644 (file)
index 0000000..fecff5a
--- /dev/null
@@ -0,0 +1,15 @@
+// -*- C -*-
+
+include <bike-phone-mount.scad>
+
+module CaseMounted(){ ////toplevel
+  Case();
+  translate([ phone_width/2,
+             -phone_height/2, epp3[1] - case_th_bottom ])
+    Mount();
+}
+
+//// toplevels-from:
+include <fairphone4-case.scad>
+$suppress_forward_holes = true;
+$suppress_hinge = true;
diff --git a/fairphone4-case-tripod.scad b/fairphone4-case-tripod.scad
new file mode 100644 (file)
index 0000000..b244673
--- /dev/null
@@ -0,0 +1,37 @@
+// -*- C -*-
+
+include <camera-mount.scad>
+
+tr_cube_offset = 20;
+tr_cube_sz = [20, 20, 15];
+tr_around = 10;
+
+module Mount(){
+  translate([0,  - tr_cube_sz[1], 0])
+  difference(){
+    translate([0, tr_cube_sz[1]/2 - tr_cube_offset/2, tr_cube_sz[2]/2])
+      cube(tr_cube_sz + [0, tr_cube_offset, 0], center=true);
+    translate([0, tr_cube_sz[1]/2 - tr_cube_offset, 0])
+      rotate([180,0,0])
+      render() CameraMountThread(tr_cube_sz[2] + 1);
+  }
+}
+
+module CaseMounted(){ ////toplevel
+  difference(){
+    render() Case();
+    translate([ phone_width/2, -phone_height/2 ])
+      linextr(-50, 50)
+      square([phone_width, phone_height] - tr_around * 2 * [1,1],
+            center=true);
+  }
+  translate([ phone_width,
+             -phone_height + tr_cube_sz[0] * 0.7,
+             epp3[1] - case_th_bottom ])
+    rotate([0,0,90])
+    Mount();
+}
+
+//// toplevels-from:
+include <fairphone4-case.scad>
+$suppress_hinge = true;
diff --git a/fairphone4-case.scad b/fairphone4-case.scad
new file mode 100644 (file)
index 0000000..0a4b63d
--- /dev/null
@@ -0,0 +1,1801 @@
+// -*- C -*-
+
+// Hard case for Fairphone 2
+//
+//  Copyright 2018 Ian Jackson.  There is NO WARRANTY.
+//  See below for full licensing and disclaimer.
+//
+// Instructions
+//
+//  1. You will want to git clone this repository.
+//
+//  2. <deleted>
+//
+//  3. use "make" to generate the necessary files:
+//
+//     make -j8 fairphone-case.auto.scads `for f in   \
+//        HingeLeverPrint   \
+//        LidPrint          \
+//        OneKeeperPrint    \
+//        Case              \
+//     ; do echo fairphone-case,$f.auto.stl; done`
+//
+//  4. Print them.  Case and OneKeeperPrint should probably be
+//     the same colour.
+//
+//  5. Assemble the hinge.  After placing the parts in the appropirate
+//     relative placement:
+//
+//        Use long bit of wire to ensure holes are lined up and proper
+//        Cut four short bits of wire, using above as a guage
+//
+//        Push two short bits into two holes on same side
+//        Use long bit of wire to ensure properly in holes
+//        Keep that side up so they don't fall out!
+//
+//        For each of the two holes
+//          Use 20-30cm hunk of 2.85mm PLA
+//          Use gas flame to melt end until it catches fire (!)
+//          Remove from flame, wave to extinguish, and quickly:
+//          Dab end onto where hole is
+//          As it congeals, use sidecutters to cut off by hole
+//
+//        Repeat for two holes on other side
+//        When cool, file down rough edges
+//
+//  6. In use:
+// 
+//      - To put the phone in, drop its RH side into the RH side of
+//        the case.  Then feed the keeper through the small hole.
+//        Feed it right through.
+//
+//      - The optional prop can be used to prop the phone up (in
+//        portrait orientation only right now).  See
+//            openscad fairphone-case,DemoPropAngles.auto.scad
+//
+// Other phones
+//
+//  It might well be possible to adapt this file for other phones.
+//  If you do, let me know how you get on.
+//
+//
+// AUTHORSHIP, COPYRIGHT, LICENCE, AND LACK OF WARRANTY
+//
+//   Copyright (C) 2018-2022 Ian Jackson.
+//
+//    This program for generating a 3D model is free software: you can
+//    redistribute it and/or modify it under the terms of the GNU
+//    General Public License as published by the Free Software
+//    Foundation, either version 3 of the License, or (at your option)
+//    any later version.
+//
+//    This program is distributed in the hope that it will be useful,
+//    but WITHOUT ANY WARRANTY; without even the implied warranty of
+//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//    GNU General Public License for more details.
+//
+//    You should have received a copy of the GNU General Public
+//    License along with this program.  If not, see
+//    <http://www.gnu.org/licenses/>.
+//
+//  In particular DO NOT BLAME ME IF THIS CASE DOES NOT ADEQUATELY
+//  PROTECT YOUR PHONE !  It is your responsibility to decide whether
+//  this case will meet your needs.
+
+include <utils.scad>
+include <funcs.scad>
+
+phone = [ 75.86, 162.0 ];
+
+prop_buildout_less = 3;
+
+prop_angles = [ 15, 30, 45, 60 ];
+
+bumper = [ 0.250, -0.025 ];
+// ^ One side.  Overall size is increased by twice this.
+// If no bumpers, is the gap around the phone.
+
+enable_support = 1;
+
+led_window_style = 0;
+// 0: no window
+// 1: simply an opening
+// 2: opening with separate cover model, for printing in clear (two colour)
+// 3: like 2 but one-layer window for ad-hoc multi-colour
+
+initial_layer_thick = 0.400; // ^ needed for mode 3 only
+initial_layer_width = 0.750; // ^ needed for mode 3 only
+multicolour_gap = 0.15; // each side
+
+phone_cnr_rad = 7.0; // actuall 8.mumble, but smaller is fine
+phone_rim_depth = 0.01; // includes allowance for a screen protector
+
+button_cutout_depth = 9;
+
+phone_edge_thick = 11.25;
+
+camera_pos_tl = [  5.600,  5.750 ]; // from tl corner (as seen from back)
+camera_edge_rad = 9.750 + 0.700;
+camera_sz = 32.920 + .750 + 1.000;
+
+// this is disabled, FP4 doesn't have one
+jack_pos = [ 13.83, 8.485 ];
+jack_dia = 10.64 + .5; // some jack I had lying around
+
+// this led stuff, is irrelevant, we have disabled it as it doesn't have one
+led_pos = []; // [ 13.98, 10.00 ];
+led_aperture = 9;
+led_window_ledge = 0.75; // each side
+
+noisecancelmic_pos = [ 15.08 + .720, 4.35 ];   // from rhs, from top edge
+noisecancelmic_dia = 4.00;
+
+mainmic_pos = [ 21.0, 4.65 ];   // from lhs, from top edge
+mainmic_dia = 4.00;
+
+lhshole_pos = [ phone[1]/2 + 0.40, 4.35 ];
+
+fingerpushhole_dias = [];
+//fingerpushhole_dias = [ 15, 18 ]; // this is for testing
+
+lanyard_half_dia = 1.15;
+lanyard_entry_rel_breadth = 2;
+lanyard_channel_len = 8;
+//rearspeaker_pos_bl = [ 12.64, 18.72 ];
+//rearspeaker_size   = [  3.76,  7.36 ];
+
+bottomspeaker_size = [ 11.35, 1.90 ] + [1,1] * 0.5;
+bottomspeaker_pos = [ 17.55, 5.17 ]; // from rhs, from top
+
+microusb_above = 1.64 - 0.25;
+microusb_below = 2.42;
+microusb_width = 12.16 + 2.0 + 1.25;
+
+case_th_bottom = 2.5;
+case_th_lid = 3.0;
+case_th_side = 2.6;
+case_th_lip = 1.2;
+
+lid_screen_gap_extra = .66;
+
+case_struts_count = 6;
+case_struts_solid_below = 1.00;
+case_struts_solid_above = 0.75;
+case_struts_width = 0.10;
+
+keeper_th_z = 0.75;
+keeper_th_x = 0.75;
+keeper_inner_width = 2.75;
+keeper_inner_height = 2.75;
+keeper_slant_slope = 2; // larger means steeper
+
+keeper_gap_z_top = 0.25;
+keeper_gap_z_bot = 0.75;
+keeper_gap_x     = 0.25;
+keeper_gap_x_holes = 0.75;
+keeper_fatter = 0.45;
+keeper_fatter_hole = 1.20;
+keeper_stubbier = 0.0;
+
+keeper_side = 0; // 0 = lhs; 1 = rhs
+
+case_lip = 1.25;
+
+lid_gap_x = 0.25;
+lid_gap_z = 0.25;
+lid_lip = 1.75;
+lid_edgepart_width = 5.0;
+lid_buttoncover_thick = 1.3;
+lid_buttoncover_reinf = 0.95;
+
+foldover_gap = 0.50;
+foldover_lever_gap = 0.50;
+
+// properties of the hinge fasteners
+hingescrew_shaft_dia = 1.600 + 0.45; // beading wire
+hingescrew_shaft_len = 10;
+hingescrew_fasteners_extra_thick = 0.40;
+// ^ amount of thread protruding if everything was completely nominal
+//   and we are using two nuts
+hingescrew_nut_access_dia = 4.72 + 0.50;
+// ^ washer is 4.72 dia
+//   also, want to get pliers or tiny spanner in to do up locknut
+hingescrew_nut_across = 3.92 + 0.25; // incl. slop around recess slop
+hingescrew_nut_thick = 1.93;
+hingescrew_head_th = 1.38 + 0.75;
+hingescrew_head_dia = 3.92;
+
+hingescrew_nut_recess_portion = 2/3; // portion of nut in recess
+
+lever_cover_th = 0.75;
+hingemount_th = 2.5;
+hingemount_wd = 4.8725;
+
+$fa = 5;
+$fs = 0.1;
+
+button_l_fudge = 4.4;
+buttonishleg_default_l_is_fudge = 10;
+
+hinge_base_slope = 1.5; // bigger is steeper
+
+strut_min_at_end = 1.5;
+
+hinge_x_gap = 0.125;
+hinge_x_postscrew_gap = 0.75;
+hinge_x_arms_gap = 0.35;
+hinge_r_arms_gap = 0.55;
+hinge_over_nut_plate = -0.50; // bodge apropos slope
+
+// there isn't one of these, speaker is by hinge
+// rearspeaker_gap    = [ 2.0, 2.0 ]; // each side
+
+thumbrecess_depth = 1.3;
+thumbrecess_width = 16.5;
+thumbrecess_topcurve_r = 5.0;
+
+prop_recess_under = 0.50;
+prop_recess_slop = 0.200; // each side
+prop_end_dia = 0.5;
+prop_main_th = 3;
+prop_taper_len = 6;
+prop_main_width = 4;
+prop_side_gap = 0.75; // each side
+prop_lidrecess_behind = 0.75;
+prop_caserecess_behind = 0.75;
+prop_caserecess_taper = 0.45; // one side only
+prop_prop_gap = 0.5;
+prop_prong_heel_slope = 0.5;
+
+lid_fold_clearance_antislop = 0.5;
+
+$button_leg_only = false;
+$suppress_forward_holes = false;
+$suppress_hinge = false;
+
+// ---------- calculated ----------
+
+phone_total_thick = phone_edge_thick;
+
+phone_width =  (phone + bumper*2)[0];
+phone_height = (phone + bumper*2)[1];
+
+inside_br = [phone_width, -phone_height];
+
+prop_prong_h = prop_main_th;
+
+//echo(camera_pos_tl + bumper,
+//     camera_pos_br + bumper);
+
+// ----- could be changed -----
+lid_buttoncover_gap = lid_gap_x;
+lid_buttoncover_overlap = case_th_lip + keeper_gap_z_top;
+
+//prop_lidrecess_depth = case_th_lid - prop_recess_under;
+
+//prop_nose_len = case_th_lid - prop_recess_under;
+//prop_recess_slope = tan(prop_max_angle); // bigger means steeper
+//prop_recess_width = prop_main_th / cos(prop_max_angle) + prop_backfwd_gap;
+
+
+epp0 = [0,0];
+epp1 = [0, -phone_edge_thick];
+epp2i = epp1; // conflated for FP4
+epp2o = epp2i;
+epp3 = epp2i + [10, 0];
+epp5 = epp0 + [0,1] * (keeper_th_z + keeper_gap_z_top + case_lip);
+epp4 = epp5 + [-1,0] * case_th_side;
+
+kppe = [0,0];
+kppd = kppe + [1,0] * keeper_inner_width;
+kppc = kppd + [0,1] * keeper_th_z;
+kppb = [ kppe[0] - keeper_th_x, kppc[1] ];
+kppf = kppe - [0,1] * keeper_inner_height;
+kppa = [ kppb[0], kppf[1] ];
+
+lpp10 = [ epp5[0] + lid_gap_x, kppc[1] + lid_gap_z ];
+lpp11 = [ lpp10[0],            epp5[1] + lid_gap_z ];
+
+lpp14 = lpp10 + [1,0] * max(keeper_inner_width, lid_edgepart_width);
+// exact x posn not very important; must extend past end of keeper
+
+lpp15 = [ lpp14[0],
+         epp0[1] - phone_rim_depth + 1/2.5 * case_th_lid
+         + lid_screen_gap_extra ];
+// ^ beam theory says to maximise force before contact,
+//   the gap below the `beam' (the lid) must be 1/3
+//   the thickness (ie the lid thickness) if the beam
+//   is solid, or 1/2 if it has a top and bottom only.
+//   ours is mostly solid.
+
+lp_r12 = max(case_th_lid - (lpp11[1] - lpp15[1]),
+            case_th_lip);
+
+lpp12 = [ epp4[0] + lp_r12,    lpp11[1] ];
+lpp13 = [ lpp12[0],            lpp12[1] + lp_r12 ];
+
+case_bottom_z = epp2o[1] - case_th_bottom;
+
+// button profile
+bppM = epp4 + [0,5];
+bppN = [ bppM[0] + lid_buttoncover_thick, bppM[1] ];
+bppR = [ bppN[0] + lid_buttoncover_gap, -button_cutout_depth ];
+bppS = [ epp1[0], bppR[1] ];
+bppQ = [ bppM[0], bppR[1] - lid_buttoncover_overlap ];
+bppP = bppQ + [0,1] * lid_buttoncover_gap;
+bppO = [ bppN[0], bppP[1] ];
+bppL = lpp10 + [5,0];
+bppK = [ bppL[0], bppN[1] ];
+bppJ = [ bppN[0], bppL[1] ];
+bppU = [ bppJ[0], lpp12[1] ];
+bppV = lpp11;
+bppW = lpp10;
+
+echo("BUTTON COVER TH", bppO[0] - bppP[0]);
+
+// notification led aperture
+
+nla_r0 = led_aperture/2;
+nla_r1 = nla_r0 + led_window_ledge;
+nla_r2 = nla_r1 + multicolour_gap;
+nla_t =
+  led_window_style >= 3 ? initial_layer_thick :
+  led_window_style >= 2 ? led_window_ledge : 0;
+
+
+// hinge plan
+hp_rn = hingescrew_nut_access_dia/2;
+hp_r2_min = hp_rn + lever_cover_th;
+hp_rs = hingescrew_shaft_dia/2;
+hp_r1_min = hp_rs + hingemount_th;
+
+hp_r1 = max(hp_r1_min, hp_r2_min);
+hp_r2 = hp_r1;
+
+hppU = lpp13;
+hppS = epp2o + [0,-1] * case_th_bottom;
+hp_k = 0.5 * (hppU[1] - hppS[1] + foldover_gap);
+
+hppM = [ epp4[0] - foldover_lever_gap - hp_r2,
+        0.5 * (hppU + hppS)[1] ];
+hppT = [ hppM[0], hppU[1] - hp_r1 ];
+hppB = hppT + [0,-1] * hp_k;
+
+hppE_y = epp2o[1] - case_th_bottom + hp_r1;
+hppE_x = hppB[0] + (hppB[1] - hppE_y) * hinge_base_slope;
+hppE = [ hppE_x, hppE_y ];
+
+// hinge elevation x coords
+
+hex20 = max(epp2o[0],
+           phone_cnr_rad,
+           kppd[0] + hingescrew_head_th + keeper_gap_x_holes);
+hex21 = hex20 + hingemount_wd;
+hex22 = hex21 + hinge_x_gap;
+hex27 = hex20 + hingescrew_shaft_len;
+hex24 = hex27 + hinge_x_postscrew_gap;
+hex23 = hex27 - (hingescrew_nut_thick*2
+                + hingescrew_fasteners_extra_thick);
+hex26 = hex23 + hingescrew_nut_thick * 2/3;
+
+//echo(hex20, hex21, hex22, hex23, hex24);
+////  6, 10.8725, 10.9975, 13.74, 18.75
+//module chk(act,exp) {
+//  if (abs(act-exp) > 1e-9) echo("WRONG", act, exp);
+//  else echo("ok", act);
+//}
+//chk(hex20, 6);
+//chk(hex21, 10.8725);
+//chk(hex22, 10.9975);
+//chk(hex23, 13.74);
+//chk(hex24, 18.75);
+
+lid_fold_clearance_skew =
+  (lpp10[1] - hppB[1]) /
+  (lpp10[0] - hppB[0]);
+
+echo("SK",lid_fold_clearance_skew);
+
+// thumb recess (used to be "catch" hence cpp*
+
+cppA = epp4 + [thumbrecess_depth, 0];
+cppB = [ cppA[0], epp1[1] ];
+
+// lanyard
+
+ly_r = lanyard_half_dia / 2;
+ly_rc = ly_r * 2;
+
+ly_theta = 90;
+ly_o = epp2i + 3 * ly_r * [0,1];
+
+max_case_bottom_edge_thickness =
+  case_th_bottom;
+
+ly_q_z = -(ly_rc + ly_r);
+ly_re = max_case_bottom_edge_thickness - (-ly_q_z);
+
+ly_oec_y = lanyard_entry_rel_breadth * ly_r;
+
+// prop recess in case
+
+prop_x_pos = phone_width/2;
+
+prop_recess_hw = 0.5 * prop_main_width + prop_side_gap;
+
+prc_r1 = prop_end_dia/2;
+prc_r3 = prc_r1 + prop_recess_slop;
+
+prcp2 = [ epp4[0] + prop_buildout_less,
+         case_bottom_z ];
+
+prop_caserecess_buildout_r = -1; // prcp2[0] - epp2o[0];
+
+prcp1 = [ epp2o[0] + prc_r3 + prop_caserecess_behind,
+         epp2i[1] - prc_r3 - prop_recess_under];
+
+// prop recess in lid
+
+prl_r10 = prop_end_dia/2;
+prl_r10o = prl_r10 + prop_recess_slop;
+
+prlp10 = lpp10 + [1,1] * prl_r10o
+  + [1,0] * prop_lidrecess_behind
+  + [0,1] * prop_recess_under;
+
+// prop
+
+$prpp10 = [0,0];
+$prpp11 = [0, prop_taper_len];
+
+$prp_r10 = prl_r10;
+
+// ---------- modules ----------
+
+module AdhocMultiprintFrame(phase, z0, zs) {
+  // from z0 to z0 + zs*layer
+  extra = phase * (initial_layer_width + multicolour_gap) + 5;
+  xextra = extra + -epp4[0];
+  xrange = [ 0, phone_width ] + [-1,+1] * xextra;
+  yextra = extra + -epp4[0];
+  yrange = [ -phone_height + +hppB[0] - hp_r2, 0 ] + [-1,+1] * yextra;
+  p0 = [ xrange[0], yrange[0] ];
+  p1 = [ xrange[1], yrange[1] ];
+  echo(p0, p1);
+  translate([0,0, z0])
+    mirror([0,0, zs<0 ? 1 : 0])
+    linear_extrude(height= initial_layer_thick)
+    difference(){
+      rectfromto(p0 - [1,1] * initial_layer_width,
+                p1 + [1,1] * initial_layer_width);
+      rectfromto(p0, p1);
+    }
+}
+
+module KeeperProfile(fatter=0, slant=0, stubbier=0){
+  use_e = kppe + [0,-1] * slant * keeper_inner_width / keeper_slant_slope;
+  polygon([use_e + [+1,-1] * fatter,
+          kppd  + [ 0,-1] * fatter - stubbier * [1,0],
+          kppc                     - stubbier * [1,0],
+          kppb,
+          kppa                     + stubbier * [0,1],
+          kppf  + [+1, 0] * fatter + stubbier * [0,1]
+          ]);
+}
+
+module EdgeProfile(){
+  difference(){
+    hull(){
+      translate(epp3) square(case_th_bottom*2, center=true);
+      circleat(epp2o, r=case_th_bottom);
+      circleat(epp1, r=case_th_side);
+      rectfromto(epp0, epp4);
+    }
+    polygon([ epp5 + [0,10],
+             epp1,
+             epp3 + [10,0] ]);
+  }
+}
+
+module LanyardLanyardProfile(entry=false){
+  hull(){
+    for (xs=[-1,+1] * (entry ? lanyard_entry_rel_breadth : 1))
+      translate(xs * 0.5 * lanyard_half_dia * [1,0])
+       circle(r= lanyard_half_dia/2);
+  }
+}
+
+module LanyardCurveChannelProfile(){
+  translate([0, -ly_r])
+    LanyardLanyardProfile();
+}  
+
+module LanyardEntryChannelProfile(){
+  LanyardLanyardProfile(true);
+}  
+
+module LanyardMainChannelProfile(){
+  LanyardCurveChannelProfile();
+  difference(){
+    square(center=true, ly_r * [6, 2]);
+    for (xs=[-1,+1])
+      translate(ly_r * [3 * xs, -1])
+       circle(r = ly_r);
+  }
+}
+
+module LanyardEntryOuterProfile(){
+  circleat([ly_re + ly_r, 0], ly_re);
+}
+
+module LanyardEntry(){
+  q_z = ly_q_z;
+  oec_y = ly_oec_y;
+
+  d_x = -ly_rc;
+
+  translate([d_x, 0, q_z]) {
+    intersection(){
+      rotate([90,0,0])
+       rotate_extrude(convexity=10)
+       rotate(90)
+       translate([0, -q_z])
+       LanyardCurveChannelProfile();
+      translate([0,-10,0])
+       cube([20,20,20]);
+    }
+  }
+
+  mirror([0,0,1])
+    translate([0,0,-1])
+    linear_extrude(height=20)
+    rotate(-90)
+    LanyardEntryChannelProfile();
+
+  translate([0, ly_r*2, 0])
+    rotate([90,0,0])
+    linear_extrude(height = ly_r*4){
+    difference(){
+      rectfromto([d_x, q_z], [ly_r, 0]);
+      circleat([d_x, q_z], ly_rc);
+    }
+  }
+
+  translate([0,0,q_z]){
+    for (my=[0,1])
+      mirror([0,my,0]){
+       translate([0, oec_y, 0]){
+         difference(){
+           translate(ly_re * [-1,0,-2])
+             cube(ly_re * [2,1,2]);
+           rotate_extrude(convexity=10)
+             LanyardEntryOuterProfile();
+         }
+       }
+      }
+    difference(){
+      translate([-ly_re, -(oec_y + 0.01), -2*ly_re])
+       cube([ly_re*2, 2*(oec_y + 0.01), 2*ly_re]);
+      for (mx=[0,1])
+       mirror([mx,0,0])
+         rotate([90,0,0])
+         translate([0,0,-10])
+         linear_extrude(height=20)
+         LanyardEntryOuterProfile();
+    }
+  }
+}
+
+module LanyardCutout(l){
+  rotate([0,-90,0])
+    linear_extrude(height=l)
+    rotate(-90)
+    LanyardMainChannelProfile();
+
+  for (ee=[0,1]){
+    translate(ee * l * [-1,0])
+      mirror([ee,0,0])
+      LanyardEntry();
+  }
+}
+
+module LidEdgeProfile(){
+  polygon([ lpp10,
+           lpp11,
+           lpp12,
+           lpp13,
+           lpp13 + [10, 0],
+           lpp15 + [10, 0],
+           lpp15,
+           lpp14,
+           ]);
+  intersection(){
+    circleat(lpp12, r=lp_r12);
+    rectfromto( lpp12 + [-10,   0],
+               lpp12 + [+10, +10] );
+  }
+}
+
+module LidEdgeFoldClearanceProfile(){
+  translate([-lid_fold_clearance_antislop, 0])
+    polygon([ lpp10,
+             lpp11,
+             lpp11 + [-20,  0],
+             lpp11 + [-20, 20],
+             lpp11 + [+20, 20],
+             lpp10 + [+20,  0] ]);
+}
+
+module ButtonCoverProfile(){
+  intersection(){
+    polygon(concat([ bppM, bppP, bppO, bppJ ],
+                  (enable_support && !$button_suppress_over_keeper
+                   ? [ bppU, bppV, bppW ] : []),
+                  [ bppL, bppK ]));
+    hull(){
+      EdgeProfile();
+      LidEdgeProfile();
+    }
+  }
+}
+
+module ButtonPlan(l, deep, cut){
+  epsilon =
+    (cut  ? 0 : lid_buttoncover_gap);
+
+  delta =
+    (deep ? lid_buttoncover_overlap : 0);
+
+  C = [0,0]; // by definition
+  T = [ 0, epp4[1] ];
+  G = T + [0,10];
+
+  B0 = C + [0,-1] * button_cutout_depth;
+  B1 = B0 + [0,1] * epsilon;
+
+  r0 = 0.5 * (T[1] - B0[1]);
+  A = [  -(l + button_l_fudge)/2 + r0, 0.5 * (T[1] + B0[1]) ];
+  H = A + [0,-1] * delta;
+
+  D = A + [-2,0] * r0;
+  F = D + [0,10];
+
+  E0 = 0.5 * (D + A);
+  E1 = E0 + [1,0] * epsilon;
+
+  I0 = [ E0[0], H[1] ];
+  I1 = [ E1[0], H[1] ];
+
+  hull(){
+    for (m=[0,1]) mirror([m,0])
+      circleat(H, r0 - epsilon);
+  }
+  for (m=[0,1]) mirror([m,0]) {
+    difference(){
+      polygon([ E1,
+               I1,
+               H,
+               B1,
+               G,
+               F,
+               D
+               ]);
+      circleat(D, r0 + epsilon);
+    }
+  }
+}
+
+module ButtonCoverReinf(){ ////toplevel
+  minkowski(){
+    rotate([90,0,0])
+      linear_extrude(height=0.01)
+      intersection(){
+        ButtonCoverProfile();
+       translate([bppJ[0] + 0.1, -50]) mirror([1,0])
+         square([100,100]);
+    }
+    mirror([0,0,1]) linear_extrude(height=0.01) intersection(){
+      circle(r= lid_buttoncover_reinf);
+      translate([-20,0]) square(40, center=true);
+    }
+  }
+}
+
+module ThumbRecessCutProfile(){
+  difference(){
+    polygon([ cppA + [-10,0],
+             cppB + [-10,0],
+             cppB,
+             cppA ]);
+    circleat(epp1, r=case_th_side);
+  }
+}
+
+module Flip_rhs(yn=[0,1]) {
+  for ($rhsflip=yn) {
+    translate([phone_width/2, 0, 0])
+      mirror([$rhsflip,0,0])
+      translate([-phone_width/2, 0, 0])
+      children();
+  }
+}
+
+module Flip_bot(yn=[0,1]) {
+  for ($botflip=yn) {
+    translate([0, -phone_height/2, 0])
+      mirror([0, $botflip, 0])
+      translate([0, phone_height/2, 0])
+      children();
+  }
+}  
+
+module AroundEdges(fill_zstart, fill_th, fill_downwards=0){
+  // sides
+  Flip_rhs(){
+    translate([0, -phone_cnr_rad, 0])
+      rotate([90,0,0])
+      linear_extrude(height = phone_height - phone_cnr_rad*2)
+      children(0);
+  }
+  // corners
+  Flip_rhs() Flip_bot() {
+    translate([+1,-1] * phone_cnr_rad)
+      intersection(){
+       rotate_extrude()
+         intersection(){
+           mirror([1,0,0])
+             translate([-1,0] * phone_cnr_rad)
+             children(0);
+           rectfromto([0,-20],[10,20]);
+         }
+       translate([-10, 0, -20] + 0.01 * [+1,-1, 0] )
+         cube([10,10,40]);
+      }
+  }
+  // top and bottom
+  Flip_bot(){
+    translate([ phone_width - phone_cnr_rad, 0,0 ])
+      rotate([90,0,-90])
+      linear_extrude(height = phone_width - phone_cnr_rad*2)
+      children(0);
+  }
+  // fill
+  translate([0,0, fill_zstart])
+    mirror([0,0, fill_downwards])
+    linear_extrude(height = fill_th)
+    rectfromto([+1,-1] * phone_cnr_rad,
+              [phone_width, -phone_height] + [-1,+1] * phone_cnr_rad);
+}
+
+module CaseAperture(pos, dia, $fn, topbottom=0) {
+  theta = 180/$fn;
+  translate([ bumper[0],
+             -epp2i[0],
+              0 ])
+    rotate([0,0, 90*topbottom])
+    translate([ pos[0] * (topbottom>0 ? -1 : +1), 0, -pos[1] ])
+    rotate([-90, theta, 0])
+    cylinder(r = dia/2 / cos(theta),
+            h = 60);
+}
+
+module SideButton(y, y_ref_sign, l, suppress_over_keeper=0){
+  // y_ref_sign:
+  //   +1  measured from top    of actual phone to top    of button
+  //   -1  measured from bottom of actual phone to bottom of button
+  //    0  y is centre of button in coordinate system
+  $button_l= l;
+  $button_suppress_over_keeper= suppress_over_keeper;
+  eff_y = y_ref_sign > 0 ?         -bumper [1] -y -l/2 :
+         y_ref_sign < 0 ? (-phone -bumper)[1] +y +l/2 :
+         y;
+  //echo(eff_y);
+  translate([0, eff_y, 0])
+    children();
+}
+
+module LidButtonishLeg(y, y_ref_sign, l=buttonishleg_default_l_is_fudge) {
+  $button_leg_only = true;
+  SideButton(y, y_ref_sign, l) children();
+}
+
+module Buttons(){
+  Flip_rhs(1) SideButton(30.320, +1, 22.960  ) children(); // volume
+  Flip_rhs(1) SideButton(64.220, +1, 14.500  ) children(); // power
+  Flip_rhs(1) LidButtonishLeg(14, -1) children();
+  Flip_rhs(0) LidButtonishLeg(21, -1) children();
+  Flip_rhs(0) LidButtonishLeg(38, +1) children();
+  Flip_rhs(0) LidButtonishLeg(14, +1) children();
+}
+
+module Struts(x_start, z_min, th){
+  // if th is negative, starts at z_min and works towards -ve z
+  // and object should then be printed other way up
+  for (i= [1 : 1 : case_struts_count]) {
+    translate([0,
+              0,
+              z_min])
+      mirror([0,0, th<0 ? 1 : 0])
+      translate([0,
+                -phone_height * i / (case_struts_count+1),
+                case_struts_solid_below])
+      linear_extrude(height= abs(th)
+                    -(case_struts_solid_below+case_struts_solid_above))
+      rectfromto([               x_start, -0.5 * case_struts_width ],
+                [ phone_width - x_start, +0.5 * case_struts_width ]);
+  }
+}
+
+module OrdinaryRearAperture(rhs,bot, pos){
+  Flip_rhs(rhs) Flip_bot(bot)
+    linextr(-20, 20)
+    mirror([0,1])
+    translate(pos + bumper)
+    children();
+}
+
+module MicroUSBEtc(){
+  Flip_bot(1){
+    rotate([90,0,0])
+      mirror([0,0,1])
+      linextr(-epp2i[0], 60)
+      translate([0.5 * phone_width, 0, 0])
+      rectfromto([-microusb_width/2, epp2i[1] + microusb_below],
+                [+microusb_width/2, epp0[1] + -microusb_above]);
+  }
+}
+
+module OrdinaryBottomEdgeApertures(){
+  Flip_bot(1)
+    CaseAperture(mainmic_pos, mainmic_dia, 8);
+
+  Flip_bot(1) Flip_rhs(1) {
+    linextr_y_xz(-epp2i[0], 60)
+      hull()
+      for (x= [-1,+1]) {
+       translate([ -bottomspeaker_pos[0], -bottomspeaker_pos[1] ] +
+                 [ 0.5 * x * bottomspeaker_size[0] - bottomspeaker_size[1],
+                   0 ])
+         rotate(360/16)
+         circle(r = bottomspeaker_size[1], $fn = 8);
+      }
+  }
+}
+
+module OrdinaryRearApertures(){
+  // rear speaker
+  //  OrdinaryRearAperture(1,1, rearspeaker_pos_bl)
+  //    rectfromto(-rearspeaker_gap,
+  //          rearspeaker_size + rearspeaker_gap);
+}
+
+module NotInTestFrameRearApertures(){
+  // finger hole to remove phone
+  if (len(fingerpushhole_dias))
+    OrdinaryRearAperture(0,0, [ fingerpushhole_dias[0] + epp2i[0],
+                               phone[1]/2 ])
+    scale(fingerpushhole_dias)
+    circle(r= 0.5 );
+}
+
+module RearCameraAperture(){
+  Flip_rhs(1)
+    mirror([0, 0, 1])
+    translate([0,0,0])
+    hull() // there is some kind of bug if hull() is done in 2D here!
+    linear_extrude(height = 20)
+    mirror([0, 1, 0])
+    translate(bumper)
+    translate(camera_pos_tl)
+    for (xy = [ [0,0], [0,1], [1,0] ]) {
+      translate(
+         camera_edge_rad * [1,1] +
+         xy * (camera_sz - camera_edge_rad * 2)
+               )
+       circle(r = camera_edge_rad);
+    }
+}
+
+module HingeLidProfile(){
+  hull(){
+    circleat(hppT, hp_r1);
+    circleat(lpp12, lp_r12);
+    polygon([lpp10,
+            lpp13 + [2,0],
+            lpp12,
+            hppT]);
+  }
+}
+
+module HingeBaseProfile(){
+  difference(){
+    hull(){
+      circleat(hppB, hp_r1);
+      circleat(hppE, hp_r1);
+      circleat(epp2o, case_th_bottom);
+      circleat(hppB + [10,0], hp_r1);
+    }
+    polygon([epp5, epp1, epp3, bppL]);
+  }
+}
+
+module HingeLeverOuterProfile(){
+  hull(){
+    circleat(hppT, hp_r2);
+    circleat(hppB, hp_r2);
+  }
+}
+
+module HingeLeverInnerProfile(){
+  for (s = [-1,+1]) {
+    c = s > 0 ? hppT : hppB;
+    translate(c)
+      mirror([0,0, s>0 ? 1 : 0])
+      rotate(s<0 ? -40 : 0)
+      hull()
+      for (x=[-20,20])
+       for (y=[0, s * 10])
+         translate([x,y])
+           circle(hp_rn);
+  }
+}
+
+module HingeLeverNutProfile(){
+  for (c= [hppB, hppT]) {
+    translate(c)
+      circle($fn=6, r= 0.5 * hingescrew_nut_across / cos(30));
+  }
+}
+
+module Flip_hinge(doflip=1){
+  hinge_origin = [0, -(phone_height - hppB[0]), hppB[1]];
+  translate(hinge_origin)
+    rotate([doflip*180,0,0])
+    translate(-hinge_origin)
+    children();
+}
+
+module HingePortion(x0,x1){
+  Flip_rhs() Flip_bot(1)
+    translate([x0,0,0])
+    mirror([1,0,0])
+    rotate([90,0,-90])
+    linear_extrude(height=x1-x0)
+    children();
+}
+
+module ThumbRecessApply(ztop){
+  width = thumbrecess_width;
+  w = width + thumbrecess_topcurve_r*2 + 1;
+  translate([phone_width/2, 0,0]){
+    difference(){
+      rotate([90,0,-90])
+       linextr(-w/2, w/2)
+       children(0);
+      translate([0, 50, 0])
+       rotate([90,0,0])
+       linear_extrude(height=100){
+       for (m=[0,1]) mirror([m,0,0]) {
+         hull(){
+           translate([w/2, ztop - thumbrecess_topcurve_r])
+             circle(thumbrecess_topcurve_r);
+           translate([w/2, -50])
+             square(thumbrecess_topcurve_r*2, center=true);
+         }
+       }
+      }
+    }
+  }
+}
+
+module CaseBase(){
+  AroundEdges(epp3[1], case_th_bottom, 1)
+    EdgeProfile();
+}
+
+function prop_x(gamma) = hp_k / (2 * sin(gamma/2)) - hppT[0];
+
+module PropProfileAssignments(gamma){
+  // https://en.wikipedia.org/wiki/Solution_of_triangles#Two_sides_and_the_included_angle_given_(SAS)
+  x = prop_x(gamma);
+  p = phone_height + prlp10[0] - hppB[0];
+  b = p + x;
+
+  q = phone_height - hppT[0] - prcp1[0]; // $prpp7[0] is 0 by definition
+  a = q + x;
+  c = sqrt(a*a + b*b - 2*a*b*cos(gamma));
+  $prp_alpha = acos( (b*b + c*c - a*a) / (2*b*c) );
+
+  $prp_theta = 90 - $prp_alpha;
+  beta = 180 - $prp_alpha - gamma;
+  psi = 90 - beta;
+
+  //echo("abc", a,b,c);
+
+  v1 = [ [ cos(psi), -sin(psi) ],    // x
+        [ sin(psi),  cos(psi) ] ];  // y
+
+  $prpp7 = [0, c + (lpp13[1] - $prpp10[1] - hp_k) ];
+
+  $prp_r1 = prc_r1;
+  $prp_r11 = prop_main_th/2;
+
+  $prpp1 = $prpp7 + [1,0] *
+    // this is approximate, but will do
+    (prop_main_th/2 + prop_prop_gap + prcp1[0] - cppA[0]);
+  $prpp3 = $prpp1 +
+    v1[0] * -$prp_r1 +
+    v1[1] * ((prcp2[1] - prcp1[1]) - prop_prop_gap);
+  $prpp12 = $prpp3 + v1[0] *
+    (prop_end_dia + prop_caserecess_taper * ($prpp1[1] - $prpp3[1]));
+  $prp_r8 = prop_main_th;
+  $prpp4 = [ prop_main_th/2, $prpp3[1] ];
+  $prp_r5 = $prp_r8;
+  $prpp5 = [ $prpp12[0] - $prp_r5,
+           $prpp3[1] - prop_prong_h + $prp_r5 ];
+  $prpp6 = $prpp4 + [0,-1] * (prop_prong_h +
+         prop_prong_heel_slope * ($prpp5[0] - $prpp4[0]));
+  $prpp8 = $prpp4 + [0,-1] * $prp_r8;
+  $prpp9 = $prpp8 + [-1,0] * $prp_r8;
+
+  children();
+}
+
+module PropProfile(gamma, cut=0, rot=0){
+  PropProfileAssignments(gamma){
+
+    //#circleat($prpp3,1);
+    //#circleat($prpp12,1);
+
+    if (!cut) {
+      hull(){
+       translate($prpp8)
+         intersection(){
+           circle($prp_r8);
+           polygon([[-20,-0], [20,20], [0,0]]);
+         }
+       rectfromto($prpp6, $prpp9);
+       translate($prpp5) intersection(){
+         circle($prp_r5);
+         polygon([[-10,-10], [0,0], [10,0]]);
+       }
+       rectfromto($prpp12 + [0,-0.1], $prpp3);
+      }
+      hull(){
+       circleat($prpp1, $prp_r1);
+       rectfromto($prpp12 + [0,-0.1], $prpp3);
+      }
+    }
+    // main shaft
+    rotate([0,0, rot*-$prp_theta]){
+      hull(){
+       extra = cut ? prop_recess_slop : 0;
+       rectfromto($prpp6, $prpp9);
+       circleat($prpp11, $prp_r11 + extra);
+       circleat($prpp10, $prp_r10 + extra);
+      }
+    }
+  }
+}
+
+module PropAggregateProfile(){
+  for (angle = prop_angles)
+    PropProfile(angle, 0,0);
+}
+
+module Prop(){ ////toplevel
+  hw = prop_main_width/2;
+  linextr(-hw, +hw)
+    PropAggregateProfile();
+}
+
+module Case(){ ////toplevel
+  difference(){
+    union(){
+      CaseBase();
+
+      // ledge (fixed keeper)
+      Flip_rhs(1-keeper_side) intersection(){
+       rotate([90, 0, 0])
+         linear_extrude(height = phone_height + phone_cnr_rad * 2)
+         KeeperProfile(fatter=0, slant=1);
+
+       // outline of the whole case, to stop it protruding
+       translate([0,0, -25])
+         linear_extrude(height = 50)
+         hull()
+         Flip_bot()
+         circleat([+1,-1] * phone_cnr_rad, phone_cnr_rad + case_th_side/2);
+      }
+
+      // hinge
+      if (!$suppress_hinge)
+       HingePortion(hex20, hex21) HingeBaseProfile();
+
+      // buildout for prop recess
+      if (prop_caserecess_buildout_r > 0) Flip_rhs(1)
+       linextr(case_bottom_z, epp2i[1])
+       hull() {
+         for (dxs = [-1,+1])
+           circleat([ prop_x_pos + dxs * prop_caserecess_buildout_r,
+                      -epp2o[0] ],
+                    r = epp2o[0] - prcp2[0]);
+        }
+    }
+
+    // slot for keeper
+    Flip_rhs(keeper_side)
+      translate([0, -phone_cnr_rad, 0])
+      rotate([90, 0, 0])
+      linear_extrude(height = phone_height + phone_cnr_rad * 2)
+      minkowski(){
+        KeeperProfile(fatter=keeper_fatter_hole);
+       rectfromto([ -keeper_gap_x,    -keeper_gap_z_bot ],
+                  [ keeper_gap_x_holes,    +keeper_gap_z_top ]);
+      }
+
+    // front camera
+    RearCameraAperture();
+
+    // struts (invisible, because they're buried in the case)
+    Struts(epp2i[0], epp2i[1] - case_th_bottom, case_th_bottom);
+
+    Buttons(){
+      mirror([1,0,0])
+       rotate([90,0,90]) {
+         if (!($button_leg_only && enable_support))
+         intersection(){
+           translate([0,0,-10])
+             linear_extrude(height= 20)
+             ButtonPlan($button_l, 0,1);
+           if ($button_leg_only)
+             rotate([-90,90,0])
+               translate([phone_width/2, -400, kppe[1]])
+               mirror([1-abs($rhsflip - keeper_side),0,0])
+               cube([400, 800, 50]);
+           if (enable_support && !$button_suppress_over_keeper)
+             rotate([-90,90,0])
+             translate([-400, -400, kppd[1]])
+               mirror([0,0,1])
+               cube([800,800,100]);
+         }
+         translate([0,0, -bppR[0]])
+           linear_extrude(height= 20)
+           ButtonPlan($button_l, 1,1);
+        }
+      
+    }
+
+    // apertures along top edge
+    if (!$suppress_forward_holes) {
+      // CaseAperture(jack_pos, jack_dia, 8);
+      Flip_rhs(1)
+       CaseAperture(noisecancelmic_pos, noisecancelmic_dia, 8);
+    }
+    CaseAperture(lhshole_pos, noisecancelmic_dia, 8, 1);
+
+    OrdinaryBottomEdgeApertures();
+
+    OrdinaryRearApertures();
+    NotInTestFrameRearApertures();
+
+    MicroUSBEtc();
+
+    // gaps for the lid's hinge arms
+    if (!$suppress_hinge) {
+      HingePortion(hex20 - hinge_x_arms_gap,
+                  hex21 + hinge_x_arms_gap)
+       minkowski(){
+        HingeLidProfile();
+       circle(r= hinge_r_arms_gap, $fn= 8);
+      }
+
+      // screw holes in the hinge arms
+      HingeScrews();
+    }
+
+    // thumb recess
+    ThumbRecessApply(epp4[1])
+      ThumbRecessCutProfile();
+
+    // lanyard
+    Flip_bot(1)
+      translate([ly_o[0], -(phone_cnr_rad + ly_re), ly_o[1]])
+      rotate([0, ly_theta, 0])
+      rotate([0,0,90])
+      LanyardCutout(lanyard_channel_len);
+
+    // prop recess
+    Flip_rhs(1)
+      translate([prop_x_pos,0,0])
+      mirror([0,1,0])
+      rotate([90,0,90])
+      linextr(-prop_recess_hw, +prop_recess_hw)
+      hull(){
+        for (d=[ [0,0], [0,-1], [+1,-1/prop_caserecess_taper] ])
+         circleat(prcp1 + 20*d,
+                  prc_r3);
+      }
+  }
+}
+
+module LidAdhocMultiprintFrame(phase){
+  if (led_window_style >= 3) {
+    AdhocMultiprintFrame(phase, lpp13[1], -1);
+  }
+}
+
+module LidAroundEdges(){
+  AroundEdges(lpp15[1], lpp13[1] - lpp15[1], 0)
+    children();
+}
+
+module Lid(){ ////toplevel
+  skew_centre = [0, lpp11[0], lpp11[1]];
+  difference(){
+    union(){
+      intersection(){
+       LidAroundEdges()
+         LidEdgeProfile();
+
+       translate(skew_centre)
+         multmatrix([[ 1, 0, 0, 0 ],
+                     [ 0, 1, -lid_fold_clearance_skew, 0 ],
+                     [ 0, 0, 1, 0 ],
+                     [ 0, 0, 0, 1 ]])
+         translate(-skew_centre)
+         LidAroundEdges()
+         LidEdgeFoldClearanceProfile();
+      }
+
+      // button covers
+      Buttons(){
+       intersection(){
+         rotate([90,0,90])
+           translate([0,0,-10])
+           linear_extrude(height= 20)
+           ButtonPlan($button_l, 1,0);
+         union(){
+           rotate([90,0,0])
+             translate([0,0,-100])
+             linear_extrude(height= 200)
+             ButtonCoverProfile();
+           hull()
+             for (y= [-1,+1] * (($button_l + button_l_fudge)/2
+                                - lid_buttoncover_reinf))
+               translate([0,y,0])
+                 ButtonCoverReinf();
+         }
+       }
+      }
+
+      // hinge arms
+      HingePortion(hex20, hex21) {
+       LidEdgeProfile();
+       HingeLidProfile();
+      }
+    }
+    Struts(lpp10[0] + strut_min_at_end, lpp13[1], -case_th_lid);
+
+    // screw holes in the hinge arms
+    HingeScrews();
+
+    // prop recess
+    translate([prop_x_pos, -prlp10[0], prlp10[1]])
+      mirror([0,1,0])
+      rotate([90,0,90])
+      linextr(-prop_recess_hw, +prop_recess_hw)
+      hull()
+      for (pa = prop_angles)
+       PropProfile(pa, 1,1);
+
+    // notification led aperture
+    if (led_window_style)
+      translate([led_pos[0], -led_pos[1], lpp13[1]]) {
+       translate([0,0,-10])
+         cylinder(r=nla_r0, h=20);
+       if (led_window_style >= 2)
+         translate([0,0, -nla_t])
+           cylinder(r=nla_r2, height=20);
+      }
+
+    }
+
+  LidAdhocMultiprintFrame(1);
+}
+
+module HingeLever(){ ////toplevel
+  difference() {
+    // outer body, positive
+    HingePortion(hex22, hex22 + phone_width/2)
+      HingeLeverOuterProfile();
+
+    // space for the screws
+//    HingePortion(hex26, hex24)
+//      HingeLeverInnerProfile();
+
+    // recesses for the nuts
+//    HingePortion(hex23, hex26+1)
+//      HingeLeverNutProfile();
+
+    // bores for the screws
+    HingeScrews();
+
+    // space for the charging cable and speaker and micc apertures
+    hull() {
+      for (x = [-1,+1]) {
+       multmatrix([[ 1,0,
+
+                    x
+                    * ( (hex24 + hinge_over_nut_plate) -
+                        (phone_width/2 - microusb_width/2)
+                       )
+                    / ( (epp0[1] - microusb_above)
+                        -
+                        (hppB[1] - hp_r2) ),
+
+                    x * (epp0[1] - microusb_above)
+
+                     ],
+                   [ 0,1,0, 0 ],
+                   [ 0,0,1, 0 ]]) {
+         union(){
+           MicroUSBEtc();
+           Flip_hinge() MicroUSBEtc();
+         }
+       }
+      }
+    }
+  }
+}
+
+module LidWindow(){ ////toplevel
+  translate([led_pos[0], -led_pos[1], lpp13[1]])
+    mirror([0,0,1])
+    cylinder(r= nla_r1, h=nla_t);
+  LidAdhocMultiprintFrame(0);
+}
+
+module LidWindowPrint(){ ////toplevel
+  rotate([0,180,0])
+    LidWindow();
+}
+
+module DemoLidWindowSelect(){
+  translate([led_pos[0], led_pos[1], -100]) {
+    translate([0, -30, 0]) cube([400, 400, 200]);
+  }
+}
+
+module DemoLidWindow(){ ////toplevel
+  %Lid();
+  LidWindow();
+  translate([0,40,0]){
+    color("blue") intersection(){ Lid(); DemoLidWindowSelect(); }
+    color("red") intersection(){ LidWindow(); DemoLidWindowSelect(); }
+  }
+}
+
+module HingeLeverPrint(){ ////toplevel
+  rotate([-90,0,0])
+    translate([-phone_width/2, phone_height, 0])
+    HingeLever();
+}
+
+module TestSelectLength(){
+  translate([-30, -200, -20])
+    cube([30 + 15, 250, 40]);
+}
+
+module TestLength(){ ////toplevel
+  intersection(){
+    Case();
+    TestSelectLength();
+  }
+}
+
+module TestLengthRight(){ ////toplevel
+  intersection(){
+    Case();
+    Flip_rhs(1)
+      TestSelectLength();
+  }
+}
+
+module TestSelectWidth(){
+  translate([-30, -(phone_height - 25), -20])
+    mirror([0, 1, 0])
+    cube([200, 50, 40]);
+}
+
+module TestWidth(){ ////toplevel
+  intersection(){
+    Case();
+    TestSelectWidth();
+  }
+}
+
+module TestLidWidthPrint(){ ////toplevel
+  rotate([0,180.0]) intersection(){
+    Lid();
+    TestSelectWidth();
+  }
+}
+
+module TestSelectRearAperture(){
+  minkowski(){
+    union() children();
+    translate([20, 0,0])
+      cube([42, 2, 1], center=true);
+  }
+}
+
+module TestSelectCamera(){
+  minkowski(){
+    TestSelectRearAperture()
+      RearCameraAperture();
+    cube([0.1, 50, 0.1]);
+  }
+}
+
+module TestSelectOrdinaryRearApertures(){
+  TestSelectRearAperture()
+    OrdinaryRearApertures();
+}
+
+module TestCamera(){ ////toplevel
+  intersection(){
+    Case();
+    TestSelectCamera();
+  }
+}
+
+module TestLidByCamera(){ ////toplevel
+  intersection(){
+    Lid();
+    TestSelectCamera();
+  }
+}
+
+module TestLidByCameraPrint(){ ////toplevel
+  rotate([180,0,0]) TestLidByCamera();
+}
+
+module DemoByCamera(){ ////toplevel
+  color("blue") TestLidByCamera();
+  color("red")  TestCamera();
+}
+
+module OneKeeper(){ ////toplevel
+  translate([0, -phone_cnr_rad, 0])
+    rotate([90, 0, 0])
+    linear_extrude(height = phone_height - phone_cnr_rad * 2)
+    KeeperProfile(fatter=keeper_fatter, stubbier=keeper_stubbier);
+}
+
+module OneKeeperPrint(){ ////toplevel
+  rotate([0,180,0])
+    OneKeeper();
+}
+
+module LidPrint(){ ////toplevel
+  rotate([0,180,0])
+    Lid();
+}
+
+module TestSelectFrame(){
+  include = [1,-1] * (epp2i[0] + 4);
+
+  difference(){
+    cube(1000, center=true);
+    translate([0,0, -100])
+      linear_extrude(height=200)
+      rectfromto(include,  inside_br - include);
+  }
+
+  for (i= [1,2]) {
+    translate([ 0, -phone[1] * i/3, 0 ])
+      cube(center=true, [1000, 4, 100]);
+  }
+}
+
+module TestSelectLidFrame(){
+  TestSelectFrame();
+  if (len(led_pos))
+    translate([led_pos[0], -led_pos[1], -50])
+    cylinder(r= nla_r2+3, h=100);
+}
+
+module TestFrameCase(){ ////toplevel
+  intersection(){
+    Case();
+    union(){
+      TestSelectFrame();
+      TestSelectCamera();
+      TestSelectOrdinaryRearApertures();
+    }
+  }
+}
+
+module TestSelectTopApertures(){
+  translate([-100, -35, -100])
+    cube([400, 100, 200]);
+  LidAdhocMultiprintFrame(0);
+  LidAdhocMultiprintFrame(1);
+}
+
+module TestTopApertures(){ ////toplevel
+  intersection(){
+    Case();
+    TestSelectFrame();
+    TestSelectTopApertures();
+  }
+}
+
+module TestLidTopAperturesPrint(){ ////toplevel
+  rotate([0,180,0]) intersection(){
+    Lid();
+    TestSelectLidFrame();
+    TestSelectTopApertures();
+  }
+}
+
+module TestLidWindowTopAperturesPrint(){ ////toplevel
+  rotate([0,180,0]) intersection(){
+    LidWindow();
+    TestSelectTopApertures();
+  }
+}
+
+module TestFrameLidPrint(){ ////toplevel
+  rotate([0,180,0]) intersection(){
+    Lid();
+    TestSelectLidFrame();
+  }
+}
+
+module ButtonPlanForDemo(z, deep, cut){
+  translate([0,0,z])
+    ButtonPlan(8, deep, cut);
+}
+
+module HingeScrews(){
+  Flip_rhs() Flip_bot(1){
+    for (c= [ hppT, hppB ])
+      translate([ hex20,
+                 -c[0],
+                 c[1] ]){
+       rotate([0,90,0])
+         translate([0,0,-.2])
+         cylinder( r= hingescrew_shaft_dia/2,
+                   h = hingescrew_shaft_len+0.2 );
+       rotate([0,-90,0])
+         translate([0,0,+.1])
+         cylinder( r= hingescrew_head_dia/2, h = hingescrew_head_th );
+      }
+  }
+}
+
+module DemoPropAngleSelect(c){
+  color(c) difference(){
+    union(){ children(); }
+    translate([ prop_x_pos, -400, -200 ])
+      cube([ 400,800,400 ]);
+  }
+}
+
+module DemoPropAngle(ang){
+  hL = [0, -(phone_height - hppT[0]), hppT[1] - hp_k*2];
+  hC = [0, -(phone_height - hppB[0]), hppB[1]];
+
+  translate(hL)
+    rotate([ang/2,0,0])
+    translate(-hL)
+    translate(hC)
+    rotate([ang/2,0,0])
+    translate(-hC) {
+      DemoPropAngleSelect("red") Case();
+
+      color("orange")
+       translate([prop_x_pos, -prcp1[0], prcp1[1]])
+       PropProfileAssignments(ang) {
+          echo($prpp1);
+         rotate([-$prp_theta, 0, 0])
+         translate([0, $prpp1[0], -$prpp1[1]])
+         rotate([90,0,-90])
+         Prop();
+        }
+    }
+
+  translate([0,0, -hp_k*2])
+    DemoPropAngleSelect("blue")
+    Lid();
+}
+
+module DemoPropAngles(){ ////toplevel
+  for (i=[0 : len(prop_angles)-1])
+    translate(i * [0, -100, 100])
+    DemoPropAngle(prop_angles[i]);
+}
+
+module DemoHingeAngle(ang1,ang2){
+  hL = [0, -(phone_height - hppT[0]), hppT[1]];
+  hC = [0, -(phone_height - hppB[0]), hppB[1]];
+
+  translate(hL)
+    rotate([ang2,0,0])
+    translate(-hL)
+    translate(hC)
+    rotate([ang1,0,0])
+    translate(-hC) {
+      color("red") Lid();
+    }
+
+  color("blue") intersection(){
+    Case();
+    union(){
+      translate([bppJ[0], -400, -200])
+       mirror([1,0,0])
+       cube([400, 800, 400]);
+      translate([10, -400, -200])
+       cube([10, 800, 400]);
+    }
+  }
+}
+
+module DemoHingeAngles(){ ////toplevel
+  angles = [ 0, 4, 8, 12 ];
+  echo("angles",angles);
+  for (i=[0 : len(angles)-1]) {
+    translate(i * [0, 0, 30]) {
+      DemoHingeAngle(0,angles[i]);
+      translate([0, 200, 0])
+       DemoHingeAngle(angles[i],0);
+    }
+  }
+}
+
+module DemoSelectAdhocLeftRight(right=0) {
+  translate([phone_width/2, -400, -100]) // , -15, -100  to cross-section
+    mirror([1-right, 0,0])
+    cube([400, 800, 200]);
+}
+
+module DemoLeft(){ ////toplevel
+  color("red")  intersection(){ Case(); DemoSelectAdhocLeftRight(); }
+  color("blue") intersection(){ Lid();  DemoSelectAdhocLeftRight(); }
+}
+
+module DemoFrame(){ ////toplevel
+  color("red") render() TestFrameCase();
+  color("blue") render() intersection(){ Lid(); TestSelectLidFrame(); }
+  color("black") render() HingeScrews();
+  %render() HingeLever();
+}
+
+module DemoLanyardCutout(){ ////toplevel
+  LanyardCutout(25);
+}
+
+module DemoHingedFrame(){ ///toplevel
+  color("red") render() TestFrameCase();
+  translate([0,0, -2*hp_k])
+  color("blue") render() intersection(){ Lid(); TestSelectLidFrame(); }
+
+  Flip_hinge(){
+    color("orange") render() HingeLever();
+    color("black") render() HingeScrews();
+  }
+}
+
+module DemoHinge(){ ////toplevel
+  translate([ -0.5*phone_width, phone_height, hp_k*3 ]) {
+    DemoFrame();
+    translate([0,0, -hp_k*3])
+      DemoHingedFrame();
+  }
+}
+
+module DemoProfiles(){ ////toplevel
+  LidEdgeProfile();
+  %EdgeProfile();
+  KeeperProfile();
+  translate([0,0,-1]) color("black") KeeperProfile(1);
+  translate(ly_o){
+    rotate(-ly_theta){
+      translate([0,0,+1]) color("purple") LanyardMainChannelProfile();
+      translate([0,0,+2]) color("red") LanyardCurveChannelProfile();
+      translate([0, ly_q_z]){
+       translate([0,0,-1]) color("blue") LanyardEntryChannelProfile();
+       translate([ly_oec_y,0,-2]) color("black") LanyardEntryOuterProfile();
+      }
+    }
+  }
+  translate([0,0,-5]) color("white") translate(epp2i)
+    rotate(-ly_theta)
+    rectfromto([-15, 0],
+              [+15, -max_case_bottom_edge_thickness]);
+
+  translate([0,20]) {
+    LanyardMainChannelProfile();
+    translate([0,0,1]) color("purple") LanyardCurveChannelProfile();
+    translate([0,0,-1]) color("red") LanyardEntryChannelProfile();
+  }
+
+  translate([20,0]) {
+    LidEdgeProfile();
+    %EdgeProfile();
+
+    demopoint_QR = [ bppS[0], bppQ[1] - 0.1];
+  
+    color("blue") ButtonCoverProfile();
+    color("red") {
+      rectfromto(bppQ, demopoint_QR);
+      rectfromto(bppR, demopoint_QR);
+    }
+  }
+
+  translate([-20,0]) {
+    color("black") ButtonPlanForDemo(-2, 0,1);
+    color("red" )  ButtonPlanForDemo(-4, 1,1);
+    color("blue")  ButtonPlanForDemo(-6, 1,0);
+  }
+
+  translate([0, -30]) {
+    %LidEdgeProfile();
+    %EdgeProfile();
+    color("blue") HingeLidProfile();
+    color("red")  HingeBaseProfile();
+    color("black") translate([0,0,-2]) HingeLeverOuterProfile();
+  }
+
+  for (f=[0,1]) {
+    translate([-30, -60 + 30*f]) {
+      translate([0,0,-4]) EdgeProfile();
+      %translate([0,0,-10]) HingeBaseProfile();
+      translate([0,-2] * f * hp_k) {
+       translate([0,0,-4]) LidEdgeProfile();
+       %translate([0,0,-10]) %HingeLidProfile();
+      }
+      translate(+hppB) rotate([0,0,180*f]) translate(-hppB) {
+       translate([0,0,-2]) color("black") HingeLeverOuterProfile(); 
+       translate([0,0,0]) color("red") difference(){
+         HingeLeverOuterProfile();
+         HingeLeverInnerProfile();
+       }
+       translate([0,0,3]) color("yellow") HingeLeverNutProfile();
+      }
+    }
+  }
+
+  translate([20,-30]) {
+    %EdgeProfile();
+    %LidEdgeProfile();
+    //translate([0,0,1]) ThumbRecessCutProfile();
+    translate([0,0,+1]) color("red")
+      difference(){ EdgeProfile(); ThumbRecessCutProfile(); }
+  }
+
+  translate([40,-30]) {
+    difference(){
+      LidEdgeProfile();
+      translate(prlp10)
+       PropProfile(10, 1, 0);
+    }
+    translate(prlp10)
+      PropProfile(15, 0);
+  }
+  translate([60,-30]) {
+    PropAggregateProfile();
+  }
+}
+
+//EdgeProfile();
+//KeeperProfile();
+//CaseBase();
+//%Case();
+//Keeper();
+//LidEdgeProfile();
+//KeeperProfile();
+//DemoProfiles();
+//PropRecess();
diff --git a/filament-test.scad b/filament-test.scad
new file mode 100644 (file)
index 0000000..443aab5
--- /dev/null
@@ -0,0 +1,16 @@
+// -*- C -*-
+translate([3,3,0]) mirror([1,1,0]) cube([15,15,1]);
+
+multmatrix([[  1,      0,      0,      0       ],
+       [       0,      1,      1.0,    0       ],
+       [       0,      0,      1,      0       ],
+       [       0,      0,      0,      1       ]])
+ cylinder(r=6.1/2, h=8);
+
+w=0.5;
+
+translate([15,0])
+difference(){
+  cube([8,8,8]);
+  translate([w,w,-1]) cube([8-w*2, 8-w*2, 8+2]);
+}
diff --git a/filamentclip.scad b/filamentclip.scad
new file mode 100644 (file)
index 0000000..e6d7afb
--- /dev/null
@@ -0,0 +1,53 @@
+include <cliphook.scad>
+include <filamentteeth.scad>
+
+rad=19;
+h=3.5;
+w=2.5;
+
+looprad=2.5;
+loopw=w;
+
+fdia=1.77;
+//fdia=3;
+
+d=0.01;
+
+module our_ClipHook(ye){
+  ClipHook(h=h, w=w, g=0.6, k=1.5, g=0.6, ye=ye, cupcaph=0.5, cupcapg=0.8);
+}
+
+module FilamentClip() {
+  rotate([0,0,-70]) {
+    translate([0,rad-1.5,0]) {
+      rotate([0,0,8])
+       our_ClipHook(ye=-1.3);
+    }
+  }
+
+  rotate([0,0,-35]) {
+    translate([0,rad,0]) {
+      rotate([0,0,180])
+       our_ClipHook(ye=0.8);
+    }
+  }
+
+  linear_extrude(height=h) {
+    assign($fn=80) {
+      FlatArc(0,0, rad-w/2,rad+w/2, 80,350);
+    }
+    assign($fn=30) {
+      FlatArc(0,rad+looprad+w, looprad,looprad+loopw);
+    }
+  }
+
+  for (mir=[0,1]) {
+    mirror([mir,0,0])
+      rotate([0,0,-40])
+      translate([rad+w*0.3+teethw*0.3+fdia/2, 0, 0])
+      rotate([0,0,95])
+      FilamentTeeth(fdia=fdia, h=h);
+  }
+}
+
+FilamentClip();
diff --git a/filamentspool-lt.scad b/filamentspool-lt.scad
new file mode 100644 (file)
index 0000000..3605f00
--- /dev/null
@@ -0,0 +1,4 @@
+//// toplevels-from:
+include <filamentspool.scad>
+lightduty = true;
+fdia = 2.85;
diff --git a/filamentspool-number.eps.pl b/filamentspool-number.eps.pl
new file mode 100755 (executable)
index 0000000..c05903a
--- /dev/null
@@ -0,0 +1,14 @@
+#!/usr/bin/perl -w
+use strict;
+die unless @ARGV==1 && $ARGV[0] =~ m/^\d+/;
+my $num = $ARGV[0];
+$num /= 1000;
+printf <<END, $num or die $!;
+%%!
+/Helvetica-Bold findfont
+15 scalefont
+setfont
+0 0 moveto
+(%.1f) show
+showpage
+END
diff --git a/filamentspool-sm.scad b/filamentspool-sm.scad
new file mode 100644 (file)
index 0000000..caaf0e4
--- /dev/null
@@ -0,0 +1,4 @@
+//// toplevels-from:
+include <filamentspool.scad>
+lightduty = true;
+fdia = 1.75;
diff --git a/filamentspool-storarm3.scad b/filamentspool-storarm3.scad
new file mode 100644 (file)
index 0000000..31dbaa9
--- /dev/null
@@ -0,0 +1,7 @@
+// -*- C -*-
+include <filamentspool.scad>
+lightduty = false;
+fdia = 2.85;
+storarm_spools=3;
+//// module StorageArmLeft ////toplevel
+//// module StorageArmRight ////toplevel
diff --git a/filamentspool.scad b/filamentspool.scad
new file mode 100644 (file)
index 0000000..b2991ea
--- /dev/null
@@ -0,0 +1,1025 @@
+// -*- C -*-
+
+// filamentspool.scad
+// 3D design for filament spools to hold coils as supplied by Faberdashery
+//
+
+//
+// Copyright 2012,2013,2016 Ian Jackson
+//
+// This work is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This work is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this work.  If not, see <http://www.gnu.org/licenses/>
+//
+
+//
+// Each spool is a hub with 3 or 4 arms.  Each arm has a cup for
+// holding the filament.  The effective diameter can be adjusted by
+// setting the cup into a different seat in the arm.  The cups are
+// held on with simple clips, so the filement coil can easily be
+// removed and replaced.
+//
+// This file (and its includes) can generate:
+//
+// ===== Heavy duty 4-armed spool for 3mm x 100m coil =====
+//
+// A heavy duty 4-armed spool suitable for holding a 100m
+// Faberdashery coil on the spool arm of a Lulzbot TAZ-5.
+//
+//     Set
+//           fdia=2.85
+//           lightduty=false
+//     And print following parts
+//            Hub
+//            ArmEnd x 4
+//            FilamentCup x 4  (or FilamentCupPair x 2)
+//            CupSecuringClip x 4
+//
+//     You will also need  4 x M4 machine screws and nuts.
+//
+//     This is the default.
+//
+// ===== Light duty 3-armed spool for 3mm x <=30m coil =====
+//
+// A light duty 3-armed spool suitable for up to around 30m
+// of Faberdashery 2.85mm PLA.
+//
+//     Set
+//           fdia=2.85
+//           lightduty=true
+//     (or look in filamentspool-lt.scad).
+//
+//     And print following parts
+//           Hub
+//           ArmEnd x 3
+//           FilamentCup x 3  (or FilamentCup + FilamentCupPair)
+//           CupSecuringClip x 3
+//           TowerDoveClipPin x 6
+//
+//     When assembling, insert one TowerDoveClipPin from each side,
+//     joining each ArmEnd to the Hub with two TowerDoveClipPins.
+//     Modest force with pliers is good to seat them properly.
+//
+//     (note that the light duty and heavy duty CupSecuringClips
+//      are slightly different)
+//
+// ===== Notes regarding both the above spools =====
+//
+// When mounting either spool on the TAZ-5 spool arm, put the `pointy'
+// end of the hub towards the printer - ie, put put the spool on
+// `backwards'.  This ensures that the spool's arms will clear the
+// printer framework.
+//
+// For the above, I generally used the Cura `Standard' PLA profile.
+//
+// ===== TAZ-5 feed tube adjustment kit =====
+//
+// With a TAZ-5 I recommend using this kit to improve the feed
+// reliability:
+//
+//       Set
+//           fdia=2.85
+//     And print following parts
+//           FilamentGuideSpacer (ideally, at `high detail')
+//           FilamentGuideArmPrint (optional; `high detail' or `standard')
+//
+//     And possibly also
+//           t-nut_jig_0.2.stl
+//     from Aleph Objects - look here:
+//           http://download.lulzbot.com/TAZ/accessories/tool_heads/version_2/Dual_Extruder_v2/production_parts/stl/
+//
+// The spacer clips onto the filament guide tube holder arm, on the
+// inside, with the pointy flanged end towards the filament guide
+// tube.  It stops the filament guide tube angle (and so the
+// filament's natural pickup location) changing as the print head moves.
+//
+// The FilamentGuideArm[Print] is a replacement for the arm supplied
+// with your TAZ-5.  It's longer, so that the filament pickup point is
+// closer to the middle of the coil.  Use the t-nut_jig to stop the
+// T-nuts in the aluminium channel from annoyingly sliding down to the
+// bottom while you swap out the arm.
+//
+// (Faberdashery coils, and therefore both the above spools, have a
+// larger diameter than the flat-walled spools often supplied by other
+// vendors.  And the spools above have individual arms rather than a
+// continuous disc.  If the filament `unhooks' from the arm, it can
+// pull taught around the hub and stop feeding properly.)
+//
+// ===== Spool storage arm, for mounting on walls =====
+//
+// A storage arm suitable for screwing to walls, bookshelves,
+// etc. (requires non-countersunk M4 screws); will hold two heavy duty
+// spools each with a 100m coil.
+//
+//     Set
+//           fdia=2.85
+//           lightduty=false
+//     And print one of these, according to taste
+//            StorageArmLeft
+//            StorageArmRight
+//
+//     NB that the `light duty' version of this is shorter and
+//     will only take two `light duty' spools.
+//
+// A longer arm for three spools is also available:
+//     Set
+//           fdia=2.85
+//           lightduty=false
+//           storarm_spools=3
+//     (or look in filamentspool-storarm3.scad).
+//
+//     And print one of these, according to taste
+//            StorageArmLeft
+//            StorageArmRight
+//
+// For all of these, I used the Cura `High detail' PLA profile because
+// I wanted it pretty, but the `Standard' profile should do fine.
+//
+// ===== Spools for 1.75mm filament =====
+//
+// Spool (in many parts) for handing 1.75mm filament, printable
+// on, and with parts for mounting on, a Reprappro Huxley.
+
+
+fdia=2.85; // or 1.75
+lightduty=false; // or true
+
+
+slop=0.5;
+bigslop=slop*2;
+
+function selsz(sm,lt,lg) = fdia < 2 ? sm : lightduty ? lt : lg;
+function usedove() = selsz(true,true,false);
+
+num_arms = selsz(3,3,4);
+
+channelslop=selsz(slop,0.75,slop);
+
+exteffrad = 70;
+hubeffrad = selsz(30, 52, 40);
+hubbigrad = selsz(20, 38, 38);
+hublwidth = selsz(3, 2.5, 3.0);
+hubstemwidth = 2;
+hublthick = 10;
+hubaxlerad = selsz(5, 28/2, 28/2);
+totalheightfromtower = 240;
+axletowerfudgebend = 0;
+axleaxlefudgebend = 3;
+axlepadlen = 1.0;
+
+armend_length = selsz(120, 150, 120);
+
+prongthick=selsz(5,4,5);
+prongwidth=selsz(5,4,5);
+prongribwidth=3;
+prongribheight=selsz(0,0,4);
+ratchetstep=10;
+ratchettooth=3;
+ratchettoothheight=5;
+ratchettoothsmoothr=1;
+ratchettoothslope=0.75;
+overlap=0.5;
+cupwidth=selsz(40,25,50);
+cupheight=selsz(75,35,75);
+
+cupstrong_dx=selsz(0,0,-10);
+
+propxshift = -6;
+
+doveclipheight = 10;
+
+teethh=3;
+teethgapx=4+fdia;
+
+prongstalkxwidth=3;
+
+stalklength=selsz(35,25,55);
+overclipcupgap=5;
+overclipdepth=15;
+overcliproundr=2.0;
+overclipthick=1.0;
+overclipcupnextgap=selsz(20,15,20);
+
+hubaxlelen = selsz(25, 62.5, 77.5);
+echo(hubaxlelen);
+
+overclipsmaller=0.5;
+overclipbigger=2.5;
+
+wingspoke=2.5;
+wingsize=6;
+wingthick=3;
+
+armendwallthick=selsz(2.5, 1.8, 2.5);
+armendbasethick=selsz(1.2, 1.2, 1.2);
+
+numbers_relief = 0.7;
+numbers_tick_len = 8;
+numbers_tick_width = 0.75;
+numbers_tick_linespc = 1.0;
+numbers_height_allow = 8;
+
+axlehorizoffset = 12.5;
+axlevertheight = 100;
+towercliph = 16;
+towerclipcount = 3;
+towerpillarw = 5;
+
+axlepinrad = 2;
+axlepintabrad = 5;
+
+washerthick = 1.2;
+washerthinthick = 0.8;
+washerverythinthick = 0.4;
+washerrad = hubaxlerad + 7.5;
+frictionwasherarmwidth = 3;
+frictionwasherextrapush = 1.0;
+
+ratchetpawl=ratchetstep-ratchettooth-bigslop*2;
+
+nondove_armhole_x = 32;
+nondove_armhole_hole = 4 + 0.8;
+nondove_armhole_support = 7;
+nondove_armhole_wall = 3.2;
+nondove_armhole_slop = 0.5;
+nondove_armhole_slop_x = 0.5;
+
+nondove_armbase = nondove_armhole_x + nondove_armhole_hole/2 +
+  nondove_armhole_support;
+echo(nondove_armbase);
+
+include <doveclip.scad>
+include <cliphook.scad>
+include <filamentteeth.scad>
+include <axlepin.scad>
+include <commitid.scad>
+
+hub_clip_baseextend = (hubeffrad - DoveClip_depth()
+                      - hubbigrad + hublwidth);
+
+real_exteffrad = selsz(exteffrad + hub_clip_baseextend,
+                      hubeffrad + DoveClip_depth(),
+                      hubeffrad + nondove_armbase);
+
+channelwidth = prongthick + channelslop;
+channeldepth = prongwidth + ratchettoothheight;
+totalwidth = armendwallthick*2 + channelwidth;
+totalheight = channeldepth + armendbasethick;
+stalkwidth = prongwidth + prongstalkxwidth;
+
+tau = PI*2;
+
+module ArmEnd(length=armend_length){ ////toplevel
+  if (usedove()) {
+    translate([ratchettoothsmoothr, channelwidth/2, -armendbasethick]) {
+      rotate([0,0,-90])
+       DoveClipPairBase(h=doveclipheight);
+    }
+  } else {
+    difference(){
+      translate([1, -armendwallthick, -armendbasethick])
+       mirror([1,0,0])
+       cube([nondove_armbase+1, totalwidth, totalheight]);
+      translate([-nondove_armbase + nondove_armhole_x,
+                -armendwallthick + totalwidth/2,
+                -armendbasethick -1])
+       cylinder(r= nondove_armhole_hole/2, h=totalheight+2, $fn=10);
+      translate([-nondove_armbase, -armendwallthick, -armendbasethick])
+        rotate([90,0,0])
+       Commitid_BestCount([nondove_armbase, totalwidth]);
+    }
+  }
+
+  difference(){
+    union(){
+      difference(){
+       translate([0, -armendwallthick, -armendbasethick])
+         cube([length, totalwidth, totalheight]);
+       translate([-1, 0, 0])
+         cube([length+1 - ratchettooth, channelwidth, channeldepth+1]);
+       translate([-1, 0, ratchettoothheight])
+         cube([length+2, channelwidth, channeldepth+1]);
+      }
+      for (dx = [0 : ratchetstep : length - ratchetstep]) translate([dx,0,0]) {
+       translate([ratchettoothsmoothr+0.5, armendwallthick/2, 0]) minkowski(){
+         rotate([90,0,0])
+           cylinder($fn=20, r=ratchettoothsmoothr, h=armendwallthick);
+         multmatrix([  [       1, 0, ratchettoothslope, 0      ],
+                           [   0,      1,      0,      0       ],
+                           [   0,      0,      1,      0       ],
+                           [   0,      0,      0,      1       ]])
+           cube([ratchettooth - ratchettoothsmoothr*2,
+                 channelwidth, ratchettoothheight - ratchettoothsmoothr]);
+       }
+      }
+    }
+
+    for (otherside=[0,1]) {
+      for (circum = [300:100:1500]) {
+       assign(rad = circum / tau)
+         assign(fn = str("filamentspool-number-n",circum,".dxf"))
+         assign(rotateoffset = [0, totalwidth/2, 0])
+         assign(xlen = rad - real_exteffrad) {
+         if (xlen >= numbers_tick_width/2
+             + (otherside ? numbers_height_allow : 0) &&
+             xlen <= length - (otherside ? 0 : numbers_height_allow))
+           translate([xlen, -armendwallthick,
+                      -armendbasethick + (totalheight - numbers_tick_len)/2])
+           translate(rotateoffset)
+           rotate([0,0, otherside*180])
+           translate(-rotateoffset){
+             translate([-numbers_tick_width/2, -1, 0])
+               cube([numbers_tick_width, numbers_relief+1, numbers_tick_len]);
+             translate([numbers_tick_width/2 + numbers_tick_linespc,
+                        1,
+                        numbers_tick_len])
+               rotate([90,0,0])
+               rotate([0,0,-90])
+               linear_extrude(height= numbers_relief+1)
+               //    scale(templatescale)
+               import(file=fn, convexity=100);
+           }
+       }
+      }
+    }
+
+    if (usedove()){
+      translate([0, -armendwallthick, -armendbasethick])
+       Commitid_BestCount_M([length/3, totalwidth]);
+    }
+  }
+}
+
+module FilamentCupHandle(){
+  pawlusewidth = ratchetpawl-ratchettoothsmoothr*2;
+  mirror([0,1,0]) {
+    cube([stalklength, stalkwidth, prongthick]);
+    translate([stalklength, stalkwidth/2, 0])
+      cylinder(r=stalkwidth/2, h=prongthick, $fn=20);
+    translate([ratchettoothsmoothr, stalkwidth, 0]) {
+      minkowski(){
+       cylinder($fn=20,r=ratchettoothsmoothr, h=1);
+       multmatrix([    [       1, -ratchettoothslope, 0, 0     ],
+                       [       0,      1,      0,      0       ],
+                       [       0,      0,      1,      0       ],
+                       [       0,      0,      0,      1       ]])
+         cube([pawlusewidth,
+               ratchettoothheight - ratchettoothsmoothr,
+               prongthick - 1]);
+      }
+    }
+  }
+}
+
+module FilamentCupCup(){
+  for (my=[0,1]) mirror([0,my,0]) {
+    translate([0, cupwidth/2, 0])
+      cube([cupheight + prongwidth, prongwidth, prongthick]);
+  }
+}
+
+module FilamentCupPositive() {
+  FilamentCupHandle();
+
+  gapy = prongwidth;
+  dy = cupwidth/2 + gapy + overclipcupgap;
+  baselen = dy+cupwidth/2;
+
+  translate([0, dy, 0])
+    FilamentCupCup();
+  cube([prongwidth, baselen+1, prongthick]);
+
+  translate([cupstrong_dx, prongwidth, 0]) {
+    cube([prongwidth, baselen-prongwidth, prongthick]);
+    for (y = [0, .33, .67, 1])
+      translate([0, (baselen - prongwidth) * y, 0])
+       cube([-cupstrong_dx + 1, prongwidth, prongthick]);
+  }
+  if (cupstrong_dx != 0) {
+    rotate([0,0,45])
+      translate([-prongwidth*.55, -prongwidth*2.1, 0])
+      cube([prongwidth*(2.65), prongwidth*4.2, prongthick]);
+  }
+
+  translate([0, -0.2, 0])
+    cube([prongribwidth, baselen, prongthick + prongribheight]);
+
+  if (prongribheight > 0) {
+    translate([-prongwidth, baselen, 0])
+      cube([cupheight/2, prongwidth + prongribheight, prongribwidth]);
+  }
+
+  midrad = cupwidth/2 + prongwidth/2;
+
+  propshift = stalklength - overclipdepth - prongthick + propxshift;
+  proptaken = propshift;
+  echo(midrad, propshift, proptaken);
+
+  translate([propshift, -1, 0]) {
+    // something is wrong with the y calculation
+    cube([prongwidth,
+         gapy+2,
+         prongthick]);
+  }
+  for (y = [overclipcupgap, overclipcupgap+overclipcupnextgap]) {
+    translate([cupstrong_dx, y + prongwidth, 0])
+      rotate([0,0, 102 + fdia])
+      FilamentTeeth(fdia=fdia, h=teethh);
+  }
+  for (x = [-0.3, -1.3]) {
+    translate([cupheight + overclipcupnextgap*x, baselen + prongwidth, 0])
+      rotate([0,0, 12 + fdia])
+      FilamentTeeth(fdia=fdia, h=teethh);
+  }      
+}
+
+module FilamentCup() { ////toplevel
+  difference(){
+    FilamentCupPositive();
+    translate([0, -stalkwidth, 0])
+      Commitid_BestCount_M([stalklength - stalkwidth, stalkwidth]);
+  }
+}
+
+module CupSecuringClipSolid(w,d,h1,h2){
+  rotate([0,-90,0]) translate([0,-h1/2,-w/2]) linear_extrude(height=w) {
+    polygon(points=[[0,0], [d,0], [d,h2], [0,h1]]);
+  }
+}
+
+module CupSecuringClipSolidSmooth(xrad=0, xdepth=0){
+  hbase = totalheight + prongstalkxwidth - overcliproundr*2;
+  minkowski(){
+    CupSecuringClipSolid(w=totalwidth,
+                        d=overclipdepth + xdepth,
+                        h1=hbase + overclipbigger,
+                        h2=hbase - overclipsmaller);
+    cylinder($fn=20, h=0.01, r=overcliproundr+xrad);
+  }
+}
+
+module CupSecuringClip(){ ////toplevel
+  wingswidth = wingspoke*2 + overclipthick*2 + overcliproundr*2 + totalwidth;
+  difference(){
+    union(){
+      CupSecuringClipSolidSmooth(xrad=overclipthick, xdepth=0);
+      translate([-wingswidth/2, -wingsize/2, 0])
+       cube([wingswidth, wingsize, wingthick]);
+      translate([-wingsize/2, -wingswidth/2, 0])
+       cube([wingsize, wingswidth, wingthick]);
+    }
+    translate([0,0,-0.1])
+      CupSecuringClipSolidSmooth(xrad=0, xdepth=0.2);
+  }
+}
+
+module ArmDoveClipPin(){ ////toplevel
+  DoveClipPin(h=doveclipheight);
+}
+
+module TowerDoveClipPin(){ ////toplevel
+  DoveClipPin(h=towercliph/2);
+}
+
+module Hub(){ ////toplevel
+  axlerad = hubaxlerad + slop;
+  xmin = axlerad+hublwidth/2;
+  xmax = hubbigrad-hublwidth/2;
+  hole = hubeffrad - hubbigrad - DoveClip_depth() - hublwidth*2;
+  holewidth = DoveClipPairSane_width() - hubstemwidth*2;
+  nondove_allwidth = nondove_armhole_wall*2 + totalwidth;
+  difference(){
+    union(){
+      difference(){
+       cylinder($fn=60, h=hublthick, r=hubbigrad);
+       translate([0,0,-1])
+         cylinder($fn=30, h=hublthick+2, r=(hubbigrad-hublwidth));
+      }
+      cylinder(h=hubaxlelen, r=axlerad+hublwidth);
+      for (ang=[0 : 360/num_arms : 359])
+       rotate([0,0,ang]) {
+         if (usedove()){
+           difference() {
+             translate([hubeffrad,0,0])
+               DoveClipPairSane(h=doveclipheight,
+                                baseextend = hub_clip_baseextend);
+             if (hole>hublwidth && holewidth > 2) {
+               translate([hubbigrad + hublwidth, -holewidth/2, -1])
+                 cube([hole, holewidth, hublthick+2]);
+             }
+           }
+         } else {
+           difference(){
+             translate([0,
+                        -nondove_allwidth/2,
+                        0])
+               cube([hubeffrad + nondove_armhole_x
+                     + nondove_armhole_hole/2 + nondove_armhole_support,
+                     nondove_allwidth,
+                     nondove_armhole_wall + totalheight]);
+             translate([hubeffrad - nondove_armhole_slop_x,
+                        -nondove_allwidth/2
+                        + nondove_armhole_wall - nondove_armhole_slop,
+                        nondove_armhole_wall])
+               cube([nondove_armhole_x + 50,
+                     totalwidth + nondove_armhole_slop*2,
+                     totalheight + 1]);
+             translate([hubeffrad + nondove_armhole_x, 0, -20])
+               cylinder(r= nondove_armhole_hole/2, h=50, $fn=10);
+           }
+         }
+       }
+      for (ang = [0 : 180/num_arms : 359])
+       rotate([0,0,ang]) rotate([90,0,0]) {
+         translate([0,0,-hublwidth/2])
+           linear_extrude(height=hublwidth)
+           polygon([[xmin,0.05], [xmax,0.05],
+                    [xmax,hublthick-0.2], [xmin, hubaxlelen-0.2]]);
+       }
+    }
+    translate([0,0,-1]) cylinder($fn=60, h=hubaxlelen+2, r=axlerad);
+
+    rotate([0,0, selsz(0,0,45)])
+      translate([axlerad+hublwidth,
+                -hublwidth/2,
+                0])
+      rotate([90,0,0])
+      Commitid_BestCount([(hubbigrad-hublwidth) - (axlerad+hublwidth),
+                         hublthick +
+                         hublwidth/2 * hubaxlelen/(hubbigrad-axlerad),
+                         ]);
+  }
+}
+
+module ArmExtender(){ ////toplevel
+  DoveClipExtender(length=exteffrad-hubeffrad,
+                  ha=doveclipheight,
+                  hb=doveclipheight);
+}
+
+module FsAxlePin(){ ////toplevel
+  AxlePin(hubaxlerad, washerrad*2, axlepinrad, axlepintabrad, slop);
+}
+
+module Axle(){ ////toplevel
+  pillarswidth = DoveClipPairSane_width(towerclipcount);
+
+  rotate([0,0, -( axleaxlefudgebend + atan(slop/hubaxlelen) ) ])
+  translate([-axlehorizoffset, -axlevertheight, 0]) {
+    rotate([0,0,-axletowerfudgebend])
+    rotate([0,0,-90])
+      DoveClipPairSane(h=towercliph, count=towerclipcount, baseextend=3);
+    translate([0, DoveClip_depth(), 0])
+    rotate([0,0,90])
+      ExtenderPillars(axlevertheight - DoveClip_depth(),
+                     pillarswidth, towercliph,
+                     pillarw=towerpillarw);
+  }
+
+  axleclearlen = hubaxlelen + slop*4 + washerthick*2 + axlepadlen;
+  axlerad = hubaxlerad-slop;
+  bump = axlerad * 0.2;
+  shift = axlerad-bump;
+  joinbelowallow = 3;
+
+  intersection(){
+    translate([0, 0, shift]) {
+      difference() {
+       union(){
+         translate([-1, 0, 0])
+           rotate([0,90,0])
+           cylinder($fn=60,
+                    r = axlerad,
+                    h = 1 + axleclearlen + axlepinrad*2 + 2);
+         mirror([1,0,0]) rotate([0,90,0])
+           cylinder(r = axlerad*1.75, h = 3);
+         intersection(){
+           mirror([1,0,0])
+             translate([axlehorizoffset - pillarswidth/2, 0, 0])
+             rotate([0,90,0])
+             cylinder($fn=60,
+                      r = towercliph - shift,
+                      h = pillarswidth);
+           translate([-50, -joinbelowallow, -50])
+             cube([100, joinbelowallow+50, 100]);
+         }
+       }
+       rotate([90,0,0])
+       translate([axleclearlen + axlepinrad/2, 0, -25])
+         cylinder(r = axlepinrad + slop, h=50);
+      }
+    }
+    translate([-50,-50,0]) cube([100,100,100]);
+  }
+}
+
+module washer(thick){
+  Washer(hubaxlerad, washerrad, thick, slop);
+}
+
+module AxleWasher(){ ////toplevel
+  washer(thick=washerthick);
+}
+
+module AxleThinWasher(){ ////toplevel
+  washer(thick=washerthinthick);
+}
+
+module AxleVeryThinWasher(){ ////toplevel
+  washer(thick=washerverythinthick);
+}
+
+module AxleFrictionWasher(){ ////toplevel
+  difference(){
+    cylinder(h=washerthick, r=washerrad);
+    translate([0,0,-1]) cylinder(h=washerthick+2, r=hubaxlerad+slop);
+  }
+  frarmr = hubbigrad;
+  frarmw = frictionwasherarmwidth;
+  frarmpawlr = hublwidth;
+  frarmpawlpush = slop*4 + frictionwasherextrapush;
+  for (ang=[0,180]) rotate([0,0,ang]) {
+    translate([washerrad-1, -frarmw/2, 0])
+      cube([frarmr - washerrad + 1, frarmw, washerthick]);
+    intersection(){
+      translate([frarmr - frarmpawlr, -50, 0])
+       cube([frarmpawlr, 100, 50]);
+      rotate([0,90,0])
+       cylinder(h = 50, r = frarmpawlpush, $fn=36);
+    }
+  }
+}
+
+module TowerExtender(){ ////toplevel
+  l = totalheightfromtower - axlevertheight;
+  echo("TowerExtender",l);
+  DoveClipExtender(length = l,
+                  ha = towercliph, hb = towercliph,
+                  counta = towerclipcount, countb = towerclipcount,
+                  pillarw = towerpillarw);
+}
+
+module FilamentCupPair(){ ////toplevel
+  FilamentCup();
+  translate([cupheight + prongthick*3,
+            cupwidth/2*1.7,
+            0])
+    rotate([0,0,180]) FilamentCup();
+}
+
+//----- storarm -----
+
+storarm_hooklen = 8;
+storarm_hookheight = 5;
+storarm_thick = 10;
+storarm_axleslop = 4;
+
+storarm_base_w = 30;
+storarm_base_h = 100;
+storarm_base_d = 15;
+storarm_base_mind = 2;
+
+storarm_cope_hubaxle_mk1 = true;
+
+storarm_screw_hole = 4;
+storarm_screw_hole_slop = 0.5;
+storarm_besides_hole = 4;
+
+storarm_under_hole = 5;
+storarm_screw_hole_head = 8.8;
+storarm_screw_hole_head_slop = 1.5;
+
+// calculated
+
+storarm_spools = 2;
+
+storarm_axlerad = hubaxlerad - storarm_axleslop;
+storarm_mainlen = hubaxlelen*storarm_spools
+  + storarm_axleslop*(storarm_spools-1)
+  + (storarm_cope_hubaxle_mk1 ? 10 : 0);
+storarm_totlen = storarm_mainlen + storarm_hooklen;
+
+storarm_taller = storarm_axleslop * (storarm_spools-2);
+
+storarm_mid_off_y = storarm_axlerad;
+
+storarm_base_off_y = storarm_mid_off_y + storarm_base_h/2;
+
+module StorageArmDiagPartSide(xmin, xmax){
+  xsz = xmax-xmin;
+  yuse = storarm_thick/2;
+
+  intersection(){
+    translate([xmin-1, -storarm_axlerad, storarm_thick/2])
+      rotate([0,90,0])
+      cylinder(r=storarm_axlerad, h=xsz+2, $fn=60);
+    translate([xmin, -yuse, 0])
+      cube([xsz, yuse, storarm_thick]);
+  }
+}
+
+module StorageArmDiagPart(xmin, xmax, adjbot, shear){
+  hull(){
+    StorageArmDiagPartSide(xmin,xmax);
+
+    multmatrix([[1,0,0,0],
+               [shear,1,0,0],
+               [0,0,1,0],
+               [0,0,0,1]])
+      translate([0, -storarm_axlerad*2 + adjbot, 0])
+      mirror([0,1,0])
+      StorageArmDiagPartSide(xmin,xmax);
+  }
+}
+
+module StorageArmBaseTemplate(){
+  square([storarm_base_w, storarm_base_h]);
+}
+
+module StorageArmAtMountingHoles(){
+  bes = storarm_besides_hole + storarm_screw_hole;
+
+  x0 = bes;
+  x1 = storarm_base_w-bes;
+  y1 = storarm_base_h - bes;
+  y0 = bes;
+
+  for (pos=[ [x0, y1],
+            [x1, y1],
+            [x1, y0] ]) {
+    rotate([0,90,0])
+      translate([pos[0] - storarm_base_w,
+                pos[1] - storarm_base_off_y, -storarm_base_d])
+      children();
+  }
+}
+
+module StorageArmRight(){ ////toplevel
+  shear = storarm_hookheight / (storarm_mainlen/2);
+  shear2 = shear + storarm_taller / (storarm_mainlen/2);
+  base_xyz = [-storarm_base_d, -storarm_base_off_y, storarm_base_w];
+
+  StorageArmDiagPart(-1, storarm_mainlen/2+1,
+                    -storarm_taller, shear2);
+  StorageArmDiagPart(storarm_mainlen/2-1, storarm_mainlen+1,
+                    storarm_hookheight/2, shear/2);
+
+  translate([0, storarm_hookheight, 0])
+    StorageArmDiagPart(storarm_mainlen, storarm_totlen,
+                      -storarm_hookheight/2, shear/2);
+
+  difference(){
+    union(){
+      hull(){
+       translate(base_xyz)
+         rotate([0,90,0])
+         linear_extrude(height=storarm_base_mind)
+         StorageArmBaseTemplate();
+       StorageArmDiagPart(-1, 0, -storarm_taller, shear);
+      }
+      StorageArmAtMountingHoles(){
+       cylinder(r= storarm_screw_hole_head/2,
+                h=10);
+      }
+    }
+    StorageArmAtMountingHoles(){
+      translate([0,0,-1])
+       cylinder(r= (storarm_screw_hole + storarm_screw_hole_slop)/2 ,
+                h=20);
+      translate([0,0,storarm_under_hole])
+       cylinder(r= (storarm_screw_hole_head + storarm_screw_hole_head_slop)/2,
+                h=20);
+    }
+    translate(base_xyz + [0, storarm_base_h/4, -storarm_base_w/4])
+      rotate([0,90,0])
+      Commitid_BestCount([storarm_base_w/2, storarm_base_h/2]);
+  }
+}
+
+module StorageArmLeft(){ ////toplevel
+  mirror([1,0,0]) StorageArmRight();
+}
+
+module StorArmHoleTest(){ ////toplevel
+  sz = storarm_screw_hole_head + storarm_besides_hole*2;
+  intersection(){
+    StorageArmRight();
+    translate([-50, -storarm_base_off_y, -1])
+      cube([100, sz, sz+1]);
+  }
+}
+
+
+//----- filament guide spacer -----
+
+guide_armdia = 15.0;
+guide_armwidth = 10.2;
+guide_armcorelen = 25.0;
+guide_clipcirclethick = 10.0;
+
+guidefilclip_outerdia = 22.8;
+
+guidespacer_armslop = 0.75;
+guidespacer_armlenslop = 1.05;
+
+guidespacer_prongprotrude = 4;
+guidespacer_thick = 1.6;
+
+// calculated
+
+guidespacer_armdia = guide_armdia + guidespacer_armslop;
+guidespacer_armwidth = guide_armwidth + guidespacer_armslop;
+guidespacer_len = guide_armcorelen - guide_clipcirclethick
+  + guidespacer_armlenslop;
+
+guidespacer_wingheight = (guidefilclip_outerdia - guidespacer_armdia)/2;
+
+module FilamentGuideArmTemplate(extra=0){
+  intersection(){
+    circle(r= (guidespacer_armdia/2) + extra);
+    square(center=true, [guidespacer_armwidth+extra*2,
+                        guidespacer_armdia + extra*2 + 10]);
+  }
+}
+
+module FilamentGuideSpacerInnerTemplate(){
+  FilamentGuideArmTemplate();
+  translate([0, -guidespacer_armdia/2])
+    square(center=true, [guidespacer_armwidth - guidespacer_prongprotrude,
+                        guidespacer_armdia]);
+}
+
+module FilamentGuideSpacer(){ ////toplevel
+  difference(){
+    union(){
+      linear_extrude(height= guidespacer_len)
+       FilamentGuideArmTemplate(extra= guidespacer_thick);
+      for (angle=[26, 60]) {
+       for (m=[0,1]) {
+         mirror([m,0,0]) {
+           rotate([0,0,angle]) {
+             hull(){
+               for (t=[[0, guidespacer_wingheight],
+                       [guidespacer_len-1, -guidespacer_wingheight]])
+                 translate([0,0, t[0] + 0.5])
+                   cube([guidespacer_thick, guidespacer_armdia + t[1]*2,
+                     1],
+                        center=true);
+             }
+           }
+         }
+       }
+      }
+    }
+    translate([0,0,-1])
+      linear_extrude(height= guidespacer_len+5)
+      FilamentGuideSpacerInnerTemplate();
+  }
+}
+
+
+//----- replacement filament guide arm for TAZ-5 -----
+
+guidearm_armslop = 0.25;
+guidearm_armlenslop = 0.25;
+
+guidearm_hookprotr = 3;
+guidearm_hookprotrflat = 1;
+guidearm_hookslope = 0.3;
+
+guidearm_totallen = 60;
+
+guidearm_screwplatesz = 12;
+guidearm_screwplateth = 4;
+guidearm_screwplatewd = 15;
+guidearm_screwhole = 5 + 0.5;
+
+guidearm_bendlen = 40;
+guidearm_bendslot = 4.5;
+
+guidearm_stopthick = 4;
+guidearm_protrslop = 1.0;
+
+// calculated
+
+guidearm_armdia = guide_armdia - guidearm_armslop;
+guidearm_armwidth = guide_armwidth - guidearm_armslop;
+guidearm_armcorelen = guide_armcorelen + guidearm_armlenslop;
+
+guidearm_base_z0 = -(guidearm_totallen - guidearm_armcorelen);
+
+guidearm_realbendlen = min(guidearm_bendlen,
+                          guidearm_totallen - guidearm_screwplateth - 0.1);
+guidearm_slopelen = guidearm_hookprotr/guidearm_hookslope;
+
+module FilamentGuideArmStop(h){
+  for (ts=[-1,+1]) {
+    translate([ts * guidearm_hookprotr, 0,0])
+      cylinder(r=guidearm_armdia/2, h, $fn=80);
+  }
+}
+
+module FilamentGuideArmShaftPositive(){
+  r = guidearm_armdia/2;
+
+  translate([0,0, guidearm_base_z0+1])
+    cylinder(r=r, h= guidearm_totallen, $fn=80);
+  translate([0,0, guidearm_armcorelen]){
+    hull(){
+      FilamentGuideArmStop(guidearm_hookprotrflat);
+      translate([0,0, guidearm_slopelen])
+       cylinder(r=r, h=guidearm_hookprotrflat, $fn=80);
+    }
+  }
+  mirror([0,0,1])
+    FilamentGuideArmStop(guidearm_stopthick);
+}
+
+module FilamentGuideArmBase(){
+  translate([0,
+            (guidearm_screwplatewd - guidearm_armwidth)/2,
+            guidearm_base_z0]){
+    difference(){
+      translate([0,0, guidearm_screwplateth/2])
+       cube(center=true,
+            [guidearm_armdia + guidearm_screwplatesz*2,
+             guidearm_screwplatewd,
+             guidearm_screwplateth]);
+      for (ts=[-1,+1]) {
+       translate([ts * (guidearm_armdia/2 + guidearm_screwplatesz/2),
+                  0,
+                  -20])
+         cylinder(r= guidearm_screwhole/2, h=40, $fn=20);
+      }
+    }
+  }
+}
+
+module FilamentGuideArm(){ ///toplevel
+  intersection(){
+    difference(){
+      FilamentGuideArmShaftPositive();
+      translate([-guidearm_bendslot/2,
+                -50,
+                -guidearm_realbendlen + guidearm_armcorelen])
+       cube([guidearm_bendslot,
+             100,
+             guidearm_realbendlen + 100]);
+      hull(){
+       for (zx=[ [ 0, guidearm_bendslot ],
+                 [ guidearm_armcorelen + guidearm_slopelen,
+                   guidearm_hookprotr*2 + guidearm_protrslop ]
+                 ]) {
+         translate([-zx[1]/2, -50, zx[0]])
+         cube([zx[1], 100, 1]);
+       }
+      }
+    }
+    cube(center=true,
+        [guidearm_armdia*2,
+         guidearm_armwidth,
+         guidearm_totallen*3]);
+  }
+  FilamentGuideArmBase();
+}
+
+module FilamentGuideArmPrint(){ ////toplevel
+  rotate([90,0,0])
+    FilamentGuideArm();
+}
+
+module Demo(){ ////toplevel
+  translate([-real_exteffrad,-20,0]) Hub();
+  ArmEnd();
+  translate([ratchettooth*2, 30, 0]) FilamentCup();
+  if (selsz(true,false,false)) {
+    translate([-exteffrad + hubeffrad - hub_clip_baseextend, -10, 0])
+      ArmExtender();
+  }
+}
+
+//ArmEnd();
+//FilamentCup();
+//FilamentCupPair();
+//CupSecuringClip();
+//Hub();
+//ArmExtender();
+//Axle();
+//AxleWasher();
+//AxlePin();
+//AxleFrictionWasher();
+//StorageArmLeft();
+//StorArmHoleTest();
+//FilamentGuideSpacer();
+//FilamentGuideArm();
+//FilamentGuideArmPrint();
+//Demo();
diff --git a/filamentteeth.scad b/filamentteeth.scad
new file mode 100644 (file)
index 0000000..f948c6e
--- /dev/null
@@ -0,0 +1,61 @@
+// -*- C -*-
+//
+// filamentteeth.scad
+//
+// 3D design for clips to hold FFF filament
+// Copyright 2012,2016 Ian Jackson
+//
+// This work is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This work is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this work.  If not, see <http://www.gnu.org/licenses/>.
+
+
+d=0.01;
+teethw=1.5;
+
+module FilamentTeeth(fdia, h, teethw=teethw,
+                        stembendd=0.5, stembendl=7, teethxl=1.5) {
+  gapw = fdia-stembendd*2;
+  teethbigw = gapw + teethw*2;
+  basew = fdia+teethw*2-stembendd*2;
+  based = basew/3;
+
+  translate([-based, -basew/2, 0]) cube([based, basew, h]);
+  difference() {
+    union() {
+      translate([-d, -teethbigw/2, 0])
+       cube([d+stembendl + teethw, teethbigw, h]);
+//  translate([
+//  stembigw = fdia + stembend
+//  translate([-d, -stemw, 
+      linear_extrude(height=h) {
+       translate([stembendl-fdia/2, 0]) circle(fdia/2+teethw, $fn=30);
+      }
+    }
+    translate([0,0,-1]) {
+      translate([0,-gapw/2])
+       cube([stembendl+teethxl+1, gapw, h+2]);
+      linear_extrude(height=h+2) {
+       translate([stembendl-fdia/2, 0]) circle(fdia/2, $fn=30);
+      }
+    }
+  }
+
+  for (mirr=[0:1]) {
+    mirror([0,mirr,0]) {
+      translate([stembendl + teethw, gapw/2, 0])
+       rotate([0,0,30])
+       cube([teethxl, teethw, h]);
+    }
+  }
+}
+
diff --git a/filamenttrestle.scad b/filamenttrestle.scad
new file mode 100644 (file)
index 0000000..376a1ae
--- /dev/null
@@ -0,0 +1,284 @@
+// -*- C -*-
+
+spoolinnerdia = 32;
+spoolwidth = 88.0;
+spoolinnerrad = (spoolinnerdia - 0.2) / 2;
+spoolouterrad = spoolinnerrad + 61.5;
+
+include <doveclip.scad>
+include <axlepin.scad>
+
+spoolradclear = 10;
+spoolradslop = 2;
+
+spoolinnerslop = 3;
+axleslop = 0.5;
+
+axlerad = 7;
+barwasherrad = 17;
+
+hubbasethick = 4;
+hubmainthick = 15;
+hubbaseweb = 1.2;
+hubbasestalkwidth = 4;
+hubwalls = 2.5;
+hubpillarw = 4;
+hubbaserad = spoolinnerrad + 10;
+hubmainrad = spoolinnerrad - spoolradslop;
+
+legw = 12;
+plugl = 20;
+plugwmin = 3;
+plugh = 10;
+plugslope = 0.5;
+plugwmax = plugwmin + plugh * plugslope * 2;
+
+trestlefoot = 15;
+
+trestlelegw = 10;
+trestlebaseh = 10;
+trestleplugd = 1;
+
+topblockthick = 3;
+topblockbasedepth = 5;
+
+pinbasew = 4.0;
+pinminh = 1.0;
+pinmaxh = 3.5;
+pindh = 1.75;
+pindwidth = 1.75;
+
+pintaperlen = 20;
+pinstraightlen = 30-pintaperlen;
+
+spoolouterpad = AxlePin_holerad()*2 * 1.5;
+spoolbarlen = spoolwidth +
+  2*(Washer_thick() + hubbasethick + AxlePin_holerad()
+     + spoolinnerslop + spoolouterpad);
+  barz = axlerad * 0.5;
+axlepin_x = spoolwidth/2 + hubbasethick +
+  Washer_thick() + spoolinnerslop + AxlePin_holerad()*0.5;
+
+trestleheight = spoolouterrad + spoolradclear - barz;
+trestlebase = trestleheight * 1.2;
+
+module Plug(d=0){
+  dw = d;
+  dh = d;
+  dhb = d*2;
+  a = atan(plugslope);
+  bdy = -dhb;
+  bdx = dw / cos(a) + bdy * plugslope;
+  tdy = dh;
+  tdx = bdx + tdy * plugslope;
+  translate([-d,0,0]) rotate([90,0,90]) linear_extrude(height=plugl+0.1+d*2){
+    polygon([[-(plugwmin/2 + bdx),  bdy],
+            [-(plugwmax/2 + tdx),  plugh + tdy],
+            [+(plugwmax/2 + tdx),  plugh + tdy],
+            [+(plugwmin/2 + bdx),  bdy]]);
+  }
+}
+
+module Bar(){ ////toplevel
+  spoolw = spoolbarlen;
+  biggestw = spoolw + 50;
+
+  intersection(){
+    for (mir=[0,1]) {
+      mirror([mir,0,0]) {
+       translate([spoolw/2, 0, 0])
+         Plug();
+       translate([-1, -50, -50])
+         cube([spoolw/2+1.1, 100, 100]);
+      }
+    }
+    difference(){
+      translate([-biggestw/2, -50, 0])
+       cube([biggestw, 100, 100]);
+      for (mir=[0,1]) {
+       mirror([mir,0,0])
+         translate([axlepin_x, 0, -50])
+         cylinder(r=AxlePin_holerad(), 100, $fn=15);
+      }
+    }
+    translate([0,0,barz]) {
+      translate([-100,0,0])
+       rotate([0,90,0]) cylinder(r=axlerad, h=200, $fn=60);
+    }
+  }
+}
+
+module FtAxlePin(){ ////toplevel
+  AxlePin(axlerad, (axlerad + barwasherrad*2)/3 * 2);
+}
+
+module AxleWasher(){ ////toplevel
+  Washer(axlerad, barwasherrad);
+}
+
+module Trestle(){ ////toplevel
+  legang = atan2(trestlebase/2, trestleheight);
+  eplen = sqrt(trestleheight*trestleheight + trestlebase*trestlebase*0.25);
+  topblockw = plugwmax + trestleplugd*2 + topblockthick*2;
+
+  pinholebasew = pinbasew + pindwidth*2;
+  pinholeh =     pinmaxh +  pindh;
+
+  difference(){
+    union(){
+      for (mir=[0,1]) {
+       mirror([mir,0,0]) {
+         rotate([0,0, -90-legang])
+           ExtenderPillars(length=eplen+trestlelegw,
+                           width=trestlelegw,
+                           height=legw,
+                           baseweb=true);
+
+         translate([-trestlebase/2, -trestleheight, 0])
+           cylinder(r=trestlelegw/2*1.2, h=trestlefoot);
+       }
+      }
+      translate([-topblockw/2, -topblockbasedepth, 0])
+       cube([topblockw,
+             topblockbasedepth + plugh + topblockthick
+             + (pinmaxh - pinminh)*0.5,
+             plugl]);
+
+      translate([-trestlebase/2, -trestleheight, 0])
+       ExtenderPillars(length=trestlebase, width=trestlebaseh*2, height=legw);
+    }
+    translate([-300, -trestleheight-50, -1])
+      cube([600, 50, 52]);
+
+    rotate([-90,-90,0])
+      Plug(d=trestleplugd);
+
+    for (rot=[0,180]) {
+      translate([0,0,plugl/2]) rotate([0,rot,0]) translate([0,0,-plugl/2]) {
+       translate([0,
+                  plugh - (pinmaxh - pinminh)*1.00,
+                  (plugl - pinholebasew*2)/3]) {
+         translate([-(topblockw*0.25+1), 0, pinholebasew/2])
+           rotate([-90,0,0]) %Pin();
+         translate([-(topblockw+1), 0, 0]) {
+           rotate([0,90,0]) {
+             linear_extrude(height = topblockw*2.0+2) {
+               polygon([[-1.0 * pinholebasew, -0.01],
+                        [-0.5 * pinholebasew, pinholeh],
+                        [ 0                 , -0.01]]);
+             }
+           }
+         }
+       }
+      }
+    }
+  }
+}
+
+module Pin(){ ////toplevel
+  rotate([90,0,90]) {
+    hull(){
+      for (mir=[0,1]) {
+       mirror([mir,0,0]) {
+         linear_extrude(height=0.1) {
+           polygon([[-0.01, 0],
+                    [-0.01, pinminh],
+                    [pinbasew*0.5*(pinminh/pinmaxh), 0]]);
+         }
+         translate([0,0,pintaperlen])
+           linear_extrude(height=pinstraightlen) {
+           polygon([[-0.01, 0],
+                    [-0.01, pinmaxh],
+                    [pinbasew*0.5, 0]]);
+         }
+       }
+      }
+    }
+  }
+}
+
+module HubEnd(){ ////toplevel
+  thick = hubmainthick+hubbasethick;
+  difference(){
+    union(){
+      for (ang=[0 : 60 : 359]) {
+       rotate([0,0,ang]) {
+         translate([hubmainrad - hubwalls/2, -hubbasestalkwidth/2, 0])
+           cube([hubbaserad - (hubmainrad - hubwalls/2),
+                 hubbasestalkwidth, hubbasethick]);
+         ExtenderPillar(length = hubmainrad-hubwalls/2,
+                        height = hubbasethick + hubmainthick,
+                        pillarw = hubpillarw);
+       }
+      }
+      cylinder(r=axlerad+hubwalls, h=thick);
+      cylinder(r=hubmainrad-0.1, h=hubbaseweb);
+      difference(){
+       cylinder(r=hubmainrad, h=thick, $fn=100);
+       translate([0,0,-1])
+         cylinder(r=hubmainrad-hubwalls, h=thick+2);
+      }
+      difference(){
+       cylinder(r=hubbaserad, h=hubbasethick, $fn=50);
+       translate([0,0,-1])
+         cylinder(r=hubbaserad-hubwalls, h=hubbasethick+2);
+      }
+    }
+    translate([0,0,-1])
+      cylinder(r=axlerad+axleslop, h=thick+2, $fn=50);
+  }
+}
+
+
+module TestKit(){ ////toplevel
+  translate([60,0,0]) mirror([1,0,0]) Pin();
+  translate([60,15,0]) mirror([1,0,0]) Pin();
+  translate([0,40,0]) intersection(){
+    Trestle();
+    translate([-50,-10,-1]) cube([100,100,100]);
+  }
+  intersection(){
+    translate([-60,10,0]) Bar();
+    cube(50,center=true);
+  }
+  %translate([50,40, AxlePin_zoffset()]) FtAxlePin();
+  %translate([0,-20,0]) AxleWasher();
+}
+
+module DemoSpool(){
+  rotate([0,90,0]) translate([0,0,-spoolwidth/2])
+    difference(){
+      cylinder(r=spoolouterrad, h=spoolwidth);
+      translate([0,0,-1]) cylinder(r=spoolinnerrad, h=spoolwidth+2);
+    }
+}
+
+module Demo(){ ////toplevel
+  color("blue") Bar();
+  for (mir=[0,1]) {
+    mirror([mir,0,0]) {
+      color("red") translate([spoolbarlen/2,0,0])
+       rotate([90,0,90]) Trestle();
+      color("orange")
+       translate([spoolwidth/2 + hubbasethick + spoolinnerslop*2/3, 0, barz])
+       rotate([0,90,0]) AxleWasher();
+      color("orange") translate([axlepin_x, 0, barz])
+       rotate([90,0,90]) FtAxlePin();
+      color("cyan")
+       translate([spoolwidth/2 + hubbasethick + spoolinnerslop*1/3, 0, barz])
+       rotate([0,-90,0]) HubEnd();
+    }
+  }
+  %translate([0,0,barz]) DemoSpool();
+}
+
+//Bar();
+//FtAxlePin();
+//AxleWasher();
+//Trestle();
+//Pin();
+//TestKit();
+//Plug(d=1);
+//ExtenderPillars(80,12,8, baseweb=true);
+//HubEnd();
+//Demo();
diff --git a/fire-blanket-wall-mushroom.scad b/fire-blanket-wall-mushroom.scad
new file mode 100644 (file)
index 0000000..dae4769
--- /dev/null
@@ -0,0 +1,40 @@
+// -*- C -*-
+
+fudge=0.15;
+
+screwrad = 4.5 / 2 + fudge; // xxx check
+shaftrad = 7.5 / 2 - fudge;
+
+diskrad = 12.0 / 2 - fudge;
+
+csinkpart = 0.5;
+
+shaftlen = 8; // xxx check
+
+diskthick = 1.5;
+
+disktaperratio = 2;
+
+// computed
+
+disktaperrad = diskrad - diskthick / disktaperratio;
+totallen = shaftlen + diskthick;
+
+module SidePlan(){
+  polygon([[-screwrad, 0],
+          [-disktaperrad, 0],
+          [-diskrad, -diskthick],
+          [-shaftrad, -diskthick],
+          [-shaftrad, -totallen],
+          [-screwrad, -totallen]]);
+}
+
+module Bush(){
+  rotate_extrude($fn=25, convexity=3){
+    SidePlan();
+  }
+}
+
+//SidePlan();
+rotate([0,180,0])
+  Bush();
diff --git a/fire-blanket-wall-mushroom.slic3r b/fire-blanket-wall-mushroom.slic3r
new file mode 100644 (file)
index 0000000..2aa4f4a
--- /dev/null
@@ -0,0 +1 @@
+first_layer_extrusion_width = 100%
diff --git a/floating-phases.scad b/floating-phases.scad
new file mode 100644 (file)
index 0000000..b419918
--- /dev/null
@@ -0,0 +1,103 @@
+// -*- C -*-
+
+// caller should define
+//
+//   z_pause = 2;    // higher than any thing in the model
+//   total_sz = [ 200, 141 ];
+//   phases=4;   // how many phases
+//   colours = [
+//        "blue",
+//        "black",
+//        "red",          
+//        "yellow",
+//        ];
+//
+// Pause height                z_pause value from caller
+// head park X         15
+// head park Y         0
+// head move Z         1
+// min head park Z     1
+// retraction          1
+// re-home             [ ] [ ]
+
+// when z pause occurs
+//   * set feed rate to 10% (or whatever minimum is)
+//   * press knob, printer will start
+//   * quickly, press again, select "stop"
+//   * set temp
+//   * set feed rate back to 100%
+//   * now change filament etc., start next file
+
+include <utils.scad>
+
+$test_thicker = 1.0; // 2.5;
+
+th_l0 = .425 * $test_thicker;
+th_l1 = .250 * $test_thicker;
+frame_w = 0.8;
+tower_w = 2.0;
+
+th_most = th_l0 + th_l1*2;
+
+noz_gap = 8;
+
+
+multicolour_gap = 0.15; // each side
+
+underlayer_smaller = 0.35; // each side
+
+total_sz_actual = $test ? $test : total_sz;
+
+module Interlayer_Bigger(){
+  offset(r=multicolour_gap, $fn=20){
+    union(){ children(); }
+  }
+}
+
+module Interlayer_Smaller(){
+  offset(r=-multicolour_gap, $fn=20){
+    union(){ children(); }
+  }
+}
+
+module Underlayer_Smaller(){
+  offset(r=-underlayer_smaller, $fn=20){
+    union(){ children(); }
+  }
+}
+
+module FloatingPhaseFraming(phase, zmin) {
+  frame_inner =
+    total_sz_actual
+    + noz_gap * [2,2] * (phase > 0 ? phase : 0.5);
+
+  frame_outer =
+    frame_inner
+    + frame_w * [2,2];    
+
+  tower_pos =
+    -0.5 * frame_inner
+    + noz_gap * [1,0] * (phases - phase)
+    + -[1,0] * tower_w + 0.5 * [0,-1] * frame_w;
+
+  // frame for alignment
+  linear_extrude(height= th_l0)
+    difference(){
+      square(frame_outer, center=true);
+      square(frame_inner, center=true);
+  }
+
+  // tower to avoid diagonal traverse to start position
+  linear_extrude(height= zmin + th_l1)
+    translate(tower_pos)
+    square([1,1] * tower_w);
+
+  // trick to pause rather than finishing
+  if (phase != phases-1)
+    translate([0,0, z_pause+th_l1])
+      linear_extrude(th_l1)
+      translate(tower_pos)
+      square([1,1] * tower_w);
+}
+
+echo(str("SET PAUSE AT Z HEIGHT TO ",z_pause));
diff --git a/floating-test.scad b/floating-test.scad
new file mode 100644 (file)
index 0000000..5f4000c
--- /dev/null
@@ -0,0 +1,19 @@
+// -*- C -*-
+
+frameth=0.8;
+
+l0= 0.425;
+l1= 0.25;
+
+del=l0+l1*2;
+
+for (r=[0,90,180,270])
+  rotate([0,0,r])
+  translate([10,-(10+frameth),0])
+    cube([frameth, 20+2*frameth, 0.4]);
+
+translate([-11,-11,0])
+cube([2,2, del + l1]);
+
+translate([-3,-3, del])
+  cube([6,6, l1*2]);
diff --git a/flyscreen-handle.scad b/flyscreen-handle.scad
new file mode 100644 (file)
index 0000000..28753b7
--- /dev/null
@@ -0,0 +1,258 @@
+// -*- C -*-
+
+opening_height = 7.84;
+opening_depth = 7.88;
+openingedge_dia = 2.00;
+opening_protrh = 2.00;
+
+pivot_x = 6;
+inside_len = 4;
+
+pivoting_gap = 0.1;
+
+outside_gap = 3;
+outside_len = 13;
+outend_height = 3;
+
+outside_len_bot = 23;
+
+outside_pushh = 4;
+outside_pushslope = 1.4;
+outside_push_inadj = 0.82;
+
+ourcirc_r = 0.5 / 2;
+
+ribble_dia = 2.2;;
+
+opening_protr_slop = 0.1;
+
+intooth_top_slop = 0.1;
+inside_h_xgap = 1;
+
+pivot_r = 2;
+pivot_slop = 0.25;
+
+strap_above = 0.1;
+strap_th = 2.5;
+strap_below = 3;
+strap_width = 5;
+
+width = 35;
+nstraps = 2;
+
+test_width = 5;
+
+// calculated
+
+inside_h = opening_height/2 - opening_protrh - inside_h_xgap/2;
+
+edge_or = openingedge_dia/2 + opening_protr_slop;
+
+Q0 = [ openingedge_dia/2,
+       openingedge_dia/2 + opening_height/2 ];
+
+p4p5d = [edge_or + ourcirc_r, 0];
+
+P0 = [ pivot_x, pivoting_gap ];
+P4 = Q0 - p4p5d;
+P3t = [ P4[0], Q0[1] - openingedge_dia/2 + opening_protrh
+       - intooth_top_slop - ourcirc_r ];
+P2 = P4 + [ -(inside_len - ourcirc_r*2), 0 ];
+P1 = [ P2[0], P3t[1] - (inside_h + ourcirc_r*2) ];
+
+P5 = Q0 + p4p5d;
+
+P8t = [ outside_len - ourcirc_r, P5[1] ];
+P9t = P8t + [ 0, -(strap_above + strap_th + strap_below - ourcirc_r*2) ];
+
+P9b = [ P9t[0], -P9t[1] + outside_gap ];
+P8b = P9b + [ 0, outend_height ];
+
+P89eadj = [ outside_len_bot - outside_len, 0 ];
+P8eb = P8b + P89eadj;
+P9eb = P9b + P89eadj;
+
+P6t = P5 + [ 0, outside_pushh - ourcirc_r*2 ];
+P7 = [ P6t[0] + (P6t[1] - P1[1]) / outside_pushslope,
+       P1[1] ];
+
+P3a = P3t + [ -outside_push_inadj, 0 ];
+P6a = P6t + [ -outside_push_inadj, 0 ];
+
+outside_push_inadj_slope = (P3t[1]-P4[1]) / (P6a[1]-P5[1]);
+
+ribble_rad = ribble_dia/2;
+
+kit_adj_shift = -opening_height - 2.0;
+
+module ExtrusionSect(){
+  cr = openingedge_dia/2;
+  toph = opening_height/2 + opening_protrh;
+
+  for (my=[0,1]) {
+    mirror([0,my]) {
+      translate(Q0) {
+       hull(){
+         circle(r=cr, $fn=20);
+         translate([-cr,10]) square([cr*2, 1]);
+       }
+      }
+    }
+  }
+  translate([-opening_depth, -toph]) {
+    difference(){
+      translate([-5,-5])
+       square([opening_depth+6, toph*2+10]);
+      square([opening_depth+2, toph*2]);
+    }
+  }
+}
+
+module PsHull(ps) {
+  hull(){
+    for (p = ps) {
+      translate(p)
+       circle(r = ourcirc_r, $fn=10);
+    }
+  }
+}
+
+module LeverSect(top, inadj=false){
+  P3 = inadj ? P3a : P3t;
+  P6 = inadj ? P6a : P6t;
+  P8 = top ? P8t : P8b;
+  P9 = top ? P9t : P9b;
+  difference(){
+    union(){
+      PsHull([P2,P3,P4]);
+      PsHull([P0,P1,P2,P5,P8,P9]);
+    }
+    hull(){
+      for (dp = [ [0,0],
+                 (P6-P5),
+                 (P3-P4)
+                 ]) {
+       translate(Q0 + 5*dp) circle(r=edge_or, $fn=20);
+      }
+    }
+  }
+}
+
+module StrapSectTop(){
+  translate(P9t + ourcirc_r * [+1,-1]) {
+    difference(){
+      circle(r = strap_below + strap_th, $fn=40);
+      circle(r = strap_below,            $fn=40);
+    }
+  }
+}
+
+module StrapSectBot(inadj=false){
+  mirror([0,1]){
+    for (dx = [ -(strap_below + strap_th),
+               0 ]) {
+      translate(P9b + [ ourcirc_r + dx, -10 ]) {
+       square([strap_th, 20]);
+      }
+    }
+  }
+}
+
+module Ribbles(xmax, xmin, y){
+  for (x = [ xmax + ourcirc_r - ribble_rad :
+            -ribble_rad * 4 :
+            xmin ]) {
+    translate([x, y])
+      circle(r = ribble_rad, $fn=20);
+  }
+}
+
+module LeverSectTop(){
+  difference(){
+    union(){
+      LeverSect(true, false);
+      Ribbles(P8t[0],
+             Q0[0] + edge_or + ribble_rad*2,
+             P5[1] + ourcirc_r);
+    }
+    translate([pivot_x,0]) circle(r= pivot_r + pivot_slop, $fn=20);
+  }
+}
+
+module LeverSectBot(inadj=false){
+  P6 = inadj ? P6a : P6t;
+  mirror([0,1]) {
+    LeverSect(false, inadj);
+    PsHull([P5,P6,P7]);
+    PsHull([P8b,P8eb,P9eb,P9b]);
+    Ribbles(P8eb[0],
+           P9b[0],
+           P8eb[1]);
+    translate([pivot_x,0]) circle(r=pivot_r, $fn=20);
+  }
+}
+
+module Demo(){
+  translate([0,0,-5]) color("white") ExtrusionSect();
+  LeverSectTop();
+  translate([0,0,5]) LeverSectBot();
+  color("black") LeverSectBot(true);
+  color("blue") translate([0,0,10]) StrapSectTop();
+  color("purple") translate([0,0,-10]) StrapSectBot();
+}
+
+module SomeLever() {
+  // SomeLever(){ LeverBot(inadj); LeverSectBot(); }
+  difference(){
+    linear_extrude(height=width, convexity=100) children(0);
+    for (i = [ 0 : nstraps - 1 ]) {
+      translate([0,0, (i + 0.5) / nstraps * width - strap_width/2])
+       linear_extrude(height=strap_width, convexity=10)
+       children(1);
+    }
+  }
+}
+
+module Test(){
+  linear_extrude(height=test_width, convexity=100) {
+    translate([0,2,0]) LeverSectTop();
+    LeverSectBot();
+    translate([0,kit_adj_shift]) LeverSectBot(true);
+  }
+}
+
+module LeverTop(){ ////toplevel
+  SomeLever(){
+    LeverSectTop();
+    StrapSectTop();
+  }
+}
+module LeverBotOutside(){ ////toplevel
+  SomeLever(){
+    LeverSectBot();
+    StrapSectBot();
+  }
+}
+module LeverBotInside(){ ////toplevel
+  SomeLever(){
+    LeverSectBot(true);
+    StrapSectBot(true);
+  }
+}
+
+module KitOutside(){ ////toplevel
+  translate([0,2,0]) LeverTop();
+  LeverBotOutside();
+}
+
+module KitInside(){ ////toplevel
+  translate([0,2,0]) LeverTop();
+  LeverBotInside();
+}
+
+//LeverSectBot(true);
+//Demo();
+//LeverTop();
+//Test();
+//Kit();
+//KitInside();
diff --git a/flyscreen-wall-spacer.scad b/flyscreen-wall-spacer.scad
new file mode 100644 (file)
index 0000000..4abe1ef
--- /dev/null
@@ -0,0 +1,100 @@
+// -*- C -*-
+
+include <utils.scad>
+
+bracket_th = 2.70;
+left_inboard_to_wall = 9.78;
+right_inboard_to_wall = 13.21;
+
+plug_dia = 10;
+screw_dia = 5;
+bucket_wall = 2.5;
+bucket_floor = 2.5;
+whole_dia = plug_dia + bucket_wall *2;
+
+min_spacing = 8;
+max_spacing = 19;
+
+general_spacer_height = 10;
+
+// calculated
+
+module Oval(r, dc) {
+  hull(){
+    circle(r);
+    translate([0, dc])
+      circle(r);
+  }
+}
+
+module MainCircle() {
+  difference(){
+    circle(r = whole_dia/2);
+    circle(r = screw_dia/2);
+  }
+}
+
+module MultiSpacer() {
+  difference(){
+    linextr(0, $inboard_to_wall - bracket_th){
+      Oval(whole_dia/2, max_spacing);
+    }
+
+    linextr(bucket_floor, 100) {
+      Oval(plug_dia/2, max_spacing);
+    }
+
+    linextr(-1, 100) {
+      circle(screw_dia/2);
+
+      translate([0, min_spacing])
+       Oval(screw_dia/2, max_spacing - min_spacing);
+    }
+  }
+}
+
+module AnySpacer(max_z) {
+  linextr(0, bucket_wall)
+    MainCircle();
+  linextr(0, max_z){
+    difference(){
+      MainCircle();
+      circle(r = plug_dia/2);
+    }
+  }
+}
+
+module Spacer($inboard_to_wall) {
+  AnySpacer($inboard_to_wall - bracket_th);
+}
+
+module Spacers1() {
+  for (dy = [0, 30]) {
+    translate([0,dy,0]) {
+      Spacer($inboard_to_wall = left_inboard_to_wall);
+      translate([0, 70, 0])
+       Spacer($inboard_to_wall = right_inboard_to_wall);
+    }
+  }
+
+  translate([40, 0, 0])
+    MultiSpacer($inboard_to_wall = left_inboard_to_wall);
+  translate([40, 70, 0])
+    MultiSpacer($inboard_to_wall = right_inboard_to_wall);
+}
+
+module Spacers2() {
+  for (dy = 30 * [0]) {
+    echo(dy);
+    translate([0, dy, 0])
+      AnySpacer(general_spacer_height);
+  }
+}
+
+module Spacers3() {
+  AnySpacer(6.08);
+  translate([0, 30, 0])
+    AnySpacer(8.18);
+}
+
+Spacers3();
diff --git a/fruit-bowl-stand.scad b/fruit-bowl-stand.scad
new file mode 100644 (file)
index 0000000..634f4d6
--- /dev/null
@@ -0,0 +1,51 @@
+// -*- C -*-
+
+include <utils.scad>
+
+across = 12.5;
+r0 = 71;
+h = 36;
+feet = 5;
+
+// calculated
+
+r_eff_across = across/2 * cos(360/8/2);
+
+effective_corner = across/2 * [ sin(360/8/2), cos(360/8/2) ];
+
+r_mid = r0 + effective_corner[0];
+
+h_mid = h - effective_corner[1] - r_eff_across;;
+
+module XSection(){
+  rotate(360/8/2)
+    circle(r= across/2, $fn=8);
+}
+
+module Ring(){
+  rotate_extrude($fa=1)
+    translate([r_mid, 0,0])
+    XSection();
+}
+
+module Foot(){
+  rotate([180,0,0])
+    linear_extrude(h_mid)
+    XSection();
+}
+
+module Stand(){
+  Ring();
+  intersection(){
+    for (a=[0:feet-1]) {
+      rotate([0,0, 360*a/feet])
+       translate([-r_mid, 0,0])
+       Foot();
+    }
+
+    linextr(-across - h, across)
+      circle(r= r_mid + r_eff_across, $fa=1);
+  }
+}
+
+Stand();
diff --git a/hole-repair-20191117.scad b/hole-repair-20191117.scad
new file mode 100644 (file)
index 0000000..0138c67
--- /dev/null
@@ -0,0 +1,49 @@
+// -*- C -*-
+
+post_dia = 23.0;
+post_height = 20;
+
+th = 4;
+
+nom_hole = 25;
+min_r = 15 + nom_hole/2;
+maj_r = 15 + nom_hole/2;
+
+postwall_th = 2;
+
+$fa=1;
+$fs=1;
+
+module Profile(r) {
+  polygon([ [0,    0],
+           [r,    0],
+           [r-th, th],
+           [0,    th] ]);
+}
+
+module Body(){
+  hull(){
+    for (x= [-1,+1] * (maj_r-min_r))
+      translate([x,0,0])
+       rotate_extrude()
+       Profile(min_r);
+  }
+}
+
+module Post(){
+  difference(){
+    translate([0,0,-1])
+      cylinder(r= post_dia/2, h= post_height+1);
+    translate([0,0,-2])
+      cylinder(r= post_dia/2 - postwall_th, h= post_height+3);
+  }
+}
+
+module Cover(){
+  rotate([0,180,0]) Body();
+  Post();
+}
+
+//Body();
+//Post();
+Cover();
diff --git a/hole-transfer-punch.scad b/hole-transfer-punch.scad
new file mode 100644 (file)
index 0000000..d9e4859
--- /dev/null
@@ -0,0 +1,32 @@
+// -*- C -*-
+
+include <utils.scad>
+
+dia = 6;
+
+point = 3;
+solid = 3;
+
+$fa= 1;
+$fs = 0.1;
+
+// calculated
+
+depth = dia * 1.5;
+
+module Blivet(){
+  rotate_extrude(){
+    polygon([[ 0,0 ],
+            [ point, 0 ],
+            [ point, solid ],
+            [ 0, solid + point]]);
+  }
+  linextr(0, solid){
+    square(center=true, [ dia*3, dia ]);
+  }
+  linextr(-depth, solid){
+    circle(r= dia/2);
+  }
+}
+
+Blivet();
diff --git a/holetest.scad b/holetest.scad
new file mode 100644 (file)
index 0000000..328ef2d
--- /dev/null
@@ -0,0 +1,52 @@
+// -*- C -*-
+
+h=2;
+
+$fa=1;
+$fs=0.1;
+
+label=true;
+
+spc= 7;
+l = 50;
+w = 10;
+
+lt = 0.5;
+lw = 10;
+
+// calculated
+
+// Actual sizes (according to calipers) of things that fit
+//  A
+//  C - 1.88mm (M3 screw)
+//  E - 2.97mm (3mm HSS bit shank)
+//  G - 
+//  I - 3.15mm tight fit (Yale padlock from extra padlocks tray)
+//  K - 3.33mm (M3.5 screw)
+
+ly0 = -w/2 -lw;
+
+difference(){
+  union(){
+    cube([l,w,h], center=true);
+    if (label)
+      translate([-l/2, ly0, -h/2])
+       cube([l, lw, lt]);
+  }
+
+  for (i=[0:2:10]) {
+
+    sz = 3 + 0.5 * i/10;
+
+    echo(sz);
+    translate([(i-5)/2 * spc, 0, -7 ]) {
+      cylinder(r= sz/2, h=14);
+      linear_extrude(height=14, convexity=100) {
+       translate([0, ly0 + lw * .2])
+         text(halign="center",
+              size= lw * .6, font="DejaVu Sans:style=Bold",
+              chr(i + 65));
+      }
+    }
+  }
+}
diff --git a/hotel-piece-model.scad b/hotel-piece-model.scad
new file mode 100644 (file)
index 0000000..2f8af81
--- /dev/null
@@ -0,0 +1,29 @@
+// -*- C -*-
+
+h = 15;
+w = 20;
+l = 30;
+roof = 10;
+eave = 1;
+peakw = 1;
+overhang = 3.0;
+chimnd= 7;
+chimnhr = 1.00;
+
+$fs = 0.1;
+
+module Hotel(){
+  cube([w,l,h + 0.1]);
+  hull(){
+    translate([0,0, h] + overhang * [-1,-1,0])
+      cube([w,l,eave] + overhang * [2,2,0]);
+    translate([0,0, h] + overhang * [0,-1,0]
+             + (w-peakw) * 0.5 * [1,0,0])
+      cube([peakw, l, roof] + overhang * [0,2,0]);
+  }
+  translate([w/4, l/2, h] + overhang * [-0.5, 0,0])
+    cylinder(r= chimnd/2, h = roof * chimnhr);
+}
+
+scale(1.11)
+Hotel();
diff --git a/itx-aperture-grommet.scad b/itx-aperture-grommet.scad
new file mode 100644 (file)
index 0000000..53f74fa
--- /dev/null
@@ -0,0 +1,56 @@
+// -*- C -*-
+
+include <utils.scad>
+include <threads.scad>
+
+ap_width = 21.30;
+ap_height = 16.45;
+tot_height = 22.74 + 1.0;
+screw_ctr_to_tr = [ 7.89, 3.87 ];
+
+tab_th = 2.5;
+wall_th = 1;
+wall_h = 2;
+
+app_slop = 0.60; // total across both sides
+
+// calculated
+
+tab_h = tot_height - ap_height;
+
+real_main_sz = [ ap_width, ap_height ] - app_slop * [ 1,1 ];
+real_all_sz = real_main_sz + tab_h * [0,1];
+real_inner_sz = real_main_sz - wall_th * [ 2,1 ];
+
+screw_pos = real_all_sz - (screw_ctr_to_tr - 0.5 * app_slop * [1,1]);
+
+module GapPlan() {
+  rectfromto([ wall_th, -1 ],
+            real_main_sz - wall_th * [1,1]);
+}
+
+module MainPlan() {
+  rectfromto([0,0],
+            real_main_sz);
+}
+
+module AllPlan() {
+  rectfromto([0,0], real_all_sz);
+}
+
+module Grommet(){ ////toplevel
+  difference(){
+    union(){
+      linextr(0, tab_th + wall_h) MainPlan();
+      linextr(0, tab_th) AllPlan();
+    }
+    linextr(-1, tab_th + wall_h + 1) GapPlan();
+    translate(concat(screw_pos, [-1]))
+      english_thread(diameter = 0.1380,
+                    threads_per_inch = 32,
+                    length = tab_th + 2,
+                    internal = true);
+  }
+}
+
+Grommet();
diff --git a/keyring-kay.scad b/keyring-kay.scad
new file mode 100644 (file)
index 0000000..9ebb34a
--- /dev/null
@@ -0,0 +1,59 @@
+// -*- C -*-
+
+letterheight = 25;
+linewidth = 3.5;
+letterthick = 2.5;
+
+basethick = 2.5;
+
+xborder = 5;
+yborder = 5;
+
+kdiag = 1;
+kprop = 0.50;
+
+diaglinewidth = linewidth * sqrt(1 + kdiag*kdiag);
+
+ringholerad = 2.5;
+ringedgewidth = 3;
+
+module kay_leg (transamount, llen, mir) {
+  translate([0,transamount,0])
+    mirror([0,mir,0])
+    translate([0,-0.1,0])
+    multmatrix([[1,kdiag,0,0],
+               [0,1,0,0],
+               [0,0,1,0],
+               [0,0,0,1]])
+    cube([diaglinewidth, llen + 0.1, letterthick]);
+}  
+
+module kay () {
+  translate([0.1,0,0])
+    cube([linewidth, letterheight, letterthick]);
+  kay_leg(letterheight*kprop, letterheight*(1-kprop), 0);
+  kay_leg(letterheight*kprop, letterheight*kprop, 1);
+}
+
+totalw = letterheight*kprop + diaglinewidth + xborder*2;
+totalh = letterheight + yborder*2;
+basez = -(basethick-0.1);
+
+module main () {
+  kay();
+  translate([-xborder, -yborder, basez])
+    cube([totalw, totalh, basethick]);
+}
+
+module ring (rad, extra) {
+  translate([totalw/2 - xborder, totalh - yborder, basez-extra])
+    cylinder(r=rad, h=basethick + extra*2, $fn=30);
+}
+
+difference(){
+  union() {
+    main();
+    ring(ringholerad + ringedgewidth/2, 0);
+  }
+  ring(ringholerad, 1);
+}
diff --git a/knifeblock,BlockPrint.auto.slic3r b/knifeblock,BlockPrint.auto.slic3r
new file mode 100644 (file)
index 0000000..22dacf8
--- /dev/null
@@ -0,0 +1 @@
+fill_density = 0.1
diff --git a/knifeblock-knives-filter b/knifeblock-knives-filter
new file mode 100755 (executable)
index 0000000..17cff74
--- /dev/null
@@ -0,0 +1,36 @@
+#!/usr/bin/perl -w
+
+use strict;
+use POSIX;
+
+our %want;
+
+our $nknives = 3;
+
+our @part_order = qw(h b l);
+
+my $want = shift @ARGV;
+my ($wknife,$wparts) = $want =~ m/^(\d)([a-z]+)$/ or die;
+
+sub want ($) {
+    my ($colournum) = @_;
+    my $knife = $nknives-1 - ($colournum-1) % $nknives;
+    return 0 unless $knife == $wknife;
+    my $part = $part_order[ floor(($colournum-1) / $nknives) ];
+    die "huh colour $colournum?" unless defined $part;
+    return 0 unless $part =~ m/[$wparts]/o;
+    print STDERR "$0: including colour $colournum ($knife $part)\n";
+    return 1;
+}
+
+our $drop;
+while (<>) {
+    if (m/^\S/) {
+       $drop = 
+           m/^2 5 / ||
+           (m/^(?:3 1|2 3) \d+ \d+ (\d+) / && !want($1));
+    }
+    next if $drop;
+
+    print or die $!;
+}
diff --git a/knifeblock-knives-photo.jpg b/knifeblock-knives-photo.jpg
new file mode 100644 (file)
index 0000000..0d757cc
Binary files /dev/null and b/knifeblock-knives-photo.jpg differ
diff --git a/knifeblock-knives-trace.fig b/knifeblock-knives-trace.fig
new file mode 100644 (file)
index 0000000..c42c2c6
--- /dev/null
@@ -0,0 +1,66 @@
+#FIG 3.2  Produced by xfig version 3.2.5b
+Landscape
+Center
+Metric
+A4      
+100.00
+Single
+-2
+1200 2
+2 5 0 1 0 -1 50 -1 -1 0.000 0 0 -1 0 0 5
+       0 knifeblock-knives-photo.jpg
+        585 270 11557 270 11557 8499 585 8499 585 270
+2 3 1 1 9 7 30 -1 -1 4.000 0 0 7 0 0 6
+        3093 6395 6633 6470 10898 6641 5808 7070 5818 6778 3093 6395
+2 3 1 1 8 7 30 -1 -1 4.000 0 0 7 0 0 6
+        3038 4586 6578 4661 10843 4832 5753 5261 5763 4969 3038 4586
+2 3 1 1 7 7 30 -1 -1 4.000 0 0 7 0 0 6
+        2978 2996 6518 3071 10783 3242 5693 3671 5703 3379 2978 2996
+3 1 0 1 2 7 40 -1 -1 0.000 0 0 0 21
+        5858 5445 6063 5290 6283 5115 6523 4865 6658 4670 6768 4510
+        6758 4405 6438 4365 5923 4315 5423 4295 4823 4290 4418 4300
+        4003 4335 3648 4380 3593 5170 3883 5190 4368 5185 4598 5160
+        4993 5075 5718 5365 5838 5435
+        1.000 1.000 1.000 1.000 1.000 1.000 1.000 1.000
+        1.000 1.000 1.000 1.000 1.000 1.000 1.000 1.000
+        1.000 1.000 1.000 1.000 1.000
+3 1 0 1 3 7 40 -1 -1 0.000 0 0 0 23
+        5878 6218 5318 6198 4858 6198 4463 6208 4118 6223 3908 6258
+        3908 6953 4293 6978 4668 6938 5078 6873 5693 7028 5718 7113
+        5783 7143 5873 7128 6028 7028 6243 6833 6453 6608 6583 6458
+        6638 6398 6598 6348 6548 6303 6418 6283 6033 6243
+        1.000 1.000 1.000 1.000 1.000 1.000 1.000 1.000
+        1.000 1.000 1.000 1.000 1.000 1.000 1.000 1.000
+        1.000 1.000 1.000 1.000 1.000 1.000 1.000
+3 1 0 1 1 7 40 -1 -1 0.000 0 0 0 20
+        5918 3753 6120 3555 6435 3285 6615 3060 6840 2745 6975 2520
+        6975 2475 6615 2430 5985 2385 5490 2355 4950 2340 4498 2360
+        3883 2385 3458 2425 3443 3430 3838 3435 4248 3410 5593 3723
+        5783 3858 5773 3848
+        1.000 1.000 1.000 1.000 1.000 1.000 1.000 1.000
+        1.000 1.000 1.000 1.000 1.000 1.000 1.000 1.000
+        1.000 1.000 1.000 1.000
+3 1 0 1 4 7 40 -1 -1 0.000 0 0 0 23
+        9223 2689 8811 2659 8398 2621 7993 2606 7603 2584 7228 2569
+        6861 2561 5871 2539 5121 3529 5818 3694 5991 3769 6583 3776
+        7206 3769 8113 3746 8826 3709 9298 3649 9816 3581 10311 3431
+        10737 3204 10702 3099 10551 3026 10243 2869 9606 2734
+        1.000 1.000 1.000 1.000 1.000 1.000 1.000 1.000
+        1.000 1.000 1.000 1.000 1.000 1.000 1.000 1.000
+        1.000 1.000 1.000 1.000 1.000 1.000 1.000
+3 1 0 1 5 7 40 -1 -1 0.000 0 0 0 23
+        7191 5366 8098 5329 8586 5291 9051 5194 9298 5119 9652 4993
+        9802 4898 9812 4828 9722 4748 9283 4676 9028 4616 8706 4594
+        8278 4549 7851 4534 7371 4534 6883 4511 6141 4511 5998 4744
+        5661 4969 5593 5366 6043 5351 6478 5366 7026 5374
+        1.000 1.000 1.000 1.000 1.000 1.000 1.000 1.000
+        1.000 1.000 1.000 1.000 1.000 1.000 1.000 1.000
+        1.000 1.000 1.000 1.000 1.000 1.000 1.000
+3 1 0 1 6 7 40 -1 -1 0.000 0 0 0 21
+        6816 7069 7191 7047 7558 6994 7896 6927 8097 6852 8277 6772
+        8267 6677 8067 6622 7881 6544 7611 6499 7356 6454 7093 6432
+        6838 6409 6561 6409 6096 6387 5901 6454 5578 6784 5668 7024
+        5811 7069 6141 7084 6388 7077
+        1.000 1.000 1.000 1.000 1.000 1.000 1.000 1.000
+        1.000 1.000 1.000 1.000 1.000 1.000 1.000 1.000
+        1.000 1.000 1.000 1.000 1.000
diff --git a/knifeblock.scad b/knifeblock.scad
new file mode 100644 (file)
index 0000000..c1f1869
--- /dev/null
@@ -0,0 +1,346 @@
+// -*- C -*-
+
+// properties of the knives
+nknives = 3;
+widths = [15.5, 15.8, 19.0];
+handlelenbase = 75;
+handlelendelta = [-15, 0, 10];
+locations = [-35, 0, 37];
+bladew = 5; // 2.5
+maxhandledepth = 45;
+
+templatescale = 27.2 / 19.6;
+
+coverlonglen = 120; // xxx
+covershortlen = 70; // xxx
+
+// other tuneables
+front = 5;
+back = 5;
+height = 50;
+minsidein = 4;
+minsideout = 4;
+
+frontbackslop = 0.25;
+
+knifewidthslop = 2.0;
+
+screwbackdepth = 6.0 - 1.0;
+screwdia =       4.0 + 0.5;
+screwcsinkdia =  9.8 + 1.0;
+
+screwabove = 15;
+
+coverthick = 2.4;
+coverside = coverthick;
+
+covertopwing = 15;
+covertopwingbase = 20;
+coveredge = 3;
+
+holesize = 12.5;
+holestrut = 7;
+holeedge = 4;
+
+holeoffx = 0.33;
+holeoffy = 0.23;
+
+indentdepth = 1;
+indentoutersize = holesize + 2.15;
+indentinnersize = indentoutersize - indentdepth * 3.0;
+
+pegstem = 3.5;
+peghead = 10;
+pegstemheight = 2;
+pegheight = 9;
+peglen = 12;
+
+recessblockwidth = peghead + pegstem*3;
+recessblockheight = peglen/2 + pegstem*1.5;
+
+pegsloph = 0.5;
+pegslopv = 0.5;
+pegslopl = 0.5;
+
+pegdepthproportion = 0.80;
+
+// computed
+
+function width(k) = widths[k] + knifewidthslop;
+
+side = minsidein + screwcsinkdia + minsideout;
+totaldepth = front + maxhandledepth + back;
+
+minkx = locations[0] -         width(0)        /2;
+maxkx = locations[nknives-1] + width(nknives-1)/2;
+
+minx = minkx - side;
+maxx = maxkx + side;
+
+holepitch = holesize+holestrut;
+
+pegrecess = pegdepthproportion*totaldepth - 0.5*peglen;
+
+module ImportTemplate(w,k,t) {
+  fn = str("knifeblock-knives-t",k,t,".dxf");
+  echo(fn);
+  translate([0,0, -w/2])
+    linear_extrude(height=w)
+    scale(templatescale) import(file=fn, convexity=100);
+}
+
+module Knife(k){
+  ImportTemplate(bladew, k,"bl");
+  hull(){
+    ImportTemplate(width(k), k,"hl");
+    translate([-100,0,0])
+      ImportTemplate(width(k), k,"hl");
+  }
+}
+
+module DoKnife(k){
+  translate([locations[k],0,0]){
+    rotate([0,90,0])
+      translate([-(handlelenbase + handlelendelta[k]),0,0])
+      Knife(k);
+  }
+}
+
+module DoKnives(){
+  for (k=[0:nknives-1])
+    DoKnife(k);
+}
+
+module ScrewHole(){
+  translate([0,-50,0])
+    rotate([-90,0,0])
+    cylinder(r=screwdia/2, h=150, $fn=40);
+  translate([0, totaldepth-front - screwbackdepth, 0])
+    rotate([90,0,0])
+    cylinder(r=screwcsinkdia/2 / (sqrt(3)/2), h=100, $fn=6);
+}
+
+module PegTemplate(apex){
+  for (mx=[0,1]) for (my=[0,1]) {
+      mirror([mx,0,0]) mirror([0,my,0])
+       polygon([[-0.1,      -0.1],
+                [pegstem/2, -0.1],
+                [pegstem/2, pegstemheight/2],
+                [peghead/2, pegheight    /2],
+                [-0.1,      pegheight    /2 + apex]]);
+    }
+}
+
+module AtSides(){
+  translate([minx,0,0])                 child(0);
+  translate([maxx,0,0]) mirror([1,0,0]) child(1);
+}
+
+module BlockPegSlot(){
+  translate([recessblockwidth/2, pegrecess - peglen, -height]){
+    rotate([-90,0,0]) linear_extrude(height=totaldepth){
+      PegTemplate(peghead/2 * 1.2);
+    }
+  }
+}
+
+module DecorativeIndents(){
+  translate([0, -front, 0])
+  rotate([90,0,0])
+  HexGrid(-height, 0, minx,maxx) {
+    hull(){
+      translate([0, 0, -indentdepth])
+       cylinder(r=indentinnersize/2, h=indentdepth, $fn=40);
+      cylinder(r=indentoutersize/2, h=indentdepth, $fn=40);
+    }
+  }
+}
+
+module Block(){
+  sidemidx = minsideout + screwcsinkdia/2;
+
+  difference(){
+    mirror([0,0,1]) {
+      translate([minx, -front, 0])
+       cube([maxx-minx, totaldepth, height]);
+    }
+    for (x=[minx + sidemidx, maxx - sidemidx]) {
+      translate([x, 0, -screwabove])
+       ScrewHole();
+    }
+    for (yshift=[-1,1])
+      translate([0, yshift * frontbackslop, 0])
+       DoKnives();
+    AtSides() { BlockPegSlot(); BlockPegSlot(); }
+    DecorativeIndents();
+  }
+}
+
+module BlockPrint(){ ////toplevel
+  rotate([0,0,90])
+    Block();
+}
+
+module CoverTemplate(){
+  linear_extrude(height=coverthick)
+    polygon([[minx, 0],
+            [maxx, 0],
+            [maxx, coverlonglen+0.1],
+            [maxx - coverside, coverlonglen+0.1],
+            [minx, covershortlen+0.1]]);
+}
+
+module CoverSide(len){
+  translate([0, 0 ,0]) {
+    rotate([90,0,90])
+      linear_extrude(height=coverside)
+      polygon([[0,                      0],
+              [0,                      totaldepth],
+              [covertopwing,           totaldepth],
+              [covertopwingbase,       coverside + coverthick],
+              [len - covertopwingbase, coverside + coverthick],
+              [len - covertopwing,     totaldepth],
+              [len,                    totaldepth],
+              [len,                    0]]);
+    cube([recessblockwidth, recessblockheight, totaldepth]);
+  }
+}
+
+module Peg(){
+  echo("peg angle slope (must be <1)",
+       (peghead-pegstem)/(pegheight-pegstemheight));
+  dx = pegsloph;
+  dy = pegslopv;
+  rotate([90,0,0]) {
+    linear_extrude(height=peglen-pegslopl) {
+      intersection(){
+       translate([-dx,-dy,0]) PegTemplate(0);
+       translate([-dx,+dy,0]) PegTemplate(0);
+       translate([+dx,+dy,0]) PegTemplate(0);
+       translate([+dx,-dy,0]) PegTemplate(0);
+      }
+    }
+  }
+}
+
+module CoverPegSlot(coverlen){
+  translate([recessblockwidth/2, 0, -1]){
+    linear_extrude(height= 1 + pegrecess + 0.5*peglen){
+      PegTemplate(0);
+    }
+  }
+}
+
+module HolesScope(){
+  intersection_for (dx=[-1,+1]) {
+    intersection_for (dy=[-1,+1]) {
+      translate([dx * holeedge, dy * holeedge, -5])
+       scale([1,1,10])
+       CoverTemplate();
+    }
+  }
+}
+
+module HexGrid(xa,xb,ya,yb) {
+  imin = floor(xa / holepitch);
+  imax =  ceil(xb / holepitch);
+  jmin = floor(ya / (sqrt(3)*holepitch));
+  jmax =  ceil(yb / (sqrt(3)*holepitch));
+  echo("HexGrid ",imin,imax,jmin,jmax);
+  for (i=[imin:imax]) {
+    for (j=[jmin:jmax]) {
+      translate([(j * sqrt(3) + holeoffx) * holepitch,
+                (i +     0.5 + holeoffy) * holepitch,
+                0]) {
+       child();
+       translate([sqrt(3)/2 * holepitch, -0.5 * holepitch, 0])
+         child();
+      }
+    }
+  }
+}
+
+module Hole(){
+  cylinder(r=holesize/2, h=40, $fn=40);
+}
+
+module Holes(){
+  intersection(){
+    translate([0, 0, -20])
+      HexGrid(0, coverlonglen, minx, maxx)
+      Hole();
+    HolesScope();
+  }
+}
+
+module CoverCover(){
+  difference(){
+    CoverTemplate();
+    Holes();
+  }
+}
+
+module Cover(){
+  difference(){
+    union(){
+      CoverCover();
+      AtSides() { CoverSide(covershortlen); CoverSide(coverlonglen); }
+    }
+    AtSides() { CoverPegSlot(); CoverPegSlot(); }
+  }
+}
+
+module CoverAligned(){
+  translate([0,-front,-height])
+    rotate([-90,0,0])
+    Cover();
+}
+
+module DemoPeg(){
+  translate([recessblockwidth/2, pegrecess, -height])
+    Peg();
+}
+
+module Demo(){ ////toplevel
+  %Block();
+  DoKnives();
+  color([0,0,1]) CoverAligned();
+  color([1,0,0]) AtSides() { DemoPeg(); DemoPeg(); }
+}
+
+module Pegs(){ ////toplevel
+  Peg();
+  translate([-peghead-3, 0,0]) Peg();
+}
+
+module CoverParts(){ ////toplevel
+  Cover();
+  translate([0, coverlonglen, pegheight/2-pegslopv])
+    Pegs();
+}
+
+module FrontDemo(){ ////toplevel
+  color([1,0,1]) Block();
+  color([1,0,1]) CoverAligned();
+  color([0,0,0]) DoKnives();
+}
+
+module BlockFrontTest(){ ////toplevel
+  intersection(){
+    Block();
+    translate([minx-10, -front-10, -height-10]) {
+      cube([75,14,35]);
+      cube([75,25,13]);
+    }
+  }
+}
+
+//Block();
+//Demo();
+//Cover();
+//CoverParts();
+//Peg();
+//Cover();
+//Holes();
+//%CoverTemplate();
+//Pegs();
diff --git a/ksafe-base.scad b/ksafe-base.scad
new file mode 100644 (file)
index 0000000..c2e2704
--- /dev/null
@@ -0,0 +1,373 @@
+// -*- C -*-
+
+// from actual ksafe
+bolt_above = 8.50 - 0.50;
+bolthole_height = 4.24 + 1.00;
+wall_thick = 4.50;
+bolthole_width = 16.62 + 1.00;
+main_sz = 149.06 - 0.25;
+cnr_rad = 19.5;
+lidinner_thick_allow = 20.78 + 0.50;
+display_width = 69.81 - 0.50;
+
+dpp3 = [ -5.5, 8.5 ];
+dpp2 = [ -11.0, 7.0 ];
+dpp1 = [ -34.0, 14.0 ];
+
+// other parameters
+web_thick = 4;
+web_height = 20; // excluding wall and base thick
+bolthole_rhs = 20;
+bolthole_lhs = 20;
+boltreinf_thick = 6;
+anchor_wall_space = 25;
+base_thick = 4;
+space = 25;
+anchor_thick = 4;
+anchor_rad = 4;
+bevel = 8;
+string_slot = 3.0;
+string_depth = 6.0;
+thumbslot_depth = 5.0;
+thumbslot_width = 15.0;
+thumbslot_between = 10;
+ksafecover_lip = 4.62;
+display_rhs_secs = 15;
+dcover_endthick = 3.0;
+dcover_mainthick = 5.0;
+dcover_slop_height = 0.35;
+dcover_slop_depth = 0.25;
+dcover_slop_inside = 1.50;
+dcover_commonvertoff = 0.00; // slop_height or slop_inside is added too
+dcover_edge_gap_more_width = 2.0; // each side
+
+// ----- calculated -----
+
+hsz = main_sz/2;
+cut = main_sz + 20;
+
+gppA = [0,0];
+gppB = gppA - [ wall_thick, 0 ];
+
+gppL = [ gppB[0], -(lidinner_thick_allow + space + base_thick) ];
+
+yw1 = -web_thick/2;
+yw2 = yw1 - bolthole_rhs;
+yw3 = yw2 - anchor_thick;
+yw4 = yw3 - anchor_wall_space;
+yw5 = yw4 - wall_thick;
+yw6 = -(hsz - cnr_rad + 0.1);
+
+yw10 = web_thick/2;
+yw11 = yw2 + anchor_wall_space;
+yw12 = yw11 + wall_thick;
+yw13 = -yw6;
+
+cpp1 = dpp1 + [  dcover_slop_depth, dcover_slop_height ];
+cpp2 = [ dpp2[0] - dcover_slop_depth, dpp3[1] + dcover_slop_height ];
+cppH = cpp1 + [ 0, dcover_endthick ];
+cppA = [ cpp2[0], dpp3[1] + dcover_slop_inside ];
+cppK = cppA + [ 0, dcover_mainthick ];
+cppZ = [ -ksafecover_lip, -dcover_commonvertoff ];
+cppD = cppZ + [ 0, -dcover_slop_inside ];
+cppC = [ dcover_slop_inside, cppD[1] ];
+cppF = cppC + dcover_mainthick * [1,-1];
+cppB = [ cppC[0], cppA[1] ];
+cppG = [ cppF[0], cppK[1] ];
+cppE = [ cppD[0], cppF[1] - (cppF[0] - cppD[0]) ];
+
+// anchor
+
+anchor_b = anchor_thick + anchor_rad;
+appM = gppL + anchor_b * [1,1];
+
+a_bevel = 2 * anchor_b * (1 + sqrt(0.5));
+
+module upp_app_Vars(t_bevel){
+  $xppE = gppL + t_bevel * [0,1];
+  $xppF = gppL + t_bevel * [1,0];
+
+  $xppJ = $xppE + wall_thick * [ 1, tan(22.5) ];
+  $xppI = $xppF + base_thick * [ tan(22.5), 1 ];
+
+  // must pass a_bevel for t_bevel for these to be valid
+  $gppP = gppA + [0,-1] * lidinner_thick_allow;
+  $gppQ = $gppP + [1,-1] * web_height;
+  $gppR = $xppJ + [ 1, tan(22.5) ] * web_height;
+  $gppS = $xppI + [ tan(22.5), 1 ] * web_height;
+  $gppT = [ $gppQ[0], $xppE[1] ];
+
+  children();
+}
+
+module upp_app_Profile(){
+  polygon([ gppA,
+           gppB,
+           $xppE,
+           $xppF,
+           $xppF + [1,0],
+           $xppI + [1,0],
+           $xppJ ],
+         convexity=10);
+}
+
+
+module UsualProfile(){
+  upp_app_Vars(bevel) upp_app_Profile();
+}
+
+module NearAnchorProfile(){
+  upp_app_Vars(a_bevel) upp_app_Profile();
+}
+
+module AnchorProfile(){
+  upp_app_Vars(a_bevel) {
+
+    upp_app_Profile();
+
+    difference(){
+      hull(){
+       polygon([ $xppE,
+                 $xppF,
+                 $xppF + [0,1],
+                 $xppE + [1,0] ],
+         convexity=10);
+       translate(appM) circle(r= anchor_b);
+      }
+      translate(appM) circle(r= anchor_rad);
+    }
+  }
+}
+
+module AnchorWallProfile(){
+  UsualProfile();
+  NearAnchorProfile();
+  hull(){
+    for (bev = [bevel, a_bevel]) {
+      upp_app_Vars(bev) {
+       polygon([ $xppE,
+                 $xppF,
+                 $xppI,
+                 $xppJ ],
+         convexity=10);
+      }
+    }
+  }
+}
+
+module WebProfile(){
+  upp_app_Vars(a_bevel){
+    if ($gppR[1] <= $gppP[1]) {
+      polygon([ $gppP,
+               $xppE,
+               $gppT,
+               $gppQ ]);
+      polygon([ $gppP,
+               $xppE,
+               $xppF,
+               $gppS,
+               $gppR ],
+         convexity=10);
+    } else {
+      polygon([ $gppP,
+               $xppE,
+               $xppF,
+               $gppS,
+               $gppP + web_height * [1,0] ],
+         convexity=10);
+    }
+    polygon([ $gppS,
+             $xppF,
+             $xppF + [1,0],
+             $gppS + [1,0] ],
+         convexity=10);
+  }
+}
+
+module SomeBaseProfile(I, F){
+  polygon([ I,
+           F,
+           [ hsz+1, F[1] ],
+           [ hsz+1, I[1] ] ]);
+}
+
+module BaseProfile(){
+  SomeBaseProfile($xppI, $xppF);
+}
+
+module DCoverProfileRaw(){
+  polygon([ cpp1,
+           cpp2,
+           cppA,
+           cppB,
+           cppC,
+           cppD,
+           cppE,
+           cppF,
+           cppG,
+           cppK,
+           cppH ],
+         convexity = 10);
+}
+
+module DCoverProfile(){
+  mirror([1,0])
+    translate(-cppZ)
+    DCoverProfileRaw();
+}
+
+module SWalls(ymin, ymax, t_bevel) {
+  upp_app_Vars(t_bevel) {
+    translate([0,ymin,0])
+      mirror([0,1,0])
+      rotate([90,0,0])
+      linear_extrude(height= ymax-ymin, convexity=10)
+      for (xm=[0,1])
+       mirror([xm,0])
+         translate([-hsz, 0])
+           children();
+  }
+}
+
+module AtTwoCorners(){
+  for (xm=[0,1]) {
+    mirror([xm,0,0]) 
+    translate((hsz - cnr_rad) * [1,1])
+    intersection(){
+      rotate_extrude(convexity=10)
+       translate([-cnr_rad,0])
+       children();
+      translate([0,0,-250])
+       cube([50,50,500]);
+    }
+  }
+}
+
+module Box(){
+  /// corners, and front and back of base
+  for (ym=[0,1]) mirror([0,ym,0]) {
+    AtTwoCorners(){
+      UsualProfile();
+    }
+    hull() AtTwoCorners(){
+      upp_app_Vars(bevel){
+       polygon([ $xppI,
+                 $xppF,
+                 $xppF + [0.1, 0],
+                 $xppI + [0.1, 0]
+                 ]);
+      }
+    }
+  }
+
+  // side walls and base
+  SWalls(yw6 , yw4 , bevel  ) { UsualProfile();      BaseProfile(); }
+  SWalls(yw5 , yw4 , a_bevel) { AnchorWallProfile(); BaseProfile(); }
+  SWalls(yw5 , yw12, a_bevel) { NearAnchorProfile(); BaseProfile(); }
+  SWalls(yw3 , yw2 , a_bevel) { AnchorProfile();     BaseProfile(); }
+  SWalls(yw11, yw12, a_bevel) { AnchorWallProfile(); BaseProfile(); }
+  SWalls(yw11, yw13, bevel  ) { UsualProfile();      BaseProfile(); }
+  SWalls(yw1,  yw10, a_bevel) { WebProfile(); SomeBaseProfile($gppS, $xppF); }
+
+  // front and rear walls
+  rotate([0,0,90]) SWalls(yw6, yw13, bevel) UsualProfile();
+}
+
+module DCover(){ ////toplevel
+  translate([ -display_width/2, -hsz, 0 ])
+    rotate([0,90,0])
+    rotate([0,0,90])
+    linear_extrude( display_width - display_rhs_secs, convexity = 10)
+    DCoverProfile();
+}
+
+module DCoverSupportAllowance(){
+  translate([0, -hsz, 0])
+    cube(center=true,
+        [ display_width + 2 * dcover_edge_gap_more_width,
+          wall_thick * 2,
+          dcover_slop_inside * 2 + 0.01 ]);
+}
+
+module BoltHoles(){
+  translate([0,0, -(bolt_above + 0.5 * bolthole_height)])
+    cube(center=true, [ cut, bolthole_width, bolthole_height ]);
+}
+
+module KsafeBase(){ ////toplevel
+  DCover();
+
+  difference(){
+    Box();
+
+    BoltHoles();
+
+    // string slot
+    translate([ -cut,
+               -(bolthole_width/2 + bolthole_rhs),
+               1 ])
+      mirror([0,1,0]) mirror([0,0,1])
+      cube([ cut*2,
+            string_slot,
+            lidinner_thick_allow + string_depth + 1 ]);
+
+    // thumb slots
+    for (mx=[0,1]) mirror([mx,0,0]) {
+      translate([ thumbslot_between/2,
+                 0,
+                 -thumbslot_depth ])
+       cube([ thumbslot_width,
+              cut,
+              thumbslot_depth+1 ]);
+    }
+
+    DCoverSupportAllowance();
+  }
+}
+
+module DemoProfiles(){ ////toplevel
+  translate([0,0,-2]) color("yellow") AnchorWallProfile();
+  color("red") AnchorProfile();
+  translate([0,0,2]) color("black") NearAnchorProfile();
+  translate([0,0,4]) color("blue") UsualProfile();
+  translate([0,0,-4]) color("pink") WebProfile();
+  translate([0,0,6]) color("purple") DCoverProfile();
+}
+
+module RimTest(){ ////toplevel
+  intersection(){
+    Box();
+    cube(center=true, [ main_sz*2, main_sz*2,
+                       2.5 ]);
+  }
+}
+
+module DCoverTest(){ ////toplevel
+  intersection(){
+    difference(){
+      union(){
+       Box();
+       DCover();
+      }
+      DCoverSupportAllowance();
+      BoltHoles();
+    }
+    translate([0,0,60])
+    cube(center=true, [ main_sz*2, main_sz*2,
+                       2 * (60 + 10) ]);
+  }
+}
+
+module BoltTest(){ ////toplevel
+  dy = 0.5 * (bolthole_width+4);
+  intersection(){
+    KsafeBase();
+    translate([ 0, -dy, -(bolt_above + bolthole_height + 1) ])
+      cube([ main_sz, dy*2, 50 ]);
+  }
+}
+
+//DemoProfiles();
+//Box();
+//KsafeBase();
+//RimTest();
diff --git a/laptop-camera-tripod-bracket.scad b/laptop-camera-tripod-bracket.scad
new file mode 100644 (file)
index 0000000..cc6ffb2
--- /dev/null
@@ -0,0 +1,76 @@
+// -*- C -*-
+
+// High Detail
+// support enabled
+//   distance x/y 2.5mm
+// fill density 30%
+
+laptop_w       = 310;
+laptop_th_rear  = 14;
+laptop_th_front = 11;
+laptop_halfdepth_real = 125;
+laptop_halfdepth = 115; //125;
+laptop_inner_x_clear = 95;
+
+include <utils.scad>
+include <camera-mount.scad>
+
+bar_w = 15;
+bar_th = 12;
+flex_allow = 3;
+claw_th = 6;
+claw_overlap = 15;
+mount_dia = 50;
+
+min_ceil = 1;
+
+//$test=true;
+$test=false;
+
+module ClawProfile(laptop_th){
+  laptop_zmin = bar_th + flex_allow;
+  laptop_zmax = laptop_zmin + laptop_th;
+  difference(){
+    rectfromto([-claw_overlap, 0],
+              [claw_th, laptop_zmax + claw_th]);
+    rectfromto([-claw_overlap-1, laptop_zmin ],
+              [ 0, laptop_zmax ]);
+  }
+}
+
+module ClawBar(inner_len, laptop_th){
+  rotate([0,0,180]) linextr_y_xz(-bar_w/2, +bar_w/2) {
+    rectfromto([0,0],
+              [inner_len, bar_th]);
+    translate([inner_len, 0])
+      ClawProfile(laptop_th);
+  }
+}
+
+module Body(){
+  translate([0, laptop_halfdepth - laptop_halfdepth_real, 0])
+    for (m=[0,1]) {
+      mirror([m,0]) {
+       ClawBar(laptop_w/2, laptop_th_rear);
+       translate([ laptop_w/2 - laptop_inner_x_clear + bar_w/2, 0])
+         rotate([0,0,-90])
+         ClawBar(laptop_halfdepth, laptop_th_front);
+      }
+    }
+  cylinder(r = mount_dia/2, h= bar_th);
+}
+
+module Bracket(){
+  difference(){
+    Body();
+    rotate([0,180,0])
+      CameraMountThread( bar_th + 2 );
+  }
+  translate([0,0, bar_th-min_ceil])
+    cylinder(r= mount_dia/2-1, h=min_ceil);
+}
+
+//ClawProfile(laptop_th_front);
+//ClawBar(125, laptop_th_front);
+//Body();
+Bracket();
diff --git a/laptop-sound-cable-hooks.scad b/laptop-sound-cable-hooks.scad
new file mode 100644 (file)
index 0000000..8cfd468
--- /dev/null
@@ -0,0 +1,145 @@
+// -*- C -*-
+
+include <utils.scad>
+
+wall_th = 2;
+hook_th = 4;
+hook_hole = 4;
+hook_w_min = 6;
+hook_hook = 12;
+
+plug_entry_gap = 2.0;
+
+plug_l_d = [[ 27.78,
+             10.62 + 0.75 ],
+           [ 40.88,
+             8.56 + 0.75 ],
+           ];
+
+plug_stem = [ 2.72 + 0.50,
+             5.20 + 0.50 ];
+
+palmrest_from_plug_z = 3.98;
+laptop_th = 16.31 + 0.75;
+
+tongue_len = 50;
+
+// calculated
+
+hook_th_plug_holder =
+  plug_l_d[0][1]/2 + wall_th * sin(22.5);
+
+hook_tongue_h = hook_hole + wall_th*2;
+
+plug_l_d_smallest = plug_l_d[len(plug_l_d)-1];
+plug_hook_x_min = -plug_l_d_smallest[0] - wall_th;
+plug_hook_z_start = -plug_l_d_smallest[1]/2 - wall_th;
+
+z_laptop_base = palmrest_from_plug_z - laptop_th;
+z_hook_min = z_laptop_base - hook_tongue_h;
+
+module PlugMainPlan() {
+  for (l_d = plug_l_d) {
+    l = l_d[0];
+    d = l_d[1];
+    rectfromto([ -l, -d/2 ],
+              [  0, +d/2 ]);
+  }
+}
+
+module PlugHolderPlan() {
+  intersection(){
+    hull()
+      offset(r= wall_th)
+      PlugMainPlan();
+
+    rectfromto([-100,-100], [-plug_entry_gap,+100]);
+  }
+}
+
+module PlugHookHookPlan(){
+  polygon([ [ plug_hook_x_min, 0 ],
+           [ plug_hook_x_min, plug_hook_z_start ],
+           [ plug_hook_x_min + (plug_hook_z_start - z_hook_min),
+             z_hook_min ],
+           [ -plug_entry_gap, z_hook_min ],
+           [ -plug_entry_gap, 0 ],
+           ]);
+}
+
+module TonguePlan(){
+  difference(){
+    rectfromto([ -plug_entry_gap - 1, z_hook_min ],
+              [ tongue_len, z_laptop_base ]);
+    translate([ tongue_len - wall_th - hook_hole/2,
+               z_hook_min + wall_th + hook_hole/2 ])
+      circle(r = hook_hole/2);
+  }
+}
+
+module FarHookPlan(){
+  mirror([1,0,0])
+    TonguePlan();
+
+  rectfromto([ 0, z_hook_min ],
+            [ hook_w_min, palmrest_from_plug_z + 0.1]);
+
+  translate([0, palmrest_from_plug_z])
+    rectfromto([ -hook_hook, 0 ],
+              [ hook_w_min, hook_w_min ]);
+}
+
+module RotateIntersect(n=6){
+  intersection_for (r = [0:n-1]) {
+    rotate([r/n * 360,0,0])
+      linextr(-100,100) children(0);
+  }
+}
+
+module PlugHolder(){
+  difference(){
+    union(){
+      RotateIntersect(8)
+       PlugHolderPlan();
+
+      rotate([0,0,180]) {
+       linextr_y_xz(-hook_th_plug_holder/2,
+                    +hook_th_plug_holder/2)
+         PlugHookHookPlan();
+
+       linextr_y_xz(-hook_th/2,
+                    +hook_th/2)
+         TonguePlan();
+      }
+    }
+
+    RotateIntersect(6)
+      PlugMainPlan();
+
+    linextr(-plug_stem[1]/2, 100)
+      rectfromto([ -100, -plug_stem[0]/2 ],
+                [ +100, +plug_stem[0]/2 ]);
+  }
+}
+
+module PlugHolderPrint(){ ////toplevel
+  render() PlugHolder();
+}
+
+module FarHookPrint(){ ////toplevel
+  linextr(0, hook_th)
+    FarHookPlan();
+}
+
+module DemoPlan() { ////toplevel
+  translate([0,0,-10]) color("grey") PlugHolderPlan();
+  PlugMainPlan();
+  translate([0,0,-5]) color("blue") {
+    PlugHookHookPlan();
+    TonguePlan();
+  }
+
+  translate([0,40,0]) {
+    FarHookPlan();
+  }
+}
diff --git a/led-panel-ceiling-bracket.scad b/led-panel-ceiling-bracket.scad
new file mode 100644 (file)
index 0000000..8f39a5b
--- /dev/null
@@ -0,0 +1,56 @@
+// -*- C -*-
+
+holespc = 20;
+
+len = 50;
+width = 20;
+backspc = 8;
+extra_height = 2;
+light_height = 12.5;
+hole_from_back = 7.2 + 0.5;
+hole_dia = 3.5 + 0.5;
+bolthead_dia = 7 + 1.0;
+bolthead_depth = 5 + 20 - 12 + 2.7/2;
+
+plasfix_dia = 4.5 + 0.5 + 1.1;
+plasfix_head = 8.7 + 0.5 + 1.1;
+plasfix_headdep = plasfix_dia;
+plasfix_sink = 8;
+
+height = light_height + backspc + extra_height;
+
+octagon_fudge = 1/cos(22.5);
+
+echo("remain", width - bolthead_depth);
+
+module Bracket(){
+  difference(){
+    translate([-len/2, 0, 0])
+      cube([len, width, height]);
+    for (xsgn=[-1,+1]) {
+      translate([xsgn * holespc/2, -1,
+                light_height - hole_from_back + extra_height]) {
+       rotate([-90,0,0]) {
+         rotate([0,0,360/8/2]) {
+           cylinder(r=bolthead_dia/2 * octagon_fudge,
+                    h= bolthead_depth +1, $fn=8);
+           cylinder(r=hole_dia/2 * octagon_fudge,
+                    h=50, $fn=8);
+         }
+       }
+      }
+    }
+    translate([0, width/2, 0]) {
+      cylinder(r= plasfix_dia/2, h=50, $fn=20);
+      translate([0,0,-1])
+       cylinder(r= plasfix_head/2, h= plasfix_sink + 1, $fn=20);
+    }
+  }
+}
+
+module BracketPrint(){
+  rotate([0,180,0])
+    Bracket();
+}
+
+BracketPrint();
diff --git a/led-panel-ceiling-bracket.slic3r b/led-panel-ceiling-bracket.slic3r
new file mode 100644 (file)
index 0000000..612eb56
--- /dev/null
@@ -0,0 +1,3 @@
+bottom_solid_layers = 3
+top_solid_layers = 3
+perimeters = 3
diff --git a/lemon-stand.scad.pl b/lemon-stand.scad.pl
new file mode 100755 (executable)
index 0000000..8c1d10f
--- /dev/null
@@ -0,0 +1,164 @@
+#!/usr/bin/perl -w
+
+use strict;
+use Math::Trig;
+use Math::Vector::Real;
+use IO::File;
+use Data::Dumper;
+use constant tau => pi*2;
+
+my $ellipse = 25 / 2;
+my $circle = 7 / 2;
+my $channelh = 3;
+my $channelw = 4;
+my $xscale = 35 / 25;
+my $N = 180; # around ellipse
+my $M = 80; # around each circle
+my @channeldistprops = (0, 1/3, 2/3);
+
+my $NMdiv = $ENV{'LEMONSTAND_COARSE'} || 1;
+
+$M /= $NMdiv;
+$N /= $NMdiv;
+
+print <<END;
+// -*- C -*-
+// *** AUTOGENERATED - DO NOT EDIT ***
+END
+
+print "torusyup = ", ($circle / sqrt(2)), ";\n";
+
+our @ellipse = map {
+    my $theta = tau * $_ / $N;
+    V( cos($theta) * $ellipse * $xscale, sin($theta) * $ellipse, 0 )
+} 0..($N-1);
+
+#print Dumper(\@ellipse);
+
+our @alongs = map {
+    my $i = $_;
+    $ellipse[ ($i+1) % $N ] - $ellipse[ ($i-1) % $N];
+} 0..($N-1);
+
+our @circles = map {
+    my $i = $_;
+    my $centre = $ellipse[$i];
+    my $axis = $alongs[$i]->versor();
+    my $rad0 = $axis x V(0,0,1);
+    my $rad1 = $rad0 x $axis;
+    [ map {
+       my $theta = tau * $_ / $M;
+       $centre + $circle * ($rad0 * cos($theta) + $rad1 * sin($theta));
+    } 0..($M-1) ];
+} 0..($N-1);
+
+sub scadvec ($) {
+    my ($v) = @_;
+    return "[ ".(join ", ", @$v)." ]"
+}
+
+sub torusy () {
+    print "module Torusy(){ polyhedron(points=[";
+    my $ptix = 0;
+    my @cirpt;
+    foreach my $i (0..$N-1) {
+       foreach my $j (0..$M-1) {
+           print "," if $ptix;
+           print "\n";
+           print "    ",(scadvec $circles[$i][$j]);
+           $cirpt[$i][$j] = $ptix++;
+       }
+    }
+    print "\n  ],\n";
+
+    print "  faces=[";
+    foreach my $i (0..$N-1) {
+       my $i2 = ($i+1) % $N;
+       foreach my $j (0..$M-1) {
+           my $j2 = ($j+1) % $M;
+           print "," if $i || $j;
+           print "\n";
+           print "   [ ", (join ", ",
+                           $cirpt[ $i  ][ $j  ],
+                           $cirpt[ $i  ][ $j2 ],
+                           $cirpt[ $i2 ][ $j  ],
+                          ), " ],";
+           print "   [ ", (join ", ",
+                           $cirpt[ $i  ][ $j2 ],
+                           $cirpt[ $i2 ][ $j2 ],
+                           $cirpt[ $i2 ][ $j  ],
+                          ), " ]";
+       }
+    }
+    print "\n  ]);\n}\n";
+}
+
+torusy();
+
+
+our @distances;
+push @distances, 0;
+foreach my $i (1..$N) {
+    my $dist = $distances[ $i-1 ];
+    $dist += abs($ellipse[$i % $N] - $ellipse[$i-1]);
+    $distances[$i] = $dist;
+}
+
+sub infodistprop ($) {
+    my ($distprop) = @_;
+    # returns
+    #   ( $ellipse_centreline_point,
+    #     $along_vector )
+    my $dist = $distprop * $distances[$N];
+    foreach my $i (0..$N-1) {
+       my $prorata =
+           ($dist - $distances[$i]) /
+           ($distances[$i+1] - $distances[$i]);
+       next unless 0 <= $prorata && $prorata <= 1;
+       print "// infodistprop $distprop => #$i=$ellipse[$i] $prorata $ellipse[$i+1]\n";
+       return (
+               (1-$prorata) * $ellipse[$i] + ($prorata) * $ellipse[$i+1],
+               $alongs[$i],
+              );
+    }
+    die "$distprop ?";
+}
+
+sub channels(){
+    print "module Channels(){\n";
+    
+    foreach my $cdp (
+                    (map { 0.5 *  $_    } @channeldistprops),
+                    (map { 0.5 * ($_+1) } @channeldistprops),
+                   ) {
+       my ($ctr, $along) = infodistprop($cdp);
+       my $angle = atan2(-$along->[0], $along->[1]);
+       print "  translate(",scadvec($ctr),")\n";
+       print "  rotate([0,0,$angle*360/",tau,"])\n";
+       print "  rotate([0,90,0])\n";
+       print "  translate([0,0, -2*$circle])\n";
+       print "  scale([1, $channelw/$channelh/2, 1])\n";
+       print "  rotate([0,0,360/8/2])\n";
+       print "  cylinder(r=$channelh, h=4*$circle, \$fn=8);\n";
+    }
+    print "}\n";
+}
+
+channels();
+
+while (<DATA>) { print };
+
+STDOUT->error and die $!;
+STDOUT->flush or die $!;
+
+__DATA__
+module Stand(){
+    difference(){
+       translate([0,0,torusyup])
+           Torusy();
+       Channels();
+       translate([-200,-200,-50])
+           cube([400,400,50]);
+    }
+}
+Stand();
diff --git a/light-bracket.scad b/light-bracket.scad
new file mode 100644 (file)
index 0000000..c49dc09
--- /dev/null
@@ -0,0 +1,139 @@
+shrinkage = 1.0126; // width of 56.2 gives 55.5
+remote_width= 56.35 * shrinkage;
+remote_height=124.7 * shrinkage;
+remote_thick=6.1; // height of 6.8 gives 6.3
+mainhole_thick=remote_thick+1;
+hook_hook_thick=1.5;
+hook_stem_thick=1.5;
+hook_hook_len=1.5;
+base_thick=3.5;
+base_margin=3.0;
+base_width=remote_width-base_margin*2;
+base_height=remote_height-base_margin*2;
+base_edgewidth=4;
+
+screw_ys=[ 28, remote_height-28 ];
+
+// origin is base of mainhole
+
+module mainhole() {
+       translate([ -remote_width/2, 0, 0 ])
+       cube(center=false,
+               size=[ remote_width, remote_height, mainhole_thick ] );
+}
+
+module hhook(extent) {
+       translate([ -hook_stem_thick, 0, -base_thick*2 ])
+               cube(center=false,
+                       size=[
+                               hook_stem_thick,
+                               extent,
+                               base_thick*2 + mainhole_thick + hook_hook_thick
+                       ]);
+       translate([ -hook_stem_thick, 0, -base_thick*2 ])
+               cube(center=false,
+                       size=[
+                               hook_stem_thick+base_margin+base_edgewidth-1,
+                               extent,
+                               base_thick*2
+                       ]);
+       translate([ -hook_stem_thick+1.0, 0, mainhole_thick ])
+               rotate(v=[0,1,0], a=-30)
+               cube(center=false,
+                       size=[
+                               3,
+                               extent,
+                               hook_hook_thick
+                       ]);
+       //difference() {
+       //      #translate([ -hook_stem_thick, 0, -base_thick*2 ])
+       //              cube(center=false,
+       //                      size=[
+       //              hook_stem_thick+base_margin+base_edgewidth-1,
+       //              extent,
+       //              base_thick*2 + mainhole_thick + hook_hook_thick
+       //                              ]);
+       //      translate([hook_hook_len, -5, 0])
+       //               cube(center=false, size=[ 20, extent+10, 30 ]);
+       //}
+}
+
+module hhookside(extent) {
+       translate([ -remote_width/2, 0, 0 ])
+               hhook(extent);
+}
+
+module hhookbot(extent) {
+       rotate(a=90, v=[0,0,1]) hhook(extent);
+}
+
+module hstuff() {
+       translate([0,70,0]) hhookside(15);
+       translate([0,10,0]) hhookside(15);
+       translate([-10,0,0]) hhookbot(15);
+}
+
+module slashes() {
+       for (y=[-30 : 60 : +40])
+               translate([0,y,0])
+                       rotate(v=[0,0,1],a=45)
+                               cube(center=true, [ 5,200,200 ]);
+}
+
+module base() {
+       translate([ 0, base_height/2 + base_margin, -base_thick/2 ])
+       intersection() {
+               cube(center=true,
+                       [ base_width, base_height, base_thick+10 ]);
+               union() {
+                       difference() {
+                               cube(center=true, [ 200,200,200 ]);
+                               cube(center=true,
+                                       [ base_width - base_edgewidth*2,
+                                         base_height - base_edgewidth*2,
+                                         base_thick + 15 ]);
+                               
+                       }
+                       slashes();
+                       mirror([1,0,0]) slashes();
+               }
+       }
+//     translate([-base_width/2, base_margin, -base_thick*2])
+//             cube(center=false, [base_width,base_height,base_thick+10]);
+}
+
+module stuff() {
+       hstuff();
+       mirror([1,0,0]) hstuff();
+       base();
+       for (y=screw_ys) translate([0, y, -20])
+               cylinder(r=6.5, h=21);
+}
+
+module screwhole(holedia, csdia) {
+       // screw goes along z axis downwards
+       // origin is top of head
+       // results are positive, so this should be subtracted
+       translate([0,0,-100]) cylinder(h=200, r=holedia/2);
+       csdist=(csdia-holedia)/2;
+       translate([0,0,-csdist]) cylinder(h=csdist, r1=holedia/2, r2=csdia/2);
+       cylinder(h=100, r=csdia/2);
+}
+
+module bracket() {
+       // this is the actual thing we want
+       difference() {
+               stuff();
+               mainhole();
+               for (y=screw_ys) translate([0, y, 0])   
+                       screwhole(5.4,10); //dia=4 gives 2.9
+                                        //holedia=10 gives 9.0 want 7.0
+               translate([0,0,-50 - base_thick])
+                       cube(center=true,[300,300,100]); // print bed
+       }
+}
+
+intersection() {
+       !bracket();
+       translate([-50,6,-50]) #cube(center=false, [100,27,100]);
+}
diff --git a/lipo-flat-mount.scad b/lipo-flat-mount.scad
new file mode 100644 (file)
index 0000000..865b6d3
--- /dev/null
@@ -0,0 +1,184 @@
+// -*- C -*-
+
+include <nutbox.scad>
+include <utils.scad>
+
+// pimoroni 3000mAh
+battery = [ 51.5,
+           81.3,
+           8.0 ];
+
+battery_wall = 2.0;
+battery_wall_unsupp = 4.0;
+battery_wall_top_gap = 0.75;
+
+battery_keeper_m_th = 4;
+battery_keeper_m_w = 4;
+battery_keeper_x_gap = 0.75; // each side
+battery_keeper_y_gap_overhang = 0.75;
+battery_keeper_y_gap_nutbox = 1.0;
+battery_keeper_z_gap_nutbox = 0.50;
+battery_keeper_z_gap_overhang = 0.75;
+
+battery_keeper_x_th_min = 1.5 * 1.5;
+battery_keeper_x_th_max = 2.5 * 1.5;
+battery_keeper_x_w  = 5;
+battery_keeper_x_n  = 5;
+
+battery_keeper_screw_x_off = -2.5;
+
+battery_wire_y = 4;
+
+battery_nutbox = nutbox_data_M3;
+
+// calculated
+
+battery_fix_sz = NutBox_outer_size(battery_nutbox);
+
+battery_nutbox_z = max( battery[2] + battery_wall_top_gap,
+                       NutBox_h_base(battery_nutbox) );
+battery_keeper_overhang_z = battery[2] + battery_keeper_m_th
+  + battery_keeper_z_gap_overhang;
+battery_keeper_overhang_wall = battery_keeper_m_w;
+battery_keeper_overhang_th = battery_keeper_m_th;
+
+// calculated outputs
+
+battery_keeper_tab_top_z = battery_nutbox_z
+     + battery_keeper_z_gap_nutbox + battery_keeper_m_th;
+     // NB does not include screw head
+
+battery_keeper_legs_top_z = battery_keeper_overhang_z
+                            + battery_keeper_overhang_th;
+
+battery_keeper_frame_top_z = battery[2] + battery_keeper_m_th;
+
+battery_mount_y_min = -battery_fix_sz;
+battery_mount_y_max = battery[1] + battery_wall;
+battery_mount_x_sz  = battery[0] + battery_wall_unsupp*2;
+
+module BatteryPlan(){
+  rectfromto([ -battery[0]/2, 0          ],
+            [ +battery[0]/2, battery[1] ]);
+}
+
+module BatteryBase(){ ////toplevel
+  // wall
+  linextr(-0.1, battery[2] - battery_wall_top_gap, convexity=7) {
+    difference(){
+      union(){
+       offset(r=battery_wall) BatteryPlan();
+       rectfromto([ 0,0 ],
+                  [ -(battery[0]/2 + battery_wall_unsupp), battery[1]/3 ]);
+      }
+      BatteryPlan();
+      rectfromto([ battery_fix_sz/2 - 0.5
+                  + battery_keeper_screw_x_off, -30 ],
+                [ -battery[0], battery_wire_y ]);
+    }
+  }
+
+  // nutbox
+  translate([battery_keeper_screw_x_off, -battery_fix_sz/2, battery_nutbox_z])
+    NutBox(battery_nutbox, battery_nutbox_z + 0.1);
+
+  // overhang for legs at rear
+  for (m=[0,1]) {
+    mirror([m,0,0]) {
+      translate([ battery[0]/2, battery[1], 0]) {
+       difference(){
+         linextr(-0.1,
+                 battery_keeper_overhang_z
+                 + battery_keeper_overhang_th,
+                 convexity=1)
+           rectfromto([ -battery_keeper_m_w*2, -battery_keeper_m_w ],
+                      [ battery_keeper_overhang_wall, battery_wall ]);
+         linextr(-1, battery_keeper_overhang_z,
+                 convexity=1)
+           rectfromto([-20, -20], [0,0]);          
+       }
+      }
+    }
+  }
+}
+
+module BatteryKeeper(){ ////toplevel
+  // A-frame
+  translate([0,0, battery[2]]) {
+    linextr(0, battery_keeper_m_th) {
+      intersection(){
+       // main legs
+       translate([0,  +battery[1], 0])
+         multmatrix([[ 1, -battery_keeper_screw_x_off/battery[1], 0, 0, ],
+                     [ 0,1,0, 0, ],
+                     [ 0,0,1, 0, ]])
+         translate([0, -battery[1], 0])
+       for (sx=[-1,+1]) {
+         multmatrix([[ 1,0,0, 0, ],
+                     [ 0,1,0, 0, ],
+                     [ 0,0,1, 0, ]] +
+                    sx *
+                    ( battery[0]/2 - 0.5 * battery_keeper_m_w
+                      - battery_keeper_x_gap ) /
+                    ( battery[1] - 0.5 * battery_keeper_m_w )
+                    *
+                    [[ 0,1,0, 0, ],
+                     [ 0,0,0, 0, ],
+                     [ 0,0,0, 0, ]])
+           rectfromto([ -battery_keeper_m_w/2,
+                        battery_keeper_y_gap_nutbox ],
+                      [ +battery_keeper_m_w/2,
+                        battery[1] - battery_keeper_y_gap_overhang ]);
+       }
+
+       // shape to round off the leg end corners
+       hull(){
+         for (sx=[-1,+1]) {
+           translate([ sx * ( battery[0]/2 - battery_keeper_m_w/2
+                              -battery_keeper_x_gap) ,
+                       battery[1] - battery_keeper_m_w/2
+                       -battery_keeper_y_gap_overhang ])
+             circle(r = battery_keeper_m_w/2);
+         }
+         square(center=true, [ battery[0], 1 ]);
+       }
+      }
+    }
+
+    // x struts
+    for (i=[0 : battery_keeper_x_n-1]) {
+      linextr(0,
+             battery_keeper_x_th_min +
+             (battery_keeper_x_th_max - battery_keeper_x_th_min)
+             * pow( i/(battery_keeper_x_n-1)*2 - 1 , 2)
+             ) {
+       difference(){
+         translate([0, battery[1] * ((i + 0.5) / battery_keeper_x_n)])
+           square(center=true, [ battery[0], battery_keeper_x_w ]);
+         children(0);
+       }
+      }
+    }
+  }
+
+  // tab for screw and nutbox
+  translate([battery_keeper_screw_x_off,
+            0,
+            battery_nutbox_z + battery_keeper_z_gap_nutbox])
+    linextr(0, battery_keeper_m_th, convexity=4) {
+    difference(){
+      rectfromto([ -battery_fix_sz/2, -battery_fix_sz ],
+                [ +battery_fix_sz/2,
+                  0.5 * battery[1] / battery_keeper_x_n +
+                  0.5 * battery_keeper_m_w ]);
+      translate([ 0, -battery_fix_sz/2 ])
+       circle(r = battery_nutbox[0]/2);
+    }
+  }
+}
+
+module BatteryDemo(){ ////toplevel
+  color("grey") BatteryBase();
+  BatteryKeeper() { union(){ } }
+}
+
diff --git a/lock-inframe-bracket.scad b/lock-inframe-bracket.scad
new file mode 100644 (file)
index 0000000..bfa6e39
--- /dev/null
@@ -0,0 +1,349 @@
+// -*- C -*-
+
+// use shell thickness 1.50
+// use fill density 40%
+
+include <funcs.scad>
+
+tube_dia = 27.5 + 1.625 + 1.32;
+lock_w = 42.5 + 0.5;
+lock_d = 28.0 + 0.5;
+main_h = 45.0;
+backflange_d = 12;
+backflange_hole_dy = -1;
+lockshaft_dia = 14.35;
+
+cliprecess_h = 16;
+total_h = 75;
+
+back_gap = 12.5;
+main_th = 4.50;
+tube_th = 5.50;
+
+midweb_d = 3;
+clip_th = 3.5;
+clip_gap = 2.5;
+clip_d = 22.0;
+
+mountscrew_dia = 4 + 0.5;
+clipbolt_dia = 5 + 0.6;
+
+mountscrew_washer = 10;
+
+backflange_th = 4.5;
+
+$fn=50;
+
+join_cr = 9;
+
+tube_rear_extra_th = 1;
+
+divide_shaft_w = 1.75;
+divide_shaft_l = 1.5;
+divide_head_dx = 1.75;
+divide_head_th = 1.5;
+divide_gap = 0.75;
+
+divide_angle = 26;
+divide_fudge_r = 4.75;
+
+backflange_angle = 20;
+
+// calculated
+
+lockshaft_r = [1, 1] * lockshaft_dia / 2;
+front_th = main_th;
+
+tube_or = tube_dia/2 + tube_th;
+back_ohw = back_gap/2 + backflange_th;
+backflange_ymin = tube_dia/2 + backflange_d;
+
+lock_0y = tube_dia/2 + lock_d/2 + midweb_d;
+lock_0x = lock_w/2 - lock_d/2;
+lock_0 = [lock_0x,lock_0y];
+
+lock_or = [lock_w, lock_d]/2 + [front_th,front_th];
+
+module oval(sz){ // sz[0] > sz[1]
+  xr = sz[0];
+  yr = sz[1];
+  hull(){
+    for (sx=[-1,+1]) {
+      translate([sx * (xr-yr), 0])
+       circle(r=yr);
+    }
+  }
+}
+
+module JoinCircs(jr){
+  // http://mathworld.wolfram.com/Circle-CircleIntersection.html
+  R = tube_or + join_cr;
+  r = lock_or[1] + join_cr;
+  d = dist2d( [0,0], lock_0 );
+  x = (d*d - r*r + R*R) / (2*d);
+  y = sqrt( R*R - x*x );
+
+  echo(lock_0x, lock_0y, R,r, d, x,y);
+
+  for (m=[0,1]) {
+    mirror([m,0]) {
+      rotate(atan2(lock_0y, lock_0x)) {
+       translate([x,-y])
+         circle(r= jr);
+      }
+    }
+  }
+}
+
+module DivideHook(){ ////toplevel
+  w = tube_th/2;
+  d = divide_gap;
+
+  translate([-1,0] * (w + d + w)){
+    for (sx=[-1,+1])
+      translate([-(w + w+d) * sx, 0]) circle(r= w);
+    difference(){
+      circle(r = 3*w + d);
+      circle(r =   w + d);
+      translate([-10*w, -10*w]) square([20*w, 10*w]);
+    }
+  }
+}
+
+module DivideCut(){
+  w = tube_th/2;
+  d = divide_gap;
+  br = tube_dia/2 + tube_th;
+
+  difference(){
+    offset(r=divide_gap) DivideHook();
+    DivideHook();
+    translate([-2*w,0]) mirror([0,1]) square([4*w, 4*w]);
+  }
+}
+
+module DivideCutB(){
+  w = tube_th/2;
+  d = divide_gap;
+  br = tube_dia/2 + tube_th;
+  
+  intersection(){
+    translate([br - tube_th/2,0]) {
+      difference(){
+       circle(r=br + d);
+       circle(r=br);
+      }
+    }
+    translate([-2*w, 0]) mirror([0,1]) square(4*w);
+  }
+}
+
+module DivideSurround(){
+  w = tube_th/2;
+  d = divide_gap;
+
+  offset(r= w*2) {
+    hull() {
+      DivideCut();
+      translate([-(4*w + 2*d), 8*w]) circle(r=w);
+    }
+  }
+}
+
+module DivideInPlace(){
+  rotate([0,0, -divide_angle])
+    translate([ -tube_dia/2 -tube_th/2, 0])
+    children();
+}
+
+module MainPlan(){ ////toplevel
+  difference(){
+    union(){
+      difference(){
+       union(){
+         hull(){
+           for (t=[0, tube_rear_extra_th])
+             translate([0, -t])
+               circle(r = tube_or);
+         }
+         rotate([0,0, backflange_angle])
+           translate([-back_ohw,0]) mirror([0,1])
+           square([back_ohw*2, backflange_ymin]);
+
+         translate([0, lock_0y]){
+           oval(lock_or);
+         }
+
+         hull(){
+           JoinCircs(0.01);
+           polygon([[0,0], lock_0, [-lock_0[0], lock_0[1]]]);
+         }
+       }
+
+       rotate([0,0, backflange_angle])
+         translate([-back_gap/2,1]) mirror([0,1])
+         square([back_gap, backflange_ymin+2]);
+
+       JoinCircs(join_cr);
+      }
+
+      DivideInPlace() DivideSurround();
+    }
+    translate([0, lock_0y]){
+      oval([lock_w/2, lock_d/2]);
+    }
+
+    circle(r = tube_dia/2);
+
+    DivideInPlace() DivideCut();
+    DivideInPlace() DivideCutB();
+  }
+}
+
+lockshaft_or = lockshaft_r + [clip_th,clip_th];
+cliprecess_ymax = cliprecess_h - lockshaft_r[1];
+clip_ymin = cliprecess_ymax - main_h;
+clip_ogap = clip_gap + clip_th*2;
+
+module ClipElevationPositive(){
+  hull(){
+    oval(lockshaft_or);
+    translate([0, -lockshaft_or[1] * sqrt(2)])
+      square(center=true, 0.5);
+  }
+  translate([-lockshaft_or[0], 0])
+    square([lockshaft_or[0]*2, cliprecess_ymax]);
+  translate([-clip_ogap/2, 0]) mirror([0,1]) square([clip_ogap, -clip_ymin]);
+}
+
+module ClipElevationNegative(){
+  hull(){
+    for (y=[0, cliprecess_ymax+1])
+      translate([0, y])
+       oval(lockshaft_r);
+  }
+  translate([-clip_gap/2, 1]) mirror([0,1]) square([clip_gap, 2-clip_ymin]);
+}
+
+module ClipElevation(){
+  difference(){
+    ClipElevationPositive(1);
+    ClipElevationNegative(0);
+  }
+}
+
+module ExtrudeClipElevation(extra=0){
+  translate([0,
+            lock_0y + lock_d/2 + clip_d + extra,
+            -clip_ymin])
+    rotate([90,0,0])
+    linear_extrude(height= clip_d + extra*2, convexity=100)
+    children(0);
+}
+
+module ThroughHole(r, y, z, x=-50) {
+  translate([x, y, z])
+    rotate([0, 90, 0])
+    cylinder(r=r, h=100, $fn=20);
+}
+
+module MountingHoleCylinders(r, x=-50){
+  for (z=[ 1/4, 3/4 ]) {
+    rotate([0,0, backflange_angle])
+      ThroughHole( r,
+                  -tube_dia/2 -0.5*backflange_d + backflange_hole_dy,
+                  total_h * z,
+                  x);
+  }
+}
+
+module ThroughHoles(){
+  MountingHoleCylinders(mountscrew_dia/2);
+
+  ThroughHole( clipbolt_dia/2,
+              lock_0y + lock_d/2 + clip_d/2 + front_th/2,
+              main_h - cliprecess_h - clip_th - clip_d/2 );
+}
+
+module SlopeTrimElevation(){
+  far_corner_nom = [ lock_0y + lock_d/2, main_h ];
+  round_centre = far_corner_nom + lock_d/2 * [0,1];
+  hull(){
+    translate(round_centre) circle(r= lock_d/2);
+    translate([ lock_0y - lock_d/2, total_h ]) square([ lock_d + clip_d, 1 ]);
+    translate(far_corner_nom) square([clip_d*2, 1]);
+  }
+}
+
+module SlopeTrim(){
+  rotate([0,90,0])
+    rotate([0,0,90])
+    translate([0,0, -lock_w])
+    linear_extrude(convexity=100, height=lock_w*2)
+    SlopeTrimElevation();
+}
+    
+module MainPositive(){
+  difference(){
+    union(){
+      linear_extrude(height=total_h, convexity=100) MainPlan();
+      ExtrudeClipElevation() ClipElevationPositive();
+    }
+    ExtrudeClipElevation(1) ClipElevationNegative();
+  }
+}
+
+module Bracket(){ ////toplevel
+  difference(){
+    MainPositive();
+    ThroughHoles();
+    SlopeTrim();
+  }
+}
+
+module TestTopEdge(){ ////toplevel
+  intersection(){
+    translate([0,0, -total_h])
+      translate([0,0, 4])
+      Bracket();
+    translate([-200,-200,0])
+      cube([400,400,100]);
+  }
+}
+
+module TestClipBoltHole(){ ////toplevel
+  intersection(){
+    union(){
+      translate([0, 0, -5])
+       Bracket();
+      translate([-4, lock_0y + lock_d/2 + 1, 0])
+       cube([8, 4, 1.5]);
+    }
+    translate([-200, lock_0y + lock_d/2 + 0.1])
+      cube([400, 400, total_h-20]);
+  }
+}
+
+module Demo(){ ////toplevel
+  Bracket();
+  color("blue") MountingHoleCylinders(mountscrew_dia/2 - 0.1);
+  color("black") MountingHoleCylinders(mountscrew_washer/2,
+                                      back_ohw + 0.25);
+}
+
+module DivideDemo(){ ////toplevel
+  color("black") translate([0,0,-2]) MainPlan();
+  color("grey") DivideInPlace() DivideHook();
+  color("blue") translate([0,0,-4]) DivideInPlace() DivideCut();
+}
+
+//MainPlan();
+//ClipElevationPositive();
+//ClipElevation();
+//MainPositive();
+//%ThroughHoles();
+//TestTopEdge();
+//TestClipBoltHole();
+
+//Bracket();
+
diff --git a/m8-thin-washer.scad b/m8-thin-washer.scad
new file mode 100644 (file)
index 0000000..8e1f26f
--- /dev/null
@@ -0,0 +1,13 @@
+// -*- C -*-
+
+include <utils.scad>
+
+$fa = 1;
+$fs = 0.1;
+
+linextr(0, 0.425 + 0.125 * 2) {
+  difference(){
+    circle(r=  15        /2);
+    circle(r= (8 + 0.5)  /2);
+  }
+}
diff --git a/maglite-holder-photo.jpg b/maglite-holder-photo.jpg
new file mode 100644 (file)
index 0000000..c0d19ce
Binary files /dev/null and b/maglite-holder-photo.jpg differ
diff --git a/maglite-holder-torch.fig b/maglite-holder-torch.fig
new file mode 100644 (file)
index 0000000..9c25deb
--- /dev/null
@@ -0,0 +1,19 @@
+#FIG 3.2  Produced by xfig version 3.2.6
+Landscape
+Center
+Metric
+A4
+100.00
+Single
+-2
+1200 2
+5 1 0 1 4 4 50 -1 -1 0.000 0 0 0 0 707.697 1147.762 -1207 1110 -1113 554 -858 45
+2 2 0 1 2 1 75 -1 -1 0.000 0 0 -1 0 0 5
+        -1800 -1350 2025 -1350 2025 3825 -1800 3825 -1800 -1350
+2 5 0 1 0 -1 100 -1 -1 0.000 0 0 -1 0 0 5
+       0 maglite-holder-photo.jpg
+        -1572 -1185 1891 -1185 1891 3762 -1572 3762 -1572 -1185
+2 2 0 1 2 1 75 -1 -1 0.000 0 0 -1 0 0 5
+        -859 13 857 13 857 -795 -859 -795 -859 13
+2 3 0 1 1 1 50 -1 -1 0.000 0 0 -1 0 0 6
+        -1201 1100 -1204 2667 -231 1187 -225 -159 -846 17 -1201 1100
diff --git a/maglite-holder.scad b/maglite-holder.scad
new file mode 100644 (file)
index 0000000..9aed425
--- /dev/null
@@ -0,0 +1,173 @@
+// -*- C -*-
+
+dxf_off = [ -40, -85 ];
+
+torch_lit_dia = 37.5;
+torch_big_dia = 56.5;
+torch_tot_len = 256;
+torch_big_len = 60;
+
+torch_clear = 30;
+torch_clear_below = 10;
+
+stem_width = 20;
+stem_thick = 8;
+
+torch_recess = 11;
+arm_width = 10;
+block_thick = 15;
+
+torch_out_more = 10;
+
+brace = [ 40, 20, 20 ];
+
+hole_dia = 4 + 0.5;
+hole_slot = 5;
+
+movement_extra_angle = 5;
+
+slop = 4; // total, not each side
+
+torch_min_xcoord_fig_cm = -2.7; // coordinates of bottom left of curve
+torch_min_ycoord_fig_cm = -5.9; // & big part in fig file.  mid top is origin
+torch_smm_xcoord_fig_cm = -1.9; // x coord of lhs of narrow part
+
+miniature = 1; // can adjust this to get commitid size right and rescale *Print
+
+$fa=5;
+
+// calculated
+
+include <commitid.scad>
+
+torch_dxf_scale =
+  [ (torch_big_dia - torch_lit_dia) /
+    (-(torch_min_xcoord_fig_cm - torch_smm_xcoord_fig_cm) * 10 * 2),
+    torch_big_len / (-torch_min_ycoord_fig_cm * 10) ];
+
+echo(torch_dxf_scale);
+
+above = torch_big_len + torch_clear + torch_clear_below;
+
+holes = [ 172, 265 ];
+
+stem_below = stem_width/2;
+
+stem_len = holes[1] - above + stem_below;
+
+torch_out = stem_thick + torch_big_dia/2 + torch_out_more;
+
+block_width = arm_width*2 + torch_big_dia;
+
+block_out = torch_out + torch_big_dia/2/sqrt(2);
+
+module TorchOrig(){
+  mirror([0,0,1]){
+    hull(){
+      rotate_extrude()
+       translate([-torch_lit_dia/2, 0])
+       scale(torch_dxf_scale)
+       translate(dxf_off)
+       translate([-torch_smm_xcoord_fig_cm * 10, 0])
+       import(file="maglite-holder-torch-curve.dxf",
+              convexity=10, center=true);
+    }
+    translate([0,0, -1])
+      cylinder(r=torch_lit_dia/2, h= torch_tot_len - torch_big_len + 1);
+  }
+}
+
+module Torch(){
+  scale(slop/torch_lit_dia + 1.0)
+    TorchOrig();
+}
+
+module ScrewHole(y, rot) {
+  translate([0,0, above -y]){
+    rotate([0,rot,0]){
+      hull(){
+       for (d= [-1,+1] * hole_slot/2) {
+         translate([d,0,0])
+           rotate([90,0,0])
+           translate([0,0,-stem_thick*2])
+           cylinder(r= hole_dia/2, h= stem_thick*4);
+       }
+      }
+    }
+  }
+}    
+
+module TorchMovement(){
+  translate([0, -torch_out, 0]) {
+    translate([0, 0, -torch_recess])
+      Torch();
+    for (as=[-1,+1]) {
+      rotate([0,0, as*movement_extra_angle])
+       rotate([90,0,0])
+       linear_extrude(height= block_out)
+       projection() rotate([-90,0,0]) Torch();
+    }
+  }
+}
+
+module Bracket(){ ////toplevel
+  cid_w = stem_width * .75;
+  hole_near = hole_slot + hole_dia;
+
+  difference(){
+    mirror([0,1,0]) {
+      translate([-stem_width/2, 0, -stem_len])
+       cube([stem_width, stem_thick, stem_len]);
+      translate([0,0, -block_thick]) hull(){
+       translate([-stem_width/2, 0, -brace[2]])
+         cube([stem_width, stem_thick, 1]);
+       translate([-brace[0]/2, 0, 0])
+         cube([brace[0], brace[1], 1]);
+      }
+    }
+    ScrewHole(holes[0], 90);
+    ScrewHole(holes[1], 0);
+    translate([-cid_w/2, 0, above - holes[0] - hole_near])
+      rotate([-90,0,0])
+      scale([miniature, miniature, 1])
+      Commitid_BestCount([cid_w, holes[1]-holes[0] - hole_near*2]
+                        / miniature);
+  }
+  difference(){
+    mirror([0,1,0])
+      translate([-block_width/2, 0, -block_thick])
+      cube([block_width, block_out, block_thick]);
+    TorchMovement();
+  }
+}
+
+module BracketPrint(){ ////toplevel
+  scale(1/miniature)
+    rotate([-90,0,0])
+    Bracket();
+}
+
+module TestTorchPrint(){ ////toplevel
+  scale(1/miniature)
+  intersection(){
+    translate([0,0, torch_lit_dia / 2 / sqrt(2)])
+      rotate([-90,0,0])
+      Torch();
+    translate([-100, -torch_tot_len*2, 0])
+      cube([200, torch_tot_len*4, 200]);
+  }
+}
+
+module Demo(){ ////toplevel
+  color("red")
+    translate([0, -torch_out, 0])
+    TorchOrig();
+  color("blue")
+    translate([0, -torch_out, above])
+    cylinder(r=torch_big_dia/2, h=1);
+  Bracket();
+}
+
+//Demo();
+//BracketPrint();
+//TestTorchPrint();
diff --git a/makita-drill-handle-blivet.scad b/makita-drill-handle-blivet.scad
new file mode 100644 (file)
index 0000000..831b83f
--- /dev/null
@@ -0,0 +1,42 @@
+// -*- C -*-
+
+include <utils.scad>
+
+hex_across = 12.70 - 0.3;
+screw_dia = 8.0 + 0.0;
+
+min_th = 0.425;
+extra_th = 1.0;
+
+// calculated
+
+total_th = min_th + extra_th;
+hex_rad = hex_across / 2 / cos(30);
+
+module Plan(){
+  difference(){
+    circle(r = hex_rad, $fn = 6);
+    circle(r = screw_dia/2);
+  }
+}
+
+module Elevation(){
+  hull(){
+    rectfromto([ -hex_rad, -1],
+              [ 0.1, min_th]);
+    translate([ hex_rad, 0 ])
+      rectfromto([ 0, -1 ],
+                [ 1, total_th]);
+  }
+}
+
+module Blivet(){
+  intersection(){
+    linextr(0, total_th + 1)
+      Plan();
+    linextr_y_xz(-hex_across, hex_across)
+      Elevation();
+  }
+}
+
+Blivet();
diff --git a/manual-gcode-generator b/manual-gcode-generator
new file mode 100755 (executable)
index 0000000..ba9a0cd
--- /dev/null
@@ -0,0 +1,142 @@
+#!/usr/bin/perl -w
+
+use strict;
+
+our @array;
+our %procs;
+
+sub readdata () {
+    my $l = '';
+    my $current = \@array;
+    while (<>) {
+       chomp or die;
+       s/\s+$//;
+       s/^\s*\!\s*/!/;
+       $l .= $_;
+       next if $l =~ s/\\$//;
+
+       $_=$l; $l='';
+       if (m/^\!(\w+)\s*\(\)\s*\{$/) {
+           my $pname=$1;
+           die if $current ne \@array;
+           die if exists $procs{$pname};
+           $current = $procs{$pname} = [];
+           next;
+       }
+       if (m/^\!\}$/) {
+           $current = \@array;
+           next;
+       }
+       push @$current, $_;
+    }
+}
+readdata();
+
+our %c;
+
+sub defvar ($;$) {
+    my ($cv,$v) = @_;
+    $c{$cv} = $v;
+}
+
+defvar('extruderate',0.097200);
+defvar('feedrate',540);
+defvar('jerkfeedrate',7800);
+defvar('retract',4.5);
+defvar('restart',4.5);
+defvar('restartfeedrate',1800);
+defvar('retractfeedrate',1800);
+defvar('movefeedrate',7800);
+defvar('zlift',0.1);
+defvar('zprint');
+defvar('orgx',0);
+defvar('orgy',0);
+
+sub float_g ($) {
+    my ($f) = @_;
+    return sprintf "%.5f", $f;
+}
+sub coords_g ($) {
+    my ($coords) = @_;
+    return "X".float_g($coords->[0])." Y".float_g($coords->[1]);
+}
+
+sub p ($) { print "$_[0]" or die $!; }
+sub pl ($) { p("$_[0]\n"); }
+
+sub proc ($);
+
+sub proc ($) {
+    my ($aref) = @_;
+    local ($_);
+    foreach (@$aref) {
+       if (!m/^\!/) {
+           pl($_);
+           next;
+       }
+       pl(";$_");
+       if (m/^\!(\w+)\s*\(\)$/) {
+           my $pname = $1;
+           die "$pname ?" unless $procs{$pname};
+           proc($procs{$pname});
+       } elsif (m/^\!draw\s+/) {
+           my @coords = split /\s+/, $'; #';
+           my @undefs = grep { !defined $c{$_} } qw(zprint);
+           die "@undefs ?" if @undefs;
+           @coords = map {
+               my $jerk = s/^\*//;
+               m/\,/ or die $!;
+               [ $`, $', !!$jerk ]; # '];
+           } @coords;
+           foreach my $co (@coords) {
+               foreach my $xy (qw(0 1)) {
+                   my $xyv = $co->[$xy];
+                   next unless $xyv =~ s/^\@//;
+                   my $orgxy = ($c{orgx},$c{orgy})[$xy];
+                   $co->[$xy] = float_g($xyv + $orgxy);
+               }
+           }
+           my $extrudepos=$c{restart};
+           pl("G92 E0");
+           my $zmove=$c{zprint}+$c{zlift};
+           pl("G1 F$c{movefeedrate} Z$zmove");
+           pl("G1 ".coords_g($coords[0]));
+           pl("G1 Z$c{zprint}");
+           pl("G1 F$c{restartfeedrate} E".float_g($extrudepos));
+           my $lastfeedrate=-1;
+           foreach (my $ci=1; $ci<@coords; $ci++) {
+               my $g = "G1 ".coords_g($coords[$ci]);
+               my $wantfeedrate;
+               if (!$coords[$ci][2]) {
+                   $wantfeedrate=$c{feedrate};
+                   my $dist = 0;
+                   foreach my $xy (qw(0 1)) {
+                       my $dxy = $coords[$ci][$xy] - $coords[$ci-1][$xy];
+                       $dist += $dxy * $dxy;
+                   }
+                   $dist = sqrt($dist);
+                   $extrudepos += $dist * $c{extruderate};
+                   $g .= " E".float_g($extrudepos);
+               } else {
+                   $wantfeedrate=$c{jerkfeedrate};
+               }
+               if ($wantfeedrate != $lastfeedrate) {
+                   $g .= " F$wantfeedrate";
+                   $lastfeedrate = $wantfeedrate;
+               }
+               pl($g);
+           }
+           $extrudepos -= $c{retract};
+           pl("G1 F$c{retractfeedrate} E".float_g($extrudepos));
+           pl("G1 F$c{movefeedrate} Z$zmove");
+           next;
+       } elsif (m/^\!(\w+)\=(\S+)$/) {
+           die "$1 ?" unless exists $c{$1};
+           $c{$1} = $2;
+       } else {
+           die "$_ ?";
+       }
+    }
+}
+
+proc(\@array);
diff --git a/mic-camera-adapter.scad b/mic-camera-adapter.scad
new file mode 100644 (file)
index 0000000..a6cb9f6
--- /dev/null
@@ -0,0 +1,54 @@
+// -*- C -*-
+
+// print on High Detail
+// but adjust infill to 50%, shell thickness to 2mm
+
+include <threads.scad>
+include <camera-mount.scad>
+
+positive_dia = inch * 3/8. - 0.375;
+positive_l = inch * 1/2.;
+
+negative_l = negative_default_l;
+
+negative_wall = 4;
+midsection = 4;
+
+spanner = 12;
+
+base_dia = 35;
+base_th_min = 1;
+base_th_max = 4;
+
+//$test = true;
+$test = false;
+$fs=0.1;
+$fa=5;
+
+module Adapter(){
+  translate([0,0,-0.1])
+    english_thread(diameter=positive_dia/inch, threads_per_inch=16,
+                  leadin=1, test=$test,
+                  length= (positive_l + 0.1) / inch);
+  rotate([180,0,0]) {
+    difference(){
+      union(){
+       cylinder(r= spanner/2 * 1/(0.5 * sqrt(3)),
+                h = negative_l + midsection,
+                $fn=6);
+       translate([0,0, midsection+negative_l]) {
+         mirror([0,0,1]) {
+           hull(){
+             cylinder(r= base_dia/2, h = base_th_min);
+             cylinder(r= 0.1,        h = base_th_max);
+           }
+         }
+       }
+      }
+      translate([0,0, midsection + negative_l])
+       CameraMountThread(negative_l);
+    }
+  }
+}
+
+Adapter();
diff --git a/mic-table-clamp.scad b/mic-table-clamp.scad
new file mode 100644 (file)
index 0000000..b88e8d1
--- /dev/null
@@ -0,0 +1,229 @@
+// -*- C -*-
+
+// print Stem and Wingnut on High Detail
+// but adjust shell thickness to 2mm
+
+// others on Standard
+
+include <utils.scad>
+include <threads.scad>
+include <camera-mount.scad>
+
+positive_dia = inch * 3/8. - 0.375;
+positive_l = inch * 1/2.;
+
+positive_blank_dia = 8.12;
+blank_l = 17;
+blank_taper = 1.0;
+
+stem_l = 40;
+stem_dia = 12;
+stem_th = 3;
+stem_ceil = 2;
+stem_base_th  = 4;
+stem_base_dia = 25;
+stem_inner_l = 15;
+
+thread_nom = 8;
+thread_pitch = 1.25;
+thread_act = thread_nom + 0.600;
+
+clamp_l = 40;
+clamp_top_th = 7;
+clamp_bot_th = 10;
+clamp_bot_tooth_h = 2.5;
+clamp_bot_tooth_d  = 10;
+clamp_bot_collar = 20;
+clamp_bot_collar_th = 3.0;
+clamp_reg_sz1 = 3;
+clamp_reg_sz2 = 5;
+clamp_w = 15;
+clamp_max_table_th = 35;
+
+clamp_hole_dia = thread_nom + 0.30;
+
+clamp_reg_clear_x = 2.5;
+clamp_reg_clear_y = 0.75; // each side
+clamp_reg_extra_x = 4;
+
+//ct_h = 7;
+
+wingnut_th = 5;
+wingnut_wall = 4;
+wingnut_wing_mindia = 17.0;
+wingnut_wing_xrad = 8;
+wingnut_wing_xh = 5;
+wingnut_wing_th = 3;
+
+//$test= true;
+$test= false;
+
+$fa= 3;
+$fs= 0.2;
+
+// calculated
+
+wingnut_cnr = wingnut_wing_th/2 -0.1;
+
+clamp_reg_bot_x_min = -stem_base_dia/2 -clamp_reg_clear_x -clamp_reg_sz2;
+clamp_collar_r = thread_nom/2 + clamp_bot_collar_th;
+
+module OurThread(l){
+  translate([0,0,-0.01])
+    metric_thread(diameter=thread_act, pitch=thread_pitch,
+                 leadin=3, internal=true,
+                 test=$test, length=l);
+}
+
+module StemWith(){
+  translate([0,0, stem_l -0.1])
+    children();
+
+  difference(){
+    union(){
+      cylinder(r= stem_dia/2 * 1/(0.5 * sqrt(3)),
+              h = stem_l,
+              $fn=6);
+      cylinder(r= stem_base_dia/2,
+              h = stem_base_th);
+    }
+    OurThread(stem_inner_l);
+  }
+}  
+
+module Stem(){ ////toplevel
+  StemWith()
+    english_thread(diameter=positive_dia/inch, threads_per_inch=16,
+                  leadin=1, test=$test,
+                  length= (positive_l + 0.1) / inch);
+}
+
+module StemBlankPart(){
+  hull(){
+    cylinder(h = blank_l + 0.1 - blank_taper,
+            r = positive_blank_dia/2);
+    cylinder(h = blank_l + 0.1,
+            r = positive_blank_dia/2 - blank_taper);
+  }
+}
+
+module BlankStem(){ ////toplevel
+  StemWith()
+    StemBlankPart();
+}
+
+module Wingnut(){ ////toplevel
+  difference(){
+    union(){
+      cylinder(r= (thread_nom+wingnut_wall)/2,
+              h= wingnut_th);
+      minkowski(){
+       sphere(r= wingnut_cnr);
+       translate([0,0, wingnut_cnr*0.5])
+         linear_extrude(height= wingnut_wing_xh + wingnut_th
+                        - wingnut_cnr*1.5)
+         square([wingnut_wing_mindia + wingnut_wing_xrad*2 - wingnut_cnr*2,
+                 wingnut_wing_th - wingnut_cnr*2],
+                center=true);
+      }
+    }
+    translate([0,0, wingnut_th])
+      linear_extrude(height= wingnut_wing_xh+1)
+      square(wingnut_wing_mindia, center=true);
+    translate([0,0, wingnut_th])
+      rotate([180,0,0])
+      OurThread(wingnut_th+3);
+    mirror([0,0,1])
+      linear_extrude(height=5)
+      square(center=true, wingnut_wing_mindia*2);
+  }
+}
+
+module ClampCollarPlan(){
+  circle(r= clamp_collar_r);
+}
+module ClampHolePlan(){
+  circle(r= clamp_hole_dia/2);
+}
+module ClampArmPlan(){
+  r = clamp_collar_r;
+  hull(){
+    rectfromto([r,       -clamp_w/2],
+              [clamp_l, +clamp_w/2]);
+    ClampCollarPlan();
+  }
+}
+
+module ClampTop(){ ////toplevel
+  linear_extrude(height = clamp_top_th, convexity=4) {
+    difference(){
+      union(){
+       ClampArmPlan();
+       ClampCollarPlan();
+      }
+      ClampHolePlan();
+    }
+  }
+  linear_extrude(height = clamp_reg_sz1, convexity=4) {
+    difference(){
+      for (m=[0,1]){
+       mirror([0,m,0])
+         translate([0, clamp_reg_sz2/2 + clamp_reg_clear_y, 0])
+         rectfromto([clamp_reg_bot_x_min - clamp_reg_extra_x, 0 ],
+                    [0,                           clamp_reg_sz1 ]);
+      }
+      ClampHolePlan();
+    }
+  }
+}
+
+module ClampBot(){ ////toplevel
+  linear_extrude(height = clamp_bot_th, convexity=4) {
+    difference(){
+      ClampArmPlan();
+      ClampHolePlan();
+    }
+  }
+  translate([clamp_l, 0, clamp_bot_th-0.1])
+    linear_extrude(height = clamp_bot_tooth_h +0.1)
+    rectfromto([ -clamp_bot_tooth_d, -clamp_w/2 ],
+              [  0,                 +clamp_w/2 ]);
+  translate([0,0, clamp_bot_th])
+    mirror([0,0,1])
+    linear_extrude(height = clamp_bot_collar)
+    difference(){
+    ClampCollarPlan();
+    ClampHolePlan();
+  }
+  translate([0, 0, clamp_bot_th]) {
+    linextr(-clamp_reg_sz2, clamp_max_table_th+clamp_reg_sz2) {
+      translate([clamp_reg_bot_x_min, 0]) {
+       rectfromto([ 0,             -clamp_reg_sz2/2 ],
+                  [ clamp_reg_sz2, +clamp_reg_sz2/2 ]);
+      }
+    }
+    linextr(-clamp_reg_sz2, 0) {
+      difference(){
+       rectfromto([ clamp_reg_bot_x_min, -clamp_reg_sz2/2 ],
+                  [  0,                  +clamp_reg_sz2/2 ]);
+       ClampHolePlan();
+      }
+    }
+  }
+}
+
+module StemBlankTest(){ ////toplevel
+  StemBlankPart();
+  linextr(-1.5,0) square(center=true, [10,35]);
+}
+
+module Demo(){ ////toplevel
+  color("blue") translate([0,0, clamp_top_th+0.5]) BlankStem();
+  color("red") ClampTop();
+  color("grey") translate([0,0, -(clamp_bot_th + 5)]) ClampBot();
+  translate([0,0, -(clamp_bot_collar +10)])
+    rotate([180,0,0]) Wingnut();
+}
+
+//Wingnut();
+//Stem();
diff --git a/nook-case-test.scad b/nook-case-test.scad
new file mode 100644 (file)
index 0000000..3f4cc51
--- /dev/null
@@ -0,0 +1,6 @@
+// -*- C -*-
+
+//// toplevels-from:
+include <nook-case.scad>
+
+$test = true;
diff --git a/nook-case.scad b/nook-case.scad
new file mode 100644 (file)
index 0000000..ecbbbc2
--- /dev/null
@@ -0,0 +1,351 @@
+// -*- C -*-
+
+// Infill density: 20%
+
+include <funcs.scad>
+include <utils.scad>
+
+nook_th = 12.41 + 0.50 - 1.50 + 1.35 - .25;
+nook_w = 127.12 + 0.75 - .95 - .50;
+nook_h = 123.44 + 21.88 + 21.05 + 0.75 - 1.90 - 0.50 - 0.50;
+
+edge_ledge_w = 9.60;
+edge_ledge_h = 2.44 - .25;
+edge_ledge_inc_ang = 10; // degrees
+
+usb_w = 14.5;
+usb_below = 1.5;
+
+open_recess_w = 12.5;
+open_recess_h = 2.5;
+
+open_recess_2_len = 15;
+open_recess_2_d_tooth = 30;
+
+nook_cnr_rad = 10;
+
+case_th = 2.5;
+ledge_w = 4;
+tape_th = 1.75;
+tape_inside = 2.0;
+
+gap = 0.5 * [1,1];
+
+tape_w = 15;
+
+test_pillar = 4;
+
+engage_l0 = 10;
+engage_l1 = 10;
+engage_l2 = 3;
+
+tooth_inward = gap[0] * 1.0 + 0.25 + 0.25;
+tooth_w = 15;
+
+diag_near_hinge_slope = 0.5;
+
+$test = false;
+
+$fa = $test ? 10 : 3;
+$fs = $test ? 0.1 : 1;
+
+// calculated
+
+tooth_height = nook_th;
+ledge_h = case_th;
+lid_th = case_th;
+tooth_th = case_th;
+
+spp0 = [0,0];
+spp1 = spp0 + case_th * [-1,0];
+spp9 = spp0 + ledge_h * [0,-1];
+spp8 = spp9 + nook_th * [0,-1];
+spp7 = spp8 + case_th * [-1,-1];
+
+spp11y = spp1[1] - tape_th;
+spp4y  = 0.5 * (spp0[1] + spp7[1]);
+spp3y = spp4y + tape_inside/2;  spp5y = spp4y - tape_inside/2;
+spp2y = spp3y + tape_th;        spp6y = spp5y - tape_th;
+
+spp20 = spp8 + nook_cnr_rad * [1,0];
+spp20x = spp20[0];
+
+tppA = spp0 + gap;
+tppB = spp1 + [0, gap[1]];
+tppC = tppB + lid_th * [0,1];
+tppD = [ spp20x, tppC[1] ];
+tppE = [ spp20x, tppB[1] ];
+tppF = tppA + ledge_w * [1,0];
+tppG = tppF + ledge_h * [0,-1];
+tppH = [ tppA[0], tppG[1] ];
+
+tppJx = tppA[0] + tape_th;
+
+tppK = [ tppC[0], tppG[1] ];
+spp31 = tppK - [0, gap[1]];
+spp30 = [ spp8[0], spp31[1] ];
+
+nom_cnr = 0.5 * [nook_w, nook_h, 0] - nook_cnr_rad * [1,1,0];
+
+tooth_y = nom_cnr[1] - tooth_w/2;
+
+etxa = nom_cnr[0] - engage_l2;
+etxb = etxa - engage_l1;
+etxc = -(nom_cnr[0] - engage_l2);
+
+tapa = nom_cnr[1] - engage_l2;
+tapb = tapa - tape_w;
+
+opra = nom_cnr[1] - engage_l2;
+oprb = opra - open_recess_w;
+
+opqa = tooth_y - open_recess_2_d_tooth + open_recess_2_len/2;
+opqb = opqa - open_recess_2_len;
+
+tppS = tppB + [-gap[0], 0];
+tppP = [ tppS[0] - tooth_th, tppC[1] ];
+tppQ = tppP + tooth_height * [0,-1] + tooth_inward * [1,0];
+tppR = [ tppS[0] + tooth_inward, tppQ[1] ];
+tppM = (tppQ + tppR) * 0.5 + tooth_th * 0.5 * [0,1];
+
+edge_ledge_rad = edge_ledge_h;
+
+module RightSideMainProfile() {
+  rectfromto(spp7, spp0);
+  rectfromto(spp7, spp20);
+  EdgeLedgeProfile();
+}
+
+module LeftSideMainProfile() {
+  rectfromto(spp7, spp30);
+  rectfromto(spp7, spp20);
+  EdgeLedgeProfile();
+}
+
+module EdgeLedgeProfile() {
+  intersection(){
+    hull(){
+      for (t=[[0,0], [-20,0], [0,-10]]) {
+       translate(spp8
+                 + [edge_ledge_w, edge_ledge_h]
+                 + edge_ledge_rad * [ -sin(edge_ledge_inc_ang),
+                                      -cos(edge_ledge_inc_ang) ]
+                 + t)
+         circle(edge_ledge_rad);
+      }
+    }
+    translate(spp7)
+      square(30);
+  }
+}
+
+module TopTapeCutout() {
+  polygon([ tppA,
+           tppA + [-40, 0],
+           tppG + [-40,-1],
+           [ tppJx, tppH[1]-1 ],
+           [ tppJx, tppC[1]+1 ],
+           [ tppA[0], tppC[1]+1 ]]);
+}
+
+module RightTopMainProfile() {
+  l = [ tppA, tppB, tppC, tppD, tppE, tppF, tppG, tppH ];
+  polygon(l);
+}
+
+module LeftTopMainProfile() {
+  l = [ tppC, tppD, tppE, tppF, tppG, tppK ];
+  polygon(l);
+}
+
+module SideTapeCutout1(y0,y1) {
+  rectfromto([ spp7[0]-1, y0 ],
+            [ spp8[0]+1, y1 ]);
+}
+
+module SideTapeCutout() {
+  SideTapeCutout1(spp6y, spp5y);
+  SideTapeCutout1(spp3y, spp2y);
+  SideTapeCutout1(spp3y, spp2y);
+  SideTapeCutout1(spp11y, spp1[1] + 1); // obsolete I think
+}
+
+module ToothProfile(){
+  polygon([tppA,
+          tppB,
+          tppS + [-0.1,0],
+          tppP,
+          tppC]);
+  hull(){
+    polygon([tppP,
+            tppM,
+            tppS]);
+    translate(tppM)
+      circle(r= tooth_th/2);
+  }
+}
+
+module Demo(){ ////toplevel
+  translate([-1,0,0]) {
+    translate([0,0,-2]) LeftSideMainProfile(); 
+    translate([0,0,-2]) color("yellow") LeftTopMainProfile();
+    color("red") difference(){
+      LeftSideMainProfile();
+      SideTapeCutout();
+    }
+    translate([0,0,-4]) color("brown") EdgeLedgeProfile();
+    translate(concat(spp8 + [edge_ledge_w, edge_ledge_h], [2]))
+      rotate(-edge_ledge_inc_ang) {
+      color("blue") square(3);
+      color("lightblue") mirror([1,0]) square(3);
+    }
+  }
+  translate([0,0,0]) color("purple") difference(){
+    LeftTopMainProfile();
+    TopTapeCutout();
+  }
+  translate([nook_cnr_rad*2 + 5, 0,0]) mirror([1,0,0]) {
+    color("red") RightSideMainProfile();
+    color("purple") RightTopMainProfile();
+    color("grey") translate([0,0,-2]) ToothProfile();
+  }
+  //%SideTapeCutout();
+}
+
+module FaceCore(z0,z1, extra_left, extra_right){
+  difference(){
+    for (mx=[0,1]) mirror([mx,0,0]) {
+       for (my=[0,1]) mirror([0,my,0]) {
+           translate(-nom_cnr) {
+             rotate_extrude(angle=90, convexity=10) {
+               intersection(){
+                 translate(-[1,0,0] * nook_cnr_rad)
+                   children(mx);
+                 rectfromto([-100,-100], [0,100]);
+               }
+             }
+           }
+         }
+       translate([nook_w/2, 0,0])
+         linextr_y_xz(-nom_cnr[1]-0.1, nom_cnr[1]+0.1)
+         children(1-mx);
+      }
+    for (my=[0,1]) mirror([0,my,0]) {
+       translate([-nook_w/2, 0,0])
+         mirror([1,0,0])
+         linextr_y_xz(tapb, tapa)
+         children(2);
+      }
+  }
+  for (my=[0,1]) mirror([0,my,0]) {
+      translate([0, -nook_h/2, 0]) {
+       linextr_x_yz(-nom_cnr[0]-0.1,    etxc + extra_left)  children(0);
+       linextr_x_yz(etxc - extra_right, etxb + extra_right) children(1);
+       linextr_x_yz(etxb - extra_left,  etxa + extra_left)  children(0);
+       linextr_x_yz(etxa - extra_right, nom_cnr[0]+0.1)     children(1);
+      }
+    }
+  if (!$test) {
+    linextr(z0,z1)
+      rectfromto(-nom_cnr, nom_cnr);
+  }
+}
+
+module DiagonaliseNearHinge(wider){
+  sz = spp0[1] - spp30[1] + gap[1];
+
+  for (my=[0,1]) mirror([0,my,0]) {
+      translate([-etxa, -nook_h/2, 0])
+       mirror([1,0,0])
+       linextr_y_xz(spp31[0] - wider, spp30[0] + gap[0] + 0.1)
+       translate([ 0, spp30[1] ])
+       polygon([[  -1, 0 ],
+                [   0, 0 ],
+                [  sz/diag_near_hinge_slope, sz ],
+                [  sz/diag_near_hinge_slope, sz + 0.1 ],
+                [  -1, sz + 0.1 ]]);
+    }
+}    
+
+module Base(){ ////toplevel
+  difference(){
+    FaceCore(spp7[1],spp8[1], 0.3, 0) {
+      LeftSideMainProfile();
+      RightSideMainProfile();
+      SideTapeCutout();
+    }
+    translate([0, -nook_h/2, 0])
+      mirror([0,1,0])
+      linextr_x_yz(-usb_w/2, usb_w/2)
+      rectfromto(spp8 + [-40, usb_below], [40, 40]);
+    translate([ gap[0], 0,0 ])
+      DiagonaliseNearHinge(10);
+/*
+    translate([nook_w/2, 0, 0])
+      linextr_y_xz(oprb, opra)
+      translate(spp0)
+      rectfromto([-40, -open_recess_h], [40, 1]);
+*/
+  }
+  if ($test) {
+    linextr(spp7[1], spp8[1]) {
+      hull(){
+       for (r=[0,180])
+         rotate([0,0,r])
+           translate(nom_cnr + -1 * nook_cnr_rad*[1,1])
+           square(12);
+      }
+    }
+  }
+}
+
+module Top(){ ////toplevel
+  difference(){
+    FaceCore(tppE[1],tppD[1], -gap[0], gap[0] + 0.3) {
+      LeftTopMainProfile();
+      RightTopMainProfile();
+      TopTapeCutout();
+    }
+    translate([nook_w/2, 0,0])
+      linextr_y_xz(opqb, opqa)
+      rectfromto(spp8, tppC + [-1,1]);
+  }
+  translate([0,0, gap[1]])
+    DiagonaliseNearHinge(0);
+  translate([nook_w/2, tooth_y, 0])
+    linextr_y_xz(-tooth_w/2, +tooth_w/2)
+    ToothProfile();
+}
+module TopPrint(){ ////toplevel
+  rotate([0,0,90]) rotate([180,0,0]) Top();
+}
+module BasePrint(){ ////toplevel
+  rotate([0,0,90]) Base();
+}
+
+module TestExtrude(){
+  difference(){
+    linextr_y_xz(-test_pillar, tape_w+test_pillar) children(0);
+    linextr_y_xz(           0, tape_w            ) children(1);
+  }
+}
+
+module BaseTestRest(){ ////toplevel
+  cube([30,15, spp8[1]-spp7[1]]);
+}
+
+module Demo3(){ ////toplevel
+  color("purple") Top();
+  color("red") Base();
+}
+
+module TestSide(){ ////toplevel
+  TestExtrude() { LeftSideMainProfile(); SideTapeCutout(); }
+}
+
+module TestTop(){ ////toplevel
+  TestExtrude() { LeftTopMainProfile(); TopTapeCutout(); }
+}
+module TestTopPrint(){ ////toplevel
+  rotate([180,0,0]) TestTop();
+}
diff --git a/nutbox.scad.m4 b/nutbox.scad.m4
new file mode 100644 (file)
index 0000000..5384eff
--- /dev/null
@@ -0,0 +1,65 @@
+// -*- C -*-
+// edit nutbos.scad.m4, not nutbos.scad!
+// shaft, nut_across, nut_thick, nut_recess, wall, ceil
+
+nutbox_data_M4 = [
+                  4.0 + 0.5,
+                  6.89 + 0.45,
+                  3.10 + 0.75,
+                  0.75,
+                  2.0,
+                  2.5
+                  ];
+
+nutbox_data_M3 = [
+                  3.0 + 0.5,
+                  5.48 + 0.45,
+                  2.26 + 0.75,
+                  0.75,
+                  1.8,
+                  2.0
+                  ];
+
+m4_define(shaft,      (nutbox_data[0]))
+m4_define(nut_across, (nutbox_data[1]))
+m4_define(nut_thick,  (nutbox_data[2]))
+m4_define(nut_recess, (nutbox_data[3]))
+m4_define(wall,       (nutbox_data[4]))
+m4_define(ceil,       (nutbox_data[5]))
+
+m4_define(nut_dia, (nut_across / cos(30)))
+m4_define(outer_size, (nut_dia + wall * 2))
+m4_define(h_base, (ceil + nut_thick + nut_recess))
+
+function NutBox_shaft(nutbox_data) = shaft;
+function NutBox_outer_size(nutbox_data) = outer_size;
+function NutBox_h_base(nutbox_data) = h_base;
+function NutBox_wall(nutbox_data) = wall; // not sure why anyone needs this
+
+module NutBox(nutbox_data, h, h_above_extra=0) {
+  // origin is centre of top of mount
+  // entrance is to positive y
+  // height is h which must be at least h_base
+  // can be mad extra tall (with hole all the way through) with h_above_extra
+  w = outer_size;
+  difference(){
+    mirror([0,0,1]) translate([-w/2,-w/2, -h_above_extra])
+      cube([w,w, h + h_above_extra]);
+    mirror([0,0,1]) translate([0,0,-1 -h_above_extra])
+      cylinder(r = shaft/2, h = h+2 + h_above_extra, $fn=20);
+    for (offset = [ [0,0, -nut_recess],
+                   [0, outer_size, 0] ]) {
+      hull(){
+       for (toffset = [[0,0,0], offset]) {
+         translate(toffset)
+           translate([0,0, -ceil])
+           mirror([0,0,1])
+           rotate([0,0, 360/6/2])
+           cylinder(r = nut_dia/2,
+                    h = nut_thick,
+                    $fn = 6);
+       }
+      }
+    }
+  }
+}
diff --git a/osstest-arm-hub-bracket.scad b/osstest-arm-hub-bracket.scad
new file mode 100644 (file)
index 0000000..63fefed
--- /dev/null
@@ -0,0 +1,36 @@
+// -*- C -*-
+
+len = 80;
+basethick = 4;
+sidewall = 5;
+width = 40;
+
+strapthick = 4;
+strapwidth = 7;
+
+strapbotgap = 1;
+strapsidegap = 4;
+overstrap = 4;
+
+wallheight = strapbotgap + strapthick + overstrap;
+
+availlen = (len - strapsidegap);
+numstraps = floor(availlen / (strapwidth + strapsidegap));
+strapstride = availlen / numstraps;
+echo(numstraps, strapstride);
+
+module Bracket(){
+  difference(){
+    cube([len, width, basethick+wallheight]);
+    translate([-1, sidewall, basethick])
+      cube([len+2, width-sidewall*2, wallheight+1]);
+    for (i=[0:numstraps-1]) {
+      translate([ (0.5+i)*strapstride + strapsidegap/2,
+                 width/2,
+                 basethick + strapbotgap + strapthick/2 ])
+       cube([strapwidth, width*2, strapthick], center=true);
+    }
+  }
+}
+
+Bracket();
diff --git a/osstest-arm-net-bracket.scad b/osstest-arm-net-bracket.scad
new file mode 100644 (file)
index 0000000..54e7d3b
--- /dev/null
@@ -0,0 +1,94 @@
+// -*- C -*-
+
+holedist = 64;
+tonguewidth = 10;
+tongue2width = 15;
+totaldepth = 26;
+tongue2depth = 35;
+thick = 4;
+tabover = 7+6;
+tabunder = 15;
+
+tabsidel = 7+1;
+tabsider = 7+10;
+
+tonguethick = 4;
+tongue2thick = 5;
+strapthick = 2;
+strapwidth = 5 + 0.35;
+ridgewidth = 2;
+
+hstrengthick = 2.5;
+hstrengdepth = strapwidth;
+
+cutoutover = 7;
+rcutoutside = 7+2 - 0.5;
+lcutoutside = 7-6.5 - 0.5;
+
+t2strengwidth = 10;
+t2strengwidtht = 4;
+t2strenglen = tongue2depth + 5;
+t2strengthicker = 1;
+
+strapholethicker = 1.5;
+
+holedia = 3.5;
+
+tongue2x = tongue2width - holedist;
+
+module Tongue(tw,tt,ad,slots=2){
+  y0=thick+0.1;
+  yn=ad-ridgewidth-strapwidth;
+  difference(){
+    union(){
+      translate([-tw, 0, 0])
+       cube([tw, ad, tt+strapthick]);
+      child();
+    }
+    for (yi=[1:slots-1])
+      translate([-tw-1, y0 + (yn-y0)*yi/(slots-1), tt])
+       cube([tw+2, strapwidth, strapthick+strapholethicker]);
+  }
+}
+
+module Body(){
+  translate([-holedist-tabsider, 0, 0]) {
+    cube([tabsidel+tabsider+holedist, thick, tabunder+tabover]);
+    cube([tabsidel+tabsider+holedist, thick+hstrengdepth, hstrengthick]);
+  }
+  Tongue(tonguewidth,tonguethick,totaldepth,3);
+  translate([tongue2x,0,0])
+    Tongue(tongue2width,tongue2thick,tongue2depth+thick,5) {
+    mirror([1,0,0]) hull(){
+      translate([-(tongue2width-t2strengwidth)*0.05, 0,0]) 
+       cube([t2strengwidth,t2strenglen,
+             tongue2thick+strapthick+strapholethicker+t2strengthicker]);
+      cube([t2strengwidtht,thick+0.1,tabunder+tabover]);
+    }
+  }
+}
+
+module Object(){
+  difference(){
+    Body();
+    translate([0,-25,tabunder+cutoutover]) {
+      translate([-(holedist+rcutoutside), 0,0])
+       mirror([1,0,0])
+       cube([50,50,50]);
+      translate([lcutoutside, 0,0])
+       cube([50,50,50]);
+    }
+    for (x=[-holedist,0])
+      translate([x, 0, tabunder]) {
+       translate([0, -1, 0]) {
+         rotate([-90,0,0]) {
+           cylinder(r= holedia/2+0.5, h=thick+2, $fn=20);
+         }
+       }
+       translate([0, 19.95 + thick, 0])
+         cube(center=true,[10,40,10]);
+      }
+  }
+}
+
+Object();
diff --git a/osstest-arm-psu-bracket.scad b/osstest-arm-psu-bracket.scad
new file mode 100644 (file)
index 0000000..2703cbc
--- /dev/null
@@ -0,0 +1,88 @@
+// -*- C -*-
+
+mainlen = 33;
+straps = [10,23];
+width = 60;
+endwall = 5;
+sidewall = 8;
+basethick = 3;
+endwallheight = 20;
+morebase = 20;
+
+plugwidth = 35;
+plugstartheight = 10;
+
+strapthick = 4;
+strapwidth = 7;
+strapbotgap = 1;
+overstrap = 6;
+
+discdia = 60;
+discoff_rear = 10;
+discoff_front = 50;
+
+sidewallraise = strapbotgap + strapthick + overstrap;
+
+module Sides(){
+  difference(){
+    for (y=[0, width-sidewall]) {
+      translate([0,y,0])
+       cube([mainlen, sidewall, basethick + sidewallraise]);
+    }
+    for (x=straps) {
+      translate([x, 0, basethick + strapbotgap + strapthick/2])
+       cube([strapwidth, width*3, strapthick], center=true);
+    }
+  }
+}
+
+module Ell(baseoff){
+  translate([-endwall,0,0]) {
+    translate([baseoff,0,0])
+      cube([mainlen + endwall + morebase, width, basethick]);
+    cube([endwall+0.1, width, endwallheight + sidewallraise + basethick]);
+  }
+}
+
+module Plug(){
+  translate([0, width/2,
+            basethick + sidewallraise + plugstartheight + 50])
+    cube([endwall*3, plugwidth, 100], center=true);
+}
+
+module Disc(discoff){
+  translate([discoff + discdia/2, width/2, -1])
+    cylinder(r=discdia/2, h=50, $fn=100);
+}
+
+module Main(baseoff){
+  difference(){
+    union(){
+      Ell(baseoff);
+      Sides();
+    }
+    Plug();
+  }
+}
+
+module RearBlock(){
+  difference(){
+    Main(-morebase);
+    Disc(discoff_rear);
+  }
+}
+
+module FrontBlock(){
+  difference(){
+    Main(0);
+    Disc(discoff_front - endwall);
+  }
+}
+
+module Both(){
+  RearBlock();
+  translate([mainlen + endwall + 10, 0, 0])
+    FrontBlock();
+}
+
+Both();
diff --git a/pandemic-counter-letters.fig b/pandemic-counter-letters.fig
new file mode 100644 (file)
index 0000000..fb9c92a
--- /dev/null
@@ -0,0 +1,16 @@
+#FIG 3.2  Produced by xfig version 3.2.5b
+Landscape
+Center
+Metric
+A4      
+100.00
+Single
+-2
+1200 2
+1 3 0 1 0 7 50 -1 -1 0.000 1 0.0000 3780 3870 675 675 3780 3870 4455 3870
+4 0 0 32 -1 14 56 0.0000 4 540 555 3522 4119 A\001
+4 0 0 30 -1 14 56 0.0000 4 570 555 3510 4140 C\001
+4 0 0 33 -1 14 56 0.0000 4 570 555 3510 4140 S\001
+4 0 0 35 -1 14 48 0.0000 4 465 960 3337 4098 DF\001
+4 0 0 34 -1 14 56 0.0000 4 540 555 3510 4140 T\001
+4 0 0 31 -1 14 56 0.0000 4 540 555 3537 4125 L\001
diff --git a/pandemic-counter.scad b/pandemic-counter.scad
new file mode 100644 (file)
index 0000000..4d427af
--- /dev/null
@@ -0,0 +1,115 @@
+// -*- C -*-
+
+tokenrad=13;
+tokenthick=1.9;
+
+joinwidth=1.0;
+
+circlerad=15;
+
+module Letter(depth) {
+  translate([-circlerad,-circlerad])
+    import(file=str("pandemic-counter-l",depth,".dxf"), convexity=100);
+}
+
+module Token(depth) {
+  rotate([0,180,0])
+  linear_extrude(height=tokenthick) union(){
+    difference(){
+      circle(tokenrad);
+      Letter(depth);
+    }
+    child();
+  }
+}
+
+module Token_CDC(){ ////toplevel
+  Token(30){};
+}
+module Token_Lab(){ ////toplevel
+  Token(31){};
+}
+module Token_Act(){ ////toplevel
+  Token(32){
+    translate([0, 1])
+      square([tokenrad*.75, joinwidth], center=true);
+  }
+}
+module Token_Spec(){ ////toplevel
+  Token(33){};
+}
+//module Token_Terr(){ ////toplevel
+//  Token(34){};
+//}
+//module Token_TerrMove(){ ////toplevel
+//  Token(35){
+//    translate([-tokenrad*.75, -1])
+//      square([tokenrad*.75, joinwidth]);
+//  };
+//}
+
+spacing = tokenrad * 2 + 2;
+
+module Tokens(rows=1,cols=1) {
+  for (i=[0:rows-1])
+    for (j=[0:cols-1])
+      translate([j*spacing, i*spacing, 0])
+       child(0);
+}
+
+module Tokens_Act(){ ////toplevel
+  // Print *twice*, LAPIS BLUE or SQUEEZED ORANGE
+  // ordinary actions
+  //  up to 4 for 5 players, plus 2 for Borrowed Time plus 1 for Generalist
+  //  so need 23, make 24
+  Tokens(4,3) Token_Act();
+}
+
+module Tokens_Spec(){ ////toplevel
+  // ELECTRIC BLUE or MELLOW YELLOW
+  // once-per-turn special action, one each for 5 players
+  Tokens(3) Token_Spec();
+  translate([spacing,0,0]) Tokens(2) Token_Spec();
+}
+
+module Tokens_CDC(){ ////toplevel
+  // STORM GREY
+  // CDC
+  // 1 action per turn + 2 Borrowed Time
+  Tokens(3) Token_CDC();
+}
+
+module Tokens_Lab(){ ////toplevel
+  // WHITE
+  // free Lab action (on building research station, etc)
+  // make 2 (probably want less than that)
+  Tokens(2) Token_Lab();
+}
+
+//module Tokens_Terr(){ ////toplevel
+//  // FIRE TRUCK RED
+//  // Bioterrorist general actions
+//  Tokens(2) Token_Terr();
+//}
+
+//module Tokens_TerrMove(){ ////toplevel
+//  // CLASSIC BLACK
+//  // Bioterrorist drive/ferry
+//  Tokens(1) Token_TerrMove();
+//}
+
+module PosToken(i,j){
+  translate([j*spacing, i*spacing, 0]) child();
+}
+
+module Demo(){ ////toplevel
+  PosToken(0,0) Token_CDC();
+  PosToken(1,0) Token_Lab();
+  PosToken(2,0) Token_Act();
+  PosToken(3,0) Token_Spec();
+//  PosToken(1,1) Token_Terr();
+//  PosToken(2,1) Token_TerrMove();
+}
+
+//Tokens_Act();
+//Demo();
diff --git a/pandemic-counter.slic3r b/pandemic-counter.slic3r
new file mode 100644 (file)
index 0000000..add6df7
--- /dev/null
@@ -0,0 +1 @@
+fill_density = 0.5
diff --git a/pandemic-quarantine-numbers.fig b/pandemic-quarantine-numbers.fig
new file mode 100644 (file)
index 0000000..1524380
--- /dev/null
@@ -0,0 +1,13 @@
+#FIG 3.2  Produced by xfig version 3.2.5b
+Landscape
+Center
+Metric
+A4      
+100.00
+Single
+-2
+1200 2
+2 2 0 1 0 7 50 -1 -1 0.000 0 0 -1 0 0 5
+        1800 1800 2385 1800 2385 2385 1800 2385 1800 1800
+4 0 0 1 -1 14 24 0.0000 4 255 240 1980 2205 1\001
+4 0 0 2 -1 14 24 0.0000 4 255 240 1980 2205 2\001
diff --git a/pandemic-quarantines.scad b/pandemic-quarantines.scad
new file mode 100644 (file)
index 0000000..834d128
--- /dev/null
@@ -0,0 +1,65 @@
+// -*- C -*-
+
+prisml = 13;
+triedge = 13;
+
+etchdepth = 1.0;
+
+figboxsize = 13;
+
+// calculated
+
+triheight = triedge / 2 * sqrt(3);
+tricentre = triedge / 2 * tan(30);
+
+module Number(number) {
+  translate([-figboxsize/2, -figboxsize/2])
+    import(file=str("pandemic-quarantine-l",number,".dxf"), convexity=100);
+}
+
+module FaceTriangle(){
+  x = triedge / 2;
+  y = triheight;
+  polygon([[-x,  0],
+          [ 0,  y],
+          [ x,  0]]);
+}
+
+module Body(){
+  translate([0, prisml/2, 0])
+    rotate([90,0,0])
+    linear_extrude(height=prisml) FaceTriangle();
+}
+
+module NumberCut(number){
+  translate([0,0, -etchdepth])
+    linear_extrude(height= etchdepth + 1)
+    Number(number);
+}
+
+module Etchings(){
+  for (rot=[0,180]) {
+    rotate([0,0, rot])
+      translate([0, -prisml/2, triedge * 0.3])
+      rotate([90, 0, 0])
+      NumberCut(2);
+  }
+  for (rot=[0,120,240]) {
+    translate([0,0, tricentre])
+      rotate([0, rot, 0])
+      translate([0,0, -tricentre])
+      rotate([0,180,0])
+      rotate([0,0, rot==240 ? 90 : -90])
+      NumberCut(1);
+  }
+}
+
+module Counter(){
+  difference(){
+    Body();
+    Etchings();
+  }
+}
+
+Counter();
+//NumberCut(1);
diff --git a/pannierstay.scad b/pannierstay.scad
new file mode 100644 (file)
index 0000000..65538a6
--- /dev/null
@@ -0,0 +1,14 @@
+len    = 54.3 - 1;
+bigdep = 10.7 - 1;
+smalldep=  4.9 - 1;
+tallw1 =  3.5 - 1.5;
+tallw2 =  3.5 - 1.5; // ish
+totalw = 11.6 - 2;
+
+gapw = totalw - tallw1 - tallw2;
+
+difference() {
+  cube([len, totalw, bigdep]);
+  translate([-1, tallw2, smalldep])
+    cube([len+2, gapw, bigdep - smalldep + 1]);
+}
diff --git a/pattress-boxes-3-cover.scad b/pattress-boxes-3-cover.scad
new file mode 100644 (file)
index 0000000..7c1d4b8
--- /dev/null
@@ -0,0 +1,201 @@
+// -*- C -*-
+
+patbox_side = 87;
+patbox_centres = 60.3;
+
+lid_thinboxbase_overlap = 5;
+lid_fatbox_overlap = 12;
+
+lid_thinbox_h = 9;
+lid_fatbox_h = 24;
+lid_fatbox_switches_h = 6+4;
+
+lid_max_switches_w = 70;
+lid_switches_y_slop = 3;
+
+total_len = 260;
+thinbox_len = 87;
+
+rail_overlap = 8;
+
+lid_top_wall = 1.5;
+lid_front_wall = 1.5;
+lid_side_wall = 1.5;
+
+peg_engage_depth = 1;
+peg_engage_dia = 6.5;
+peg_main_dia = 9;
+peg_max_dia = 15;
+peg_inner_dia = 3.5;
+peg_top_thick = 1;
+peg_straight_len = 3;
+
+$peg_inner_slop = 0.75;
+$peg_outer_slop = -0.9;
+$peg_outer_slop_engage = 0.1;
+
+peg_slope = 1;
+
+lid_side_slop = 0.5;
+lid_rail_behindslop = 0.5;
+lid_rail_strongwall = 2.5;
+
+// computed
+
+lid_inner_max_h =
+  lid_thinboxbase_overlap + lid_fatbox_h + lid_fatbox_switches_h;
+lid_inner_min_h = lid_fatbox_overlap + lid_fatbox_switches_h;
+
+lid_inner_kink = [thinbox_len, thinbox_len*2];
+
+lid_inner_w_nom = patbox_side;
+lid_inner_w = lid_inner_w_nom + lid_side_slop * 2;
+
+lid_seatline_w = (lid_inner_w - lid_max_switches_w)/2 - lid_switches_y_slop;
+
+lid_seatline_h = lid_fatbox_switches_h;
+
+peg_main_height = peg_straight_len + (peg_max_dia - peg_main_dia)/2/peg_slope;
+
+thinbox_front_z = lid_fatbox_switches_h + lid_fatbox_h - lid_thinbox_h;
+
+raillen = patbox_side/2 + rail_overlap;
+
+module LidSideProfile(){
+  polygon([[-lid_top_wall,     lid_inner_max_h],
+          [min(lid_inner_kink[0],total_len), lid_inner_max_h],
+          [min(lid_inner_kink[1],total_len), lid_inner_min_h],
+          [total_len,         lid_inner_min_h],
+          [total_len,         -lid_front_wall],
+          [-lid_top_wall,     -lid_front_wall]]);
+}
+
+module RailProfile(lslop=0.1){
+  yt_base = thinbox_front_z;
+  yt = yt_base - lid_rail_behindslop;
+  pegx = (lid_inner_w_nom - patbox_centres)/2;
+  sw = lid_rail_strongwall;
+  
+  polygon([[-lslop,                  yt],
+          [pegx - peg_main_dia/2, yt],
+          [pegx - peg_main_dia/2, yt_base - peg_straight_len],
+          [sw,
+           yt_base - peg_straight_len - (pegx - peg_main_dia/2)/peg_slope
+           +sw],
+          [sw,
+           lid_seatline_h - 1],
+          [-lslop,
+           lid_seatline_h - 1]]);
+}
+
+module LidSide(){
+  overlap = [0.1, 0.1, 0.1];
+
+  // main side profile
+  rotate([90,0,0])
+    linear_extrude(height= lid_side_wall)
+    LidSideProfile();
+
+  // seatline
+  translate(-overlap)
+    cube(overlap + [total_len, lid_seatline_w, lid_seatline_h]);
+
+  // lid front
+  translate([-0.1, -0.1, -lid_front_wall])
+    cube([total_len+0.1, lid_inner_w/2 + 0.2, lid_front_wall]);
+
+  // lid top
+  translate([-lid_top_wall, -lid_side_wall, -lid_front_wall])
+    cube([lid_top_wall, lid_inner_w/2 + 10, lid_front_wall + lid_inner_max_h]);
+
+  // rail
+  rotate([90,0,90])
+    translate([0,0,-0.1])
+    linear_extrude(height=raillen+0.1) //todo
+    RailProfile();
+
+  // rail end
+  translate([raillen, 0,0])
+    intersection(){
+      rotate_extrude($fn=50)
+       RailProfile(lslop=0);
+      translate([0, 25-0.1, 0])
+       cube(50, center=true);
+    }
+}
+
+module Lid(){ ////toplevel
+  for (m=[0,1])
+    mirror([0,m,0])
+      translate([0, -lid_inner_w/2, 0]) LidSide();
+}
+
+module PegProfile(){
+  polygon([[-peg_engage_depth, (peg_engage_dia - $peg_outer_slop_engage)/2],
+          [0,                 (peg_engage_dia - $peg_outer_slop_engage)/2],
+          [0,                 (peg_main_dia - $peg_outer_slop)/2],
+          [peg_straight_len,  (peg_main_dia - $peg_outer_slop)/2],
+          [peg_main_height,   (peg_max_dia - $peg_outer_slop)/2],
+          [peg_main_height+peg_top_thick, (peg_max_dia - $peg_outer_slop)/2],
+          [peg_main_height+peg_top_thick, (peg_inner_dia + $peg_inner_slop)/2],
+          [-peg_engage_depth,  (peg_inner_dia + $peg_inner_slop)/2]]);
+}
+
+module Peg(){ ////toplevel
+  echo($peg_outer_slop_engage);
+  rotate_extrude($fn=50)
+    rotate([0,0,-90])
+    PegProfile();
+}
+
+module Pegs(){ ////toplevel
+  baseslop = 0.1;
+  dslops = [0, -0.5, -1.0, -1.5];
+  stride = peg_max_dia + 4;
+  for (i=[0:len(dslops)-1]) {
+    translate([i*stride,0,0])
+      assign($peg_outer_slop_engage= baseslop + dslops[i])
+      Peg();
+  }
+}
+
+module AtFixingCentres(){
+  for (fc=[-1,+1]) {
+    translate([patbox_side/2 + fc*patbox_centres/2,
+              patbox_side/2,
+              0])
+      children();
+  }
+}
+
+module TopPattressBox(){
+  difference(){
+    translate([0,0, -lid_thinbox_h])
+      cube([patbox_side, patbox_side, lid_thinbox_h]);
+    AtFixingCentres(){
+      translate([0,0,-10]) cylinder(r=peg_engage_dia/2, h=20);
+    }
+  }
+}
+
+module Demo(){
+  Lid();
+  translate([0,0, thinbox_front_z])
+    rotate([0,180,0]) translate([0, -patbox_side/2, 0])
+    rotate([0,0,90]) union(){
+      %TopPattressBox();
+      color("blue") AtFixingCentres(){
+       rotate([180,0,0]) Peg();
+      }
+  }
+}
+
+//LidSide();
+//PegProfile();
+//Peg();
+//Pegs();
+//TopPattressBox();
+//RailProfile();
+//Demo();
+//Lid();
+//translate([0,0,-lid_fatbox_switches_h]) Lid();
diff --git a/pawn.scad b/pawn.scad
new file mode 100644 (file)
index 0000000..7277dcd
--- /dev/null
+++ b/pawn.scad
@@ -0,0 +1,80 @@
+// -*- C -*-
+
+// shape parameters
+
+r1 =  2.85;
+r2 =  4; a2 = 27;
+r3 = r2;
+r4 =  4; a4 = 18;
+r5 = 30;
+h6 = 7; a6 = 4;
+
+// coordinates
+
+                                z1 =  0;
+h2 = r2 * sin(a2);              z2 = z1 - h2;
+h3 = r3 * sin(a2);              z3 = z2 - h3;
+h4 = r4 * sin(a4);              z4 = z3 - h4;
+
+zc5 = z4 - r5 * sin(a4);
+z5 = zc5 + r5 * sin(a6);        z6 = z5 - h6;
+
+x1 =  0 - r1;
+x2 = x1 + r2 * (1-cos(a2));
+x3 = x2 + r3 * (1-cos(a2));
+x4 = x3 - r4 * (1-cos(a4));
+
+xc5 = x4 + r5 * cos(a4);
+x5 = xc5 - r5 * cos(a6);
+
+x6 = x5 - h6 * tan(a6);
+
+htotal = r1 - z6;
+echo("height", htotal);
+
+d = 0.01;
+dx = 0.00;
+
+$fa=2;
+$fs=0.2;
+
+module SegmentBasisSquare(zmin, zmax, xmin){
+  translate([xmin, zmin-d]) square([-xmin+dx, zmax-zmin+d*2]);
+}
+module ConvexSegment(xc, zc, r, zmin, zmax){
+  intersection(){
+    translate([xc,zc]) circle(r=r);
+    SegmentBasisSquare(zmin,zmax,-50);
+  }
+}
+module ConcaveSegment(xc, zc, r, zmin, zmax){
+  difference(){
+    SegmentBasisSquare(zmin,zmax, xc);
+    translate([xc,zc]) circle(r=r);
+  }
+}
+
+module PawnTemplate(){
+  ConvexSegment(  x1 + r1,  z1,   r1, z1, 50);
+  ConvexSegment(  x1 + r2,  z1,   r2, z2, z1);
+  ConcaveSegment( x3 - r3,  z3,   r3, z3, z2);
+  ConcaveSegment( x3 - r4,  z3,   r4, z4, z3);
+  ConvexSegment(  xc5,      zc5,  r5, z5, z4);
+  polygon([[x6, z6],
+          [x5, z5+d],
+          [dx, z5+d],
+          [dx, z6]]);
+}
+
+module Pawn(h=htotal){
+  scale(h/htotal) {
+    rotate_extrude(convexity=10, $fn=50){
+      assign($fn=undef){
+       PawnTemplate();
+      }
+    }
+  }
+}
+
+Pawn(h=30);
+//PawnTemplate();
diff --git a/pawn.slic3r b/pawn.slic3r
new file mode 100644 (file)
index 0000000..be356f2
--- /dev/null
@@ -0,0 +1,7 @@
+first_layer_bed_temperature = 80
+bed_temperature = 70
+skirts = 3
+first_layer_temperature = 190
+temperature = 177
+extrusion_multiplier = 0.9
+fill_density = 1.0
diff --git a/pin-hinge.scad b/pin-hinge.scad
new file mode 100644 (file)
index 0000000..81d527e
--- /dev/null
@@ -0,0 +1,100 @@
+// -*- C -*-
+
+include <utils.scad>
+
+$hinge_pin_dia = 0.795 + 0.75;
+$hinge_main_dia = 4.0;
+$hinge_inter_gap = 0.50;
+$hinge_prong_minwidth = 3.0;
+$hinge_noncrit_gap = 1.0;
+
+$fa = 3;
+$fs = 0.05;
+
+module HingePinPlan(){
+  circle(r= $hinge_pin_dia/2);
+}
+
+module HingeProngPlan(behind){
+  hull(){
+    circle(r= $hinge_main_dia/2);
+    polygon([[0,0],
+            [-$hinge_main_dia/2, -behind],
+            [+$hinge_main_dia/2, -behind]]);
+  }
+}
+
+module HingeGapPlan() {
+  circle(r = $hinge_main_dia/2 + $hinge_inter_gap);
+}
+
+module PlanDemo(){
+  HingeProngPlan(5);
+  %HingeGapPlan();
+  translate([0,0,1]) color("red") HingePinPlan();
+}
+
+module HingePinUnitCell(l) {
+  eff_l = l + $hinge_inter_gap;
+  pairs = floor(eff_l / (2*($hinge_prong_minwidth + $hinge_inter_gap)));
+  stride = eff_l / pairs;
+  $hinge__prong_width = stride/2 - $hinge_inter_gap;
+  for (i=[0:pairs-1]) {
+    translate(stride * i * [1,0,0])
+      children(0);
+  }
+}
+
+module HingePositive(l, behind){
+  HingePinUnitCell(l){
+    linextr_x_yz(0, $hinge__prong_width)
+      HingeProngPlan(behind);
+  }
+}
+
+module HingeNegative(l){
+  linextr_x_yz(-0.1, l+0.1)
+    HingePinPlan();
+  HingePinUnitCell(l){
+    linextr_x_yz($hinge__prong_width,
+                $hinge__prong_width*2 + 2*$hinge_inter_gap)
+      HingeGapPlan();
+  }
+}
+
+test_l = 30;
+test_wb = 12;
+test_h = 12;
+
+test_face_gap = 0.75;
+
+module Demo(){
+  difference(){
+    HingePositive(test_l, test_h/2);
+    %HingeNegative(test_l);
+  }
+}
+
+module TestBody(){
+  linextr_x_yz(0, test_l){
+    offset(delta = -test_face_gap/2)
+      polygon([[0,0],
+              [-test_wb/2, -test_h],
+              [+test_wb/2, -test_h]]);
+  }
+}
+
+module Test(){
+  difference(){
+    union(){
+      TestBody();
+      HingePositive(test_l, test_h/2);
+    }
+    HingeNegative(test_l);
+  }
+}
+
+//PlanDemo();
+//Demo();
+//TestBody();
+Test();
diff --git a/poster-tube-lid-coarse.scad b/poster-tube-lid-coarse.scad
new file mode 100644 (file)
index 0000000..2ff8f3d
--- /dev/null
@@ -0,0 +1,6 @@
+// -*- C -*-
+
+//// toplevels-from:
+include <poster-tube-lid.scad>
+
+coarse = true;
diff --git a/poster-tube-lid-parametric.scad.pl b/poster-tube-lid-parametric.scad.pl
new file mode 100755 (executable)
index 0000000..64d4d00
--- /dev/null
@@ -0,0 +1,234 @@
+#!/usr/bin/perl -w
+use strict;
+
+use Math::Vector::Real;
+use Math::Trig qw(pi);
+use POSIX;
+use Data::Dumper;
+
+sub TAU () { pi*2; }
+
+my $thick = 2.5;
+
+my $small_dia = 20;
+my $large_dia = 30;
+my $slope_angle = 45 * TAU/360;
+my $jcurverad = 5;
+my $tall = 50;
+
+my $lin_len = 2;
+my $sine_size = 5;
+my $sine_angle = 1.20 * TAU/8;
+
+my $ballend_xr = $thick/2;
+
+my $skew_slope = 0.7;
+
+my @i_sections = qw(ball0  -6
+                   sine0  -10
+                   lin0    -2
+                   circle 40
+                   lin1    2
+                   sine1  10
+                   ball2   6
+                   -
+                   );
+
+my @j_sections = qw(lin0    2
+                   -
+                   curve1 10
+                   -
+                   curveE 20
+                   -
+                   curve2 -10
+                   -
+                   );
+
+sub point ($$$$) {
+    my ($ip,$it, $jp,$jt) = @_;
+
+    my ($i_offset);
+
+    my $i_outward = V( 0,
+                      ($ip =~ m/0$/ ? -1 : +1),
+                      0 );
+
+    my $i_j_y_angle = 0;
+
+    my $i_thickscale = 1.0;
+    my $sine_len = $sine_size * sin($sine_angle);
+    my $sine_height = $sine_size * (1 - cos($sine_angle));
+
+    if ($ip =~ m/^lin[01]$/) {
+       $i_offset = V( -$lin_len * $it,
+                      0,
+                      0 );
+    } elsif ($ip =~ m/^circle$/) {
+       $i_offset = V( 0,0,0 );
+       $i_outward = V(  sin($it * TAU/2),
+                        -cos($it * TAU/2),
+                        0 );
+    } elsif ($ip =~ m/^sine[01]$/) {
+       my $angle = $it * $sine_angle;
+       $i_offset = V( -$lin_len -$sine_size * sin($angle),
+                      0,
+                      +$sine_size * (1 - cos($angle))
+                    );
+       $i_j_y_angle = $angle;
+    } elsif ($ip =~ m/^ball[02]$/) {
+       $i_j_y_angle = $sine_angle;
+       my $angle = $it * TAU/4;
+       my $dx = sin($angle) * $ballend_xr;
+       $i_offset = V( -$lin_len -$sine_len - $dx * cos($sine_angle),
+                      0,
+                      +$sine_height + $dx * sin($sine_angle)
+                    );
+       $i_thickscale = cos($angle);
+    } else {
+       die "$ip ?";
+    }
+
+    my $i_j_y_vect = V( sin($i_j_y_angle),
+                       0,
+                       cos($i_j_y_angle ));
+
+    my $j_plus_th = $jp =~ m/2$/ ? $thick : 0;
+
+    my $i_thick = $thick * $i_thickscale;
+    my $j_p_x = $small_dia/2 + $thick/2;
+    my $j_rs_x = $large_dia/2 + $thick/2;
+    my $j_dqr_x = (1-cos($slope_angle)) * $jcurverad;
+    my $j_q_x = $j_rs_x - $j_dqr_x;
+    my $j_dpq = ($j_q_x - $j_p_x) / asin($slope_angle);
+    #print STDERR "($j_q_x - $j_p_x) / asin($slope_angle); => $j_dpq\n";
+    my $j_p_y = 0;
+    my $j_q_y = $j_p_y + $j_dpq * cos($slope_angle);
+    my $j_r_y = $j_q_y + sin($slope_angle) * $jcurverad;
+    my $j_s_y = $tall;
+    my $j_qrc_x = $j_rs_x - $jcurverad;
+    my $j_qrc_y = $j_r_y;
+
+    my $j_x;
+    my $j_y;
+
+    if ($jp =~ m/^curveE$/) {
+       my $angle = ($jt + 1) * TAU/2 - $slope_angle;
+       $j_x = $j_p_x + $i_thick/2 * cos($angle);
+       $j_y = $j_p_y + $i_thick/2 * sin($angle);
+    } elsif ($jp =~ m/^curve[12]$/) {
+       my $angle = $slope_angle * $jt;
+       my $outwards = $jp =~ m/1/ ? -1 : +1;
+       $j_x = $j_qrc_x + cos($angle) * ($jcurverad + $outwards * $i_thick/2);
+       $j_y = $j_qrc_y - sin($angle) * ($jcurverad + $outwards * $i_thick/2);
+    } elsif ($jp =~ m/^lin0$/) {
+       $j_x = $j_rs_x + $i_thick * (+0.5 - $jt);
+       $j_y = $j_s_y;
+       $i_offset->[2] = 0;
+    } else {
+       die "$jp ?";
+    }
+
+    $j_y -= $j_qrc_y;
+
+    if ($j_y > 0) {
+       $i_j_y_vect = V(0,0,1);
+    }
+
+#    print STDERR "@_ $j_x $j_y $i_offset $i_outward\n";
+    return
+       $i_offset +
+       $j_x * $i_outward +
+       $i_j_y_vect * $j_y +
+       V(0,0,1) * $j_qrc_y +
+       V(0,0,-$tall) ;
+}
+
+sub get_sections_ptvals {
+    my $last_ptval;
+    my @out;
+    while (my $name = shift @_) {
+       if ($name eq '-') {
+           push @out, $last_ptval;
+       } else {
+           my $count = shift @_;
+           my $neg = sub { $_[0] };
+           if ($count < 0) {
+               $count = -$count;
+               $neg = sub { 1- $_[0] };
+           }
+           foreach (my $ix = 0; $ix < $count; $ix++) {
+               push @out, [ $name, $neg->($ix/$count) ];
+           }
+           $last_ptval = [ $name, $neg->(1.0) ];
+       }
+    }
+    return @out;
+}
+
+our @points;
+our %point_indices;
+our @triangles;
+
+my @ipts;
+my @jpts;
+
+my $qi;
+my $qj;
+
+sub triangle {
+    my @pixs;
+    foreach my $pval (@_) {
+       my $pix = $point_indices{$pval}
+           //= ((push @points, $pval), $#points);
+       if (grep { $pix eq $_ } @pixs) {
+           print "// elide @{ $ipts[$qi] } @{ $jpts[$qj] }\n";
+           return;
+       }
+       push @pixs, $pix;
+    }
+    push @triangles, [ $qi,$qj, \@pixs ];
+}
+
+sub make_sheet () {
+    @ipts = get_sections_ptvals(@i_sections);
+    @jpts = get_sections_ptvals(@j_sections);
+    my @sheet;
+    foreach my $ipt (@ipts) {
+       my @row = ();
+       foreach my $jpt (@jpts) {
+           push @row, &point(@$ipt, @$jpt);
+       }
+       push @sheet, \@row;
+    }
+    foreach ($qi=0; $qi<$#ipts; $qi++) { # i direction does not wrap
+       my $qi2 = $qi+1;
+       foreach ($qj=0; $qj<@jpts; $qj++) { # j direction does wrap
+           my $qj2 = ($qj+1) % @jpts;
+           my $p0 = $sheet[$qi][$qj];
+           triangle($p0, $sheet[$qi2][$qj], $sheet[$qi2][$qj2]);
+           triangle($p0, $sheet[$qi2][$qj2], $sheet[$qi][$qj2]);
+       }
+    }
+}
+
+sub pv ($) {
+    my $v = shift @_;
+    return "[".(join ',', @$v)."]";
+}
+
+sub write_out () {
+    print "module ImplHeadCup(){ polyhedron(points=[\n" or die $!;
+    print pv($_),",\n" or die $! foreach @points;
+    print "],faces=[\n" or die $!;
+    foreach (@triangles) {
+       print pv($_->[2]),", // @{ $ipts[$_->[0]] } @{ $jpts[$_->[1]] }\n" or die $!;
+    }
+    print "],convexity=10); }\n" or die $!;
+    print <<END or die $!;
+implheadcup_large_dia = $large_dia;
+implheadcup_thick     = $thick;
+END
+}
+
+make_sheet();
+write_out();
diff --git a/poster-tube-lid.scad b/poster-tube-lid.scad
new file mode 100644 (file)
index 0000000..000dec7
--- /dev/null
@@ -0,0 +1,1043 @@
+// -*- C -*-
+
+// Print, for each end:
+//
+//   CoverPrint
+//   StrapMount
+//   CatchAssembly
+//
+// For attaching tube to wall, with base, for storing sticks etc.
+//
+//   WallMount             goes near top
+//   WallMountForBase      goes at bottom
+//   WallMountBase         attaches to bottom, glue some neoprene to it
+//   WallMountBaseCutJig   jig for cutting neoprene
+
+include <funcs.scad>
+include <utils.scad>
+
+coarse = false;
+enable_head_cups = false;
+
+main_dia = 71.2 + 0.50 - 2.26;
+top_thick_middle = 4;
+top_thick_by_oring = 3.0;
+top_middle_dr = 11;
+
+main_cnr = 6.0;
+
+min_wall = 3;
+
+rivet_posn = 6.0 + 0.30;
+rivet_thick = 1.67;
+rivet_width = 4.15 + 1.0;
+rivet_tall = 5.51 + 1.49;
+
+over_rivet_wall = 1.0;
+side_rivet_gap = 1.5;
+inside_rivet_gap = 1.5;
+
+bayo_interf = 0.30;
+bayo_behind = 8.5;
+bayo_interf_width = 2.0;
+bayo_interf_slope = 0.5;
+
+oring_thick = 5.0;
+oring_bore = 62.0;
+
+oring_upper_embed_angle = 80;
+oring_compress = 0.1; // proportion
+oring_compress_more = 0.2;
+
+oring_rm_beside = 8;
+oring_rm_scale = 2.0;
+oring_rm_angle = 20;
+
+side_taper = 1.0;
+
+bayo_gap = 6.0;
+
+bayo_entry = 1.167;
+bayo_inramp = 0.9;
+
+bayo_slice_size = coarse ? 5 : 1;
+
+brace_hole_width = 1.0;
+brace_above_below = 1.2;
+brace_end_shorter = 0.3;
+
+jig_thick = 1.4;
+jig_hole_dia = 3.0;
+jig_rim = 5;
+jig_mark = 5;
+
+strap_loop_thick = 6;
+strap_loop_inside = 10;
+strap_loop_strlen = 10;
+strap_loop_elevation = 45;
+
+sm_inner_circum = 218 - 1.90 - 1.00 - 0.50;
+sm_main_thick = 2.0;
+sm_main_width = 20;
+
+sm_bolt_dia = 3.5 + 0.1;
+sm_bolt_shaft = 21.0;
+sm_bolt_head_dia = 6.94 + 1.0;
+sm_bolt_head_thick = 2.14;
+sm_bolt_nut_width = 5.89 + 0.25;
+sm_bolt_nut_thick = 3.68;
+sm_bolt_tighten_allow = 2.0;
+
+sm_bolt_y_clear = 0.75;
+sm_bolt_y_over = 0.5;
+
+sm_closure_cnr = 3.0;
+
+wm_thick = 5;
+wm_screw_dia = 4.5; // Timco wood screw 40mm, use brown plug
+wm_screwdriver_dia = 6.3 + 1.5;
+wm_screw_around = 5.0;
+wm_screw_slot = 3.5;
+wm_screw_head = 8.0;
+
+wmb_screw_dia = 5;
+wmb_screw_head_dia = 8.7 + 0.5;
+wmb_screw_around_x = 4;
+wmb_screw_around_z = 6;
+wmb_screw_depth_min = 10;
+web_screw_len = 16 + 0.5;
+wmb_nut_across = 7.82 + 0.35;
+wmb_nut_around_min = 2;
+wmb_nut_behind_min = 5;
+wmb_nut_th = 3.84 + 0.75;
+wmb_mount_wall = 4.5;
+wmb_mount_gap_xy = 0.1;
+wmb_mount_gap_z = 0.2;
+wmb_mount_y_width = 10;
+wmb_bottom_gap = 35; // includes allowance for padding, etc.
+wmb_bottom_th = 7;
+wmb_bottom_th_min = 1;
+wmb_ring_gap = 1.0;
+wmb_base_extra_rad = 10;
+wmb_jig_th = 1;
+wmb_jig_around_gap = 1;
+
+catch_stalk_h = 4.5;
+catch_stalk_len = 50;
+catch_tip_th = 4;
+catch_head_th = 3;
+
+catch_pin_slop = 0.25; // each side, and above
+catch_pin_slop_x_extra = 0.0; // only on one side
+catch_stalk_above_gap = 1.5;
+catch_stalk_eff_bend_rad = catch_stalk_len * 0.75;
+
+catch_strap_width = 12;
+catch_stalk_base_width = 15;
+
+catch_knob_dia = 6;
+catch_knob_above_gap = 5;
+catch_knob_height = 3.0;
+
+catch_stalk_below_gap = 1.0;
+catch_stalk_beside_gap = 2.0;
+
+// calculated
+
+TAU = PI*2;
+
+bayo_entry_x = bayo_entry;
+bayo_entry_z = bayo_entry;
+bayo_inramp_x = bayo_inramp;
+bayo_inramp_z = bayo_inramp;
+
+oring_mid_dia = oring_bore + oring_thick;
+oring_outer_dia = oring_mid_dia + oring_thick;
+
+oring_oblate = (1 - oring_compress);
+
+oring_y_rad = oring_thick/2 * oring_oblate;
+oring_x_rad = oring_thick/2 / oring_oblate;
+
+by_oring_z = oring_y_rad * (1 + cos(oring_upper_embed_angle));
+
+side_height = rivet_posn + bayo_behind + rivet_thick/2;
+side_thick = rivet_tall + over_rivet_wall;
+
+top_z = top_thick_by_oring + oring_y_rad + by_oring_z;
+
+middle_bot_z = top_z - top_thick_middle;
+
+bayo_top_z = bayo_behind + bayo_gap;
+
+bayo_nom_rad = main_dia/2 + side_thick;
+bayo_real_rad = main_dia/2 + rivet_tall;
+
+rivet_entry_width = rivet_width + side_rivet_gap;
+
+jig_mark_rad = jig_mark + main_dia/2 + jig_thick;
+
+handling_dia = oring_bore + oring_thick*2 + min_wall*2;
+handling_angle = 45;
+
+sm_inner_rad = (sm_inner_circum + sm_bolt_tighten_allow/2) / TAU;
+sm_outer_rad = sm_inner_rad + sm_main_thick;
+
+wm_main_width = sm_main_width;
+wm_y_min = sqrt( pow(sm_inner_rad, 2) -
+                pow(sm_inner_rad - (wm_thick - sm_main_thick), 2) );
+wm_y_screw = wm_y_min + wm_screw_around + wm_screw_dia/2;
+wm_y_max = wm_y_screw + wm_screw_dia/2 + wm_screw_around;
+wm_lhs_y_min = -wm_y_max;
+wm_y_slotc_screw = wm_y_screw + wm_screw_slot/2;
+wm_y_slot1_screw = wm_y_screw + wm_screw_slot;
+wm_y_slot1_max = wm_y_max + wm_screw_slot/2;
+wm_z_slot0_screw = wm_main_width + wm_screwdriver_dia/2;
+wm_z_slotc_screw = wm_z_slot0_screw + wm_screw_slot/2;
+wm_z_slot1_screw = wm_z_slot0_screw + wm_screw_slot;
+wm_z_max = wm_z_slot1_screw + wm_screw_around;
+
+wmb_mount_cut_rad = sm_outer_rad + wmb_ring_gap;
+wmb_nut_rad = wmb_nut_across / cos(30) * 0.5;
+wmb_x_screw_plus_around_r = max(
+                               wmb_screw_around_x + wmb_screw_dia/2,
+                               wmb_nut_around_min + wmb_nut_across/2
+                               );
+wmb_x_screw = -sm_outer_rad + wmb_x_screw_plus_around_r;
+wmb_x_outer = -sm_outer_rad + wmb_x_screw_plus_around_r * 2;
+function wmb_screw_thing_y_min(dia) = sqrt(
+                      pow(wmb_mount_cut_rad, 2) -
+                      pow(wmb_x_screw + dia/2, 2)
+                      );
+wmb_y_screw_end = wmb_screw_thing_y_min(wmb_screw_dia);
+wmb_y_nut_min = max(
+    wmb_screw_thing_y_min(wmb_nut_across + wmb_nut_around_min*2),
+    wm_y_slot1_max
+                   );
+wmb_y_mount_max = max(
+                     wmb_y_nut_min + wmb_nut_th + wmb_nut_behind_min,
+                     wmb_y_screw_end + wmb_screw_depth_min
+                     );
+wmb_z_screw = max(
+                 wmb_screw_around_z + wmb_screw_dia/2,
+                 wmb_nut_around_min + wmb_nut_rad
+                 );
+wmb_z_max = wmb_z_screw * 2;
+wmbb_y_max = max(
+                wmb_y_mount_max + wmb_mount_gap_xy + wmb_mount_wall,
+                wmb_y_screw_end + web_screw_len
+                );
+wmbb_x_outer = wmb_x_outer + (wmb_mount_gap_xy + wmb_mount_wall);
+wmbb_z_flat_max = -wmb_bottom_gap;
+wmbb_z_flat_whole_min = wmbb_z_flat_max - wmb_bottom_th_min;
+wmbb_z_min      = wmbb_z_flat_max - wmb_bottom_th;
+wmbb_r_top = main_dia/2 + wmb_base_extra_rad;
+wmbb_r_bottom = wmbb_r_top - (wmb_bottom_th - wmb_bottom_th_min);
+
+smc_pos = [ 0, sm_inner_rad, 0 ];
+
+smc_bolt_nut_dia = sm_bolt_nut_width / cos(30);
+smc_bolt_nut_eff_thick = sm_bolt_nut_thick + sm_bolt_tighten_allow;
+
+smc_bolt_y = sm_bolt_dia/2 + sm_bolt_y_clear;
+smc_max_y = smc_bolt_y + sm_bolt_y_over
+  + max(sm_bolt_head_dia/2, smc_bolt_nut_dia/2);
+smc_cnr_c_x = sm_bolt_shaft/2 - sm_closure_cnr
+  + sm_bolt_head_thick/2 + smc_bolt_nut_eff_thick/2;
+
+catch_cr = catch_knob_dia/2 + catch_stalk_beside_gap;
+catch_strap_thick = sm_main_thick;
+
+echo("R ", sm_inner_rad, bayo_real_rad, bayo_nom_rad);
+
+$fs= coarse ? 2.5 : 0.5;
+$fa= coarse ? 5 : 1;
+
+include <poster-tube-lid-parametric.scad>
+
+// bayonet definition
+
+bayo_a = [ bayo_entry_x, 0 ];
+bayo_p = [ 0, bayo_entry_z ];
+bayo_n = [ 0, bayo_behind-bayo_inramp_z ];
+bayo_m = [ bayo_inramp_x, bayo_behind ];
+bayo_l = bayo_m + bayo_interf * [ 1/bayo_interf_slope,  1 ];
+bayo_k = bayo_l + [ bayo_interf_width, 0 ];
+bayo_j = bayo_k + bayo_interf * [ 1/bayo_interf_slope, -1 ];
+bayo_i = bayo_j + [ rivet_width + inside_rivet_gap, 0 ];
+bayo_h = [ bayo_i[0], bayo_behind + bayo_gap + bayo_interf ];
+bayo_g = [ bayo_m[0] - rivet_width, bayo_h[1] ];
+
+bayo_e = [-bayo_p[0], bayo_p[1]] - [rivet_entry_width,0];
+bayo_d = [-bayo_a[0], bayo_a[1]] - [rivet_entry_width,0];
+bayo_c = bayo_d + [0,-5];
+bayo_b = bayo_a + [0,-5];
+
+bayo_f = [ bayo_e[0], bayo_g[1] + (bayo_e[0] - bayo_g[0]) ];
+
+bayo_polygon = [ bayo_a,
+                bayo_b,
+                bayo_c,
+                bayo_d,
+                bayo_e,
+                bayo_f,
+                bayo_g,
+                bayo_h,
+                bayo_i,
+                bayo_j,
+                bayo_k,
+                bayo_l,
+                bayo_m,
+                bayo_n,
+                bayo_p ];
+
+echo(bayo_polygon);
+
+// CATCH
+
+cppxC = 0.41 * sm_inner_rad * TAU;
+
+// catch pin
+
+cpp_adj = (bayo_n[0] - bayo_f[0]) * (1 - sm_inner_rad / bayo_nom_rad);
+// radius scaling due to nom and actual radius difference in
+// bayo entry construction
+
+cppa = bayo_f + [1,-1] * catch_pin_slop + [1,0] * cpp_adj;
+cppb = bayo_g + [1,-1] * catch_pin_slop + [1,0] * cpp_adj;
+cppd = [ bayo_n[0]
+        - catch_pin_slop - catch_pin_slop_x_extra,
+        -catch_stalk_above_gap ];
+cppi = [ cppa[0], cppd[1] ];
+cppc = [ cppd[0], cppb[1] ];
+cpph = cppd + [0,-1] * catch_stalk_h;
+cppe = cppd + [0,-1] * (catch_knob_above_gap + catch_knob_dia/2);
+cppf = [ cppa[0], cppe[1] ];
+cppg = [ cppa[0], cpph[1] ];
+cppB = 0.5 * (cppf + cppe);
+
+echo("RR", sm_inner_rad / bayo_nom_rad);
+
+// catch assembly depression below pin
+
+cppy6 = cppB[1] - (catch_knob_dia/2
+                  + (cppc[1] - cppd[1])
+                  + catch_stalk_below_gap);
+cpp7 = [ cppB[0], cppy6 + catch_cr ];
+cpp11 = cpp7 + [1,0] * catch_cr;
+cppy9 = cppy6 + catch_strap_width * 1/3;
+cpp9 = [ cpp7[0] + catch_cr * 2, cppy9 ];
+cpp8 = cpp9 + [0,-1] * catch_cr;
+cpp10 = cpp8 + [-1,0] * catch_cr;
+cppC = [ cppxC, cpp9[1] ];
+cppD = cppC + [0,-1] * catch_strap_width;
+
+// catch assembly stalk and so on
+
+catch_cr3 = catch_cr + catch_stalk_h;
+
+cppF = [ cppg[0] - catch_stalk_eff_bend_rad, cppd[1] ];
+cpp4 = [ cppg[0] - catch_stalk_len, cpph[1] ] + [1,-1] * catch_cr;
+cpp5 = [ cpp4[0], cppC[1] + catch_cr ];
+cpp2 = cpp5 + [-1,0] * (catch_cr * 2 + catch_stalk_base_width);
+cpp2r = cpp2 + [1,0] * catch_cr;
+cpp2d = cpp2 + [0,-1] * catch_cr;
+cpp3 = [ cpp2[0] + catch_cr + catch_cr3, cppd[1] - catch_cr3 ];
+cppA = [ -cppxC, cpp9[1] ];
+cppE = [ cppA[0], cppD[1] ];
+
+catch_assembly_dy = -cppy9 + catch_strap_width;
+
+
+module MainProfile(){
+  main_cnr_pos = [ side_thick, top_z ] - [1,1]*main_cnr;
+  difference(){
+    union(){
+      translate(main_cnr_pos){
+       intersection(){
+         difference(){
+           circle(r = main_cnr);
+           circle(r = main_cnr * 0.5);
+         }
+         square([10,10]);
+       }
+      }
+      polygon([[ -top_middle_dr,        middle_bot_z      ],
+              [ -top_middle_dr,        top_z             ],
+              [ main_cnr_pos[0],       top_z             ],
+              [ side_thick,            main_cnr_pos[1]   ],
+              [ side_thick,            -side_height      ],
+              [ side_taper,            -side_height      ],
+              [ 0,                     -rivet_posn       ],
+              [ 0,                     by_oring_z        ],
+              [ -oring_x_rad,          by_oring_z        ],
+              ],
+             convexity=10);
+    }
+    translate([ oring_mid_dia/2 - main_dia/2, 0 ])
+      hull(){
+      translate([ 0, oring_y_rad ])
+       scale([ 1/oring_oblate * (oring_compress_more+1) , oring_oblate ])
+       circle(oring_thick/2);
+      translate([ 0, oring_y_rad*2 - oring_thick/2 ])
+       circle(oring_thick/2);
+    }
+  }
+}
+
+module StrapLoopProfile(){
+  circle(r = strap_loop_thick/2);
+}
+
+module StrapLoop(){ ////toplevel
+  bigrad = strap_loop_inside/2 + strap_loop_thick/2;
+  extralen = strap_loop_thick * 5;
+
+  intersection(){
+    rotate([strap_loop_elevation, 0,0]){
+      for (x= [ -1, +1 ] * bigrad) {
+       translate([x, -extralen, 0])
+         rotate([-90,0,0])
+         linear_extrude(height= extralen + strap_loop_strlen + 0.1,
+                        convexity=10)
+         StrapLoopProfile();
+      }
+      translate([0, strap_loop_strlen, 0]){
+       intersection(){
+         rotate_extrude(convexity=10)
+           translate([bigrad, 0,0])
+           StrapLoopProfile();
+         translate([0,50,0])
+           cube([100,100,100], center=true);
+       }
+      }
+    }
+    translate([0, 50, 0])
+      cube(100, center=true);
+  }
+}
+
+module RotateProjectSlice(offset, slice_size, nom_rad, real_rad){
+  // nom_rad > real_rad
+  rotate([0,0, atan2(offset, nom_rad) ]){
+    intersection(){
+      translate([-offset, -10, 0])
+       rotate([90,0,0])
+       linear_extrude(height= nom_rad*2, convexity=50)
+       children(0);
+      translate([0,0, -25])
+       cylinder(h=50, r= real_rad);
+      translate([0,0, -25])
+       linear_extrude(height= 50, convexity=50)
+       polygon([ [ 0,0 ],
+                 [ -slice_size, -real_rad*2 ],
+                 [ +slice_size, -real_rad*2 ] ]);
+    }
+  }
+}
+
+module RotateProject(x_min, x_max, slice_size, nom_rad, real_rad){
+  offs = [ for (i=[ x_min :
+                   slice_size :
+                   x_max + slice_size ]) i ];
+  echo (offs);
+  for (off=offs)
+    RotateProjectSlice(off, slice_size, nom_rad, real_rad)
+    children(0);
+}
+
+module BayonetCutout(){
+  RotateProject(bayo_c[0], bayo_i[0], bayo_slice_size,
+               bayo_nom_rad, 
+               bayo_real_rad)
+    translate([-0.5 * (bayo_a[0] + bayo_d[0]), 0])
+    polygon(bayo_polygon, convexity=10);
+}
+
+module ProfilesDemo(){ ////toplevel
+  translate([-10,0]) MainProfile();
+  translate([+10, -side_height]) polygon(bayo_polygon, convexity=10);
+}
+
+module LimitForHandling(){ ////toplevel
+  hull() for (r=[0,180])
+    rotate([0,0,r]) {
+      for (rs=[-1,+1]) {
+       for (xd=[0,1]) {
+         rotate([0,0, rs * handling_angle/2]) {
+           translate([rs * xd * main_dia/2 * tan(handling_angle/2),
+                      main_dia/2 + side_thick - main_cnr,
+                      top_z - main_cnr]) {
+             mirror([0,0,1])
+               cylinder(r= main_cnr, h=50);
+             sphere(main_cnr);
+           }
+         }
+       }
+      } 
+    }
+  hull() rotate_extrude(convexity=10){
+    translate([ handling_dia/2 - main_cnr, top_z - main_cnr ]) {
+      circle(r = main_cnr);
+      mirror([0,1]) square([ main_cnr, 50 ]);
+    }
+  }
+  //cylinder(r= handling_dia/2, h=20);
+}
+
+module Cover(){ ////toplevel
+  render() difference(){
+    intersection(){
+      union(){
+       rotate_extrude(convexity=10)
+         translate([main_dia/2, 0])
+         MainProfile();
+       translate([0,0, middle_bot_z])
+         cylinder(h= top_thick_middle, r = main_dia/2 - top_middle_dr + 1);
+      }
+      LimitForHandling();
+    }
+    for (r=[0,180]){
+      rotate([0,0, r])
+       translate([0,0, -side_height])
+       BayonetCutout();
+      rotate([0,0, r + asin((-oring_rm_beside) / (main_dia/2))])
+       translate([0,
+                  oring_mid_dia/2 + oring_thick/4 * oring_rm_scale,
+                  oring_y_rad * 1.5])
+       rotate([-oring_rm_angle, 0, 0])
+       mirror([0,0,1])
+       cylinder(r = oring_thick/4 * oring_rm_scale, h=20);
+    }
+    for (r=[0 : 60 : 179]) {
+      rotate([0,0, r]) {
+       height = top_thick_middle - brace_above_below*2;
+       translate([0,0, middle_bot_z + brace_above_below + height/2 ])
+       cube(center=true, [ oring_bore - brace_end_shorter,
+                           brace_hole_width, height ]);
+      }
+    }
+  }
+  if (enable_head_cups)
+    for (r=[0,180])
+      rotate([0,0,r])
+       translate([-implheadcup_large_dia * .5 - implheadcup_thick/2,
+                  -implheadcup_large_dia * .0,
+                  middle_bot_z + 0.1])
+       ImplHeadCup();
+
+//  translate(strap_loop_thick * [-0.5, 0, +1])
+//    translate([handling_dia/2, 0, -side_height])
+//    rotate([0,180,0]) rotate([0,0,90])
+//    StrapLoop();
+}
+
+module SavingHole(){
+  translate([0,0, -10])
+    cylinder(r= main_dia/2 - jig_rim, h=20);
+}
+
+module Jig(){ ////toplevel
+  difference(){
+    union(){
+      translate([0,0, -side_height]){
+       cylinder(r= main_dia/2 + jig_thick, h= side_height + jig_thick);
+      }
+      translate([-jig_mark_rad, 0, jig_thick - jig_mark])
+       cube([jig_mark_rad*2, jig_mark, jig_mark]);
+    }
+    translate([0,0, -side_height-1])
+      cylinder(r= main_dia/2, h= side_height + 1);
+    SavingHole();
+    translate([0,0, -rivet_posn])
+      rotate([90, 0,0])
+      translate([0,0, -100])
+      cylinder(r= jig_hole_dia/2, h = 200);
+  }
+}
+
+module CoverPrint(){ ////toplevel
+  rotate([0,180,0]) Cover();
+}
+
+module CoverTest2(){ ////toplevel
+  difference(){
+    Cover();
+    SavingHole();
+  }
+}
+
+module CoverTest1(){ ////toplevel
+  difference(){
+    CoverTest2();
+    difference(){
+      for (r= [ 40, 147 ]){
+       rotate([0,0, r]){
+         translate([0,0, -10]) {
+           cube([ main_dia*3, main_dia * .53, 18], center=true);
+         }
+       }
+      }
+//      translate([ 50, 0, 0 ])
+//     cube([ 100,
+//            strap_loop_inside + strap_loop_thick*2 + 1,
+//            100 ],
+//          center=true);
+    }
+  }
+}
+
+module ImplHeadCupTest(){ ////toplevel
+  for (r=[0,180])
+    rotate([0,0,r])
+      translate([-17,0,0])
+      ImplHeadCup();
+}
+
+module SomeStrap(width, cut_width=0){
+  // children(0) is to add, (1) subtract
+  difference(){
+    union(){
+      cylinder(r=sm_outer_rad, h=width);
+      StrapMountProtrusion(smc_cnr_c_x + sm_closure_cnr,
+                          smc_max_y,
+                          sm_closure_cnr,
+                          width);
+      children(0);
+    }
+    translate([0,0,-1])
+      cylinder(r=sm_inner_rad, h=max(width+2, cut_width));
+    translate(smc_pos)
+      StrapMountBolt(5, width);
+    translate(smc_pos)
+      cube([ sm_bolt_tighten_allow, 40,100 ], center=true);
+    children(1);
+  }
+}
+
+module StrapMountBolt(l_delta, strap_width){ ///toplevel
+  // positioned relative to smc_pos
+  translate([(smc_bolt_nut_eff_thick - sm_bolt_head_thick)/2,
+            smc_bolt_y,
+            strap_width/2]){
+    translate([ -sm_bolt_shaft/2-1, 0,0 ]){
+      rotate([0,90,0]) cylinder(r= sm_bolt_dia/2, h= sm_bolt_shaft+2);
+    }
+    translate([ -sm_bolt_shaft/2, 0,0 ])
+      rotate([0,-90,0])
+      cylinder($fn=6, r=smc_bolt_nut_dia/2,
+              h=smc_bolt_nut_eff_thick + l_delta);
+    translate([ sm_bolt_shaft/2, 0,0 ])
+      rotate([0,90,0])
+      cylinder(r=sm_bolt_head_dia/2, h=sm_bolt_head_thick + l_delta);
+  }
+}
+
+module StrapMountProtrusion(half_x, max_y, cnr, width){
+  translate(smc_pos){
+    linear_extrude(height=width, convexity=10){
+      hull(){
+       for (m = [0,1]) mirror([m,0,0]) {
+         translate([-(half_x - cnr), max_y - cnr])
+           circle(r=cnr);
+         translate([-half_x, -sm_inner_rad])
+           square([1,1]);
+       }
+      }
+    }
+  }
+}
+
+module StrapMount(){ ////toplevel
+  SomeStrap(sm_main_width){
+    rotate([0,0,180]){
+      StrapMountProtrusion(strap_loop_inside/2 + strap_loop_thick,
+                          strap_loop_thick,
+                          sm_closure_cnr,
+                          sm_main_width);
+      translate(smc_pos +
+               [0,0, sm_main_width] +
+               strap_loop_thick * [ 0, 0.5, -1.0 ])
+       StrapLoop();
+    }
+    union(){ };
+  }
+}
+
+module WallScrewHoleSlot(){ ////toplevel
+  ds = [-1,+1] * wm_screw_slot/2;
+  linextr_x_yz(-(wm_thick + 1), 1) {
+    hull(){
+      for (d = ds)
+       translate([d, 0])
+         circle(r = wm_screw_dia/2);
+    }
+  }
+  hull(){
+    for (d = ds){
+      translate([0, d, 0]){
+       linextr_x_yz(0, 1)
+         circle(r = wm_screw_head/2);
+       linextr_x_yz(-(wm_screw_head - wm_screw_dia)/2, 0)
+         circle(r = wm_screw_dia/2);
+      }
+    }
+  }
+}
+
+module WallMountMounts(){
+  linextr(0, wm_z_max){
+    translate([ -sm_outer_rad, 0 ])
+      rectfromto([ 0,        wm_lhs_y_min ],
+                [ wm_thick, wm_y_slot1_max ]);
+  }
+}
+module WallMountScrewHoles(){
+  translate([ -sm_outer_rad + wm_thick, 0, wm_z_slotc_screw ]) {
+    translate([ 0, wm_y_slotc_screw, 0 ])
+      WallScrewHoleSlot();
+    translate([ 0, -wm_y_slotc_screw, 0 ])
+      rotate([90,0,0])
+      WallScrewHoleSlot();
+  }
+}
+
+module WallMount(){ ////toplevel
+  SomeStrap(sm_main_width, wm_z_max + 2){
+    WallMountMounts();
+    WallMountScrewHoles();
+  }
+}
+
+module WallMountBaseRingCut(){
+  circle(r = wmb_mount_cut_rad);
+}
+
+module WallMountBaseMounts(){
+  linextr(0, wmb_z_max) {
+    difference(){
+      rectfromto([ -sm_outer_rad, -wmb_y_mount_max ],
+                [ wmb_x_outer,   +wmb_y_mount_max ]);
+      WallMountBaseRingCut();
+    }
+  }
+}
+
+// screws, nuts, slots for nuts to go down into
+module WallMountBaseScrewsEtc(){ ////toplevel
+  for (my=[0,1]) {
+    mirror([0, my, 0]) {
+      translate([wmb_x_screw, 0, wmb_z_screw]) {
+       linextr_y_xz(wmb_y_screw_end,
+                    wmb_y_screw_end + 50)
+         circle(r = wmb_screw_dia/2);
+       linextr_y_xz(wmb_y_screw_end + web_screw_len,
+                    wmb_y_screw_end + 50)
+         circle(r = wmb_screw_head_dia/2);
+       linextr_y_xz(wmb_y_nut_min,
+                    wmb_y_nut_min + wmb_nut_th) {
+         hull(){
+           rotate(30)
+             circle(r = wmb_nut_rad, $fn = 6);
+           translate([0, 50])
+             square(wmb_nut_across, center=true);
+         }
+       }
+      }
+    }
+  }
+}
+
+module WallMountForBase(){ ////toplevel
+  SomeStrap(sm_main_width, wm_z_max + 2){
+    union(){
+      WallMountMounts();
+      WallMountBaseMounts();
+    }
+    union(){
+      WallMountScrewHoles();
+      WallMountBaseScrewsEtc();
+    }
+  }
+}
+
+module WallMountForBaseFixingsTest(){ ////toplevel
+  intersection(){
+    WallMountForBase();
+    linextr(-100,100)
+      rectfromto([ -sm_outer_rad-10, -wm_y_min ],
+                [ wmb_x_outer + 1, -100 ]);
+  }
+}
+
+module WallMountBaseFixingsTest(){ ////toplevel
+  intersection(){
+    WallMountBase();
+    linextr(-2,100)
+      rectfromto([ -sm_outer_rad-10, -wm_y_min ],
+                [ wmbb_x_outer + 1, -100 ]);
+  }
+}
+
+module WallMountBasePillarsPlan(){
+  for (my = [0,1]) {
+    mirror([0, my]) {
+      rectfromto([ -sm_outer_rad, wmbb_y_max - wmb_mount_y_width ],
+                [ wmbb_x_outer, wmbb_y_max ]);
+    }
+  }
+}
+
+// trim parts that are would foul the wall
+module WallMountTrimWallFoulPlan(){
+    translate([ -sm_outer_rad, 0])
+    rectfromto([ -wmbb_r_top, -(wmbb_r_top + 1) ],
+              [ 0,           +(wmbb_r_top + 1) ]);
+}
+
+module WallMountBase(){ ////toplevel
+  difference(){
+    union(){
+      // vertical blocks rising to join to wall mount
+      linextr(wmbb_z_min, wmb_z_max) {
+       difference(){
+         WallMountBasePillarsPlan();
+         WallMountBaseRingCut();
+       }
+      }
+
+      hull(){
+       linextr(wmbb_z_flat_whole_min, wmbb_z_flat_max)
+         circle(r = wmbb_r_top);
+       linextr(wmbb_z_min, wmbb_z_flat_max)
+         circle(r = wmbb_r_bottom);
+      }
+      linextr(wmbb_z_min, wmbb_z_flat_max) {
+       hull(){
+         WallMountBasePillarsPlan();
+         circle(r = wmbb_r_bottom);
+       }
+      }
+    }
+
+    // cutaway for mount part
+    linextr(-wmb_mount_gap_z, wmb_z_max+1) {
+      for (my = [0,1]) {
+       mirror([0, my])
+         rectfromto([ -sm_outer_rad-1, wmb_y_mount_max + wmb_mount_gap_xy ],
+                    [ wmb_x_outer + wmb_mount_gap_xy, 1 ]);
+      }
+    }
+
+    linextr(wmbb_z_min - 1, wmb_z_max + 1)
+      WallMountTrimWallFoulPlan();
+    WallMountBaseScrewsEtc();
+  }
+}
+
+module WallMountBaseCutJigPlan(){ ////toplevel
+  difference(){
+    union(){
+      circle(r = wmbb_r_top);
+    }
+
+    translate([ wmb_jig_around_gap, 0 ])
+      WallMountTrimWallFoulPlan();
+
+    offset(delta = wmb_jig_around_gap)
+      WallMountBasePillarsPlan();
+  }
+}
+
+module WallMountBaseCutJig(){ ////toplevel
+  translate([ 0,0, wmbb_z_flat_max + 0.5 ])
+    linextr(0, wmb_jig_th)
+    WallMountBaseCutJigPlan();
+}
+
+module WallMountForBaseDemo(){ ////toplevel
+  render() WallMountForBase();
+  color("blue") render() WallMountBase();
+  %WallMountBaseScrewsEtc();
+  %WallMountBaseCutJig();
+}
+
+module CatchAssemblyCoreProfile(){
+  difference(){
+    union(){
+      hull(){
+       translate(cpp3) circle(r= catch_cr3);
+       polygon([ cpp3,
+                 cpp2r,
+                 cpp5,
+                 cpph,
+                 cppd
+                 ]);
+      }
+      polygon([cppD,
+              cppC,
+              cpp9,
+              cpp10,
+              cpp11,
+              cpp4,
+              cpp2r,
+              cpp2d,
+              cppA,
+              cppE
+              ]);
+      translate(cpp8) circle(r= catch_cr);
+    }
+    hull(){
+      translate(cpp4) circle(r= catch_cr);
+      translate(cpp5) circle(r= catch_cr);
+      translate(cpp7) circle(r= catch_cr);
+      polygon([cpp4,
+              cppg,
+              cpph,
+              cpp10,
+              cpp11,
+              ]);
+    }
+    translate(cpp2) circle(r= catch_cr);
+  }
+  // if cpp11 is above cpp10, the subtracted hull above
+  // can go down too far.  Ensure we do not cut off below cppy6.
+  polygon([ cppE,
+           cppD,
+           cpp9,
+           [ cpp9[0],            cppy6 ],
+           [ cpp7[0] - catch_cr, cppy6 ],
+           cpp2d
+           ]);
+}
+
+module CatchTipProfile(dy){
+  ddy = [0,dy];
+  intersection(){
+    translate(cppF){
+      difference(){
+//     circle(r = dist2d(cppF, cppd));
+       //circle(r = dist2d(cppF, cppa));
+      }
+    }
+    polygon([ cppa,
+             cppi + ddy,
+             cppd + ddy,
+             cppc,
+             cppb ]);
+  }
+}
+
+module CatchHeadProfile(){
+  polygon([ cppd,
+           cppd,
+           cppi,
+           cppf,
+           cppe,
+           cpph ]);
+}
+
+
+module CatchCore(){ /////toplevel
+  linear_extrude(height=catch_strap_thick, convexity=10)
+    CatchAssemblyCoreProfile();
+
+  hull(){
+    linear_extrude(height=catch_head_th, convexity=10)
+      CatchTipProfile(0);
+    linear_extrude(height=catch_tip_th, convexity=10)
+      CatchTipProfile(catch_tip_th - catch_head_th);
+  }
+
+  linear_extrude(height=catch_head_th, convexity=10)
+    CatchHeadProfile();
+
+  translate(concat(cppB,[0])) hull(){
+    translate([0,0, catch_knob_height + catch_head_th - catch_knob_dia/2])
+      sphere(r = catch_knob_dia/2);
+    cylinder(r = catch_knob_dia/2, h = 0.1);
+  }
+}
+
+module CatchPreDistort(){ /////toplevel
+  scale(100 / sm_inner_rad)
+    rotate([90,0,0])
+    CatchCore();
+}
+
+module CatchAssembly(){ /////toplevel
+  rotate([0,0, -(cppe[0] + cppB[0] + catch_pin_slop) / sm_inner_rad * 360/TAU])
+    translate([0,0, catch_assembly_dy])
+    scale(sm_inner_rad / 100)
+    import(str("poster-tube-lid,CatchPostDistort-fa",
+              (coarse ? 20 : 3),
+              ".stl"),
+          convexity=20);
+
+  SomeStrap(catch_strap_width){
+    union(){ }
+    union(){
+      translate([-200, -200, -200])
+       cube([400, 200, 400]);
+    }
+  }
+}
+
+module CatchDemo(){ /////toplevel
+  color("blue") translate([0,0,
+                          -catch_assembly_dy
+            ])
+    CatchAssembly();
+  translate([0,0,+side_height
+            ])
+    Cover();
+}
+
+module CatchDemoS(){ /////toplevel
+  color("blue") translate([0,0,
+            -catch_assembly_dy
+            ])
+    CatchAssembly();
+  intersection(){
+    translate([0,0,+side_height
+              ])
+      Cover();
+    mirror([0,1,0]) translate([-250,33,0]) cube([500,500,500]);
+  }
+  color("black")
+    translate([0,-33,0])
+    cube([6.15, 2,2], center=true);
+}
+
+module CatchPinProfileDemo(){ /////toplevel
+  translate([0, 0 * -bayo_behind,0]) {
+    echo("G ",
+        bayo_n[0] - bayo_e[0]);
+    color("blue") translate([0,0,
+                            +1,
+              ]) {
+      CatchAssemblyCoreProfile();
+      CatchHeadProfile();
+    }
+    translate([0,0,10])
+      color("red")
+      CatchTipProfile(0);
+
+    polygon(bayo_polygon, convexity=10);
+
+    // adhoc show a position
+    color("purple")
+    translate(concat(
+                    cppa,
+                    10
+                    )) difference(){ circle(2.5); circle(2.0); }
+
+ }
+}
+
+//ProfilesDemo();
+//BayonetCutout();
+//MainProfile();
+//Cover();
+//Jig();
+//CoverTest();
diff --git a/powerbank-anker-10000.eps b/powerbank-anker-10000.eps
new file mode 100644 (file)
index 0000000..01359d8
--- /dev/null
@@ -0,0 +1,89 @@
+%!PS-Adobe-3.0 EPSF-3.0
+%%Creator: cairo 1.16.0 (https://cairographics.org)
+%%CreationDate: Fri Feb  5 23:12:37 2021
+%%Pages: 1
+%%DocumentData: Clean7Bit
+%%LanguageLevel: 2
+%%BoundingBox: 149 274 598 1452
+%%EndComments
+%%BeginProlog
+50 dict begin
+/q { gsave } bind def
+/Q { grestore } bind def
+/cm { 6 array astore concat } bind def
+/w { setlinewidth } bind def
+/J { setlinecap } bind def
+/j { setlinejoin } bind def
+/M { setmiterlimit } bind def
+/d { setdash } bind def
+/m { moveto } bind def
+/l { lineto } bind def
+/c { curveto } bind def
+/h { closepath } bind def
+/re { exch dup neg 3 1 roll 5 3 roll moveto 0 rlineto
+      0 exch rlineto 0 rlineto closepath } bind def
+/S { stroke } bind def
+/f { fill } bind def
+/f* { eofill } bind def
+/n { newpath } bind def
+/W { clip } bind def
+/W* { eoclip } bind def
+/BT { } bind def
+/ET { } bind def
+/BDC { mark 3 1 roll /BDC pdfmark } bind def
+/EMC { mark /EMC pdfmark } bind def
+/cairo_store_point { /cairo_point_y exch def /cairo_point_x exch def } def
+/Tj { show currentpoint cairo_store_point } bind def
+/TJ {
+  {
+    dup
+    type /stringtype eq
+    { show } { -0.001 mul 0 cairo_font_matrix dtransform rmoveto } ifelse
+  } forall
+  currentpoint cairo_store_point
+} bind def
+/cairo_selectfont { cairo_font_matrix aload pop pop pop 0 0 6 array astore
+    cairo_font exch selectfont cairo_point_x cairo_point_y moveto } bind def
+/Tf { pop /cairo_font exch def /cairo_font_matrix where
+      { pop cairo_selectfont } if } bind def
+/Td { matrix translate cairo_font_matrix matrix concatmatrix dup
+      /cairo_font_matrix exch def dup 4 get exch 5 get cairo_store_point
+      /cairo_font where { pop cairo_selectfont } if } bind def
+/Tm { 2 copy 8 2 roll 6 array astore /cairo_font_matrix exch def
+      cairo_store_point /cairo_font where { pop cairo_selectfont } if } bind def
+/g { setgray } bind def
+/rg { setrgbcolor } bind def
+/d1 { setcachedevice } bind def
+/cairo_data_source {
+  CairoDataIndex CairoData length lt
+    { CairoData CairoDataIndex get /CairoDataIndex CairoDataIndex 1 add def }
+    { () } ifelse
+} def
+/cairo_flush_ascii85_file { cairo_ascii85_file status { cairo_ascii85_file flushfile } if } def
+/cairo_image { image cairo_flush_ascii85_file } def
+/cairo_imagemask { imagemask cairo_flush_ascii85_file } def
+%%EndProlog
+%%BeginSetup
+%%EndSetup
+%%Page: 1 1
+%%BeginPageSetup
+%%PageBoundingBox: 149 274 598 1452
+%%EndPageSetup
+q 149 274 449 1178 rectclip
+1 0 0 -1 0 1612 cm q
+0 g
+360.93 1337.57 m 298.676 1334.211 219.23 1335.672 183.594 1274.547 c 142.047
+ 1187.812 152.582 1088.758 149.211 995.395 c 151.73 795.32 144.426 595.004
+ 156.238 395.148 c 157.656 328.41 155.359 251.004 205.434 199.816 c 263.293
+ 157.641 339.281 161.27 407.469 160.113 c 465.984 160.328 537.961 171.273
+ 565.277 230.957 c 601.477 310.977 590.16 401.633 593.281 486.977 c 592.355
+ 612.16 596.633 737.312 597.473 862.445 c 596.184 971.23 597.672 1080.117
+ 595.984 1188.836 c 591.852 1244.773 555.328 1300.199 498.418 1313.117 c
+ 459.289 1331.371 416.695 1338.148 373.711 1336.93 c 367.316 1337.203 l 
+h
+360.93 1337.57 m f
+Q Q
+showpage
+%%Trailer
+end
+%%EOF
diff --git a/powerbank-anker-10000.svg b/powerbank-anker-10000.svg
new file mode 100644 (file)
index 0000000..5999a71
--- /dev/null
@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   version="1.1"
+   id="svg2"
+   width="1048"
+   height="2149.3333"
+   viewBox="0 0 1048 2149.3333"
+   sodipodi:docname="anker-powerbank.svg"
+   inkscape:version="0.92.4 (5da689c313, 2019-01-14)">
+  <metadata
+     id="metadata8">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <defs
+     id="defs6" />
+  <sodipodi:namedview
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1"
+     objecttolerance="10"
+     gridtolerance="10"
+     guidetolerance="10"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:window-width="2961"
+     inkscape:window-height="2094"
+     id="namedview4"
+     showgrid="false"
+     inkscape:snap-page="false"
+     inkscape:zoom="1.7568239"
+     inkscape:cx="497.30928"
+     inkscape:cy="570.53214"
+     inkscape:window-x="842"
+     inkscape:window-y="0"
+     inkscape:window-maximized="0"
+     inkscape:current-layer="svg2" />
+  <path
+     style="fill:#000000;stroke-width:1.33333337"
+     d="m 481.23878,1783.4274 c -83.00255,-4.4776 -188.92949,-2.5338 -236.44742,-84.0321 -55.39794,-115.6427 -41.34772,-247.7191 -45.84221,-372.2039 3.35787,-266.7652 -6.38184,-533.85214 9.36873,-800.32809 1.88831,-88.98392 -1.17396,-192.18932 65.59317,-260.44389 77.14644,-56.22961 178.46251,-51.39211 269.38093,-52.93653 78.02295,0.28556 173.99094,14.87991 210.40981,94.45887 48.26475,106.69537 33.17812,227.56859 37.33958,341.35772 -1.23437,166.91394 4.46685,333.78584 5.58917,500.62752 -1.71663,145.0466 0.26483,290.2296 -1.98544,435.1878 -5.51041,74.5828 -54.21013,148.4825 -130.0869,165.7076 -52.1723,24.3388 -108.96694,33.3778 -166.27445,31.7502 l -8.52964,0.3672 z"
+     id="path838"
+     inkscape:connector-curvature="0" />
+</svg>
diff --git a/powerbank-bike-clamp.scad b/powerbank-bike-clamp.scad
new file mode 100644 (file)
index 0000000..2a755a8
--- /dev/null
@@ -0,0 +1,431 @@
+// -*- C -*-
+
+// Print one of each:
+//    Main
+//    TubeClampRight
+
+include <utils.scad>
+
+tube_dia = 22.4;
+
+hinge_around = 2.5;
+hinge_pin = 1.8 + 0.75;
+main_th = 3;
+minor_wall_min = 1;
+
+screw = 5.0 + 0.75;
+screw_head = 8.7 + 1.2;
+screw_head_space_above = 10;
+screw_nut_across = 7.9 + 0.75;
+screw_nut_th = 3.9 + 0.75;
+
+knob_behind_across = 12.2 + 0.75;
+behind_knob_th = 5;
+knob_standout_h = 2;
+
+clamp_min_width = 20;
+
+clamp_gap = 2;
+
+corner_rounding_r = 10;
+
+lower_th = 1;
+
+overlap_l = 0.1;
+
+bridge_slop = 1.2;
+
+hinge_lobe_per = 10;
+hinge_gap_z = 0.75;
+hinge_gap_xy = 0.75;
+
+$fs = 0.1;
+$fa = 5;
+
+bank_eps_bbox_x = [149, 598];
+bank_eps_bbox_y = [274, 1452];
+
+bank_y_sz = 102.25 + 0.50 + 3.2;
+bank_x_sz = (26.0 + 0.5);
+bank_recess_y = 5;
+
+strap_th = 3;
+strap_w = 5;
+strap_above = 2.5;
+
+strap_around_over = 1.0;
+strap_around_attach = 2.0;
+
+retainer_walls = [18, 30];
+
+bank_profile_scale_bodge = 1.0;
+
+bank_output_ctr = [ 12.5, 11.5 ]; // from nearest corner
+bank_output_sz  = [ 11.0, 10.5 ];
+
+mounted_pos_y_offset_lim = -100;
+
+liner_th = 0.8;
+
+brace_r = 5;
+brace_len = 50;
+
+straps_y_adj = [ 3.5,
+                0,
+                0 ];
+
+// calculated
+
+straps_y = [ -bank_y_sz * 0.25, // these entries are special and used
+            +bank_y_sz * 0.25, //  for the brace struts
+            0 ];
+
+screw_head_behind = main_th;
+endwall_th = main_th;
+
+bank_recess_dx = minor_wall_min;
+
+pspt_to_mm = 25.4 / 72;
+
+main_r = tube_dia/2 + main_th;
+
+screw_max_y_lhs = -main_r -screw_nut_across/2;
+screw_max_y_rhs = -main_r -knob_behind_across/2;
+
+screw_y = min(screw_max_y_lhs,
+             screw_max_y_rhs);
+
+bot_y = screw_y -max( screw_nut_across/2, knob_behind_across/2 )
+  -minor_wall_min;
+
+holder_x_sz = bank_x_sz + bank_recess_dx*2;
+bank_bot_y = strap_above + strap_th;
+strap_r = strap_th;
+
+brace_total_len = brace_len + main_th;
+brace_ctrs_y_nom = [ straps_y[0] - (brace_r + strap_w/2),
+                    straps_y[1] + (brace_r + strap_w/2) ];
+
+brace_ctrs_y = [ (straps_y + straps_y_adj)[0] + (brace_r + strap_w/2),
+                (straps_y + straps_y_adj)[1] + (brace_r + strap_w/2) ];
+
+clamp_width_actual = max(clamp_min_width, holder_x_sz);
+
+hinge_lobes = floor(clamp_width_actual / hinge_lobe_per);
+hinge_stride = (clamp_width_actual + hinge_gap_z) / hinge_lobes;
+
+hinge_outer_r = hinge_around + hinge_pin/2;
+hinge_y = tube_dia/2 + hinge_outer_r;
+
+top_cnr_r = min(endwall_th, main_th);
+
+mounted_pos_y_offset = max(mounted_pos_y_offset_lim,
+                          bot_y - (-(bank_y_sz/2 + endwall_th)));
+
+
+module TubePlan(){ circle(r = tube_dia/2); }
+module HingePinPlan(){ translate([0, hinge_y]) circle(r= hinge_pin/2); }
+module HingeBodyPlan(){ translate([0, hinge_y]) circle(r= hinge_outer_r); }
+
+module TubeClampLeftPlan(){
+  difference(){
+    union(){
+      polygon([[ 0,                      hinge_y + hinge_outer_r ],
+              [ -(main_r + overlap_l),  hinge_y + hinge_outer_r ],
+              [ -(main_r + overlap_l),  bot_y                   ],
+              [ -clamp_gap/2,           bot_y                   ],
+              [ -clamp_gap/2,           0,                      ],
+              [ 0,                      0,                      ],
+              ]);
+      HingeBodyPlan();
+    }
+    TubePlan();
+    HingePinPlan();
+  }
+}
+
+module TubeClampLeft() { ////toplevel
+  linextr(-clamp_width_actual/2, clamp_width_actual/2)
+    TubeClampLeftPlan();
+}
+
+module TubeClampRightPlan(){ ////toplevel
+  difference(){
+    // It broke at the inside corner, round these a bit
+    offset(r=-corner_rounding_r)
+    offset(r=+corner_rounding_r)
+    difference(){
+      union(){
+       rectfromto([ clamp_gap/2,                   bot_y ],
+                  [ clamp_gap/2 + behind_knob_th,  0     ]);
+       intersection(){
+         circle(r= main_r); // maybe split off from main_r and increase?
+         union(){
+           rectfromto([0,0],
+                      main_r *  [5,5]);
+           rectfromto([ clamp_gap/2, main_r*5 ],
+                      main_r * [2,-5]);
+         }
+       }
+       HingeBodyPlan();
+      }
+      TubePlan();
+    }
+    HingePinPlan();
+  }
+}
+
+module Screws(){
+  linextr_x_yz(-main_r*5, main_r*5)
+    translate([screw_y, 0])
+    circle(r= screw/2);
+
+  translate([0, screw_y, 0]) {
+    linextr_x_yz(-(clamp_gap/2 + screw_nut_th), 0)
+      square([screw_nut_across,
+             screw_nut_across / cos(30) + bridge_slop*2],
+            center=true);
+
+    linextr_x_yz(-(main_r + bank_recess_y + screw_head_space_above),
+                -(clamp_gap/2 + screw_nut_th + screw_head_behind))
+      square([screw_head, screw_head + bridge_slop*2],
+            center=true);
+  }
+}
+
+module SomeClamp(hinge_alt=false){
+  difference(){
+    linextr(-clamp_width_actual/2, clamp_width_actual/2)
+      children(0);
+
+    Screws();
+
+    for (i=[0 : hinge_lobes-1]) {
+      translate([0,
+                hinge_y,
+                -clamp_width_actual/2 + i * hinge_stride
+                + (hinge_alt ? hinge_stride/2 : 0)
+                ])
+       linextr(-hinge_gap_z, hinge_stride/2)
+       square(hinge_outer_r*2 + hinge_gap_xy, center=true);
+    }
+  }
+}
+
+module PowerBankItselfSidePlan(){
+  translate([0, bank_bot_y]){
+    minkowski(){
+      circle($fn=8, r=liner_th);
+      scale( bank_profile_scale_bodge *
+            bank_x_sz / ( (
+                           bank_eps_bbox_x[1] -
+                           bank_eps_bbox_x[0]
+                           ) * pspt_to_mm ))
+       translate(pspt_to_mm *
+                 [-0.5 * (bank_eps_bbox_x[0] +
+                          bank_eps_bbox_x[1]),
+                  -bank_eps_bbox_y[0]])
+       import("powerbank-anker-10000.dxf", convexity=5);
+    }
+  }
+}
+
+module PowerBankItself(){ ////toplevel
+  rotate([0,90,0])
+    linextr_y_xz(-bank_y_sz/2,
+                +bank_y_sz/2)
+    PowerBankItselfSidePlan();
+}
+
+module PowerBankSidePlan(){ ////toplevel
+  render() difference(){
+    rectfromto([ -holder_x_sz/2,  0 ],
+              [ +holder_x_sz/2,  bank_recess_y + bank_bot_y ]);
+
+    PowerBankItselfSidePlan();
+  }
+}
+
+module PowerBankStrapCut(){ ////toplevel
+  difference(){
+    rectfromto([ -holder_x_sz, -0.05 ],
+              [ +holder_x_sz, strap_th + strap_r ]);
+    hull(){
+      for (sx=[-1,+1]) {
+       translate([sx * (holder_x_sz/2 - strap_r + 0.1),
+                  strap_th + strap_r])
+         circle(strap_r);
+      }
+    }
+  }
+}
+
+module PowerBankHolderTest(){ ////toplevel
+  difference(){
+    linextr(-1,5) PowerBankSidePlan();
+    linextr(0, strap_w) PowerBankStrapCut();
+  }
+}
+
+module EndRetainer(depth){ ////toplevel
+  translate([0, -bank_y_sz/2, 0]) {
+    linextr_y_xz(-endwall_th, 0)
+    rectfromto([ 0,         -holder_x_sz/2 ],
+              [ -depth,    +holder_x_sz/2 ]);
+    
+    for (m=[0,1]) {
+      mirror([0,0,m]) {
+       linextr(-holder_x_sz/2, -bank_x_sz/2){
+         hull(){
+           rectfromto([ 0, -endwall_th ],
+                      [ depth, 0 ]);
+           rectfromto([ 0, 0 ],
+                      [ 0.1, depth-0.1 ]);
+         }
+       }
+      }
+    }
+  }
+}
+
+module BraceTubePlan(){
+  intersection(){
+    circle(r= brace_r);
+    rectfromto(brace_r * [-2,  0],
+              brace_r * [+2, +2]);
+  }
+}
+
+module PowerBankHolder(){ ////toplevel
+  difference(){
+    union(){
+      rotate([0,90,0])
+       linextr_y_xz(-(bank_y_sz/2 + 0.1),
+                    +(bank_y_sz/2 + 0.1))
+       PowerBankSidePlan();
+
+      EndRetainer(retainer_walls[0]);
+      mirror([0,1,0]) EndRetainer(retainer_walls[1]);
+
+      translate([0,0, bank_x_sz/2]){
+       for (y = brace_ctrs_y) {
+         translate([0,y,0]) {
+           linextr_x_yz(0, brace_total_len)
+             BraceTubePlan();
+         }
+       }
+       translate([brace_total_len, 0,0])
+         linextr_y_xz(brace_ctrs_y_nom[0] - brace_r,
+                      brace_ctrs_y_nom[1] + brace_r)
+         BraceTubePlan();
+      }
+
+      for (strap_y = straps_y + straps_y_adj) {
+       translate([0, strap_y, 0]) {
+         linextr(-holder_x_sz/2,
+                 +holder_x_sz/2){
+           hull(){
+             for (dy = [-1,+1] *
+                    (strap_w/2 + strap_around_attach - strap_around_over)) {
+               translate([0, dy, 0])
+                 circle(r=strap_around_over);
+             }
+           }
+         }
+       }
+      }
+    }
+
+    for (strap_y = straps_y + straps_y_adj)
+      translate([0, strap_y, 0])
+       rotate([0,0,-90])
+       rotate([0,90,0])
+       linextr(-strap_w/2,
+               +strap_w/2)
+       PowerBankStrapCut();
+
+    translate([ bank_bot_y, -bank_y_sz/2, -bank_x_sz/2 ])
+      linextr_y_xz(-50,50)
+       rotate([0,0,90])
+       translate(bank_output_ctr)
+       square(center=true, bank_output_sz);
+
+    translate([0, -(bank_y_sz/2 + endwall_th), 0] + 0.01 * [-1,-1]) {
+      linextr(-200,200){
+       difference(){
+         square(center=true, top_cnr_r*2);
+         translate(top_cnr_r * [1,1])
+           circle(r= top_cnr_r);
+       }
+      }
+    }
+  }
+}
+
+module TubeClampLeft() { ////toplevel
+  // We want this to print with the recess overhand to the right
+  // where the workpiece cooling fan is
+  rotate([0,0,180]){
+    difference(){
+      SomeClamp(true)
+       TubeClampLeftPlan();
+
+      Screws();
+    }
+  }
+}
+
+module PlacePowerBank(){
+  translate([main_r, -mounted_pos_y_offset, 0])
+    children(0);
+}
+
+module Main(){ ////toplevel
+  TubeClampLeft();
+  difference(){
+    PlacePowerBank()
+      PowerBankHolder();
+    rotate([0,0,180])
+      Screws();
+  }
+}
+
+module TubeClampRight() { ////toplevel
+  rotate([0,0,180]) {
+    rotate([180,0,0]) {
+      difference(){
+       union(){
+         SomeClamp()
+           TubeClampRightPlan();
+
+         translate([clamp_gap/2 + behind_knob_th, screw_y, 0]) {
+           hull(){
+             linextr_x_yz(-0.1, 0)
+               square(center=true,
+                      [knob_behind_across,
+                       knob_behind_across + knob_standout_h*2]);
+             linextr_x_yz(0, knob_standout_h)
+               square(center=true,
+                      knob_behind_across);
+           }
+         }
+       }
+       Screws();
+      }
+    }
+  }
+}
+
+module TubeClampDemo() { ////toplevel
+  TubeClampLeft();
+  rotate([180,0,0])
+    TubeClampRight();
+}
+
+module Demo() { ////toplevel
+  Main();
+  rotate([180,0,0])
+    TubeClampRight();
+  PlacePowerBank()
+    %PowerBankItself();
+}
diff --git a/pull-cord-keeper.scad b/pull-cord-keeper.scad
new file mode 100644 (file)
index 0000000..c8655c9
--- /dev/null
@@ -0,0 +1,189 @@
+// -*- C -*-
+
+hoopthick = 3;
+
+hinnerrad = 15;
+houterrad = hinnerrad + hoopthick;
+hcentredist = 10;
+
+blockdepth = 5;
+blockwidth = hcentredist*2 + 6;
+
+height = 20;
+
+roundedgedia = 7.5;
+
+ziglen = hcentredist/2;
+
+feedxgap = 5;
+feedzgap = 5;
+ribsgap = 1;
+
+ribdepth = 3;
+ribheight = 4;
+
+backxgap = 1;
+
+blockoverlapcnr = 5;
+
+screwholedia = 4 + 0.5;
+
+module Oval(centredist, rad) {
+  hull() {
+    translate([-centredist/2,0,0]) circle(r=rad);
+    translate([+centredist/2,0,0]) circle(r=rad);
+  }
+}  
+
+module VExtrude(){
+  translate([0,0, -height/2])
+    linear_extrude(height=20)
+    children(0);
+}
+
+module OuterOval(){
+  Oval(hcentredist, houterrad);
+}
+
+module Hoop(){
+  difference(){
+    hull(){
+      OuterOval();
+      translate([0, (blockdepth + hoopthick)/2 + hinnerrad])
+       square([blockwidth,
+               blockdepth + hoopthick],
+              center=true);
+    }
+    Oval(hcentredist, hinnerrad);
+  }
+}
+
+module RoundEdges(){
+  intersection(){
+    VExtrude()
+      OuterOval();
+
+    for (xi=[-1,1]) {
+      hull(){
+       for (yi=[-1,1]) {
+         translate([xi * (hcentredist/2 + hinnerrad),
+                    houterrad,
+                    yi * (height/2 - roundedgedia / 4 * sqrt(2))])
+           rotate([90,0,0])
+           cylinder(r=roundedgedia/2, h=houterrad*2, $fn=20);
+       }
+      }
+    }
+  }
+}
+
+module Positive(){
+  difference(){
+    VExtrude()
+      Hoop();
+
+    rotate([90,0,0])
+      translate([0,0,-50])
+      cylinder(r=screwholedia/2, h=100);
+  }
+
+  RoundEdges();
+}
+
+module Ribs(){
+  imax = ceil(height*2 / ribheight);
+  for (i=[-imax:imax]) {
+    hull(){
+      translate([-ribdepth/2,
+                ribheight*i,
+                0])
+       polygon([[0,          0],
+                [ribdepth, -ribheight],
+                [ribdepth, +ribheight]]);
+      translate([50, 0])
+       square([1, height*2], center=true);
+    }
+  }
+}          
+
+module Division(cutmore) {
+  mirror([0,0,1]) {
+    translate([0, 0, -cutmore*feedzgap/2]) {
+      translate([-ziglen + -cutmore*feedxgap/2, -100, 0])
+       cube([100, 100, 50]);
+    }
+  }
+  translate([blockwidth/2 - blockoverlapcnr + -cutmore*backxgap/2,
+            -1,
+            -50])
+    cube([100, 100, 100]);
+
+  translate([ziglen + -cutmore*feedxgap/2,
+            -50,
+            -50])
+    cube([100, 51, 100]);
+
+  translate([50,
+            hinnerrad/2 + houterrad/2 + blockdepth/2 + -cutmore*ribsgap/2,
+            0])
+    rotate([-90,0,90])
+    linear_extrude(height=100)
+    Ribs();
+}
+
+module SDemo(){
+  //difference(){
+  %  Positive();
+  //  Division(0);
+  //}
+  Division(-1);
+}
+
+module A(){
+  difference(){
+    Positive();
+    Division(+1);
+  }
+}
+
+module B(){
+  intersection(){
+    Positive();
+    Division(-1);
+  }
+}
+
+module Demo(){
+  color("red") A();
+  color("blue") B();
+}
+
+module APrint(){ ////toplevel
+  rotate([0,180,0])
+    A();
+}
+
+module BPrint(){ ////toplevel
+  B();
+}
+
+module Kit(){ ////toplevel
+  translate([0, hinnerrad, 0])
+    APrint();
+  rotate([0,0,180])
+    BPrint();
+}
+
+//Ribs();
+//Demo();
+
+//A();
+//B();
+//%Division(+1);
+
+//Hoop();
+
+//Demo();
+//BPrint();
+
+//Kit();
diff --git a/quacks-ingredients-L1.scad b/quacks-ingredients-L1.scad
new file mode 100644 (file)
index 0000000..5a85e58
--- /dev/null
@@ -0,0 +1,5 @@
+//  autogenerated by quacks-ingredients-updates-levels - do not edit
+$phase=1;
+module Token_L(){ Token_L1(); }
+//// toplevels-from:
+include <quacks-ingredients.scad>
diff --git a/quacks-ingredients-L2.scad b/quacks-ingredients-L2.scad
new file mode 100644 (file)
index 0000000..6c7a087
--- /dev/null
@@ -0,0 +1,5 @@
+//  autogenerated by quacks-ingredients-updates-levels - do not edit
+$phase=2;
+module Token_L(){ Token_L2(); }
+//// toplevels-from:
+include <quacks-ingredients.scad>
diff --git a/quacks-ingredients-L3.scad b/quacks-ingredients-L3.scad
new file mode 100644 (file)
index 0000000..b39277d
--- /dev/null
@@ -0,0 +1,5 @@
+//  autogenerated by quacks-ingredients-updates-levels - do not edit
+$phase=3;
+module Token_L(){ Token_L3(); }
+//// toplevels-from:
+include <quacks-ingredients.scad>
diff --git a/quacks-ingredients-L4.scad b/quacks-ingredients-L4.scad
new file mode 100644 (file)
index 0000000..9f86e2a
--- /dev/null
@@ -0,0 +1,5 @@
+//  autogenerated by quacks-ingredients-updates-levels - do not edit
+$phase=4;
+module Token_L(){ Token_L4(); }
+//// toplevels-from:
+include <quacks-ingredients.scad>
diff --git a/quacks-ingredients-L5.scad b/quacks-ingredients-L5.scad
new file mode 100644 (file)
index 0000000..2f35fd3
--- /dev/null
@@ -0,0 +1,5 @@
+//  autogenerated by quacks-ingredients-updates-levels - do not edit
+$phase=5;
+module Token_L(){ Token_L5(); }
+//// toplevels-from:
+include <quacks-ingredients.scad>
diff --git a/quacks-ingredients-counts b/quacks-ingredients-counts
new file mode 100755 (executable)
index 0000000..5377cbc
--- /dev/null
@@ -0,0 +1,111 @@
+#!/usr/bin/perl -w
+
+use strict;
+
+use Data::Dumper;
+use POSIX;
+
+our $which = shift @ARGV;
+
+sub xdata ($) {
+    my ($cb) = @_;
+    return unless $which eq 'Base';
+    foreach my $count (qw(1 2 3)) {
+       foreach my $nspots (qw(0 1 2 3 4)) {
+           $_ = $cb->($count,$nspots)."\t".$_;
+       }
+    }
+}
+
+$_=<DATA>; chomp or die;
+xdata sub {
+    my ($xcount,$xnspots) = @_;
+    "${xcount}x". (qw(Zero One Two Three Four)[$xnspots]);
+};
+our @names = split /\t/, $_;
+
+our %count;
+
+foreach my $nspots (qw(1 2 3 4 0)) {
+    $_=<DATA>; chomp or die;
+    xdata sub {
+       my ($xcount,$xnspots) = @_;
+       $xnspots == $nspots and "$xcount+0";
+    };
+    my @l = split /\t/, $_;
+    foreach my $i (0..$#names) {
+       $_ = $l[$i] || '0+0';
+       $_ ||= 0;
+       m/\+/ or die "$which $nspots ?";
+       
+       $count{$names[$i]}{$nspots} =
+           $which eq 'All'     ? $` + $' :
+           $which eq 'Base'    ? $`      :
+           $which eq 'Witches' ?      $' :
+           die "$which ?";
+    }
+}
+
+$_ = Dumper(\%count);
+s{^}{// }mg;
+#print STDERR;
+
+our $name;
+our $total_count;
+our $total_real_count;
+our $max_nrows=0;
+our $max_rowsz=0;
+
+sub wrtoplevel () {
+    my $cs = $count{$name};
+    my $total = 0; $total += $_ foreach values %$cs;
+    return unless $total;
+    print "module ${which}_$name(){ ////toplevel\n";
+    my $rowsz = ceil(sqrt($total));
+    my $nrows = ceil($total / $rowsz);
+    $total_count += $total;
+    $total_real_count += $total if $name =~ m/^[A-Z][a-z]+$/;
+    $max_nrows = $nrows if $nrows > $max_nrows;
+    $max_rowsz = $rowsz if $rowsz > $max_rowsz;
+    my $ix = 0;
+    printf "// %s  %-10s  total=%2d  rowsz=$rowsz  nrows=$nrows\n",
+       $which, "$name", $total;
+    foreach my $nspots (sort keys %$cs) {
+       my $c = $cs->{$nspots};
+       print <<END;
+  union(){
+    Frame(\$phase, token_pitch * [ $rowsz + 1.00, $nrows + 0.50 ]);
+    \$nspots = $nspots;
+END
+       while ($c--) {
+           my $xy = sprintf "[ %5.1f, %5.1f ]",
+               int($ix / $nrows) - 0.5 * ($rowsz-1),
+               $ix % $nrows - 0.5 * ($nrows-1);
+           print "    translate(token_pitch * $xy) Token_L();\n";
+           $ix++;
+       }
+       print <<END;
+  };
+END
+    }
+    print "}\n";
+}
+
+foreach $name (sort keys %count) {
+    wrtoplevel();
+}
+
+print <<END;
+// $which  total_count=$total_count   total_real_count=$total_real_count
+// $which  max_rowsz=$max_rowsz       max_nrows=$max_nrows
+END
+
+STDOUT->error and die $!;
+
+__DATA__
+White  Green   Blue    Red     Yellow  Purple  Black   Orange  Orange6 Loco    WhiteSpare
+21+6   15+10   14+8    12+6    13+6    15+8    18+8    20+12                   1+0
+9+3    10+5    10+5    8+5     6+5                                             1+0
+5+2                                                                            1+0
+       13+5    10+5    10+5    10+5
+                                                               0+20    0+25
diff --git a/quacks-ingredients-counts.scad b/quacks-ingredients-counts.scad
new file mode 100644 (file)
index 0000000..c2a8d08
--- /dev/null
@@ -0,0 +1,1826 @@
+//  autogenerated - do not edit
+//   update script is quacks-ingredients-updates-levels
+//   source is quacks-ingredients-counts
+module Base_1xFour(){ ////toplevel
+// Base  1xFour      total= 1  rowsz=1  nrows=1
+  union(){
+    Frame($phase, token_pitch * [ 1 + 1.00, 1 + 0.50 ]);
+    $nspots = 0;
+  };
+  union(){
+    Frame($phase, token_pitch * [ 1 + 1.00, 1 + 0.50 ]);
+    $nspots = 1;
+  };
+  union(){
+    Frame($phase, token_pitch * [ 1 + 1.00, 1 + 0.50 ]);
+    $nspots = 2;
+  };
+  union(){
+    Frame($phase, token_pitch * [ 1 + 1.00, 1 + 0.50 ]);
+    $nspots = 3;
+  };
+  union(){
+    Frame($phase, token_pitch * [ 1 + 1.00, 1 + 0.50 ]);
+    $nspots = 4;
+    translate(token_pitch * [   0.0,   0.0 ]) Token_L();
+  };
+}
+module Base_1xOne(){ ////toplevel
+// Base  1xOne       total= 1  rowsz=1  nrows=1
+  union(){
+    Frame($phase, token_pitch * [ 1 + 1.00, 1 + 0.50 ]);
+    $nspots = 0;
+  };
+  union(){
+    Frame($phase, token_pitch * [ 1 + 1.00, 1 + 0.50 ]);
+    $nspots = 1;
+    translate(token_pitch * [   0.0,   0.0 ]) Token_L();
+  };
+  union(){
+    Frame($phase, token_pitch * [ 1 + 1.00, 1 + 0.50 ]);
+    $nspots = 2;
+  };
+  union(){
+    Frame($phase, token_pitch * [ 1 + 1.00, 1 + 0.50 ]);
+    $nspots = 3;
+  };
+  union(){
+    Frame($phase, token_pitch * [ 1 + 1.00, 1 + 0.50 ]);
+    $nspots = 4;
+  };
+}
+module Base_1xThree(){ ////toplevel
+// Base  1xThree     total= 1  rowsz=1  nrows=1
+  union(){
+    Frame($phase, token_pitch * [ 1 + 1.00, 1 + 0.50 ]);
+    $nspots = 0;
+  };
+  union(){
+    Frame($phase, token_pitch * [ 1 + 1.00, 1 + 0.50 ]);
+    $nspots = 1;
+  };
+  union(){
+    Frame($phase, token_pitch * [ 1 + 1.00, 1 + 0.50 ]);
+    $nspots = 2;
+  };
+  union(){
+    Frame($phase, token_pitch * [ 1 + 1.00, 1 + 0.50 ]);
+    $nspots = 3;
+    translate(token_pitch * [   0.0,   0.0 ]) Token_L();
+  };
+  union(){
+    Frame($phase, token_pitch * [ 1 + 1.00, 1 + 0.50 ]);
+    $nspots = 4;
+  };
+}
+module Base_1xTwo(){ ////toplevel
+// Base  1xTwo       total= 1  rowsz=1  nrows=1
+  union(){
+    Frame($phase, token_pitch * [ 1 + 1.00, 1 + 0.50 ]);
+    $nspots = 0;
+  };
+  union(){
+    Frame($phase, token_pitch * [ 1 + 1.00, 1 + 0.50 ]);
+    $nspots = 1;
+  };
+  union(){
+    Frame($phase, token_pitch * [ 1 + 1.00, 1 + 0.50 ]);
+    $nspots = 2;
+    translate(token_pitch * [   0.0,   0.0 ]) Token_L();
+  };
+  union(){
+    Frame($phase, token_pitch * [ 1 + 1.00, 1 + 0.50 ]);
+    $nspots = 3;
+  };
+  union(){
+    Frame($phase, token_pitch * [ 1 + 1.00, 1 + 0.50 ]);
+    $nspots = 4;
+  };
+}
+module Base_1xZero(){ ////toplevel
+// Base  1xZero      total= 1  rowsz=1  nrows=1
+  union(){
+    Frame($phase, token_pitch * [ 1 + 1.00, 1 + 0.50 ]);
+    $nspots = 0;
+    translate(token_pitch * [   0.0,   0.0 ]) Token_L();
+  };
+  union(){
+    Frame($phase, token_pitch * [ 1 + 1.00, 1 + 0.50 ]);
+    $nspots = 1;
+  };
+  union(){
+    Frame($phase, token_pitch * [ 1 + 1.00, 1 + 0.50 ]);
+    $nspots = 2;
+  };
+  union(){
+    Frame($phase, token_pitch * [ 1 + 1.00, 1 + 0.50 ]);
+    $nspots = 3;
+  };
+  union(){
+    Frame($phase, token_pitch * [ 1 + 1.00, 1 + 0.50 ]);
+    $nspots = 4;
+  };
+}
+module Base_2xFour(){ ////toplevel
+// Base  2xFour      total= 2  rowsz=2  nrows=1
+  union(){
+    Frame($phase, token_pitch * [ 2 + 1.00, 1 + 0.50 ]);
+    $nspots = 0;
+  };
+  union(){
+    Frame($phase, token_pitch * [ 2 + 1.00, 1 + 0.50 ]);
+    $nspots = 1;
+  };
+  union(){
+    Frame($phase, token_pitch * [ 2 + 1.00, 1 + 0.50 ]);
+    $nspots = 2;
+  };
+  union(){
+    Frame($phase, token_pitch * [ 2 + 1.00, 1 + 0.50 ]);
+    $nspots = 3;
+  };
+  union(){
+    Frame($phase, token_pitch * [ 2 + 1.00, 1 + 0.50 ]);
+    $nspots = 4;
+    translate(token_pitch * [  -0.5,   0.0 ]) Token_L();
+    translate(token_pitch * [   0.5,   0.0 ]) Token_L();
+  };
+}
+module Base_2xOne(){ ////toplevel
+// Base  2xOne       total= 2  rowsz=2  nrows=1
+  union(){
+    Frame($phase, token_pitch * [ 2 + 1.00, 1 + 0.50 ]);
+    $nspots = 0;
+  };
+  union(){
+    Frame($phase, token_pitch * [ 2 + 1.00, 1 + 0.50 ]);
+    $nspots = 1;
+    translate(token_pitch * [  -0.5,   0.0 ]) Token_L();
+    translate(token_pitch * [   0.5,   0.0 ]) Token_L();
+  };
+  union(){
+    Frame($phase, token_pitch * [ 2 + 1.00, 1 + 0.50 ]);
+    $nspots = 2;
+  };
+  union(){
+    Frame($phase, token_pitch * [ 2 + 1.00, 1 + 0.50 ]);
+    $nspots = 3;
+  };
+  union(){
+    Frame($phase, token_pitch * [ 2 + 1.00, 1 + 0.50 ]);
+    $nspots = 4;
+  };
+}
+module Base_2xThree(){ ////toplevel
+// Base  2xThree     total= 2  rowsz=2  nrows=1
+  union(){
+    Frame($phase, token_pitch * [ 2 + 1.00, 1 + 0.50 ]);
+    $nspots = 0;
+  };
+  union(){
+    Frame($phase, token_pitch * [ 2 + 1.00, 1 + 0.50 ]);
+    $nspots = 1;
+  };
+  union(){
+    Frame($phase, token_pitch * [ 2 + 1.00, 1 + 0.50 ]);
+    $nspots = 2;
+  };
+  union(){
+    Frame($phase, token_pitch * [ 2 + 1.00, 1 + 0.50 ]);
+    $nspots = 3;
+    translate(token_pitch * [  -0.5,   0.0 ]) Token_L();
+    translate(token_pitch * [   0.5,   0.0 ]) Token_L();
+  };
+  union(){
+    Frame($phase, token_pitch * [ 2 + 1.00, 1 + 0.50 ]);
+    $nspots = 4;
+  };
+}
+module Base_2xTwo(){ ////toplevel
+// Base  2xTwo       total= 2  rowsz=2  nrows=1
+  union(){
+    Frame($phase, token_pitch * [ 2 + 1.00, 1 + 0.50 ]);
+    $nspots = 0;
+  };
+  union(){
+    Frame($phase, token_pitch * [ 2 + 1.00, 1 + 0.50 ]);
+    $nspots = 1;
+  };
+  union(){
+    Frame($phase, token_pitch * [ 2 + 1.00, 1 + 0.50 ]);
+    $nspots = 2;
+    translate(token_pitch * [  -0.5,   0.0 ]) Token_L();
+    translate(token_pitch * [   0.5,   0.0 ]) Token_L();
+  };
+  union(){
+    Frame($phase, token_pitch * [ 2 + 1.00, 1 + 0.50 ]);
+    $nspots = 3;
+  };
+  union(){
+    Frame($phase, token_pitch * [ 2 + 1.00, 1 + 0.50 ]);
+    $nspots = 4;
+  };
+}
+module Base_2xZero(){ ////toplevel
+// Base  2xZero      total= 2  rowsz=2  nrows=1
+  union(){
+    Frame($phase, token_pitch * [ 2 + 1.00, 1 + 0.50 ]);
+    $nspots = 0;
+    translate(token_pitch * [  -0.5,   0.0 ]) Token_L();
+    translate(token_pitch * [   0.5,   0.0 ]) Token_L();
+  };
+  union(){
+    Frame($phase, token_pitch * [ 2 + 1.00, 1 + 0.50 ]);
+    $nspots = 1;
+  };
+  union(){
+    Frame($phase, token_pitch * [ 2 + 1.00, 1 + 0.50 ]);
+    $nspots = 2;
+  };
+  union(){
+    Frame($phase, token_pitch * [ 2 + 1.00, 1 + 0.50 ]);
+    $nspots = 3;
+  };
+  union(){
+    Frame($phase, token_pitch * [ 2 + 1.00, 1 + 0.50 ]);
+    $nspots = 4;
+  };
+}
+module Base_3xFour(){ ////toplevel
+// Base  3xFour      total= 3  rowsz=2  nrows=2
+  union(){
+    Frame($phase, token_pitch * [ 2 + 1.00, 2 + 0.50 ]);
+    $nspots = 0;
+  };
+  union(){
+    Frame($phase, token_pitch * [ 2 + 1.00, 2 + 0.50 ]);
+    $nspots = 1;
+  };
+  union(){
+    Frame($phase, token_pitch * [ 2 + 1.00, 2 + 0.50 ]);
+    $nspots = 2;
+  };
+  union(){
+    Frame($phase, token_pitch * [ 2 + 1.00, 2 + 0.50 ]);
+    $nspots = 3;
+  };
+  union(){
+    Frame($phase, token_pitch * [ 2 + 1.00, 2 + 0.50 ]);
+    $nspots = 4;
+    translate(token_pitch * [  -0.5,  -0.5 ]) Token_L();
+    translate(token_pitch * [  -0.5,   0.5 ]) Token_L();
+    translate(token_pitch * [   0.5,  -0.5 ]) Token_L();
+  };
+}
+module Base_3xOne(){ ////toplevel
+// Base  3xOne       total= 3  rowsz=2  nrows=2
+  union(){
+    Frame($phase, token_pitch * [ 2 + 1.00, 2 + 0.50 ]);
+    $nspots = 0;
+  };
+  union(){
+    Frame($phase, token_pitch * [ 2 + 1.00, 2 + 0.50 ]);
+    $nspots = 1;
+    translate(token_pitch * [  -0.5,  -0.5 ]) Token_L();
+    translate(token_pitch * [  -0.5,   0.5 ]) Token_L();
+    translate(token_pitch * [   0.5,  -0.5 ]) Token_L();
+  };
+  union(){
+    Frame($phase, token_pitch * [ 2 + 1.00, 2 + 0.50 ]);
+    $nspots = 2;
+  };
+  union(){
+    Frame($phase, token_pitch * [ 2 + 1.00, 2 + 0.50 ]);
+    $nspots = 3;
+  };
+  union(){
+    Frame($phase, token_pitch * [ 2 + 1.00, 2 + 0.50 ]);
+    $nspots = 4;
+  };
+}
+module Base_3xThree(){ ////toplevel
+// Base  3xThree     total= 3  rowsz=2  nrows=2
+  union(){
+    Frame($phase, token_pitch * [ 2 + 1.00, 2 + 0.50 ]);
+    $nspots = 0;
+  };
+  union(){
+    Frame($phase, token_pitch * [ 2 + 1.00, 2 + 0.50 ]);
+    $nspots = 1;
+  };
+  union(){
+    Frame($phase, token_pitch * [ 2 + 1.00, 2 + 0.50 ]);
+    $nspots = 2;
+  };
+  union(){
+    Frame($phase, token_pitch * [ 2 + 1.00, 2 + 0.50 ]);
+    $nspots = 3;
+    translate(token_pitch * [  -0.5,  -0.5 ]) Token_L();
+    translate(token_pitch * [  -0.5,   0.5 ]) Token_L();
+    translate(token_pitch * [   0.5,  -0.5 ]) Token_L();
+  };
+  union(){
+    Frame($phase, token_pitch * [ 2 + 1.00, 2 + 0.50 ]);
+    $nspots = 4;
+  };
+}
+module Base_3xTwo(){ ////toplevel
+// Base  3xTwo       total= 3  rowsz=2  nrows=2
+  union(){
+    Frame($phase, token_pitch * [ 2 + 1.00, 2 + 0.50 ]);
+    $nspots = 0;
+  };
+  union(){
+    Frame($phase, token_pitch * [ 2 + 1.00, 2 + 0.50 ]);
+    $nspots = 1;
+  };
+  union(){
+    Frame($phase, token_pitch * [ 2 + 1.00, 2 + 0.50 ]);
+    $nspots = 2;
+    translate(token_pitch * [  -0.5,  -0.5 ]) Token_L();
+    translate(token_pitch * [  -0.5,   0.5 ]) Token_L();
+    translate(token_pitch * [   0.5,  -0.5 ]) Token_L();
+  };
+  union(){
+    Frame($phase, token_pitch * [ 2 + 1.00, 2 + 0.50 ]);
+    $nspots = 3;
+  };
+  union(){
+    Frame($phase, token_pitch * [ 2 + 1.00, 2 + 0.50 ]);
+    $nspots = 4;
+  };
+}
+module Base_3xZero(){ ////toplevel
+// Base  3xZero      total= 3  rowsz=2  nrows=2
+  union(){
+    Frame($phase, token_pitch * [ 2 + 1.00, 2 + 0.50 ]);
+    $nspots = 0;
+    translate(token_pitch * [  -0.5,  -0.5 ]) Token_L();
+    translate(token_pitch * [  -0.5,   0.5 ]) Token_L();
+    translate(token_pitch * [   0.5,  -0.5 ]) Token_L();
+  };
+  union(){
+    Frame($phase, token_pitch * [ 2 + 1.00, 2 + 0.50 ]);
+    $nspots = 1;
+  };
+  union(){
+    Frame($phase, token_pitch * [ 2 + 1.00, 2 + 0.50 ]);
+    $nspots = 2;
+  };
+  union(){
+    Frame($phase, token_pitch * [ 2 + 1.00, 2 + 0.50 ]);
+    $nspots = 3;
+  };
+  union(){
+    Frame($phase, token_pitch * [ 2 + 1.00, 2 + 0.50 ]);
+    $nspots = 4;
+  };
+}
+module Base_Black(){ ////toplevel
+// Base  Black       total=18  rowsz=5  nrows=4
+  union(){
+    Frame($phase, token_pitch * [ 5 + 1.00, 4 + 0.50 ]);
+    $nspots = 0;
+  };
+  union(){
+    Frame($phase, token_pitch * [ 5 + 1.00, 4 + 0.50 ]);
+    $nspots = 1;
+    translate(token_pitch * [  -2.0,  -1.5 ]) Token_L();
+    translate(token_pitch * [  -2.0,  -0.5 ]) Token_L();
+    translate(token_pitch * [  -2.0,   0.5 ]) Token_L();
+    translate(token_pitch * [  -2.0,   1.5 ]) Token_L();
+    translate(token_pitch * [  -1.0,  -1.5 ]) Token_L();
+    translate(token_pitch * [  -1.0,  -0.5 ]) Token_L();
+    translate(token_pitch * [  -1.0,   0.5 ]) Token_L();
+    translate(token_pitch * [  -1.0,   1.5 ]) Token_L();
+    translate(token_pitch * [   0.0,  -1.5 ]) Token_L();
+    translate(token_pitch * [   0.0,  -0.5 ]) Token_L();
+    translate(token_pitch * [   0.0,   0.5 ]) Token_L();
+    translate(token_pitch * [   0.0,   1.5 ]) Token_L();
+    translate(token_pitch * [   1.0,  -1.5 ]) Token_L();
+    translate(token_pitch * [   1.0,  -0.5 ]) Token_L();
+    translate(token_pitch * [   1.0,   0.5 ]) Token_L();
+    translate(token_pitch * [   1.0,   1.5 ]) Token_L();
+    translate(token_pitch * [   2.0,  -1.5 ]) Token_L();
+    translate(token_pitch * [   2.0,  -0.5 ]) Token_L();
+  };
+  union(){
+    Frame($phase, token_pitch * [ 5 + 1.00, 4 + 0.50 ]);
+    $nspots = 2;
+  };
+  union(){
+    Frame($phase, token_pitch * [ 5 + 1.00, 4 + 0.50 ]);
+    $nspots = 3;
+  };
+  union(){
+    Frame($phase, token_pitch * [ 5 + 1.00, 4 + 0.50 ]);
+    $nspots = 4;
+  };
+}
+module Base_Blue(){ ////toplevel
+// Base  Blue        total=34  rowsz=6  nrows=6
+  union(){
+    Frame($phase, token_pitch * [ 6 + 1.00, 6 + 0.50 ]);
+    $nspots = 0;
+  };
+  union(){
+    Frame($phase, token_pitch * [ 6 + 1.00, 6 + 0.50 ]);
+    $nspots = 1;
+    translate(token_pitch * [  -2.5,  -2.5 ]) Token_L();
+    translate(token_pitch * [  -2.5,  -1.5 ]) Token_L();
+    translate(token_pitch * [  -2.5,  -0.5 ]) Token_L();
+    translate(token_pitch * [  -2.5,   0.5 ]) Token_L();
+    translate(token_pitch * [  -2.5,   1.5 ]) Token_L();
+    translate(token_pitch * [  -2.5,   2.5 ]) Token_L();
+    translate(token_pitch * [  -1.5,  -2.5 ]) Token_L();
+    translate(token_pitch * [  -1.5,  -1.5 ]) Token_L();
+    translate(token_pitch * [  -1.5,  -0.5 ]) Token_L();
+    translate(token_pitch * [  -1.5,   0.5 ]) Token_L();
+    translate(token_pitch * [  -1.5,   1.5 ]) Token_L();
+    translate(token_pitch * [  -1.5,   2.5 ]) Token_L();
+    translate(token_pitch * [  -0.5,  -2.5 ]) Token_L();
+    translate(token_pitch * [  -0.5,  -1.5 ]) Token_L();
+  };
+  union(){
+    Frame($phase, token_pitch * [ 6 + 1.00, 6 + 0.50 ]);
+    $nspots = 2;
+    translate(token_pitch * [  -0.5,  -0.5 ]) Token_L();
+    translate(token_pitch * [  -0.5,   0.5 ]) Token_L();
+    translate(token_pitch * [  -0.5,   1.5 ]) Token_L();
+    translate(token_pitch * [  -0.5,   2.5 ]) Token_L();
+    translate(token_pitch * [   0.5,  -2.5 ]) Token_L();
+    translate(token_pitch * [   0.5,  -1.5 ]) Token_L();
+    translate(token_pitch * [   0.5,  -0.5 ]) Token_L();
+    translate(token_pitch * [   0.5,   0.5 ]) Token_L();
+    translate(token_pitch * [   0.5,   1.5 ]) Token_L();
+    translate(token_pitch * [   0.5,   2.5 ]) Token_L();
+  };
+  union(){
+    Frame($phase, token_pitch * [ 6 + 1.00, 6 + 0.50 ]);
+    $nspots = 3;
+  };
+  union(){
+    Frame($phase, token_pitch * [ 6 + 1.00, 6 + 0.50 ]);
+    $nspots = 4;
+    translate(token_pitch * [   1.5,  -2.5 ]) Token_L();
+    translate(token_pitch * [   1.5,  -1.5 ]) Token_L();
+    translate(token_pitch * [   1.5,  -0.5 ]) Token_L();
+    translate(token_pitch * [   1.5,   0.5 ]) Token_L();
+    translate(token_pitch * [   1.5,   1.5 ]) Token_L();
+    translate(token_pitch * [   1.5,   2.5 ]) Token_L();
+    translate(token_pitch * [   2.5,  -2.5 ]) Token_L();
+    translate(token_pitch * [   2.5,  -1.5 ]) Token_L();
+    translate(token_pitch * [   2.5,  -0.5 ]) Token_L();
+    translate(token_pitch * [   2.5,   0.5 ]) Token_L();
+  };
+}
+module Base_Green(){ ////toplevel
+// Base  Green       total=38  rowsz=7  nrows=6
+  union(){
+    Frame($phase, token_pitch * [ 7 + 1.00, 6 + 0.50 ]);
+    $nspots = 0;
+  };
+  union(){
+    Frame($phase, token_pitch * [ 7 + 1.00, 6 + 0.50 ]);
+    $nspots = 1;
+    translate(token_pitch * [  -3.0,  -2.5 ]) Token_L();
+    translate(token_pitch * [  -3.0,  -1.5 ]) Token_L();
+    translate(token_pitch * [  -3.0,  -0.5 ]) Token_L();
+    translate(token_pitch * [  -3.0,   0.5 ]) Token_L();
+    translate(token_pitch * [  -3.0,   1.5 ]) Token_L();
+    translate(token_pitch * [  -3.0,   2.5 ]) Token_L();
+    translate(token_pitch * [  -2.0,  -2.5 ]) Token_L();
+    translate(token_pitch * [  -2.0,  -1.5 ]) Token_L();
+    translate(token_pitch * [  -2.0,  -0.5 ]) Token_L();
+    translate(token_pitch * [  -2.0,   0.5 ]) Token_L();
+    translate(token_pitch * [  -2.0,   1.5 ]) Token_L();
+    translate(token_pitch * [  -2.0,   2.5 ]) Token_L();
+    translate(token_pitch * [  -1.0,  -2.5 ]) Token_L();
+    translate(token_pitch * [  -1.0,  -1.5 ]) Token_L();
+    translate(token_pitch * [  -1.0,  -0.5 ]) Token_L();
+  };
+  union(){
+    Frame($phase, token_pitch * [ 7 + 1.00, 6 + 0.50 ]);
+    $nspots = 2;
+    translate(token_pitch * [  -1.0,   0.5 ]) Token_L();
+    translate(token_pitch * [  -1.0,   1.5 ]) Token_L();
+    translate(token_pitch * [  -1.0,   2.5 ]) Token_L();
+    translate(token_pitch * [   0.0,  -2.5 ]) Token_L();
+    translate(token_pitch * [   0.0,  -1.5 ]) Token_L();
+    translate(token_pitch * [   0.0,  -0.5 ]) Token_L();
+    translate(token_pitch * [   0.0,   0.5 ]) Token_L();
+    translate(token_pitch * [   0.0,   1.5 ]) Token_L();
+    translate(token_pitch * [   0.0,   2.5 ]) Token_L();
+    translate(token_pitch * [   1.0,  -2.5 ]) Token_L();
+  };
+  union(){
+    Frame($phase, token_pitch * [ 7 + 1.00, 6 + 0.50 ]);
+    $nspots = 3;
+  };
+  union(){
+    Frame($phase, token_pitch * [ 7 + 1.00, 6 + 0.50 ]);
+    $nspots = 4;
+    translate(token_pitch * [   1.0,  -1.5 ]) Token_L();
+    translate(token_pitch * [   1.0,  -0.5 ]) Token_L();
+    translate(token_pitch * [   1.0,   0.5 ]) Token_L();
+    translate(token_pitch * [   1.0,   1.5 ]) Token_L();
+    translate(token_pitch * [   1.0,   2.5 ]) Token_L();
+    translate(token_pitch * [   2.0,  -2.5 ]) Token_L();
+    translate(token_pitch * [   2.0,  -1.5 ]) Token_L();
+    translate(token_pitch * [   2.0,  -0.5 ]) Token_L();
+    translate(token_pitch * [   2.0,   0.5 ]) Token_L();
+    translate(token_pitch * [   2.0,   1.5 ]) Token_L();
+    translate(token_pitch * [   2.0,   2.5 ]) Token_L();
+    translate(token_pitch * [   3.0,  -2.5 ]) Token_L();
+    translate(token_pitch * [   3.0,  -1.5 ]) Token_L();
+  };
+}
+module Base_Orange(){ ////toplevel
+// Base  Orange      total=20  rowsz=5  nrows=4
+  union(){
+    Frame($phase, token_pitch * [ 5 + 1.00, 4 + 0.50 ]);
+    $nspots = 0;
+  };
+  union(){
+    Frame($phase, token_pitch * [ 5 + 1.00, 4 + 0.50 ]);
+    $nspots = 1;
+    translate(token_pitch * [  -2.0,  -1.5 ]) Token_L();
+    translate(token_pitch * [  -2.0,  -0.5 ]) Token_L();
+    translate(token_pitch * [  -2.0,   0.5 ]) Token_L();
+    translate(token_pitch * [  -2.0,   1.5 ]) Token_L();
+    translate(token_pitch * [  -1.0,  -1.5 ]) Token_L();
+    translate(token_pitch * [  -1.0,  -0.5 ]) Token_L();
+    translate(token_pitch * [  -1.0,   0.5 ]) Token_L();
+    translate(token_pitch * [  -1.0,   1.5 ]) Token_L();
+    translate(token_pitch * [   0.0,  -1.5 ]) Token_L();
+    translate(token_pitch * [   0.0,  -0.5 ]) Token_L();
+    translate(token_pitch * [   0.0,   0.5 ]) Token_L();
+    translate(token_pitch * [   0.0,   1.5 ]) Token_L();
+    translate(token_pitch * [   1.0,  -1.5 ]) Token_L();
+    translate(token_pitch * [   1.0,  -0.5 ]) Token_L();
+    translate(token_pitch * [   1.0,   0.5 ]) Token_L();
+    translate(token_pitch * [   1.0,   1.5 ]) Token_L();
+    translate(token_pitch * [   2.0,  -1.5 ]) Token_L();
+    translate(token_pitch * [   2.0,  -0.5 ]) Token_L();
+    translate(token_pitch * [   2.0,   0.5 ]) Token_L();
+    translate(token_pitch * [   2.0,   1.5 ]) Token_L();
+  };
+  union(){
+    Frame($phase, token_pitch * [ 5 + 1.00, 4 + 0.50 ]);
+    $nspots = 2;
+  };
+  union(){
+    Frame($phase, token_pitch * [ 5 + 1.00, 4 + 0.50 ]);
+    $nspots = 3;
+  };
+  union(){
+    Frame($phase, token_pitch * [ 5 + 1.00, 4 + 0.50 ]);
+    $nspots = 4;
+  };
+}
+module Base_Purple(){ ////toplevel
+// Base  Purple      total=15  rowsz=4  nrows=4
+  union(){
+    Frame($phase, token_pitch * [ 4 + 1.00, 4 + 0.50 ]);
+    $nspots = 0;
+  };
+  union(){
+    Frame($phase, token_pitch * [ 4 + 1.00, 4 + 0.50 ]);
+    $nspots = 1;
+    translate(token_pitch * [  -1.5,  -1.5 ]) Token_L();
+    translate(token_pitch * [  -1.5,  -0.5 ]) Token_L();
+    translate(token_pitch * [  -1.5,   0.5 ]) Token_L();
+    translate(token_pitch * [  -1.5,   1.5 ]) Token_L();
+    translate(token_pitch * [  -0.5,  -1.5 ]) Token_L();
+    translate(token_pitch * [  -0.5,  -0.5 ]) Token_L();
+    translate(token_pitch * [  -0.5,   0.5 ]) Token_L();
+    translate(token_pitch * [  -0.5,   1.5 ]) Token_L();
+    translate(token_pitch * [   0.5,  -1.5 ]) Token_L();
+    translate(token_pitch * [   0.5,  -0.5 ]) Token_L();
+    translate(token_pitch * [   0.5,   0.5 ]) Token_L();
+    translate(token_pitch * [   0.5,   1.5 ]) Token_L();
+    translate(token_pitch * [   1.5,  -1.5 ]) Token_L();
+    translate(token_pitch * [   1.5,  -0.5 ]) Token_L();
+    translate(token_pitch * [   1.5,   0.5 ]) Token_L();
+  };
+  union(){
+    Frame($phase, token_pitch * [ 4 + 1.00, 4 + 0.50 ]);
+    $nspots = 2;
+  };
+  union(){
+    Frame($phase, token_pitch * [ 4 + 1.00, 4 + 0.50 ]);
+    $nspots = 3;
+  };
+  union(){
+    Frame($phase, token_pitch * [ 4 + 1.00, 4 + 0.50 ]);
+    $nspots = 4;
+  };
+}
+module Base_Red(){ ////toplevel
+// Base  Red         total=30  rowsz=6  nrows=5
+  union(){
+    Frame($phase, token_pitch * [ 6 + 1.00, 5 + 0.50 ]);
+    $nspots = 0;
+  };
+  union(){
+    Frame($phase, token_pitch * [ 6 + 1.00, 5 + 0.50 ]);
+    $nspots = 1;
+    translate(token_pitch * [  -2.5,  -2.0 ]) Token_L();
+    translate(token_pitch * [  -2.5,  -1.0 ]) Token_L();
+    translate(token_pitch * [  -2.5,   0.0 ]) Token_L();
+    translate(token_pitch * [  -2.5,   1.0 ]) Token_L();
+    translate(token_pitch * [  -2.5,   2.0 ]) Token_L();
+    translate(token_pitch * [  -1.5,  -2.0 ]) Token_L();
+    translate(token_pitch * [  -1.5,  -1.0 ]) Token_L();
+    translate(token_pitch * [  -1.5,   0.0 ]) Token_L();
+    translate(token_pitch * [  -1.5,   1.0 ]) Token_L();
+    translate(token_pitch * [  -1.5,   2.0 ]) Token_L();
+    translate(token_pitch * [  -0.5,  -2.0 ]) Token_L();
+    translate(token_pitch * [  -0.5,  -1.0 ]) Token_L();
+  };
+  union(){
+    Frame($phase, token_pitch * [ 6 + 1.00, 5 + 0.50 ]);
+    $nspots = 2;
+    translate(token_pitch * [  -0.5,   0.0 ]) Token_L();
+    translate(token_pitch * [  -0.5,   1.0 ]) Token_L();
+    translate(token_pitch * [  -0.5,   2.0 ]) Token_L();
+    translate(token_pitch * [   0.5,  -2.0 ]) Token_L();
+    translate(token_pitch * [   0.5,  -1.0 ]) Token_L();
+    translate(token_pitch * [   0.5,   0.0 ]) Token_L();
+    translate(token_pitch * [   0.5,   1.0 ]) Token_L();
+    translate(token_pitch * [   0.5,   2.0 ]) Token_L();
+  };
+  union(){
+    Frame($phase, token_pitch * [ 6 + 1.00, 5 + 0.50 ]);
+    $nspots = 3;
+  };
+  union(){
+    Frame($phase, token_pitch * [ 6 + 1.00, 5 + 0.50 ]);
+    $nspots = 4;
+    translate(token_pitch * [   1.5,  -2.0 ]) Token_L();
+    translate(token_pitch * [   1.5,  -1.0 ]) Token_L();
+    translate(token_pitch * [   1.5,   0.0 ]) Token_L();
+    translate(token_pitch * [   1.5,   1.0 ]) Token_L();
+    translate(token_pitch * [   1.5,   2.0 ]) Token_L();
+    translate(token_pitch * [   2.5,  -2.0 ]) Token_L();
+    translate(token_pitch * [   2.5,  -1.0 ]) Token_L();
+    translate(token_pitch * [   2.5,   0.0 ]) Token_L();
+    translate(token_pitch * [   2.5,   1.0 ]) Token_L();
+    translate(token_pitch * [   2.5,   2.0 ]) Token_L();
+  };
+}
+module Base_White(){ ////toplevel
+// Base  White       total=35  rowsz=6  nrows=6
+  union(){
+    Frame($phase, token_pitch * [ 6 + 1.00, 6 + 0.50 ]);
+    $nspots = 0;
+  };
+  union(){
+    Frame($phase, token_pitch * [ 6 + 1.00, 6 + 0.50 ]);
+    $nspots = 1;
+    translate(token_pitch * [  -2.5,  -2.5 ]) Token_L();
+    translate(token_pitch * [  -2.5,  -1.5 ]) Token_L();
+    translate(token_pitch * [  -2.5,  -0.5 ]) Token_L();
+    translate(token_pitch * [  -2.5,   0.5 ]) Token_L();
+    translate(token_pitch * [  -2.5,   1.5 ]) Token_L();
+    translate(token_pitch * [  -2.5,   2.5 ]) Token_L();
+    translate(token_pitch * [  -1.5,  -2.5 ]) Token_L();
+    translate(token_pitch * [  -1.5,  -1.5 ]) Token_L();
+    translate(token_pitch * [  -1.5,  -0.5 ]) Token_L();
+    translate(token_pitch * [  -1.5,   0.5 ]) Token_L();
+    translate(token_pitch * [  -1.5,   1.5 ]) Token_L();
+    translate(token_pitch * [  -1.5,   2.5 ]) Token_L();
+    translate(token_pitch * [  -0.5,  -2.5 ]) Token_L();
+    translate(token_pitch * [  -0.5,  -1.5 ]) Token_L();
+    translate(token_pitch * [  -0.5,  -0.5 ]) Token_L();
+    translate(token_pitch * [  -0.5,   0.5 ]) Token_L();
+    translate(token_pitch * [  -0.5,   1.5 ]) Token_L();
+    translate(token_pitch * [  -0.5,   2.5 ]) Token_L();
+    translate(token_pitch * [   0.5,  -2.5 ]) Token_L();
+    translate(token_pitch * [   0.5,  -1.5 ]) Token_L();
+    translate(token_pitch * [   0.5,  -0.5 ]) Token_L();
+  };
+  union(){
+    Frame($phase, token_pitch * [ 6 + 1.00, 6 + 0.50 ]);
+    $nspots = 2;
+    translate(token_pitch * [   0.5,   0.5 ]) Token_L();
+    translate(token_pitch * [   0.5,   1.5 ]) Token_L();
+    translate(token_pitch * [   0.5,   2.5 ]) Token_L();
+    translate(token_pitch * [   1.5,  -2.5 ]) Token_L();
+    translate(token_pitch * [   1.5,  -1.5 ]) Token_L();
+    translate(token_pitch * [   1.5,  -0.5 ]) Token_L();
+    translate(token_pitch * [   1.5,   0.5 ]) Token_L();
+    translate(token_pitch * [   1.5,   1.5 ]) Token_L();
+    translate(token_pitch * [   1.5,   2.5 ]) Token_L();
+  };
+  union(){
+    Frame($phase, token_pitch * [ 6 + 1.00, 6 + 0.50 ]);
+    $nspots = 3;
+    translate(token_pitch * [   2.5,  -2.5 ]) Token_L();
+    translate(token_pitch * [   2.5,  -1.5 ]) Token_L();
+    translate(token_pitch * [   2.5,  -0.5 ]) Token_L();
+    translate(token_pitch * [   2.5,   0.5 ]) Token_L();
+    translate(token_pitch * [   2.5,   1.5 ]) Token_L();
+  };
+  union(){
+    Frame($phase, token_pitch * [ 6 + 1.00, 6 + 0.50 ]);
+    $nspots = 4;
+  };
+}
+module Base_WhiteSpare(){ ////toplevel
+// Base  WhiteSpare  total= 3  rowsz=2  nrows=2
+  union(){
+    Frame($phase, token_pitch * [ 2 + 1.00, 2 + 0.50 ]);
+    $nspots = 0;
+  };
+  union(){
+    Frame($phase, token_pitch * [ 2 + 1.00, 2 + 0.50 ]);
+    $nspots = 1;
+    translate(token_pitch * [  -0.5,  -0.5 ]) Token_L();
+  };
+  union(){
+    Frame($phase, token_pitch * [ 2 + 1.00, 2 + 0.50 ]);
+    $nspots = 2;
+    translate(token_pitch * [  -0.5,   0.5 ]) Token_L();
+  };
+  union(){
+    Frame($phase, token_pitch * [ 2 + 1.00, 2 + 0.50 ]);
+    $nspots = 3;
+    translate(token_pitch * [   0.5,  -0.5 ]) Token_L();
+  };
+  union(){
+    Frame($phase, token_pitch * [ 2 + 1.00, 2 + 0.50 ]);
+    $nspots = 4;
+  };
+}
+module Base_Yellow(){ ////toplevel
+// Base  Yellow      total=29  rowsz=6  nrows=5
+  union(){
+    Frame($phase, token_pitch * [ 6 + 1.00, 5 + 0.50 ]);
+    $nspots = 0;
+  };
+  union(){
+    Frame($phase, token_pitch * [ 6 + 1.00, 5 + 0.50 ]);
+    $nspots = 1;
+    translate(token_pitch * [  -2.5,  -2.0 ]) Token_L();
+    translate(token_pitch * [  -2.5,  -1.0 ]) Token_L();
+    translate(token_pitch * [  -2.5,   0.0 ]) Token_L();
+    translate(token_pitch * [  -2.5,   1.0 ]) Token_L();
+    translate(token_pitch * [  -2.5,   2.0 ]) Token_L();
+    translate(token_pitch * [  -1.5,  -2.0 ]) Token_L();
+    translate(token_pitch * [  -1.5,  -1.0 ]) Token_L();
+    translate(token_pitch * [  -1.5,   0.0 ]) Token_L();
+    translate(token_pitch * [  -1.5,   1.0 ]) Token_L();
+    translate(token_pitch * [  -1.5,   2.0 ]) Token_L();
+    translate(token_pitch * [  -0.5,  -2.0 ]) Token_L();
+    translate(token_pitch * [  -0.5,  -1.0 ]) Token_L();
+    translate(token_pitch * [  -0.5,   0.0 ]) Token_L();
+  };
+  union(){
+    Frame($phase, token_pitch * [ 6 + 1.00, 5 + 0.50 ]);
+    $nspots = 2;
+    translate(token_pitch * [  -0.5,   1.0 ]) Token_L();
+    translate(token_pitch * [  -0.5,   2.0 ]) Token_L();
+    translate(token_pitch * [   0.5,  -2.0 ]) Token_L();
+    translate(token_pitch * [   0.5,  -1.0 ]) Token_L();
+    translate(token_pitch * [   0.5,   0.0 ]) Token_L();
+    translate(token_pitch * [   0.5,   1.0 ]) Token_L();
+  };
+  union(){
+    Frame($phase, token_pitch * [ 6 + 1.00, 5 + 0.50 ]);
+    $nspots = 3;
+  };
+  union(){
+    Frame($phase, token_pitch * [ 6 + 1.00, 5 + 0.50 ]);
+    $nspots = 4;
+    translate(token_pitch * [   0.5,   2.0 ]) Token_L();
+    translate(token_pitch * [   1.5,  -2.0 ]) Token_L();
+    translate(token_pitch * [   1.5,  -1.0 ]) Token_L();
+    translate(token_pitch * [   1.5,   0.0 ]) Token_L();
+    translate(token_pitch * [   1.5,   1.0 ]) Token_L();
+    translate(token_pitch * [   1.5,   2.0 ]) Token_L();
+    translate(token_pitch * [   2.5,  -2.0 ]) Token_L();
+    translate(token_pitch * [   2.5,  -1.0 ]) Token_L();
+    translate(token_pitch * [   2.5,   0.0 ]) Token_L();
+    translate(token_pitch * [   2.5,   1.0 ]) Token_L();
+  };
+}
+// Base  total_count=252   total_real_count=219
+// Base  max_rowsz=7       max_nrows=6
+module All_Black(){ ////toplevel
+// All  Black       total=26  rowsz=6  nrows=5
+  union(){
+    Frame($phase, token_pitch * [ 6 + 1.00, 5 + 0.50 ]);
+    $nspots = 0;
+  };
+  union(){
+    Frame($phase, token_pitch * [ 6 + 1.00, 5 + 0.50 ]);
+    $nspots = 1;
+    translate(token_pitch * [  -2.5,  -2.0 ]) Token_L();
+    translate(token_pitch * [  -2.5,  -1.0 ]) Token_L();
+    translate(token_pitch * [  -2.5,   0.0 ]) Token_L();
+    translate(token_pitch * [  -2.5,   1.0 ]) Token_L();
+    translate(token_pitch * [  -2.5,   2.0 ]) Token_L();
+    translate(token_pitch * [  -1.5,  -2.0 ]) Token_L();
+    translate(token_pitch * [  -1.5,  -1.0 ]) Token_L();
+    translate(token_pitch * [  -1.5,   0.0 ]) Token_L();
+    translate(token_pitch * [  -1.5,   1.0 ]) Token_L();
+    translate(token_pitch * [  -1.5,   2.0 ]) Token_L();
+    translate(token_pitch * [  -0.5,  -2.0 ]) Token_L();
+    translate(token_pitch * [  -0.5,  -1.0 ]) Token_L();
+    translate(token_pitch * [  -0.5,   0.0 ]) Token_L();
+    translate(token_pitch * [  -0.5,   1.0 ]) Token_L();
+    translate(token_pitch * [  -0.5,   2.0 ]) Token_L();
+    translate(token_pitch * [   0.5,  -2.0 ]) Token_L();
+    translate(token_pitch * [   0.5,  -1.0 ]) Token_L();
+    translate(token_pitch * [   0.5,   0.0 ]) Token_L();
+    translate(token_pitch * [   0.5,   1.0 ]) Token_L();
+    translate(token_pitch * [   0.5,   2.0 ]) Token_L();
+    translate(token_pitch * [   1.5,  -2.0 ]) Token_L();
+    translate(token_pitch * [   1.5,  -1.0 ]) Token_L();
+    translate(token_pitch * [   1.5,   0.0 ]) Token_L();
+    translate(token_pitch * [   1.5,   1.0 ]) Token_L();
+    translate(token_pitch * [   1.5,   2.0 ]) Token_L();
+    translate(token_pitch * [   2.5,  -2.0 ]) Token_L();
+  };
+  union(){
+    Frame($phase, token_pitch * [ 6 + 1.00, 5 + 0.50 ]);
+    $nspots = 2;
+  };
+  union(){
+    Frame($phase, token_pitch * [ 6 + 1.00, 5 + 0.50 ]);
+    $nspots = 3;
+  };
+  union(){
+    Frame($phase, token_pitch * [ 6 + 1.00, 5 + 0.50 ]);
+    $nspots = 4;
+  };
+}
+module All_Blue(){ ////toplevel
+// All  Blue        total=52  rowsz=8  nrows=7
+  union(){
+    Frame($phase, token_pitch * [ 8 + 1.00, 7 + 0.50 ]);
+    $nspots = 0;
+  };
+  union(){
+    Frame($phase, token_pitch * [ 8 + 1.00, 7 + 0.50 ]);
+    $nspots = 1;
+    translate(token_pitch * [  -3.5,  -3.0 ]) Token_L();
+    translate(token_pitch * [  -3.5,  -2.0 ]) Token_L();
+    translate(token_pitch * [  -3.5,  -1.0 ]) Token_L();
+    translate(token_pitch * [  -3.5,   0.0 ]) Token_L();
+    translate(token_pitch * [  -3.5,   1.0 ]) Token_L();
+    translate(token_pitch * [  -3.5,   2.0 ]) Token_L();
+    translate(token_pitch * [  -3.5,   3.0 ]) Token_L();
+    translate(token_pitch * [  -2.5,  -3.0 ]) Token_L();
+    translate(token_pitch * [  -2.5,  -2.0 ]) Token_L();
+    translate(token_pitch * [  -2.5,  -1.0 ]) Token_L();
+    translate(token_pitch * [  -2.5,   0.0 ]) Token_L();
+    translate(token_pitch * [  -2.5,   1.0 ]) Token_L();
+    translate(token_pitch * [  -2.5,   2.0 ]) Token_L();
+    translate(token_pitch * [  -2.5,   3.0 ]) Token_L();
+    translate(token_pitch * [  -1.5,  -3.0 ]) Token_L();
+    translate(token_pitch * [  -1.5,  -2.0 ]) Token_L();
+    translate(token_pitch * [  -1.5,  -1.0 ]) Token_L();
+    translate(token_pitch * [  -1.5,   0.0 ]) Token_L();
+    translate(token_pitch * [  -1.5,   1.0 ]) Token_L();
+    translate(token_pitch * [  -1.5,   2.0 ]) Token_L();
+    translate(token_pitch * [  -1.5,   3.0 ]) Token_L();
+    translate(token_pitch * [  -0.5,  -3.0 ]) Token_L();
+  };
+  union(){
+    Frame($phase, token_pitch * [ 8 + 1.00, 7 + 0.50 ]);
+    $nspots = 2;
+    translate(token_pitch * [  -0.5,  -2.0 ]) Token_L();
+    translate(token_pitch * [  -0.5,  -1.0 ]) Token_L();
+    translate(token_pitch * [  -0.5,   0.0 ]) Token_L();
+    translate(token_pitch * [  -0.5,   1.0 ]) Token_L();
+    translate(token_pitch * [  -0.5,   2.0 ]) Token_L();
+    translate(token_pitch * [  -0.5,   3.0 ]) Token_L();
+    translate(token_pitch * [   0.5,  -3.0 ]) Token_L();
+    translate(token_pitch * [   0.5,  -2.0 ]) Token_L();
+    translate(token_pitch * [   0.5,  -1.0 ]) Token_L();
+    translate(token_pitch * [   0.5,   0.0 ]) Token_L();
+    translate(token_pitch * [   0.5,   1.0 ]) Token_L();
+    translate(token_pitch * [   0.5,   2.0 ]) Token_L();
+    translate(token_pitch * [   0.5,   3.0 ]) Token_L();
+    translate(token_pitch * [   1.5,  -3.0 ]) Token_L();
+    translate(token_pitch * [   1.5,  -2.0 ]) Token_L();
+  };
+  union(){
+    Frame($phase, token_pitch * [ 8 + 1.00, 7 + 0.50 ]);
+    $nspots = 3;
+  };
+  union(){
+    Frame($phase, token_pitch * [ 8 + 1.00, 7 + 0.50 ]);
+    $nspots = 4;
+    translate(token_pitch * [   1.5,  -1.0 ]) Token_L();
+    translate(token_pitch * [   1.5,   0.0 ]) Token_L();
+    translate(token_pitch * [   1.5,   1.0 ]) Token_L();
+    translate(token_pitch * [   1.5,   2.0 ]) Token_L();
+    translate(token_pitch * [   1.5,   3.0 ]) Token_L();
+    translate(token_pitch * [   2.5,  -3.0 ]) Token_L();
+    translate(token_pitch * [   2.5,  -2.0 ]) Token_L();
+    translate(token_pitch * [   2.5,  -1.0 ]) Token_L();
+    translate(token_pitch * [   2.5,   0.0 ]) Token_L();
+    translate(token_pitch * [   2.5,   1.0 ]) Token_L();
+    translate(token_pitch * [   2.5,   2.0 ]) Token_L();
+    translate(token_pitch * [   2.5,   3.0 ]) Token_L();
+    translate(token_pitch * [   3.5,  -3.0 ]) Token_L();
+    translate(token_pitch * [   3.5,  -2.0 ]) Token_L();
+    translate(token_pitch * [   3.5,  -1.0 ]) Token_L();
+  };
+}
+module All_Green(){ ////toplevel
+// All  Green       total=58  rowsz=8  nrows=8
+  union(){
+    Frame($phase, token_pitch * [ 8 + 1.00, 8 + 0.50 ]);
+    $nspots = 0;
+  };
+  union(){
+    Frame($phase, token_pitch * [ 8 + 1.00, 8 + 0.50 ]);
+    $nspots = 1;
+    translate(token_pitch * [  -3.5,  -3.5 ]) Token_L();
+    translate(token_pitch * [  -3.5,  -2.5 ]) Token_L();
+    translate(token_pitch * [  -3.5,  -1.5 ]) Token_L();
+    translate(token_pitch * [  -3.5,  -0.5 ]) Token_L();
+    translate(token_pitch * [  -3.5,   0.5 ]) Token_L();
+    translate(token_pitch * [  -3.5,   1.5 ]) Token_L();
+    translate(token_pitch * [  -3.5,   2.5 ]) Token_L();
+    translate(token_pitch * [  -3.5,   3.5 ]) Token_L();
+    translate(token_pitch * [  -2.5,  -3.5 ]) Token_L();
+    translate(token_pitch * [  -2.5,  -2.5 ]) Token_L();
+    translate(token_pitch * [  -2.5,  -1.5 ]) Token_L();
+    translate(token_pitch * [  -2.5,  -0.5 ]) Token_L();
+    translate(token_pitch * [  -2.5,   0.5 ]) Token_L();
+    translate(token_pitch * [  -2.5,   1.5 ]) Token_L();
+    translate(token_pitch * [  -2.5,   2.5 ]) Token_L();
+    translate(token_pitch * [  -2.5,   3.5 ]) Token_L();
+    translate(token_pitch * [  -1.5,  -3.5 ]) Token_L();
+    translate(token_pitch * [  -1.5,  -2.5 ]) Token_L();
+    translate(token_pitch * [  -1.5,  -1.5 ]) Token_L();
+    translate(token_pitch * [  -1.5,  -0.5 ]) Token_L();
+    translate(token_pitch * [  -1.5,   0.5 ]) Token_L();
+    translate(token_pitch * [  -1.5,   1.5 ]) Token_L();
+    translate(token_pitch * [  -1.5,   2.5 ]) Token_L();
+    translate(token_pitch * [  -1.5,   3.5 ]) Token_L();
+    translate(token_pitch * [  -0.5,  -3.5 ]) Token_L();
+  };
+  union(){
+    Frame($phase, token_pitch * [ 8 + 1.00, 8 + 0.50 ]);
+    $nspots = 2;
+    translate(token_pitch * [  -0.5,  -2.5 ]) Token_L();
+    translate(token_pitch * [  -0.5,  -1.5 ]) Token_L();
+    translate(token_pitch * [  -0.5,  -0.5 ]) Token_L();
+    translate(token_pitch * [  -0.5,   0.5 ]) Token_L();
+    translate(token_pitch * [  -0.5,   1.5 ]) Token_L();
+    translate(token_pitch * [  -0.5,   2.5 ]) Token_L();
+    translate(token_pitch * [  -0.5,   3.5 ]) Token_L();
+    translate(token_pitch * [   0.5,  -3.5 ]) Token_L();
+    translate(token_pitch * [   0.5,  -2.5 ]) Token_L();
+    translate(token_pitch * [   0.5,  -1.5 ]) Token_L();
+    translate(token_pitch * [   0.5,  -0.5 ]) Token_L();
+    translate(token_pitch * [   0.5,   0.5 ]) Token_L();
+    translate(token_pitch * [   0.5,   1.5 ]) Token_L();
+    translate(token_pitch * [   0.5,   2.5 ]) Token_L();
+    translate(token_pitch * [   0.5,   3.5 ]) Token_L();
+  };
+  union(){
+    Frame($phase, token_pitch * [ 8 + 1.00, 8 + 0.50 ]);
+    $nspots = 3;
+  };
+  union(){
+    Frame($phase, token_pitch * [ 8 + 1.00, 8 + 0.50 ]);
+    $nspots = 4;
+    translate(token_pitch * [   1.5,  -3.5 ]) Token_L();
+    translate(token_pitch * [   1.5,  -2.5 ]) Token_L();
+    translate(token_pitch * [   1.5,  -1.5 ]) Token_L();
+    translate(token_pitch * [   1.5,  -0.5 ]) Token_L();
+    translate(token_pitch * [   1.5,   0.5 ]) Token_L();
+    translate(token_pitch * [   1.5,   1.5 ]) Token_L();
+    translate(token_pitch * [   1.5,   2.5 ]) Token_L();
+    translate(token_pitch * [   1.5,   3.5 ]) Token_L();
+    translate(token_pitch * [   2.5,  -3.5 ]) Token_L();
+    translate(token_pitch * [   2.5,  -2.5 ]) Token_L();
+    translate(token_pitch * [   2.5,  -1.5 ]) Token_L();
+    translate(token_pitch * [   2.5,  -0.5 ]) Token_L();
+    translate(token_pitch * [   2.5,   0.5 ]) Token_L();
+    translate(token_pitch * [   2.5,   1.5 ]) Token_L();
+    translate(token_pitch * [   2.5,   2.5 ]) Token_L();
+    translate(token_pitch * [   2.5,   3.5 ]) Token_L();
+    translate(token_pitch * [   3.5,  -3.5 ]) Token_L();
+    translate(token_pitch * [   3.5,  -2.5 ]) Token_L();
+  };
+}
+module All_Loco(){ ////toplevel
+// All  Loco        total=25  rowsz=5  nrows=5
+  union(){
+    Frame($phase, token_pitch * [ 5 + 1.00, 5 + 0.50 ]);
+    $nspots = 0;
+    translate(token_pitch * [  -2.0,  -2.0 ]) Token_L();
+    translate(token_pitch * [  -2.0,  -1.0 ]) Token_L();
+    translate(token_pitch * [  -2.0,   0.0 ]) Token_L();
+    translate(token_pitch * [  -2.0,   1.0 ]) Token_L();
+    translate(token_pitch * [  -2.0,   2.0 ]) Token_L();
+    translate(token_pitch * [  -1.0,  -2.0 ]) Token_L();
+    translate(token_pitch * [  -1.0,  -1.0 ]) Token_L();
+    translate(token_pitch * [  -1.0,   0.0 ]) Token_L();
+    translate(token_pitch * [  -1.0,   1.0 ]) Token_L();
+    translate(token_pitch * [  -1.0,   2.0 ]) Token_L();
+    translate(token_pitch * [   0.0,  -2.0 ]) Token_L();
+    translate(token_pitch * [   0.0,  -1.0 ]) Token_L();
+    translate(token_pitch * [   0.0,   0.0 ]) Token_L();
+    translate(token_pitch * [   0.0,   1.0 ]) Token_L();
+    translate(token_pitch * [   0.0,   2.0 ]) Token_L();
+    translate(token_pitch * [   1.0,  -2.0 ]) Token_L();
+    translate(token_pitch * [   1.0,  -1.0 ]) Token_L();
+    translate(token_pitch * [   1.0,   0.0 ]) Token_L();
+    translate(token_pitch * [   1.0,   1.0 ]) Token_L();
+    translate(token_pitch * [   1.0,   2.0 ]) Token_L();
+    translate(token_pitch * [   2.0,  -2.0 ]) Token_L();
+    translate(token_pitch * [   2.0,  -1.0 ]) Token_L();
+    translate(token_pitch * [   2.0,   0.0 ]) Token_L();
+    translate(token_pitch * [   2.0,   1.0 ]) Token_L();
+    translate(token_pitch * [   2.0,   2.0 ]) Token_L();
+  };
+  union(){
+    Frame($phase, token_pitch * [ 5 + 1.00, 5 + 0.50 ]);
+    $nspots = 1;
+  };
+  union(){
+    Frame($phase, token_pitch * [ 5 + 1.00, 5 + 0.50 ]);
+    $nspots = 2;
+  };
+  union(){
+    Frame($phase, token_pitch * [ 5 + 1.00, 5 + 0.50 ]);
+    $nspots = 3;
+  };
+  union(){
+    Frame($phase, token_pitch * [ 5 + 1.00, 5 + 0.50 ]);
+    $nspots = 4;
+  };
+}
+module All_Orange(){ ////toplevel
+// All  Orange      total=32  rowsz=6  nrows=6
+  union(){
+    Frame($phase, token_pitch * [ 6 + 1.00, 6 + 0.50 ]);
+    $nspots = 0;
+  };
+  union(){
+    Frame($phase, token_pitch * [ 6 + 1.00, 6 + 0.50 ]);
+    $nspots = 1;
+    translate(token_pitch * [  -2.5,  -2.5 ]) Token_L();
+    translate(token_pitch * [  -2.5,  -1.5 ]) Token_L();
+    translate(token_pitch * [  -2.5,  -0.5 ]) Token_L();
+    translate(token_pitch * [  -2.5,   0.5 ]) Token_L();
+    translate(token_pitch * [  -2.5,   1.5 ]) Token_L();
+    translate(token_pitch * [  -2.5,   2.5 ]) Token_L();
+    translate(token_pitch * [  -1.5,  -2.5 ]) Token_L();
+    translate(token_pitch * [  -1.5,  -1.5 ]) Token_L();
+    translate(token_pitch * [  -1.5,  -0.5 ]) Token_L();
+    translate(token_pitch * [  -1.5,   0.5 ]) Token_L();
+    translate(token_pitch * [  -1.5,   1.5 ]) Token_L();
+    translate(token_pitch * [  -1.5,   2.5 ]) Token_L();
+    translate(token_pitch * [  -0.5,  -2.5 ]) Token_L();
+    translate(token_pitch * [  -0.5,  -1.5 ]) Token_L();
+    translate(token_pitch * [  -0.5,  -0.5 ]) Token_L();
+    translate(token_pitch * [  -0.5,   0.5 ]) Token_L();
+    translate(token_pitch * [  -0.5,   1.5 ]) Token_L();
+    translate(token_pitch * [  -0.5,   2.5 ]) Token_L();
+    translate(token_pitch * [   0.5,  -2.5 ]) Token_L();
+    translate(token_pitch * [   0.5,  -1.5 ]) Token_L();
+    translate(token_pitch * [   0.5,  -0.5 ]) Token_L();
+    translate(token_pitch * [   0.5,   0.5 ]) Token_L();
+    translate(token_pitch * [   0.5,   1.5 ]) Token_L();
+    translate(token_pitch * [   0.5,   2.5 ]) Token_L();
+    translate(token_pitch * [   1.5,  -2.5 ]) Token_L();
+    translate(token_pitch * [   1.5,  -1.5 ]) Token_L();
+    translate(token_pitch * [   1.5,  -0.5 ]) Token_L();
+    translate(token_pitch * [   1.5,   0.5 ]) Token_L();
+    translate(token_pitch * [   1.5,   1.5 ]) Token_L();
+    translate(token_pitch * [   1.5,   2.5 ]) Token_L();
+    translate(token_pitch * [   2.5,  -2.5 ]) Token_L();
+    translate(token_pitch * [   2.5,  -1.5 ]) Token_L();
+  };
+  union(){
+    Frame($phase, token_pitch * [ 6 + 1.00, 6 + 0.50 ]);
+    $nspots = 2;
+  };
+  union(){
+    Frame($phase, token_pitch * [ 6 + 1.00, 6 + 0.50 ]);
+    $nspots = 3;
+  };
+  union(){
+    Frame($phase, token_pitch * [ 6 + 1.00, 6 + 0.50 ]);
+    $nspots = 4;
+  };
+}
+module All_Orange6(){ ////toplevel
+// All  Orange6     total=20  rowsz=5  nrows=4
+  union(){
+    Frame($phase, token_pitch * [ 5 + 1.00, 4 + 0.50 ]);
+    $nspots = 0;
+    translate(token_pitch * [  -2.0,  -1.5 ]) Token_L();
+    translate(token_pitch * [  -2.0,  -0.5 ]) Token_L();
+    translate(token_pitch * [  -2.0,   0.5 ]) Token_L();
+    translate(token_pitch * [  -2.0,   1.5 ]) Token_L();
+    translate(token_pitch * [  -1.0,  -1.5 ]) Token_L();
+    translate(token_pitch * [  -1.0,  -0.5 ]) Token_L();
+    translate(token_pitch * [  -1.0,   0.5 ]) Token_L();
+    translate(token_pitch * [  -1.0,   1.5 ]) Token_L();
+    translate(token_pitch * [   0.0,  -1.5 ]) Token_L();
+    translate(token_pitch * [   0.0,  -0.5 ]) Token_L();
+    translate(token_pitch * [   0.0,   0.5 ]) Token_L();
+    translate(token_pitch * [   0.0,   1.5 ]) Token_L();
+    translate(token_pitch * [   1.0,  -1.5 ]) Token_L();
+    translate(token_pitch * [   1.0,  -0.5 ]) Token_L();
+    translate(token_pitch * [   1.0,   0.5 ]) Token_L();
+    translate(token_pitch * [   1.0,   1.5 ]) Token_L();
+    translate(token_pitch * [   2.0,  -1.5 ]) Token_L();
+    translate(token_pitch * [   2.0,  -0.5 ]) Token_L();
+    translate(token_pitch * [   2.0,   0.5 ]) Token_L();
+    translate(token_pitch * [   2.0,   1.5 ]) Token_L();
+  };
+  union(){
+    Frame($phase, token_pitch * [ 5 + 1.00, 4 + 0.50 ]);
+    $nspots = 1;
+  };
+  union(){
+    Frame($phase, token_pitch * [ 5 + 1.00, 4 + 0.50 ]);
+    $nspots = 2;
+  };
+  union(){
+    Frame($phase, token_pitch * [ 5 + 1.00, 4 + 0.50 ]);
+    $nspots = 3;
+  };
+  union(){
+    Frame($phase, token_pitch * [ 5 + 1.00, 4 + 0.50 ]);
+    $nspots = 4;
+  };
+}
+module All_Purple(){ ////toplevel
+// All  Purple      total=23  rowsz=5  nrows=5
+  union(){
+    Frame($phase, token_pitch * [ 5 + 1.00, 5 + 0.50 ]);
+    $nspots = 0;
+  };
+  union(){
+    Frame($phase, token_pitch * [ 5 + 1.00, 5 + 0.50 ]);
+    $nspots = 1;
+    translate(token_pitch * [  -2.0,  -2.0 ]) Token_L();
+    translate(token_pitch * [  -2.0,  -1.0 ]) Token_L();
+    translate(token_pitch * [  -2.0,   0.0 ]) Token_L();
+    translate(token_pitch * [  -2.0,   1.0 ]) Token_L();
+    translate(token_pitch * [  -2.0,   2.0 ]) Token_L();
+    translate(token_pitch * [  -1.0,  -2.0 ]) Token_L();
+    translate(token_pitch * [  -1.0,  -1.0 ]) Token_L();
+    translate(token_pitch * [  -1.0,   0.0 ]) Token_L();
+    translate(token_pitch * [  -1.0,   1.0 ]) Token_L();
+    translate(token_pitch * [  -1.0,   2.0 ]) Token_L();
+    translate(token_pitch * [   0.0,  -2.0 ]) Token_L();
+    translate(token_pitch * [   0.0,  -1.0 ]) Token_L();
+    translate(token_pitch * [   0.0,   0.0 ]) Token_L();
+    translate(token_pitch * [   0.0,   1.0 ]) Token_L();
+    translate(token_pitch * [   0.0,   2.0 ]) Token_L();
+    translate(token_pitch * [   1.0,  -2.0 ]) Token_L();
+    translate(token_pitch * [   1.0,  -1.0 ]) Token_L();
+    translate(token_pitch * [   1.0,   0.0 ]) Token_L();
+    translate(token_pitch * [   1.0,   1.0 ]) Token_L();
+    translate(token_pitch * [   1.0,   2.0 ]) Token_L();
+    translate(token_pitch * [   2.0,  -2.0 ]) Token_L();
+    translate(token_pitch * [   2.0,  -1.0 ]) Token_L();
+    translate(token_pitch * [   2.0,   0.0 ]) Token_L();
+  };
+  union(){
+    Frame($phase, token_pitch * [ 5 + 1.00, 5 + 0.50 ]);
+    $nspots = 2;
+  };
+  union(){
+    Frame($phase, token_pitch * [ 5 + 1.00, 5 + 0.50 ]);
+    $nspots = 3;
+  };
+  union(){
+    Frame($phase, token_pitch * [ 5 + 1.00, 5 + 0.50 ]);
+    $nspots = 4;
+  };
+}
+module All_Red(){ ////toplevel
+// All  Red         total=46  rowsz=7  nrows=7
+  union(){
+    Frame($phase, token_pitch * [ 7 + 1.00, 7 + 0.50 ]);
+    $nspots = 0;
+  };
+  union(){
+    Frame($phase, token_pitch * [ 7 + 1.00, 7 + 0.50 ]);
+    $nspots = 1;
+    translate(token_pitch * [  -3.0,  -3.0 ]) Token_L();
+    translate(token_pitch * [  -3.0,  -2.0 ]) Token_L();
+    translate(token_pitch * [  -3.0,  -1.0 ]) Token_L();
+    translate(token_pitch * [  -3.0,   0.0 ]) Token_L();
+    translate(token_pitch * [  -3.0,   1.0 ]) Token_L();
+    translate(token_pitch * [  -3.0,   2.0 ]) Token_L();
+    translate(token_pitch * [  -3.0,   3.0 ]) Token_L();
+    translate(token_pitch * [  -2.0,  -3.0 ]) Token_L();
+    translate(token_pitch * [  -2.0,  -2.0 ]) Token_L();
+    translate(token_pitch * [  -2.0,  -1.0 ]) Token_L();
+    translate(token_pitch * [  -2.0,   0.0 ]) Token_L();
+    translate(token_pitch * [  -2.0,   1.0 ]) Token_L();
+    translate(token_pitch * [  -2.0,   2.0 ]) Token_L();
+    translate(token_pitch * [  -2.0,   3.0 ]) Token_L();
+    translate(token_pitch * [  -1.0,  -3.0 ]) Token_L();
+    translate(token_pitch * [  -1.0,  -2.0 ]) Token_L();
+    translate(token_pitch * [  -1.0,  -1.0 ]) Token_L();
+    translate(token_pitch * [  -1.0,   0.0 ]) Token_L();
+  };
+  union(){
+    Frame($phase, token_pitch * [ 7 + 1.00, 7 + 0.50 ]);
+    $nspots = 2;
+    translate(token_pitch * [  -1.0,   1.0 ]) Token_L();
+    translate(token_pitch * [  -1.0,   2.0 ]) Token_L();
+    translate(token_pitch * [  -1.0,   3.0 ]) Token_L();
+    translate(token_pitch * [   0.0,  -3.0 ]) Token_L();
+    translate(token_pitch * [   0.0,  -2.0 ]) Token_L();
+    translate(token_pitch * [   0.0,  -1.0 ]) Token_L();
+    translate(token_pitch * [   0.0,   0.0 ]) Token_L();
+    translate(token_pitch * [   0.0,   1.0 ]) Token_L();
+    translate(token_pitch * [   0.0,   2.0 ]) Token_L();
+    translate(token_pitch * [   0.0,   3.0 ]) Token_L();
+    translate(token_pitch * [   1.0,  -3.0 ]) Token_L();
+    translate(token_pitch * [   1.0,  -2.0 ]) Token_L();
+    translate(token_pitch * [   1.0,  -1.0 ]) Token_L();
+  };
+  union(){
+    Frame($phase, token_pitch * [ 7 + 1.00, 7 + 0.50 ]);
+    $nspots = 3;
+  };
+  union(){
+    Frame($phase, token_pitch * [ 7 + 1.00, 7 + 0.50 ]);
+    $nspots = 4;
+    translate(token_pitch * [   1.0,   0.0 ]) Token_L();
+    translate(token_pitch * [   1.0,   1.0 ]) Token_L();
+    translate(token_pitch * [   1.0,   2.0 ]) Token_L();
+    translate(token_pitch * [   1.0,   3.0 ]) Token_L();
+    translate(token_pitch * [   2.0,  -3.0 ]) Token_L();
+    translate(token_pitch * [   2.0,  -2.0 ]) Token_L();
+    translate(token_pitch * [   2.0,  -1.0 ]) Token_L();
+    translate(token_pitch * [   2.0,   0.0 ]) Token_L();
+    translate(token_pitch * [   2.0,   1.0 ]) Token_L();
+    translate(token_pitch * [   2.0,   2.0 ]) Token_L();
+    translate(token_pitch * [   2.0,   3.0 ]) Token_L();
+    translate(token_pitch * [   3.0,  -3.0 ]) Token_L();
+    translate(token_pitch * [   3.0,  -2.0 ]) Token_L();
+    translate(token_pitch * [   3.0,  -1.0 ]) Token_L();
+    translate(token_pitch * [   3.0,   0.0 ]) Token_L();
+  };
+}
+module All_White(){ ////toplevel
+// All  White       total=46  rowsz=7  nrows=7
+  union(){
+    Frame($phase, token_pitch * [ 7 + 1.00, 7 + 0.50 ]);
+    $nspots = 0;
+  };
+  union(){
+    Frame($phase, token_pitch * [ 7 + 1.00, 7 + 0.50 ]);
+    $nspots = 1;
+    translate(token_pitch * [  -3.0,  -3.0 ]) Token_L();
+    translate(token_pitch * [  -3.0,  -2.0 ]) Token_L();
+    translate(token_pitch * [  -3.0,  -1.0 ]) Token_L();
+    translate(token_pitch * [  -3.0,   0.0 ]) Token_L();
+    translate(token_pitch * [  -3.0,   1.0 ]) Token_L();
+    translate(token_pitch * [  -3.0,   2.0 ]) Token_L();
+    translate(token_pitch * [  -3.0,   3.0 ]) Token_L();
+    translate(token_pitch * [  -2.0,  -3.0 ]) Token_L();
+    translate(token_pitch * [  -2.0,  -2.0 ]) Token_L();
+    translate(token_pitch * [  -2.0,  -1.0 ]) Token_L();
+    translate(token_pitch * [  -2.0,   0.0 ]) Token_L();
+    translate(token_pitch * [  -2.0,   1.0 ]) Token_L();
+    translate(token_pitch * [  -2.0,   2.0 ]) Token_L();
+    translate(token_pitch * [  -2.0,   3.0 ]) Token_L();
+    translate(token_pitch * [  -1.0,  -3.0 ]) Token_L();
+    translate(token_pitch * [  -1.0,  -2.0 ]) Token_L();
+    translate(token_pitch * [  -1.0,  -1.0 ]) Token_L();
+    translate(token_pitch * [  -1.0,   0.0 ]) Token_L();
+    translate(token_pitch * [  -1.0,   1.0 ]) Token_L();
+    translate(token_pitch * [  -1.0,   2.0 ]) Token_L();
+    translate(token_pitch * [  -1.0,   3.0 ]) Token_L();
+    translate(token_pitch * [   0.0,  -3.0 ]) Token_L();
+    translate(token_pitch * [   0.0,  -2.0 ]) Token_L();
+    translate(token_pitch * [   0.0,  -1.0 ]) Token_L();
+    translate(token_pitch * [   0.0,   0.0 ]) Token_L();
+    translate(token_pitch * [   0.0,   1.0 ]) Token_L();
+    translate(token_pitch * [   0.0,   2.0 ]) Token_L();
+  };
+  union(){
+    Frame($phase, token_pitch * [ 7 + 1.00, 7 + 0.50 ]);
+    $nspots = 2;
+    translate(token_pitch * [   0.0,   3.0 ]) Token_L();
+    translate(token_pitch * [   1.0,  -3.0 ]) Token_L();
+    translate(token_pitch * [   1.0,  -2.0 ]) Token_L();
+    translate(token_pitch * [   1.0,  -1.0 ]) Token_L();
+    translate(token_pitch * [   1.0,   0.0 ]) Token_L();
+    translate(token_pitch * [   1.0,   1.0 ]) Token_L();
+    translate(token_pitch * [   1.0,   2.0 ]) Token_L();
+    translate(token_pitch * [   1.0,   3.0 ]) Token_L();
+    translate(token_pitch * [   2.0,  -3.0 ]) Token_L();
+    translate(token_pitch * [   2.0,  -2.0 ]) Token_L();
+    translate(token_pitch * [   2.0,  -1.0 ]) Token_L();
+    translate(token_pitch * [   2.0,   0.0 ]) Token_L();
+  };
+  union(){
+    Frame($phase, token_pitch * [ 7 + 1.00, 7 + 0.50 ]);
+    $nspots = 3;
+    translate(token_pitch * [   2.0,   1.0 ]) Token_L();
+    translate(token_pitch * [   2.0,   2.0 ]) Token_L();
+    translate(token_pitch * [   2.0,   3.0 ]) Token_L();
+    translate(token_pitch * [   3.0,  -3.0 ]) Token_L();
+    translate(token_pitch * [   3.0,  -2.0 ]) Token_L();
+    translate(token_pitch * [   3.0,  -1.0 ]) Token_L();
+    translate(token_pitch * [   3.0,   0.0 ]) Token_L();
+  };
+  union(){
+    Frame($phase, token_pitch * [ 7 + 1.00, 7 + 0.50 ]);
+    $nspots = 4;
+  };
+}
+module All_WhiteSpare(){ ////toplevel
+// All  WhiteSpare  total= 3  rowsz=2  nrows=2
+  union(){
+    Frame($phase, token_pitch * [ 2 + 1.00, 2 + 0.50 ]);
+    $nspots = 0;
+  };
+  union(){
+    Frame($phase, token_pitch * [ 2 + 1.00, 2 + 0.50 ]);
+    $nspots = 1;
+    translate(token_pitch * [  -0.5,  -0.5 ]) Token_L();
+  };
+  union(){
+    Frame($phase, token_pitch * [ 2 + 1.00, 2 + 0.50 ]);
+    $nspots = 2;
+    translate(token_pitch * [  -0.5,   0.5 ]) Token_L();
+  };
+  union(){
+    Frame($phase, token_pitch * [ 2 + 1.00, 2 + 0.50 ]);
+    $nspots = 3;
+    translate(token_pitch * [   0.5,  -0.5 ]) Token_L();
+  };
+  union(){
+    Frame($phase, token_pitch * [ 2 + 1.00, 2 + 0.50 ]);
+    $nspots = 4;
+  };
+}
+module All_Yellow(){ ////toplevel
+// All  Yellow      total=45  rowsz=7  nrows=7
+  union(){
+    Frame($phase, token_pitch * [ 7 + 1.00, 7 + 0.50 ]);
+    $nspots = 0;
+  };
+  union(){
+    Frame($phase, token_pitch * [ 7 + 1.00, 7 + 0.50 ]);
+    $nspots = 1;
+    translate(token_pitch * [  -3.0,  -3.0 ]) Token_L();
+    translate(token_pitch * [  -3.0,  -2.0 ]) Token_L();
+    translate(token_pitch * [  -3.0,  -1.0 ]) Token_L();
+    translate(token_pitch * [  -3.0,   0.0 ]) Token_L();
+    translate(token_pitch * [  -3.0,   1.0 ]) Token_L();
+    translate(token_pitch * [  -3.0,   2.0 ]) Token_L();
+    translate(token_pitch * [  -3.0,   3.0 ]) Token_L();
+    translate(token_pitch * [  -2.0,  -3.0 ]) Token_L();
+    translate(token_pitch * [  -2.0,  -2.0 ]) Token_L();
+    translate(token_pitch * [  -2.0,  -1.0 ]) Token_L();
+    translate(token_pitch * [  -2.0,   0.0 ]) Token_L();
+    translate(token_pitch * [  -2.0,   1.0 ]) Token_L();
+    translate(token_pitch * [  -2.0,   2.0 ]) Token_L();
+    translate(token_pitch * [  -2.0,   3.0 ]) Token_L();
+    translate(token_pitch * [  -1.0,  -3.0 ]) Token_L();
+    translate(token_pitch * [  -1.0,  -2.0 ]) Token_L();
+    translate(token_pitch * [  -1.0,  -1.0 ]) Token_L();
+    translate(token_pitch * [  -1.0,   0.0 ]) Token_L();
+    translate(token_pitch * [  -1.0,   1.0 ]) Token_L();
+  };
+  union(){
+    Frame($phase, token_pitch * [ 7 + 1.00, 7 + 0.50 ]);
+    $nspots = 2;
+    translate(token_pitch * [  -1.0,   2.0 ]) Token_L();
+    translate(token_pitch * [  -1.0,   3.0 ]) Token_L();
+    translate(token_pitch * [   0.0,  -3.0 ]) Token_L();
+    translate(token_pitch * [   0.0,  -2.0 ]) Token_L();
+    translate(token_pitch * [   0.0,  -1.0 ]) Token_L();
+    translate(token_pitch * [   0.0,   0.0 ]) Token_L();
+    translate(token_pitch * [   0.0,   1.0 ]) Token_L();
+    translate(token_pitch * [   0.0,   2.0 ]) Token_L();
+    translate(token_pitch * [   0.0,   3.0 ]) Token_L();
+    translate(token_pitch * [   1.0,  -3.0 ]) Token_L();
+    translate(token_pitch * [   1.0,  -2.0 ]) Token_L();
+  };
+  union(){
+    Frame($phase, token_pitch * [ 7 + 1.00, 7 + 0.50 ]);
+    $nspots = 3;
+  };
+  union(){
+    Frame($phase, token_pitch * [ 7 + 1.00, 7 + 0.50 ]);
+    $nspots = 4;
+    translate(token_pitch * [   1.0,  -1.0 ]) Token_L();
+    translate(token_pitch * [   1.0,   0.0 ]) Token_L();
+    translate(token_pitch * [   1.0,   1.0 ]) Token_L();
+    translate(token_pitch * [   1.0,   2.0 ]) Token_L();
+    translate(token_pitch * [   1.0,   3.0 ]) Token_L();
+    translate(token_pitch * [   2.0,  -3.0 ]) Token_L();
+    translate(token_pitch * [   2.0,  -2.0 ]) Token_L();
+    translate(token_pitch * [   2.0,  -1.0 ]) Token_L();
+    translate(token_pitch * [   2.0,   0.0 ]) Token_L();
+    translate(token_pitch * [   2.0,   1.0 ]) Token_L();
+    translate(token_pitch * [   2.0,   2.0 ]) Token_L();
+    translate(token_pitch * [   2.0,   3.0 ]) Token_L();
+    translate(token_pitch * [   3.0,  -3.0 ]) Token_L();
+    translate(token_pitch * [   3.0,  -2.0 ]) Token_L();
+    translate(token_pitch * [   3.0,  -1.0 ]) Token_L();
+  };
+}
+// All  total_count=376   total_real_count=353
+// All  max_rowsz=8       max_nrows=8
+module Witches_Black(){ ////toplevel
+// Witches  Black       total= 8  rowsz=3  nrows=3
+  union(){
+    Frame($phase, token_pitch * [ 3 + 1.00, 3 + 0.50 ]);
+    $nspots = 0;
+  };
+  union(){
+    Frame($phase, token_pitch * [ 3 + 1.00, 3 + 0.50 ]);
+    $nspots = 1;
+    translate(token_pitch * [  -1.0,  -1.0 ]) Token_L();
+    translate(token_pitch * [  -1.0,   0.0 ]) Token_L();
+    translate(token_pitch * [  -1.0,   1.0 ]) Token_L();
+    translate(token_pitch * [   0.0,  -1.0 ]) Token_L();
+    translate(token_pitch * [   0.0,   0.0 ]) Token_L();
+    translate(token_pitch * [   0.0,   1.0 ]) Token_L();
+    translate(token_pitch * [   1.0,  -1.0 ]) Token_L();
+    translate(token_pitch * [   1.0,   0.0 ]) Token_L();
+  };
+  union(){
+    Frame($phase, token_pitch * [ 3 + 1.00, 3 + 0.50 ]);
+    $nspots = 2;
+  };
+  union(){
+    Frame($phase, token_pitch * [ 3 + 1.00, 3 + 0.50 ]);
+    $nspots = 3;
+  };
+  union(){
+    Frame($phase, token_pitch * [ 3 + 1.00, 3 + 0.50 ]);
+    $nspots = 4;
+  };
+}
+module Witches_Blue(){ ////toplevel
+// Witches  Blue        total=18  rowsz=5  nrows=4
+  union(){
+    Frame($phase, token_pitch * [ 5 + 1.00, 4 + 0.50 ]);
+    $nspots = 0;
+  };
+  union(){
+    Frame($phase, token_pitch * [ 5 + 1.00, 4 + 0.50 ]);
+    $nspots = 1;
+    translate(token_pitch * [  -2.0,  -1.5 ]) Token_L();
+    translate(token_pitch * [  -2.0,  -0.5 ]) Token_L();
+    translate(token_pitch * [  -2.0,   0.5 ]) Token_L();
+    translate(token_pitch * [  -2.0,   1.5 ]) Token_L();
+    translate(token_pitch * [  -1.0,  -1.5 ]) Token_L();
+    translate(token_pitch * [  -1.0,  -0.5 ]) Token_L();
+    translate(token_pitch * [  -1.0,   0.5 ]) Token_L();
+    translate(token_pitch * [  -1.0,   1.5 ]) Token_L();
+  };
+  union(){
+    Frame($phase, token_pitch * [ 5 + 1.00, 4 + 0.50 ]);
+    $nspots = 2;
+    translate(token_pitch * [   0.0,  -1.5 ]) Token_L();
+    translate(token_pitch * [   0.0,  -0.5 ]) Token_L();
+    translate(token_pitch * [   0.0,   0.5 ]) Token_L();
+    translate(token_pitch * [   0.0,   1.5 ]) Token_L();
+    translate(token_pitch * [   1.0,  -1.5 ]) Token_L();
+  };
+  union(){
+    Frame($phase, token_pitch * [ 5 + 1.00, 4 + 0.50 ]);
+    $nspots = 3;
+  };
+  union(){
+    Frame($phase, token_pitch * [ 5 + 1.00, 4 + 0.50 ]);
+    $nspots = 4;
+    translate(token_pitch * [   1.0,  -0.5 ]) Token_L();
+    translate(token_pitch * [   1.0,   0.5 ]) Token_L();
+    translate(token_pitch * [   1.0,   1.5 ]) Token_L();
+    translate(token_pitch * [   2.0,  -1.5 ]) Token_L();
+    translate(token_pitch * [   2.0,  -0.5 ]) Token_L();
+  };
+}
+module Witches_Green(){ ////toplevel
+// Witches  Green       total=20  rowsz=5  nrows=4
+  union(){
+    Frame($phase, token_pitch * [ 5 + 1.00, 4 + 0.50 ]);
+    $nspots = 0;
+  };
+  union(){
+    Frame($phase, token_pitch * [ 5 + 1.00, 4 + 0.50 ]);
+    $nspots = 1;
+    translate(token_pitch * [  -2.0,  -1.5 ]) Token_L();
+    translate(token_pitch * [  -2.0,  -0.5 ]) Token_L();
+    translate(token_pitch * [  -2.0,   0.5 ]) Token_L();
+    translate(token_pitch * [  -2.0,   1.5 ]) Token_L();
+    translate(token_pitch * [  -1.0,  -1.5 ]) Token_L();
+    translate(token_pitch * [  -1.0,  -0.5 ]) Token_L();
+    translate(token_pitch * [  -1.0,   0.5 ]) Token_L();
+    translate(token_pitch * [  -1.0,   1.5 ]) Token_L();
+    translate(token_pitch * [   0.0,  -1.5 ]) Token_L();
+    translate(token_pitch * [   0.0,  -0.5 ]) Token_L();
+  };
+  union(){
+    Frame($phase, token_pitch * [ 5 + 1.00, 4 + 0.50 ]);
+    $nspots = 2;
+    translate(token_pitch * [   0.0,   0.5 ]) Token_L();
+    translate(token_pitch * [   0.0,   1.5 ]) Token_L();
+    translate(token_pitch * [   1.0,  -1.5 ]) Token_L();
+    translate(token_pitch * [   1.0,  -0.5 ]) Token_L();
+    translate(token_pitch * [   1.0,   0.5 ]) Token_L();
+  };
+  union(){
+    Frame($phase, token_pitch * [ 5 + 1.00, 4 + 0.50 ]);
+    $nspots = 3;
+  };
+  union(){
+    Frame($phase, token_pitch * [ 5 + 1.00, 4 + 0.50 ]);
+    $nspots = 4;
+    translate(token_pitch * [   1.0,   1.5 ]) Token_L();
+    translate(token_pitch * [   2.0,  -1.5 ]) Token_L();
+    translate(token_pitch * [   2.0,  -0.5 ]) Token_L();
+    translate(token_pitch * [   2.0,   0.5 ]) Token_L();
+    translate(token_pitch * [   2.0,   1.5 ]) Token_L();
+  };
+}
+module Witches_Loco(){ ////toplevel
+// Witches  Loco        total=25  rowsz=5  nrows=5
+  union(){
+    Frame($phase, token_pitch * [ 5 + 1.00, 5 + 0.50 ]);
+    $nspots = 0;
+    translate(token_pitch * [  -2.0,  -2.0 ]) Token_L();
+    translate(token_pitch * [  -2.0,  -1.0 ]) Token_L();
+    translate(token_pitch * [  -2.0,   0.0 ]) Token_L();
+    translate(token_pitch * [  -2.0,   1.0 ]) Token_L();
+    translate(token_pitch * [  -2.0,   2.0 ]) Token_L();
+    translate(token_pitch * [  -1.0,  -2.0 ]) Token_L();
+    translate(token_pitch * [  -1.0,  -1.0 ]) Token_L();
+    translate(token_pitch * [  -1.0,   0.0 ]) Token_L();
+    translate(token_pitch * [  -1.0,   1.0 ]) Token_L();
+    translate(token_pitch * [  -1.0,   2.0 ]) Token_L();
+    translate(token_pitch * [   0.0,  -2.0 ]) Token_L();
+    translate(token_pitch * [   0.0,  -1.0 ]) Token_L();
+    translate(token_pitch * [   0.0,   0.0 ]) Token_L();
+    translate(token_pitch * [   0.0,   1.0 ]) Token_L();
+    translate(token_pitch * [   0.0,   2.0 ]) Token_L();
+    translate(token_pitch * [   1.0,  -2.0 ]) Token_L();
+    translate(token_pitch * [   1.0,  -1.0 ]) Token_L();
+    translate(token_pitch * [   1.0,   0.0 ]) Token_L();
+    translate(token_pitch * [   1.0,   1.0 ]) Token_L();
+    translate(token_pitch * [   1.0,   2.0 ]) Token_L();
+    translate(token_pitch * [   2.0,  -2.0 ]) Token_L();
+    translate(token_pitch * [   2.0,  -1.0 ]) Token_L();
+    translate(token_pitch * [   2.0,   0.0 ]) Token_L();
+    translate(token_pitch * [   2.0,   1.0 ]) Token_L();
+    translate(token_pitch * [   2.0,   2.0 ]) Token_L();
+  };
+  union(){
+    Frame($phase, token_pitch * [ 5 + 1.00, 5 + 0.50 ]);
+    $nspots = 1;
+  };
+  union(){
+    Frame($phase, token_pitch * [ 5 + 1.00, 5 + 0.50 ]);
+    $nspots = 2;
+  };
+  union(){
+    Frame($phase, token_pitch * [ 5 + 1.00, 5 + 0.50 ]);
+    $nspots = 3;
+  };
+  union(){
+    Frame($phase, token_pitch * [ 5 + 1.00, 5 + 0.50 ]);
+    $nspots = 4;
+  };
+}
+module Witches_Orange(){ ////toplevel
+// Witches  Orange      total=12  rowsz=4  nrows=3
+  union(){
+    Frame($phase, token_pitch * [ 4 + 1.00, 3 + 0.50 ]);
+    $nspots = 0;
+  };
+  union(){
+    Frame($phase, token_pitch * [ 4 + 1.00, 3 + 0.50 ]);
+    $nspots = 1;
+    translate(token_pitch * [  -1.5,  -1.0 ]) Token_L();
+    translate(token_pitch * [  -1.5,   0.0 ]) Token_L();
+    translate(token_pitch * [  -1.5,   1.0 ]) Token_L();
+    translate(token_pitch * [  -0.5,  -1.0 ]) Token_L();
+    translate(token_pitch * [  -0.5,   0.0 ]) Token_L();
+    translate(token_pitch * [  -0.5,   1.0 ]) Token_L();
+    translate(token_pitch * [   0.5,  -1.0 ]) Token_L();
+    translate(token_pitch * [   0.5,   0.0 ]) Token_L();
+    translate(token_pitch * [   0.5,   1.0 ]) Token_L();
+    translate(token_pitch * [   1.5,  -1.0 ]) Token_L();
+    translate(token_pitch * [   1.5,   0.0 ]) Token_L();
+    translate(token_pitch * [   1.5,   1.0 ]) Token_L();
+  };
+  union(){
+    Frame($phase, token_pitch * [ 4 + 1.00, 3 + 0.50 ]);
+    $nspots = 2;
+  };
+  union(){
+    Frame($phase, token_pitch * [ 4 + 1.00, 3 + 0.50 ]);
+    $nspots = 3;
+  };
+  union(){
+    Frame($phase, token_pitch * [ 4 + 1.00, 3 + 0.50 ]);
+    $nspots = 4;
+  };
+}
+module Witches_Orange6(){ ////toplevel
+// Witches  Orange6     total=20  rowsz=5  nrows=4
+  union(){
+    Frame($phase, token_pitch * [ 5 + 1.00, 4 + 0.50 ]);
+    $nspots = 0;
+    translate(token_pitch * [  -2.0,  -1.5 ]) Token_L();
+    translate(token_pitch * [  -2.0,  -0.5 ]) Token_L();
+    translate(token_pitch * [  -2.0,   0.5 ]) Token_L();
+    translate(token_pitch * [  -2.0,   1.5 ]) Token_L();
+    translate(token_pitch * [  -1.0,  -1.5 ]) Token_L();
+    translate(token_pitch * [  -1.0,  -0.5 ]) Token_L();
+    translate(token_pitch * [  -1.0,   0.5 ]) Token_L();
+    translate(token_pitch * [  -1.0,   1.5 ]) Token_L();
+    translate(token_pitch * [   0.0,  -1.5 ]) Token_L();
+    translate(token_pitch * [   0.0,  -0.5 ]) Token_L();
+    translate(token_pitch * [   0.0,   0.5 ]) Token_L();
+    translate(token_pitch * [   0.0,   1.5 ]) Token_L();
+    translate(token_pitch * [   1.0,  -1.5 ]) Token_L();
+    translate(token_pitch * [   1.0,  -0.5 ]) Token_L();
+    translate(token_pitch * [   1.0,   0.5 ]) Token_L();
+    translate(token_pitch * [   1.0,   1.5 ]) Token_L();
+    translate(token_pitch * [   2.0,  -1.5 ]) Token_L();
+    translate(token_pitch * [   2.0,  -0.5 ]) Token_L();
+    translate(token_pitch * [   2.0,   0.5 ]) Token_L();
+    translate(token_pitch * [   2.0,   1.5 ]) Token_L();
+  };
+  union(){
+    Frame($phase, token_pitch * [ 5 + 1.00, 4 + 0.50 ]);
+    $nspots = 1;
+  };
+  union(){
+    Frame($phase, token_pitch * [ 5 + 1.00, 4 + 0.50 ]);
+    $nspots = 2;
+  };
+  union(){
+    Frame($phase, token_pitch * [ 5 + 1.00, 4 + 0.50 ]);
+    $nspots = 3;
+  };
+  union(){
+    Frame($phase, token_pitch * [ 5 + 1.00, 4 + 0.50 ]);
+    $nspots = 4;
+  };
+}
+module Witches_Purple(){ ////toplevel
+// Witches  Purple      total= 8  rowsz=3  nrows=3
+  union(){
+    Frame($phase, token_pitch * [ 3 + 1.00, 3 + 0.50 ]);
+    $nspots = 0;
+  };
+  union(){
+    Frame($phase, token_pitch * [ 3 + 1.00, 3 + 0.50 ]);
+    $nspots = 1;
+    translate(token_pitch * [  -1.0,  -1.0 ]) Token_L();
+    translate(token_pitch * [  -1.0,   0.0 ]) Token_L();
+    translate(token_pitch * [  -1.0,   1.0 ]) Token_L();
+    translate(token_pitch * [   0.0,  -1.0 ]) Token_L();
+    translate(token_pitch * [   0.0,   0.0 ]) Token_L();
+    translate(token_pitch * [   0.0,   1.0 ]) Token_L();
+    translate(token_pitch * [   1.0,  -1.0 ]) Token_L();
+    translate(token_pitch * [   1.0,   0.0 ]) Token_L();
+  };
+  union(){
+    Frame($phase, token_pitch * [ 3 + 1.00, 3 + 0.50 ]);
+    $nspots = 2;
+  };
+  union(){
+    Frame($phase, token_pitch * [ 3 + 1.00, 3 + 0.50 ]);
+    $nspots = 3;
+  };
+  union(){
+    Frame($phase, token_pitch * [ 3 + 1.00, 3 + 0.50 ]);
+    $nspots = 4;
+  };
+}
+module Witches_Red(){ ////toplevel
+// Witches  Red         total=16  rowsz=4  nrows=4
+  union(){
+    Frame($phase, token_pitch * [ 4 + 1.00, 4 + 0.50 ]);
+    $nspots = 0;
+  };
+  union(){
+    Frame($phase, token_pitch * [ 4 + 1.00, 4 + 0.50 ]);
+    $nspots = 1;
+    translate(token_pitch * [  -1.5,  -1.5 ]) Token_L();
+    translate(token_pitch * [  -1.5,  -0.5 ]) Token_L();
+    translate(token_pitch * [  -1.5,   0.5 ]) Token_L();
+    translate(token_pitch * [  -1.5,   1.5 ]) Token_L();
+    translate(token_pitch * [  -0.5,  -1.5 ]) Token_L();
+    translate(token_pitch * [  -0.5,  -0.5 ]) Token_L();
+  };
+  union(){
+    Frame($phase, token_pitch * [ 4 + 1.00, 4 + 0.50 ]);
+    $nspots = 2;
+    translate(token_pitch * [  -0.5,   0.5 ]) Token_L();
+    translate(token_pitch * [  -0.5,   1.5 ]) Token_L();
+    translate(token_pitch * [   0.5,  -1.5 ]) Token_L();
+    translate(token_pitch * [   0.5,  -0.5 ]) Token_L();
+    translate(token_pitch * [   0.5,   0.5 ]) Token_L();
+  };
+  union(){
+    Frame($phase, token_pitch * [ 4 + 1.00, 4 + 0.50 ]);
+    $nspots = 3;
+  };
+  union(){
+    Frame($phase, token_pitch * [ 4 + 1.00, 4 + 0.50 ]);
+    $nspots = 4;
+    translate(token_pitch * [   0.5,   1.5 ]) Token_L();
+    translate(token_pitch * [   1.5,  -1.5 ]) Token_L();
+    translate(token_pitch * [   1.5,  -0.5 ]) Token_L();
+    translate(token_pitch * [   1.5,   0.5 ]) Token_L();
+    translate(token_pitch * [   1.5,   1.5 ]) Token_L();
+  };
+}
+module Witches_White(){ ////toplevel
+// Witches  White       total=11  rowsz=4  nrows=3
+  union(){
+    Frame($phase, token_pitch * [ 4 + 1.00, 3 + 0.50 ]);
+    $nspots = 0;
+  };
+  union(){
+    Frame($phase, token_pitch * [ 4 + 1.00, 3 + 0.50 ]);
+    $nspots = 1;
+    translate(token_pitch * [  -1.5,  -1.0 ]) Token_L();
+    translate(token_pitch * [  -1.5,   0.0 ]) Token_L();
+    translate(token_pitch * [  -1.5,   1.0 ]) Token_L();
+    translate(token_pitch * [  -0.5,  -1.0 ]) Token_L();
+    translate(token_pitch * [  -0.5,   0.0 ]) Token_L();
+    translate(token_pitch * [  -0.5,   1.0 ]) Token_L();
+  };
+  union(){
+    Frame($phase, token_pitch * [ 4 + 1.00, 3 + 0.50 ]);
+    $nspots = 2;
+    translate(token_pitch * [   0.5,  -1.0 ]) Token_L();
+    translate(token_pitch * [   0.5,   0.0 ]) Token_L();
+    translate(token_pitch * [   0.5,   1.0 ]) Token_L();
+  };
+  union(){
+    Frame($phase, token_pitch * [ 4 + 1.00, 3 + 0.50 ]);
+    $nspots = 3;
+    translate(token_pitch * [   1.5,  -1.0 ]) Token_L();
+    translate(token_pitch * [   1.5,   0.0 ]) Token_L();
+  };
+  union(){
+    Frame($phase, token_pitch * [ 4 + 1.00, 3 + 0.50 ]);
+    $nspots = 4;
+  };
+}
+module Witches_Yellow(){ ////toplevel
+// Witches  Yellow      total=16  rowsz=4  nrows=4
+  union(){
+    Frame($phase, token_pitch * [ 4 + 1.00, 4 + 0.50 ]);
+    $nspots = 0;
+  };
+  union(){
+    Frame($phase, token_pitch * [ 4 + 1.00, 4 + 0.50 ]);
+    $nspots = 1;
+    translate(token_pitch * [  -1.5,  -1.5 ]) Token_L();
+    translate(token_pitch * [  -1.5,  -0.5 ]) Token_L();
+    translate(token_pitch * [  -1.5,   0.5 ]) Token_L();
+    translate(token_pitch * [  -1.5,   1.5 ]) Token_L();
+    translate(token_pitch * [  -0.5,  -1.5 ]) Token_L();
+    translate(token_pitch * [  -0.5,  -0.5 ]) Token_L();
+  };
+  union(){
+    Frame($phase, token_pitch * [ 4 + 1.00, 4 + 0.50 ]);
+    $nspots = 2;
+    translate(token_pitch * [  -0.5,   0.5 ]) Token_L();
+    translate(token_pitch * [  -0.5,   1.5 ]) Token_L();
+    translate(token_pitch * [   0.5,  -1.5 ]) Token_L();
+    translate(token_pitch * [   0.5,  -0.5 ]) Token_L();
+    translate(token_pitch * [   0.5,   0.5 ]) Token_L();
+  };
+  union(){
+    Frame($phase, token_pitch * [ 4 + 1.00, 4 + 0.50 ]);
+    $nspots = 3;
+  };
+  union(){
+    Frame($phase, token_pitch * [ 4 + 1.00, 4 + 0.50 ]);
+    $nspots = 4;
+    translate(token_pitch * [   0.5,   1.5 ]) Token_L();
+    translate(token_pitch * [   1.5,  -1.5 ]) Token_L();
+    translate(token_pitch * [   1.5,  -0.5 ]) Token_L();
+    translate(token_pitch * [   1.5,   0.5 ]) Token_L();
+    translate(token_pitch * [   1.5,   1.5 ]) Token_L();
+  };
+}
+// Witches  total_count=154   total_real_count=134
+// Witches  max_rowsz=5       max_nrows=5
diff --git a/quacks-ingredients-demos.scad b/quacks-ingredients-demos.scad
new file mode 100644 (file)
index 0000000..1f02c60
--- /dev/null
@@ -0,0 +1,39 @@
+// -*- C -*-
+
+include <quacks-ingredients.scad>
+
+sandingframe_gap = 0.3; // to be added to radius
+sandingframe_nw = 5;
+sandingframe_nl = 6;
+sandingframe_th = thick*2/3;
+
+module Demo(){ ////toplevel
+  $nspots = 3;
+  color("red") { Token_L3(); }
+  color("white") { Token_L1(); Token_L5(); }
+  color("black") { Token_L2(); Token_L4(); }
+}
+
+module SandingFrame(){ ////toplevel
+  stridel = token_dia + 5;
+  nl = sandingframe_nl;
+  nw = sandingframe_nw;
+  stridew = stridel * cos(30);
+  stride = [stridel,stridew];
+
+  linear_extrude(height = sandingframe_th) {
+    difference(){
+      translate((token_dia/2 + stridel) * 0.5 * [-1,-1])
+       square([ stridel * (nl + 0.5),
+                stridew * nw + stridel * 0.5 ]);
+      for (j = [0 : nw-1]) {
+       eo = j % 2;
+       for (i = [0 : nl-1-0.5*eo]) {
+         translate([stridel * (i + 0.5 * eo),
+                    stridew * j])
+           circle(r = token_dia/2 + sandingframe_gap);
+       }
+      }
+    }
+  }
+}
diff --git a/quacks-ingredients-make-copy-gcodes b/quacks-ingredients-make-copy-gcodes
new file mode 100755 (executable)
index 0000000..fd34367
--- /dev/null
@@ -0,0 +1,135 @@
+#!/bin/bash
+#
+# usage:
+#    ./quacks-ingredients-make-copy-gcodes Tests_L
+# Uses
+#    quacks-ingredients-L$l,Tests_L.auto.stl
+# to make
+#    quacks-ingredients-L$l,Tests_L.auto.gcode
+# and then edits them and copies them to the SD card as
+#    PREPARED/Q$l.G
+
+set -e
+
+f=$1
+shift
+
+lhs=quacks-ingredients-L
+
+gh () {
+       g=$lhs$l.auto.gcode
+       h=/media/sd/PREPARED/Q$l.G
+}
+
+for l in 1 2 3 4 5; do
+       gh
+       qi=quacks-L$l.auto.ini
+
+       cp quacks.ini $qi
+
+       case $l in
+       2|4|5)
+               perl -i~ -pe '
+                       s/^(retraction_hop *= *.*)/retraction_hop = 0.6/m
+               ' $qi
+               ;;
+       esac
+
+       cura -i $qi -s $lhs$l,$f.auto.stl -o $g
+       case $l in
+       1|2|3|4)
+               perl -i~ -pe 's/^/;/ if m/^M140 S0\b.*\n/' $g
+               ;;
+       esac
+
+       perl -i~ -ne '
+               $l =~ s/^/;/ if m/^M400/;
+               $l .= "G91\nG1 Z5\nG90\n" if m/^M84/;
+               print $l or die $!;
+               $l = $_;
+               END { print $l or die $!; }
+       ' $g
+done
+
+for l in 2 4 5; do
+       gh
+       : perl -i~ -0777 -ne '
+               @l = split m{^(?=;LAYER:\d+\n)}m, $_;
+               foreach my $i (0..$#l) {
+                       $l[$i] =~
+                               s{
+                               (       ^G1 \ Z\S+ \s*\n        
+                                 |     ^\;LAYER:\d+ \s*\n
+                                   (?: ^M10[67].* \s*\n )?
+                               )
+                                       ^G0 \ F(\d+) \ ( X\S+ \ Y\S+ )
+                                                        \ Z(\S+) \s*\n
+                               }{
+                                       die "$& $2" unless $2 > 9000;
+                                       $1 .
+                                       "G1 Z$4\n".
+                                       "G0 F$2 $3\n".
+                                       "G1 Z$4\n"
+                               }mxe    or $i==0
+                                       or die "$l[$i] $i";
+                       $l[$i] =~
+                               s{
+                                       ^G1 \ Z([0-9.]+) \s*\n
+                               (       ^G0 \ F(\d+) \ X\S+ \ Y\S+ \s*\n
+                                       (?: ; .* \s*\n )?
+                                       ^G1 \ Z([0-9.]+) \s*\n  )
+                               }{
+                                       die "$& $3" unless $1 >= $4;
+                                       die "$& $3" unless $3 > 9000;
+                                       my $z = $i == $#l ? $1 : $4 + 0.5;
+                                       "G0 F$3\n".
+                                       "G1 Z$z\n" .
+                                       $2
+                               }gmxe or $i==0 or die "$l[$i] $i";
+               }
+               print or die $! foreach @l;
+       ' $g
+done
+
+exec 3>${lhs}234.auto.gcode
+for l in 2 3; do
+       gh
+       perl -pe 's/^/;/ if m/^M104 S0\b/ || (m/^M84/..0)' $g >&3
+done
+for l in 4; do
+       gh
+       cat $g >&3
+done
+
+copies="3:5 2:234 1:1"
+copyls=""
+for copy in $copies; do
+       l=-P-${copy%:*}
+       ci=$lhs${copy#*:}.auto.gcode
+       copyls+=" $l"
+       gh
+       rm -f $g
+       ln -vs $ci $g
+done
+
+umount /media/sd >/dev/null 2>&1 ||:
+mount /media/sd
+
+for l in 1 2 3 4 5 234 $copyls; do
+       gh
+       cp $g $h
+done
+
+sleep 0.5
+
+umount /media/sd
+mount /media/sd
+
+for l in 1 2 3 4 5 234 $copyls; do
+       gh
+       cmp $g $h
+       ls -l $h
+done
+sleep 0.5
+
+umount /media/sd
diff --git a/quacks-ingredients-update-levels b/quacks-ingredients-update-levels
new file mode 100755 (executable)
index 0000000..395770d
--- /dev/null
@@ -0,0 +1,28 @@
+#!/bin/sh
+set -e
+
+for l in 1 2 3 4 5; do
+       f=quacks-ingredients-L$l.scad
+       cat >$f.tmp <<END
+//  autogenerated by quacks-ingredients-updates-levels - do not edit
+\$phase=$l;
+module Token_L(){ Token_L$l(); }
+//// toplevels-from:
+include <quacks-ingredients.scad>
+END
+       mv -f $f.tmp $f
+done
+
+f=quacks-ingredients-counts.scad
+cat >$f.tmp <<END
+//  autogenerated - do not edit
+//   update script is quacks-ingredients-updates-levels
+//   source is quacks-ingredients-counts
+END
+
+for which in Base All Witches; do
+       ./quacks-ingredients-counts $which >>$f.tmp
+done
+mv -f $f.tmp $f
+
+egrep '^// [A-Z][a-z]*  *[A-Za-z]' $f | sort
diff --git a/quacks-ingredients.scad b/quacks-ingredients.scad
new file mode 100644 (file)
index 0000000..6925a1b
--- /dev/null
@@ -0,0 +1,145 @@
+// -*- C -*-
+
+//
+// git clean -xdff
+// ./quacks-ingredients-update-levels
+// make autoincs
+//
+// make -j8 quacks-stls 2>&1 | tee log
+// OR EG
+// make -j8 quacks-ingredients-L{1,2,3,4,5},Base_Yellow.auto.stl
+//
+// ./quacks-ingredients-make-copy-gcodes Base_Yellow   etc.
+//
+//   Print Q-P-1 in spots, Q-P-2 in main colour, Q-P-3 in spots
+//
+// For colours which only have zero-spot counters, print only Q-P-2
+
+token_dia = 20;
+spot_dia = 4.3;
+spot_gap = spot_dia / 3.0;
+
+thick = 3.0;
+
+multicolour_gap = 0.075; // each side
+initial_layer_thick = 0.400;
+initial_layer_width = 0.750;
+final_layer_thick = 0.500;
+multicolour_post = 4;
+
+$fs=0.1;
+$fa=1;
+
+// calculated
+
+token_pitch = token_dia + 3;
+
+// autoadjusted
+
+$spots_absent = false;
+$spots_plusgap = false;
+
+module Spots_Extrude_Lower(){
+  d = $spots_plusgap ? 1 : 0;
+  translate([0,0,-d])
+    linear_extrude(height= initial_layer_thick + d)
+    children(0);
+}
+
+module Spots_Extrude_Upper(){
+  d = $spots_plusgap ? 1 : 0;
+  translate([0,0, thick + d])
+    mirror([0,0, 1])
+    linear_extrude(height= final_layer_thick + d)
+    children(0);
+}
+
+module SpotAt(condition, xy) {
+  if (condition == !$spots_absent) {
+    translate(xy * (spot_gap + spot_dia) * sqrt(0.5))
+      circle(r= spot_dia/2 +
+            ($spots_plusgap ? multicolour_gap : 0));
+  }
+}
+
+module Token_Spots(){
+  SpotAt(($nspots % 2) > 0,  [0,0]);
+  SpotAt($nspots >= 2, [ 1, 1]);
+  SpotAt($nspots >= 2, [-1,-1]);
+  SpotAt($nspots >= 4, [ 1,-1]);
+  SpotAt($nspots >= 4, [-1, 1]);
+}
+
+module Token_Spots_All(){
+  $nspots = 5;
+  Token_Spots();
+}
+
+module Token_L1(){
+  Spots_Extrude_Lower()
+    Token_Spots();
+}
+
+module Token_L2(){
+  $spots_absent = true;
+  Spots_Extrude_Lower()
+    Token_Spots();
+}
+
+module Token_L3(){
+  $spots_plusgap = true;
+  difference(){
+    linear_extrude(height=thick)
+      circle(r=token_dia/2);
+    Spots_Extrude_Lower() Token_Spots_All();
+    Spots_Extrude_Upper() Token_Spots_All();
+  }
+}
+
+module Token_L4(){
+  $spots_absent = true;
+  Spots_Extrude_Upper()
+    Token_Spots();
+}
+
+module Token_L5(){
+  Spots_Extrude_Upper()
+    Token_Spots();
+}
+
+module Frame(phase, base_sz) {
+  zs = [ initial_layer_thick,
+        initial_layer_thick,
+        thick,
+        thick,
+        thick ];
+
+  sz = base_sz + phase * initial_layer_width * 2 * [1,1];
+  linear_extrude(height= initial_layer_thick) {
+    difference(){
+      square(center=true, sz + initial_layer_width * 2 * [1,1]);
+      square(center=true, sz);
+    }
+  }
+  // Priming tower
+  translate([-base_sz[0]/2, (2.8-phase)*(multicolour_post*1.7)])
+    linear_extrude(height= zs[phase-1])
+    square(multicolour_post);
+}
+
+module Tests(){
+  for ($nspots = [1,2,3,4]) {
+    translate(($nspots - 2) * token_pitch * [1,0])
+      children();
+  }
+}
+
+module Tests_L() { ////toplevel
+  Frame($phase, token_dia * [ 6, 1.5 ]);
+  Tests() Token_L();
+}
+
+//// toplevels-from:
+include <quacks-ingredients-counts.scad>
+
+//Demo();
diff --git a/quacks.ini b/quacks.ini
new file mode 100644 (file)
index 0000000..8a4f7cc
--- /dev/null
@@ -0,0 +1,266 @@
+[profile]
+layer_height = 0.14
+wall_thickness = 1
+retraction_enable = True
+solid_layer_thickness = 1
+fill_density = 20
+perimeter_before_infill = True
+nozzle_size = 0.5
+print_speed = 50
+print_temperature = 0
+print_temperature2 = 0
+print_temperature3 = 0
+print_temperature4 = 0
+print_temperature5 = 0
+print_bed_temperature = 0
+support = None
+platform_adhesion = None
+support_dual_extrusion = Both
+wipe_tower = False
+wipe_tower_volume = 15
+ooze_shield = False
+filament_diameter = 2.85
+filament_diameter2 = 0
+filament_diameter3 = 0
+filament_diameter4 = 0
+filament_diameter5 = 0
+filament_flow = 100.0
+retraction_speed = 10
+retraction_amount = 1.5
+retraction_dual_amount = 16.5
+retraction_min_travel = 1.5
+retraction_combing = All
+retraction_minimal_extrusion = 0.005
+retraction_hop = 0.1
+bottom_thickness = 0.425
+layer0_width_factor = 125
+object_sink = 0.0
+overlap_dual = 0.15
+travel_speed = 175
+bottom_layer_speed = 15
+infill_speed = 40
+solidarea_speed = 30
+inset0_speed = 30
+insetx_speed = 35
+fill_angle = 45
+cool_min_layer_time = 20
+fan_enabled = True
+skirt_line_count = 1
+skirt_gap = 3.0
+skirt_minimal_length = 250
+fan_full_height = 0.5
+fan_speed = 75
+fan_speed_max = 100
+cool_min_feedrate = 10
+cool_head_lift = False
+solid_top = True
+solid_bottom = True
+fill_overlap = 15
+support_type = Lines
+support_angle = 60
+support_fill_rate = 30
+support_xy_distance = 1.5
+support_z_distance = 0.1
+spiralize = False
+simple_mode = False
+brim_line_count = 10
+raft_margin = 5.0
+raft_line_spacing = 3.0
+raft_base_thickness = 0.3
+raft_base_linewidth = 1.0
+raft_interface_thickness = 0.27
+raft_interface_linewidth = 0.4
+raft_airgap_all = 0.0
+raft_airgap = 0.5
+raft_surface_layers = 2
+raft_surface_thickness = 0.27
+raft_surface_linewidth = 0.4
+fix_horrible_union_all_type_a = True
+fix_horrible_union_all_type_b = False
+fix_horrible_use_open_bits = False
+fix_horrible_extensive_stitching = False
+plugin_config = 
+object_center_x = -1
+object_center_y = -1
+simplemodesettings = 
+simplemodeprofile = Standard
+simplemodematerial = PLA
+simplemodematerialtype = Beginner
+
+[alterations]
+start.gcode = ;Sliced at: {day} {date} {time}
+       ;Basic settings: Layer height: {layer_height} Walls: {wall_thickness} Fill: {fill_density}
+       ;Print time: {print_time}
+       ;Filament used: {filament_amount}m {filament_weight}g
+       ;Filament cost: {filament_cost}
+       ;M190 R{print_bed_temperature} ;Uncomment to add your own bed temperature line
+       ;M109 R{print_temperature} ;Uncomment to add your own temperature line
+       G21                     ;metric values
+       G90                     ;absolute positioning
+       M82                     ;set extruder to absolute mode
+       M107                    ;start with the fan off
+       G28 X0 Y0               ;move X/Y to min endstops
+       G28 Z0                  ;move Z to min endstops
+       G1 Z15.0 F{travel_speed};move the platform down 15mm
+       G92 E0                  ; zero the extruded length
+       G1 F200 E0              ; extrude 3mm of feed stock
+       G92 E0                  ; zero the extruded length again
+       G1 F{travel_speed}      ; set travel speed
+       M203 X192 Y208 Z3       ; speed limits
+       M117 Printing...        ; send message to LCD
+end.gcode = M400
+       M104 S0                        ; hotend off
+       M140 S0                        ; heated bed heater off (if you have it)
+       M107                           ; fans off
+       G91                            ; relative positioning
+       G1 E-1 F300                    ; retract the filament a bit before lifting the nozzle, to release some of the pressure
+       G1 Z+0.5 E-5 X-20 Y-20 F3000   ; move Z up a bit and retract filament even more
+       G90                            ; absolute positioning
+       G1 X0 Y250                     ; move to cooling position
+       M84                            ; steppers off
+       G90                            ; absolute positioning
+       M117 TAZ Ready.
+       ;{profile_string}
+start2.gcode = ;Sliced at: {day} {date} {time}
+       ;Basic settings: Layer height: {layer_height} Walls: {wall_thickness} Fill: {fill_density}
+       ;Print time: {print_time}
+       ;Filament used: {filament_amount}m {filament_weight}g
+       ;Filament cost: {filament_cost}
+       ;M190 R{print_bed_temperature} ;Uncomment to add your own bed temperature line
+       ;M104 S{print_temperature} ;Uncomment to add your own temperature line
+       ;M109 T1 S{print_temperature2} ;Uncomment to add your own temperature line
+       ;M109 T0 S{print_temperature} ;Uncomment to add your own temperature line
+       G21        ;metric values
+       G90        ;absolute positioning
+       M107       ;start with the fan off
+       G28 X0 Y0  ;move X/Y to min endstops
+       G28 Z0     ;move Z to min endstops
+       G1 Z15.0 F{travel_speed} ;move the platform down 15mm
+       T1                      ;Switch to the 2nd extruder
+       G92 E0                  ;zero the extruded length
+       G1 F200 E10             ;extrude 10mm of feed stock
+       G92 E0                  ;zero the extruded length again
+       G1 F200 E-{retraction_dual_amount}
+       T0                      ;Switch to the first extruder
+       G92 E0                  ;zero the extruded length
+       G1 F200 E10             ;extrude 10mm of feed stock
+       G92 E0                  ;zero the extruded length again
+       G1 F{travel_speed}
+       ;Put printing message on LCD screen
+       M117 Printing...
+end2.gcode = ;End GCode
+       M104 T0 S0                     ;extruder heater off
+       M104 T1 S0                     ;extruder heater off
+       M140 S0                     ;heated bed heater off (if you have it)
+       G91                                    ;relative positioning
+       G1 E-1 F300                            ;retract the filament a bit before lifting the nozzle, to release some of the pressure
+       G1 Z+0.5 E-5 X-20 Y-20 F{travel_speed} ;move Z up a bit and retract filament even more
+       G28 X0 Y0                              ;move X/Y to min endstops, so the head is out of the way
+       M84                         ;steppers off
+       G90                         ;absolute positioning
+       ;{profile_string}
+start3.gcode = ;Sliced at: {day} {date} {time}
+       ;Basic settings: Layer height: {layer_height} Walls: {wall_thickness} Fill: {fill_density}
+       ;Print time: {print_time}
+       ;Filament used: {filament_amount}m {filament_weight}g
+       ;Filament cost: {filament_cost}
+       ;M190 R{print_bed_temperature} ;Uncomment to add your own bed temperature line
+       ;M104 S{print_temperature} ;Uncomment to add your own temperature line
+       ;M109 T1 S{print_temperature2} ;Uncomment to add your own temperature line
+       ;M109 T0 S{print_temperature} ;Uncomment to add your own temperature line
+       G21        ;metric values
+       G90        ;absolute positioning
+       M107       ;start with the fan off
+       G28 X0 Y0  ;move X/Y to min endstops
+       G28 Z0     ;move Z to min endstops
+       G1 Z15.0 F{travel_speed} ;move the platform down 15mm
+       T2                      ;Switch to the 2nd extruder
+       G92 E0                  ;zero the extruded length
+       G1 F200 E10             ;extrude 10mm of feed stock
+       G92 E0                  ;zero the extruded length again
+       G1 F200 E-{retraction_dual_amount}
+       T1                      ;Switch to the 2nd extruder
+       G92 E0                  ;zero the extruded length
+       G1 F200 E10             ;extrude 10mm of feed stock
+       G92 E0                  ;zero the extruded length again
+       G1 F200 E-{retraction_dual_amount}
+       T0                      ;Switch to the first extruder
+       G92 E0                  ;zero the extruded length
+       G1 F200 E10             ;extrude 10mm of feed stock
+       G92 E0                  ;zero the extruded length again
+       G1 F{travel_speed}
+       ;Put printing message on LCD screen
+       M117 Printing...
+end3.gcode = ;End GCode
+       M104 T0 S0                     ;extruder heater off
+       M104 T1 S0                     ;extruder heater off
+       M104 T2 S0                     ;extruder heater off
+       M140 S0                     ;heated bed heater off (if you have it)
+       G91                                    ;relative positioning
+       G1 E-1 F300                            ;retract the filament a bit before lifting the nozzle, to release some of the pressure
+       G1 Z+0.5 E-5 X-20 Y-20 F{travel_speed} ;move Z up a bit and retract filament even more
+       G28 X0 Y0                              ;move X/Y to min endstops, so the head is out of the way
+       M84                         ;steppers off
+       G90                         ;absolute positioning
+       ;{profile_string}
+start4.gcode = ;Sliced at: {day} {date} {time}
+       ;Basic settings: Layer height: {layer_height} Walls: {wall_thickness} Fill: {fill_density}
+       ;Print time: {print_time}
+       ;Filament used: {filament_amount}m {filament_weight}g
+       ;Filament cost: {filament_cost}
+       ;M190 R{print_bed_temperature} ;Uncomment to add your own bed temperature line
+       ;M104 S{print_temperature} ;Uncomment to add your own temperature line
+       ;M109 T2 S{print_temperature2} ;Uncomment to add your own temperature line
+       ;M109 T1 S{print_temperature2} ;Uncomment to add your own temperature line
+       ;M109 T0 S{print_temperature} ;Uncomment to add your own temperature line
+       G21        ;metric values
+       G90        ;absolute positioning
+       M107       ;start with the fan off
+       G28 X0 Y0  ;move X/Y to min endstops
+       G28 Z0     ;move Z to min endstops
+       G1 Z15.0 F{travel_speed} ;move the platform down 15mm
+       T3                      ;Switch to the 4th extruder
+       G92 E0                  ;zero the extruded length
+       G1 F200 E10             ;extrude 10mm of feed stock
+       G92 E0                  ;zero the extruded length again
+       G1 F200 E-{retraction_dual_amount}
+       T2                      ;Switch to the 3th extruder
+       G92 E0                  ;zero the extruded length
+       G1 F200 E10             ;extrude 10mm of feed stock
+       G92 E0                  ;zero the extruded length again
+       G1 F200 E-{retraction_dual_amount}
+       T1                      ;Switch to the 2nd extruder
+       G92 E0                  ;zero the extruded length
+       G1 F200 E10             ;extrude 10mm of feed stock
+       G92 E0                  ;zero the extruded length again
+       G1 F200 E-{retraction_dual_amount}
+       T0                      ;Switch to the first extruder
+       G92 E0                  ;zero the extruded length
+       G1 F200 E10             ;extrude 10mm of feed stock
+       G92 E0                  ;zero the extruded length again
+       G1 F{travel_speed}
+       ;Put printing message on LCD screen
+       M117 Printing...
+end4.gcode = ;End GCode
+       M104 T0 S0                     ;extruder heater off
+       M104 T1 S0                     ;extruder heater off
+       M104 T2 S0                     ;extruder heater off
+       M104 T3 S0                     ;extruder heater off
+       M140 S0                     ;heated bed heater off (if you have it)
+       G91                                    ;relative positioning
+       G1 E-1 F300                            ;retract the filament a bit before lifting the nozzle, to release some of the pressure
+       G1 Z+0.5 E-5 X-20 Y-20 F{travel_speed} ;move Z up a bit and retract filament even more
+       G28 X0 Y0                              ;move X/Y to min endstops, so the head is out of the way
+       M84                         ;steppers off
+       G90                         ;absolute positioning
+       ;{profile_string}
+support_start.gcode = 
+support_end.gcode = 
+cool_start.gcode = 
+cool_end.gcode = 
+replace.csv = 
+preswitchextruder.gcode = ;Switch between the current extruder and the next extruder, when printing with multiple extruders.
+       ;This code is added before the T(n)
+postswitchextruder.gcode = ;Switch between the current extruder and the next extruder, when printing with multiple extruders.
+       ;This code is added after the T(n)
+
diff --git a/question-question.fig b/question-question.fig
new file mode 100644 (file)
index 0000000..6d4e5b9
--- /dev/null
@@ -0,0 +1,11 @@
+#FIG 3.2  Produced by xfig version 3.2.6a
+Landscape
+Center
+Metric
+A4
+100.00
+Single
+-2
+1200 2
+1 3 0 1 0 7 50 -1 -1 0.000 1 0.0000 3780 3870 675 675 3780 3870 4455 3870
+4 0 0 34 -1 14 64 0.0000 4 645 645 3465 4185 ?\001
diff --git a/question-token.scad b/question-token.scad
new file mode 100644 (file)
index 0000000..2bd7bc6
--- /dev/null
@@ -0,0 +1,25 @@
+// -*- C -*-
+
+tokenrad=13;
+tokenthick=1.9;
+
+joinwidth=1.0;
+
+circlerad=15;
+
+module Letter() {
+  translate([-circlerad,-circlerad])
+    import("question-question.dxf", convexity=100);
+}
+
+module Token() { ////toplevel
+  rotate([0,180,0])
+  linear_extrude(height=tokenthick) union(){
+    difference(){
+      %circle(tokenrad);
+      Letter();
+    }
+  }
+}
+
+Token();
diff --git a/ring-tests.scad b/ring-tests.scad
new file mode 100644 (file)
index 0000000..c3bd34f
--- /dev/null
@@ -0,0 +1,43 @@
+// -*- C -*-
+
+thick = 3.5;
+width = 5;
+
+centre_dia = 21;
+
+// calculated
+
+ellipse_width = width / 0.90;
+
+module Profile(){
+  intersection(){
+    scale([thick, ellipse_width]) circle(r=0.5, $fn=50);
+    square([thick*2, width], center=true);
+  }
+}
+
+module Ring(dia){
+  rotate_extrude($fn=100){
+    translate([dia/2 + thick/2, 0]){
+      Profile();
+    }
+  }
+}
+
+//Profile();
+//Ring(centre_dia);
+
+module Tests(){
+  min_dia = 19;
+  max_dia = 22;
+  dia_step = .25;
+  n_dias = (max_dia - min_dia)/dia_step;
+  for (i= [ 0 : n_dias ]) {
+    echo(i);
+    rotate([0,0, i*360 / (n_dias+2)])
+      translate([80,0,0])
+      Ring(min_dia + (max_dia-min_dia)*(i/n_dias));
+  }
+}
+
+Tests();
diff --git a/rope-adjuster.scad b/rope-adjuster.scad
new file mode 100644 (file)
index 0000000..fa4f591
--- /dev/null
@@ -0,0 +1,77 @@
+// -*- C -*-
+
+include <utils.scad>
+
+hole_dia = 10;
+around_hole = 4;
+thick = 3;
+lever_len = 50;
+
+teeth_n = 4;
+teeth_bite = 4;
+teeth_pitch = 4;
+teeth_gap = 3;
+teeth_back = 1;
+teeth_height = 12;
+teeth_chamfer = 3;
+
+// calculated
+
+teeth_x_mid = lever_len/2 - hole_dia/2 - teeth_bite - teeth_gap*1.5;
+teeth_height_total = teeth_height + teeth_chamfer;
+
+module Circles(r) {
+  for (x = [-1,+1] * 0.5 * lever_len) {
+    translate([x, 0])
+      circle(r);
+  }
+}
+
+module Plan() {
+  difference(){
+    hull(){
+      Circles(hole_dia/2 + around_hole);
+    }
+    Circles(hole_dia/2);
+    translate([ teeth_x_mid - teeth_bite - teeth_back - teeth_gap - hole_dia/2,
+               0 ])
+      circle(hole_dia/2);
+  }
+}
+
+module TeethPlan(){
+  translate([
+            teeth_x_mid,
+            -0.5 * teeth_n * teeth_pitch,
+            ]) {
+    for (m=[0,1]) {
+      mirror([m,0]) {
+       translate([ teeth_bite + teeth_gap/2, 0 ])
+         rectfromto([ -0.1, 0 ],
+                    [ teeth_back, teeth_n * teeth_pitch ]);
+       for (i= [ 0: teeth_n-1 ]) {
+         translate([teeth_gap/2, teeth_pitch*i])
+           polygon([[ 0,0 ],
+                    [ teeth_bite, 0 ],
+                    [ teeth_bite, teeth_pitch ]]);
+       }
+      }
+    }
+  }
+}
+
+module Adjuster(){
+  linextr(0,thick)
+    Plan();
+  difference(){
+    linextr(thick - 0.1, thick + teeth_height_total)
+      TeethPlan();
+    translate([ teeth_x_mid, 0, thick + teeth_height_total + 1])
+      linextr_y_xz(-teeth_pitch * teeth_n,
+                +teeth_pitch * teeth_n)
+      rotate(45)
+      square(sqrt(2) * (teeth_gap/2 + teeth_chamfer + 1), center=true);
+  }
+}
+
+Adjuster();
diff --git a/rpi-mount.scad b/rpi-mount.scad
new file mode 100644 (file)
index 0000000..b1b9c12
--- /dev/null
@@ -0,0 +1,46 @@
+// -*- C -*-
+
+include <utils.scad>
+
+pi_board_gap = 0.5;
+pi_board_support_z = 2;
+pi_board_support_wall = 2;
+
+pi_sz   = [ 66.0, 30.5 ] + pi_board_gap * [1,1];
+
+pi_nom_sz = [ 65, 30 ];
+pi_nom_centres_in = 3.5;
+pi_solder_side_gap = 1.5 * 2;
+pi_screw_hole_dia  = 2.3;
+pi_screw_hole_wall = 2.3;
+
+pi_sz_z_incl_ribbon = 18.0;
+
+pi_mount_z_min = 1.75;
+pi_mount_z_def = 2.50;
+
+// calculated, output
+
+function pi_ribbon_top_z(pi_mount_z= pi_mount_z_def)
+  = pi_mount_z + pi_sz_z_incl_ribbon;
+
+module PiMount(pi_mount_z= pi_mount_z_def){
+  sxy = pi_nom_sz/2 - [1,1] * pi_nom_centres_in;
+  for (mx=[0,1]) mirror([mx,0,0]) for (my=[0,1]) mirror([0,my,0]) {
+    difference(){
+      union(){
+       linextr(-0.1, pi_mount_z + pi_board_support_z, convexity=1)
+         rectfromto( pi_nom_sz/2 - 2 * [1,1] * pi_nom_centres_in,
+                     pi_sz/2 + [1,1] * pi_board_support_wall);
+       linextr(-0.1, pi_mount_z - pi_solder_side_gap, convexity=1)
+         translate(sxy)
+         square(center=true, pi_screw_hole_dia + pi_screw_hole_wall*2);
+      }
+      linextr(pi_mount_z, pi_mount_z + 5, convexity=1)
+       rectfromto(-[10,10], pi_sz/2);
+      translate( sxy )
+       linextr(-1, pi_mount_z + 10, convexity=1)
+       circle(r= pi_screw_hole_dia/2);
+    }
+  }
+}
diff --git a/salter-scale-hook.scad b/salter-scale-hook.scad
new file mode 100644 (file)
index 0000000..3a8cfa3
--- /dev/null
@@ -0,0 +1,108 @@
+// -*- C -*-
+
+include <utils.scad>
+
+rod_dia = 8+2;
+thick = 8;
+screw_dia = 3.5 + 0.75;
+screw_head_dia = 8.2 + 1.0;
+rod_offset = 14 + 2;
+mainheight = 25;
+width = 40;
+rearthick = 4;
+screw_head_depth = 2;
+
+// calculated
+
+d = rod_dia/2 + thick/2;
+yminc = -d;
+ymin = yminc-thick/2;
+ymaxc = mainheight;
+ymax = mainheight+thick/2;
+
+cutdepth = rod_offset - rod_dia/2 - rearthick;
+
+cut_z0 = screw_head_dia/2;
+cut_z1 = width/2 - rearthick;
+
+cutslopez = cutdepth * 0.5;
+
+module C() {
+  circle(r = thick/2, $fn=30);
+}
+
+module Profile() {
+  e = rod_offset;
+  hull(){
+    translate([-d, 0]) C();
+    translate([-d,-d]) C();
+  }
+  difference(){
+    rectfromto([-d,ymin], [e,0]);
+    circle(r= rod_dia/2, $fn=50);
+  }
+  hull(){
+    for (y= [-d, +mainheight]) {
+      translate([d, y]) C();
+      rectfromto([d, y-thick/2], [e, y+thick/2]);
+    }
+  }
+}
+
+module CutProfile(){
+  hull(){
+    for (x = [rod_dia/2 + thick/2, 30]) {
+      for (y= [yminc,ymaxc] ) {
+       translate([x,y]) circle(r = (thick-rearthick)/2, $fn=20);
+      }
+    }
+  }
+}
+
+module ProfileDemo(){
+  Profile();
+  color("red") translate([0,0,1]) CutProfile();
+}
+
+module Cut(less){
+  translate([0,0, cut_z0 + less])
+    linear_extrude(height = cut_z1 - cut_z0 - less*2)
+    CutProfile();
+}
+
+module ScrewHole(){
+  xd =  (screw_head_dia-screw_dia)/2;
+  translate([0,0,-50])
+    cylinder(h=100, r= screw_dia/2, $fn=20);
+  hull(){
+    translate([0,0,-xd])
+      cylinder(h=1, r= screw_dia/2, $fn=50);
+    cylinder(h=20, r= screw_head_dia/2, $fn=50);
+  }
+}
+
+module Hook(){
+  difference(){
+    translate([0,0, -width/2])
+      linear_extrude(height=width) Profile();
+
+    for (m=[0,1]) {
+      mirror([0,0,m]) {
+       hull(){
+         Cut(cutslopez);
+         translate([cutdepth,0,0]) Cut(0);
+       }
+      }
+    }
+
+    translate([rod_dia/2 + screw_head_depth,
+              ymaxc - screw_head_dia,
+              0]) {
+      rotate([0,-90,0])
+       ScrewHole();
+    }
+  }
+}
+
+//ProfileDemo();
+Hook();
diff --git a/scaffold-clamp-cleat.scad b/scaffold-clamp-cleat.scad
new file mode 100644 (file)
index 0000000..69e8538
--- /dev/null
@@ -0,0 +1,13 @@
+// -*- C -*-
+
+// Per cleat print
+//     VCleatA        OR    VCleatA
+//     GeneralB             VCleatA
+//     Pin                  Pin
+//
+// These bits are compatible with scaffold-clamp-tensioner
+
+//// toplevels-from:
+include <scaffold-clamp-common.scad>
+
+module DemoA(){ VCleatARaw(); }
diff --git a/scaffold-clamp-common.scad b/scaffold-clamp-common.scad
new file mode 100644 (file)
index 0000000..79ed1da
--- /dev/null
@@ -0,0 +1,489 @@
+// -*- C -*-
+
+include <utils.scad>
+
+tube_dia = 48.3;
+
+th = 7;
+
+pin_gap = 1.5; // around
+
+smooth_r = 15;
+
+bolt_dia = 5;
+bolt_flat = 10 + 1;
+
+nbolts = 2;
+
+open_gap = 10;
+
+hinge_unit = 10;
+hinge_z_gap = 1;
+
+hinge_units = 4;
+
+bolt_gap = 1.0; // total, on both sides
+
+// ---------- vhook ----------
+
+vhook_th = 14;
+
+// ---------- cleat ----------
+
+cleat_frames = 10;
+cleat_curve_r = 200;
+cleat_horn_l = 40;
+cleat_horn_d_min = [10, 12];
+cleat_horn_d_max = [12, 14];
+cleat_height = 25;
+cleat_stem_l = 20;
+
+cleat_overlap = (1-cos(60));
+
+// ---------- hhook ----------
+
+hhook_inside = 40;
+hhook_th = 4;
+hhook_l = 40;
+
+// ---------- linear bracket ----------
+
+linear_bracket_h = 50;
+linear_bracket_l = 100;
+linear_bracket_t = 15;
+linear_bracket_hole_offset = 20;
+linear_bracket_hole_dia = 5 + 1.00;
+
+// ========== defaults ==========
+
+pin_head_th = th/2;
+pin_dia = th;
+pin_hole_dia = pin_dia/2;
+pin_tail = pin_hole_dia + pin_head_th + hinge_z_gap*3;
+
+// ========== calculated ==========
+
+TAU = PI*2;
+
+hole_dia = th + pin_gap;
+
+bolt_hole_r = (bolt_dia + bolt_gap)/2;
+
+main_r = tube_dia/2 + th;
+
+hinge_gap = pin_gap;
+
+hinge_o_r = 0.5 * hole_dia + th;
+
+hinge_x = -0.5 * tube_dia - hinge_o_r;
+bolt_x = 0.5 * tube_dia + th + bolt_flat * 0.5;
+max_x = bolt_x + max(bolt_hole_r + th, 0.5 * bolt_flat/2);
+
+flats_y = open_gap/2 + th;
+
+stride_z = hinge_unit*2 + hinge_z_gap*2;
+total_z = hinge_units * stride_z - hinge_z_gap;
+
+min_z = -total_z/2;
+max_z = +total_z/2;
+
+pin_flatten = pin_dia/2 * (1 - cos(45));
+
+bolt_stride = total_z / nbolts;
+
+// calculated - vhook
+
+vhook_inside = 15;
+
+vhook_theta = atan2( smooth_r, main_r );
+
+vhook_y0 = -max(main_r, (tube_dia/2 + vhook_th));
+vhook_ctr = vhook_y0 - vhook_inside/2;
+vhook_outer_dia = vhook_inside + vhook_th*2;
+
+// calculated - cleat
+
+cleat_horn_tl = cleat_horn_l + cleat_stem_l/2;
+
+vcleat_dz = max(0,
+               cleat_horn_tl
+               + cleat_horn_d_min[0]/2
+               - cleat_horn_d_min[0]/2 * cleat_overlap
+               - total_z/2
+               );
+
+// calculated - hhook
+
+hhook_outer_dia = hhook_inside + hhook_th*2;
+
+hhook_ctr = -max(main_r + hhook_inside/2,
+                tube_dia/2 + hhook_outer_dia/2);
+
+$fa = 3;
+$fs = 0.1;
+
+module SmoothPlan(){
+  offset(r=-smooth_r) offset(delta=smooth_r) children(0);
+}
+
+module TubePlan(){ circle(r = tube_dia/2); }
+module MainCirclePlan(){ circle(r = main_r); }
+
+module PlanWeldMainCircle(){
+  intersection(){
+    difference(){
+      SmoothPlan(){
+       union(){
+         MainCirclePlan();
+         children(0);
+       }
+      }
+      TubePlan();
+    }
+    rotate(-135) square(100);
+  }
+}
+
+module MainPlan(flatten=false) {
+  difference(){
+    SmoothPlan()
+      union(){
+      translate([hinge_x, 0]) circle(r= hinge_o_r);
+      MainCirclePlan();
+      rectfromto([0,           -flats_y],
+                [max_x,       +flats_y]);
+    }
+    TubePlan();
+    rectfromto([0,       -open_gap/2],
+              [max_x+1, +open_gap/2]);
+    translate([hinge_x, 0]) {
+      intersection(){
+       circle(r= hole_dia/2);
+       if (flatten)
+         translate([ pin_flatten, 0 ])
+         square(center=true, [hole_dia, hole_dia + 1]);
+      }
+    }
+  }
+}
+
+module Portion(d=0) {
+  translate([hinge_x, 0]) circle(r= hinge_o_r + d);
+  rectfromto([hinge_x*2, 0],
+            [max_x+10, -(tube_dia/2+th+10)]);
+}
+
+module MainPlanA(flatten){
+  intersection(){
+    MainPlan(flatten);
+    Portion(0);
+  }
+}
+
+module MainPlanB(flatten){
+  difference(){
+    MainPlan(flatten);
+    Portion(hinge_gap);
+  }
+}
+
+module HalfClampXPositive(flatten=false){
+  translate([0,0, min_z]) {
+    linextr(0, total_z) mirror([0,1]) MainPlanB();
+    for (i=[0 : hinge_units-1]) {
+      translate([0,0, stride_z*i])
+       linextr(0, hinge_unit) MainPlanA(flatten);
+    }
+  }
+}
+
+module HalfClampXNegative(){
+  for (j=[0:nbolts-1]) {
+    translate([ bolt_x, 0, min_z + (j + 0.5) * bolt_stride ]) {
+      translate([0, -tube_dia/2, 0])
+       rotate([-90,0,0])
+       cylinder(r= bolt_hole_r, h= tube_dia);
+      translate([0, -flats_y, 0])
+       rotate([90,0,0])
+       cylinder(r= bolt_flat/2, h= tube_dia/2);
+    }
+  }
+}
+
+module HalfClampX(flatten=false){
+  difference(){
+    HalfClampXPositive(flatten);
+    HalfClampXNegative();
+  }
+}
+
+// ---------- vhook ----------
+
+module VHookProfile() {
+  translate([0, -vhook_inside/2 - vhook_th/2])
+    circle(r = vhook_th/2);
+}
+
+module VHookHookMain(outer=false){ ////toplevel
+  rotate([0,90,0])
+    rotate_extrude(convexity=10)
+    rotate([0,0,90])
+    hull(){
+      VHookProfile();
+      if (outer) {
+       translate([0,-vhook_outer_dia]) square(center=true, vhook_th);
+      }
+    }
+}
+
+module VHookA(){ ////toplevel
+  DummyA();
+
+  translate([0, vhook_ctr, 0]){
+    for (m=[0,1]) {
+      mirror([0, m, 0]) {
+       linextr(-0.1, vhook_outer_dia/2)
+         VHookProfile();
+       translate([0, -vhook_inside/2 -vhook_th/2, vhook_outer_dia/2])
+         sphere(r= vhook_th/2);
+      }
+    }
+
+    intersection(){
+      VHookHookMain(outer=true);
+      linextr_y_xz(0, vhook_outer_dia/2) hull(){
+       VHookProfile();
+       translate([0,-0.1]) square(center=true, [vhook_th, 0.2]);
+      }
+    }
+
+    intersection(){
+      VHookHookMain();
+      translate([0,0, -vhook_outer_dia])
+       cube(center=true, vhook_outer_dia*2);
+    }
+  }
+
+  //translate([0, vhook_y0, 50]) rotate([0,0,-90]) color("black") cube(10);
+  // translate([0,0,-150]) rotate([0,0,180 + theta]) color("blue") cube(100);
+}
+
+module VHookPlanDemo(){
+  MainPlanA();
+  translate([0, vhook_ctr, 5])
+    for (m=[0,1]) {
+      mirror([0,m])
+       color("blue") VHookProfile();
+    }
+}
+
+// ---------- cleat ----------
+
+function cleat_frame_theta(s) = s * cleat_horn_tl / cleat_curve_r * 360/TAU;
+function cleat_frame_z(s) = cleat_curve_r * (1 - cos(cleat_frame_theta(s)));
+function cleat_frame_x(s) = cleat_curve_r * sin(cleat_frame_theta(s));
+function cleat_frame_r(s) = ( cleat_horn_d_min * s +
+                             cleat_horn_d_max * (1-s) ) * 0.5;
+
+module CleatFrameSphere(r) {
+  scale([1, r[1]/r[0], 1])
+    sphere(r= r[0]);
+}
+
+module CleatFrame(s) {
+  r = cleat_frame_r(s);
+  translate([cleat_frame_x(s), 0, cleat_frame_z(s)])
+    rotate([0, 90, 0])
+    CleatFrameSphere(r);
+}
+
+
+module CleatHorn(){
+  for (si=[0 : cleat_frames-2]) {
+    s0 = si / (cleat_frames-1);
+    s1 = (si+1) / (cleat_frames-1);
+    hull(){
+      CleatFrame(s0);
+      CleatFrame(s1);
+    }
+  }
+}
+
+module CleatBase(){
+  frames = cleat_frames/2;
+  se = cleat_stem_l/2 / cleat_horn_tl;
+  r = cleat_frame_r(se);
+
+  hull(){
+    for (s = [-se, se]) {
+      for (z= [-(cleat_height + tube_dia/2),
+              cleat_frame_z(s)]) {
+       translate([cleat_frame_x(s), 0, z])
+         linear_extrude(height=0.1)
+         scale([1, r[1]/r[0]])
+         circle(r=r[0]);
+      }
+    }
+  }
+}
+
+module VCleat(){
+  intersection(){
+    translate([0,0, vcleat_dz]){
+      difference(){
+       translate([0, -(main_r + cleat_height), 0]) {
+         rotate([0, -90, 90]) {
+           CleatBase();
+           for (m=[0,1]) {
+             mirror([m,0,0]) {
+               CleatHorn();
+             }
+           }
+         }
+       }
+       linextr(-cleat_stem_l, +cleat_stem_l)
+         circle(r = tube_dia/2 + 0.1);
+      }
+    }
+      translate([0,0, total_z * 0.5])
+       cube(center=true,
+            (main_r + cleat_stem_l)*4 * [1,1,0] +
+            total_z * [0,0,2]);
+  }
+}
+
+module VCleatA(){ ////toplevel
+  DummyA();
+  VCleat();
+}
+
+// ---------- hhook ----------
+
+module HHookHookPlan(){
+  translate([0, hhook_ctr]){
+    difference(){
+      circle(r = hhook_outer_dia/2);
+      circle(r = hhook_inside/2);
+      rectfromto([+hhook_outer_dia, -hhook_outer_dia],
+                [0,                +hhook_outer_dia]);
+    }
+    translate([0, -(hhook_inside/2 + hhook_th/2)]){
+      hull(){
+       for (x=[-0.1, hhook_l]) {
+         translate([x,0]) square(center=true, hhook_th);
+       }
+      }
+    }
+  }
+}
+
+module HHookA(){ ////toplevel
+  DummyA();
+  linextr(min_z, max_z) {
+    HHookHookPlan();
+  }
+}
+
+module HHookPlanDemo(){
+  MainPlanA();
+  HHookHookPlan();
+}
+
+// ---------- linear bracket ----------
+
+module LinearBracketA(){ ////toplevel
+  difference(){
+    union(){
+      HalfClampXPositive();
+      mirror([1,0,0])
+      linextr_y_xz(-open_gap/2 - linear_bracket_t, -open_gap/2)
+       rectfromto([0, min_z],
+                  [max_x + linear_bracket_l, min_z + linear_bracket_h]);
+    }
+    HalfClampXNegative();
+    linextr(-1000,1000)
+      TubePlan();
+    mirror([1,0,0])
+      linextr_y_xz(-100,100) {
+      for (t = [
+               [1,1] * linear_bracket_hole_offset,
+               -[1,1] * linear_bracket_hole_offset +
+               [linear_bracket_l, linear_bracket_h]
+               ]) {
+       translate([ max_x, min_z ] + t)
+         circle(r= linear_bracket_hole_dia/2);
+      }
+    }
+  }
+}
+
+// ---------- misc ----------
+
+module PinSitu(){ ////toplevel
+  difference(){
+    union(){
+      translate([0,0, -pin_head_th])
+       cylinder(r= pin_dia/2, h = total_z + pin_head_th + pin_tail);
+      mirror([0,0,1])
+       cylinder(r= hinge_o_r - pin_gap, h = pin_head_th);
+    }
+    translate([0,0, total_z + pin_tail/2])
+      rotate([0,90,0])
+      translate([0,0, -pin_dia])
+      cylinder(r= pin_hole_dia/2, h=pin_dia*2);
+    translate([pin_dia/2 * cos(45), -50, -pin_head_th*2])
+      cube([50,100, total_z*2]);
+  }
+}
+
+module Pin(){ ////toplevel
+  rotate([0,90,0]) {
+    PinSitu();
+  }
+}
+
+module GeneralB(){ ////toplevel
+  HalfClampX(true);
+}
+
+module DummyA(){ ////toplevel
+  HalfClampX();
+}
+
+module PlanDemo(){ ////toplevel
+  MainPlan();
+  translate([0,0,-4]) color("red") Portion(1);
+  translate([0,0,-2]) color("grey") Portion(0);
+
+  translate([0, tube_dia*1.5]) {
+    MainPlanB();
+    MainPlanA();
+  }
+
+  translate([0, -tube_dia*1.5]) {
+    VHookPlanDemo();
+  }
+  translate([tube_dia*4, 0]) {
+    HHookPlanDemo();
+  }
+//  translate([max_x - hinge_x + 20, 0]) color("blue") MainPlanA();
+}
+
+module DemoA(){ DummyA(); }
+
+module Demo(){ ////toplevel
+  color("red") rotate([180,0,0]) GeneralB();
+  color("blue") DemoA();
+  color("orange") translate([hinge_x, 0, min_z - hinge_z_gap])
+    rotate([0,0,180]) PinSitu();
+}
+
+module DemoPair(){ ////toplevel
+  color("red") rotate([180,0,0]) DemoA();
+  color("blue") DemoA();
+  color("orange") translate([hinge_x, 0, min_z - hinge_z_gap])
+    rotate([0,0,180]) PinSitu();
+}
+
+//PlanDemo();
+//HalfClamp();
diff --git a/scaffold-clamp-linear-bracket.scad b/scaffold-clamp-linear-bracket.scad
new file mode 100644 (file)
index 0000000..a5fbe24
--- /dev/null
@@ -0,0 +1,11 @@
+// -*- C -*-
+
+// Per linear bracket print
+//     LinearBracketA
+//     GeneralB
+//     Pin
+
+//// toplevels-from:
+include <scaffold-clamp-common.scad>
+
+module DemoA(){ LinearBracketA(); }
diff --git a/scaffold-clamp-straphook.scad b/scaffold-clamp-straphook.scad
new file mode 100644 (file)
index 0000000..1804be9
--- /dev/null
@@ -0,0 +1,22 @@
+// -*- C -*-
+
+// Per gym ring strap retainer print
+//     HHookA
+//     GeneralB
+//     Pin
+
+//// toplevels-from:
+include <scaffold-clamp-common.scad>
+
+th = 3;
+hhook_th = 3;
+hinge_units = 2;
+nbolts = 1;
+hinge_unit = 5;
+bolt_dia = 3;
+bolt_flat = 7 + 1;
+
+hhook_inside = 35;
+hhook_l = 50;
+
+module DemoA(){ HHookA(); }
diff --git a/scaffold-clamp-tensioner.scad b/scaffold-clamp-tensioner.scad
new file mode 100644 (file)
index 0000000..6207dfb
--- /dev/null
@@ -0,0 +1,11 @@
+// -*- C -*-
+
+// Per tensioner print
+//     VHookA
+//     GeneralB
+//     Pin
+
+//// toplevels-from:
+include <scaffold-clamp-common.scad>
+
+module DemoA(){ VHookA(); }
diff --git a/screw-recess-test-number.fig.pl b/screw-recess-test-number.fig.pl
new file mode 100755 (executable)
index 0000000..023b315
--- /dev/null
@@ -0,0 +1,19 @@
+#!/usr/bin/perl
+
+my $number = shift @ARGV;
+die unless $number =~ m/^\d+$/;
+
+my $fontsz = $number * 6 + 12;
+
+print <<END or die $!;
+#FIG 3.2  Produced by xfig version 3.2.5b
+Landscape
+Center
+Metric
+A4      
+100.00
+Single
+-2
+1200 2
+4 0 0 50 -1 18 $fontsz 0.0000 4 285 225 3600 4500 $number\001
+END
diff --git a/screw-recess-test.scad b/screw-recess-test.scad
new file mode 100644 (file)
index 0000000..08fdf20
--- /dev/null
@@ -0,0 +1,125 @@
+// -*- C -*-
+
+module RecessScrewCutout_RecessCylinder(recessdia,zbelow, h){
+  translate([0,0,-zbelow]) cylinder(r=recessdia/2, h=h+1, $fn=40);
+}
+
+RecessedScrewCutout_defaultrecessdepth_flat = -0.30;
+RecessedScrewCutout_defaultrecessdepth_hex = -0.60;
+
+function RecessedScrewCutout_recessdepth(recessdia,
+        recessdepth_arg=RecessedScrewCutout_defaultrecessdepth_flat) =
+  recessdepth_arg >= 0 ? recessdepth_arg : -recessdepth_arg * recessdia;
+
+function RecessedScrewCutout_totaldepth(recessdia,
+        recessdepth_arg=RecessedScrewCutout_defaultrecessdepth_flat) =
+  RecessedScrewCutout_recessdepth(recessdia, recessdepth_arg) +
+  + 0.5*recessdia + 0.1;
+
+module RecessedScrewCutout(shaftdia, recessdia, shaftlen,
+        zbelow=1,
+        recessdepth_arg=RecessedScrewCutout_defaultrecessdepth_flat) {
+  // pass recessdepth_arg=-1 for the default for flat heads
+  // pass recessdepth_arg=-1 for the default for flat heads
+  recessdepth = RecessedScrewCutout_recessdepth(recessdia, recessdepth_arg);
+  recesstopz = RecessedScrewCutout_totaldepth(recessdia, recessdepth_arg);
+
+  translate([0,0,-zbelow]) cylinder(r=shaftdia/2, h=shaftlen+zbelow, $fn=20);
+  RecessScrewCutout_RecessCylinder(recessdia,zbelow, recessdepth);
+  intersection(){
+    cube([recessdia + 1, shaftdia + 0.1, recesstopz*2 + 1], center=true);
+    translate([0, -recessdia, recesstopz])
+      rotate([0,135,0]) cube([recessdia, recessdia*2, 10]);
+    RecessScrewCutout_RecessCylinder(recessdia,zbelow, recesstopz+1);
+  }
+}
+
+//              nom.   shaft
+//              shaft   slop
+screw_info_M2   = [2,   1.2];
+screw_info_M3   = [3,   1.2];
+screw_info_M4   = [4,   1.1];
+screw_info_M5   = [5,   1.0];
+screw_info_M6   = [6,   1.2];
+
+function screw_shaft_dia_nom(info)      = info[0];
+function screw_shaft_dia_use(info)      = info[0] + info[1];
+function screw_recess_dia_use(info)     = info[0] * 2.50 + 1.0;
+function screw_recess_depth(info)       = info[0] * 1.00 + 0.50;
+function screw_recess_depth_allen(info) = info[0] * 1.55 + 0.50;
+
+function RecessedScrewCutoutStandard_totaldepth(info) =
+  RecessedScrewCutout_totaldepth(screw_recess_dia_use(info),
+                                screw_recess_depth(info));
+
+function RecessedScrewCutoutStandardAllen_totaldepth(info) =
+  RecessedScrewCutout_totaldepth(screw_recess_dia_use(info),
+                                screw_recess_depth_allen(info));
+
+module RecessedScrewCutoutStandard(info, shaftlen, zbelow=1) {
+  RecessedScrewCutout(screw_shaft_dia_use(info),
+                     screw_recess_dia_use(info),
+                     shaftlen, zbelow,
+                     screw_recess_depth(info));
+}                               
+
+module RecessedScrewCutoutStandardAllen(info, shaftlen, zbelow=1) {
+  RecessedScrewCutout(screw_shaft_dia_use(info),
+                     screw_recess_dia_use(info),
+                     shaftlen, zbelow,
+                     screw_recess_depth_allen(info));
+}                               
+
+tests = [
+        screw_info_M2,
+        screw_info_M3,
+        screw_info_M4,
+2       screw_info_M5,
+        screw_info_M6
+        ];
+
+function Test_blocksz(t) = screw_recess_dia_use(t) + 7;
+
+module OneTestCore(t, h, ymul, labelnumber=false){
+  blocksz = Test_blocksz(t);
+  translate([0, ymul * (blocksz*0.5 - 1.5), 0]) {
+    difference(){
+      translate([-blocksz/2, -blocksz/2, 0])
+       cube([blocksz, blocksz, h]);
+      child();
+    }
+    if (labelnumber) {
+      rotate([90,0,0])
+       translate([-blocksz/4,blocksz/5, blocksz/2-1])
+       linear_extrude(height=0.3+1)
+      import(file=str("screw-recess-test-number-s",t[0],".dxf"), convexity=100);
+    }
+  }
+}
+
+module OneTest(t){
+  h = RecessedScrewCutoutStandard_totaldepth(t) + 3;
+  ha = RecessedScrewCutoutStandardAllen_totaldepth(t) + 3;
+  OneTestCore(t, h, 1){
+    RecessedScrewCutoutStandard(t, h+1);
+  }
+  OneTestCore(t, ha, -1, true){
+    RecessedScrewCutoutStandardAllen(t, ha+1);
+  }
+}
+
+function Test_x(i) = i<=0 ? 0 :
+  Test_x(i-1) + Test_blocksz(tests[i-1])/2 + Test_blocksz(tests[i])/2 - 3;
+
+module Tests(){
+  for (i = [0:len(tests)-1]) {
+    echo(i, Test_x(i));
+    translate([Test_x(i), 0, 0])
+      OneTest(tests[i]);
+  }
+}
+
+//OneTest(tests[1]);
+Tests();
+//Hole();
+//Holes();
diff --git a/sealing-box.scad.m4 b/sealing-box.scad.m4
new file mode 100644 (file)
index 0000000..dbfb220
--- /dev/null
@@ -0,0 +1,180 @@
+// -*- C -*-
+
+// This file can be used in two ways:
+//
+// A. Rectangular boxes
+//      1. include <sealing-box.scad>
+//      2. assign() values to (xxx these should be $ variables)
+//           $sealingbox_wallth
+//           $sealingbox_sz[0] (outer dimension)
+//           $sealingbox_sz[1] (outer dimension)
+//           $sealingbox_sz[2] (inner dimension)
+//           $sealingbox_ceilth
+//           $sealingbox_floorth
+//           $sealingbox_wallth
+//      3. use the modules
+//           SealingBox_RectBox
+//           SealingBox_RectLid
+//           (origin is notional outside corner, but at level of
+//            inside of base; box extends to positive x,y,z)
+//
+// B. Complicated shapes, but harder work
+//      1. Be a .m4 file and m4_include sealing-box.scad.m4
+//      2. Define your own BoxDoShapeSomething like BoxDoShapeRect
+//      3. Invoke BoxUseShape
+//      4. Use the Box and Lid modules generated
+//
+// Other settings
+//  $sealingbox_cnrrad
+
+$sealingbox_cnrrad = 10;
+$sealingbox_crude = false;
+$sealingbox_inner_slop = 0.2;
+
+m4_define(`BoxLocals',`
+  xbox = $sealingbox_sz[0];
+  ybox = $sealingbox_sz[1];
+  zbox = $sealingbox_sz[2];
+  wall = $sealingbox_wallth;
+  floorth = $sealingbox_floorth;
+  ceilth = $sealingbox_ceilth;
+  cnrrad = $sealingbox_cnrrad;
+
+  xbox_lin = xbox - cnrrad*2;
+  ybox_lin = ybox - cnrrad*2;
+')
+
+m4_define(`innertube', `(1.0 + 0.2)')
+m4_define(`lidoverlap', `1.5')
+m4_define(`lidoverhang', `6')
+m4_define(`tubesealrad', `2.0')
+
+m4_define(`BoxFn',`$fn= $sealingbox_crude ? ($2) : ($1)')
+
+m4_dnl Box_Part($1=transl_x,$2=transl_y, $3=rot_z,$4=mirror_xy)
+m4_dnl          $5=kind, $6=kindargs, $7=profile(profileargsargs))
+m4_define(`Box_Part',`
+  translate([($1),($2)])
+    rotate([0,0,($3)])
+    mirror([($4),0,0])
+    BoxPart_Extrude_$5($6, $7)') m4_dnl
+
+boxpart_d = 0.01;
+
+m4_dnl BoxPart_Extrude_Linear(dist, `profile(...);');
+m4_define(`BoxPart_Extrude_Linear',`
+  rotate([90,0,0])
+    translate([0,0, -($1)])
+    linear_extrude(height= boxpart_d + ($1)) {
+      $2
+    }
+')
+
+m4_dnl BoxPart_Extrude_Arc(x0_radius, swept_angle, `profile(...);')
+m4_dnl  arc starting at transl_x, transl_y, moving towards positive
+m4_dnl  y at first and then bending towards negative x, until
+m4_dnl  use negative x0_radius to inciate bending towards positive x
+m4_dnl  swept_angle is reached
+m4_dnl  x0_radius is the radius of the extruded part at x=0, not of the box
+m4_define(`BoxPart_Extrude_Arc',`
+  translate([-($1),0,0])
+    intersection(){
+      translate([0,0,-500])
+        linear_extrude(height=1000)
+        scale(500)
+        mirror([($1)<0, 0,0])
+        FArcSegment_mask($2);
+      rotate_extrude(convexity=10, $fs=1, BoxFn(36,8))
+        mirror([($1)<0, 0,0])
+        translate([+($1),0,0]){
+          $3
+        }
+    }
+')
+
+m4_dnl BoxDoShapeRect(`profile(profileargs)');
+m4_define(`BoxDoShapeRect',`
+  Box_Part(0,           cnrrad,         0,0, Linear,`ybox_lin', `$1' )
+  Box_Part(0,           ybox-cnrrad,    0,0, Arc,`-cnrrad,90' , `$1' )
+  Box_Part(cnrrad,      ybox,         -90,0, Linear,`xbox_lin', `$1' )
+  Box_Part(xbox-cnrrad, ybox,         -90,0, Arc,`-cnrrad,90' , `$1' )
+  Box_Part(xbox,        ybox-cnrrad, -180,0, Linear,`ybox_lin', `$1' )
+  Box_Part(xbox,        cnrrad,      -180,0, Arc,`-cnrrad,90' , `$1' )
+  Box_Part(xbox-cnrrad, 0,           -270,0, Linear,`xbox_lin', `$1' )
+  Box_Part(cnrrad,      0,           -270,0, Arc,`-cnrrad,90' , `$1' )
+')
+
+m4_dnl '
+
+module SealingBox_WallProfile(){
+  BoxLocals
+  z = zbox - innertube - tubesealrad;
+  translate([0, -0.1]) square([wall, z]);
+  translate([tubesealrad, z]) circle(r=tubesealrad, BoxFn(20,6));
+}
+
+module SealingBox_FloorProfile(){
+  BoxLocals
+  mirror([0,1]) square([wall, floorth]);
+}
+
+function SealingBox_lidbigger() = lidoverlap + innertube;
+
+module SealingBox_LidProfile(){
+  BoxLocals
+  rad = tubesealrad + innertube;
+  morex = wall;
+  inner_buttress_h = tubesealrad*1.5 + innertube + ceilth;
+
+  difference(){
+    translate([0, zbox + ceilth]) mirror([0,1]) {
+      translate([-SealingBox_lidbigger(),
+                0])
+      square([lidoverlap + innertube + tubesealrad,
+             lidoverhang + innertube + ceilth]);
+      square([tubesealrad*2 + innertube + lidoverlap,
+             inner_buttress_h]);
+    }
+    hull(){
+      translate([tubesealrad,
+                zbox - innertube - tubesealrad])
+       for (t=[ [0,0],
+                [0, -zbox]
+                ]) {
+         translate(t)
+           circle(r= tubesealrad + innertube, BoxFn(20,6));
+       }
+    }
+  }
+  translate([tubesealrad*2 + $sealingbox_inner_slop,
+            zbox + ceilth]) {
+    mirror([0,1]) {
+      square([lidoverlap + innertube,
+             inner_buttress_h]);
+    }
+  }
+}
+
+module SealingBox_CeilProfile(){
+  BoxLocals
+  translate([0, zbox])
+    square([wall*2, ceilth]);
+}
+
+// BoxDoShape(Basename,BoxDoShapeSomething)
+// generates modules BasenameBox and BasenameLid
+m4_define(`BoxUseShape',`
+  module $1Box(){
+    BoxLocals
+    $2(SealingBox_WallProfile(););
+    hull(){ $2(SealingBox_FloorProfile();); }
+  }
+
+  module $1Lid(){
+    BoxLocals
+    $2(SealingBox_LidProfile(););
+    hull(){ $2(SealingBox_CeilProfile();); }
+  }
+')
+
+BoxUseShape(`SealingBox_Rect',`BoxDoShapeRect')
diff --git a/secateurs-clip.scad b/secateurs-clip.scad
new file mode 100644 (file)
index 0000000..bdb008d
--- /dev/null
@@ -0,0 +1,22 @@
+// -*- C -*-
+
+l = 47;
+d = 18;
+
+w = 15;
+t = 10;
+te = 7;
+
+linear_extrude(height=15, convexity=4) {
+  for (m=[0,1]) {
+    mirror([m,0]) {
+      polygon([[ -1,      0 ],
+              [ -1,     -t ],
+              [ l/2+te, -t ],
+              [ l/2+te,  d ],
+              [ l/2,     d ],
+              [ l/2,     0 ],
+              ]);
+    }
+  }
+}
diff --git a/sewing-table-end-profile-photo.jpg b/sewing-table-end-profile-photo.jpg
new file mode 100644 (file)
index 0000000..73f3a20
Binary files /dev/null and b/sewing-table-end-profile-photo.jpg differ
diff --git a/sewing-table-end-profile.fig b/sewing-table-end-profile.fig
new file mode 100644 (file)
index 0000000..2250e94
--- /dev/null
@@ -0,0 +1,29 @@
+#FIG 3.2  Produced by xfig version 3.2.6a
+Landscape
+Center
+Metric
+A4
+100.00
+Single
+-2
+1200 2
+2 5 0 1 0 -1 60 -1 -1 0.000 0 0 -1 0 0 5
+       0 sewing-table-end-profile-photo.jpg
+        2655 6615 23743 6615 23743 12787 2655 12787 2655 6615
+2 1 0 1 1 7 50 -1 -1 0.000 0 0 -1 0 0 2
+        3780 10170 22635 9990
+2 1 0 2 9 7 50 -1 -1 0.000 0 0 -1 0 0 2
+        22671 9881 22587 6522
+2 1 0 3 14 7 50 -1 -1 0.000 0 0 -1 0 0 2
+        3780 8137 22635 7957
+2 1 0 2 9 7 50 -1 -1 0.000 0 0 -1 0 0 2
+        4090 9278 4034 6610
+2 2 0 2 28 7 50 -1 -1 0.000 0 0 -1 0 0 5
+        1818 8008 5248 8008 5248 14648 1818 14648 1818 8008
+3 1 0 1 6 7 40 -1 -1 0.000 0 0 0 17
+        8544 8080 12341 8071 16290 7965 19845 7830 22214 7727 22560 7582
+        22605 7370 22539 6869 22548 6611 12555 4725 4252 5873 4101 6534
+        4052 7107 4047 7674 4058 7812 4323 7950 4864 8005
+        1.000 1.000 1.000 1.000 1.000 1.000 1.000 1.000
+        1.000 1.000 1.000 1.000 1.000 1.000 1.000 1.000
+        1.000
diff --git a/sewing-table-front-profile-photo.jpg b/sewing-table-front-profile-photo.jpg
new file mode 100644 (file)
index 0000000..d0dd7f1
Binary files /dev/null and b/sewing-table-front-profile-photo.jpg differ
diff --git a/sewing-table-front-profile.fig b/sewing-table-front-profile.fig
new file mode 100644 (file)
index 0000000..d318174
--- /dev/null
@@ -0,0 +1,26 @@
+#FIG 3.2  Produced by xfig version 3.2.6a
+Landscape
+Center
+Metric
+A4
+100.00
+Single
+-2
+1200 2
+2 5 0 1 0 -1 60 -1 -1 0.000 0 0 -1 0 0 5
+       0 sewing-table-front-profile-photo.jpg
+        2250 2115 14794 2115 14794 8172 2250 8172 2250 2115
+2 2 0 1 2 7 50 -1 -1 0.000 0 0 -1 0 0 5
+        5311 3627 1892 3627 1892 8634 5311 8634 5311 3627
+2 1 0 1 1 7 50 -1 -1 0.000 0 0 -1 0 0 2
+        3455 7525 12541 7946
+3 1 0 1 28 7 40 -1 -1 0.000 0 0 0 25
+        5678 3664 4889 3652 4306 3643 3820 3620 3256 3600 2800 3591
+        2416 3574 3055 1515 11023 1659 11041 2606 10905 2752 10663 2946
+        10504 3060 10304 3176 10113 3305 9859 3397 9581 3493 9306 3565
+        8734 3613 8367 3618 7996 3636 7520 3639 7119 3651 6713 3663
+        6299 3670
+        1.000 1.000 1.000 1.000 1.000 1.000 1.000 1.000
+        1.000 1.000 1.000 1.000 1.000 1.000 1.000 1.000
+        1.000 1.000 1.000 1.000 1.000 1.000 1.000 1.000
+        1.000
diff --git a/sewing-table-jig.scad b/sewing-table-jig.scad
new file mode 100644 (file)
index 0000000..9302119
--- /dev/null
@@ -0,0 +1,21 @@
+//// toplevels-from:sewing-table.scad
+include <sewing-table.scad>
+
+JIG = true;
+
+tile_th=0.8;
+interlock_dia=5;
+
+jig_pencil_rad = 1;
+jig_pencil_slotlen = 10;
+jig_min_th = 0.3;
+jig_post_hole_slop = 0.5;
+
+test_tile_th = -0.1;
+
+test_edge = interlock_dia * 0.5 + interlock_fine + 2;
+round_edge_rad = tile_th/2;
+frontcurve_z_slop = 15;
+rearcurve_z_slop = 20;
+
+interlock_fine = tile_th/8;
diff --git a/sewing-table-rear-profile-photo.jpg b/sewing-table-rear-profile-photo.jpg
new file mode 100644 (file)
index 0000000..34bf64d
Binary files /dev/null and b/sewing-table-rear-profile-photo.jpg differ
diff --git a/sewing-table-rear-profile.fig b/sewing-table-rear-profile.fig
new file mode 100644 (file)
index 0000000..3a6af71
--- /dev/null
@@ -0,0 +1,28 @@
+#FIG 3.2  Produced by xfig version 3.2.6a
+Landscape
+Center
+Metric
+A4
+100.00
+Single
+-2
+1200 2
+2 2 0 1 2 7 50 -1 -1 0.000 0 0 -1 0 0 5
+        3255 -6357 -7303 -6357 -7303 18013 3255 18013 3255 -6357
+2 1 0 1 1 7 50 -1 -1 0.000 0 0 -1 0 0 2
+        -2808 6102 -1101 12715
+2 5 0 1 0 -1 60 -1 -1 0.000 0 0 -1 0 0 5
+       0 sewing-table-rear-profile-photo.jpg
+        11014 -9675 -5445 -9675 -5445 16370 11014 16370 11014 -9675
+2 1 0 1 14 7 50 -1 -1 0.000 0 0 -1 0 0 2
+        -536 -9216 -5067 1826
+3 1 0 1 28 7 40 -1 -1 0.000 0 0 0 26
+        3330 -6390 1755 -5895 495 -5310 -630 -4590 -1440 -3825 -2115 -2790
+        -2745 -1710 -3240 -450 -3555 990 -3555 2430 -3420 3780 -2970 5625
+        -2610 6930 -2205 8415 -1890 9630 -1485 11160 -1125 12690 -855 14085
+        -90 16020 14715 9630 9855 -6975 9270 -8550 8685 -8010 7200 -7560
+        5985 -7200 4365 -6660
+        1.000 1.000 1.000 1.000 1.000 1.000 1.000 1.000
+        1.000 1.000 1.000 1.000 1.000 1.000 1.000 1.000
+        1.000 1.000 1.000 1.000 1.000 1.000 1.000 1.000
+        1.000 1.000
diff --git a/sewing-table-test.scad b/sewing-table-test.scad
new file mode 100644 (file)
index 0000000..14b83b9
--- /dev/null
@@ -0,0 +1,15 @@
+//// toplevels-from:sewing-table.scad
+include <sewing-table.scad>
+TEST = true;
+test_tile_th = 0.67;
+test_edge = interlock_dia * 0.5 + interlock_fine + 2;
+
+leg_n_fins = 2;
+
+leg_top_thick = 3;
+leg_bot_thick = 4;
+leg_top_flat_z = 0.5;
+leg_fin_top_w = 2;
+leg_fin_bot_w = 3;
+leg_fin_bot_rad = 15;
+leg_bot_mid_dia = 9;
diff --git a/sewing-table.scad.m4 b/sewing-table.scad.m4
new file mode 100644 (file)
index 0000000..b71e041
--- /dev/null
@@ -0,0 +1,1193 @@
+// -*- C -*-
+
+include <funcs.scad>
+include <commitid.scad>
+
+ply_th = 18;
+ply_hole_dia = 15;
+ply_edge_min = 10;
+
+ply_hole_dia_real = 12;
+
+tile_th = 3;
+post_dia = 8;
+
+post_shorter = 1;
+
+$screw_dia = 3.2;
+screw_big_dia = 3.6;
+screw_big_len = 4.0;
+
+round_edge_rad = 2.0;
+
+round_cnr_rad = 10;
+
+interlock_dia = 10;
+interlock_fine = 0.66;
+
+interlock_fine_slope = 1.0;
+interlock_fine_lenslop = 1.0;
+
+demo_slop = 0.1;
+
+leg_height = 53.75 - 0.95;
+
+leg_hole_dia = 5 + 0.75;
+leg_big_dia = 37;
+leg_bot_dia = 15;
+leg_top_flat_z = 2;
+leg_top_thick = 8;
+
+leg_midspc_dia = 20;
+leg_bot_thick = 8;
+leg_bot_mid_dia = 12;
+
+leg_fin_top_w = 3;
+leg_fin_bot_w = 5;
+leg_fin_bot_rad = 30;
+leg_fin_bot_flat_z = 5;
+
+leg_n_fins = 4;
+leg_n_tubules = 4;
+leg_tubule_dia = 4;
+
+// spacer
+
+spacer_ext_slop = 0.25;
+spacer_int_slop = 0.25;
+spacer_height = 10;
+
+// cutout
+
+machine_rear_to_front = 84 + 0.25 - 1.4;
+
+cutout_l_end_y_front_slop = 0.5;
+cutout_l_end_y_rear_slop = 0.5;
+
+cutout_l_end_x = 22;
+cutout_l_end_y = machine_rear_to_front;
+cutout_l_end_new_x_slop = 1.4 - 1.95;
+cutout_l_end_y_total = cutout_l_end_y
+  + cutout_l_end_y_front_slop + cutout_l_end_y_rear_slop;
+
+tile02_tr = [-250, 0];
+tile01_tr = [  0, 0];
+
+cutout_tile01_y = 170 - 147 + cutout_l_end_y_front_slop;
+cutout_tile11_y = cutout_l_end_y_total - cutout_tile01_y;
+
+// front and rear curves
+
+rearedge_len = 170 + 0.70;
+frontedge_len = 170;
+
+rearcurve_strt_len = 52;
+
+rearcurve_z_slop = -0.50;
+
+rearcurve_avoid_y = 35;
+
+rearcurve_double_inrad = 26.10 + 8.04;
+
+reartablet_z = 2.54;
+reartablet_x = 5 + 1;
+reartablet_y = 8;
+
+frontcurve_side_skew = 3.5 / 72;
+frontcurve_avoid_y = 70;
+frontcurve_z_slop = 0.75;
+
+frontcurve_strt_len = 50;
+frontcurve_dualcurve_angle = 30;
+
+teststrapslots_at = [ [ 110, 70 ], [ 110, -35 ],
+                     [ 180, 90 ],
+                     [ 190, -80 ], // do not change index of this one
+                     [   0, 70 ],  [  0, -35 ],
+                     ];
+
+teststrap = [ 3, 5 ];
+teststrap_peg = [7.5, 3.5];
+
+ply_edge_hole_dist_real = 14;
+
+// calculated
+
+TEST = false;
+JIG = false;
+
+ply_edge_hole_dist = ply_edge_min + ply_hole_dia/2;
+
+echo(str("HOLES IN PLY ctr dist from PLY edge = ", ply_edge_hole_dist));
+
+hole_slop = (ply_hole_dia - post_dia)/2;
+tile_hard_edge_hole_dist = ply_edge_hole_dist + hole_slop;
+
+echo(str("HOLES IN PLY ctr dist from TILE HARD edge = ",
+        tile_hard_edge_hole_dist));
+
+echo(str("HOLES IN PLY ctr dist from TILE ROUND edge = ",
+        tile_hard_edge_hole_dist + round_edge_rad));
+
+thehd = [ tile_hard_edge_hole_dist, tile_hard_edge_hole_dist ];
+thehd_tr = thehd;
+thehd_tl = [ -thehd_tr[0], thehd_tr[1] ];
+thehd_bl = -thehd_tr;
+thehd_br = -thehd_tl;
+
+tablet_z_slop = 1.00;
+
+interlock_rad = interlock_dia/2;
+interlock_negative_rad = interlock_rad + 0.125;
+
+interlock_sq_adj = 0.2; // arbitrary
+
+leg_fin_top_rad = sqrt( pow(leg_big_dia/2,2) -
+                       pow(leg_fin_top_w/2,2) );
+
+leg_tubule_pos_rad = leg_big_dia/2 * 0.6;
+
+m4_define(`POST_TCROSSSZ',
+          `2*( tile_hard_edge_hole_dist - test_edge + 1 )')
+
+module Post(){
+  mirror([0,0,1]) {
+    if (!JIG) {
+      difference(){
+       cylinder(r= post_dia/2, h= tile_th + ply_th - post_shorter);
+       translate([0,0, tile_th]) {
+         cylinder(r= screw_big_dia/2, h= screw_big_len);
+         cylinder(r= $screw_dia/2, h= ply_th, $fn=20);
+       }
+      }
+    }
+    if (TEST) {
+      translate([0,0, tile_th/2]) {
+       cube([post_dia,      POST_TCROSSSZ, tile_th], center=true);
+       cube([POST_TCROSSSZ, post_dia,      tile_th], center=true);
+      }
+    }
+    if (JIG) {
+      translate([0,0, tile_th/2]) {
+       cube([POST_TCROSSSZ, POST_TCROSSSZ, tile_th], center=true);
+      }
+    }
+  }
+}
+
+module PostHole(){
+  if (JIG) {
+    translate([0,0,-5])
+      cylinder(r= post_dia/2 + jig_post_hole_slop, h=10);
+    translate([0,0, -jig_min_th])
+      cylinder(r= ply_hole_dia_real/2, h = 5);
+    for (rot=[0:90:270]) rotate(rot) {
+       translate([ ply_edge_hole_dist_real, 0, 0 ])
+         cube([ jig_pencil_rad*2, jig_pencil_slotlen, 20 ], center=true);
+      }
+  }
+}
+
+module Posts(posts) {
+  for (p= posts) {
+    translate(concat(p, [0]))
+      Post();
+  }
+}
+module PostHoles(posts) {
+  for (p= posts)  {
+    translate(concat(p, [0]))
+      PostHole();
+  }
+}
+
+module TileBase(botleft, topright){
+  size = topright - botleft;
+  botleft_post = botleft + thehd_tr;
+  topright_post = topright + thehd_bl;
+  difference(){
+    mirror([0,0,1])
+      translate(concat(botleft, [0]))
+      cube(concat(size, [tile_th]));
+    if (!(TEST || JIG)) {
+      cidsz = topright_post-botleft_post
+       + [-post_dia,-post_dia]
+       + [0, thehd[1]];
+      cidszr = [ min(cidsz[0],50), min(cidsz[1],50) ];
+      echo("CID",cidsz,cidszr);
+      translate( concat(botleft_post, [ -tile_th ])
+                + 0.5 * [ post_dia, post_dia, 0 ]
+                + 0.5 * concat( cidsz - cidszr, [ 0 ]) )
+       Commitid_BestCount_M(cidszr);
+    }
+    if ((TEST || JIG)) {
+      crossoff = tile_hard_edge_hole_dist + POST_TCROSSSZ/2;
+      cidsz = [ thehd[0], size[1] - 2*crossoff ];
+      cidszr = [ cidsz[0], min(cidsz[1], 50) ];
+      if (TEST)
+       translate( concat(botleft + [0, crossoff] + (cidsz-cidszr)/2, [0]) )
+         Commitid_BestCount(cidszr);
+      difference(){
+       mirror([0,0,1]) {
+         translate(concat(botleft + [test_edge,test_edge], [test_tile_th]))
+           cube(concat(size - [test_edge,test_edge]*2, [tile_th*2]));
+         translate(concat(botleft_post, [-1]))
+           cube(concat(topright_post-botleft_post, [tile_th+2]));
+       }
+       shufflesz = max(test_edge, tile_hard_edge_hole_dist)*2;
+       minkowski(){
+         MachineEnvelope();
+         cube(shufflesz, center=true);
+       }
+       if (JIG) {
+         translate([0,0,-20]) linear_extrude(height=20) {
+           for (diag=[[ +1, botleft                   ],
+                      [ -1, [topright[0], botleft[1]] ]]) {
+             translate(diag[1])
+               rotate(atan2(size[1], diag[0] * size[0]))
+               translate([0, -test_edge/2])
+               square([vectorlen2d(size), test_edge]);
+           }
+         }
+       }
+      }
+    }
+  }
+}
+
+m4_dnl   R_EDGE(c,ix)
+m4_dnl        c is from Rectangle_corners and
+m4_dnl        ix is a corner number
+m4_dnl    expands to two comma-separated corners:
+m4_dnl    that denoted by ix, and the next one anticlockwise
+m4_define(`R_EDGE',`$1[$2], $1[(($2)+1)%4]')
+
+m4_dnl   R_CNR(c,ix)
+m4_dnl        c is from Rectangle_corners and
+m4_dnl        ix is a corner number
+m4_dnl    expands to an array of corners as for RoundCorner
+m4_define(`R_CNR',`[ $1[$2], $1[(($2)+1)%4], $1[(($2)+3)%4] ]')
+
+m4_dnl  INREFFRAME(left_cnr, right_cnr, morevars) { body; }
+m4_define(`INREFFRAME',`
+  length_vec = ($2) - ($1);
+  length = dist2d([0,0], length_vec);
+  length_uvec = length_vec / length;
+  ortho_uvec = [ -length_uvec[1], length_uvec[0] ];
+  m = [ [ length_uvec[0],  ortho_uvec[0], 0, ($1)[0], ],
+       [ length_uvec[1],  ortho_uvec[1], 0, ($1)[1], ],
+       [ 0,              0,              1,            0, ],
+       [ 0,              0,              0,            1, ] ];
+  $3
+  multmatrix(m)
+')
+
+m4_dnl  INREFFRAME(left_cnr, right_cnr, morevars)
+m4_dnl    INREFFRAME_EDGE { body; }
+m4_define(`INREFFRAME_EDGE',`
+  translate([0,0, -round_edge_rad])
+')
+
+module RoundEdge(left_cnr, right_cnr) {
+  INREFFRAME(left_cnr, right_cnr)
+    INREFFRAME_EDGE {
+    difference(){
+      rotate([0,90,0])
+       cylinder(r= round_edge_rad, h= length, $fn=50);
+      translate([-1, 0, -20])
+       cube([length+2, 20, 20]);
+    }
+  }
+}
+
+m4_define(`ROUNDCORNER_VARS',`
+  this_cnr = ci[0];
+  right_cnr = ci[1];
+  left_cnr = ci[2];
+  bigr= round_cnr_rad - round_edge_rad;
+  l_uvec = unitvector2d(left_cnr - this_cnr);
+  r_uvec = unitvector2d(right_cnr - this_cnr);
+  lp1 = left_cnr  + clockwise2d(l_uvec) * bigr;
+  lp2 = this_cnr  + clockwise2d(l_uvec) * bigr;
+  lp3 = this_cnr  - clockwise2d(r_uvec) * bigr;
+  lp4 = right_cnr - clockwise2d(r_uvec) * bigr;
+  ctr = line_intersection_2d(lp1,lp2,lp3,lp4);
+  ctr3 = concat(ctr,[0])
+')
+
+module RoundCorner_selector(ci, adj) {
+  ROUNDCORNER_VARS;
+  intersection(){
+    union(){
+      INREFFRAME(ctr3,concat(lp1,[4])){
+       translate([0,0,-bigr]) linear_extrude(height=bigr*2) {
+         translate([-bigr*2 + adj, -bigr])
+           square([bigr*2, bigr*3]);
+       }
+      }
+    }
+    union(){
+      INREFFRAME(ctr3,concat(lp4,[0])){
+       translate([0,0,-bigr]) linear_extrude(height=bigr*2) {
+         translate([-bigr*2, -bigr*2])
+           square([bigr*2 + adj, bigr*3]);
+       }
+      }
+    }
+  }
+}
+
+module RoundCornerCut(ci) {
+  // ci should be [this_cnr, right_cnr, left_cnr]
+  // where right_cnr is to the right (ie, anticlockwise)
+  ROUNDCORNER_VARS;
+  difference(){
+    RoundCorner_selector(ci, -0.1);
+    translate(ctr3)
+      cylinder(center=true, h=20, r= bigr);
+  }
+}
+
+module RoundCornerAdd(ci) {
+  ROUNDCORNER_VARS;
+  intersection(){
+    RoundCorner_selector(ci, +0.1);
+    INREFFRAME_EDGE {
+      translate(ctr3){
+       rotate_extrude(convexity=10, $fn=50)
+         translate([bigr, 0])
+         difference(){
+         circle(r= round_edge_rad, $fn=50);
+         mirror([1,1])
+           square([20,20]);
+       }
+      }
+    }
+  }
+}
+
+module InterlockLobePlan(negative) {
+  r = negative ? interlock_negative_rad : interlock_rad;
+  ymir = negative ? 0 : 1;
+
+  dx = sqrt(3) * r;
+  $fn= 80;
+  translate([thehd[0], 0]){
+    mirror([0,ymir]){
+      circle(r=r);
+      difference(){
+       translate([-dx, -0.1])
+         square([ dx*2, r/2 + 0.1 ]);
+       for (xi = [-1, 1]) {
+         translate([ xi*dx, r ])
+           circle(r=r);
+       }
+      }
+    }
+  }
+}
+
+module InterlockEdgePlan(negative, nlobes, length, dosquare=true) {
+  for (lobei = [ 0 : nlobes-1 ]) {
+    lobex = (length - thehd[0]*2) * (lobei ? lobei / (nlobes-1) : 0);
+    translate([lobex, 0, 0]) {
+      InterlockLobePlan(negative);
+    }
+  }
+
+  if (dosquare) {
+    iadj = 0;
+    slotshorter = negative ? -0.1 : interlock_fine_lenslop;
+    mirror([0, negative])
+      translate([slotshorter, iadj])
+      square([length - slotshorter*2, interlock_fine + iadj*2]);
+  }
+}
+
+module InterlockEdge(left_cnr, right_cnr, negative=0, nlobes=2) {
+  plusth = negative * 1.0;
+  protr = interlock_fine + interlock_sq_adj;
+
+  z2 = -tile_th/2;
+  z1 = -tile_th/2 - protr / interlock_fine_slope;
+  z3 = -tile_th/2 + protr / interlock_fine_slope;
+
+  negsign = negative ? -1 : +1;
+  yprotr = negsign * protr;
+
+  INREFFRAME(left_cnr, right_cnr) {
+    for (vsect = [ // zs0            zs1      ys0,            ys1
+                 [ -tile_th-plusth, plusth,  0,              0],
+                 [ z1,              z2,      0, yprotr],
+                 [ z2,              z3,      yprotr, 0],
+                 ]) {
+      zs0 = vsect[0];
+      zs1 = vsect[1];
+      zsd = zs1-zs0;
+      ys0 = vsect[2];
+      ys1 = vsect[3];
+      ysd = ys1-ys0;
+      sl = ysd/zsd;
+      m = [ [ 1,0,   0,    0 ],
+           [ 0,1, -sl, -ys0 + negsign*interlock_sq_adj ],
+           [ 0,0,   1,  zs0 ],
+           [ 0,0,   0,    1 ] ];
+      multmatrix(m)
+       linear_extrude(height=zsd, convexity=10)
+       InterlockEdgePlan(negative, nlobes, length, !!ysd);
+    }
+  }
+}
+
+function TestPiece_holes2corners(holes) =
+  [ holes[0] + thehd_bl,
+    holes[1] + thehd_br,
+    holes[1] + thehd_tr,
+    holes[0] + thehd_tl ];
+
+module TestPiece1(){ ////toplevel
+  holes = [ [-100, 0],
+           [   0, 0]
+           ];
+  corners = TestPiece_holes2corners(holes);
+  rcs = R_CNR(corners,0);
+  difference(){
+    union(){
+      TileBase(corners[0], corners[2]);
+      Posts(holes);
+      RoundEdge(corners[0], corners[1]);
+      RoundEdge(corners[3], corners[0]);
+    }
+    InterlockEdge(corners[1], corners[2], 1, nlobes=1);
+    RoundCornerCut(rcs);
+    PostHoles(holes);
+  }
+  RoundCornerAdd(rcs);
+}
+
+module TestPiece2(){ ////toplevel
+  holes = [ [   0, 0],
+           [  50, 0]
+           ];
+  corners = TestPiece_holes2corners(holes);
+  difference(){
+    union(){
+      TileBase(corners[0], corners[2]);
+      Posts(holes);
+      RoundEdge(corners[0], corners[1]);
+      InterlockEdge(corners[3], corners[0], 0, nlobes=1);
+    }
+    PostHoles(holes);
+  }
+}
+
+module TestDemo(){ ////toplevel
+  translate([ -thehd[0], 0 ])
+    color("blue")
+    TestPiece1();
+  translate([ +thehd[0] + demo_slop, 0 ])
+    TestPiece2();
+}
+
+module PostTestPiece(){ ////toplevel
+  hole_sizes = [2.8, 3.0, 3.1, 3.134, 3.168, 3.2, 3.3, 3.5];
+  nholes = len(hole_sizes)*2;
+  nrows = 4;
+  stride = post_dia*1.5;
+  rect_sz = stride * [ nrows,
+                      ceil(nholes/nrows) ];
+  corners = Rectangle_corners(-stride * 0.5 * [1,1], rect_sz);
+  difference(){
+    union(){
+      TileBase(corners[0], corners[2]);
+      RoundEdge(corners[0], corners[1]);
+      for (i= [ 0: nholes-1 ]) {
+       $screw_dia = hole_sizes[ floor(i/2) ];
+       translate(stride * [ (nrows-1) - (i % nrows),
+                            floor(i / nrows),
+                            0
+                            ]) {
+         Posts([[0,0]]);
+         color("blue")
+           mirror([0,0,1])
+           translate([post_dia/2, -post_dia/2, 1])
+           cube([1, post_dia * (i / nholes), tile_th]);
+       }
+      }
+    }
+  }
+}
+
+module Machine_NewRearProfile(){
+  // figures copied out of xfig edit boxes
+  // best not to edit the posbox size if poss - just move it
+  posbox = 10 * ([7.2333,-14.1267] - [-16.2289,40.0289]); // box, Green
+  sideline = -10 * ([-6.2400,13.5600] - [-2.4467,28.2556]); // line, Blue
+  scaleline = 10 * dist2d([-1.1911,-20.4800], [-11.2600,4.0578]); // Green2
+  scaleline_mm = 12+5+10+5+3;
+  sh = -[abs(posbox[0]), abs(posbox[1])];
+  rot = atan2(-sideline[0], sideline[1]);
+  sc = scaleline_mm / scaleline;
+  //echo("SH",sh,rot,sc);
+  scale(sc) rotate(rot) translate(sh){
+    import("sewing-table-rear-profile.dxf", convexity=10); // spline, Pink3
+  }
+}
+
+module Machine_NewFrontProfile(){
+  // figures copied out of xfig edit boxes
+  // best not to edit the posbox size if poss - just move it
+  posbox = 10 * ([11.8022,8.0600] - [4.2044,19.1867]); // box, Green
+  refline = 10 * ([7.6778,16.7222] - [27.8689,17.6578]); // line, Blue
+  refline_mm = (11-1)*10;
+  sh = -[abs(posbox[0]), abs(posbox[1])];
+  rot = atan2(-refline[0], refline[1]);
+  sc = refline_mm / vectorlen2d(refline);
+  //echo("SH",sh,rot,sc);
+  mirror([1,0]) scale(sc) rotate(rot+90) translate(sh){
+    import("sewing-table-front-profile.dxf", convexity=10); // spline, Pink3
+  }
+}
+
+module Machine_NewEndProfile(){
+  // figures copied out of xfig edit boxes
+  // NB that y coords are reversed - xfig origin is at bottom left
+  posboxs = 10 * [[4.0400,17.7956], [11.6622,32.5511]]; // box, Pink3
+  refline = 10 * ([8.4000,22.6000] - [50.3000,22.2000]); // line, Blue
+  refline_mm = 10 * (11 - 2.5);
+  sidelines = 10 * [[[9.0889,20.6178], [8.9644,14.6889]],
+                   [[50.3800,21.9578], [50.1933,14.4933]]]; // lines, Blue3
+  baseline = 10 * [[8.4000,18.0822], [50.3000,17.6822]]; // line, Green2
+
+  rot_adj = -0.38;
+
+  posbox = [min(posboxs[0][0],posboxs[1][0]),
+           max(posboxs[0][1],posboxs[1][1])];
+
+  m4_define(`MNEP_ELP',
+     `line_intersection_2d(baseline[0],baseline[1],
+                           sidelines[$1][0],sidelines[$1][1])')
+  endline = [MNEP_ELP(0),MNEP_ELP(1)];
+
+  rot = atan2(-refline[1], -refline[0]);
+  sc = refline_mm / vectorlen2d(refline);
+  sh = (0.5 * (endline[0] + endline[1])) - posbox;
+
+  ellen = sc * dist2d(endline[0],endline[1]);
+  scy = cutout_l_end_y_total / ellen;
+
+  scale([scy,1]) scale(sc) rotate(rot + rot_adj) translate(-[sh[0],-sh[1]]){
+
+    mirror([0,1]){
+  //%translate(1 * (posboxs[0] - posbox)) square(50);
+  //%translate(1 * (posboxs[1] - posbox)) square(50);
+//  %translate(1 * (baseline[0] - posbox)) square([50,10]);
+
+//  %translate(1 * (endline[0] - posbox)) square([50,10]);
+//  %translate(1 * (endline[1] - posbox)) square([50,10]);
+
+//  %translate(1 * (sidelines[0][0] - posbox)) square([10,50]);
+//  %translate(1 * (sidelines[0][1] - posbox)) square([10,50]);
+//  %translate(1 * (sidelines[1][0] - posbox)) square([10,50]);
+//  %translate(1 * (sidelines[1][1] - posbox)) square([10,50]);
+    }
+
+    import("sewing-table-end-profile.dxf", convexity=10); // spline, Pink3
+  }
+}
+
+module Machine_NewEndProfileDemo(){ ////toplevel
+    translate([0,5,0])                             Machine_NewEndProfile();
+    translate([0,5,1]) color("blue") mirror([1,0]) Machine_NewEndProfile();
+  mirror([0,1,0]){
+    translate([0,5, 0])                             Machine_NewEndProfile();
+    translate([0,5,-1]) color("blue") mirror([1,0]) Machine_NewEndProfile();
+  }
+}
+
+module Machine_NewArm(){
+  translate([0,0,-30]) linear_extrude(height=60) {
+    translate(tile01_tr + [ -cutout_l_end_x - cutout_l_end_new_x_slop,
+                           (-cutout_tile01_y + cutout_tile11_y)/2 ]){
+      rotate(-90){
+       hull(){
+         for (d=[0,400]) 
+           translate([0,d]) Machine_NewEndProfile();
+       }
+      }
+    }
+  }
+}
+
+module Machine_NewRearCurve(){
+  slant = atan2(4,210-10);
+  //echo("SL",slant);
+  translate([0,0, rearcurve_double_inrad]) rotate([slant,0,0]){
+    translate([ rearcurve_double_inrad,
+               0,
+               -rearcurve_double_inrad + 10 ]){
+      rotate([180,0,0]) rotate([0,0,90]) linear_extrude(height=30){
+       hull(){
+         Machine_NewRearProfile();
+         translate([0,-100]) Machine_NewRearProfile();
+       }
+      }
+    }
+    rotate([0,90,0]) rotate([90,0,0]) {
+      intersection(){
+       rotate_extrude(convexity=10, $fn=64)
+         rotate(90)
+         translate([ 0, -rearcurve_double_inrad ])
+         Machine_NewRearProfile();
+       translate([0,0,-500])
+         cube([500,500,1000]);
+      }
+    }
+    translate([1,0,-rearcurve_double_inrad])
+      rotate([0,-90,0]) rotate([0,0,-90])
+      linear_extrude(height= rearcurve_strt_len + 1)
+      Machine_NewRearProfile();
+  }
+}
+
+module Machine_Curves(){ ////toplevel
+  translate([ tile01_tr[0] - cutout_l_end_x + rearedge_len,
+             cutout_tile11_y,
+             0 ]){
+    //%cube([20,20,20]);
+    translate([ -reartablet_x,
+               -1,
+               -reartablet_z + tablet_z_slop])
+      mirror([0,0,1])
+      cube([ reartablet_x+1,
+            reartablet_y+1,
+            20 ]);
+  }
+  translate([ tile01_tr[0] - cutout_l_end_x + frontedge_len,
+             cutout_tile11_y,
+             frontcurve_z_slop ]){
+    translate([0, -machine_rear_to_front, 0])
+      multmatrix([[1, -frontcurve_side_skew, 0, 0],
+                 [0,  1,   0, 0],
+                 [0,  0,   1, 0],
+                 [0,  0,   0, 1]])
+      mirror([1,0,0]) rotate([0,-90,0])rotate([0,0,-90])
+      linear_extrude(height= 200)
+      Machine_NewFrontProfile();
+  }
+  translate([ tile01_tr[0] - cutout_l_end_x + rearedge_len,
+             cutout_tile11_y,
+             frontcurve_z_slop ]){
+    translate([ rearcurve_strt_len,
+               0,
+               rearcurve_z_slop ]){
+      Machine_NewRearCurve();
+    }
+  }
+}
+
+module TestStrapSlots(){
+  pegwidth = teststrap_peg[0];
+  for (pos = teststrapslots_at) {
+    echo("TSS",pos);
+    translate(concat(pos,[0]))
+      for (mx = [0,1]) mirror([mx,0,0]) {
+         translate([ pegwidth/2, -teststrap[1]/2, -20 ])
+           cube(concat(teststrap,[40]));
+       }
+  }
+}
+
+module TestStrapPeg_any(l){ cube(concat([l],teststrap_peg)); }
+
+module TestStrapPeg_Short(){ ////toplevel
+  TestStrapPeg_any(35);
+}
+
+module TestStrapPeg_Long(){ ////toplevel
+  TestStrapPeg_any(60);
+}
+
+module PostSpacer(){ ////toplevel
+  $fn = 50;
+  difference(){
+    cylinder(r= ply_hole_dia_real/2 - spacer_ext_slop,
+            h= spacer_height);
+    translate([0,0,-1])
+      cylinder(r= post_dia/2 + spacer_int_slop,
+              h= ply_th + 2);
+  }
+}
+
+module Machine(){ ////toplevel
+  Machine_NewArm();
+  Machine_Curves();
+  if (TEST)
+    TestStrapSlots();
+}
+
+module MachineEnvelope(){
+  // used for testing
+  p_arm_bl = [-cutout_l_end_x, -cutout_tile01_y];
+  y_arm_t  = cutout_tile11_y;
+  p_crv_fl = p_arm_bl + [frontedge_len, -frontcurve_avoid_y];
+  y_crv_b  = y_arm_t + rearcurve_avoid_y;
+
+  translate([0,0,-50]) linear_extrude(height= 100){
+    translate(p_arm_bl) square([400, y_arm_t] - p_arm_bl);
+    translate(p_crv_fl) square([400, y_crv_b] - p_crv_fl);
+  }
+}
+
+module Leg(){ ////toplevel
+  difference(){
+    union(){
+      hull(){
+       mirror([0,0,1])
+         cylinder(r= leg_big_dia/2, h=leg_top_flat_z, $fn=100);
+       translate([0,0, -leg_top_thick])
+         cylinder(r= leg_bot_dia/2, height=1, $fn=100);
+      }
+      if (!TEST)
+       translate([0,0,-leg_height])
+         cylinder(r= leg_bot_mid_dia/2, h=leg_bot_thick);
+      for (rot=[0: 360/leg_n_fins : 359]) rotate(rot) {
+         hull(){
+           mirror([0,0,1]) translate([0, -leg_fin_top_w/2, 0])
+             cube([ leg_fin_top_rad - 0.1,
+                    leg_fin_top_w,
+                    1 ])
+             ;
+           translate([0, -leg_fin_bot_w/2, -leg_height])
+             cube([ leg_fin_bot_rad,
+                    leg_fin_bot_w,
+                    leg_fin_bot_flat_z ]);
+         }
+       }
+    }
+    mirror([0,0,1]) translate([0,0,-1])
+      cylinder(r= leg_hole_dia/2,
+              h= (!TEST ? leg_height+2 : leg_height/2),
+              $fn=30);
+    mirror([0,0,1]) translate([0,0,leg_top_thick - 0.1])
+      hull(){
+        cylinder(r= (!TEST ? leg_midspc_dia/2 : 0.1),
+                h= leg_height - leg_top_thick - leg_bot_thick + 0.2,
+                $fn=30);
+       if (TEST)
+         cylinder(r= leg_midspc_dia/2,
+                  h= leg_height - leg_top_thick - leg_bot_thick
+                     + (!TEST ? 0.2 : -leg_midspc_dia/2),
+                  $fn=30);
+      }
+    cid_shear = (leg_fin_bot_w - leg_fin_top_w)/2 /
+                 (leg_height -leg_fin_bot_flat_z);
+    multmatrix([[ 1, 0, 0, leg_midspc_dia/2 ],
+                 [ 0, cid_shear,
+                         1, -leg_fin_bot_w/2 ],
+                 [ 0, 1, 0, -leg_height + leg_fin_bot_flat_z ],
+                 [ 0, 0, 0, 1 ]])
+      Commitid_BestCount([ leg_big_dia/2 - leg_midspc_dia/2,
+                            leg_height - leg_fin_bot_flat_z
+                             - leg_top_thick ]);
+    if (!TEST)
+      for (rot=[45: 360/leg_n_tubules : 359]) rotate(rot) {
+         mirror([0,0,1]) translate([ leg_tubule_pos_rad, 0, -1])
+           cylinder(r= leg_tubule_dia/2, h=leg_height+2, $fn=20);
+       }
+  }
+}
+
+function Rectangle_corners(c0, sz) =
+  // returns the corners of a rectangle from c0 to c0+sz
+  // if sz is positive, the corners are anticlockwise starting with c0
+  [ c0 + [ 0,     0     ],
+    c0 + [ sz[0], 0     ],
+    c0 + [ sz[0], sz[1] ],
+    c0 + [ 0,     sz[1] ] ];
+
+function Rectangle_corners2posts(c) =
+  [ c[0] + thehd_tr,
+    c[1] + thehd_tl,
+    c[2] + thehd_bl,
+    c[3] + thehd_br ];
+
+module Rectangle_TileBase(c) { TileBase(c[0], c[2]); }
+
+function Posts_interpolate_one(c0,c1) = [c0, (c0+c1)/2, c1];
+
+module Tile02(){ ////toplevel
+  sz = [100,170];
+  c0 = tile02_tr + -sz;
+  c = Rectangle_corners(c0, sz);
+  posts = Rectangle_corners2posts(c);
+  rcs = R_CNR(c,0);
+  difference(){
+    union(){
+      Rectangle_TileBase(c);
+      Posts(posts);
+      RoundEdge(R_EDGE(c,0));
+      RoundEdge(R_EDGE(c,3));
+      InterlockEdge(R_EDGE(c,2), 0);
+    }
+    InterlockEdge(R_EDGE(c,1), 1);
+    RoundCornerCut(rcs);
+    PostHoles(posts);
+  }
+  RoundCornerAdd(rcs);
+}
+
+module Tile12(){ ////toplevel
+  sz = [100,250];
+  c0 = tile02_tr + [-sz[0], 0];
+  c = Rectangle_corners(c0, sz);
+  posts = Rectangle_corners2posts(c);
+  rcs = R_CNR(c,3);
+  difference(){
+    union(){
+      Rectangle_TileBase(c);
+      Posts(posts);
+      RoundEdge(R_EDGE(c,2));
+      RoundEdge(R_EDGE(c,3));
+    }
+    InterlockEdge(R_EDGE(c,0), 1);
+    InterlockEdge(R_EDGE(c,1), 1);
+    RoundCornerCut(rcs);
+    PostHoles(posts);
+  }
+  RoundCornerAdd(rcs);
+}
+
+tile_01_11_cnr = tile01_tr + [-cutout_l_end_x, 0];
+tile_11_10_cnr = tile01_tr + [0, cutout_tile11_y];
+tile_01_00_cnr = tile01_tr - [0, cutout_tile01_y];
+
+module Tile11(){ ////toplevel
+  sz = [250,250];
+  c0 = tile01_tr + [-sz[0],0];
+  c = Rectangle_corners(c0, sz);
+  cnr_posts = Rectangle_corners2posts(c);
+  posts = concat(
+                Posts_interpolate_one(cnr_posts[0],
+                                      cnr_posts[1] - [cutout_l_end_x, 0]),
+                [ cnr_posts[1] + [0, cutout_tile11_y],
+                  cnr_posts[2],
+                  cnr_posts[3]
+                  ]);
+  difference(){
+    union(){
+      Rectangle_TileBase(c);
+      Posts(posts);
+      RoundEdge(R_EDGE(c,2));
+      InterlockEdge(R_EDGE(c,3));
+    }
+    InterlockEdge(c[0], tile_01_11_cnr, 1);
+    InterlockEdge(tile_11_10_cnr, c[2], 1);
+    PostHoles(posts);
+    Machine();
+  }
+}    
+
+module Tile01(){ ////toplevel
+  sz = [250,170];
+  c0 = tile01_tr + -sz;
+  c = Rectangle_corners(c0, sz);
+  cnr_posts = Rectangle_corners2posts(c);
+  posts = concat(
+                Posts_interpolate_one(R_EDGE(cnr_posts,0)),
+                [ cnr_posts[2] + [0, -cutout_tile01_y] ],
+                Posts_interpolate_one(cnr_posts[2] - [cutout_l_end_x, 0],
+                                      cnr_posts[3])
+                );
+  difference(){
+    union(){
+      Rectangle_TileBase(c);
+      Posts(posts);
+      RoundEdge(R_EDGE(c,0));
+      InterlockEdge(tile_01_11_cnr, c[3]);
+      InterlockEdge(R_EDGE(c,3));
+    }
+    PostHoles(posts);
+    InterlockEdge(c[1], tile_01_00_cnr, 1);
+    Machine();
+  }
+}    
+
+module Tile10(){ ////toplevel
+  sz = [250,250];
+  c0 = tile01_tr + [0,0];
+  c = Rectangle_corners(c0, sz);
+  cnr_posts = Rectangle_corners2posts(c);
+  cty = cutout_tile11_y;
+  rcy = cty + rearcurve_avoid_y;
+  posts = [ cnr_posts[0] + [ 0,                             cty ],
+           cnr_posts[1] + [ -sz[0] + rearedge_len - cutout_l_end_x, cty ],
+           cnr_posts[1] + [ 0,                             rcy ],
+           cnr_posts[2],
+           cnr_posts[3] ];
+  rcs = R_CNR(c,2);
+  difference(){
+    union(){
+      Rectangle_TileBase(c);
+      Posts(posts);
+      RoundEdge(R_EDGE(c,1));
+      RoundEdge(R_EDGE(c,2));
+      InterlockEdge(c[3], tile_11_10_cnr);
+    }
+    PostHoles(posts);
+    RoundCornerCut(rcs);
+    Machine();
+  }
+  RoundCornerAdd(rcs);
+}
+
+module Tile00(){ ////toplevel
+  sz = [250,170];
+  c0 = tile01_tr + [0,-sz[1]];
+  c = Rectangle_corners(c0, sz);
+
+  // the edge c[1]..c[2] needs a diagonal chunk, from c1bis to c2bis
+  c2bis = [ -cutout_l_end_x + frontedge_len + frontcurve_strt_len, c[2][1] ];
+  c1bis = [ c[1][0],
+           c[2][1] -
+           (c[2][0] - c2bis[0]) * tan(90 - frontcurve_dualcurve_angle) ];
+
+  cnr_posts = Rectangle_corners2posts(c);
+  cty = cutout_tile01_y;
+  rcy = cty + frontcurve_avoid_y;
+  posts = [ cnr_posts[0],
+           cnr_posts[1],
+           0.5 * (cnr_posts[0] + cnr_posts[1]),
+           cnr_posts[2] + [ 0,                             -rcy ],
+           cnr_posts[2] + [ -sz[0] + frontedge_len - cutout_l_end_x, -cty ],
+           cnr_posts[3] + [ 0,                             -cty ]
+           ];
+  rcs = R_CNR(c,1);
+  rc2 = [c1bis,c2bis,c[1]];
+  difference(){
+    union(){
+      difference(){
+       union(){
+         Rectangle_TileBase(c);
+         Posts(posts);
+         RoundEdge(R_EDGE(c,0));
+         RoundEdge(c[1], c1bis);
+         InterlockEdge(tile_01_00_cnr, c[0]);
+       }
+       RoundCornerCut(rcs);
+       translate([0,0,-20]) linear_extrude(height=40) {
+         polygon([ c1bis, c1bis + [50,0], c2bis + [50,0], c2bis ]);
+       }
+      }
+      RoundEdge(c1bis, c2bis);
+    }
+    Machine();
+    PostHoles(posts);
+    RoundCornerCut(rc2);
+  }
+  RoundCornerAdd(rcs);
+  RoundCornerAdd(rc2);
+}
+
+module FitTest_general(c0,sz, dobrace=false, bracexx=0){
+  c = Rectangle_corners(c0, sz);
+  brace = [7,7,9];
+  bsz = sz + [bracexx,0,0];
+  difference(){
+    union(){
+      Rectangle_TileBase(c);
+      if (dobrace) {
+       translate(concat(c0, [-brace[2] + 0.1])){
+         difference(){
+           cube(concat(bsz, [brace[2]]) - [5,0,0]);
+           translate(brace + [0,0, -25])
+             cube(concat(bsz, [50]) - brace*2 + [10,0,0]);
+         }
+       }
+      }
+      RoundEdge(R_EDGE(c,1));
+    }
+    Machine();
+  }
+}
+
+module FitTest_PairLink(cut=false){ ////toplevel
+  cy0=-55; cy1=85; cx=132;
+  bar = [10,10];
+  legrad = 12;
+  footrad_min = 1; footrad_max = 4; footrad_depth = 5;
+  strap = [3,5];
+  adj_neg_slop = 1.0;
+  bar_z_slop = 1.75;
+
+  // calculated
+  straphole_x_max = legrad/sqrt(2) + footrad_max;
+  dz = cut ? adj_neg_slop : 0;
+
+  translate([cx - bar[0]/2, cy0, dz + bar_z_slop])
+    cube([bar[0], cy1-cy0, bar[1] - bar_z_slop]);
+
+  for (endy=[cy0,cy1]) {
+    $fn=32;
+    translate([cx,endy,dz]){
+      // feet
+      for (rot=[45:90:315]) rotate(rot) {
+       translate([legrad,0,0]){
+         hull(){
+           cylinder(r= footrad_max, h=1);
+           translate([0,0,-footrad_depth])
+             cylinder(r= footrad_min, h=1);
+         }
+         if (cut)
+           translate([0,0,-10])
+           cylinder(r= footrad_min +
+                    adj_neg_slop * (footrad_max-footrad_min)/footrad_depth,
+                    h=20);
+       }
+      }
+      // legs
+      for (rot=[45,135]) rotate(rot) {
+       hull(){
+         for (s=[-1,+1]){
+           translate([s*legrad,0,0])
+             cylinder(r= footrad_max, h=bar[1]);
+         }
+       }
+      }
+      // strap holes
+      if (cut) {
+       for (rot=[0,180]) rotate(rot) {
+           translate([ straphole_x_max - strap[0]/2, 0,0 ])
+             cube(concat(strap,[20]), center=true);
+         }
+      }
+    }
+  }
+}
+
+module FitTest_RearCurve(){ ////toplevel
+  difference(){
+    FitTest_general([100,0], [180,100]);
+    FitTest_PairLink(true);
+    TestStrapSlots();
+  }
+}
+
+module FitTest_FrontCurve(){ ////toplevel
+  p0 = [100,-80];
+  sz = [180,80];
+  difference(){
+    intersection() {
+      Tile00();
+      translate([0,0,-8]) linear_extrude(height=18) {
+       translate(p0) square(sz);
+       translate(teststrapslots_at[3])
+         scale(2* [ teststrap_peg[0], teststrap[1] ])
+         circle(r=1, $fn=20);
+      }
+    }
+    FitTest_PairLink(true);
+    TestStrapSlots();
+  }
+}
+
+module FitTest_Entire(){ ////toplevel
+  p0 = [-33,-80];
+  szrear = [263,180];
+  szfront = [243,szrear[1]];
+  difference(){
+    FitTest_general(p0, szrear, dobrace=true, bracexx=0);
+    FitTest_PairLink(true);
+    translate(concat(p0,[0]) + [szfront[0],-10,-40])
+      cube([100, -p0[1], 80]);
+    TestStrapSlots();
+  }
+  intersection(){
+    FitTest_RearCurve();
+    translate(concat(p0,[-20])) cube(concat(szrear,[40]));
+  }
+  FitTest_FrontCurve();
+}
+
+module FitTest_EntireDemo(){ ////toplevel
+  FitTest_Entire();
+  //%Tile00();
+}
+
+module FitTest_EndEnd(){ ////toplevel
+  p0 = [-30,-32];
+  sz = [156,81] - p0;
+  sz2 = [136,68] - p0;
+  difference(){
+    FitTest_general(p0, sz);
+    translate([ p0[0] -1, p0[1]+sz2[1], -10])
+      cube([ sz2[0] +1, 50, 20 ]);
+  }
+}
+
+module FitTest_PairDemo(){ ////toplevel
+  sh=[-90,-15,0];
+  translate(sh){
+    FitTest_PairLink();
+    %FitTest_FrontCurve();
+    %FitTest_RearCurve();
+  }
+  rotate([0,0,180]){
+    translate(sh){
+      difference(){
+       union(){
+         FitTest_FrontCurve();
+         FitTest_RearCurve();
+       }
+       #FitTest_PairLink(true);
+      }
+    }
+  }
+}
+
+module RoundCornerDemo_plat(cnr){
+  mirror([0,0,1]) linear_extrude(height=1) polygon(cnr);
+}
+
+module RoundCornerDemo(){ ////toplevel
+  cnr = [ [-2,-3], [13,-3], [-12,9] ];
+  translate([0,25,0]) RoundCornerDemo_plat(cnr);
+  translate([25,0,0]) RoundCornerAdd(cnr);
+  translate([-25,0,0]) RoundCornerCut(cnr);
+  translate([0,-25,0]) RoundCorner_selector(cnr, 0);
+  difference(){
+    RoundCornerDemo_plat(cnr);
+    RoundCornerCut(cnr);
+  }
+  RoundCornerAdd(cnr);
+}
+
+module Demo(){ ////toplevel
+  translate(demo_slop*[-2,1]) color("blue") Tile12();
+  translate(demo_slop*[-2,0]) color("red")  Tile02();
+  translate(demo_slop*[-2,1]) color("orange") Tile11();
+  translate(demo_slop*[-2,0]) color("purple") Tile01();
+  translate(demo_slop*[-3,1]) color("blue")   Tile10();
+  translate(demo_slop*[-3,0]) color("red")    Tile00();
+  %Machine();
+  // Can also do this, to print reference sheet:
+  //  load this into openscad
+  //  select Ctrl-4 view, view all, scale appropriately
+  //  import sewing-table,Demo-flat.png
+  //  pngtopnm <sewing-table,Demo-flat.png | ppmbrighten -s -50 -v +100 >t.pnm
+  //  lpr t.pnm
+}
+  
+//TestPiece1();
+//TestPiece2();
+//Demo();
+
+//Machine_NewRearProfile();
+//Machine_NewRearCurve();
+//Machine_NewFrontProfile();
+//Machine_NewEndProfile();
+//Machine_NewEndProfileDemo();
+//Machine_Curves();
+//Machine();
+//FitTest();
+//MachineEnvelope();
diff --git a/shelf-label-holder.scad b/shelf-label-holder.scad
new file mode 100644 (file)
index 0000000..8353d7e
--- /dev/null
@@ -0,0 +1,65 @@
+// -*- C -*-
+
+prong_nomdepth = 15;
+prong_curverad = 30;
+prong_thick = 0.7;
+prong_maxdepth = 18;
+
+front_thick = 2.5;
+
+//nom_shelf = 14.54 + 0.5;
+nom_shelf = 20.315 + 0.5;
+
+interference = 0.75;
+
+length = 60;
+
+// calculated
+
+interference_angle = atan2(interference, prong_nomdepth);
+
+module ProngElevationUnrotated(){
+  intersection(){
+    union(){
+      translate([ prong_nomdepth, prong_curverad ])
+       circle( prong_curverad , $fa=0.5 );
+      translate([ -10, 0 ])
+       square([ prong_nomdepth + 10, 10 ]);
+    }
+    translate([-5, -5])
+      square([ prong_maxdepth + 5, prong_thick + 5]);
+  }
+}
+
+module Elevation(){
+  difference(){
+    union(){
+      rotate(-interference_angle)
+       ProngElevationUnrotated();
+      translate([0, -nom_shelf])
+       mirror([0,1])
+       rotate(-interference_angle)
+       ProngElevationUnrotated();
+      translate([-10, -nom_shelf - prong_thick/2])
+       square([10, nom_shelf + prong_thick]);
+    }
+    mirror([1,0])
+      translate([ front_thick, -100 ])
+      square([ 50, 200 ]);
+  }
+}
+
+module Main(){
+  linear_extrude(height=length)
+    Elevation();
+}
+
+module Print(){
+  rotate([0,-90,0])
+    Main();
+}
+
+//ProngElevationUnrotated();
+//Elevation();
+Main();
+//Print();
diff --git a/simplephone-case-test.scad b/simplephone-case-test.scad
new file mode 100644 (file)
index 0000000..eedb932
--- /dev/null
@@ -0,0 +1,3 @@
+//// toplevels-from:sewing-table.scad
+include <simplephone-case.scad>
+$test = true;
diff --git a/simplephone-case.scad b/simplephone-case.scad
new file mode 100644 (file)
index 0000000..2cbc5ff
--- /dev/null
@@ -0,0 +1,167 @@
+// -*- C -*-
+
+psz = [
+       120,
+       56 + 5 - 3.75,
+       15 + 3,
+       ];
+
+thick = [
+        2,
+        2,
+        1.5,
+        ];
+
+btn_x = 59.6;
+btn_dia = 13;
+btn_y = 14.03;
+
+abtn_x = 46.85;
+abtn_sz = [ 11, 13 ];
+
+screen_xbot = 79;
+screen_sz = [ 35, 46 ];
+
+thumb_xbot = 90;
+thumb_dia = 25;
+
+vol_xbot = 86.5;
+vol_xtop = 107.5;
+vol_depth = 1.0;
+vol_zsz = 9;
+vol_zoff = 0;
+
+rail_ysz = 2.5;
+rail_zsz = 2.5;
+
+stay_height = 1.49;
+
+case_x_less = 0; //case_x_less = 10;
+
+inner_cnr_rad = 4.0;
+
+// calculated
+
+btn_yprop = btn_y / psz[1];
+echo(btn_yprop);
+
+ym = psz[1]/2;
+outer_cnr_rad = inner_cnr_rad + thick[2];
+
+x_sliced = outer_cnr_rad * (1-sin(45));
+
+$screen = true;
+
+module RoundedProfile(sz, cnr_rad){
+  hull(){
+    for (x=[ cnr_rad, sz[0]-cnr_rad ])
+      for (y=[ cnr_rad, sz[1]-cnr_rad ])
+       translate([x,y])
+         circle(r= cnr_rad, $fn=20);
+  }
+}
+
+module RoundedCube(sz, cnr_rad){
+  if ($test)
+    cube(sz);
+  else hull(){
+    for (x=[ cnr_rad, sz[0]-cnr_rad ])
+      for (y=[ cnr_rad, sz[1]-cnr_rad ])
+       for (z=[ cnr_rad, sz[2]-cnr_rad ])
+         translate([x,y,z])
+           sphere(r= cnr_rad, $fn=40);
+  }
+}
+
+module Stay(xbot, xtop, width, midgap_width) {
+  translate([ (xbot+xtop)/2, psz[1]/2, psz[2] ]){
+    difference(){
+      cube([ xtop-xbot, width, stay_height*2 ], center=true);
+      if (midgap_width > 0)
+       cube([ 200, midgap_width, 10 ], center=true);
+    }
+  }
+}
+
+module Stays(){
+  Stay(  76, 82, 10, 0);
+  Stay(-0.1, 55, 10, 0);
+  Stay( 113,125, 70, 15);
+}
+
+module Case(){
+  difference(){
+    mirror([1,0,0])
+      translate(-thick +
+               - [1,0,0] * x_sliced)
+      RoundedCube(psz
+                 + 2*thick
+                 - [1,0,0] * (thick[0])
+                 + [1,0,0] * (x_sliced)
+                 - [case_x_less, 0, 0],
+                 outer_cnr_rad);
+
+    for (yp= [ btn_yprop, 1-btn_yprop ])
+      translate([ -btn_x,
+                 yp * psz[1],
+                 0.5 * psz[2] ])
+       cylinder(r= btn_dia/2, h=20);
+
+    translate([ -abtn_x,
+               btn_yprop * psz[1],
+               psz[2] ])
+      cube(concat(abtn_sz, [ thick[2]*3 ]), center=true);
+
+    if ($screen)
+      mirror([1,0,0])
+      translate([ screen_xbot,
+                 (psz[1] - screen_sz[1])/2,
+                 psz[2]-3 ])
+      cube(concat(screen_sz, [ thick[2]+6 ]));
+
+    hull(){
+      for (x=[ thumb_xbot+thumb_dia/2, psz[0]+10 ])
+       translate([ -x,
+                   ym,
+                   -thick[2]-1 ])
+         cylinder(r= thumb_dia/2,
+                  h= thick[2] + 2,
+                  $fn= 20);
+    }
+
+    mirror([1,0,0])
+      translate([ (vol_xbot+vol_xtop)/2, 0, psz[2]/2 + vol_zoff ])
+      cube([ vol_xtop-vol_xbot, vol_depth*2, vol_zsz ], center=true);
+
+    translate([ thick[0], -10, -10 ])
+      cube([ 10, psz[1]+20, psz[2]+20 ]);
+
+    //translate([-50,-50,10]) cube([100,100,100]);
+
+    mirror([1,0,0])
+      difference(){
+       RoundedCube(psz + [1,0,0],
+                   inner_cnr_rad);
+
+        Stays();
+
+       if (0) for (m=[0,1]) {
+         translate([0,ym,0]) mirror([0,m,0]) translate([0,-ym,0])
+           translate([-1,-1, psz[2]-rail_zsz])
+           cube([psz[0]+1, rail_ysz+1, rail_zsz+1]);
+       }
+      }
+  }
+}
+
+module TestLoop(){
+  intersection(){
+    Case($screen=false);
+    translate([ -vol_xbot, 0,0 ])
+      cube([ 4, 200,200 ], center=true);
+  }
+}
+
+Case();
+//TestLoop();
+//RoundedCube(psz, inner_cnr_rad);
diff --git a/size-tests.m-g b/size-tests.m-g
new file mode 100644 (file)
index 0000000..fbb666b
--- /dev/null
@@ -0,0 +1,213 @@
+; -*- fundamental -*-
+
+; slic3r originally produced this, with these comments, and then we edited:
+; layer_height = 0.4
+; perimeters = 3
+; solid_layers = 3
+; fill_density = 0.4
+; perimeter_speed = 30
+; infill_speed = 60
+; travel_speed = 130
+; scale = 1
+; nozzle_diameter = 0.59
+; filament_diameter = 1.77
+; extrusion_multiplier = 1
+; single wall width = 0.71mm
+; first layer single wall width = 0.60mm
+
+M190 S65 ; wait for bed temperature to be reached
+M104 S215 ; set temperature
+G28 ; home all axes
+M109 S215 ; wait for temperature to be reached
+G90 ; use absolute coordinates
+G21 ; set units to millimeters
+G92 E0
+M82 ; use absolute distances for extrusion
+
+!zprint=0.3
+
+; "skirt" - prep extruder
+!draw 5,5 100,5 101,5.5 101,6.0 100,6.5 5,6.5
+
+; edge ticks for global motions sizing
+
+!edge_ticks(){
+!draw 20,10 10,10 10,20
+!draw 10,65 10,75
+!draw 10,120 10,130 20,130
+!draw 65,130 75,130
+!draw 120,130 130,130 130,120
+!draw 130,75 130,65
+!draw 130,20 130,10 120,10
+!draw 75,10 65,10
+!}
+
+;!edge_ticks()
+
+!outer_square(){
+ !draw @0.000,@0.000 @5.000,@0.000 @5.000,@5.000 @0.000,@5.000 @0.000,@0.000
+!}
+!inner_square(){
+ !draw @0.500,@0.500 @4.500,@0.500 @4.500,@4.500 @0.500,@4.500 @0.500,@0.500
+!}
+!outer_vlines(){
+ !draw @0.000,@0.000 @0.000,@5.000
+ !draw @5.000,@0.000 @5.000,@5.000
+!}
+!inner_vlines(){
+ !draw @0.500,@0.500 @0.500,@4.500
+ !draw @4.500,@0.500 @4.500,@4.500
+!}
+!outer_longrect(){
+ !draw @0.000,@0.000 @5.000,@0.000 @5.000,@20.000 @0.000,@20.000 @0.000,@0.000
+!}
+!inner_longrect(){
+ !draw @0.500,@0.500 @4.500,@0.500 @4.500,@19.500 @0.500,@19.500 @0.500,@0.500
+!}
+
+!org_oblong(){
+; original test oblong
+
+!draw 68.498,79.498 \
+71.502,79.498 \
+71.502,82.502 \
+68.498,82.502 \
+68.498,79.588 \
+*67.899,78.899 \
+72.101,78.899 \
+72.101,83.101 \
+67.899,83.101 \
+67.899,78.989 \
+*67.300,78.300 \
+72.700,78.300 \
+72.700,83.700 \
+67.300,83.700 \
+67.300,78.390 \
+
+!}
+
+!squares_tests(){
+
+!orgy=40
+
+!orgx=30
+!outer_longrect()
+
+!orgx=50
+!inner_longrect()
+!outer_longrect()
+
+!orgx=90
+!inner_square()
+!outer_square()
+
+!orgx=110
+!outer_square()
+
+!orgy=60
+!outer_vlines()
+
+!orgx=90
+!inner_vlines()
+!outer_vlines()
+
+!orgy=80
+!outer_square()
+!inner_square()
+
+!orgx=110
+!inner_square()
+
+!orgy=100
+!inner_vlines()
+
+!orgx=90
+!outer_vlines()
+!inner_vlines()
+
+!orgx=50
+!orgy=80
+!outer_longrect()
+!inner_longrect()
+
+!orgx=30
+!inner_longrect()
+
+!}
+
+!feedrate_tests(){
+
+!orgx=20
+!extruderate=0.045
+!feedrate_test1()
+
+!orgx=30
+!extruderate=0.060
+!feedrate_test1()
+
+!orgx=40
+!extruderate=0.080
+!feedrate_test1()
+
+!orgx=50
+!extruderate=0.100
+!feedrate_test1()
+
+!orgx=60
+!extruderate=0.125
+!feedrate_test1()
+
+!orgx=70
+!extruderate=0.150
+!feedrate_test1()
+
+!orgx=80
+!extruderate=0.200
+!feedrate_test1()
+
+!}
+
+!feedrate_test1(){
+
+!orgy=20
+!outer_longrect()
+
+!orgy=50
+!inner_longrect()
+!outer_longrect()
+
+!orgy=80
+!outer_longrect()
+!inner_longrect()
+
+!}
+
+!layer(){
+
+;--------------------
+
+;!squares_tests()
+;!org_oblong()
+!feedrate_tests()
+
+!}
+
+!layer()
+
+M104 S210 ; set temperature
+M140 S60 ; set bed temperature
+
+!zprint=0.7
+!layer()
+
+!zprint=1.1
+!layer()
+
+M83 ; extruder relative
+G1 F1000 ;1000mm/min extrusion
+G1 E-10; retract
+G1 X0 Y0 F10000
+G28 X0 Y0
+M104 S0 ; turn off temperature
+M140 S0 ; turn off bed
+M84     ; disable motors
diff --git a/sleepphone-cable-box.scad b/sleepphone-cable-box.scad
new file mode 100644 (file)
index 0000000..836f07d
--- /dev/null
@@ -0,0 +1,248 @@
+// -*- C -*-
+
+include <funcs.scad>
+
+wall = 0.75 * [1,1,1];
+wall_bot = 1.0;
+
+phone = [ 76.40, 30.96, 6.00 ]; // includes socket
+phone_button_z = 6.58;
+minwall = 0.50;
+
+expose = 4.00;
+
+cutout_dia = 7;
+cutout_between = 5;
+
+button_dz = 1.35;
+button_dz_centre = 1.35 + 0.75;
+button_dz_outer  = 1.35;
+
+button_dy_outer = 28.42;
+button_dy_inner = 19.05;
+button_dy_centre = 5.65;
+
+nrbutton_brace_x = 37.5;
+
+phone_slop = 0.5 * [1,1,0]
+           + 0.5 * [0,0,1];
+
+led = [25.9, 9.44]; // y is from edge
+led_dia = 4.4;
+
+// next values include slop
+plug_maxw = 10.95 + 0.35;
+plug_minw=   6.53 + 0.35;
+plug_sllen=  6.50;
+plug_totlen = 84.90 + 1.5 - 2.0; // to maxw, including phone
+
+plug_h = 6.5;
+plug_tooth_h = 0.5;
+plug_tooth_dy = 0.5;
+
+keeper_prong = 2;
+keeper_stalk_basewidth = 12;
+keeper_stalk_len = 70;
+keeper_stalk_gap = 1;
+keeper_stalk_thick = wall_bot;
+
+keeper_stalk_base_reinforce_len = 5;
+keeper_stalk_base_reinforce_thick = 2.0;
+
+// calculated
+
+top_z = max( phone[2] + wall[2],
+            phone_button_z + minwall )
+  + phone_slop[2];
+
+plugkeeper_x_maxw = phone[0] - plug_totlen;
+
+plugkeeper_p_max = [ 0, plug_maxw/2 ];
+plugkeeper_p_min = [ -plug_sllen, plug_minw/2 ];;
+plugkeeper_d_u = unitvector2d(
+                 clockwise2d(
+                 vecdiff2d( plugkeeper_p_max, plugkeeper_p_min )
+                            )
+                            );
+
+module MainProfileInnerHalf(){
+  p = phone + phone_slop;
+  pbc = p[2] + button_dz_centre;
+  pbo = p[2] + button_dz_outer;
+  polygon([[ -2,                 0    ],
+          [ p[1]/2,             0    ],
+          [ p[1]/2,             p[2] ],
+          [ button_dy_outer/2,  p[2] ],
+          [ button_dy_outer/2,  pbo  ],
+          [ button_dy_inner/2,  pbo  ],
+          [ button_dy_inner/2,  p[2] ],
+          [ button_dy_centre/2, p[2] ],
+          [ button_dy_centre/2, pbc  ],
+          [ -2,                 pbc  ]]);
+}
+
+module MainProfile(){
+  p = phone + phone_slop;
+  difference(){
+    for (m=[0,1]) mirror([m,0]) {
+       minkowski(){
+         translate([ -wall[1], -wall_bot ])
+           square([ wall[1]*2, wall_bot + wall[2] ]);
+         MainProfileInnerHalf();
+       }
+      }
+    for (m=[0,1]) mirror([m,0]) {
+       MainProfileInnerHalf();
+      }
+  }
+}
+
+module BraceProfileInitial(){
+  p = phone + phone_slop;
+  pbo = p[2] + button_dz_outer;
+  pbc = p[2] + button_dz_centre;
+  polygon([[ button_dy_outer/2 + 0.2,  p[2]           ],
+          [ button_dy_outer/2 + 0.2,  pbo  + wall[2] ],
+          [ button_dy_outer/2      ,  pbo  + wall[2] ],
+          [ button_dy_outer/2      ,  p[2]           ],
+          ]);
+}  
+
+module BraceProfileFinal(){
+  p = phone + phone_slop;
+  pbo = p[2] + button_dz_outer;
+  pbc = p[2] + button_dz_centre;
+  polygon([[ -1,                       p[2]           ],
+          [ -1,                       pbc  + wall[2] ],
+          [ 0,                        pbc  + wall[2] ],
+          [ 0,                        p[2]           ]
+          ]);
+}  
+
+module Brace(){
+  for (m=[0,1]) mirror([0,m,0]) {
+      hull(){
+       for (e=[0,1]) {
+         translate([ nrbutton_brace_x + e * phone[1]/2,
+                     0, 0 ]){
+           rotate([ 90,0,90 ]){
+             linear_extrude(height= (e==0 ? wall[0] : 0.1)){
+               hull(){
+                 BraceProfileInitial();
+                 if (e==0) BraceProfileFinal();
+             }
+             }
+           }
+         }
+       }
+      }
+    }
+}
+
+module BoxMain(){
+  rotate([0,0,90]) rotate([90,0,0]) {
+    translate([0,0, expose])
+      linear_extrude(height = phone[0] + wall[0] - expose, convexity=20)
+      MainProfile();
+    translate([0,0, phone[0]])
+      linear_extrude(height = wall[0], convexity=20)
+      hull() MainProfile();
+  }
+}
+
+module PlugKeeperProfileHalf(){
+  p_max = plugkeeper_p_max;
+  p_min = plugkeeper_p_min;
+  d = plugkeeper_d_u * keeper_prong;
+  
+  translate([ plugkeeper_x_maxw, 0 ]) {
+    polygon([ p_min,
+             p_max,
+             p_max + d,
+             p_min + d ]);
+  }
+}
+
+module PlugKeeperStalkProfile(){
+  hull(){
+    for (m=[0,1]) mirror([0,m,0]) PlugKeeperProfileHalf();
+    translate([ plugkeeper_x_maxw + keeper_stalk_len, 0,0 ])
+      square([ 0.1, keeper_stalk_basewidth ], center=true);
+  }
+}
+
+module PlugKeeper(){
+  for (m=[0,1]) mirror([0,m,0]) {
+      translate([0,0, -keeper_stalk_thick])
+       linear_extrude(height=plug_h + keeper_stalk_thick)
+       PlugKeeperProfileHalf();
+
+      translate([0, 0, plug_h - plug_tooth_h])
+       linear_extrude(height= plug_tooth_h)
+       translate(plugkeeper_d_u * -plug_tooth_dy)
+       PlugKeeperProfileHalf();
+
+    }
+}
+
+module Box(){
+  sidewall_cutout_z = phone[2] + phone_slop[2] + button_dz_outer;
+
+  difference(){
+    union(){
+      BoxMain();
+      Brace();
+    }
+
+    translate([ led[0], phone[1]/2 - led[1], 1 ])
+      rotate([0,0, 360/8/2])
+      cylinder(r = led_dia/2 / cos(360/8/2), h= phone[2]*2, $fn=8);
+
+    for (ys=[-1,+1]) {
+      translate([ -0.1, ys * keeper_stalk_gap, -wall[2]*2])
+       linear_extrude(height = wall[2]*3)
+       PlugKeeperStalkProfile();
+
+      translate([ phone[0] + wall[0],
+                 ys * (cutout_between/2 + cutout_dia/2),
+                 -10 ])
+       cylinder( r= cutout_dia/2, h = 50, $fn = 20 );
+
+      translate([expose, ys*phone[1]/2, sidewall_cutout_z/2])
+       rotate([90,0,0])
+       translate([0,0,-3])
+       cylinder( r= sidewall_cutout_z/2 - 0.1, h=6 , $fn=20 );
+    }
+  }
+
+  PlugKeeper();
+
+  translate([0,0, -keeper_stalk_thick])
+    linear_extrude(height = keeper_stalk_thick)
+    PlugKeeperStalkProfile();
+
+  translate([ plugkeeper_x_maxw + keeper_stalk_len +
+              -keeper_stalk_base_reinforce_len/2,
+              -keeper_stalk_basewidth/2,
+              0 ])
+    mirror([0,0,1])
+    cube([ keeper_stalk_base_reinforce_len,
+          keeper_stalk_basewidth,
+          keeper_stalk_base_reinforce_thick ]);
+}
+
+module BoxPrint(){
+  // This makes' Cura's support more optimal: specifically,
+  // it then doesn't seem to touch the back (bottom) wall
+  translate([0,0,phone[0]])
+    rotate([0,90,0])
+    Box();
+}
+
+//MainProfileInnerHalf();
+//MainProfile();
+//translate([0,0,1]) color("black") BraceProfileInitial();
+//translate([0,0,1]) color("black") BraceProfileFinal();
+//Brace();
+//Box();
+BoxPrint();
diff --git a/slic3r-config.ini b/slic3r-config.ini
new file mode 100644 (file)
index 0000000..c496b91
--- /dev/null
@@ -0,0 +1,98 @@
+# generated by Slic3r 0.9.7 on Fri Dec 18 00:34:03 2015
+acceleration = 0
+bed_size = 140,140
+bed_temperature = 60
+bottom_solid_layers = 2
+bridge_fan_speed = 100
+bridge_flow_ratio = 1
+bridge_speed = 60
+brim_width = 0
+complete_objects = 0
+cooling = 1
+disable_fan_first_layers = 1
+duplicate = 1
+duplicate_distance = 6
+duplicate_grid = 1,1
+end_gcode = M83 ; extruder relative\nG1 F1000 ;1000mm/min extrusion\nG1 E-10; retract\nG1 X0 Y0 F10000\nG28 X0 Y0\nM104 S0 ; turn off temperature\nM140 S0 ; turn off bed\nM84     ; disable motors
+external_perimeter_speed = 100%
+extra_perimeters = 1
+extruder_clearance_height = 20
+extruder_clearance_radius = 20
+extruder_offset = 0x0
+extrusion_axis = E
+extrusion_multiplier = 1.0
+extrusion_width = 0
+fan_always_on = 0
+fan_below_layer_time = 60
+filament_diameter = 1.77
+fill_angle = 45
+fill_density = 0.2
+fill_pattern = rectilinear
+first_layer_bed_temperature = 60
+first_layer_extrusion_width = 200%
+first_layer_height = 0.4
+first_layer_speed = 30%
+first_layer_temperature = 205
+g0 = 0
+gap_fill_speed = 20
+gcode_arcs = 0
+gcode_comments = 0
+gcode_flavor = reprap
+infill_acceleration = 50
+infill_every_layers = 1
+infill_extruder = 1
+infill_extrusion_width = 0
+infill_speed = 60
+layer_gcode = 
+layer_height = 0.4
+max_fan_speed = 100
+min_fan_speed = 35
+min_print_speed = 10
+min_skirt_length = 0
+notes = 
+nozzle_diameter = 0.50
+only_retract_when_crossing_perimeters = 1
+output_filename_format = [input_filename_base].gcode
+perimeter_acceleration = 25
+perimeter_extruder = 1
+perimeter_extrusion_width = 100%
+perimeter_speed = 30
+perimeters = 2
+post_process = 
+print_center = 70,70
+randomize_start = 1
+retract_before_travel = 2
+retract_length = 4.5
+retract_length_toolchange = 3
+retract_lift = 0.1
+retract_restart_extra = 0
+retract_restart_extra_toolchange = 0
+retract_speed = 30
+rotate = 0
+scale = 1
+skirt_distance = 3
+skirt_height = 1
+skirts = 2
+slowdown_below_layer_time = 15
+small_perimeter_speed = 30
+solid_fill_pattern = rectilinear
+solid_infill_below_area = 1
+solid_infill_every_layers = 0
+solid_infill_speed = 60
+start_gcode = G28 ; home all axes
+support_material = 0
+support_material_angle = 0
+support_material_extruder = 1
+support_material_extrusion_width = 0
+support_material_pattern = rectilinear
+support_material_spacing = 2.5
+support_material_speed = 60
+support_material_threshold = 45
+temperature = 205
+threads = 8
+top_solid_infill_speed = 50
+top_solid_layers = 2
+travel_speed = 130
+use_relative_e_distances = 0
+vibration_limit = 0
+z_offset = 0
diff --git a/smallfilamentclip.scad b/smallfilamentclip.scad
new file mode 100644 (file)
index 0000000..724d4cf
--- /dev/null
@@ -0,0 +1,40 @@
+include <cliphook.scad>
+include <filamentteeth.scad>
+
+rad=7.7;
+smrad=2.0;
+h=3.5;
+w=2.5;
+teethw=1.5;
+
+looprad=2.5;
+loopw=w;
+
+fdia=1.77;
+//fdia=3;
+
+d=0.01;
+
+module FilamentClip() {
+  linear_extrude(height=h) {
+    assign($fn=80) {
+      FlatArc(0,0, rad-w/2,rad+w/2, 80,361);
+    }
+    assign($fn=30) {
+      FlatArc(0,rad+looprad+w, looprad,looprad+loopw);
+    }
+    FlatArc(0, rad-smrad, smrad-w/2,smrad+w/2, -55,91);
+    FlatArc(rad-smrad, 0, smrad-w/2,smrad+w/2, 145,-1);
+  }
+
+  for (mir=[0,1]) {
+    mirror([0,mir,0])
+      mirror([1,0,0])
+      rotate([0,0,-56])
+      translate([rad+w*0.3+teethw*0.3+fdia/2 -0.6, -1.1, 0])
+      rotate([0,0,95])
+      FilamentTeeth(fdia=fdia);
+  }
+}
+
+FilamentClip();
diff --git a/splitpin.scad b/splitpin.scad
new file mode 100644 (file)
index 0000000..9631c95
--- /dev/null
@@ -0,0 +1,81 @@
+// -*- C -*-
+
+include <cliphook.scad>
+
+tau = 6.28318530718;
+function deg2rad(deg) = deg/360 * tau;
+function rad2deg(rad) = rad/tau * 360;
+
+module SplitPin(w=1.5, holeminrad=2.50, thick=3, deviationrad=1.5,
+               mainlen=15, handlerad=20, handlelen=12) {
+  spare = holeminrad*2 - deviationrad - w*2;
+  echo("splitpin spare",spare);
+  %translate([0,mainlen+handlelen,0]) cylinder(r=spare, h=thick);
+  %translate([0,mainlen,thick/2]) rotate([90,0,0])
+     cylinder(r=holeminrad, h=thick);
+
+  bent_dx = holeminrad;
+  unbent_dx = bent_dx + deviationrad;
+
+  unbent_subang = atan(unbent_dx / mainlen);
+  unbent_rad = mainlen / deg2rad(unbent_subang);
+
+  corner_x = unbent_rad * (1 - cos(unbent_subang));
+  corner_y = unbent_rad * sin(unbent_subang);
+
+  main_cx = unbent_rad;
+
+//  translate([w*1.5, 0, 0]) {
+//    translate([corner_x, corner_y, 10]) %cube([10,10,10]);
+//    translate([bent_dx, 0, 10]) %cube([10,10,10]);
+//    translate([unbent_dx, 5, 10]) %cube([10,10,10]);
+//  }
+
+  linear_extrude(height=thick) {
+    for (xmir=[0,1]) mirror([xmir,0,0])
+      FlatArc(0,0, w*0.5, w*1.5, 270-1,360);
+    translate([w*1.5, 0, 0]) {
+      FlatArc($fa=1, main_cx,0, unbent_rad, unbent_rad+w,
+             180-unbent_subang, 180);
+      translate([corner_x, corner_y]) rotate([0,0,-unbent_subang]) {
+       rotate([0,0,10])
+         translate([w*0.2,0,0])
+         translate([-(w + deviationrad), -0.1])
+         square(size=[w + deviationrad, w+0.1]);
+       FlatArc(-deviationrad + handlerad, w,
+               handlerad, handlerad+w,
+               180-rad2deg(handlelen/handlerad), 180+rad2deg(w/handlerad),
+               $fa=0.25, $fn=60);
+      }
+    }
+    mirror([1,0,0]) translate([w*1.5, 0, 0])
+      FlatArc($fa=1, main_cx,0, unbent_rad, unbent_rad+w,
+             180-(unbent_subang + rad2deg((handlelen+w)/unbent_rad)), 180);
+  }
+}
+
+module SplitPinCavity(w=1.5, holeminrad=2.50, thick=3, deviationrad=1.5,
+                     mainlen=15, slop=0.5, insertby = 5) {
+  smallgap2 = holeminrad;
+  biggap2 = smallgap2 + deviationrad + slop;
+  toegap2 = w*1.5 + slop;
+  toeend = -mainlen-insertby;
+
+  translate([0,thick/2,0]) rotate([90,0,0]) {
+    linear_extrude(height = thick + slop*2) {
+    for (xmir=[0,1]) mirror([xmir,0]) {
+       polygon([[-0.1, 1],
+                [(smallgap2+biggap2)/2, 1],
+                [smallgap2, -insertby],
+                [biggap2, -insertby],
+                [toegap2, toeend-1],
+                [-0.1, toeend-1]]);
+      }
+    }
+  }
+}
+
+SplitPin();
+translate([0,15+5,-10])
+  rotate([-90,0,0])
+  SplitPinCavity();
diff --git a/sprinkler-spike-receptacle.scad b/sprinkler-spike-receptacle.scad
new file mode 100644 (file)
index 0000000..c1ac3b5
--- /dev/null
@@ -0,0 +1,200 @@
+// -*- C -*-
+
+main_height = 95;
+
+spike_web_thick = 2.52 + 0.75;
+
+spike_top_width = 21.04 + 1.5;
+
+spike_botpos_height = 9.5;
+spike_botpos_width = 11.68 + 0.00;
+
+topwall_width = 1.5;
+
+mount_dist = 20;
+mount_width = 10;
+mount_height = 5;
+mount_hole_dia = 4.5;
+mount_head_dia = 7.5;
+mount_hole_th = 2.5;
+
+strap_height = main_height * 0.5;
+
+strap_width = 5.5;
+strap_thick = 2.5;
+strap_around = 2.5;
+strap_fixing_height = 4.0;
+strap_fixing_slope = 1.0;
+
+// calculated
+
+main_width = spike_top_width + topwall_width*2;
+
+pos_web_thick = spike_web_thick + topwall_width*2;
+
+module NegativePlan(){
+  x4z =
+    (spike_top_width - spike_botpos_width) /
+    (main_height - spike_botpos_height);
+
+  x0 = (spike_botpos_width - x4z * spike_botpos_height)/2;
+  x1 =  spike_top_width/2;
+  z1 = main_height;
+
+  polygon([[ x0, -5],
+          [ x0, 0],
+          [ x1, z1],
+          [ x1, z1+5],
+          [-x1, z1+5],
+          [-x1, z1],
+          [-x0, 0],
+          [-x0, -5]]);
+}
+
+module SomeMidRounding(sq_size, z_extra) {
+  translate([0,0,-z_extra])
+    linear_extrude(height= main_height + z_extra*2)
+    rotate(45)
+    square( sq_size, center=true );
+}
+
+module PositiveMidRounding(){
+  SomeMidRounding(pos_web_thick*2, 0);
+}
+
+module NegativeMidRounding(){
+  SomeMidRounding(spike_web_thick*2.5, 5);
+}
+
+module PositivePlan(){
+  w = main_width;
+  translate([ -w/2, 0 ])
+    square([ w, main_height ]);
+}
+
+module MultiplySolidifyPlan(th){
+  for (r=[0,90]) {
+    rotate([0,0,r])
+      rotate([90,0,0])
+      translate([0,0,-th/2])
+      linear_extrude(height=th)
+      children(0);
+  }
+}
+
+module MultiplyForFixings(){
+  for (r=[0:90:270])
+    rotate([0,0,r])
+    children(0);
+}
+
+module FixingsPositive(){
+  // mount
+  translate([ -1,
+             -mount_width/2,
+             0 ])
+    cube([ mount_dist + mount_width/2 + 1,
+          mount_width,
+          mount_height ]);
+
+  // strap
+  for (m=[0,1]) mirror([0,m,0]) {
+      translate([main_width/2, 0, strap_height]) {
+       hull(){
+         translate([ -strap_around,
+                     -pos_web_thick/2,
+                     -(strap_thick + strap_around) / strap_fixing_slope ])
+           cube([ strap_around,
+                  pos_web_thick/2 - strap_width/2,
+                  0.5 ]);
+         translate([ -strap_around,
+                     -(strap_around + strap_width/2),
+                     0 ])
+           cube([ strap_around*2 + strap_thick,
+                  strap_around,
+                  strap_fixing_height ]);
+       }
+       mirror([0,1,0])
+         translate([ strap_thick,
+                     -strap_width/2,
+                     0 ])
+         cube([ strap_around,
+                strap_around + strap_width,
+                strap_fixing_height ]);
+      }
+    }
+}
+
+module FixingsNegative(){
+  // mount hole
+  translate([ mount_dist, 0,0 ]) {
+    translate([0,0, -1])
+      cylinder(r= mount_hole_dia/2, h= 20, $fn=20);
+    translate([0,0, mount_hole_th])
+      cylinder(r = mount_head_dia/2, h=20, $fn=20);
+  }
+}
+
+module Main(){
+  difference(){
+    union(){
+      MultiplySolidifyPlan(pos_web_thick) PositivePlan();
+      PositiveMidRounding();
+      MultiplyForFixings() FixingsPositive();
+    }
+    MultiplySolidifyPlan(spike_web_thick) NegativePlan();
+    NegativeMidRounding();
+    MultiplyForFixings() FixingsNegative();
+  }
+}
+
+module PlanTest(){
+  linear_extrude(height=2.0){
+    difference(){
+      PositivePlan();
+      NegativePlan();
+    }
+    difference(){
+      circle(r = spike_botpos_width/2 + 5);
+      circle(r = spike_botpos_width/2);
+      translate([-50, 0]) square([100,50]);
+    }
+  }
+  linear_extrude(height=4.0){
+    difference(){
+      translate([ -main_width/2, 0 ]) square([ main_width, 2 ]);
+      NegativePlan();
+    }
+  }
+}
+
+module MainFitTest(){
+  for (top = [0,1]) {
+    translate([ top * (mount_dist*2 + mount_width), 0,0 ]){
+      intersection(){
+       translate([0, 0, (-main_height + 0.5) * top])
+         Main();
+       translate([-50,-50,0])
+         cube([100,100, spike_botpos_height + 1.5]);
+      }
+    }
+  }
+}
+
+module Tests(){
+  translate([-mount_dist*3, 0,0])
+    PlanTest();
+  MainFitTest();
+}
+
+module StrapFixingTest(){
+  intersection(){
+    Main();
+    translate([ -10, -10, 40 ])
+      cube([ 20, 40, 15 ]);
+  }
+}
+
+//Tests();
+//StrapFixingTest();
+Main();
diff --git a/startech-dell-usb-cable-retainer.scad b/startech-dell-usb-cable-retainer.scad
new file mode 100644 (file)
index 0000000..122f76c
--- /dev/null
@@ -0,0 +1,87 @@
+// -*- C -*-
+
+include <utils.scad>
+
+body_depth = 40.15 + 4;
+body_height = 16.78 + 0.50;
+
+back_round_depth = 2.0;
+back_round_rad = 8.0;
+back_above_height = 5.3;
+back_thick = 3.0;
+
+conn_thick = 6.42 + 2.25;
+wire_thick = 6.00 + 0.75;
+total_depth = 63.82 - 1.0;
+
+body_front_overlap = 3;
+
+prong_higher = 1.5;
+prong_depth = 5.0;
+prong_width = 2.0;
+
+base_thick = 4;
+
+$fa= 3;
+$fs= 0.3;
+
+// calculated
+
+epp0 = [ 0, -body_height/2 ];
+epp1 = [ 0, -conn_thick/2 ];
+epp2 = epp1 + [ -body_front_overlap, 0 ];
+epp3 = [ +body_depth -total_depth, epp2[1] ];
+epp4 = [ epp3[0], +conn_thick/2 +prong_higher ];
+epp4a = epp4 + prong_higher * [0,-1];
+epp4b = epp4 + prong_higher * [1,0];
+epp5 = epp4 + [ -prong_depth, 0 ];
+epp6 = [ epp5[0], epp0[1] -base_thick ];
+epp7 = [ epp2[0], epp6[1] ];
+epp12 = [ +body_depth +back_round_rad, 0 ];
+epp11 = [ +body_depth +back_round_depth, epp0[1] ];
+epp10 = [ epp11[0], +back_above_height ];
+epp9 = epp10 + [ +back_thick, 0 ];
+epp8 = [ epp9[0], epp7[1] ];
+  
+y1 = wire_thick/2;
+y2 = y1 + prong_width;
+
+module MainElevation(){
+  polygon([epp0,
+          epp1,
+          epp3,
+          epp4a,
+          epp4b,
+          epp5,
+          epp6,
+          epp8,
+          epp9,
+          epp10,
+          epp11
+          ]);
+  intersection(){
+    translate(epp12) circle(r= back_round_rad);
+    rectfromto(epp8,
+              epp10 + [-back_round_rad, 0]);
+  }
+}
+
+module Retainer(){
+  difference(){
+    linextr_y_xz( -y2, +y2 ) {
+      MainElevation();
+    }
+    linextr_y_xz( -y1, +y1 ) {
+      rectfromto( epp7 + [+1, -1],
+                 epp5 + [-1, +1] );
+    }
+  }
+}
+
+module RetainerPrint(){
+  rotate([0,0,180]) Retainer();
+}
+
+//MainElevation();
+
+RetainerPrint();
diff --git a/steamer-handle-clip.scad b/steamer-handle-clip.scad
new file mode 100644 (file)
index 0000000..5001861
--- /dev/null
@@ -0,0 +1,50 @@
+// -*- C -*-
+
+include <funcs.scad>
+include <utils.scad>
+
+width = 30 - 2;
+cup = 2.5;
+jaw = 32.36 - 2.00 - 2.00 - 3.00;
+th = 3.0;
+l = 15;
+
+a = cup;
+b = width/2;
+alpha = atan2(a, b);
+c = vectorlen2d([a, b]);
+r = a / (1 - cos(2*alpha));
+C = [0, a-r];
+
+$fa = 1;
+
+module HalfBaseline() {
+  intersection(){
+    translate(C + [0, jaw/2])
+      circle(r=r);
+    rectfromto([ -width/2, -1, ],
+              [  width/2, jaw ]);
+  }
+}
+
+module Baseline(){
+  HalfBaseline();
+  mirror([0,1]) HalfBaseline();
+}
+
+module Plan(){
+  difference(){
+    offset(delta=th) Baseline();
+    Baseline();
+    rectfromto([-width, -jaw/2],
+              [0,       jaw/2]);
+  }
+}
+
+module Thing(){
+  linextr(0,l) Plan();
+}
+
+//HalfPlan();
+//Plan();
+Thing();
diff --git a/stringing-test.scad b/stringing-test.scad
new file mode 100644 (file)
index 0000000..a75704d
--- /dev/null
@@ -0,0 +1,15 @@
+// -*- C -*-
+
+height = 15;
+$fn= 20;
+
+cylinder(r=7, h=height);
+
+for (r= [0:4]) {
+  rotate(r * 72) {
+    d = 10 * pow(1.5, r);
+    cube([d, 3, 2]);
+    translate([d, 0,0])
+      cube([7, 7, height]);
+  }
+}
diff --git a/summit-lantern-hook.scad b/summit-lantern-hook.scad
new file mode 100644 (file)
index 0000000..e91573a
--- /dev/null
@@ -0,0 +1,64 @@
+// -*- C -*-
+
+include <utils.scad>
+
+height = 60;
+curl = 10;
+width = 85;
+sides_depth = 50;
+th = 6;
+th2 = 4;
+
+$fa = 3;
+$fs = 0.3;
+
+// calculated
+
+upper_r = th/2;
+upper_ctr_maj_r = curl/2 + upper_r;
+
+zmin = curl/2 + th;
+
+module UpperPlan(){
+  circle(r = upper_r);
+}
+
+module EndCurl(){
+  rotate([90,0,0])
+    rotate_extrude(angle=180)
+    translate([upper_ctr_maj_r, 0])
+    UpperPlan();
+  translate([-upper_ctr_maj_r, 0,0])
+    sphere(r= upper_r);
+}
+
+module Upper(){
+  translate([upper_ctr_maj_r, 0, 0])
+    linextr(-0.1, height + 0.1)
+    UpperPlan();
+  translate([0, 0, height])
+    EndCurl();
+}
+
+module Lower(){
+  rotate([180,0,0])
+    EndCurl();
+  linextr(-zmin, -zmin + th) {
+    square(center=true, [th2, width]);
+    for (m=[0,1])
+      mirror([0,m])
+       hull()
+      {
+       for (x= sides_depth/2 * [-1,+1])
+         translate([ x, width/2 - th2/2 ])
+           circle(r= th2/2);
+      }
+  }
+}
+
+module Hook(){
+  Upper();
+  Lower();
+}
+
+Hook();
diff --git a/svg-prep-dxf b/svg-prep-dxf
new file mode 100755 (executable)
index 0000000..a5ecc38
--- /dev/null
@@ -0,0 +1,178 @@
+#!/bin/bash
+us=svg-prep-dxf
+usage () { cat <<END
+usage: $us [--flatness=VALUE] IN.svg OUT.dxf
+
+Turns a stroked path into its outline path; "union"s it (so that it
+does not self-intersect); approximates its bezier curves with line
+segments; and produces a dxf for openscad.
+END
+}
+# Copyright 2016 Ian Jackson
+#
+# This program is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the Free
+# Software Foundation; either version 2 of the License, or (at your option)
+# any later version.
+#
+# As a special exception, you have permission to link this program
+# with the CGAL library and distribute executables, as long as you
+# follow the requirements of the GNU GPL in regard to all of the
+# software in the executable aside from CGAL.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+# more details.
+
+set -e
+
+fail () { echo >&2 "svg-dxf-prep: $*"; exit 1; }
+badusage () { usage >&2; exit 1; }
+
+flatness=0.01
+# ^ this number seems to work reasonably well
+#   although maybe it should be adjusted?
+
+while true; do
+    case "$1" in
+    --flatness=*)
+       flatness="${1#*=}"
+       ;;
+    -*)
+       badusage
+       ;;
+    *)
+       break
+       ;;
+    esac
+    shift
+done
+
+case "$#" in
+2)  ;;
+*)  badusage ;;
+esac
+
+source=$1
+dest=$2
+
+case "$source" in
+*.svg) ;;
+*)  badusage ;;
+esac
+
+case "$dest" in
+*.dxf) ;;
+*)  badusage ;;
+esac
+
+tmpdir="$(dirname "$dest")/.$(basename "$dest").tmpdir"
+rm -rf -- "$tmpdir"
+mkdir -- "$tmpdir"
+
+case "$tmpdir" in
+/*)  abstmpdir="$tmpdir" ;;
+*)   abstmpdir="$PWD/$tmpdir" ;;
+esac
+
+cp -- "$source" "$tmpdir/t.svg"
+
+# startx seems to send "client" stdout/stderr somewhere hopeless
+exec 4>&2
+
+baseextdir=$(inkscape -x)
+ourid=uk.org.greenend.chiark.ijackson.swirly.flatten
+
+# inkscape doesn't provide a way to specify a per-invocation
+# extension directory, but it does look in $HOME.
+# Anyway, we're going to want a fresh HOME for startx.
+ourxd="$tmpdir"/.config/inkscape/extensions
+mkdir -p "$ourxd"
+mkdir -p "$tmpdir/.local/share" # inkscape complains otherwise, wtf
+
+# Putting the absolute path in the XML doesn't seem to work:
+# inkscape complains that this "dependency" (implicit, apparently)
+# is not satisfied.
+# I'm not sure why this is, but this does work:
+ln -s -- "$baseextdir/flatten.py" "$ourxd"
+
+xmlstarlet \
+ed \
+ -N ie=http://www.inkscape.org/namespace/inkscape/extension \
+ -u "/ie:inkscape-extension/ie:_name" -v "Our Flatten Beziers" \
+ -u "/ie:inkscape-extension/ie:id" -v $ourid \
+ -d '/ie:inkscape-extension/ie:dependency' \
+ -u '/ie:inkscape-extension/ie:param[@name="flatness"]' -v $flatness \
+ "$baseextdir"/flatten.inx \
+ >"$ourxd"/our-flatten.inx
+# known bugs with this approach:
+#  - we rely on the .inx filename
+#  - we rely on flatten.py being in the extensions/ directory
+#    and called exactly that
+#  - we drop the dependencies rather than editing them
+
+cat <<'END' >"$tmpdir/run-inkscape"
+#!/bin/sh
+exec >&4 2>&4
+echo
+printf "running inkscape to transform and clean up..."
+cd "$HOME"
+inkscape \
+ --with-gui \
+ t.svg \
+ --verb=ToolNode \
+ --verb=EditSelectAll \
+ --verb=SelectionUnGroup \
+ --verb=EditSelectAll \
+ --verb=StrokeToPath \
+ --verb=ToolNode \
+ --verb=EditSelectAll \
+ --verb=SelectionUnion \
+ --verb=EditSelectAll \
+ --verb=uk.org.greenend.chiark.ijackson.swirly.flatten.noprefs \
+ --verb=FileSave \
+ --verb=FileQuit \
+
+echo done.
+echo
+END
+chmod +x "$tmpdir"/run-inkscape
+
+cat <<END
+
+Expect some complaints from xinit, for example
+  about XFree86_VT property
+  lost connection
+  file descriptors for console
+END
+
+HOME="$abstmpdir" \
+/usr/bin/startx \
+ "$abstmpdir"/run-inkscape \
+ -- \
+ `type -p Xvfb`
+
+# It's awkward to get inkscape to save as DXF noninteractively.
+# And inkscape doesn't seem to like to exit if I try to export
+# from the "interactive" one.
+
+# But anyway inkscape DXF is just made with pstoedit.  So save as SVG,
+# and then do a separate inkscape run to convert EPS, and run pstoedit
+# to make the DXF.
+
+inkscape --export-area-page \
+ -f "$tmpdir"/t.svg \
+ -E "$tmpdir"/t.eps \
+
+pstoedit -dt -f 'dxf:-polyaslines -mm' \
+ "$tmpdir"/t.eps \
+ "$tmpdir/"t.dxf
+
+mv -- "$tmpdir"/t.dxf "$dest"
+
+echo
+printf "$us: success, wrote %s\n" "$dest"
+echo
+
+exit 0
diff --git a/tablet-case-corner-mount.scad b/tablet-case-corner-mount.scad
new file mode 100644 (file)
index 0000000..d981afd
--- /dev/null
@@ -0,0 +1,86 @@
+// -*- C -*-
+
+main_sz = 22.5;
+
+wall_th = 3;
+
+front_hook = 3;
+front_slope = 1.0;
+
+front_th = 2;
+gap_th = 6.5 + 0.5;
+
+back_tot_l = 35.5;
+back_cut_l = 6;
+back_cut_w = 15.0 + 1.0;
+back_prong_w = 3;
+back_hole_d = 2;
+back_hole_dia = 1 + 1.5;
+
+back_th = back_hole_dia + 2.4;
+
+module MidPlan(){
+  polygon([[0,            0],
+          [0,            main_sz],
+          [wall_th,      main_sz],
+          [wall_th,      wall_th],
+          [main_sz,      wall_th],
+          [main_sz,      0]]);
+}
+
+module FrontElevation(){
+  hook_z = front_hook / front_slope;
+  translate([0, back_th+gap_th]) {
+    hull(){
+      square([wall_th, hook_z + 0.01]);
+      translate([front_hook, hook_z])
+       square([wall_th, 0.01]);
+    }
+  }
+}
+
+module FrontEdge(){
+  rotate([90,0,0]) linear_extrude(height=main_sz) FrontElevation();
+}
+
+module Front(){
+  mirror([0,1,0]) FrontEdge();
+  rotate([0,0,90]) FrontEdge();
+}
+
+module BackPlan(){
+  sqmid = main_sz * sqrt(0.5);
+  prlen = back_tot_l - sqmid;
+  prx = 0.5*back_cut_w + back_prong_w;
+  difference(){
+    union(){
+      square(main_sz);
+      rotate(-45) translate([-prx, sqmid])
+       square([prx*2,prlen]);
+    }
+    rotate(-45) translate([-back_cut_w/2, back_tot_l-back_cut_l])
+      square([back_cut_w, back_cut_l+1]);
+  }
+}
+
+module Hook(){
+  difference(){
+    union(){
+      linear_extrude(height=back_th)
+       BackPlan();
+      linear_extrude(height=back_th+gap_th+front_th)
+       MidPlan();
+      Front();
+    }
+    rotate([0,0,-45])
+      translate([0, back_tot_l - back_hole_d, back_th/2])
+      rotate([0,90,0]) translate([0,0,-50])
+      cylinder(h=100, r=back_hole_dia/2, $fn=40);
+  }
+}
+
+//MidPlan();
+//FrontPlan();
+//BackPlan();
+//Front();
+Hook();
diff --git a/tablet-stand.scad b/tablet-stand.scad
new file mode 100644 (file)
index 0000000..d7ef564
--- /dev/null
@@ -0,0 +1,72 @@
+// -*- C -*-
+
+whole_depth = 90;
+whole_width = 120;
+
+antifoot_width = 15;
+antifoot_height = 15;
+antifoot_slope = 1.0;
+antifoot_depth = 10;
+antifoot_base = 12;
+antifoot_front = 5;
+
+leg_width = 8;
+leg_thick = 8;
+
+post_height = 50;
+orifice_dia = 22.1 + 0.3;
+post_thick = 8;
+
+stretcher_thick = 5;
+stretcher_width = 8;
+
+antifoot_back = antifoot_depth + antifoot_height/antifoot_slope;
+post_rad = orifice_dia/2 + post_thick;
+
+module AntiFoot(){
+  translate([-antifoot_front-antifoot_back, antifoot_width/2, 0])
+    rotate([90,0,0])
+    translate([antifoot_front, antifoot_base, 0])
+    linear_extrude(height=antifoot_width)
+    polygon([[-antifoot_front, -antifoot_base],
+          [-antifoot_front,  antifoot_height],
+          [0,               antifoot_height],
+          [0,               0],
+          [antifoot_depth,  0],
+          [antifoot_back, antifoot_height],
+          [antifoot_back, -antifoot_base]]);
+  translate([-antifoot_back, 0, 0])
+    cube([stretcher_width, whole_width*0.55, stretcher_width]);
+}
+
+module LeftLeg(){
+  effective_depth = whole_depth - antifoot_back;
+  translate([-effective_depth, -whole_width/2, 0])
+    AntiFoot();
+  hull(){
+    translate([-effective_depth-leg_width/2, -whole_width/2, 0])
+      cylinder(r=leg_width/2, h=antifoot_base);
+    cylinder(r=leg_width/2, h=post_height);
+  }
+}
+
+module RightLeg(){
+  mirror([0,1,0]) LeftLeg();
+}
+
+module Post(){
+  cylinder(h=post_height, r=post_rad, $fn=70);
+}
+
+module Stand(){
+  difference(){
+    union(){
+      LeftLeg();
+      RightLeg();
+      Post();
+    }
+    translate([0,0,-1]) cylinder(h=post_height+2, r=orifice_dia/2);
+  }
+}
+
+Stand();
diff --git a/test-cup.scad b/test-cup.scad
new file mode 100644 (file)
index 0000000..5bb826a
--- /dev/null
@@ -0,0 +1,12 @@
+// -*- C -*-
+
+radius = 25/2;
+wall = 1.0;
+height = 40;
+floor = 1.0;
+
+difference(){
+  cylinder(r=radius+wall, h=height);
+  translate([0,0, floor])
+    cylinder(r=radius, h=height);
+}
diff --git a/test-object.scad b/test-object.scad
new file mode 100644 (file)
index 0000000..359254c
--- /dev/null
@@ -0,0 +1,6 @@
+size=1.7;
+scale([size,size,size])
+difference() {
+    cube(size=[10,10,4.0], center=true);
+    translate([0,0,2.0]) sphere(r=4.5, $fn=50);
+}
diff --git a/thread-external-test.scad b/thread-external-test.scad
new file mode 100644 (file)
index 0000000..6899632
--- /dev/null
@@ -0,0 +1,33 @@
+// -*- C -*-
+
+include <threads.scad>
+include <utils.scad>
+
+// https://en.wikipedia.org/wiki/ISO_metric_screw_thread
+
+// M6
+thread_nom = 6;
+thread_pitch = 1.00;
+thread_act = thread_nom - 0.300;
+head_size = 10;
+
+thread_len = 12.5;
+base_th = 1.5;
+
+$test = false;
+
+// calculated
+
+base_dia = head_size / cos(30);
+
+module MachineScrew(){
+  translate([0, 0, -0.1])
+    render()
+    metric_thread(diameter=thread_act, pitch=thread_pitch,
+                 leadin=1, internal=false,
+                 test=$test, length=thread_len + 0.1);
+  linextr(-base_th, 0)
+    circle(r= base_dia/2, $fn=6);
+}
+
+MachineScrew();
diff --git a/thread-internal-test.scad b/thread-internal-test.scad
new file mode 100644 (file)
index 0000000..cab40b6
--- /dev/null
@@ -0,0 +1,46 @@
+// -*- C -*-
+
+include <threads.scad>
+include <utils.scad>
+
+// https://en.wikipedia.org/wiki/ISO_metric_screw_thread
+
+// M6
+thread_nom = 4;
+thread_pitch = 0.70;
+thread_act = thread_nom + 0.375;
+head_size = 10;
+
+thread_len = 12.5;
+base_th = 1.5;
+base_sz = [40, head_size];
+
+$test = false;
+
+// calculated
+
+base_dia = head_size / cos(30);
+
+module ScrewThread(){
+  translate([0, 0, -0.1])
+    render()
+    metric_thread(diameter=thread_act, pitch=thread_pitch,
+                 leadin=1, internal=true,
+                 test=$test, length=thread_len + 0.1);
+}
+
+module TestThread(){
+  difference(){
+    union(){
+      linextr(-base_th, 0)
+       square(center=true, base_sz);
+
+      linextr(-base_th, thread_len - 0.1)
+       circle(r= base_dia/2, $fn=6);
+    }
+
+    ScrewThread();
+  }
+}
+
+TestThread();
deleted file mode 100644 (file)
index b2eee23309a8de23d208d0473ad6620bb1a8be04..0000000000000000000000000000000000000000
+++ /dev/null
@@ -1,407 +0,0 @@
-/*\r
- * ISO-standard metric threads, following this specification:\r
- *          http://en.wikipedia.org/wiki/ISO_metric_screw_thread\r
- *\r
- * Copyright 2020 Dan Kirshner - dan_kirshner@yahoo.com\r
- * This program is free software: you can redistribute it and/or modify\r
- * it under the terms of the GNU General Public License as published by\r
- * the Free Software Foundation, either version 3 of the License, or\r
- * (at your option) any later version.\r
- *\r
- * This program is distributed in the hope that it will be useful,\r
- * but WITHOUT ANY WARRANTY; without even the implied warranty of\r
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
- * GNU General Public License for more details.\r
- *\r
- * See <http://www.gnu.org/licenses/>.\r
- *\r
- * Version 2.5.  2020-04-11  Leadin option works for internal threads.\r
- * Version 2.4.  2019-07-14  Add test option - do not render threads.\r
- * Version 2.3.  2017-08-31  Default for leadin: 0 (best for internal threads).\r
- * Version 2.2.  2017-01-01  Correction for angle; leadfac option.  (Thanks to\r
- *                           Andrew Allen <a2intl@gmail.com>.)\r
- * Version 2.1.  2016-12-04  Chamfer bottom end (low-z); leadin option.\r
- * Version 2.0.  2016-11-05  Backwards compatibility (earlier OpenSCAD) fixes.\r
- * Version 1.9.  2016-07-03  Option: tapered.\r
- * Version 1.8.  2016-01-08  Option: (non-standard) angle.\r
- * Version 1.7.  2015-11-28  Larger x-increment - for small-diameters.\r
- * Version 1.6.  2015-09-01  Options: square threads, rectangular threads.\r
- * Version 1.5.  2015-06-12  Options: thread_size, groove.\r
- * Version 1.4.  2014-10-17  Use "faces" instead of "triangles" for polyhedron\r
- * Version 1.3.  2013-12-01  Correct loop over turns -- don't have early cut-off\r
- * Version 1.2.  2012-09-09  Use discrete polyhedra rather than linear_extrude ()\r
- * Version 1.1.  2012-09-07  Corrected to right-hand threads!\r
- */\r
-\r
-// Examples.\r
-//\r
-// Standard M8 x 1.\r
-// metric_thread (diameter=8, pitch=1, length=4);\r
-\r
-// Square thread.\r
-// metric_thread (diameter=8, pitch=1, length=4, square=true);\r
-\r
-// Non-standard: long pitch, same thread size.\r
-//metric_thread (diameter=8, pitch=4, length=4, thread_size=1, groove=true);\r
-\r
-// Non-standard: 20 mm diameter, long pitch, square "trough" width 3 mm,\r
-// depth 1 mm.\r
-//metric_thread (diameter=20, pitch=8, length=16, square=true, thread_size=6,\r
-//               groove=true, rectangle=0.333);\r
-\r
-// English: 1/4 x 20.\r
-//english_thread (diameter=1/4, threads_per_inch=20, length=1);\r
-\r
-// Tapered.  Example -- pipe size 3/4" -- per:\r
-// http://www.engineeringtoolbox.com/npt-national-pipe-taper-threads-d_750.html\r
-// english_thread (diameter=1.05, threads_per_inch=14, length=3/4, taper=1/16);\r
-\r
-// Thread for mounting on Rohloff hub.\r
-//difference () {\r
-//   cylinder (r=20, h=10, $fn=100);\r
-//\r
-//   metric_thread (diameter=34, pitch=1, length=10, internal=true, n_starts=6);\r
-//}\r
-\r
-\r
-// ----------------------------------------------------------------------------\r
-function segments (diameter) = min (50, max (ceil (diameter*6), 25));\r
-\r
-\r
-// ----------------------------------------------------------------------------\r
-// diameter -    outside diameter of threads in mm. Default: 8.\r
-// pitch    -    thread axial "travel" per turn in mm.  Default: 1.\r
-// length   -    overall axial length of thread in mm.  Default: 1.\r
-// internal -    true = clearances for internal thread (e.g., a nut).\r
-//               false = clearances for external thread (e.g., a bolt).\r
-//               (Internal threads should be "cut out" from a solid using\r
-//               difference ()).  Default: false.\r
-// n_starts -    Number of thread starts (e.g., DNA, a "double helix," has\r
-//               n_starts=2).  See wikipedia Screw_thread.  Default: 1.\r
-// thread_size - (non-standard) axial width of a single thread "V" - independent\r
-//               of pitch.  Default: same as pitch.\r
-// groove      - (non-standard) true = subtract inverted "V" from cylinder\r
-//                (rather thanadd protruding "V" to cylinder).  Default: false.\r
-// square      - true = square threads (per\r
-//               https://en.wikipedia.org/wiki/Square_thread_form).  Default:\r
-//               false.\r
-// rectangle   - (non-standard) "Rectangular" thread - ratio depth/(axial) width\r
-//               Default: 0 (standard "v" thread).\r
-// angle       - (non-standard) angle (deg) of thread side from perpendicular to\r
-//               axis (default = standard = 30 degrees).\r
-// taper       - diameter change per length (National Pipe Thread/ANSI B1.20.1\r
-//               is 1" diameter per 16" length). Taper decreases from 'diameter'\r
-//               as z increases.  Default: 0 (no taper).\r
-// leadin      - 0 (default): no chamfer; 1: chamfer (45 degree) at max-z end;\r
-//               2: chamfer at both ends, 3: chamfer at z=0 end.\r
-// leadfac     - scale of leadin chamfer length (default: 1.0 = 1/2 thread).\r
-// test        - true = do not render threads (just draw "blank" cylinder).\r
-//               Default: false (draw threads).\r
-module metric_thread (diameter=8, pitch=1, length=1, internal=false, n_starts=1,\r
-                      thread_size=-1, groove=false, square=false, rectangle=0,\r
-                      angle=30, taper=0, leadin=0, leadfac=1.0, test=false)\r
-{\r
-   // thread_size: size of thread "V" different than travel per turn (pitch).\r
-   // Default: same as pitch.\r
-   local_thread_size = thread_size == -1 ? pitch : thread_size;\r
-   local_rectangle = rectangle ? rectangle : 1;\r
-\r
-   n_segments = segments (diameter);\r
-   h = (test && ! internal) ? 0 : (square || rectangle) ? local_thread_size*local_rectangle/2 : local_thread_size / (2 * tan(angle));\r
-\r
-   h_fac1 = (square || rectangle) ? 0.90 : 0.625;\r
-\r
-   // External thread includes additional relief.\r
-   h_fac2 = (square || rectangle) ? 0.95 : 5.3/8;\r
-\r
-   tapered_diameter = diameter - length*taper;\r
-\r
-   difference () {\r
-      union () {\r
-         if (! groove) {\r
-            if (! test) {\r
-               metric_thread_turns (diameter, pitch, length, internal, n_starts,\r
-                                    local_thread_size, groove, square, rectangle, angle,\r
-                                    taper);\r
-            }\r
-         }\r
-\r
-         difference () {\r
-\r
-            // Solid center, including Dmin truncation.\r
-            if (groove) {\r
-               cylinder (r1=diameter/2, r2=tapered_diameter/2,\r
-                         h=length, $fn=n_segments);\r
-            } else if (internal) {\r
-               cylinder (r1=diameter/2 - h*h_fac1, r2=tapered_diameter/2 - h*h_fac1,\r
-                         h=length, $fn=n_segments);\r
-            } else {\r
-\r
-               // External thread.\r
-               cylinder (r1=diameter/2 - h*h_fac2, r2=tapered_diameter/2 - h*h_fac2,\r
-                         h=length, $fn=n_segments);\r
-            }\r
-\r
-            if (groove) {\r
-               if (! test) {\r
-                  metric_thread_turns (diameter, pitch, length, internal, n_starts,\r
-                                       local_thread_size, groove, square, rectangle,\r
-                                       angle, taper);\r
-               }\r
-            }\r
-         }\r
-\r
-         // Internal thread lead-in: take away from external solid.\r
-         if (internal) {\r
-\r
-            // "Negative chamfer" z=0 end if leadin is 2 or 3.\r
-            if (leadin == 2 || leadin == 3) {\r
-               cylinder (r1=diameter/2, r2=diameter/2 - h*h_fac1*leadfac, h=h*h_fac1*leadfac,\r
-                         $fn=n_segments);\r
-            }\r
-\r
-            // "Negative chamfer" z-max end if leadin is 1 or 2.\r
-            if (leadin == 1 || leadin == 2) {\r
-               translate ([0, 0, length + 0.05 - h*h_fac1*leadfac]) {\r
-                  cylinder (r1=tapered_diameter/2 - h*h_fac1*leadfac, h=h*h_fac1*leadfac,\r
-                            r2=tapered_diameter/2,\r
-                            $fn=n_segments);\r
-               }\r
-            }\r
-         }\r
-      }\r
-\r
-      if (! internal) {\r
-\r
-         // Chamfer z=0 end if leadin is 2 or 3.\r
-         if (leadin == 2 || leadin == 3) {\r
-            difference () {\r
-               cylinder (r=diameter/2 + 1, h=h*h_fac1*leadfac, $fn=n_segments);\r
-\r
-               cylinder (r2=diameter/2, r1=diameter/2 - h*h_fac1*leadfac, h=h*h_fac1*leadfac,\r
-                         $fn=n_segments);\r
-            }\r
-         }\r
-\r
-         // Chamfer z-max end if leadin is 1 or 2.\r
-         if (leadin == 1 || leadin == 2) {\r
-            translate ([0, 0, length + 0.05 - h*h_fac1*leadfac]) {\r
-               difference () {\r
-                  cylinder (r=diameter/2 + 1, h=h*h_fac1*leadfac, $fn=n_segments);\r
-\r
-                  cylinder (r1=tapered_diameter/2, r2=tapered_diameter/2 - h*h_fac1*leadfac, h=h*h_fac1*leadfac,\r
-                            $fn=n_segments);\r
-               }\r
-            }\r
-         }\r
-      }\r
-   }\r
-}\r
-\r
-\r
-// ----------------------------------------------------------------------------\r
-// Input units in inches.\r
-// Note: units of measure in drawing are mm!\r
-module english_thread (diameter=0.25, threads_per_inch=20, length=1,\r
-                      internal=false, n_starts=1, thread_size=-1, groove=false,\r
-                      square=false, rectangle=0, angle=30, taper=0, leadin=0,\r
-                      leadfac=1.0, test=false)\r
-{\r
-   // Convert to mm.\r
-   mm_diameter = diameter*25.4;\r
-   mm_pitch = (1.0/threads_per_inch)*25.4;\r
-   mm_length = length*25.4;\r
-\r
-   echo (str ("mm_diameter: ", mm_diameter));\r
-   echo (str ("mm_pitch: ", mm_pitch));\r
-   echo (str ("mm_length: ", mm_length));\r
-   metric_thread (mm_diameter, mm_pitch, mm_length, internal, n_starts,\r
-                  thread_size, groove, square, rectangle, angle, taper, leadin,\r
-                  leadfac, test);\r
-}\r
-\r
-// ----------------------------------------------------------------------------\r
-module metric_thread_turns (diameter, pitch, length, internal, n_starts,\r
-                            thread_size, groove, square, rectangle, angle,\r
-                            taper)\r
-{\r
-   // Number of turns needed.\r
-   n_turns = floor (length/pitch);\r
-\r
-   intersection () {\r
-\r
-      // Start one below z = 0.  Gives an extra turn at each end.\r
-      for (i=[-1*n_starts : n_turns+1]) {\r
-         translate ([0, 0, i*pitch]) {\r
-            metric_thread_turn (diameter, pitch, internal, n_starts,\r
-                                thread_size, groove, square, rectangle, angle,\r
-                                taper, i*pitch);\r
-         }\r
-      }\r
-\r
-      // Cut to length.\r
-      translate ([0, 0, length/2]) {\r
-         cube ([diameter*3, diameter*3, length], center=true);\r
-      }\r
-   }\r
-}\r
-\r
-\r
-// ----------------------------------------------------------------------------\r
-module metric_thread_turn (diameter, pitch, internal, n_starts, thread_size,\r
-                           groove, square, rectangle, angle, taper, z)\r
-{\r
-   n_segments = segments (diameter);\r
-   fraction_circle = 1.0/n_segments;\r
-   for (i=[0 : n_segments-1]) {\r
-      rotate ([0, 0, i*360*fraction_circle]) {\r
-         translate ([0, 0, i*n_starts*pitch*fraction_circle]) {\r
-            //current_diameter = diameter - taper*(z + i*n_starts*pitch*fraction_circle);\r
-            thread_polyhedron ((diameter - taper*(z + i*n_starts*pitch*fraction_circle))/2,\r
-                               pitch, internal, n_starts, thread_size, groove,\r
-                               square, rectangle, angle);\r
-         }\r
-      }\r
-   }\r
-}\r
-\r
-\r
-// ----------------------------------------------------------------------------\r
-module thread_polyhedron (radius, pitch, internal, n_starts, thread_size,\r
-                          groove, square, rectangle, angle)\r
-{\r
-   n_segments = segments (radius*2);\r
-   fraction_circle = 1.0/n_segments;\r
-\r
-   local_rectangle = rectangle ? rectangle : 1;\r
-\r
-   h = (square || rectangle) ? thread_size*local_rectangle/2 : thread_size / (2 * tan(angle));\r
-   outer_r = radius + (internal ? h/20 : 0); // Adds internal relief.\r
-   //echo (str ("outer_r: ", outer_r));\r
-\r
-   // A little extra on square thread -- make sure overlaps cylinder.\r
-   h_fac1 = (square || rectangle) ? 1.1 : 0.875;\r
-   inner_r = radius - h*h_fac1; // Does NOT do Dmin_truncation - do later with\r
-                                // cylinder.\r
-\r
-   translate_y = groove ? outer_r + inner_r : 0;\r
-   reflect_x   = groove ? 1 : 0;\r
-\r
-   // Make these just slightly bigger (keep in proportion) so polyhedra will\r
-   // overlap.\r
-   x_incr_outer = (! groove ? outer_r : inner_r) * fraction_circle * 2 * PI * 1.02;\r
-   x_incr_inner = (! groove ? inner_r : outer_r) * fraction_circle * 2 * PI * 1.02;\r
-   z_incr = n_starts * pitch * fraction_circle * 1.005;\r
-\r
-   /*\r
-    (angles x0 and x3 inner are actually 60 deg)\r
-\r
-                          /\  (x2_inner, z2_inner) [2]\r
-                         /  \\r
-   (x3_inner, z3_inner) /    \\r
-                  [3]   \     \\r
-                        |\     \ (x2_outer, z2_outer) [6]\r
-                        | \    /\r
-                        |  \  /|\r
-             z          |[7]\/ / (x1_outer, z1_outer) [5]\r
-             |          |   | /\r
-             |   x      |   |/\r
-             |  /       |   / (x0_outer, z0_outer) [4]\r
-             | /        |  /     (behind: (x1_inner, z1_inner) [1]\r
-             |/         | /\r
-    y________|          |/\r
-   (r)                  / (x0_inner, z0_inner) [0]\r
-\r
-   */\r
-\r
-   x1_outer = outer_r * fraction_circle * 2 * PI;\r
-\r
-   z0_outer = (outer_r - inner_r) * tan(angle);\r
-   //echo (str ("z0_outer: ", z0_outer));\r
-\r
-   //polygon ([[inner_r, 0], [outer_r, z0_outer],\r
-   //        [outer_r, 0.5*pitch], [inner_r, 0.5*pitch]]);\r
-   z1_outer = z0_outer + z_incr;\r
-\r
-   // Give internal square threads some clearance in the z direction, too.\r
-   bottom = internal ? 0.235 : 0.25;\r
-   top    = internal ? 0.765 : 0.75;\r
-\r
-   translate ([0, translate_y, 0]) {\r
-      mirror ([reflect_x, 0, 0]) {\r
-\r
-         if (square || rectangle) {\r
-\r
-            // Rule for face ordering: look at polyhedron from outside: points must\r
-            // be in clockwise order.\r
-            polyhedron (\r
-               points = [\r
-                         [-x_incr_inner/2, -inner_r, bottom*thread_size],         // [0]\r
-                         [x_incr_inner/2, -inner_r, bottom*thread_size + z_incr], // [1]\r
-                         [x_incr_inner/2, -inner_r, top*thread_size + z_incr],    // [2]\r
-                         [-x_incr_inner/2, -inner_r, top*thread_size],            // [3]\r
-\r
-                         [-x_incr_outer/2, -outer_r, bottom*thread_size],         // [4]\r
-                         [x_incr_outer/2, -outer_r, bottom*thread_size + z_incr], // [5]\r
-                         [x_incr_outer/2, -outer_r, top*thread_size + z_incr],    // [6]\r
-                         [-x_incr_outer/2, -outer_r, top*thread_size]             // [7]\r
-                        ],\r
-\r
-               faces = [\r
-                         [0, 3, 7, 4],  // This-side trapezoid\r
-\r
-                         [1, 5, 6, 2],  // Back-side trapezoid\r
-\r
-                         [0, 1, 2, 3],  // Inner rectangle\r
-\r
-                         [4, 7, 6, 5],  // Outer rectangle\r
-\r
-                         // These are not planar, so do with separate triangles.\r
-                         [7, 2, 6],     // Upper rectangle, bottom\r
-                         [7, 3, 2],     // Upper rectangle, top\r
-\r
-                         [0, 5, 1],     // Lower rectangle, bottom\r
-                         [0, 4, 5]      // Lower rectangle, top\r
-                        ]\r
-            );\r
-         } else {\r
-\r
-            // Rule for face ordering: look at polyhedron from outside: points must\r
-            // be in clockwise order.\r
-            polyhedron (\r
-               points = [\r
-                         [-x_incr_inner/2, -inner_r, 0],                        // [0]\r
-                         [x_incr_inner/2, -inner_r, z_incr],                    // [1]\r
-                         [x_incr_inner/2, -inner_r, thread_size + z_incr],      // [2]\r
-                         [-x_incr_inner/2, -inner_r, thread_size],              // [3]\r
-\r
-                         [-x_incr_outer/2, -outer_r, z0_outer],                 // [4]\r
-                         [x_incr_outer/2, -outer_r, z0_outer + z_incr],         // [5]\r
-                         [x_incr_outer/2, -outer_r, thread_size - z0_outer + z_incr], // [6]\r
-                         [-x_incr_outer/2, -outer_r, thread_size - z0_outer]    // [7]\r
-                        ],\r
-\r
-               faces = [\r
-                         [0, 3, 7, 4],  // This-side trapezoid\r
-\r
-                         [1, 5, 6, 2],  // Back-side trapezoid\r
-\r
-                         [0, 1, 2, 3],  // Inner rectangle\r
-\r
-                         [4, 7, 6, 5],  // Outer rectangle\r
-\r
-                         // These are not planar, so do with separate triangles.\r
-                         [7, 2, 6],     // Upper rectangle, bottom\r
-                         [7, 3, 2],     // Upper rectangle, top\r
-\r
-                         [0, 5, 1],     // Lower rectangle, bottom\r
-                         [0, 4, 5]      // Lower rectangle, top\r
-                        ]\r
-            );\r
-         }\r
-      }\r
-   }\r
-}\r
-\r
-\r
-\r
new file mode 120000 (symlink)
index 0000000000000000000000000000000000000000..de35975a6a4d5c21423169f5a7b351bd2d50f660
--- /dev/null
@@ -0,0 +1 @@
+diziet-utils/threads.scad
\ No newline at end of file
diff --git a/topeak-mtx-tortec-expeditionrack-adapter.scad b/topeak-mtx-tortec-expeditionrack-adapter.scad
new file mode 100644 (file)
index 0000000..c751f78
--- /dev/null
@@ -0,0 +1,502 @@
+// -*- C -*-
+
+// brk_*: "bracket", the Topeak MTX bracket
+// rack_*: the Tortec rack
+// adapt_*: the adapter, ie this file
+
+include <utils.scad>
+
+// strength factor, set to 1 for real prints
+//$strf = 0.33;
+$strf = 1;
+
+brk_recess_actual = 5.20;
+
+rack_rail_dia = 10.40 + 0.30;
+rack_width_inner = 115.86 - 1.0; // between insides of rails
+
+rear_elevation_nominal = 10.04;
+// ^ top of rack to bottom of bracket, at rack cross rail (fam)
+rear_to_front_distance = 230; // rack cross rail (fam) to very front end
+rear_to_cross_rail = 43.05; // bolt centre to rail centre, rail to rear
+rear_bolt_to_front_bolt = 155.4;
+front_elevation_nominal = 0; // this parameter adjusts rear too somehow?
+
+cross_rail_distance = 232.09;
+
+general_gap_y = 1.0;
+support_bridge_gap_z = 1.0;
+
+strap_w = 8.0 + 1.0;
+strap_th = 2.5;
+strap_barrel_dia = 14;
+strap_guide_sz = 1;
+
+brk_block_xw = 68.5;
+brk_block_z = 14.55 - 0.00;
+
+brk_bolt_dia = 5.0 + 0.5;
+brk_nearbolt_recess_dia = 8.86 + 1.5;
+brk_nearbolt_recess_depth = 1.09 + 0.25;
+
+bolt_nut_around = 5;
+bolt_nut_around_y_extra = 3;
+
+brk_bolt_eff_len = 11.78; // inside of recess, to end of bolt
+brk_bolt_len_slop = 0.5;
+brk_bolt_nut_th = 3.89;
+brk_bolt_nut_across_flats = 7.86 + 0.50;
+
+brk_overall_w = 90.07;
+
+fit_slope_len = 5;
+
+// "foreaftmaint" aka "fam" is the hook-like part that stops
+// the adapter sliding forwards/backwards along the rails
+foreaftmaint_r_slop = 0.0;
+foreaftmaint_y_slop = -0.25;
+foreaftmaint_top_block_zs = [34.0, 39.0]; // rearwards from bolt hole
+
+main_sz_y = $strf * 12;
+grasp_sz = $strf * 6;
+grasp_thin_sz = $strf * 0.5;
+beside_strap_sz = $strf * 8;
+main_sz_core_z = $strf * 12;
+
+// "length" in for-aft direction of interaction with rack rail
+min_on_rail_sz_z = $strf * 18;
+
+// when printer produces support
+support_around = 1.7; // how far does the support extend around (in XY)
+support_remnant = 0.75; // how much frass remains attached (Z height)
+
+$fa=10;
+$fs=1;
+
+// calculated
+
+bolt_z = -brk_block_z/2;
+
+front_to_rear_elevation_change =
+  rear_elevation_nominal - front_elevation_nominal;
+
+main_sz_rhs_z = max(min_on_rail_sz_z, beside_strap_sz*2 + strap_w);
+main_sz_lhs_z = min_on_rail_sz_z;
+
+main_sz_x_fam = main_sz_y;
+
+brk_bottom_y = -brk_recess_actual;
+adapt_main_top_y = brk_bottom_y - general_gap_y;
+
+// on LHS, so -ve
+rack_rail_x = -(rack_width_inner/2 + rack_rail_dia/2);
+rack_rail_outer_x = -(rack_width_inner/2 + rack_rail_dia);
+
+grasp_large_r = (rack_rail_dia + grasp_sz)/2;
+grasp_small_r = (rack_rail_dia + grasp_thin_sz)/2;
+grasp_large_x = rack_rail_outer_x + grasp_large_r;
+grasp_small_x = rack_rail_outer_x + grasp_small_r;
+
+block_x = grasp_large_x + grasp_large_r;
+block_y_min = adapt_main_top_y - main_sz_y;
+
+strap_barrel_x = rack_width_inner/2 + strap_barrel_dia/2;
+
+rack_shear_ratio = - front_to_rear_elevation_change / rear_to_front_distance;
+
+front_to_cross_rail =
+  cross_rail_distance * sqrt(1 - rack_shear_ratio * rack_shear_ratio)
+  - rear_bolt_to_front_bolt
+  - rear_to_cross_rail
+  - sqrt( pow( cross_rail_distance * rack_shear_ratio, 2 )
+         - pow(  front_to_rear_elevation_change, 2 ) )
+  ;
+
+brk_bolt_nut_top_y = -brk_nearbolt_recess_depth
+  - brk_bolt_eff_len + brk_bolt_nut_th + brk_bolt_len_slop;
+                      
+brk_bolt_nut_r = brk_bolt_nut_across_flats/2 / cos(360/12);
+
+function elevation_of_bolt_for(z) = rear_elevation_nominal
+  + front_elevation_nominal
+  + (z - brk_block_z/2) * rack_shear_ratio;
+
+function rack_rail_y_of_elevation(elevation_nominal) =
+  brk_bottom_y - elevation_nominal - general_gap_y  - rack_rail_dia/2;
+
+echo(rack_shear_ratio);
+
+module GraspElevation(){
+  hull(){
+    translate([ grasp_large_x, adapt_main_top_y - grasp_large_r ])
+      circle(grasp_large_r);
+
+    translate([ grasp_small_x, $rack_rail_y - rack_rail_dia/2 ])
+      circle(grasp_small_r);
+
+    translate([ rack_rail_x + grasp_large_r/2,
+               $rack_rail_y - rack_rail_dia/2 ])
+      circle(grasp_small_r);
+
+    translate([ grasp_large_x, $rack_rail_y + rack_rail_dia/2 ])
+      circle(grasp_large_r);
+
+    translate([ grasp_large_x + grasp_large_r/2,
+               $rack_rail_y + rack_rail_dia/2 ])
+      circle(grasp_large_r);
+  }
+}
+
+module BlockElevation(){
+  hull(){
+    rectfromto([ +block_x, adapt_main_top_y ],
+              [ -block_x, block_y_min ]);
+    rectfromto([ -grasp_large_x, adapt_main_top_y ],
+              [ +grasp_large_x, adapt_main_top_y - 0.1 ]);
+  }
+  hull(){
+    rectfromto([ +block_x, adapt_main_top_y ],
+              [ -block_x, block_y_min ]);
+    rectfromto([ grasp_large_x, block_y_min ],
+              [ 0, block_y_min + 0.1 ]);
+  }
+}
+
+module MainExtrude(z){
+  linextr(0, z)
+    children();
+}
+module RackShear(){
+  s = rack_shear_ratio * $reverse_sign;
+  multmatrix([ [ 1, 0,  0, 0 ],
+              [ 0, 1, s , 0 ],
+              [ 0, 0,  1, 0 ],
+              [ 0, 0,  0, 1 ] ])
+    children();
+}
+
+module GraspFixingElevation(){
+  intersection(){
+    union(){
+      hull(){
+       mirror([1,0]) {
+         GraspElevation();
+       }
+       translate([ -block_x, block_y_min ] + [0,0.1]*1 )
+         circle(0.1);
+      }
+      translate([ strap_barrel_x, $strap_barrel_y ])
+       circle(strap_barrel_dia/2 + strap_guide_sz);
+    }
+    union(){
+      rectfromto([0, $rack_rail_y],
+                [rack_width_inner, 50]);
+      intersection(){
+       translate([ rack_rail_x, $rack_rail_y ])
+         circle(r = rack_width_inner/2 - rack_rail_x);
+       polygon([ [ -block_x-0.1, 0 ],
+                 [ rack_width_inner/2, 0 ],
+                 $rail_fixing_fit_corner,
+                 $rail_fixing_fit_corner + [-1,-1] * fit_slope_len,
+                 [ -grasp_large_x - grasp_large_r*2, block_y_min ],
+                 [ -block_x-0.1 -2, block_y_min +2 ]]);
+      }
+    }
+  }
+}
+
+module StrapBarrelElevation(){
+  translate([ strap_barrel_x, $strap_barrel_y ])
+    circle(strap_barrel_dia/2);
+}
+
+// Bracket support block, goes up inside bracket
+// Z origin is bolt hole
+module BrkBlock(){
+  difference(){
+    linextr( -brk_block_z/2,
+            +brk_block_z/2 ) {
+      rectfromto([ -brk_block_xw/2, adapt_main_top_y - 0.1 ],
+                [ +brk_block_xw/2, 0 ]);
+    }
+    linextr_y_xz( -50, 10 ) {
+      translate([ 0, brk_block_z + bolt_z ])
+       square(center=true,
+              [ main_sz_x_fam + support_around*2,
+                support_remnant *2 ]);
+    }
+  }
+}
+
+// Z origin is bolt hole
+module BoltHole(){
+  linextr_y_xz( -100, 10 )
+    circle(brk_bolt_dia/2);
+
+  linextr_y_xz( -brk_nearbolt_recess_depth, 10)
+    circle(brk_nearbolt_recess_dia/2);
+
+  linextr_y_xz( -100, brk_bolt_nut_top_y ) {
+    hull()
+      for (dz = [0, support_bridge_gap_z])
+       translate([0, dz])
+         circle( r= brk_bolt_nut_r, $fn = 6 );
+  }
+}
+
+module IfFam(){
+  if ($foreaftmaint_dz) {
+    children();
+  }
+}
+
+module FamLinextr(){
+  IfFam()
+    linextr_x_yz(-main_sz_x_fam/2, +main_sz_x_fam/2)
+    rotate(-90)
+    children();
+}
+
+module FamGraspElevation(){
+  difference(){
+    hull(){
+      ybot = $rack_rail_y - rack_rail_dia/2 + grasp_large_r
+       - fit_slope_len * 0.5;
+      for (y = [
+               ybot,
+               adapt_main_top_y - grasp_large_r
+               ])
+       for (dx= [/*-1,*/ +1] * rack_rail_dia/2)
+         translate([ -$foreaftmaint_rail_z + dx, y ])
+           circle(r= grasp_large_r);
+    }
+    if ($foreaftmaint_cutoff) {
+      translate([ -$foreaftmaint_rail_z, 0 ])
+       rectfromto([-100, -100],
+                  [0, 100]);
+    }
+  }
+}
+
+module FamStemElevation(){
+  hull(){
+    rectfromto([ -$foreaftmaint_rail_z
+                , adapt_main_top_y ],
+              [ 0, block_y_min]);
+    translate([
+              -$foreaftmaint_rail_z,
+              $rack_rail_y +
+              rack_shear_ratio * $foreaftmaint_rail_z * $reverse_sign,
+              ])
+      square([0.1, rack_rail_dia * 0.5], center=true);
+  }
+}
+
+module Principal(){
+  // calculated
+  $rack_rail_y = rack_rail_y_of_elevation($elevation_nominal);
+
+  $strap_barrel_y = $rack_rail_y + rack_rail_dia/2 + strap_barrel_dia/2;
+
+  $rail_fixing_fit_corner = [
+    rack_width_inner/2,
+    $rack_rail_y - rack_rail_dia/2
+  ];
+
+  $foreaftmaint_rail_z = brk_block_z/2 + $foreaftmaint_dz - foreaftmaint_y_slop;
+
+  translate([0,0,brk_block_z/2])
+  mirror([0,0, $reverse_sign > 0 ? 0 : 1])
+  translate([0,0,-brk_block_z/2])
+  difference(){
+    union(){
+      MainExtrude(main_sz_lhs_z){
+       GraspElevation();
+      }
+      RackShear() MainExtrude(main_sz_rhs_z){
+       StrapBarrelElevation();
+      }
+      translate([ 0,0, brk_block_z/2]) {
+       BrkBlock();
+      }
+
+      difference(){
+       union(){
+         MainExtrude(main_sz_core_z){
+           BlockElevation();
+         }
+         if ($strf<1) {
+           MainExtrude(max(brk_block_z, main_sz_rhs_z)){
+             rectfromto([-8, adapt_main_top_y + 0.1],
+                        [+8, block_y_min]);
+             rectfromto([-block_x -5,  adapt_main_top_y],
+                        [-grasp_large_x, block_y_min]);
+           }
+         }
+         RackShear() MainExtrude(main_sz_rhs_z){
+           GraspFixingElevation();
+         }
+       }
+
+       translate([0,0, main_sz_rhs_z/2]) linextr(-strap_w/2, +strap_w/2) {
+         translate([ rack_width_inner/2 - strap_th, 0 ])
+           rectfromto([ 0, -50 ], [ 50, 50 ]);
+       }
+      }
+
+      FamLinextr(){
+       if ($foreaftmaint_top_block) {
+         rectfromto([ -foreaftmaint_top_block_zs[0] + bolt_z, 0 ],
+                    [ -foreaftmaint_top_block_zs[1] + bolt_z, block_y_min] );
+       }
+       FamGraspElevation();
+      }
+      intersection(){
+       union(){
+         RackShear()
+           FamLinextr()
+           FamGraspElevation();
+         FamLinextr()
+           FamStemElevation();
+       }
+       translate([ 0,
+                   adapt_main_top_y - 50,
+                   $foreaftmaint_rail_z ])
+         cube(center=true, 100);
+      }
+
+      linextr_y_xz( block_y_min - bolt_nut_around_y_extra , adapt_main_top_y )
+       intersection(){
+         translate([ 0, brk_block_z/2 ])
+           circle(r = bolt_nut_around + brk_bolt_nut_r );
+         rectfromto([-100, 0], [+100,+100]);
+      }
+    }
+
+    RackShear() linextr(-10, main_sz_lhs_z+main_sz_rhs_z) {
+      for (mx=[0,1]) {
+       mirror([mx,0]) {
+         translate([ rack_rail_x, $rack_rail_y ]){
+           hull(){
+             for (dx = [-rack_rail_dia, 0])
+               translate([dx, 0])
+                 circle(r= rack_rail_dia/2);
+           }
+         }
+       }
+      }
+    }
+
+    RackShear() IfFam(){
+      // Distance from bolt hole, in backwards direction
+      cr = rack_rail_dia/2 + foreaftmaint_r_slop;
+      translate([ 0, $rack_rail_y, $foreaftmaint_rail_z ])
+       linextr_x_yz(+rack_rail_x,
+                    -rack_rail_x) {
+       hull(){
+         for (dy=[0,50]) {
+           translate([-dy,0])
+             circle(r= cr);
+         }
+       }
+       hull(){
+         for (dd=[[0,0], [-1,-1], [-1,+1]]) {
+           translate(
+                     [-1, 0] * (rack_rail_dia - fit_slope_len)
+                     + 20 * dd
+                     )
+             circle(r= cr);
+         }
+       }
+      }
+    }
+
+    translate([ 0,0, brk_block_z/2]) BoltHole();
+  }
+}
+
+module ForRackForDemo(){
+  elevation = elevation_of_bolt_for(rear_to_cross_rail);
+  rack_rail_y = rack_rail_y_of_elevation(elevation);
+
+  rotate([atan(
+              front_to_rear_elevation_change /
+              cross_rail_distance
+              ), 0,0])
+    translate([0, rack_rail_y, brk_block_z/2 + rack_rail_y*rack_shear_ratio])
+    children();
+}
+
+module RackForDemoRails(){
+  ForRackForDemo() {
+    for (m=[0]) mirror([m,0,0]) {
+      linextr(-(50 + cross_rail_distance), 50 + rear_to_cross_rail)
+       translate([rack_rail_x, 0])
+       circle(r= rack_rail_dia/2);
+    }
+  }
+}
+
+module RackForDemoCrosses(){
+  ForRackForDemo() {
+    for (z = [
+             rear_to_cross_rail,
+             rear_to_cross_rail - cross_rail_distance,
+             ]) {
+      translate([0,0,z])
+       linextr_x_yz(rack_rail_x, -rack_rail_x)
+       circle(r= rack_rail_dia/2, $fn=8);
+    }
+  }
+}
+
+module Front(){ ////toplevel
+  rotate([180,0,0])
+  Principal($reverse_sign = -1,
+           $foreaftmaint_top_block = false,
+           $foreaftmaint_cutoff = true,
+           $elevation_nominal=
+      elevation_of_bolt_for(rear_to_cross_rail + rear_bolt_to_front_bolt),
+           $foreaftmaint_dz= front_to_cross_rail);
+}
+
+module Rear(){ ////toplevel
+  Principal($reverse_sign = +1,
+           $foreaftmaint_top_block = true,
+           $foreaftmaint_cutoff = false,
+           $elevation_nominal=
+      elevation_of_bolt_for(rear_to_cross_rail),
+           $foreaftmaint_dz= rear_to_cross_rail);
+}
+
+module SomeDemo(){
+  rotate([90, 0, 0]){
+    children();
+
+    color("blue")
+      translate([ 0, -2, -4 ])
+      square(center=true, [ brk_overall_w, 1 ]);
+
+    color("red")
+      translate([ 0, -brk_nearbolt_recess_depth, -4 ])
+      linextr_y_xz(-brk_bolt_eff_len, 0)
+      circle(r = brk_bolt_dia/2);
+
+  }
+}
+
+module FrontDemo(){ ////toplevel
+  SomeDemo() rotate([180,0,0]) Front();
+}
+module RearDemo(){ ////toplevel
+  SomeDemo() Rear();
+}
+module RearRackDemo(){ ////toplevel
+  rotate([atan(rack_shear_ratio),0,0]) SomeDemo() {
+    Rear();
+    translate([0, 0, -rear_bolt_to_front_bolt])
+      rotate([180,0,0]) Front();
+    %RackForDemoRails();
+    color("blue") RackForDemoCrosses();
+  }
+}
diff --git a/topeak-seatstay-lock.scad b/topeak-seatstay-lock.scad
new file mode 100644 (file)
index 0000000..072a557
--- /dev/null
@@ -0,0 +1,147 @@
+// -*- C -*-
+
+pump_dia = 27 + 0.9;
+seatstay_mindia = 14 + 0.5;
+seatstay_maxdia = 19 + 0.7;
+pump_seatstay_gap = 12.3;
+pump_seatstay_delta = 0.1;
+pump_ridge_width = 8 + 2.0;
+
+body_thick = 5;
+
+pin_workdepth = 16 - 1.0;
+pin_width = 11 + 1.0;
+pin_thick = 3 + 0.7;
+pin_base = 25;
+
+lock_manouvre_thick = 3.7 + 0.6;
+lock_manouvre_len = 18;
+lock_hang_width = 17.5;
+lock_manouvre_len_smaller = 13;
+
+body_depth_each = 5;
+clatter_gap = 0.5;
+
+roof_extent = 7;
+roof_thick = 2;
+
+// fudgeish
+
+cut_rotation = 2;
+holes_rotation = 9;
+pin_y_offset = 5.5;
+pin_x_offset = 0.5;
+ridge_rotation = 8.5;
+lock_hang_ratio = 7;
+
+// computed
+
+body_depth = pin_width + body_depth_each*2;
+
+module Holes(forbody=false){
+  translate([0, -pump_dia/2]);
+  rotate(-holes_rotation){
+    translate([-(pump_seatstay_gap/2 + pump_dia/2),
+              0]) {
+      if (!forbody)
+       rotate(-ridge_rotation + holes_rotation)
+         translate([-50, -pump_ridge_width/2])
+         square([50, pump_ridge_width]);
+      circle(r=pump_dia/2, $fn=80);
+    }
+    translate([+(pump_seatstay_gap/2 + seatstay_mindia/2),
+              pump_dia/2 -seatstay_maxdia/2 -pump_seatstay_delta]) {
+      hull(){
+       for (ud=[-1,1])
+         translate([0, ud * (seatstay_maxdia-seatstay_mindia)/2])
+           circle(r=seatstay_mindia/2, $fn=50);
+      }
+    }
+  }
+}
+
+module BodyPlan(){
+   minkowski(){
+    circle(body_thick);
+    hull()
+      Holes(true);
+  }
+}
+
+module Body(){
+  translate([0,0,body_depth/2])mirror([0,0,1]){
+    linear_extrude(height=body_depth){
+      difference(){
+       BodyPlan();
+       Holes();
+      }
+    }
+    linear_extrude(height=roof_thick){
+      difference(){
+       hull(){
+         BodyPlan();
+         translate([0,-roof_extent,0]) BodyPlan();
+       }
+       Holes();
+      }
+    }
+  }
+}
+
+module Pin(){
+  translate([pin_x_offset, pin_y_offset, 0]) rotate([0,90,0]){
+    translate([0, 0, -pin_thick/2])
+      linear_extrude(height=pin_thick){
+      translate([-pin_base/2, 0]) square([pin_base, 50]);
+      translate([-pin_width/2, -100]) square([pin_width, 101]);
+    }
+    hull() for (d=[0,10]) {
+      translate([d*lock_hang_ratio,-d,0])
+       translate([-lock_manouvre_thick/2,
+                  -pin_workdepth-100,
+                  -lock_manouvre_len_smaller])
+       cube([lock_manouvre_thick, 100,
+             lock_manouvre_len + lock_manouvre_len_smaller]);
+    }
+  }
+}
+
+module All(){
+  difference(){
+    Body();
+    Pin();
+  }
+}
+
+module Piece(pc,interval){
+  translate([0,-pc*interval,0])
+    intersection(){
+      rotate([0,0,pc*180-cut_rotation])
+      translate([-200,clatter_gap/2,-200]) cube([400,400,400]);
+    All();
+  }
+}
+
+module PiecePrint(pc){
+  rotate([0,0,90]) rotate([0,180,0])
+    Piece(pc,4);
+}
+
+module PiecesPrint(){
+  PiecePrint(0);
+  PiecePrint(1);
+}
+
+module Demo(){
+  for (pc=[0,1])
+    Piece(pc,0);
+}
+
+//Holes();
+//Demo();
+//All();
+//Pin();
+//Pieces();
+PiecesPrint();
+//PiecePrint(0);
+//PiecePrint(1);
diff --git a/tower-base.scad b/tower-base.scad
new file mode 100644 (file)
index 0000000..0e097e9
--- /dev/null
@@ -0,0 +1,152 @@
+/* -*- C -*- */
+
+motorwidth=35.7;
+motorheight=34.5;
+totalheight=58;
+
+pillarthick=8;
+sidethick=2.5;
+archthick=6.5;
+frameextra=3.5;
+framesplayx=5;
+framesplayy=5;
+botleftgap=4.5;
+botleftstand=0.75;
+archoutwards=(pillarthick-archthick)/sqrt(8);
+
+topgluecubex=18;
+topgluecubez=5;
+clippairy=16;
+clippairdz=-2.5;
+topgluecubedy=1;
+
+dovebasecutcylz=4;
+dovebasecutcylr=10;
+
+d=0.01;
+
+mw2=motorwidth/2;
+
+include <doveclip.scad>
+
+module corner() {
+  $fn=30;
+  frameheight= motorheight + frameextra;
+  slopeheight= totalheight - frameheight;
+  slopex = (mw2 + archoutwards - framesplayx)/slopeheight;
+  slopey = (mw2 + archoutwards - framesplayy)/slopeheight;
+  echo(sqrt(slopex*slopex + slopey*slopey));
+
+  translate([-mw2,-mw2,0]) union(){
+    difference(){
+      union(){
+       cylinder(r=pillarthick/2, h=frameheight);
+       translate([0,0,frameheight])
+         sphere(r=pillarthick/2);
+      }
+      translate([d,d,-1])
+       cube([mw2-1,mw2-1,frameheight+pillarthick+2]);
+    }
+    intersection(){
+      multmatrix
+       ([      [       1,      0,      slopey, -archoutwards ],
+               [       0,      1,      slopex, -archoutwards ],
+               [       0,      0,      1, frameheight  ],
+               [       0,      0,      0,      1       ]])
+       translate([0,0,-frameextra])
+       cylinder(r=archthick/2,
+                h=slopeheight+frameextra);
+      union() {
+       cylinder(r=pillarthick/2, h=frameheight);
+       translate([-100,-100,frameheight])
+         cube([200,200,100]);
+      }
+    }
+  }
+}
+
+module halfside() {
+  spacesz = (motorwidth - pillarthick/2*2) / 4;
+  panelheight = spacesz + sidethick;
+  panelbasez = motorheight+pillarthick/4-panelheight;
+  translate([0,-mw2,0]) {
+    translate([-mw2,-sidethick,0])
+      cube([motorwidth,sidethick,sidethick]);
+    difference(){
+      translate([-mw2,-sidethick, panelbasez])
+       cube([mw2,sidethick,panelheight]);
+      translate([-mw2+pillarthick/3, -sidethick, panelbasez])
+       rotate([0,45,0])
+       translate([0,-1,0])
+       cube([spacesz * sqrt(2),
+             sidethick+2,
+             spacesz * sqrt(2)]);
+    }
+    intersection(){
+      for (xz=[[-mw2+pillarthick/3-sidethick, 0,
+               panelbasez+sidethick],
+              [0, 0, panelbasez + sidethick/sqrt(2)]]) {
+       translate(xz)
+         translate([0,-sidethick,0])
+         rotate([0,55,0])
+         translate([0,0,-sidethick])
+         cube([100, sidethick, sidethick]);
+      }
+      translate([-mw2,-sidethick,0])
+       cube([motorwidth,sidethick,
+             motorheight+pillarthick]);
+    }
+  }
+}
+
+module towerbase() {
+  difference(){
+    union(){
+      for (mirx=[0,1]) for (miry=[0,1])
+                        mirror([mirx,0,0]) mirror([0,miry,0]) corner();
+      for (angle=[0,90,180]) {
+       rotate([0,0,angle]) halfside();
+       rotate([0,0,angle]) mirror([1,0,0]) halfside();
+      }
+    }
+//    multmatrix([[    -1,     0,      0, -mw2 - botleftstand ],
+//             [       0,      1,      0,      -100    ],
+//             [       1,      0,      1, -100 + botleftgap ],
+//             [       0,      0,      0,      1       ] ])
+//      cube([100,200,100]);
+  }
+  translate([clippairy/2,0,totalheight]) {
+    difference(){
+      translate([-clippairy+topgluecubedy/2,-topgluecubex/2,0])
+       cube([clippairy-topgluecubedy,topgluecubex,topgluecubez]);
+    }
+    translate([0,0,topgluecubez+clippairdz+DoveClip_depth()]) rotate([0,-90,0])
+//      DoveClipPair(h=clippairy);
+      DoveClipPairSane(h=clippairy, count=3);
+  }
+}
+
+if (towerbase_demo) {
+  intersection(){
+    translate([0,0,-50]) towerbase();
+    translate([-100,-100,0]) cube([200,200,32]);
+  }
+
+  intersection(){
+    translate([40,0,-60]) towerbase();
+    translate([-100,-100,0]) cube([200,200,32]);
+  }
+
+  translate([60,-90,0]) {
+    DoveClipPairSane(h=clippairy, count=3);
+    mirror([1,0,0]) translate([DoveClip_depth()-0.1,0,0]) cube([20,8,6]);
+  }
+
+  for (x=[0,20,40]) {
+    translate([x,-50,0]) DoveClipPin(h=clippairy);
+    translate([x+10,-50,0]) DoveClipPin(h=clippairy/2);
+    translate([x+10,-30,0]) DoveClipPin(h=clippairy/2);
+  }
+} else {
+  towerbase();
+}
diff --git a/trackpump-mutlihead-clip.scad b/trackpump-mutlihead-clip.scad
new file mode 100644 (file)
index 0000000..c5108fd
--- /dev/null
@@ -0,0 +1,174 @@
+// -*- C -*-
+
+include <commitid.scad>
+
+pump_main_dia = 38;
+pump_side_width = 5;
+pump_side_thick = 4;
+pump_shaft_dia = 14;
+baseplate = 3;
+
+pump_protr_flat = 3;
+pump_protr_slope = 0.9;
+
+hose_inner_dia = 20;
+hose_aperture = 11;
+hose_side_width = 5;
+hose_base_offset = 30;
+
+hose_side_thick = 6;
+hose_side_stalk_width = 6;
+
+pump_protr_protr = 3;
+pump_side_height = 20;
+
+// calculated
+pump_protr_slheight = pump_protr_protr / pump_protr_slope;
+
+pump_side_outer_rad = pump_side_width + pump_main_dia/2;
+
+baseplate_width_rad =
+  sqrt( pow(pump_side_outer_rad, 2)
+       -pow( pump_main_dia/2 - pump_protr_protr, 2) );
+
+xm = baseplate + pump_main_dia/2;
+
+pump_side_total_height =
+  pump_side_thick + pump_side_height + pump_protr_slheight + pump_protr_flat;
+
+$fa=5;
+
+module PumpSidePlan() {
+  or = pump_side_outer_rad;
+  difference(){
+    union(){
+      intersection(){
+       translate([-xm, 0]) circle(r=or);
+//     translate([-(xm+or), -or]) square([xm+or, or*2]);
+      }
+    }
+    translate([-xm-or, 0])
+      square(center=true, [pump_side_width*4, pump_shaft_dia]);
+  }
+}
+
+module PumpSideElevation(){
+  x3 = 0;
+  x2 = x3 - baseplate;
+  x1 = x2 - pump_main_dia;
+  x0 = x1 - pump_side_width;
+  x2a = x2 - pump_protr_protr;
+  x4 = x2 + pump_side_width;
+
+  z0 = 0;
+  z1 = z0 - pump_side_thick;
+  z2 = z1 - pump_side_height;
+  z2a = z2 - pump_protr_slheight;
+  z2b = z2a - pump_protr_flat;
+
+  arcx = x2-x1;
+  arcy = z1-z2;
+
+  translate([x0,z1]) square([x1-x0, z0-z1]);
+
+  difference(){
+    translate([x1,z2]) square([x3-x1, z0-z2]);
+    translate([x1,z2]) scale([1,arcy/arcx]) circle(r=arcx);
+  }
+
+  translate([x2,z2a]) square([x4-x2, z0-z2a]);
+
+  hull(){
+    translate([x2,z2a]) square([x4-x2, z2-z2a]);
+    translate([x2a,z2b]) square([x3-x2a, z2a-z2b]);
+  }
+}
+
+module PumpSide(){
+  br = baseplate_width_rad;
+  brs = hose_side_stalk_width/2;
+  echo(brs);
+
+  difference(){
+    intersection(){
+      translate([0,100,0])
+       rotate([90,0,0])
+       linear_extrude(height=200, convexity=10)
+       PumpSideElevation();
+      union(){
+       translate([0,0,-100])
+         linear_extrude(height=200, convexity=10)
+         PumpSidePlan();
+       // baseplate
+       hull(){
+         mirror([0,0,1])
+           translate([-xm, -brs, 0])
+           cube([pump_main_dia/2 + pump_side_width,
+                 brs*2,
+                 1]);
+         translate([-xm, -br, -pump_side_total_height])
+           cube([xm,
+                 br*2,
+                 pump_protr_flat]);
+       }
+      }
+    }
+    translate([-(baseplate + pump_main_dia/2), 0,
+               -(pump_side_thick + pump_side_height)])
+      cylinder(r=pump_main_dia/2, h=200);
+  }
+  rotate([0,0,180])
+    mirror([0,0,1])
+    translate([-0,
+              -br,
+              pump_side_total_height])
+    Commitid_BestCount_M([baseplate + pump_protr_protr,
+                         br*2]);
+}
+
+module HoseSidePlan(){
+  ro = hose_inner_dia/2 + hose_side_width;
+  ri = (hose_inner_dia/2);
+  st = hose_side_stalk_width/2;
+
+  apx = sqrt( ri*ri - (hose_aperture*hose_aperture)/4 );
+  apsq = hose_base_offset + apx - hose_aperture/2;
+  echo(apx,apsq);
+
+  difference(){
+    union(){
+      translate([-1, -st]) square([hose_base_offset+1, st*2]);
+      translate([hose_base_offset, 0]) circle(r= ro);
+    }
+    translate([hose_base_offset, 0]) circle(r= hose_inner_dia/2);
+    translate([apsq, 0])
+      rotate(-45)
+      square([50,50]);
+  }
+
+  //%translate([hose_base_offset + apx, 0]) square([50,50]);
+  //%square(center=true, [100, hose_aperture]);
+}
+
+module HoseSide(){
+  mirror([0,0,1])
+    linear_extrude(height=hose_side_thick, convexity=10)
+    HoseSidePlan();
+}
+
+module Clip(){
+  PumpSide();
+  HoseSide();
+}
+
+module ClipPrint(){
+  rotate([180,0,0])
+    Clip();
+}
+
+//PumpSidePlan();
+//PumpSideElevation();
+//PumpSide();
+//HoseSide();
+//Clip();
+ClipPrint();
diff --git a/trailerhubcap.scad b/trailerhubcap.scad
new file mode 100644 (file)
index 0000000..4ae4c7d
--- /dev/null
@@ -0,0 +1,80 @@
+// Copyright (C)2012 Ian Jackson
+// Licenced under the GNU General Public Licence, version 3, or
+// (at your option) any later version.  There is NO WARRANTY.
+
+maindia = 29.7;
+poleholeh = 5.0;
+polecovth = 0.4;
+poleholerad = 6;
+
+mainoverlap = 1.5;
+
+hookbasew = 5.0;
+hookfullw = 7;
+hookheight = 4.5;
+hookwidth = 8;
+hookbevelw = 0.75;
+hookbevelh = 1.5;
+
+fingernaildepth = 5;
+fingernailheight = 2.5;
+fingernailwidth = 6;
+
+bigrad = maindia/2 + mainoverlap;
+mainth = poleholeh + polecovth;
+hooklessdepth = hookfullw - hookbasew;
+
+module base() {
+  rotate_extrude(convexity=10)
+    mirror([1,0,0])
+    polygon(points=[[-bigrad, 0],
+                   [-bigrad + mainth, -mainth],
+                   [0, -mainth],
+                   [0, -poleholeh],
+                   [-poleholerad, -poleholeh],
+                   [-poleholerad, 0]]);
+}
+
+module fingernails() {
+  for (ang=[60,180,300])
+    rotate([0,0,ang])
+      translate([bigrad - fingernaildepth,
+                -fingernailwidth/2,
+                -fingernailheight])
+      cube([fingernaildepth + 1, fingernailwidth, fingernailheight + 1]);
+}
+
+module hookrim() {
+  rotate_extrude(convexity=10)
+    mirror([1,0,0])
+    translate([-maindia/2, 0, 0])
+    polygon(points=[[hooklessdepth, 0],
+                   [hookfullw, 0],
+                   [hookfullw*0.33, hookheight],
+                   [hookbevelw, hookheight],
+                   [0, hookheight-hookbevelh],
+                   [0, hooklessdepth]]);
+}
+
+module hooktriangles() {
+  for (ang=[0,120,240]) {
+    rotate([0,0,ang]) {
+      translate([0,0,-1]) {
+       linear_extrude(height=hookheight+2) {
+         polygon(points=[[0, 0],
+                         [maindia/2 + 1, -hookwidth],
+                         [maindia/2 + 1, +hookwidth]]);
+       }
+      }
+    }
+  }
+}
+
+difference(){
+  base();
+  fingernails();
+}
+intersection(){
+  hookrim();
+  hooktriangles();
+}
diff --git a/treefoil.scad.pl b/treefoil.scad.pl
new file mode 100755 (executable)
index 0000000..ef9ef26
--- /dev/null
@@ -0,0 +1,79 @@
+#!/usr/bin/perl -w
+
+# Use:
+#   - support X/Y dist 0.5mm
+
+use strict;
+
+our $shape = <<'END';
+xyyZZYYXYxxyzYYZXzzxyXXYXXXZxxxyyXXZyyyzXXzz
+zxxYYXXZXzzxyXXYZyyzxZZXZZZYzzzxxZZYxxxyZZyy
+yzzXXZZYZyyzxZZXYxxyzYYZYYYXyyyzzYYXzzzxYYxx
+END
+# simple version (unknotted, [0,1,2]^3):
+# YxxyzYYZXzzxyXXYZyyzxZZX
+#
+# New and less symmetric one that also fits in the 2x2x2 box:
+# YXXyzzYxYXZZxzyxYzyyZXZx
+# (email 8.9.2022)
+
+sub o { print @_ or die $!; }
+
+o <<'END';
+// -*- autogenerated, do not edit -*-
+
+module Trace() {
+END
+
+my @p = qw(0 0 0);
+
+$"=',';
+
+while ($shape =~ s/^\s*(\w)//) {
+    my $ix = index('xyz', (lc $1));
+    my $sign = $1 =~ /[A-Z]/ ? +1 : -1;
+    my @q = @p;
+    $q[$ix] += $sign;
+    o "    TraceEdge([@p],[@q]);\n";
+    @p = @q;
+}
+
+die @p unless "@p" eq '0,0,0';
+
+o "}\n\n";
+
+while (<DATA>) { o $_ }
+
+__DATA__
+
+thick = 6;
+edgeu = 10;
+//edgeu = 15;
+
+// calculated
+
+octa_long = thick;
+octa_short = octa_long / (1 + sqrt(2));
+
+module OctaThing() {
+    hull(){
+       for (r = [[0,0,0], [90,0,0], [0,90,0]]) {
+           rotate(r)
+               cube([ octa_short,octa_short, octa_long ], center=true);
+       }
+    }
+}
+
+module TraceEdge(p,q) {
+    hull(){
+        for (x=[p,q]) {
+            translate(x * edgeu)
+                OctaThing();
+        }
+    }
+}
+
+rotate([0,0,45])
+  Trace();
+
+       
diff --git a/tube-crossdrill-jig.scad b/tube-crossdrill-jig.scad
new file mode 100644 (file)
index 0000000..843a2a6
--- /dev/null
@@ -0,0 +1,138 @@
+// -*- C -*-
+
+//$fs=0.1;
+//$fa=3;
+$fs=0.2;
+$fa=6;
+
+rearwallthick = 3;
+basethick = 2;
+mainframeendthick = 2.5;
+
+tubedia = 16 + 0.8;
+tubetubethick=2;
+tubetubetopslop=1;
+
+boltholedia = 6.5 + 0.5;
+boltholeslotshorter = 6;
+mainframeholedia = 5 + 1.0;
+
+// "slot" refers to the things in the base of the drill press stand
+backslotedgespace = 59;
+slotwidth = 11.5 - 0.5;
+backslotmid2screwhole = 17;
+slotplugheight = 5.5;
+slotplugshorterlen =10;
+slotpluglongerlen = 20;
+
+//slotslope = 11.0 / 18.5;
+slotslope = 1 / tan(51);
+
+// "keepslot" refers to the screws in the wooden jig block
+keepslotstartz = 20;
+keepslotlen = 18;
+keepslotx = backslotedgespace / 2;
+keepslotwidth = 4;
+
+mainframeextraside = 12;
+mainframeextrafront = 15;
+
+rearwallstrengthwidth = 10;
+keepslotclear = 10;
+
+// computed values
+
+slotslopediag = sqrt(1 + slotslope*slotslope);
+slotwidthx = slotwidth * slotslopediag;
+
+slotxperlen = slotslope / slotslopediag;
+slotyperlen =         1 / slotslopediag;
+
+mainframeholex = backslotedgespace/2 + slotpluglongerlen * slotxperlen
+  + 0.5 * slotwidth * slotyperlen;
+
+mainframeholey = -slotpluglongerlen * slotyperlen
+  + 0.5 * slotwidth * slotxperlen;
+
+mainframemaxx = mainframeholex + mainframeextraside;
+mainframeminy = mainframeholey - mainframeextrafront;
+mainframemaxz = keepslotstartz + keepslotlen;
+
+module MainFrame(){
+  for (m=[0,1]) {
+    mirror([m,0,0]) {
+      translate([-1, mainframeminy, 0])
+       cube([mainframemaxx+1, -mainframeminy, basethick]);
+      translate([-1, -rearwallthick, 0])
+       cube([mainframemaxx+1, rearwallthick, mainframemaxz]);
+
+      for (x=[keepslotx - keepslotclear, mainframemaxx - 0.5]) {
+       translate([x,0,0])
+         rotate([90,0,-90])
+         linear_extrude(height=mainframeendthick)
+         polygon([[-mainframeminy, 0],
+                  [0, mainframemaxz],
+                  [0, 0]]);
+      }
+
+      translate([backslotedgespace/2, 0, 1])
+       mirror([0,0,1])
+       linear_extrude(height=slotplugheight+1)
+       polygon([[0,0],
+                [slotwidthx, 0],
+                [slotwidthx + slotplugshorterlen * slotxperlen,
+                 -slotplugshorterlen * slotyperlen],
+                [slotpluglongerlen * slotxperlen,
+                 -slotpluglongerlen * slotyperlen]]);
+      translate([-1,
+                -rearwallthick - boltholeslotshorter + 0.2,
+                tubedia + tubetubetopslop + tubetubethick + 4])
+       cube([keepslotx - keepslotclear + 1,
+             boltholeslotshorter + 0.5,
+             rearwallstrengthwidth]);
+    }
+  }
+}
+
+module TubeThing(extralen, dia, extraheight, underheight){
+  effheight = tubetubetopslop + extraheight + underheight;
+  len = -mainframeminy + extralen * 2;
+  translate([0, mainframeminy - extralen, -underheight]) {
+    translate([0,0, dia/2 + effheight])
+      rotate([-90,0,0]) cylinder(h=len, r=dia/2);
+    translate([-dia/2, 0, 0])
+      cube([dia, len, effheight + dia/2]);
+  }
+}
+
+module Jig(){
+  difference(){
+    union(){
+      MainFrame();
+      TubeThing(0, tubedia+tubetubethick*2, -tubetubethick, 0);
+    }
+    union(){
+      translate([0,0,-0.1])
+       TubeThing(10, tubedia, 0, 5);
+      translate([-boltholedia/2, mainframeminy - 1, -5])
+       cube([boltholedia,
+             -mainframeminy + 1 - rearwallthick - boltholeslotshorter,
+             mainframemaxz + 10]);
+      for (m=[0,1]) {
+       mirror([m,0,0]) {
+         translate([mainframeholex, mainframeholey, -30])
+           cylinder(h=basethick+40, r=mainframeholedia/2);
+         translate([keepslotx - keepslotwidth/2,
+                    -10, keepslotstartz])
+           cube([keepslotwidth, 20, keepslotlen + 10]);
+       }
+      }
+    }
+  }
+}
+
+//MainFrame();
+//TubeThing(0, tubedia);
+
+rotate([-90,0,0])
+  Jig();
deleted file mode 100644 (file)
index e440b5f03d6cf1966889522bbc497946d851fb57..0000000000000000000000000000000000000000
+++ /dev/null
@@ -1,51 +0,0 @@
-// -*- C -*-
-
-// suitable for masking things within radius sqrt(2) only
-module FArcSegment_mask(beta) {
-  for (i=[0 : 0.75 : 3]) {
-    rotate(i*beta/4)
-      polygon([[0, 0],
-              [1, 0],
-              [cos(beta/4), sin(beta/4)]]);
-  }
-}
-
-module FArcSegment(xc,yc,inrad,outrad,alpha,delta) {
-  translate([xc,yc]) {
-    intersection() {
-      difference() {
-       circle(r=outrad, $fn=70);
-       circle(r=inrad, $fn=70);
-      }
-      rotate(alpha) scale(outrad*2) {
-       FArcSegment_mask(delta);
-      }
-    }
-  }
-}
-
-module rectfromto(a,b) {
-  ab = b - a;
-  translate([min(a[0], b[0]), min(a[1], b[1])])
-    square([abs(ab[0]), abs(ab[1])]);
-}
-module circleat(c,r) { translate(c) circle(r); }
-module linextr(z0,z1, convexity=20) {
-  translate([0,0,z0])
-    linear_extrude(height=z1-z0, convexity=convexity)
-    children();
-}
-
-module linextr_x_yz(x0,x1, convexity=20) { // XY turn into YZ
-  rotate([90,0,0])
-    rotate([0,90,0])
-    linextr(x0,x1, convexity=convexity)
-    children();
-}
-
-module linextr_y_xz(y0,y1, convexity=20) { // XY turn into YZ
-  rotate([0,0,180])
-    rotate([90,0,0])
-    linextr(y0,y1, convexity=convexity)
-    children();
-}
new file mode 120000 (symlink)
index 0000000000000000000000000000000000000000..b1ed2005c164f0c0e0f7ca6e76dfcf10b206767e
--- /dev/null
@@ -0,0 +1 @@
+diziet-utils/utils.scad
\ No newline at end of file
diff --git a/velux-window-grip.scad b/velux-window-grip.scad
new file mode 100644 (file)
index 0000000..d59a78f
--- /dev/null
@@ -0,0 +1,138 @@
+// -*- C -*-
+
+include <funcs.scad>
+
+// MainLoop
+
+main_thick = 9.0;
+main_in_dia = 28.9;
+
+horn_ext_dia = 20 - 0.5;
+
+horn_c_x = -4.6;
+horn_c_dy= -4;
+
+blhook_start_ang = 45;
+blhook_in_rad = 1.85;
+blhook_str_len = 2.9;
+
+width = 20;
+
+// Attach
+
+at_bolt_into = 13.0 + 0.5;
+at_tube_dia = 16.7 + 0.5;
+at_prong_minw = 4;
+at_rear_thick = 4.5;
+at_bolt_dia = 5 + 0.5;
+
+at_rear_width = at_tube_dia;
+at_stem_len = main_in_dia/2 * 0.3;
+
+at_prong_depth = at_bolt_into * 2;
+at_gap_width = at_tube_dia * 0.75;
+
+// computed
+
+blhook_mid_rad = blhook_in_rad + main_thick/2;
+mc_mid_rad = main_in_dia/2 + main_thick/2;
+
+mc_bl = circle_point([0,0], mc_mid_rad, 270-blhook_start_ang);
+
+at_block_x = at_tube_dia + at_prong_minw * 2;
+at_block_y = at_prong_depth + at_rear_thick;
+at_block_z = width;
+
+at_stem_yy = at_stem_len + mc_mid_rad;
+
+at_offset_y = at_block_y + at_stem_len + mc_mid_rad;
+
+$fs=0.05;
+
+horn_thick = main_thick;
+
+module MainLoop(){
+  intersection(){
+    difference(){
+      circle(r= main_in_dia/2 + main_thick, $fn=50);
+      circle(r= main_in_dia/2, $fn=50);
+    }
+    polygon([[0,0],
+            3*mc_bl,
+            [0, -100],
+            [100,-100],
+            [100,100],
+            [0,100]]);
+  }
+  translate(mc_bl)
+    circle(main_thick/2);
+  translate([horn_c_x, mc_mid_rad + horn_c_dy])
+    intersection(){
+    difference(){
+      circle(horn_ext_dia/2);
+      intersection(){
+       circle(horn_ext_dia/2 - horn_thick);
+       polygon([[-50,-50],
+                [-50,-horn_c_dy],
+                [50,-horn_c_dy],
+                [50,-50]]);
+      }
+    }
+    polygon([[0,0],
+             [-50,0],
+             [0,50]]);
+  }
+  translate([0,main_in_dia/2]) mirror([1,0])
+    square([-horn_c_x + horn_ext_dia/2 * 0.75, main_thick]);
+  translate(mc_bl){
+    translate([-blhook_str_len/2, 0])
+      square(center=true, [blhook_str_len, main_thick]);
+    translate([-blhook_str_len, blhook_mid_rad]){
+      intersection(){
+       difference(){
+         circle(r=blhook_mid_rad + main_thick/2);
+         circle(r=blhook_mid_rad - main_thick/2);
+       }
+       mirror([1,1]) square(50);
+      }
+    }
+  }
+}
+
+module MainLoopTest(){
+  linear_extrude(height=1.6)
+    MainLoop();
+}
+
+module Attach(){
+  difference(){
+    translate([0, at_block_y/2, 0])
+      cube(center=true, [at_block_x, at_block_y, at_block_z]);
+    translate([0, at_prong_depth/2-1, 0])
+      cube(center=true, [at_gap_width, at_prong_depth+2, at_block_z+1]);
+    translate([0,-1,0])
+      rotate([-90,0,0])
+      cylinder(r= at_tube_dia/2, h= at_prong_depth+1);
+    translate([-50, at_prong_depth-at_bolt_into, 0])
+      rotate([0,90,0])
+      cylinder(r= at_bolt_dia/2, h= 100);
+  }
+  difference(){
+    translate([0, at_block_y + at_stem_yy/2 - 0.1, 0])
+      cube(center=true, [at_tube_dia, at_stem_yy + 0.2, at_block_z]);
+    translate([0, at_offset_y, -50])
+      cylinder(r = mc_mid_rad, 100);
+  }
+}
+
+module Combine(){
+  rotate([0,0,45]) translate([0,-main_thick/2,0]){
+    linear_extrude(height=width)
+      translate([0,at_offset_y,0])
+      MainLoop();
+    translate([0,0, width/2])
+      Attach();
+  }
+}
+
+Combine();
diff --git a/velux-window-grip.slic3r b/velux-window-grip.slic3r
new file mode 100644 (file)
index 0000000..3a44d19
--- /dev/null
@@ -0,0 +1 @@
+solid_infill_every_layers = 11
diff --git a/wall-cable-hook.scad b/wall-cable-hook.scad
new file mode 100644 (file)
index 0000000..3076265
--- /dev/null
@@ -0,0 +1,40 @@
+// -*- C -*-
+
+circle_inner_rad = 10 + 0.5;
+
+thick = 3;
+
+tab_sz = 20;
+width = 20;
+
+screw_hole_dia = 4.5 + 0.5;
+
+// calculated
+
+circle_outer_rad = circle_inner_rad + thick;
+
+module Plan() {
+  difference(){
+    circle(r=circle_outer_rad, $fn=150);
+    circle(r=circle_inner_rad, $fn=150);
+    mirror([1,0]) square([50,50]);
+  }
+  translate([-circle_outer_rad, -0.1])
+    multmatrix([[1,0,0,0],
+               [-1,1,0,0],
+               [0,0,1,0],
+               [0,0,0,1]])
+    square([thick, tab_sz + circle_outer_rad + thick]);
+}
+
+module Hook(){
+  difference(){
+    linear_extrude(height=width) Plan();
+    if (false)
+      translate([-50, circle_outer_rad + tab_sz - width/2, width/2])
+      rotate([0,90,0])
+      cylinder(r= screw_hole_dia / 2, h=100, $fn=50);
+  }
+}
+
+Hook();
diff --git a/wardrobe-hook.scad b/wardrobe-hook.scad
new file mode 100644 (file)
index 0000000..959e967
--- /dev/null
@@ -0,0 +1,192 @@
+// -*- C -*-
+
+include <funcs.scad>
+include <utils.scad>
+
+tubeslop = 0.5;
+tubeheight = 30 + tubeslop;
+tubewidth = 15 + tubeslop;
+mainthick = 4;
+
+clipthick = 2;
+clipang = 135;
+
+stemlen = 40;
+
+topwidth = 20;
+
+hookinrad = 7.5;
+hookcurl = 60;
+hookwidth = 4;
+
+tuberad = tubewidth/2;
+bend = atan(tuberad/stemlen);
+mainoutrad = tuberad + mainthick;
+hookoutrad = hookinrad + hookwidth;
+hookcy = -(stemlen - hookoutrad);
+
+eltop = [topwidth/2, -tuberad + tubeheight + mainthick + 0.1];
+elmid = [topwidth/2, -tuberad];
+ellow = tangent_intersect_b([0,hookcy], hookinrad, elmid);
+ellowextra = 180 - tangent_intersect_beta([0,hookcy], hookinrad, elmid);
+
+module ClipPlan(qbend, qstemleny){
+  dy = tubeheight - tuberad*2;
+  FArcSegment(0, dy, tuberad, mainoutrad, -1, 181);
+  FArcSegment(0, 0,  tuberad, mainoutrad, -qbend, qbend+1);
+  translate([tuberad, 0]) square(center=false, size=[mainthick,dy]);
+  FArcSegment(0, 0, tuberad, tuberad + clipthick, 360-clipang, clipang+1);
+  rotate(-qbend) translate([tuberad, 0]) mirror([0,1])
+    square(center=false, size=[mainthick, qstemleny/cos(qbend)]);
+}
+
+module Plan(){
+  ClipPlan(bend,stemlen);
+}
+
+module ElevationCore(){
+  FArcSegment(0, hookcy, hookinrad, hookoutrad,
+             180 - ellowextra,
+             90 + hookcurl + ellowextra);
+  translate([-hookoutrad*sqrt(0.5),
+             hookcy - hookoutrad*sqrt(0.5) + 0.1])
+    mirror([1,0])
+    square(center=false, size=[topwidth, stemlen + tubeheight + 20]);
+  polygon([[-hookoutrad, ellow[1]],
+          reflect_in_y(eltop),
+          eltop,
+          elmid,
+          ellow]);
+}
+
+// after here is all 3D
+
+module Primary(){
+  intersection(){
+    translate([0,0, -(topwidth+10)/2])
+      linear_extrude(height=topwidth+10) Plan();
+    translate([50,0])
+      rotate([0,-90,0])
+      linear_extrude(height=100)
+      ElevationCore();
+  }
+}
+
+module PlaneAbove(){
+  translate([-100,-100,0]) cube(center=false,size=[200,200,200]);
+}
+
+taperangle = -270 + tangent_intersect_beta([-hookcy, 0],
+                                         hookoutrad,
+                                         [-eltop[1], -eltop[0]]);
+module HookL(){ ////toplevel
+  difference(){
+    rotate([taperangle,0,0])
+      translate([0,-eltop[1],0])
+      Primary();
+    translate([0,0,topwidth/2])
+      rotate([taperangle*2,0,0])
+      PlaneAbove();
+    translate([0,0,-topwidth/2])
+      mirror([0,0,1]) PlaneAbove(0);
+  }
+}
+
+// straight-on version, everything prefixed with s or S
+
+shookcy = -(stemlen-hookoutrad);
+sstemleny = -shookcy;
+sbend_raw = tangents_intersect_beta([0,0],tuberad,
+                                   [0,shookcy],hookinrad);
+sbend = angle_map_range(360-sbend_raw, -180);
+
+module SPlan(){
+  ClipPlan(sbend, sstemleny);
+  FArcSegment(0,shookcy, hookinrad,hookoutrad,
+             270 - hookcurl,
+             hookcurl + 90 - sbend);
+}
+
+module SElevation(){
+  boty = shookcy - hookoutrad - 1;
+  polygon([[-1,         tubeheight],
+          [topwidth,   tubeheight],
+          [topwidth,   elmid[1]],
+          [hookwidth,  shookcy],
+          [hookwidth,  boty],
+          [-1,         boty]]);
+}
+
+module SElevationPlaced(){
+  rotate([0,-90,0]) translate([0,0,-100]) linear_extrude(height=200)
+    SElevation();
+}
+
+module SHookL(){ ///toplevel
+  intersection(){
+    linear_extrude(height=topwidth) SPlan();
+    SElevationPlaced();
+  }
+}
+
+// straight-on version, reversed, everything prefixed with t or T
+
+tjoinrad = mainoutrad * 0.7;
+tstem0leny = tuberad - tjoinrad*0.5;
+tjoinoutrad = tjoinrad + mainthick;
+
+thookcy = shookcy;
+
+tjoin0c = [tuberad - tjoinrad, -tstem0leny];
+tjoin1c = [0,                      thookcy];
+
+tbend_raw = tangents_intersect_beta(tjoin0c, tjoinrad,
+                                   tjoin1c, -hookoutrad);
+tbend0 = angle_map_range(tbend_raw,       0);
+tbend1 = angle_map_range(tbend_raw + 180, -180);
+
+tbend0p = circle_point(tjoin0c, tjoinrad, tbend_raw);
+tbend1p = circle_point(tjoin1c, -hookoutrad, tbend_raw);
+
+module TPlan(){
+  ClipPlan(0, tstem0leny);
+  FArcSegment(tjoin0c[0],tjoin0c[1], tjoinrad,tjoinoutrad,
+             tbend0, 360-tbend0);
+  FArcSegment(0,shookcy, hookinrad,hookoutrad,
+             tbend1, 270+hookcurl - tbend1);
+  translate(tbend0p) {
+    rotate(tbend_raw+180) mirror([1,0]) {
+      translate([0,-0.1]) square(size=[mainthick, dist2d(tbend0p,tbend1p)+0.2]);
+    }
+  }
+}
+
+module THookR(){ ///toplevel
+  intersection(){
+    linear_extrude(height=topwidth) TPlan();
+    SElevationPlaced();
+  }
+}
+
+// other toplevels etc.
+
+module HookR(){ ////toplevel
+  mirror([1,0,0]) HookL();
+}
+
+module SHookR(){ ////toplevel
+  mirror([1,0,0]) SHookL();
+}
+
+module THookL(){ ////toplevel
+  mirror([1,0,0]) THookR();
+}
+
+module Demo(){ ////toplevel
+  translate([-30,tubeheight,0]) HookL();
+  translate([  0,tubeheight,0]) HookR();
+  translate([ 30,         0,0]) SHookL();
+  translate([ 60,         0,0]) SHookR();
+  translate([ 90,         0,0]) THookL();
+  translate([120,         0,0]) THookR();
+}
diff --git a/warptest.scad b/warptest.scad
new file mode 100644 (file)
index 0000000..ee5b3b2
--- /dev/null
@@ -0,0 +1,2 @@
+//rotate([0,0,45])
+cube([3,100,25]);
diff --git a/warptest2.scad b/warptest2.scad
new file mode 100644 (file)
index 0000000..d6a11b8
--- /dev/null
@@ -0,0 +1,8 @@
+// -*- C -*-
+rotate([90,0,0])
+linear_extrude(height=50){
+  polygon([[-3/2, 0],
+          [-11/2, 8],
+          [+11/2, 8],
+          [+3/2, 0]]);
+}
diff --git a/warptest3.scad b/warptest3.scad
new file mode 100644 (file)
index 0000000..ee80051
--- /dev/null
@@ -0,0 +1,30 @@
+// -*- C -*-
+
+dy= 145;
+dx=  65;
+
+h1= 8;
+h2= 14;
+ratio = 0.8;
+
+module Plan(){
+  polygon([[ -dx/2,  0    ],
+          [     0,  dy/2 ],
+          [  dx/2,  0    ],
+          [     0, -dy/2 ]]);
+}
+
+module Solid(){
+  rotate([0,0, -45]) {
+    hull(){
+      linear_extrude(height= h1) {
+       Plan();
+      }
+      linear_extrude(height= h2) {
+       scale(ratio) Plan();
+      }
+    }
+  }
+}
+
+Solid();
diff --git a/wine-vacuum-adapter.scad b/wine-vacuum-adapter.scad
new file mode 100644 (file)
index 0000000..5ce11a8
--- /dev/null
@@ -0,0 +1,74 @@
+// -*- C -*-
+
+india_nom = 27.0;
+india_slop = 0.63;
+
+middia_nom = 31.0;
+middia_slop = 0.10;
+
+outdia = 44.0;
+
+wall = 4;
+
+htop = 5;
+hbot = 7;
+
+slope = 0.65;
+
+$fa=3;
+$fs=0.1;
+
+// calculated
+
+india_use = india_nom + india_slop;
+middia_use = middia_nom - middia_slop;
+
+//echo("MIN WALL", (middia_use - india_use)/2);
+
+ppA = [middia_use/2, 0];
+ppB = ppA + [0, 1] * htop;
+ppC = ppB + [-1,0] * wall;
+ppD = [ppC[0], ppA[1] - wall/slope];
+ppE = [india_use/2, ppD[1] - (india_use/2 - ppD[0])/slope];
+ppF = ppE + [0,-1] * (htop + hbot);
+ppG = ppF + [1, 0] * wall;
+ppK = [outdia/2, ppA[1]];
+ppJ = ppK + [0,-1] * wall;
+ppH = [ppG[0], ppJ[1] - (ppJ[0]-ppG[0])/slope];
+
+module Plan1() {
+  polygon([[ india_use/2,    -hbot ],
+          [ outdia/2,       -hbot ],
+          [ outdia/2,       0     ],
+          [ middia_use/2,   0     ],
+          [ middia_use/2,   htop  ],
+          [ india_use/2,    htop  ]]);
+}
+
+module Plan3() {
+  p = [ ppA,
+       ppB,
+       ppC,
+       ppD,
+       ppE,
+       ppF,
+       ppG,
+       ppH,
+       ppJ,
+       ppK ];
+  echo(p);
+  polygon(p);
+}
+
+module Demo(){
+  color("blue") translate([0,0,1]) Plan1();
+  Plan3();
+}
+
+module Adapter(){
+  rotate_extrude(convexity=5)
+    Plan1();
+}
+
+//Demo();
+Adapter();
diff --git a/xeno-drivebay-bracket.scad b/xeno-drivebay-bracket.scad
new file mode 100644 (file)
index 0000000..2073e05
--- /dev/null
@@ -0,0 +1,195 @@
+// -*- C -*-
+
+basel = 16;
+basew = 24;
+baset = 4.0;
+
+wallt = 2.5;
+
+wallh = 42;
+
+baseholesz = 3.7;
+baseholeslot = 6.5;
+baseholeslop = -0.5;
+
+holeslop = 0.5;
+
+webt = 2.5;
+
+pad = false;
+padw = 12;
+
+padt = webt;
+padl = padw;
+padholesz = 3.0;
+
+wallholeh = 6+14+2;
+wallholesz = 3.0;
+wallholeslot = 4.5;
+
+walll = basel + webt + (pad ? padl : -0.1);
+
+webw = min(basew, pad ? padw : padt);
+
+module slothole(sz, slot, thick, csunk=true, slop=holeslop) {
+  hull(){
+    for (y = [-slot/2,slot/2]) {
+      translate([0,y,-0.15])
+       cylinder(r1=sz/2 + slop,
+                r2=sz/2 + (csunk ? thick : 0) + slop,
+                h=thick+0.30);
+    }
+  }
+}
+
+module Bracket(){
+  difference(){
+    translate([0, -basew, 0])
+      cube([basel, basew, baset]);
+
+    translate([basel/2, -(basew+wallt)/2, 0])
+      slothole(baseholesz, baseholeslot, baset, slop=baseholeslop);
+  }
+
+  difference(){
+    translate([0.1, 0.3, 0.1])
+    rotate([90,0,0]) {
+      linear_extrude(height=wallt){
+       polygon([[0,0],
+                [0, wallh/2 + wallholesz/2 + wallt + wallt],
+                [basel, wallh],
+                [walll, wallh],
+                [walll, wallh - padt - padt],
+                [basel + webt, 0]]);
+      }
+    }
+
+    translate([basel/2, 0, wallholeh])
+      rotate([90,90,0])
+      slothole(wallholesz, wallholeslot, wallt, csunk=false);
+  }
+
+  translate([basel-0.01, 0, 0]) {
+    rotate([90,0,90]) {
+      linear_extrude(height=webt+0.02) {
+       polygon([[-basew, 0],
+                [-basew, baset],
+                [-webw, wallh],
+                [0, wallh],
+                [0, 0]]);
+      }
+    }
+  }
+
+  if (pad) {
+    translate([basel+webt, -padw, wallh-padt]) {
+      difference(){
+       cube([padl, padw, padt]);
+       translate([padl/2, padw/2, -1])
+         cylinder(r=padholesz/2 + holeslop, h=padt+2);
+      }
+    }
+  }
+}
+
+module BracketR(){ ////toplevel
+  rotate([-90,0,0]) Bracket();
+}
+
+module BracketL(){ ////toplevel
+  mirror([1,0,0]) BracketR();
+}
+
+protinnerh = 47;
+protinnerw = 53;
+protd = 45;
+protbaset = 4;
+protwallt = 2;
+protlidt = protwallt;
+protwingd = 28;
+protwingw = 23;
+
+module RearCableProtector(){
+  for (x = [-protwallt, protinnerw]) {
+    translate([x, 0, 0]) {
+      cube([protwallt, protd, protinnerh+protlidt]);
+    }
+  }
+  translate([-(protwallt-0.1), 0, protinnerh])
+    cube([protinnerw + (protwallt-0.1)*2, protd, protlidt]);
+  for (lr = [1,0]) {
+    translate([(lr ? -(protwingw + protwallt) : protinnerw), 0, 0]) {
+      difference(){
+       translate([0, 0, 0])
+         cube([protwingw, protwingd, protbaset]);
+       translate([protwingw/2, protwingd/2, 0])
+         rotate([0,0, lr ? 45 : -45])
+         slothole(baseholesz, baseholeslot, baset, slop=baseholeslop);
+      }
+    }
+  }
+}
+
+module RearCableProtectorT(){ ////toplevel
+  rotate([90,0,0]) RearCableProtector();
+}
+
+chabd = 20;
+chablidw = 40;
+chabinnerh = 11;
+chabwallt = 2;
+chablidt = 2;
+chabwebt = 2.5;
+chabbaset = baset;
+chabbasew = 20;
+chabslot = 3;
+chablidholed = 3;
+chabwebh = 5;
+
+module ChannelBracket(){
+  translate([0, -chabd, 0])
+    cube([chabwallt, chabd, chabinnerh+chablidt]);
+  translate([-chablidw, -chabd, chabinnerh]) {
+    difference(){
+      cube([chablidw + chabwallt - 0.1, chabd - 0.1, chablidt]);
+      translate([chablidw/2, chabd/2, -1])
+       cylinder(r=chablidholed/2, h=chablidt+2, $fn=20);
+    }
+  }
+  translate([chabwallt-0.1, -chabd, 0]) {
+    difference(){
+      cube([chabbasew, chabd-0.1, chabbaset]);
+      translate([chabbasew/2, (chabd-chabwebt)/2, 0])
+       rotate([0,0,90])
+       slothole(baseholesz, chabslot, baset, slop=baseholeslop);
+    }
+  }
+  rotate([90,0,0]) linear_extrude(height=chabwebt) {
+    polygon([[-chablidw, chabinnerh],
+            [-chablidw, chablidt+chabinnerh],
+            [-chabwebh, chablidt+chabinnerh+chabwebh],
+            [+chabwebh, chablidt+chabinnerh+chabwebh],
+            [+chabbasew, chabbaset],
+            [+chabbasew, 0],
+            [0, 0],
+            [0, chabinnerh]]);
+  }
+}
+
+module ChannelBracketT(){
+  rotate([-90,0,0]) ChannelBracket();
+}
+
+module Kit(){ ////toplevel
+  for (y=[0, -wallh-5]) {
+    translate([0,y,0]) {
+      translate([5,0,0]) BracketR();
+      BracketL();
+    }
+  }
+}
+
+//Kit();
+//BracketR();
+//RearCableProtectorT();
+//ChannelBracketT();
diff --git a/y-large-axlebar-washer.scad b/y-large-axlebar-washer.scad
new file mode 100644 (file)
index 0000000..de8553e
--- /dev/null
@@ -0,0 +1,15 @@
+// -*- C -*-
+
+$fa=3;
+$fs=0.1;
+
+r0 = 12 + 0.75;
+r1 = 22;
+h = 7.36 - 0.20;
+
+linear_extrude(height=h, convexity=3) {
+  difference(){
+    circle(r = r1/2);
+    circle(r = r0/2);
+  }
+}
diff --git a/yubikey-5c-nano-loop.scad b/yubikey-5c-nano-loop.scad
new file mode 100644 (file)
index 0000000..654e11c
--- /dev/null
@@ -0,0 +1,74 @@
+// -*- C -*-
+
+include <utils.scad>
+
+base = [ 8.4, 4.1 ];
+base_th = 0.7;
+base_slope = 2.0;
+
+hoop_th = 2.1;
+hoop_inner_dia = 3.0;
+
+time_square = 8;
+
+$fa = 3;
+$fs = 0.1;
+
+// caclulated
+
+loop_post_z = hoop_inner_dia/2;
+max_z = hoop_inner_dia + hoop_th;
+
+module Base() {
+  hull(){
+    linextr(-base_th, -base_th + 0.01)
+      square(base, center=true);
+    linextr(-base_th, 0) {
+      square(base - 2 * base_th * base_slope * [1,1], center=true);
+      LoopPlan2();
+    }
+  }
+}
+
+module LoopPlan() {
+  translate([hoop_inner_dia/2 + hoop_th/2, 0])
+    circle(r = hoop_th/2);
+}
+
+module LoopPlan2() {
+  for (m=[0,1]) {
+    mirror([m,0,0]) LoopPlan();
+  }
+}
+
+module Loop() {
+  translate([0,0, loop_post_z]) {
+    intersection(){
+      rotate([90, 0,0]){
+       rotate_extrude(){
+         LoopPlan();
+       }
+      }
+      linextr(-0.1, hoop_inner_dia*2)
+       square(hoop_inner_dia*4, center=true);
+    }
+  }
+  linextr(0, loop_post_z)
+    LoopPlan2();
+}
+
+module UseUpTime() {
+  linextr(-base_th, max_z)
+    translate([0, base[0] * 3, 0])
+    square(time_square, center=true);
+}
+
+module Whole() {
+  rotate([0,0, 90]) {
+    Base();
+    Loop();
+  }
+  UseUpTime();
+}
+
+Whole();