Check this out and let me know what you think. It seems like something wrong with bezier functions in bosl2. I had to change the include in roundcorners to the old version or I got errors, but no time to troubleshoot.
use<BOSL2/transforms.scad>
use<BOSL2/std.scad>
// Opposite of bselect: Sets array[i] if indexset[i] is true, taking values sequentially from valuelist
function array_set(indexset, valuelist, _i=0, _v=0,array=[]) =
_i==len(indexset) ? array :
array_set(indexset, valuelist, _i+1, _v+(indexset[_i]?1:0), concat(array, [ indexset[_i]?valuelist[_v]:0]));
// index is a boolean array. Return a list where array[i] is on the list if index[i] is true
function bselect(array,index) = [for(i=[0:len(array)-1]) if (index[i]) array[i]];
function circular_arc(center, p1, p2, N) = let(
angle = pathangle([p1,center,p2]),
v1 = p1-center,
v2 = p2-center
)
len(center)==2 ?
let(dir = sign(v1.x*v2.y-v1.y*v2.x), // z component of cross product
r=norm(v1))
assert(dir != 0, "Colinear inputs don't define a unique arc")
[for(i=[0:N-1])
let(theta=atan2(v1.y,v1.x)+i*dir*angle/(N-1))
r*[cos(theta),sin(theta)]+center] :
let(axis = cross(v1,v2))
assert( axis != [0,0,0], "Colinear inputs don't define a unique arc")
[for(i=[0:N-1])
matrix3_rot_by_axis(axis, i*angle/(N-1)) * v1 + center];
function is_def(x) = !is_undef(x);
// Compute offset to the input path.
// The offset is given as delta or r (as is done with the offset command)
// and a positive value shifts to the left of the path direction and a
// negative value to the right.
//
// Specify closed=true if you want a closed curve
//
// maxstep specifies the maximum step size for roundovers.
// Function: pathoffset()
//
// Description:
// Takes an input path and returns a path offset by the specified amount. As with offset(), you can use
// r to specify rounded offset and delta to specify offset with corners. Positive offsets shift the path
// to the left (relative to the direction of the path).
//
// When offsets shrink the path, segments cross and become invalid. By default pathoffset checks for this situation.
// This check takes O(N^2) time and can be disabled if you don't need it by setting check_valid=false.
// In some cases the check may mistakenly eliminate segments that should have been included. If this
// happens, increase `quality` to 2 or 3. Run time will increase. You may be able to save run time
// in some cases by setting `quality` to 0.
//
// For construction of polyhedra pathoffset() can also return face lists. These list faces between the
// original path and the offset path where the vertices are ordered with the original path first,
// starting at `firstface_index` and the offset path vertices appearing afterwords. The direction
// of the faces can be flipped using `flip_faces`. When you request faces the return value
// is a list: [offset_path, face_list].
//
// Arguments:
// path = the path to process. A list of 2d points.
// r = offset radius. Distance to offset. Will round over corners.
// delta = offset distance. Distance to offset with pointed corners.
// closed = path is a closed curve. Default: False.
// check_valid = perform segment validity check. Default: True.
// quality = validity check quality parameter, a small integer. Default: 1.
// return_faces = return face list. Default: False.
// firstface_index = starting index for face list. Default: 0.
// flip_faces = flip face direction. Default: false
function pathoffset(path, r=undef, delta=undef, maxstep=.1, closed=false, check_valid=true, quality=1,
return_faces=false, firstface_index=0, flip_faces=false) =
let(rcount = len(remove_undefs([r,delta])))
assert(rcount==1,"Must define exactly one of 'delta' and 'r'")
let(
quality = max(0,round(quality)),
d = is_def(r)? r : delta,
shiftsegs = [for(i=[0:len(path)-1]) _shift_segment(select(path,i,i+1), d)],
// good segments are ones where no point on the segment is less than distance d from any point on the path
good = check_valid ? _good_segments(path, abs(d), shiftsegs, closed, quality) : replist(1,len(shiftsegs)),
goodsegs = bselect(shiftsegs, good),
goodpath = bselect(path,good)
)
assert(len(goodsegs)>0,"Offset of path is degenerate")
let(
// Extend the shifted segments to their intersection points
sharpcorners = [for(i=[0:len(goodsegs)-1]) _segment_extension(select(goodsegs,i-1), select(goodsegs,i))],
// If some segments are parallel then the extended segments are undefined. This case is not handled
parallelsegs = len(sharpcorners)!=len(remove_undefs(sharpcorners))
)
assert(!parallelsegs, "Path turns back on itself (180 deg turn)")
let(
// This is a boolean array that indicates whether a corner is an outside or inside corner
// For outside corners, the newcorner is an extension (angle 0), for inside corners, it turns backward
// If either side turns back it is an inside corner---must check both.
// Outside corners can get rounded (if r is specified and there is space to round them)
outsidecorner = [for(i=[0:len(goodsegs)-1]) let(prevseg=select(goodsegs,i-1))
(goodsegs[i][1]-goodsegs[i][0]) * (goodsegs[i][0]-sharpcorners[i]) > 0
&& (prevseg[1]-prevseg[0]) * (sharpcorners[i]-prevseg[1]) > 0 ],
steps = is_def(delta) ? [] : [for(i=[0:len(goodsegs)-1])
ceil(abs(r)*vector_angle(select(goodsegs,i-1)[1]-goodpath[i], goodsegs[i][0]-goodpath[i])*PI/180/maxstep)],
// If rounding is true then newcorners replaces sharpcorners with rounded arcs where needed
// Otherwise it's the same as sharpcorners
// If rounding is on then newcorners[i] will be the point list that replaces goodpath[i] and newcorners later
// gets flattened. If rounding is off then we set it to [sharpcorners] so we can later flatten it and get
// plain sharpcorners back.
newcorners = is_def(delta) ? [sharpcorners] : [for(i=[0:len(goodsegs)-1])
steps[i] <=2 || // Don't round if steps <=2
!outsidecorner[i] || // Don't round inside corners
(!closed && (i==0 || i==len(goodsegs)-1)) ? [sharpcorners[i]]:
circular_arc(goodpath[i], select(goodsegs,i-1)[1], goodsegs[i][0], steps[i])],
pointcount = is_def(delta) ? replist(1,len(sharpcorners)) : [for(i=[0:len(goodsegs)-1]) len(newcorners[i])],
start = [goodsegs[0][0]],
end = [goodsegs[len(goodsegs)-2][1]],
edges = closed ? flatten(newcorners) : concat(start,slice(flatten(newcorners),1,-2),end),
faces = return_faces ? _makefaces(flip_faces, firstface_index, good, pointcount, closed) : []
)
return_faces ? [edges,faces] : edges;
function _shift_segment(segment, d) =
translate_points(segment,d*normalize([segment[0].y-segment[1].y, segment[1].x-segment[0].x]));
// Extend to segments to their intersection point. First check if the segments already have a point in common,
// which can happen if two colinear segments are input to pathoffset
function _segment_extension(s1,s2) = norm(s1[1]-s2[0])<1e-6 ? s1[1] :
line_intersection(s1,s2);
function _makefaces(direction, startind, good, pointcount, closed) =
let( lenlist = array_set(good, pointcount),
numfirst = len(lenlist),
numsecond = sum(lenlist),
prelim_faces = _makefaces_recurse(startind, startind+len(lenlist), numfirst, numsecond, lenlist, closed)
)
direction ? [for(entry=prelim_faces) reverse(entry)] : prelim_faces;
function _makefaces_recurse(startind1, startind2, numfirst, numsecond, lenlist, closed, firstind=0, secondind=0, faces=[]) =
// We are done if *both* firstind and secondind reach their max value, which is the last point if !closed or one past
// the last point if closed (wrapping around). If you don't check both you can leave a triangular gap in the output.
firstind == numfirst - (closed?0:1) && secondind == numsecond - (closed?0:1) ? faces :
_makefaces_recurse(startind1, startind2, numfirst, numsecond, lenlist, closed, firstind+1, secondind+lenlist[firstind],
lenlist[firstind]==0 ? // point in original path has been deleted in offset path, so it has no match. We therefore
// make a triangular face using the current point from the offset (second) path
// (The current point in the second path can be equal to numsecond if firstind is the last point)
concat(faces,[[secondind%numsecond+startind2, firstind+startind1, (firstind+1)%numfirst+startind1]]) :
// in this case a point or points exist in the offset path corresponding to the original path
concat(faces,
// First generate triangular faces for all of the extra points (if there are any---loop may be empty)
[for(i=[0:1:lenlist[firstind]-2]) [firstind+startind1, secondind+i+1+startind2, secondind+i+startind2]],
// Finish (unconditionally) with a quadrilateral face
[[firstind+startind1, (firstind+1)%numfirst+startind1,
(secondind+lenlist[firstind])%numsecond+startind2, (secondind+lenlist[firstind]-1)%numsecond+startind2]]
)
);
// Determine which of the shifted segments are good
function _good_segments(path, d, shiftsegs, closed, quality) =
let(
maxind = len(path)-(closed ? 1 : 2),
pathseg = [for(i=[0:maxind]) select(path,i+1)-path[i]],
pathseg_len = [for(seg=pathseg) norm(seg)],
pathseg_unit = [for(i=[0:maxind]) pathseg[i]/pathseg_len[i]],
// Order matters because as soon as a valid point is found, the test stops
// This order works better for circular paths because they succeed in the center
alpha = concat([for(i=[1:1:quality]) i/(quality+1)],[0,1])
)
[for (i=[0:len(shiftsegs)-1]) _segment_good(path,pathseg_unit,pathseg_len, d - 1e-6, shiftsegs[i], alpha)];
// Determine if a segment is good (approximately)
// Input is the path, the path segments normalized to unit length, the length of each path segment
// the distance threshold, the segment to test, and the locations on the segment to test (normalized to [0,1])
// The last parameter, index, gives the current alpha index.
//
// A segment is good if any part of it is farther than distance d from the path. The test is expensive, so
// we want to quit as soon as we find a point with distance > d, hence the recursive code structure.
//
// This test is approximate because it only samples the points listed in alpha. Listing more points
// will make the test more accurate, but slower.
function _segment_good(path,pathseg_unit,pathseg_len, d, seg,alpha ,index=0) =
index == len(alpha) ? false :
_point_dist(path,pathseg_unit,pathseg_len, alpha[index]*seg[0]+(1-alpha[index])*seg[1]) > d ? true :
_segment_good(path,pathseg_unit,pathseg_len,d,seg,alpha,index+1);
// Input is the path, the path segments normalized to unit length, the length of each path segment
// and a test point. Computes the (minimum) distance from the path to the point, taking into
// account that the minimal distance may be anywhere along a path segment, not just at the ends.
function _point_dist(path,pathseg_unit,pathseg_len,pt) =
min(
[for(i=[0:len(pathseg_unit)-1])
let(
v = pt-path[i],
projection = v*pathseg_unit[i],
segdist = projection < 0 ? norm(pt-path[i]) :
projection > pathseg_len[i] ? norm(pt-select(path,i+1)) :
norm(v-projection*pathseg_unit[i])
) segdist]);
function pathangle(pts) = vector_angle(pts[0]-pts[1], pts[2]-pts[1]);
function det2(M) = M[0][0] * M[1][1] - M[0][1]*M[1][0];
// Line intersection from two segments
// This function returns [p,t,u] where p is the intersection point of
// the lines defined by the two segments, t is the bezier parameter
// for the intersection point on s1 and u is the bezier parameter for
// the intersection point on s2. The bezier parameter runs over [0,1]
// for each segment, so if it is in this range, then the intersection
// lies on the segment. Otherwise it lies somewhere on the extension
// of the segment.
function _general_line_intersection(s1,s2) =
let( denominator = det2([s1[0],s2[0]]-[s1[1],s2[1]]),
t=det2([s1[0],s2[0]]-s2)/denominator,
u=det2([s1[0],s1[0]]-[s1[1],s2[1]])/denominator)
[denominator==0 ? undef : s1[0]+t*(s1[1]-s1[0]),t,u];
// Returns the intersection point of the lines defined by two segments, or undef
// if the lines are parallel.
function line_intersection(s1,s2) = let( int = _general_line_intersection(s1,s2)) int[0];
// Returns the intersection point of two segments and undef if they do not intersect
function segment_intersection(s1,s2) = let( int = _general_line_intersection(s1,s2))
int[1]<0 || int[1]>1 || int[2]<0 || int[2]>1 ? undef : int[0];
// Returns the intersection point of a line defined by the first segment with a segment.
// If the segment doesn't intersect the line it returns undef
function line_segment_intersection(line,segment) = let( int = _general_line_intersection(line,segment))
int[2]<0 || int[2]>1 ? undef : int[0];
// Return true if simple polygon is in clockwise order, false otherwise.
// Results for complex (self-intersecting) polygon are indeterminate
function polygon_clockwise(path) =
let(
minx = min(array_subindex(path,0)),
lowind = search(minx, path, 0, 0),
lowpts = select(path, lowind),
miny = min(array_subindex(lowpts, 1)),
extreme_sub = search(miny, lowpts, 1, 1)[0],
extreme = select(lowind,extreme_sub)
)
det2( [select(path,extreme+1)-path[extreme], select(path, extreme-1)-path[extreme]])<0;
// End options
// simple rounded end
// roundover with specified radius and cutting back/extrapolation, including negative radius for flare
// "flat" with optional angle relative to object normal or relative to coordinate system
// "pointed" : [d,L], d is distance and L is extension
//
// what if path crosses itself (e.g. pentagram)?
function stroke(path, thickness, rounded=true, check_valid=false) =
let(
left_r = !rounded ? undef : get_stroke_width(thickness, "left"),
left_delta = rounded ? undef : get_stroke_width(thickness, "left"),
right_r = !rounded ? undef : get_stroke_width(thickness, "right"),
right_delta = rounded ? undef : get_stroke_width(thickness, "right"),
left_path = pathoffset(path, delta=left_delta, r=left_r, closed=false, check_valid=check_valid),
right_path = pathoffset(path, delta=right_delta, r=right_r, closed=false, check_valid=check_valid)
)
concat(left_path,reverse(right_path));
function get_stroke_width(thickness, side) =
side=="left" ? (is_list(thickness) ? max(thickness) : thickness/2)
: (is_list(thickness) ? min(thickness) : -thickness/2);
module stroke(path,thickness,rounded=true,closed=false,check_valid=true) {
if (closed) {
echo("vals",left_r, left_delta, right_r, right_delta);
left_r = !rounded ? undef : get_stroke_width(thickness, "left");
left_delta = rounded ? undef : get_stroke_width(thickness, "left");
right_r = !rounded ? undef : get_stroke_width(thickness, "right");
right_delta = rounded ? undef : get_stroke_width(thickness, "right");
polygon1 = pathoffset(path, delta=left_delta, r=left_r, closed=true, check_valid=check_valid);
polygon2 = pathoffset(path, delta=right_delta, r=right_r, closed=true, check_valid=check_valid);
if (polygon_clockwise(path))
difference(){
polygon(polygon1);
polygon(polygon2);
}
else
difference(){
polygon(polygon2);
polygon(polygon1);
}
}
else {
echo("open case");
polygon(stroke(path,thickness,rounded));
}
}
module stroke2(path, width) {
for (i=[1:len(path)-2])
translate(path[i])
circle(d=width);
for (i=[0:len(path)-2])
translate(path[i])
rot(from=BACK, to=path[i+1]-path[i])
left(width/2)
square([width, norm(path[i+1]-path[i])], center=false);
}
// Height is the total height
// It should be larger than r1+r2
//
// Negative value of r1 or r2 indicates flaring instead of rounding
//
function rounded_extrude_polyhedron(path,offsets, type, flip_faces, check_valid, offsetind=0, vertexcount=0, vertices=[], faces=[] )=
offsetind==len(offsets) ?
let( bottom = list_range(n=len(path),s=vertexcount),
oriented_bottom = !flip_faces ? bottom : reverse(bottom)
)
[vertices, concat(faces,[oriented_bottom])] :
let( this_offset = offsetind==0 ? offsets[0][0] : offsets[offsetind][0] - offsets[offsetind-1][0],
delta = type=="delta" ? this_offset : undef,
r = type=="round" ? this_offset : undef,
vertices_faces = pathoffset(path, r=r, delta=delta, closed=true, check_valid=check_valid,
return_faces=true, firstface_index=vertexcount, flip_faces=flip_faces)
)
rounded_extrude_polyhedron(vertices_faces[0], offsets, type, flip_faces, check_valid, offsetind+1, vertexcount+len(path),
vertices=concat(vertices, array_zip(vertices_faces[0],replist(offsets[offsetind][1],len(vertices_faces[0])))),
faces=concat(faces, vertices_faces[1]));
function compute_offsets(radius, edgetype, N,sign=1,zoff=0,extra=0) =
let( offsets = edgetype == "chamfer" ? [[-radius,sign*abs(radius)+zoff]] :
edgetype == "teardrop" ? concat([for(i=[1:N]) [radius*(cos(i*45/N)-1),zoff+sign*abs(radius)* sin(i*45/N)]],
[[-2*radius*(1-sqrt(2)/2), zoff+sign*abs(radius)]]):
/* round */ [for(i=[1:N]) [radius*(cos(i*90/N)-1), zoff+sign*abs(radius)*sin(i*90/N)]])
extra > 0 ? concat(offsets, [select(offsets,-1)+[0,sign*extra]]) : offsets;
// Module: rounded_extrude()
//
// Description:
// Takes a 2d path as input and extrudes it to a specified height with roundovers or chamfers at the ends.
// The rounding is accomplished by using pathoffset to shift the input path. The path is shifted multiple times
// in sequence to produce the profile (not multiple shifts from one parent), so coarse definition of the input path will
// multiply from the success shifts. If the result seems rough try increasing the number of points you use for your input.
// However, be aware that large numbers of points (especially when check_valid is true) can lead to lengthy run times.
// Multiple rounding shapes are available, including circular rounding, teardrop rounding, and chamfer "rounding".
// Also note that if the rounding radius is negative then the rounding will project outwards.
//
// Rounding options:
// * "circle": Circular rounding with radius as specified
// * "teardrop": 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": Chamfer the edge at 45 degrees. The radius specifies the height of the chamfer.
//
// Arguments:
// path = 2d path (list of points) to extrude
// height = total height (including rounded portions) of the output
// r1 = radius of roundover at bottom end (may be zero)
// r2 = radius of roundover at top end (may be zero)
// edge1 = type of rounding to apply to the bottom edge, one of "circle", "teardrop" or "chamfer". Default: "circle"
// edge2 = type of rounding to apply to top edge (same options as edge1). Default: "circle"
// offset_type1 = type of offset to use at bottom end, either "round" or "sharp". Default: "round"
// offset_type2 = type of offset to use at top end. Default: "round"
// check_valid = passed to pathoffset. Default: True
// steps = number of steps to use for roundovers.
module rounded_extrude(path, height, r1, r2, edge1="circle", edge2="circle", extra1=0, extra2=0,
offset_type1="round", offset_type2="round", steps=20, check_valid=true)
{
clockwise = polygon_clockwise(path);
flipR = clockwise ? 1 : -1;
assert(height>=0, "Height must be nonnegative");
middle = height-abs(r1)-abs(r2);
assert(middle>=0,"Specified radii are too large for extrusion height");
offsets1 = r1==0 ? [] : compute_offsets(flipR*r1,edge1,steps,-1,0,extra1);
initial_vertices_bot = array_zip(path, replist(0,len(path)));
vertices_faces_bot = rounded_extrude_polyhedron(path, offsets1, offset_type1, clockwise,check_valid,vertices=initial_vertices_bot);
top_start = len(vertices_faces_bot[0]);
offsets2 = r2==0 ? [] : compute_offsets(flipR*r2,edge2,steps,1,middle,extra2);
initial_vertices_top = array_zip(path, replist(middle,len(path)));
vertices_faces_top = rounded_extrude_polyhedron(path, offsets2, offset_type2, !clockwise,check_valid,vertexcount=top_start,
vertices=initial_vertices_top);
middle_faces = middle==0 ? [] :
[for(i=[0:len(path)-1]) let(oneface=[i, (i+1)%len(path), top_start+(i+1)%len(path), top_start+i])
!clockwise ? reverse(oneface) : oneface];
up(abs(r1))
polyhedron(concat(vertices_faces_bot[0],vertices_faces_top[0]),
faces=concat(vertices_faces_bot[1], vertices_faces_top[1], middle_faces));
}
function ngon(n, side=undef, r=undef, d=undef, or=undef, od=undef, ir=undef, id=undef, cp=[0,0], scale=[1,1], align="side") =
let(argcount = len(remove_undefs([side,r,d,or,od,ir,id])))
assert(argcount==1,"You must specify exactly one of 'ir', 'id', 'or', 'od', 'r', 'd', and 'side'")
let(
dtheta = 360/n,
theta_ofs = align=="side"? dtheta/2 : 0,
factor = is_def(side) || is_def(or) || is_def(od) ? 1 : 1/cos(180/n),
r = factor * (is_def(side) ? side/2/sin(180/n) :
is_def(d) ? d/2 :
is_def(id) ? id/2 :
is_def(ir) ? ir :
is_def(od) ? od /2 :
is_def(or) ? or :
r),
ff=echo(factor=factor,r=r, prer = r/factor)
)
[for(theta=[theta_ofs:dtheta:360-1e-12]) r*[scale.x*cos(theta),scale.y*sin(theta)]+cp];
// Function: star()
//
// Description:
// Produce the coordinates of an n-pointed star with specified radius or diameter. If you specify step
// it creates a star that could be drawn by connecting n points on a circle with lines where each line
// moves around the circle the specified number of steps. Setting steps=2 creates a star with very blunt points;
// The steps parameter must be smaller than n/2 and its maximum value produces the pointiest star.
// Other stars can be created by specifying the inner radius or diameter.
function star(n, r, d, ir, id, step, align="point") =
let(
r = get_radius(r=r, d=d),
count = len(remove_undefs([ir,id,step])),
stepOK = is_undef(step) || (step>1 && step<n/2)
)
assert(count==1, "Must specify exactly one if ir, id, step")
assert(stepOK, str("Parameter 'step' must be between 2 and ",floor(n/2)," for ",n," point star"))
let(
ir = get_radius(r=ir, d=id, dflt=is_def(step)?cos(180*step/n)/cos(180*(step-1)/n):0),
offset = align=="point" ? 0 : 180/n
)
[for(i=[0:2*n-1]) let(theta=180*i/n+offset, radius=(i%2)?ir:r) radius*[cos(theta), sin(theta)]];
/////////////////////////////////////////////////////////////////////////////
//
// End library functions
//
// Remaining stuff is testing calls
use<roundcorners.scad>
// Example 1
box = [[0,0], [0,50], [255,50], [255,0]];
rbox = roundcorners(box, curve="smooth", type="cut", all=4, $fn=36);
difference(){
rounded_extrude(rbox, height=50, r1=2, r2=1, steps = 22, edge1="teardrop", check_valid=false);
up(2)rounded_extrude(pathoffset(rbox, r=-2, closed=true), height=48, r1=4, r2=-1,steps=22,extra2=1,check_valid=false);
}
smallbox = [[0,0], [0,50], [75,50], [75,0]];
roundbox = roundcorners(smallbox, curve="smooth", type="cut", all=4);
back(100)
difference(){
rounded_extrude(roundbox, height=50, r1=2, r2=1, steps = 22, edge1="teardrop", check_valid=false);
up(2)rounded_extrude(pathoffset(roundbox, r=-2, closed=true), height=48, r1=4, r2=-1,steps=22,extra2=1,check_valid=false);
}
// Example 2
// Path turns 180 deg; Case not allowed
/*
path = [[0,0],[5,0],[3,0],[3,3]];
echo(pathoffset(path, r=1, closed=false, check_valid=true));
*/
// Example 3
/*
test = [[0,0],[10,0],[10,7],[0,7], [-1,-3]];
polygon(pathoffset(test,r=1.9, closed=true, check_valid=true,quality=1)); // This succeeds
//polygon(pathoffset(test,r=1.9, closed=true, check_valid=true,quality=1)); // This fails
%down(.1)polygon(test);
*/
// Example 4
/*
$fn=12;
star = star(5, r=22, ir=13);
rounded_star = roundcorners(array_zip(star, flatten(replist([.5,0],5))), curve="circle", type="cut");
polygon(pathoffset(rounded_star,r=8,closed=true,check_valid=true));
right(15)polygon(pathoffset(rounded_star,delta=8,closed=true,check_valid=true));
*/
// Example 5
/*
star = star(5, r=22, ir=13);
rounded_star = roundcorners(array_zip(star, flatten(replist([.5,0],5))), curve="circle", type="cut", $fn=12);
rounded_extrude(rounded_star, height=20, r1=4, r2=1, steps=15, edge1="round", edge2="round");
right(45) rounded_extrude(rounded_star, height=20, r1=-4, r2=1, steps=15, edge1="round", edge2="round");
// Check out the rounded corners on the chamfer
right(90) rounded_extrude(rounded_star, height=20, r1=4, r2=4, steps=15, edge1="teardrop", edge2="chamfer");
left(45)
difference(){
rounded_extrude((star), height=20, r1=4, r2=1, steps=15, edge1="sharp", edge2="sharp");
//up(2)rounded_extrude(pathoffset(reverse(rounded_star),r=-.5,closed=true), height=18, r1=1, r2=-1, steps=15,extra2=1);
up(2)rounded_extrude(pathoffset(reverse(star),r=-2,closed=true), height=18, r1=7, r2=-1, steps=15,extra2=1);
}
*/
// Example 6
/*
path = 2*[for(theta=[0:360]) [theta/75,sin(theta)]];
color("red")stroke(path,thickness=.1);
stroke(pathoffset(path, r=1.75), thickness=.1);
*/