Merge pull request #1478 from adrianVmariano/master

attachments update
This commit is contained in:
Revar Desmera 2024-09-29 01:25:07 -07:00 committed by GitHub
commit 38f9a9b40c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 367 additions and 95 deletions

View file

@ -12,10 +12,12 @@
// FileFootnotes: STD=Included in std.scad // FileFootnotes: STD=Included in std.scad
////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////
include<structs.scad>
// Default values for attachment code. // Default values for attachment code.
$tags=undef; // for backward compatibility $tags=undef; // for backward compatibility
$tag = ""; $tag = "";
$save_tag = undef;
$tag_prefix = ""; $tag_prefix = "";
$overlap = 0; $overlap = 0;
$color = "default"; $color = "default";
@ -100,18 +102,18 @@ _ANCHOR_TYPES = ["intersect","hull"];
// stepper motor shape. The names, positions, directions, and spins of these anchors are // stepper motor shape. The names, positions, directions, and spins of these anchors are
// specific to the object, and are documented when they exist. // specific to the object, and are documented when they exist.
// Subsection: Spin // Subsection: Spin
// Spin is specified with the `spin` argument in most shape modules. Specifying a scalar `spin` // Spin is specified with the `spin` argument in most shape modules. Specifying a spin`
// when creating an object will rotate the object counter-clockwise around the Z axis by the given // angle when creating an object will rotate the object counter-clockwise around the Z axis by the given
// number of degrees. If given as a 3D vector, the object will be rotated around each of the X, Y, Z // number of degrees. Spin is always applied after anchoring, and before orientation.
// axes by the number of degrees in each component of the vector. Spin is always applied after // Since spin is applied after anchoring it is not always what you might think of intuitively
// anchoring, and before orientation. Since spin is applied after anchoring it is not what // as spinning the shape. To do that, apply `zrot()` to the shape before anchoring.
// you might think of intuitively as spinning the shape. To do that, apply `zrot()` to the shape before anchoring.
// Subsection: Orient // Subsection: Orient
// Orientation is specified with the `orient` argument in most shape modules. Specifying `orient` // Orientation is specified with the `orient` argument in most shape modules. Specifying `orient`
// when creating an object will rotate the object such that the top of the object will be pointed // when creating an object will rotate the object such that the top of the object will be pointed
// at the vector direction given in the `orient` argument. Orientation is always applied after // at the vector direction given in the `orient` argument. Orientation is always applied after
// anchoring and spin. The constants `UP`, `DOWN`, `FRONT`, `BACK`, `LEFT`, and `RIGHT` can be // anchoring and spin. The constants `UP`, `DOWN`, `FRONT`, `BACK`, `LEFT`, and `RIGHT` can be
// added together to form the directional vector for this. ie: `LEFT+BACK` // added together to form the directional vector for this (e.g. `LEFT+BACK`). The orient parameter
// is ignored when you use {{attach()}} because {{attach()}} provides its own orientation.
// Subsection: Specifying Directions // Subsection: Specifying Directions
// You can use direction vectors to specify anchors for objects or to specify edges, faces, and // You can use direction vectors to specify anchors for objects or to specify edges, faces, and
// corners of cubes. You can simply specify these direction vectors numerically, but another // corners of cubes. You can simply specify these direction vectors numerically, but another
@ -772,10 +774,17 @@ function _make_anchor_legal(anchor,geom) =
// up on the top while aligning it with the right edge of the top face, and `attach(RIGHT,BOT,align=TOP)` which // up on the top while aligning it with the right edge of the top face, and `attach(RIGHT,BOT,align=TOP)` which
// stand the object on the right face while aligning with the top edge. If you apply spin using the // stand the object on the right face while aligning with the top edge. If you apply spin using the
// argument to `attach()` then it will be taken into account for the alignment. If you apply spin with // argument to `attach()` then it will be taken into account for the alignment. If you apply spin with
// a parameter to the child it will NOT be taken into account. Note that spin is not permitted for // a parameter to the child it will NOT be taken into account. The special spin value "align" will
// spin the child so that the child's BACK direction is pointed towards the aligned edge on the parent.
// Note that spin is not permitted for
// 2D objects because it would change the child orientation so that the anchors are no longer parallel. // 2D objects because it would change the child orientation so that the anchors are no longer parallel.
// When you use `align=` you can also adjust the position using `inset=`, which shifts the child // When you use `align=` you can also adjust the position using `inset=`, which shifts the child
// away from the edge or corner it is aligned to. // away from the edge or corner it is aligned to.
// .
// Note that the concept of alignment doesn't always make sense for objects without corners, such as spheres or cylinders.
// In same cases the alignments using such children will be odd because the alignment computation is trying to
// place a non-existent corner somewhere. Because attach() doesn't have in formation about the child when
// it runs it cannot handle curved shapes differently from cubes, so this behavior cannot be changed.
// . // .
// If you give `inside=true` then the anchor arrows are lined up so they are pointing the same direction and // If you give `inside=true` then the anchor arrows are lined up so they are pointing the same direction and
// the child object will be located inside the parent. In this case a default "remove" tag is applied to // the child object will be located inside the parent. In this case a default "remove" tag is applied to
@ -820,7 +829,7 @@ function _make_anchor_legal(anchor,geom) =
// 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. // 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.
// inside = If `child` is given you can set `inside=true` to attach the child to the inside of the parent for diff() operations. Default: false // inside = If `child` is given you can set `inside=true` to attach the child to the inside of the parent for diff() operations. Default: false
// shiftout = Shift an inside object outward so that it overlaps all the aligned faces. Default: 0 // shiftout = Shift an inside object outward so that it overlaps all the aligned faces. Default: 0
// spin = Amount to rotate the parent around the axis of the parent anchor. (Only permitted in 3D.) // spin = Amount to rotate the parent around the axis of the parent anchor. Can set to "align" to align the child's BACK with the parent aligned edge. (Only permitted in 3D.)
// Side Effects: // Side Effects:
// `$anchor` set to the parent anchor value used for the child. // `$anchor` set to the parent anchor value used for the child.
// `$align` set to the align value used for the child. // `$align` set to the align value used for the child.
@ -828,6 +837,8 @@ function _make_anchor_legal(anchor,geom) =
// `$attach_anchor` for each anchor given, this is set to the `[ANCHOR, POSITION, ORIENT, SPIN]` information for that anchor. // `$attach_anchor` for each anchor given, this is set to the `[ANCHOR, POSITION, ORIENT, SPIN]` information for that anchor.
// if inside is true then set default tag to "remove" // if inside is true then set default tag to "remove"
// `$attach_to` is set to the value of the `child` argument, if given. Otherwise, `undef` // `$attach_to` is set to the value of the `child` argument, if given. Otherwise, `undef`
// `$edge_angle` is set to the angle of the edge if the anchor is on an edge and the parent is a prismoid or vnf with "hull" anchoring
// `$edge_length` is set to the length of the edge if the anchor is on an edge and the parent is a prismoid or vnf with "hull" anchoring
// Example: Cylinder placed on top of cube: // Example: Cylinder placed on top of cube:
// cuboid(50) // cuboid(50)
// attach(TOP,BOT) cylinder(d1=30,d2=15,h=25); // attach(TOP,BOT) cylinder(d1=30,d2=15,h=25);
@ -878,12 +889,39 @@ function _make_anchor_legal(anchor,geom) =
// attach(RIGHT+FRONT, TOP, inside=true) cuboid([10,3,5]); // attach(RIGHT+FRONT, TOP, inside=true) cuboid([10,3,5]);
// attach(RIGHT+FRONT, TOP, inside=true, align=TOP,shiftout=.01) cuboid([5,1,2]); // attach(RIGHT+FRONT, TOP, inside=true, align=TOP,shiftout=.01) cuboid([5,1,2]);
// } // }
// Example: Attaching a 3d edge mask. Simple 2d masks can be done using {{edge_profile()}} but this mask varies along its length.
// module wavy_edge(length,cycles, r, steps, n)
// {
// rmin = is_vector(r) ? r[0] : 0.01;
// rmax = is_vector(r) ? r[1] : r;
// layers = [for(z=[0:steps])
// let(
// r=rmin+(rmax-rmin)/2*(cos(z*360*cycles/steps)+1),ff=echo(r=r)
// )
// path3d( concat([[0,0]],
// arc(corner=path2d([BACK,CTR,RIGHT]), n=n, r=r)),
// z/steps*length-length/2)
// ];
// attachable([rmax,rmax,length]){
// skin(layers,slices=0);
// children();
// }
// }
// diff()
// cuboid(25)
// attach([TOP+RIGHT,TOP+LEFT,TOP+FWD, FWD+RIGHT], FWD+LEFT, inside=true, shiftout=.01)
// wavy_edge(length=25.1,cycles=1.4,r=4,steps=24,n=15);
module attach(parent, child, overlap, align, spin=0, norot, inset=0, shiftout=0, inside=false, from, to) module attach(parent, child, overlap, align, spin=0, norot, inset=0, shiftout=0, inside=false, from, to)
{ {
dummy3= dummy3=
assert(num_defined([to,child])<2, "Cannot combine deprecated 'to' argument with 'child' parameter") assert(num_defined([to,child])<2, "Cannot combine deprecated 'to' argument with 'child' parameter")
assert(num_defined([from,parent])<2, "Cannot combine deprecated 'from' argument with 'parent' parameter"); assert(num_defined([from,parent])<2, "Cannot combine deprecated 'from' argument with 'parent' parameter")
assert(spin!="align" || is_def(align), "Can only set spin to \"align\" when the 'align' parameter is given")
assert(is_finite(spin) || spin=="align", "Spin must be a number (unless align is given)")
assert((is_undef(overlap) || is_finite(overlap)) && (is_def(overlap) || is_undef($overlap) || is_finite($overlap)),
str("Provided ",is_def(overlap)?"":"$","overlap is not valid."));
if (is_def(to)) if (is_def(to))
echo("The 'to' option to attach() is deprecated and will be removed in the future. Use 'child' instead."); echo("The 'to' option to attach() is deprecated and will be removed in the future. Use 'child' instead.");
if (is_def(from)) if (is_def(from))
@ -893,10 +931,16 @@ module attach(parent, child, overlap, align, spin=0, norot, inset=0, shiftout=0,
req_children($children); req_children($children);
dummy=assert($parent_geom != undef, "No object to attach to!") dummy=assert($parent_geom != undef, "No object to attach to!")
assert(is_undef(child) || is_string(child) || (is_vector(child) && (len(child)==2 || len(child)==3)), "child must be a named anchor (a string) or a 2-vector or 3-vector") assert(is_undef(child) || is_string(child) || (is_vector(child) && (len(child)==2 || len(child)==3)),
"child must be a named anchor (a string) or a 2-vector or 3-vector")
assert(is_undef(align) || !is_string(child), "child is a named anchor. Named anchors are not supported with align="); assert(is_undef(align) || !is_string(child), "child is a named anchor. Named anchors are not supported with align=");
two_d = _attach_geom_2d($parent_geom); two_d = _attach_geom_2d($parent_geom);
basegeom = $parent_geom[0]=="conoid" ? attach_geom(r=2,h=2,axis=$parent_geom[5])
: $parent_geom[0]=="prismoid" ? attach_geom(size=[2,2,2],axis=$parent_geom[4])
: attach_geom(size=[2,2,2]);
childgeom = attach_geom([2,2,2]);
child_abstract_anchor = is_vector(child) && !two_d ? _find_anchor(_make_anchor_legal(child,childgeom), childgeom) : undef;
overlap = (overlap!=undef)? overlap : $overlap; overlap = (overlap!=undef)? overlap : $overlap;
parent = first_defined([parent,from]); parent = first_defined([parent,from]);
anchors = is_vector(parent) || is_string(parent) ? [parent] : parent; anchors = is_vector(parent) || is_string(parent) ? [parent] : parent;
@ -923,34 +967,63 @@ module attach(parent, child, overlap, align, spin=0, norot, inset=0, shiftout=0,
: point3d(anchors[anch_ind]); : point3d(anchors[anch_ind]);
$anchor=anchor; $anchor=anchor;
anchor_data = _find_anchor(anchor, $parent_geom); anchor_data = _find_anchor(anchor, $parent_geom);
$edge_angle = len(anchor_data)==5 ? struct_val(anchor_data[4],"edge_angle") : undef;
$edge_length = len(anchor_data)==5 ? struct_val(anchor_data[4],"edge_length") : undef;
anchor_pos = anchor_data[1]; anchor_pos = anchor_data[1];
anchor_dir = factor*anchor_data[2]; anchor_dir = factor*anchor_data[2];
anchor_spin = two_d || !inside || anchor==TOP || anchor==BOT ? anchor_data[3] anchor_spin = two_d || !inside || anchor==TOP || anchor==BOT ? anchor_data[3]
: let(spin_dir = rot(anchor_data[3],from=UP, to=-anchor_dir, p=BACK)) : let(spin_dir = rot(anchor_data[3],from=UP, to=-anchor_dir, p=BACK))
_compute_spin(anchor_dir,spin_dir); _compute_spin(anchor_dir,spin_dir);
parent_abstract_anchor = is_vector(anchor) && !two_d ? _find_anchor(_make_anchor_legal(anchor,basegeom),basegeom) : undef;
for(align_ind = idx(align_list)){ for(align_ind = idx(align_list)){
align = is_undef(align_list[align_ind]) ? undef align = is_undef(align_list[align_ind]) ? undef
: assert(is_vector(align_list[align_ind],2) || is_vector(align_list[align_ind],3), "align direction must be a 2-vector or 3-vector") : assert(is_vector(align_list[align_ind],2) || is_vector(align_list[align_ind],3), "align direction must be a 2-vector or 3-vector")
two_d ? _force_anchor_2d(align_list[align_ind]) two_d ? _force_anchor_2d(align_list[align_ind])
: point3d(align_list[align_ind]); : point3d(align_list[align_ind]);
spin = is_num(spin) ? spin
: align==CENTER ? 0
: sum(v_abs(anchor))==1 ? // parent anchor is a face
let(
spindir = in_list(anchor,[TOP,BOT]) ? BACK : UP,
proj = project_plane(point4d(anchor),[spindir,align]),
ang = v_theta(proj[1])-v_theta(proj[0])
)
ang
: // parent anchor is not a face, so must be an edge (corners not allowed)
let(
nativeback = apply(rot(to=parent_abstract_anchor[2],from=UP)
*affine3d_zrot(parent_abstract_anchor[3]), BACK)
)
nativeback*align<0 ? -180:0;
$idx = align_ind+len(align_list)*anch_ind; $idx = align_ind+len(align_list)*anch_ind;
$align=align; $align=align;
goodcyl = $parent_geom[0] != "conoid" || is_undef(align) || align==CTR ? true
: let(
align=rot(from=$parent_geom[5],to=UP,p=align),
anchor=rot(from=$parent_geom[5],to=UP,p=anchor)
)
anchor==TOP || anchor==BOT || align==TOP || align==BOT;
badcorner = !in_list($parent_geom[0],["conoid","spheroid"]) && !is_undef(align) && align!=CTR && sum(v_abs(anchor))==3;
badsphere = $parent_geom[0]=="spheroid" && !is_undef(align) && align!=CTR;
dummy=assert(is_undef(align) || all_zero(v_mul(anchor,align)), dummy=assert(is_undef(align) || all_zero(v_mul(anchor,align)),
str("Invalid alignment: align value (",align,") includes component parallel to parent anchor (",anchor,")")); str("Invalid alignment: align value (",align,") includes component parallel to parent anchor (",anchor,")"))
assert(goodcyl, str("Cannot use align with an anchor on a curved edge or surface of a cylinder at parent anchor (",anchor,")"))
assert(!badcorner, str("Cannot use align at a corner anchor (",anchor,")"))
assert(!badsphere, "Cannot use align on spheres.");
// Now compute position on the parent (including alignment but not inset) where the child will be anchored // Now compute position on the parent (including alignment but not inset) where the child will be anchored
pos = is_undef(align) ? anchor_data[1] : _find_anchor(anchor+align, $parent_geom)[1]; pos = is_undef(align) ? anchor_data[1] : _find_anchor(anchor+align, $parent_geom)[1];
$attach_anchor = list_set(anchor_data, 1, pos); // Never used; For user informational use? Should this be set at all? $attach_anchor = list_set(anchor_data, 1, pos); // Never used; For user informational use? Should this be set at all?
startdir = two_d || is_undef(align)? undef
: anchor==UP || anchor==DOWN ? BACK
: UP - (anchor*UP)*anchor/(anchor*anchor); // Component of UP perpendicular to anchor
enddir = is_undef(child) || child.z==0 ? UP : BACK;
// Compute adjustment to the child anchor for position purposes. This adjustment // Compute adjustment to the child anchor for position purposes. This adjustment
// accounts for the change in the anchor needed to to alignment. // accounts for the change in the anchor needed to to alignment.
child_adjustment = is_undef(align)? CTR child_adjustment = is_undef(align)? CTR
: two_d ? rot(to=child,from=-factor*anchor,p=align) : two_d ? rot(to=child,from=-factor*anchor,p=align)
: apply( frame_map(x=child, z=enddir) : apply( rot(to=child_abstract_anchor[2],from=UP)
*frame_map(x=-factor*anchor, z=startdir, reverse=true) * affine3d_zrot(child_abstract_anchor[3])
*rot(v=anchor,-spin), align); * affine3d_yrot(inside?0:180)
* affine3d_zrot(-parent_abstract_anchor[3])
* rot(from=parent_abstract_anchor[2],to=UP)
* rot(v=anchor,-spin),
align);
// The $anchor_override anchor value forces an override of the *position* only for the anchor // The $anchor_override anchor value forces an override of the *position* only for the anchor
// used when attachable() places the child // used when attachable() places the child
$anchor_override = all_zero(child_adjustment)? inside?child:undef $anchor_override = all_zero(child_adjustment)? inside?child:undef
@ -959,8 +1032,11 @@ module attach(parent, child, overlap, align, spin=0, norot, inset=0, shiftout=0,
// inset_dir is the direction for insetting when alignment is in effect // inset_dir is the direction for insetting when alignment is in effect
inset_dir = is_undef(align) ? CTR inset_dir = is_undef(align) ? CTR
: two_d ? rot(to=reference, from=anchor,p=align) : two_d ? rot(to=reference, from=anchor,p=align)
: apply(zrot(-factor*spin)*frame_map(x=reference, z=BACK)*frame_map(x=factor*anchor, z=startdir, reverse=true), : apply(affine3d_yrot(inside?180:0)
align); * affine3d_zrot(-parent_abstract_anchor[3])
* rot(from=parent_abstract_anchor[2],to=UP)
* rot(v=anchor,-spin),
align);
spinaxis = two_d? UP : anchor_dir; spinaxis = two_d? UP : anchor_dir;
olap = - overlap * reference - inset*inset_dir + shiftout * (inset_dir + factor*reference); olap = - overlap * reference - inset*inset_dir + shiftout * (inset_dir + factor*reference);
if (norot || (approx(anchor_dir,reference) && anchor_spin==0)) if (norot || (approx(anchor_dir,reference) && anchor_spin==0))
@ -981,7 +1057,7 @@ module attach(parent, child, overlap, align, spin=0, norot, inset=0, shiftout=0,
// Module: tag() // Module: tag()
// Synopsis: Assigns a tag to an object // Synopsis: Assigns a tag to an object
// Topics: Attachments // Topics: Attachments
// See Also: force_tag(), recolor(), hide(), show_only(), diff(), intersect() // See Also: tag_this(), force_tag(), recolor(), hide(), show_only(), diff(), intersect()
// Usage: // Usage:
// PARENT() tag(tag) CHILDREN; // PARENT() tag(tag) CHILDREN;
// Description: // Description:
@ -1016,6 +1092,40 @@ module tag(tag)
} }
// Module: tag_this()
// Synopsis: Assigns a tag to an object at the current level only.
// Topics: Attachments
// See Also: tag(), force_tag(), recolor(), hide(), show_only(), diff(), intersect()
// Usage:
// PARENT() tag(tag) CHILDREN;
// Description:
// Assigns the specified tag to the children at the current level only, with tags reverting to
// the previous tag in force for deeper descendents. This works using `$tag` and `$save_tag`.
// .
// 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, and saves current tag in `$save_tag`.
// Example(3D): Here we subtract a cube while keeping its child. With {{tag()}} the child would inherit the "remove" tag and we would need to explicitly retag the child to prevent it from also being subtracted.
// diff()
// cuboid([10,10,4])
// tag_this("remove")position(TOP) cuboid(3) // This cube is subtracted
// attach(TOP,BOT) cuboid(1); // Tag is reset so this cube displays
module tag_this(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"));
$save_tag=default($tag,"");
$tag = str($tag_prefix,tag);
children();
}
// Module: force_tag() // Module: force_tag()
// Synopsis: Assigns a tag to a non-attachable object. // Synopsis: Assigns a tag to a non-attachable object.
// Topics: Attachments // Topics: Attachments
@ -1683,6 +1793,40 @@ module hide(tags)
} }
// Module: hide_this()
// Synopsis: Hides attachable children at the current level
// Topics: Attachments
// See Also: hide(), tag_this(), tag(), recolor(), show_only(), show_all(), show_int(), diff(), intersect()
// Usage:
// hide_this() CHILDREN;
// Description:
// Hides all attachable children at the current level, while still displaying descendants.
// For a step-by-step explanation of attachments, see the [Attachments Tutorial](Tutorial-Attachments).
// Side Effects:
// Sets `$tag` and `$save_tag`
// Example: Use an invisible parent to position children. Unlike with {{hide()}} we do not need to explicitly use any tags.
// $fn=16;
// hide_this() cuboid(10)
// {
// attach(RIGHT,BOT) cyl(r=1,h=5);
// attach(LEFT,BOT) cyl(r=1,h=5);
// }
// Example: Nexting applications of hide_this()
// $fn=32;
// hide_this() cuboid(10)
// attach(TOP,BOT) cyl(r=2,h=5)
// hide_this() attach(TOP,BOT) cuboid(4)
// attach(RIGHT,BOT) cyl(r=1,h=2);
module hide_this()
{
tag_scope()
hide("child")
tag_this("child")
children();
}
// Module: show_only() // Module: show_only()
// Synopsis: Show only the children with the listed tags. // Synopsis: Show only the children with the listed tags.
// See Also: tag(), recolor(), show_all(), show_int(), diff(), intersect() // See Also: tag(), recolor(), show_all(), show_int(), diff(), intersect()
@ -1690,7 +1834,7 @@ module hide(tags)
// Usage: // Usage:
// show_only(tags) CHILDREN; // show_only(tags) CHILDREN;
// Description: // 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. // Show only the children with the listed tags, which you supply 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). // For a step-by-step explanation of attachments, see the [Attachments Tutorial](Tutorial-Attachments).
// Side Effects: // Side Effects:
// Sets `$tags_shown` to the tag you specify. // Sets `$tags_shown` to the tag you specify.
@ -1981,18 +2125,18 @@ module face_profile(faces=[], r, d, excess=0.01, convexity=10) {
// cube([50,60,70],center=true) // cube([50,60,70],center=true)
// edge_profile([TOP,"Z"],except=[BACK,TOP+LEFT]) // edge_profile([TOP,"Z"],except=[BACK,TOP+LEFT])
// mask2d_roundover(r=10, inset=2); // mask2d_roundover(r=10, inset=2);
// Example: Using $edge_angle on a Conoid // Example: Using $edge_angle on a conoid
// diff() // diff()
// cyl(d1=50, d2=30, l=40, anchor=BOT) { // cyl(d1=50, d2=30, l=40, anchor=BOT) {
// edge_profile([TOP,BOT], excess=10, convexity=6) { // edge_profile([TOP,BOT], excess=10, convexity=6) {
// mask2d_roundover(r=8, inset=1, excess=1, mask_angle=$edge_angle); // mask2d_roundover(r=8, inset=1, excess=1, mask_angle=$edge_angle);
// } // }
// } // }
// Example: Using $edge_angle on a Prismoid // Example: Using $edge_angle on a prismoid
// diff() // diff()
// prismoid([60,50],[30,20],h=40,shift=[-25,15]) { // prismoid([60,50],[30,20],h=40,shift=[-25,15]) {
// edge_profile(excess=10, convexity=20) { // edge_profile(excess=10, convexity=20) {
// mask2d_roundover(r=5,inset=1,mask_angle=$edge_angle); // mask2d_roundover(r=5,inset=1,mask_angle=$edge_angle,$fn=32);
// } // }
// } // }
@ -2864,11 +3008,23 @@ module attachable(
children(0); children(0);
} }
} }
if (is_def($save_color)) { if (is_def($save_tag) && is_def($save_color)){
$tag=$save_tag;
$save_tag=undef;
$color=$save_color; // Revert to the color before color_this() call $color=$save_color; // Revert to the color before color_this() call
$save_color=undef; $save_color=undef;
children(1); children(1);
} }
else if (is_def($save_color)) {
$color=$save_color; // Revert to the color before color_this() call
$save_color=undef;
children(1);
}
else if (is_def($save_tag)) {
$tag=$save_tag;
$save_tag=undef;
children(1);
}
else children(1); else children(1);
} }
} }
@ -3482,13 +3638,8 @@ function _attach_transform(anchor, spin, orient, geom, p) =
* rot(to=FWD, from=point3d(anch[2])) * rot(to=FWD, from=point3d(anch[2]))
* affine3d_translate(point3d(-pos)) * affine3d_translate(point3d(-pos))
: :
let(
spinT = is_num(spin) ? affine3d_zrot(-anch[3]-spin)
: affine3d_zrot(-spin.z) * affine3d_yrot(-spin.y) * affine3d_xrot(-spin.x)
* affine3d_zrot(-anch[3])
)
affine3d_yrot(180) affine3d_yrot(180)
* spinT * affine3d_zrot(-anch[3]-spin)
* rot(from=anch[2],to=UP) * rot(from=anch[2],to=UP)
* affine3d_translate(point3d(-pos)) * affine3d_translate(point3d(-pos))
) )
@ -3568,7 +3719,7 @@ function _get_cp(geom) =
/// Arguments: /// Arguments:
/// anchor = Vector or named anchor string. /// anchor = Vector or named anchor string.
/// geom = The geometry description of the shape. /// geom = The geometry description of the shape.
function _find_anchor(anchor, geom) = function _find_anchor(anchor, geom)=
is_string(anchor)? ( is_string(anchor)? (
anchor=="origin"? [anchor, CENTER, UP, 0] // Ok that this returns 3d anchor in the 2d case? anchor=="origin"? [anchor, CENTER, UP, 0] // Ok that this returns 3d anchor in the 2d case?
: let( : let(
@ -3596,8 +3747,10 @@ function _find_anchor(anchor, geom) =
let(all_comps_good = [for (c=anchor) if (c!=sign(c)) 1]==[]) let(all_comps_good = [for (c=anchor) if (c!=sign(c)) 1]==[])
assert(all_comps_good, "All components of an anchor for a cuboid/prismoid must be -1, 0, or 1") assert(all_comps_good, "All components of an anchor for a cuboid/prismoid must be -1, 0, or 1")
let( let(
size=geom[1], size2=geom[2], size=geom[1],
shift=point2d(geom[3]), axis=point3d(geom[4]), size2=geom[2],
shift=point2d(geom[3]),
axis=point3d(geom[4]),
override = geom[5](anchor) override = geom[5](anchor)
) )
let( let(
@ -3610,7 +3763,7 @@ function _find_anchor(anchor, geom) =
axy = point2d(anch), axy = point2d(anch),
bot = point3d(v_mul(point2d(size )/2, axy), -h/2), bot = point3d(v_mul(point2d(size )/2, axy), -h/2),
top = point3d(v_mul(point2d(size2)/2, axy) + shift, h/2), top = point3d(v_mul(point2d(size2)/2, axy) + shift, h/2),
edge = top-bot, edge = top-bot,
pos = point3d(cp) + lerp(bot,top,u) + offset, pos = point3d(cp) + lerp(bot,top,u) + offset,
// Find vectors of the faces involved in the anchor // Find vectors of the faces involved in the anchor
facevecs = facevecs =
@ -3619,7 +3772,7 @@ function _find_anchor(anchor, geom) =
if (anch.y!=0) unit(rot(from=UP, to=[0,edge.y,max(0.01,h)], p=[0,axy.y,0]), UP), if (anch.y!=0) unit(rot(from=UP, to=[0,edge.y,max(0.01,h)], p=[0,axy.y,0]), UP),
if (anch.z!=0) unit([0,0,anch.z],UP) if (anch.z!=0) unit([0,0,anch.z],UP)
], ],
dir = anchor==CENTER? UP dir = anch==CENTER? UP
: len(facevecs)==1? unit(facevecs[0],UP) : len(facevecs)==1? unit(facevecs[0],UP)
: len(facevecs)==2? vector_bisect(facevecs[0],facevecs[1]) : len(facevecs)==2? vector_bisect(facevecs[0],facevecs[1])
: let( : let(
@ -3631,39 +3784,67 @@ function _find_anchor(anchor, geom) =
v3 = unit(line[1]-line[0],UP) * anch.z v3 = unit(line[1]-line[0],UP) * anch.z
) )
unit(v3,UP), unit(v3,UP),
final_dir = default(override[1],rot(from=UP, to=axis, p=dir)), edgeang = len(facevecs)==2 ? 180-vector_angle(facevecs[0], facevecs[1]) : undef,
final_dir = default(override[1],anch==CENTER?UP:rot(from=UP, to=axis, p=dir)),
final_pos = default(override[0],rot(from=UP, to=axis, p=pos)), final_pos = default(override[0],rot(from=UP, to=axis, p=pos)),
// If the anchor is on a face or horizontal edge we take the oang value for spin // If the anchor is on a face or horizontal edge we take the oang value for spin
// If the anchor is on a vertical or sloped edge or corner we want to align the spin to point upward along the edge // If the anchor is on a vertical or sloped edge or corner we want to align the spin to point upward along the edge
// The "native" spin direction is the rotation of UP to the anchor direction
// The desired spin direction is the edge vector // Set "vertical" edge and corner anchors point along the edge
// The axis of rotation is the direction vector, so we need component of edge perpendicular to dir spin = anch.x!=0 && anch.y!=0 ? _compute_spin(final_dir, rot(from=UP, to=axis, p=edge))
spin = anchor.x!=0 && anchor.y!=0 ? _compute_spin(dir, edge) //sign(anchor.x)*vector_angle(edge - (edge*dir)*dir/(dir*dir), rot(from=UP,to=dir,p=BACK)) // Horizontal anchors point clockwise
: oang : anch.z!=0 && sum(v_abs(anch))==2 ? _compute_spin(final_dir, rot(from=UP, to=axis, p=anch.z*[anch.y,-anch.x,0]))
) [anchor, final_pos, final_dir, default(override[2],spin)] : norm(anch)==3 ? _compute_spin(final_dir, final_dir==DOWN || final_dir==UP ? BACK : UP)
: oang // face anchors point UP/BACK
) [anchor, final_pos, final_dir, default(override[2],spin), if (is_def(edgeang)) [["edge_angle",edgeang],["edge_length",norm(edge)]]]
) : type == "conoid"? ( //r1, r2, l, shift ) : type == "conoid"? ( //r1, r2, l, shift
assert(anchor.z == sign(anchor.z), "The Z component of an anchor for a cylinder/cone must be -1, 0, or 1")
let( let(
rr1=geom[1], rr2=geom[2], l=geom[3], rr1=geom[1],
shift=point2d(geom[4]), axis=point3d(geom[5]), rr2=geom[2],
length=geom[3],
shift=point2d(geom[4]),
axis=point3d(geom[5]),
r1 = is_num(rr1)? [rr1,rr1] : point2d(rr1), r1 = is_num(rr1)? [rr1,rr1] : point2d(rr1),
r2 = is_num(rr2)? [rr2,rr2] : point2d(rr2), r2 = is_num(rr2)? [rr2,rr2] : point2d(rr2),
anch = rot(from=axis, to=UP, p=anchor), anch = rot(from=axis, to=UP, p=anchor),
axisname = axis==UP ? "Z"
: axis==RIGHT ? "X"
: axis==BACK ? "Y"
: "",
dummy = assert(anch.z == sign(anch.z), str("The ",axisname," component of an anchor for the cylinder/cone must be -1, 0, or 1")),
offset = rot(from=axis, to=UP, p=offset), offset = rot(from=axis, to=UP, p=offset),
u = (anch.z+1)/2, u = (anch.z+1)/2,
// Returns [point,tangent_dir]
solve_ellipse = function (r,dir) approx(dir,[0,0]) ? [[0,0],[0,0]]
: let(
x = r.x*dir.x*r.y / sqrt(dir.x^2*r.y^2+dir.y^2*r.x^2),
y = r.x*dir.y*r.y / sqrt(dir.x^2*r.y^2+dir.y^2*r.x^2)
)
[[x,y], unit([y*r.x^2,-x*r.y^2],CTR)],
on_center = approx(point2d(anch), [0,0]),
botdata = solve_ellipse(r1,point2d(anch)),
topdata = solve_ellipse(r2,point2d(anch)),
bot = point3d(botdata[0], -length/2),
top = point3d(topdata[0], length/2),
tangent = lerp(botdata[1],topdata[1],u),
normal = [-tangent.y,tangent.x],
axy = unit(point2d(anch),[0,0]), axy = unit(point2d(anch),[0,0]),
bot = point3d(v_mul(r1,axy), -l/2), obot = point3d(v_mul(r1,axy), -length/2),
top = point3d(v_mul(r2,axy)+shift, l/2), otop = point3d(v_mul(r2,axy)+shift, length/2),
pos = point3d(cp) + lerp(bot,top,u) + offset, pos = point3d(cp) + lerp(bot,top,u) + offset,
sidevec = rot(from=UP, to=top==bot?UP:top-bot, p=point3d(axy)), sidevec = rot(from=UP, to=top==bot?UP:top-bot, p=point3d(normal)),
vvec = anch==CENTER? UP : unit([0,0,anch.z],UP), vvec = anch==CENTER? UP : unit([0,0,anch.z],UP),
vec = anch==CENTER? CENTER : vec = on_center? unit(anch,UP)
approx(axy,[0,0])? unit(anch,UP) : : approx(anch.z,0)? sidevec
approx(anch.z,0)? sidevec : : unit((sidevec+vvec)/2,UP),
unit((sidevec+vvec)/2,UP),
pos2 = rot(from=UP, to=axis, p=pos), pos2 = rot(from=UP, to=axis, p=pos),
vec2 = anch==CENTER? UP : rot(from=UP, to=axis, p=vec) vec2 = anch==CENTER? UP : rot(from=UP, to=axis, p=vec),
) [anchor, pos2, vec2, oang] // Set spin for top/bottom to be clockwise
spin = anch.z!=0 && (anch.x!=0 || anch.y!=0) ? _compute_spin(vec2,rot(from=UP,to=axis,p=point3d(tangent)*anch.z))
: anch.z==0 && norm(anch)>0 ? _compute_spin(vec2, (vec2==DOWN || vec2==UP)?BACK:UP)
: oang
) [anchor, pos2, vec2, spin]
) : type == "point"? ( ) : type == "point"? (
let( let(
anchor = unit(point3d(anchor),CENTER), anchor = unit(point3d(anchor),CENTER),
@ -3742,24 +3923,60 @@ function _find_anchor(anchor, geom) =
rpts = apply(rot(from=anchor, to=RIGHT) * move(point3d(-cp)), vnf[0]), rpts = apply(rot(from=anchor, to=RIGHT) * move(point3d(-cp)), vnf[0]),
maxx = max(column(rpts,0)), maxx = max(column(rpts,0)),
idxs = [for (i = idx(rpts)) if (approx(rpts[i].x, maxx)) i], idxs = [for (i = idx(rpts)) if (approx(rpts[i].x, maxx)) i],
dir = len(idxs)>2 ? [anchor,oang] // We want to catch the case where the points lie on an edge. The complication is that the edge
: len(idxs)==2 ? // may appear twice WITH DIFFERENT VERTEX INDICES if repeated points appear in the vnf.
let( edges_faces = len(idxs)==2 ? // Simple case, no repeated points, [idxs] gives the edge
edgefaces = _vnf_find_edge_faces(vnf,idxs), approx(vnf[0][idxs[0]],vnf[0][idxs[1]]) ? [] // Are edge points identical?
edge = select(vnf[0],idxs) : let( facelist = _vnf_find_edge_faces(vnf,idxs))
len(facelist)==2 ? [[idxs], facelist] : []
: len(idxs)!=4 ? [] // If we don't have four points it's not an edge pair
: let(
pts = select(vnf[0],idxs),
matchind = [for(i=[1:3]) if (approx(pts[i],pts[0])) i] // indices where actual vertex point is the same as point zero
)
len(matchind)!=1 ? []
: let( // After this runs we have two edges as index pairs, and their associated faces as index values
match1 = select(idxs,[0,matchind[0]]),
match2 = list_remove_values(idxs,match1),
face1 = _vnf_find_edge_faces(vnf,[match1[0],match2[0]]),
face2 = _vnf_find_edge_faces(vnf,[match1[0],match2[1]]),
edge1 = [match1[0], face1==[] ? match2[1] : match2[0]],
edge2 = list_remove_values(idxs,edge1),
face3 = _vnf_find_edge_faces(vnf,edge2),
allfaces = concat(face1,face2,face3)
)
assert(len(allfaces)==2, "Invalid polyhedron encountered while computing VNF anchor")
[[edge1,edge2], allfaces],
dir = len(idxs)>2 && edges_faces==[] ? [anchor,oang]
: edges_faces!=[] ?
let(
faces = edges_faces[1],
edge = select(vnf[0],edges_faces[0][0]),
facenormals = [for(face=faces) polygon_normal(select(vnf[0],vnf[1][face]))],
direction= unit(mean(facenormals)),
projnormals = project_plane(point4d(cross(facenormals[0],facenormals[1])), facenormals),
ang = 180- posmod(v_theta(projnormals[1])-v_theta(projnormals[0]),360),
horiz_face = [for(i=[0:1]) if (approx(v_abs(facenormals[i]),UP)) i],
spin = horiz_face==[] ?
let(
edgedir = edge[1]-edge[0],
nz = [for(i=[0:2]) if (!approx(edgedir[i],0)) i],
flip = edgedir[last(nz)] < 0 ? -1 : 1
)
_compute_spin(direction, flip*edgedir)
:
let(
hedge = len(edges_faces[0])==1 ? edges_faces[0][0]
: edges_faces[0][horiz_face[0]],
face = select(vnf[1],faces[horiz_face[0]]),
edgeind = search([hedge[0]], face)[0],
flip = select(face,edgeind+1)== hedge[1] ? 1 : -1,
edgedir = edge[1]-edge[0]
)
_compute_spin(direction, flip*edgedir)
) )
len(edgefaces)==0 ? [anchor,oang] [direction,spin,[["edge_angle",ang],["edge_length",norm(edge[0]-edge[1])]]]
: assert(len(edgefaces)==2, "Invalid polyhedron encountered while computing VNF anchor") : let( // This section handles corner anchors, currently spins just point up
edge[0]==edge[1] ? [anchor,oang] // two "edge" points are the same, so give up
: let(
direction= unit(mean([for(face=edgefaces) polygon_normal(select(vnf[0],vnf[1][face]))])),
edgedir = edge[1]-edge[0],
nz = [for(i=[0:2]) if (!approx(edgedir[i],0)) i],
flip = edgedir[last(nz)] < 0 ? -1 : 1,
spin = _compute_spin(direction, flip*edgedir)
)
[direction,spin]
: let(
vertices = vnf[0], vertices = vnf[0],
faces = vnf[1], faces = vnf[1],
cornerfaces = _vnf_find_corner_faces(vnf,idxs[0]), // faces = [3,9,12] indicating which faces cornerfaces = _vnf_find_corner_faces(vnf,idxs[0]), // faces = [3,9,12] indicating which faces
@ -3777,7 +3994,7 @@ function _find_anchor(anchor, geom) =
avep = sum(select(rpts,idxs))/len(idxs), avep = sum(select(rpts,idxs))/len(idxs),
mpt = approx(point2d(anchor),[0,0])? [maxx,0,0] : avep, mpt = approx(point2d(anchor),[0,0])? [maxx,0,0] : avep,
pos = point3d(cp) + rot(from=RIGHT, to=anchor, p=mpt) pos = point3d(cp) + rot(from=RIGHT, to=anchor, p=mpt)
) [anchor, default(override[0],pos),default(override[1],dir[0]),default(override[2],dir[1])] ) [anchor, default(override[0],pos),default(override[1],dir[0]),default(override[2],dir[1]),if (len(dir)==3) dir[2]]
) : type == "trapezoid"? ( //size, size2, shift, override ) : type == "trapezoid"? ( //size, size2, shift, override
let(all_comps_good = [for (c=anchor) if (c!=sign(c)) 1]==[]) let(all_comps_good = [for (c=anchor) if (c!=sign(c)) 1]==[])
assert(all_comps_good, "All components of an anchor for a rectangle/trapezoid must be -1, 0, or 1") assert(all_comps_good, "All components of an anchor for a rectangle/trapezoid must be -1, 0, or 1")
@ -4636,10 +4853,20 @@ function _compute_spin(anchor_dir, spin_dir) =
let( let(
native_dir = rot(from=UP, to=anchor_dir, p=BACK), native_dir = rot(from=UP, to=anchor_dir, p=BACK),
spin_dir = spin_dir - (spin_dir*anchor_dir)*anchor_dir, // component of spin_dir perpendicular to anchor_dir spin_dir = spin_dir - (spin_dir*anchor_dir)*anchor_dir, // component of spin_dir perpendicular to anchor_dir
dummy = assert(!approx(spin_dir,[0,0,0]),"spin direction is parallel to anchor"),
angle = vector_angle(native_dir,spin_dir), angle = vector_angle(native_dir,spin_dir),
sign = cross(native_dir,spin_dir)*anchor_dir<0 ? -1 : 1 sign = cross(native_dir,spin_dir)*anchor_dir<0 ? -1 : 1
) )
sign*angle; sign*angle;
// Compute canonical edge direction so that edge is either Z+, Y+ or X+ in that order
function _canonical_edge(edge) =
let(
nz = [for(i=[0:2]) if (!approx(edge[i],0)) i],
flip = edge[last(nz)] < 0 ? -1 : 1
)
flip * edge;
// vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap // vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap

View file

@ -786,7 +786,7 @@ function arc(n, r, angle, d, cp, points, corner, width, thickness, start, wedge=
plane = [corner[2], corner[0], corner[1]], plane = [corner[2], corner[0], corner[1]],
points2d = project_plane(plane, corner) points2d = project_plane(plane, corner)
) )
lift_plane(plane,arc(n,corner=points2d,wedge=wedge,long=long)) lift_plane(plane,arc(n,corner=points2d,wedge=wedge,r=r, d=d))
) : ) :
assert(is_path(corner) && len(corner) == 3) assert(is_path(corner) && len(corner) == 3)
let(col = is_collinear(corner[0],corner[1],corner[2])) let(col = is_collinear(corner[0],corner[1],corner[2]))

View file

@ -223,15 +223,6 @@ include <BOSL2/std.scad>
cube([20,20,40], center=true, spin=45); cube([20,20,40], center=true, spin=45);
``` ```
You can also spin around other axes, or multiple axes at once, by giving 3 angles (in degrees) to
`spin=` as a vector, like [Xang,Yang,Zang]. Similarly to `rotate()`,
the rotations apply in the order given, X-axis spin, then Y-axis, then Z-axis:
```openscad-3D
include <BOSL2/std.scad>
cube([20,20,40], center=true, spin=[10,20,30]);
```
This example shows a cylinder which has been anchored at its FRONT, This example shows a cylinder which has been anchored at its FRONT,
with a rotated copy in gray. The rotation is performed around the with a rotated copy in gray. The rotation is performed around the
origin, but the cylinder is off the origin, so the rotation **does** origin, but the cylinder is off the origin, so the rotation **does**
@ -666,13 +657,13 @@ To show all the standard cardinal anchor points, you can use the [show_anchors()
```openscad-3D;Big ```openscad-3D;Big
include <BOSL2/std.scad> include <BOSL2/std.scad>
cube(40, center=true) cube(20, center=true)
show_anchors(); show_anchors();
``` ```
```openscad-3D;Big ```openscad-3D;Big
include <BOSL2/std.scad> include <BOSL2/std.scad>
cylinder(h=40, d=40, center=true) cylinder(h=25, d=25, center=true)
show_anchors(); show_anchors();
``` ```
@ -687,7 +678,56 @@ For large objects, you can again change the size of the arrows with the `s=` arg
```openscad-3D;Big ```openscad-3D;Big
include <BOSL2/std.scad> include <BOSL2/std.scad>
prismoid(150,60,100) prismoid(150,60,100)
show_anchors(s=35); show_anchors(s=45);
```
Is is also possible to attach to edges and corners of the parent
object. The anchors for edges spin the child so its BACK direction is
aligned with the edge. If the edge belongs to a top or bottom
horizontal face, then the BACK directions will point clockwise around
the face, as seen from outside the shape. (This is the same direction
required for construction of valid faces in OpenSCAD.) Otherwise, the
BACK direction will point upwards.
Examine the red flags below, where only edge anchors appear on a
prismoid. The top face shows the red flags pointing clockwise.
The sloped side edges point along the edges, generally upward, and
the bottom ones appear to point counter-clockwise, but if we viewed
the shape from the bottom they would also appear clockwise.
```openscad-3D;Big
include <BOSL2/std.scad>
prismoid([100,175],[55,88], h=55)
for(i=[-1:1], j=[-1:1], k=[-1:1])
let(anchor=[i,j,k])
if (sum(v_abs(anchor))==2)
attach(anchor,BOT)anchor_arrow(40);
```
In this example cylinders sink half-way into the top edges of the
prismoid:
```openscad-3D;Big
include <BOSL2/std.scad>
$fn=16;
r=6;
prismoid([100,175],[55,88], h=55){
attach([TOP+RIGHT,TOP+LEFT],LEFT,overlap=r/2) cyl(r=r,l=88+2*r,rounding=r);
attach([TOP+FWD,TOP+BACK],LEFT,overlap=r/2) cyl(r=r,l=55+2*r, rounding=r);
}
```
This type of edge attachment is useful for attaching 3d edge masks to
edges:
```openscad-3D;Big
include <BOSL2/std.scad>
$fn=32;
diff()
cuboid(75)
attach([FRONT+LEFT, FRONT+RIGHT, BACK+LEFT, BACK+RIGHT],
FWD+LEFT,inside=true)
rounding_edge_mask(l=76, r1=8,r2=28);
``` ```
## Parent-Child Anchor Attachment (Double Argument Attachment) ## Parent-Child Anchor Attachment (Double Argument Attachment)
@ -2019,7 +2059,8 @@ override the position. If you omit the other list items then the
value drived from the standard anchor will be used. Below we override value drived from the standard anchor will be used. Below we override
position of the FWD anchor: position of the FWD anchor:
``` ```openscad-3D
include<BOSL2/std.scad>
module cubic_barbell(s=100, anchor=CENTER, spin=0, orient=UP) { module cubic_barbell(s=100, anchor=CENTER, spin=0, orient=UP) {
override = [ override = [
[FWD, [[0,-s/8,0]]] [FWD, [[0,-s/8,0]]]
@ -2039,7 +2080,8 @@ Note how the FWD anchor is now rooted on the cylindrical portion. If
you wanted to also change its direction and spin you could do it like you wanted to also change its direction and spin you could do it like
this: this:
``` ```openscad-3D
include<BOSL2/std.scad>
module cubic_barbell(s=100, anchor=CENTER, spin=0, orient=UP) { module cubic_barbell(s=100, anchor=CENTER, spin=0, orient=UP) {
override = [ override = [
[FWD, [[0,-s/8,0], FWD+LEFT, 225]] [FWD, [[0,-s/8,0], FWD+LEFT, 225]]
@ -2062,7 +2104,9 @@ The third entry gives a spin override, whose effect is shown by the
position of the red flag on the arrow. If you want to override all of position of the red flag on the arrow. If you want to override all of
the x=0 anchors to be on the cylinder, with their standard directions, the x=0 anchors to be on the cylinder, with their standard directions,
you can do that by supplying a list: you can do that by supplying a list:
```
```openscad-3D
include<BOSL2/std.scad>
module cubic_barbell(s=100, anchor=CENTER, spin=0, orient=UP) { module cubic_barbell(s=100, anchor=CENTER, spin=0, orient=UP) {
override = [ override = [
for(j=[-1:1:1], k=[-1:1:1]) for(j=[-1:1:1], k=[-1:1:1])
@ -2086,7 +2130,8 @@ the default, or a `[position, direction, spin]` triple to override the
default. As before, you can omit values to keep their default. default. As before, you can omit values to keep their default.
Here is the same example using a function literal for the override: Here is the same example using a function literal for the override:
``` ```openscad-3D
include<BOSL2/std.scad>
module cubic_barbell(s=100, anchor=CENTER, spin=0, orient=UP) { module cubic_barbell(s=100, anchor=CENTER, spin=0, orient=UP) {
override = function (anchor) override = function (anchor)
anchor.x!=0 || anchor==CTR ? undef // Keep these anchor.x!=0 || anchor==CTR ? undef // Keep these