Removed redundant bezier_curve() function. Standardized formatting.

This commit is contained in:
Revar Desmera 2019-08-09 23:57:19 -07:00
parent 65b78f90ae
commit 5c7fc2eaf6

View file

@ -227,10 +227,15 @@ function round_corners(path, curve="circle", measure="cut", size=undef, k=0.5,
have_size ? " when 'size' is specified" : "when 'all' is not specified"
))
assert(len(path)>2,str("Path has length ",len(path),". Length must be 3 or more."))
assert(size_ok, is_list(size)?
(str("Input `size` has length ", len(size),". Length must be ",
(curve=="smooth"?"2 or ":""), len(path))) :
str("Input `size` is ",size," which is not a number"))
assert(size_ok,
is_list(size)? (
str(
"Input `size` has length ", len(size),
". Length must be ",
(curve=="smooth"?"2 or ":""), len(path)
)
) : str("Input `size` is ",size," which is not a number")
)
let(
dim = pathdim - 1 + have_size,
points = have_size ? path : subindex(path, [0:dim-1]),
@ -279,8 +284,7 @@ function round_corners(path, curve="circle", measure="cut", size=undef, k=0.5,
//
// k is the curvature parameter, ranging from 0 for very slow transition
// up to 1 for a sharp transition that doesn't have continuous curvature any more
function _smooth_bez_fill(points,k) =
[
function _smooth_bez_fill(points,k) = [
points[0],
lerp(points[1],points[0],k),
points[1],
@ -302,22 +306,24 @@ function _smooth_bez_fill(points,k) =
function _bezcorner(points, parm) =
let(
P = is_list(parm) ?
P = is_list(parm)? (
let(
d = parm[0],
k = parm[1],
prev = normalize(points[0]-points[1]),
next = normalize(points[2]-points[1]))
[
next = normalize(points[2]-points[1])
) [
points[1]+d*prev,
points[1]+k*d*prev,
points[1],
points[1]+k*d*next,
points[1]+d*next
] :
]
) : (
_smooth_bez_fill(points,parm),
N = max(3,$fn>0 ?$fn : ceil(bezier_segment_length(P)/$fs))
)
)
bezier_curve(P,N);
@ -334,9 +340,6 @@ function _circlecorner(points, parm) =
)
arc(segs(norm(start-center)), cp=center, points=[start,end]);
function bezier_curve(P,N) =
[for(i=[0:1:N-1]) bez_point(P, i/(N-1))];
// Module: rounded_sweep()
//
@ -518,30 +521,51 @@ 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=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)
{
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
// the inputs for the polyhedron module.
function make_polyhedron(path,offsets, offset_type, flip_faces, quality, check_valid, maxstep, offsetind=0, vertexcount=0, vertices=[], faces=[] )=
offsetind==len(offsets) ?
let( bottom = list_range(n=len(path),s=vertexcount),
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],
) [vertices, concat(faces,[oriented_bottom])]
) : (
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
)
assert(num_defined([r,delta])==1,"Must set `offset` to \"round\" or \"delta")
let(
vertices_faces = offset(path, r=r, delta=delta, closed=true, check_valid=check_valid, quality=quality, maxstep=maxstep,
return_faces=true, firstface_index=vertexcount, flip_faces=flip_faces)
vertices_faces = offset(
path, r=r, delta=delta, closed=true,
check_valid=check_valid, quality=quality,
maxstep=maxstep, return_faces=true,
firstface_index=vertexcount,
flip_faces=flip_faces
)
make_polyhedron(vertices_faces[0], offsets, offset_type, flip_faces, quality, check_valid, maxstep, offsetind+1, vertexcount+len(path),
vertices=concat(vertices, zip(vertices_faces[0],replist(offsets[offsetind][1],len(vertices_faces[0])))),
faces=concat(faces, vertices_faces[1]));
)
make_polyhedron(
vertices_faces[0], offsets, offset_type,
flip_faces, quality, check_valid, maxstep,
offsetind+1, vertexcount+len(path),
vertices=concat(
vertices,
zip(vertices_faces[0],replist(offsets[offsetind][1],len(vertices_faces[0])))
),
faces=concat(faces, vertices_faces[1])
)
);
// Produce edge profile curve from the edge specification
// z_dir is the direction multiplier (1 to build up, -1 to build down)
@ -555,37 +579,45 @@ module rounded_sweep(path, height, top=[], bottom=[], offset="round", r=0, steps
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,
edgetype=="chamfer"? first_defined([sqrt(2)*cut,r]) : undef,
chamf_angle = struct_val(edgespec, "angle"),
cheight = struct_val(edgespec, "height"),
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([struct_val(edgespec,"joint"),
16*cut/sqrt(2)/(1+4*k)]),
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 == "custom"? points[0]==[0,0] :
false)
false
)
assert(argsOK,str("Invalid specification with type ",edgetype))
let(
offsets = edgetype == "custom" ? scale([-1,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)]]):
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)
1, -1
)
)
extra > 0? concat(offsets, [select(offsets,-1)+[0,z_dir*extra]]) : offsets;
argspec = [["r",r],
argspec = [
["r",r],
["extra",extra],
["type","circle"],
["check_valid",check_valid],
@ -611,12 +643,11 @@ module rounded_sweep(path, height, top=[], bottom=[], offset="round", r=0, steps
assert(height>=0, "Height must be nonnegative");
/* This code does not work. It hits the error in make_polyhedron from offset being wrong
before this code executes. Had to move the test into make_polyhedron, which is ugly since it's in the loop
offsetsok = in_list(struct_val(top, "offset"),["round","delta"])
&& in_list(struct_val(bottom, "offset"),["round","delta"]);
assert(offsetsok,"Offsets must be one of \"round\" or \"delta\"");
*/
// This code does not work. It hits the error in make_polyhedron from offset being wrong
// before this code executes. Had to move the test into make_polyhedron, which is ugly since it's in the loop
//offsetsok = in_list(struct_val(top, "offset"),["round","delta"]) &&
// in_list(struct_val(bottom, "offset"),["round","delta"]);
//assert(offsetsok,"Offsets must be one of \"round\" or \"delta\"");
offsets_bot = rounding_offsets(bottom, -1);
offsets_top = rounding_offsets(top, 1);
@ -626,31 +657,50 @@ module rounded_sweep(path, height, top=[], bottom=[], offset="round", r=0, steps
top_height = len(offsets_top)==0 ? 0 : abs(select(offsets_top,-1)[1]) - struct_val(top,"extra");
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,")"));
assert(
middle>=0, str(
"Specified end treatments (bottom height = ",bottom_height,
" top_height = ",top_height,") are too large for extrusion height (",height,")"
)
);
initial_vertices_bot = path3d(path);
vertices_faces_bot = make_polyhedron(path, offsets_bot, struct_val(bottom,"offset"), clockwise,
struct_val(bottom,"quality"),struct_val(bottom,"check_valid"),struct_val(bottom,"offset_maxstep"),
vertices=initial_vertices_bot);
vertices_faces_bot = make_polyhedron(
path, offsets_bot, struct_val(bottom,"offset"), clockwise,
struct_val(bottom,"quality"),
struct_val(bottom,"check_valid"),
struct_val(bottom,"offset_maxstep"),
vertices=initial_vertices_bot
);
top_start_ind = len(vertices_faces_bot[0]);
initial_vertices_top = zip(path, replist(middle,len(path)));
vertices_faces_top = make_polyhedron(path, translate_points(offsets_top,[0,middle]), struct_val(top,"offset"), !clockwise,
struct_val(top,"quality"),struct_val(top,"check_valid"),struct_val(top,"offset_maxstep"),
vertexcount=top_start_ind, vertices=initial_vertices_top);
middle_faces = middle==0 ? [] :
[for(i=[0:len(path)-1]) let(oneface=[i, (i+1)%len(path), top_start_ind+(i+1)%len(path), top_start_ind+i])
!clockwise ? reverse(oneface) : oneface];
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);
vertices_faces_top = make_polyhedron(
path, translate_points(offsets_top,[0,middle]),
struct_val(top,"offset"), !clockwise,
struct_val(top,"quality"),
struct_val(top,"check_valid"),
struct_val(top,"offset_maxstep"),
vertexcount=top_start_ind,
vertices=initial_vertices_top
);
middle_faces = middle==0 ? [] : [
for(i=[0:len(path)-1]) let(
oneface=[i, (i+1)%len(path), top_start_ind+(i+1)%len(path), top_start_ind+i]
) !clockwise ? reverse(oneface) : oneface
];
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
);
}
}
function rs_circle(r,cut,extra,check_valid, quality,steps, offset_maxstep, offset) =
assert(num_defined([r,cut])==1, "Must define exactly one of `r` and `cut`")
_remove_undefined_vals(
[
_remove_undefined_vals([
"type", "circle",
"r",r,
"cut",cut,
@ -664,8 +714,7 @@ function rs_circle(r,cut,extra,check_valid, quality,steps, offset_maxstep, offse
function rs_teardrop(r,cut,extra,check_valid, quality,steps, offset_maxstep, offset) =
assert(num_defined([r,cut])==1, "Must define exactly one of `r` and `cut`")
_remove_undefined_vals(
[
_remove_undefined_vals([
"type", "teardrop",
"r",r,
"cut",cut,
@ -680,8 +729,7 @@ function rs_teardrop(r,cut,extra,check_valid, quality,steps, offset_maxstep, off
function rs_chamfer(height, width, cut, angle, extra,check_valid, quality,steps, offset_maxstep, offset) =
let(ok = (is_def(cut) && num_defined([height,width])==0) || num_defined([height,width])>0)
assert(ok, "Must define `cut`, or one or both of `width` and `height`")
_remove_undefined_vals(
[
_remove_undefined_vals([
"type", "chamfer",
"width",width,
"height",height,
@ -697,8 +745,7 @@ function rs_chamfer(height, width, cut, angle, extra,check_valid, quality,steps,
function rs_smooth(cut, joint, k, extra,check_valid, quality,steps, offset_maxstep, offset) =
assert(num_defined([joint,cut])==1, "Must define exactly one of `joint` and `cut`")
_remove_undefined_vals(
[
_remove_undefined_vals([
"type", "smooth",
"joint",joint,
"k",k,
@ -713,8 +760,7 @@ function rs_smooth(cut, joint, k, extra,check_valid, quality,steps, offset_maxst
function rs_custom(points, extra,check_valid, quality,steps, offset_maxstep, offset) =
//assert(is_path(points),"Custom point list is not valid")
_remove_undefined_vals(
[
_remove_undefined_vals([
"type", "custom",
"points", points,
"extra",extra,
@ -878,8 +924,16 @@ function offset_stroke(path, width=1, rounded=true, start="flat", end="flat", ch
left_delta = rounded? undef : width[0],
right_r = !rounded? undef : width[1],
right_delta = rounded? undef : width[1],
left_path = offset(path, delta=left_delta, r=left_r, closed=closed, check_valid=check_valid, quality=quality, chamfer=chamfer, maxstep=maxstep),
right_path = offset(path, delta=right_delta, r=right_r, closed=closed, check_valid=check_valid, quality=quality,chamfer=chamfer, maxstep=maxstep)
left_path = offset(
path, delta=left_delta, r=left_r, closed=closed,
check_valid=check_valid, quality=quality,
chamfer=chamfer, maxstep=maxstep
),
right_path = offset(
path, delta=right_delta, r=right_r, closed=closed,
check_valid=check_valid, quality=quality,
chamfer=chamfer, maxstep=maxstep
)
)
closed? [left_path, right_path] :
let(
@ -888,8 +942,12 @@ function offset_stroke(path, width=1, rounded=true, start="flat", end="flat", ch
clipping_ok = startpath[1]+endpath[2]<=len(left_path) && startpath[2]+endpath[1]<=len(right_path)
)
assert(clipping_ok, "End treatment removed the whole stroke")
concat(slice(left_path,startpath[1],-1-endpath[2]), endpath[0],
reverse(slice(right_path,startpath[2],-1-endpath[1])),startpath[0]);
concat(
slice(left_path,startpath[1],-1-endpath[2]),
endpath[0],
reverse(slice(right_path,startpath[2],-1-endpath[1])),
startpath[0]
);
function os_pointed(loc=0,dist) =
@ -917,7 +975,8 @@ function os_round(cut, angle, abs_angle, k) =
function os_flat(angle, abs_angle) =
let( acount = num_defined([angle,abs_angle]),
let(
acount = num_defined([angle,abs_angle]),
use_angle = first_defined([angle,abs_angle,0])
)
assert(acount<2, "You must define only one of `angle` and `abs_angle`")
@ -930,14 +989,19 @@ function os_flat(angle, abs_angle) =
// Return angle in (-90,90] required to map line1 onto line2 (lines specified as lists of two points)
function angle_between_lines(line1,line2) = let(angle = atan2(det2([line1,line2]),line1*line2))
function angle_between_lines(line1,line2) =
let(angle = atan2(det2([line1,line2]),line1*line2))
angle > 90 ? angle-180 :
angle <= -90 ? angle+180 :
angle;
function _parse_stroke_end(spec) =
is_string(spec) ? assert(in_list(spec,["flat","round","pointed"]),str("Unknown end string specification \"", spec,"\". Must be \"flat\", \"round\", or \"pointed\""))
is_string(spec)?
assert(
in_list(spec,["flat","round","pointed"]),
str("Unknown end string specification \"", spec,"\". Must be \"flat\", \"round\", or \"pointed\"")
)
[["type", spec]] :
struct_set([], spec);
@ -955,35 +1019,45 @@ function _stroke_end(width,left, right, spec) =
)
type == "round"? [arc(points=[right[0],normal_pt,left[0]],N=50),1,1] :
type == "pointed"? [[normal_pt],0,0] :
type == "shifted_point" ? let( shiftedcenter = center + width_dir * parallel_dir * struct_val(spec, "loc"))
[[shiftedcenter+normal_dir*struct_val(spec, "dist")],0,0] :
type == "shifted_point"? (
let(shiftedcenter = center + width_dir * parallel_dir * struct_val(spec, "loc"))
[[shiftedcenter+normal_dir*struct_val(spec, "dist")],0,0]
) :
// Remaining types all support angled cutoff, so compute that
assert(abs(user_angle)<=90, "End angle must be in [-90,90]")
let(
angle = struct_val(spec,"absolute") ? angle_between_lines(left[0]-right[0],[cos(user_angle),sin(user_angle)])
: user_angle,
angle = struct_val(spec,"absolute")?
angle_between_lines(left[0]-right[0],[cos(user_angle),sin(user_angle)]) :
user_angle,
endseg = [center, rotate_points2d([left[0]],angle, cp=center)[0]],
intright = angle>0,
pathclip = _path_line_intersection(intright? right : left, endseg),
pathextend = line_intersection(endseg, select(intright? left:right,0,1))
)
type == "flat" ? (intright ? [[pathclip[0], pathextend], 1, pathclip[1]] :
[[pathextend, pathclip[0]], pathclip[1],1]) :
type == "roundover" ?
type == "flat"? (
intright?
[[pathclip[0], pathextend], 1, pathclip[1]] :
[[pathextend, pathclip[0]], pathclip[1],1]
) :
type == "roundover"? (
let(
bez_k = struct_val(spec,"k"),
cut = struct_val(spec,"cut"),
cutleft = cut[0],
cutright = cut[1],
// Create updated paths taking into account clipping for end rotation
newright = intright ? concat([pathclip[0]],select(right,pathclip[1],-1)) :
newright = intright?
concat([pathclip[0]],select(right,pathclip[1],-1)) :
concat([pathextend],select(right,1,-1)),
newleft = !intright ? concat([pathclip[0]],select(left,pathclip[1],-1)) :
newleft = !intright?
concat([pathclip[0]],select(left,pathclip[1],-1)) :
concat([pathextend],select(left,1,-1)),
// calculate corner angles, which are different when the cut is negative (outside corner)
leftangle = cutleft>=0 ? vector_angle([newleft[1],newleft[0],newright[0]])/2 :
leftangle = cutleft>=0?
vector_angle([newleft[1],newleft[0],newright[0]])/2 :
90-vector_angle([newleft[1],newleft[0],newright[0]])/2,
rightangle = cutright>=0 ? vector_angle([newright[1],newright[0],newleft[0]])/2 :
rightangle = cutright>=0?
vector_angle([newright[1],newright[0],newleft[0]])/2 :
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),
@ -998,17 +1072,21 @@ function _stroke_end(width,left, right, spec) =
assert(roundover_fits,"Roundover too large to fit")
let(
angled_dir = normalize(newleft[0]-newright[0]),
nPleft = [leftcorner - jointleft*angled_dir,
nPleft = [
leftcorner - jointleft*angled_dir,
leftcorner,
pathcutleft[0]],
nPright = [pathcutright[0],
pathcutleft[0]
],
nPright = [
pathcutright[0],
rightcorner,
rightcorner + jointright*angled_dir],
rightcorner + jointright*angled_dir
],
leftcurve = _bezcorner(nPleft, bez_k),
rightcurve = _bezcorner(nPright, bez_k)
)
[concat(rightcurve, leftcurve), leftdelete, rightdelete] :
[[],0,0]; // This case shouldn't occur
[concat(rightcurve, leftcurve), leftdelete, rightdelete]
) : [[],0,0]; // This case shouldn't occur
// returns [intersection_pt, index of first point in path after the intersection]
function _path_line_intersection(path, line, ind=0) =
@ -1016,15 +1094,24 @@ function _path_line_intersection(path, line, ind=0) =
let(intersect=line_segment_intersection(line, select(path,ind,ind+1)))
// If it intersects the segment excluding it's final point, then we're done
// The final point is treated as part of the next segment
is_def(intersect) && intersect != path[ind+1] ? [intersect, ind+1] :
is_def(intersect) && intersect != path[ind+1]?
[intersect, ind+1] :
_path_line_intersection(path, line, ind+1);
module offset_stroke(path, width=1, rounded=true, start, end, check_valid=true, quality=1, maxstep=0.1, chamfer=false, closed=false)
{
result = offset_stroke(path, width=width, rounded=rounded, start=start, end=end, check_valid=check_valid, quality=quality,
maxstep=maxstep, chamfer=chamfer, closed=closed);
if (closed) region(result);
else polygon(result);
result = offset_stroke(
path, width=width, rounded=rounded,
start=start, end=end,
check_valid=check_valid, quality=quality,
maxstep=maxstep, chamfer=chamfer,
closed=closed
);
if (closed) {
region(result);
} else {
polygon(result);
}
}