//////////////////////////////////////////////////////////////////////
// LibFile: masks3d.scad
//   This file defines 3D masks for applying chamfers, roundovers, and teardrop roundovers to straight edges and circular
//   edges in three dimensions.  
// Includes:
//   include <BOSL2/std.scad>
// FileGroup: Basic Modeling
// FileSummary: 3D masks for rounding edges and corners.
// FileFootnotes: STD=Included in std.scad
//////////////////////////////////////////////////////////////////////


// Section: Chamfer Masks


// Module: chamfer_edge_mask()
// Usage:
//   chamfer_edge_mask(l, chamfer, [excess]);
// Description:
//   Creates a shape that can be used to chamfer a 90 degree edge.
//   Difference it from the object to be chamfered.  The center of
//   the mask object should align exactly with the edge to be chamfered.
// Arguments:
//   l = Length of mask.
//   chamfer = Size of chamfer.
//   excess = The extra amount to add to the length of the mask so that it differences away from other shapes cleanly.  Default: `0.1`
//   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`
//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#subsection-orient).  Default: `UP`
// Example:
//   chamfer_edge_mask(l=50, chamfer=10);
// Example:
//   difference() {
//       cube(50, anchor=BOTTOM+FRONT);
//       #chamfer_edge_mask(l=50, chamfer=10, orient=RIGHT);
//   }
// Example: Masking by Attachment
//   diff("mask")
//   cube(50, center=true) {
//       edge_mask(TOP+RIGHT)
//           #chamfer_edge_mask(l=50, chamfer=10);
//   }
module chamfer_edge_mask(l=1, chamfer=1, excess=0.1, anchor=CENTER, spin=0, orient=UP) {
    attachable(anchor,spin,orient, size=[chamfer*2, chamfer*2, l]) {
        cylinder(r=chamfer, h=l+excess, center=true, $fn=4);
        children();
    }
}


// Module: chamfer_corner_mask()
// Usage:
//   chamfer_corner_mask(chamfer);
// Description:
//   Creates a shape that can be used to chamfer a 90 degree corner.
//   Difference it from the object to be chamfered.  The center of
//   the mask object should align exactly with the corner to be chamfered.
// Arguments:
//   chamfer = Size of 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`
//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#subsection-orient).  Default: `UP`
// Example:
//   chamfer_corner_mask(chamfer=10);
// Example:
//   difference() {
//       cuboid(50, chamfer=10, trimcorners=false);
//       move(25*[1,-1,1]) #chamfer_corner_mask(chamfer=10);
//   }
// Example: Masking by Attachment
//   diff("mask")
//   cuboid(100, chamfer=20, trimcorners=false) {
//       corner_mask(TOP+FWD+RIGHT)
//           chamfer_corner_mask(chamfer=20);
//   }
// Example: Anchors
//   chamfer_corner_mask(chamfer=20)
//       show_anchors();
module chamfer_corner_mask(chamfer=1, anchor=CENTER, spin=0, orient=UP) {
    octahedron(chamfer*4, anchor=anchor, spin=spin, orient=orient) children();
}


// Module: chamfer_cylinder_mask()
// Usage:
//   chamfer_cylinder_mask(r|d, chamfer, [ang], [from_end])
// Description:
//   Create a mask that can be used to bevel/chamfer the end of a cylindrical region.
//   Difference it from the end of the region to be chamfered.  The center of the mask
//   object should align exactly with the center of the end of the cylindrical region
//   to be chamfered.
// Arguments:
//   r = Radius of cylinder to chamfer.
//   d = Diameter of cylinder to chamfer. Use instead of r.
//   chamfer = Size of the edge chamfered, inset from edge. (Default: 0.25)
//   ang = Angle of chamfer in degrees from vertical.  (Default: 45)
//   from_end = If true, chamfer size is measured from end of cylinder.  If false, chamfer is measured outset from the radius of the cylinder.  (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`
//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#subsection-orient).  Default: `UP`
// Example:
//   difference() {
//       cylinder(r=50, h=100, center=true);
//       up(50) #chamfer_cylinder_mask(r=50, chamfer=10);
//   }
// Example:
//   difference() {
//       cylinder(r=50, h=100, center=true);
//       up(50) chamfer_cylinder_mask(r=50, chamfer=10);
//   }
// Example: Masking by Attachment
module chamfer_cylinder_mask(r, d, chamfer=0.25, ang=45, from_end=false, anchor=CENTER, spin=0, orient=UP)
{
    r = get_radius(r=r, d=d, dflt=1);
    ch = from_end? chamfer : opp_ang_to_adj(chamfer,ang);
    attachable(anchor,spin,orient, r=r, l=ch*2) {
        difference() {
            cyl(r=r+chamfer, l=ch*2, anchor=CENTER);
            cyl(r=r, l=ch*3, chamfer=chamfer, chamfang=ang, from_end=from_end, anchor=TOP);
        }
        children();
    }
}



// Section: Rounding Masks

// Module: rounding_edge_mask()
// Usage:
//   rounding_edge_mask(l|h, r|d)
//   rounding_edge_mask(l|h, r1|d1, r2|d2)
// Description:
//   Creates a shape that can be used to round a vertical 90 degree edge.
//   Difference it from the object to be rounded.  The center of the mask
//   object should align exactly with the edge to be rounded.
// Arguments:
//   l = Length of mask.
//   r = Radius of the rounding.
//   r1 = Bottom radius of rounding.
//   r2 = Top radius of rounding.
//   d = Diameter of the rounding.
//   d1 = Bottom diameter of rounding.
//   d2 = Top diameter of rounding.
//   excess = Extra size for the mask.  Defaults: 0.1
//   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`
//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#subsection-orient).  Default: `UP`
// Example(VPD=200,VPR=[55,0,120]):
//   rounding_edge_mask(l=50, r1=10, r2=25);
// Example:
//   difference() {
//       cube(size=100, center=false);
//       #rounding_edge_mask(l=100, r=25, orient=UP, anchor=BOTTOM);
//   }
// Example: Varying Rounding Radius
//   difference() {
//       cube(size=50, center=false);
//       #rounding_edge_mask(l=50, r1=25, r2=10, orient=UP, anchor=BOTTOM);
//   }
// Example: Masking by Attachment
//   diff("mask")
//   cube(100, center=true)
//       edge_mask(FRONT+RIGHT)
//           #rounding_edge_mask(l=$parent_size.z+0.01, r=25);
// Example: Multiple Masking by Attachment
//   diff("mask")
//   cube([80,90,100], center=true) {
//       let(p = $parent_size*1.01) {
//           edge_mask(TOP)
//               rounding_edge_mask(l=p.z, r=25);
//       }
//   }
module rounding_edge_mask(l, r, r1, r2, d, d1, d2, excess=0.1, anchor=CENTER, spin=0, orient=UP, h=undef)
{
    l = first_defined([l, h, 1]);
    r1 = get_radius(r1=r1, r=r, d1=d1, d=d, dflt=1);
    r2 = get_radius(r1=r2, r=r, d1=d2, d=d, dflt=1);
    sides = quantup(segs(max(r1,r2)),4);
    attachable(anchor,spin,orient, size=[2*r1,2*r1,l], size2=[2*r2,2*r2]) {
        if (r1<r2) {
            zflip() {
                linear_extrude(height=l, convexity=4, center=true, scale=r1/r2) {
                    difference() {
                        translate(-excess*[1,1]) square(r2+excess);
                        translate([r2,r2]) circle(r=r2, $fn=sides);
                    }
                }
            }
        } else {
            linear_extrude(height=l, convexity=4, center=true, scale=r2/r1) {
                difference() {
                    translate(-excess*[1,1]) square(r1+excess);
                    translate([r1,r1]) circle(r=r1, $fn=sides);
                }
            }
        }
        children();
    }
}


// Module: rounding_corner_mask()
// Usage:
//   rounding_corner_mask(r|d, [excess=], [style=]);
// Description:
//   Creates a shape that you can use to round 90 degree corners.
//   Difference it from the object to be rounded.  The center of the mask
//   object should align exactly with the corner to be rounded.
// Arguments:
//   r = Radius of corner rounding.
//   d = Diameter of corner rounding.
//   ---
//   excess = Extra size for the mask.  Defaults: 0.1
//   style = The style of the sphere cutout's construction. One of "orig", "aligned", "stagger", "octa", or "icosa".  Default: "octa"
//   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`
//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#subsection-orient).  Default: `UP`
// Example:
//   rounding_corner_mask(r=20.0);
// Example:
//   difference() {
//       cube(size=[50, 60, 70], center=true);
//       translate([-25, -30, 35])
//           #rounding_corner_mask(r=20, spin=90, orient=DOWN);
//       translate([25, -30, 35])
//           #rounding_corner_mask(r=20, orient=DOWN);
//       translate([25, -30, -35])
//           #rounding_corner_mask(r=20, spin=90);
//   }
// Example: Masking by Attachment
//   diff("mask")
//   cube(size=[50, 60, 70]) {
//       corner_mask(TOP)
//           #rounding_corner_mask(r=20);
//   }
module rounding_corner_mask(r, d, style="octa", excess=0.1, anchor=CENTER, spin=0, orient=UP)
{
    r = get_radius(r=r, d=d, dflt=1);
    attachable(anchor,spin,orient, size=[2,2,2]*r) {
        difference() {
            translate(-excess*[1,1,1])
                cube(size=r+excess, center=false);
            translate([r,r,r])
                sphere(r=r, style=style);
        }
        children();
    }
}


// Module: rounding_angled_edge_mask()
// Usage:
//   rounding_angled_edge_mask(h, r|d, [ang]);
//   rounding_angled_edge_mask(h, r1|d1, r2|d2, [ang]);
// Description:
//   Creates a vertical mask that can be used to round the edge where two face meet, at any arbitrary
//   angle.  Difference it from the object to be rounded.  The center of the mask should align exactly
//   with the edge to be rounded.
// Arguments:
//   h = Height of vertical mask.
//   r = Radius of the rounding.
//   r1 = Bottom radius of rounding.
//   r2 = Top radius of rounding.
//   d = Diameter of the rounding.
//   d1 = Bottom diameter of rounding.
//   d2 = Top diameter of rounding.
//   ang = Angle that the planes meet at.
//   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`
//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#subsection-orient).  Default: `UP`
// Example:
//   difference() {
//       pie_slice(ang=70, h=50, d=100, center=true);
//       #rounding_angled_edge_mask(h=51, r=20.0, ang=70, $fn=32);
//   }
// Example: Varying Rounding Radius
//   difference() {
//       pie_slice(ang=70, h=50, d=100, center=true);
//       #rounding_angled_edge_mask(h=51, r1=10, r2=25, ang=70, $fn=32);
//   }
module rounding_angled_edge_mask(h=1.0, r, r1, r2, d, d1, d2, ang=90, anchor=CENTER, spin=0, orient=UP)
{
    function _mask_shape(r) = [
        for (i = [0:1:n]) let (a=90+ang+i*sweep/n) [r*cos(a)+x, r*sin(a)+r],
        for (i = [0:1:n]) let (a=90+i*sweep/n) [r*cos(a)+x, r*sin(a)-r],
        [min(-1, r*cos(270-ang)+x-1), r*sin(270-ang)-r],
        [min(-1, r*cos(90+ang)+x-1), r*sin(90+ang)+r],
    ];

    sweep = 180-ang;
    r1 = get_radius(r1=r1, r=r, d1=d1, d=d, dflt=1);
    r2 = get_radius(r1=r2, r=r, d1=d2, d=d, dflt=1);
    n = ceil(segs(max(r1,r2))*sweep/360);
    x = sin(90-(ang/2))/sin(ang/2) * (r1<r2? r2 : r1);
    if(r1<r2) {
        attachable(anchor,spin,orient, size=[2*x*r1/r2,2*r1,h], size2=[2*x,2*r2]) {
            zflip() {
                linear_extrude(height=h, convexity=4, center=true, scale=r1/r2) {
                    polygon(_mask_shape(r2));
                }
            }
            children();
        }
    } else {
        attachable(anchor,spin,orient, size=[2*x,2*r1,h], size2=[2*x*r2/r1,2*r2]) {
            linear_extrude(height=h, convexity=4, center=true, scale=r2/r1) {
                polygon(_mask_shape(r1));
            }
            children();
        }
    }
}


// Module: rounding_angled_corner_mask()
// Usage:
//   rounding_angled_corner_mask(r|d, ang);
// Description:
//   Creates a shape that can be used to round the corner of an angle.
//   Difference it from the object to be rounded.  The center of the mask
//   object should align exactly with the point of the corner to be rounded.
// Arguments:
//   r = Radius of the rounding.
//   d = Diameter of the rounding.
//   ang = Angle between planes that you need to round the corner of.
//   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`
//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#subsection-orient).  Default: `UP`
// Example(Med):
//   ang=60;
//   difference() {
//       pie_slice(ang=ang, h=50, r=200, center=true);
//       up(50/2) #rounding_angled_corner_mask(r=20, ang=ang);
//   }
module rounding_angled_corner_mask(r, ang=90, d, anchor=CENTER, spin=0, orient=UP)
{
    r = get_radius(r=r, d=d, dflt=1);
    dx = r / tan(ang/2);
    dx2 = dx / cos(ang/2) + 1;
    fn = quantup(segs(r), 4);
    attachable(anchor,spin,orient, d=dx2, l=2*r) {
        difference() {
            down(r) cylinder(r=dx2, h=r+1, center=false);
            yflip_copy() {
                translate([dx, r, -r]) {
                    hull() {
                        sphere(r=r, $fn=fn);
                        down(r*3) sphere(r=r, $fn=fn);
                        zrot_copies([0,ang]) {
                            right(r*3) sphere(r=r, $fn=fn);
                        }
                    }
                }
            }
        }
        children();
    }
}


// Module: rounding_cylinder_mask()
// Usage:
//   rounding_cylinder_mask(r|d, rounding);
// Description:
//   Create a mask that can be used to round the end of a cylinder.
//   Difference it from the cylinder to be rounded.  The center of the
//   mask object should align exactly with the center of the end of the
//   cylinder to be rounded.
// Arguments:
//   r = Radius of cylinder. (Default: 1.0)
//   d = Diameter of cylinder. (Default: 1.0)
//   rounding = Radius of the edge rounding. (Default: 0.25)
// Example:
//   difference() {
//     cylinder(r=50, h=50, center=false);
//     up(50) #rounding_cylinder_mask(r=50, rounding=10);
//   }
// Example:
//   difference() {
//     cylinder(r=50, h=50, center=false);
//     up(50) rounding_cylinder_mask(r=50, rounding=10);
//   }
// Example: Masking by Attachment
//   diff("mask")
//   cyl(h=30, d=30) {
//       attach(TOP)
//           #rounding_cylinder_mask(d=30, rounding=5, $tags="mask");
//   }
module rounding_cylinder_mask(r, rounding=0.25, d)
{
    r = get_radius(r=r, d=d, dflt=1);
    difference() {
        cyl(r=r+rounding, l=rounding*2, anchor=CENTER);
        cyl(r=r, l=rounding*3, rounding=rounding, anchor=TOP);
    }
}



// Module: rounding_hole_mask()
// Usage:
//   rounding_hole_mask(r|d, rounding, [excess]);
// Description:
//   Create a mask that can be used to round the edge of a circular hole.
//   Difference it from the hole to be rounded.  The center of the
//   mask object should align exactly with the center of the end of the
//   hole to be rounded.
// Arguments:
//   r = Radius of hole.
//   d = Diameter of hole to rounding.
//   rounding = Radius of the rounding. (Default: 0.25)
//   excess = The extra thickness of the mask.  Default: `0.1`.
//   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`
//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#subsection-orient).  Default: `UP`
// Example:
//   rounding_hole_mask(r=40, rounding=20, $fa=2, $fs=2);
// Example(Med):
//   difference() {
//     cube([150,150,100], center=true);
//     cylinder(r=50, h=100.1, center=true);
//     up(50) #rounding_hole_mask(r=50, rounding=10);
//   }
// Example(Med):
//   difference() {
//     cube([150,150,100], center=true);
//     cylinder(r=50, h=100.1, center=true);
//     up(50) rounding_hole_mask(r=50, rounding=10);
//   }
module rounding_hole_mask(r, rounding=0.25, excess=0.1, d, anchor=CENTER, spin=0, orient=UP)
{
    r = get_radius(r=r, d=d, dflt=1);
    attachable(anchor,spin,orient, r=r+rounding, l=2*rounding) {
        rotate_extrude(convexity=4) {
            difference() {
                right(r-excess) fwd(rounding) square(rounding+excess, center=false);
                right(r+rounding) fwd(rounding) circle(r=rounding);
            }
        }
        children();
    }
}


// Section: Teardrop Masking

// Module: teardrop_edge_mask()
// Usage:
//   teardrop_edge_mask(r|d, [angle], [excess]);
// Description:
//   Makes an apropriate 3D corner rounding mask that keeps within `angle` degrees of vertical.
// Arguments:
//   r = Radius of the mask rounding.
//   d = Diameter of the mask rounding.
//   angle = Maximum angle from vertical. Default: 45
//   excess = Excess mask size.  Default: 0.1
//   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`
//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#subsection-orient).  Default: `UP`
// Example(VPD=50,VPR=[55,0,120]):
//   teardrop_edge_mask(l=20, r=10, angle=40);
// Example(VPD=300,VPR=[75,0,25]):
//   diff("mask")
//   cuboid([50,60,70],rounding=10,edges="Z",anchor=CENTER) {
//       edge_mask(BOT)
//           teardrop_edge_mask(l=max($parent_size)+1, r=10, angle=40);
//       corner_mask(BOT)
//           teardrop_corner_mask(r=10, angle=40);
//   }
module teardrop_edge_mask(l, r, angle, excess=0.1, d, anchor=CENTER, spin=0, orient=UP) {
    assert(is_num(l));
    assert(is_num(angle));
    assert(is_num(excess));
    assert(angle>0 && angle<90);
    r = get_radius(r=r, d=d, dflt=1);
    difference() {
        translate(-[1,1,0]*excess) cube([r+excess,r+excess,l], anchor=FWD+LEFT);
        translate([r,r,0]) teardrop(r=r, l=l+1, cap_h=r, ang=angle, orient=FWD);
    }
}


// Module: teardrop_corner_mask()
// Usage:
//   teardrop_corner_mask(r|d, [angle], [excess]);
// Description:
//   Makes an apropriate 3D corner rounding mask that keeps within `angle` degrees of vertical.
// Arguments:
//   r = Radius of the mask rounding.
//   d = Diameter of the mask rounding.
//   angle = Maximum angle from vertical. Default: 45
//   excess = Excess mask size.  Default: 0.1
//   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`
//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#subsection-orient).  Default: `UP`
// Example:
//   teardrop_corner_mask(r=20, angle=40);
// Example:
//   diff("mask")
//   cuboid([50,60,70],rounding=10,edges="Z",anchor=CENTER) {
//       edge_profile(BOT)
//           mask2d_teardrop(r=10, angle=40);
//       corner_mask(BOT)
//           teardrop_corner_mask(r=10, angle=40);
//   }
module teardrop_corner_mask(r, angle, excess=0.1, d, anchor=CENTER, spin=0, orient=UP) {
    assert(is_num(angle));
    assert(is_num(excess));
    assert(angle>0 && angle<90);
    r = get_radius(r=r, d=d, dflt=1);
    difference() {
        translate(-[1,1,1]*excess) cube(r+excess, center=false);
        translate([1,1,1]*r) onion(r=r, ang=angle, orient=DOWN);
    }
}



// vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap