Changed offset() to check polygon orientation when closed==true.

Changed rounded_sweep() to work with updated offset().
Added make_path_valid() and use it in rounded_sweep for better error
handling and to support single component regions.
Added divided box example to rounded_sweep.
Updated examples to work with updated offset().
This commit is contained in:
Adrian Mariano 2019-07-18 19:21:08 -04:00
parent 5abc7db530
commit 20eae2b5d2
2 changed files with 85 additions and 52 deletions

View file

@ -702,6 +702,27 @@ function is_region(x) = is_list(x) && is_path(x.x);
// Closes all paths within a given region.
function close_region(region, eps=EPSILON) = [for (path=region) close_path(path, eps=eps)];
// Function: make_path_valid()
// Usage:
// make_path_valid(path, [valid_dim], [closed])
// Description:
// Checks that the input is a path. If it is a region with one component, converts it to a path.
// valid_dim specfies the allowed dimension of the points in the path.
// If the path is closed, removed duplicate endpoint if present.
function make_path_valid(path,valid_dim=undef,closed=false) =
let( path = is_region(path) ?
assert(len(path)==1,"Region supplied as path does not have exactly one component")
path[0] :
assert(is_path(path), "Input is not a path") path,
dim = array_dim(path))
assert(dim[0]>1,"Path must have at least 2 points")
assert(len(dim)==2,"Invalid path: path is either a list of scalars or a list of matrices")
assert(is_def(dim[1]), "Invalid path: entries in the path have variable length")
let(valid=is_undef(valid_dim) || in_list(dim[1],valid_dim))
assert(valid, str("The points on the path have length ",dim[1]," but length must be ",
len(valid_dim)==1? valid_dim[0] : str("one of ",valid_dim)))
closed && approx(path[0],select(path,-1)) ? slice(path,0,-2) : path;
// Function: region_path_crossings()
// Usage:
@ -908,6 +929,18 @@ function _offset_region(
// Example(2D):
// star = star(5, r=100, ir=30);
// #stroke(closed=true, star);
// stroke(closed=true, offset(star, delta=10, closed=true));
// Example(2D):
// star = star(5, r=100, ir=30);
// #stroke(closed=true, star);
// stroke(closed=true, offset(star, delta=10, chamfer=true, closed=true));
// Example(2D):
// star = star(5, r=100, ir=30);
// #stroke(closed=true, star);
// stroke(closed=true, offset(star, r=10, closed=true));
// Example(2D):
// star = star(5, r=100, ir=30);
// #stroke(closed=true, star);
// stroke(closed=true, offset(star, delta=-10, closed=true));
// Example(2D):
// star = star(5, r=100, ir=30);
@ -917,46 +950,34 @@ function _offset_region(
// star = star(5, r=100, ir=30);
// #stroke(closed=true, star);
// stroke(closed=true, offset(star, r=-10, closed=true));
// Example(2D):
// star = star(5, r=100, ir=30);
// #stroke(closed=true, star);
// stroke(closed=true, offset(star, delta=10, closed=true));
// Example(2D):
// star = star(5, r=100, ir=30);
// #stroke(closed=true, star);
// stroke(closed=true, offset(star, delta=-10, chamfer=true, closed=true));
// Example(2D):
// star = star(5, r=100, ir=30);
// #stroke(closed=true, star);
// stroke(closed=true, offset(star, r=10, closed=true));
// Example(2D): This case needs `quality=2` for success
// test = [[0,0],[10,0],[10,7],[0,7], [-1,-3]];
// polygon(offset(test,r=1.9, closed=true, quality=2));
// //polygon(offset(test,r=1.9, closed=true, quality=1)); // Fails with erroneous 180 deg path error
// polygon(offset(test,r=-1.9, closed=true, quality=2));
// //polygon(offset(test,r=-1.9, closed=true, quality=1)); // Fails with erroneous 180 deg path error
// %down(.1)polygon(test);
// Example(2D): This case fails if `check_valid=true` when delta is large enough because segments are too close to the opposite side of the curve.
// star = star(5, r=22, ir=13);
// stroke(star,width=.1,closed=true);
// stroke(star,width=.2,closed=true);
// color("green")
// stroke(offset(star, delta=9, closed=true),width=.1,closed=true); // Works with check_valid=true (the default)
// stroke(offset(star, delta=-9, closed=true),width=.2,closed=true); // Works with check_valid=true (the default)
// color("red")
// stroke(offset(star, delta=10, closed=true, check_valid=false), // Fails if check_valid=true
// width=.1,closed=true);
// stroke(offset(star, delta=-10, closed=true, check_valid=false), // Fails if check_valid=true
// width=.2,closed=true);
// Example(2D): But if you use rounding with offset then you need `check_valid=true` when `r` is big enough. It works without the validity check as long as the offset shape retains a some of the straight edges at the star tip, but once the shape shrinks smaller than that, it fails. There is no simple way to get a correct result for the case with `r=10`, because as in the previous example, it will fail if you turn on validity checks.
// star = star(5, r=22, ir=13);
// color("green")
// stroke(offset(star, r=8, closed=true,check_valid=false), width=.1, closed=true);
// stroke(offset(star, r=-8, closed=true,check_valid=false), width=.1, closed=true);
// color("red")
// stroke(offset(star, r=10, closed=true,check_valid=false), width=.1, closed=true);
// stroke(offset(star, r=-10, closed=true,check_valid=false), width=.1, closed=true);
// Example(2D): The extra triangles in this example show that the validity check cannot be skipped
// ellipse = scale([20,4], p=circle(r=1));
// ellipse = scale([20,4], p=circle(r=1,$fn=64));
// stroke(ellipse, closed=true, width=0.3);
// stroke(offset(ellipse, r=-3, check_valid=false, closed=true), width=0.3, closed=true);
// Example(2D): The triangles are removed by the validity check
// ellipse = scale([20,4], p=circle(r=1));
// ellipse = scale([20,4], p=circle(r=1,$fn=64));
// stroke(ellipse, closed=true, width=0.3);
// stroke(offset(ellipse, r=-3, check_valid=true, closed=true), width=0.3, closed=true);
// Example(2D):
// Example(2D): Open path. The path moves from left to right and the positive offset shifts to the left of the initial red path.
// sinpath = 2*[for(theta=[-180:5:180]) [theta/4,45*sin(theta)]];
// #stroke(sinpath);
// stroke(offset(sinpath, r=17.5));
@ -994,7 +1015,8 @@ function offset(
let(
chamfer = is_def(r) ? false : chamfer,
quality = max(0,round(quality)),
d = is_def(r)? r : delta,
flip_dir = closed && !polygon_clockwise(path) ? -1 : 1,
d = flip_dir * (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(true,len(shiftsegs)),

View file

@ -441,7 +441,7 @@ function bezier_curve(P,N) =
// roundbox = round_corners(smallbox, curve="smooth", measure="cut", size=4, $fn=36);
// thickness=4;
// height=50;
// back_half(y=37, s=200)
// back_half(y=25, s=200)
// difference(){
// rounded_sweep(roundbox, height=height, bottom=["r",10,"type","teardrop"], top=["r",2], steps = 22, check_valid=false);
// up(thickness)
@ -450,6 +450,24 @@ function bezier_curve(P,N) =
// bottom=["r",6],
// top=["type","chamfer","angle",30,"height",-3,"extra",1,"check_valid",false]);
// }
// Example: A box with multiple sections and rounded dividers
// thickness = 2;
// box = ([[0,0], [0,50], [255,50], [255,0]]);
// cutpoints = [0, 125, 190, 255];
// rbox = round_corners(box, curve="smooth", measure="cut", size=4, $fn=36);
// back_half(y=25, s=700)
// difference(){
// rounded_sweep(rbox, height=50, check_valid=false, steps=22, bottom=rs_teardrop(r=2), top=rs_circle(r=1));
// up(thickness)
// for(i=[0:2]){
// ofs = i==1 ? 2 : 0;
// hole = round_corners([[cutpoints[i]-ofs,0], [cutpoints[i]-ofs,50], [cutpoints[i+1]+ofs, 50], [cutpoints[i+1]+ofs,0]],
// curve="smooth", measure="cut", size=4, $fn=36);
// rounded_sweep(offset(hole, r=-thickness, closed=true,check_valid=false),
// height=48, steps=22, check_valid=false, bottom=rs_circle(r=4), top=rs_circle(r=-1,extra=1));
//
// }
// }
// Example: Star shaped box
// star = star(5, r=22, ir=13);
// rounded_star = round_corners(zip(star, flatten(replist([.5,0],5))), curve="circle", measure="cut", $fn=12);
@ -458,7 +476,7 @@ function bezier_curve(P,N) =
// difference(){
// rounded_sweep(rounded_star, height=ht, bottom=["r",4], top=["r",1], steps=15);
// up(thickness)
// rounded_sweep(offset(rounded_star,r=thickness,closed=true),
// rounded_sweep(offset(rounded_star,r=-thickness,closed=true),
// height=ht-thickness, check_valid=false,
// bottom=rs_circle(r=7), top=rs_circle(r=-1, extra=1));
// }
@ -488,7 +506,7 @@ function bezier_curve(P,N) =
// up(1)
// rounded_sweep(offset(rhex,r=1), height=9.5, bottom=rs_circle(r=2), top=rs_teardrop(r=-4));
// }
module rounded_sweep(path, height, top=[], bottom=[], offset="round", r=undef, steps=16, quality=1, check_valid=true, offset_maxstep=1, extra=0,
module rounded_sweep(path, height, top=[], bottom=[], offset="round", r=0, steps=16, quality=1, check_valid=true, offset_maxstep=1, extra=0,
cut=undef, width=undef, joint=undef, k=0.75, angle=45, convexity=10)
{
// This function does the actual work of repeatedly calling offset() and concatenating the resulting face and vertex lists to produce
@ -514,13 +532,13 @@ module rounded_sweep(path, height, top=[], bottom=[], offset="round", r=undef, s
// 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,flipR,z_dir=1) =
function rounding_offsets(edgespec,z_dir=1) =
let(
edgetype = struct_val(edgespec, "type"),
extra = struct_val(edgespec,"extra"),
N = struct_val(edgespec, "steps"),
r = flipR * struct_val(edgespec,"r"),
cut = flipR * struct_val(edgespec,"cut"),
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]) :
@ -528,10 +546,10 @@ module rounded_sweep(path, height, top=[], bottom=[], offset="round", r=undef, s
undef,
chamf_angle = struct_val(edgespec, "angle"),
cheight = struct_val(edgespec, "height"),
cwidth = flipR * struct_val(edgespec, "width"),
cwidth = struct_val(edgespec, "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([flipR*struct_val(edgespec,"joint"),
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) :
@ -541,7 +559,7 @@ module rounded_sweep(path, height, top=[], bottom=[], offset="round", r=undef, s
false)
assert(argsOK,str("Invalid specification with type ",edgetype))
let(
offsets = edgetype == "custom" ? scale([-flipR,z_dir], slice(points,1,-1)) :
offsets = edgetype == "custom" ? scale([-1,z_dir], slice(points,1,-1)) :
edgetype == "chamfer" ? width==0 && 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)]]):
@ -554,8 +572,8 @@ module rounded_sweep(path, height, top=[], bottom=[], offset="round", r=undef, s
extra > 0 ? concat(offsets, [select(offsets,-1)+[0,z_dir*extra]]) : offsets;
argspec = [["r",0],
["extra",0],
argspec = [["r",r],
["extra",extra],
["type","circle"],
["check_valid",check_valid],
["quality",quality],
@ -570,13 +588,13 @@ module rounded_sweep(path, height, top=[], bottom=[], offset="round", r=undef, s
["k", k],
["points", []],
];
path = make_path_valid(path, [2], closed=true);
top = struct_set(argspec, top, grow=false);
bottom = struct_set(argspec, bottom, grow=false);
struct_echo(top,"top");
clockwise = polygon_clockwise(path);
flipR = clockwise ? 1 : -1;
assert(height>=0, "Height must be nonnegative");
@ -587,10 +605,8 @@ module rounded_sweep(path, height, top=[], bottom=[], offset="round", r=undef, s
assert(offsetsok,"Offsets must be one of \"round\" or \"delta\"");
*/
offsets_bot = rounding_offsets(bottom, flipR,-1);
offsets_top = rounding_offsets(top, flipR,1);
echo(ofstop = offsets_top);
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");
@ -616,11 +632,6 @@ module rounded_sweep(path, height, top=[], bottom=[], offset="round", r=undef, s
up(bottom_height)
polyhedron(concat(vertices_faces_bot[0],vertices_faces_top[0]),
faces=concat(vertices_faces_bot[1], vertices_faces_top[1], middle_faces),convexity=convexity);
echo(botv=vertices_faces_bot[0]);
echo(topv=vertices_faces_top[0]);
echo(fbot=vertices_faces_bot[1]);
echo(ftop=vertices_faces_top[1]);
echo(fmid=middle_faces);
}
function rs_circle(r,cut,extra,check_valid, quality,steps, offset_maxstep, offset) =