From e09a6cd6dd6f2e9d4f081dd97c1afc3d2cb9531a Mon Sep 17 00:00:00 2001 From: Garth Minette Date: Sun, 16 Jan 2022 23:30:13 -0800 Subject: [PATCH 1/7] Added negative roundings/chamfers to rect() and trapezoid(). Added teardrop= to cuboid() --- shapes2d.scad | 144 ++++++++++++++++++++++++++++++++++---------------- shapes3d.scad | 49 ++++++++++++----- 2 files changed, 135 insertions(+), 58 deletions(-) diff --git a/shapes2d.scad b/shapes2d.scad index e77d314..aa70a09 100644 --- a/shapes2d.scad +++ b/shapes2d.scad @@ -84,8 +84,8 @@ module square(size=1, center, anchor, spin) { // When called as a function, returns a 2D path/list of points for a square/rectangle of the given size. // Arguments: // size = The size of the rectangle to create. If given as a scalar, both X and Y will be the same size. -// 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 chamfer size for 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) +// rounding = The rounding radius for the corners. If negative, produces external roundover spikes on the X axis. 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 chamfer size for the corners. If negative, produces external chamfer spikes on the X axis. 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#subsection-anchor). Default: `CENTER` // spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#subsection-spin). Default: `0` // Example(2D): @@ -98,6 +98,10 @@ module square(size=1, center, anchor, spin) { // rect([40,30], chamfer=5); // Example(2D): Rounded Rect // rect([40,30], rounding=5); +// Example(2D): Negative-Chamferred Rect +// rect([40,30], chamfer=-5); +// Example(2D): Negative-Rounded Rect +// rect([40,30], rounding=-5); // Example(2D): Mixed Chamferring and Rounding // rect([40,30],rounding=[5,0,10,0],chamfer=[0,8,0,15],$fa=1,$fs=1); // Example(2D): Called as Function @@ -145,7 +149,8 @@ function rect(size=1, rounding=0, chamfer=0, anchor=CENTER, spin=0) = rounding = is_list(rounding)? rounding : [for (i=[0:3]) rounding], quadorder = [3,2,1,0], quadpos = [[1,1],[-1,1],[-1,-1],[1,-1]], - insets = [for (i=[0:3]) chamfer[i]>0? chamfer[i] : rounding[i]>0? rounding[i] : 0], + eps = 1e-9, + insets = [for (i=[0:3]) abs(chamfer[i])>=eps? chamfer[i] : abs(rounding[i])>=eps? rounding[i] : 0], insets_x = max(insets[0]+insets[1],insets[2]+insets[3]), insets_y = max(insets[0]+insets[3],insets[1]+insets[2]) ) @@ -156,16 +161,20 @@ function rect(size=1, rounding=0, chamfer=0, anchor=CENTER, spin=0) = for(i = [0:3]) let( quad = quadorder[i], - inset = insets[quad], - cverts = quant(segs(inset),4)/4, - cp = v_mul(size/2-[inset,inset], quadpos[quad]), + qinset = insets[quad], + qpos = quadpos[quad], + qchamf = chamfer[quad], + qround = rounding[quad], + cverts = quant(segs(abs(qinset)),4)/4, step = 90/cverts, - angs = - chamfer[quad] > 0? [0,-90]-90*[i,i] : - rounding[quad] > 0? [for (j=[0:1:cverts]) 360-j*step-i*90] : - [0] + cp = v_mul(size/2-[qinset,abs(qinset)], qpos), + qpts = abs(qchamf) >= eps? [[0,abs(qinset)], [qinset,0]] : + abs(qround) >= eps? [for (j=[0:1:cverts]) let(a=90-j*step) v_mul(polar_to_xy(abs(qinset),a),[sign(qinset),1])] : + [[0,0]], + qfpts = [for (p=qpts) v_mul(p,qpos)], + qrpts = qpos.x*qpos.y < 0? reverse(qfpts) : qfpts ) - each [for (a = angs) cp + inset*[cos(a),sin(a)]] + each move(cp, p=qrpts) ] ) complex? reorient(anchor,spin, two_d=true, path=path, p=path) : @@ -785,6 +794,7 @@ module right_triangle(size=[1,1], center, anchor, spin=0) { // 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) +// flip = If true, negative roundings and chamfers will point forward and back instead of left and right. Default: `false`. // anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#subsection-anchor). Default: `CENTER` // spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#subsection-spin). Default: `0` // Examples(2D): @@ -796,15 +806,23 @@ module right_triangle(size=[1,1], center, anchor, spin=0) { // 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 +// Example(2D): Chamfered Trapezoid // trapezoid(h=30, w1=60, w2=40, chamfer=5); +// Example(2D): Negative Chamfered Trapezoid +// trapezoid(h=30, w1=60, w2=40, chamfer=-5); +// Example(2D): Flipped Negative Chamfered Trapezoid +// trapezoid(h=30, w1=60, w2=40, chamfer=-5, flip=true); // Example(2D): Rounded Trapezoid // trapezoid(h=30, w1=60, w2=40, rounding=5); +// Example(2D): Negative Rounded Trapezoid +// trapezoid(h=30, w1=60, w2=40, rounding=-5); +// Example(2D): Flipped Negative Rounded Trapezoid +// trapezoid(h=30, w1=60, w2=40, rounding=-5, flip=true); // 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); +// 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, chamfer=0, rounding=0, anchor=CENTER, spin=0) = +function trapezoid(h, w1, w2, angle, shift=0, chamfer=0, rounding=0, flip=false, 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)) @@ -817,23 +835,66 @@ function trapezoid(h, w1, w2, angle, shift=0, chamfer=0, rounding=0, anchor=CENT 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) + w2 = !is_undef(w2)? w2 : w1 - 2*(adj_ang_to_opp(h, angle) + shift), + chamfs = is_num(chamfer)? [for (i=[0:3]) chamfer] : + assert(len(chamfer)==4) chamfer, + rounds = is_num(rounding)? [for (i=[0:3]) rounding] : + assert(len(rounding)==4) rounding, + srads = [for (i=[0:3]) rounds[i]? rounds[i] : chamfs[i]], + rads = v_abs(srads) ) assert(w1>=0 && w2>=0 && h>0, "Degenerate trapezoid geometry.") assert(w1+w2>0, "Degenerate trapezoid geometry.") let( - base_path = [ - [w2/2+shift,h/2], - [-w2/2+shift,h/2], + base = [ + [ w2/2+shift, h/2], + [-w2/2+shift, h/2], [-w1/2,-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 + ang1 = v_theta(base[0]-base[3])-90, + ang2 = v_theta(base[1]-base[2])-90, + angs = [ang1, ang2, ang2, ang1], + qdirs = [[1,1], [-1,1], [-1,-1], [1,-1]], + hyps = [for (i=[0:3]) adj_ang_to_hyp(rads[i],angs[i])], + fluh=echo(), + offs = [ + for (i=[0:3]) let( + xoff = adj_ang_to_opp(rads[i],angs[i]), + a = [xoff, -rads[i]] * qdirs[i].y * (srads[i]<0 && flip? -1 : 1), + b = a + [hyps[i] * qdirs[i].x * (srads[i]<0 && !flip? 1 : -1), 0] + ) b + ], + cpath = [ + each ( + let(i = 0) + rads[i] == 0? [base[i]] : + srads[i] > 0? arc(N=rounds[i]?undef:2, cp=base[i]+offs[i], angle=[angs[i], 90], r=rads[i]) : + flip? arc(N=rounds[i]?undef:2, cp=base[i]+offs[i], angle=[angs[i],-90], r=rads[i]) : + arc(N=rounds[i]?undef:2, cp=base[i]+offs[i], angle=[180+angs[i],90], r=rads[i]) ), + each ( + let(i = 1) + rads[i] == 0? [base[i]] : + srads[i] > 0? arc(N=rounds[i]?undef:2, cp=base[i]+offs[i], angle=[90,180+angs[i]], r=rads[i]) : + flip? arc(N=rounds[i]?undef:2, cp=base[i]+offs[i], angle=[270,180+angs[i]], r=rads[i]) : + arc(N=rounds[i]?undef:2, cp=base[i]+offs[i], angle=[90,angs[i]], r=rads[i]) + ), + each ( + let(i = 2) + rads[i] == 0? [base[i]] : + srads[i] > 0? arc(N=rounds[i]?undef:2, cp=base[i]+offs[i], angle=[180+angs[i],270], r=rads[i]) : + flip? arc(N=rounds[i]?undef:2, cp=base[i]+offs[i], angle=[180+angs[i],90], r=rads[i]) : + arc(N=rounds[i]?undef:2, cp=base[i]+offs[i], angle=[angs[i],-90], r=rads[i]) + ), + each ( + let(i = 3) + rads[i] == 0? [base[i]] : + srads[i] > 0? arc(N=rounds[i]?undef:2, cp=base[i]+offs[i], angle=[-90,angs[i]], r=rads[i]) : + flip? arc(N=rounds[i]?undef:2, cp=base[i]+offs[i], angle=[90,angs[i]], r=rads[i]) : + arc(N=rounds[i]?undef:2, cp=base[i]+offs[i], angle=[270,180+angs[i]], r=rads[i]) + ), + ], path = reverse(cpath) ) simple ? reorient(anchor,spin, two_d=true, size=[w1,h], size2=w2, shift=shift, p=path) @@ -841,8 +902,8 @@ function trapezoid(h, w1, w2, angle, shift=0, chamfer=0, rounding=0, anchor=CENT -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); +module trapezoid(h, w1, w2, angle, shift=0, chamfer=0, rounding=0, flip=false, anchor=CENTER, spin=0) { + path = trapezoid(h=h, w1=w1, w2=w2, angle=angle, shift=shift, chamfer=chamfer, rounding=rounding, flip=flip); union() { simple = chamfer==0 && rounding==0; h = !is_undef(h)? h : opp_ang_to_adj(abs(w2-w1)/2, abs(angle)); @@ -1103,26 +1164,17 @@ module teardrop2d(r, ang=45, cap_h, d, anchor=CENTER, spin=0) function teardrop2d(r, ang=45, cap_h, d, anchor=CENTER, spin=0) = let( r = get_radius(r=r, d=d, dflt=1), - tanpt = polar_to_xy(r, ang), - tip_y = adj_ang_to_hyp(r, 90-ang), - cap_h = min(default(cap_h,tip_y), tip_y), - cap_w = tanpt.y >= cap_h - ? hyp_opp_to_adj(r, cap_h) - : adj_ang_to_opp(tip_y-cap_h, ang), - ang2 = min(ang,atan2(cap_h,cap_w)), - sa = 180 - ang2, - ea = 360 + ang2, - steps = ceil(segs(r)*(ea-sa)/360), - path = deduplicate( - [ - [ cap_w,cap_h], - for (a=lerpn(ea,sa,steps+1)) r*[cos(a),sin(a)], - [-cap_w,cap_h] - ], closed=true - ), - maxx_idx = max_index(column(path,0)), - path2 = list_rotate(path,maxx_idx) - ) reorient(anchor,spin, two_d=true, path=path2, p=path2, extent=false); + ang2 = 90-ang, + prepath = zrot(90, p=circle(r=r)), + eps=1e-9, + prepath2 = [for (p=prepath) let(a=atan2(p.y,p.x)) if(a<=90-ang2+eps || a>=90+ang2-eps) p], + hyp = is_undef(cap_h) + ? opp_ang_to_hyp(abs(prepath2[0].x), ang) + : adj_ang_to_hyp(cap_h-prepath2[0].y, ang), + p1 = prepath2[0] + polar_to_xy(hyp, 90+ang), + p2 = last(prepath2) + polar_to_xy(hyp, 90-ang), + path = deduplicate([p1, each prepath2, p2], closed=true) + ) reorient(anchor,spin, two_d=true, path=path, p=path, extent=false); diff --git a/shapes3d.scad b/shapes3d.scad index 5c03ec2..603e8d2 100644 --- a/shapes3d.scad +++ b/shapes3d.scad @@ -113,6 +113,7 @@ function cube(size=1, center, anchor, spin=0, orient=UP) = // edges = Edges to mask. See [Specifying Edges](attachments.scad#section-specifying-edges). Default: all edges. // except = Edges to explicitly NOT mask. See [Specifying Edges](attachments.scad#section-specifying-edges). Default: No edges. // trimcorners = If true, rounds or chamfers corners where three chamfered/rounded edges meet. Default: `true` +// teardrop = If given as a number, rounding around the bottom edge of the cuboid won't exceed this many degrees from vertical. If true, the limit angle is 45 degrees. Default: `false` // p1 = Align the cuboid's corner at `p1`, if given. Forces `anchor=FRONT+LEFT+BOTTOM`. // p2 = If given with `p1`, defines the cornerpoints of the cuboid. // anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#subsection-anchor). Default: `CENTER` @@ -181,10 +182,32 @@ module cuboid( except=[], except_edges, trimcorners=true, + teardrop=false, anchor=CENTER, spin=0, orient=UP ) { + module xtcyl(l,r) { + if (teardrop) { + teardrop(r=r, l=l, cap_h=r, ang=teardrop, spin=90, orient=DOWN); + } else { + yrot(90) cyl(l=l, r=r); + } + } + module ytcyl(l,r) { + if (teardrop) { + teardrop(r=r, l=l, cap_h=r, ang=teardrop, spin=0, orient=DOWN); + } else { + zrot(90) yrot(90) cyl(l=l, r=r); + } + } + module tsphere(r) { + if (teardrop) { + onion(r=r, cap_h=r, ang=teardrop, orient=DOWN); + } else { + spheroid(r=r, style="octa", orient=DOWN); + } + } module corner_shape(corner) { e = _corner_edges(edges, corner); cnt = sum(e); @@ -197,33 +220,33 @@ module cuboid( if (cnt == 0 || approx(r,0)) { translate(c2) cube(c, center=true); } else if (cnt == 1) { - if (e.x) right(c2.x) xcyl(l=c.x, r=r); - if (e.y) back (c2.y) ycyl(l=c.y, r=r); + if (e.x) right(c2.x) xtcyl(l=c.x, r=r); + if (e.y) back (c2.y) ytcyl(l=c.y, r=r); if (e.z) up (c2.z) zcyl(l=c.z, r=r); } else if (cnt == 2) { if (!e.x) { intersection() { - ycyl(l=c.y*2, r=r); + ytcyl(l=c.y*2, r=r); zcyl(l=c.z*2, r=r); } } else if (!e.y) { intersection() { - xcyl(l=c.x*2, r=r); + xtcyl(l=c.x*2, r=r); zcyl(l=c.z*2, r=r); } } else { intersection() { - xcyl(l=c.x*2, r=r); - ycyl(l=c.y*2, r=r); + xtcyl(l=c.x*2, r=r); + ytcyl(l=c.y*2, r=r); } } } else { if (trimcorners) { - spheroid(r=r, style="octa"); + tsphere(r=r); } else { intersection() { - xcyl(l=c.x*2, r=r); - ycyl(l=c.y*2, r=r); + xtcyl(l=c.x*2, r=r); + ytcyl(l=c.y*2, r=r); zcyl(l=c.z*2, r=r); } } @@ -233,6 +256,7 @@ module cuboid( size = scalar_vec3(size); edges = _edges(edges, except=first_defined([except_edges,except])); + teardrop = is_bool(teardrop)&&teardrop? 45 : teardrop; chamfer = approx(chamfer,0) ? undef : chamfer; rounding = approx(rounding,0) ? undef : rounding; assert(is_vector(size,3)); @@ -240,6 +264,7 @@ module cuboid( assert(is_undef(chamfer) || is_finite(chamfer),"chamfer must be a finite value"); assert(is_undef(rounding) || is_finite(rounding),"rounding must be a finite value"); assert(is_undef(rounding) || is_undef(chamfer), "Cannot specify nonzero value for both chamfer and rounding"); + assert(teardrop==false || (is_finite(teardrop) && teardrop>0 && teardrop<90), "teardrop must be either false or an angle number between 0 and 90") assert(is_undef(p1) || is_vector(p1)); assert(is_undef(p2) || is_vector(p2)); assert(is_bool(trimcorners)); @@ -358,12 +383,12 @@ module cuboid( minkowski() { cube(isize, center=true); if (trimcorners) { - spheroid(r=rounding, style="octa", $fn=sides); + tsphere(r=rounding, $fn=sides); } else { intersection() { + xtcyl(r=rounding, h=rounding*2, $fn=sides); + ytcyl(r=rounding, h=rounding*2, $fn=sides); cyl(r=rounding, h=rounding*2, $fn=sides); - rotate([90,0,0]) cyl(r=rounding, h=rounding*2, $fn=sides); - rotate([0,90,0]) cyl(r=rounding, h=rounding*2, $fn=sides); } } } From c79608737236b18de747623ce4f767ab451a36b5 Mon Sep 17 00:00:00 2001 From: Garth Minette Date: Mon, 17 Jan 2022 00:19:11 -0800 Subject: [PATCH 2/7] Bugfix for teardrop changes to cuboid() --- shapes3d.scad | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/shapes3d.scad b/shapes3d.scad index 603e8d2..a80a81c 100644 --- a/shapes3d.scad +++ b/shapes3d.scad @@ -386,8 +386,8 @@ module cuboid( tsphere(r=rounding, $fn=sides); } else { intersection() { - xtcyl(r=rounding, h=rounding*2, $fn=sides); - ytcyl(r=rounding, h=rounding*2, $fn=sides); + xtcyl(r=rounding, l=rounding*2, $fn=sides); + ytcyl(r=rounding, l=rounding*2, $fn=sides); cyl(r=rounding, h=rounding*2, $fn=sides); } } From b436dba2d3e3265e08265b8511c0297281aef95b Mon Sep 17 00:00:00 2001 From: Garth Minette Date: Mon, 17 Jan 2022 00:28:22 -0800 Subject: [PATCH 3/7] Regressions fixes for changes to teardrop2d() --- shapes2d.scad | 1 - tests/test_shapes2d.scad | 8 ++++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/shapes2d.scad b/shapes2d.scad index aa70a09..a194bb9 100644 --- a/shapes2d.scad +++ b/shapes2d.scad @@ -857,7 +857,6 @@ function trapezoid(h, w1, w2, angle, shift=0, chamfer=0, rounding=0, flip=false, angs = [ang1, ang2, ang2, ang1], qdirs = [[1,1], [-1,1], [-1,-1], [1,-1]], hyps = [for (i=[0:3]) adj_ang_to_hyp(rads[i],angs[i])], - fluh=echo(), offs = [ for (i=[0:3]) let( xoff = adj_ang_to_opp(rads[i],angs[i]), diff --git a/tests/test_shapes2d.scad b/tests/test_shapes2d.scad index 7948120..f5a585a 100644 --- a/tests/test_shapes2d.scad +++ b/tests/test_shapes2d.scad @@ -90,10 +90,10 @@ test_octagon(); module test_teardrop2d() { $fn=24; - assert_approx(teardrop2d(r=50), [[50,0],[48.2962913145,-12.9409522551],[43.3012701892,-25],[35.3553390593,-35.3553390593],[25,-43.3012701892],[12.9409522551,-48.2962913145],[0,-50],[-12.9409522551,-48.2962913145],[-25,-43.3012701892],[-35.3553390593,-35.3553390593],[-43.3012701892,-25],[-48.2962913145,-12.9409522551],[-50,0],[-48.2962913145,12.9409522551],[-43.3012701892,25],[-35.3553390593,35.3553390593],[0,70.7106781187],[35.3553390593,35.3553390593],[43.3012701892,25],[48.2962913145,12.9409522551]]); - assert_approx(teardrop2d(d=100), [[50,0],[48.2962913145,-12.9409522551],[43.3012701892,-25],[35.3553390593,-35.3553390593],[25,-43.3012701892],[12.9409522551,-48.2962913145],[0,-50],[-12.9409522551,-48.2962913145],[-25,-43.3012701892],[-35.3553390593,-35.3553390593],[-43.3012701892,-25],[-48.2962913145,-12.9409522551],[-50,0],[-48.2962913145,12.9409522551],[-43.3012701892,25],[-35.3553390593,35.3553390593],[0,70.7106781187],[35.3553390593,35.3553390593],[43.3012701892,25],[48.2962913145,12.9409522551]]); - assert_approx(teardrop2d(r=50,cap_h=50), [[50,0],[48.2962913145,-12.9409522551],[43.3012701892,-25],[35.3553390593,-35.3553390593],[25,-43.3012701892],[12.9409522551,-48.2962913145],[0,-50],[-12.9409522551,-48.2962913145],[-25,-43.3012701892],[-35.3553390593,-35.3553390593],[-43.3012701892,-25],[-48.2962913145,-12.9409522551],[-50,0],[-48.2962913145,12.9409522551],[-43.3012701892,25],[-35.3553390593,35.3553390593],[-20.7106781187,50],[20.7106781187,50],[35.3553390593,35.3553390593],[43.3012701892,25],[48.2962913145,12.9409522551]]); - assert_approx(teardrop2d(r=50,cap_h=50,ang=30), [[50,0],[48.2962913145,-12.9409522551],[43.3012701892,-25],[35.3553390593,-35.3553390593],[25,-43.3012701892],[12.9409522551,-48.2962913145],[0,-50],[-12.9409522551,-48.2962913145],[-25,-43.3012701892],[-35.3553390593,-35.3553390593],[-43.3012701892,-25],[-48.2962913145,-12.9409522551],[-50,0],[-48.2962913145,12.9409522551],[-43.3012701892,25],[-28.8675134595,50],[28.8675134595,50],[43.3012701892,25],[48.2962913145,12.9409522551]]); + assert_approx(teardrop2d(r=50), [[0,70.7106781187],[35.3553390593,35.3553390593],[43.3012701892,25],[48.2962913145,12.9409522551],[50,0],[48.2962913145,-12.9409522551],[43.3012701892,-25],[35.3553390593,-35.3553390593],[25,-43.3012701892],[12.9409522551,-48.2962913145],[0,-50],[-12.9409522551,-48.2962913145],[-25,-43.3012701892],[-35.3553390593,-35.3553390593],[-43.3012701892,-25],[-48.2962913145,-12.9409522551],[-50,0],[-48.2962913145,12.9409522551],[-43.3012701892,25],[-35.3553390593,35.3553390593]]); + assert_approx(teardrop2d(d=100), [[0,70.7106781187],[35.3553390593,35.3553390593],[43.3012701892,25],[48.2962913145,12.9409522551],[50,0],[48.2962913145,-12.9409522551],[43.3012701892,-25],[35.3553390593,-35.3553390593],[25,-43.3012701892],[12.9409522551,-48.2962913145],[0,-50],[-12.9409522551,-48.2962913145],[-25,-43.3012701892],[-35.3553390593,-35.3553390593],[-43.3012701892,-25],[-48.2962913145,-12.9409522551],[-50,0],[-48.2962913145,12.9409522551],[-43.3012701892,25],[-35.3553390593,35.3553390593]]); + assert_approx(teardrop2d(r=50,cap_h=50), [[20.7106781187,50],[35.3553390593,35.3553390593],[43.3012701892,25],[48.2962913145,12.9409522551],[50,0],[48.2962913145,-12.9409522551],[43.3012701892,-25],[35.3553390593,-35.3553390593],[25,-43.3012701892],[12.9409522551,-48.2962913145],[0,-50],[-12.9409522551,-48.2962913145],[-25,-43.3012701892],[-35.3553390593,-35.3553390593],[-43.3012701892,-25],[-48.2962913145,-12.9409522551],[-50,0],[-48.2962913145,12.9409522551],[-43.3012701892,25],[-35.3553390593,35.3553390593],[-20.7106781187,50]]); + assert_approx(teardrop2d(r=50,cap_h=50,ang=30), [[28.8675134595,50],[43.3012701892,25],[48.2962913145,12.9409522551],[50,0],[48.2962913145,-12.9409522551],[43.3012701892,-25],[35.3553390593,-35.3553390593],[25,-43.3012701892],[12.9409522551,-48.2962913145],[0,-50],[-12.9409522551,-48.2962913145],[-25,-43.3012701892],[-35.3553390593,-35.3553390593],[-43.3012701892,-25],[-48.2962913145,-12.9409522551],[-50,0],[-48.2962913145,12.9409522551],[-43.3012701892,25],[-28.8675134595,50]]); } test_teardrop2d(); From 2ed060fb35116cf6d5084500774a6e3a1052b762 Mon Sep 17 00:00:00 2001 From: Garth Minette Date: Mon, 17 Jan 2022 00:39:06 -0800 Subject: [PATCH 4/7] Split out github action tutorial check. --- .github/workflows/main.yml | 44 +++++++++++++++++++++++++++++++------- 1 file changed, 36 insertions(+), 8 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 04e17d1..618fc6b 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -21,7 +21,7 @@ jobs: export OPENSCADPATH=$(dirname $GITHUB_WORKSPACE) ./scripts/run_tests.sh - CheckDocs: + CheckTutorials: runs-on: ubuntu-latest steps: - name: Checkout @@ -55,13 +55,6 @@ jobs: echo "::add-matcher::.github/check_for_tabs.json" ./scripts/check_for_tabs.sh - - name: Checking Docs - run: | - cd $GITHUB_WORKSPACE - echo "::add-matcher::.github/openscad_docsgen.json" - export OPENSCADPATH=$(dirname $GITHUB_WORKSPACE) - openscad-docsgen -Tmf *.scad - - name: Checking Tutorials run: | cd $GITHUB_WORKSPACE @@ -69,3 +62,38 @@ jobs: export OPENSCADPATH=$(dirname $GITHUB_WORKSPACE) openscad-mdimggen -T *.scad + CheckDocs: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Clone Wiki + uses: actions/checkout@v2 + with: + repository: revarbat/BOSL2.wiki + path: BOSL2.wiki + + - name: Apt Update + run: sudo apt update + + - name: Install Python dev + run: sudo apt-get install python3-pip python3-dev python3-setuptools python3-pil + + - name: Install OpenSCAD-DocsGen package. + run: sudo pip3 install openscad-docsgen + + - name: Install OpenSCAD + run: | + cd $GITHUB_WORKSPACE + wget https://files.openscad.org/OpenSCAD-2021.01-x86_64.AppImage + sudo mv OpenSCAD-2021.01*-x86_64.AppImage /usr/local/bin/openscad + sudo chmod +x /usr/local/bin/openscad + + - name: Checking Docs + run: | + cd $GITHUB_WORKSPACE + echo "::add-matcher::.github/openscad_docsgen.json" + export OPENSCADPATH=$(dirname $GITHUB_WORKSPACE) + openscad-docsgen -Tmf *.scad + From c9d12f22d3038c3b59426a134f991418d096bb96 Mon Sep 17 00:00:00 2001 From: Garth Minette Date: Mon, 17 Jan 2022 00:41:39 -0800 Subject: [PATCH 5/7] Tweak to tutorials check. --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 618fc6b..46286b5 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -60,7 +60,7 @@ jobs: cd $GITHUB_WORKSPACE echo "::add-matcher::.github/openscad_docsgen.json" export OPENSCADPATH=$(dirname $GITHUB_WORKSPACE) - openscad-mdimggen -T *.scad + openscad-mdimggen *.scad CheckDocs: runs-on: ubuntu-latest From 7ffbec29119386e4218b94ed7fa269a93d2ae8e0 Mon Sep 17 00:00:00 2001 From: Garth Minette Date: Mon, 17 Jan 2022 00:45:36 -0800 Subject: [PATCH 6/7] More tutorial check tweaks. --- .github/workflows/main.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 46286b5..ecc79e4 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -60,7 +60,7 @@ jobs: cd $GITHUB_WORKSPACE echo "::add-matcher::.github/openscad_docsgen.json" export OPENSCADPATH=$(dirname $GITHUB_WORKSPACE) - openscad-mdimggen *.scad + openscad-mdimggen -T CheckDocs: runs-on: ubuntu-latest @@ -95,5 +95,5 @@ jobs: cd $GITHUB_WORKSPACE echo "::add-matcher::.github/openscad_docsgen.json" export OPENSCADPATH=$(dirname $GITHUB_WORKSPACE) - openscad-docsgen -Tmf *.scad + openscad-docsgen -Tmf From 5c39bcc873960eba839292776624a0f16ba6c136 Mon Sep 17 00:00:00 2001 From: Garth Minette Date: Mon, 17 Jan 2022 15:49:50 -0800 Subject: [PATCH 7/7] Removed PCAR --- paths.scad | 161 -------------------------------------------------- shapes3d.scad | 4 +- 2 files changed, 3 insertions(+), 162 deletions(-) diff --git a/paths.scad b/paths.scad index fc67255..f401604 100644 --- a/paths.scad +++ b/paths.scad @@ -689,169 +689,8 @@ function path_torsion(path, closed=false) = ]; -// Section: Modifying paths - -// 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 2*[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=true, chamfer, rounding) = - let ( - p = force_path(path) - ) - assert(is_path(p),"Input 'path' is not a path") - let( - path = deduplicate(p,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(last(crn1)-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) last(path) - ] - ) 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; - - - - // Section: Breaking paths up into subpaths - /// Internal Function: _path_cut_points() /// /// Usage: diff --git a/shapes3d.scad b/shapes3d.scad index a80a81c..35ebb57 100644 --- a/shapes3d.scad +++ b/shapes3d.scad @@ -94,7 +94,7 @@ function cube(size=1, center, anchor, spin=0, orient=UP) = // Usage: Chamfered Cubes // cuboid(size, [chamfer=], [edges=], [except=], [trimcorners=], ...); // Usage: Rounded Cubes -// cuboid(size, [rounding=], [edges=], [except=], [trimcorners=], ...); +// cuboid(size, [rounding=], [teardrop=], [edges=], [except=], [trimcorners=], ...); // Usage: Attaching children // cuboid(size, [anchor=], ...) [attachments]; // @@ -133,6 +133,8 @@ function cube(size=1, center, anchor, spin=0, orient=UP) = // cuboid([30,40,50], chamfer=5, trimcorners=false); // Example: Rounded Edges and Corners // cuboid([30,40,50], rounding=10); +// Example(VPR=[100,0,25],VPD=180): Rounded Edges and Corners with Teardrop Bottoms +// cuboid([30,40,50], rounding=10, teardrop=true); // Example: Rounded Edges, Untrimmed Corners // cuboid([30,40,50], rounding=10, trimcorners=false); // Example: Chamferring Selected Edges