diff --git a/attachments.scad b/attachments.scad index 4be6ffa..7404b98 100644 --- a/attachments.scad +++ b/attachments.scad @@ -17,13 +17,13 @@ $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_spin = 0; $parent_orient = UP; +$parent_size = undef; +$parent_geom = undef; + $tags_shown = []; $tags_hidden = []; @@ -103,72 +103,226 @@ $tags_hidden = []; function anchorpt(name, pos=[0,0,0], orient=UP, spin=0) = [name, pos, orient, spin]; +// Function: attach_geom_2d() +// Usage: +// attach_geom_2d(geom); +// Description: +// Returns true if the given attachment geometry description is for a 2D shape. +function attach_geom_2d(geom) = + let( type = geom[0] ) + type == "rect" || type == "circle" || + type == "path_isect" || type == "path_extent"; + + +// Function: attach_geom_size() +// Usage: +// attach_geom_size(geom); +// Description: +// Returns the `[X,Y,Z]` bounding size for the given attachment geometry description. +function attach_geom_size(geom) = + let( type = geom[0] ) + type == "cuboid"? ( //size, size2, shift + let( + size=geom[1], size2=geom[2], shift=point2d(geom[3]), + maxx = max(size.x,size2.x), + maxy = max(size.y,size2.y), + z = size.z + ) [maxx, maxy, z] + ) : type == "cyl"? ( //r1, r2, l, shift + let( + r1=geom[1], r2=geom[2], l=geom[3], shift=point2d(geom[4]), + maxr = max(r1,r2) + ) [2*maxr,2*maxr,l] + ) : type == "spheroid"? ( //r + let( r=geom[1] ) [2,2,2]*r + ) : type == "vnf_extent" || type=="vnf_isect"? ( //vnf + let( + mm = pointlist_bounds(geom[1][0]), + delt = mm[1]-mm[0] + ) delt + ) : type == "rect"? ( //size, size2 + let( + size=geom[1], size2=geom[2], + maxx = max(size.x,size2) + ) [maxx, size.y] + ) : type == "circle"? ( //r + let( r=geom[1] ) [2,2]*r + ) : type == "path_isect" || type == "path_extent"? ( //path + let( + mm = pointlist_bounds(geom[1]), + delt = mm[1]-mm[0] + ) [delt.x, delt.y] + ) : + assert(false, "Unknown attachment geometry type."); + + // Function: find_anchor() // Usage: -// find_anchor(anchor, h, size, [size2], [shift], [edges], [corners]); +// find_anchor(anchor, geom); // Description: -// Returns anchor data for the given vector or anchor name. +// Calculates the anchor data for the given `anchor` vector or name, in the given attachment +// geometry. Returns `[ANCHOR, POS, VEC, ANG]` where `ANCHOR` is the requested anchorname +// or vector, `POS` is the anchor position, `VEC` is the direction vector of the anchor, and +// `ANG` is the angle to align with around the rotation axis of th anchor direction vector. // 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) = +// geom = The geometry description of the shape. +function find_anchor(anchor, geom) = + let( + anchor = point3d(anchor), + offset = anchor==CENTER? CENTER : select(geom,-2), + anchors = select(geom,-1), + type = geom[0] + ) 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)) + ) : + assert(is_vector(anchor),str("anchor=",anchor)) + anchor==CENTER? [anchor, CENTER, UP, 0] : + let( + oang = ( + approx(point2d(anchor), [0,0])? 0 : + atan2(anchor.y, anchor.x)+90 + ) + ) + type == "cuboid"? ( //size, size2, shift 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 + size=geom[1], size2=geom[2], shift=point2d(geom[3]), + h = size.z, + u = (anchor.z+1)/2, + axy = point2d(anchor), + bot = point3d(vmul(point2d(size)/2,axy),-h/2), + top = point3d(vmul(point2d(size2)/2,axy)+shift,h/2), + pos = lerp(bot,top,u)+offset, + sidevec = normalize(rot(from=UP, to=top-bot, p=point3d(axy))), + vvec = normalize([0,0,anchor.z]), + vec = anchor==CENTER? UP : + approx(axy,[0,0])? normalize(anchor) : + approx(anchor.z,0)? sidevec : + normalize((sidevec+vvec)/2) + ) [anchor, pos, vec, oang] + ) : type == "cyl"? ( //r1, r2, l, shift + let( + r1=geom[1], r2=geom[2], l=geom[3], shift=point2d(geom[4]), + u = (anchor.z+1)/2, + axy = normalize(point2d(anchor)), + bot = point3d(r1*axy,-l/2), + top = point3d(r2*axy+shift, l/2), + pos = lerp(bot,top,u)+offset, + sidevec = rot(from=UP, to=top-bot, p=point3d(axy)), + vvec = normalize([0,0,anchor.z]), + vec = anchor==CENTER? UP : + approx(axy,[0,0])? normalize(anchor) : + approx(anchor.z,0)? sidevec : + normalize((sidevec+vvec)/2) + ) [anchor, pos, vec, oang] + ) : type == "spheroid"? ( //r + let( + r=geom[1] + ) [anchor, r*normalize(anchor)+offset, normalize(anchor), oang] + ) : type == "vnf_isect"? ( //vnf + let( + vnf=geom[1], + eps = 1/2048, + rpts = rot(from=anchor, to=RIGHT, p=vnf[0]), + hits = [ + for (i = idx(vnf[1])) let( + face = vnf[1][i], + verts = select(rpts, face) + ) if ( + max(subindex(verts,0)) >= -eps && + max(subindex(verts,1)) >= -eps && + max(subindex(verts,2)) >= -eps && + min(subindex(verts,1)) <= eps && + min(subindex(verts,2)) <= eps + ) let( + pt = polygon_line_intersection( + select(vnf[0], face), + [CENTER,anchor], eps=eps + ) + ) if (!is_undef(pt)) [norm(pt),i,pt] + ] + ) + assert(len(hits)>0, "Anchor vector does not intersect with the shape. Attachment failed.") + let( + furthest = max_index(subindex(hits,0)), + pos = hits[furthest][2], + dist = hits[furthest][0], + nfaces = [for (hit = hits) if(approx(hit[0],dist,eps=eps)) hit[1]], + n = normalize( + sum([ + for (i = nfaces) let( + faceverts = select(vnf[0],vnf[1][i]), + faceplane = plane_from_pointslist(faceverts), + nrm = plane_normal(faceplane) + ) nrm + ]) / len(nfaces) ) ) - 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] - ); + [anchor, pos, n, oang] + ) : type == "vnf_extent"? ( //vnf + let( + vnf=geom[1], + rpts = rot(from=anchor, to=RIGHT, p=vnf[0]), + maxx = max(subindex(rpts,0)), + idxs = [for (i = idx(rpts)) if (approx(rpts[i].x, maxx)) i], + mm = pointlist_bounds(select(rpts,idxs)), + avgy = (mm[0].y+mm[1].y)/2, + avgz = (mm[0].z+mm[1].z)/2, + pos = rot(from=RIGHT, to=anchor, p=[maxx, avgy, avgz]) + ) [anchor, pos, anchor, oang] + ) : type == "rect"? ( //size, size2 + let( + size=geom[1], size2=geom[2], + u = (anchor.y+1)/2, + frpt = [size.x/2*anchor.x, -size.y/2], + bkpt = [size2/2*anchor.x, size.y/2], + pos = lerp(frpt, bkpt, u), + vec = normalize(rot(from=BACK, to=bkpt-frpt, p=anchor)) + ) [anchor, pos, vec, 0] + ) : type == "circle"? ( //r + let( + r=geom[1], + anchor = normalize(point2d(anchor)) + ) [anchor, r*anchor+offset, anchor, 0] + ) : type == "path_isect"? ( //path + let( + path=geom[1], + anchor = point2d(anchor), + isects = [ + for (t=triplet_wrap(path)) let( + seg1 = [t[0],t[1]], + seg2 = [t[1],t[2]], + isect = ray_segment_intersection([[0,0],anchor], seg1), + n = is_undef(isect)? [0,1] : + !approx(isect, t[1])? line_normal(seg1) : + normalize((line_normal(seg1)+line_normal(seg2))/2), + n2 = vector_angle(anchor,n)>90? -n : n + ) + if(!is_undef(isect) && !approx(isect,t[0])) [norm(isect), isect, n2] + ], + maxidx = max_index(subindex(isects,0)), + isect = isects[maxidx], + pos = isect[1], + vec = normalize(isect[2]) + ) [anchor, pos, vec, 0] + ) : type == "path_extent"? ( //path + let( + path=geom[1], + anchor = point2d(anchor), + rpath = rot(from=anchor, to=RIGHT, p=path), + maxx = max(subindex(rpath,0)), + idxs = [for (i = idx(rpath)) if (approx(rpath[i].x, maxx)) i], + miny = min([for (i=idxs) rpath[i].y]), + maxy = max([for (i=idxs) rpath[i].y]), + avgy = (miny+maxy)/2, + pos = rot(from=RIGHT, to=anchor, p=[maxx,avgy]) + ) [anchor, pos, anchor, 0] + ) : + assert(false, "Unknown attachment geometry type."); @@ -180,17 +334,33 @@ function _str_char_split(s,delim,n=0,acc=[],word="") = -// Section: Modules +// Section: Attachability Modules -// Module: orient_and_anchor() +// Module: attachable() +// +// Usage: +// attachable(anchor, spin, [orient], two_d, size, [size2], [shift], [offset], [anchors] ... +// attachable(anchor, spin, [orient], two_d, r|d, [offset], [anchors]) ... +// attachable(anchor, spin, [orient], two_d, path, [extent], [offset], [anchors] ... +// attachable(anchor, spin, [orient], size, [size2], [shift], [offset], [anchors] ... +// attachable(anchor, spin, [orient], r|d, l, [offset], [anchors]) ... +// attachable(anchor, spin, [orient], r1|d1, r2|d2, l, [offset], [anchors]) ... +// attachable(anchor, spin, [orient], r|d, [offset], [anchors]) ... +// attachable(anchor, spin, [orient], vnf, [extent], [offset], [anchors]) ... // // 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. +// Manages the anchoring, spin, orientation, and attachments for a 3D volume or 2D area. +// A managed 3D volume is assumed to be vertically (Z-axis) oriented, and centered. +// A managed 2D area is just assumed to be centered. The shape to be managed is given +// as the first child to this module, and the second child should be given as `children()`. +// For example, to manage a conical shape: +// ```openscad +// attachable(anchor, spin, orient, r1=r1, r2=r2, l=h) { +// cyl(r1=r1, r2=r2, l=h); +// children(); +// } +// ``` // // If this is *not* run as a child of `attach()` with the `to` argument // given, then the following transformations are performed in order: @@ -206,67 +376,182 @@ function _str_char_split(s,delim,n=0,acc=[],word="") = // * 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. +// size = If given as a 3D vector, contains the XY size of the bottom of the cuboidal/prismoidal volume, and the Z height. If given as a 2D vector, contains the front X width of the rectangular/trapezoidal shape, and the Y length. +// size2 = If given as a 2D vector, contains the XY size of the top of the prismoidal volume. If given as a number, contains the back width of the trapezoidal shape. +// shift = If given as a 2D vector, shifts the top of the prismoidal or conical shape by the given amount. If given as a number, shifts the back of the trapezoidal shape right by that amount. Default: No shift. +// r = Radius of the cylindrical/conical volume. +// d = Diameter of the cylindrical/conical volume. +// r1 = Radius of the bottom of the conical volume. +// r2 = Radius of the top of the conical volume. +// d1 = Diameter of the bottom of the conical volume. +// d2 = Diameter of the top of the conical volume. +// l = Length of the cylindrical/conical volume along axis. +// vnf = The [VNF](vnf.scad) of the volume. +// path = The path to generate a polygon from. +// extent = If true, calculate anchors by extents, rather than intersection. Default: false. +// offset = If given, offsets the center of the volume. +// anchors = If given as a list of anchor points, allows named anchor points. +// two_d = If true, the attachable shape is 2D. If false, 3D. Default: false (3D) // // 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. +// `$parent_spin` is set to the parent object's `spin` value. +// `$parent_orient` is set to the parent object's `orient` value. +// `$parent_geom` is set to the parent object's `geom` value. +// `$parent_size` is set to the parent object's cubical `[X,Y,Z]` volume size. // -// 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, +// Example(NORENDER): Cubical Shape +// attachable(anchor, spin, orient, size=size) { +// cube(size, center=true); +// children(); +// } +// +// Example(NORENDER): Prismoidal Shape +// attachable( +// anchor, spin, orient, +// size=point3d(botsize,h), +// size2=topsize, +// shift=shift +// ) { +// prismoid(botsize, topsize, h=h, shift=shift); +// children(); +// } +// +// Example(NORENDER): Cylindrical Shape +// attachable(anchor, spin, orient, r=r, l=h) { +// cyl(r=r, l=h); +// children(); +// } +// +// Example(NORENDER): Conical Shape +// attachable(anchor, spin, orient, r1=r1, r2=r2, l=h) { +// cyl(r1=r1, r2=r2, l=h); +// children(); +// } +// +// Example(NORENDER): Spherical Shape +// attachable(anchor, spin, orient, r=r) { +// staggered_sphere(r=r); +// children(); +// } +// +// Example(NORENDER): Arbitrary VNF Shape +// attachable(anchor, spin, orient, vnf=vnf) { +// vnf_polyhedron(vnf); +// children(); +// } +// +// Example(NORENDER): 2D Rectangular Shape +// attachable(anchor, spin, orient, size=size) { +// square(size, center=true); +// children(); +// } +// +// Example(NORENDER): 2D Trapezoidal Shape +// attachable( +// anchor, spin, orient, +// size=[x1,y], +// size2=x2, +// shift=shift +// ) { +// trapezoid(w1=x1, w2=x2, h=y, shift=shift); +// children(); +// } +// +// Example(NORENDER): 2D Circular Shape +// attachable(anchor, spin, orient, two_d=true, r=r) { +// circle(r=r); +// children(); +// } +// +// Example(NORENDER): Arbitrary 2D Polygon Shape +// attachable(anchor, spin, orient, path=path) { +// polygon(path); +// children(); +// } +module attachable( anchor=CENTER, - center=undef, - noncentered=BOTTOM, spin=0, - size2=undef, - shift=[0,0], + orient=UP, + size, size2, shift, + r,r1,r2, d,d1,d2, l, + vnf, path, + extent=true, 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]; + assert($children==2); + assert(is_string(anchor) || is_vector(anchor)); + assert(is_num(spin)); + assert(is_vector(orient)); + assert(is_vector(offset)); + assert(is_list(anchors)); - $parent_size = size; - $parent_size2 = size2; - $parent_shift = shift; - $parent_geom = geometry; + geom = !is_undef(size)? ( + two_d? ( + let( + size2 = default(size2, size.x), + shift = default(shift, 0) + ) + assert(is_vector(size) && len(size)==2) + assert(is_num(size2)) + assert(is_num(shift)) + ["rect", point2d(size), size2, shift, offset, anchors] + ) : ( + let( + size2 = default(size2, point2d(size)), + shift = default(shift, [0,0]) + ) + assert(is_vector(size) && len(size)==3) + assert(is_vector(size2) && len(size2)==2) + assert(is_vector(shift) && len(shift)==2) + ["cuboid", size, size2, shift, offset, anchors] + ) + ) : !is_undef(vnf)? ( + assert(is_vnf(vnf)) + assert(two_d == false) + extent? ["vnf_extent", vnf, offset, anchors] : + ["vnf_isect", vnf, offset, anchors] + ) : !is_undef(path)? ( + assert(is_path(path)) + assert(two_d == true) + extent? ["path_extent", path, offset, anchors] : + ["path_isect", path, offset, anchors] + ) : + let( + r1 = get_radius(r1=r1,d1=d1,r=r,d=d,dflt=undef) + ) + !is_undef(r1)? ( + assert(is_num(r1)) + !is_undef(l)? ( + let( + shift = default(shift, [0,0]), + r2 = get_radius(r1=r2,d1=d2,r=r,d=d,dflt=undef) + ) + assert(is_num(l)) + assert(is_num(r2)) + assert(is_vector(shift) && len(shift)==2) + ["cyl", r1, r2, l, shift, offset, anchors] + ) : ( + two_d? ["circle", r1, offset, anchors] : + ["spheroid", r1, offset, anchors] + ) + ) : + assert(false, "attachable(): Unrecognizable geometry description."); + + pos = find_anchor(anchor, geom)[1]; + size = attach_geom_size(geom); + + $parent_anchor = anchor; + $parent_spin = spin; $parent_orient = orient; - $parent_offset = offset; - $parent_2d = two_d; - $parent_anchor = anchr; - $parent_anchors = anchors; + $parent_geom = geom; + $parent_size = size; tags = _str_char_split($tags, " "); s_tags = $tags_shown; @@ -274,7 +559,7 @@ module orient_and_anchor( 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); + anch = find_anchor($attach_to, geom); 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]; @@ -283,36 +568,39 @@ module orient_and_anchor( 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); + translate(-anch[1]) { + if(shown && !hidden) { + if (is_undef($color)) { + children(0); + } else color($color) { + $color = undef; + children(0); } - children($children-1); - } else { - if(shown && !hidden) color($color) children(); } + children(1); } } 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); + translate(-pos) { + if(shown && !hidden) { + if (is_undef($color)) { + children(0); + } else color($color) { + $color = undef; + children(0); } - children($children-1); - } else { - if(shown && !hidden) color($color) children(); } + children(1); } } } +// Section: Attachment Positioning + + // Module: position() // Usage: // position(from, [overlap]) ... @@ -328,10 +616,10 @@ module orient_and_anchor( // } module position(from) { - assert($parent_size != undef, "No object to attach to!"); + assert($parent_geom != 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); + anch = find_anchor(anchr, $parent_geom); $attach_to = undef; $attach_anchor = anch; $attach_norot = true; @@ -364,18 +652,19 @@ module position(from) // } module attach(from, to=undef, overlap=undef, norot=false) { - assert($parent_size != undef, "No object to attach to!"); + assert($parent_geom != 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); + anch = find_anchor(anchr, $parent_geom); + two_d = attach_geom_2d($parent_geom); $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; + fromvec = two_d? BACK : UP; translate(anch[1]) rot(anch[3],from=fromvec,to=anch[2]) translate([0,0,-overlap]) children(); } } @@ -400,7 +689,7 @@ module attach(from, to=undef, overlap=undef, norot=false) // 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!"); + assert($parent_geom != undef, "No object to attach to!"); edges = edges(edges, except=except); vecs = [ for (i = [0:3], axis=[0:2]) @@ -410,7 +699,7 @@ module edge_profile(edges=EDGES_ALL, except=[], convexity=10) { 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); + anch = find_anchor(vec, $parent_geom); $attach_to = undef; $attach_anchor = anch; $attach_norot = true; @@ -449,7 +738,7 @@ module edge_profile(edges=EDGES_ALL, except=[], convexity=10) { // 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!"); + assert($parent_geom != undef, "No object to attach to!"); edges = edges(edges, except=except); vecs = [ for (i = [0:3], axis=[0:2]) @@ -459,7 +748,7 @@ module edge_mask(edges=EDGES_ALL, except=[]) { 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); + anch = find_anchor(vec, $parent_geom); $attach_to = undef; $attach_anchor = anch; $attach_norot = true; @@ -495,13 +784,13 @@ module edge_mask(edges=EDGES_ALL, except=[]) { // translate([20,20,20]) sphere(r=20); // } module corner_mask(corners=CORNERS_ALL, except=[]) { - assert($parent_size != undef, "No object to attach to!"); + assert($parent_geom != 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); + anch = find_anchor(vec, $parent_geom); $attach_to = undef; $attach_anchor = anch; $attach_norot = true; @@ -597,7 +886,7 @@ module show(tags="") // Example: // diff("neg", "pos", keep="axle") // sphere(d=100, $tags="pos") { -// attach(CENTER) xcyl(d=40, h=120, $tags="axle"); +// attach(CENTER) xcyl(d=40, l=120, $tags="axle"); // attach(CENTER) cube([40,120,100], anchor=CENTER, $tags="neg"); // } // Example: Masking @@ -655,7 +944,7 @@ module diff(neg, pos=undef, keep=undef) // 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"); +// attach(CENTER) xcyl(d=40, l=100, $tags="axle"); // } module intersect(a, b=undef, keep=undef) { @@ -695,7 +984,7 @@ module intersect(a, b=undef, keep=undef) // 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"); +// attach(CENTER) xcyl(d=40, l=120, $tags="other"); // } module hulling(a) { diff --git a/beziers.scad b/beziers.scad index 5b0519d..27963ee 100644 --- a/beziers.scad +++ b/beziers.scad @@ -468,10 +468,11 @@ module bezier_polygon(bezier, splinesteps=16, N=3) { // [ 25, -15], [-10, 0] // ]; // linear_sweep_bezier(bez, height=20, splinesteps=32); -module linear_sweep_bezier(bezier, height=100, splinesteps=16, N=3, center=undef, convexity=undef, twist=undef, slices=undef, scale=undef, anchor=BOTTOM, spin=0, orient=UP) { +module linear_sweep_bezier(bezier, height=100, splinesteps=16, N=3, center, convexity, twist, slices, scale, anchor, spin=0, orient=UP) { maxx = max([for (pt = bezier) abs(pt[0])]); maxy = max([for (pt = bezier) abs(pt[1])]); - orient_and_anchor([maxx*2,maxy*2,height], orient, anchor, spin=spin, center=center, chain=true) { + anchor = get_anchor(anchor,center,BOT,BOT); + attachable(anchor,spin,orient, size=[maxx*2,maxy*2,height]) { linear_extrude(height=height, center=true, convexity=convexity, twist=twist, slices=slices, scale=scale) { bezier_polygon(bezier, splinesteps=splinesteps, N=N); } @@ -506,11 +507,13 @@ module linear_sweep_bezier(bezier, height=100, splinesteps=16, N=3, center=undef // rotate_sweep_bezier(path, splinesteps=32, $fn=180); module rotate_sweep_bezier(bezier, splinesteps=16, N=3, convexity=undef, angle=360, anchor=CENTER, spin=0, orient=UP) { - maxx = max([for (pt = bezier) abs(pt[0])]); - maxy = max([for (pt = bezier) abs(pt[1])]); - orient_and_anchor([maxx*2,maxx*2,0], orient, anchor, spin=spin, geometry="cylinder", chain=true) { + oline = bezier_polyline(bezier, splinesteps=splinesteps, N=N); + maxx = max([for (pt = oline) abs(pt[0])]); + miny = min(subindex(oline,1)); + maxy = max(subindex(oline,1)); + attachable(anchor,spin,orient, r=maxx, l=max(abs(miny),abs(maxy))*2) { rotate_extrude(convexity=convexity, angle=angle) { - bezier_polygon(bezier, splinesteps, N); + polygon(oline); } } } diff --git a/bottlecaps.scad b/bottlecaps.scad index 4867913..e052ae7 100644 --- a/bottlecaps.scad +++ b/bottlecaps.scad @@ -64,7 +64,7 @@ module pco1810_neck(wall=2, anchor="support-ring", spin=0, orient=UP) anchorpt("support-ring", [0,0,neck_h-h/2]), anchorpt("tamper-ring", [0,0,h/2-tamper_base_h]) ]; - orient_and_anchor([support_d,support_d,h], orient, anchor, spin=spin, anchors=anchors, chain=true) { + attachable(anchor,spin,orient, d=support_d, l=h, anchors=anchors) { down(h/2) { rotate_extrude(convexity=10) { polygon(turtle( @@ -159,7 +159,7 @@ module pco1810_cap(wall=2, texture="none", anchor=BOTTOM, spin=0, orient=UP) anchors = [ anchorpt("inside-top", [0,0,-(h/2-wall)]) ]; - orient_and_anchor([w, w, h], orient, anchor, spin=spin, anchors=anchors, chain=true) { + attachable(anchor,spin,orient, d=w, l=h, anchors=anchors) { down(h/2) zrot(45) { difference() { union() { @@ -236,7 +236,7 @@ module pco1881_neck(wall=2, anchor="support-ring", spin=0, orient=UP) anchorpt("support-ring", [0,0,neck_h-h/2]), anchorpt("tamper-ring", [0,0,h/2-tamper_base_h]) ]; - orient_and_anchor([support_d,support_d,h], orient, anchor, spin=spin, anchors=anchors, chain=true) { + attachable(anchor,spin,orient, d=support_d, l=h, anchors=anchors) { down(h/2) { rotate_extrude(convexity=10) { polygon(turtle( @@ -323,7 +323,7 @@ module pco1881_cap(wall=2, texture="none", anchor=BOTTOM, spin=0, orient=UP) anchors = [ anchorpt("inside-top", [0,0,-(h/2-wall)]) ]; - orient_and_anchor([w, w, h], orient, anchor, spin=spin, anchors=anchors, chain=true) { + attachable(anchor,spin,orient, d=w, l=h, anchors=anchors) { down(h/2) zrot(45) { difference() { union() { diff --git a/cubetruss.scad b/cubetruss.scad index e50d5c1..31950f1 100644 --- a/cubetruss.scad +++ b/cubetruss.scad @@ -55,7 +55,7 @@ module cubetruss_segment(size=undef, strut=undef, bracing=undef, anchor=CENTER, h = size; crossthick = strut/sqrt(2); voffset = 0.333; - orient_and_anchor(size=[size, size, size], anchor=anchor, spin=spin, orient=orient, chain=true) { + attachable(anchor,spin,orient, size=[size,size,size]) { render(convexity=10) union() { difference() { @@ -123,7 +123,7 @@ module cubetruss_clip(extents=1, size=undef, strut=undef, clipthick=undef, ancho clipheight = min(size+strut, size/3+2*strut*2.6); clipsize = 0.5; s = [extents*(size-strut)+strut+2*clipthick, strut*2, clipheight-2*strut]; - orient_and_anchor(size=s, anchor=anchor, spin=spin, orient=orient, chain=true) { + attachable(anchor,spin,orient, size=s) { xflip_copy(offset=(extents*(size-strut)+strut)/2) { difference() { union() { @@ -184,7 +184,7 @@ module cubetruss_foot(w=1, size=undef, strut=undef, clipthick=undef, anchor=CENT wall_h = strut+clipthick*1.5; cyld = (size-2*strut)/cos(180/8); s = [w*(size-strut)+strut+2*clipthick, size-2*strut, strut+clipthick]; - orient_and_anchor(size=s, anchor=anchor, spin=spin, orient=orient, offset=[0,0,(strut-clipthick)/2], chain=true) { + attachable(anchor,spin,orient, size=s, offset=[0,0,(strut-clipthick)/2]) { down(clipthick) { // Base up(clipthick/2) { @@ -258,7 +258,7 @@ module cubetruss_joiner(w=1, vert=true, size=undef, strut=undef, clipthick=undef clipthick = is_undef(clipthick)? $cubetruss_clip_thickness : clipthick; clipsize = 0.5; s = [cubetruss_dist(w,1)+2*clipthick, cubetruss_dist(2,0)-0.1, strut+clipthick]; - orient_and_anchor(size=s, anchor=anchor, spin=spin, orient=orient, offset=[0,0,-(clipthick-strut)/2], chain=true) { + attachable(anchor,spin,orient, size=s, offset=[0,0,-(clipthick-strut)/2]) { down(clipthick) { // Base cube([w*(size-strut)+strut+2*clipthick, size, clipthick], anchor=BOT); @@ -319,7 +319,7 @@ module cubetruss_uclip(dual=true, size=undef, strut=undef, clipthick=undef, anch clipthick = is_undef(clipthick)? $cubetruss_clip_thickness : clipthick; clipsize = 0.5; s = [(dual?2:1)*strut+2*clipthick+$slop, strut+2*clipthick, size/3.5]; - orient_and_anchor(size=s, anchor=anchor, spin=spin, orient=orient, chain=true) { + attachable(anchor,spin,orient, size=s) { union() { difference() { cube(s, center=true); @@ -373,7 +373,7 @@ module cubetruss(extents=6, clips=[], bracing=undef, size=undef, strut=undef, cl l = extents[1]; h = extents[2]; s = [cubetruss_dist(w,1), cubetruss_dist(l,1), cubetruss_dist(h,1)]; - orient_and_anchor(size=s, anchor=anchor, spin=spin, orient=orient, chain=true) { + attachable(anchor,spin,orient, size=s) { union() { for (zrow = [0:h-1]) { up((zrow-(h-1)/2)*(size-strut)) { @@ -437,7 +437,7 @@ module cubetruss_corner(h=1, extents=[1,1,0,0,1], bracing=undef, size=undef, str exts = is_vector(extents)? list_fit(extents,5,fill=0) : [extents, extents, 0, 0, extents]; s = [cubetruss_dist(1+exts[0]+exts[2],1), cubetruss_dist(1+exts[1]+exts[3],1), cubetruss_dist(h+exts[4],1)]; offset = [cubetruss_dist(exts[0]-exts[2],0), cubetruss_dist(exts[1]-exts[3],0), cubetruss_dist(exts[4],0)]/2; - orient_and_anchor(size=s, anchor=anchor, spin=spin, orient=orient, offset=offset, chain=true) { + attachable(anchor,spin,orient, size=s, offset=offset) { union() { for (zcol = [0:h-1]) { up((size-strut+0.01)*zcol) { diff --git a/debug.scad b/debug.scad index cc02f32..356bb09 100644 --- a/debug.scad +++ b/debug.scad @@ -168,13 +168,17 @@ function standard_anchors() = [ // anchor_arrow(s=20); module anchor_arrow(s=10, color=[0.333,0.333,1], flag=true, $tags="anchor-arrow") { $fn=12; - recolor("gray") spheroid(d=s/6) - attach(CENTER,BOT) recolor(color) cyl(h=s*2/3, d=s/15, anchor=BOT) - attach(TOP) cyl(h=s/3, d1=s/5, d2=0, anchor=BOT) { - if(flag) { - attach(BOTTOM) recolor([1,0.5,0.5]) cuboid([s/50, s/6, s/4], anchor=FRONT+TOP); + recolor("gray") spheroid(d=s/6) { + attach(CENTER,BOT) recolor(color) cyl(h=s*2/3, d=s/15) { + attach(TOP,BOT) cyl(h=s/3, d1=s/5, d2=0) { + if(flag) { + position(BOT) + recolor([1,0.5,0.5]) + cuboid([s/100, s/6, s/4], anchor=FRONT+BOT); + } + children(); + } } - children(); } } @@ -210,7 +214,7 @@ module show_anchors(s=10, std=true, custom=true) { } } if (custom) { - for (anchor=$parent_anchors) { + for (anchor=select($parent_geom,-1)) { attach(anchor[0]) { anchor_arrow(s, color="cyan"); recolor("black") @@ -287,7 +291,7 @@ module ruler(length=100, width=undef, thickness=1, depth=3, labels=false, pipsca width = default(width, scales[0]); widths = width * widthfactor * [for(logsize = [0:-1:-depth+1]) pow(pipscale,-logsize)]; offsets = concat([0],cumsum(widths)); - orient_and_anchor([length,width,thickness], anchor=anchor, spin=spin, orient=orient, chain=true) { + attachable(anchor,spin,orient, size=[length,width,thickness], offset=offset) { translate([-length/2, -width/2, 0]) for(i=[0:1:len(scales)-1]){ count = ceil(length/scales[i]); diff --git a/examples/attachments.scad b/examples/attachments.scad index 21b6da6..a0e8ed1 100644 --- a/examples/attachments.scad +++ b/examples/attachments.scad @@ -4,8 +4,8 @@ $fn=32; cuboid([60,40,40], rounding=5, edges=edges("Z"), anchor=BOTTOM) { attach(TOP, BOTTOM) 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); + attach(TOP) cylinder(d=20, h=30, center=false) { + attach(TOP) cylinder(d1=50, d2=30, h=12, center=false); } attach([FRONT, BACK, LEFT, RIGHT]) cylinder(d1=14, d2=5, h=20) { attach(TOP, LEFT, overlap=5) prismoid([30,20], [20,20], h=10, shift=[-7,0]); diff --git a/examples/lsystems.scad b/examples/lsystems.scad new file mode 100644 index 0000000..d9d9734 --- /dev/null +++ b/examples/lsystems.scad @@ -0,0 +1,70 @@ +include + +function _lsystem_recurse(s, rules, lev) = + lev<=0? s : _lsystem_recurse([ + for ( + i = 0, + slen = len(s), + sout = ""; + + i <= slen; + + ch = s[i], + found = search([ch], rules)[0], + sout = str(sout, i==slen? "" : found==[]? ch : rules[found][1]), + i = i + 1 + ) if (i==slen) sout + ][0], rules, lev-1); + + +function _lsystem_to_turtle(s, step=1, angle=90, startang=0) = + concat( + startang? ["left", startang] : [], + ["angle", angle, "length", step], + [ + for ( + i = 0, + slen = len(s); + + i <= slen; + + ch = s[i], + cmd = (ch=="A" || ch=="B" || ch=="F")? ["move"] : + (ch=="+")? ["left"] : + (ch=="-")? ["right"] : + [], + i=i+1 + ) if(i>0 && cmd!=[]) each cmd + ] + ); + + +function lsystem_turtle(basis, rules, levels=5, step=1, angle=90, startang=0) = + turtle(_lsystem_to_turtle(_lsystem_recurse(basis, rules, levels), step=step, angle=angle, startang=startang)); + + +function dragon_curve (levels=9, step=1) = lsystem_turtle(levels=levels, step=step, angle=90, "FX", [["X", "X+YF+"], ["Y", "-FX-Y"]]); +function terdragon_curve (levels=7, step=1) = lsystem_turtle(levels=levels, step=step, angle=120, "F", [["F", "F+F-F"]]); +function twindragon_curve (levels=11, step=1) = lsystem_turtle(levels=levels, step=step, angle=90, "FX+FX+", [["X", "X+YF"], ["Y","FX-Y"]]); +function moore_curve (levels=4, step=1) = lsystem_turtle(levels=levels, step=step, angle=90, "LFL+F+LFL", [["L", "-RF+LFL+FR-"], ["R", "+LF-RFR-FL+"]]); +function hilbert_curve (levels=4, step=1) = lsystem_turtle(levels=levels, step=step, angle=90, "X", [["X","-YF+XFX+FY-"], ["Y","+XF-YFY-FX+"]]); +function gosper_curve (levels=4, step=1) = lsystem_turtle(levels=levels, step=step, angle=60, "A", [["A", "A-B--B+A++AA+B-"], ["B", "+A-BB--B-A++A+B"]]); +function quadratic_gosper (levels=2, step=1) = lsystem_turtle(levels=levels, step=step, angle=90, "-YF", [["X", "XFX-YF-YF+FX+FX-YF-YFFX+YF+FXFXYF-FX+YF+FXFX+YF-FXYF-YF-FX+FX+YFYF-"], ["Y", "+FXFX-YF-YF+FX+FXYF+FX-YFYF-FX-YF+FXYFYF-FX-YFFX+FX+YF-YF-FX+FX+YFY"]]); +function peano_curve (levels=4, step=1) = lsystem_turtle(levels=levels, step=step, angle=90, "X", [["X","XFYFX+F+YFXFY-F-XFYFX"], ["Y","YFXFY-F-XFYFX+F+YFXFY"]]); +function koch_snowflake (levels=4, step=1) = lsystem_turtle(levels=levels, step=step, angle=60, "F++F++F", [["F","F-F++F-F"]]); +function sierpinski_arrowhead(levels=6, step=1) = lsystem_turtle(levels=levels, step=step, angle=60, "A", [["A", "B-A-B"], ["B","A+B+A"]]); +function sierpinski_triangle (levels=4, step=1) = lsystem_turtle(levels=levels, step=step, angle=120, "A-B-B", [["A","A-B+A+B-A"], ["B","BB"]]); +function square_sierpinski (levels=5, step=1) = lsystem_turtle(levels=levels, step=step, angle=90, "F+XF+F+XF", [["X","XF-F+F-XF+F+XF-F+F-X"]]); +function cesaro_curve (levels=4, step=1) = lsystem_turtle(levels=levels, step=step, angle=85, "F", [["F","F+F--F+F"]]); +function paul_bourke1 (levels=3, step=1) = lsystem_turtle(levels=levels, step=step, angle=90, "F+F+F+F+", [["F","F+F-F-FF+F+F-F"]]); +function paul_bourke_triangle(levels=6, step=1) = lsystem_turtle(levels=levels, step=step, angle=120, "F+F+F", [["F","F-F+F"]]); +function paul_bourke_crystal (levels=4, step=1) = lsystem_turtle(levels=levels, step=step, angle=90, "F+F+F+F", [["F","FF+F++F+F"]]); +function space_filling_tree (levels=4, step=1) = lsystem_turtle(levels=levels, step=step, angle=90, "X", [["X","FX++F-FX++F-FX++F-FX++F-"],["F", "FF"]], startang=45); +function krishna_anklets (levels=6, step=1) = lsystem_turtle(levels=levels, step=step, angle=45, "-X--X", [["X","XFX--XFX"]]); + + +points = hilbert_curve(levels=5, step=100/pow(2,5)); +stroke(points, width=1); + + +// vim: noexpandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap diff --git a/hingesnaps.scad b/hingesnaps.scad index f75e069..20d7c47 100644 --- a/hingesnaps.scad +++ b/hingesnaps.scad @@ -30,7 +30,9 @@ module folding_hinge_mask(l, thick, layerheight=0.2, foldangle=90, hingegap=undef, anchor=CENTER, spin=0, orient=UP) { hingegap = default(hingegap, layerheight)+2*$slop; - orient_and_anchor(size=[l, hingegap, 2*thick], size2=[l,hingegap+2*thick*tan(foldangle/2)], anchor=anchor, spin=spin, orient=orient, chain=true) { + size = [l, hingegap, 2*thick]; + size2 = [l, hingegap+2*thick*tan(foldangle/2)]; + attachable(anchor,spin,orient, size=size, size2=size2) { up(layerheight*2) prismoid([l,hingegap], [l, hingegap+2*thick/tan(foldangle/2)], h=thick, anchor=BOT); children(); } @@ -58,7 +60,8 @@ module snap_lock(thick, snaplen=5, snapdiam=5, layerheight=0.2, foldangle=90, hi { hingegap = default(hingegap, layerheight)+2*$slop; snap_x = (snapdiam/2) / tan(foldangle/2) + (thick-2*layerheight)/tan(foldangle/2) + hingegap/2; - orient_and_anchor(size=[snaplen, snapdiam, 2*thick], anchor=anchor, spin=spin, orient=orient, chain=true) { + size = [snaplen, snapdiam, 2*thick]; + attachable(anchor,spin,orient, size=size) { back(snap_x) { cube([snaplen, snapdiam, snapdiam/2+thick], anchor=BOT) { attach(TOP) xcyl(l=snaplen, d=snapdiam, $fn=16); @@ -91,7 +94,8 @@ module snap_socket(thick, snaplen=5, snapdiam=5, layerheight=0.2, foldangle=90, { hingegap = default(hingegap, layerheight)+2*$slop; snap_x = (snapdiam/2) / tan(foldangle/2) + (thick-2*layerheight)/tan(foldangle/2) + hingegap/2; - orient_and_anchor(size=[snaplen, snapdiam, 2*thick], anchor=anchor, spin=spin, orient=orient, chain=true) { + size = [snaplen, snapdiam, 2*thick]; + attachable(anchor,spin,orient, size=size) { fwd(snap_x) { zrot_copies([0,180], r=snaplen+$slop) { diff("divot") diff --git a/involute_gears.scad b/involute_gears.scad index 6c5b666..22fd4c0 100644 --- a/involute_gears.scad +++ b/involute_gears.scad @@ -371,7 +371,7 @@ module gear( c = outer_radius(pitch, teeth, clearance, interior); r = root_radius(pitch, teeth, clearance, interior); twist = atan2(thickness*tan(helical),p); - orient_and_anchor([p, p, thickness], orient, anchor, spin=spin, geometry="cylinder", chain=true) { + attachable(anchor,spin,orient, r=p, l=thickness) { difference() { linear_extrude(height=thickness, center=true, convexity=10, twist=twist) { gear2d( @@ -545,7 +545,7 @@ module bevel_gear( ] ] ); - orient_and_anchor([p1, p1, thickness], orient, anchor, spin=spin, size2=[p2,p2], geometry="cylinder", chain=true) { + attachable(anchor,spin,orient, r1=p1, r2=p2, l=thickness) { union() { difference() { down(thickness/2) { @@ -630,7 +630,7 @@ module rack( anchorpt("dedendum-top", [0,-d,thickness/2], UP), anchorpt("dedendum-bottom", [0,-d,-thickness/2], DOWN), ]; - orient_and_anchor([l, 2*abs(a-height), thickness], orient, anchor, spin=spin, anchors=anchors, chain=true) { + attachable(anchor,spin,orient, size=[l, 2*abs(a-height), thickness], anchors=anchors) { left((teeth-1)*pitch/2) { linear_extrude(height = thickness, center = true, convexity = 10) { for (i = [0:1:teeth-1] ) { diff --git a/joiners.scad b/joiners.scad index f97ed47..c98e476 100644 --- a/joiners.scad +++ b/joiners.scad @@ -39,7 +39,7 @@ module half_joiner_clear(h=20, w=10, a=30, clearance=0, overlap=0.01, anchor=CEN guide_size = w/3; guide_width = 2*(dmnd_height/2-guide_size)*tan(a); - orient_and_anchor([w, guide_width, h], orient, anchor, spin=spin) { + attachable(anchor,spin,orient, size=[w, guide_width, h]) { union() { yspread(overlap, n=overlap>0? 2 : 1) { difference() { @@ -55,6 +55,7 @@ module half_joiner_clear(h=20, w=10, a=30, clearance=0, overlap=0.01, anchor=CEN } if (overlap>0) cube([w+clearance, overlap+0.001, h], center=true); } + children(); } } @@ -85,14 +86,8 @@ module half_joiner(h=20, w=10, l=10, a=30, screwsize=undef, guides=true, anchor= guide_size = w/3; guide_width = 2*(dmnd_height/2-guide_size)*tan(a); - if ($children > 0) { - difference() { - children(); - half_joiner_clear(h=h, w=w, a=a, clearance=0.1, overlap=0.01, anchor=anchor, spin=spin, orient=orient); - } - } render(convexity=12) - orient_and_anchor([w, 2*l, h], orient, anchor, spin=spin) { + attachable(anchor,spin,orient, size=[w, 2*l, h]) { difference() { union() { // Make base. @@ -101,7 +96,7 @@ module half_joiner(h=20, w=10, l=10, a=30, screwsize=undef, guides=true, anchor= fwd(l/2) cube(size=[w, l, h], center=true); // Clear diamond for tab - grid3d(xa=[-(w*2/3), (w*2/3)]) { + xspread(2*w*2/3) { half_joiner_clear(h=h+0.01, w=w, clearance=$slop*2, a=a); } } @@ -138,6 +133,7 @@ module half_joiner(h=20, w=10, l=10, a=30, screwsize=undef, guides=true, anchor= yrot(90) cylinder(r=screwsize*1.1/2, h=w+1, center=true, $fn=12); } } + children(); } } //half_joiner(screwsize=3); @@ -168,15 +164,8 @@ module half_joiner2(h=20, w=10, l=10, a=30, screwsize=undef, guides=true, anchor guide_size = w/3; guide_width = 2*(dmnd_height/2-guide_size)*tan(a); - if ($children > 0) { - difference() { - children(); - half_joiner_clear(h=h, w=w, a=a, clearance=0.1, overlap=0.01, orient=orient, spin=spin, anchor=anchor); - } - } - render(convexity=12) - orient_and_anchor([w, 2*l, h], orient, anchor, spin=spin) { + attachable(anchor,spin,orient, size=[w, 2*l, h]) { difference() { union () { fwd(l/2) cube(size=[w, l, h], center=true); @@ -191,6 +180,7 @@ module half_joiner2(h=20, w=10, l=10, a=30, screwsize=undef, guides=true, anchor xcyl(r=screwsize*1.1/2, l=w+1, $fn=12); } } + children(); } } @@ -222,11 +212,12 @@ module joiner_clear(h=40, w=10, a=30, clearance=0, overlap=0.01, anchor=CENTER, guide_size = w/3; guide_width = 2*(dmnd_height/2-guide_size)*tan(a); - orient_and_anchor([w, guide_width, h], orient, anchor, spin=spin) { + attachable(anchor,spin,orient, size=[w, guide_width, h]) { union() { up(h/4) half_joiner_clear(h=h/2.0-0.01, w=w, a=a, overlap=overlap, clearance=clearance); down(h/4) half_joiner_clear(h=h/2.0-0.01, w=w, a=a, overlap=overlap, clearance=-0.01); } + children(); } } @@ -253,17 +244,12 @@ module joiner_clear(h=40, w=10, a=30, clearance=0, overlap=0.01, anchor=CENTER, // joiner(w=10, l=10, h=40, spin=-90) cuboid([10, 10*2, 40], anchor=RIGHT); module joiner(h=40, w=10, l=10, a=30, screwsize=undef, guides=true, anchor=CENTER, spin=0, orient=UP) { - if ($children > 0) { - difference() { - children(); - joiner_clear(h=h, w=w, a=a, clearance=0.1, orient=orient, spin=spin, anchor=anchor); - } - } - orient_and_anchor([w, 2*l, h], orient, anchor, spin=spin) { + attachable(anchor,spin,orient, size=[w, 2*l, h]) { union() { up(h/4) half_joiner(h=h/2, w=w, l=l, a=a, screwsize=screwsize, guides=guides); down(h/4) half_joiner2(h=h/2, w=w, l=l, a=a, screwsize=screwsize, guides=guides); } + children(); } } @@ -298,10 +284,11 @@ module joiner_pair_clear(spacing=100, h=40, w=10, a=30, n=2, clearance=0, overla guide_size = w/3; guide_width = 2*(dmnd_height/2-guide_size)*tan(a); - orient_and_anchor([spacing+w, guide_width, h], orient, anchor, spin=spin) { + attachable(anchor,spin,orient, size=[spacing+w, guide_width, h]) { xspread(spacing, n=n) { joiner_clear(h=h, w=w, a=a, clearance=clearance, overlap=overlap); } + children(); } } @@ -334,13 +321,7 @@ module joiner_pair_clear(spacing=100, h=40, w=10, a=30, n=2, clearance=0, overla // joiner_pair(spacing=50, l=10, n=3, alternate="alt", spin=-90); module joiner_pair(spacing=100, h=40, w=10, l=10, a=30, n=2, alternate=true, screwsize=undef, guides=true, anchor=CENTER, spin=0, orient=UP) { - if ($children > 0) { - difference() { - children(); - joiner_pair_clear(spacing=spacing, h=h, w=w, a=a, clearance=0.1, orient=orient, spin=spin, anchor=anchor); - } - } - orient_and_anchor([spacing+w, 2*l, h], orient, anchor, spin=spin) { + attachable(anchor,spin,orient, size=[spacing+w, 2*l, h]) { left((n-1)*spacing/2) { for (i=[0:1:n-1]) { right(i*spacing) { @@ -350,6 +331,7 @@ module joiner_pair(spacing=100, h=40, w=10, l=10, a=30, n=2, alternate=true, scr } } } + children(); } } @@ -382,12 +364,13 @@ module joiner_quad_clear(xspacing=undef, yspacing=undef, spacing1=undef, spacing { spacing1 = first_defined([spacing1, xspacing, 100]); spacing2 = first_defined([spacing2, yspacing, 50]); - orient_and_anchor([w+spacing1, spacing2, h], orient, anchor, spin=spin) { + attachable(anchor,spin,orient, size=[w+spacing1, spacing2, h]) { zrot_copies(n=2) { back(spacing2/2) { joiner_pair_clear(spacing=spacing1, n=n, h=h, w=w, a=a, clearance=clearance, overlap=overlap); } } + children(); } } @@ -422,18 +405,13 @@ module joiner_quad(spacing1=undef, spacing2=undef, xspacing=undef, yspacing=unde { spacing1 = first_defined([spacing1, xspacing, 100]); spacing2 = first_defined([spacing2, yspacing, 50]); - if ($children > 0) { - difference() { - children(); - joiner_quad_clear(spacing1=spacing1, spacing2=spacing2, h=h, w=w, a=a, clearance=0.1, orient=orient, spin=spin, anchor=anchor); - } - } - orient_and_anchor([w+spacing1, spacing2, h], orient, anchor, spin=spin) { + attachable(anchor,spin,orient, size=[w+spacing1, spacing2, h]) { zrot_copies(n=2) { back(spacing2/2) { joiner_pair(spacing=spacing1, n=n, h=h, w=w, l=l, a=a, screwsize=screwsize, guides=guides); } } + children(); } } @@ -571,7 +549,7 @@ module dovetail(gender, length, l, width, w, height, h, angle, slope, taper, bac adjustment = gender == "male" ? -0.01 : 0.01; // Adjustment for default overlap in attach() - orient_and_anchor([width+2*offset, length, height],anchor=anchor,orient=orient,spin=spin, chain=true) { + attachable(anchor,spin,orient, size=[width+2*offset, length, height]) { down(height/2+adjustment) { skin( [ diff --git a/knurling.scad b/knurling.scad index 094a1b2..bcf44a5 100644 --- a/knurling.scad +++ b/knurling.scad @@ -107,7 +107,7 @@ module knurled_cylinder( ] ] ); - orient_and_anchor([2*r1,2*r1,l], size2=[2*r2,2*r2], anchor=anchor, spin=spin, orient=orient, geometry="cylinder", chain=true) { + attachable(anchor,spin,orient, r1=r1, r2=r2, l=l) { intersection() { polyhedron(points=vertices, faces=faces, convexity=2*layers); cyl( @@ -157,7 +157,7 @@ module knurled_cylinder_mask( ) { r1 = get_radius(r1=r1, r=r, d1=d1, d=d, dflt=10); r2 = get_radius(r1=r2, r=r, d1=d2, d=d, dflt=10); - orient_and_anchor([2*r1,2*r1,l], size2=[2*r2,2*r2], anchor=anchor, spin=spin, orient=orient, geometry="cylinder", chain=true) { + attachable(anchor,spin,orient, r1=r1, r2=r2, l=l) { difference() { cylinder(r1=r1+overage, r2=r2+overage, h=l, center=true); knurled_cylinder(r1=r1, r2=r2, l=l+0.01); diff --git a/linear_bearings.scad b/linear_bearings.scad index f52a417..94baed9 100644 --- a/linear_bearings.scad +++ b/linear_bearings.scad @@ -92,7 +92,7 @@ module linear_bearing_housing(d=15, l=24, tab=7, gap=5, wall=3, tabwall=5, screw anchorpt("screw", [0,2-ogap/2,tabh-tab/2/2],FWD), anchorpt("nut", [0,ogap/2-2,tabh-tab/2/2],FWD) ]; - orient_and_anchor([l, od, h], orient, anchor, spin=spin, anchors=anchors, chain=true) { + attachable(anchor,spin,orient, size=[l, od, h], anchors=anchors) { down(tab/2/2) difference() { union() { diff --git a/masks.scad b/masks.scad index 76fc781..5930539 100644 --- a/masks.scad +++ b/masks.scad @@ -40,7 +40,7 @@ module angle_pie_mask( l = first_defined([l, h, 1]); r1 = get_radius(r1=r1, r=r, d1=d1, d=d, dflt=10); r2 = get_radius(r1=r2, r=r, d1=d2, d=d, dflt=10); - orient_and_anchor([2*r1, 2*r1, l], orient, anchor, spin=spin, chain=true) { + attachable(anchor,spin,orient, r1=r1, r2=r2, l=l) { pie_slice(ang=ang, l=l+0.1, r1=r1, r2=r2, anchor=CENTER); children(); } @@ -126,7 +126,7 @@ module cylinder_mask( cylinder_mask(l=l, r1=sc*r1, r2=sc*r2, chamfer1=cham1, chamfer2=cham2, chamfang1=ang1, chamfang2=ang2, rounding1=fil1, rounding2=fil2, orient=orient, from_end=from_end); } } else { - orient_and_anchor([2*r1, 2*r1, l], orient, anchor, spin=spin, chain=true) { + attachable(anchor,spin,orient, r=r1, l=l) { difference() { union() { chlen1 = cham1 / (from_end? 1 : tan(ang1)); @@ -142,7 +142,7 @@ module cylinder_mask( } cyl(r1=sc*r1, r2=sc*r2, l=l, chamfer1=cham1, chamfer2=cham2, chamfang1=ang1, chamfang2=ang2, from_end=from_end, rounding1=fil1, rounding2=fil2); } - children(); + nil(); } } } @@ -171,7 +171,7 @@ module cylinder_mask( // #chamfer_mask(l=50, chamfer=10, orient=RIGHT); // } module chamfer_mask(l=1, chamfer=1, anchor=CENTER, spin=0, orient=UP) { - orient_and_anchor([chamfer*2, chamfer*2, l], orient, anchor, spin=spin, chain=true) { + attachable(anchor,spin,orient, size=[chamfer*2, chamfer*2, l]) { cylinder(r=chamfer, h=l+0.1, center=true, $fn=4); children(); } @@ -304,7 +304,7 @@ module chamfer(chamfer=1, size=[1,1,1], edges=EDGES_ALL, except_edges=[]) module chamfer_cylinder_mask(r=undef, d=undef, chamfer=0.25, ang=45, from_end=false, anchor=CENTER, spin=0, orient=UP) { r = get_radius(r=r, d=d, dflt=1); - orient_and_anchor([2*r,2*r,chamfer*2], orient, anchor, spin=spin, chain=true) { + attachable(anchor,spin,orient, r=r, l=chamfer*2) { cylinder_mask(l=chamfer*3, r=r, chamfer2=chamfer, chamfang2=ang, from_end=from_end, ends_only=true, anchor=TOP); children(); } @@ -348,7 +348,7 @@ module chamfer_hole_mask(r=undef, d=undef, chamfer=0.25, ang=45, from_end=false, h = chamfer * (from_end? 1 : tan(90-ang)); r2 = r + chamfer * (from_end? tan(ang) : 1); $fn = segs(r); - orient_and_anchor([2*r, 2*r, h*2], orient, anchor, spin=spin, size2=[2*r2, 2*r2], chain=true) { + attachable(anchor,spin,orient, r1=r, r2=r2, l=h*2) { union() { cylinder(r=r2, h=overage, center=false); down(h) cylinder(r1=r, r2=r2, h=h, center=false); @@ -410,7 +410,7 @@ module rounding_mask(l=undef, r=undef, r1=undef, r2=undef, anchor=CENTER, spin=0 r1 = get_radius(r1=r1, r=r, dflt=1); r2 = get_radius(r1=r2, r=r, dflt=1); sides = quantup(segs(max(r1,r2)),4); - orient_and_anchor([2*r1, 2*r1, l], orient, anchor, spin=spin, size2=[2*r2,2*r2], chain=true) { + attachable(anchor,spin,orient, size=[2*r1,2*r1,l], size2=[2*r2,2*r2]) { if (r10? chamfer[i] : rounding[i]>0? rounding[i] : 0], @@ -116,7 +128,7 @@ module circle(r, d, realign=false, circum=false, anchor=CENTER, spin=0) { sides = segs(r); rr = circum? r/cos(180/sides) : r; pts = circle(r=rr, realign=realign, $fn=sides); - orient_and_anchor([2*rr,2*rr,0], UP, anchor, spin=spin, geometry="cylinder", two_d=true, chain=true) { + attachable(anchor,spin, two_d=true, r=rr) { polygon(pts); children(); } @@ -162,10 +174,11 @@ function circle(r, d, realign=false, circum=false, anchor=CENTER, spin=0) = // cube([20,40,50], anchor=BOTTOM+FRONT, spin=30, orient=FWD); // Example: Standard Connectors. // cube(40, center=true) show_anchors(); -module cube(size=1, center, anchor=ALLNEG, spin=0, orient=UP) +module cube(size=1, center, anchor, spin=0, orient=UP) { size = scalar_vec3(size); - orient_and_anchor(size, orient, anchor, center, spin=spin, noncentered=ALLNEG, chain=true) { + anchor = get_anchor(anchor, center, ALLNEG, ALLNEG); + attachable(anchor,spin,orient, size=size) { linear_extrude(height=size.z, convexity=2, center=true) { square([size.x, size.y], center=true); } @@ -214,8 +227,9 @@ module cube(size=1, center, anchor=ALLNEG, spin=0, orient=UP) // cylinder(h=30, d=25) show_anchors(); // cylinder(h=30, d1=25, d2=10) show_anchors(); // } -module cylinder(h, r1, r2, center, l, r, d, d1, d2, anchor=BOTTOM, spin=0, orient=UP) +module cylinder(h, r1, r2, center, l, r, d, d1, d2, anchor, spin=0, orient=UP) { + anchor = get_anchor(anchor, center, BOTTOM, BOTTOM); 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, 1]); @@ -223,7 +237,7 @@ module cylinder(h, r1, r2, center, l, r, d, d1, d2, anchor=BOTTOM, spin=0, orien sides = segs(max(r1,r2)); size = [r1*2, r1*2, l]; path = [[0,hh],[r2,hh],[r1,-hh],[0,-hh]]; - orient_and_anchor(size, orient, anchor, center, spin=spin, size2=[r2*2,r2*2], noncentered=BOTTOM, geometry="cylinder", chain=true) { + attachable(anchor,spin,orient, r1=r1, r2=r2, l=l) { rotate_extrude(convexity=2, $fn=sides) { polygon(path); } @@ -262,7 +276,7 @@ module sphere(r, d, anchor=CENTER, spin=0, orient=UP) r = get_radius(r=r, d=d, dflt=1); sides = segs(r); size = [r*2, r*2, r*2]; - orient_and_anchor(size, orient, anchor, spin=spin, geometry="sphere", chain=true) { + attachable(anchor,spin,orient, r=r) { rotate_extrude(convexity=2) { difference() { circle(r=r, $fn=sides); diff --git a/shapes.scad b/shapes.scad index 525aa56..c2cf150 100644 --- a/shapes.scad +++ b/shapes.scad @@ -97,7 +97,7 @@ module cuboid( if (any(edges[2])) assert(rounding <= size.x/2 && rounding<=size.y/2, "rounding radius must be smaller than half the cube width or length."); } majrots = [[0,90,0], [90,0,0], [0,0,0]]; - orient_and_anchor(size, orient, anchor, spin=spin, chain=true) { + attachable(anchor,spin,orient, size=size) { if (chamfer != undef) { if (edges == EDGES_ALL && trimcorners) { if (chamfer<0) { @@ -349,7 +349,7 @@ module prismoid( shiftby = point3d(point2d(shift)); s1 = [max(size1.x, eps), max(size1.y, eps)]; s2 = [max(size2.x, eps), max(size2.y, eps)]; - orient_and_anchor([s1.x,s1.y,h], orient, anchor, spin=spin, size2=s2, shift=shift, chain=true) { + attachable(anchor,spin,orient, size=[s1.x,s1.y,h], size2=s2, shift=shift) { polyhedron( points=[ [+s2.x/2, +s2.y/2, +h/2] + shiftby, @@ -419,16 +419,16 @@ module rounded_prismoid( rr1 = min(maxrad1, (r1!=undef)? r1 : r); rr2 = min(maxrad2, (r2!=undef)? r2 : r); shiftby = point3d(shift); - orient_and_anchor([size1.x, size1.y, h], orient, anchor, spin=spin, size2=size2, shift=shift, noncentered=UP, chain=true) { + attachable(anchor,spin,orient, size=[size1.x, size1.y, h], size2=size2, shift=shift) { down(h/2) { hull() { linear_extrude(height=eps, center=false, convexity=2) { - square([max(eps, size1[0]-2*rr1), max(eps, size1[1]-2*rr1)], rounding=rr1, center=true); + square(size1, rounding=rr1, center=true); } up(h-0.01) { translate(shiftby) { linear_extrude(height=eps, center=false, convexity=2) { - square([max(eps, size2[0]-2*rr2), max(eps, size2[1]-2*rr2)], rounding=rr2, center=true); + square(size2, rounding=rr2, center=true); } } } @@ -460,10 +460,11 @@ module rounded_prismoid( // right_triangle([60, 40, 10]); // Example: Standard Connectors // right_triangle([60, 40, 15]) show_anchors(); -module right_triangle(size=[1, 1, 1], anchor=ALLNEG, spin=0, orient=UP, center=undef) +module right_triangle(size=[1, 1, 1], center, anchor, spin=0, orient=UP) { size = scalar_vec3(size); - orient_and_anchor(size, orient, anchor, spin=spin, center=center, noncentered=ALLNEG, chain=true) { + anchor = get_anchor(anchor, center, ALLNEG, ALLNEG); + attachable(anchor,spin,orient, size=size) { linear_extrude(height=size.z, convexity=2, center=true) { polygon([[-size.x/2,-size.y/2], [-size.x/2,size.y/2], [size.x/2,-size.y/2]]); } @@ -581,17 +582,16 @@ module cyl( chamfang=undef, chamfang1=undef, chamfang2=undef, rounding=undef, rounding1=undef, rounding2=undef, circum=false, realign=false, from_end=false, - anchor=CENTER, spin=0, orient=UP, center=undef + center, anchor, spin=0, orient=UP ) { 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([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; phi = atan2(l, r2-r1); - orient_and_anchor(size1, orient, anchor, spin=spin, center=center, size2=size2, geometry="cylinder", chain=true) { + anchor = get_anchor(anchor,center,BOT,CENTER); + attachable(anchor,spin,orient, r1=r1, r2=r2, l=l) { zrot(realign? 180/sides : 0) { if (!any_defined([chamfer, chamfer1, chamfer2, rounding, rounding1, rounding2])) { cylinder(h=l, r1=r1*sc, r2=r2*sc, center=true, $fn=sides); @@ -850,8 +850,8 @@ module tube( od=undef, od1=undef, od2=undef, ir=undef, id=undef, ir1=undef, ir2=undef, id1=undef, id2=undef, - anchor=BOTTOM, spin=0, orient=UP, - center=undef, realign=false + anchor, spin=0, orient=UP, + center, realign=false ) { r1 = first_defined([or1, od1/2, r1, d1/2, or, od/2, r, d/2, ir1+wall, id1/2+wall, ir+wall, id/2+wall]); r2 = first_defined([or2, od2/2, r2, d2/2, or, od/2, r, d/2, ir2+wall, id2/2+wall, ir+wall, id/2+wall]); @@ -860,9 +860,8 @@ module tube( assert(ir1 <= r1, "Inner radius is larger than outer radius."); assert(ir2 <= r2, "Inner radius is larger than outer radius."); sides = segs(max(r1,r2)); - size = [r1*2,r1*2,h]; - size2 = [r2*2,r2*2,h]; - orient_and_anchor(size, orient, anchor, spin=spin, center=center, size2=size2, geometry="cylinder", chain=true) { + anchor = get_anchor(anchor, center, BOT, BOT); + attachable(anchor,spin,orient, r1=r1, r2=r2) { zrot(realign? 180/sides : 0) { difference() { cyl(h=h, r1=r1, r2=r2, $fn=sides) children(); @@ -908,15 +907,14 @@ module torus( r2=undef, d2=undef, or=undef, od=undef, ir=undef, id=undef, - anchor=CENTER, center=undef, - spin=0, orient=UP + center, anchor, spin=0, orient=UP ) { orr = get_radius(r=or, d=od, dflt=1.0); 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); - size = [(majrad+minrad)*2, (majrad+minrad)*2, minrad*2]; - orient_and_anchor(size, orient, anchor, spin=spin, center=center, geometry="cylinder", chain=true) { + anchor = get_anchor(anchor, center, BOT, CENTER); + attachable(anchor,spin,orient, r=(majrad+minrad), l=minrad*2) { rotate_extrude(convexity=4) { right(majrad) circle(minrad); } @@ -953,8 +951,7 @@ module spheroid(r=undef, d=undef, circum=false, anchor=CENTER, spin=0, orient=UP 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_anchor(size, orient, anchor, spin=spin, geometry="sphere", chain=true) { + attachable(anchor,spin,orient, r=rr) { sphere(r=rr); children(); } @@ -1020,8 +1017,7 @@ module staggered_sphere(r=undef, d=undef, circum=false, anchor=CENTER, spin=0, o ) each [[v1,v4,v3], [v1,v2,v4]] ] ); - size = [2*rr, 2*rr, 2*rr]; - orient_and_anchor(size, orient, anchor, spin=spin, geometry="sphere", chain=true) { + attachable(anchor,spin,orient, r=rr) { zrot((floor(sides/4)%2==1)? 180/sides : 0) polyhedron(points=pts, faces=faces); children(); } @@ -1061,10 +1057,11 @@ module teardrop(r=undef, d=undef, l=undef, h=undef, ang=45, cap_h=undef, anchor= r = get_radius(r=r, d=d, dflt=1); l = first_defined([l, h, 1]); size = [r*2,l,r*2]; - orient_and_anchor(size, orient, anchor, spin=spin, chain=true) { - rot(from=UP,to=FWD) - linear_extrude(height=l, center=true, slices=2) { - teardrop2d(r=r, ang=ang, cap_h=cap_h); + attachable(anchor,spin,orient, size=size) { + rot(from=UP,to=FWD) { + linear_extrude(height=l, center=true, slices=2) { + teardrop2d(r=r, ang=ang, cap_h=cap_h); + } } children(); } @@ -1101,11 +1098,10 @@ module onion(cap_h=undef, r=undef, d=undef, maxang=45, h=undef, anchor=CENTER, s r = get_radius(r=r, d=d, dflt=1); h = first_defined([cap_h, h]); maxd = 3*r/tan(maxang); - size = [r*2,r*2,r*2]; anchors = [ ["cap", [0,0,h], UP, 0] ]; - orient_and_anchor(size, orient, anchor, spin=spin, geometry="sphere", anchors=anchors, chain=true) { + attachable(anchor,spin,orient, r=r, anchors=anchors) { rotate_extrude(convexity=2) { difference() { teardrop2d(r=r, ang=maxang, cap_h=h); @@ -1137,7 +1133,7 @@ module nil() union(){} // Arguments: // 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` -module noop(spin=0, orient=UP) orient_and_anchor([0.01,0.01,0.01], orient, CENTER, spin=spin, chain=true) {nil(); children();} +module noop(spin=0, orient=UP) attachable(CENTER,spin,orient, d=0.01) {nil(); children();} // Module: pie_slice() @@ -1171,15 +1167,15 @@ module pie_slice( ang=30, l=undef, r=10, r1=undef, r2=undef, d=undef, d1=undef, d2=undef, - anchor=BOTTOM, spin=0, orient=UP, - center=undef, h=undef + h=undef, center, + anchor, spin=0, orient=UP ) { l = first_defined([l, h, 1]); r1 = get_radius(r1=r1, r=r, d1=d1, d=d, dflt=10); r2 = get_radius(r1=r2, r=r, d1=d2, d=d, dflt=10); maxd = max(r1,r2)+0.1; - size = [2*r1, 2*r1, l]; - orient_and_anchor(size, orient, anchor, spin=spin, center=center, geometry="cylinder", chain=true) { + anchor = get_anchor(anchor, center, BOT, BOT); + attachable(anchor,spin,orient, r1=r1, r2=r2, l=l) { 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); @@ -1232,7 +1228,7 @@ module interior_fillet(l=1.0, r=1.0, ang=90, overlap=0.01, anchor=FRONT+LEFT, sp dy = r/tan(ang/2); steps = ceil(segs(r)*ang/360); step = ang/steps; - orient_and_anchor([r,r,l], orient, anchor, spin=spin, chain=true) { + attachable(anchor,spin,orient, size=[r,r,l]) { linear_extrude(height=l, convexity=4, center=true) { path = concat( [[0,0]], @@ -1330,8 +1326,7 @@ module arced_slot( sr2 = get_radius(r1=sr2, r=sr, d1=sd2, d=sd, dflt=2); fn_minor = first_defined([$fn2, $fn]); da = ea - sa; - size = [r+sr1, r+sr1, h]; - orient_and_anchor(size, orient, anchor, spin=spin, geometry="cylinder", chain=true) { + attachable(anchor,spin,orient, r1=r+sr1, r2=r+sr2) { translate(cp) { zrot(sa) { difference() { diff --git a/shapes2d.scad b/shapes2d.scad index d4d08f6..61edf2b 100644 --- a/shapes2d.scad +++ b/shapes2d.scad @@ -628,8 +628,9 @@ function regular_ngon(n=6, r, d, or, od, ir, id, side, rounding=0, realign=false module regular_ngon(n=6, r, d, or, od, ir, id, side, rounding=0, realign=false, anchor=CENTER, spin=0) { sc = 1/cos(180/n); r = get_radius(r1=ir*sc, r2=or, r=r, d1=id*sc, d2=od, d=d, dflt=side/2/sin(180/n)); - orient_and_anchor([2*r,2*r,0], UP, anchor, spin=spin, geometry="cylinder", two_d=true, chain=true) { - polygon(regular_ngon(n=n, r=r, rounding=rounding, realign=realign)); + path = regular_ngon(n=n, r=r, rounding=rounding, realign=realign); + attachable(anchor,spin, two_d=true, path=path) { + polygon(path); children(); } } @@ -788,8 +789,9 @@ function trapezoid(h, w1, w2, anchor=CENTER, spin=0) = module trapezoid(h, w1, w2, anchor=CENTER, spin=0) { - orient_and_anchor([w1,h,0], size2=[w2,h], UP, anchor, spin=spin, two_d=true, chain=true) { - polygon(trapezoid(h=h, w1=w1, w2=w2)); + path = trapezoid(h=h, w1=w1, w2=w2); + attachable(anchor,spin, two_d=true, size=[w1,h], size2=w2) { + polygon(path); children(); } } @@ -819,8 +821,11 @@ module trapezoid(h, w1, w2, anchor=CENTER, spin=0) { // teardrop2d(r=30, ang=30, cap_h=20); module teardrop2d(r, d, ang=45, cap_h, anchor=CENTER, spin=0) { - path = teardrop2d(r=r, d=d, ang=ang, cap_h=cap_h, anchor=anchor, spin=spin); - polygon(path); + path = teardrop2d(r=r, d=d, ang=ang, cap_h=cap_h); + attachable(anchor,spin, two_d=true, path=path) { + polygon(path); + children(); + } } @@ -892,8 +897,13 @@ function glued_circles(r, d, spread=10, tangent=30, anchor=CENTER, spin=0) = ) rot(spin, p=move(-vmul(anchor,s), p=path)); -module glued_circles(r, d, spread=10, tangent=30, anchor=CENTER, spin=0) - polygon(glued_circles(r=r, d=d, spread=spread, tangent=tangent, anchor=anchor, spin=spin)); +module glued_circles(r, d, spread=10, tangent=30, anchor=CENTER, spin=0) { + path = glued_circles(r=r, d=d, spread=spread, tangent=tangent); + attachable(anchor,spin, two_d=true, path=path, extent=true) { + polygon(path); + children(); + } +} // Function&Module: star() @@ -940,8 +950,13 @@ function star(n, r, d, or, od, ir, id, step, realign=false, anchor=CENTER, spin= ) rot(spin, p=move(-r*normalize(anchor), p=path)); -module star(n, r, d, or, od, ir, id, step, realign=false, anchor=CENTER, spin=0) - polygon(star(n=n, r=r, d=d, od=od, or=or, ir=ir, id=id, step=step, realign=realign, anchor=anchor, spin=spin)); +module star(n, r, d, or, od, ir, id, step, realign=false, anchor=CENTER, spin=0) { + path = star(n=n, r=r, d=d, od=od, or=or, ir=ir, id=id, step=step, realign=realign); + attachable(anchor,spin, two_d=true, path=path, extent=true) { + polygon(path); + children(); + } +} function _superformula(theta,m1,m2,n1,n2=1,n3=1,a=1,b=1) = @@ -1006,8 +1021,13 @@ function supershape(step=0.5,m1=4,m2=undef,n1=1,n2=undef,n3=undef,a=1,b=undef,r= path = [for (i = [0:steps-1]) let(a=angs[i]) scale*rads[i]*[cos(a), sin(a)]] ) rot(spin, p=move(-scale*max(rads)*normalize(anchor), p=path)); -module supershape(step=0.5,m1=4,m2=undef,n1,n2=undef,n3=undef,a=1,b=undef, r=undef, d=undef, anchor=CENTER, spin=0) - polygon(supershape(step=step,m1=m1,m2=m2,n1=n1,n2=n2,n3=n3,a=a,b=b, r=r,d=d, anchor=anchor, spin=spin)); +module supershape(step=0.5,m1=4,m2=undef,n1,n2=undef,n3=undef,a=1,b=undef, r=undef, d=undef, anchor=CENTER, spin=0) { + path = supershape(step=step,m1=m1,m2=m2,n1=n1,n2=n2,n3=n3,a=a,b=b,r=r,d=d); + attachable(anchor,spin, two_d=true, path=path, extent=true) { + polygon(path); + children(); + } +} // Section: 2D Masking Shapes diff --git a/sliders.scad b/sliders.scad index e3e37da..2d5b0e4 100644 --- a/sliders.scad +++ b/sliders.scad @@ -35,7 +35,7 @@ module slider(l=30, w=10, h=10, base=10, wall=5, ang=30, anchor=BOTTOM, spin=0, full_width = w + 2*wall; full_height = h + base; - orient_and_anchor([full_width, l, h+2*base], orient, anchor, spin=spin, chain=true) { + attachable(anchor,spin,orient, size=[full_width, l, h+2*base]) { zrot(90) down(base+h/2) { // Base @@ -102,7 +102,7 @@ module rail(l=30, w=10, h=10, chamfer=1.0, ang=30, anchor=BOTTOM, spin=0, orient y1 = l/2; y2 = y1 - attack_len * cos(attack_ang); - orient_and_anchor([w, l, h], orient, anchor, spin=spin, chain=true) { + attachable(anchor,spin,orient, size=[w, l, h]) { polyhedron( convexity=4, points=[ diff --git a/threading.scad b/threading.scad index 70b77e7..1d5a1bf 100644 --- a/threading.scad +++ b/threading.scad @@ -63,7 +63,7 @@ module thread_helix(base_d, pitch, thread_depth=undef, thread_angle=15, twist=72 pline = scale_points(profile, [1,1,1]*pitch); dir = left_handed? -1 : 1; idir = internal? -1 : 1; - orient_and_anchor([2*r, 2*r, h], orient, anchor, spin=spin, chain=true) { + attachable(anchor,spin,orient, r=r, l=h) { difference() { spiral_sweep(pline, h=h, r=base_d/2, twist=twist*dir, $fn=segs(base_d/2), anchor=CENTER); down(h/2) right(r) right(internal? thread_depth : 0) zrot(higbee*dir*idir) fwd(dir*pitch/2) cube([3*thread_depth/cos(higbee), pitch, pitch], center=true); @@ -137,10 +137,7 @@ module trapezoidal_threaded_rod( starts=1, profile=undef, internal=false, - anchor=CENTER, - spin=0, - orient=UP, - center=undef + center, anchor, spin=0, orient=UP ) { function _thread_pt(thread, threads, start, starts, astep, asteps, part, parts) = astep + asteps * (thread + threads * (part + parts * start)); @@ -277,7 +274,8 @@ module trapezoidal_threaded_rod( ) otri ] ); - orient_and_anchor([d,d,l], orient, anchor, spin=spin, center=center, chain=true) { + anchor = get_anchor(anchor, center, BOT, CENTER); + attachable(anchor,spin,orient, d=d, l=l) { difference() { polyhedron(points=poly_points, faces=poly_faces, convexity=threads*starts*2); zspread(l+4*pitch*starts) cube([d+1, d+1, 4*pitch*starts], center=true); @@ -332,7 +330,7 @@ module trapezoidal_threaded_nut( orient=UP ) { depth = min((thread_depth==undef? pitch/2 : thread_depth), pitch/2/tan(thread_angle)); - orient_and_anchor([od/cos(30),od,h], orient, anchor, spin=spin, chain=true) { + attachable(anchor,spin,orient, size=[od/cos(30),od,h]) { difference() { cylinder(d=od/cos(30), h=h, center=true, $fn=6); trapezoidal_threaded_rod( diff --git a/torx_drive.scad b/torx_drive.scad index 49f5b08..573d1e8 100644 --- a/torx_drive.scad +++ b/torx_drive.scad @@ -184,9 +184,10 @@ module torx_drive2d(size) { // orient = Vector to rotate top towards, after spin. See [orient](attachments.scad#orient). Default: `UP` // Examples: // torx_drive(size=30, l=10, $fa=1, $fs=1); -module torx_drive(size, l=5, center=undef, anchor=BOTTOM, spin=0, orient=UP) { +module torx_drive(size, l=5, center, anchor, spin=0, orient=UP) { + anchor = get_anchor(anchor, center, BOT, BOT); od = torx_outer_diam(size); - orient_and_anchor([od, od, l], orient, anchor, spin=spin, center=center, chain=true) { + attachable(anchor,spin,orient, d=od, l=l) { linear_extrude(height=l, convexity=4, center=true) { torx_drive2d(size); } diff --git a/version.scad b/version.scad index 418bb3c..f9a59b1 100644 --- a/version.scad +++ b/version.scad @@ -8,7 +8,7 @@ ////////////////////////////////////////////////////////////////////// -BOSL_VERSION = [2,0,136]; +BOSL_VERSION = [2,0,137]; // Section: BOSL Library Version Functions diff --git a/walls.scad b/walls.scad index b359b72..b1df94d 100644 --- a/walls.scad +++ b/walls.scad @@ -38,7 +38,7 @@ module narrowing_strut(w=10, l=100, wall=5, ang=30, anchor=BOTTOM, spin=0, orien { h = wall + w/2/tan(ang); size = [w, l, h]; - orient_and_anchor(size, orient, anchor, spin=spin, chain=true) { + attachable(anchor,spin,orient, size=size) { xrot(90) fwd(h/2) { linear_extrude(height=l, center=true, slices=2) { @@ -115,7 +115,7 @@ module thinning_wall(h=50, l=100, thick=5, ang=30, braces=false, strut=5, wall=2 brace_len = norm(corner1-corner2); size = [l1, thick, h]; - orient_and_anchor(size, orient, anchor, spin=spin, size2=[l2,thick], chain=true) { + attachable(anchor,spin,orient, size=size, size2=[l2,thick]) { union() { polyhedron( points=[ @@ -262,12 +262,13 @@ module thinning_wall(h=50, l=100, thick=5, ang=30, braces=false, strut=5, wall=2 // thinning_triangle(h=50, l=80, thick=4, ang=30, strut=5, wall=2, center=false); // Example: Diagonal Brace Only // thinning_triangle(h=50, l=80, thick=4, ang=30, strut=5, wall=2, diagonly=true, center=false); -module thinning_triangle(h=50, l=100, thick=5, ang=30, strut=5, wall=3, diagonly=false, center=undef, anchor=CENTER, spin=0, orient=UP) +module thinning_triangle(h=50, l=100, thick=5, ang=30, strut=5, wall=3, diagonly=false, center, anchor, spin=0, orient=UP) { dang = atan(h/l); dlen = h/sin(dang); size = [thick, l, h]; - orient_and_anchor(size, orient, anchor, spin=spin, center=center, noncentered=BOTTOM+FRONT, chain=true) { + anchor = get_anchor(anchor, center, BOT+FRONT, CENTER); + attachable(anchor,spin,orient, size=size) { difference() { union() { if (!diagonly) { @@ -344,7 +345,7 @@ module sparse_strut(h=50, l=100, thick=4, maxang=30, strut=5, max_bridge=20, anc len = zstep / cos(ang); size = [thick, l, h]; - orient_and_anchor(size, orient, anchor, spin=spin, chain=true) { + attachable(anchor,spin,orient, size=size) { yrot(90) linear_extrude(height=thick, convexity=4*yreps, center=true) { difference() { @@ -415,7 +416,7 @@ module sparse_strut3d(h=50, l=100, w=50, thick=3, maxang=40, strut=3, max_bridge supp_step = cross_len/2/supp_reps; size = [w, l, h]; - orient_and_anchor(size, orient, anchor, spin=spin, chain=true) { + attachable(anchor,spin,orient, size=size) { intersection() { union() { ybridge = (l - (yreps+1) * strut) / yreps; @@ -495,7 +496,7 @@ module corrugated_wall(h=50, l=100, thick=5, strut=5, wall=2, anchor=CENTER, spi step = period/steps; il = l - 2*strut + 2*step; size = [thick, l, h]; - orient_and_anchor(size, orient, anchor, spin=spin, chain=true) { + attachable(anchor,spin,orient, size=size) { union() { linear_extrude(height=h-2*strut+0.1, slices=2, convexity=ceil(2*il/period), center=true) { polygon(