diff --git a/attachments.scad b/attachments.scad index c34e936..78c7807 100644 --- a/attachments.scad +++ b/attachments.scad @@ -121,12 +121,12 @@ function anchorpt(name, pos=[0,0,0], orient=UP, spin=0) = [name, pos, orient, sp // 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. +// r = Radius of the cylindrical/conical volume. Can be a scalar, or a list of sizes per axis. +// d = Diameter of the cylindrical/conical volume. Can be a scalar, or a list of sizes per axis. +// r1 = Radius of the bottom of the conical volume. Can be a scalar, or a list of sizes per axis. +// r2 = Radius of the top of the conical volume. Can be a scalar, or a list of sizes per axis. +// d1 = Diameter of the bottom of the conical volume. Can be a scalar, a list of sizes per axis. +// d2 = Diameter of the top of the conical volume. Can be a scalar, a list of sizes per axis. // 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. @@ -220,20 +220,25 @@ function attach_geom( 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(r1) || is_vector(r1,2)) + assert(is_num(r2) || is_vector(r2,2)) assert(is_num(l)) - assert(is_num(r2)) assert(is_vector(shift,2)) ["cyl", r1, r2, l, shift, offset, anchors] ) : ( - two_d? ["circle", r1, offset, anchors] : - ["spheroid", r1, offset, anchors] + two_d? ( + assert(is_num(r1) || is_vector(r1,2)) + ["circle", r1, offset, anchors] + ) : ( + assert(is_num(r1) || is_vector(r1,3)) + ["spheroid", r1, offset, anchors] + ) ) ) : assert(false, "Unrecognizable geometry description."); @@ -268,10 +273,16 @@ function attach_geom_size(geom) = ) : 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] + rx1 = default(r1[0],r1), + ry1 = default(r1[1],r1), + rx2 = default(r2[0],r2), + ry2 = default(r2[1],r2), + maxxr = max(rx1,rx2), + maxyr = max(ry1,ry2) + ) [2*maxxr,2*maxyr,l] ) : type == "spheroid"? ( //r - let( r=geom[1] ) [2,2,2]*r + let( r=geom[1] ) + is_num(r)? [2,2,2]*r : vmul([2,2,2],r) ) : type == "vnf_extent" || type=="vnf_isect"? ( //vnf let( mm = pointlist_bounds(geom[1][0]), @@ -283,7 +294,8 @@ function attach_geom_size(geom) = maxx = max(size.x,size2) ) [maxx, size.y] ) : type == "circle"? ( //r - let( r=geom[1] ) [2,2]*r + let( r=geom[1] ) + is_num(r)? [2,2]*r : vmul([2,2],r) ) : type == "path_isect" || type == "path_extent"? ( //path let( mm = pointlist_bounds(geom[1]), @@ -414,11 +426,13 @@ function find_anchor(anchor, geom) = ) [anchor, pos, vec, oang] ) : type == "cyl"? ( //r1, r2, l, shift let( - r1=geom[1], r2=geom[2], l=geom[3], shift=point2d(geom[4]), + rr1=geom[1], rr2=geom[2], l=geom[3], shift=point2d(geom[4]), + r1 = is_num(rr1)? [rr1,rr1] : rr1, + r2 = is_num(rr2)? [rr2,rr2] : rr2, u = (anchor.z+1)/2, axy = unit(point2d(anchor)), - bot = point3d(r1*axy,-l/2), - top = point3d(r2*axy+shift, l/2), + bot = point3d(vmul(r1,axy), -l/2), + top = point3d(vmul(r2,axy)+shift, l/2), pos = lerp(bot,top,u)+offset, sidevec = rot(from=UP, to=top-bot, p=point3d(axy)), vvec = unit([0,0,anchor.z]), @@ -429,8 +443,10 @@ function find_anchor(anchor, geom) = ) [anchor, pos, vec, oang] ) : type == "spheroid"? ( //r let( - r=geom[1] - ) [anchor, r*unit(anchor)+offset, unit(anchor), oang] + rr = geom[1], + r = is_num(rr)? [rr,rr,rr] : rr, + anchor = unit(point3d(anchor)) + ) [anchor, vmul(r,anchor)+offset, unit(vmul(r,anchor)), oang] ) : type == "vnf_isect"? ( //vnf let( vnf=geom[1], @@ -494,9 +510,10 @@ function find_anchor(anchor, geom) = ) [anchor, pos, vec, 0] ) : type == "circle"? ( //r let( - r=geom[1], + rr = geom[1], + r = is_num(rr)? [rr,rr] : rr, anchor = unit(point2d(anchor)) - ) [anchor, r*anchor+offset, anchor, 0] + ) [anchor, vmul(r,anchor)+offset, unit(vmul([r.y,r.x],anchor)), 0] ) : type == "path_isect"? ( //path let( path=geom[1], @@ -590,12 +607,12 @@ function attachment_is_shown(tags) = // 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. +// r = Radius of the cylindrical/conical volume. Can be a scalar, or a list of sizes per axis. +// d = Diameter of the cylindrical/conical volume. Can be a scalar, or a list of sizes per axis. +// r1 = Radius of the bottom of the conical volume. Can be a scalar, or a list of sizes per axis. +// r2 = Radius of the top of the conical volume. Can be a scalar, or a list of sizes per axis. +// d1 = Diameter of the bottom of the conical volume. Can be a scalar, a list of sizes per axis. +// d2 = Diameter of the top of the conical volume. Can be a scalar, a list of sizes per axis. // 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. @@ -677,12 +694,12 @@ function reorient( // 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. +// r = Radius of the cylindrical/conical volume. Can be a scalar, or a list of sizes per axis. +// d = Diameter of the cylindrical/conical volume. Can be a scalar, or a list of sizes per axis. +// r1 = Radius of the bottom of the conical volume. Can be a scalar, or a list of sizes per axis. +// r2 = Radius of the top of the conical volume. Can be a scalar, or a list of sizes per axis. +// d1 = Diameter of the bottom of the conical volume. Can be a scalar, a list of sizes per axis. +// d2 = Diameter of the top of the conical volume. Can be a scalar, a list of sizes per axis. // 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. diff --git a/shapes2d.scad b/shapes2d.scad index e685ea5..20ffb32 100644 --- a/shapes2d.scad +++ b/shapes2d.scad @@ -730,14 +730,17 @@ function _turtle_command(command, parm, parm2, state, index) = module rect(size=1, center, rounding=0, chamfer=0, anchor, spin=0) { size = is_num(size)? [size,size] : point2d(size); anchor = get_anchor(anchor, center, FRONT+LEFT, FRONT+LEFT); - attachable(anchor,spin, two_d=true, size=size) { - if (rounding==0 && chamfer==0) { + if (rounding==0 && chamfer==0) { + attachable(anchor,spin, two_d=true, size=size) { square(size, center=true); - } else { - pts = rect(size=size, rounding=rounding, chamfer=chamfer, center=true); - polygon(pts); + children(); + } + } else { + pts = rect(size=size, rounding=rounding, chamfer=chamfer, center=true); + attachable(anchor,spin, two_d=true, path=pts) { + polygon(pts); + children(); } - children(); } } @@ -798,9 +801,9 @@ function rect(size=1, center, rounding=0, chamfer=0, anchor, spin=0) = // When called as a module, creates a 2D polygon that approximates a circle of the given size. // When called as a function, returns a 2D list of points (path) for a polygon that approximates a circle of the given size. // Arguments: -// r = The radius of the circle to create. -// d = The diameter of the circle to create. -// realign = If true, rotates the polygon that approximates the circle by half of one size. +// r = Radius of the circle/oval to create. Can be a scalar, or a list of sizes per axis. +// d = Diameter of the circle/oval to create. Can be a scalar, or a list of sizes per axis. +// realign = If true, rotates the polygon that approximates the circle/oval by half of one size. // circum = If true, the polygon that approximates the circle will be upsized slightly to circumscribe the theoretical circle. If false, it inscribes the theoretical circle. Default: false // 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` @@ -816,10 +819,24 @@ function rect(size=1, center, rounding=0, chamfer=0, anchor, spin=0) = // path = oval(d=50, anchor=FRONT, spin=45); module oval(r, d, realign=false, circum=false, anchor=CENTER, spin=0) { r = get_radius(r=r, d=d, dflt=1); - sides = segs(r); - rr = circum? r/cos(180/sides) : r; - attachable(anchor,spin, two_d=true, r=rr) { - zrot(realign? 180/sides : 0) circle(r=rr, $fn=sides); + sides = segs(max(r)); + sc = circum? (1 / cos(180/sides)) : 1; + rx = default(r[0],r) * sc; + ry = default(r[1],r) * sc; + attachable(anchor,spin, two_d=true, r=[rx,ry]) { + if (rx < ry) { + xscale(rx/ry) { + zrot(realign? 180/sides : 0) { + circle(r=ry, $fn=sides); + } + } + } else { + yscale(ry/rx) { + zrot(realign? 180/sides : 0) { + circle(r=rx, $fn=sides); + } + } + } children(); } } @@ -828,11 +845,13 @@ module oval(r, d, realign=false, circum=false, anchor=CENTER, spin=0) { function oval(r, d, realign=false, circum=false, anchor=CENTER, spin=0) = let( r = get_radius(r=r, d=d, dflt=1), - sides = segs(r), + sides = segs(max(r)), 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)]] - ) reorient(anchor,spin, two_d=true, r=rr, p=pts); + sc = circum? (1 / cos(180/sides)) : 1, + rx = default(r[0],r) * sc, + ry = default(r[1],r) * sc, + pts = [for (i=[0:1:sides-1]) let(a=360-offset-i*360/sides) [rx*cos(a), ry*sin(a)]] + ) reorient(anchor,spin, two_d=true, r=[rx,ry], p=pts); diff --git a/tutorials/Basic_Shapes.md b/tutorials/Basic_Shapes.md index e033bd9..9c9725b 100644 --- a/tutorials/Basic_Shapes.md +++ b/tutorials/Basic_Shapes.md @@ -6,6 +6,7 @@ There are 5 built-in primitive shapes that OpenSCAD provides. The BOSL2 library extends or provides alternative to these shapes so that they support more features, and more ways to simply reorient them. + ### 2D Squares You can still use the built-in `square()` in the familiar ways that OpenSCAD provides: @@ -140,6 +141,7 @@ Anchoring or centering is performed before the spin: rect([60,40], anchor=BACK, spin=30); ``` + ### 2D Circles The built-in `circle()` primitive can be used as expected: @@ -192,6 +194,18 @@ Circumscribing the ideal circle: } ``` +The `oval()` module, as its name suggests, can be given separate X and Y radii +or diameters. To do this, just give `r=` or `d=` with a list of two radii or +diameters: + +```openscad-2D + oval(r=[30,20]); +``` + +```openscad-2D + oval(d=[60,40]); +``` + Another way that `oval()` is enhanced over `circle()`, is that you can anchor, spin and attach it. @@ -207,11 +221,13 @@ Using spin on a circle may not make initial sense, until you remember that anchoring is performed before spin: ```openscad-2D - oval(r=50, anchor=FRONT, spin=30); + oval(r=50, anchor=FRONT, spin=-30); ``` -### Enhanced 3D Cube -You can use enhanced `cube()` like the normal OpenSCAD built-in: + +### 3D Cubes +BOSL2 overrides the built-in `cube()` module. It still can be used as you +expect from the built-in: ```openscad-3D cube(100); @@ -225,8 +241,11 @@ You can use enhanced `cube()` like the normal OpenSCAD built-in: cube([50,40,20], center=true); ``` -You can use `anchor` similarly to `square()`, except you can anchor vertically -too, in 3D, allowing anchoring to faces, edges, and corners: +It is also enhanced to allow you to anchor, spin, orient, and attach it. + +You can use `anchor=` similarly to how you use it with `square()` or `rect()`, +except you can also anchor vertically in 3D, allowing anchoring to faces, edges, +and corners: ```openscad-3D cube([50,40,20], anchor=BOTTOM); @@ -240,36 +259,143 @@ too, in 3D, allowing anchoring to faces, edges, and corners: cube([50,40,20], anchor=TOP+FRONT+LEFT); ``` -You can use `spin` as well, to rotate around the Z axis: +You can use `spin=` to rotate around the Z axis: ```openscad-3D cube([50,40,20], anchor=FRONT, spin=30); ``` -3D objects also gain the ability to use an extra trick with `spin`; -if you pass a list of `[X,Y,Z]` rotation angles to `spin`, it will +3D objects also gain the ability to use an extra trick with `spin=`; +if you pass a list of `[X,Y,Z]` rotation angles to `spin=`, it will rotate by the three given axis angles, similar to using `rotate()`: ```openscad-3D cube([50,40,20], anchor=FRONT, spin=[15,0,30]); ``` -3D objects also can be given an `orient` argument that is given as a vector, -pointing towards where the top of the shape should be rotated towards. +3D objects also can be given an `orient=` argument as a vector, pointing +to where the top of the shape should be rotated towards. ```openscad-3D cube([50,40,20], orient=UP+BACK+RIGHT); ``` -If you use `anchor`, `spin`, and `orient` together, the anchor is performed +If you use `anchor=`, `spin=`, and `orient=` together, the anchor is performed first, then the spin, then the orient: +```openscad-3D + cube([50,40,20], anchor=FRONT); +``` + +```openscad-3D + cube([50,40,20], anchor=FRONT, spin=45); +``` + ```openscad-3D cube([50,40,20], anchor=FRONT, spin=45, orient=UP+FWD+RIGHT); ``` -### Enhanced 3D Cylinder -You can use the enhanced `cylinder()` as normal for OpenSCAD: +BOSL2 provides a `cuboid()` module that expands on `cube()`, by providing +rounding and chamfering of edges. You can use it similarly to `cube()`, +except that `cuboid()` centers by default. + +You can round the edges with the `rounding=` argument: + +```openscad-3D + cuboid([100,80,60], rounding=20); +``` + +Similarly, you can chamfer the edges with the `chamfer=` argument: + +```openscad-3D + cuboid([100,80,60], chamfer=10); +``` + +You can round only some edges, by using the `edges=` arguments. It can be +given a few types of arguments. If you gave it a vector pointed at a face, +it will only round the edges surrounding that face: + +```openscad-3D + cuboid([100,80,60], rounding=20, edges=TOP); +``` + +```openscad-3D + cuboid([100,80,60], rounding=20, edges=RIGHT); +``` + +If you give `edges=` a vector pointing at a corner, it will round all edges +that meet at that corner: + +```openscad-3D + cuboid([100,80,60], rounding=20, edges=RIGHT+FRONT+TOP); +``` + +```openscad-3D + cuboid([100,80,60], rounding=20, edges=LEFT+FRONT+TOP); +``` + +If you give `edges=` a vector pointing at an edge, it will round only that edge: + +```openscad-3D + cuboid([100,80,60], rounding=10, edges=FRONT+TOP); +``` + +```openscad-3D + cuboid([100,80,60], rounding=10, edges=RIGHT+FRONT); +``` + +If you give the string "X", "Y", or "Z", then all edges aligned with the specified +axis will be rounded: + +```openscad-3D + cuboid([100,80,60], rounding=10, edges="X"); +``` + +```openscad-3D + cuboid([100,80,60], rounding=10, edges="Y"); +``` + +```openscad-3D + cuboid([100,80,60], rounding=10, edges="Z"); +``` + +If you give a list of edge specs, then all edges referenced in the list will +be rounded: + +```openscad-3D + cuboid([100,80,60], rounding=10, edges=[TOP,"Z",BOTTOM+RIGHT]); +``` + +The default value for `edges=` is `EDGES_ALL`, which is all edges. You can also +give an `except_edges=` argument that specifies edges to NOT round: + +```openscad-3D + cuboid([100,80,60], rounding=10, except_edges=BOTTOM+RIGHT); +``` + +You can give the `except_edges=` argument any type of argument that you can +give to `edges=`: + +```openscad-3D + cuboid([100,80,60], rounding=10, except_edges=[BOTTOM,"Z",TOP+RIGHT]); +``` + +You can give both `edges=` and `except_edges=`, to simplify edge specs: + +```openscad-3D + cuboid([100,80,60], rounding=10, edges=[TOP,FRONT], except_edges=TOP+FRONT); +``` + +You can specify what edges to chamfer similarly: + +```openscad-3D + cuboid([100,80,60], chamfer=10, edges=[TOP,FRONT], except_edges=TOP+FRONT); +``` + + +### 3D Cylinder +BOSL2 overrides the built-in `cylinder()` module. It still can be used as you +expect from the built-in: ```openscad-3D cylinder(r=50,h=50); @@ -287,3 +413,144 @@ You can use the enhanced `cylinder()` as normal for OpenSCAD: cylinder(d1=100,d2=80,h=50,center=true); ``` +You can also anchor, spin, orient, and attach like the `cuboid()` module: + +```openscad-3D + cylinder(r=50, h=50, anchor=TOP+FRONT); +``` + +```openscad-3D + cylinder(r=50, h=50, anchor=BOTTOM+LEFT); +``` + +```openscad-3D + cylinder(r=50, h=50, anchor=BOTTOM+LEFT, spin=30); +``` + +```openscad-3D + cylinder(r=50, h=50, anchor=BOTTOM, orient=UP+BACK+RIGHT); +``` + + +BOSL2 provides a `cyl()` module that expands on `cylinder()`, by providing +rounding and chamfering of edges. You can use it similarly to `cylinder()`, +except that `cyl()` centers the cylinder by default. + +```openscad-3D + cyl(r=60, l=100); +``` + +```openscad-3D + cyl(d=100, l=100); +``` + +```openscad-3D + cyl(d=100, l=100, anchor=TOP); +``` + +You can round the edges with the `rounding=` argument: + +```openscad-3D + cyl(d=100, l=100, rounding=20); +``` + +Similarly, you can chamfer the edges with the `chamfer=` argument: + +```openscad-3D + cyl(d=100, l=100, chamfer=10); +``` + +You can specify rounding and chamfering for each end individually: + +```openscad-3D + cyl(d=100, l=100, rounding1=20); +``` + +```openscad-3D + cyl(d=100, l=100, rounding2=20); +``` + +```openscad-3D + cyl(d=100, l=100, chamfer1=10); +``` + +```openscad-3D + cyl(d=100, l=100, chamfer2=10); +``` + +You can even mix and match rounding and chamfering: + +```openscad-3D + cyl(d=100, l=100, rounding1=20, chamfer2=10); +``` + +```openscad-3D + cyl(d=100, l=100, rounding2=20, chamfer1=10); +``` + + +### 3D Spheres +BOSL2 overrides the built-in `sphere()` module. It still can be used as you +expect from the built-in: + +```openscad-3D + cylinder(r=50); +``` + +```openscad-3D + cylinder(d=100); +``` + +You can anchor, spin, and orient `sphere()`s, much like you can with `cylinder()` +and `cube()`: + +```openscad-3D + sphere(d=100, anchor=FRONT); +``` + +```openscad-3D + sphere(d=100, anchor=FRONT, spin=30); +``` + +```openscad-3D + sphere(d=100, anchor=BOTTOM, orient=RIGHT+TOP); +``` + +BOSL2 also provides `spheroid()`, which enhances `sphere()` with a few features +like the `circum=` and `style=` arguments: + +You can use the `circum=true` argument to force the sphere to circumscribe the +ideal sphere, as opposed to the default inscribing: + +```openscad-3D + spheroid(d=100, circum=true); +``` + +The `style=` argument can choose the way that the sphere will be constructed: +The "orig" style matches the `sphere()` built-in's construction. + +```openscad-3D + spheroid(d=100, style="orig"); +``` + +The "aligned" style will ensure that there is a vertex at each axis extrama, +so long as `$fn` is a multiple of 4. + +```openscad-3D + spheroid(d=100, style="aligned"); +``` + +The "stagger" style will stagger the triangulation of the vertical rows: + +```openscad-3D + spheroid(d=100, style="stagger"); +``` + +The "icosa"` style will make for roughly equal-sized triangles for the entire +sphere surface: + +```openscad-3D + spheroid(d=100, style="icosa"); +``` + + diff --git a/version.scad b/version.scad index 228a6e5..2eb6a61 100644 --- a/version.scad +++ b/version.scad @@ -8,7 +8,7 @@ ////////////////////////////////////////////////////////////////////// -BOSL_VERSION = [2,0,291]; +BOSL_VERSION = [2,0,292]; // Section: BOSL Library Version Functions