mirror of
https://github.com/BelfrySCAD/BOSL2.git
synced 2024-12-29 16:29:40 +00:00
398 lines
14 KiB
OpenSCAD
398 lines
14 KiB
OpenSCAD
//////////////////////////////////////////////////////////////////////
|
|
// LibFile: attachments.scad
|
|
// This is the file that handles attachments and orientation of children.
|
|
// To use, add the following lines to the beginning of your file:
|
|
// ```
|
|
// include <BOSL2/std.scad>
|
|
// ```
|
|
//////////////////////////////////////////////////////////////////////
|
|
|
|
/*
|
|
BSD 2-Clause License
|
|
|
|
Copyright (c) 2017-2019, Revar Desmera
|
|
All rights reserved.
|
|
|
|
Redistribution and use in source and binary forms, with or without
|
|
modification, are permitted provided that the following conditions are met:
|
|
|
|
* Redistributions of source code must retain the above copyright notice, this
|
|
list of conditions and the following disclaimer.
|
|
|
|
* Redistributions in binary form must reproduce the above copyright notice,
|
|
this list of conditions and the following disclaimer in the documentation
|
|
and/or other materials provided with the distribution.
|
|
|
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
|
|
|
|
// Section: Functions
|
|
|
|
|
|
// Function: anchorpt()
|
|
// Usage:
|
|
// anchor(name, pos, [dir], [rot])
|
|
// Description:
|
|
// Creates a anchor data structure.
|
|
// Arguments:
|
|
// name = The string name of the anchor. Lowercase. Words separated by single dashes. No spaces.
|
|
// pos = The [X,Y,Z] position of the anchor.
|
|
// dir = A vector pointing in the direction parts should project from the anchor position.
|
|
// rot = If needed, the angle to rotate the part around the direction vector.
|
|
function anchorpt(name, pos=[0,0,0], dir=UP, rot=0) = [name, pos, dir, rot];
|
|
|
|
|
|
|
|
// Function: find_anchor()
|
|
// Usage:
|
|
// find_anchor(anchor, h, size, [size2], [shift], [edges], [corners]);
|
|
// Description:
|
|
// Returns anchor data for the given vector or anchor name.
|
|
// Arguments:
|
|
// anchor = Vector or named anchor string.
|
|
// h = Height of the region.
|
|
// size = The [X,Y] size of the bottom of the cubical region.
|
|
// size2 = The [X,Y] size of the top of the cubical region.
|
|
// shift = The [X,Y] amount to shift the center of the top with respect to the center of the bottom.
|
|
// geometry = One of "cube", "cylinder", or "sphere" to denote the overall geometry of the shape. Cones are "cylinder", and prismoids are "cube" for this purpose. Default: "cube"
|
|
// extra_anchors = A list of extra non-standard named anchors.
|
|
function find_anchor(anchor, h, size, size2=undef, shift=[0,0], extra_anchors=[], geometry="cube") =
|
|
is_string(anchor)? (
|
|
let(found = search([anchor], extra_anchors, num_returns_per_match=1)[0])
|
|
assert(found!=[], str("Unknown anchor: ",anchor))
|
|
extra_anchors[found]
|
|
) : (
|
|
let(
|
|
size = point2d(size),
|
|
size2 = (size2!=undef)? point2d(size2) : size,
|
|
shift = point2d(shift),
|
|
oang = (
|
|
anchor == UP? 0 :
|
|
anchor == DOWN? 0 :
|
|
(norm([anchor.x,anchor.y]) < EPSILON)? 0 :
|
|
atan2(anchor.y, anchor.x)+90
|
|
)
|
|
)
|
|
geometry=="sphere"? let(
|
|
phi = (anchor==UP||anchor==CENTER)? 0 : anchor==DOWN? 180 : 90 + (45 * anchor.z),
|
|
theta = anchor==CENTER? 90 : atan2(anchor.y, anchor.x),
|
|
vec = spherical_to_xyz(1, theta, phi),
|
|
pos = anchor==CENTER? CENTER : vmul(vec, (point3d(size)+h*UP)/2)
|
|
) [anchor, pos, vec, oang] : let (
|
|
xyal = (
|
|
geometry=="cylinder"? (
|
|
let(xy = point2d(anchor))
|
|
norm(xy)>0? xy/norm(xy) : [0,0]
|
|
) : point2d(anchor)
|
|
),
|
|
botpt = point3d(vmul(size/2,xyal))+DOWN*h/2,
|
|
toppt = point3d(vmul(size2/2,xyal)+shift)+UP*h/2,
|
|
pos = lerp(botpt, toppt, (anchor.z+1)/2),
|
|
sidevec = rotate_points3d([point3d(xyal)], from=UP, to=toppt-botpt)[0],
|
|
vec = (
|
|
anchor==CENTER? UP :
|
|
norm([anchor.x,anchor.y]) < EPSILON? anchor :
|
|
abs(anchor.z) < EPSILON? sidevec :
|
|
anchor.z>0? (UP+sidevec)/2 :
|
|
(DOWN+sidevec)/2
|
|
)
|
|
) [anchor, pos, vec, oang]
|
|
);
|
|
|
|
|
|
|
|
function _str_char_split(s,delim,n=0,acc=[],word="") =
|
|
(n>=len(s))? concat(acc, [word]) :
|
|
(s[n]==delim)?
|
|
_str_char_split(s,delim,n+1,concat(acc,[word]),"") :
|
|
_str_char_split(s,delim,n+1,acc,str(word,s[n]));
|
|
|
|
|
|
|
|
// Section: Modules
|
|
|
|
|
|
// Module: orient_and_anchor()
|
|
//
|
|
// Description:
|
|
// Takes a vertically oriented shape, and re-orients and anchors it.
|
|
// This is useful for making a custom shape available in various
|
|
// orientations and anchor without extra translate()s and rotate()s.
|
|
// Children should be vertically (Z-axis) oriented, and centered.
|
|
// Non-vector anchor points should be named via the `anchors` arg.
|
|
// Named anchors are translated pre-rotation.
|
|
//
|
|
// Usage:
|
|
// orient_and_anchor(size, [orient], [anchor], [center], [noncentered], [orig_orient], [orig_anchor], [anchors], [chain]) ...
|
|
//
|
|
// Arguments:
|
|
// size = The [X,Y,Z] size of the part.
|
|
// size2 = The [X,Y] size of the top of the part.
|
|
// shift = The [X,Y] offset of the top of the part, compared to the bottom of the part.
|
|
// orient = The axis to orient to. Use `ORIENT_` constants from `constants.scad`.
|
|
// anchor = The side of the part that will be anchored to the origin.
|
|
// center = If given, overrides `anchor`. If true, centers vertically. If false, `anchor` will be set to the value in `noncentered`.
|
|
// noncentered = The value to set `anchor` to if `center` == `false`. Default: `BOTTOM`.
|
|
// orig_orient = The original orientation of the part. Default: `ORIENT_Z`.
|
|
// orig_anchor = The original anchor of the part. Default: `CENTER`.
|
|
// geometry = One of "cube", "cylinder", or "sphere" to denote the overall geometry of the shape. Cones are "cylinder", and prismoids are "cube" for this purpose. Default: "cube"
|
|
// anchors = A list of extra, non-standard optional anchors.
|
|
// chain = If true, allow attachable children.
|
|
//
|
|
// Side Effects:
|
|
// `$parent_size` is set to the parent object's cubical region size.
|
|
// `$parent_size2` is set to the parent object's top [X,Y] size.
|
|
// `$parent_shift` is set to the parent object's `shift` value, if any.
|
|
// `$parent_geom` is set to the parent object's `geometry` value.
|
|
// `$parent_orient` is set to the parent object's `orient` value.
|
|
// `$parent_anchor` is set to the parent object's `anchor` value.
|
|
// `$parent_anchors` is set to the parent object's list of non-standard extra anchors.
|
|
//
|
|
// Example:
|
|
// #cylinder(d=5, h=10);
|
|
// orient_and_anchor([5,5,10], orient=ORIENT_Y, anchor=BACK, orig_anchor=UP) cylinder(d=5, h=10);
|
|
module orient_and_anchor(
|
|
size=undef, orient=ORIENT_Z, anchor=CENTER,
|
|
center=undef, noncentered=BOTTOM,
|
|
orig_orient=ORIENT_Z, orig_anchor=CENTER,
|
|
size2=undef, shift=[0,0],
|
|
anchors=[], chain=false,
|
|
geometry="cube"
|
|
) {
|
|
size2 = point2d(default(size2, size));
|
|
shift = point2d(shift);
|
|
anchor = !is_undef(center)? (center? CENTER : noncentered) : anchor;
|
|
m = matrix4_mult(concat(
|
|
(orig_anchor==CENTER)? [] : [
|
|
// If original anchor is not centered, center it.
|
|
matrix4_translate(vmul(size/2, -orig_anchor))
|
|
],
|
|
(orig_orient==ORIENT_Z)? [] : [
|
|
// If original orientation is not upright, rotate it upright.
|
|
matrix4_zrot(-orig_orient.z),
|
|
matrix4_yrot(-orig_orient.y),
|
|
matrix4_xrot(-orig_orient.x)
|
|
],
|
|
($attach_to!=undef)? (
|
|
let(
|
|
anch = find_anchor($attach_to, size.z, size, size2=size2, shift=shift, geometry=geometry),
|
|
ang = vector_angle(anch[2], DOWN),
|
|
axis = vector_axis(anch[2], DOWN),
|
|
ang2 = (anch[2]==UP || anch[2]==DOWN)? 0 : 180-anch[3],
|
|
axis2 = rotate_points3d([axis],[0,0,ang2])[0]
|
|
) [
|
|
matrix4_translate(-anch[1]),
|
|
matrix4_zrot(ang2),
|
|
matrix4_rot_by_axis(axis2, ang)
|
|
]
|
|
) : concat(
|
|
(anchor==CENTER)? [] : [
|
|
let(anch = find_anchor(anchor, size.z, size, size2=size2, shift=shift, extra_anchors=anchors, geometry=geometry))
|
|
matrix4_translate(-anch[1])
|
|
],
|
|
(orient==ORIENT_Z)? [] : [
|
|
matrix4_xrot(orient.x),
|
|
matrix4_yrot(orient.y),
|
|
matrix4_zrot(orient.z)
|
|
]
|
|
)
|
|
));
|
|
$attach_to = undef;
|
|
$parent_size = size;
|
|
$parent_size2 = size2;
|
|
$parent_shift = shift;
|
|
$parent_geom = geometry;
|
|
$parent_orient = orient;
|
|
$parent_anchor = anchor;
|
|
$parent_anchors = anchors;
|
|
tags = _str_char_split($tags, " ");
|
|
s_tags = $tags_shown;
|
|
h_tags = $tags_hidden;
|
|
shown = !s_tags || any([for (tag=tags) in_list(tag, s_tags)]);
|
|
hidden = any([for (tag=tags) in_list(tag, h_tags)]);
|
|
multmatrix(m) {
|
|
if ($children>1 && chain) {
|
|
if(shown && !hidden) color($color) for (i=[0:$children-2]) children(i);
|
|
children($children-1);
|
|
} else {
|
|
if(shown && !hidden) color($color) children();
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// Module: attach()
|
|
// Usage:
|
|
// attach(name, [overlap], [norot]) ...
|
|
// attach(name, to, [overlap]) ...
|
|
// Description:
|
|
// Attaches children to a parent object at an anchor point and orientation.
|
|
// Arguments:
|
|
// name = The name of the parent anchor point to attach to.
|
|
// to = The name of the child anchor point.
|
|
// overlap = Amount to sink child into the parent.
|
|
// norot = If true, don't rotate children when attaching to the anchor point.
|
|
// Example:
|
|
// spheroid(d=20) {
|
|
// attach(TOP) down(1.5) cyl(l=11.5, d1=10, d2=5, anchor=BOTTOM);
|
|
// attach(RIGHT, BOTTOM) down(1.5) cyl(l=11.5, d1=10, d2=5);
|
|
// attach(FRONT) down(1.5) cyl(l=11.5, d1=10, d2=5, anchor=BOTTOM);
|
|
// }
|
|
module attach(name, to=undef, overlap=undef, norot=false)
|
|
{
|
|
assert($parent_size != undef, "No object to attach to!");
|
|
overlap = (overlap!=undef)? overlap : $overlap;
|
|
anch = find_anchor(name, $parent_size.z, point2d($parent_size), size2=$parent_size2, shift=$parent_shift, extra_anchors=$parent_anchors, geometry=$parent_geom);
|
|
pos = anch[1];
|
|
vec = anch[2];
|
|
ang = anch[3];
|
|
$attach_to = to;
|
|
$attach_anchor = anch;
|
|
if (norot || (norm(vec-UP)<1e-9 && ang==0)) {
|
|
translate(pos) translate([0,0,-overlap]) children();
|
|
} else {
|
|
translate(pos) rot(ang,from=UP,to=vec) translate([0,0,-overlap]) children();
|
|
}
|
|
}
|
|
|
|
|
|
// Module: tags()
|
|
// Usage:
|
|
// tags(tags) ...
|
|
// Description:
|
|
// Marks all children with the given tags.
|
|
// Arguments:
|
|
// tags = String containing space delimited set of tags to apply.
|
|
module tags(tags)
|
|
{
|
|
$tags = tags;
|
|
children();
|
|
}
|
|
|
|
|
|
// Module: recolor()
|
|
// Usage:
|
|
// recolor(c) ...
|
|
// Description:
|
|
// Sets the color for children that can use the $color special variable.
|
|
// Example:
|
|
// recolor("red") cyl(l=20, d=10);
|
|
module recolor(c)
|
|
{
|
|
$color = c;
|
|
children();
|
|
}
|
|
|
|
|
|
// Module: hide()
|
|
// Usage:
|
|
// hide(tags) ...
|
|
// Description: Hides all children with the given tags.
|
|
module hide(tags="")
|
|
{
|
|
$tags_hidden = tags==""? [] : _str_char_split(tags, " ");
|
|
children();
|
|
}
|
|
|
|
|
|
// Module: show()
|
|
// Usage:
|
|
// show(tags) ...
|
|
// Description: Shows only children with the given tags.
|
|
module show(tags="")
|
|
{
|
|
$tags_shown = tags==""? [] : _str_char_split(tags, " ");
|
|
children();
|
|
}
|
|
|
|
|
|
// Module: diff()
|
|
// Usage:
|
|
// diff(neg, [keep]) ...
|
|
// diff(neg, pos, [keep]) ...
|
|
// Description:
|
|
// If `neg` is given, takes the union of all children with tags
|
|
// that are in `neg`, and differences them from the union of all
|
|
// children with tags in `pos`. If `pos` is not given, then all
|
|
// items in `neg` are differenced from all items not in `neg`. If
|
|
// `keep` is given, all children with tags in `keep` are then unioned
|
|
// with the result. If `keep` is not given, all children without
|
|
// tags in `pos` or `neg` are then unioned with the result.
|
|
// Arguments:
|
|
// neg = String containing space delimited set of tag names of children to difference away.
|
|
// pos = String containing space delimited set of tag names of children to be differenced away from.
|
|
// keep = String containing space delimited set of tag names of children to keep whole.
|
|
module diff(neg, pos=undef, keep=undef)
|
|
{
|
|
difference() {
|
|
if (pos != undef) {
|
|
show(pos) children();
|
|
} else {
|
|
if (keep == undef) {
|
|
hide(neg) children();
|
|
} else {
|
|
hide(str(neg," ",keep)) children();
|
|
}
|
|
}
|
|
show(neg) children();
|
|
}
|
|
if (keep!=undef) {
|
|
show(keep) children();
|
|
} else if (pos!=undef) {
|
|
hide(str(pos," ",neg)) children();
|
|
}
|
|
}
|
|
|
|
|
|
// Module: intersect()
|
|
// Usage:
|
|
// intersect(a, [keep]) ...
|
|
// intersect(a, b, [keep]) ...
|
|
// Description:
|
|
// If `a` is given, takes the union of all children with tags that
|
|
// are in `a`, and intersection()s them with the union of all
|
|
// children with tags in `b`. If `b` is not given, then the union
|
|
// of all items with tags in `a` are intersection()ed with the union
|
|
// of all items without tags in `a`. If `keep` is given, then the
|
|
// result is unioned with all the children with tags in `keep`. If
|
|
// `keep` is not given, all children without tags in `a` or `b` are
|
|
// unioned with the result.
|
|
// Arguments:
|
|
// a = String containing space delimited set of tag names of children.
|
|
// b = String containing space delimited set of tag names of children.
|
|
// keep = String containing space delimited set of tag names of children to keep whole.
|
|
module intersect(a, b=undef, keep=undef)
|
|
{
|
|
intersection() {
|
|
if (b != undef) {
|
|
show(b) children();
|
|
} else {
|
|
if (keep == undef) {
|
|
hide(a) children();
|
|
} else {
|
|
hide(str(a," ",keep)) children();
|
|
}
|
|
}
|
|
show(a) children();
|
|
}
|
|
if (keep!=undef) {
|
|
show(keep) children();
|
|
} else if (b!=undef) {
|
|
hide(str(a," ",b)) children();
|
|
}
|
|
}
|
|
|
|
|
|
// vim: noexpandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap
|