////////////////////////////////////////////////////////////////////// // LibFile: attachments.scad // This is the file that handles attachments and orientation of children. // To use, add the following lines to the beginning of your file: // ``` // include // ``` ////////////////////////////////////////////////////////////////////// // Default values for attachment code. $tags = ""; $overlap = 0.01; $color = undef; $attach_to = undef; $attach_anchor = [CENTER, CENTER, UP, 0]; $attach_norot = false; $parent_size = undef; $parent_size2 = undef; $parent_shift = [0,0]; $parent_anchors = []; $parent_anchor = BOTTOM; $parent_orient = UP; $tags_shown = []; $tags_hidden = []; // Section: Anchors, Spin, and Orientation // This library adds the concept of anchoring, spin and orientation to the `cube()`, `cylinder()` // and `sphere()` builtins, as well as to most of the shapes provided by this library itself. // * An anchor is a place on an object which you can align the object to, or attach other objects // to using `attach()` or `position()`. An anchor has a position, a direction, and a spin. // The direction and spin are used to orient other objects to match when using `attach()`. // * Spin is a simple rotation around the Z axis. // * Orientation is rotating an object so that its top is pointed towards a given vector. // An object will first be translated to its anchor position, then spun, then oriented. // // ## Anchor // Anchoring is specified with the `anchor` argument in most shape modules. // Specifying `anchor` when creating an object will translate the object so // that the anchor point is at the origin (0,0,0). Anchoring always occurs // before spin and orientation are applied. // // An anchor can be referred to in one of two ways; as a directional vector, // or as a named anchor string. // // When given as a vector, it points, in a general way, towards the face, edge, or // corner of the object that you want the anchor for, relative to the center of // the object. There are directional constants named `TOP`, `BOTTOM`, `FRONT`, `BACK`, // `LEFT`, and `RIGHT` that you can add together to specify an anchor point. // For example: // - `[0,0,1]` is the same as `TOP` and refers to the center of the top face. // - `[-1,0,1]` is the same as `TOP+LEFT`, and refers to the center of the top-left edge. // - `[1,1,-1]` is the same as `BOTTOM+BACK+RIGHT`, and refers to the bottom-back-right corner. // // The components of the directional vector should all be `1`, `0`, or `-1`. // When the object is cylindrical, conical, or spherical in nature, the anchors will be // located around the surface of the cylinder, cone, or sphere, relative to the center. // The direction of a face anchor will be perpendicular to the face, pointing outward. // The direction of a edge anchor will be the average of the anchor directions of the // two faces the edge is between. The direction of a corner anchor will be the average // of the anchor directions of the three faces the corner is on. The spin of all standard // anchors is 0. // // Some more complex objects, like screws and stepper motors, have named anchors // to refer to places on the object that are not at one of the standard faces, edges // or corners. For example, stepper motors have anchors for `"screw1"`, `"screw2"`, // etc. to refer to the various screwholes on the stepper motor shape. The names, // positions, directions, and spins of these anchors will be specific to the object, // and will be documented when they exist. // // ## Spin // Spin is specified with the `spin` argument in most shape modules. Specifying `spin` // when creating an object will rotate the object counter-clockwise around the Z axis // by the given number of degrees. Spin is always applied after anchoring, and before // orientation. // // ## Orient // Orientation is specified with the `orient` argument in most shape modules. Specifying // `orient` when creating an object will rotate the object such that the top of the // object will be pointed at the vector direction given in the `orient` argument. // Orientation is always applied after anchoring and spin. The constants `UP`, `DOWN`, // `FRONT`, `BACK`, `LEFT`, and `RIGHT` can be added together to form the directional // vector for this. ie: `LEFT+BACK` // Section: Functions // Function: anchorpt() // Usage: // anchor(name, pos, [dir], [rot]) // Description: // Creates a anchor data structure. // Arguments: // name = The string name of the anchor. Lowercase. Words separated by single dashes. No spaces. // pos = The [X,Y,Z] position of the anchor. // orient = A vector pointing in the direction parts should project from the anchor position. // spin = If needed, the angle to rotate the part around the direction vector. function anchorpt(name, pos=[0,0,0], orient=UP, spin=0) = [name, pos, orient, spin]; // Function: find_anchor() // Usage: // find_anchor(anchor, h, size, [size2], [shift], [edges], [corners]); // Description: // Returns anchor data for the given vector or anchor name. // Arguments: // anchor = Vector or named anchor 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. // offset = If the anchor is not CENTER, this is the offset to add to the rest of the anchor points. // geometry = One of "cube", "cylinder", or "sphere" to denote the overall geometry of the shape. Cones are "cylinder", and prismoids are "cube" for this purpose. Default: "cube" // anchors = A list of extra non-standard named anchors. // two_d = If true, object will be treated as 2D. function find_anchor(anchor, h, size, size2=undef, shift=[0,0], offset=[0,0,0], anchors=[], geometry="cube", two_d=false) = is_string(anchor)? ( let(found = search([anchor], anchors, num_returns_per_match=1)[0]) assert(found!=[], str("Unknown anchor: ",anchor)) anchors[found] ) : ( assert(is_vector(anchor),str("anchor=",anchor)) let( size = point2d(size), size2 = (size2!=undef)? point2d(size2) : size, shift = point2d(shift), oang = ( two_d? 0 : anchor == UP? 0 : anchor == DOWN? 0 : (norm([anchor.x,anchor.y]) < EPSILON)? 0 : atan2(anchor.y, anchor.x)+90 ) ) geometry=="sphere"? let( phi = (anchor==UP||anchor==CENTER)? 0 : anchor==DOWN? 180 : 90 + (45 * anchor.z), theta = anchor==CENTER? 90 : atan2(anchor.y, anchor.x), vec = spherical_to_xyz(1, theta, phi), offset = anchor==CENTER? [0,0,0] : offset, pos = anchor==CENTER? CENTER : vmul(vec, (point3d(size)+h*UP)/2) + offset ) [anchor, pos, vec, oang] : let ( xyal = ( geometry=="cylinder"? ( let(xy = point2d(anchor)) norm(xy)>0? xy/norm(xy) : [0,0] ) : point2d(anchor) ), botpt = point3d(vmul(size/2,xyal))+DOWN*h/2, toppt = point3d(vmul(size2/2,xyal)+shift)+UP*h/2, offset = anchor==CENTER? [0,0,0] : offset, pos = lerp(botpt, toppt, (anchor.z+1)/2) + offset, sidevec = two_d? point3d(xyal) : approx(norm(xyal),0)? [0,0,0] : rotate_points3d([point3d(xyal)], from=UP, to=toppt-botpt)[0], vec = ( two_d? sidevec : anchor==CENTER? UP : norm([anchor.x,anchor.y]) < EPSILON? anchor : norm(size)+norm(size2) < EPSILON? anchor : abs(anchor.z) < EPSILON? sidevec : anchor.z>0? (UP+sidevec)/2 : (DOWN+sidevec)/2 ) ) [anchor, pos, vec, oang] ); 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])); // Section: Modules // Module: orient_and_anchor() // // Description: // Takes a vertically oriented part and anchors, spins and orients it. // This is useful for making a custom shape available in various // orientations and anchorings without extra translate()s and rotate()s. // Children should be vertically (Z-axis) oriented, and centered. // Non-vector anchor points should be named via the `anchors` arg. // // If this is *not* run as a child of `attach()` with the `to` argument // given, then the following transformations are performed in order: // * Translates so the `anchor` point is at the origin (0,0,0). // * Rotates around the Z axis by `spin` degrees counter-clockwise. // * Rotates so the top of the part points towards the vector `orient`. // // If this is called as a child of `attach(from,to)`, then the info // for the anchor points referred to by `from` and `to` are fetched, // which will include position, direction, and spin. With that info, // the following transformations are performed: // * Translates this part so it's anchor position matches the parent's anchor position. // * Rotates this part so it's anchor direction vector exactly opposes the parent's anchor direction vector. // * Rotates this part so it's anchor spin matches the parent's anchor spin. // // Usage: // orient_and_anchor(size, [anchor], [spin], [orient], [center], [noncentered], [anchors], [chain]) ... // // Arguments: // 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. // anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#anchor). Default: `CENTER` // spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#spin). Default: `0` // orient = Vector to rotate top towards, after spin. See [orient](attachments.scad#orient). Default: `UP` // center = If given, overrides `anchor`. If true, centers vertically. If false, `anchor` will be set to the value in `noncentered`. // noncentered = The value to set `anchor` to if `center` == `false`. Default: `BOTTOM`. // offset = The offset of the center of the object from the CENTER anchor. // geometry = One of "cube", "cylinder", or "sphere" to denote the overall geometry of the shape. Cones are "cylinder", and prismoids are "cube" for this purpose. Default: "cube" // anchors = A list of extra, non-standard optional anchors. // chain = If true, allow attachable children. // two_d = If true, object will be treated as 2D. // // 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_geom` is set to the parent object's `geometry` value. // `$parent_orient` is set to the parent object's `orient` value. // `$parent_anchor` is set to the parent object's `anchor` value. // `$parent_anchors` is set to the parent object's list of non-standard extra anchors. // `$parent_2d` is set to the parent object's `two_d` value. // // Example(Med): // #cylinder(d1=50, d2=30, h=60); // orient_and_anchor(size=[50,50,60], size2=[30,30], anchor=RIGHT, orient=FWD) // cylinder(d1=50, d2=30, h=60); module orient_and_anchor( size=undef, orient=UP, anchor=CENTER, center=undef, noncentered=BOTTOM, spin=0, size2=undef, shift=[0,0], offset=[0,0,0], geometry="cube", anchors=[], chain=false, two_d=false ) { size2 = point2d(default(size2, size)); shift = point2d(shift); anchr = is_undef(center)? anchor : (center? CENTER : noncentered); pos = find_anchor(anchr, size.z, size, size2=size2, shift=shift, offset=offset, anchors=anchors, geometry=geometry, two_d=two_d)[1]; $parent_size = size; $parent_size2 = size2; $parent_shift = shift; $parent_geom = geometry; $parent_orient = orient; $parent_offset = offset; $parent_2d = two_d; $parent_anchor = anchr; $parent_anchors = anchors; 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)]); if ($attach_to != undef) { anch = find_anchor($attach_to, size.z, size, size2=size2, shift=shift, offset=offset, anchors=anchors, geometry=geometry, two_d=two_d); ang = vector_angle(anch[2], two_d? BACK : DOWN); axis = two_d? UP : vector_axis(anch[2], DOWN); ang2 = (anch[2]==UP || anch[2]==DOWN)? 0 : 180-anch[3]; axis2 = rotate_points3d([axis],[0,0,ang2])[0]; $attach_to = undef; rot(ang, v=axis2) rotate(ang2+spin) translate(-anch[1]) { if ($children>1 && chain) { if(shown && !hidden) { color($color) for (i=[0:1:$children-2]) children(i); } children($children-1); } else { if(shown && !hidden) color($color) children(); } } } else { rot(from=UP,to=orient) rotate(spin) translate(-pos) { if ($children>1 && chain) { if(shown && !hidden) { color($color) for (i=[0:1:$children-2]) children(i); } children($children-1); } else { if(shown && !hidden) color($color) children(); } } } } // Module: position() // Usage: // position(from, [overlap]) ... // Description: // Attaches children to a parent object at an anchor point. // Arguments: // from = The vector, or name of the parent anchor point to attach to. // Example: // spheroid(d=20) { // position(TOP) cyl(l=10, d1=10, d2=5, anchor=BOTTOM); // position(RIGHT) cyl(l=10, d1=10, d2=5, anchor=BOTTOM); // position(FRONT) cyl(l=10, d1=10, d2=5, anchor=BOTTOM); // } module position(from) { assert($parent_size != undef, "No object to attach to!"); anchors = (is_vector(from)||is_string(from))? [from] : from; for (anchr = anchors) { anch = find_anchor(anchr, $parent_size.z, point2d($parent_size), size2=$parent_size2, shift=$parent_shift, offset=$parent_offset, anchors=$parent_anchors, geometry=$parent_geom, two_d=$parent_2d); $attach_to = undef; $attach_anchor = anch; $attach_norot = true; translate(anch[1]) children(); } } // Module: attach() // Usage: // attach(from, [overlap]) ... // attach(from, to, [overlap]) ... // Description: // Attaches children to a parent object at an anchor point and orientation. // Attached objects will be overlapped into the parent object by a little bit, // as specified by the default `$overlap` value (0.01 by default), or by the // overriding `overlap=` argument. This is to prevent OpenSCAD from making // non-manifold objects. You can also define `$overlap=` as an argument in a // parent module to set the default for all attachments to it. // Arguments: // from = The vector, or name of the parent anchor point to attach to. // to = Optional name of the child anchor point. If given, orients the child such that the named anchors align together rotationally. // overlap = Amount to sink child into the parent. Equivalent to `down(X)` after the attach. This defaults to the value in `$overlap`, which is `0.01` by default. // norot = If true, don't rotate children when attaching to the anchor point. Only translate to the anchor point. // Example: // spheroid(d=20) { // attach(TOP) down(1.5) cyl(l=11.5, d1=10, d2=5, anchor=BOTTOM); // attach(RIGHT, BOTTOM) down(1.5) cyl(l=11.5, d1=10, d2=5); // attach(FRONT, BOTTOM, overlap=1.5) cyl(l=11.5, d1=10, d2=5); // } module attach(from, to=undef, overlap=undef, norot=false) { assert($parent_size != undef, "No object to attach to!"); overlap = (overlap!=undef)? overlap : $overlap; anchors = (is_vector(from)||is_string(from))? [from] : from; for (anchr = anchors) { anch = find_anchor(anchr, $parent_size.z, point2d($parent_size), size2=$parent_size2, shift=$parent_shift, offset=$parent_offset, anchors=$parent_anchors, geometry=$parent_geom, two_d=$parent_2d); $attach_to = to; $attach_anchor = anch; $attach_norot = norot; if (norot || (norm(anch[2]-UP)<1e-9 && anch[3]==0)) { translate(anch[1]) translate([0,0,-overlap]) children(); } else { fromvec = $parent_2d? BACK : UP; translate(anch[1]) rot(anch[3],from=fromvec,to=anch[2]) translate([0,0,-overlap]) children(); } } } // Module: edge_profile() // Usage: // edge_profile([edges], [except], [convexity]) ... // Description: // Takes a 2D mask shape and attaches it to the selected edges, with the appropriate orientation // and extruded length to be `diff()`ed away, to give the edge a matching profile. // Arguments: // edges = Edges to mask. See the docs for [`edges()`](edges.scad#edges) to see acceptable values. Default: All edges. // except = Edges to explicitly NOT mask. See the docs for [`edges()`](edges.scad#edges) to see acceptable values. Default: No edges. // convexity = Max number of times a line could intersect the perimeter of the mask shape. Default: 10 // Side Effects: // Sets `$tags = "mask"` for all children. // Example: // diff("mask") // cube([50,60,70],center=true) // edge_profile([TOP,"Z"],except=[BACK,TOP+LEFT]) // mask2d_roundover(r=10, inset=2); module edge_profile(edges=EDGES_ALL, except=[], convexity=10) { assert($parent_size != undef, "No object to attach to!"); edges = edges(edges, except=except); vecs = [ for (i = [0:3], axis=[0:2]) if (edges[axis][i]>0) EDGE_OFFSETS[axis][i] ]; for (vec = vecs) { vcount = (vec.x?1:0) + (vec.y?1:0) + (vec.z?1:0); assert(vcount == 2, "Not an edge vector!"); anch = find_anchor(vec, $parent_size.z, point2d($parent_size), size2=$parent_size2, shift=$parent_shift, offset=$parent_offset, anchors=$parent_anchors, geometry=$parent_geom, two_d=$parent_2d); $attach_to = undef; $attach_anchor = anch; $attach_norot = true; $tags = "mask"; length = sum(vmul($parent_size, [for (x=vec) x?0:1]))+0.1; rotang = vec.z<0? [90,0,180+vang(point2d(vec))] : vec.z==0 && sign(vec.x)==sign(vec.y)? 135+vang(point2d(vec)) : vec.z==0 && sign(vec.x)!=sign(vec.y)? [0,180,45+vang(point2d(vec))] : [-90,0,180+vang(point2d(vec))]; translate(anch[1]) { rot(rotang) { linear_extrude(height=length, center=true, convexity=convexity) { children(); } } } } } // Module: edge_mask() // Usage: // edge_mask([edges], [except]) ... // Description: // Takes a 3D mask shape, and attaches it to the given edges, with the // appropriate orientation to be `diff()`ed away. // Arguments: // edges = Edges to mask. See the docs for [`edges()`](edges.scad#edges) to see acceptable values. Default: All edges. // except = Edges to explicitly NOT mask. See the docs for [`edges()`](edges.scad#edges) to see acceptable values. Default: No edges. // Side Effects: // Sets `$tags = "mask"` for all children. // Example: // diff("mask") // cube([50,60,70],center=true) // edge_mask([TOP,"Z"],except=[BACK,TOP+LEFT]) // rounding_mask_z(l=71,r=10); module edge_mask(edges=EDGES_ALL, except=[]) { assert($parent_size != undef, "No object to attach to!"); edges = edges(edges, except=except); vecs = [ for (i = [0:3], axis=[0:2]) if (edges[axis][i]>0) EDGE_OFFSETS[axis][i] ]; for (vec = vecs) { vcount = (vec.x?1:0) + (vec.y?1:0) + (vec.z?1:0); assert(vcount == 2, "Not an edge vector!"); anch = find_anchor(vec, $parent_size.z, point2d($parent_size), size2=$parent_size2, shift=$parent_shift, offset=$parent_offset, anchors=$parent_anchors, geometry=$parent_geom, two_d=$parent_2d); $attach_to = undef; $attach_anchor = anch; $attach_norot = true; $tags = "mask"; rotang = vec.z<0? [90,0,180+vang(point2d(vec))] : vec.z==0 && sign(vec.x)==sign(vec.y)? 135+vang(point2d(vec)) : vec.z==0 && sign(vec.x)!=sign(vec.y)? [0,180,45+vang(point2d(vec))] : [-90,0,180+vang(point2d(vec))]; translate(anch[1]) rot(rotang) children(); } } // Module: corner_mask() // Usage: // corner_mask([corners], [except]) ... // Description: // Takes a 3D mask shape, and attaches it to the given corners, with the appropriate // orientation to be `diff()`ed away. The 3D corner mask shape should be designed to // mask away the X+Y+Z+ octant. // Arguments: // corners = Edges to mask. See the docs for [`corners()`](edges.scad#corners) to see acceptable values. Default: All corners. // except = Edges to explicitly NOT mask. See the docs for [`corners()`](edges.scad#corners) to see acceptable values. Default: No corners. // Side Effects: // Sets `$tags = "mask"` for all children. // Example: // diff("mask") // cube(100, center=true) // corner_mask([TOP,FRONT],LEFT+FRONT+TOP) // difference() { // translate(-0.01*[1,1,1]) cube(20); // translate([20,20,20]) sphere(r=20); // } module corner_mask(corners=CORNERS_ALL, except=[]) { assert($parent_size != undef, "No object to attach to!"); corners = corners(corners, except=except); vecs = [for (i = [0:7]) if (corners[i]>0) CORNER_OFFSETS[i]]; for (vec = vecs) { vcount = (vec.x?1:0) + (vec.y?1:0) + (vec.z?1:0); assert(vcount == 3, "Not an edge vector!"); anch = find_anchor(vec, $parent_size.z, point2d($parent_size), size2=$parent_size2, shift=$parent_shift, offset=$parent_offset, anchors=$parent_anchors, geometry=$parent_geom, two_d=$parent_2d); $attach_to = undef; $attach_anchor = anch; $attach_norot = true; $tags = "mask"; rotang = vec.z<0? [ 0,0,180+vang(point2d(vec))-45] : [180,0,-90+vang(point2d(vec))-45]; translate(anch[1]) rot(rotang) 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. // Arguments: // c = Color name or RGBA vector. // 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. // Example: // hide("A") cube(50, anchor=CENTER, $tags="Main") { // attach(LEFT, BOTTOM) cylinder(d=30, l=30, $tags="A"); // attach(RIGHT, BOTTOM) cylinder(d=30, l=30, $tags="B"); // } module hide(tags="") { $tags_hidden = tags==""? [] : _str_char_split(tags, " "); children(); } // Module: show() // Usage: // show(tags) ... // Description: // Shows only children with the given tags. // Example: // show("A B") cube(50, anchor=CENTER, $tags="Main") { // attach(LEFT, BOTTOM) cylinder(d=30, l=30, $tags="A"); // attach(RIGHT, BOTTOM) cylinder(d=30, l=30, $tags="B"); // } 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. // Example: // diff("neg", "pos", keep="axle") // sphere(d=100, $tags="pos") { // attach(CENTER) xcyl(d=40, h=120, $tags="axle"); // attach(CENTER) cube([40,120,100], anchor=CENTER, $tags="neg"); // } // Example: Masking // diff("mask") // cube([80,90,100], center=true) { // let(p = $parent_size*1.01, $tags="mask") { // position([for (y=[-1,1],z=[-1,1]) [0,y,z]]) // rounding_mask_x(l=p.x, r=25); // position([for (x=[-1,1],z=[-1,1]) [x,0,z]]) // rounding_mask_y(l=p.y, r=20); // position([for (x=[-1,1],y=[-1,1]) [x,y,0]]) // rounding_mask_z(l=p.z, r=25); // } // } 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. // Example: // intersect("wheel", "mask", keep="axle") // sphere(d=100, $tags="wheel") { // attach(CENTER) cube([40,100,100], anchor=CENTER, $tags="mask"); // attach(CENTER) xcyl(d=40, h=100, $tags="axle"); // } 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(); } } // Module: hulling() // Usage: // hulling(a, [keep]) ... // Description: // Takes the union of all children with tags that are in `a`, and hull()s them. // 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` are // unioned with the result. // Arguments: // a = String containing space delimited set of tag names of children. // keep = String containing space delimited set of tag names of children to keep whole. // Example: // hulling("body") // sphere(d=100, $tags="body") { // attach(CENTER) cube([40,90,90], anchor=CENTER, $tags="body"); // attach(CENTER) xcyl(d=40, h=120, $tags="other"); // } module hulling(a) { hull() show(a) children(); children(); } // vim: noexpandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap