Initialize project

This commit is contained in:
Matt McWilliams 2024-02-18 10:09:33 -05:00
commit a5d640e5b8
5 changed files with 1698 additions and 0 deletions

215
Triangles.scad Normal file
View File

@ -0,0 +1,215 @@
/*
Triangles.scad
Author: Tim Koopman
https://github.com/tkoopman/Delta-Diamond/blob/master/OpenSCAD/Triangles.scad
angleCA
/|\
a / H \ c
/ | \
angleAB ------- angleBC
b
Standard Parameters
center: true/false
If true same as centerXYZ = [true, true, true]
centerXYZ: Vector of 3 true/false values [CenterX, CenterY, CenterZ]
center must be left undef
height: The 3D height of the Triangle. Ignored if heights defined
heights: Vector of 3 height values heights @ [angleAB, angleBC, angleCA]
If CenterZ is true each height will be centered individually, this means
the shape will be different depending on CenterZ. Most times you will want
CenterZ to be true to get the shape most people want.
*/
/*
Triangle
a: Length of side a
b: Length of side b
angle: angle at point angleAB
*/
module Triangle(
a, b, angle, height=1, heights=undef,
center=undef, centerXYZ=[false,false,false])
{
// Calculate Heights at each point
heightAB = ((heights==undef) ? height : heights[0])/2;
heightBC = ((heights==undef) ? height : heights[1])/2;
heightCA = ((heights==undef) ? height : heights[2])/2;
centerZ = (center || (center==undef && centerXYZ[2]))?0:max(heightAB,heightBC,heightCA);
// Calculate Offsets for centering
offsetX = (center || (center==undef && centerXYZ[0]))?((cos(angle)*a)+b)/3:0;
offsetY = (center || (center==undef && centerXYZ[1]))?(sin(angle)*a)/3:0;
pointAB1 = [-offsetX,-offsetY, centerZ-heightAB];
pointAB2 = [-offsetX,-offsetY, centerZ+heightAB];
pointBC1 = [b-offsetX,-offsetY, centerZ-heightBC];
pointBC2 = [b-offsetX,-offsetY, centerZ+heightBC];
pointCA1 = [(cos(angle)*a)-offsetX,(sin(angle)*a)-offsetY, centerZ-heightCA];
pointCA2 = [(cos(angle)*a)-offsetX,(sin(angle)*a)-offsetY, centerZ+heightCA];
polyhedron(
points=[ pointAB1, pointBC1, pointCA1,
pointAB2, pointBC2, pointCA2 ],
faces=[
[0, 1, 2],
[3, 5, 4],
[0, 3, 1],
[1, 3, 4],
[1, 4, 2],
[2, 4, 5],
[2, 5, 0],
[0, 5, 3] ] );
}
/*
Isosceles Triangle
Exactly 2 of the following paramaters must be defined.
If all 3 defined H will be ignored.
b: length of side b
angle: angle at points angleAB & angleBC.
*/
module Isosceles_Triangle(
b, angle, H=undef, height=1, heights=undef,
center=undef, centerXYZ=[true, false, false])
{
valid = (angle!=undef)?((angle < 90) && (b!=undef||H!=undef)) : (b!=undef&&H!=undef);
ANGLE = (angle!=undef) ? angle : atan(H / (b/2));
a = (b==undef)?(H/sin((180-(angle*2))/2)) :
(b / cos(ANGLE))/2;
B = (b==undef)? (cos(angle)*a)*2:b;
if (valid)
{
Triangle(a=a, b=B, angle=ANGLE, height=height, heights=heights,
center=center, centerXYZ=centerXYZ);
} else {
echo("Invalid Isosceles_Triangle. Must specify any 2 of b, angle and H, and if angle used angle must be less than 90");
}
}
/*
Right Angled Triangle
Create a Right Angled Triangle where the hypotenuse will be calculated.
|\
a| \
| \
----
b
a: length of side a
b: length of side b
*/
module Right_Angled_Triangle(
a, b, height=1, heights=undef,
center=undef, centerXYZ=[false, false, false])
{
Triangle(a=a, b=b, angle=90, height=height, heights=heights,
center=center, centerXYZ=centerXYZ);
}
/*
Wedge
Is same as Right Angled Triangle with 2 different heights, and rotated.
Good for creating support structures.
*/
module Wedge(a, b, w1, w2)
{
rotate([90,0,0])
Right_Angled_Triangle(a, b, heights=[w1, w2, w1], centerXYZ=[false, false, true]);
}
/*
Equilateral Triangle
Create a Equilateral Triangle.
l: Length of all sides (a, b & c)
H: Triangle size will be based on the this 2D height
When using H, l is ignored.
*/
module Equilateral_Triangle(
l=10, H=undef, height=1, heights=undef,
center=undef, centerXYZ=[true,false,false])
{
L = (H==undef)?l:H/sin(60);
Triangle(a=L,b=L,angle=60,height=height, heights=heights,
center=center, centerXYZ=centerXYZ);
}
/*
Trapezoid
Create a Basic Trapezoid (Based on Isosceles_Triangle)
d
/----\
/ | \
a / H \ c
/ | \
angle ------------ angle
b
b: Length of side b
angle: Angle at points angleAB & angleBC
H: The 2D height at which the triangle should be cut to create the trapezoid
heights: If vector of size 3 (Standard for triangles) both cd & da will be the same height, if vector have 4 values [ab,bc,cd,da] than each point can have different heights.
*/
module Trapezoid(
b, angle=60, H, height=1, heights=undef,
center=undef, centerXYZ=[true,false,false])
{
validAngle = (angle < 90);
adX = H / tan(angle);
// Calculate Heights at each point
heightAB = ((heights==undef) ? height : heights[0])/2;
heightBC = ((heights==undef) ? height : heights[1])/2;
heightCD = ((heights==undef) ? height : heights[2])/2;
heightDA = ((heights==undef) ? height : ((len(heights) > 3)?heights[3]:heights[2]))/2;
// Centers
centerX = (center || (center==undef && centerXYZ[0]))?0:b/2;
centerY = (center || (center==undef && centerXYZ[1]))?0:H/2;
centerZ = (center || (center==undef && centerXYZ[2]))?0:max(heightAB,heightBC,heightCD,heightDA);
// Points
y = H/2;
bx = b/2;
dx = (b-(adX*2))/2;
pointAB1 = [centerX-bx, centerY-y, centerZ-heightAB];
pointAB2 = [centerX-bx, centerY-y, centerZ+heightAB];
pointBC1 = [centerX+bx, centerY-y, centerZ-heightBC];
pointBC2 = [centerX+bx, centerY-y, centerZ+heightBC];
pointCD1 = [centerX+dx, centerY+y, centerZ-heightCD];
pointCD2 = [centerX+dx, centerY+y, centerZ+heightCD];
pointDA1 = [centerX-dx, centerY+y, centerZ-heightDA];
pointDA2 = [centerX-dx, centerY+y, centerZ+heightDA];
validH = (adX < b/2);
if (validAngle && validH)
{
polyhedron(
points=[ pointAB1, pointBC1, pointCD1, pointDA1,
pointAB2, pointBC2, pointCD2, pointDA2 ],
triangles=[
[0, 1, 2],
[0, 2, 3],
[4, 6, 5],
[4, 7, 6],
[0, 4, 1],
[1, 4, 5],
[1, 5, 2],
[2, 5, 6],
[2, 6, 3],
[3, 6, 7],
[3, 7, 0],
[0, 7, 4] ] );
} else {
if (!validAngle) echo("Trapezoid invalid, angle must be less than 90");
else echo("Trapezoid invalid, H is larger than triangle");
}
}

827
gnal_v3.scad Normal file
View File

@ -0,0 +1,827 @@
//GNAL v3 Shared Library
include <./path_extrude.scad>;
include <./threads.scad>;
include <./Triangles.scad>;
/**
* THREADS
* TOP (large screw)
* metric_thread (diameter=13.6, pitch=1.5 ,thread_size = 1.6, length = 21);
* TOP VOID
* metric_thread (diameter=13.6 + .5, pitch=1.5, thread_size = 1.6, length = 21);
* + clone translated along Z by 0.2mm
* BOTTOM (small screw)
* metric_thread (diameter=10, pitch=1.5, thread_size = 1.6, length=LEN);
* SINGLE LEVEL (middle screw)
*
*/
DEBUG = false;
FINE = 200;
OD = 10 + .5;
PITCH = 1.5;
THREAD = 1.6;
LEN = 21;
INSERT_D = 26;
SINGLE_THREAD_D = 12;
function X (start_r, spacing, fn, r, i) = (start_r + (r * spacing) + (i * calcIncrement(spacing, fn))) * cos(i * calcAngle(fn));
function Y (start_r, spacing, fn, r, i) = (start_r + (r * spacing) + (i * calcIncrement(spacing, fn))) * sin(i * calcAngle(fn));
function circ (d) = PI * d;
function calcFacetSize (end_d, fn) = circ( end_d ) / fn;
//function calcSteps(rotations, fn) = fn * rotations;
function calcAngle (fn) = 360 / fn;
function calcFn(start_d, start_fn, end_d, spacing, r) = start_fn +
( ((circ(calcR(start_d, spacing, r) * 2) - circ(start_d) )
/ (circ(end_d) - circ(start_d))) * ($fn - start_fn));
function calcR(start_d, spacing, r) = (start_d / 2) + (spacing * r);
function calcIncrement(spacing, fn) = spacing / fn;
/**
* spiral_7 - Combination of spiral_3 and spiral_4 that doesn't sacrifice
* performance. Hits an overflow when $fn is higher than 245 which creates
* 8418 vectors at 60 rotations. This is an edge case, only appearing in OpenSCAD
* 2019.05 (and maybe earlier), but should be explored.
**/
module spiral (rotations = 40, start_d = 48, spacing = 2.075, bottom = -7.1, fn) {
//bottom = -7.1;
w = 1.4;
top_w = .8;
top_offset = (w - top_w);
h = 2.2;
facetProfile = [
[w, -bottom],
[0, -bottom],
[0, 0],
[top_offset, -h],
[w, -h],
[w, 0]
];
end_d = start_d + (spacing * 2 * rotations);
end_r = end_d / 2;
start_r = start_d / 2;
facetSize = calcFacetSize(end_d, fn);
start_fn = round(circ(start_d) / facetSize);
spiralPath = [ for (r = [0 : rotations - 1]) for (i = [0 : round(calcFn(start_d, start_fn, end_d, spacing, r )) - 1 ])
[
X(start_r, spacing, round(calcFn(start_d, start_fn, end_d, spacing, r )), r, i),
Y(start_r, spacing, round(calcFn(start_d, start_fn, end_d, spacing, r )), r, i),
0]
];
path_extrude(exShape=facetProfile, exPath=spiralPath);
}
module spiral_reinforcement ( start_d = 48, spacing = 2.075, bottom = -2, fn) {
rotations = 1;
w = 1;
top_w = .8;
top_offset = (w - top_w);
h = 2.2;
facetProfile = [
[w, -bottom],
[0, -bottom],
[0, 0],
[0, -h],
[w, -h],
[w, 0]
];
end_d = start_d + (spacing * 2 * rotations);
end_r = end_d / 2;
start_r = start_d / 2;
facetSize = calcFacetSize(end_d, fn);
start_fn = round(circ(start_d) / facetSize);
spiralPath = [ for (r = [0 : rotations - 1]) for (i = [0 : round(calcFn(start_d, start_fn, end_d, spacing, r )) - 1 ])
[
X(start_r, spacing, round(calcFn(start_d, start_fn, end_d, spacing, r )), r, i),
Y(start_r, spacing, round(calcFn(start_d, start_fn, end_d, spacing, r )), r, i),
0]
];
path_extrude(exShape=facetProfile, exPath=spiralPath);
}
/**
* Core (center of the reel)
**/
module gnal_spiral_core () {
$fn = 360;
core_center_h = 4.2 + 3;;
core_bottom_outer_d = 53;
core_bottom_outer_void_d = 44;
core_bottom_outer_h = 4.2;
core_d = 29.5;
core_h = 8.5;
core_bottom_d = 26;
core_bottom_h = 4.2;
top_z_offset = (core_h / 2) - (core_center_h / 2);
core_void_outer_d = 20.5;
core_void_inner_d = 14.5;
core_void_h = 11.5;
arms_outer_d = 48;
arms_inner_d = 48 - 7;
void_d = 18;
film_void = 0.6;
difference () {
union() {
//center
translate([0, 0, -core_center_h / 2]) {
cylinder(r = (core_bottom_outer_d - 1) / 2, h = core_center_h, center = true);
}
//top
translate([0, 0, top_z_offset]) {
cylinder(r = core_d / 2, h = core_h + core_center_h, center = true);
}
}
cylinder(r = void_d / 2, h = 30, center = true);
translate([0, 0, -7.2]) spiral_insert_void();
}
//arms
difference () {
union () {
translate([0, 0, top_z_offset]) difference() {
//adjusted arm (shorter)
intersection () {
cylinder(r = arms_outer_d / 2, h = core_h + core_center_h, center = true);
translate([1, 0, 0]) cylinder(r = arms_outer_d / 2, h = core_h + core_center_h, center = true);
}
intersection () {
cylinder(r = arms_inner_d / 2, h = core_h + core_center_h + 1, center = true);
translate([1, 0, 0]) cylinder(r = arms_inner_d / 2, h = core_h + core_center_h + 1, center = true);
}
translate([0, arms_outer_d / 2, 0]) cube([arms_outer_d, arms_outer_d, arms_outer_d], center = true);
}
//rounded arm end
translate([(arms_outer_d + arms_inner_d) / 4, 0, top_z_offset]) cylinder(r = 3.5 / 2, h = core_h + core_center_h, center = true, $fn = 40);
//adjusted arm
translate([-((arms_outer_d + arms_inner_d) / 4) + 1, 0, top_z_offset]) cylinder(r = 3.5 / 2, h = core_h + core_center_h, center = true, $fn = 40);
difference () {
rotate([0, 0, -120]) translate([13.75, 0, top_z_offset]) cube([16, 20, core_h + core_center_h], center = true);
//remove piece from adjusted arm
translate([-19, -14, 0]) rotate([0, 0, 10]) cube([4, 4, 30], center = true);
//remove piece from non-adjusted arm
rotate([0, 0, 45]) translate([-19, -14, 0]) rotate([0, 0, -10]) cube([4, 4, 30], center = true);
rotate([0, 0, -120 - 37]) translate([18, 0, top_z_offset]) {
cylinder(r = 6.8 / 2, h = 30, center = true);
translate([-4, -2, 0]) cube([4, 4, 30], center = true);
}
rotate([0, 0, -120 + 37]) translate([18, 0, top_z_offset]) {
cylinder(r = 6.8 / 2, h = 30, center = true);
translate([-4, 2, 0]) cube([4, 4, 30], center = true);
}
}
}
//film void (notches)
rotate([0, 0, -120]) {
translate([20, -5, 0]) {
rotate([0, 0, 45]) {
cube([20, film_void, 30], center = true);
}
}
}
rotate([0, 0, -120]) {
translate([20, 5, 0]) {
rotate([0, 0, -45]) {
cube([20, film_void, 30], center = true);
}
}
}
//flatten piece
rotate([0, 0, -120]) translate([25, 0, 0]) difference () {
cylinder(r = 8 / 2, h = 30, center = true);
translate([-6.9, 0, 0]) cube([8, 8, 30], center = true);
}
cylinder(r = core_void_outer_d / 2, h = core_void_h, center = true);
rotate([0, 0, -120]) translate([20, 0, -1.5]) rotate([0, 0, 45]) cube([20, 20, 3.01], center = true);
cylinder(r = void_d / 2, h = 30, center = true);
translate([0, 0, -7.2]) spiral_insert_void();
}
}
module spiral_insert_void () {
intersection () {
rotate([0, 45, 0]) cube([3, INSERT_D + 2, 3], center = true);
cylinder(r = (INSERT_D + 1) / 2, h = 6, center = true);
}
intersection () {
rotate([0, 45, 90]) cube([3, INSERT_D + 2, 3], center = true);
cylinder(r = (INSERT_D + 1) / 2, h = 6, center = true);
}
}
module gnal_spiral_bottom_insert_s8 () {
$fn = 160;
OD = 10.5 + .3;
void_d = 18 - .6;
H = 17;
D2 = INSERT_D;
translate([0, 0, 0]) difference () {
union () {
cylinder(r = void_d / 2, h = H, center = true);
//skirt
translate([0, 0, -(H - 1) / 2]) cylinder(r = D2 / 2, h = 1.5, center = true);
//notches
translate([0, 0, -((H - 2.5) / 2) - .1]) {
intersection () {
cylinder(r = D2 / 2, h = 6, center = true);
difference () {
rotate([0, 45, 0]) cube([3, D2 + 2, 3], center = true);
translate([0, 0, -1.5]) cube([6, D2 + 3, 3], center = true);
}
}
intersection () {
cylinder(r = D2 / 2, h = 6, center = true);
rotate([0, 0, 90]) difference () {
rotate([0, 45, 0]) cube([3, D2 + 2, 3], center = true);
translate([0, 0, -1.5]) cube([6, D2 + 3, 3], center = true);
}
}
}
}
translate([0, 0, -LEN / 2]) {
if (DEBUG) {
cylinder(r = OD / 2, h = LEN);
} else {
metric_thread (diameter=OD, pitch=PITCH, thread_size = THREAD, length=LEN);
}
}
}
}
module gnal_spiral_bottom_insert_16 () {
$fn = 160;
OD = 10.5 + .3;
void_d = 18 - .6;
H = 17 + 8;
D2 = INSERT_D;
RIDGES = 8;
RIDGE_D = 3;
translate([0, 0, 0]) difference () {
union () {
cylinder(r = void_d / 2, h = H, center = true);
//skirt
translate([0, 0, -(H - 1) / 2]) cylinder(r = D2 / 2, h = 1.5, center = true);
//notches
translate([0, 0, -((H - 2.5) / 2) - .1]) {
intersection () {
cylinder(r = D2 / 2, h = 6, center = true);
difference () {
rotate([0, 45, 0]) cube([3, D2 + 2, 3], center = true);
translate([0, 0, -1.5]) cube([6, D2 + 3, 3], center = true);
}
}
intersection () {
cylinder(r = D2 / 2, h = 6, center = true);
rotate([0, 0, 90]) difference () {
rotate([0, 45, 0]) cube([3, D2 + 2, 3], center = true);
translate([0, 0, -1.5]) cube([6, D2 + 3, 3], center = true);
}
}
}
}
translate([0, 0, -(H / 2) - 2]) {
if (DEBUG) {
cylinder(r = OD / 2, h = LEN + 8);
} else {
metric_thread (diameter=OD, pitch=PITCH, thread_size = THREAD, length=LEN + 8);
}
}
translate([0, 0, 8.5]) {
for (i = [0: RIDGES - 1]) {
rotate([0, 0, i * (360 / RIDGES)]) translate([void_d / 2, 0, 0]) cylinder(r = RIDGE_D / 2, h = 8.1, center = true);
}
}
}
}
/**
* Comment to preserve my sanity when developing: This single-spiral
* insert is the same height as the s8 insert but has a different
* diameter void fo the screw to prevent mismatching of spindle screws
* designed for different purposes.
**/
module gnal_spiral_bottom_insert_single () {
$fn = 160;
void_d = 18 - .6;
H = 17;
D2 = INSERT_D;
translate([0, 0, 0]) difference () {
union () {
cylinder(r = void_d / 2, h = H, center = true);
//skirt
translate([0, 0, -(H - 1) / 2]) cylinder(r = D2 / 2, h = 1.5, center = true);
//notches
translate([0, 0, -((H - 2.5) / 2) - .1]) {
intersection () {
cylinder(r = D2 / 2, h = 6, center = true);
difference () {
rotate([0, 45, 0]) cube([3, D2 + 2, 3], center = true);
translate([0, 0, -1.5]) cube([6, D2 + 3, 3], center = true);
}
}
intersection () {
cylinder(r = D2 / 2, h = 6, center = true);
rotate([0, 0, 90]) difference () {
rotate([0, 45, 0]) cube([3, D2 + 2, 3], center = true);
translate([0, 0, -1.5]) cube([6, D2 + 3, 3], center = true);
}
}
}
}
translate([0, 0, -LEN / 2]) {
if (DEBUG) {
cylinder(r = SINGLE_THREAD_D / 2, h = LEN);
} else {
metric_thread (diameter=SINGLE_THREAD_D, pitch=PITCH, thread_size = THREAD, length = LEN);
}
}
}
}
/**
* Spacers
**/
module spacer_ridges () {
ridges = 16;
for (i = [0 : ridges]) {
rotate([0, 0, i * (360 / ridges)]) translate([13.5, 0, 0]) cylinder(r = 1.25, h = 8, $fn = 60);
}
}
module spacer_ridges_loose () {
ridges = 16;
intersection () {
union () {
for (i = [0 : ridges]) {
rotate([0, 0, i * (360 / ridges)]) translate([13.7, 0, 0]) cylinder(r = 1.25, h = 8, $fn = 60);
}
}
cylinder(r = 13.7, h = 12, center = true);
}
}
module spacer_outer_ridges () {
ridges = 24;
H = 6.5;
difference () {
union () {
for (i = [0 : ridges]) {
rotate([0, 0, i * (360 / ridges)]) translate([14.6, 0, -4.75]) cylinder(r = 1.25, h = 8, $fn = 30);
}
}
translate([0, 0, -4.1]) difference () {
cylinder(r = 33 / 2, h = 4, center = true, $fn = 100);
cylinder(r2 = 33 / 2, r1 = 27.75 / 2, h = 4.1, center = true, $fn = 100);
}
}
}
module gnal_spacer_solid () {
core_d = 29.5;
core_bottom_d = 26.2 + .2;
void_d = 18;
h = 8;
RIDGES = 8;
RIDGE_D = 3;
translate([0, 0, 0]) difference () {
union () {
difference () {
cylinder(r = core_d / 2, h = h, center = true, $fn = 200);
}
translate([0, 0, -.75]) rotate([0, 180, 0]) spacer_outer_ridges();
}
}
}
/**
* This spacer attaches to the top piece when it is used
* for Super8 film.
**/
module gnal_spacer () {
add = 3.25;
core_d = 29.5;
core_bottom_d = 26.2 + .2;
void_d = 22.5;
h = 8 + add;
translate([0, 0, (add / 2) - 1]) difference () {
union () {
difference () {
cylinder(r = core_d / 2, h = h, center = true, $fn = 200);
translate([0, 0, 8]) cylinder(r = core_bottom_d / 2, h = h, center = true, $fn = 200);
cylinder(r = void_d / 2, h = h + 1, center = true, $fn = 200);
}
translate([0, 0, 0]) spacer_ridges_loose();
spacer_outer_ridges();
}
//trim top
translate([0, 0, h - 0.1]) cylinder(r = (core_d + 1) / 2, h = h, center = true, $fn = 200);
//trim bottom
translate([0, 0, -h + 0.9]) cylinder(r = (core_d + 1) / 2, h = h, center = true, $fn = 200);
}
}
module gnal_spacer_16 () {
core_d = 29.5;
core_bottom_d = 26.2 + .2;
void_d = 18.3;
h = 8;
RIDGES = 8;
RIDGE_D = 3;
difference () {
gnal_spacer_solid();
cylinder(r = void_d / 2, h = h + 1, center = true, $fn = 200);
}
translate([0, 0, 0]) {
for (i = [0: RIDGES - 1]) {
rotate([0, 0, i * (360 / RIDGES)]) translate([void_d / 2, 0, 0]) cylinder(r = RIDGE_D / 2, h = 8, center = true);
}
}
}
/**
* Spindles
**/
module gnal_spindle_base ( ) {
D = 8.45 * 2;
H = 20;
union() {
translate([0, 0, -15]) {
cylinder(r = D / 2, h = H, center = true, $fn = FINE);
}
}
}
module gnal_spindle_bottom_base ( HEX = false) {
//for grip
BUMP = 2; //diameter
BUMPS = 6;
TOP_D = 19;
TOP_H = 9.5;
TOP_OFFSET = -24.5;
union() {
gnal_spindle_base();
//hex version
if (HEX) {
translate([0, 0, TOP_OFFSET]) {
cylinder(r = 11.1, h = TOP_H, center = true, $fn = 6);
}
} else {
translate([0, 0, TOP_OFFSET]) {
cylinder(r = TOP_D / 2, h = TOP_H, center = true, $fn = FINE);
}
}
for (i = [0 : BUMPS]) {
rotate([0, 0, (360 / BUMPS) * i]) {
translate([0, 8.9, TOP_OFFSET]) {
cylinder(r = BUMP, h = TOP_H, center = true, $fn = 60);
}
}
}
}
}
module outer_screw (LEN) {
OD = 10;
PITCH = 1.5;
THREAD = 1.6;
difference () {
translate([0, 0, -7.1]) {
if (DEBUG) {
cylinder(r = OD / 2, h = LEN);
} else {
metric_thread (diameter=OD, pitch=PITCH, thread_size = THREAD, length=LEN);
}
}
//bevel top of screw
translate([0, 0, LEN - 8]) difference() {
cylinder(r = 8, h = 3, center = true, $fn = FINE);
cylinder(r1 = 6, r2 = 3, h = 3.01, center = true, $fn = FINE);
}
}
}
module gnal_spindle_bottom (ALT = false, HEX = false) {
OD = 13.6 + .5;
PITCH = 1.5;
THREAD = 1.6;
IN_LEN = 21;
LEN = 17.1;
ALT_LEN = 27.1;
difference () {
gnal_spindle_bottom_base(HEX);
//inner screw negative
translate([0, 0, -30]) union() {
if (DEBUG) {
cylinder(r = OD / 2, h = IN_LEN);
} else {
metric_thread (diameter=OD, pitch=PITCH, thread_size = THREAD, length = IN_LEN);
}
translate([0, 0, 0.2]) {
if (DEBUG) {
cylinder(r = OD / 2, h = IN_LEN);
} else {
metric_thread (diameter=OD, pitch=PITCH, thread_size = THREAD, length = IN_LEN);
}
}
}
}
difference () {
//outer screw
if (ALT) {
outer_screw(ALT_LEN);
} else {
outer_screw(LEN);
}
}
}
module number_one () {
rotate([0, 45, 0]) cube([1, 6, 1], center = true);
translate([0, 6 / 2, 0]) rotate([45, 0, 0]) cube([2, 1, 1], center = true);
translate([0, -6 / 2, 0]) rotate([45, 0, 0]) cube([2, 1, 1], center = true);
}
module gnal_spindle_top () {
D = 50;
THICKNESS = 2.5;
H = 19.5;
ROUND = 8;
HANDLE_D = 13.25;
HANDLE_BASE = 16;
HANDLE_TOP = 13;
HANDLE_H = 54.5;
NOTCHES = 17;
NOTCH = 1.5;
FINE = 200;
difference () {
//cup
translate([0, 0, ROUND - 2]) minkowski () {
cylinder(r = (D / 2) - ROUND, h = (H * 2) - ROUND, center = true, $fn = FINE);
sphere(r = ROUND, $fn = FINE);
}
translate([0, 0, ROUND - 2 + THICKNESS]) minkowski () {
cylinder(r = (D / 2) - THICKNESS - ROUND, h = (H * 2) - ROUND, center = true, $fn = 200);
sphere(r = ROUND, $fn = FINE);
}
//hollow out cup
translate([0, 0, H + ROUND - 4 - 3]) {
cylinder(r = (D / 2) + 1, h = H * 2, center = true);
}
//inner cup bevel
translate([0, 0, (H / 2) - ROUND - 1]) {
cylinder(r1 = (D / 2) - 2.5, r2 = (D / 2) - 2.5 + 1, h = 1, center = true, $fn = FINE);
}
//outer cup bevel
translate([0, 0, (H / 2) - ROUND - 1]) {
difference () {
cylinder(r = (D / 2) + .25, h = 1, center = true, $fn = FINE);
cylinder(r2 = (D / 2) - .8, r1 = (D / 2) - .8 + 1, h = 1, center = true, $fn = FINE);
}
}
//hole in cup
translate([21, 0, -10]) cylinder(r = 3 / 2, h = 40, center = true, $fn = 40);
}
//reference cylinder
//translate([0, 0, -6.6]) color("red") cylinder(r = 50 / 2, h = 19.57, center = true);
//handle
translate([0, 0, -15]) {
difference() {
cylinder(r1 = HANDLE_BASE / 2, r2 = HANDLE_TOP / 2, h = HANDLE_H, $fn = FINE);
//text
translate([3 / 2, 0, 15 + 39.75]) number_one();
translate([-3 / 2, 0, 15 + 39.75]) number_one();
//ring negative
translate([0, 0, 31 + 14.5]) {
difference () {
cylinder(r = HANDLE_D / 2 + 2, h = 20, center = true);
cylinder(r = HANDLE_D / 2 - .5, h = 20 + 1, center = true);
}
}
//handle notches
for(i = [0 : NOTCHES]) {
rotate([0, 0, i * (360 / NOTCHES)]) {
translate([0, HANDLE_D / 2 - .5, 31 + 14.5]) {
rotate([0.75, 0, 0]) rotate([0, 0, 45]) {
Right_Angled_Triangle(a = NOTCH, b = NOTCH, height = 20, centerXYZ=[true, true, true]);
}
}
}
}
//bevel handle at top
translate([0, 0, 54.01]) {
difference () {
cylinder(r = 13 / 2, h = 1, center = true);
cylinder(r1 = 12.5 / 2, r2 = 11.5 / 2, h = 1.01, center = true);
}
}
}
}
//attach handle with pyramid cylinder
translate ([0, 0, -13.7]) {
cylinder(r1 = 16 / 2 + 2, r2 = 16 / 2 - .1, h = 3, center = true, $fn = FINE);
}
//plate under cup
translate([0, 0, -17.75]) {
cylinder(r = 31.5 / 2, h = 1, center = true, $fn = FINE);
}
//screw
translate([0, 0, -37.5]) {
if (DEBUG) {
cylinder(r = 13.6 / 2, h = 21);
} else {
metric_thread (diameter=13.6, pitch = PITCH, thread_size = THREAD, length = 21);
}
}
//cylinder plug
translate([0, 0, -37.5 + (21 / 2) - 1]) {
cylinder(r = 12 / 2, h = 21, center = true, $fn = FINE);
}
}
module gnal_spindle_single () {
D = 50;
THICKNESS = 2.5;
H = 19.5;
ROUND = 8;
HANDLE_D = 13.25;
HANDLE_BASE = 16;
HANDLE_TOP = 13;
HANDLE_H = 54.5;
NOTCHES = 17;
NOTCH = 1.5;
FINE = 200;
SINGLE_INSERT = 11;
difference () {
//cup
translate([0, 0, ROUND - 2]) minkowski () {
cylinder(r = (D / 2) - ROUND, h = (H * 2) - ROUND, center = true, $fn = FINE);
sphere(r = ROUND, $fn = FINE);
}
translate([0, 0, ROUND - 2 + THICKNESS]) minkowski () {
cylinder(r = (D / 2) - THICKNESS - ROUND, h = (H * 2) - ROUND, center = true, $fn = 200);
sphere(r = ROUND, $fn = FINE);
}
//hollow out cup
translate([0, 0, H + ROUND - 4 - 3]) {
cylinder(r = (D / 2) + 1, h = H * 2, center = true);
}
//inner cup bevel
translate([0, 0, (H / 2) - ROUND - 1]) {
cylinder(r1 = (D / 2) - 2.5, r2 = (D / 2) - 2.5 + 1, h = 1, center = true, $fn = FINE);
}
//outer cup bevel
translate([0, 0, (H / 2) - ROUND - 1]) {
difference () {
cylinder(r = (D / 2) + .25, h = 1, center = true, $fn = FINE);
cylinder(r2 = (D / 2) - .8, r1 = (D / 2) - .8 + 1, h = 1, center = true, $fn = FINE);
}
}
//hole in cup
translate([21, 0, -10]) cylinder(r = 3 / 2, h = 40, center = true, $fn = 40);
}
//reference cylinder
//translate([0, 0, -6.6]) color("red") cylinder(r = 50 / 2, h = 19.57, center = true);
//handle
translate([0, 0, -15]) {
difference() {
cylinder(r1 = HANDLE_BASE / 2, r2 = HANDLE_TOP / 2, h = HANDLE_H, $fn = FINE);
//text
translate([0, 0, 15 + 39.75]) number_one();
//ring negative
translate([0, 0, 31 + 14.5]) {
difference () {
cylinder(r = HANDLE_D / 2 + 2, h = 20, center = true);
cylinder(r = HANDLE_D / 2 - .5, h = 20 + 1, center = true);
}
}
//handle notches
for(i = [0 : NOTCHES]) {
rotate([0, 0, i * (360 / NOTCHES)]) {
translate([0, HANDLE_D / 2 - .5, 31 + 14.5]) {
rotate([0.75, 0, 0]) rotate([0, 0, 45]) {
Right_Angled_Triangle(a = NOTCH, b = NOTCH, height = 20, centerXYZ=[true, true, true]);
}
}
}
}
//bevel handle at top
translate([0, 0, 54.01]) {
difference () {
cylinder(r = 13 / 2, h = 1, center = true);
cylinder(r1 = 12.5 / 2, r2 = 11.5 / 2, h = 1.01, center = true);
}
}
}
}
//attach handle with pyramid cylinder
translate ([0, 0, -13.7]) {
cylinder(r1 = 16 / 2 + 2, r2 = 16 / 2 - .1, h = 3, center = true, $fn = FINE);
}
//plate under cup
translate([0, 0, -17.75]) {
cylinder(r = 31.5 / 2, h = 1, center = true, $fn = FINE);
}
//insert for single layer
translate ([0, 0, -24.25]) {
cylinder(r = 22 / 2, h = 14, center = true, $fn = FINE);
}
//screw
translate([0, 0, -37.5 - SINGLE_INSERT]) {
if (DEBUG) {
cylinder(r = SINGLE_THREAD_D / 2, h = 21);
} else {
metric_thread (diameter=SINGLE_THREAD_D, pitch = PITCH, thread_size = THREAD, length = 21);
}
}
//cylinder plug
translate([0, 0, -37.5 - SINGLE_INSERT + (21 / 2) - 1]) {
cylinder(r = 10 / 2, h = 21, center = true, $fn = FINE);
}
}
module gnal_stacking_spindle () {
OD = 10.5 + .3;
IN_LEN = 21;
LEN = 17.1;
ALT_LEN = 27.1;
difference () {
union () {
gnal_spindle_base();
translate([0, 0, -23.75]) gnal_spacer_solid();
}
//inner screw negative
translate([0, 0, -30]) union() {
if (DEBUG) {
cylinder(r = OD / 2, h = IN_LEN);
} else {
metric_thread (diameter=OD, pitch=PITCH, thread_size = THREAD, length = IN_LEN);
}
translate([0, 0, 0.2]) {
if (DEBUG) {
cylinder(r = OD / 2, h = IN_LEN);
} else {
metric_thread (diameter=OD, pitch=PITCH, thread_size = THREAD, length = IN_LEN);
}
}
}
}
difference () {
outer_screw(LEN - 2);
}
}

89
paterson16.scad Normal file
View File

@ -0,0 +1,89 @@
include <./gnal_v3.scad>;
$fn = 250;
REEL_H = 42;
REEL_D = 99;
REEL_OUTER_WALL_H = 4;
REEL_OUTER_WALL_W = 3;
REEL_INNER_D = 25.7 + 6;
REEL_INNER_H = 19.5;
REEL_INNER_WALL_W = 3;
SPOKE_H = 3;
module reel_frame () {
//outer wall
translate([0, 0, REEL_OUTER_WALL_H / 2]) difference () {
cylinder(r = REEL_D / 2, h = REEL_OUTER_WALL_H, center = true);
cylinder(r = (REEL_D / 2) - REEL_OUTER_WALL_W, h = REEL_OUTER_WALL_H + 1, center = true);
}
//inner wall
translate([0, 0, REEL_INNER_H / 2]) difference () {
cylinder(r = REEL_INNER_D / 2, h = REEL_INNER_H, center = true);
cylinder(r = (REEL_INNER_D / 2) - REEL_INNER_WALL_W, h = REEL_INNER_H + 1, center = true);
translate([14, 0, 0]) rotate([0, 0, 45]) cube([10, 0.3, 20], center = true);
translate([0, 0, -REEL_INNER_H / 2]) intersection () {
cylinder(r = 30.5 / 2, h = 6, center = true);
rotate([0, 45, 0]) cube([3, 31, 3], center = true);
}
}
//top notches
translate([0, 0, 19.4]) difference () {
intersection () {
cylinder(r = 30 / 2, h = 6, center = true);
rotate([0, 45, 0]) cube([3, 30, 3], center = true);
}
cylinder(r = (REEL_INNER_D / 2) - REEL_INNER_WALL_W, h = 6 + 1, center = true);
}
//spokes
translate([0, 0, SPOKE_H / 2]) for (i = [0:5]) {
rotate([0, 0, i * (360 / 6)]) {
translate([0, ((REEL_D - REEL_INNER_D) / 2) - 1.5, 0]) {
cube([4.5, (REEL_D - REEL_INNER_D) / 2, SPOKE_H], center = true);
}
}
}
translate([0, 0, 4]) rotate([0, 0, 8]) spiral(rotations = 15, fn = $fn, start_d = 29.5, bottom = -4);
}
module reel_top () {
//outer wall
translate([0, 0, SPOKE_H / 2]) difference () {
cylinder(r = REEL_D / 2, h = SPOKE_H, center = true);
cylinder(r = (REEL_D / 2) - REEL_OUTER_WALL_W, h = REEL_OUTER_WALL_H + 1, center = true);
}
//inner wall
translate([0, 0, SPOKE_H / 2]) difference () {
cylinder(r = REEL_INNER_D / 2, h = SPOKE_H, center = true);
cylinder(r = (REEL_INNER_D / 2) - REEL_INNER_WALL_W, h = REEL_INNER_H + 1, center = true);
translate([0, 0, -SPOKE_H / 2]) intersection () {
cylinder(r = 30.5 / 2, h = 6, center = true);
rotate([0, 45, 0]) cube([3, 31, 3], center = true);
}
}
translate([0, 0, SPOKE_H / 2]) difference () {
cylinder(r = (REEL_INNER_D + REEL_D) / 4, h = SPOKE_H, center = true);
cylinder(r = ((REEL_INNER_D + REEL_D) / 4) - REEL_INNER_WALL_W, h = REEL_INNER_H + 1, center = true);
}
//spokes
translate([0, 0, SPOKE_H / 2]) for (i = [0:5]) {
rotate([0, 0, i * (360 / 6)]) {
translate([0, ((REEL_D - REEL_INNER_D) / 2) - 1.5, 0]) {
cube([4.5, (REEL_D - REEL_INNER_D) / 2, SPOKE_H], center = true);
}
}
}
}
//reel_frame();
rotate([180, 0, 0]) reel_top();
//translate([0, 0, -19.5]) reel_frame();

195
path_extrude.scad Normal file
View File

@ -0,0 +1,195 @@
// path_extrude.scad -- Extrude a path in 3D space
// usage: add "use <path_extrude.scad>;" to the top of your OpenSCAD source code
// Copyright (C) 2014-2019 David Eccles (gringer) <bioinformatics@gringene.org>
// 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 <https://www.gnu.org/licenses/>.
// Determine the projection of a point on a plane centered at c1 with normal n1
function project(p, c, n) =
p - (n * (p - c)) * n / (n * n);
// determine angle between two points with a given normal orientation
// see https://stackoverflow.com/questions/14066933/
// direct-way-of-computing-clockwise-angle-between-2-vectors
// dot = p1 * p2;
// det = (p1[0]*p2[1]*n1[2] + p2[0]*n1[1]*p1[2] + n1[0]*p1[1]*p2[2]) -
// (n1[0]*p2[1]*p1[2] + p1[0]*n1[1]*p2[2] + p2[0]*p1[1]*n1[2]);
// atan2(det, dot);
// determine angle between two planar points and a centre
// with a given normal orientation
function getPlanarAngle(p1, p2, c1, n1) =
let(p1 = p1-c1, n1=n1 / norm(n1), p2=p2-c1)
atan2((p1[0]*p2[1]*n1[2] + p2[0]*n1[1]*p1[2] + n1[0]*p1[1]*p2[2]) -
(n1[0]*p2[1]*p1[2] + p1[0]*n1[1]*p2[2] + p2[0]*p1[1]*n1[2]), p1 * p2);
function c3D(tPoints) =
(len(tPoints[0]) == undef) ? // single point
c3D([tPoints])[0] :
(len(tPoints[0]) < 3) ? // collection of 2D points
tPoints * [[1,0,0],[0,1,0]] :
tPoints; // 3D points
// translate a point (or points)
function myTranslate(ofs, points, acc = []) =
(len(points[0]) == undef) ?
myTranslate(ofs, [points])[0] :
[ for(i = [0:(len(points) - 1)])
[ for(d = [0:(len(points[0])-1)]) (ofs[d] + points[i][d])]];
// rotate a point (or points)
function myRotate(rotVec, points) =
let(rotX = [[1, 0, 0],
[0, cos(rotVec[0]), -sin(rotVec[0])],
[0, sin(rotVec[0]), cos(rotVec[0])]],
rotY = [[ cos(rotVec[1]), 0,-sin(rotVec[1])],
[ 0, 1, 0],
[ sin(rotVec[1]), 0, cos(rotVec[1])]],
rotZ = [[ cos(rotVec[2]), sin(rotVec[2]), 0],
[ sin(rotVec[2]), -cos(rotVec[2]), 0],
[0, 0, 1]])
(len(points[0]) == undef) ?
myRotate(rotVec, [points])[0] :
c3D(points) * rotX * rotY * rotZ;
// Determine spherical rotation for cartesian coordinates
function rToS(pt) =
[-acos((pt[2]) / norm(pt)),
0,
-atan2(pt[0],pt[1])];
function calcPreRot(p1, p2, p3) =
let(n1=p2-p1, // normal between the two points (i.e. the plane that the polygon sits on)
n2=p3-p2,
rt1=rToS(n1),
rt2=rToS(n2),
pj1=(p2 + myRotate(rt2, [[1e42,0,0]])[0]),
pj2=project(p=(p1 + myRotate(rt1, [[1e42,0,0]])[0]), c=p2, n=n2))
getPlanarAngle(p1=pj1, p2=pj2, c1=p2, n1=n2);
function cumSum(x, res=[]) =
(len(x) == len(res)) ? concat([0], res) :
(len(res) == 0) ? cumSum(x=x, res=[x[0]]) :
cumSum(x=x, res=concat(res, [x[len(res)] + res[len(res)-1]]));
// Create extrusion side panels for one polygon segment as triangles.
// Note: panels are not necessarily be planar due to path twists
function makeSides(shs, pts, ofs=0) =
concat(
[for(i=[0:(shs-1)]) [i+ofs, ((i+1) % shs + ofs + shs) % (shs * pts),
(i+1) % shs + ofs]],
[for(i=[0:(shs-1)]) [((i+1) % shs + ofs + shs) % (shs * pts),
i+ofs, (i + ofs + shs) % (shs * pts)]]);
// Concatenate the contents of the outermost list
function flatten(A, acc = [], aDone = 0) =
(aDone >= len(A)) ? acc :
flatten(A, acc=concat(acc, A[aDone]), aDone = aDone + 1);
// Linearly interpolate between two shapes
function makeTween(shape1, shape2, t) =
(t == 0) ? shape1 :
(t == 1) ? shape2 :
[for (i=[0:(len(shape1)-1)])
(shape1[i]*(1-t) + shape2[i % len(shape2)]*(t))];
// Extrude a 2D shape through a 3D path
// Note: merge has two effects:
// 1) Removes end caps
// 2) Adjusts the rotation of each path point
// so that the end and start match up
module path_extrude(exPath, exShape, exShape2=[],
exRots = [0], exScale = [1], merge=false, preRotate=true){
exShapeTween = (len(exShape2) == 0) ?
exShape : exShape2;
shs = len(exShape); // shs: shape size
pts = len(exPath); // pts: path size
exPathX = (merge) ? concat(exPath, [exPath[0], exPath[1]]) :
concat(exPath,
[exPath[pts-1] + (exPath[pts-1] - exPath[pts-2]),
exPath[pts-1] + 2*(exPath[pts-1] - exPath[pts-2])]);
exScaleX = (len(exScale) == len(exPath)) ? exScale :
[for (i = [0:(pts-1)]) exScale[i % len(exScale)]];
preRots = [for(i = [0:(pts-1)])
preRotate ?
calcPreRot(p1=exPathX[i], // "current" point on the path
p2=exPathX[(i+1)], // "next" point on the path
p3=exPathX[(i+2)]) :
0 ];
cumPreRots = cumSum(preRots);
seDiff = cumPreRots[len(cumPreRots)-1]; // rotation difference (start - end)
// rotation adjustment to get start to look like end
seAdj = -seDiff / (len(cumPreRots));
adjPreRots = (!merge) ? cumPreRots :
[for(i = [0:(pts-1)]) (cumPreRots[i] + seAdj * i)];
adjExRots = (len(exRots) == 1) ?
[for(i = [0:(len(adjPreRots)-1)]) (adjPreRots[i] + exRots[0])] :
[for(i = [0:(len(adjPreRots)-1)]) (adjPreRots[i] + exRots[i % len(exRots)])];
phPoints = flatten([
for(i = [0:(pts-1)])
let(p1=exPathX[i],
p2=exPathX[(i+1)],
n1=p2-p1, // normal between the two points
rt1=rToS(n1))
myTranslate(p1, myRotate(rt1, myRotate([0,0,-adjExRots[i]],
c3D(makeTween(exShape, exShapeTween, i / (pts-1)) *
exScaleX[i]))))
]);
if(merge){ // just the surface, no end caps
polyhedron(points=phPoints,
faces=flatten([
for(i = [0:(pts-1)])
makeSides(shs, pts, ofs=shs*i)
])
);
} else {
polyhedron(points=phPoints,
faces=concat(
flatten([
for(i = [0:(pts-2)])
makeSides(shs, pts, ofs=shs*i)
]),
concat( // add in start / end covers
[[for(i= [0:(shs-1)]) i]],
[[for(i= [(len(phPoints)-1):-1:(len(phPoints)-shs)]) i]]
)
));
}
}
myPathTrefoil = [ for(t = [0:(360 / 101):359]) [ // trefoil knot
5*(.41*cos(t) - .18*sin(t) - .83*cos(2*t) - .83*sin(2*t) -
.11*cos(3*t) + .27*sin(3*t)),
5*(.36*cos(t) + .27*sin(t) - 1.13*cos(2*t) + .30*sin(2*t) +
.11*cos(3*t) - .27*sin(3*t)),
5*(.45*sin(t) - .30*cos(2*t) +1.13*sin(2*t) -
.11*cos(3*t) + .27*sin(3*t))] ];
myPointsOctagon =
let(ofs1=15)
[ for(t = [0:(360/8):359])
((t==90)?1:2) * [cos(t+ofs1),sin(t+ofs1)]];
myPointsChunkOctagon =
let(ofs1=15)
[ for(t = [0:(360/8):359])
((t==90)?0.4:1.9) *
[cos((t * 135/360 + 45)+ofs1+45)+0.5,sin((t * 135/360 + 45)+ofs1+45)]];
//myPoints = [ for(t = [0:(360/8):359]) 2 * [cos(t+45),sin(t+45)]];
pts=[2,0,0.5];
/*translate([0,0,0]) {
path_extrude(exRots = [$t*360], exShape=myPointsOctagon,
exPath=myPathTrefoil, merge=true);
}*/

