Compare commits

..

23 commits

Author SHA1 Message Date
Revar Desmera
a351fae9a9
Merge pull request #1522 from adrianVmariano/master
highlight
2024-12-11 20:49:10 -08:00
Revar Desmera
5e6b3e30df
Merge pull request #1521 from amatulic/anachronist_vnf
Replaced vnf_tri_array(), added example to vnf_from_polygons()
2024-12-11 20:40:34 -08:00
Revar Desmera
76dedf07fd
Merge pull request #1517 from amatulic/anachronist_dev
added squircle to shapes2d.scad
2024-12-11 20:32:47 -08:00
Adrian Mariano
9ed7a3ec48 add highlight and highlight_this 2024-12-11 16:20:58 -05:00
Alex Matulich
fe98042255 Changed height and view angle of cymbal model, removed redundant comment block 2024-12-11 09:28:25 -08:00
Alex Matulich
2a2062231a Fixed another issue from inadvertent undo operation, retested 2024-12-11 07:43:33 -08:00
Alex Matulich
1ed79d4cac Restored critical line in vnf_tri_array that somehow got removed while cleaning up comments 2024-12-11 07:26:11 -08:00
Alex Matulich
564db53c51 Replaced vnf_tri_array() with additional examples, added example to vnf_from_polygons() 2024-12-10 23:36:03 -08:00
Alex Matulich
3c47a8253e Fixed link to supershape() in squircle() documentation 2024-12-10 14:45:11 -08:00
Alex Matulich
50112abbe9 Corrected Example headers in squircle 2024-12-10 11:49:42 -08:00
Alex Matulich
df720b99de Removed another trailing comma in let() 2024-12-10 09:13:00 -08:00
Alex Matulich
ba73f4e1fd Fixed 2021.01 syntax error, removed _module_call flag from function parameter, fixed spelling error in Reuleax documentation 2024-12-10 08:57:40 -08:00
Alex Matulich
cba3391131 Replaced override anchoring with builtin perimeter anchoring, minor fixes, documentation cleanup 2024-12-09 20:40:42 -08:00
Alex Matulich
835cbc0f00 Removed _squircle_anchor_radius() function no longer needed 2024-12-09 12:29:03 -08:00
Alex Matulich
039485e913 Added paragraph breaks in coments for squircle 2024-12-09 12:21:53 -08:00
Alex Matulich
c18c955376 Revised to manage attachments 2024-12-09 11:59:46 -08:00
Alex Matulich
a2c30affea Corrected link in comment about linearizing squareness in squircle() 2024-12-06 09:07:44 -08:00
Alex Matulich
6c92e0313a Final fix for exact squareness linearity in squircle() 2024-12-06 08:28:03 -08:00
Alex Matulich
504c92bba9 Corrected anchor positions, improved linear response of squareness, added example 2024-12-05 16:59:58 -08:00
Alex Matulich
72a2c1470a Merge branch 'anachronist_dev' of https://github.com/amatulic/BOSL2 into anachronist_dev 2024-12-05 15:20:58 -08:00
Alex Matulich
495ebbefd8 Expanded documentation to explain differences from supershape() 2024-12-05 15:17:28 -08:00
Alex Matulich
dd96a30c92 Expanded documentation to explain differences from supershape() 2024-12-05 10:18:10 -08:00
Alex Matulich
65ec17f4b3 added squircle to shapes2d.scad 2024-12-04 23:26:54 -08:00
5 changed files with 397 additions and 85 deletions

View file

@ -43,6 +43,9 @@ $ghost_this=false;
$ghost=false; $ghost=false;
$ghosting=false; // Ghosting is in effect, so don't apply it again $ghosting=false; // Ghosting is in effect, so don't apply it again
$highlight_this=false;
$highlight=false;
_ANCHOR_TYPES = ["intersect","hull"]; _ANCHOR_TYPES = ["intersect","hull"];
@ -1154,8 +1157,8 @@ module tag_this(tag)
// that don't use {{attachable()}} or built in modules such as // that don't use {{attachable()}} or built in modules such as
// - `polygon()` // - `polygon()`
// - `projection()` // - `projection()`
// - `polyhedron()` (or use [`vnf_polyhedron()`](vnf.scad#vnf_polyhedron)) // - `polyhedron()` (or use {{vnf_polyhedron()}})
// - `linear_extrude()` (or use [`linear_sweep()`](regions.scad#linear_sweep)) // - `linear_extrude()` (or use {{linear_sweep()}})
// - `rotate_extrude()` // - `rotate_extrude()`
// - `surface()` // - `surface()`
// - `import()` // - `import()`
@ -3107,24 +3110,15 @@ module attachable(
if (expose_tags || _is_shown()){ if (expose_tags || _is_shown()){
if (!keep_color) if (!keep_color)
_color($color) _color($color)
if (($ghost || $ghost_this) && !$ghosting) _show_ghost() children(0);
%union(){
$ghosting=true;
children(0);
}
else children(0);
else { else {
$save_color=undef; // Force color_this() color in effect to persist for the entire object $save_color=undef; // Force color_this() color in effect to persist for the entire object
if (($ghost || $ghost_this) && !$ghosting) _show_ghost() children(0);
%union(){
$ghosting=true;
children(0);
}
else children(0);
} }
} }
let( let(
$ghost_this=false, $ghost_this=false,
$highlight_this=false,
$tag=default($save_tag,$tag), $tag=default($save_tag,$tag),
$save_tag=undef, $save_tag=undef,
$color=default($save_color,$color), $color=default($save_color,$color),
@ -3134,6 +3128,26 @@ module attachable(
} }
} }
module _show_highlight()
{
if ($highlight || $highlight_this)
#children();
else
children();
}
module _show_ghost()
{
if (($ghost || $ghost_this) && !$ghosting)
%union(){
$ghosting=true;
_show_highlight()children();
}
else _show_highlight()children();
}
// Function: reorient() // Function: reorient()
// Synopsis: Calculates the transformation matrix needed to reorient an object. // Synopsis: Calculates the transformation matrix needed to reorient an object.
// SynTags: Trans, Path, VNF // SynTags: Trans, Path, VNF

View file

@ -161,21 +161,74 @@ module color_overlaps(color="red") {
%children(); %children();
} }
// Section: Setting Object Transparency // Section: Setting Object Modifiers
// Module: highlight()
// Synopsis: Sets # modifier for attachable children and their descendents.
// SynTags: Trans
// Topics: Attachments, Modifiers
// See Also: highlight_this(), ghost(), ghost_this(), recolor(), color_this()
// Usage:
// highlight([highlight]) CHILDREN;
// Description:
// Sets the `#` modifier for the attachable children and their descendents until another {{highlight()}} or {{highlight_this()}}.
// By default, turns `#` on, which makes the children transparent pink and displays them even if they are subtracted from the model.
// Give the `false` parameter to disable the modifier and restore children to normal.
// Do not mix this with user supplied `#` modifiers anywhere in the geometry tree.
// Arguments:
// highlight = If true set the descendents to use `#`; if false, disable `#` for descendents. Default: true
// Example(3D):
// highlight() cuboid(10)
// highlight(false) attach(RIGHT,BOT)cuboid(5);
function highlight(highlight) = no_function("highlight");
module highlight(highlight=true)
{
$highlight=highlight;
children();
}
// Module: highlight_this()
// Synopsis: Apply # modifier to children at a single level.
// SynTags: Trans
// Topics: Attachments, Modifiers
// See Also: highlight(), ghost(), ghost_this(), recolor(), color_this()
// Usage:
// highlight_this() CHILDREN;
// Description:
// Applies the `#` modifier to the children at a single level, reverting to the previous highlight state for further descendents.
// This works only with attachables and you cannot give the `#` operator anywhere in the geometry tree.
// Example(3D):
// highlight_this()
// cuboid(10)
// attach(TOP,BOT)cuboid(5);
function highlight_this() = no_function("highlight_this");
module highlight_this()
{
$highlight_this=true;
children();
}
// Module: ghost() // Module: ghost()
// Synopsis: Sets transparency for attachable children and their descendents. // Synopsis: Sets % modifier for attachable children and their descendents.
// SynTags: Trans // SynTags: Trans
// Topics: Attachments // Topics: Attachments, Modifiers
// See Also: ghost_this(), recolor(), color_this() // See Also: ghost_this(), recolor(), color_this()
// Usage: // Usage:
// ghost([ghost]) CHILDREN; // ghost([ghost]) CHILDREN;
// Description: // Description:
// Sets the transparency for the attachable children and their descendents until another {{ghost()}} or {{ghost_this()}}. // Sets the `%` modifier for the attachable children and their descendents until another {{ghost()}} or {{ghost_this()}}.
// By default, turns transparency on. Give the `false` parameter to disable transparency. // By default, turns `%` on, which makes the children gray and also removes them from interaction with the model.
// Do not mix this with user supplied `%` operators anywhere in the geometry tree. // Give the `false` parameter to disable the modifier and restore children to normal.
// Do not mix this with user supplied `%` modifiers anywhere in the geometry tree.
// Arguments: // Arguments:
// ghost = If true set the descendents to be transparent; if false, disable transparency. Default: true // ghost = If true set the descendents to use `%`; if false, disable `%` for descendents. Default: true
// Example(3D): // Example(3D):
// ghost() cuboid(10) // ghost() cuboid(10)
// ghost(false) cuboid(5); // ghost(false) cuboid(5);
@ -188,14 +241,14 @@ module ghost(ghost=true)
// Module: ghost_this() // Module: ghost_this()
// Synopsis: Makes the children at a single level transparent. // Synopsis: Apply % modifier to children at a single level.
// SynTags: Trans // SynTags: Trans
// Topics: Attachments // Topics: Attachments, Modifiers
// See Also: ghost(), recolor(), color_this() // See Also: ghost(), recolor(), color_this()
// Usage: // Usage:
// ghost_this() CHILDREN; // ghost_this() CHILDREN;
// Description: // Description:
// Makes the children transparent for one level, reverting to the previous transparency state for further descendents. // Applies the `%` modifier to the children at a single level, reverting to the previous ghost state for further descendents.
// This works only with attachables and you cannot give the `%` operator anywhere in the geometry tree. // This works only with attachables and you cannot give the `%` operator anywhere in the geometry tree.
// Example(3D): // Example(3D):
// ghost_this() cuboid(10) // ghost_this() cuboid(10)

View file

@ -1609,6 +1609,14 @@ function real_roots(p,eps=undef,tol=1e-14) =
// x0 = endpoint of interval to search for root // x0 = endpoint of interval to search for root
// x1 = second endpoint of interval to search for root // x1 = second endpoint of interval to search for root
// tol = tolerance for solution. Default: 1e-15 // tol = tolerance for solution. Default: 1e-15
// Example(2D): Solve x*sin(x)=4
// f = function (x) x*sin(x)-4;
// root=root_find(f, 0,25); // root = 15.2284
// // Graphical verification
// stroke([for(x=[0:25]) [x,f(x)]],width=.2);
// color("red")move([root,f(root)])
// circle(r=.25,$fn=16);
// The algorithm is based on Brent's method and is a combination of // The algorithm is based on Brent's method and is a combination of
// bisection and inverse quadratic approximation, where bisection occurs // bisection and inverse quadratic approximation, where bisection occurs

View file

@ -1929,7 +1929,7 @@ function _superformula(theta,m1,m2,n1,n2=1,n3=1,a=1,b=1) =
// Usage: As Function // Usage: As Function
// path = reuleaux_polygon(n, r|d=, ...); // path = reuleaux_polygon(n, r|d=, ...);
// Description: // Description:
// When called as a module, reates a 2D Reuleaux Polygon; a constant width shape that is not circular. Uses "intersect" type anchoring. // When called as a module, creates a 2D Reuleaux Polygon; a constant width shape that is not circular. Uses "intersect" type anchoring.
// When called as a function, returns a 2D path for a Reulaux Polygon. // When called as a function, returns a 2D path for a Reulaux Polygon.
// Arguments: // Arguments:
// n = Number of "sides" to the Reuleaux Polygon. Must be an odd positive number. Default: 3 // n = Number of "sides" to the Reuleaux Polygon. Must be an odd positive number. Default: 3
@ -1988,6 +1988,133 @@ 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 // Section: Text
// Module: text() // Module: text()

230
vnf.scad
View file

@ -237,21 +237,27 @@ function vnf_vertex_array(
// Function: vnf_tri_array() // Function: vnf_tri_array()
// Synopsis: Returns a VNF from an array of points. // Synopsis: Returns a VNF from an array of points. The array need not be rectangular.
// SynTags: VNF // SynTags: VNF
// Topics: VNF Generators, Lists // Topics: VNF Generators, Lists
// See Also: vnf_vertex_array(), vnf_join(), vnf_from_polygons(), vnf_from_region() // See Also: vnf_vertex_array(), vnf_join(), vnf_from_polygons(), vnf_merge_points()
// Usage: // Usage:
// vnf = vnf_tri_array(points, [row_wrap], [reverse]) // vnf = vnf_tri_array(points, [row_wrap], [reverse], [col_wrap], [caps], [cap1], [cap2])
// Description: // Description:
// Produces a VNF from an array of points where each row length can differ from the adjacent rows by up to 2 in length. This enables // 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.
// the construction of triangular VNF patches. The resulting VNF can be wrapped along the rows by setting `row_wrap` to true. // If `row_wrap` is false, end caps can be generated across either the top and bottom rows.
// You cannot wrap columns: if you need to do that you'll need to merge two VNF arrays that share edges. Degenerate faces // .
// are not included in the output, but if this results in unused vertices they will still appear in the output. // 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: // Arguments:
// points = List of point lists for each row // points = List of point lists for each row.
// row_wrap = If true then add faces connecting the first row and last row. These rows must differ by at most 2 in length. // row_wrap = If true, then add faces connecting the first row and last row. The rows may be unequal length.
// reverse = Set this to reverse the direction of the faces // 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`.
// Example(3D,NoAxes): Each row has one more point than the preceeding one. // 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]]]; // pts = [for(y=[1:1:10]) [for(x=[0:y-1]) [x,y,y]]];
// vnf = vnf_tri_array(pts); // vnf = vnf_tri_array(pts);
@ -276,60 +282,149 @@ function vnf_vertex_array(
// vnf_tri_array(pts2)]); // vnf_tri_array(pts2)]);
// color("green")vnf_wireframe(vnf,width=0.1); // color("green")vnf_wireframe(vnf,width=0.1);
// vnf_polyhedron(vnf); // vnf_polyhedron(vnf);
// Example(3D,NoAxes): Point count can change irregularly // Example(3D,NoAxes): The number of points per row can change irregularly by any amount.
// lens = [10,9,7,5,6,8,8,10]; // lens = [10,9,8,6,4,8,2,5,3,10,4];
// pts = [for(y=idx(lens)) lerpn([-lens[y],y,y],[lens[y],y,y],lens[y])]; // pts = [for(y=idx(lens)) lerpn([-lens[y],y,y],[lens[y],y,y],lens[y])];
// vnf = vnf_tri_array(pts); // vnf = vnf_tri_array(pts);
// vnf_wireframe(vnf,width=0.1); // vnf_wireframe(vnf,width=0.1);
// color("red")move_copies(flatten(pts)) sphere(r=.15,$fn=9); // color("red")move_copies(flatten(pts)) sphere(r=.15,$fn=9);
function vnf_tri_array(points, row_wrap=false, reverse=false) = // 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.
let( // polypoints = [12, 16, 25, 26, 25, 18, 10];
lens = [for(row=points) len(row)], // arc_elev = [1, 0, 6, 8, 10, 15, 14];
rowstarts = [0,each cumsum(lens)], // rows = [
faces = // for(i = [0:len(arc_elev)-1])
[for(i=[0:1:len(points) - 1 - (row_wrap ? 0 : 1)]) each // path3d(arc(r=polypoints[i]*2, angle=280, $fn=polypoints[i]), 5*arc_elev[i])
let( // ];
rowstart = rowstarts[i], // vnf1 = vnf_tri_array(rows, reverse=true); // no row or column wrapping
nextrow = select(rowstarts,i+1), // translate([-60,60,0]) {
delta = select(lens,i+1)-lens[i] // vnf_polyhedron(vnf1);
) // color("green") vnf_wireframe(vnf1);
delta == 0 ? // }
[for(j=[0:1:lens[i]-2]) reverse ? [j+rowstart+1, j+rowstart, j+nextrow] : [j+rowstart, j+rowstart+1, j+nextrow], // vnf2 = vnf_tri_array(rows, reverse=true, row_wrap=true); // wrap rows only
for(j=[0:1:lens[i]-2]) reverse ? [j+rowstart+1, j+nextrow, j+nextrow+1] : [j+rowstart+1, j+nextrow+1, j+nextrow]] : // translate([60,60,0]) {
delta == 1 ? // vnf_polyhedron(vnf2);
[for(j=[0:1:lens[i]-2]) reverse ? [j+rowstart+1, j+rowstart, j+nextrow+1] : [j+rowstart, j+rowstart+1, j+nextrow+1], // color("green") vnf_wireframe(vnf2);
for(j=[0:1:lens[i]-1]) reverse ? [j+rowstart, j+nextrow, j+nextrow+1] : [j+rowstart, j+nextrow+1, j+nextrow]] : // }
delta == -1 ? // vnf3 = vnf_tri_array(rows, reverse=true, row_wrap=true, col_wrap=true); // wrap rows and columns
[for(j=[0:1:lens[i]-3]) reverse ? [j+rowstart+1, j+nextrow, j+nextrow+1]: [j+rowstart+1, j+nextrow+1, j+nextrow], // translate([60,-60,0]) {
for(j=[0:1:lens[i]-2]) reverse ? [j+rowstart+1, j+rowstart, j+nextrow] : [j+rowstart, j+rowstart+1, j+nextrow]] : // vnf_polyhedron(vnf3);
let(count = floor((lens[i]-1)/2)) // color("green") vnf_wireframe(vnf3);
delta == 2 ? // }
[ // vnf4 = vnf_tri_array(rows, reverse=true, col_wrap=true, caps=true); // wrap columns only, with caps
for(j=[0:1:count-1]) reverse ? [j+rowstart+1, j+rowstart, j+nextrow+1] : [j+rowstart, j+rowstart+1, j+nextrow+1], // top triangles left // translate([-60,-60,0]) {
for(j=[count:1:lens[i]-2]) reverse ? [j+rowstart+1, j+rowstart, j+nextrow+2] : [j+rowstart, j+rowstart+1, j+nextrow+2], // top triangles right // vnf_polyhedron(vnf4);
for(j=[0:1:count]) reverse ? [j+rowstart, j+nextrow, j+nextrow+1] : [j+rowstart, j+nextrow+1, j+nextrow], // bot triangles left // color("green") vnf_wireframe(vnf4);
for(j=[count+1:1:select(lens,i+1)-2]) reverse ? [j+rowstart-1, j+nextrow, j+nextrow+1] : [j+rowstart-1, j+nextrow+1, j+nextrow], // bot triangles right // }
] : // 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.
delta == -2 ? // include <BOSL2/beziers.scad>
[ // bez = [
for(j=[0:1:count-2]) reverse ? [j+nextrow, j+nextrow+1, j+rowstart+1] : [j+nextrow, j+rowstart+1, j+nextrow+1], // [[0,26], [35,26], [29,0], [80,16], [102,0]], //top
for(j=[count-1:1:lens[i]-4]) reverse ? [j+nextrow,j+nextrow+1,j+rowstart+2] : [j+nextrow,j+rowstart+2, j+nextrow+1], // [[99,-1], [79,15], [28,-1], [34,25], [-1,25]] // bottom
for(j=[0:1:count-1]) reverse ? [j+nextrow, j+rowstart+1, j+rowstart] : [j+nextrow, j+rowstart, j+rowstart+1], // ];
for(j=[count:1:select(lens,i+1)]) reverse ? [ j+nextrow-1, j+rowstart+1, j+rowstart]: [ j+nextrow-1, j+rowstart, j+rowstart+1], // points = [
] : // for(b=bez)
assert(false,str("Unsupported row length difference of ",delta, " between row ",i," and ",(i+1)%len(points))) // for(u=[0.01:0.04:1]) let(
], // bzp = bezier_points(b,u),
verts = flatten(points), // r = bzp[0],
culled_faces= // n = max(3, round(360 / (6/r * 180/PI)))
[for(face=faces) // ) path3d(regular_ngon(n, r=r), bzp[1])
if (norm(verts[face[0]]-verts[face[1]])>EPSILON && // ];
norm(verts[face[1]]-verts[face[2]])>EPSILON && // vnf = vnf_tri_array(points, reverse=true, col_wrap=true, caps=true);
norm(verts[face[2]]-verts[face[0]])>EPSILON) // color("brown") difference() {
face // vnf_polyhedron(vnf);
] // cylinder(30, d=8);
) // }
[flatten(points), culled_faces];
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")
let(
plen = len(points),
// append first vertex of each polygon to its end if wrapping columns
st = col_wrap ? [
for(i=[0:plen-1])
points[i][0] != points[i][len(points[i])-1]
? concat(points[i], [points[i][0]])
: points[i]
] : 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,
pcumlen = [0, each cumsum(rowstarts)],
faces = flatten([
// close first end
if (capfirst)
if (reverse) [[ for(i=[0:rowstarts[0]-1-addcol]) i ]]
else [[ for(i=[rowstarts[0]-1-addcol:-1:0]) i ]],
// triangulate between the two polygons
for(i = [0:plen-2+(row_wrap?1:0)]) let(j = (i+1)%plen)
_lofttri(st[i], st[j], pcumlen[i], pcumlen[j], rowstarts[i], rowstarts[j], reverse),
// close up the last end
if (caplast)
if (reverse) [[ for(i=[pcumlen[plen]-1-addcol:-1:pcumlen[plen-1]]) i ]]
else [[ for(i=[pcumlen[plen-1]:pcumlen[plen]-1-addcol]) i ]]
]),
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
triangles with minimum new-side length.
The first side of the first triangle always connects the two first
vertices of each path.
To triangulate between two closed paths, the first and last vertices
must be the same.
Parameters:
p1 = first path, an array of [x,y,z] vertices
p2 = second path, an array of [x,y,z] vertices
i1offset = index offset of first vertex in the first path
(sum of any prior path lengths)
i2offset = index offset of first vertex in the second path
(sum of any prior path lengths)
n1 = number of vertices in first path
n2 = number of vertices in second path
reverse = if true, assume a polygon path goes around
counterclockwise with respect to the direction from
p1 to p2 (right hand rule), clockwise if false
Other parameters are for internal use:
trilist[] = array of triangles to return
i1 = vertex index on p1 of the next triangle
i2 = vertex index on p2 of the next triangle
(next triangle vertex found can be on either p1 or p2, depending
on which triangle is smaller.)
Returns an array of triangles using vertex indices offset by
i1offset and i2offset
*/
function _lofttri(p1, p2, i1offset, i2offset, n1, n2, reverse=false, trilist=[], i1=0, i2=0) = n1!=n2 ?
// unequal row lengths
let(
t1 = i1 < n1 ? i1+1 : n1, // test point 1
t2 = i2 < n2 ? i2+1 : n2, // test point 2
d12 = t2>=n2 ? 9e+9 : norm(p2[t2]-p1[i1]), // distance from i1 to t2
d21 = t1>=n1 ? 9e+9 : norm(p1[t1]-p2[i2]), // distance from i2 to t1
triangle = reverse ?
[i1offset+i1, i2offset+i2, d12<d21 ? i2offset+t2 : i1offset+t1] :
[i2offset+i2, i1offset+i1, d12<d21 ? i2offset+t2 : i1offset+t1]
) t1>=n1 && t2>=n2 ? trilist :
_lofttri(p1, p2, i1offset, i2offset, n1, n2, reverse, concat(trilist, [triangle]), d12<d21 ? i1 : t1, d12<d21 ? t2 : i2)
: // equal row lengths
let(n=n1, i=i1,
t = i < n ? i+1 : n, // test point
d12 = t>=n ? 9e+9 : norm(p2[t]-p1[i]), // distance from p1 to new p2
d21 = t>=n ? 9e+9 : norm(p1[t]-p2[i]), // distance from p2 to new p1
triangle1 = reverse ?
[i1offset+i, i2offset+i, d12<d21 ? i2offset+t : i1offset+t] :
[i2offset+i, i1offset+i, d12<d21 ? i2offset+t : i1offset+t],
triangle2 = reverse ?
[i2offset+t, i1offset+t, d12<d21 ? i1offset+i : i2offset+i] :
[i1offset+t, i2offset+t, d12<d21 ? i1offset+i : i2offset+i]
) t>=n ? trilist :
_lofttri(p1, p2, i1offset, i2offset, n, n, reverse, concat(trilist, [triangle1, triangle2]), t, t);
// Function: vnf_join() // Function: vnf_join()
@ -436,7 +531,22 @@ function vnf_join(vnfs) =
// Arguments: // Arguments:
// polygons = The list of 3D polygons to turn into a VNF // polygons = The list of 3D polygons to turn into a VNF
// fast = Set to true to skip area and coplanarity checks for increased speed. Default: false // fast = Set to true to skip area and coplanarity checks for increased speed. Default: false
// eps = Polygons with area small than this are discarded. Default: EPSILON // eps = Polygons with area smaller than this are discarded. Default: EPSILON
// Example(3D,VPR=[60,0,40]): Construction of a dodecahedron from pentagon faces.
// dihedral = 2*atan(PHI); // dodecahedron face dihedral
// rpenta = 10; // pentagon face radius
// edge = 2*rpenta*sin(36); // edge length
// inrad = 0.5*edge * PHI*PHI/sqrt(3-PHI); // inner radius
// face3d = path3d(pentagon(rpenta), inrad); // single face
// facepoints = [
// face3d,
// for(a=[36:72:360]) zrot(a, yrot(180-dihedral, face3d)),
// for(a=[36:72:360]) zrot(a, yrot(360-dihedral, face3d)),
// yrot(180, face3d)
// ];
// vnf = vnf_from_polygons(facepoints, fast=true);
// vnf_polyhedron(vnf);
function vnf_from_polygons(polygons,fast=false,eps=EPSILON) = function vnf_from_polygons(polygons,fast=false,eps=EPSILON) =
assert(is_list(polygons) && is_path(polygons[0]),"Input should be a list of polygons") assert(is_list(polygons) && is_path(polygons[0]),"Input should be a list of polygons")
let( let(