Merge pull request #898 from revarbat/revarbat_dev

Improvements to  vnf_validate()
This commit is contained in:
Revar Desmera 2022-07-15 20:26:57 -07:00 committed by GitHub
commit 919e2b4d7b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 545 additions and 352 deletions

View file

@ -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) /// 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) = function _assemble_a_path_from_fragments(fragments, rightmost=true, startfrag=0, eps=EPSILON) =
len(fragments)==0? [[],[]] : len(fragments)==0? [[],[]] :
len(fragments)==1? [fragments[0],[]] :
let( let(
path = fragments[startfrag], path = fragments[startfrag],
newfrags = [for (i=idx(fragments)) if (i!=startfrag) fragments[i]] newfrags = [for (i=idx(fragments)) if (i!=startfrag) fragments[i]]

583
skin.scad
View file

@ -2100,9 +2100,9 @@ function associate_vertices(polygons, split, curpoly=0) =
// Section: Texturing // Section: Texturing
// DefineHeader(Table;Headers=Texture Name|Type|Description): Texture Values // DefineHeader(Table;Headers=Texture Name|Type|Description): Texture Values
// Function: get_texture() // Function: texture()
// Usage: // Usage:
// tx = get_texture(tex, [n], [m]); // tx = texture(tex, [n], [m]);
// Topics: Textures, Knurling // Topics: Textures, Knurling
// Description: // Description:
// Given a texture name, and two optional variables, returns a heightfield texture as a 2D array of scalars. // 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_hex_grid" = VNF Tile = A hexagonal grid of thin lines.
// "vnf_pyramids" = VNF Tile = Like "pyramids", but slower and more consistent in triangulation. // "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. // "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. // Example(3D): "ribs" texture.
// tex = get_texture("ribs"); // tex = texture("ribs");
// textured_linear_sweep( // textured_linear_sweep(
// rect(50), tex, h=40, // rect(50), tex, h=40, tscale=3,
// tex_size=[5,10], style="concave" // tex_size=[10,10], style="concave"
// ); // );
// Example(3D): Truncated "trunc_ribs" texture. // Example(3D): Truncated "trunc_ribs" texture.
// tex = get_texture("trunc_ribs"); // tex = texture("trunc_ribs");
// textured_linear_sweep( // textured_linear_sweep(
// rect(50), tex, h=40, // rect(50), tex, h=40, tscale=3,
// tex_size=[5,10], style="concave" // tex_size=[10,10], style="concave"
// ); // );
// Example(3D): "wave_ribs" texture. // Example(3D): "wave_ribs" texture.
// tex = get_texture("wave_ribs"); // tex = texture("wave_ribs");
// textured_linear_sweep( // textured_linear_sweep(
// rect(50), tex, h=40, // rect(50), tex, h=40,
// tex_size=[10,10], style="concave" // tex_size=[10,10], style="concave"
// ); // );
// Example(3D): "diamonds" texture. // Example(3D): "diamonds" texture.
// tex = get_texture("diamonds"); // tex = texture("diamonds");
// textured_linear_sweep( // textured_linear_sweep(
// rect(50), tex, h=40, // rect(50), tex, h=40,
// tex_size=[10,10], style="concave" // tex_size=[10,10], style="concave"
// ); // );
// Example(3D): "vnf_diamonds" texture. Slower, but more consistent around complex curves. // Example(3D): "vnf_diamonds" texture. Slower, but more consistent around complex curves.
// tex = get_texture("vnf_diamonds"); // tex = texture("vnf_diamonds");
// textured_linear_sweep( // textured_linear_sweep(
// rect(50), tex, h=40, // rect(50), tex, h=40,
// tex_size=[10,10] // tex_size=[10,10]
// ); // );
// Example(3D): "pyramids" texture. // Example(3D): "pyramids" texture.
// tex = get_texture("pyramids"); // tex = texture("pyramids");
// textured_linear_sweep( // textured_linear_sweep(
// rect(50), tex, h=40, // rect(50), tex, h=40,
// tex_size=[10,10], style="convex" // tex_size=[10,10], style="convex"
// ); // );
// Example(3D): "vnf_pyramids" texture. Slower, but more consistent around complex curves. // Example(3D): "vnf_pyramids" texture. Slower, but more consistent around complex curves.
// tex = get_texture("vnf_pyramids"); // tex = texture("vnf_pyramids");
// textured_linear_sweep( // textured_linear_sweep(
// rect(50), tex, h=40, // rect(50), tex, h=40,
// tex_size=[10,10] // tex_size=[10,10]
// ); // );
// Example(3D): "trunc_pyramids" texture. // Example(3D): "trunc_pyramids" texture.
// tex = get_texture("trunc_pyramids"); // tex = texture("trunc_pyramids");
// textured_linear_sweep( // textured_linear_sweep(
// rect(50), tex, h=40, // rect(50), tex, h=40,
// tex_size=[10,10], style="convex" // tex_size=[10,10], style="convex"
// ); // );
// Example(3D): "vnf_trunc_pyramids" texture. Slower, but more consistent around complex curves. // 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( // textured_linear_sweep(
// rect(50), tex, h=40, // rect(50), tex, h=40,
// tex_size=[10,10] // tex_size=[10,10]
// ); // );
// Example(3D): "hills" texture. // Example(3D): "hills" texture.
// tex = get_texture("hills"); // tex = texture("hills");
// textured_linear_sweep( // textured_linear_sweep(
// rect(50), tex, h=40, // rect(50), tex, h=40,
// tex_size=[10,10], style="quincunx" // tex_size=[10,10], style="quincunx"
// ); // );
// Example(3D): "vnf_dots" texture. // Example(3D): "vnf_dots" texture.
// tex = get_texture("vnf_dots"); // tex = texture("vnf_dots");
// textured_linear_sweep( // textured_linear_sweep(
// rect(50), tex, h=40, // rect(50), tex, h=40, tscale=3,
// tex_size=[10,10] // tex_size=[10,10]
// ); // );
// Example(3D): "vnf_dimples" texture. // Example(3D): "vnf_dimples" texture.
// tex = get_texture("vnf_dimples"); // tex = texture("vnf_dimples");
// textured_linear_sweep( // textured_linear_sweep(
// rect(50), tex, h=40, // rect(50), tex, h=40, tscale=3,
// tex_size=[10,10] // tex_size=[10,10]
// ); // );
// Example(3D): "vnf_cones" texture. // Example(3D): "vnf_cones" texture.
// tex = get_texture("vnf_cones"); // tex = texture("vnf_cones");
// textured_linear_sweep( // textured_linear_sweep(
// rect(50), tex, h=40, // rect(50), tex, h=40, tscale=3,
// tex_size=[10,10] // tex_size=[10,10]
// ); // );
// Example(3D): "bricks" texture. // Example(3D): "bricks" texture.
// tex = get_texture("bricks"); // tex = texture("bricks");
// textured_linear_sweep( // textured_linear_sweep(
// rect(50), tex, h=40, // rect(50), tex, h=40,
// tex_size=[10,10] // tex_size=[10,10]
// ); // );
// Example(3D): "vnf_bricks" texture. // Example(3D): "vnf_bricks" texture.
// tex = get_texture("vnf_bricks"); // tex = texture("vnf_bricks");
// textured_linear_sweep( // textured_linear_sweep(
// rect(50), tex, h=40, // rect(50), tex, h=40,
// tex_size=[10,10] // tex_size=[10,10]
// ); // );
// Example(3D): "vnf_diagonal_grid" texture. // Example(3D): "vnf_diagonal_grid" texture.
// tex = get_texture("vnf_diagonal_grid"); // tex = texture("vnf_diagonal_grid");
// textured_linear_sweep( // textured_linear_sweep(
// rect(50), tex, h=40, // rect(50), tex, h=40,
// tex_size=[10,10] // tex_size=[10,10]
// ); // );
// Example(3D): "vnf_hex_grid" texture. // Example(3D): "vnf_hex_grid" texture.
// tex = get_texture("vnf_hex_grid"); // tex = texture("vnf_hex_grid");
// textured_linear_sweep( // textured_linear_sweep(
// rect(50), tex, h=40, // rect(50), tex, h=40,
// tex_size=[12.5,20] // tex_size=[12.5,20]
// ); // );
// Example(3D): "vnf_checkers" texture. // Example(3D): "vnf_checkers" texture.
// tex = get_texture("vnf_checkers"); // tex = texture("vnf_checkers");
// textured_linear_sweep( // textured_linear_sweep(
// rect(50), tex, h=40, // rect(50), tex, h=40,
// tex_size=[10,10] // tex_size=[10,10]
// ); // );
// Example(3D): "rough" texture. // Example(3D): "rough" texture.
// tex = get_texture("rough"); // tex = texture("rough");
// textured_linear_sweep( // textured_linear_sweep(
// rect(50), tex, h=40, // rect(50), tex, h=40,
// tex_size=[10,10], style="min_edge" // 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=="ribs"? [[1,0]] :
tex=="trunc_ribs"? [[each repeat(0,default(n,1)+1), each repeat(1,default(n,1)+1)]] : 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]] : tex=="wave_ribs"? [[for(a=[0:360/default(n,8):359]) (cos(a)+1)/2]] :
@ -2266,7 +2266,7 @@ function get_texture(tex,n,m,o) =
[ [0,1,0], [1,1,0], [1/2,1/2,m], [0,0,0], [1,0,0] ], [ [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] ] [ [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)) [ tex=="vnf_trunc_pyramids"? let(n=default(n,0.25), m=default(m,1)) [
[ [
each path3d(square(1)), each path3d(square(1)),
@ -2440,21 +2440,16 @@ function get_texture(tex,n,m,o) =
// Function&Module: textured_linear_sweep() // Function&Module: textured_linear_sweep()
// Usage: As Function // Usage: As Function
// vnf = textured_linear_sweep(path, texture, tex_size, h, ...); // vnf = textured_linear_sweep(region, texture, tex_size, h, ...);
// vnf = textured_linear_sweep(path, texture, counts=, h=, ...); // vnf = textured_linear_sweep(region, texture, counts=, h=, ...);
// Usage: As Module // Usage: As Module
// textured_linear_sweep(path, texture, tex_size, h, ...) [ATTACHMENTS]; // textured_linear_sweep(region, texture, tex_size, h, ...) [ATTACHMENTS];
// textured_linear_sweep(path, texture, counts=, h=, ...) [ATTACHMENTS]; // textured_linear_sweep(region, texture, counts=, h=, ...) [ATTACHMENTS];
// Topics: Sweep, Extrusion, Textures, Knurling // Topics: Sweep, Extrusion, Textures, Knurling
// Description: // Description:
// Given a single polygon path, creates a linear extrusion of that polygon vertically, optionally twisted, // Given a [[Region|regions.scad]], creates a linear extrusion of it vertically, optionally twisted, scaled, and/or shifted,
// scaled, and/or shifted, with a given texture tiled evenly over the side surfaces. // with a given texture tiled evenly over the side surfaces. The texture can be given in one of three ways:
// If the path to be swept is clockwise on the XY plane, then the output shape should have its faces pointed outwards, // - As a texture name string. (See {{texture()}} for supported named textures.)
// 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.)
// - As a 2D array of evenly spread height values. (AKA a heightfield.) // - 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 // - 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 // 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: // 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 // https://raw.githubusercontent.com/revarbat/BOSL2/master/scripts/img2scad.py
// Arguments: // Arguments:
// path = The path to sweep/extrude. // 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 {{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 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]` // 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. // 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 // 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 // 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] // 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"` // 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` // 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` // 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` // 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_top = The centroid of the top of the shape, oriented UP.
// centroid = The centroid of the center 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. // 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. // Example: "diamonds" texture.
// path = glued_circles(r=15, spread=40, tangent=45); // path = glued_circles(r=15, spread=40, tangent=45);
// textured_linear_sweep( // textured_linear_sweep(
@ -2532,45 +2525,36 @@ function get_texture(tex,n,m,o) =
// tscale=1, style="convex"); // tscale=1, style="convex");
// vnf_polyhedron(vnf, convexity=10); // vnf_polyhedron(vnf, convexity=10);
function textured_linear_sweep( function textured_linear_sweep(
path, texture, region, texture,
tex_size=[5,5], h, counts, tex_size=[5,5], h, counts,
inset=false, rot=false, tscale=1, inset=false, rot=false, tscale=1,
caps=true, col_wrap=true,
twist, scale, shift, twist, scale, shift,
style="min_edge", reverse=false, l, style="min_edge", l, samples,
anchor=CENTER, spin=0, orient=UP anchor=CENTER, spin=0, orient=UP
) = ) =
assert(is_path(path,[2])) assert(is_path(region,[2])||is_region(region))
assert(is_bool(caps)) assert(is_undef(samples) || is_int(samples))
assert(is_bool(reverse))
assert(counts==undef || is_vector(counts,2)) assert(counts==undef || is_vector(counts,2))
assert(tex_size==undef || is_vector(tex_size,2)) assert(tex_size==undef || is_vector(tex_size,2))
assert(is_bool(rot) || in_list(rot,[0,90,180,270])) assert(is_bool(rot) || in_list(rot,[0,90,180,270]))
let( let(
tex = is_string(texture)? get_texture(texture) : texture, regions = is_path(region,2)? [[region]] : region_parts(region),
path = col_wrap && is_polygon_clockwise(path)? reverse(path) : path, tex = is_string(texture)? texture(texture) : texture,
texture = !rot? tex : texture = !rot? tex :
is_vnf(tex)? zrot(is_num(rot)?rot:90, cp=[1/2,1/2], p=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==180? reverse([for (row=tex) reverse(row)]) :
rot==270? [for (row=transpose(tex)) reverse(row)] : rot==270? [for (row=transpose(tex)) reverse(row)] :
reverse(transpose(tex)), reverse(transpose(tex)),
h = first_defined([h, l, 1]),
inset = is_num(inset)? inset : inset? 1 : 0,
twist = default(twist, 0), twist = default(twist, 0),
shift = default(shift, [0,0]), shift = default(shift, [0,0]),
scale = scale==undef? [1,1,1] : is_num(scale)? [scale,scale,1] : scale, scale = scale==undef? [1,1,1] :
h = first_defined([h, l, 1]), is_num(scale)? [scale,scale,1] : scale,
plen = path_length(path, closed=col_wrap), samples = !is_vnf(texture)? len(texture[0]) :
counts = is_vector(counts,2)? counts : is_num(samples)? samples : 8,
is_vector(tex_size,2) check_tex = is_vnf(texture)
? [round(plen/tex_size.x), max(1,round(h/tex_size.y)), ] ? let( // Validate VNF tile texture
: [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
bounds = pointlist_bounds(texture[0]), bounds = pointlist_bounds(texture[0]),
min_xy = point2d(bounds[0]), min_xy = point2d(bounds[0]),
max_xy = point2d(bounds[1]) max_xy = point2d(bounds[1])
@ -2583,10 +2567,43 @@ function textured_linear_sweep(
allgoody = all(vverts, function(v) any(vverts, function(w) w==[v.x, 1-v.y, v.z])) 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.") assert(allgoodx && allgoody, "All VNF tile edge vertices must line up with a vertex on the opposite side of the tile.")
: 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."),
sorted_tile =
!is_vnf(texture)? texture :
samples<=1? texture :
let( let(
tex2 = vnf_slice(texture, "X", list([1/8:1/8:7/8])), s = 1/samples,
sorted_tile = _vnf_sort_vertices(tex2, idx=[1,0]), vnf = vnf_slice(texture, "X", list([s:s:1-s/2]))
vertzs = group_sort(sorted_tile[0], idx=1), ) _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),
pre_skew_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],
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
row_vnf = vnf_join([ row_vnf = vnf_join([
for (j = [0:1:counts.x-1]) [ for (j = [0:1:counts.x-1]) [
[ [
@ -2612,8 +2629,8 @@ function textured_linear_sweep(
[ [
for (group = rvertzs) let( for (group = rvertzs) let(
v = (i + group[0].z) / counts.y, v = (i + group[0].z) / counts.y,
mat = move(shift*v) * sc = lerp([1,1,1], scale, v),
scale(lerp([1,1,1],scale,v)) * mat = scale(sc) *
zrot(twist*v) * zrot(twist*v) *
up(((i/counts.y)-0.5)*h) * up(((i/counts.y)-0.5)*h) *
zscale(h/counts.y) zscale(h/counts.y)
@ -2621,12 +2638,8 @@ function textured_linear_sweep(
], ],
sorted_row[1] sorted_row[1]
] ]
]), ])
tmat = move(shift) * scale(scale) * zrot(twist) * up(h/2), ) vnf1
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 : let( // Heightfield texture
texcnt = [len(texture[0]), len(texture)], texcnt = [len(texture[0]), len(texture)],
tile_rows = [ tile_rows = [
@ -2651,30 +2664,62 @@ function textured_linear_sweep(
if (i != counts.y || ti == 0) if (i != counts.y || ti == 0)
let( let(
v = (i + (ti/texcnt.y)) / counts.y, v = (i + (ti/texcnt.y)) / counts.y,
sc = lerp([1,1,1], scale, v),
mat = down((v-0.5)*h) * mat = down((v-0.5)*h) *
move(shift*v) * scale(sc) *
scale(lerp([1,1,1],scale,v)) *
zrot(twist*v) zrot(twist*v)
) apply(mat, tile_rows[ti]) ) apply(mat, tile_rows[ti])
] ]
) vnf_vertex_array( ) vnf_vertex_array(
tiles, caps=caps, style=style, reverse=reverse, tiles, caps=false, style=style,
col_wrap=col_wrap, row_wrap=false col_wrap=true, row_wrap=false
), )
cent = centroid(path), ) vnf
]),
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 = [ anchors = [
named_anchor("centroid_top", point3d(cent, h/2), UP), named_anchor("centroid_top", point3d(cent, h/2), UP),
named_anchor("centroid", point3d(cent), UP), named_anchor("centroid", point3d(cent), UP),
named_anchor("centroid_bot", point3d(cent,-h/2), DOWN) 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( module textured_linear_sweep(
path, texture, tex_size=[5,5], h, path, texture, tex_size=[5,5], h,
inset=false, rot=false, tscale=1, inset=false, rot=false, tscale=1,
twist, scale, shift, twist, scale, shift, samples,
style="min_edge", reverse=false, l, counts, style="min_edge", l, counts,
anchor=CENTER, spin=0, orient=UP, anchor=CENTER, spin=0, orient=UP,
convexity=10 convexity=10
) { ) {
@ -2684,8 +2729,7 @@ module textured_linear_sweep(
tex_size=tex_size, counts=counts, tex_size=tex_size, counts=counts,
inset=inset, rot=rot, tscale=tscale, inset=inset, rot=rot, tscale=tscale,
twist=twist, scale=scale, shift=shift, twist=twist, scale=scale, shift=shift,
caps=true, col_wrap=true, samples=samples, style=style,
style=style, reverse=reverse,
anchor=CENTER, spin=0, orient=UP anchor=CENTER, spin=0, orient=UP
); );
cent = centroid(path); cent = centroid(path);
@ -2700,37 +2744,39 @@ module textured_linear_sweep(
} }
} }
function _find_vnf_z_edge_path(vnf, z) = function _find_vnf_tile_bottom_edge_path(vnf, val) =
let( let(
verts = vnf[0], verts = vnf[0],
faces = vnf[1], faces = vnf[1],
goods = [for (v = verts) approx(v.z, z)], goods = [for (v = verts) approx(v[1], val)],
fragments = [ fragments = [
for (face = faces) for (face = faces)
for (seg = pair(face, wrap=true)) for (seg = pair(face, wrap=true))
if (goods[seg[0]] && goods[seg[1]]) let(s0 = seg[0], s1 = seg[1])
path2d([verts[seg[0]], verts[seg[1]]]) if (goods[s0] && goods[s1])
] let(v0 = verts[s0], v1 = verts[s1])
) _assemble_a_path_from_fragments(fragments, rightmost=true)[0]; 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() // Function&Module: textured_revolution()
// Usage: As Function // Usage: As Function
// vnf = textured_revolution(path, texture, tex_size, [tscale=], ...); // vnf = textured_revolution(region, texture, tex_size, [tscale=], ...);
// vnf = textured_revolution(path, texture, counts=, [tscale=], ...); // vnf = textured_revolution(region, texture, counts=, [tscale=], ...);
// Usage: As Module // Usage: As Module
// textured_revolution(path, texture, tex_size, [tscale=], ...) [ATTACHMENTS]; // textured_revolution(region, texture, tex_size, [tscale=], ...) [ATTACHMENTS];
// textured_revolution(path, texture, counts=, [tscale=], ...) [ATTACHMENTS]; // textured_revolution(region, texture, counts=, [tscale=], ...) [ATTACHMENTS];
// Topics: Sweep, Extrusion, Textures, Knurling // Topics: Sweep, Extrusion, Textures, Knurling
// Description: // 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+). // 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, capped top and bottom, with the sides covered in a given tiled texture. // This creates a solid from that surface of revolution, possibly 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.
// The texture can be given in one of three ways: // 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 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 // - 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 // being the height of the texture point from the surface. VNF tiles MUST be able to tile in both X and Y
@ -2738,26 +2784,25 @@ 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: // 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 // https://raw.githubusercontent.com/revarbat/BOSL2/master/scripts/img2scad.py
// Arguments: // Arguments:
// path = The path to sweep/extrude. // 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 {{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 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]` // 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. // 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` // 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º. // rot = If true, rotates the texture 90º.
// 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]
// wrap = (function only) If true, the path is considered a closed polygon. Useful mainly for things like making a textured torus. Default: `false` // 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"` // 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. // 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` // 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` // 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` // 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: // Example:
// path = right(50, p=circle(d=40)); // 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: // Example:
// tex = [ // tex = [
// [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], // [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
@ -2791,53 +2836,37 @@ function _find_vnf_z_edge_path(vnf, z) =
// each arc(r=20, corner=[[20,20],[10,0],[20,-20]]), // each arc(r=20, corner=[[20,20],[10,0],[20,-20]]),
// [20,-20], [20,-30], // [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); // vnf_polyhedron(vnf, convexity=10);
function textured_revolution( function textured_revolution(
path, texture, tex_size, shape, texture, tex_size, tscale=1,
tscale=1, inset=false, rot=false, inset=false, rot=false,
caps=true, wrap=false, shift=[0,0], shift=[0,0], closed=true, angle=360,
style="min_edge", reverse=false, style="min_edge", counts, samples
counts
) = ) =
assert(is_path(path,[2])) assert(angle>0 && angle<=360)
assert(is_bool(caps)) assert(is_path(shape,[2]) || is_region(shape))
assert(is_bool(wrap)) assert(is_undef(samples) || is_int(samples))
assert(is_bool(reverse)) assert(is_bool(closed))
assert(counts==undef || is_vector(counts,2)) assert(counts==undef || is_vector(counts,2))
assert(tex_size==undef || is_vector(tex_size,2)) assert(tex_size==undef || is_vector(tex_size,2))
assert(is_bool(rot) || in_list(rot,[0,90,180,270])) assert(is_bool(rot) || in_list(rot,[0,90,180,270]))
let( 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 : texture = !rot? tex :
is_vnf(tex)? zrot(is_num(rot)?rot:90, cp=[1/2,1/2], p=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==180? reverse([for (row=tex) reverse(row)]) :
rot==270? [for (row=transpose(tex)) reverse(row)] : rot==270? [for (row=transpose(tex)) reverse(row)] :
reverse(transpose(tex)), reverse(transpose(tex)),
plen = path_length(path), check_tex = is_vnf(texture)
bounds = pointlist_bounds(path), ? let( // Validate VNF tile texture
maxx = bounds[1].x, bounds = pointlist_bounds(texture[0]),
miny = bounds[0].y, min_xy = point2d(bounds[0]),
maxy = bounds[1].y, max_xy = point2d(bounds[1])
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])
) )
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.") 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( let(
@ -2847,12 +2876,52 @@ function textured_revolution(
allgoody = all(vverts, function(v) any(vverts, function(w) w==[v.x, 1-v.y, v.z])) 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.") assert(allgoodx && allgoody, "All VNF tile edge vertices must line up with a vertex on the opposite side of the tile.")
: 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."),
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( let(
tex2 = vnf_slice(vnf_slice(texture, "X", list([1/8:1/8:7/8])), "Y", list([1/8:1/8:7/8])), utex = samples<=1? texture :
sorted_tile = _vnf_sort_vertices(tex2, idx=[0,1]), let(
vertzs = group_sort(sorted_tile[0], idx=0), s = 1 / samples,
col_vnf = vnf_join([ slices = list([s : s : 1-s/2]),
for (j = [0:1:counts.y-1]) [ 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 (group = vertzs) each [
for (vert = group) let( for (vert = group) let(
@ -2860,50 +2929,39 @@ function textured_revolution(
u = floor(part), u = floor(part),
uu = part - u, uu = part - u,
tscale = tscale =
wrap? tscale : closed? tscale :
caps && j==0 && approx(vert.y,0)? 0 : !closed && j==0 && approx(vert.y,0)? 0 :
caps && j==counts.y-1 && approx(vert.y,1)? 0 : !closed && j==counts_y-1 && approx(vert.y,1)? 0 :
tscale, tscale,
base = lerp(select(bases,u), select(bases,u+1), uu), base = lerp(select(bases,u), select(bases,u+1), uu),
norm = unit(lerp(select(norms,u), select(norms,u+1), uu)), norm = unit(lerp(select(norms,u), select(norms,u+1), uu)),
texh = (vert.z - inset) * tscale * (base.x / maxx), texh = (vert.z - inset) * tscale * (base.x / maxx),
xyz = base - norm * texh xyz = base - norm * texh
) zrot(vert.x*360/counts.x, p=xyz) ) zrot(vert.x*angle/counts_x, p=xyz)
] ]
], ],
sorted_tile[1] 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 : let( // Heightfield texture
texcnt = [len(texture[0]), len(texture)], texcnt = [len(texture[0]), len(texture)],
skmat = down(-miny) * skew(sxz=shift.x/h, syz=shift.y/h) * up(-miny),
tiles = transpose([ tiles = transpose([
for (j = [0:1:counts.x-1], tj = [0:1:texcnt.x-1]) let( for (j = [0,1], tj = [0:1:texcnt.x-1])
v = (j + (tj/texcnt.x)) / counts.x, if (j == 0 || tj == 0)
mat = skmat * zrot(v*360) let(
v = (j + (tj/texcnt.x)) / counts_x,
mat = zrot(v*angle)
) apply(mat, [ ) apply(mat, [
for (i = [0:1:counts.y-(wrap?1:0)], ti = [0:1:texcnt.y-1]) for (i = [0:1:counts_y-(closed?1:0)], ti = [0:1:texcnt.y-1])
if (i != counts.y || ti == 0) if (i != counts_y || ti == 0)
let( let(
part = (i + (ti/texcnt.y)) * samples, part = (i + (ti/texcnt.y)) * samples,
u = floor(part), u = floor(part),
uu = part - u, uu = part - u,
tscale = tscale =
wrap? tscale : closed? tscale :
caps && i==0 && ti==0? 0 : !closed && i==0 && ti==0? 0 :
caps && i==counts.y && ti==0? 0 : !closed && i==counts_y && ti==0? 0 :
tscale, tscale,
base = lerp(bases[u], select(bases,u+1), uu), base = lerp(bases[u], select(bases,u+1), uu),
norm = unit(lerp(norms[u], select(norms,u+1), uu)), norm = unit(lerp(norms[u], select(norms,u+1), uu)),
@ -2913,28 +2971,134 @@ function textured_revolution(
]) ])
]) ])
) vnf_vertex_array( ) vnf_vertex_array(
tiles, caps=caps, style=style, reverse=reverse, tiles, caps=false, style=style,
col_wrap=true, row_wrap=wrap col_wrap=(angle==360), row_wrap=closed
) )
) vnf; ) 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( module textured_revolution(
path, texture, tex_size, shape, texture, tex_size, tscale=1,
tscale=1, inset=false, rot=false, inset=false, rot=false, shift=[0,0],
caps=true, wrap=false, shift=[0,0], closed=true, angle=360,
style="min_edge", reverse=false, style="min_edge", atype="surface",
atype="surface", convexity=10, counts, samples,
convexity=10, counts,
anchor=CENTER, spin=0, orient=UP anchor=CENTER, spin=0, orient=UP
) { ) {
assert(in_list(atype, ["surface","extent"])); assert(in_list(atype, ["surface","extent"]));
vnf = textured_revolution( vnf = textured_revolution(
path, texture, tex_size=tex_size, shape, texture, tex_size=tex_size,
tscale=tscale, inset=inset, rot=rot, tscale=tscale, inset=inset, rot=rot,
caps=caps, wrap=wrap, style=style, closed=closed, style=style,
reverse=reverse, shift=shift, shift=shift, angle=angle,
counts=counts samples=samples, counts=counts
); );
geom = atype=="surface" geom = atype=="surface"
? attach_geom(vnf=vnf, extent=false) ? attach_geom(vnf=vnf, extent=false)
@ -2957,7 +3121,7 @@ module textured_revolution(
// Description: // Description:
// Creates a cylinder or cone with optional chamfers or roundings, covered in a textured surface. // 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: // 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 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 // - 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 // being the height of the texture point from the surface. VNF tiles MUST be able to tile in both X and Y
@ -2967,7 +3131,7 @@ module textured_revolution(
// Arguments: // Arguments:
// h | l = The height of the cylinder. // h | l = The height of the cylinder.
// r = The radius 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]` // 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. // r1 = The radius of the bottom of the cylinder.
@ -2981,7 +3145,6 @@ module textured_revolution(
// caps = (function only) If true, create endcaps for the extruded shape. Default: `true` // 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] // 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"` // 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. // 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. // 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. // 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 +3155,7 @@ module textured_revolution(
// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#subsection-anchor). Default: `CENTER` // 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` // 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` // 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: // Examples:
// textured_cylinder(h=40, r=20, texture="diamonds", tex_size=[5,5]); // 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"); // textured_cylinder(h=40, r1=20, r2=15, texture="pyramids", tex_size=[5,5], style="convex");
@ -3003,8 +3166,7 @@ function textured_cylinder(
h, r, texture, tex_size=[1,1], counts, h, r, texture, tex_size=[1,1], counts,
tscale=1, inset=false, rot=false, tscale=1, inset=false, rot=false,
caps=true, style="min_edge", caps=true, style="min_edge",
reverse=false, shift=[0,0], shift=[0,0], l, r1, r2, d, d1, d2,
l, r1, r2, d, d1, d2,
chamfer, chamfer1, chamfer2, chamfer, chamfer1, chamfer2,
rounding, rounding1, rounding2 rounding, rounding1, rounding2
) = ) =
@ -3032,11 +3194,10 @@ function textured_cylinder(
else [r2,h/2], else [r2,h/2],
], ],
vnf = textured_revolution( vnf = textured_revolution(
reverse(path), texture, reverse(path), texture, closed=false,
tex_size=tex_size, counts=counts, tex_size=tex_size, counts=counts,
tscale=tscale, inset=inset, rot=rot, tscale=tscale, inset=inset, rot=rot,
caps=caps, style=style, reverse=reverse, style=style, shift=shift
shift=shift
) )
) vnf; ) vnf;
@ -3044,7 +3205,7 @@ function textured_cylinder(
module textured_cylinder( module textured_cylinder(
h, r, texture, tex_size=[1,1], h, r, texture, tex_size=[1,1],
counts, tscale=1, inset=false, rot=false, 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, l, r1, r2, d, d1, d2,
chamfer, chamfer1, chamfer2, chamfer, chamfer1, chamfer2,
rounding, rounding1, rounding2, rounding, rounding1, rounding2,
@ -3063,7 +3224,7 @@ module textured_cylinder(
tscale=tscale, inset=inset, rot=rot, tscale=tscale, inset=inset, rot=rot,
counts=counts, tex_size=tex_size, counts=counts, tex_size=tex_size,
caps=true, style=style, caps=true, style=style,
reverse=reverse, shift=shift, shift=shift,
chamfer1=chamf1, chamfer2=chamf2, chamfer1=chamf1, chamfer2=chamf2,
rounding1=round1, rounding2=round2 rounding1=round1, rounding2=round2
); );

119
vnf.scad
View file

@ -1234,7 +1234,7 @@ function vnf_bend(vnf,r,d,axis="Z") =
/// Internal Module: _show_vertices() /// Internal Module: _show_vertices()
/// Usage: /// Usage:
/// _show_vertices(vertices, [size]) /// _show_vertices(vertices, [size], [filter=])
/// Description: /// Description:
/// Draws all the vertices in an array, at their 3D position, numbered by their /// 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 /// position in the vertex array. Also draws any children of this module with
@ -1248,10 +1248,11 @@ function vnf_bend(vnf,r,d,axis="Z") =
/// _show_vertices(vertices=verts, size=2) { /// _show_vertices(vertices=verts, size=2) {
/// polyhedron(points=verts, faces=faces); /// polyhedron(points=verts, faces=faces);
/// } /// }
module _show_vertices(vertices, size=1) { module _show_vertices(vertices, size=1, filter) {
color("blue") { color("blue") {
dups = vector_search(vertices, EPSILON, vertices); dups = vector_search(vertices, EPSILON, vertices);
for (ind = dups){ for (ind = dups) {
if (is_undef(filter) || any(ind, filter)) {
numstr = str_join([for(i=ind) str(i)],","); numstr = str_join([for(i=ind) str(i)],",");
v = vertices[ind[0]]; v = vertices[ind[0]];
translate(v) { translate(v) {
@ -1264,12 +1265,13 @@ module _show_vertices(vertices, size=1) {
} }
} }
} }
}
} }
/// Internal Module: _show_faces() /// Internal Module: _show_faces()
/// Usage: /// Usage:
/// _show_faces(vertices, faces, [size=]); /// _show_faces(vertices, faces, [size=], [filter=]);
/// Description: /// Description:
/// Draws all the vertices at their 3D position, numbered in blue by their /// 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 /// 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) { /// _show_faces(vertices=verts, faces=faces, size=2) {
/// polyhedron(points=verts, faces=faces); /// polyhedron(points=verts, faces=faces);
/// } /// }
module _show_faces(vertices, faces, size=1) { module _show_faces(vertices, faces, size=1, filter) {
vlen = len(vertices); vlen = len(vertices);
color("red") { color("red") {
for (i = [0:1:len(faces)-1]) { for (i = [0:1:len(faces)-1]) {
face = faces[i]; face = faces[i];
if (face[0] < 0 || face[1] < 0 || face[2] < 0 || face[0] >= vlen || face[1] >= vlen || face[2] >= vlen) { 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); echo("BAD FACE: ", vlen=vlen, face=face);
} else { } else if (is_undef(filter) || any(face,filter)) {
verts = select(vertices,face); verts = select(vertices,face);
c = mean(verts); c = mean(verts);
v0 = verts[0]; v0 = verts[0];
@ -1325,7 +1327,7 @@ module _show_faces(vertices, faces, size=1) {
// Module: debug_vnf() // Module: debug_vnf()
// Usage: // Usage:
// debug_vnf(vnfs, [faces=], [vertices=], [opacity=], [size=], [convexity=]); // debug_vnf(vnfs, [faces=], [vertices=], [opacity=], [size=], [convexity=], [filter=]);
// Description: // Description:
// A drop-in module to replace `vnf_polyhedron()` to help debug vertices and faces. // A drop-in module to replace `vnf_polyhedron()` to help debug vertices and faces.
// Draws all the vertices at their 3D position, numbered in blue by their // Draws all the vertices at their 3D position, numbered in blue by their
@ -1346,16 +1348,18 @@ module _show_faces(vertices, faces, size=1) {
// opacity = Opacity of the polyhedron faces. Default: 0.5 // opacity = Opacity of the polyhedron faces. Default: 0.5
// convexity = The max number of walls a ray can pass through the given polygon paths. // 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 // 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): // Example(EdgesMed):
// verts = [for (z=[-10,10], a=[0:120:359.9]) [10*cos(a),10*sin(a),z]]; // 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]]; // 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); // 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); no_children($children);
if (faces) 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) if (vertices)
_show_vertices(vertices=vnf[0], size=size); _show_vertices(vertices=vnf[0], size=size, filter=filter);
if (opacity > 0)
color([0.2, 1.0, 0, opacity]) color([0.2, 1.0, 0, opacity])
vnf_polyhedron(vnf,convexity=convexity); 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 // Usage: As Function
// fails = vnf_validate(vnf); // fails = vnf_validate(vnf);
// Usage: As Module // 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: // Description:
// When called as a function, returns a list of non-manifold errors with the given VNF. // 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]`. // Each error has the format `[ERR_OR_WARN,CODE,MESG,POINTS,COLOR]`.
@ -1394,13 +1398,18 @@ 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 // show_warns = If true show warnings for non-triangular faces. Default: true
// check_isects = If true, performs slow checks for intersecting faces. Default: false // check_isects = If true, performs slow checks for intersecting faces. 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. // 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(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([ // vnf = skin([
// path3d(regular_ngon(n=3, d=100),0), // path3d(regular_ngon(n=3, d=100),0),
// path3d(regular_ngon(n=5, d=100),100) // path3d(regular_ngon(n=5, d=100),100)
// ], slices=0, caps=true, method="tangent"); // ], slices=0, caps=true, method="tangent");
// vnf_validate(vnf); // 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]; // a = [ 0, 0,-50];
// b = [-50,-50, 50]; // b = [-50,-50, 50];
// c = [-50, 50, 50]; // c = [-50, 50, 50];
@ -1410,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] // [a, b, e], [a, c, b], [a, d, c], [a, e, d], [b, c, d, e]
// ]); // ]);
// vnf_validate(vnf); // 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 = vnf_triangulate(linear_sweep(union(square(50), square(50,anchor=BACK+RIGHT)), height=50));
// vnf_validate(vnf); // vnf_validate(vnf);
// Example: REVERSAL Errors; Faces Reversed Across Edge // Example(3D,Edges): REVERSAL Errors; Faces Reversed Across Edge
// vnf1 = skin([ // vnf1 = skin([
// path3d(square(100,center=true),0), // path3d(square(100,center=true),0),
// path3d(square(100,center=true),100), // path3d(square(100,center=true),100),
@ -1425,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]], // [[-50,-50,100], [ 50,-50,100], [ 50, 50,100]],
// ])]); // ])]);
// vnf_validate(vnf); // vnf_validate(vnf);
// Example: T_JUNCTION Errors; Vertex is Mid-Edge on Another Face. // Example(3D,Edges): T_JUNCTION Errors; Vertex is Mid-Edge on Another Face.
// vnf1 = skin([ // vnf = [
// path3d(square(100,center=true),0), // [
// path3d(square(100,center=true),100), // each path3d(square(100,center=true),0),
// ], slices=0, caps=false); // each path3d(square(100,center=true),100),
// vnf = vnf_join([vnf1, vnf_from_polygons([ // [0,-50,100],
// [[-50,-50,0], [50,50,0], [-50,50,0]], // ], [
// [[-50,-50,0], [50,-50,0], [50,50,0]], // [0,2,1], [0,3,2], [0,8,4], [0,1,8], [1,5,8],
// [[-50,-50,100], [-50,50,100], [0,50,100]], // [0,4,3], [4,7,3], [1,2,5], [2,6,5], [3,7,6],
// [[-50,-50,100], [0,50,100], [0,-50,100]], // [3,6,2], [4,5,6], [4,6,7],
// [[0,-50,100], [0,50,100], [50,50,100]], // ]
// [[0,-50,100], [50,50,100], [50,-50,100]], // ];
// ])]);
// vnf_validate(vnf); // vnf_validate(vnf);
// Example: FACE_ISECT Errors; Faces Intersect // Example(3D,Edges): FACE_ISECT Errors; Faces Intersect
// vnf = vnf_join([ // vnf = vnf_join([
// vnf_triangulate(linear_sweep(square(100,center=true), height=100)), // 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))) // move([75,35,30],p=vnf_triangulate(linear_sweep(square(100,center=true), height=100)))
// ]); // ]);
// vnf_validate(vnf,size=2,check_isects=true); // 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([ // vnf = skin([
// path3d(regular_ngon(n=4, d=100),0), // path3d(regular_ngon(n=4, d=100),0),
// path3d(regular_ngon(n=5, d=100),100) // path3d(regular_ngon(n=5, d=100),100)
@ -1481,16 +1489,15 @@ function vnf_validate(vnf, show_warns=true, check_isects=false) =
big_faces = !show_warns? [] : [ big_faces = !show_warns? [] : [
for (face = faces) for (face = faces)
if (len(face) > 3) if (len(face) > 3)
_vnf_validate_err("BIG_FACE", [for (i=face) varr[i]]) _vnf_validate_err("BIG_FACE", face)
], ],
null_faces = !show_warns? [] : [ null_faces = !show_warns? [] : [
for (i = idx(faces)) let( for (i = idx(faces)) let(
face = faces[i], face = faces[i],
area = face_areas[i], area = face_areas[i]
faceverts = [for (k=face) varr[k]]
) )
if (is_num(area) && abs(area) < EPSILON) 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) issues = concat(big_faces, null_faces)
) )
@ -1515,7 +1522,7 @@ function vnf_validate(vnf, show_warns=true, check_isects=false) =
sface1 = list_rotate(face1,min1), sface1 = list_rotate(face1,min1),
sface2 = list_rotate(face2,min2) sface2 = list_rotate(face2,min2)
) if (sface1 == sface2) ) if (sface1 == sface2)
_vnf_validate_err("DUP_FACE", [for (i=sface1) varr[i]]) _vnf_validate_err("DUP_FACE", sface1)
], ],
issues = concat(issues, repeated_faces) issues = concat(issues, repeated_faces)
) repeated_faces? issues : ) repeated_faces? issues :
@ -1523,7 +1530,7 @@ function vnf_validate(vnf, show_warns=true, check_isects=false) =
multconn_edges = unique([ multconn_edges = unique([
for (i = idx(uniq_edges)) for (i = idx(uniq_edges))
if (edgecnts[1][i]>2) 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) issues = concat(issues, multconn_edges)
) multconn_edges? issues : ) multconn_edges? issues :
@ -1534,7 +1541,7 @@ function vnf_validate(vnf, show_warns=true, check_isects=false) =
for(edge2 = pair(faces[j],true)) for(edge2 = pair(faces[j],true))
if(edge1 == edge2) // Valid adjacent faces will never have the same vertex ordering. if(edge1 == edge2) // Valid adjacent faces will never have the same vertex ordering.
if(_edge_not_reported(edge1, varr, multconn_edges)) 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) issues = concat(issues, reversals)
) reversals? issues : ) reversals? issues :
@ -1554,7 +1561,7 @@ function vnf_validate(vnf, show_warns=true, check_isects=false) =
pt = line_closest_point([a,c],b,SEGMENT) pt = line_closest_point([a,c],b,SEGMENT)
) )
if (approx(pt,b)) if (approx(pt,b))
_vnf_validate_err("T_JUNCTION", [b]) _vnf_validate_err("T_JUNCTION", [ib])
]), ]),
issues = concat(issues, t_juncts) issues = concat(issues, t_juncts)
) t_juncts? issues : ) t_juncts? issues :
@ -1606,7 +1613,7 @@ function vnf_validate(vnf, show_warns=true, check_isects=false) =
if (edgecnts[1][i]<2) if (edgecnts[1][i]<2)
if (_pts_not_reported(uniq_edges[i], varr, t_juncts)) if (_pts_not_reported(uniq_edges[i], varr, t_juncts))
if (_pts_not_reported(uniq_edges[i], varr, isect_faces)) 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) issues = concat(issues, hole_edges)
) hole_edges? issues : ) hole_edges? issues :
@ -1619,7 +1626,7 @@ function vnf_validate(vnf, show_warns=true, check_isects=false) =
) )
if (is_num(area) && abs(area) > EPSILON) if (is_num(area) && abs(area) > EPSILON)
if (!is_coplanar(faceverts)) if (!is_coplanar(faceverts))
_vnf_validate_err("NONPLANAR", faceverts) _vnf_validate_err("NONPLANAR", face)
]), ]),
issues = concat(issues, nonplanars) issues = concat(issues, nonplanars)
) issues; ) issues;
@ -1662,19 +1669,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); no_children($children);
verts = vnf[0];
faults = vnf_validate( faults = vnf_validate(
vnf, show_warns=show_warns, vnf, show_warns=show_warns,
check_isects=check_isects check_isects=check_isects
); );
if (!faults) {
echo("VNF appears valid.");
}
for (fault = faults) { for (fault = faults) {
err = fault[0]; err = fault[0];
typ = fault[1]; typ = fault[1];
clr = fault[2]; clr = fault[2];
msg = fault[3]; msg = fault[3];
pts = fault[4]; idxs = fault[4];
echo(str(typ, " ", err, " (", clr ,"): ", msg, " at ", pts)); pts = [for (i=idxs) if(is_finite(i) && i>=0 && i<len(verts)) verts[i]];
echo(str(typ, " ", err, " (", clr ,"): ", msg, " at ", pts, " indices: ", idxs));
color(clr) { color(clr) {
if (is_vector(pts[0])) { if (is_vector(pts[0])) {
if (len(pts)==2) { if (len(pts)==2) {
@ -1688,7 +1700,26 @@ module vnf_validate(vnf, size=1, show_warns=true, check_isects=false) {
} }
} }
} }
color([0.5,0.5,0.5,0.67]) vnf_polyhedron(vnf); badverts = unique([for (fault=faults) each fault[4]]);
badverts2 = unique([for (j=idx(verts), i=badverts) if (i!=j && verts[i]==verts[j]) j]);
all_badverts = unique(concat(badverts, badverts2));
adjacent = !faults? false : adjacent;
filter_fn = !adjacent? undef : function(i) in_list(i,all_badverts);
adj_vnf = !adjacent? vnf : [
verts, [for (face=vnf[1]) if (any(face,filter_fn)) face]
];
if (wireframe) {
vnf_wireframe(adj_vnf, width=size*0.25);
}
if (label_verts) {
debug_vnf(adj_vnf, size=size*3, opacity=0, faces=false, vertices=true, filter=filter_fn);
}
if (label_faces) {
debug_vnf(vnf, size=size*3, opacity=0, faces=true, vertices=false, filter=filter_fn);
}
if (opacity > 0) {
color([0.5,1,0.5,opacity]) vnf_polyhedron(adj_vnf);
}
} }