Merge pull request #699 from adrianVmariano/master

vnf tweaks
This commit is contained in:
Revar Desmera 2021-10-17 22:08:59 -07:00 committed by GitHub
commit 6f1ac73d59
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 121 additions and 116 deletions

View file

@ -1124,7 +1124,7 @@ function is_patch(x) =
// Function: bezier_patch()
// Usage:
// vnf = bezier_patch(patch, [splinesteps], [vnf=], [style=]);
// vnf = bezier_patch(patch, [splinesteps], [style=]);
// Topics: Bezier Patches
// See Also: bezier_points(), bezier_curve(), bezier_path(), bezier_patch_points(), bezier_triangle_point()
// Description:
@ -1137,7 +1137,6 @@ function is_patch(x) =
// patch = The rectangular or triangular array of endpoints and control points for this bezier patch.
// splinesteps = Number of steps to divide each bezier segment into. For rectangular patches you can specify [XSTEPS,YSTEPS]. Default: 16
// ---
// vnf = Vertices'n'Faces [VNF structure](vnf.scad) to add new vertices and faces to. Default: empty VNF
// style = The style of subdividing the quads into faces. Valid options are "default", "alt", and "quincunx".
// Example(3D):
// patch = [
@ -1158,7 +1157,7 @@ function is_patch(x) =
// ];
// vnf = bezier_patch(tri, splinesteps=16);
// vnf_polyhedron(vnf);
// Example(3D,FlatSpin,VPD=444): Chaining Patches
// Example(3D,FlatSpin,VPD=444): Merging multiple patches
// patch = [
// // u=0,v=0 u=1,v=0
// [[0, 0,0], [33, 0, 0], [67, 0, 0], [100, 0,0]],
@ -1167,13 +1166,15 @@ function is_patch(x) =
// [[0,100,0], [33,100, 0], [67,100, 0], [100,100,0]],
// // u=0,v=1 u=1,v=1
// ];
// vnf1 = bezier_patch(translate(p=patch,[-50,-50,50]));
// vnf2 = bezier_patch(vnf=vnf1, rot(a=[90,0,0],p=translate(p=patch,[-50,-50,50])));
// vnf3 = bezier_patch(vnf=vnf2, rot(a=[-90,0,0],p=translate(p=patch,[-50,-50,50])));
// vnf4 = bezier_patch(vnf=vnf3, rot(a=[180,0,0],p=translate(p=patch,[-50,-50,50])));
// vnf5 = bezier_patch(vnf=vnf4, rot(a=[0,90,0],p=translate(p=patch,[-50,-50,50])));
// vnf6 = bezier_patch(vnf=vnf5, rot(a=[0,-90,0],p=translate(p=patch,[-50,-50,50])));
// vnf_polyhedron(vnf6);
// tpatch = translate([-50,-50,50], patch);
// vnf = vnf_merge([
// bezier_patch(tpatch),
// bezier_patch(xrot(90, tpatch)),
// bezier_patch(xrot(-90, tpatch)),
// bezier_patch(xrot(180, tpatch)),
// bezier_patch(yrot(90, tpatch)),
// bezier_patch(yrot(-90, tpatch))]);
// vnf_polyhedron(vnf);
// Example(3D): Connecting Patches with Asymmetric Splinesteps
// steps = 8;
// edge_patch = [
@ -1223,10 +1224,10 @@ function is_patch(x) =
// )
// ];
// vnf_polyhedron(concat(edges,corners,faces));
function bezier_patch(patch, splinesteps=16, vnf=EMPTY_VNF, style="default") =
function bezier_patch(patch, splinesteps=16, style="default") =
assert(is_num(splinesteps) || is_vector(splinesteps,2))
assert(all_positive(splinesteps))
is_tripatch(patch)? _bezier_triangle(patch, splinesteps=splinesteps, vnf=vnf) :
is_tripatch(patch)? _bezier_triangle(patch, splinesteps=splinesteps) :
let(
splinesteps = is_list(splinesteps) ? splinesteps : [splinesteps,splinesteps],
uvals = [
@ -1238,7 +1239,7 @@ function bezier_patch(patch, splinesteps=16, vnf=EMPTY_VNF, style="default") =
1-step/splinesteps.y
],
pts = bezier_patch_points(patch, uvals, vvals),
vnf = vnf_vertex_array(pts, style=style, vnf=vnf, reverse=false)
vnf = vnf_vertex_array(pts, style=style, reverse=false)
) vnf;
@ -1431,7 +1432,7 @@ function bezier_patch_degenerate(patch, splinesteps=16, reverse=false, return_ed
function _tri_count(n) = (n*(1+n))/2;
function _bezier_triangle(tri, splinesteps=16, vnf=EMPTY_VNF) =
function _bezier_triangle(tri, splinesteps=16) =
assert(is_num(splinesteps))
let(
pts = [
@ -1456,7 +1457,7 @@ function _bezier_triangle(tri, splinesteps=16, vnf=EMPTY_VNF) =
)
) for (face=allfaces) face
]
) vnf_merge([vnf,[pts, faces]]);
) [pts, faces];
@ -1507,7 +1508,7 @@ function patch_reverse(patch) =
// Function: bezier_surface()
// Usage:
// vnf = bezier_surface(patches, [splinesteps], [vnf=], [style=]);
// vnf = bezier_surface(patches, [splinesteps], [style]);
// Topics: Bezier Patches
// See Also: bezier_patch_points(), bezier_patch_flat()
// Description:
@ -1520,8 +1521,6 @@ function patch_reverse(patch) =
// Arguments:
// patches = A list of triangular and/or rectangular bezier patches.
// splinesteps = Number of steps to divide each bezier segment into. Default: 16
// ---
// vnf = Vertices'n'Faces [VNF structure](vnf.scad) to add new vertices and faces to. Default: empty VNF
// style = The style of subdividing the quads into faces. Valid options are "default", "alt", and "quincunx".
// Example(3D):
// patch1 = [
@ -1538,13 +1537,8 @@ function patch_reverse(patch) =
// ];
// vnf = bezier_surface(patches=[patch1, patch2], splinesteps=16);
// polyhedron(points=vnf[0], faces=vnf[1]);
function bezier_surface(patches=[], splinesteps=16, vnf=EMPTY_VNF, style="default", i=0) =
let(
vnf = (i >= len(patches))? vnf :
bezier_patch(patches[i], splinesteps=splinesteps, vnf=vnf, style=style)
) (i >= len(patches))? vnf :
bezier_surface(patches=patches, splinesteps=splinesteps, vnf=vnf, style=style, i=i+1);
function bezier_surface(patches=[], splinesteps=16, style="default") =
vnf_merge([for(patch=patches) bezier_patch(patch, splinesteps=splinesteps, style=style)]);
// Module: trace_bezier_patches()

View file

@ -11,7 +11,7 @@ module test_skin() {
assert_equal(vnf1, [[[-100,-100,0],[0,100,0],[0,100,0],[100,-100,0],[-100,-100,100],[-100,100,100],[100,100,100],[100,-100,100]],[[0,5,4],[0,1,5],[5,2,6],[2,3,6],[6,3,7],[3,0,7],[7,0,4]]]);
vnf2 = skin(profiles, slices=0, caps=true, method="distance");
assert_equal(vnf2,[[[-100,-100,0],[0,100,0],[0,100,0],[100,-100,0],[-100,-100,100],[-100,100,100],[100,100,100],[100,-100,100]],[[0,5,4],[0,1,5],[5,2,6],[2,3,6],[6,3,7],[3,0,7],[7,0,4],[3,2,1,0],[4,5,6,7]]]);
assert_equal(vnf2,[[[-100,-100,0],[0,100,0],[0,100,0],[100,-100,0],[-100,-100,100],[-100,100,100],[100,100,100],[100,-100,100]],[[3,2,1,0],[4,5,6,7],[0,5,4],[0,1,5],[5,2,6],[2,3,6],[6,3,7],[3,0,7],[7,0,4]]]);
}
test_skin();

View file

@ -98,9 +98,9 @@ module test_vnf_vertex_array() {
points=[for (h=[0:100:100]) [[100,-50,h],[-100,-50,h],[0,100,h]]],
col_wrap=true, caps=true, style="quincunx"
);
assert(vnf1 == [[[100,-50,0],[-100,-50,0],[0,100,0],[100,-50,100],[-100,-50,100],[0,100,100]],[[0,4,3],[0,1,4],[1,5,4],[1,2,5],[2,3,5],[2,0,3],[2,1,0],[3,4,5]]]);
assert(vnf2 == [[[100,-50,0],[-100,-50,0],[0,100,0],[100,-50,100],[-100,-50,100],[0,100,100]],[[0,1,3],[3,1,4],[1,2,4],[4,2,5],[2,0,5],[5,0,3],[2,1,0],[3,4,5]]]);
assert(vnf3 == [[[100,-50,0],[-100,-50,0],[0,100,0],[100,-50,100],[-100,-50,100],[0,100,100],[0,-50,50],[-50,25,50],[50,25,50]],[[0,6,3],[3,6,4],[4,6,1],[1,6,0],[1,7,4],[4,7,5],[5,7,2],[2,7,1],[2,8,5],[5,8,3],[3,8,0],[0,8,2],[2,1,0],[3,4,5]]]);
assert(vnf1 == [[[100,-50,0],[-100,-50,0],[0,100,0],[100,-50,100],[-100,-50,100],[0,100,100]],[[2,1,0],[3,4,5],[0,4,3],[0,1,4],[1,5,4],[1,2,5],[2,3,5],[2,0,3]]]);
assert(vnf2 == [[[100,-50,0],[-100,-50,0],[0,100,0],[100,-50,100],[-100,-50,100],[0,100,100]],[[2,1,0],[3,4,5],[0,1,3],[3,1,4],[1,2,4],[4,2,5],[2,0,5],[5,0,3]]]);
assert(vnf3 == [[[100,-50,0],[-100,-50,0],[0,100,0],[100,-50,100],[-100,-50,100],[0,100,100],[0,-50,50],[-50,25,50],[50,25,50]],[[2,1,0],[3,4,5],[0,6,3],[3,6,4],[4,6,1],[1,6,0],[1,7,4],[4,7,5],[5,7,2],[2,7,1],[2,8,5],[5,8,3],[3,8,0],[0,8,2]]]);
}
test_vnf_vertex_array();

View file

@ -16,17 +16,17 @@
// You can construct a `polyhedron()` in parts by describing each part in a self-contained VNF, then
// merge the various VNFs to get the completed polyhedron vertex list and faces.
// Constant: EMPTY_VNF
// Description:
// The empty VNF data structure. Equal to `[[],[]]`.
/// Constant: EMPTY_VNF
/// Description:
/// The empty VNF data structure. Equal to `[[],[]]`.
EMPTY_VNF = [[],[]]; // The standard empty VNF with no vertices or faces.
// Function: vnf_vertex_array()
// Usage:
// vnf = vnf_vertex_array(points, [caps], [cap1], [cap2], [style], [reverse], [col_wrap], [row_wrap], [vnf]);
// vnf = vnf_vertex_array(points, [caps], [cap1], [cap2], [style], [reverse], [col_wrap], [row_wrap]);
// Description:
// Creates a VNF structure from a vertex list, by dividing the vertices into columns and rows,
// Creates a VNF structure from a rectangular vertex list, by dividing the vertices into columns and rows,
// adding faces to tile the surface. You can optionally have faces added to wrap the last column
// back to the first column, or wrap the last row to the first. Endcaps can be added to either
// the first and/or last rows. The style parameter determines how the quadrilaterals are divided into
@ -34,9 +34,11 @@ EMPTY_VNF = [[],[]]; // The standard empty VNF with no vertices or faces.
// is the uniform subdivision in the other (alternate) direction. The "min_edge" style picks the shorter edge to
// subdivide for each quadrilateral, so the division may not be uniform across the shape. The "quincunx" style
// adds a vertex in the center of each quadrilateral and creates four triangles, and the "convex" and "concave" styles
// chooses the locally convex/concave subdivision.
// chooses the locally convex/concave subdivision. Degenerate faces
// are not included in the output, but if this results in unused vertices they will still appear in the output.
// Arguments:
// points = A list of vertices to divide into columns and rows.
// ---
// caps = If true, add endcap faces to the first AND last rows.
// cap1 = If true, add an endcap face to the first row.
// cap2 = If true, add an endcap face to the last row.
@ -44,7 +46,6 @@ EMPTY_VNF = [[],[]]; // The standard empty VNF with no vertices or faces.
// row_wrap = If true, add faces to connect the last row to the first.
// reverse = If true, reverse all face normals.
// style = The style of subdividing the quads into faces. Valid options are "default", "alt", "min_edge", "quincunx", "convex" and "concave".
// vnf = If given, add all the vertices and faces to this existing VNF structure.
// Example(3D):
// vnf = vnf_vertex_array(
// points=[
@ -104,12 +105,12 @@ function vnf_vertex_array(
col_wrap=false,
row_wrap=false,
reverse=false,
style="default",
vnf=EMPTY_VNF
style="default"
) =
assert(!(any([caps,cap1,cap2]) && !col_wrap), "col_wrap must be true if caps are requested")
assert(!(any([caps,cap1,cap2]) && row_wrap), "Cannot combine caps with row_wrap")
assert(in_list(style,["default","alt","quincunx", "convex","concave", "min_edge"]))
assert(is_matrix(points[0], n=3),"Point array has the wrong shape or points are not 3d")
assert(is_consistent(points), "Non-rectangular or invalid point array")
let(
pts = flatten(points),
@ -117,7 +118,7 @@ function vnf_vertex_array(
rows = len(points),
cols = len(points[0])
)
rows<=1 || cols<=1 ? vnf :
rows<=1 || cols<=1 ? EMPTY_VNF :
let(
cap1 = first_defined([cap1,caps,false]),
cap2 = first_defined([cap2,caps,false]),
@ -134,13 +135,10 @@ function vnf_vertex_array(
i4 = ((r+0)%rows)*cols + ((c+1)%cols)
)
mean([pts[i1], pts[i2], pts[i3], pts[i4]])
]
)
vnf_merge(cleanup=false, [
vnf,
[
verts,
[
],
allfaces = [
if (cap1) count(cols,reverse=!reverse),
if (cap2) count(cols,(rows-1)*cols, reverse=reverse),
for (r = [0:1:rowcnt-1], c=[0:1:colcnt-1])
each
let(
@ -189,11 +187,9 @@ function vnf_vertex_array(
rfaces = reverse? [for (face=culled_faces) reverse(face)] : culled_faces
)
rfaces,
if (cap1) count(cols,reverse=!reverse),
if (cap2) count(cols,(rows-1)*cols, reverse=reverse)
]
]
]);
)
[verts,allfaces];
// Function: vnf_tri_array()
@ -202,7 +198,8 @@ function vnf_vertex_array(
// Description:
// Produces a vnf from an array of points where each row length can differ from the adjacent rows by up to 2 in length. This enables
// the construction of triangular VNF patches. The resulting VNF can be wrapped along the rows by setting `row_wrap` to true.
// You cannot wrap columns: if you need to do that you'll need to combine two VNF arrays that share edges.
// You cannot wrap columns: if you need to do that you'll need to merge two VNF arrays that share edges. Degenerate faces
// are not included in the output, but if this results in unused vertices they will still appear in the output.
// Arguments:
// points = List of point lists for each row
// row_wrap = If true then add faces connecting the first row and last row. These rows must differ by at most 2 in length.
@ -217,18 +214,18 @@ function vnf_vertex_array(
// vnf = vnf_tri_array(pts);
// vnf_wireframe(vnf,width=0.1);
// color("red")move_copies(flatten(pts)) sphere(r=.15,$fn=9);
// Example(3D): Chaining two VNFs to construct a cone with one point length change between rows.
// Example(3D): Merging two VNFs to construct a cone with one point length change between rows.
// pts1 = [for(z=[0:10]) path3d(arc(3+z,r=z/2+1, angle=[0,180]),10-z)];
// pts2 = [for(z=[0:10]) path3d(arc(3+z,r=z/2+1, angle=[180,360]),10-z)];
// vnf = vnf_tri_array(pts1,
// vnf=vnf_tri_array(pts2));
// vnf = vnf_merge([vnf_tri_array(pts1),
// vnf_tri_array(pts2)]);
// color("green")vnf_wireframe(vnf,width=0.1);
// vnf_polyhedron(vnf);
// Example(3D): Cone with length change two between rows
// pts1 = [for(z=[0:1:10]) path3d(arc(3+2*z,r=z/2+1, angle=[0,180]),10-z)];
// pts2 = [for(z=[0:1:10]) path3d(arc(3+2*z,r=z/2+1, angle=[180,360]),10-z)];
// vnf = vnf_tri_array(pts1,
// vnf=vnf_tri_array(pts2));
// vnf = vnf_merge([vnf_tri_array(pts1),
// vnf_tri_array(pts2)]);
// color("green")vnf_wireframe(vnf,width=0.1);
// vnf_polyhedron(vnf);
// Example(3D,NoAxes): Point count can change irregularly
@ -237,7 +234,7 @@ function vnf_vertex_array(
// vnf = vnf_tri_array(pts);
// vnf_wireframe(vnf,width=0.1);
// color("red")move_copies(flatten(pts)) sphere(r=.15,$fn=9);
function vnf_tri_array(points, row_wrap=false, reverse=false, vnf=EMPTY_VNF) =
function vnf_tri_array(points, row_wrap=false, reverse=false) =
let(
lens = [for(row=points) len(row)],
rowstarts = [0,each cumsum(lens)],
@ -273,8 +270,17 @@ function vnf_tri_array(points, row_wrap=false, reverse=false, vnf=EMPTY_VNF) =
for(j=[count:1:select(lens,i+1)]) reverse ? [ j+nextrow-1, j+rowstart+1, j+rowstart]: [ j+nextrow-1, j+rowstart, j+rowstart+1],
] :
assert(false,str("Unsupported row length difference of ",delta, " between row ",i," and ",(i+1)%len(points)))
])
vnf_merge(cleanup=true, [vnf, [flatten(points), faces]]);
],
verts = flatten(points),
culled_faces=
[for(face=faces)
if (norm(verts[face[0]]-verts[face[1]])>EPSILON &&
norm(verts[face[1]]-verts[face[2]])>EPSILON &&
norm(verts[face[2]]-verts[face[0]])>EPSILON)
face
]
)
[flatten(points), culled_faces];
@ -292,7 +298,9 @@ function vnf_tri_array(points, row_wrap=false, reverse=false, vnf=EMPTY_VNF) =
// eps = the tolerance in finding duplicates when cleanup=true. Default: EPSILON
function vnf_merge(vnfs, cleanup=false, eps=EPSILON) =
is_vnf(vnfs) ? vnf_merge([vnfs], cleanup, eps) :
assert( is_vnf_list(vnfs) , "Improper vnf or vnf list")  
assert( is_vnf_list(vnfs) , "Improper vnf or vnf list")
len(vnfs)==1 ? (cleanup ? _vnf_cleanup(vnfs[0][0],vnfs[0][1],eps) : vnfs[0])
:
let (
offs = cumsum([ 0, for (vnf = vnfs) len(vnf[0]) ]),
verts = [for (vnf=vnfs) each vnf[0]],
@ -307,7 +315,11 @@ function vnf_merge(vnfs, cleanup=false, eps=EPSILON) =
offs[i] + j ]
]
)
! cleanup ? [verts, faces] :
cleanup? _vnf_cleanup(verts,faces,eps) : [verts,faces];
function _vnf_cleanup(verts,faces,eps) =
let(
dedup = vector_search(verts,eps,verts), // collect vertex duplicates
map = [for(i=idx(verts)) min(dedup[i]) ], // remap duplic vertices
@ -405,7 +417,7 @@ function _cleave_connected_region(region) =
// Function: vnf_from_region()
// Usage:
// vnf = vnf_from_region(region, [transform], [reverse], [vnf]);
// vnf = vnf_from_region(region, [transform], [reverse]);
// Description:
// Given a (two-dimensional) region, applies the given transformation matrix to it and makes a (three-dimensional) triangulated VNF of
// faces for that region, reversed if desired.
@ -413,7 +425,6 @@ function _cleave_connected_region(region) =
// region = The region to conver to a vnf.
// transform = If given, a transformation matrix to apply to the faces generated from the region. Default: No transformation applied.
// reverse = If true, reverse the normals of the faces generated from the region. An untransformed region will have face normals pointing `UP`. Default: false
// vnf = If given, the faces are added to this VNF. Default: `EMPTY_VNF`
// Example(3D):
// region = [square([20,10],center=true),
// right(5,square(4,center=true)),
@ -422,11 +433,10 @@ function _cleave_connected_region(region) =
// color("gray")down(.125)
// linear_extrude(height=.125)region(region);
// vnf_wireframe(vnf,width=.25);
function vnf_from_region(region, transform, reverse=false, vnf=EMPTY_VNF) =
function vnf_from_region(region, transform, reverse=false) =
let (
regions = region_parts(force_region(region)),
vnfs = [
if (vnf != EMPTY_VNF) vnf,
for (rgn = regions) let(
cleaved = path3d(_cleave_connected_region(rgn)),
face = is_undef(transform)? cleaved : apply(transform,cleaved),
@ -684,11 +694,11 @@ module vnf_polyhedron(vnf, convexity=2, extent=true, cp=[0,0,0], anchor="origin"
// Module: vnf_wireframe()
// Usage:
// vnf_wireframe(vnf, <r|d>);
// vnf_wireframe(vnf, [width]);
// 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. The width parameter specifies the width of the sticks
// that form the wire frame.
// that form the wire frame and the diameter of the balls.
// Arguments:
// vnf = A vnf structure
// width = width of the cylinders forming the wire frame. Default: 1
@ -930,6 +940,7 @@ function _triangulate_planar_convex_polygons(polys) =
// Arguments:
// vnf = The original VNF to bend.
// r = If given, the radius where the size of the original shape is the same as in the original.
// ---
// d = If given, the diameter where the size of the original shape is the same as in the original.
// axis = The axis to wrap around. "X", "Y", or "Z". Default: "Z"
// Example(3D):