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