1//////////////////////////////////////////////////////////////////////
  2// LibFile: partitions.scad
  3//   Cut objects with a plane, or partition them into interlocking pieces for easy printing of large objects. 
  4// Includes:
  5//   include <BOSL2/std.scad>
  6// FileGroup: Basic Modeling
  7// FileSummary: Cut objects with a plane or partition them into interlocking pieces.
  8// FileFootnotes: STD=Included in std.scad
  9//////////////////////////////////////////////////////////////////////
 10
 11
 12// Section: Planar Cutting
 13
 14// Function&Module: half_of()
 15//
 16// Usage: as module
 17//   half_of(v, [cp], [s], [planar]) CHILDREN;
 18// Usage: as function
 19//   result = half_of(p,v,[cp]);
 20//
 21// Description:
 22//   Slices an object at a cut plane, and masks away everything that is on one side.  The v parameter is either a plane specification or
 23//   a normal vector.  The s parameter is needed for the module
 24//   version to control the size of the masking cube.  If s is too large then the preview display will flip around and display the
 25//   wrong half, but if it is too small it won't fully mask your model.  
 26//   When called as a function, you must supply a vnf, path or region in p.  If planar is set to true for the module version the operation
 27//   is performed in 2D and UP and DOWN are treated as equivalent to BACK and FWD respectively.
 28//
 29// Arguments:
 30//   p = path, region or VNF to slice.  (Function version)
 31//   v = Normal of plane to slice at.  Keeps everything on the side the normal points to.  Default: [0,0,1] (UP)
 32//   cp = If given as a scalar, moves the cut plane along the normal by the given amount.  If given as a point, specifies a point on the cut plane.  Default: [0,0,0]
 33//   s = Mask size to use.  Use a number larger than twice your object's largest axis.  If you make this too large, OpenSCAD's preview rendering may display the wrong half.  (Module version)  Default: 100
 34//   planar = If true, perform a 2D operation.  When planar, a `v` of `UP` or `DOWN` becomes equivalent of `BACK` and `FWD` respectively.  (Module version).  Default: false.  
 35//
 36// Examples:
 37//   half_of(DOWN+BACK, cp=[0,-10,0]) cylinder(h=40, r1=10, r2=0, center=false);
 38//   half_of(DOWN+LEFT, s=200) sphere(d=150);
 39// Example(2D):
 40//   half_of([1,1], planar=true) circle(d=50);
 41module half_of(v=UP, cp, s=100, planar=false)
 42{
 43    req_children($children);
 44    cp = is_vector(v,4)? assert(cp==undef, "Don't use cp with plane definition.") plane_normal(v) * v[3] :
 45        is_vector(cp)? cp :
 46        is_num(cp)? cp*unit(v) :
 47        [0,0,0];
 48    v = is_vector(v,4)? plane_normal(v) : v;
 49    if (cp != [0,0,0]) {
 50        translate(cp) half_of(v=v, s=s, planar=planar) translate(-cp) children();
 51    } else if (planar) {
 52        v = (v==UP)? BACK : (v==DOWN)? FWD : v;
 53        ang = atan2(v.y, v.x);
 54        difference() {
 55            children();
 56            rotate(ang+90) {
 57                back(s/2) square(s, center=true);
 58            }
 59        }
 60    } else {
 61        difference() {
 62            children();
 63            rot(from=UP, to=-v) {
 64                up(s/2) cube(s, center=true);
 65            }
 66        }
 67    }
 68}
 69
 70function half_of(p, v=UP, cp) =
 71    is_vnf(p) ?
 72       assert(is_vector(v) && (len(v)==3 || len(v)==4),str("Must give 3-vector or plane specification",v))
 73       assert(select(v,0,2)!=[0,0,0], "vector v must be nonzero")
 74       let(
 75            plane = is_vector(v,4) ? assert(cp==undef, "Don't use cp with plane definition.") v
 76                  : is_undef(cp) ? [each v, 0]
 77                  : is_num(cp) ? [each v, cp*(v*v)/norm(v)]
 78                  : assert(is_vector(cp,3),"Centerpoint must be a 3-vector")
 79                    [each v, cp*v]
 80       )
 81       vnf_halfspace(plane, p)
 82   : is_path(p) || is_region(p) ?
 83      let(
 84          v = (v==UP)? BACK : (v==DOWN)? FWD : v,
 85          cp = is_undef(cp) ? [0,0]
 86             : is_num(cp) ? v*cp
 87             : assert(is_vector(cp,2) || (is_vector(cp,3) && cp.z==0),"Centerpoint must be 2-vector")
 88               cp
 89      )
 90      assert(is_vector(v,2) || (is_vector(v,3) && v.z==0),"Must give 2-vector")
 91      assert(!all_zero(v), "Vector v must be nonzero")
 92      let(
 93          bounds = pointlist_bounds(move(-cp,p)),
 94          L = 2*max(flatten(bounds)),
 95          n = unit(v),
 96          u = [-n.y,n.x],
 97          box = [cp+u*L, cp+(v+u)*L, cp+(v-u)*L, cp-u*L]
 98      )
 99      intersection(box,p)
100   : assert(false, "Input must be a region, path or VNF");
101
102
103
104/*  This code cut 3d paths but leaves behind connecting line segments
105    is_path(p) ?
106        //assert(len(p[0]) == d, str("path must have dimension ", d))
107        let(z = [for(x=p) (x-cp)*v])
108        [ for(i=[0:len(p)-1]) each concat(z[i] >= 0 ? [p[i]] : [],
109            // we assume a closed path here;
110            // to make this correct for an open path,
111            // just replace this by [] when i==len(p)-1:
112            let(j=(i+1)%len(p))
113            // the remaining path may have flattened sections, but this cannot
114            // create self-intersection or whiskers:
115            z[i]*z[j] >= 0 ? [] : [(z[j]*p[i]-z[i]*p[j])/(z[j]-z[i])]) ]
116        :
117*/
118
119
120// Function&Module: left_half()
121//
122// Usage: as module
123//   left_half([s], [x]) CHILDREN;
124//   left_half(planar=true, [s], [x]) CHILDREN;
125// Usage: as function
126//   result = left_half(p, [x]);
127//
128// Description:
129//   Slices an object at a vertical Y-Z cut plane, and masks away everything that is right of it.
130//   The s parameter is needed for the module
131//   version to control the size of the masking cube.  If s is too large then the preview display will flip around and display the
132//   wrong half, but if it is too small it won't fully mask your model.  
133//
134// Arguments:
135//   p = VNF, region or path to slice (function version)
136//   s = Mask size to use.  Use a number larger than twice your object's largest axis.  If you make this too large, OpenSCAD's preview rendering may display the wrong half.  (Module version)  Default: 100
137//   x = The X coordinate of the cut-plane.  Default: 0
138//   planar = If true, perform a 2D operation.  (Module version)  Default: false. 
139// Examples:
140//   left_half() sphere(r=20);
141//   left_half(x=-8) sphere(r=20);
142// Example(2D):
143//   left_half(planar=true) circle(r=20);
144module left_half(s=100, x=0, planar=false)
145{
146    req_children($children);
147    dir = LEFT;
148    difference() {
149        children();
150        translate([x,0,0]-dir*s/2) {
151            if (planar) {
152                square(s, center=true);
153            } else {
154                cube(s, center=true);
155            }
156        }
157    }
158}
159function left_half(p,x=0) = half_of(p, LEFT, [x,0,0]);
160
161
162
163// Function&Module: right_half()
164//
165// Usage: as module
166//   right_half([s=], [x=]) CHILDREN;
167//   right_half(planar=true, [s=], [x=]) CHILDREN;
168// Usage: as function
169//   result = right_half(p, [x=]);
170//
171// Description:
172//   Slices an object at a vertical Y-Z cut plane, and masks away everything that is left of it.
173//   The s parameter is needed for the module
174//   version to control the size of the masking cube.  If s is too large then the preview display will flip around and display the
175//   wrong half, but if it is too small it won't fully mask your model.  
176// Arguments:
177//   p = VNF, region or path to slice (function version)
178//   s = Mask size to use.  Use a number larger than twice your object's largest axis.  If you make this too large, OpenSCAD's preview rendering may display the wrong half.  (Module version)  Default: 100
179//   x = The X coordinate of the cut-plane.  Default: 0
180//   planar = If true, perform a 2D operation.  (Module version)  Default: false. 
181// Examples(FlatSpin,VPD=175):
182//   right_half() sphere(r=20);
183//   right_half(x=-5) sphere(r=20);
184// Example(2D):
185//   right_half(planar=true) circle(r=20);
186module right_half(s=100, x=0, planar=false)
187{
188    dir = RIGHT;
189    difference() {
190        children();
191        translate([x,0,0]-dir*s/2) {
192            if (planar) {
193                square(s, center=true);
194            } else {
195                cube(s, center=true);
196            }
197        }
198    }
199}
200function right_half(p,x=0) = half_of(p, RIGHT, [x,0,0]);
201
202
203
204// Function&Module: front_half()
205//
206// Usage:
207//   front_half([s], [y]) CHILDREN;
208//   front_half(planar=true, [s], [y]) CHILDREN;
209// Usage: as function
210//   result = front_half(p, [y]);
211//
212// Description:
213//   Slices an object at a vertical X-Z cut plane, and masks away everything that is behind it.
214//   The s parameter is needed for the module
215//   version to control the size of the masking cube.  If s is too large then the preview display will flip around and display the
216//   wrong half, but if it is too small it won't fully mask your model.  
217// Arguments:
218//   p = VNF, region or path to slice (function version)
219//   s = Mask size to use.  Use a number larger than twice your object's largest axis.  If you make this too large, OpenSCAD's preview rendering may display the wrong half.  (Module version)  Default: 100
220//   y = The Y coordinate of the cut-plane.  Default: 0
221//   planar = If true, perform a 2D operation.  (Module version)  Default: false. 
222// Examples(FlatSpin,VPD=175):
223//   front_half() sphere(r=20);
224//   front_half(y=5) sphere(r=20);
225// Example(2D):
226//   front_half(planar=true) circle(r=20);
227module front_half(s=100, y=0, planar=false)
228{
229    req_children($children);
230    dir = FWD;
231    difference() {
232        children();
233        translate([0,y,0]-dir*s/2) {
234            if (planar) {
235                square(s, center=true);
236            } else {
237                cube(s, center=true);
238            }
239        }
240    }
241}
242function front_half(p,y=0) = half_of(p, FRONT, [0,y,0]);
243
244
245
246// Function&Module: back_half()
247//
248// Usage:
249//   back_half([s], [y]) CHILDREN;
250//   back_half(planar=true, [s], [y]) CHILDREN;
251// Usage: as function
252//   result = back_half(p, [y]);
253//
254// Description:
255//   Slices an object at a vertical X-Z cut plane, and masks away everything that is in front of it.
256//   The s parameter is needed for the module
257//   version to control the size of the masking cube.  If s is too large then the preview display will flip around and display the
258//   wrong half, but if it is too small it won't fully mask your model.  
259// Arguments:
260//   p = VNF, region or path to slice (function version)
261//   s = Mask size to use.  Use a number larger than twice your object's largest axis.  If you make this too large, OpenSCAD's preview rendering may display the wrong half.  (Module version)  Default: 100
262//   y = The Y coordinate of the cut-plane.  Default: 0
263//   planar = If true, perform a 2D operation.  (Module version)  Default: false. 
264// Examples:
265//   back_half() sphere(r=20);
266//   back_half(y=8) sphere(r=20);
267// Example(2D):
268//   back_half(planar=true) circle(r=20);
269module back_half(s=100, y=0, planar=false)
270{
271    req_children($children);
272    dir = BACK;
273    difference() {
274        children();
275        translate([0,y,0]-dir*s/2) {
276            if (planar) {
277                square(s, center=true);
278            } else {
279                cube(s, center=true);
280            }
281        }
282    }
283}
284function back_half(p,y=0) = half_of(p, BACK, [0,y,0]);
285
286
287
288// Function&Module: bottom_half()
289//
290// Usage:
291//   bottom_half([s], [z]) CHILDREN;
292// Usage: as function
293//   result = bottom_half(p, [z]);
294//
295// Description:
296//   Slices an object at a horizontal X-Y cut plane, and masks away everything that is above it.
297//   The s parameter is needed for the module
298//   version to control the size of the masking cube.  If s is too large then the preview display will flip around and display the
299//   wrong half, but if it is too small it won't fully mask your model. 
300// Arguments:
301//   p = VNF, region or path to slice (function version)
302//   s = Mask size to use.  Use a number larger than twice your object's largest axis.  If you make this too large, OpenSCAD's preview rendering may display the wrong half.  (Module version)  Default: 100
303//   z = The Z coordinate of the cut-plane.  Default: 0
304// Examples:
305//   bottom_half() sphere(r=20);
306//   bottom_half(z=-10) sphere(r=20);
307module bottom_half(s=100, z=0)
308{
309    req_children($children);
310    dir = DOWN;
311    difference() {
312        children();
313        translate([0,0,z]-dir*s/2) {
314            cube(s, center=true);
315        }
316    }
317}
318function bottom_half(p,z=0) = half_of(p,BOTTOM,[0,0,z]);
319
320
321
322// Function&Module: top_half()
323//
324// Usage: as module
325//   top_half([s], [z]) CHILDREN;
326// Usage: as function
327//   result = top_half(p, [z]);
328//
329// Description:
330//   Slices an object at a horizontal X-Y cut plane, and masks away everything that is below it.
331//   The s parameter is needed for the module
332//   version to control the size of the masking cube.  If s is too large then the preview display will flip around and display the
333//   wrong half, but if it is too small it won't fully mask your model.  
334// Arguments:
335//   p = VNF, region or path to slice (function version)
336//   s = Mask size to use.  Use a number larger than twice your object's largest axis.  If you make this too large, OpenSCAD's preview rendering may display the wrong half.  (Module version)  Default: 100
337//   z = The Z coordinate of the cut-plane.  Default: 0
338// Examples(Spin,VPD=175):
339//   top_half() sphere(r=20);
340//   top_half(z=5) sphere(r=20);
341module top_half(s=100, z=0)
342{
343    req_children($children);
344    dir = UP;
345    difference() {
346        children();
347        translate([0,0,z]-dir*s/2) {
348            cube(s, center=true);
349        }
350    }
351}
352function top_half(p,z=0) = half_of(p,UP,[0,0,z]);
353
354
355
356// Section: Partioning into Interlocking Pieces
357
358
359function _partition_subpath(type) =
360    type=="flat"?  [[0,0],[1,0]] :
361    type=="sawtooth"? [[0,-0.5], [0.5,0.5], [1,-0.5]] :
362    type=="sinewave"? [for (a=[0:5:360]) [a/360,sin(a)/2]] :
363    type=="comb"?   let(dx=0.5*sin(2))  [[0,0],[0+dx,0.5],[0.5-dx,0.5],[0.5+dx,-0.5],[1-dx,-0.5],[1,0]] :
364    type=="finger"? let(dx=0.5*sin(20)) [[0,0],[0+dx,0.5],[0.5-dx,0.5],[0.5+dx,-0.5],[1-dx,-0.5],[1,0]] :
365    type=="dovetail"? [[0,-0.5], [0.3,-0.5], [0.2,0.5], [0.8,0.5], [0.7,-0.5], [1,-0.5]] :
366    type=="hammerhead"? [[0,-0.5], [0.35,-0.5], [0.35,0], [0.15,0], [0.15,0.5], [0.85,0.5], [0.85,0], [0.65,0], [0.65,-0.5],[1,-0.5]] :
367    type=="jigsaw"? concat(
368                        arc(r=5/16, cp=[0,-3/16],  start=270, angle=125),
369                        arc(r=5/16, cp=[1/2,3/16], start=215, angle=-250),
370                        arc(r=5/16, cp=[1,-3/16],  start=145, angle=125)
371                    ) :
372    assert(false, str("Unsupported cutpath type: ", type));
373
374
375function _partition_cutpath(l, h, cutsize, cutpath, gap) =
376    let(
377        check = assert(is_finite(l))
378            assert(is_finite(h))
379            assert(is_finite(gap))
380            assert(is_finite(cutsize) || is_vector(cutsize,2))
381            assert(is_string(cutpath) || is_path(cutpath,2)),
382        cutsize = is_vector(cutsize)? cutsize : [cutsize*2, cutsize],
383        cutpath = is_path(cutpath)? cutpath :
384            _partition_subpath(cutpath),
385        reps = ceil(l/(cutsize.x+gap)),
386        cplen = (cutsize.x+gap) * reps,
387        path = deduplicate(concat(
388            [[-l/2, cutpath[0].y*cutsize.y]],
389            [for (i=[0:1:reps-1], pt=cutpath) v_mul(pt,cutsize)+[i*(cutsize.x+gap)+gap/2-cplen/2,0]],
390            [[ l/2, cutpath[len(cutpath)-1].y*cutsize.y]]
391        )),
392        stidxs = [for (i = idx(path)) if (path[i].x < -l/2) i],
393        enidxs = [for (i = idx(path)) if (path[i].x > +l/2) i],
394        stidx = stidxs? last(stidxs) : 0,
395        enidx = enidxs? enidxs[0] : -1,
396        trunc = select(path, stidx, enidx)
397    ) trunc;
398
399
400// Module: partition_mask()
401// Usage:
402//   partition_mask(l, w, h, [cutsize], [cutpath], [gap], [inverse], [$slop=], [anchor=], [spin=], [orient=]) [ATTACHMENTS];
403// Description:
404//   Creates a mask that you can use to difference or intersect with an object to remove half of it, leaving behind a side designed to allow assembly of the sub-parts.
405// Arguments:
406//   l = The length of the cut axis.
407//   w = The width of the part to be masked, back from the cut plane.
408//   h = The height of the part to be masked.
409//   cutsize = The width of the cut pattern to be used.
410//   cutpath = The cutpath to use.  Standard named paths are "flat", "sawtooth", "sinewave", "comb", "finger", "dovetail", "hammerhead", and "jigsaw".  Alternatively, you can give a cutpath as a 2D path, where X is between 0 and 1, and Y is between -0.5 and 0.5.
411//   gap = Empty gaps between cutpath iterations.  Default: 0
412//   inverse = If true, create a cutpath that is meant to mate to a non-inverted cutpath.
413//   spin = Rotate this many degrees around the Z axis.  See [spin](attachments.scad#subsection-spin).  Default: `0`
414//   orient = Vector to rotate top towards.  See [orient](attachments.scad#subsection-orient).  Default: `UP`
415//   $slop = The amount to shrink the mask by, to correct for printer-specific fitting.
416// Examples:
417//   partition_mask(w=50, gap=0, cutpath="jigsaw");
418//   partition_mask(w=50, gap=30, cutpath="jigsaw");
419//   partition_mask(w=50, gap=30, cutpath="jigsaw", inverse=true);
420//   partition_mask(w=50, gap=30, cutsize=15, cutpath="jigsaw");
421//   partition_mask(w=50, cutsize=[20,20], gap=30, cutpath="jigsaw");
422// Examples(2D):
423//   partition_mask(w=20, cutpath="sawtooth");
424//   partition_mask(w=20, cutpath="sinewave");
425//   partition_mask(w=20, cutpath="comb");
426//   partition_mask(w=20, cutpath="finger");
427//   partition_mask(w=20, cutpath="dovetail");
428//   partition_mask(w=20, cutpath="hammerhead");
429//   partition_mask(w=20, cutpath="jigsaw");
430module partition_mask(l=100, w=100, h=100, cutsize=10, cutpath="jigsaw", gap=0, inverse=false, anchor=CENTER, spin=0, orient=UP)
431{
432    cutsize = is_vector(cutsize)? point2d(cutsize) : [cutsize*2, cutsize];
433    path = _partition_cutpath(l, h, cutsize, cutpath, gap);
434    midpath = select(path,1,-2);
435    sizepath = concat([path[0]+[-get_slop(),0]], midpath, [last(path)+[get_slop(),0]], [[+(l/2+get_slop()), (w+get_slop())*(inverse?-1:1)], [-(l/2+get_slop()), (w+get_slop())*(inverse?-1:1)]]);
436    bnds = pointlist_bounds(sizepath);
437    fullpath = concat(path, [[last(path).x, w*(inverse?-1:1)], [path[0].x, w*(inverse?-1:1)]]);
438    attachable(anchor,spin,orient, size=point3d(bnds[1]-bnds[0],h)) {
439        linear_extrude(height=h, center=true, convexity=10) {
440            intersection() {
441                offset(delta=-get_slop()) polygon(fullpath);
442                square([l, w*2], center=true);
443            }
444        }
445        children();
446    }
447}
448
449
450// Module: partition_cut_mask()
451// Usage:
452//   partition_cut_mask(l, w, h, [cutsize], [cutpath], [gap], [inverse], [$slop=], [anchor=], [spin=], [orient=]) [ATTACHMENTS];
453// Description:
454//   Creates a mask that you can use to difference with an object to cut it into two sub-parts that can be assembled.
455//   The `$slop` value is important to get the proper fit and should probably be smaller than 0.2.  The examples below
456//   use larger values to make the mask easier to see.  
457// Arguments:
458//   l = The length of the cut axis.
459//   w = The width of the part to be masked, back from the cut plane.
460//   h = The height of the part to be masked.
461//   cutsize = The width of the cut pattern to be used.
462//   cutpath = The cutpath to use.  Standard named paths are "flat", "sawtooth", "sinewave", "comb", "finger", "dovetail", "hammerhead", and "jigsaw".  Alternatively, you can give a cutpath as a 2D path, where X is between 0 and 1, and Y is between -0.5 and 0.5.  Default: "jigsaw"
463//   gap = Empty gaps between cutpath iterations.  Default: 0
464//   spin = Rotate this many degrees around the Z axis.  See [spin](attachments.scad#subsection-spin).  Default: `0`
465//   orient = Vector to rotate top towards.  See [orient](attachments.scad#subsection-orient).  Default: `UP`
466//   $slop = The width of the cut mask, to correct for printer-specific fitting. 
467// Examples:
468//   partition_cut_mask(gap=0, cutpath="dovetail");
469//   partition_cut_mask(gap=30, cutpath="dovetail");
470//   partition_cut_mask(gap=30, cutsize=15, cutpath="dovetail");
471//   partition_cut_mask(gap=30, cutsize=[20,20], cutpath="dovetail");
472// Examples(2DMed):
473//   partition_cut_mask(cutpath="sawtooth",$slop=0.5);
474//   partition_cut_mask(cutpath="sinewave",$slop=0.5);
475//   partition_cut_mask(cutpath="comb",$slop=0.5);
476//   partition_cut_mask(cutpath="finger",$slop=0.5);
477//   partition_cut_mask(cutpath="dovetail",$slop=1);
478//   partition_cut_mask(cutpath="hammerhead",$slop=1);
479//   partition_cut_mask(cutpath="jigsaw",$slop=0.5);
480module partition_cut_mask(l=100, h=100, cutsize=10, cutpath="jigsaw", gap=0, anchor=CENTER, spin=0, orient=UP)
481{
482    cutsize = is_vector(cutsize)? cutsize : [cutsize*2, cutsize];
483    path = _partition_cutpath(l, h, cutsize, cutpath, gap);
484    attachable(anchor,spin,orient, size=[l,cutsize.y,h]) {
485        linear_extrude(height=h, center=true, convexity=10) {
486            stroke(path, width=max(0.1, get_slop()*2));
487        }
488        children();
489    }
490}
491
492
493// Module: partition()
494// Usage:
495//   partition(size, [spread], [cutsize], [cutpath], [gap], [spin], [$slop=]) CHILDREN;
496// Description:
497//   Partitions an object into two parts, spread apart a small distance, with matched joining edges.
498// Arguments:
499//   size = The [X,Y,Z] size of the object to partition.
500//   spread = The distance to spread the two parts by.
501//   cutsize = The width of the cut pattern to be used.
502//   cutpath = The cutpath to use.  Standard named paths are "flat", "sawtooth", "sinewave", "comb", "finger", "dovetail", "hammerhead", and "jigsaw".  Alternatively, you can give a cutpath as a 2D path, where X is between 0 and 1, and Y is between -0.5 and 0.5.
503//   gap = Empty gaps between cutpath iterations.  Default: 0
504//   spin = Rotate this many degrees around the Z axis.  See [spin](attachments.scad#subsection-spin).  Default: `0`
505//   ---
506//   $slop = Extra gap to leave to correct for printer-specific fitting. 
507// Examples(Med):
508//   partition(spread=12, cutpath="dovetail") cylinder(h=50, d=80, center=false);
509//   partition(spread=12, gap=30, cutpath="dovetail") cylinder(h=50, d=80, center=false);
510//   partition(spread=20, gap=20, cutsize=15, cutpath="dovetail") cylinder(h=50, d=80, center=false);
511//   partition(spread=25, gap=15, cutsize=[20,20], cutpath="dovetail") cylinder(h=50, d=80, center=false);
512// Examples(2DMed):
513//   partition(cutpath="sawtooth") cylinder(h=50, d=80, center=false);
514//   partition(cutpath="sinewave") cylinder(h=50, d=80, center=false);
515//   partition(cutpath="comb") cylinder(h=50, d=80, center=false);
516//   partition(cutpath="finger") cylinder(h=50, d=80, center=false);
517//   partition(spread=12, cutpath="dovetail") cylinder(h=50, d=80, center=false);
518//   partition(spread=12, cutpath="hammerhead") cylinder(h=50, d=80, center=false);
519//   partition(cutpath="jigsaw") cylinder(h=50, d=80, center=false);
520module partition(size=100, spread=10, cutsize=10, cutpath="jigsaw", gap=0, spin=0)
521{
522    req_children($children);
523    size = is_vector(size)? size : [size,size,size];
524    cutsize = is_vector(cutsize)? cutsize : [cutsize*2, cutsize];
525    rsize = v_abs(rot(spin,p=size));
526    vec = rot(spin,p=BACK)*spread/2;
527    move(vec) {
528        intersection() {
529            children();
530            partition_mask(l=rsize.x, w=rsize.y, h=rsize.z, cutsize=cutsize, cutpath=cutpath, gap=gap, spin=spin);
531        }
532    }
533    move(-vec) {
534        intersection() {
535            children();
536            partition_mask(l=rsize.x, w=rsize.y, h=rsize.z, cutsize=cutsize, cutpath=cutpath, gap=gap, inverse=true, spin=spin);
537        }
538    }
539}
540
541
542
543// vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap