diff --git a/attachments.scad b/attachments.scad index 4a9416e0..93297515 100644 --- a/attachments.scad +++ b/attachments.scad @@ -35,9 +35,14 @@ $parent_orient = UP; $parent_size = undef; $parent_geom = undef; +$edge_angle = undef; +$edge_length = undef; + $tags_shown = "ALL"; $tags_hidden = []; + + _ANCHOR_TYPES = ["intersect","hull"]; @@ -972,6 +977,7 @@ module attach(parent, child, overlap, align, spin=0, norot, inset=0, shiftout=0, anchor_data = _find_anchor(anchor, $parent_geom); $edge_angle = len(anchor_data)==5 ? struct_val(anchor_data[4],"edge_angle") : undef; $edge_length = len(anchor_data)==5 ? struct_val(anchor_data[4],"edge_length") : undef; + $edge_end1 = len(anchor_data)==5 ? struct_val(anchor_data[4],"vec") : undef; anchor_pos = anchor_data[1]; anchor_dir = factor*anchor_data[2]; anchor_spin = two_d || !inside || anchor==TOP || anchor==BOT ? anchor_data[3] @@ -1955,7 +1961,7 @@ module face_mask(faces=[LEFT,RIGHT,FRONT,BACK,BOT,TOP]) { // Usage: // PARENT() edge_mask([edges], [except]) CHILDREN; // Description: -// Takes a 3D mask shape, and attaches it to the given edges, with the appropriate orientation to be +// Takes a 3D mask shape, and attaches it to the given edges of a cuboid parent, with the appropriate orientation to be // differenced away. The mask shape should be vertically oriented (Z-aligned) with the back-right // quadrant (X+Y+) shaped to be diffed away from the edge of parent attachable shape. If no tag is set // then `edge_mask` sets the tag for children to "remove" so that it will work with the default {{diff()}} tag. @@ -3179,12 +3185,13 @@ function reorient( // orient = A vector pointing in the direction parts should project from the anchor position. Default: UP // spin = If needed, the angle to rotate the part around the direction vector. Default: 0 // --- +// info = structure listing info to be propagated to the attached child, e.g. "edge_anchor" // rot = A 4x4 rotations matrix, which may include a translation // flip = If true, flip the anchor the opposite direction. Default: false -function named_anchor(name, pos, orient, spin, rot, flip) = +function named_anchor(name, pos, orient, spin, rot, flip, info) = assert(num_defined([orient,spin])==0 || num_defined([rot,flip])==0, "Cannot mix orient or spin with rot or flip") assert(num_defined([pos,rot])>0, "Must give pos or rot") - is_undef(rot) ? [name, pos, default(orient,UP), default(spin,0)] + is_undef(rot) ? [name, pos, default(orient,UP), default(spin,0), if (info) info] : let( flip = default(flip,false), @@ -3198,7 +3205,7 @@ function named_anchor(name, pos, orient, spin, rot, flip) = decode=rot_decode(rot(to=UP,from=dir)*_force_rot(rot)), spin = decode[0]*sign(decode[1].z) ) - [name, pos, dir, spin]; + [name, pos, dir, spin, if (info) info]; // Function: attach_geom() @@ -3789,6 +3796,26 @@ function _find_anchor(anchor, geom)= ) unit(v3,UP), edgeang = len(facevecs)==2 ? 180-vector_angle(facevecs[0], facevecs[1]) : undef, + edgelen = anch.z==0 ? norm(edge) + : anch.z>0 ? abs([size2.y,size2.x]*axy) + : abs([size.y,size.x]*axy), + endvecs = len(facevecs)!=2 ? undef + : anch.z==0 ? [DOWN, UP] + : let( + raxy = zrot(-90,axy), + bot1 = point3d(v_mul(point2d(size )/2, raxy), -h/2), + top1 = point3d(v_mul(point2d(size2)/2, raxy) + shift, h/2), + edge1 = top1-bot1, + vec1 = (raxy.x!=0) ? unit(rot(from=UP, to=[edge1.x,0,max(0.01,h)], p=[raxy.x,0,0]), UP) + : unit(rot(from=UP, to=[0,edge1.y,max(0.01,h)], p=[0,raxy.y,0]), UP), + raxy2 = zrot(90,axy), + bot2 = point3d(v_mul(point2d(size )/2, raxy2), -h/2), + top2 = point3d(v_mul(point2d(size2)/2, raxy2) + shift, h/2), + edge2 = top2-bot2, + vec2 = (raxy2.y!=0) ? unit(rot(from=UP, to=[edge.x,0,max(0.01,h)], p=[raxy2.x,0,0]), UP) + : unit(rot(from=UP, to=[0,edge.y,max(0.01,h)], p=[0,raxy2.y,0]), UP) + ) + [vec1,vec2], final_dir = default(override[1],anch==CENTER?UP:rot(from=UP, to=axis, p=dir)), final_pos = default(override[0],rot(from=UP, to=axis, p=pos)), @@ -3801,7 +3828,8 @@ function _find_anchor(anchor, geom)= : anch.z!=0 && sum(v_abs(anch))==2 ? _compute_spin(final_dir, rot(from=UP, to=axis, p=anch.z*[anch.y,-anch.x,0])) : norm(anch)==3 ? _compute_spin(final_dir, final_dir==DOWN || final_dir==UP ? BACK : UP) : oang // face anchors point UP/BACK - ) [anchor, final_pos, final_dir, default(override[2],spin), if (is_def(edgeang)) [["edge_angle",edgeang],["edge_length",norm(edge)]]] + ) [anchor, final_pos, final_dir, default(override[2],spin), + if (is_def(edgeang)) [["edge_angle",edgeang],["edge_length",edgelen], ["vec", endvecs]]] ) : type == "conoid"? ( //r1, r2, l, shift let( rr1=geom[1], diff --git a/constants.scad b/constants.scad index 466dfcc1..ee1eb3db 100644 --- a/constants.scad +++ b/constants.scad @@ -44,6 +44,8 @@ _UNDEF="LRG+HX7dy89RyHvDlAKvb9Y04OTuaikpx205CTh8BSI"; // The correct hole should hold the plug when the long block is turned upside-down. // The number in front of that hole will indicate the `$slop` value that is ideal for your printer. // Remember to set that slop value in your scripts after you include the BOSL2 library: ie: `$slop = 0.15;` +// . +// Note that the `$slop` value may be different using different materials even on the same printer. // Example(3D,Med): Slop Calibration Part. // min_slop = 0.00; // slop_step = 0.05; @@ -215,6 +217,36 @@ CENTER = [ 0, 0, 0]; // Centered zero vector. CTR = CENTER; CENTRE = CENTER; +// Function: EDGE() +// Synopsis: Named edge anchor constants +// Topics: Constants, Attachment +// Usage: +// EDGE(i) +// EDGE(direction,i) +// Description: +// A shorthand for the named anchors "edge0", "top_edge0", "bot_edge0", etc. +// Use `EDGE(i)` to get "edge". Use `EDGE(TOP,i)` to get "top_edge" and +// use `EDGE(BOT,i)` to get "bot_edge(i)". You can also use +// `EDGE(CTR,i)` to get "edge" and you can replace TOP or BOT with simply 1 or -1. + +function EDGE(a,b) = + is_undef(b) ? str("edge",a) + : assert(in_list(a,[TOP,BOT,CTR,1,0,-1]),str("Invalid direction: ",a)) + let( + choices=["bot_","","top_"], + ind=is_vector(a) ? a.z : a + ) + str(choices[ind+1],"edge",b); + +// Function: FACE() +// Synopsis: Named face anchor constants +// Topics: Constants, Attachment +// Usage: +// FACE(i) +// Description: +// A shorthand for the named anchors "face0", "face1", etc. + +function FACE(i) = str("face",i); // Section: Line specifiers // Used by functions in geometry.scad for specifying whether two points diff --git a/masks3d.scad b/masks3d.scad index aebbf605..2b1bf78d 100644 --- a/masks3d.scad +++ b/masks3d.scad @@ -25,7 +25,7 @@ // Difference it from the object to be chamfered. The center of // the mask object should align exactly with the edge to be chamfered. // Arguments: -// l/h/length/height = Length of mask. +// l/h/length/height = Length of mask. Default: $edge_length if defined // chamfer = Size of chamfer. // excess = The extra amount to add to the length of the mask so that it differences away from other shapes cleanly. Default: `0.1` // --- @@ -49,7 +49,8 @@ // } function chamfer_edge_mask(l, chamfer=1, excess=0.1, h, length, height, anchor=CENTER, spin=0, orient=UP) = no_function("chamfer_edge_mask"); module chamfer_edge_mask(l, chamfer=1, excess=0.1, h, length, height, anchor=CENTER, spin=0, orient=UP) { - l = one_defined([l, h, height, length], "l,h,height,length"); + l = is_def($edge_length) && !any_defined([l,length,h,height]) ? $edge_length + : one_defined([l,length,h,height],"l,length,h,height"); default_tag("remove") { attachable(anchor,spin,orient, size=[chamfer*2, chamfer*2, l]) { cylinder(r=chamfer, h=l+excess, center=true, $fn=4); @@ -169,28 +170,35 @@ module chamfer_cylinder_mask(r, chamfer, d, ang=45, from_end=false, anchor=CENTE } - // Section: Rounding Masks // Module: rounding_edge_mask() // Synopsis: Creates a shape to round a 90° edge. // SynTags: Geom // Topics: Masks, Rounding, Shapes (3D) -// See Also: rounding_corner_mask(), default_tag(), diff() +// See Also: edge_profile(), rounding_corner_mask(), default_tag(), diff() // Usage: -// rounding_edge_mask(l|h=|length=|height=, r|d=, [ang], [excess=]) [ATTACHMENTS]; -// rounding_edge_mask(l|h=|length=|height=, r1=|d1=, r2=|d2=, [ang=], [excess=]) [ATTACHMENTS]; +// rounding_edge_mask(l|h=|length=|height=, r|d=, [ang], [excess=], [rounding=|chamfer=], ) [ATTACHMENTS]; +// rounding_edge_mask(l|h=|length=|height=, r1=|d1=, r2=|d2=, [ang=], [excess=], [rounding=|chamfer=]) [ATTACHMENTS]; // Description: -// Creates a shape that can be used to round a straight edge at any angle. -// Difference it from the object to be rounded. The center of the mask -// object should align exactly with the edge to be rounded. You can use it with {{diff()}} and -// {{edge_mask()}} to attach masks automatically to objects. The default "remove" tag is set -// automatically. +// Creates a mask shape that can be used to round a straight edge at any angle, with +// different rounding radii at each end. The corner of the mask appears on the Z axis with one face on the XZ plane. +// You must align the mask corner with the edge you want to round. If your parent object is a cuboid, the easiest way to +// do this is to use {{diff()}} and {{edge_mask()}}. However, this method is somewhat inflexible regarding orientation of a tapered +// mask, and it does not support other parent shapes. You can attach the mask to a larger range of shapes using +// {{attach()}} to anchor the `LEFT+FWD` anchor of the mask to a desired corner on the parent with `inside=true`. +// Many shapes propagate `$edge_angle` and `$edge_length` which can aid in configuring the mask, and you can adjust the +// mask as needed to align the taper as desired. The default "remove" tag is set so {{diff()}} will automatically difference +// away the mask. You can of course also position the mask manually and use `difference()`. +// . +// For mating with other roundings or chamfers on cuboids or regular prisms, you can choose end roundings and end chamfers. These affect +// only the curved edge of the mask ends and will only work if the terminating face is perpendicular to the masked edge. The `excess` +// parameter will add extra length to the mask when you use these settings. // // Arguments: -// l/h/length/height = Length of mask. +// l/h/length/height = Length of mask. Default: $edge_length if defined // r = Radius of the rounding. -// ang = Angle between faces for rounding. Default: 90 +// ang = Angle between faces for rounding. Default: $edge_angle if defined, otherwise 90 // --- // r1 = Bottom radius of rounding. // r2 = Top radius of rounding. @@ -198,6 +206,12 @@ module chamfer_cylinder_mask(r, chamfer, d, ang=45, from_end=false, anchor=CENTE // d1 = Bottom diameter of rounding. // d2 = Top diameter of rounding. // excess = Extra size for the mask. Defaults: 0.1 +// rounding = Radius of roundong along ends. Default: 0 +// rounding1 = Radius of rounding along bottom end +// rounding2 = Radius of rounding along top end +// chamfer = Chamfer size of end chamfers. Default: 0 +// chamfer1 = Chamfer size of chamfer at bottom end +// chamfer2 = Chamfer size of chamfer at top end // anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#subsection-anchor). Default: `CENTER` // spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#subsection-spin). Default: `0` // orient = Vector to rotate top towards, after spin. See [orient](attachments.scad#subsection-orient). Default: `UP` @@ -249,34 +263,85 @@ module chamfer_cylinder_mask(r, chamfer, d, ang=45, from_end=false, anchor=CENTE // rounding_edge_mask(l=p.z, r=25); // } // } +// Example(3D,VPT=[5.02872,6.37039,-0.503894],VPR=[75.3,0,107.4],VPD=74.4017): Mask shape with end rounding at the top, chamfer at the bottom, and a large excess value: +// rounding_edge_mask(r=10,h=20, chamfer1=3, rounding2=3, excess=1); +// Example(3D,VPT=[1.05892,1.10442,2.20513],VPR=[60.6,0,118.1],VPD=74.4017): Attaching masks using {{attach()}} with automatic angle and length from the parent. Note that sometimes the automatic length is too short because it is the length of the edge itself. +// diff() +// prismoid([20,30],[12,19], h=10,shift=[4,7]) +// attach([TOP+RIGHT,RIGHT+FRONT],LEFT+FWD,inside=true) +// rounding_edge_mask(r1=2,r2=4); +// Example(3D): The mask does not need to be the full length of the edge +// diff() +// cuboid(20) +// attach(RIGHT+TOP,LEFT+FWD,inside=true,inset=-.1,align=FWD) +// rounding_edge_mask(r1=0,r2=10,length=10); function rounding_edge_mask(l, r, ang=90, r1, r2, d, d1, d2, excess=0.1, anchor=CENTER, spin=0, orient=UP, h,height,length) = no_function("rounding_edge_mask"); -module rounding_edge_mask(l, r, ang=90, r1, r2, excess=0.01, d1, d2,d,r,length, h, height, anchor=CENTER, spin=0, orient=UP, +module rounding_edge_mask(l, r, ang, r1, r2, excess=0.01, d1, d2,d,r,length, h, height, anchor=CENTER, spin=0, orient=UP, + rounding,rounding1,rounding2,chamfer,chamfer1,chamfer2, _remove_tag=true) { - length = one_defined([l,length,h,height],"l,length,h,height"); + ang = first_defined([ang,$edge_angle,90]); + length = is_def($edge_length) && !any_defined([l,length,h,height]) ? $edge_length + : one_defined([l,length,h,height],"l,length,h,height"); r1 = get_radius(r1=r1, d1=d1,d=d,r=r); r2 = get_radius(r2=r2, d1=d2,d=d,r=r); + dummy1 = assert(num_defined([chamfer,rounding])<2, "Cannot give both rounding and chamfer") + assert(num_defined([chamfer1,rounding1])<2, "Cannot give both rounding1 and chamfer1") + assert(num_defined([chamfer2,rounding2])<2, "Cannot give both rounding2 and chamfer2"); + rounding1 = first_defined([rounding1,rounding,0]); + rounding2 = first_defined([rounding2,rounding,0]); + chamfer1 = first_defined([chamfer1,chamfer,0]); + chamfer2 = first_defined([chamfer2,chamfer,0]); dummy = assert(all_nonnegative([r1,r2]), "radius/diameter value(s) must be nonnegative") assert(all_positive([length]), "length/l/h/height must be a positive value") - assert(is_finite(ang) && ang>0 && ang<180, "ang must be a number between 0 and 180"); + assert(is_finite(ang) && ang>0 && ang<180, "ang must be a number between 0 and 180") + assert(all_nonnegative([chamfer1,chamfer2,rounding1,rounding2]), "chamfers and roundings must be nonnegative"); steps = ceil(segs(max(r1,r2))*(180-ang)/360); function make_path(r) = - let( - arc = r==0 ? repeat([0,0],steps+1) - : arc(n=steps+1, r=r, corner=[polar_to_xy(r,ang),[0,0],[r,0]]), - maxx = last(arc).x, - maxy = arc[0].y, - cp = [-excess/tan(ang/2),-excess] - ) - [ - [maxx, -excess], - cp, - arc[0] + polar_to_xy(excess, 90+ang), - each arc - ]; + r==0 ? repeat([0,0],steps+1) + : arc(n=steps+1, r=r, corner=[polar_to_xy(r,ang),[0,0],[r,0]]); path1 = path3d(make_path(r1),-length/2); path2 = path3d(make_path(r2),length/2); + + function getarc(bigr,r,chamfer,p1,p2,h,print=false) = + r==0 && chamfer==0? [p2] + : + let( + steps = ceil(segs(r)/4)+1, + center = [bigr/tan(ang/2), bigr,h], + refplane = plane_from_normal([-(p2-center).y, (p2-center).x, 0], p2), + refnormal = plane_normal(refplane), + mplane = plane3pt(p2,p1,center), + A = plane_normal(mplane), + basept = lerp(p2,p1,max(r,chamfer)/2/h), + corner = [basept+refnormal*(refplane[3]-basept*refnormal)/(refnormal*refnormal), + p2, + center], + bare_arc = chamfer ? [p2+chamfer*unit(corner[0]-corner[1]),p2+chamfer*unit(corner[2]-corner[1])] + : arc(r=r, corner = corner, n=steps), + arc_with_excess = [each bare_arc, up(excess, last(bare_arc))], + arc = [for(pt=arc_with_excess) pt+refnormal*(mplane[3]-pt*A)/(refnormal*A)] + ) + arc; + cp = [-excess/tan(ang/2), -excess]; + extra1 = rounding1 || chamfer1 ? [0,0,excess] : CTR; + extra2 = rounding2 || chamfer2 ? [0,0,excess] : CTR; + pathlist = [for(i=[0:len(path1)-1]) + let( + path = [ + if (i==0) move(polar_to_xy( excess, 90+ang),path1[i]-extra1) + else if (i==len(path1)-1) fwd(excess,last(path1)-extra1) + else point3d(cp,-length/2-extra1.z), + each reverse(zflip(getarc(r1,rounding1,chamfer1,zflip(path2[i]), zflip(path1[i]),length/2))), + each getarc(r2,rounding2,chamfer2,path1[i],path2[i],length/2,print=rounding2!=0&&!is_undef(rounding2)&&i==3), + if (i==0) move(polar_to_xy( excess, 90+ang),path2[i]+extra2) + else if (i==len(path2)-1) fwd(excess,last(path2)+extra2) + else point3d(cp, length/2+extra2.z), + ] + ) + path]; + left_normal = cylindrical_to_xyz(1,90+ang,0); left_dir = cylindrical_to_xyz(1,ang,0); zdir = unit([length, 0,-(r2-r1)/tan(ang/2)]); @@ -315,7 +380,7 @@ module rounding_edge_mask(l, r, ang=90, r1, r2, excess=0.01, d1, d2,d,r,length, [BACK+RIGHT+TOP, [cylindrical_to_xyz(cutfact*r2,ang/2,length/2), zrot(ang/2,zdir)+UP,ang/2+90]], [BACK+RIGHT+BOT, [cylindrical_to_xyz(cutfact*r1,ang/2,-length/2), zrot(ang/2,zdir)+DOWN,ang/2+90]], ]; - vnf = vnf_vertex_array([path1,path2],caps=true,col_wrap=true); + vnf = vnf_vertex_array(reverse(pathlist), col_wrap=true,caps=true); default_tag("remove", _remove_tag) attachable(anchor,spin,orient,size=[1,1,length],override=override){ vnf_polyhedron(vnf); diff --git a/shapes3d.scad b/shapes3d.scad index 2718641f..ac8fdd9a 100644 --- a/shapes3d.scad +++ b/shapes3d.scad @@ -599,8 +599,8 @@ function cuboid( // specifying `size2=[100,undef]` sets the size in the X direction but allows the size in the Y direction to be computed based on yang. // . // The anchors on the top and bottom faces have spin pointing back. The anchors on the side faces have spin point UP. -// The anchors on the top and bottom edges also have anchors that point up. The anchors on the side edges and the corners -// have spin with positive Z component, pointing along the edge where the anchor is located. +// The anchors on the top and bottom edges also have anchors that point clockwise as viewed from outside the shapep. +// The anchors on the side edges and the corners have spin with positive Z component, pointing along the edge where the anchor is located. // Arguments: // size1 = [width, length] of the bottom end of the prism. // size2 = [width, length] of the top end of the prism. @@ -835,7 +835,7 @@ function octahedron(size=1, anchor=CENTER, spin=0, orient=UP) = // Synopsis: Creates a regular prism with roundovers and chamfering // SynTags: Geom, VNF // Topics: Textures, Rounding, Chamfers -// See Also: cyl(), rounded_prism(), texture(), linear_sweep() +// See Also: cyl(), rounded_prism(), texture(), linear_sweep(), EDGE(), FACE() // Usage: Normal prisms // regular_prism(n, h|l=|height=|length=, r, [center=], [realign=]) [ATTACHMENTS]; // regular_prism(n, h|l=|height=|length=, d=|id=|od=|ir=|or=|side=, ...) [ATTACHMENTS]; @@ -863,7 +863,8 @@ function octahedron(size=1, anchor=CENTER, spin=0, orient=UP) = // . // Anchors are based on the VNF of the prism. Especially for tapered or shifted prisms, this may give unexpected anchor positions, such as top side anchors // being located at the bottom of the shape, so confirm anchor positions before use. -// Additional face and edge anchors are located on the side faces and vertical edges of the prism. +// Additional named face and edge anchors are located on the side faces and vertical edges of the prism. +// You can use `EDGE(i)`, `EDGE(TOP,i)` and `EDGE(BOT,i)` as a shorthand for accessing the named edge anchors, and `FACE(i)` for the face anchors. // When you use `shift`, which moves the top face of the prism, the spin for the side face and edges anchors will align the child with the edge or face direction. // Named anchors located along the top and bottom edges and corners are pointed in the direction of the associated face or edge to enable positioning // in the direction of the side faces but positioned at the top/bottom, since {{align()}} cannot be used for this task. These edge and corners anchors do @@ -875,8 +876,8 @@ function octahedron(size=1, anchor=CENTER, spin=0, orient=UP) = // Named Anchors: // "edge0", "edge1", etc. = Center of each side edge, spin pointing up along the edge // "face0", "face1", etc. = Center of each side face, spin pointing up -// "topedge0", "topedge1", etc = Center of each top edge, pointing in direction of associated side face, spin up -// "botedge0", "botedge1", etc = Center of each bottom edge, pointing in direction of associated side face, spin up +// "top_edge0", "top_edge1", etc = Center of each top edge, spin pointing clockwise (from top) +// "bot_edge0", "bot_edge1", etc = Center of each bottom edge, spin pointing clockwise (from bottom) // "topcorner0", "topcorner1", etc = Top corner, pointing in direction of associated edge anchor, spin up along associated edge // "botcorner0", "botcorner1", etc = Bottom corner, pointing in direction of associated edge anchor, spin up along associated edge // Arguments: @@ -1131,33 +1132,18 @@ function regular_prism(n, ovnf = apply(skmat, vnf), edge_face = [ [r2-r1,0,height],[(r2-r1)/sc,0,height]], // regular edge, then face edge, in xz plane names = ["edge","face"], - anchors = approx(shift,[0,0]) ? - [for(i=[0:n-1], j=[0:1]) - let( - M = zrot(-(i+j/2-(realign?1/2:0))*360/n), - edge = apply(M,edge_face[j]), - dir = apply(M,[height,0,-edge_face[j].x]), - spin = sign(dir.x)*vector_angle(edge - (edge*dir)*dir, rot(from=UP,to=dir,p=BACK)) - ) - each [ - named_anchor(str(names[j],i), apply(M,[(r1+r2)/2/(j==0?1:sc),0,0]), dir, spin), - named_anchor(str(j==0?"top_corner":"top_edge",i), apply(M,[r2/(j==0?1:sc),0,height/2]), dir, spin), - named_anchor(str(j==0?"bot_corner":"bot_edge",i), apply(M,[r1/(j==0?1:sc),0,-height/2]), dir, spin), - ] - ] - : - let( + anchors = let( faces = [ for(i=[0:n-1]) let( - M1 = skmat*zrot(-i*360/n), - M2 = skmat*zrot(-(i+1)*360/n), - edge1 = apply(M1,[[r2,0,height/2], [r1,0,-height/2]]), - edge2 = apply(M2,[[r2,0,height/2], [r1,0,-height/2]]), - face_edge = (edge1+edge2)/2, + M1 = skmat*zrot(-i*360/n), // map to point i + M2 = skmat*zrot(-(i+1)*360/n), // map to point i+1 + edge1 = apply(M1,[[r2,0,height/2], [r1,0,-height/2]]), // "vertical" edge at i + edge2 = apply(M2,[[r2,0,height/2], [r1,0,-height/2]]), // "vertical" edge at i+1 + face_edge = (edge1+edge2)/2, // "vertical" edge across side face between i and i+1 facenormal = unit(cross(edge1[0]-edge1[1], edge2[1]-edge1[0])) - ) - [facenormal,face_edge[0]-face_edge[1],edge1[0]-edge1[1]] // [normal to face, edge through face center, actual edge] + ) // [normal to face, edge through face center vector, actual edge vector, top edge vector] + [facenormal,face_edge[0]-face_edge[1],edge1[0]-edge1[1],edge2[0]-edge1[0]] ] ) [for(i=[0:n-1]) @@ -1165,20 +1151,31 @@ function regular_prism(n, Mface = skmat*zrot(-(i+1/2)*360/n), faceedge = faces[i][1], facenormal = faces[i][0], - //facespin = _compute_spin(facenormal, faceedge), // spin along centerline of face instea of pointing up---seems to be wrong choice + //facespin = _compute_spin(facenormal, faceedge), // spin along centerline of face instead of pointing up---seems to be wrong choice facespin = _compute_spin(facenormal, UP), edgenormal = unit(vector_bisect(facenormal,select(faces,i-1)[0])), Medge = skmat*zrot(-i*360/n), edge = faces[i][2], - edgespin = _compute_spin(edgenormal, edge) + edgespin = _compute_spin(edgenormal, edge), + topedge = unit(faces[i][3]), + topnormal = unit(facenormal+UP), + botnormal = unit(facenormal+DOWN), + topedgespin = _compute_spin(topnormal, topedge), + botedgespin = _compute_spin(botnormal, -topedge), + topedgeangle = 180-vector_angle(UP,facenormal), + sideedgeangle = 180-vector_angle(facenormal, select(faces,i-1)[0]), + edgelen = norm(select(faces,i)[2]) ) each [ named_anchor(str("face",i), apply(Mface,[(r1+r2)/2/sc,0,0]), facenormal, facespin), - named_anchor(str("edge",i), apply(Medge,[(r1+r2)/2,0,0]), edgenormal, edgespin), - named_anchor(str("top_edge",i), apply(Mface,[r2/sc,0,height/2]), facenormal, facespin), - named_anchor(str("top_corner",i), apply(Medge,[r2,0,height/2]), edgenormal, edgespin), - named_anchor(str("bot_edge",i), apply(Mface,[r1/sc,0,-height/2]), facenormal, facespin), - named_anchor(str("bot_corner",i), apply(Medge,[r1,0,-height/2]), edgenormal, edgespin) + named_anchor(str("edge",i), apply(Medge,[(r1+r2)/2,0,0]), edgenormal, edgespin, + info=[["edge_angle",sideedgeangle], ["edge_length",edgelen]]), + named_anchor(str("top_edge",i), apply(Mface,[r2/sc,0,height/2]), topnormal, topedgespin, + info=[["edge_angle",topedgeangle],["edge_length",2*sin(180/n)*r2]]), + named_anchor(str("bot_edge",i), apply(Mface,[r1/sc,0,-height/2]), botnormal, botedgespin, + info=[["edge_angle",180-topedgeangle],["edge_length",2*sin(180/n)*r1]]), + named_anchor(str("top_corner",i), apply(Medge,[r2,0,height/2]), unit(edgenormal+UP), edgespin), + named_anchor(str("bot_corner",i), apply(Medge,[r1,0,-height/2]), unit(edgenormal+DOWN), edgespin) ] ], override = approx(shift,[0,0]) ? undef : [[UP, [point3d(shift,height/2), UP]]], @@ -3666,20 +3663,34 @@ module path_text(path, text, font, size, thickness, lettersize, offset=0, revers // Description: // Creates a shape that can be unioned into a concave joint between two faces, to fillet them. // Note that this module is the same as {{rounding_edge_mask()}}, except that it does not -// apply the default "remove" tag. -// +// apply the default "remove" tag and has a different default angle. +// It can be convenient to {{attach()}} the fillet to the edge of a parent object. +// Many objects propagate the $edge_angle and $edge_length which are used as defaults for the fillet. +// If you attach the fillet to the edge, it will be hovering in space and you need to apply {{yrot()}} +// to place it on the parent object, generally either 90 degrees or -90 degrees dependong on which +// face you want the fillet. // Usage: -// fillet(l|h=|length=|height=, r|d=, [ang=], [excess=]) [ATTACHMENTS]; -// fillet(l|h=|length=|height=, r1=|d1=, r2=|d2=, [ang=], [excess=]) [ATTACHMENTS]; +// fillet(l|h=|length=|height=, r|d=, [ang=], [excess=], [rounding=|chamfer=]) [ATTACHMENTS]; +// fillet(l|h=|length=|height=, r1=|d1=, r2=|d2=, [ang=], [excess=], [rounding=|chamfer=]) [ATTACHMENTS]; // // Arguments: -// l / length / h / height = Length of edge to fillet. -// r = Radius of fillet. -// ang = Angle between faces to fillet. -// excess = Overlap size for unioning with faces. +// l/h/length/height = Length of mask. Default: $edge_length if defined +// r = Radius of the rounding. +// ang = Angle between faces for rounding. Default: 180-$edge_angle if defined, otherwise 90 // --- -// d = Diameter of fillet. -// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#subsection-anchor). Default: `FRONT+LEFT` +// r1 = Bottom radius of fillet. +// r2 = Top radius of fillet. +// d = Diameter of the fillet. +// d1 = Bottom diameter of fillet. +// d2 = Top diameter of fillet. +// excess = Extra size for the fillet. Defaults: .1 +// rounding = Radius of roundong along ends. Default: 0 +// rounding1 = Radius of rounding along bottom end +// rounding2 = Radius of rounding along top end +// chamfer = Chamfer size of end chamfers. Default: 0 +// chamfer1 = Chamfer size of chamfer at bottom end +// chamfer2 = Chamfer size of chamfer at top end +// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#subsection-anchor). Default: `CENTER` // spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#subsection-spin). Default: `0` // orient = Vector to rotate top towards, after spin. See [orient](attachments.scad#subsection-orient). Default: `UP` // @@ -3712,7 +3723,15 @@ module path_text(path, text, font, size, thickness, lettersize, offset=0, revers // cuboid(50){ // align(TOP,RIGHT,inset=10) fillet(l=50,r=10,orient=FWD); // align(TOP,RIGHT,inset=20) cuboid([4,50,20],anchor=BOT); -// } +// } +// Example(3D,VPT=[3.03052,-2.34905,8.07573],VPR=[70.4,0,326.2],VPD=82.6686): Automatic positioning of the fillet at the odd angle of this shifted prismoid is simple using {{attach()}} with the inherited $edge_angle. +// $fn=64; +// prismoid([20,15],[12,17], h=10, shift=[3,5]){ +// attach(TOP+RIGHT,FWD+LEFT,inside=false) +// yrot(90)fillet(r=4); +// attach(RIGHT,BOT) +// cuboid([22,22,2]); +// } module interior_fillet(l=1.0, r, ang=90, overlap=0.01, d, length, h, height, anchor=CENTER, spin=0, orient=UP) { @@ -3722,9 +3741,13 @@ module interior_fillet(l=1.0, r, ang=90, overlap=0.01, d, length, h, height, anc function fillet(l, r, ang, r1, r2, d, d1, d2, excess=0.1, anchor=CENTER, spin=0, orient=UP, h,height,length) = no_function("fillet"); -module fillet(l, r, ang=90, r1, r2, excess=0.01, d1, d2,d,length, h, height, anchor=CENTER, spin=0, orient=UP) +module fillet(l, r, ang, r1, r2, excess=0.01, d1, d2,d,length, h, height, anchor=CENTER, spin=0, orient=UP, + rounding,rounding1,rounding2,chamfer,chamfer1,chamfer2) { + ang = first_defined([ang, u_add(u_mul($edge_angle,-1),180), 90]); + //echo(ang,180-$edge_angle); rounding_edge_mask(l=l, r1=r1, r2=r2, ang=ang, excess=excess, d1=d1, d2=d2,d=d,r=r,length=length, h=h, height=height, + chamfer1=chamfer1, chamfer2=chamfer2, chamfer=chamfer, rounding1=rounding1, rounding2=rounding2, rounding=rounding, anchor=anchor, spin=spin, orient=orient, _remove_tag=false) children(); }