Skip to content

Instantly share code, notes, and snippets.

@jbrown123
Created February 9, 2020 22:12
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jbrown123/1bf9c3d9404a4797b33c3d3d238f746e to your computer and use it in GitHub Desktop.
Save jbrown123/1bf9c3d9404a4797b33c3d3d238f746e to your computer and use it in GitHub Desktop.
A library of openscad functions that I frequently use in my designs
/*
* MyLib.scad
*
* A bunch of openscad functions that I might need from time to time in various projects
*
* Usage: use <mylib.scad>;
*
* Version: 20200209
*
* Copyright (c) 2011-2020 by James H. Brown
* License: Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported (CC BY-NC-SA 3.0)
* License text: https://creativecommons.org/licenses/by-nc-sa/3.0/
*
* You are explicitly allowed to use this library to generate objects that you can then sell commercially.
* You may NOT sell the library itself nor any of it's individual components.
*
* Some components below are included from other authors. Their license information is included as well
* and applies to their code only. Any modifications I made follow Creative Commons licensing rules.
*
* Some of these functions require openscad 2019.05 or later.
*/
// openscad cheat sheet: http://www.openscad.org/cheatsheet/
// translate from inches to mm
function in2mm(in)=25.4*in;
// translate diameter to radius & radius to diameter
function d2r(d)=d/2;
function r2d(r)=r*2;
// torus - a doughnut or o-ring shape
// specify either od & id or cs + either od or id
// cs = cross section (diameter of the torus itself)
// center, if true, will center the torus in the Z plane
// angle defaults to 360 & specifies the number of degrees to sweep,
// starting at the positive X axis. The direction of the sweep follows
// the Right Hand Rule, hence a negative angle will sweep clockwise.
// spin defaults to 0 & specifies the degrees of rotation of a torus around Z
module torus(od=-1, id=-1, cs=0, center=false, angle=360, spin=0)
{
if (od == -1 && id == -1)
echo("<font color='red'>both od & id are not set in torus()</font>");
else
{
od = (od == -1) ? id + (cs * 2) : od;
id = (id == -1) ? od - (cs * 2) : id;
or = od/2;
ir = id/2;
rotate([0,0,spin]) // spin around Z as requested
translate([0, 0, center==true ? 0 : (or-ir)/2])
// This rotation is required because apparelty rotate_extrude doesn't
// start at the same place as the sides of a sphere with an odd $fn value
// Without this if you put a torus on top of a cylinder with an odd $fn,
// they will be misaligned by 1/2 a rotation
rotate([0,0,$fn % 2 == 1 ? 360/$fn/2 : 0])
rotate_extrude(convexity=10, angle=angle)
translate([ir+(or-ir)/2, 0, 0])
circle(r=(or-ir)/2);
}
}
// ring - a hollow cylinder
// either specify the od or odt & odb
// same with id (or idt, idb)
// xxT and xxB stand for 'top' and 'bottom'
// rounded, if true, makes the ends of the ring rounded over
// rounded = 1 rounds only the top, rounded = 2 rounds only the bottom, rounded = true / 3 rounds both
// the diam of the rounding is based on the od-id of each end
// angle defaults to 360 & specifies the number of degrees to sweep,
// starting at the positive X axis. The direction of the sweep follows
// the Right Hand Rule, hence a negative angle will sweep clockwise.
// spin defaults to 0 & specifies the degrees of rotation of a torus around Z
module ring(od=0, id=0, h=0, odt=-1, odb=-1, idt=-1, idb=-1, rounded=false, center=false, angle=360, spin=0)
{
odt = odt == -1 ? od : odt;
odb = odb == -1 ? od : odb;
idt = idt == -1 ? id : idt;
idb = idb == -1 ? id : idb;
rounded = rounded == false ? 0 : rounded == true ? 3 : rounded;
intersection()
{
// had to do the pie call and interesection here to fix issues with very low $fn values
if (angle != 360)
pie(radius=max(odb, odt)/2, angle=angle, height=h, spin=spin, center = center, $fn=max($fn, 30));
translate([0,0, center==true ? -h/2 : 0])
{
offsetT = (rounded == 1 || rounded == 3) ? (odt-idt)/4 : 0;
offsetB = (rounded == 2 || rounded == 3) ? (odb-idb)/4 : 0;
rh = h - offsetT - offsetB;
translate([0,0,offsetB])
difference()
{
cylinder(d1=odb, d2=odt, h=rh);
cylinder(d1=idb, d2=idt, h=rh);
// clean up the preview render
// requires version 2019.05
if ($preview)
{
translate([0,0,-0.1])
cylinder(d=idb, h=.2);
translate([0,0,rh-.1])
cylinder(d=idt, h=.2);
}
}
// bottom torus
if (rounded == 2 || rounded == 3)
torus(od=odb, id=idb);
// top torus
if (rounded == 1 || rounded == 3)
translate([0,0,h-(odt-idt)/2])
torus(od=odt, id=idt);
}
}
}
// make a clone of all children using the translate, rotate, and mirror transformations
// the parameters t, r, & m are the translate vector, rotate vector, and mirror vector
// the o parameter is the order to execute the three different transforms
// note that the order is the execution order which is the reverse of the order they appear in the code
// so 'trm' means first translate, then rotate, them mirror; in the code that would be mirror() rotate() translate()
// useful defaults are provided so that you can use any one or more of the transforms to create clones of an object or objects
//
// example:
// clone(m=[1,0,0]) cube([3,2,1]); // create 2 cubes mirrored across the X axis (a cube 6x2,1)
// clone(m=[1,0,0], t=[1,1,0]) cube([3,2,1]); // 2 cubes mirrored across X, the left one moved -1mm X and 1mm Y (default order is "trm" so translate first, then mirror)
// clone(m=[1,0,0], t=[1,1,0], o="mtr") cube([3,2,1]); // 2 cubes mirrored across X, the left one moved 1mmX and 1mm Y (mirror first, then translate)
// clone(t=[0,2,1]) cube([3,2,1]); // creates 2 cubes with the second shifted in Y & Z (stair steps?)
module clone(t=[0,0,0], r=[0,0,0], m=[0,0,0], o="trm")
{
children();
if (o == "trm")
mirror(m)
rotate(r)
translate(t)
children();
else if (o == "tmr")
rotate(r)
mirror(m)
translate(t)
children();
else if (o == "rtm")
mirror(m)
translate(t)
rotate(r)
children();
else if (o == "mtr")
rotate(r)
translate(t)
mirror(m)
children();
else if (o == "rmt")
translate(t)
mirror(m)
rotate(r)
children();
else if (o == "mrt")
translate(t)
rotate(r)
mirror(m)
children();
else
echo(str("<font color='red'><b>clone can't understand ", o, "</b></font>"));
}
// create a grid of all children
// cols = how many columns (x)
// rows = how many rows (y)
// xstep = the amount to offset each column from the previous element
// ystep = the amount to offset each row from the previous element
// num = the number of items to put in the grid; default is cols * rows
//
// specify either rows & cols, or num plus one of rows & cols; if you specify all 3 num acts as a limit or expansion
//
// examples:
// grid(cols=4, rows=3, xstep=10, ystep=12) cube([8,10,5]);
// grid(cols=4, rows=3, xstep=10, ystep=12, num=10) cube([8,10,5]);
// grid(cols=4, num=12, xstep=10, ystep=12) cube([8,10,5]);
module grid(cols=undef, rows=undef, xstep=1, ystep=1, num=undef)
{
assert((cols != undef && rows != undef) || (num != undef && (cols != undef || rows != undef)),
"At least 2 of cols, rows, and num must be specified");
assert((xstep > 0 && ystep > 0), "both xstep & ystep must be > 0");
num = num == undef ? cols * rows : num;
cols = cols == undef ? ceil(num / rows) : cols;
rows = rows == undef ? ceil(num / cols) : rows; // not really needed
for (i=[0:1:num-1])
{
x = i % cols;
y = floor(i / cols);
translate([x*xstep,y*ystep,0])
children();
}
}
// calculate the height of a ring made by cutting
// a cylinder out of a sphere
// R is outer radius (sphere), r is inner (cylinder cut) radius
function ringHeight(R,r) = 2*sqrt((R*R) - (r*r));
// rounded cube - cube with rounded corners and sides using hull() & spheres
// size= [x, y, z]
// center = true / false
// radius = radius of rounded corners (0 = standard cube)
// t = [x,y,z] translate parameters
// r = [x,y,z] rotate parameters
// results will be rotated, then translated
module cubeR( size, center=false, radius=1, t=[0,0,0], r=[0,0,0])
{
x = (len(size)==undef) ? size : size[0];
y = (len(size)==undef) ? size : size[1];
z = (len(size)==undef) ? size : size[2];
if (radius > x/2 || radius > y/2 || radius > z/2)
echo("<font color='red'>radius too large in cubeR</font>");
if (radius == 0)
{
// just a regular cube; don't know why you are calling us
translate(t)
rotate(r)
cube([x,y,z], center=center);
}
else
{
translate(t)
rotate(r)
translate(center ? [-x/2, -y/2, -z/2] : [0 , 0, 0])
hull()
{
translate( [radius, radius, radius] )
sphere( radius );
translate( [radius, y-radius, radius] )
sphere( radius );
translate( [radius, radius, z-radius] )
sphere( radius );
translate( [radius, y-radius, z-radius] )
sphere( radius );
translate( [x-radius, radius, radius] )
sphere( radius );
translate( [x-radius, y-radius, radius] )
sphere( radius );
translate( [x-radius, radius, z-radius] )
sphere( radius );
translate( [x-radius, y-radius, z-radius] )
sphere( radius );
}
}
}
/*
* I took the following code and modified it to center
* the generated grid in the space.
* I removed the module call to create a single honeycomb
* since it was just a single primitive call (circle).
* I converted it from 2D objects (circles) to 3D objects (cylinders).
* I changed the interface so the first parameter is the vector
* to create the honeycomb inside ([x, y, z])
* I changed the previous 'dia' parameter to be 'width' instead
* meaning the distance from flat side to flat side. I think this
* is the way most people think of dimensions of a honeycomb
* I added an option to return basically the holes (hexes) insted of the
* honeycomb pattern. This is useful with difference()
* I added the ability to show the bounding box (ghosted using the % operator)
*
* Example:
* honeycomb(box=[15, 20, 4], width=4, wall=1, showBoundingBox=true);
*/
/**
* Honeycomb library
* License: Creative Commons - Attribution
* Copyright: Gael Lafond 2017
* URL: https://www.thingiverse.com/thing:2484395
*
* Inspired from:
* https://www.thingiverse.com/thing:1763704
*/
module honeycomb(box, width, wall, inverted = false, showBoundingBox = false)
{
// Diagram
// ______ ___ ___
// / /\ | |
// / dia / \ | smallDia |
// / / \ _|_ | width
// \ / ____ |
// \ / / |
// ___ \______/ / _|_
// wall | /
// _|_ ______ \
// / \ \
// / \ \
// |---|
// projWall
//
dia = width * (2/sqrt(3));
x = box[0];
y = box[1];
z = box[2];
smallDia = dia * cos(30);
projWall = wall * cos(30);
yStep = smallDia + wall;
xStep = dia*3/2 + projWall*2;
xStart = xStep - ((x % xStep) / 2);
yStart = yStep - ((y % yStep) / 2);
if (inverted)
{
intersection()
{
cube(box); // constrain it to exactly what they asked for
// only return the hexs themselves, not the 'borders'
// Note, extra loops to ensure the whole surface is covered
for (yOffset = [-yStart:yStep:y+yStep], xOffset = [-xStart:xStep:x+xStep])
{
translate([xOffset, yOffset, -.1])
cylinder(d=dia, h=z+.2, $fn=6);
translate([xOffset + dia*3/4 + projWall, yOffset + (smallDia+wall)/2, -.1])
cylinder(d=dia, h=z+.2, $fn=6);
}
}
}
else
{
difference()
{
cube(box);
// Note, extra loops to ensure the whole surface is covered
for (yOffset = [-yStart:yStep:y+yStep], xOffset = [-xStart:xStep:x+xStep])
{
translate([xOffset, yOffset, -.1])
cylinder(d=dia, h=z+.2, $fn=6);
translate([xOffset + dia*3/4 + projWall, yOffset + (smallDia+wall)/2, -.1])
cylinder(d=dia, h=z+.2, $fn=6);
}
}
}
if (showBoundingBox)
%difference()
{
cube(box);
translate([wall/2,wall/2,-.1])
cube([box[0]-wall, box[1]-wall, box[2]+.2]);
}
}
/**
* pie.scad
*
* Use this module to generate a pie- or pizza- slice shape, which is particularly useful
* in combination with `difference()` and `intersection()` to render shapes that extend a
* certain number of degrees around or within a circle.
*
* This openSCAD library is part of the [dotscad](https://github.com/dotscad/dotscad)
* project.
*
* @copyright Chris Petersen, 2013
* @license http://creativecommons.org/licenses/LGPL/2.1/
* @license http://creativecommons.org/licenses/by-sa/3.0/
*
* @see http://www.thingiverse.com/thing:109467
* @source https://github.com/dotscad/dotscad/blob/master/pie.scad
*
* example:
* union()
* {
* pie(40, 45, 5);
* rotate([0,0,-5])
* difference()
* {
* sphere(r=25);
* translate([0,0,-30])
* pie(50, -45, 60, 15);
* }
* }
*
* @param float radius Radius of the pie
* @param float angle Angle (size) of the pie to slice (right hand rule, starting at positive X axis)
* @param float height Height (thickness) of the pie
* @param float spin Angle to spin the slice on the Z axis (right hand rule, starting at positive X axis)
* @param bool center If true, center the pie slice vertically
*/
module pie(radius, angle, height, spin=0, center=false)
{
// this portion copyright 2020 by James H. Brown
translate([0,0, center==true ? -height/2 : 0])
rotate([0,0,spin])
// This rotation is required because apparelty rotate_extrude doesn't
// start at the same place as the sides of a sphere with an odd $fn value
rotate([0,0,$fn % 2 == 1 ? 360/$fn/2 : 0])
rotate_extrude(convexity=10, angle=angle)
square([radius, height]);
/* JHB: Previous to openscad v 2019.05 the code below was necessary. Now rotate extrude can do all this for us
// Negative angles shift direction of rotation
clockwise = (angle < 0) ? true : false;
// Support angles < 0 and > 360
normalized_angle = abs((angle % 360 != 0) ? angle % 360 : angle % 360 + 360);
// Select rotation direction
rotation = clockwise ? [0, 180 - normalized_angle] : [180, normalized_angle];
// Render
if (angle != 0) {
rotate([0,0,spin]) linear_extrude(height=height)
difference() {
circle(radius);
if (normalized_angle < 180) {
union() for(a = rotation)
rotate(a) translate([-radius, 0, 0]) square(radius * 2);
}
else if (normalized_angle != 360) {
intersection_for(a = rotation)
rotate(a) translate([-radius, 0, 0]) square(radius * 2);
}
}
}
*/
}
/*
* roundclip
* od = outside diamaeter of the clip body
* id = inside diameter of the clip body
* cliparc = the amount of a circle the clip takes up
* height = the height of the clip in Z
* tiparc = the amount of a circle the tips of the clip take up (default 60)
* rounded = whether to round the top and bottom edges (default false)
* upright = rotate the finished clip so the opening is centered along the positive X axis
*/
module roundclip(od, id, cliparc, height, tiparc=60, rounded=false, upright=true)
{
cs = (od-id) / 2; // cross section of the ring
tipOD = od/2;
tipID = tipOD-cs*2;
tipOffset = od/2 + tipOD/2 - cs;
translate([0,upright ? height/2 : 0,0])
rotate([upright ? 90 : 0,0,0])
rotate([0,0,90-((cliparc-180) / 2)])
{
intersection()
{
ring(od=od, id=id, h=height, rounded=rounded);
pie(od, cliparc, height, spin=0);
}
translate([tipOffset,0,0])
intersection()
{
ring(od=tipOD, id=tipID, h=height, rounded=rounded);
union()
{
pie(tipOD, -tiparc, height, spin=-180+tiparc);
rotate([0,0,-180+tiparc])
translate([tipOD/2-cs/2,0,0])
cylinder(d=cs, h=height);
}
}
rotate([0,0,cliparc])
translate([tipOffset,0,0])
intersection()
{
ring(od=tipOD, id=tipID, h=height, rounded=rounded);
union()
{
pie(tipOD, tiparc, height, spin=180-tiparc);
rotate([0,0,180-tiparc])
translate([tipOD/2-cs/2,0,0])
cylinder(d=cs, h=height);
}
}
}
}
/*
Pegboard Specs
All pegboard has holes with 1-in. spacing, but there are two thicknesses and two hole sizes available.
‘Small hole’ pegboard is usually 1/8-in.-thick hardboard with 3/16-in.-diameter holes. The holes will accommodate only the smaller 1/8-in. pegs. This thickness is good for small projects and for hanging lighter weight stuff. But for heavy tools—and longevity—go with the thicker board.
‘Large hole’ pegboard is usually 1/4-in.-thick hardboard with 1/4-in.-diameter holes that will accept both 1/8-in. and 1/4-in. hooks. This is the type you need for workshops, garages and other heavy-use areas. Some pegboard shelves come with a melamine coating on one side.
box is a vector [x, y, z] of the size of pegboard you want
inverted, if true, creates the holes, rather than a square of pegboard
centered, if true, centers the pegboard holes in the box (x, & y); otherwise they start .5" from x & y
showBoundingBox, if true & inverted is true, will add a %-masked cube the size of box
*/
module pegboard(box, inverted = false, centered=true, showBoundingBox=false)
{
bx = box[0];
by = box[1];
bz = box[2];
xStart = centered ? (bx % in2mm(1)) / 2 : in2mm(.5) ;
yStart = centered ? (by % in2mm(1)) / 2 : in2mm(.5) ;
if (inverted)
{
intersection()
{
cube(box);
for (x = [xStart:in2mm(1):bx+in2mm(1)], y=[yStart:in2mm(1):by+in2mm(1)])
{
translate([x, y, 0])
cylinder(d=in2mm(.25), h=bz);
}
}
if (showBoundingBox)
%cube(box);
}
else
{
difference()
{
cube(box);
translate([0,0,-.1])
pegboard([bx,by,bz+.2], inverted=true, centered=centered, showBoundingBox=false);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment