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