diff --git a/masks2d.scad b/masks2d.scad index 81071a6..f36a5d5 100644 --- a/masks2d.scad +++ b/masks2d.scad @@ -20,14 +20,16 @@ // Topics: Shapes (2D), Paths (2D), Path Generators, Attachable, Masks (2D) // See Also: corner_profile(), edge_profile(), face_profile(), fillet() // Usage: As module -// mask2d_roundover(r|d=, [inset], [mask_angle], [excess]) [ATTACHMENTS]; +// mask2d_roundover(r|d=|h=|cut=|joint=, [inset], [mask_angle], [excess], [flat_top=]) [ATTACHMENTS]; // Usage: As function -// path = mask2d_roundover(r|d=, [inset], [mask_angle], [excess]); +// path = mask2d_roundover(r|d=|h=|cut=|joint=, [inset], [mask_angle], [excess], [flat_top=]); // Description: // Creates a 2D roundover/bead mask shape that is useful for extruding into a 3D mask for an edge. // Conversely, you can use that same extruded shape to make an interior fillet between two walls. // As a 2D mask, this is designed to be differenced away from the edge of a shape that is in the first (X+Y+) quadrant. // If called as a function, this just returns a 2D path of the outline of the mask shape. +// The roundover can be specified by radius, diameter, height, cut, or joint length. +// ![Types of Roundovers](images/rounding/section-types-of-roundovers_fig1.png) // Arguments: // r = Radius of the roundover. // inset = Optional bead inset size. Default: 0 @@ -35,24 +37,34 @@ // excess = Extra amount of mask shape to creates on the X- and Y- sides of the shape. Default: 0.01 // --- // d = Diameter of the roundover. +// h = Mask height. Given instead of r or d when you want a consistent mask height, no matter what the mask angle. +// cut = Cut distance. IE: How much of the corner to cut off. See [Types of Roundovers](rounding.scad#section-types-of-roundovers). +// joint = Joint distance. IE: How far from the edge the roundover should start. See [Types of Roundovers](rounding.scad#section-types-of-roundovers). +// flat_top = If true, the top inset of the mask will be horizontal instead of angled by the mask_angle. Default: true. // 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` // Side Effects: // Tags the children with "remove" (and hence sets `$tag`) if no tag is already set. // -// Example(2D): 2D Roundover Mask +// Example(2D): 2D Roundover Mask by Radius // mask2d_roundover(r=10); // Example(2D): 2D Bead Mask // mask2d_roundover(r=10,inset=2); +// Example(2D): 2D Bead Mask by Height +// mask2d_roundover(h=10,inset=2); // Example(2D): 2D Bead Mask for a Non-Right Edge. // mask2d_roundover(r=10, inset=2, mask_angle=75); +// Example(2D): Disabling flat_top= +// mask2d_roundover(r=10, inset=2, flat_top=false, mask_angle=75); +// Example(2D): 2D Angled Bead Mask by Joint Length +// mask2d_roundover(joint=10, inset=2, mask_angle=75); // Example(2D): Increasing the Excess // mask2d_roundover(r=10, inset=2, mask_angle=75, excess=2); // Example: Masking by Edge Attachment // diff() // cube([50,60,70],center=true) // edge_profile([TOP,"Z"],except=[BACK,TOP+LEFT]) -// mask2d_roundover(r=10, inset=2); +// mask2d_roundover(h=12, inset=2); // Example: Making an interior fillet // %render() difference() { // move(-[5,0,5]) cube(30, anchor=BOT+LEFT); @@ -61,8 +73,8 @@ // xrot(90) // linear_extrude(height=30, center=true) // mask2d_roundover(r=10); -module mask2d_roundover(r, inset=0, mask_angle=90, excess=0.01, d, anchor=CENTER,spin=0) { - path = mask2d_roundover(r=r, d=d, inset=inset, mask_angle=mask_angle, excess=excess); +module mask2d_roundover(r, inset=0, mask_angle=90, excess=0.01, flat_top=true, d, h, cut, joint, anchor=CENTER,spin=0) { + path = mask2d_roundover(r=r, d=d, h=h, cut=cut, joint=joint, inset=inset, flat_top=flat_top, mask_angle=mask_angle, excess=excess); default_tag("remove") { attachable(anchor,spin, two_d=true, path=path) { polygon(path); @@ -71,40 +83,127 @@ module mask2d_roundover(r, inset=0, mask_angle=90, excess=0.01, d, anchor=CENTER } } -function mask2d_roundover(r, inset=0, mask_angle=90, excess=0.01, d, anchor=CENTER, spin=0) = - assert(is_finite(r)||is_finite(d)) +function mask2d_roundover(r, inset=0, mask_angle=90, excess=0.01, flat_top=true, d, h, cut, joint, anchor=CENTER, spin=0) = + assert(one_defined([r,d,h,cut,joint],"r,d,h,cut,joint")) + assert(is_undef(r) || is_finite(r)) + assert(is_undef(d) || is_finite(d)) + assert(is_undef(h) || is_finite(h)) + assert(is_undef(cut) || is_finite(cut)) + assert(is_undef(joint) || is_finite(joint)) assert(is_finite(excess)) assert(is_finite(mask_angle) && mask_angle>0 && mask_angle<180) assert(is_finite(inset)||(is_vector(inset)&&len(inset)==2)) + assert(is_bool(flat_top)) let( inset = is_list(inset)? inset : [inset,inset], - r = get_radius(r=r,d=d,dflt=1), - avec = polar_to_xy(inset.x,mask_angle-90), - line1 = [[0,inset.y], [100,inset.y]], - line2 = [avec, polar_to_xy(100,mask_angle)+avec], - corner = line_intersection(line1,line2), - arcpts = arc(r=r, corner=[line2.y, corner, line1.y]), - ipath = [ - arcpts[0] + polar_to_xy(inset.x+excess, mask_angle+90), + r = is_finite(joint)? adj_ang_to_opp(joint, mask_angle/2) : + is_finite(h)? ( + mask_angle==90? h-inset.y : + mask_angle < 90 ? adj_ang_to_opp(opp_ang_to_hyp(h-inset.y,mask_angle), mask_angle/2) : + adj_ang_to_opp(adj_ang_to_hyp(h-inset.y,mask_angle-90), mask_angle/2) + ) : + is_finite(cut) + ? let( + o = adj_ang_to_opp(cut, mask_angle/2), + h = adj_ang_to_hyp(cut, mask_angle/2) + ) adj_ang_to_opp(o+h, mask_angle/2) + : get_radius(r=r,d=d,dflt=undef), + pts = _inset_isect(inset,mask_angle,flat_top,excess,-r), + arcpts = arc(r=r, corner=[pts[4],pts[5],pts[0]]), + path = [ + each select(pts, 1, 3), each arcpts, - last(arcpts) + polar_to_xy(inset.y+excess, -90), - [0,-excess], - [-excess,-excess], - [-excess,0] - ], - path = deduplicate(ipath) + ] ) reorient(anchor,spin, two_d=true, path=path, extent=false, p=path); +function _inset_isect(inset,mask_angle,flat_top,excess,r,size) = + assert(one_defined([size,r],"size,r")) + let( + lft_n = polar_to_xy(1, mask_angle-90), + rgt_n = [1,0], + top_n = flat_top? [1,0] : lft_n, + bot_n = [0,1], + + line_lft = [[0,0], polar_to_xy(100, mask_angle)], + line_bot = [[0,0], [100,0]], + ex_line_lft = move(-excess*lft_n, p=line_lft), + ex_line_bot = move(-excess*bot_n, p=line_bot), + in_line_lft = move(inset.x*top_n, p=line_lft), + in_line_bot = move(inset.y*bot_n, p=line_bot), + + ex_pt = line_intersection(ex_line_lft, ex_line_bot), + in_pt = line_intersection(in_line_lft, in_line_bot), + + pos_r = r==undef || r >= 0, + r = r==undef? undef : abs(r), + x = is_undef(size)? r : size.x, + y = is_undef(size)? r : size.y, + base_pt = !flat_top && is_num(r)? in_pt : + in_pt + [y*cos(mask_angle)/sin(mask_angle), 0], + line_top = !flat_top && is_num(r) + ? let( pt = in_pt + polar_to_xy(r/(pos_r?1:tan(mask_angle/2)), mask_angle) ) + [pt, pt - top_n] + : [base_pt + [0,y], base_pt + [0,y] - top_n], + line_rgt = !flat_top && is_num(r) + ? pos_r + ? [in_pt + [r,0], in_pt + [r,1]] + : [in_pt + [r/tan(mask_angle/2),0], in_pt + [r/tan(mask_angle/2),1]] + : [base_pt + [x,0], base_pt + [x,1]], + top_pt = line_intersection(ex_line_lft, line_top), + + path = is_vector(size)? [ + // All size based + base_pt + [size.x,0], + [base_pt.x + size.x, -excess], + ex_pt, + top_pt, + base_pt + [0,size.y], + base_pt, + base_pt + size, + ] : flat_top? [ + // flat_top radius + base_pt + [r,0], + [base_pt.x + r, -excess], + ex_pt, + top_pt, + base_pt + [0,r], + base_pt, + base_pt + [r,r], + ] : let( + cp_pt = line_intersection(line_rgt, line_top) + ) pos_r? [ + // non-flat_top radius from inside + in_pt + [r,0], + [in_pt.x + r, -excess], + ex_pt, + top_pt, + in_pt + polar_to_xy(r,mask_angle), + in_pt, + cp_pt, + ] : [ + // non-flat_top radius from outside + line_rgt[0], + [cp_pt.x, -excess], + ex_pt, + top_pt, + in_pt + polar_to_xy(r/tan(mask_angle/2),mask_angle), + in_pt, + cp_pt, + ] + ) path; + + + // Function&Module: mask2d_cove() // Synopsis: Creates a 2D cove (quarter-round) mask shape. // SynTags: Geom, Path // Topics: Shapes (2D), Paths (2D), Path Generators, Attachable, Masks (2D) // See Also: corner_profile(), edge_profile(), face_profile() // Usage: As module -// mask2d_cove(r|d=, [inset], [mask_angle], [excess]) [ATTACHMENTS]; +// mask2d_cove(r|d=|h=, [inset], [mask_angle], [excess], [flat_top=]) [ATTACHMENTS]; // Usage: As function -// path = mask2d_cove(r|d=, [inset], [mask_angle], [excess]); +// path = mask2d_cove(r|d=|h=, [inset], [mask_angle], [excess], [flat_top=]); // Description: // Creates a 2D cove mask shape that is useful for extruding into a 3D mask for an edge. // Conversely, you can use that same extruded shape to make an interior rounded shelf decoration between two walls. @@ -117,23 +216,31 @@ function mask2d_roundover(r, inset=0, mask_angle=90, excess=0.01, d, anchor=CENT // excess = Extra amount of mask shape to creates on the X- and Y- sides of the shape. Default: 0.01 // --- // d = Diameter of the cove. +// h = Mask height. Given instead of r or d when you want a consistent mask height, no matter what the mask angle. +// flat_top = If true, the top inset of the mask will be horizontal instead of angled by the mask_angle. Default: true. // 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` // Side Effects: // Tags the children with "remove" (and hence sets `$tag`) if no tag is already set. -// Example(2D): 2D Cove Mask +// Example(2D): 2D Cove Mask by Radius // mask2d_cove(r=10); // Example(2D): 2D Inset Cove Mask // mask2d_cove(r=10,inset=3); +// Example(2D): 2D Inset Cove Mask by Height +// mask2d_cove(h=10,inset=2); // Example(2D): 2D Inset Cove Mask for a Non-Right Edge // mask2d_cove(r=10,inset=3,mask_angle=75); +// Example(2D): Disabling flat_top= +// mask2d_cove(r=10, inset=3, flat_top=false, mask_angle=75); +// Example(2D): 2D Angled Inset Cove Mask by Joint Length +// mask2d_cove(joint=10, inset=3, mask_angle=75); // Example(2D): Increasing the Excess // mask2d_cove(r=10,inset=3,mask_angle=75, excess=2); // Example: Masking by Edge Attachment // diff() // cube([50,60,70],center=true) // edge_profile([TOP,"Z"],except=[BACK,TOP+LEFT]) -// mask2d_cove(r=10, inset=2); +// mask2d_cove(h=10, inset=3); // Example: Making an interior rounded shelf // %render() difference() { // move(-[5,0,5]) cube(30, anchor=BOT+LEFT); @@ -142,8 +249,8 @@ function mask2d_roundover(r, inset=0, mask_angle=90, excess=0.01, d, anchor=CENT // xrot(90) // linear_extrude(height=30, center=true) // mask2d_cove(r=5, inset=5); -module mask2d_cove(r, inset=0, mask_angle=90, excess=0.01, d, anchor=CENTER, spin=0) { - path = mask2d_cove(r=r, d=d, inset=inset, mask_angle=mask_angle, excess=excess); +module mask2d_cove(r, inset=0, mask_angle=90, excess=0.01, flat_top=true, d, h, anchor=CENTER, spin=0) { + path = mask2d_cove(r=r, d=d, h=h, flat_top=flat_top, inset=inset, mask_angle=mask_angle, excess=excess); default_tag("remove") { attachable(anchor,spin, two_d=true, path=path) { polygon(path); @@ -152,26 +259,27 @@ module mask2d_cove(r, inset=0, mask_angle=90, excess=0.01, d, anchor=CENTER, spi } } -function mask2d_cove(r, inset=0, mask_angle=90, excess=0.01, d, anchor=CENTER, spin=0) = - assert(is_finite(r)||is_finite(d)) +function mask2d_cove(r, inset=0, mask_angle=90, excess=0.01, flat_top=true, d, h, anchor=CENTER, spin=0) = + assert(one_defined([r,d,h],"r,d,h")) + assert(is_undef(r) || is_finite(r)) + assert(is_undef(d) || is_finite(d)) + assert(is_undef(h) || is_finite(h)) assert(is_finite(mask_angle) && mask_angle>0 && mask_angle<180) assert(is_finite(excess)) assert(is_finite(inset)||(is_vector(inset)&&len(inset)==2)) + assert(is_bool(flat_top)) let( inset = is_list(inset)? inset : [inset,inset], - r = get_radius(r=r,d=d,dflt=1), - avec = polar_to_xy(inset.x,mask_angle-90), - line1 = [[0,inset.y], [100,inset.y]], - line2 = [avec, polar_to_xy(100,mask_angle)+avec], - corner = line_intersection(line1,line2), - arcpts = arc(r=r, cp=corner, start=mask_angle, angle=-mask_angle), + r = is_finite(h)? ( + mask_angle==90? h-inset.y : + mask_angle < 90 ? adj_ang_to_opp(opp_ang_to_hyp(h-inset.y,mask_angle), mask_angle/2) : + adj_ang_to_opp(adj_ang_to_hyp(h-inset.y,mask_angle-90), mask_angle/2) + ) : get_radius(r=r,d=d,dflt=undef), + pts = _inset_isect(inset,mask_angle,flat_top,excess,r), + arcpts = arc(r=r, corner=[pts[4],pts[6],pts[0]]), ipath = [ - arcpts[0] + polar_to_xy(inset.x+excess, mask_angle+90), + each select(pts, 1, 3), each arcpts, - last(arcpts) + polar_to_xy(inset.y+excess, -90), - [0,-excess], - [-excess,-excess], - [-excess,0] ], path = deduplicate(ipath) ) reorient(anchor,spin, two_d=true, path=path, p=path); @@ -201,10 +309,12 @@ function mask2d_cove(r, inset=0, mask_angle=90, excess=0.01, d, anchor=CENTER, s // edge = The length of the edge of the chamfer. // angle = The angle of the chamfer edge, away from vertical. Default: 45. // inset = Optional amount to inset code from corner. Default: 0 +// mask_angle = Number of degrees in the corner angle to mask. Default: 90 // excess = Extra amount of mask shape to creates on the X- and Y- sides of the shape. Default: 0.01 // --- // x = The width of the chamfer. // y = The height of the chamfer. +// flat_top = If true, the top inset of the mask will be horizontal instead of angled by the mask_angle. Default: true. // 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` // Side Effects: @@ -230,8 +340,8 @@ function mask2d_cove(r, inset=0, mask_angle=90, excess=0.01, d, anchor=CENTER, s // xrot(90) // linear_extrude(height=30, center=true) // mask2d_chamfer(edge=10); -module mask2d_chamfer(edge, angle=45, inset=0, excess=0.01, x, y, anchor=CENTER,spin=0) { - path = mask2d_chamfer(x=x, y=y, edge=edge, angle=angle, excess=excess, inset=inset); +module mask2d_chamfer(edge, angle=45, inset=0, excess=0.01, mask_angle=90, flat_top=true, x, y, anchor=CENTER,spin=0) { + path = mask2d_chamfer(x=x, y=y, edge=edge, angle=angle, excess=excess, inset=inset, mask_angle=mask_angle, flat_top=flat_top); default_tag("remove") { attachable(anchor,spin, two_d=true, path=path, extent=true) { polygon(path); @@ -240,10 +350,11 @@ module mask2d_chamfer(edge, angle=45, inset=0, excess=0.01, x, y, anchor=CENTER, } } -function mask2d_chamfer(edge, angle=45, inset=0, excess=0.01, x, y, anchor=CENTER,spin=0) = +function mask2d_chamfer(edge, angle=45, inset=0, excess=0.01, mask_angle=90, flat_top=true, x, y, anchor=CENTER,spin=0) = let(dummy=one_defined([x,y,edge],["x","y","edge"])) assert(is_finite(angle)) assert(is_finite(excess)) + assert(is_finite(mask_angle) && mask_angle>0 && mask_angle<180) assert(is_finite(inset)||(is_vector(inset)&&len(inset)==2)) let( inset = is_list(inset)? inset : [inset,inset], @@ -251,12 +362,10 @@ function mask2d_chamfer(edge, angle=45, inset=0, excess=0.01, x, y, anchor=CENTE is_def(y)? adj_ang_to_opp(adj=y,ang=angle) : hyp_ang_to_opp(hyp=edge,ang=angle), y = opp_ang_to_adj(opp=x,ang=angle), + pts = _inset_isect(inset,mask_angle,flat_top,excess,size=[x,y]), path = [ - [x+inset.x, -excess], - [-excess, -excess], - [-excess, y+inset.y], - [inset.x, y+inset.y], - [x+inset.x, inset.y] + each select(pts, 1, 4), + pts[0], ] ) reorient(anchor,spin, two_d=true, path=path, extent=true, p=path); @@ -267,9 +376,9 @@ function mask2d_chamfer(edge, angle=45, inset=0, excess=0.01, x, y, anchor=CENTE // Topics: Shapes (2D), Paths (2D), Path Generators, Attachable, Masks (2D) // See Also: corner_profile(), edge_profile(), face_profile() // Usage: As Module -// mask2d_rabbet(size, [mask_angle], [excess]) [ATTACHMENTS]; +// mask2d_rabbet(size, [mask_angle], [excess], [flat_top=]) [ATTACHMENTS]; // Usage: As Function -// path = mask2d_rabbet(size, [mask_angle], [excess]); +// path = mask2d_rabbet(size, [mask_angle], [excess], [flat_top=]); // Description: // Creates a 2D rabbet mask shape that is useful for extruding into a 3D mask for an edge. // Conversely, you can use that same extruded shape to make an interior shelf decoration between two walls. @@ -277,9 +386,11 @@ function mask2d_chamfer(edge, angle=45, inset=0, excess=0.01, x, y, anchor=CENTE // If called as a function, this just returns a 2D path of the outline of the mask shape. // Arguments: // size = The size of the rabbet, either as a scalar or an [X,Y] list. +// inset = Optional bead inset size. Default: 0 // mask_angle = Number of degrees in the corner angle to mask. Default: 90 // excess = Extra amount of mask shape to creates on the X- and Y- sides of the shape. Default: 0.01 // --- +// flat_top = If true, the top inset of the mask will be horizontal instead of angled by the mask_angle. Default: true. // 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` // Side Effects: @@ -289,7 +400,9 @@ function mask2d_chamfer(edge, angle=45, inset=0, excess=0.01, x, y, anchor=CENTE // Example(2D): 2D Asymmetrical Rabbet Mask // mask2d_rabbet(size=[5,10]); // Example(2D): 2D Mask for a Non-Right Edge -// mask2d_rabbet(size=10,mask_angle=75); +// mask2d_rabbet(size=10, mask_angle=75); +// Example(2D): Disabling flat_top= +// mask2d_rabbet(size=10, flat_top=false, mask_angle=75); // Example: Masking by Edge Attachment // diff() // cube([50,60,70],center=true) @@ -303,8 +416,8 @@ function mask2d_chamfer(edge, angle=45, inset=0, excess=0.01, x, y, anchor=CENTE // xrot(90) // linear_extrude(height=30, center=true) // mask2d_rabbet(size=[5,10]); -module mask2d_rabbet(size, mask_angle=90, excess=0.01, anchor=CTR, spin=0) { - path = mask2d_rabbet(size=size, mask_angle=mask_angle, excess=excess); +module mask2d_rabbet(size, inset=[0,0], mask_angle=90, excess=0.01, flat_top=true, anchor=CTR, spin=0) { + path = mask2d_rabbet(size=size, inset=inset, mask_angle=mask_angle, excess=excess, flat_top=flat_top); default_tag("remove") { attachable(anchor,spin, two_d=true, path=path, extent=false) { polygon(path); @@ -313,23 +426,18 @@ module mask2d_rabbet(size, mask_angle=90, excess=0.01, anchor=CTR, spin=0) { } } -function mask2d_rabbet(size, mask_angle=90, excess=0.01, anchor=CTR, spin=0) = +function mask2d_rabbet(size, inset=[0,0], mask_angle=90, excess=0.01, flat_top=true, anchor=CTR, spin=0) = assert(is_finite(size)||(is_vector(size)&&len(size)==2)) assert(is_finite(mask_angle) && mask_angle>0 && mask_angle<180) assert(is_finite(excess)) + assert(is_bool(flat_top)) let( size = is_list(size)? size : [size,size], - avec = polar_to_xy(size.x,mask_angle-90), - line1 = [[0,size.y], [100,size.y]], - line2 = [avec, polar_to_xy(100,mask_angle)+avec], - cp = line_intersection(line1,line2), + pts = _inset_isect(inset,mask_angle,flat_top,excess,size=size), path = [ - cp + polar_to_xy(size.x+excess, mask_angle+90), - cp, - cp + polar_to_xy(size.y+excess, -90), - [0,-excess], - [-excess,-excess], - [-excess,0] + each select(pts, 1, 4), + pts[6], + pts[0], ] ) reorient(anchor,spin, two_d=true, path=path, extent=false, p=path); @@ -353,12 +461,14 @@ function mask2d_rabbet(size, mask_angle=90, excess=0.01, anchor=CTR, spin=0) = // Arguments: // edge = The length of the edge of the dovetail. // angle = The angle of the chamfer edge, away from vertical. Default: 30. -// inset = Optional amount to inset code from corner. Default: 0 // shelf = The extra height to add to the inside corner of the dovetail. Default: 0 +// inset = Optional amount to inset code from corner. Default: 0 +// mask_angle = Number of degrees in the corner angle to mask. Default: 90 // excess = Extra amount of mask shape to creates on the X- and Y- sides of the shape. Default: 0.01 // --- // x = The width of the dovetail. // y = The height of the dovetail. +// flat_top = If true, the top inset of the mask will be horizontal instead of angled by the mask_angle. Default: true. // 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` // Side Effects: @@ -384,8 +494,8 @@ function mask2d_rabbet(size, mask_angle=90, excess=0.01, anchor=CTR, spin=0) = // xrot(90) // linear_extrude(height=30, center=true) // mask2d_dovetail(x=10); -module mask2d_dovetail(edge, angle=30, inset=0, shelf=0, excess=0.01, x, y, anchor=CENTER, spin=0) { - path = mask2d_dovetail(x=x, y=y, edge=edge, angle=angle, inset=inset, shelf=shelf, excess=excess); +module mask2d_dovetail(edge, angle=30, shelf=0, inset=0, mask_angle=90, excess=0.01, flat_top=true, x, y, anchor=CENTER, spin=0) { + path = mask2d_dovetail(x=x, y=y, edge=edge, angle=angle, inset=inset, shelf=shelf, excess=excess, flat_top=flat_top, mask_angle=mask_angle); default_tag("remove") { attachable(anchor,spin, two_d=true, path=path) { polygon(path); @@ -394,7 +504,7 @@ module mask2d_dovetail(edge, angle=30, inset=0, shelf=0, excess=0.01, x, y, anch } } -function mask2d_dovetail(edge, angle=30, inset=0, shelf=0, excess=0.01, x, y, anchor=CENTER, spin=0) = +function mask2d_dovetail(edge, angle=30, shelf=0, inset=0, mask_angle=90, excess=0.01, flat_top=true, x, y, anchor=CENTER, spin=0) = assert(num_defined([x,y,edge])==1) assert(is_finite(first_defined([x,y,edge]))) assert(is_finite(angle)) @@ -406,13 +516,13 @@ function mask2d_dovetail(edge, angle=30, inset=0, shelf=0, excess=0.01, x, y, an !is_undef(y)? adj_ang_to_opp(adj=y,ang=angle) : hyp_ang_to_opp(hyp=edge,ang=angle), y = opp_ang_to_adj(opp=x,ang=angle), + pts = _inset_isect(inset,mask_angle,flat_top,excess,size=[x,y+shelf]), path = [ - [inset.x,0], - [-excess, 0], - [-excess, y+inset.y+shelf], - inset+[x,y+shelf], - inset+[x,y], - inset + [max(0,pts[5].x),-excess], + each select(pts, 2, 4), + pts[6], + pts[6]-[0,shelf], + pts[5], ] ) reorient(anchor,spin, two_d=true, path=path, p=path); @@ -432,13 +542,20 @@ function mask2d_dovetail(edge, angle=30, inset=0, shelf=0, excess=0.01, x, y, an // As a 2D mask, this is designed to be differenced away from the edge of a shape that is in the first (X+Y+) quadrant. // If called as a function, this just returns a 2D path of the outline of the mask shape. // This is particularly useful to make partially rounded bottoms, that don't need support to print. +// The roundover can be specified by radius, diameter, height, cut, or joint length. +// ![Types of Roundovers](images/rounding/section-types-of-roundovers_fig1.png) // Arguments: // r = Radius of the rounding. // angle = The maximum angle from vertical. +// inset = Optional bead inset size. Default: 0 // mask_angle = Number of degrees in the corner angle to mask. Default: 90 // excess = Extra amount of mask shape to creates on the X- and Y- sides of the shape. Default: 0.01 // --- // d = Diameter of the rounding. +// h = Mask height. Given instead of r or d when you want a consistent mask height, no matter what the mask angle. +// cut = Cut distance. IE: How much of the corner to cut off. See [Types of Roundovers](rounding.scad#section-types-of-roundovers). +// joint = Joint distance. IE: How far from the edge the roundover should start. See [Types of Roundovers](rounding.scad#section-types-of-roundovers). +// flat_top = If true, the top inset of the mask will be horizontal instead of angled by the mask_angle. Default: true. // 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` // Side Effects: @@ -464,34 +581,50 @@ function mask2d_dovetail(edge, angle=30, inset=0, shelf=0, excess=0.01, x, y, an // xrot(90) // linear_extrude(height=30, center=true) // mask2d_teardrop(r=10); -function mask2d_teardrop(r, angle=45, mask_angle=90, excess=0.01, d, anchor=CENTER, spin=0) = +function mask2d_teardrop(r, angle=45, inset=[0,0], mask_angle=90, excess=0.01, flat_top=true, d, h, cut, joint, anchor=CENTER, spin=0) = + assert(one_defined([r,d,h,cut,joint],"r,d,h,cut,joint")) + assert(is_undef(r) || is_finite(r)) + assert(is_undef(d) || is_finite(d)) + assert(is_undef(h) || is_finite(h)) + assert(is_undef(cut) || is_finite(cut)) + assert(is_undef(joint) || is_finite(joint)) assert(is_finite(angle)) assert(angle>0 && angle<90) assert(is_finite(mask_angle) && mask_angle>0 && mask_angle<180) assert(is_finite(excess)) let( - r = get_radius(r=r, d=d, dflt=1), - avec = polar_to_xy(r,mask_angle-90), - line1 = [[0,r], [100,r]], - line2 = [avec, polar_to_xy(100,mask_angle)+avec], - cp = line_intersection(line1,line2), - tp = cp + polar_to_xy(r,180+angle), - bp = [tp.x+adj_ang_to_opp(tp.y,angle), 0], - arcpts = arc(r=r, cp=cp, angle=[mask_angle+90,180+angle]), - ipath = [ - arcpts[0] + polar_to_xy(excess, mask_angle+90), - each arcpts, - bp, - bp + [0,-excess], - [0,-excess], - [-excess,-excess], - [-excess,0] + r = is_finite(joint)? adj_ang_to_opp(joint, mask_angle/2) : + is_finite(h)? ( + mask_angle==90? h : + mask_angle < 90 ? adj_ang_to_opp(opp_ang_to_hyp(h,mask_angle), mask_angle/2) : + adj_ang_to_opp(adj_ang_to_hyp(h,mask_angle-90), mask_angle/2) + ) : + is_finite(cut) + ? let( + o = adj_ang_to_opp(cut, mask_angle/2), + h = adj_ang_to_hyp(cut, mask_angle/2) + ) adj_ang_to_opp(o+h, mask_angle/2) + : get_radius(r=r,d=d,dflt=undef), + pts = _inset_isect(inset,mask_angle,flat_top,excess,-r), + arcpts = arc(r=r, corner=[pts[4],pts[5],pts[0]]), + arcpts2 = [ + for (i = idx(arcpts)) + if(i==0 || v_theta(arcpts[i]-arcpts[i-1]) <= angle-90) + arcpts[i] ], - path = deduplicate(ipath) + line1 = [last(arcpts2), last(arcpts2) + polar_to_xy(1, angle-90)], + line2 = [[0,inset.y], [100,inset.y]], + ipt = line_intersection(line1,line2), + path = [ + [ipt.x, -excess], + each select(pts, 2, 3), + each arcpts2, + ipt, + ] ) reorient(anchor,spin, two_d=true, path=path, p=path); -module mask2d_teardrop(r, angle=45, mask_angle=90, excess=0.01, d, anchor=CENTER, spin=0) { - path = mask2d_teardrop(r=r, d=d, angle=angle, mask_angle=mask_angle, excess=excess); +module mask2d_teardrop(r, angle=45, mask_angle=90, excess=0.01, flat_top=true, d, h, cut, joint, anchor=CENTER, spin=0) { + path = mask2d_teardrop(r=r, d=d, h=h, cut=cut, joint=joint, angle=angle, mask_angle=mask_angle, excess=excess); default_tag("remove") { attachable(anchor,spin, two_d=true, path=path) { polygon(path);