372
threads.scad Normal file
View File

@ -0,0 +1,372 @@
/*
* ISO-standard metric threads, following this specification:
* http://en.wikipedia.org/wiki/ISO_metric_screw_thread
*
* Copyright 2017 Dan Kirshner - dan_kirshner@yahoo.com
* 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.
*
* See <http://www.gnu.org/licenses/>.
*
* Version 2.3. 2017-08-31 Default for leadin: 0 (best for internal threads).
* Version 2.2. 2017-01-01 Correction for angle; leadfac option. (Thanks to
* Andrew Allen <a2intl@gmail.com>.)
* Version 2.1. 2016-12-04 Chamfer bottom end (low-z); leadin option.
* Version 2.0. 2016-11-05 Backwards compatibility (earlier OpenSCAD) fixes.
* Version 1.9. 2016-07-03 Option: tapered.
* Version 1.8. 2016-01-08 Option: (non-standard) angle.
* Version 1.7. 2015-11-28 Larger x-increment - for small-diameters.
* Version 1.6. 2015-09-01 Options: square threads, rectangular threads.
* Version 1.5. 2015-06-12 Options: thread_size, groove.
* Version 1.4. 2014-10-17 Use "faces" instead of "triangles" for polyhedron
* Version 1.3. 2013-12-01 Correct loop over turns -- don't have early cut-off
* Version 1.2. 2012-09-09 Use discrete polyhedra rather than linear_extrude ()
* Version 1.1. 2012-09-07 Corrected to right-hand threads!
*/
// Examples.
//
// Standard M8 x 1.
// metric_thread (diameter=8, pitch=1, length=4);
// Square thread.
// metric_thread (diameter=8, pitch=1, length=4, square=true);
// Non-standard: long pitch, same thread size.
//metric_thread (diameter=8, pitch=4, length=4, thread_size=1, groove=true);
// Non-standard: 20 mm diameter, long pitch, square "trough" width 3 mm,
// depth 1 mm.
//metric_thread (diameter=20, pitch=8, length=16, square=true, thread_size=6,
// groove=true, rectangle=0.333);
// English: 1/4 x 20.
//english_thread (diameter=1/4, threads_per_inch=20, length=1);
// Tapered. Example -- pipe size 3/4" -- per:
// http://www.engineeringtoolbox.com/npt-national-pipe-taper-threads-d_750.html
// english_thread (diameter=1.05, threads_per_inch=14, length=3/4, taper=1/16);
// Thread for mounting on Rohloff hub.
//difference () {
// cylinder (r=20, h=10, $fn=100);
//
// metric_thread (diameter=34, pitch=1, length=10, internal=true, n_starts=6);
//}
// ----------------------------------------------------------------------------
function segments (diameter) = min (50, ceil (diameter*6));
// ----------------------------------------------------------------------------
// diameter - outside diameter of threads in mm. Default: 8.
// pitch - thread axial "travel" per turn in mm. Default: 1.
// length - overall axial length of thread in mm. Default: 1.
// internal - true = clearances for internal thread (e.g., a nut).
// false = clearances for external thread (e.g., a bolt).
// (Internal threads should be "cut out" from a solid using
// difference ()).
// n_starts - Number of thread starts (e.g., DNA, a "double helix," has
// n_starts=2). See wikipedia Screw_thread.
// thread_size - (non-standard) axial width of a single thread "V" - independent
// of pitch. Default: same as pitch.
// groove - (non-standard) subtract inverted "V" from cylinder (rather than
// add protruding "V" to cylinder).
// square - Square threads (per
// https://en.wikipedia.org/wiki/Square_thread_form).
// rectangle - (non-standard) "Rectangular" thread - ratio depth/(axial) width
// Default: 1 (square).
// angle - (non-standard) angle (deg) of thread side from perpendicular to
// axis (default = standard = 30 degrees).
// taper - diameter change per length (National Pipe Thread/ANSI B1.20.1
// is 1" diameter per 16" length). Taper decreases from 'diameter'
// as z increases.
// leadin - 0 (default): no chamfer; 1: chamfer (45 degree) at max-z end;
// 2: chamfer at both ends, 3: chamfer at z=0 end.
// leadfac - scale of leadin chamfer (default: 1.0 = 1/2 thread).
module metric_thread (diameter=8, pitch=1, length=1, internal=false, n_starts=1,
thread_size=-1, groove=false, square=false, rectangle=0,
angle=30, taper=0, leadin=0, leadfac=1.0)
{
// thread_size: size of thread "V" different than travel per turn (pitch).
// Default: same as pitch.
local_thread_size = thread_size == -1 ? pitch : thread_size;
local_rectangle = rectangle ? rectangle : 1;
n_segments = segments (diameter);
h = (square || rectangle) ? local_thread_size*local_rectangle/2 : local_thread_size / (2 * tan(angle));
h_fac1 = (square || rectangle) ? 0.90 : 0.625;
// External thread includes additional relief.
h_fac2 = (square || rectangle) ? 0.95 : 5.3/8;
tapered_diameter = diameter - length*taper;
difference () {
union () {
if (! groove) {
metric_thread_turns (diameter, pitch, length, internal, n_starts,
local_thread_size, groove, square, rectangle, angle,
taper);
}
difference () {
// Solid center, including Dmin truncation.
if (groove) {
cylinder (r1=diameter/2, r2=tapered_diameter/2,
h=length, $fn=n_segments);
} else if (internal) {
cylinder (r1=diameter/2 - h*h_fac1, r2=tapered_diameter/2 - h*h_fac1,
h=length, $fn=n_segments);
} else {
// External thread.
cylinder (r1=diameter/2 - h*h_fac2, r2=tapered_diameter/2 - h*h_fac2,
h=length, $fn=n_segments);
}
if (groove) {
metric_thread_turns (diameter, pitch, length, internal, n_starts,
local_thread_size, groove, square, rectangle,
angle, taper);
}
}
}
// chamfer z=0 end if leadin is 2 or 3
if (leadin == 2 || leadin == 3) {
difference () {
cylinder (r=diameter/2 + 1, h=h*h_fac1*leadfac, $fn=n_segments);
cylinder (r2=diameter/2, r1=diameter/2 - h*h_fac1*leadfac, h=h*h_fac1*leadfac,
$fn=n_segments);
}
}
// chamfer z-max end if leadin is 1 or 2.
if (leadin == 1 || leadin == 2) {
translate ([0, 0, length + 0.05 - h*h_fac1*leadfac]) {
difference () {
cylinder (r=diameter/2 + 1, h=h*h_fac1*leadfac, $fn=n_segments);
cylinder (r1=tapered_diameter/2, r2=tapered_diameter/2 - h*h_fac1*leadfac, h=h*h_fac1*leadfac,
$fn=n_segments);
}
}
}
}
}
// ----------------------------------------------------------------------------
// Input units in inches.
// Note: units of measure in drawing are mm!
module english_thread (diameter=0.25, threads_per_inch=20, length=1,
internal=false, n_starts=1, thread_size=-1, groove=false,
square=false, rectangle=0, angle=30, taper=0, leadin=0,
leadfac=1.0)
{
// Convert to mm.
mm_diameter = diameter*25.4;
mm_pitch = (1.0/threads_per_inch)*25.4;
mm_length = length*25.4;
echo (str ("mm_diameter: ", mm_diameter));
echo (str ("mm_pitch: ", mm_pitch));
echo (str ("mm_length: ", mm_length));
metric_thread (mm_diameter, mm_pitch, mm_length, internal, n_starts,
thread_size, groove, square, rectangle, angle, taper, leadin,
leadfac);
}
// ----------------------------------------------------------------------------
module metric_thread_turns (diameter, pitch, length, internal, n_starts,
thread_size, groove, square, rectangle, angle,
taper)
{
// Number of turns needed.
n_turns = floor (length/pitch);
intersection () {
// Start one below z = 0. Gives an extra turn at each end.
for (i=[-1*n_starts : n_turns+1]) {
translate ([0, 0, i*pitch]) {
metric_thread_turn (diameter, pitch, internal, n_starts,
thread_size, groove, square, rectangle, angle,
taper, i*pitch);
}
}
// Cut to length.
translate ([0, 0, length/2]) {
cube ([diameter*3, diameter*3, length], center=true);
}
}
}
// ----------------------------------------------------------------------------
module metric_thread_turn (diameter, pitch, internal, n_starts, thread_size,
groove, square, rectangle, angle, taper, z)
{
n_segments = segments (diameter);
fraction_circle = 1.0/n_segments;
for (i=[0 : n_segments-1]) {
rotate ([0, 0, i*360*fraction_circle]) {
translate ([0, 0, i*n_starts*pitch*fraction_circle]) {
//current_diameter = diameter - taper*(z + i*n_starts*pitch*fraction_circle);
thread_polyhedron ((diameter - taper*(z + i*n_starts*pitch*fraction_circle))/2,
pitch, internal, n_starts, thread_size, groove,
square, rectangle, angle);
}
}
}
}
// ----------------------------------------------------------------------------
module thread_polyhedron (radius, pitch, internal, n_starts, thread_size,
groove, square, rectangle, angle)
{
n_segments = segments (radius*2);
fraction_circle = 1.0/n_segments;
local_rectangle = rectangle ? rectangle : 1;
h = (square || rectangle) ? thread_size*local_rectangle/2 : thread_size / (2 * tan(angle));
outer_r = radius + (internal ? h/20 : 0); // Adds internal relief.
//echo (str ("outer_r: ", outer_r));
// A little extra on square thread -- make sure overlaps cylinder.
h_fac1 = (square || rectangle) ? 1.1 : 0.875;
inner_r = radius - h*h_fac1; // Does NOT do Dmin_truncation - do later with
// cylinder.
translate_y = groove ? outer_r + inner_r : 0;
reflect_x = groove ? 1 : 0;
// Make these just slightly bigger (keep in proportion) so polyhedra will
// overlap.
x_incr_outer = (! groove ? outer_r : inner_r) * fraction_circle * 2 * PI * 1.02;
x_incr_inner = (! groove ? inner_r : outer_r) * fraction_circle * 2 * PI * 1.02;
z_incr = n_starts * pitch * fraction_circle * 1.005;
/*
(angles x0 and x3 inner are actually 60 deg)
/\ (x2_inner, z2_inner) [2]
/ \
(x3_inner, z3_inner) / \
[3] \ \
|\ \ (x2_outer, z2_outer) [6]
| \ /
| \ /|
z |[7]\/ / (x1_outer, z1_outer) [5]
| | | /
| x | |/
| / | / (x0_outer, z0_outer) [4]
| / | / (behind: (x1_inner, z1_inner) [1]
|/ | /
y________| |/
(r) / (x0_inner, z0_inner) [0]
*/
x1_outer = outer_r * fraction_circle * 2 * PI;
z0_outer = (outer_r - inner_r) * tan(angle);
//echo (str ("z0_outer: ", z0_outer));
//polygon ([[inner_r, 0], [outer_r, z0_outer],
// [outer_r, 0.5*pitch], [inner_r, 0.5*pitch]]);
z1_outer = z0_outer + z_incr;
// Give internal square threads some clearance in the z direction, too.
bottom = internal ? 0.235 : 0.25;
top = internal ? 0.765 : 0.75;
translate ([0, translate_y, 0]) {
mirror ([reflect_x, 0, 0]) {
if (square || rectangle) {
// Rule for face ordering: look at polyhedron from outside: points must
// be in clockwise order.
polyhedron (
points = [
[-x_incr_inner/2, -inner_r, bottom*thread_size], // [0]
[x_incr_inner/2, -inner_r, bottom*thread_size + z_incr], // [1]
[x_incr_inner/2, -inner_r, top*thread_size + z_incr], // [2]
[-x_incr_inner/2, -inner_r, top*thread_size], // [3]
[-x_incr_outer/2, -outer_r, bottom*thread_size], // [4]
[x_incr_outer/2, -outer_r, bottom*thread_size + z_incr], // [5]
[x_incr_outer/2, -outer_r, top*thread_size + z_incr], // [6]
[-x_incr_outer/2, -outer_r, top*thread_size] // [7]
],
faces = [
[0, 3, 7, 4], // This-side trapezoid
[1, 5, 6, 2], // Back-side trapezoid
[0, 1, 2, 3], // Inner rectangle
[4, 7, 6, 5], // Outer rectangle
// These are not planar, so do with separate triangles.
[7, 2, 6], // Upper rectangle, bottom
[7, 3, 2], // Upper rectangle, top
[0, 5, 1], // Lower rectangle, bottom
[0, 4, 5] // Lower rectangle, top
]
);
} else {
// Rule for face ordering: look at polyhedron from outside: points must
// be in clockwise order.
polyhedron (
points = [
[-x_incr_inner/2, -inner_r, 0], // [0]
[x_incr_inner/2, -inner_r, z_incr], // [1]
[x_incr_inner/2, -inner_r, thread_size + z_incr], // [2]
[-x_incr_inner/2, -inner_r, thread_size], // [3]
[-x_incr_outer/2, -outer_r, z0_outer], // [4]
[x_incr_outer/2, -outer_r, z0_outer + z_incr], // [5]
[x_incr_outer/2, -outer_r, thread_size - z0_outer + z_incr], // [6]
[-x_incr_outer/2, -outer_r, thread_size - z0_outer] // [7]
],
faces = [
[0, 3, 7, 4], // This-side trapezoid
[1, 5, 6, 2], // Back-side trapezoid
[0, 1, 2, 3], // Inner rectangle
[4, 7, 6, 5], // Outer rectangle
// These are not planar, so do with separate triangles.
[7, 2, 6], // Upper rectangle, bottom
[7, 3, 2], // Upper rectangle, top
[0, 5, 1], // Lower rectangle, bottom
[0, 4, 5] // Lower rectangle, top
]
);
}
}
}
}