1//////////////////////////////////////////////////////////////////////
   2// LibFile: attachments.scad
   3//   The modules in this file allows you to attach one object to another by making one object the child of another object.
   4//   You can place the child object in relation to its parent object and control the position and orientation
   5//   relative to the parent.  The modifiers allow you to treat children in different ways that simple union, such
   6//   as differencing them from the parent, or changing their color.  Attachment only works when the parent and child
   7//   are both written to support attachment.  Also included in this file  are the tools to make your own "attachable" objects.
   8// Includes:
   9//   include <BOSL2/std.scad>
  10// FileGroup: Basic Modeling
  11// FileSummary: Positioning objects on or relative to other objects.  Making your own objects support attachment.
  12// FileFootnotes: STD=Included in std.scad
  13//////////////////////////////////////////////////////////////////////
  14
  15
  16// Default values for attachment code.
  17$tags=undef;      // for backward compatibility
  18$tag = "";
  19$tag_prefix = "";
  20$overlap = 0;
  21$color = "default";          
  22$save_color = undef;         // Saved color to revert back for children
  23
  24$attach_to = undef;
  25$attach_anchor = [CENTER, CENTER, UP, 0];
  26$attach_norot = false;
  27
  28$parent_anchor = BOTTOM;
  29$parent_spin = 0;
  30$parent_orient = UP;
  31
  32$parent_size = undef;
  33$parent_geom = undef;
  34
  35$tags_shown = "ALL";
  36$tags_hidden = [];
  37
  38_ANCHOR_TYPES = ["intersect","hull"];
  39
  40
  41// Section: Terminology and Shortcuts
  42//   This library adds the concept of anchoring, spin and orientation to the `cube()`, `cylinder()`
  43//   and `sphere()` builtins, as well as to most of the shapes provided by this library itself.
  44//   - An anchor is a place on an object which you can align the object to, or attach other objects
  45//     to using `attach()` or `position()`.  An anchor has a position, a direction, and a spin.
  46//     The direction and spin are used to orient other objects to match when using `attach()`.
  47//   - Spin is a simple rotation around the Z axis.
  48//   - Orientation is rotating an object so that its top is pointed towards a given vector.
  49//   An object will first be translated to its anchor position, then spun, then oriented.
  50//   For a detailed step-by-step explanation of attachments, see the [[Attachments Tutorial|Tutorial-Attachments]].
  51//   .
  52//   For describing directions, faces, edges, and corners the library provides a set of shortcuts
  53//   all based on combinations of unit direction vectors.  You can use these for anchoring and orienting
  54//   attachable objects.  You can also them to specify edge sets for rounding or chamfering cuboids,
  55//   or for placing edge, face and corner masks.  
  56// Subsection: Anchor
  57//   Anchoring is specified with the `anchor` argument in most shape modules.  Specifying `anchor`
  58//   when creating an object will translate the object so that the anchor point is at the origin
  59//   (0,0,0).  Anchoring always occurs before spin and orientation are applied.
  60//   .
  61//   An anchor can be referred to in one of two ways; as a directional vector, or as a named anchor string.
  62//   .
  63//   When given as a vector, it points, in a general way, towards the face, edge, or corner of the
  64//   object that you want the anchor for, relative to the center of the object.  You can simply
  65//   specify a vector like `[0,0,1]` to anchor an object at the Z+ end, but you can also use 
  66//   directional constants with names like `TOP`, `BOTTOM`, `LEFT`, `RIGHT` and `BACK` that you can add together
  67//   to specify anchor points.  See [specifying directions](attachments.scad#subsection-specifying-directions) 
  68//   below for the full list of pre-defined directional constants. 
  69//   .
  70//   For example:
  71//   - `[0,0,1]` is the same as `TOP` and refers to the center of the top face.
  72//   - `[-1,0,1]` is the same as `TOP+LEFT`, and refers to the center of the top-left edge.
  73//   - `[1,1,-1]` is the same as `BOTTOM+BACK+RIGHT`, and refers to the bottom-back-right corner.
  74//   .
  75//   When the object is cubical or rectangular in shape the anchors must have zero or one values
  76//   for their components and they refer to the face centers, edge centers, or corners of the object.
  77//   The direction of a face anchor will be perpendicular to the face, pointing outward.  The direction of a edge anchor
  78//   will be the average of the anchor directions of the two faces the edge is between.  The direction
  79//   of a corner anchor will be the average of the anchor directions of the three faces the corner is
  80//   on. 
  81//   .
  82//   When the object is cylindrical, conical, or spherical in nature, the anchors will be located
  83//   around the surface of the cylinder, cone, or sphere, relative to the center.
  84//   You can generally use an arbitrary vector to get an anchor positioned anywhere on the curved
  85//   surface of such an object, and the anchor direction will be the surface normal at the anchor location.
  86//   However, for anchor component pointing toward the flat face should be either -1, 1, or 0, and
  87//   anchors that point diagonally toward one of the flat faces will select a point on the edge.
  88//   .
  89//   For objects in two dimensions, the natural expectation is for TOP and BOTTOM to refer to the Y direction
  90//   of the shape.  To support this, if you give an anchor in 2D that has anchor.y=0 then the Z component
  91//   will be mapped to the Y direction.  This  means you can use TOP and BOTTOM for anchors of 2D objects.
  92//   But remember that TOP and BOTTOM are three dimensional vectors and this is a special interpretation
  93//   for 2d anchoring.  
  94//   .
  95//   Some more complex objects, like screws and stepper motors, have named anchors to refer to places
  96//   on the object that are not at one of the standard faces, edges or corners.  For example, stepper
  97//   motors have anchors for `"screw1"`, `"screw2"`, etc. to refer to the various screwholes on the
  98//   stepper motor shape.  The names, positions, directions, and spins of these anchors are
  99//   specific to the object, and are documented when they exist.
 100// Subsection: Spin
 101//   Spin is specified with the `spin` argument in most shape modules.  Specifying a scalar `spin`
 102//   when creating an object will rotate the object counter-clockwise around the Z axis by the given
 103//   number of degrees.  If given as a 3D vector, the object will be rotated around each of the X, Y, Z
 104//   axes by the number of degrees in each component of the vector.  Spin is always applied after
 105//   anchoring, and before orientation.  Since spin is applied after anchoring it is not what
 106//   you might think of intuitively as spinning the shape.  To do that, apply `zrot()` to the shape before anchoring.  
 107// Subsection: Orient
 108//   Orientation is specified with the `orient` argument in most shape modules.  Specifying `orient`
 109//   when creating an object will rotate the object such that the top of the object will be pointed
 110//   at the vector direction given in the `orient` argument.  Orientation is always applied after
 111//   anchoring and spin.  The constants `UP`, `DOWN`, `FRONT`, `BACK`, `LEFT`, and `RIGHT` can be
 112//   added together to form the directional vector for this.  ie: `LEFT+BACK`
 113// Subsection: Specifying Directions
 114//   You can use direction vectors to specify anchors for objects or to specify edges, faces, and
 115//   corners of cubes.  You can simply specify these direction vectors numerically, but another
 116//   option is to use named constants for direction vectors.  These constants define unit vectors
 117//   for the six axis directions as shown below.
 118// Figure(3D,Big,VPD=6): Named constants for direction vectors.  Some directions have more than one name.  
 119//   $fn=12;
 120//   stroke([[0,0,0],RIGHT], endcap2="arrow2", width=.05);
 121//   color("black")right(.05)up(.05)move(RIGHT) text3d("RIGHT",size=.1,h=.01,anchor=LEFT,orient=FRONT);
 122//   stroke([[0,0,0],LEFT], endcap2="arrow2", width=.05);
 123//   color("black")left(.05)up(.05)move(LEFT) text3d("LEFT",size=.1,h=.01,anchor=RIGHT,orient=FRONT);
 124//   stroke([[0,0,0],FRONT], endcap2="arrow2", width=.05);
 125//   color("black")
 126//   left(.1){
 127//   up(.12)move(FRONT) text3d("FRONT",size=.1,h=.01,anchor=RIGHT,orient=FRONT);
 128//   move(FRONT) text3d("FWD",size=.1,h=.01,anchor=RIGHT,orient=FRONT);
 129//   down(.12)move(FRONT) text3d("FORWARD",size=.1,h=.01,anchor=RIGHT,orient=FRONT);
 130//   }
 131//   stroke([[0,0,0],BACK], endcap2="arrow2", width=.05);
 132//   right(.05)
 133//   color("black")move(BACK) text3d("BACK",size=.1,h=.01,anchor=LEFT,orient=FRONT);
 134//   stroke([[0,0,0],DOWN], endcap2="arrow2", width=.05);
 135//   color("black")
 136//   right(.1){
 137//   up(.12)move(BOT) text3d("DOWN",size=.1,h=.01,anchor=LEFT,orient=FRONT);
 138//   move(BOT) text3d("BOTTOM",size=.1,h=.01,anchor=LEFT,orient=FRONT);
 139//   down(.12)move(BOT) text3d("BOT",size=.1,h=.01,anchor=LEFT,orient=FRONT);
 140//   }
 141//   stroke([[0,0,0],TOP], endcap2="arrow2", width=.05);
 142//   color("black")left(.05){
 143//   up(.12)move(TOP) text3d("TOP",size=.1,h=.01,anchor=RIGHT,orient=FRONT);
 144//   move(TOP) text3d("UP",size=.1,h=.01,anchor=RIGHT,orient=FRONT);
 145//   }
 146// Figure(2D,Big): Named constants for direction vectors in 2D.  For anchors the TOP and BOTTOM directions are collapsed into 2D as shown here, but do not try to use TOP or BOTTOM as 2D directions in other situations.  
 147//   $fn=12;
 148//   stroke(path2d([[0,0,0],RIGHT]), endcap2="arrow2", width=.05);
 149//   color("black")fwd(.22)left(.05)move(RIGHT) text("RIGHT",size=.1,anchor=RIGHT);
 150//   stroke(path2d([[0,0,0],LEFT]), endcap2="arrow2", width=.05);
 151//   color("black")right(.05)fwd(.22)move(LEFT) text("LEFT",size=.1,anchor=LEFT);
 152//   stroke(path2d([[0,0,0],FRONT]), endcap2="arrow2", width=.05);
 153//   color("black")
 154//   fwd(.2)
 155//   right(.15)
 156//   color("black")move(BACK) { text("BACK",size=.1,anchor=LEFT); back(.14) text("(TOP)", size=.1, anchor=LEFT);}
 157//   color("black")
 158//   left(.15)back(.2+.14)move(FRONT){
 159//   back(.14) text("FRONT",size=.1,anchor=RIGHT);
 160//       text("FWD",size=.1,anchor=RIGHT);
 161//   fwd(.14) text("FORWARD",size=.1,anchor=RIGHT);
 162//   fwd(.28) text("(BOTTOM)",size=.1,anchor=RIGHT);
 163//   fwd(.14*3) text("(BOT)",size=.1,anchor=RIGHT);   
 164//   }
 165//   stroke(path2d([[0,0,0],BACK]), endcap2="arrow2", width=.05);
 166// Subsection: Specifying Faces
 167//   Modules operating on faces accept a list of faces to describe the faces to operate on.  Each
 168//   face is given by a vector that points to that face.  Attachments of cuboid objects onto their faces also
 169//   work by choosing an attachment face with a single vector in the same manner.  
 170// Figure(3D,Big,NoScales,VPD=275): The six faces of the cube.  Some have faces have more than one name.  
 171//   ydistribute(50) {
 172//      xdistribute(35){
 173//        _show_cube_faces([BACK], botlabel=["BACK"]);
 174//        _show_cube_faces([UP],botlabel=["TOP","UP"]);
 175//        _show_cube_faces([RIGHT],botlabel=["RIGHT"]);  
 176//      }
 177//      xdistribute(35){
 178//        _show_cube_faces([FRONT],toplabel=["FRONT","FWD", "FORWARD"]);
 179//        _show_cube_faces([DOWN],toplabel=["BOTTOM","BOT","DOWN"]);
 180//        _show_cube_faces([LEFT],toplabel=["LEFT"]);  
 181//      }  
 182//   }
 183// Subsection: Specifying Edges
 184//   Modules operating on edges use two arguments to describe the edge set they will use: The `edges` argument
 185//   is a list of edge set descriptors to include in the edge set, and the `except` argument is a list of
 186//   edge set descriptors to remove from the edge set.
 187//   The default value for `edges` is `"ALL"`, the set of all edges.
 188//   The default value for `except` is the    empty set, meaning no edges are removed. 
 189//   If either argument is just a single edge set
 190//   descriptor it can be passed directly rather than in a singleton list.  
 191//   Each edge set descriptor must be one of:
 192//   - A vector pointing towards an edge, indicating that single edge.
 193//   - A vector pointing towards a face, indicating all edges surrounding that face.
 194//   - A vector pointing towards a corner, indicating all edges touching that corner.
 195//   - The string `"X"`, indicating all X axis aligned edges.
 196//   - The string `"Y"`, indicating all Y axis aligned edges.
 197//   - The string `"Z"`, indicating all Z axis aligned edges.
 198//   - The string `"ALL"`, indicating all edges.
 199//   - The string `"NONE"`, indicating no edges at all.
 200//   - A 3x4 array, where each entry corresponds to one of the 12 edges and is set to 1 if that edge is included and 0 if the edge is not.  The edge ordering is:
 201//       ```
 202//       [
 203//           [Y-Z-, Y+Z-, Y-Z+, Y+Z+],
 204//           [X-Z-, X+Z-, X-Z+, X+Z+],
 205//           [X-Y-, X+Y-, X-Y+, X+Y+]
 206//       ]
 207//       ```
 208//   You can specify edge descriptors directly by giving a vector, or you can use sums of the
 209//   named direction vectors described above.  Below we show all of the edge sets you can
 210//   describe with sums of the direction vectors, and then we show some examples of combining
 211//   edge set descriptors.  
 212// Figure(3D,Big,VPD=300,NoScales): Vectors pointing toward an edge select that single edge
 213//   ydistribute(50) {
 214//       xdistribute(30) {
 215//           _show_edges(edges=BOT+RIGHT);
 216//           _show_edges(edges=BOT+BACK);
 217//           _show_edges(edges=BOT+LEFT);
 218//           _show_edges(edges=BOT+FRONT);
 219//       }
 220//       xdistribute(30) {
 221//           _show_edges(edges=FWD+RIGHT);
 222//           _show_edges(edges=BACK+RIGHT);
 223//           _show_edges(edges=BACK+LEFT);
 224//           _show_edges(edges=FWD+LEFT);
 225//       }
 226//       xdistribute(30) {
 227//           _show_edges(edges=TOP+RIGHT);
 228//           _show_edges(edges=TOP+BACK);
 229//           _show_edges(edges=TOP+LEFT);
 230//           _show_edges(edges=TOP+FRONT);
 231//       }
 232//   }
 233// Figure(3D,Med,VPD=205,NoScales): Vectors pointing toward a face select all edges surrounding that face.
 234//   ydistribute(50) {
 235//       xdistribute(30) {
 236//           _show_edges(edges=LEFT);
 237//           _show_edges(edges=FRONT);
 238//           _show_edges(edges=RIGHT);
 239//       }
 240//       xdistribute(30) {
 241//           _show_edges(edges=TOP);
 242//           _show_edges(edges=BACK);
 243//           _show_edges(edges=BOTTOM);
 244//       }
 245//   }
 246// Figure(3D,Big,VPD=300,NoScales): Vectors pointing toward a corner select all edges surrounding that corner.
 247//   ydistribute(50) {
 248//       xdistribute(30) {
 249//           _show_edges(edges=FRONT+LEFT+TOP);
 250//           _show_edges(edges=FRONT+RIGHT+TOP);
 251//           _show_edges(edges=FRONT+LEFT+BOT);
 252//           _show_edges(edges=FRONT+RIGHT+BOT);
 253//       }
 254//       xdistribute(30) {
 255//           _show_edges(edges=TOP+LEFT+BACK);
 256//           _show_edges(edges=TOP+RIGHT+BACK);
 257//           _show_edges(edges=BOT+LEFT+BACK);
 258//           _show_edges(edges=BOT+RIGHT+BACK);
 259//       }
 260//   }
 261// Figure(3D,Med,VPD=205,NoScales): Named Edge Sets
 262//   ydistribute(50) {
 263//       xdistribute(30) {
 264//           _show_edges(edges="X");
 265//           _show_edges(edges="Y");
 266//           _show_edges(edges="Z");
 267//       }
 268//       xdistribute(30) {
 269//           _show_edges(edges="ALL");
 270//           _show_edges(edges="NONE");
 271//       }
 272//   }
 273// Figure(3D,Big,VPD=310,NoScales):  Next are some examples showing how you can combine edge descriptors to obtain different edge sets.    You can specify the top front edge with a numerical vector or by combining the named direction vectors.  If you combine them as a list you get all the edges around the front or top faces.  Adding `except` removes an edge.  
 274//   xdistribute(43){
 275//     _show_edges(_edges([0,-1,1]),toplabel=["edges=[0,-1,1]"]);
 276//     _show_edges(_edges(TOP+FRONT),toplabel=["edges=TOP+FRONT"]);
 277//     _show_edges(_edges([TOP,FRONT]),toplabel=["edges=[TOP,FRONT]"]);
 278//     _show_edges(_edges([TOP,FRONT],TOP+FRONT),toplabel=["edges=[TOP,FRONT]","except=TOP+FRONT"]);      
 279//   }
 280// Figure(3D,Big,VPD=310,NoScales): Using `except=BACK` removes the four edges surrounding the back face if they are present in the edge set.  In the first example only one edge needs to be removed.  In the second example we remove two of the Z-aligned edges.  The third example removes all four back edges from the default edge set of all edges.  You can explicitly give `edges="ALL"` but it is not necessary, since this is the default.  In the fourth example, the edge set of Y-aligned edges contains no back edges, so the `except` parameter has no effect.  
 281//   xdistribute(43){
 282//     _show_edges(_edges(BOT,BACK), toplabel=["edges=BOT","except=BACK"]);
 283//     _show_edges(_edges("Z",BACK), toplabel=["edges=\"Z\"", "except=BACK"]);
 284//     _show_edges(_edges("ALL",BACK), toplabel=["(edges=\"ALL\")", "except=BACK"]);
 285//     _show_edges(_edges("Y",BACK), toplabel=["edges=\"Y\"","except=BACK"]);   
 286//   }
 287// Figure(3D,Big,NoScales,VPD=310): On the left `except` is a list to remove two edges.  In the center we show a corner edge set defined by a numerical vector, and at the right we remove that same corner edge set with named direction vectors.  
 288//   xdistribute(52){
 289//    _show_edges(_edges("ALL",[FRONT+RIGHT,FRONT+LEFT]),
 290//               toplabel=["except=[FRONT+RIGHT,","       FRONT+LEFT]"]);
 291//    _show_edges(_edges([1,-1,1]),toplabel=["edges=[1,-1,1]"]);             
 292//    _show_edges(_edges([TOP,BOT], TOP+RIGHT+FRONT),toplabel=["edges=[TOP,BOT]","except=TOP+RIGHT+FRONT"]); 
 293//   }             
 294// Subsection: Specifying Corners
 295//   Modules operating on corners use two arguments to describe the corner set they will use: The `corners` argument
 296//   is a list of corner set descriptors to include in the corner set, and the `except` argument is a list of
 297//   corner set descriptors to remove from the corner set.
 298//   The default value for `corners` is `"ALL"`, the set of all corners.
 299//   The default value for `except` is the   empty set, meaning no corners are removed.  
 300//   If either argument is just a single corner set
 301//   descriptor it can be passed directly rather than in a singleton list.
 302//   Each corner set descriptor must be one of:
 303//   - A vector pointing towards a corner, indicating that corner.
 304//   - A vector pointing towards an edge indicating both corners at the ends of that edge.
 305//   - A vector pointing towards a face, indicating all the corners of that face.
 306//   - The string `"ALL"`, indicating all corners.
 307//   - The string `"NONE"`, indicating no corners at all.
 308//   - A length 8 vector where each entry corresponds to a corner and is 1 if the corner is included and 0 if it is excluded.  The corner ordering is
 309//       ```
 310//       [X-Y-Z-, X+Y-Z-, X-Y+Z-, X+Y+Z-, X-Y-Z+, X+Y-Z+, X-Y+Z+, X+Y+Z+]
 311//       ```
 312//   You can specify corner descriptors directly by giving a vector, or you can use sums of the
 313//   named direction vectors described above.  Below we show all of the corner sets you can
 314//   describe with sums of the direction vectors and then we show some examples of combining
 315//   corner set descriptors.  
 316// Figure(3D,Big,NoScales,VPD=300): Vectors pointing toward a corner select that corner.
 317//   ydistribute(55) {
 318//       xdistribute(35) {
 319//           _show_corners(corners=FRONT+LEFT+TOP);
 320//           _show_corners(corners=FRONT+RIGHT+TOP);
 321//           _show_corners(corners=FRONT+LEFT+BOT);
 322//           _show_corners(corners=FRONT+RIGHT+BOT);
 323//       }
 324//       xdistribute(35) {
 325//           _show_corners(corners=TOP+LEFT+BACK);
 326//           _show_corners(corners=TOP+RIGHT+BACK);
 327//           _show_corners(corners=BOT+LEFT+BACK);
 328//           _show_corners(corners=BOT+RIGHT+BACK);
 329//       }
 330//   }
 331// Figure(3D,Big,NoScales,VPD=340): Vectors pointing toward an edge select the corners and the ends of the edge.
 332//   ydistribute(55) {
 333//       xdistribute(35) {
 334//           _show_corners(corners=BOT+RIGHT);
 335//           _show_corners(corners=BOT+BACK);
 336//           _show_corners(corners=BOT+LEFT);
 337//           _show_corners(corners=BOT+FRONT);
 338//       }
 339//       xdistribute(35) {
 340//           _show_corners(corners=FWD+RIGHT);
 341//           _show_corners(corners=BACK+RIGHT);
 342//           _show_corners(corners=BACK+LEFT);
 343//           _show_corners(corners=FWD+LEFT);
 344//       }
 345//       xdistribute(35) {
 346//           _show_corners(corners=TOP+RIGHT);
 347//           _show_corners(corners=TOP+BACK);
 348//           _show_corners(corners=TOP+LEFT);
 349//           _show_corners(corners=TOP+FRONT);
 350//       }
 351//   }
 352// Figure(3D,Med,NoScales,VPD=225): Vectors pointing toward a face select the corners of the face.
 353//   ydistribute(55) {
 354//       xdistribute(35) {
 355//           _show_corners(corners=LEFT);
 356//           _show_corners(corners=FRONT);
 357//           _show_corners(corners=RIGHT);
 358//       }
 359//       xdistribute(35) {
 360//           _show_corners(corners=TOP);
 361//           _show_corners(corners=BACK);
 362//           _show_corners(corners=BOTTOM);
 363//       }
 364//   }
 365// Figure(3D,Med,NoScales,VPD=200): Corners by name
 366//   xdistribute(35) {
 367//       _show_corners(corners="ALL");
 368//       _show_corners(corners="NONE");
 369//   }
 370// Figure(3D,Big,NoScales,VPD=300):     Next are some examples showing how you can combine corner descriptors to obtain different corner sets.   You can specify corner sets numerically or by adding together named directions.  The third example shows a list of two corner specifications, giving all the corners on the front face or the right face.  
 371//   xdistribute(52){
 372//     _show_corners(_corners([1,-1,-1]),toplabel=["corners=[1,-1,-1]"]);
 373//     _show_corners(_corners(BOT+RIGHT+FRONT),toplabel=["corners=BOT+RIGHT+FRONT"]);
 374//     _show_corners(_corners([FRONT,RIGHT]), toplabel=["corners=[FRONT,RIGHT]"]);
 375//   }
 376// Figure(3D,Big,NoScales,VPD=300): Corners for one edge, two edges, and all the edges except the two on one edge.  Note that since the default is all edges, you only need to give the except argument in this case:
 377//    xdistribute(52){
 378//      _show_corners(_corners(FRONT+TOP), toplabel=["corners=FRONT+TOP"]);
 379//       _show_corners(_corners([FRONT+TOP,BOT+BACK]), toplabel=["corners=[FRONT+TOP,","        BOT+BACK]"]);
 380//       _show_corners(_corners("ALL",FRONT+TOP), toplabel=["(corners=\"ALL\")","except=FRONT+TOP"]);
 381//    }
 382// Figure(3D,Med,NoScales,VPD=240): The first example shows a single corner removed from the top corners using a numerical vector.  The second one shows removing a set of two corner descriptors from the implied set of all corners.  
 383//    xdistribute(58){
 384//       _show_corners(_corners(TOP,[1,1,1]), toplabel=["corners=TOP","except=[1,1,1]"]);
 385//       _show_corners(_corners("ALL",[FRONT+RIGHT+TOP,FRONT+LEFT+BOT]),
 386//                    toplabel=["except=[FRONT+RIGHT+TOP,","       FRONT+LEFT+BOT]"]);
 387//    }
 388
 389
 390
 391// Section: Attachment Positioning
 392
 393// Module: position()
 394// Usage:
 395//   position(from) CHILDREN;
 396//
 397// Topics: Attachments
 398// See Also: attachable(), attach(), orient()
 399//
 400// Description:
 401//   Attaches children to a parent object at an anchor point.  For a step-by-step explanation
 402//   of attachments, see the [[Attachments Tutorial|Tutorial-Attachments]].
 403// Arguments:
 404//   from = The vector, or name of the parent anchor point to attach to.
 405// Example:
 406//   spheroid(d=20) {
 407//       position(TOP) cyl(l=10, d1=10, d2=5, anchor=BOTTOM);
 408//       position(RIGHT) cyl(l=10, d1=10, d2=5, anchor=BOTTOM);
 409//       position(FRONT) cyl(l=10, d1=10, d2=5, anchor=BOTTOM);
 410//   }
 411module position(from)
 412{
 413    req_children($children);
 414    assert($parent_geom != undef, "No object to attach to!");
 415    anchors = (is_vector(from)||is_string(from))? [from] : from;
 416    for (anchr = anchors) {
 417        anch = _find_anchor(anchr, $parent_geom);
 418        $attach_to = undef;
 419        $attach_anchor = anch;
 420        $attach_norot = true;
 421        translate(anch[1]) children();
 422    }
 423}
 424
 425
 426// Module: orient()
 427// Usage:
 428//   orient(dir, [spin=]) CHILDREN;
 429//   orient(anchor=, [spin=]) CHILDREN;
 430// Topics: Attachments
 431// Description:
 432//   Orients children such that their top is tilted towards the given direction, or towards the
 433//   direction of a given anchor point on the parent.  For a step-by-step explanation of
 434//   attachments, see the [[Attachments Tutorial|Tutorial-Attachments]].
 435// Arguments:
 436//   dir = The direction to orient towards.
 437//   ---
 438//   anchor = The anchor on the parent which you want to match the orientation of.  Use instead of `dir`.
 439//   spin = The spin to add to the children.  (Overrides anchor spin.)
 440// See Also: attachable(), attach(), orient()
 441// Example: Orienting by Vector
 442//   prismoid([50,50],[30,30],h=40) {
 443//       position(TOP+RIGHT)
 444//           orient(RIGHT)
 445//               prismoid([30,30],[0,5],h=20,anchor=BOT+LEFT);
 446//   }
 447// Example: When orienting to an anchor, the spin of the anchor may cause confusion:
 448//   prismoid([50,50],[30,30],h=40) {
 449//       position(TOP+RIGHT)
 450//           orient(anchor=RIGHT)
 451//               prismoid([30,30],[0,5],h=20,anchor=BOT+LEFT);
 452//   }
 453// Example: You can override anchor spin with `spin=`.
 454//   prismoid([50,50],[30,30],h=40) {
 455//       position(TOP+RIGHT)
 456//           orient(anchor=RIGHT,spin=0)
 457//               prismoid([30,30],[0,5],h=20,anchor=BOT+LEFT);
 458//   }
 459// Example: Or you can anchor the child from the back
 460//   prismoid([50,50],[30,30],h=40) {
 461//       position(TOP+RIGHT)
 462//           orient(anchor=RIGHT)
 463//               prismoid([30,30],[0,5],h=20,anchor=BOT+BACK);
 464//   }
 465module orient(dir, anchor, spin) {
 466    req_children($children);
 467    if (!is_undef(dir)) {
 468        spin = default(spin, 0);
 469        check = 
 470          assert(anchor==undef, "Only one of dir= or anchor= may be given to orient()")
 471          assert(is_vector(dir))
 472          assert(is_finite(spin));
 473        two_d = _attach_geom_2d($parent_geom);
 474        fromvec = two_d? BACK : UP;
 475        rot(spin, from=fromvec, to=dir) children();
 476    } else {
 477        check=
 478          assert(dir==undef, "Only one of dir= or anchor= may be given to orient()")
 479          assert($parent_geom != undef, "No parent to orient from!")
 480          assert(is_string(anchor) || is_vector(anchor));
 481        anch = _find_anchor(anchor, $parent_geom);
 482        two_d = _attach_geom_2d($parent_geom);
 483        fromvec = two_d? BACK : UP;
 484        $attach_to = undef;
 485        $attach_anchor = anch;
 486        $attach_norot = true;
 487        spin = default(spin, anch[3]);
 488        assert(is_finite(spin));
 489        rot(spin, from=fromvec, to=anch[2]) children();
 490    }
 491}
 492
 493
 494
 495// Module: attach()
 496// Usage:
 497//   attach(from, [overlap=], [norot=]) CHILDREN;
 498//   attach(from, to, [overlap=], [norot=]) CHILDREN;
 499// Topics: Attachments
 500// See Also: attachable(), position(), face_profile(), edge_profile(), corner_profile()
 501// Description:
 502//   Attaches children to a parent object at an anchor point and orientation.  Attached objects will
 503//   be overlapped into the parent object by a little bit, as specified by the `$overlap`
 504//   value (0 by default), or by the overriding `overlap=` argument.  This is to prevent OpenSCAD
 505//   from making non-manifold objects.  You can define `$overlap=` as an argument in a parent
 506//   module to set the default for all attachments to it.  For a step-by-step explanation of
 507//   attachments, see the [[Attachments Tutorial|Tutorial-Attachments]].
 508// Arguments:
 509//   from = The vector, or name of the parent anchor point to attach to.
 510//   to = Optional name of the child anchor point.  If given, orients the child such that the named anchors align together rotationally.
 511//   ---
 512//   overlap = Amount to sink child into the parent.  Equivalent to `down(X)` after the attach.  This defaults to the value in `$overlap`, which is `0` by default.
 513//   norot = If true, don't rotate children when attaching to the anchor point.  Only translate to the anchor point.
 514// Example:
 515//   spheroid(d=20) {
 516//       attach(TOP) down(1.5) cyl(l=11.5, d1=10, d2=5, anchor=BOTTOM);
 517//       attach(RIGHT, BOTTOM) down(1.5) cyl(l=11.5, d1=10, d2=5);
 518//       attach(FRONT, BOTTOM, overlap=1.5) cyl(l=11.5, d1=10, d2=5);
 519//   }
 520module attach(from, to, overlap, norot=false)
 521{
 522    req_children($children);
 523    assert($parent_geom != undef, "No object to attach to!");
 524    overlap = (overlap!=undef)? overlap : $overlap;
 525    anchors = (is_vector(from)||is_string(from))? [from] : from;
 526    for (anchr = anchors) {
 527        anch = _find_anchor(anchr, $parent_geom);
 528        two_d = _attach_geom_2d($parent_geom);
 529        $attach_to = to;
 530        $attach_anchor = anch;
 531        $attach_norot = norot;
 532        olap = two_d? [0,-overlap,0] : [0,0,-overlap];
 533        if (norot || (norm(anch[2]-UP)<1e-9 && anch[3]==0)) {
 534            translate(anch[1]) translate(olap) children();
 535        } else {
 536            fromvec = two_d? BACK : UP;
 537            translate(anch[1]) rot(anch[3],from=fromvec,to=anch[2]) translate(olap) children();
 538        }
 539    }
 540}
 541
 542// Section: Tagging
 543
 544// Module: tag()
 545// Usage:
 546//   tag(tag) CHILDREN;
 547// Topics: Attachments
 548// See Also: force_tag(), recolor(), hide(), show_only(), diff(), intersect()
 549// Description:
 550//   Assigns the specified tag to all of the children. Note that if you want
 551//   to apply a tag to non-tag-aware objects you need to use {{force_tag()}} instead.
 552//   This works by setting the `$tag` variable, but it provides extra error checking and
 553//   handling of scopes.  You may set `$tag` directly yourself, but this is not recommended. 
 554//   .
 555//   For a step-by-step explanation of attachments, see the [[Attachments Tutorial|Tutorial-Attachments]].
 556// Arguments:
 557//   tag = tag string, which must not contain any spaces.
 558// Side Effects:
 559//   Sets `$tag` to the tag you specify, possibly with a scope prefix. 
 560// Example(3D):  Applies the tag to both cuboids instead of having to repeat `$tag="remove"` for each one. 
 561//   diff("remove")
 562//     cuboid(10){
 563//       position(TOP) cuboid(3);
 564//       tag("remove")
 565//       {
 566//         position(FRONT) cuboid(3);
 567//         position(RIGHT) cuboid(3);
 568//       }
 569//     }  
 570module tag(tag)
 571{
 572    req_children($children);
 573    check=
 574      assert(is_string(tag),"tag must be a string")
 575      assert(undef==str_find(tag," "),str("Tag string \"",tag,"\" contains a space, which is not allowed"));
 576    $tag = str($tag_prefix,tag);
 577    children();
 578}
 579
 580
 581// Module: force_tag()
 582// Usage:
 583//   force_tag([tag]) CHILDREN;
 584// Topics: Attachments
 585// See Also: tag(), recolor(), hide(), show_only(), diff(), intersect()
 586// Description:
 587//   You use this module when you want to make a non-attachable or non-BOSL2 module respect tags.
 588//   It applies to its children the tag specified (or the tag currently in force if you don't specify a tag),
 589//   making a final determination about whether to show or hide the children.
 590//   This means that tagging in children's children will be ignored.
 591//   This module is specifically provided for operating on children that are not tag aware such as modules
 592//   that don't use {{attachable()}} or built in modules such as 
 593//   - `polygon()`
 594//   - `projection()`
 595//   - `polyhedron()`  (or use [`vnf_polyhedron()`](vnf.scad#vnf_polyhedron))
 596//   - `linear_extrude()`  (or use [`linear_sweep()`](regions.scad#linear_sweep))
 597//   - `rotate_extrude()`
 598//   - `surface()`
 599//   - `import()`
 600//   - `difference()`
 601//   - `intersection()`
 602//   - `hull()`
 603//   .
 604//   When you use tag-based modules like {{diff()}} with a non-attachable module, the result may be puzzling.
 605//   Any time a test occurs for display of child() that test will succeed.  This means that when diff() checks
 606//   to see if it should show a module it will show it, and when diff() checks to see if it should subtract the module
 607//   it will subtract it.  The result will be a hole, possibly with zero-thickness edges or faces.  In order to
 608//   get the correct behavior, every non-attachable module needs an invocation of force_tag, even ones
 609//   that are not tagged.
 610//   .
 611//   For a step-by-step explanation of attachments, see the [[Attachments Tutorial|Tutorial-Attachments]].
 612// Arguments:
 613//   tag = tag string, which must not contain any spaces
 614// Side Effects:
 615//   Sets `$tag` to the tag you specify, possibly with a scope prefix. 
 616// Example(2D): This example produces the full square without subtracting the "remove" item.  When you use non-attachable modules with tags, results are unpredictable.
 617//   diff()
 618//   {
 619//     polygon(square(10));
 620//     move(-[.01,.01])polygon(square(5),$tag="remove");
 621//   }
 622// Example(2D): Adding force_tag() fixes the model.  Note you need to add it to *every* non-attachable module, even the untagged ones, as shown here.  
 623//   diff()
 624//   {
 625//     force_tag()
 626//       polygon(square(10));
 627//     force_tag("remove")
 628//       move(-[.01,.01])polygon(square(5));
 629//   }
 630module force_tag(tag)
 631{
 632    req_children($children);
 633    check1=assert(is_undef(tag) || is_string(tag),"tag must be a string");
 634    $tag = str($tag_prefix,default(tag,$tag));
 635    assert(undef==str_find($tag," "),str("Tag string \"",$tag,"\" contains a space, which is not allowed"));
 636    if(_is_shown())
 637      show_all()
 638        children();
 639}
 640
 641
 642
 643// Module: default_tag()
 644// Usage:
 645//   default_tag(tag) CHILDREN;
 646// Topics: Attachments
 647// See Also: force_tag(), recolor(), hide(), show_only(), diff(), intersect()
 648// Description:
 649//   Sets a default tag for all of the children.  This is intended to be used to set a tag for a whole module
 650//   that is then used outside the module, such as setting the tag to "remove" for easy operation with {{diff()}}.
 651//   The default_tag() module sets the `$tag` variable only if it is not already
 652//   set so you can have a module set a default tag of "remove" but that tag can be overridden by a {{tag()}}
 653//   in force from a parent.  If you use {{tag()}} it will override any previously
 654//   specified tag from a parent, which can be very confusing to a user trying to change the tag on a module.  
 655//   .
 656//   For a step-by-step explanation of attachments, see the [[Attachments Tutorial|Tutorial-Attachments]].
 657// Arguments:
 658//   tag = tag string, which must not contain any spaces.
 659// Side Effects:
 660//   Sets `$tag` to the tag you specify, possibly with a scope prefix. 
 661// Example(3D):  The module thing() is defined with {{tag()}} and the user applied tag of "keep_it" is ignored, leaving the user puzzled.  
 662//   module thing() { tag("remove") cuboid(10);}
 663//   diff()
 664//     cuboid(20){
 665//       position(TOP) thing();
 666//       position(RIGHT) tag("keep_it") thing();
 667//   }
 668// Example(3D):  Using default_tag() fixes this problem: the user applied tag does not get overridden by the tag hidden in the module definition.
 669//   module thing() { default_tag("remove") cuboid(10);}
 670//   diff()
 671//     cuboid(20){
 672//       position(TOP) thing();
 673//       position(RIGHT) tag("keep_it") thing();
 674//   }
 675module default_tag(tag)
 676{
 677    if ($tag=="") tag(tag) children();
 678    else children();
 679}
 680
 681
 682// Module: tag_scope()
 683// Usage:
 684//   tag_scope([scope]) CHILDREN;
 685// Description:
 686//   Creates a tag scope with locally altered tag names to avoid tag name conflict with other code.
 687//   This is necessary when writing modules because the module's caller might happen to use the same tags.
 688//   Note that if you directly set the `$tag` variable then tag scoping will not work correctly.  
 689// Example: In this example the ring module uses "remove" tags which will conflict with use of the same tags by the parent.  
 690//   module ring(r,h,w=1,anchor,spin,orient)
 691//   {
 692//     tag_scope("ringscope")
 693//       attachable(anchor,spin,orient,r=r,h=h){
 694//         diff()
 695//           cyl(r=r,h=h)
 696//             tag("remove") cyl(r=r-w,h=h+1);
 697//         children();
 698//       }
 699//   }
 700//   // Calling the module using "remove" tags
 701//   // will conflict with internal tag use in
 702//   // the ring module.
 703//   $fn=32;
 704//   diff(){
 705//       ring(10,7,w=4);
 706//       tag("remove")ring(8,8);
 707//       tag("remove")diff("rem"){
 708//          ring(9.5,8,w=1);
 709//          tag("rem")ring(9.5,8,w=.3);
 710//       }
 711//     }
 712module tag_scope(scope){
 713  req_children($children);
 714  scope = is_undef(scope) ? rand_str(20) : scope;
 715  assert(is_string(scope), "scope must be a string");
 716  assert(undef==str_find(scope," "),str("Scope string \"",scope,"\" contains a space, which is not allowed"));
 717  $tag_prefix=scope;
 718  children();
 719}  
 720
 721
 722// Section: Attachment Modifiers 
 723
 724// Module: diff()
 725// Usage:
 726//   diff([remove], [keep]) CHILDREN;
 727// Topics: Attachments
 728// See Also: tag(), force_tag(), recolor(), show_only(), hide(), tag_diff(), intersect(), tag_intersect()
 729// Description:
 730//   Perform a differencing operation using tags to control what happens.  This is specifically intended to
 731//   address the situation where you want differences between a parent and child object, something
 732//   that is impossible with the native difference() module.  
 733//   The children to diff are grouped into three categories, regardless of nesting level.
 734//   The `remove` argument is a space delimited list of tags specifying objects to
 735//   subtract.  The `keep` argument is a similar list of tags giving objects to be kept.
 736//   Objects not matching either the `remove` or `keep` lists form the third category of base objects.
 737//   To produce its output, diff() forms the union of all the base objects and then 
 738//   subtracts all the objects with tags in `remove`.  Finally it adds in objects listed in `keep`.
 739//   Attachable objects should be tagged using {{tag()}}
 740//   and non-attachable objects with {{force_tag()}}.  
 741//   . 
 742//   For a step-by-step explanation of attachments, see the [[Attachments Tutorial|Tutorial-Attachments]].
 743// Arguments:
 744//   remove = String containing space delimited set of tag names of children to difference away.  Default: `"remove"`
 745//   keep = String containing space delimited set of tag names of children to keep; that is, to union into the model after differencing is completed.  Default: `"keep"`
 746// Example: Diffing using default tags
 747//   diff()
 748//   cuboid(50) {
 749//       tag("remove") attach(TOP) sphere(d=40);
 750//       tag("keep") attach(CTR) cylinder(h=40, d=10);
 751//   }
 752// Example: The "hole" items are subtracted from everything else.  The other tags can be anything you find convenient.  
 753//   diff("hole")
 754//     tag("body")sphere(d=100) {
 755//       tag("pole") zcyl(d=55, h=100);  // attach() not needed for center-to-center.
 756//       tag("hole") {
 757//          xcyl(d=55, h=101);
 758//          ycyl(d=55, h=101);
 759//       }
 760//       tag("axle")zcyl(d=15, h=140);
 761//     }
 762// Example:
 763//   diff(keep="axle")
 764//   sphere(d=100) {
 765//       tag("axle")xcyl(d=40, l=120);
 766//       tag("remove")cuboid([40,120,100]);
 767//   }
 768// Example: Masking
 769//   diff()
 770//   cube([80,90,100], center=true) {
 771//       edge_mask(FWD)
 772//           rounding_edge_mask(l=max($parent_size)*1.01, r=25);
 773//   }
 774// Example: Here we subtract the parent object from the child.  Because tags propagate to children we need to clear the "remove" tag from the child. 
 775//  diff()
 776//     tag("remove")cuboid(10)
 777//       tag("")position(RIGHT+BACK)cyl(r=8,h=9);
 778// Example(3D,VPR=[104,0,200], VPT=[-0.9,3.03, -0.74], VPD=19,NoAxes,NoScales): A pipe module that subtracts its interior when you call it using diff().  Normally if you union two pipes together, you'll get interfering walls at the intersection, but not here:
 779//   $fn=16;
 780//   // This module must be called by subtracting with "diff"
 781//   module pipe(length, od, id) {
 782//       // Strip the tag the user is using to subtract
 783//       tag("")cylinder(h=length, d=od, center=true);
 784//       // Leave the tag along here, so this one is removed
 785//       cylinder(h=length+.02, d=id, center=true);
 786//   }
 787//   // Draw some intersecting pipes
 788//   diff(){
 789//     tag("remove"){
 790//       pipe(length=5, od=2, id=1.9);
 791//       zrot(10)xrot(75)
 792//         pipe(length=5, od=2, id=1.9);
 793//     }
 794//     // The orange bar has its center removed
 795//     color("orange") down(1) xcyl(h=8, d=1);
 796//     // "keep" prevents interior of the blue bar intact
 797//     tag("keep") recolor("blue") up(1) xcyl(h=8, d=1);  
 798//   }
 799//   // Objects outside the diff don't have pipe interiors removed
 800//   color("purple") down(2.2) ycyl(h=8, d=0.3);
 801// Example(3D,NoScales,NoAxes): Nested diff() calls work as expected, but be careful of reusing tag names, even hidden in submodules.
 802//   $fn=32;
 803//   diff("rem1")
 804//   cyl(r=10,h=10){
 805//     diff("rem2",$tag="rem1"){
 806//       cyl(r=8,h=11);
 807//       tag("rem2")diff("rem3"){
 808//           cyl(r=6,h=12);
 809//           tag("rem3")cyl(r=4,h=13);
 810//           }
 811//       }
 812//   }
 813// Example: This example shows deep nesting, where all the differences cross levels.  Unlike the preceding example, each cylinder is positioned relative to its parent.  Note that it suffices to use two remove tags, alternating between them at each level.
 814//   $fn=32;
 815//   diff("remA")
 816//     cyl(r=9, h=6)
 817//       tag("remA")diff("remB")
 818//         left(.2)position(RIGHT)cyl(r=8,h=7,anchor=RIGHT)
 819//           tag("remB")diff("remA")
 820//            left(.2)position(LEFT)cyl(r=7,h=7,anchor=LEFT)
 821//              tag("remA")diff("remB")
 822//                left(.2)position(LEFT)cyl(r=6,h=8,anchor=LEFT)
 823//                  tag("remB")diff("remA")
 824//                    right(.2)position(RIGHT)cyl(r=5,h=9,anchor=RIGHT)
 825//                      tag("remA")diff("remB")
 826//                        right(.2)position(RIGHT)cyl(r=4,h=10,anchor=RIGHT)
 827//                          tag("remB")left(.2)position(LEFT)cyl(r=3,h=11,anchor=LEFT);
 828// Example(3D,NoAxes,NoScales): When working with Non-Attachables like rotate_extrude() you must apply {{force_tag()}} to every non-attachable object.  
 829//   back_half()
 830//     diff("remove")
 831//       cuboid(40) {
 832//         attach(TOP)
 833//           recolor("lightgreen")
 834//             cyl(l=10,d=30);
 835//         position(TOP+RIGHT)
 836//           force_tag("remove")
 837//             xrot(90)
 838//               rotate_extrude()
 839//                 right(20)
 840//                   circle(5);
 841//       }
 842// Example: Here is another example where two children are intersected using the native intersection operator, and then tagged with {{force_tag()}}.  Note that because the children are at the same level, you don't need to use a tagged operator for their intersection.  
 843//  $fn=32;
 844//  diff()
 845//    cuboid(10){
 846//      force_tag("remove")intersection()
 847//        {
 848//          position(RIGHT) cyl(r=7,h=15);
 849//          position(LEFT) cyl(r=7,h=15);
 850//        }
 851//      tag("keep")cyl(r=1,h=9);
 852//    }
 853// Example: In this example the children that are subtracted are each at different nesting levels, with a kept object in between.
 854//   $fn=32;
 855//   diff()
 856//     cuboid(10){
 857//       tag("remove")cyl(r=4,h=11)
 858//         tag("keep")cyl(r=3,h=17)
 859//           tag("remove")position(RIGHT)cyl(r=2,h=18);
 860//     }
 861// Example: Combining tag operators can be tricky.  Here the `diff()` operation keeps two tags, "fullkeep" and "keep".  Then {{intersect()}} intersects the "keep" tagged item with everything else, but keeps the "fullkeep" object.  
 862//   $fn=32;
 863//   intersect("keep","fullkeep")
 864//     diff(keep="fullkeep keep")
 865//       cuboid(10){
 866//         tag("remove")cyl(r=4,h=11);
 867//         tag("keep") position(RIGHT)cyl(r=8,h=12);
 868//         tag("fullkeep")cyl(r=1,h=12);
 869//     }
 870// Example: In this complex example we form an intersection, subtract an object, and keep some objects.  Note that for the small cylinders on either side, marking them as "keep" or removing their tag gives the same effect.  This is because without a tag they become part of the intersection and the result ends up the same.  For the two cylinders at the back, however, the result is different.  With "keep" the cylinder on the left appears whole, but without it, the cylinder at the back right is subject to intersection.
 871//   $fn=64;
 872//   diff()
 873//     intersect(keep="remove keep")
 874//       cuboid(10,$thing="cube"){
 875//         tag("intersect"){
 876//           position(RIGHT) cyl(r=5.5,h=15)
 877//              tag("")cyl(r=2,h=10);
 878//           position(LEFT) cyl(r=5.54,h=15)
 879//              tag("keep")cyl(r=2,h=10);
 880//         }
 881//         // Untagged it is in the intersection
 882//         tag("") position(BACK+RIGHT)
 883//           cyl(r=2,h=10,anchor=CTR);
 884//         // With keep the full cylinder appears
 885//         tag("keep") position(BACK+LEFT)
 886//           cyl(r=2,h=10,anchor=CTR);
 887//         tag("remove") cyl(r=3,h=15);
 888//       }
 889module diff(remove="remove", keep="keep")
 890{
 891    req_children($children);
 892    assert(is_string(remove),"remove must be a string of tags");
 893    assert(is_string(keep),"keep must be a string of tags"); 
 894    if (_is_shown())
 895    {      
 896        difference() {
 897            hide(str(remove," ",keep)) children();
 898            show_only(remove) children();
 899        }
 900    }
 901    show_int(keep)children();        
 902}
 903
 904
 905// Module: tag_diff()
 906// Usage:
 907//   tag_diff(tag, [remove], [keep]) CHILDREN;
 908// Topics: Attachments
 909// See Also: tag(), force_tag(), recolor(), show_only(), hide(), diff(), intersect(), tag_intersect()
 910// Description:
 911//   Perform a differencing operation in the manner of {{diff()}} using tags to control what happens,
 912//   and then tag the resulting difference object with the specified tag.  This forces the specified
 913//   tag to be resolved at the level of the difference operation.  In most cases, this is not necessary,
 914//   but if you have kept objects and want to operate on this difference object as a whole object using
 915//   more tag operations, you will probably not get the results you want if you simply use {{tag()}}.  
 916//   . 
 917//   For a step-by-step explanation of attachments, see the [[Attachments Tutorial|Tutorial-Attachments]].
 918// Arguments:
 919//   tag = Tag string to apply to this difference object
 920//   remove = String containing space delimited set of tag names of children to difference away.  Default: `"remove"`
 921//   keep = String containing space delimited set of tag names of children to keep; that is, to union into the model after differencing is completed.  Default: `"keep"`
 922// Side Effects:
 923//   Sets `$tag` to the tag you specify, possibly with a scope prefix. 
 924// Example: In this example we have a difference with a kept object that is then subtracted from a cube, but we don't want the kept object to appear in the final output, so this result is wrong:
 925//   diff("rem"){
 926//     cuboid([20,10,30],anchor=FRONT);
 927//     tag("rem")diff("remove","keep"){
 928//       cuboid([10,10,20]);
 929//       tag("remove")cuboid([11,11,5]);
 930//       tag("keep")cuboid([2,2,20]);
 931//     }
 932//   }
 933// Example: Using tag_diff corrects the problem:
 934//   diff("rem"){
 935//     cuboid([20,10,30],anchor=FRONT);
 936//       tag_diff("rem","remove","keep"){
 937//         cuboid([10,10,20]);
 938//         tag("remove")cuboid([11,11,5]);
 939//         tag("keep")cuboid([2,2,20]);
 940//       }
 941//   }
 942// Example: This concentric cylinder example uses "keep" and produces the wrong result.  The kept cylinder gets kept in the final output instead of subtracted.  This happens even when we make sure to change the `keep` argument at the top level {{diff()}} call.  
 943//   diff("rem","nothing")
 944//     cyl(r=8,h=6)
 945//       tag("rem")diff()
 946//         cyl(r=7,h=7)
 947//           tag("remove")cyl(r=6,h=8)
 948//           tag("keep")cyl(r=5,h=9);
 949// Example: Changing to tag_diff() causes the kept cylinder to be subtracted, producing the desired result:
 950//   diff("rem")
 951//     cyl(r=8,h=6)
 952//       tag_diff("rem")
 953//         cyl(r=7,h=7)
 954//           tag("remove")cyl(r=6,h=8)
 955//           tag("keep")cyl(r=5,h=9);
 956// 
 957module tag_diff(tag,remove="remove", keep="keep")
 958{
 959    req_children($children);
 960    assert(is_string(remove),"remove must be a string of tags");
 961    assert(is_string(keep),"keep must be a string of tags");
 962    assert(is_string(tag),"tag must be a string");
 963    assert(undef==str_find(tag," "),str("Tag string \"",tag,"\" contains a space, which is not allowed"));   
 964    $tag=str($tag_prefix,tag);
 965    if (_is_shown())
 966      show_all(){
 967         difference() {
 968            hide(str(remove," ",keep)) children();
 969            show_only(remove) children();
 970         }
 971         show_only(keep)children();        
 972      }
 973}
 974
 975
 976// Module: intersect()
 977// Usage:
 978//   intersect([intersect], [keep]) CHILDREN;
 979// Topics: Attachments
 980// See Also: tag(), force_tag(), recolor(), show_only(), hide(), diff(), tag_diff(), tag_intersect()
 981// Description:
 982//   Performs an intersection operation on its children, using tags to
 983//   determine what happens.  This is specifically intended to address
 984//   the situation where you want intersections involving a parent and
 985//   child object, something that is impossible with the native
 986//   intersection() module.  This module treats the children in three
 987//   groups: objects matching the tags listed in `intersect`, objects
 988//   matching tags listed in `keep`, and the remaining objects that
 989//   don't match any of the listed tags.  The intersection is computed
 990//   between the union of the `intersect` tagged objects and union of the objects that don't
 991//   match any of the listed tags.  Finally the objects listed in `keep` are
 992//   unioned with the result.  Attachable objects should be tagged using {{tag()}}
 993//   and non-attachable objects with {{force_tag()}}.  
 994//   .
 995//   For a step-by-step explanation of attachments, see the [[Attachments Tutorial|Tutorial-Attachments]].
 996// Arguments:
 997//   intersect = String containing space delimited set of tag names of children to intersect.  Default: "intersect"
 998//   keep = String containing space delimited set of tag names of children to keep whole.  Default: "keep"
 999// Example:
