diff --git a/threading.scad b/threading.scad index bd77ad9..47c6db4 100644 --- a/threading.scad +++ b/threading.scad @@ -103,7 +103,7 @@ module thread_helix( // internal = If true, make this a mask for making internal threads. // d1 = Bottom outside diameter of threads. // d2 = Top outside diameter of threads. -// higbee = Length to taper thread ends over. Default: 0 +// higbee = Length to taper thread ends over. Default: 0 (No higbee thread tapering) // higbee1 = Length to taper bottom thread end over. // higbee2 = Length to taper top thread end over. // center = If given, overrides `anchor`. A true value sets `anchor=CENTER`, false sets `anchor=UP`. @@ -148,90 +148,92 @@ module trapezoidal_threaded_rod( profile, internal=false, d1, d2, - higbee=0, higbee1, higbee2, + higbee, higbee1, higbee2, center, anchor, spin, orient ) { _r1 = get_radius(d1=d1, d=d, dflt=10); _r2 = get_radius(d1=d2, d=d, dflt=10); sides = quantup(segs(max(_r1,_r2)), starts); rsc = internal? (1/cos(180/sides) + $slop*3) : 1; - threads = ceil(l/pitch/starts)+(starts<4?4-starts:1); + threads = ceil(l/pitch/starts) + 2; + ll = threads * pitch * starts; depth = min((thread_depth==undef? pitch/2 : thread_depth), pitch/2/tan(thread_angle)); pa_delta = min(pitch/4-0.01,depth*tan(thread_angle)/2)/pitch; dir = left_handed? -1 : 1; twist = 360 * l / pitch / starts; - higbee1 = first_defined([higbee1, higbee, 0]); - higbee2 = first_defined([higbee2, higbee, 0]); - higang1 = 360 * higbee1 / (2 * _r1 * PI); - higang2 = 360 * higbee2 / (2 * _r2 * PI); - higsteps1 = ceil(higang1/360*sides); - higsteps2 = ceil(higang2/360*sides); + _higbee1 = first_defined([higbee1, higbee, 0]); + _higbee2 = first_defined([higbee2, higbee, 0]); + higang1 = 360 * _higbee1 / (2 * PI * _r1); + higang2 = 360 * _higbee2 / (2 * PI * _r2); assert(higang1 < twist/2); assert(higang2 < twist/2); + higstart = twist/2 + 360/starts/4; higbee_table = [ - [-twist, 0.01], - [-twist/2, 0.01], - [-twist/2+higang1, 1], - [ twist/2-higang2, 1], - [ twist/2, 0.01], - [ twist, 0.01] + [-higstart*2, 0.01], + [-higstart-0.001, 0.01], + [-higstart+higang1, 1 ], + [+higstart-higang2, 1 ], + [+higstart+0.001, 0.01], + [+higstart*2, 0.01] ]; + echo(higbee_table); r1 = -depth/pitch; z1 = 1/4-pa_delta; z2 = 1/4+pa_delta; - profile = profile!=undef? profile : [ - [-z2, r1], - [-z1, 0], - [ z1, 0], - [ z2, r1], - ]; + profile = pitch * ( + profile!=undef? profile : [ + [-z2, r1], + [-z1, 0], + [ z1, 0], + [ z2, r1], + ] + ); pdepth = -min(subindex(profile,1)); eprofile = [ - [-0.5, 0], each move([0,pdepth], p=profile), - [ 0.5, 0], - ] * pitch; + move([pitch,pdepth], p=profile[0]), + ]; angstep = 360 / sides; - angsteps = ceil(twist / (360 / sides)) + sides; + angsteps = ceil(sides * (twist / 360 + 2)); zang = atan2(_r2-_r1,l); thread_verts = [ - [for (x = eprofile) [0,0,-l/2]], - for (a = [0:1:angsteps]) let ( - u = (a-angsteps/2) / (angsteps-sides), - ang = u * twist, + [for (i = idx(eprofile)) [0,0,-ll/2]], + for (thread = [0:1:threads-1], side=[0:1:sides-1]) let( + ang = ((thread - threads/2) + (side / sides)) * 360, + u = ang / twist, r = lerp(_r1, _r2, u) * rsc, hsc = higbee1==0 && higbee2==0? 1 : lookup(ang, higbee_table), mat = affine3d_zrot(ang*dir) * - affine3d_translate([r-pdepth*pitch, 0, l*u-0*pitch]) * + affine3d_translate([r-pdepth*pitch, 0, l*u]) * affine3d_xrot(90) * affine3d_skew_xz(xa=zang) * affine3d_mirror([-1,1]) * affine3d_scale([1,hsc,1]), pts = apply(mat, path3d(eprofile)) ) pts, - [for (x = eprofile) [0,0, l/2]], + [for (x = eprofile) [0,0,+ll/2]], ]; thread_vnf = vnf_vertex_array(thread_verts, reverse=left_handed); eplen = len(eprofile); vlen = len(thread_vnf[0]); thread_vnf2 = [ - concat(thread_vnf[0], [[0,0,-l/2], [0,0,l/2]]), + concat(thread_vnf[0], [[0,0,-ll/2], [0,0,+ll/2]]), concat(thread_vnf[1], [ for (i = [0:1:sides/starts]) each left_handed? [ [eplen*(i+1), eplen*i, vlen], - [vlen-eplen*(i+1)-1, vlen-eplen*i-1, vlen+1] + [vlen-eplen*(i+1)-1, vlen-eplen*(i+0)-1, vlen+1] ] : [ - [eplen*i, eplen*(i+1), vlen], - [vlen-eplen*i-1, vlen-eplen*(i+1)-1, vlen+1] + [eplen*i, eplen*(i+1), vlen], + [vlen-eplen*(i+0)-1, vlen-eplen*(i+1)-1, vlen+1] ] ]) ]; thread_vnfs = vnf_merge([ for (start = [0:1:starts-1]) zrot(start*360/starts, p=thread_vnf2) - ]); + ], cleanup=true); anchor = get_anchor(anchor, center, BOT, CENTER); attachable(anchor,spin,orient, r1=_r1, r2=_r2, l=l) { difference() { diff --git a/version.scad b/version.scad index 429cd19..47a6dfe 100644 --- a/version.scad +++ b/version.scad @@ -6,7 +6,7 @@ ////////////////////////////////////////////////////////////////////// -BOSL_VERSION = [2,0,555]; +BOSL_VERSION = [2,0,556]; // Section: BOSL Library Version Functions diff --git a/vnf.scad b/vnf.scad index 9e5ada8..98a1767 100644 --- a/vnf.scad +++ b/vnf.scad @@ -145,18 +145,31 @@ function vnf_add_faces(vnf=EMPTY_VNF, faces) = // Function: vnf_merge() // Usage: -// vnf = vnf_merge([VNF, VNF, VNF, ...]); +// vnf = vnf_merge([VNF, VNF, VNF, ...], ); // Description: // Given a list of VNF structures, merges them all into a single VNF structure. -function vnf_merge(vnfs=[],_i=0,_acc=EMPTY_VNF) = - (assert(is_vnf_list(vnfs)) _i>=len(vnfs))? _acc : - vnf_merge( - vnfs, _i=_i+1, - _acc = let(base=len(_acc[0])) [ - concat(_acc[0], vnfs[_i][0]), - concat(_acc[1], [for (f=vnfs[_i][1]) [for (i=f) i+base]]), +function vnf_merge(vnfs, cleanup=false) = + let ( + offs = cumsum([ + 0, for (vnf = vnfs) len(vnf[0]) + ]) + ) [ + [for (vnf=vnfs) each vnf[0]], + [ + for (i = idx(vnfs)) let( + vnf = vnfs[i], + verts = vnf[0], + faces = vnf[1] + ) + for (face = faces) let( + dface = !cleanup ? face : + deduplicate_indexed(verts, face, closed=true) + ) + if (len(dface) >= 3) + [ for (j = dface) offs[i] + j ] ] - ); + ]; + // Function: vnf_compact() // Usage: @@ -281,7 +294,7 @@ function vnf_vertex_array( rowcnt = rows - (row_wrap?0:1) ) rows<=1 || cols<=1 ? vnf : - vnf_merge([ + vnf_merge(cleanup=true, [ vnf, [ concat( pts, @@ -681,13 +694,8 @@ function vnf_validate(vnf, show_warns=true, check_isects=false) = uniq_edges = edgecnts[0], big_faces = !show_warns? [] : [ for (face = faces) - if (len(face) > 3) [ - "WARNING", - "BIG_FACE", - "Face has more than 3 vertices, and may confuse CGAL", - [for (i=face) varr[i]], - "yellow" - ] + if (len(face) > 3) + _vnf_validate_err("BIG_FACE", [for (i=face) varr[i]]) ], null_faces = !show_warns? [] : [ for (face = faces) let( @@ -696,13 +704,9 @@ function vnf_validate(vnf, show_warns=true, check_isects=false) = if (len(face)>=3) let( faceverts = [for (k=face) varr[k]], area = polygon_area(faceverts) - ) if (is_num(area) && abs(area) < EPSILON) [ - "WARNING", - "NULL_FACE", - str("Face has zero area: ",fmt_float(abs(area),15)), - faceverts, - "brown" - ] + ) + if (is_num(area) && abs(area) < EPSILON) + _vnf_validate_err("NULL_FACE", faceverts) ], nonplanars = unique([ for (face = faces) let( @@ -710,23 +714,13 @@ function vnf_validate(vnf, show_warns=true, check_isects=false) = area = polygon_area(faceverts) ) if (is_num(area) && abs(area) > EPSILON) - if (!coplanar(faceverts)) [ - "ERROR", - "NONPLANAR", - "Face vertices are not coplanar", - faceverts, - "cyan" - ] + if (!coplanar(faceverts)) + _vnf_validate_err("NONPLANAR", faceverts) ]), overpop_edges = unique([ for (i=idx(uniq_edges)) - if (edgecnts[1][i]>2) [ - "ERROR", - "OVRPOP_EDGE", - "Too many faces attached at Edge", - [for (i=uniq_edges[i]) varr[i]], - "#f70" - ] + if (edgecnts[1][i]>2) + _vnf_validate_err("OVRPOP_EDGE", [for (i=uniq_edges[i]) varr[i]]) ]), reversals = unique([ for(i = idx(faces), j = idx(faces)) if(i != j) @@ -736,13 +730,7 @@ function vnf_validate(vnf, show_warns=true, check_isects=false) = for(edge2 = pair(faces[j],true)) if(edge1 == edge2) // Valid adjacent faces will never have the same vertex ordering. if(_edge_not_reported(edge1, varr, overpop_edges)) - [ - "ERROR", - "REVERSAL", - "Faces Reverse Across Edge", - [for (i=edge1) varr[i]], - "violet" - ] + _vnf_validate_err("REVERSAL", [for (i=edge1) varr[i]]) ]), t_juncts = unique([ for (v=idx(varr), edge=uniq_edges) @@ -754,13 +742,8 @@ function vnf_validate(vnf, show_warns=true, check_isects=false) = if (a != b && b != c && a != c) let( pt = segment_closest_point([a,c],b) ) - if (pt == b) [ - "ERROR", - "T_JUNCTION", - "Vertex is mid-edge on another Face", - [b], - "red" - ] + if (pt == b) + _vnf_validate_err("T_JUNCTION", [b]) ]), isect_faces = !check_isects? [] : unique([ for (i = [0:1:len(faces)-2]) @@ -791,26 +774,15 @@ function vnf_validate(vnf, show_warns=true, check_isects=false) = ) if (!is_undef(isects2)) for (seg=isects2) - if (seg[0] != seg[1]) [ - "ERROR", - "FACE_ISECT", - "Faces intersect", - seg, - "blue" - ] + if (seg[0] != seg[1]) + _vnf_validate_err("FACE_ISECT", seg) ]), hole_edges = unique([ for (i=idx(uniq_edges)) if (edgecnts[1][i]<2) if (_pts_not_reported(uniq_edges[i], varr, t_juncts)) if (_pts_not_reported(uniq_edges[i], varr, isect_faces)) - [ - "ERROR", - "HOLE_EDGE", - "Edge bounds Hole", - [for (i=uniq_edges[i]) varr[i]], - "magenta" - ] + _vnf_validate_err("HOLE_EDGE", [for (i=uniq_edges[i]) varr[i]]) ]) ) concat( big_faces, @@ -824,6 +796,24 @@ function vnf_validate(vnf, show_warns=true, check_isects=false) = ); +_vnf_validate_errs = [ + ["BIG_FACE", "WARNING", "cyan", "Face has more than 3 vertices, and may confuse CGAL"], + ["NULL_FACE", "WARNING", "blue", "Face has zero area."], + ["NONPLANAR", "ERROR", "yellow", "Face vertices are not coplanar"], + ["OVRPOP_EDGE", "ERROR", "orange", "Too many faces attached at Edge"], + ["REVERSAL", "ERROR", "violet", "Faces Reverse Across Edge"], + ["T_JUNCTION", "ERROR", "magenta", "Vertex is mid-edge on another Face"], + ["FACE_ISECT", "ERROR", "brown", "Faces intersect"], + ["HOLE_EDGE", "ERROR", "red", "Edge bounds Hole"] +]; + + +function _vnf_validate_err(name, extra) = + let( + info = [for (x = _vnf_validate_errs) if (x[0] == name) x][0] + ) concat(info, [extra]); + + function _pts_not_reported(pts, varr, reports) = [ for (i = pts, report = reports, pt = report[3]) @@ -847,12 +837,12 @@ module vnf_validate(vnf, size=1, show_warns=true, check_isects=false) { check_isects=check_isects ); for (fault = faults) { - typ = fault[0]; - err = fault[1]; - msg = fault[2]; - pts = fault[3]; - clr = fault[4]; - echo(str(typ, " ", err, ": ", msg, " at ", pts)); + err = fault[0]; + typ = fault[1]; + clr = fault[2]; + msg = fault[3]; + pts = fault[4]; + echo(str(typ, " ", err, " (", clr ,"): ", msg, " at ", pts)); color(clr) { if (len(pts)==2) { stroke(pts, width=size);