Merge pull request #117 from adrianVmariano/master

Changed dovetail orientation to be more intuitive.
This commit is contained in:
Revar Desmera 2020-01-24 22:24:17 -08:00 committed by GitHub
commit 4662348aa6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 285 additions and 98 deletions

View file

@ -460,7 +460,7 @@ function list_remove_values(list,values=[],all=false) =
// Example:
// bselect([3,4,5,6,7], [false,true,true,false,true]); // Returns: [4,5,7]
function bselect(array,index) =
assert(is_list(array)||is_string(list))
assert(is_list(array)||is_string(array))
assert(is_list(index))
[for(i=[0:len(array)-1]) if (index[i]) array[i]];

View file

@ -443,15 +443,16 @@ module joiner_quad(spacing1=undef, spacing2=undef, xspacing=undef, yspacing=unde
// Module: dovetail()
//
// Usage:
// dovetail(l|length, h|height, w|width, slope|angle, taper|width2, [chamfer], [r|radius], [round], [$slop])
// dovetail(l|length, h|height, w|width, slope|angle, taper|back_width, [chamfer], [r|radius], [round], [$slop])
//
// 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.
// printing and assembly, or choose a fully rounded joint that looks more like a puzzle piece. The dovetail appears
// parallel to the Y axis and projecting upwards, so in its default orientation it will slide together with a translation
// in the positive Y direction. The default anchor for dovetails is BOTTOM; the default orientation depends on the gender,
// with male dovetails oriented UP and female ones DOWN.
//
// Arguments:
// l / length = Length of the dovetail (amount the joint slides during assembly)
@ -459,62 +460,64 @@ module joiner_quad(spacing1=undef, spacing2=undef, xspacing=undef, yspacing=unde
// 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.
// taper = taper angle (in degrees). Dovetail gets narrower by this angle. Default: no taper
// back_width = 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 `back_width`. Note that `back_width` should be smaller than `width` to taper in the customary direction, with the smaller end at the back.
// 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)
// round = true to round both corners of the dovetail and give it a puzzle piece look. Default: false.
// extra = amount of extra length and base extension added to dovetails for unions and differences. Default: 0.01
// Example: Ordinary straight dovetail, male version (sticking up) and female version (below the xy plane)
// dovetail("male", length=30, width=15, height=8);
// fwd(25) dovetail("female", length=30, width=15, height=8);
// right(20) 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);
// right(20) 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");
// attach(BACK) dovetail("male", length=10, width=15, height=8);
// attach(FRONT) dovetail("female", length=10, width=15, height=8,$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");
// attach(BACK) dovetail("male", length=10, width=15, height=8,angle=30);
// attach(FRONT) dovetail("female", length=10, width=15, height=8,angle=30,$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");
// attach(BACK) dovetail("male", length=10, width=15, height=8,chamfer=1);
// attach(FRONT) dovetail("female", length=10, width=15, height=8,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");
// attach(BACK) dovetail("male", length=10, width=15, height=8,radius=1,$fn=32);
// attach(FRONT) dovetail("female", length=10, width=15, height=8,radius=1,$tags="remove",$fn=32);
// }
// Example: Or you can make a fully rounded joint
// $fn=32;
// 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");
// attach(BACK) dovetail("male", length=10, width=15, height=8,radius=1.5, round=true);
// attach(FRONT) dovetail("female", length=10, width=15, height=8,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.
// 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 `back_width` may be easier than using a taper angle.
// cuboid([50,30,10])
// attach(TOP) dovetail("male", length=50, width=15, height=4, width2=18);
// attach(TOP) dovetail("male", length=50, width=18, height=4, back_width=15, spin=90);
// fwd(35)
// diff("remove")
// cuboid([50,30,10])
// attach(TOP) dovetail("female", length=50, width=15, height=4, width2=18, $tags="remove");
// attach(TOP) dovetail("female", length=50, width=18, height=4, back_width=15, spin=90,$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
// attach(BACK) xspread(10,5) dovetail("male", length=10, width=7, height=4);
// Example: Mating pin board for a right angle joint. Note that the anchor method and use of `spin` ensures that the joint works even with a taper.
// 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, r, radius, round=false, anchor=BOTTOM, spin=0, orient)
// position(TOP+BACK) xspread(10,5) dovetail("female", length=10, width=7, taper=4, height=4, $tags="remove",anchor=BOTTOM+FRONT,spin=180);
module dovetail(gender, length, l, width, w, height, h, angle, slope, taper, back_width, chamfer, extra=0.01, r, radius, round=false, anchor=BOTTOM, spin=0, orient)
{
radius = get_radius(r1=radius,r2=r);
lcount = num_defined([l,length]);
@ -530,8 +533,8 @@ module dovetail(gender, length, l, width, w, height, h, angle, slope, taper, wid
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");
count2 = num_defined([taper,back_width]);
assert(count2<=1, "Do not specify both taper and back_width");
count3 = num_defined([chamfer, radius]);
assert(count3<=1 || (radius==0 && chamfer==0), "Do not specify both chamfer and radius");
slope = is_def(slope) ? slope :
@ -539,8 +542,8 @@ module dovetail(gender, length, l, width, w, height, h, angle, slope, taper, wid
width = gender == "male" ? w : w + 2*$slop;
height = h + (gender == "female" ? 2*$slop : 0);
front_offset = is_def(taper) ? extra * tan(taper) :
is_def(width2) ? extra * (width2-width)/length/2 : 0;
front_offset = is_def(taper) ? -extra * tan(taper) :
is_def(back_width) ? extra * (back_width-width)/length/2 : 0;
size = is_def(chamfer) && chamfer>0 ? chamfer :
is_def(radius) && radius>0 ? radius : 0;
@ -551,29 +554,29 @@ module dovetail(gender, length, l, width, w, height, h, angle, slope, taper, wid
smallend_half = round_corners(
move(
[-length/2-extra,0,0],
[0,-length/2-extra,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]
[0 , 0, height],
[width/2-front_offset , 0, height],
[width/2 - height/slope - front_offset, 0, 0 ],
[width/2 - front_offset + height, 0, 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);
offset = is_def(taper) ? -(length+extra) * tan(taper) :
is_def(back_width) ? (back_width-width) / 2 : 0;
bigend_points = move([offset,length+2*extra,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) {
orient_and_anchor([width+2*offset, length, 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)))
reverse(concat(smallend_points, xflip(p=reverse(smallend_points)))),
reverse(concat(bigend_points, xflip(p=reverse(bigend_points))))
],
convexity=4
);

View file

@ -353,6 +353,60 @@ function _circlecorner(points, parm) =
arc(max(3,angle/180*segs(norm(start-center))), cp=center, points=[start,end]);
// Used by offset_sweep and convex_offset_extrude:
// Produce edge profile curve from the edge specification
// z_dir is the direction multiplier (1 to build up, -1 to build down)
function _rounding_offsets(edgespec,z_dir=1) =
let(
edgetype = struct_val(edgespec, "type"),
extra = struct_val(edgespec,"extra"),
N = struct_val(edgespec, "steps"),
r = struct_val(edgespec,"r"),
cut = struct_val(edgespec,"cut"),
k = struct_val(edgespec,"k"),
radius = in_list(edgetype,["circle","teardrop"])?
first_defined([cut/(sqrt(2)-1),r]) :
edgetype=="chamfer"? first_defined([sqrt(2)*cut,r]) : undef,
chamf_angle = struct_val(edgespec, "angle"),
cheight = struct_val(edgespec, "chamfer_height"),
cwidth = struct_val(edgespec, "chamfer_width"),
chamf_width = first_defined([cut/cos(chamf_angle), cwidth, cheight*tan(chamf_angle)]),
chamf_height = first_defined([cut/sin(chamf_angle),cheight, cwidth/tan(chamf_angle)]),
joint = first_defined([
struct_val(edgespec,"joint"),
16*cut/sqrt(2)/(1+4*k)
]),
points = struct_val(edgespec, "points"),
argsOK = in_list(edgetype,["circle","teardrop"])? is_def(radius) :
edgetype == "chamfer"? chamf_angle>0 && chamf_angle<90 && num_defined([chamf_height,chamf_width])==2 :
edgetype == "smooth"? num_defined([k,joint])==2 :
edgetype == "profile"? points[0]==[0,0] :
false
)
assert(argsOK,str("Invalid specification with type ",edgetype))
let(
offsets =
edgetype == "profile"? scale([-1,z_dir], slice(points,1,-1)) :
edgetype == "chamfer"? chamf_width==0 && chamf_height==0? [] : [[-chamf_width,z_dir*abs(chamf_height)]] :
edgetype == "teardrop"? (
radius==0? [] : concat(
[for(i=[1:N]) [radius*(cos(i*45/N)-1),z_dir*abs(radius)* sin(i*45/N)]],
[[-2*radius*(1-sqrt(2)/2), z_dir*abs(radius)]]
)
) :
edgetype == "circle"? radius==0? [] : [for(i=[1:N]) [radius*(cos(i*90/N)-1), z_dir*abs(radius)*sin(i*90/N)]] :
/* smooth */ joint==0 ? [] :
select(
_bezcorner([[0,0],[0,z_dir*abs(joint)],[-joint,z_dir*abs(joint)]], k, $fn=N+2),
1, -1
)
)
quant(extra > 0? concat(offsets, [select(offsets,-1)+[0,z_dir*extra]]) : offsets, 1/1024);
// Module: offset_sweep()
//
// Description:
@ -390,7 +444,7 @@ function _circlecorner(points, parm) =
// - quality: passed to offset(). Default: 1
// - steps: Number of vertical steps to use for the profile. (Not used by os_profile). Default: 16
// - offset_maxstep: The maxstep distance for offset() calls; controls the horizontal step density. Set smaller if you don't get the expected rounding. Default: 1
// - offset: Select "round" (r=) or "delta" (delta=) offset types for offset. Default: "round"
// - offset: Select "round" (r=) or "delta" (delta=) offset types for offset. You can also choose "chamfer" but this leads to exponential growth in the number of vertices with the steps parameter. Default: "round"
//
// Many of the arguments are described as setting "default" values because they establish settings which may be overridden by
// the top and bottom profile specifications.
@ -411,8 +465,16 @@ function _circlecorner(points, parm) =
// - "quality" - passed to offset. Default: 1.
// - "steps" - number of vertical steps to use for the roundover. Default: 16.
// - "offset_maxstep" - maxstep distance for offset() calls; controls the horizontal step density. Set smaller if you don't get expected rounding. Default: 1
// - "offset" - select "round" (r=) or "delta" (delta=) offset type for offset. Default: "round"
// - "offset" - select "round" (r=), "delta" (delta=), or "chamfer" offset type for offset. Default: "round"
//
// Note that if you set the "offset" parameter to "chamfer" then every exterior corner turns from one vertex into two vertices with
// each offset operation. Since the offsets are done one after another, each on the output of the previous one, this leads to
// exponential growth in the number of vertices. This can lead to long run times or yield models that
// run out of recursion depth and give a cryptic error. Furthermore, the generated vertices are distributed non-uniformly. Generally you
// will get a similar or better looking model with fewer vertices using "round" instead of
// "chamfer". Use the "chamfer" style offset only in cases where the number of steps is very small or just one (such as when using
// the `os_chamfer` profile type).
//
// Arguments:
// path = 2d path (list of points) to extrude
// height / l / h = total height (including rounded portions, but not extra sections) of the output. Default: combined height of top and bottom end treatments.
@ -559,13 +621,14 @@ module offset_sweep(
) : (
let(
this_offset = offsetind==0? offsets[0][0] : offsets[offsetind][0] - offsets[offsetind-1][0],
delta = offset_type=="delta"? this_offset : undef,
r = offset_type=="round"? this_offset : undef
delta = offset_type=="delta" || offset_type=="chamfer" ? this_offset : undef,
r = offset_type=="round"? this_offset : undef,
do_chamfer = offset_type == "chamfer"
)
assert(num_defined([r,delta])==1,"Must set `offset` to \"round\" or \"delta")
let(
vertices_faces = offset(
path, r=r, delta=delta, closed=true,
path, r=r, delta=delta, chamfer = do_chamfer, closed=true,
check_valid=check_valid, quality=quality,
maxstep=maxstep, return_faces=true,
firstface_index=vertexcount,
@ -584,54 +647,6 @@ module offset_sweep(
)
);
// Produce edge profile curve from the edge specification
// z_dir is the direction multiplier (1 to build up, -1 to build down)
function rounding_offsets(edgespec,z_dir=1) =
let(
edgetype = struct_val(edgespec, "type"),
extra = struct_val(edgespec,"extra"),
N = struct_val(edgespec, "steps"),
r = struct_val(edgespec,"r"),
cut = struct_val(edgespec,"cut"),
k = struct_val(edgespec,"k"),
radius = in_list(edgetype,["circle","teardrop"])?
first_defined([cut/(sqrt(2)-1),r]) :
edgetype=="chamfer"? first_defined([sqrt(2)*cut,r]) : undef,
chamf_angle = struct_val(edgespec, "angle"),
cheight = struct_val(edgespec, "chamfer_height"),
cwidth = struct_val(edgespec, "chamfer_width"),
chamf_width = first_defined([cut/cos(chamf_angle), cwidth, cheight*tan(chamf_angle)]),
chamf_height = first_defined([cut/sin(chamf_angle),cheight, cwidth/tan(chamf_angle)]),
joint = first_defined([
struct_val(edgespec,"joint"),
16*cut/sqrt(2)/(1+4*k)
]),
points = struct_val(edgespec, "points"),
argsOK = in_list(edgetype,["circle","teardrop"])? is_def(radius) :
edgetype == "chamfer"? angle>0 && angle<90 && num_defined([chamf_height,chamf_width])==2 :
edgetype == "smooth"? num_defined([k,joint])==2 :
edgetype == "profile"? points[0]==[0,0] :
false
)
assert(argsOK,str("Invalid specification with type ",edgetype))
let(
offsets =
edgetype == "profile"? scale([-1,z_dir], slice(points,1,-1)) :
edgetype == "chamfer"? chamf_width==0 && chamf_height==0? [] : [[-chamf_width,z_dir*abs(chamf_height)]] :
edgetype == "teardrop"? (
radius==0? [] : concat(
[for(i=[1:N]) [radius*(cos(i*45/N)-1),z_dir*abs(radius)* sin(i*45/N)]],
[[-2*radius*(1-sqrt(2)/2), z_dir*abs(radius)]]
)
) :
edgetype == "circle"? radius==0? [] : [for(i=[1:N]) [radius*(cos(i*90/N)-1), z_dir*abs(radius)*sin(i*90/N)]] :
/* smooth */ joint==0 ? [] :
select(
_bezcorner([[0,0],[0,z_dir*abs(joint)],[-joint,z_dir*abs(joint)]], k, $fn=N+2),
1, -1
)
)
quant(extra > 0? concat(offsets, [select(offsets,-1)+[0,z_dir*extra]]) : offsets, 1/1024);
argspec = [
["r",r],
@ -664,9 +679,12 @@ module offset_sweep(
//assert(offsetsok,"Offsets must be one of \"round\" or \"delta\"");
offsets_bot = rounding_offsets(bottom, -1);
offsets_top = rounding_offsets(top, 1);
offsets_bot = _rounding_offsets(bottom, -1);
offsets_top = _rounding_offsets(top, 1);
if (offset == "chamfer" && (len(offsets_bot)>5 || len(offsets_top)>5)) {
echo("WARNING: You have selected offset=\"chamfer\", which leads to exponential growth in the vertex count and requested many layers. This can be slow or run out of recursion depth.");
}
// "Extra" height enlarges the result beyond the requested height, so subtract it
bottom_height = len(offsets_bot)==0 ? 0 : abs(select(offsets_bot,-1)[1]) - struct_val(bottom,"extra");
top_height = len(offsets_top)==0 ? 0 : abs(select(offsets_top,-1)[1]) - struct_val(top,"extra");
@ -789,6 +807,172 @@ function os_profile(points, extra,check_valid, quality, offset_maxstep, offset)
]);
// Module: convex_offset_extrude()
//
// Description:
// Extrudes 2d children with layers formed from the convex hull of the offset of each child according to a sequence of offset values.
// Like `offset_sweep` this module can use built-in offset profiles to provide treatments such as roundovers or chamfers but unlike `offset_sweep()` it
// operates on 2d children rather than a point list. Each offset is computed using
// the native `offset()` module from the input geometry. If your geometry has internal holes or is too small for the specified offset then you may get
// unexpected results.
//
// The build-in profiles are: circular rounding, teardrop rounding, chamfer, continuous curvature rounding, and chamfer.
// Also note that when a rounding radius is negative the rounding will flare outwards. The easieast way to specify
// the profile is by using the profile helper functions. These functions take profile parameters, as well as some
// general settings and translate them into a profile specification, with error checking on your input. The description below
// describes the helper functions and the parameters specific to each function. Below that is a description of the generic
// settings that you can optionally use with all of the helper functions.
//
// The final shape is created by combining convex hulls of small extrusions. The thickness of these small extrusions may result
// your model being slightly too long (if the curvature at the end is flaring outward), so if the exact length is very important
// you may need to intersect with a bounding cube. (Note that extra length can also be intentionally added with the `extra` argument.)
//
// - profile: os_profile(points)
// Define the offset profile with a list of points. The first point must be [0,0] and the roundover should rise in the positive y direction, with positive x values for inward motion (standard roundover) and negative x values for flaring outward. If the y value ever decreases then you might create a self-intersecting polyhedron, which is invalid. Such invalid polyhedra will create cryptic assertion errors when you render your model and it is your responsibility to avoid creating them. Note that the starting point of the profile is the center of the extrusion. If you use a profile as the top it will rise upwards. If you use it as the bottom it will be inverted, and will go downward.
// - circle: os_circle(r|cut). Define circular rounding either by specifying the radius or cut distance.
// - smooth: os_smooth(cut|joint). Define continuous curvature rounding, with `cut` and `joint` as for round_corners.
// - teardrop: os_teardrop(r|cut). Rounding using a 1/8 circle that then changes to a 45 degree chamfer. The chamfer is at the end, and enables the object to be 3d printed without support. The radius gives the radius of the circular part.
// - chamfer: os_chamfer([height], [width], [cut], [angle]). Chamfer the edge at desired angle or with desired height and width. You can specify height and width together and the angle will be ignored, or specify just one of height and width and the angle is used to determine the shape. Alternatively, specify "cut" along with angle to specify the cut back distance of the chamfer.
//
// The general settings that you can use with all of the helper functions are mostly used to control how offset_sweep() calls the offset() function.
// - extra: Add an extra vertical step of the specified height, to be used for intersections or differences. This extra step will extend the resulting object beyond the height you specify. Default: 0
// - steps: Number of vertical steps to use for the profile. (Not used by os_profile). Default: 16
// - offset: Select "round" (r=), "delta" (delta=), or "chamfer" offset types for offset. Default: "round"
//
// Many of the arguments are described as setting "default" values because they establish settings which may be overridden by
// the top and bottom profile specifications.
//
// You will generally want to use the above helper functions to generate the profiles.
// The profile specification is a list of pairs of keywords and values, e.g. ["r",12, type, "circle"]. The keywords are
// - "type" - type of rounding to apply, one of "circle", "teardrop", "chamfer", "smooth", or "profile" (Default: "circle")
// - "r" - the radius of the roundover, which may be zero for no roundover, or negative to round or flare outward. Default: 0
// - "cut" - the cut distance for the roundover or chamfer, which may be negative for flares
// - "chamfer_width" - the width of a chamfer
// - "chamfer_height" - the height of a chamfer
// - "angle" - the chamfer angle, measured from the vertical (so zero is vertical, 90 is horizontal). Default: 45
// - "joint" - the joint distance for a "smooth" roundover
// - "k" - the curvature smoothness parameter for "smooth" roundovers, a value in [0,1]. Default: 0.75
// - "points" - point list for use with the "profile" type
// - "extra" - extra height added for unions/differences. This makes the shape taller than the requested height. (Default: 0)
// - "steps" - number of vertical steps to use for the roundover. Default: 16.
// - "offset" - select "round" (r=) or "delta" (delta=) offset type for offset. Default: "round"
//
// Note that unlike `offset_sweep`, because the offset operation is always performed from the base shape, using chamfered offsets does not increase the
// number of vertices or lead to any special complications.
//
// Arguments:
// height / l / h = total height (including rounded portions, but not extra sections) of the output. Default: combined height of top and bottom end treatments.
// top = rounding spec for the top end.
// bottom = rounding spec for the bottom end
// offset = default offset, `"round"`, `"delta"`, or `"chamfer"`. Default: `"round"`
// steps = default step count. Default: 16
// extra = default extra height. Default: 0
// cut = default cut value.
// chamfer_width = default width value for chamfers.
// chamfer_height = default height value for chamfers.
// angle = default angle for chamfers. Default: 45
// joint = default joint value for smooth roundover.
// k = default curvature parameter value for "smooth" roundover
// convexity = convexity setting for use with polyhedron. Default: 10
//
// Example: Chamfered elliptical prism. If you stretch a chamfered cylinder the chamfer will be uneven.
// $fn=32;
// convex_offset_extrude(bottom = os_chamfer(height=-2), top=os_chamfer(height=1), height=7)
// xscale(4)circle(r=6);
// Example: Elliptical prism with circular roundovers.
// $fn=32;
// convex_offset_extrude(bottom=os_circle(r=-2), top=os_circle(r=1), height=7,steps=10)
// xscale(4)circle(r=6);
// Example: If you give a non-convex input you get a convex hull output
// $fn=32;
// right(50) linear_extrude(height=7) star(5,r=22,ir=13);
// convex_offset_extrude(bottom = os_chamfer(height=-2), top=os_chamfer(height=1), height=7)
// star(5,r=22,ir=13)
module convex_offset_extrude(
height, h, l,
top=[], bottom=[],
offset="round", r=0, steps=16,
extra=0,
cut=undef, chamfer_width=undef, chamfer_height=undef,
joint=undef, k=0.75, angle=45,
convexity=10, thickness = 1/1024
) {
argspec = [
["r",r],
["extra",extra],
["type","circle"],
["steps",steps],
["offset",offset],
["chamfer_width",chamfer_width],
["chamfer_height",chamfer_height],
["angle",angle],
["cut",cut],
["joint",joint],
["k", k],
["points", []],
];
top = struct_set(argspec, top, grow=false);
bottom = struct_set(argspec, bottom, grow=false);
offsets_bot = rounding_offsets(bottom, -1);
offsets_top = rounding_offsets(top, 1);
// "Extra" height enlarges the result beyond the requested height, so subtract it
bottom_height = len(offsets_bot)==0 ? 0 : abs(select(offsets_bot,-1)[1]) - struct_val(bottom,"extra");
top_height = len(offsets_top)==0 ? 0 : abs(select(offsets_top,-1)[1]) - struct_val(top,"extra");
height = get_height(l=l,h=h,height=height,dflt=bottom_height+top_height);
assert(height>=0, "Height must be nonnegative");
middle = height-bottom_height-top_height;
assert(
middle>=0, str(
"Specified end treatments (bottom height = ",bottom_height,
" top_height = ",top_height,") are too large for extrusion height (",height,")"
)
);
// The entry r[i] is [radius,z] for a given layer
r = move([0,bottom_height],p=concat(
reverse(offsets_bot), [[0,0], [0,middle]], move([0,middle], p=offsets_top)));
delta = [for(val=deltas(subindex(r,0))) sign(val)];
below=[-thickness,0];
above=[0,thickness];
// layers is a list of pairs of the relative positions for each layer, e.g. [0,thickness]
// puts the layer above the polygon, and [-thickness,0] puts it below.
layers = [for (i=[0:len(r)-1])
i==0 ? (delta[0]<0 ? below : above) :
i==len(r)-1 ? (delta[len(delta)-1] < 0 ? below : above) :
delta[i]==0 ? above :
delta[i+1]==0 ? below :
delta[i]==delta[i-1] ? [-thickness/2, thickness/2] :
delta[i] == 1 ? above :
/* delta[i] == -1 ? */ below];
dochamfer = offset=="chamfer";
for(i=[0:len(r)-2])
for(j=[0:$children-1])
hull(){
up(r[i][1]+layers[i][0])
linear_extrude(convexity=convexity,height=layers[i][1]-layers[i][0])
if (offset=="round")
offset(r=r[i][0])
children(j);
else
offset(delta=r[i][0],chamfer = dochamfer)
children(j);
up(r[i+1][1]+layers[i+1][0])
linear_extrude(convexity=convexity,height=layers[i+1][1]-layers[i+1][0])
if (offset=="round")
offset(r=r[i+1][0])
children(j);
else
offset(delta=r[i+1][0],chamfer=dochamfer)
children(j);
}
}
function _remove_undefined_vals(list) =
let(ind=search([undef],list,0)[0])
list_remove(list, concat(ind, add_scalar(ind,-1)));