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