Ed Nisley's Blog: Shop notes, electronics, firmware, machinery, 3D printing, laser cuttery, and curiosities. Contents: 100% human thinking, 0% AI slop.
Tag: Thing-O-Matic
Using and tweaking a Makerbot Thing-O-Matic 3D printer
The Canon SX230HS tripod mounting screw sits offset from the lens axis, presumably because there’s no way to jam the screw socket under all the machinery that retracts the lens turret. I laid out the mounting plate as a round-cornered triangle with a 1/4-20 clearance hole for the screw that holds the camera to the plate and a 1/4-20 nut sunk under the lens axis for a tripod screw. The main adapter tube supporting the microscope mount or macro lens holder glues into the shallow trench:
Camera mount baseplate – top view
The barely visible trench along the back edge matches the bottom of the camera closely enough to align it against the main tube. This exploded view shows how that works:
Mounting plate camera interface – solid model
In fact, that trench and the hole under the camera’s tripod socket come from subtracting the whole camera model from the mounting plate. I added the fully extended lens turret just for completeness; the body is no wider then necessary to carve that trench:
Canon SX230HS Camera – solid model
The Z=0 plane cuts through the top of the curved bottom shell to simplify the subtraction. The post diameter clears the 1/4-20 tripod screw and is long enough to punch through the mounting plate.
The bottom view shows the recess for the camera tripod screw head, with the support material (which blocks the hole in the picture above) removed:
Camera mount baseplate – support removed
It looks like this with the support plug in place:
Camera mount baseplate – bottom view – with support
Removing the support plug involves twisting the legs with a needle-nose pliers.
The yellow layer comes from switching colors in mid-print, but it certainly adds some much-needed contrast to the image.
The OpenSCAD code there sinks a second 1/4-20 nut that might be useful for an offset tripod mount if you need such a thing.
The deal was, if my Shop Assistant repaired my pocket camera, she could have it. She did, which meant I lost the ability to take pix through the microscope. While I was conjuring up a replacement, it occurred to me that I should also build a gadget to hold a close-up lens in front of the camera for tighter macro shots that don’t quite require a microscope’s magnification.
The solid model of the microscope adapter:
Microscope mount – solid model
The close-up macro adapter, with an LED ring light around the snout:
LED Ring mount – solid model
They have a common camera mounting plate, with a hex recess for a 1/4-20 nut that mates with a standard tripod screw and some support material sticking up through the hole for the screw that holds the camera to the plate:
Mounting plate – solid model – top view
The main tube glues into the plate’s cutout and is long enough to accommodate the fully extended lens turret, with four shallow holes for filament snippet locating pins to align the snout:
Main tube – solid model – bottom view
An exploded view shows how everything fits together, with the stud below the camera representing its tripod mounting screw:
LED Ring mount – solid model – exploded view
More details on the parts will appear over the next few days, but here’s the view through the macro adapter:
Dahlia through macro adapter
Yeah, some slight vignetting, but overall it’s pretty good.
The OpenSCAD source code that builds both adapters:
// Close-up lens mount & Microscope adapter for Canon SX230HS camera
// Ed Nisley KE4ZNU - Nov 2011
Mount = "Eyepiece"; // End result: LEDRing Eyepiece
Layout = "Show"; // Assembly: Show
// Parts: Plate Tube LEDRing Camera Eyepiece
// Build Plates: Build1..4
Gap = 12; // between "Show" objects
include </home/ed/Thing-O-Matic/lib/MCAD/units.scad>
include </home/ed/Thing-O-Matic/Useful Sizes.scad>
include </home/ed/Thing-O-Matic/lib/visibone_colors.scad>
//-------
//- Extrusion parameters must match reality!
// Print with +1 shells, 3 solid layers, 0.2 infill
ThreadThick = 0.33;
ThreadWidth = 2.0 * ThreadThick;
HoleFinagle = 0.2;
HoleFudge = 1.02;
function HoleAdjust(Diameter) = HoleFudge*Diameter + HoleFinagle;
Protrusion = 0.1; // make holes end cleanly
function IntegerMultiple(Size,Unit) = Unit * ceil(Size / Unit);
//-------
// Dimensions
// doublet lens
LensDia = 25.0;
LensRad = LensDia/2;
LensClearance = 0.2;
LensEdge = 6.7;
LensThick = 8.6;
LensRimThick = IntegerMultiple((2.0 + LensThick),ThreadThick);
// LED ring light
LEDRingOD = 50.0;
LEDRingID = 36.0;
LEDBoardThick = 1.5;
LEDThick = 4.0;
LEDRingClearance = 0.5;
LEDWireHoleDia = 3.0;
// microscope eyepiece
EyepieceOD = 30.0;
EyepieceID = 24.0;
EyepieceLength = 25.0;
// camera
// Origin at base of [0] ring, Z+ along lens axis, X+ toward bottom, Y+ toward left
CameraBodyWidth = 2*10.6; // 2 x center-to-curve edge
CameraBaseWidth = 15.5; // flat part of bottom front to back
CameraBaseRadius = (CameraBodyWidth - CameraBaseWidth)/2; // edge rounding
CameraBaseLength = 60.0; // centered on lens axis
CameraBaseHeight = 55.0; // main body height
CameraBaseThick = 0.9; // downward from lens ring
echo(str("Camera base radius: ",CameraBaseRadius));
TripodHoleOffset = -19.0; // mount screw wrt lens centerline
TripodHoleDia = Clear025_20; // clearance hole
TripodScrewHeadDia = 14.5; // recess for screw mounting camera
TripodScrewHeadRad = TripodScrewHeadDia/2;
TripodScrewHeadThick = 3.0;
// main lens tube
TubeDia = [53.0, 44.0, 40.0, 37.6]; // lens rings, [0] is fixed to body
TubeLength = [8.1, 20.6, 17.6, 12.7];
TubeEndClearance = 2.0; // camera lens end to tube end
TubeEndThickness = IntegerMultiple(1.5,ThreadThick);
TubeInnerClearance = 0.5;
TubeInnerLength = TubeLength[0] + TubeLength[1] + TubeLength[2] + TubeLength[3] +
TubeEndClearance;
TubeOuterLength = TubeInnerLength + TubeEndThickness;
TubeID = TubeDia[0] + TubeInnerClearance;
TubeOD = TubeID + 6*ThreadWidth;
TubeWall = (TubeOD - TubeID)/2;
TubeSides = 48;
echo(str("Main tube outer length: ",TubeOuterLength));
echo(str(" ID: ",TubeID," OD: ",TubeOD," wall: ",TubeWall));
// camera mounting base
BaseWidth = IntegerMultiple((CameraBaseWidth + 2*CameraBaseRadius),ThreadThick);
BaseLength = 60.0;
BaseThick = IntegerMultiple((1.0 + Nut025_20Thick + CameraBaseThick),ThreadThick);
// LED ring mount
LEDBaseThick = IntegerMultiple(2.0,ThreadThick); // base under lens + LED ring
LEDBaseRimWidth = IntegerMultiple(6.0,ThreadWidth);
LEDBaseRimThick = IntegerMultiple(LensThick,ThreadThick);
LEDBaseOD = max((LEDRingOD + LEDRingClearance + LEDBaseRimWidth),TubeOD);
echo(str("LED Ring OD: ",LEDBaseOD));
// alignment pins between tube and LED ring / microscope eyepiece
AlignPins = 4;
AlignPinOD = 2.9;
AlignPinCircleDia = TubeOD - 2*TubeWall - 2*AlignPinOD; // 2*PinOD -> more clearance
//-------
module PolyCyl(Dia,Height,ForceSides=0) { // based on nophead's polyholes
Sides = (ForceSides != 0) ? ForceSides : (ceil(Dia) + 2);
FixDia = Dia / cos(180/Sides);
cylinder(r=HoleAdjust(FixDia)/2,h=Height,$fn=Sides);
}
module ShowPegGrid(Space = 10.0,Size = 1.0) {
Range = floor(50 / Space);
for (x=[-Range:Range])
for (y=[-Range:Range])
translate([x*Space,y*Space,Size/2])
%cube(Size,center=true);
}
//-------
//- Camera body segment
// Including lens base and peg for tripod hole access
// Z=0 at edge of lens base ring, X=0 along lens axis
module CameraBody() {
translate([0,0,-CameraBaseThick])
rotate(90)
union() {
translate([0,0,(CameraBaseHeight/2 + CameraBaseRadius)])
minkowski() {
cube([CameraBaseWidth,
(CameraBaseLength + 2*Protrusion),
CameraBaseHeight],center=true);
rotate([90,0,0])
cylinder(r=CameraBaseRadius,h=Protrusion,$fn=8);
}
translate([0,0,(TubeDia[0]/2 + CameraBaseThick)])
rotate([0,90,0])
rotate(180/TubeSides)
cylinder(r=(TubeDia[0]/2 + CameraBaseThick),
h=(CameraBodyWidth/2 + Protrusion),
$fn=TubeSides);
translate([CameraBodyWidth/2,0,(TubeDia[0]/2 + CameraBaseThick)])
rotate([0,90,0])
cylinder(r=TubeDia[0]/2,h=TubeLength[0]);
translate([(TubeLength[0] + CameraBodyWidth/2),
0,(TubeDia[0]/2 + CameraBaseThick)])
rotate([0,90,0])
cylinder(r=TubeDia[1]/2,h=TubeLength[1]);
translate([(TubeLength[0] + TubeLength[1] + CameraBodyWidth/2),
0,(TubeDia[0]/2 + CameraBaseThick)])
rotate([0,90,0])
cylinder(r=TubeDia[2]/2,h=TubeLength[2]);
translate([(TubeLength[0] + TubeLength[1] + TubeLength[2] + CameraBodyWidth/2),
0,(TubeDia[0]/2 + CameraBaseThick)])
rotate([0,90,0])
cylinder(r=TubeDia[3]/2,h=TubeLength[3]);
translate([0,TripodHoleOffset,-BaseThick])
PolyCyl(TripodHoleDia,(BaseThick + 2*Protrusion));
}
}
//- Main tube
module Tube() {
difference() {
cylinder(r=TubeOD/2,h=TubeOuterLength,$fn=TubeSides);
translate([0,0,TubeEndThickness])
PolyCyl(TubeID,(TubeInnerLength + Protrusion),TubeSides);
translate([0,0,-Protrusion]) {
if (Mount == "LEDRing")
cylinder(r=LensRad,h=(TubeEndThickness + 2*Protrusion));
if (Mount == "Eyepiece")
cylinder(r=EyepieceID/2,h=(TubeEndThickness + 2*Protrusion));
}
for (Index = [0:AlignPins-1])
rotate(Index*90)
translate([(AlignPinCircleDia/2),0,-ThreadThick])
rotate(180) // flat sides outward
PolyCyl(AlignPinOD,TubeEndThickness);
}
}
//- Base plate
module BasePlate() {
union() {
difference() {
linear_extrude(height=BaseThick)
hull() {
translate([-(BaseLength/2 - BaseWidth/2),0,0])
circle(BaseWidth/2);
translate([ (BaseLength/2 - BaseWidth/2),0,0])
circle(BaseWidth/2);
translate([0,(0.75*BaseLength),0])
circle(BaseWidth/2);
}
translate([0,0,BaseThick])
CameraBody();
translate([0,(TubeOuterLength + CameraBodyWidth/2),
(BaseThick + TubeDia[0]/2)])
rotate([90,0,0])
PolyCyl(TubeOD,TubeOuterLength,$fn=TubeSides);
translate([0,0,3*ThreadThick])
PolyCyl((Nut025_20Dia*sqrt(3)/2),2*Nut025_20Thick,6); // dia across hex flats
translate([0,0,-Protrusion])
PolyCyl(Clear025_20,(BaseThick + 2*Protrusion));
translate([TripodHoleOffset,0,3*ThreadThick])
PolyCyl((Nut025_20Dia*sqrt(3)/2),2*Nut025_20Thick,6); // dia across hex flats
translate([TripodHoleOffset,0,-Protrusion])
PolyCyl(Clear025_20,(BaseThick + 2*Protrusion));
translate([-TripodHoleOffset,0,-Protrusion])
PolyCyl(TripodScrewHeadDia,(TripodScrewHeadThick + Protrusion));
}
translate([-TripodHoleOffset,0,0]) { // support for tripod screw hole
for (Index=[0:3])
rotate(Index*45)
translate([-ThreadWidth,-TripodScrewHeadRad,0])
cube([2*ThreadWidth,TripodScrewHeadDia,TripodScrewHeadThick]);
cylinder(r=0.4*TripodScrewHeadRad,h=(BaseThick - CameraBaseThick),$fn=9);
}
}
}
//- LED mounting ring
module LEDRing() {
difference() {
cylinder(r=LEDBaseOD/2,h=LensRimThick,$fn=48);
translate([0,0,-Protrusion])
PolyCyl((LensDia + LensClearance),
(LensRimThick + 2*Protrusion));
translate([0,0,LEDBaseRimThick])
difference() {
PolyCyl(LEDBaseOD,LensThick);
PolyCyl(LEDRingID,LensThick);
}
translate([0,0,LEDBaseThick])
difference() {
PolyCyl((LEDRingOD + LEDRingClearance),LensThick);
cylinder(r1=HoleAdjust(LEDRingID - LEDRingClearance)/2,
r2=HoleAdjust(LensDia + LensClearance)/2 + 2*ThreadWidth,
h=LensThick);
}
for (Index = [0:AlignPins-1])
rotate(Index*90)
translate([(AlignPinCircleDia/2),0,-ThreadThick])
rotate(180) // flat sides outward
PolyCyl(AlignPinOD,LEDBaseThick);
rotate(45)
translate([0,LEDRingID/2,(LEDBaseThick + 1.2*LEDWireHoleDia/2)])
rotate([0,-90,0]) // flat side down
rotate([-90,0,0])
PolyCyl(LEDWireHoleDia,2*LEDBaseRimWidth);
}
}
//- Microscope eyepiece adapter
module EyepieceMount() {
difference() {
cylinder(r1=TubeOD/2,
r2=(EyepieceOD + 8*ThreadWidth)/2,
h=EyepieceLength,
$fn=TubeSides);
translate([0,0,-Protrusion])
PolyCyl(EyepieceOD,(EyepieceLength + 2*Protrusion));
for (Index = [0:AlignPins-1])
rotate(Index*90)
translate([(AlignPinCircleDia/2),0,-ThreadThick])
rotate(180) // flat sides outward
PolyCyl(AlignPinOD,6*ThreadThick);
}
}
//-------
// Build it!
if (Layout != "Show")
ShowPegGrid();
if (Layout == "Tube")
Tube();
if (Layout == "LEDRing")
LEDRing();
if (Layout == "Plate")
BasePlate();
if (Layout == "Camera")
CameraBody();
if (Layout == "Eyepiece")
EyepieceMount();
if (Layout == "Build1")
translate([0,-BaseLength/3,0])
BasePlate();
if (Layout == "Build2")
Tube();
if (Layout == "Build3")
LEDRing();
if (Layout == "Build4")
EyepieceMount();
if (Layout == "Show") {
translate([0,TubeOuterLength,TubeDia[0]/2]) {
rotate([90,0,0])
color(LTC) Tube();
translate([0,Gap,0])
rotate([-90,0,0]) {
if (Mount == "LEDRing")
color(OOR) LEDRing();
if (Mount == "Eyepiece")
color(OOR) EyepieceMount();
}
}
translate([0,-CameraBodyWidth/2,0])
color(PG) CameraBody();
color(PDA)
render()
translate([0,-CameraBodyWidth/2,-(BaseThick + Gap)])
BasePlate();
}
The SX230HS camera lives in my pants pocket, where it gets pressed between my leg and anything I lean against. Turns out that the lens turret end cap isn’t quite thick enough to not bend inward against the leaves that cover the lens, which causes them to hang up. The solution boils down to a hideous external lens cap:
Canon SX230HS with lens cap
It’s built from forget-me-not yellow filament for an obvious reason…
The sheet-metal plate bears against the non-moving rim around the turret. I marked the plate’s diameter with a compass, extracted it from the sheet with left-cutting tin snips, filed off the slivers, rounded the edge, and it snapped right into the recess where a touch of acrylic caulk holds it firmly in place.
A thin plastic cover would be too flexible and a thicker plastic cover would be too thick; this must fit into an already-snug cloth pouch where a few additional millimeters of girth actually matter. My previous camera taught me that pocket fuzz gets into everything, so a pouch isn’t optional.
The interior isn’t too inspiring, but you can see what two layers of plastic look like across the bottom:
SX230HS lens cap – interior
The front has the shallow recess that captures the metal plate. Because the front builds against the aluminum build platform, I added a support structure inside the recess:
SX230HS lens cap – support in place
The solid model gives a better view:
Lens cap – solid model – bottom view
It’s basically a ring with tabs under the recess. The ring OD matches the lens caps’s ID, with a height equal to the recess depth, so only the tabs contact the cap. I removed them by twisting each tab with a needle-nose pliers until the whole thing popped loose:
SX230HS lens cap – support structure
A bit of scraper and scalpel cleanup and it’s all good. The detail pix show the first trial of the lens cap, which lacks the nice bevel around the front rim.
The camera is smart enough to notice when something blocks the lens: it immediately shuts down and displays a lens failure error message. That’s probably not a Good Thing on a regular basis, but it doesn’t seem to do any harm.
FWIW, my previous pocket camera, a Casio EX-Z850 , sported a recessed and somewhat thicker turret end cap that didn’t have this problem. Mary says she’ll make a case for this camera, too, but until then I’m using a pouch from a dinky VOIP phone that just barely holds the camera.
The OpenSCAD source code:
// Lens cap for Canon SX230HS
// Ed Nisley KE4ZNU - Nov 2011
//-------
//- Extrusion parameters must match reality!
// Print with +1 shells, 3 solid layers, 0.2 infill
ThreadThick = 0.33;
ThreadWidth = 2.0 * ThreadThick;
HoleFinagle = 0.20;
HoleFudge = 1.00;
function HoleAdjust(Diameter) = HoleFudge*Diameter + HoleFinagle;
Protrusion = 0.1; // make holes end cleanly
function IntegerMultiple(Size,Unit) = Unit * ceil(Size / Unit);
//-------
// Dimensions
LensDia = 53.0;
LensRad = LensDia/2;
LensLength = 8.0;
PlateThick = IntegerMultiple(0.75,ThreadThick);
PlateDia = 48.0;
Shell = 2*ThreadWidth;
Spacer = 2*ThreadThick;
CapOD = LensDia + 2*Shell;
CapLength = LensLength + Spacer + PlateThick;
CapSides = 48;
CenterHoleDia = 44.0;
BevelWidth = PlateThick;
NumStruts = 16;
SupportStrutLen = (PlateDia - ThreadWidth)/2; // small gap to cap
//-------
module PolyCyl(Dia,Height,ForceSides=0) { // based on nophead's polyholes
Sides = (ForceSides != 0) ? ForceSides : (ceil(Dia) + 2);
FixDia = Dia / cos(180/Sides);
cylinder(r=HoleAdjust(FixDia)/2,h=Height,$fn=Sides);
}
module ShowPegGrid(Space = 10.0,Size = 1.0) {
Range = floor(50 / Space);
for (x=[-Range:Range])
for (y=[-Range:Range])
translate([x*Space,y*Space,Size/2])
%cube(Size,center=true);
}
//-------
ShowPegGrid();
difference() {
PolyCyl(CapOD,CapLength,CapSides);
translate([0,0,(Spacer + PlateThick)]) // lens shell
PolyCyl(LensDia,(LensLength + Protrusion),CapSides);
translate([0,0,-Protrusion]) // center hole
PolyCyl(CenterHoleDia,(CapLength + Protrusion));
translate([0,0,-Protrusion]) // bevel
difference() {
cylinder(r=(CapOD/2 + 2*(BevelWidth + Protrusion)),
h=(2*BevelWidth + Protrusion),
$fn=CapSides);
cylinder(r1=(CapOD/2 - BevelWidth - Protrusion),
r2=(CapOD/2 + BevelWidth),
h=(2*BevelWidth + Protrusion),
$fn=CapSides);
}
difference() {
translate([0,0,-Protrusion]) // cover plate recess
PolyCyl(PlateDia,(PlateThick + Protrusion));
for (Index=[0:(NumStruts - 1)]) // support struts
rotate(Index*360/NumStruts)
translate([-ThreadWidth,-SupportStrutLen,0])
cube([2*ThreadWidth,SupportStrutLen,PlateThick]);
}
}
difference() { // support ring
PolyCyl(CenterHoleDia,PlateThick);
translate([0,0,-Protrusion])
PolyCyl((CenterHoleDia - 4*ThreadWidth),(PlateThick + 2*Protrusion));
}
My Useful Sizes.scad file has been accumulating the dimensions of nuts & bolts & a motor that don’t (seem to) appear elsewhere in the OpenSCAD universe:
The Logitech notebook webcam that peers into the Thing-O-Matic has terrible dynamic range compensation; turning on the LED ring light washes out the image something awful. An old Logitech ball camera seems better, but it sits atop a rubbery dingus adapted to grip huge old laptops. So I built an adapter with a standard 1/4-20 tripod screw thread in the bottom that ought to make it more useful.
The old & new mounts compared:
Logitech ball camera mounts
The color change comes from switching to yellow filament for an upcoming larger object.
The solid model shows those tiny little notches will require a bit of riffler file work:
Logitech camera tripod adapter – solid model
The bottom has a blind 1/4-20 tapped hole. Lacking a bottoming tap, not having any broken 1/4-20 taps, and being unwilling to grind the end off a perfectly good taper tap, I filed three notches along a bolt. Ran the taper tap in until it hit bottom, ran the bolt in likewise, and defined the result to be Good Enough:
Homebrew bottoming tap
On the other end, the most probable failure will leave that delicate little post jammed firmly inside the camera’s socket. There’s not enough post to allow printing a small guide hole, but there’s no real need for one; I drilled a #50 hole right down the middle, ran a 2-56 screw into it without tapping the hole, and filed the screw head flat:
Camera mount with filed screw
After cleaning up those notches, it snapped solidly into place:
Logitech ball camera with mount
And then the camera sits neatly atop a cheap Gorillapod knockoff:
Logitech ball camera on tripod
That tiny reddish dot in the middle of the imposing set of rings marks the actual lens, so it’s more of a pinhole camera than anything else. The fixed focus kicks in beyond a meter, but a bit of rummaging in the Box o’ Lenses produced a random meniscus lens that pulled the focus in to maybe 100 mm. Alas, that means the camera must float in mid-air about 15 mm inside the Thing-O-Matic’s box. If I can conjure up a mount that holds the ball inside the box, above-and-forward of the stage, that’d work great. VLC can allegedly rotate the image upside-down, so maybe I can mount it bottom-up.
Here’s everything I know about those two cameras, with the ball camera on top and the webcam on the bottom:
Logitech ball and notebook webcam data
Apparently it’s easier to put that information on a tag than provide a good old data plate on the camera body.
The OpenSCAD source code:
// Tripod mount for Logitech ball camera
// Ed Nisley KE4ZNU - Oct 2011
include </home/ed/Thing-O-Matic/lib/MCAD/units.scad>
include </home/ed/Thing-O-Matic/Useful Sizes.scad>
include </home/ed/Thing-O-Matic/lib/visibone_colors.scad>
//-------
//- Extrusion parameters must match reality!
// Print with +0 shells and 3 solid layers
ThreadThick = 0.33;
ThreadWidth = 2.0 * ThreadThick;
HoleFinagle = 0.2;
HoleFudge = 1.02;
function HoleAdjust(Diameter) = HoleFudge*Diameter + HoleFinagle;
function IntegerMultiple(Size,Unit) = Unit * ceil(Size / Unit);
Protrusion = 0.1; // make holes end cleanly
//-------
// Dimensions
BallDia = 60.0; // camera ball
BallRad = BallDia/2;
BaseDia = 16.0; // interface at tripod surface
BaseRad = BaseDia/2;
BaseLength = 10.0; // to base of ball
BoltDia = Tap025_20; // standard 1/4-20 thread
BoltLength = 7.0;
StemLength = 8.5;
StemDia = 4.7;
StemRad = StemDia/2;
FlangeWidth = 6.6;
FlangeThick = 2.6;
NotchSectionDia = 1.4; // toroid cross-section diameter
NotchSectionRad = NotchSectionDia/2;
NotchOffset = 2.3; // from top of stem
//-------
module PolyCyl(Dia,Height,ForceSides=0) { // based on nophead's polyholes
Sides = (ForceSides != 0) ? ForceSides : (ceil(Dia) + 2);
FixDia = Dia / cos(180/Sides);
cylinder(r=HoleAdjust(FixDia)/2,h=Height,$fn=Sides);
}
module ShowPegGrid(Space = 10.0,Size = 1.0) {
Range = floor(50 / Space);
for (x=[-Range:Range])
for (y=[-Range:Range])
translate([x*Space,y*Space,Size/2])
%cube(Size,center=true);
}
//-------
//
ShowPegGrid();
translate([0,0,BaseLength])
union() {
difference() {
translate([0,0,-BaseLength])
cylinder(r=BaseRad,h=2*BaseLength);
translate([0,0,BallRad])
sphere(r=BallRad);
translate([0,0,-(BaseLength + Protrusion)])
PolyCyl(BoltDia,(BoltLength + Protrusion));
}
rotate(180/16)
cylinder(r=StemRad,h=StemLength,$fn=16);
difference() {
translate([0,0,StemLength/2])
cube([FlangeWidth,FlangeThick,StemLength],center=true);
translate([0,0,(StemLength - NotchOffset)])
rotate_extrude(convexity=3,$fn=64)
translate([FlangeWidth/2,0,0])
circle(r=NotchSectionRad,$fn=16);
translate([0,-FlangeWidth/2,StemLength + sqrt(FlangeWidth)])
rotate([0,45,0])
cube(FlangeWidth + 2*Protrusion);
translate([0,FlangeWidth/2,StemLength + sqrt(FlangeWidth)])
rotate([0,45,180])
cube(FlangeWidth + 2*Protrusion);
}
}
Scaling the cubes to about 15 mm on a side puts a 6×6 array neatly on the build plate. Takes nigh onto four hours to print all 36 of them at 30 mm/s print and 100 mm/s move… a bit over 6 minutes each.
The print quality is Good Enough. The bottom surface of the front cubes faces forward and reflects the scale markings:
When confronted with a zombie horde, though, nothing exceeds like excess:
Finned CO2 Cartridge Array
In real life, they’re 12 gram CO2 capsules, of the type used in tire inflators and air pistols. I knew I’d find something to do with the box of empties I’d been accumulating: they became (somewhat threatening) tchotchkes. This was inspired by that thing, although that STL file doesn’t render into anything and, as with many interesting Thingiverse things, there’s no source code.
These fins were an exercise in thin-wall printing: the outer square is one thread thick, the diagonal struts are two threads, and the ring around the nozzle has just a touch of fill inside, with a one-thread-thick base below the cartridge nozzle:
Fin Array on build platform
The solid model looks about like you’d expect:
Fin Assembly- solid model
The teeny little quarter-cylinders in the corners encourage Skeinforge to do the right thing: build each quadrant in one pass, leaving the corners unfinished. The diagonals must be exactly two threads wide to make that possible: each strut thread connects to the corresponding single-thread outer edge.
It turns out that my box has several different types of CO2 cartridges and the nozzle ends are all different. To get it right, there’s a template for matching the curves:
Cartridge nozzle template
That end of the cartridge consists of a cylinder for the body, a sphere mated to a tangential conic section, another conic fillet, and then the cylindrical nozzle. Basically, you twiddle with the parameters until the template comes pretty close to fitting, then fire off a few trial fins until it comes out right.
CO2 Capsule Nozzle – solid model detail
They were a big hit at the Long Island Linux Users Group meeting…
The OpenSCAD source code:
// CO2 capsule tail fins
// Ed Nisley KE4ZNU - Oct 2011
Layout = "Show"; // Show Build FinBlock Cartridge Fit
include
//-------
//- Extrusion parameters must match reality!
// Print with +0 shells and 3 solid layers
ThreadThick = 0.33;
ThreadWidth = 2.0 * ThreadThick;
HoleWindage = 0.2;
Protrusion = 0.1; // make holes end cleanly
function IntegerMultiple(Size,Unit) = Unit * ceil(Size / Unit);
//-------
// Capsule dimensions
BodyDia = 18.70;
BodyRad = BodyDia/2;
BodyLength = 53.0; // between hemispherical endcap centers
BodyBaseLength = 21; // tip to endcap center
TipDia = 7.40;
TipRad = TipDia/2;
TipLength = IntegerMultiple(4.0,ThreadThick);
FilletLength = 5.0; // fillet between tip and cone
FilletTop = TipLength + FilletLength;
FilletBaseDia = 8.60;
FilletBaseRad= FilletBaseDia/2;
FilletTopDia = 9.5;
FilletTopRad = FilletTopDia/2;
ConeTop = 16.0; // tip to tangent with endcap
ConeLength = ConeTop - FilletTop;
echo(str("Cone Length: ",ConeLength));
IntersectZ = ConeTop; // coordinates of intersect tangent
IntersectX = sqrt(pow(BodyRad,2) - pow(BodyBaseLength - ConeTop,2));
echo(str("IntersectZ: ",IntersectZ));
echo(str("IntersectX: ",IntersectX," dia: ",2*IntersectX));
//-------
// Fin dimensions
FinThick = 1*ThreadWidth; // outer square
StrutThick = 2*FinThick; // diagonal struts
FinSquare = 24.0;
FinTaperLength = sqrt(2)*FinSquare/2 - sqrt(2)*FinThick - ThreadWidth;
FinBaseLength = 2*TipLength;
//-------
module PolyCyl(Dia,Height,ForceSides=0) { // based on nophead's polyholes
Sides = (ForceSides != 0) ? ForceSides : (ceil(Dia) + 2);
FixDia = Dia / cos(180/Sides);
cylinder(r=(FixDia + HoleWindage)/2,h=Height,$fn=Sides);
}
module ShowPegGrid(Space = 10.0,Size = 1.0) {
Range = floor(50 / Space);
for (x=[-Range:Range])
for (y=[-Range:Range])
translate([x*Space,y*Space,Size/2])
%cube(Size,center=true);
}
//-------
// CO2 cartridge outline
module Cartridge() {
$fn = 48;
union() {
translate([0,0,BodyBaseLength]) {
cylinder(r=BodyDia/2,h=BodyLength);
translate([0,0,BodyLength])
sphere(r=BodyRad);
}
intersection() {
translate([0,0,BodyBaseLength])
sphere(r=BodyRad);
union() {
translate([0,0,(TipLength + FilletLength+ConeLength)])
cylinder(r=BodyRad,h=(BodyBaseLength - ConeLength));
translate([0,0,(TipLength + FilletLength)])
cylinder(r1=FilletTopRad,r2=IntersectX,h=(ConeLength + Protrusion));
translate([0,0,TipLength])
cylinder(r1=FilletBaseRad,r2=FilletTopRad,h=(FilletLength + Protrusion));
}
}
translate([0,0,FilletTop])
cylinder(r1=FilletTopRad,r2=IntersectX,h=ConeLength);
translate([0,0,TipLength])
cylinder(r1=FilletBaseRad,r2=FilletTopRad,h=(FilletLength + Protrusion));
translate([0,0,-Protrusion])
PolyCyl(TipDia,(TipLength + 2*Protrusion));
}
}
//-------
// Diagonal fin strut
module FinStrut() {
rotate([90,0,45])
translate([0,0,-StrutThick/2])
linear_extrude(height=StrutThick)
polygon(points=[
[0,0],
[FinTaperLength,0],
[FinTaperLength,FinBaseLength],
[0,(FinBaseLength + FinTaperLength)]
]);
}
//-------
// Fin outline
module FinBlock() {
union() {
translate([0,0,FinBaseLength/2])
difference() {
cube([FinSquare,FinSquare,FinBaseLength],center=true);
difference() {
cube([(FinSquare - 2*FinThick),
(FinSquare - 2*FinThick),
(FinBaseLength + 2*Protrusion)],center=true);
for (Index = [0:3])
rotate(Index*90)
translate([(FinSquare/2 - FinThick),(FinSquare/2 - FinThick),0])
cylinder(r=StrutThick,h=(FinBaseLength + 2*Protrusion),center=true,$fn=16);
}
}
for (Index = [0:3])
rotate(Index*90)
FinStrut();
cylinder(r=IntegerMultiple((FilletBaseRad + StrutThick),ThreadWidth),h=TipLength);
}
}
//-------
// Fins
module FinAssembly() {
difference() {
FinBlock();
translate([0,0,ThreadThick]) // add one layer to close base cylinder
Cartridge();
}
}
module FinFit() {
translate([0,0.75*BodyBaseLength,2*ThreadThick])
rotate([90,0,0])
difference() {
translate([-FinSquare/2,-2*ThreadThick,0])
cube([IntegerMultiple(FinSquare,ThreadWidth),
4*ThreadThick,
1.5*BodyBaseLength]);
translate([0,0,5*ThreadWidth])
Cartridge();
}
}
//-------
// Build it!
ShowPegGrid();
if (Layout == "FinBlock")
FinBlock();
if (Layout == "Cartridge")
Cartridge();
if (Layout == "Show") {
FinAssembly();
color(LG) Cartridge();
}
if (Layout == "Fit")
FinFit();
if (Layout == "Build")
FinAssembly();