diff --git a/attachments.scad b/attachments.scad index 9a46040..ccec4cb 100644 --- a/attachments.scad +++ b/attachments.scad @@ -89,7 +89,6 @@ $tags_hidden = []; // Section: Functions - // Function: anchorpt() // Usage: // anchor(name, pos, [dir], [rot]) @@ -103,6 +102,142 @@ $tags_hidden = []; function anchorpt(name, pos=[0,0,0], orient=UP, spin=0) = [name, pos, orient, spin]; +// 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 +) = + assert(is_vector(offset)) + assert(is_list(anchors)) + !is_undef(size)? ( + two_d? ( + let( + size2 = default(size2, size.x), + shift = default(shift, 0) + ) + assert(is_vector(size) && len(size)==2) + 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]) + ) + assert(is_vector(size) && len(size)==3) + assert(is_vector(size2) && len(size2)==2) + assert(is_vector(shift) && len(shift)==2) + ["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)? ( + assert(is_path(path)) + 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)) + assert(is_vector(shift) && len(shift)==2) + ["cyl", r1, r2, l, shift, offset, anchors] + ) : ( + two_d? ["circle", r1, offset, anchors] : + ["spheroid", r1, offset, anchors] + ) + ) : + assert(false, "Unrecognizable geometry description."); + + + // Function: attach_geom_2d() // Usage: // attach_geom_2d(geom); @@ -156,6 +291,65 @@ function attach_geom_size(geom) = assert(false, "Unknown attachment geometry type."); +// 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); + // Function: find_anchor() // Usage: @@ -325,6 +519,18 @@ function find_anchor(anchor, geom) = assert(false, "Unknown attachment geometry type."); +// 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 = !$tags_shown || any([for (tag=tags) in_list(tag, $tags_shown)]), + hidden = any([for (tag=tags) in_list(tag, $tags_hidden)]) + ) shown && !hidden; + function _str_char_split(s,delim,n=0,acc=[],word="") = (n>=len(s))? concat(acc, [word]) : @@ -333,10 +539,88 @@ function _str_char_split(s,delim,n=0,acc=[],word="") = _str_char_split(s,delim,n+1,acc,str(word,s[n])); +// 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); + + // Section: Attachability Modules - // Module: attachable() // // Usage: @@ -478,7 +762,7 @@ module attachable( spin=0, orient=UP, size, size2, shift, - r,r1,r2, d,d1,d2, l, + r,r1,r2, d,d1,d2, l,h, vnf, path, extent=true, offset=[0,0,0], @@ -486,110 +770,32 @@ module attachable( two_d=false ) { assert($children==2); - assert(is_string(anchor) || is_vector(anchor)); - assert(is_num(spin)); - assert(is_vector(orient)); - assert(is_vector(offset)); - assert(is_list(anchors)); + 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($tags)) { + $parent_anchor = anchor; + $parent_spin = spin; + $parent_orient = orient; + $parent_geom = geom; + $parent_size = attach_geom_size(geom); - geom = !is_undef(size)? ( - two_d? ( - let( - size2 = default(size2, size.x), - shift = default(shift, 0) - ) - assert(is_vector(size) && len(size)==2) - 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]) - ) - assert(is_vector(size) && len(size)==3) - assert(is_vector(size2) && len(size2)==2) - assert(is_vector(shift) && len(shift)==2) - ["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)? ( - assert(is_path(path)) - 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)) - !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)) - assert(is_vector(shift) && len(shift)==2) - ["cyl", r1, r2, l, shift, offset, anchors] - ) : ( - two_d? ["circle", r1, offset, anchors] : - ["spheroid", r1, offset, anchors] - ) - ) : - assert(false, "attachable(): Unrecognizable geometry description."); + $attach_to = undef; + $tags_shown = undef; + $tags_hidden = undef; - pos = find_anchor(anchor, geom)[1]; - size = attach_geom_size(geom); - - $parent_anchor = anchor; - $parent_spin = spin; - $parent_orient = orient; - $parent_geom = geom; - $parent_size = size; - - 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)]); - if ($attach_to != undef) { - anch = find_anchor($attach_to, geom); - ang = vector_angle(anch[2], two_d? BACK : DOWN); - axis = two_d? UP : vector_axis(anch[2], DOWN); - ang2 = (anch[2]==UP || anch[2]==DOWN)? 0 : 180-anch[3]; - axis2 = rotate_points3d([axis],[0,0,ang2])[0]; - $attach_to = undef; - - rot(ang, v=axis2) - rotate(ang2+spin) - translate(-anch[1]) { - if(shown && !hidden) { - if (is_undef($color)) { - children(0); - } else color($color) { - $color = undef; - children(0); - } - } - children(1); - } - } else { - rot(from=UP,to=orient) - rotate(spin) - translate(-pos) { - if(shown && !hidden) { - if (is_undef($color)) { - children(0); - } else color($color) { - $color = undef; - children(0); - } + if (is_undef($color)) { + children(0); + } else color($color) { + $color = undef; + children(0); } children(1); } @@ -600,7 +806,6 @@ module attachable( // Section: Attachment Positioning - // Module: position() // Usage: // position(from, [overlap]) ... diff --git a/involute_gears.scad b/involute_gears.scad index 22fd4c0..89c29c8 100644 --- a/involute_gears.scad +++ b/involute_gears.scad @@ -241,6 +241,8 @@ module gear_tooth_profile( // clearance = Gap between top of a tooth on one gear and bottom of valley on a meshing gear (in millimeters) // backlash = Gap between two meshing teeth, in the direction along the circumference of the pitch circle // interior = If true, create a mask for difference()ing from something else. +// 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` // Example(2D): Typical Gear Shape // gear2d(pitch=5, teeth=20); // Example(2D): Lower Pressure Angle @@ -254,8 +256,11 @@ function gear2d( PA = 28, clearance = undef, backlash = 0.0, - interior = false + interior = false, + anchor = CENTER, + spin = 0 ) = let( + pr = pitch_radius(pitch=pitch, teeth=teeth), pts = concat( [for (tooth = [0:1:teeth-hide-1]) each rot(tooth*360/teeth, @@ -273,7 +278,7 @@ function gear2d( ], hide>0? [[0,0]] : [] ) -) pts; +) attachable(anchor,spin, two_d=true, r=pr, p=pts); module gear2d( @@ -283,19 +288,23 @@ module gear2d( PA = 28, clearance = undef, backlash = 0.0, - interior = false + interior = false, + anchor = CENTER, + spin = 0 ) { - polygon( - gear2d( - pitch = pitch, - teeth = teeth, - hide = hide, - PA = PA, - clearance = clearance, - backlash = backlash, - interior = interior - ) + path = gear2d( + pitch = pitch, + teeth = teeth, + hide = hide, + PA = PA, + clearance = clearance, + backlash = backlash, + interior = interior ); + attachable(anchor,spin, two_d=true, r=pr) { + polygon(path); + children(); + } } diff --git a/primitives.scad b/primitives.scad index 4b0776e..44424ea 100644 --- a/primitives.scad +++ b/primitives.scad @@ -60,7 +60,8 @@ function square(size=1, center, rounding=0, chamfer=0, anchor, spin=0) = assert(is_num(rounding) || len(rounding)==4) let( size = is_num(size)? [size,size] : point2d(size), - anchor = get_anchor(anchor, center, FRONT+LEFT, FRONT+LEFT) + anchor = get_anchor(anchor, center, FRONT+LEFT, FRONT+LEFT), + complex = rounding!=0 || chamfer!=0 ) (rounding==0 && chamfer==0)? let( path = [ @@ -97,7 +98,9 @@ function square(size=1, center, rounding=0, chamfer=0, anchor, spin=0) = ) each [for (a = angs) cp + inset*[cos(a),sin(a)]] ] - ) rot(spin, p=move(-vmul(anchor,size/2), p=path)); + ) complex? + reorient(anchor,spin, two_d=true, path=path, p=path) : + reorient(anchor,spin, two_d=true, size=size, p=path); // Function&Module: circle() @@ -142,7 +145,7 @@ function circle(r, d, realign=false, circum=false, anchor=CENTER, spin=0) = offset = realign? 180/sides : 0, rr = r / (circum? cos(180/sides) : 1), pts = [for (i=[0:1:sides-1]) let(a=360-offset-i*360/sides) rr*[cos(a),sin(a)]] - ) rot(spin, p=move(-unit(anchor)*rr, p=pts)); + ) reorient(anchor,spin, two_d=true, r=rr, p=pts); @@ -235,7 +238,6 @@ module cylinder(h, r1, r2, center, l, r, d, d1, d2, anchor, spin=0, orient=UP) l = first_defined([h, l, 1]); hh = l/2; sides = segs(max(r1,r2)); - size = [r1*2, r1*2, l]; path = [[0,hh],[r2,hh],[r1,-hh],[0,-hh]]; attachable(anchor,spin,orient, r1=r1, r2=r2, l=l) { rotate_extrude(convexity=2, $fn=sides) { @@ -275,7 +277,6 @@ module sphere(r, d, anchor=CENTER, spin=0, orient=UP) { r = get_radius(r=r, d=d, dflt=1); sides = segs(r); - size = [r*2, r*2, r*2]; attachable(anchor,spin,orient, r=r) { rotate_extrude(convexity=2) { difference() { diff --git a/shapes2d.scad b/shapes2d.scad index 5ba8578..a51cc1e 100644 --- a/shapes2d.scad +++ b/shapes2d.scad @@ -562,13 +562,11 @@ function _turtle_command(command, parm, parm2, state, index) = rot(delta_angle,p=state[step],planar=true) ] ) : - - - assert(false,str("Unknown turtle command \"",command,"\" at index",index)) []; + // Section: 2D N-Gons // Function&Module: regular_ngon() @@ -625,7 +623,7 @@ function regular_ngon(n=6, r, d, or, od, ir, id, side, rounding=0, realign=false (r-rounding*sc)*[cos(a),sin(a)] + rounding*[cos(b),sin(b)] ] - ) rot(spin, p=move(-r*unit(anchor), p=path)); + ) reorient(anchor,spin, two_d=true, path=path, extent=false, p=path); module regular_ngon(n=6, r, d, or, od, ir, id, side, rounding=0, realign=false, anchor=CENTER, spin=0) { @@ -643,8 +641,8 @@ module regular_ngon(n=6, r, d, or, od, ir, id, side, rounding=0, realign=false, // Function&Module: pentagon() // Usage: // pentagon(or|od, [realign]); -// pentagon(ir|id, [realign]; -// pentagon(side, [realign]; +// pentagon(ir|id, [realign]); +// pentagon(side, [realign]); // Description: // When called as a function, returns a 2D path for a regular pentagon. // When called as a module, creates a 2D regular pentagon. @@ -786,14 +784,13 @@ module octagon(r, d, or, od, ir, id, side, rounding=0, realign=false, anchor=CEN // stroke(closed=true, trapezoid(h=30, w1=40, w2=20)); function trapezoid(h, w1, w2, anchor=CENTER, spin=0) = let( - s = anchor.y>0? [w2,h] : anchor.y<0? [w1,h] : [(w1+w2)/2,h], path = [[w1/2,-h/2], [-w1/2,-h/2], [-w2/2,h/2], [w2/2,h/2]] - ) rot(spin, p=move(-vmul(anchor,s/2), p=path)); + ) reorient(anchor,spin, two_d=true, size=[w1,h], size2=w2, p=path); module trapezoid(h, w1, w2, anchor=CENTER, spin=0) { - path = trapezoid(h=h, w1=w1, w2=w2); + path = [[w1/2,-h/2], [-w1/2,-h/2], [-w2/2,h/2], [w2/2,h/2]]; attachable(anchor,spin, two_d=true, size=[w1,h], size2=w2) { polygon(path); children(); @@ -846,12 +843,14 @@ function teardrop2d(r, d, ang=45, cap_h, anchor=CENTER, spin=0) = ea = 360 + ang, steps = segs(r)*(ea-sa)/360, step = (ea-sa)/steps, - path = concat( - [[ cap_w/2,cap_h]], - [for (i=[0:1:steps]) let(a=ea-i*step) r*[cos(a),sin(a)]], - [[-cap_w/2,cap_h]] + path = deduplicate( + [ + [ cap_w/2,cap_h], + for (i=[0:1:steps]) let(a=ea-i*step) r*[cos(a),sin(a)], + [-cap_w/2,cap_h] + ], closed=true ) - ) rot(spin, p=move(-vmul(anchor,[r,cap_h]), p=deduplicate(path,closed=true))); + ) reorient(anchor,spin, two_d=true, path=path, p=path); @@ -891,14 +890,13 @@ function glued_circles(r, d, spread=10, tangent=30, anchor=CENTER, spin=0) = subarc = ea2-sa2, arcsegs = ceil(segs(r2)*abs(subarc)/360), arcstep = subarc / arcsegs, - s = [spread/2+r, r], path = concat( [for (i=[0:1:lobesegs]) let(a=sa1+i*lobestep) r * [cos(a),sin(a)] - cp1], tangent==0? [] : [for (i=[0:1:arcsegs]) let(a=ea2-i*arcstep+180) r2 * [cos(a),sin(a)] - cp2], [for (i=[0:1:lobesegs]) let(a=sa1+i*lobestep+180) r * [cos(a),sin(a)] + cp1], tangent==0? [] : [for (i=[0:1:arcsegs]) let(a=ea2-i*arcstep) r2 * [cos(a),sin(a)] + cp2] ) - ) rot(spin, p=move(-vmul(anchor,s), p=path)); + ) reorient(anchor,spin, two_d=true, path=path, extent=true, p=path); module glued_circles(r, d, spread=10, tangent=30, anchor=CENTER, spin=0) { @@ -951,12 +949,12 @@ function star(n, r, d, or, od, ir, id, step, realign=false, anchor=CENTER, spin= ir = get_radius(r=ir, d=id, dflt=stepr), offset = 90+(realign? 180/n : 0), path = [for(i=[0:1:2*n-1]) let(theta=180*i/n+offset, radius=(i%2)?ir:r) radius*[cos(theta), sin(theta)]] - ) rot(spin, p=move(-r*unit(anchor), p=path)); + ) reorient(anchor,spin, two_d=true, path=path, p=path); module star(n, r, d, or, od, ir, id, step, realign=false, anchor=CENTER, spin=0) { path = star(n=n, r=r, d=d, od=od, or=or, ir=ir, id=id, step=step, realign=realign); - attachable(anchor,spin, two_d=true, path=path, extent=true) { + attachable(anchor,spin, two_d=true, path=path) { polygon(path); children(); } @@ -1023,11 +1021,11 @@ function supershape(step=0.5,m1=4,m2=undef,n1=1,n2=undef,n3=undef,a=1,b=undef,r= rads = [for (theta = angs) _superformula(theta=theta,m1=m1,m2=m2,n1=n1,n2=n2,n3=n3,a=a,b=b)], scale = is_def(r) ? r/max(rads) : 1, path = [for (i = [0:steps-1]) let(a=angs[i]) scale*rads[i]*[cos(a), sin(a)]] - ) rot(spin, p=move(-scale*max(rads)*unit(anchor), p=path)); + ) reorient(anchor,spin, two_d=true, path=path, p=path); module supershape(step=0.5,m1=4,m2=undef,n1,n2=undef,n3=undef,a=1,b=undef, r=undef, d=undef, anchor=CENTER, spin=0) { path = supershape(step=step,m1=m1,m2=m2,n1=n1,n2=n2,n3=n3,a=a,b=b,r=r,d=d); - attachable(anchor,spin, two_d=true, path=path, extent=true) { + attachable(anchor,spin, two_d=true, path=path) { polygon(path); children(); } @@ -1057,11 +1055,15 @@ module supershape(step=0.5,m1=4,m2=undef,n1,n2=undef,n3=undef,a=1,b=undef, r=und // cube([50,60,70],center=true) // edge_profile([TOP,"Z"],except=[BACK,TOP+LEFT]) // mask2d_roundover(r=10, inset=2); -module mask2d_roundover(r, d, excess, inset=0) { - polygon(mask2d_roundover(r=r,d=d,excess=excess,inset=inset)); +module mask2d_roundover(r, d, excess, inset=0, anchor=CENTER,spin=0) { + path = mask2d_roundover(r=r,d=d,excess=excess,inset=inset); + attachable(anchor,spin, two_d=true, path=path, p=path) { + polygon(path); + children(); + } } -function mask2d_roundover(r, d, excess, inset=0) = +function mask2d_roundover(r, d, excess, inset=0, anchor=CENTER,spin=0) = assert(is_num(r)||is_num(d)) assert(is_undef(excess)||is_num(excess)) assert(is_num(inset)||(is_vector(inset)&&len(inset)==2)) @@ -1070,12 +1072,13 @@ function mask2d_roundover(r, d, excess, inset=0) = excess = default(excess,$overlap), r = get_radius(r=r,d=d,dflt=1), steps = quantup(segs(r),4)/4, - step = 90/steps - ) [ - [-excess,-excess], [-excess, r+inset.y], - for (i=[0:1:steps]) [r,r] + inset + polar_to_xy(r,180+i*step), - [r+inset.x,-excess] - ]; + step = 90/steps, + path = [ + [-excess,-excess], [-excess, r+inset.y], + for (i=[0:1:steps]) [r,r] + inset + polar_to_xy(r,180+i*step), + [r+inset.x,-excess] + ] + ) reorient(anchor,spin, two_d=true, path=path, extent=false, p=path); // Function&Module: mask2d_cove() @@ -1099,11 +1102,15 @@ function mask2d_roundover(r, d, excess, inset=0) = // cube([50,60,70],center=true) // edge_profile([TOP,"Z"],except=[BACK,TOP+LEFT]) // mask2d_cove(r=10, inset=2); -module mask2d_cove(r, d, inset=0, excess) { - polygon(mask2d_cove(r=r,d=d,excess=excess,inset=inset)); +module mask2d_cove(r, d, inset=0, excess, anchor=CENTER,spin=0) { + path = mask2d_cove(r=r,d=d,excess=excess,inset=inset); + attachable(anchor,spin, two_d=true, path=path) { + polygon(path); + children(); + } } -function mask2d_cove(r, d, inset=0, excess) = +function mask2d_cove(r, d, inset=0, excess, anchor=CENTER,spin=0) = assert(is_num(r)||is_num(d)) assert(is_undef(excess)||is_num(excess)) assert(is_num(inset)||(is_vector(inset)&&len(inset)==2)) @@ -1112,12 +1119,13 @@ function mask2d_cove(r, d, inset=0, excess) = excess = default(excess,$overlap), r = get_radius(r=r,d=d,dflt=1), steps = quantup(segs(r),4)/4, - step = 90/steps - ) [ - [-excess,-excess], [-excess, r+inset.y], - for (i=[0:1:steps]) inset + polar_to_xy(r,90-i*step), - [r+inset.x,-excess] - ]; + step = 90/steps, + path = [ + [-excess,-excess], [-excess, r+inset.y], + for (i=[0:1:steps]) inset + polar_to_xy(r,90-i*step), + [r+inset.x,-excess] + ] + ) reorient(anchor,spin, two_d=true, path=path, p=path); // Function&Module: mask2d_chamfer() @@ -1147,11 +1155,15 @@ function mask2d_cove(r, d, inset=0, excess) = // cube([50,60,70],center=true) // edge_profile([TOP,"Z"],except=[BACK,TOP+LEFT]) // mask2d_chamfer(x=10, inset=2); -module mask2d_chamfer(x, y, edge, angle=45, excess, inset=0) { - polygon(mask2d_chamfer(x=x, y=y, edge=edge, angle=angle, excess=excess, inset=inset)); +module mask2d_chamfer(x, y, edge, angle=45, excess, inset=0, anchor=CENTER,spin=0) { + path = mask2d_chamfer(x=x, y=y, edge=edge, angle=angle, excess=excess, inset=inset); + attachable(anchor,spin, two_d=true, path=path, extent=true) { + polygon(path); + children(); + } } -function mask2d_chamfer(x, y, edge, angle=45, excess, inset=0) = +function mask2d_chamfer(x, y, edge, angle=45, excess, inset=0, anchor=CENTER,spin=0) = assert(num_defined([x,y,edge])==1) assert(is_num(first_defined([x,y,edge]))) assert(is_num(angle)) @@ -1163,12 +1175,13 @@ function mask2d_chamfer(x, y, edge, angle=45, excess, inset=0) = x = !is_undef(x)? x : !is_undef(y)? adj_ang_to_opp(adj=y,ang=angle) : hyp_ang_to_opp(hyp=edge,ang=angle), - y = opp_ang_to_adj(opp=x,ang=angle) - ) [ - [-excess, -excess], [-excess, y+inset.y], - [inset.x, y+inset.y], [x+inset.x, inset.y], - [x+inset.x, -excess] - ]; + y = opp_ang_to_adj(opp=x,ang=angle), + path = [ + [-excess, -excess], [-excess, y+inset.y], + [inset.x, y+inset.y], [x+inset.x, inset.y], + [x+inset.x, -excess] + ] + ) reorient(anchor,spin, two_d=true, path=path, extent=true, p=path); // Function&Module: mask2d_rabbet() @@ -1191,19 +1204,25 @@ function mask2d_chamfer(x, y, edge, angle=45, excess, inset=0) = // cube([50,60,70],center=true) // edge_profile([TOP,"Z"],except=[BACK,TOP+LEFT]) // mask2d_rabbet(size=10); -module mask2d_rabbet(size, excess) { - polygon(mask2d_rabbet(size=size, excess=excess)); +module mask2d_rabbet(size, excess, anchor=CENTER,spin=0) { + path = mask2d_rabbet(size=size, excess=excess); + attachable(anchor,spin, two_d=true, path=path, extent=false, p=path) { + polygon(path); + children(); + } } -function mask2d_rabbet(size, excess) = +function mask2d_rabbet(size, excess, anchor=CENTER,spin=0) = assert(is_num(size)||(is_vector(size)&&len(size)==2)) assert(is_undef(excess)||is_num(excess)) let( excess = default(excess,$overlap), - size = is_list(size)? size : [size,size] - ) [ - [-excess, -excess], [-excess, size.y], size, [size.x, -excess] - ]; + size = is_list(size)? size : [size,size], + path = [ + [-excess, -excess], [-excess, size.y], + size, [size.x, -excess] + ] + ) reorient(anchor,spin, two_d=true, path=path, extent=false, p=path); // Function&Module: mask2d_dovetail() @@ -1234,11 +1253,15 @@ function mask2d_rabbet(size, excess) = // cube([50,60,70],center=true) // edge_profile([TOP,"Z"],except=[BACK,TOP+LEFT]) // mask2d_dovetail(x=10, inset=2); -module mask2d_dovetail(x, y, edge, angle=30, inset=0, shelf=0, excess) { - polygon(mask2d_dovetail(x=x, y=y, edge=edge, angle=angle, inset=inset, shelf=shelf, excess=excess)); +module mask2d_dovetail(x, y, edge, angle=30, inset=0, shelf=0, excess, anchor=CENTER, spin=0) { + path = mask2d_dovetail(x=x, y=y, edge=edge, angle=angle, inset=inset, shelf=shelf, excess=excess); + attachable(anchor,spin, two_d=true, path=path) { + polygon(path); + children(); + } } -function mask2d_dovetail(x, y, edge, angle=30, inset=0, shelf=0, excess) = +function mask2d_dovetail(x, y, edge, angle=30, inset=0, shelf=0, excess, anchor=CENTER, spin=0) = assert(num_defined([x,y,edge])==1) assert(is_num(first_defined([x,y,edge]))) assert(is_num(angle)) @@ -1250,11 +1273,12 @@ function mask2d_dovetail(x, y, edge, angle=30, inset=0, shelf=0, excess) = x = !is_undef(x)? x : !is_undef(y)? adj_ang_to_opp(adj=y,ang=angle) : hyp_ang_to_opp(hyp=edge,ang=angle), - y = opp_ang_to_adj(opp=x,ang=angle) - ) [ - [-excess, 0], [-excess, y+inset.y+shelf], - inset+[x,y+shelf], inset+[x,y], inset, [inset.x,0] - ]; + y = opp_ang_to_adj(opp=x,ang=angle), + path = [ + [-excess, 0], [-excess, y+inset.y+shelf], + inset+[x,y+shelf], inset+[x,y], inset, [inset.x,0] + ] + ) reorient(anchor,spin, two_d=true, path=path, p=path); // Function&Module: mask2d_ogee() @@ -1289,7 +1313,11 @@ function mask2d_dovetail(x, y, edge, angle=30, inset=0, shelf=0, excess) = // "ystep",1, "xstep",1 // Ending shoulder. // ]); module mask2d_ogee(pattern, excess) { - polygon(mask2d_ogee(pattern, excess=excess)); + path = mask2d_ogee(pattern, excess=excess); + attachable(anchor,spin, two_d=true, path=path) { + polygon(path); + children(); + } } function mask2d_ogee(pattern, excess) = @@ -1356,8 +1384,9 @@ function mask2d_ogee(pattern, excess) = a = pat[0]=="round"? (180+i*step) : (90-i*step) ) pat[2] + abs(r)*[cos(a),sin(a)] ] - ] - ) deduplicate(path); + ], + path2 = deduplicate(path) + ) reorient(anchor,spin, two_d=true, path=path2, p=path2); diff --git a/version.scad b/version.scad index b8ee6e0..d877a4d 100644 --- a/version.scad +++ b/version.scad @@ -8,7 +8,7 @@ ////////////////////////////////////////////////////////////////////// -BOSL_VERSION = [2,0,162]; +BOSL_VERSION = [2,0,163]; // Section: BOSL Library Version Functions