1000//   intersect("mask", keep="axle")
1001//     sphere(d=100) {
1002//         tag("mask")cuboid([40,100,100]);
1003//         tag("axle")xcyl(d=40, l=100);
1004//     } 
1005// Example: Combining tag operators can be tricky.  Here the {{diff()}} operation keeps two tags, "fullkeep" and "keep".  Then `intersect()` intersects the "keep" tagged item with everything else, but keeps the "fullkeep" object.  
1006//   $fn=32;
1007//   intersect("keep","fullkeep")
1008//     diff(keep="fullkeep keep")
1009//       cuboid(10){
1010//         tag("remove")cyl(r=4,h=11);
1011//         tag("keep") position(RIGHT)cyl(r=8,h=12);
1012//         tag("fullkeep")cyl(r=1,h=12);
1013//     }
1014// Example: In this complex example we form an intersection, subtract an object, and keep some objects.  Note that for the small cylinders on either side, marking them as "keep" or removing their tag gives the same effect.  This is because without a tag they become part of the intersection and the result ends up the same.  For the two cylinders at the back, however, the result is different.  With "keep" the cylinder on the left appears whole, but without it, the cylinder at the back right is subject to intersection.
1015//   $fn=64;
1016//   diff()
1017//     intersect(keep="remove keep")
1018//       cuboid(10,$thing="cube"){
1019//         tag("intersect"){
1020//           position(RIGHT) cyl(r=5.5,h=15)
1021//              tag("")cyl(r=2,h=10);
1022//           position(LEFT) cyl(r=5.54,h=15)
1023//              tag("keep")cyl(r=2,h=10);
1024//         }
1025//         // Untagged it is in the intersection
1026//         tag("") position(BACK+RIGHT)
1027//           cyl(r=2,h=10,anchor=CTR);
1028//         // With keep the full cylinder appears
1029//         tag("keep") position(BACK+LEFT)
1030//           cyl(r=2,h=10,anchor=CTR);
1031//         tag("remove") cyl(r=3,h=15);
1032//       }
1033module intersect(intersect="intersect",keep="keep")
1034{
1035   assert(is_string(intersect),"intersect must be a string of tags");
1036   assert(is_string(keep),"keep must be a string of tags");
1037   intersection(){
1038      show_only(intersect) children();
1039      hide(str(intersect," ",keep)) children();
1040   }
1041   show_int(keep) children();
1042}   
1043
1044
1045// Module: tag_intersect()
1046// Usage:
1047//   tag_intersect(tag, [intersect], [keep]) CHILDREN;
1048// Topics: Attachments
1049// See Also: tag(), force_tag(), recolor(), show_only(), hide(), diff(), tag_diff(), intersect()
1050// Description:
1051//   Perform an intersection operation in the manner of {{intersect()}} using tags to control what happens,
1052//   and then tag the resulting difference object with the specified tag.  This forces the specified
1053//   tag to be resolved at the level of the intersect operation.  In most cases, this is not necessary,
1054//   but if you have kept objects and want to operate on this difference object as a whole object using
1055//   more tag operations, you will probably not get the results you want if you simply use {{tag()}}.  
1056//   .
1057//   For a step-by-step explanation of attachments, see the [[Attachments Tutorial|Tutorial-Attachments]].
1058// Arguments:
1059//   tag = Tag to set for the intersection
1060//   intersect = String containing space delimited set of tag names of children to intersect.  Default: "intersect"
1061//   keep = String containing space delimited set of tag names of children to keep whole.  Default: "keep"
1062// Side Effects:
1063//   Sets `$tag` to the tag you specify, possibly with a scope prefix. 
1064// Example:  Without `tag_intersect()` the kept object is not included in the difference.  
1065//   $fn=32;
1066//   diff()
1067//     cuboid([20,15,9])
1068//     tag("remove")intersect()
1069//       cuboid(10){
1070//         tag("intersect")position(RIGHT) cyl(r=7,h=10);
1071//         tag("keep")position(LEFT)cyl(r=4,h=10);
1072//       }
1073// Example: Using tag_intersect corrects the problem. 
1074//   $fn=32;
1075//   diff()
1076//     cuboid([20,15,9])
1077//     tag_intersect("remove")
1078//       cuboid(10){
1079//         tag("intersect")position(RIGHT) cyl(r=7,h=10);
1080//         tag("keep")position(LEFT)cyl(r=4,h=10);
1081//       } 
1082module tag_intersect(tag,intersect="intersect",keep="keep")
1083{
1084   assert(is_string(intersect),"intersect must be a string of tags");
1085   assert(is_string(keep),"keep must be a string of tags");
1086   assert(is_string(tag),"tag must be a string");
1087   assert(undef==str_find(tag," "),str("Tag string \"",tag,"\" contains a space, which is not allowed"));   
1088   $tag=str($tag_prefix,tag);
1089   if (_is_shown())
1090     show_all(){
1091       intersection(){
1092          show_only(intersect) children();
1093          hide(str(intersect," ",keep)) children();
1094       }
1095       show_only(keep) children();
1096   }
1097}   
1098
1099
1100// Module: conv_hull()
1101// Usage:
1102//   conv_hull([keep]) CHILDREN;
1103// Topics: Attachments
1104// See Also: tag(), recolor(), show_only(), hide(), diff(), intersect()
1105// Description:
1106//   Performs a hull operation on the children using tags to determine what happens.  The items
1107//   not tagged with the `keep` tags are combined into a convex hull, and the children tagged with the keep tags
1108//   are unioned with the result.  
1109//   .
1110//   For a step-by-step explanation of attachments, see the [[Attachments Tutorial|Tutorial-Attachments]].
1111// Arguments:
1112//   keep = String containing space delimited set of tag names of children to keep out of the hull.  Default: "keep"
1113// Example:  
1114//   conv_hull("keep")
1115//      sphere(d=100, $fn=64) {
1116//        cuboid([40,90,90]); 
1117//        tag("keep")xcyl(d=40, l=120);
1118//      }
1119// Example: difference combined with hull where all objects are relative to each other.
1120//   $fn=32;
1121//   diff()
1122//     conv_hull("remove")
1123//       cuboid(10)
1124//         position(RIGHT+BACK)cyl(r=4,h=10)
1125//           tag("remove")cyl(r=2,h=12);
1126module conv_hull(keep="keep")
1127{  
1128    req_children($children);
1129    assert(is_string(keep),"keep must be a string of tags");
1130    if (_is_shown())
1131        hull() hide(keep) children();
1132    show_int(keep) children();
1133}
1134
1135
1136// Module: tag_conv_hull()
1137// Usage:
1138//   tag_conv_hull(tag, [keep]) CHILDREN;
1139// Topics: Attachments
1140// See Also: tag(), recolor(), show_only(), hide(), diff(), intersect()
1141// Description:
1142//   Perform a convex hull operation in the manner of {{conv_hull()}} using tags to control what happens,
1143//   and then tag the resulting hull object with the specified tag.  This forces the specified
1144//   tag to be resolved at the level of the hull operation.  In most cases, this is not necessary,
1145//   but if you have kept objects and want to operate on the hull object as a whole object using
1146//   more tag operations, you will probably not get the results you want if you simply use {{tag()}}.  
1147//   .
1148//   For a step-by-step explanation of attachments, see the [[Attachments Tutorial|Tutorial-Attachments]].
1149// Arguments:
1150//   keep = String containing space delimited set of tag names of children to keep out of the hull.  Default: "keep"
1151// Side Effects:
1152//   Sets `$tag` to the tag you specify, possibly with a scope prefix. 
1153// Example: With a regular tag, the kept object is not handled as desired:
1154//   diff(){
1155//      cuboid([30,30,9])
1156//        tag("remove")conv_hull("remove")       
1157//          cuboid(10,anchor=LEFT+FRONT){
1158//            position(RIGHT+BACK)cyl(r=4,h=10);
1159//            tag("keep")position(FRONT+LEFT)cyl(r=4,h=10);         
1160//          }
1161//   }
1162// Example: Using `tag_conv_hull()` fixes the problem:
1163//   diff(){
1164//      cuboid([30,30,9])
1165//        tag_conv_hull("remove")       
1166//          cuboid(10,anchor=LEFT+FRONT){
1167//            position(RIGHT+BACK)cyl(r=4,h=10);
1168//            tag("keep")position(FRONT+LEFT)cyl(r=4,h=10);         
1169//          }
1170//   }
1171module tag_conv_hull(tag,keep="keep")
1172{
1173    req_children($children);
1174    assert(is_string(keep),"keep must be a string of tags");
1175    assert(is_string(tag),"tag must be a string");
1176    assert(undef==str_find(tag," "),str("Tag string \"",tag,"\" contains a space, which is not allowed"));   
1177    $tag=str($tag_prefix,tag);
1178    if (_is_shown())
1179      show_all(){
1180        hull() hide(keep) children();
1181        show_only(keep) children();
1182      }
1183}    
1184
1185
1186// Module: hide()
1187// Usage:
1188//   hide(tags) CHILDREN;
1189// Topics: Attachments
1190// See Also: tag(), recolor(), show_only(), diff(), intersect()
1191// Description:
1192//   Hides all attachable children with the given tags, which you supply as a space separated string. Previously hidden objects remain hidden, so hiding is cumulative, unlike `show_only()`.  
1193//   For a step-by-step explanation of attachments, see the [[Attachments Tutorial|Tutorial-Attachments]].
1194// Side Effects:
1195//   Sets `$tags_hidden` to include the tags you specify. 
1196// Example:  Hides part of the model.  
1197//   hide("A")
1198//     tag("main") cube(50, anchor=CENTER, $tag="Main") {
1199//       tag("A")attach(LEFT, BOTTOM) cylinder(d=30, l=30);
1200//       tag("B")attach(RIGHT, BOTTOM) cylinder(d=30, l=30);
1201//     }
1202// Example: Use an invisible parent to position children.  Note that children must be retagged because they inherit the parent tag.
1203//   $fn=16;
1204//   hide("hidden")
1205//     tag("hidden")cuboid(10)
1206//       tag("visible") {
1207//         position(RIGHT) cyl(r=1,h=12);
1208//         position(LEFT) cyl(r=1,h=12);
1209//       }  
1210module hide(tags)
1211{
1212    req_children($children);
1213    dummy=assert(is_string(tags), "tags must be a string");
1214    taglist = [for(s=str_split(tags," ",keep_nulls=false)) str($tag_prefix,s)];
1215    $tags_hidden = concat($tags_hidden,taglist);
1216    children();
1217}
1218
1219
1220// Module: show_only()
1221// Usage:
1222//   show_only(tags) CHILDREN;
1223// Topics: Attachments
1224// See Also: tag(), recolor(), hide(), diff(), intersect()
1225// Description:
1226//   Show only the children with the listed tags, which you sply as a space separated string.  Only unhidden objects will be shown, so if an object is hidden either before or after the `show_only()` call then it will remain hidden.  This overrides any previous `show_only()` calls.  Unlike `hide()`, calls to `show_only()` are not cumulative.  
1227//   For a step-by-step explanation of attachments, see the [[Attachments Tutorial|Tutorial-Attachments]].
1228// Side Effects:
1229//   Sets `$tags_shown` to the tag you specify. 
1230// Example:  Display the attachments but not the parent
1231//   show_only("visible")
1232//     cube(50, anchor=CENTER) 
1233//       tag("visible"){
1234//         attach(LEFT, BOTTOM) cylinder(d=30, l=30);
1235//         attach(RIGHT, BOTTOM) cylinder(d=30, l=30);
1236//       }
1237module show_only(tags)
1238{
1239    req_children($children);
1240    dummy=assert(is_string(tags), str("tags must be a string",tags));
1241    taglist = [for(s=str_split(tags," ",keep_nulls=false)) str($tag_prefix,s)];
1242    $tags_shown = taglist;    
1243    children();
1244}
1245
1246// Module: show_all()
1247// Usage;
1248//   show_all() CHILDREN;
1249// Description:
1250//   Shows all children.  Clears the list of hidden tags and shown tags so that all child objects will be
1251//   fully displayed.
1252// Side Effects:
1253//   Sets `$tags_shown="ALL"`
1254//   Sets `$tags_hidden=[]`
1255module show_all()
1256{
1257   req_children($children);
1258   $tags_shown="ALL";
1259   $tags_hidden=[];
1260   children();
1261}   
1262
1263
1264// Module: show_int()
1265// Usage:
1266//   show_int(tags) CHILDREN;
1267// Description:
1268//   Show only the children with the listed tags which were already shown in the parent context.
1269//   This intersects the current show list with the list of tags you provide.
1270// Arguments:
1271//   tags = list of tags to show
1272// Side Effects:
1273//   Sets `$tags_shown`
1274module show_int(tags)
1275{
1276    req_children($children);
1277    dummy=assert(is_string(tags), str("tags must be a string",tags));
1278    taglist = [for(s=str_split(tags," ",keep_nulls=false)) str($tag_prefix,s)];
1279    $tags_shown = $tags_shown == "ALL" ? taglist : set_intersection($tags_shown,taglist);
1280    children();
1281}
1282
1283
1284// Section: Attachable Masks
1285
1286
1287// Module: edge_mask()
1288// Usage:
1289//   edge_mask([edges], [except]) CHILDREN;
1290// Topics: Attachments
1291// See Also: attachable(), position(), attach(), face_profile(), edge_profile(), corner_mask()
1292// Description:
1293//   Takes a 3D mask shape, and attaches it to the given edges, with the appropriate orientation to be
1294//   differenced away.  The mask shape should be vertically oriented (Z-aligned) with the back-right
1295//   quadrant (X+Y+) shaped to be diffed away from the edge of parent attachable shape.  If no tag is set
1296//   then `edge_mask` sets the tag for children to "remove" so that it will work with the default {{diff()}} tag.  
1297//   For details on specifying the edges to mask see [Specifying Edges](attachments.scad#subsection-specifying-edges).
1298//   For a step-by-step explanation of attachments, see the [[Attachments Tutorial|Tutorial-Attachments]].
1299// Figure: A Typical Edge Rounding Mask
1300//   module roundit(l,r) difference() {
1301//       translate([-1,-1,-l/2])
1302//           cube([r+1,r+1,l]);
1303//       translate([r,r])
1304//           cylinder(h=l+1,r=r,center=true, $fn=quantup(segs(r),4));
1305//   }
1306//   roundit(l=30,r=10);
1307// Arguments:
1308//   edges = Edges to mask.  See [Specifying Edges](attachments.scad#subsection-specifying-edges).  Default: All edges.
1309//   except = Edges to explicitly NOT mask.  See [Specifying Edges](attachments.scad#subsection-specifying-edges).  Default: No edges.
1310// Side Effects:
1311//   Tags the children with "remove" (and hence sets `$tag`) if no tag is already set. 
1312// Example:  
1313//   diff()
1314//   cube([50,60,70],center=true)
1315//       edge_mask([TOP,"Z"],except=[BACK,TOP+LEFT])
1316//           rounding_edge_mask(l=71,r=10);
1317module edge_mask(edges=EDGES_ALL, except=[]) {
1318    req_children($children);
1319    assert($parent_geom != undef, "No object to attach to!");
1320    edges = _edges(edges, except=except);
1321    vecs = [
1322        for (i = [0:3], axis=[0:2])
1323        if (edges[axis][i]>0)
1324        EDGE_OFFSETS[axis][i]
1325    ];
1326    for (vec = vecs) {
1327        vcount = (vec.x?1:0) + (vec.y?1:0) + (vec.z?1:0);
1328        dummy=assert(vcount == 2, "Not an edge vector!");
1329        anch = _find_anchor(vec, $parent_geom);
1330        $attach_to = undef;
1331        $attach_anchor = anch;
1332        $attach_norot = true;
1333        rotang =
1334            vec.z<0? [90,0,180+v_theta(vec)] :
1335            vec.z==0 && sign(vec.x)==sign(vec.y)? 135+v_theta(vec) :
1336            vec.z==0 && sign(vec.x)!=sign(vec.y)? [0,180,45+v_theta(vec)] :
1337            [-90,0,180+v_theta(vec)];
1338        translate(anch[1]) rot(rotang)
1339           if ($tag=="") tag("remove") children();
1340           else children();
1341    }
1342}
1343
1344
1345// Module: corner_mask()
1346// Usage:
1347//   corner_mask([corners], [except]) CHILDREN;
1348// Topics: Attachments
1349// See Also: attachable(), position(), attach(), face_profile(), edge_profile(), edge_mask()
1350// Description:
1351//   Takes a 3D mask shape, and attaches it to the specified corners, with the appropriate orientation to
1352//   be differenced away.  The 3D corner mask shape should be designed to mask away the X+Y+Z+ octant.  If no tag is set
1353//   then `corner_mask` sets the tag for children to "remove" so that it will work with the default {{diff()}} tag.  
1354//   See [Specifying Corners](attachments.scad#subsection-specifying-corners) for information on how to specify corner sets.  
1355//   For a step-by-step explanation of attachments, see the [[Attachments Tutorial|Tutorial-Attachments]].
1356// Arguments:
1357//   corners = Corners to mask.  See [Specifying Corners](attachments.scad#subsection-specifying-corners).  Default: All corners.
1358//   except = Corners to explicitly NOT mask.  See [Specifying Corners](attachments.scad#subsection-specifying-corners).  Default: No corners.
1359// Side Effects:
1360//   Tags the children with "remove" (and hence sets `$tag`) if no tag is already set. 
1361// Example:
1362//   diff()
1363//   cube(100, center=true)
1364//       corner_mask([TOP,FRONT],LEFT+FRONT+TOP)
1365//           difference() {
1366//               translate(-0.01*[1,1,1]) cube(20);
1367//               translate([20,20,20]) sphere(r=20);
1368//           }
1369module corner_mask(corners=CORNERS_ALL, except=[]) {
1370    req_children($children);
1371    assert($parent_geom != undef, "No object to attach to!");
1372    corners = _corners(corners, except=except);
1373    vecs = [for (i = [0:7]) if (corners[i]>0) CORNER_OFFSETS[i]];
1374    for (vec = vecs) {
1375        vcount = (vec.x?1:0) + (vec.y?1:0) + (vec.z?1:0);
1376        dummy=assert(vcount == 3, "Not an edge vector!");
1377        anch = _find_anchor(vec, $parent_geom);
1378        $attach_to = undef;
1379        $attach_anchor = anch;
1380        $attach_norot = true;
1381        rotang = vec.z<0?
1382            [  0,0,180+v_theta(vec)-45] :
1383            [180,0,-90+v_theta(vec)-45];
1384        translate(anch[1]) rot(rotang)
1385           if ($tag=="") tag("remove") children();
1386           else children();
1387    }
1388}
1389
1390
1391// Module: face_profile()
1392// Usage:
1393//   face_profile(faces, r|d=, [convexity=]) CHILDREN;
1394// Topics: Attachments
1395// See Also: attachable(), position(), attach(), edge_profile(), corner_profile()
1396// Description:
1397//   Given a 2D edge profile, extrudes it into a mask for all edges and corners bounding each given face. If no tag is set
1398//   then `face_profile` sets the tag for children to "remove" so that it will work with the default {{diff()}} tag.  
1399//   See  [Specifying Faces](attachments.scad#subsection-specifying-faces) for information on specifying faces.  
1400//   For a step-by-step explanation of attachments, see the [[Attachments Tutorial|Tutorial-Attachments]].
1401// Arguments:
1402//   faces = Faces to mask edges and corners of.
1403//   r = Radius of corner mask.
1404//   ---
1405//   d = Diameter of corner mask.
1406//   convexity = Max number of times a line could intersect the perimeter of the mask shape.  Default: 10
1407// Side Effects:
1408//   Tags the children with "remove" (and hence sets `$tag`) if no tag is already set. 
1409// Example:
1410//   diff()
1411//   cube([50,60,70],center=true)
1412//       face_profile(TOP,r=10)
1413//           mask2d_roundover(r=10);
1414module face_profile(faces=[], r, d, convexity=10) {
1415    req_children($children);
1416    faces = is_vector(faces)? [faces] : faces;
1417    assert(all([for (face=faces) is_vector(face) && sum([for (x=face) x!=0? 1 : 0])==1]), "Vector in faces doesn't point at a face.");
1418    r = get_radius(r=r, d=d, dflt=undef);
1419    assert(is_num(r) && r>0);
1420    edge_profile(faces) children();
1421    corner_profile(faces, convexity=convexity, r=r) children();
1422}
1423
1424
1425// Module: edge_profile()
1426// Usage:
1427//   edge_profile([edges], [except], [convexity]) CHILDREN;
1428// Topics: Attachments
1429// See Also: attachable(), position(), attach(), face_profile(), corner_profile()
1430// Description:
1431//   Takes a 2D mask shape and attaches it to the selected edges, with the appropriate orientation and
1432//   extruded length to be `diff()`ed away, to give the edge a matching profile.  If no tag is set
1433//   then `edge_profile` sets the tag for children to "remove" so that it will work with the default {{diff()}} tag.  
1434//   For details on specifying the edges to mask see [Specifying Edges](attachments.scad#subsection-specifying-edges).
1435//   For a step-by-step
1436//   explanation of attachments, see the [[Attachments Tutorial|Tutorial-Attachments]].
1437// Arguments:
1438//   edges = Edges to mask.  See [Specifying Edges](attachments.scad#subsection-specifying-edges).  Default: All edges.
1439//   except = Edges to explicitly NOT mask.  See [Specifying Edges](attachments.scad#subsection-specifying-edges).  Default: No edges.
1440//   convexity = Max number of times a line could intersect the perimeter of the mask shape.  Default: 10
1441// Side Effects:
1442//   Tags the children with "remove" (and hence sets `$tag`) if no tag is already set. 
1443// Example:
1444//   diff()
1445//   cube([50,60,70],center=true)
1446//       edge_profile([TOP,"Z"],except=[BACK,TOP+LEFT])
1447//           mask2d_roundover(r=10, inset=2);
1448module edge_profile(edges=EDGES_ALL, except=[], convexity=10) {
1449    req_children($children);
1450    assert($parent_geom != undef, "No object to attach to!");
1451    edges = _edges(edges, except=except);
1452    vecs = [
1453        for (i = [0:3], axis=[0:2])
1454        if (edges[axis][i]>0)
1455        EDGE_OFFSETS[axis][i]
1456    ];
1457    for (vec = vecs) {
1458        vcount = (vec.x?1:0) + (vec.y?1:0) + (vec.z?1:0);
1459        dummy=assert(vcount == 2, "Not an edge vector!");
1460        anch = _find_anchor(vec, $parent_geom);
1461        $attach_to = undef;
1462        $attach_anchor = anch;
1463        $attach_norot = true;
1464        psize = point3d($parent_size);
1465        length = [for (i=[0:2]) if(!vec[i]) psize[i]][0]+0.1;
1466        rotang =
1467            vec.z<0? [90,0,180+v_theta(vec)] :
1468            vec.z==0 && sign(vec.x)==sign(vec.y)? 135+v_theta(vec) :
1469            vec.z==0 && sign(vec.x)!=sign(vec.y)? [0,180,45+v_theta(vec)] :
1470            [-90,0,180+v_theta(vec)];
1471        translate(anch[1]) {
1472            rot(rotang) {
1473                linear_extrude(height=length, center=true, convexity=convexity) {
1474                   if ($tag=="") tag("remove") children();
1475                   else children();
1476                }
1477            }
1478        }
1479    }
1480}
1481
1482// Module: corner_profile()
1483// Usage:
1484//   corner_profile([corners], [except], [r=|d=], [convexity=]) CHILDREN;
1485// Topics: Attachments
1486// See Also: attachable(), position(), attach(), face_profile(), edge_profile()
1487// Description:
1488//   Takes a 2D mask shape, rotationally extrudes and converts it into a corner mask, and attaches it
1489//   to the selected corners with the appropriate orientation. If no tag is set
1490//   then `corner_profile` sets the tag for children to "remove" so that it will work with the default {{diff()}} tag.  
1491//   See [Specifying Corners](attachments.scad#subsection-specifying-corners) for information on how to specify corner sets.  
1492//   For a step-by-step explanation of attachments, see the [[Attachments Tutorial|Tutorial-Attachments]].
1493// Arguments:
1494//   corners = Corners to mask.  See [Specifying Corners](attachments.scad#subsection-specifying-corners).  Default: All corners.
1495//   except = Corners to explicitly NOT mask.  See [Specifying Corners](attachments.scad#subsection-specifying-corners).  Default: No corners.
1496//   ---
1497//   r = Radius of corner mask.
1498//   d = Diameter of corner mask.
1499//   convexity = Max number of times a line could intersect the perimeter of the mask shape.  Default: 10
1500// Side Effects:
1501//   Tags the children with "remove" (and hence sets $tag) if no tag is already set. 
1502// Example:
1503//   diff()
1504//   cuboid([50,60,70],rounding=10,edges="Z",anchor=CENTER) {
1505//       corner_profile(TOP,r=10)
1506//           mask2d_teardrop(r=10, angle=40);
1507//   }
1508module corner_profile(corners=CORNERS_ALL, except=[], r, d, convexity=10) {
1509    assert($parent_geom != undef, "No object to attach to!");
1510    r = get_radius(r=r, d=d, dflt=undef);
1511    assert(is_num(r));
1512    corners = _corners(corners, except=except);
1513    vecs = [for (i = [0:7]) if (corners[i]>0) CORNER_OFFSETS[i]];
1514    for (vec = vecs) {
1515        vcount = (vec.x?1:0) + (vec.y?1:0) + (vec.z?1:0);
1516        dummy=assert(vcount == 3, "Not an edge vector!");
1517        anch = _find_anchor(vec, $parent_geom);
1518        $attach_to = undef;
1519        $attach_anchor = anch;
1520        $attach_norot = true;
1521        rotang = vec.z<0?
1522            [  0,0,180+v_theta(vec)-45] :
1523            [180,0,-90+v_theta(vec)-45];
1524        $tag = $tag=="" ? str($tag_prefix,"remove") : $tag;
1525        translate(anch[1]) {
1526            rot(rotang) {
1527                render(convexity=convexity)
1528                difference() {
1529                    translate(-0.1*[1,1,1]) cube(r+0.1, center=false);
1530                    right(r) back(r) zrot(180) {
1531                        rotate_extrude(angle=90, convexity=convexity) {
1532                            xflip() left(r) {
1533                                difference() {
1534                                    square(r,center=false);
1535                                    children();
1536                                }
1537                            }
1538                        }
1539                    }
1540                }
1541            }
1542        }
1543    }
1544}
1545
1546
1547// Section: Making your objects attachable
1548
1549
1550// Module: attachable()
1551//
1552// Usage: Square/Trapezoid Geometry
1553//   attachable(anchor, spin, two_d=true, size=, [size2=], [shift=], ...) {OBJECT; children();}
1554// Usage: Circle/Oval Geometry
1555//   attachable(anchor, spin, two_d=true, r=|d=, ...) {OBJECT; children();}
1556// Usage: 2D Path/Polygon Geometry
1557//   attachable(anchor, spin, two_d=true, path=, [extent=], ...) {OBJECT; children();}
1558// Usage: 2D Region Geometry
1559//   attachable(anchor, spin, two_d=true, region=, [extent=], ...) {OBJECT; children();}
1560// Usage: Cubical/Prismoidal Geometry
1561//   attachable(anchor, spin, [orient], size=, [size2=], [shift=], ...) {OBJECT; children();}
1562// Usage: Cylindrical Geometry
1563//   attachable(anchor, spin, [orient], r=|d=, l=, [axis=], ...) {OBJECT; children();}
1564// Usage: Conical Geometry
1565//   attachable(anchor, spin, [orient], r1=|d1=, r2=|d2=, l=, [axis=], ...) {OBJECT; children();}
1566// Usage: Spheroid/Ovoid Geometry
1567//   attachable(anchor, spin, [orient], r=|d=, ...) {OBJECT; children();}
1568// Usage: Extruded Path/Polygon Geometry
1569//   attachable(anchor, spin, path=, l=|h=, [extent=], ...) {OBJECT; children();}
1570// Usage: Extruded Region Geometry
1571//   attachable(anchor, spin, region=, l=|h=, [extent=], ...) {OBJECT; children();}
1572// Usage: VNF Geometry
1573//   attachable(anchor, spin, [orient], vnf=, [extent=], ...) {OBJECT; children();}
1574// Usage: Pre-Specified Geometry
1575//   attachable(anchor, spin, [orient], geom=) {OBJECT; children();}
1576//
1577// Topics: Attachments
1578// See Also: reorient()
1579//
1580// Description:
1581//   Manages the anchoring, spin, orientation, and attachments for OBJECT, located in a 3D volume or 2D area.
1582//   A managed 3D volume is assumed to be vertically (Z-axis) oriented, and centered.
1583//   A managed 2D area is just assumed to be centered.  The shape to be managed is given
1584//   as the first child to this module, and the second child should be given as `children()`.
1585//   For example, to manage a conical shape:
1586//   ```openscad
1587//   attachable(anchor, spin, orient, r1=r1, r2=r2, l=h) {
1588//       cyl(r1=r1, r2=r2, l=h);
1589//       children();
1590//   }
1591//   ```
1592//   .
1593//   If this is *not* run as a child of `attach()` with the `to` argument
1594//   given, then the following transformations are performed in order:
1595//   * Translates so the `anchor` point is at the origin (0,0,0).
1596//   * Rotates around the Z axis by `spin` degrees counter-clockwise.
1597//   * Rotates so the top of the part points towards the vector `orient`.
1598//   .
1599//   If this is called as a child of `attach(from,to)`, then the info
1600//   for the anchor points referred to by `from` and `to` are fetched,
1601//   which will include position, direction, and spin.  With that info,
1602//   the following transformations are performed:
1603//   * Translates this part so it's anchor position matches the parent's anchor position.
1604//   * Rotates this part so it's anchor direction vector exactly opposes the parent's anchor direction vector.
1605//   * Rotates this part so it's anchor spin matches the parent's anchor spin.
1606//   .
1607//   For a step-by-step explanation of attachments, see the [[Attachments Tutorial|Tutorial-Attachments]].
1608//
1609// Arguments:
1610//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#subsection-anchor).  Default: `CENTER`
1611//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#subsection-spin).  Default: `0`
1612//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#subsection-orient).  Default: `UP`
1613//   ---
1614//   size = If given as a 3D vector, contains the XY size of the bottom of the cuboidal/prismoidal volume, and the Z height.  If given as a 2D vector, contains the front X width of the rectangular/trapezoidal shape, and the Y length.
1615//   size2 = If given as a 2D vector, contains the XY size of the top of the prismoidal volume.  If given as a number, contains the back width of the trapezoidal shape.
1616//   shift = If given as a 2D vector, shifts the top of the prismoidal or conical shape by the given amount.  If given as a number, shifts the back of the trapezoidal shape right by that amount.  Default: No shift.
1617//   r = Radius of the cylindrical/conical volume.  Can be a scalar, or a list of sizes per axis.
1618//   d = Diameter of the cylindrical/conical volume.  Can be a scalar, or a list of sizes per axis.
1619//   r1 = Radius of the bottom of the conical volume.  Can be a scalar, or a list of sizes per axis.
1620//   r2 = Radius of the top of the conical volume.  Can be a scalar, or a list of sizes per axis.
1621//   d1 = Diameter of the bottom of the conical volume.  Can be a scalar, a list of sizes per axis.
1622//   d2 = Diameter of the top of the conical volume.  Can be a scalar, a list of sizes per axis.
1623//   l/h = Length of the cylindrical, conical, or extruded path volume along axis.
1624//   vnf = The [VNF](vnf.scad) of the volume.
1625//   path = The path to generate a polygon from.
1626//   region = The region to generate a shape from.
1627//   extent = If true, calculate anchors by extents, rather than intersection, for VNFs and paths.  Default: true.
1628//   cp = If given, specifies the centerpoint of the volume.  Default: `[0,0,0]`
1629//   offset = If given, offsets the perimeter of the volume around the centerpoint.
1630//   anchors = If given as a list of anchor points, allows named anchor points.
1631//   two_d = If true, the attachable shape is 2D.  If false, 3D.  Default: false (3D)
1632//   axis = The vector pointing along the axis of a geometry.  Default: UP
1633//   geom = If given, uses the pre-defined (via {{attach_geom()}} geometry.
1634//
1635// Side Effects:
1636//   `$parent_anchor` is set to the parent object's `anchor` value.
1637//   `$parent_spin` is set to the parent object's `spin` value.
1638//   `$parent_orient` is set to the parent object's `orient` value.
1639//   `$parent_geom` is set to the parent object's `geom` value.
1640//   `$parent_size` is set to the parent object's cubical `[X,Y,Z]` volume size.
1641//   `$color` is used to set the color of the object
1642//   `$save_color` is used to revert color to the parent's color
1643//
1644// Example(NORENDER): Cubical Shape
1645//   attachable(anchor, spin, orient, size=size) {
1646//       cube(size, center=true);
1647//       children();
1648//   }
1649//
1650// Example(NORENDER): Prismoidal Shape
1651//   attachable(
1652//       anchor, spin, orient,
1653//       size=point3d(botsize,h),
1654//       size2=topsize,
1655//       shift=shift
1656//   ) {
1657//       prismoid(botsize, topsize, h=h, shift=shift);
1658//       children();
1659//   }
1660//
1661// Example(NORENDER): Cylindrical Shape, Z-Axis Aligned
1662//   attachable(anchor, spin, orient, r=r, l=h) {
1663//       cyl(r=r, l=h);
1664//       children();
1665//   }
1666//
1667// Example(NORENDER): Cylindrical Shape, Y-Axis Aligned
1668//   attachable(anchor, spin, orient, r=r, l=h, axis=BACK) {
1669//       cyl(r=r, l=h);
1670//       children();
1671//   }
1672//
1673// Example(NORENDER): Cylindrical Shape, X-Axis Aligned
1674//   attachable(anchor, spin, orient, r=r, l=h, axis=RIGHT) {
1675//       cyl(r=r, l=h);
1676//       children();
1677//   }
1678//
1679// Example(NORENDER): Conical Shape, Z-Axis Aligned
1680//   attachable(anchor, spin, orient, r1=r1, r2=r2, l=h) {
1681//       cyl(r1=r1, r2=r2, l=h);
1682//       children();
1683//   }
1684//
1685// Example(NORENDER): Conical Shape, Y-Axis Aligned
1686//   attachable(anchor, spin, orient, r1=r1, r2=r2, l=h, axis=BACK) {
1687//       cyl(r1=r1, r2=r2, l=h);
1688//       children();
1689//   }
1690//
1691// Example(NORENDER): Conical Shape, X-Axis Aligned
1692//   attachable(anchor, spin, orient, r1=r1, r2=r2, l=h, axis=RIGHT) {
1693//       cyl(r1=r1, r2=r2, l=h);
1694//       children();
1695//   }
1696//
1697// Example(NORENDER): Spherical Shape
1698//   attachable(anchor, spin, orient, r=r) {
1699//       sphere(r=r);
1700//       children();
1701//   }
1702//
1703// Example(NORENDER): Extruded Polygon Shape, by Extents
1704//   attachable(anchor, spin, orient, path=path, l=length) {
1705//       linear_extrude(height=length, center=true)
1706//           polygon(path);
1707//       children();
1708//   }
1709//
1710// Example(NORENDER): Extruded Polygon Shape, by Intersection
1711//   attachable(anchor, spin, orient, path=path, l=length, extent=false) {
1712//       linear_extrude(height=length, center=true)
1713//           polygon(path);
1714//       children();
1715//   }
1716//
1717// Example(NORENDER): Arbitrary VNF Shape, by Extents
1718//   attachable(anchor, spin, orient, vnf=vnf) {
1719//       vnf_polyhedron(vnf);
1720//       children();
1721//   }
1722//
1723// Example(NORENDER): Arbitrary VNF Shape, by Intersection
1724//   attachable(anchor, spin, orient, vnf=vnf, extent=false) {
1725//       vnf_polyhedron(vnf);
1726//       children();
1727//   }
1728//
1729// Example(NORENDER): 2D Rectangular Shape
1730//   attachable(anchor, spin, orient, two_d=true, size=size) {
1731//       square(size, center=true);
1732//       children();
1733//   }
1734//
1735// Example(NORENDER): 2D Trapezoidal Shape
1736//   attachable(
1737//       anchor, spin, orient,
1738//       two_d=true,
1739//       size=[x1,y],
1740//       size2=x2,
1741//       shift=shift
1742//   ) {
1743//       trapezoid(w1=x1, w2=x2, h=y, shift=shift);
1744//       children();
1745//   }
1746//
1747// Example(NORENDER): 2D Circular Shape
1748//   attachable(anchor, spin, orient, two_d=true, r=r) {
1749//       circle(r=r);
1750//       children();
1751//   }
1752//
1753// Example(NORENDER): Arbitrary 2D Polygon Shape, by Extents
1754//   attachable(anchor, spin, orient, two_d=true, path=path) {
1755//       polygon(path);
1756//       children();
1757//   }
1758//
1759// Example(NORENDER): Arbitrary 2D Polygon Shape, by Intersection
1760//   attachable(anchor, spin, orient, two_d=true, path=path, extent=false) {
1761//       polygon(path);
1762//       children();
1763//   }
1764//
1765// Example(NORENDER): Using Pre-defined Geometry
1766//   geom = atype=="perim"? attach_geom(two_d=true, path=path, extent=false) :
1767//       atype=="extents"? attach_geom(two_d=true, path=path, extent=true) :
1768//       atype=="circle"? attach_geom(two_d=true, r=r) :
1769//       assert(false, "Bad atype");
1770//   attachable(anchor, spin, orient, geom=geom) {
1771//       polygon(path);
1772//       children();
1773//   }
1774//
1775// Example: An object can be designed to attach as negative space using {{diff()}}, but if you want an object to include both positive and negative space then you need to call attachable() twice, because tags inside the attachable() call don't work as expected.  This example shows how you can call attachable twice to create an object with positive and negative space.  Note, however, that children in the negative space are differenced away: the highlighted little cube does not survive into the final model. 
1776//   module thing(anchor,spin,orient) {
1777//      tag("remove") attachable(size=[15,15,15],anchor=anchor,spin=spin,orient=orient){
1778//        cuboid([10,10,16]);
1779//        union(){}   // dummy children
1780//      }
1781//      attachable(size=[15,15,15], anchor=anchor, spin=spin, orient=orient){
1782//        cuboid([15,15,15]);
1783//        children();
1784//      }
1785//   }   
1786//   diff()
1787//     cube([19,10,19])
1788//       attach([FRONT],overlap=-4)
1789//         thing(anchor=TOP)
1790//           # attach(TOP) cuboid(2,anchor=TOP);
1791// Example: Here is an example where the "keep" tag allows children to appear in the negative space.  That tag is also needed for this module to produce the desired output.  As above, the tag must be applied outside the attachable() call.  
1792//   module thing(anchor = CENTER, spin = 0, orient = UP) {
1793//      tag("remove") attachable(anchor, spin, orient, d1=0,d2=95,h=33) {
1794//          cylinder(h = 33.1, d1 = 0, d2 = 95, anchor=CENTER);
1795//          union(){}  // dummy children
1796//      }
1797//      tag("keep") attachable(anchor, spin, orient,d1=0,d2=95,h=33) {      
1798//            cylinder(h = 33, d = 10,anchor=CENTER);
1799//            children();
1800//        }
1801//    }
1802//    diff()
1803//      cube(100)
1804//        attach([FRONT,TOP],overlap=-4)
1805//          thing(anchor=TOP)
1806//            tube(ir=12,h=10);
1807module attachable(
1808    anchor, spin, orient,
1809    size, size2, shift,
1810    r,r1,r2, d,d1,d2, l,h,
1811    vnf, path, region,
1812    extent=true,
1813    cp=[0,0,0],
1814    offset=[0,0,0],
1815    anchors=[],
1816    two_d=false,
1817    axis=UP,
1818    geom
1819) {
1820    dummy1 =
1821        assert($children==2, "attachable() expects exactly two children; the shape to manage, and the union of all attachment candidates.")
1822        assert(is_undef(anchor) || is_vector(anchor) || is_string(anchor), str("Got: ",anchor))
1823        assert(is_undef(spin)   || is_vector(spin,3) || is_num(spin), str("Got: ",spin))
1824        assert(is_undef(orient) || is_vector(orient,3), str("Got: ",orient));
1825    anchor = default(anchor, CENTER);
1826    spin =   default(spin,   0);
1827    orient = default(orient, UP);
1828    region = !is_undef(region)? region :
1829        !is_undef(path)? [path] :
1830        undef;
1831    geom = is_def(geom)? geom :
1832        attach_geom(
1833            size=size, size2=size2, shift=shift,
1834            r=r, r1=r1, r2=r2, h=h,
1835            d=d, d1=d1, d2=d2, l=l,
1836            vnf=vnf, region=region, extent=extent,
1837            cp=cp, offset=offset, anchors=anchors,
1838            two_d=two_d, axis=axis
1839        );
1840    m = _attach_transform(anchor,spin,orient,geom);
1841    multmatrix(m) {
1842        $parent_anchor = anchor;
1843        $parent_spin   = spin;
1844        $parent_orient = orient;
1845        $parent_geom   = geom;
1846        $parent_size   = _attach_geom_size(geom);
1847        $attach_to   = undef;
1848        if (_is_shown())
1849            _color($color) children(0);
1850        if (is_def($save_color)) {
1851            $color=$save_color;
1852            $save_color=undef;
1853            children(1);
1854        }
1855        else children(1);
1856    }
1857}
1858
1859// Function: reorient()
1860//
1861// Usage: Square/Trapezoid Geometry
1862//   mat = reorient(anchor, spin, [orient], two_d=true, size=, [size2=], [shift=], ...);
1863//   pts = reorient(anchor, spin, [orient], two_d=true, size=, [size2=], [shift=], p=, ...);
1864// Usage: Circle/Oval Geometry
1865//   mat = reorient(anchor, spin, [orient], two_d=true, r=|d=, ...);
1866//   pts = reorient(anchor, spin, [orient], two_d=true, r=|d=, p=, ...);
1867// Usage: 2D Path/Polygon Geometry
1868//   mat = reorient(anchor, spin, [orient], two_d=true, path=, [extent=], ...);
1869//   pts = reorient(anchor, spin, [orient], two_d=true, path=, [extent=], p=, ...);
1870// Usage: 2D Region/Polygon Geometry
1871//   mat = reorient(anchor, spin, [orient], two_d=true, region=, [extent=], ...);
1872//   pts = reorient(anchor, spin, [orient], two_d=true, region=, [extent=], p=, ...);
1873// Usage: Cubical/Prismoidal Geometry
1874//   mat = reorient(anchor, spin, [orient], size=, [size2=], [shift=], ...);
1875//   pts = reorient(anchor, spin, [orient], size=, [size2=], [shift=], p=, ...);
1876// Usage: Cylindrical Geometry
1877//   mat = reorient(anchor, spin, [orient], r=|d=, l=, [axis=], ...);
1878//   pts = reorient(anchor, spin, [orient], r=|d=, l=, [axis=], p=, ...);
1879// Usage: Conical Geometry
1880//   mat = reorient(anchor, spin, [orient], r1=|d1=, r2=|d2=, l=, [axis=], ...);
1881//   pts = reorient(anchor, spin, [orient], r1=|d1=, r2=|d2=, l=, [axis=], p=, ...);
1882// Usage: Spheroid/Ovoid Geometry
1883//   mat = reorient(anchor, spin, [orient], r|d=, ...);
1884//   pts = reorient(anchor, spin, [orient], r|d=, p=, ...);
1885// Usage: Extruded Path/Polygon Geometry
1886//   mat = reorient(anchor, spin, [orient], path=, l=|h=, [extent=], ...);
1887//   pts = reorient(anchor, spin, [orient], path=, l=|h=, [extent=], p=, ...);
1888// Usage: Extruded Region Geometry
1889//   mat = reorient(anchor, spin, [orient], region=, l=|h=, [extent=], ...);
1890//   pts = reorient(anchor, spin, [orient], region=, l=|h=, [extent=], p=, ...);
1891// Usage: VNF Geometry
1892//   mat = reorient(anchor, spin, [orient], vnf, [extent], ...);
1893//   pts = reorient(anchor, spin, [orient], vnf, [extent], p=, ...);
1894//
1895// Topics: Attachments
1896// See Also: reorient(), attachable()
1897//
1898// Description:
1899//   Given anchor, spin, orient, and general geometry info for a managed volume, this calculates
1900//   the transformation matrix needed to be applied to the contents of that volume.  A managed 3D
1901//   volume is assumed to be vertically (Z-axis) oriented, and centered.  A managed 2D area is just
1902//   assumed to be centered.
1903//   .
1904//   If `p` is not given, then the transformation matrix will be returned.
1905//   If `p` contains a VNF, a new VNF will be returned with the vertices transformed by the matrix.
1906//   If `p` contains a path, a new path will be returned with the vertices transformed by the matrix.
1907//   If `p` contains a point, a new point will be returned, transformed by the matrix.
1908//   .
1909//   If `$attach_to` is not defined, then the following transformations are performed in order:
1910//   * Translates so the `anchor` point is at the origin (0,0,0).
1911//   * Rotates around the Z axis by `spin` degrees counter-clockwise.
1912//   * Rotates so the top of the part points towards the vector `orient`.
1913//   .
1914//   If `$attach_to` is defined, as a consequence of `attach(from,to)`, then
1915//   the following transformations are performed in order:
1916//   * Translates this part so it's anchor position matches the parent's anchor position.
1917//   * Rotates this part so it's anchor direction vector exactly opposes the parent's anchor direction vector.
1918//   * Rotates this part so it's anchor spin matches the parent's anchor spin.
1919//   .
1920//   For a step-by-step explanation of attachments, see the [[Attachments Tutorial|Tutorial-Attachments]].
1921//
1922// Arguments:
1923//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#subsection-anchor).  Default: `CENTER`
1924//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#subsection-spin).  Default: `0`
1925//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#subsection-orient).  Default: `UP`
1926//   ---
1927//   size = If given as a 3D vector, contains the XY size of the bottom of the cuboidal/prismoidal volume, and the Z height.  If given as a 2D vector, contains the front X width of the rectangular/trapezoidal shape, and the Y length.
1928//   size2 = If given as a 2D vector, contains the XY size of the top of the prismoidal volume.  If given as a number, contains the back width of the trapezoidal shape.
1929//   shift = If given as a 2D vector, shifts the top of the prismoidal or conical shape by the given amount.  If given as a number, shifts the back of the trapezoidal shape right by that amount.  Default: No shift.
1930//   r = Radius of the cylindrical/conical volume.  Can be a scalar, or a list of sizes per axis.
1931//   d = Diameter of the cylindrical/conical volume.  Can be a scalar, or a list of sizes per axis.
1932//   r1 = Radius of the bottom of the conical volume.  Can be a scalar, or a list of sizes per axis.
1933//   r2 = Radius of the top of the conical volume.  Can be a scalar, or a list of sizes per axis.
1934//   d1 = Diameter of the bottom of the conical volume.  Can be a scalar, a list of sizes per axis.
1935//   d2 = Diameter of the top of the conical volume.  Can be a scalar, a list of sizes per axis.
1936//   l/h = Length of the cylindrical, conical, or extruded path volume along axis.
1937//   vnf = The [VNF](vnf.scad) of the volume.
1938//   path = The path to generate a polygon from.
1939//   region = The region to generate a shape from.
1940//   extent = If true, calculate anchors by extents, rather than intersection.  Default: false.
1941//   cp = If given, specifies the centerpoint of the volume.  Default: `[0,0,0]`
1942//   offset = If given, offsets the perimeter of the volume around the centerpoint.
1943//   anchors = If given as a list of anchor points, allows named anchor points.
1944//   two_d = If true, the attachable shape is 2D.  If false, 3D.  Default: false (3D)
1945//   axis = The vector pointing along the axis of a geometry.  Default: UP
1946//   p = The VNF, path, or point to transform.
1947function reorient(
1948    anchor, spin, orient,
1949    size, size2, shift,
1950    r,r1,r2, d,d1,d2, l,h,
1951    vnf, path, region,
1952    extent=true,
1953    offset=[0,0,0],
1954    cp=[0,0,0],
1955    anchors=[],
1956    two_d=false,
1957    axis=UP,
1958    geom,
1959    p=undef
1960) = 
1961    assert(is_undef(anchor) || is_vector(anchor) || is_string(anchor), str("Got: ",anchor))
1962    assert(is_undef(spin)   || is_vector(spin,3) || is_num(spin), str("Got: ",spin))
1963    assert(is_undef(orient) || is_vector(orient,3), str("Got: ",orient))
1964    let(
1965        anchor = default(anchor, CENTER),
1966        spin =   default(spin,   0),
1967        orient = default(orient, UP),
1968        region = !is_undef(region)? region :
1969            !is_undef(path)? [path] :
1970            undef
1971    )
1972    (anchor==CENTER && spin==0 && orient==UP && p!=undef)? p :
1973    let(
1974        geom = is_def(geom)? geom :
1975            attach_geom(
1976                size=size, size2=size2, shift=shift,
1977                r=r, r1=r1, r2=r2, h=h,
1978                d=d, d1=d1, d2=d2, l=l,
1979                vnf=vnf, region=region, extent=extent,
1980                cp=cp, offset=offset, anchors=anchors,
1981                two_d=two_d, axis=axis
1982            ),
1983        $attach_to = undef
1984    ) _attach_transform(anchor,spin,orient,geom,p);
1985
1986
1987// Function: named_anchor()
1988// Usage:
1989//   a = named_anchor(name, pos, [orient], [spin]);
1990// Topics: Attachments
1991// See Also: reorient(), attachable()
1992// Description:
1993//   Creates an anchor data structure.  For a step-by-step explanation of attachments,
1994//   see the [[Attachments Tutorial|Tutorial-Attachments]].
1995// Arguments:
1996//   name = The string name of the anchor.  Lowercase.  Words separated by single dashes.  No spaces.
1997//   pos = The [X,Y,Z] position of the anchor.
1998//   orient = A vector pointing in the direction parts should project from the anchor position.  Default: UP
1999//   spin = If needed, the angle to rotate the part around the direction vector.  Default: 0
2000function named_anchor(name, pos, orient=UP, spin=0) = [name, pos, orient, spin];
2001
2002
2003// Function: attach_geom()
2004//
2005// Usage: Square/Trapezoid Geometry
2006//   geom = attach_geom(two_d=true, size=, [size2=], [shift=], ...);
2007// Usage: Circle/Oval Geometry
2008//   geom = attach_geom(two_d=true, r=|d=, ...);
2009// Usage: 2D Path/Polygon/Region Geometry
2010//   geom = attach_geom(two_d=true, region=, [extent=], ...);
2011// Usage: Cubical/Prismoidal Geometry
2012//   geom = attach_geom(size=, [size2=], [shift=], ...);
2013// Usage: Cylindrical Geometry
2014//   geom = attach_geom(r=|d=, l=|h=, [axis=], ...);
2015// Usage: Conical Geometry
2016//   geom = attach_geom(r1|d1=, r2=|d2=, l=, [axis=], ...);
2017// Usage: Spheroid/Ovoid Geometry
2018//   geom = attach_geom(r=|d=, ...);
2019// Usage: Extruded 2D Path/Polygon/Region Geometry
2020//   geom = attach_geom(region=, l=|h=, [extent=], [shift=], [scale=], [twist=], ...);
2021// Usage: VNF Geometry
2022//   geom = attach_geom(vnf=, [extent=], ...);
2023//
2024// Topics: Attachments
2025// See Also: reorient(), attachable()
2026//
2027// Description:
2028//   Given arguments that describe the geometry of an attachable object, returns the internal geometry description.
2029//   This will probably not not ever need to be called by the end user.
2030//
2031// Arguments:
2032//   ---
2033//   size = If given as a 3D vector, contains the XY size of the bottom of the cuboidal/prismoidal volume, and the Z height.  If given as a 2D vector, contains the front X width of the rectangular/trapezoidal shape, and the Y length.
2034//   size2 = If given as a 2D vector, contains the XY size of the top of the prismoidal volume.  If given as a number, contains the back width of the trapezoidal shape.
2035//   shift = If given as a 2D vector, shifts the top of the prismoidal or conical shape by the given amount.  If given as a number, shifts the back of the trapezoidal shape right by that amount.  Default: No shift.
2036//   scale = If given as number or a 2D vector, scales the top of the shape, relative to the bottom.  Default: `[1,1]`
2037//   twist = If given as number, rotates the top of the shape by the given number of degrees clockwise, relative to the bottom.  Default: `0`
2038//   r = Radius of the cylindrical/conical volume.  Can be a scalar, or a list of sizes per axis.
2039//   d = Diameter of the cylindrical/conical volume.  Can be a scalar, or a list of sizes per axis.
2040//   r1 = Radius of the bottom of the conical volume.  Can be a scalar, or a list of sizes per axis.
2041//   r2 = Radius of the top of the conical volume.  Can be a scalar, or a list of sizes per axis.
2042//   d1 = Diameter of the bottom of the conical volume.  Can be a scalar, a list of sizes per axis.
2043//   d2 = Diameter of the top of the conical volume.  Can be a scalar, a list of sizes per axis.
2044//   l/h = Length of the cylindrical, conical or extruded region volume along axis.
2045//   vnf = The [VNF](vnf.scad) of the volume.
2046//   region = The region to generate a shape from.
2047//   extent = If true, calculate anchors by extents, rather than intersection.  Default: true.
2048//   cp = If given, specifies the centerpoint of the volume.  Default: `[0,0,0]`
2049//   offset = If given, offsets the perimeter of the volume around the centerpoint.
2050//   anchors = If given as a list of anchor points, allows named anchor points.
2051//   two_d = If true, the attachable shape is 2D.  If false, 3D.  Default: false (3D)
2052//   axis = The vector pointing along the axis of a geometry.  Default: UP
2053//
2054// Example(NORENDER): Cubical Shape
2055//   geom = attach_geom(size=size);
2056//
2057// Example(NORENDER): Prismoidal Shape
2058//   geom = attach_geom(
2059//       size=point3d(botsize,h),
2060//       size2=topsize, shift=shift
2061//   );
2062//
2063// Example(NORENDER): Cylindrical Shape, Z-Axis Aligned
2064//   geom = attach_geom(r=r, h=h);
2065//
2066// Example(NORENDER): Cylindrical Shape, Y-Axis Aligned
2067//   geom = attach_geom(r=r, h=h, axis=BACK);
2068//
2069// Example(NORENDER): Cylindrical Shape, X-Axis Aligned
2070//   geom = attach_geom(r=r, h=h, axis=RIGHT);
2071//
2072// Example(NORENDER): Conical Shape, Z-Axis Aligned
2073//   geom = attach_geom(r1=r1, r2=r2, h=h);
2074//
2075// Example(NORENDER): Conical Shape, Y-Axis Aligned
2076//   geom = attach_geom(r1=r1, r2=r2, h=h, axis=BACK);
2077//
2078// Example(NORENDER): Conical Shape, X-Axis Aligned
2079//   geom = attach_geom(r1=r1, r2=r2, h=h, axis=RIGHT);
2080//
2081// Example(NORENDER): Spherical Shape
2082//   geom = attach_geom(r=r);
2083//
2084// Example(NORENDER): Ovoid Shape
2085//   geom = attach_geom(r=[r_x, r_y, r_z]);
2086//
2087// Example(NORENDER): Arbitrary VNF Shape, Anchored by Extents
2088//   geom = attach_geom(vnf=vnf);
2089//
2090// Example(NORENDER): Arbitrary VNF Shape, Anchored by Intersection
2091//   geom = attach_geom(vnf=vnf, extent=false);
2092//
2093// Example(NORENDER): 2D Rectangular Shape
2094//   geom = attach_geom(two_d=true, size=size);
2095//
2096// Example(NORENDER): 2D Trapezoidal Shape
2097//   geom = attach_geom(two_d=true, size=[x1,y], size2=x2, shift=shift);
2098//
2099// Example(NORENDER): 2D Circular Shape
2100//   geom = attach_geom(two_d=true, r=r);
2101//
2102// Example(NORENDER): 2D Oval Shape
2103//   geom = attach_geom(two_d=true, r=[r_x, r_y]);
2104//
2105// Example(NORENDER): Arbitrary 2D Region Shape, Anchored by Extents
2106//   geom = attach_geom(two_d=true, region=region);
2107//
2108// Example(NORENDER): Arbitrary 2D Region Shape, Anchored by Intersection
2109//   geom = attach_geom(two_d=true, region=region, extent=false);
2110//
2111// Example(NORENDER): Extruded Region, Anchored by Extents
2112//   geom = attach_geom(region=region, l=height);
2113//
2114// Example(NORENDER): Extruded Region, Anchored by Intersection
2115//   geom = attach_geom(region=region, l=length, extent=false);
2116//
2117function attach_geom(
2118    size, size2,
2119    shift, scale, twist,
2120    r,r1,r2, d,d1,d2, l,h,
2121    vnf, region,
2122    extent=true,
2123    cp=[0,0,0],
2124    offset=[0,0,0],
2125    anchors=[],
2126    two_d=false,
2127    axis=UP
2128) =
2129    assert(is_bool(extent))
2130    assert(is_vector(cp) || is_string(cp))
2131    assert(is_vector(offset))
2132    assert(is_list(anchors))
2133    assert(is_bool(two_d))
2134    assert(is_vector(axis))
2135    !is_undef(size)? (
2136        two_d? (
2137            let(
2138                size2 = default(size2, size.x),
2139                shift = default(shift, 0)
2140            )
2141            assert(is_vector(size,2))
2142            assert(is_num(size2))
2143            assert(is_num(shift))
2144            ["trapezoid", point2d(size), size2, shift, cp, offset, anchors]
2145        ) : (
2146            let(
2147                size2 = default(size2, point2d(size)),
2148                shift = default(shift, [0,0])
2149            )
2150            assert(is_vector(size,3))
2151            assert(is_vector(size2,2))
2152            assert(is_vector(shift,2))
2153            ["prismoid", size, size2, shift, axis, cp, offset, anchors]
2154        )
2155    ) : !is_undef(vnf)? (
2156        assert(is_vnf(vnf))
2157        assert(two_d == false)
2158        extent? ["vnf_extent", vnf, cp, offset, anchors] :
2159        ["vnf_isect", vnf, cp, offset, anchors]
2160    ) : !is_undef(region)? (
2161        assert(is_region(region),2)
2162        let( l = default(l, h) )
2163        two_d==true
2164          ? assert(is_undef(l))
2165            extent==true
2166              ? ["rgn_extent", region, cp, offset, anchors]
2167              : ["rgn_isect",  region, cp, offset, anchors]
2168          : assert(is_finite(l))
2169            let(
2170                shift = default(shift, [0,0]),
2171                scale = is_num(scale)? [scale,scale] : default(scale, [1,1]),
2172                twist = default(twist, 0)
2173            )
2174            assert(is_vector(shift,2))
2175            assert(is_vector(scale,2))
2176            assert(is_num(twist))
2177            extent==true
2178              ? ["xrgn_extent", region, l, twist, scale, shift, cp, offset, anchors]
2179              : ["xrgn_isect",  region, l, twist, scale, shift, cp, offset, anchors]
2180    ) :
2181    let(
2182        r1 = get_radius(r1=r1,d1=d1,r=r,d=d,dflt=undef)
2183    )
2184    !is_undef(r1)? (
2185        let( l = default(l, h) )
2186        !is_undef(l)? (
2187            let(
2188                shift = default(shift, [0,0]),
2189                r2 = get_radius(r1=r2,d1=d2,r=r,d=d,dflt=undef)
2190            )
2191            assert(is_num(r1) || is_vector(r1,2))
2192            assert(is_num(r2) || is_vector(r2,2))
2193            assert(is_num(l))
2194            assert(is_vector(shift,2))
2195            ["conoid", r1, r2, l, shift, axis, cp, offset, anchors]
2196        ) : (
2197            two_d? (
2198                assert(is_num(r1) || is_vector(r1,2))
2199                ["ellipse", r1, cp, offset, anchors]
2200            ) : (
2201                assert(is_num(r1) || is_vector(r1,3))
2202                ["spheroid", r1, cp, offset, anchors]
2203            )
2204        )
2205    ) :
2206    assert(false, "Unrecognizable geometry description.");
2207
2208
2209
2210
2211
2212
2213//////////////////////////////////////////////////////////////////////////////////////////////////////////////
2214//
2215// Attachment internal functions
2216
2217
2218/// Internal Function: _attach_geom_2d()
2219// Usage:
2220//   bool = _attach_geom_2d(geom);
2221/// Topics: Attachments
2222/// See Also: reorient(), attachable()
2223// Description:
2224//   Returns true if the given attachment geometry description is for a 2D shape.
2225function _attach_geom_2d(geom) =
2226    let( type = geom[0] )
2227    type == "trapezoid" || type == "ellipse" ||
2228    type == "rgn_isect" || type == "rgn_extent";
2229
2230
2231/// Internal Function: _attach_geom_size()
2232// Usage:
2233//   bounds = _attach_geom_size(geom);
2234/// Topics: Attachments
2235/// See Also: reorient(), attachable()
2236// Description:
2237//   Returns the `[X,Y,Z]` bounding size for the given attachment geometry description.
2238function _attach_geom_size(geom) =
2239    let( type = geom[0] )
2240    type == "prismoid"? ( //size, size2, shift, axis
2241        let(
2242            size=geom[1], size2=geom[2], shift=point2d(geom[3]),
2243            maxx = max(size.x,size2.x),
2244            maxy = max(size.y,size2.y),
2245            z = size.z
2246        ) [maxx, maxy, z]
2247    ) : type == "conoid"? ( //r1, r2, l, shift
2248        let(
2249            r1=geom[1], r2=geom[2], l=geom[3],
2250            shift=point2d(geom[4]), axis=point3d(geom[5]),
2251            rx1 = default(r1[0],r1),
2252            ry1 = default(r1[1],r1),
2253            rx2 = default(r2[0],r2),
2254            ry2 = default(r2[1],r2),
2255            maxxr = max(rx1,rx2),
2256            maxyr = max(ry1,ry2)
2257        )
2258        approx(axis,UP)? [2*maxxr,2*maxyr,l] :
2259        approx(axis,RIGHT)? [l,2*maxyr,2*maxxr] :
2260        approx(axis,BACK)? [2*maxxr,l,2*maxyr] :
2261        [2*maxxr, 2*maxyr, l]
2262    ) : type == "spheroid"? ( //r
2263        let( r=geom[1] )
2264        is_num(r)? [2,2,2]*r : v_mul([2,2,2],point3d(r))
2265    ) : type == "vnf_extent" || type=="vnf_isect"? ( //vnf
2266        let(
2267            vnf = geom[1]
2268        ) vnf==EMPTY_VNF? [0,0,0] :
2269        let(
2270            mm = pointlist_bounds(geom[1][0]),
2271            delt = mm[1]-mm[0]
2272        ) delt
2273    ) : type == "xrgn_isect" || type == "xrgn_extent"? ( //path, l
2274        let(
2275            mm = pointlist_bounds(flatten(geom[1])),
2276            delt = mm[1]-mm[0]
2277        ) [delt.x, delt.y, geom[2]]
2278    ) : type == "trapezoid"? ( //size, size2
2279        let(
2280            size=geom[1], size2=geom[2], shift=geom[3],
2281            maxx = max(size.x,size2+abs(shift))
2282        ) [maxx, size.y]
2283    ) : type == "ellipse"? ( //r
2284        let( r=geom[1] )
2285        is_num(r)? [2,2]*r : v_mul([2,2],point2d(r))
2286    ) : type == "rgn_isect" || type == "rgn_extent"? ( //path
2287        let(
2288            mm = pointlist_bounds(flatten(geom[1])),
2289            delt = mm[1]-mm[0]
2290        ) [delt.x, delt.y]
2291    ) :
2292    assert(false, "Unknown attachment geometry type.");
2293
2294
2295/// Internal Function: _attach_transform()
2296// Usage: To Get a Transformation Matrix
2297//   mat = _attach_transform(anchor, spin, orient, geom);
2298// Usage: To Transform Points, Paths, Patches, or VNFs
2299//   new_p = _attach_transform(anchor, spin, orient, geom, p);
2300/// Topics: Attachments
2301/// See Also: reorient(), attachable()
2302// Description:
2303//   Returns the affine3d transformation matrix needed to `anchor`, `spin`, and `orient`
2304//   the given geometry `geom` shape into position.
2305// Arguments:
2306//   anchor = Anchor point to translate to the origin `[0,0,0]`.  See [anchor](attachments.scad#subsection-anchor).  Default: `CENTER`
2307//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#subsection-spin).  Default: `0`
2308//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#subsection-orient).  Default: `UP`
2309//   geom = The geometry description of the shape.
2310//   p = If given as a VNF, path, or point, applies the affine3d transformation matrix to it and returns the result.
2311function _attach_transform(anchor, spin, orient, geom, p) =
2312    assert(is_undef(anchor) || is_vector(anchor) || is_string(anchor), str("Got: ",anchor))
2313    assert(is_undef(spin)   || is_vector(spin,3) || is_num(spin), str("Got: ",spin))
2314    assert(is_undef(orient) || is_vector(orient,3), str("Got: ",orient))
2315    let(
2316        anchor = default(anchor, CENTER),
2317        spin   = default(spin,   0),
2318        orient = default(orient, UP),
2319        two_d = _attach_geom_2d(geom),
2320        m = ($attach_to != undef)? (
2321            let(
2322                anch = _find_anchor($attach_to, geom),
2323                pos = anch[1]
2324            ) two_d? (
2325                assert(two_d && is_num(spin))
2326                affine3d_zrot(spin) *
2327                rot(to=FWD, from=point3d(anch[2])) *
2328                affine3d_translate(point3d(-pos))
2329            ) : (
2330                assert(is_num(spin) || is_vector(spin,3))
2331                let(
2332                    ang = vector_angle(anch[2], DOWN),
2333                    axis = vector_axis(anch[2], DOWN),
2334                    ang2 = (anch[2]==UP || anch[2]==DOWN)? 0 : 180-anch[3],
2335                    axis2 = rot(p=axis,[0,0,ang2])
2336                )
2337                affine3d_rot_by_axis(axis2,ang) * (
2338                    is_num(spin)? affine3d_zrot(ang2+spin) : (
2339                        affine3d_zrot(spin.z) *
2340                        affine3d_yrot(spin.y) *
2341                        affine3d_xrot(spin.x) *
2342                        affine3d_zrot(ang2)
2343                    )
2344                ) * affine3d_translate(point3d(-pos))
2345            )
2346        ) : (
2347            let(
2348                pos = _find_anchor(anchor, geom)[1]
2349            ) two_d? (
2350                assert(two_d && is_num(spin))
2351                affine3d_zrot(spin) *
2352                affine3d_translate(point3d(-pos))
2353            ) : (
2354                assert(is_num(spin) || is_vector(spin,3))
2355                let(
2356                    axis = vector_axis(UP,orient),
2357                    ang = vector_angle(UP,orient)
2358                )
2359                affine3d_rot_by_axis(axis,ang) * (
2360                    is_num(spin)? affine3d_zrot(spin) : (
2361                        affine3d_zrot(spin.z) *
2362                        affine3d_yrot(spin.y) *
2363                        affine3d_xrot(spin.x)
2364                    )
2365                ) * affine3d_translate(point3d(-pos))
2366            )
2367        )
2368    ) is_undef(p)? m :
2369    is_vnf(p)? [(p==EMPTY_VNF? p : apply(m, p[0])), p[1]] :
2370    apply(m, p);
2371
2372
2373function _get_cp(geom) =
2374    
2375    let(cp=select(geom,-3))
2376    is_vector(cp) ? cp
2377  : let(
2378        type = in_list(geom[0],["vnf_extent","vnf_isect"]) ? "vnf"
2379             : in_list(geom[0],["rgn_extent","rgn_isect"]) ? "path"
2380             : in_list(geom[0],["xrgn_extent","xrgn_isect"]) ? "xpath"
2381             : "other"
2382    )
2383    assert(type!="other", "Invalid cp value")
2384    cp=="centroid" ? (
2385       type=="vnf" && (len(geom[1][0])==0 || len(geom[1][1])==0) ? [0,0,0] :
2386       [each centroid(geom[1]), if (type=="xpath") 0]
2387    )
2388  : let(points = type=="vnf"?geom[1][0]:flatten(force_region(geom[1])))
2389    cp=="mean" ? [each mean(points), if (type=="xpath") geom[2]/2]
2390  : cp=="box" ?[each  mean(pointlist_bounds(points)), if (type=="xpath") geom[2]/2]
2391  : assert(false,"Invalid cp specification");
2392
2393
2394function _force_anchor_2d(anchor) =
2395  assert(anchor.y==0 || anchor.z==0, "Anchor for a 2D shape cannot be fully 3D.  It must have either Y or Z component equal to zero.")
2396  anchor.y==0 ? [anchor.x,anchor.z] : point2d(anchor);
2397
2398
2399/// Internal Function: _find_anchor()
2400// Usage:
2401//   anchorinfo = _find_anchor(anchor, geom);
2402/// Topics: Attachments
2403/// See Also: reorient(), attachable()
2404// Description:
2405//   Calculates the anchor data for the given `anchor` vector or name, in the given attachment
2406//   geometry.  Returns `[ANCHOR, POS, VEC, ANG]` where `ANCHOR` is the requested anchorname
2407//   or vector, `POS` is the anchor position, `VEC` is the direction vector of the anchor, and
2408//   `ANG` is the angle to align with around the rotation axis of th anchor direction vector.
2409// Arguments:
2410//   anchor = Vector or named anchor string.
2411//   geom = The geometry description of the shape.
2412function _find_anchor(anchor, geom) =
2413    is_string(anchor)? (  
2414          anchor=="origin"? [anchor, CENTER, UP, 0]
2415        : let(
2416              anchors = last(geom),
2417              found = search([anchor], anchors, num_returns_per_match=1)[0]
2418          )
2419          assert(found!=[], str("Unknown anchor: ",anchor))
2420          anchors[found]
2421    ) :
2422    let( 
2423        cp = _get_cp(geom),
2424        offset_raw = select(geom,-2),
2425        offset = [for (i=[0:2]) anchor[i]==0? 0 : offset_raw[i]],  // prevents bad centering.
2426        type = geom[0]
2427    )
2428    assert(is_vector(anchor),str("Invalid anchor: anchor=",anchor))
2429    let(
2430        anchor = point3d(anchor),
2431        oang = (
2432            approx(point2d(anchor), [0,0])? 0 :
2433            atan2(anchor.y, anchor.x)+90
2434        )
2435    )
2436    type == "prismoid"? ( //size, size2, shift, axis
2437        let(all_comps_good = [for (c=anchor) if (c!=sign(c)) 1]==[])
2438        assert(all_comps_good, "All components of an anchor for a cuboid/prismoid must be -1, 0, or 1")
2439        let(
2440            size=geom[1], size2=geom[2],
2441            shift=point2d(geom[3]), axis=point3d(geom[4]),
2442            anch = rot(from=axis, to=UP, p=anchor),
2443            offset = rot(from=axis, to=UP, p=offset),
2444            h = size.z,
2445            u = (anch.z + 1) / 2,  // u is one of 0, 0.5, or 1
2446            axy = point2d(anch),
2447            bot = point3d(v_mul(point2d(size )/2, axy), -h/2),
2448            top = point3d(v_mul(point2d(size2)/2, axy) + shift, h/2),
2449            pos = point3d(cp) + lerp(bot,top,u) + offset,
2450            vecs = [
2451                if (anch.x!=0) unit(rot(from=UP, to=[(top-bot).x,0,h], p=[axy.x,0,0]), UP),
2452                if (anch.y!=0) unit(rot(from=UP, to=[0,(top-bot).y,h], p=[0,axy.y,0]), UP),
2453                if (anch.z!=0) anch==CENTER? UP : unit([0,0,anch.z],UP)
2454            ],
2455            vec = anchor==CENTER? UP : rot(from=UP, to=axis, p=unit(sum(vecs) / len(vecs))),
2456            pos2 = rot(from=UP, to=axis, p=pos)
2457        ) [anchor, pos2, vec, oang]
2458    ) : type == "conoid"? ( //r1, r2, l, shift
2459        assert(anchor.z == sign(anchor.z), "The Z component of an anchor for a cylinder/cone must be -1, 0, or 1")
2460        let(
2461            rr1=geom[1], rr2=geom[2], l=geom[3],
2462            shift=point2d(geom[4]), axis=point3d(geom[5]),
2463            r1 = is_num(rr1)? [rr1,rr1] : point2d(rr1),
2464            r2 = is_num(rr2)? [rr2,rr2] : point2d(rr2),
2465            anch = rot(from=axis, to=UP, p=anchor),
2466            offset = rot(from=axis, to=UP, p=offset),
2467            u = (anch.z+1)/2,
2468            axy = unit(point2d(anch),[0,0]),
2469            bot = point3d(v_mul(r1,axy), -l/2),
2470            top = point3d(v_mul(r2,axy)+shift, l/2),
2471            pos = point3d(cp) + lerp(bot,top,u) + offset,
2472            sidevec = rot(from=UP, to=top==bot?UP:top-bot, p=point3d(axy)),
2473            vvec = anch==CENTER? UP : unit([0,0,anch.z],UP),
2474            vec = anch==CENTER? CENTER :
2475                approx(axy,[0,0])? unit(anch,UP) :
2476                approx(anch.z,0)? sidevec :
2477                unit((sidevec+vvec)/2,UP),
2478            pos2 = rot(from=UP, to=axis, p=pos),
2479            vec2 = anch==CENTER? UP : rot(from=UP, to=axis, p=vec)
2480        ) [anchor, pos2, vec2, oang]
2481    ) : type == "spheroid"? ( //r
2482        let(
2483            rr = geom[1],
2484            r = is_num(rr)? [rr,rr,rr] : point3d(rr),
2485            anchor = unit(point3d(anchor),CENTER),
2486            pos = point3d(cp) + v_mul(r,anchor) + point3d(offset),
2487            vec = unit(v_mul(r,anchor),UP)
2488        ) [anchor, pos, vec, oang]
2489    ) : type == "vnf_isect"? ( //vnf
2490        let( vnf=geom[1] )
2491        approx(anchor,CTR)? [anchor, [0,0,0], UP, 0] :
2492        vnf==EMPTY_VNF? [anchor, [0,0,0], unit(anchor), 0] :
2493        let(
2494            eps = 1/2048,
2495            points = vnf[0],
2496            faces = vnf[1],
2497            rpts = apply(rot(from=anchor, to=RIGHT) * move(-cp), points),
2498            hits = [
2499                for (face = faces)
2500                    let(
2501                        verts = select(rpts, face),
2502                        ys = column(verts,1),
2503                        zs = column(verts,2)
2504                    )
2505                    if (max(ys) >= -eps && max(zs) >= -eps &&
2506                        min(ys) <=  eps &&  min(zs) <=  eps)
2507                        let(
2508                            poly = select(points, face),
2509                            isect = polygon_line_intersection(poly, [cp,cp+anchor], eps=eps),
2510                            ptlist = is_undef(isect) ? [] :
2511                                     is_vector(isect) ? [isect]
2512                                                      : flatten(isect),   // parallel to a face
2513                            n = len(ptlist)>0 ? polygon_normal(poly) : undef
2514                        )
2515                        for(pt=ptlist) [anchor * (pt-cp), n, pt]
2516            ]
2517        )
2518        assert(len(hits)>0, "Anchor vector does not intersect with the shape.  Attachment failed.")
2519        let(
2520            furthest = max_index(column(hits,0)),
2521            dist = hits[furthest][0],
2522            pos = hits[furthest][2],
2523            hitnorms = [for (hit = hits) if (approx(hit[0],dist,eps=eps)) hit[1]],
2524            unorms = [
2525                      for (i = idx(hitnorms))
2526                          let(
2527                              thisnorm = hitnorms[i],
2528                              isdup = [
2529                                       for (j = [i+1:1:len(hitnorms)-1])
2530                                           if (approx(thisnorm, hitnorms[j])) 1
2531                                      ] != []
2532                          )
2533                          if (!isdup) thisnorm
2534                     ],
2535            n = unit(sum(unorms)),
2536            oang = approx(point2d(n), [0,0])? 0 : atan2(n.y, n.x) + 90
2537        )
2538        [anchor, pos, n, oang]
2539    ) : type == "vnf_extent"? ( //vnf
2540        let( vnf=geom[1] )
2541        approx(anchor,CTR)? [anchor, [0,0,0], UP, 0] :
2542        vnf==EMPTY_VNF? [anchor, [0,0,0], unit(anchor,UP), 0] :
2543        let(
2544            rpts = apply(rot(from=anchor, to=RIGHT) * move(point3d(-cp)), vnf[0]),
2545            maxx = max(column(rpts,0)),
2546            idxs = [for (i = idx(rpts)) if (approx(rpts[i].x, maxx)) i],
2547            avep = sum(select(rpts,idxs))/len(idxs),
2548            mpt = approx(point2d(anchor),[0,0])? [maxx,0,0] : avep,
2549            pos = point3d(cp) + rot(from=RIGHT, to=anchor, p=mpt)
2550        ) [anchor, pos, anchor, oang]
2551    ) : type == "trapezoid"? ( //size, size2, shift
2552        let(all_comps_good = [for (c=anchor) if (c!=sign(c)) 1]==[])
2553        assert(all_comps_good, "All components of an anchor for a rectangle/trapezoid must be -1, 0, or 1")
2554        let(
2555            anchor=_force_anchor_2d(anchor),
2556            size=geom[1], size2=geom[2], shift=geom[3],
2557            u = (anchor.y+1)/2,  // 0<=u<=1
2558            frpt = [size.x/2*anchor.x, -size.y/2],
2559            bkpt = [size2/2*anchor.x+shift,  size.y/2],
2560            pos = point2d(cp) + lerp(frpt, bkpt, u) + point2d(offset),
2561            svec = point3d(line_normal(bkpt,frpt)*anchor.x),
2562            vec = anchor.y < 0? (
2563                    anchor.x == 0? FWD :
2564                    size.x == 0? unit(-[shift,size.y], FWD) :
2565                    unit((point3d(svec) + FWD) / 2, FWD)
2566                ) :
2567                anchor.y == 0? ( anchor.x == 0? BACK : svec ) :
2568                (  // anchor.y > 0
2569                    anchor.x == 0? BACK :
2570                    size2 == 0? unit([shift,size.y], BACK) :
2571                    unit((point3d(svec) + BACK) / 2, BACK)
2572                )
2573        ) [anchor, pos, vec, 0]
2574    ) : type == "ellipse"? ( //r
2575        let(
2576            anchor = unit(_force_anchor_2d(anchor),[0,0]),
2577            r = force_list(geom[1],2),
2578            pos = approx(anchor.x,0) ? [0,sign(anchor.y)*r.y]
2579                      : let(
2580                             m = anchor.y/anchor.x,
2581                             px = sign(anchor.x) * sqrt(1/(1/sqr(r.x) + m*m/sqr(r.y)))
2582                        )
2583                        [px,m*px],
2584            vec = unit([r.y/r.x*pos.x, r.x/r.y*pos.y],BACK)
2585        ) [anchor, point2d(cp+offset)+pos, vec, 0]
2586    ) : type == "rgn_isect"? ( //region
2587        let(
2588            anchor = _force_anchor_2d(anchor),
2589            rgn = force_region(move(-point2d(cp), p=geom[1]))
2590        )
2591        approx(anchor,[0,0])? [anchor, [0,0,0], BACK, 0] :
2592        let(
2593            isects = [
2594                for (path=rgn, t=triplet(path,true)) let(
2595                    seg1 = [t[0],t[1]],
2596                    seg2 = [t[1],t[2]],
2597                    isect = line_intersection([[0,0],anchor], seg1, RAY, SEGMENT),
2598                    n = is_undef(isect)? [0,1] :
2599                        !approx(isect, t[1])? line_normal(seg1) :
2600                        unit((line_normal(seg1)+line_normal(seg2))/2,[0,1]),
2601                    n2 = vector_angle(anchor,n)>90? -n : n
2602                )
2603                if(!is_undef(isect) && !approx(isect,t[0])) [norm(isect), isect, n2]
2604            ]
2605        )
2606        assert(len(isects)>0, "Anchor vector does not intersect with the shape.  Attachment failed.")
2607        let(
2608            maxidx = max_index(column(isects,0)),
2609            isect = isects[maxidx],
2610            pos = point2d(cp) + isect[1],
2611            vec = unit(isect[2],[0,1])
2612        ) [anchor, pos, vec, 0]
2613    ) : type == "rgn_extent"? ( //region
2614        let( anchor = _force_anchor_2d(anchor) )
2615        approx(anchor,[0,0])? [anchor, [0,0,0], BACK, 0] :
2616        let(
2617            rgn = force_region(geom[1]),
2618            rpts = rot(from=anchor, to=RIGHT, p=flatten(rgn)),
2619            maxx = max(column(rpts,0)),
2620            ys = [for (pt=rpts) if (approx(pt.x, maxx)) pt.y],            
2621            midy = (min(ys)+max(ys))/2,
2622            pos = rot(from=RIGHT, to=anchor, p=[maxx,midy])
2623        ) [anchor, pos, unit(anchor,BACK), 0]
2624    ) : type=="xrgn_extent" || type=="xrgn_isect" ? (  // extruded region
2625        assert(in_list(anchor.z,[-1,0,1]), "The Z component of an anchor for an extruded 2D shape must be -1, 0, or 1.")
2626        let(
2627            anchor_xy = point2d(anchor),
2628            rgn = geom[1],
2629            L = geom[2],
2630            twist = geom[3],
2631            scale = geom[4],
2632            shift = geom[5],
2633            u = (anchor.z + 1) / 2,
2634            shmat = move(lerp([0,0], shift, u)),
2635            scmat = scale(lerp([1,1], scale, u)),
2636            twmat = zrot(lerp(0, -twist, u)),
2637            mat = shmat * scmat * twmat
2638        )
2639        approx(anchor_xy,[0,0]) ? [anchor, apply(mat, up(anchor.z*L/2,cp)), unit(anchor, UP), oang] :
2640        let(
2641            newrgn = apply(mat, rgn),
2642            newgeom = attach_geom(two_d=true, region=newrgn, extent=type=="xrgn_extent", cp=cp),
2643            result2d = _find_anchor(anchor_xy, newgeom),
2644            pos = point3d(result2d[1], cp.z+anchor.z*L/2),
2645            vec = unit(point3d(result2d[2], anchor.z),UP),
2646            oang = atan2(vec.y,vec.x) + 90
2647        )
2648        [anchor, pos, vec, oang]
2649    ) :
2650    assert(false, "Unknown attachment geometry type.");
2651
2652
2653/// Internal Function: _is_shown()
2654// Usage:
2655//   bool = _is_shown();
2656/// Topics: Attachments
2657/// See Also: reorient(), attachable()
2658// Description:
2659//   Returns true if objects should currently be shown based on the tag settings.  
2660function _is_shown() =
2661    assert(is_list($tags_shown) || $tags_shown=="ALL")
2662    assert(is_list($tags_hidden))
2663    let(
2664        dummy=is_undef($tags) ? 0 : echo("Use tag() instead of $tags for specifying an object's tag."),
2665        $tag = default($tag,$tags)
2666    )
2667    assert(is_string($tag), str("Tag value (",$tag,") is not a string"))
2668    assert(undef==str_find($tag," "),str("Tag string \"",$tag,"\" contains a space, which is not allowed"))
2669    let(
2670        shown  = $tags_shown=="ALL" || in_list($tag,$tags_shown),
2671        hidden = in_list($tag, $tags_hidden)
2672    )
2673    shown && !hidden;
2674
2675
2676// Section: Visualizing Anchors
2677
2678/// Internal Function: _standard_anchors()
2679/// Usage:
2680///   anchs = _standard_anchors([two_d]);
2681/// Description:
2682///   Return the vectors for all standard anchors.
2683/// Arguments:
2684///   two_d = If true, returns only the anchors where the Z component is 0.  Default: false
2685function _standard_anchors(two_d=false) = [
2686    for (
2687        zv = [
2688            if (!two_d) TOP,
2689            CENTER,
2690            if (!two_d) BOTTOM
2691        ],
2692        yv = [FRONT, CENTER, BACK],
2693        xv = [LEFT, CENTER, RIGHT]
2694    ) xv+yv+zv
2695];
2696
2697
2698
2699// Module: show_anchors()
2700// Usage:
2701//   PARENT() show_anchors([s], [std=], [custom=]);
2702// Description:
2703//   Show all standard anchors for the parent object.
2704// Arguments:
2705//   s = Length of anchor arrows.
2706//   ---
2707//   std = If true show standard anchors.  Default: true
2708//   custom = If true show named anchors.  Default: true 
2709// Example(FlatSpin,VPD=333):
2710//   cube(50, center=true) show_anchors();
2711module show_anchors(s=10, std=true, custom=true) {
2712    check = assert($parent_geom != undef);
2713    two_d = _attach_geom_2d($parent_geom);
2714    if (std) {
2715        for (anchor=_standard_anchors(two_d=two_d)) {
2716            if(two_d) {
2717                attach(anchor) anchor_arrow2d(s);
2718            } else {
2719                attach(anchor) anchor_arrow(s);
2720            }
2721        }
2722    }
2723    if (custom) {
2724        for (anchor=last($parent_geom)) {
2725            attach(anchor[0]) {
2726                if(two_d) {
2727                    anchor_arrow2d(s, color="cyan");
2728                } else {
2729                    anchor_arrow(s, color="cyan");
2730                }
2731                color("black")
2732                tag("anchor-arrow") {
2733                    xrot(two_d? 0 : 90) {
2734                        back(s/3) {
2735                            yrot_copies(n=2)
2736                            up(two_d? 0.51 : s/30) {
2737                                linear_extrude(height=0.01, convexity=12, center=true) {
2738                                    text(text=anchor[0], size=s/4, halign="center", valign="center", font="Helvetica", $fn=36);
2739                                }
2740                            }
2741                        }
2742                    }
2743                }
2744                color([1, 1, 1, 1])
2745                tag("anchor-arrow") {
2746                    xrot(two_d? 0 : 90) {
2747                        back(s/3) {
2748                             cube([s/4.5*len(anchor[0]), s/3, 0.01], center=true);
2749                        }
2750                   }
2751                }
2752            }
2753        }
2754    }
2755    children();
2756}
2757
2758
2759// Module: anchor_arrow()
2760// Usage:
2761//   anchor_arrow([s], [color], [flag], [anchor=], [orient=], [spin=]) [ATTACHMENTS];
2762// Description:
2763//   Show an anchor orientation arrow.  By default, tagged with the name "anchor-arrow".
2764// Arguments:
2765//   s = Length of the arrows.  Default: `10`
2766//   color = Color of the arrow.  Default: `[0.333, 0.333, 1]`
2767//   flag = If true, draw the orientation flag on the arrowhead.  Default: true
2768//   ---
2769//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#subsection-anchor).  Default: `CENTER`
2770//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#subsection-spin).  Default: `0`
2771//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#subsection-orient).  Default: `UP`
2772// Example:
2773//   anchor_arrow(s=20);
2774module anchor_arrow(s=10, color=[0.333,0.333,1], flag=true, $tag="anchor-arrow", $fn=12, anchor=BOT, spin=0, orient=UP) {
2775    attachable(anchor,spin,orient, r=s/6, l=s) {
2776        down(s/2)
2777        recolor("gray") spheroid(d=s/6) {
2778            attach(CENTER,BOT) recolor(color) cyl(h=s*2/3, d=s/15) {
2779                attach(TOP,BOT) cyl(h=s/3, d1=s/5, d2=0) {
2780                    if(flag) {
2781                        position(BOT)
2782                            recolor([1,0.5,0.5])
2783                                cuboid([s/100, s/6, s/4], anchor=FRONT+BOT);
2784                    }
2785                }
2786            }
2787        }
2788        children();
2789    }
2790}
2791
2792
2793
2794// Module: anchor_arrow2d()
2795// Usage:
2796//   anchor_arrow2d([s], [color], [flag]);
2797// Description:
2798//   Show an anchor orientation arrow.
2799// Arguments:
2800//   s = Length of the arrows.
2801//   color = Color of the arrow.
2802// Example:
2803//   anchor_arrow2d(s=20);
2804module anchor_arrow2d(s=15, color=[0.333,0.333,1], $tag="anchor-arrow") {
2805    color(color) stroke([[0,0],[0,s]], width=s/10, endcap1="butt", endcap2="arrow2");
2806}
2807
2808
2809
2810// Module: expose_anchors()
2811// Usage:
2812//   expose_anchors(opacity) {child1() show_anchors(); child2() show_anchors(); ...}
2813// Description:
2814//   Used in combination with show_anchors() to display an object in transparent gray with its anchors in solid color.
2815//   Children will appear transparent and any anchor arrows drawn with will appear in solid color.  
2816// Arguments:
2817//   opacity = The opacity of the children.  0.0 is invisible, 1.0 is opaque.  Default: 0.2
2818// Example(FlatSpin,VPD=333):
2819//   expose_anchors() cube(50, center=true) show_anchors();
2820module expose_anchors(opacity=0.2) {
2821    show_only("anchor-arrow")
2822        children();
2823    hide("anchor-arrow")
2824        color(is_undef($color) || $color=="default" ? [0,0,0] :
2825              is_string($color) ? $color 
2826                                : point3d($color),
2827              opacity)
2828            children();
2829}
2830
2831
2832
2833// Module: frame_ref()
2834// Usage:
2835//   frame_ref(s, opacity);
2836// Description:
2837//   Displays X,Y,Z axis arrows in red, green, and blue respectively.
2838// Arguments:
2839//   s = Length of the arrows.
2840//   opacity = The opacity of the arrows.  0.0 is invisible, 1.0 is opaque.  Default: 1.0
2841// Examples:
2842//   frame_ref(25);
2843//   frame_ref(30, opacity=0.5);
2844module frame_ref(s=15, opacity=1) {
2845    cube(0.01, center=true) {
2846        attach([1,0,0]) anchor_arrow(s=s, flag=false, color=[1.0, 0.3, 0.3, opacity]);
2847        attach([0,1,0]) anchor_arrow(s=s, flag=false, color=[0.3, 1.0, 0.3, opacity]);
2848        attach([0,0,1]) anchor_arrow(s=s, flag=false, color=[0.3, 0.3, 1.0, opacity]);
2849        children();
2850    }
2851}
2852
2853
2854////////////////////////////////////////////////////////////////////////////////////////////////////
2855////////////////////////////////////////////////////////////////////////////////////////////////////
2856////////////////////////////////////////////////////////////////////////////////////////////////////
2857////////////////////////////////////////////////////////////////////////////////////////////////////
2858///
2859/// Code after this is internal code for managing edge and corner sets and for displaying
2860/// edge and corners in the docs
2861///
2862
2863module _edges_text3d(txt,size=3) {
2864    if (is_list(txt)) {
2865        for (i=idx(txt)) {
2866            down((i-len(txt)/2+0.5)*size*1.5) {
2867                _edges_text3d(txt[i], size=size);
2868            }
2869        }
2870    } else {
2871        xrot(90) color("#000")
2872        linear_extrude(height=0.1) {
2873            text(text=txt, size=size, halign="center", valign="center");
2874        }
2875    }
2876}
2877
2878
2879function _edges_vec_txt(x) = is_string(x)? str("\"", x, "\"") :
2880    assert(is_string(x) || is_vector(x,3), str(x))
2881    let(
2882        lst = concat(
2883            x.z>0? ["TOP"]   : x.z<0? ["BOT"]  : [],
2884            x.y>0? ["BACK"]  : x.y<0? ["FWD"]  : [],
2885            x.x>0? ["RIGHT"] : x.x<0? ["LEFT"] : []
2886        ),
2887        out = [
2888           for (i = idx(lst))
2889           i>0? str("+",lst[i]) : lst[i]
2890        ]
2891    ) out;
2892
2893
2894function _edges_text(edges) =
2895    is_string(edges) ? [str("\"",edges,"\"")] :
2896    edges==EDGES_NONE ? ["EDGES_NONE"] :
2897    edges==EDGES_ALL ? ["EDGES_ALL"] :
2898    _is_edge_array(edges) ? [""] :
2899    is_vector(edges,3) ? _edges_vec_txt(edges) :
2900    is_list(edges) ? let(
2901        lst = [for (x=edges) each _edges_text(x)],
2902        out = [
2903            for (i=idx(lst))
2904            str(
2905                (i==0? "[" : ""),
2906                lst[i],
2907                (i<len(lst)-1? "," : ""),
2908                (i==len(lst)-1? "]" : "")
2909            )
2910        ]
2911    ) out :
2912    [""];
2913
2914
2915
2916/// Internal Constant: EDGES_NONE
2917/// Topics: Edges
2918/// See Also: EDGES_ALL, edges()
2919/// Description:
2920///   The set of no edges.
2921/// Figure(3D):
2922///   _show_edges(edges="NONE");
2923EDGES_NONE = [[0,0,0,0], [0,0,0,0], [0,0,0,0]];
2924
2925
2926/// Internal Constant: EDGES_ALL
2927/// Topics: Edges
2928/// See Also: EDGES_NONE, edges()
2929/// Description:
2930///   The set of all edges.
2931/// Figure(3D):
2932///   _show_edges(edges="ALL");
2933EDGES_ALL = [[1,1,1,1], [1,1,1,1], [1,1,1,1]];
2934
2935
2936/// Internal Constant: EDGES_OFFSETS
2937/// Topics: Edges
2938/// See Also: EDGES_NONE, EDGES_ALL, edges()
2939/// Description:
2940///   The vectors pointing to the center of each edge of a unit sized cube.
2941///   Each item in an edge array will have a corresponding vector in this array.
2942EDGE_OFFSETS = [
2943    [
2944        [ 0,-1,-1],
2945        [ 0, 1,-1],
2946        [ 0,-1, 1],
2947        [ 0, 1, 1]
2948    ], [
2949        [-1, 0,-1],
2950        [ 1, 0,-1],
2951        [-1, 0, 1],
2952        [ 1, 0, 1]
2953    ], [
2954        [-1,-1, 0],
2955        [ 1,-1, 0],
2956        [-1, 1, 0],
2957        [ 1, 1, 0]
2958    ]
2959];
2960
2961
2962
2963/// Internal Function: _is_edge_array()
2964/// Topics: Edges, Type Checking
2965/// Usage:
2966///   bool = _is_edge_array(x);
2967/// Description:
2968///   Returns true if the given value has the form of an edge array.
2969/// Arguments:
2970///   x = The item to check the type of.
2971/// See Also: edges(), EDGES_NONE, EDGES_ALL
2972function _is_edge_array(x) = is_list(x) && is_vector(x[0]) && len(x)==3 && len(x[0])==4;
2973
2974
2975function _edge_set(v) =
2976    _is_edge_array(v)? v : [
2977    for (ax=[0:2]) [
2978        for (b=[-1,1], a=[-1,1]) let(
2979            v2=[[0,a,b],[a,0,b],[a,b,0]][ax]
2980        ) (
2981            is_string(v)? (
2982                v=="X"? (ax==0) :   // Return all X axis aligned edges.
2983                v=="Y"? (ax==1) :   // Return all Y axis aligned edges.
2984                v=="Z"? (ax==2) :   // Return all Z axis aligned edges.
2985                v=="ALL"? true :    // Return all edges.
2986                v=="NONE"? false :  // Return no edges.
2987                let(valid_values = ["X", "Y", "Z", "ALL", "NONE"])
2988                assert(
2989                    in_list(v, valid_values),
2990                    str(v, " must be a vector, edge array, or one of ", valid_values)
2991                ) v
2992            ) :
2993            let(nonz = sum(v_abs(v)))
2994            nonz==2? (v==v2) :  // Edge: return matching edge.
2995            let(
2996                matches = num_true([
2997                    for (i=[0:2]) v[i] && (v[i]==v2[i])
2998                ])
2999            )
3000            nonz==1? (matches==1) :  // Face: return surrounding edges.
3001            (matches==2)             // Corner: return touching edges.
3002        )? 1 : 0
3003    ]
3004];
3005
3006
3007/// Internal Function: _normalize_edges()
3008/// Topics: Edges
3009/// Usage:
3010///   edges = _normalize_edges(v);
3011/// Description:
3012///   Normalizes all values in an edge array to be `1`, if it was originally greater than `0`,
3013///   or `0`, if it was originally less than or equal to `0`.
3014/// See Also:  edges(), EDGES_NONE, EDGES_ALL
3015function _normalize_edges(v) = [for (ax=v) [for (edge=ax) edge>0? 1 : 0]];
3016
3017
3018
3019
3020/// Internal Function: _edges()
3021/// Topics: Edges
3022/// Usage:
3023///   edgs = _edges(v);
3024///   edgs = _edges(v, except);
3025///
3026/// Description:
3027///   Takes a list of edge set descriptors, and returns a normalized edges array
3028///   that represents all those given edges.  
3029/// Arguments:
3030///   v = The edge set to include.
3031///   except = The edge set to specifically exclude, even if they are in `v`.
3032///
3033/// See Also:  EDGES_NONE, EDGES_ALL
3034///
3035function _edges(v, except=[]) =
3036    v==[] ? EDGES_NONE :
3037    (is_string(v) || is_vector(v) || _is_edge_array(v))? _edges([v], except=except) :
3038    (is_string(except) || is_vector(except) || _is_edge_array(except))? _edges(v, except=[except]) :
3039    except==[]? _normalize_edges(sum([for (x=v) _edge_set(x)])) :
3040    _normalize_edges(
3041        _normalize_edges(sum([for (x=v) _edge_set(x)])) -
3042        sum([for (x=except) _edge_set(x)])
3043    );
3044
3045
3046/// Internal Module: _show_edges()
3047/// Topics: Edges, Debugging
3048/// Usage:
3049///   _show_edges(edges, [size=], [text=], [txtsize=]);
3050/// Description:
3051///   Draws a semi-transparent cube with the given edges highlighted in red.
3052/// Arguments:
3053///   edges = The edges to highlight.
3054///   size = The scalar size of the cube.
3055///   text = The text to show on the front of the cube.
3056///   txtsize = The size of the text.
3057/// See Also: _edges(), EDGES_NONE, EDGES_ALL
3058/// Example:
3059///   _show_edges(size=30, edges=["X","Y"]);
3060module _show_edges(edges="ALL", size=20, text, txtsize=3,toplabel) {
3061    edge_set = _edges(edges);
3062    text = !is_undef(text) ? text : _edges_text(edges);
3063    color("red") {
3064        for (axis=[0:2], i=[0:3]) {
3065            if (edge_set[axis][i] > 0) {
3066                translate(EDGE_OFFSETS[axis][i]*size/2) {
3067                    if (axis==0) xcyl(h=size, d=2);
3068                    if (axis==1) ycyl(h=size, d=2);
3069                    if (axis==2) zcyl(h=size, d=2);
3070                }
3071            }
3072        }
3073    }
3074    fwd(size/2) _edges_text3d(text, size=txtsize);
3075    color("yellow",0.7) cuboid(size=size);
3076    vpr = [55,0,25];
3077    color("black")
3078    if (is_def(toplabel))
3079      for(h=idx(toplabel)) up(21+6*h)rot(vpr) text3d(select(toplabel,-h-1),size=3.3,h=0.1,orient=UP,anchor=FRONT);
3080}
3081
3082
3083
3084
3085/// Internal Constant: CORNERS_NONE
3086/// Topics: Corners
3087/// Description:
3088///   The set of no corners.
3089/// Figure(3D):
3090///   _show_corners(corners="NONE");
3091/// See Also: CORNERS_ALL, corners()
3092CORNERS_NONE = [0,0,0,0,0,0,0,0];  // No corners.
3093
3094
3095/// Internal Constant: CORNERS_ALL
3096/// Topics: Corners
3097/// Description:
3098///   The set of all corners.
3099/// Figure(3D):
3100///   _show_corners(corners="ALL");
3101/// See Also: CORNERS_NONE, _corners()
3102CORNERS_ALL = [1,1,1,1,1,1,1,1];
3103
3104
3105/// Internal Constant: CORNER_OFFSETS
3106/// Topics: Corners
3107/// Description:
3108///   The vectors pointing to each corner of a unit sized cube.
3109///   Each item in a corner array will have a corresponding vector in this array.
3110/// See Also: CORNERS_NONE, CORNERS_ALL, _corners()
3111CORNER_OFFSETS = [
3112    [-1,-1,-1], [ 1,-1,-1], [-1, 1,-1], [ 1, 1,-1],
3113    [-1,-1, 1], [ 1,-1, 1], [-1, 1, 1], [ 1, 1, 1]
3114];
3115
3116
3117
3118
3119/// Internal Function: _is_corner_array()
3120/// Topics: Corners, Type Checking
3121/// Usage:
3122///   bool = _is_corner_array(x)
3123/// Description:
3124///   Returns true if the given value has the form of a corner array.
3125/// See Also: CORNERS_NONE, CORNERS_ALL, _corners()
3126function _is_corner_array(x) = is_vector(x) && len(x)==8 && all([for (xx=x) xx==1||xx==0]);
3127
3128
3129/// Internal Function: _normalize_corners()
3130/// Topics: Corners
3131/// Usage:
3132///   corns = _normalize_corners(v);
3133/// Description:
3134///   Normalizes all values in a corner array to be `1`, if it was originally greater than `0`,
3135///   or `0`, if it was originally less than or equal to `0`.
3136/// See Also: CORNERS_NONE, CORNERS_ALL, _corners()
3137function _normalize_corners(v) = [for (x=v) x>0? 1 : 0];
3138
3139
3140function _corner_set(v) =
3141    _is_corner_array(v)? v : [
3142    for (i=[0:7]) let(
3143        v2 = CORNER_OFFSETS[i]
3144    ) (
3145        is_string(v)? (
3146            v=="ALL"? true :    // Return all corners.
3147            v=="NONE"? false :  // Return no corners.
3148            let(valid_values = ["ALL", "NONE"])
3149            assert(
3150                in_list(v, valid_values),
3151                str(v, " must be a vector, corner array, or one of ", valid_values)
3152            ) v
3153        ) :
3154        all([for (i=[0:2]) !v[i] || (v[i]==v2[i])])
3155    )? 1 : 0
3156];
3157
3158
3159/// Function: _corners()
3160/// Topics: Corners
3161/// Usage:
3162///   corns = _corners(v);
3163///   corns = _corners(v, except);
3164/// Description:
3165///   Takes a list of corner set descriptors, and returns a normalized corners array
3166///   that represents all those given corners.  If the `except` argument is given
3167///   a list of corner set descriptors, then all those corners will be removed
3168///   from the returned corners array.  If either argument only has a single corner
3169///   set descriptor, you do not have to pass it in a list.
3170function _corners(v, except=[]) =
3171    v==[] ? CORNERS_NONE :
3172    (is_string(v) || is_vector(v) || _is_corner_array(v))? _corners([v], except=except) :
3173    (is_string(except) || is_vector(except) || _is_corner_array(except))? _corners(v, except=[except]) :
3174    except==[]? _normalize_corners(sum([for (x=v) _corner_set(x)])) :
3175    let(
3176        a = _normalize_corners(sum([for (x=v) _corner_set(x)])),
3177        b = _normalize_corners(sum([for (x=except) _corner_set(x)]))
3178    ) _normalize_corners(a - b);
3179
3180
3181/// Internal Function: _corner_edges()
3182/// Topics: Corners
3183/// Description:
3184///   Returns [XCOUNT,YCOUNT,ZCOUNT] where each is the count of edges aligned with that
3185///   axis that are in the edge set and touch the given corner.
3186/// Arguments:
3187///   edges = Standard edges array.
3188///   v = Vector pointing to the corner to count edge intersections at.
3189/// See Also: CORNERS_NONE, CORNERS_ALL, _corners()
3190function _corner_edges(edges, v) =
3191    let(u = (v+[1,1,1])/2) [edges[0][u.y+u.z*2], edges[1][u.x+u.z*2], edges[2][u.x+u.y*2]];
3192
3193
3194/// InternalFunction: _corner_edge_count()
3195/// Topics: Corners
3196/// Description:
3197///   Counts how many given edges intersect at a specific corner.
3198/// Arguments:
3199///   edges = Standard edges array.
3200///   v = Vector pointing to the corner to count edge intersections at.
3201/// See Also: CORNERS_NONE, CORNERS_ALL, _corners()
3202function _corner_edge_count(edges, v) =
3203    let(u = (v+[1,1,1])/2) edges[0][u.y+u.z*2] + edges[1][u.x+u.z*2] + edges[2][u.x+u.y*2];
3204
3205
3206function _corners_text(corners) =
3207    is_string(corners) ? [str("\"",corners,"\"")] :
3208    corners==CORNERS_NONE ? ["CORNERS_NONE"] :
3209    corners==CORNERS_ALL ? ["CORNERS_ALL"] :
3210    _is_corner_array(corners) ? [""] :
3211    is_vector(corners,3) ? _edges_vec_txt(corners) :
3212    is_list(corners) ? let(
3213        lst = [for (x=corners) each _corners_text(x)],
3214        out = [
3215            for (i=idx(lst))
3216            str(
3217                (i==0? "[" : ""),
3218                lst[i],
3219                (i<len(lst)-1? "," : ""),
3220                (i==len(lst)-1? "]" : "")
3221            )
3222        ]
3223    ) out :
3224    [""];
3225
3226
3227/// Internal Module: _show_corners()
3228/// Topics: Corners, Debugging
3229/// Usage:
3230///   _show_corners(corners, [size=], [text=], [txtsize=]);
3231/// Description:
3232///   Draws a semi-transparent cube with the given corners highlighted in red.
3233/// Arguments:
3234///   corners = The corners to highlight.
3235///   size = The scalar size of the cube.
3236///   text = If given, overrides the text to be shown on the front of the cube.
3237///   txtsize = The size of the text.
3238/// See Also: CORNERS_NONE, CORNERS_ALL, corners()
3239/// Example:
3240///   _show_corners(corners=FWD+RIGHT, size=30);
3241module _show_corners(corners="ALL", size=20, text, txtsize=3,toplabel) {
3242    corner_set = _corners(corners);
3243    text = !is_undef(text) ? text : _corners_text(corners);
3244    for (i=[0:7]) if (corner_set[i]>0)
3245        translate(CORNER_OFFSETS[i]*size/2)
3246            color("red") sphere(d=2, $fn=16);
3247    fwd(size/2) _edges_text3d(text, size=txtsize);
3248    color("yellow",0.7) cuboid(size=size);
3249    vpr = [55,0,25];
3250    color("black")
3251    if (is_def(toplabel))
3252      for(h=idx(toplabel)) up(21+6*h)rot(vpr) text3d(select(toplabel,-h-1),size=3.3,h=.1,orient=UP,anchor=FRONT);
3253}
3254
3255module _show_cube_faces(faces, size=20, toplabel,botlabel) {
3256   color("red")
3257     for(f=faces){
3258          move(f*size/2) rot(from=UP,to=f)
3259             cuboid([size,size,.1]);
3260     }
3261    
3262   vpr = [55,0,25];
3263   color("black"){
3264   if (is_def(toplabel))
3265     for(h=idx(toplabel)) up(21+6*h)rot(vpr) text3d(select(toplabel,-h-1),size=3.3,h=.1,orient=UP,anchor=FRONT);
3266   if (is_def(botlabel))
3267     for(h=idx(botlabel)) down(26+6*h)rot(vpr) text3d(botlabel[h],size=3.3,h=.1,orient=UP,anchor=FRONT);
3268   }
3269   color("yellow",0.7) cuboid(size=size);
3270}
3271
3272// vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap