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