From f035df980a56128eaf4c05ef430b47f68e9dfdbd Mon Sep 17 00:00:00 2001 From: Adrian Mariano Date: Tue, 1 Nov 2022 23:36:52 -0400 Subject: [PATCH 1/4] bug fixes --- lists.scad | 15 ++++++--------- vnf.scad | 2 +- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/lists.scad b/lists.scad index 7b8d94ca..e215cebb 100644 --- a/lists.scad +++ b/lists.scad @@ -336,20 +336,17 @@ function list_tail(list, from=1) = // Topics: List Handling // See Also: list_bset() // Description: -// Returns the items in `list` whose matching element in `index` is true. +// Returns the items in `list` whose matching element in `index` evaluates as true. // Arguments: -// list = Initial list to extract items from. -// index = List of booleans. +// list = Initial list (or string) to extract items from. +// index = List of values that will be evaluated as boolean, same length as `list`. // Example: // a = bselect([3,4,5,6,7], [false,true,true,false,true]); // Returns: [4,5,7] function bselect(list,index) = - assert(is_list(list)||is_string(list), "Improper list." ) - assert(is_list(index) && len(index)>=len(list) , "Improper index list." ) + assert(is_list(list)||is_string(list), "First argument must be a list or string." ) + assert(is_list(index) && len(index)==len(list) , "Second argument must have same length as the first." ) is_string(list)? str_join(bselect( [for (x=list) x], index)) : - [for(i=[0:len(list)-1]) if (index[i]) list[i]]; - - - + [for(i=idx(list)) if (index[i]) list[i]]; // Section: List Construction diff --git a/vnf.scad b/vnf.scad index 91c478ec..9989f5e7 100644 --- a/vnf.scad +++ b/vnf.scad @@ -1049,7 +1049,7 @@ function vnf_halfspace(plane, vnf, closed=true) = assert(_valid_plane(plane), "Invalid plane") assert(is_vnf(vnf), "Invalid vnf") let( - inside = [for(x=vnf[0]) plane*[each x,-1] >= 0 ? 1 : 0], + inside = [for(x=vnf[0]) plane*[each x,-1] >= -EPSILON ? 1 : 0], vertexmap = [0,each cumsum(inside)], faces_edges_vertices = _vnfcut(plane, vnf[0],vertexmap,inside, vnf[1], last(vertexmap)), newvert = concat(bselect(vnf[0],inside), faces_edges_vertices[2]) From cc556dab8ebc37a114343399500bc383798b6c3e Mon Sep 17 00:00:00 2001 From: Adrian Mariano Date: Wed, 2 Nov 2022 22:04:18 -0400 Subject: [PATCH 2/4] doc tweak --- transforms.scad | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/transforms.scad b/transforms.scad index b2e81713..91e5a731 100644 --- a/transforms.scad +++ b/transforms.scad @@ -1372,6 +1372,11 @@ function is_2d_transform(t) = // z-parameters are zero, except we allow t[2][ // . // Any other combination of matrices will produce an error, including acting with a 2D matrix (3x3) on 3D data. // The output of apply is always the same dimension as the input—projections are not supported. +// . +// Note that a matrix with a negative determinant such as any mirror reflection flips the orientation of faces. +// If the transform matrix is square then apply() checks the determinant and if it is negative, apply() reverses the face order so that +// the transformed VNF has faces with the same winding direction as the original VNF. This adjustment applies +// only to VNFs, not to beziers or point lists. // Arguments: // transform = The 2D (3x3 or 2x3) or 3D (4x4 or 3x4) transformation matrix to apply. // points = The point, point list, bezier patch, or VNF to apply the transformation to. @@ -1395,7 +1400,7 @@ function is_2d_transform(t) = // z-parameters are zero, except we allow t[2][ // stroke(path2,closed=true); function apply(transform,points) = points==[] ? [] - : is_vector(points) ? _apply(transform, [points])[0] // point + : is_vector(points) ? _apply(transform, [points])[0] // point : is_vnf(points) ? // vnf let( newvnf = [_apply(transform, points[0]), points[1]], From c1149a55e65ce92c99aea5c73970fa42450f2d90 Mon Sep 17 00:00:00 2001 From: Adrian Mariano Date: Thu, 3 Nov 2022 17:33:32 -0400 Subject: [PATCH 3/4] improve vnf_halfspace() docs and add boundary option --- vnf.scad | 70 ++++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 60 insertions(+), 10 deletions(-) diff --git a/vnf.scad b/vnf.scad index 9989f5e7..ac3e98b7 100644 --- a/vnf.scad +++ b/vnf.scad @@ -1009,16 +1009,26 @@ function _vnf_centroid(vnf,eps=EPSILON) = // Function: vnf_halfspace() // Usage: -// newvnf = vnf_halfspace(plane, vnf, [closed]); +// newvnf = vnf_halfspace(plane, vnf, [closed], [boundary]); // Description: // Returns the intersection of the vnf with a half space. The half space is defined by // plane = [A,B,C,D], taking the side where the normal [A,B,C] points: Ax+By+Cz≥D. // If closed is set to false then the cut face is not included in the vnf. This could -// allow further extension of the vnf by merging with other vnfs. +// allow further extension of the vnf by join with other vnfs using {{vnf_join()}}. +// Note that if your given VNF has holes (missing faces) or is not a complete polyhedron +// then closed=true is may produce invalid results when it tries to construct closing faces +// on the cut plane. Set closed=false for such inputs. +// . +// If you set boundary to true then the return will be the pair [vnf,boundary] where vnf is the +// vnf as usual (with closed=false) and boundary is a list giving each connected component of the cut +// boundary surface. Each entry in boundary is a list of index values that index into the vnf vertex list (vnf[0]). +// This makes it possible to construct mating shapes, e.g. with {{skin()}} or {{vnf_vertex_array()}} that +// can be combined using {{vnf_join()}} to make a valid polyhedron. // Arguments: // plane = plane defining the boundary of the half space // vnf = vnf to cut -// closed = if false do not return include cut face(s). Default: true +// closed = if false do not return the cut face(s) in the returned VNF. Default: true +// boundary = if true return a pair [vnf,boundary] where boundary is a list of paths on the cut boundary indexed into the VNF vertex list. If boundary is true, then closed is set to false. Default: false // Example(3D): // vnf = cube(10,center=true); // cutvnf = vnf_halfspace([-1,1,-1,0], vnf); @@ -1045,7 +1055,48 @@ function _vnf_centroid(vnf,eps=EPSILON) = // knot=path_sweep(ushape, knot_path, closed=true, method="incremental"); // cut_knot = vnf_halfspace([1,0,0,0], knot); // vnf_polyhedron(cut_knot); -function vnf_halfspace(plane, vnf, closed=true) = +// Example(VPR=[80,0,15]): Cut a sphere with an arbitrary plane +// vnf1=sphere(r=50, style="icosa", $fn=16); +// vnf2=vnf_halfspace([.8,1,-1.5,0], vnf1); +// vnf_polyhedron(vnf2); +// Example(VPR=[80,0,15]): Cut it again, but with closed=false to leave an open boundary. +// vnf1=sphere(r=50, style="icosa", $fn=16); +// vnf2=vnf_halfspace([.8,1,-1.5,0], vnf1); +// vnf3=vnf_halfspace([0,0,-1,0], vnf2, closed=false); +// vnf_polyhedron(vnf3); +// Example(VPR=[80,0,15]): Use {vnf_join()} to combine with a mating vnf, in this case a reflection of the part we made. +// vnf1=sphere(r=50, style="icosa", $fn=16); +// vnf2=vnf_halfspace([.8,1,-1.5,0], vnf1); +// vnf3=vnf_halfspace([0,0,-1,0], vnf2, closed=false); +// vnf4=vnf_join([vnf3, zflip(vnf3,1)]); +// vnf_polyhedron(vnf4); +// Example: When the input VNF is a surface with a boundary, if you use the default setting closed=true, then vnf_halfspace() tries to construct closing faces from the edges created by the cut. These faces may be invalid, for example if the cut points are collinear. In this example the constructed face is a valid face. +// include +// patch=[ +// [[10,-10,0],[1,-1,0],[-1,-1,0],[-10,-10,0]], +// [[10,-10,20],[1,-1,20],[-1,-1,20],[-10,-10,20]] +// ]; +// vnf=bezier_vnf(patch); +// vnfcut = vnf_halfspace([-.8,0,-1,-14],vnf); +// vnf_polyhedron(vnfcut); +// Example: Setting closed to false eliminates this (possibly invalid) face: +// include +// patch=[ +// [[10,-10,0],[1,-1,0],[-1,-1,0],[-10,-10,0]], +// [[10,-10,20],[1,-1,20],[-1,-1,20],[-10,-10,20]] +// ]; +// vnf=bezier_vnf(patch); +// vnfcut = vnf_halfspace([-.8,0,-1,-14],vnf,closed=false); +// vnf_polyhedron(vnfcut); +// Example: If boundary=true then the return is a list with the VNF and boundary data. +// vnf = path_sweep(circle(r=4, $fn=16), +// circle(r=20, $fn=64),closed=true); +// cut_bnd = vnf_halfspace([-1,1,-4,0], vnf, boundary=true);*/ +// cutvnf = cut_bnd[0]; +// boundary = [for(b=cut_bnd[1]) select(cutvnf[0],b)]; +// vnf_polyhedron(cutvnf); +// stroke(boundary,color="red"); +function vnf_halfspace(plane, vnf, closed=true, boundary=false) = assert(_valid_plane(plane), "Invalid plane") assert(is_vnf(vnf), "Invalid vnf") let( @@ -1054,23 +1105,22 @@ function vnf_halfspace(plane, vnf, closed=true) = faces_edges_vertices = _vnfcut(plane, vnf[0],vertexmap,inside, vnf[1], last(vertexmap)), newvert = concat(bselect(vnf[0],inside), faces_edges_vertices[2]) ) - closed==false ? [newvert, faces_edges_vertices[0]] : - let( + closed==false && !boundary ? [newvert, faces_edges_vertices[0]] + : let( allpaths = _assemble_paths(newvert, faces_edges_vertices[1]), newpaths = [for(p=allpaths) if (len(p)>=3) p else assert(approx(p[0],p[1]),"Orphan edge found when assembling cut edges.") ] ) - len(newpaths)<=1 ? [newvert, concat(faces_edges_vertices[0], newpaths)] - : - let( + boundary ? [[newvert, faces_edges_vertices[0]], newpaths] + : len(newpaths)<=1 ? [newvert, concat(faces_edges_vertices[0], newpaths)] + : let( M = project_plane(plane), faceregion = [for(path=newpaths) path2d(apply(M,select(newvert,path)))], facevnf = vnf_from_region(faceregion,transform=rot_inverse(M),reverse=true) ) vnf_join([[newvert, faces_edges_vertices[0]], facevnf]); - function _assemble_paths(vertices, edges, paths=[],i=0) = i==len(edges) ? paths : norm(vertices[edges[i][0]]-vertices[edges[i][1]]) Date: Fri, 4 Nov 2022 17:05:56 -0400 Subject: [PATCH 4/4] fix double comma --- shapes3d.scad | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shapes3d.scad b/shapes3d.scad index 37c5322c..36f70469 100644 --- a/shapes3d.scad +++ b/shapes3d.scad @@ -2559,7 +2559,7 @@ module teardrop(h, r, ang=45, cap_h, r1, r2, d, d1, d2, cap_h1, cap_h2, l, lengt ]; attachable(anchor,spin,orient, r1=r1, r2=r2, l=length, axis=BACK, anchors=anchors) { - vnf_polyhedron(teardrop(ang=ang,cap_h=cap_h,r1=r1,r2=r2,,cap_h1=cap_h1,cap_h2=cap_h2, + vnf_polyhedron(teardrop(ang=ang,cap_h=cap_h,r1=r1,r2=r2,cap_h1=cap_h1,cap_h2=cap_h2, length=length, chamfer1=chamfer1,chamfer2=chamfer2,chamfer=chamfer)); children(); }