diff --git a/shapes2d.scad b/shapes2d.scad index a948c197..8c5b449c 100644 --- a/shapes2d.scad +++ b/shapes2d.scad @@ -1321,26 +1321,33 @@ module jittered_poly(path, dist=1/512) { // Section: Curved 2D Shapes +// When called as a module, makes a 2D teardrop shape. Useful for extruding into 3D printable holes as it limits overhang to a desired angle. +// Uses "intersect" style anchoring. + + // Function&Module: teardrop2d() // Synopsis: Creates a 2D teardrop shape. // SynTags: Geom, Path // Topics: Shapes (2D), Paths (2D), Path Generators, Attachable // See Also: teardrop(), onion(), keyhole() // Description: -// When called as a module, makes a 2D teardrop shape. Useful for extruding into 3D printable holes as it limits overhang to 45 degrees. Uses "intersect" style anchoring. -// The cap_h parameter truncates the top of the teardrop. If cap_h is taller than the untruncated form then -// the result will be the full, untruncated shape. The segments of the bottom section of the teardrop are -// calculated to be the same as a circle or cylinder when rotated 90 degrees. (Note that this agreement is poor when `$fn=6` or `$fn=7`. +// A teardrop shape is a circle that comes to a point at the top. This shape is useful for extruding into 3d printable holes as it +// limits the overhang angle. A bottom point can also help ensure a 3d printable hole. This module can make a teardrop shape +// or produce the path for a teardrop with a point at the top or with the top truncated to create a flat cap. It also provides the option to add a bottom point. +// . +// The default teardrop has a pointed top and round bottom. The `ang` parameter specifies the angle away from vertical of the two flat segments at the +// top of the shape. The cap_h parameter truncates the top of the teardrop at the specified +// distance from the center. If `cap_h` is taller than the untruncated form then +// the result will be the full, untruncated shape. You can set `cap_h` smaller than the radius to produce a truncated circle. The segments of the round section of the teardrop +// are the same as a circle or cylinder with matching `$fn` when rotated 90 degrees. The number of facets in the teardrop is only approximately +// equal to `$fn`, and may also change if you set `realign=true`, which adjusts the facets so the bottom of the teardrop has a flat base. // If `$fn` is a multiple of four then the teardrop will reach its extremes on all four axes. The circum option -// produces a teardrop that circumscribes the circle; in this case set `realign=true` to get a teardrop that meets its internal extremes -// on the axes. -// When called as a function, returns a 2D path to for a teardrop shape. -// +// produces a teardrop that circumscribes the circle; in this, `realign=true` produces a teardrop that meets its internal extremes +// on the axes. You can add a bottom corner using the `bot_corner` parameter, which specifies the length that the corner protrudes from the ideal circle. // Usage: As Module -// teardrop2d(r/d=, [ang], [cap_h]) [ATTACHMENTS]; +// teardrop2d(r/d=, [ang], [cap_h], [circum=], [realign=], [bot_corner=]) [ATTACHMENTS]; // Usage: As Function -// path = teardrop2d(r|d=, [ang], [cap_h]); -// +// path = teardrop2d(r|d=, [ang], [cap_h], [circum=], [realign=], [bot_corner=]); // Arguments: // r = radius of circular part of teardrop. (Default: 1) // ang = angle of hat walls from the Y axis (half the angle of the peak). (Default: 45 degrees) @@ -1348,19 +1355,22 @@ module jittered_poly(path, dist=1/512) { // --- // d = diameter of circular portion of bottom. (Use instead of r) // circum = if true, create a circumscribing teardrop. Default: false +// bot_corner = create a bottom corner the specified distance below the given radius. Default: 0 // realign = if true, change whether bottom of teardrop is a point or a flat. 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` -// // Example(2D): Typical Shape // teardrop2d(r=30, ang=30); // Example(2D): Crop Cap // teardrop2d(r=30, ang=30, cap_h=40); // Example(2D): Close Crop // teardrop2d(r=30, ang=30, cap_h=20); -module teardrop2d(r, ang=45, cap_h, d, circum=false, realign=false, anchor=CENTER, spin=0) +// Example(2D): Add bottom corner. Here the bottom corner is quite large. Guidance for 3d printing suggests that `bot_corner` should equal the layer thickness. +// teardrop2d(r=30, cap_h=35, bot_corner=5); + +module teardrop2d(r, ang=45, cap_h, d, circum=false, realign=false, bot_corner=0, anchor=CENTER, spin=0) { - path = teardrop2d(r=r, d=d, ang=ang, circum=circum, realign=realign, cap_h=cap_h); + path = teardrop2d(r=r, d=d, ang=ang, circum=circum, realign=realign, cap_h=cap_h, bot_corner=bot_corner); attachable(anchor,spin, two_d=true, path=path, extent=false) { polygon(path); children(); @@ -1370,9 +1380,32 @@ module teardrop2d(r, ang=45, cap_h, d, circum=false, realign=false, anchor=CENTE // _extrapt = true causes the point to be duplicated so a teardrop with no cap // has the same point count as one with a cap. -function teardrop2d(r, ang=45, cap_h, d, circum=false, realign=false, anchor=CENTER, spin=0, _extrapt=false) = +function teardrop2d(r, ang=45, cap_h, d, circum=false, realign=false, anchor=CENTER, spin=0, bot_corner=0, _extrapt=false) = + let( + r = get_radius(r=r, d=d, dflt=1) + ) + bot_corner!=0 ? + assert(all_nonnegative([bot_corner]),"bot_corner must be nonnegative") + let( + path = teardrop2d(r=r,ang=ang, cap_h=cap_h, circum=circum, realign=realign), + corner = -r-bot_corner, + alpha = acos(r/corner), + joint = r*[sin(alpha),cos(alpha)], + table = [[0,corner],joint], + halfpath = [for(pt=path) if (pt.x>=0) + let(proj=lookup(pt.x,table)) + pt.x>joint.x || pt.y>0 || pt.y<=proj ? pt : [pt.x,proj]], + fullpath = deduplicate( + [ + each halfpath, + if (last(halfpath).x>0) [0,corner], + each reverse(xflip(halfpath)) + ], closed=!_extrapt + ) + ) + reorient(anchor,spin,two_d=true, path=fullpath, p=fullpath, extent=false) + : let( - r = get_radius(r=r, d=d, dflt=1), minheight = r*sin(ang), maxheight = r/sin(ang), //cos(90-ang), pointycap = is_undef(cap_h) || cap_h>=maxheight diff --git a/shapes3d.scad b/shapes3d.scad index 89435763..8c05fef6 100644 --- a/shapes3d.scad +++ b/shapes3d.scad @@ -3564,19 +3564,21 @@ function torus( // Topics: Shapes (3D), Attachable, VNF Generators, FDM Optimized // See Also: onion(), teardrop2d() // Description: -// Makes a teardrop shape in the XZ plane. Useful for 3D printable holes. +// Makes a teardrop extrusion along the Y axis, which is useful for 3D printable holes. // Optional chamfers can be added with positive or negative distances. A positive distance // specifies the amount to inset the chamfer along the front/back faces of the shape. // The chamfer will extend the same y distance into the shape. If the radii are the same // then the chamfer will be a 45 degree chamfer, but in other cases it will not. -// Note that with caps, the chamfer must not be so big that it makes the cap height illegal. -// +// With caps, the chamfer must not be so big that it makes the cap height illegal. +// Similarly the chamfer cannot be larger than `bot_corner` if it is set, and if you do +// set chamfer exactly equal to bottom corner, then `$fn` must be even if `realign` is false +// and odd otherwise. // Usage: Typical -// teardrop(h|l=|length=|height=, r, [ang], [cap_h], [chamfer=], ...) [ATTACHMENTS]; -// teardrop(h|l=|length=|height=, d=, [ang=], [cap_h=], [chamfer=], ...) [ATTACHMENTS]; +// teardrop(h|l=|length=|height=, r, [ang], [cap_h], [chamfer=], [bot_corner=], ...) [ATTACHMENTS]; +// teardrop(h|l=|length=|height=, d=, [ang=], [cap_h=], [chamfer=], [bot_corner=], ...) [ATTACHMENTS]; // Usage: Psuedo-Conical -// teardrop(h|l=|height=|length=, r1=, r2=, [ang=], [cap_h1=], [cap_h2=], ...) [ATTACHMENTS]; -// teardrop(h|l=|height=|length=, d1=, d2=, [ang=], [cap_h1=], [cap_h2=], ...) [ATTACHMENTS]; +// teardrop(h|l=|height=|length=, r1=, r2=, [ang=], [cap_h1=], [cap_h2=], [bot_corner1=], [bot_corner2=], ...) [ATTACHMENTS]; +// teardrop(h|l=|height=|length=, d1=, d2=, [ang=], [cap_h1=], [cap_h2=], [bot_corner1=], [bot_corner2=], ...) [ATTACHMENTS]; // Usage: As Function // vnf = teardrop(h|l=|height=|length=, r|d=, [ang=], [cap_h=], ...); // vnf = teardrop(h|l=|height=|length=, r1=|d1=, r2=|d2=, [ang=], [cap_h=], ...); @@ -3619,6 +3621,8 @@ function torus( // teardrop(r1=20, r2=30, h=40, cap_h1=25, cap_h2=35); // Example: Adding chamfers can be useful for a teardrop hole mask // teardrop(r=10, l=50, chamfer1=2, chamfer2=-1.5); +// Example: This teardrop has a 1 unit clearance at the top and bottom using the cap and the bottom corner: +// teardrop(r=10, l=50, cap_h=11, bot_corner=1); // Example: Getting a VNF // vnf = teardrop(r1=25, r2=30, l=20, cap_h1=25, cap_h2=35); // vnf_polyhedron(vnf); @@ -3628,9 +3632,22 @@ function torus( // Example(Spin,VPD=150,Med): Named Conical Connectors // teardrop(d1=20, d2=30, h=20, cap_h1=11, cap_h2=16) // show_anchors(std=false); +// Example: Creating holes using attachment +// $fn=32; +// diff() +// cuboid(15) +// attach([FWD,RIGHT],FWD,inside=true, shiftout=.1) +// tag("remove")teardrop(d=4, l=10); +// Example: You can rotate the point using the `spin` option to {{attach()}}. Don't use the `spin` parameter to `teardrop()`. +// $fn=32; +// diff() +// cuboid(15) +// attach(FWD,FWD,align=[TOP,BOT], inset=2, +// inside=true, shiftout=.1, spin=90) +// tag("remove")teardrop(d=4, l=10); module teardrop(h, r, ang=45, cap_h, r1, r2, d, d1, d2, cap_h1, cap_h2, l, length, height, circum=false, realign=false, - chamfer, chamfer1, chamfer2,anchor=CENTER, spin=0, orient=UP) + chamfer, chamfer1, chamfer2,anchor=CENTER, spin=0, orient=UP, bot_corner1, bot_corner2, bot_corner=0) { length = one_defined([l, h, length, height],"l,h,length,height"); dummy=assert(is_finite(length) && length>0, "length must be positive"); @@ -3649,13 +3666,13 @@ module teardrop(h, r, ang=45, cap_h, r1, r2, d, d1, d2, cap_h1, cap_h2, l, lengt attachable(anchor,spin,orient, r1=r1, r2=r2, l=length, axis=BACK, anchors=anchors) { vnf_polyhedron(teardrop(ang=ang,cap_h=cap_h,r1=r1,r2=r2,cap_h1=cap_h1,cap_h2=cap_h2,circum=circum,realign=realign, - length=length, chamfer1=chamfer1,chamfer2=chamfer2,chamfer=chamfer)); + length=length, chamfer1=chamfer1,chamfer2=chamfer2,chamfer=chamfer,bot_corner1=bot_corner1, bot_corner2=bot_corner2,bot_corner=bot_corner)); children(); } } -function teardrop(h, r, ang=45, cap_h, r1, r2, d, d1, d2, cap_h1, cap_h2, chamfer, chamfer1, chamfer2, circum=false, realign=false, +function teardrop(h, r, ang=45, cap_h, r1, r2, d, d1, d2, cap_h1, cap_h2, chamfer, chamfer1, chamfer2, circum=false, realign=false, bot_corner1, bot_corner2, bot_corner=0, l, length, height, anchor=CENTER, spin=0, orient=UP) = let( r1 = get_radius(r=r, r1=r1, d=d, d1=d1, dflt=1), @@ -3664,11 +3681,13 @@ function teardrop(h, r, ang=45, cap_h, r1, r2, d, d1, d2, cap_h1, cap_h2, chamf dummy0=assert(is_finite(length) && length>0, "length must be positive"), cap_h1 = first_defined([cap_h1, cap_h]), cap_h2 = first_defined([cap_h2, cap_h]), + bot_corner1 = first_defined([bot_corner1, bot_corner]), + bot_corner2 = first_defined([bot_corner2, bot_corner]), chamfer1 = first_defined([chamfer1,chamfer,0]), chamfer2 = first_defined([chamfer2,chamfer,0]), sides = segs(max(r1,r2)), - profile1 = teardrop2d(r=r1, ang=ang, cap_h=cap_h1, $fn=sides, circum=circum, realign=realign,_extrapt=true), - profile2 = teardrop2d(r=r2, ang=ang, cap_h=cap_h2, $fn=sides, circum=circum, realign=realign,_extrapt=true), + profile1 = teardrop2d(r=r1, ang=ang, cap_h=cap_h1, $fn=sides, circum=circum, realign=realign,_extrapt=true, bot_corner=bot_corner1), + profile2 = teardrop2d(r=r2, ang=ang, cap_h=cap_h2, $fn=sides, circum=circum, realign=realign,_extrapt=true, bot_corner=bot_corner2), tip_y1 = r1/cos(90-ang), tip_y2 = r2/cos(90-ang), _cap_h1 = min(default(cap_h1, tip_y1), tip_y1), @@ -3677,12 +3696,18 @@ function teardrop(h, r, ang=45, cap_h, r1, r2, d, d1, d2, cap_h1, cap_h2, chamf dummy= assert(abs(chamfer1)+abs(chamfer2) <= length,"chamfers are too big to fit in the length") assert(chamfer1<=r1 && chamfer2<=r2, "Chamfers cannot be larger than raduis") + assert(bot_corner1==0 || bot_corner1>=chamfer1, "\nchamfer1 doesn't work with bottom corner: must have chamfer1 <= bot_corner1") + assert(bot_corner2==0 || bot_corner2>=chamfer2, "\nchamfer2 doesn't work with bottom corner: must have chamfer2 <= bot_corner2") + assert(bot_corner1==0 || bot_corner1>chamfer1 || sides%2==(realign?1:0), + str("\nWith chamfer1==bot_corner1 and realign=",realign," must have ",realign?"odd":"even"," number of sides, but sides=",sides)) assert(is_undef(cap_h1) || cap_h1-chamfer1 > r1*sin(ang), "chamfer1 is too big to work with the specified cap_h1") assert(is_undef(cap_h2) || cap_h2-chamfer2 > r2*sin(ang), "chamfer2 is too big to work with the specified cap_h2"), cprof1 = r1==chamfer1 ? repeat([0,0],len(profile1)) - : teardrop2d(r=r1-chamfer1, ang=ang, cap_h=u_add(cap_h1,-chamfer1), $fn=sides, circum=circum, realign=realign,_extrapt=true), + : teardrop2d(r=r1-chamfer1, ang=ang, cap_h=u_add(cap_h1,-chamfer1), bot_corner=bot_corner1==0?0:bot_corner1-chamfer1, + $fn=sides, circum=circum, realign=realign,_extrapt=true), cprof2 = r2==chamfer2 ? repeat([0,0],len(profile2)) - : teardrop2d(r=r2-chamfer2, ang=ang, cap_h=u_add(cap_h2,-chamfer2), $fn=sides, circum=circum, realign=realign,_extrapt=true), + : teardrop2d(r=r2-chamfer2, ang=ang, cap_h=u_add(cap_h2,-chamfer2), bot_corner=bot_corner2==0?0:bot_corner2-chamfer2, + $fn=sides, circum=circum, realign=realign,_extrapt=true), anchors = [ named_anchor("cap", [0,0,(_cap_h1+_cap_h2)/2], capvec), named_anchor("cap_fwd", [0,-length/2,_cap_h1], unit((capvec+FWD)/2)),