// 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.
// Modules operating on edges use two arguments to describe the edge set they will use: The `edges` argument
// is a list of edge set descriptors to include in the edge set, and the `except` argument is a list of
// edge set descriptors to remove from the edge set.
// The default value for `edges` is `"ALL"`, the set of all edges.
// The default value for `except` is the empty set, meaning no edges are removed.
// If either argument is just a single edge set
// descriptor it can be passed directly rather than in a singleton list.
// Each edge set descriptor must be one of:
// - A vector pointing towards an edge, indicating that single edge.
// - A vector pointing towards a face, indicating all edges surrounding that face.
// - A vector pointing towards a corner, indicating all edges touching that corner.
// - The string `"X"`, indicating all X axis aligned edges.
// - The string `"Y"`, indicating all Y axis aligned edges.
// - The string `"Z"`, indicating all Z axis aligned edges.
// - The string `"ALL"`, indicating all edges.
// - The string `"NONE"`, indicating no edges at all.
// - 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:
// ```
// [
// [Y-Z-, Y+Z-, Y-Z+, Y+Z+],
// [X-Z-, X+Z-, X-Z+, X+Z+],
// [X-Y-, X+Y-, X-Y+, X+Y+]
// ]
// ```
// You can specify edge descriptors directly by giving a vector, or you can use sums of the
// named direction vectors described above. Below we show all of the edge sets you can
// describe with sums of the direction vectors, and then we show some examples of combining
// edge set descriptors.
// Figure(3D,Big,VPD=300,NoScales): Vectors pointing toward an edge select that single edge
// ydistribute(50) {
// xdistribute(30) {
// _show_edges(edges=BOT+RIGHT);
// _show_edges(edges=BOT+BACK);
// _show_edges(edges=BOT+LEFT);
// _show_edges(edges=BOT+FRONT);
// }
// xdistribute(30) {
// _show_edges(edges=FWD+RIGHT);
// _show_edges(edges=BACK+RIGHT);
// _show_edges(edges=BACK+LEFT);
// _show_edges(edges=FWD+LEFT);
// }
// xdistribute(30) {
// _show_edges(edges=TOP+RIGHT);
// _show_edges(edges=TOP+BACK);
// _show_edges(edges=TOP+LEFT);
// _show_edges(edges=TOP+FRONT);
// }
// }
// Figure(3D,Med,VPD=205,NoScales): Vectors pointing toward a face select all edges surrounding that face.
// ydistribute(50) {
// xdistribute(30) {
// _show_edges(edges=LEFT);
// _show_edges(edges=FRONT);
// _show_edges(edges=RIGHT);
// }
// xdistribute(30) {
// _show_edges(edges=TOP);
// _show_edges(edges=BACK);
// _show_edges(edges=BOTTOM);
// }
// }
// Figure(3D,Big,VPD=300,NoScales): Vectors pointing toward a corner select all edges surrounding that corner.
// ydistribute(50) {
// xdistribute(30) {
// _show_edges(edges=FRONT+LEFT+TOP);
// _show_edges(edges=FRONT+RIGHT+TOP);
// _show_edges(edges=FRONT+LEFT+BOT);
// _show_edges(edges=FRONT+RIGHT+BOT);
// }
// xdistribute(30) {
// _show_edges(edges=TOP+LEFT+BACK);
// _show_edges(edges=TOP+RIGHT+BACK);
// _show_edges(edges=BOT+LEFT+BACK);
// _show_edges(edges=BOT+RIGHT+BACK);
// }
// }
// Figure(3D,Med,VPD=205,NoScales): Named Edge Sets
// ydistribute(50) {
// xdistribute(30) {
// _show_edges(edges="X");
// _show_edges(edges="Y");
// _show_edges(edges="Z");
// }
// xdistribute(30) {
// _show_edges(edges="ALL");
// _show_edges(edges="NONE");
// }
// }
// 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.
// 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.
// 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.
// You can specify corner descriptors directly by giving a vector, or you can use sums of the
// named direction vectors described above. Below we show all of the corner sets you can
// describe with sums of the direction vectors and then we show some examples of combining
// corner set descriptors.
// Figure(3D,Big,NoScales,VPD=300): Vectors pointing toward a corner select that corner.
// ydistribute(55) {
// xdistribute(35) {
// _show_corners(corners=FRONT+LEFT+TOP);
// _show_corners(corners=FRONT+RIGHT+TOP);
// _show_corners(corners=FRONT+LEFT+BOT);
// _show_corners(corners=FRONT+RIGHT+BOT);
// }
// xdistribute(35) {
// _show_corners(corners=TOP+LEFT+BACK);
// _show_corners(corners=TOP+RIGHT+BACK);
// _show_corners(corners=BOT+LEFT+BACK);
// _show_corners(corners=BOT+RIGHT+BACK);
// }
// }
// Figure(3D,Big,NoScales,VPD=340): Vectors pointing toward an edge select the corners and the ends of the edge.
// ydistribute(55) {
// xdistribute(35) {
// _show_corners(corners=BOT+RIGHT);
// _show_corners(corners=BOT+BACK);
// _show_corners(corners=BOT+LEFT);
// _show_corners(corners=BOT+FRONT);
// }
// xdistribute(35) {
// _show_corners(corners=FWD+RIGHT);
// _show_corners(corners=BACK+RIGHT);
// _show_corners(corners=BACK+LEFT);
// _show_corners(corners=FWD+LEFT);
// }
// xdistribute(35) {
// _show_corners(corners=TOP+RIGHT);
// _show_corners(corners=TOP+BACK);
// _show_corners(corners=TOP+LEFT);
// _show_corners(corners=TOP+FRONT);
// }
// }
// Figure(3D,Med,NoScales,VPD=225): Vectors pointing toward a face select the corners of the face.
// ydistribute(55) {
// xdistribute(35) {
// _show_corners(corners=LEFT);
// _show_corners(corners=FRONT);
// _show_corners(corners=RIGHT);
// }
// xdistribute(35) {
// _show_corners(corners=TOP);
// _show_corners(corners=BACK);
// _show_corners(corners=BOTTOM);
// }
// }
// Figure(3D,Med,NoScales,VPD=200): Corners by name
// xdistribute(35) {
// _show_corners(corners="ALL");
// _show_corners(corners="NONE");
// }
// 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.
// 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:
// 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.
// Example: Of course, {{orient()}} can also place children on different sides of the parent. In this case you don't have to figure out that the required anchor is BOT+BACK.
// Example: You can combine this with {{diff()}} to remove the child. Note that it's more intuitive to shift the child after positioning, relative to the global coordinate system:
// diff()
// cuboid([50,40,15])
// right(.1)up(.1)
// position(RIGHT+TOP)
// orient(LEFT)
// tag("remove")cuboid([10,5,4], anchor=$align);
moduleposition(from)
{
req_children($children);
assert($parent_geom!=undef,"No object to attach to!");
// PARENT() attach(from, to, [overlap=], [norot=]) CHILDREN;
// Description:
// Attaches children to a parent object at an anchor point and orientation. Attached objects will
// be overlapped into the parent object by a little bit, as specified by the `$overlap`
// value (0 by default), or by the overriding `overlap=` argument. This is to prevent OpenSCAD
// from making non-manifold objects. You can define `$overlap=` as an argument in a parent
// module to set the default for all attachments to it. For a step-by-step explanation of
// attachments, see the [Attachments Tutorial](Tutorial-Attachments).
// Arguments:
// from = The vector, or name of the parent anchor point to attach to.
// to = Optional name of the child anchor point. If given, orients the child such that the named anchors align together rotationally.
// ---
// 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.
// norot = If true, don't rotate children when attaching to the anchor point. Only translate to the anchor point.
// Side Effects:
// `$idx` is set to the index number of each anchor if a list of anchors is given. Otherwise is set to `0`.
// `$attach_anchor` for each `from=` anchor given, this is set to the `[ANCHOR, POSITION, ORIENT, SPIN]` information for that anchor.
// `$attach_to` is set to the value of the `to=` argument, if given. Otherwise, `undef`
// `$attach_norot` is set to the value of the `norot=` argument.
// See Also: force_tag(), recolor(), hide(), show_only(), diff(), intersect()
// Usage:
// PARENT() tag(tag) CHILDREN;
// Description:
// Assigns the specified tag to all of the children. Note that if you want
// to apply a tag to non-tag-aware objects you need to use {{force_tag()}} instead.
// This works by setting the `$tag` variable, but it provides extra error checking and
// handling of scopes. You may set `$tag` directly yourself, but this is not recommended.
// .
// For a step-by-step explanation of attachments, see the [Attachments Tutorial](Tutorial-Attachments).
// Arguments:
// tag = tag string, which must not contain any spaces.
// Side Effects:
// Sets `$tag` to the tag you specify, possibly with a scope prefix.
// Example(3D): Applies the tag to both cuboids instead of having to repeat `$tag="remove"` for each one.
// diff("remove")
// cuboid(10){
// position(TOP) cuboid(3);
// tag("remove")
// {
// position(FRONT) cuboid(3);
// position(RIGHT) cuboid(3);
// }
// }
moduletag(tag)
{
req_children($children);
check=
assert(is_string(tag),"tag must be a string")
assert(undef==str_find(tag," "),str("Tag string \"",tag,"\" contains a space, which is not allowed"));
$tag=str($tag_prefix,tag);
children();
}
// Module: force_tag()
// Synopsis: Assigns a tag to a non-attachable object.
// Topics: Attachments
// See Also: tag(), recolor(), hide(), show_only(), diff(), intersect()
// Usage:
// PARENT() force_tag([tag]) CHILDREN;
// Description:
// You use this module when you want to make a non-attachable or non-BOSL2 module respect tags.
// It applies to its children the tag specified (or the tag currently in force if you don't specify a tag),
// making a final determination about whether to show or hide the children.
// This means that tagging in children's children will be ignored.
// This module is specifically provided for operating on children that are not tag aware such as modules
// that don't use {{attachable()}} or built in modules such as
// - `polygon()`
// - `projection()`
// - `polyhedron()` (or use [`vnf_polyhedron()`](vnf.scad#vnf_polyhedron))
// - `linear_extrude()` (or use [`linear_sweep()`](regions.scad#linear_sweep))
// - `rotate_extrude()`
// - `surface()`
// - `import()`
// - `difference()`
// - `intersection()`
// - `hull()`
// .
// When you use tag-based modules like {{diff()}} with a non-attachable module, the result may be puzzling.
// Any time a test occurs for display of child() that test will succeed. This means that when diff() checks
// to see if it should show a module it will show it, and when diff() checks to see if it should subtract the module
// it will subtract it. The result will be a hole, possibly with zero-thickness edges or faces. In order to
// get the correct behavior, every non-attachable module needs an invocation of force_tag, even ones
// that are not tagged.
// .
// For a step-by-step explanation of attachments, see the [Attachments Tutorial](Tutorial-Attachments).
// Arguments:
// tag = tag string, which must not contain any spaces
// Side Effects:
// Sets `$tag` to the tag you specify, possibly with a scope prefix.
// Example(2D): This example produces the full square without subtracting the "remove" item. When you use non-attachable modules with tags, results are unpredictable.
// Performs a differencing operation using tags to control what happens. This is specifically intended to
// address the situation where you want differences between a parent and child object, something
// that is impossible with the native difference() module.
// The children to diff are grouped into three categories, regardless of nesting level.
// The `remove` argument is a space delimited list of tags specifying objects to
// subtract. The `keep` argument is a similar list of tags giving objects to be kept.
// Objects not matching either the `remove` or `keep` lists form the third category of base objects.
// To produce its output, diff() forms the union of all the base objects and then
// subtracts all the objects with tags in `remove`. Finally it adds in objects listed in `keep`.
// Attachable objects should be tagged using {{tag()}}
// and non-attachable objects with {{force_tag()}}.
// .
// Remember when using tagged operations with that the operations don't happen in hierarchical order, since
// the point of tags is to break the hierarchy. If you tag an object with a keep tag, nothing will be
// subtracted from it, no matter where it appears because kept objects are unioned in at the end.
// If you want a child of an object tagged with a remove tag to stay in the model it may be
// better to give it a tag that is not a remove tag or a keep tag. Such an object *will* be subject to
// subtractions from other remove-tagged objects.
// .
// For a step-by-step explanation of attachments, see the [Attachments Tutorial](Tutorial-Attachments).
// Arguments:
// remove = String containing space delimited set of tag names of children to difference away. Default: `"remove"`
// 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"`
// Example: Diffing using default tags
// diff()
// cuboid(50) {
// tag("remove") attach(TOP) sphere(d=40);
// tag("keep") attach(CTR) cylinder(h=40, d=10);
// }
// Example: The "hole" items are subtracted from everything else. The other tags can be anything you find convenient.
// diff("hole")
// tag("body")sphere(d=100) {
// tag("pole") zcyl(d=55, h=100); // attach() not needed for center-to-center.
// 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.
// diff()
// tag("remove")cuboid(10)
// tag("")position(RIGHT+BACK)cyl(r=8,h=9);
// 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:
// $fn=16;
// // This module must be called by subtracting with "diff"
// module pipe(length, od, id) {
// // Strip the tag the user is using to subtract
// tag("")cylinder(h=length, d=od, center=true);
// // Leave the tag along here, so this one is removed
// cylinder(h=length+.02, d=id, center=true);
// }
// // Draw some intersecting pipes
// diff(){
// tag("remove"){
// pipe(length=5, od=2, id=1.9);
// zrot(10)xrot(75)
// pipe(length=5, od=2, id=1.9);
// }
// // The orange bar has its center removed
// color("orange") down(1) xcyl(h=8, d=1);
// // "keep" prevents interior of the blue bar intact
// // Objects outside the diff don't have pipe interiors removed
// color("purple") down(2.2) ycyl(h=8, d=0.3);
// Example(3D,NoScales,NoAxes): Nested diff() calls work as expected, but be careful of reusing tag names, even hidden in submodules.
// $fn=32;
// diff("rem1")
// cyl(r=10,h=10){
// diff("rem2",$tag="rem1"){
// cyl(r=8,h=11);
// tag("rem2")diff("rem3"){
// cyl(r=6,h=12);
// tag("rem3")cyl(r=4,h=13);
// }
// }
// }
// 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.
// Example(3D,NoAxes,NoScales): When working with Non-Attachables like rotate_extrude() you must apply {{force_tag()}} to every non-attachable object.
// back_half()
// diff("remove")
// cuboid(40) {
// attach(TOP)
// recolor("lightgreen")
// cyl(l=10,d=30);
// position(TOP+RIGHT)
// force_tag("remove")
// xrot(90)
// rotate_extrude()
// right(20)
// circle(5);
// }
// 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.
// $fn=32;
// diff()
// cuboid(10){
// force_tag("remove")intersection()
// {
// position(RIGHT) cyl(r=7,h=15);
// position(LEFT) cyl(r=7,h=15);
// }
// tag("keep")cyl(r=1,h=9);
// }
// Example: In this example the children that are subtracted are each at different nesting levels, with a kept object in between.
// $fn=32;
// diff()
// cuboid(10){
// tag("remove")cyl(r=4,h=11)
// tag("keep")cyl(r=3,h=17)
// tag("remove")position(RIGHT)cyl(r=2,h=18);
// }
// 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.
// $fn=32;
// intersect("keep","fullkeep")
// diff(keep="fullkeep keep")
// cuboid(10){
// tag("remove")cyl(r=4,h=11);
// tag("keep") position(RIGHT)cyl(r=8,h=12);
// tag("fullkeep")cyl(r=1,h=12);
// }
// 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.
// $fn=64;
// diff()
// intersect(keep="remove keep")
// cuboid(10,$thing="cube"){
// tag("intersect"){
// position(RIGHT) cyl(r=5.5,h=15)
// tag("")cyl(r=2,h=10);
// position(LEFT) cyl(r=5.54,h=15)
// tag("keep")cyl(r=2,h=10);
// }
// // Untagged it is in the intersection
// tag("") position(BACK+RIGHT)
// cyl(r=2,h=10,anchor=CTR);
// // With keep the full cylinder appears
// tag("keep") position(BACK+LEFT)
// cyl(r=2,h=10,anchor=CTR);
// tag("remove") cyl(r=3,h=15);
// }
modulediff(remove="remove",keep="keep")
{
req_children($children);
assert(is_string(remove),"remove must be a string of tags");
assert(is_string(keep),"keep must be a string of tags");
if(_is_shown())
{
difference(){
hide(str(remove," ",keep))children();
show_only(remove)children();
}
}
show_int(keep)children();
}
// Module: tag_diff()
// Synopsis: Performs a {{diff()}} and then sets a tag on the result.
// Perform a differencing operation in the manner of {{diff()}} using tags to control what happens,
// and then tag the resulting difference object with the specified tag. This forces the specified
// tag to be resolved at the level of the difference operation. In most cases, this is not necessary,
// but if you have kept objects and want to operate on this difference object as a whole object using
// more tag operations, you will probably not get the results you want if you simply use {{tag()}}.
// .
// For a step-by-step explanation of attachments, see the [Attachments Tutorial](Tutorial-Attachments).
// Arguments:
// tag = Tag string to apply to this difference object
// remove = String containing space delimited set of tag names of children to difference away. Default: `"remove"`
// 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"`
// Side Effects:
// Sets `$tag` to the tag you specify, possibly with a scope prefix.
// 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:
// diff("rem"){
// cuboid([20,10,30],anchor=FRONT);
// tag("rem")diff("remove","keep"){
// cuboid([10,10,20]);
// tag("remove")cuboid([11,11,5]);
// tag("keep")cuboid([2,2,20]);
// }
// }
// Example: Using tag_diff corrects the problem:
// diff("rem"){
// cuboid([20,10,30],anchor=FRONT);
// tag_diff("rem","remove","keep"){
// cuboid([10,10,20]);
// tag("remove")cuboid([11,11,5]);
// tag("keep")cuboid([2,2,20]);
// }
// }
// 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.
// diff("rem","nothing")
// cyl(r=8,h=6)
// tag("rem")diff()
// cyl(r=7,h=7)
// tag("remove")cyl(r=6,h=8)
// tag("keep")cyl(r=5,h=9);
// Example: Changing to tag_diff() causes the kept cylinder to be subtracted, producing the desired result:
// diff("rem")
// cyl(r=8,h=6)
// tag_diff("rem")
// cyl(r=7,h=7)
// tag("remove")cyl(r=6,h=8)
// tag("keep")cyl(r=5,h=9);
moduletag_diff(tag,remove="remove",keep="keep")
{
req_children($children);
assert(is_string(remove),"remove must be a string of tags");
assert(is_string(keep),"keep must be a string of tags");
assert(is_string(tag),"tag must be a string");
assert(undef==str_find(tag," "),str("Tag string \"",tag,"\" contains a space, which is not allowed"));
$tag=str($tag_prefix,tag);
if(_is_shown())
show_all(){
difference(){
hide(str(remove," ",keep))children();
show_only(remove)children();
}
show_only(keep)children();
}
}
// Module: intersect()
// Synopsis: Perform an intersection operation on children using tags rather than hierarchy to control what happens.
// Performs an intersection operation on its children, using tags to
// determine what happens. This is specifically intended to address
// the situation where you want intersections involving a parent and
// child object, something that is impossible with the native
// intersection() module. This module treats the children in three
// groups: objects matching the tags listed in `intersect`, objects
// matching tags listed in `keep`, and the remaining objects that
// don't match any of the listed tags. The intersection is computed
// between the union of the `intersect` tagged objects and union of the objects that don't
// match any of the listed tags. Finally the objects listed in `keep` are
// unioned with the result. Attachable objects should be tagged using {{tag()}}
// and non-attachable objects with {{force_tag()}}.
// .
// For a step-by-step explanation of attachments, see the [Attachments Tutorial](Tutorial-Attachments).
// Arguments:
// intersect = String containing space delimited set of tag names of children to intersect. Default: "intersect"
// keep = String containing space delimited set of tag names of children to keep whole. Default: "keep"
// Example:
// intersect("mask", keep="axle")
// sphere(d=100) {
// tag("mask")cuboid([40,100,100]);
// tag("axle")xcyl(d=40, l=100);
// }
// 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.
// $fn=32;
// intersect("keep","fullkeep")
// diff(keep="fullkeep keep")
// cuboid(10){
// tag("remove")cyl(r=4,h=11);
// tag("keep") position(RIGHT)cyl(r=8,h=12);
// tag("fullkeep")cyl(r=1,h=12);
// }
// 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.
assert(is_string(intersect),"intersect must be a string of tags");
assert(is_string(keep),"keep must be a string of tags");
assert(is_string(tag),"tag must be a string");
assert(undef==str_find(tag," "),str("Tag string \"",tag,"\" contains a space, which is not allowed"));
$tag=str($tag_prefix,tag);
if(_is_shown())
show_all(){
intersection(){
show_only(intersect)children();
hide(str(intersect," ",keep))children();
}
show_only(keep)children();
}
}
// Module: conv_hull()
// Synopsis: Performs a hull operation on the children using tags to determine what happens.
// Topics: Attachments
// See Also: tag(), recolor(), show_only(), hide(), diff(), intersect()
// Usage:
// conv_hull([keep]) CHILDREN;
// Description:
// Performs a hull operation on the children using tags to determine what happens. The items
// not tagged with the `keep` tags are combined into a convex hull, and the children tagged with the keep tags
// are unioned with the result.
// .
// For a step-by-step explanation of attachments, see the [Attachments Tutorial](Tutorial-Attachments).
// Arguments:
// keep = String containing space delimited set of tag names of children to keep out of the hull. Default: "keep"
// Example:
// conv_hull("keep")
// sphere(d=100, $fn=64) {
// cuboid([40,90,90]);
// tag("keep")xcyl(d=40, l=120);
// }
// Example: difference combined with hull where all objects are relative to each other.
// $fn=32;
// diff()
// conv_hull("remove")
// cuboid(10)
// position(RIGHT+BACK)cyl(r=4,h=10)
// tag("remove")cyl(r=2,h=12);
moduleconv_hull(keep="keep")
{
req_children($children);
assert(is_string(keep),"keep must be a string of tags");
if(_is_shown())
hull()hide(keep)children();
show_int(keep)children();
}
// Module: tag_conv_hull()
// Synopsis: Performs a {{conv_hull()}} and then sets a tag on the result.
// Topics: Attachments
// See Also: tag(), recolor(), show_only(), hide(), diff(), intersect()
// Usage:
// tag_conv_hull(tag, [keep]) CHILDREN;
// Description:
// Perform a convex hull operation in the manner of {{conv_hull()}} using tags to control what happens,
// and then tag the resulting hull object with the specified tag. This forces the specified
// tag to be resolved at the level of the hull operation. In most cases, this is not necessary,
// but if you have kept objects and want to operate on the hull object as a whole object using
// more tag operations, you will probably not get the results you want if you simply use {{tag()}}.
// .
// For a step-by-step explanation of attachments, see the [Attachments Tutorial](Tutorial-Attachments).
// Arguments:
// keep = String containing space delimited set of tag names of children to keep out of the hull. Default: "keep"
// Side Effects:
// Sets `$tag` to the tag you specify, possibly with a scope prefix.
// Example: With a regular tag, the kept object is not handled as desired:
// diff(){
// cuboid([30,30,9])
// tag("remove")conv_hull("remove")
// cuboid(10,anchor=LEFT+FRONT){
// position(RIGHT+BACK)cyl(r=4,h=10);
// tag("keep")position(FRONT+LEFT)cyl(r=4,h=10);
// }
// }
// Example: Using `tag_conv_hull()` fixes the problem:
// diff(){
// cuboid([30,30,9])
// tag_conv_hull("remove")
// cuboid(10,anchor=LEFT+FRONT){
// position(RIGHT+BACK)cyl(r=4,h=10);
// tag("keep")position(FRONT+LEFT)cyl(r=4,h=10);
// }
// }
moduletag_conv_hull(tag,keep="keep")
{
req_children($children);
assert(is_string(keep),"keep must be a string of tags");
assert(is_string(tag),"tag must be a string");
assert(undef==str_find(tag," "),str("Tag string \"",tag,"\" contains a space, which is not allowed"));
$tag=str($tag_prefix,tag);
if(_is_shown())
show_all(){
hull()hide(keep)children();
show_only(keep)children();
}
}
// Module: hide()
// Synopsis: Hides attachable children with the given tags.
// Topics: Attachments
// See Also: tag(), recolor(), show_only(), show_all(), show_int(), diff(), intersect()
// Usage:
// hide(tags) CHILDREN;
// Description:
// 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()`.
// For a step-by-step explanation of attachments, see the [Attachments Tutorial](Tutorial-Attachments).
// Side Effects:
// Sets `$tags_hidden` to include the tags you specify.
// Synopsis: Show only the children with the listed tags.
// See Also: tag(), recolor(), show_all(), show_int(), diff(), intersect()
// Topics: Attachments
// Usage:
// show_only(tags) CHILDREN;
// Description:
// 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.
// For a step-by-step explanation of attachments, see the [Attachments Tutorial](Tutorial-Attachments).
// Side Effects:
// Sets `$tags_shown` to the tag you specify.
// Example: Display the attachments but not the parent
// show_only("visible")
// cube(50, anchor=CENTER)
// tag("visible"){
// attach(LEFT, BOTTOM) cylinder(d=30, h=30);
// attach(RIGHT, BOTTOM) cylinder(d=30, h=30);
// }
moduleshow_only(tags)
{
req_children($children);
dummy=assert(is_string(tags),str("tags must be a string",tags));
// If this is *not* run as a child of `attach()` with the `to` argument
// given, then the following transformations are performed in order:
// * Translates so the `anchor` point is at the origin (0,0,0).
// * Rotates around the Z axis by `spin` degrees counter-clockwise.
// * Rotates so the top of the part points towards the vector `orient`.
// .
// If this is called as a child of `attach(from,to)`, then the info
// for the anchor points referred to by `from` and `to` are fetched,
// which will include position, direction, and spin. With that info,
// the following transformations are performed:
// * Translates this part so it's anchor position matches the parent's anchor position.
// * Rotates this part so it's anchor direction vector exactly opposes the parent's anchor direction vector.
// * Rotates this part so it's anchor spin matches the parent's anchor spin.
// .
// For a step-by-step explanation of attachments, see the [Attachments Tutorial](Tutorial-Attachments).
//
// Arguments:
// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#subsection-anchor). Default: `CENTER`
// spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#subsection-spin). Default: `0`
// orient = Vector to rotate top towards, after spin. See [orient](attachments.scad#subsection-orient). Default: `UP`
// ---
// 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.
// 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.
// 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.
// r = Radius of the cylindrical/conical volume. Can be a scalar, or a list of sizes per axis.
// d = Diameter of the cylindrical/conical volume. Can be a scalar, or a list of sizes per axis.
// r1 = Radius of the bottom of the conical volume. Can be a scalar, or a list of sizes per axis.
// r2 = Radius of the top of the conical volume. Can be a scalar, or a list of sizes per axis.
// d1 = Diameter of the bottom of the conical volume. Can be a scalar, a list of sizes per axis.
// d2 = Diameter of the top of the conical volume. Can be a scalar, a list of sizes per axis.
// l/h = Length of the cylindrical, conical, or extruded path volume along axis.
// vnf = The [VNF](vnf.scad) of the volume.
// path = The path to generate a polygon from.
// region = The region to generate a shape from.
// extent = If true, calculate anchors by extents, rather than intersection, for VNFs and paths. Default: true.
// cp = If given, specifies the centerpoint of the volume. Default: `[0,0,0]`
// offset = If given, offsets the perimeter of the volume around the centerpoint.
// anchors = If given as a list of anchor points, allows named anchor points.
// two_d = If true, the attachable shape is 2D. If false, 3D. Default: false (3D)
// axis = The vector pointing along the axis of a geometry. Default: UP
// 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.
// geom = If given, uses the pre-defined (via {{attach_geom()}} geometry.
//
// Side Effects:
// `$parent_anchor` is set to the parent object's `anchor` value.
// `$parent_spin` is set to the parent object's `spin` value.
// `$parent_orient` is set to the parent object's `orient` value.
// `$parent_geom` is set to the parent object's `geom` value.
// `$parent_size` is set to the parent object's cubical `[X,Y,Z]` volume size.
// `$color` is used to set the color of the object
// `$save_color` is used to revert color to the parent's color
// 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.
// 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.
// Given anchor, spin, orient, and general geometry info for a managed volume, this calculates
// the transformation matrix needed to be applied to the contents of that volume. A managed 3D
// volume is assumed to be vertically (Z-axis) oriented, and centered. A managed 2D area is just
// assumed to be centered.
// .
// If `p` is not given, then the transformation matrix will be returned.
// If `p` contains a VNF, a new VNF will be returned with the vertices transformed by the matrix.
// If `p` contains a path, a new path will be returned with the vertices transformed by the matrix.
// If `p` contains a point, a new point will be returned, transformed by the matrix.
// .
// If `$attach_to` is not defined, then the following transformations are performed in order:
// * Translates so the `anchor` point is at the origin (0,0,0).
// * Rotates around the Z axis by `spin` degrees counter-clockwise.
// * Rotates so the top of the part points towards the vector `orient`.
// .
// If `$attach_to` is defined, as a consequence of `attach(from,to)`, then
// the following transformations are performed in order:
// * Translates this part so it's anchor position matches the parent's anchor position.
// * Rotates this part so it's anchor direction vector exactly opposes the parent's anchor direction vector.
// * Rotates this part so it's anchor spin matches the parent's anchor spin.
// .
// For a step-by-step explanation of attachments, see the [Attachments Tutorial](Tutorial-Attachments).
//
// Arguments:
// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#subsection-anchor). Default: `CENTER`
// spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#subsection-spin). Default: `0`
// orient = Vector to rotate top towards, after spin. See [orient](attachments.scad#subsection-orient). Default: `UP`
// ---
// 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.
// 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.
// 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.
// r = Radius of the cylindrical/conical volume. Can be a scalar, or a list of sizes per axis.
// d = Diameter of the cylindrical/conical volume. Can be a scalar, or a list of sizes per axis.
// r1 = Radius of the bottom of the conical volume. Can be a scalar, or a list of sizes per axis.
// r2 = Radius of the top of the conical volume. Can be a scalar, or a list of sizes per axis.
// d1 = Diameter of the bottom of the conical volume. Can be a scalar, a list of sizes per axis.
// d2 = Diameter of the top of the conical volume. Can be a scalar, a list of sizes per axis.
// l/h = Length of the cylindrical, conical, or extruded path volume along axis.
// vnf = The [VNF](vnf.scad) of the volume.
// path = The path to generate a polygon from.
// region = The region to generate a shape from.
// extent = If true, calculate anchors by extents, rather than intersection. Default: false.
// cp = If given, specifies the centerpoint of the volume. Default: `[0,0,0]`
// offset = If given, offsets the perimeter of the volume around the centerpoint.
// anchors = If given as a list of anchor points, allows named anchor points.
// two_d = If true, the attachable shape is 2D. If false, 3D. Default: false (3D)
// axis = The vector pointing along the axis of a geometry. Default: UP
// Given arguments that describe the geometry of an attachable object, returns the internal geometry description.
// This will probably not not ever need to be called by the end user.
//
// Arguments:
// ---
// 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.
// 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.
// 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.
// scale = If given as number or a 2D vector, scales the top of the shape, relative to the bottom. Default: `[1,1]`
// twist = If given as number, rotates the top of the shape by the given number of degrees clockwise, relative to the bottom. Default: `0`
// r = Radius of the cylindrical/conical volume. Can be a scalar, or a list of sizes per axis.
// d = Diameter of the cylindrical/conical volume. Can be a scalar, or a list of sizes per axis.
// r1 = Radius of the bottom of the conical volume. Can be a scalar, or a list of sizes per axis.
// r2 = Radius of the top of the conical volume. Can be a scalar, or a list of sizes per axis.
// d1 = Diameter of the bottom of the conical volume. Can be a scalar, a list of sizes per axis.
// d2 = Diameter of the top of the conical volume. Can be a scalar, a list of sizes per axis.
// l/h = Length of the cylindrical, conical or extruded region volume along axis.
// vnf = The [VNF](vnf.scad) of the volume.
// region = The region to generate a shape from.
// extent = If true, calculate anchors by extents, rather than intersection. Default: true.
// cp = If given, specifies the centerpoint of the volume. Default: `[0,0,0]`
// offset = If given, offsets the perimeter of the volume around the centerpoint.
// anchors = If given as a list of anchor points, allows named anchor points.
// two_d = If true, the attachable shape is 2D. If false, 3D. Default: false (3D)
// axis = The vector pointing along the axis of a geometry. Default: UP
// 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.