////////////////////////////////////////////////////////////////////// // LibFile: miscellaneous.scad // Miscellaneous modules that didn't fit in anywhere else, including // bounding box, chain hull, extrusions, and minkowski based // modules. // Includes: // include // FileGroup: Basic Modeling // FileSummary: Extrusion, bounding box, chain hull and minkowski-based transforms. // FileFootnotes: STD=Included in std.scad ////////////////////////////////////////////////////////////////////// // Section: Extrusion // Module: extrude_from_to() // Synopsis: Extrudes 2D children between two points in 3D space. // SynTags: Geom // Topics: Extrusion, Miscellaneous // See Also: path_sweep(), path_extrude2d() // Usage: // extrude_from_to(pt1, pt2, [convexity=], [twist=], [scale=], [slices=]) 2D-CHILDREN; // Description: // Extrudes the 2D children linearly between the 3d points pt1 and pt2. The origin of the 2D children are placed on // pt1 and pt2, and oriented perpendicular to the line between the points. // Arguments: // pt1 = starting point of extrusion. // pt2 = ending point of extrusion. // --- // convexity = max number of times a line could intersect a wall of the 2D shape being extruded. // twist = number of degrees to twist the 2D shape over the entire extrusion length. // scale = scale multiplier for end of extrusion compared the start. // slices = Number of slices along the extrusion to break the extrusion into. Useful for refining `twist` extrusions. // Example(FlatSpin,VPD=200,VPT=[0,0,15]): // extrude_from_to([0,0,0], [10,20,30], convexity=4, twist=360, scale=3.0, slices=40) { // xcopies(3) circle(3, $fn=32); // } module extrude_from_to(pt1, pt2, convexity, twist, scale, slices) { req_children($children); check = assert(is_vector(pt1),"First point must be a vector") assert(is_vector(pt2),"Second point must be a vector"); pt1 = point3d(pt1); pt2 = point3d(pt2); rtp = xyz_to_spherical(pt2-pt1); attachable() { translate(pt1) { rotate([0, rtp[2], rtp[1]]) { if (rtp[0] > 0) { linear_extrude(height=rtp[0], convexity=convexity, center=false, slices=slices, twist=twist, scale=scale) { children(); } } } } union(); } } // Module: path_extrude2d() // Synopsis: Extrudes 2D children along a 2D path. // SynTags: Geom // Topics: Miscellaneous, Extrusion // See Also: path_sweep(), path_extrude() // Usage: // path_extrude2d(path, [caps=], [closed=], [s=], [convexity=]) 2D-CHILDREN; // Description: // Extrudes 2D children along the given 2D path, with optional rounded endcaps. // It works by constructing straight sections corresponding to each segment of the path and inserting rounded joints at each corner. // If the children are symmetric across the Y axis line then you can set caps=true to produce rounded caps on the ends of the profile. // If you set caps to true for asymmetric children then incorrect caps will be generated. // Arguments: // path = The 2D path to extrude the geometry along. // --- // caps = If true, caps each end of the path with a rounded copy of the children. Children must by symmetric across the Y axis, or results are wrong. Default: false // closed = If true, connect the starting point of the path to the ending point. Default: false // convexity = The max number of times a line could pass though a wall. Default: 10 // s = Mask size to use. Use a number larger than twice your object's largest axis. If you make this too large, it messes with centering your view. Default: The length of the diagonal of the path's bounding box. // Example: // path = [ // each right(50, p=arc(d=100,angle=[90,180])), // each left(50, p=arc(d=100,angle=[0,-90])), // ]; // path_extrude2d(path,caps=false) { // fwd(2.5) square([5,6],center=true); // fwd(6) square([10,5],center=true); // } // Example: // path_extrude2d(arc(d=100,angle=[180,270]),caps=true) // trapezoid(w1=10, w2=5, h=10, anchor=BACK); // Example: // include // path = bezpath_curve([ // [-50,0], [-25,50], [0,0], [50,0] // ]); // path_extrude2d(path, caps=false) // trapezoid(w1=10, w2=3, h=5, anchor=BACK); // Example: Un-Closed Path // $fn=16; // spath = star(id=15,od=35,n=5); // path_extrude2d(spath, caps=false, closed=false) // move_copies([[-3.5,1.5],[0.0,3.0],[3.5,1.5]]) // circle(r=1.5); // Example: Complex Endcaps // $fn=16; // spath = star(id=15,od=35,n=5); // path_extrude2d(spath, caps=true, closed=false) // move_copies([[-3.5,1.5],[0.0,3.0],[3.5,1.5]]) // circle(r=1.5); module path_extrude2d(path, caps=false, closed=false, s, convexity=10) { req_children($children); extra_ang = 0.1; // Extra angle for overlap of joints check = assert(caps==false || closed==false, "Cannot have caps on a closed extrusion") assert(is_path(path,2)); path = deduplicate(path); s = s!=undef? s : let(b = pointlist_bounds(path)) norm(b[1]-b[0]); check2 = assert(is_finite(s)); L = len(path); attachable(){ union(){ for (i = [0:1:L-(closed?1:2)]) { seg = select(path, i, i+1); segv = seg[1] - seg[0]; seglen = norm(segv); translate((seg[0]+seg[1])/2) { rot(from=BACK, to=segv) { difference() { xrot(90) { linear_extrude(height=seglen, center=true, convexity=convexity) { children(); } } if (closed || i>0) { pt = select(path, i-1); pang = v_theta(rot(from=-segv, to=RIGHT, p=pt - seg[0])); fwd(seglen/2+0.01) zrot(pang/2) cube(s, anchor=BACK); } if (closed || i 0) { linear_extrude(height=dist+clipsize/2, convexity=convexity) { children(); } } } } } translate(pt1) { hq = (i > 0)? interp[2*i-1] : T; multmatrix(hq) down(clipsize/2+epsilon) cube(clipsize, center=true); } translate(pt2) { hq = (i < ptcount-2)? interp[2*i+1] : T; multmatrix(hq) up(clipsize/2+epsilon) cube(clipsize, center=true); } } } union(); } } // Module: cylindrical_extrude() // Synopsis: Extrudes 2D children outwards around a cylinder. // SynTags: Geom // Topics: Miscellaneous, Extrusion, Rotation // See Also: heightfield(), cylindrical_heightfield(), cyl() // Usage: // cylindrical_extrude(ir|id=, or|od=, [size=], [convexity=], [spin=], [orient=]) 2D-CHILDREN; // Description: // Chops the 2D children into rectangles and extrudes each rectangle as a facet around an // approximate cylindrical shape. Uses $fn/$fa/$fs to control the number of facets. // By default the calculation assumes that the children occupy in the X direction one revolution of the // cylinder of specified radius/diameter and are not more than 1000 units tall (in the Y direction). // If the children are in fact much smaller in width then this assumption is inefficient. If the children // are wider then they will be truncated at one revolution. To address either of these problems you can set // the `size` parameter. Note that the specified height isn't very important: it just needs to be larger than // the actual height of the children, which is why it defaults to 1000. If you set `size` to a scalar then // that only changes the X value and the Y value remains at the default of 1000. // . // When performing the wrap, the X=0 line of the children maps to the Y- axis and the facets are centered on the Y- axis. // This is not consistent with how cylinder() creates its facets. If `$fn` is a multiple of 4 then the facets will line // up with a cylinder. Otherwise you must rotate a cylinder by 90 deg in the case of `$fn` even or `90-360/$fn/2` if `$fn` is odd. // Arguments: // ir = The inner radius to extrude from. // or = The outer radius to extrude to. // --- // od = The outer diameter to extrude to. // id = The inner diameter to extrude from. // size = If a scalar, the width of the 2D children. If a vector, the [X,Y] size of the 2D children. Default: [`2*PI*or`,1000] // convexity = The max number of times a line could pass though a wall. Default: 10 // spin = Amount in degrees to spin around cylindrical axis. Default: 0 // orient = The orientation of the cylinder to wrap around, given as a vector. Default: UP // Example: Basic example with defaults. This will run faster with large facet counts if you set `size=100` // cylindrical_extrude(or=50, ir=45) // text(text="Hello World!", size=10, halign="center", valign="center"); // Example: Spin Around the Cylindrical Axis // cylindrical_extrude(or=50, ir=45, spin=90) // text(text="Hello World!", size=10, halign="center", valign="center"); // Example: Orient to the Y Axis. // cylindrical_extrude(or=40, ir=35, orient=BACK) // text(text="Hello World!", size=10, halign="center", valign="center"); // Example(Med): You must give a size argument for this example where the child wraps fully around the cylinder // cylindrical_extrude(or=27, ir=25, size=300, spin=-85) // zrot(-10)text(text="This long text wraps around the cylinder.", size=10, halign="center", valign="center"); module cylindrical_extrude(ir, or, od, id, size, convexity=10, spin=0, orient=UP) { req_children($children); ir = get_radius(r=ir,d=id); or = get_radius(r=or,d=od); check2 = assert(all_positive([ir,or]), "Must supply positive inner and outer radius or diameter"); circumf = 2 * PI * or; size = is_undef(size) ? [circumf, 1000] : is_num(size) ? [size, 1000] : size; check1 = assert(is_vector(size,2) && all_positive(size), "Size must be a positive number or 2-vector"); sides = segs(or); step = circumf / sides; steps = ceil(size.x / step); scalefactor = sides/PI*sin(180/sides); // Scale from circle to polygon, which has shorter length attachable() { rot(from=UP, to=orient) rot(spin) { for (i=[0:1:steps-1]) { x = (i+0.5-steps/2) * step; zrot(360 * x / circumf) { fwd(or*cos(180/sides)) { xrot(-90) { linear_extrude(height=or-ir, scale=[ir/or,1], center=false, convexity=convexity) { yflip() xscale(scalefactor) intersection() { left(x) children(); rect([quantup(step,pow(2,-15)),size.y]); } } } } } } } union(); } } ////////////////////////////////////////////////////////////////////// // Section: Bounding Box ////////////////////////////////////////////////////////////////////// // Module: bounding_box() // Synopsis: Creates the smallest bounding box that contains all the children. // SynTags: Geom // Topics: Miscellaneous, Bounds, Bounding Boxes // See Also: pointlist_bounds() // Usage: // bounding_box([excess],[planar]) CHILDREN; // Description: // Returns the smallest axis-aligned square (or cube) shape that contains all the 2D (or 3D) // children given. The module children() must 3d when planar=false and // 2d when planar=true, or you will get a warning of mixing dimension // or scaling by 0. // Arguments: // excess = The amount that the bounding box should be larger than needed to bound the children, in each axis. // planar = If true, creates a 2D bounding rectangle. Is false, creates a 3D bounding cube. Default: false // Example(3D): // module shapes() { // translate([10,8,4]) cube(5); // translate([3,0,12]) cube(2); // } // #bounding_box() shapes(); // shapes(); // Example(2D): // module shapes() { // translate([10,8]) square(5); // translate([3,0]) square(2); // } // #bounding_box(planar=true) shapes(); // shapes(); module bounding_box(excess=0, planar=false) { // a 3d (or 2d when planar=true) approx. of the children projection on X axis module _xProjection() { if (planar) { projection() rotate([90,0,0]) linear_extrude(1, center=true) hull() children(); } else { xs = excess<.1? 1: excess; linear_extrude(xs, center=true) projection() rotate([90,0,0]) linear_extrude(xs, center=true) projection() hull() children(); } } // a bounding box with an offset of 1 in all axis module _oversize_bbox() { if (planar) { minkowski() { _xProjection() children(); // x axis rotate(-90) _xProjection() rotate(90) children(); // y axis } } else { minkowski() { _xProjection() children(); // x axis rotate(-90) _xProjection() rotate(90) children(); // y axis rotate([0,-90,0]) _xProjection() rotate([0,90,0]) children(); // z axis } } } // offsets a cube by `excess` module _shrink_cube() { intersection() { translate((1-excess)*[ 1, 1, 1]) children(); translate((1-excess)*[-1,-1,-1]) children(); } } req_children($children); attachable(){ if(planar) { offset(excess-1/2) _oversize_bbox() children(); } else { render(convexity=2) if (excess>.1) { _oversize_bbox() children(); } else { _shrink_cube() _oversize_bbox() children(); } } union(); } } ////////////////////////////////////////////////////////////////////// // Section: Hull Based Modules ////////////////////////////////////////////////////////////////////// // Module: chain_hull() // Synopsis: Performs the union of hull operations between consecutive pairs of children. // SynTags: Geom // Topics: Miscellaneous // See Also: hull() // Usage: // chain_hull() CHILDREN; // // Description: // Performs hull operations between consecutive pairs of children, // then unions all of the hull results. This can be a very slow // operation, but it can provide results that are hard to get // otherwise. // // Side Effects: // `$idx` is set to the index value of the first child of each hulling pair, and can be used to modify each child pair individually. // `$primary` is set to true when the child is the first in a chain pair. // // Example: // chain_hull() { // cube(5, center=true); // translate([30, 0, 0]) sphere(d=15); // translate([60, 30, 0]) cylinder(d=10, h=20); // translate([60, 60, 0]) cube([10,1,20], center=false); // } // Example: Using `$idx` and `$primary` // chain_hull() { // zrot( 0) right(100) if ($primary) cube(5+3*$idx,center=true); else sphere(r=10+3*$idx); // zrot( 45) right(100) if ($primary) cube(5+3*$idx,center=true); else sphere(r=10+3*$idx); // zrot( 90) right(100) if ($primary) cube(5+3*$idx,center=true); else sphere(r=10+3*$idx); // zrot(135) right(100) if ($primary) cube(5+3*$idx,center=true); else sphere(r=10+3*$idx); // zrot(180) right(100) if ($primary) cube(5+3*$idx,center=true); else sphere(r=10+3*$idx); // } module chain_hull() { req_children($children); attachable(){ if ($children == 1) { children(); } else { for (i =[1:1:$children-1]) { $idx = i; hull() { let($primary=true) children(i-1); let($primary=false) children(i); } } } union(); } } ////////////////////////////////////////////////////////////////////// // Section: Minkowski and 3D Offset ////////////////////////////////////////////////////////////////////// // Module: minkowski_difference() // Synopsis: Removes diff shapes from base shape surface. // SynTags: Geom // Topics: Miscellaneous // See Also: offset3d() // Usage: // minkowski_difference() { BASE; DIFF1; DIFF2; ... } // Description: // Takes a 3D base shape and one or more 3D diff shapes, carves out the diff shapes from the // surface of the base shape, in a way complementary to how `minkowski()` unions shapes to the // surface of its base shape. // Arguments: // planar = If true, performs minkowski difference in 2D. Default: false (3D) // Example: // minkowski_difference() { // union() { // cube([120,70,70], center=true); // cube([70,120,70], center=true); // cube([70,70,120], center=true); // } // sphere(r=10); // } module minkowski_difference(planar=false) { req_children($children); attachable(){ difference() { bounding_box(excess=0, planar=planar) children(0); render(convexity=20) { minkowski() { difference() { bounding_box(excess=1, planar=planar) children(0); children(0); } for (i=[1:1:$children-1]) children(i); } } } union(); } } // Module: offset3d() // Synopsis: Expands or contracts the surface of a 3D object. // SynTags: Geom // Topics: Miscellaneous // See Also: minkowski_difference(), round3d() // Usage: // offset3d(r, [size], [convexity]) CHILDREN; // Description: // Expands or contracts the surface of a 3D object by a given amount. This is very, very slow. // No really, this is unbearably slow. It uses `minkowski()`. Use this as a last resort. // This is so slow that no example images will be rendered. // Arguments: // r = Radius to expand object by. Negative numbers contract the object. // size = Maximum size of object to be contracted, given as a scalar. Default: 100 // convexity = Max number of times a line could intersect the walls of the object. Default: 10 module offset3d(r, size=100, convexity=10) { req_children($children); n = quant(max(8,segs(abs(r))),4); attachable(){ if (r==0) { children(); } else if (r>0) { render(convexity=convexity) minkowski() { children(); sphere(r, $fn=n); } } else { size2 = size * [1,1,1]; size1 = size2 * 1.02; render(convexity=convexity) difference() { cube(size2, center=true); minkowski() { difference() { cube(size1, center=true); children(); } sphere(-r, $fn=n); } } } union(); } } // Module: round3d() // Synopsis: Rounds arbitrary 3d objects. // SynTags: Geom // Topics: Rounding, Miscellaneous // See Also: offset3d(), minkowski_difference() // Usage: // round3d(r) CHILDREN; // round3d(or) CHILDREN; // round3d(ir) CHILDREN; // round3d(or, ir) CHILDREN; // Description: // Rounds arbitrary 3D objects. Giving `r` rounds all concave and convex corners. Giving just `ir` // rounds just concave corners. Giving just `or` rounds convex corners. Giving both `ir` and `or` // can let you round to different radii for concave and convex corners. The 3D object must not have // any parts narrower than twice the `or` radius. Such parts will disappear. This is an *extremely* // slow operation. I cannot emphasize enough just how slow it is. It uses `minkowski()` multiple times. // Use this as a last resort. This is so slow that no example images will be rendered. // Arguments: // r = Radius to round all concave and convex corners to. // or = Radius to round only outside (convex) corners to. Use instead of `r`. // ir = Radius to round only inside (concave) corners to. Use instead of `r`. module round3d(r, or, ir, size=100) { req_children($children); or = get_radius(r1=or, r=r, dflt=0); ir = get_radius(r1=ir, r=r, dflt=0); attachable(){ offset3d(or, size=size) offset3d(-ir-or, size=size) offset3d(ir, size=size) children(); union(); } } // vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap