Merge pull request #970 from adrianVmariano/master

nut and screw fixes, path_cut_points, scaled path_sweep
This commit is contained in:
Revar Desmera 2022-10-18 03:37:10 -07:00 committed by GitHub
commit baa23c3d8f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 811 additions and 541 deletions

View file

@ -978,6 +978,11 @@ function bottle_adapter_neck_to_neck(
// Thread specs from https://www.isbt.com/threadspecs-downloads.asp
// T = peak to peak diameter (outer diameter)
// I = Inner diameter
// S = space above top thread
// H = total height of neck
_sp_specs = [
[400, //diam T I H S tpi
[[ 18, [ 17.68, 8.26, 9.42, 0.94, 8]],
@ -1042,7 +1047,7 @@ _sp_thread_width= [
];
function _sp_thread_profile(tpi, a, S, style) =
function _sp_thread_profile(tpi, a, S, style, flip=false) =
let(
pitch = 1/tpi*INCH,
cL = a*(1-1/sqrt(3)),
@ -1052,7 +1057,7 @@ function _sp_thread_profile(tpi, a, S, style) =
: style=="M" && tpi < 12 ? [0.25, 0.25, 0.75, 0.75]
: style=="L" ? [0.38, 0.13, 0.13, 0.38]
: /* style=="M" */ [0.25, 0.25, 0.2, 0.5],
path = style=="L"
path1 = style=="L"
? round_corners([[-1/2*pitch,-a/2],
[-a/2,-a/2],
[-cL/2,0],
@ -1065,10 +1070,11 @@ function _sp_thread_profile(tpi, a, S, style) =
[-cM, 0],
[0,0],
[a/2,-a/2],
[1/2*pitch,-a/2]], radius=roundings, closed=false, $fn=24)
[1/2*pitch,-a/2]], radius=roundings, closed=false, $fn=24),
path2 = flip ? reverse(xflip(path1)) : path1
)
// Shift so that the profile is S mm from the right end to create proper length S top gap
select(right(-a/2+1/2-S,p=path),1,-2)/pitch;
select(right(-a/2+1/2-S,p=path2),1,-2)/pitch;
function sp_neck(diam,type,wall,id,style="L",bead=false, anchor, spin, orient) = no_function("sp_neck");
@ -1132,7 +1138,89 @@ module sp_neck(diam,type,wall,id,style="L",bead=false, anchor, spin, orient)
}
children();
}
}
}
// Module: sp_cap()
// Usage:
// sp_neck(cap, type, wall, [style=], [top_adj=]) [ATTACHMENTS];
// Description:
// Make a SPI (Society of Plastics Industry) threaded bottle neck. You must
// supply the nominal outer diameter of the threads and the thread type, one of
// 400, 410 and 415. The 400 type neck has 360 degrees of thread, the 410
// neck has 540 degrees of thread, and the 415 neck has 720 degrees of thread.
// You can also choose between the L style thread, which is symmetric and
// the M style thread, which is an asymmetric buttress thread. Note that it
// is OK to mix styles, so you can put an L-style cap onto an M-style neck.
// .
// These caps often contain a cardboard or foam sealer disk, which can be as much as 1mm thick.
// If you don't include this, your cap may bottom out on the bead on the neck instead of sealing
// against the top. If you set top_adj to 1 it will make the top space 1mm smaller so that the
// cap will not bottom out. The 410 and 415 caps have very long unthreaded sections at the bottom.
// The bot_adj parameter specifies am amount to reduce that bottom extension. Be careful that
// you don't shrink past the threads.
// .
// Note: there is a published SPI standard for necks, but absolutely nothing for caps. This
// cap module was designed based on the neck standard to mate reasonably well, but if you
// find ways that it does the wrong thing, file a report.
// Arguments:
// diam = nominal outer diameter of threads
// type = thread type, one of 400, 410 and 415
// wall = wall thickness
// ---
// style = Either "L" or "M" to specify the thread style. Default: "L"
// top_adj = Amount to reduce top space in the cap, which means it doesn't screw down as far. Default: 0
// bot_adj = Amount to reduce extension of cap at the bottom, which also means it doesn't screw down as far. Default: 0
// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#subsection-anchor). Default: `CENTER`
// spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#subsection-spin). Default: `0`
// orient = Vector to rotate top towards, after spin. See [orient](attachments.scad#subsection-orient). Default: `UP`
// Examples:
// sp_cap(48,400,2);
// sp_cap(22,410,2);
// sp_cap(28,415,1.5,style="M");
module sp_cap(diam,type,wall,style="L",top_adj=0, bot_adj=0, anchor, spin, orient)
{
table = struct_val(_sp_specs,type);
dum1=assert(is_def(table),"Unknown SP closure type. Type must be one of 400, 410, or 415");
entry = struct_val(table, diam);
dum2=assert(is_def(entry), str("Unknown closure nominal diameter. Allowed diameters for SP",type,": ",struct_keys(table)))
assert(style=="L" || style=="M", "style must be \"L\" or \"M\"");
T = entry[0];
I = entry[1];
H = entry[2]-1;
S = entry[3];
tpi = entry[4];
a = (style=="M" && tpi==12) ? 1.3 : struct_val(_sp_thread_width,tpi);
twist = struct_val(_sp_twist, type);
dum3=assert(top_adj<S+0.75*a, str("The top_adj value is too large so the thread won't fit. It must be smaller than ",S+0.75*a));
oprofile = _sp_thread_profile(tpi,a,S+0.75*a-top_adj,style,flip=true);
bounds=pointlist_bounds(oprofile);
profile = fwd(-bounds[0].y,yflip(oprofile));
depth = a/2;
higlen = 2*a;
higang = higlen / ((T-2*depth)*PI) * 360;
echo(a=a,depth=depth,halfdepth=depth/2, tpi*pointlist_bounds(profile));
space=2*depth/10+4*get_slop();
attachable(anchor,spin,orient,r= (T+space)/2+wall, l=H-bot_adj+wall){
xrot(180)
up((H-bot_adj)/2-wall/2){
difference(){
up(wall)cyl(d=T+space+2*wall,l=H+wall-bot_adj,anchor=TOP,chamfer2=.8);
cyl(d=T+space, l=H-bot_adj+1, anchor=TOP);
}
thread_helix(d=T+space-.01, profile=profile, pitch = INCH/tpi, turns=(twist+2*higang)/360, higbee=higlen, anchor=TOP, internal=true);
}
children();
}
}
// Function: sp_diameter()

View file

@ -48,15 +48,15 @@ module move_copies(a=[[0,0,0]])
// Function&Module: line_of()
//
// Usage: Spread `n` copies by a given spacing
// Usage: Place `n` copies at a given spacing along the line
// line_of(spacing, [n], [p1=]) CHILDREN;
// Usage: Spread copies every given spacing along the line
// Usage: Place as many copies as will fit at a given spacing
// line_of(spacing, [l=], [p1=]) CHILDREN;
// Usage: Spread `n` copies along the length of the line
// Usage: Place `n` copies along the length of the line
// line_of([n=], [l=], [p1=]) CHILDREN;
// Usage: Spread `n` copies along the line from `p1` to `p2`
// Usage: Place `n` copies along the line from `p1` to `p2`
// line_of([n=], [p1=], [p2=]) CHILDREN;
// Usage: Spread copies every given spacing, centered along the line from `p1` to `p2`
// Usage: Place copies at the given spacing, centered along the line from `p1` to `p2`
// line_of([spacing], [p1=], [p2=]) CHILDREN;
// Usage: As a function
// pts = line_of([spacing], [n], [p1=]);
@ -65,11 +65,11 @@ module move_copies(a=[[0,0,0]])
// pts = line_of([n=], [p1=], [p2=]);
// pts = line_of([spacing], [p1=], [p2=]);
// Description:
// When called as a function, returns a list of points at evenly spread positions along a line.
// When called as a module, copies `children()` at one or more evenly spread positions along a line.
// When called as a function, returns a list of points at evenly spaced positions along a line.
// When called as a module, copies `children()` at one or more evenly spaced positions along a line.
// By default, the line will be centered at the origin, unless the starting point `p1` is given.
// The line will be pointed towards `RIGHT` (X+) unless otherwise given as a vector in `l`,
// `spacing`, or `p1`/`p2`. The spread is specified in one of several ways:
// `spacing`, or `p1`/`p2`. The psotion of the copies is specified in one of several ways:
// .
// If You Know... | Then Use Something Like...
// -------------------------------- | --------------------------------
@ -110,7 +110,7 @@ module move_copies(a=[[0,0,0]])
// line_of(p1=[0,0,0], p2=[5,5,20], n=6) cube(size=[3,2,1],center=true);
// Example(FlatSpin,VPD=133):
// line_of(p1=[0,0,0], p2=[5,5,20], spacing=6) cube(size=[3,2,1],center=true);
// Example: All Children are Copied at Each Spread Position
// Example: All children are copied to each position
// line_of(l=20, n=3) {
// cube(size=[1,3,1],center=true);
// cube(size=[3,1,1],center=true);
@ -156,7 +156,7 @@ function line_of(spacing, n, l, p1, p2) =
// Module: xcopies()
//
// Description:
// Spreads out `n` copies of the children along a line on the X axis.
// Places out `n` copies of the children along a line on the X axis.
//
// Usage:
// xcopies(spacing, [n], [sp]) CHILDREN;
@ -165,9 +165,9 @@ function line_of(spacing, n, l, p1, p2) =
//
// Arguments:
// spacing = Given a scalar, specifies a uniform spacing between copies. Given a list of scalars, each one gives a specific position along the line. (Default: 1.0)
// n = Number of copies to spread out. (Default: 2)
// l = Length to spread copies over.
// sp = If given as a point, copies will be spread on a line to the right of starting position `sp`. If given as a scalar, copies will be spread on a line to the right of starting position `[sp,0,0]`. If not given, copies will be spread along a line that is centered at [0,0,0].
// n = Number of copies to place. (Default: 2)
// l = Length to place copies over.
// sp = If given as a point, copies will be placed on a line to the right of starting position `sp`. If given as a scalar, copies will be placed on a line to the right of starting position `[sp,0,0]`. If not given, copies will be placed along a line that is centered at [0,0,0].
//
// Side Effects:
// `$pos` is set to the relative centerpoint of each child copy, and can be used to modify each child individually.
@ -209,7 +209,7 @@ module xcopies(spacing, n, l, sp)
// Module: ycopies()
//
// Description:
// Spreads out `n` copies of the children along a line on the Y axis.
// Places `n` copies of the children along a line on the Y axis.
//
// Usage:
// ycopies(spacing, [n], [sp]) CHILDREN;
@ -218,9 +218,9 @@ module xcopies(spacing, n, l, sp)
//
// Arguments:
// spacing = Given a scalar, specifies a uniform spacing between copies. Given a list of scalars, each one gives a specific position along the line. (Default: 1.0)
// n = Number of copies to spread out. (Default: 2)
// l = Length to spread copies over.
// sp = If given as a point, copies will be spread on a line back from starting position `sp`. If given as a scalar, copies will be spread on a line back from starting position `[0,sp,0]`. If not given, copies will be spread along a line that is centered at [0,0,0].
// n = Number of copies to place on the line. (Default: 2)
// l = Length to place copies over.
// sp = If given as a point, copies will be place on a line back from starting position `sp`. If given as a scalar, copies will be placed on a line back from starting position `[0,sp,0]`. If not given, copies will be placed along a line that is centered at [0,0,0].
//
// Side Effects:
// `$pos` is set to the relative centerpoint of each child copy, and can be used to modify each child individually.
@ -262,7 +262,7 @@ module ycopies(spacing, n, l, sp)
// Module: zcopies()
//
// Description:
// Spreads out `n` copies of the children along a line on the Z axis.
// Places `n` copies of the children along a line on the Z axis.
//
// Usage:
// zcopies(spacing, [n], [sp]) CHILDREN;
@ -271,9 +271,9 @@ module ycopies(spacing, n, l, sp)
//
// Arguments:
// spacing = Given a scalar, specifies a uniform spacing between copies. Given a list of scalars, each one gives a specific position along the line. (Default: 1.0)
// n = Number of copies to spread out. (Default: 2)
// l = Length to spread copies over.
// sp = If given as a point, copies will be spread on a line up from starting position `sp`. If given as a scalar, copies will be spread on a line up from starting position `[0,0,sp]`. If not given, copies will be spread along a line that is centered at [0,0,0].
// n = Number of copies to place. (Default: 2)
// l = Length to place copies over.
// sp = If given as a point, copies will be placed on a line up from starting position `sp`. If given as a scalar, copies will be placed on a line up from starting position `[0,0,sp]`. If not given, copies will be placed on a line that is centered at [0,0,0].
//
// Side Effects:
// `$pos` is set to the relative centerpoint of each child copy, and can be used to modify each child individually.
@ -293,7 +293,7 @@ module ycopies(spacing, n, l, sp)
// s = 20;
// s2 = s * sin(45);
// zcopies(s2,n=8) union()
// grid2d([s2,s2],n=8,stagger=($idx%2)? true : "alt")
// grid_copies([s2,s2],n=8,stagger=($idx%2)? true : "alt")
// sphere(d=s);
// Example: Hexagonal sphere packing
// s = 20;
@ -301,7 +301,7 @@ module ycopies(spacing, n, l, sp)
// h = hyp_adj_to_opp(s,xyr);
// zcopies(h,n=8) union()
// back(($idx%2)*xyr*cos(60))
// grid2d(s,n=[12,7],stagger=($idx%2)? "alt" : true)
// grid_copies(s,n=[12,7],stagger=($idx%2)? "alt" : true)
// sphere(d=s);
// Example:
// zcopies([1,2,3,5,7]) sphere(d=1);
@ -329,16 +329,16 @@ module zcopies(spacing, n, l, sp)
// Module: grid2d()
// Module: grid_copies()
//
// Description:
// Makes a square or hexagonal grid of copies of children, with an optional masking polygon or region.
//
// Usage:
// grid2d(spacing, size=, [stagger=], [scale=], [inside=]) CHILDREN;
// grid2d(n=, size=, [stagger=], [scale=], [inside=]) CHILDREN;
// grid2d(spacing, [n], [stagger=], [scale=], [inside=]) CHILDREN;
// grid2d(n=, inside=, [stagger], [scale]) CHILDREN;
// grid_copies(spacing, size=, [stagger=], [scale=], [inside=]) CHILDREN;
// grid_copies(n=, size=, [stagger=], [scale=], [inside=]) CHILDREN;
// grid_copies(spacing, [n], [stagger=], [scale=], [inside=]) CHILDREN;
// grid_copies(n=, inside=, [stagger], [scale]) CHILDREN;
//
// Arguments:
// spacing = Distance between copies in [X,Y] or scalar distance.
@ -355,20 +355,20 @@ module zcopies(spacing, n, l, sp)
// `$row` is set to the integer row number for each child.
//
// Examples:
// grid2d(size=50, spacing=10) cylinder(d=10, h=1);
// grid2d(size=50, spacing=[10,15]) cylinder(d=10, h=1);
// grid2d(spacing=10, n=[13,7], stagger=true) cylinder(d=6, h=5);
// grid2d(spacing=10, n=[13,7], stagger="alt") cylinder(d=6, h=5);
// grid2d(size=50, n=11, stagger=true) cylinder(d=5, h=1);
// grid_copies(size=50, spacing=10) cylinder(d=10, h=1);
// grid_copies(size=50, spacing=[10,15]) cylinder(d=10, h=1);
// grid_copies(spacing=10, n=[13,7], stagger=true) cylinder(d=6, h=5);
// grid_copies(spacing=10, n=[13,7], stagger="alt") cylinder(d=6, h=5);
// grid_copies(size=50, n=11, stagger=true) cylinder(d=5, h=1);
//
// Example:
// poly = [[-25,-25], [25,25], [-25,25], [25,-25]];
// grid2d(spacing=5, stagger=true, inside=poly)
// grid_copies(spacing=5, stagger=true, inside=poly)
// zrot(180/6) cylinder(d=5, h=1, $fn=6);
// %polygon(poly);
//
// Example: Using `$row` and `$col`
// grid2d(spacing=8, n=8)
// grid_copies(spacing=8, n=8)
// color(($row+$col)%2?"black":"red")
// cube([8,8,0.01], center=false);
//
@ -376,7 +376,7 @@ module zcopies(spacing, n, l, sp)
// // Makes a grid of hexagon pillars whose tops are all
// // angled to reflect light at [0,0,50], if they were shiny.
// hexregion = circle(r=50.01,$fn=6);
// grid2d(spacing=10, stagger=true, inside=hexregion) union() {
// grid_copies(spacing=10, stagger=true, inside=hexregion) union() {
// // Note: The union() is needed or else $pos will be
// // inexplicably unreadable.
// ref_v = (unit([0,0,50]-point3d($pos)) + UP)/2;
@ -384,7 +384,15 @@ module zcopies(spacing, n, l, sp)
// zrot(180/6)
// cylinder(h=20, d=10/cos(180/6)+0.01, $fn=6);
// }
function grid_copies(spacing, n, size, stagger=false, inside=undef, nonzero) = no_function("grid_copies");
module grid2d(spacing, n, size, stagger=false, inside=undef, nonzero)
{
deprecate("grid_copies");
grid_copies(spacing, n, size, stagger, inside, nonzero) children();
}
module grid_copies(spacing, n, size, stagger=false, inside=undef, nonzero)
{
req_children($children);
assert(in_list(stagger, [false, true, "alt"]));
@ -809,13 +817,13 @@ module arc_of(
// Module: ovoid_spread()
// Module: sphere_copies()
//
// Description:
// Spreads children semi-evenly over the surface of a sphere.
// Spreads children semi-evenly over the surface of a sphere or ellipsoid.
//
// Usage:
// ovoid_spread(n, r|d=, [cone_ang=], [scale=], [perp=]) CHILDREN;
// sphere_copies(n, r|d=, [cone_ang=], [scale=], [perp=]) CHILDREN;
//
// Arguments:
// n = How many copies to evenly spread over the surface.
@ -834,14 +842,21 @@ module arc_of(
// `$idx` is set to the index number of each child being copied.
//
// Example:
// ovoid_spread(n=250, d=100, cone_ang=45, scale=[3,3,1])
// sphere_copies(n=250, d=100, cone_ang=45, scale=[3,3,1])
// cylinder(d=10, h=10, center=false);
//
// Example:
// ovoid_spread(n=500, d=100, cone_ang=180)
// sphere_copies(n=500, d=100, cone_ang=180)
// color(unit(point3d(v_abs($pos))))
// cylinder(d=8, h=10, center=false);
function sphere_copies(n=100, r=undef, d=undef, cone_ang=90, scale=[1,1,1], perp=true) = no_function("sphere_copies");
module ovoid_spread(n=100, r=undef, d=undef, cone_ang=90, scale=[1,1,1], perp=true)
{
deprecate("sphere_copies");
sphere_copies(n,r,d,cone_ang,scale,perp);
}
module sphere_copies(n=100, r=undef, d=undef, cone_ang=90, scale=[1,1,1], perp=true)
{
req_children($children);
r = get_radius(r=r, d=d, dflt=50);
@ -872,16 +887,20 @@ module ovoid_spread(n=100, r=undef, d=undef, cone_ang=90, scale=[1,1,1], perp=tr
// Section: Placing copies of all children on a path
// Module: path_spread()
// Module: path_copies()
//
// Description:
// Uniformly spreads out copies of children along a path. Copies are located based on path length. If you specify `n` but not spacing then `n` copies will be placed
// with one at path[0] of `closed` is true, or spanning the entire path from start to end if `closed` is false.
// If you specify `spacing` but not `n` then copies will spread out starting from one at path[0] for `closed=true` or at the path center for open paths.
// If you specify `sp` then the copies will start at `sp`.
// Place copies all of the children at points along the path based on path length. You can specify `dist` as
// a scalar or distance list and the children will be placed at the specified distances from the start of the path. Otherwise the children are
// placed at uniformly spaced points along the path. If you specify `n` but not `spacing` then `n` copies will be placed
// with one at path[0] if `closed` is true, or spanning the entire path from start to end if `closed` is false.
// If you specify `spacing` but not `n` then copies will spread out starting from one set at path[0] for `closed=true` or at the path center for open paths.
// If you specify `sp` then the copies will start at distance `sp` from the start of the path.
//
// Usage:
// path_spread(path, [n], [spacing], [sp], [rotate_children], [closed]) CHILDREN;
// Usage: Uniformly distribute copies
// path_copies(path, [n], [spacing], [sp], [rotate_children], [closed=]) CHILDREN;
// Usage: Place copies at specified locations
// path_copies(path, dist=, [rotate_children=], [closed=]) CHILDREN;
//
// Arguments:
// path = path or 1-region where children are placed
@ -889,6 +908,8 @@ module ovoid_spread(n=100, r=undef, d=undef, cone_ang=90, scale=[1,1,1], perp=tr
// spacing = space between copies
// sp = if given, copies will start distance sp from the path start and spread beyond that point
// rotate_children = if true, rotate children to line up with curve normal. Default: true
// ---
// dist = Specify a list of distances to determine placement of children.
// closed = If true treat path as a closed curve. Default: false
//
// Side Effects:
@ -900,72 +921,86 @@ module ovoid_spread(n=100, r=undef, d=undef, cone_ang=90, scale=[1,1,1], perp=tr
// Example(2D):
// spiral = [for(theta=[0:360*8]) theta * [cos(theta), sin(theta)]]/100;
// stroke(spiral,width=.25);
// color("red") path_spread(spiral, n=100) circle(r=1);
// color("red") path_copies(spiral, n=100) circle(r=1);
// Example(2D):
// circle = regular_ngon(n=64, or=10);
// stroke(circle,width=1,closed=true);
// color("green") path_spread(circle, n=7, closed=true) circle(r=1+$idx/3);
// color("green") path_copies(circle, n=7, closed=true) circle(r=1+$idx/3);
// Example(2D):
// heptagon = regular_ngon(n=7, or=10);
// stroke(heptagon, width=1, closed=true);
// color("purple") path_spread(heptagon, n=9, closed=true) rect([0.5,3],anchor=FRONT);
// color("purple") path_copies(heptagon, n=9, closed=true) rect([0.5,3],anchor=FRONT);
// Example(2D): Direction at the corners is the average of the two adjacent edges
// heptagon = regular_ngon(n=7, or=10);
// stroke(heptagon, width=1, closed=true);
// color("purple") path_spread(heptagon, n=7, closed=true) rect([0.5,3],anchor=FRONT);
// color("purple") path_copies(heptagon, n=7, closed=true) rect([0.5,3],anchor=FRONT);
// Example(2D): Don't rotate the children
// heptagon = regular_ngon(n=7, or=10);
// stroke(heptagon, width=1, closed=true);
// color("red") path_spread(heptagon, n=9, closed=true, rotate_children=false) rect([0.5,3],anchor=FRONT);
// color("red") path_copies(heptagon, n=9, closed=true, rotate_children=false) rect([0.5,3],anchor=FRONT);
// Example(2D): Open path, specify `n`
// sinwav = [for(theta=[0:360]) 5*[theta/180, sin(theta)]];
// stroke(sinwav,width=.1);
// color("red") path_spread(sinwav, n=5) rect([.2,1.5],anchor=FRONT);
// color("red") path_copies(sinwav, n=5) rect([.2,1.5],anchor=FRONT);
// Example(2D): Open path, specify `n` and `spacing`
// sinwav = [for(theta=[0:360]) 5*[theta/180, sin(theta)]];
// stroke(sinwav,width=.1);
// color("red") path_spread(sinwav, n=5, spacing=1) rect([.2,1.5],anchor=FRONT);
// color("red") path_copies(sinwav, n=5, spacing=1) rect([.2,1.5],anchor=FRONT);
// Example(2D): Closed path, specify `n` and `spacing`, copies centered around circle[0]
// circle = regular_ngon(n=64,or=10);
// stroke(circle,width=.1,closed=true);
// color("red") path_spread(circle, n=10, spacing=1, closed=true) rect([.2,1.5],anchor=FRONT);
// color("red") path_copies(circle, n=10, spacing=1, closed=true) rect([.2,1.5],anchor=FRONT);
// Example(2D): Open path, specify `spacing`
// sinwav = [for(theta=[0:360]) 5*[theta/180, sin(theta)]];
// stroke(sinwav,width=.1);
// color("red") path_spread(sinwav, spacing=5) rect([.2,1.5],anchor=FRONT);
// color("red") path_copies(sinwav, spacing=5) rect([.2,1.5],anchor=FRONT);
// Example(2D): Open path, specify `sp`
// sinwav = [for(theta=[0:360]) 5*[theta/180, sin(theta)]];
// stroke(sinwav,width=.1);
// color("red") path_spread(sinwav, n=5, sp=18) rect([.2,1.5],anchor=FRONT);
// color("red") path_copies(sinwav, n=5, sp=18) rect([.2,1.5],anchor=FRONT);
// Example(2D): Open path, specify `dist`
// sinwav = [for(theta=[0:360]) 5*[theta/180, sin(theta)]];
// stroke(sinwav,width=.1);
// color("red") path_copies(sinwav, dist=[1,4,9,16]) rect([.2,1.5],anchor=FRONT);
// Example(2D):
// wedge = arc(angle=[0,100], r=10, $fn=64);
// difference(){
// polygon(concat([[0,0]],wedge));
// path_spread(wedge,n=5,spacing=3) fwd(.1) rect([1,4],anchor=FRONT);
// path_copies(wedge,n=5,spacing=3) fwd(.1) rect([1,4],anchor=FRONT);
// }
// Example(Spin,VPD=115): 3d example, with children rotated into the plane of the path
// tilted_circle = lift_plane([[0,0,0], [5,0,5], [0,2,3]],regular_ngon(n=64, or=12));
// path_sweep(regular_ngon(n=16,or=.1),tilted_circle);
// path_spread(tilted_circle, n=15,closed=true) {
// path_copies(tilted_circle, n=15,closed=true) {
// color("blue") cyl(h=3,r=.2, anchor=BOTTOM); // z-aligned cylinder
// color("red") xcyl(h=10,r=.2, anchor=FRONT+LEFT); // x-aligned cylinder
// }
// Example(Spin,VPD=115): 3d example, with rotate_children set to false
// tilted_circle = lift_plane([[0,0,0], [5,0,5], [0,2,3]], regular_ngon(n=64, or=12));
// path_sweep(regular_ngon(n=16,or=.1),tilted_circle);
// path_spread(tilted_circle, n=25,rotate_children=false,closed=true) {
// path_copies(tilted_circle, n=25,rotate_children=false,closed=true) {
// color("blue") cyl(h=3,r=.2, anchor=BOTTOM); // z-aligned cylinder
// color("red") xcyl(h=10,r=.2, anchor=FRONT+LEFT); // x-aligned cylinder
// }
module path_spread(path, n, spacing, sp=undef, rotate_children=true, closed)
function path_copies(path, n, spacing, sp=undef, rotate_children=true, dist, closed) = no_function("path_copies");
module path_spread(path, n, spacing, sp=undef, rotate_children=true, dist, closed){
deprecate("path_copes");
path_copies(path,n,spacing,sp,dist,rotate_children,dist, closed) children();
}
module path_copies(path, n, spacing, sp=undef, dist, rotate_children=true, dist, closed)
{
req_children($children);
is_1reg = is_1region(path);
path = is_1reg ? path[0] : path;
closed = default(closed, is_1reg);
length = path_length(path,closed);
distind = is_def(dist) ? sortidx(dist) : undef;
distances =
is_def(sp)? ( // Start point given
is_def(dist) ? assert(is_undef(n) && is_undef(spacing) && is_undef(sp), "Can't use n, spacing or undef with dist")
select(dist,distind)
: is_def(sp)? ( // Start point given
is_def(n) && is_def(spacing)? count(n,sp,spacing) :
is_def(n)? lerpn(sp, length, n) :
list([sp:spacing:length])
@ -982,11 +1017,11 @@ module path_spread(path, n, spacing, sp=undef, rotate_children=true, closed)
);
distOK = is_def(n) || (min(distances)>=0 && max(distances)<=length);
assert(distOK,"Cannot fit all of the copies");
cutlist = _path_cut_points(path, distances, closed, direction=true);
cutlist = path_cut_points(path, distances, closed, direction=true);
planar = len(path[0])==2;
if (true) for(i=[0:1:len(cutlist)-1]) {
for(i=[0:1:len(cutlist)-1]) {
$pos = cutlist[i][0];
$idx = i;
$idx = is_def(dist) ? distind[i] : i;
$dir = rotate_children ? (planar?[1,0]:[1,0,0]) : cutlist[i][2];
$normal = rotate_children? (planar?[0,1]:[0,0,1]) : cutlist[i][3];
translate($pos) {
@ -1174,7 +1209,7 @@ module zflip_copy(offset=0, z=0)
// Module: distribute()
//
// Description:
// Spreads out each individual child along the direction `dir`.
// Spreads out the children individually along the direction `dir`.
// Every child is placed at a different position, in order.
// This is useful for laying out groups of disparate objects
// where you only really care about the spacing between them.
@ -1220,7 +1255,7 @@ module distribute(spacing=undef, sizes=undef, dir=RIGHT, l=undef)
// Module: xdistribute()
//
// Description:
// Spreads out each individual child along the X axis.
// Spreads out the children individually along the X axis.
// Every child is placed at a different position, in order.
// This is useful for laying out groups of disparate objects
// where you only really care about the spacing between them.
@ -1266,7 +1301,7 @@ module xdistribute(spacing=10, sizes=undef, l=undef)
// Module: ydistribute()
//
// Description:
// Spreads out each individual child along the Y axis.
// Spreads out the children individually along the Y axis.
// Every child is placed at a different position, in order.
// This is useful for laying out groups of disparate objects
// where you only really care about the spacing between them.

View file

@ -332,7 +332,7 @@ module stroke(
}
} else {
dummy=assert(trim1<path_length(path)-trim2, "Path is too short for endcap(s). Try a smaller width, or set endcap_length to a smaller value.");
pathcut = _path_cut_points(path, [trim1, path_length(path)-trim2], closed=false);
pathcut = path_cut_points(path, [trim1, path_length(path)-trim2], closed=false);
pathcut_su = _cut_to_seg_u_form(pathcut,path);
path2 = _path_cut_getpaths(path, pathcut, closed=false)[1];
widths = _path_select(width, pathcut_su[0][0], pathcut_su[0][1], pathcut_su[1][0], pathcut_su[1][1]);

View file

@ -514,7 +514,7 @@ function resample_path(path, n, spacing, closed=true) =
// Add last point later
n = is_def(n) ? n-(closed?0:1) : round(length/spacing),
distlist = lerpn(0,length,n,false),
cuts = _path_cut_points(path, distlist, closed=closed)
cuts = path_cut_points(path, distlist, closed=closed)
)
[ each column(cuts,0),
if (!closed) last(path) // Then add last point here
@ -709,47 +709,109 @@ function path_torsion(path, closed=false) =
// Section: Breaking paths up into subpaths
/// Internal Function: _path_cut_points()
///
/// Usage:
/// cuts = _path_cut_points(path, dists, [closed=], [direction=]);
///
/// Description:
/// Cuts a path at a list of distances from the first point in the path. Returns a list of the cut
/// points and indices of the next point in the path after that point. So for example, a return
/// value entry of [[2,3], 5] means that the cut point was [2,3] and the next point on the path after
/// this point is path[5]. If the path is too short then _path_cut_points returns undef. If you set
/// `direction` to true then `_path_cut_points` will also return the tangent vector to the path and a normal
/// vector to the path. It tries to find a normal vector that is coplanar to the path near the cut
/// point. If this fails it will return a normal vector parallel to the xy plane. The output with
/// direction vectors will be `[point, next_index, tangent, normal]`.
/// .
/// If you give the very last point of the path as a cut point then the returned index will be
/// one larger than the last index (so it will not be a valid index). If you use the closed
/// option then the returned index will be equal to the path length for cuts along the closing
/// path segment, and if you give a point equal to the path length you will get an
/// index of len(path)+1 for the index.
///
/// Arguments:
/// path = path to cut
/// dists = distances where the path should be cut (a list) or a scalar single distance
/// ---
/// closed = set to true if the curve is closed. Default: false
/// direction = set to true to return direction vectors. Default: false
///
/// Example(NORENDER):
/// square=[[0,0],[1,0],[1,1],[0,1]];
/// _path_cut_points(square, [.5,1.5,2.5]); // Returns [[[0.5, 0], 1], [[1, 0.5], 2], [[0.5, 1], 3]]
/// _path_cut_points(square, [0,1,2,3]); // Returns [[[0, 0], 1], [[1, 0], 2], [[1, 1], 3], [[0, 1], 4]]
/// _path_cut_points(square, [0,0.8,1.6,2.4,3.2], closed=true); // Returns [[[0, 0], 1], [[0.8, 0], 1], [[1, 0.6], 2], [[0.6, 1], 3], [[0, 0.8], 4]]
/// _path_cut_points(square, [0,0.8,1.6,2.4,3.2]); // Returns [[[0, 0], 1], [[0.8, 0], 1], [[1, 0.6], 2], [[0.6, 1], 3], undef]
function _path_cut_points(path, dists, closed=false, direction=false) =
// Function: path_cut()
// Topics: Paths
// See Also: split_path_at_self_crossings()
// Usage:
// path_list = path_cut(path, cutdist, [closed]);
// Description:
// Given a list of distances in `cutdist`, cut the path into
// subpaths at those lengths, returning a list of paths.
// If the input path is closed then the final path will include the
// original starting point. The list of cut distances must be
// in ascending order and should not include the endpoints: 0
// or len(path). If you repeat a distance you will get an
// empty list in that position in the output. If you give an
// empty cutdist array you will get the input path as output
// (without the final vertex doubled in the case of a closed path).
// Arguments:
// path = path of any dimension or a 1-region
// cutdist = Distance or list of distances where path is cut
// closed = If true, treat the path as a closed polygon. Default: false
// Example(2D,NoAxes):
// path = circle(d=100);
// segs = path_cut(path, [50, 200], closed=true);
// rainbow(segs) stroke($item, endcaps="butt", width=3);
function path_cut(path,cutdist,closed) =
is_num(cutdist) ? path_cut(path,[cutdist],closed) :
is_1region(path) ? path_cut(path[0], cutdist, default(closed,true)):
let(closed=default(closed,false))
assert(is_bool(closed))
assert(is_vector(cutdist))
assert(last(cutdist)<path_length(path,closed=closed),"Cut distances must be smaller than the path length")
assert(cutdist[0]>0, "Cut distances must be strictly positive")
let(
cutlist = path_cut_points(path,cutdist,closed=closed)
)
_path_cut_getpaths(path, cutlist, closed);
function _path_cut_getpaths(path, cutlist, closed) =
let(
cuts = len(cutlist)
)
[
[ each list_head(path,cutlist[0][1]-1),
if (!approx(cutlist[0][0], path[cutlist[0][1]-1])) cutlist[0][0]
],
for(i=[0:1:cuts-2])
cutlist[i][0]==cutlist[i+1][0] && cutlist[i][1]==cutlist[i+1][1] ? []
:
[ if (!approx(cutlist[i][0], select(path,cutlist[i][1]))) cutlist[i][0],
each slice(path, cutlist[i][1], cutlist[i+1][1]-1),
if (!approx(cutlist[i+1][0], select(path,cutlist[i+1][1]-1))) cutlist[i+1][0],
],
[
if (!approx(cutlist[cuts-1][0], select(path,cutlist[cuts-1][1]))) cutlist[cuts-1][0],
each select(path,cutlist[cuts-1][1],closed ? 0 : -1)
]
];
// Function: path_cut_points()
//
// Usage:
// cuts = path_cut_points(path, cutdist, [closed=], [direction=]);
//
// Description:
// Cuts a path at a list of distances from the first point in the path. Returns a list of the cut
// points and indices of the next point in the path after that point. So for example, a return
// value entry of [[2,3], 5] means that the cut point was [2,3] and the next point on the path after
// this point is path[5]. If the path is too short then path_cut_points returns undef. If you set
// `direction` to true then `path_cut_points` will also return the tangent vector to the path and a normal
// vector to the path. It tries to find a normal vector that is coplanar to the path near the cut
// point. If this fails it will return a normal vector parallel to the xy plane. The output with
// direction vectors will be `[point, next_index, tangent, normal]`.
// .
// If you give the very last point of the path as a cut point then the returned index will be
// one larger than the last index (so it will not be a valid index). If you use the closed
// option then the returned index will be equal to the path length for cuts along the closing
// path segment, and if you give a point equal to the path length you will get an
// index of len(path)+1 for the index.
//
// Arguments:
// path = path to cut
// cutdist = distances where the path should be cut (a list) or a scalar single distance
// ---
// closed = set to true if the curve is closed. Default: false
// direction = set to true to return direction vectors. Default: false
//
// Example(NORENDER):
// square=[[0,0],[1,0],[1,1],[0,1]];
// path_cut_points(square, [.5,1.5,2.5]); // Returns [[[0.5, 0], 1], [[1, 0.5], 2], [[0.5, 1], 3]]
// path_cut_points(square, [0,1,2,3]); // Returns [[[0, 0], 1], [[1, 0], 2], [[1, 1], 3], [[0, 1], 4]]
// path_cut_points(square, [0,0.8,1.6,2.4,3.2], closed=true); // Returns [[[0, 0], 1], [[0.8, 0], 1], [[1, 0.6], 2], [[0.6, 1], 3], [[0, 0.8], 4]]
// path_cut_points(square, [0,0.8,1.6,2.4,3.2]); // Returns [[[0, 0], 1], [[0.8, 0], 1], [[1, 0.6], 2], [[0.6, 1], 3], undef]
function path_cut_points(path, cutdist, closed=false, direction=false) =
let(long_enough = len(path) >= (closed ? 3 : 2))
assert(long_enough,len(path)<2 ? "Two points needed to define a path" : "Closed path must include three points")
is_num(dists) ? _path_cut_points(path, [dists],closed, direction)[0] :
assert(is_vector(dists))
assert(is_increasing(dists), "Cut distances must be an increasing list")
let(cuts = _path_cut_points_recurse(path,dists,closed))
is_num(cutdist) ? path_cut_points(path, [cutdist],closed, direction)[0] :
assert(is_vector(cutdist))
assert(is_increasing(cutdist), "Cut distances must be an increasing list")
let(cuts = path_cut_points_recurse(path,cutdist,closed))
!direction
? cuts
: let(
@ -759,7 +821,7 @@ function _path_cut_points(path, dists, closed=false, direction=false) =
hstack(cuts, list_to_matrix(dir,1), list_to_matrix(normals,1));
// Main recursive path cut function
function _path_cut_points_recurse(path, dists, closed=false, pind=0, dtotal=0, dind=0, result=[]) =
function path_cut_points_recurse(path, dists, closed=false, pind=0, dtotal=0, dind=0, result=[]) =
dind == len(dists) ? result :
let(
lastpt = len(result)==0? [] : last(result)[0], // location of last cut point
@ -768,7 +830,7 @@ function _path_cut_points_recurse(path, dists, closed=false, pind=0, dtotal=0, d
? [lerp(lastpt,select(path,pind),(dists[dind]-dtotal)/dpartial),pind]
: _path_cut_single(path, dists[dind]-dtotal-dpartial, closed, pind)
)
_path_cut_points_recurse(path, dists, closed, nextpoint[1], dists[dind],dind+1, concat(result, [nextpoint]));
path_cut_points_recurse(path, dists, closed, nextpoint[1], dists[dind],dind+1, concat(result, [nextpoint]));
// Search for a single cut point in the path
@ -826,65 +888,6 @@ function _path_cuts_dir(path, cuts, closed=false, eps=1e-2) =
];
// Function: path_cut()
// Topics: Paths
// See Also: split_path_at_self_crossings()
// Usage:
// path_list = path_cut(path, cutdist, [closed]);
// Description:
// Given a list of distances in `cutdist`, cut the path into
// subpaths at those lengths, returning a list of paths.
// If the input path is closed then the final path will include the
// original starting point. The list of cut distances must be
// in ascending order and should not include the endpoints: 0
// or len(path). If you repeat a distance you will get an
// empty list in that position in the output. If you give an
// empty cutdist array you will get the input path as output
// (without the final vertex doubled in the case of a closed path).
// Arguments:
// path = path of any dimension or a 1-region
// cutdist = Distance or list of distances where path is cut
// closed = If true, treat the path as a closed polygon. Default: false
// Example(2D,NoAxes):
// path = circle(d=100);
// segs = path_cut(path, [50, 200], closed=true);
// rainbow(segs) stroke($item, endcaps="butt", width=3);
function path_cut(path,cutdist,closed) =
is_num(cutdist) ? path_cut(path,[cutdist],closed) :
is_1region(path) ? path_cut(path[0], cutdist, default(closed,true)):
let(closed=default(closed,false))
assert(is_bool(closed))
assert(is_vector(cutdist))
assert(last(cutdist)<path_length(path,closed=closed),"Cut distances must be smaller than the path length")
assert(cutdist[0]>0, "Cut distances must be strictly positive")
let(
cutlist = _path_cut_points(path,cutdist,closed=closed)
)
_path_cut_getpaths(path, cutlist, closed);
function _path_cut_getpaths(path, cutlist, closed) =
let(
cuts = len(cutlist)
)
[
[ each list_head(path,cutlist[0][1]-1),
if (!approx(cutlist[0][0], path[cutlist[0][1]-1])) cutlist[0][0]
],
for(i=[0:1:cuts-2])
cutlist[i][0]==cutlist[i+1][0] && cutlist[i][1]==cutlist[i+1][1] ? []
:
[ if (!approx(cutlist[i][0], select(path,cutlist[i][1]))) cutlist[i][0],
each slice(path, cutlist[i][1], cutlist[i+1][1]-1),
if (!approx(cutlist[i+1][0], select(path,cutlist[i+1][1]-1))) cutlist[i+1][0],
],
[
if (!approx(cutlist[cuts-1][0], select(path,cutlist[cuts-1][1]))) cutlist[cuts-1][0],
each select(path,cutlist[cuts-1][1],closed ? 0 : -1)
]
];
// internal function
// converts pathcut output form to a [segment, u]
// form list that works withi path_select

View file

@ -845,8 +845,8 @@ function _path_join(paths,joint,k=0.5,i=0,result=[],relocate=true,closed=false)
assert(d_first<path_length(revresult),str("Path ",i," is too short for specified cut distance ",d_first))
assert(d_next<path_length(nextpath), str("Path ",i+1," is too short for specified cut distance ",d_next))
let(
firstcut = _path_cut_points(revresult, d_first, direction=true),
nextcut = _path_cut_points(nextpath, d_next, direction=true)
firstcut = path_cut_points(revresult, d_first, direction=true),
nextcut = path_cut_points(nextpath, d_next, direction=true)
)
assert(!loop || nextcut[1] < len(revresult)-1-firstcut[1], "Path is too short to close the loop")
let(
@ -1197,8 +1197,8 @@ function _stroke_end(width,left, right, spec) =
90-vector_angle([newright[1],newright[0],newleft[0]])/2,
jointleft = 8*cutleft/cos(leftangle)/(1+4*bez_k),
jointright = 8*cutright/cos(rightangle)/(1+4*bez_k),
pathcutleft = _path_cut_points(newleft,abs(jointleft)),
pathcutright = _path_cut_points(newright,abs(jointright)),
pathcutleft = path_cut_points(newleft,abs(jointleft)),
pathcutright = path_cut_points(newright,abs(jointright)),
leftdelete = intright? pathcutleft[1] : pathcutleft[1] + pathclip[1] -1,
rightdelete = intright? pathcutright[1] + pathclip[1] -1 : pathcutright[1],
leftcorner = line_intersection([pathcutleft[0], newleft[pathcutleft[1]]], [newright[0],newleft[0]]),

View file

@ -97,7 +97,8 @@ include <screw_drive.scad>
// from the screw size, but by passing the `drive_size=` argument you can override the default, or
// in cases where no default exists you can specify it. Flat head screws have variations such as 100 degree
// angle for UTS, or undercut heads. You can also request a "sharp" screw which will set the screw diameter
// the theoretical maximum and produce sharp corners instead of a flat edge on the head. The flat head options
// the theoretical maximum and produce sharp corners instead of a flat edge on the head. For a flat head screw
// the drive specification must start with "flat", but the flat head options
// can be mixed in any order, for example, "flat sharp undercut" or "flat undercut sharp".
// Subsection: Nuts
// Nuts come in standard sizes and BOSL2 has tables to produce sizes for both Imperial and metric nuts.
@ -497,14 +498,14 @@ function screw(spec, head, drive, thread, drive_size,
undersize, shaft_undersize, head_undersize,
atype="screw",anchor=BOTTOM, spin=0, orient=UP,
_shoulder_diam=0, _shoulder_len=0,
_internal=false, _counterbore) = no_function("screw");
_internal=false, _counterbore, _teardrop) = no_function("screw");
module screw(spec, head, drive, thread, drive_size,
length, l, thread_len, tolerance, details=true,
undersize, shaft_undersize, head_undersize,
atype="screw",anchor=BOTTOM, spin=0, orient=UP,
_shoulder_diam=0, _shoulder_len=0,
_internal=false, _counterbore)
_internal=false, _counterbore, _teardrop=false)
{
tempspec = _get_spec(spec, "screw_info", _internal ? "screw_hole" : "screw",
thread=thread, head=head, drive=drive, drive_size=drive_size);
@ -523,12 +524,17 @@ module screw(spec, head, drive, thread, drive_size,
_counterbore = _counterbore==true ? struct_val(tempspec,"head_height")
: _counterbore==false ? undef
: _counterbore;
head = struct_val(tempspec,"head");
headless = head=="none";
flathead = starts_with(head,"flat");
reset_headsize = _internal && flathead ? struct_val(tempspec,"head_size_sharp") : undef;
spec=_struct_reset(tempspec,[
["length", l],
["threads_oversize", u_mul(-1,shaft_undersize)],
["head_oversize", u_mul(-1,head_undersize)],
["counterbore", _counterbore],
["thread_len", thread_len]
["thread_len", thread_len],
["head_size", reset_headsize],
]);
dummy = _validate_screw_spec(spec);
$screw_spec = spec;
@ -537,18 +543,15 @@ module screw(spec, head, drive, thread, drive_size,
nominal_diam = _nominal_diam(spec);
d_major = pitch==0 ? nominal_diam : mean(struct_val(threadspec, "d_major"));
length = struct_val(spec,"length");
head = struct_val(spec,"head");
headless = head=="none";
flathead = starts_with(head,"flat");
counterbore = default(struct_val(spec,"counterbore"),0);
user_thread_len = struct_val(spec,"thread_len");
dummyC = assert(in_list(atype,["shaft","head","shank","threads","screw","shoulder"]),str("Unknown anchor type: \"",atype,"\""))
assert(is_finite(length) && length>0, "Must specify positive screw length")
assert(is_finite(_shoulder_len) && _shoulder_len>=0, "Must specify a nonegative shoulder length")
assert(is_finite(_shoulder_diam) && _shoulder_diam>=0, "Must specify nonnegative shoulder diameter")
assert(is_undef(user_thread_len) || (is_finite(user_thread_len) && user_thread_len>=0), "Must specify nonnegative thread length");
sides = max(12, segs(nominal_diam/2));
assert(is_undef(user_thread_len) || (is_finite(user_thread_len) && user_thread_len>=0), "Must specify nonnegative thread length")
assert(!_teardrop || pitch==0);
sides = max(pitch==0 ? 3 : 12, segs(nominal_diam/2));
head_height = headless || flathead ? 0
: counterbore==true || is_undef(counterbore) || counterbore==0 ? struct_val(spec, "head_height")
: counterbore;
@ -604,7 +607,9 @@ module screw(spec, head, drive, thread, drive_size,
named_anchor("threads_bot", [0,0,-length-shoulder_full+offset]),
named_anchor("threads_center", [0,0,(-shank_len-length-_shoulder_len-shoulder_full-flat_height)/2+offset])
];
vnf = head=="hex" && atype=="head" && counterbore==0 ? linear_sweep(hexagon(id=head_diam),height=head_height,center=true) : undef;
rad_scale = _internal? (1/cos(180/sides)) : 1;
islop = _internal ? 4*get_slop() : 0;
vnf = head=="hex" && atype=="head" && counterbore==0 ? linear_sweep(hexagon(id=head_diam*rad_scale),height=head_height,center=true) : undef;
head_diam_full = head=="hex" ? 2*head_diam/sqrt(3) : head_diam;
attach_d = in_list(atype,["threads","shank","shaft"]) ? d_major
: atype=="screw" ? max(d_major,_shoulder_diam,default(head_diam_full,0))
@ -620,7 +625,7 @@ module screw(spec, head, drive, thread, drive_size,
: head_height+flat_height+flat_cbore_height;
attachable(
vnf = vnf,
d = attach_d,
d = u_add(u_mul(attach_d, rad_scale), islop),
l = attach_l,
orient = orient,
anchor = anchor,
@ -630,14 +635,22 @@ module screw(spec, head, drive, thread, drive_size,
up(offset)
difference(){
union(){
screw_head(spec,details,counterbore=counterbore,flat_height=flat_height);
screw_head(spec,details,counterbore=counterbore,flat_height=flat_height,
oversize=islop,teardrop=_teardrop);
if (_shoulder_len>0)
up(eps_shoulder-flat_height)
cyl(d=_shoulder_diam, h=_shoulder_len+eps_shoulder, anchor=TOP, $fn=sides, chamfer1=details ? _shoulder_diam/30:0);
up(eps_shoulder-flat_height){
if (_teardrop)
teardrop(d=_shoulder_diam*rad_scale+islop, h=_shoulder_len+eps_shoulder, anchor=FRONT, orient=BACK, $fn=sides);
else
cyl(d=_shoulder_diam*rad_scale+islop, h=_shoulder_len+eps_shoulder, anchor=TOP, $fn=sides, chamfer1=details ? _shoulder_diam/30:0);
}
if (shank_len>0 || pitch==0){
L = pitch==0 ? length - (_shoulder_len==0?flat_height:0) : shank_len;
down(_shoulder_len+flat_height-eps_shank)
cyl(d=d_major, h=L+eps_shank, anchor=TOP, $fn=sides);
down(_shoulder_len+flat_height-eps_shank)
if (_teardrop)
teardrop(d=d_major*rad_scale+islop, h=L+eps_shank, anchor=FRONT, orient=BACK, $fn=sides);
else
cyl(d=d_major*rad_scale+islop, h=L+eps_shank, anchor=TOP, $fn=sides);
}
if (thread_len>0 && pitch>0)
down(_shoulder_len+flat_height+shank_len-eps_thread)
@ -661,7 +674,7 @@ module screw(spec, head, drive, thread, drive_size,
// Module: screw_hole()
// Usage:
// screw_hole([spec], [head], [thread=], [length=|l=], [oversize=], [hole_oversize=], [head_oversize], [tolerance=], [$slop=], [anchor=], [atype=], [orient=], [spin=]) [ATTACHMENTS];
// screw_hole([spec], [head], [thread=], [length=|l=], [oversize=], [hole_oversize=], [teardrop=], [head_oversize], [tolerance=], [$slop=], [anchor=], [atype=], [orient=], [spin=]) [ATTACHMENTS];
// Description:
// Create a screw hole mask. See [screw and nut parameters](#section-screw-and-nut-parameters) for details on the parameters that define a screw.
// The screw hole can be threaded to receive a screw or it can be an unthreaded clearance hole.
@ -678,22 +691,27 @@ module screw(spec, head, drive, thread, drive_size,
// and "H14" for "coarse". These designations will also work, but only for metric holes. You can also set tolerance to 0 or "none" to produce holes at the nominal size.
// .
// The counterbore parameter adds a cylindrical clearance hole above the screw shaft. For flat heads it extends above the flathead and for other screw types it
// replaces the head with a cylinder large enough for the head to fit. For a flat head you must specify the length of the counterbore. For other heads you can
// replaces the head with a cylinder large enough in diameter for the head to fit. For a flat head you must specify the length of the counterbore. For other heads you can
// set counterbore to true and it will be sized to match the head height. The counterbore will extend 0.01 above the TOP of the hole mask to ensure no
// problems with differences.
// problems with differences. Note that the counterbore defaults to true for non-flathead screws. If you want the actual head shape to appear, set counterbore to zero.
// .
// For 3d printing circular holes can be problematic. One solution is to use octagonal holes, setting $fn=8. Another option is to use a teardrop hole, which
// can be accomplished by setting `teardrop=true`. The point of the teardrop will point in the Y direction (BACK) so you will need to ensure that you orient it
// correctly in your final model.
// .
// Anchoring for screw_hole() is the same as anchoring for {{screw()}}, with all the same anchor types and named anchors. If you specify a counterbore it is treated as
// the "head", or in the case of flat heads, it becomes part of the head.
// the "head", or in the case of flat heads, it becomes part of the head. If you make a teardrop hole the point is ignored for purposes of anchoring.
// Arguments:
// spec = screw specification, e.g. "M5x1" or "#8-32". See [screw naming](#subsection-screw-naming). This can also be a screw specification structure of the form produced by {{screw_info()}}.
// head = head type. See [screw heads](#subsection-screw-heads) Default: none
// ---
// thread = thread type or specification for threaded masks, or false to make an unthreaded mask. See [screw pitch](#subsection-standard-screw-pitch). Default: false
// teardrop = if true produce teardrop hole. Only compatible with clearance holes, not threaded. Default: false
// oversize = amount to increase diameter of all screw parts, a scalar or length 3 vector. Default: 0
// oversize_hole = amount to increase diameter of the hole.
// oversize_head = amount to increase diameter of head.
// length / l= length of screw (in mm)
// counterbore = set to length of counterbore, or true to make a counterbore equal to head height. Default: no counterbore
// counterbore = set to length of counterbore, or true to make a counterbore equal to head height. Default: false for flat heads and headless, true otherwise
// tolerance = threading or clearance hole tolerance. For internal threads, detrmines actual thread geometry based on nominal sizing. See [tolerance](#subsection-tolerance). Default is "2B" for UTS and 6H for ISO. For clearance holes, determines how much clearance to add. Default is "normal".
// $slop = add extra gap to account for printer overextrusion. Default: 0
// atype = anchor type, one of "screw", "head", "shaft", "threads", "shank"
@ -740,23 +758,24 @@ module screw(spec, head, drive, thread, drive_size,
// attach(FRONT)
// screw_hole("M16,15",anchor=TOP,thread=true);
module screw_hole(spec, head, thread, oversize, hole_oversize, head_oversize,
length, l, thread_len, tolerance=undef, counterbore=0,
length, l, thread_len, tolerance=undef, counterbore, teardrop=false,
atype="screw",anchor=BOTTOM,spin=0, orient=UP)
{
// Force flatheads to sharp for proper countersink shape
head = is_def(head) && starts_with(head,"flat") ? str(head," sharp")
: head;
screwspec = _get_spec(spec, "screw_info", "screw_hole",
thread=thread, head=head);
checkhead = struct_val(screwspec,"head");
default_counterbore = checkhead=="none" || starts_with(checkhead,"flat") ? 0 : true;
counterbore = default(counterbore, default_counterbore);
dummy = _validate_screw_spec(screwspec);
threaded = thread==true || (is_finite(thread) && thread>0) || (is_undef(thread) && struct_val(screwspec,"pitch")>0);
dummy2 = assert(!threaded || !teardrop, "Cannot make threaded teardrop holes");
if (threaded || is_def(oversize) || is_def(hole_oversize) || tolerance==0 || tolerance=="none") {
undersize = is_def(oversize) ? -oversize
: -[default(hole_oversize,0), default(head_oversize,0)];
default_tag("remove")
screw(spec,head=head,thread=thread,undersize=undersize,
length=length,l=l,thread_len=thread_len, tolerance=tolerance, _counterbore=counterbore,
atype=atype, anchor=anchor, spin=spin, orient=orient, _internal=true)
atype=atype, anchor=anchor, spin=spin, orient=orient, _internal=true, _teardrop=teardrop)
children();
}
else {
@ -870,7 +889,7 @@ module screw_hole(spec, head, thread, oversize, hole_oversize, head_oversize,
default_tag("remove")
screw(spec,head=head,thread=0,shaft_undersize=-hole_oversize, head_undersize=-head_oversize,
length=length,l=l,thread_len=thread_len, _counterbore=counterbore,
atype=atype, anchor=anchor, spin=spin, orient=orient, _internal=true)
atype=atype, anchor=anchor, spin=spin, orient=orient, _internal=true, _teardrop=teardrop)
children();
}
}
@ -1059,7 +1078,6 @@ module _driver(spec)
head = struct_val(spec,"head");
diameter = _nominal_diam(spec);
drive_size = struct_val(spec,"drive_size");
drive_width = struct_val(spec,"drive_width");
drive_diameter = struct_val(spec, "drive_diameter");
drive_depth = first_defined([struct_val(spec, "drive_depth"), .7*diameter]); // Note hack for unspecified depth
head_top = starts_with(head,"flat") || head=="none" ? 0 :
@ -1071,7 +1089,7 @@ module _driver(spec)
if (drive=="hex") hex_drive_mask(drive_size,drive_depth+1,anchor=BOT);
if (drive=="slot") {
head_width = first_defined([u_add(struct_val(spec, "head_size"),struct_val(spec,"head_oversize",0)), diameter]);
cuboid([2*head_width, drive_width, drive_depth+1],anchor=BOTTOM);
cuboid([2*head_width, drive_size, drive_depth+1],anchor=BOTTOM);
}
}
}
@ -1282,7 +1300,7 @@ function _parse_drive(drive=undef, drive_size=undef) =
// Module: screw_head()
// Usage:
// screw_head(screw_info, [details],[counterbore],[flat_height])
// screw_head(screw_info, [details],[counterbore],[flat_height],[oversize],[teardrop])
// Description:
// Draws the screw head described by the data structure `screw_info`, which
// should have the fields produced by {{screw_info()}}. See that function for
@ -1293,11 +1311,13 @@ function _parse_drive(drive=undef, drive_size=undef) =
// screw_info = structure produced by {{screw_info()}}
// details = true for more detailed model. Default: false
// counterbore = counterbore height. Default: no counterbore
// flat_height = height of flat head
// flat_height = height of flat head
// oversize = amount to oversize the head
// teardrop = if true make flathead and counterbores teardrop shaped
function screw_head(screw_info,details,counterbore,flat_height) = no_function("screw_head");
module screw_head(screw_info,details=false, counterbore=0,flat_height) {
module screw_head(screw_info,details=false, counterbore=0,flat_height,oversize=0,teardrop=false) {
no_children($children);
head_oversize = struct_val(screw_info, "head_oversize",0);
head_oversize = struct_val(screw_info, "head_oversize",0) + oversize;
head = struct_val(screw_info, "head");
head_size = struct_val(screw_info, "head_size",0) + head_oversize;
head_height = struct_val(screw_info, "head_height");
@ -1312,8 +1332,13 @@ module screw_head(screw_info,details=false, counterbore=0,flat_height) {
: "Counterbore must be a nonnegative number"));
counterbore = counterbore_temp==0 && head!="flat" ? counterbore_temp : counterbore_temp + 0.01;
if (head!="flat" && counterbore>0)
cyl(d=head=="hex"? 2*head_size/sqrt(3) : head_size, l=counterbore, anchor=BOTTOM);
if (head!="flat" && counterbore>0){
d = head=="hex"? 2*head_size/sqrt(3) : head_size;
if (teardrop)
teardrop(d=d, l=counterbore, orient=BACK, anchor=BACK);
else
cyl(d=d, l=counterbore, anchor=BOTTOM);
}
if (head=="flat") { // For flat head, counterbore is integrated
angle = struct_val(screw_info, "head_angle")/2;
diam = _nominal_diam(screw_info);
@ -1323,8 +1348,10 @@ module screw_head(screw_info,details=false, counterbore=0,flat_height) {
slopeheight = flat_height - sidewall_height;
r1 = head_size/2;
r2 = r1 - tan(angle)*slopeheight;
rotate_extrude()
polygon([[0,-flat_height],[r2,-flat_height],[r1,-flat_height+slopeheight],[r1,counterbore], [0,counterbore]]);
n = segs(r1);
prof1 = teardrop ? teardrop2d(r=r1,$fn=n) : circle(r=r1, $fn=n);
prof2 = teardrop ? teardrop2d(r=r2,$fn=n) : circle(r=r2, $fn=n);
skin([prof2,prof1,prof1], z=[-flat_height, -flat_height+slopeheight, counterbore],slices=0);
}
if (head!="flat" && counterbore==0) {
if (in_list(head,["round","pan round","button","fillister","cheese"])) {
@ -1394,7 +1421,13 @@ module screw_head(screw_info,details=false, counterbore=0,flat_height) {
// nutwidth = width of nut (overrides table values)
// thread = thread type or specification. See [screw pitch](#subsection-standard-screw-pitch). Default: "coarse"
// hole_oversize = amount to increase hole diameter. Default: 0
// bevel = bevel the nut. Default: false
// bevel = if true, bevel the outside of the nut.
// bevel1 = if true, bevel the outside of the nut bottom.
// bevel2 = if true, bevel the outside of the nut top.
// bevang = set the angle for the outside nut bevel. Default: 15
// ibevel = if true, bevel the inside (the hole). Default: true
// ibevel1 = if true bevel the inside, bottom end.
// ibevel2 = if true bevel the inside, top end.
// tolerance = nut tolerance. Determines actual nut thread geometry based on nominal sizing. See [tolerance](#subsection-tolerance). Default is "2B" for UTS and "6H" for ISO.
// $slop = extra space left to account for printing over-extrusion. Default: 0
// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#subsection-anchor). Default: `CENTER`
@ -1436,11 +1469,14 @@ module screw_head(screw_info,details=false, counterbore=0,flat_height) {
// mark(2) nut("1/4-20", thickness=8, nutwidth=0.5*INCH,tolerance="2B");
// mark(3) nut("1/4-20", thickness=8, nutwidth=0.5*INCH,tolerance="3B");
// }
// Example: Threadless nut
// nut("#8", thread="none");
function nut(spec, shape, thickness, nutwidth, thread, tolerance, hole_oversize,
bevel=true, anchor=BOTTOM, spin=0, orient=UP, oversize=0) = no_function("nut");
bevel,bevel1,bevel2,bevang=15,ibevel,ibevel1,ibevel2, anchor=BOTTOM, spin=0, orient=UP, oversize=0)
= no_function("nut");
module nut(spec, shape, thickness, nutwidth, thread, tolerance, hole_oversize,
bevel=true, anchor=BOTTOM, spin=0, orient=UP, oversize=0)
bevel,bevel1,bevel2,bevang=15,ibevel,ibevel1,ibevel2, anchor=BOTTOM, spin=0, orient=UP, oversize=0)
{
dummyA = assert(is_undef(nutwidth) || (is_num(nutwidth) && nutwidth>0));
@ -1459,13 +1495,15 @@ module nut(spec, shape, thickness, nutwidth, thread, tolerance, hole_oversize,
thickness = struct_val(spec, "thickness");
threaded_nut(
nutwidth=nutwidth,
id=[mean(struct_val(threadspec, "d_minor")),
mean(struct_val(threadspec, "d_pitch")),
mean(struct_val(threadspec, "d_major"))],
pitch = struct_val(threadspec, "pitch"),
id=pitch==0 ? _nominal_diam(spec)
: [mean(struct_val(threadspec, "d_minor")),
mean(struct_val(threadspec, "d_pitch")),
mean(struct_val(threadspec, "d_major"))],
pitch = pitch,
h=thickness,
shape=shape,
bevel=bevel,
bevel=bevel,bevel1=bevel1,bevel2=bevel2,bevang=bevang,
ibevel=ibevel,ibevel1=ibevel1,ibevel2=ibevel2,
anchor=anchor,spin=spin,orient=orient) children();
}
@ -1626,7 +1664,7 @@ module nut_trap_inline(length, spec, shape, l, height, h, nutwidth, anchor, orie
// Function: screw_info()
// Usage:
// info = screw_info(spec, [head], [drive], [thread=], [drive_size=], [oversize=], [head_oversize=])
// info = screw_info(name, [head], [drive], [thread=], [drive_size=], [oversize=], [head_oversize=])
// Description:
// Look up screw characteristics for the specified screw type.
// See [screw and nut parameters](#section-screw-and-nut-parameters) for details on the parameters that define a screw.
@ -1668,17 +1706,17 @@ module nut_trap_inline(length, spec, shape, l, height, h, nutwidth, anchor, orie
// "head_angle" | Countersink angle for flat heads.
// "head_height" | Height of the head beyond the screw's nominal length. The screw's total length is "length" + "head_height". For flat heads "head_height" is zero, because they do not extend the screw.
// "drive" | The drive type (`"phillips"`, `"torx"`, `"slot"`, `"hex"`, `"none"`)
// "drive_size" | The drive size, either a drive number (phillips or torx) or a dimension in mm (hex). Not defined for slot drive.
// "drive_diameter" | Diameter of a phillips drive.
// "drive_width" | Width of the arms of the cross in a phillips drive or the slot for a slot drive.
// "drive_size" | The drive size, either a drive number (phillips, torx) or a dimension in mm (hex, slot).
// "drive_depth" | Depth of the drive recess.
// "length" | Length of the screw in mm measured in the customary fashion. For flat head screws the total length and for other screws, the length from the bottom of the head to the screw tip.
// "thread_len" | Length of threaded portion of screw in mm
// "threads_oversize"| Amount to oversize the threads
// "head_oversize" | Amount to oversize the head
// .
// If you want to define a custom drive for a screw you will need to provide the drive size and drive depth.
//
// Arguments:
// spec = screw specification, e.g. "M5x1" or "#8-32". See [screw naming](#subsection-screw-naming).
// name = screw specification, e.g. "M5x1" or "#8-32". See [screw naming](#subsection-screw-naming).
// head = head type. See [screw heads](#subsection-screw-heads) Default: none
// drive = drive type. See [screw heads](#subsection-screw-heads) Default: none
// ---
@ -1687,14 +1725,14 @@ module nut_trap_inline(length, spec, shape, l, height, h, nutwidth, anchor, orie
// oversize = amount to increase screw diameter for clearance holes. Default: 0
// head_oversize = amount to increase head diameter for countersink holes. Default: 0
function screw_info(spec, head, drive, thread, drive_size, threads_oversize=0, head_oversize=0, _origin) =
assert(is_string(spec), "Screw specification must be a string")
function screw_info(name, head, drive, thread, drive_size, threads_oversize=0, head_oversize=0, _origin) =
assert(is_string(name), "Screw specification must be a string")
let(
thread = is_undef(thread) || thread==true ? "coarse"
: thread==false || thread=="none" ? 0
: thread,
head = default(head,"none"),
type=_parse_screw_name(spec),
type=_parse_screw_name(name),
drive_info = _parse_drive(drive, drive_size),
drive=drive_info[0],
screwdata = type[0] == "english" ? _screw_info_english(type[1],type[2], head, thread, drive)
@ -1706,7 +1744,7 @@ function screw_info(spec, head, drive, thread, drive_size, threads_oversize=0, h
["drive_depth", drive_info[2]],
["length", type[3]],
["drive_size", drive_info[1]],
["name", spec],
["name", name],
["threads_oversize", threads_oversize],
["head_oversize", head_oversize],
["origin",_origin]
@ -1715,7 +1753,7 @@ function screw_info(spec, head, drive, thread, drive_size, threads_oversize=0, h
// Function: nut_info()
// Usage:
// nut_spec = nut_info(spec, [shape], [thickness=], [thread=], [width=], [hole_oversize=]);
// nut_spec = nut_info(name, [shape], [thickness=], [thread=], [width=], [hole_oversize=]);
// Description:
// Produces a nut specification structure that describes a nut. You can specify the width
// and thickness numerically, or you can let the width be calculated automatically from
@ -1738,7 +1776,7 @@ function screw_info(spec, head, drive, thread, drive_size, threads_oversize=0, h
// "thickness" | Thickness of the nut
// "threads_oversize" | amount to oversize the threads (not including $slop)
// Arguments:
// spec = screw specification, e.g. "M5x1" or "#8-32". See [screw naming](#subsection-screw-naming).
// name = screw name, e.g. "M5x1" or "#8-32". See [screw naming](#subsection-screw-naming).
// shape = shape of the nut, either "hex" or "square". Default: "hex"
// ---
// thread = thread type or specification. See [screw pitch](#subsection-standard-screw-pitch). Default: "coarse"
@ -1746,19 +1784,19 @@ function screw_info(spec, head, drive, thread, drive_size, threads_oversize=0, h
// width = width of nut in mm. Default: computed from thread specification
// hole_oversize = amount ot increase diameter of hole in nut. Default: 0
function nut_info(spec, shape, thickness, thread, hole_oversize=0, width, _origin) =
function nut_info(name, shape, thickness, thread, hole_oversize=0, width, _origin) =
assert(is_undef(thickness) || (is_num(thickness) && thickness>0) ||
in_list(_downcase_if_str(thickness),["thin","normal","thick","undersized","din"]),
"thickness must be a positive number of one of \"thin\", \"thick\", \"normal\", \"undersized\", or \"DIN\"")
let(
shape = downcase(default(shape,"hex")),
shape = _downcase_if_str(default(shape,"hex")),
thickness = _downcase_if_str(default(thickness, "normal"))
)
assert(is_string(spec), str("Nut specification must be a string ",spec))
assert(is_string(name), str("Nut nameification must be a string ",name))
assert(in_list(shape, ["hex","square"]), "Nut shape must be \"hex\" or \"square\"")
assert(is_undef(width) || (is_num(width) && width>0), "Specified width must be a positive number")
let(
type = _parse_screw_name(spec),
type = _parse_screw_name(name),
thread = is_undef(thread) || thread==true ? "coarse"
: thread==false || thread=="none" ? 0
: thread,
@ -1766,7 +1804,7 @@ function nut_info(spec, shape, thickness, thread, hole_oversize=0, width, _origi
: type[0]=="metric" ? _nut_info_metric(type[1],type[2], thread, shape, thickness, width)
: []
)
_struct_reset(nutdata, [["name", spec],
_struct_reset(nutdata, [["name", name],
["threads_oversize",hole_oversize],
["width", width],
["origin",_origin]
@ -2057,7 +2095,7 @@ function _screw_info_english(diam, threadcount, head, thread, drive) =
dummy=assert(is_def(entry), str("Screw size ",diam," unsupported for headless screws")),
drive_dims = drive == "hex" ? [["drive_size", INCH*entry[0]], ["drive_depth", INCH*entry[1]]]
: drive == "torx" ? [["drive_size", entry[2]], ["drive_depth", INCH*entry[3]]]
: drive == "slot" ? [["drive_width", INCH*entry[4]], ["drive_depth", INCH*entry[5]]]
: drive == "slot" ? [["drive_size", INCH*entry[4]], ["drive_depth", INCH*entry[5]]]
: []
) concat([["head","none"]], drive_dims)
: head=="hex" ? let(
@ -2149,8 +2187,12 @@ function _screw_info_english(diam, threadcount, head, thread, drive) =
htind = drive=="slot" ? 1 : 2,
entry = struct_val(UTS_pan, diam),
dummy=assert(is_def(entry), str("Screw size ",diam," unsupported for head type \"",head,"\"")),
drive_size = drive=="phillips" ? [["drive_size", entry[3]], ["drive_diameter",INCH*entry[4]],["drive_width",INCH*entry[5]],["drive_depth",INCH*entry[6]]] :
[["drive_width", INCH*entry[7]], ["drive_depth",INCH*entry[8]]])
drive_size = drive=="phillips" ? [["drive_size", entry[3]],
// ["drive_diameter",INCH*entry[4]],
// ["drive_width",INCH*entry[5]],
["drive_depth",INCH*entry[6]]]
: [["drive_size", INCH*entry[7]],
["drive_depth",INCH*entry[8]]])
concat([["head","pan round"], ["head_size", INCH*entry[0]], ["head_height", INCH*entry[htind]]], drive_size)
: head=="button" || head=="round" ? let(
UTS_button = [ // button, hex or torx drive
@ -2195,12 +2237,17 @@ function _screw_info_english(diam, threadcount, head, thread, drive) =
drive_index = drive=="phillips" ? 2 :
drive=="hex" ? 3 :
drive=="torx" ? 4 : undef,
drive_size = drive=="phillips" && head=="round" ? [["drive_size", entry[2]], ["drive_diameter",u_mul(INCH,entry[5])],
["drive_width",INCH*entry[6]],["drive_depth",INCH*entry[7]]] :
drive=="slot" && head=="round" ? [["drive_width", INCH*entry[8]], ["drive_depth",u_mul(INCH,entry[9])]] :
drive=="hex" && head=="button" ? [["drive_size", INCH*entry[drive_index]], ["drive_depth", u_mul(INCH,entry[5])]]:
drive=="torx" && head=="button" ? [["drive_size", entry[drive_index]], ["drive_depth", u_mul(INCH,entry[6])]]:
is_def(drive_index) && head=="button" ? [["drive_size", entry[drive_index]]] : []
drive_size = drive=="phillips" && head=="round" ? [["drive_size", entry[2]],
// ["drive_diameter",u_mul(INCH,entry[5])],
// ["drive_width",INCH*entry[6]],
["drive_depth",INCH*entry[7]]]
: drive=="slot" && head=="round" ? [["drive_size", INCH*entry[8]],
["drive_depth",u_mul(INCH,entry[9])]]
: drive=="hex" && head=="button" ? [["drive_size", INCH*entry[drive_index]],
["drive_depth", u_mul(INCH,entry[5])]]
: drive=="torx" && head=="button" ? [["drive_size", entry[drive_index]],
["drive_depth", u_mul(INCH,entry[6])]]
: is_def(drive_index) && head=="button" ? [["drive_size", entry[drive_index]]] : []
)
concat([["head",head],["head_size",INCH*entry[0]], ["head_height", INCH*entry[1]]],drive_size)
: head=="fillister" ? let(
@ -2221,9 +2268,12 @@ function _screw_info_english(diam, threadcount, head, thread, drive) =
],
entry = struct_val(UTS_fillister, diam),
dummy=assert(is_def(entry), str("Screw size ",diam," unsupported for head type \"",head,"\"")),
drive_size = drive=="phillips" ? [["drive_size", entry[7]], ["drive_diameter",INCH*entry[4]],
["drive_width",INCH*entry[6]],["drive_depth",INCH*entry[5]]] :
drive=="slot"? [["drive_width", INCH*entry[2]], ["drive_depth",INCH*entry[3]]] : []
drive_size = drive=="phillips" ? [["drive_size", entry[7]],
// ["drive_diameter",INCH*entry[4]],
// ["drive_width",INCH*entry[6]],
["drive_depth",INCH*entry[5]]]
: drive=="slot"? [["drive_size", INCH*entry[2]],
["drive_depth",INCH*entry[3]]] : []
)
concat([["head", "fillister"], ["head_size", INCH*entry[0]], ["head_height", INCH*entry[1]]], drive_size)
: starts_with(head,"flat ") || head=="flat" ?
@ -2332,13 +2382,17 @@ function _screw_info_english(diam, threadcount, head, thread, drive) =
: drive=="torx" ? 2
: undef,
drive_dims = small ? (
drive=="phillips" && !undercut ? [["drive_diameter",INCH*entry[2]],
["drive_width",INCH*entry[4]],
["drive_depth",INCH*entry[3]]] :
drive=="phillips" && undercut ? [["drive_diameter",INCH*entry[6]],
["drive_width",INCH*entry[8]],
["drive_depth",INCH*entry[7]]] :
drive=="slot" ? [["drive_width", INCH*entry[5]],
drive=="phillips" && !undercut ? [
// ["drive_diameter",INCH*entry[2]],
// ["drive_width",INCH*entry[4]],
["drive_depth",INCH*entry[3]]
]
: drive=="phillips" && undercut ? [
// ["drive_diameter",INCH*entry[6]],
// ["drive_width",INCH*entry[8]],
["drive_depth",INCH*entry[7]]
]
: drive=="slot" ? [["drive_size", INCH*entry[5]],
["drive_depth", INCH*tipdepth_small]] :
[]
@ -2459,10 +2513,10 @@ function _screw_info_metric(diam, pitch, head, thread, drive) =
[20, [10, undef, undef]],
],
entry = struct_val(metric_setscrew, diam),
dummy=assert(is_def(entry), str("Screw size M",diam," unsupported for headless screws")),
dummy=assert(drive=="none" || is_undef(drive) || is_def(entry), str("Screw size M",diam," unsupported for headless screws")),
drive_dim = drive=="hex" ? [["drive_size", entry[0]], ["drive_depth", diam/2]]
: drive=="torx" ? [["drive_size", entry[1]], ["drive_depth", entry[2]]]
: drive=="slot" ? [["drive_width", entry[3]], ["drive_depth", entry[4]]]
: drive=="slot" ? [["drive_size", entry[3]], ["drive_depth", entry[4]]]
: []
)
concat([["head","none"]], drive_dim)
@ -2542,9 +2596,13 @@ function _screw_info_metric(diam, pitch, head, thread, drive) =
htind = drive=="slot" ? 1 : 2,
entry = struct_val(metric_pan, diam),
dummy=assert(is_def(entry), str("Screw size M",diam," unsupported for headless screws")),
drive_size = drive=="phillips" ? [["drive_size", entry[3]], ["drive_diameter", entry[4]], ["drive_depth",entry[5]], ["drive_width",entry[6]]]
drive_size = drive=="phillips" ? [["drive_size", entry[3]],
//["drive_diameter", entry[4]],
["drive_depth",entry[5]],
//["drive_width",entry[6]]
]
: drive=="torx" ? [["drive_size", entry[9]], ["drive_depth", entry[10]]]
: drive=="slot" ? [["drive_width", entry[7]], ["drive_depth", entry[8]]]
: drive=="slot" ? [["drive_size", entry[7]], ["drive_depth", entry[8]]]
: []
)
concat([["head",type], ["head_size", entry[0]], ["head_height", entry[htind]]], drive_size)
@ -2607,9 +2665,12 @@ function _screw_info_metric(diam, pitch, head, thread, drive) =
drive_dim = head=="button" && drive=="hex" ? [["drive_depth", entry[4]]]
: head=="button" && drive=="torx" ? [["drive_size", entry[5]],["drive_depth", entry[6]]]
: head=="cheese" && drive=="torx" ? [["drive_size", entry[2]],["drive_depth", entry[3]]]
: head=="cheese" && drive=="slot" ? [["drive_width", entry[4]], ["drive_depth", entry[5]]]
: head=="cheese" && drive=="phillips" ? [["drive_diameter", entry[6]], ["drive_depth", entry[7]],
["drive_width", entry[6]/4]] // Fabricated this width value to fill in missing field
: head=="cheese" && drive=="slot" ? [["drive_size", entry[4]], ["drive_depth", entry[5]]]
: head=="cheese" && drive=="phillips" ? [
//["drive_diameter", entry[6]],
["drive_depth", entry[7]],
//["drive_width", entry[6]/4] // Fabricated this width value to fill in missing field
]
:[],
drive_size = is_def(drive_index) ? [["drive_size", entry[drive_index]]] : []
)
@ -2663,8 +2724,12 @@ function _screw_info_metric(diam, pitch, head, thread, drive) =
: !small && drive=="hex" ? 2
: !small && drive=="torx" ? 4
: small && drive=="torx" ? 8 : undef,
drive_dim = small && drive=="phillips" ? [["drive_diameter", entry[3]], ["drive_depth",entry[4]], ["drive_width", entry[5]]]
: small && drive=="slot" ? [["drive_width", entry[6]], ["drive_depth", entry[7]]]
drive_dim = small && drive=="phillips" ? [
// ["drive_diameter", entry[3]],
["drive_depth",entry[4]],
// ["drive_width", entry[5]]
]
: small && drive=="slot" ? [["drive_size", entry[6]], ["drive_depth", entry[7]]]
: drive=="torx" ? [["drive_depth", entry[driveind+1]]]
: !small && drive=="hex" ? [["drive_depth", entry[3]]]
: [],
@ -2717,29 +2782,21 @@ function _validate_nut_spec(spec) =
spec;
function _validate_screw_spec(spec) = let(
//dummy=echo_struct(spec,"Screw Specification"),
systemOK = in_list(struct_val(spec,"system"), ["UTS","ISO"]),
diamOK = _is_positive(struct_val(spec, "diameter")),
pitch = struct_val(spec,"pitch"),
pitchOK = is_undef(pitch) || (is_num(pitch) && pitch>=0),
head = struct_val(spec,"head"),
headOK = head=="none" ||
(in_list(head, ["cheese","pan flat","pan round", "flat", "button","socket","socket ribbed", "fillister","round","hex"]) &&
_is_positive(struct_val(spec, "head_size"))),
flatheadOK = (head!="flat" || _is_positive(struct_val(spec,"head_size_sharp"))),
drive = struct_val(spec, "drive"),
driveOK = is_undef(drive) || drive=="none"
|| (
_is_positive(struct_val(spec, "drive_depth")) &&
(
in_list(drive, ["torx","hex"])
|| (drive=="phillips" && _is_positive(struct_val(spec, "drive_diameter")) &&
_is_positive(struct_val(spec, "drive_width")) &&
_is_positive(struct_val(spec, "drive_width")))
|| (drive=="slot" && _is_positive(struct_val(spec, "drive_width")))
)
)
function _validate_screw_spec(spec) =
let(
//dummy=echo_struct(spec,"Screw Specification"),
systemOK = in_list(struct_val(spec,"system"), ["UTS","ISO"]),
diamOK = _is_positive(struct_val(spec, "diameter")),
pitch = struct_val(spec,"pitch"),
pitchOK = is_undef(pitch) || (is_num(pitch) && pitch>=0),
head = struct_val(spec,"head"),
headOK = head=="none" ||
(in_list(head, ["cheese","pan flat","pan round", "flat", "button","socket","socket ribbed", "fillister","round","hex"]) &&
_is_positive(struct_val(spec, "head_size"))),
flatheadOK = (head!="flat" || _is_positive(struct_val(spec,"head_size_sharp"))),
drive = struct_val(spec, "drive"),
driveOK = is_undef(drive) || drive=="none"
|| (_is_positive(struct_val(spec, "drive_depth")) && _is_positive(struct_val(spec, "drive_size")))
)
assert(systemOK, str("Screw spec has invalid \"system\", ", struct_val(spec,"system"), ". Must be \"ISO\" or \"UTS\""))
assert(diamOK, str("Screw spec has invalid \"diameter\", ", struct_val(spec,"diameter")))
@ -2800,11 +2857,7 @@ http://files.engineering.com/getfile.aspx?folder=76fb0d5e-1fff-4c49-87a5-0597947
*/
// To do list
//
// Is there no way to create a mask for making threaded holes? This seems to be missing.
//
// Metric hex engagement:
// https://www.bayoucitybolt.com/socket-head-cap-screws-metric.html
//
// Torx drive depth for UTS and ISO (at least missing for "flat small", which means you can't select torx for this head type)
@ -2812,9 +2865,6 @@ http://files.engineering.com/getfile.aspx?folder=76fb0d5e-1fff-4c49-87a5-0597947
// https://www.fasteners.eu/tech-info/ISO/7721-2/
//
// How do you insert a threaded hole into a model?
// Default nut thickness
//
// JIS
//https://www.garagejournal.com/forum/media/jis-b-4633-vs-iso-8764-1-din-5260-ph.84492/
@ -2826,38 +2876,28 @@ http://files.engineering.com/getfile.aspx?folder=76fb0d5e-1fff-4c49-87a5-0597947
// thread standards:
// https://www.gewinde-normen.de/en/index.html
///////////////////////////////////////////////////////
//
// how to make screw mask: examples (e.g. for clearance hole w/ countersink)
// how to make a screw hole (a mask function?)
//
/////////////////////////////////////////////////////////////////////////////////////////*
/////////////////////////////////////////////////////////////////////////////////////////*
/////////////////////////////////////////////////////////////////////////////////////////*
///
/// TODO list:
///
/// need to make holes at actual size instead of nominal?
/// or relative to actual size?
/// That means I need to preserve thread= to specify this
/// torx depth for UTS pan head
/// $fn control
/// phillips driver spec with ph# is confusing since it still looks up depth in tables
/// and can give an error if it's not found
/// torx depths missing for pan head
/// support for square drive? (It's in the ASME standard)
///
/////////////////////////////////////////////////////////////////////////////////////////*
/////////////////////////////////////////////////////////////////////////////////////////*
/////////////////////////////////////////////////////////////////////////////////////////*
// vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap
/*
TODO list:
anchoring for counterbores OK?: counterbore ignored for flatheads
counterbore is treated as the head for regular heads
for flathead counterbore is ignored. Need an anchor that gives
access to counterbore for the flathead case but also the top of the head(?)
anchoring for other heads: using bounding cylinder
hex head anchoring OK?
need to make holes at actual size instead of nominal?
or relative to actual size?
That means I need to preserve thread= to specify this
shoulder screws: generally how to handle these?
torx depth for UTS pan head
$fn control
phillips driver spec with ph# is confusing since it still looks up depth in tables
and can give an error if it's not found
phillips code just uses depth, not width and slot width; maybe remove excess data?
torx depths missing for pan head
support for square drive? (It's in the ASME standard)
proper support for nuts, nut traps
*/

View file

@ -2971,8 +2971,9 @@ module path_text(path, text, font, size, thickness, lettersize, offset=0, revers
dummy1 = assert(textlength<=path_length(path),"Path is too short for the text");
start = center ? (path_length(path) - textlength)/2 : 0;
pts = path_cut_points(path, add_scalar([0, each cumsum(lsize)],start+lsize[0]/2), direction=true);
pts = _path_cut_points(path, add_scalar([0, each cumsum(lsize)],start+lsize[0]/2), direction=true);
usernorm = is_def(normal);
usetop = is_def(top);

169
skin.scad
View file

@ -1104,9 +1104,9 @@ module spiral_sweep(poly, h, r, turns=1, higbee, center, r1, r2, d, d1, d2, higb
// Function&Module: path_sweep()
// Usage: As module
// path_sweep(shape, path, [method], [normal=], [closed=], [twist=], [twist_by_length=], [symmetry=], [last_normal=], [tangent=], [uniform=], [relaxed=], [caps=], [style=], [convexity=], [anchor=], [cp=], [spin=], [orient=], [atype=]) [ATTACHMENTS];
// path_sweep(shape, path, [method], [normal=], [closed=], [twist=], [twist_by_length=], [symmetry=], [scale=], [scale_by_length=], [last_normal=], [tangent=], [uniform=], [relaxed=], [caps=], [style=], [convexity=], [anchor=], [cp=], [spin=], [orient=], [atype=]) [ATTACHMENTS];
// Usage: As function
// vnf = path_sweep(shape, path, [method], [normal=], [closed=], [twist=], [twist_by_length=], [symmetry=], [last_normal=], [tangent=], [uniform=], [relaxed=], [caps=], [style=], [transforms=], [anchor=], [cp=], [spin=], [orient=], [atype=]);
// vnf = path_sweep(shape, path, [method], [normal=], [closed=], [twist=], [twist_by_length=], [symmetry=], [scale=], [scale_by_length=], [last_normal=], [tangent=], [uniform=], [relaxed=], [caps=], [style=], [transforms=], [anchor=], [cp=], [spin=], [orient=], [atype=]);
// Description:
// Takes as input `shape`, a 2D polygon path (list of points), and `path`, a 2d or 3d path (also a list of points)
// and constructs a polyhedron by sweeping the shape along the path. When run as a module returns the polyhedron geometry.
@ -1223,6 +1223,11 @@ module spiral_sweep(poly, h, r, turns=1, higbee, center, r1, r2, d, d1, d2, higb
// the cross section orientation. Specifying a list of normal vectors gives you complete control over the orientation of your
// cross sections and can be useful if you want to position your model to be on the surface of some solid.
// .
// You can also apply scaling to the profile along the path. You can give a list of scalar scale factors or a list of 2-vector scale.
// In the latter scale the x and y scales of the profile are scaled separately before the profile is placed onto the path. For non-closed
// paths you can also give a single scale value or a 2-vector which is treated as the final scale. The intermediate sections
// are then scaled by linear interpolation either relative to length (if scale_by_length is true) or by point count otherwise.
// .
// You can use set `transforms` to true to return a list of transformation matrices instead of the swept shape. In this case, you can
// often omit shape entirely. The exception is when `closed=true` and you are using the "incremental" method. In this case, `path_sweep`
// uses the shape to correct for twist when the shape closes on itself, so you must include a valid shape.
@ -1234,7 +1239,10 @@ module spiral_sweep(poly, h, r, turns=1, higbee, center, r1, r2, d, d1, d2, higb
// normal = normal vector for initializing the incremental method, or for setting normals with method="manual". Default: UP if the path makes an angle lower than 45 degrees to the xy plane, BACK otherwise.
// closed = path is a closed loop. Default: false
// twist = amount of twist to add in degrees. For closed sweeps must be a multiple of 360/symmetry. Default: 0
// twist_by_length = if true then interpolate twist based on the path length of the path. If false interoplate based on point count. Default: true
// symmetry = symmetry of the shape when closed=true. Allows the shape to join with a 360/symmetry rotation instead of a full 360 rotation. Default: 1
// scale = Amount to scale the profiles. If you give a scalar the scale starts at 1 and ends at your specified value. The same is true for a 2-vector, but x and y are scaled separately. You can also give a vector of values, one for each path point, and you can give a list of 2-vectors that give the x and y scales of your profile for every point on the path (a Nx2 matrix for a path of length N. Default: 1 (no scaling)
// scale_by_length = if true then interpolate scale based on the path length of the path. If false interoplate based on point count. Default: true
// last_normal = normal to last point in the path for the "incremental" method. Constrains the orientation of the last cross section if you supply it.
// uniform = if set to false then compute tangents using the uniform=false argument, which may give better results when your path is non-uniformly sampled. This argument is passed to {{path_tangents()}}. Default: true
// tangent = a list of tangent vectors in case you need more accuracy (particularly at the end points of your curve)
@ -1491,6 +1499,18 @@ module spiral_sweep(poly, h, r, turns=1, higbee, center, r1, r2, d, d1, d2, higb
// outside = [for(i=[0:len(trans)-1]) trans[i]*scale(lerp(1,1.5,i/(len(trans)-1)))];
// inside = [for(i=[len(trans)-1:-1:0]) trans[i]*scale(lerp(1.1,1.4,i/(len(trans)-1)))];
// sweep(shape, concat(outside,inside),closed=true);
// Example(NoScales): An easier way to scale your model is to use the scale parameter.
// elliptic_arc = xscale(2, p=arc($fn=64,angle=[0,180], r=3));
// path_sweep(pentagon(r=1), path3d(elliptic_arc), scale=2);
// Example(NoScales): Scaling only in the y direction of the profile (z direction in the model in this case)
// elliptic_arc = xscale(2, p=arc($fn=64,angle=[0,180], r=3));
// path_sweep(rect(2), path3d(elliptic_arc), scale=[1,2]);
// Example(NoScales): Specifying scale at every point for a closed path
// N=64;
// path = circle(r=5, $fn=64);
// theta = lerpn(0,360,N,endpoint=false);
// scale = [for(t=theta) sin(6*t)/5+1];
// path_sweep(rect(2), path3d(path), closed=true, scale=scale);
// Example(Med,NoScales): Using path_sweep on a region
// rgn1 = [for (d=[10:10:60]) circle(d=d,$fn=8)];
// rgn2 = [square(30,center=false)];
@ -1517,17 +1537,17 @@ module spiral_sweep(poly, h, r, turns=1, higbee, center, r1, r2, d, d1, d2, higb
// method="manual", normal=UP);
// }
module path_sweep(shape, path, method="incremental", normal, closed, twist=0, twist_by_length=true,
module path_sweep(shape, path, method="incremental", normal, closed, twist=0, twist_by_length=true, scale=1, scale_by_length=true,
symmetry=1, last_normal, tangent, uniform=true, relaxed=false, caps, style="min_edge", convexity=10,
anchor="origin",cp="centroid",spin=0, orient=UP, atype="hull",profiles=false,width=1)
{
dummy = assert(is_region(shape) || is_path(shape,2), "shape must be a 2D path or region");
vnf = path_sweep(shape, path, method, normal, closed, twist, twist_by_length,
vnf = path_sweep(shape, path, method, normal, closed, twist, twist_by_length, scale, scale_by_length,
symmetry, last_normal, tangent, uniform, relaxed, caps, style);
if (profiles){
assert(in_list(atype, _ANCHOR_TYPES), "Anchor type must be \"hull\" or \"intersect\"");
tran = path_sweep(shape, path, method, normal, closed, twist, twist_by_length,
tran = path_sweep(shape, path, method, normal, closed, twist, twist_by_length, scale, scale_by_length,
symmetry, last_normal, tangent, uniform, relaxed,transforms=true);
rshape = is_path(shape) ? [path3d(shape)]
: [for(s=shape) path3d(s)];
@ -1542,11 +1562,11 @@ module path_sweep(shape, path, method="incremental", normal, closed, twist=0, tw
}
function path_sweep(shape, path, method="incremental", normal, closed, twist=0, twist_by_length=true,
function path_sweep(shape, path, method="incremental", normal, closed, twist=0, twist_by_length=true, scale=1, scale_by_length=true,
symmetry=1, last_normal, tangent, uniform=true, relaxed=false, caps, style="min_edge", transforms=false,
anchor="origin",cp="centroid",spin=0, orient=UP, atype="hull") =
is_1region(path) ? path_sweep(shape=shape,path=path[0], method=method, normal=normal, closed=default(closed,true),
twist=twist, twist_by_length=twist_by_length, symmetry=symmetry, last_normal=last_normal,
is_1region(path) ? path_sweep(shape=shape,path=path[0], method=method, normal=normal, closed=default(closed,true),
twist=twist, scale=scale, scale_by_length=scale_by_length, twist_by_length=twist_by_length, symmetry=symmetry, last_normal=last_normal,
tangent=tangent, uniform=uniform, relaxed=relaxed, caps=caps, style=style, transforms=transforms,
anchor=anchor, cp=cp, spin=spin, orient=orient, atype=atype) :
let(closed=default(closed,false))
@ -1565,7 +1585,9 @@ function path_sweep(shape, path, method="incremental", normal, closed, twist=0,
capsOK = is_bool(caps) || is_bool_list(caps,2),
fullcaps = is_bool(caps) ? [caps,caps] : caps,
normalOK = is_undef(normal) || (method!="natural" && is_vector(normal,3))
|| (method=="manual" && same_shape(normal,path))
|| (method=="manual" && same_shape(normal,path)),
scaleOK = scale==1 || ((is_num(scale) || is_vector(scale,2)) && !closed) || is_vector(scale,len(path)) || is_matrix(scale,len(path),2)
)
assert(normalOK, method=="natural" ? "Cannot specify normal with the \"natural\" method"
: method=="incremental" ? "Normal with \"incremental\" method must be a 3-vector"
@ -1574,79 +1596,88 @@ function path_sweep(shape, path, method="incremental", normal, closed, twist=0,
assert(!closed || !caps, "Cannot make closed shape with caps")
assert(is_undef(normal) || (is_vector(normal) && len(normal)==3) || (is_path(normal) && len(normal)==len(path) && len(normal[0])==3), "Invalid normal specified")
assert(is_undef(tangent) || (is_path(tangent) && len(tangent)==len(path) && len(tangent[0])==3), "Invalid tangent specified")
assert(scaleOK,str("Incompatible or invalid scale",closed?" for closed path":"",": must be ", closed?"":"a scalar, a 2-vector, ",
"a vector of length ",len(path)," or a ",len(path),"x2 matrix of scales"))
let(
scale = !(is_num(scale) || is_vector(scale,2)) ? scale
: let(s=is_num(scale) ? [scale,scale] : scale)
!scale_by_length ? lerpn([1,1],s,len(path))
: lerp([1,1],s, path_length_fractions(path,false)),
scale_list = [for(s=scale) scale(s),if (closed) scale(scale[0])],
tangents = is_undef(tangent) ? path_tangents(path,uniform=uniform,closed=closed) : [for(t=tangent) unit(t)],
normal = is_path(normal) ? [for(n=normal) unit(n)] :
is_def(normal) ? unit(normal) :
method =="incremental" && abs(tangents[0].z) > 1/sqrt(2) ? BACK : UP,
normals = is_path(normal) ? normal : repeat(normal,len(path)),
pathfrac = twist_by_length ? path_length_fractions(path, closed) : [for(i=[0:1:len(path)]) i / (len(path)-(closed?0:1))],
tpathfrac = twist_by_length ? path_length_fractions(path, closed) : [for(i=[0:1:len(path)]) i / (len(path)-(closed?0:1))],
spathfrac = scale_by_length ? path_length_fractions(path, closed) : [for(i=[0:1:len(path)]) i / (len(path)-(closed?0:1))],
L = len(path),
transform_list =
method=="incremental" ?
let(rotations =
[for( i = 0,
ynormal = normal - (normal * tangents[0])*tangents[0],
rotation = frame_map(y=ynormal, z=tangents[0])
;
i < len(tangents) + (closed?1:0) ;
rotation = i<len(tangents)-1+(closed?1:0)? rot(from=tangents[i],to=tangents[(i+1)%L])*rotation : undef,
i=i+1
)
rotation],
// The mismatch is the inverse of the last transform times the first one for the closed case, or the inverse of the
// desired final transform times the realized final transform in the open case. Note that when closed==true the last transform
// is a actually looped around and applies to the first point position, so if we got back exactly where we started
// then it will be the identity, but we might have accumulated some twist which will show up as a rotation around the
// X axis. Similarly, in the closed==false case the desired and actual transformations can only differ in the twist,
// so we can need to calculate the twist angle so we can apply a correction, which we distribute uniformly over the whole path.
reference_rot = closed ? rotations[0] :
is_undef(last_normal) ? last(rotations) :
let(
last_tangent = last(tangents),
lastynormal = last_normal - (last_normal * last_tangent) * last_tangent
)
frame_map(y=lastynormal, z=last_tangent),
mismatch = transpose(last(rotations)) * reference_rot,
correction_twist = atan2(mismatch[1][0], mismatch[0][0]),
// Spread out this extra twist over the whole sweep so that it doesn't occur
// abruptly as an artifact at the last step.
twistfix = correction_twist%(360/symmetry),
adjusted_final = !closed ? undef :
translate(path[0]) * rotations[0] * zrot(-correction_twist+correction_twist%(360/symmetry)-twist)
) [for(i=idx(path)) translate(path[i]) * rotations[i] * zrot((twistfix-twist)*pathfrac[i]), if(closed) adjusted_final] :
method=="manual" ?
[for(i=[0:L-(closed?0:1)]) let(
ynormal = relaxed ? normals[i%L] : normals[i%L] - (normals[i%L] * tangents[i%L])*tangents[i%L],
znormal = relaxed ? tangents[i%L] - (normals[i%L] * tangents[i%L])*normals[i%L] : tangents[i%L],
rotation = frame_map(y=ynormal, z=znormal)
unscaled_transform_list =
method=="incremental" ?
let(rotations =
[for( i = 0,
ynormal = normal - (normal * tangents[0])*tangents[0],
rotation = frame_map(y=ynormal, z=tangents[0])
;
i < len(tangents) + (closed?1:0) ;
rotation = i<len(tangents)-1+(closed?1:0)? rot(from=tangents[i],to=tangents[(i+1)%L])*rotation : undef,
i=i+1
)
rotation],
// The mismatch is the inverse of the last transform times the first one for the closed case, or the inverse of the
// desired final transform times the realized final transform in the open case. Note that when closed==true the last transform
// is a actually looped around and applies to the first point position, so if we got back exactly where we started
// then it will be the identity, but we might have accumulated some twist which will show up as a rotation around the
// X axis. Similarly, in the closed==false case the desired and actual transformations can only differ in the twist,
// so we can need to calculate the twist angle so we can apply a correction, which we distribute uniformly over the whole path.
reference_rot = closed ? rotations[0] :
is_undef(last_normal) ? last(rotations) :
let(
last_tangent = last(tangents),
lastynormal = last_normal - (last_normal * last_tangent) * last_tangent
)
frame_map(y=lastynormal, z=last_tangent),
mismatch = transpose(last(rotations)) * reference_rot,
correction_twist = atan2(mismatch[1][0], mismatch[0][0]),
// Spread out this extra twist over the whole sweep so that it doesn't occur
// abruptly as an artifact at the last step.
twistfix = correction_twist%(360/symmetry),
adjusted_final = !closed ? undef :
translate(path[0]) * rotations[0] * zrot(-correction_twist+correction_twist%(360/symmetry)-twist)
) [for(i=idx(path)) translate(path[i]) * rotations[i] * zrot((twistfix-twist)*tpathfrac[i]), if(closed) adjusted_final]
: method=="manual" ?
[for(i=[0:L-(closed?0:1)]) let(
ynormal = relaxed ? normals[i%L] : normals[i%L] - (normals[i%L] * tangents[i%L])*tangents[i%L],
znormal = relaxed ? tangents[i%L] - (normals[i%L] * tangents[i%L])*normals[i%L] : tangents[i%L],
rotation = frame_map(y=ynormal, z=znormal)
)
assert(approx(ynormal*znormal,0),str("Supplied normal is parallel to the path tangent at point ",i))
translate(path[i%L])*rotation*zrot(-twist*tpathfrac[i])
]
: method=="natural" ? // map x axis of shape to the path normal, which points in direction of curvature
let (pathnormal = path_normals(path, tangents, closed))
assert(all_defined(pathnormal),"Natural normal vanishes on your curve, select a different method")
let( testnormals = [for(i=[0:len(pathnormal)-1-(closed?1:2)]) pathnormal[i]*select(pathnormal,i+2)],
a=[for(i=idx(testnormals)) testnormals[i]<.5 ? echo(str("Big change at index ",i," pn=",pathnormal[i]," pn2= ",select(pathnormal,i+2))):0],
dummy = min(testnormals) < .5 ? echo("WARNING: ***** Abrupt change in normal direction. Consider a different method in path_sweep() *****") :0
)
assert(approx(ynormal*znormal,0),str("Supplied normal is parallel to the path tangent at point ",i))
translate(path[i%L])*rotation*zrot(-twist*pathfrac[i]),
] :
method=="natural" ? // map x axis of shape to the path normal, which points in direction of curvature
let (pathnormal = path_normals(path, tangents, closed))
assert(all_defined(pathnormal),"Natural normal vanishes on your curve, select a different method")
let( testnormals = [for(i=[0:len(pathnormal)-1-(closed?1:2)]) pathnormal[i]*select(pathnormal,i+2)],
a=[for(i=idx(testnormals)) testnormals[i]<.5 ? echo(str("Big change at index ",i," pn=",pathnormal[i]," pn2= ",select(pathnormal,i+2))):0],
dummy = min(testnormals) < .5 ? echo("WARNING: ***** Abrupt change in normal direction. Consider a different method *****") :0
)
[for(i=[0:L-(closed?0:1)]) let(
rotation = frame_map(x=pathnormal[i%L], z=tangents[i%L])
)
translate(path[i%L])*rotation*zrot(-twist*pathfrac[i])
] :
assert(false,"Unknown method or no method given")[], // unknown method
ends_match = !closed ? true
[for(i=[0:L-(closed?0:1)]) let(
rotation = frame_map(x=pathnormal[i%L], z=tangents[i%L])
)
translate(path[i%L])*rotation*zrot(-twist*tpathfrac[i])
]
: assert(false,"Unknown method or no method given"), // unknown method
transform_list = v_mul(unscaled_transform_list, scale_list),
ends_match = !closed ? true
: let( rshape = is_path(shape) ? [path3d(shape)]
: [for(s=shape) path3d(s)]
)
are_regions_equal(apply(transform_list[0], rshape),
apply(transform_list[L], rshape)),
dummy = ends_match ? 0 : echo("WARNING: ***** The points do not match when closing the model *****")
)
transforms ? transform_list
: sweep(is_path(shape)?clockwise_polygon(shape):shape, transform_list, closed=false, caps=fullcaps,style=style,
dummy = ends_match ? 0 : echo("WARNING: ***** The points do not match when closing the model in path_sweep() *****")
)
transforms ? transform_list
: sweep(is_path(shape)?clockwise_polygon(shape):shape, transform_list, closed=false, caps=fullcaps,style=style,
anchor=anchor,cp=cp,spin=spin,orient=orient,atype=atype);

View file

@ -13,7 +13,7 @@
// Module: threaded_rod()
// Usage:
// threaded_rod(d, l, pitch, [internal=], ...) [ATTACHMENTS];
// threaded_rod(d, l|length, pitch, [internal=], ...) [ATTACHMENTS];
// Description:
// Constructs a standard ISO (metric) or UTS (English) threaded rod. These threads are close to triangular,
// with a 60 degree thread angle. You can give the outer diameter and get the "basic form" or you can
@ -22,7 +22,7 @@
// using the specification parameters.
// Arguments:
// d = Outer diameter of threaded rod, or a triplet of [d_min, d_pitch, d_major].
// l = length of threaded rod.
// l / length = length of threaded rod.
// pitch = Length between threads.
// ---
// left_handed = if true, create left-handed threads. Default = false
@ -74,7 +74,7 @@ module threaded_rod(
left_handed=false,
bevel,bevel1,bevel2,starts=1,
internal=false,
d1, d2,
d1, d2, length,
higbee, higbee1, higbee2,
anchor, spin, orient
) {
@ -114,7 +114,7 @@ module threaded_rod(
profile=profile,starts=starts,
left_handed=left_handed,
bevel=bevel,bevel1=bevel1,bevel2=bevel2,
internal=internal,
internal=internal, length=length,
higbee=higbee,
higbee1=higbee1,
higbee2=higbee2,
@ -128,21 +128,25 @@ module threaded_rod(
// Module: threaded_nut()
// Usage:
// threaded_nut(nutwidth, id, h, pitch,...) [ATTACHMENTS];
// threaded_nut(nutwidth, id, h|height|thickness, pitch,...) [ATTACHMENTS];
// Description:
// Constructs a hex nut or square nut for an ISO (metric) or UTS (English) threaded rod.
// Arguments:
// nutwidth = flat to flat width of nut
// id = diameter of threaded rod to screw onto.
// h = height/thickness of nut.
// pitch = Length between threads.
// h / height / thickness = height/thickness of nut.
// pitch = Distance between threads, or zero for no threads.
// ---
// shape = specifies shape of nut, either "hex" or "square". Default: "hex"
// left_handed = if true, create left-handed threads. Default = false
// starts = The number of lead starts. Default: 1
// bevel = if true, bevel the thread ends. Default: false
// bevel1 = if true bevel the bottom end.
// bevel2 = if true bevel the top end.
// bevel = if true, bevel the outside of the nut.
// bevel1 = if true, bevel the outside of the nut bottom.
// bevel2 = if true, bevel the outside of the nut top.
// bevang = set the angle for the outside nut bevel. Default: 15
// ibevel = if true, bevel the inside (the hole). Default: true
// ibevel1 = if true bevel the inside, bottom end.
// ibevel2 = if true bevel the inside, top end.
// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#subsection-anchor). Default: `CENTER`
// spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#subsection-spin). Default: `0`
// orient = Vector to rotate top towards, after spin. See [orient](attachments.scad#subsection-orient). Default: `UP`
@ -152,18 +156,20 @@ module threaded_rod(
// threaded_nut(nutwidth=16, id=8, h=8, pitch=1.25, left_handed=true, bevel=true, $slop=0.1, $fa=1, $fs=1);
function threaded_nut(
nutwidth, id, h,
pitch, starts=1, shape, left_handed=false, bevel, bevel1, bevel2, id1,id2,
pitch, starts=1, shape="hex", left_handed=false, bevel, bevel1, bevel2, id1,id2,
ibevel1, ibevel2, ibevel, bevang=15, thickness, height,
anchor, spin, orient
)=no_function("threaded_nut");
module threaded_nut(
nutwidth, id, h,
pitch, starts=1, shape="hex", left_handed=false, bevel, bevel1, bevel2, id1,id2,
ibevel1, ibevel2, ibevel, bevang=15, thickness, height,
anchor, spin, orient
) {
dummy1=
assert(all_positive(pitch), "Nut pitch must be positive")
assert(all_positive(id), "Nut inner diameter must be positive")
assert(all_positive(h),"Nut thickness must be positive");
assert(all_nonnegative(pitch), "Nut pitch must be nonnegative")
assert(all_positive(id), "Nut inner diameter must be positive")
assert(all_positive(h),"Nut thickness must be positive");
basic = is_num(id) || is_undef(id) || is_def(id1) || is_def(id2);
dummy2 = assert(basic || is_vector(id,3));
depth = basic ? cos(30) * 5/8
@ -191,6 +197,8 @@ module threaded_nut(
profile=profile,starts=starts,shape=shape,
left_handed=left_handed,
bevel=bevel,bevel1=bevel1,bevel2=bevel2,
ibevel1=ibevel1, ibevel2=ibevel2, ibevel=ibevel,
height=height, thickness=thickness, bevang=bevang,
anchor=anchor, spin=spin,
orient=orient
) children();
@ -201,7 +209,7 @@ module threaded_nut(
// Module: trapezoidal_threaded_rod()
// Usage:
// trapezoidal_threaded_rod(d, l, pitch, [thread_angle], [thread_depth], [internal=], ...) [ATTACHMENTS];
// trapezoidal_threaded_rod(d, l|length, pitch, [thread_angle], [thread_depth], [internal=], ...) [ATTACHMENTS];
// Description:
// Constructs a threaded rod with a symmetric trapezoidal thread. Trapezoidal threads are used for lead screws because
// they are one of the strongest symmetric profiles. This tooth shape is stronger than a similarly
@ -237,7 +245,7 @@ module threaded_nut(
// }
// Arguments:
// d = Outer diameter of threaded rod.
// l = Length of threaded rod.
// l / length = Length of threaded rod.
// pitch = Thread spacing.
// thread_angle = Angle between two thread faces. Default: 30
// thread_depth = Depth of threads. Default: pitch/2
@ -253,7 +261,6 @@ module threaded_nut(
// higbee = Length to taper thread ends over. Default: 0 (No higbee thread tapering)
// higbee1 = Length to taper bottom thread end over.
// higbee2 = Length to taper top thread end over.
// center = If given, overrides `anchor`. A true value sets `anchor=CENTER`, false sets `anchor=UP`.
// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#subsection-anchor). Default: `CENTER`
// spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#subsection-spin). Default: `0`
// orient = Vector to rotate top towards, after spin. See [orient](attachments.scad#subsection-orient). Default: `UP`
@ -271,7 +278,7 @@ module threaded_nut(
// trapezoidal_threaded_rod(d=60, l=16, pitch=8, thread_depth=3, thread_angle=90, left_handed=true, $fa=2, $fs=2);
// trapezoidal_threaded_rod(d=60, l=16, pitch=8, thread_depth=3, thread_angle=90, left_handed=true, starts=4, $fa=2, $fs=2);
// trapezoidal_threaded_rod(d=16, l=40, pitch=2, thread_angle=60);
// trapezoidal_threaded_rod(d=25, l=40, pitch=10, thread_depth=8/3, thread_angle=100, starts=4, center=false, $fa=2, $fs=2);
// trapezoidal_threaded_rod(d=25, l=40, pitch=10, thread_depth=8/3, thread_angle=100, starts=4, anchor=BOT, $fa=2, $fs=2);
// trapezoidal_threaded_rod(d=50, l=35, pitch=8, thread_angle=60, starts=11, higbee=10,$fn=120);
// Example(Med): Using as a Mask to Make Internal Threads
// bottom_half() difference() {
@ -284,10 +291,10 @@ function trapezoidal_threaded_rod(
thread_depth=undef,
left_handed=false,
bevel,bevel1,bevel2,
starts=1,
starts=1, length,
internal=false,
higbee, higbee1, higbee2,d1,d2,
center, anchor, spin, orient
anchor, spin, orient
) = no_function("trapezoidal_threaded_rod");
module trapezoidal_threaded_rod(
d, l, pitch,
@ -295,10 +302,10 @@ module trapezoidal_threaded_rod(
thread_depth=undef,
left_handed=false,
bevel,bevel1,bevel2,
starts=1,
starts=1, length,
internal=false,
higbee, higbee1, higbee2,d1,d2,
center, anchor, spin, orient
anchor, spin, orient
) {
dummy0 = assert(all_positive(pitch));
dummy1 = assert(thread_angle>=0 && thread_angle<180);
@ -316,14 +323,14 @@ module trapezoidal_threaded_rod(
];
generic_threaded_rod(d=d,l=l,pitch=pitch,profile=profile,
left_handed=left_handed,bevel=bevel,bevel1=bevel1,bevel2=bevel2,starts=starts,internal=internal,d1=d1,d2=d2,
higbee=higbee,higbee1=higbee1,higbee2=higbee2,center=center,anchor=anchor,spin=spin,orient=orient)
higbee=higbee,higbee1=higbee1,higbee2=higbee2,anchor=anchor,spin=spin,orient=orient,length=length)
children();
}
// Module: trapezoidal_threaded_nut()
// Usage:
// trapezoidal_threaded_nut(nutwidth, id, h, pitch, [thread_angle], [thread_depth], ...) [ATTACHMENTS];
// trapezoidal_threaded_nut(nutwidth, id, h|height|thickness, pitch, [thread_angle], [thread_depth], ...) [ATTACHMENTS];
// Description:
// Constructs a hex nut or square nut for a symmetric trapzoidal threaded rod.
// By default produces the nominal dimensions
@ -332,7 +339,7 @@ module trapezoidal_threaded_rod(
// Arguments:
// nutwidth = flat to flat width of nut
// id = diameter of threaded rod to screw onto.
// h = height/thickness of nut.
// h / height / thickness = height/thickness of nut.
// pitch = Thread spacing.
// thread_angle = Angle between two thread faces. Default: 30
// thread_depth = Depth of the threads. Default: pitch/2
@ -340,9 +347,13 @@ module trapezoidal_threaded_rod(
// shape = specifies shape of nut, either "hex" or "square". Default: "hex"
// left_handed = if true, create left-handed threads. Default = false
// starts = The number of lead starts. Default = 1
// bevel = if true, bevel the thread ends. Default: false
// bevel1 = if true bevel the bottom end.
// bevel2 = if true bevel the top end.
// bevel = if true, bevel the outside of the nut.
// bevel1 = if true, bevel the outside of the nut bottom.
// bevel2 = if true, bevel the outside of the nut top.
// bevang = set the angle for the outside nut bevel. Default: 15
// ibevel = if true, bevel the inside (the hole). Default: true
// ibevel1 = if true bevel the inside, bottom end.
// ibevel2 = if true bevel the inside, top end.
// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#subsection-anchor). Default: `CENTER`
// spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#subsection-spin). Default: `0`
// orient = Vector to rotate top towards, after spin. See [orient](attachments.scad#subsection-orient). Default: `UP`
@ -352,16 +363,21 @@ module trapezoidal_threaded_rod(
// trapezoidal_threaded_nut(nutwidth=16, id=8, h=8, pitch=2, bevel=true, $slop=0.05, anchor=UP);
// trapezoidal_threaded_nut(nutwidth=17.4, id=10, h=10, pitch=2, $slop=0.1, left_handed=true);
// trapezoidal_threaded_nut(nutwidth=17.4, id=10, h=10, pitch=2, starts=3, $fa=1, $fs=1, $slop=0.15);
// trapezoidal_threaded_nut(nutwidth=17.4, id=10, h=10, pitch=0, $slop=0.2); // No threads
function trapezoidal_threaded_nut(
d, l, pitch,
nutwidth,
id,
h,
pitch,
thread_angle=30,
thread_depth=undef, shape,
thread_depth, shape="hex",
left_handed=false,
bevel,bevel1,bevel2,
starts=1,
internal=false,
higbee, higbee1, higbee2,d1,d2,
center, anchor, spin, orient
bevel,bevel1,bevel2,bevang=15,
ibevel1,ibevel2,ibevel,
thickness,height,
id1,id2,
anchor, spin, orient
) = no_function("trapezoidal_threaded_nut");
module trapezoidal_threaded_nut(
nutwidth,
@ -372,14 +388,16 @@ module trapezoidal_threaded_nut(
thread_depth, shape="hex",
left_handed=false,
starts=1,
bevel,bevel1,bevel2,
bevel,bevel1,bevel2,bevang=15,
ibevel1,ibevel2,ibevel,
thickness,height,
id1,id2,
anchor, spin, orient
) {
dummy1 = assert(pitch>0 && thread_angle>=0 && thread_angle<180);
dummy1 = assert(is_num(pitch) && pitch>=0 && thread_angle>=0 && thread_angle<180);
depth = first_defined([thread_depth, pitch/2]);
pa_delta = 0.5*depth*tan(thread_angle/2) / pitch;
dummy2 = assert(pa_delta<1/4, "Specified thread geometry is impossible");
dummy2 = assert(pitch==0 || pa_delta<1/4, "Specified thread geometry is impossible");
rr1 = -depth/pitch;
z1 = 1/4-pa_delta;
z2 = 1/4+pa_delta;
@ -391,6 +409,7 @@ module trapezoidal_threaded_nut(
];
generic_threaded_nut(nutwidth=nutwidth,id=id,h=h,pitch=pitch,profile=profile,id1=id1,id2=id2,
shape=shape,left_handed=left_handed,bevel=bevel,bevel1=bevel1,bevel2=bevel2,starts=starts,
ibevel=ibevel,ibevel1=ibevel1,ibevel2=ibevel2,bevang=bevang,height=height,thickness=thickness,
anchor=anchor,spin=spin,orient=orient)
children();
}
@ -398,13 +417,13 @@ module trapezoidal_threaded_nut(
// Module: acme_threaded_rod()
// Usage:
// acme_threaded_rod(d, l, tpi|pitch=, [internal=], ...) [ATTACHMENTS];
// acme_threaded_rod(d, l|length, tpi|pitch=, [internal=], ...) [ATTACHMENTS];
// Description:
// Constructs an ACME trapezoidal threaded screw rod. This form has a 29 degree thread angle with a
// symmetric trapezoidal thread.
// Arguments:
// d = Outer diameter of threaded rod.
// l = length of threaded rod.
// l / length = length of threaded rod.
// tpi = threads per inch.
// ---
// pitch = thread spacing (alternative to tpi)
@ -432,7 +451,7 @@ function acme_threaded_rod(
starts=1,
left_handed=false,
bevel,bevel1,bevel2,
internal=false,
internal=false, length,
higbee, higbee1, higbee2,
anchor, spin, orient
) = no_function("acme_threaded_rod");
@ -441,7 +460,7 @@ module acme_threaded_rod(
starts=1,
left_handed=false,
bevel,bevel1,bevel2,
internal=false,
internal=false, length,
higbee, higbee1, higbee2,
anchor, spin, orient
) {
@ -454,7 +473,7 @@ module acme_threaded_rod(
starts=starts,
left_handed=left_handed,
bevel=bevel,bevel1=bevel1,bevel2=bevel2,
internal=internal,
internal=internal, length=length,
anchor=anchor,
spin=spin,
orient=orient
@ -465,22 +484,26 @@ module acme_threaded_rod(
// Module: acme_threaded_nut()
// Usage:
// acme_threaded_nut(nutwidth, id, h, tpi|pitch=, [shape=], ...) [ATTACHMENTS];
// acme_threaded_nut(nutwidth, id, h|height|thickness, tpi|pitch=, [shape=], ...) [ATTACHMENTS];
// Description:
// Constructs a hexagonal or square nut for an ACME threaded screw rod.
// Arguments:
// nutwidth = flat to flat width of nut.
// id = diameter of threaded rod to screw onto.
// h = height/thickness of nut.
// h / height / thickness = height/thickness of nut.
// tpi = threads per inch
// ---
// pitch = Thread spacing (alternative to tpi)
// shape = specifies shape of nut, either "hex" or "square". Default: "hex"
// left_handed = if true, create left-handed threads. Default = false
// starts = Number of lead starts. Default: 1
// bevel = if true, bevel the thread ends. Default: false
// bevel1 = if true bevel the bottom end.
// bevel2 = if true bevel the top end.
// bevel = if true, bevel the outside of the nut.
// bevel1 = if true, bevel the outside of the nut bottom.
// bevel2 = if true, bevel the outside of the nut top.
// bevang = set the angle for the outside nut bevel. Default: 15
// ibevel = if true, bevel the inside (the hole). Default: true
// ibevel1 = if true bevel the inside, bottom end.
// ibevel2 = if true bevel the inside, top end.
// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#subsection-anchor). Default: `CENTER`
// spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#subsection-spin). Default: `0`
// orient = Vector to rotate top towards, after spin. See [orient](attachments.scad#subsection-orient). Default: `UP`
@ -491,26 +514,32 @@ module acme_threaded_rod(
function acme_threaded_nut(
nutwidth, id, h, tpi, pitch,
starts=1,
left_handed=false,shape,
bevel,bevel1,bevel2,
left_handed=false,shape="hex",
bevel,bevel1,bevel2,bevang=15,
ibevel,ibevel1,ibevel2,
height,thickness,
anchor, spin, orient
) = no_function("acme_threaded_nut");
module acme_threaded_nut(
nutwidth, id, h, tpi, pitch,
starts=1,
left_handed=false,shape="hex",
bevel,bevel1,bevel2,
bevel,bevel1,bevel2,bevang=15,
ibevel,ibevel1,ibevel2,
height,thickness,
anchor, spin, orient
) {
dummy = assert(num_defined([pitch,tpi])==1,"Must give exactly one of pitch and tpi");
pitch = is_undef(pitch) ? INCH/tpi : pitch;
dummy2=assert(is_num(pitch) && pitch>0);
dummy2=assert(is_num(pitch) && pitch>=0);
trapezoidal_threaded_nut(
nutwidth=nutwidth, id=id, h=h, pitch=pitch,
thread_depth = pitch/2,
thread_angle=29,shape=shape,
left_handed=left_handed,
bevel=bevel,bevel1=bevel1,bevel2=bevel2,
ibevel=ibevel,ibevel1=ibevel1,ibevel2=ibevel2,
height=height,thickness=thickness,
starts=starts,
anchor=anchor,
spin=spin,
@ -644,14 +673,14 @@ module npt_threaded_rod(
// Module: buttress_threaded_rod()
// Usage:
// buttress_threaded_rod(d, l, pitch, [internal=], ...) [ATTACHMENTS];
// buttress_threaded_rod(d, l|length, pitch, [internal=], ...) [ATTACHMENTS];
// Description:
// Constructs a simple buttress threaded rod with a 45 degree angle. The buttress thread or sawtooth thread has low friction and high loading
// in one direction at the cost of higher friction and inferior loading in the other direction. Buttress threads are sometimes used on
// vises, which are loaded only in one direction.
// Arguments:
// d = Outer diameter of threaded rod.
// l = length of threaded rod.
// l / length = length of threaded rod.
// pitch = Thread spacing.
// ---
// left_handed = if true, create left-handed threads. Default = false
@ -676,25 +705,25 @@ module npt_threaded_rod(
// buttress_threaded_rod(d=10, l=20, pitch=1.25, left_handed=true, $fa=1, $fs=1);
// buttress_threaded_rod(d=25, l=20, pitch=2, $fa=1, $fs=1);
function buttress_threaded_rod(
d=10, l=100, pitch=2,
d, l, pitch,
left_handed=false,
bevel,bevel1,bevel2,
internal=false,
higbee=0,
higbee1,
higbee2,
d1,d2,starts,
d1,d2,starts=1,length,
anchor, spin, orient
) = no_function("buttress_threaded_rod");
module buttress_threaded_rod(
d=10, l=100, pitch=2,
d, l, pitch,
left_handed=false,
bevel,bevel1,bevel2,
internal=false,
higbee=0,
higbee1,
higbee2,
d1,d2,starts=1,
d1,d2,starts=1,length,
anchor, spin, orient
) {
depth = pitch * 3/4;
@ -715,7 +744,7 @@ module buttress_threaded_rod(
higbee1=higbee1,
higbee2=higbee2,
d1=d1,d2=d2,
anchor=anchor,
anchor=anchor,length=length,
spin=spin,starts=starts,
orient=orient
) children();
@ -725,7 +754,7 @@ module buttress_threaded_rod(
// Module: buttress_threaded_nut()
// Usage:
// buttress_threaded_nut(nutwidth, id, h, pitch, ...) [ATTACHMENTS];
// buttress_threaded_nut(nutwidth, id, h|height|thickness, pitch, ...) [ATTACHMENTS];
// Description:
// Constructs a hexagonal or square nut for a simple buttress threaded screw rod.
// Arguments:
@ -737,9 +766,13 @@ module buttress_threaded_rod(
// shape = specifies shape of nut, either "hex" or "square". Default: "hex"
// left_handed = if true, create left-handed threads. Default = false
// starts = The number of lead starts. Default: 1
// bevel = if true, bevel the thread ends. Default: false
// bevel1 = if true bevel the bottom end.
// bevel2 = if true bevel the top end.
// bevel = if true, bevel the outside of the nut.
// bevel1 = if true, bevel the outside of the nut bottom.
// bevel2 = if true, bevel the outside of the nut top.
// bevang = set the angle for the outside nut bevel. Default: 15
// ibevel = if true, bevel the inside (the hole). Default: true
// ibevel1 = if true bevel the inside, bottom end.
// ibevel2 = if true bevel the inside, top end.
// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#subsection-anchor). Default: `CENTER`
// spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#subsection-spin). Default: `0`
// orient = Vector to rotate top towards, after spin. See [orient](attachments.scad#subsection-orient). Default: `UP`
@ -747,15 +780,17 @@ module buttress_threaded_rod(
// Examples(Med):
// buttress_threaded_nut(nutwidth=16, id=8, h=8, pitch=1.25, left_handed=true, $slop=0.05, $fa=1, $fs=1);
function buttress_threaded_nut(
nutwidth=16, id=10, h=10,
pitch=2, shape, left_handed=false,
bevel,bevel1,bevel2,starts,
nutwidth, id, h,
pitch, shape="hex", left_handed=false,
bevel,bevel1,bevel2,bevang=15,starts=1,
ibevel,ibevel1,ibevel2,height,thickness,
anchor, spin, orient
) = no_function("buttress_threaded_nut");
module buttress_threaded_nut(
nutwidth=16, id=10, h=10,
pitch=2, shape="hex", left_handed=false,
bevel,bevel1,bevel2,starts=1,
nutwidth, id, h,
pitch, shape="hex", left_handed=false,
bevel,bevel1,bevel2,bevang=15,starts=1,
ibevel,ibevel1,ibevel2,height,thickness,
anchor, spin, orient
) {
depth = pitch * 3/4;
@ -772,8 +807,9 @@ module buttress_threaded_nut(
profile=profile,
shape=shape,
left_handed=left_handed,starts=starts,
bevel=bevel,bevel1=bevel1,bevel2=bevel2,
anchor=anchor, spin=spin,
bevel=bevel,bevel1=bevel1,bevel2=bevel2,bevang=bevang,
ibevel=ibevel,ibevel1=ibevel1,ibevel2=ibevel2,
anchor=anchor, spin=spin, height=height, thickness=thickness,
orient=orient
) children();
}
@ -784,13 +820,13 @@ module buttress_threaded_nut(
// Module: square_threaded_rod()
// Usage:
// square_threaded_rod(d, l, pitch, [internal=], ...) [ATTACHMENTS];
// square_threaded_rod(d, l|length, pitch, [internal=], ...) [ATTACHMENTS];
// Description:
// Constructs a square profile threaded screw rod. The greatest advantage of square threads is that they have the least friction and a much higher intrinsic efficiency than trapezoidal threads.
// They produce no radial load on the nut. However, square threads cannot carry as much load as trapezoidal threads.
// Arguments:
// d = Outer diameter of threaded rod.
// l = length of threaded rod.
// l / length = length of threaded rod.
// pitch = Thread spacing.
// ---
// left_handed = if true, create left-handed threads. Default = false
@ -830,7 +866,7 @@ module square_threaded_rod(
starts=1,
internal=false,
higbee=0, higbee1, higbee2,
d1,d2,
d1,d2,length,
anchor, spin, orient
) {
trapezoidal_threaded_rod(
@ -845,6 +881,7 @@ module square_threaded_rod(
higbee2=higbee2,
d1=d1,
d2=d2,
length=length,
anchor=anchor,
spin=spin,
orient=orient
@ -855,21 +892,25 @@ module square_threaded_rod(
// Module: square_threaded_nut()
// Usage:
// square_threaded_nut(nutwidth, id, h, pitch, ...) [ATTACHMENTS];
// square_threaded_nut(nutwidth, id, h|height|thickness, pitch, ...) [ATTACHMENTS];
// Description:
// Constructs a hexagonal or square nut for a square profile threaded screw rod.
// Arguments:
// nutwidth = diameter of the nut.
// id = diameter of threaded rod to screw onto.
// h = height/thickness of nut.
// h / height / thickness = height/thickness of nut.
// pitch = Length between threads.
// ---
// shape = specifies shape of nut, either "hex" or "square". Default: "hex"
// left_handed = if true, create left-handed threads. Default = false
// starts = The number of lead starts. Default = 1
// bevel = if true, bevel the thread ends. Default: false
// bevel1 = if true bevel the bottom end.
// bevel2 = if true bevel the top end.
// bevel = if true, bevel the outside of the nut.
// bevel1 = if true, bevel the outside of the nut bottom.
// bevel2 = if true, bevel the outside of the nut top.
// bevang = set the angle for the outside nut bevel. Default: 15
// ibevel = if true, bevel the inside (the hole). Default: true
// ibevel1 = if true bevel the inside, bottom end.
// ibevel2 = if true bevel the inside, top end.
// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#subsection-anchor). Default: `CENTER`
// spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#subsection-spin). Default: `0`
// orient = Vector to rotate top towards, after spin. See [orient](attachments.scad#subsection-orient). Default: `UP`
@ -879,9 +920,10 @@ module square_threaded_rod(
function square_threaded_nut(
nutwidth, id, h,
pitch,
shape=shape,
left_handed=false,
bevel,bevel1,bevel2,
bevel,bevel1,bevel2,bevang=15,
ibevel,ibevel1,ibevel2,
height,thickness,
starts=1,
anchor, spin, orient
) = no_function("square_threaded_nut");
@ -889,16 +931,20 @@ module square_threaded_nut(
nutwidth, id, h,
pitch,
left_handed=false,
bevel,bevel1,bevel2,
bevel,bevel1,bevel2,bevang=15,
ibevel,ibevel1,ibevel2,
height,thickness,
starts=1,
anchor, spin, orient
) {
assert(is_num(pitch) && pitch>0)
assert(is_num(pitch) && pitch>=0)
trapezoidal_threaded_nut(
nutwidth=nutwidth, id=id, h=h, pitch=pitch,
thread_angle=0,
left_handed=left_handed,
bevel=bevel,bevel1=bevel1,bevel2=bevel2,
bevel=bevel,bevel1=bevel1,bevel2=bevel2, bevang=bevang,
ibevel=ibevel, ibevel1=ibevel1, ibevel2=ibevel2,
height=height,thickness=thickness,
starts=starts,
anchor=anchor,
spin=spin,
@ -911,12 +957,12 @@ module square_threaded_nut(
// Module: ball_screw_rod()
// Usage:
// ball_screw_rod(d, l, pitch, [ball_diam], [ball_arc], [internal=], ...) [ATTACHMENTS];
// ball_screw_rod(d, l|length, pitch, [ball_diam], [ball_arc], [internal=], ...) [ATTACHMENTS];
// Description:
// Constructs a ball screw rod. This type of rod is used with ball bearings.
// Arguments:
// d = Outer diameter of threaded rod.
// l = length of threaded rod.
// l / length = length of threaded rod.
// pitch = Thread spacing. Also, the diameter of the ball bearings used.
// ball_diam = The diameter of the ball bearings to use with this ball screw.
// ball_arc = The arc portion that should touch the ball bearings. Default: 120 degrees.
@ -947,7 +993,7 @@ function ball_screw_rod(
starts=1,
left_handed=false,
internal=false,
bevel,bevel1,bevel2,
bevel,bevel1,bevel2, length,
anchor, spin, orient
) = no_function("ball_screw_rod");
module ball_screw_rod(
@ -956,7 +1002,7 @@ module ball_screw_rod(
starts=1,
left_handed=false,
internal=false,
bevel,bevel1,bevel2,
bevel,bevel1,bevel2, length,
anchor, spin, orient
) {
n = max(3,ceil(segs(ball_diam/2)*ball_arc/2/360));
@ -973,7 +1019,7 @@ module ball_screw_rod(
starts=starts,
bevel=bevel,bevel1=bevel1,bevel2=bevel2,
internal=internal,
higbee=0,
higbee=0, length=length,
anchor=anchor,
spin=spin,
orient=orient
@ -986,7 +1032,7 @@ module ball_screw_rod(
// Module: generic_threaded_rod()
// Usage:
// generic_threaded_rod(d, l, pitch, profile, [internal=], ...) [ATTACHMENTS];
// generic_threaded_rod(d, l|length, pitch, profile, [internal=], ...) [ATTACHMENTS];
// Description:
// Constructs a generic threaded rod using an arbitrary thread profile that you supply. The rod can be tapered (e.g. for pipe threads).
// For specific thread types use other modules that supply the appropriate profile.
@ -1008,7 +1054,7 @@ module ball_screw_rod(
// only works for external threads. It is ignored if internal is true.
// Arguments:
// d = Outer diameter of threaded rod.
// l = Length of threaded rod.
// l / length = Length of threaded rod.
// pitch = Thread spacing.
// profile = A 2D path giving the shape of a thread
// ---
@ -1023,7 +1069,6 @@ module ball_screw_rod(
// higbee = Angle to taper thread ends over. Default: 0 (No higbee thread tapering)
// higbee1 = Angle to taper bottom thread end over.
// higbee2 = Angle to taper top thread end over.
// center = If given, overrides `anchor`. A true value sets `anchor=CENTER`, false sets `anchor=UP`.
// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#subsection-anchor). Default: `CENTER`
// spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#subsection-spin). Default: `0`
// orient = Vector to rotate top towards, after spin. See [orient](attachments.scad#subsection-orient). Default: `UP`
@ -1059,9 +1104,9 @@ function generic_threaded_rod(
bevel1, bevel2,
starts=1,
internal=false,
d1, d2,
d1, d2, length,
higbee, higbee1, higbee2,
center, anchor, spin, orient
anchor, spin, orient
) = no_function("generic_threaded_rod");
module generic_threaded_rod(
d, l, pitch, profile,
@ -1070,10 +1115,11 @@ module generic_threaded_rod(
bevel1, bevel2,
starts=1,
internal=false,
d1, d2,
d1, d2, length,
higbee, higbee1, higbee2,
center, anchor, spin, orient
anchor, spin, orient
) {
l = one_defined([l,length],"l,length");
dummy0 =
assert(all_positive(pitch))
assert(all_positive(l))
@ -1162,7 +1208,6 @@ module generic_threaded_rod(
slope = (_r1-_r2)/l;
maxlen = 2*pitch;
anchor = get_anchor(anchor, center, BOT, CENTER);
attachable(anchor,spin,orient, r1=_r1, r2=_r2, l=l) {
union(){
@ -1215,23 +1260,27 @@ module generic_threaded_rod(
// Module: generic_threaded_nut()
// Usage:
// generic_threaded_nut(nutwidth, id, h, pitch, profile, [$slop], ...) [ATTACHMENTS];
// generic_threaded_nut(nutwidth, id, h|height|thickness, pitch, profile, [$slop], ...) [ATTACHMENTS];
// Description:
// Constructs a hexagonal or square nut for an generic threaded rod using a user-supplied thread profile.
// See generic_threaded_rod for details on the profile specification.
// See {{generic_threaded_rod()}} for details on the profile specification.
// Arguments:
// nutwidth = outer dimension of nut from flat to flat.
// id = diameter of threaded rod to screw onto.
// h = height/thickness of nut.
// h / height / thickness = height/thickness of nut.
// pitch = Thread spacing.
// profile = Thread profile.
// ---
// shape = specifies shape of nut, either "hex" or "square". Default: "hex"
// left_handed = if true, create left-handed threads. Default = false
// starts = The number of lead starts. Default = 1
// bevel = if true, bevel the thread ends. Default: false
// bevel1 = if true bevel the bottom end.
// bevel2 = if true bevel the top end.
// bevel = if true, bevel the outside of the nut.
// bevel1 = if true, bevel the outside of the nut bottom.
// bevel2 = if true, bevel the outside of the nut top.
// bevang = set the angle for the outside nut bevel. Default: 15
// ibevel = if true, bevel the inside (the hole). Default: true
// ibevel1 = if true bevel the inside, bottom end.
// ibevel2 = if true bevel the inside, top end.
// id1 = inner diameter at the bottom
// id2 = inner diameter at the top
// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#subsection-anchor). Default: `CENTER`
@ -1244,11 +1293,12 @@ function generic_threaded_nut(
h,
pitch,
profile,
shape,
shape="hex",
left_handed=false,
starts=1,
bevel,bevel1,bevel2,bevang=15,
id1,id2,
ibevel, ibevel1, ibevel2,
id1,id2, height, thickness,
anchor, spin, orient
) = no_function("generic_threaded_nut");
module generic_threaded_nut(
@ -1261,22 +1311,28 @@ module generic_threaded_nut(
left_handed=false,
starts=1,
bevel,bevel1,bevel2,bevang=15,
id1,id2,
ibevel, ibevel1, ibevel2,
id1,id2, height, thickness,
anchor, spin, orient
) {
assert(in_list(shape,["square","hex"]), "shape must be \"hex\" or \"square\"");
extra = 0.01;
id1 = first_defined([id1,id]);
id2 = first_defined([id2,id]);
assert(is_def(id1) && is_def(id2), "Must specify inner diameter of nut");
h = one_defined([h,height,thickness],"h,height,thickness");
dummyA = assert(is_num(pitch) && pitch>=0, "pitch must be a nonnegative number")
assert(is_num(h) && h>0, "height/thickness must be a positive number")
assert(in_list(shape,["square","hex"]), "shape must be \"hex\" or \"square\"")
assert(all_positive([id1,id2]), "Inner diameter(s) of nut must be positive number(s)");
slope = (id2-id1)/h;
full_id1 = id1-slope*extra/2;
full_id2 = id2+slope*extra/2;
ibevel1 = first_defined([ibevel1,ibevel,true]);
ibevel2 = first_defined([ibevel2,ibevel,true]);
bevel1 = first_defined([bevel1,bevel,false]);
bevel2 = first_defined([bevel2,bevel,false]);
dummy1 = assert(is_num(pitch) && pitch>0);
depth = -pitch*min(column(profile,1));
bevel_d=.975;
bevel_d=0.975;
IBEV=0.05;
vnf = linear_sweep(hexagon(id=nutwidth), height=h, center=true);
attachable(anchor,spin,orient, size=shape=="square" ? [nutwidth,nutwidth,h] : undef, vnf=shape=="hex" ? vnf : undef) {
difference() {
@ -1288,16 +1344,19 @@ module generic_threaded_nut(
if (bevel2) cyl(h=h+.01, d2=nutwidth*bevel_d,d1=nutwidth*bevel_d+h/tan(bevang), $fn=64);
if (bevel1) down(.01) cyl(h=h+.01, d1=nutwidth*bevel_d,d2=nutwidth*bevel_d+h/tan(bevang), $fn=64);
}
generic_threaded_rod(
d1=full_id1,d2=full_id2,
l=h+extra,
pitch=pitch,
profile=profile,
left_handed=left_handed,
starts=starts,
internal=true,
bevel1=bevel1,bevel2=bevel2
);
if (pitch==0)
cyl(l=h+extra, d1=full_id1+4*get_slop(), d2=full_id2+4*get_slop(), chamfer1=ibevel1?-IBEV*full_id1:undef, chamfer2=ibevel2?-IBEV*full_id2:undef);
else
generic_threaded_rod(
d1=full_id1,d2=full_id2,
l=h+extra,
pitch=pitch,
profile=profile,
left_handed=left_handed,
starts=starts,
internal=true,
bevel1=ibevel1,bevel2=ibevel2
);
}
children();
}
@ -1322,7 +1381,8 @@ module generic_threaded_nut(
// .
// Unlike generic_threaded_rod, when internal=true this module generates the threads, not a thread mask.
// The profile needs to be inverted to produce the proper thread form. If you use the built-in trapezoidal
// thread you get the inverted thread, designed so that the inner diameter is d. With adequate clearance
// thread you get the inverted thread, designed so that the inner diameter is d. If you supply a custom profile
// you must invert it yourself to get internal threads. With adequate clearance
// this thread will mate with the thread that uses the same parameters but has internal=false. Note that
// unlike the threaded_rod modules, thread_helix does not adjust the diameter for faceting, nor does it
// subtract any $slop for clearance.
@ -1339,7 +1399,7 @@ module generic_threaded_nut(
// profile = If an asymmetrical thread profile is needed, it can be specified here.
// starts = The number of thread starts. Default: 1
// left_handed = If true, thread has a left-handed winding.
// internal = If true, invert threads for internal threading.
// internal = If true, apply tapers for internal threading, and invert the default profile. Default: false
// d1 = Bottom inside base diameter of threads.
// d2 = Top inside base diameter of threads.
// higbee = Length to taper thread ends over. Default: 0

View file

@ -937,7 +937,7 @@ module spikeball(r, d, anchor=CENTER, spin=0, orient=UP) {
r = get_radius(r=r,d=d,dflt=1);
attachable(anchor,spin,orient, r=r*1.1) {
union() {
ovoid_spread(r=r, n=512, cone_ang=180) cylinder(r1=r/10, r2=0, h=r/10);
sphere_copies(r=r, n=512, cone_ang=180) cylinder(r1=r/10, r2=0, h=r/10);
sphere(r=r);
}
children();
@ -946,7 +946,7 @@ module spikeball(r, d, anchor=CENTER, spin=0, orient=UP) {
spikeball(r=50) show_anchors(20);
```
If the shape is more of an ovoid, you can pass a 3-item vector of sizes to `r=` or `d=`.
If the shape is an ellipsoid, you can pass a 3-item vector of sizes to `r=` or `d=`.
```openscad-3D
include <BOSL2/std.scad>
@ -954,7 +954,7 @@ module spikeball(r, d, scale, anchor=CENTER, spin=0, orient=UP) {
r = get_radius(r=r,d=d,dflt=1);
attachable(anchor,spin,orient, r=r*1.1*scale) {
union() {
ovoid_spread(r=r, n=512, scale=scale, cone_ang=180) cylinder(r1=r/10, r2=0, h=r/10);
sphere_copies(r=r, n=512, scale=scale, cone_ang=180) cylinder(r1=r/10, r2=0, h=r/10);
scale(scale) sphere(r=r);
}
children();

View file

@ -13,7 +13,7 @@ Transforms | Related Distributors
`left()`, `right()` | `xcopies()`
`fwd()`, `back()` | `ycopies()`
`down()`, `up()` | `zcopies()`
`move()`, `translate()` | `move_copies()`, `line_of()`, `grid2d()`
`move()`, `translate()` | `move_copies()`, `line_of()`, `grid_copies()`
`xrot()` | `xrot_copies()`
`yrot()` | `yrot_copies()`
`zrot()` | `zrot_copies()`
@ -127,30 +127,30 @@ line_of(p1=[0,100,0], p2=[100,0,0], n=4)
sphere(d=10);
```
The `grid2d()` command will let you spread copies across both the X and Y
The `grid_copies()` command will let you spread copies across both the X and Y
axes at the same time:
```openscad-2D
include <BOSL2/std.scad>
grid2d(20, n=6) sphere(d=10);
grid_copies(20, n=6) sphere(d=10);
```
The spacing can be separately specified for both the X and Y axes, as can
the count of rows and columns:
```openscad-2D
include <BOSL2/std.scad>
grid2d([20,30], n=[6,4]) sphere(d=10);
grid_copies([20,30], n=[6,4]) sphere(d=10);
```
Another neat trick of `grid2d()`, is that you can stagger the output:
Another neat trick of `grid_copies()`, is that you can stagger the output:
```openscad-2D
include <BOSL2/std.scad>
grid2d(20, n=[12,6], stagger=true) sphere(d=10);
grid_copies(20, n=[12,6], stagger=true) sphere(d=10);
```
You can get the alternate stagger pattern if you set `stagger="alt"`:
```openscad-2D
include <BOSL2/std.scad>
grid2d(20, n=[12,6], stagger="alt") sphere(d=10);
grid_copies(20, n=[12,6], stagger="alt") sphere(d=10);
```
By default, if you give a scalar for the spacing value, staggering will give
@ -159,49 +159,49 @@ six of the surrounding items. If you give the spacing as a 2-item vector,
then that will force the X and Y spacings between columns and rows instead.
```openscad-2D
include <BOSL2/std.scad>
grid2d([20,20], n=6, stagger=true) sphere(d=10);
grid_copies([20,20], n=6, stagger=true) sphere(d=10);
```
You can alternately specify a grid using size and spacing:
```openscad-2D
include <BOSL2/std.scad>
grid2d(20, size=100) sphere(d=10);
grid_copies(20, size=100) sphere(d=10);
```
```openscad-2D
include <BOSL2/std.scad>
grid2d(20, size=[100,80]) sphere(d=10);
grid_copies(20, size=[100,80]) sphere(d=10);
```
```openscad-2D
include <BOSL2/std.scad>
grid2d(20, size=[100,80], stagger=true) sphere(d=10);
grid_copies(20, size=[100,80], stagger=true) sphere(d=10);
```
You can also make grids by specifying size and column/row count:
```openscad-2D
include <BOSL2/std.scad>
grid2d(n=5, size=100) sphere(d=10);
grid_copies(n=5, size=100) sphere(d=10);
```
```openscad-2D
include <BOSL2/std.scad>
grid2d(n=[4,5], size=100) sphere(d=10);
grid_copies(n=[4,5], size=100) sphere(d=10);
```
```openscad-2D
include <BOSL2/std.scad>
grid2d(n=[4,5], size=[100,80]) sphere(d=10);
grid_copies(n=[4,5], size=[100,80]) sphere(d=10);
```
Finally, the `grid2d()` command will let you give a polygon or region shape
Finally, the `grid_copies()` command will let you give a polygon or region shape
to fill with items. Only the items in the grid whose center would be inside
the polygon or region will be created. To fill a star shape with items, you
can do something like:
```openscad-3D
include <BOSL2/std.scad>
poly = [for (i=[0:11]) polar_to_xy(50*(i%2+1), i*360/12-90)];
grid2d(5, stagger=true, inside=poly) {
grid_copies(5, stagger=true, inside=poly) {
cylinder(d=4,h=10,spin=90,$fn=6);
}
```

View file

@ -536,7 +536,7 @@ region(rgn);
```
You can use regions for several useful things. If you wanted a grid of holes in your object that
form the shape given by a region, you can do that with `grid2d()`:
form the shape given by a region, you can do that with `grid_copies()`:
```openscad-3D
include <BOSL2/std.scad>
@ -546,7 +546,7 @@ rgn = [
];
difference() {
cyl(h=5, d=120);
grid2d(size=[120,120], spacing=[4,4], inside=rgn) cyl(h=10,d=2);
grid_copies(size=[120,120], spacing=[4,4], inside=rgn) cyl(h=10,d=2);
}
```

View file

@ -804,6 +804,18 @@ module no_module() {
}
// Module: deprecate()
// Usage:
// deprecate(new_name);
// Description:
// Display info that the current module is deprecated and you should switch to a new name
// Arguments:
// new_name = name of the new module that replaces the old one
module deprecate(new_name)
{
echo(str("***** Module ",parent_module(1),"() has been replaced by ",new_name,"() and will be removed in a future version *****"));
}
// Section: Testing Helpers