//////////////////////////////////////////////////////////////////////
// LibFile: joiners.scad
//   Snap-together joiners.
//   To use, add the following lines to the beginning of your file:
//   ```
//   include <BOSL2/std.scad>
//   include <BOSL2/joiners.scad>
//   ```
//////////////////////////////////////////////////////////////////////


// Section: Half Joiners


// Module: half_joiner_clear()
// Description:
//   Creates a mask to clear an area so that a half_joiner can be placed there.
// Usage:
//   half_joiner_clear(h, w, [a], [clearance], [overlap])
// Arguments:
//   h = Height of the joiner to clear space for.
//   w = Width of the joiner to clear space for.
//   a = Overhang angle of the joiner.
//   clearance = Extra width to clear.
//   overlap = Extra depth to clear.
//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#anchor).  Default: `CENTER`
//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#spin).  Default: `0`
//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#orient).  Default: `UP`
// Example:
//   half_joiner_clear(spin=-90);
module half_joiner_clear(h=20, w=10, a=30, clearance=0, overlap=0.01, anchor=CENTER, spin=0, orient=UP)
{
	dmnd_height = h*1.0;
	dmnd_width = dmnd_height*tan(a);
	guide_size = w/3;
	guide_width = 2*(dmnd_height/2-guide_size)*tan(a);

	orient_and_anchor([w, guide_width, h], orient, anchor, spin=spin) {
		union() {
			yspread(overlap, n=overlap>0? 2 : 1) {
				difference() {
					// Diamonds.
					scale([w+clearance, dmnd_width/2, dmnd_height/2]) {
						xrot(45) cube(size=[1,sqrt(2),sqrt(2)], center=true);
					}
					// Blunt point of tab.
					yspread(guide_width+4) {
						cube(size=[(w+clearance)*1.05, 4, h*0.99], center=true);
					}
				}
			}
			if (overlap>0) cube([w+clearance, overlap+0.001, h], center=true);
		}
	}
}



// Module: half_joiner()
// Usage:
//   half_joiner(h, w, l, [a], [screwsize], [guides], [$slop])
// Description:
//   Creates a half_joiner object that can be attached to half_joiner2 object.
// Arguments:
//   h = Height of the half_joiner.
//   w = Width of the half_joiner.
//   l = Length of the backing to the half_joiner.
//   a = Overhang angle of the half_joiner.
//   screwsize = Diameter of screwhole.
//   guides = If true, create sliding alignment guides.
//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#anchor).  Default: `CENTER`
//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#spin).  Default: `0`
//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#orient).  Default: `UP`
//   $slop = Printer specific slop value to make parts fit more closely.
// Example:
//   half_joiner(screwsize=3, spin=-90);
module half_joiner(h=20, w=10, l=10, a=30, screwsize=undef, guides=true, anchor=CENTER, spin=0, orient=UP)
{
	dmnd_height = h*1.0;
	dmnd_width = dmnd_height*tan(a);
	guide_size = w/3;
	guide_width = 2*(dmnd_height/2-guide_size)*tan(a);

	if ($children > 0) {
		difference() {
			children();
			half_joiner_clear(h=h, w=w, a=a, clearance=0.1, overlap=0.01, anchor=anchor, spin=spin, orient=orient);
		}
	}
	render(convexity=12)
	orient_and_anchor([w, 2*l, h], orient, anchor, spin=spin) {
		difference() {
			union() {
				// Make base.
				difference() {
					// Solid backing base.
					fwd(l/2) cube(size=[w, l, h], center=true);

					// Clear diamond for tab
					grid3d(xa=[-(w*2/3), (w*2/3)]) {
						half_joiner_clear(h=h+0.01, w=w, clearance=$slop*2, a=a);
					}
				}

				difference() {
					// Make tab
					scale([w/3-$slop*2, dmnd_width/2, dmnd_height/2]) xrot(45)
						cube(size=[1,sqrt(2),sqrt(2)], center=true);

					// Blunt point of tab.
					back(guide_width/2+2)
						cube(size=[w*0.99,4,guide_size*2], center=true);
				}


				// Guide ridges.
				if (guides == true) {
					xspread(w/3-$slop*2) {
						// Guide ridge.
						fwd(0.05/2) {
							scale([0.75, 1, 2]) yrot(45)
								cube(size=[guide_size/sqrt(2), guide_width+0.05, guide_size/sqrt(2)], center=true);
						}

						// Snap ridge.
						scale([0.25, 0.5, 1]) zrot(45)
							cube(size=[guide_size/sqrt(2), guide_size/sqrt(2), dmnd_width], center=true);
					}
				}
			}

			// Make screwholes, if needed.
			if (screwsize != undef) {
				yrot(90) cylinder(r=screwsize*1.1/2, h=w+1, center=true, $fn=12);
			}
		}
	}
}
//half_joiner(screwsize=3);



