Added "chamfer" to round_corners and fixed a bug with non-closed paths

requiring an extra size entry.

Added dovetail module to joiners.
This commit is contained in:
Adrian Mariano 2020-01-09 18:52:08 -05:00
parent 231bb69634
commit 72d7bedd9a
2 changed files with 163 additions and 15 deletions

View file

@ -434,5 +434,139 @@ module joiner_quad(spacing1=undef, spacing2=undef, xspacing=undef, yspacing=unde
}
// Module: dovetail()
//
// Description:
// Produces a possibly tapered dovetail joint shape to attach to or subtract from two parts you wish to join together.
// The tapered dovetail is particularly advantageous for long joints because the joint assembles without binding until
// it is fully closed, and then wedges tightly. You can chamfer or round the corners of the dovetail shape for better
// printing and assembly, or choose a fully rounded joint that looks more like a puzzle piece. The dovetail appears
// parallel to the X axis and projecting upwards, so in its default orientation it will slide together with a translation
// in the X direction.
//
// Usage:
// dovetail(l|length, h|height, w|width, slope|angle, taper|width2, chamfer, r|radius, round, anchor, orient, spin)
//
// Arguments:
// l / length = Length of the dovetail (amount the joint slides during assembly)
// h / height = Height of the dovetail
// w / width = Width (at the wider, top end) of the dovetail before tapering
// slope = slope of the dovetail. Standard woodworking slopes are 4, 6, or 8. Default: 6.
// angle = angle (in degrees) of the dovetail. Specify only one of slope and angle.
// taper = taper angle (in degrees). Dovetail gets wider by this angle. Default: no taper
// width2 = width of right hand end of the dovetail. This alternate method of specifying the taper may be easier to manage. Specify only one of taper and width2.
// chamfer = amount to chamfer the corners of the joint (Default: no chamfer)
// r / radius = amount to round over the corners of the joint (Default: no rounding)
// round =
// Example: Ordinary straight dovetail, male version (sticking up) and female verison (below the xy plane)
// dovetail("male", length=30, width=15, height=8);
// fwd(25)dovetail("female", length=30, width=15, height=8);
// Example: Adding a 6 degree taper (Such a big taper is usually not necessary, but easier to see for the example.)
// dovetail("male", length=30, width=15, ,height=8, taper=6);
// fwd(25)dovetail("female", length=30, width=15, height=8, taper=6);
// Example: A block that can link to itself
// diff("remove")
// cuboid([50,30,10]){
// attach(BACK) dovetail("male", length=10, width=15, height=8,spin=90);
// attach(FRONT) dovetail("female", length=10, width=15, height=8,spin=90,$tags="remove");
// }
// Example: Setting the dovetail angle. This is too extreme to be useful.
// diff("remove")
// cuboid([50,30,10]){
// attach(BACK) dovetail("male", length=10, width=15, height=8,angle=30,spin=90);
// attach(FRONT) dovetail("female", length=10, width=15, height=8,angle=30,spin=90,$tags="remove");
// }
// Example: Adding a chamfer helps printed parts fit together without problems at the corners
// diff("remove")
// cuboid([50,30,10]){
// attach(BACK) dovetail("male", length=10, width=15, height=8,spin=90,chamfer=1);
// attach(FRONT) dovetail("female", length=10, width=15, height=8,spin=90,chamfer=1,$tags="remove");
// }
// Example: Rounding the outside corners is another option
// diff("remove")
// cuboid([50,30,10]){
// attach(BACK) dovetail("male", length=10, width=15, height=8,spin=90,radius=1);
// attach(FRONT) dovetail("female", length=10, width=15, height=8,spin=90,radius=1,$tags="remove");
// }
// Example: Or you can make a fully rounded joint
// diff("remove")
// cuboid([50,30,10]){
// attach(BACK) dovetail("male", length=10, width=15, height=8,spin=90,radius=1.5, round=true);
// attach(FRONT) dovetail("female", length=10, width=15, height=8,spin=90,radius=1.5, round=true, $tags="remove");
// }
// Example: With a long joint like this, a taper makes the joint easy to assemble. It will go together easily and wedge tightly if you get the tolerances right. Specifying the taper with `width2` may be easier than using a taper angle.
// cuboid([50,30,10])
// attach(TOP) dovetail("male", length=50, width=15, height=4, width2=18);
// fwd(35)
// diff("remove")
// cuboid([50,30,10])
// attach(TOP) dovetail("female", length=50, width=15, height=4, width2=18, $tags="remove");
// Example: A series of dovtails
// cuboid([50,30,10])
// attach(BACK) xspread(10,5) dovetail("male", length=10, width=7, height=4, spin=90);
// Example: Mating pin board for a right angle joint
// diff("remove")
// cuboid([50,30,10])
// position(TOP+BACK) xspread(10,5) dovetail("female", length=10, width=7, height=4, spin=90,$tags="remove",anchor=BOTTOM+RIGHT);
module dovetail(gender, length, l, width, w, height, h, angle, slope, taper, width2, chamfer, extra=0.01,orient,spin=0,r,radius,round=false,anchor=BOTTOM)
{
radius = get_radius(r1=radius,r2=r);
lcount = num_defined([l,length]);
hcount = num_defined([h,height]);
wcount = num_defined([w,width]);
assert(lcount==1, "Must define exactly one of l and length");
assert(wcount==1, "Must define exactly one of w and width");
assert(lcount==1, "Must define exactly one of h and height");
h = first_defined([h,height]);
w = first_defined([w,width]);
length = first_defined([l,length]);
orient = is_def(orient) ? orient :
gender == "female" ? DOWN : UP;
count = num_defined([angle,slope]);
assert(count<=1, "Do not specify both angle and slope");
count2 = num_defined([taper,width2]);
assert(count2<=1, "Do not specify both taper and width2");
count3 = num_defined([chamfer, radius]);
assert(count3<=1 || (radius==0 && chamfer==0), "Do not specify both chamfer and radius");
slope = is_def(slope) ? slope :
is_def(angle) ? 1/tan(angle) : 6;
width = gender == "male" ? w : w + $slop;
height = h + (gender == "female" ? $slop : 0);
front_offset = is_def(taper) ? extra * tan(taper) :
is_def(width2) ? extra * (width2-width)/length/2 : 0;
size = is_def(chamfer) && chamfer>0 ? chamfer :
is_def(radius) && radius>0 ? radius : 0;
type = is_def(chamfer) && chamfer>0 ? "chamfer" : "circle";
fullsize = round ? [0,size,size] :
gender == "male" ? [0,size,0] : [0,0,size];
smallend_half = round_corners(
move([-length/2-extra,0,0],
p=[[0,0 , height],
[0,width/2-front_offset , height],
[0,width/2 - height/slope - front_offset, 0 ],
[0,width/2 - front_offset + height, 0]
]),
curve=type, size = fullsize,closed=false);
smallend_points = concat(select(smallend_half, 1, -2), [down(extra,p=select(smallend_half, -2))]);
offset = is_def(taper) ? (length+extra) * tan(taper) :
is_def(width2) ? (width2-width) / 2 : 0;
bigend_points = move([length+2*extra,offset,0], p=smallend_points);
adjustment = gender == "male" ? -0.01 : 0.01; // Adjustment for default overlap in attach()
orient_and_anchor([length, width+2*offset, height],anchor=anchor,orient=orient,spin=spin, chain=true)
down(height/2+adjustment)
skin([
concat(smallend_points, yflip(p=reverse(smallend_points))),
concat(bigend_points, yflip(p=reverse(bigend_points)))]
);
}
// vim: noexpandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap

View file

@ -31,18 +31,18 @@ include <BOSL2/structs.scad>
// tactile "bump" where the curvature changes from flat to circular.
// See https://hackernoon.com/apples-icons-have-that-shape-for-a-very-good-reason-720d4e7c8a14
//
// You select the type of rounding using the `curve` option, which should be either `"smooth"` to
// get continuous curvature rounding or `"circle"` to get circular rounding. The default is circle
// You select the type of rounding using the `curve` option, which should be "smooth"` to
// get continuous curvature rounding, `"circle"` to get circular rounding, or `"chamfer"` to get chamfers. The default is circle
// rounding. Each rounding method has two options for how you measure the amount of rounding, which
// you specify using the `measure` argument. Both rounding methods accept `measure="cut"`, which is
// you specify using the `measure` argument. All of the rounding methods accept `measure="cut"`, which is
// the default. This mode specifies the amount of rounding as the minimum distance from the corner
// to the curve. This can be easier to understand than setting a circular radius, which can be
// unexpectedly extreme when the corner is very sharp. It also allows a systematic specification of
// curves that is the same for both `"circle"` and `"smooth"`.
//
// The second `measure` setting for circular rounding is `"radius"`, which sets a circular rounding
// radius. The second `measure` setting for smooth rounding is `"joint"` which specifies the distance
// away from the corner along the path where the roundover should start. The figure below shows
// radius. The second `measure` setting for smooth rounding and chamfers is `"joint"` which specifies the distance
// away from the corner along the path where the roundover or chamfer should start. The figure below shows
// the cut and joint distances for a given roundover.
//
// The `"smooth"` type rounding also has a parameter that specifies how smooth the curvature match
@ -105,8 +105,8 @@ include <BOSL2/structs.scad>
//
// Arguments:
// path = list of points defining the path to be rounded. Can be 2D or 3D, and may have an extra coordinate giving rounding parameters. If you specify rounding parameters you must do so on every point.
// curve = rounding method to use. Set to "circle" for circular rounding and "smooth" for continuous curvature 4th order bezier rounding
// measure = how to measure the amount of rounding. Set to "cut" to specify the cut back with either "smooth" or "circle" rounding curves. Set to "radius" with `curve="circle"` to set circular radius rounding. Set to "joint" with `curve="smooth"` for joint type rounding. (See above for details on these rounding options.)
// curve = rounding method to use. Set to "chamfer" for chamfers, "circle" for circular rounding and "smooth" for continuous curvature 4th order bezier rounding
// measure = how to measure the amount of rounding. Set to "cut" to specify the cut back with either "chamfer", "smooth", or "circle" rounding curves. Set to "radius" with `curve="circle"` to set circular radius rounding. Set to "joint" with `curve="smooth"` for joint type rounding. (See above for details on these rounding options.)
// size = curvature parameter(s). Set this to a single curvature parameter or parameter pair to apply uniform roundovers to every corner. Alternatively set this to a list of curvature parameters with the same length as `path` to specify the curvature at every corner. If you set this then all values given in `path` are treated as geometric coordinates. If you don't set this then the last value of each entry in `path` is treated as a rounding parameter.
// closed = if true treat the path as a closed polygon, otherwise treat it as open. Default: true.
// k = continuous curvature smoothness parameter default value. This value will apply with `curve=="smooth"` if you don't otherwise specify a smoothness parameter for a corner. Default: 0.5.
@ -135,6 +135,10 @@ include <BOSL2/structs.scad>
// shape = [[0,0,[1.5,.6]], [10,0,0], [15,12,2], [6,6,[.3,.7]], [6, 12,[1.2,.3]], [-3,7,0]];
// polygon(round_corners(shape, curve="smooth", measure="cut", $fs=0.1));
// color("red") down(.1) polygon(subindex(shape,[0:1]));
// Example(Med2D): Chamfers
// shape = [[0,0], [10,0], [15,12], [6,6], [6, 12], [-3,7]];
// polygon(round_corners(shape, curve="chamfer", measure="cut", size=1));
// color("red") down(.1) polygon(shape);
// Example(Med3D): 3D printing test pieces to display different curvature shapes. You can see the discontinuity in the curvature on the "C" piece in the rendered image.
// ten = [[0,0,5],[50,0,5],[50,50,5],[0,50,5]];
// linear_extrude(height=14){
@ -193,7 +197,8 @@ include <BOSL2/structs.scad>
// squareind = [for(i=[0:9]) each [i,i,i,i]]; // Index of the square for each point
// z = list_range(40)*.2+squareind;
// path3d = zip(spiral,z); // 3D spiral
// rounding = squareind/20; // Rounding parameters get larger up the spiral
// // Rounding parameters get larger up the spiral; delete last point because
// rounding = select(squareind/20,0,-2); // the path is not closed, so there are only len(path)-1 corners
// // Setting k=1 means curvature won't be continuous, but curves are as round as possible
// // Try changing the value to see the effect.
// rpath = round_corners(path3d, size=rounding, k=1, curve="smooth", measure="joint",closed=false);
@ -204,7 +209,7 @@ function round_corners(path, curve="circle", measure="cut", size=undef, k=0.5,
measureok = (
measure == "cut" ||
(curve=="circle" && measure=="radius") ||
(curve=="smooth" && measure=="joint")
((curve=="smooth" ||curve=="chamfer") && measure=="joint")
),
path = is_region(path)?
assert(len(path)==1, "Region supplied as path does not have exactly one component")
@ -213,12 +218,12 @@ function round_corners(path, curve="circle", measure="cut", size=undef, k=0.5,
have_size = size==undef ? 0 : 1,
pathsize_ok = is_num(pathdim) && pathdim >= 3-have_size && pathdim <= 4-have_size,
size_ok = !have_size || is_num(size) ||
is_list(size) && ((len(size)==2 && curve=="smooth") || len(size)==len(path))
is_list(size) && ((len(size)==2 && curve=="smooth") || len(size)==len(path)-(closed?0:1))
)
assert(curve=="smooth" || curve=="circle", "Unknown 'curve' setting in round_corners")
assert(curve=="smooth" || curve=="circle" || curve=="chamfer", "Unknown 'curve' setting in round_corners")
assert(measureok, curve=="circle"?
"In round_corners curve==\"circle\" requires 'measure' of 'radius' or 'cut'" :
"In round_corners curve==\"smooth\" requires 'measure' of 'joint' or 'cut'"
"In round_corners curve==\"smooth\" or \"chamfer\" requires 'measure' of 'joint' or 'cut'"
)
assert(pathdim!=undef, "Input 'path' has entries with inconsistent length")
assert(pathsize_ok, str(
@ -232,7 +237,7 @@ function round_corners(path, curve="circle", measure="cut", size=undef, k=0.5,
str(
"Input `size` has length ", len(size),
". Length must be ",
(curve=="smooth"?"2 or ":""), len(path)
(curve=="smooth"?"2 or ":""), len(path)-(closed?0:1)
)
) : str("Input `size` is ",size," which is not a number")
)
@ -254,6 +259,8 @@ function round_corners(path, curve="circle", measure="cut", size=undef, k=0.5,
default_curvature
)
(!closed && (i==0 || i==len(points)-1))? [0,0] :
(curve=="chamfer" && measure=="joint") ? [parm0] :
(curve=="chamfer" && measure=="cut") ? [parm0/cos(angle)] :
(curve=="circle")? [k/tan(angle), k] :
(curve=="smooth" && measure=="joint")? [parm0,k] :
[8*parm0/cos(angle)/(1+4*k),k]
@ -273,6 +280,7 @@ function round_corners(path, curve="circle", measure="cut", size=undef, k=0.5,
for(i=[0:1:len(points)-1]) each
(dk[i][0] == 0)? [points[i]] :
(curve=="smooth")? _bezcorner(select(points,i-1,i+1), dk[i]) :
(curve=="chamfer") ? _chamfcorner(select(points,i-1,i+1), dk[i]) :
_circlecorner(select(points,i-1,i+1), dk[i])
];
@ -323,11 +331,17 @@ function _bezcorner(points, parm) =
)
bezier_curve(P,N);
function _chamfcorner(points, parm) =
let(
d = parm[0],
prev = normalize(points[0]-points[1]),
next = normalize(points[2]-points[1])
)
[points[1]+prev*d, points[1]+next*d];
function _circlecorner(points, parm) =
let(
angle = vector_angle(points)/2,
df=echo(angle=angle),
d = parm[0],
r = parm[1],
prev = normalize(points[0]-points[1]),