From 8603aa4bee7d612a09c22e7977022496f18f1768 Mon Sep 17 00:00:00 2001
From: Garth Minette <revarbat@gmail.com>
Date: Thu, 20 May 2021 01:37:08 -0700
Subject: [PATCH] Expanded Attachments tutorial for 2D, recolor(), and masking.

---
 tutorials/Attachments.md | 236 +++++++++++++++++++++++++++++++++++++--
 1 file changed, 228 insertions(+), 8 deletions(-)

diff --git a/tutorials/Attachments.md b/tutorials/Attachments.md
index b17b70a..98a0341 100644
--- a/tutorials/Attachments.md
+++ b/tutorials/Attachments.md
@@ -91,6 +91,23 @@ overrides the `anchor=` argument.  A `center=true` argument is the same as `anch
 A `center=false` argument can mean `anchor=[-1,-1,-1]` for a cube, or `anchor=BOTTOM` for a
 cylinder.
 
+Many 2D shapes provided by BOSL2 are also anchorable.  Due to technical limitations of OpenSCAD,
+however, `square()` and `circle()` are *not*.  BOSL2 provides `rect()` and `oval()` as attachable
+and anchorable equivalents.  You can only anchor on the XY plane, of course, but you can use the
+same `FRONT`, `BACK`, `LEFT`, `RIGHT`, and `CENTER` anchor constants.
+
+```openscad-2D
+rect([40,30], anchor=BACK+LEFT);
+```
+
+```openscad-2D
+oval(d=50, anchor=FRONT);
+```
+
+```openscad-2D
+hexagon(d=50, anchor=BACK);
+```
+
 
 ## Spin
 Attachable shapes also can be spun in place as you create them.  You can do this by passing in
@@ -107,6 +124,17 @@ vector, like [Xang,Yang,Zang]:
 cube([20,20,40], center=true, spin=[10,20,30]);
 ```
 
+You can also apply spin to 2D shapes from BOSL2.  Again, you should use `rect()` and `oval()`
+instead of `square()` and `circle()`:
+
+```openscad-2D
+rect([40,30], spin=30);
+```
+
+```openscad-2D
+oval(d=[40,30], spin=30);
+```
+
 
 ## Orientation
 Another way to specify a rotation for an attachable shape, is to pass a 3D vector via the
@@ -117,6 +145,9 @@ For example, you can make a cone that is tilted up and to the right like this:
 cylinder(h=100, r1=50, r2=20, orient=UP+RIGHT);
 ```
 
+You can *not* use `orient=` with 2D shapes.
+
+
 ## Mixing Anchoring, Spin, and Orientation
 When giving `anchor=`, `spin=`, and `orient=`, they are applied anchoring first, spin second,
 then orient last.  For example, here's a cube:
@@ -150,8 +181,14 @@ However, since spin is applied *after* anchoring, it can actually have a signifi
 cylinder(d=50, l=40, anchor=FWD, spin=-30);
 ```
 
+For 2D shapes, you can mix `anchor=` with `spin=`, but not with `orient=`.
 
-## Attaching Children
+```openscad-2D
+rect([40,30], anchor=BACK+LEFT, spin=30);
+```
+
+
+## Attaching 3D Children
 The reason attachables are called that, is because they can be attached to each other.
 You can do that by making one attachable shape be a child of another attachable shape.
 By default, the child of an attachable is attached to the center of the parent shape.
@@ -178,13 +215,21 @@ cube(50,center=true)
     attach(TOP,TOP) cylinder(d1=50,d2=20,l=20);
 ```
 
-By default, `attach()` causes the child to overlap the parent by 0.01, to let CGAL correctly
-join the parts.  If you need the child to have no overlap, or a different overlap, you can use
-the `overlap=` argument:
+By default, `attach()` places the child exactly flush with the surface of the parent.  Sometimes
+it's useful to have the child overlap the parent by insetting a bit.  You can do this with the
+`overlap=` argument to `attach()`.  A positive value will inset the child into the parent, and
+a negative value will outset out from the parent:
 
 ```openscad
 cube(50,center=true)
-    attach(TOP,TOP,overlap=0) cylinder(d1=50,d2=20,l=20);
+    attach(TOP,overlap=10)
+        cylinder(d=20,l=20);
+```
+
+```openscad
+cube(50,center=true)
+    attach(TOP,overlap=-20)
+        cylinder(d=20,l=20);
 ```
 
 If you want to position the child at the parent's anchorpoint, without re-orienting, you can
@@ -217,6 +262,23 @@ cube(50, center=true)
     position([TOP,RIGHT,FRONT]) cylinder(d1=50,d2=20,l=20);
 ```
 
+## Attaching 2D Children
+You can use attachments in 2D as well, but only in the XY plane.  Also, the built-in `square()`
+and `circle()` 2D modules do not support attachments.  Instead, you should use the `rect()` and
+`oval()` modules:
+
+```openscad-2D
+rect(50,center=true)
+    attach(RIGHT,FRONT)
+        trapezoid(w1=30,w2=0,h=30);
+```
+
+```openscad-2D
+oval(d=50)
+    attach(BACK,FRONT,overlap=5)
+        trapezoid(w1=30,w2=0,h=30);
+```
+
 ## Anchor Arrows
 One way that is useful to show the position and orientation of an anchorpoint is by attaching
 an anchor arrow to that anchor.
@@ -259,6 +321,7 @@ cylinder(h=100, d=100, center=true)
     show_anchors(s=30);
 ```
 
+
 ## Tagged Operations
 BOSL2 introduces the concept of tags.  Tags are names that can be given to attachables, so that
 you can refer to them when performing `diff()`, `intersect()`, and `hulling()` operations.
@@ -372,12 +435,169 @@ cube(50, center=true, $tags="hull") {
 ```
 
 
-## Masking Children
-TBW
+## 3D Masking Attachments
+To make it easier to mask away shapes from various edges of an attachable parent shape, there
+are a few specialized alternatives to the `attach()` and `position()` modules.
+
+### `edge_mask()`
+If you have a 3D mask shape that you want to difference away from various edges, you can use
+the `edge_mask()` module.  This module will take a vertically oriented shape, and will rotate
+and move it such that the BACK, RIGHT (X+,Y+) side of the shape will be aligned with the given
+edges.  The shape will be tagged as a "mask" so that you can use `diff("mask")`.  For example,
+here's a shape for rounding an edge:
+
+```openscad
+module round_edge(l,r) difference() {
+    translate([-1,-1,-l/2])
+        cube([r+1,r+1,l]);
+    translate([r,r])
+        cylinder(h=l+1,r=r,center=true, $fn=quantup(segs(r),4));
+}
+round_edge(l=30, r=19);
+```
+
+You can use that mask to round various edges of a cube:
+
+```openscad
+module round_edge(l,r) difference() {
+    translate([-1,-1,-l/2])
+        cube([r+1,r+1,l]);
+    translate([r,r])
+        cylinder(h=l+1,r=r,center=true, $fn=quantup(segs(r),4));
+}
+diff("mask")
+cube([50,60,70],center=true)
+    edge_mask([TOP,"Z"],except=[BACK,TOP+LEFT])
+        round_edge(l=71,r=10);
+```
+
+### `corner_mask()`
+If you have a 3D mask shape that you want to difference away from various corners, you can use
+the `corner_mask()` module.  This module will take a shape and rotate and move it such that the
+BACK RIGHT TOP (X+,Y+,Z+) side of the shape will be aligned with the given corner.  The shape
+will be tagged as a "mask" so that you can use `diff("mask")`.  For example, here's a shape for
+rounding a corner:
+
+```openscad
+module round_corner(r) difference() {
+    translate(-[1,1,1])
+    	cube(r+1);
+    translate([r,r,r])
+    	sphere(r=r, style="aligned", $fn=quantup(segs(r),4));
+}
+round_corner(r=10);
+```
+
+You can use that mask to round various corners of a cube:
+
+```openscad
+module round_corner(r) difference() {
+    translate(-[1,1,1])
+    	cube(r+1);
+    translate([r,r,r])
+    	sphere(r=r, style="aligned", $fn=quantup(segs(r),4));
+}
+diff("mask")
+cube([50,60,70],center=true)
+    corner_mask([TOP,FRONT],LEFT+FRONT+TOP)
+        round_corner(r=10);
+```
+
+### Mix and Match Masks
+You can use `edge_mask()` and `corner_mask()` together as well:
+
+```openscad
+module round_corner(r) difference() {
+    translate(-[1,1,1])
+    	cube(r+1);
+    translate([r,r,r])
+    	sphere(r=r, style="aligned", $fn=quantup(segs(r),4));
+}
+module round_edge(l,r) difference() {
+    translate([-1,-1,-l/2])
+        cube([r+1,r+1,l]);
+    translate([r,r])
+        cylinder(h=l+1,r=r,center=true, $fn=quantup(segs(r),4));
+}
+diff("mask")
+cube([50,60,70],center=true) {
+    edge_mask("ALL") round_edge(l=71,r=10);
+    corner_mask("ALL") round_corner(r=10);
+}
+```
+
+## 2D Profile Mask Attachments
+While 3D mask shapes give you a great deal of control, you need to make sure they are correctly
+sized, and you need to provide separate mask shapes for corners and edges.  Often, a single 2D
+profile could be used to describe the edge mask shape (via `linear_extrude()`), and the corner
+mask shape (via `rotate_extrude()`).  This is where `edge_profile()`, `corner_profile()`, and
+`face_profile()` come in.
+
+### `edge_profile()`
+Using the `edge_profile()` module, you can provide a 2D profile shape and it will be linearly
+extruded to a mask of the apropriate length for each given edge.  The resultant mask will be
+tagged with "mask" so that you can difference it away with `diff("mask")`.  The 2D profile is
+assumed to be oriented with the BACK, RIGHT (X+,Y+) quadrant as the "cutter edge" that gets
+re-oriented towards the edges of the parent shape.  A typical mask profile for chamfering an
+edge may look like:
+
+```openscad
+mask2d_roundover(10);
+```
+
+Using that mask profile, you can mask the edges of a cube like:
+
+```openscad
+diff("mask")
+cube([50,60,70],center=true)
+   edge_profile("ALL")
+       mask2d_roundover(10);
+```
+
+### `corner_profile()`
+You can use the same profile to make a rounded corner mask as well:
+
+```openscad
+diff("mask")
+cube([50,60,70],center=true)
+   corner_profile("ALL", r=10)
+       mask2d_roundover(10);
+```
+
+### `face_profile()`
+As a simple shortcut to apply a profile mask to all edges and corners of a face, you can use the
+`face_profile()` module:
+
+```openscad
+diff("mask")
+cube([50,60,70],center=true)
+   face_profile(TOP, r=10)
+       mask2d_roundover(10);
+```
 
 
 ## Coloring Attachables
-TBW
+Usually, when coloring a shape with the `color()` module, the parent color overrides the colors of
+all children.  This is often not what you want:
+
+```openscad
+color("red") spheroid(d=3) {
+    attach(CENTER,BOT) color("white") cyl(h=10, d=1) {
+        attach(TOP,BOT) color("green") cyl(h=5, d1=3, d2=0);
+    }
+}
+```
+
+If you use the `recolor()` module, however, the child's color overrides the color of the parent.
+This is probably easier to understand by example:
+
+```openscad
+recolor("red") spheroid(d=3) {
+    attach(CENTER,BOT) recolor("white") cyl(h=10, d=1) {
+        attach(TOP,BOT) recolor("green") cyl(h=5, d1=3, d2=0);
+    }
+}
+```
 
 
 ## Making Attachables