mirror of
https://github.com/BelfrySCAD/BOSL2.git
synced 2024-12-29 16:29:40 +00:00
commit
38f9a9b40c
3 changed files with 367 additions and 95 deletions
383
attachments.scad
383
attachments.scad
|
@ -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
|
||||||
|
|
|
@ -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]))
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue