From 6f342f450fb8aa8cb65f2356f4624dbb4e099df2 Mon Sep 17 00:00:00 2001 From: Revar Desmera Date: Tue, 16 Apr 2019 19:16:50 -0700 Subject: [PATCH] First pass at attachments support. --- constants.scad | 15 ++ debug.scad | 72 ++++++ examples/attachments.scad | 21 ++ examples/cylinder_connectors.scad | 9 + examples/fractal_tree.scad | 42 ++++ examples/prismoid_connectors.scad | 9 + examples/tagged_diff.scad | 18 ++ primitives.scad | 153 ++++++++++++ shapes.scad | 318 +++++++++++++++---------- transforms.scad | 378 +++++++++++++++++++++++++----- 10 files changed, 861 insertions(+), 174 deletions(-) create mode 100644 examples/attachments.scad create mode 100644 examples/cylinder_connectors.scad create mode 100644 examples/fractal_tree.scad create mode 100644 examples/prismoid_connectors.scad create mode 100644 examples/tagged_diff.scad create mode 100644 primitives.scad diff --git a/constants.scad b/constants.scad index 644bdba..21b8be3 100644 --- a/constants.scad +++ b/constants.scad @@ -342,5 +342,20 @@ function corner_edge_count(edges, v) = ); +// Default values for attachment code. +$color = undef; +$overlap = 0.01; +$attach_to = undef; +$attach_conn = ["center", V_ZERO, V_UP, 0]; +$parent_size = undef; +$parent_size2 = undef; +$parent_shift = [0,0]; +$parent_orient = ORIENT_Z; +$parent_align = "center"; +$parent_conns = []; +$tags_shown = []; +$tags_hidden = []; +$tags = ""; + // vim: noexpandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap diff --git a/debug.scad b/debug.scad index 6b489a9..7efe5a2 100644 --- a/debug.scad +++ b/debug.scad @@ -175,4 +175,76 @@ module debug_polyhedron(points, faces, convexity=10, txtsize=1, disabled=false) +// Function: all_conns() +// Description: +// Return the names for all standard connectors for a region. +// Arguments: +// type = The type of region to show connectors for. "cube", "cylinder", "sphere" +function all_conns(type="cube") = + assert(in_list(type,["cube", "cylinder", "sphere"])) + let ( + zs = ["top", "bottom"], + ys = ["front", "back"], + xs = ["left", "right"] + ) concat( + ["center"], + [for (a=concat(xs,ys,zs)) a], + in_list(type,["cube","cylinder"])? [for (a=zs, b=ys) str(a,"-",b)] : [], + in_list(type,["cube","cylinder"])? [for (a=zs, b=xs) str(a,"-",b)] : [], + in_list(type,["cube"])? [for (a=ys, b=xs) str(a,"-",b)] : [], + in_list(type,["cube"])? [for (a=zs, b=ys, c=xs) str(a,"-",b,"-",c)] : [] + ); + + + +// Module: connector_arrow() +// Usage: +// connector_arrow([s], [color], [flag]); +// Description: +// Show a connector orientation arrow. +// Arguments: +// s = Length of the arrows. +// color = Color of the arrow. +// flag = If true, draw the orientation flag on the arrowhead. +module connector_arrow(s=10, color=[0.333,0.333,1], flag=true) { + $fn=12; + recolor("gray") spheroid(d=s/6) + recolor(color) cyl(h=s*2/3, d=s/15, align=V_UP) + attach("top") cyl(h=s/3, d1=s/5, d2=0, align=V_UP) { + if(flag) { + attach("bottom") recolor([1,0.5,0.5]) cuboid([s/50, s/6, s/4], align="front-top"); + } + } +} + + + +// Module: show_connectors() +// Description: +// Show all standard connectors for a given region. +// Arguments: +// type = The type of region to show connectors for. "cube", "cylinder", "sphere" +module show_connectors(type="cube") { + for (conn=all_conns(type)) { + attach(conn) connector_arrow(); + } + children(); +} + + + +// Module: frameref() +// Description: +// Displays X,Y,Z axis arrows in red, green, and blue respectively. +// Arguments: +// s = Length of the arrows. +module frameref(s=15) { + sphere(0.001) { + attach("right") connector_arrow(s=s, color="red", flag=false); + attach("back") connector_arrow(s=s, color="green", flag=false); + attach("top") connector_arrow(s=s, color="blue", flag=false); + } +} + + // vim: noexpandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap diff --git a/examples/attachments.scad b/examples/attachments.scad new file mode 100644 index 0000000..9eab546 --- /dev/null +++ b/examples/attachments.scad @@ -0,0 +1,21 @@ +include +include +include +include +include + +cuboid([60,40,40], fillet=5, edges=EDGES_Z_ALL, align="bottom") { + attach("top") rounded_prismoid([60,40],[20,20], h=50, r1=5, r2=10) { + attach("top") cylinder(d=20, h=30) { + attach("top") cylinder(d1=50, d2=30, h=12); + } + for (a = ["front", "back", "left", "right"]) { + attach(a) cylinder(d1=14, d2=5, h=20) { + attach("top", "left", overlap=5) prismoid([30,20], [20,20], h=10, shift=[-7,0]); + } + } + } +} + + +// vim: noexpandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap diff --git a/examples/cylinder_connectors.scad b/examples/cylinder_connectors.scad new file mode 100644 index 0000000..9bdd2e3 --- /dev/null +++ b/examples/cylinder_connectors.scad @@ -0,0 +1,9 @@ +include +include +include + + +cylinder(h=30, d1=50, d2=30) show_connectors("cylinder"); + + +// vim: noexpandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap diff --git a/examples/fractal_tree.scad b/examples/fractal_tree.scad new file mode 100644 index 0000000..bbe81d4 --- /dev/null +++ b/examples/fractal_tree.scad @@ -0,0 +1,42 @@ +include +include +include +include + +module leaf(s) { + path = [ + [0,0], [1.5,-1], + [2,1], [0,3], [-2,1], + [-1.5,-1], [0,0] + ]; + xrot(90) + linear_extrude_bezier( + scale_points(path, [s,s]/2), + height=0.02 + ); +} + +module branches(minsize){ + if($parent_size2.x>minsize) { + attach("top") + zrot(gaussian_rand(90,10)) + zring(n=floor(log_rand(2,5,4))) + zrot(gaussian_rand(0,5)) + yrot(gaussian_rand(30,5)) + let( + sc = gaussian_rand(0.7,0.05), + s1 = $parent_size.z*sc, + s2 = $parent_size2.x + ) + cylinder(d1=s2, d2=s2*sc, l=s1) + branches(minsize); + } else { + recolor("springgreen") + attach("top") zrot(90) + leaf(gaussian_rand(100,5)); + } +} +recolor("lightgray") cylinder(d1=300, d2=250, l=1500) branches(5); + + +// vim: noexpandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap diff --git a/examples/prismoid_connectors.scad b/examples/prismoid_connectors.scad new file mode 100644 index 0000000..e27b5bc --- /dev/null +++ b/examples/prismoid_connectors.scad @@ -0,0 +1,9 @@ +include +include +include + + +prismoid([60,40], [30,20], h=40) show_connectors(); + + +// vim: noexpandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap diff --git a/examples/tagged_diff.scad b/examples/tagged_diff.scad new file mode 100644 index 0000000..84b8be0 --- /dev/null +++ b/examples/tagged_diff.scad @@ -0,0 +1,18 @@ +include +include +include +include + + +diff("hole", "body pole") +sphere(d=100, $tags="body") { + zcyl(d=55, h=100, $tags="pole"); // attach() not needed for center-to-center. + tags("hole") { + xcyl(d=55, h=101); + ycyl(d=55, h=101); + } + zcyl(d=15, h=140, $tags="axle"); +} + + +// vim: noexpandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap diff --git a/primitives.scad b/primitives.scad new file mode 100644 index 0000000..449b7da --- /dev/null +++ b/primitives.scad @@ -0,0 +1,153 @@ +////////////////////////////////////////////////////////////////////// +// LibFile: primitives.scad +// The basic built-in shapes, reworked to integrate better with +// other BOSL library shapes and utilities. +// To use, add the following lines to the beginning of your file: +// ``` +// include +// use +// ``` +////////////////////////////////////////////////////////////////////// + +/* +BSD 2-Clause License + +Copyright (c) 2017, Revar Desmera +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +include +include +use +use + + +// Section: Primitive Shapes + + +// Module: cube() +// +// Description: +// Creates a cube object, with support for alignment and attachments. +// This is a drop-in replacement for the built-in `cube()` module. +// +// Arguments: +// size = The size of the cube. +// align = The side of the origin to align to. Use `V_` constants from `constants.scad`. Default: `V_CENTER` +// center = If given, overrides `align`. A true value sets `align=V_CENTER`, false sets `align=V_UP+V_BACK+V_RIGHT`. +// +// Example: Simple regular cube. +// cube(40); +// Example: Rectangular cube, with given X, Y, and Z sizes. +// cuboid([20,40,50]); +module cube(size, center=undef, align=V_ALLPOS) +{ + size = scalar_vec3(size); + orient_and_align(size, ORIENT_Z, align, center, noncentered=V_ALLPOS, chain=true) { + linear_extrude(height=size.z, convexity=2, center=true) { + square([size.x, size.y], center=true); + } + children(); + } +} + + +// Module: cylinder() +// Usage: +// cylinder(h, r|d, [center], [orient], [align]); +// cylinder(h, r1/d1, r2/d2, [center], [orient], [align]); +// Description: +// Creates a cylinder object, with support for alignment and attachments. +// This is a drop-in replacement for the built-in `cylinder()` module. +// Arguments: +// l / h = The height of the cylinder. +// r = The radius of the cylinder. +// r1 = The bottom radius of the cylinder. (Before orientation.) +// r2 = The top radius of the cylinder. (Before orientation.) +// d = The diameter of the cylinder. +// d1 = The bottom diameter of the cylinder. (Before orientation.) +// d2 = The top diameter of the cylinder. (Before orientation.) +// orient = Orientation of the cylinder. Use the `ORIENT_` constants from `constants.scad`. Default: vertical. +// align = The side of the origin to align to. Use `V_` constants from `constants.scad`. Default: `V_CENTER` +// center = If given, overrides `align`. A true value sets `align=V_CENTER`, false sets `align=V_UP+V_BACK+V_RIGHT`. +// Example: By Radius +// xdistribute(30) { +// cylinder(h=40, r=10); +// cylinder(h=40, r1=10, r2=5); +// } +// Example: By Diameter +// xdistribute(30) { +// cylinder(h=40, d=25); +// cylinder(h=40, d1=25, d2=10); +// } +module cylinder(r=undef, d=undef, r1=undef, r2=undef, d1=undef, d2=undef, h=undef, l=undef, center=undef, orient=ORIENT_Z, align=ALIGN_POS) +{ + r1 = get_radius(r1=r1, r=r, d1=d1, d=d, dflt=1); + r2 = get_radius(r1=r2, r=r, d1=d2, d=d, dflt=1); + l = first_defined([h, l]); + sides = segs(max(r1,r2)); + size = [r1*2, r1*2, l]; + orient_and_align(size, orient, align, center, size2=[r2*2,r2*2], noncentered=ALIGN_POS, chain=true) { + linear_extrude(height=l, scale=r2/r1, convexity=2, center=true) { + circle(r=r1, $fn=sides); + } + children(); + } +} + + + +// Module: sphere() +// Usage: +// sphere(r|d, [orient], [align]) +// Description: +// Creates a sphere object, with support for alignment and attachments. +// This is a drop-in replacement for the built-in `sphere()` module. +// Arguments: +// r = Radius of the sphere. +// d = Diameter of the sphere. +// orient = Orientation of the sphere, if you don't like where the vertices lay. Use the `ORIENT_` constants from `constants.scad`. Default: `ORIENT_Z`. +// align = Alignment of the sphere. Use the `V_` constants from `constants.scad`. Default: `V_CENTER`. +// Example: +// staggered_sphere(d=100); +module sphere(r=undef, d=undef, orient=ORIENT_Z, align=V_CENTER) +{ + r = get_radius(r=r, d=d, dflt=1); + sides = segs(r); + size = [r*2, r*2, r*2]; + orient_and_align(size, orient, align, chain=true) { + rotate_extrude(convexity=2) { + difference() { + circle(r=r, $fn=sides); + left(r+0.1) square(r*2+0.2, center=true); + } + } + children(); + } +} + + + +// vim: noexpandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap diff --git a/shapes.scad b/shapes.scad index 062ce09..be782f0 100644 --- a/shapes.scad +++ b/shapes.scad @@ -96,26 +96,25 @@ module cuboid( if (is_def(p1)) { if (is_def(p2)) { translate([for (v=array_zip([p1,p2],0)) min(v)]) { - cuboid(size=vabs(p2-p1), chamfer=chamfer, fillet=fillet, edges=edges, trimcorners=trimcorners, align=V_ALLPOS); + cuboid(size=vabs(p2-p1), chamfer=chamfer, fillet=fillet, edges=edges, trimcorners=trimcorners, align=V_ALLPOS) children(); } } else { translate(p1) { - cuboid(size=size, chamfer=chamfer, fillet=fillet, edges=edges, trimcorners=trimcorners, align=V_ALLPOS); + cuboid(size=size, chamfer=chamfer, fillet=fillet, edges=edges, trimcorners=trimcorners, align=V_ALLPOS) children(); } } } else { - majrots = [[0,90,0], [90,0,0], [0,0,0]]; if (chamfer != undef) assertion(chamfer <= min(size)/2, "chamfer must be smaller than half the cube width, length, or height."); if (fillet != undef) assertion(fillet <= min(size)/2, "fillet must be smaller than half the cube width, length, or height."); - algn = (!is_def(center))? (is_scalar(align)? align*V_UP : align) : (center==true)? V_CENTER : V_ALLPOS; - translate(vmul(size/2, algn)) { + majrots = [[0,90,0], [90,0,0], [0,0,0]]; + orient_and_align(size, ORIENT_Z, align, center=center, noncentered=V_ALLPOS, chain=true) { if (chamfer != undef) { isize = [for (v = size) max(0.001, v-2*chamfer)]; if (edges == EDGES_ALL && trimcorners) { hull() { - cube([size[0], isize[1], isize[2]], center=true); - cube([isize[0], size[1], isize[2]], center=true); - cube([isize[0], isize[1], size[2]], center=true); + cube([size.x, isize.y, isize.z], center=true); + cube([isize.x, size.y, isize.z], center=true); + cube([isize.x, isize.y, size.z], center=true); } } else { difference() { @@ -201,6 +200,7 @@ module cuboid( } else { cube(size=size, center=true); } + children(); } } } @@ -221,7 +221,7 @@ module cuboid( // p2 = Coordinate point of opposite cube corner. module cube2pt(p1,p2) { deprecate("cube2pt()", "cuboid(p1,p2)"); - cuboid(p1=p1, p2=p2); + cuboid(p1=p1, p2=p2) children(); } @@ -240,7 +240,7 @@ module cube2pt(p1,p2) { // span_cube([0,15], [5,10], [0, 10]); module span_cube(xspan, yspan, zspan) { span = [xspan, yspan, zspan]; - cuboid(p1=array_subindex(span,0), p2=array_subindex(span,1)); + cuboid(p1=array_subindex(span,0), p2=array_subindex(span,1)) children(); } @@ -257,7 +257,7 @@ module span_cube(xspan, yspan, zspan) { // v = vector to offset along. module offsetcube(size=[1,1,1], v=[0,0,0]) { deprecate("offsetcube()", "cuboid()"); - cuboid(size=size, align=v); + cuboid(size=size, align=v) children(); } @@ -379,7 +379,7 @@ module chamfcube(size=[1,1,1], chamfer=0.25, chamfaxes=[1,1,1], chamfcorners=fal (chamfaxes[1]? EDGES_Y_ALL : EDGES_NONE) + (chamfaxes[2]? EDGES_Z_ALL : EDGES_NONE) ) - ); + ) children(); } @@ -396,7 +396,7 @@ module chamfcube(size=[1,1,1], chamfer=0.25, chamfaxes=[1,1,1], chamfcorners=fal // center = If true, object will be centered. If false, sits on top of XY plane. module rrect(size=[1,1,1], r=0.25, center=false) { deprecate("rrect()", "cuboid()"); - cuboid(size=size, fillet=r, edges=EDGES_Z_ALL, align=center? V_CENTER : V_UP); + cuboid(size=size, fillet=r, edges=EDGES_Z_ALL, align=center? V_CENTER : V_UP) children(); } @@ -413,7 +413,7 @@ module rrect(size=[1,1,1], r=0.25, center=false) { // center = If true, object will be centered. If false, sits on top of XY plane. module rcube(size=[1,1,1], r=0.25, center=false) { deprecate("rcube()", "cuboid()"); - cuboid(size=size, fillet=r, align=center? V_CENTER : V_UP); + cuboid(size=size, fillet=r, align=center? V_CENTER : V_UP) children(); } @@ -459,20 +459,20 @@ module prismoid( orient=ORIENT_Z, align=ALIGN_POS, center=undef ) { eps = 0.001; - s1 = [max(size1[0], eps), max(size1[1], eps)]; - s2 = [max(size2[0], eps), max(size2[1], eps)]; - shiftby = point3d(shift); - orient_and_align([s1[0], s1[1], h], orient, align, center, noncentered=ALIGN_POS) { + shiftby = point3d(point2d(shift)); + s1 = [max(size1.x, eps), max(size1.y, eps)]; + s2 = [max(size2.x, eps), max(size2.y, eps)]; + orient_and_align([s1.x,s1.y,h], orient, align, center, size2=s2, shift=shift, noncentered=ALIGN_POS, chain=true) { polyhedron( points=[ - [+s2[0]/2, +s2[1]/2, +h/2] + shiftby, - [+s2[0]/2, -s2[1]/2, +h/2] + shiftby, - [-s2[0]/2, -s2[1]/2, +h/2] + shiftby, - [-s2[0]/2, +s2[1]/2, +h/2] + shiftby, - [+s1[0]/2, +s1[1]/2, -h/2], - [+s1[0]/2, -s1[1]/2, -h/2], - [-s1[0]/2, -s1[1]/2, -h/2], - [-s1[0]/2, +s1[1]/2, -h/2], + [+s2.x/2, +s2.y/2, +h/2] + shiftby, + [+s2.x/2, -s2.y/2, +h/2] + shiftby, + [-s2.x/2, -s2.y/2, +h/2] + shiftby, + [-s2.x/2, +s2.y/2, +h/2] + shiftby, + [+s1.x/2, +s1.y/2, -h/2], + [+s1.x/2, -s1.y/2, -h/2], + [-s1.x/2, -s1.y/2, -h/2], + [-s1.x/2, +s1.y/2, -h/2], ], faces=[ [0, 1, 2], @@ -490,6 +490,7 @@ module prismoid( ], convexity=2 ); + children(); } } @@ -513,7 +514,7 @@ module prismoid( // center = If given, overrides `align`. A true value sets `align=V_CENTER`, false sets `align=V_UP`. module trapezoid(size1=[1,1], size2=[1,1], h=1, center=false) { deprecate("trapezoid()", "prismoid()"); - prismoid(size=size, size2=size2, h=h, center=center); + prismoid(size=size, size2=size2, h=h, center=center) children(); } @@ -548,29 +549,31 @@ module rounded_prismoid( align=V_UP, orient=ORIENT_Z, center=undef ) { eps = 0.001; - maxrad1 = min(size1[0]/2, size1[1]/2); - maxrad2 = min(size2[0]/2, size2[1]/2); + maxrad1 = min(size1.x/2, size1.y/2); + maxrad2 = min(size2.x/2, size2.y/2); rr1 = min(maxrad1, (r1!=undef)? r1 : r); rr2 = min(maxrad2, (r2!=undef)? r2 : r); shiftby = point3d(shift); - orient_and_align([size1.x, size1.y, h], orient, align, center, noncentered=ALIGN_POS) { - down(h/2) - hull() { - linear_extrude(height=eps, center=false, convexity=2) { - offset(r=rr1) { - square([max(eps, size1[0]-2*rr1), max(eps, size1[1]-2*rr1)], center=true); + orient_and_align([size1.x, size1.y, h], orient, align, center, size2=size2, shift=shift, noncentered=ALIGN_POS, chain=true) { + down(h/2) { + hull() { + linear_extrude(height=eps, center=false, convexity=2) { + offset(r=rr1) { + square([max(eps, size1[0]-2*rr1), max(eps, size1[1]-2*rr1)], center=true); + } } - } - up(h-0.01) { - translate(shiftby) { - linear_extrude(height=eps, center=false, convexity=2) { - offset(r=rr2) { - square([max(eps, size2[0]-2*rr2), max(eps, size2[1]-2*rr2)], center=true); + up(h-0.01) { + translate(shiftby) { + linear_extrude(height=eps, center=false, convexity=2) { + offset(r=rr2) { + square([max(eps, size2[0]-2*rr2), max(eps, size2[1]-2*rr2)], center=true); + } } } } } } + children(); } } @@ -596,7 +599,7 @@ module pyramid(n=4, h=1, l=1, r=undef, d=undef, circum=false) { deprecate("pyramid()", "cyl()"); radius = get_radius(r=r, d=d, dflt=l/2/sin(180/n)); - cyl(r1=radius, r2=0, l=h, circum=circum, $fn=n, realign=true, align=ALIGN_POS); + cyl(r1=radius, r2=0, l=h, circum=circum, $fn=n, realign=true, align=ALIGN_POS) children(); } @@ -620,7 +623,7 @@ module prism(n=3, h=1, l=1, r=undef, d=undef, circum=false, center=false) { deprecate("prism()", "cyl()"); radius = get_radius(r=r, d=d, dflt=l/2/sin(180/n)); - cyl(r=radius, l=h, circum=circum, $fn=n, realign=true, center=center); + cyl(r=radius, l=h, circum=circum, $fn=n, realign=true, center=center) children(); } @@ -644,36 +647,37 @@ module prism(n=3, h=1, l=1, r=undef, d=undef, circum=false, center=false) // right_triangle([60, 10, 40]); module right_triangle(size=[1, 1, 1], orient=ORIENT_Y, align=V_ALLPOS, center=undef) { - siz = scalar_vec3(size); - orient_and_align(siz, align=align, center=center) { + size = scalar_vec3(size); + orient_and_align(size, align=align, center=center, chain=true) { if (orient == ORIENT_X) { - ang = atan2(siz[1], siz[2]); - masksize = [siz[0], siz[1], norm([siz[1],siz[2]])] + [1,1,1]; + ang = atan2(size.y, size.z); + masksize = [size.x, size.y, norm([size.y,size.z])] + [1,1,1]; xrot(ang) { difference() { - xrot(-ang) cube(siz, center=true); - back(masksize[1]/2) cube(masksize, center=true); + xrot(-ang) cube(size, center=true); + back(masksize.y/2) cube(masksize, center=true); } } } else if (orient == ORIENT_Y) { - ang = atan2(siz[0], siz[2]); - masksize = [siz[0], siz[1], norm([siz[0],siz[2]])] + [1,1,1]; + ang = atan2(size.x, size.z); + masksize = [size.x, size.y, norm([size.x,size.z])] + [1,1,1]; yrot(-ang) { difference() { - yrot(ang) cube(siz, center=true); - right(masksize[0]/2) cube(masksize, center=true); + yrot(ang) cube(size, center=true); + right(masksize.x/2) cube(masksize, center=true); } } } else if (orient == ORIENT_Z) { - ang = atan2(siz[0], siz[1]); - masksize = [norm([siz[0],siz[1]]), siz[1], siz[2]] + [1,1,1]; + ang = atan2(size.x, size.y); + masksize = [norm([size.x,size.y]), size.y, size.z] + [1,1,1]; zrot(-ang) { difference() { - zrot(ang) cube(siz, center=true); - back(masksize[1]/2) cube(masksize, center=true); + zrot(ang) cube(size, center=true); + back(masksize.y/2) cube(masksize, center=true); } } } + children(); } } @@ -778,9 +782,12 @@ module cyl( r1 = get_radius(r1, r, d1, d, 1); r2 = get_radius(r2, r, d2, d, 1); l = first_defined([l, h, 1]); + size1 = [r1*2,r1*2,l]; + size2 = [r2*2,r2*2,l]; sides = segs(max(r1,r2)); sc = circum? 1/cos(180/sides) : 1; - orient_and_align([r1*2,r1*2,l], orient, align, center=center) { + phi = atan2(l, r1-r2); + orient_and_align(size1, orient, align, center=center, size2=size2, chain=true) { zrot(realign? 180/sides : 0) { if (!any_defined([chamfer, chamfer1, chamfer2, fillet, fillet1, fillet2])) { cylinder(h=l, r1=r1*sc, r2=r2*sc, center=true, $fn=sides); @@ -901,6 +908,7 @@ module cyl( } } } + children(); } } @@ -930,10 +938,8 @@ module cyl( // downcyl(r1=10, r2=20, h=40); module downcyl(r=undef, h=undef, l=undef, d=undef, d1=undef, d2=undef, r1=undef, r2=undef) { - h = first_defined([l, h, 1]); - down(h/2) { - cylinder(r=r, r1=r1, r2=r2, d=d, d1=d1, d2=d2, h=h, center=true); - } + l = first_defined([l, h, 1]); + cyl(r=r, r1=r1, r2=r2, d=d, d1=d1, d2=d2, l=l, align=V_DOWN) children(); } @@ -971,7 +977,7 @@ module downcyl(r=undef, h=undef, l=undef, d=undef, d1=undef, d2=undef, r1=undef, // } module xcyl(l=undef, r=undef, d=undef, r1=undef, r2=undef, d1=undef, d2=undef, h=undef, align=V_CENTER, center=undef) { - cyl(l=l, h=h, r=r, r1=r1, r2=r2, d=d, d1=d1, d2=d2, orient=ORIENT_X, align=align, center=center); + cyl(l=l, h=h, r=r, r1=r1, r2=r2, d=d, d1=d1, d2=d2, orient=ORIENT_X, align=align, center=center) children(); } @@ -1009,7 +1015,7 @@ module xcyl(l=undef, r=undef, d=undef, r1=undef, r2=undef, d1=undef, d2=undef, h // } module ycyl(l=undef, r=undef, d=undef, r1=undef, r2=undef, d1=undef, d2=undef, h=undef, align=V_CENTER, center=undef) { - cyl(l=l, h=h, r=r, r1=r1, r2=r2, d=d, d1=d1, d2=d2, orient=ORIENT_Y, align=align, center=center); + cyl(l=l, h=h, r=r, r1=r1, r2=r2, d=d, d1=d1, d2=d2, orient=ORIENT_Y, align=align, center=center) children(); } @@ -1047,7 +1053,7 @@ module ycyl(l=undef, r=undef, d=undef, r1=undef, r2=undef, d1=undef, d2=undef, h // } module zcyl(l=undef, r=undef, d=undef, r1=undef, r2=undef, d1=undef, d2=undef, h=undef, align=V_CENTER, center=undef) { - cyl(l=l, h=h, r=r, r1=r1, r2=r2, d=d, d1=d1, d2=d2, orient=ORIENT_Z, align=align, center=center); + cyl(l=l, h=h, r=r, r1=r1, r2=r2, d=d, d1=d1, d2=d2, orient=ORIENT_Z, align=align, center=center) children(); } @@ -1075,7 +1081,7 @@ module chamferred_cylinder(h=1, r=undef, d=undef, chamfer=0.25, chamfedge=undef, deprecate("chamf_cyl()` and `chamferred_cylinder()", "cyl()"); r = get_radius(r=r, d=d, dflt=1); chamf = (chamfedge == undef)? chamfer : chamfedge * cos(angle); - cyl(l=h, r=r, chamfer1=bottom? chamf : 0, chamfer2=top? chamf : 0, chamfang=angle, center=center); + cyl(l=h, r=r, chamfer1=bottom? chamf : 0, chamfer2=top? chamf : 0, chamfang=angle, center=center) children(); } @@ -1099,7 +1105,7 @@ module chamferred_cylinder(h=1, r=undef, d=undef, chamfer=0.25, chamfedge=undef, // bottom = boolean. If true, chamfer the bottom edges. (Default: True) // center = boolean. If true, cylinder is centered. (Default: false) module chamf_cyl(h=1, r=undef, d=undef, chamfer=0.25, chamfedge=undef, angle=45, center=false, top=true, bottom=true) - chamferred_cylinder(h=h, r=r, d=d, chamfer=chamfer, chamfedge=chamfedge, angle=angle, center=center, top=top, bottom=bottom); + chamferred_cylinder(h=h, r=r, d=d, chamfer=chamfer, chamfedge=chamfedge, angle=angle, center=center, top=top, bottom=bottom) children(); // Module: filleted_cylinder() @@ -1119,7 +1125,7 @@ module chamf_cyl(h=1, r=undef, d=undef, chamfer=0.25, chamfedge=undef, angle=45, // center = boolean. If true, cylinder is centered. (Default: false) module filleted_cylinder(h=1, r=undef, d=undef, r1=undef, r2=undef, d1=undef, d2=undef, fillet=0.25, center=false) { deprecate("filleted_cylinder()", "cyl()"); - cyl(l=h, r=r, d=d, r1=r1, r2=r2, d1=d1, d2=d2, fillet=fillet, orient=ORIENT_Z, center=center); + cyl(l=h, r=r, d=d, r1=r1, r2=r2, d1=d1, d2=d2, fillet=fillet, orient=ORIENT_Z, center=center) children(); } @@ -1142,7 +1148,7 @@ module filleted_cylinder(h=1, r=undef, d=undef, r1=undef, r2=undef, d1=undef, d2 // center = boolean. If true, cylinder is centered. (Default: false) module rcylinder(h=1, r=1, r1=undef, r2=undef, d=undef, d1=undef, d2=undef, fillet=0.25, center=false) { deprecate("rcylinder()", "cyl(..., fillet)"); - cyl(l=h, r=r, d=d, r1=r1, r2=r2, d1=d1, d2=d2, fillet=fillet, orient=ORIENT_Z, center=center); + cyl(l=h, r=r, d=d, r1=r1, r2=r2, d1=d1, d2=d2, fillet=fillet, orient=ORIENT_Z, center=center) children(); } @@ -1208,13 +1214,16 @@ module tube( assertion(ir1 <= r1, "Inner radius is larger than outer radius."); assertion(ir2 <= r2, "Inner radius is larger than outer radius."); sides = segs(max(r1,r2)); - orient_and_align([r1*2,r1*2,h], orient, align, center=center) { + size = [r1*2,r1*2,h]; + size2 = [r2*2,r2*2,h]; + orient_and_align(size, orient, align, center=center, size2=size2, chain=true) { zrot(realign? 180/sides : 0) { difference() { - cylinder(h=h, r1=r1, r2=r2, center=true, $fn=sides); - cylinder(h=h+0.05, r1=ir1, r2=ir2, center=true); + cyl(h=h, r1=r1, r2=r2, $fn=sides) children(); + cyl(h=h+0.05, r1=ir1, r2=ir2); } } + children(); } } @@ -1257,10 +1266,12 @@ module torus( irr = get_radius(r=ir, d=id, dflt=0.5); majrad = get_radius(r=r, d=d, dflt=(orr+irr)/2); minrad = get_radius(r=r2, d=d2, dflt=(orr-irr)/2); - orient_and_align([(majrad+minrad)*2, (majrad+minrad)*2, minrad*2], orient, align, center=center) { + size = [(majrad+minrad)*2, (majrad+minrad)*2, minrad*2]; + orient_and_align(size, orient, align, center=center, chain=true) { rotate_extrude(convexity=4) { right(majrad) circle(minrad); } + children(); } } @@ -1269,6 +1280,34 @@ module torus( // Section: Spheroids +// Module: spheroid() +// Description: +// An version of `sphere()` with connector points, orientation, and alignment. +// Usage: +// spheroid(r|d, [circum]) +// Arguments: +// r = Radius of the sphere. +// d = Diameter of the sphere. +// circum = If true, circumscribes the perfect sphere of the given radius/diameter. +// orient = Orientation of the sphere, if you don't like where the vertices lay. Use the `ORIENT_` constants from `constants.scad`. Default: `ORIENT_Z`. +// align = Alignment of the sphere. Use the `V_` constants from `constants.scad`. Default: `V_CENTER`. +// Example: +// spheroid(d=100, circum=true, $fn=10); +module spheroid(r=undef, d=undef, circum=false, orient=V_UP, align=V_CENTER) +{ + r = get_radius(r=r, d=d, dflt=1); + hsides = segs(r); + vsides = ceil(hsides/2); + rr = circum? (r / cos(90/vsides) / cos(180/hsides)) : r; + size = [2*rr, 2*rr, 2*rr]; + orient_and_align(size, orient, align, chain=true) { + sphere(r=rr); + children(); + } +} + + + // Module: staggered_sphere() // // Description: @@ -1281,16 +1320,18 @@ module torus( // r = Radius of the sphere. // d = Diameter of the sphere. // circum = If true, circumscribes the perfect sphere of the given size. +// orient = Orientation of the sphere, if you don't like where the vertices lay. Use the `ORIENT_` constants from `constants.scad`. Default: `ORIENT_Z`. +// align = Alignment of the sphere. Use the `V_` constants from `constants.scad`. Default: `V_CENTER`. // // Example: // staggered_sphere(d=100, circum=true, $fn=10); -module staggered_sphere(r=undef, d=undef, circum=false, align=V_CENTER) { +module staggered_sphere(r=undef, d=undef, circum=false, orient=V_UP, align=V_CENTER) { r = get_radius(r=r, d=d, dflt=1); sides = segs(r); vsides = max(3, ceil(sides/2))+1; step = 360/sides; vstep = 180/(vsides-1); - rr = circum? r/cos(180/sides)/cos(180/sides) : r; + rr = circum? (r / cos(180/sides) / cos(90/vsides)) : r; pts = concat( [[0,0,rr]], [ @@ -1320,7 +1361,11 @@ module staggered_sphere(r=undef, d=undef, circum=false, align=V_CENTER) { ) each [[v1,v4,v3], [v1,v2,v4]] ] ); - polyhedron(points=pts, faces=faces); + size = [2*rr, 2*rr, 2*rr]; + orient_and_align(size, orient, align, chain=true) { + polyhedron(points=pts, faces=faces); + children(); + } } @@ -1394,10 +1439,12 @@ module teardrop(r=undef, d=undef, l=undef, h=undef, ang=45, cap_h=undef, orient= { r = get_radius(r=r, d=d, dflt=1); l = first_defined([l, h, 1]); - orient_and_align([r*2,r*2,l], orient, align) { + size = [r*2,r*2,l]; + orient_and_align(size, orient, align, chain=true) { linear_extrude(height=l, center=true, slices=2) { teardrop2d(r=r, ang=ang, cap_h=cap_h); } + children(); } } @@ -1429,13 +1476,15 @@ module onion(cap_h=undef, r=undef, d=undef, maxang=45, h=undef, orient=ORIENT_Z, r = get_radius(r=r, d=d, dflt=1); h = first_defined([cap_h, h]); maxd = 3*r/tan(maxang); - orient_and_align([r*2,r*2,r*2], orient, align) { + size = [r*2,r*2,r*2]; + orient_and_align(size, orient, align, chain=true) { rotate_extrude(convexity=2) { difference() { teardrop2d(r=r, ang=maxang, cap_h=h); left(r) square(size=[2*r,maxd], center=true); } } + children(); } } @@ -1464,7 +1513,8 @@ module onion(cap_h=undef, r=undef, d=undef, maxang=45, h=undef, orient=ORIENT_Z, module narrowing_strut(w=10, l=100, wall=5, ang=30, orient=ORIENT_Y, align=V_UP) { h = wall + w/2/tan(ang); - orient_and_align([w, h, l], orient, align, orig_orient=ORIENT_Z) { + size = [w, h, l]; + orient_and_align(size, orient, align, chain=true) { fwd(h/2) { linear_extrude(height=l, center=true, slices=2) { back(wall/2) square([w, wall], center=true); @@ -1478,6 +1528,7 @@ module narrowing_strut(w=10, l=100, wall=5, ang=30, orient=ORIENT_Y, align=V_UP) } } } + children(); } } @@ -1505,7 +1556,7 @@ module narrowing_strut(w=10, l=100, wall=5, ang=30, orient=ORIENT_Y, align=V_UP) // thinning_wall(h=50, l=80, thick=4); // Example: Trapezoidal // thinning_wall(h=50, l=[80,50], thick=4); -module thinning_wall(h=50, l=100, thick=5, ang=30, strut=5, wall=2, orient=ORIENT_X, align=V_CENTER) +module thinning_wall(h=50, l=100, thick=5, ang=30, strut=5, wall=2, orient=ORIENT_Z, align=V_CENTER) { l1 = (l[0] == undef)? l : l[0]; l2 = (l[1] == undef)? l : l[1]; @@ -1528,7 +1579,8 @@ module thinning_wall(h=50, l=100, thick=5, ang=30, strut=5, wall=2, orient=ORIEN y1 = thick/2; y2 = y1 - min(z2-z3, x2-x3) * sin(ang); - orient_and_align([l1, thick, h], orient, align, orig_orient=ORIENT_X) { + size = [l1, thick, h]; + orient_and_align(size, orient, align, size2=[l2,thick], chain=true) { polyhedron( points=[ [-x4, -y1, -z1], @@ -1620,6 +1672,7 @@ module thinning_wall(h=50, l=100, thick=5, ang=30, strut=5, wall=2, orient=ORIEN ], convexity=6 ); + children(); } } @@ -1649,23 +1702,27 @@ module braced_thinning_wall(h=50, l=100, thick=5, ang=30, strut=5, wall=2, orien { dang = atan((h-2*strut)/(l-2*strut)); dlen = (h-2*strut)/sin(dang); - orient_and_align([thick, l, h], orient, align, orig_orient=ORIENT_Y) { - xrot_copies([0, 180]) { - down(h/2) narrowing_strut(w=thick, l=l, wall=strut, ang=ang); - fwd(l/2) xrot(-90) narrowing_strut(w=thick, l=h-0.1, wall=strut, ang=ang); - intersection() { - cube(size=[thick, l, h], center=true); - xrot_copies([-dang,dang]) { - zspread(strut/2) { - scale([1,1,1.5]) yrot(45) { - cube(size=[thick/sqrt(2), dlen, thick/sqrt(2)], center=true); + size = [l, thick, h]; + orient_and_align(size, orient, align, orig_orient=ORIENT_Y, chain=true) { + union() { + xrot_copies([0, 180]) { + down(h/2) narrowing_strut(w=thick, l=l, wall=strut, ang=ang); + fwd(l/2) xrot(-90) narrowing_strut(w=thick, l=h-0.1, wall=strut, ang=ang); + intersection() { + cube(size=[thick, l, h], center=true); + xrot_copies([-dang,dang]) { + zspread(strut/2) { + scale([1,1,1.5]) yrot(45) { + cube(size=[thick/sqrt(2), dlen, thick/sqrt(2)], center=true); + } } + cube(size=[thick, dlen, strut/2], center=true); } - cube(size=[thick, dlen, strut/2], center=true); } } + cube(size=[wall, l-0.1, h-0.1], center=true); } - cube(size=[wall, l-0.1, h-0.1], center=true); + children(); } } @@ -1702,7 +1759,8 @@ module thinning_triangle(h=50, l=100, thick=5, ang=30, strut=5, wall=3, diagonly { dang = atan(h/l); dlen = h/sin(dang); - orient_and_align([thick, l, h], orient, align, center=center, noncentered=V_UP+V_BACK, orig_orient=ORIENT_Y) { + size = [thick, l, h]; + orient_and_align(size, orient, align, center=center, noncentered=V_UP+V_BACK, orig_orient=ORIENT_Y, chain=true) { difference() { union() { if (!diagonly) { @@ -1725,6 +1783,7 @@ module thinning_triangle(h=50, l=100, thick=5, ang=30, strut=5, wall=3, diagonly } } } + children(); } } @@ -1750,7 +1809,7 @@ module thinning_triangle(h=50, l=100, thick=5, ang=30, strut=5, wall=3, diagonly module thinning_brace(h=50, l=100, thick=5, ang=30, strut=5, wall=3, center=true) { deprecate("thinning_brace()", "thinning_triangle(..., diagonly=true)"); - thinning_triangle(h=h, l=l, thick=thick, ang=ang, strut=strut, wall=wall, diagonly=true, center=center); + thinning_triangle(h=h, l=l, thick=thick, ang=ang, strut=strut, wall=wall, diagonly=true, center=center) children(); } @@ -1801,17 +1860,21 @@ module sparse_strut(h=50, l=100, thick=4, maxang=30, strut=5, max_bridge=20, ori ang = atan(ystep/zstep); len = zstep / cos(ang); - orient_and_align([thick, l, h], orient, align, orig_orient=ORIENT_Y) { - zspread(zoff*2) - cube(size=[thick, l, strut], center=true); - yspread(yoff*2) - cube(size=[thick, strut, h], center=true); - yspread(ystep, n=yreps) { - zspread(zstep, n=zreps) { - xrot( ang) cube(size=[thick, strut, len], center=true); - xrot(-ang) cube(size=[thick, strut, len], center=true); + size = [thick, l, h]; + orient_and_align(size, orient, align, orig_orient=ORIENT_Y, chain=true) { + union() { + zspread(zoff*2) + cube(size=[thick, l, strut], center=true); + yspread(yoff*2) + cube(size=[thick, strut, h], center=true); + yspread(ystep, n=yreps) { + zspread(zstep, n=zreps) { + xrot( ang) cube(size=[thick, strut, len], center=true); + xrot(-ang) cube(size=[thick, strut, len], center=true); + } } } + children(); } } @@ -1866,7 +1929,8 @@ module sparse_strut3d(h=50, l=100, w=50, thick=3, maxang=40, strut=3, max_bridge supp_reps = floor(cross_len/2/(zstep*sin(supp_ang))); supp_step = cross_len/2/supp_reps; - orient_and_align([w, l, h], orient, align, orig_orient=ORIENT_Y) { + size = [w, l, h]; + orient_and_align(size, orient, align, orig_orient=ORIENT_Y, chain=true) { intersection() { union() { ybridge = (l - (yreps+1) * strut) / yreps; @@ -1908,6 +1972,7 @@ module sparse_strut3d(h=50, l=100, w=50, thick=3, maxang=40, strut=3, max_bridge } cube([w,l,h], center=true); } + children(); } } @@ -1943,20 +2008,23 @@ module corrugated_wall(h=50, l=100, thick=5, strut=5, wall=2, orient=ORIENT_Y, a steps = quantup(segs(thick/2),4); step = period/steps; il = l - 2*strut + 2*step; - orient_and_align([thick, l, h], orient, align, orig_orient=ORIENT_Y) { - linear_extrude(height=h-2*strut+0.1, slices=2, convexity=ceil(2*il/period), center=true) { - polygon( - points=concat( - [for (y=[-il/2:step:il/2]) [amplitude*sin(y/period*360)-wall/2, y] ], - [for (y=[il/2:-step:-il/2]) [amplitude*sin(y/period*360)+wall/2, y] ] - ) - ); - } - - difference() { - cube([thick, l, h], center=true); - cube([thick+0.5, l-2*strut, h-2*strut], center=true); + size = [thick, l, h]; + orient_and_align(size, orient, align, orig_orient=ORIENT_Y, chain=true) { + union() { + linear_extrude(height=h-2*strut+0.1, slices=2, convexity=ceil(2*il/period), center=true) { + polygon( + points=concat( + [for (y=[-il/2:step:il/2]) [amplitude*sin(y/period*360)-wall/2, y] ], + [for (y=[il/2:-step:-il/2]) [amplitude*sin(y/period*360)+wall/2, y] ] + ) + ); + } + difference() { + cube([thick, l, h], center=true); + cube([thick+0.5, l-2*strut, h-2*strut], center=true); + } } + children(); } } @@ -2016,7 +2084,8 @@ module pie_slice( r1 = get_radius(r1, r, d1, d, 10); r2 = get_radius(r2, r, d2, d, 10); maxd = max(r1,r2)+0.1; - orient_and_align([2*r1, 2*r1, l], orient, align, center=center) { + size = [2*r1, 2*r1, l]; + orient_and_align(size, orient, align, center=center, chain=true) { difference() { cylinder(r1=r1, r2=r2, h=l, center=true); if (ang<180) rotate(ang) back(maxd/2) cube([2*maxd, maxd, l+0.1], center=true); @@ -2025,6 +2094,7 @@ module pie_slice( if (ang>180) rotate(ang-180) back(maxd/2) cube([2*maxd, maxd, l+0.1], center=true); } } + children(); } } @@ -2057,7 +2127,8 @@ module pie_slice( // interior_fillet(l=40, r=10, orient=ORIENT_Y_90); module interior_fillet(l=1.0, r=1.0, ang=90, overlap=0.01, orient=ORIENT_X, align=V_CENTER) { dy = r/tan(ang/2); - orient_and_align([l,r,r], orient, align, orig_orient=ORIENT_X) { + size = [l,r,r]; + orient_and_align(size, orient, align, orig_orient=ORIENT_X, chain=true) { difference() { translate([0,-overlap/tan(ang/2),-overlap]) { if (ang == 90) { @@ -2068,6 +2139,7 @@ module interior_fillet(l=1.0, r=1.0, ang=90, overlap=0.01, orient=ORIENT_X, alig } translate([0,dy,r]) xcyl(l=l+0.1, r=r); } + children(); } } @@ -2108,6 +2180,8 @@ module slot( r1 = get_radius(r1=r1, r=r, d1=d1, d=d, dflt=5); r2 = get_radius(r1=r2, r=r, d1=d2, d=d, dflt=5); sides = quantup(segs(max(r1, r2)), 4); + // TODO: implement orient and align. + // TODO: implement connectors. hull() spread(p1=p1, p2=p2, l=l, n=2) cyl(l=h, r1=r1, r2=r2, center=true, $fn=sides); } @@ -2156,7 +2230,8 @@ module arced_slot( sr2 = get_radius(sr2, sr, sd2, sd, 2); fn_minor = first_defined([$fn2, $fn]); da = ea - sa; - orient_and_align([r+sr1, r+sr1, h], orient, align) { + size = [r+sr1, r+sr1, h]; + orient_and_align(size, orient, align, chain=true) { translate(cp) { zrot(sa) { difference() { @@ -2167,6 +2242,7 @@ module arced_slot( zrot(da) right(r) cylinder(h=h, r1=sr1, r2=sr2, center=true, $fn=fn_minor); } } + children(); } } diff --git a/transforms.scad b/transforms.scad index 3189097..5330f1d 100644 --- a/transforms.scad +++ b/transforms.scad @@ -1439,27 +1439,22 @@ module zrot_copies(rots=[], cp=[0,0,0], n=undef, count=undef, sa=0, offset=0, r= // Module: xring() -// // Description: // Distributes `n` copies of the given children on a circle of radius `r` // around the X axis. If `rot` is true, each copy is rotated in place to keep // the same side towards the center. The first, unrotated copy will be at the // starting angle `sa`. -// // Usage: // xring(n, r, [sa], [cp], [rot]) ... -// // Arguments: // n = Number of copies of children to distribute around the circle. (Default: 2) // r = Radius of ring to distribute children around. (Default: 0) // sa = Start angle for first (unrotated) copy. (Default: 0) // cp = Centerpoint of ring. Default: [0,0,0] // rot = If true, rotate each copy to keep the same side towards the center of the ring. Default: true. -// // Side Effects: // `$ang` is set to the rotation angle of each child copy, and can be used to modify each child individually. // `$idx` is set to the index value of each child copy. -// // Examples: // xring(n=6, r=10) xrot(-90) cylinder(h=20, r1=5, r2=0); // xring(n=6, r=10, sa=45) xrot(-90) cylinder(h=20, r1=5, r2=0); @@ -1643,7 +1638,7 @@ module ovoid_spread(r=undef, d=undef, n=100, cone_ang=90, scale=[1,1,1], perp=tr theta_phis = [for (x=[0:n-1]) [180*(1+sqrt(5))*(x+0.5)%360, acos(1-2*(x+0.5)/cnt)]]; for ($idx = [0:len(theta_phis)-1]) { - tp = theta_phis[$idx]; + tp = theta_phis[$idx]; xyz = spherical_to_xyz(r, tp[0], tp[1]); $pos = vmul(xyz,scale); $theta = tp[0]; @@ -2274,17 +2269,28 @@ module shell2d(thickness, or=0, ir=0, fill=0, round=0) // Named alignments, as well as `ALIGN_NEG`/`ALIGN_POS` are aligned pre-rotation. // // Usage: -// orient_and_align(size, [orient], [align], [center], [noncentered], [orig_orient], [orig_align], [alignments]) ... +// orient_and_align(size, [orient], [align], [center], [noncentered], [orig_orient], [orig_align], [alignments], [chain]) ... // // Arguments: -// size = The size of the part. -// orient = The axis to align to. Use ORIENT_ constants from constants.scad +// size = The [X,Y,Z] size of the part. +// size2 = The [X,Y] size of the top of the part. +// shift = The [X,Y] offset of the top of the part, compared to the bottom of the part. +// orient = The axis to align to. Use `ORIENT_` constants from `constants.scad`. // align = The side of the origin the part should be aligned with. // center = If given, overrides `align`. If true, centers vertically. If false, `align` will be set to the value in `noncentered`. // noncentered = The value to set `align` to if `center` == `false`. Default: `V_UP`. // orig_orient = The original orientation of the part. Default: `ORIENT_Z`. // orig_align = The original alignment of the part. Default: `V_CENTER`. -// alignments = A list of `["name", [X,Y,Z]]` alignment-label/offset pairs. +// alignments = A list of extra, non-standard connectors that can be aligned to. +// chain = If true, allow attachable children. +// +// Side Effects: +// `$parent_size` is set to the parent object's cubical region size. +// `$parent_size2` is set to the parent object's top [X,Y] size. +// `$parent_shift` is set to the parent object's `shift` value, if any. +// `$parent_orient` is set to the parent object's `orient` value. +// `$parent_align` is set to the parent object's `align` value. +// `$parent_conns` is set to the parent object's list of non-standard extra connectors. // // Example: // #cylinder(d=5, h=10); @@ -2293,55 +2299,321 @@ module orient_and_align( size=undef, orient=ORIENT_Z, align=V_CENTER, center=undef, noncentered=ALIGN_POS, orig_orient=ORIENT_Z, orig_align=V_CENTER, - alignments=[] + size2=undef, shift=[0,0], + alignments=[], chain=false ) { - algn = is_def(center)? (center? V_CENTER : noncentered) : align; - if (orig_align != V_CENTER) { - orient_and_align(size=size, orient=orient, align=algn) { - translate(vmul(size/2, -orig_align)) children(); - } - } else if (orig_orient != ORIENT_Z) { - rotsize = ( - (orig_orient==ORIENT_X)? [size[1], size[2], size[0]] : - (orig_orient==ORIENT_Y)? [size[0], size[2], size[1]] : - vabs(rotate_points3d([size], orig_orient, reverse=true)[0]) - ); - orient_and_align(size=rotsize, orient=orient, align=algn) { - rot(orig_orient,reverse=true) children(); - } - } else if (is_scalar(algn)) { - // If align is a number and not a vector, then translate PRE-rotation. - orient_and_align(size=size, orient=orient) { - translate(vmul(size/2, algn*V_UP)) children(); - } - } else if (is_str(algn)) { - // If align is a string, look for an alignments label that matches. - found = search([algn], alignments, num_returns_per_match=1); - if (found != [[]]) { - orient_and_align(size=size, orient=orient) { - idx = found[0]; - delta = alignments[idx][1]; - translate(-delta) children(); - } + size2 = point2d(default(size2, size)); + shift = point2d(shift); + align = is_def(center)? (center? V_CENTER : noncentered) : align; + m = matrix4_mult(concat( + (orig_align==V_CENTER)? [] : [ + // If original alignment is not centered, center it. + matrix4_translate(vmul(size/2, -orig_align)) + ], + (orig_orient==ORIENT_Z)? [] : [ + // If original orientation is not upright, rotate it upright. + matrix4_zrot(-orig_orient.z), + matrix4_yrot(-orig_orient.y), + matrix4_xrot(-orig_orient.x) + ], + ($attach_to!=undef)? ( + let( + conn = find_connector($attach_to, size.z, size, size2=size2, shift=shift), + ang = vector_angle(conn[2],V_DOWN), + axis = vector_axis(conn[2],V_DOWN), + ang2 = (conn[2]==V_UP || conn[2]==V_DOWN)? 0 : 180-conn[3], + axis2 = rotate_points3d([axis],[0,0,ang2])[0] + ) [ + matrix4_translate(-conn[1]), + matrix4_zrot(ang2), + matrix4_rot_by_axis(axis2, ang) + ] + ) : concat( + (!is_scalar(align) && !is_str(align))? [] : [ + let(conn = find_connector(align, size.z, size, size2=size2, shift=shift, extra_conns=alignments)) + matrix4_translate(-conn[1]) + ], + (orient==ORIENT_Z)? [] : [ + matrix4_xrot(orient.x), + matrix4_yrot(orient.y), + matrix4_zrot(orient.z) + ], + (!is_array(align) || align==[0,0,0])? [] : [ + let(conn = find_connector(align, size.z, size, size2=size2, shift=shift)) + matrix4_translate(conn[1]) + ] + ) + )); + $attach_to = undef; + $parent_size = size; + $parent_size2 = size2; + $parent_shift = shift; + $parent_orient = orient; + $parent_align = align; + $parent_conns = alignments; + tags = _str_char_split($tags, " "); + s_tags = $tags_shown; + h_tags = $tags_hidden; + shown = !s_tags || any([for (tag=tags) in_list(tag, s_tags)]); + hidden = any([for (tag=tags) in_list(tag, h_tags)]); + echo(tags=tags, shown=shown, hidden=hidden, view=shown&&!hidden); + multmatrix(m) { + if ($children>1 && chain) { + if(shown && !hidden) color($color) for (i=[0:$children-2]) children(i); + children($children-1); } else { - assertion(1==0, str("Alignment label '", algn, "' is not known.", (alignments? str(" Try one of ", [for (v=alignments) v[0]], ".") : ""))); + if(shown && !hidden) color($color) children(); } - } else if (orient != ORIENT_Z) { - rotsize = ( - (orient==ORIENT_X)? [size[2], size[0], size[1]] : - (orient==ORIENT_Y)? [size[0], size[2], size[1]] : - vabs(rotate_points3d([size], orient)[0]) - ); - orient_and_align(size=rotsize, align=algn) { - rotate(orient) children(); - } - } else if (is_def(algn) && algn != [0,0,0]) { - translate(vmul(size/2, algn)) children(); - } else { - children(); } } +// Internal. Not exposed. +function _str_char_split(s,delim,n=0,acc=[],word="") = + (n>=len(s))? concat(acc, [word]) : + (s[n]==delim)? + _str_char_split(s,delim,n+1,concat(acc,[word]),"") : + _str_char_split(s,delim,n+1,acc,str(word,s[n])); + + + +// Function: connector() +// Usage: +// connector(name, pos, dir, [rot]) +// Description: +// Creates a connector data structure. +// Arguments: +// name = The string name of the connector. Lowercase. Words separated by single dashes. No spaces. +// pos = The [X,Y,Z] position of the connector. +// dir = A vector pointing in the direction parts should project from the connector position. +// rot = If needed, the angle to rotate the part around the direction vector. +function connector(name, pos=[0,0,0], dir=V_UP, rot=0) = [name, pos, dir, rot]; + + + +// Function: find_connector() +// Usage: +// find_connector(align, h, size, [size2], [shift], [edges], [corners]); +// Description: +// Generates a list of typical connectors for a cubical region of the given size. +// Arguments: +// align = Named alignment/connector string. +// h = Height of the region. +// size = The [X,Y] size of the bottom of the cubical region. +// size2 = The [X,Y] size of the top of the cubical region. +// shift = The [X,Y] amount to shift the center of the top with respect to the center of the bottom. +// extra_conns = A list of extra named connectors. +function find_connector(align, h, size, size2=undef, shift=[0,0], extra_conns=[]) = + let( + eps = 1e-9, + shift = point3d(shift), + size = point3d(point2d(size)), + size2 = (size2!=undef)? point3d(point2d(size2)) : size, + found = !is_str(align)? [] : search([align], extra_conns, num_returns_per_match=1)[0] + ) (found!=[])? extra_conns[found] : let( + words = is_scalar(align)? ( + align==ALIGN_NEG? ["top"] : + align==ALIGN_POS? ["bottom"] : + ["center"] + ) : is_array(align)? align : _str_char_split(align,"-"), + ovec = is_array(align)? align : + sum([ + for (word = words) + word=="left"? V_LEFT : + word=="right"? V_RIGHT : + word=="front"? V_FWD : + word=="back"? V_BACK : + word=="top"? V_UP : + word=="bottom"? V_DOWN : + word=="center"? V_ZERO : + assertion(false, + str( + "Alignment label '", align, "' is not known.", + (!extra_conns? "" : str( + " Try one of ", [for (v=extra_conns) v[0]], " or the standard alignments." + )) + ) + ) + ]), + top = [-size2/2+shift, shift, size2/2+shift], + bot = [-size/2, V_ZERO, size/2], + toppt = [top[ovec.x+1].x, top[ovec.y+1].y, h/2], + botpt = [bot[ovec.x+1].x, bot[ovec.y+1].y, -h/2], + pos = lerp(botpt, toppt, (ovec.z+1)/2), + oang = ( + ovec == V_UP? 0 : + ovec == V_DOWN? 0 : + (norm([ovec.x,ovec.y]) < eps)? 0 : atan2(ovec.y, ovec.x)+90 + ), + vec = ( + abs(ovec.z) > eps? ovec : + rotate_points3d([ovec], from=V_UP, to=toppt-botpt)[0] + ) + ) [align, pos, vec, oang]; + + + +// Module: attach() +// Usage: +// attach(name, [overlap], [norot]) ... +// attach(name, to, [overlap]) ... +// Description: +// Attaches children to a parent object at an attachment point and orientation. +// Arguments: +// name = The name of the parent attachment point to attach to. +// to = The name of the child attachment point. +// overlap = Amount to sink child into the parent. +// norot = If true, don't rotate children when aligning to the attachment point. +// Example: +// spheroid(d=20) { +// attach("top") down(1.5) cyl(l=11.5, d1=10, d2=5, align="bottom"); +// attach("right", "bottom") down(1.5) cyl(l=11.5, d1=10, d2=5); +// attach("front") down(1.5) cyl(l=11.5, d1=10, d2=5, align="bottom"); +// } +module attach(name, to=undef, overlap=undef, norot=false) +{ + assertion($parent_size != undef, "No object to attach to!"); + overlap = (overlap!=undef)? overlap : $overlap; + conn = find_connector(name, $parent_size.z, point2d($parent_size), size2=$parent_size2, shift=$parent_shift, extra_conns=$parent_conns); + pos = conn[1]; + vec = conn[2]; + ang = conn[3]; + $attach_to = to; + $attach_conn = conn; + if (norot || (norm(vec-V_UP)<1e-9 && ang==0)) { + translate(pos) translate([0,0,-overlap]) children(); + } else { + translate(pos) rot(ang,from=V_UP,to=vec) translate([0,0,-overlap]) children(); + } +} + + +// Module: tags() +// Usage: +// tags(tags) ... +// Description: +// Marks all children with the given tags. +// Arguments: +// tags = String containing space delimited set of tags to apply. +module tags(tags) +{ + $tags = tags; + children(); +} + + +// Module: recolor() +// Usage: +// recolor(c) ... +// Description: +// Sets the color for children that can use the $color special variable. +// Example: +// recolor("red") cyl(l=20, d=10); +module recolor(c) +{ + $color = c; + children(); +} + + +// Module: hide() +// Usage: +// hide(tags) ... +// Description: Hides all children with the given tags. +module hide(tags="") +{ + $tags_hidden = tags==""? [] : _str_char_split(tags, " "); + children(); +} + + +// Module: show() +// Usage: +// show(tags) ... +// Description: Shows only children with the given tags. +module show(tags="") +{ + $tags_shown = tags==""? [] : _str_char_split(tags, " "); + children(); +} + + +// Module: diff() +// Usage: +// diff(neg, [keep]) ... +// diff(neg, pos, [keep]) ... +// Description: +// If `neg` is given, takes the union of all children with tags +// that are in `neg`, and differences them from the union of all +// children with tags in `pos`. If `pos` is not given, then all +// items in `neg` are differenced from all items not in `neg`. If +// `keep` is given, all children with tags in `keep` are then unioned +// with the result. If `keep` is not given, all children without +// tags in `pos` or `neg` are then unioned with the result. +// Arguments: +// neg = String containing space delimited set of tag names of children to difference away. +// pos = String containing space delimited set of tag names of children to be differenced away from. +// keep = String containing space delimited set of tag names of children to keep whole. +module diff(neg, pos=undef, keep=undef) +{ + difference() { + if (pos != undef) { + show(pos) children(); + } else { + if (keep == undef) { + hide(neg) children(); + } else { + hide(str(neg," ",keep)) children(); + } + } + show(neg) children(); + } + if (keep!=undef) { + show(keep) children(); + } else if (pos!=undef) { + hide(str(pos," ",neg)) children(); + } +} + + +// Module: intersect() +// Usage: +// intersect(a, [keep]) ... +// intersect(a, b, [keep]) ... +// Description: +// If `a` is given, takes the union of all children with tags that +// are in `a`, and intersection()s them with the union of all +// children with tags in `b`. If `b` is not given, then the union +// of all items with tags in `a` are intersection()ed with the union +// of all items without tags in `a`. If `keep` is given, then the +// result is unioned with all the children with tags in `keep`. If +// `keep` is not given, all children without tags in `a` or `b` are +// unioned with the result. +// Arguments: +// a = String containing space delimited set of tag names of children. +// b = String containing space delimited set of tag names of children. +// keep = String containing space delimited set of tag names of children to keep whole. +module intersect(a, b=undef, keep=undef) +{ + intersection() { + if (b != undef) { + show(b) children(); + } else { + if (keep == undef) { + hide(a) children(); + } else { + hide(str(a," ",keep)) children(); + } + } + show(a) children(); + } + if (keep!=undef) { + show(keep) children(); + } else if (b!=undef) { + hide(str(a," ",b)) children(); + } +} + + // vim: noexpandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap