From 65ec17f4b33d9545b091c41832fabba1fbbc9933 Mon Sep 17 00:00:00 2001 From: Alex Matulich Date: Wed, 4 Dec 2024 23:26:54 -0800 Subject: [PATCH 01/23] added squircle to shapes2d.scad --- shapes2d.scad | 69 ++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 68 insertions(+), 1 deletion(-) diff --git a/shapes2d.scad b/shapes2d.scad index 45fda070..d87f58fd 100644 --- a/shapes2d.scad +++ b/shapes2d.scad @@ -1929,7 +1929,7 @@ function _superformula(theta,m1,m2,n1,n2=1,n3=1,a=1,b=1) = // Usage: As Function // path = reuleaux_polygon(n, r|d=, ...); // 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. // Arguments: // n = Number of "sides" to the Reuleaux Polygon. Must be an odd positive number. Default: 3 @@ -1988,6 +1988,73 @@ 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() +// Usage: As Module +// squircle(squareness, size) [ATTACHMENTS]; +// Usage: As Function +// path = squircle(squareness, size); +// Description: +// A 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 squircles. +// When called as a module, creates a 2D squircle with the desired squareness. Uses "intersect" type anchoring. +// When called as a function, returns a 2D path for a squircle. +// Arguments: +// squareness = Value between 0 and 1. Controls the shape of the squircle. When `squareness=0` the shape is a circle, and when `squareness=1` the shape is a square. Default: 0.8 +// size = Bounding box of the squircle, same as the `size` parameter in `square()`, can be a single number or an `[xsize,ysize]` vector. Default: [10,10] +// $fn = Number of points. 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 non-uniformly around the squircle so they are more dense sharper curves. Default if not set: 40 +// Examples(2D): +// squircle(squareness=0.5, size=50); +// squircle(0.95, [80,60], $fn=64); +// Examples(2D): Standard vector anchors are based on extents +// squircle(0.8, 50) show_anchors(custom=false); +// Examples(2D): Named anchors exist for the sides and corners +// squircle(0.8, 50) show_anchors(std=false); +module squircle(squareness=0.8, size=[10,10], anchor=CENTER, spin=0) { + check = assert(squareness >= 0 && squareness <= 1); + bbox = is_num(size) ? [size,size] : point2d(size); + assert(all_positive(bbox), "All components of size must be positive."); + path = squircle(squareness, size); + anchors = [ + for (i = [0:1:3]) let( + ca = 360 - i*90, + cp = polar_to_xy(squircle_radius(squareness, bbox[0], ca), ca) + ) named_anchor(str("side",i), cp, unit(cp,BACK), 0), + for (i = [0:1:3]) let( + ca = 360-45 - i*90, + cp = polar_to_xy(squircle_radius(squareness, bbox[0], ca), ca) + ) named_anchor(str("corner",i), cp, unit(cp,BACK), 0) + ]; + attachable(anchor,spin, two_d=true, path=path, extent=false, anchors=anchors) { + polygon(path); + children(); + } +} + + +function squircle(squareness=0.8, size=[10,10]) = + assert(squareness >= 0 && squareness <= 1) [ + let( + sq = sqrt(squareness), // somewhat linearize the squareness response + bbox = is_num(size) ? [size,size] : point2d(size), + aspect = bbox[1] / bbox[0], + r = 0.5 * bbox[0], + astep = $fn>=12 ? 90/round($fn/4) : 9 + ) for(a=[360:-astep:0.01]) let( + theta = a + sq * sin(4*a) * 30/PI, // tighter angle steps at corners + p = squircle_radius(sq, r, theta) + ) [p*cos(theta), aspect*p*sin(theta)] +]; + + +function squircle_radius(squareness, r, angle) = let( + s2a = abs(squareness*sin(2*angle)) + ) s2a>0 ? r*sqrt(2)/s2a * sqrt(1 - sqrt(1 - s2a*s2a)) : r; + + + // Section: Text // Module: text() From dd96a30c92731c60cf94f82395596b617b7098fa Mon Sep 17 00:00:00 2001 From: Alex Matulich Date: Thu, 5 Dec 2024 10:18:10 -0800 Subject: [PATCH 02/23] Expanded documentation to explain differences from supershape() --- shapes2d.scad | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/shapes2d.scad b/shapes2d.scad index d87f58fd..cb63af30 100644 --- a/shapes2d.scad +++ b/shapes2d.scad @@ -1992,14 +1992,15 @@ function reuleaux_polygon(n=3, r, d, anchor=CENTER, spin=0) = // 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() +// See Also: circle(), square(), supershape() // Usage: As Module // squircle(squareness, size) [ATTACHMENTS]; // Usage: As Function // path = squircle(squareness, size); // Description: -// A 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 squircles. -// When called as a module, creates a 2D squircle with the desired squareness. Uses "intersect" type anchoring. +// A squircle is a shape intermediate between a square/rectangle and a circle/ellipse. A squircle is a special case of supershape (shown in `supershape()` example 3), but this implementation uses a different method that requires just two parameters, and the vertex distribution is optimized for smoothness. +// 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 squircles. +// When called as a module, creates a 2D squircle with the desired squareness. Uses "intersect" type anchoring. // When called as a function, returns a 2D path for a squircle. // Arguments: // squareness = Value between 0 and 1. Controls the shape of the squircle. When `squareness=0` the shape is a circle, and when `squareness=1` the shape is a square. Default: 0.8 From 495ebbefd85025111cc889f2787e11dce536757b Mon Sep 17 00:00:00 2001 From: Alex Matulich Date: Thu, 5 Dec 2024 10:18:10 -0800 Subject: [PATCH 03/23] Expanded documentation to explain differences from supershape() --- shapes2d.scad | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/shapes2d.scad b/shapes2d.scad index d87f58fd..3de6bcb4 100644 --- a/shapes2d.scad +++ b/shapes2d.scad @@ -1992,27 +1992,31 @@ function reuleaux_polygon(n=3, r, d, anchor=CENTER, spin=0) = // 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() +// See Also: circle(), square(), supershape() // Usage: As Module // squircle(squareness, size) [ATTACHMENTS]; // Usage: As Function // path = squircle(squareness, size); // Description: -// A 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 squircles. -// When called as a module, creates a 2D squircle with the desired squareness. Uses "intersect" type anchoring. +// A squircle is a shape intermediate between a square/rectangle and a circle/ellipse. A squircle is a special case of supershape (shown in `supershape()` example 3), but this implementation uses a different method that requires just two parameters, and the vertex distribution is optimized for smoothness. +// 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 squircles. +// When called as a module, creates a 2D squircle with the desired squareness. Uses "intersect" type anchoring. // When called as a function, returns a 2D path for a squircle. // Arguments: -// squareness = Value between 0 and 1. Controls the shape of the squircle. When `squareness=0` the shape is a circle, and when `squareness=1` the shape is a square. Default: 0.8 +// squareness = Value between 0 and 1. Controls the shape of the squircle. When `squareness=0` the shape is a circle, and when `squareness=1` the shape is a square. Default: 0.7 // size = Bounding box of the squircle, same as the `size` parameter in `square()`, can be a single number or an `[xsize,ysize]` vector. Default: [10,10] // $fn = Number of points. 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 non-uniformly around the squircle so they are more dense sharper curves. Default if not set: 40 // Examples(2D): -// squircle(squareness=0.5, size=50); -// squircle(0.95, [80,60], $fn=64); +// squircle(squareness=0.4, size=50); +// squircle(0.8, [80,60], $fn=64); +// Examples(2D): Ten increments of squareness parameter +// for(sq=[0:0.1:1]) +// stroke(squircle(sq, 100, $fn=128), closed=true, width=0.5); // Examples(2D): Standard vector anchors are based on extents // squircle(0.8, 50) show_anchors(custom=false); // Examples(2D): Named anchors exist for the sides and corners // squircle(0.8, 50) show_anchors(std=false); -module squircle(squareness=0.8, size=[10,10], anchor=CENTER, spin=0) { +module squircle(squareness=0.7, size=[10,10], anchor=CENTER, spin=0) { check = assert(squareness >= 0 && squareness <= 1); bbox = is_num(size) ? [size,size] : point2d(size); assert(all_positive(bbox), "All components of size must be positive."); @@ -2034,10 +2038,11 @@ module squircle(squareness=0.8, size=[10,10], anchor=CENTER, spin=0) { } -function squircle(squareness=0.8, size=[10,10]) = +function squircle(squareness=0.7, size=[10,10]) = assert(squareness >= 0 && squareness <= 1) [ let( - sq = sqrt(squareness), // somewhat linearize the squareness response + sqlim = max(0, min(1, squareness)), + sq = sqrt(sqlim*(2-sqlim)), // somewhat linearize squareness response bbox = is_num(size) ? [size,size] : point2d(size), aspect = bbox[1] / bbox[0], r = 0.5 * bbox[0], From 504c92bba9c1eb4a0260a3f0abfcae6d3e9dc4d3 Mon Sep 17 00:00:00 2001 From: Alex Matulich Date: Thu, 5 Dec 2024 16:59:58 -0800 Subject: [PATCH 04/23] Corrected anchor positions, improved linear response of squareness, added example --- shapes2d.scad | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/shapes2d.scad b/shapes2d.scad index 3de6bcb4..9a9f5a8a 100644 --- a/shapes2d.scad +++ b/shapes2d.scad @@ -2024,11 +2024,11 @@ module squircle(squareness=0.7, size=[10,10], anchor=CENTER, spin=0) { anchors = [ for (i = [0:1:3]) let( ca = 360 - i*90, - cp = polar_to_xy(squircle_radius(squareness, bbox[0], ca), ca) + cp = polar_to_xy(squircle_radius(squareness, bbox[0]/2, ca), ca) ) named_anchor(str("side",i), cp, unit(cp,BACK), 0), for (i = [0:1:3]) let( ca = 360-45 - i*90, - cp = polar_to_xy(squircle_radius(squareness, bbox[0], ca), ca) + cp = polar_to_xy(squircle_radius(squareness, bbox[0]/2, ca), ca) ) named_anchor(str("corner",i), cp, unit(cp,BACK), 0) ]; attachable(anchor,spin, two_d=true, path=path, extent=false, anchors=anchors) { From 6c92e0313ad4b1afc4a9bafe33a3dcb2a79b922c Mon Sep 17 00:00:00 2001 From: Alex Matulich Date: Fri, 6 Dec 2024 08:28:03 -0800 Subject: [PATCH 05/23] Final fix for exact squareness linearity in squircle() --- shapes2d.scad | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/shapes2d.scad b/shapes2d.scad index 9a9f5a8a..11a5a300 100644 --- a/shapes2d.scad +++ b/shapes2d.scad @@ -1929,7 +1929,7 @@ function _superformula(theta,m1,m2,n1,n2=1,n3=1,a=1,b=1) = // Usage: As Function // path = reuleaux_polygon(n, r|d=, ...); // Description: -// 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 module, reates 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. // Arguments: // n = Number of "sides" to the Reuleaux Polygon. Must be an odd positive number. Default: 3 @@ -2021,14 +2021,14 @@ module squircle(squareness=0.7, size=[10,10], anchor=CENTER, spin=0) { bbox = is_num(size) ? [size,size] : point2d(size); assert(all_positive(bbox), "All components of size must be positive."); path = squircle(squareness, size); - anchors = [ + anchors = let(sq = _linearize_squareness(squareness)) [ for (i = [0:1:3]) let( ca = 360 - i*90, - cp = polar_to_xy(squircle_radius(squareness, bbox[0]/2, ca), ca) + cp = polar_to_xy(squircle_radius(sq, bbox[0]/2, ca), ca) ) named_anchor(str("side",i), cp, unit(cp,BACK), 0), for (i = [0:1:3]) let( ca = 360-45 - i*90, - cp = polar_to_xy(squircle_radius(squareness, bbox[0]/2, ca), ca) + cp = polar_to_xy(squircle_radius(sq, bbox[0]/2, ca), ca) ) named_anchor(str("corner",i), cp, unit(cp,BACK), 0) ]; attachable(anchor,spin, two_d=true, path=path, extent=false, anchors=anchors) { @@ -2041,8 +2041,7 @@ module squircle(squareness=0.7, size=[10,10], anchor=CENTER, spin=0) { function squircle(squareness=0.7, size=[10,10]) = assert(squareness >= 0 && squareness <= 1) [ let( - sqlim = max(0, min(1, squareness)), - sq = sqrt(sqlim*(2-sqlim)), // somewhat linearize squareness response + sq = _linearize_squareness(squareness), bbox = is_num(size) ? [size,size] : point2d(size), aspect = bbox[1] / bbox[0], r = 0.5 * bbox[0], @@ -2058,6 +2057,13 @@ function squircle_radius(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/vc/arxiv/papers/1604/1604.02174v1.pdf + let(c = 2 - 2*sqrt(2), d = 1 - 0.5*c*s) + 2 * sqrt((1+c)*s*s - c*s) / (d*d); + // Section: Text From a2c30affeab03f23083d10ea1c1266ac8a267d87 Mon Sep 17 00:00:00 2001 From: Alex Matulich Date: Fri, 6 Dec 2024 09:07:44 -0800 Subject: [PATCH 06/23] Corrected link in comment about linearizing squareness in squircle() --- shapes2d.scad | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shapes2d.scad b/shapes2d.scad index 11a5a300..865a14d1 100644 --- a/shapes2d.scad +++ b/shapes2d.scad @@ -2060,7 +2060,7 @@ function squircle_radius(squareness, r, angle) = let( function _linearize_squareness(s) = // from Chamberlain Fong (2016). "Squircular Calculations". arXiv. - // https://arxiv.org/vc/arxiv/papers/1604/1604.02174v1.pdf + // 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); From c18c9553765484110047b3a37738766977b85ab9 Mon Sep 17 00:00:00 2001 From: Alex Matulich Date: Mon, 9 Dec 2024 11:59:46 -0800 Subject: [PATCH 07/23] Revised to manage attachments --- shapes2d.scad | 156 +++++++++++++++++++++++++++++++++++++------------- 1 file changed, 115 insertions(+), 41 deletions(-) diff --git a/shapes2d.scad b/shapes2d.scad index 865a14d1..e9c52851 100644 --- a/shapes2d.scad +++ b/shapes2d.scad @@ -1994,70 +1994,112 @@ function reuleaux_polygon(n=3, r, d, anchor=CENTER, spin=0) = // Topics: Shapes (2D), Paths (2D), Path Generators, Attachable // See Also: circle(), square(), supershape() // Usage: As Module -// squircle(squareness, size) [ATTACHMENTS]; +// squircle(squareness, size, [style]) [ATTACHMENTS]; // Usage: As Function -// path = squircle(squareness, size); +// path = squircle(squareness, size, [style]); // Description: -// A squircle is a shape intermediate between a square/rectangle and a circle/ellipse. A squircle is a special case of supershape (shown in `supershape()` example 3), but this implementation uses a different method that requires just two parameters, and the vertex distribution is optimized for smoothness. -// 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 squircles. -// When called as a module, creates a 2D squircle with the desired squareness. Uses "intersect" type anchoring. +// 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 to adjust the shape. Another, called 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. +// 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: -// squareness = Value between 0 and 1. Controls the shape of the squircle. When `squareness=0` the shape is a circle, and when `squareness=1` the shape is a square. Default: 0.7 -// size = Bounding box of the squircle, same as the `size` parameter in `square()`, can be a single number or an `[xsize,ysize]` vector. Default: [10,10] -// $fn = Number of points. 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 non-uniformly around the squircle so they are more dense sharper curves. Default if not set: 40 +// squareness = Value between 0 and 1. Controls the shape of the squircle. When `squareness=0` the shape is a circle, and when `squareness=1` the shape is a square. Otherwise, this parameter sets the location of a squircle "corner" at the specified interpolated position between a circle and 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 +// size = Same as the `size` parameter in `square()`, can be a single number or an `[xsize,ysize]` vector. Default: [1,1] +// 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 non-uniformly around the squircle so they are more dense at sharper curves. Default if not set: 40 // Examples(2D): // squircle(squareness=0.4, size=50); // squircle(0.8, [80,60], $fn=64); -// Examples(2D): Ten increments of squareness parameter +// Examples(2D): Ten increments of squareness parameter for a superellipse squircle // for(sq=[0:0.1:1]) -// stroke(squircle(sq, 100, $fn=128), closed=true, width=0.5); -// Examples(2D): Standard vector anchors are based on extents -// squircle(0.8, 50) show_anchors(custom=false); -// Examples(2D): Named anchors exist for the sides and corners -// squircle(0.8, 50) show_anchors(std=false); -module squircle(squareness=0.7, size=[10,10], anchor=CENTER, spin=0) { +// stroke(squircle(sq, 100, style="superellipse", $fn=128), closed=true, width=0.5); +// Examples(2D): Standard vector anchors are based on the bounding box +// squircle(0.6, 50) show_anchors(); +// Examples(2D): Perimeter anchors, anchoring at bottom left and spinning 20° +// squircle(0.5, [60,40], anchor=(BOTTOM+LEFT), atype="perim", spin=20) +// show_anchors(); + +module squircle(squareness=0.5, size=[1,1], style="fg", atype="box", anchor=CENTER, spin=0) { check = assert(squareness >= 0 && squareness <= 1); - bbox = is_num(size) ? [size,size] : point2d(size); - assert(all_positive(bbox), "All components of size must be positive."); - path = squircle(squareness, size); - anchors = let(sq = _linearize_squareness(squareness)) [ - for (i = [0:1:3]) let( - ca = 360 - i*90, - cp = polar_to_xy(squircle_radius(sq, bbox[0]/2, ca), ca) - ) named_anchor(str("side",i), cp, unit(cp,BACK), 0), - for (i = [0:1:3]) let( - ca = 360-45 - i*90, - cp = polar_to_xy(squircle_radius(sq, bbox[0]/2, ca), ca) - ) named_anchor(str("corner",i), cp, unit(cp,BACK), 0) - ]; - attachable(anchor,spin, two_d=true, path=path, extent=false, anchors=anchors) { - polygon(path); - children(); + 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."); + if (atype == "box") { + path = squircle(squareness, size, style); + attachable(anchor, spin, two_d=true, size=size) { + polygon(path); + children(); + } + } else { // atype=="perim" + override_path = squircle(squareness, size, style, atype, _return_override=true); + attachable(anchor, spin, two_d=true, size=size, extent=false, override=override_path[1]) { + polygon(override_path[0]); + children(); + } } } -function squircle(squareness=0.7, size=[10,10]) = - assert(squareness >= 0 && squareness <= 1) [ +function squircle(squareness=0.5, size=[1,1], style="fg", atype="box", anchor=CENTER, spin=0, _return_override=false) = + assert(squareness >= 0 && squareness <= 1) + assert(is_num(size) || is_vector(size,2)) + assert(in_list(atype, ["box", "perim"])) + let( + path = + style == "fg" ? _squircle_fg(squareness, size) + : style == "superellipse" ? _squircle_se(squareness, size) + : assert(false, "Style must be \"fg\" or \"superellipse\""), + size = is_num(size) ? [size,size] : point2d(size), + a = 0.5 * size[0], + b = 0.5 * size[1], + override = atype == "box" ? undef + : let( + sn = style=="fg" ? _linearize_squareness(squareness) + : _squircle_se_exponent(squareness), + derivq1 = style=="fg" ? // 1+derivative of squircle in first quadrant + function (x) let(s2=sn*sn, a2=a*a, b2=b*b, x2=x*x, denom=a2-s2*x2) a2*b*(s2-1)*x/(denom*denom*sqrt((a2-x2)/denom)) + 1 + : function (x) let(n=sn) 1 - (b/a)*((a/x)^n - 1)^(1/n-1), + xc = root_find(derivq1, 0.01, a-0.01), // find where slope=-1 + yc = style=="fg" ? + let(s2=sn*sn, a2=a*a, b2=b*b, x2=xc*xc) sqrt(b2*(a2-x2)/(a2-s2*x2)) + : b*(1-(xc/a)^sn)^(1/sn), + corners = [[xc,yc], [-xc,yc], [-xc,-yc], [xc,-yc]], + anchorpos = [[1,1],[-1,1],[-1,-1],[1,-1]] + ) [ for(i=[0:3]) [anchorpos[i], [corners[i]]] ] + ) _return_override + ? [reorient(anchor, spin, two_d=true, size=size, p=path, extent=false, override=override), override] + : reorient(anchor, spin, two_d=true, size=size, p=path, extent=false, override=override); + +function _squircle_anchor_radius(squareness, angle, style) = + style == "fg" + ? let(sq = _linearize_squareness(squareness)) + squircle_radius_fg(sq, 1, angle) + : let(n = _squircle_se_exponent(squareness)) + squircle_radius_se(n, 1, angle); + //_superformula(theta=angle, m1=4,m2=4,n1=n,n2=n,n3=n,a=1,b=1); + + +/* FG squircle functions */ + +function _squircle_fg(squareness, size) = [ let( sq = _linearize_squareness(squareness), - bbox = is_num(size) ? [size,size] : point2d(size), - aspect = bbox[1] / bbox[0], - r = 0.5 * bbox[0], + 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) : 9 ) for(a=[360:-astep:0.01]) let( theta = a + sq * sin(4*a) * 30/PI, // tighter angle steps at corners - p = squircle_radius(sq, r, theta) - ) [p*cos(theta), aspect*p*sin(theta)] + p = squircle_radius_fg(sq, r, theta) + ) p*[cos(theta), aspect*sin(theta)] ]; - -function squircle_radius(squareness, r, angle) = let( +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 @@ -2065,6 +2107,38 @@ function _linearize_squareness(s) = 2 * sqrt((1+c)*s*s - c*s) / (d*d); +/* Superellipse squircle functions */ + +function _squircle_se(squareness, size) = [ + 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) : 9, + 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 From 039485e913d19e40bfdf77cc465fef6e46688fd4 Mon Sep 17 00:00:00 2001 From: Alex Matulich Date: Mon, 9 Dec 2024 12:21:53 -0800 Subject: [PATCH 08/23] Added paragraph breaks in coments for squircle --- shapes2d.scad | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/shapes2d.scad b/shapes2d.scad index e9c52851..0af11333 100644 --- a/shapes2d.scad +++ b/shapes2d.scad @@ -1999,9 +1999,12 @@ function reuleaux_polygon(n=3, r, d, anchor=CENTER, spin=0) = // path = squircle(squareness, size, [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 to adjust the shape. Another, called 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. +// . // 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 module, creates a 2D squircle with the desired squareness. // When called as a function, returns a 2D path for a squircle. // Arguments: // squareness = Value between 0 and 1. Controls the shape of the squircle. When `squareness=0` the shape is a circle, and when `squareness=1` the shape is a square. Otherwise, this parameter sets the location of a squircle "corner" at the specified interpolated position between a circle and 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 From 835cbc0f000a8eea9835b07ed02912590b3cc3d7 Mon Sep 17 00:00:00 2001 From: Alex Matulich Date: Mon, 9 Dec 2024 12:29:03 -0800 Subject: [PATCH 09/23] Removed _squircle_anchor_radius() function no longer needed --- shapes2d.scad | 8 -------- 1 file changed, 8 deletions(-) diff --git a/shapes2d.scad b/shapes2d.scad index 0af11333..8688c2c6 100644 --- a/shapes2d.scad +++ b/shapes2d.scad @@ -2075,14 +2075,6 @@ function squircle(squareness=0.5, size=[1,1], style="fg", atype="box", anchor=CE ? [reorient(anchor, spin, two_d=true, size=size, p=path, extent=false, override=override), override] : reorient(anchor, spin, two_d=true, size=size, p=path, extent=false, override=override); -function _squircle_anchor_radius(squareness, angle, style) = - style == "fg" - ? let(sq = _linearize_squareness(squareness)) - squircle_radius_fg(sq, 1, angle) - : let(n = _squircle_se_exponent(squareness)) - squircle_radius_se(n, 1, angle); - //_superformula(theta=angle, m1=4,m2=4,n1=n,n2=n,n3=n,a=1,b=1); - /* FG squircle functions */ From cba3391131358c114d80001fdf0bbeef96222298 Mon Sep 17 00:00:00 2001 From: Alex Matulich Date: Mon, 9 Dec 2024 20:40:42 -0800 Subject: [PATCH 10/23] Replaced override anchoring with builtin perimeter anchoring, minor fixes, documentation cleanup --- shapes2d.scad | 72 +++++++++++++++++++-------------------------------- 1 file changed, 26 insertions(+), 46 deletions(-) diff --git a/shapes2d.scad b/shapes2d.scad index 8688c2c6..11fb6e60 100644 --- a/shapes2d.scad +++ b/shapes2d.scad @@ -1994,97 +1994,77 @@ function reuleaux_polygon(n=3, r, d, anchor=CENTER, spin=0) = // Topics: Shapes (2D), Paths (2D), Path Generators, Attachable // See Also: circle(), square(), supershape() // Usage: As Module -// squircle(squareness, size, [style]) [ATTACHMENTS]; +// squircle(size, [squareness], [style]) [ATTACHMENTS]; // Usage: As Function -// path = squircle(squareness, size, [style]); +// 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 to adjust the shape. Another, called 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. +// 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: -// squareness = Value between 0 and 1. Controls the shape of the squircle. When `squareness=0` the shape is a circle, and when `squareness=1` the shape is a square. Otherwise, this parameter sets the location of a squircle "corner" at the specified interpolated position between a circle and 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 -// size = Same as the `size` parameter in `square()`, can be a single number or an `[xsize,ysize]` vector. Default: [1,1] +// 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 non-uniformly around the squircle so they are more dense at sharper curves. Default if not set: 40 +// $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(squareness=0.4, size=50); -// squircle(0.8, [80,60], $fn=64); +// squircle(size=50, squareness=0.4); +// squircle([80,60], 0.7, $fn=64); // Examples(2D): Ten increments of squareness parameter for a superellipse squircle // for(sq=[0:0.1:1]) -// stroke(squircle(sq, 100, style="superellipse", $fn=128), closed=true, width=0.5); +// stroke(squircle(100, sq, style="superellipse", $fn=128), closed=true, width=0.5); // Examples(2D): Standard vector anchors are based on the bounding box -// squircle(0.6, 50) show_anchors(); +// squircle(50, 0.6) show_anchors(); // Examples(2D): Perimeter anchors, anchoring at bottom left and spinning 20° -// squircle(0.5, [60,40], anchor=(BOTTOM+LEFT), atype="perim", spin=20) +// squircle([60,40], 0.5, anchor=(BOTTOM+LEFT), atype="perim", spin=20) // show_anchors(); -module squircle(squareness=0.5, size=[1,1], style="fg", atype="box", anchor=CENTER, spin=0) { +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, _module_call=true); if (atype == "box") { - path = squircle(squareness, size, style); - attachable(anchor, spin, two_d=true, size=size) { + attachable(anchor, spin, two_d=true, size=size, extent=false) { polygon(path); children(); } } else { // atype=="perim" - override_path = squircle(squareness, size, style, atype, _return_override=true); - attachable(anchor, spin, two_d=true, size=size, extent=false, override=override_path[1]) { - polygon(override_path[0]); + attachable(anchor, spin, two_d=true, extent=true, path=path) { + polygon(path); children(); } } } -function squircle(squareness=0.5, size=[1,1], style="fg", atype="box", anchor=CENTER, spin=0, _return_override=false) = +function squircle(size, squareness=0.5, style="fg", atype="box", anchor=CENTER, spin=0, _module_call=false) = assert(squareness >= 0 && squareness <= 1) - assert(is_num(size) || is_vector(size,2)) + assert(is_num(size) || is_vector(size,2)) assert(in_list(atype, ["box", "perim"])) let( - path = - style == "fg" ? _squircle_fg(squareness, size) - : style == "superellipse" ? _squircle_se(squareness, size) - : assert(false, "Style must be \"fg\" or \"superellipse\""), size = is_num(size) ? [size,size] : point2d(size), - a = 0.5 * size[0], - b = 0.5 * size[1], - override = atype == "box" ? undef - : let( - sn = style=="fg" ? _linearize_squareness(squareness) - : _squircle_se_exponent(squareness), - derivq1 = style=="fg" ? // 1+derivative of squircle in first quadrant - function (x) let(s2=sn*sn, a2=a*a, b2=b*b, x2=x*x, denom=a2-s2*x2) a2*b*(s2-1)*x/(denom*denom*sqrt((a2-x2)/denom)) + 1 - : function (x) let(n=sn) 1 - (b/a)*((a/x)^n - 1)^(1/n-1), - xc = root_find(derivq1, 0.01, a-0.01), // find where slope=-1 - yc = style=="fg" ? - let(s2=sn*sn, a2=a*a, b2=b*b, x2=xc*xc) sqrt(b2*(a2-x2)/(a2-s2*x2)) - : b*(1-(xc/a)^sn)^(1/sn), - corners = [[xc,yc], [-xc,yc], [-xc,-yc], [xc,-yc]], - anchorpos = [[1,1],[-1,1],[-1,-1],[1,-1]] - ) [ for(i=[0:3]) [anchorpos[i], [corners[i]]] ] - ) _return_override - ? [reorient(anchor, spin, two_d=true, size=size, p=path, extent=false, override=override), override] - : reorient(anchor, spin, two_d=true, size=size, p=path, extent=false, override=override); + 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=_module_call?undef:path, p=path, extent=true); /* FG squircle functions */ -function _squircle_fg(squareness, size) = [ +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) : 9 + 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) @@ -2104,13 +2084,13 @@ function _linearize_squareness(s) = /* Superellipse squircle functions */ -function _squircle_se(squareness, size) = [ +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) : 9, + 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 From ba73f4e1fd5af958ae9d760b200f1120fa12859c Mon Sep 17 00:00:00 2001 From: Alex Matulich Date: Tue, 10 Dec 2024 08:57:40 -0800 Subject: [PATCH 11/23] Fixed 2021.01 syntax error, removed _module_call flag from function parameter, fixed spelling error in Reuleax documentation --- shapes2d.scad | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/shapes2d.scad b/shapes2d.scad index 11fb6e60..19770704 100644 --- a/shapes2d.scad +++ b/shapes2d.scad @@ -1929,7 +1929,7 @@ function _superformula(theta,m1,m2,n1,n2=1,n3=1,a=1,b=1) = // Usage: As Function // path = reuleaux_polygon(n, r|d=, ...); // 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. // Arguments: // n = Number of "sides" to the Reuleaux Polygon. Must be an odd positive number. Default: 3 @@ -2029,7 +2029,7 @@ module squircle(size, squareness=0.5, style="fg", atype="box", anchor=CENTER, sp 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, _module_call=true); + path = squircle(size, squareness, style, atype="box"); if (atype == "box") { attachable(anchor, spin, two_d=true, size=size, extent=false) { polygon(path); @@ -2044,7 +2044,7 @@ module squircle(size, squareness=0.5, style="fg", atype="box", anchor=CENTER, sp } -function squircle(size, squareness=0.5, style="fg", atype="box", anchor=CENTER, spin=0, _module_call=false) = +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"])) @@ -2052,8 +2052,8 @@ function squircle(size, squareness=0.5, style="fg", atype="box", anchor=CENTER, 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=_module_call?undef:path, p=path, extent=true); + : 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 */ From df720b99de9defdef29f94181ffdd1a838a69692 Mon Sep 17 00:00:00 2001 From: Alex Matulich Date: Tue, 10 Dec 2024 09:13:00 -0800 Subject: [PATCH 12/23] Removed another trailing comma in let() --- shapes2d.scad | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shapes2d.scad b/shapes2d.scad index 19770704..a5a647fe 100644 --- a/shapes2d.scad +++ b/shapes2d.scad @@ -2096,7 +2096,7 @@ function _squircle_se(size, squareness) = [ 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 = (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 ]; From 50112abbe9239009d890d47a84e70f9cd13e312d Mon Sep 17 00:00:00 2001 From: Alex Matulich Date: Tue, 10 Dec 2024 11:49:42 -0800 Subject: [PATCH 13/23] Corrected Example headers in squircle --- shapes2d.scad | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/shapes2d.scad b/shapes2d.scad index a5a647fe..4b5c0361 100644 --- a/shapes2d.scad +++ b/shapes2d.scad @@ -2015,12 +2015,12 @@ function reuleaux_polygon(n=3, r, d, anchor=CENTER, spin=0) = // Examples(2D): // squircle(size=50, squareness=0.4); // squircle([80,60], 0.7, $fn=64); -// Examples(2D): Ten increments of squareness parameter for a superellipse squircle +// 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); -// Examples(2D): Standard vector anchors are based on the bounding box +// Example(2D): Standard vector anchors are based on the bounding box // squircle(50, 0.6) show_anchors(); -// Examples(2D): Perimeter anchors, anchoring at bottom left and spinning 20° +// 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(); From 3c47a8253e4b65d473bca1042d28f7b3dcd38550 Mon Sep 17 00:00:00 2001 From: Alex Matulich Date: Tue, 10 Dec 2024 14:45:11 -0800 Subject: [PATCH 14/23] Fixed link to supershape() in squircle() documentation --- shapes2d.scad | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shapes2d.scad b/shapes2d.scad index 4b5c0361..b7978296 100644 --- a/shapes2d.scad +++ b/shapes2d.scad @@ -2000,7 +2000,7 @@ function reuleaux_polygon(n=3, r, d, anchor=CENTER, spin=0) = // 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. +// 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`. // . From 564db53c519fcd0e605dbe3e1dc6a0b110e0026f Mon Sep 17 00:00:00 2001 From: Alex Matulich Date: Tue, 10 Dec 2024 23:36:03 -0800 Subject: [PATCH 15/23] Replaced vnf_tri_array() with additional examples, added example to vnf_from_polygons() --- vnf.scad | 242 +++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 183 insertions(+), 59 deletions(-) diff --git a/vnf.scad b/vnf.scad index c94b892d..76669a7b 100644 --- a/vnf.scad +++ b/vnf.scad @@ -237,21 +237,27 @@ function vnf_vertex_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 // 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: -// vnf = vnf_tri_array(points, [row_wrap], [reverse]) +// vnf = vnf_tri_array(points, [row_wrap], [reverse], [col_wrap], [caps], [cap1], [cap2]) // Description: -// Produces a VNF from an array of points where each row length can differ from the adjacent rows by up to 2 in length. This enables -// the construction of triangular VNF patches. The resulting VNF can be wrapped along the rows by setting `row_wrap` to true. -// You cannot wrap columns: if you need to do that you'll need to 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. +// 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. +// . +// 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. These rows must differ by at most 2 in length. -// reverse = Set this to reverse the direction of the faces +// 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`. // 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); @@ -276,61 +282,164 @@ function vnf_vertex_array( // vnf_tri_array(pts2)]); // color("green")vnf_wireframe(vnf,width=0.1); // vnf_polyhedron(vnf); -// Example(3D,NoAxes): Point count can change irregularly -// lens = [10,9,7,5,6,8,8,10]; +// Example(3D,NoAxes): The number of points per row can change irregularly by any amount. +// 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])]; // vnf = vnf_tri_array(pts); // vnf_wireframe(vnf,width=0.1); // color("red")move_copies(flatten(pts)) sphere(r=.15,$fn=9); -function vnf_tri_array(points, row_wrap=false, reverse=false) = +// 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): Model of a cymbal with roughly same-size facets, using a different number of points for each concentric ring of vertices. +// include +// bez = [ +// [[0,22], [35,22], [30,0], [80,14], [102,0]], //top +// [[99,-1], [79,13], [29,-1], [34,21], [-1,21]] // bottom +// ]; +// points = [ +// for(b=bez) +// for(u=[0.01:0.04:1]) let( +// bzp = bezier_points(b,u), +// r = bzp[0], +// n = max(3, round(360 / (6/r * 180/PI))) +// ) path3d(regular_ngon(n, r=r), bzp[1]) +// ]; +// vnf = vnf_tri_array(points, reverse=true, col_wrap=true, caps=true); +// color("brown") difference() { +// 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") let( - lens = [for(row=points) len(row)], - rowstarts = [0,each cumsum(lens)], - faces = - [for(i=[0:1:len(points) - 1 - (row_wrap ? 0 : 1)]) each - let( - rowstart = rowstarts[i], - nextrow = select(rowstarts,i+1), - delta = select(lens,i+1)-lens[i] - ) - delta == 0 ? - [for(j=[0:1:lens[i]-2]) reverse ? [j+rowstart+1, j+rowstart, j+nextrow] : [j+rowstart, j+rowstart+1, j+nextrow], - for(j=[0:1:lens[i]-2]) reverse ? [j+rowstart+1, j+nextrow, j+nextrow+1] : [j+rowstart+1, j+nextrow+1, j+nextrow]] : - delta == 1 ? - [for(j=[0:1:lens[i]-2]) reverse ? [j+rowstart+1, j+rowstart, j+nextrow+1] : [j+rowstart, j+rowstart+1, j+nextrow+1], - for(j=[0:1:lens[i]-1]) reverse ? [j+rowstart, j+nextrow, j+nextrow+1] : [j+rowstart, j+nextrow+1, j+nextrow]] : - delta == -1 ? - [for(j=[0:1:lens[i]-3]) reverse ? [j+rowstart+1, j+nextrow, j+nextrow+1]: [j+rowstart+1, j+nextrow+1, j+nextrow], - for(j=[0:1:lens[i]-2]) reverse ? [j+rowstart+1, j+rowstart, j+nextrow] : [j+rowstart, j+rowstart+1, j+nextrow]] : - let(count = floor((lens[i]-1)/2)) - delta == 2 ? - [ - 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 - 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 - for(j=[0:1:count]) reverse ? [j+rowstart, j+nextrow, j+nextrow+1] : [j+rowstart, j+nextrow+1, j+nextrow], // bot triangles left - 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 - ] : - delta == -2 ? - [ - for(j=[0:1:count-2]) reverse ? [j+nextrow, j+nextrow+1, j+rowstart+1] : [j+nextrow, j+rowstart+1, j+nextrow+1], - 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], - 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], - ] : - assert(false,str("Unsupported row length difference of ",delta, " between row ",i," and ",(i+1)%len(points))) - ], - verts = flatten(points), - culled_faces= - [for(face=faces) - if (norm(verts[face[0]]-verts[face[1]])>EPSILON && - norm(verts[face[1]]-verts[face[2]])>EPSILON && - norm(verts[face[2]]-verts[face[0]])>EPSILON) - face - ] - ) - [flatten(points), culled_faces]; + 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=n1 && t2>=n2 ? trilist : + _lofttri(p1, p2, i1offset, i2offset, n1, n2, reverse, concat(trilist, [triangle]), d12=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=n ? trilist : + _lofttri_eq(p1, p2, i1offset, i2offset, n, reverse, concat(trilist, [triangle1, triangle2]), t); + +/* +function _lofttri_eq(p1, p2, i1offset, i2offset, n, reverse=false, trilist=[], i=0) = let( + 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=n ? trilist : + _lofttri_eq(p1, p2, i1offset, i2offset, n, reverse, concat(trilist, [triangle1, triangle2]), t); +*/ // Function: vnf_join() // Synopsis: Returns a single VNF structure from a list of VNF structures. @@ -436,7 +545,22 @@ function vnf_join(vnfs) = // Arguments: // 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 -// 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) = assert(is_list(polygons) && is_path(polygons[0]),"Input should be a list of polygons") let( From 1ed79d4cac846f5b4b614cac9a039d7960e01aeb Mon Sep 17 00:00:00 2001 From: Alex Matulich Date: Wed, 11 Dec 2024 07:26:11 -0800 Subject: [PATCH 16/23] Restored critical line in vnf_tri_array that somehow got removed while cleaning up comments --- vnf.scad | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vnf.scad b/vnf.scad index 76669a7b..f61de3f9 100644 --- a/vnf.scad +++ b/vnf.scad @@ -413,7 +413,7 @@ function _lofttri(p1, p2, i1offset, i2offset, n1, n2, reverse=false, trilist=[], _lofttri(p1, p2, i1offset, i2offset, n1, n2, reverse, concat(trilist, [triangle]), d12=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 From 2a2062231af5504d6e5c237f2c914f85cc2ff019 Mon Sep 17 00:00:00 2001 From: Alex Matulich Date: Wed, 11 Dec 2024 07:43:33 -0800 Subject: [PATCH 17/23] Fixed another issue from inadvertent undo operation, retested --- vnf.scad | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/vnf.scad b/vnf.scad index f61de3f9..e937b1ed 100644 --- a/vnf.scad +++ b/vnf.scad @@ -318,8 +318,8 @@ function vnf_vertex_array( // Example(3D,NoAxes,Edges): Model of a cymbal with roughly same-size facets, using a different number of points for each concentric ring of vertices. // include // bez = [ -// [[0,22], [35,22], [30,0], [80,14], [102,0]], //top -// [[99,-1], [79,13], [29,-1], [34,21], [-1,21]] // bottom +// [[0,24], [35,24], [30,0], [80,15], [102,0]], //top +// [[99,-1], [79,14], [29,-1], [34,23], [-1,23]] // bottom // ]; // points = [ // for(b=bez) @@ -413,7 +413,7 @@ function _lofttri(p1, p2, i1offset, i2offset, n1, n2, reverse=false, trilist=[], _lofttri(p1, p2, i1offset, i2offset, n1, n2, reverse, concat(trilist, [triangle]), d12=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 @@ -424,7 +424,7 @@ function _lofttri(p1, p2, i1offset, i2offset, n1, n2, reverse=false, trilist=[], [i2offset+t, i1offset+t, d12=n ? trilist : - _lofttri_eq(p1, p2, i1offset, i2offset, n, reverse, concat(trilist, [triangle1, triangle2]), t); + _lofttri(p1, p2, i1offset, i2offset, n, n, reverse, concat(trilist, [triangle1, triangle2]), t, t); /* function _lofttri_eq(p1, p2, i1offset, i2offset, n, reverse=false, trilist=[], i=0) = let( From fe98042255acd96a82b14329e13c61eb73396ff3 Mon Sep 17 00:00:00 2001 From: Alex Matulich Date: Wed, 11 Dec 2024 09:28:25 -0800 Subject: [PATCH 18/23] Changed height and view angle of cymbal model, removed redundant comment block --- vnf.scad | 20 +++----------------- 1 file changed, 3 insertions(+), 17 deletions(-) diff --git a/vnf.scad b/vnf.scad index e937b1ed..2fc51a95 100644 --- a/vnf.scad +++ b/vnf.scad @@ -315,11 +315,11 @@ function vnf_vertex_array( // vnf_polyhedron(vnf4); // color("green") vnf_wireframe(vnf4); // } -// Example(3D,NoAxes,Edges): 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]): Model of a cymbal with roughly same-size facets, using a different number of points for each concentric ring of vertices. // include // bez = [ -// [[0,24], [35,24], [30,0], [80,15], [102,0]], //top -// [[99,-1], [79,14], [29,-1], [34,23], [-1,23]] // bottom +// [[0,26], [35,26], [29,0], [80,16], [102,0]], //top +// [[99,-1], [79,15], [28,-1], [34,25], [-1,25]] // bottom // ]; // points = [ // for(b=bez) @@ -426,20 +426,6 @@ function _lofttri(p1, p2, i1offset, i2offset, n1, n2, reverse=false, trilist=[], ) t>=n ? trilist : _lofttri(p1, p2, i1offset, i2offset, n, n, reverse, concat(trilist, [triangle1, triangle2]), t, t); -/* -function _lofttri_eq(p1, p2, i1offset, i2offset, n, reverse=false, trilist=[], i=0) = let( - 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=n ? trilist : - _lofttri_eq(p1, p2, i1offset, i2offset, n, reverse, concat(trilist, [triangle1, triangle2]), t); -*/ // Function: vnf_join() // Synopsis: Returns a single VNF structure from a list of VNF structures. From 9ed7a3ec48c0c66076b0f6066991076c944f6c8b Mon Sep 17 00:00:00 2001 From: Adrian Mariano Date: Wed, 11 Dec 2024 16:20:58 -0500 Subject: [PATCH 19/23] add highlight and highlight_this --- attachments.scad | 42 ++++++++++++++++++---------- color.scad | 73 +++++++++++++++++++++++++++++++++++++++++------- math.scad | 8 ++++++ 3 files changed, 99 insertions(+), 24 deletions(-) diff --git a/attachments.scad b/attachments.scad index 6c4173fa..a31c8f3e 100644 --- a/attachments.scad +++ b/attachments.scad @@ -43,6 +43,9 @@ $ghost_this=false; $ghost=false; $ghosting=false; // Ghosting is in effect, so don't apply it again +$highlight_this=false; +$highlight=false; + _ANCHOR_TYPES = ["intersect","hull"]; @@ -1154,8 +1157,8 @@ module tag_this(tag) // that don't use {{attachable()}} or built in modules such as // - `polygon()` // - `projection()` -// - `polyhedron()` (or use [`vnf_polyhedron()`](vnf.scad#vnf_polyhedron)) -// - `linear_extrude()` (or use [`linear_sweep()`](regions.scad#linear_sweep)) +// - `polyhedron()` (or use {{vnf_polyhedron()}}) +// - `linear_extrude()` (or use {{linear_sweep()}}) // - `rotate_extrude()` // - `surface()` // - `import()` @@ -3107,24 +3110,15 @@ module attachable( if (expose_tags || _is_shown()){ if (!keep_color) _color($color) - if (($ghost || $ghost_this) && !$ghosting) - %union(){ - $ghosting=true; - children(0); - } - else children(0); + _show_ghost() children(0); else { $save_color=undef; // Force color_this() color in effect to persist for the entire object - if (($ghost || $ghost_this) && !$ghosting) - %union(){ - $ghosting=true; - children(0); - } - else children(0); + _show_ghost() children(0); } } let( $ghost_this=false, + $highlight_this=false, $tag=default($save_tag,$tag), $save_tag=undef, $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() // Synopsis: Calculates the transformation matrix needed to reorient an object. // SynTags: Trans, Path, VNF diff --git a/color.scad b/color.scad index d601e6f6..4195c1bf 100644 --- a/color.scad +++ b/color.scad @@ -161,21 +161,74 @@ module color_overlaps(color="red") { %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() -// Synopsis: Sets transparency for attachable children and their descendents. +// Synopsis: Sets % modifier for attachable children and their descendents. // SynTags: Trans -// Topics: Attachments +// Topics: Attachments, Modifiers // See Also: ghost_this(), recolor(), color_this() // Usage: // ghost([ghost]) CHILDREN; // Description: -// Sets the transparency 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. -// Do not mix this with user supplied `%` operators anywhere in the geometry tree. +// Sets the `%` modifier for the attachable children and their descendents until another {{ghost()}} or {{ghost_this()}}. +// By default, turns `%` on, which makes the children gray and also removes them from interaction with 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: -// 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): // ghost() cuboid(10) // ghost(false) cuboid(5); @@ -188,14 +241,14 @@ module ghost(ghost=true) // Module: ghost_this() -// Synopsis: Makes the children at a single level transparent. +// Synopsis: Apply % modifier to children at a single level. // SynTags: Trans -// Topics: Attachments +// Topics: Attachments, Modifiers // See Also: ghost(), recolor(), color_this() // Usage: // ghost_this() CHILDREN; // 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. // Example(3D): // ghost_this() cuboid(10) diff --git a/math.scad b/math.scad index 0058d02d..272738aa 100644 --- a/math.scad +++ b/math.scad @@ -1609,6 +1609,14 @@ function real_roots(p,eps=undef,tol=1e-14) = // x0 = endpoint of interval to search for root // x1 = second endpoint of interval to search for root // 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 // bisection and inverse quadratic approximation, where bisection occurs From 17b670f41288937370071f4b37fbfc944b791540 Mon Sep 17 00:00:00 2001 From: Alex Matulich Date: Thu, 12 Dec 2024 10:20:17 -0800 Subject: [PATCH 20/23] Added rounding parameter as discussed in #1476 --- ball_bearings.scad | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/ball_bearings.scad b/ball_bearings.scad index 38018009..7e1c2d2d 100644 --- a/ball_bearings.scad +++ b/ball_bearings.scad @@ -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(); } } From 93d0ed07cde7cf167a7d4ac8854e29438daf2efa Mon Sep 17 00:00:00 2001 From: Alex Matulich Date: Thu, 12 Dec 2024 11:58:12 -0800 Subject: [PATCH 21/23] Moved squircle up before keyhole, copyedited documentation, changed view distance and color of one example --- shapes2d.scad | 257 +++++++++++++++++++++++++------------------------- 1 file changed, 130 insertions(+), 127 deletions(-) diff --git a/shapes2d.scad b/shapes2d.scad index b7978296..364d33aa 100644 --- a/shapes2d.scad +++ b/shapes2d.scad @@ -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() From 7621838c06b26f436f3c68f9a7d416b9dccf8de6 Mon Sep 17 00:00:00 2001 From: Alex Matulich Date: Thu, 12 Dec 2024 15:56:55 -0800 Subject: [PATCH 22/23] 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 --- vnf.scad | 97 +++++++++++++++++++++++++++++++------------------------- 1 file changed, 53 insertions(+), 44 deletions(-) diff --git a/vnf.scad b/vnf.scad index 2fc51a95..76fc2d1a 100644 --- a/vnf.scad +++ b/vnf.scad @@ -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 // 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 From 00d2cd77e031f018e1b9be1227e483f4d6d71037 Mon Sep 17 00:00:00 2001 From: Alex Matulich Date: Thu, 12 Dec 2024 19:35:37 -0800 Subject: [PATCH 23/23] Fixed typo in new example for vnf_vertex_array --- vnf.scad | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vnf.scad b/vnf.scad index 76fc2d1a..441da0b2 100644 --- a/vnf.scad +++ b/vnf.scad @@ -98,7 +98,7 @@ EMPTY_VNF = [[],[]]; // The standard empty VNF with no vertices or faces. // 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 = 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.