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