, );
// Usage: Circle/Oval Geometry
-// reorient(anchor, spin, [orient], two_d, r|d, [cp], [offset], [anchors], [p]);
+// reorient(anchor, spin, , two_d, r|d, , , , );
// Usage: 2D Path/Polygon Geometry
-// reorient(anchor, spin, [orient], two_d, path, [extent], [cp], [offset], [anchors], [p]);
+// reorient(anchor, spin, , two_d, path, , , , , );
// Usage: Cubical/Prismoidal Geometry
-// reorient(anchor, spin, [orient], size, [size2], [shift], [cp], [offset], [anchors], [p]);
+// reorient(anchor, spin, , size, , , , , , );
// Usage: Cylindrical Geometry
-// reorient(anchor, spin, [orient], r|d, l, [offset], [axis], [cp], [anchors], [p]);
+// reorient(anchor, spin, , r|d, l, , , , , );
// Usage: Conical Geometry
-// reorient(anchor, spin, [orient], r1|d1, r2|d2, l, [axis], [cp], [offset], [anchors], [p]);
+// reorient(anchor, spin, , r1|d1, r2|d2, l, , , , , );
// Usage: Spheroid/Ovoid Geometry
-// reorient(anchor, spin, [orient], r|d, [cp], [offset], [anchors], [p]);
+// reorient(anchor, spin, , r|d, , , , );
// Usage: VNF Geometry
-// reorient(anchor, spin, [orient], vnf, [extent], [cp], [offset], [anchors], [p]);
+// reorient(anchor, spin, , vnf, , , , , );
//
// Description:
// Given anchor, spin, orient, and general geometry info for a managed volume, this calculates
@@ -723,21 +723,21 @@ function reorient(
// Module: attachable()
//
// Usage: Square/Trapezoid Geometry
-// attachable(anchor, spin, [orient], two_d, size, [size2], [shift], [cp], [offset], [anchors] ...
+// attachable(anchor, spin, two_d, size, , , , , ...
// Usage: Circle/Oval Geometry
-// attachable(anchor, spin, [orient], two_d, r|d, [cp], [offset], [anchors]) ...
+// attachable(anchor, spin, two_d, r|d, , , ) ...
// Usage: 2D Path/Polygon Geometry
-// attachable(anchor, spin, [orient], two_d, path, [extent], [cp], [offset], [anchors] ...
+// attachable(anchor, spin, two_d, path, , , , ...
// Usage: Cubical/Prismoidal Geometry
-// attachable(anchor, spin, [orient], size, [size2], [shift], [cp], [offset], [anchors] ...
+// attachable(anchor, spin, , size, , , , , ...
// Usage: Cylindrical Geometry
-// attachable(anchor, spin, [orient], r|d, l, [axis], [cp], [offset], [anchors]) ...
+// attachable(anchor, spin, , r|d, l, , , , ) ...
// Usage: Conical Geometry
-// attachable(anchor, spin, [orient], r1|d1, r2|d2, l, [axis], [cp], [offset], [anchors]) ...
+// attachable(anchor, spin, , r1|d1, r2|d2, l, , , , ) ...
// Usage: Spheroid/Ovoid Geometry
-// attachable(anchor, spin, [orient], r|d, [cp], [offset], [anchors]) ...
+// attachable(anchor, spin, , r|d, , , ) ...
// Usage: VNF Geometry
-// attachable(anchor, spin, [orient], vnf, [extent], [cp], [offset], [anchors]) ...
+// attachable(anchor, spin, , vnf, , , , ) ...
//
// Description:
// Manages the anchoring, spin, orientation, and attachments for a 3D volume or 2D area.
@@ -969,8 +969,8 @@ module position(from)
// Module: attach()
// Usage:
-// attach(from, [overlap]) ...
-// attach(from, to, [overlap]) ...
+// attach(from, ) ...
+// attach(from, to, ) ...
// Description:
// Attaches children to a parent object at an anchor point and orientation.
// Attached objects will be overlapped into the parent object by a little bit,
@@ -1012,7 +1012,7 @@ module attach(from, to=undef, overlap=undef, norot=false)
// Module: face_profile()
// Usage:
-// face_profile(faces=[], convexity=10, r, d) ...
+// face_profile(faces, r, d, ) ...
// Description:
// Given a 2D edge profile, extrudes it into a mask for all edges and corners bounding each given face.
// Arguments:
@@ -1032,7 +1032,7 @@ module face_profile(faces=[], r, d, convexity=10) {
// Module: edge_profile()
// Usage:
-// edge_profile([edges], [except], [convexity]) ...
+// edge_profile(, , ) ...
// Description:
// Takes a 2D mask shape and attaches it to the selected edges, with the appropriate orientation
// and extruded length to be `diff()`ed away, to give the edge a matching profile.
@@ -1082,7 +1082,7 @@ module edge_profile(edges=EDGES_ALL, except=[], convexity=10) {
// Module: corner_profile()
// Usage:
-// corner_profile([corners], [except], [convexity]) ...
+// corner_profile(, , ) ...
// Description:
// Takes a 2D mask shape, rotationally extrudes and converts it into a corner mask, and attaches it
// to the selected corners with the appropriate orientation. Tags it as a "mask" to allow it to be
@@ -1144,7 +1144,7 @@ module corner_profile(corners=CORNERS_ALL, except=[], r, d, convexity=10) {
// Module: edge_mask()
// Usage:
-// edge_mask([edges], [except]) ...
+// edge_mask(, ) ...
// Description:
// Takes a 3D mask shape, and attaches it to the given edges, with the
// appropriate orientation to be `diff()`ed away.
@@ -1186,7 +1186,7 @@ module edge_mask(edges=EDGES_ALL, except=[]) {
// Module: corner_mask()
// Usage:
-// corner_mask([corners], [except]) ...
+// corner_mask(, ) ...
// Description:
// Takes a 3D mask shape, and attaches it to the given corners, with the appropriate
// orientation to be `diff()`ed away. The 3D corner mask shape should be designed to
@@ -1305,8 +1305,8 @@ module show(tags="")
// Module: diff()
// Usage:
-// diff(neg, [keep]) ...
-// diff(neg, pos, [keep]) ...
+// diff(neg, ) ...
+// diff(neg, pos, ) ...
// Description:
// If `neg` is given, takes the union of all children with tags that are in `neg`, and differences
// them from the union of all children with tags in `pos`. If `pos` is not given, then all items in
@@ -1363,8 +1363,8 @@ module diff(neg, pos=undef, keep=undef)
// Module: intersect()
// Usage:
-// intersect(a, [keep]) ...
-// intersect(a, b, [keep]) ...
+// intersect(a, ) ...
+// intersect(a, b, ) ...
// Description:
// If `a` is given, takes the union of all children with tags that are in `a`, and `intersection()`s
// them with the union of all children with tags in `b`. If `b` is not given, then the union of all
diff --git a/debug.scad b/debug.scad
index a38a690..96e5715 100644
--- a/debug.scad
+++ b/debug.scad
@@ -274,7 +274,7 @@ function standard_anchors(two_d=false) = [
// Module: anchor_arrow()
// Usage:
-// anchor_arrow([s], [color], [flag]);
+// anchor_arrow(, , );
// Description:
// Show an anchor orientation arrow.
// Arguments:
@@ -303,7 +303,7 @@ module anchor_arrow(s=10, color=[0.333,0.333,1], flag=true, $tags="anchor-arrow"
// Module: anchor_arrow2d()
// Usage:
-// anchor_arrow2d([s], [color], [flag]);
+// anchor_arrow2d(, , );
// Description:
// Show an anchor orientation arrow.
// Arguments:
@@ -312,7 +312,7 @@ module anchor_arrow(s=10, color=[0.333,0.333,1], flag=true, $tags="anchor-arrow"
// Example:
// anchor_arrow2d(s=20);
module anchor_arrow2d(s=15, color=[0.333,0.333,1], $tags="anchor-arrow") {
- noop() stroke([[0,0],[0,s]], width=s/10, endcap1="butt", endcap2="arrow2");
+ noop() color(color) stroke([[0,0],[0,s]], width=s/10, endcap1="butt", endcap2="arrow2");
}
@@ -341,18 +341,28 @@ module show_internal_anchors(opacity=0.2) {
// Example(FlatSpin):
// cube(50, center=true) show_anchors();
module show_anchors(s=10, std=true, custom=true) {
+ check = assert($parent_geom != undef) 1;
+ two_d = attach_geom_2d($parent_geom);
if (std) {
- for (anchor=standard_anchors()) {
- attach(anchor) anchor_arrow(s);
+ for (anchor=standard_anchors(two_d=two_d)) {
+ if(two_d) {
+ attach(anchor) anchor_arrow2d(s);
+ } else {
+ attach(anchor) anchor_arrow(s);
+ }
}
}
if (custom) {
for (anchor=select($parent_geom,-1)) {
attach(anchor[0]) {
- anchor_arrow(s, color="cyan");
- recolor("black")
+ if(two_d) {
+ anchor_arrow2d(s, color="cyan");
+ } else {
+ anchor_arrow(s, color="cyan");
+ }
+ color("black")
noop($tags="anchor-arrow") {
- xrot(90) {
+ xrot(two_d? 0 : 90) {
up(s/10) {
linear_extrude(height=0.01, convexity=12, center=true) {
text(text=anchor[0], size=s/4, halign="center", valign="center");
diff --git a/scripts/make_tutorials.sh b/scripts/make_tutorials.sh
index 10ba8ae..9c0fee4 100755
--- a/scripts/make_tutorials.sh
+++ b/scripts/make_tutorials.sh
@@ -15,7 +15,7 @@ done
if [[ "$FILES" != "" ]]; then
PREVIEW_LIBS="$FILES"
else
- PREVIEW_LIBS="Transforms Distributors Basic_Shapes FractalTree"
+ PREVIEW_LIBS="Transforms Distributors Shapes2d Shapes3d Paths FractalTree"
fi
dir="$(basename $PWD)"
diff --git a/shapes.scad b/shapes.scad
index 5b8c975..31e35b1 100644
--- a/shapes.scad
+++ b/shapes.scad
@@ -1159,6 +1159,14 @@ module torus(
// Creates a spheroid object, with support for anchoring and attachments.
// This is a drop-in replacement for the built-in `sphere()` module.
// When called as a function, returns a [VNF](vnf.scad) for a spheroid.
+// The exact triangulation of this spheroid can be controlled via the `style=`
+// argument, where the value can be one of `"orig"`, `"aligned"`, `"stagger"`,
+// `"octa"`, or `"icosa"`:
+// - `style="orig"` constructs a sphere the same way that the OpenSCAD `sphere()` built-in does.
+// - `style="aligned"` constructs a sphere where, if `$fn` is a multiple of 4, it has vertices at all axis maxima and minima. ie: its bounding box is exactly the sphere diameter in length on all three axes. This is the default.
+// - `style="stagger"` forms a sphere where all faces are triangular, but the top and bottom poles have thinner triangles.
+// - `style="octa"` forms a sphere by subdividing an octahedron (8-sided platonic solid). This makes more uniform faces over the entirety of the sphere, and guarantees the bounding box is the sphere diameter in size on all axes. The effective `$fn` value is quantized to a multiple of 4, though. This is used in constructing rounded corners for various other shapes.
+// - `style="icosa"` forms a sphere by subdividing an icosahedron (20-sided platonic solid). This makes even more uniform faces over the entirety of the sphere. The effective `$fn` value is quantized to a multiple of 5, though.
// Arguments:
// r = Radius of the spheroid.
// d = Diameter of the spheroid.
@@ -1200,21 +1208,16 @@ module spheroid(r, d, circum=false, style="aligned", anchor=CENTER, spin=0, orie
{
r = get_radius(r=r, d=d, dflt=1);
sides = segs(r);
+ vsides = ceil(sides/2);
attachable(anchor,spin,orient, r=r) {
if (style=="orig") {
- rotate_extrude(convexity=2,$fn=sides) {
- difference() {
- oval(r=r, circum=circum, realign=true, $fn=sides);
- left(r) square(2*r,center=true);
- }
- }
- } else if (style=="aligned") {
- rotate_extrude(convexity=2,$fn=sides) {
- difference() {
- oval(r=r, circum=circum, $fn=sides);
- left(r) square(2*r,center=true);
- }
- }
+ merids = [ for (i=[0:1:vsides-1]) 90-(i+0.5)*180/vsides ];
+ path = [
+ let(a = merids[0]) [0, sin(a)],
+ for (a=merids) [cos(a), sin(a)],
+ let(a = select(merids,-1)) [0, sin(a)]
+ ];
+ scale(r) rotate(180) rotate_extrude(convexity=2,$fn=sides) polygon(path);
} else {
vnf = spheroid(r=r, circum=circum, style=style);
vnf_polyhedron(vnf, convexity=2);
diff --git a/shapes2d.scad b/shapes2d.scad
index b9750da..4fec9a3 100644
--- a/shapes2d.scad
+++ b/shapes2d.scad
@@ -1250,7 +1250,7 @@ module trapezoid(h, w1, w2, angle, shift=0, anchor=CENTER, spin=0) {
w2 = !is_undef(w2)? w2 : w1 - 2*(adj_ang_to_opp(h, angle) + shift);
assert(w1>=0 && w2>=0 && h>0, "Degenerate trapezoid geometry.");
path = [[w1/2,-h/2], [-w1/2,-h/2], [-w2/2+shift,h/2], [w2/2+shift,h/2]];
- attachable(anchor,spin, two_d=true, size=[w1,h], size2=w2) {
+ attachable(anchor,spin, two_d=true, size=[w1,h], size2=w2, shift=shift) {
polygon(path);
children();
}
@@ -1325,8 +1325,8 @@ function teardrop2d(r, d, ang=45, cap_h, anchor=CENTER, spin=0) =
// Arguments:
// r = The radius of the end circles.
// d = The diameter of the end circles.
-// spread = The distance between the centers of the end circles.
-// tangent = The angle in degrees of the tangent point for the joining arcs, measured away from the Y axis.
+// spread = The distance between the centers of the end circles. Default: 10
+// tangent = The angle in degrees of the tangent point for the joining arcs, measured away from the Y axis. Default: 30
// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#anchor). Default: `CENTER`
// spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#spin). Default: `0`
// Examples(2D):
diff --git a/tutorials/Paths.md b/tutorials/Paths.md
new file mode 100644
index 0000000..68963dd
--- /dev/null
+++ b/tutorials/Paths.md
@@ -0,0 +1,504 @@
+# Paths, Polygons and Regions Tutorial
+
+## Paths
+A number of advanced features in BOSL2 rely on paths, which are just ordered lists of points.
+
+First-off, some terminology:
+- A 2D point is a vectors of X and Y axis position values. ie: `[3,4]` or `[7,-3]`.
+- A 3D point is a vectors of X, Y and Z axis position values. ie: `[3,4,2]` or `[-7,5,3]`.
+- A 2D path is simply a list of two or more 2D points. ie: `[[5,7], [1,-5], [-5,6]]`
+- A 3D path is simply a list of two or more 3D points. ie: `[[5,7,-1], [1,-5,3], [-5,6,1]]`
+- A polygon is a 2D (or planar 3D) path where the last point is assumed to connect to the first point.
+- A region is a list of 2D polygons, where each polygon is XORed against all the others. ie: if one polygon is inside another, it makes a hole in the first polygon.
+
+### Stroke
+A path can be hard to visualize, since it's just a bunch of numbers in the source code.
+One way to see the path is to pass it to `polygon()`:
+
+```openscad-2D
+ path = [[0,0], [-10,10], [0,20], [10,20], [10,10]];
+ polygon(path);
+```
+
+Sometimes, however, it's easier to see just the path itself. For this, you can use the `stroke()` module.
+At its most basic, `stroke()` just shows the path's line segments:
+
+```openscad-2D
+ path = [[0,0], [-10,10], [0,20], [10,20], [10,10]];
+ stroke(path);
+```
+
+You can vary the width of the drawn path with the `width=` argument:
+
+```openscad-2D
+ path = [[0,0], [-10,10], [0,20], [10,20], [10,10]];
+ stroke(path, width=3);
+```
+
+You can vary the line length along the path by giving a list of widths, one per point:
+
+```openscad-2D
+ path = [[0,0], [-10,10], [0,20], [10,20], [10,10]];
+ stroke(path, width=[3,2,1,2,3]);
+```
+
+If a path is meant to represent a closed polygon, you can use `closed=true` to show it that way:
+
+```openscad-2D
+ path = [[0,0], [-10,10], [0,20], [10,20], [10,10]];
+ stroke(path, closed=true);
+```
+
+The ends of the drawn path are normally capped with a "round" endcap, but there are other options:
+
+```openscad-2D
+ path = [[0,0], [-10,10], [0,20], [10,20], [10,10]];
+ stroke(path, endcaps="round");
+```
+
+```openscad-2D
+ path = [[0,0], [-10,10], [0,20], [10,20], [10,10]];
+ stroke(path, endcaps="butt");
+```
+
+```openscad-2D
+ path = [[0,0], [-10,10], [0,20], [10,20], [10,10]];
+ stroke(path, endcaps="line");
+```
+
+```openscad-2D
+ path = [[0,0], [-10,10], [0,20], [10,20], [10,10]];
+ stroke(path, endcaps="tail");
+```
+
+```openscad-2D
+ path = [[0,0], [-10,10], [0,20], [10,20], [10,10]];
+ stroke(path, endcaps="arrow2");
+```
+
+For more standard supported endcap options, see the docs for [`stroke()`](shapes2d.scad#stroke).
+
+The start and ending endcaps can be specified individually or separately, using `endcap1=` and `endcap2=`:
+
+```openscad-2D
+ path = [[0,0], [-10,10], [0,20], [10,20], [10,10]];
+ stroke(path, endcap2="arrow2");
+```
+
+```openscad-2D
+ path = [[0,0], [-10,10], [0,20], [10,20], [10,10]];
+ stroke(path, endcap1="butt", endcap2="arrow2");
+```
+
+```openscad-2D
+ path = [[0,0], [-10,10], [0,20], [10,20], [10,10]];
+ stroke(path, endcap1="tail", endcap2="arrow");
+```
+
+The size of the endcaps will be relative to the width of the line where the endcap is to be placed:
+
+```openscad-2D
+ path = [[0,0], [-10,10], [0,20], [10,20], [10,10]];
+ widths = [1, 1.25, 1.5, 1.75, 2];
+ stroke(path, width=widths, endcaps="arrow2");
+```
+
+If none of the standard endcaps are useful to you, it is possible to design your own, simply by
+passing a path to the `endcaps=`, `endcap1=`, or `endcap2=` arguments. You may also need to give
+`trim=` to tell it how far back to trim the main line, so it renders nicely. The values in the
+endcap polygon, and in the `trim=` argument are relative to the line width. A value of 1 is one
+line width size.
+
+Untrimmed:
+
+```openscad-2D
+ path = [[0,0], [-10,10], [0,20], [10,20], [10,10]];
+ dblarrow = [[0,0], [2,-3], [0.5,-2.3], [2,-4], [0.5,-3.5], [-0.5,-3.5], [-2,-4], [-0.5,-2.3], [-2,-3]];
+ stroke(path, endcaps=dblarrow);
+```
+
+Trimmed:
+
+```openscad-2D
+ path = [[0,0], [-10,10], [0,20], [10,20], [10,10]];
+ dblarrow = [[0,0], [2,-3], [0.5,-2.3], [2,-4], [0.5,-3.5], [-0.5,-3.5], [-2,-4], [-0.5,-2.3], [-2,-3]];
+ stroke(path, trim=3.5, endcaps=dblarrow);
+```
+
+### Standard 2D Shape Polygons
+BOSL2 will let you get the perimeter polygon for almost all of the standard 2D shapes simply by calling them like a function:
+
+```openscad-2D
+ path = square(40, center=true);
+ stroke(path, closed=true, endcap2="arrow2");
+```
+
+```openscad-2D
+ path = rect([40,30], rounding=5, center=true);
+ stroke(path, closed=true, endcap2="arrow2");
+```
+
+```openscad-2D
+ path = trapezoid(w1=40, w2=20, h=30);
+ stroke(path, closed=true, endcap2="arrow2");
+```
+
+```openscad-2D
+ path = circle(d=50);
+ stroke(path, closed=true, endcap2="arrow2");
+```
+
+```openscad-2D
+ path = oval(d=[50,30]);
+ stroke(path, closed=true, endcap2="arrow2");
+```
+
+```openscad-2D
+ path = pentagon(d=50);
+ stroke(path, closed=true, endcap2="arrow2");
+```
+
+```openscad-2D
+ path = star(n=5, step=2, d=50);
+ stroke(path, closed=true, endcap2="arrow2");
+```
+
+### Arcs
+Often, when you are constructing a path, you will want to add an arc. The `arc()` command lets you do that:
+
+```openscad-2D
+ path = arc(r=30, angle=120);
+ stroke(path, endcap2="arrow2");
+```
+
+```openscad-2D
+ path = arc(d=60, angle=120);
+ stroke(path, endcap2="arrow2");
+```
+
+If you give the `N=` argument, you can control exactly how many points the arc is divided into:
+
+```openscad-2D
+ path = arc(N=5, r=30, angle=120);
+ stroke(path, endcap2="arrow2");
+```
+
+With the `start=` argument, you can start the arc somewhere other than the X+ axis:
+
+```openscad-2D
+ path = arc(start=45, r=30, angle=120);
+ stroke(path, endcap2="arrow2");
+```
+
+Alternatively, you can give starting and ending angles in a list in the `angle=` argument:
+
+```openscad-2D
+ path = arc(angle=[120,45], r=30);
+ stroke(path, endcap2="arrow2");
+```
+
+The `cp=` argument lets you center the arc somewhere other than the origin:
+
+```openscad-2D
+ path = arc(cp=[10,0], r=30, angle=120);
+ stroke(path, endcap2="arrow2");
+```
+
+The arc can also be given by three points on the arc:
+
+```openscad-2D
+ pts = [[-15,10],[0,20],[35,-5]];
+ path = arc(points=pts);
+ stroke(path, endcap2="arrow2");
+```
+
+
+### Turtle Graphics
+Another way you can create a path is using the `turtle()` command. It implements a simple path
+description language that is similar to LOGO Turtle Graphics. The concept is that you have a virtial
+turtle or cursor walking a path. It can "move" forward or backward, or turn "left" or "right" in
+place:
+
+```openscad-2D
+ path = turtle([
+ "move", 10,
+ "left", 90,
+ "move", 20,
+ "left", 135,
+ "move", 10*sqrt(2),
+ "right", 90,
+ "move", 10*sqrt(2),
+ "left", 135,
+ "move", 20
+ ]);
+ stroke(path, endcap2="arrow2");
+```
+
+The position and the facing of the turtle/cursor updates after each command. The motion and turning
+commands can also have default distances or angles given:
+
+```openscad-2D
+ path = turtle([
+ "angle",360/6,
+ "length",10,
+ "move","turn",
+ "move","turn",
+ "move","turn",
+ "move","turn",
+ "move"
+ ]);
+ stroke(path, endcap2="arrow2");
+```
+
+You can use "scale" to relatively scale up the default motion length:
+
+```openscad-2D
+ path = turtle([
+ "angle",360/6,
+ "length",10,
+ "move","turn",
+ "move","turn",
+ "scale",2,
+ "move","turn",
+ "move","turn",
+ "scale",0.5,
+ "move"
+ ]);
+ stroke(path, endcap2="arrow2");
+```
+
+Sequences of commands can be repeated using the "repeat" command:
+
+```openscad-2D
+ path=turtle([
+ "angle",360/5,
+ "length",10,
+ "repeat",5,["move","turn"]
+ ]);
+ stroke(path, endcap2="arrow2");
+```
+
+More complicated commands also exist, including those that form arcs:
+
+```openscad-2D
+ path = turtle([
+ "move", 10,
+ "left", 90,
+ "move", 20,
+ "arcleft", 10, 180,
+ "move", 20
+ ]);
+ stroke(path, endcap2="arrow2");
+```
+
+A comprehensive list of supported turtle commands can be found in the docs for [`turtle()`](shapes2d.scad#turtle).
+
+### Transforming Paths and Polygons
+To translate a path, you can just pass it to the `move()` (or up/down/left/right/fwd/back) function in the `p=` argument:
+
+```openscad-2D
+ path = move([-15,-30], p=square(50,center=true));
+ stroke(path, closed=true, endcap2="arrow2");
+```
+
+```openscad-2D
+ path = fwd(30, p=square(50,center=true));
+ stroke(path, closed=true, endcap2="arrow2");
+```
+
+```openscad-2D
+ path = left(30, p=square(50,center=true));
+ stroke(path, closed=true, endcap2="arrow2");
+```
+
+To scale a path, you can just pass it to the `scale()` (or [xyz]scale) function in the `p=` argument:
+
+```openscad-2D
+ path = scale([1.5,0.75], p=square(50,center=true));
+ stroke(path, closed=true, endcap2="arrow2");
+```
+
+```openscad-2D
+ path = xscale(1.5, p=square(50,center=true));
+ stroke(path, closed=true, endcap2="arrow2");
+```
+
+```openscad-2D
+ path = yscale(1.5, p=square(50,center=true));
+ stroke(path, closed=true, endcap2="arrow2");
+```
+
+To rotate a path, just can pass it to the `rot()` (or [xyz]rot) function in the `p=` argument:
+
+```openscad-2D
+ path = rot(30, p=square(50,center=true));
+ stroke(path, closed=true, endcap2="arrow2");
+```
+
+```openscad-2D
+ path = zrot(30, p=square(50,center=true));
+ stroke(path, closed=true, endcap2="arrow2");
+```
+
+To mirror a path, just can pass it to the `mirror()` (or [xyz]flip) function in the `p=` argument:
+
+```openscad-2D
+ path = mirror([1,1], p=trapezoid(w1=40, w2=10, h=25));
+ stroke(path, closed=true, endcap2="arrow2");
+```
+
+```openscad-2D
+ path = xflip(p=trapezoid(w1=40, w2=10, h=25));
+ stroke(path, closed=true, endcap2="arrow2");
+```
+
+```openscad-2D
+ path = yflip(p=trapezoid(w1=40, w2=10, h=25));
+ stroke(path, closed=true, endcap2="arrow2");
+```
+
+You can get raw transformation matrices for various transformations by calling them like a function without a `p=` argument:
+
+```openscad-2D
+ mat = move([5,10,0]);
+ multmatrix(mat) square(50,center=true);
+```
+
+```openscad-2D
+ mat = scale([1.5,0.75,1]);
+ multmatrix(mat) square(50,center=true);
+```
+
+```openscad-2D
+ mat = rot(30);
+ multmatrix(mat) square(50,center=true);
+```
+
+Raw transformation matrices can be multiplied together to precalculate a compound transformation. For example, to scale a shape, then rotate it, then translate the result, you can do something like:
+
+```openscad-2D
+ mat = move([5,10,0]) * rot(30) * scale([1.5,0.75,1]);
+ multmatrix(mat) square(50,center=true);
+```
+
+To apply a compound transformation matrix to a path, you can use the `apply()` function:
+
+```openscad-2D
+ mat = move([5,10]) * rot(30, planar=true) * scale([1.5,0.75]);
+ path = square(50,center=true);
+ tpath = apply(mat, path);
+ stroke(tpath, endcap2="arrow2");
+```
+
+
+### Regions
+A polygon is good to denote a single closed 2D shape with no holes in it. For more complex 2D
+shapes, you will need to use regions. A region is a list of 2D polygons, where each polygon is
+XORed against all the others. You can display a region using the `region()` module.
+
+If you have a region with one polygon fully inside another, it makes a hole:
+
+```openscad-2D
+ rgn = [square(50,center=true), circle(d=30)];
+ region(rgn);
+```
+
+If you have a region with multiple polygons that are not contained by any others, they make multiple discontiguous shapes:
+
+```openscad-2D
+ rgn = [
+ move([-30, 20], p=square(20,center=true)),
+ move([ 0,-20], p=trapezoid(w1=20, w2=10, h=20)),
+ move([ 30, 20], p=square(20,center=true)),
+ ];
+ region(rgn);
+```
+
+Region polygons can be nested abitrarily deep, in multiple discontiguous shapes:
+
+```openscad-2D
+ rgn = [
+ for (d=[50:-10:10]) left(30, p=circle(d=d)),
+ for (d=[50:-10:10]) right(30, p=circle(d=d))
+ ];
+ region(rgn);
+```
+
+A region with crossing polygons is somewhat poorly formed, but the intersection(s) of the polygons become holes:
+
+```openscad-2D
+ rgn = [
+ left(15, p=circle(d=50)),
+ right(15, p=circle(d=50))
+ ];
+ region(rgn);
+```
+
+### Boolean Region Geometry
+Similarly to how OpenSCAD can perform operations like union/difference/intersection/offset on shape geometry,
+the BOSL2 library lets you perform those same operations on regions:
+
+```openscad-2D
+ rgn1 = [for (d=[40:-10:10]) circle(d=d)];
+ rgn2 = [square([60,12], center=true)];
+ rgn = union(rgn1, rgn2);
+ region(rgn);
+```
+
+```openscad-2D
+ rgn1 = [for (d=[40:-10:10]) circle(d=d)];
+ rgn2 = [square([60,12], center=true)];
+ rgn = difference(rgn1, rgn2);
+ region(rgn);
+```
+
+```openscad-2D
+ rgn1 = [for (d=[40:-10:10]) circle(d=d)];
+ rgn2 = [square([60,12], center=true)];
+ rgn = intersection(rgn1, rgn2);
+ region(rgn);
+```
+
+```openscad-2D
+ rgn1 = [for (d=[40:-10:10]) circle(d=d)];
+ rgn2 = [square([60,12], center=true)];
+ rgn = exclusive_or(rgn1, rgn2);
+ region(rgn);
+```
+
+```openscad-2D
+ orig_rgn = [star(n=5, step=2, d=50)];
+ rgn = offset(orig_rgn, r=-3, closed=true);
+ color("blue") region(orig_rgn);
+ region(rgn);
+```
+
+You can use regions for several useful things. If you wanted a grid of holes in your object that
+form the shape given by a region, you can do that with `grid2d()`:
+
+```openscad-3D
+ rgn = [
+ circle(d=100),
+ star(n=5,step=2,d=100,spin=90)
+ ];
+ difference() {
+ cyl(h=5, d=120);
+ grid2d(size=[120,120], spacing=[4,4], inside=rgn) cyl(h=10,d=2);
+ }
+```
+
+You can also sweep a region through 3-space to make a solid:
+
+```openscad-3D
+ $fa=1; $fs=1;
+ rgn = [ for (d=[50:-10:10]) circle(d=d) ];
+ tforms = [
+ for (a=[90:-5:0]) xrot(a, cp=[0,-70]),
+ for (a=[0:5:90]) xrot(a, cp=[0,70]),
+ move([0,150,-70]) * xrot(90),
+ ];
+ sweep(rgn, tforms, closed=false, caps=true);
+```
+
+
+
diff --git a/tutorials/Shapes2d.md b/tutorials/Shapes2d.md
new file mode 100644
index 0000000..28b353b
--- /dev/null
+++ b/tutorials/Shapes2d.md
@@ -0,0 +1,553 @@
+# 2D Shapes Tutorial
+
+## Primitives
+There are two built-in 2D primitive shapes that OpenSCAD provides: `square()`, and `circle()`.
+The BOSL2 library provides alternative to these shapes so that they support more features,
+and more ways to simply reorient them.
+
+
+### 2D Squares
+You can still use the built-in `square()` in the familiar ways that OpenSCAD provides:
+
+```openscad-2D
+ square(100, center=false);
+```
+
+```openscad-2D
+ square(100, center=true);
+```
+
+```openscad-2D
+ square([60,40], center=true);
+```
+
+The BOSL2 library provides an enhanced equivalent to `square()` called `rect()`.
+You can use it in the same way you use `square()`, but it also provides
+extended functionality. For example, it allows you to round the corners:
+
+```openscad-2D
+ rect([60,40], center=true, rounding=10);
+```
+
+Or chamfer them:
+
+```openscad-2D
+ rect([60,40], center=true, chamfer=10);
+```
+
+You can even specify *which* corners get rounded or chamfered. If you pass a
+list of four size numbers to the `rounding=` or `chamfer=` arguments, it will
+give each corner its own size. In order, it goes from the back-right (quadrant I)
+corner, counter-clockwise around to the back-left (quadrant II) corner, to the
+forward-left (quadrant III) corner, to the forward-right (quadrant IV) corner:
+
+```openscad-2DImgOnly
+ module text3d(text) color("black") text(
+ text=text, font="Times", size=10,
+ halign="center", valign="center"
+ );
+ translate([ 50, 50]) text3d("I");
+ translate([-50, 50]) text3d("II");
+ translate([-50,-50]) text3d("III");
+ translate([ 50,-50]) text3d("IV");
+ rect([90,80], center=true);
+```
+
+If a size is given as `0`, then there is no rounding and/or chamfering for
+that quadrant's corner:
+
+```openscad-2D
+ rect([60,40], center=true, rounding=[0,5,10,15]);
+```
+
+```openscad-2D
+ rect([60,40], center=true, chamfer=[0,5,10,15]);
+```
+
+You can give both `rounding=` and `chamfer=` arguments to mix rounding and
+chamfering, but only if you specify per corner. If you want a rounding in
+a corner, specify a 0 chamfer for that corner, and vice versa:
+
+```openscad-2D
+ rect([60,40], center=true, rounding=[5,0,10,0], chamfer=[0,5,0,15]);
+```
+
+#### Anchors and Spin
+Another way that `rect()` is enhanced over `square()`, is that you can anchor,
+spin and attach it.
+
+The `anchor=` argument is an alternative to `center=`, which allows more
+alignment options. It takes a vector as a value, pointing roughly towards
+the side or corner you want to align to the origin. For example, to align
+the center of the back edge to the origin, set the anchor to `[0,1]`:
+
+```openscad-2D
+ rect([60,40], anchor=[0,1]);
+```
+
+To align the front right corner to the origin:
+
+```openscad-2D
+ rect([60,40], anchor=[1,-1]);
+```
+
+To center:
+
+```openscad-2D
+ rect([60,40], anchor=[0,0]);
+```
+
+To make it clearer when giving vectors, there are several standard vector
+constants defined:
+
+Constant | Direction | Value
+-------- | --------- | -----------
+`LEFT` | X- | `[-1, 0, 0]`
+`RIGHT` | X+ | `[ 1, 0, 0]`
+`FRONT`/`FORWARD`/`FWD` | Y- | `[ 0,-1, 0]`
+`BACK` | Y+ | `[ 0, 1, 0]`
+`BOTTOM`/`BOT`/`BTM`/`DOWN` | Z- | `[ 0, 0,-1]` (3D only.)
+`TOP`/`UP` | Z+ | `[ 0, 0, 1]` (3D only.)
+`CENTER`/`CTR` | Centered | `[ 0, 0, 0]`
+
+Note that even though these are 3D vectors, you can use most of them,
+(except `UP`/`DOWN`, of course) for anchors in 2D shapes:
+
+```openscad-2D
+ rect([60,40], anchor=BACK);
+```
+
+```openscad-2D
+ rect([60,40], anchor=CENTER);
+```
+
+You can add vectors together to point to corners:
+
+```openscad-2D
+ rect([60,40], anchor=FRONT+RIGHT);
+```
+
+Finally, the `spin` argument can rotate the shape by a given number of degrees
+clockwise:
+
+```openscad-2D
+ rect([60,40], anchor=CENTER, spin=30);
+```
+
+Anchoring or centering is performed before the spin:
+
+```openscad-2D
+ rect([60,40], anchor=BACK, spin=30);
+```
+
+Anchor points double as attachment points, so that you can attach other shapes:
+
+```openscad-2D
+ rect([60,40],center=true)
+ show_anchors();
+```
+
+### 2D Circles and Ovals
+The built-in `circle()` primitive can be used as expected:
+
+```openscad-2D
+ circle(r=50);
+```
+
+```openscad-2D
+ circle(d=100);
+```
+
+```openscad-2D
+ circle(d=100, $fn=8);
+```
+
+The BOSL2 library also provides an enhanced equivalent of `circle()` called `oval()`.
+You can use it in the same way you use `circle()`, but it also provides extended
+functionality. For example, it allows more control over its size and orientation.
+
+Since a circle in OpenSCAD can only be approximated by a regular polygon with
+a number of straight sides, this can lead to size and shape inaccuracies.
+To counter this, the `realign=` and `circum=` arguments are also provided.
+
+The `realign=` argument, if set `true`, rotates the `oval()` by half the angle
+between the sides:
+
+```openscad-2D
+ oval(d=100, $fn=8, realign=true);
+```
+
+The `circum=` argument, if true, makes it so that the polygon forming the
+`oval()` circumscribes the ideal circle instead of inscribing it.
+
+Inscribing the ideal circle:
+
+```openscad-2D
+ difference() {
+ circle(d=100, $fn=360);
+ oval(d=100, $fn=8);
+ }
+```
+
+Circumscribing the ideal circle:
+
+```openscad-2D
+ difference() {
+ oval(d=100, $fn=8, circum=true);
+ circle(d=100, $fn=360);
+ }
+```
+
+The `oval()` module, as its name suggests, can be given separate X and Y radii
+or diameters. To do this, just give `r=` or `d=` with a list of two radii or
+diameters:
+
+```openscad-2D
+ oval(r=[30,20]);
+```
+
+```openscad-2D
+ oval(d=[60,40]);
+```
+
+Another way that `oval()` is enhanced over `circle()`, is that you can anchor,
+spin and attach it.
+
+```openscad-2D
+ oval(r=50, anchor=BACK);
+```
+
+```openscad-2D
+ oval(r=50, anchor=FRONT+RIGHT);
+```
+
+Using spin on a circle may not make initial sense, until you remember that
+anchoring is performed before spin:
+
+```openscad-2D
+ oval(r=50, anchor=FRONT, spin=-30);
+```
+
+
+### Trapezoids
+
+OpenSCAD doesn't provide a simple way to make 2D triangles, trapezoids, or parallelograms.
+The BOSL2 library can provide all of these shapes with the `trapezoid()` module.
+
+To make a simple triangle, just make one of the widths zero:
+
+```openscad-2D
+ trapezoid(w1=50, w2=0, h=50);
+```
+
+To make a right triangle, you need to use the `shift=` argument, to shift the back of the trapezoid along the X axis:
+
+```openscad-2D
+ trapezoid(w1=50, w2=0, h=50, shift=-25);
+```
+
+```openscad-2D
+ trapezoid(w1=50, w2=0, h=50, shift=25);
+```
+
+```openscad-2D
+ trapezoid(w1=0, w2=50, h=50, shift=-25);
+```
+
+```openscad-2D
+ trapezoid(w1=0, w2=50, h=50, shift=25);
+```
+
+You can make a trapezoid by specifying non-zero widths for both the front (`w1=`) and back (`w2=`):
+
+```openscad-2D
+ trapezoid(w1=30, w2=50, h=50);
+```
+
+A parallelogram is just a matter of using the same width for front and back, with a shift along the X axis:
+
+```openscad-2D
+ trapezoid(w1=50, w2=50, shift=20, h=50);
+```
+
+A quadrilateral can be made by having unequal, non-zero front (`w1=`) and back (`w2=`) widths, with the back shifted along the X axis:
+
+```openscad-2D
+ trapezoid(w1=50, w2=30, shift=20, h=50);
+```
+
+You can use `anchor=` and `spin=`, just like with other attachable shapes. However, the anchor
+points are based on the side angles of the faces, and may not be where you expect them:
+
+```openscad-2D
+ trapezoid(w1=30, w2=50, h=50)
+ show_anchors();
+```
+
+### Regular N-Gons
+
+OpenSCAD lets you make regular N-gons (pentagon, hexagon, etc) by using `circle()` with `$fn`.
+While this is concise, it may be less than obvious at first glance:
+
+```openscad-2D
+ circle(d=50, $fn=5);
+```
+
+The BOSL2 library has modules that are named more clearly:
+
+```openscad-2D
+ pentagon(d=50);
+```
+
+```openscad-2D
+ hexagon(d=50);
+```
+
+```openscad-2D
+ octagon(d=50);
+```
+
+```openscad-2D
+ regular_ngon(n=7, d=50);
+```
+
+These modules also provide you with extra functionality.
+
+They can be sized by side length:
+
+```openscad-2D
+ pentagon(side=20);
+```
+
+They can be sized by circumscribed circle radius/diameter:
+
+```openscad-2D
+ pentagon(ir=25);
+ pentagon(id=50);
+```
+
+They can be realigned by half a side's angle:
+
+```openscad-2D
+ left(30) pentagon(d=50, realign=true);
+ right(30) pentagon(d=50, realign=false);
+```
+
+They can be rounded:
+
+```openscad-2D
+ pentagon(d=50, rounding=10);
+```
+
+```openscad-2D
+ hexagon(d=50, rounding=10);
+```
+
+They also have somewhat different attachment behavior:
+
+```openscad-2D
+ color("green") stroke(circle(d=50), closed=true);
+ oval(d=50,$fn=5)
+ attach(LEFT) color("blue") anchor_arrow2d();
+```
+
+```openscad-2D
+ pentagon(d=50)
+ attach(LEFT) color("blue") anchor_arrow2d();
+```
+
+You can use `anchor=` and `spin=`, just like with other attachable shapes. However, the anchor
+points are based on where the anchor vector would intersect the side of the N-gon, and may not
+be where you expect them:
+
+```openscad-2D
+ pentagon(d=50)
+ show_anchors(custom=false);
+```
+
+N-gons also have named anchor points for their sides and tips:
+
+```openscad-2D
+ pentagon(d=30)
+ show_anchors(std=false);
+```
+
+
+### Stars
+
+The BOSL2 library has stars as a basic supported shape. They can have any number of points.
+You can specify a star's shape by point count, inner and outer vertex radius/diameters:
+
+```openscad-2D
+ star(n=3, id=10, d=50);
+```
+
+```openscad-2D
+ star(n=5, id=15, r=25);
+```
+
+```openscad-2D
+ star(n=10, id=30, d=50);
+```
+
+Or you can specify the star shape by point count and number of points to step:
+
+```openscad-2D
+ star(n=7, step=2, d=50);
+```
+
+```openscad-2D
+ star(n=7, step=3, d=50);
+```
+
+If the `realign=` argument is given a true value, then the star will be rotated by half a point angle:
+
+```openscad-2D
+ left(30) star(n=5, step=2, d=50);
+ right(30) star(n=5, step=2, d=50, realign=true);
+```
+
+The `align_tip=` argument can be given a vector so that you can align the first point in a specific direction:
+
+```openscad-2D
+ star(n=5, ir=15, or=30, align_tip=BACK+LEFT)
+ attach("tip0") color("blue") anchor_arrow2d();
+```
+
+```openscad-2D
+ star(n=5, ir=15, or=30, align_tip=BACK+RIGHT)
+ attach("tip0") color("blue") anchor_arrow2d();
+```
+
+Similarly, the first indentation or pit can be oriented towards a specific vector with `align_pit=`:
+
+
+```openscad-2D
+ star(n=5, ir=15, or=30, align_pit=BACK+LEFT)
+ attach("pit0") color("blue") anchor_arrow2d();
+```
+
+```openscad-2D
+ star(n=5, ir=15, or=30, align_pit=BACK+RIGHT)
+ attach("pit0") color("blue") anchor_arrow2d();
+```
+
+You can use `anchor=` and `spin=`, just like with other attachable shapes. However, the anchor
+points are based on the furthest extents of the shape, and may not be where you expect them:
+
+```openscad-2D
+ star(n=5, step=2, d=50)
+ show_anchors(custom=false);
+```
+
+Stars also have named anchor points for their pits, tips, and midpoints between tips:
+
+```openscad-2D
+ star(n=5, step=2, d=40)
+ show_anchors(std=false);
+```
+
+
+
+### Teardrop2D
+
+Often when 3D printing, you may want to make a circular hole in a vertical wall. If the hole is
+too big, however, the overhang at the top of the hole can cause problems with printing on an
+FDM/FFF printer. If you don't want to use support material, you can just use the teardrop shape.
+The `teardrop2d()` module will let you make a 2D version of the teardrop shape, so that you can
+extrude it later:
+
+```openscad-2D
+ teardrop2d(r=20);
+```
+
+```openscad-2D
+ teardrop2d(d=50);
+```
+
+The default overhang angle is 45 degrees, but you can adjust that with the `ang=` argument:
+
+```openscad-2D
+ teardrop2d(d=50, ang=30);
+```
+
+If you prefer to flatten the top of the teardrop, to encourage bridging, you can use the `cap_h=`
+argument:
+
+```openscad-2D
+ teardrop2d(d=50, cap_h=25);
+```
+
+```openscad-2D
+ teardrop2d(d=50, ang=30, cap_h=30);
+```
+
+You can use `anchor=` and `spin=`, just like with other attachable shapes. However, the anchor
+points are based on the furthest extents of the shape, and may not be where you expect them:
+
+```openscad-2D
+ teardrop2d(d=50, ang=30, cap_h=30)
+ show_anchors();
+```
+
+
+### Glued Circles
+
+A more unusal shape that BOSL2 provides is Glued Circles. It's basically a pair of circles,
+connected by what looks like a gloopy glued miniscus:
+
+```openscad-2D
+ glued_circles(d=30, spread=40);
+```
+
+The `r=`/`d=` arguments can specify the radius or diameter of the two circles:
+
+```openscad-2D
+ glued_circles(r=20, spread=45);
+```
+
+```openscad-2D
+ glued_circles(d=40, spread=45);
+```
+
+The `spread=` argument specifies the distance between the centers of the two circles:
+
+```openscad-2D
+ glued_circles(d=30, spread=30);
+```
+
+```openscad-2D
+ glued_circles(d=30, spread=40);
+```
+
+The `tangent=` argument gives the angle of the tangent of the meniscus on the two circles:
+
+```openscad-2D
+ glued_circles(d=30, spread=30, tangent=45);
+```
+
+```openscad-2D
+ glued_circles(d=30, spread=30, tangent=20);
+```
+
+```openscad-2D
+ glued_circles(d=30, spread=30, tangent=-20);
+```
+
+One useful thing you can do is to string a few `glued_circle()`s in a line then extrude them to make a ribbed wall:
+
+```openscad-3D
+ $fn=36; s=10;
+ linear_extrude(height=50,convexity=16,center=true)
+ xcopies(s*sqrt(2),n=3)
+ glued_circles(d=s, spread=s*sqrt(2), tangent=45);
+```
+
+You can use `anchor=` and `spin=`, just like with other attachable shapes. However, the anchor
+points are based on the furthest extents of the shape, and may not be where you expect them:
+
+```openscad-2D
+ glued_circles(d=40, spread=40, tangent=45)
+ show_anchors();
+```
+
diff --git a/tutorials/Basic_Shapes.md b/tutorials/Shapes3d.md
similarity index 55%
rename from tutorials/Basic_Shapes.md
rename to tutorials/Shapes3d.md
index 9f848b5..c65f2c0 100644
--- a/tutorials/Basic_Shapes.md
+++ b/tutorials/Shapes3d.md
@@ -1,233 +1,13 @@
# Basic Shapes Tutorial
## Primitives
-There are 5 built-in primitive shapes that OpenSCAD provides.
-`square()`, `circle()`, `cube()`, `cylinder()`, and `sphere()`.
-The BOSL2 library extends or provides alternative to these shapes so
+There are 3 built-in 3D primitive shapes that OpenSCAD provides: `cube()`, `cylinder()`,
+and `sphere()`. The BOSL2 library extends and provides alternative to these shapes so
that they support more features, and more ways to simply reorient them.
-### 2D Squares
-You can still use the built-in `square()` in the familiar ways that OpenSCAD provides:
-
-```openscad-2D
- square(100, center=false);
-```
-
-```openscad-2D
- square(100, center=true);
-```
-
-```openscad-2D
- square([60,40], center=true);
-```
-
-The BOSL2 library provides an enhanced equivalent to `square()` called `rect()`.
-You can use it in the same way you use `square()`, but it also provides
-extended functionality. For example, it allows you to round the corners:
-
-```openscad-2D
- rect([60,40], center=true, rounding=10);
-```
-
-Or chamfer them:
-
-```openscad-2D
- rect([60,40], center=true, chamfer=10);
-```
-
-You can even specify *which* corners get rounded or chamfered. If you pass a
-list of four size numbers to the `rounding=` or `chamfer=` arguments, it will
-give each corner its own size. In order, it goes from the back-right (quadrant I)
-corner, counter-clockwise around to the back-left (quadrant II) corner, to the
-forward-left (quadrant III) corner, to the forward-right (quadrant IV) corner:
-
-```openscad-2DImgOnly
- module text3d(text) color("black") text(
- text=text, font="Times", size=10,
- halign="center", valign="center"
- );
- translate([ 50, 50]) text3d("I");
- translate([-50, 50]) text3d("II");
- translate([-50,-50]) text3d("III");
- translate([ 50,-50]) text3d("IV");
- rect([90,80], center=true);
-```
-
-If a size is given as `0`, then there is no rounding and/or chamfering for
-that quadrant's corner:
-
-```openscad-2D
- rect([60,40], center=true, rounding=[0,5,10,15]);
-```
-
-```openscad-2D
- rect([60,40], center=true, chamfer=[0,5,10,15]);
-```
-
-You can give both `rounding=` and `chamfer=` arguments to mix rounding and
-chamfering, but only if you specify per corner. If you want a rounding in
-a corner, specify a 0 chamfer for that corner, and vice versa:
-
-```openscad-2D
- rect([60,40], center=true, rounding=[5,0,10,0], chamfer=[0,5,0,15]);
-```
-
-#### Anchors and Spin
-Another way that `rect()` is enhanced over `square()`, is that you can anchor,
-spin and attach it.
-
-The `anchor=` argument is an alternative to `center=`, which allows more
-alignment options. It takes a vector as a value, pointing roughly towards
-the side or corner you want to align to the origin. For example, to align
-the center of the back edge to the origin, set the anchor to `[0,1]`:
-
-```openscad-2D
- rect([60,40], anchor=[0,1]);
-```
-
-To align the front right corner to the origin:
-
-```openscad-2D
- rect([60,40], anchor=[1,-1]);
-```
-
-To center:
-
-```openscad-2D
- rect([60,40], anchor=[0,0]);
-```
-
-To make it clearer when giving vectors, there are several standard vector
-constants defined:
-
-Constant | Direction | Value
--------- | --------- | -----------
-`LEFT` | X- | `[-1, 0, 0]`
-`RIGHT` | X+ | `[ 1, 0, 0]`
-`FRONT`/`FORWARD`/`FWD` | Y- | `[ 0,-1, 0]`
-`BACK` | Y+ | `[ 0, 1, 0]`
-`BOTTOM`/`BOT`/`BTM`/`DOWN` | Z- | `[ 0, 0,-1]` (3D only.)
-`TOP`/`UP` | Z+ | `[ 0, 0, 1]` (3D only.)
-`CENTER`/`CTR` | Centered | `[ 0, 0, 0]`
-
-Note that even though these are 3D vectors, you can use most of them,
-(except `UP`/`DOWN`, of course) for anchors in 2D shapes:
-
-```openscad-2D
- rect([60,40], anchor=BACK);
-```
-
-```openscad-2D
- rect([60,40], anchor=CENTER);
-```
-
-You can add vectors together to point to corners:
-
-```openscad-2D
- rect([60,40], anchor=FRONT+RIGHT);
-```
-
-Finally, the `spin` argument can rotate the shape by a given number of degrees
-clockwise:
-
-```openscad-2D
- rect([60,40], anchor=CENTER, spin=30);
-```
-
-Anchoring or centering is performed before the spin:
-
-```openscad-2D
- rect([60,40], anchor=BACK, spin=30);
-```
-
-
-### 2D Circles
-The built-in `circle()` primitive can be used as expected:
-
-```openscad-2D
- circle(r=50);
-```
-
-```openscad-2D
- circle(d=100);
-```
-
-```openscad-2D
- circle(d=100, $fn=8);
-```
-
-The BOSL2 library provides an enhanced equivalent of `circle()` called `oval()`.
-You can use it in the same way you use `circle()`, but it also provides
-extended functionality. For example, it allows more control over its size and
-orientation.
-
-Since a circle in OpenSCAD can only be approximated by a regular polygon with
-a number of straight sides, this can lead to size and shape inaccuracies.
-To counter this, the `realign=` and `circum=` arguments are also provided.
-
-The `realign=` argument, if set `true`, rotates the `oval()` by half the angle
-between the sides:
-
-```openscad-2D
- oval(d=100, $fn=8, realign=true);
-```
-
-The `circum=` argument, if true, makes it so that the polygon forming the
-`oval()` circumscribes the ideal circle instead of inscribing it.
-
-Inscribing the ideal circle:
-
-```openscad-2D
- difference() {
- circle(d=100, $fn=360);
- oval(d=100, $fn=8);
- }
-```
-
-Circumscribing the ideal circle:
-
-```openscad-2D
- difference() {
- oval(d=100, $fn=8, circum=true);
- circle(d=100, $fn=360);
- }
-```
-
-The `oval()` module, as its name suggests, can be given separate X and Y radii
-or diameters. To do this, just give `r=` or `d=` with a list of two radii or
-diameters:
-
-```openscad-2D
- oval(r=[30,20]);
-```
-
-```openscad-2D
- oval(d=[60,40]);
-```
-
-Another way that `oval()` is enhanced over `circle()`, is that you can anchor,
-spin and attach it.
-
-```openscad-2D
- oval(r=50, anchor=BACK);
-```
-
-```openscad-2D
- oval(r=50, anchor=FRONT+RIGHT);
-```
-
-Using spin on a circle may not make initial sense, until you remember that
-anchoring is performed before spin:
-
-```openscad-2D
- oval(r=50, anchor=FRONT, spin=-30);
-```
-
-
### 3D Cubes
-BOSL2 overrides the built-in `cube()` module. It still can be used as you
-expect from the built-in:
+BOSL2 overrides the built-in `cube()` module. It still can be used as you expect from the built-in:
```openscad-3D
cube(100);
@@ -243,7 +23,7 @@ expect from the built-in:
It is also enhanced to allow you to anchor, spin, orient, and attach it.
-You can use `anchor=` similarly to how you use it with `square()` or `rect()`,
+You can use `anchor=` similarly to how you use it with `rect()` or `oval()`,
except you can also anchor vertically in 3D, allowing anchoring to faces, edges,
and corners:
@@ -546,11 +326,20 @@ The "stagger" style will stagger the triangulation of the vertical rows:
spheroid(d=100, style="stagger", $fn=20);
```
-The "icosa"` style will make for roughly equal-sized triangles for the entire
-sphere surface:
+The "icosa" style will make for roughly equal-sized triangles for the entire
+sphere surface, based on subdividing an icosahedron. This style will round the
+effective `$fn` to a multiple of 5 when constructing the spheroid:
```openscad-3D
spheroid(d=100, style="icosa", $fn=20);
```
+The "octa" style will also make for roughly equal-sized triangles for the entire
+sphere surface, but based on subdividing an octahedron. This is useful in that it
+guarantees vertices at the axis extrema. This style will round the effective `$fn`
+to a multiple of 4 when constructing the spheroid:
+
+```openscad-3D
+ spheroid(d=100, style="octa", $fn=20);
+```
diff --git a/version.scad b/version.scad
index 9757673..1c6817a 100644
--- a/version.scad
+++ b/version.scad
@@ -8,7 +8,7 @@
//////////////////////////////////////////////////////////////////////
-BOSL_VERSION = [2,0,483];
+BOSL_VERSION = [2,0,492];
// Section: BOSL Library Version Functions