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

@ -206,14 +206,14 @@ function round_corners(path, curve="circle", measure="cut", size=undef, k=0.5,
(curve=="circle" && measure=="radius") || (curve=="circle" && measure=="radius") ||
(curve=="smooth" && measure=="joint") (curve=="smooth" && measure=="joint")
), ),
path = is_region(path) ? path = is_region(path)?
assert(len(path)==1, "Region supplied as path does not have exactly one component") assert(len(path)==1, "Region supplied as path does not have exactly one component")
path[0] : path, path[0] : path,
pathdim = array_dim(path,1), pathdim = array_dim(path,1),
have_size = size==undef ? 0 : 1, have_size = size==undef ? 0 : 1,
pathsize_ok = is_num(pathdim) && pathdim >= 3-have_size && pathdim <= 4-have_size, pathsize_ok = is_num(pathdim) && pathdim >= 3-have_size && pathdim <= 4-have_size,
size_ok = !have_size || is_num(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))
) )
assert(curve=="smooth" || curve=="circle", "Unknown 'curve' setting in round_corners") assert(curve=="smooth" || curve=="circle", "Unknown 'curve' setting in round_corners")
assert(measureok, curve=="circle"? assert(measureok, curve=="circle"?
@ -226,17 +226,22 @@ function round_corners(path, curve="circle", measure="cut", size=undef, k=0.5,
2+have_size, " or ", 3+have_size, 2+have_size, " or ", 3+have_size,
have_size ? " when 'size' is specified" : "when 'all' is not specified" 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(len(path)>2,str("Path has length ",len(path),". Length must be 3 or more."))
assert(size_ok, is_list(size)? assert(size_ok,
(str("Input `size` has length ", len(size),". Length must be ", is_list(size)? (
(curve=="smooth"?"2 or ":""), len(path))) : str(
str("Input `size` is ",size," which is not a number")) "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( let(
dim = pathdim - 1 + have_size, dim = pathdim - 1 + have_size,
points = have_size ? path : subindex(path, [0:dim-1]), points = have_size ? path : subindex(path, [0:dim-1]),
parm = have_size && is_list(size) && len(size)>2 ? size : parm = have_size && is_list(size) && len(size)>2? size :
have_size ? replist(size, len(path)) : have_size? replist(size, len(path)) :
subindex(path, dim), subindex(path, dim),
// dk will be a list of parameters, for the "smooth" curve the distance and curvature parameter pair, // dk will be a list of parameters, for the "smooth" curve the distance and curvature parameter pair,
// and for the "circle" curve, distance and radius. // and for the "circle" curve, distance and radius.
dk = [ dk = [
@ -279,14 +284,13 @@ 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 // 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 // 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],
points[0], lerp(points[1],points[0],k),
lerp(points[1],points[0],k), points[1],
points[1], lerp(points[1],points[2],k),
lerp(points[1],points[2],k), points[2],
points[2], ];
];
// Computes the points of a continuous curvature roundover given as input // Computes the points of a continuous curvature roundover given as input
// the list of 3 points defining the corner and a parameter specification // the list of 3 points defining the corner and a parameter specification
@ -301,22 +305,24 @@ function _smooth_bez_fill(points,k) =
// to calculate the point count. // to calculate the point count.
function _bezcorner(points, parm) = function _bezcorner(points, parm) =
let( let(
P = is_list(parm) ? P = is_list(parm)? (
let( let(
d = parm[0], d = parm[0],
k = parm[1], k = parm[1],
prev = normalize(points[0]-points[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]+d*prev,
points[1]+k*d*prev, points[1]+k*d*prev,
points[1], points[1],
points[1]+k*d*next, points[1]+k*d*next,
points[1]+d*next points[1]+d*next
] : ]
) : (
_smooth_bez_fill(points,parm), _smooth_bez_fill(points,parm),
N = max(3,$fn>0 ?$fn : ceil(bezier_segment_length(P)/$fs)) N = max(3,$fn>0 ?$fn : ceil(bezier_segment_length(P)/$fs))
)
) )
bezier_curve(P,N); bezier_curve(P,N);
@ -329,14 +335,11 @@ function _circlecorner(points, parm) =
prev = normalize(points[0]-points[1]), prev = normalize(points[0]-points[1]),
next = normalize(points[2]-points[1]), next = normalize(points[2]-points[1]),
center = r/sin(angle) * normalize(prev+next)+points[1], center = r/sin(angle) * normalize(prev+next)+points[1],
start = points[1]+prev*d, start = points[1]+prev*d,
end = points[1]+next*d end = points[1]+next*d
) )
arc(segs(norm(start-center)), cp=center, points=[start,end]); 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() // Module: rounded_sweep()
// //
@ -518,217 +521,260 @@ function bezier_curve(P,N) =
// up(1) // up(1)
// rounded_sweep(offset(rhex,r=1), height=9.5, bottom=rs_circle(r=2), top=rs_teardrop(r=-4)); // 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, module rounded_sweep(
cut=undef, width=undef, joint=undef, k=0.75, angle=45, convexity=10) path, height,
{ top=[], bottom=[],
// This function does the actual work of repeatedly calling offset() and concatenating the resulting face and vertex lists to produce offset="round", r=0, steps=16,
// the inputs for the polyhedron module. quality=1, check_valid=true,
function make_polyhedron(path,offsets, offset_type, flip_faces, quality, check_valid, maxstep, offsetind=0, vertexcount=0, vertices=[], faces=[] )= offset_maxstep=1, extra=0,
offsetind==len(offsets) ? cut=undef, width=undef,
let( bottom = list_range(n=len(path),s=vertexcount), joint=undef, k=0.75, angle=45,
oriented_bottom = !flip_faces ? bottom : reverse(bottom) convexity=10
) ) {
[vertices, concat(faces,[oriented_bottom])] : // This function does the actual work of repeatedly calling offset() and concatenating the resulting face and vertex lists to produce
let( this_offset = offsetind==0 ? offsets[0][0] : offsets[offsetind][0] - offsets[offsetind-1][0], // the inputs for the polyhedron module.
delta = offset_type=="delta" ? this_offset : undef, function make_polyhedron(path,offsets, offset_type, flip_faces, quality, check_valid, maxstep, offsetind=0, vertexcount=0, vertices=[], faces=[] )=
r = offset_type=="round" ? this_offset : undef offsetind==len(offsets)? (
) let(
assert(num_defined([r,delta])==1,"Must set `offset` to \"round\" or \"delta") bottom = list_range(n=len(path),s=vertexcount),
let( oriented_bottom = !flip_faces? bottom : reverse(bottom)
vertices_faces = offset(path, r=r, delta=delta, closed=true, check_valid=check_valid, quality=quality, maxstep=maxstep, ) [vertices, concat(faces,[oriented_bottom])]
return_faces=true, firstface_index=vertexcount, flip_faces=flip_faces) ) : (
) let(
make_polyhedron(vertices_faces[0], offsets, offset_type, flip_faces, quality, check_valid, maxstep, offsetind+1, vertexcount+len(path), this_offset = offsetind==0? offsets[0][0] : offsets[offsetind][0] - offsets[offsetind-1][0],
vertices=concat(vertices, zip(vertices_faces[0],replist(offsets[offsetind][1],len(vertices_faces[0])))), delta = offset_type=="delta"? this_offset : undef,
faces=concat(faces, vertices_faces[1])); 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
)
)
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 // Produce edge profile curve from the edge specification
// z_dir is the direction multiplier (1 to build up, -1 to build down) // z_dir is the direction multiplier (1 to build up, -1 to build down)
function rounding_offsets(edgespec,z_dir=1) = function rounding_offsets(edgespec,z_dir=1) =
let( let(
edgetype = struct_val(edgespec, "type"), edgetype = struct_val(edgespec, "type"),
extra = struct_val(edgespec,"extra"), extra = struct_val(edgespec,"extra"),
N = struct_val(edgespec, "steps"), N = struct_val(edgespec, "steps"),
r = struct_val(edgespec,"r"), r = struct_val(edgespec,"r"),
cut = struct_val(edgespec,"cut"), cut = struct_val(edgespec,"cut"),
k = struct_val(edgespec,"k"), k = struct_val(edgespec,"k"),
radius = in_list(edgetype,["circle","teardrop"]) ? radius = in_list(edgetype,["circle","teardrop"])?
first_defined([cut/(sqrt(2)-1),r]) : first_defined([cut/(sqrt(2)-1),r]) :
edgetype=="chamfer" ? first_defined([sqrt(2)*cut,r]) : edgetype=="chamfer"? first_defined([sqrt(2)*cut,r]) : undef,
undef, chamf_angle = struct_val(edgespec, "angle"),
chamf_angle = struct_val(edgespec, "angle"), cheight = struct_val(edgespec, "height"),
cheight = struct_val(edgespec, "height"), cwidth = struct_val(edgespec, "width"),
cwidth = struct_val(edgespec, "width"), chamf_width = first_defined([cut/cos(chamf_angle), cwidth, cheight*tan(chamf_angle)]),
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)]),
chamf_height = first_defined([cut/sin(chamf_angle),cheight, cwidth/tan(chamf_angle)]), joint = first_defined([
joint = first_defined([struct_val(edgespec,"joint"), struct_val(edgespec,"joint"),
16*cut/sqrt(2)/(1+4*k)]), 16*cut/sqrt(2)/(1+4*k)
points = struct_val(edgespec, "points"), ]),
argsOK = in_list(edgetype,["circle","teardrop"]) ? is_def(radius) : points = struct_val(edgespec, "points"),
edgetype == "chamfer" ? angle>0 && angle<90 && num_defined([chamf_height,chamf_width])==2 : argsOK = in_list(edgetype,["circle","teardrop"])? is_def(radius) :
edgetype == "smooth" ? num_defined([k,joint])==2 : edgetype == "chamfer"? angle>0 && angle<90 && num_defined([chamf_height,chamf_width])==2 :
edgetype == "custom" ? points[0]==[0,0] : edgetype == "smooth"? num_defined([k,joint])==2 :
false) edgetype == "custom"? points[0]==[0,0] :
assert(argsOK,str("Invalid specification with type ",edgetype)) false
let( )
offsets = edgetype == "custom" ? scale([-1,z_dir], slice(points,1,-1)) : assert(argsOK,str("Invalid specification with type ",edgetype))
edgetype == "chamfer" ? width==0 && height==0 ? [] : [[-chamf_width,z_dir*abs(chamf_height)]] : let(
edgetype == "teardrop" ? radius==0 ? [] : concat([for(i=[1:N]) [radius*(cos(i*45/N)-1),z_dir*abs(radius)* sin(i*45/N)]], offsets =
[[-2*radius*(1-sqrt(2)/2), z_dir*abs(radius)]]): edgetype == "custom"? scale([-1,z_dir], slice(points,1,-1)) :
edgetype == "circle" ? radius==0 ? [] : [for(i=[1:N]) [radius*(cos(i*90/N)-1), z_dir*abs(radius)*sin(i*90/N)]] : edgetype == "chamfer"? width==0 && height==0? [] : [[-chamf_width,z_dir*abs(chamf_height)]] :
/* smooth */ joint==0 ? [] : edgetype == "teardrop"? (
select( radius==0? [] : concat(
_bezcorner([[0,0],[0,z_dir*abs(joint)],[-joint,z_dir*abs(joint)]], k, $fn=N+2), [for(i=[1:N]) [radius*(cos(i*45/N)-1),z_dir*abs(radius)* sin(i*45/N)]],
1, -1) [[-2*radius*(1-sqrt(2)/2), z_dir*abs(radius)]]
) )
extra > 0 ? concat(offsets, [select(offsets,-1)+[0,z_dir*extra]]) : offsets; ) :
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
)
)
extra > 0? concat(offsets, [select(offsets,-1)+[0,z_dir*extra]]) : offsets;
argspec = [
argspec = [["r",r], ["r",r],
["extra",extra], ["extra",extra],
["type","circle"], ["type","circle"],
["check_valid",check_valid], ["check_valid",check_valid],
["quality",quality], ["quality",quality],
["offset_maxstep", offset_maxstep], ["offset_maxstep", offset_maxstep],
["steps",steps], ["steps",steps],
["offset",offset], ["offset",offset],
["width",width], ["width",width],
["height",undef], ["height",undef],
["angle",angle], ["angle",angle],
["cut",cut], ["cut",cut],
["joint",joint], ["joint",joint],
["k", k], ["k", k],
["points", []], ["points", []],
]; ];
path = check_and_fix_path(path, [2], closed=true); path = check_and_fix_path(path, [2], closed=true);
top = struct_set(argspec, top, grow=false);
bottom = struct_set(argspec, bottom, grow=false);
clockwise = polygon_clockwise(path); top = struct_set(argspec, top, grow=false);
bottom = struct_set(argspec, bottom, grow=false);
assert(height>=0, "Height must be nonnegative"); clockwise = polygon_clockwise(path);
/* This code does not work. It hits the error in make_polyhedron from offset being wrong assert(height>=0, "Height must be nonnegative");
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);
// "Extra" height enlarges the result beyond the requested height, so subtract it // This code does not work. It hits the error in make_polyhedron from offset being wrong
bottom_height = len(offsets_bot)==0 ? 0 : abs(select(offsets_bot,-1)[1]) - struct_val(bottom,"extra"); // before this code executes. Had to move the test into make_polyhedron, which is ugly since it's in the loop
top_height = len(offsets_top)==0 ? 0 : abs(select(offsets_top,-1)[1]) - struct_val(top,"extra"); //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\"");
middle = height-bottom_height-top_height; offsets_bot = rounding_offsets(bottom, -1);
assert(middle>=0,str("Specified end treatments (bottom height = ",bottom_height, offsets_top = rounding_offsets(top, 1);
" 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, // "Extra" height enlarges the result beyond the requested height, so subtract it
struct_val(bottom,"quality"),struct_val(bottom,"check_valid"),struct_val(bottom,"offset_maxstep"), bottom_height = len(offsets_bot)==0 ? 0 : abs(select(offsets_bot,-1)[1]) - struct_val(bottom,"extra");
vertices=initial_vertices_bot); top_height = len(offsets_top)==0 ? 0 : abs(select(offsets_top,-1)[1]) - struct_val(top,"extra");
top_start_ind = len(vertices_faces_bot[0]); middle = height-bottom_height-top_height;
initial_vertices_top = zip(path, replist(middle,len(path))); assert(
vertices_faces_top = make_polyhedron(path, translate_points(offsets_top,[0,middle]), struct_val(top,"offset"), !clockwise, middle>=0, str(
struct_val(top,"quality"),struct_val(top,"check_valid"),struct_val(top,"offset_maxstep"), "Specified end treatments (bottom height = ",bottom_height,
vertexcount=top_start_ind, vertices=initial_vertices_top); " top_height = ",top_height,") are too large for extrusion height (",height,")"
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]; initial_vertices_bot = path3d(path);
up(bottom_height)
polyhedron(concat(vertices_faces_bot[0],vertices_faces_top[0]), vertices_faces_bot = make_polyhedron(
faces=concat(vertices_faces_bot[1], vertices_faces_top[1], middle_faces),convexity=convexity); 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
);
}
} }
function rs_circle(r,cut,extra,check_valid, quality,steps, offset_maxstep, offset) = 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`") assert(num_defined([r,cut])==1, "Must define exactly one of `r` and `cut`")
_remove_undefined_vals( _remove_undefined_vals([
[ "type", "circle",
"type", "circle", "r",r,
"r",r, "cut",cut,
"cut",cut, "extra",extra,
"extra",extra, "check_valid",check_valid,
"check_valid",check_valid, "quality", quality,
"quality", quality, "steps", steps,
"steps", steps, "offset_maxstep", offset_maxstep,
"offset_maxstep", offset_maxstep, "offset", offset
"offset", offset ]);
]);
function rs_teardrop(r,cut,extra,check_valid, quality,steps, offset_maxstep, offset) = 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`") assert(num_defined([r,cut])==1, "Must define exactly one of `r` and `cut`")
_remove_undefined_vals( _remove_undefined_vals([
[ "type", "teardrop",
"type", "teardrop", "r",r,
"r",r, "cut",cut,
"cut",cut, "extra",extra,
"extra",extra, "check_valid",check_valid,
"check_valid",check_valid, "quality", quality,
"quality", quality, "steps", steps,
"steps", steps, "offset_maxstep", offset_maxstep,
"offset_maxstep", offset_maxstep, "offset", offset
"offset", offset ]);
]);
function rs_chamfer(height, width, cut, angle, extra,check_valid, quality,steps, offset_maxstep, offset) = 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) 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`") assert(ok, "Must define `cut`, or one or both of `width` and `height`")
_remove_undefined_vals( _remove_undefined_vals([
[ "type", "chamfer",
"type", "chamfer", "width",width,
"width",width, "height",height,
"height",height, "cut",cut,
"cut",cut, "angle",angle,
"angle",angle, "extra",extra,
"extra",extra, "check_valid",check_valid,
"check_valid",check_valid, "quality", quality,
"quality", quality, "steps", steps,
"steps", steps, "offset_maxstep", offset_maxstep,
"offset_maxstep", offset_maxstep, "offset", offset
"offset", offset ]);
]);
function rs_smooth(cut, joint, k, extra,check_valid, quality,steps, offset_maxstep, offset) = 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`") assert(num_defined([joint,cut])==1, "Must define exactly one of `joint` and `cut`")
_remove_undefined_vals( _remove_undefined_vals([
[ "type", "smooth",
"type", "smooth", "joint",joint,
"joint",joint, "k",k,
"k",k, "cut",cut,
"cut",cut, "extra",extra,
"extra",extra, "check_valid",check_valid,
"check_valid",check_valid, "quality", quality,
"quality", quality, "steps", steps,
"steps", steps, "offset_maxstep", offset_maxstep,
"offset_maxstep", offset_maxstep, "offset", offset
"offset", offset ]);
]);
function rs_custom(points, extra,check_valid, quality,steps, offset_maxstep, offset) = function rs_custom(points, extra,check_valid, quality,steps, offset_maxstep, offset) =
//assert(is_path(points),"Custom point list is not valid") //assert(is_path(points),"Custom point list is not valid")
_remove_undefined_vals( _remove_undefined_vals([
[ "type", "custom",
"type", "custom", "points", points,
"points", points, "extra",extra,
"extra",extra, "check_valid",check_valid,
"check_valid",check_valid, "quality", quality,
"quality", quality, "steps", steps,
"steps", steps, "offset_maxstep", offset_maxstep,
"offset_maxstep", offset_maxstep, "offset", offset
"offset", offset ]);
]);
function _remove_undefined_vals(list) = function _remove_undefined_vals(list) =
let(ind=search([undef],list,0)[0]) let(ind=search([undef],list,0)[0])
list_remove(list, concat(ind, add_scalar(ind,-1))); list_remove(list, concat(ind, add_scalar(ind,-1)));
// Function&Module: offset_stroke() // Function&Module: offset_stroke()
@ -868,163 +914,204 @@ function _remove_undefined_vals(list) =
// right(12) // right(12)
// offset_stroke(path, width=1, closed=true); // offset_stroke(path, width=1, closed=true);
function offset_stroke(path, width=1, rounded=true, start="flat", end="flat", check_valid=true, quality=1, maxstep=0.1, chamfer=false, closed=false) = function offset_stroke(path, width=1, rounded=true, start="flat", end="flat", check_valid=true, quality=1, maxstep=0.1, chamfer=false, closed=false) =
let(closedok = !closed || (is_undef(start) && is_undef(end))) let(closedok = !closed || (is_undef(start) && is_undef(end)))
assert(closedok, "Parameters `start` and `end` not allowed with closed path") assert(closedok, "Parameters `start` and `end` not allowed with closed path")
let( let(
start = closed ? [] : _parse_stroke_end(default(start,"flat")), start = closed? [] : _parse_stroke_end(default(start,"flat")),
end = closed ? [] : _parse_stroke_end(default(end,"flat")), end = closed? [] : _parse_stroke_end(default(end,"flat")),
width = is_list(width) ? reverse(sort(width)) : [1,-1]*width/2, width = is_list(width)? reverse(sort(width)) : [1,-1]*width/2,
left_r = !rounded ? undef : width[0], left_r = !rounded? undef : width[0],
left_delta = rounded ? undef : width[0], left_delta = rounded? undef : width[0],
right_r = !rounded ? undef : width[1], right_r = !rounded? undef : width[1],
right_delta = 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), left_path = offset(
right_path = offset(path, delta=right_delta, r=right_r, closed=closed, check_valid=check_valid, quality=quality,chamfer=chamfer, maxstep=maxstep) path, delta=left_delta, r=left_r, closed=closed,
) check_valid=check_valid, quality=quality,
closed ? [left_path, right_path] : chamfer=chamfer, maxstep=maxstep
let( ),
startpath = _stroke_end(width,left_path, right_path, start), right_path = offset(
endpath = _stroke_end(reverse(width),reverse(right_path), reverse(left_path),end), path, delta=right_delta, r=right_r, closed=closed,
clipping_ok = startpath[1]+endpath[2]<=len(left_path) && startpath[2]+endpath[1]<=len(right_path) check_valid=check_valid, quality=quality,
) chamfer=chamfer, maxstep=maxstep
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]); closed? [left_path, right_path] :
let(
startpath = _stroke_end(width,left_path, right_path, start),
endpath = _stroke_end(reverse(width),reverse(right_path), reverse(left_path),end),
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]
);
function os_pointed(loc=0,dist) = function os_pointed(loc=0,dist) =
assert(is_def(dist), "Must specify `dist`") assert(is_def(dist), "Must specify `dist`")
[ [
"type", "shifted_point", "type", "shifted_point",
"loc",loc, "loc",loc,
"dist",dist "dist",dist
]; ];
function os_round(cut, angle, abs_angle, k) = function os_round(cut, angle, abs_angle, k) =
let( let(
acount = num_defined([angle,abs_angle]), acount = num_defined([angle,abs_angle]),
use_angle = first_defined([angle,abs_angle,0]) use_angle = first_defined([angle,abs_angle,0])
) )
assert(acount<2, "You must define only one of `angle` and `abs_angle`") assert(acount<2, "You must define only one of `angle` and `abs_angle`")
assert(is_def(cut), "Parameter `cut` not defined.") assert(is_def(cut), "Parameter `cut` not defined.")
[ [
"type", "roundover", "type", "roundover",
"angle", use_angle, "angle", use_angle,
"absolute", is_def(abs_angle), "absolute", is_def(abs_angle),
"cut", is_vector(cut) ? point2d(cut) : [cut,cut], "cut", is_vector(cut)? point2d(cut) : [cut,cut],
"k", first_defined([k, 0.75]) "k", first_defined([k, 0.75])
]; ];
function os_flat(angle, abs_angle) = function os_flat(angle, abs_angle) =
let( acount = num_defined([angle,abs_angle]), let(
use_angle = first_defined([angle,abs_angle,0]) 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`") )
[ assert(acount<2, "You must define only one of `angle` and `abs_angle`")
"type", "flat", [
"angle", use_angle, "type", "flat",
"absolute", is_def(abs_angle) "angle", use_angle,
]; "absolute", is_def(abs_angle)
];
// Return angle in (-90,90] required to map line1 onto line2 (lines specified as lists of two points) // 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) =
angle > 90 ? angle-180 : let(angle = atan2(det2([line1,line2]),line1*line2))
angle <= -90 ? angle+180 : angle > 90 ? angle-180 :
angle; angle <= -90 ? angle+180 :
angle;
function _parse_stroke_end(spec) = 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)?
[["type", spec]] : assert(
struct_set([], spec); in_list(spec,["flat","round","pointed"]),
str("Unknown end string specification \"", spec,"\". Must be \"flat\", \"round\", or \"pointed\"")
)
[["type", spec]] :
struct_set([], spec);
function _stroke_end(width,left, right, spec) = function _stroke_end(width,left, right, spec) =
let( let(
type = struct_val(spec, "type"), type = struct_val(spec, "type"),
user_angle = default(struct_val(spec, "angle"), 0), user_angle = default(struct_val(spec, "angle"), 0),
normal_seg = _normal_segment(right[0], left[0]), normal_seg = _normal_segment(right[0], left[0]),
normal_pt = normal_seg[1], normal_pt = normal_seg[1],
center = normal_seg[0], center = normal_seg[0],
parallel_dir = normalize(left[0]-right[0]), parallel_dir = normalize(left[0]-right[0]),
normal_dir = normalize(normal_seg[1]-normal_seg[0]), normal_dir = normalize(normal_seg[1]-normal_seg[0]),
width_dir = sign(width[0]-width[1]) width_dir = sign(width[0]-width[1])
) )
type == "round" ? [arc(points=[right[0],normal_pt,left[0]],N=50),1,1] : type == "round"? [arc(points=[right[0],normal_pt,left[0]],N=50),1,1] :
type == "pointed" ? [[normal_pt],0,0] : type == "pointed"? [[normal_pt],0,0] :
type == "shifted_point" ? let( shiftedcenter = center + width_dir * parallel_dir * struct_val(spec, "loc")) type == "shifted_point"? (
[[shiftedcenter+normal_dir*struct_val(spec, "dist")],0,0] : let(shiftedcenter = center + width_dir * parallel_dir * struct_val(spec, "loc"))
// Remaining types all support angled cutoff, so compute that [[shiftedcenter+normal_dir*struct_val(spec, "dist")],0,0]
assert(abs(user_angle)<=90, "End angle must be in [-90,90]") ) :
let( // Remaining types all support angled cutoff, so compute that
angle = struct_val(spec,"absolute") ? angle_between_lines(left[0]-right[0],[cos(user_angle),sin(user_angle)]) assert(abs(user_angle)<=90, "End angle must be in [-90,90]")
: user_angle, let(
endseg = [center, rotate_points2d([left[0]],angle, cp=center)[0]], angle = struct_val(spec,"absolute")?
intright = angle>0, angle_between_lines(left[0]-right[0],[cos(user_angle),sin(user_angle)]) :
pathclip = _path_line_intersection(intright ? right : left, endseg), user_angle,
pathextend = line_intersection(endseg, select(intright ? left:right,0,1)) endseg = [center, rotate_points2d([left[0]],angle, cp=center)[0]],
) intright = angle>0,
type == "flat" ? (intright ? [[pathclip[0], pathextend], 1, pathclip[1]] : pathclip = _path_line_intersection(intright? right : left, endseg),
[[pathextend, pathclip[0]], pathclip[1],1]) : pathextend = line_intersection(endseg, select(intright? left:right,0,1))
type == "roundover" ? )
let( type == "flat"? (
bez_k = struct_val(spec,"k"), intright?
cut = struct_val(spec,"cut"), [[pathclip[0], pathextend], 1, pathclip[1]] :
cutleft = cut[0], [[pathextend, pathclip[0]], pathclip[1],1]
cutright = cut[1], ) :
// Create updated paths taking into account clipping for end rotation type == "roundover"? (
newright = intright ? concat([pathclip[0]],select(right,pathclip[1],-1)) : let(
concat([pathextend],select(right,1,-1)), bez_k = struct_val(spec,"k"),
newleft = !intright ? concat([pathclip[0]],select(left,pathclip[1],-1)) : cut = struct_val(spec,"cut"),
concat([pathextend],select(left,1,-1)), cutleft = cut[0],
// calculate corner angles, which are different when the cut is negative (outside corner) cutright = cut[1],
leftangle = cutleft>=0 ? vector_angle([newleft[1],newleft[0],newright[0]])/2 : // Create updated paths taking into account clipping for end rotation
90-vector_angle([newleft[1],newleft[0],newright[0]])/2, newright = intright?
rightangle = cutright>=0 ? vector_angle([newright[1],newright[0],newleft[0]])/2 : concat([pathclip[0]],select(right,pathclip[1],-1)) :
90-vector_angle([newright[1],newright[0],newleft[0]])/2, concat([pathextend],select(right,1,-1)),
jointleft = 8*cutleft/cos(leftangle)/(1+4*bez_k), newleft = !intright?
jointright = 8*cutright/cos(rightangle)/(1+4*bez_k), concat([pathclip[0]],select(left,pathclip[1],-1)) :
pathcutleft = path_cut(newleft,abs(jointleft)), concat([pathextend],select(left,1,-1)),
pathcutright = path_cut(newright,abs(jointright)), // calculate corner angles, which are different when the cut is negative (outside corner)
leftdelete = intright ? pathcutleft[1] : pathcutleft[1] + pathclip[1] -1, leftangle = cutleft>=0?
rightdelete = intright ? pathcutright[1] + pathclip[1] -1 : pathcutright[1], vector_angle([newleft[1],newleft[0],newright[0]])/2 :
leftcorner = line_intersection([pathcutleft[0], newleft[pathcutleft[1]]], [newright[0],newleft[0]]), 90-vector_angle([newleft[1],newleft[0],newright[0]])/2,
rightcorner = line_intersection([pathcutright[0], newright[pathcutright[1]]], [newright[0],newleft[0]]), rightangle = cutright>=0?
roundover_fits = jointleft+jointright < norm(rightcorner-leftcorner) vector_angle([newright[1],newright[0],newleft[0]])/2 :
) 90-vector_angle([newright[1],newright[0],newleft[0]])/2,
assert(roundover_fits,"Roundover too large to fit") jointleft = 8*cutleft/cos(leftangle)/(1+4*bez_k),
let( jointright = 8*cutright/cos(rightangle)/(1+4*bez_k),
angled_dir = normalize(newleft[0]-newright[0]), pathcutleft = path_cut(newleft,abs(jointleft)),
nPleft = [leftcorner - jointleft*angled_dir, pathcutright = path_cut(newright,abs(jointright)),
leftcorner, leftdelete = intright? pathcutleft[1] : pathcutleft[1] + pathclip[1] -1,
pathcutleft[0]], rightdelete = intright? pathcutright[1] + pathclip[1] -1 : pathcutright[1],
nPright = [pathcutright[0], leftcorner = line_intersection([pathcutleft[0], newleft[pathcutleft[1]]], [newright[0],newleft[0]]),
rightcorner, rightcorner = line_intersection([pathcutright[0], newright[pathcutright[1]]], [newright[0],newleft[0]]),
rightcorner + jointright*angled_dir], roundover_fits = jointleft+jointright < norm(rightcorner-leftcorner)
leftcurve = _bezcorner(nPleft, bez_k), )
rightcurve = _bezcorner(nPright, bez_k) assert(roundover_fits,"Roundover too large to fit")
) let(
[concat(rightcurve, leftcurve), leftdelete, rightdelete] : angled_dir = normalize(newleft[0]-newright[0]),
[[],0,0]; // This case shouldn't occur nPleft = [
leftcorner - jointleft*angled_dir,
leftcorner,
pathcutleft[0]
],
nPright = [
pathcutright[0],
rightcorner,
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
// returns [intersection_pt, index of first point in path after the intersection] // returns [intersection_pt, index of first point in path after the intersection]
function _path_line_intersection(path, line, ind=0) = function _path_line_intersection(path, line, ind=0) =
ind==len(path)-1 ? undef : ind==len(path)-1 ? undef :
let(intersect=line_segment_intersection(line, select(path,ind,ind+1))) 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 // 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 // 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]?
_path_line_intersection(path, line, 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) 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, result = offset_stroke(
maxstep=maxstep, chamfer=chamfer, closed=closed); path, width=width, rounded=rounded,
if (closed) region(result); start=start, end=end,
else polygon(result); check_valid=check_valid, quality=quality,
maxstep=maxstep, chamfer=chamfer,
closed=closed
);
if (closed) {
region(result);
} else {
polygon(result);
}
} }