//
// Anybeam OpenSCAD Library
// 
// This library provides an anybeam() module that can be used to create beams
// with a variaty of hole, axle and slot patterns using a simple declaritive syntax. 
// 
// 
// Beam String
// 
// A beam is speficied with a sequence of characters, each representing a hole
// type in the beam.
// 
//  O  Pin hole
//  X  Axle hole
//  (  Half hole with a slot on the right.
//  )  Half hole with the slot on the left.
//  -  (dash) A full width slot, use with ( and ) to create long slots with half hole ends like "(--)" (4 span slot) or "()" (2 span slot)
//  |  (line) Pin hole at the edge (rotated 90 degree in respect to normal pin hole). Beam will be rotated 90 degree if at least one its end is "|", and none of its ends are "O".
//  #  (hash) Axle hole at the edge.
//  .  (period) Skip this hole.
// 
// Use the above characters to represent the hole layout on the beam:
// 
//   XOOOOX   - Size 6 beam with axle holes at the ends.
//   OOXOO    - Size 5 beam with an axle hole in the middle.
//   (---)OOO - Size 8 beam with a size 5 slot and three holes at the end.
// 
// Between each beam is a connection vector that defines how the beams connect.
// 
//   [ PREVIOUS_BEAM_HOLE, CURRENT_BEAM_HOLE, ANGLE ]
// 
//  * Holes are numbered from 1 to N (the length of the beam) from left to right.
//  * Angles are in degrees.
// 
// Here is a standard 4x2 90 degree lift arm:
//  
//   [ "XOOO", [ 4, 1, 90 ], "OO" ]
// 
// The connection hole specifier may includ a fractional part.
// 
// 
// Fractional Hole Spacing
//
// Here is a 4x2 beam with the size 2 beam in the midde of the size 4 beam.
// The space prevents a hole from appearing at the start of the beam where
// the two overlap.
//
// Connecting hole 2.5 of the size 4 beam to hole 1 of the size 2 beam at 90 degrees.
//
//   [ "OOOO", [ 2.5, 1, 90 ], ".O" ]
//
//
// Examples:


// Constants.
AB_HOLE_SPACING = 8.0;
AB_HOLE_INSIDE_DIAMETER = 5.4;
AB_STUD_DIAMETER = 4.8;
AB_HOLE_RING_DIAMETER = 6.28;
AB_HOLE_RING_DEPTH = 0.9;
AB_BEAM_WIDTH = 7.8; // originally 7.6, restore if you want slightly thinner beams like standard ones
AB_AXLE_GAP = 1.95;
AB_AXLE_LENGTH = 5.1;

AB_BEAM_HEIGHT = 7.8;
AB_THIN_BEAM_HEIGHT = AB_BEAM_HEIGHT/3;

// From roipoussiere's string functions - https://www.thingiverse.com/thing:202724
function ab_fill(car, nb_occ, out="") = (nb_occ == 0) ? out : str(ab_fill(car, nb_occ-1, out), car);

//anybeam( [ "XOOO", [ 4, 1, 53.13 ], "OOO()", [ 5, 1, -53.13 ], "(-)X" ], 1 );

//anybeam( [ "X##X", [ 4, 1, 90 ], "X|OO|X", [ 6, 1, 90 ], "X##X", [ 4, 1, 90 ], "X|OO|X" ], 1);

// Standard small frame
//anybeam( [ "O|||O", [ 5, 1, 90 ], "O|O|O|O", [ 7, 1, 90 ], "O|||O", [ 5, 1, 90 ], "O|O|O|O" ], 1);

// Standard big frame
//anybeam( [ "O|||O", [ 5, 3, 90 ], "||O|O|O|O||", [ 9, 1, 90 ], "O|||O", [ 5, 3, 90 ], "||O|O|O|O||" ], 1);

//anybeam( [ "|O|O|", [ 5, 1, 90 ], "_|O|O|O|_", [ 9, 1, 90 ], "|O|O|", [ 5, 1, 90 ], "_|O|O|O|_" ], 1);

//anybeam_tee(stem="|O|", top="_O|");

module anybeam( beams = [], height = 1 ) {
  translate([0, 0, height/2]) {
    difference() {
      ab_beams( beams, height * AB_BEAM_HEIGHT );
      ab_holes( beams, height * AB_BEAM_HEIGHT );
    }
  }
}

module anybeam_straight( holes = 10, height = 1 ) {
  if( len(holes )) {
    anybeam( [ holes ], height);    
  }
  else {
    anybeam( [ ab_fill("O", holes ) ], height);    
  }
}

module anybeam_tee( stem = "OOO", top = "OOO", height = 1 ) {
  anybeam( [ stem, [ 2, 1, 90 ], top ], height );
}

module anybeam_143( left = "XOOO", right = "OOOOOX", height = 1 ) {
  anybeam( [ left, [ len(left), 1, 53.13 ], right ], height );
}

module anybeam_90(left = "XOOO", right = "OO", height = 1) {
  anybeam( [ left, [ len(left), 1, 90 ], right ], height );
}

module anybeam_135x2(left = "XOOOOOO", middle = " () ", right = "OOX", height = 1) {
  anybeam( [ left, [ len(left), 1, 45 ], middle, [ len(middle), 1, 45], right ], height );
}

//
// Test beam that uses all features.
//
module anybeam_test(left = "XOOOOOO", middle = "O(-) ", right = 3, height = 1) {
  anybeam( [ left, [ len(left), 3, 45 ], middle, [ len(middle), 1, 45], right ], height );
}

//
// Support Modules
//

//
// Layout beam holes. 
// 
module ab_holes( beams = [], height = AB_BEAM_HEIGHT, b = 0 ) {
  beam = beams[b];
  connection = beams[b-1];
  next_beam = beams[b+2];

  if( connection ) {
    if( next_beam ) {
    translate( [ (connection[0]-1)*AB_HOLE_SPACING, 0,  0 ] )
     rotate([0, 0, connection[2]])
      translate( [ -(connection[1]-1)*AB_HOLE_SPACING, 0, 0 ] )
        ab_beam_holes( beam, height )
          ab_holes( beams, height, b+2 );          
    }
    else {
      translate( [ (connection[0]-1)*AB_HOLE_SPACING, 0, 0 ] )
       rotate([0, 0, connection[2]])
        translate( [ -(connection[1]-1)*AB_HOLE_SPACING, 0, 0 ] )
          ab_beam_holes( beam, height );
    }
  }
  else {
    if( next_beam ) {
      ab_beam_holes( beam, height )
        ab_holes( beams, height, b+2 );            
    }
    else {
      ab_beam_holes( beam, height );              
    }
  }
}

//
// Layout solid beams.
//
module ab_beams( beams = [], height = AB_BEAM_HEIGHT, b = 0 ) {

  beam = beams[b];
  connection = beams[b-1];
  next_beam = beams[b+2];

  if( connection ) {
    if( next_beam ) {
    translate( [ (connection[0]-1)*AB_HOLE_SPACING, 0, 0 ] )
     rotate([0, 0, connection[2]])
      translate( [ -(connection[1]-1)*AB_HOLE_SPACING, 0, 0 ] )
        ab_solid_beam( beam, height )
          ab_beams( beams, height, b+2 );          
    }
    else {
      translate( [ (connection[0]-1)*AB_HOLE_SPACING, 0, 0 ] )
       rotate([0, 0, connection[2]])
        translate( [ -(connection[1]-1)*AB_HOLE_SPACING, 0, 0 ] )
          ab_solid_beam( beam, height );
    }
  }
  else {
    if( next_beam ) {
      ab_solid_beam( beam, height )
        ab_beams( beams, height, b+2 );            
    }
    else {
      ab_solid_beam( beam, height );              
    }
  }
}


//
// A single solid beam.
//
module ab_solid_beam( beam = "OOOO", beam_height = AB_BEAM_HEIGHT ) {
  holes = len(beam) ? len(beam)-1 : beam-1;
  beam_length = holes*AB_HOLE_SPACING;
  rot = (beam[0] == "|" && beam[len(beam)-1] != "O" || beam[0] != "O" && beam[len(beam)-1] == "|" ? 90 : 0);
  union() {
    rotate([ rot, 0, 0 ])
    translate( [beam_length/2,0,0] ) {
      cube( [ beam_length, AB_BEAM_WIDTH, beam_height ], center=true );

      translate( [-beam_length/2, 0, 0] )
        cylinder( r = AB_BEAM_WIDTH/2, beam_height, center=true, $fn=100 );
      translate( [beam_length/2, 0, 0] )
        cylinder( r = AB_BEAM_WIDTH/2, beam_height, center=true, $fn=100 );
      }

    if( $children ) child(0);
  }
}

module ab_beam_holes( beam = "OOOO", beam_height = AB_BEAM_HEIGHT ) {
  holes = len(beam)-1;
  beam_length = holes*AB_HOLE_SPACING;
  layout = beam;

  for (hole = [0:1:holes]) {
    translate( [hole*AB_HOLE_SPACING,0,0] ) {
      if( layout == "" ) {
        ab_hole_pin( beam_height );
      }
      else {
        if( layout[hole] == "O" ) {
          ab_hole_pin( beam_height );              
        } 
        if( layout[hole] == "|" ) {
          rotate([-90, 0, 0]) ab_hole_pin( beam_height );
        }
        if( layout[hole] == "(" ) {
          ab_hole_left_slot( beam_height );              
        } 
        if( layout[hole] == ")" ) {
          ab_hole_right_slot( beam_height );              
        } 
        if( layout[hole] == "-" ) {
          ab_hole_slot( beam_height );
        }
        if( layout[hole] == "X" ) {
          ab_hole_axle( beam_height, first = (hole==0), last = (hole==holes) );
        } 
        if( layout[hole] == "#" ) {
          rotate([-90, 0, 0]) ab_hole_axle( beam_height, first = (hole==0), last = (hole==holes) );
        }
        // Any other letter is a space.
      }
    }
  }
  if( $children ) child(0);
}

module ab_hole_pin( beam_height = AB_BEAM_HEIGHT ) {
  cylinder(beam_height+2, AB_HOLE_INSIDE_DIAMETER/2, AB_HOLE_INSIDE_DIAMETER/2, center = true, $fn=100);

  translate([0,0,beam_height/2-AB_HOLE_RING_DEPTH/2+.5])
    cylinder(AB_HOLE_RING_DEPTH+1, AB_HOLE_RING_DIAMETER/2, AB_HOLE_RING_DIAMETER/2, center = true, $fn=100);     

  translate([0,0,-(beam_height/2-AB_HOLE_RING_DEPTH/2+.5)])
    cylinder(AB_HOLE_RING_DEPTH+1, AB_HOLE_RING_DIAMETER/2, AB_HOLE_RING_DIAMETER/2, center = true, $fn=100);     
}

module ab_hole_left_slot( beam_height = AB_BEAM_HEIGHT ) {
  ab_hole_pin(beam_height);

  translate([AB_HOLE_SPACING/4, 0, 0]) {
    cube([AB_HOLE_SPACING/2+.05,AB_HOLE_INSIDE_DIAMETER,beam_height+2], center = true);

    translate([0,0,beam_height/2-AB_HOLE_RING_DEPTH/2+.5])
      cube([AB_HOLE_SPACING/2+0.05, AB_HOLE_RING_DIAMETER, AB_HOLE_RING_DEPTH+1,], center = true);     

    translate([0,0,-(beam_height/2-AB_HOLE_RING_DEPTH/2+.5)])
      cube([AB_HOLE_SPACING/2+0.05, AB_HOLE_RING_DIAMETER, AB_HOLE_RING_DEPTH+1], center = true);     
  }  
}

module ab_hole_right_slot( beam_height = AB_BEAM_HEIGHT ) {
  ab_hole_pin(beam_height);

  translate([-AB_HOLE_SPACING/4, 0, 0]) {
    cube([AB_HOLE_SPACING/2+.05,AB_HOLE_INSIDE_DIAMETER,beam_height+2], center = true);

    translate([0,0,beam_height/2-AB_HOLE_RING_DEPTH/2+.5])
      cube([AB_HOLE_SPACING/2+0.05, AB_HOLE_RING_DIAMETER, AB_HOLE_RING_DEPTH+1,], center = true);     

    translate([0,0,-(beam_height/2-AB_HOLE_RING_DEPTH/2+.5)])
      cube([AB_HOLE_SPACING/2+0.05, AB_HOLE_RING_DIAMETER, AB_HOLE_RING_DEPTH+1], center = true);     
  }  
}

module ab_hole_slot( beam_height = AB_BEAM_HEIGHT ) {
  cube([AB_HOLE_SPACING,AB_HOLE_INSIDE_DIAMETER,beam_height+2], center = true);

  translate([0,0,beam_height/2-AB_HOLE_RING_DEPTH/2+.5])
    cube([AB_HOLE_SPACING, AB_HOLE_RING_DIAMETER, AB_HOLE_RING_DEPTH+1,], center = true);     

  translate([0,0,-(beam_height/2-AB_HOLE_RING_DEPTH/2+.5)])
    cube([AB_HOLE_SPACING, AB_HOLE_RING_DIAMETER, AB_HOLE_RING_DEPTH+1], center = true);     
}

module ab_hole_axle( beam_height = AB_BEAM_HEIGHT , first = false, last = false) {
  if( first == true ) {
    cube([AB_AXLE_GAP,AB_AXLE_LENGTH,beam_height+2], center = true);
      translate([+.5,0,0])
      cube([AB_AXLE_LENGTH+1,AB_AXLE_GAP,beam_height+2], center = true);    
  }
  if( last == true ) {
    cube([AB_AXLE_GAP,AB_AXLE_LENGTH,beam_height+2], center = true);
      translate([-.5,0,0])
      cube([AB_AXLE_LENGTH+1,AB_AXLE_GAP,beam_height+2], center = true);    
  }
  if( first == false && last == false ) {
    cube([AB_AXLE_GAP,AB_AXLE_LENGTH,beam_height+2], center = true);
      cube([AB_AXLE_LENGTH,AB_AXLE_GAP,beam_height+2], center = true);    
  }
}

