diff --git a/beziers.scad b/beziers.scad index 4cd87cb..53ba02e 100644 --- a/beziers.scad +++ b/beziers.scad @@ -1446,6 +1446,56 @@ function bezier_patch_normals(patch, u, v) = : column(bezier_patch_normals(patch,u,force_list(v)),0); +// Function: bezier_sheet() +// Topics: Bezier Patches +// See Also: bezier_patch_normals(), vnf_sheet() +// Description: +// Constructs a thin sheet from a bezier patch by offsetting the given patch along the normal vectors +// to the patch surface. The thickness value must be small enough so that no points cross each other +// when the offset is computed, because that results in invalid geometry and will give rendering errors. +// Rendering errors may not manifest until you add other objects to your model. +// **It is your responsibility to avoid invalid geometry!** +// . +// The normals are computed using {{bezier_patch_normals()}} and if they are degenerate then +// the computation will fail or produce incorrect results. See {{bezier_patch_normals()}} for +// examples of various ways the normals can be degenerate. +// . +// When thickness is positive, the given bezier patch is extended towards its "inside", which is the +// side that appears purple in the "thrown together" view. You can extend the patch in the other direction +// using a negative thickness value. +// Arguments: +// patch = bezier patch to process +// thickness = amount to offset; can be positive or negative +// --- +// splinesteps = Number of segments on the border edges of the bezier surface. You can specify [USTEPS,VSTEPS]. Default: 16 +// style = {{vnf_vertex_array()}} style to use. Default: "default" +// Example: +// patch = [ +// // u=0,v=0 u=1,v=0 +// [[-50,-50, 0], [-16,-50, 20], [ 16,-50, -20], [50,-50, 0]], +// [[-50,-16, 20], [-16,-16, 20], [ 16,-16, -20], [50,-16, 20]], +// [[-50, 16, 20], [-16, 16, -20], [ 16, 16, 20], [50, 16, 20]], +// [[-50, 50, 0], [-16, 50, -20], [ 16, 50, 20], [50, 50, 0]], +// // u=0,v=1 u=1,v=1 +// ]; +// vnf_polyhedron(bezier_sheet(patch, 10)); +function bezier_sheet(patch, thickness, splinesteps=16, style="default") = + assert(is_bezier_patch(patch)) + assert(all_nonzero([thickness]), "thickness must be nonzero") + let( + splinesteps = force_list(splinesteps,2), + uvals = lerpn(0,1,splinesteps.x+1), + vvals = lerpn(1,0,splinesteps.y+1), + pts = bezier_patch_points(patch, uvals, vvals), + normals = bezier_patch_normals(patch, uvals, vvals), + dummy=assert(is_matrix(flatten(normals)),"Bezier patch has degenerate normals"), + offset = pts + thickness*normals, + allpoints = [for(i=idx(pts)) concat(pts[i], reverse(offset[i]))], + vnf = vnf_vertex_array(allpoints, col_wrap=true, caps=true, style=style) + ) + thickness<0 ? vnf_reverse_faces(vnf) : vnf; + + // Section: Debugging Beziers diff --git a/gears.scad b/gears.scad index 9a8829e..907130e 100644 --- a/gears.scad +++ b/gears.scad @@ -2422,8 +2422,8 @@ module crown_gear( // xrot(ang) // bevel_gear(mod=3,15,35,ang,spiral=0,right_handed=true,anchor="apex") // cyl(h=65,d=3,$fn=16,anchor=BOT); -// Example(NoAxes,VPT=[-6.28233,3.60349,15.6594],VPR=[71.1,0,52.1],VPD=213.382): Non-right angled bevel gear pair positioned in a frame, with holes cut in the frame for the shafts. -// include +// Example(NoAxes,VPT=[-6.28233,3.60349,15.6594],VPR=[71.1,0,52.1],VPD=213.382): Non-right angled bevel gear pair positioned in a frame, with holes cut in the frame for the shafts. Note that when rotating a gear to its appropriate angle, you must rotate around an axis tangent to the gear's pitch base, **not** the gear center. This is accomplished by shifting the gear by its pitch radius before applying the rotation. +// include // angle = 60; // t1=17; t2=29; mod=2; bot=4; wall=2; shaft=5; // r1 = pitch_radius(mod=mod, teeth=t1); diff --git a/vnf.scad b/vnf.scad index d67c297..2486f48 100644 --- a/vnf.scad +++ b/vnf.scad @@ -618,7 +618,7 @@ function _bridge(pt, outer,eps) = // color("gray")down(.125) // linear_extrude(height=.125)region(region); // vnf_wireframe(vnf,width=.25); -function vnf_from_region(region, transform, reverse=false) = +function vnf_from_region(region, transform, reverse=false, triangulate=true) = let ( region = [for (path = region) deduplicate(path, closed=true)], regions = region_parts(force_region(region)), @@ -636,7 +636,7 @@ function vnf_from_region(region, transform, reverse=false) = ], outvnf = vnf_join(vnfs) ) - vnf_triangulate(outvnf); + triangulate ? vnf_triangulate(outvnf) : outvnf; @@ -1616,7 +1616,214 @@ module vnf_hull(vnf, fast=false) if (is_vnf(vnf)) hull()vnf_polyhedron(vnf); else hull_points(vnf, fast); } - + + + +function _sort_pairs0(arr) = + len(arr)<=1 ? arr : + let( + pivot = arr[floor(len(arr)/2)][0], + lesser = [ for (y = arr) if (y[0].x < pivot.x || (y[0].x==pivot.x && y[0].y pivot.x || (y[0].x==pivot.x && y[0].y>pivot.y)) y ] + ) + concat( _sort_pairs0(lesser), equal, _sort_pairs0(greater) ); + + +// Function: vnf_boundary() +// Synopsis: Returns the boundary of a VNF as an list of paths +// SynTags: VNF +// Topics: VNF Manipulation +// See Also: vnf_halfspace(), vnf_merge_points() +// Usage: +// boundary = vnf_boundary(vnf, [merge=], [idx=]); +// Description: +// Returns the boundary of a VNF as a list of paths. **The input VNF must not contain duplicate points.** By default, vnf_boundary() calls {{vnf_merge_points()}} +// to remove duplicate points. Note, however, that this operation can be slow. If you are **certain** there are no duplicate points you can +// set `merge=false` to disable the automatic point merge and save time. The result of running on a VNF with duplicate points is likely to +// be incorrect or invalid; it may produce obscure errors. +// . +// The output will be a list of closed 3D paths. If the VNF has no boundary then the output is `[]`. The boundary path(s) are +// traversed in the same direction as the edges in the original VNF. +// . +// It is sometimes desirable to have the boundary available as an index list into the VNF vertex list. However, merging the points in the VNF changes the +// VNF vertex point list. If you set `merge=false` you can also set `idx=true` to get an index list. As noted above, you must be certain +// that your in put VNF has no duplicate vertices, perhaps by running {{vnf_merge_points()}} yourself on it. With `idx=true` +// the output will be indices into the VNF vertex list, which enables you to associate the vertices on the boundary path with the original VNF. +// Arguments: +// vnf = input vnf +// --- +// merge = set to false to suppress the automatic invocation of {{vnf_merge_points()}}. Default: true +// idx = if true, return indices into VNF vertices instead of actual 3D points. Must set `merge=false` to enable this. Default: false +// Example(NoAxes,VPT=[7.06325,-20.8414,20.1803],VPD=292.705,VPR=[55,0,25.7]): In this example we know that the bezier patch VNF has no duplicate vertices, so we do not need to run {{vnf_merge_points()}}. +// include +// patch = [ +// // u=0,v=0 u=1,v=0 +// [[-50,-50, 0], [-16,-50, 20], [ 16,-50, -20], [50,-50, 0]], +// [[-50,-16, 20], [-16,-16, 20], [ 16,-16, -20], [50,-16, 20]], +// [[-50, 16, 20], [-16, 16, -20], [ 16, 16, 20], [50, 16, 20]], +// [[-50, 50, 0], [-16, 50, -20], [ 16, 50, 20], [50, 50, 0]], +// // u=0,v=1 u=1,v=1 +// ]; +// bezvnf = bezier_vnf(patch); +// boundary = vnf_boundary(bezvnf); +// vnf_polyhedron(bezvnf); +// stroke(boundary,color="green"); +// Example(NoAxes,VPT=[-11.1252,-19.7333,8.39927],VPD=82.6686,VPR=[71.8,0,335.3]): An example with two path components on the boundary. The output from {{vnf_halfspace()}} can contain duplicate vertices, so we must invoke {{vnf_merge_points()}}. +// vnf = torus(id=20,od=40,$fn=28); +// cutvnf=vnf_halfspace([0,1,0,0], +// vnf_halfspace([-1,.5,-2.5,-12], vnf, closed=false), +// closed=false); +// vnf_polyhedron(cutvnf); +// boundary = vnf_boundary(vnf_merge_points(cutvnf)); +// stroke(boundary,color="green"); +function vnf_boundary(vnf,merge=true,idx=false) = + assert(!idx || !merge, "Cannot request indices unless marge=false and VNF contains no duplicate vertices") + let( + vnf = merge ? vnf_merge_points(vnf) : vnf, + edgelist= [ for(face=vnf[1], edge=pair(face,wrap=true)) + [edge.x +// vnf = regular_polyhedron_info("vnf","pentagonal icositetrahedron",d=25); +// xdistribute(spacing=300){ +// scale(11)vnf_polyhedron(vnf); +// vnf_polyhedron(vnf_small_offset(vnf,125)); +// } +function vnf_small_offset(vnf, delta, merge=true) = + let( + vnf = merge ? vnf_merge_points(vnf) : vnf, + vertices = vnf[0], + faces = vnf[1], + vert_faces = group_data( + [for (i = idx(faces), vert = faces[i]) vert], + [for (i = idx(faces), vert = faces[i]) i] + ), + normals = [for(face=faces) polygon_normal(select(vertices,face))], // Normals for each face + offset = [for(vertex=idx(vertices)) + let( + vfaces = vert_faces[vertex], // Faces that surround this vertex + adjacent_normals = select(normals,vfaces), + angles = [for(faceind=vfaces) + let( + thisface = faces[faceind], + vind = search(vertex,thisface)[0] + ) + vector_angle(select(vertices, select(thisface,vind-1,vind+1))) + ] + ) + vertices[vertex] +unit(angles*adjacent_normals)*delta + ] + ) + [offset,faces]; + +// Function: vnf_sheet() +// Synopsis: Extends a VNF into a thin sheet by forming a small offset +// SynTags: VNF +// Topics: VNF Manipulation +// See Also: vnf_small_offset(), vnf_boundary(), vnf_merge_points() +// Usage: +// newvnf = vnf_sheet(vnf, thickness, [style=], [merge=]); +// Description: +// Constructs a thin sheet from a vnf by offsetting the vnf along the normal vectors estimated at +// each vertex by averaging the normals of the adjacent faces. This is done using {{vnf_small_offset()}. +// The thickness value must be small enough so that no points cross each other +// when the offset is computed, because that results in invalid geometry and will give rendering errors. +// Rendering errors may not manifest until you add other objects to your model. +// **It is your responsibility to avoid invalid geometry!** +// . +// Once the offset to the original VNF is computed the original and offset VNF are connected by filling +// in the boundary strip(s) between them +// . +// When thickness is positive, the given bezier patch is extended towards its "inside", which is the +// side that appears purple in the "thrown together" view. Note that this is the opposite direction +// of {{vnf_small_offset()}}. Extending toward the inside means that your original VNF remains unchanged +// in the output. You can extend the patch in the other direction +// using a negative thickness value. When you extend to the outside with a negative thickness, your VNF needs to have all +// of its faces reversed to produce a valid polyhedron, so your original VNF is reversed in the output. +// . +// **The input VNF must not contain duplicate points.** By default, vnf_sheet() calls {{vnf_merge_points()}} +// to remove duplicate points. Note, however, that this operation can be slow. If you are **certain** there are no duplicate points you can +// set `merge=false` to disable the automatic point merge and save time. The result of running on a VNF with duplicate points is likely to +// be incorrect or invalid, or it may result in cryptic errors. +// Arguments: +// vnf = vnf to process +// thickness = thickness of sheet to produce; can be positive or negative +// --- +// style = {{vnf_vertex_array()}} style to use. Default: "default" +// merge = if false then do not run {{vnf_merge_points()}}. Default: true +// Example: +// pts = [for(x=[30:5:180]) [for(y=[-6:0.5:6]) [7*y,x, sin(x)*y^2]]]; +// vnf=vnf_vertex_array(pts); +// vnf_polyhedron(vnf_sheet(vnf,-10)); +// Example: This example has multiple holes +// pts = [for(x=[-10:2:10]) [ for(y=[-10:2:10]) [x,1.4*y,(-abs(x)^3+y^3)/250]]]; +// vnf = vnf_vertex_array(pts); +// newface = list_remove(vnf[1], [43,42,63,88,108,109,135,134,129,155,156,164,165]); +// newvnf = [vnf[0],newface]; +// vnf_polyhedron(vnf_sheet(newvnf,2)); +// Example: When applied to a sphere the sheet is constructed inward, so the object appears unchanged, but cutting it in half reveals that we have changed the sphere into a shell. +// vnf = sphere(d=100, $fn=28); +// left_half() +// vnf_polyhedron(vnf_sheet(vnf,15)); + +function vnf_sheet(vnf, thickness, style="default", merge=true) = + let( + vnf = merge ? vnf_merge_points(vnf) : vnf, + offset = vnf_small_offset(vnf, -thickness, merge=false), + boundary = vnf_boundary(vnf,merge=false,idx=true), + newvnf = vnf_join([vnf, + vnf_reverse_faces(offset), + for(p=boundary) vnf_vertex_array([select(offset[0],p),select(vnf[0],p)],col_wrap=true,style=style) + ]) + ) + thickness < 0 ? vnf_reverse_faces(newvnf) : newvnf; + + // Section: Debugging Polyhedrons