1//////////////////////////////////////////////////////////////////////
2// LibFile: shapes3d.scad
3// Some standard modules for making 3d shapes with attachment support, and function forms
4// that produce a VNF. Also included are shortcuts cylinders in each orientation and extended versions of
5// the standard modules that provide roundovers and chamfers. The spheroid() module provides
6// several different ways to make a sphere, and the text modules let you write text on a path
7// so you can place it on a curved object. A ruler lets you measure objects.
8// Includes:
9// include <BOSL2/std.scad>
10// FileGroup: Basic Modeling
11// FileSummary: Attachable cubes, cylinders, spheres, ruler, and text. Many can produce a VNF.
12// FileFootnotes: STD=Included in std.scad
13//////////////////////////////////////////////////////////////////////
14
15use <builtins.scad>
16
17
18// Section: Cuboids, Prismoids and Pyramids
19
20// Function&Module: cube()
21// Topics: Shapes (3D), Attachable, VNF Generators
22// Usage: As Module
23// cube(size, [center], ...);
24// Usage: With Attachments
25// cube(size, [center], ...) [ATTACHMENTS];
26// Usage: As Function
27// vnf = cube(size, [center], ...);
28// See Also: cuboid(), prismoid()
29// Description:
30// Creates a 3D cubic object with support for anchoring and attachments.
31// This can be used as a drop-in replacement for the built-in `cube()` module.
32// When called as a function, returns a [VNF](vnf.scad) for a cube.
33// Arguments:
34// size = The size of the cube.
35// center = If given, overrides `anchor`. A true value sets `anchor=CENTER`, false sets `anchor=FRONT+LEFT+BOTTOM`.
36// ---
37// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#subsection-anchor). Default: `CENTER`
38// spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#subsection-spin). Default: `0`
39// orient = Vector to rotate top towards, after spin. See [orient](attachments.scad#subsection-orient). Default: `UP`
40// Example: Simple cube.
41// cube(40);
42// Example: Rectangular cube.
43// cube([20,40,50]);
44// Example: Anchoring.
45// cube([20,40,50], anchor=BOTTOM+FRONT);
46// Example: Spin.
47// cube([20,40,50], anchor=BOTTOM+FRONT, spin=30);
48// Example: Orientation.
49// cube([20,40,50], anchor=BOTTOM+FRONT, spin=30, orient=FWD);
50// Example: Standard Connectors.
51// cube(40, center=true) show_anchors();
52// Example: Called as Function
53// vnf = cube([20,40,50]);
54// vnf_polyhedron(vnf);
55
56module cube(size=1, center, anchor, spin=0, orient=UP)
57{
58 anchor = get_anchor(anchor, center, -[1,1,1], -[1,1,1]);
59 size = scalar_vec3(size);
60 attachable(anchor,spin,orient, size=size) {
61 _cube(size, center=true);
62 children();
63 }
64}
65
66function cube(size=1, center, anchor, spin=0, orient=UP) =
67 let(
68 siz = scalar_vec3(size),
69 anchor = get_anchor(anchor, center, -[1,1,1], -[1,1,1]),
70 unscaled = [
71 [-1,-1,-1],[1,-1,-1],[1,1,-1],[-1,1,-1],
72 [-1,-1, 1],[1,-1, 1],[1,1, 1],[-1,1, 1],
73 ]/2,
74 verts = is_num(size)? unscaled * size :
75 is_vector(size,3)? [for (p=unscaled) v_mul(p,size)] :
76 assert(is_num(size) || is_vector(size,3)),
77 faces = [
78 [0,1,2], [0,2,3], //BOTTOM
79 [0,4,5], [0,5,1], //FRONT
80 [1,5,6], [1,6,2], //RIGHT
81 [2,6,7], [2,7,3], //BACK
82 [3,7,4], [3,4,0], //LEFT
83 [6,4,7], [6,5,4] //TOP
84 ]
85 ) [reorient(anchor,spin,orient, size=siz, p=verts), faces];
86
87
88
89// Module: cuboid()
90//
91// Usage: Standard Cubes
92// cuboid(size, [anchor=], [spin=], [orient=]);
93// cuboid(size, p1=, ...);
94// cuboid(p1=, p2=, ...);
95// Usage: Chamfered Cubes
96// cuboid(size, [chamfer=], [edges=], [except=], [trimcorners=], ...);
97// Usage: Rounded Cubes
98// cuboid(size, [rounding=], [teardrop=], [edges=], [except=], [trimcorners=], ...);
99// Usage: Attaching children
100// cuboid(...) ATTACHMENTS;
101//
102// Description:
103// Creates a cube or cuboid object, with optional chamfering or rounding of edges and corners.
104// You cannot mix chamfering and rounding: just one edge treatment with the same size applies to all selected edges.
105// Negative chamfers and roundings can be applied to create external fillets, but they
106// only apply to edges around the top or bottom faces. If you specify an edge set other than "ALL"
107// with negative roundings or chamfers then you will get an error. See [Specifying Edges](attachments.scad#section-specifying-edges)
108// for information on how to specify edge sets.
109// Arguments:
110// size = The size of the cube, a number or length 3 vector.
111// ---
112// chamfer = Size of chamfer, inset from sides. Default: No chamfering.
113// rounding = Radius of the edge rounding. Default: No rounding.
114// edges = Edges to mask. See [Specifying Edges](attachments.scad#section-specifying-edges). Default: all edges.
115// except = Edges to explicitly NOT mask. See [Specifying Edges](attachments.scad#section-specifying-edges). Default: No edges.
116// trimcorners = If true, rounds or chamfers corners where three chamfered/rounded edges meet. Default: `true`
117// teardrop = If given as a number, rounding around the bottom edge of the cuboid won't exceed this many degrees from vertical. If true, the limit angle is 45 degrees. Default: `false`
118// p1 = Align the cuboid's corner at `p1`, if given. Forces `anchor=FRONT+LEFT+BOTTOM`.
119// p2 = If given with `p1`, defines the cornerpoints of the cuboid.
120// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#subsection-anchor). Default: `CENTER`
121// spin = Rotate this many degrees around the Z axis. See [spin](attachments.scad#subsection-spin). Default: `0`
122// orient = Vector to rotate top towards. See [orient](attachments.scad#subsection-orient). Default: `UP`
123// Example: Simple regular cube.
124// cuboid(40);
125// Example: Cube with minimum cornerpoint given.
126// cuboid(20, p1=[10,0,0]);
127// Example: Rectangular cube, with given X, Y, and Z sizes.
128// cuboid([20,40,50]);
129// Example: Cube by Opposing Corners.
130// cuboid(p1=[0,10,0], p2=[20,30,30]);
131// Example: Chamferred Edges and Corners.
132// cuboid([30,40,50], chamfer=5);
133// Example: Chamferred Edges, Untrimmed Corners.
134// cuboid([30,40,50], chamfer=5, trimcorners=false);
135// Example: Rounded Edges and Corners
136// cuboid([30,40,50], rounding=10);
137// Example(VPR=[100,0,25],VPD=180): Rounded Edges and Corners with Teardrop Bottoms
138// cuboid([30,40,50], rounding=10, teardrop=true);
139// Example: Rounded Edges, Untrimmed Corners
140// cuboid([30,40,50], rounding=10, trimcorners=false);
141// Example: Chamferring Selected Edges
142// cuboid(
143// [30,40,50], chamfer=5,
144// edges=[TOP+FRONT,TOP+RIGHT,FRONT+RIGHT],
145// $fn=24
146// );
147// Example: Rounding Selected Edges
148// cuboid(
149// [30,40,50], rounding=5,
150// edges=[TOP+FRONT,TOP+RIGHT,FRONT+RIGHT],
151// $fn=24
152// );
153// Example: Negative Chamferring
154// cuboid(
155// [30,40,50], chamfer=-5,
156// edges=[TOP,BOT], except=RIGHT,
157// $fn=24
158// );
159// Example: Negative Chamferring, Untrimmed Corners
160// cuboid(
161// [30,40,50], chamfer=-5,
162// edges=[TOP,BOT], except=RIGHT,
163// trimcorners=false, $fn=24
164// );
165// Example: Negative Rounding
166// cuboid(
167// [30,40,50], rounding=-5,
168// edges=[TOP,BOT], except=RIGHT,
169// $fn=24
170// );
171// Example: Negative Rounding, Untrimmed Corners
172// cuboid(
173// [30,40,50], rounding=-5,
174// edges=[TOP,BOT], except=RIGHT,
175// trimcorners=false, $fn=24
176// );
177// Example: Roundings and Chamfers can be as large as the full size of the cuboid, so long as the edges would not interfere.
178// cuboid([4,2,1], rounding=2, edges=[FWD+RIGHT,BACK+LEFT]);
179// Example: Standard Connectors
180// cuboid(40) show_anchors();
181
182module cuboid(
183 size=[1,1,1],
184 p1, p2,
185 chamfer,
186 rounding,
187 edges=EDGES_ALL,
188 except=[],
189 except_edges,
190 trimcorners=true,
191 teardrop=false,
192 anchor=CENTER,
193 spin=0,
194 orient=UP
195) {
196 module trunc_cube(s,corner) {
197 multmatrix(
198 (corner.x<0? xflip() : ident(4)) *
199 (corner.y<0? yflip() : ident(4)) *
200 (corner.z<0? zflip() : ident(4)) *
201 scale(s+[1,1,1]*0.001) *
202 move(-[1,1,1]/2)
203 ) polyhedron(
204 [[1,1,1],[1,1,0],[1,0,0],[0,1,1],[0,1,0],[1,0,1],[0,0,1]],
205 [[0,1,2],[2,5,0],[0,5,6],[0,6,3],[0,3,4],[0,4,1],[1,4,2],[3,6,4],[5,2,6],[2,4,6]]
206 );
207 }
208 module xtcyl(l,r) {
209 if (teardrop) {
210 teardrop(r=r, l=l, cap_h=r, ang=teardrop, spin=90, orient=DOWN);
211 } else {
212 yrot(90) cyl(l=l, r=r);
213 }
214 }
215 module ytcyl(l,r) {
216 if (teardrop) {
217 teardrop(r=r, l=l, cap_h=r, ang=teardrop, spin=0, orient=DOWN);
218 } else {
219 zrot(90) yrot(90) cyl(l=l, r=r);
220 }
221 }
222 module tsphere(r) {
223 if (teardrop) {
224 onion(r=r, cap_h=r, ang=teardrop, orient=DOWN);
225 } else {
226 spheroid(r=r, style="octa", orient=DOWN);
227 }
228 }
229 module corner_shape(corner) {
230 e = _corner_edges(edges, corner);
231 cnt = sum(e);
232 r = first_defined([chamfer, rounding]);
233 dummy = assert(is_finite(r) && !approx(r,0));
234 c = [r,r,r];
235 m = 0.01;
236 c2 = v_mul(corner,c/2);
237 c3 = v_mul(corner,c-[1,1,1]*m/2);
238 $fn = is_finite(chamfer)? 4 : quantup(segs(r),4);
239 translate(v_mul(corner, size/2-c)) {
240 if (cnt == 0 || approx(r,0)) {
241 translate(c3) cube(m, center=true);
242 } else if (cnt == 1) {
243 if (e.x) {
244 right(c3.x) {
245 intersection() {
246 xtcyl(l=m, r=r);
247 multmatrix(
248 (corner.y<0? yflip() : ident(4)) *
249 (corner.z<0? zflip() : ident(4))
250 ) {
251 yrot(-90) linear_extrude(height=m+0.1, center=true) {
252 polygon([[r,0],[0.999*r,0],[0,0.999*r],[0,r],[r,r]]);
253 }
254 }
255 }
256 }
257 } else if (e.y) {
258 back(c3.y) {
259 intersection() {
260 ytcyl(l=m, r=r);
261 multmatrix(
262 (corner.x<0? xflip() : ident(4)) *
263 (corner.z<0? zflip() : ident(4))
264 ) {
265 xrot(90) linear_extrude(height=m+0.1, center=true) {
266 polygon([[r,0],[0.999*r,0],[0,0.999*r],[0,r],[r,r]]);
267 }
268 }
269 }
270 }
271 } else if (e.z) {
272 up(c3.z) {
273 intersection() {
274 zcyl(l=m, r=r);
275 multmatrix(
276 (corner.x<0? xflip() : ident(4)) *
277 (corner.y<0? yflip() : ident(4))
278 ) {
279 linear_extrude(height=m+0.1, center=true) {
280 polygon([[r,0],[0.999*r,0],[0,0.999*r],[0,r],[r,r]]);
281 }
282 }
283 }
284 }
285 }
286 } else if (cnt == 2) {
287 intersection() {
288 if (!e.x) {
289 intersection() {
290 ytcyl(l=c.y*2, r=r);
291 zcyl(l=c.z*2, r=r);
292 }
293 } else if (!e.y) {
294 intersection() {
295 xtcyl(l=c.x*2, r=r);
296 zcyl(l=c.z*2, r=r);
297 }
298 } else {
299 intersection() {
300 xtcyl(l=c.x*2, r=r);
301 ytcyl(l=c.y*2, r=r);
302 }
303 }
304 translate(c2) trunc_cube(c,corner); // Trim to just the octant.
305 }
306 } else {
307 intersection() {
308 if (trimcorners) {
309 tsphere(r=r);
310 } else {
311 intersection() {
312 xtcyl(l=c.x*2, r=r);
313 ytcyl(l=c.y*2, r=r);
314 zcyl(l=c.z*2, r=r);
315 }
316 }
317 translate(c2) trunc_cube(c,corner); // Trim to just the octant.
318 }
319 }
320 }
321 }
322
323 size = scalar_vec3(size);
324 edges = _edges(edges, except=first_defined([except_edges,except]));
325 teardrop = is_bool(teardrop)&&teardrop? 45 : teardrop;
326 chamfer = approx(chamfer,0) ? undef : chamfer;
327 rounding = approx(rounding,0) ? undef : rounding;
328 checks =
329 assert(is_vector(size,3))
330 assert(all_positive(size))
331 assert(is_undef(chamfer) || is_finite(chamfer),"chamfer must be a finite value")
332 assert(is_undef(rounding) || is_finite(rounding),"rounding must be a finite value")
333 assert(is_undef(rounding) || is_undef(chamfer), "Cannot specify nonzero value for both chamfer and rounding")
334 assert(teardrop==false || (is_finite(teardrop) && teardrop>0 && teardrop<90), "teardrop must be either false or an angle number between 0 and 90")
335 assert(is_undef(p1) || is_vector(p1))
336 assert(is_undef(p2) || is_vector(p2))
337 assert(is_bool(trimcorners));
338 if (!is_undef(p1)) {
339 if (!is_undef(p2)) {
340 translate(pointlist_bounds([p1,p2])[0]) {
341 cuboid(size=v_abs(p2-p1), chamfer=chamfer, rounding=rounding, edges=edges, trimcorners=trimcorners, anchor=-[1,1,1]) children();
342 }
343 } else {
344 translate(p1) {
345 cuboid(size=size, chamfer=chamfer, rounding=rounding, edges=edges, trimcorners=trimcorners, anchor=-[1,1,1]) children();
346 }
347 }
348 } else {
349 rr = max(default(chamfer,0), default(rounding,0));
350 if (rr>0) {
351 minx = max(
352 edges.y[0] + edges.y[1], edges.y[2] + edges.y[3],
353 edges.z[0] + edges.z[1], edges.z[2] + edges.z[3],
354 edges.y[0] + edges.z[1], edges.y[0] + edges.z[3],
355 edges.y[1] + edges.z[0], edges.y[1] + edges.z[2],
356 edges.y[2] + edges.z[1], edges.y[2] + edges.z[3],
357 edges.y[3] + edges.z[0], edges.y[3] + edges.z[2]
358 ) * rr;
359 miny = max(
360 edges.x[0] + edges.x[1], edges.x[2] + edges.x[3],
361 edges.z[0] + edges.z[2], edges.z[1] + edges.z[3],
362 edges.x[0] + edges.z[2], edges.x[0] + edges.z[3],
363 edges.x[1] + edges.z[0], edges.x[1] + edges.z[1],
364 edges.x[2] + edges.z[2], edges.x[2] + edges.z[3],
365 edges.x[3] + edges.z[0], edges.x[3] + edges.z[1]
366 ) * rr;
367 minz = max(
368 edges.x[0] + edges.x[2], edges.x[1] + edges.x[3],
369 edges.y[0] + edges.y[2], edges.y[1] + edges.y[3],
370 edges.x[0] + edges.y[2], edges.x[0] + edges.y[3],
371 edges.x[1] + edges.y[2], edges.x[1] + edges.y[3],
372 edges.x[2] + edges.y[0], edges.x[2] + edges.y[1],
373 edges.x[3] + edges.y[0], edges.x[3] + edges.y[1]
374 ) * rr;
375 check =
376 assert(minx <= size.x, "Rounding or chamfering too large for cuboid size in the X axis.")
377 assert(miny <= size.y, "Rounding or chamfering too large for cuboid size in the Y axis.")
378 assert(minz <= size.z, "Rounding or chamfering too large for cuboid size in the Z axis.")
379 ;
380 }
381 majrots = [[0,90,0], [90,0,0], [0,0,0]];
382 attachable(anchor,spin,orient, size=size) {
383 if (is_finite(chamfer) && !approx(chamfer,0)) {
384 if (edges == EDGES_ALL && trimcorners) {
385 if (chamfer<0) {
386 cube(size, center=true) {
387 attach(TOP,overlap=0) prismoid([size.x,size.y], [size.x-2*chamfer,size.y-2*chamfer], h=-chamfer, anchor=TOP);
388 attach(BOT,overlap=0) prismoid([size.x,size.y], [size.x-2*chamfer,size.y-2*chamfer], h=-chamfer, anchor=TOP);
389 }
390 } else {
391 isize = [for (v = size) max(0.001, v-2*chamfer)];
392 hull() {
393 cube([ size.x, isize.y, isize.z], center=true);
394 cube([isize.x, size.y, isize.z], center=true);
395 cube([isize.x, isize.y, size.z], center=true);
396 }
397 }
398 } else if (chamfer<0) {
399 checks = assert(edges == EDGES_ALL || edges[2] == [0,0,0,0], "Cannot use negative chamfer with Z aligned edges.");
400 ach = abs(chamfer);
401 cube(size, center=true);
402
403 // External-Chamfer mask edges
404 difference() {
405 union() {
406 for (i = [0:3], axis=[0:1]) {
407 if (edges[axis][i]>0) {
408 vec = EDGE_OFFSETS[axis][i];
409 translate(v_mul(vec/2, size+[ach,ach,-ach])) {
410 rotate(majrots[axis]) {
411 cube([ach, ach, size[axis]], center=true);
412 }
413 }
414 }
415 }
416
417 // Add multi-edge corners.
418 if (trimcorners) {
419 for (za=[-1,1], ya=[-1,1], xa=[-1,1]) {
420 ce = _corner_edges(edges, [xa,ya,za]);
421 if (ce.x + ce.y > 1) {
422 translate(v_mul([xa,ya,za]/2, size+[ach-0.01,ach-0.01,-ach])) {
423 cube([ach+0.01,ach+0.01,ach], center=true);
424 }
425 }
426 }
427 }
428 }
429
430 // Remove bevels from overhangs.
431 for (i = [0:3], axis=[0:1]) {
432 if (edges[axis][i]>0) {
433 vec = EDGE_OFFSETS[axis][i];
434 translate(v_mul(vec/2, size+[2*ach,2*ach,-2*ach])) {
435 rotate(majrots[axis]) {
436 zrot(45) cube([ach*sqrt(2), ach*sqrt(2), size[axis]+2.1*ach], center=true);
437 }
438 }
439 }
440 }
441 }
442 } else {
443 hull() {
444 corner_shape([-1,-1,-1]);
445 corner_shape([ 1,-1,-1]);
446 corner_shape([-1, 1,-1]);
447 corner_shape([ 1, 1,-1]);
448 corner_shape([-1,-1, 1]);
449 corner_shape([ 1,-1, 1]);
450 corner_shape([-1, 1, 1]);
451 corner_shape([ 1, 1, 1]);
452 }
453 }
454 } else if (is_finite(rounding) && !approx(rounding,0)) {
455 sides = quantup(segs(rounding),4);
456 if (edges == EDGES_ALL) {
457 if(rounding<0) {
458 cube(size, center=true);
459 zflip_copy() {
460 up(size.z/2) {
461 difference() {
462 down(-rounding/2) cube([size.x-2*rounding, size.y-2*rounding, -rounding], center=true);
463 down(-rounding) {
464 ycopies(size.y-2*rounding) xcyl(l=size.x-3*rounding, r=-rounding);
465 xcopies(size.x-2*rounding) ycyl(l=size.y-3*rounding, r=-rounding);
466 }
467 }
468 }
469 }
470 } else {
471 isize = [for (v = size) max(0.001, v-2*rounding)];
472 minkowski() {
473 cube(isize, center=true);
474 if (trimcorners) {
475 tsphere(r=rounding, $fn=sides);
476 } else {
477 intersection() {
478 xtcyl(r=rounding, l=rounding*2, $fn=sides);
479 ytcyl(r=rounding, l=rounding*2, $fn=sides);
480 cyl(r=rounding, h=rounding*2, $fn=sides);
481 }
482 }
483 }
484 }
485 } else if (rounding<0) {
486 checks = assert(edges == EDGES_ALL || edges[2] == [0,0,0,0], "Cannot use negative rounding with Z aligned edges.");
487 ard = abs(rounding);
488 cube(size, center=true);
489
490 // External-Rounding mask edges
491 difference() {
492 union() {
493 for (i = [0:3], axis=[0:1]) {
494 if (edges[axis][i]>0) {
495 vec = EDGE_OFFSETS[axis][i];
496 translate(v_mul(vec/2, size+[ard,ard,-ard]-[0.01,0.01,0])) {
497 rotate(majrots[axis]) {
498 cube([ard, ard, size[axis]], center=true);
499 }
500 }
501 }
502 }
503
504 // Add multi-edge corners.
505 if (trimcorners) {
506 for (za=[-1,1], ya=[-1,1], xa=[-1,1]) {
507 ce = _corner_edges(edges, [xa,ya,za]);
508 if (ce.x + ce.y > 1) {
509 translate(v_mul([xa,ya,za]/2, size+[ard-0.01,ard-0.01,-ard])) {
510 cube([ard+0.01,ard+0.01,ard], center=true);
511 }
512 }
513 }
514 }
515 }
516
517 // Remove roundings from overhangs.
518 for (i = [0:3], axis=[0:1]) {
519 if (edges[axis][i]>0) {
520 vec = EDGE_OFFSETS[axis][i];
521 translate(v_mul(vec/2, size+[2*ard,2*ard,-2*ard])) {
522 rotate(majrots[axis]) {
523 cyl(l=size[axis]+2.1*ard, r=ard);
524 }
525 }
526 }
527 }
528 }
529 } else {
530 hull() {
531 corner_shape([-1,-1,-1]);
532 corner_shape([ 1,-1,-1]);
533 corner_shape([-1, 1,-1]);
534 corner_shape([ 1, 1,-1]);
535 corner_shape([-1,-1, 1]);
536 corner_shape([ 1,-1, 1]);
537 corner_shape([-1, 1, 1]);
538 corner_shape([ 1, 1, 1]);
539 }
540 }
541 } else {
542 cube(size=size, center=true);
543 }
544 children();
545 }
546 }
547}
548
549
550function cuboid(
551 size=[1,1,1],
552 p1, p2,
553 chamfer,
554 rounding,
555 edges=EDGES_ALL,
556 except_edges=[],
557 trimcorners=true,
558 anchor=CENTER,
559 spin=0,
560 orient=UP
561) = no_function("cuboid");
562
563
564
565// Function&Module: prismoid()
566//
567// Usage: Typical Prismoids
568// prismoid(size1, size2, h|l, [shift], ...) [ATTACHMENTS];
569// Usage: Chamfered Prismoids
570// prismoid(size1, size2, h|l, [chamfer=], ...) [ATTACHMENTS];
571// prismoid(size1, size2, h|l, [chamfer1=], [chamfer2=], ...) [ATTACHMENTS];
572// Usage: Rounded Prismoids
573// prismoid(size1, size2, h|l, [rounding=], ...) [ATTACHMENTS];
574// prismoid(size1, size2, h|l, [rounding1=], [rounding2=], ...) [ATTACHMENTS];
575// Usage: As Function
576// vnf = prismoid(size1, size2, h|l, [shift], [rounding], [chamfer]);
577// vnf = prismoid(size1, size2, h|l, [shift], [rounding1], [rounding2], [chamfer1], [chamfer2]);
578//
579// Description:
580// Creates a rectangular prismoid shape with optional roundovers and chamfering.
581// You can only round or chamfer the vertical(ish) edges. For those edges, you can
582// specify rounding and/or chamferring per-edge, and for top and bottom separately.
583// If you want to round the bottom or top edges see {{rounded_prism()}}.
584//
585// Arguments:
586// size1 = [width, length] of the bottom end of the prism.
587// size2 = [width, length] of the top end of the prism.
588// h/l = Height of the prism.
589// shift = [X,Y] amount to shift the center of the top end with respect to the center of the bottom end.
590// ---
591// rounding = The roundover radius for the vertical-ish edges of the prismoid. If given as a list of four numbers, gives individual radii for each corner, in the order [X+Y+,X-Y+,X-Y-,X+Y-]. Default: 0 (no rounding)
592// rounding1 = The roundover radius for the bottom of the vertical-ish edges of the prismoid. If given as a list of four numbers, gives individual radii for each corner, in the order [X+Y+,X-Y+,X-Y-,X+Y-].
593// rounding2 = The roundover radius for the top of the vertical-ish edges of the prismoid. If given as a list of four numbers, gives individual radii for each corner, in the order [X+Y+,X-Y+,X-Y-,X+Y-].
594// chamfer = The chamfer size for the vertical-ish edges of the prismoid. If given as a list of four numbers, gives individual chamfers for each corner, in the order [X+Y+,X-Y+,X-Y-,X+Y-]. Default: 0 (no chamfer)
595// chamfer1 = The chamfer size for the bottom of the vertical-ish edges of the prismoid. If given as a list of four numbers, gives individual chamfers for each corner, in the order [X+Y+,X-Y+,X-Y-,X+Y-].
596// chamfer2 = The chamfer size for the top of the vertical-ish edges of the prismoid. If given as a list of four numbers, gives individual chamfers for each corner, in the order [X+Y+,X-Y+,X-Y-,X+Y-].
597// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#subsection-anchor). Default: `CENTER`
598// spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#subsection-spin). Default: `0`
599// orient = Vector to rotate top towards, after spin. See [orient](attachments.scad#subsection-orient). Default: `UP`
600//
601// See Also: rounded_prism()
602//
603// Example: Rectangular Pyramid
604// prismoid([40,40], [0,0], h=20);
605// Example: Prism
606// prismoid(size1=[40,40], size2=[0,40], h=20);
607// Example: Truncated Pyramid
608// prismoid(size1=[35,50], size2=[20,30], h=20);
609// Example: Wedge
610// prismoid(size1=[60,35], size2=[30,0], h=30);
611// Example: Truncated Tetrahedron
612// prismoid(size1=[10,40], size2=[40,10], h=40);
613// Example: Inverted Truncated Pyramid
614// prismoid(size1=[15,5], size2=[30,20], h=20);
615// Example: Right Prism
616// prismoid(size1=[30,60], size2=[0,60], shift=[-15,0], h=30);
617// Example(FlatSpin,VPD=160,VPT=[0,0,10]): Shifting/Skewing
618// prismoid(size1=[50,30], size2=[20,20], h=20, shift=[15,5]);
619// Example: Rounding
620// prismoid(100, 80, rounding=10, h=30);
621// Example: Outer Chamfer Only
622// prismoid(100, 80, chamfer=5, h=30);
623// Example: Gradiant Rounding
624// prismoid(100, 80, rounding1=10, rounding2=0, h=30);
625// Example: Per Corner Rounding
626// prismoid(100, 80, rounding=[0,5,10,15], h=30);
627// Example: Per Corner Chamfer
628// prismoid(100, 80, chamfer=[0,5,10,15], h=30);
629// Example: Mixing Chamfer and Rounding
630// prismoid(
631// 100, 80, h=30,
632// chamfer=[0,5,0,10],
633// rounding=[5,0,10,0]
634// );
635// Example: Really Mixing It Up
636// prismoid(
637// size1=[100,80], size2=[80,60], h=20,
638// chamfer1=[0,5,0,10], chamfer2=[5,0,10,0],
639// rounding1=[5,0,10,0], rounding2=[0,5,0,10]
640// );
641// Example(Spin,VPD=160,VPT=[0,0,10]): Standard Connectors
642// prismoid(size1=[50,30], size2=[20,20], h=20, shift=[15,5])
643// show_anchors();
644
645module prismoid(
646 size1, size2, h, shift=[0,0],
647 rounding=0, rounding1, rounding2,
648 chamfer=0, chamfer1, chamfer2,
649 l, center,
650 anchor, spin=0, orient=UP
651) {
652 checks =
653 assert(is_num(size1) || is_vector(size1,2))
654 assert(is_num(size2) || is_vector(size2,2))
655 assert(is_num(h) || is_num(l))
656 assert(is_vector(shift,2))
657 assert(is_num(rounding) || is_vector(rounding,4), "Bad rounding argument.")
658 assert(is_undef(rounding1) || is_num(rounding1) || is_vector(rounding1,4), "Bad rounding1 argument.")
659 assert(is_undef(rounding2) || is_num(rounding2) || is_vector(rounding2,4), "Bad rounding2 argument.")
660 assert(is_num(chamfer) || is_vector(chamfer,4), "Bad chamfer argument.")
661 assert(is_undef(chamfer1) || is_num(chamfer1) || is_vector(chamfer1,4), "Bad chamfer1 argument.")
662 assert(is_undef(chamfer2) || is_num(chamfer2) || is_vector(chamfer2,4), "Bad chamfer2 argument.");
663 eps = pow(2,-14);
664 size1 = is_num(size1)? [size1,size1] : size1;
665 size2 = is_num(size2)? [size2,size2] : size2;
666 checks2 =
667 assert(all_nonnegative(size1))
668 assert(all_nonnegative(size2))
669 assert(size1.x + size2.x > 0)
670 assert(size1.y + size2.y > 0);
671 s1 = [max(size1.x, eps), max(size1.y, eps)];
672 s2 = [max(size2.x, eps), max(size2.y, eps)];
673 rounding1 = default(rounding1, rounding);
674 rounding2 = default(rounding2, rounding);
675 chamfer1 = default(chamfer1, chamfer);
676 chamfer2 = default(chamfer2, chamfer);
677 anchor = get_anchor(anchor, center, BOT, BOT);
678 vnf = prismoid(
679 size1=size1, size2=size2, h=h, shift=shift,
680 rounding1=rounding1, rounding2=rounding2,
681 chamfer1=chamfer1, chamfer2=chamfer2,
682 l=l, center=CENTER
683 );
684 attachable(anchor,spin,orient, size=[s1.x,s1.y,h], size2=s2, shift=shift) {
685 vnf_polyhedron(vnf, convexity=4);
686 children();
687 }
688}
689
690function prismoid(
691 size1, size2, h, shift=[0,0],
692 rounding=0, rounding1, rounding2,
693 chamfer=0, chamfer1, chamfer2,
694 l, center,
695 anchor=DOWN, spin=0, orient=UP
696) =
697 assert(is_vector(size1,2))
698 assert(is_vector(size2,2))
699 assert(is_num(h) || is_num(l))
700 assert(is_vector(shift,2))
701 assert(
702 (is_num(rounding) && rounding>=0) ||
703 (is_vector(rounding,4) && all_nonnegative(rounding)),
704 "Bad rounding argument."
705 )
706 assert(
707 is_undef(rounding1) || (is_num(rounding1) && rounding1>=0) ||
708 (is_vector(rounding1,4) && all_nonnegative(rounding1)),
709 "Bad rounding1 argument."
710 )
711 assert(
712 is_undef(rounding2) || (is_num(rounding2) && rounding2>=0) ||
713 (is_vector(rounding2,4) && all_nonnegative(rounding2)),
714 "Bad rounding2 argument."
715 )
716 assert(
717 (is_num(chamfer) && chamfer>=0) ||
718 (is_vector(chamfer,4) && all_nonnegative(chamfer)),
719 "Bad chamfer argument."
720 )
721 assert(
722 is_undef(chamfer1) || (is_num(chamfer1) && chamfer1>=0) ||
723 (is_vector(chamfer1,4) && all_nonnegative(chamfer1)),
724 "Bad chamfer1 argument."
725 )
726 assert(
727 is_undef(chamfer2) || (is_num(chamfer2) && chamfer2>=0) ||
728 (is_vector(chamfer2,4) && all_nonnegative(chamfer2)),
729 "Bad chamfer2 argument."
730 )
731 let(
732 eps = pow(2,-14),
733 h = first_defined([h,l,1]),
734 shiftby = point3d(point2d(shift)),
735 s1 = [max(size1.x, eps), max(size1.y, eps)],
736 s2 = [max(size2.x, eps), max(size2.y, eps)],
737 rounding1 = default(rounding1, rounding),
738 rounding2 = default(rounding2, rounding),
739 chamfer1 = default(chamfer1, chamfer),
740 chamfer2 = default(chamfer2, chamfer),
741 anchor = get_anchor(anchor, center, BOT, BOT),
742 vnf = (rounding1==0 && rounding2==0 && chamfer1==0 && chamfer2==0)? (
743 let(
744 corners = [[1,1],[1,-1],[-1,-1],[-1,1]] * 0.5,
745 points = [
746 for (p=corners) point3d(v_mul(s2,p), +h/2) + shiftby,
747 for (p=corners) point3d(v_mul(s1,p), -h/2)
748 ],
749 faces=[
750 [0,1,2], [0,2,3], [0,4,5], [0,5,1],
751 [1,5,6], [1,6,2], [2,6,7], [2,7,3],
752 [3,7,4], [3,4,0], [4,7,6], [4,6,5],
753 ]
754 ) [points, faces]
755 ) : (
756 let(
757 path1 = rect(size1, rounding=rounding1, chamfer=chamfer1, anchor=CTR),
758 path2 = rect(size2, rounding=rounding2, chamfer=chamfer2, anchor=CTR),
759 points = [
760 each path3d(path1, -h/2),
761 each path3d(move(shiftby, p=path2), +h/2),
762 ],
763 faces = hull(points)
764 ) [points, faces]
765 )
766 ) reorient(anchor,spin,orient, size=[s1.x,s1.y,h], size2=s2, shift=shift, p=vnf);
767
768
769// Function&Module: octahedron()
770// Usage: As Module
771// octahedron(size, ...) [ATTACHMENTS];
772// Usage: As Function
773// vnf = octahedron(size, ...);
774// Description:
775// When called as a module, creates an octahedron with axis-aligned points.
776// When called as a function, creates a [[VNF|vnf.scad]] of an octahedron with axis-aligned points.
777// Arguments:
778// size = Width of the octahedron, tip to tip.
779// ---
780// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#subsection-anchor). Default: `CENTER`
781// spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#subsection-spin). Default: `0`
782// orient = Vector to rotate top towards, after spin. See [orient](attachments.scad#subsection-orient). Default: `UP`
783// Example:
784// octahedron(size=40);
785// Example: Anchors
786// octahedron(size=40) show_anchors();
787
788module octahedron(size=1, anchor=CENTER, spin=0, orient=UP) {
789 vnf = octahedron(size=size);
790 attachable(anchor,spin,orient, vnf=vnf, extent=true) {
791 vnf_polyhedron(vnf, convexity=2);
792 children();
793 }
794}
795
796function octahedron(size=1, anchor=CENTER, spin=0, orient=UP) =
797 let(
798 size = scalar_vec3(size),
799 s = size/2,
800 vnf = [
801 [ [0,0,s.z], [s.x,0,0], [0,s.y,0], [-s.x,0,0], [0,-s.y,0], [0,0,-s.z] ],
802 [ [0,2,1], [0,3,2], [0,4,3], [0,1,4], [5,1,2], [5,2,3], [5,3,4], [5,4,1] ]
803 ]
804 ) reorient(anchor,spin,orient, vnf=vnf, extent=true, p=vnf);
805
806
807// Module: rect_tube()
808// Usage: Typical Rectangular Tubes
809// rect_tube(h, size, isize, [center], [shift]);
810// rect_tube(h, size, wall=, [center=]);
811// rect_tube(h, isize=, wall=, [center=]);
812// Usage: Tapering Rectangular Tubes
813// rect_tube(h, size1=, size2=, wall=, ...);
814// rect_tube(h, isize1=, isize2=, wall=, ...);
815// rect_tube(h, size1=, size2=, isize1=, isize2=, ...);
816// Usage: Chamfered
817// rect_tube(h, size, isize, chamfer=, ...);
818// rect_tube(h, size, isize, chamfer1=, chamfer2= ...);
819// rect_tube(h, size, isize, ichamfer=, ...);
820// rect_tube(h, size, isize, ichamfer1=, ichamfer2= ...);
821// rect_tube(h, size, isize, chamfer=, ichamfer=, ...);
822// Usage: Rounded
823// rect_tube(h, size, isize, rounding=, ...);
824// rect_tube(h, size, isize, rounding1=, rounding2= ...);
825// rect_tube(h, size, isize, irounding=, ...);
826// rect_tube(h, size, isize, irounding1=, irounding2= ...);
827// rect_tube(h, size, isize, rounding=, irounding=, ...);
828// Usage: Attaching Children
829// rect_tube(...) ATTACHMENTS;
830//
831// Description:
832// Creates a rectangular or prismoid tube with optional roundovers and/or chamfers.
833// You can only round or chamfer the vertical(ish) edges. For those edges, you can
834// specify rounding and/or chamferring per-edge, and for top and bottom, inside and
835// outside separately.
836// Arguments:
837// h/l = The height or length of the rectangular tube. Default: 1
838// size = The outer [X,Y] size of the rectangular tube.
839// isize = The inner [X,Y] size of the rectangular tube.
840// center = If given, overrides `anchor`. A true value sets `anchor=CENTER`, false sets `anchor=UP`.
841// shift = [X,Y] amount to shift the center of the top end with respect to the center of the bottom end.
842// ---
843// wall = The thickness of the rectangular tube wall.
844// size1 = The [X,Y] size of the outside of the bottom of the rectangular tube.
845// size2 = The [X,Y] size of the outside of the top of the rectangular tube.
846// isize1 = The [X,Y] size of the inside of the bottom of the rectangular tube.
847// isize2 = The [X,Y] size of the inside of the top of the rectangular tube.
848// rounding = The roundover radius for the outside edges of the rectangular tube.
849// rounding1 = The roundover radius for the outside bottom corner of the rectangular tube.
850// rounding2 = The roundover radius for the outside top corner of the rectangular tube.
851// chamfer = The chamfer size for the outside edges of the rectangular tube.
852// chamfer1 = The chamfer size for the outside bottom corner of the rectangular tube.
853// chamfer2 = The chamfer size for the outside top corner of the rectangular tube.
854// irounding = The roundover radius for the inside edges of the rectangular tube. Default: Same as `rounding`
855// irounding1 = The roundover radius for the inside bottom corner of the rectangular tube.
856// irounding2 = The roundover radius for the inside top corner of the rectangular tube.
857// ichamfer = The chamfer size for the inside edges of the rectangular tube. Default: Same as `chamfer`
858// ichamfer1 = The chamfer size for the inside bottom corner of the rectangular tube.
859// ichamfer2 = The chamfer size for the inside top corner of the rectangular tube.
860// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#subsection-anchor). Default: `BOTTOM`
861// spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#subsection-spin). Default: `0`
862// orient = Vector to rotate top towards, after spin. See [orient](attachments.scad#subsection-orient). Default: `UP`
863// Examples:
864// rect_tube(size=50, wall=5, h=30);
865// rect_tube(size=[100,60], wall=5, h=30);
866// rect_tube(isize=[60,80], wall=5, h=30);
867// rect_tube(size=[100,60], isize=[90,50], h=30);
868// rect_tube(size1=[100,60], size2=[70,40], wall=5, h=30);
869// Example:
870// rect_tube(
871// size1=[100,60], size2=[70,40],
872// isize1=[40,20], isize2=[65,35], h=15
873// );
874// Example: Outer Rounding Only
875// rect_tube(size=100, wall=5, rounding=10, irounding=0, h=30);
876// Example: Outer Chamfer Only
877// rect_tube(size=100, wall=5, chamfer=5, ichamfer=0, h=30);
878// Example: Outer Rounding, Inner Chamfer
879// rect_tube(size=100, wall=5, rounding=10, ichamfer=8, h=30);
880// Example: Inner Rounding, Outer Chamfer
881// rect_tube(size=100, wall=5, chamfer=10, irounding=8, h=30);
882// Example: Gradiant Rounding
883// rect_tube(
884// size1=100, size2=80, wall=5, h=30,
885// rounding1=10, rounding2=0,
886// irounding1=8, irounding2=0
887// );
888// Example: Per Corner Rounding
889// rect_tube(
890// size=100, wall=10, h=30,
891// rounding=[0,5,10,15], irounding=0
892// );
893// Example: Per Corner Chamfer
894// rect_tube(
895// size=100, wall=10, h=30,
896// chamfer=[0,5,10,15], ichamfer=0
897// );
898// Example: Mixing Chamfer and Rounding
899// rect_tube(
900// size=100, wall=10, h=30,
901// chamfer=[0,5,0,10], ichamfer=0,
902// rounding=[5,0,10,0], irounding=0
903// );
904// Example: Really Mixing It Up
905// rect_tube(
906// size1=[100,80], size2=[80,60],
907// isize1=[50,30], isize2=[70,50], h=20,
908// chamfer1=[0,5,0,10], ichamfer1=[0,3,0,8],
909// chamfer2=[5,0,10,0], ichamfer2=[3,0,8,0],
910// rounding1=[5,0,10,0], irounding1=[3,0,8,0],
911// rounding2=[0,5,0,10], irounding2=[0,3,0,8]
912// );
913
914module rect_tube(
915 h, size, isize, center, shift=[0,0],
916 wall, size1, size2, isize1, isize2,
917 rounding=0, rounding1, rounding2,
918 irounding=0, irounding1, irounding2,
919 chamfer=0, chamfer1, chamfer2,
920 ichamfer=0, ichamfer1, ichamfer2,
921 anchor, spin=0, orient=UP,
922 l
923) {
924 h = one_defined([h,l],"h,l");
925 checks =
926 assert(is_num(h), "l or h argument required.")
927 assert(is_vector(shift,2));
928 s1 = is_num(size1)? [size1, size1] :
929 is_vector(size1,2)? size1 :
930 is_num(size)? [size, size] :
931 is_vector(size,2)? size :
932 undef;
933 s2 = is_num(size2)? [size2, size2] :
934 is_vector(size2,2)? size2 :
935 is_num(size)? [size, size] :
936 is_vector(size,2)? size :
937 undef;
938 is1 = is_num(isize1)? [isize1, isize1] :
939 is_vector(isize1,2)? isize1 :
940 is_num(isize)? [isize, isize] :
941 is_vector(isize,2)? isize :
942 undef;
943 is2 = is_num(isize2)? [isize2, isize2] :
944 is_vector(isize2,2)? isize2 :
945 is_num(isize)? [isize, isize] :
946 is_vector(isize,2)? isize :
947 undef;
948 size1 = is_def(s1)? s1 :
949 (is_def(wall) && is_def(is1))? (is1+2*[wall,wall]) :
950 undef;
951 size2 = is_def(s2)? s2 :
952 (is_def(wall) && is_def(is2))? (is2+2*[wall,wall]) :
953 undef;
954 isize1 = is_def(is1)? is1 :
955 (is_def(wall) && is_def(s1))? (s1-2*[wall,wall]) :
956 undef;
957 isize2 = is_def(is2)? is2 :
958 (is_def(wall) && is_def(s2))? (s2-2*[wall,wall]) :
959 undef;
960 checks2 =
961 assert(wall==undef || is_num(wall))
962 assert(size1!=undef, "Bad size/size1 argument.")
963 assert(size2!=undef, "Bad size/size2 argument.")
964 assert(isize1!=undef, "Bad isize/isize1 argument.")
965 assert(isize2!=undef, "Bad isize/isize2 argument.")
966 assert(isize1.x < size1.x, "Inner size is larger than outer size.")
967 assert(isize1.y < size1.y, "Inner size is larger than outer size.")
968 assert(isize2.x < size2.x, "Inner size is larger than outer size.")
969 assert(isize2.y < size2.y, "Inner size is larger than outer size.");
970 anchor = get_anchor(anchor, center, BOT, BOT);
971 attachable(anchor,spin,orient, size=[each size1, h], size2=size2, shift=shift) {
972 down(h/2) {
973 difference() {
974 prismoid(
975 size1, size2, h=h, shift=shift,
976 rounding=rounding, rounding1=rounding1, rounding2=rounding2,
977 chamfer=chamfer, chamfer1=chamfer1, chamfer2=chamfer2,
978 anchor=BOT
979 );
980 down(0.01) prismoid(
981 isize1, isize2, h=h+0.02, shift=shift,
982 rounding=irounding, rounding1=irounding1, rounding2=irounding2,
983 chamfer=ichamfer, chamfer1=ichamfer1, chamfer2=ichamfer2,
984 anchor=BOT
985 );
986 }
987 }
988 children();
989 }
990}
991
992function rect_tube(
993 h, size, isize, center, shift=[0,0],
994 wall, size1, size2, isize1, isize2,
995 rounding=0, rounding1, rounding2,
996 irounding=0, irounding1, irounding2,
997 chamfer=0, chamfer1, chamfer2,
998 ichamfer=0, ichamfer1, ichamfer2,
999 anchor, spin=0, orient=UP,
1000 l
1001) = no_function("rect_tube");
1002
1003
1004// Function&Module: wedge()
1005//
1006// Usage: As Module
1007// wedge(size, [center], ...) [ATTACHMENTS];
1008// Usage: As Function
1009// vnf = wedge(size, [center], ...);
1010//
1011// Description:
1012// When called as a module, creates a 3D triangular wedge with the hypotenuse in the X+Z+ quadrant.
1013// When called as a function, creates a VNF for a 3D triangular wedge with the hypotenuse in the X+Z+ quadrant.
1014//
1015// Arguments:
1016// size = [width, thickness, height]
1017// center = If given, overrides `anchor`. A true value sets `anchor=CENTER`, false sets `anchor=UP`.
1018// ---
1019// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#subsection-anchor). Default: `FRONT+LEFT+BOTTOM`
1020// spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#subsection-spin). Default: `0`
1021// orient = Vector to rotate top towards, after spin. See [orient](attachments.scad#subsection-orient). Default: `UP`
1022//
1023// Example: Centered
1024// wedge([20, 40, 15], center=true);
1025// Example: *Non*-Centered
1026// wedge([20, 40, 15]);
1027// Example: Standard Connectors
1028// wedge([20, 40, 15]) show_anchors();
1029
1030module wedge(size=[1, 1, 1], center, anchor, spin=0, orient=UP)
1031{
1032 size = scalar_vec3(size);
1033 anchor = get_anchor(anchor, center, -[1,1,1], -[1,1,1]);
1034 vnf = wedge(size, center=true);
1035 attachable(anchor,spin,orient, size=size, size2=[size.x,0], shift=[0,-size.y/2]) {
1036 if (size.z > 0) {
1037 vnf_polyhedron(vnf);
1038 }
1039 children();
1040 }
1041}
1042
1043
1044function wedge(size=[1,1,1], center, anchor, spin=0, orient=UP) =
1045 let(
1046 size = scalar_vec3(size),
1047 anchor = get_anchor(anchor, center, -[1,1,1], -[1,1,1]),
1048 pts = [
1049 [ 1,1,-1], [ 1,-1,-1], [ 1,-1,1],
1050 [-1,1,-1], [-1,-1,-1], [-1,-1,1],
1051 ],
1052 faces = [
1053 [0,1,2], [3,5,4], [0,3,1], [1,3,4],
1054 [1,4,2], [2,4,5], [2,5,3], [0,2,3],
1055 ],
1056 vnf = [scale(size/2,p=pts), faces]
1057 )
1058 reorient(anchor,spin,orient, size=size, size2=[size.x,0], shift=[0,-size.y/2], p=vnf);
1059
1060
1061// Section: Cylinders
1062
1063
1064// Function&Module: cylinder()
1065// Topics: Shapes (3D), Attachable, VNF Generators
1066// Usage: As Module
1067// cylinder(h, r=/d=, [center=], ...) [ATTACHMENTS];
1068// cylinder(h, r1/d1=, r2/d2=, [center=], ...) [ATTACHMENTS];
1069// Usage: As Function
1070// vnf = cylinder(h, r=/d=, [center=], ...);
1071// vnf = cylinder(h, r1/d1=, r2/d2=, [center=], ...);
1072// See Also: cyl()
1073// Description:
1074// Creates a 3D cylinder or conic object with support for anchoring and attachments.
1075// This can be used as a drop-in replacement for the built-in `cylinder()` module.
1076// When called as a function, returns a [VNF](vnf.scad) for a cylinder.
1077// Arguments:
1078// l / h = The height of the cylinder.
1079// r1 = The bottom radius of the cylinder. (Before orientation.)
1080// r2 = The top radius of the cylinder. (Before orientation.)
1081// center = If given, overrides `anchor`. A true value sets `anchor=CENTER`, false sets `anchor=BOTTOM`.
1082// ---
1083// d1 = The bottom diameter of the cylinder. (Before orientation.)
1084// d2 = The top diameter of the cylinder. (Before orientation.)
1085// r = The radius of the cylinder.
1086// d = The diameter of the cylinder.
1087// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#subsection-anchor). Default: `CENTER`
1088// spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#subsection-spin). Default: `0`
1089// orient = Vector to rotate top towards, after spin. See [orient](attachments.scad#subsection-orient). Default: `UP`
1090// Example: By Radius
1091// xdistribute(30) {
1092// cylinder(h=40, r=10);
1093// cylinder(h=40, r1=10, r2=5);
1094// }
1095// Example: By Diameter
1096// xdistribute(30) {
1097// cylinder(h=40, d=25);
1098// cylinder(h=40, d1=25, d2=10);
1099// }
1100// Example(Med): Anchoring
1101// cylinder(h=40, r1=10, r2=5, anchor=BOTTOM+FRONT);
1102// Example(Med): Spin
1103// cylinder(h=40, r1=10, r2=5, anchor=BOTTOM+FRONT, spin=45);
1104// Example(Med): Orient
1105// cylinder(h=40, r1=10, r2=5, anchor=BOTTOM+FRONT, spin=45, orient=FWD);
1106// Example(Big): Standard Connectors
1107// xdistribute(40) {
1108// cylinder(h=30, d=25) show_anchors();
1109// cylinder(h=30, d1=25, d2=10) show_anchors();
1110// }
1111
1112module cylinder(h, r1, r2, center, l, r, d, d1, d2, anchor, spin=0, orient=UP)
1113{
1114 anchor = get_anchor(anchor, center, BOTTOM, BOTTOM);
1115 r1 = get_radius(r1=r1, r=r, d1=d1, d=d, dflt=1);
1116 r2 = get_radius(r1=r2, r=r, d1=d2, d=d, dflt=1);
1117 l = first_defined([h, l, 1]);
1118 attachable(anchor,spin,orient, r1=r1, r2=r2, l=l) {
1119 _cylinder(h=l, r1=r1, r2=r2, center=true);
1120 children();
1121 }
1122}
1123
1124function cylinder(h, r1, r2, center, l, r, d, d1, d2, anchor, spin=0, orient=UP) =
1125 let(
1126 anchor = get_anchor(anchor, center, BOTTOM, BOTTOM),
1127 r1 = get_radius(r1=r1, r=r, d1=d1, d=d, dflt=1),
1128 r2 = get_radius(r1=r2, r=r, d1=d2, d=d, dflt=1),
1129 l = first_defined([h, l, 1]),
1130 sides = segs(max(r1,r2)),
1131 verts = [
1132 for (i=[0:1:sides-1]) let(a=360*(1-i/sides)) [r1*cos(a),r1*sin(a),-l/2],
1133 for (i=[0:1:sides-1]) let(a=360*(1-i/sides)) [r2*cos(a),r2*sin(a), l/2],
1134 ],
1135 faces = [
1136 [for (i=[0:1:sides-1]) sides-1-i],
1137 for (i=[0:1:sides-1]) [i, ((i+1)%sides)+sides, i+sides],
1138 for (i=[0:1:sides-1]) [i, (i+1)%sides, ((i+1)%sides)+sides],
1139 [for (i=[0:1:sides-1]) sides+i]
1140 ]
1141 ) [reorient(anchor,spin,orient, l=l, r1=r1, r2=r2, p=verts), faces];
1142
1143
1144
1145// Function&Module: cyl()
1146//
1147// Usage: Normal Cylinders
1148// cyl(l|h, r, [center], [circum=], [realign=]) [ATTACHMENTS];
1149// cyl(l|h, d=, ...) [ATTACHMENTS];
1150// cyl(l|h, r1=, r2=, ...) [ATTACHMENTS];
1151// cyl(l|h, d1=, d2=, ...) [ATTACHMENTS];
1152//
1153// Usage: Chamferred Cylinders
1154// cyl(l|h, r|d, chamfer=, [chamfang=], [from_end=], ...);
1155// cyl(l|h, r|d, chamfer1=, [chamfang1=], [from_end=], ...);
1156// cyl(l|h, r|d, chamfer2=, [chamfang2=], [from_end=], ...);
1157// cyl(l|h, r|d, chamfer1=, chamfer2=, [chamfang1=], [chamfang2=], [from_end=], ...);
1158//
1159// Usage: Rounded End Cylinders
1160// cyl(l|h, r|d, rounding=, ...);
1161// cyl(l|h, r|d, rounding1=, ...);
1162// cyl(l|h, r|d, rounding2=, ...);
1163// cyl(l|h, r|d, rounding1=, rounding2=, ...);
1164//
1165// Usage: Textured Cylinders
1166// cyl(l|h, r|d, texture=, [tex_size=]|[tex_counts=], [tex_scale=], [tex_rot=], [tex_samples=], [tex_style=], [tex_taper=], [tex_inset=], ...);
1167// cyl(l|h, r1=, r2=, texture=, [tex_size=]|[tex_counts=], [tex_scale=], [tex_rot=], [tex_samples=], [tex_style=], [tex_taper=], [tex_inset=], ...);
1168// cyl(l|h, d1=, d2=, texture=, [tex_size=]|[tex_counts=], [tex_scale=], [tex_rot=], [tex_samples=], [tex_style=], [tex_taper=], [tex_inset=], ...);
1169//
1170// Topics: Cylinders, Textures, Rounding, Chamfers
1171//
1172// Description:
1173// Creates cylinders in various anchorings and orientations, with optional rounding, chamfers, or textures.
1174// You can use `h` and `l` interchangably, and all variants allow specifying size by either `r`|`d`,
1175// or `r1`|`d1` and `r2`|`d2`. Note: the chamfers and rounding cannot be cumulatively longer than
1176// the cylinder or cone's sloped side. The more specific parameters like chamfer1 or rounding2 override the more
1177// general ones like chamfer or rounding, so if you specify `rounding=3, chamfer2=3` you will get a chamfer at the top and
1178// rounding at the bottom.
1179// Figure(2D,Big,NoAxes,VPR = [0, 0, 0], VPT = [0,0,0], VPD = 82): Chamfers on cones can be tricky. This figure shows chamfers of the same size and same angle, A=30 degrees. Note that the angle is measured on the inside, and produces a quite different looking chamfer at the top and bottom of the cone. Straight black arrows mark the size of the chamfers, which may not even appear the same size visually. When you do not give an angle, the triangle that is cut off will be isoceles, like the triangle at the top, with two equal angles.
1180// color("lightgray")
1181// projection()
1182// cyl(r2=10, r1=20, l=20,chamfang=30, chamfer=0,orient=BACK);
1183// projection()
1184// cyl(r2=10, r1=20, l=20,chamfang=30, chamfer=8,orient=BACK);
1185// color("black"){
1186// fwd(9.6)right(20-4.8)text("A",size=1.3);
1187// fwd(-8.4)right(10-4.9)text("A",size=1.3);
1188// right(20-8)fwd(10.5)stroke([[0,0],[8,0]], endcaps="arrow2",width=.15);
1189// right(10-8)fwd(-10.5)stroke([[0,0],[8,0]], endcaps="arrow2",width=.15);
1190// stroke(arc(cp=[2,10], angle=[0,-30], n=20, r=5), width=.18, endcaps="arrow2");
1191// stroke(arc(cp=[12,-10], angle=[0,30], n=20, r=5), width=.18, endcaps="arrow2");
1192// }
1193// Figure(2D,Big,NoAxes,VPR = [0, 0, 0], VPT = [0,0,0], VPD = 82): The cone in this example is narrow but has the same slope. With negative chamfers, the angle A=30 degrees is on the outside. The chamfers are again quite different looking. As before, the default will feature two congruent angles, and in this case it happens at the bottom of the cone but not the top. The straight arrows again show the size of the chamfer.
1194// r1=10-7.5;r2=20-7.5;
1195// color("lightgray")
1196// projection()
1197// cyl(r2=r1, r1=r2, l=20,chamfang=30, chamfer=-8,orient=BACK);
1198// projection()
1199// cyl(r2=r1, r1=r2, l=20,chamfang=30, chamfer=0,orient=BACK);
1200// color("black"){
1201// fwd(9.7)right(r2+3.8)text("A",size=1.3);
1202// fwd(-8.5)right(r1+3.7)text("A",size=1.3);
1203// right(r2)fwd(10.5)stroke([[0,0],[8,0]], endcaps="arrow2",width=.15);
1204// right(r1)fwd(-10.5)stroke([[0,0],[8,0]], endcaps="arrow2",width=.15);
1205// stroke(arc(cp=[r1+8,10], angle=[180,180+30], n=20, r=5), width=.18, endcaps="arrow2");
1206// stroke(arc(cp=[r2+8,-10], angle=[180-30,180], n=20, r=5), width=.18, endcaps="arrow2");
1207// }
1208// Arguments:
1209// l / h = Length of cylinder along oriented axis. Default: 1
1210// r = Radius of cylinder. Default: 1
1211// center = If given, overrides `anchor`. A true value sets `anchor=CENTER`, false sets `anchor=DOWN`.
1212// ---
1213// r1 = Radius of the negative (X-, Y-, Z-) end of cylinder.
1214// r2 = Radius of the positive (X+, Y+, Z+) end of cylinder.
1215// d = Diameter of cylinder.
1216// d1 = Diameter of the negative (X-, Y-, Z-) end of cylinder.
1217// d2 = Diameter of the positive (X+, Y+, Z+) end of cylinder.
1218// circum = If true, cylinder should circumscribe the circle of the given size. Otherwise inscribes. Default: `false`
1219// shift = [X,Y] amount to shift the center of the top end with respect to the center of the bottom end.
1220// chamfer = The size of the chamfers on the ends of the cylinder. (Also see: `from_end=`) Default: none.
1221// chamfer1 = The size of the chamfer on the bottom end of the cylinder. (Also see: `from_end1=`) Default: none.
1222// chamfer2 = The size of the chamfer on the top end of the cylinder. (Also see: `from_end2=`) Default: none.
1223// chamfang = The angle in degrees of the chamfers away from the ends of the cylinder. Default: Chamfer angle is halfway between the endcap and cone face.
1224// chamfang1 = The angle in degrees of the bottom chamfer away from the bottom end of the cylinder. Default: Chamfer angle is halfway between the endcap and cone face.
1225// chamfang2 = The angle in degrees of the top chamfer away from the top end of the cylinder. Default: Chamfer angle is halfway between the endcap and cone face.
1226// from_end = If true, chamfer is measured along the conic face from the ends of the cylinder, instead of inset from the edge. Default: `false`.
1227// from_end1 = If true, chamfer on the bottom end of the cylinder is measured along the conic face from the end of the cylinder, instead of inset from the edge. Default: `false`.
1228// from_end2 = If true, chamfer on the top end of the cylinder is measured along the conic face from the end of the cylinder, instead of inset from the edge. Default: `false`.
1229// rounding = The radius of the rounding on the ends of the cylinder. Default: none.
1230// rounding1 = The radius of the rounding on the bottom end of the cylinder.
1231// rounding2 = The radius of the rounding on the top end of the cylinder.
1232// realign = If true, rotate the cylinder by half the angle of one face.
1233// texture = A texture name string, or a rectangular array of scalar height values (0.0 to 1.0), or a VNF tile that defines the texture to apply to vertical surfaces. See {{texture()}} for what named textures are supported.
1234// tex_size = An optional 2D target size for the textures. Actual texture sizes will be scaled somewhat to evenly fit the available surface. Default: `[5,5]`
1235// tex_counts = If given instead of tex_size, gives the tile repetition counts for textures over the surface length and height.
1236// tex_inset = If numeric, lowers the texture into the surface by that amount, before the tex_scale multiplier is applied. If `true`, insets by exactly `1`. Default: `false`
1237// tex_rot = If true, rotates the texture 90º.
1238// tex_scale = Scaling multiplier for the texture depth.
1239// tex_samples = Minimum number of "bend points" to have in VNF texture tiles. Default: 8
1240// tex_style = {{vnf_vertex_array()}} style used to triangulate heightfield textures. Default: "min_edge"
1241// tex_taper = If given as a number, tapers the texture height to zero over the first and last given percentage of the path. If given as a lookup table with indices between 0 and 100, uses the percentage lookup table to ramp the texture heights. Default: `undef` (no taper)
1242// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#subsection-anchor). Default: `CENTER`
1243// spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#subsection-spin). Default: `0`
1244// orient = Vector to rotate top towards, after spin. See [orient](attachments.scad#subsection-orient). Default: `UP`
1245//
1246// See Also: texture(), rotate_sweep()
1247//
1248// Example: By Radius
1249// xdistribute(30) {
1250// cyl(l=40, r=10);
1251// cyl(l=40, r1=10, r2=5);
1252// }
1253//
1254// Example: By Diameter
1255// xdistribute(30) {
1256// cyl(l=40, d=25);
1257// cyl(l=40, d1=25, d2=10);
1258// }
1259//
1260// Example: Chamferring
1261// xdistribute(60) {
1262// // Shown Left to right.
1263// cyl(l=40, d=40, chamfer=7); // Default chamfang=45
1264// cyl(l=40, d=40, chamfer=7, chamfang=30, from_end=false);
1265// cyl(l=40, d=40, chamfer=7, chamfang=30, from_end=true);
1266// }
1267//
1268// Example: Rounding
1269// cyl(l=40, d=40, rounding=10);
1270//
1271// Example: Heterogenous Chamfers and Rounding
1272// ydistribute(80) {
1273// // Shown Front to Back.
1274// cyl(l=40, d=40, rounding1=15, orient=UP);
1275// cyl(l=40, d=40, chamfer2=5, orient=UP);
1276// cyl(l=40, d=40, chamfer1=12, rounding2=10, orient=UP);
1277// }
1278//
1279// Example: Putting it all together
1280// cyl(
1281// l=20, d1=25, d2=15,
1282// chamfer1=5, chamfang1=60,
1283// from_end=true, rounding2=5
1284// );
1285//
1286// Example: External Chamfers
1287// cyl(l=50, r=30, chamfer=-5, chamfang=30, $fa=1, $fs=1);
1288//
1289// Example: External Roundings
1290// cyl(l=50, r=30, rounding1=-5, rounding2=5, $fa=1, $fs=1);
1291//
1292// Example(Med): Standard Connectors
1293// xdistribute(40) {
1294// cyl(l=30, d=25) show_anchors();
1295// cyl(l=30, d1=25, d2=10) show_anchors();
1296// }
1297//
1298// Example: Texturing with heightfield diamonds
1299// cyl(h=40, r=20, texture="diamonds", tex_size=[5,5]);
1300//
1301// Example: Texturing with heightfield pyramids
1302// cyl(h=40, r1=20, r2=15,
1303// texture="pyramids", tex_size=[5,5],
1304// tex_style="convex");
1305//
1306// Example: Texturing with heightfield truncated pyramids
1307// cyl(h=40, r1=20, r2=15, chamfer=5,
1308// texture="trunc_pyramids",
1309// tex_size=[5,5], tex_style="convex");
1310//
1311// Example: Texturing with VNF tile "dots"
1312// cyl(h=40, r1=20, r2=15, rounding=9,
1313// texture="dots", tex_size=[5,5],
1314// tex_samples=6);
1315//
1316// Example: Texturing with VNF tile "bricks_vnf"
1317// cyl(h=50, r1=25, r2=20, shift=[0,10], rounding1=-10,
1318// texture="bricks_vnf", tex_size=[10,10],
1319// tex_scale=0.5, tex_style="concave");
1320//
1321// Example: No Texture Taper
1322// cyl(d1=25, d2=20, h=30, rounding=5,
1323// texture="trunc_ribs", tex_size=[5,1]);
1324//
1325// Example: Taper Texure at Extreme Ends
1326// cyl(d1=25, d2=20, h=30, rounding=5,
1327// texture="trunc_ribs", tex_taper=0,
1328// tex_size=[5,1]);
1329//
1330// Example: Taper Texture over First and Last 10%
1331// cyl(d1=25, d2=20, h=30, rounding=5,
1332// texture="trunc_ribs", tex_taper=10,
1333// tex_size=[5,1]);
1334//
1335// Example: Making a Clay Pattern Roller
1336// tex = [
1337// [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,],
1338// [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,],
1339// [1,1,1,0,0,1,1,1,1,1,1,1,1,1,1,1,],
1340// [1,1,1,0,0,1,1,1,1,1,1,1,1,1,1,1,],
1341// [0,1,1,0,0,1,1,0,0,0,0,0,0,0,0,0,],
1342// [0,1,1,0,0,1,1,0,0,0,0,0,0,0,0,0,],
1343// [0,1,1,0,0,1,1,0,0,1,1,1,1,1,1,0,],
1344// [0,1,1,0,0,1,1,0,0,1,1,1,1,1,1,0,],
1345// [0,1,1,0,0,1,1,0,0,1,1,0,0,1,1,0,],
1346// [0,1,1,0,0,1,1,0,0,1,1,0,0,1,1,0,],
1347// [0,1,1,0,0,1,1,1,1,1,1,0,0,1,1,0,],
1348// [0,1,1,0,0,1,1,1,1,1,1,0,0,1,1,0,],
1349// [0,1,1,0,0,0,0,0,0,0,0,0,0,1,1,0,],
1350// [0,1,1,0,0,0,0,0,0,0,0,0,0,1,1,0,],
1351// [0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,],
1352// [0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,],
1353// [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,],
1354// ];
1355// diff()
1356// cyl(d=20*10/PI, h=10, chamfer=0,
1357// texture=tex, tex_counts=[20,1], tex_scale=-1,
1358// tex_taper=undef, tex_style="concave") {
1359// attach([TOP,BOT]) {
1360// cyl(d1=20*10/PI, d2=30, h=5, anchor=BOT)
1361// attach(TOP) {
1362// tag("remove") zscale(0.5) up(3) sphere(d=15);
1363// }
1364// }
1365// }
1366
1367function cyl(
1368 h, r, center,
1369 l, r1, r2,
1370 d, d1, d2,
1371 length, height,
1372 chamfer, chamfer1, chamfer2,
1373 chamfang, chamfang1, chamfang2,
1374 rounding, rounding1, rounding2,
1375 circum=false, realign=false, shift=[0,0],
1376 from_end, from_end1, from_end2,
1377 texture, tex_size=[5,5], tex_counts,
1378 tex_inset=false, tex_rot=false,
1379 tex_scale=1, tex_samples,
1380 tex_taper, tex_style="min_edge",
1381 anchor, spin=0, orient=UP
1382) =
1383 let(
1384 l = first_defined([l, h, length, height, 1]),
1385 _r1 = get_radius(r1=r1, r=r, d1=d1, d=d, dflt=1),
1386 _r2 = get_radius(r1=r2, r=r, d1=d2, d=d, dflt=1),
1387 sides = segs(max(_r1,_r2)),
1388 sc = circum? 1/cos(180/sides) : 1,
1389 r1 = _r1 * sc,
1390 r2 = _r2 * sc,
1391 phi = atan2(l, r2-r1),
1392 anchor = get_anchor(anchor,center,BOT,CENTER)
1393 )
1394 assert(is_finite(l), "l/h/length/height must be a finite number.")
1395 assert(is_finite(r1), "r/r1/d/d1 must be a finite number.")
1396 assert(is_finite(r2), "r2 or d2 must be a finite number.")
1397 assert(is_vector(shift,2), "shift must be a 2D vector.")
1398 let(
1399 vnf = !any_defined([chamfer, chamfer1, chamfer2, rounding, rounding1, rounding2, texture])
1400 ? cylinder(h=l, r1=r1, r2=r2, center=true, $fn=sides)
1401 : let(
1402 vang = atan2(r1-r2,l),
1403 _chamf1 = first_defined([chamfer1, if (is_undef(rounding1)) chamfer, 0]),
1404 _chamf2 = first_defined([chamfer2, if (is_undef(rounding2)) chamfer, 0]),
1405 _fromend1 = first_defined([from_end1, from_end, false]),
1406 _fromend2 = first_defined([from_end2, from_end, false]),
1407 chang1 = first_defined([chamfang1, chamfang, 45+sign(_chamf1)*vang/2]),
1408 chang2 = first_defined([chamfang2, chamfang, 45-sign(_chamf2)*vang/2]),
1409 round1 = first_defined([rounding1, if (is_undef(chamfer1)) rounding, 0]),
1410 round2 = first_defined([rounding2, if (is_undef(chamfer2)) rounding, 0]),
1411 checks1 =
1412 assert(is_finite(_chamf1), "chamfer1 must be a finite number if given.")
1413 assert(is_finite(_chamf2), "chamfer2 must be a finite number if given.")
1414 assert(is_finite(chang1) && chang1>0, "chamfang1 must be a positive number if given.")
1415 assert(is_finite(chang2) && chang2>0, "chamfang2 must be a positive number if given.")
1416 assert(chang1<90+sign(_chamf1)*vang, "chamfang1 must be smaller than the cone face angle")
1417 assert(chang2<90-sign(_chamf2)*vang, "chamfang2 must be smaller than the cone face angle")
1418 assert(num_defined([chamfer1,rounding1])<2, "cannot define both chamfer1 and rounding1")
1419 assert(num_defined([chamfer2,rounding2])<2, "cannot define both chamfer2 and rounding2")
1420 assert(num_defined([chamfer,rounding])<2, "cannot define both chamfer and rounding")
1421 undef,
1422 chamf1r = !_chamf1? 0
1423 : !_fromend1? _chamf1
1424 : law_of_sines(a=_chamf1, A=chang1, B=180-chang1-(90-sign(_chamf2)*vang)),
1425 chamf2r = !_chamf2? 0
1426 : !_fromend2? _chamf2
1427 : law_of_sines(a=_chamf2, A=chang2, B=180-chang2-(90+sign(_chamf2)*vang)),
1428 chamf1l = !_chamf1? 0
1429 : _fromend1? abs(_chamf1)
1430 : abs(law_of_sines(a=_chamf1, A=180-chang1-(90-sign(_chamf1)*vang), B=chang1)),
1431 chamf2l = !_chamf2? 0
1432 : _fromend2? abs(_chamf2)
1433 : abs(law_of_sines(a=_chamf2, A=180-chang2-(90+sign(_chamf2)*vang), B=chang2)),
1434 facelen = adj_ang_to_hyp(l, abs(vang)),
1435
1436 cp1 = [r1,-l/2],
1437 cp2 = [r2,+l/2],
1438 roundlen1 = round1 >= 0 ? round1/tan(45-vang/2)
1439 : round1/tan(45+vang/2),
1440 roundlen2 = round2 >=0 ? round2/tan(45+vang/2)
1441 : round2/tan(45-vang/2),
1442 dy1 = abs(_chamf1 ? chamf1l : round1 ? roundlen1 : 0),
1443 dy2 = abs(_chamf2 ? chamf2l : round2 ? roundlen2 : 0),
1444
1445 checks2 =
1446 assert(is_finite(round1), "rounding1 must be a number if given.")
1447 assert(is_finite(round2), "rounding2 must be a number if given.")
1448 assert(chamf1r <= r1, "chamfer1 is larger than the r1 radius of the cylinder.")
1449 assert(chamf2r <= r2, "chamfer2 is larger than the r2 radius of the cylinder.")
1450 assert(roundlen1 <= r1, "size of rounding1 is larger than the r1 radius of the cylinder.")
1451 assert(roundlen2 <= r2, "size of rounding2 is larger than the r2 radius of the cylinder.")
1452 assert(dy1+dy2 <= facelen, "Chamfers/roundings don't fit on the cylinder/cone. They exceed the length of the cylinder/cone face.")
1453 undef,
1454 path = [
1455 if (texture==undef) [0,-l/2],
1456 if (!approx(chamf1r,0))
1457 each [
1458 [r1, -l/2] + polar_to_xy(chamf1r,180),
1459 [r1, -l/2] + polar_to_xy(chamf1l,90+vang),
1460 ]
1461 else if (!approx(round1,0))
1462 each arc(r=abs(round1), corner=[[max(0,r1-2*roundlen1),-l/2],[r1,-l/2],[r2,l/2]])
1463 else [r1,-l/2],
1464 if (is_finite(chamf2r) && !approx(chamf2r,0))
1465 each [
1466 [r2, l/2] + polar_to_xy(chamf2l,270+vang),
1467 [r2, l/2] + polar_to_xy(chamf2r,180),
1468 ]
1469 else if (is_finite(round2) && !approx(round2,0))
1470 each arc(r=abs(round2), corner=[[r1,-l/2],[r2,l/2],[max(0,r2-2*roundlen2),l/2]])
1471 else [r2,l/2],
1472 if (texture==undef) [0,l/2],
1473 ]
1474 ) rotate_sweep(path,
1475 texture=texture, tex_counts=tex_counts, tex_size=tex_size,
1476 tex_inset=tex_inset, tex_rot=tex_rot,
1477 tex_scale=tex_scale, tex_samples=tex_samples,
1478 tex_taper=tex_taper, style=tex_style, closed=false
1479 ),
1480 skmat = down(l/2) *
1481 skew(sxz=shift.x/l, syz=shift.y/l) *
1482 up(l/2) *
1483 zrot(realign? 180/sides : 0),
1484 ovnf = apply(skmat, vnf)
1485 )
1486 reorient(anchor,spin,orient, r1=r1, r2=r2, l=l, shift=shift, p=ovnf);
1487
1488
1489module cyl(
1490 h, r, center,
1491 l, r1, r2,
1492 d, d1, d2,
1493 chamfer, chamfer1, chamfer2,
1494 chamfang, chamfang1, chamfang2,
1495 rounding, rounding1, rounding2,
1496 circum=false, realign=false, shift=[0,0],
1497 from_end, from_end1, from_end2,
1498 texture, tex_size=[5,5], tex_counts,
1499 tex_inset=false, tex_rot=false,
1500 tex_scale=1, tex_samples,
1501 tex_taper, tex_style="min_edge",
1502 anchor, spin=0, orient=UP
1503) {
1504 l = first_defined([l, h, 1]);
1505 _r1 = get_radius(r1=r1, r=r, d1=d1, d=d, dflt=1);
1506 _r2 = get_radius(r1=r2, r=r, d1=d2, d=d, dflt=1);
1507 sides = segs(max(_r1,_r2));
1508 sc = circum? 1/cos(180/sides) : 1;
1509 r1 = _r1 * sc;
1510 r2 = _r2 * sc;
1511 phi = atan2(l, r2-r1);
1512 anchor = get_anchor(anchor,center,BOT,CENTER);
1513 skmat = down(l/2) * skew(sxz=shift.x/l, syz=shift.y/l) * up(l/2);
1514 attachable(anchor,spin,orient, r1=r1, r2=r2, l=l, shift=shift) {
1515 multmatrix(skmat)
1516 zrot(realign? 180/sides : 0) {
1517 if (!any_defined([chamfer, chamfer1, chamfer2, rounding, rounding1, rounding2, texture])) {
1518 cylinder(h=l, r1=r1, r2=r2, center=true, $fn=sides);
1519 } else {
1520 vnf = cyl(
1521 l=l, r1=r1, r2=r2, center=true, $fn=sides,
1522 chamfer=chamfer, chamfer1=chamfer1, chamfer2=chamfer2,
1523 chamfang=chamfang, chamfang1=chamfang1, chamfang2=chamfang2,
1524 rounding=rounding, rounding1=rounding1, rounding2=rounding2,
1525 from_end=from_end, from_end1=from_end1, from_end2=from_end2,
1526 texture=texture, tex_size=tex_size,
1527 tex_counts=tex_counts, tex_scale=tex_scale,
1528 tex_inset=tex_inset, tex_rot=tex_rot,
1529 tex_style=tex_style, tex_taper=tex_taper,
1530 tex_samples=tex_samples
1531 );
1532 vnf_polyhedron(vnf, convexity=texture!=undef? 2 : 10);
1533 }
1534 }
1535 children();
1536 }
1537}
1538
1539
1540
1541// Module: xcyl()
1542//
1543// Description:
1544// Creates a cylinder oriented along the X axis.
1545//
1546// Usage: Typical
1547// xcyl(l|h, r|d=, [anchor=], ...) [ATTACHMENTS];
1548// xcyl(l|h, r1=|d1=, r2=|d2=, [anchor=], ...) [ATTACHMENTS];
1549//
1550// Arguments:
1551// l / h = Length of cylinder along oriented axis. Default: 1
1552// r = Radius of cylinder. Default: 1
1553// ---
1554// r1 = Optional radius of left (X-) end of cylinder.
1555// r2 = Optional radius of right (X+) end of cylinder.
1556// d = Optional diameter of cylinder. (use instead of `r`)
1557// d1 = Optional diameter of left (X-) end of cylinder.
1558// d2 = Optional diameter of right (X+) end of cylinder.
1559// circum = If true, cylinder should circumscribe the circle of the given size. Otherwise inscribes. Default: `false`
1560// chamfer = The size of the chamfers on the ends of the cylinder. Default: none.
1561// chamfer1 = The size of the chamfer on the left end of the cylinder. Default: none.
1562// chamfer2 = The size of the chamfer on the right end of the cylinder. Default: none.
1563// chamfang = The angle in degrees of the chamfers on the ends of the cylinder.
1564// chamfang1 = The angle in degrees of the chamfer on the left end of the cylinder.
1565// chamfang2 = The angle in degrees of the chamfer on the right end of the cylinder.
1566// from_end = If true, chamfer is measured from the end of the cylinder, instead of inset from the edge. Default: `false`.
1567// rounding = The radius of the rounding on the ends of the cylinder. Default: none.
1568// rounding1 = The radius of the rounding on the left end of the cylinder.
1569// rounding2 = The radius of the rounding on the right end of the cylinder.
1570// realign = If true, rotate the cylinder by half the angle of one face.
1571// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#subsection-anchor). Default: `CENTER`
1572// spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#subsection-spin). Default: `0`
1573// orient = Vector to rotate top towards, after spin. See [orient](attachments.scad#subsection-orient). Default: `UP`
1574//
1575// Example: By Radius
1576// ydistribute(50) {
1577// xcyl(l=35, r=10);
1578// xcyl(l=35, r1=15, r2=5);
1579// }
1580//
1581// Example: By Diameter
1582// ydistribute(50) {
1583// xcyl(l=35, d=20);
1584// xcyl(l=35, d1=30, d2=10);
1585// }
1586
1587module xcyl(
1588 h, r, d, r1, r2, d1, d2, l,
1589 chamfer, chamfer1, chamfer2,
1590 chamfang, chamfang1, chamfang2,
1591 rounding, rounding1, rounding2,
1592 circum=false, realign=false, from_end=false,
1593 anchor=CENTER, spin=0, orient=UP
1594) {
1595 r1 = get_radius(r1=r1, r=r, d1=d1, d=d, dflt=1);
1596 r2 = get_radius(r1=r2, r=r, d1=d2, d=d, dflt=1);
1597 l = first_defined([l, h, 1]);
1598 attachable(anchor,spin,orient, r1=r1, r2=r2, l=l, axis=RIGHT) {
1599 cyl(
1600 l=l, r1=r1, r2=r2,
1601 chamfer=chamfer, chamfer1=chamfer1, chamfer2=chamfer2,
1602 chamfang=chamfang, chamfang1=chamfang1, chamfang2=chamfang2,
1603 rounding=rounding, rounding1=rounding1, rounding2=rounding2,
1604 circum=circum, realign=realign, from_end=from_end,
1605 anchor=CENTER, orient=RIGHT
1606 );
1607 children();
1608 }
1609}
1610
1611
1612// Module: ycyl()
1613//
1614// Description:
1615// Creates a cylinder oriented along the Y axis.
1616//
1617// Usage: Typical
1618// ycyl(l|h, r|d=, [anchor=], ...) [ATTACHMENTS];
1619// ycyl(l|h, r1=|d1=, r2=|d2=, [anchor=], ...) [ATTACHMENTS];
1620//
1621// Arguments:
1622// l / h = Length of cylinder along oriented axis. (Default: `1.0`)
1623// r = Radius of cylinder.
1624// ---
1625// r1 = Radius of front (Y-) end of cone.
1626// r2 = Radius of back (Y+) end of one.
1627// d = Diameter of cylinder.
1628// d1 = Diameter of front (Y-) end of one.
1629// d2 = Diameter of back (Y+) end of one.
1630// circum = If true, cylinder should circumscribe the circle of the given size. Otherwise inscribes. Default: `false`
1631// chamfer = The size of the chamfers on the ends of the cylinder. Default: none.
1632// chamfer1 = The size of the chamfer on the front end of the cylinder. Default: none.
1633// chamfer2 = The size of the chamfer on the back end of the cylinder. Default: none.
1634// chamfang = The angle in degrees of the chamfers on the ends of the cylinder.
1635// chamfang1 = The angle in degrees of the chamfer on the front end of the cylinder.
1636// chamfang2 = The angle in degrees of the chamfer on the back end of the cylinder.
1637// from_end = If true, chamfer is measured from the end of the cylinder, instead of inset from the edge. Default: `false`.
1638// rounding = The radius of the rounding on the ends of the cylinder. Default: none.
1639// rounding1 = The radius of the rounding on the front end of the cylinder.
1640// rounding2 = The radius of the rounding on the back end of the cylinder.
1641// realign = If true, rotate the cylinder by half the angle of one face.
1642// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#subsection-anchor). Default: `CENTER`
1643// spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#subsection-spin). Default: `0`
1644// orient = Vector to rotate top towards, after spin. See [orient](attachments.scad#subsection-orient). Default: `UP`
1645//
1646// Example: By Radius
1647// xdistribute(50) {
1648// ycyl(l=35, r=10);
1649// ycyl(l=35, r1=15, r2=5);
1650// }
1651//
1652// Example: By Diameter
1653// xdistribute(50) {
1654// ycyl(l=35, d=20);
1655// ycyl(l=35, d1=30, d2=10);
1656// }
1657
1658module ycyl(
1659 h, r, d, r1, r2, d1, d2, l,
1660 chamfer, chamfer1, chamfer2,
1661 chamfang, chamfang1, chamfang2,
1662 rounding, rounding1, rounding2,
1663 circum=false, realign=false, from_end=false,
1664 anchor=CENTER, spin=0, orient=UP
1665) {
1666 r1 = get_radius(r1=r1, r=r, d1=d1, d=d, dflt=1);
1667 r2 = get_radius(r1=r2, r=r, d1=d2, d=d, dflt=1);
1668 l = first_defined([l, h, 1]);
1669 attachable(anchor,spin,orient, r1=r1, r2=r2, l=l, axis=BACK) {
1670 cyl(
1671 l=l, r1=r1, r2=r2,
1672 chamfer=chamfer, chamfer1=chamfer1, chamfer2=chamfer2,
1673 chamfang=chamfang, chamfang1=chamfang1, chamfang2=chamfang2,
1674 rounding=rounding, rounding1=rounding1, rounding2=rounding2,
1675 circum=circum, realign=realign, from_end=from_end,
1676 anchor=CENTER, orient=BACK
1677 );
1678 children();
1679 }
1680}
1681
1682
1683
1684// Module: zcyl()
1685//
1686// Description:
1687// Creates a cylinder oriented along the Z axis.
1688//
1689// Usage: Typical
1690// zcyl(l|h, r|d=, [anchor=],...) [ATTACHMENTS];
1691// zcyl(l|h, r1=|d1=, r2=|d2=, [anchor=],...);
1692//
1693// Arguments:
1694// l / h = Length of cylinder along oriented axis. (Default: 1.0)
1695// r = Radius of cylinder.
1696// ---
1697// r1 = Radius of front (Y-) end of cone.
1698// r2 = Radius of back (Y+) end of one.
1699// d = Diameter of cylinder.
1700// d1 = Diameter of front (Y-) end of one.
1701// d2 = Diameter of back (Y+) end of one.
1702// circum = If true, cylinder should circumscribe the circle of the given size. Otherwise inscribes. Default: `false`
1703// chamfer = The size of the chamfers on the ends of the cylinder. Default: none.
1704// chamfer1 = The size of the chamfer on the bottom end of the cylinder. Default: none.
1705// chamfer2 = The size of the chamfer on the top end of the cylinder. Default: none.
1706// chamfang = The angle in degrees of the chamfers on the ends of the cylinder.
1707// chamfang1 = The angle in degrees of the chamfer on the bottom end of the cylinder.
1708// chamfang2 = The angle in degrees of the chamfer on the top end of the cylinder.
1709// from_end = If true, chamfer is measured from the end of the cylinder, instead of inset from the edge. Default: `false`.
1710// rounding = The radius of the rounding on the ends of the cylinder. Default: none.
1711// rounding1 = The radius of the rounding on the bottom end of the cylinder.
1712// rounding2 = The radius of the rounding on the top end of the cylinder.
1713// realign = If true, rotate the cylinder by half the angle of one face.
1714// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#subsection-anchor). Default: `CENTER`
1715// spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#subsection-spin). Default: `0`
1716// orient = Vector to rotate top towards, after spin. See [orient](attachments.scad#subsection-orient). Default: `UP`
1717//
1718// Example: By Radius
1719// xdistribute(50) {
1720// zcyl(l=35, r=10);
1721// zcyl(l=35, r1=15, r2=5);
1722// }
1723//
1724// Example: By Diameter
1725// xdistribute(50) {
1726// zcyl(l=35, d=20);
1727// zcyl(l=35, d1=30, d2=10);
1728// }
1729
1730module zcyl(
1731 h, r, d, r1, r2, d1, d2, l,
1732 chamfer, chamfer1, chamfer2,
1733 chamfang, chamfang1, chamfang2,
1734 rounding, rounding1, rounding2,
1735 circum=false, realign=false, from_end=false,
1736 anchor=CENTER, spin=0, orient=UP
1737) {
1738 r1 = get_radius(r1=r1, r=r, d1=d1, d=d, dflt=1);
1739 r2 = get_radius(r1=r2, r=r, d1=d2, d=d, dflt=1);
1740 l = first_defined([l, h, 1]);
1741 attachable(anchor,spin,orient, r1=r1, r2=r2, l=l) {
1742 cyl(
1743 l=l, r1=r1, r2=r2,
1744 chamfer=chamfer, chamfer1=chamfer1, chamfer2=chamfer2,
1745 chamfang=chamfang, chamfang1=chamfang1, chamfang2=chamfang2,
1746 rounding=rounding, rounding1=rounding1, rounding2=rounding2,
1747 circum=circum, realign=realign, from_end=from_end,
1748 anchor=CENTER
1749 );
1750 children();
1751 }
1752}
1753
1754
1755
1756// Module: tube()
1757//
1758// Description:
1759// Makes a hollow tube that can be cylindrical or conical by specifying inner and outer dimensions or by giving one dimension and
1760// wall thickness.
1761// Usage: Basic cylindrical tube, specifying inner and outer radius or diameter
1762// tube(h|l, or, ir, [center], [realign=], [anchor=], [spin=],[orient=]) [ATTACHMENTS];
1763// tube(h|l, od=, id=, ...) [ATTACHMENTS];
1764// Usage: Specify wall thickness
1765// tube(h|l, or|od=|ir=|id=, wall=, ...) [ATTACHMENTS];
1766// Usage: Conical tubes
1767// tube(h|l, ir1=|id1=, ir2=|id2=, or1=|od1=, or2=|od2=, ...) [ATTACHMENTS];
1768// tube(h|l, or1=|od1=, or2=|od2=, wall=, ...) [ATTACHMENTS];
1769// Arguments:
1770// h / l = height of tube. Default: 1
1771// or = Outer radius of tube. Default: 1
1772// ir = Inner radius of tube.
1773// center = If given, overrides `anchor`. A true value sets `anchor=CENTER`, false sets `anchor=DOWN`.
1774// ---
1775// od = Outer diameter of tube.
1776// id = Inner diameter of tube.
1777// wall = horizontal thickness of tube wall. Default 1
1778// or1 = Outer radius of bottom of tube. Default: value of r)
1779// or2 = Outer radius of top of tube. Default: value of r)
1780// od1 = Outer diameter of bottom of tube.
1781// od2 = Outer diameter of top of tube.
1782// ir1 = Inner radius of bottom of tube.
1783// ir2 = Inner radius of top of tube.
1784// id1 = Inner diameter of bottom of tube.
1785// id2 = Inner diameter of top of tube.
1786// realign = If true, rotate the tube by half the angle of one face.
1787// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#subsection-anchor). Default: `CENTER`
1788// spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#subsection-spin). Default: `0`
1789// orient = Vector to rotate top towards, after spin. See [orient](attachments.scad#subsection-orient). Default: `UP`
1790//
1791// Example: These all Produce the Same Tube
1792// tube(h=30, or=40, wall=5);
1793// tube(h=30, ir=35, wall=5);
1794// tube(h=30, or=40, ir=35);
1795// tube(h=30, od=80, id=70);
1796// Example: These all Produce the Same Conical Tube
1797// tube(h=30, or1=40, or2=25, wall=5);
1798// tube(h=30, ir1=35, or2=20, wall=5);
1799// tube(h=30, or1=40, or2=25, ir1=35, ir2=20);
1800// Example: Circular Wedge
1801// tube(h=30, or1=40, or2=30, ir1=20, ir2=30);
1802// Example: Standard Connectors
1803// tube(h=30, or=40, wall=5) show_anchors();
1804
1805module tube(
1806 h, or, ir, center,
1807 od, id, wall,
1808 or1, or2, od1, od2,
1809 ir1, ir2, id1, id2,
1810 realign=false, l,
1811 anchor, spin=0, orient=UP
1812) {
1813 h = first_defined([h,l,1]);
1814 orr1 = get_radius(r1=or1, r=or, d1=od1, d=od, dflt=undef);
1815 orr2 = get_radius(r1=or2, r=or, d1=od2, d=od, dflt=undef);
1816 irr1 = get_radius(r1=ir1, r=ir, d1=id1, d=id, dflt=undef);
1817 irr2 = get_radius(r1=ir2, r=ir, d1=id2, d=id, dflt=undef);
1818 wall = default(wall, 1);
1819 r1 = default(orr1, u_add(irr1,wall));
1820 r2 = default(orr2, u_add(irr2,wall));
1821 ir1 = default(irr1, u_sub(orr1,wall));
1822 ir2 = default(irr2, u_sub(orr2,wall));
1823 checks =
1824 assert(all_defined([r1, r2, ir1, ir2]), "Must specify two of inner radius/diam, outer radius/diam, and wall width.")
1825 assert(ir1 <= r1, "Inner radius is larger than outer radius.")
1826 assert(ir2 <= r2, "Inner radius is larger than outer radius.");
1827 sides = segs(max(r1,r2));
1828 anchor = get_anchor(anchor, center, BOT, CENTER);
1829 attachable(anchor,spin,orient, r1=r1, r2=r2, l=h) {
1830 zrot(realign? 180/sides : 0) {
1831 difference() {
1832 cyl(h=h, r1=r1, r2=r2, $fn=sides) children();
1833 cyl(h=h+0.05, r1=ir1, r2=ir2);
1834 }
1835 }
1836 children();
1837 }
1838}
1839
1840
1841
1842// Function&Module: pie_slice()
1843//
1844// Description:
1845// Creates a pie slice shape.
1846//
1847// Usage: As Module
1848// pie_slice(l|h, r, ang, [center]);
1849// pie_slice(l|h, d=, ang=, ...);
1850// pie_slice(l|h, r1=|d1=, r2=|d2=, ang=, ...);
1851// Usage: As Function
1852// vnf = pie_slice(l|h, r, ang, [center]);
1853// vnf = pie_slice(l|h, d=, ang=, ...);
1854// vnf = pie_slice(l|h, r1=|d1=, r2=|d2=, ang=, ...);
1855// Usage: Attaching Children
1856// pie_slice(l|h, r, ang, ...) ATTACHMENTS;
1857//
1858// Arguments:
1859// h / l = height of pie slice.
1860// r = radius of pie slice.
1861// ang = pie slice angle in degrees.
1862// center = If given, overrides `anchor`. A true value sets `anchor=CENTER`, false sets `anchor=UP`.
1863// ---
1864// r1 = bottom radius of pie slice.
1865// r2 = top radius of pie slice.
1866// d = diameter of pie slice.
1867// d1 = bottom diameter of pie slice.
1868// d2 = top diameter of pie slice.
1869// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#subsection-anchor). Default: `CENTER`
1870// spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#subsection-spin). Default: `0`
1871// orient = Vector to rotate top towards, after spin. See [orient](attachments.scad#subsection-orient). Default: `UP`
1872//
1873// Example: Cylindrical Pie Slice
1874// pie_slice(ang=45, l=20, r=30);
1875// Example: Conical Pie Slice
1876// pie_slice(ang=60, l=20, d1=50, d2=70);
1877// Example: Big Slice
1878// pie_slice(ang=300, l=20, d1=50, d2=70);
1879// Example: Generating a VNF
1880// vnf = pie_slice(ang=150, l=20, r1=30, r2=50);
1881// vnf_polyhedron(vnf);
1882
1883module pie_slice(
1884 h, r, ang=30, center,
1885 r1, r2, d, d1, d2, l,
1886 anchor, spin=0, orient=UP
1887) {
1888 l = first_defined([l, h, 1]);
1889 r1 = get_radius(r1=r1, r=r, d1=d1, d=d, dflt=10);
1890 r2 = get_radius(r1=r2, r=r, d1=d2, d=d, dflt=10);
1891 maxd = max(r1,r2)+0.1;
1892 anchor = get_anchor(anchor, center, BOT, BOT);
1893 attachable(anchor,spin,orient, r1=r1, r2=r2, l=l) {
1894 difference() {
1895 cyl(r1=r1, r2=r2, h=l);
1896 if (ang<180) rotate(ang) back(maxd/2) cube([2*maxd, maxd, l+0.1], center=true);
1897 difference() {
1898 fwd(maxd/2) cube([2*maxd, maxd, l+0.2], center=true);
1899 if (ang>180) rotate(ang-180) back(maxd/2) cube([2*maxd, maxd, l+0.1], center=true);
1900 }
1901 }
1902 children();
1903 }
1904}
1905
1906function pie_slice(
1907 h, r, ang=30, center,
1908 r1, r2, d, d1, d2, l,
1909 anchor, spin=0, orient=UP
1910) = let(
1911 anchor = get_anchor(anchor, center, BOT, BOT),
1912 l = first_defined([l, h, 1]),
1913 r1 = get_radius(r1=r1, r=r, d1=d1, d=d, dflt=10),
1914 r2 = get_radius(r1=r2, r=r, d1=d2, d=d, dflt=10),
1915 maxd = max(r1,r2)+0.1,
1916 sides = ceil(segs(max(r1,r2))*ang/360),
1917 step = ang/sides,
1918 vnf = vnf_vertex_array(
1919 points=[
1920 for (u = [0,1]) let(
1921 h = lerp(-l/2,l/2,u),
1922 r = lerp(r1,r2,u)
1923 ) [
1924 for (theta = [0:step:ang+EPSILON])
1925 cylindrical_to_xyz(r,theta,h),
1926 [0,0,h]
1927 ]
1928 ],
1929 col_wrap=true, caps=true, reverse=true
1930 )
1931 ) reorient(anchor,spin,orient, r1=r1, r2=r2, l=l, p=vnf);
1932
1933
1934
1935// Section: Other Round Objects
1936
1937
1938// Function&Module: sphere()
1939// Topics: Shapes (3D), Attachable, VNF Generators
1940// Usage: As Module
1941// sphere(r|d=, [circum=], [style=], ...) [ATTACHMENTS];
1942// Usage: As Function
1943// vnf = sphere(r|d=, [circum=], [style=], ...);
1944// See Also: spheroid()
1945// Description:
1946// Creates a sphere object, with support for anchoring and attachments.
1947// This is a drop-in replacement for the built-in `sphere()` module.
1948// When called as a function, returns a [VNF](vnf.scad) for a sphere.
1949// Arguments:
1950// r = Radius of the sphere.
1951// ---
1952// d = Diameter of the sphere.
1953// circum = If true, the sphere is made large enough to circumscribe the sphere of the ideal side. Otherwise inscribes. Default: false (inscribes)
1954// style = The style of the sphere's construction. One of "orig", "aligned", "stagger", "octa", or "icosa". Default: "orig"
1955// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#subsection-anchor). Default: `CENTER`
1956// spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#subsection-spin). Default: `0`
1957// orient = Vector to rotate top towards, after spin. See [orient](attachments.scad#subsection-orient). Default: `UP`
1958// Example: By Radius
1959// sphere(r=50);
1960// Example: By Diameter
1961// sphere(d=100);
1962// Example: style="orig"
1963// sphere(d=100, style="orig", $fn=10);
1964// Example: style="aligned"
1965// sphere(d=100, style="aligned", $fn=10);
1966// Example: style="stagger"
1967// sphere(d=100, style="stagger", $fn=10);
1968// Example: style="icosa"
1969// sphere(d=100, style="icosa", $fn=10);
1970// // In "icosa" style, $fn is quantized
1971// // to the nearest multiple of 5.
1972// Example: Anchoring
1973// sphere(d=100, anchor=FRONT);
1974// Example: Spin
1975// sphere(d=100, anchor=FRONT, spin=45);
1976// Example: Orientation
1977// sphere(d=100, anchor=FRONT, spin=45, orient=FWD);
1978// Example: Standard Connectors
1979// sphere(d=50) show_anchors();
1980// Example: Called as Function
1981// vnf = sphere(d=100, style="icosa");
1982// vnf_polyhedron(vnf);
1983
1984module sphere(r, d, circum=false, style="orig", anchor=CENTER, spin=0, orient=UP) {
1985 r = get_radius(r=r, d=d, dflt=1);
1986 if (!circum && style=="orig" && is_num(r)) {
1987 attachable(anchor,spin,orient, r=r) {
1988 _sphere(r=r);
1989 children();
1990 }
1991 } else {
1992 spheroid(
1993 r=r, circum=circum, style=style,
1994 anchor=anchor, spin=spin, orient=orient
1995 ) children();
1996 }
1997}
1998
1999function sphere(r, d, circum=false, style="orig", anchor=CENTER, spin=0, orient=UP) =
2000 spheroid(r=r, d=d, circum=circum, style=style, anchor=anchor, spin=spin, orient=orient);
2001
2002
2003// Function&Module: spheroid()
2004// Usage: Typical
2005// spheroid(r|d, [circum], [style]) [ATTACHMENTS];
2006// Usage: As Function
2007// vnf = spheroid(r|d, [circum], [style]);
2008// Description:
2009// Creates a spheroid object, with support for anchoring and attachments.
2010// This is a drop-in replacement for the built-in `sphere()` module.
2011// When called as a function, returns a [VNF](vnf.scad) for a spheroid.
2012// The exact triangulation of this spheroid can be controlled via the `style=`
2013// argument, where the value can be one of `"orig"`, `"aligned"`, `"stagger"`,
2014// `"octa"`, or `"icosa"`.
2015// - `style="orig"` constructs a sphere the same way that the OpenSCAD `sphere()` built-in does.
2016// - `style="aligned"` constructs a sphere where, if `$fn` is a multiple of 4, it has vertices at all axis maxima and minima. ie: its bounding box is exactly the sphere diameter in length on all three axes. This is the default.
2017// - `style="stagger"` forms a sphere where all faces are triangular, but the top and bottom poles have thinner triangles.
2018// - `style="octa"` forms a sphere by subdividing an octahedron. This makes more uniform faces over the entirety of the sphere, and guarantees the bounding box is the sphere diameter in size on all axes. The effective `$fn` value is quantized to a multiple of 4. This is used in constructing rounded corners for various other shapes.
2019// - `style="icosa"` forms a sphere by subdividing an icosahedron. This makes even more uniform faces over the whole sphere. The effective `$fn` value is quantized to a multiple of 5. This sphere has a guaranteed bounding box when `$fn` is a multiple of 10.
2020// .
2021// By default the object spheroid() produces is a polyhedron whose vertices all lie on the requested sphere. This means
2022// the approximating polyhedron is inscribed in the sphere.
2023// The `circum` argument requests a circumscribing sphere, where the true sphere is
2024// inside and tangent to all the faces of the approximating polyhedron. To produce
2025// a circumscribing polyhedron, we use the dual polyhedron of the basic form. The dual of a polyhedron is
2026// a new polyhedron whose vertices are obtained from the faces of the parent polyhedron.
2027// The "orig" and "align" forms are duals of each other. If you request a circumscribing polyhedron in
2028// these styles then the polyhedron will look the same as the default inscribing form. But for the other
2029// styles, the duals are completely different from their parents, and from each other. Generation of the circumscribed versions (duals)
2030// for "octa" and "icosa" is fast if you use the module form but can be very slow (several minutes) if you use the functional
2031// form and choose a large $fn value.
2032// .
2033// With style="align", the circumscribed sphere has its maximum radius on the X and Y axes
2034// but is undersized on the Z axis. With style="octa" the circumscribed sphere has faces at each axis, so
2035// the radius on the axes is equal to the specified radius, which is the *minimum* radius of the circumscribed sphere.
2036// The same thing is true for style="icosa" when $fn is a multiple of 10. This would enable you to create spherical
2037// holes with guaranteed on-axis dimensions.
2038// Arguments:
2039// r = Radius of the spheroid.
2040// style = The style of the spheroid's construction. One of "orig", "aligned", "stagger", "octa", or "icosa". Default: "aligned"
2041// ---
2042// d = Diameter of the spheroid.
2043// circum = If true, the approximate sphere circumscribes the true sphere of the requested size. Otherwise inscribes. Note that for some styles, the circumscribed sphere looks different than the inscribed sphere. Default: false (inscribes)
2044// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#subsection-anchor). Default: `CENTER`
2045// spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#subsection-spin). Default: `0`
2046// orient = Vector to rotate top towards, after spin. See [orient](attachments.scad#subsection-orient). Default: `UP`
2047// Example: By Radius
2048// spheroid(r=50);
2049// Example: By Diameter
2050// spheroid(d=100);
2051// Example: style="orig"
2052// spheroid(d=100, style="orig", $fn=10);
2053// Example: style="aligned"
2054// spheroid(d=100, style="aligned", $fn=10);
2055// Example: style="stagger"
2056// spheroid(d=100, style="stagger", $fn=10);
2057// Example: style="stagger" with circum=true
2058// spheroid(d=100, style="stagger", circum=true, $fn=10);
2059// Example: style="octa", octahedral based tesselation. In this style, $fn is quantized to a multiple of 4.
2060// spheroid(d=100, style="octa", $fn=10);
2061// Example: style="octa", with circum=true, produces mostly very irregular hexagonal faces
2062// spheroid(d=100, style="octa", circum=true, $fn=16);
2063// Example: style="icosa", icosahedral based tesselation. In this style, $fn is quantized to a multiple of 5.
2064// spheroid(d=100, style="icosa", $fn=10);
2065// Example: style="icosa", circum=true. This style has hexagons and 12 pentagons, similar to (but not the same as) a soccer ball.
2066// spheroid(d=100, style="icosa", circum=true, $fn=10);
2067// Example: Anchoring
2068// spheroid(d=100, anchor=FRONT);
2069// Example: Spin
2070// spheroid(d=100, anchor=FRONT, spin=45);
2071// Example: Orientation
2072// spheroid(d=100, anchor=FRONT, spin=45, orient=FWD);
2073// Example: Standard Connectors
2074// spheroid(d=50) show_anchors();
2075// Example: Called as Function
2076// vnf = spheroid(d=100, style="icosa");
2077// vnf_polyhedron(vnf);
2078// Example: With "orig" the circumscribing sphere has the same form. The green sphere is a tiny bit oversized so it pokes through the low points in the circumscribed sphere with low $fn. This demonstrates that these spheres are in fact circumscribing.
2079// color("green")spheroid(r=10.01, $fn=256);
2080// spheroid(r=10, style="orig", circum=true, $fn=16);
2081// Example: With "aligned" the same is true: the circumscribing sphere is also aligned, if $fn is divisible by 4.
2082// color("green")spheroid(r=10.01, $fn=256);
2083// spheroid(r=10, style="aligned", circum=true, $fn=16);
2084// Example: For the other styles, the circumscribing sphere is different, as shown here with "stagger"
2085// color("green")spheroid(r=10.01, $fn=256);
2086// spheroid(r=10, style="stagger", circum=true, $fn=16);
2087// Example: The dual of "octa" that provides the circumscribing sphere has weird asymmetric hexagonal faces:
2088// color("green")spheroid(r=10.01, $fn=256);
2089// spheroid(r=10, style="octa", circum=true, $fn=16);
2090// Example: The dual of "icosa" features hexagons and always 12 pentagons:
2091// color("green")spheroid(r=10.01, $fn=256);
2092// spheroid(r=10, style="icosa", circum=true, $fn=16);
2093
2094module spheroid(r, style="aligned", d, circum=false, dual=false, anchor=CENTER, spin=0, orient=UP)
2095{
2096 r = get_radius(r=r, d=d, dflt=1);
2097 sides = segs(r);
2098 vsides = ceil(sides/2);
2099 attachable(anchor,spin,orient, r=r) {
2100 if (style=="orig" && !circum) {
2101 merids = [ for (i=[0:1:vsides-1]) 90-(i+0.5)*180/vsides ];
2102 path = [
2103 let(a = merids[0]) [0, sin(a)],
2104 for (a=merids) [cos(a), sin(a)],
2105 let(a = last(merids)) [0, sin(a)]
2106 ];
2107 scale(r) rotate(180) rotate_extrude(convexity=2,$fn=sides) polygon(path);
2108 }
2109 // Don't now how to construct faces for these efficiently, so use hull_points, which
2110 // is very much faster than using hull() as happens in the spheroid() function
2111 else if (circum && (style=="octa" || style=="icosa")) {
2112 orig_sphere = spheroid(r,style,circum=false);
2113 dualvert = _dual_vertices(orig_sphere);
2114 hull_points(dualvert,fast=true);
2115 } else {
2116 vnf = spheroid(r=r, circum=circum, style=style);
2117 vnf_polyhedron(vnf, convexity=2);
2118 }
2119 children();
2120 }
2121}
2122
2123
2124// p is a list of 3 points defining a triangle in any dimension. N is the number of extra points
2125// to add, so output triangle has N+2 points on each side.
2126function _subsample_triangle(p,N) =
2127 [for(i=[0:N+1]) [for (j=[0:N+1-i]) unit(lerp(p[0],p[1],i/(N+1)) + (p[2]-p[0])*j/(N+1))]];
2128
2129
2130// Input should have only triangular faces
2131function _dual_vertices(vnf) =
2132 let(vert=vnf[0])
2133 [for(face=vnf[1])
2134 let(planes = select(vert,face))
2135 //linear_solve3(planes, [for(p=planes) p*p])
2136 linear_solve3(select(planes,0,2), [for(i=[0:2]) planes[i]*planes[i]]) // Handle larger faces, maybe?
2137 ];
2138
2139
2140function spheroid(r, style="aligned", d, circum=false, anchor=CENTER, spin=0, orient=UP) =
2141 let(
2142 r = get_radius(r=r, d=d, dflt=1),
2143 hsides = segs(r),
2144 vsides = max(2,ceil(hsides/2)),
2145 octa_steps = round(max(4,hsides)/4),
2146 icosa_steps = round(max(5,hsides)/5),
2147 stagger = style=="stagger"
2148 )
2149 circum && style=="orig" ?
2150 let(
2151 orig_sphere = spheroid(r,"aligned",circum=false),
2152 dualvert = zrot(360/hsides/2,_dual_vertices(orig_sphere)),
2153 culledvert = [
2154 [for(i=[0:2:2*hsides-1]) dualvert[i]],
2155 for(j=[1:vsides-2])
2156 [for(i=[0:2:2*hsides-1]) dualvert[j*2*hsides+i]],
2157 [for(i=[1:2:2*hsides-1]) dualvert[i]]
2158 ],
2159 vnf = vnf_vertex_array(culledvert,col_wrap=true,caps=true)
2160 )
2161 [reorient(anchor,spin,orient, r=r, p=vnf[0]), vnf[1]]
2162 :
2163 circum && (style=="octa" || style=="icosa") ?
2164 let(
2165 orig_sphere = spheroid(r,style,circum=false),
2166 dualvert = _dual_vertices(orig_sphere),
2167 faces = hull(dualvert)
2168 )
2169 [reorient(anchor,spin,orient, r=r, p=dualvert), faces]
2170 :
2171 style=="icosa" ? // subdivide faces of an icosahedron and project them onto a sphere
2172 let(
2173 N = icosa_steps-1,
2174 // construct an icosahedron
2175 icovert=[ for(i=[-1,1], j=[-1,1]) each [[0,i,j*PHI], [i,j*PHI,0], [j*PHI,0,i]]],
2176 icoface = hull(icovert),
2177 // Subsample face 0 of the icosahedron
2178 face0 = select(icovert,icoface[0]),
2179 sampled = r * _subsample_triangle(face0,N),
2180 dir0 = mean(face0),
2181 point0 = face0[0]-dir0,
2182 // Make a rotated copy of the subsampled triangle on each icosahedral face
2183 tri_list = [sampled,
2184 for(i=[1:1:len(icoface)-1])
2185 let(face = select(icovert,icoface[i]))
2186 apply(frame_map(z=mean(face),x=face[0]-mean(face))
2187 *frame_map(z=dir0,x=point0,reverse=true),
2188 sampled)],
2189 // faces for the first triangle group
2190 faces = vnf_tri_array(tri_list[0],reverse=true)[1],
2191 size = repeat((N+2)*(N+3)/2,3),
2192 // Expand to full face list
2193 fullfaces = [for(i=idx(tri_list)) each [for(f=faces) f+i*size]],
2194 fullvert = flatten(flatten(tri_list)) // eliminate triangle structure
2195 )
2196 [reorient(anchor,spin,orient, r=r, p=fullvert), fullfaces]
2197 :
2198 let(
2199 verts = circum && style=="stagger" ? _dual_vertices(spheroid(r,style,circum=false))
2200 : circum && style=="aligned" ?
2201 let(
2202 orig_sphere = spheroid(r,"orig",circum=false),
2203 dualvert = _dual_vertices(orig_sphere),
2204 culledvert = zrot(360/hsides/2,
2205 [dualvert[0],
2206 for(i=[2:2:len(dualvert)-1]) dualvert[i],
2207 dualvert[1]])
2208 )
2209 culledvert
2210 : style=="orig"? [
2211 for (i=[0:1:vsides-1])
2212 let(phi = (i+0.5)*180/(vsides))
2213 for (j=[0:1:hsides-1])
2214 let(theta = j*360/hsides)
2215 spherical_to_xyz(r, theta, phi),
2216 ]
2217 : style=="aligned" || style=="stagger"?
2218 [ spherical_to_xyz(r, 0, 0),
2219 for (i=[1:1:vsides-1])
2220 let(phi = i*180/vsides)
2221 for (j=[0:1:hsides-1])
2222 let(theta = (j+((stagger && i%2!=0)?0.5:0))*360/hsides)
2223 spherical_to_xyz(r, theta, phi),
2224 spherical_to_xyz(r, 0, 180)
2225 ]
2226 : style=="octa"?
2227 let(
2228 meridians = [
2229 1,
2230 for (i = [1:1:octa_steps]) i*4,
2231 for (i = [octa_steps-1:-1:1]) i*4,
2232 1,
2233 ]
2234 )
2235 [
2236 for (i=idx(meridians), j=[0:1:meridians[i]-1])
2237 spherical_to_xyz(r, j*360/meridians[i], i*180/(len(meridians)-1))
2238 ]
2239 : assert(in_list(style,["orig","aligned","stagger","octa","icosa"])),
2240 lv = len(verts),
2241 faces = circum && style=="stagger" ?
2242 let(ptcount=2*hsides)
2243 [
2244 [for(i=[ptcount-2:-2:0]) i],
2245 for(j=[0:hsides-1])
2246 [j*2, (j*2+2)%ptcount,ptcount+(j*2+2)%ptcount,ptcount+(j*2+3)%ptcount,ptcount+j*2],
2247 for(i=[1:vsides-3])
2248 let(base=ptcount*i)
2249 for(j=[0:hsides-1])
2250 i%2==0 ? [base+2*j, base+(2*j+1)%ptcount, base+(2*j+2)%ptcount,
2251 base+ptcount+(2*j)%ptcount, base+ptcount+(2*j+1)%ptcount, base+ptcount+(2*j-2+ptcount)%ptcount]
2252 : [base+(1+2*j)%ptcount, base+(2*j)%ptcount, base+(2*j+3)%ptcount,
2253 base+ptcount+(3+2*j)%ptcount, base+ptcount+(2*j+2)%ptcount,base+ptcount+(2*j+1)%ptcount],
2254 for(j=[0:hsides-1])
2255 vsides%2==0
2256 ? [(j*2+3)%ptcount, j*2+1, lv-ptcount+(2+j*2)%ptcount, lv-ptcount+(3+j*2)%ptcount, lv-ptcount+(4+j*2)%ptcount]
2257 : [(j*2+3)%ptcount, j*2+1, lv-ptcount+(1+j*2)%ptcount, lv-ptcount+(j*2)%ptcount, lv-ptcount+(3+j*2)%ptcount],
2258 [for(i=[1:2:ptcount-1]) i],
2259 ]
2260 : style=="aligned" || style=="stagger" ? // includes case of aligned with circum == true
2261 [
2262 for (i=[0:1:hsides-1])
2263 let(b2 = lv-2-hsides)
2264 each [
2265 [i+1, 0, ((i+1)%hsides)+1],
2266 [lv-1, b2+i+1, b2+((i+1)%hsides)+1],
2267 ],
2268 for (i=[0:1:vsides-3], j=[0:1:hsides-1])
2269 let(base = 1 + hsides*i)
2270 each (
2271 (stagger && i%2!=0)? [
2272 [base+j, base+hsides+j%hsides, base+hsides+(j+hsides-1)%hsides],
2273 [base+j, base+(j+1)%hsides, base+hsides+j],
2274 ] : [
2275 [base+j, base+(j+1)%hsides, base+hsides+(j+1)%hsides],
2276 [base+j, base+hsides+(j+1)%hsides, base+hsides+j],
2277 ]
2278 )
2279 ]
2280 : style=="orig"? [
2281 [for (i=[0:1:hsides-1]) hsides-i-1],
2282 [for (i=[0:1:hsides-1]) lv-hsides+i],
2283 for (i=[0:1:vsides-2], j=[0:1:hsides-1])
2284 each [
2285 [(i+1)*hsides+j, i*hsides+j, i*hsides+(j+1)%hsides],
2286 [(i+1)*hsides+j, i*hsides+(j+1)%hsides, (i+1)*hsides+(j+1)%hsides],
2287 ]
2288 ]
2289 : /*style=="octa"?*/
2290 let(
2291 meridians = [
2292 0, 1,
2293 for (i = [1:1:octa_steps]) i*4,
2294 for (i = [octa_steps-1:-1:1]) i*4,
2295 1,
2296 ],
2297 offs = cumsum(meridians),
2298 pc = last(offs)-1,
2299 os = octa_steps * 2
2300 )
2301 [
2302 for (i=[0:1:3]) [0, 1+(i+1)%4, 1+i],
2303 for (i=[0:1:3]) [pc-0, pc-(1+(i+1)%4), pc-(1+i)],
2304 for (i=[1:1:octa_steps-1])
2305 let(m = meridians[i+2]/4)
2306 for (j=[0:1:3], k=[0:1:m-1])
2307 let(
2308 m1 = meridians[i+1],
2309 m2 = meridians[i+2],
2310 p1 = offs[i+0] + (j*m1/4 + k+0) % m1,
2311 p2 = offs[i+0] + (j*m1/4 + k+1) % m1,
2312 p3 = offs[i+1] + (j*m2/4 + k+0) % m2,
2313 p4 = offs[i+1] + (j*m2/4 + k+1) % m2,
2314 p5 = offs[os-i+0] + (j*m1/4 + k+0) % m1,
2315 p6 = offs[os-i+0] + (j*m1/4 + k+1) % m1,
2316 p7 = offs[os-i-1] + (j*m2/4 + k+0) % m2,
2317 p8 = offs[os-i-1] + (j*m2/4 + k+1) % m2
2318 )
2319 each [
2320 [p1, p4, p3],
2321 if (k<m-1) [p1, p2, p4],
2322 [p5, p7, p8],
2323 if (k<m-1) [p5, p8, p6],
2324 ],
2325 ]
2326 ) [reorient(anchor,spin,orient, r=r, p=verts), faces];
2327
2328
2329
2330// Function&Module: torus()
2331//
2332// Usage: As Module
2333// torus(r_maj|d_maj, r_min|d_min, [center], ...) [ATTACHMENTS];
2334// torus(or|od, ir|id, ...) [ATTACHMENTS];
2335// torus(r_maj|d_maj, or|od, ...) [ATTACHMENTS];
2336// torus(r_maj|d_maj, ir|id, ...) [ATTACHMENTS];
2337// torus(r_min|d_min, or|od, ...) [ATTACHMENTS];
2338// torus(r_min|d_min, ir|id, ...) [ATTACHMENTS];
2339// Usage: As Function
2340// vnf = torus(r_maj|d_maj, r_min|d_min, [center], ...);
2341// vnf = torus(or|od, ir|id, ...);
2342// vnf = torus(r_maj|d_maj, or|od, ...);
2343// vnf = torus(r_maj|d_maj, ir|id, ...);
2344// vnf = torus(r_min|d_min, or|od, ...);
2345// vnf = torus(r_min|d_min, ir|id, ...);
2346//
2347// Description:
2348// Creates a torus shape.
2349//
2350// Figure(2D,Med):
2351// module dashcirc(r,start=0,angle=359.9,dashlen=5) let(step=360*dashlen/(2*r*PI)) for(a=[start:step:start+angle]) stroke(arc(r=r,start=a,angle=step/2));
2352// r = 75; r2 = 30;
2353// down(r2+0.1) #torus(r_maj=r, r_min=r2, $fn=72);
2354// color("blue") linear_extrude(height=0.01) {
2355// dashcirc(r=r,start=15,angle=45);
2356// dashcirc(r=r-r2, start=90+15, angle=60);
2357// dashcirc(r=r+r2, start=180+45, angle=30);
2358// dashcirc(r=r+r2, start=15, angle=30);
2359// }
2360// rot(240) color("blue") linear_extrude(height=0.01) {
2361// stroke([[0,0],[r+r2,0]], endcaps="arrow2",width=2);
2362// right(r) fwd(9) rot(-240) text("or",size=10,anchor=CENTER);
2363// }
2364// rot(135) color("blue") linear_extrude(height=0.01) {
2365// stroke([[0,0],[r-r2,0]], endcaps="arrow2",width=2);
2366// right((r-r2)/2) back(8) rot(-135) text("ir",size=10,anchor=CENTER);
2367// }
2368// rot(45) color("blue") linear_extrude(height=0.01) {
2369// stroke([[0,0],[r,0]], endcaps="arrow2",width=2);
2370// right(r/2) back(8) text("r_maj",size=9,anchor=CENTER);
2371// }
2372// rot(30) color("blue") linear_extrude(height=0.01) {
2373// stroke([[r,0],[r+r2,0]], endcaps="arrow2",width=2);
2374// right(r+r2/2) fwd(8) text("r_min",size=7,anchor=CENTER);
2375// }
2376//
2377// Arguments:
2378// r_maj = major radius of torus ring. (use with 'r_min', or 'd_min')
2379// r_min = minor radius of torus ring. (use with 'r_maj', or 'd_maj')
2380// center = If given, overrides `anchor`. A true value sets `anchor=CENTER`, false sets `anchor=DOWN`.
2381// ---
2382// d_maj = major diameter of torus ring. (use with 'r_min', or 'd_min')
2383// d_min = minor diameter of torus ring. (use with 'r_maj', or 'd_maj')
2384// or = outer radius of the torus. (use with 'ir', or 'id')
2385// ir = inside radius of the torus. (use with 'or', or 'od')
2386// od = outer diameter of the torus. (use with 'ir' or 'id')
2387// id = inside diameter of the torus. (use with 'or' or 'od')
2388// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#subsection-anchor). Default: `CENTER`
2389// orient = Vector to rotate top towards, after spin. See [orient](attachments.scad#subsection-orient). Default: `UP`
2390//
2391// Example:
2392// // These all produce the same torus.
2393// torus(r_maj=22.5, r_min=7.5);
2394// torus(d_maj=45, d_min=15);
2395// torus(or=30, ir=15);
2396// torus(od=60, id=30);
2397// torus(d_maj=45, id=30);
2398// torus(d_maj=45, od=60);
2399// torus(d_min=15, id=30);
2400// torus(d_min=15, od=60);
2401// vnf_polyhedron(torus(d_min=15, od=60), convexity=4);
2402// Example: Standard Connectors
2403// torus(od=60, id=30) show_anchors();
2404
2405module torus(
2406 r_maj, r_min, center,
2407 d_maj, d_min,
2408 or, od, ir, id,
2409 anchor, spin=0, orient=UP
2410) {
2411 _or = get_radius(r=or, d=od, dflt=undef);
2412 _ir = get_radius(r=ir, d=id, dflt=undef);
2413 _r_maj = get_radius(r=r_maj, d=d_maj, dflt=undef);
2414 _r_min = get_radius(r=r_min, d=d_min, dflt=undef);
2415 maj_rad = is_finite(_r_maj)? _r_maj :
2416 is_finite(_ir) && is_finite(_or)? (_or + _ir)/2 :
2417 is_finite(_ir) && is_finite(_r_min)? (_ir + _r_min) :
2418 is_finite(_or) && is_finite(_r_min)? (_or - _r_min) :
2419 assert(false, "Bad Parameters");
2420 min_rad = is_finite(_r_min)? _r_min :
2421 is_finite(_ir)? (maj_rad - _ir) :
2422 is_finite(_or)? (_or - maj_rad) :
2423 assert(false, "Bad Parameters");
2424 anchor = get_anchor(anchor, center, BOT, CENTER);
2425 attachable(anchor,spin,orient, r=(maj_rad+min_rad), l=min_rad*2) {
2426 rotate_extrude(convexity=4) {
2427 right_half(s=min_rad*2, planar=true)
2428 right(maj_rad)
2429 circle(r=min_rad);
2430 }
2431 children();
2432 }
2433}
2434
2435
2436function torus(
2437 r_maj, r_min, center,
2438 d_maj, d_min,
2439 or, od, ir, id,
2440 anchor, spin=0, orient=UP
2441) = let(
2442 _or = get_radius(r=or, d=od, dflt=undef),
2443 _ir = get_radius(r=ir, d=id, dflt=undef),
2444 _r_maj = get_radius(r=r_maj, d=d_maj, dflt=undef),
2445 _r_min = get_radius(r=r_min, d=d_min, dflt=undef),
2446 maj_rad = is_finite(_r_maj)? _r_maj :
2447 is_finite(_ir) && is_finite(_or)? (_or + _ir)/2 :
2448 is_finite(_ir) && is_finite(_r_min)? (_ir + _r_min) :
2449 is_finite(_or) && is_finite(_r_min)? (_or - _r_min) :
2450 assert(false, "Bad Parameters"),
2451 min_rad = is_finite(_r_min)? _r_min :
2452 is_finite(_ir)? (maj_rad - _ir) :
2453 is_finite(_or)? (_or - maj_rad) :
2454 assert(false, "Bad Parameters"),
2455 anchor = get_anchor(anchor, center, BOT, CENTER),
2456 maj_sides = segs(maj_rad+min_rad),
2457 maj_step = 360 / maj_sides,
2458 min_sides = segs(min_rad),
2459 min_step = 360 / min_sides,
2460 xyprofile = min_rad <= maj_rad? right(maj_rad, p=circle(r=min_rad)) :
2461 right_half(p=right(maj_rad, p=circle(r=min_rad)))[0],
2462 profile = xrot(90, p=path3d(xyprofile)),
2463 vnf = vnf_vertex_array(
2464 points=[for (a=[0:maj_step:360-EPSILON]) zrot(a, p=profile)],
2465 caps=false, col_wrap=true, row_wrap=true, reverse=true
2466 )
2467) reorient(anchor,spin,orient, r=(maj_rad+min_rad), l=min_rad*2, p=vnf);
2468
2469
2470// Function&Module: teardrop()
2471//
2472// Description:
2473// Makes a teardrop shape in the XZ plane. Useful for 3D printable holes.
2474// Optional chamfers can be added with positive or negative distances. A positive distance
2475// specifies the amount to inset the chamfer along the front/back faces of the shape.
2476// The chamfer will extend the same y distance into the shape. If the radii are the same
2477// then the chamfer will be a 45 degree chamfer, but in other cases it will not.
2478// Note that with caps, the chamfer must not be so big that it makes the cap height illegal.
2479//
2480// Usage: Typical
2481// teardrop(h|l|length|height, r, [ang], [cap_h], [chamfer=], ...) [ATTACHMENTS];
2482// teardrop(h|l|length|height, d=, [ang=], [cap_h=], [chamfer=], ...) [ATTACHMENTS];
2483// Usage: Psuedo-Conical
2484// teardrop(h|l, r1=, r2=, [ang=], [cap_h1=], [cap_h2=], ...) [ATTACHMENTS];
2485// teardrop(h|l, d1=, d2=, [ang=], [cap_h1=], [cap_h2=], ...) [ATTACHMENTS];
2486// Usage: As Function
2487// vnf = teardrop(h|l=, r|d=, [ang=], [cap_h=], ...);
2488// vnf = teardrop(h|l=, r1=|d1=, r2=|d2=, [ang=], [cap_h=], ...);
2489// vnf = teardrop(h|l=, r1=|d1=, r2=|d2=, [ang=], [cap_h1=], [cap_h2=], ...);
2490//
2491// Arguments:
2492// h / l = Thickness of teardrop. Default: 1
2493// r = Radius of circular part of teardrop. Default: 1
2494// ang = Angle of hat walls from the Z axis. Default: 45 degrees
2495// cap_h = If given, height above center where the shape will be truncated. Default: `undef` (no truncation)
2496// ---
2497// circum = produce a circumscribing teardrop shape. Default: false
2498// r1 = Radius of circular portion of the front end of the teardrop shape.
2499// r2 = Radius of circular portion of the back end of the teardrop shape.
2500// d = Diameter of circular portion of the teardrop shape.
2501// d1 = Diameter of circular portion of the front end of the teardrop shape.
2502// d2 = Diameter of circular portion of the back end of the teardrop shape.
2503// cap_h1 = If given, height above center where the shape will be truncated, on the front side. Default: `undef` (no truncation)
2504// cap_h2 = If given, height above center where the shape will be truncated, on the back side. Default: `undef` (no truncation)
2505// chamfer = Specifies size of chamfer as distance along the bottom and top faces. Default: 0
2506// chamfer1 = Specifies size of chamfer on bottom as distance along bottom face. Default: 0
2507// chamfer2 = Specifies size of chamfer on top as distance along top face. Default: 0
2508// realign = Passes realign option to teardrop2d, which shifts face alignment. Default: false
2509// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#subsection-anchor). Default: `CENTER`
2510// spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#subsection-spin). Default: `0`
2511// orient = Vector to rotate top towards, after spin. See [orient](attachments.scad#subsection-orient). Default: `UP`
2512//
2513// Extra Anchors:
2514// cap = The center of the top of the cap, oriented with the cap face normal.
2515// cap_fwd = The front edge of the cap.
2516// cap_back = The back edge of the cap.
2517//
2518// Example: Typical Shape
2519// teardrop(r=30, h=10, ang=30);
2520// Example: Crop Cap
2521// teardrop(r=30, h=10, ang=30, cap_h=40);
2522// Example: Close Crop
2523// teardrop(r=30, h=10, ang=30, cap_h=20);
2524// Example: Psuedo-Conical
2525// teardrop(r1=20, r2=30, h=40, cap_h1=25, cap_h2=35);
2526// Example: Adding chamfers can be useful for a teardrop hole mask
2527// teardrop(r=10, l=50, chamfer1=2, chamfer2=-1.5);
2528// Example: Getting a VNF
2529// vnf = teardrop(r1=25, r2=30, l=20, cap_h1=25, cap_h2=35);
2530// vnf_polyhedron(vnf);
2531// Example: Standard Conical Connectors
2532// teardrop(d1=20, d2=30, h=20, cap_h1=11, cap_h2=16)
2533// show_anchors(custom=false);
2534// Example(Spin,VPD=150,Med): Named Conical Connectors
2535// teardrop(d1=20, d2=30, h=20, cap_h1=11, cap_h2=16)
2536// show_anchors(std=false);
2537
2538module teardrop(h, r, ang=45, cap_h, r1, r2, d, d1, d2, cap_h1, cap_h2, l, length, height, circum=false, realign=false,
2539 chamfer, chamfer1, chamfer2,anchor=CENTER, spin=0, orient=UP)
2540{
2541 length = one_defined([l, h, length, height],"l,h,length,height");
2542 dummy=assert(is_finite(length) && length>0, "length must be positive");
2543 r1 = get_radius(r=r, r1=r1, d=d, d1=d1);
2544 r2 = get_radius(r=r, r1=r2, d=d, d1=d2);
2545 tip_y1 = r1/cos(90-ang);
2546 tip_y2 = r2/cos(90-ang);
2547 _cap_h1 = min(default(cap_h1, tip_y1), tip_y1);
2548 _cap_h2 = min(default(cap_h2, tip_y2), tip_y2);
2549 capvec = unit([0, _cap_h1-_cap_h2, length]);
2550 anchors = [
2551 named_anchor("cap", [0,0,(_cap_h1+_cap_h2)/2], capvec),
2552 named_anchor("cap_fwd", [0,-length/2,_cap_h1], unit((capvec+FWD)/2)),
2553 named_anchor("cap_back", [0,+length/2,_cap_h2], unit((capvec+BACK)/2), 180),
2554 ];
2555 attachable(anchor,spin,orient, r1=r1, r2=r2, l=length, axis=BACK, anchors=anchors)
2556 {
2557 vnf_polyhedron(teardrop(ang=ang,cap_h=cap_h,r1=r1,r2=r2,cap_h1=cap_h1,cap_h2=cap_h2,circum=circum,realign=realign,
2558 length=length, chamfer1=chamfer1,chamfer2=chamfer2,chamfer=chamfer));
2559 children();
2560 }
2561}
2562
2563
2564function teardrop(h, r, ang=45, cap_h, r1, r2, d, d1, d2, cap_h1, cap_h2, chamfer, chamfer1, chamfer2, circum=false, realign=false,
2565 l, length, height, anchor=CENTER, spin=0, orient=UP) =
2566 let(
2567 r1 = get_radius(r=r, r1=r1, d=d, d1=d1, dflt=1),
2568 r2 = get_radius(r=r, r1=r2, d=d, d1=d2, dflt=1),
2569 length = one_defined([l, h, length, height],"l,h,length,height"),
2570 dummy0=assert(is_finite(length) && length>0, "length must be positive"),
2571 cap_h1 = first_defined([cap_h1, cap_h]),
2572 cap_h2 = first_defined([cap_h2, cap_h]),
2573 chamfer1 = first_defined([chamfer1,chamfer,0]),
2574 chamfer2 = first_defined([chamfer2,chamfer,0]),
2575 sides = segs(max(r1,r2)),
2576 profile1 = teardrop2d(r=r1, ang=ang, cap_h=cap_h1, $fn=sides, circum=circum, realign=realign,_extrapt=true),
2577 profile2 = teardrop2d(r=r2, ang=ang, cap_h=cap_h2, $fn=sides, circum=circum, realign=realign,_extrapt=true),
2578 tip_y1 = r1/cos(90-ang),
2579 tip_y2 = r2/cos(90-ang),
2580 _cap_h1 = min(default(cap_h1, tip_y1), tip_y1),
2581 _cap_h2 = min(default(cap_h2, tip_y2), tip_y2),
2582 capvec = unit([0, _cap_h1-_cap_h2, length]),
2583 dummy=
2584 assert(abs(chamfer1)+abs(chamfer2) <= length,"chamfers are too big to fit in the length")
2585 assert(chamfer1<=r1 && chamfer2<=r2, "Chamfers cannot be larger than raduis")
2586 assert(is_undef(cap_h1) || cap_h1-chamfer1 > r1*sin(ang), "chamfer1 is too big to work with the specified cap_h1")
2587 assert(is_undef(cap_h2) || cap_h2-chamfer2 > r2*sin(ang), "chamfer2 is too big to work with the specified cap_h2"),
2588 cprof1 = r1==chamfer1 ? repeat([0,0],len(profile1))
2589 : teardrop2d(r=r1-chamfer1, ang=ang, cap_h=u_add(cap_h1,-chamfer1), $fn=sides, circum=circum, realign=realign,_extrapt=true),
2590 cprof2 = r2==chamfer2 ? repeat([0,0],len(profile2))
2591 : teardrop2d(r=r2-chamfer2, ang=ang, cap_h=u_add(cap_h2,-chamfer2), $fn=sides, circum=circum, realign=realign,_extrapt=true),
2592 anchors = [
2593 named_anchor("cap", [0,0,(_cap_h1+_cap_h2)/2], capvec),
2594 named_anchor("cap_fwd", [0,-length/2,_cap_h1], unit((capvec+FWD)/2)),
2595 named_anchor("cap_back", [0,+length/2,_cap_h2], unit((capvec+BACK)/2), 180),
2596 ],
2597 vnf = vnf_vertex_array(
2598 points = [
2599 if (chamfer1!=0) fwd(length/2, xrot(90, path3d(cprof1))),
2600 fwd(length/2-abs(chamfer1), xrot(90, path3d(profile1))),
2601 back(length/2-abs(chamfer2), xrot(90, path3d(profile2))),
2602 if (chamfer2!=0) back(length/2, xrot(90, path3d(cprof2))),
2603 ],
2604 caps=true, col_wrap=true, reverse=true
2605 )
2606 ) reorient(anchor,spin,orient, r1=r1, r2=r2, l=l, axis=BACK, anchors=anchors, p=vnf);
2607
2608
2609// Function&Module: onion()
2610//
2611// Description:
2612// Creates a sphere with a conical hat, to make a 3D teardrop.
2613//
2614// Usage: As Module
2615// onion(r|d=, [ang=], [cap_h=], [circum=], [realign=], ...) [ATTACHMENTS];
2616// Usage: As Function
2617// vnf = onion(r|d=, [ang=], [cap_h=], [circum=], [realign=], ...);
2618//
2619// Arguments:
2620// r = radius of spherical portion of the bottom. Default: 1
2621// ang = Angle of cone on top from vertical. Default: 45 degrees
2622// cap_h = If given, height above sphere center to truncate teardrop shape. Default: `undef` (no truncation)
2623// ---
2624// circum = set to true to circumscribe the specified radius/diameter. Default: False
2625// realign = adjust point alignment to determine if bottom is flat or pointy. Default: False
2626// d = diameter of spherical portion of bottom.
2627// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#subsection-anchor). Default: `CENTER`
2628// spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#subsection-spin). Default: `0`
2629// orient = Vector to rotate top towards, after spin. See [orient](attachments.scad#subsection-orient). Default: `UP`
2630//
2631// Extra Anchors:
2632// cap = The center of the top of the cap, oriented with the cap face normal.
2633// tip = The position where an un-capped onion would come to a point, oriented in the direction the point is from the center.
2634//
2635// Example: Typical Shape
2636// onion(r=30, ang=30);
2637// Example: Crop Cap
2638// onion(r=30, ang=30, cap_h=40);
2639// Example: Close Crop
2640// onion(r=30, ang=30, cap_h=20);
2641// Example: Onions are useful for making the tops of large cylindrical voids.
2642// difference() {
2643// cuboid([100,50,100], anchor=FWD+BOT);
2644// down(0.1)
2645// cylinder(h=50,d=50,anchor=BOT)
2646// attach(TOP)
2647// onion(d=50, cap_h=30);
2648// }
2649// Example: Standard Connectors
2650// onion(d=30, ang=30, cap_h=20) show_anchors();
2651
2652module onion(r, ang=45, cap_h, d, circum=false, realign=false, anchor=CENTER, spin=0, orient=UP)
2653{
2654 r = get_radius(r=r, d=d, dflt=1);
2655 xyprofile = teardrop2d(r=r, ang=ang, cap_h=cap_h, circum=circum, realign=realign);
2656 tip_h = max(column(xyprofile,1));
2657 _cap_h = min(default(cap_h,tip_h), tip_h);
2658 anchors = [
2659 ["cap", [0,0,_cap_h], UP, 0],
2660 ["tip", [0,0,tip_h], UP, 0]
2661 ];
2662 attachable(anchor,spin,orient, r=r, anchors=anchors) {
2663 rotate_extrude(convexity=2) {
2664 difference() {
2665 polygon(xyprofile);
2666 square([2*r,2*max(_cap_h,r)+1], anchor=RIGHT);
2667 }
2668 }
2669 children();
2670 }
2671}
2672
2673
2674function onion(r, ang=45, cap_h, d, anchor=CENTER, spin=0, orient=UP) =
2675 let(
2676 r = get_radius(r=r, d=d, dflt=1),
2677 xyprofile = right_half(p=teardrop2d(r=r, ang=ang, cap_h=cap_h))[0],
2678 profile = xrot(90, p=path3d(xyprofile)),
2679 tip_h = max(column(xyprofile,1)),
2680 _cap_h = min(default(cap_h,tip_h), tip_h),
2681 anchors = [
2682 ["cap", [0,0,_cap_h], UP, 0],
2683 ["tip", [0,0,tip_h], UP, 0]
2684 ],
2685 sides = segs(r),
2686 step = 360 / sides,
2687 vnf = vnf_vertex_array(
2688 points=[for (a = [0:step:360-EPSILON]) zrot(a, p=profile)],
2689 caps=false, col_wrap=true, row_wrap=true, reverse=true
2690 )
2691 ) reorient(anchor,spin,orient, r=r, anchors=anchors, p=vnf);
2692
2693
2694// Section: Text
2695
2696// Module: text3d()
2697// Topics: Attachments, Text
2698// Usage:
2699// text3d(text, [h], [size], [font], ...);
2700// Description:
2701// Creates a 3D text block that supports anchoring and attachment to attachable objects. You cannot attach children to text.
2702// .
2703// Historically fonts were specified by their "body size", the height of the metal body
2704// on which the glyphs were cast. This means the size was an upper bound on the size
2705// of the font glyphs, not a direct measurement of their size. In digital typesetting,
2706// the metal body is replaced by an invisible box, the em square, whose side length is
2707// defined to be the font's size. The glyphs can be contained in that square, or they
2708// can extend beyond it, depending on the choices made by the font designer. As a
2709// result, the meaning of font size varies between fonts: two fonts at the "same" size
2710// can differ significantly in the actual size of their characters. Typographers
2711// customarily specify the size in the units of "points". A point is 1/72 inch. In
2712// OpenSCAD, you specify the size in OpenSCAD units (often treated as millimeters for 3d
2713// printing), so if you want points you will need to perform a suitable unit conversion.
2714// In addition, the OpenSCAD font system has a bug: if you specify size=s you will
2715// instead get a font whose size is s/0.72. For many fonts this means the size of
2716// capital letters will be approximately equal to s, because it is common for fonts to
2717// use about 70% of their height for the ascenders in the font. To get the customary
2718// font size, you should multiply your desired size by 0.72.
2719// .
2720// To find the fonts that you have available in your OpenSCAD installation,
2721// go to the Help menu and select "Font List".
2722// Arguments:
2723// text = Text to create.
2724// h = Extrusion height for the text. Default: 1
2725// size = The font will be created at this size divided by 0.72. Default: 10
2726// font = Font to use. Default: "Liberation Sans"
2727// ---
2728// halign = If given, specifies the horizontal alignment of the text. `"left"`, `"center"`, or `"right"`. Overrides `anchor=`.
2729// valign = If given, specifies the vertical alignment of the text. `"top"`, `"center"`, `"baseline"` or `"bottom"`. Overrides `anchor=`.
2730// spacing = The relative spacing multiplier between characters. Default: `1.0`
2731// direction = The text direction. `"ltr"` for left to right. `"rtl"` for right to left. `"ttb"` for top to bottom. `"btt"` for bottom to top. Default: `"ltr"`
2732// language = The language the text is in. Default: `"en"`
2733// script = The script the text is in. Default: `"latin"`
2734// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#subsection-anchor). Default: `"baseline"`
2735// spin = Rotate this many degrees around the Z axis. See [spin](attachments.scad#subsection-spin). Default: `0`
2736// orient = Vector to rotate top towards. See [orient](attachments.scad#subsection-orient). Default: `UP`
2737// See Also: path_text()
2738// Extra Anchors:
2739// "baseline" = Anchors at the baseline of the text, at the start of the string.
2740// str("baseline",VECTOR) = Anchors at the baseline of the text, modified by the X and Z components of the appended vector.
2741// Examples:
2742// text3d("Foobar", h=3, size=10);
2743// text3d("Foobar", h=2, size=12, font="Helvetica");
2744// text3d("Foobar", h=2, anchor=CENTER);
2745// text3d("Foobar", h=2, anchor=str("baseline",CENTER));
2746// text3d("Foobar", h=2, anchor=str("baseline",BOTTOM+RIGHT));
2747
2748module text3d(text, h=1, size=10, font="Helvetica", halign, valign, spacing=1.0, direction="ltr", language="em", script="latin", anchor="baseline[-1,0,-1]", spin=0, orient=UP) {
2749 no_children($children);
2750 dummy1 =
2751 assert(is_undef(anchor) || is_vector(anchor) || is_string(anchor), str("Got: ",anchor))
2752 assert(is_undef(spin) || is_vector(spin,3) || is_num(spin), str("Got: ",spin))
2753 assert(is_undef(orient) || is_vector(orient,3), str("Got: ",orient));
2754 anchor = default(anchor, CENTER);
2755 spin = default(spin, 0);
2756 orient = default(orient, UP);
2757 geom = attach_geom(size=[size,size,h]);
2758 anch = !any([for (c=anchor) c=="["])? anchor :
2759 let(
2760 parts = str_split(str_split(str_split(anchor,"]")[0],"[")[1],","),
2761 vec = [for (p=parts) parse_float(str_strip(p," ",start=true))]
2762 ) vec;
2763 ha = anchor=="baseline"? "left" :
2764 anchor==anch && is_string(anchor)? "center" :
2765 anch.x<0? "left" :
2766 anch.x>0? "right" :
2767 "center";
2768 va = starts_with(anchor,"baseline")? "baseline" :
2769 anchor==anch && is_string(anchor)? "center" :
2770 anch.y<0? "bottom" :
2771 anch.y>0? "top" :
2772 "center";
2773 base = anchor=="baseline"? CENTER :
2774 anchor==anch && is_string(anchor)? CENTER :
2775 anch.z<0? BOTTOM :
2776 anch.z>0? TOP :
2777 CENTER;
2778 m = _attach_transform(base,spin,orient,geom);
2779 multmatrix(m) {
2780 $parent_anchor = anchor;
2781 $parent_spin = spin;
2782 $parent_orient = orient;
2783 $parent_geom = geom;
2784 $parent_size = _attach_geom_size(geom);
2785 $attach_to = undef;
2786 if (_is_shown()) {
2787 _color($color) {
2788 linear_extrude(height=h, center=true)
2789 _text(
2790 text=text, size=size, font=font,
2791 halign=ha, valign=va, spacing=spacing,
2792 direction=direction, language=language,
2793 script=script
2794 );
2795 }
2796 }
2797 }
2798}
2799
2800
2801// This could be replaced with _cut_to_seg_u_form
2802function _cut_interp(pathcut, path, data) =
2803 [for(entry=pathcut)
2804 let(
2805 a = path[entry[1]-1],
2806 b = path[entry[1]],
2807 c = entry[0],
2808 i = max_index(v_abs(b-a)),
2809 factor = (c[i]-a[i])/(b[i]-a[i])
2810 )
2811 (1-factor)*data[entry[1]-1]+ factor * data[entry[1]]
2812 ];
2813
2814
2815// Module: path_text()
2816// Usage:
2817// path_text(path, text, [size], [thickness], [font], [lettersize], [offset], [reverse], [normal], [top], [textmetrics], [kern])
2818// Description:
2819// Place the text letter by letter onto the specified path using textmetrics (if available and requested)
2820// or user specified letter spacing. The path can be 2D or 3D. In 2D the text appears along the path with letters upright
2821// as determined by the path direction. In 3D by default letters are positioned on the tangent line to the path with the path normal
2822// pointing toward the reader. The path normal points away from the center of curvature (the opposite of the normal produced
2823// by path_normals()). Note that this means that if the center of curvature switches sides the text will flip upside down.
2824// If you want text on such a path you must supply your own normal or top vector.
2825// .
2826// Text appears starting at the beginning of the path, so if the 3D path moves right to left
2827// then a left-to-right reading language will display in the wrong order. (For a 2D path text will appear upside down.)
2828// The text for a 3D path appears positioned to be read from "outside" of the curve (from a point on the other side of the
2829// curve from the center of curvature). If you need the text to read properly from the inside, you can set reverse to
2830// true to flip the text, or supply your own normal.
2831// .
2832// If you do not have the experimental textmetrics feature enabled then you must specify the space for the letters
2833// using lettersize, which can be a scalar or array. You will have the easiest time getting good results by using
2834// a monospace font such as Courier. Note that even with text metrics, spacing may be different because path_text()
2835// doesn't do kerning to adjust positions of individual glyphs. Also if your font has ligatures they won't be used.
2836// .
2837// By default letters appear centered on the path. The offset can be specified to shift letters toward the reader (in
2838// the direction of the normal).
2839// .
2840// You can specify your own normal by setting `normal` to a direction or a list of directions. Your normal vector should
2841// point toward the reader. You can also specify
2842// top, which directs the top of the letters in a desired direction. If you specify your own directions and they
2843// are not perpendicular to the path then the direction you specify will take priority and the
2844// letters will not rest on the tangent line of the path. Note that the normal or top directions that you
2845// specify must not be parallel to the path.
2846// .
2847// Historically fonts were specified by their "body size", the height of the metal body
2848// on which the glyphs were cast. This means the size was an upper bound on the size
2849// of the font glyphs, not a direct measurement of their size. In digital typesetting,
2850// the metal body is replaced by an invisible box, the em square, whose side length is
2851// defined to be the font's size. The glyphs can be contained in that square, or they
2852// can extend beyond it, depending on the choices made by the font designer. As a
2853// result, the meaning of font size varies between fonts: two fonts at the "same" size
2854// can differ significantly in the actual size of their characters. Typographers
2855// customarily specify the size in the units of "points". A point is 1/72 inch. In
2856// OpenSCAD, you specify the size in OpenSCAD units (often treated as millimeters for 3d
2857// printing), so if you want points you will need to perform a suitable unit conversion.
2858// In addition, the OpenSCAD font system has a bug: if you specify size=s you will
2859// instead get a font whose size is s/0.72. For many fonts this means the size of
2860// capital letters will be approximately equal to s, because it is common for fonts to
2861// use about 70% of their height for the ascenders in the font. To get the customary
2862// font size, you should multiply your desired size by 0.72.
2863// .
2864// To find the fonts that you have available in your OpenSCAD installation,
2865// go to the Help menu and select "Font List".
2866// Arguments:
2867// path = path to place the text on
2868// text = text to create
2869// size = The font will be created at this size divided by 0.72. Default: 10
2870// thickness = thickness of letters (not allowed for 2D path)
2871// font = font to use. Default: "Liberation Sans"
2872// ---
2873// lettersize = scalar or array giving size of letters
2874// center = center text on the path instead of starting at the first point. Default: false
2875// offset = distance to shift letters "up" (towards the reader). Not allowed for 2D path. Default: 0
2876// normal = direction or list of directions pointing towards the reader of the text. Not allowed for 2D path.
2877// top = direction or list of directions pointing toward the top of the text
2878// reverse = reverse the letters if true. Not allowed for 2D path. Default: false
2879// textmetrics = if set to true and lettersize is not given then use the experimental textmetrics feature. You must be running a dev snapshot that includes this feature and have the feature turned on in your preferences. Default: false
2880// kern = scalar or array giving size adjustments for each letter. Default: 0
2881// Example(3D,NoScales): The examples use Courier, a monospaced font. The width is 1/1.2 times the specified size for this font. This text could wrap around a cylinder.
2882// path = path3d(arc(100, r=25, angle=[245, 370]));
2883// color("red")stroke(path, width=.3);
2884// path_text(path, "Example text", font="Courier", size=5, lettersize = 5/1.2);
2885// Example(3D,NoScales): By setting the normal to UP we can get text that lies flat, for writing around the edge of a disk:
2886// path = path3d(arc(100, r=25, angle=[245, 370]));
2887// color("red")stroke(path, width=.3);
2888// path_text(path, "Example text", font="Courier", size=5, lettersize = 5/1.2, normal=UP);
2889// Example(3D,NoScales): If we want text that reads from the other side we can use reverse. Note we have to reverse the direction of the path and also set the reverse option.
2890// path = reverse(path3d(arc(100, r=25, angle=[65, 190])));
2891// color("red")stroke(path, width=.3);
2892// path_text(path, "Example text", font="Courier", size=5, lettersize = 5/1.2, reverse=true);
2893// Example(3D,Med,NoScales): text debossed onto a cylinder in a spiral. The text is 1 unit deep because it is half in, half out.
2894// text = ("A long text example to wrap around a cylinder, possibly for a few times.");
2895// L = 5*len(text);
2896// maxang = 360*L/(PI*50);
2897// spiral = [for(a=[0:1:maxang]) [25*cos(a), 25*sin(a), 10-30/maxang*a]];
2898// difference(){
2899// cyl(d=50, l=50, $fn=120);
2900// path_text(spiral, text, size=5, lettersize=5/1.2, font="Courier", thickness=2);
2901// }
2902// Example(3D,Med,NoScales): Same example but text embossed. Make sure you have enough depth for the letters to fully overlap the object.
2903// text = ("A long text example to wrap around a cylinder, possibly for a few times.");
2904// L = 5*len(text);
2905// maxang = 360*L/(PI*50);
2906// spiral = [for(a=[0:1:maxang]) [25*cos(a), 25*sin(a), 10-30/maxang*a]];
2907// cyl(d=50, l=50, $fn=120);
2908// path_text(spiral, text, size=5, lettersize=5/1.2, font="Courier", thickness=2);
2909// Example(3D,NoScales): Here the text baseline sits on the path. (Note the default orientation makes text readable from below, so we specify the normal.)
2910// path = arc(100, points = [[-20, 0, 20], [0,0,5], [20,0,20]]);
2911// color("red")stroke(path,width=.2);
2912// path_text(path, "Example Text", size=5, lettersize=5/1.2, font="Courier", normal=FRONT);
2913// Example(3D,NoScales): If we use top to orient the text upward, the text baseline is no longer aligned with the path.
2914// path = arc(100, points = [[-20, 0, 20], [0,0,5], [20,0,20]]);
2915// color("red")stroke(path,width=.2);
2916// path_text(path, "Example Text", size=5, lettersize=5/1.2, font="Courier", top=UP);
2917// Example(3D,Med,NoScales): This sine wave wrapped around the cylinder has a twisting normal that produces wild letter layout. We fix it with a custom normal which is different at every path point.
2918// path = [for(theta = [0:360]) [25*cos(theta), 25*sin(theta), 4*cos(theta*4)]];
2919// normal = [for(theta = [0:360]) [cos(theta), sin(theta),0]];
2920// zrot(-120)
2921// difference(){
2922// cyl(r=25, h=20, $fn=120);
2923// path_text(path, "A sine wave wiggles", font="Courier", lettersize=5/1.2, size=5, normal=normal);
2924// }
2925// Example(3D,Med,NoScales): The path center of curvature changes, and the text flips.
2926// path = zrot(-120,p=path3d( concat(arc(100, r=25, angle=[0,90]), back(50,p=arc(100, r=25, angle=[268, 180])))));
2927// color("red")stroke(path,width=.2);
2928// path_text(path, "A shorter example", size=5, lettersize=5/1.2, font="Courier", thickness=2);
2929// Example(3D,Med,NoScales): We can fix it with top:
2930// path = zrot(-120,p=path3d( concat(arc(100, r=25, angle=[0,90]), back(50,p=arc(100, r=25, angle=[268, 180])))));
2931// color("red")stroke(path,width=.2);
2932// path_text(path, "A shorter example", size=5, lettersize=5/1.2, font="Courier", thickness=2, top=UP);
2933// Example(2D,NoScales): With a 2D path instead of 3D there's no ambiguity about direction and it works by default:
2934// path = zrot(-120,p=concat(arc(100, r=25, angle=[0,90]), back(50,p=arc(100, r=25, angle=[268, 180]))));
2935// color("red")stroke(path,width=.2);
2936// path_text(path, "A shorter example", size=5, lettersize=5/1.2, font="Courier");
2937// Example(3D,NoScales): The kern parameter lets you adjust the letter spacing either with a uniform value for each letter, or with an array to make adjustments throughout the text. Here we show a case where adding some extra space gives a better look in a tight circle. When textmetrics are off, `lettersize` can do this job, but with textmetrics, you'll need to use `kern` to make adjustments relative to the text metric sizes.
2938// path = path3d(arc(100, r=12, angle=[150, 450]));
2939// color("red")stroke(path, width=.3);
2940// kern = [1,1.2,1,1,.3,-.2,1,0,.8,1,1.1,1];
2941// path_text(path, "Example text", font="Courier", size=5, lettersize = 5/1.2, kern=kern, normal=UP);
2942
2943module path_text(path, text, font, size, thickness, lettersize, offset=0, reverse=false, normal, top, center=false, textmetrics=false, kern=0)
2944{
2945 no_children($children);
2946 dummy2=assert(is_path(path,[2,3]),"Must supply a 2d or 3d path")
2947 assert(num_defined([normal,top])<=1, "Cannot define both \"normal\" and \"top\"");
2948 dim = len(path[0]);
2949 normalok = is_undef(normal) || is_vector(normal,3) || (is_path(normal,3) && len(normal)==len(path));
2950 topok = is_undef(top) || is_vector(top,dim) || (dim==2 && is_vector(top,3) && top[2]==0)
2951 || (is_path(top,dim) && len(top)==len(path));
2952 dummy4 = assert(dim==3 || is_undef(thickness), "Cannot give a thickness with 2d path")
2953 assert(dim==3 || !reverse, "Reverse not allowed with 2d path")
2954 assert(dim==3 || offset==0, "Cannot give offset with 2d path")
2955 assert(dim==3 || is_undef(normal), "Cannot define \"normal\" for a 2d path, only \"top\"")
2956 assert(normalok,"\"normal\" must be a vector or path compatible with the given path")
2957 assert(topok,"\"top\" must be a vector or path compatible with the given path");
2958 thickness = first_defined([thickness,1]);
2959 normal = is_vector(normal) ? repeat(normal, len(path))
2960 : is_def(normal) ? normal
2961 : undef;
2962
2963 top = is_vector(top) ? repeat(dim==2?point2d(top):top, len(path))
2964 : is_def(top) ? top
2965 : undef;
2966
2967 kern = force_list(kern, len(text));
2968 dummy3 = assert(is_list(kern) && len(kern)==len(text), "kern must be a scalar or list whose length is len(text)");
2969
2970 lsize = kern + (
2971 is_def(lettersize) ? force_list(lettersize, len(text))
2972 : textmetrics ? [for(letter=text) let(t=textmetrics(letter, font=font, size=size)) t.advance[0]]
2973 : assert(false, "textmetrics disabled: Must specify letter size")
2974 );
2975 textlength = sum(lsize);
2976 dummy1 = assert(textlength<=path_length(path),"Path is too short for the text");
2977
2978 start = center ? (path_length(path) - textlength)/2 : 0;
2979
2980 pts = path_cut_points(path, add_scalar([0, each cumsum(lsize)],start+lsize[0]/2), direction=true);
2981
2982
2983 usernorm = is_def(normal);
2984 usetop = is_def(top);
2985
2986 normpts = is_undef(normal) ? (reverse?1:-1)*column(pts,3) : _cut_interp(pts,path, normal);
2987 toppts = is_undef(top) ? undef : _cut_interp(pts,path,top);
2988 for (i = idx(text)) {
2989 tangent = pts[i][2];
2990 checks =
2991 assert(!usetop || !approx(tangent*toppts[i],norm(top[i])*norm(tangent)),
2992 str("Specified top direction parallel to path at character ",i))
2993 assert(usetop || !approx(tangent*normpts[i],norm(normpts[i])*norm(tangent)),
2994 str("Specified normal direction parallel to path at character ",i));
2995 adjustment = usetop ? (tangent*toppts[i])*toppts[i]/(toppts[i]*toppts[i])
2996 : usernorm ? (tangent*normpts[i])*normpts[i]/(normpts[i]*normpts[i])
2997 : [0,0,0];
2998 move(pts[i][0]) {
2999 if (dim==3) {
3000 frame_map(
3001 x=tangent-adjustment,
3002 z=usetop ? undef : normpts[i],
3003 y=usetop ? toppts[i] : undef
3004 ) up(offset-thickness/2) {
3005 linear_extrude(height=thickness)
3006 left(lsize[0]/2)
3007 text(text[i], font=font, size=size);
3008 }
3009 } else {
3010 frame_map(
3011 x=point3d(tangent-adjustment),
3012 y=point3d(usetop ? toppts[i] : -normpts[i])
3013 ) left(lsize[0]/2) {
3014 text(text[i], font=font, size=size);
3015 }
3016 }
3017 }
3018 }
3019}
3020
3021
3022
3023// Section: Miscellaneous
3024
3025
3026// Module: interior_fillet()
3027//
3028// Description:
3029// Creates a shape that can be unioned into a concave joint between two faces, to fillet them.
3030// Center this part along the concave edge to be chamfered and union it in.
3031//
3032// Usage: Typical
3033// interior_fillet(l, r, [ang], [overlap], ...) [ATTACHMENTS];
3034// interior_fillet(l, d=, [ang=], [overlap=], ...) [ATTACHMENTS];
3035//
3036// Arguments:
3037// l = Length of edge to fillet.
3038// r = Radius of fillet.
3039// ang = Angle between faces to fillet.
3040// overlap = Overlap size for unioning with faces.
3041// ---
3042// d = Diameter of fillet.
3043// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#subsection-anchor). Default: `FRONT+LEFT`
3044// spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#subsection-spin). Default: `0`
3045// orient = Vector to rotate top towards, after spin. See [orient](attachments.scad#subsection-orient). Default: `UP`
3046//
3047// Example:
3048// union() {
3049// translate([0,2,-4])
3050// cube([20, 4, 24], anchor=BOTTOM);
3051// translate([0,-10,-4])
3052// cube([20, 20, 4], anchor=BOTTOM);
3053// color("green")
3054// interior_fillet(
3055// l=20, r=10,
3056// spin=180, orient=RIGHT
3057// );
3058// }
3059//
3060// Examples:
3061// interior_fillet(l=10, r=20, ang=60);
3062// interior_fillet(l=10, r=20, ang=90);
3063// interior_fillet(l=10, r=20, ang=120);
3064//
3065// Example: Using with Attachments
3066// cube(50,center=true) {
3067// position(FRONT+LEFT)
3068// interior_fillet(l=50, r=10, spin=-90);
3069// position(BOT+FRONT)
3070// interior_fillet(l=50, r=10, spin=180, orient=RIGHT);
3071// }
3072
3073module interior_fillet(l=1.0, r, ang=90, overlap=0.01, d, anchor=CENTER, spin=0, orient=UP) {
3074 r = get_radius(r=r, d=d, dflt=1);
3075 steps = ceil(segs(r)*(180-ang)/360);
3076 arc = arc(n=steps+1, r=r, corner=[polar_to_xy(r,ang),[0,0],[r,0]]);
3077 maxx = last(arc).x;
3078 maxy = arc[0].y;
3079 path = [
3080 [maxx, -overlap],
3081 polar_to_xy(overlap, 180+ang/2),
3082 arc[0] + polar_to_xy(overlap, 90+ang),
3083 each arc
3084 ];
3085 attachable(anchor,spin,orient, size=[2*maxx,2*maxy,l]) {
3086 if (l > 0) {
3087 linear_extrude(height=l, convexity=4, center=true) {
3088 polygon(path);
3089 }
3090 }
3091 children();
3092 }
3093}
3094
3095
3096// Function&Module: heightfield()
3097// Usage: As Module
3098// heightfield(data, [size], [bottom], [maxz], [xrange], [yrange], [style], [convexity], ...) [ATTACHMENTS];
3099// Usage: As Function
3100// vnf = heightfield(data, [size], [bottom], [maxz], [xrange], [yrange], [style], ...);
3101// Topics: Textures, Heightfield
3102// Description:
3103// Given a regular rectangular 2D grid of scalar values, or a function literal, generates a 3D
3104// surface where the height at any given point is the scalar value for that position.
3105// One script to convert a grayscale image to a heightfield array in a .scad file can be found at:
3106// https://raw.githubusercontent.com/revarbat/BOSL2/master/scripts/img2scad.py
3107// Arguments:
3108// data = This is either the 2D rectangular array of heights, or a function literal that takes X and Y arguments.
3109// size = The [X,Y] size of the surface to create. If given as a scalar, use it for both X and Y sizes. Default: `[100,100]`
3110// bottom = The Z coordinate for the bottom of the heightfield object to create. Any heights lower than this will be truncated to very slightly above this height. Default: -20
3111// maxz = The maximum height to model. Truncates anything taller to this height. Default: 99
3112// xrange = A range of values to iterate X over when calculating a surface from a function literal. Default: [-1 : 0.01 : 1]
3113// yrange = A range of values to iterate Y over when calculating a surface from a function literal. Default: [-1 : 0.01 : 1]
3114// style = The style of subdividing the quads into faces. Valid options are "default", "alt", and "quincunx". Default: "default"
3115// ---
3116// convexity = Max number of times a line could intersect a wall of the surface being formed. Module only. Default: 10
3117// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#subsection-anchor). Default: `CENTER`
3118// spin = Rotate this many degrees around the Z axis. See [spin](attachments.scad#subsection-spin). Default: `0`
3119// orient = Vector to rotate top towards. See [orient](attachments.scad#subsection-orient). Default: `UP`
3120// See Also: heightfield(), cylindrical_heightfield()
3121// Example:
3122// heightfield(size=[100,100], bottom=-20, data=[
3123// for (y=[-180:4:180]) [
3124// for(x=[-180:4:180])
3125// 10*cos(3*norm([x,y]))
3126// ]
3127// ]);
3128// Example:
3129// intersection() {
3130// heightfield(size=[100,100], data=[
3131// for (y=[-180:5:180]) [
3132// for(x=[-180:5:180])
3133// 10+5*cos(3*x)*sin(3*y)
3134// ]
3135// ]);
3136// cylinder(h=50,d=100);
3137// }
3138// Example: Heightfield by Function
3139// fn = function (x,y) 10*sin(x*360)*cos(y*360);
3140// heightfield(size=[100,100], data=fn);
3141// Example: Heightfield by Function, with Specific Ranges
3142// fn = function (x,y) 2*cos(5*norm([x,y]));
3143// heightfield(
3144// size=[100,100], bottom=-20, data=fn,
3145// xrange=[-180:2:180], yrange=[-180:2:180]
3146// );
3147
3148module heightfield(data, size=[100,100], bottom=-20, maxz=100, xrange=[-1:0.04:1], yrange=[-1:0.04:1], style="default", convexity=10, anchor=CENTER, spin=0, orient=UP)
3149{
3150 size = is_num(size)? [size,size] : point2d(size);
3151 vnf = heightfield(data=data, size=size, xrange=xrange, yrange=yrange, bottom=bottom, maxz=maxz, style=style);
3152 attachable(anchor,spin,orient, vnf=vnf) {
3153 vnf_polyhedron(vnf, convexity=convexity);
3154 children();
3155 }
3156}
3157
3158
3159function heightfield(data, size=[100,100], bottom=-20, maxz=100, xrange=[-1:0.04:1], yrange=[-1:0.04:1], style="default", anchor=CENTER, spin=0, orient=UP) =
3160 assert(is_list(data) || is_function(data))
3161 let(
3162 size = is_num(size)? [size,size] : point2d(size),
3163 xvals = is_list(data)
3164 ? [for (i=idx(data[0])) i]
3165 : assert(is_list(xrange)||is_range(xrange)) [for (x=xrange) x],
3166 yvals = is_list(data)
3167 ? [for (i=idx(data)) i]
3168 : assert(is_list(yrange)||is_range(yrange)) [for (y=yrange) y],
3169 xcnt = len(xvals),
3170 minx = min(xvals),
3171 maxx = max(xvals),
3172 ycnt = len(yvals),
3173 miny = min(yvals),
3174 maxy = max(yvals),
3175 verts = is_list(data) ? [
3176 for (y = [0:1:ycnt-1]) [
3177 for (x = [0:1:xcnt-1]) [
3178 size.x * (x/(xcnt-1)-0.5),
3179 size.y * (y/(ycnt-1)-0.5),
3180 data[y][x]
3181 ]
3182 ]
3183 ] : [
3184 for (y = yrange) [
3185 for (x = xrange) let(
3186 z = data(x,y)
3187 ) [
3188 size.x * ((x-minx)/(maxx-minx)-0.5),
3189 size.y * ((y-miny)/(maxy-miny)-0.5),
3190 min(maxz, max(bottom+0.1, default(z,0)))
3191 ]
3192 ]
3193 ],
3194 vnf = vnf_join([
3195 vnf_vertex_array(verts, style=style, reverse=true),
3196 vnf_vertex_array([
3197 verts[0],
3198 [for (v=verts[0]) [v.x, v.y, bottom]],
3199 ]),
3200 vnf_vertex_array([
3201 [for (v=verts[ycnt-1]) [v.x, v.y, bottom]],
3202 verts[ycnt-1],
3203 ]),
3204 vnf_vertex_array([
3205 [for (r=verts) let(v=r[0]) [v.x, v.y, bottom]],
3206 [for (r=verts) let(v=r[0]) v],
3207 ]),
3208 vnf_vertex_array([
3209 [for (r=verts) let(v=r[xcnt-1]) v],
3210 [for (r=verts) let(v=r[xcnt-1]) [v.x, v.y, bottom]],
3211 ]),
3212 vnf_vertex_array([
3213 [
3214 for (v=verts[0]) [v.x, v.y, bottom],
3215 for (r=verts) let(v=r[xcnt-1]) [v.x, v.y, bottom],
3216 ], [
3217 for (r=verts) let(v=r[0]) [v.x, v.y, bottom],
3218 for (v=verts[ycnt-1]) [v.x, v.y, bottom],
3219 ]
3220 ])
3221 ])
3222 ) reorient(anchor,spin,orient, vnf=vnf, p=vnf);
3223
3224
3225// Function&Module: cylindrical_heightfield()
3226// Usage: As Function
3227// vnf = cylindrical_heightfield(data, l, r|d=, [base=], [transpose=], [aspect=]);
3228// Usage: As Module
3229// cylindrical_heightfield(data, l, r|d=, [base=], [transpose=], [aspect=]) [ATTACHMENTS];
3230// Topics: Extrusion, Textures, Knurling, Heightfield
3231// Description:
3232// Given a regular rectangular 2D grid of scalar values, or a function literal of signature (x,y), generates
3233// a cylindrical 3D surface where the height at any given point above the radius `r=`, is the scalar value
3234// for that position.
3235// One script to convert a grayscale image to a heightfield array in a .scad file can be found at:
3236// https://raw.githubusercontent.com/revarbat/BOSL2/master/scripts/img2scad.py
3237// Arguments:
3238// data = This is either the 2D rectangular array of heights, or a function literal of signature `(x, y)`.
3239// l = The length of the cylinder to wrap around.
3240// r = The radius of the cylinder to wrap around.
3241// ---
3242// r1 = The radius of the bottom of the cylinder to wrap around.
3243// r2 = The radius of the top of the cylinder to wrap around.
3244// d = The diameter of the cylinder to wrap around.
3245// d1 = The diameter of the bottom of the cylinder to wrap around.
3246// d2 = The diameter of the top of the cylinder to wrap around.
3247// base = The radius for the bottom of the heightfield object to create. Any heights smaller than this will be truncated to very slightly above this height. Default: -20
3248// transpose = If true, swaps the radial and length axes of the data. Default: false
3249// aspect = The aspect ratio of the generated heightfield at the surface of the cylinder. Default: 1
3250// xrange = A range of values to iterate X over when calculating a surface from a function literal. Default: [-1 : 0.01 : 1]
3251// yrange = A range of values to iterate Y over when calculating a surface from a function literal. Default: [-1 : 0.01 : 1]
3252// maxh = The maximum height above the radius to model. Truncates anything taller to this height. Default: 99
3253// style = The style of subdividing the quads into faces. Valid options are "default", "alt", and "quincunx". Default: "default"
3254// convexity = Max number of times a line could intersect a wall of the surface being formed. Module only. Default: 10
3255// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#subsection-anchor). Default: `CENTER`
3256// spin = Rotate this many degrees around the Z axis. See [spin](attachments.scad#subsection-spin). Default: `0`
3257// orient = Vector to rotate top towards. See [orient](attachments.scad#subsection-orient). Default: `UP`
3258// See Also: heightfield(), cylindrical_heightfield()
3259// Example(VPD=400;VPR=[55,0,150]):
3260// cylindrical_heightfield(l=100, r=30, base=5, data=[
3261// for (y=[-180:4:180]) [
3262// for(x=[-180:4:180])
3263// 5*cos(5*norm([x,y]))+5
3264// ]
3265// ]);
3266// Example(VPD=400;VPR=[55,0,150]):
3267// cylindrical_heightfield(l=100, r1=60, r2=30, base=5, data=[
3268// for (y=[-180:4:180]) [
3269// for(x=[-180:4:180])
3270// 5*cos(5*norm([x,y]))+5
3271// ]
3272// ]);
3273// Example(VPD=400;VPR=[55,0,150]): Heightfield by Function
3274// fn = function (x,y) 5*sin(x*360)*cos(y*360)+5;
3275// cylindrical_heightfield(l=100, r=30, data=fn);
3276// Example(VPD=400;VPR=[55,0,150]): Heightfield by Function, with Specific Ranges
3277// fn = function (x,y) 2*cos(5*norm([x,y]));
3278// cylindrical_heightfield(
3279// l=100, r=30, base=5, data=fn,
3280// xrange=[-180:2:180], yrange=[-180:2:180]
3281// );
3282
3283function cylindrical_heightfield(
3284 data, l, r, base=1,
3285 transpose=false, aspect=1,
3286 style="min_edge", maxh=99,
3287 xrange=[-1:0.01:1],
3288 yrange=[-1:0.01:1],
3289 r1, r2, d, d1, d2, h, height,
3290 anchor=CTR, spin=0, orient=UP
3291) =
3292 let(
3293 l = first_defined([l, h, height]),
3294 r1 = get_radius(r1=r1, r=r, d1=d1, d=d),
3295 r2 = get_radius(r1=r2, r=r, d1=d2, d=d)
3296 )
3297 assert(is_finite(l) && l>0, "Must supply one of l=, h=, or height= as a finite positive number.")
3298 assert(is_finite(r1) && r1>0, "Must supply one of r=, r1=, d=, or d1= as a finite positive number.")
3299 assert(is_finite(r2) && r2>0, "Must supply one of r=, r2=, d=, or d2= as a finite positive number.")
3300 assert(is_finite(base) && base>0, "Must supply base= as a finite positive number.")
3301 assert(is_matrix(data)||is_function(data), "data= must be a function literal, or contain a 2D array of numbers.")
3302 let(
3303 xvals = is_list(data)? [for (x = idx(data[0])) x] :
3304 is_range(xrange)? [for (x = xrange) x] :
3305 assert(false, "xrange= must be given as a range if data= is a function literal."),
3306 yvals = is_list(data)? [for (y = idx(data)) y] :
3307 is_range(yrange)? [for (y = yrange) y] :
3308 assert(false, "yrange= must be given as a range if data= is a function literal."),
3309 xlen = len(xvals),
3310 ylen = len(yvals),
3311 stepy = l / (ylen-1),
3312 stepx = stepy * aspect,
3313 maxr = max(r1,r2),
3314 circ = 2 * PI * maxr,
3315 astep = 360 / circ * stepx,
3316 arc = astep * (xlen-1),
3317 bsteps = round(segs(maxr-base) * arc / 360),
3318 bstep = arc / bsteps
3319 )
3320 assert(stepx*xlen <= circ, str("heightfield (",xlen," x ",ylen,") needs a radius of at least ",maxr*stepx*xlen/circ))
3321 let(
3322 verts = [
3323 for (yi = idx(yvals)) let(
3324 z = yi * stepy - l/2,
3325 rr = lerp(r1, r2, yi/(ylen-1))
3326 ) [
3327 cylindrical_to_xyz(rr-base, -arc/2, z),
3328 for (xi = idx(xvals)) let( a = xi*astep )
3329 let(
3330 rad = transpose? (
3331 is_list(data)? data[xi][yi] : data(yvals[yi],xvals[xi])
3332 ) : (
3333 is_list(data)? data[yi][xi] : data(xvals[xi],yvals[yi])
3334 ),
3335 rad2 = constrain(rad, 0.01-base, maxh)
3336 )
3337 cylindrical_to_xyz(rr+rad2, a-arc/2, z),
3338 cylindrical_to_xyz(rr-base, arc/2, z),
3339 for (b = [1:1:bsteps-1]) let( a = arc/2-b*bstep )
3340 cylindrical_to_xyz((z>0?r2:r1)-base, a, l/2*(z>0?1:-1)),
3341 ]
3342 ],
3343 vnf = vnf_vertex_array(verts, caps=true, col_wrap=true, reverse=true, style=style)
3344 ) reorient(anchor,spin,orient, r1=r1, r2=r2, l=l, p=vnf);
3345
3346
3347module cylindrical_heightfield(
3348 data, l, r, base=1,
3349 transpose=false, aspect=1,
3350 style="min_edge", convexity=10,
3351 xrange=[-1:0.01:1], yrange=[-1:0.01:1],
3352 maxh=99, r1, r2, d, d1, d2, h, height,
3353 anchor=CTR, spin=0, orient=UP
3354) {
3355 l = first_defined([l, h, height]);
3356 r1 = get_radius(r1=r1, r=r, d1=d1, d=d);
3357 r2 = get_radius(r1=r2, r=r, d1=d2, d=d);
3358 vnf = cylindrical_heightfield(
3359 data, l=l, r1=r1, r2=r2, base=base,
3360 xrange=xrange, yrange=yrange,
3361 maxh=maxh, transpose=transpose,
3362 aspect=aspect, style=style
3363 );
3364 attachable(anchor,spin,orient, r1=r1, r2=r2, l=l) {
3365 vnf_polyhedron(vnf, convexity=convexity);
3366 children();
3367 }
3368}
3369
3370
3371// Module: ruler()
3372// Usage:
3373// ruler(length, width, [thickness=], [depth=], [labels=], [pipscale=], [maxscale=], [colors=], [alpha=], [unit=], [inch=]) [ATTACHMENTS];
3374// Description:
3375// Creates a ruler for checking dimensions of the model
3376// Arguments:
3377// length = length of the ruler. Default 100
3378// width = width of the ruler. Default: size of the largest unit division
3379// ---
3380// thickness = thickness of the ruler. Default: 1
3381// depth = the depth of mark subdivisions. Default: 3
3382// labels = draw numeric labels for depths where labels are larger than 1. Default: false
3383// pipscale = width scale of the pips relative to the next size up. Default: 1/3
3384// maxscale = log10 of the maximum width divisions to display. Default: based on input length
3385// colors = colors to use for the ruler, a list of two values. Default: `["black","white"]`
3386// alpha = transparency value. Default: 1.0
3387// unit = unit to mark. Scales the ruler marks to a different length. Default: 1
3388// inch = set to true for a ruler scaled to inches (assuming base dimension is mm). Default: false
3389// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#subsection-anchor). Default: `LEFT+BACK+TOP`
3390// spin = Rotate this many degrees around the Z axis. See [spin](attachments.scad#subsection-spin). Default: `0`
3391// orient = Vector to rotate top towards. See [orient](attachments.scad#subsection-orient). Default: `UP`
3392// Examples(2D,Big):
3393// ruler(100,depth=3);
3394// ruler(100,depth=3,labels=true);
3395// ruler(27);
3396// ruler(27,maxscale=0);
3397// ruler(100,pipscale=3/4,depth=2);
3398// ruler(100,width=2,depth=2);
3399// Example(2D,Big): Metric vs Imperial
3400// ruler(12,width=50,inch=true,labels=true,maxscale=0);
3401// fwd(50)ruler(300,width=50,labels=true);
3402
3403module ruler(length=100, width, thickness=1, depth=3, labels=false, pipscale=1/3, maxscale,
3404 colors=["black","white"], alpha=1.0, unit=1, inch=false, anchor=LEFT+BACK+TOP, spin=0, orient=UP)
3405{
3406 inchfactor = 25.4;
3407 checks =
3408 assert(depth<=5, "Cannot render scales smaller than depth=5")
3409 assert(len(colors)==2, "colors must contain a list of exactly two colors.");
3410 length = inch ? inchfactor * length : length;
3411 unit = inch ? inchfactor*unit : unit;
3412 maxscale = is_def(maxscale)? maxscale : floor(log(length/unit-EPSILON));
3413 scales = unit * [for(logsize = [maxscale:-1:maxscale-depth+1]) pow(10,logsize)];
3414 widthfactor = (1-pipscale) / (1-pow(pipscale,depth));
3415 width = default(width, scales[0]);
3416 widths = width * widthfactor * [for(logsize = [0:-1:-depth+1]) pow(pipscale,-logsize)];
3417 offsets = concat([0],cumsum(widths));
3418 attachable(anchor,spin,orient, size=[length,width,thickness]) {
3419 translate([-length/2, -width/2, 0])
3420 for(i=[0:1:len(scales)-1]) {
3421 count = ceil(length/scales[i]);
3422 fontsize = 0.5*min(widths[i], scales[i]/ceil(log(count*scales[i]/unit)));
3423 back(offsets[i]) {
3424 xcopies(scales[i], n=count, sp=[0,0,0]) union() {
3425 actlen = ($idx<count-1) || approx(length%scales[i],0) ? scales[i] : length % scales[i];
3426 color(colors[$idx%2], alpha=alpha) {
3427 w = i>0 ? quantup(widths[i],1/1024) : widths[i]; // What is the i>0 test supposed to do here?
3428 cube([quantup(actlen,1/1024),quantup(w,1/1024),thickness], anchor=FRONT+LEFT);
3429 }
3430 mark =
3431 i == 0 && $idx % 10 == 0 && $idx != 0 ? 0 :
3432 i == 0 && $idx % 10 == 9 && $idx != count-1 ? 1 :
3433 $idx % 10 == 4 ? 1 :
3434 $idx % 10 == 5 ? 0 : -1;
3435 flip = 1-mark*2;
3436 if (mark >= 0) {
3437 marklength = min(widths[i]/2, scales[i]*2);
3438 markwidth = marklength*0.4;
3439 translate([mark*scales[i], widths[i], 0]) {
3440 color(colors[1-$idx%2], alpha=alpha) {
3441 linear_extrude(height=thickness+scales[i]/100, convexity=2, center=true) {
3442 polygon(scale([flip*markwidth, marklength],p=[[0,0], [1, -1], [0,-0.9]]));
3443 }
3444 }
3445 }
3446 }
3447 if (labels && scales[i]/unit+EPSILON >= 1) {
3448 color(colors[($idx+1)%2], alpha=alpha) {
3449 linear_extrude(height=thickness+scales[i]/100, convexity=2, center=true) {
3450 back(scales[i]*.02) {
3451 text(text=str( $idx * scales[i] / unit), size=fontsize, halign="left", valign="baseline");
3452 }
3453 }
3454 }
3455 }
3456
3457 }
3458 }
3459 }
3460 children();
3461 }
3462}
3463
3464
3465
3466
3467
3468// vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap