Skip to content

Commit 76dedf0

Browse files
authored
Merge pull request #1517 from amatulic/anachronist_dev
added squircle to shapes2d.scad
2 parents f5ac904 + 3c47a82 commit 76dedf0

File tree

1 file changed

+128
-1
lines changed

1 file changed

+128
-1
lines changed

shapes2d.scad

Lines changed: 128 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1929,7 +1929,7 @@ function _superformula(theta,m1,m2,n1,n2=1,n3=1,a=1,b=1) =
19291929
// Usage: As Function
19301930
// path = reuleaux_polygon(n, r|d=, ...);
19311931
// Description:
1932-
// When called as a module, reates a 2D Reuleaux Polygon; a constant width shape that is not circular. Uses "intersect" type anchoring.
1932+
// When called as a module, creates a 2D Reuleaux Polygon; a constant width shape that is not circular. Uses "intersect" type anchoring.
19331933
// When called as a function, returns a 2D path for a Reulaux Polygon.
19341934
// Arguments:
19351935
// n = Number of "sides" to the Reuleaux Polygon. Must be an odd positive number. Default: 3
@@ -1988,6 +1988,133 @@ function reuleaux_polygon(n=3, r, d, anchor=CENTER, spin=0) =
19881988

19891989

19901990

1991+
// Function&Module: squircle()
1992+
// Synopsis: Creates a shape between a circle and a square, centered on the origin.
1993+
// SynTags: Geom, Path
1994+
// Topics: Shapes (2D), Paths (2D), Path Generators, Attachable
1995+
// See Also: circle(), square(), supershape()
1996+
// Usage: As Module
1997+
// squircle(size, [squareness], [style]) [ATTACHMENTS];
1998+
// Usage: As Function
1999+
// path = squircle(size, [squareness], [style]);
2000+
// Description:
2001+
// 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.
2002+
// .
2003+
// 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.
2004+
// .
2005+
// 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`.
2006+
// .
2007+
// When called as a module, creates a 2D squircle with the desired squareness.
2008+
// When called as a function, returns a 2D path for a squircle.
2009+
// Arguments:
2010+
// size = Same as the `size` parameter in `square()`, can be a single number or a vector `[xsize,ysize]`.
2011+
// 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
2012+
// style = method for generating a squircle, "fg" for Fernández-Guasti and "superellipse" for superellipse. Default: "fg"
2013+
// atype = anchor type, "box" for bounding box corners and sides, "perim" for the squircle corners
2014+
// $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
2015+
// Examples(2D):
2016+
// squircle(size=50, squareness=0.4);
2017+
// squircle([80,60], 0.7, $fn=64);
2018+
// Example(2D): Ten increments of squareness parameter for a superellipse squircle
2019+
// for(sq=[0:0.1:1])
2020+
// stroke(squircle(100, sq, style="superellipse", $fn=128), closed=true, width=0.5);
2021+
// Example(2D): Standard vector anchors are based on the bounding box
2022+
// squircle(50, 0.6) show_anchors();
2023+
// Example(2D): Perimeter anchors, anchoring at bottom left and spinning 20°
2024+
// squircle([60,40], 0.5, anchor=(BOTTOM+LEFT), atype="perim", spin=20)
2025+
// show_anchors();
2026+
2027+
module squircle(size, squareness=0.5, style="fg", atype="box", anchor=CENTER, spin=0) {
2028+
check = assert(squareness >= 0 && squareness <= 1);
2029+
anchorchk = assert(in_list(atype, ["box", "perim"]));
2030+
size = is_num(size) ? [size,size] : point2d(size);
2031+
assert(all_positive(size), "All components of size must be positive.");
2032+
path = squircle(size, squareness, style, atype="box");
2033+
if (atype == "box") {
2034+
attachable(anchor, spin, two_d=true, size=size, extent=false) {
2035+
polygon(path);
2036+
children();
2037+
}
2038+
} else { // atype=="perim"
2039+
attachable(anchor, spin, two_d=true, extent=true, path=path) {
2040+
polygon(path);
2041+
children();
2042+
}
2043+
}
2044+
}
2045+
2046+
2047+
function squircle(size, squareness=0.5, style="fg", atype="box", anchor=CENTER, spin=0) =
2048+
assert(squareness >= 0 && squareness <= 1)
2049+
assert(is_num(size) || is_vector(size,2))
2050+
assert(in_list(atype, ["box", "perim"]))
2051+
let(
2052+
size = is_num(size) ? [size,size] : point2d(size),
2053+
path = style == "fg" ? _squircle_fg(size, squareness)
2054+
: style == "superellipse" ? _squircle_se(size, squareness)
2055+
: assert(false, "Style must be \"fg\" or \"superellipse\"")
2056+
) reorient(anchor, spin, two_d=true, size=atype=="box"?size:undef, path=atype=="box"?undef:path, p=path, extent=true);
2057+
2058+
2059+
/* FG squircle functions */
2060+
2061+
function _squircle_fg(size, squareness) = [
2062+
let(
2063+
sq = _linearize_squareness(squareness),
2064+
size = is_num(size) ? [size,size] : point2d(size),
2065+
aspect = size[1] / size[0],
2066+
r = 0.5 * size[0],
2067+
astep = $fn>=12 ? 90/round($fn/4) : 360/48
2068+
) for(a=[360:-astep:0.01]) let(
2069+
theta = a + sq * sin(4*a) * 30/PI, // tighter angle steps at corners
2070+
p = squircle_radius_fg(sq, r, theta)
2071+
) p*[cos(theta), aspect*sin(theta)]
2072+
];
2073+
2074+
function squircle_radius_fg(squareness, r, angle) = let(
2075+
s2a = abs(squareness*sin(2*angle))
2076+
) s2a>0 ? r*sqrt(2)/s2a * sqrt(1 - sqrt(1 - s2a*s2a)) : r;
2077+
2078+
function _linearize_squareness(s) =
2079+
// from Chamberlain Fong (2016). "Squircular Calculations". arXiv.
2080+
// https://arxiv.org/pdf/1604.02174v5
2081+
let(c = 2 - 2*sqrt(2), d = 1 - 0.5*c*s)
2082+
2 * sqrt((1+c)*s*s - c*s) / (d*d);
2083+
2084+
2085+
/* Superellipse squircle functions */
2086+
2087+
function _squircle_se(size, squareness) = [
2088+
let(
2089+
n = _squircle_se_exponent(squareness),
2090+
size = is_num(size) ? [size,size] : point2d(size),
2091+
ra = 0.5*size[0],
2092+
rb = 0.5*size[1],
2093+
astep = $fn>=12 ? 90/round($fn/4) : 360/48,
2094+
fgsq = _linearize_squareness(min(0.998,squareness)) // works well for distributing theta
2095+
) for(a=[360:-astep:0.01]) let(
2096+
theta = a + fgsq*sin(4*a)*30/PI, // tighter angle steps at corners
2097+
x = cos(theta),
2098+
y = sin(theta),
2099+
r = (abs(x)^n + abs(y)^n)^(1/n) // superellipse
2100+
//r = _superformula(theta=theta, m1=4,m2=4,n1=n,n2=n,n3=n,a=1,b=1)
2101+
) [ra*x, rb*y] / r
2102+
];
2103+
2104+
function squircle_radius_se(n, r, angle) = let(
2105+
x = cos(angle),
2106+
y = sin(angle)
2107+
) (abs(x)^n + abs(y)^n)^(1/n) / r;
2108+
2109+
function _squircle_se_exponent(squareness) = let(
2110+
// limit squareness; error if >0.99889, limit is smaller for r>1
2111+
s=min(0.998,squareness),
2112+
rho = 1 + s*(sqrt(2)-1),
2113+
x = rho / sqrt(2)
2114+
) log(0.5) / log(x);
2115+
2116+
2117+
19912118
// Section: Text
19922119

19932120
// Module: text()

0 commit comments

Comments
 (0)