From b39e67da0ebeda25b2ceb17fd7ed7822de9e9971 Mon Sep 17 00:00:00 2001 From: Garth Minette Date: Wed, 13 Jul 2022 21:34:56 -0700 Subject: [PATCH 1/6] Improvements to vnf_validate() --- vnf.scad | 100 ++++++++++++++++++++++++++++++++++++------------------- 1 file changed, 66 insertions(+), 34 deletions(-) diff --git a/vnf.scad b/vnf.scad index a202835..feb7062 100644 --- a/vnf.scad +++ b/vnf.scad @@ -1234,7 +1234,7 @@ function vnf_bend(vnf,r,d,axis="Z") = /// Internal Module: _show_vertices() /// Usage: -/// _show_vertices(vertices, [size]) +/// _show_vertices(vertices, [size], [filter=]) /// 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 @@ -1248,19 +1248,21 @@ function vnf_bend(vnf,r,d,axis="Z") = /// _show_vertices(vertices=verts, size=2) { /// polyhedron(points=verts, faces=faces); /// } -module _show_vertices(vertices, size=1) { +module _show_vertices(vertices, size=1, filter) { 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"); - } + for (ind = dups) { + if (is_undef(filter) || any(ind, filter)) { + 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); } - sphere(size/10); } } } @@ -1269,7 +1271,7 @@ module _show_vertices(vertices, size=1) { /// Internal Module: _show_faces() /// Usage: -/// _show_faces(vertices, faces, [size=]); +/// _show_faces(vertices, faces, [size=], [filter=]); /// 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 @@ -1285,14 +1287,14 @@ module _show_vertices(vertices, size=1) { /// _show_faces(vertices=verts, faces=faces, size=2) { /// polyhedron(points=verts, faces=faces); /// } -module _show_faces(vertices, faces, size=1) { +module _show_faces(vertices, faces, size=1, filter) { 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 { + } else if (is_undef(filter) || any(face,filter)) { verts = select(vertices,face); c = mean(verts); v0 = verts[0]; @@ -1325,7 +1327,7 @@ module _show_faces(vertices, faces, size=1) { // Module: debug_vnf() // Usage: -// debug_vnf(vnfs, [faces=], [vertices=], [opacity=], [size=], [convexity=]); +// debug_vnf(vnfs, [faces=], [vertices=], [opacity=], [size=], [convexity=], [filter=]); // Description: // 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 @@ -1346,18 +1348,20 @@ module _show_faces(vertices, faces, size=1) { // opacity = Opacity of the polyhedron faces. Default: 0.5 // convexity = The max number of walls a ray can pass through the given polygon paths. // size = The size of the text used to label the faces and vertices. Default: 1 +// filter = If given a function literal of signature `function(i)`, will only show labels for vertices and faces that have a vertex index that gets a true result from that function. Default: no filter. // 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], size=2); -module debug_vnf(vnf, faces=true, vertices=true, opacity=0.5, size=1, convexity=6 ) { +module debug_vnf(vnf, faces=true, vertices=true, opacity=0.5, size=1, convexity=6, filter ) { no_children($children); if (faces) - _show_faces(vertices=vnf[0], faces=vnf[1], size=size); + _show_faces(vertices=vnf[0], faces=vnf[1], size=size, filter=filter); if (vertices) - _show_vertices(vertices=vnf[0], size=size); - color([0.2, 1.0, 0, opacity]) - vnf_polyhedron(vnf,convexity=convexity); + _show_vertices(vertices=vnf[0], size=size, filter=filter); + if (opacity > 0) + color([0.2, 1.0, 0, opacity]) + vnf_polyhedron(vnf,convexity=convexity); } @@ -1365,7 +1369,7 @@ module debug_vnf(vnf, faces=true, vertices=true, opacity=0.5, size=1, convexity= // Usage: As Function // fails = vnf_validate(vnf); // Usage: As Module -// vnf_validate(vnf, [size], [check_isects]); +// vnf_validate(vnf, [size], [show_warns=], [check_isects=], [opacity=], [adjacent=], [label_verts=], [label_faces=], [wireframe=]); // Description: // When called as a function, returns a list of non-manifold errors with the given VNF. // Each error has the format `[ERR_OR_WARN,CODE,MESG,POINTS,COLOR]`. @@ -1394,6 +1398,11 @@ module debug_vnf(vnf, faces=true, vertices=true, opacity=0.5, size=1, convexity= // -- // show_warns = If true show warnings for non-triangular faces. Default: true // check_isects = If true, performs slow checks for intersecting faces. Default: false +// opacity = The opacity level to show the polyhedron itself with. (Module only) Default: 0.67 +// label_verts = If true, shows labels at each vertex that show the vertex number. (Module only) Default: false +// label_faces = If true, shows labels at the center of each face that show the face number. (Module only) Default: false +// wireframe = If true, shows edges more clearly so you can see them in Thrown Together mode. (Module only) Default: false +// adjacent = If true, only display faces adjacent to a vertex listed in the errors. (Module only) Default: false // Example: BIG_FACE Warnings; Faces with More Than 3 Vertices. CGAL often will fail to accept that a face is planar after a rotation, if it has more than 3 vertices. // vnf = skin([ // path3d(regular_ngon(n=3, d=100),0), @@ -1481,16 +1490,15 @@ function vnf_validate(vnf, show_warns=true, check_isects=false) = big_faces = !show_warns? [] : [ for (face = faces) if (len(face) > 3) - _vnf_validate_err("BIG_FACE", [for (i=face) varr[i]]) + _vnf_validate_err("BIG_FACE", face) ], null_faces = !show_warns? [] : [ for (i = idx(faces)) let( face = faces[i], - area = face_areas[i], - faceverts = [for (k=face) varr[k]] + area = face_areas[i] ) if (is_num(area) && abs(area) < EPSILON) - _vnf_validate_err("NULL_FACE", faceverts) + _vnf_validate_err("NULL_FACE", face) ], issues = concat(big_faces, null_faces) ) @@ -1515,7 +1523,7 @@ function vnf_validate(vnf, show_warns=true, check_isects=false) = sface1 = list_rotate(face1,min1), sface2 = list_rotate(face2,min2) ) if (sface1 == sface2) - _vnf_validate_err("DUP_FACE", [for (i=sface1) varr[i]]) + _vnf_validate_err("DUP_FACE", sface1) ], issues = concat(issues, repeated_faces) ) repeated_faces? issues : @@ -1523,7 +1531,7 @@ function vnf_validate(vnf, show_warns=true, check_isects=false) = multconn_edges = unique([ for (i = idx(uniq_edges)) if (edgecnts[1][i]>2) - _vnf_validate_err("MULTCONN", [for (i=uniq_edges[i]) varr[i]]) + _vnf_validate_err("MULTCONN", uniq_edges[i]) ]), issues = concat(issues, multconn_edges) ) multconn_edges? issues : @@ -1534,7 +1542,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, multconn_edges)) - _vnf_validate_err("REVERSAL", [for (i=edge1) varr[i]]) + _vnf_validate_err("REVERSAL", edge1) ]), issues = concat(issues, reversals) ) reversals? issues : @@ -1606,7 +1614,7 @@ function vnf_validate(vnf, show_warns=true, check_isects=false) = 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)) - _vnf_validate_err("HOLE_EDGE", [for (i=uniq_edges[i]) varr[i]]) + _vnf_validate_err("HOLE_EDGE", uniq_edges[i]) ]), issues = concat(issues, hole_edges) ) hole_edges? issues : @@ -1619,7 +1627,7 @@ function vnf_validate(vnf, show_warns=true, check_isects=false) = ) if (is_num(area) && abs(area) > EPSILON) if (!is_coplanar(faceverts)) - _vnf_validate_err("NONPLANAR", faceverts) + _vnf_validate_err("NONPLANAR", face) ]), issues = concat(issues, nonplanars) ) issues; @@ -1662,19 +1670,24 @@ function _edge_not_reported(edge, varr, reports) = ] == []; -module vnf_validate(vnf, size=1, show_warns=true, check_isects=false) { +module vnf_validate(vnf, size=1, show_warns=true, check_isects=false, opacity=0.67, adjacent=false, label_verts=false, label_faces=false, wireframe=false) { no_children($children); + verts = vnf[0]; faults = vnf_validate( vnf, show_warns=show_warns, check_isects=check_isects ); + if (!faults) { + echo("VNF appears valid."); + } for (fault = faults) { err = fault[0]; typ = fault[1]; clr = fault[2]; msg = fault[3]; - pts = fault[4]; - echo(str(typ, " ", err, " (", clr ,"): ", msg, " at ", pts)); + idxs = fault[4]; + pts = [for (i=idxs) if(i>=0 && i 0) { + color([0.5,1,0.5,opacity]) vnf_polyhedron(adj_vnf); + } } From f8ea3fb305eb846ebca7a647184bf734e65cc36a Mon Sep 17 00:00:00 2001 From: Garth Minette Date: Wed, 13 Jul 2022 23:03:47 -0700 Subject: [PATCH 2/6] vnf_validate() minor bugfixes. --- vnf.scad | 41 ++++++++++++++++++++--------------------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/vnf.scad b/vnf.scad index feb7062..7e0283e 100644 --- a/vnf.scad +++ b/vnf.scad @@ -1403,13 +1403,13 @@ module debug_vnf(vnf, faces=true, vertices=true, opacity=0.5, size=1, convexity= // label_faces = If true, shows labels at the center of each face that show the face number. (Module only) Default: false // wireframe = If true, shows edges more clearly so you can see them in Thrown Together mode. (Module only) Default: false // adjacent = If true, only display faces adjacent to a vertex listed in the errors. (Module only) Default: false -// Example: BIG_FACE Warnings; Faces with More Than 3 Vertices. CGAL often will fail to accept that a face is planar after a rotation, if it has more than 3 vertices. +// Example(3D,Edges): BIG_FACE Warnings; Faces with More Than 3 Vertices. CGAL often will fail to accept that a face is planar after a rotation, if it has more than 3 vertices. // vnf = skin([ // path3d(regular_ngon(n=3, d=100),0), // path3d(regular_ngon(n=5, d=100),100) // ], slices=0, caps=true, method="tangent"); // vnf_validate(vnf); -// Example: NONPLANAR Errors; Face Vertices are Not Coplanar +// Example(3D,Edges): NONPLANAR Errors; Face Vertices are Not Coplanar // a = [ 0, 0,-50]; // b = [-50,-50, 50]; // c = [-50, 50, 50]; @@ -1419,10 +1419,10 @@ module debug_vnf(vnf, faces=true, vertices=true, opacity=0.5, size=1, convexity= // [a, b, e], [a, c, b], [a, d, c], [a, e, d], [b, c, d, e] // ]); // vnf_validate(vnf); -// Example: MULTCONN Errors; More Than Two Faces Attached to the Same Edge. This confuses CGAL, and can lead to failed renders. +// Example(3D,Edges): MULTCONN Errors; More Than Two Faces Attached to the Same Edge. This confuses CGAL, and can lead to failed renders. // vnf = vnf_triangulate(linear_sweep(union(square(50), square(50,anchor=BACK+RIGHT)), height=50)); // vnf_validate(vnf); -// Example: REVERSAL Errors; Faces Reversed Across Edge +// Example(3D,Edges): REVERSAL Errors; Faces Reversed Across Edge // vnf1 = skin([ // path3d(square(100,center=true),0), // path3d(square(100,center=true),100), @@ -1434,27 +1434,26 @@ module debug_vnf(vnf, faces=true, vertices=true, opacity=0.5, size=1, convexity= // [[-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_join([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]], -// ])]); +// Example(3D,Edges): T_JUNCTION Errors; Vertex is Mid-Edge on Another Face. +// vnf = [ +// [ +// each path3d(square(100,center=true),0), +// each path3d(square(100,center=true),100), +// [0,-50,100], +// ], [ +// [0,2,1], [0,3,2], [0,8,4], [0,1,8], [1,5,8], +// [0,4,3], [4,7,3], [1,2,5], [2,6,5], [3,7,6], +// [3,6,2], [4,5,6], [4,6,7], +// ] +// ]; // vnf_validate(vnf); -// Example: FACE_ISECT Errors; Faces Intersect +// Example(3D,Edges): FACE_ISECT Errors; Faces Intersect // vnf = vnf_join([ // vnf_triangulate(linear_sweep(square(100,center=true), height=100)), // move([75,35,30],p=vnf_triangulate(linear_sweep(square(100,center=true), height=100))) // ]); // vnf_validate(vnf,size=2,check_isects=true); -// Example: HOLE_EDGE Errors; Edges Adjacent to Holes. +// Example(3D,Edges): HOLE_EDGE Errors; Edges Adjacent to Holes. // vnf = skin([ // path3d(regular_ngon(n=4, d=100),0), // path3d(regular_ngon(n=5, d=100),100) @@ -1562,7 +1561,7 @@ function vnf_validate(vnf, show_warns=true, check_isects=false) = pt = line_closest_point([a,c],b,SEGMENT) ) if (approx(pt,b)) - _vnf_validate_err("T_JUNCTION", [b]) + _vnf_validate_err("T_JUNCTION", [ib]) ]), issues = concat(issues, t_juncts) ) t_juncts? issues : @@ -1686,7 +1685,7 @@ module vnf_validate(vnf, size=1, show_warns=true, check_isects=false, opacity=0. clr = fault[2]; msg = fault[3]; idxs = fault[4]; - pts = [for (i=idxs) if(i>=0 && i=0 && i Date: Wed, 13 Jul 2022 23:05:18 -0700 Subject: [PATCH 3/6] Made texture functions/modules region aware. --- skin.scad | 717 +++++++++++++++++++++++++++++++----------------------- 1 file changed, 418 insertions(+), 299 deletions(-) diff --git a/skin.scad b/skin.scad index ce8372a..c589899 100644 --- a/skin.scad +++ b/skin.scad @@ -2100,9 +2100,9 @@ function associate_vertices(polygons, split, curpoly=0) = // Section: Texturing // DefineHeader(Table;Headers=Texture Name|Type|Description): Texture Values -// Function: get_texture() +// Function: texture() // Usage: -// tx = get_texture(tex, [n], [m]); +// tx = texture(tex, [n], [m]); // Topics: Textures, Knurling // Description: // Given a texture name, and two optional variables, returns a heightfield texture as a 2D array of scalars. @@ -2131,122 +2131,122 @@ function associate_vertices(polygons, split, curpoly=0) = // "vnf_hex_grid" = VNF Tile = A hexagonal grid of thin lines. // "vnf_pyramids" = VNF Tile = Like "pyramids", but slower and more consistent in triangulation. // "vnf_trunc_pyramids" = VNF Tile = Like "trunc_pyramids", but slower and more consistent in triangulation. -// See Also: textured_revolution(), textured_cylinder(), textured_linear_sweep(), heightfield(), cylindrical_heightfield(), get_texture() +// See Also: textured_revolution(), textured_cylinder(), textured_linear_sweep(), heightfield(), cylindrical_heightfield(), texture() // Example(3D): "ribs" texture. -// tex = get_texture("ribs"); +// tex = texture("ribs"); // textured_linear_sweep( -// rect(50), tex, h=40, -// tex_size=[5,10], style="concave" +// rect(50), tex, h=40, tscale=3, +// tex_size=[10,10], style="concave" // ); // Example(3D): Truncated "trunc_ribs" texture. -// tex = get_texture("trunc_ribs"); +// tex = texture("trunc_ribs"); // textured_linear_sweep( -// rect(50), tex, h=40, -// tex_size=[5,10], style="concave" +// rect(50), tex, h=40, tscale=3, +// tex_size=[10,10], style="concave" // ); // Example(3D): "wave_ribs" texture. -// tex = get_texture("wave_ribs"); +// tex = texture("wave_ribs"); // textured_linear_sweep( // rect(50), tex, h=40, // tex_size=[10,10], style="concave" // ); // Example(3D): "diamonds" texture. -// tex = get_texture("diamonds"); +// tex = texture("diamonds"); // textured_linear_sweep( // rect(50), tex, h=40, // tex_size=[10,10], style="concave" // ); // Example(3D): "vnf_diamonds" texture. Slower, but more consistent around complex curves. -// tex = get_texture("vnf_diamonds"); +// tex = texture("vnf_diamonds"); // textured_linear_sweep( // rect(50), tex, h=40, // tex_size=[10,10] // ); // Example(3D): "pyramids" texture. -// tex = get_texture("pyramids"); +// tex = texture("pyramids"); // textured_linear_sweep( // rect(50), tex, h=40, // tex_size=[10,10], style="convex" // ); // Example(3D): "vnf_pyramids" texture. Slower, but more consistent around complex curves. -// tex = get_texture("vnf_pyramids"); +// tex = texture("vnf_pyramids"); // textured_linear_sweep( // rect(50), tex, h=40, // tex_size=[10,10] // ); // Example(3D): "trunc_pyramids" texture. -// tex = get_texture("trunc_pyramids"); +// tex = texture("trunc_pyramids"); // textured_linear_sweep( // rect(50), tex, h=40, // tex_size=[10,10], style="convex" // ); // Example(3D): "vnf_trunc_pyramids" texture. Slower, but more consistent around complex curves. -// tex = get_texture("vnf_trunc_pyramids"); +// tex = texture("vnf_trunc_pyramids"); // textured_linear_sweep( // rect(50), tex, h=40, // tex_size=[10,10] // ); // Example(3D): "hills" texture. -// tex = get_texture("hills"); +// tex = texture("hills"); // textured_linear_sweep( // rect(50), tex, h=40, // tex_size=[10,10], style="quincunx" // ); // Example(3D): "vnf_dots" texture. -// tex = get_texture("vnf_dots"); +// tex = texture("vnf_dots"); // textured_linear_sweep( -// rect(50), tex, h=40, +// rect(50), tex, h=40, tscale=3, // tex_size=[10,10] // ); // Example(3D): "vnf_dimples" texture. -// tex = get_texture("vnf_dimples"); +// tex = texture("vnf_dimples"); // textured_linear_sweep( -// rect(50), tex, h=40, +// rect(50), tex, h=40, tscale=3, // tex_size=[10,10] // ); // Example(3D): "vnf_cones" texture. -// tex = get_texture("vnf_cones"); +// tex = texture("vnf_cones"); // textured_linear_sweep( -// rect(50), tex, h=40, +// rect(50), tex, h=40, tscale=3, // tex_size=[10,10] // ); // Example(3D): "bricks" texture. -// tex = get_texture("bricks"); +// tex = texture("bricks"); // textured_linear_sweep( // rect(50), tex, h=40, // tex_size=[10,10] // ); // Example(3D): "vnf_bricks" texture. -// tex = get_texture("vnf_bricks"); +// tex = texture("vnf_bricks"); // textured_linear_sweep( // rect(50), tex, h=40, // tex_size=[10,10] // ); // Example(3D): "vnf_diagonal_grid" texture. -// tex = get_texture("vnf_diagonal_grid"); +// tex = texture("vnf_diagonal_grid"); // textured_linear_sweep( // rect(50), tex, h=40, // tex_size=[10,10] // ); // Example(3D): "vnf_hex_grid" texture. -// tex = get_texture("vnf_hex_grid"); +// tex = texture("vnf_hex_grid"); // textured_linear_sweep( // rect(50), tex, h=40, // tex_size=[12.5,20] // ); // Example(3D): "vnf_checkers" texture. -// tex = get_texture("vnf_checkers"); +// tex = texture("vnf_checkers"); // textured_linear_sweep( // rect(50), tex, h=40, // tex_size=[10,10] // ); // Example(3D): "rough" texture. -// tex = get_texture("rough"); +// tex = texture("rough"); // textured_linear_sweep( // rect(50), tex, h=40, // tex_size=[10,10], style="min_edge" // ); -function get_texture(tex,n,m,o) = +function texture(tex,n,m,o) = tex=="ribs"? [[1,0]] : tex=="trunc_ribs"? [[each repeat(0,default(n,1)+1), each repeat(1,default(n,1)+1)]] : tex=="wave_ribs"? [[for(a=[0:360/default(n,8):359]) (cos(a)+1)/2]] : @@ -2440,21 +2440,16 @@ function get_texture(tex,n,m,o) = // Function&Module: textured_linear_sweep() // Usage: As Function -// vnf = textured_linear_sweep(path, texture, tex_size, h, ...); -// vnf = textured_linear_sweep(path, texture, counts=, h=, ...); +// vnf = textured_linear_sweep(region, texture, tex_size, h, ...); +// vnf = textured_linear_sweep(region, texture, counts=, h=, ...); // Usage: As Module -// textured_linear_sweep(path, texture, tex_size, h, ...) [ATTACHMENTS]; -// textured_linear_sweep(path, texture, counts=, h=, ...) [ATTACHMENTS]; +// textured_linear_sweep(region, texture, tex_size, h, ...) [ATTACHMENTS]; +// textured_linear_sweep(region, texture, counts=, h=, ...) [ATTACHMENTS]; // Topics: Sweep, Extrusion, Textures, Knurling // Description: -// Given a single polygon path, creates a linear extrusion of that polygon vertically, optionally twisted, -// scaled, and/or shifted, with a given texture tiled evenly over the side surfaces. -// If the path to be swept is clockwise on the XY plane, then the output shape should have its faces pointed outwards, -// though you can use `reverse=true` to reverse the face directions if needed. It is recommended that you preview with -// OpenSCAD's "Thrown Together" view mode, to verify the orientation of the faces. If you see purple, then your model is -// non-manifold, and not 3D print-able. -// The texture can be given in one of three ways: -// - As a texture name string. (See {{get_texture()}} for supported named textures.) +// Given a [[Region|regions.scad]], creates a linear extrusion of it vertically, optionally twisted, scaled, and/or shifted, +// with a given texture tiled evenly over the side surfaces. The texture can be given in one of three ways: +// - As a texture name string. (See {{texture()}} for supported named textures.) // - As a 2D array of evenly spread height values. (AKA a heightfield.) // - As a VNF texture tile. A VNF tile exactly defines a surface from `[0,0]` to `[1,1]`, with the Z coordinates // being the height of the texture point from the surface. VNF tiles MUST be able to tile in both X and Y @@ -2462,8 +2457,8 @@ function get_texture(tex,n,m,o) = // One script to convert a grayscale image to a texture heightfield array in a .scad file can be found at: // https://raw.githubusercontent.com/revarbat/BOSL2/master/scripts/img2scad.py // Arguments: -// path = The path to sweep/extrude. -// texture = A texture name string, or a rectangular array of scalar height values (0.0 to 1.0), or a VNF tile that defines the texture to apply to vertical surfaces. See {{get_texture()}} for what named textures are supported. +// region = The [[Region|regions.scad]] to sweep/extrude. +// texture = A texture name string, or a rectangular array of scalar height values (0.0 to 1.0), or a VNF tile that defines the texture to apply to vertical surfaces. See {{texture()}} for what named textures are supported. // tex_size = An optional 2D target size for the textures. Actual texture sizes will be scaled somewhat to evenly fit the available surface. Default: `[5,5]` // h / l = The height to extrude/sweep the path. // --- @@ -2474,10 +2469,8 @@ function get_texture(tex,n,m,o) = // twist = Degrees of twist for the top of the extrustion/sweep, compared to the bottom. Default: 0 // scale = Scaling multiplier for the top of the extrustion/sweep, compared to the bottom. Default: 1 // shift = [X,Y] amount to translate the top, relative to the bottom. Default: [0,0] -// caps = (function only) If true, create endcaps for the extruded shape. -// col_wrap = (function only) If true, the path is considered a closed polygon. // style = The triangulation style used. See {{vnf_vertex_array()}} for valid styles. Used only with heightfield type textures. Default: `"min_edge"` -// reverse = If the default faces are facing the wrong way, you can reverse them by setting this to `true`. Default: `false` +// samples = Minimum number of "bend points" to have in VNF texture tiles. Default: 8 // anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#subsection-anchor). Default: `CENTER` // spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#subsection-spin). Default: `0` // orient = Vector to rotate top towards, after spin. See [orient](attachments.scad#subsection-orient). Default: `UP` @@ -2485,7 +2478,7 @@ function get_texture(tex,n,m,o) = // centroid_top = The centroid of the top of the shape, oriented UP. // centroid = The centroid of the center of the shape, oriented UP. // centroid_bot = The centroid of the bottom of the shape, oriented DOWN. -// See Also: textured_revolution(), textured_cylinder(), textured_linear_sweep(), heightfield(), cylindrical_heightfield(), get_texture() +// See Also: textured_revolution(), textured_cylinder(), textured_linear_sweep(), heightfield(), cylindrical_heightfield(), texture() // Example: "diamonds" texture. // path = glued_circles(r=15, spread=40, tangent=45); // textured_linear_sweep( @@ -2532,45 +2525,36 @@ function get_texture(tex,n,m,o) = // tscale=1, style="convex"); // vnf_polyhedron(vnf, convexity=10); function textured_linear_sweep( - path, texture, + region, texture, tex_size=[5,5], h, counts, inset=false, rot=false, tscale=1, - caps=true, col_wrap=true, twist, scale, shift, - style="min_edge", reverse=false, l, + style="min_edge", l, samples, anchor=CENTER, spin=0, orient=UP ) = - assert(is_path(path,[2])) - assert(is_bool(caps)) - assert(is_bool(reverse)) + assert(is_path(region,[2])||is_region(region)) + assert(is_undef(samples) || is_int(samples)) assert(counts==undef || is_vector(counts,2)) assert(tex_size==undef || is_vector(tex_size,2)) assert(is_bool(rot) || in_list(rot,[0,90,180,270])) let( - tex = is_string(texture)? get_texture(texture) : texture, - path = col_wrap && is_polygon_clockwise(path)? reverse(path) : path, + regions = is_path(region,2)? [[region]] : region_parts(region), + tex = is_string(texture)? texture(texture) : texture, texture = !rot? tex : is_vnf(tex)? zrot(is_num(rot)?rot:90, cp=[1/2,1/2], p=tex) : rot==180? reverse([for (row=tex) reverse(row)]) : rot==270? [for (row=transpose(tex)) reverse(row)] : reverse(transpose(tex)), + h = first_defined([h, l, 1]), + inset = is_num(inset)? inset : inset? 1 : 0, twist = default(twist, 0), shift = default(shift, [0,0]), - scale = scale==undef? [1,1,1] : is_num(scale)? [scale,scale,1] : scale, - h = first_defined([h, l, 1]), - plen = path_length(path, closed=col_wrap), - counts = is_vector(counts,2)? counts : - is_vector(tex_size,2) - ? [round(plen/tex_size.x), max(1,round(h/tex_size.y)), ] - : [ceil(6*plen/h), 6], - inset = is_num(inset)? inset : inset? 1 : 0, - samples = is_vnf(texture)? 12 : len(texture[0]), - obases = resample_path(path, n=counts.x * samples, closed=col_wrap), - onorms = path_normals(obases, closed=col_wrap), - bases = col_wrap? close_path(obases) : obases, - norms = col_wrap? close_path(onorms) : onorms, - vnf = is_vnf(texture) - ? let( // VNF tile texture + scale = scale==undef? [1,1,1] : + is_num(scale)? [scale,scale,1] : scale, + samples = !is_vnf(texture)? len(texture[0]) : + is_num(samples)? samples : 8, + check_tex = is_vnf(texture) + ? let( // Validate VNF tile texture bounds = pointlist_bounds(texture[0]), min_xy = point2d(bounds[0]), max_xy = point2d(bounds[1]) @@ -2583,97 +2567,122 @@ function textured_linear_sweep( allgoody = all(vverts, function(v) any(vverts, function(w) w==[v.x, 1-v.y, v.z])) ) assert(allgoodx && allgoody, "All VNF tile edge vertices must line up with a vertex on the opposite side of the tile.") - let( - tex2 = vnf_slice(texture, "X", list([1/8:1/8:7/8])), - sorted_tile = _vnf_sort_vertices(tex2, idx=[1,0]), - vertzs = group_sort(sorted_tile[0], idx=1), - row_vnf = vnf_join([ - for (j = [0:1:counts.x-1]) [ - [ - for (group = vertzs) - each [ - for (vert = group) let( - u = floor((j + vert.x) * samples), - uu = ((j + vert.x) * samples) - u, - texh = (vert.z - inset) * tscale, - base = lerp(bases[u], select(bases,u+1), uu), - norm = unit(lerp(norms[u], select(norms,u+1), uu)), - xy = base + norm * texh - ) point3d(xy,vert.y) - ] - ], - sorted_tile[1] - ] + : let( // Validate heightfield texture. + tex_dim = list_shape(texture) + ) + assert(len(tex_dim) == 2, "Heightfield texture must be a 2D square array of scalar heights.") + assert(all_defined(tex_dim), "Heightfield texture must be a 2D square array of scalar heights."), + skmat = down(h/2) * skew(sxz=shift.x/h, syz=shift.y/h) * up(h/2), + tmat = scale(scale) * zrot(twist) * up(h/2), + final_vnf = vnf_join([ + for (rgn = regions) let( + walls_vnf = vnf_join([ + for (path = rgn) let( + path = reverse(path), + plen = path_length(path, closed=true), + counts = is_vector(counts,2)? counts : + is_vector(tex_size,2) + ? [round(plen/tex_size.x), max(1,round(h/tex_size.y)), ] + : [ceil(6*plen/h), 6], + bases = close_path(resample_path(path, n=counts.x * samples, closed=true)), + norms = close_path(path_normals(bases, closed=true)), + vnf = is_vnf(texture) + ? let( // VNF tile texture + tex2 = samples<=1? texture : + let( s = 1/samples ) + vnf_slice(texture, "X", list([s:s:1-s/2])), + sorted_tile = _vnf_sort_vertices(tex2, idx=[1,0]), + vertzs = group_sort(sorted_tile[0], idx=1), + row_vnf = vnf_join([ + for (j = [0:1:counts.x-1]) [ + [ + for (group = vertzs) + each [ + for (vert = group) let( + u = floor((j + vert.x) * samples), + uu = ((j + vert.x) * samples) - u, + texh = (vert.z - inset) * tscale, + base = lerp(bases[u], select(bases,u+1), uu), + norm = unit(lerp(norms[u], select(norms,u+1), uu)), + xy = base + norm * texh + ) point3d(xy,vert.y) + ] + ], + sorted_tile[1] + ] + ]), + sorted_row = _vnf_sort_vertices(row_vnf, idx=[1,0]), + rvertzs = group_sort(sorted_row[0], idx=1), + vnf1 = vnf_join([ + for (i = [0:1:counts.y-1]) [ + [ + for (group = rvertzs) let( + v = (i + group[0].z) / counts.y, + mat = move(shift*v) * + scale(lerp([1,1,1],scale,v)) * + zrot(twist*v) * + up(((i/counts.y)-0.5)*h) * + zscale(h/counts.y) + ) each apply(mat, group) + ], + sorted_row[1] + ] + ]) + ) vnf1 + : let( // Heightfield texture + texcnt = [len(texture[0]), len(texture)], + tile_rows = [ + for (ti = [0:1:texcnt.y-1]) + path3d([ + for (j = [0:1:counts.x]) + for (tj = [0:1:texcnt.x-1]) + if (j != counts.x || tj == 0) + let( + part = (j + (tj/texcnt.x)) * samples, + u = floor(part), + uu = part - u, + texh = (texture[ti][tj] - inset) * tscale, + base = lerp(bases[u], select(bases,u+1), uu), + norm = unit(lerp(norms[u], select(norms,u+1), uu)), + xy = base + norm * texh + ) xy + ]) + ], + tiles = [ + for (i = [0:1:counts.y], ti = [0:1:texcnt.y-1]) + if (i != counts.y || ti == 0) + let( + v = (i + (ti/texcnt.y)) / counts.y, + mat = down((v-0.5)*h) * + move(shift*v) * + scale(lerp([1,1,1],scale,v)) * + zrot(twist*v) + ) apply(mat, tile_rows[ti]) + ] + ) vnf_vertex_array( + tiles, caps=false, style=style, + col_wrap=true, row_wrap=false + ) + ) vnf ]), - sorted_row = _vnf_sort_vertices(row_vnf, idx=[1,0]), - rvertzs = group_sort(sorted_row[0], idx=1), - vnf1 = vnf_join([ - for (i = [0:1:counts.y-1]) [ - [ - for (group = rvertzs) let( - v = (i + group[0].z) / counts.y, - mat = move(shift*v) * - scale(lerp([1,1,1],scale,v)) * - zrot(twist*v) * - up(((i/counts.y)-0.5)*h) * - zscale(h/counts.y) - ) each apply(mat, group) - ], - sorted_row[1] - ] - ]), - tmat = move(shift) * scale(scale) * zrot(twist) * up(h/2), - bpath = _find_vnf_z_edge_path(vnf1,-h/2), - vnf2 = vnf_from_region(bpath, down(h/2), reverse=true), - vnf3 = vnf_from_region(bpath, tmat, reverse=false) - ) vnf_join([vnf1, vnf2, vnf3]) - : let( // Heightfield texture - texcnt = [len(texture[0]), len(texture)], - tile_rows = [ - for (ti = [0:1:texcnt.y-1]) - path3d([ - for (j = [0:1:counts.x]) - for (tj = [0:1:texcnt.x-1]) - if (j != counts.x || tj == 0) - let( - part = (j + (tj/texcnt.x)) * samples, - u = floor(part), - uu = part - u, - texh = (texture[ti][tj] - inset) * tscale, - base = lerp(bases[u], select(bases,u+1), uu), - norm = unit(lerp(norms[u], select(norms,u+1), uu)), - xy = base + norm * texh - ) xy - ]) - ], - tiles = [ - for (i = [0:1:counts.y], ti = [0:1:texcnt.y-1]) - if (i != counts.y || ti == 0) - let( - v = (i + (ti/texcnt.y)) / counts.y, - mat = down((v-0.5)*h) * - move(shift*v) * - scale(lerp([1,1,1],scale,v)) * - zrot(twist*v) - ) apply(mat, tile_rows[ti]) - ] - ) vnf_vertex_array( - tiles, caps=caps, style=style, reverse=reverse, - col_wrap=col_wrap, row_wrap=false - ), - cent = centroid(path), + brgn = _find_vnf_edge_paths(walls_vnf,2,-h/2), + bot_vnf = vnf_from_region(brgn, down(h/2), reverse=true), + top_vnf = vnf_from_region(brgn, tmat, reverse=false) + ) vnf_join([walls_vnf, bot_vnf, top_vnf]) + ]), + cent = centroid(region), anchors = [ named_anchor("centroid_top", point3d(cent, h/2), UP), named_anchor("centroid", point3d(cent), UP), named_anchor("centroid_bot", point3d(cent,-h/2), DOWN) ] - ) reorient(anchor,spin,orient, vnf=vnf, extent=true, anchors=anchors, p=vnf); + ) reorient(anchor,spin,orient, vnf=final_vnf, extent=true, anchors=anchors, p=final_vnf); module textured_linear_sweep( path, texture, tex_size=[5,5], h, inset=false, rot=false, tscale=1, - twist, scale, shift, + twist, scale, shift, samples, style="min_edge", reverse=false, l, counts, anchor=CENTER, spin=0, orient=UP, convexity=10 @@ -2684,8 +2693,7 @@ module textured_linear_sweep( tex_size=tex_size, counts=counts, inset=inset, rot=rot, tscale=tscale, twist=twist, scale=scale, shift=shift, - caps=true, col_wrap=true, - style=style, reverse=reverse, + samples=samples, style=style, reverse=reverse, anchor=CENTER, spin=0, orient=UP ); cent = centroid(path); @@ -2700,37 +2708,33 @@ module textured_linear_sweep( } } -function _find_vnf_z_edge_path(vnf, z) = +function _find_vnf_edge_paths(vnf, idx, val) = let( verts = vnf[0], faces = vnf[1], - goods = [for (v = verts) approx(v.z, z)], + goods = [for (v = verts) approx(v[idx], val)], fragments = [ for (face = faces) for (seg = pair(face, wrap=true)) if (goods[seg[0]] && goods[seg[1]]) path2d([verts[seg[0]], verts[seg[1]]]) ] - ) _assemble_a_path_from_fragments(fragments, rightmost=true)[0]; + ) _assemble_path_fragments(fragments); // Function&Module: textured_revolution() // Usage: As Function -// vnf = textured_revolution(path, texture, tex_size, [tscale=], ...); -// vnf = textured_revolution(path, texture, counts=, [tscale=], ...); +// vnf = textured_revolution(region, texture, tex_size, [tscale=], ...); +// vnf = textured_revolution(region, texture, counts=, [tscale=], ...); // Usage: As Module -// textured_revolution(path, texture, tex_size, [tscale=], ...) [ATTACHMENTS]; -// textured_revolution(path, texture, counts=, [tscale=], ...) [ATTACHMENTS]; +// textured_revolution(region, texture, tex_size, [tscale=], ...) [ATTACHMENTS]; +// textured_revolution(region, texture, counts=, [tscale=], ...) [ATTACHMENTS]; // Topics: Sweep, Extrusion, Textures, Knurling // Description: -// Given a single 2D path, fully in the X+ half-plane, revolves that path around the Z axis (after rotating its Y+ to Z+). -// This creates a solid from that surface of revolution, capped top and bottom, with the sides covered in a given tiled texture. -// If the path to be revolved is clockwise on the XY plane, then the output shape should have its faces pointed outwards, -// though you can use `reverse=true` to reverse the face directions if needed. It is recommended that you preview with -// OpenSCAD's "Thrown Together" view mode, to verify the orientation of the faces. If you see purple, then your model is -// non-manifold, and not 3D print-able. +// Given a 2D region or path, fully in the X+ half-plane, revolves that shape around the Z axis (after rotating its Y+ to Z+). +// This creates a solid from that surface of revolution, possibly capped top and bottom, with the sides covered in a given tiled texture. // The texture can be given in one of three ways: -// - As a texture name string. (See {{get_texture()}} for supported named textures.) +// - As a texture name string. (See {{texture()}} for supported named textures.) // - As a 2D array of evenly spread height values. (AKA a heightfield.) // - As a VNF texture tile. A VNF tile exactly defines a surface from `[0,0]` to `[1,1]`, with the Z coordinates // being the height of the texture point from the surface. VNF tiles MUST be able to tile in both X and Y @@ -2738,23 +2742,22 @@ function _find_vnf_z_edge_path(vnf, z) = // One script to convert a grayscale image to a texture heightfield array in a .scad file can be found at: // https://raw.githubusercontent.com/revarbat/BOSL2/master/scripts/img2scad.py // Arguments: -// path = The path to sweep/extrude. -// texture = A texture name string, or a rectangular array of scalar height values (0.0 to 1.0), or a VNF tile that defines the texture to apply to the revolution surface. See {{get_texture()}} for what named textures are supported. +// shape = The path or region to sweep/extrude. +// texture = A texture name string, or a rectangular array of scalar height values (0.0 to 1.0), or a VNF tile that defines the texture to apply to the revolution surface. See {{texture()}} for what named textures are supported. // tex_size = An optional 2D target size for the textures. Actual texture sizes will be scaled somewhat to evenly fit the available surface. Default: `[5,5]` -// --- -// shift = [X,Y] amount to translate the top, relative to the bottom. Default: [0,0] // tscale = Scaling multiplier for the texture depth. +// --- // inset = If numeric, lowers the texture into the surface by that amount, before the tscale multiplier is applied. If `true`, insets by exactly `1`. Default: `false` // rot = If true, rotates the texture 90º. -// caps = (function only) If true, create endcaps for the extruded shape. Default: `true` -// wrap = (function only) If true, the path is considered a closed polygon. Useful mainly for things like making a textured torus. Default: `false` +// shift = [X,Y] amount to translate the top, relative to the bottom. Default: [0,0] +// closed = If false, and shape is given as a path, then the revolved path will be sealed to the axis of rotation with untextured caps. Default: `true` +// angle = The number of degrees counter-clockwise from X+ to revolve around the Z axis. Default: `360` // style = The triangulation style used. See {{vnf_vertex_array()}} for valid styles. Used only with heightfield type textures. Default: `"min_edge"` -// reverse = If the default faces are facing the wrong way, you can reverse them by setting this to `true`. Default: `false` // counts = If given instead of tex_size, gives the tile repetition counts for textures over the surface length and height. // anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#subsection-anchor). Default: `CENTER` // spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#subsection-spin). Default: `0` // orient = Vector to rotate top towards, after spin. See [orient](attachments.scad#subsection-orient). Default: `UP` -// See Also: textured_revolution(), textured_cylinder(), textured_linear_sweep(), heightfield(), cylindrical_heightfield(), get_texture() +// See Also: textured_revolution(), textured_cylinder(), textured_linear_sweep(), heightfield(), cylindrical_heightfield(), texture() // Example: // path = right(50, p=circle(d=40)); // textured_revolution(path, "vnf_bricks", tex_size=[10,10], tscale=0.5, wrap=true, caps=false, style="concave"); @@ -2794,50 +2797,34 @@ function _find_vnf_z_edge_path(vnf, z) = // vnf = textured_revolution(path, "trunc_pyramids", tex_size=[5,5], tscale=1, style="convex"); // vnf_polyhedron(vnf, convexity=10); function textured_revolution( - path, texture, tex_size, - tscale=1, inset=false, rot=false, - caps=true, wrap=false, shift=[0,0], - style="min_edge", reverse=false, - counts + shape, texture, tex_size, tscale=1, + inset=false, rot=false, + shift=[0,0], closed=true, angle=360, + style="min_edge", counts, samples ) = - assert(is_path(path,[2])) - assert(is_bool(caps)) - assert(is_bool(wrap)) - assert(is_bool(reverse)) + assert(angle>0 && angle<=360) + assert(is_path(shape,[2]) || is_region(shape)) + assert(is_undef(samples) || is_int(samples)) + assert(is_bool(closed)) assert(counts==undef || is_vector(counts,2)) assert(tex_size==undef || is_vector(tex_size,2)) assert(is_bool(rot) || in_list(rot,[0,90,180,270])) let( - tex = is_string(texture)? get_texture(texture) : texture, + regions = is_path(shape,2)? [[shape]] : region_parts(shape) + ) + assert(closed || is_path(shape,2)) + let( + tex = is_string(texture)? texture(texture) : texture, texture = !rot? tex : is_vnf(tex)? zrot(is_num(rot)?rot:90, cp=[1/2,1/2], p=tex) : rot==180? reverse([for (row=tex) reverse(row)]) : rot==270? [for (row=transpose(tex)) reverse(row)] : reverse(transpose(tex)), - plen = path_length(path), - bounds = pointlist_bounds(path), - maxx = bounds[1].x, - miny = bounds[0].y, - maxy = bounds[1].y, - h = maxy - miny, - circumf = 2 * PI * maxx, - counts = is_vector(counts,2)? counts : - is_vector(tex_size,2) - ? [max(1,round(circumf/tex_size.x)), max(1,round(plen/tex_size.y))] - : [ceil(6*circumf/(maxy-miny)), 6], - inset = is_num(inset)? inset : inset? 1 : 0, - samples = is_vnf(texture)? 12 : len(texture), - obases = resample_path(path, n=counts.y * samples + (wrap?0:1), closed=wrap), - onorms = path_normals(obases, closed=wrap), - rbases = wrap? close_path(obases) : obases, - rnorms = wrap? close_path(onorms) : onorms, - bases = xrot(90, p=path3d(rbases)), - norms = xrot(90, p=path3d(rnorms)), - vnf = is_vnf(texture) - ? let( // VNF tile texture - tbounds = pointlist_bounds(texture[0]), - min_xy = point2d(tbounds[0]), - max_xy = point2d(tbounds[1]) + check_tex = is_vnf(texture) + ? let( // Validate VNF tile texture + bounds = pointlist_bounds(texture[0]), + min_xy = point2d(bounds[0]), + max_xy = point2d(bounds[1]) ) assert(min_xy==[0,0] && max_xy==[1,1], "VNF tiles must span exactly from [0,0] to [1,1] in the X and Y components.") let( @@ -2847,94 +2834,229 @@ function textured_revolution( allgoody = all(vverts, function(v) any(vverts, function(w) w==[v.x, 1-v.y, v.z])) ) assert(allgoodx && allgoody, "All VNF tile edge vertices must line up with a vertex on the opposite side of the tile.") - let( - tex2 = vnf_slice(vnf_slice(texture, "X", list([1/8:1/8:7/8])), "Y", list([1/8:1/8:7/8])), - sorted_tile = _vnf_sort_vertices(tex2, idx=[0,1]), - vertzs = group_sort(sorted_tile[0], idx=0), - col_vnf = vnf_join([ - for (j = [0:1:counts.y-1]) [ - [ - for (group = vertzs) each [ - for (vert = group) let( - part = (j + (1-vert.y)) * samples, - u = floor(part), - uu = part - u, - tscale = - wrap? tscale : - caps && j==0 && approx(vert.y,0)? 0 : - caps && j==counts.y-1 && approx(vert.y,1)? 0 : - tscale, - base = lerp(select(bases,u), select(bases,u+1), uu), - norm = unit(lerp(select(norms,u), select(norms,u+1), uu)), - texh = (vert.z - inset) * tscale * (base.x / maxx), - xyz = base - norm * texh - ) zrot(vert.x*360/counts.x, p=xyz) - ] - ], - sorted_tile[1] - ] - ]), - vnf1 = vnf_join([ - for (i = [0:1:counts.x-1]) - zrot(i*360/counts.x, col_vnf) - ]), - skmat = down(-miny) * skew(sxz=shift.x/h, syz=shift.y/h) * up(-miny), - vnf_out = wrap? apply(skmat,vnf1) : - let( - bpath = _find_vnf_z_edge_path(vnf1,-h/2), - vnf2 = vnf_from_region(bpath, down(h/2), reverse=true), - vnf3 = vnf_from_region(bpath, up(h/2), reverse=false) - ) apply(skmat, vnf_join([vnf1, vnf2, vnf3])) - ) vnf_out - : let( // Heightfield texture - texcnt = [len(texture[0]), len(texture)], - skmat = down(-miny) * skew(sxz=shift.x/h, syz=shift.y/h) * up(-miny), - tiles = transpose([ - for (j = [0:1:counts.x-1], tj = [0:1:texcnt.x-1]) let( - v = (j + (tj/texcnt.x)) / counts.x, - mat = skmat * zrot(v*360) - ) apply(mat, [ - for (i = [0:1:counts.y-(wrap?1:0)], ti = [0:1:texcnt.y-1]) - if (i != counts.y || ti == 0) - let( - part = (i + (ti/texcnt.y)) * samples, - u = floor(part), - uu = part - u, - tscale = - wrap? tscale : - caps && i==0 && ti==0? 0 : - caps && i==counts.y && ti==0? 0 : - tscale, - base = lerp(bases[u], select(bases,u+1), uu), - norm = unit(lerp(norms[u], select(norms,u+1), uu)), - texh = (texture[ti][tj] - inset) * tscale * (base.x / maxx), - xyz = base - norm * texh - ) xyz - ]) - ]) - ) vnf_vertex_array( - tiles, caps=caps, style=style, reverse=reverse, - col_wrap=true, row_wrap=wrap + : let( // Validate heightfield texture. + tex_dim = list_shape(texture) ) - ) vnf; + assert(len(tex_dim) == 2, "Heightfield texture must be a 2D square array of scalar heights.") + assert(all_defined(tex_dim), "Heightfield texture must be a 2D square array of scalar heights."), + inset = is_num(inset)? inset : inset? 1 : 0, + samples = !is_vnf(texture)? len(texture) : + is_num(samples)? samples : 8, + bounds = pointlist_bounds(flatten(flatten(regions))), + maxx = bounds[1].x, + miny = bounds[0].y, + maxy = bounds[1].y, + h = maxy - miny, + circumf = 2 * PI * maxx, + tile = !is_vnf(texture)? texture : + let( + utex = samples<=1? texture : + let( + s = 1 / samples, + slices = list([s : s : 1-s/2]), + vnfx = vnf_slice(texture, "X", slices), + vnfy = vnf_slice(vnfx, "Y", slices), + vnft = vnf_triangulate(vnfy) + ) vnft + ) _vnf_sort_vertices(utex, idx=[0,1]), + vertzs = is_vnf(texture)? group_sort(tile[0], idx=0) : undef, + counts_x = is_vector(counts,2)? counts.x : + is_vector(tex_size,2) + ? max(1,round(angle/360*circumf/tex_size.x)) + : ceil(6*angle/360*circumf/h), + full_vnf = vnf_join([ + for (rgn = regions) let( + rgn_wall_vnf = vnf_join([ + for (path = rgn) let( + plen = path_length(path, closed=closed), + counts_y = is_vector(counts,2)? counts.y : + is_vector(tex_size,2)? max(1,round(plen/tex_size.y)) : 6, + obases = resample_path(path, n=counts_y * samples + (closed?0:1), closed=closed), + onorms = path_normals(obases, closed=closed), + rbases = closed? close_path(obases) : obases, + rnorms = closed? close_path(onorms) : onorms, + bases = xrot(90, p=path3d(rbases)), + norms = xrot(90, p=path3d(rnorms)), + vnf = is_vnf(texture) + ? vnf_join([ // VNF tile texture + for (j = [0:1:counts_y-1]) [ + [ + for (group = vertzs) each [ + for (vert = group) let( + part = (j + (1-vert.y)) * samples, + u = floor(part), + uu = part - u, + tscale = + closed? tscale : + !closed && j==0 && approx(vert.y,0)? 0 : + !closed && j==counts_y-1 && approx(vert.y,1)? 0 : + tscale, + base = lerp(select(bases,u), select(bases,u+1), uu), + norm = unit(lerp(select(norms,u), select(norms,u+1), uu)), + texh = (vert.z - inset) * tscale * (base.x / maxx), + xyz = base - norm * texh + ) zrot(vert.x*angle/counts_x, p=xyz) + ] + ], + tile[1] + ] + ]) + : let( // Heightfield texture + texcnt = [len(texture[0]), len(texture)], + tiles = transpose([ + for (j = [0,1], tj = [0:1:texcnt.x-1]) + if (j == 0 || tj == 0) + let( + v = (j + (tj/texcnt.x)) / counts_x, + mat = zrot(v*angle) + ) apply(mat, [ + for (i = [0:1:counts_y-(closed?1:0)], ti = [0:1:texcnt.y-1]) + if (i != counts_y || ti == 0) + let( + part = (i + (ti/texcnt.y)) * samples, + u = floor(part), + uu = part - u, + tscale = + closed? tscale : + !closed && i==0 && ti==0? 0 : + !closed && i==counts_y && ti==0? 0 : + tscale, + base = lerp(bases[u], select(bases,u+1), uu), + norm = unit(lerp(norms[u], select(norms,u+1), uu)), + texh = (texture[ti][tj] - inset) * tscale * (base.x / maxx), + xyz = base - norm * texh + ) xyz + ]) + ]) + ) vnf_vertex_array( + tiles, caps=false, style=style, + col_wrap=(angle==360), row_wrap=closed + ) + ) vnf + ]), + walls_vnf = vnf_join([ + for (i = [0:1:counts_x-1]) + zrot(i*angle/counts_x, rgn_wall_vnf) + ]), + endcap_vnf = angle == 360? EMPTY_VNF : + let( + cap_rgn = [ + for (path = rgn) let( + plen = path_length(path, closed=closed), + counts_y = is_vector(counts,2)? counts.y : + is_vector(tex_size,2)? max(1,round(plen/tex_size.y)) : 6, + obases = resample_path(path, n=counts_y * samples + (closed?0:1), closed=closed), + bases = closed? close_path(obases) : obases, + ppath = is_vnf(texture) + ? [ // VNF tile texture + for (j = [0:1:counts_y-1]) + for (group = vertzs, vert = group) + if (vert.x == 0) let( + part = (j + vert.y) * samples, + u = floor(part), + uu = part - u + ) + lerp(select(bases,u), select(bases,u+1), uu) + ] + : let( // Heightfield texture + texcnt = [len(texture[0]), len(texture)] + ) [ + for (i = [0:1:counts_y-(closed?1:0)], ti = [0:1:texcnt.y-1]) + if (i != counts_y || ti == 0) + let( + part = (i + (ti/texcnt.y)) * samples, + u = floor(part), + uu = part - u + ) + lerp(select(bases,u), select(bases,u+1), uu) + ], + path = closed? ppath : [ + [0, ppath[0].y], + each ppath, + [0, last(ppath).y], + ] + ) deduplicate(path, closed=closed) + ], + vnf2 = vnf_from_region(cap_rgn, xrot(90), reverse=false), + vnf3 = vnf_from_region(cap_rgn, rot([90,0,angle]), reverse=true) + ) vnf_join([vnf2, vnf3]), + topcap_vnf = closed? EMPTY_VNF : + let( + rad = last(rgn[0]).x, + top_rgn = [ + for (path = rgn) let( + ppath = is_vnf(texture) + ? [ // VNF tile texture + for (j = [0:1:counts_x-1]) + for (vert = tile[0]) + if (vert.y == 1) let( + u = (j + vert.x) / counts_x + ) + polar_to_xy(rad, angle*u) + ] + : let( // Heightfield texture + texcnt = [len(texture[0]), len(texture)] + ) [ + for (i = [0:1:counts_x], ti = [0:1:texcnt.x-1]) + if (i != counts_x || ti == 0) + let( + u = (i + (ti / texcnt.x)) / counts_x + ) + polar_to_xy(rad, angle*u) + ], + path = closed? ppath : concat(ppath, [[0,0]]) + ) deduplicate(path, closed=closed) + ] + ) vnf_from_region(top_rgn, up(last(rgn[0]).y), reverse=true), + botcap_vnf = closed? EMPTY_VNF : + let( + rad = rgn[0][0].x, + bot_rgn = [ + for (path = rgn) let( + ppath = is_vnf(texture) + ? [ // VNF tile texture + for (j = [0:1:counts_x-1]) + for (vert = tile[0]) + if (vert.y == 0) let( + u = (j + vert.x) / counts_x + ) + polar_to_xy(rad, angle*u) + ] + : let( // Heightfield texture + texcnt = [len(texture[0]), len(texture)] + ) [ + for (i = [0:1:counts_x], ti = [0:1:texcnt.x-1]) + if (i != counts_x || ti == 0) + let( + u = (i + (ti / texcnt.x)) / counts_x + ) + polar_to_xy(rad, angle*u) + ], + path = closed? ppath : concat(ppath, [[0,0]]) + ) deduplicate(path, closed=closed) + ] + ) vnf_from_region(bot_rgn, up(rgn[0][0].y), reverse=false) + ) vnf_join([walls_vnf, endcap_vnf, botcap_vnf, topcap_vnf]) + ]), + skmat = down(-miny) * skew(sxz=shift.x/h, syz=shift.y/h) * up(-miny) + ) apply(skmat, full_vnf); module textured_revolution( - path, texture, tex_size, - tscale=1, inset=false, rot=false, - caps=true, wrap=false, shift=[0,0], - style="min_edge", reverse=false, - atype="surface", - convexity=10, counts, + shape, texture, tex_size, tscale=1, + inset=false, rot=false, shift=[0,0], + closed=true, angle=360, + style="min_edge", atype="surface", + convexity=10, counts, samples, anchor=CENTER, spin=0, orient=UP ) { assert(in_list(atype, ["surface","extent"])); vnf = textured_revolution( - path, texture, tex_size=tex_size, + shape, texture, tex_size=tex_size, tscale=tscale, inset=inset, rot=rot, - caps=caps, wrap=wrap, style=style, - reverse=reverse, shift=shift, - counts=counts + closed=closed, style=style, + shift=shift, angle=angle, + samples=samples, counts=counts ); geom = atype=="surface" ? attach_geom(vnf=vnf, extent=false) @@ -2957,7 +3079,7 @@ module textured_revolution( // Description: // Creates a cylinder or cone with optional chamfers or roundings, covered in a textured surface. // The texture can be given in one of three ways: -// - As a texture name string. (See {{get_texture()}} for supported named textures.) +// - As a texture name string. (See {{texture()}} for supported named textures.) // - As a 2D array of evenly spread height values. (AKA a heightfield.) // - As a VNF texture tile. A VNF tile exactly defines a surface from `[0,0]` to `[1,1]`, with the Z coordinates // being the height of the texture point from the surface. VNF tiles MUST be able to tile in both X and Y @@ -2967,7 +3089,7 @@ module textured_revolution( // Arguments: // h | l = The height of the cylinder. // r = The radius of the cylinder. -// texture = A texture name string, or a rectangular array of scalar height values (0.0 to 1.0), or a VNF tile that defines the texture to apply to the cylinder wall surfaces. See {{get_texture()}} for what named textures are supported. +// texture = A texture name string, or a rectangular array of scalar height values (0.0 to 1.0), or a VNF tile that defines the texture to apply to the cylinder wall surfaces. See {{texture()}} for what named textures are supported. // tex_size = An optional 2D target size for the textures. Actual texture sizes will be scaled somewhat to evenly fit the available surface. Default: `[5,5]` // --- // r1 = The radius of the bottom of the cylinder. @@ -2981,7 +3103,6 @@ module textured_revolution( // caps = (function only) If true, create endcaps for the extruded shape. Default: `true` // shift = [X,Y] amount to translate the top, relative to the bottom. Default: [0,0] // style = The triangulation style used. See {{vnf_vertex_array()}} for valid styles. Default: `"min_edge"` -// reverse = If the default faces are facing the wrong way, you can reverse them by setting this to `true`. Default: `false` // counts = If given instead of tex_size, gives the tile repetition counts for textures over the surface length and height. // chamfer = If given, chamfers the top and bottom of the cylinder by the given size. If given a negative size, creates a chamfer that juts *outward* from the cylinder. // chamfer1 = If given, chamfers the bottom of the cylinder by the given size. If given a negative size, creates a chamfer that juts *outward* from the cylinder. @@ -2992,7 +3113,7 @@ module textured_revolution( // anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#subsection-anchor). Default: `CENTER` // spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#subsection-spin). Default: `0` // orient = Vector to rotate top towards, after spin. See [orient](attachments.scad#subsection-orient). Default: `UP` -// See Also: textured_revolution(), textured_cylinder(), textured_linear_sweep(), heightfield(), cylindrical_heightfield(), get_texture() +// See Also: textured_revolution(), textured_cylinder(), textured_linear_sweep(), heightfield(), cylindrical_heightfield(), texture() // Examples: // textured_cylinder(h=40, r=20, texture="diamonds", tex_size=[5,5]); // textured_cylinder(h=40, r1=20, r2=15, texture="pyramids", tex_size=[5,5], style="convex"); @@ -3003,8 +3124,7 @@ function textured_cylinder( h, r, texture, tex_size=[1,1], counts, tscale=1, inset=false, rot=false, caps=true, style="min_edge", - reverse=false, shift=[0,0], - l, r1, r2, d, d1, d2, + shift=[0,0], l, r1, r2, d, d1, d2, chamfer, chamfer1, chamfer2, rounding, rounding1, rounding2 ) = @@ -3032,11 +3152,10 @@ function textured_cylinder( else [r2,h/2], ], vnf = textured_revolution( - reverse(path), texture, + reverse(path), texture, closed=false, tex_size=tex_size, counts=counts, tscale=tscale, inset=inset, rot=rot, - caps=caps, style=style, reverse=reverse, - shift=shift + style=style, shift=shift ) ) vnf; @@ -3044,7 +3163,7 @@ function textured_cylinder( module textured_cylinder( h, r, texture, tex_size=[1,1], counts, tscale=1, inset=false, rot=false, - style="min_edge", reverse=false, shift=[0,0], + style="min_edge", shift=[0,0], l, r1, r2, d, d1, d2, chamfer, chamfer1, chamfer2, rounding, rounding1, rounding2, @@ -3063,7 +3182,7 @@ module textured_cylinder( tscale=tscale, inset=inset, rot=rot, counts=counts, tex_size=tex_size, caps=true, style=style, - reverse=reverse, shift=shift, + shift=shift, chamfer1=chamf1, chamfer2=chamf2, rounding1=round1, rounding2=round2 ); From 9a8202f54ec1cfb9110fec412a8e9ff5383d5577 Mon Sep 17 00:00:00 2001 From: Garth Minette Date: Fri, 15 Jul 2022 18:58:15 -0700 Subject: [PATCH 4/6] textured_linear_sweep() bugfixes. --- paths.scad | 1 + skin.scad | 88 ++++++++++++++++++++++++++++++++++++++++-------------- 2 files changed, 66 insertions(+), 23 deletions(-) diff --git a/paths.scad b/paths.scad index 9af7a71..7ba4171 100644 --- a/paths.scad +++ b/paths.scad @@ -1089,6 +1089,7 @@ function _extreme_angle_fragment(seg, fragments, rightmost=true, eps=EPSILON) = /// eps = The epsilon error value to determine whether two points coincide. Default: `EPSILON` (1e-9) function _assemble_a_path_from_fragments(fragments, rightmost=true, startfrag=0, eps=EPSILON) = len(fragments)==0? [[],[]] : + len(fragments)==1? [fragments[0],[]] : let( path = fragments[startfrag], newfrags = [for (i=idx(fragments)) if (i!=startfrag) fragments[i]] diff --git a/skin.scad b/skin.scad index c589899..8164c49 100644 --- a/skin.scad +++ b/skin.scad @@ -2266,7 +2266,7 @@ function texture(tex,n,m,o) = [ [0,1,0], [1,1,0], [1/2,1/2,m], [0,0,0], [1,0,0] ], [ [2,0,1], [2,1,4], [2,4,3], [2,3,0] ] ] : - tex=="trunc_pyramids"? let(n=default(n,3), m=default(m,1)) [repeat(0,n+1), each repeat([0, each repeat(m,n+1)], n+1)] : + tex=="trunc_pyramids"? let(n=default(n,3), m=default(m,1)) [repeat(0,n+2), each repeat([0, each repeat(m,n+1)], n+1)] : tex=="vnf_trunc_pyramids"? let(n=default(n,0.25), m=default(m,1)) [ [ each path3d(square(1)), @@ -2572,9 +2572,23 @@ function textured_linear_sweep( ) assert(len(tex_dim) == 2, "Heightfield texture must be a 2D square array of scalar heights.") assert(all_defined(tex_dim), "Heightfield texture must be a 2D square array of scalar heights."), - skmat = down(h/2) * skew(sxz=shift.x/h, syz=shift.y/h) * up(h/2), + sorted_tile = + !is_vnf(texture)? texture : + samples<=1? texture : + let( + s = 1/samples, + vnf = vnf_slice(texture, "X", list([s:s:1-s/2])) + ) _vnf_sort_vertices(vnf, idx=[1,0]), + vertzs = !is_vnf(sorted_tile)? undef : + group_sort(sorted_tile[0], idx=1), + tpath = is_vnf(sorted_tile) + ? _find_vnf_tile_bottom_edge_path(sorted_tile,0) + : let( + row = last(sorted_tile), + rlen = len(row) + ) [for (i = [0:1:rlen]) [i/rlen, row[i%rlen]]], tmat = scale(scale) * zrot(twist) * up(h/2), - final_vnf = vnf_join([ + pre_skew_vnf = vnf_join([ for (rgn = regions) let( walls_vnf = vnf_join([ for (path = rgn) let( @@ -2584,15 +2598,12 @@ function textured_linear_sweep( is_vector(tex_size,2) ? [round(plen/tex_size.x), max(1,round(h/tex_size.y)), ] : [ceil(6*plen/h), 6], - bases = close_path(resample_path(path, n=counts.x * samples, closed=true)), - norms = close_path(path_normals(bases, closed=true)), + obases = resample_path(path, n=counts.x * samples, closed=true), + onorms = path_normals(obases, closed=true), + bases = close_path(obases), + norms = close_path(onorms), vnf = is_vnf(texture) ? let( // VNF tile texture - tex2 = samples<=1? texture : - let( s = 1/samples ) - vnf_slice(texture, "X", list([s:s:1-s/2])), - sorted_tile = _vnf_sort_vertices(tex2, idx=[1,0]), - vertzs = group_sort(sorted_tile[0], idx=1), row_vnf = vnf_join([ for (j = [0:1:counts.x-1]) [ [ @@ -2618,8 +2629,8 @@ function textured_linear_sweep( [ for (group = rvertzs) let( v = (i + group[0].z) / counts.y, - mat = move(shift*v) * - scale(lerp([1,1,1],scale,v)) * + sc = lerp([1,1,1], scale, v), + mat = scale(sc) * zrot(twist*v) * up(((i/counts.y)-0.5)*h) * zscale(h/counts.y) @@ -2653,9 +2664,9 @@ function textured_linear_sweep( if (i != counts.y || ti == 0) let( v = (i + (ti/texcnt.y)) / counts.y, + sc = lerp([1,1,1], scale, v), mat = down((v-0.5)*h) * - move(shift*v) * - scale(lerp([1,1,1],scale,v)) * + scale(sc) * zrot(twist*v) ) apply(mat, tile_rows[ti]) ] @@ -2665,11 +2676,36 @@ function textured_linear_sweep( ) ) vnf ]), - brgn = _find_vnf_edge_paths(walls_vnf,2,-h/2), + brgn = [ + for (path = rgn) let( + path = reverse(path), + plen = path_length(path, closed=true), + counts = is_vector(counts,2)? counts : + is_vector(tex_size,2) + ? [round(plen/tex_size.x), max(1,round(h/tex_size.y)), ] + : [ceil(6*plen/h), 6], + obases = resample_path(path, n=counts.x * samples, closed=true), + onorms = path_normals(obases, closed=true), + bases = close_path(obases), + norms = close_path(onorms) + ) [ + for (j = [0:1:counts.x-1], vert = tpath) let( + part = (j + vert.x) * samples, + u = floor(part), + uu = part - u, + texh = (vert.y - inset) * tscale, + base = lerp(bases[u], select(bases,u+1), uu), + norm = unit(lerp(norms[u], select(norms,u+1), uu)), + xy = base + norm * texh + ) xy + ] + ], bot_vnf = vnf_from_region(brgn, down(h/2), reverse=true), top_vnf = vnf_from_region(brgn, tmat, reverse=false) ) vnf_join([walls_vnf, bot_vnf, top_vnf]) ]), + skmat = down(h/2) * skew(sxz=shift.x/h, syz=shift.y/h) * up(h/2), + final_vnf = apply(skmat, pre_skew_vnf), cent = centroid(region), anchors = [ named_anchor("centroid_top", point3d(cent, h/2), UP), @@ -2683,7 +2719,7 @@ module textured_linear_sweep( path, texture, tex_size=[5,5], h, inset=false, rot=false, tscale=1, twist, scale, shift, samples, - style="min_edge", reverse=false, l, counts, + style="min_edge", l, counts, anchor=CENTER, spin=0, orient=UP, convexity=10 ) { @@ -2693,7 +2729,7 @@ module textured_linear_sweep( tex_size=tex_size, counts=counts, inset=inset, rot=rot, tscale=tscale, twist=twist, scale=scale, shift=shift, - samples=samples, style=style, reverse=reverse, + samples=samples, style=style, anchor=CENTER, spin=0, orient=UP ); cent = centroid(path); @@ -2708,18 +2744,24 @@ module textured_linear_sweep( } } -function _find_vnf_edge_paths(vnf, idx, val) = +function _find_vnf_tile_bottom_edge_path(vnf, val) = let( verts = vnf[0], faces = vnf[1], - goods = [for (v = verts) approx(v[idx], val)], + goods = [for (v = verts) approx(v[1], val)], fragments = [ for (face = faces) for (seg = pair(face, wrap=true)) - if (goods[seg[0]] && goods[seg[1]]) - path2d([verts[seg[0]], verts[seg[1]]]) - ] - ) _assemble_path_fragments(fragments); + let(s0 = seg[0], s1 = seg[1]) + if (goods[s0] && goods[s1]) + let(v0 = verts[s0], v1 = verts[s1]) + v0.x <= v1.x? [[v0.x,v0.z], [v1.x,v1.z]] : + [[v1.x,v1.z], [v0.x,v0.z]] + ], + sfrags = sort(fragments, idx=[0,1]), + rpath = _assemble_a_path_from_fragments(sfrags)[0], + opath = rpath[0].x > last(rpath).x? reverse(rpath) : rpath + ) opath; // Function&Module: textured_revolution() From 63039f5f9754e1bccdc35fd684dfce3c5613b72e Mon Sep 17 00:00:00 2001 From: Garth Minette Date: Fri, 15 Jul 2022 19:29:12 -0700 Subject: [PATCH 5/6] Fixed textured_revolution() example. --- skin.scad | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skin.scad b/skin.scad index 8164c49..dafd3e9 100644 --- a/skin.scad +++ b/skin.scad @@ -2802,7 +2802,7 @@ function _find_vnf_tile_bottom_edge_path(vnf, val) = // See Also: textured_revolution(), textured_cylinder(), textured_linear_sweep(), heightfield(), cylindrical_heightfield(), texture() // Example: // path = right(50, p=circle(d=40)); -// textured_revolution(path, "vnf_bricks", tex_size=[10,10], tscale=0.5, wrap=true, caps=false, style="concave"); +// textured_revolution(path, "vnf_bricks", tex_size=[10,10], tscale=0.5, style="concave"); // Example: // tex = [ // [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], From e69b881ad09aa4d87d855f1cea2f885dcc947a53 Mon Sep 17 00:00:00 2001 From: Garth Minette Date: Fri, 15 Jul 2022 19:35:14 -0700 Subject: [PATCH 6/6] Another textured_revolution() example bugfix. --- skin.scad | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skin.scad b/skin.scad index dafd3e9..f4c796a 100644 --- a/skin.scad +++ b/skin.scad @@ -2836,7 +2836,7 @@ function _find_vnf_tile_bottom_edge_path(vnf, val) = // each arc(r=20, corner=[[20,20],[10,0],[20,-20]]), // [20,-20], [20,-30], // ]; -// vnf = textured_revolution(path, "trunc_pyramids", tex_size=[5,5], tscale=1, style="convex"); +// vnf = textured_revolution(path, closed=false, texture="trunc_pyramids", tex_size=[5,5], tscale=1, style="convex"); // vnf_polyhedron(vnf, convexity=10); function textured_revolution( shape, texture, tex_size, tscale=1,