diff --git a/attachments.scad b/attachments.scad index c96e27b..f375217 100644 --- a/attachments.scad +++ b/attachments.scad @@ -3333,7 +3333,7 @@ function _find_anchor(anchor, geom) = ) [anchor, pos, vec, oang] ) : type == "vnf_isect"? ( //vnf let( vnf=geom[1] ) - approx(anchor,CTR)? [anchor, [0,0,0], UP, 0] : + approx(anchor,CTR)? [anchor, cp, UP, 0] : // CENTER anchors anchor on cp, "origin" anchors on [0,0] vnf==EMPTY_VNF? [anchor, [0,0,0], unit(anchor), 0] : let( eps = 1/2048, @@ -3383,7 +3383,7 @@ function _find_anchor(anchor, geom) = [anchor, pos, n, oang] ) : type == "vnf_extent"? ( //vnf let( vnf=geom[1] ) - approx(anchor,CTR)? [anchor, [0,0,0], UP, 0] : + approx(anchor,CTR)? [anchor, cp, UP, 0] : // CENTER anchors anchor on cp, "origin" anchors on [0,0] vnf==EMPTY_VNF? [anchor, [0,0,0], unit(anchor,UP), 0] : let( rpts = apply(rot(from=anchor, to=RIGHT) * move(point3d(-cp)), vnf[0]), @@ -3432,7 +3432,7 @@ function _find_anchor(anchor, geom) = anchor = _force_anchor_2d(anchor), rgn = force_region(move(-point2d(cp), p=geom[1])) ) - approx(anchor,[0,0])? [anchor, [0,0,0], BACK, 0] : + approx(anchor,[0,0])? [anchor, cp, BACK, 0] : // CENTER anchors anchor on cp, "origin" anchors on [0,0] let( isects = [ for (path=rgn, t=triplet(path,true)) let( @@ -3456,7 +3456,7 @@ function _find_anchor(anchor, geom) = ) [anchor, pos, vec, 0] ) : type == "rgn_extent"? ( //region let( anchor = _force_anchor_2d(anchor) ) - approx(anchor,[0,0])? [anchor, [0,0,0], BACK, 0] : + approx(anchor,[0,0])? [anchor, cp, BACK, 0] : // CENTER anchors anchor on cp, "origin" anchors on [0,0] let( rgn = force_region(geom[1]), rpts = rot(from=anchor, to=RIGHT, p=flatten(rgn)), diff --git a/rounding.scad b/rounding.scad index 75365cb..bb88dc1 100644 --- a/rounding.scad +++ b/rounding.scad @@ -1365,8 +1365,13 @@ module offset_stroke(path, width=1, rounded=true, start, end, check_valid=true, // anchor = Translate so anchor point is at the origin. (module only) Default: "origin" // spin = Rotate this many degrees around Z axis after anchor. (module only) Default: 0 // orient = Vector to rotate top towards after spin (module only) -// atype = Select "hull" or "intersect" anchor types. Default: "hull" +// atype = Select "hull", "intersect", "surf_hull" or "surf_intersect" anchor types. Default: "hull" // cp = Centerpoint for determining "intersect" anchors or centering the shape. Determintes the base of the anchor vector. Can be "centroid", "mean", "box" or a 3D point. Default: "centroid" +// Anchor Types: +// hull = Anchors to the convex hull of the linear sweep of the path, ignoring any end roundings. +// intersect = Anchors to the surface of the linear sweep of the path, ignoring any end roundings. +// surf_hull = Anchors to the convex hull of the offset_sweep shape, including end treatments. +// surf_intersect = Anchors to the surface of the offset_sweep shape, including any end treatments. // Example: Rounding a star shaped prism with postive radius values // star = star(5, r=22, ir=13); // rounded_star = round_corners(star, cut=flatten(repeat([.5,0],5)), $fn=24); @@ -1641,12 +1646,21 @@ module offset_sweep(path, height, convexity=10,anchor="origin",cp="centroid", spin=0, orient=UP, atype="hull") { - assert(in_list(atype, _ANCHOR_TYPES), "Anchor type must be \"hull\" or \"intersect\""); + assert(in_list(atype, ["intersect","hull","surf_hull","surf_intersect"]), "Anchor type must be \"hull\" or \"intersect\""); vnf = offset_sweep(path=path, height=height, h=h, l=l, top=top, bottom=bottom, offset=offset, r=r, steps=steps, quality=quality, check_valid=check_valid, extra=extra, cut=cut, chamfer_width=chamfer_width, chamfer_height=chamfer_height, joint=joint, k=k, angle=angle); - vnf_polyhedron(vnf,convexity=convexity,anchor=anchor, spin=spin, orient=orient, atype=atype, cp=cp) - children(); + + if (in_list(atype,["hull","intersect"])){ + h=first_defined([h,l,height]); + attachable(anchor,spin,orient,region=[path],h=h,extent=atype=="hull",cp=cp){ + down(h/2)polyhedron(vnf[0],vnf[1],convexity=convexity); + children(); + } + } + else + vnf_polyhedron(vnf,convexity=convexity,anchor=anchor, spin=spin, orient=orient, atype=atype=="surf_hull"?"hull":"intersect", cp=cp) + children(); } @@ -2012,6 +2026,11 @@ function _rp_compute_patches(top, bot, rtop, rsides, ktop, ksides, concave) = // joint_top[1] is negative the shape will flare upward. At least one value must be non-negative. The same rules apply for joint_bot. // The joint_sides parameter must be entirely nonnegative. // . +// If the roundings at two adjacent side edges exceed the width of the face then the polyhedron will have self-intersecting faces, so it will be invalid. +// Similarly, if the roundings on the top or bottom edges cross the top face and intersect with each other, the resulting polyhedron is invalid: +// the top face after the roundings are applied must be a valid, non-degenerate polyhedron. There are two exceptions: it is permissible to +// construct a top that is a single point or two points. This means you can completely round a cube by setting the joint to half of +// the cube's width. // If you set `debug` to true the module version will display the polyhedron even when it is invalid and it will show the bezier patches at the corners. // This can help troubleshoot problems with your parameters. With the function form setting debug to true causes it to return [patches,vnf] where // patches is a list of the bezier control points for the corner patches. @@ -2032,7 +2051,7 @@ function _rp_compute_patches(top, bot, rtop, rsides, ktop, ksides, concave) = // k = continuous curvature rounding parameter for all edges. Default: 0.5 // k_top = continuous curvature rounding parameter for top // k_bot = continuous curvature rounding parameter for bottom -// k_bot = continuous curvature rounding parameter for bottom +// k_sides = continuous curvature rounding parameter side edges, a number or vector. // splinesteps = number of segments to use for curved patches. Default: 16 // debug = turn on debug mode which displays illegal polyhedra and shows the bezier corner patches for troubleshooting purposes. Default: False // convexity = convexity parameter for polyhedron(), only for module version. Default: 10 @@ -2196,23 +2215,44 @@ function rounded_prism(bottom, top, joint_bot=0, joint_top=0, joint_sides=0, k_b bot_patch = _rp_compute_patches(bottom, top, joint_bot, joint_sides_vec, k_bot, k_sides_vec, concave), vertbad = [for(i=[0:N-1]) - if (norm(top[i]-top_patch[i][4][2]) + norm(bottom[i]-bot_patch[i][4][2]) > norm(bottom[i]-top[i])) i], + if (norm(top[i]-top_patch[i][4][2]) + norm(bottom[i]-bot_patch[i][4][2]) > EPSILON + norm(bottom[i]-top[i])) i], + // Check that the patch fits on the polygon edge topbad = [for(i=[0:N-1]) if (norm(top_patch[i][2][4]-top_patch[i][2][2]) + norm(select(top_patch,i+1)[2][0]-select(top_patch,i+1)[2][2]) - > norm(top_patch[i][2][2] - select(top_patch,i+1)[2][2])) [i,(i+1)%N]], + > EPSILON + norm(top_patch[i][2][2] - select(top_patch,i+1)[2][2])) [i,(i+1)%N]], botbad = [for(i=[0:N-1]) if (norm(bot_patch[i][2][4]-bot_patch[i][2][2]) + norm(select(bot_patch,i+1)[2][0]-select(bot_patch,i+1)[2][2]) - > norm(bot_patch[i][2][2] - select(bot_patch,i+1)[2][2])) [i,(i+1)%N]], - topinbad = [for(i=[0:N-1]) + > EPSILON + norm(bot_patch[i][2][2] - select(bot_patch,i+1)[2][2])) [i,(i+1)%N]], + // If top/bot is L-shaped, check that arms of L from adjacent patches don't cross + topLbad = [for(i=[0:N-1]) if (norm(top_patch[i][0][2]-top_patch[i][0][4]) + norm(select(top_patch,i+1)[0][0]-select(top_patch,i+1)[0][2]) - > norm(top_patch[i][0][2]-select(top_patch,i+1)[0][2])) [i,(i+1)%N]], - botinbad = [for(i=[0:N-1]) + > EPSILON + norm(top_patch[i][0][2]-select(top_patch,i+1)[0][2])) [i,(i+1)%N]], + botLbad = [for(i=[0:N-1]) if (norm(bot_patch[i][0][2]-bot_patch[i][0][4]) + norm(select(bot_patch,i+1)[0][0]-select(bot_patch,i+1)[0][2]) - > norm(bot_patch[i][0][2]-select(bot_patch,i+1)[0][2])) [i,(i+1)%N]] + > EPSILON + norm(bot_patch[i][0][2]-select(bot_patch,i+1)[0][2])) [i,(i+1)%N]], + // Check that the inner edges of the patch don't cross + topinbad = [for(i=[0:N-1]) + let( + line1 = project_plane(top,[top_patch[i][2][0],top_patch[i][0][0]]), + line2 = project_plane(top,[select(top_patch,i+1)[2][4],select(top_patch,i+1)[0][4]]) + ) + if (!approx(line1[0],line1[1]) && !approx(line2[0],line2[1]) && + line_intersection(line1,line2, SEGMENT,SEGMENT)) + [i,(i+1)%N]], + botinbad = [for(i=[0:N-1]) + let( + line1 = project_plane(bottom,[bot_patch[i][2][0],bot_patch[i][0][0]]), + line2 = project_plane(bottom,[select(bot_patch,i+1)[2][4],select(bot_patch,i+1)[0][4]]) + ) + if (!approx(line1[0],line1[1]) && !approx(line2[0],line2[1]) && + line_intersection(line1,line2, SEGMENT,SEGMENT)) + [i,(i+1)%N]] ) assert(debug || vertbad==[], str("Top and bottom joint lengths are too large; they interfere with each other at vertices: ",vertbad)) - assert(debug || topbad==[], str("Joint lengths too large at top edges: ",topbad)) - assert(debug || botbad==[], str("Joint lengths too large at bottom edges: ",botbad)) + assert(debug || topbad==[], str("Joint lengths too large at top or side edges: ",topbad)) + assert(debug || botbad==[], str("Joint lengths too large at bottom or side edges: ",botbad)) + assert(debug || topLbad==[], str("Joint length too large on the top face or side at edges: ", topLbad)) + assert(debug || botLbad==[], str("Joint length too large on the bottom face or side at edges: ", botLbad)) assert(debug || topinbad==[], str("Joint length too large on the top face at edges: ", topinbad)) assert(debug || botinbad==[], str("Joint length too large on the bottom face at edges: ", botinbad)) let( @@ -2242,8 +2282,12 @@ function rounded_prism(bottom, top, joint_bot=0, joint_top=0, joint_sides=0, k_b top_patch[i][4][4] ] ], - top_simple = is_path_simple(project_plane(faces[0],faces[0]),closed=true), - bot_simple = is_path_simple(project_plane(faces[1],faces[1]),closed=true), + top_collinear = is_collinear(faces[0]), + bot_collinear = is_collinear(faces[1]), + top_degen_ok = top_collinear && len(deduplicate(faces[0]))<=2, + bot_degen_ok = bot_collinear && len(deduplicate(faces[1]))<=2, + top_simple = top_degen_ok || (!top_collinear && is_path_simple(project_plane(faces[0],faces[0]),closed=true)), + bot_simple = bot_degen_ok || (!bot_collinear && is_path_simple(project_plane(faces[1],faces[1]),closed=true)), // verify vertical edges verify_vert = [for(i=[0:N-1],j=[0:4]) diff --git a/shapes3d.scad b/shapes3d.scad index 26026ad..b1dcc4b 100644 --- a/shapes3d.scad +++ b/shapes3d.scad @@ -3349,7 +3349,11 @@ module fillet(l=1.0, r, ang=90, overlap=0.01, d, length, h, height, anchor=CENTE arc[0] + polar_to_xy(overlap, 90+ang), each arc ]; - attachable(anchor,spin,orient, size=[2*maxx,2*maxy,l]) { + override = function (anchor) + anchor.x>=0 && anchor.y>=0 ? undef + : + [[max(0,anchor.x)*maxx, max(0,anchor.y)*maxy, anchor.z*l/2]]; + attachable(anchor,spin,orient, size=[2*maxx,2*maxy,l],override=override) { if (l > 0) { linear_extrude(height=l, convexity=4, center=true) { polygon(path); diff --git a/tutorials/Attachments.md b/tutorials/Attachments.md index a55bf6f..9efa7c5 100644 --- a/tutorials/Attachments.md +++ b/tutorials/Attachments.md @@ -1262,7 +1262,7 @@ module cubic_barbell(s=100, anchor=CENTER, spin=0, orient=UP) { children(); } } -cubic_barbell(100) show_anchors(30); +cubic_barbell(100) show_anchors(60); ``` When the shape is prismoidal, where the top is a different size from the bottom, you can use @@ -1637,3 +1637,99 @@ sphere_pt = apply( ``` +## Overriding Standard Anchors + +Sometimes you may want to use the standard anchors but override some +of them. Returning to the square barebell example above, the anchors +at the right and left sides are on the cubes at each end, but the +anchors at x=0 are in floating in space. For prismoidal/cubic anchors +in 3D and trapezoidal/rectangular anchors in 2D we can override a single anchor by +specifying the override option and giving the anchor that is being +overridden, and then the replacement in the form +`[position, direction, spin]`. Most often you will only want to +override the position. If you omit the other list items then the +value drived from the standard anchor will be used. Below we override +position of the FWD anchor: + +``` +module cubic_barbell(s=100, anchor=CENTER, spin=0, orient=UP) { + override = [ + [FWD, [[0,-s/8,0]]] + ]; + attachable(anchor,spin,orient, size=[s*3,s,s],override=override) { + union() { + xcopies(2*s) cube(s, center=true); + xcyl(h=2*s, d=s/4); + } + children(); + } +} +cubic_barbell(100) show_anchors(60); +``` + +Note how the FWD anchor is now rooted on the cylindrical portion. If +you wanted to also change its direction and spin you could do it like +this: + +``` +module cubic_barbell(s=100, anchor=CENTER, spin=0, orient=UP) { + override = [ + [FWD, [[0,-s/8,0], FWD+LEFT, 225]] + ]; + attachable(anchor,spin,orient, size=[s*3,s,s],override=override) { + union() { + xcopies(2*s) cube(s, center=true); + xcyl(h=2*s, d=s/4); + } + children(); + } +} +cubic_barbell(100) show_anchors(60); +``` + +In the above example we give three values for the override. As +before, the first one places the anchor on the cylinder. We have +added the second entry which points the anchor off to the left. +The third entry gives a spin override, whose effect is shown by the +position of the red flag on the arrow. If you want to override all of +the x=0 anchors to be on the cylinder, with their standard directions, +you can do that by supplying a list: +``` +module cubic_barbell(s=100, anchor=CENTER, spin=0, orient=UP) { + override = [ + for(j=[-1:1:1], k=[-1:1:1]) + if ([j,k]!=[0,0]) [[0,j,k], [s/8*unit([0,j,k])]] + ]; + attachable(anchor,spin,orient, size=[s*3,s,s],override=override) { + union() { + xcopies(2*s) cube(s, center=true); + xcyl(h=2*s, d=s/4); + } + children(); + } +} +cubic_barbell(100) show_anchors(30); +``` + +Now all of the anchors in the middle are all rooted to the cylinder. Another +way to do the same thing is to use a function literal for override. +It will be called with the anchor as its argument and needs to return undef to just use +the default, or a `[position, direction, spin]` triple to override the +default. As before, you can omit values to keep their default. +Here is the same example using a function literal for the override: + +``` +module cubic_barbell(s=100, anchor=CENTER, spin=0, orient=UP) { + override = function (anchor) + anchor.x!=0 || anchor==CTR ? undef // Keep these + : [s/8*unit(anchor)]; + attachable(anchor,spin,orient, size=[s*3,s,s],override=override) { + union() { + xcopies(2*s) cube(s, center=true); + xcyl(h=2*s, d=s/4); + } + children(); + } +} +cubic_barbell(100) show_anchors(30); +``` diff --git a/version.scad b/version.scad index c3eba25..fb8477d 100644 --- a/version.scad +++ b/version.scad @@ -10,7 +10,8 @@ -BOSL_VERSION = [2,0,710]; +BOSL_VERSION = [2,0,714]; + // Section: BOSL Library Version Functions