diff --git a/paths.scad b/paths.scad index eaf40d2..db2c279 100644 --- a/paths.scad +++ b/paths.scad @@ -210,36 +210,31 @@ function path_length_fractions(path, closed=false) = /// for (isect=isects) translate(isect[0]) color("blue") sphere(d=10); function _path_self_intersections(path, closed=true, eps=EPSILON) = let( - path = cleanup_path(path, eps=eps), + path = closed ? close_path(path,eps=eps) : path, plen = len(path) ) - [ - for (i = [0:1:plen-(closed?2:3)]) - let( - a1 = path[i], - a2 = path[(i+1)%plen], - maxax = max(a1.x,a2.x), - minax = min(a1.x,a2.x), - maxay = max(a1.y,a2.y), - minay = min(a1.y,a2.y) - ) - for(j=[i+2:1:plen-(closed?1:2)]) + [ for (i = [0:1:plen-3]) let( + a1 = path[i], + a2 = path[i+1], + // The sign of signals is positive if the segment is one one side of + // the line defined by [a1,a2] and negative on the other side. + seg_normal = unit([-(a2-a1).y, (a2-a1).x]), + signals = [for(j=[i+2:1:plen-(i==0 && closed? 2: 1)]) path[j]-a1 ]*seg_normal + ) + for(j=[i+2:1:plen-(i==0 && closed? 3: 2)]) + // The signals test requires the two signals to have different signs, + // otherwise b1 and b2 are on the same side of the line defined by [a1,a2] + // and hence intersection is impossible + if( signals[j-i-2]*signals[j-i-1] <= 0 ) let( b1 = path[j], - b2 = path[(j+1)%plen], - isect = - maxax < b1.x && maxax < b2.x || - minax > b1.x && minax > b2.x || - maxay < b1.y && maxay < b2.y || - minay > b1.y && minay > b2.y - ? undef - : _general_line_intersection([a1,a2],[b1,b2]) + b2 = path[j+1] ) - if ((!closed || i!=0 || j!=plen-1) - && isect != undef - && isect[1]>=-eps && isect[1]<=1+eps - && isect[2]>=-eps && isect[2]<=1+eps) - [isect[0], i, isect[1], j, isect[2]] + // This test checks that a1 and a2 are on opposite sides of the + // line defined by [b1,b2]. + if( cross(b2-b1, a1-b1)*cross(b2-b1, a2-b1) <= 0 ) + let(isect = _general_line_intersection([a1,a2],[b1,b2],eps=eps)) + if (isect) [isect[0], i, isect[1], j, isect[2]] ]; @@ -266,7 +261,7 @@ function _sum_preserving_round(data, index=0) = // Function: subdivide_path() // Usage: -// newpath = subdivide_path(path, [N|refine], method); +// newpath = subdivide_path(path, [N|refine], method, [closed], [exact]); // Description: // Takes a path as input (closed or open) and subdivides the path to produce a more // finely sampled path. The new points can be distributed proportional to length diff --git a/rounding.scad b/rounding.scad index da18760..425db47 100644 --- a/rounding.scad +++ b/rounding.scad @@ -1931,8 +1931,8 @@ 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(faces[0],closed=true), - bot_simple = is_path_simple(faces[1],closed=true), + 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), // verify vertical edges verify_vert = [for(i=[0:N-1],j=[0:4]) @@ -1958,8 +1958,8 @@ function rounded_prism(bottom, top, joint_bot=0, joint_top=0, joint_sides=0, k_b vnf = vnf_merge([ each subindex(top_samples,0), each subindex(bot_samples,0), for(pts=edge_points) vnf_vertex_array(pts), - debug ? vnf_add_faces(EMPTY_VNF,faces) - : vnf_triangulate(vnf_add_faces(EMPTY_VNF,faces)) + debug ? vnf_from_polygons(faces) + : vnf_triangulate(vnf_from_polygons(faces)) ]) ) debug ? [concat(top_patch, bot_patch), vnf] : vnf; diff --git a/tests/test_attachments.scad b/tests/test_attachments.scad new file mode 100644 index 0000000..4985cd2 --- /dev/null +++ b/tests/test_attachments.scad @@ -0,0 +1,8 @@ +include<../std.scad> + + +module test__standard_anchors() { + assert_equal(_standard_anchors(), [[-1,-1,1],[0,-1,1],[1,-1,1],[-1,0,1],[0,0,1],[1,0,1],[-1,1,1],[0,1,1],[1,1,1],[-1,-1,0],[0,-1,0],[1,-1,0],[-1,0,0],[0,0,0],[1,0,0],[-1,1,0],[0,1,0],[1,1,0],[-1,-1,-1],[0,-1,-1],[1,-1,-1],[-1,0,-1],[0,0,-1],[1,0,-1],[-1,1,-1],[0,1,-1],[1,1,-1]]); +} +test__standard_anchors(); + diff --git a/tests/test_vnf.scad b/tests/test_vnf.scad index a119999..e793ff7 100644 --- a/tests/test_vnf.scad +++ b/tests/test_vnf.scad @@ -34,27 +34,13 @@ module test_vnf_faces() { test_vnf_faces(); -module test_vnf_add_face() { +module test_vnf_from_polygons() { verts = [[-1,-1,-1],[1,-1,-1],[0,1,-1],[0,0,1]]; - faces = [[0,1,2],[0,3,1],[1,3,2],[2,3,0]]; - vnf1 = vnf_add_face(pts=select(verts,faces[0])); - vnf2 = vnf_add_face(vnf1, pts=select(verts,faces[1])); - vnf3 = vnf_add_face(vnf2, pts=select(verts,faces[2])); - vnf4 = vnf_add_face(vnf3, pts=select(verts,faces[3])); - assert(vnf1 == [select(verts,0,2),select(faces,[0])]); - assert(vnf2 == [verts,select(faces,[0:1])]); - assert(vnf3 == [verts,select(faces,[0:2])]); - assert(vnf4 == [verts,faces]); + faces = [[0,1,2],[0,1,3,2],[2,3,0]]; + assert(vnf_merge(cleanup=true, + [vnf_from_polygons([for (face=faces) select(verts,face)])]) == [verts,faces]); } -test_vnf_add_face(); - - -module test_vnf_add_faces() { - verts = [[-1,-1,-1],[1,-1,-1],[0,1,-1],[0,0,1]]; - faces = [[0,1,2],[0,3,1],[1,3,2],[2,3,0]]; - assert(vnf_add_faces(faces=[for (face=faces) select(verts,face)]) == [verts,faces]); -} -test_vnf_add_faces(); +test_vnf_from_polygons(); module test_vnf_centroid() { @@ -85,8 +71,8 @@ test_vnf_area(); module test_vnf_merge() { - vnf1 = vnf_add_face(pts=[[-1,-1,-1],[1,-1,-1],[0,1,-1]]); - vnf2 = vnf_add_face(pts=[[1,1,1],[-1,1,1],[0,1,-1]]); + vnf1 = vnf_from_polygons([[[-1,-1,-1],[1,-1,-1],[0,1,-1]]]); + vnf2 = vnf_from_polygons([[[1,1,1],[-1,1,1],[0,1,-1]]]); assert(vnf_merge([vnf1,vnf2]) == [[[-1,-1,-1],[1,-1,-1],[0,1,-1],[1,1,1],[-1,1,1],[0,1,-1]],[[0,1,2],[3,4,5]]]); } test_vnf_merge(); diff --git a/threading.scad b/threading.scad index 6379944..40fcc93 100644 --- a/threading.scad +++ b/threading.scad @@ -23,7 +23,8 @@ // left_handed = if true, create left-handed threads. Default = false // bevel = if true, bevel the thread ends. Default: false // bevel1 = if true bevel the bottom end. -// bevel2 = if true bevel the top end. +// bevel2 = if true bevel the top end. +// starts = The number of lead starts. Default: 1 // internal = If true, make this a mask for making internal threads. // d1 = Bottom outside diameter of threads. // d2 = Top outside diameter of threads. @@ -40,10 +41,23 @@ // Examples(Med): // threaded_rod(d=10, l=20, pitch=1.25, left_handed=true, $fa=1, $fs=1); // threaded_rod(d=25, l=20, pitch=2, $fa=1, $fs=1); +// Example: Diamond threading where both left-handed and right-handed nuts travel (in the same direction) on the threaded rod: +// $slop = 0.075; +// d = 3/8*INCH; +// pitch = 1/16*INCH; +// starts=3; +// xdistribute(19){ +// intersection(){ +// threaded_rod(l=40, pitch=pitch, d=d,starts=starts,anchor=BOTTOM); +// threaded_rod(l=40, pitch=pitch, d=d, left_handed=true,starts=starts,anchor=BOTTOM); +// } +// threaded_nut(od=4.5/8*INCH,id=d,h=3/8*INCH,pitch=pitch,starts=starts,anchor=BOTTOM); +// threaded_nut(od=4.5/8*INCH,id=d,h=3/8*INCH,pitch=pitch,starts=starts,left_handed=true,anchor=BOTTOM); +// } module threaded_rod( d, l, pitch, left_handed=false, - bevel,bevel1,bevel2, + bevel,bevel1,bevel2,starts=1, internal=false, d1, d2, higbee, higbee1, higbee2, @@ -82,7 +96,7 @@ module threaded_rod( generic_threaded_rod( d=basic ? d : d[2], d1=d1, d2=d2, l=l, pitch=pitch, - profile=profile, + profile=profile,starts=starts, left_handed=left_handed, bevel=bevel,bevel1=bevel1,bevel2=bevel2, internal=internal, @@ -106,6 +120,7 @@ module threaded_rod( // h = height/thickness of nut. // pitch = Length between threads. // --- +// starts = The number of lead starts. Default: 1 // left_handed = if true, create left-handed threads. Default = false // bevel = if true, bevel the thread ends. Default: false // bevel1 = if true bevel the bottom end. @@ -119,7 +134,7 @@ module threaded_rod( // threaded_nut(od=16, id=8, h=8, pitch=1.25, left_handed=true, bevel=true, $slop=0.1, $fa=1, $fs=1); module threaded_nut( od, id, h, - pitch, left_handed=false, bevel, bevel1, bevel2, + pitch, starts=1, left_handed=false, bevel, bevel1, bevel2, anchor, spin, orient ) { depth = pitch * cos(30) * 5/8; @@ -134,7 +149,7 @@ module threaded_nut( generic_threaded_nut( od=od, id=id, h=h, pitch=pitch, - profile=profile, + profile=profile,starts=starts, left_handed=left_handed, bevel=bevel,bevel1=bevel1,bevel2=bevel2, anchor=anchor, spin=spin, diff --git a/vnf.scad b/vnf.scad index db9ca92..45cb54d 100644 --- a/vnf.scad +++ b/vnf.scad @@ -328,59 +328,26 @@ function vnf_merge(vnfs, cleanup=false, eps=EPSILON) = [nverts, nfaces]; - -// Function: vnf_add_face() +// Function: vnf_from_polygons() // Usage: -// vnf_add_face(vnf, pts); +// vnf = vnf_from_polygons(polygons); // Description: -// Given a VNF structure and a list of face vertex points, adds the face to the VNF structure. -// Returns the modified VNF structure `[VERTICES, FACES]`. It is up to the caller to make -// sure that the points are in the correct order to make the face normal point outwards. -// Arguments: -// vnf = The VNF structure to add a face to. -// pts = The vertex points for the face. -function vnf_add_face(vnf=EMPTY_VNF, pts) = - assert(is_vnf(vnf)) - assert(is_path(pts)) - let( - res = set_union(vnf[0], pts, get_indices=true), - face = deduplicate(res[0], closed=true) - ) [ - res[1], - concat(vnf[1], len(face)>2? [face] : []) - ]; - - - -// Function: vnf_add_faces() -// Usage: -// vnf_add_faces(vnf, faces); -// Description: -// Given a VNF structure and a list of faces, where each face is given as a list of vertex points, -// adds the faces to the VNF structure. Returns the modified VNF structure `[VERTICES, FACES]`. +// Given a list of 3d polygons, produces a VNF containing those polygons. // It is up to the caller to make sure that the points are in the correct order to make the face -// normals point outwards. +// normals point outwards. No checking for duplicate vertices is done. If you want to +// remove duplicate vertices use vnf_merge with the cleanup option. // Arguments: -// vnf = The VNF structure to add a face to. -// faces = The list of faces, where each face is given as a list of vertex points. -function vnf_add_faces(vnf=EMPTY_VNF, faces) = - assert(is_vnf(vnf)) - assert(is_list(faces)) - let( - res = set_union(vnf[0], flatten(faces), get_indices=true), - idxs = res[0], - nverts = res[1], - offs = cumsum([0, for (face=faces) len(face)]), - ifaces = [ - for (i=idx(faces)) [ - for (j=idx(faces[i])) - idxs[offs[i]+j] - ] - ] - ) [ - nverts, - concat(vnf[1],ifaces) - ]; +// polygons = The list of 3d polygons to turn into a VNF +function vnf_from_polygons(polygons) = + assert(is_list(polygons) && is_path(polygons[0]),"Input should be a list of polygons") + let( + offs = cumsum([0, for(p=polygons) len(p)]), + faces = [for(i=idx(polygons)) + [for (j=idx(polygons[i])) offs[i]+j] + ] + ) + [flatten(polygons), faces]; + // Section: VNF Testing and Access @@ -486,33 +453,34 @@ module vnf_polyhedron(vnf, convexity=2, extent=true, cp=[0,0,0], anchor="origin" // Usage: // vnf_wireframe(vnf, ); // Description: -// Given a VNF, creates a wire frame ball-and-stick model of the polyhedron with a cylinder for each edge and a sphere at each vertex. +// Given a VNF, creates a wire frame ball-and-stick model of the polyhedron with a cylinder for +// each edge and a sphere at each vertex. The width parameter specifies the width of the sticks +// that form the wire frame. // Arguments: // vnf = A vnf structure -// r|d = radius or diameter of the cylinders forming the wire frame. Default: r=1 +// width = width of the cylinders forming the wire frame. Default: 1 // Example: // $fn=32; // ball = sphere(r=20, $fn=6); -// vnf_wireframe(ball,d=1); +// vnf_wireframe(ball,width=1); // Example: -// include -// $fn=32; -// cube_oct = regular_polyhedron_info("vnf", name="cuboctahedron", or=20); -// vnf_wireframe(cube_oct); +// include +// $fn=32; +// cube_oct = regular_polyhedron_info("vnf", name="cuboctahedron", or=20); +// vnf_wireframe(cube_oct); // Example: The spheres at the vertex are imperfect at aligning with the cylinders, so especially at low $fn things look prety ugly. This is normal. -// include -// $fn=8; -// octahedron = regular_polyhedron_info("vnf", name="octahedron", or=20); -// vnf_wireframe(octahedron,r=5); -module vnf_wireframe(vnf, r, d) +// include +// $fn=8; +// octahedron = regular_polyhedron_info("vnf", name="octahedron", or=20); +// vnf_wireframe(octahedron,width=5); +module vnf_wireframe(vnf, width=1) { - r = get_radius(r=r,d=d,dflt=1); vertex = vnf[0]; edges = unique([for (face=vnf[1], i=idx(face)) sort([face[i], select(face,i+1)]) ]); - for (e=edges) extrude_from_to(vertex[e[0]],vertex[e[1]]) circle(r=r); - move_copies(vertex) sphere(r=r); + for (e=edges) extrude_from_to(vertex[e[0]],vertex[e[1]]) circle(d=width); + move_copies(vertex) sphere(d=width); } @@ -682,6 +650,109 @@ function _vnfcut(plane, vertices, vertexmap, inside, faces, vertcount, newfaces= +// Function: vnf_slice() +// Usage: +// sliced = vnf_slice(vnf, dir, cuts); +// Description: +// Slice the faces of a VNF along a specified axis direction at a given list +// of cut points. You can use this to refine the faces of a VNF before applying +// a nonlinear transformation to its vertex set. +// Example: +// include +// vnf = regular_polyhedron_info("vnf", "dodecahedron", side=12); +// vnf_polyhedron(vnf); +// sliced = vnf_slice(vnf, "X", [-6,-1,10]); +// color("red")vnf_wireframe(sliced,width=.3); +function vnf_slice(vnf,dir,cuts) = + let( + vert = vnf[0], + faces = [for(face=vnf[1]) select(vert,face)], + poly_list = _slice_3dpolygons(faces, dir, cuts) + ) + vnf_merge([vnf_from_polygons(poly_list)], cleanup=true); + + +function _split_polygon_at_x(poly, x) = + let( + xs = subindex(poly,0) + ) (min(xs) >= x || max(xs) <= x)? [poly] : + let( + poly2 = [ + for (p = pair(poly,true)) each [ + p[0], + if( + (p[0].x < x && p[1].x > x) || + (p[1].x < x && p[0].x > x) + ) let( + u = (x - p[0].x) / (p[1].x - p[0].x) + ) [ + x, // Important for later exact match tests + u*(p[1].y-p[0].y)+p[0].y + ] + ] + ], + out1 = [for (p = poly2) if(p.x <= x) p], + out2 = [for (p = poly2) if(p.x >= x) p], + out3 = [ + if (len(out1)>=3) each split_path_at_self_crossings(out1), + if (len(out2)>=3) each split_path_at_self_crossings(out2), + ], + out = [for (p=out3) if (len(p) > 2) cleanup_path(p)] + ) out; + + +function _split_2dpolygons_at_each_x(polys, xs, _i=0) = + _i>=len(xs)? polys : + _split_2dpolygons_at_each_x( + [ + for (poly = polys) + each _split_polygon_at_x(poly, xs[_i]) + ], xs, _i=_i+1 + ); + +/// Function: _slice_3dpolygons() +/// Usage: +/// splitpolys = _slice_3dpolygons(polys, dir, cuts); +/// Topics: Geometry, Polygons, Intersections +/// Description: +/// Given a list of 3D polygons, a choice of X, Y, or Z, and a cut list, `cuts`, splits all of the polygons where they cross +/// X/Y/Z at any value given in cuts. +/// Arguments: +/// polys = A list of 3D polygons to split. +/// dir_ind = slice direction, 0=X, 1=Y, or 2=Z +/// cuts = A list of scalar values for locating the cuts +function _slice_3dpolygons(polys, dir, cuts) = + assert( [for (poly=polys) if (!is_path(poly,3)) 1] == [], "Expects list of 3D paths.") + assert( is_vector(cuts), "The split list must be a vector.") + assert( in_list(dir, ["X", "Y", "Z"])) + let( + I = ident(3), + dir_ind = ord(dir)-ord("X") + ) + flatten([for (poly = polys) + let( + plane = plane_from_polygon(poly), + normal = point3d(plane), + pnormal = normal - (normal*I[dir_ind])*I[dir_ind] + ) + approx(pnormal,[0,0,0]) ? [poly] : + let ( + pind = max_index(v_abs(pnormal)), // project along this direction + otherind = 3-pind-dir_ind, // keep dir_ind and this direction + keep = [I[dir_ind], I[otherind]], // dir ind becomes the x dir + poly2d = poly*transpose(keep), // project to 2d, putting selected direction in the X position + poly_list = [for(p=_split_2dpolygons_at_each_x([poly2d], cuts)) + let( + a = p*keep, // unproject, but pind dimension data is missing + ofs = outer_product((repeat(plane[3], len(a))-a*normal)/plane[pind],I[pind]) + ) + a+ofs] // ofs computes the missing pind dimension data and adds it back in + ) + poly_list + ]); + + + function _triangulate_planar_convex_polygons(polys) = polys==[]? [] : let( @@ -708,9 +779,11 @@ function _triangulate_planar_convex_polygons(polys) = // Usage: // bentvnf = vnf_bend(vnf,r,d,[axis]); // Description: -// Given a VNF that is entirely above, or entirely below the Z=0 plane, bends the VNF around the -// Y axis, splitting up faces as necessary. Returns the bent VNF. Will error out if the VNF -// straddles the Z=0 plane, or if the bent VNF would wrap more than completely around. The 1:1 +// Bend a VNF around the X, Y or Z axis, splitting up faces as necessary. Returns the bent +// VNF. For bending around the Z axis the input VNF must not cross the Y=0 plane. For bending +// around the X or Y axes the VNF must not cross the Z=0 plane. Note that if you wrap a VNF all the way around +// it may intersect itself, which produces an invalid polyhedron. It is your responsibility to +// avoid this situation. The 1:1 // radius is where the curved length of the bent VNF matches the length of the original VNF. If the // `r` or `d` arguments are given, then they will specify the 1:1 radius or diameter. If they are // not given, then the 1:1 radius will be defined by the distance of the furthest vertex in the @@ -775,10 +848,14 @@ function _triangulate_planar_convex_polygons(polys) = // #vnf_polyhedron(vnf1); // bent1 = vnf_bend(vnf1, axis="Z"); // vnf_polyhedron([bent1]); +// Example(3D): Bending more than once around the cylinder +// $fn=32; +// vnf = apply(fwd(5)*yrot(30),cube([100,2,5],center=true)); +// bent = vnf_bend(vnf, axis="Z"); +// vnf_polyhedron(bent); function vnf_bend(vnf,r,d,axis="Z") = let( chk_axis = assert(in_list(axis,["X","Y","Z"])), - vnf = vnf_triangulate(vnf), verts = vnf[0], bounds = pointlist_bounds(verts), bmin = bounds[0], @@ -787,153 +864,34 @@ function vnf_bend(vnf,r,d,axis="Z") = max(abs(bmax.y), abs(bmin.y)) : max(abs(bmax.z), abs(bmin.z)), r = get_radius(r=r,d=d,dflt=dflt), - width = axis=="X"? (bmax.y-bmin.y) : (bmax.x - bmin.x) + extent = axis=="X" ? [bmin.y, bmax.y] : [bmin.x, bmax.x] ) - assert(width <= 2*PI*r, "Shape would wrap more than completely around the cylinder.") let( span_chk = axis=="Z"? assert(bmin.y > 0 || bmax.y < 0, "Entire shape MUST be completely in front of or behind y=0.") : assert(bmin.z > 0 || bmax.z < 0, "Entire shape MUST be completely above or below z=0."), - min_ang = 180 * bmin.x / (PI * r), - max_ang = 180 * bmax.x / (PI * r), - ang_span = max_ang-min_ang, - steps = ceil(segs(r) * ang_span/360), - step = width / steps, - bend_at = axis=="X"? [for(i = [1:1:steps-1]) i*step+bmin.y] : - [for(i = [1:1:steps-1]) i*step+bmin.x], - facepolys = [for (face=vnf[1]) select(verts,face)], - splits = axis=="X"? - _split_polygons_at_each_y(facepolys, bend_at) : - _split_polygons_at_each_x(facepolys, bend_at), - newtris = _triangulate_planar_convex_polygons(splits), - bent_faces = [ - for (tri = newtris) [ - for (p = tri) let( - a = axis=="X"? 180*p.y/(r*PI) * sign(bmax.z) : - axis=="Y"? 180*p.x/(r*PI) * sign(bmax.z) : - 180*p.x/(r*PI) * sign(bmax.y) - ) - axis=="X"? [p.x, p.z*sin(a), p.z*cos(a)] : - axis=="Y"? [p.z*sin(a), p.y, p.z*cos(a)] : - [p.y*sin(a), p.y*cos(a), p.z] - ] - ] - ) vnf_add_faces(faces=bent_faces); + steps = ceil(segs(r) * (extent[1]-extent[0])/(2*PI*r)), + step = (extent[1]-extent[0]) / steps, + bend_at = [for(i = [1:1:steps-1]) i*step+extent[0]], + slicedir = axis=="X"? "Y" : "X", // slice in y dir for X axis case, and x dir otherwise + sliced = vnf_slice(vnf, slicedir, bend_at), + coord = axis=="X" ? [0,sign(bmax.z),0] : axis=="Y" ? [sign(bmax.z),0,0] : [sign(bmax.y),0,0], + new_vert = [for(p=sliced[0]) + let(a=coord*p*180/(PI*r)) + axis=="X"? [p.x, p.z*sin(a), p.z*cos(a)] : + axis=="Y"? [p.z*sin(a), p.y, p.z*cos(a)] : + [p.y*sin(a), p.y*cos(a), p.z]] + + ) [new_vert,sliced[1]]; -function _split_polygon_at_x(poly, x) = - let( - xs = subindex(poly,0) - ) (min(xs) >= x || max(xs) <= x)? [poly] : - let( - poly2 = [ - for (p = pair(poly,true)) each [ - p[0], - if( - (p[0].x < x && p[1].x > x) || - (p[1].x < x && p[0].x > x) - ) let( - u = (x - p[0].x) / (p[1].x - p[0].x) - ) [ - x, // Important for later exact match tests - u*(p[1].y-p[0].y)+p[0].y, - u*(p[1].z-p[0].z)+p[0].z, - ] - ] - ], - out1 = [for (p = poly2) if(p.x <= x) p], - out2 = [for (p = poly2) if(p.x >= x) p], - out3 = [ - if (len(out1)>=3) each split_path_at_self_crossings(out1), - if (len(out2)>=3) each split_path_at_self_crossings(out2), - ], - out = [for (p=out3) if (len(p) > 2) cleanup_path(p)] - ) out; - - -function _split_polygon_at_y(poly, y) = - let( - ys = subindex(poly,1) - ) (min(ys) >= y || max(ys) <= y)? [poly] : - let( - poly2 = [ - for (p = pair(poly,true)) each [ - p[0], - if( - (p[0].y < y && p[1].y > y) || - (p[1].y < y && p[0].y > y) - ) let( - u = (y - p[0].y) / (p[1].y - p[0].y) - ) [ - u*(p[1].x-p[0].x)+p[0].x, - y, // Important for later exact match tests - u*(p[1].z-p[0].z)+p[0].z, - ] - ] - ], - out1 = [for (p = poly2) if(p.y <= y) p], - out2 = [for (p = poly2) if(p.y >= y) p], - out3 = [ - if (len(out1)>=3) each split_path_at_self_crossings(out1), - if (len(out2)>=3) each split_path_at_self_crossings(out2), - ], - out = [for (p=out3) if (len(p) > 2) cleanup_path(p)] - ) out; - - - -/// Function: _split_polygons_at_each_x() -// Usage: -// splitpolys = split_polygons_at_each_x(polys, xs); -/// Topics: Geometry, Polygons, Intersections -// Description: -// Given a list of 3D polygons, splits all of them wherever they cross any X value given in `xs`. -// Arguments: -// polys = A list of 3D polygons to split. -// xs = A list of scalar X values to split at. -function _split_polygons_at_each_x(polys, xs, _i=0) = - assert( [for (poly=polys) if (!is_path(poly,3)) 1] == [], "Expects list of 3D paths.") - assert( is_vector(xs), "The split value list should contain only numbers." ) - _i>=len(xs)? polys : - _split_polygons_at_each_x( - [ - for (poly = polys) - each _split_polygon_at_x(poly, xs[_i]) - ], xs, _i=_i+1 - ); - - -///Internal Function: _split_polygons_at_each_y() -// Usage: -// splitpolys = _split_polygons_at_each_y(polys, ys); -/// Topics: Geometry, Polygons, Intersections -// Description: -// Given a list of 3D polygons, splits all of them wherever they cross any Y value given in `ys`. -// Arguments: -// polys = A list of 3D polygons to split. -// ys = A list of scalar Y values to split at. -function _split_polygons_at_each_y(polys, ys, _i=0) = - assert( [for (poly=polys) if (!is_path(poly,3)) 1] == [], "Expects list of 3D paths.") - assert( is_vector(ys), "The split value list should contain only numbers." ) - _i>=len(ys)? polys : - _split_polygons_at_each_y( - [ - for (poly = polys) - each _split_polygon_at_y(poly, ys[_i]) - ], ys, _i=_i+1 - ); - - - -// Section: Debugging VNFs // Section: Debugging Polyhedrons - -// Module: debug_vertices() +// Module: _show_vertices() // Usage: -// debug_vertices(vertices, [size], [disabled=]); +// _show_vertices(vertices, [size]) // Description: // Draws all the vertices in an array, at their 3D position, numbered by their // position in the vertex array. Also draws any children of this module with @@ -941,92 +899,76 @@ function _split_polygons_at_each_y(polys, ys, _i=0) = // Arguments: // vertices = Array of point vertices. // size = The size of the text used to label the vertices. Default: 1 -// --- -// disabled = If true, don't draw numbers, and draw children without transparency. Default = false. // Example: // verts = [for (z=[-10,10], y=[-10,10], x=[-10,10]) [x,y,z]]; // faces = [[0,1,2], [1,3,2], [0,4,5], [0,5,1], [1,5,7], [1,7,3], [3,7,6], [3,6,2], [2,6,4], [2,4,0], [4,6,7], [4,7,5]]; -// debug_vertices(vertices=verts, size=2) { +// _show_vertices(vertices=verts, size=2) { // polyhedron(points=verts, faces=faces); // } -module debug_vertices(vertices, size=1, disabled=false) { - if (!disabled) { - color("blue") { - dups = vector_search(vertices, EPSILON, vertices); - for (ind = dups){ - numstr = str_join([for(i=ind) str(i)],","); - v = vertices[ind[0]]; - translate(v) { - up(size/8) zrot($vpr[2]) xrot(90) { - linear_extrude(height=size/10, center=true, convexity=10) { - text(text=numstr, size=size, halign="center"); - } - } - sphere(size/10); +module _show_vertices(vertices, size=1) { + color("blue") { + dups = vector_search(vertices, EPSILON, vertices); + for (ind = dups){ + numstr = str_join([for(i=ind) str(i)],","); + v = vertices[ind[0]]; + translate(v) { + rot($vpr) back(size/8){ + linear_extrude(height=size/10, center=true, convexity=10) { + text(text=numstr, size=size, halign="center"); + } } + sphere(size/10); } } } - if ($children > 0) { - if (!disabled) { - color([0.2, 1.0, 0, 0.5]) children(); - } else { - children(); - } - } } - -// Module: debug_faces() -// Usage: -// debug_faces(vertices, faces, [size=], [disabled=]); -// Description: -// Draws all the vertices at their 3D position, numbered in blue by their -// position in the vertex array. Each face will have their face number drawn -// in red, aligned with the center of face. All children of this module are drawn -// with transparency. -// Arguments: -// vertices = Array of point vertices. -// faces = Array of faces by vertex numbers. -// --- -// size = The size of the text used to label the faces and vertices. Default: 1 -// disabled = If true, don't draw numbers, and draw children without transparency. Default: false. -// Example(EdgesMed): -// verts = [for (z=[-10,10], y=[-10,10], x=[-10,10]) [x,y,z]]; -// faces = [[0,1,2], [1,3,2], [0,4,5], [0,5,1], [1,5,7], [1,7,3], [3,7,6], [3,6,2], [2,6,4], [2,4,0], [4,6,7], [4,7,5]]; -// debug_faces(vertices=verts, faces=faces, size=2) { -// polyhedron(points=verts, faces=faces); -// } -module debug_faces(vertices, faces, size=1, disabled=false) { - if (!disabled) { - vlen = len(vertices); - color("red") { - for (i = [0:1:len(faces)-1]) { - face = faces[i]; - if (face[0] < 0 || face[1] < 0 || face[2] < 0 || face[0] >= vlen || face[1] >= vlen || face[2] >= vlen) { - echo("BAD FACE: ", vlen=vlen, face=face); - } else { - verts = select(vertices,face); - c = mean(verts); - v0 = verts[0]; - v1 = verts[1]; - v2 = verts[2]; - dv0 = unit(v1 - v0); - dv1 = unit(v2 - v0); - nrm0 = cross(dv0, dv1); - nrm1 = UP; - axis = vector_axis(nrm0, nrm1); - ang = vector_angle(nrm0, nrm1); - theta = atan2(nrm0[1], nrm0[0]); - translate(c) { - rotate(a=180-ang, v=axis) { - zrot(theta-90) - linear_extrude(height=size/10, center=true, convexity=10) { - union() { - text(text=str(i), size=size, halign="center"); - text(text=str("_"), size=size, halign="center"); - } +/// Module: _show_faces() +/// Usage: +/// _show_faces(vertices, faces, [size=]); +/// Description: +/// Draws all the vertices at their 3D position, numbered in blue by their +/// position in the vertex array. Each face will have their face number drawn +/// in red, aligned with the center of face. All children of this module are drawn +/// with transparency. +/// Arguments: +/// vertices = Array of point vertices. +/// faces = Array of faces by vertex numbers. +/// size = The size of the text used to label the faces and vertices. Default: 1 +/// Example(EdgesMed): +/// verts = [for (z=[-10,10], y=[-10,10], x=[-10,10]) [x,y,z]]; +/// faces = [[0,1,2], [1,3,2], [0,4,5], [0,5,1], [1,5,7], [1,7,3], [3,7,6], [3,6,2], [2,6,4], [2,4,0], [4,6,7], [4,7,5]]; +/// _show_faces(vertices=verts, faces=faces, size=2) { +/// polyhedron(points=verts, faces=faces); +/// } +module _show_faces(vertices, faces, size=1) { + vlen = len(vertices); + color("red") { + for (i = [0:1:len(faces)-1]) { + face = faces[i]; + if (face[0] < 0 || face[1] < 0 || face[2] < 0 || face[0] >= vlen || face[1] >= vlen || face[2] >= vlen) { + echo("BAD FACE: ", vlen=vlen, face=face); + } else { + verts = select(vertices,face); + c = mean(verts); + v0 = verts[0]; + v1 = verts[1]; + v2 = verts[2]; + dv0 = unit(v1 - v0); + dv1 = unit(v2 - v0); + nrm0 = cross(dv0, dv1); + nrm1 = UP; + axis = vector_axis(nrm0, nrm1); + ang = vector_angle(nrm0, nrm1); + theta = atan2(nrm0[1], nrm0[0]); + translate(c) { + rotate(a=180-ang, v=axis) { + zrot(theta-90) + linear_extrude(height=size/10, center=true, convexity=10) { + union() { + text(text=str(i), size=size, halign="center"); + text(text=str("_"), size=size, halign="center"); } } } @@ -1034,44 +976,48 @@ module debug_faces(vertices, faces, size=1, disabled=false) { } } } - debug_vertices(vertices, size=size, disabled=disabled) { - children(); - } - if (!disabled) { - echo(faces=faces); - } } -// Module: debug_vnf() +// Module: vnf_debug() // Usage: -// debug_vnf(vnfs, [convexity=], [txtsize=], [disabled=]); +// vnf_debug(vnfs, [faces], [vertices], [opacity], [size], [convexity]); // Description: -// A drop-in module to replace `vnf_polyhedron()` and help debug vertices and faces. +// A drop-in module to replace `vnf_polyhedron()` to help debug vertices and faces. // Draws all the vertices at their 3D position, numbered in blue by their // position in the vertex array. Each face will have its face number drawn // in red, aligned with the center of face. All given faces are drawn with // transparency. All children of this module are drawn with transparency. // Works best with Thrown-Together preview mode, to see reversed faces. +// You can set opacity to 0 if you want to supress the display of the polyhedron faces. +// . +// The vertex numbers are shown rotated to face you. As you rotate your polyhedron you +// can rerun the preview to display them oriented for viewing from a different viewpoint. +// Topics: Polyhedra, Debugging // Arguments: // vnf = vnf to display // --- +// faces = if true display face numbers. Default: true +// vertices = if true display vertex numbers. Default: true +// opacity = Opacity of the polyhedron faces. Default: 0.5 // convexity = The max number of walls a ray can pass through the given polygon paths. -// txtsize = The size of the text used to label the faces and vertices. -// disabled = If true, act exactly like `polyhedron()`. Default = false. +// size = The size of the text used to label the faces and vertices. Default: 1 // Example(EdgesMed): // verts = [for (z=[-10,10], a=[0:120:359.9]) [10*cos(a),10*sin(a),z]]; // faces = [[0,1,2], [5,4,3], [0,3,4], [0,4,1], [1,4,5], [1,5,2], [2,5,3], [2,3,0]]; -// debug_vnf([verts,faces], txtsize=2); -module debug_vnf(vnf, convexity=6, txtsize=1, disabled=false) { - debug_faces(vertices=vnf[0], faces=vnf[1], size=txtsize, disabled=disabled) { - vnf_polyhedron(vnf, convexity=convexity); - } +// vnf_debug([verts,faces], size=2); +module vnf_debug(vnf, faces=true, vertices=true, opacity=0.5, size=1, convexity=6 ) { + no_children($children); + if (faces) + _show_faces(vertices=vnf[0], faces=vnf[1], size=size); + if (vertices) + _show_vertices(vertices=vnf[0], size=size); + color([0.2, 1.0, 0, opacity]) + vnf_polyhedron(vnf,convexity=convexity); } - // Function&Module: vnf_validate() // Usage: As Function // fails = vnf_validate(vnf); @@ -1115,7 +1061,7 @@ module debug_vnf(vnf, convexity=6, txtsize=1, disabled=false) { // c = [-50, 50, 50]; // d = [ 50, 50, 60]; // e = [ 50,-50, 50]; -// vnf = vnf_add_faces(faces=[ +// vnf = vnf_from_polygons([ // [a, b, e], [a, c, b], [a, d, c], [a, e, d], [b, c, d, e] // ]); // vnf_validate(vnf); @@ -1127,26 +1073,26 @@ module debug_vnf(vnf, convexity=6, txtsize=1, disabled=false) { // path3d(square(100,center=true),0), // path3d(square(100,center=true),100), // ], slices=0, caps=false); -// vnf = vnf_add_faces(vnf=vnf1, faces=[ +// vnf = vnf_merge([vnf1, vnf_from_polygons([ // [[-50,-50, 0], [ 50, 50, 0], [-50, 50, 0]], // [[-50,-50, 0], [ 50,-50, 0], [ 50, 50, 0]], // [[-50,-50,100], [-50, 50,100], [ 50, 50,100]], // [[-50,-50,100], [ 50,-50,100], [ 50, 50,100]], -// ]); +// ])]); // vnf_validate(vnf); // Example: T_JUNCTION Errors; Vertex is Mid-Edge on Another Face. // vnf1 = skin([ // path3d(square(100,center=true),0), // path3d(square(100,center=true),100), // ], slices=0, caps=false); -// vnf = vnf_add_faces(vnf=vnf1, faces=[ +// vnf = vnf_merge([vnf1, vnf_from_polygons([ // [[-50,-50,0], [50,50,0], [-50,50,0]], // [[-50,-50,0], [50,-50,0], [50,50,0]], // [[-50,-50,100], [-50,50,100], [0,50,100]], // [[-50,-50,100], [0,50,100], [0,-50,100]], // [[0,-50,100], [0,50,100], [50,50,100]], // [[0,-50,100], [50,50,100], [50,-50,100]], -// ]); +// ])]); // vnf_validate(vnf); // Example: FACE_ISECT Errors; Faces Intersect // vnf = vnf_merge([