diff --git a/paths.scad b/paths.scad index fe14a50f..0588310f 100644 --- a/paths.scad +++ b/paths.scad @@ -407,6 +407,158 @@ function path_torsion(path, closed=false) = ]; +// Function: path_chamfer_and_rounding() +// Usage: +// path2 = path_chamfer_and_rounding(path, [closed], [chamfer], [rounding]); +// Description: +// Rounds or chamfers corners in the given path. +// Arguments: +// path = The path to chamfer and/or round. +// closed = If true, treat path like a closed polygon. Default: true +// chamfer = The length of the chamfer faces at the corners. If given as a list of numbers, gives individual chamfers for each corner, from first to last. Default: 0 (no chamfer) +// rounding = The rounding radius for the corners. If given as a list of numbers, gives individual radii for each corner, from first to last. Default: 0 (no rounding) +// Example(2D): Chamfering a Path +// path = star(5, step=2, d=100); +// path2 = path_chamfer_and_rounding(path, closed=true, chamfer=5); +// stroke(path2, closed=true); +// Example(2D): Per-Corner Chamfering +// path = star(5, step=2, d=100); +// chamfs = [for (i=[0:1:4]) each 3*[i,i]]; +// path2 = path_chamfer_and_rounding(path, closed=true, chamfer=chamfs); +// stroke(path2, closed=true); +// Example(2D): Rounding a Path +// path = star(5, step=2, d=100); +// path2 = path_chamfer_and_rounding(path, closed=true, rounding=5); +// stroke(path2, closed=true); +// Example(2D): Per-Corner Chamfering +// path = star(5, step=2, d=100); +// rs = [for (i=[0:1:4]) each 3*[i,i]]; +// path2 = path_chamfer_and_rounding(path, closed=true, rounding=rs); +// stroke(path2, closed=true); +// Example(2D): Mixing Chamfers and Roundings +// path = star(5, step=2, d=100); +// chamfs = [for (i=[0:4]) each [5,0]]; +// rs = [for (i=[0:4]) each [0,10]]; +// path2 = path_chamfer_and_rounding(path, closed=true, chamfer=chamfs, rounding=rs); +// stroke(path2, closed=true); +function path_chamfer_and_rounding(path, closed, chamfer, rounding) = + let ( + path = deduplicate(path,closed=true), + lp = len(path), + chamfer = is_undef(chamfer)? repeat(0,lp) : + is_vector(chamfer)? list_pad(chamfer,lp,0) : + is_num(chamfer)? repeat(chamfer,lp) : + assert(false, "Bad chamfer value."), + rounding = is_undef(rounding)? repeat(0,lp) : + is_vector(rounding)? list_pad(rounding,lp,0) : + is_num(rounding)? repeat(rounding,lp) : + assert(false, "Bad rounding value."), + corner_paths = [ + for (i=(closed? [0:1:lp-1] : [1:1:lp-2])) let( + p1 = select(path,i-1), + p2 = select(path,i), + p3 = select(path,i+1) + ) + chamfer[i] > 0? _corner_chamfer_path(p1, p2, p3, side=chamfer[i]) : + rounding[i] > 0? _corner_roundover_path(p1, p2, p3, r=rounding[i]) : + [p2] + ], + out = [ + if (!closed) path[0], + for (i=(closed? [0:1:lp-1] : [1:1:lp-2])) let( + p1 = select(path,i-1), + p2 = select(path,i), + crn1 = select(corner_paths,i-1), + crn2 = corner_paths[i], + l1 = norm(select(crn1,-1)-p1), + l2 = norm(crn2[0]-p2), + needed = l1 + l2, + seglen = norm(p2-p1), + check = assert(seglen >= needed, str("Path segment ",i," is too short to fulfill rounding/chamfering for the adjacent corners.")) + ) each crn2, + if (!closed) select(path,-1) + ] + ) deduplicate(out); + + +function _corner_chamfer_path(p1, p2, p3, dist1, dist2, side, angle) = + let( + v1 = unit(p1 - p2), + v2 = unit(p3 - p2), + n = vector_axis(v1,v2), + ang = vector_angle(v1,v2), + path = (is_num(dist1) && is_undef(dist2) && is_undef(side))? ( + // dist1 & optional angle + assert(dist1 > 0) + let(angle = default(angle,(180-ang)/2)) + assert(is_num(angle)) + assert(angle > 0 && angle < 180) + let( + pta = p2 + dist1*v1, + a3 = 180 - angle - ang + ) assert(a3>0, "Angle too extreme.") + let( + side = sin(angle) * dist1/sin(a3), + ptb = p2 + side*v2 + ) [pta, ptb] + ) : (is_undef(dist1) && is_num(dist2) && is_undef(side))? ( + // dist2 & optional angle + assert(dist2 > 0) + let(angle = default(angle,(180-ang)/2)) + assert(is_num(angle)) + assert(angle > 0 && angle < 180) + let( + ptb = p2 + dist2*v2, + a3 = 180 - angle - ang + ) assert(a3>0, "Angle too extreme.") + let( + side = sin(angle) * dist2/sin(a3), + pta = p2 + side*v1 + ) [pta, ptb] + ) : (is_undef(dist1) && is_undef(dist2) && is_num(side))? ( + // side & optional angle + assert(side > 0) + let(angle = default(angle,(180-ang)/2)) + assert(is_num(angle)) + assert(angle > 0 && angle < 180) + let( + a3 = 180 - angle - ang + ) assert(a3>0, "Angle too extreme.") + let( + dist1 = sin(a3) * side/sin(ang), + dist2 = sin(angle) * side/sin(ang), + pta = p2 + dist1*v1, + ptb = p2 + dist2*v2 + ) [pta, ptb] + ) : (is_num(dist1) && is_num(dist2) && is_undef(side) && is_undef(side))? ( + // dist1 & dist2 + assert(dist1 > 0) + assert(dist2 > 0) + let( + pta = p2 + dist1*v1, + ptb = p2 + dist2*v2 + ) [pta, ptb] + ) : ( + assert(false,"Bad arguments.") + ) + ) path; + + +function _corner_roundover_path(p1, p2, p3, r, d) = + let( + r = get_radius(r=r,d=d,dflt=undef), + res = circle_2tangents(p1, p2, p3, r=r, tangents=true), + cp = res[0], + n = res[1], + tp1 = res[2], + ang = res[4]+res[5], + steps = floor(segs(r)*ang/360+0.5), + step = ang / steps, + path = [for (i=[0:1:steps]) move(cp, p=rot(a=-i*step, v=n, p=tp1-cp))] + ) path; + + + // Function: path3d_spiral() // Description: // Returns a 3D spiral path. diff --git a/shapes2d.scad b/shapes2d.scad index 4fec9a35..8325bb88 100644 --- a/shapes2d.scad +++ b/shapes2d.scad @@ -1206,6 +1206,8 @@ module octagon(r, d, or, od, ir, id, side, rounding=0, realign=false, align_tip, // w2 = The X axis width of the back end of the trapezoid. // angle = If given in place of `h`, `w1`, or `w2`, then the missing value is calculated such that the right side has that angle away from the Y axis. // shift = Scalar value to shift the back of the trapezoid along the X axis by. Default: 0 +// rounding = The rounding radius for the corners. If given as a list of four numbers, gives individual radii for each corner, in the order [X+Y+,X-Y+,X-Y-,X+Y-]. Default: 0 (no rounding) +// chamfer = The Length of the chamfer faces at the corners. If given as a list of four numbers, gives individual chamfers for each corner, in the order [X+Y+,X-Y+,X-Y-,X+Y-]. Default: 0 (no chamfer) // anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#anchor). Default: `CENTER` // spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#spin). Default: `0` // Examples(2D): @@ -1217,42 +1219,68 @@ module octagon(r, d, or, od, ir, id, side, rounding=0, realign=false, align_tip, // trapezoid(h=20, w2=10, angle=30); // trapezoid(h=20, w2=30, angle=-30); // trapezoid(w1=30, w2=10, angle=30); +// Example(2D): Chamferred Trapezoid +// trapezoid(h=30, w1=60, w2=40, chamfer=5); +// Example(2D): Rounded Trapezoid +// trapezoid(h=30, w1=60, w2=40, rounding=5); +// Example(2D): Mixed Chamfering and Rounding +// trapezoid(h=30, w1=60, w2=40, rounding=[5,0,10,0],chamfer=[0,8,0,15],$fa=1,$fs=1); // Example(2D): Called as Function // stroke(closed=true, trapezoid(h=30, w1=40, w2=20)); -function trapezoid(h, w1, w2, angle, shift=0, anchor=CENTER, spin=0) = +function trapezoid(h, w1, w2, angle, shift=0, chamfer=0, rounding=0, anchor=CENTER, spin=0) = assert(is_undef(h) || is_finite(h)) assert(is_undef(w1) || is_finite(w1)) assert(is_undef(w2) || is_finite(w2)) assert(is_undef(angle) || is_finite(angle)) assert(num_defined([h, w1, w2, angle]) == 3, "Must give exactly 3 of the arguments h, w1, w2, and angle.") assert(is_finite(shift)) + assert(is_finite(chamfer) || is_vector(chamfer,4)) + assert(is_finite(rounding) || is_vector(rounding,4)) let( + simple = chamfer==0 && rounding==0, h = !is_undef(h)? h : opp_ang_to_adj(abs(w2-w1)/2, abs(angle)), w1 = !is_undef(w1)? w1 : w2 + 2*(adj_ang_to_opp(h, angle) + shift), - w2 = !is_undef(w2)? w2 : w1 - 2*(adj_ang_to_opp(h, angle) + shift), - path = [[w1/2,-h/2], [-w1/2,-h/2], [-w2/2+shift,h/2], [w2/2+shift,h/2]] + w2 = !is_undef(w2)? w2 : w1 - 2*(adj_ang_to_opp(h, angle) + shift) ) assert(w1>=0 && w2>=0 && h>0, "Degenerate trapezoid geometry.") - reorient(anchor,spin, two_d=true, size=[w1,h], size2=w2, p=path); + assert(w1+w2>0, "Degenerate trapezoid geometry.") + let( + base_path = [ + [w2/2+shift,h/2], + [-w2/2+shift,h/2], + [-w1/2,-h/2], + [w1/2,-h/2], + ], + cpath = simple? base_path : + path_chamfer_and_rounding( + base_path, closed=true, + chamfer=chamfer, + rounding=rounding + ), + path = reverse(cpath) + ) simple? + reorient(anchor,spin, two_d=true, size=[w1,h], size2=w2, shift=shift, p=path) : + reorient(anchor,spin, two_d=true, path=path, p=path); -module trapezoid(h, w1, w2, angle, shift=0, anchor=CENTER, spin=0) { - assert(is_undef(h) || is_finite(h)); - assert(is_undef(w1) || is_finite(w1)); - assert(is_undef(w2) || is_finite(w2)); - assert(is_undef(angle) || is_finite(angle)); - assert(num_defined([h, w1, w2, angle]) == 3, "Must give exactly 3 of the arguments h, w1, w2, and angle."); - assert(is_finite(shift)); +module trapezoid(h, w1, w2, angle, shift=0, chamfer=0, rounding=0, anchor=CENTER, spin=0) { + path = trapezoid(h=h, w1=w1, w2=w2, angle=angle, shift=shift, chamfer=chamfer, rounding=rounding); union() { + simple = chamfer==0 && rounding==0; h = !is_undef(h)? h : opp_ang_to_adj(abs(w2-w1)/2, abs(angle)); w1 = !is_undef(w1)? w1 : w2 + 2*(adj_ang_to_opp(h, angle) + shift); w2 = !is_undef(w2)? w2 : w1 - 2*(adj_ang_to_opp(h, angle) + shift); - assert(w1>=0 && w2>=0 && h>0, "Degenerate trapezoid geometry."); - path = [[w1/2,-h/2], [-w1/2,-h/2], [-w2/2+shift,h/2], [w2/2+shift,h/2]]; - attachable(anchor,spin, two_d=true, size=[w1,h], size2=w2, shift=shift) { - polygon(path); - children(); + if (simple) { + attachable(anchor,spin, two_d=true, size=[w1,h], size2=w2, shift=shift) { + polygon(path); + children(); + } + } else { + attachable(anchor,spin, two_d=true, path=path) { + polygon(path); + children(); + } } } } diff --git a/version.scad b/version.scad index ae8cf68b..2aa024d9 100644 --- a/version.scad +++ b/version.scad @@ -8,7 +8,7 @@ ////////////////////////////////////////////////////////////////////// -BOSL_VERSION = [2,0,496]; +BOSL_VERSION = [2,0,497]; // Section: BOSL Library Version Functions