// Module: half_joiner2()
// Usage:
//   half_joiner2(h, w, l, [a], [screwsize], [guides])
// Description:
//   Creates a half_joiner2 object that can be attached to half_joiner object.
// Arguments:
//   h = Height of the half_joiner.
//   w = Width of the half_joiner.
//   l = Length of the backing to the half_joiner.
//   a = Overhang angle of the half_joiner.
//   screwsize = Diameter of screwhole.
//   guides = If true, create sliding alignment guides.
//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#anchor).  Default: `CENTER`
//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#spin).  Default: `0`
//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#orient).  Default: `UP`
// Example:
//   half_joiner2(screwsize=3, spin=-90);
module half_joiner2(h=20, w=10, l=10, a=30, screwsize=undef, guides=true, anchor=CENTER, spin=0, orient=UP)
{
	dmnd_height = h*1.0;
	dmnd_width = dmnd_height*tan(a);
	guide_size = w/3;
	guide_width = 2*(dmnd_height/2-guide_size)*tan(a);

	if ($children > 0) {
		difference() {
			children();
			half_joiner_clear(h=h, w=w, a=a, clearance=0.1, overlap=0.01, orient=orient, spin=spin, anchor=anchor);
		}
	}

	render(convexity=12)
	orient_and_anchor([w, 2*l, h], orient, anchor, spin=spin) {
		difference() {
			union () {
				fwd(l/2) cube(size=[w, l, h], center=true);
				cube([w, guide_width, h], center=true);
			}

			// Subtract mated half_joiner.
			zrot(180) half_joiner(h=h+0.01, w=w+0.01, l=guide_width+0.01, a=a, screwsize=undef, guides=guides, $slop=0.0);

			// Make screwholes, if needed.
			if (screwsize != undef) {
				xcyl(r=screwsize*1.1/2, l=w+1, $fn=12);
			}
		}
	}
}



// Section: Full Joiners


// Module: joiner_clear()
// Description:
//   Creates a mask to clear an area so that a joiner can be placed there.
// Usage:
//   joiner_clear(h, w, [a], [clearance], [overlap])
// Arguments:
//   h = Height of the joiner to clear space for.
//   w = Width of the joiner to clear space for.
//   a = Overhang angle of the joiner.
//   clearance = Extra width to clear.
//   overlap = Extra depth to clear.
//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#anchor).  Default: `CENTER`
//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#spin).  Default: `0`
//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#orient).  Default: `UP`
// Example:
//   joiner_clear(spin=-90);
module joiner_clear(h=40, w=10, a=30, clearance=0, overlap=0.01, anchor=CENTER, spin=0, orient=UP)
{
	dmnd_height = h*0.5;
	dmnd_width = dmnd_height*tan(a);
	guide_size = w/3;
	guide_width = 2*(dmnd_height/2-guide_size)*tan(a);

	orient_and_anchor([w, guide_width, h], orient, anchor, spin=spin) {
		union() {
			up(h/4) half_joiner_clear(h=h/2.0-0.01, w=w, a=a, overlap=overlap, clearance=clearance);
			down(h/4) half_joiner_clear(h=h/2.0-0.01, w=w, a=a, overlap=overlap, clearance=-0.01);
		}
	}
}



// Module: joiner()
// Usage:
//   joiner(h, w, l, [a], [screwsize], [guides], [$slop])
// Description:
//   Creates a joiner object that can be attached to another joiner object.
// Arguments:
//   h = Height of the joiner.
//   w = Width of the joiner.
//   l = Length of the backing to the joiner.
//   a = Overhang angle of the joiner.
//   screwsize = Diameter of screwhole.
//   guides = If true, create sliding alignment guides.
//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#anchor).  Default: `CENTER`
//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#spin).  Default: `0`
//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#orient).  Default: `UP`
//   $slop = Printer specific slop value to make parts fit more closely.
// Examples:
//   joiner(screwsize=3, spin=-90);
//   joiner(w=10, l=10, h=40, spin=-90) cuboid([10, 10*2, 40], anchor=RIGHT);
module joiner(h=40, w=10, l=10, a=30, screwsize=undef, guides=true, anchor=CENTER, spin=0, orient=UP)
{
	if ($children > 0) {
		difference() {
			children();
			joiner_clear(h=h, w=w, a=a, clearance=0.1, orient=orient, spin=spin, anchor=anchor);
		}
	}
	orient_and_anchor([w, 2*l, h], orient, anchor, spin=spin) {
		union() {
			up(h/4) half_joiner(h=h/2, w=w, l=l, a=a, screwsize=screwsize, guides=guides);
			down(h/4) half_joiner2(h=h/2, w=w, l=l, a=a, screwsize=screwsize, guides=guides);
		}
	}
}



// Section: Full Joiners Pairs/Sets


// Module: joiner_pair_clear()
// Description:
//   Creates a mask to clear an area so that a pair of joiners can be placed there.
// Usage:
//   joiner_pair_clear(spacing, [n], [h], [w], [a], [clearance], [overlap])
// Arguments:
//   spacing = Spacing between joiner centers.
//   h = Height of the joiner to clear space for.
//   w = Width of the joiner to clear space for.
//   a = Overhang angle of the joiner.
//   n = Number of joiners (2 by default) to clear for.
//   clearance = Extra width to clear.
//   overlap = Extra depth to clear.
//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#anchor).  Default: `CENTER`
//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#spin).  Default: `0`
//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#orient).  Default: `UP`
// Examples:
//   joiner_pair_clear(spacing=50, n=2);
//   joiner_pair_clear(spacing=50, n=3);
module joiner_pair_clear(spacing=100, h=40, w=10, a=30, n=2, clearance=0, overlap=0.01, anchor=CENTER, spin=0, orient=UP)
{
	dmnd_height = h*0.5;
	dmnd_width = dmnd_height*tan(a);
	guide_size = w/3;
	guide_width = 2*(dmnd_height/2-guide_size)*tan(a);

	orient_and_anchor([spacing+w, guide_width, h], orient, anchor, spin=spin) {
		xspread(spacing, n=n) {
			joiner_clear(h=h, w=w, a=a, clearance=clearance, overlap=overlap);
		}
	}
}



// Module: joiner_pair()
// Usage:
//   joiner_pair(h, w, l, [a], [screwsize], [guides], [$slop])
// Description:
//   Creates a joiner_pair object that can be attached to other joiner_pairs .
// Arguments:
//   spacing = Spacing between joiner centers.
//   h = Height of the joiners.
//   w = Width of the joiners.
//   l = Length of the backing to the joiners.
//   a = Overhang angle of the joiners.
//   n = Number of joiners in a row.  Default: 2
//   alternate = If true (default), each joiner alternates it's orientation.  If alternate is "alt", do opposite alternating orientations.
//   screwsize = Diameter of screwhole.
//   guides = If true, create sliding alignment guides.
//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#anchor).  Default: `CENTER`
//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#spin).  Default: `0`
//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#orient).  Default: `UP`
//   $slop = Printer specific slop value to make parts fit more closely.
// Examples:
//   joiner_pair(spacing=50, l=10, spin=-90) cuboid([10, 50+10-0.1, 40], anchor=RIGHT);
//   joiner_pair(spacing=50, l=10, n=2, spin=-90);
//   joiner_pair(spacing=50, l=10, n=3, alternate=false, spin=-90);
//   joiner_pair(spacing=50, l=10, n=3, alternate=true, spin=-90);
//   joiner_pair(spacing=50, l=10, n=3, alternate="alt", spin=-90);
module joiner_pair(spacing=100, h=40, w=10, l=10, a=30, n=2, alternate=true, screwsize=undef, guides=true, anchor=CENTER, spin=0, orient=UP)
{
	if ($children > 0) {
		difference() {
			children();
			joiner_pair_clear(spacing=spacing, h=h, w=w, a=a, clearance=0.1, orient=orient, spin=spin, anchor=anchor);
		}
	}
	orient_and_anchor([spacing+w, 2*l, h], orient, anchor, spin=spin) {
		left((n-1)*spacing/2) {
			for (i=[0:1:n-1]) {
				right(i*spacing) {
					yrot(180 + (alternate? (i*180+(alternate=="alt"?180:0))%360 : 0)) {
						joiner(h=h, w=w, l=l, a=a, screwsize=screwsize, guides=guides);
					}
				}
			}
		}
	}
}



// Section: Full Joiners Quads/Sets


// Module: joiner_quad_clear()
// Description:
//   Creates a mask to clear an area so that a pair of joiners can be placed there.
// Usage:
//   joiner_quad_clear(spacing, [n], [h], [w], [a], [clearance], [overlap])
// Arguments:
//   spacing1 = Spacing between joiner centers.
//   spacing2 = Spacing between back-to-back pairs/sets of joiners.
//   h = Height of the joiner to clear space for.
//   w = Width of the joiner to clear space for.
//   a = Overhang angle of the joiner.
//   n = Number of joiners in a row.  Default: 2
//   clearance = Extra width to clear.
//   overlap = Extra depth to clear.
//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#anchor).  Default: `CENTER`
//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#spin).  Default: `0`
//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#orient).  Default: `UP`
// Examples:
//   joiner_quad_clear(spacing1=50, spacing2=50, n=2);
//   joiner_quad_clear(spacing1=50, spacing2=50, n=3);
module joiner_quad_clear(xspacing=undef, yspacing=undef, spacing1=undef, spacing2=undef, n=2, h=40, w=10, a=30, clearance=0, overlap=0.01, anchor=CENTER, spin=0, orient=UP)
{
	spacing1 = first_defined([spacing1, xspacing, 100]);
	spacing2 = first_defined([spacing2, yspacing, 50]);
	orient_and_anchor([w+spacing1, spacing2, h], orient, anchor, spin=spin) {
		zrot_copies(n=2) {
			back(spacing2/2) {
				joiner_pair_clear(spacing=spacing1, n=n, h=h, w=w, a=a, clearance=clearance, overlap=overlap);
			}
		}
	}
}



// Module: joiner_quad()
// Usage:
//   joiner_quad(h, w, l, [a], [screwsize], [guides], [$slop])
// Description:
//   Creates a joiner_quad object that can be attached to other joiner_pairs .
// Arguments:
//   spacing = Spacing between joiner centers.
//   h = Height of the joiners.
//   w = Width of the joiners.
//   l = Length of the backing to the joiners.
//   a = Overhang angle of the joiners.
//   n = Number of joiners in a row.  Default: 2
//   alternate = If true (default), each joiner alternates it's orientation.  If alternate is "alt", do opposite alternating orientations.
//   screwsize = Diameter of screwhole.
//   guides = If true, create sliding alignment guides.
//   $slop = Printer specific slop value to make parts fit more closely.
//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#anchor).  Default: `CENTER`
//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#spin).  Default: `0`
//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#orient).  Default: `UP`
// Examples:
//   joiner_quad(spacing1=50, spacing2=50, l=10, spin=-90) cuboid([50, 50+10-0.1, 40]);
//   joiner_quad(spacing1=50, spacing2=50, l=10, n=2, spin=-90);
//   joiner_quad(spacing1=50, spacing2=50, l=10, n=3, alternate=false, spin=-90);
//   joiner_quad(spacing1=50, spacing2=50, l=10, n=3, alternate=true, spin=-90);
//   joiner_quad(spacing1=50, spacing2=50, l=10, n=3, alternate="alt", spin=-90);
module joiner_quad(spacing1=undef, spacing2=undef, xspacing=undef, yspacing=undef, h=40, w=10, l=10, a=30, n=2, alternate=true, screwsize=undef, guides=true, anchor=CENTER, spin=0, orient=UP)
{
	spacing1 = first_defined([spacing1, xspacing, 100]);
	spacing2 = first_defined([spacing2, yspacing, 50]);
	if ($children > 0) {
		difference() {
			children();
			joiner_quad_clear(spacing1=spacing1, spacing2=spacing2, h=h, w=w, a=a, clearance=0.1, orient=orient, spin=spin, anchor=anchor);
		}
	}
	orient_and_anchor([w+spacing1, spacing2, h], orient, anchor, spin=spin) {
		zrot_copies(n=2) {
			back(spacing2/2) {
				joiner_pair(spacing=spacing1, n=n, h=h, w=w, l=l, a=a, screwsize=screwsize, guides=guides);
			}
		}
	}
}



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