1//////////////////////////////////////////////////////////////////////
  2// LibFile: hinges.scad
  3//   Functions and modules for creating hinges and snap-locking hinged parts. 
  4// Includes:
  5//   include <BOSL2/std.scad>
  6//   include <BOSL2/hinges.scad>
  7// FileGroup: Parts
  8// FileSummary: Hinges and snap-locking hinged parts.  
  9//////////////////////////////////////////////////////////////////////
 10
 11include <rounding.scad>
 12include <screws.scad>
 13
 14// Section: Hinges
 15
 16// Module: knuckle_hinge()
 17// Synopsis: Creates a knuckle-hinge shape.
 18// SynTags: Geom
 19// Topics: Hinges, Parts
 20// See Also: living_hinge_mask(), snap_lock(), snap_socket()
 21// Usage:
 22//   knuckle_hinge(length, offset, segs, [inner], [arm_height=], [arm_angle=], [fill=], [clear_top=], [gap=], [round_top=], [round_bot=], [knuckle_diam=], [pin_diam=], [pin_fn=], [anchor=], [spin=], [orient=]) [ATTACHMENTS];
 23// Description:
 24//   Construct standard knuckle hinge in two parts using a hinge pin that must be separately supplied.
 25//   The default is configured to use a piece of 1.75 mm filament as the hinge pin, but you can select
 26//   any dimensions you like to use a screw or other available pin material.  The BOTTOM of the hinge
 27//   is its mount point, which is aligned with the hinge pin centersurface, and the hinge pin hole is
 28//   the CENTER of the hinge.  The offset is the distance from a vertical mounting point to the center
 29//   of the hinge pin.  The hinge barrel is held by an angled support and vertical support.  The
 30//   length of the angled support is determined by its angle and the offset.  You specify the length
 31//   of the vertical support with the arm_height parameter.
 32//   .
 33//   A hinge requires clearance so its parts don't interfere.  If the hinge pin is exactly centered on
 34//   the top of your part, then the hinge may not close all the way due to interference at the edge.
 35//   A small clearance, specified with `clearance=`, raises the hinge up and can ease this
 36//   interference.  It should probably be equal to a layer thickness or two.  If the hinge knuckle is
 37//   close to the hinged part then the mating part may interfere.  You can create clearance to address
 38//   this problem by increasing the offset to move the hinge knuckles farther away.  Another method is
 39//   to cut out a curved recess on the parts to allow space for the other hinges.  This is possible
 40//   using the `knuckle_clearance=` parameter, which specifies the extra space to cut away to leave
 41//   room for the hinge knuckles.  It must be positive for any space to be cut, and to use this option
 42//   you must make the hinge a child of some object and specify {{diff()}} for the parent object of
 43//   the hinge.
 44// Figure(2D,Med,NoScales):  The basic hinge form appears on the left.  If fill is set to true the gap between the mount surface and hinge arm is filled as shown on the right. 
 45//   _knuckle_hinge_profile(4, 5, $fn=32, fill=false);
 46//   right(13)_knuckle_hinge_profile(4, 5, $fn=32, fill=true);
 47//   fwd(9)stroke([[0,0],[4,4],[4,9]], width=.3,color="black");
 48//   stroke([[5,-5],[5,0]], endcaps="arrow2", color="blue",width=.15);
 49//   color("blue"){move([6.2,-2.5])text("arm_height",size=.75,valign="center");
 50//      stroke(arc(r=3, cp=[0,-9], angle=[47,90],$fn=64),width=.15,endcaps="arrow2");
 51//      move([-.5,-6])text("arm_angle", size=0.75,halign="right");
 52//      move([14,-4])text("fill=true", size=1);
 53//   }
 54// Continues:
 55//   As shown in the above figure, the fill option fills the gap between the hinge arm and the mount surface to make a stronger connection.  When the
 56//   arm height is set to zero, only a single segment connects the hinge barrel to the mount surface.  
 57// Figure(2D,Med,NoScales): Zero arm height with 45 deg arm
 58//   right(10)   _knuckle_hinge_profile(4, 0, $fn=32);
 59//   _knuckle_hinge_profile(4, 0, $fn=32,fill=false);
 60//   right(11)fwd(-3)color("blue")text("fill=true",size=1);
 61//   right(.5)fwd(-3)color("blue")text("fill=false",size=1);
 62// Continues:
 63// Figure(2D,Med,NoScales): Zero arm height with 90 deg arm.  The clear_top parameter removes the hinge support material that is above the x axis
 64//   _knuckle_hinge_profile(4, 0, 90, $fn=32);
 65//   right(10)  _knuckle_hinge_profile(4, 0, 90, $fn=32,clear_top=true);
 66//   right(9.5)fwd(-3)color("blue")text("clear_top=true",size=.76);
 67//   right(.5)fwd(-3)color("blue")text("clear_top=false",size=.76);
 68// Figure(2D,Med,NoScales):  An excessively large clearance value raises up the hinge center.  Note that the hinge mounting remains bounded by the X axis, so when `fill=true` or `clear_top=true` this is different than simply raising up the entire hinge.  
 69//   right(10)  _knuckle_hinge_profile(4, 0, 90, $fn=32,clear_top=true,clearance=.5);
 70//   _knuckle_hinge_profile(4, 0, $fn=32,fill=true,clearance=.5);
 71// Continues:
 72//   For 3D printability, you may prefer a teardrop shaped hole, which you can get with `teardrop=true`; 
 73//   if necessary you can specify the teardrop direction to be UP, DOWN, FORWARD, or BACK.
 74//   (These directions assume that the base of the hinge is mounted on the back of something.)
 75//   Another option for printability is to use an octagonal hole, though it does seem more
 76//   difficult to size these for robust printability.  To get an octagonal hole set `pin_fn=8`.
 77// Figure(2D,Med,NoScales): Alternate hole shapes for improved 3D printabililty
 78//   right(10)   _knuckle_hinge_profile(4, 0, $fn=32,pin_fn=8);
 79//   _knuckle_hinge_profile(4, 0, $fn=32,tearspin=0);
 80//   right(11)fwd(-3)color("blue")text("octagonal",size=1);
 81//   right(1.5)fwd(-3)color("blue")text("teardrop",size=1);
 82// Continues:
 83//   The default pin hole size admits a piece of 1.75 mm filament.  If you prefer to use a machine
 84//   screw you can set the pin_diam to a screw specification like `"M3"` or "#6".  In this case,
 85//   a clearance hole is created through most of the hinge with a self-tap hole for the last segment.
 86//   If the last segment is very long you may shrink the self-tap portion using the tap_depth parameter.
 87//   The pin hole diameter is enlarged by the `2*$slop` for numerically specified holes.
 88//   Screw holes are made using {{screw_hole()}} which enlarges the hole by `4*$slop`.  
 89//   .
 90//   To blend hinges better with a model you can round off the joint with the mounting surface using
 91//   the `round_top` and `round_bot` parameters, which specify the cut distance, the amount of material to add.
 92//   They make a continuous curvature "smooth" roundover with `k=0.8`.  See [smooth roundovers](rounding.scad#section-types-of-roundovers) for more
 93//   information.  If you specify too large of a roundover you will get an error that the rounding doesn't fit.  
 94// Figure(2D,Med,NoScales): Top and bottom roundovers for smooth hinge attachment
 95//   right(12)_knuckle_hinge_profile(6, 0, $fn=32,fill=false,round_top=1.5);
 96//   _knuckle_hinge_profile(4, 0, $fn=32,fill=false,round_bot=1.5);
 97//   right(12)fwd(11)color("blue")text("round_top=1.8",size=1);
 98//   right(.5)fwd(-3)color("blue")text("round_bot=1.5",size=1);
 99// Arguments:
100//   length = total length of the entire hinge
101//   offset = horizontal offset of the hinge pin center from the mount point
102//   segs = number of hinge segments
103//   inner = set to true for the "inner" hinge.  Default: false
104//   ---
105//   arm_height = vertical height of the arm that holds the hinge barrel.  Default: 0
106//   arm_angle = angle of the arm down from the vertical.  Default: 45
107//   fill = if true fill in space between arm and mount surface.  Default: true
108//   clear_top = if true remove any excess arm geometry that appears above the top of the mount surface.  Default: false
109//   gap = gap between hinge segments.  Default: 0.2
110//   round_top = rounding amount to add where top of hinge arm joins the mount surface.  Generally only useful when fill=false.  Default: 0
111//   round_bot = rounding amount to add where bottom of hinge arm joins the mount surface.  Default: 0
112//   knuckle_diam = diameter of hinge barrel.  Default: 4
113//   pin_diam = diameter of hinge pin hole as a number of screw specification.  Default: 1.75
114//   pin_fn = $fn value to use for the pin.
115//   teardrop = Set to true or UP/DOWN/FWD/BACK to specify teardrop shape for the pin hole.  Default: false
116//   screw_head = screw head to use for countersink
117//   screw_tolerance = screw hole tolerance.  Default: "close"
118//   tap_depth = Don't make the tapped part of the screw hole larger than this. 
119//   $slop = increases pin hole diameter
120//   clearance = raises pin hole to create clearance at the edge of the mounted surface.  Default: 0.15
121//   clear_knuckle = clear space for hinge knuckle of mating part.  Must use with {{diff()}}.  Default: 0 
122//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#subsection-anchor).  Default: `BOTTOM`
123//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#subsection-spin).  Default: `0`
124//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#subsection-orient).  Default: `UP`
125// Example: Basic hinge, inner=false in front and inner=true in the back
126//   $fn=32;
127//   ydistribute(30){
128//     knuckle_hinge(length=35, segs=5, offset=3, arm_height=1);
129//     knuckle_hinge(length=35, segs=5, offset=3, arm_height=1,inner=true);
130//   }
131// Example(NoScales):  Basic hinge, mounted.  Odd segment count means the "outside" hinge is on the outside at both ends.  
132//   $fn=32;
133//   cuboid([2,40,15])
134//     position(TOP+RIGHT) orient(anchor=RIGHT)
135//       knuckle_hinge(length=35, segs=9, offset=3, arm_height=1);
136// Example(NoScales):  Corresponding inner hinge to go with previous example.  Note that the total number of hinge segments adds to the 9 specified.  
137//   $fn=32;
138//   cuboid([2,40,15])
139//     position(TOP+RIGHT) orient(anchor=RIGHT)
140//       knuckle_hinge(length=35, segs=9, offset=3, arm_height=1, inner=true);
141// Example(NoScales):  This example shows how to position and orient the hinge onto the front of an object instead of the right side. 
142//   $fn=32;
143//   cuboid([40,2,15])
144//     position(TOP+FRONT) orient(anchor=FWD)
145//       knuckle_hinge(length=35, segs=9, offset=3, arm_height=1);
146// Example(NoScales):  Hinge with round_bot set to create a smooth transition, but octagonal hinge pin holes for printing
147//   $fn=32;
148//   cuboid([2,40,15])
149//     position(TOP+RIGHT) orient(anchor=RIGHT)
150//       knuckle_hinge(length=35, segs=9, offset=3, arm_height=1,
151//             round_bot=1, pin_fn=8);
152// Example(NoScales):  Hinge with no vertical arm, just angled arm
153//   $fn=32;
154//   cuboid([2,40,15])
155//     position(TOP+RIGHT) orient(anchor=RIGHT)
156//       knuckle_hinge(length=35, segs=9, offset=3, pin_fn=8);
157// Example(NoScales): Setting the arm_angle to a large value like 90 produces a hinge that doesn't look great
158//   $fn=32;
159//   cuboid([2,40,15])
160//     position(TOP+RIGHT) orient(anchor=RIGHT)
161//       knuckle_hinge(length=35, segs=9, offset=3, arm_angle=90,
162//             arm_height=0, pin_fn=8);
163// Example(NoScales): The above hinge is improved with clear_top, which allows nice attachment to a shape half the thickness of the hinge barrel
164//   $fn=32;
165//   cuboid([20,40,2])
166//     position(TOP+RIGHT) orient(anchor=RIGHT)
167//       knuckle_hinge(length=35, segs=9, offset=3, arm_height=0,
168//             arm_angle=90, pin_fn=8, clear_top=true);
169// Example(NoScales): Uneven hinge using seg_ratio.  Here the inner hinge segments are 1/3 the outer, a rather extreme difference.  Note also that it's a little simpler to mount the inner hinge on the LEFT side of the top section to interface with the hinge mounted on the RIGHT. 
170//   $fn=32;
171//   cuboid([2,40,15]){
172//     position(TOP+RIGHT) orient(anchor=RIGHT)
173//       knuckle_hinge(length=35, segs=9, offset=3, arm_height=1,
174//             seg_ratio=1/3);
175//     attach(TOP,TOP) color("green")
176//       cuboid([2,40,15],anchor=TOP)
177//         position(TOP+LEFT) orient(anchor=LEFT)
178//           knuckle_hinge(length=35, segs=9, offset=3, arm_height=1,
179//                 seg_ratio=1/3, inner=true);
180//    }
181// Example(NoScales): A single hinge with an even number of segments will probably look strange, but they work together neatly in a pair.  This example also shows that the arm_height can change between the inner and outer hinge parts and they will still interface properly.
182//   $fn=32;
183//   cuboid([2,40,15]){
184//     yflip_copy()
185//       position(TOP+RIGHT+FRONT) orient(anchor=RIGHT)
186//         knuckle_hinge(length=12, segs=2, offset=2, arm_height=2,
187//               anchor=BOT+LEFT);
188//     attach(TOP,TOP) color("green")
189//       cuboid([2,40,15],anchor=TOP)
190//         yflip_copy()
191//           position(TOP+LEFT+FRONT) orient(anchor=LEFT)
192//             knuckle_hinge(length=12, segs=2, offset=2, arm_height=0,
193//                   inner=true, anchor=BOT+RIGHT);
194//    }
195// Example(NoScales): Hinge with self-tapping screw hole.  Note that last segment has smaller diameter for screw to bite, whereas other segments have clearance holes. 
196//   $fn=32;
197//   bottom_half(z=.01)
198//     cuboid([2,40,15],anchor=TOP)
199//       position(TOP+RIGHT) orient(anchor=RIGHT)
200//         knuckle_hinge(length=35, segs=5, offset=5, knuckle_diam=9, pin_diam="#6", fill=false,inner=false, screw_head="flat");
201// Example(NoScales): If you give a non-flat screw head then a counterbore for that head is generated.  If you don't want the counterbore, don't give a head type.  In this example, tap_depth limits the narrower self-tap section of the hole.  
202//   $fn=32;
203//   bottom_half(z=.01)
204//      cuboid([2,40,15],anchor=TOP)
205//        position(TOP+RIGHT) orient(anchor=RIGHT)
206//           knuckle_hinge(length=35, segs=3, offset=5, knuckle_diam=9, pin_diam="#6",
207//                 fill=false, inner=false, tap_depth=6, screw_head="socket");
208// Example(NoScales): This hinge has a small offset, so the hinged parts may interfere.  To prevent this, use `knuckle_clearance`.  This example shows an excessive clearance value to make the effect obvious.  Note that you **must** use {{diff()}} when you set `knuckle_clearance`, and the hinge must be a child of the object it mounts to.  Otherwise the cylinders that are supposed to be subtracted will appear as extra objects.  This is an inner hinge, so it has clearance zones for the larger outer hinge that will mate with it.  
209//   $fn=32;
210//   diff()
211//     cuboid([4,40,15])
212//       position(TOP+RIGHT) orient(anchor=RIGHT)
213//         knuckle_hinge(length=35, segs=5, offset=2, inner=true, knuckle_clearance=1);
214// Example(NoScales): Oh no! Forgot to use {{diff()}} with knuckle_clearance!
215//   $fn=32;
216//     cuboid([4,40,15])
217//       position(TOP+RIGHT) orient(anchor=RIGHT)
218//         knuckle_hinge(length=35, segs=5, offset=2, inner=true, knuckle_clearance=1);
219
220function knuckle_hinge(length, segs, offset, inner=false, arm_height=0, arm_angle=45, gap=0.2,
221             seg_ratio=1, knuckle_diam=4, pin_diam=1.75, fill=true, clear_top=false,
222             round_bot=0, round_top=0, pin_fn, clearance,
223             tap_depth, screw_head, screw_tolerance="close", 
224             anchor=BOT,orient,spin) = no_function("hinge");
225
226module knuckle_hinge(length, segs, offset, inner=false, arm_height=0, arm_angle=45, gap=0.2,
227             seg_ratio=1, knuckle_diam=4, pin_diam=1.75, fill=true, clear_top=false,
228             round_bot=0, round_top=0, pin_fn, clearance=0, teardrop,
229             tap_depth, screw_head, screw_tolerance="close", knuckle_clearance, 
230             anchor=BOT,orient,spin)
231{
232  dummy =
233    assert(is_str(pin_diam) || all_positive([pin_diam]), "pin_diam must be a screw spec string or a positive number")
234    assert(all_positive(length), "length must be a postive number")
235    assert(is_int(segs) && segs>=2, "segs must be an integer 2 or greater")
236    assert(is_finite(offset) && offset>=knuckle_diam/2, "offset must be a valid number that is not smaller than radius of the hinge knuckle")
237    assert(is_finite(arm_angle) && arm_angle>0 && arm_angle<=90, "arm_angle must be greater than zero and less than or equal to 90");
238  segs1 = ceil(segs/2);
239  segs2 = floor(segs/2);
240  seglen1 = gap + (length-(segs-1)*gap) / (segs1 + segs2*seg_ratio);
241  seglen2 = gap + (length-(segs-1)*gap) / (segs1 + segs2*seg_ratio) * seg_ratio;
242  z_adjust = segs%2==1 ? 0
243           : inner? seglen1/2
244           : seglen2/2;
245  tearspin = is_undef(teardrop) || teardrop==false ? undef
246           : teardrop==UP || teardrop==true ? 0
247           : teardrop==DOWN ? 180
248           : teardrop==BACK ? 270
249           : teardrop==FWD ? 90
250           : assert(false, "Illegal value for teardrop");
251  knuckle_segs = segs(knuckle_diam);
252  transform = down(offset)*yrot(-90)*zmove(z_adjust);
253
254  if(knuckle_clearance){
255    knuckle_clearance_diam = knuckle_diam / cos(180/knuckle_segs) + 2*knuckle_clearance;
256    tag("remove")
257      attachable(anchor,spin,orient,
258                 size=[length,
259                       arm_height+offset/tan(arm_angle)+knuckle_diam/2+knuckle_diam/2/sin(arm_angle),
260                       offset+knuckle_diam/2],
261                 offset=[0,
262                         -arm_height/2-offset/tan(arm_angle)/2-knuckle_diam/sin(arm_angle)/4+knuckle_diam/4,
263                         -offset/2+knuckle_diam/4]
264      )
265      {
266        multmatrix(transform) down(segs%2==1? 0 : (seglen1+seglen2)/2){
267          move([offset,clearance])
268            intersection(){
269              n = inner && segs%2==1 ? segs1
270                
271                : inner ? segs1
272                : segs2;
273              zcopies(n=n, spacing=seglen1+seglen2)
274                 cyl(h=(inner?seglen1:seglen2)+gap-.01, d=knuckle_clearance_diam, circum=true, $fn=knuckle_segs, realign=true);
275              //cyl(h=length+2*gap, d=knuckle_clearance_diam, circum=true, $fn=knuckle_segs, realign=true);
276            }
277        }
278        union(){}
279    }
280  }
281  attachable(anchor,spin,orient,
282             size=[length,
283                   arm_height+offset/tan(arm_angle)+knuckle_diam/2+knuckle_diam/2/sin(arm_angle),
284                   offset+knuckle_diam/2],
285             offset=[0,
286                     -arm_height/2-offset/tan(arm_angle)/2-knuckle_diam/sin(arm_angle)/4+knuckle_diam/4,
287                     -offset/2+knuckle_diam/4]
288  )
289  {
290    multmatrix(transform)
291      force_tag() difference() {
292        zcopies(n=inner?segs2:segs1, spacing=seglen1+seglen2)
293          linear_extrude((inner?seglen2:seglen1)-gap,center=true)
294            _knuckle_hinge_profile(offset=offset, arm_height=arm_height, arm_angle=arm_angle, knuckle_diam=knuckle_diam, pin_diam=pin_diam,
295                                   fill=fill, clear_top=clear_top, round_bot=round_bot, round_top=round_top, pin_fn=pin_fn,clearance=clearance,tearspin=tearspin);
296        if (is_str(pin_diam)) back(clearance)right(offset) up(length/2-(inner?1:1)*z_adjust) zrot(default(tearspin,0)){
297          $fn = default(pin_fn,$fn);
298          tap_depth = min(segs%2==1?seglen1-gap/2:seglen2-gap/2, default(tap_depth, length));
299          screw_hole(pin_diam, length=length+.01, tolerance="self tap", bevel=false, anchor=TOP, teardrop=is_def(tearspin));
300          multmatrix(inner ? zflip(z=-length/2) : IDENT)
301            if (is_undef(screw_head) || screw_head=="none" || starts_with(screw_head,"flat"))
302              screw_hole(pin_diam, length=length-tap_depth, tolerance=screw_tolerance, bevel=false, anchor=TOP, head=screw_head, teardrop=is_def(tearspin));
303            else {
304              screw_hole(pin_diam, length=length-tap_depth, tolerance=screw_tolerance, bevel=false, anchor=TOP, teardrop=is_def(tearspin));
305              screw_hole(pin_diam, length=.01, tolerance=screw_tolerance, bevel=false, anchor=TOP, head=screw_head, teardrop=is_def(tearspin));
306            }
307        }
308      }
309    children();
310  }    
311}  
312
313
314module _knuckle_hinge_profile(offset, arm_height, arm_angle=45, knuckle_diam=4, pin_diam=1.75, fill=true, clear_top=false, round_bot=0, round_top=0, pin_fn, clearance=0, tearspin)
315{
316  extra = .01;
317  skel = turtle(["left", 90-arm_angle, "untilx", offset+extra, "left", arm_angle,
318                 if (arm_height>0) each ["move", arm_height]]);
319  ofs = arm_height+offset/tan(arm_angle);
320  start=round_bot==0 && round_top==0 ? os_flat(abs_angle=90)
321                                     : os_round(abs_angle=90, cut=[-round_top,-round_bot],k=.8);
322  f=echo(clearance=clearance);
323  back(clearance)
324  difference(){
325    union(){
326      difference(){
327        fwd(ofs){
328          left(extra)offset_stroke(skel, width=knuckle_diam, start=start);
329          if (fill) polygon([each list_head(skel,-2), fwd(clearance,last(skel)), [-extra,ofs-clearance]]);
330        }
331        if (clear_top==true || clear_top=="all") left(.1)fwd(clearance) rect([offset+knuckle_diam,knuckle_diam+1+clearance],anchor=BOT+LEFT);
332        if (is_num(clear_top)) left(.1)fwd(clearance) rect([.1+clear_top, knuckle_diam+1+clearance], anchor=BOT+LEFT);
333      }
334      right(offset)ellipse(d=knuckle_diam,realign=true,circum=true);
335    }
336    if (is_num(pin_diam) && pin_diam>0){
337      $fn = default(pin_fn,$fn);
338      right(offset)
339        if (is_def(tearspin)){
340          teardrop2d(d=pin_diam+2*get_slop(), realign=true, circum=true, spin=tearspin);
341        }
342        else ellipse(d=pin_diam+2*get_slop(), realign=true, circum=true);
343    }
344  }
345} 
346
347
348// Module: living_hinge_mask()
349// Synopsis: Creates a mask to make a folding "living" hinge.
350// SynTags: Geom
351// Topics: Hinges, Parts
352// See Also: knuckle_hinge(), living_hinge_mask(), snap_lock(), snap_socket(), apply_folding_hinges_and_snaps()
353// Usage:
354//   living_hinge_mask(l, thick, [layerheight=], [foldangle=], [hingegap=], [$slop=], [anchor=], [spin=], [orient=]) [ATTACHMENTS];
355// Description:
356//   Creates a mask to be differenced away from a plate to create a "live" hinge, where a thin layer of plastic holds two parts together.  
357//   Center the mask at the bottom of the part you want to make a hinge in.
358//   The mask will leave  hinge material `2*layerheight` thick on the bottom of the hinge.
359// Arguments:
360//   l = Length of the hinge in mm.
361//   thick = Thickness in mm of the material to make the hinge in.
362//   ---
363//   layerheight = The expected printing layer height in mm.
364//   foldangle = The interior angle in degrees of the joint to be created with the hinge.  Default: 90
365//   hingegap = Size in mm of the gap at the bottom of the hinge, to make room for folding.
366//   $slop = Increase size of hinge gap by double this amount
367//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#subsection-anchor).  Default: `CENTER`
368//   spin = Rotate this many degrees around the Z axis.  See [spin](attachments.scad#subsection-spin).  Default: `0`
369//   orient = Vector to rotate top towards.  See [orient](attachments.scad#subsection-orient).  Default: `UP`
370// Example:
371//   living_hinge_mask(l=100, thick=3, foldangle=60);
372module living_hinge_mask(l, thick, layerheight=0.2, foldangle=90, hingegap=undef, anchor=CENTER, spin=0, orient=UP)
373{
374    hingegap = default(hingegap, layerheight)+2*get_slop();
375    size = [l, hingegap, 2*thick];
376    size2 = [l, hingegap+2*thick*tan(foldangle/2)];
377    attachable(anchor,spin,orient, size=size, size2=size2) {
378        up(layerheight*2) prismoid([l,hingegap], [l, hingegap+2*thick/tan(foldangle/2)], h=thick, anchor=BOT);
379        children();
380    }
381}
382
383module folding_hinge_mask(l, thick, layerheight=0.2, foldangle=90, hingegap=undef, anchor=CENTER, spin=0, orient=UP)
384{
385    deprecate("living_hinge_mask");
386    living_hinge_mask(l, thick, layerheight, foldangle, hingegap, anchor, spin, orient);
387}
388
389
390
391// Section: Snap Locks
392
393
394// Module: apply_folding_hinges_and_snaps()
395// Synopsis: Adds snap shapes and removes living hinges from a child shape.
396// SynTags: Geom
397// Topics: Hinges, Parts
398// See Also: knuckle_hinge(), living_hinge_mask(), snap_lock(), snap_socket()
399// Usage:
400//   apply_folding_hinges_and_snaps(thick, [foldangle=], [hinges=], [snaps=], [sockets=], [snaplen=], [snapdiam=], [hingegap=], [layerheight=], [$slop=]) CHILDREN;
401// Description:
402//   Adds snaplocks and create hinges in children at the given positions.
403// Arguments:
404//   thick = Thickness in mm of the material to make the hinge in.
405//   foldangle = The interior angle in degrees of the joint to be created with the hinge.  Default: 90
406//   hinges = List of [LENGTH, POSITION, SPIN] for each hinge to difference from the children.
407//   snaps = List of [POSITION, SPIN] for each central snaplock to add to the children.
408//   sockets = List of [POSITION, SPIN] for each outer snaplock sockets to add to the children.
409//   snaplen = Length of locking snaps.
410//   snapdiam = Diameter/width of locking snaps.
411//   hingegap = Size in mm of the gap at the bottom of the hinge, to make room for folding.
412//   layerheight = The expected printing layer height in mm.
413//   ---
414//   $slop = increase hinge gap by twice this amount
415// Example(Med):
416//   size=100;
417//   apply_folding_hinges_and_snaps(
418//       thick=3, foldangle=54.74,
419//       hinges=[
420//           for (a=[0,120,240], b=[-size/2,size/4]) each [
421//               [200, polar_to_xy(b,a), a+90]
422//           ]
423//       ],
424//       snaps=[
425//           for (a=[0,120,240]) each [
426//               [rot(a,p=[ size/4, 0        ]), a+90],
427//               [rot(a,p=[-size/2,-size/2.33]), a-90]
428//           ]
429//       ],
430//       sockets=[
431//           for (a=[0,120,240]) each [
432//               [rot(a,p=[ size/4, 0        ]), a+90],
433//               [rot(a,p=[-size/2, size/2.33]), a+90]
434//           ]
435//       ]
436//   ) {
437//       $fn=3;
438//       difference() {
439//           cylinder(r=size-1, h=3);
440//           down(0.01) cylinder(r=size/4.5, h=3.1, spin=180);
441//           down(0.01) for (a=[0:120:359.9]) zrot(a) right(size/2) cylinder(r=size/4.5, h=3.1);
442//       }
443//   }
444module apply_folding_hinges_and_snaps(thick, foldangle=90, hinges=[], snaps=[], sockets=[], snaplen=5, snapdiam=5, hingegap=undef, layerheight=0.2)
445{
446    hingegap = default(hingegap, layerheight)+2*get_slop();
447    difference() {
448        children();
449        for (hinge = hinges) {
450            translate(hinge[1]) {
451                living_hinge_mask(
452                    l=hinge[0], thick=thick, layerheight=layerheight,
453                    foldangle=foldangle, hingegap=hingegap, spin=hinge[2]
454                );
455            }
456        }
457    }
458    for (snap = snaps) {
459        translate(snap[0]) {
460            snap_lock(
461                thick=thick, snaplen=snaplen, snapdiam=snapdiam,
462                layerheight=layerheight, foldangle=foldangle,
463                hingegap=hingegap, spin=snap[1]
464            );
465        }
466    }
467    for (socket = sockets) {
468        translate(socket[0]) {
469            snap_socket(
470                thick=thick, snaplen=snaplen, snapdiam=snapdiam,
471                layerheight=layerheight, foldangle=foldangle,
472                hingegap=hingegap, spin=socket[1]
473            );
474        }
475    }
476}
477
478
479
480// Module: snap_lock()
481// Synopsis: Creates a snap-lock shape.
482// SynTags: Geom
483// Topics: Hinges, Parts
484// See Also: knuckle_hinge(), living_hinge_mask(), snap_lock(), snap_socket()
485// Usage:
486//   snap_lock(thick, [snaplen=], [snapdiam=], [layerheight=], [foldangle=], [hingegap=], [$slop=], [anchor=], [spin=], [orient=]) [ATTACHMENTS];
487// Description:
488//   Creates the central snaplock part.
489// Arguments:
490//   thick = Thickness in mm of the material to make the hinge in.
491//   ---
492//   snaplen = Length of locking snaps.
493//   snapdiam = Diameter/width of locking snaps.
494//   layerheight = The expected printing layer height in mm.
495//   foldangle = The interior angle in degrees of the joint to be created with the hinge.  Default: 90
496//   hingegap = Size in mm of the gap at the bottom of the hinge, to make room for folding.
497//   $slop = increase size of hinge gap by double this amount
498//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#subsection-anchor).  Default: `CENTER`
499//   spin = Rotate this many degrees around the Z axis.  See [spin](attachments.scad#subsection-spin).  Default: `0`
500//   orient = Vector to rotate top towards.  See [orient](attachments.scad#subsection-orient).  Default: `UP`
501// Example:
502//   snap_lock(thick=3, foldangle=60);
503module snap_lock(thick, snaplen=5, snapdiam=5, layerheight=0.2, foldangle=90, hingegap=undef, anchor=CENTER, spin=0, orient=UP)
504{
505    hingegap = default(hingegap, layerheight)+2*get_slop();
506    snap_x = (snapdiam/2) / tan(foldangle/2) + (thick-2*layerheight)/tan(foldangle/2) + hingegap/2;
507    size = [snaplen, snapdiam, 2*thick];
508    attachable(anchor,spin,orient, size=size) {
509        back(snap_x) {
510            cube([snaplen, snapdiam, snapdiam/2+thick], anchor=BOT) {
511                attach(TOP) xcyl(l=snaplen, d=snapdiam, $fn=16);
512                attach(TOP) xcopies(snaplen-snapdiam/4/3) xscale(0.333) sphere(d=snapdiam*0.8, $fn=12);
513            }
514        }
515        children();
516    }
517}
518
519
520// Module: snap_socket()
521// Synopsis: Creates a snap-lock socket shape.
522// SynTags: Geom
523// Topics: Hinges, Parts
524// See Also: knuckle_hinge(), living_hinge_mask(), snap_lock(), snap_socket()
525// Usage:
526//   snap_socket(thick, [snaplen=], [snapdiam=], [layerheight=], [foldangle=], [hingegap=], [$slop=], [anchor=], [spin=], [orient=]) [ATTACHMENTS];
527// Description:
528//   Creates the outside snaplock socketed part.
529// Arguments:
530//   thick = Thickness in mm of the material to make the hinge in.
531//   ---
532//   snaplen = Length of locking snaps.
533//   snapdiam = Diameter/width of locking snaps.
534//   layerheight = The expected printing layer height in mm.
535//   foldangle = The interior angle in degrees of the joint to be created with the hinge.  Default: 90
536//   hingegap = Size in mm of the gap at the bottom of the hinge, to make room for folding.
537//   $slop = Increase size of hinge gap by double this amount
538//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#subsection-anchor).  Default: `CENTER`
539//   spin = Rotate this many degrees around the Z axis.  See [spin](attachments.scad#subsection-spin).  Default: `0`
540//   orient = Vector to rotate top towards.  See [orient](attachments.scad#subsection-orient).  Default: `UP`
541// Example:
542//   snap_socket(thick=3, foldangle=60);
543module snap_socket(thick, snaplen=5, snapdiam=5, layerheight=0.2, foldangle=90, hingegap=undef, anchor=CENTER, spin=0, orient=UP)
544{
545    hingegap = default(hingegap, layerheight)+2*get_slop();
546    snap_x = (snapdiam/2) / tan(foldangle/2) + (thick-2*layerheight)/tan(foldangle/2) + hingegap/2;
547    size = [snaplen, snapdiam, 2*thick];
548    attachable(anchor,spin,orient, size=size) {
549        fwd(snap_x) {
550            zrot_copies([0,180], r=snaplen+get_slop()) {
551                diff("divot")
552                cube([snaplen, snapdiam, snapdiam/2+thick], anchor=BOT) {
553                    attach(TOP) xcyl(l=snaplen, d=snapdiam, $fn=16);
554                    tag("divot") attach(TOP) left((snaplen+snapdiam/4/3)/2) xscale(0.333) sphere(d=snapdiam*0.8, $fn=12);
555                }
556            }
557        }
558        children();
559    }
560}
561
562
563
564// vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap