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 ways different from 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$anchor_override = undef;
  25$attach_to = undef;
  26$attach_anchor = [CENTER, CENTER, UP, 0];
  27$attach_norot = false;
  28
  29$parent_anchor = BOTTOM;
  30$parent_spin = 0;
  31$parent_orient = UP;
  32
  33$parent_size = undef;
  34$parent_geom = undef;
  35
  36$tags_shown = "ALL";
  37$tags_hidden = [];
  38
  39_ANCHOR_TYPES = ["intersect","hull"];
  40
  41
  42// Section: Terminology and Shortcuts
  43//   This library adds the concept of anchoring, spin and orientation to the `cube()`, `cylinder()`
  44//   and `sphere()` builtins, as well as to most of the shapes provided by this library itself.
  45//   - An anchor is a place on an object which you can align the object to, or attach other objects
  46//     to using `attach()` or `position()`. An anchor has a position, a direction, and a spin.
  47//     The direction and spin are used to orient other objects to match when using `attach()`.
  48//   - Spin is a simple rotation around the Z axis.
  49//   - Orientation is rotating an object so that its top is pointed towards a given vector.
  50//   An object will first be translated to its anchor position, then spun, then oriented.
  51//   For a detailed step-by-step explanation of attachments, see the [Attachments Tutorial](Tutorial-Attachments).
  52//   .
  53//   For describing directions, faces, edges, and corners the library provides a set of shortcuts
  54//   all based on combinations of unit direction vectors.  You can use these for anchoring and orienting
  55//   attachable objects.  You can also them to specify edge sets for rounding or chamfering cuboids,
  56//   or for placing edge, face and corner masks.
  57// Subsection: Anchor
  58//   Anchoring is specified with the `anchor` argument in most shape modules.  Specifying `anchor`
  59//   when creating an object will translate the object so that the anchor point is at the origin
  60//   (0,0,0).  Anchoring always occurs before spin and orientation are applied.
  61//   .
  62//   An anchor can be referred to in one of two ways; as a directional vector, or as a named anchor string.
  63//   .
  64//   When given as a vector, it points, in a general way, towards the face, edge, or corner of the
  65//   object that you want the anchor for, relative to the center of the object.  You can simply
  66//   specify a vector like `[0,0,1]` to anchor an object at the Z+ end, but you can also use
  67//   directional constants with names like `TOP`, `BOTTOM`, `LEFT`, `RIGHT` and `BACK` that you can add together
  68//   to specify anchor points.  See [specifying directions](attachments.scad#subsection-specifying-directions)
  69//   below for the full list of pre-defined directional constants.
  70//   .
  71//   For example:
  72//   - `[0,0,1]` is the same as `TOP` and refers to the center of the top face.
  73//   - `[-1,0,1]` is the same as `TOP+LEFT`, and refers to the center of the top-left edge.
  74//   - `[1,1,-1]` is the same as `BOTTOM+BACK+RIGHT`, and refers to the bottom-back-right corner.
  75//   .
  76//   When the object is cubical or rectangular in shape the anchors must have zero or one values
  77//   for their components and they refer to the face centers, edge centers, or corners of the object.
  78//   The direction of a face anchor will be perpendicular to the face, pointing outward.  The direction of a edge anchor
  79//   will be the average of the anchor directions of the two faces the edge is between.  The direction
  80//   of a corner anchor will be the average of the anchor directions of the three faces the corner is
  81//   on.
  82//   .
  83//   When the object is cylindrical, conical, or spherical in nature, the anchors will be located
  84//   around the surface of the cylinder, cone, or sphere, relative to the center.
  85//   You can generally use an arbitrary vector to get an anchor positioned anywhere on the curved
  86//   surface of such an object, and the anchor direction will be the surface normal at the anchor location.
  87//   However, for anchor component pointing toward the flat face should be either -1, 1, or 0, and
  88//   anchors that point diagonally toward one of the flat faces will select a point on the edge.
  89//   .
  90//   For objects in two dimensions, the natural expectation is for TOP and BOTTOM to refer to the Y direction
  91//   of the shape.  To support this, if you give an anchor in 2D that has anchor.y=0 then the Z component
  92//   will be mapped to the Y direction.  This  means you can use TOP and BOTTOM for anchors of 2D objects.
  93//   But remember that TOP and BOTTOM are three dimensional vectors and this is a special interpretation
  94//   for 2d anchoring.
  95//   .
  96//   Some more complex objects, like screws and stepper motors, have named anchors to refer to places
  97//   on the object that are not at one of the standard faces, edges or corners.  For example, stepper
  98//   motors have anchors for `"screw1"`, `"screw2"`, etc. to refer to the various screwholes on the
  99//   stepper motor shape.  The names, positions, directions, and spins of these anchors are
 100//   specific to the object, and are documented when they exist.
 101// Subsection: Spin
 102//   Spin is specified with the `spin` argument in most shape modules.  Specifying a scalar `spin`
 103//   when creating an object will rotate the object counter-clockwise around the Z axis by the given
 104//   number of degrees.  If given as a 3D vector, the object will be rotated around each of the X, Y, Z
 105//   axes by the number of degrees in each component of the vector.  Spin is always applied after
 106//   anchoring, and before orientation.  Since spin is applied after anchoring it is not what
 107//   you might think of intuitively as spinning the shape.  To do that, apply `zrot()` to the shape before anchoring.
 108// Subsection: Orient
 109//   Orientation is specified with the `orient` argument in most shape modules.  Specifying `orient`
 110//   when creating an object will rotate the object such that the top of the object will be pointed
 111//   at the vector direction given in the `orient` argument.  Orientation is always applied after
 112//   anchoring and spin.  The constants `UP`, `DOWN`, `FRONT`, `BACK`, `LEFT`, and `RIGHT` can be
 113//   added together to form the directional vector for this.  ie: `LEFT+BACK`
 114// Subsection: Specifying Directions
 115//   You can use direction vectors to specify anchors for objects or to specify edges, faces, and
 116//   corners of cubes.  You can simply specify these direction vectors numerically, but another
 117//   option is to use named constants for direction vectors.  These constants define unit vectors
 118//   for the six axis directions as shown below.
 119// Figure(3D,Big,VPD=6): Named constants for direction vectors.  Some directions have more than one name.
 120//   $fn=12;
 121//   stroke([[0,0,0],RIGHT], endcap2="arrow2", width=.05);
 122//   color("black")right(.05)up(.05)move(RIGHT) text3d("RIGHT",size=.1,h=.01,anchor=LEFT,orient=FRONT);
 123//   stroke([[0,0,0],LEFT], endcap2="arrow2", width=.05);
 124//   color("black")left(.05)up(.05)move(LEFT) text3d("LEFT",size=.1,h=.01,anchor=RIGHT,orient=FRONT);
 125//   stroke([[0,0,0],FRONT], endcap2="arrow2", width=.05);
 126//   color("black")
 127//   left(.1){
 128//   up(.12)move(FRONT) text3d("FRONT",size=.1,h=.01,anchor=RIGHT,orient=FRONT);
 129//   move(FRONT) text3d("FWD",size=.1,h=.01,anchor=RIGHT,orient=FRONT);
 130//   down(.12)move(FRONT) text3d("FORWARD",size=.1,h=.01,anchor=RIGHT,orient=FRONT);
 131//   }
 132//   stroke([[0,0,0],BACK], endcap2="arrow2", width=.05);
 133//   right(.05)
 134//   color("black")move(BACK) text3d("BACK",size=.1,h=.01,anchor=LEFT,orient=FRONT);
 135//   stroke([[0,0,0],DOWN], endcap2="arrow2", width=.05);
 136//   color("black")
 137//   right(.1){
 138//   up(.12)move(BOT) text3d("DOWN",size=.1,h=.01,anchor=LEFT,orient=FRONT);
 139//   move(BOT) text3d("BOTTOM",size=.1,h=.01,anchor=LEFT,orient=FRONT);
 140//   down(.12)move(BOT) text3d("BOT",size=.1,h=.01,anchor=LEFT,orient=FRONT);
 141//   }
 142//   stroke([[0,0,0],TOP], endcap2="arrow2", width=.05);
 143//   color("black")left(.05){
 144//   up(.12)move(TOP) text3d("TOP",size=.1,h=.01,anchor=RIGHT,orient=FRONT);
 145//   move(TOP) text3d("UP",size=.1,h=.01,anchor=RIGHT,orient=FRONT);
 146//   }
 147// 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.
 148//   $fn=12;
 149//   stroke(path2d([[0,0,0],RIGHT]), endcap2="arrow2", width=.05);
 150//   color("black")fwd(.22)left(.05)move(RIGHT) text("RIGHT",size=.1,anchor=RIGHT);
 151//   stroke(path2d([[0,0,0],LEFT]), endcap2="arrow2", width=.05);
 152//   color("black")right(.05)fwd(.22)move(LEFT) text("LEFT",size=.1,anchor=LEFT);
 153//   stroke(path2d([[0,0,0],FRONT]), endcap2="arrow2", width=.05);
 154//   color("black")
 155//   fwd(.2)
 156//   right(.15)
 157//   color("black")move(BACK) { text("BACK",size=.1,anchor=LEFT); back(.14) text("(TOP)", size=.1, anchor=LEFT);}
 158//   color("black")
 159//   left(.15)back(.2+.14)move(FRONT){
 160//   back(.14) text("FRONT",size=.1,anchor=RIGHT);
 161//       text("FWD",size=.1,anchor=RIGHT);
 162//   fwd(.14) text("FORWARD",size=.1,anchor=RIGHT);
 163//   fwd(.28) text("(BOTTOM)",size=.1,anchor=RIGHT);
 164//   fwd(.14*3) text("(BOT)",size=.1,anchor=RIGHT);
 165//   }
 166//   stroke(path2d([[0,0,0],BACK]), endcap2="arrow2", width=.05);
 167// Subsection: Specifying Faces
 168//   Modules operating on faces accept a list of faces to describe the faces to operate on.  Each
 169//   face is given by a vector that points to that face.  Attachments of cuboid objects onto their faces also
 170//   work by choosing an attachment face with a single vector in the same manner.
 171// Figure(3D,Big,NoScales,VPD=275): The six faces of the cube.  Some have faces have more than one name.
 172//   ydistribute(50) {
 173//      xdistribute(35){
 174//        _show_cube_faces([BACK], botlabel=["BACK"]);
 175//        _show_cube_faces([UP],botlabel=["TOP","UP"]);
 176//        _show_cube_faces([RIGHT],botlabel=["RIGHT"]);
 177//      }
 178//      xdistribute(35){
 179//        _show_cube_faces([FRONT],toplabel=["FRONT","FWD", "FORWARD"]);
 180//        _show_cube_faces([DOWN],toplabel=["BOTTOM","BOT","DOWN"]);
 181//        _show_cube_faces([LEFT],toplabel=["LEFT"]);
 182//      }
 183//   }
 184// Subsection: Specifying Edges
 185//   Modules operating on edges use two arguments to describe the edge set they will use: The `edges` argument
 186//   is a list of edge set descriptors to include in the edge set, and the `except` argument is a list of
 187//   edge set descriptors to remove from the edge set.
 188//   The default value for `edges` is `"ALL"`, the set of all edges.
 189//   The default value for `except` is the    empty set, meaning no edges are removed.
 190//   If either argument is just a single edge set
 191//   descriptor it can be passed directly rather than in a singleton list.
 192//   Each edge set descriptor must be one of:
 193//   - A vector pointing towards an edge, indicating that single edge.
 194//   - A vector pointing towards a face, indicating all edges surrounding that face.
 195//   - A vector pointing towards a corner, indicating all edges touching that corner.
 196//   - The string `"X"`, indicating all X axis aligned edges.
 197//   - The string `"Y"`, indicating all Y axis aligned edges.
 198//   - The string `"Z"`, indicating all Z axis aligned edges.
 199//   - The string `"ALL"`, indicating all edges.
 200//   - The string `"NONE"`, indicating no edges at all.
 201//   - 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:
 202//       ```
 203//       [
 204//           [Y-Z-, Y+Z-, Y-Z+, Y+Z+],
 205//           [X-Z-, X+Z-, X-Z+, X+Z+],
 206//           [X-Y-, X+Y-, X-Y+, X+Y+]
 207//       ]
 208//       ```
 209//   You can specify edge descriptors directly by giving a vector, or you can use sums of the
 210//   named direction vectors described above.  Below we show all of the edge sets you can
 211//   describe with sums of the direction vectors, and then we show some examples of combining
 212//   edge set descriptors.
 213// Figure(3D,Big,VPD=300,NoScales): Vectors pointing toward an edge select that single edge
 214//   ydistribute(50) {
 215//       xdistribute(30) {
 216//           _show_edges(edges=BOT+RIGHT);
 217//           _show_edges(edges=BOT+BACK);
 218//           _show_edges(edges=BOT+LEFT);
 219//           _show_edges(edges=BOT+FRONT);
 220//       }
 221//       xdistribute(30) {
 222//           _show_edges(edges=FWD+RIGHT);
 223//           _show_edges(edges=BACK+RIGHT);
 224//           _show_edges(edges=BACK+LEFT);
 225//           _show_edges(edges=FWD+LEFT);
 226//       }
 227//       xdistribute(30) {
 228//           _show_edges(edges=TOP+RIGHT);
 229//           _show_edges(edges=TOP+BACK);
 230//           _show_edges(edges=TOP+LEFT);
 231//           _show_edges(edges=TOP+FRONT);
 232//       }
 233//   }
 234// Figure(3D,Med,VPD=205,NoScales): Vectors pointing toward a face select all edges surrounding that face.
 235//   ydistribute(50) {
 236//       xdistribute(30) {
 237//           _show_edges(edges=LEFT);
 238//           _show_edges(edges=FRONT);
 239//           _show_edges(edges=RIGHT);
 240//       }
 241//       xdistribute(30) {
 242//           _show_edges(edges=TOP);
 243//           _show_edges(edges=BACK);
 244//           _show_edges(edges=BOTTOM);
 245//       }
 246//   }
 247// Figure(3D,Big,VPD=300,NoScales): Vectors pointing toward a corner select all edges surrounding that corner.
 248//   ydistribute(50) {
 249//       xdistribute(30) {
 250//           _show_edges(edges=FRONT+LEFT+TOP);
 251//           _show_edges(edges=FRONT+RIGHT+TOP);
 252//           _show_edges(edges=FRONT+LEFT+BOT);
 253//           _show_edges(edges=FRONT+RIGHT+BOT);
 254//       }
 255//       xdistribute(30) {
 256//           _show_edges(edges=TOP+LEFT+BACK);
 257//           _show_edges(edges=TOP+RIGHT+BACK);
 258//           _show_edges(edges=BOT+LEFT+BACK);
 259//           _show_edges(edges=BOT+RIGHT+BACK);
 260//       }
 261//   }
 262// Figure(3D,Med,VPD=205,NoScales): Named Edge Sets
 263//   ydistribute(50) {
 264//       xdistribute(30) {
 265//           _show_edges(edges="X");
 266//           _show_edges(edges="Y");
 267//           _show_edges(edges="Z");
 268//       }
 269//       xdistribute(30) {
 270//           _show_edges(edges="ALL");
 271//           _show_edges(edges="NONE");
 272//       }
 273//   }
 274// 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 and top faces.  Adding `except` removes an edge.
 275//   xdistribute(43){
 276//     _show_edges(_edges([0,-1,1]),toplabel=["edges=[0,-1,1]"]);
 277//     _show_edges(_edges(TOP+FRONT),toplabel=["edges=TOP+FRONT"]);
 278//     _show_edges(_edges([TOP,FRONT]),toplabel=["edges=[TOP,FRONT]"]);
 279//     _show_edges(_edges([TOP,FRONT],TOP+FRONT),toplabel=["edges=[TOP,FRONT]","except=TOP+FRONT"]);
 280//   }
 281// 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.
 282//   xdistribute(43){
 283//     _show_edges(_edges(BOT,BACK), toplabel=["edges=BOT","except=BACK"]);
 284//     _show_edges(_edges("Z",BACK), toplabel=["edges=\"Z\"", "except=BACK"]);
 285//     _show_edges(_edges("ALL",BACK), toplabel=["(edges=\"ALL\")", "except=BACK"]);
 286//     _show_edges(_edges("Y",BACK), toplabel=["edges=\"Y\"","except=BACK"]);
 287//   }
 288// 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.
 289//   xdistribute(52){
 290//    _show_edges(_edges("ALL",[FRONT+RIGHT,FRONT+LEFT]),
 291//               toplabel=["except=[FRONT+RIGHT,","       FRONT+LEFT]"]);
 292//    _show_edges(_edges([1,-1,1]),toplabel=["edges=[1,-1,1]"]);
 293//    _show_edges(_edges([TOP,BOT], TOP+RIGHT+FRONT),toplabel=["edges=[TOP,BOT]","except=TOP+RIGHT+FRONT"]);
 294//   }
 295// Subsection: Specifying Corners
 296//   Modules operating on corners use two arguments to describe the corner set they will use: The `corners` argument
 297//   is a list of corner set descriptors to include in the corner set, and the `except` argument is a list of
 298//   corner set descriptors to remove from the corner set.
 299//   The default value for `corners` is `"ALL"`, the set of all corners.
 300//   The default value for `except` is the   empty set, meaning no corners are removed.
 301//   If either argument is just a single corner set
 302//   descriptor it can be passed directly rather than in a singleton list.
 303//   Each corner set descriptor must be one of:
 304//   - A vector pointing towards a corner, indicating that corner.
 305//   - A vector pointing towards an edge indicating both corners at the ends of that edge.
 306//   - A vector pointing towards a face, indicating all the corners of that face.
 307//   - The string `"ALL"`, indicating all corners.
 308//   - The string `"NONE"`, indicating no corners at all.
 309//   - 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
 310//       ```
 311//       [X-Y-Z-, X+Y-Z-, X-Y+Z-, X+Y+Z-, X-Y-Z+, X+Y-Z+, X-Y+Z+, X+Y+Z+]
 312//       ```
 313//   You can specify corner descriptors directly by giving a vector, or you can use sums of the
 314//   named direction vectors described above.  Below we show all of the corner sets you can
 315//   describe with sums of the direction vectors and then we show some examples of combining
 316//   corner set descriptors.
 317// Figure(3D,Big,NoScales,VPD=300): Vectors pointing toward a corner select that corner.
 318//   ydistribute(55) {
 319//       xdistribute(35) {
 320//           _show_corners(corners=FRONT+LEFT+TOP);
 321//           _show_corners(corners=FRONT+RIGHT+TOP);
 322//           _show_corners(corners=FRONT+LEFT+BOT);
 323//           _show_corners(corners=FRONT+RIGHT+BOT);
 324//       }
 325//       xdistribute(35) {
 326//           _show_corners(corners=TOP+LEFT+BACK);
 327//           _show_corners(corners=TOP+RIGHT+BACK);
 328//           _show_corners(corners=BOT+LEFT+BACK);
 329//           _show_corners(corners=BOT+RIGHT+BACK);
 330//       }
 331//   }
 332// Figure(3D,Big,NoScales,VPD=340): Vectors pointing toward an edge select the corners and the ends of the edge.
 333//   ydistribute(55) {
 334//       xdistribute(35) {
 335//           _show_corners(corners=BOT+RIGHT);
 336//           _show_corners(corners=BOT+BACK);
 337//           _show_corners(corners=BOT+LEFT);
 338//           _show_corners(corners=BOT+FRONT);
 339//       }
 340//       xdistribute(35) {
 341//           _show_corners(corners=FWD+RIGHT);
 342//           _show_corners(corners=BACK+RIGHT);
 343//           _show_corners(corners=BACK+LEFT);
 344//           _show_corners(corners=FWD+LEFT);
 345//       }
 346//       xdistribute(35) {
 347//           _show_corners(corners=TOP+RIGHT);
 348//           _show_corners(corners=TOP+BACK);
 349//           _show_corners(corners=TOP+LEFT);
 350//           _show_corners(corners=TOP+FRONT);
 351//       }
 352//   }
 353// Figure(3D,Med,NoScales,VPD=225): Vectors pointing toward a face select the corners of the face.
 354//   ydistribute(55) {
 355//       xdistribute(35) {
 356//           _show_corners(corners=LEFT);
 357//           _show_corners(corners=FRONT);
 358//           _show_corners(corners=RIGHT);
 359//       }
 360//       xdistribute(35) {
 361//           _show_corners(corners=TOP);
 362//           _show_corners(corners=BACK);
 363//           _show_corners(corners=BOTTOM);
 364//       }
 365//   }
 366// Figure(3D,Med,NoScales,VPD=200): Corners by name
 367//   xdistribute(35) {
 368//       _show_corners(corners="ALL");
 369//       _show_corners(corners="NONE");
 370//   }
 371// 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.
 372//   xdistribute(52){
 373//     _show_corners(_corners([1,-1,-1]),toplabel=["corners=[1,-1,-1]"]);
 374//     _show_corners(_corners(BOT+RIGHT+FRONT),toplabel=["corners=BOT+RIGHT+FRONT"]);
 375//     _show_corners(_corners([FRONT,RIGHT]), toplabel=["corners=[FRONT,RIGHT]"]);
 376//   }
 377// 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:
 378//    xdistribute(52){
 379//      _show_corners(_corners(FRONT+TOP), toplabel=["corners=FRONT+TOP"]);
 380//       _show_corners(_corners([FRONT+TOP,BOT+BACK]), toplabel=["corners=[FRONT+TOP,","        BOT+BACK]"]);
 381//       _show_corners(_corners("ALL",FRONT+TOP), toplabel=["(corners=\"ALL\")","except=FRONT+TOP"]);
 382//    }
 383// 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.
 384//    xdistribute(58){
 385//       _show_corners(_corners(TOP,[1,1,1]), toplabel=["corners=TOP","except=[1,1,1]"]);
 386//       _show_corners(_corners("ALL",[FRONT+RIGHT+TOP,FRONT+LEFT+BOT]),
 387//                    toplabel=["except=[FRONT+RIGHT+TOP,","       FRONT+LEFT+BOT]"]);
 388//    }
 389// Subsection: Anchoring of Non-Rectangular Objects and Anchor Type (atype)
 390//   We focused above on rectangular objects that have well-defined faces and edges aligned with the coordinate axes.
 391//   Things get difficult when the objects are curved, or even when their edges are not neatly aligned with the coordinate axes.
 392//   In these cases, the library may provide multiple different anchoring schemes, called the anchor types.  When a module supports
 393//   multiple anchor types, use the `atype=` parameter to select the anchor type you need.
 394// .
 395//   First consider the case of a simple rectangle whose corners have been rounded.  Where should the anchors lie?
 396//   The default anchor type puts them in the same location as the anchors of an unrounded rectangle, which means that for
 397//   positive rounding radii, they are not even located on the perimeter of the object.
 398// Figure(2D,Med,NoAxes): Default "box" atype anchors for a rounded {{rect()}}
 399//   rect([100,50], rounding=[10,0,0,-20],chamfer=[0,10,-20,0]) show_anchors();
 400// Continues:
 401//   This choice enables you to position the box, or attach things to it, without regard to its rounding or chamfers.  If you need to
 402//   anchor onto the roundovers or chamfers then you can use the "perim" anchor type:
 403// Figure(2D,Med,NoAxes): The "perim" atype for a rounded and chamfered {{rect()}}
 404//   rect([100,50], rounding=[10,0,0,-20],chamfer=[0,10,-20,0],atype="perim") show_anchors();
 405// Continues:
 406//   With this anchor type, the anchors are located on the perimeter.  For positive roundings they point in the standard anchor direction;
 407//   for negative roundings they are parallel to the base.  As noted above, for circles, cylinders, and spheres, the anchor point is
 408//   determined by choosing the point where the anchor vector intersects the shape.  On a circle, this results in an anchor whose direction
 409//   matches the user provided anchor vector.  But on an ellipse, something else happens:
 410// Figure(2D,Med,NoAxes): Anchors on an ellipse.  The red arrow shows a TOP+RIGHT anchor direction. 
 411//   ellipse([70,30]) show_anchors();
 412//   stroke([[0,0],[45,45]], color="red",endcap2="arrow2");
 413// Continues:
 414//   For a TOP+RIGHT anchor direction, the surface normal at the intersection point does not match the anchor direction,
 415//   so the direction of the anchor shown in blue does not match the direction specified, in red.
 416//   Anchors computed this way have anchor type "intersect".  When a shape is concave, intersection anchors can produce
 417//   a result buried inside the shape's concavity.  Consider the RIGHT anchor of this supershape example:
 418// Figure(2D,Med,NoAxes): A supershape with "intersect" anchor type:
 419//   supershape(n=150,r=75, m1=4, n1=4.0,n2=16, n3=1.5, a=0.9, b=9,atype="intersect") show_anchors();
 420// Continues:
 421//   A different anchor type called "hull" finds anchors that are on the convex hull of the shape.  
 422// Figure(2D,Med,NoAxes): A supershape with "hull" anchor type:
 423//   supershape(n=150,r=55, m1=4, n1=4.0,n2=16, n3=1.5, a=0.9, b=9,atype="hull") show_anchors();
 424// Continues:
 425//   Hull anchoring works by creating the line (or plane in 3D) that is normal to the specified anchor direction, and
 426//   finding the point farthest from the center that intersects that line (or plane).
 427// Figure(2D,Med,NoAxes): Finding the RIGHT and BACK+LEFT "hull" anchors
 428//   supershape(n=128,r=55, m1=4, n1=4.0,n2=16, n3=1.5, a=0.9, b=9,atype="hull") {
 429//     position(RIGHT) color_this("red")rect([1,90],anchor=LEFT);
 430//     attach(RIGHT)anchor_arrow2d(13);
 431//     attach(BACK+LEFT) {
 432//        anchor_arrow2d(13);
 433//        color_this("red")rect([30,1]);
 434//        }
 435//     }
 436// Continues:
 437//   In the example the RIGHT anchor is found when the normal line (shown in red) is tangent to the shape at two points.
 438//   The anchor is then taken to be the midpoint.  The BACK+LEFT anchor occurs with a single tangent point, and the
 439//   anchor point is located at the tangent point.  For circles intersection is done to the exact circle, but for other
 440//   shapes these calculations are done on the point lists that defines the shape, so if you change the number of points
 441//   in the list, the precise location of the anchors can change.  You can also get surprising results if your point list is badly chosen.
 442// Figure(2D,Med,NoAxes): Circle anchor in blue.  The red anchor is computed to a point list of a circle with 17 segments.  
 443//   circle(r=31,$fn=128) attach(TOP)anchor_arrow2d(15);
 444//   region(circle(r=33,$fn=17)) {color("red")attach(TOP)anchor_arrow2d(13);}
 445// Continues:
 446//   The figure shows a large horizontal offset due to a poor choice of sampling for the circular shape when using the "hull" anchor type.
 447//   The determination of "hull" or "intersect" anchors may depend on the location of the centerpoint used in the computation.
 448//   Some of the modules allow you to change the centerpoint using a `cp=` argument.  If you need to change the centerpoint for
 449//   a module that does not provide this option, you can use the generic {{region()}} module, which will let you specify a centerpoint.
 450//   The default center point is the centroid, specified by "centroid".  You can also choose "mean", which gives the mean of all
 451//   the data points, or "bbox", which gives the centerpoint of the bounding box for the data.  Your last option for centerpoint is to
 452//   choose an arbitrary point that meets your needs.
 453// Figure(2D,Med,NoAxes): The centerpoint for "intersect" anchors is located at the red dot
 454//   region(supershape(n=128,r=55, m1=4, n1=4.0,n2=16, n3=1.5, a=0.9, b=9),atype="intersect",cp=[0,30]) show_anchors();
 455//   color("red")back(30)circle(r=2,$fn=16);
 456// Continues:
 457//   Note that all the anchors for an object have to be determined based on one anchor type and relative to the same centerpoint.
 458//   The supported anchor types for each module appear in the "Anchor Types" section of its entry.  
 459
 460
 461
 462
 463
 464// Section: Attachment Positioning
 465
 466// Module: position()
 467// Synopsis: Attaches children to a parent object at an anchor point.
 468// SynTags: Trans
 469// Topics: Attachments
 470// See Also: attachable(), attach(), orient()
 471// Usage:
 472//   PARENT() position(from) CHILDREN;
 473// Description:
 474//   Attaches children to a parent object at an anchor point.  For a step-by-step explanation
 475//   of attachments, see the [Attachments Tutorial](Tutorial-Attachments).
 476// Arguments:
 477//   from = The vector, or name of the parent anchor point to attach to.
 478// Side Effects:
 479//   `$attach_anchor` for each `from=` anchor given, this is set to the `[ANCHOR, POSITION, ORIENT, SPIN]` information for that anchor.
 480//   `$attach_to` is set to `undef`.
 481//   `$attach_norot` is set to `true`.
 482// Example:
 483//   spheroid(d=20) {
 484//       position(TOP) cyl(l=10, d1=10, d2=5, anchor=BOTTOM);
 485//       position(RIGHT) cyl(l=10, d1=10, d2=5, anchor=BOTTOM);
 486//       position(FRONT) cyl(l=10, d1=10, d2=5, anchor=BOTTOM);
 487//   }
 488module position(from)
 489{
 490    req_children($children);
 491    dummy1=assert($parent_geom != undef, "No object to position relative to.");
 492    anchors = (is_vector(from)||is_string(from))? [from] : from;
 493    two_d = _attach_geom_2d($parent_geom);
 494    for (anchr = anchors) {
 495        anch = _find_anchor(anchr, $parent_geom);
 496        $attach_to = undef;
 497        $attach_anchor = anch;
 498        $attach_norot = true;
 499        translate(anch[1]) children();
 500    }
 501}
 502
 503
 504
 505// Module: orient()
 506// Synopsis: Orients children's tops in the directon of the specified anchor.
 507// SynTags: Trans
 508// Topics: Attachments
 509// See Also: attachable(), attach(), position()
 510// Usage:
 511//   PARENT() orient(anchor, [spin]) CHILDREN;
 512// Description:
 513//   Orients children such that their top is tilted in the direction of the specified parent anchor point. 
 514//   For a step-by-step explanation of attachments, see the [Attachments Tutorial](Tutorial-Attachments).
 515// Arguments:
 516//   anchor = The anchor on the parent which you want to match the orientation of.
 517//   spin = The spin to add to the children.  (Overrides anchor spin.)
 518// Side Effects:
 519//   `$attach_to` is set to `undef`.
 520//   `$attach_norot` is set to `true`.
 521// Example: When orienting to an anchor, the spin of the anchor may cause confusion:
 522//   prismoid([50,50],[30,30],h=40) {
 523//       position(TOP+RIGHT)
 524//           orient(RIGHT)
 525//               prismoid([30,30],[0,5],h=20,anchor=BOT+LEFT);
 526//   }
 527// Example: You can override anchor spin with `spin=`.
 528//   prismoid([50,50],[30,30],h=40) {
 529//       position(TOP+RIGHT)
 530//           orient(RIGHT,spin=0)
 531//               prismoid([30,30],[0,5],h=20,anchor=BOT+LEFT);
 532//   }
 533// Example: Or you can anchor the child from the back
 534//   prismoid([50,50],[30,30],h=40) {
 535//       position(TOP+RIGHT)
 536//           orient(RIGHT)
 537//               prismoid([30,30],[0,5],h=20,anchor=BOT+BACK);
 538//   }
 539module orient(anchor, spin) {
 540    req_children($children);
 541    check=
 542      assert($parent_geom != undef, "No parent to orient from!")
 543      assert(is_string(anchor) || is_vector(anchor));
 544    anch = _find_anchor(anchor, $parent_geom);
 545    two_d = _attach_geom_2d($parent_geom);
 546    fromvec = two_d? BACK : UP;
 547    spin = default(spin, anch[3]);
 548    dummy=assert(is_finite(spin));
 549
 550    $attach_to = undef;
 551    $attach_norot = true;
 552    if (two_d)
 553        rot(spin)rot(from=fromvec, to=anch[2]) children();
 554    else
 555        rot(spin, from=fromvec, to=anch[2]) children();
 556}
 557
 558
 559
 560// Module: align()
 561// Synopsis: Position and orient children with alignment to parent edges.
 562// SynTags: Trans
 563// Topics: Attachments
 564// See Also: attachable(), attach(), position(), orient()
 565// Usage:
 566//   PARENT() align(anchor, [orient], [spin], [inside=]) CHILDREN;
 567// Description:
 568//   Positions children to the specified anchor(s) on the parent and anchors the
 569//   children so that they are aligned with the edge(s) of the parent at those parent anchors.
 570//   You can specify a parent anchor point in `orient` and in this case, the top of the child
 571//   is tilted in the direction of that anchor.
 572//   This means you can easily place children so they are aligned flush with edges of the parent.
 573//   In contrast, with {{position()}} you will have to work out the correct anchor for the children
 574//   which is not always obvious.  It also enables you to place several children that have different
 575//   anchors, which would otherwise require several {{position()}} calls.  The inside argument
 576//   causes the object to appear inside the parent for use with {{diff()}}.  
 577//   .
 578//   When you use `align()`, the `orient=` and `anchor=` arguments to the child objects are overriden,
 579//   so they do not have any effect.  The `spin=` argument to the child still applies. 
 580// Arguments:
 581//   anchor = parent anchor or list of parent anchors for positioning children
 582//   orient = parent anchor to give direction for orienting the children.  Default: UP
 583//   spin = spin in degrees for rotating the children.  Default: Derived from orient anchor
 584//   ---
 585//   inside = if true, place object inside the parent instead of outside.  Default: false
 586// Side Effects:
 587//   `$attach_anchor` for each anchor given, this is set to the `[ANCHOR, POSITION, ORIENT, SPIN]` information for that anchor.
 588//   `$attach_to` is set to `undef`.
 589//   `$attach_norot` is set to `true`.
 590//   `$anchor_override` is set to the anchor required for proper positioning of the child.  
 591//   if inside is true then set default tag to "remove"
 592// Example: Child would require anchor of RIGHT+FRONT+BOT if placed with {{position()}}. 
 593//   cuboid([50,40,15])
 594//     align(RIGHT+FRONT+TOP)
 595//       color("lightblue")prismoid([10,5],[7,4],height=4);
 596// Example: Child requires a different anchor for each position, so explicit specification of the anchor for children is impossible in this case, without using two separate commands.
 597//   cuboid([50,40,15])
 598//     align([RIGHT+TOP,LEFT+TOP])
 599//       color("lightblue")prismoid([10,5],[7,4],height=4);
 600// Example: If you try to spin your child, the spin happens after the alignment anchor, so the child will not be flush:
 601//   cuboid([50,40,15])
 602//     align([RIGHT+TOP])
 603//       color("lightblue")
 604//          prismoid([10,5],[7,4],height=4,spin=90);
 605// Example: You can instead spin the attached children using the spin parameter to `align()`.  In this example, the required anchor is BOT+FWD, which is less obvious.
 606//   cuboid([50,40,15])
 607//     align(RIGHT+TOP,spin=90)
 608//       color("lightblue")prismoid([10,5],[7,4],height=4);
 609// Example: Here the child is oriented to the RIGHT, so it appears flush with the top.  In this case you don't have to figure out that the required child anchor is BOT+BACK.  
 610//   cuboid([50,40,15])
 611//     align(RIGHT+TOP,RIGHT)
 612//       color("lightblue")prismoid([10,5],[7,4],height=4);
 613// Example: If you change the orientation the child still appears aligned flush in its changed orientation:
 614//   cuboid([50,40,15])
 615//     align(RIGHT+TOP,DOWN)
 616//       color("lightblue")prismoid([10,5],[7,4],height=4);
 617// Example: Objects on the right already have nonzero spin by default, so setting spin=0 changes the spin:
 618//   prismoid(50,30,25){
 619//     align(RIGHT+TOP,RIGHT,spin=0)
 620//       color("lightblue")prismoid([10,5],[7,4],height=4);
 621//     align(RIGHT+BOT,RIGHT)
 622//       color("green")prismoid([10,5],[7,4],height=4);
 623//   }
 624// Example: Setting inside=true enables us to subtract the child from the parent with {{diff()}.  The "remove" tag is automatically applied when you set `inside=true`.  
 625//   diff()
 626//     cuboid([40,30,10])
 627//       move(.1*[0,-1,1])
 628//         align(FRONT+TOP,inside=true)
 629//           prismoid([10,5],[7,5],height=4);
 630module align(anchor,orient=UP,spin,inside=false)
 631{
 632    req_children($children);
 633    dummy1=assert($parent_geom != undef, "No object to align to.")
 634           assert(is_string(orient) || is_vector(orient),"Bad orient value");
 635    position_anchors = (is_vector(anchor)||is_string(anchor))? [anchor] : anchor;
 636    two_d = _attach_geom_2d($parent_geom);
 637    fromvec = two_d? BACK : UP;
 638
 639    orient_anch = _find_anchor(orient, $parent_geom);
 640    spin = default(spin, orient_anch[3]);
 641    dummy2=assert(is_finite(spin));
 642    
 643    $attach_to = undef;
 644    $attach_norot = true;
 645
 646    factor = inside?1:-1;
 647    
 648    for (thisanch = position_anchors) {
 649        pos_anch = _find_anchor(thisanch, $parent_geom);
 650        init_anch = two_d ? rot(from=orient_anch[2], to=fromvec, p=zrot(-spin,pos_anch[0]))
 651                          : rot(spin, from=fromvec, to=orient_anch[2], reverse=true, p=pos_anch[0]);
 652        quant_anch = [for(v=init_anch) sign(round(v))];
 653        $anchor_override = two_d && quant_anch.y!=0 ? [quant_anch.x,factor*quant_anch.y]
 654                         : !two_d && quant_anch.z!=0 ? [quant_anch.x,quant_anch.y, factor*quant_anch.z]
 655                         : factor*quant_anch;
 656        $attach_anchor = pos_anch;
 657        translate(pos_anch[1]) {
 658            if (two_d)
 659                rot(spin)rot(from=fromvec, to=orient_anch[2])
 660                  default_tag("remove",inside) children();
 661            else
 662                rot(spin, from=fromvec, to=orient_anch[2])
 663                  default_tag("remove",inside) children();                  
 664        }
 665    }
 666}
 667
 668
 669
 670
 671
 672// Module: attach()
 673// Synopsis: Attaches children to a parent object at an anchor point and orientation.
 674// SynTags: Trans
 675// Topics: Attachments
 676// See Also: attachable(), position(), face_profile(), edge_profile(), corner_profile()
 677// Usage:
 678//   PARENT() attach(from, [overlap=], [norot=]) CHILDREN;
 679//   PARENT() attach(from, to, [overlap=], [norot=]) CHILDREN;
 680// Description:
 681//   Attaches children to a parent object at an anchor point and orientation.  Attached objects will
 682//   be overlapped into the parent object by a little bit, as specified by the `$overlap`
 683//   value (0 by default), or by the overriding `overlap=` argument.  This is to prevent OpenSCAD
 684//   from making non-manifold objects.  You can define `$overlap=` as an argument in a parent
 685//   module to set the default for all attachments to it.  For a step-by-step explanation of
 686//   attachments, see the [Attachments Tutorial](Tutorial-Attachments).
 687// Arguments:
 688//   from = The vector, or name of the parent anchor point to attach to.
 689//   to = Optional name of the child anchor point.  If given, orients the child such that the named anchors align together rotationally.
 690//   ---
 691//   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.
 692//   norot = If true, don't rotate children when attaching to the anchor point.  Only translate to the anchor point.
 693// Side Effects:
 694//   `$idx` is set to the index number of each anchor if a list of anchors is given.  Otherwise is set to `0`.
 695//   `$attach_anchor` for each `from=` anchor given, this is set to the `[ANCHOR, POSITION, ORIENT, SPIN]` information for that anchor.
 696//   `$attach_to` is set to the value of the `to=` argument, if given.  Otherwise, `undef`
 697//   `$attach_norot` is set to the value of the `norot=` argument.
 698// Example:
 699//   spheroid(d=20) {
 700//       attach(TOP) down(1.5) cyl(l=11.5, d1=10, d2=5, anchor=BOTTOM);
 701//       attach(RIGHT, BOTTOM) down(1.5) cyl(l=11.5, d1=10, d2=5);
 702//       attach(FRONT, BOTTOM, overlap=1.5) cyl(l=11.5, d1=10, d2=5);
 703//   }
 704module attach(from, to, overlap, norot=false)
 705{
 706    req_children($children);
 707    assert($parent_geom != undef, "No object to attach to!");
 708    overlap = (overlap!=undef)? overlap : $overlap;
 709    anchors = (is_vector(from)||is_string(from))? [from] : from;
 710    for ($idx = idx(anchors)) {
 711        anchr = anchors[$idx];
 712        anch = _find_anchor(anchr, $parent_geom);
 713        two_d = _attach_geom_2d($parent_geom);
 714        $attach_to = to;
 715        $attach_anchor = anch;
 716        $attach_norot = norot;
 717        olap = two_d? [0,-overlap,0] : [0,0,-overlap];
 718        if (norot || (norm(anch[2]-UP)<1e-9 && anch[3]==0)) {
 719            translate(anch[1]) translate(olap) children();
 720        } else {
 721            fromvec = two_d? BACK : UP;
 722            translate(anch[1]) rot(anch[3],from=fromvec,to=anch[2]) translate(olap) children();
 723        }
 724    }
 725}
 726
 727// Section: Tagging
 728
 729// Module: tag()
 730// Synopsis: Assigns a tag to an object
 731// Topics: Attachments
 732// See Also: force_tag(), recolor(), hide(), show_only(), diff(), intersect()
 733// Usage:
 734//   PARENT() tag(tag) CHILDREN;
 735// Description:
 736//   Assigns the specified tag to all of the children. Note that if you want
 737//   to apply a tag to non-tag-aware objects you need to use {{force_tag()}} instead.
 738//   This works by setting the `$tag` variable, but it provides extra error checking and
 739//   handling of scopes.  You may set `$tag` directly yourself, but this is not recommended.
 740//   .
 741//   For a step-by-step explanation of attachments, see the [Attachments Tutorial](Tutorial-Attachments).
 742// Arguments:
 743//   tag = tag string, which must not contain any spaces.
 744// Side Effects:
 745//   Sets `$tag` to the tag you specify, possibly with a scope prefix.
 746// Example(3D):  Applies the tag to both cuboids instead of having to repeat `$tag="remove"` for each one.
 747//   diff("remove")
 748//     cuboid(10){
 749//       position(TOP) cuboid(3);
 750//       tag("remove")
 751//       {
 752//         position(FRONT) cuboid(3);
 753//         position(RIGHT) cuboid(3);
 754//       }
 755//     }
 756module tag(tag)
 757{
 758    req_children($children);
 759    check=
 760      assert(is_string(tag),"tag must be a string")
 761      assert(undef==str_find(tag," "),str("Tag string \"",tag,"\" contains a space, which is not allowed"));
 762    $tag = str($tag_prefix,tag);
 763    children();
 764}
 765
 766
 767// Module: force_tag()
 768// Synopsis: Assigns a tag to a non-attachable object.
 769// Topics: Attachments
 770// See Also: tag(), recolor(), hide(), show_only(), diff(), intersect()
 771// Usage:
 772//   PARENT() force_tag([tag]) CHILDREN;
 773// Description:
 774//   You use this module when you want to make a non-attachable or non-BOSL2 module respect tags.
 775//   It applies to its children the tag specified (or the tag currently in force if you don't specify a tag),
 776//   making a final determination about whether to show or hide the children.
 777//   This means that tagging in children's children will be ignored.
 778//   This module is specifically provided for operating on children that are not tag aware such as modules
 779//   that don't use {{attachable()}} or built in modules such as
 780//   - `polygon()`
 781//   - `projection()`
 782//   - `polyhedron()`  (or use [`vnf_polyhedron()`](vnf.scad#vnf_polyhedron))
 783//   - `linear_extrude()`  (or use [`linear_sweep()`](regions.scad#linear_sweep))
 784//   - `rotate_extrude()`
 785//   - `surface()`
 786//   - `import()`
 787//   - `difference()`
 788//   - `intersection()`
 789//   - `hull()`
 790//   .
 791//   When you use tag-based modules like {{diff()}} with a non-attachable module, the result may be puzzling.
 792//   Any time a test occurs for display of child() that test will succeed.  This means that when diff() checks
 793//   to see if it should show a module it will show it, and when diff() checks to see if it should subtract the module
 794//   it will subtract it.  The result will be a hole, possibly with zero-thickness edges or faces.  In order to
 795//   get the correct behavior, every non-attachable module needs an invocation of force_tag, even ones
 796//   that are not tagged.
 797//   .
 798//   For a step-by-step explanation of attachments, see the [Attachments Tutorial](Tutorial-Attachments).
 799// Arguments:
 800//   tag = tag string, which must not contain any spaces
 801// Side Effects:
 802//   Sets `$tag` to the tag you specify, possibly with a scope prefix.
 803// Example(2D): This example produces the full square without subtracting the "remove" item.  When you use non-attachable modules with tags, results are unpredictable.
 804//   diff()
 805//   {
 806//     polygon(square(10));
 807//     move(-[.01,.01])polygon(square(5),$tag="remove");
 808//   }
 809// 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.
 810//   diff()
 811//   {
 812//     force_tag()
 813//       polygon(square(10));
 814//     force_tag("remove")
 815//       move(-[.01,.01])polygon(square(5));
 816//   }
 817module force_tag(tag)
 818{
 819    req_children($children);
 820    check1=assert(is_undef(tag) || is_string(tag),"tag must be a string");
 821    $tag = str($tag_prefix,default(tag,$tag));
 822    assert(undef==str_find($tag," "),str("Tag string \"",$tag,"\" contains a space, which is not allowed"));
 823    if(_is_shown())
 824      show_all()
 825        children();
 826}
 827
 828
 829
 830// Module: default_tag()
 831// Synopsis: Sets a default tag for all children.
 832// Topics: Attachments
 833// See Also: force_tag(), recolor(), hide(), show_only(), diff(), intersect()
 834// Usage:
 835//   PARENT() default_tag(tag) CHILDREN;
 836// Description:
 837//   Sets a default tag for all of the children.  This is intended to be used to set a tag for a whole module
 838//   that is then used outside the module, such as setting the tag to "remove" for easy operation with {{diff()}}.
 839//   The default_tag() module sets the `$tag` variable only if it is not already
 840//   set so you can have a module set a default tag of "remove" but that tag can be overridden by a {{tag()}}
 841//   in force from a parent.  If you use {{tag()}} it will override any previously
 842//   specified tag from a parent, which can be very confusing to a user trying to change the tag on a module.
 843//   The `do_tag` parameter allows you to apply a default tag conditionally without having to repeat the children.  
 844//   .
 845//   For a step-by-step explanation of attachments, see the [Attachments Tutorial](Tutorial-Attachments).
 846// Arguments:
 847//   tag = tag string, which must not contain any spaces.
 848//   do_tag = if false do not set the tag.  
 849// Side Effects:
 850//   Sets `$tag` to the tag you specify, possibly with a scope prefix.
 851// Example(3D):  The module thing() is defined with {{tag()}} and the user applied tag of "keep_it" is ignored, leaving the user puzzled.
 852//   module thing() { tag("remove") cuboid(10);}
 853//   diff()
 854//     cuboid(20){
 855//       position(TOP) thing();
 856//       position(RIGHT) tag("keep_it") thing();
 857//   }
 858// Example(3D):  Using default_tag() fixes this problem: the user applied tag does not get overridden by the tag hidden in the module definition.
 859//   module thing() { default_tag("remove") cuboid(10);}
 860//   diff()
 861//     cuboid(20){
 862//       position(TOP) thing();
 863//       position(RIGHT) tag("keep_it") thing();
 864//   }
 865module default_tag(tag,do_tag=true)
 866{
 867    if ($tag=="" && do_tag) tag(tag) children();
 868    else children();
 869}
 870
 871
 872// Module: tag_scope()
 873// Synopsis: Creates a new tag scope.
 874// See Also: tag(), force_tag(), default_tag()
 875// Topics: Attachments
 876// Usage:
 877//   tag_scope([scope]) CHILDREN;
 878// Description:
 879//   Creates a tag scope with locally altered tag names to avoid tag name conflict with other code.
 880//   This is necessary when writing modules because the module's caller might happen to use the same tags.
 881//   Note that if you directly set the `$tag` variable then tag scoping will not work correctly.
 882// Side Effects:
 883//   `$tag_prefix` is set to the value of `scope=` if given, otherwise is set to a random string.
 884// Example: In this example the ring module uses "remove" tags which will conflict with use of the same tags by the parent.
 885//   module ring(r,h,w=1,anchor,spin,orient)
 886//   {
 887//     tag_scope("ringscope")
 888//       attachable(anchor,spin,orient,r=r,h=h){
 889//         diff()
 890//           cyl(r=r,h=h)
 891//             tag("remove") cyl(r=r-w,h=h+1);
 892//         children();
 893//       }
 894//   }
 895//   // Calling the module using "remove" tags
 896//   // will conflict with internal tag use in
 897//   // the ring module.
 898//   $fn=32;
 899//   diff(){
 900//       ring(10,7,w=4);
 901//       tag("remove")ring(8,8);
 902//       tag("remove")diff("rem"){
 903//          ring(9.5,8,w=1);
 904//          tag("rem")ring(9.5,8,w=.3);
 905//       }
 906//     }
 907module tag_scope(scope){
 908  req_children($children);
 909  scope = is_undef(scope) ? rand_str(20) : scope;
 910  assert(is_string(scope), "scope must be a string");
 911  assert(undef==str_find(scope," "),str("Scope string \"",scope,"\" contains a space, which is not allowed"));
 912  $tag_prefix=scope;
 913  children();
 914}
 915
 916
 917// Section: Attachment Modifiers
 918
 919// Module: diff()
 920// Synopsis: Performs a differencing operation using tags rather than hierarchy to control what happens.
 921// Topics: Attachments
 922// See Also: tag(), force_tag(), recolor(), show_only(), hide(), tag_diff(), intersect(), tag_intersect()
 923// Usage:
 924//   diff([remove], [keep]) PARENT() CHILDREN;
 925// Description:
 926//   Performs a differencing operation using tags to control what happens.  This is specifically intended to
 927//   address the situation where you want differences between a parent and child object, something
 928//   that is impossible with the native difference() module.
 929//   The children to diff are grouped into three categories, regardless of nesting level.
 930//   The `remove` argument is a space delimited list of tags specifying objects to
 931//   subtract.  The `keep` argument is a similar list of tags giving objects to be kept.
 932//   Objects not matching either the `remove` or `keep` lists form the third category of base objects.
 933//   To produce its output, diff() forms the union of all the base objects and then
 934//   subtracts all the objects with tags in `remove`.  Finally it adds in objects listed in `keep`.
 935//   Attachable objects should be tagged using {{tag()}}
 936//   and non-attachable objects with {{force_tag()}}.
 937//   .
 938//   Remember when using tagged operations with that the operations don't happen in hierarchical order, since
 939//   the point of tags is to break the hierarchy.  If you tag an object with a keep tag, nothing will be
 940//   subtracted from it, no matter where it appears because kept objects are unioned in at the end.
 941//   If you want a child of an object tagged with a remove tag to stay in the model it may be
 942//   better to give it a tag that is not a remove tag or a keep tag.  Such an object *will* be subject to
 943//   subtractions from other remove-tagged objects.
 944//   .
 945//   Note that `diff()` invokes its children three times.
 946//   .
 947//   For a step-by-step explanation of attachments, see the [Attachments Tutorial](Tutorial-Attachments).
 948// Arguments:
 949//   remove = String containing space delimited set of tag names of children to difference away.  Default: `"remove"`
 950//   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"`
 951// Example: Diffing using default tags
 952//   diff()
 953//   cuboid(50) {
 954//       tag("remove") attach(TOP) sphere(d=40);
 955//       tag("keep") attach(CTR) cylinder(h=40, d=10);
 956//   }
 957// Example: The "hole" items are subtracted from everything else.  The other tags can be anything you find convenient.
 958//   diff("hole")
 959//     tag("body")sphere(d=100) {
 960//       tag("pole") zcyl(d=55, h=100);  // attach() not needed for center-to-center.
 961//       tag("hole") {
 962//          xcyl(d=55, h=101);
 963//          ycyl(d=55, h=101);
 964//       }
 965//       tag("axle")zcyl(d=15, h=140);
 966//     }
 967// Example:
 968//   diff(keep="axle")
 969//   sphere(d=100) {
 970//       tag("axle")xcyl(d=40, l=120);
 971//       tag("remove")cuboid([40,120,100]);
 972//   }
 973// Example: Masking
 974//   diff()
 975//   cube([80,90,100], center=true) {
 976//       edge_mask(FWD)
 977//           rounding_edge_mask(l=max($parent_size)*1.01, r=25);
 978//   }
 979// 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.
 980//  diff()
 981//     tag("remove")cuboid(10)
 982//       tag("")position(RIGHT+BACK)cyl(r=8,h=9);
 983// 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:
 984//   $fn=16;
 985//   // This module must be called by subtracting with "diff"
 986//   module pipe(length, od, id) {
 987//       // Strip the tag the user is using to subtract
 988//       tag("")cylinder(h=length, d=od, center=true);
 989//       // Leave the tag alone here, so this one is removed
 990//       cylinder(h=length+.02, d=id, center=true);
 991//   }
 992//   // Draw some intersecting pipes
 993//   diff(){
 994//     tag("remove"){
 995//       pipe(length=5, od=2, id=1.9);
 996//       zrot(10)xrot(75)
 997//         pipe(length=5, od=2, id=1.9);
 998//     }
 999//     // The orange bar has its center removed
1000//     color("orange") down(1) xcyl(h=8, d=1);
1001//     // "keep" preserves the interior of the blue bar intact
1002//     tag("keep") recolor("blue") up(1) xcyl(h=8, d=1);
1003//   }
1004//   // Objects outside the diff don't have pipe interiors removed
1005//   color("purple") down(2.2) ycyl(h=8, d=0.3);
1006// Example(3D,NoScales,NoAxes): Nested diff() calls work as expected, but be careful of reusing tag names, even hidden in submodules.
1007//   $fn=32;
1008//   diff("rem1")
1009//   cyl(r=10,h=10){
1010//     diff("rem2",$tag="rem1"){
1011//       cyl(r=8,h=11);
1012//       tag("rem2")diff("rem3"){
1013//           cyl(r=6,h=12);
1014//           tag("rem3")cyl(r=4,h=13);
1015//           }
1016//       }
1017//   }
1018// 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.
1019//   $fn=32;
1020//   diff("remA")
1021//     cyl(r=9, h=6)
1022//       tag("remA")diff("remB")
1023//         left(.2)position(RIGHT)cyl(r=8,h=7,anchor=RIGHT)
1024//           tag("remB")diff("remA")
1025//            left(.2)position(LEFT)cyl(r=7,h=7,anchor=LEFT)
1026//              tag("remA")diff("remB")
1027//                left(.2)position(LEFT)cyl(r=6,h=8,anchor=LEFT)
1028//                  tag("remB")diff("remA")
1029//                    right(.2)position(RIGHT)cyl(r=5,h=9,anchor=RIGHT)
1030//                      tag("remA")diff("remB")
1031//                        right(.2)position(RIGHT)cyl(r=4,h=10,anchor=RIGHT)
1032//                          tag("remB")left(.2)position(LEFT)cyl(r=3,h=11,anchor=LEFT);
1033// Example(3D,NoAxes,NoScales): When working with Non-Attachables like rotate_extrude() you must apply {{force_tag()}} to every non-attachable object.
1034//   back_half()
1035//     diff("remove")
1036//       cuboid(40) {
1037//         attach(TOP)
1038//           recolor("lightgreen")
1039//             cyl(l=10,d=30);
1040//         position(TOP+RIGHT)
1041//           force_tag("remove")
1042//             xrot(90)
1043//               rotate_extrude()
1044//                 right(20)
1045//                   circle(5);
1046//       }
1047// 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.
1048//  $fn=32;
1049//  diff()
1050//    cuboid(10){
1051//      force_tag("remove")intersection()
1052//        {
1053//          position(RIGHT) cyl(r=7,h=15);
1054//          position(LEFT) cyl(r=7,h=15);
1055//        }
1056//      tag("keep")cyl(r=1,h=9);
1057//    }
1058// Example: In this example the children that are subtracted are each at different nesting levels, with a kept object in between.
1059//   $fn=32;
1060//   diff()
1061//     cuboid(10){
1062//       tag("remove")cyl(r=4,h=11)
1063//         tag("keep")cyl(r=3,h=17)
1064//           tag("remove")position(RIGHT)cyl(r=2,h=18);
1065//     }
1066// 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.
1067//   $fn=32;
1068//   intersect("keep","fullkeep")
1069//     diff(keep="fullkeep keep")
1070//       cuboid(10){
1071//         tag("remove")cyl(r=4,h=11);
1072//         tag("keep") position(RIGHT)cyl(r=8,h=12);
1073//         tag("fullkeep")cyl(r=1,h=12);
1074//     }
1075// 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.
1076//   $fn=64;
1077//   diff()
1078//     intersect(keep="remove keep")
1079//       cuboid(10,$thing="cube"){
1080//         tag("intersect"){
1081//           position(RIGHT) cyl(r=5.5,h=15)
1082//              tag("")cyl(r=2,h=10);
1083//           position(LEFT) cyl(r=5.54,h=15)
1084//              tag("keep")cyl(r=2,h=10);
1085//         }
1086//         // Untagged it is in the intersection
1087//         tag("") position(BACK+RIGHT)
1088//           cyl(r=2,h=10,anchor=CTR);
1089//         // With keep the full cylinder appears
1090//         tag("keep") position(BACK+LEFT)
1091//           cyl(r=2,h=10,anchor=CTR);
1092//         tag("remove") cyl(r=3,h=15);
1093//       }
1094module diff(remove="remove", keep="keep")
1095{
1096    req_children($children);
1097    assert(is_string(remove),"remove must be a string of tags");
1098    assert(is_string(keep),"keep must be a string of tags");
1099    if (_is_shown())
1100    {
1101        difference() {
1102            hide(str(remove," ",keep)) children();
1103            show_only(remove) children();
1104        }
1105    }
1106    show_int(keep)children();
1107}
1108
1109
1110// Module: tag_diff()
1111// Synopsis: Performs a {{diff()}} and then sets a tag on the result.
1112// Topics: Attachments
1113// See Also: tag(), force_tag(), recolor(), show_only(), hide(), diff(), intersect(), tag_intersect()
1114// Usage:
1115//   tag_diff(tag, [remove], [keep]) PARENT() CHILDREN;
1116// Description:
1117//   Perform a differencing operation in the manner of {{diff()}} using tags to control what happens,
1118//   and then tag the resulting difference object with the specified tag.  This forces the specified
1119//   tag to be resolved at the level of the difference operation.  In most cases, this is not necessary,
1120//   but if you have kept objects and want to operate on this difference object as a whole object using
1121//   more tag operations, you will probably not get the results you want if you simply use {{tag()}}.
1122//   .
1123//   For a step-by-step explanation of attachments, see the [Attachments Tutorial](Tutorial-Attachments).
1124// Arguments:
1125//   tag = Tag string to apply to this difference object
1126//   remove = String containing space delimited set of tag names of children to difference away.  Default: `"remove"`
1127//   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"`
1128// Side Effects:
1129//   Sets `$tag` to the tag you specify, possibly with a scope prefix.
1130// 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:
1131//   diff("rem"){
1132//     cuboid([20,10,30],anchor=FRONT);
1133//     tag("rem")diff("remove","keep"){
1134//       cuboid([10,10,20]);
1135//       tag("remove")cuboid([11,11,5]);
1136//       tag("keep")cuboid([2,2,20]);
1137//     }
1138//   }
1139// Example: Using tag_diff corrects the problem:
1140//   diff("rem"){
1141//     cuboid([20,10,30],anchor=FRONT);
1142//       tag_diff("rem","remove","keep"){
1143//         cuboid([10,10,20]);
1144//         tag("remove")cuboid([11,11,5]);
1145//         tag("keep")cuboid([2,2,20]);
1146//       }
1147//   }
1148// 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.
1149//   diff("rem","nothing")
1150//     cyl(r=8,h=6)
1151//       tag("rem")diff()
1152//         cyl(r=7,h=7)
1153//           tag("remove")cyl(r=6,h=8)
1154//           tag("keep")cyl(r=5,h=9);
1155// Example: Changing to tag_diff() causes the kept cylinder to be subtracted, producing the desired result:
1156//   diff("rem")
1157//     cyl(r=8,h=6)
1158//       tag_diff("rem")
1159//         cyl(r=7,h=7)
1160//           tag("remove")cyl(r=6,h=8)
1161//           tag("keep")cyl(r=5,h=9);
1162module tag_diff(tag,remove="remove", keep="keep")
1163{
1164    req_children($children);
1165    assert(is_string(remove),"remove must be a string of tags");
1166    assert(is_string(keep),"keep must be a string of tags");
1167    assert(is_string(tag),"tag must be a string");
1168    assert(undef==str_find(tag," "),str("Tag string \"",tag,"\" contains a space, which is not allowed"));
1169    $tag=str($tag_prefix,tag);
1170    if (_is_shown())
1171      show_all(){
1172         difference() {
1173            hide(str(remove," ",keep)) children();
1174            show_only(remove) children();
1175         }
1176         show_only(keep)children();
1177      }
1178}
1179
1180
1181// Module: intersect()
1182// Synopsis: Perform an intersection operation on children using tags rather than hierarchy to control what happens.
1183// Topics: Attachments
1184// See Also: tag(), force_tag(), recolor(), show_only(), hide(), diff(), tag_diff(), tag_intersect()
1185// Usage:
1186//   intersect([intersect], [keep]) PARENT() CHILDREN;
1187// Description:
1188//   Performs an intersection operation on its children, using tags to
1189//   determine what happens.  This is specifically intended to address
1190//   the situation where you want intersections involving a parent and
1191//   child object, something that is impossible with the native
1192//   intersection() module.  This module treats the children in three
1193//   groups: objects matching the tags listed in `intersect`, objects
1194//   matching tags listed in `keep`, and the remaining objects that
1195//   don't match any of the listed tags.  The intersection is computed
1196//   between the union of the `intersect` tagged objects and union of the objects that don't
1197//   match any of the listed tags.  Finally the objects listed in `keep` are
1198//   unioned with the result.  Attachable objects should be tagged using {{tag()}}
1199//   and non-attachable objects with {{force_tag()}}.
1200//   .
1201//   Note that `intersect()` invokes its children three times.
1202//   .
1203//   For a step-by-step explanation of attachments, see the [Attachments Tutorial](Tutorial-Attachments).
1204// Arguments:
1205//   intersect = String containing space delimited set of tag names of children to intersect.  Default: "intersect"
1206//   keep = String containing space delimited set of tag names of children to keep whole.  Default: "keep"
1207// Example:
1208//   intersect("mask", keep="axle")
1209//     sphere(d=100) {
1210//         tag("mask")cuboid([40,100,100]);
1211//         tag("axle")xcyl(d=40, l=100);
1212//     }
1213// 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.
1214//   $fn=32;
1215//   intersect("keep","fullkeep")
1216//     diff(keep="fullkeep keep")
1217//       cuboid(10){
1218//         tag("remove")cyl(r=4,h=11);
1219//         tag("keep") position(RIGHT)cyl(r=8,h=12);
1220//         tag("fullkeep")cyl(r=1,h=12);
1221//     }
1222// 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.
1223//   $fn=64;
1224//   diff()
1225//     intersect(keep="remove keep")
1226//       cuboid(10,$thing="cube"){
1227//         tag("intersect"){
1228//           position(RIGHT) cyl(r=5.5,h=15)
1229//              tag("")cyl(r=2,h=10);
1230//           position(LEFT) cyl(r=5.54,h=15)
1231//              tag("keep")cyl(r=2,h=10);
1232//         }
1233//         // Untagged it is in the intersection
1234//         tag("") position(BACK+RIGHT)
1235//           cyl(r=2,h=10,anchor=CTR);
1236//         // With keep the full cylinder appears
1237//         tag("keep") position(BACK+LEFT)
1238//           cyl(r=2,h=10,anchor=CTR);
1239//         tag("remove") cyl(r=3,h=15);
1240//       }
1241module intersect(intersect="intersect",keep="keep")
1242{
1243   assert(is_string(intersect),"intersect must be a string of tags");
1244   assert(is_string(keep),"keep must be a string of tags");
1245   intersection(){
1246      show_only(intersect) children();
1247      hide(str(intersect," ",keep)) children();
1248   }
1249   show_int(keep) children();
1250}
1251
1252
1253// Module: tag_intersect()
1254// Synopsis: Performs an {{intersect()}} and then tags the result.
1255// Topics: Attachments
1256// See Also: tag(), force_tag(), recolor(), show_only(), hide(), diff(), tag_diff(), intersect()
1257// Usage:
1258//   tag_intersect(tag, [intersect], [keep]) PARENT() CHILDREN;
1259// Description:
1260//   Perform an intersection operation in the manner of {{intersect()}} using tags to control what happens,
1261//   and then tag the resulting difference object with the specified tag.  This forces the specified
1262//   tag to be resolved at the level of the intersect operation.  In most cases, this is not necessary,
1263//   but if you have kept objects and want to operate on this difference object as a whole object using
1264//   more tag operations, you will probably not get the results you want if you simply use {{tag()}}.
1265//   .
1266//   For a step-by-step explanation of attachments, see the [Attachments Tutorial](Tutorial-Attachments).
1267// Arguments:
1268//   tag = Tag to set for the intersection
1269//   intersect = String containing space delimited set of tag names of children to intersect.  Default: "intersect"
1270//   keep = String containing space delimited set of tag names of children to keep whole.  Default: "keep"
1271// Side Effects:
1272//   Sets `$tag` to the tag you specify, possibly with a scope prefix.
1273// Example:  Without `tag_intersect()` the kept object is not included in the difference.
1274//   $fn=32;
1275//   diff()
1276//     cuboid([20,15,9])
1277//     tag("remove")intersect()
1278//       cuboid(10){
1279//         tag("intersect")position(RIGHT) cyl(r=7,h=10);
1280//         tag("keep")position(LEFT)cyl(r=4,h=10);
1281//       }
1282// Example: Using tag_intersect corrects the problem.
1283//   $fn=32;
1284//   diff()
1285//     cuboid([20,15,9])
1286//     tag_intersect("remove")
1287//       cuboid(10){
1288//         tag("intersect")position(RIGHT) cyl(r=7,h=10);
1289//         tag("keep")position(LEFT)cyl(r=4,h=10);
1290//       }
1291module tag_intersect(tag,intersect="intersect",keep="keep")
1292{
1293   assert(is_string(intersect),"intersect must be a string of tags");
1294   assert(is_string(keep),"keep must be a string of tags");
1295   assert(is_string(tag),"tag must be a string");
1296   assert(undef==str_find(tag," "),str("Tag string \"",tag,"\" contains a space, which is not allowed"));
1297   $tag=str($tag_prefix,tag);
1298   if (_is_shown())
1299     show_all(){
1300       intersection(){
1301          show_only(intersect) children();
1302          hide(str(intersect," ",keep)) children();
1303       }
1304       show_only(keep) children();
1305   }
1306}
1307
1308
1309// Module: conv_hull()
1310// Synopsis:  Performs a hull operation on the children using tags to determine what happens.
1311// Topics: Attachments
1312// See Also: tag(), recolor(), show_only(), hide(), diff(), intersect()
1313// Usage:
1314//   conv_hull([keep]) CHILDREN;
1315// Description:
1316//   Performs a hull operation on the children using tags to determine what happens.  The items
1317//   not tagged with the `keep` tags are combined into a convex hull, and the children tagged with the keep tags
1318//   are unioned with the result.
1319//   .
1320//   Note that `conv_hull()` invokes its children twice.  
1321//   .
1322//   For a step-by-step explanation of attachments, see the [Attachments Tutorial](Tutorial-Attachments).
1323// Arguments:
1324//   keep = String containing space delimited set of tag names of children to keep out of the hull.  Default: "keep"
1325// Example:
1326//   conv_hull("keep")
1327//      sphere(d=100, $fn=64) {
1328//        cuboid([40,90,90]);
1329//        tag("keep")xcyl(d=40, l=120);
1330//      }
1331// Example: difference combined with hull where all objects are relative to each other.
1332//   $fn=32;
1333//   diff()
1334//     conv_hull("remove")
1335//       cuboid(10)
1336//         position(RIGHT+BACK)cyl(r=4,h=10)
1337//           tag("remove")cyl(r=2,h=12);
1338module conv_hull(keep="keep")
1339{
1340    req_children($children);
1341    assert(is_string(keep),"keep must be a string of tags");
1342    if (_is_shown())
1343        hull() hide(keep) children();
1344    show_int(keep) children();
1345}
1346
1347
1348// Module: tag_conv_hull()
1349// Synopsis: Performs a {{conv_hull()}} and then sets a tag on the result.
1350// Topics: Attachments
1351// See Also: tag(), recolor(), show_only(), hide(), diff(), intersect()
1352// Usage:
1353//   tag_conv_hull(tag, [keep]) CHILDREN;
1354// Description:
1355//   Perform a convex hull operation in the manner of {{conv_hull()}} using tags to control what happens,
1356//   and then tag the resulting hull object with the specified tag.  This forces the specified
1357//   tag to be resolved at the level of the hull operation.  In most cases, this is not necessary,
1358//   but if you have kept objects and want to operate on the hull object as a whole object using
1359//   more tag operations, you will probably not get the results you want if you simply use {{tag()}}.
1360//   .
1361//   For a step-by-step explanation of attachments, see the [Attachments Tutorial](Tutorial-Attachments).
1362// Arguments:
1363//   keep = String containing space delimited set of tag names of children to keep out of the hull.  Default: "keep"
1364// Side Effects:
1365//   Sets `$tag` to the tag you specify, possibly with a scope prefix.
1366// Example: With a regular tag, the kept object is not handled as desired:
1367//   diff(){
1368//      cuboid([30,30,9])
1369//        tag("remove")conv_hull("remove")
1370//          cuboid(10,anchor=LEFT+FRONT){
1371//            position(RIGHT+BACK)cyl(r=4,h=10);
1372//            tag("keep")position(FRONT+LEFT)cyl(r=4,h=10);
1373//          }
1374//   }
1375// Example: Using `tag_conv_hull()` fixes the problem:
1376//   diff(){
1377//      cuboid([30,30,9])
1378//        tag_conv_hull("remove")
1379//          cuboid(10,anchor=LEFT+FRONT){
1380//            position(RIGHT+BACK)cyl(r=4,h=10);
1381//            tag("keep")position(FRONT+LEFT)cyl(r=4,h=10);
1382//          }
1383//   }
1384module tag_conv_hull(tag,keep="keep")
1385{
1386    req_children($children);
1387    assert(is_string(keep),"keep must be a string of tags");
1388    assert(is_string(tag),"tag must be a string");
1389    assert(undef==str_find(tag," "),str("Tag string \"",tag,"\" contains a space, which is not allowed"));
1390    $tag=str($tag_prefix,tag);
1391    if (_is_shown())
1392      show_all(){
1393        hull() hide(keep) children();
1394        show_only(keep) children();
1395      }
1396}
1397
1398
1399// Module: hide()
1400// Synopsis: Hides attachable children with the given tags.
1401// Topics: Attachments
1402// See Also: tag(), recolor(), show_only(), show_all(), show_int(), diff(), intersect()
1403// Usage:
1404//   hide(tags) CHILDREN;
1405// Description:
1406//   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()`.
1407//   For a step-by-step explanation of attachments, see the [Attachments Tutorial](Tutorial-Attachments).
1408// Side Effects:
1409//   Sets `$tags_hidden` to include the tags you specify.
1410// Example:  Hides part of the model.
1411//   hide("A")
1412//     tag("main") cube(50, anchor=CENTER, $tag="Main") {
1413//       tag("A")attach(LEFT, BOTTOM) cylinder(d=30, h=30);
1414//       tag("B")attach(RIGHT, BOTTOM) cylinder(d=30, h=30);
1415//     }
1416// Example: Use an invisible parent to position children.  Note that children must be retagged because they inherit the parent tag.
1417//   $fn=16;
1418//   hide("hidden")
1419//     tag("hidden")cuboid(10)
1420//       tag("visible") {
1421//         position(RIGHT) cyl(r=1,h=12);
1422//         position(LEFT) cyl(r=1,h=12);
1423//       }
1424module hide(tags)
1425{
1426    req_children($children);
1427    dummy=assert(is_string(tags), "tags must be a string");
1428    taglist = [for(s=str_split(tags," ",keep_nulls=false)) str($tag_prefix,s)];
1429    $tags_hidden = concat($tags_hidden,taglist);
1430    children();
1431}
1432
1433
1434// Module: show_only()
1435// Synopsis: Show only the children with the listed tags.
1436// See Also: tag(), recolor(), show_all(), show_int(), diff(), intersect()
1437// Topics: Attachments
1438// Usage:
1439//   show_only(tags) CHILDREN;
1440// Description:
1441//   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.
1442//   For a step-by-step explanation of attachments, see the [Attachments Tutorial](Tutorial-Attachments).
1443// Side Effects:
1444//   Sets `$tags_shown` to the tag you specify.
1445// Example:  Display the attachments but not the parent
1446//   show_only("visible")
1447//     cube(50, anchor=CENTER)
1448//       tag("visible"){
1449//         attach(LEFT, BOTTOM) cylinder(d=30, h=30);
1450//         attach(RIGHT, BOTTOM) cylinder(d=30, h=30);
1451//       }
1452module show_only(tags)
1453{
1454    req_children($children);
1455    dummy=assert(is_string(tags), str("tags must be a string",tags));
1456    taglist = [for(s=str_split(tags," ",keep_nulls=false)) str($tag_prefix,s)];
1457    $tags_shown = taglist;
1458    children();
1459}
1460
1461// Module: show_all()
1462// Synopsis: Shows all children and clears tags.
1463// See Also: tag(), recolor(), show_only(), show_int(), diff(), intersect()
1464// Topics: Attachments
1465// Usage;
1466//   show_all() CHILDREN;
1467// Description:
1468//   Shows all children.  Clears the list of hidden tags and shown tags so that all child objects will be
1469//   fully displayed.
1470// Side Effects:
1471//   Sets `$tags_shown="ALL"`
1472//   Sets `$tags_hidden=[]`
1473module show_all()
1474{
1475   req_children($children);
1476   $tags_shown="ALL";
1477   $tags_hidden=[];
1478   children();
1479}
1480
1481
1482// Module: show_int()
1483// Synopsis: Shows children with the listed tags which were already shown in the parent context.
1484// See Also: tag(), recolor(), show_only(), show_all(), show_int(), diff(), intersect()
1485// Topics: Attachments
1486// Usage:
1487//   show_int(tags) CHILDREN;
1488// Description:
1489//   Show only the children with the listed tags which were already shown in the parent context.
1490//   This intersects the current show list with the list of tags you provide.
1491// Arguments:
1492//   tags = list of tags to show
1493// Side Effects:
1494//   Sets `$tags_shown`
1495module show_int(tags)
1496{
1497    req_children($children);
1498    dummy=assert(is_string(tags), str("tags must be a string",tags));
1499    taglist = [for(s=str_split(tags," ",keep_nulls=false)) str($tag_prefix,s)];
1500    $tags_shown = $tags_shown == "ALL" ? taglist : set_intersection($tags_shown,taglist);
1501    children();
1502}
1503
1504
1505// Section: Mask Attachment
1506
1507
1508// Module: face_mask()
1509// Synopsis: Ataches a 3d mask shape to the given faces of the parent.
1510// SynTags: Trans
1511// Topics: Attachments, Masking
1512// See Also: attachable(), position(), attach(), edge_mask(), corner_mask(), face_profile(), edge_profile(), corner_profile()
1513// Usage:
1514//   PARENT() face_mask(faces) CHILDREN;
1515// Description:
1516//   Takes a 3D mask shape, and attaches it to the given faces, with the appropriate orientation to be
1517//   differenced away.  The mask shape should be vertically oriented (Z-aligned) with the bottom half
1518//   (Z-) shaped to be diffed away from the face of parent attachable shape.  If no tag is set then
1519//   `face_mask()` sets the tag for children to "remove" so that it will work with the default {{diff()}} tag.
1520//   For details on specifying the faces to mask see [Specifying Faces](attachments.scad#subsection-specifying-faces).
1521//   For a step-by-step explanation of attachments, see the [Attachments Tutorial](Tutorial-Attachments).
1522// Arguments:
1523//   edges = Faces to mask.  See  [Specifying Faces](attachments.scad#subsection-specifying-faces) for information on specifying faces.  Default: All faces
1524// Side Effects:
1525//   Tags the children with "remove" (and hence sets `$tag`) if no tag is already set.
1526//   `$idx` is set to the index number of each face in the list of faces given.
1527//   `$attach_anchor` is set for each face given, to the `[ANCHOR, POSITION, ORIENT, SPIN]` information for that anchor.
1528// Example:
1529//   diff()
1530//   cylinder(r=30, h=60)
1531//       face_mask(TOP) {
1532//           rounding_cylinder_mask(r=30,rounding=5);
1533//           cuboid([5,61,10]);
1534//       }
1535// Example: Using `$idx`
1536//   diff()
1537//   cylinder(r=30, h=60)
1538//       face_mask([TOP, BOT])
1539//           zrot(45*$idx) zrot_copies([0,90]) cuboid([5,61,10]);
1540module face_mask(faces=[LEFT,RIGHT,FRONT,BACK,BOT,TOP]) {
1541    req_children($children);
1542    faces = is_vector(faces)? [faces] : faces;
1543    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.");
1544    assert($parent_geom != undef, "No object to attach to!");
1545    attach(faces) {
1546       default_tag("remove") children();
1547    }
1548}
1549
1550
1551// Module: edge_mask()
1552// Synopsis: Attaches a 3D mask shape to the given edges of the parent.
1553// SynTags: Trans
1554// Topics: Attachments, Masking
1555// See Also: attachable(), position(), attach(), face_mask(), corner_mask(), face_profile(), edge_profile(), corner_profile()
1556// Usage:
1557//   PARENT() edge_mask([edges], [except]) CHILDREN;
1558// Description:
1559//   Takes a 3D mask shape, and attaches it to the given edges, with the appropriate orientation to be
1560//   differenced away.  The mask shape should be vertically oriented (Z-aligned) with the back-right
1561//   quadrant (X+Y+) shaped to be diffed away from the edge of parent attachable shape.  If no tag is set
1562//   then `edge_mask` sets the tag for children to "remove" so that it will work with the default {{diff()}} tag.
1563//   For details on specifying the edges to mask see [Specifying Edges](attachments.scad#subsection-specifying-edges).
1564//   For a step-by-step explanation of attachments, see the [Attachments Tutorial](Tutorial-Attachments).
1565// Figure: A Typical Edge Rounding Mask
1566//   module roundit(l,r) difference() {
1567//       translate([-1,-1,-l/2])
1568//           cube([r+1,r+1,l]);
1569//       translate([r,r])
1570//           cylinder(h=l+1,r=r,center=true, $fn=quantup(segs(r),4));
1571//   }
1572//   roundit(l=30,r=10);
1573// Arguments:
1574//   edges = Edges to mask.  See [Specifying Edges](attachments.scad#subsection-specifying-edges).  Default: All edges.
1575//   except = Edges to explicitly NOT mask.  See [Specifying Edges](attachments.scad#subsection-specifying-edges).  Default: No edges.
1576// Side Effects:
1577//   Tags the children with "remove" (and hence sets `$tag`) if no tag is already set.
1578//   `$idx` is set to the index number of each edge.
1579//   `$attach_anchor` is set for each edge given, to the `[ANCHOR, POSITION, ORIENT, SPIN]` information for that anchor.
1580// Example:
1581//   diff()
1582//   cube([50,60,70],center=true)
1583//       edge_mask([TOP,"Z"],except=[BACK,TOP+LEFT])
1584//           rounding_edge_mask(l=71,r=10);
1585module edge_mask(edges=EDGES_ALL, except=[]) {
1586    req_children($children);
1587    assert($parent_geom != undef, "No object to attach to!");
1588    edges = _edges(edges, except=except);
1589    vecs = [
1590        for (i = [0:3], axis=[0:2])
1591        if (edges[axis][i]>0)
1592        EDGE_OFFSETS[axis][i]
1593    ];
1594    for ($idx = idx(vecs)) {
1595        vec = vecs[$idx];
1596        vcount = (vec.x?1:0) + (vec.y?1:0) + (vec.z?1:0);
1597        dummy=assert(vcount == 2, "Not an edge vector!");
1598        anch = _find_anchor(vec, $parent_geom);
1599        $attach_to = undef;
1600        $attach_anchor = anch;
1601        $attach_norot = true;
1602        rotang =
1603            vec.z<0? [90,0,180+v_theta(vec)] :
1604            vec.z==0 && sign(vec.x)==sign(vec.y)? 135+v_theta(vec) :
1605            vec.z==0 && sign(vec.x)!=sign(vec.y)? [0,180,45+v_theta(vec)] :
1606            [-90,0,180+v_theta(vec)];
1607        translate(anch[1]) rot(rotang)
1608           default_tag("remove") children();
1609    }
1610}
1611
1612
1613// Module: corner_mask()
1614// Synopsis: Attaches a 3d mask shape to the given corners of the parent.
1615// SynTags: Trans
1616// Topics: Attachments, Masking
1617// See Also: attachable(), position(), attach(), face_mask(), edge_mask(), face_profile(), edge_profile(), corner_profile()
1618// Usage:
1619//   PARENT() corner_mask([corners], [except]) CHILDREN;
1620// Description:
1621//   Takes a 3D mask shape, and attaches it to the specified corners, with the appropriate orientation to
1622//   be differenced away.  The 3D corner mask shape should be designed to mask away the X+Y+Z+ octant.  If no tag is set
1623//   then `corner_mask` sets the tag for children to "remove" so that it will work with the default {{diff()}} tag.
1624//   See [Specifying Corners](attachments.scad#subsection-specifying-corners) for information on how to specify corner sets.
1625//   For a step-by-step explanation of attachments, see the [Attachments Tutorial](Tutorial-Attachments).
1626// Arguments:
1627//   corners = Corners to mask.  See [Specifying Corners](attachments.scad#subsection-specifying-corners).  Default: All corners.
1628//   except = Corners to explicitly NOT mask.  See [Specifying Corners](attachments.scad#subsection-specifying-corners).  Default: No corners.
1629// Side Effects:
1630//   Tags the children with "remove" (and hence sets `$tag`) if no tag is already set.
1631//   `$idx` is set to the index number of each corner.
1632//   `$attach_anchor` is set for each corner given, to the `[ANCHOR, POSITION, ORIENT, SPIN]` information for that anchor.
1633// Example:
1634//   diff()
1635//   cube(100, center=true)
1636//       corner_mask([TOP,FRONT],LEFT+FRONT+TOP)
1637//           difference() {
1638//               translate(-0.01*[1,1,1]) cube(20);
1639//               translate([20,20,20]) sphere(r=20);
1640//           }
1641module corner_mask(corners=CORNERS_ALL, except=[]) {
1642    req_children($children);
1643    assert($parent_geom != undef, "No object to attach to!");
1644    corners = _corners(corners, except=except);
1645    vecs = [for (i = [0:7]) if (corners[i]>0) CORNER_OFFSETS[i]];
1646    for ($idx = idx(vecs)) {
1647        vec = vecs[$idx];
1648        vcount = (vec.x?1:0) + (vec.y?1:0) + (vec.z?1:0);
1649        dummy=assert(vcount == 3, "Not an edge vector!");
1650        anch = _find_anchor(vec, $parent_geom);
1651        $attach_to = undef;
1652        $attach_anchor = anch;
1653        $attach_norot = true;
1654        rotang = vec.z<0?
1655            [  0,0,180+v_theta(vec)-45] :
1656            [180,0,-90+v_theta(vec)-45];
1657        translate(anch[1]) rot(rotang)
1658            default_tag("remove") children();
1659    }
1660}
1661
1662
1663// Module: face_profile()
1664// Synopsis: Extrudes a 2D edge profile into a mask for all edges and corners of the given faces on the parent.
1665// SynTags: Geom
1666// Topics: Attachments, Masking
1667// See Also: attachable(), position(), attach(), edge_profile(), corner_profile(), face_mask(), edge_mask(), corner_mask()
1668// Usage:
1669//   PARENT() face_profile(faces, r|d=, [convexity=]) CHILDREN;
1670// Description:
1671//   Given a 2D edge profile, extrudes it into a mask for all edges and corners bounding each given face. If no tag is set
1672//   then `face_profile` sets the tag for children to "remove" so that it will work with the default {{diff()}} tag.
1673//   See  [Specifying Faces](attachments.scad#subsection-specifying-faces) for information on specifying faces.
1674//   For a step-by-step explanation of attachments, see the [Attachments Tutorial](Tutorial-Attachments).
1675// Arguments:
1676//   faces = Faces to mask edges and corners of.
1677//   r = Radius of corner mask.
1678//   ---
1679//   d = Diameter of corner mask.
1680//   excess = Excess length to extrude the profile to make edge masks.  Default: 0.01
1681//   convexity = Max number of times a line could intersect the perimeter of the mask shape.  Default: 10
1682// Side Effects:
1683//   Tags the children with "remove" (and hence sets `$tag`) if no tag is already set.
1684//   `$idx` is set to the index number of each face.
1685//   `$attach_anchor` is set for each edge or corner given, to the `[ANCHOR, POSITION, ORIENT, SPIN]` information for that anchor.
1686//   `$profile_type` is set to `"edge"` or `"corner"`, depending on what is being masked.
1687// Example:
1688//   diff()
1689//   cube([50,60,70],center=true)
1690//       face_profile(TOP,r=10)
1691//           mask2d_roundover(r=10);
1692module face_profile(faces=[], r, d, excess=0.01, convexity=10) {
1693    req_children($children);
1694    faces = is_vector(faces)? [faces] : faces;
1695    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.");
1696    r = get_radius(r=r, d=d, dflt=undef);
1697    assert(is_num(r) && r>=0);
1698    edge_profile(faces, excess=excess) children();
1699    corner_profile(faces, convexity=convexity, r=r) children();
1700}
1701
1702
1703// Module: edge_profile()
1704// Synopsis: Extrudes a 2d edge profile into a mask on the given edges of the parent.
1705// SynTags: Geom
1706// Topics: Attachments, Masking
1707// See Also: attachable(), position(), attach(), face_profile(), edge_profile_asym(), corner_profile(), edge_mask(), face_mask(), corner_mask()
1708// Usage:
1709//   PARENT() edge_profile([edges], [except], [convexity]) CHILDREN;
1710// Description:
1711//   Takes a 2D mask shape and attaches it to the selected edges, with the appropriate orientation and
1712//   extruded length to be `diff()`ed away, to give the edge a matching profile.  If no tag is set
1713//   then `edge_profile` sets the tag for children to "remove" so that it will work with the default {{diff()}} tag.
1714//   For details on specifying the edges to mask see [Specifying Edges](attachments.scad#subsection-specifying-edges).
1715//   For a step-by-step
1716//   explanation of attachments, see the [Attachments Tutorial](Tutorial-Attachments).
1717// Arguments:
1718//   edges = Edges to mask.  See [Specifying Edges](attachments.scad#subsection-specifying-edges).  Default: All edges.
1719//   except = Edges to explicitly NOT mask.  See [Specifying Edges](attachments.scad#subsection-specifying-edges).  Default: No edges.
1720//   excess = Excess length to extrude the profile to make edge masks.  Default: 0.01
1721//   convexity = Max number of times a line could intersect the perimeter of the mask shape.  Default: 10
1722// Side Effects:
1723//   Tags the children with "remove" (and hence sets `$tag`) if no tag is already set.
1724//   `$idx` is set to the index number of each edge.
1725//   `$attach_anchor` is set for each edge given, to the `[ANCHOR, POSITION, ORIENT, SPIN]` information for that anchor.
1726//   `$profile_type` is set to `"edge"`.
1727//   `$edge_angle` is set to the inner angle of the current edge.
1728// Example:
1729//   diff()
1730//   cube([50,60,70],center=true)
1731//       edge_profile([TOP,"Z"],except=[BACK,TOP+LEFT])
1732//           mask2d_roundover(r=10, inset=2);
1733// Example: Using $edge_angle on a Conoid
1734//   diff()
1735//   cyl(d1=50, d2=30, l=40, anchor=BOT) {
1736//       edge_profile([TOP,BOT], excess=10, convexity=6) {
1737//           mask2d_roundover(r=8, inset=1, excess=1, mask_angle=$edge_angle);
1738//       }
1739//   }
1740// Example: Using $edge_angle on a Prismoid
1741//   diff()
1742//   prismoid([60,50],[30,20],h=40,shift=[-25,15]) {
1743//       edge_profile(excess=10, convexity=20) {
1744//           mask2d_roundover(r=5,inset=1,mask_angle=$edge_angle);
1745//       }
1746//   }
1747
1748module edge_profile(edges=EDGES_ALL, except=[], excess=0.01, convexity=10) {
1749    req_children($children);
1750    check1 = assert($parent_geom != undef, "No object to attach to!");
1751    conoid = $parent_geom[0] == "conoid";
1752    edges = !conoid? _edges(edges, except=except) :
1753        edges==EDGES_ALL? [TOP,BOT] :
1754        assert(all([for (e=edges) in_list(e,[TOP,BOT])]), "Invalid conoid edge spec.")
1755        edges;
1756    vecs = conoid
1757      ? [for (e=edges) e+FWD]
1758      : [
1759            for (i = [0:3], axis=[0:2])
1760            if (edges[axis][i]>0)
1761            EDGE_OFFSETS[axis][i]
1762        ];
1763    all_vecs_are_edges = all([for (vec = vecs) sum(v_abs(vec))==2]);
1764    check2 = assert(all_vecs_are_edges, "All vectors must be edges.");
1765    default_tag("remove")
1766    for ($idx = idx(vecs)) {
1767        vec = vecs[$idx];
1768        anch = _find_anchor(vec, $parent_geom);
1769        path_angs_T = _attach_geom_edge_path($parent_geom, vec);
1770        path = path_angs_T[0];
1771        vecs = path_angs_T[1];
1772        post_T = path_angs_T[2];
1773        $attach_to = undef;
1774        $attach_anchor = anch;
1775        $attach_norot = true;
1776        $profile_type = "edge";
1777        multmatrix(post_T) {
1778            for (i = idx(path,e=-2)) {
1779                pt1 = select(path,i);
1780                pt2 = select(path,i+1);
1781                cp = (pt1 + pt2) / 2;
1782                v1 = vecs[i][0];
1783                v2 = vecs[i][1];
1784                $edge_angle = 180 - vector_angle(v1,v2);
1785                if (!approx(pt1,pt2)) {
1786                    seglen = norm(pt2-pt1) + 2 * excess;
1787                    move(cp) {
1788                        frame_map(x=-v2, z=unit(pt2-pt1)) {
1789                            linear_extrude(height=seglen, center=true, convexity=convexity)
1790                                mirror([-1,1]) children();
1791                        }
1792                    }
1793                }
1794            }
1795        }
1796    }
1797}
1798
1799
1800// Module: edge_profile_asym()
1801// Synopsis: Extrudes an asymmetric 2D profile into a mask on the given edges and corners of the parent.
1802// SynTags: Geom
1803// Topics: Attachments, Masking
1804// See Also: attachable(), position(), attach(), face_profile(), edge_profile(), corner_profile(), edge_mask(), face_mask(), corner_mask()
1805// Usage:
1806//   PARENT() edge_profile([edges], [except=], [convexity=], [flip=], [corner_type=]) CHILDREN;
1807// Description:
1808//   Takes an asymmetric 2D mask shape and attaches it to the selected edges and corners, with the appropriate
1809//   orientation and extruded length to be `diff()`ed away, to give the edges and corners a matching profile.
1810//   If no tag is set then `edge_profile_asym()` sets the tag for children to "remove" so that it will work
1811//   with the default {{diff()}} tag.  For details on specifying the edges to mask see [Specifying Edges](attachments.scad#subsection-specifying-edges).
1812//   For a step-by-step explanation of attachments, see the [Attachments Tutorial](Tutorial-Attachments).
1813//   Profile orientation will be made consistent for all connected edges and corners.  This prohibits having three
1814//   edges meeting at any one corner.  You can intert the orientations of all edges with `flip=true`.
1815// Arguments:
1816//   edges = Edges to mask.  See [Specifying Edges](attachments.scad#subsection-specifying-edges).  Default: All edges.
1817//   except = Edges to explicitly NOT mask.  See [Specifying Edges](attachments.scad#subsection-specifying-edges).  Default: No edges.
1818//   excess = Excess length to extrude the profile to make edge masks.  Default: 0.01
1819//   convexity = Max number of times a line could intersect the perimeter of the mask shape.  Default: 10
1820//   flip = If true, reverses the orientation of any external profile parts at each edge.  Default false
1821//   corner_type = Specifies how exterior corners should be formed.  Must be one of `"none"`, `"chamfer"`, `"round"`, or `"sharp"`.  Default: `"none"`
1822//   size = If given the width and height of the 2D profile, will enable rounding and chamfering of internal corners when given a negative profile.
1823// Side Effects:
1824//   Tags the children with "remove" (and hence sets `$tag`) if no tag is already set.
1825//   `$idx` is set to the index number of each edge.
1826//   `$attach_anchor` is set for each edge given, to the `[ANCHOR, POSITION, ORIENT, SPIN]` information for that anchor.
1827//   `$profile_type` is set to `"edge"`.
1828// Example:
1829//   ogee = [
1830//       "xstep",1,  "ystep",1,  // Starting shoulder.
1831//       "fillet",5, "round",5,  // S-curve.
1832//       "ystep",1,  "xstep",1   // Ending shoulder.
1833//   ];
1834//   diff()
1835//   cuboid(50) {
1836//       edge_profile_asym(FRONT)
1837//          mask2d_ogee(ogee);
1838//   }
1839// Example: Flipped
1840//   ogee = [
1841//       "xstep",1,  "ystep",1,  // Starting shoulder.
1842//       "fillet",5, "round",5,  // S-curve.
1843//       "ystep",1,  "xstep",1   // Ending shoulder.
1844//   ];
1845//   diff()
1846//   cuboid(50) {
1847//       edge_profile_asym(FRONT, flip=true)
1848//          mask2d_ogee(ogee);
1849//   }
1850// Example: Negative Chamfering
1851//   cuboid(50) {
1852//       edge_profile_asym(FWD, flip=false)
1853//           xflip() mask2d_chamfer(10);
1854//       edge_profile_asym(BACK, flip=true, corner_type="sharp")
1855//           xflip() mask2d_chamfer(10);
1856//   }
1857// Example: Negative Roundings
1858//   cuboid(50) {
1859//       edge_profile_asym(FWD, flip=false)
1860//           xflip() mask2d_roundover(10);
1861//       edge_profile_asym(BACK, flip=true, corner_type="round")
1862//           xflip() mask2d_roundover(10);
1863//   }
1864// Example: Cornerless
1865//   cuboid(50) {
1866//       edge_profile_asym(
1867//           "ALL", except=[TOP+FWD+RIGHT, BOT+BACK+LEFT]
1868//        ) xflip() mask2d_roundover(10);
1869//   }
1870// Example: More complicated edge sets
1871//   cuboid(50) {
1872//       edge_profile_asym(
1873//           [FWD,BACK,BOT+RIGHT], except=[FWD+RIGHT,BOT+BACK],
1874//           corner_type="round"
1875//        ) xflip() mask2d_roundover(10);
1876//   }
1877// Example: Mixing it up a bit.
1878//   diff()
1879//   cuboid(60) {
1880//       tag("keep") edge_profile_asym(LEFT, flip=true, corner_type="chamfer")
1881//           xflip() mask2d_chamfer(10);
1882//       edge_profile_asym(RIGHT)
1883//           mask2d_roundover(10);
1884//   }
1885// Example: Chamfering internal corners.
1886//   cuboid(40) {
1887//       edge_profile_asym(
1888//           [FWD+DOWN,FWD+LEFT],
1889//           corner_type="chamfer", size=[7,10]
1890//        ) xflip() mask2d_chamfer(10);
1891//   }
1892// Example: Rounding internal corners.
1893//   cuboid(40) {
1894//       edge_profile_asym(
1895//           [FWD+DOWN,FWD+LEFT],
1896//           corner_type="round", size=[10,10]
1897//        ) xflip() mask2d_roundover(10);
1898//   }
1899
1900module edge_profile_asym(
1901    edges=EDGES_ALL, except=[],
1902    excess=0.01, convexity=10,
1903    flip=false, corner_type="none",
1904    size=[0,0]
1905) {
1906    function _corner_orientation(pos,pvec) =
1907        let(
1908            j = [for (i=[0:2]) if (pvec[i]) i][0],
1909            T = (pos.x>0? xflip() : ident(4)) *
1910                (pos.y>0? yflip() : ident(4)) *
1911                (pos.z>0? zflip() : ident(4)) *
1912                rot(-120*(2-j), v=[1,1,1])
1913        ) T;
1914
1915    function _default_edge_orientation(edge) =
1916        edge.z < 0? [[-edge.x,-edge.y,0], UP] :
1917        edge.z > 0? [[-edge.x,-edge.y,0], DOWN] :
1918        edge.y < 0? [[-edge.x,0,0], BACK] :
1919        [[-edge.x,0,0], FWD] ;
1920
1921    function _edge_transition_needs_flip(from,to) =
1922        let(
1923            flip_edges = [
1924                [BOT+FWD, [FWD+LEFT, FWD+RIGHT]],
1925                [BOT+BACK, [BACK+LEFT, BACK+RIGHT]],
1926                [BOT+LEFT, []],
1927                [BOT+RIGHT, []],
1928                [TOP+FWD, [FWD+LEFT, FWD+RIGHT]],
1929                [TOP+BACK, [BACK+LEFT, BACK+RIGHT]],
1930                [TOP+LEFT, []],
1931                [TOP+RIGHT, []],
1932                [FWD+LEFT, [TOP+FWD, BOT+FWD]],
1933                [FWD+RIGHT, [TOP+FWD, BOT+FWD]],
1934                [BACK+LEFT, [TOP+BACK, BOT+BACK]],
1935                [BACK+RIGHT, [TOP+BACK, BOT+BACK]],
1936            ],
1937            i = search([from], flip_edges, num_returns_per_match=1)[0],
1938            check = assert(i!=[], "Bad edge vector.")
1939        ) in_list(to,flip_edges[i][1]);
1940
1941    function _edge_corner_numbers(vec) =
1942        let(
1943            v2 = [for (i=idx(vec)) vec[i]? (vec[i]+1)/2*pow(2,i) : 0],
1944            off = v2.x + v2.y + v2.z,
1945            xs = [0, if (!vec.x) 1],
1946            ys = [0, if (!vec.y) 2],
1947            zs = [0, if (!vec.z) 4]
1948        ) [for (x=xs, y=ys, z=zs) x+y+z + off];
1949
1950    function _gather_contiguous_edges(edge_corners) =
1951        let(
1952            no_tri_corners = all([for(cn = [0:7]) len([for (ec=edge_corners) if(in_list(cn,ec[1])) 1])<3]),
1953            check = assert(no_tri_corners, "Cannot have three edges that meet at the same corner.")
1954        )
1955        _gather_contiguous_edges_r(
1956            [for (i=idx(edge_corners)) if(i) edge_corners[i]],
1957            edge_corners[0][1],
1958            [edge_corners[0][0]], []);
1959
1960    function _gather_contiguous_edges_r(edge_corners, ecns, curr, out) =
1961        len(edge_corners)==0? [each out, curr] :
1962        let(
1963            i1 = [
1964                for (i = idx(edge_corners))
1965                if (in_list(ecns[0], edge_corners[i][1]))
1966                i
1967            ],
1968            i2 = [
1969                for (i = idx(edge_corners))
1970                if (in_list(ecns[1], edge_corners[i][1]))
1971                i
1972            ]
1973        ) !i1 && !i2? _gather_contiguous_edges_r(
1974            [for (i=idx(edge_corners)) if(i) edge_corners[i]],
1975            edge_corners[0][1],
1976            [edge_corners[0][0]],
1977            [each out, curr]
1978        ) : let(
1979            nu_curr = [
1980                if (i1) edge_corners[i1[0]][0],
1981                each curr,
1982                if (i2) edge_corners[i2[0]][0],
1983            ],
1984            nu_ecns = [
1985                if (!i1) ecns[0] else [
1986                    for (ecn = edge_corners[i1[0]][1])
1987                    if (ecn != ecns[0]) ecn
1988                ][0],
1989                if (!i2) ecns[1] else [
1990                    for (ecn = edge_corners[i2[0]][1])
1991                    if (ecn != ecns[1]) ecn
1992                ][0],
1993            ],
1994            rem = [
1995                for (i = idx(edge_corners))
1996                if (i != i1[0] && i != i2[0])
1997                edge_corners[i]
1998            ]
1999        )
2000        _gather_contiguous_edges_r(rem, nu_ecns, nu_curr, out);
2001
2002    function _edge_transition_inversions(edge_string) =
2003        let(
2004            // boolean cumulative sum
2005            bcs = function(list, i=0, inv=false, out=[])
2006                    i>=len(list)? out :
2007                    let( nu_inv = list[i]? !inv : inv )
2008                    bcs(list, i+1, nu_inv, [each out, nu_inv]),
2009            inverts = bcs([
2010                false,
2011                for(i = idx(edge_string)) if (i)
2012                    _edge_transition_needs_flip(
2013                        edge_string[i-1],
2014                        edge_string[i]
2015                    )
2016            ]),
2017            boti = [for(i = idx(edge_string)) if (edge_string[i].z<0) i],
2018            topi = [for(i = idx(edge_string)) if (edge_string[i].z>0) i],
2019            lfti = [for(i = idx(edge_string)) if (edge_string[i].x<0) i],
2020            rgti = [for(i = idx(edge_string)) if (edge_string[i].x>0) i],
2021            idx = [for (m = [boti, topi, lfti, rgti]) if(m) m[0]][0],
2022            rinverts = inverts[idx] == false? inverts : [for (x = inverts) !x]
2023        ) rinverts;
2024
2025    function _is_closed_edge_loop(edge_string) =
2026        let(
2027            e1 = edge_string[0],
2028            e2 = last(edge_string)
2029        )
2030        len([for (i=[0:2]) if (abs(e1[i])==1 && e1[i]==e2[i]) 1]) == 1 &&
2031        len([for (i=[0:2]) if (e1[i]==0 && abs(e2[i])==1) 1]) == 1 &&
2032        len([for (i=[0:2]) if (e2[i]==0 && abs(e1[i])==1) 1]) == 1;
2033
2034    function _edge_pair_perp_vec(e1,e2) =
2035        [for (i=[0:2]) if (abs(e1[i])==1 && e1[i]==e2[i]) -e1[i] else 0];
2036
2037    req_children($children);
2038    check1 = assert($parent_geom != undef, "No object to attach to!")
2039        assert(in_list(corner_type, ["none", "round", "chamfer", "sharp"]))
2040        assert(is_bool(flip));
2041    edges = _edges(edges, except=except);
2042    vecs = [
2043        for (i = [0:3], axis=[0:2])
2044        if (edges[axis][i]>0)
2045        EDGE_OFFSETS[axis][i]
2046    ];
2047    all_vecs_are_edges = all([for (vec = vecs) sum(v_abs(vec))==2]);
2048    check2 = assert(all_vecs_are_edges, "All vectors must be edges.");
2049    edge_corners = [for (vec = vecs) [vec, _edge_corner_numbers(vec)]];
2050    edge_strings = _gather_contiguous_edges(edge_corners);
2051    default_tag("remove")
2052    for (edge_string = edge_strings) {
2053        inverts = _edge_transition_inversions(edge_string);
2054        flipverts = [for (x = inverts) flip? !x : x];
2055        vecpairs = [
2056            for (i = idx(edge_string))
2057            let (p = _default_edge_orientation(edge_string[i]))
2058            flipverts[i]? [p.y,p.x] : p
2059        ];
2060        is_loop = _is_closed_edge_loop(edge_string);
2061        for (i = idx(edge_string)) {
2062            if (corner_type!="none" && (i || is_loop)) {
2063                e1 = select(edge_string,i-1);
2064                e2 = select(edge_string,i);
2065                vp1 = select(vecpairs,i-1);
2066                vp2 = select(vecpairs,i);
2067                pvec = _edge_pair_perp_vec(e1,e2);
2068                pos = [for (i=[0:2]) e1[i]? e1[i] : e2[i]];
2069                mirT = _corner_orientation(pos, pvec);
2070                $attach_to = undef;
2071                $attach_anchor = _find_anchor(pos, $parent_geom);
2072                $attach_norot = true;
2073                $profile_type = "corner";
2074                position(pos) {
2075                    multmatrix(mirT) {
2076                        if (vp1.x == vp2.x && size.y > 0) {
2077                            zflip() {
2078                                if (corner_type=="chamfer") {
2079                                    fn = $fn;
2080                                    move([size.y,size.y]) {
2081                                        rotate_extrude(angle=90, $fn=4)
2082                                            left_half(planar=true, $fn=fn)
2083                                                zrot(-90) fwd(size.y) children();
2084                                    }
2085                                    linear_extrude(height=size.x) {
2086                                        mask2d_roundover(size.y, inset=0.01, $fn=4);
2087                                    }
2088                                } else if (corner_type=="round") {
2089                                    move([size.y,size.y]) {
2090                                        rotate_extrude(angle=90)
2091                                            left_half(planar=true)
2092                                                zrot(-90) fwd(size.y) children();
2093                                    }
2094                                    linear_extrude(height=size.x) {
2095                                        mask2d_roundover(size.y, inset=0.01);
2096                                    }
2097                                }
2098                            }
2099                        } else if (vp1.y == vp2.y) {
2100                            if (corner_type=="chamfer") {
2101                                fn = $fn;
2102                                rotate_extrude(angle=90, $fn=4)
2103                                    right_half(planar=true, $fn=fn)
2104                                        children();
2105                                rotate_extrude(angle=90, $fn=4)
2106                                    left_half(planar=true, $fn=fn)
2107                                        children();
2108                            } else if (corner_type=="round") {
2109                                rotate_extrude(angle=90)
2110                                    right_half(planar=true)
2111                                        children();
2112                                rotate_extrude(angle=90)
2113                                    left_half(planar=true)
2114                                        children();
2115                            } else { //corner_type == "sharp"
2116                                intersection() {
2117                                    rot([90,0, 0]) linear_extrude(height=100,center=true,convexity=convexity) children();
2118                                    rot([90,0,90]) linear_extrude(height=100,center=true,convexity=convexity) children();
2119                                }
2120                            }
2121                        }
2122                    }
2123                }
2124            }
2125        }
2126        for (i = idx(edge_string)) {
2127            $attach_to = undef;
2128            $attach_anchor = _find_anchor(edge_string[i], $parent_geom);
2129            $attach_norot = true;
2130            $profile_type = "edge";
2131            edge_profile(edge_string[i], excess=excess, convexity=convexity) {
2132                if (flipverts[i]) {
2133                    mirror([-1,1]) children();
2134                } else {
2135                    children();
2136                }
2137            }
2138        }
2139    }
2140}
2141
2142
2143
2144// Module: corner_profile()
2145// Synopsis: Rotationally extrudes a 2d edge profile into corner mask on the given corners of the parent.
2146// SynTags: Geom
2147// Topics: Attachments, Masking
2148// See Also: attachable(), position(), attach(), face_profile(), edge_profile(), corner_mask(), face_mask(), edge_mask()
2149// Usage:
2150//   PARENT() corner_profile([corners], [except], [r=|d=], [convexity=]) CHILDREN;
2151// Description:
2152//   Takes a 2D mask shape, rotationally extrudes and converts it into a corner mask, and attaches it
2153//   to the selected corners with the appropriate orientation. If no tag is set then `corner_profile()`
2154//   sets the tag for children to "remove" so that it will work with the default {{diff()}} tag.
2155//   See [Specifying Corners](attachments.scad#subsection-specifying-corners) for information on how to specify corner sets.
2156//   For a step-by-step explanation of attachments, see the [Attachments Tutorial](Tutorial-Attachments).
2157// Arguments:
2158//   corners = Corners to mask.  See [Specifying Corners](attachments.scad#subsection-specifying-corners).  Default: All corners.
2159//   except = Corners to explicitly NOT mask.  See [Specifying Corners](attachments.scad#subsection-specifying-corners).  Default: No corners.
2160//   ---
2161//   r = Radius of corner mask.
2162//   d = Diameter of corner mask.
2163//   convexity = Max number of times a line could intersect the perimeter of the mask shape.  Default: 10
2164// Side Effects:
2165//   Tags the children with "remove" (and hence sets `$tag`) if no tag is already set.
2166//   `$idx` is set to the index number of each corner.
2167//   `$attach_anchor` is set for each corner given, to the `[ANCHOR, POSITION, ORIENT, SPIN]` information for that anchor.
2168//   `$profile_type` is set to `"corner"`.
2169// Example:
2170//   diff()
2171//   cuboid([50,60,70],rounding=10,edges="Z",anchor=CENTER) {
2172//       corner_profile(TOP,r=10)
2173//           mask2d_teardrop(r=10, angle=40);
2174//   }
2175module corner_profile(corners=CORNERS_ALL, except=[], r, d, convexity=10) {
2176    check1 = assert($parent_geom != undef, "No object to attach to!");
2177    r = max(0.01, get_radius(r=r, d=d, dflt=undef));
2178    check2 = assert(is_num(r), "Bad r/d argument.");
2179    corners = _corners(corners, except=except);
2180    vecs = [for (i = [0:7]) if (corners[i]>0) CORNER_OFFSETS[i]];
2181    all_vecs_are_corners = all([for (vec = vecs) sum(v_abs(vec))==3]);
2182    check3 = assert(all_vecs_are_corners, "All vectors must be corners.");
2183    for ($idx = idx(vecs)) {
2184        vec = vecs[$idx];
2185        anch = _find_anchor(vec, $parent_geom);
2186        $attach_to = undef;
2187        $attach_anchor = anch;
2188        $attach_norot = true;
2189        $profile_type = "corner";
2190        rotang = vec.z<0?
2191            [  0,0,180+v_theta(vec)-45] :
2192            [180,0,-90+v_theta(vec)-45];
2193        default_tag("remove"){
2194            translate(anch[1]) {
2195                rot(rotang) {
2196                    down(0.01) {
2197                        linear_extrude(height=r+0.01, center=false) {
2198                            difference() {
2199                                translate(-[0.01,0.01]) square(r);
2200                                translate([r,r]) circle(r=r*0.999);
2201                            }
2202                        }
2203                    }
2204                    translate([r,r]) zrot(180) {
2205                        rotate_extrude(angle=90, convexity=convexity) {
2206                            right(r) xflip() {
2207                                children();
2208                            }
2209                        }
2210                    }
2211                }
2212            }
2213        }
2214    }
2215}
2216
2217
2218// Section: Making your objects attachable
2219
2220
2221// Module: attachable()
2222// Synopsis: Manages the anchoring, spin, orientation, and attachments for an object.
2223// Topics: Attachments
2224// See Also: reorient()
2225// Usage: Square/Trapezoid Geometry
2226//   attachable(anchor, spin, two_d=true, size=, [size2=], [shift=], [override=], ...) {OBJECT; children();}
2227// Usage: Circle/Oval Geometry
2228//   attachable(anchor, spin, two_d=true, r=|d=, ...) {OBJECT; children();}
2229// Usage: 2D Path/Polygon Geometry
2230//   attachable(anchor, spin, two_d=true, path=, [extent=], ...) {OBJECT; children();}
2231// Usage: 2D Region Geometry
2232//   attachable(anchor, spin, two_d=true, region=, [extent=], ...) {OBJECT; children();}
2233// Usage: Cubical/Prismoidal Geometry
2234//   attachable(anchor, spin, [orient], size=, [size2=], [shift=], [override=],  ...) {OBJECT; children();}
2235// Usage: Cylindrical Geometry
2236//   attachable(anchor, spin, [orient], r=|d=, l=, [axis=], ...) {OBJECT; children();}
2237// Usage: Conical Geometry
2238//   attachable(anchor, spin, [orient], r1=|d1=, r2=|d2=, l=, [axis=], ...) {OBJECT; children();}
2239// Usage: Spheroid/Ovoid Geometry
2240//   attachable(anchor, spin, [orient], r=|d=, ...) {OBJECT; children();}
2241// Usage: Extruded Path/Polygon Geometry
2242//   attachable(anchor, spin, path=, l=|h=, [extent=], ...) {OBJECT; children();}
2243// Usage: Extruded Region Geometry
2244//   attachable(anchor, spin, region=, l=|h=, [extent=], ...) {OBJECT; children();}
2245// Usage: VNF Geometry
2246//   attachable(anchor, spin, [orient], vnf=, [extent=], ...) {OBJECT; children();}
2247// Usage: Pre-Specified Geometry
2248//   attachable(anchor, spin, [orient], geom=) {OBJECT; children();}
2249//
2250// Description:
2251//   Manages the anchoring, spin, orientation, and attachments for OBJECT, located in a 3D volume or 2D area.
2252//   A managed 3D volume is assumed to be vertically (Z-axis) oriented, and centered.
2253//   A managed 2D area is just assumed to be centered.  The shape to be managed is given
2254//   as the first child to this module, and the second child should be given as `children()`.
2255//   For example, to manage a conical shape:
2256//   ```openscad
2257//   attachable(anchor, spin, orient, r1=r1, r2=r2, l=h) {
2258//       cyl(r1=r1, r2=r2, l=h);
2259//       children();
2260//   }
2261//   ```
2262//   .
2263//   If this is *not* run as a child of `attach()` with the `to` argument
2264//   given, then the following transformations are performed in order:
2265//   * Translates so the `anchor` point is at the origin (0,0,0).
2266//   * Rotates around the Z axis by `spin` degrees counter-clockwise.
2267//   * Rotates so the top of the part points towards the vector `orient`.
2268//   .
2269//   If this is called as a child of `attach(from,to)`, then the info
2270//   for the anchor points referred to by `from` and `to` are fetched,
2271//   which will include position, direction, and spin.  With that info,
2272//   the following transformations are performed:
2273//   * Translates this part so it's anchor position matches the parent's anchor position.
2274//   * Rotates this part so it's anchor direction vector exactly opposes the parent's anchor direction vector.
2275//   * Rotates this part so it's anchor spin matches the parent's anchor spin.
2276//   .
2277//   This module is also responsible for handing coloring of objects with {{recolor()}} and {{color_this()}}, and
2278//   it is responsible for processing tags and determining whether the object should
2279//   display or not in the current context.  The determination to display the attachable object
2280//   occurs in this module, which means that an object which does not display (e.g. a "remove" tagged object
2281//   inside {{diff()}} cannot have internal {{tag()}} calls that change its tags and cause submodel
2282//   portions to display: the entire child simply does not run.  
2283
2284
2285//   For a step-by-step explanation of attachments, see the [Attachments Tutorial](Tutorial-Attachments).
2286//
2287// Arguments:
2288//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#subsection-anchor).  Default: `CENTER`
2289//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#subsection-spin).  Default: `0`
2290//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#subsection-orient).  Default: `UP`
2291//   ---
2292//   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.
2293//   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.
2294//   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.
2295//   r = Radius of the cylindrical/conical volume.  Can be a scalar, or a list of sizes per axis.
2296//   d = Diameter of the cylindrical/conical volume.  Can be a scalar, or a list of sizes per axis.
2297//   r1 = Radius of the bottom of the conical volume.  Can be a scalar, or a list of sizes per axis.
2298//   r2 = Radius of the top of the conical volume.  Can be a scalar, or a list of sizes per axis.
2299//   d1 = Diameter of the bottom of the conical volume.  Can be a scalar, a list of sizes per axis.
2300//   d2 = Diameter of the top of the conical volume.  Can be a scalar, a list of sizes per axis.
2301//   l/h = Length of the cylindrical, conical, or extruded path volume along axis.
2302//   vnf = The [VNF](vnf.scad) of the volume.
2303//   path = The path to generate a polygon from.
2304//   region = The region to generate a shape from.
2305//   extent = If true, calculate anchors by extents, rather than intersection, for VNFs and paths.  Default: true.
2306//   cp = If given, specifies the centerpoint of the volume.  Default: `[0,0,0]`
2307//   offset = If given, offsets the perimeter of the volume around the centerpoint.
2308//   anchors = If given as a list of anchor points, allows named anchor points.
2309//   two_d = If true, the attachable shape is 2D.  If false, 3D.  Default: false (3D)
2310//   axis = The vector pointing along the axis of a geometry.  Default: UP
2311//   override = Function that takes an anchor and for 3d returns a triple `[position, direction, spin]` or for 2d returns a pair `[position,direction]` to use for that anchor to override the normal one.  You can also supply a lookup table that is a list of `[anchor, [position, direction, spin]]` entries.  If the direction/position/spin that is returned is undef then the default will be used.  This option applies only to the "trapezoid" and "prismoid" geometry types.  
2312//   geom = If given, uses the pre-defined (via {{attach_geom()}} geometry.
2313//
2314// Side Effects:
2315//   `$parent_anchor` is set to the parent object's `anchor` value.
2316//   `$parent_spin` is set to the parent object's `spin` value.
2317//   `$parent_orient` is set to the parent object's `orient` value.
2318//   `$parent_geom` is set to the parent object's `geom` value.
2319//   `$parent_size` is set to the parent object's cubical `[X,Y,Z]` volume size.
2320//   `$color` is used to set the color of the object
2321//   `$save_color` is used to revert color to the parent's color
2322//
2323// Example(NORENDER): Cubical Shape
2324//   attachable(anchor, spin, orient, size=size) {
2325//       cube(size, center=true);
2326//       children();
2327//   }
2328//
2329// Example(NORENDER): Prismoidal Shape
2330//   attachable(
2331//       anchor, spin, orient,
2332//       size=point3d(botsize,h),
2333//       size2=topsize,
2334//       shift=shift
2335//   ) {
2336//       prismoid(botsize, topsize, h=h, shift=shift);
2337//       children();
2338//   }
2339//
2340// Example(NORENDER): Cylindrical Shape, Z-Axis Aligned
2341//   attachable(anchor, spin, orient, r=r, l=h) {
2342//       cyl(r=r, l=h);
2343//       children();
2344//   }
2345//
2346// Example(NORENDER): Cylindrical Shape, Y-Axis Aligned
2347//   attachable(anchor, spin, orient, r=r, l=h, axis=BACK) {
2348//       cyl(r=r, l=h);
2349//       children();
2350//   }
2351//
2352// Example(NORENDER): Cylindrical Shape, X-Axis Aligned
2353//   attachable(anchor, spin, orient, r=r, l=h, axis=RIGHT) {
2354//       cyl(r=r, l=h);
2355//       children();
2356//   }
2357//
2358// Example(NORENDER): Conical Shape, Z-Axis Aligned
2359//   attachable(anchor, spin, orient, r1=r1, r2=r2, l=h) {
2360//       cyl(r1=r1, r2=r2, l=h);
2361//       children();
2362//   }
2363//
2364// Example(NORENDER): Conical Shape, Y-Axis Aligned
2365//   attachable(anchor, spin, orient, r1=r1, r2=r2, l=h, axis=BACK) {
2366//       cyl(r1=r1, r2=r2, l=h);
2367//       children();
2368//   }
2369//
2370// Example(NORENDER): Conical Shape, X-Axis Aligned
2371//   attachable(anchor, spin, orient, r1=r1, r2=r2, l=h, axis=RIGHT) {
2372//       cyl(r1=r1, r2=r2, l=h);
2373//       children();
2374//   }
2375//
2376// Example(NORENDER): Spherical Shape
2377//   attachable(anchor, spin, orient, r=r) {
2378//       sphere(r=r);
2379//       children();
2380//   }
2381//
2382// Example(NORENDER): Extruded Polygon Shape, by Extents
2383//   attachable(anchor, spin, orient, path=path, l=length) {
2384//       linear_extrude(height=length, center=true)
2385//           polygon(path);
2386//       children();
2387//   }
2388//
2389// Example(NORENDER): Extruded Polygon Shape, by Intersection
2390//   attachable(anchor, spin, orient, path=path, l=length, extent=false) {
2391//       linear_extrude(height=length, center=true)
2392//           polygon(path);
2393//       children();
2394//   }
2395//
2396// Example(NORENDER): Arbitrary VNF Shape, by Extents
2397//   attachable(anchor, spin, orient, vnf=vnf) {
2398//       vnf_polyhedron(vnf);
2399//       children();
2400//   }
2401//
2402// Example(NORENDER): Arbitrary VNF Shape, by Intersection
2403//   attachable(anchor, spin, orient, vnf=vnf, extent=false) {
2404//       vnf_polyhedron(vnf);
2405//       children();
2406//   }
2407//
2408// Example(NORENDER): 2D Rectangular Shape
2409//   attachable(anchor, spin, orient, two_d=true, size=size) {
2410//       square(size, center=true);
2411//       children();
2412//   }
2413//
2414// Example(NORENDER): 2D Trapezoidal Shape
2415//   attachable(
2416//       anchor, spin, orient,
2417//       two_d=true,
2418//       size=[x1,y],
2419//       size2=x2,
2420//       shift=shift
2421//   ) {
2422//       trapezoid(w1=x1, w2=x2, h=y, shift=shift);
2423//       children();
2424//   }
2425//
2426// Example(NORENDER): 2D Circular Shape
2427//   attachable(anchor, spin, orient, two_d=true, r=r) {
2428//       circle(r=r);
2429//       children();
2430//   }
2431//
2432// Example(NORENDER): Arbitrary 2D Polygon Shape, by Extents
2433//   attachable(anchor, spin, orient, two_d=true, path=path) {
2434//       polygon(path);
2435//       children();
2436//   }
2437//
2438// Example(NORENDER): Arbitrary 2D Polygon Shape, by Intersection
2439//   attachable(anchor, spin, orient, two_d=true, path=path, extent=false) {
2440//       polygon(path);
2441//       children();
2442//   }
2443//
2444// Example(NORENDER): Using Pre-defined Geometry
2445//   geom = atype=="perim"? attach_geom(two_d=true, path=path, extent=false) :
2446//       atype=="extents"? attach_geom(two_d=true, path=path, extent=true) :
2447//       atype=="circle"? attach_geom(two_d=true, r=r) :
2448//       assert(false, "Bad atype");
2449//   attachable(anchor, spin, orient, geom=geom) {
2450//       polygon(path);
2451//       children();
2452//   }
2453//
2454// 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.
2455//   module thing(anchor,spin,orient) {
2456//      tag("remove") attachable(size=[15,15,15],anchor=anchor,spin=spin,orient=orient){
2457//        cuboid([10,10,16]);
2458//        union(){}   // dummy children
2459//      }
2460//      attachable(size=[15,15,15], anchor=anchor, spin=spin, orient=orient){
2461//        cuboid([15,15,15]);
2462//        children();
2463//      }
2464//   }
2465//   diff()
2466//     cube([19,10,19])
2467//       attach([FRONT],overlap=-4)
2468//         thing(anchor=TOP)
2469//           # attach(TOP) cuboid(2,anchor=TOP);
2470// 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.
2471//   module thing(anchor = CENTER, spin = 0, orient = UP) {
2472//      tag("remove") attachable(anchor, spin, orient, d1=0,d2=95,h=33) {
2473//          cylinder(h = 33.1, d1 = 0, d2 = 95, anchor=CENTER);
2474//          union(){}  // dummy children
2475//      }
2476//      tag("keep") attachable(anchor, spin, orient,d1=0,d2=95,h=33) {
2477//            cylinder(h = 33, d = 10,anchor=CENTER);
2478//            children();
2479//        }
2480//    }
2481//    diff()
2482//      cube(100)
2483//        attach([FRONT,TOP],overlap=-4)
2484//          thing(anchor=TOP)
2485//            tube(ir=12,h=10);
2486module attachable(
2487    anchor, spin, orient,
2488    size, size2, shift,
2489    r,r1,r2, d,d1,d2, l,h,
2490    vnf, path, region,
2491    extent=true,
2492    cp=[0,0,0],
2493    offset=[0,0,0],
2494    anchors=[],
2495    two_d=false,
2496    axis=UP,override,
2497    geom
2498) { 
2499    dummy1 =
2500        assert($children==2, "attachable() expects exactly two children; the shape to manage, and the union of all attachment candidates.")
2501        assert(is_undef(anchor) || is_vector(anchor) || is_string(anchor), str("Got: ",anchor))
2502        assert(is_undef(spin)   || is_vector(spin,3) || is_num(spin), str("Got: ",spin))
2503        assert(is_undef(orient) || is_vector(orient,3), str("Got: ",orient));
2504    anchor = first_defined([$anchor_override, anchor, CENTER]);
2505    spin =   default(spin,   0);
2506    orient = is_def($anchor_override)? UP : default(orient, UP);
2507    region = !is_undef(region)? region :
2508        !is_undef(path)? [path] :
2509        undef;
2510    geom = is_def(geom)? geom :
2511        attach_geom(
2512            size=size, size2=size2, shift=shift,
2513            r=r, r1=r1, r2=r2, h=h,
2514            d=d, d1=d1, d2=d2, l=l,
2515            vnf=vnf, region=region, extent=extent,
2516            cp=cp, offset=offset, anchors=anchors,
2517            two_d=two_d, axis=axis, override=override
2518        );
2519    m = _attach_transform(anchor,spin,orient,geom);
2520    multmatrix(m) {
2521        $parent_anchor = anchor;
2522        $parent_spin   = spin;
2523        $parent_orient = orient;
2524        $parent_geom   = geom;
2525        $parent_size   = _attach_geom_size(geom);
2526        $attach_to   = undef;
2527        $anchor_override=undef;
2528        if (_is_shown())
2529            _color($color) children(0);
2530        if (is_def($save_color)) {
2531            $color=$save_color;
2532            $save_color=undef;
2533            children(1);
2534        }
2535        else children(1);
2536    }
2537}
2538
2539// Function: reorient()
2540// Synopsis: Calculates the transformation matrix needed to reorient an object.
2541// SynTags: Trans, Path, VNF
2542// Topics: Attachments
2543// See Also: reorient(), attachable()
2544// Usage: Square/Trapezoid Geometry
2545//   mat = reorient(anchor, spin, [orient], two_d=true, size=, [size2=], [shift=], ...);
2546//   pts = reorient(anchor, spin, [orient], two_d=true, size=, [size2=], [shift=], p=, ...);
2547// Usage: Circle/Oval Geometry
2548//   mat = reorient(anchor, spin, [orient], two_d=true, r=|d=, ...);
2549//   pts = reorient(anchor, spin, [orient], two_d=true, r=|d=, p=, ...);
2550// Usage: 2D Path/Polygon Geometry
2551//   mat = reorient(anchor, spin, [orient], two_d=true, path=, [extent=], ...);
2552//   pts = reorient(anchor, spin, [orient], two_d=true, path=, [extent=], p=, ...);
2553// Usage: 2D Region/Polygon Geometry
2554//   mat = reorient(anchor, spin, [orient], two_d=true, region=, [extent=], ...);
2555//   pts = reorient(anchor, spin, [orient], two_d=true, region=, [extent=], p=, ...);
2556// Usage: Cubical/Prismoidal Geometry
2557//   mat = reorient(anchor, spin, [orient], size=, [size2=], [shift=], ...);
2558//   vnf = reorient(anchor, spin, [orient], size=, [size2=], [shift=], p=, ...);
2559// Usage: Cylindrical Geometry
2560//   mat = reorient(anchor, spin, [orient], r=|d=, l=, [axis=], ...);
2561//   vnf = reorient(anchor, spin, [orient], r=|d=, l=, [axis=], p=, ...);
2562// Usage: Conical Geometry
2563//   mat = reorient(anchor, spin, [orient], r1=|d1=, r2=|d2=, l=, [axis=], ...);
2564//   vnf = reorient(anchor, spin, [orient], r1=|d1=, r2=|d2=, l=, [axis=], p=, ...);
2565// Usage: Spheroid/Ovoid Geometry
2566//   mat = reorient(anchor, spin, [orient], r|d=, ...);
2567//   vnf = reorient(anchor, spin, [orient], r|d=, p=, ...);
2568// Usage: Extruded Path/Polygon Geometry
2569//   mat = reorient(anchor, spin, [orient], path=, l=|h=, [extent=], ...);
2570//   vnf = reorient(anchor, spin, [orient], path=, l=|h=, [extent=], p=, ...);
2571// Usage: Extruded Region Geometry
2572//   mat = reorient(anchor, spin, [orient], region=, l=|h=, [extent=], ...);
2573//   vnf = reorient(anchor, spin, [orient], region=, l=|h=, [extent=], p=, ...);
2574// Usage: VNF Geometry
2575//   mat = reorient(anchor, spin, [orient], vnf, [extent], ...);
2576//   vnf = reorient(anchor, spin, [orient], vnf, [extent], p=, ...);
2577//
2578// Description:
2579//   Given anchor, spin, orient, and general geometry info for a managed volume, this calculates
2580//   the transformation matrix needed to be applied to the contents of that volume.  A managed 3D
2581//   volume is assumed to be vertically (Z-axis) oriented, and centered.  A managed 2D area is just
2582//   assumed to be centered.
2583//   .
2584//   If `p` is not given, then the transformation matrix will be returned.
2585//   If `p` contains a VNF, a new VNF will be returned with the vertices transformed by the matrix.
2586//   If `p` contains a path, a new path will be returned with the vertices transformed by the matrix.
2587//   If `p` contains a point, a new point will be returned, transformed by the matrix.
2588//   .
2589//   If `$attach_to` is not defined, then the following transformations are performed in order:
2590//   * Translates so the `anchor` point is at the origin (0,0,0).
2591//   * Rotates around the Z axis by `spin` degrees counter-clockwise.
2592//   * Rotates so the top of the part points towards the vector `orient`.
2593//   .
2594//   If `$attach_to` is defined, as a consequence of `attach(from,to)`, then
2595//   the following transformations are performed in order:
2596//   * Translates this part so it's anchor position matches the parent's anchor position.
2597//   * Rotates this part so it's anchor direction vector exactly opposes the parent's anchor direction vector.
2598//   * Rotates this part so it's anchor spin matches the parent's anchor spin.
2599//   .
2600//   For a step-by-step explanation of attachments, see the [Attachments Tutorial](Tutorial-Attachments).
2601//
2602// Arguments:
2603//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#subsection-anchor).  Default: `CENTER`
2604//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#subsection-spin).  Default: `0`
2605//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#subsection-orient).  Default: `UP`
2606//   ---
2607//   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.
2608//   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.
2609//   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.
2610//   r = Radius of the cylindrical/conical volume.  Can be a scalar, or a list of sizes per axis.
2611//   d = Diameter of the cylindrical/conical volume.  Can be a scalar, or a list of sizes per axis.
2612//   r1 = Radius of the bottom of the conical volume.  Can be a scalar, or a list of sizes per axis.
2613//   r2 = Radius of the top of the conical volume.  Can be a scalar, or a list of sizes per axis.
2614//   d1 = Diameter of the bottom of the conical volume.  Can be a scalar, a list of sizes per axis.
2615//   d2 = Diameter of the top of the conical volume.  Can be a scalar, a list of sizes per axis.
2616//   l/h = Length of the cylindrical, conical, or extruded path volume along axis.
2617//   vnf = The [VNF](vnf.scad) of the volume.
2618//   path = The path to generate a polygon from.
2619//   region = The region to generate a shape from.
2620//   extent = If true, calculate anchors by extents, rather than intersection.  Default: false.
2621//   cp = If given, specifies the centerpoint of the volume.  Default: `[0,0,0]`
2622//   offset = If given, offsets the perimeter of the volume around the centerpoint.
2623//   anchors = If given as a list of anchor points, allows named anchor points.
2624//   two_d = If true, the attachable shape is 2D.  If false, 3D.  Default: false (3D)
2625//   axis = The vector pointing along the axis of a geometry.  Default: UP
2626//   p = The VNF, path, or point to transform.
2627function reorient(
2628    anchor, spin, orient,
2629    size, size2, shift,
2630    r,r1,r2, d,d1,d2, l,h,
2631    vnf, path, region,
2632    extent=true,
2633    offset=[0,0,0],
2634    cp=[0,0,0],
2635    anchors=[],
2636    two_d=false,
2637    axis=UP, override, 
2638    geom,
2639    p=undef
2640) =
2641    assert(is_undef(anchor) || is_vector(anchor) || is_string(anchor), str("Got: ",anchor))
2642    assert(is_undef(spin)   || is_vector(spin,3) || is_num(spin), str("Got: ",spin))
2643    assert(is_undef(orient) || is_vector(orient,3), str("Got: ",orient))
2644    let(
2645        anchor = default(anchor, CENTER),
2646        spin =   default(spin,   0),
2647        orient = default(orient, UP),
2648        region = !is_undef(region)? region :
2649            !is_undef(path)? [path] :
2650            undef,
2651        geom = is_def(geom)? geom :
2652            attach_geom(
2653                size=size, size2=size2, shift=shift,
2654                r=r, r1=r1, r2=r2, h=h,
2655                d=d, d1=d1, d2=d2, l=l,
2656                vnf=vnf, region=region, extent=extent,
2657                cp=cp, offset=offset, anchors=anchors,
2658                two_d=two_d, axis=axis, override=override
2659            ),
2660        $attach_to = undef
2661    ) _attach_transform(anchor,spin,orient,geom,p);
2662
2663
2664// Function: named_anchor()
2665// Synopsis: Creates an anchor data structure.
2666// Topics: Attachments
2667// See Also: reorient(), attachable()
2668// Usage:
2669//   a = named_anchor(name, pos, [orient], [spin]);
2670// Description:
2671//   Creates an anchor data structure.  For a step-by-step explanation of attachments,
2672//   see the [Attachments Tutorial](Tutorial-Attachments).
2673// Arguments:
2674//   name = The string name of the anchor.  Lowercase.  Words separated by single dashes.  No spaces.
2675//   pos = The [X,Y,Z] position of the anchor.
2676//   orient = A vector pointing in the direction parts should project from the anchor position.  Default: UP
2677//   spin = If needed, the angle to rotate the part around the direction vector.  Default: 0
2678function named_anchor(name, pos, orient=UP, spin=0) = [name, pos, orient, spin];
2679
2680
2681// Function: attach_geom()
2682// Synopsis: Returns the internal geometry description of an attachable object.
2683// Topics: Attachments
2684// See Also: reorient(), attachable()
2685// Usage: Null/Point Geometry
2686//   geom = attach_geom(...);
2687// Usage: Square/Trapezoid Geometry
2688//   geom = attach_geom(two_d=true, size=, [size2=], [shift=], ...);
2689// Usage: Circle/Oval Geometry
2690//   geom = attach_geom(two_d=true, r=|d=, ...);
2691// Usage: 2D Path/Polygon/Region Geometry
2692//   geom = attach_geom(two_d=true, region=, [extent=], ...);
2693// Usage: Cubical/Prismoidal Geometry
2694//   geom = attach_geom(size=, [size2=], [shift=], ...);
2695// Usage: Cylindrical Geometry
2696//   geom = attach_geom(r=|d=, l=|h=, [axis=], ...);
2697// Usage: Conical Geometry
2698//   geom = attach_geom(r1|d1=, r2=|d2=, l=, [axis=], ...);
2699// Usage: Spheroid/Ovoid Geometry
2700//   geom = attach_geom(r=|d=, ...);
2701// Usage: Extruded 2D Path/Polygon/Region Geometry
2702//   geom = attach_geom(region=, l=|h=, [extent=], [shift=], [scale=], [twist=], ...);
2703// Usage: VNF Geometry
2704//   geom = attach_geom(vnf=, [extent=], ...);
2705//
2706// Description:
2707//   Given arguments that describe the geometry of an attachable object, returns the internal geometry description.
2708//   This will probably not not ever need to be called by the end user.
2709//
2710// Arguments:
2711//   ---
2712//   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.
2713//   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.
2714//   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.
2715//   scale = If given as number or a 2D vector, scales the top of the shape, relative to the bottom.  Default: `[1,1]`
2716//   twist = If given as number, rotates the top of the shape by the given number of degrees clockwise, relative to the bottom.  Default: `0`
2717//   r = Radius of the cylindrical/conical volume.  Can be a scalar, or a list of sizes per axis.
2718//   d = Diameter of the cylindrical/conical volume.  Can be a scalar, or a list of sizes per axis.
2719//   r1 = Radius of the bottom of the conical volume.  Can be a scalar, or a list of sizes per axis.
2720//   r2 = Radius of the top of the conical volume.  Can be a scalar, or a list of sizes per axis.
2721//   d1 = Diameter of the bottom of the conical volume.  Can be a scalar, a list of sizes per axis.
2722//   d2 = Diameter of the top of the conical volume.  Can be a scalar, a list of sizes per axis.
2723//   l/h = Length of the cylindrical, conical or extruded region volume along axis.
2724//   vnf = The [VNF](vnf.scad) of the volume.
2725//   region = The region to generate a shape from.
2726//   extent = If true, calculate anchors by extents, rather than intersection.  Default: true.
2727//   cp = If given, specifies the centerpoint of the volume.  Default: `[0,0,0]`
2728//   offset = If given, offsets the perimeter of the volume around the centerpoint.
2729//   anchors = If given as a list of anchor points, allows named anchor points.
2730//   two_d = If true, the attachable shape is 2D.  If false, 3D.  Default: false (3D)
2731//   axis = The vector pointing along the axis of a geometry.  Default: UP
2732//   override = Function that takes an anchor and returns a pair `[position,direction]` to use for that anchor to override the normal one.  You can also supply a lookup table that is a list of `[anchor, [position, direction]]` entries.  If the direction/position that is returned is undef then the default will be used.
2733//
2734// Example(NORENDER): Null/Point Shape
2735//   geom = attach_geom();
2736//
2737// Example(NORENDER): Cubical Shape
2738//   geom = attach_geom(size=size);
2739//
2740// Example(NORENDER): Prismoidal Shape
2741//   geom = attach_geom(
2742//       size=point3d(botsize,h),
2743//       size2=topsize, shift=shift
2744//   );
2745//
2746// Example(NORENDER): Cylindrical Shape, Z-Axis Aligned
2747//   geom = attach_geom(r=r, h=h);
2748//
2749// Example(NORENDER): Cylindrical Shape, Y-Axis Aligned
2750//   geom = attach_geom(r=r, h=h, axis=BACK);
2751//
2752// Example(NORENDER): Cylindrical Shape, X-Axis Aligned
2753//   geom = attach_geom(r=r, h=h, axis=RIGHT);
2754//
2755// Example(NORENDER): Conical Shape, Z-Axis Aligned
2756//   geom = attach_geom(r1=r1, r2=r2, h=h);
2757//
2758// Example(NORENDER): Conical Shape, Y-Axis Aligned
2759//   geom = attach_geom(r1=r1, r2=r2, h=h, axis=BACK);
2760//
2761// Example(NORENDER): Conical Shape, X-Axis Aligned
2762//   geom = attach_geom(r1=r1, r2=r2, h=h, axis=RIGHT);
2763//
2764// Example(NORENDER): Spherical Shape
2765//   geom = attach_geom(r=r);
2766//
2767// Example(NORENDER): Ovoid Shape
2768//   geom = attach_geom(r=[r_x, r_y, r_z]);
2769//
2770// Example(NORENDER): Arbitrary VNF Shape, Anchored by Extents
2771//   geom = attach_geom(vnf=vnf);
2772//
2773// Example(NORENDER): Arbitrary VNF Shape, Anchored by Intersection
2774//   geom = attach_geom(vnf=vnf, extent=false);
2775//
2776// Example(NORENDER): 2D Rectangular Shape
2777//   geom = attach_geom(two_d=true, size=size);
2778//
2779// Example(NORENDER): 2D Trapezoidal Shape
2780//   geom = attach_geom(two_d=true, size=[x1,y], size2=x2, shift=shift, override=override);
2781//
2782// Example(NORENDER): 2D Circular Shape
2783//   geom = attach_geom(two_d=true, r=r);
2784//
2785// Example(NORENDER): 2D Oval Shape
2786//   geom = attach_geom(two_d=true, r=[r_x, r_y]);
2787//
2788// Example(NORENDER): Arbitrary 2D Region Shape, Anchored by Extents
2789//   geom = attach_geom(two_d=true, region=region);
2790//
2791// Example(NORENDER): Arbitrary 2D Region Shape, Anchored by Intersection
2792//   geom = attach_geom(two_d=true, region=region, extent=false);
2793//
2794// Example(NORENDER): Extruded Region, Anchored by Extents
2795//   geom = attach_geom(region=region, l=height);
2796//
2797// Example(NORENDER): Extruded Region, Anchored by Intersection
2798//   geom = attach_geom(region=region, l=length, extent=false);
2799//
2800
2801function _local_struct_val(struct, key)=
2802    assert(is_def(key),"key is missing")
2803    let(ind = search([key],struct)[0])
2804    ind == [] ? undef : struct[ind][1];
2805
2806
2807function attach_geom(
2808    size, size2,
2809    shift, scale, twist,
2810    r,r1,r2, d,d1,d2, l,h,
2811    vnf, region,
2812    extent=true,
2813    cp=[0,0,0],
2814    offset=[0,0,0],
2815    anchors=[],
2816    two_d=false,
2817    axis=UP, override
2818) =
2819    assert(is_bool(extent))
2820    assert(is_vector(cp) || is_string(cp))
2821    assert(is_vector(offset))
2822    assert(is_list(anchors))
2823    assert(is_bool(two_d))
2824    assert(is_vector(axis))
2825    !is_undef(size)? (
2826        let(
2827            over_f = is_undef(override) ? function(anchor) [undef,undef,undef]
2828                   : is_func(override) ? override
2829                   : function(anchor) _local_struct_val(override,anchor)
2830        )
2831        two_d? (
2832            let(
2833                size2 = default(size2, size.x),
2834                shift = default(shift, 0)
2835            )
2836            assert(is_vector(size,2))
2837            assert(is_num(size2))
2838            assert(is_num(shift))
2839            ["trapezoid", point2d(size), size2, shift, over_f, cp, offset, anchors]
2840        ) : (
2841            let(
2842                size2 = default(size2, point2d(size)),
2843                shift = default(shift, [0,0])
2844            )
2845            assert(is_vector(size,3))
2846            assert(is_vector(size2,2))
2847            assert(is_vector(shift,2))
2848            ["prismoid", size, size2, shift, axis, over_f, cp, offset, anchors]
2849        )
2850    ) : !is_undef(vnf)? (
2851        assert(is_vnf(vnf))
2852        assert(two_d == false)
2853        extent? ["vnf_extent", vnf, cp, offset, anchors] :
2854        ["vnf_isect", vnf, cp, offset, anchors]
2855    ) : !is_undef(region)? (
2856        assert(is_region(region),2)
2857        let( l = default(l, h) )
2858        two_d==true
2859          ? assert(is_undef(l))
2860            extent==true
2861              ? ["rgn_extent", region, cp, offset, anchors]
2862              : ["rgn_isect",  region, cp, offset, anchors]
2863          : assert(is_finite(l))
2864            let(
2865                shift = default(shift, [0,0]),
2866                scale = is_num(scale)? [scale,scale] : default(scale, [1,1]),
2867                twist = default(twist, 0)
2868            )
2869            assert(is_vector(shift,2))
2870            assert(is_vector(scale,2))
2871            assert(is_num(twist))
2872            extent==true
2873              ? ["extrusion_extent", region, l, twist, scale, shift, cp, offset, anchors]
2874              : ["extrusion_isect",  region, l, twist, scale, shift, cp, offset, anchors]
2875    ) :
2876    let(
2877        r1 = get_radius(r1=r1,d1=d1,r=r,d=d,dflt=undef)
2878    )
2879    !is_undef(r1)? (
2880        let( l = default(l, h) )
2881        !is_undef(l)? (
2882            let(
2883                shift = default(shift, [0,0]),
2884                r2 = get_radius(r1=r2,d1=d2,r=r,d=d,dflt=undef)
2885            )
2886            assert(is_num(r1) || is_vector(r1,2))
2887            assert(is_num(r2) || is_vector(r2,2))
2888            assert(is_num(l))
2889            assert(is_vector(shift,2))
2890            ["conoid", r1, r2, l, shift, axis, cp, offset, anchors]
2891        ) : (
2892            two_d? (
2893                assert(is_num(r1) || is_vector(r1,2))
2894                ["ellipse", r1, cp, offset, anchors]
2895            ) : (
2896                assert(is_num(r1) || is_vector(r1,3))
2897                ["spheroid", r1, cp, offset, anchors]
2898            )
2899        )
2900    ) :
2901    ["point", cp, offset, anchors];
2902
2903
2904
2905
2906
2907
2908//////////////////////////////////////////////////////////////////////////////////////////////////////////////
2909//
2910// Attachment internal functions
2911
2912
2913/// Internal Function: _attach_geom_2d()
2914/// Topics: Attachments
2915/// See Also: reorient(), attachable()
2916/// Usage:
2917///   bool = _attach_geom_2d(geom);
2918/// Description:
2919///   Returns true if the given attachment geometry description is for a 2D shape.
2920function _attach_geom_2d(geom) =
2921    let( type = geom[0] )
2922    type == "trapezoid" || type == "ellipse" ||
2923    type == "rgn_isect" || type == "rgn_extent";
2924
2925
2926/// Internal Function: _attach_geom_size()
2927/// Usage:
2928///   bounds = _attach_geom_size(geom);
2929/// Topics: Attachments
2930/// See Also: reorient(), attachable()
2931/// Description:
2932///   Returns the `[X,Y,Z]` bounding size for the given attachment geometry description.
2933function _attach_geom_size(geom) =
2934    let( type = geom[0] )
2935    type == "point"? [0,0,0] :
2936    type == "prismoid"? ( //size, size2, shift, axis
2937        let(
2938            size=geom[1], size2=geom[2], shift=point2d(geom[3]),
2939            maxx = max(size.x,size2.x),
2940            maxy = max(size.y,size2.y),
2941            z = size.z
2942        ) [maxx, maxy, z]
2943    ) : type == "conoid"? ( //r1, r2, l, shift
2944        let(
2945            r1=geom[1], r2=geom[2], l=geom[3],
2946            shift=point2d(geom[4]), axis=point3d(geom[5]),
2947            rx1 = default(r1[0],r1),
2948            ry1 = default(r1[1],r1),
2949            rx2 = default(r2[0],r2),
2950            ry2 = default(r2[1],r2),
2951            maxxr = max(rx1,rx2),
2952            maxyr = max(ry1,ry2)
2953        )
2954        approx(axis,UP)? [2*maxxr,2*maxyr,l] :
2955        approx(axis,RIGHT)? [l,2*maxyr,2*maxxr] :
2956        approx(axis,BACK)? [2*maxxr,l,2*maxyr] :
2957        [2*maxxr, 2*maxyr, l]
2958    ) : type == "spheroid"? ( //r
2959        let( r=geom[1] )
2960        is_num(r)? [2,2,2]*r : v_mul([2,2,2],point3d(r))
2961    ) : type == "vnf_extent" || type=="vnf_isect"? ( //vnf
2962        let(
2963            vnf = geom[1]
2964        ) vnf==EMPTY_VNF? [0,0,0] :
2965        let(
2966            mm = pointlist_bounds(geom[1][0]),
2967            delt = mm[1]-mm[0]
2968        ) delt
2969    ) : type == "extrusion_isect" || type == "extrusion_extent"? ( //path, l
2970        let(
2971            mm = pointlist_bounds(flatten(geom[1])),
2972            delt = mm[1]-mm[0]
2973        ) [delt.x, delt.y, geom[2]]
2974    ) : type == "trapezoid"? ( //size, size2
2975        let(
2976            size=geom[1], size2=geom[2], shift=geom[3],
2977            maxx = max(size.x,size2+abs(shift))
2978        ) [maxx, size.y]
2979    ) : type == "ellipse"? ( //r
2980        let( r=geom[1] )
2981        is_num(r)? [2,2]*r : v_mul([2,2],point2d(r))
2982    ) : type == "rgn_isect" || type == "rgn_extent"? ( //path
2983        let(
2984            mm = pointlist_bounds(flatten(geom[1])),
2985            delt = mm[1]-mm[0]
2986        ) [delt.x, delt.y]
2987    ) :
2988    assert(false, "Unknown attachment geometry type.");
2989
2990
2991
2992/// Internal Function: _attach_geom_edge_path()
2993/// Usage:
2994///   angle = _attach_geom_edge_path(geom, edge);
2995/// Topics: Attachments
2996/// See Also: reorient(), attachable()
2997/// Description:
2998///   Returns the path and post-transform matrix of the indicated edge.
2999///   If the edge is invalid for the geometry, returns `undef`.
3000function _attach_geom_edge_path(geom, edge) =
3001    assert(is_vector(edge),str("Invalid edge: edge=",edge))
3002    let(
3003        type = geom[0],
3004        cp = _get_cp(geom),
3005        offset_raw = select(geom,-2),
3006        offset = [for (i=[0:2]) edge[i]==0? 0 : offset_raw[i]],  // prevents bad centering.
3007        edge = point3d(edge)
3008    )
3009    type == "prismoid"? ( //size, size2, shift, axis
3010        let(all_comps_good = [for (c=edge) if (c!=sign(c)) 1]==[])
3011        assert(all_comps_good, "All components of an edge for a cuboid/prismoid must be -1, 0, or 1")
3012        let(edge_good = len([for (c=edge) if(c) 1])==2)
3013        assert(edge_good, "Invalid edge.")
3014        let(
3015            size = geom[1],
3016            size2 = geom[2],
3017            shift = point2d(geom[3]),
3018            axis = point3d(geom[4]),
3019            edge = rot(from=axis, to=UP, p=edge),
3020            offset = rot(from=axis, to=UP, p=offset),
3021            h = size.z,
3022            cpos = function(vec) let(
3023                        u = (vec.z + 1) / 2,
3024                        siz = lerp(point2d(size), size2, u) / 2,
3025                        z = vec.z * h / 2,
3026                        pos = point3d(v_mul(siz, point2d(vec)) + shift * u, z)
3027                    ) pos,
3028            ep1 = cpos([for (c=edge) c? c : -1]),
3029            ep2 = cpos([for (c=edge) c? c :  1]),
3030            cp = (ep1 + ep2) / 2,
3031            axy = point2d(edge),
3032            bot = point3d(v_mul(point2d(size )/2, axy), -h/2),
3033            top = point3d(v_mul(point2d(size2)/2, axy) + shift, h/2),
3034            xang = atan2(h,(top-bot).x),
3035            yang = atan2(h,(top-bot).y),
3036            vecs = [
3037                if (edge.x) yrot(90-xang, p=sign(axy.x)*RIGHT),
3038                if (edge.y) xrot(yang-90, p=sign(axy.y)*BACK),
3039                if (edge.z) [0,0,sign(edge.z)]
3040            ], 
3041            segvec = cross(unit(vecs[1]), unit(vecs[0])),
3042            seglen = norm(ep2 - ep1),
3043            path = [
3044                cp - segvec * seglen/2,
3045                cp + segvec * seglen/2
3046            ],
3047            m = rot(from=UP,to=axis) * move(offset)
3048        ) [path, [vecs], m]
3049    ) : type == "conoid"? ( //r1, r2, l, shift, axis
3050        assert(edge.z && edge.z == sign(edge.z), "The Z component of an edge for a cylinder/cone must be -1 or 1")
3051        let(
3052            rr1 = geom[1],
3053            rr2 = geom[2],
3054            l = geom[3],
3055            shift = point2d(geom[4]),
3056            axis = point3d(geom[5]),
3057            r1 = is_num(rr1)? [rr1,rr1] : point2d(rr1),
3058            r2 = is_num(rr2)? [rr2,rr2] : point2d(rr2),
3059            edge = rot(from=axis, to=UP, p=edge),
3060            offset = rot(from=axis, to=UP, p=offset),
3061            maxr = max([each r1, each r2]),
3062            sides = segs(maxr),
3063            top = path3d(move(shift, p=ellipse(r=r2, $fn=sides)), l/2),
3064            bot = path3d(ellipse(r=r1, $fn=sides), -l/2),
3065            path = edge.z < 0 ? bot : top,
3066            path2 = edge.z < 0 ? top : bot,
3067            zed = edge.z<0? [0,0,-l/2] : point3d(shift,l/2),
3068            vecs = [
3069                for (i = idx(top)) let(
3070                    pt1 = (path[i] + select(path,i+1)) /2,
3071                    pt2 = (path2[i] + select(path2,i+1)) /2,
3072                    v1 = unit(zed - pt1),
3073                    v2 = unit(pt2 - pt1),
3074                    v3 = unit(cross(v1,v2)),
3075                    v4 = cross(v3,v2),
3076                    v5 = cross(v1,v3)
3077                ) [v4, v5]
3078            ],
3079            m = rot(from=UP,to=axis) * move(offset)
3080        ) edge.z>0
3081          ? [reverse(list_wrap(path)), reverse(vecs), m]
3082          : [list_wrap(path), vecs, m]
3083    ) : undef;
3084
3085
3086/// Internal Function: _attach_transform()
3087/// Usage: To Get a Transformation Matrix
3088///   mat = _attach_transform(anchor, spin, orient, geom);
3089/// Usage: To Transform Points, Paths, Patches, or VNFs
3090///   new_p = _attach_transform(anchor, spin, orient, geom, p);
3091/// Topics: Attachments
3092/// See Also: reorient(), attachable()
3093/// Description:
3094///   Returns the affine3d transformation matrix needed to `anchor`, `spin`, and `orient`
3095///   the given geometry `geom` shape into position.
3096/// Arguments:
3097///   anchor = Anchor point to translate to the origin `[0,0,0]`.  See [anchor](attachments.scad#subsection-anchor).  Default: `CENTER`
3098///   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#subsection-spin).  Default: `0`
3099///   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#subsection-orient).  Default: `UP`
3100///   geom = The geometry description of the shape.
3101///   p = If given as a VNF, path, or point, applies the affine3d transformation matrix to it and returns the result.
3102function _attach_transform(anchor, spin, orient, geom, p) =
3103    assert(is_undef(anchor) || is_vector(anchor) || is_string(anchor), str("Got: ",anchor))
3104    assert(is_undef(spin)   || is_vector(spin,3) || is_num(spin), str("Got: ",spin))
3105    assert(is_undef(orient) || is_vector(orient,3), str("Got: ",orient))
3106    let(
3107        anchor = default(anchor, CENTER),
3108        
3109        spin   = default(spin,   0),
3110        orient = default(orient, UP),
3111        two_d = _attach_geom_2d(geom),
3112        m = ($attach_to != undef)? (
3113            let(
3114                anch = _find_anchor($attach_to, geom),
3115                pos = anch[1]
3116            ) two_d? (
3117                assert(two_d && is_num(spin))
3118                affine3d_zrot(spin) *
3119                rot(to=FWD, from=point3d(anch[2])) *
3120                affine3d_translate(point3d(-pos))
3121            ) : (
3122                assert(is_num(spin) || is_vector(spin,3))
3123                let(
3124                    ang = vector_angle(anch[2], DOWN),
3125                    axis = vector_axis(anch[2], DOWN),
3126                    ang2 = (anch[2]==UP || anch[2]==DOWN)? 0 : 180-anch[3],
3127                    axis2 = rot(p=axis,[0,0,ang2])
3128                )
3129                affine3d_rot_by_axis(axis2,ang) * (
3130                    is_num(spin)? affine3d_zrot(ang2+spin) : (
3131                        affine3d_zrot(spin.z) *
3132                        affine3d_yrot(spin.y) *
3133                        affine3d_xrot(spin.x) *
3134                        affine3d_zrot(ang2)
3135                    )
3136                ) * affine3d_translate(point3d(-pos))
3137            )
3138        ) : (
3139            let(
3140                pos = _find_anchor(anchor, geom)[1]
3141            ) two_d? (
3142                assert(two_d && is_num(spin))
3143                affine3d_zrot(spin) *
3144                affine3d_translate(point3d(-pos))
3145            ) : (
3146                assert(is_num(spin) || is_vector(spin,3))
3147                let(
3148                    axis = vector_axis(UP,orient),
3149                    ang = vector_angle(UP,orient)
3150                )
3151                affine3d_rot_by_axis(axis,ang) * (
3152                    is_num(spin)? affine3d_zrot(spin) : (
3153                        affine3d_zrot(spin.z) *
3154                        affine3d_yrot(spin.y) *
3155                        affine3d_xrot(spin.x)
3156                    )
3157                ) * affine3d_translate(point3d(-pos))
3158            )
3159        )
3160    ) is_undef(p)? m :
3161    is_vnf(p)? [(p==EMPTY_VNF? p : apply(m, p[0])), p[1]] :
3162    apply(m, p);
3163
3164
3165function _get_cp(geom) =
3166    let(cp=select(geom,-3))
3167    is_vector(cp) ? cp
3168  : let(
3169        type = in_list(geom[0],["vnf_extent","vnf_isect"]) ? "vnf"
3170             : in_list(geom[0],["rgn_extent","rgn_isect"]) ? "path"
3171             : in_list(geom[0],["extrusion_extent","extrusion_isect"]) ? "xpath"
3172             : "other"
3173    )
3174    assert(type!="other", "Invalid cp value")
3175    cp=="centroid" ? (
3176       type=="vnf" && (len(geom[1][0])==0 || len(geom[1][1])==0) ? [0,0,0] :
3177       [each centroid(geom[1]), if (type=="xpath") 0]
3178    )
3179  : let(points = type=="vnf"?geom[1][0]:flatten(force_region(geom[1])))
3180    cp=="mean" ? [each mean(points), if (type=="xpath") 0]
3181  : cp=="box" ?[each  mean(pointlist_bounds(points)), if (type=="xpath") 0]
3182  : assert(false,"Invalid cp specification");
3183
3184
3185function _get_cp(geom) =
3186    let(cp=select(geom,-3))
3187    is_vector(cp) ? cp
3188  : let(
3189        is_vnf = in_list(geom[0],["vnf_extent","vnf_isect"])
3190    )
3191    cp == "centroid" ? (
3192       is_vnf && len(geom[1][1])==0
3193          ? [0,0,0]
3194          : centroid(geom[1])
3195    )
3196  : let(points = is_vnf?geom[1][0]:flatten(force_region(geom[1])))
3197    cp=="mean" ? mean(points)
3198  : cp=="box" ? mean(pointlist_bounds(points))
3199  : assert(false,"Invalid cp specification");
3200
3201
3202
3203function _force_anchor_2d(anchor) =
3204  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.")
3205  anchor.y==0 ? [anchor.x,anchor.z] : point2d(anchor);
3206
3207
3208/// Internal Function: _find_anchor()
3209/// Usage:
3210///   anchorinfo = _find_anchor(anchor, geom);
3211/// Topics: Attachments
3212/// See Also: reorient(), attachable()
3213/// Description:
3214///   Calculates the anchor data for the given `anchor` vector or name, in the given attachment
3215///   geometry.  Returns `[ANCHOR, POS, VEC, ANG]` where `ANCHOR` is the requested anchorname
3216///   or vector, `POS` is the anchor position, `VEC` is the direction vector of the anchor, and
3217///   `ANG` is the angle to align with around the rotation axis of th anchor direction vector.
3218/// Arguments:
3219///   anchor = Vector or named anchor string.
3220///   geom = The geometry description of the shape.
3221function _find_anchor(anchor, geom) =
3222    is_string(anchor)? (
3223          anchor=="origin"? [anchor, CENTER, UP, 0]
3224        : let(
3225              anchors = last(geom),
3226              found = search([anchor], anchors, num_returns_per_match=1)[0]
3227          )
3228          assert(found!=[], str("Unknown anchor: ",anchor))
3229          anchors[found]
3230    ) :
3231    let(
3232        cp = _get_cp(geom),
3233        offset_raw = select(geom,-2),
3234        offset = [for (i=[0:2]) anchor[i]==0? 0 : offset_raw[i]],  // prevents bad centering.
3235        type = geom[0]
3236    )
3237    assert(is_vector(anchor),str("Invalid anchor: anchor=",anchor))
3238    let(
3239        anchor = point3d(anchor),
3240        oang = (
3241            approx(point2d(anchor), [0,0])? 0 :
3242            atan2(anchor.y, anchor.x)+90
3243        )
3244    )
3245    type == "prismoid"? ( //size, size2, shift, axis
3246        let(all_comps_good = [for (c=anchor) if (c!=sign(c)) 1]==[])
3247        assert(all_comps_good, "All components of an anchor for a cuboid/prismoid must be -1, 0, or 1")
3248        let(
3249            size=geom[1], size2=geom[2],
3250            shift=point2d(geom[3]), axis=point3d(geom[4]),
3251            override = geom[5](anchor)
3252        )
3253        let(
3254            size = [for (c = size) max(0,c)],
3255            size2 = [for (c = size2) max(0,c)],
3256            anch = rot(from=axis, to=UP, p=anchor),
3257            offset = rot(from=axis, to=UP, p=offset),
3258            h = size.z,
3259            u = (anch.z + 1) / 2,  // u is one of 0, 0.5, or 1
3260            axy = point2d(anch),
3261            bot = point3d(v_mul(point2d(size )/2, axy), -h/2),
3262            top = point3d(v_mul(point2d(size2)/2, axy) + shift, h/2),
3263            pos = point3d(cp) + lerp(bot,top,u) + offset,
3264            vecs = anchor==CENTER? [UP]
3265              : [
3266                    if (anch.x!=0) unit(rot(from=UP, to=[(top-bot).x,0,max(0.01,h)], p=[axy.x,0,0]), UP),
3267                    if (anch.y!=0) unit(rot(from=UP, to=[0,(top-bot).y,max(0.01,h)], p=[0,axy.y,0]), UP),
3268                    if (anch.z!=0) unit([0,0,anch.z],UP)
3269                ],
3270            vec2 = anchor==CENTER? UP
3271              : len(vecs)==1? unit(vecs[0],UP)
3272              : len(vecs)==2? vector_bisect(vecs[0],vecs[1])
3273              : let(
3274                    v1 = vector_bisect(vecs[0],vecs[2]),
3275                    v2 = vector_bisect(vecs[1],vecs[2]),
3276                    p1 = plane_from_normal(yrot(90,p=v1)),
3277                    p2 = plane_from_normal(xrot(-90,p=v2)),
3278                    line = plane_intersection(p1,p2),
3279                    v3 = unit(line[1]-line[0],UP) * anch.z
3280                )
3281                unit(v3,UP),
3282            vec = default(override[1],rot(from=UP, to=axis, p=vec2)),
3283            pos2 = default(override[0],rot(from=UP, to=axis, p=pos))
3284        ) [anchor, pos2, vec, default(override[2],oang)]
3285    ) : type == "conoid"? ( //r1, r2, l, shift
3286        assert(anchor.z == sign(anchor.z), "The Z component of an anchor for a cylinder/cone must be -1, 0, or 1")
3287        let(
3288            rr1=geom[1], rr2=geom[2], l=geom[3],
3289            shift=point2d(geom[4]), axis=point3d(geom[5]),
3290            r1 = is_num(rr1)? [rr1,rr1] : point2d(rr1),
3291            r2 = is_num(rr2)? [rr2,rr2] : point2d(rr2),
3292            anch = rot(from=axis, to=UP, p=anchor),
3293            offset = rot(from=axis, to=UP, p=offset),
3294            u = (anch.z+1)/2,
3295            axy = unit(point2d(anch),[0,0]),
3296            bot = point3d(v_mul(r1,axy), -l/2),
3297            top = point3d(v_mul(r2,axy)+shift, l/2),
3298            pos = point3d(cp) + lerp(bot,top,u) + offset,
3299            sidevec = rot(from=UP, to=top==bot?UP:top-bot, p=point3d(axy)),
3300            vvec = anch==CENTER? UP : unit([0,0,anch.z],UP),
3301            vec = anch==CENTER? CENTER :
3302                approx(axy,[0,0])? unit(anch,UP) :
3303                approx(anch.z,0)? sidevec :
3304                unit((sidevec+vvec)/2,UP),
3305            pos2 = rot(from=UP, to=axis, p=pos),
3306            vec2 = anch==CENTER? UP : rot(from=UP, to=axis, p=vec)
3307        ) [anchor, pos2, vec2, oang]
3308    ) : type == "point"? (
3309        let(
3310            anchor = unit(point3d(anchor),CENTER),
3311            pos = point3d(cp) + point3d(offset),
3312            vec = unit(anchor,UP)
3313        ) [anchor, pos, vec, oang]
3314    ) : type == "spheroid"? ( //r
3315        let(
3316            rr = geom[1],
3317            r = is_num(rr)? [rr,rr,rr] : point3d(rr),
3318            anchor = unit(point3d(anchor),CENTER),
3319            pos = point3d(cp) + v_mul(r,anchor) + point3d(offset),
3320            vec = unit(v_mul(r,anchor),UP)
3321        ) [anchor, pos, vec, oang]
3322    ) : type == "vnf_isect"? ( //vnf
3323        let( vnf=geom[1] )
3324        approx(anchor,CTR)? [anchor, [0,0,0], UP, 0] :
3325        vnf==EMPTY_VNF? [anchor, [0,0,0], unit(anchor), 0] :
3326        let(
3327            eps = 1/2048,
3328            points = vnf[0],
3329            faces = vnf[1],
3330            rpts = apply(rot(from=anchor, to=RIGHT) * move(-cp), points),
3331            hits = [
3332                for (face = faces)
3333                    let(
3334                        verts = select(rpts, face),
3335                        ys = column(verts,1),
3336                        zs = column(verts,2)
3337                    )
3338                    if (max(ys) >= -eps && max(zs) >= -eps &&
3339                        min(ys) <=  eps &&  min(zs) <=  eps)
3340                        let(
3341                            poly = select(points, face),
3342                            isect = polygon_line_intersection(poly, [cp,cp+anchor], eps=eps),
3343                            ptlist = is_undef(isect) ? [] :
3344                                     is_vector(isect) ? [isect]
3345                                                      : flatten(isect),   // parallel to a face
3346                            n = len(ptlist)>0 ? polygon_normal(poly) : undef
3347                        )
3348                        for(pt=ptlist) [anchor * (pt-cp), n, pt]
3349            ]
3350        )
3351        assert(len(hits)>0, "Anchor vector does not intersect with the shape.  Attachment failed.")
3352        let(
3353            furthest = max_index(column(hits,0)),
3354            dist = hits[furthest][0],
3355            pos = hits[furthest][2],
3356            hitnorms = [for (hit = hits) if (approx(hit[0],dist,eps=eps)) hit[1]],
3357            unorms = [
3358                      for (i = idx(hitnorms))
3359                          let(
3360                              thisnorm = hitnorms[i],
3361                              isdup = [
3362                                       for (j = [i+1:1:len(hitnorms)-1])
3363                                           if (approx(thisnorm, hitnorms[j])) 1
3364                                      ] != []
3365                          )
3366                          if (!isdup) thisnorm
3367                     ],
3368            n = unit(sum(unorms)),
3369            oang = approx(point2d(n), [0,0])? 0 : atan2(n.y, n.x) + 90
3370        )
3371        [anchor, pos, n, oang]
3372    ) : type == "vnf_extent"? ( //vnf
3373        let( vnf=geom[1] )
3374        approx(anchor,CTR)? [anchor, [0,0,0], UP, 0] :
3375        vnf==EMPTY_VNF? [anchor, [0,0,0], unit(anchor,UP), 0] :
3376        let(
3377            rpts = apply(rot(from=anchor, to=RIGHT) * move(point3d(-cp)), vnf[0]),
3378            maxx = max(column(rpts,0)),
3379            idxs = [for (i = idx(rpts)) if (approx(rpts[i].x, maxx)) i],
3380            avep = sum(select(rpts,idxs))/len(idxs),
3381            mpt = approx(point2d(anchor),[0,0])? [maxx,0,0] : avep,
3382            pos = point3d(cp) + rot(from=RIGHT, to=anchor, p=mpt)
3383        ) [anchor, pos, anchor, oang]
3384    ) : type == "trapezoid"? ( //size, size2, shift, override
3385        let(all_comps_good = [for (c=anchor) if (c!=sign(c)) 1]==[])
3386        assert(all_comps_good, "All components of an anchor for a rectangle/trapezoid must be -1, 0, or 1")
3387        let(
3388            anchor=_force_anchor_2d(anchor),
3389            size=geom[1], size2=geom[2], shift=geom[3],
3390            u = (anchor.y+1)/2,  // 0<=u<=1
3391            frpt = [size.x/2*anchor.x, -size.y/2],
3392            bkpt = [size2/2*anchor.x+shift, size.y/2],
3393            override = geom[4](anchor),
3394            pos = override[0] != undef? override[0] :
3395                point2d(cp) + lerp(frpt, bkpt, u) + point2d(offset),
3396            svec = approx(bkpt,frpt)? [anchor.x,0,0] :
3397                point3d(line_normal(bkpt,frpt)*anchor.x),
3398            vec = is_def(override[1]) ? override[1]
3399                : anchor.y == 0? ( anchor.x == 0? BACK : svec )
3400                : anchor.x == 0? [0,anchor.y,0]
3401                : unit((svec + [0,anchor.y,0]) / 2, [0,anchor.y,0])
3402        ) [anchor, pos, vec, 0]
3403    ) : type == "ellipse"? ( //r
3404        let(
3405            anchor = unit(_force_anchor_2d(anchor),[0,0]),
3406            r = force_list(geom[1],2),
3407            pos = approx(anchor.x,0)
3408                ? [0,sign(anchor.y)*r.y]
3409                : let(
3410                       m = anchor.y/anchor.x,
3411                       px = approx(min(r),0)? 0 :
3412                           sign(anchor.x) * sqrt(1/(1/sqr(r.x) + m*m/sqr(r.y)))
3413                  )
3414                  [px,m*px],
3415            vec = approx(min(r),0)? (approx(norm(anchor),0)? BACK : anchor) :
3416                unit([r.y/r.x*pos.x, r.x/r.y*pos.y],BACK)
3417        ) [anchor, point2d(cp+offset)+pos, vec, 0]
3418    ) : type == "rgn_isect"? ( //region
3419        let(
3420            anchor = _force_anchor_2d(anchor),
3421            rgn = force_region(move(-point2d(cp), p=geom[1]))
3422        )
3423        approx(anchor,[0,0])? [anchor, [0,0,0], BACK, 0] :
3424        let(
3425            isects = [
3426                for (path=rgn, t=triplet(path,true)) let(
3427                    seg1 = [t[0],t[1]],
3428                    seg2 = [t[1],t[2]],
3429                    isect = line_intersection([[0,0],anchor], seg1, RAY, SEGMENT),
3430                    n = is_undef(isect)? [0,1] :
3431                        !approx(isect, t[1])? line_normal(seg1) :
3432                        unit((line_normal(seg1)+line_normal(seg2))/2,[0,1]),
3433                    n2 = vector_angle(anchor,n)>90? -n : n
3434                )
3435                if(!is_undef(isect) && !approx(isect,t[0])) [norm(isect), isect, n2]
3436            ]
3437        )
3438        assert(len(isects)>0, "Anchor vector does not intersect with the shape.  Attachment failed.")
3439        let(
3440            maxidx = max_index(column(isects,0)),
3441            isect = isects[maxidx],
3442            pos = point2d(cp) + isect[1],
3443            vec = unit(isect[2],[0,1])
3444        ) [anchor, pos, vec, 0]
3445    ) : type == "rgn_extent"? ( //region
3446        let( anchor = _force_anchor_2d(anchor) )
3447        approx(anchor,[0,0])? [anchor, [0,0,0], BACK, 0] :
3448        let(
3449            rgn = force_region(geom[1]),
3450            rpts = rot(from=anchor, to=RIGHT, p=flatten(rgn)),
3451            maxx = max(column(rpts,0)),
3452            ys = [for (pt=rpts) if (approx(pt.x, maxx)) pt.y],
3453            midy = (min(ys)+max(ys))/2,
3454            pos = rot(from=RIGHT, to=anchor, p=[maxx,midy])
3455        ) [anchor, pos, unit(anchor,BACK), 0]
3456    ) : type=="extrusion_extent" || type=="extrusion_isect" ? (  // extruded region
3457        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.")
3458        let(
3459            anchor_xy = point2d(anchor),
3460            rgn = geom[1],
3461            L = geom[2],
3462            twist = geom[3],
3463            scale = geom[4],
3464            shift = geom[5],
3465            u = (anchor.z + 1) / 2,
3466            shmat = move(lerp([0,0], shift, u)),
3467            scmat = scale(lerp([1,1], scale, u)),
3468            twmat = zrot(lerp(0, -twist, u)),
3469            mat = shmat * scmat * twmat
3470        )
3471        approx(anchor_xy,[0,0]) ? [anchor, apply(mat, point3d(cp,anchor.z*L/2)), unit(anchor, UP), oang] :
3472        let(
3473            newrgn = apply(mat, rgn),
3474            newgeom = attach_geom(two_d=true, region=newrgn, extent=type=="extrusion_extent", cp=cp),
3475            topmat = anchor.z!=0 ? []
3476                   : move(shift)*scale(scale)*zrot(-twist),
3477            topgeom = anchor.z!=0? []
3478                    : attach_geom(two_d=true, region=apply(topmat,rgn), extent=type=="extrusion_extent", cp=cp),
3479            top2d =  anchor.z!=0? []
3480                  : _find_anchor(anchor_xy, topgeom),
3481            result2d = _find_anchor(anchor_xy, newgeom),
3482            pos = point3d(result2d[1], anchor.z*L/2),
3483            vec = anchor.z==0? rot(from=UP,to=point3d(top2d[1],L/2)-point3d(result2d[1]),p=point3d(result2d[2]))
3484                : unit(point3d(result2d[2], anchor.z),UP),
3485            oang = atan2(vec.y,vec.x) + 90
3486        )
3487        [anchor, pos, vec, oang]
3488    ) :
3489    assert(false, "Unknown attachment geometry type.");
3490
3491
3492/// Internal Function: _is_shown()
3493/// Usage:
3494///   bool = _is_shown();
3495/// Topics: Attachments
3496/// See Also: reorient(), attachable()
3497/// Description:
3498///   Returns true if objects should currently be shown based on the tag settings.
3499function _is_shown() =
3500    assert(is_list($tags_shown) || $tags_shown=="ALL")
3501    assert(is_list($tags_hidden))
3502    let(
3503        dummy=is_undef($tags) ? 0 : echo("Use tag() instead of $tags for specifying an object's tag."),
3504        $tag = default($tag,$tags)
3505    )
3506    assert(is_string($tag), str("Tag value (",$tag,") is not a string"))
3507    assert(undef==str_find($tag," "),str("Tag string \"",$tag,"\" contains a space, which is not allowed"))
3508    let(
3509        shown  = $tags_shown=="ALL" || in_list($tag,$tags_shown),
3510        hidden = in_list($tag, $tags_hidden)
3511    )
3512    shown && !hidden;
3513
3514
3515// Section: Visualizing Anchors
3516
3517/// Internal Function: _standard_anchors()
3518/// Usage:
3519///   anchs = _standard_anchors([two_d]);
3520/// Description:
3521///   Return the vectors for all standard anchors.
3522/// Arguments:
3523///   two_d = If true, returns only the anchors where the Z component is 0.  Default: false
3524function _standard_anchors(two_d=false) = [
3525    for (
3526        zv = [
3527            if (!two_d) TOP,
3528            CENTER,
3529            if (!two_d) BOTTOM
3530        ],
3531        yv = [FRONT, CENTER, BACK],
3532        xv = [LEFT, CENTER, RIGHT]
3533    ) xv+yv+zv
3534];
3535
3536
3537
3538// Module: show_anchors()
3539// Synopsis: Shows anchors for the parent object.
3540// SynTags: Geom
3541// Topics: Attachments
3542// See Also: expose_anchors(), anchor_arrow(), anchor_arrow2d(), frame_ref()
3543// Usage:
3544//   PARENT() show_anchors([s], [std=], [custom=]);
3545// Description:
3546//   Show all standard anchors for the parent object.
3547// Arguments:
3548//   s = Length of anchor arrows.
3549//   ---
3550//   std = If true show standard anchors.  Default: true
3551//   custom = If true show named anchors.  Default: true
3552// Example(FlatSpin,VPD=333):
3553//   cube(50, center=true) show_anchors();
3554module show_anchors(s=10, std=true, custom=true) {
3555    check = assert($parent_geom != undef);
3556    two_d = _attach_geom_2d($parent_geom);
3557    if (std) {
3558        for (anchor=_standard_anchors(two_d=two_d)) {
3559            if(two_d) {
3560                attach(anchor) anchor_arrow2d(s);
3561            } else {
3562                attach(anchor) anchor_arrow(s);
3563            }
3564        }
3565    }
3566    if (custom) {
3567        for (anchor=last($parent_geom)) {
3568            attach(anchor[0]) {
3569                if(two_d) {
3570                    anchor_arrow2d(s, color="cyan");
3571                } else {
3572                    anchor_arrow(s, color="cyan");
3573                }
3574                color("black")
3575                tag("anchor-arrow") {
3576                    xrot(two_d? 0 : 90) {
3577                        back(s/3) {
3578                            yrot_copies(n=2)
3579                            up(two_d? 0.51 : s/30) {
3580                                linear_extrude(height=0.01, convexity=12, center=true) {
3581                                    text(text=anchor[0], size=s/4, halign="center", valign="center", font="Helvetica", $fn=36);
3582                                }
3583                            }
3584                        }
3585                    }
3586                }
3587                color([1, 1, 1, 1])
3588                tag("anchor-arrow") {
3589                    xrot(two_d? 0 : 90) {
3590                        back(s/3) {
3591                             cube([s/4.5*len(anchor[0]), s/3, 0.01], center=true);
3592                        }
3593                   }
3594                }
3595            }
3596        }
3597    }
3598    children();
3599}
3600
3601
3602// Module: anchor_arrow()
3603// Synopsis: Shows a 3d anchor orientation arrow.
3604// SynTags: Geom
3605// Topics: Attachments
3606// See Also: anchor_arrow2d(), show_anchors(), expose_anchors(), frame_ref(), generic_airplane()
3607// Usage:
3608//   anchor_arrow([s], [color], [flag], [anchor=], [orient=], [spin=]) [ATTACHMENTS];
3609// Description:
3610//   Show an anchor orientation arrow.  By default, tagged with the name "anchor-arrow".
3611// Arguments:
3612//   s = Length of the arrows.  Default: `10`
3613//   color = Color of the arrow.  Default: `[0.333, 0.333, 1]`
3614//   flag = If true, draw the orientation flag on the arrowhead.  Default: true
3615//   ---
3616//   anchor = Translate so anchor point is at origin (0,0,0).  See [anchor](attachments.scad#subsection-anchor).  Default: `CENTER`
3617//   spin = Rotate this many degrees around the Z axis after anchor.  See [spin](attachments.scad#subsection-spin).  Default: `0`
3618//   orient = Vector to rotate top towards, after spin.  See [orient](attachments.scad#subsection-orient).  Default: `UP`
3619// Example:
3620//   anchor_arrow(s=20);
3621module anchor_arrow(s=10, color=[0.333,0.333,1], flag=true, $tag="anchor-arrow", $fn=12, anchor=BOT, spin=0, orient=UP) {
3622    attachable(anchor,spin,orient, r=s/6, l=s) {
3623        down(s/2)
3624        recolor("gray") spheroid(d=s/6) {
3625            attach(CENTER,BOT) recolor(color) cyl(h=s*2/3, d=s/15) {
3626                attach(TOP,BOT) cyl(h=s/3, d1=s/5, d2=0) {
3627                    if(flag) {
3628                        position(BOT)
3629                            recolor([1,0.5,0.5])
3630                                cuboid([s/100, s/6, s/4], anchor=FRONT+BOT);
3631                    }
3632                }
3633            }
3634        }
3635        children();
3636    }
3637}
3638
3639
3640
3641// Module: anchor_arrow2d()
3642// Synopsis: Shows a 2d anchor orientation arrow.
3643// SynTags: Geom
3644// Topics: Attachments
3645// See Also: anchor_arrow(), show_anchors(), expose_anchors(), frame_ref()
3646// Usage:
3647//   anchor_arrow2d([s], [color]);
3648// Description:
3649//   Show an anchor orientation arrow.
3650// Arguments:
3651//   s = Length of the arrows.
3652//   color = Color of the arrow.
3653// Example:
3654//   anchor_arrow2d(s=20);
3655module anchor_arrow2d(s=15, color=[0.333,0.333,1], $tag="anchor-arrow") {
3656    color(color) stroke([[0,0],[0,s]], width=s/10, endcap1="butt", endcap2="arrow2");
3657}
3658
3659
3660
3661// Module: expose_anchors()
3662// Synopsis: Used to show a transparent object with solid color anchor arrows.
3663// Topics: Attachments
3664// See Also: anchor_arrow2d(), show_anchors(), show_anchors(), frame_ref()
3665// Usage:
3666//   expose_anchors(opacity) {child1() show_anchors(); child2() show_anchors(); ...}
3667// Description:
3668//   Used in combination with show_anchors() to display an object in transparent gray with its anchors in solid color.
3669//   Children will appear transparent and any anchor arrows drawn with will appear in solid color.
3670// Arguments:
3671//   opacity = The opacity of the children.  0.0 is invisible, 1.0 is opaque.  Default: 0.2
3672// Example(FlatSpin,VPD=333):
3673//   expose_anchors() cube(50, center=true) show_anchors();
3674module expose_anchors(opacity=0.2) {
3675    show_only("anchor-arrow")
3676        children();
3677    hide("anchor-arrow")
3678        color(is_undef($color) || $color=="default" ? [0,0,0] :
3679              is_string($color) ? $color
3680                                : point3d($color),
3681              opacity)
3682            children();
3683}
3684
3685
3686
3687// Module: show_transform_list()
3688// Synopsis: Shows a list of transforms and how they connect.
3689// SynTags: Geom
3690// Topics: Attachments
3691// See Also: generic_airplane(), anchor_arrow(), show_anchors(), expose_anchors(), frame_ref()
3692// Usage:
3693//   show_transform_list(tlist, [s]);
3694//   show_transform_list(tlist) {CHILDREN};
3695// Description:
3696//   Given a list of transformation matrices, shows the position and orientation of each one.
3697//   A line is drawn from each transform position to the next one, and an orientation indicator is
3698//   shown at each position.  If a child is passed, that child will be used as the orientation indicator.
3699//   By default, a {{generic_airplane()}} is used as the orientation indicator.
3700// Arguments:
3701//   s = Length of the {{generic_airplane()}}.  Default: 5
3702// Example:
3703//   tlist = [
3704//       zrot(90),
3705//       zrot(90) * fwd(30) * zrot(30),
3706//       zrot(90) * fwd(30) * zrot(30) *
3707//           fwd(35) * xrot(-30),
3708//       zrot(90) * fwd(30) * zrot(30) *
3709//           fwd(35) * xrot(-30) * fwd(40) * yrot(15),
3710//   ];
3711//   show_transform_list(tlist, s=20);
3712// Example:
3713//   tlist = [
3714//       zrot(90),
3715//       zrot(90) * fwd(30) * zrot(30),
3716//       zrot(90) * fwd(30) * zrot(30) *
3717//           fwd(35) * xrot(-30),
3718//       zrot(90) * fwd(30) * zrot(30) *
3719//           fwd(35) * xrot(-30) * fwd(40) * yrot(15),
3720//   ];
3721//   show_transform_list(tlist) frame_ref();
3722module show_transform_list(tlist, s=5) {
3723    path = [for (m = tlist) apply(m, [0,0,0])];
3724    stroke(path, width=s*0.03);
3725    for (m = tlist) {
3726        multmatrix(m) {
3727            if ($children>0) children();
3728            else generic_airplane(s=s);
3729        }
3730    }
3731}
3732
3733
3734// Module: generic_airplane()
3735// Synopsis: Shows a generic airplane shape, useful for viewing orientations.
3736// SynTags: Geom
3737// Topics: Attachments
3738// See Also: anchor_arrow(), show_anchors(), expose_anchors(), frame_ref()
3739// Usage:
3740//   generic_airplane([s]);
3741// Description:
3742//   Creates a generic airplane shape.  This can be useful for viewing the orientation of 3D transforms.
3743// Arguments:
3744//   s = Length of the airplane.  Default: 5
3745// Example:
3746//   generic_airplane(s=20);
3747module generic_airplane(s=5) {
3748    $fn = max(segs(0.05*s), 12);
3749    color("#ddd")
3750    fwd(s*0.05)
3751    ycyl(l=0.7*s, d=0.1*s) {
3752        attach(FWD) top_half(s=s) zscale(2) sphere(d=0.1*s);
3753        attach(BACK,FWD) ycyl(l=0.2*s, d1=0.1*s, d2=0.05*s) {
3754            yrot_copies([-90,0,90])
3755                prismoid(s*[0.01,0.2], s*[0.01,0.05],
3756                    h=0.2*s, shift=s*[0,0.15], anchor=BOT);
3757        }
3758        yrot_copies([-90,90])
3759            prismoid(s*[0.01,0.2], s*[0.01,0.05],
3760                h=0.5*s, shift=s*[0,0.15], anchor=BOT);
3761    }
3762    color("#777") zcopies(0.1*s) sphere(d=0.02*s);
3763    back(0.09*s) {
3764        color("#f00") right(0.46*s) sphere(d=0.04*s);
3765        color("#0f0") left(0.46*s) sphere(d=0.04*s);
3766    }
3767}
3768
3769
3770
3771// Module: frame_ref()
3772// Synopsis: Shows axis orientation arrows.
3773// SynTags: Geom
3774// Topics: Attachments
3775// See Also: anchor_arrow(), anchor_arrow2d(), show_anchors(), expose_anchors()
3776// Usage:
3777//   frame_ref(s, opacity);
3778// Description:
3779//   Displays X,Y,Z axis arrows in red, green, and blue respectively.
3780// Arguments:
3781//   s = Length of the arrows.
3782//   opacity = The opacity of the arrows.  0.0 is invisible, 1.0 is opaque.  Default: 1.0
3783// Examples:
3784//   frame_ref(25);
3785//   frame_ref(30, opacity=0.5);
3786module frame_ref(s=15, opacity=1) {
3787    cube(0.01, center=true) {
3788        attach([1,0,0]) anchor_arrow(s=s, flag=false, color=[1.0, 0.3, 0.3, opacity]);
3789        attach([0,1,0]) anchor_arrow(s=s, flag=false, color=[0.3, 1.0, 0.3, opacity]);
3790        attach([0,0,1]) anchor_arrow(s=s, flag=false, color=[0.3, 0.3, 1.0, opacity]);
3791        children();
3792    }
3793}
3794
3795
3796////////////////////////////////////////////////////////////////////////////////////////////////////
3797////////////////////////////////////////////////////////////////////////////////////////////////////
3798////////////////////////////////////////////////////////////////////////////////////////////////////
3799////////////////////////////////////////////////////////////////////////////////////////////////////
3800///
3801/// Code after this is internal code for managing edge and corner sets and for displaying
3802/// edge and corners in the docs
3803///
3804
3805module _edges_text3d(txt,size=3) {
3806    if (is_list(txt)) {
3807        for (i=idx(txt)) {
3808            down((i-len(txt)/2+0.5)*size*1.5) {
3809                _edges_text3d(txt[i], size=size);
3810            }
3811        }
3812    } else {
3813        xrot(90) color("#000")
3814        linear_extrude(height=0.1) {
3815            text(text=txt, size=size, halign="center", valign="center");
3816        }
3817    }
3818}
3819
3820
3821function _edges_vec_txt(x) = is_string(x)? str("\"", x, "\"") :
3822    assert(is_string(x) || is_vector(x,3), str(x))
3823    let(
3824        lst = concat(
3825            x.z>0? ["TOP"]   : x.z<0? ["BOT"]  : [],
3826            x.y>0? ["BACK"]  : x.y<0? ["FWD"]  : [],
3827            x.x>0? ["RIGHT"] : x.x<0? ["LEFT"] : []
3828        ),
3829        out = [
3830           for (i = idx(lst))
3831           i>0? str("+",lst[i]) : lst[i]
3832        ]
3833    ) out;
3834
3835
3836function _edges_text(edges) =
3837    is_string(edges) ? [str("\"",edges,"\"")] :
3838    edges==EDGES_NONE ? ["EDGES_NONE"] :
3839    edges==EDGES_ALL ? ["EDGES_ALL"] :
3840    _is_edge_array(edges) ? [""] :
3841    is_vector(edges,3) ? _edges_vec_txt(edges) :
3842    is_list(edges) ? let(
3843        lst = [for (x=edges) each _edges_text(x)],
3844        out = [
3845            for (i=idx(lst))
3846            str(
3847                (i==0? "[" : ""),
3848                lst[i],
3849                (i<len(lst)-1? "," : ""),
3850                (i==len(lst)-1? "]" : "")
3851            )
3852        ]
3853    ) out :
3854    [""];
3855
3856
3857
3858/// Internal Constant: EDGES_NONE
3859/// Topics: Edges
3860/// See Also: EDGES_ALL, edges()
3861/// Description:
3862///   The set of no edges.
3863/// Figure(3D):
3864///   _show_edges(edges="NONE");
3865EDGES_NONE = [[0,0,0,0], [0,0,0,0], [0,0,0,0]];
3866
3867
3868/// Internal Constant: EDGES_ALL
3869/// Topics: Edges
3870/// See Also: EDGES_NONE, edges()
3871/// Description:
3872///   The set of all edges.
3873/// Figure(3D):
3874///   _show_edges(edges="ALL");
3875EDGES_ALL = [[1,1,1,1], [1,1,1,1], [1,1,1,1]];
3876
3877
3878/// Internal Constant: EDGES_OFFSETS
3879/// Topics: Edges
3880/// See Also: EDGES_NONE, EDGES_ALL, edges()
3881/// Description:
3882///   The vectors pointing to the center of each edge of a unit sized cube.
3883///   Each item in an edge array will have a corresponding vector in this array.
3884EDGE_OFFSETS = [
3885    [
3886        [ 0,-1,-1],
3887        [ 0, 1,-1],
3888        [ 0,-1, 1],
3889        [ 0, 1, 1]
3890    ], [
3891        [-1, 0,-1],
3892        [ 1, 0,-1],
3893        [-1, 0, 1],
3894        [ 1, 0, 1]
3895    ], [
3896        [-1,-1, 0],
3897        [ 1,-1, 0],
3898        [-1, 1, 0],
3899        [ 1, 1, 0]
3900    ]
3901];
3902
3903
3904
3905/// Internal Function: _is_edge_array()
3906/// Topics: Edges, Type Checking
3907/// Usage:
3908///   bool = _is_edge_array(x);
3909/// Description:
3910///   Returns true if the given value has the form of an edge array.
3911/// Arguments:
3912///   x = The item to check the type of.
3913/// See Also: edges(), EDGES_NONE, EDGES_ALL
3914function _is_edge_array(x) = is_list(x) && is_vector(x[0]) && len(x)==3 && len(x[0])==4;
3915
3916
3917function _edge_set(v) =
3918    _is_edge_array(v)? v : [
3919    for (ax=[0:2]) [
3920        for (b=[-1,1], a=[-1,1]) let(
3921            v2=[[0,a,b],[a,0,b],[a,b,0]][ax]
3922        ) (
3923            is_string(v)? (
3924                v=="X"? (ax==0) :   // Return all X axis aligned edges.
3925                v=="Y"? (ax==1) :   // Return all Y axis aligned edges.
3926                v=="Z"? (ax==2) :   // Return all Z axis aligned edges.
3927                v=="ALL"? true :    // Return all edges.
3928                v=="NONE"? false :  // Return no edges.
3929                let(valid_values = ["X", "Y", "Z", "ALL", "NONE"])
3930                assert(
3931                    in_list(v, valid_values),
3932                    str(v, " must be a vector, edge array, or one of ", valid_values)
3933                ) v
3934            ) :
3935            let(nonz = sum(v_abs(v)))
3936            nonz==2? (v==v2) :  // Edge: return matching edge.
3937            let(
3938                matches = num_true([
3939                    for (i=[0:2]) v[i] && (v[i]==v2[i])
3940                ])
3941            )
3942            nonz==1? (matches==1) :  // Face: return surrounding edges.
3943            (matches==2)             // Corner: return touching edges.
3944        )? 1 : 0
3945    ]
3946];
3947
3948
3949/// Internal Function: _normalize_edges()
3950/// Topics: Edges
3951/// Usage:
3952///   edges = _normalize_edges(v);
3953/// Description:
3954///   Normalizes all values in an edge array to be `1`, if it was originally greater than `0`,
3955///   or `0`, if it was originally less than or equal to `0`.
3956/// See Also:  edges(), EDGES_NONE, EDGES_ALL
3957function _normalize_edges(v) = [for (ax=v) [for (edge=ax) edge>0? 1 : 0]];
3958
3959
3960
3961
3962/// Internal Function: _edges()
3963/// Topics: Edges
3964/// Usage:
3965///   edgs = _edges(v);
3966///   edgs = _edges(v, except);
3967///
3968/// Description:
3969///   Takes a list of edge set descriptors, and returns a normalized edges array
3970///   that represents all those given edges.
3971/// Arguments:
3972///   v = The edge set to include.
3973///   except = The edge set to specifically exclude, even if they are in `v`.
3974///
3975/// See Also:  EDGES_NONE, EDGES_ALL
3976///
3977function _edges(v, except=[]) =
3978    v==[] ? EDGES_NONE :
3979    (is_string(v) || is_vector(v) || _is_edge_array(v))? _edges([v], except=except) :
3980    (is_string(except) || is_vector(except) || _is_edge_array(except))? _edges(v, except=[except]) :
3981    except==[]? _normalize_edges(sum([for (x=v) _edge_set(x)])) :
3982    _normalize_edges(
3983        _normalize_edges(sum([for (x=v) _edge_set(x)])) -
3984        sum([for (x=except) _edge_set(x)])
3985    );
3986
3987
3988/// Internal Module: _show_edges()
3989/// Topics: Edges, Debugging
3990/// Usage:
3991///   _show_edges(edges, [size=], [text=], [txtsize=]);
3992/// Description:
3993///   Draws a semi-transparent cube with the given edges highlighted in red.
3994/// Arguments:
3995///   edges = The edges to highlight.
3996///   size = The scalar size of the cube.
3997///   text = The text to show on the front of the cube.
3998///   txtsize = The size of the text.
3999/// See Also: _edges(), EDGES_NONE, EDGES_ALL
4000/// Example:
4001///   _show_edges(size=30, edges=["X","Y"]);
4002module _show_edges(edges="ALL", size=20, text, txtsize=3,toplabel) {
4003    edge_set = _edges(edges);
4004    text = !is_undef(text) ? text : _edges_text(edges);
4005    color("red") {
4006        for (axis=[0:2], i=[0:3]) {
4007            if (edge_set[axis][i] > 0) {
4008                translate(EDGE_OFFSETS[axis][i]*size/2) {
4009                    if (axis==0) xcyl(h=size, d=2);
4010                    if (axis==1) ycyl(h=size, d=2);
4011                    if (axis==2) zcyl(h=size, d=2);
4012                }
4013            }
4014        }
4015    }
4016    fwd(size/2) _edges_text3d(text, size=txtsize);
4017    color("yellow",0.7) cuboid(size=size);
4018    vpr = [55,0,25];
4019    color("black")
4020    if (is_def(toplabel))
4021      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);
4022}
4023
4024
4025
4026
4027/// Internal Constant: CORNERS_NONE
4028/// Topics: Corners
4029/// Description:
4030///   The set of no corners.
4031/// Figure(3D):
4032///   _show_corners(corners="NONE");
4033/// See Also: CORNERS_ALL, corners()
4034CORNERS_NONE = [0,0,0,0,0,0,0,0];  // No corners.
4035
4036
4037/// Internal Constant: CORNERS_ALL
4038/// Topics: Corners
4039/// Description:
4040///   The set of all corners.
4041/// Figure(3D):
4042///   _show_corners(corners="ALL");
4043/// See Also: CORNERS_NONE, _corners()
4044CORNERS_ALL = [1,1,1,1,1,1,1,1];
4045
4046
4047/// Internal Constant: CORNER_OFFSETS
4048/// Topics: Corners
4049/// Description:
4050///   The vectors pointing to each corner of a unit sized cube.
4051///   Each item in a corner array will have a corresponding vector in this array.
4052/// See Also: CORNERS_NONE, CORNERS_ALL, _corners()
4053CORNER_OFFSETS = [
4054    [-1,-1,-1], [ 1,-1,-1], [-1, 1,-1], [ 1, 1,-1],
4055    [-1,-1, 1], [ 1,-1, 1], [-1, 1, 1], [ 1, 1, 1]
4056];
4057
4058
4059
4060
4061/// Internal Function: _is_corner_array()
4062/// Topics: Corners, Type Checking
4063/// Usage:
4064///   bool = _is_corner_array(x)
4065/// Description:
4066///   Returns true if the given value has the form of a corner array.
4067/// See Also: CORNERS_NONE, CORNERS_ALL, _corners()
4068function _is_corner_array(x) = is_vector(x) && len(x)==8 && all([for (xx=x) xx==1||xx==0]);
4069
4070
4071/// Internal Function: _normalize_corners()
4072/// Topics: Corners
4073/// Usage:
4074///   corns = _normalize_corners(v);
4075/// Description:
4076///   Normalizes all values in a corner array to be `1`, if it was originally greater than `0`,
4077///   or `0`, if it was originally less than or equal to `0`.
4078/// See Also: CORNERS_NONE, CORNERS_ALL, _corners()
4079function _normalize_corners(v) = [for (x=v) x>0? 1 : 0];
4080
4081
4082function _corner_set(v) =
4083    _is_corner_array(v)? v : [
4084    for (i=[0:7]) let(
4085        v2 = CORNER_OFFSETS[i]
4086    ) (
4087        is_string(v)? (
4088            v=="ALL"? true :    // Return all corners.
4089            v=="NONE"? false :  // Return no corners.
4090            let(valid_values = ["ALL", "NONE"])
4091            assert(
4092                in_list(v, valid_values),
4093                str(v, " must be a vector, corner array, or one of ", valid_values)
4094            ) v
4095        ) :
4096        all([for (i=[0:2]) !v[i] || (v[i]==v2[i])])
4097    )? 1 : 0
4098];
4099
4100
4101/// Function: _corners()
4102/// Topics: Corners
4103/// Usage:
4104///   corns = _corners(v);
4105///   corns = _corners(v, except);
4106/// Description:
4107///   Takes a list of corner set descriptors, and returns a normalized corners array
4108///   that represents all those given corners.  If the `except` argument is given
4109///   a list of corner set descriptors, then all those corners will be removed
4110///   from the returned corners array.  If either argument only has a single corner
4111///   set descriptor, you do not have to pass it in a list.
4112function _corners(v, except=[]) =
4113    v==[] ? CORNERS_NONE :
4114    (is_string(v) || is_vector(v) || _is_corner_array(v))? _corners([v], except=except) :
4115    (is_string(except) || is_vector(except) || _is_corner_array(except))? _corners(v, except=[except]) :
4116    except==[]? _normalize_corners(sum([for (x=v) _corner_set(x)])) :
4117    let(
4118        a = _normalize_corners(sum([for (x=v) _corner_set(x)])),
4119        b = _normalize_corners(sum([for (x=except) _corner_set(x)]))
4120    ) _normalize_corners(a - b);
4121
4122
4123/// Internal Function: _corner_edges()
4124/// Topics: Corners
4125/// Description:
4126///   Returns [XCOUNT,YCOUNT,ZCOUNT] where each is the count of edges aligned with that
4127///   axis that are in the edge set and touch the given corner.
4128/// Arguments:
4129///   edges = Standard edges array.
4130///   v = Vector pointing to the corner to count edge intersections at.
4131/// See Also: CORNERS_NONE, CORNERS_ALL, _corners()
4132function _corner_edges(edges, v) =
4133    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]];
4134
4135
4136/// InternalFunction: _corner_edge_count()
4137/// Topics: Corners
4138/// Description:
4139///   Counts how many given edges intersect at a specific corner.
4140/// Arguments:
4141///   edges = Standard edges array.
4142///   v = Vector pointing to the corner to count edge intersections at.
4143/// See Also: CORNERS_NONE, CORNERS_ALL, _corners()
4144function _corner_edge_count(edges, v) =
4145    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];
4146
4147
4148function _corners_text(corners) =
4149    is_string(corners) ? [str("\"",corners,"\"")] :
4150    corners==CORNERS_NONE ? ["CORNERS_NONE"] :
4151    corners==CORNERS_ALL ? ["CORNERS_ALL"] :
4152    _is_corner_array(corners) ? [""] :
4153    is_vector(corners,3) ? _edges_vec_txt(corners) :
4154    is_list(corners) ? let(
4155        lst = [for (x=corners) each _corners_text(x)],
4156        out = [
4157            for (i=idx(lst))
4158            str(
4159                (i==0? "[" : ""),
4160                lst[i],
4161                (i<len(lst)-1? "," : ""),
4162                (i==len(lst)-1? "]" : "")
4163            )
4164        ]
4165    ) out :
4166    [""];
4167
4168
4169/// Internal Module: _show_corners()
4170/// Topics: Corners, Debugging
4171/// Usage:
4172///   _show_corners(corners, [size=], [text=], [txtsize=]);
4173/// Description:
4174///   Draws a semi-transparent cube with the given corners highlighted in red.
4175/// Arguments:
4176///   corners = The corners to highlight.
4177///   size = The scalar size of the cube.
4178///   text = If given, overrides the text to be shown on the front of the cube.
4179///   txtsize = The size of the text.
4180/// See Also: CORNERS_NONE, CORNERS_ALL, corners()
4181/// Example:
4182///   _show_corners(corners=FWD+RIGHT, size=30);
4183module _show_corners(corners="ALL", size=20, text, txtsize=3,toplabel) {
4184    corner_set = _corners(corners);
4185    text = !is_undef(text) ? text : _corners_text(corners);
4186    for (i=[0:7]) if (corner_set[i]>0)
4187        translate(CORNER_OFFSETS[i]*size/2)
4188            color("red") sphere(d=2, $fn=16);
4189    fwd(size/2) _edges_text3d(text, size=txtsize);
4190    color("yellow",0.7) cuboid(size=size);
4191    vpr = [55,0,25];
4192    color("black")
4193    if (is_def(toplabel))
4194      for(h=idx(toplabel)) up(21+6*h)rot(vpr) text3d(select(toplabel,-h-1),size=3.3,h=.1,orient=UP,anchor=FRONT);
4195}
4196
4197module _show_cube_faces(faces, size=20, toplabel,botlabel) {
4198   color("red")
4199     for(f=faces){
4200          move(f*size/2) rot(from=UP,to=f)
4201             cuboid([size,size,.1]);
4202     }
4203   vpr = [55,0,25];
4204   color("black"){
4205   if (is_def(toplabel))
4206     for(h=idx(toplabel)) up(21+6*h)rot(vpr) text3d(select(toplabel,-h-1),size=3.3,h=.1,orient=UP,anchor=FRONT);
4207   if (is_def(botlabel))
4208     for(h=idx(botlabel)) down(26+6*h)rot(vpr) text3d(botlabel[h],size=3.3,h=.1,orient=UP,anchor=FRONT);
4209   }
4210   color("yellow",0.7) cuboid(size=size);
4211}
4212
4213// vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap