1//////////////////////////////////////////////////////////////////////
2// LibFile: mutators.scad
3// Functions and modules to mutate children in various ways.
4// Includes:
5// include <BOSL2/std.scad>
6// FileGroup: Basic Modeling
7// FileSummary: Modules and Functions to mutate items.
8// FileFootnotes: STD=Included in std.scad
9//////////////////////////////////////////////////////////////////////
10
11//////////////////////////////////////////////////////////////////////
12// Section: Bounding Box
13//////////////////////////////////////////////////////////////////////
14
15// Module: bounding_box()
16// Synopsis: Creates the smallest bounding box that contains all the children.
17// SynTags: Geom
18// Topics: Mutators, Bounds, Bounding Boxes
19// See Also: pointlist_bounds()
20// Usage:
21// bounding_box([excess],[planar]) CHILDREN;
22// Description:
23// Returns the smallest axis-aligned square (or cube) shape that contains all the 2D (or 3D)
24// children given. The module children() must 3d when planar=false and
25// 2d when planar=true, or you will get a warning of mixing dimension
26// or scaling by 0.
27// Arguments:
28// excess = The amount that the bounding box should be larger than needed to bound the children, in each axis.
29// planar = If true, creates a 2D bounding rectangle. Is false, creates a 3D bounding cube. Default: false
30// Example(3D):
31// module shapes() {
32// translate([10,8,4]) cube(5);
33// translate([3,0,12]) cube(2);
34// }
35// #bounding_box() shapes();
36// shapes();
37// Example(2D):
38// module shapes() {
39// translate([10,8]) square(5);
40// translate([3,0]) square(2);
41// }
42// #bounding_box(planar=true) shapes();
43// shapes();
44module bounding_box(excess=0, planar=false) {
45 // a 3d (or 2d when planar=true) approx. of the children projection on X axis
46 module _xProjection() {
47 if (planar) {
48 projection()
49 rotate([90,0,0])
50 linear_extrude(1, center=true)
51 hull()
52 children();
53 } else {
54 xs = excess<.1? 1: excess;
55 linear_extrude(xs, center=true)
56 projection()
57 rotate([90,0,0])
58 linear_extrude(xs, center=true)
59 projection()
60 hull()
61 children();
62 }
63 }
64
65 // a bounding box with an offset of 1 in all axis
66 module _oversize_bbox() {
67 if (planar) {
68 minkowski() {
69 _xProjection() children(); // x axis
70 rotate(-90) _xProjection() rotate(90) children(); // y axis
71 }
72 } else {
73 minkowski() {
74 _xProjection() children(); // x axis
75 rotate(-90) _xProjection() rotate(90) children(); // y axis
76 rotate([0,-90,0]) _xProjection() rotate([0,90,0]) children(); // z axis
77 }
78 }
79 }
80
81 // offsets a cube by `excess`
82 module _shrink_cube() {
83 intersection() {
84 translate((1-excess)*[ 1, 1, 1]) children();
85 translate((1-excess)*[-1,-1,-1]) children();
86 }
87 }
88
89 req_children($children);
90 attachable(){
91 if(planar) {
92 offset(excess-1/2) _oversize_bbox() children();
93 } else {
94 render(convexity=2)
95 if (excess>.1) {
96 _oversize_bbox() children();
97 } else {
98 _shrink_cube() _oversize_bbox() children();
99 }
100 }
101 union();
102 }
103}
104
105
106//////////////////////////////////////////////////////////////////////
107// Section: Warp Mutators
108//////////////////////////////////////////////////////////////////////
109
110
111// Module: chain_hull()
112// Synopsis: Performs the union of hull operations between consecutive pairs of children.
113// SynTags: Geom
114// Topics: Mutators
115// See Also: hull()
116// Usage:
117// chain_hull() CHILDREN;
118//
119// Description:
120// Performs hull operations between consecutive pairs of children,
121// then unions all of the hull results. This can be a very slow
122// operation, but it can provide results that are hard to get
123// otherwise.
124//
125// Side Effects:
126// `$idx` is set to the index value of the first child of each hulling pair, and can be used to modify each child pair individually.
127// `$primary` is set to true when the child is the first in a chain pair.
128//
129// Example:
130// chain_hull() {
131// cube(5, center=true);
132// translate([30, 0, 0]) sphere(d=15);
133// translate([60, 30, 0]) cylinder(d=10, h=20);
134// translate([60, 60, 0]) cube([10,1,20], center=false);
135// }
136// Example: Using `$idx` and `$primary`
137// chain_hull() {
138// zrot( 0) right(100) if ($primary) cube(5+3*$idx,center=true); else sphere(r=10+3*$idx);
139// zrot( 45) right(100) if ($primary) cube(5+3*$idx,center=true); else sphere(r=10+3*$idx);
140// zrot( 90) right(100) if ($primary) cube(5+3*$idx,center=true); else sphere(r=10+3*$idx);
141// zrot(135) right(100) if ($primary) cube(5+3*$idx,center=true); else sphere(r=10+3*$idx);
142// zrot(180) right(100) if ($primary) cube(5+3*$idx,center=true); else sphere(r=10+3*$idx);
143// }
144module chain_hull()
145{
146 req_children($children);
147 attachable(){
148 if ($children == 1) {
149 children();
150 }
151 else {
152 for (i =[1:1:$children-1]) {
153 $idx = i;
154 hull() {
155 let($primary=true) children(i-1);
156 let($primary=false) children(i);
157 }
158 }
159 }
160 union();
161 }
162}
163
164
165// Module: path_extrude2d()
166// Synopsis: Extrudes 2D children along a 2D path.
167// SynTags: Geom
168// Topics: Mutators, Extrusion
169// See Also: path_sweep(), path_extrude()
170// Usage:
171// path_extrude2d(path, [caps=], [closed=], [s=], [convexity=]) 2D-CHILDREN;
172// Description:
173// Extrudes 2D children along the given 2D path, with optional rounded endcaps.
174// It works by constructing straight sections corresponding to each segment of the path and inserting rounded joints at each corner.
175// If the children are symmetric across the Y axis line then you can set caps=true to produce rounded caps on the ends of the profile.
176// If you set caps to true for asymmetric children then incorrect caps will be generated.
177// Arguments:
178// path = The 2D path to extrude the geometry along.
179// ---
180// caps = If true, caps each end of the path with a rounded copy of the children. Children must by symmetric across the Y axis, or results are wrong. Default: false
181// closed = If true, connect the starting point of the path to the ending point. Default: false
182// convexity = The max number of times a line could pass though a wall. Default: 10
183// s = Mask size to use. Use a number larger than twice your object's largest axis. If you make this too large, it messes with centering your view. Default: The length of the diagonal of the path's bounding box.
184// Example:
185// path = [
186// each right(50, p=arc(d=100,angle=[90,180])),
187// each left(50, p=arc(d=100,angle=[0,-90])),
188// ];
189// path_extrude2d(path,caps=false) {
190// fwd(2.5) square([5,6],center=true);
191// fwd(6) square([10,5],center=true);
192// }
193// Example:
194// path_extrude2d(arc(d=100,angle=[180,270]),caps=true)
195// trapezoid(w1=10, w2=5, h=10, anchor=BACK);
196// Example:
197// include <BOSL2/beziers.scad>
198// path = bezpath_curve([
199// [-50,0], [-25,50], [0,0], [50,0]
200// ]);
201// path_extrude2d(path, caps=false)
202// trapezoid(w1=10, w2=3, h=5, anchor=BACK);
203// Example: Un-Closed Path
204// $fn=16;
205// spath = star(id=15,od=35,n=5);
206// path_extrude2d(spath, caps=false, closed=false)
207// move_copies([[-3.5,1.5],[0.0,3.0],[3.5,1.5]])
208// circle(r=1.5);
209// Example: Complex Endcaps
210// $fn=16;
211// spath = star(id=15,od=35,n=5);
212// path_extrude2d(spath, caps=true, closed=false)
213// move_copies([[-3.5,1.5],[0.0,3.0],[3.5,1.5]])
214// circle(r=1.5);
215module path_extrude2d(path, caps=false, closed=false, s, convexity=10) {
216 req_children($children);
217 extra_ang = 0.1; // Extra angle for overlap of joints
218 check =
219 assert(caps==false || closed==false, "Cannot have caps on a closed extrusion")
220 assert(is_path(path,2));
221 path = deduplicate(path);
222 s = s!=undef? s :
223 let(b = pointlist_bounds(path))
224 norm(b[1]-b[0]);
225 check2 = assert(is_finite(s));
226 L = len(path);
227 attachable(){
228 union(){
229 for (i = [0:1:L-(closed?1:2)]) {
230 seg = select(path, i, i+1);
231 segv = seg[1] - seg[0];
232 seglen = norm(segv);
233 translate((seg[0]+seg[1])/2) {
234 rot(from=BACK, to=segv) {
235 difference() {
236 xrot(90) {
237 linear_extrude(height=seglen, center=true, convexity=convexity) {
238 children();
239 }
240 }
241 if (closed || i>0) {
242 pt = select(path, i-1);
243 pang = v_theta(rot(from=-segv, to=RIGHT, p=pt - seg[0]));
244 fwd(seglen/2+0.01) zrot(pang/2) cube(s, anchor=BACK);
245 }
246 if (closed || i<L-2) {
247 pt = select(path, i+2);
248 pang = v_theta(rot(from=segv, to=RIGHT, p=pt - seg[1]));
249 back(seglen/2+0.01) zrot(pang/2) cube(s, anchor=FWD);
250 }
251 }
252 }
253 }
254 }
255 for (t=triplet(path,wrap=closed)) {
256 ang = -(180-vector_angle(t)) * sign(_point_left_of_line2d(t[2],[t[0],t[1]]));
257 delt = point3d(t[2] - t[1]);
258 if (ang!=0)
259 translate(t[1]) {
260 frame_map(y=delt, z=UP)
261 rotate(-sign(ang)*extra_ang/2)
262 rotate_extrude(angle=ang+sign(ang)*extra_ang)
263 if (ang<0)
264 right_half(planar=true) children();
265 else
266 left_half(planar=true) children();
267 }
268
269 }
270 if (caps) {
271 bseg = select(path,0,1);
272 move(bseg[0])
273 rot(from=BACK, to=bseg[0]-bseg[1])
274 rotate_extrude(angle=180)
275 right_half(planar=true) children();
276 eseg = select(path,-2,-1);
277 move(eseg[1])
278 rot(from=BACK, to=eseg[1]-eseg[0])
279 rotate_extrude(angle=180)
280 right_half(planar=true) children();
281 }
282 }
283 union();
284 }
285}
286
287
288// Module: cylindrical_extrude()
289// Synopsis: Extrudes 2D children outwards around a cylinder.
290// SynTags: Geom
291// Topics: Mutators, Extrusion, Rotation
292// See Also: heightfield(), cylindrical_heightfield(), cyl()
293// Usage:
294// cylindrical_extrude(ir|id=, or|od=, [size=], [convexity=], [spin=], [orient=]) 2D-CHILDREN;
295// Description:
296// Extrudes its 2D children outwards, curved around a cylindrical shape. Uses $fn/$fa/$fs to
297// control the faceting of the extrusion.
298// Arguments:
299// ir = The inner radius to extrude from.
300// or = The outer radius to extrude to.
301// ---
302// od = The outer diameter to extrude to.
303// id = The inner diameter to extrude from.
304// size = The [X,Y] size of the 2D children to extrude. Default: [1000,1000]
305// convexity = The max number of times a line could pass though a wall. Default: 10
306// spin = Amount in degrees to spin around cylindrical axis. Default: 0
307// orient = The orientation of the cylinder to wrap around, given as a vector. Default: UP
308// Example:
309// cylindrical_extrude(or=50, ir=45)
310// text(text="Hello World!", size=10, halign="center", valign="center");
311// Example: Spin Around the Cylindrical Axis
312// cylindrical_extrude(or=50, ir=45, spin=90)
313// text(text="Hello World!", size=10, halign="center", valign="center");
314// Example: Orient to the Y Axis.
315// cylindrical_extrude(or=40, ir=35, orient=BACK)
316// text(text="Hello World!", size=10, halign="center", valign="center");
317module cylindrical_extrude(ir, or, od, id, size=1000, convexity=10, spin=0, orient=UP) {
318 req_children($children);
319 check1 = assert(is_num(size) || is_vector(size,2));
320 size = is_num(size)? [size,size] : size;
321 ir = get_radius(r=ir,d=id);
322 or = get_radius(r=or,d=od);
323 check2 = assert(all_positive([ir,or]), "Must supply positive inner and outer radius or diameter");
324 index_r = or;
325 circumf = 2 * PI * index_r;
326 width = min(size.x, circumf);
327 check3 = assert(width <= circumf, "Shape would more than completely wrap around.");
328 sides = segs(or);
329 step = circumf / sides;
330 steps = ceil(width / step);
331 attachable() {
332 rot(from=UP, to=orient) rot(spin) {
333 for (i=[0:1:steps-2]) {
334 x = (i+0.5-steps/2) * step;
335 zrot(360 * x / circumf) {
336 fwd(or*cos(180/sides)) {
337 xrot(-90) {
338 linear_extrude(height=or-ir, scale=[ir/or,1], center=false, convexity=convexity) {
339 yflip()
340 intersection() {
341 left(x) children();
342 rect([quantup(step,pow(2,-15)),size.y]);
343 }
344 }
345 }
346 }
347 }
348 }
349 }
350 union();
351 }
352}
353
354
355// Module: extrude_from_to()
356// Synopsis: Extrudes 2D children between two points in 3D space.
357// SynTags: Geom
358// Topics: Extrusion, Mutators
359// See Also: path_sweep(), path_extrude2d()
360// Usage:
361// extrude_from_to(pt1, pt2, [convexity=], [twist=], [scale=], [slices=]) 2D-CHILDREN;
362// Description:
363// Extrudes the 2D children linearly between the 3d points pt1 and pt2. The origin of the 2D children are placed on
364// pt1 and pt2, and oriented perpendicular to the line between the points.
365// Arguments:
366// pt1 = starting point of extrusion.
367// pt2 = ending point of extrusion.
368// ---
369// convexity = max number of times a line could intersect a wall of the 2D shape being extruded.
370// twist = number of degrees to twist the 2D shape over the entire extrusion length.
371// scale = scale multiplier for end of extrusion compared the start.
372// slices = Number of slices along the extrusion to break the extrusion into. Useful for refining `twist` extrusions.
373// Example(FlatSpin,VPD=200,VPT=[0,0,15]):
374// extrude_from_to([0,0,0], [10,20,30], convexity=4, twist=360, scale=3.0, slices=40) {
375// xcopies(3) circle(3, $fn=32);
376// }
377module extrude_from_to(pt1, pt2, convexity, twist, scale, slices) {
378 req_children($children);
379 check =
380 assert(is_vector(pt1),"First point must be a vector")
381 assert(is_vector(pt2),"Second point must be a vector");
382 pt1 = point3d(pt1);
383 pt2 = point3d(pt2);
384 rtp = xyz_to_spherical(pt2-pt1);
385 attachable()
386 {
387 translate(pt1) {
388 rotate([0, rtp[2], rtp[1]]) {
389 if (rtp[0] > 0) {
390 linear_extrude(height=rtp[0], convexity=convexity, center=false, slices=slices, twist=twist, scale=scale) {
391 children();
392 }
393 }
394 }
395 }
396 union();
397 }
398}
399
400
401
402// Module: path_extrude()
403// Synopsis: Extrudes 2D children along a 3D path.
404// SynTags: Geom
405// Topics: Paths, Extrusion, Mutators
406// See Also: path_sweep(), path_extrude2d()
407// Usage:
408// path_extrude(path, [convexity], [clipsize]) 2D-CHILDREN;
409// Description:
410// Extrudes 2D children along a 3D path. This may be slow and can have problems with twisting.
411// Arguments:
412// path = Array of points for the bezier path to extrude along.
413// convexity = Maximum number of walls a ray can pass through.
414// clipsize = Increase if artifacts are left. Default: 100
415// Example(FlatSpin,VPD=600,VPT=[75,16,20]):
416// path = [ [0, 0, 0], [33, 33, 33], [66, 33, 40], [100, 0, 0], [150,0,0] ];
417// path_extrude(path) circle(r=10, $fn=6);
418module path_extrude(path, convexity=10, clipsize=100) {
419 req_children($children);
420 rotmats = cumprod([
421 for (i = idx(path,e=-2)) let(
422 vec1 = i==0? UP : unit(path[i]-path[i-1], UP),
423 vec2 = unit(path[i+1]-path[i], UP)
424 ) rot(from=vec1,to=vec2)
425 ]);
426 // This adds a rotation midway between each item on the list
427 interp = rot_resample(rotmats,n=2,method="count");
428 epsilon = 0.0001; // Make segments ever so slightly too long so they overlap.
429 ptcount = len(path);
430 attachable(){
431 for (i = [0:1:ptcount-2]) {
432 pt1 = path[i];
433 pt2 = path[i+1];
434 dist = norm(pt2-pt1);
435 T = rotmats[i];
436 difference() {
437 translate(pt1) {
438 multmatrix(T) {
439 down(clipsize/2/2) {
440 if ((dist+clipsize/2) > 0) {
441 linear_extrude(height=dist+clipsize/2, convexity=convexity) {
442 children();
443 }
444 }
445 }
446 }
447 }
448 translate(pt1) {
449 hq = (i > 0)? interp[2*i-1] : T;
450 multmatrix(hq) down(clipsize/2+epsilon) cube(clipsize, center=true);
451 }
452 translate(pt2) {
453 hq = (i < ptcount-2)? interp[2*i+1] : T;
454 multmatrix(hq) up(clipsize/2+epsilon) cube(clipsize, center=true);
455 }
456 }
457 }
458 union();
459 }
460}
461
462
463
464
465//////////////////////////////////////////////////////////////////////
466// Section: Offset Mutators
467//////////////////////////////////////////////////////////////////////
468
469// Module: minkowski_difference()
470// Synopsis: Removes diff shapes from base shape surface.
471// SynTags: Geom
472// Topics: Mutators
473// See Also: offset3d()
474// Usage:
475// minkowski_difference() { BASE; DIFF1; DIFF2; ... }
476// Description:
477// Takes a 3D base shape and one or more 3D diff shapes, carves out the diff shapes from the
478// surface of the base shape, in a way complementary to how `minkowski()` unions shapes to the
479// surface of its base shape.
480// Arguments:
481// planar = If true, performs minkowski difference in 2D. Default: false (3D)
482// Example:
483// minkowski_difference() {
484// union() {
485// cube([120,70,70], center=true);
486// cube([70,120,70], center=true);
487// cube([70,70,120], center=true);
488// }
489// sphere(r=10);
490// }
491module minkowski_difference(planar=false) {
492 req_children($children);
493 attachable(){
494 difference() {
495 bounding_box(excess=0, planar=planar) children(0);
496 render(convexity=20) {
497 minkowski() {
498 difference() {
499 bounding_box(excess=1, planar=planar) children(0);
500 children(0);
501 }
502 for (i=[1:1:$children-1]) children(i);
503 }
504 }
505 }
506 union();
507 }
508}
509
510
511
512
513// Module: offset3d()
514// Synopsis: Expands or contracts the surface of a 3D object.
515// SynTags: Geom
516// Topics: Mutators
517// See Also: minkowski_difference(), round3d()
518// Usage:
519// offset3d(r, [size], [convexity]) CHILDREN;
520// Description:
521// Expands or contracts the surface of a 3D object by a given amount. This is very, very slow.
522// No really, this is unbearably slow. It uses `minkowski()`. Use this as a last resort.
523// This is so slow that no example images will be rendered.
524// Arguments:
525// r = Radius to expand object by. Negative numbers contract the object.
526// size = Maximum size of object to be contracted, given as a scalar. Default: 100
527// convexity = Max number of times a line could intersect the walls of the object. Default: 10
528module offset3d(r, size=100, convexity=10) {
529 req_children($children);
530 n = quant(max(8,segs(abs(r))),4);
531 attachable(){
532 if (r==0) {
533 children();
534 } else if (r>0) {
535 render(convexity=convexity)
536 minkowski() {
537 children();
538 sphere(r, $fn=n);
539 }
540 } else {
541 size2 = size * [1,1,1];
542 size1 = size2 * 1.02;
543 render(convexity=convexity)
544 difference() {
545 cube(size2, center=true);
546 minkowski() {
547 difference() {
548 cube(size1, center=true);
549 children();
550 }
551 sphere(-r, $fn=n);
552 }
553 }
554 }
555 union();
556 }
557}
558
559
560// Module: round3d()
561// Synopsis: Rounds arbitrary 3d objects.
562// SynTags: Geom
563// Topics: Rounding, Mutators
564// See Also: offset3d(), minkowski_difference()
565// Usage:
566// round3d(r) CHILDREN;
567// round3d(or) CHILDREN;
568// round3d(ir) CHILDREN;
569// round3d(or, ir) CHILDREN;
570// Description:
571// Rounds arbitrary 3D objects. Giving `r` rounds all concave and convex corners. Giving just `ir`
572// rounds just concave corners. Giving just `or` rounds convex corners. Giving both `ir` and `or`
573// can let you round to different radii for concave and convex corners. The 3D object must not have
574// any parts narrower than twice the `or` radius. Such parts will disappear. This is an *extremely*
575// slow operation. I cannot emphasize enough just how slow it is. It uses `minkowski()` multiple times.
576// Use this as a last resort. This is so slow that no example images will be rendered.
577// Arguments:
578// r = Radius to round all concave and convex corners to.
579// or = Radius to round only outside (convex) corners to. Use instead of `r`.
580// ir = Radius to round only inside (concave) corners to. Use instead of `r`.
581module round3d(r, or, ir, size=100)
582{
583 req_children($children);
584 or = get_radius(r1=or, r=r, dflt=0);
585 ir = get_radius(r1=ir, r=r, dflt=0);
586 attachable(){
587 offset3d(or, size=size)
588 offset3d(-ir-or, size=size)
589 offset3d(ir, size=size)
590 children();
591 union();
592 }
593}
594
595
596
597// vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap