Compare commits

..

7 commits

Author SHA1 Message Date
Revar Desmera
0f09d1fac9
Merge pull request #1523 from amatulic/ballbearing
Added rounding parameter as discussed in #1476
2024-12-14 02:20:09 -08:00
Revar Desmera
f152d9ec63
Merge pull request #1524 from amatulic/anachronist_dev
Formatting improvements to squircle() as suggested in discussion
2024-12-14 02:09:01 -08:00
Revar Desmera
25ea42b66d
Merge pull request #1525 from amatulic/anachronist_vnf
Documentation, argument order, and demo changes in vnf_tri_array
2024-12-14 02:08:44 -08:00
Alex Matulich
00d2cd77e0 Fixed typo in new example for vnf_vertex_array 2024-12-12 19:35:37 -08:00
Alex Matulich
7621838c06 Re-ordered args and edited documentation in vnf_tri_array for consistency with vnf_vertex_array,modified wrap+cap demos and moved them into vnf_vertex_array 2024-12-12 15:56:55 -08:00
Alex Matulich
93d0ed07cd Moved squircle up before keyhole, copyedited documentation, changed view distance and color of one example 2024-12-12 11:58:12 -08:00
Alex Matulich
17b670f412 Added rounding parameter as discussed in #1476 2024-12-12 10:20:17 -08:00
3 changed files with 197 additions and 186 deletions

View file

@ -24,10 +24,11 @@
// id = Inner diameter of ball bearing assembly.
// od = Outer diameter of ball bearing assembly.
// width = Width of ball bearing assembly.
// shield = Does the ball bearing assembly have a shield.
// flange = Does the ball bearing assembly have a flange.
// fd = Diameter of the flange (only used if `flange=true`).
// fw = Width of the flange (only used if `flange=true`).
// shield = If true, the ball bearing assembly has a shield.
// flange = If true, the ball bearing assembly has a flange.
// fd = Diameter of the flange (required if `flange=true`).
// fw = Width of the flange (required if `flange=true`).
// rounding = Edge rounding radius, if any. The outermost top and bottom edges are rounded by this amount. The edges of the inner hole are also rounded. If you set `trade_size` and you want edges rounded, you must set `rounding` yourself. This parameter has no default value because the rounding depends on manufacturer and bearing size.
// 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`
@ -43,9 +44,9 @@
// ball_bearing("MF105ZZ", $fn=72);
// Example:
// ball_bearing("F688ZZ", $fn=72);
// Example:
// ball_bearing(id=12,od=24,width=6,shield=true, flange=true, fd=26.5, fw=1.5, $fn=72);
module ball_bearing(trade_size, id, od, width, shield=true, flange=false, fd, fw, anchor=CTR, spin=0, orient=UP) {
// Example: With flange, shield, and rounded edges.
// ball_bearing(id=12,od=24,width=6,shield=true, flange=true, fd=26.5, fw=1.5, rounding=0.6, $fn=72);
module ball_bearing(trade_size, id, od, width, shield=true, flange=false, fd, fw, rounding, anchor=CTR, spin=0, orient=UP) {
info = is_undef(trade_size)? [id, od, width, shield, flange, fd, fw] :
ball_bearing_info(trade_size);
check = assert(all_defined(select(info, 0,4)), "Bad Input");
@ -65,18 +66,18 @@ module ball_bearing(trade_size, id, od, width, shield=true, flange=false, fd, fw
color("silver")
attachable(anchor,spin,orient, d=od, l=width) {
if (shield) {
tube(id=id, wall=wall, h=width);
tube(od=od, wall=wall, h=width);
tube(id=id, wall=wall, h=width, irounding=rounding);
tube(od=od, wall=wall, h=width, orounding1=flange?undef:rounding, orounding2=rounding);
tube(id=id+0.1, od=od-0.1, h=(wall*2+width)/2);
if (flange){
translate([0,0,-width/2+fw/2])tube(id=od, od=fd, h=fw);
translate([0,0,-width/2+fw/2])tube(id=od, od=fd, h=fw, orounding1=rounding);
}
} else {
ball_cnt = floor(PI*mid_d*0.95 / (wall*2));
difference() {
union() {
tube(id=id, wall=wall, h=width);
tube(od=od, wall=wall, h=width);
tube(id=id, wall=wall, h=width, irounding=rounding);
tube(od=od, wall=wall, h=width, orounding1=flange?undef:rounding, orounding2=rounding);
}
torus(r_maj=mid_d/2, r_min=wall);
}
@ -84,11 +85,9 @@ module ball_bearing(trade_size, id, od, width, shield=true, flange=false, fd, fw
zrot(i*360/ball_cnt) right(mid_d/2) sphere(d=wall*2);
}
if (flange){
translate([0,0,-width/2+fw/2])tube(id=od, od=fd, h=fw);
translate([0,0,-width/2+fw/2])tube(id=od, od=fd, h=fw, orounding1=rounding);
}
}
children();
}
}

View file

@ -1751,6 +1751,136 @@ module glued_circles(r, spread=10, tangent=30, d, anchor=CENTER, spin=0) {
}
// Function&Module: squircle()
// Synopsis: Creates a shape between a circle and a square, centered on the origin.
// SynTags: Geom, Path
// Topics: Shapes (2D), Paths (2D), Path Generators, Attachable
// See Also: circle(), square(), supershape()
// Usage: As Module
// squircle(size, [squareness], [style]) [ATTACHMENTS];
// Usage: As Function
// path = squircle(size, [squareness], [style]);
// Description:
// A [squircle](https://en.wikipedia.org/wiki/Squircle) is a shape intermediate between a square/rectangle and a circle/ellipse. Squircles are sometimes used to make dinner plates (more area for the same radius as a circle), keyboard buttons, and smartphone icons. Old CRT television screens also resembled elongated squircles.
// .
// Multiple definitions exist for the squircle. We support two versions: the superellipse {{supershape()}} Example 3, also known as the Lamé upper squircle), and the Fernandez-Guasti squircle. They are visually almost indistinguishable, with the superellipse having slightly rounder "corners" than FG at the same corner radius. These two squircles have different, unintuitive methods for controlling how square or circular the shape is. A `squareness` parameter determines the shape, specifying the corner position linearly, with 0 being on the circle and 1 being the square. Vertices are positioned to be more dense near the corners to preserve smoothness at low values of `$fn`'.
// .
// For the "superellipse" style, the special case where the superellipse exponent is 4 results in a squircle at the geometric mean between radial points on the circle and square, corresponding to squareness=0.456786.
// .
// When called as a module, creates a 2D squircle with the desired squareness.
// When called as a function, returns a 2D path for a squircle.
// Arguments:
// size = Same as the `size` parameter in `square()`, can be a single number or a vector `[xsize,ysize]`.
// squareness = Value between 0 and 1. Controls the shape, setting the location of a squircle "corner" at the specified interpolated position between a circle and a square. When `squareness=0` the shape is a circle, and when `squareness=1` the shape is a square. Default: 0.5
// ---
// style = method for generating a squircle, "fg" for Fernández-Guasti and "superellipse" for superellipse. Default: "fg"
// 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`
// atype = anchor type, "box" for bounding box corners and sides, "perim" for the squircle corners. Default: "box"
// $fn = Number of points. The special variables `$fs` and `$fa` are ignored. If set, `$fn` must be 12 or greater, and is rounded to the nearest multiple of 4. Points are generated so they are more dense around sharper curves. Default if not set: 48
// Examples(2D):
// squircle(size=50, squareness=0.4);
// squircle([80,60], 0.7, $fn=64);
// Example(2D,VPD=265,NoAxes): Ten increments of squareness parameter for a superellipse squircle
// color("green") for(sq=[0:0.1:1])
// stroke(squircle(100, sq, style="superellipse", $fn=96), closed=true, width=0.5);
// Example(2D): Standard vector anchors are based on the bounding box
// squircle(50, 0.6) show_anchors();
// Example(2D): Perimeter anchors, anchoring at bottom left and spinning 20°
// squircle([60,40], 0.5, anchor=(BOTTOM+LEFT), atype="perim", spin=20)
// show_anchors();
module squircle(size, squareness=0.5, style="fg", anchor=CENTER, spin=0, atype="box" ) {
check = assert(squareness >= 0 && squareness <= 1);
anchorchk = assert(in_list(atype, ["box", "perim"]));
size = is_num(size) ? [size,size] : point2d(size);
assert(all_positive(size), "All components of size must be positive.");
path = squircle(size, squareness, style, atype="box");
if (atype == "box") {
attachable(anchor, spin, two_d=true, size=size, extent=false) {
polygon(path);
children();
}
} else { // atype=="perim"
attachable(anchor, spin, two_d=true, extent=true, path=path) {
polygon(path);
children();
}
}
}
function squircle(size, squareness=0.5, style="fg", anchor=CENTER, spin=0, atype="box") =
assert(squareness >= 0 && squareness <= 1)
assert(is_num(size) || is_vector(size,2))
assert(in_list(atype, ["box", "perim"]))
let(
size = is_num(size) ? [size,size] : point2d(size),
path = style == "fg" ? _squircle_fg(size, squareness)
: style == "superellipse" ? _squircle_se(size, squareness)
: assert(false, "Style must be \"fg\" or \"superellipse\"")
) reorient(anchor, spin, two_d=true, size=atype=="box"?size:undef, path=atype=="box"?undef:path, p=path, extent=true);
/* FG squircle functions */
function _squircle_fg(size, squareness) = [
let(
sq = _linearize_squareness(squareness),
size = is_num(size) ? [size,size] : point2d(size),
aspect = size[1] / size[0],
r = 0.5 * size[0],
astep = $fn>=12 ? 90/round($fn/4) : 360/48
) for(a=[360:-astep:0.01]) let(
theta = a + sq * sin(4*a) * 30/PI, // tighter angle steps at corners
p = squircle_radius_fg(sq, r, theta)
) p*[cos(theta), aspect*sin(theta)]
];
function squircle_radius_fg(squareness, r, angle) = let(
s2a = abs(squareness*sin(2*angle))
) s2a>0 ? r*sqrt(2)/s2a * sqrt(1 - sqrt(1 - s2a*s2a)) : r;
function _linearize_squareness(s) =
// from Chamberlain Fong (2016). "Squircular Calculations". arXiv.
// https://arxiv.org/pdf/1604.02174v5
let(c = 2 - 2*sqrt(2), d = 1 - 0.5*c*s)
2 * sqrt((1+c)*s*s - c*s) / (d*d);
/* Superellipse squircle functions */
function _squircle_se(size, squareness) = [
let(
n = _squircle_se_exponent(squareness),
size = is_num(size) ? [size,size] : point2d(size),
ra = 0.5*size[0],
rb = 0.5*size[1],
astep = $fn>=12 ? 90/round($fn/4) : 360/48,
fgsq = _linearize_squareness(min(0.998,squareness)) // works well for distributing theta
) for(a=[360:-astep:0.01]) let(
theta = a + fgsq*sin(4*a)*30/PI, // tighter angle steps at corners
x = cos(theta),
y = sin(theta),
r = (abs(x)^n + abs(y)^n)^(1/n) // superellipse
//r = _superformula(theta=theta, m1=4,m2=4,n1=n,n2=n,n3=n,a=1,b=1)
) [ra*x, rb*y] / r
];
function squircle_radius_se(n, r, angle) = let(
x = cos(angle),
y = sin(angle)
) (abs(x)^n + abs(y)^n)^(1/n) / r;
function _squircle_se_exponent(squareness) = let(
// limit squareness; error if >0.99889, limit is smaller for r>1
s=min(0.998,squareness),
rho = 1 + s*(sqrt(2)-1),
x = rho / sqrt(2)
) log(0.5) / log(x);
// Function&Module: keyhole()
// Synopsis: Creates a 2D keyhole shape.
// SynTags: Geom, Path
@ -1988,133 +2118,6 @@ function reuleaux_polygon(n=3, r, d, anchor=CENTER, spin=0) =
// Function&Module: squircle()
// Synopsis: Creates a shape between a circle and a square, centered on the origin.
// SynTags: Geom, Path
// Topics: Shapes (2D), Paths (2D), Path Generators, Attachable
// See Also: circle(), square(), supershape()
// Usage: As Module
// squircle(size, [squareness], [style]) [ATTACHMENTS];
// Usage: As Function
// path = squircle(size, [squareness], [style]);
// Description:
// A [squircle](https://en.wikipedia.org/wiki/Squircle) is a shape intermediate between a square/rectangle and a circle/ellipse.Squircles are sometimes used to make dinner plates (more area for the same radius as a circle), keyboard buttons, and smartphone icons. Old CRT television screens also resembled elongated squircles.
// .
// There are multiple approaches to constructing a squircle. One approach is a special case of superellipse (shown in {{supershape()}} example 3), and uses exponents between 2 and infinity to adjust the shape. Another, the Fernández-Guasti squircle or FG squircle, arises from work in optics and uses a "squareness" parameter between 0 and 1 to adjust the shape. We use the same squareness parameter for both types, adjusting the internal FG parameter or superellipse exponent as needed to achieve the same squircle corner extents.
// .
// The FG style and superellipse style squircles are visually almost indistinguishable, with the superellipse having slightly rounder "corners" than FG for a given value of squareness. Either style requires just the two parameters `squareness` and `size`. The vertex distribution is adjusted to be more dense at the corners for smoothness at low values of `$fn`.
// .
// When called as a module, creates a 2D squircle with the desired squareness.
// When called as a function, returns a 2D path for a squircle.
// Arguments:
// size = Same as the `size` parameter in `square()`, can be a single number or a vector `[xsize,ysize]`.
// squareness = Value between 0 and 1. Controls the shape, setting the location of a squircle "corner" at the specified interpolated position between a circle and a square. When `squareness=0` the shape is a circle, and when `squareness=1` the shape is a square. For the "superellipse" style, the special case where the superellipse exponent is 4 (also known as *Lamé's quartic curve*) results in a squircle at the geometric mean between radial points on the circle and square, corresponding to squareness=0.456786. Default: 0.5
// style = method for generating a squircle, "fg" for Fernández-Guasti and "superellipse" for superellipse. Default: "fg"
// atype = anchor type, "box" for bounding box corners and sides, "perim" for the squircle corners
// $fn = Number of points. The special variables `$fs` and `$fa` are ignored. If set, `$fn` must be 12 or greater, and is rounded to the nearest multiple of 4. Points are generated so they are more dense around sharper curves. Default if not set: 48
// Examples(2D):
// squircle(size=50, squareness=0.4);
// squircle([80,60], 0.7, $fn=64);
// Example(2D): Ten increments of squareness parameter for a superellipse squircle
// for(sq=[0:0.1:1])
// stroke(squircle(100, sq, style="superellipse", $fn=128), closed=true, width=0.5);
// Example(2D): Standard vector anchors are based on the bounding box
// squircle(50, 0.6) show_anchors();
// Example(2D): Perimeter anchors, anchoring at bottom left and spinning 20°
// squircle([60,40], 0.5, anchor=(BOTTOM+LEFT), atype="perim", spin=20)
// show_anchors();
module squircle(size, squareness=0.5, style="fg", atype="box", anchor=CENTER, spin=0) {
check = assert(squareness >= 0 && squareness <= 1);
anchorchk = assert(in_list(atype, ["box", "perim"]));
size = is_num(size) ? [size,size] : point2d(size);
assert(all_positive(size), "All components of size must be positive.");
path = squircle(size, squareness, style, atype="box");
if (atype == "box") {
attachable(anchor, spin, two_d=true, size=size, extent=false) {
polygon(path);
children();
}
} else { // atype=="perim"
attachable(anchor, spin, two_d=true, extent=true, path=path) {
polygon(path);
children();
}
}
}
function squircle(size, squareness=0.5, style="fg", atype="box", anchor=CENTER, spin=0) =
assert(squareness >= 0 && squareness <= 1)
assert(is_num(size) || is_vector(size,2))
assert(in_list(atype, ["box", "perim"]))
let(
size = is_num(size) ? [size,size] : point2d(size),
path = style == "fg" ? _squircle_fg(size, squareness)
: style == "superellipse" ? _squircle_se(size, squareness)
: assert(false, "Style must be \"fg\" or \"superellipse\"")
) reorient(anchor, spin, two_d=true, size=atype=="box"?size:undef, path=atype=="box"?undef:path, p=path, extent=true);
/* FG squircle functions */
function _squircle_fg(size, squareness) = [
let(
sq = _linearize_squareness(squareness),
size = is_num(size) ? [size,size] : point2d(size),
aspect = size[1] / size[0],
r = 0.5 * size[0],
astep = $fn>=12 ? 90/round($fn/4) : 360/48
) for(a=[360:-astep:0.01]) let(
theta = a + sq * sin(4*a) * 30/PI, // tighter angle steps at corners
p = squircle_radius_fg(sq, r, theta)
) p*[cos(theta), aspect*sin(theta)]
];
function squircle_radius_fg(squareness, r, angle) = let(
s2a = abs(squareness*sin(2*angle))
) s2a>0 ? r*sqrt(2)/s2a * sqrt(1 - sqrt(1 - s2a*s2a)) : r;
function _linearize_squareness(s) =
// from Chamberlain Fong (2016). "Squircular Calculations". arXiv.
// https://arxiv.org/pdf/1604.02174v5
let(c = 2 - 2*sqrt(2), d = 1 - 0.5*c*s)
2 * sqrt((1+c)*s*s - c*s) / (d*d);
/* Superellipse squircle functions */
function _squircle_se(size, squareness) = [
let(
n = _squircle_se_exponent(squareness),
size = is_num(size) ? [size,size] : point2d(size),
ra = 0.5*size[0],
rb = 0.5*size[1],
astep = $fn>=12 ? 90/round($fn/4) : 360/48,
fgsq = _linearize_squareness(min(0.998,squareness)) // works well for distributing theta
) for(a=[360:-astep:0.01]) let(
theta = a + fgsq*sin(4*a)*30/PI, // tighter angle steps at corners
x = cos(theta),
y = sin(theta),
r = (abs(x)^n + abs(y)^n)^(1/n) // superellipse
//r = _superformula(theta=theta, m1=4,m2=4,n1=n,n2=n,n3=n,a=1,b=1)
) [ra*x, rb*y] / r
];
function squircle_radius_se(n, r, angle) = let(
x = cos(angle),
y = sin(angle)
) (abs(x)^n + abs(y)^n)^(1/n) / r;
function _squircle_se_exponent(squareness) = let(
// limit squareness; error if >0.99889, limit is smaller for r>1
s=min(0.998,squareness),
rho = 1 + s*(sqrt(2)-1),
x = rho / sqrt(2)
) log(0.5) / log(x);
// Section: Text
// Module: text()

View file

@ -50,7 +50,7 @@ EMPTY_VNF = [[],[]]; // The standard empty VNF with no vertices or faces.
// Arguments:
// points = A list of vertices to divide into columns and rows.
// ---
// caps = If true, add endcap faces to the first AND last rows.
// caps = If true, add endcap faces to the first **and** last rows.
// cap1 = If true, add an endcap face to the first row.
// cap2 = If true, add an endcap face to the last row.
// col_wrap = If true, add faces to connect the last column to the first.
@ -69,6 +69,38 @@ EMPTY_VNF = [[],[]]; // The standard empty VNF with no vertices or faces.
// col_wrap=true, caps=true, reverse=true, style="alt"
// );
// vnf_polyhedron(vnf);
// Example(3D,NoAxes,ThrownTogether,VPD=183): Open shape made from a three arcs.
// rows = [
// for(h=[-20:20:20])
// path3d(arc(r=40-abs(h), angle=280, 10), h)
// ];
// vnf = vnf_vertex_array(rows, reverse=true);
// vnf_polyhedron(vnf);
// color("green") vnf_wireframe(vnf);
// Example(3D,NoAxes,ThrownTogether,VPD=183): Open shape made from a three arcs, with `row_wrap=true`.
// rows = [
// for(h=[-20:20:20])
// path3d(arc(r=40-abs(h), angle=280, 10), h)
// ];
// vnf = vnf_vertex_array(rows, reverse=true, row_wrap=true);
// vnf_polyhedron(vnf);
// color("green") vnf_wireframe(vnf);
// Example(3D,NoAxes,ThrownTogether,VPD=183): Open shape made from a three arcs, with `col_wrap=true`.
// rows = [
// for(h=[-20:20:20])
// path3d(arc(r=40-abs(h), angle=280, 10), h)
// ];
// vnf = vnf_vertex_array(rows, reverse=true, col_wrap=true);
// vnf_polyhedron(vnf);
// color("green") vnf_wireframe(vnf);
// Example(3D,NoAxes,ThrownTogether,VPD=183): Open shape made from a three arcs, with `caps=true` and `col_wrap=true`.
// rows = [
// for(h=[-20:20:20])
// path3d(arc(r=40-abs(h), angle=280, 10), h)
// ];
// vnf = vnf_vertex_array(rows, reverse=true, caps=true, col_wrap=true);
// vnf_polyhedron(vnf);
// color("green") vnf_wireframe(vnf);
// Example(3D): Both `col_wrap` and `row_wrap` are true to make a torus.
// vnf = vnf_vertex_array(
// points=[
@ -242,22 +274,23 @@ function vnf_vertex_array(
// Topics: VNF Generators, Lists
// See Also: vnf_vertex_array(), vnf_join(), vnf_from_polygons(), vnf_merge_points()
// Usage:
// vnf = vnf_tri_array(points, [row_wrap], [reverse], [col_wrap], [caps], [cap1], [cap2])
// vnf = vnf_tri_array(points, [caps=], [cap1=], [cap2=], [reverse=], [col_wrap=], [row_wrap=])
// Description:
// Produces a VNF from an array of points where each row length can differ from the adjacent rows by any amount. This enables the construction of triangular or even irregular VNF patches. The resulting VNF can be wrapped along the rows by setting `row_wrap` to true, and wrapped along columns by setting `col_wrap` to true. It is possible to do both at once.
// If `row_wrap` is false, end caps can be generated across either the top and bottom rows.
// If `row_wrap` is false or not provided, end caps can be generated across the top and/or bottom rows.
// .
// The algorithm starts with the first point on each row and recursively walks around finding the minimum-length edge to make each new triangle face. This may result in several triangles being connected to one vertex. When triangulating two rows that happen to be equal length, the result is equivalent to {{vnf_vertex_array()}} using the "min_edge" style. If you already have a rectangular vertex list (equal length rows), you should use `vnf_vertex_array()` if you need a different triangulation style.
// .
// If you need to merge two VNF arrays that share edges using `vnf_join()` you can remove the duplicated vertices using `vnf_merge_points()`.
// Arguments:
// points = List of point lists for each row.
// row_wrap = If true, then add faces connecting the first row and last row. The rows may be unequal length.
// reverse = If true, reverses the direction of the faces.
// col_wrap = If true, then add faces connecting the first column and last column.
// caps = Close both first and last rows with end caps, if `row_wrap=false`.
// cap1 = Close first row with an end cap, if `row_wrap=false`.
// cap2 = Close last row with an end cap, if `row_wrap=false`.
// ---
// caps = If true, add endcap faces to the first **and** last rows.
// cap1 = If true, add an endcap face to the first row.
// cap2 = If true, add an endcap face to the last row.
// col_wrap = If true, add faces to connect the last column to the first.
// row_wrap = If true, add faces to connect the last row to the first.
// reverse = If true, reverse all face normals.
// Example(3D,NoAxes): Each row has one more point than the preceeding one.
// pts = [for(y=[1:1:10]) [for(x=[0:y-1]) [x,y,y]]];
// vnf = vnf_tri_array(pts);
@ -288,34 +321,7 @@ function vnf_vertex_array(
// vnf = vnf_tri_array(pts);
// vnf_wireframe(vnf,width=0.1);
// color("red")move_copies(flatten(pts)) sphere(r=.15,$fn=9);
// Example(3D,NoAxes,ThrownTogether): A simple open-ended shape made from a series of arcs with different number of vertices at different elevations. Clockwise from upper left: (1) no row or column wrapping, (2) wrap rows only, (3) wrap rows and columns, (4) wrap columns only, with caps. The `reverse=true` parameter is needed because arcs generate counterclockwise, while `vnf_tri_array()` expects clockwise, as with polygons.
// polypoints = [12, 16, 25, 26, 25, 18, 10];
// arc_elev = [1, 0, 6, 8, 10, 15, 14];
// rows = [
// for(i = [0:len(arc_elev)-1])
// path3d(arc(r=polypoints[i]*2, angle=280, $fn=polypoints[i]), 5*arc_elev[i])
// ];
// vnf1 = vnf_tri_array(rows, reverse=true); // no row or column wrapping
// translate([-60,60,0]) {
// vnf_polyhedron(vnf1);
// color("green") vnf_wireframe(vnf1);
// }
// vnf2 = vnf_tri_array(rows, reverse=true, row_wrap=true); // wrap rows only
// translate([60,60,0]) {
// vnf_polyhedron(vnf2);
// color("green") vnf_wireframe(vnf2);
// }
// vnf3 = vnf_tri_array(rows, reverse=true, row_wrap=true, col_wrap=true); // wrap rows and columns
// translate([60,-60,0]) {
// vnf_polyhedron(vnf3);
// color("green") vnf_wireframe(vnf3);
// }
// vnf4 = vnf_tri_array(rows, reverse=true, col_wrap=true, caps=true); // wrap columns only, with caps
// translate([-60,-60,0]) {
// vnf_polyhedron(vnf4);
// color("green") vnf_wireframe(vnf4);
// }
// Example(3D,NoAxes,Edges,VPR=[65,0,25]): Model of a cymbal with roughly same-size facets, using a different number of points for each concentric ring of vertices.
// Example(3D,NoAxes,Edges,VPR=[65,0,25],VPD=380,Med): Model of a cymbal with roughly same-size facets, using a different number of points for each concentric ring of vertices.
// include <BOSL2/beziers.scad>
// bez = [
// [[0,26], [35,26], [29,0], [80,16], [102,0]], //top
@ -334,9 +340,14 @@ function vnf_vertex_array(
// vnf_polyhedron(vnf);
// cylinder(30, d=8);
// }
function vnf_tri_array(points, row_wrap=false, reverse=false, col_wrap=false, caps=false, cap1=false, cap2=false) =
assert(!(row_wrap && (caps || cap1 || cap2)), "Caps cannot exist when row_wrap=true")
function vnf_tri_array(
points,
caps, cap1, cap2,
col_wrap=false,
row_wrap=false,
reverse=false
) =
assert(!(any([caps,cap1,cap2]) && row_wrap), "Cannot combine caps with row_wrap")
let(
plen = len(points),
// append first vertex of each polygon to its end if wrapping columns
@ -348,10 +359,9 @@ function vnf_tri_array(points, row_wrap=false, reverse=false, col_wrap=false, ca
] : points,
addcol = col_wrap ? len(st[0])-len(points[0]) : 0,
rowstarts = [ for(i=[0:plen-1]) len(st[i]) ],
capfirst = caps ? true : cap1,
caplast = caps ? true : cap2,
capfirst = first_defined([cap1,caps,false]),
caplast = first_defined([cap2,caps,false]),
pcumlen = [0, each cumsum(rowstarts)],
faces = flatten([
// close first end
if (capfirst)
@ -368,7 +378,6 @@ function vnf_tri_array(points, row_wrap=false, reverse=false, col_wrap=false, ca
vnf = [flatten(st), faces]
) col_wrap ? vnf_merge_points(vnf) : vnf;
/*
Recursively triangulate between two 3D paths (which can be different
in length by any amount), starting at index 0 and generate a list of