2019-04-22 08:08:41 +00:00
//////////////////////////////////////////////////////////////////////
// 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>
// ```
//////////////////////////////////////////////////////////////////////
2019-05-08 05:42:44 +00:00
// Default values for attachment code.
2019-05-26 06:31:05 +00:00
$t ags = "" ;
2019-05-08 05:42:44 +00:00
$ overlap = 0.01 ;
2019-05-26 06:31:05 +00:00
$ color = undef ;
2019-05-08 05:42:44 +00:00
$ attach_to = undef ;
$ attach_anchor = [ CENTER , CENTER , UP , 0 ] ;
$ attach_norot = false ;
2019-05-26 06:31:05 +00:00
$ parent_anchor = BOTTOM ;
2020-02-29 21:16:15 +00:00
$ parent_spin = 0 ;
2019-05-26 06:31:05 +00:00
$ parent_orient = UP ;
2020-02-29 21:16:15 +00:00
$ parent_size = undef ;
$ parent_geom = undef ;
2019-05-08 05:42:44 +00:00
$t ags_shown = [ ] ;
$t ags_hidden = [ ] ;
2019-05-26 06:31:05 +00:00
// Section: Anchors, Spin, and Orientation
// This library adds the concept of anchoring, spin and orientation to the `cube()`, `cylinder()`
// and `sphere()` builtins, as well as to most of the shapes provided by this library itself.
// * An anchor is a place on an object which you can align the object to, or attach other objects
// to using `attach()` or `position()`. An anchor has a position, a direction, and a spin.
// The direction and spin are used to orient other objects to match when using `attach()`.
// * Spin is a simple rotation around the Z axis.
// * Orientation is rotating an object so that its top is pointed towards a given vector.
// An object will first be translated to its anchor position, then spun, then oriented.
2019-05-26 19:47:50 +00:00
//
// ## Anchor
2019-05-26 06:31:05 +00:00
// Anchoring is specified with the `anchor` argument in most shape modules.
// Specifying `anchor` when creating an object will translate the object so
// that the anchor point is at the origin (0,0,0). Anchoring always occurs
// before spin and orientation are applied.
//
// An anchor can be referred to in one of two ways; as a directional vector,
// or as a named anchor string.
//
// When given as a vector, it points, in a general way, towards the face, edge, or
// corner of the object that you want the anchor for, relative to the center of
// the object. There are directional constants named `TOP`, `BOTTOM`, `FRONT`, `BACK`,
// `LEFT`, and `RIGHT` that you can add together to specify an anchor point.
// For example:
// - `[0,0,1]` is the same as `TOP` and refers to the center of the top face.
// - `[-1,0,1]` is the same as `TOP+LEFT`, and refers to the center of the top-left edge.
// - `[1,1,-1]` is the same as `BOTTOM+BACK+RIGHT`, and refers to the bottom-back-right corner.
//
// The components of the directional vector should all be `1`, `0`, or `-1`.
// When the object is cylindrical, conical, or spherical in nature, the anchors will be
// located around the surface of the cylinder, cone, or sphere, relative to the center.
// The direction of a face anchor will be perpendicular to the face, pointing outward.
// The direction of a edge anchor will be the average of the anchor directions of the
// two faces the edge is between. The direction of a corner anchor will be the average
// of the anchor directions of the three faces the corner is on. The spin of all standard
// anchors is 0.
//
// Some more complex objects, like screws and stepper motors, have named anchors
// to refer to places on the object that are not at one of the standard faces, edges
// or corners. For example, stepper motors have anchors for `"screw1"`, `"screw2"`,
// etc. to refer to the various screwholes on the stepper motor shape. The names,
// positions, directions, and spins of these anchors will be specific to the object,
// and will be documented when they exist.
2019-05-26 19:47:50 +00:00
//
// ## Spin
2019-05-26 06:31:05 +00:00
// Spin is specified with the `spin` argument in most shape modules. Specifying `spin`
// when creating an object will rotate the object counter-clockwise around the Z axis
// by the given number of degrees. Spin is always applied after anchoring, and before
// orientation.
2019-05-26 19:47:50 +00:00
//
// ## Orient
2019-05-26 06:31:05 +00:00
// 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 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 added together to form the directional
// vector for this. ie: `LEFT+BACK`
2019-05-08 05:42:44 +00:00
2019-04-22 08:08:41 +00:00
// Section: Functions
2019-04-23 03:55:03 +00:00
// Function: anchorpt()
2019-04-22 08:08:41 +00:00
// Usage:
2019-04-23 03:55:03 +00:00
// anchor(name, pos, [dir], [rot])
2019-04-22 08:08:41 +00:00
// Description:
2019-04-23 03:55:03 +00:00
// Creates a anchor data structure.
2019-04-22 08:08:41 +00:00
// Arguments:
2019-04-23 03:55:03 +00:00
// name = The string name of the anchor. Lowercase. Words separated by single dashes. No spaces.
// pos = The [X,Y,Z] position of the anchor.
2019-05-26 06:31:05 +00:00
// orient = A vector pointing in the direction parts should project from the anchor position.
// spin = If needed, the angle to rotate the part around the direction vector.
function anchorpt ( name , pos = [ 0 , 0 , 0 ] , orient = UP , spin = 0 ) = [ name , pos , orient , spin ] ;
2019-04-22 08:08:41 +00:00
2020-03-06 23:32:53 +00:00
// Function: attach_geom()
//
// Usage:
// geom = attach_geom(anchor, spin, [orient], two_d, size, [size2], [shift], [offset], [anchors]);
// geom = attach_geom(anchor, spin, [orient], two_d, r|d, [offset], [anchors]);
// geom = attach_geom(anchor, spin, [orient], two_d, path, [extent], [offset], [anchors]);
// geom = attach_geom(anchor, spin, [orient], size, [size2], [shift], [offset], [anchors]);
// geom = attach_geom(anchor, spin, [orient], r|d, l, [offset], [anchors]);
// geom = attach_geom(anchor, spin, [orient], r1|d1, r2|d2, l, [offset], [anchors]);
// geom = attach_geom(anchor, spin, [orient], r|d, [offset], [anchors]);
// geom = attach_geom(anchor, spin, [orient], vnf, [extent], [offset], [anchors]);
//
// Description:
// Given arguments that describe the geometry of an attachable object, returns the internal geometry description.
//
// Arguments:
// size = If given as a 3D vector, contains the XY size of the bottom of the cuboidal/prismoidal volume, and the Z height. If given as a 2D vector, contains the front X width of the rectangular/trapezoidal shape, and the Y length.
// size2 = If given as a 2D vector, contains the XY size of the top of the prismoidal volume. If given as a number, contains the back width of the trapezoidal shape.
// shift = If given as a 2D vector, shifts the top of the prismoidal or conical shape by the given amount. If given as a number, shifts the back of the trapezoidal shape right by that amount. Default: No shift.
// r = Radius of the cylindrical/conical volume.
// d = Diameter of the cylindrical/conical volume.
// r1 = Radius of the bottom of the conical volume.
// r2 = Radius of the top of the conical volume.
// d1 = Diameter of the bottom of the conical volume.
// d2 = Diameter of the top of the conical volume.
// l = Length of the cylindrical/conical volume along axis.
// vnf = The [VNF](vnf.scad) of the volume.
// path = The path to generate a polygon from.
// extent = If true, calculate anchors by extents, rather than intersection. Default: false.
// offset = If given, offsets the center of the volume.
// anchors = If given as a list of anchor points, allows named anchor points.
// two_d = If true, the attachable shape is 2D. If false, 3D. Default: false (3D)
//
// Example(NORENDER): Cubical Shape
// geom = attach_geom(anchor, spin, orient, size=size);
//
// Example(NORENDER): Prismoidal Shape
// geom = attach_geom(
// anchor, spin, orient,
// size=point3d(botsize,h),
// size2=topsize, shift=shift
// );
//
// Example(NORENDER): Cylindrical Shape
// geom = attach_geom(anchor, spin, orient, r=r, h=h);
//
// Example(NORENDER): Conical Shape
// geom = attach_geom(anchor, spin, orient, r1=r1, r2=r2, h=h);
//
// Example(NORENDER): Spherical Shape
// geom = attach_geom(anchor, spin, orient, r=r);
//
// Example(NORENDER): Arbitrary VNF Shape
// geom = attach_geom(anchor, spin, orient, vnf=vnf);
//
// Example(NORENDER): 2D Rectangular Shape
// geom = attach_geom(anchor, spin, orient, size=size);
//
// Example(NORENDER): 2D Trapezoidal Shape
// geom = attach_geom(
// anchor, spin, orient,
// size=[x1,y], size2=x2, shift=shift
// );
//
// Example(NORENDER): 2D Circular Shape
// geom = attach_geom(anchor, spin, orient, two_d=true, r=r);
//
// Example(NORENDER): Arbitrary 2D Polygon Shape
// geom = attach_geom(anchor, spin, orient, path=path);
//
function attach_geom (
size , size2 , shift ,
r , r1 , r2 , d , d1 , d2 , l , h ,
vnf , path ,
extent = true ,
offset = [ 0 , 0 , 0 ] ,
anchors = [ ] ,
two_d = false
) =
2020-03-08 06:26:39 +00:00
assert ( is_bool ( extent ) )
2020-03-06 23:32:53 +00:00
assert ( is_vector ( offset ) )
assert ( is_list ( anchors ) )
2020-03-08 06:26:39 +00:00
assert ( is_bool ( two_d ) )
2020-03-06 23:32:53 +00:00
! is_undef ( size ) ? (
two_d ? (
let (
size2 = default ( size2 , size . x ) ,
shift = default ( shift , 0 )
)
2020-03-08 06:26:39 +00:00
assert ( is_vector ( size , 2 ) )
2020-03-06 23:32:53 +00:00
assert ( is_num ( size2 ) )
assert ( is_num ( shift ) )
[ "rect" , point2d ( size ) , size2 , shift , offset , anchors ]
) : (
let (
size2 = default ( size2 , point2d ( size ) ) ,
shift = default ( shift , [ 0 , 0 ] )
)
2020-03-08 06:26:39 +00:00
assert ( is_vector ( size , 3 ) )
assert ( is_vector ( size2 , 2 ) )
assert ( is_vector ( shift , 2 ) )
2020-03-06 23:32:53 +00:00
[ "cuboid" , size , size2 , shift , offset , anchors ]
)
) : ! is_undef ( vnf ) ? (
assert ( is_vnf ( vnf ) )
assert ( two_d = = false )
extent ? [ "vnf_extent" , vnf , offset , anchors ] :
[ "vnf_isect" , vnf , offset , anchors ]
) : ! is_undef ( path ) ? (
2020-03-08 06:26:39 +00:00
assert ( is_path ( path ) , 2 )
2020-03-06 23:32:53 +00:00
assert ( two_d = = true )
extent ? [ "path_extent" , path , offset , anchors ] :
[ "path_isect" , path , offset , anchors ]
) :
let (
r1 = get_radius ( r1 = r1 , d1 = d1 , r = r , d = d , dflt = undef )
)
! is_undef ( r1 ) ? (
assert ( is_num ( r1 ) )
let ( l = default ( l , h ) )
! is_undef ( l ) ? (
let (
shift = default ( shift , [ 0 , 0 ] ) ,
r2 = get_radius ( r1 = r2 , d1 = d2 , r = r , d = d , dflt = undef )
)
assert ( is_num ( l ) )
assert ( is_num ( r2 ) )
2020-03-08 06:26:39 +00:00
assert ( is_vector ( shift , 2 ) )
2020-03-06 23:32:53 +00:00
[ "cyl" , r1 , r2 , l , shift , offset , anchors ]
) : (
two_d ? [ "circle" , r1 , offset , anchors ] :
[ "spheroid" , r1 , offset , anchors ]
)
) :
assert ( false , "Unrecognizable geometry description." ) ;
2020-02-29 21:16:15 +00:00
// Function: attach_geom_2d()
// Usage:
// attach_geom_2d(geom);
// Description:
// Returns true if the given attachment geometry description is for a 2D shape.
function attach_geom_2d ( geom ) =
let ( type = geom [ 0 ] )
type = = "rect" || type = = "circle" ||
type = = "path_isect" || type = = "path_extent" ;
// Function: attach_geom_size()
// Usage:
// attach_geom_size(geom);
// Description:
// Returns the `[X,Y,Z]` bounding size for the given attachment geometry description.
function attach_geom_size ( geom ) =
let ( type = geom [ 0 ] )
type = = "cuboid" ? ( //size, size2, shift
let (
size = geom [ 1 ] , size2 = geom [ 2 ] , shift = point2d ( geom [ 3 ] ) ,
maxx = max ( size . x , size2 . x ) ,
maxy = max ( size . y , size2 . y ) ,
z = size . z
) [ maxx , maxy , z ]
) : type = = "cyl" ? ( //r1, r2, l, shift
let (
r1 = geom [ 1 ] , r2 = geom [ 2 ] , l = geom [ 3 ] , shift = point2d ( geom [ 4 ] ) ,
maxr = max ( r1 , r2 )
) [ 2 * maxr , 2 * maxr , l ]
) : type = = "spheroid" ? ( //r
let ( r = geom [ 1 ] ) [ 2 , 2 , 2 ] * r
) : type = = "vnf_extent" || type = = "vnf_isect" ? ( //vnf
let (
mm = pointlist_bounds ( geom [ 1 ] [ 0 ] ) ,
delt = mm [ 1 ] - mm [ 0 ]
) delt
) : type = = "rect" ? ( //size, size2
let (
size = geom [ 1 ] , size2 = geom [ 2 ] ,
maxx = max ( size . x , size2 )
) [ maxx , size . y ]
) : type = = "circle" ? ( //r
let ( r = geom [ 1 ] ) [ 2 , 2 ] * r
) : type = = "path_isect" || type = = "path_extent" ? ( //path
let (
mm = pointlist_bounds ( geom [ 1 ] ) ,
delt = mm [ 1 ] - mm [ 0 ]
) [ delt . x , delt . y ]
) :
assert ( false , "Unknown attachment geometry type." ) ;
2020-03-06 23:32:53 +00:00
// Function: attach_transform()
// Usage:
// mat = attach_transform(anchor=CENTER, spin=0, orient=UP, geom);
// Description:
// Returns the affine3d transformation matrix needed to `anchor`, `spin`, and `orient`
// the given geometry `geom` shape into position.
// Arguments:
// anchor = Anchor point to translate to the origin `[0,0,0]`. See [anchor](attachments.scad#anchor). Default: `CENTER`
// spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#spin). Default: `0`
// orient = Vector to rotate top towards, after spin. See [orient](attachments.scad#orient). Default: `UP`
// geom = The geometry description of the shape.
// p = If given as a VNF, path, or point, applies the affine3d transformation matrix to it and returns the result.
function attach_transform ( anchor = CENTER , spin = 0 , orient = UP , geom , p ) =
assert ( is_string ( anchor ) || is_vector ( anchor ) )
assert ( is_num ( spin ) )
assert ( is_vector ( orient ) )
let (
two_d = attach_geom_2d ( geom ) ,
m = ( $ attach_to ! = undef ) ? (
let (
anch = find_anchor ( $ attach_to , geom ) ,
pos = anch [ 1 ]
) two_d ? (
let (
ang = vector_angle ( anch [ 2 ] , BACK )
)
affine3d_zrot ( ang + spin ) *
affine3d_translate ( point3d ( - pos ) )
) : (
let (
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 ]
)
affine3d_rot_by_axis ( axis2 , ang ) *
affine3d_zrot ( ang2 + spin ) *
affine3d_translate ( point3d ( - pos ) )
)
) : (
let (
pos = find_anchor ( anchor , geom ) [ 1 ]
) two_d ? (
affine3d_zrot ( spin ) *
affine3d_translate ( point3d ( - pos ) )
) : (
let (
axis = vector_axis ( UP , orient ) ,
ang = vector_angle ( UP , orient )
)
affine3d_rot_by_axis ( axis , ang ) *
affine3d_zrot ( spin ) *
affine3d_translate ( point3d ( - pos ) )
)
)
) is_undef ( p ) ? m :
is_vnf ( p ) ? [ apply ( m , p [ 0 ] ) , p [ 1 ] ] :
apply ( m , p ) ;
2019-04-22 08:08:41 +00:00
2019-04-23 03:55:03 +00:00
// Function: find_anchor()
2019-04-22 08:08:41 +00:00
// Usage:
2020-02-29 21:16:15 +00:00
// find_anchor(anchor, geom);
2019-04-22 08:08:41 +00:00
// Description:
2020-02-29 21:16:15 +00:00
// Calculates the anchor data for the given `anchor` vector or name, in the given attachment
// geometry. Returns `[ANCHOR, POS, VEC, ANG]` where `ANCHOR` is the requested anchorname
// or vector, `POS` is the anchor position, `VEC` is the direction vector of the anchor, and
// `ANG` is the angle to align with around the rotation axis of th anchor direction vector.
2019-04-22 08:08:41 +00:00
// Arguments:
2019-04-23 03:55:03 +00:00
// anchor = Vector or named anchor string.
2020-02-29 21:16:15 +00:00
// geom = The geometry description of the shape.
function find_anchor ( anchor , geom ) =
let (
anchor = point3d ( anchor ) ,
offset = anchor = = CENTER ? CENTER : select ( geom , - 2 ) ,
anchors = select ( geom , - 1 ) ,
type = geom [ 0 ]
)
2019-04-23 03:55:03 +00:00
is_string ( anchor ) ? (
2019-05-26 06:31:05 +00:00
let ( found = search ( [ anchor ] , anchors , num_returns_per_match = 1 ) [ 0 ] )
2019-04-23 03:55:03 +00:00
assert ( found ! = [ ] , str ( "Unknown anchor: " , anchor ) )
2019-05-26 06:31:05 +00:00
anchors [ found ]
2020-02-29 21:16:15 +00:00
) :
assert ( is_vector ( anchor ) , str ( "anchor=" , anchor ) )
anchor = = CENTER ? [ anchor , CENTER , UP , 0 ] :
let (
oang = (
approx ( point2d ( anchor ) , [ 0 , 0 ] ) ? 0 :
atan2 ( anchor . y , anchor . x ) + 90
)
)
type = = "cuboid" ? ( //size, size2, shift
2019-04-22 08:08:41 +00:00
let (
2020-02-29 21:16:15 +00:00
size = geom [ 1 ] , size2 = geom [ 2 ] , shift = point2d ( geom [ 3 ] ) ,
h = size . z ,
u = ( anchor . z + 1 ) / 2 ,
axy = point2d ( anchor ) ,
bot = point3d ( vmul ( point2d ( size ) / 2 , axy ) , - h / 2 ) ,
top = point3d ( vmul ( point2d ( size2 ) / 2 , axy ) + shift , h / 2 ) ,
pos = lerp ( bot , top , u ) + offset ,
2020-03-03 03:30:20 +00:00
sidevec = unit ( rot ( from = UP , to = top - bot , p = point3d ( axy ) ) ) ,
vvec = unit ( [ 0 , 0 , anchor . z ] ) ,
2020-02-29 21:16:15 +00:00
vec = anchor = = CENTER ? UP :
2020-03-03 03:30:20 +00:00
approx ( axy , [ 0 , 0 ] ) ? unit ( anchor ) :
2020-02-29 21:16:15 +00:00
approx ( anchor . z , 0 ) ? sidevec :
2020-03-03 03:30:20 +00:00
unit ( ( sidevec + vvec ) / 2 )
2020-02-29 21:16:15 +00:00
) [ anchor , pos , vec , oang ]
) : type = = "cyl" ? ( //r1, r2, l, shift
let (
r1 = geom [ 1 ] , r2 = geom [ 2 ] , l = geom [ 3 ] , shift = point2d ( geom [ 4 ] ) ,
u = ( anchor . z + 1 ) / 2 ,
2020-03-03 03:30:20 +00:00
axy = unit ( point2d ( anchor ) ) ,
2020-02-29 21:16:15 +00:00
bot = point3d ( r1 * axy , - l / 2 ) ,
top = point3d ( r2 * axy + shift , l / 2 ) ,
pos = lerp ( bot , top , u ) + offset ,
sidevec = rot ( from = UP , to = top - bot , p = point3d ( axy ) ) ,
2020-03-03 03:30:20 +00:00
vvec = unit ( [ 0 , 0 , anchor . z ] ) ,
2020-02-29 21:16:15 +00:00
vec = anchor = = CENTER ? UP :
2020-03-03 03:30:20 +00:00
approx ( axy , [ 0 , 0 ] ) ? unit ( anchor ) :
2020-02-29 21:16:15 +00:00
approx ( anchor . z , 0 ) ? sidevec :
2020-03-03 03:30:20 +00:00
unit ( ( sidevec + vvec ) / 2 )
2020-02-29 21:16:15 +00:00
) [ anchor , pos , vec , oang ]
) : type = = "spheroid" ? ( //r
let (
r = geom [ 1 ]
2020-03-03 03:30:20 +00:00
) [ anchor , r * unit ( anchor ) + offset , unit ( anchor ) , oang ]
2020-02-29 21:16:15 +00:00
) : type = = "vnf_isect" ? ( //vnf
let (
vnf = geom [ 1 ] ,
eps = 1 / 2048 ,
rpts = rot ( from = anchor , to = RIGHT , p = vnf [ 0 ] ) ,
hits = [
for ( i = idx ( vnf [ 1 ] ) ) let (
face = vnf [ 1 ] [ i ] ,
verts = select ( rpts , face )
) if (
max ( subindex ( verts , 0 ) ) >= - eps &&
max ( subindex ( verts , 1 ) ) >= - eps &&
max ( subindex ( verts , 2 ) ) >= - eps &&
min ( subindex ( verts , 1 ) ) < = eps &&
min ( subindex ( verts , 2 ) ) < = eps
) let (
pt = polygon_line_intersection (
select ( vnf [ 0 ] , face ) ,
[ CENTER , anchor ] , eps = eps
)
) if ( ! is_undef ( pt ) ) [ norm ( pt ) , i , pt ]
]
2019-04-22 08:08:41 +00:00
)
2020-02-29 21:16:15 +00:00
assert ( len ( hits ) > 0 , "Anchor vector does not intersect with the shape. Attachment failed." )
let (
furthest = max_index ( subindex ( hits , 0 ) ) ,
pos = hits [ furthest ] [ 2 ] ,
dist = hits [ furthest ] [ 0 ] ,
nfaces = [ for ( hit = hits ) if ( approx ( hit [ 0 ] , dist , eps = eps ) ) hit [ 1 ] ] ,
2020-03-03 03:30:20 +00:00
n = unit (
2020-02-29 21:16:15 +00:00
sum ( [
for ( i = nfaces ) let (
faceverts = select ( vnf [ 0 ] , vnf [ 1 ] [ i ] ) ,
faceplane = plane_from_pointslist ( faceverts ) ,
nrm = plane_normal ( faceplane )
) nrm
] ) / len ( nfaces )
2019-04-22 08:08:41 +00:00
)
2020-02-29 21:16:15 +00:00
)
[ anchor , pos , n , oang ]
) : type = = "vnf_extent" ? ( //vnf
let (
vnf = geom [ 1 ] ,
rpts = rot ( from = anchor , to = RIGHT , p = vnf [ 0 ] ) ,
maxx = max ( subindex ( rpts , 0 ) ) ,
idxs = [ for ( i = idx ( rpts ) ) if ( approx ( rpts [ i ] . x , maxx ) ) i ] ,
mm = pointlist_bounds ( select ( rpts , idxs ) ) ,
avgy = ( mm [ 0 ] . y + mm [ 1 ] . y ) / 2 ,
avgz = ( mm [ 0 ] . z + mm [ 1 ] . z ) / 2 ,
pos = rot ( from = RIGHT , to = anchor , p = [ maxx , avgy , avgz ] )
) [ anchor , pos , anchor , oang ]
) : type = = "rect" ? ( //size, size2
let (
size = geom [ 1 ] , size2 = geom [ 2 ] ,
u = ( anchor . y + 1 ) / 2 ,
frpt = [ size . x / 2 * anchor . x , - size . y / 2 ] ,
bkpt = [ size2 / 2 * anchor . x , size . y / 2 ] ,
pos = lerp ( frpt , bkpt , u ) ,
2020-03-03 03:30:20 +00:00
vec = unit ( rot ( from = BACK , to = bkpt - frpt , p = anchor ) )
2020-02-29 21:16:15 +00:00
) [ anchor , pos , vec , 0 ]
) : type = = "circle" ? ( //r
let (
r = geom [ 1 ] ,
2020-03-03 03:30:20 +00:00
anchor = unit ( point2d ( anchor ) )
2020-02-29 21:16:15 +00:00
) [ anchor , r * anchor + offset , anchor , 0 ]
) : type = = "path_isect" ? ( //path
let (
path = geom [ 1 ] ,
anchor = point2d ( anchor ) ,
isects = [
for ( t = triplet_wrap ( path ) ) let (
seg1 = [ t [ 0 ] , t [ 1 ] ] ,
seg2 = [ t [ 1 ] , t [ 2 ] ] ,
isect = ray_segment_intersection ( [ [ 0 , 0 ] , anchor ] , seg1 ) ,
n = is_undef ( isect ) ? [ 0 , 1 ] :
! approx ( isect , t [ 1 ] ) ? line_normal ( seg1 ) :
2020-03-03 03:30:20 +00:00
unit ( ( line_normal ( seg1 ) + line_normal ( seg2 ) ) / 2 ) ,
2020-02-29 21:16:15 +00:00
n2 = vector_angle ( anchor , n ) > 90 ? - n : n
)
if ( ! is_undef ( isect ) && ! approx ( isect , t [ 0 ] ) ) [ norm ( isect ) , isect , n2 ]
] ,
maxidx = max_index ( subindex ( isects , 0 ) ) ,
isect = isects [ maxidx ] ,
pos = isect [ 1 ] ,
2020-03-03 03:30:20 +00:00
vec = unit ( isect [ 2 ] )
2020-02-29 21:16:15 +00:00
) [ anchor , pos , vec , 0 ]
) : type = = "path_extent" ? ( //path
let (
path = geom [ 1 ] ,
anchor = point2d ( anchor ) ,
rpath = rot ( from = anchor , to = RIGHT , p = path ) ,
maxx = max ( subindex ( rpath , 0 ) ) ,
idxs = [ for ( i = idx ( rpath ) ) if ( approx ( rpath [ i ] . x , maxx ) ) i ] ,
miny = min ( [ for ( i = idxs ) rpath [ i ] . y ] ) ,
maxy = max ( [ for ( i = idxs ) rpath [ i ] . y ] ) ,
avgy = ( miny + maxy ) / 2 ,
pos = rot ( from = RIGHT , to = anchor , p = [ maxx , avgy ] )
) [ anchor , pos , anchor , 0 ]
) :
assert ( false , "Unknown attachment geometry type." ) ;
2019-04-22 08:08:41 +00:00
2020-03-06 23:32:53 +00:00
// Function: attachment_is_shown()
// Usage:
// attachment_is_shown(tags);
// Description:
// Returns true if the given space-delimited string of tag names should currently be shown.
function attachment_is_shown ( tags ) =
let (
tags = _str_char_split ( tags , " " ) ,
shown = ! $t ags_shown || any ( [ for ( tag = tags ) in_list ( tag , $t ags_shown ) ] ) ,
hidden = any ( [ for ( tag = tags ) in_list ( tag , $t ags_hidden ) ] )
) shown && ! hidden ;
2019-04-22 08:08:41 +00:00
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 ] ) ) ;
2020-03-06 23:32:53 +00:00
// Function: reorient()
//
// Usage:
// reorient(anchor, spin, [orient], two_d, size, [size2], [shift], [offset], [anchors], [p]);
// reorient(anchor, spin, [orient], two_d, r|d, [offset], [anchors], [p]);
// reorient(anchor, spin, [orient], two_d, path, [extent], [offset], [anchors], [p]);
// reorient(anchor, spin, [orient], size, [size2], [shift], [offset], [anchors], [p]);
// reorient(anchor, spin, [orient], r|d, l, [offset], [anchors], [p]);
// reorient(anchor, spin, [orient], r1|d1, r2|d2, l, [offset], [anchors], [p]);
// reorient(anchor, spin, [orient], r|d, [offset], [anchors], [p]);
// reorient(anchor, spin, [orient], vnf, [extent], [offset], [anchors], [p]);
//
// Description:
// Given anchor, spin, orient, and general geometry info for a managed volume, this calculates
// the transformation matrix needed to be applied to the contents of that volume. A managed 3D
// volume is assumed to be vertically (Z-axis) oriented, and centered. A managed 2D area is just
// assumed to be centered.
//
// If `p` is not given, then the transformation matrix will be returned.
// If `p` contains a VNF, a new VNF will be returned with the vertices transformed by the matrix.
// If `p` contains a path, a new path will be returned with the vertices transformed by the matrix.
// If `p` contains a point, a new point will be returned, transformed by the matrix.
//
// If `$attach_to` is not defined, then the following transformations are performed in order:
// * Translates so the `anchor` point is at the origin (0,0,0).
// * Rotates around the Z axis by `spin` degrees counter-clockwise.
// * Rotates so the top of the part points towards the vector `orient`.
//
// If `$attach_to` is defined, as a consequence of `attach(from,to)`, then
// the following transformations are performed in order:
// * Translates this part so it's anchor position matches the parent's anchor position.
// * Rotates this part so it's anchor direction vector exactly opposes the parent's anchor direction vector.
// * Rotates this part so it's anchor spin matches the parent's anchor spin.
//
// Arguments:
// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#anchor). Default: `CENTER`
// spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#spin). Default: `0`
// orient = Vector to rotate top towards, after spin. See [orient](attachments.scad#orient). Default: `UP`
// size = If given as a 3D vector, contains the XY size of the bottom of the cuboidal/prismoidal volume, and the Z height. If given as a 2D vector, contains the front X width of the rectangular/trapezoidal shape, and the Y length.
// size2 = If given as a 2D vector, contains the XY size of the top of the prismoidal volume. If given as a number, contains the back width of the trapezoidal shape.
// shift = If given as a 2D vector, shifts the top of the prismoidal or conical shape by the given amount. If given as a number, shifts the back of the trapezoidal shape right by that amount. Default: No shift.
// r = Radius of the cylindrical/conical volume.
// d = Diameter of the cylindrical/conical volume.
// r1 = Radius of the bottom of the conical volume.
// r2 = Radius of the top of the conical volume.
// d1 = Diameter of the bottom of the conical volume.
// d2 = Diameter of the top of the conical volume.
// l = Length of the cylindrical/conical volume along axis.
// vnf = The [VNF](vnf.scad) of the volume.
// path = The path to generate a polygon from.
// extent = If true, calculate anchors by extents, rather than intersection. Default: false.
// offset = If given, offsets the center of the volume.
// anchors = If given as a list of anchor points, allows named anchor points.
// two_d = If true, the attachable shape is 2D. If false, 3D. Default: false (3D)
// p = The VNF, path, or point to transform.
function reorient (
anchor = CENTER ,
spin = 0 ,
orient = UP ,
size , size2 , shift ,
r , r1 , r2 , d , d1 , d2 , l , h ,
vnf , path ,
extent = true ,
offset = [ 0 , 0 , 0 ] ,
anchors = [ ] ,
two_d = false ,
p = undef
) = let (
geom = attach_geom (
size = size , size2 = size2 , shift = shift ,
r = r , r1 = r1 , r2 = r2 , h = h ,
d = d , d1 = d1 , d2 = d2 , l = l ,
vnf = vnf , path = path , extent = extent ,
offset = offset , anchors = anchors ,
two_d = two_d
)
) attach_transform ( anchor , spin , orient , geom , p ) ;
2019-04-22 08:08:41 +00:00
2020-02-29 21:16:15 +00:00
// Section: Attachability Modules
2019-04-22 08:08:41 +00:00
2020-02-29 21:16:15 +00:00
// Module: attachable()
//
// Usage:
// attachable(anchor, spin, [orient], two_d, size, [size2], [shift], [offset], [anchors] ...
// attachable(anchor, spin, [orient], two_d, r|d, [offset], [anchors]) ...
// attachable(anchor, spin, [orient], two_d, path, [extent], [offset], [anchors] ...
// attachable(anchor, spin, [orient], size, [size2], [shift], [offset], [anchors] ...
// attachable(anchor, spin, [orient], r|d, l, [offset], [anchors]) ...
// attachable(anchor, spin, [orient], r1|d1, r2|d2, l, [offset], [anchors]) ...
// attachable(anchor, spin, [orient], r|d, [offset], [anchors]) ...
// attachable(anchor, spin, [orient], vnf, [extent], [offset], [anchors]) ...
2019-04-22 08:08:41 +00:00
//
// Description:
2020-02-29 21:16:15 +00:00
// Manages the anchoring, spin, orientation, and attachments for a 3D volume or 2D area.
// A managed 3D volume is assumed to be vertically (Z-axis) oriented, and centered.
// A managed 2D area is just assumed to be centered. The shape to be managed is given
// as the first child to this module, and the second child should be given as `children()`.
// For example, to manage a conical shape:
// ```openscad
// attachable(anchor, spin, orient, r1=r1, r2=r2, l=h) {
// cyl(r1=r1, r2=r2, l=h);
// children();
// }
// ```
2019-05-26 06:31:05 +00:00
//
// If this is *not* run as a child of `attach()` with the `to` argument
// given, then the following transformations are performed in order:
// * Translates so the `anchor` point is at the origin (0,0,0).
// * Rotates around the Z axis by `spin` degrees counter-clockwise.
// * Rotates so the top of the part points towards the vector `orient`.
//
// If this is called as a child of `attach(from,to)`, then the info
// for the anchor points referred to by `from` and `to` are fetched,
// which will include position, direction, and spin. With that info,
// the following transformations are performed:
// * Translates this part so it's anchor position matches the parent's anchor position.
// * Rotates this part so it's anchor direction vector exactly opposes the parent's anchor direction vector.
// * Rotates this part so it's anchor spin matches the parent's anchor spin.
2019-04-22 08:08:41 +00:00
//
// Arguments:
2019-05-26 19:47:50 +00:00
// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#anchor). Default: `CENTER`
// spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#spin). Default: `0`
// orient = Vector to rotate top towards, after spin. See [orient](attachments.scad#orient). Default: `UP`
2020-02-29 21:16:15 +00:00
// size = If given as a 3D vector, contains the XY size of the bottom of the cuboidal/prismoidal volume, and the Z height. If given as a 2D vector, contains the front X width of the rectangular/trapezoidal shape, and the Y length.
// size2 = If given as a 2D vector, contains the XY size of the top of the prismoidal volume. If given as a number, contains the back width of the trapezoidal shape.
// shift = If given as a 2D vector, shifts the top of the prismoidal or conical shape by the given amount. If given as a number, shifts the back of the trapezoidal shape right by that amount. Default: No shift.
// r = Radius of the cylindrical/conical volume.
// d = Diameter of the cylindrical/conical volume.
// r1 = Radius of the bottom of the conical volume.
// r2 = Radius of the top of the conical volume.
// d1 = Diameter of the bottom of the conical volume.
// d2 = Diameter of the top of the conical volume.
// l = Length of the cylindrical/conical volume along axis.
// vnf = The [VNF](vnf.scad) of the volume.
// path = The path to generate a polygon from.
// extent = If true, calculate anchors by extents, rather than intersection. Default: false.
// offset = If given, offsets the center of the volume.
// anchors = If given as a list of anchor points, allows named anchor points.
// two_d = If true, the attachable shape is 2D. If false, 3D. Default: false (3D)
2019-04-22 08:08:41 +00:00
//
// Side Effects:
2019-04-23 03:55:03 +00:00
// `$parent_anchor` is set to the parent object's `anchor` value.
2020-02-29 21:16:15 +00:00
// `$parent_spin` is set to the parent object's `spin` value.
// `$parent_orient` is set to the parent object's `orient` value.
// `$parent_geom` is set to the parent object's `geom` value.
// `$parent_size` is set to the parent object's cubical `[X,Y,Z]` volume size.
2019-04-22 08:08:41 +00:00
//
2020-02-29 21:16:15 +00:00
// Example(NORENDER): Cubical Shape
// attachable(anchor, spin, orient, size=size) {
// cube(size, center=true);
// children();
// }
//
// Example(NORENDER): Prismoidal Shape
// attachable(
// anchor, spin, orient,
// size=point3d(botsize,h),
// size2=topsize,
// shift=shift
// ) {
// prismoid(botsize, topsize, h=h, shift=shift);
// children();
// }
//
// Example(NORENDER): Cylindrical Shape
// attachable(anchor, spin, orient, r=r, l=h) {
// cyl(r=r, l=h);
// children();
// }
//
// Example(NORENDER): Conical Shape
// attachable(anchor, spin, orient, r1=r1, r2=r2, l=h) {
// cyl(r1=r1, r2=r2, l=h);
// children();
// }
//
// Example(NORENDER): Spherical Shape
// attachable(anchor, spin, orient, r=r) {
// staggered_sphere(r=r);
// children();
// }
//
// Example(NORENDER): Arbitrary VNF Shape
// attachable(anchor, spin, orient, vnf=vnf) {
// vnf_polyhedron(vnf);
// children();
// }
//
// Example(NORENDER): 2D Rectangular Shape
// attachable(anchor, spin, orient, size=size) {
// square(size, center=true);
// children();
// }
//
// Example(NORENDER): 2D Trapezoidal Shape
// attachable(
// anchor, spin, orient,
// size=[x1,y],
// size2=x2,
// shift=shift
// ) {
// trapezoid(w1=x1, w2=x2, h=y, shift=shift);
// children();
// }
//
// Example(NORENDER): 2D Circular Shape
// attachable(anchor, spin, orient, two_d=true, r=r) {
// circle(r=r);
// children();
// }
//
// Example(NORENDER): Arbitrary 2D Polygon Shape
// attachable(anchor, spin, orient, path=path) {
// polygon(path);
// children();
// }
module attachable (
2019-05-26 06:31:05 +00:00
anchor = CENTER ,
spin = 0 ,
2020-02-29 21:16:15 +00:00
orient = UP ,
size , size2 , shift ,
2020-03-06 23:32:53 +00:00
r , r1 , r2 , d , d1 , d2 , l , h ,
2020-02-29 21:16:15 +00:00
vnf , path ,
extent = true ,
2019-06-25 06:53:22 +00:00
offset = [ 0 , 0 , 0 ] ,
2019-05-26 06:31:05 +00:00
anchors = [ ] ,
two_d = false
2019-04-22 08:08:41 +00:00
) {
2020-03-08 06:26:39 +00:00
assert ( $children = = 2 , "attachable() expects exactly two children; the shape to manage, and the union of all attachment candidates." ) ;
assert ( ! is_undef ( anchor ) , str ( "anchor undefined in attachable(). Did you forget to set a default value for anchor in " , parent_module ( 1 ) ) ) ;
assert ( ! is_undef ( spin ) , str ( "spin undefined in attachable(). Did you forget to set a default value for spin in " , parent_module ( 1 ) ) ) ;
assert ( ! is_undef ( orient ) , str ( "orient undefined in attachable(). Did you forget to set a default value for orient in " , parent_module ( 1 ) ) ) ;
2020-03-06 23:32:53 +00:00
geom = attach_geom (
size = size , size2 = size2 , shift = shift ,
r = r , r1 = r1 , r2 = r2 , h = h ,
d = d , d1 = d1 , d2 = d2 , l = l ,
vnf = vnf , path = path , extent = extent ,
offset = offset , anchors = anchors ,
two_d = two_d
) ;
m = attach_transform ( anchor , spin , orient , geom ) ;
multmatrix ( m ) {
if ( attachment_is_shown ( $t ags ) ) {
$ parent_anchor = anchor ;
$ parent_spin = spin ;
$ parent_orient = orient ;
$ parent_geom = geom ;
$ parent_size = attach_geom_size ( geom ) ;
$ attach_to = undef ;
$t ags_shown = undef ;
$t ags_hidden = undef ;
if ( is_undef ( $ color ) ) {
children ( 0 ) ;
} else color ( $ color ) {
$ color = undef ;
children ( 0 ) ;
2019-05-26 06:31:05 +00:00
}
2019-04-22 08:08:41 +00:00
}
2020-03-10 02:35:46 +00:00
children ( 1 ) ;
2019-04-22 08:08:41 +00:00
}
}
2020-02-29 21:16:15 +00:00
// Section: Attachment Positioning
2019-05-26 06:31:05 +00:00
// Module: position()
// Usage:
// position(from, [overlap]) ...
// Description:
// Attaches children to a parent object at an anchor point.
// Arguments:
// from = The vector, or name of the parent anchor point to attach to.
// Example:
// spheroid(d=20) {
// position(TOP) cyl(l=10, d1=10, d2=5, anchor=BOTTOM);
// position(RIGHT) cyl(l=10, d1=10, d2=5, anchor=BOTTOM);
// position(FRONT) cyl(l=10, d1=10, d2=5, anchor=BOTTOM);
// }
2019-08-30 01:43:44 +00:00
module position ( from )
2019-05-26 06:31:05 +00:00
{
2020-02-29 21:16:15 +00:00
assert ( $ parent_geom ! = undef , "No object to attach to!" ) ;
2019-05-26 06:31:05 +00:00
anchors = ( is_vector ( from ) || is_string ( from ) ) ? [ from ] : from ;
for ( anchr = anchors ) {
2020-02-29 21:16:15 +00:00
anch = find_anchor ( anchr , $ parent_geom ) ;
2019-05-26 06:31:05 +00:00
$ attach_to = undef ;
$ attach_anchor = anch ;
$ attach_norot = true ;
translate ( anch [ 1 ] ) children ( ) ;
}
}
2019-04-22 08:08:41 +00:00
// Module: attach()
// Usage:
2019-05-26 06:31:05 +00:00
// attach(from, [overlap]) ...
// attach(from, to, [overlap]) ...
2019-04-22 08:08:41 +00:00
// Description:
2019-04-23 03:55:03 +00:00
// Attaches children to a parent object at an anchor point and orientation.
2020-01-14 03:06:56 +00:00
// Attached objects will be overlapped into the parent object by a little bit,
// as specified by the default `$overlap` value (0.01 by default), or by the
// overriding `overlap=` argument. This is to prevent OpenSCAD from making
// non-manifold objects. You can also define `$overlap=` as an argument in a
// parent module to set the default for all attachments to it.
2019-04-22 08:08:41 +00:00
// Arguments:
2019-05-26 06:31:05 +00:00
// from = The vector, or name of the parent anchor point to attach to.
2019-04-26 09:15:53 +00:00
// to = Optional name of the child anchor point. If given, orients the child such that the named anchors align together rotationally.
2020-01-14 03:06:56 +00:00
// 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.01` by default.
2019-04-26 09:15:53 +00:00
// norot = If true, don't rotate children when attaching to the anchor point. Only translate to the anchor point.
2019-04-22 08:08:41 +00:00
// Example:
// spheroid(d=20) {
2019-04-26 09:15:53 +00:00
// attach(TOP) down(1.5) cyl(l=11.5, d1=10, d2=5, anchor=BOTTOM);
2019-04-22 08:08:41 +00:00
// attach(RIGHT, BOTTOM) down(1.5) cyl(l=11.5, d1=10, d2=5);
2019-04-26 09:15:53 +00:00
// attach(FRONT, BOTTOM, overlap=1.5) cyl(l=11.5, d1=10, d2=5);
2019-04-22 08:08:41 +00:00
// }
2019-05-26 06:31:05 +00:00
module attach ( from , to = undef , overlap = undef , norot = false )
2019-04-22 08:08:41 +00:00
{
2020-02-29 21:16:15 +00:00
assert ( $ parent_geom ! = undef , "No object to attach to!" ) ;
2019-04-22 08:08:41 +00:00
overlap = ( overlap ! = undef ) ? overlap : $ overlap ;
2019-05-26 06:31:05 +00:00
anchors = ( is_vector ( from ) || is_string ( from ) ) ? [ from ] : from ;
for ( anchr = anchors ) {
2020-02-29 21:16:15 +00:00
anch = find_anchor ( anchr , $ parent_geom ) ;
two_d = attach_geom_2d ( $ parent_geom ) ;
2019-05-26 06:31:05 +00:00
$ attach_to = to ;
$ attach_anchor = anch ;
$ attach_norot = norot ;
if ( norot || ( norm ( anch [ 2 ] - UP ) < 1e-9 && anch [ 3 ] = = 0 ) ) {
translate ( anch [ 1 ] ) translate ( [ 0 , 0 , - overlap ] ) children ( ) ;
} else {
2020-02-29 21:16:15 +00:00
fromvec = two_d ? BACK : UP ;
2019-05-26 06:31:05 +00:00
translate ( anch [ 1 ] ) rot ( anch [ 3 ] , from = fromvec , to = anch [ 2 ] ) translate ( [ 0 , 0 , - overlap ] ) children ( ) ;
}
2019-04-22 08:08:41 +00:00
}
}
2020-02-12 04:11:59 +00:00
// Module: edge_profile()
// Usage:
// edge_profile([edges], [except], [convexity]) ...
// Description:
// Takes a 2D mask shape and attaches it to the selected edges, with the appropriate orientation
// and extruded length to be `diff()`ed away, to give the edge a matching profile.
// Arguments:
// edges = Edges to mask. See the docs for [`edges()`](edges.scad#edges) to see acceptable values. Default: All edges.
// except = Edges to explicitly NOT mask. See the docs for [`edges()`](edges.scad#edges) to see acceptable values. Default: No edges.
// convexity = Max number of times a line could intersect the perimeter of the mask shape. Default: 10
// Side Effects:
// Sets `$tags = "mask"` for all children.
// Example:
// diff("mask")
// cube([50,60,70],center=true)
// edge_profile([TOP,"Z"],except=[BACK,TOP+LEFT])
// mask2d_roundover(r=10, inset=2);
module edge_profile ( edges = EDGES_ALL , except = [ ] , convexity = 10 ) {
2020-02-29 21:16:15 +00:00
assert ( $ parent_geom ! = undef , "No object to attach to!" ) ;
2020-02-12 04:11:59 +00:00
edges = edges ( edges , except = except ) ;
vecs = [
for ( i = [ 0 : 3 ] , axis = [ 0 : 2 ] )
if ( edges [ axis ] [ i ] > 0 )
EDGE_OFFSETS [ axis ] [ i ]
] ;
for ( vec = vecs ) {
vcount = ( vec . x ? 1 : 0 ) + ( vec . y ? 1 : 0 ) + ( vec . z ? 1 : 0 ) ;
assert ( vcount = = 2 , "Not an edge vector!" ) ;
2020-02-29 21:16:15 +00:00
anch = find_anchor ( vec , $ parent_geom ) ;
2020-02-12 04:11:59 +00:00
$ attach_to = undef ;
$ attach_anchor = anch ;
$ attach_norot = true ;
$t ags = "mask" ;
length = sum ( vmul ( $ parent_size , [ for ( x = vec ) x ? 0 : 1 ] ) ) + 0.1 ;
rotang =
vec . z < 0 ? [ 90 , 0 , 180 + vang ( point2d ( vec ) ) ] :
vec . z = = 0 && sign ( vec . x ) = = sign ( vec . y ) ? 135 + vang ( point2d ( vec ) ) :
vec . z = = 0 && sign ( vec . x ) ! = sign ( vec . y ) ? [ 0 , 180 , 45 + vang ( point2d ( vec ) ) ] :
[ - 90 , 0 , 180 + vang ( point2d ( vec ) ) ] ;
translate ( anch [ 1 ] ) {
rot ( rotang ) {
linear_extrude ( height = length , center = true , convexity = convexity ) {
children ( ) ;
}
}
}
}
}
// Module: edge_mask()
// Usage:
// edge_mask([edges], [except]) ...
// Description:
// Takes a 3D mask shape, and attaches it to the given edges, with the
// appropriate orientation to be `diff()`ed away.
// Arguments:
// edges = Edges to mask. See the docs for [`edges()`](edges.scad#edges) to see acceptable values. Default: All edges.
// except = Edges to explicitly NOT mask. See the docs for [`edges()`](edges.scad#edges) to see acceptable values. Default: No edges.
// Side Effects:
// Sets `$tags = "mask"` for all children.
// Example:
// diff("mask")
// cube([50,60,70],center=true)
// edge_mask([TOP,"Z"],except=[BACK,TOP+LEFT])
// rounding_mask_z(l=71,r=10);
module edge_mask ( edges = EDGES_ALL , except = [ ] ) {
2020-02-29 21:16:15 +00:00
assert ( $ parent_geom ! = undef , "No object to attach to!" ) ;
2020-02-12 04:11:59 +00:00
edges = edges ( edges , except = except ) ;
vecs = [
for ( i = [ 0 : 3 ] , axis = [ 0 : 2 ] )
if ( edges [ axis ] [ i ] > 0 )
EDGE_OFFSETS [ axis ] [ i ]
] ;
for ( vec = vecs ) {
vcount = ( vec . x ? 1 : 0 ) + ( vec . y ? 1 : 0 ) + ( vec . z ? 1 : 0 ) ;
assert ( vcount = = 2 , "Not an edge vector!" ) ;
2020-02-29 21:16:15 +00:00
anch = find_anchor ( vec , $ parent_geom ) ;
2020-02-12 04:11:59 +00:00
$ attach_to = undef ;
$ attach_anchor = anch ;
$ attach_norot = true ;
$t ags = "mask" ;
rotang =
vec . z < 0 ? [ 90 , 0 , 180 + vang ( point2d ( vec ) ) ] :
vec . z = = 0 && sign ( vec . x ) = = sign ( vec . y ) ? 135 + vang ( point2d ( vec ) ) :
vec . z = = 0 && sign ( vec . x ) ! = sign ( vec . y ) ? [ 0 , 180 , 45 + vang ( point2d ( vec ) ) ] :
[ - 90 , 0 , 180 + vang ( point2d ( vec ) ) ] ;
translate ( anch [ 1 ] ) rot ( rotang ) children ( ) ;
}
}
2020-02-13 08:30:37 +00:00
// Module: corner_mask()
// Usage:
// corner_mask([corners], [except]) ...
// Description:
// Takes a 3D mask shape, and attaches it to the given corners, with the appropriate
// orientation to be `diff()`ed away. The 3D corner mask shape should be designed to
// mask away the X+Y+Z+ octant.
// Arguments:
// corners = Edges to mask. See the docs for [`corners()`](edges.scad#corners) to see acceptable values. Default: All corners.
// except = Edges to explicitly NOT mask. See the docs for [`corners()`](edges.scad#corners) to see acceptable values. Default: No corners.
// Side Effects:
// Sets `$tags = "mask"` for all children.
// Example:
// diff("mask")
// cube(100, center=true)
// corner_mask([TOP,FRONT],LEFT+FRONT+TOP)
// difference() {
// translate(-0.01*[1,1,1]) cube(20);
// translate([20,20,20]) sphere(r=20);
// }
module corner_mask ( corners = CORNERS_ALL , except = [ ] ) {
2020-02-29 21:16:15 +00:00
assert ( $ parent_geom ! = undef , "No object to attach to!" ) ;
2020-02-13 08:30:37 +00:00
corners = corners ( corners , except = except ) ;
vecs = [ for ( i = [ 0 : 7 ] ) if ( corners [ i ] > 0 ) CORNER_OFFSETS [ i ] ] ;
for ( vec = vecs ) {
vcount = ( vec . x ? 1 : 0 ) + ( vec . y ? 1 : 0 ) + ( vec . z ? 1 : 0 ) ;
assert ( vcount = = 3 , "Not an edge vector!" ) ;
2020-02-29 21:16:15 +00:00
anch = find_anchor ( vec , $ parent_geom ) ;
2020-02-13 08:30:37 +00:00
$ attach_to = undef ;
$ attach_anchor = anch ;
$ attach_norot = true ;
$t ags = "mask" ;
rotang = vec . z < 0 ?
[ 0 , 0 , 180 + vang ( point2d ( vec ) ) - 45 ] :
[ 180 , 0 , - 90 + vang ( point2d ( vec ) ) - 45 ] ;
translate ( anch [ 1 ] ) rot ( rotang ) children ( ) ;
}
}
2019-04-22 08:08:41 +00:00
// 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 )
{
$t ags = tags ;
children ( ) ;
}
// Module: recolor()
// Usage:
// recolor(c) ...
// Description:
// Sets the color for children that can use the $color special variable.
2019-04-26 09:15:53 +00:00
// Arguments:
// c = Color name or RGBA vector.
2019-04-22 08:08:41 +00:00
// Example:
// recolor("red") cyl(l=20, d=10);
module recolor ( c )
{
$ color = c ;
children ( ) ;
}
// Module: hide()
// Usage:
// hide(tags) ...
2019-04-26 09:15:53 +00:00
// Description:
// Hides all children with the given tags.
// Example:
// hide("A") cube(50, anchor=CENTER, $tags="Main") {
// attach(LEFT, BOTTOM) cylinder(d=30, l=30, $tags="A");
// attach(RIGHT, BOTTOM) cylinder(d=30, l=30, $tags="B");
// }
2019-04-22 08:08:41 +00:00
module hide ( tags = "" )
{
$t ags_hidden = tags = = "" ? [ ] : _str_char_split ( tags , " " ) ;
children ( ) ;
}
// Module: show()
// Usage:
// show(tags) ...
2019-04-26 09:15:53 +00:00
// Description:
// Shows only children with the given tags.
// Example:
// show("A B") cube(50, anchor=CENTER, $tags="Main") {
// attach(LEFT, BOTTOM) cylinder(d=30, l=30, $tags="A");
// attach(RIGHT, BOTTOM) cylinder(d=30, l=30, $tags="B");
// }
2019-04-22 08:08:41 +00:00
module show ( tags = "" )
{
$t ags_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.
2019-04-26 09:15:53 +00:00
// Example:
// diff("neg", "pos", keep="axle")
// sphere(d=100, $tags="pos") {
2020-02-29 21:16:15 +00:00
// attach(CENTER) xcyl(d=40, l=120, $tags="axle");
2019-04-26 09:15:53 +00:00
// attach(CENTER) cube([40,120,100], anchor=CENTER, $tags="neg");
// }
2019-05-26 06:31:05 +00:00
// Example: Masking
// diff("mask")
// cube([80,90,100], center=true) {
// let(p = $parent_size*1.01, $tags="mask") {
// position([for (y=[-1,1],z=[-1,1]) [0,y,z]])
// rounding_mask_x(l=p.x, r=25);
// position([for (x=[-1,1],z=[-1,1]) [x,0,z]])
// rounding_mask_y(l=p.y, r=20);
// position([for (x=[-1,1],y=[-1,1]) [x,y,0]])
// rounding_mask_z(l=p.z, r=25);
// }
// }
2019-04-22 08:08:41 +00:00
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.
2019-04-26 09:15:53 +00:00
// Example:
// intersect("wheel", "mask", keep="axle")
// sphere(d=100, $tags="wheel") {
// attach(CENTER) cube([40,100,100], anchor=CENTER, $tags="mask");
2020-02-29 21:16:15 +00:00
// attach(CENTER) xcyl(d=40, l=100, $tags="axle");
2019-04-26 09:15:53 +00:00
// }
2019-04-22 08:08:41 +00:00
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 ( ) ;
}
}
2019-05-08 05:42:44 +00:00
2019-05-10 11:03:30 +00:00
// Module: hulling()
// Usage:
// hulling(a, [keep]) ...
// Description:
// Takes the union of all children with tags that are in `a`, and hull()s them.
// 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` are
// unioned with the result.
// Arguments:
// a = String containing space delimited set of tag names of children.
// keep = String containing space delimited set of tag names of children to keep whole.
// Example:
2019-05-13 01:07:47 +00:00
// hulling("body")
2019-05-10 11:03:30 +00:00
// sphere(d=100, $tags="body") {
2019-05-17 21:41:45 +00:00
// attach(CENTER) cube([40,90,90], anchor=CENTER, $tags="body");
2020-02-29 21:16:15 +00:00
// attach(CENTER) xcyl(d=40, l=120, $tags="other");
2019-05-10 11:03:30 +00:00
// }
module hulling ( a )
{
hull ( ) show ( a ) children ( ) ;
children ( ) ;
}
2019-04-22 08:08:41 +00:00
// vim: noexpandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap