Merge remote-tracking branch 'upstream/master'

This commit is contained in:
Adrian Mariano 2021-01-01 21:16:47 -05:00
commit 0cff52023f
14 changed files with 1863 additions and 525 deletions

View file

@ -1,4 +1,5 @@
# BOSL2
![BOSL2 Logo](images/BOSL2logo.png)
**The Belfry OpenScad Library, v2**

View file

@ -91,7 +91,7 @@ $tags_hidden = [];
// Function: anchorpt()
// Usage:
// anchor(name, pos, [dir], [rot])
// anchor(name, pos, <dir>, <rot>)
// Description:
// Creates a anchor data structure.
// Arguments:
@ -105,21 +105,21 @@ function anchorpt(name, pos=[0,0,0], orient=UP, spin=0) = [name, pos, orient, sp
// Function: attach_geom()
//
// Usage: Square/Trapezoid Geometry
// geom = attach_geom(anchor, spin, [orient], two_d, size, [size2], [shift], [cp], [offset], [anchors]);
// geom = attach_geom(anchor, spin, two_d, size, <size2>, <shift>, <cp>, <offset>, <anchors>);
// Usage: Circle/Oval Geometry
// geom = attach_geom(anchor, spin, [orient], two_d, r|d, [cp], [offset], [anchors]);
// geom = attach_geom(anchor, spin, two_d, r|d, <cp>, <offset>, <anchors>);
// Usage: 2D Path/Polygon Geometry
// geom = attach_geom(anchor, spin, [orient], two_d, path, [extent], [cp], [offset], [anchors]);
// geom = attach_geom(anchor, spin, two_d, path, <extent>, <cp>, <offset>, <anchors>);
// Usage: Cubical/Prismoidal Geometry
// geom = attach_geom(anchor, spin, [orient], size, [size2], [shift], [cp], [offset], [anchors]);
// geom = attach_geom(anchor, spin, <orient>, size, <size2>, <shift>, <cp>, <offset>, <anchors>);
// Usage: Cylindrical Geometry
// geom = attach_geom(anchor, spin, [orient], r|d, l, [cp], [axis], [offset], [anchors]);
// geom = attach_geom(anchor, spin, <orient>, r|d, l, <cp>, <axis>, <offset>, <anchors>);
// Usage: Conical Geometry
// geom = attach_geom(anchor, spin, [orient], r1|d1, r2|d2, l, [cp], [axis], [offset], [anchors]);
// geom = attach_geom(anchor, spin, <orient>, r1|d1, r2|d2, l, <cp>, <axis>, <offset>, <anchors>);
// Usage: Spheroid/Ovoid Geometry
// geom = attach_geom(anchor, spin, [orient], r|d, [cp], [offset], [anchors]);
// geom = attach_geom(anchor, spin, <orient>, r|d, <cp>, <offset>, <anchors>);
// Usage: VNF Geometry
// geom = attach_geom(anchor, spin, [orient], vnf, [extent], [cp], [offset], [anchors]);
// geom = attach_geom(anchor, spin, <orient>, vnf, <extent>, <cp>, <offset>, <anchors>);
//
// Description:
// Given arguments that describe the geometry of an attachable object, returns the internal geometry description.
@ -332,8 +332,8 @@ function attach_geom_size(geom) =
) delt
) : type == "rect"? ( //size, size2
let(
size=geom[1], size2=geom[2],
maxx = max(size.x,size2)
size=geom[1], size2=geom[2], shift=geom[3],
maxx = max(size.x,size2+abs(shift))
) [maxx, size.y]
) : type == "circle"? ( //r
let( r=geom[1] )
@ -349,7 +349,7 @@ function attach_geom_size(geom) =
// Function: attach_transform()
// Usage:
// mat = attach_transform(anchor=CENTER, spin=0, orient=UP, geom);
// mat = attach_transform(anchor, spin, orient, geom);
// Description:
// Returns the affine3d transformation matrix needed to `anchor`, `spin`, and `orient`
// the given geometry `geom` shape into position.
@ -557,12 +557,12 @@ function find_anchor(anchor, geom) =
mpt = approx(point2d(anchor),[0,0])? [maxx,0,0] : [maxx, avgy, avgz],
pos = point3d(cp) + rot(from=RIGHT, to=anchor, p=mpt)
) [anchor, pos, anchor, oang]
) : type == "rect"? ( //size, size2
) : type == "rect"? ( //size, size2, shift
let(
size=geom[1], size2=geom[2],
size=geom[1], size2=geom[2], shift=geom[3],
u = (anchor.y+1)/2,
frpt = [size.x/2*anchor.x, -size.y/2],
bkpt = [size2/2*anchor.x, size.y/2],
bkpt = [size2/2*anchor.x+shift, size.y/2],
pos = point2d(cp) + lerp(frpt, bkpt, u) + offset,
vec = unit(rot(from=BACK, to=bkpt-frpt, p=anchor),[0,1])
) [anchor, pos, vec, 0]
@ -629,21 +629,21 @@ function attachment_is_shown(tags) =
// Function: reorient()
//
// Usage: Square/Trapezoid Geometry
// reorient(anchor, spin, [orient], two_d, size, [size2], [shift], [cp], [offset], [anchors], [p]);
// reorient(anchor, spin, <orient>, two_d, size, <size2>, <shift>, <cp>, <offset>, <anchors>, <p>);
// Usage: Circle/Oval Geometry
// reorient(anchor, spin, [orient], two_d, r|d, [cp], [offset], [anchors], [p]);
// reorient(anchor, spin, <orient>, two_d, r|d, <cp>, <offset>, <anchors>, <p>);
// Usage: 2D Path/Polygon Geometry
// reorient(anchor, spin, [orient], two_d, path, [extent], [cp], [offset], [anchors], [p]);
// reorient(anchor, spin, <orient>, two_d, path, <extent>, <cp>, <offset>, <anchors>, <p>);
// Usage: Cubical/Prismoidal Geometry
// reorient(anchor, spin, [orient], size, [size2], [shift], [cp], [offset], [anchors], [p]);
// reorient(anchor, spin, <orient>, size, <size2>, <shift>, <cp>, <offset>, <anchors>, <p>);
// Usage: Cylindrical Geometry
// reorient(anchor, spin, [orient], r|d, l, [offset], [axis], [cp], [anchors], [p]);
// reorient(anchor, spin, <orient>, r|d, l, <offset>, <axis>, <cp>, <anchors>, <p>);
// Usage: Conical Geometry
// reorient(anchor, spin, [orient], r1|d1, r2|d2, l, [axis], [cp], [offset], [anchors], [p]);
// reorient(anchor, spin, <orient>, r1|d1, r2|d2, l, <axis>, <cp>, <offset>, <anchors>, <p>);
// Usage: Spheroid/Ovoid Geometry
// reorient(anchor, spin, [orient], r|d, [cp], [offset], [anchors], [p]);
// reorient(anchor, spin, <orient>, r|d, <cp>, <offset>, <anchors>, <p>);
// Usage: VNF Geometry
// reorient(anchor, spin, [orient], vnf, [extent], [cp], [offset], [anchors], [p]);
// reorient(anchor, spin, <orient>, vnf, <extent>, <cp>, <offset>, <anchors>, <p>);
//
// Description:
// Given anchor, spin, orient, and general geometry info for a managed volume, this calculates
@ -723,21 +723,21 @@ function reorient(
// Module: attachable()
//
// Usage: Square/Trapezoid Geometry
// attachable(anchor, spin, [orient], two_d, size, [size2], [shift], [cp], [offset], [anchors] ...
// attachable(anchor, spin, two_d, size, <size2>, <shift>, <cp>, <offset>, <anchors> ...
// Usage: Circle/Oval Geometry
// attachable(anchor, spin, [orient], two_d, r|d, [cp], [offset], [anchors]) ...
// attachable(anchor, spin, two_d, r|d, <cp>, <offset>, <anchors>) ...
// Usage: 2D Path/Polygon Geometry
// attachable(anchor, spin, [orient], two_d, path, [extent], [cp], [offset], [anchors] ...
// attachable(anchor, spin, two_d, path, <extent>, <cp>, <offset>, <anchors> ...
// Usage: Cubical/Prismoidal Geometry
// attachable(anchor, spin, [orient], size, [size2], [shift], [cp], [offset], [anchors] ...
// attachable(anchor, spin, <orient>, size, <size2>, <shift>, <cp>, <offset>, <anchors> ...
// Usage: Cylindrical Geometry
// attachable(anchor, spin, [orient], r|d, l, [axis], [cp], [offset], [anchors]) ...
// attachable(anchor, spin, <orient>, r|d, l, <axis>, <cp>, <offset>, <anchors>) ...
// Usage: Conical Geometry
// attachable(anchor, spin, [orient], r1|d1, r2|d2, l, [axis], [cp], [offset], [anchors]) ...
// attachable(anchor, spin, <orient>, r1|d1, r2|d2, l, <axis>, <cp>, <offset>, <anchors>) ...
// Usage: Spheroid/Ovoid Geometry
// attachable(anchor, spin, [orient], r|d, [cp], [offset], [anchors]) ...
// attachable(anchor, spin, <orient>, r|d, <cp>, <offset>, <anchors>) ...
// Usage: VNF Geometry
// attachable(anchor, spin, [orient], vnf, [extent], [cp], [offset], [anchors]) ...
// attachable(anchor, spin, <orient>, vnf, <extent>, <cp>, <offset>, <anchors>) ...
//
// Description:
// Manages the anchoring, spin, orientation, and attachments for a 3D volume or 2D area.
@ -969,8 +969,8 @@ module position(from)
// Module: attach()
// Usage:
// attach(from, [overlap]) ...
// attach(from, to, [overlap]) ...
// attach(from, <overlap>) ...
// attach(from, to, <overlap>) ...
// Description:
// Attaches children to a parent object at an anchor point and orientation.
// Attached objects will be overlapped into the parent object by a little bit,
@ -1012,7 +1012,7 @@ module attach(from, to=undef, overlap=undef, norot=false)
// Module: face_profile()
// Usage:
// face_profile(faces=[], convexity=10, r, d) ...
// face_profile(faces, r, d, <convexity>) ...
// Description:
// Given a 2D edge profile, extrudes it into a mask for all edges and corners bounding each given face.
// Arguments:
@ -1032,7 +1032,7 @@ module face_profile(faces=[], r, d, convexity=10) {
// Module: edge_profile()
// Usage:
// edge_profile([edges], [except], [convexity]) ...
// 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.
@ -1082,7 +1082,7 @@ module edge_profile(edges=EDGES_ALL, except=[], convexity=10) {
// Module: corner_profile()
// Usage:
// corner_profile([corners], [except], [convexity]) ...
// corner_profile(<corners>, <except>, <convexity>) ...
// Description:
// Takes a 2D mask shape, rotationally extrudes and converts it into a corner mask, and attaches it
// to the selected corners with the appropriate orientation. Tags it as a "mask" to allow it to be
@ -1144,7 +1144,7 @@ module corner_profile(corners=CORNERS_ALL, except=[], r, d, convexity=10) {
// Module: edge_mask()
// Usage:
// edge_mask([edges], [except]) ...
// 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.
@ -1186,7 +1186,7 @@ module edge_mask(edges=EDGES_ALL, except=[]) {
// Module: corner_mask()
// Usage:
// corner_mask([corners], [except]) ...
// 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
@ -1305,8 +1305,8 @@ module show(tags="")
// Module: diff()
// Usage:
// diff(neg, [keep]) ...
// diff(neg, pos, [keep]) ...
// 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
@ -1363,8 +1363,8 @@ module diff(neg, pos=undef, keep=undef)
// Module: intersect()
// Usage:
// intersect(a, [keep]) ...
// intersect(a, b, [keep]) ...
// 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

View file

@ -274,7 +274,7 @@ function standard_anchors(two_d=false) = [
// Module: anchor_arrow()
// Usage:
// anchor_arrow([s], [color], [flag]);
// anchor_arrow(<s>, <color>, <flag>);
// Description:
// Show an anchor orientation arrow.
// Arguments:
@ -303,7 +303,7 @@ module anchor_arrow(s=10, color=[0.333,0.333,1], flag=true, $tags="anchor-arrow"
// Module: anchor_arrow2d()
// Usage:
// anchor_arrow2d([s], [color], [flag]);
// anchor_arrow2d(<s>, <color>, <flag>);
// Description:
// Show an anchor orientation arrow.
// Arguments:
@ -312,7 +312,7 @@ module anchor_arrow(s=10, color=[0.333,0.333,1], flag=true, $tags="anchor-arrow"
// Example:
// anchor_arrow2d(s=20);
module anchor_arrow2d(s=15, color=[0.333,0.333,1], $tags="anchor-arrow") {
noop() stroke([[0,0],[0,s]], width=s/10, endcap1="butt", endcap2="arrow2");
noop() color(color) stroke([[0,0],[0,s]], width=s/10, endcap1="butt", endcap2="arrow2");
}
@ -341,18 +341,28 @@ module show_internal_anchors(opacity=0.2) {
// Example(FlatSpin):
// cube(50, center=true) show_anchors();
module show_anchors(s=10, std=true, custom=true) {
check = assert($parent_geom != undef) 1;
two_d = attach_geom_2d($parent_geom);
if (std) {
for (anchor=standard_anchors()) {
attach(anchor) anchor_arrow(s);
for (anchor=standard_anchors(two_d=two_d)) {
if(two_d) {
attach(anchor) anchor_arrow2d(s);
} else {
attach(anchor) anchor_arrow(s);
}
}
}
if (custom) {
for (anchor=select($parent_geom,-1)) {
attach(anchor[0]) {
anchor_arrow(s, color="cyan");
recolor("black")
if(two_d) {
anchor_arrow2d(s, color="cyan");
} else {
anchor_arrow(s, color="cyan");
}
color("black")
noop($tags="anchor-arrow") {
xrot(90) {
xrot(two_d? 0 : 90) {
up(s/10) {
linear_extrude(height=0.01, convexity=12, center=true) {
text(text=anchor[0], size=s/4, halign="center", valign="center");

BIN
images/BOSL2logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

View file

@ -19,6 +19,7 @@
// Returns an axis-aligned cube shape that exactly contains all the 3D children given.
// Arguments:
// excess = The amount that the bounding box should be larger than needed to bound the children, in each axis.
// planar = If true, creates a 2D bounding rectangle. Is false, creates a 3D bounding cube. Default: false
// Example:
// #bounding_box() {
// translate([10,8,4]) cube(5);
@ -26,32 +27,46 @@
// }
// translate([10,8,4]) cube(5);
// translate([3,0,12]) cube(2);
module bounding_box(excess=0) {
module bounding_box(excess=0, planar=true) {
xs = excess>.1? excess : 1;
// a 3D approx. of the children projection on X axis
module _xProjection()
linear_extrude(xs, center=true)
if (planar) {
projection()
rotate([90,0,0])
linear_extrude(xs, center=true)
projection()
hull()
children();
hull()
children();
} else {
linear_extrude(xs, center=true)
projection()
rotate([90,0,0])
linear_extrude(xs, center=true)
projection()
hull()
children();
}
// a bounding box with an offset of 1 in all axis
module _oversize_bbox() {
minkowski() {
_xProjection() children(); // x axis
rotate(-90) _xProjection() rotate(90) children(); // y axis
rotate([0,-90,0]) _xProjection() rotate([0,90,0]) children(); // z axis
if (planar) {
minkowski() {
_xProjection() children(); // x axis
rotate(-90) _xProjection() rotate(90) children(); // y axis
}
} else {
minkowski() {
_xProjection() children(); // x axis
rotate(-90) _xProjection() rotate(90) children(); // y axis
rotate([0,-90,0]) _xProjection() rotate([0,90,0]) children(); // z axis
}
}
}
// offset children() (a cube) by -1 in all axis
module _shrink_cube() {
intersection() {
translate((1-excess)*[ 1, 1, 1]) children();
translate((1-excess)*[-1,-1,-1]) children();
translate((1-excess)*[ 1, 1, planar?0: 1]) children();
translate((1-excess)*[-1,-1, planar?0:-1]) children();
}
}
@ -209,7 +224,6 @@ function left_half(_arg1=_undef, _arg2=_undef, _arg3=_undef,
// right_half([s], [x]) ...
// right_half(planar=true, [s], [x]) ...
//
//
// Description:
// Slices an object at a vertical Y-Z cut plane, and masks away everything that is left of it.
//
@ -516,31 +530,103 @@ module cylindrical_extrude(or, ir, od, id, size=1000, convexity=10, spin=0, orie
// Section: Offset Mutators
//////////////////////////////////////////////////////////////////////
// Module: round3d()
// Module: minkowski_difference()
// Usage:
// round3d(r) ...
// round3d(or) ...
// round3d(ir) ...
// round3d(or, ir) ...
// minkowski_difference() { base_shape(); diff_shape(); ... }
// Description:
// Rounds arbitrary 3D objects. Giving `r` rounds all concave and convex corners. Giving just `ir`
// Takes a 3D base shape and one or more 3D diff shapes, carves out the diff shapes from the
// surface of the base shape, in a way complementary to how `minkowski()` unions shapes to the
// surface of its base shape.
// Arguments:
// planar = If true, performs minkowski difference in 2D. Default: false (3D)
// Example:
// minkowski_difference() {
// union() {
// cube([120,70,70], center=true);
// cube([70,120,70], center=true);
// cube([70,70,120], center=true);
// }
// sphere(r=10);
// }
module minkowski_difference(planar=false) {
difference() {
bounding_box(excess=0, planar=planar) children(0);
render(convexity=20) {
minkowski() {
difference() {
bounding_box(excess=1, planar=planar) children(0);
children(0);
}
for (i=[1:1:$children-1]) children(i);
}
}
}
}
// Module: round2d()
// Usage:
// round2d(r) ...
// round2d(or) ...
// round2d(ir) ...
// round2d(or, ir) ...
// Description:
// Rounds arbitrary 2D objects. Giving `r` rounds all concave and convex corners. Giving just `ir`
// rounds just concave corners. Giving just `or` rounds convex corners. Giving both `ir` and `or`
// can let you round to different radii for concave and convex corners. The 3D object must not have
// any parts narrower than twice the `or` radius. Such parts will disappear. This is an *extremely*
// slow operation. I cannot emphasize enough just how slow it is. It uses `minkowski()` multiple times.
// Use this as a last resort. This is so slow that no example images will be rendered.
// can let you round to different radii for concave and convex corners. The 2D object must not have
// any parts narrower than twice the `or` radius. Such parts will disappear.
// Arguments:
// r = Radius to round all concave and convex corners to.
// or = Radius to round only outside (convex) corners to. Use instead of `r`.
// ir = Radius to round only inside (concave) corners to. Use instead of `r`.
module round3d(r, or, ir, size=100)
// Examples(2D):
// round2d(r=10) {square([40,100], center=true); square([100,40], center=true);}
// round2d(or=10) {square([40,100], center=true); square([100,40], center=true);}
// round2d(ir=10) {square([40,100], center=true); square([100,40], center=true);}
// round2d(or=16,ir=8) {square([40,100], center=true); square([100,40], center=true);}
module round2d(r, or, ir)
{
or = get_radius(r1=or, r=r, dflt=0);
ir = get_radius(r1=ir, r=r, dflt=0);
offset3d(or, size=size)
offset3d(-ir-or, size=size)
offset3d(ir, size=size)
offset(or) offset(-ir-or) offset(delta=ir,chamfer=true) children();
}
// Module: shell2d()
// Usage:
// shell2d(thickness, [or], [ir], [fill], [round])
// Description:
// Creates a hollow shell from 2D children, with optional rounding.
// Arguments:
// thickness = Thickness of the shell. Positive to expand outward, negative to shrink inward, or a two-element list to do both.
// or = Radius to round corners on the outside of the shell. If given a list of 2 radii, [CONVEX,CONCAVE], specifies the radii for convex and concave corners separately. Default: 0 (no outside rounding)
// ir = Radius to round corners on the inside of the shell. If given a list of 2 radii, [CONVEX,CONCAVE], specifies the radii for convex and concave corners separately. Default: 0 (no inside rounding)
// Examples(2D):
// shell2d(10) {square([40,100], center=true); square([100,40], center=true);}
// shell2d(-10) {square([40,100], center=true); square([100,40], center=true);}
// shell2d([-10,10]) {square([40,100], center=true); square([100,40], center=true);}
// shell2d(10,or=10) {square([40,100], center=true); square([100,40], center=true);}
// shell2d(10,ir=10) {square([40,100], center=true); square([100,40], center=true);}
// shell2d(10,round=10) {square([40,100], center=true); square([100,40], center=true);}
// shell2d(10,fill=10) {square([40,100], center=true); square([100,40], center=true);}
// shell2d(8,or=16,ir=8,round=16,fill=8) {square([40,100], center=true); square([100,40], center=true);}
module shell2d(thickness, or=0, ir=0)
{
thickness = is_num(thickness)? (
thickness<0? [thickness,0] : [0,thickness]
) : (thickness[0]>thickness[1])? (
[thickness[1],thickness[0]]
) : thickness;
orad = is_finite(or)? [or,or] : or;
irad = is_finite(ir)? [ir,ir] : ir;
difference() {
round2d(or=orad[0],ir=orad[1])
offset(delta=thickness[1])
children();
round2d(or=irad[1],ir=irad[0])
offset(delta=thickness[0])
children();
}
}
@ -583,101 +669,31 @@ module offset3d(r=1, size=100, convexity=10) {
}
// Module: round2d()
// Module: round3d()
// Usage:
// round2d(r) ...
// round2d(or) ...
// round2d(ir) ...
// round2d(or, ir) ...
// round3d(r) ...
// round3d(or) ...
// round3d(ir) ...
// round3d(or, ir) ...
// Description:
// Rounds arbitrary 2D objects. Giving `r` rounds all concave and convex corners. Giving just `ir`
// Rounds arbitrary 3D objects. Giving `r` rounds all concave and convex corners. Giving just `ir`
// rounds just concave corners. Giving just `or` rounds convex corners. Giving both `ir` and `or`
// can let you round to different radii for concave and convex corners. The 2D object must not have
// any parts narrower than twice the `or` radius. Such parts will disappear.
// can let you round to different radii for concave and convex corners. The 3D object must not have
// any parts narrower than twice the `or` radius. Such parts will disappear. This is an *extremely*
// slow operation. I cannot emphasize enough just how slow it is. It uses `minkowski()` multiple times.
// Use this as a last resort. This is so slow that no example images will be rendered.
// Arguments:
// r = Radius to round all concave and convex corners to.
// or = Radius to round only outside (convex) corners to. Use instead of `r`.
// ir = Radius to round only inside (concave) corners to. Use instead of `r`.
// Examples(2D):
// round2d(r=10) {square([40,100], center=true); square([100,40], center=true);}
// round2d(or=10) {square([40,100], center=true); square([100,40], center=true);}
// round2d(ir=10) {square([40,100], center=true); square([100,40], center=true);}
// round2d(or=16,ir=8) {square([40,100], center=true); square([100,40], center=true);}
module round2d(r, or, ir)
module round3d(r, or, ir, size=100)
{
or = get_radius(r1=or, r=r, dflt=0);
ir = get_radius(r1=ir, r=r, dflt=0);
offset(or) offset(-ir-or) offset(delta=ir,chamfer=true) children();
}
// Module: shell2d()
// Usage:
// shell2d(thickness, [or], [ir], [fill], [round])
// Description:
// Creates a hollow shell from 2D children, with optional rounding.
// Arguments:
// thickness = Thickness of the shell. Positive to expand outward, negative to shrink inward, or a two-element list to do both.
// or = Radius to round convex corners/pointy bits on the outside of the shell.
// ir = Radius to round concave corners on the outside of the shell.
// round = Radius to round convex corners/pointy bits on the inside of the shell.
// fill = Radius to round concave corners on the inside of the shell.
// Examples(2D):
// shell2d(10) {square([40,100], center=true); square([100,40], center=true);}
// shell2d(-10) {square([40,100], center=true); square([100,40], center=true);}
// shell2d([-10,10]) {square([40,100], center=true); square([100,40], center=true);}
// shell2d(10,or=10) {square([40,100], center=true); square([100,40], center=true);}
// shell2d(10,ir=10) {square([40,100], center=true); square([100,40], center=true);}
// shell2d(10,round=10) {square([40,100], center=true); square([100,40], center=true);}
// shell2d(10,fill=10) {square([40,100], center=true); square([100,40], center=true);}
// shell2d(8,or=16,ir=8,round=16,fill=8) {square([40,100], center=true); square([100,40], center=true);}
module shell2d(thickness, or=0, ir=0, fill=0, round=0)
{
thickness = is_num(thickness)? (
thickness<0? [thickness,0] : [0,thickness]
) : (thickness[0]>thickness[1])? (
[thickness[1],thickness[0]]
) : thickness;
difference() {
round2d(or=or,ir=ir)
offset(delta=thickness[1])
offset3d(or, size=size)
offset3d(-ir-or, size=size)
offset3d(ir, size=size)
children();
round2d(or=fill,ir=round)
offset(delta=thickness[0])
children();
}
}
// Module: minkowski_difference()
// Usage:
// minkowski_difference() { base_shape(); diff_shape(); ... }
// Description:
// Takes a 3D base shape and one or more 3D diff shapes, carves out the diff shapes from the
// surface of the base shape, in a way complementary to how `minkowski()` unions shapes to the
// surface of its base shape.
// Example:
// minkowski_difference() {
// union() {
// cube([120,70,70], center=true);
// cube([70,120,70], center=true);
// cube([70,70,120], center=true);
// }
// sphere(r=10);
// }
module minkowski_difference() {
difference() {
bounding_box(excess=0) children(0);
render(convexity=10) {
minkowski() {
difference() {
bounding_box(excess=1) children(0);
children(0);
}
for (i=[1:1:$children-1]) children(i);
}
}
}
}

View file

@ -407,6 +407,158 @@ function path_torsion(path, closed=false) =
];
// Function: path_chamfer_and_rounding()
// Usage:
// path2 = path_chamfer_and_rounding(path, [closed], [chamfer], [rounding]);
// Description:
// Rounds or chamfers corners in the given path.
// Arguments:
// path = The path to chamfer and/or round.
// closed = If true, treat path like a closed polygon. Default: true
// chamfer = The length of the chamfer faces at the corners. If given as a list of numbers, gives individual chamfers for each corner, from first to last. Default: 0 (no chamfer)
// rounding = The rounding radius for the corners. If given as a list of numbers, gives individual radii for each corner, from first to last. Default: 0 (no rounding)
// Example(2D): Chamfering a Path
// path = star(5, step=2, d=100);
// path2 = path_chamfer_and_rounding(path, closed=true, chamfer=5);
// stroke(path2, closed=true);
// Example(2D): Per-Corner Chamfering
// path = star(5, step=2, d=100);
// chamfs = [for (i=[0:1:4]) each 3*[i,i]];
// path2 = path_chamfer_and_rounding(path, closed=true, chamfer=chamfs);
// stroke(path2, closed=true);
// Example(2D): Rounding a Path
// path = star(5, step=2, d=100);
// path2 = path_chamfer_and_rounding(path, closed=true, rounding=5);
// stroke(path2, closed=true);
// Example(2D): Per-Corner Chamfering
// path = star(5, step=2, d=100);
// rs = [for (i=[0:1:4]) each 2*[i,i]];
// path2 = path_chamfer_and_rounding(path, closed=true, rounding=rs);
// stroke(path2, closed=true);
// Example(2D): Mixing Chamfers and Roundings
// path = star(5, step=2, d=100);
// chamfs = [for (i=[0:4]) each [5,0]];
// rs = [for (i=[0:4]) each [0,10]];
// path2 = path_chamfer_and_rounding(path, closed=true, chamfer=chamfs, rounding=rs);
// stroke(path2, closed=true);
function path_chamfer_and_rounding(path, closed, chamfer, rounding) =
let (
path = deduplicate(path,closed=true),
lp = len(path),
chamfer = is_undef(chamfer)? repeat(0,lp) :
is_vector(chamfer)? list_pad(chamfer,lp,0) :
is_num(chamfer)? repeat(chamfer,lp) :
assert(false, "Bad chamfer value."),
rounding = is_undef(rounding)? repeat(0,lp) :
is_vector(rounding)? list_pad(rounding,lp,0) :
is_num(rounding)? repeat(rounding,lp) :
assert(false, "Bad rounding value."),
corner_paths = [
for (i=(closed? [0:1:lp-1] : [1:1:lp-2])) let(
p1 = select(path,i-1),
p2 = select(path,i),
p3 = select(path,i+1)
)
chamfer[i] > 0? _corner_chamfer_path(p1, p2, p3, side=chamfer[i]) :
rounding[i] > 0? _corner_roundover_path(p1, p2, p3, r=rounding[i]) :
[p2]
],
out = [
if (!closed) path[0],
for (i=(closed? [0:1:lp-1] : [1:1:lp-2])) let(
p1 = select(path,i-1),
p2 = select(path,i),
crn1 = select(corner_paths,i-1),
crn2 = corner_paths[i],
l1 = norm(select(crn1,-1)-p1),
l2 = norm(crn2[0]-p2),
needed = l1 + l2,
seglen = norm(p2-p1),
check = assert(seglen >= needed, str("Path segment ",i," is too short to fulfill rounding/chamfering for the adjacent corners."))
) each crn2,
if (!closed) select(path,-1)
]
) deduplicate(out);
function _corner_chamfer_path(p1, p2, p3, dist1, dist2, side, angle) =
let(
v1 = unit(p1 - p2),
v2 = unit(p3 - p2),
n = vector_axis(v1,v2),
ang = vector_angle(v1,v2),
path = (is_num(dist1) && is_undef(dist2) && is_undef(side))? (
// dist1 & optional angle
assert(dist1 > 0)
let(angle = default(angle,(180-ang)/2))
assert(is_num(angle))
assert(angle > 0 && angle < 180)
let(
pta = p2 + dist1*v1,
a3 = 180 - angle - ang
) assert(a3>0, "Angle too extreme.")
let(
side = sin(angle) * dist1/sin(a3),
ptb = p2 + side*v2
) [pta, ptb]
) : (is_undef(dist1) && is_num(dist2) && is_undef(side))? (
// dist2 & optional angle
assert(dist2 > 0)
let(angle = default(angle,(180-ang)/2))
assert(is_num(angle))
assert(angle > 0 && angle < 180)
let(
ptb = p2 + dist2*v2,
a3 = 180 - angle - ang
) assert(a3>0, "Angle too extreme.")
let(
side = sin(angle) * dist2/sin(a3),
pta = p2 + side*v1
) [pta, ptb]
) : (is_undef(dist1) && is_undef(dist2) && is_num(side))? (
// side & optional angle
assert(side > 0)
let(angle = default(angle,(180-ang)/2))
assert(is_num(angle))
assert(angle > 0 && angle < 180)
let(
a3 = 180 - angle - ang
) assert(a3>0, "Angle too extreme.")
let(
dist1 = sin(a3) * side/sin(ang),
dist2 = sin(angle) * side/sin(ang),
pta = p2 + dist1*v1,
ptb = p2 + dist2*v2
) [pta, ptb]
) : (is_num(dist1) && is_num(dist2) && is_undef(side) && is_undef(side))? (
// dist1 & dist2
assert(dist1 > 0)
assert(dist2 > 0)
let(
pta = p2 + dist1*v1,
ptb = p2 + dist2*v2
) [pta, ptb]
) : (
assert(false,"Bad arguments.")
)
) path;
function _corner_roundover_path(p1, p2, p3, r, d) =
let(
r = get_radius(r=r,d=d,dflt=undef),
res = circle_2tangents(p1, p2, p3, r=r, tangents=true),
cp = res[0],
n = res[1],
tp1 = res[2],
ang = res[4]+res[5],
steps = floor(segs(r)*ang/360+0.5),
step = ang / steps,
path = [for (i=[0:1:steps]) move(cp, p=rot(a=-i*step, v=n, p=tp1-cp))]
) path;
// Function: path3d_spiral()
// Description:
// Returns a 3D spiral path.

View file

@ -15,7 +15,7 @@ done
if [[ "$FILES" != "" ]]; then
PREVIEW_LIBS="$FILES"
else
PREVIEW_LIBS="Transforms Distributors Basic_Shapes FractalTree"
PREVIEW_LIBS="Shapes2d Shapes3d Transforms Distributors Mutators Paths FractalTree"
fi
dir="$(basename $PWD)"

View file

@ -185,7 +185,8 @@ module cuboid(
// Add multi-edge corners.
if (trimcorners) {
for (za=[-1,1], ya=[-1,1], xa=[-1,1]) {
if (corner_edge_count(edges, [xa,ya,za]) > 1) {
ce = corner_edges(edges, [xa,ya,za]);
if (ce.x + ce.y > 1) {
translate(vmul([xa,ya,za]/2, size+[ach-0.01,ach-0.01,-ach])) {
cube([ach+0.01,ach+0.01,ach], center=true);
}
@ -270,7 +271,8 @@ module cuboid(
// Add multi-edge corners.
if (trimcorners) {
for (za=[-1,1], ya=[-1,1], xa=[-1,1]) {
if (corner_edge_count(edges, [xa,ya,za]) > 1) {
ce = corner_edges(edges, [xa,ya,za]);
if (ce.x + ce.y > 1) {
translate(vmul([xa,ya,za]/2, size+[ard-0.01,ard-0.01,-ard])) {
cube([ard+0.01,ard+0.01,ard], center=true);
}
@ -1159,6 +1161,14 @@ module torus(
// Creates a spheroid object, with support for anchoring and attachments.
// This is a drop-in replacement for the built-in `sphere()` module.
// When called as a function, returns a [VNF](vnf.scad) for a spheroid.
// The exact triangulation of this spheroid can be controlled via the `style=`
// argument, where the value can be one of `"orig"`, `"aligned"`, `"stagger"`,
// `"octa"`, or `"icosa"`:
// - `style="orig"` constructs a sphere the same way that the OpenSCAD `sphere()` built-in does.
// - `style="aligned"` constructs a sphere where, if `$fn` is a multiple of 4, it has vertices at all axis maxima and minima. ie: its bounding box is exactly the sphere diameter in length on all three axes. This is the default.
// - `style="stagger"` forms a sphere where all faces are triangular, but the top and bottom poles have thinner triangles.
// - `style="octa"` forms a sphere by subdividing an octahedron (8-sided platonic solid). This makes more uniform faces over the entirety of the sphere, and guarantees the bounding box is the sphere diameter in size on all axes. The effective `$fn` value is quantized to a multiple of 4, though. This is used in constructing rounded corners for various other shapes.
// - `style="icosa"` forms a sphere by subdividing an icosahedron (20-sided platonic solid). This makes even more uniform faces over the entirety of the sphere. The effective `$fn` value is quantized to a multiple of 5, though.
// Arguments:
// r = Radius of the spheroid.
// d = Diameter of the spheroid.
@ -1200,21 +1210,16 @@ module spheroid(r, d, circum=false, style="aligned", anchor=CENTER, spin=0, orie
{
r = get_radius(r=r, d=d, dflt=1);
sides = segs(r);
vsides = ceil(sides/2);
attachable(anchor,spin,orient, r=r) {
if (style=="orig") {
rotate_extrude(convexity=2,$fn=sides) {
difference() {
oval(r=r, circum=circum, realign=true, $fn=sides);
left(r) square(2*r,center=true);
}
}
} else if (style=="aligned") {
rotate_extrude(convexity=2,$fn=sides) {
difference() {
oval(r=r, circum=circum, $fn=sides);
left(r) square(2*r,center=true);
}
}
merids = [ for (i=[0:1:vsides-1]) 90-(i+0.5)*180/vsides ];
path = [
let(a = merids[0]) [0, sin(a)],
for (a=merids) [cos(a), sin(a)],
let(a = select(merids,-1)) [0, sin(a)]
];
scale(r) rotate(180) rotate_extrude(convexity=2,$fn=sides) polygon(path);
} else {
vnf = spheroid(r=r, circum=circum, style=style);
vnf_polyhedron(vnf, convexity=2);
@ -1686,112 +1691,118 @@ module arced_slot(
}
// Module: heightfield()
// Usage:
// heightfield(heightfield, [size], [bottom]);
// Function&Module: heightfield()
// Usage: As Module
// heightfield(data, <size>, <bottom>, <maxz>, <xrange>, <yrange>, <style>, <convexity>);
// Usage: As Function
// vnf = heightfield(data, <size>, <bottom>, <maxz>, <xrange>, <yrange>, <style>);
// Description:
// Given a regular rectangular 2D grid of scalar values, generates a 3D surface where the height at
// any given point is the scalar value for that position.
// Given a regular rectangular 2D grid of scalar values, or a function literal, generates a 3D
// surface where the height at any given point is the scalar value for that position.
// Arguments:
// heightfield = The 2D rectangular array of heights.
// size = The [X,Y] size of the surface to create. If given as a scalar, use it for both X and Y sizes.
// bottom = The Z coordinate for the bottom of the heightfield object to create. Must be less than the minimum heightfield value. Default: 0
// convexity = Max number of times a line could intersect a wall of the surface being formed.
// data = This is either the 2D rectangular array of heights, or a function literal that takes X and Y arguments.
// size = The [X,Y] size of the surface to create. If given as a scalar, use it for both X and Y sizes. Default: `[100,100]`
// bottom = The Z coordinate for the bottom of the heightfield object to create. Any heights lower than this will be truncated to very slightly above this height. Default: -20
// maxz = The maximum height to model. Truncates anything taller to this height. Default: 99
// xrange = A range of values to iterate X over when calculating a surface from a function literal. Default: [-1 : 0.01 : 1]
// yrange = A range of values to iterate Y over when calculating a surface from a function literal. Default: [-1 : 0.01 : 1]
// style = The style of subdividing the quads into faces. Valid options are "default", "alt", and "quincunx". Default: "default"
// convexity = Max number of times a line could intersect a wall of the surface being formed. Module only. Default: 10
// 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. See [spin](attachments.scad#spin). Default: `0`
// orient = Vector to rotate top towards. See [orient](attachments.scad#orient). Default: `UP`
// Example:
// heightfield(size=[100,100], bottom=-20, heightfield=[
// for (x=[-180:4:180]) [for(y=[-180:4:180]) 10*cos(3*norm([x,y]))]
// heightfield(size=[100,100], bottom=-20, data=[
// for (y=[-180:4:180]) [for(x=[-180:4:180]) 10*cos(3*norm([x,y]))]
// ]);
// Example:
// intersection() {
// heightfield(size=[100,100], heightfield=[
// for (x=[-180:5:180]) [for(y=[-180:5:180]) 10+5*cos(3*x)*sin(3*y)]
// heightfield(size=[100,100], data=[
// for (y=[-180:5:180]) [for(x=[-180:5:180]) 10+5*cos(3*x)*sin(3*y)]
// ]);
// cylinder(h=50,d=100);
// }
module heightfield(heightfield, size=[100,100], bottom=0, convexity=10)
// Example(NORENDER): Heightfield by Function
// fn = function (x,y) 10*sin(x*360)*cos(y*360);
// heightfield(size=[100,100], data=fn);
// Example(NORENDER): Heightfield by Function, with Specific Ranges
// fn = function (x,y) 2*cos(5*norm([x,y]));
// heightfield(size=[100,100], bottom=-20, data=fn, xrange=[-180:2:180], yrange=[-180:2:180]);
module heightfield(data, size=[100,100], xrange=[-1:0.04:1], yrange=[-1:0.04:1], bottom=-20, maxz=100, style="default", convexity=10, anchor=CENTER, spin=0, orient=UP)
{
size = is_num(size)? [size,size] : point2d(size);
dim = array_dim(heightfield);
assert(dim.x!=undef);
assert(dim.y!=undef);
assert(bottom<min(flatten(heightfield)), "bottom must be less than the minimum heightfield value.");
spacing = vdiv(size,dim-[1,1]);
vertices = concat(
[
for (i=[0:1:dim.x-1], j=[0:1:dim.y-1]) let(
pos = [i*spacing.x-size.x/2, j*spacing.y-size.y/2, heightfield[i][j]]
) pos
], [
for (i=[0:1:dim.x-1]) let(
pos = [i*spacing.x-size.x/2, -size.y/2, bottom]
) pos
], [
for (i=[0:1:dim.x-1]) let(
pos = [i*spacing.x-size.x/2, size.y/2, bottom]
) pos
], [
for (j=[0:1:dim.y-1]) let(
pos = [-size.x/2, j*spacing.y-size.y/2, bottom]
) pos
], [
for (j=[0:1:dim.y-1]) let(
pos = [size.x/2, j*spacing.y-size.y/2, bottom]
) pos
]
);
faces = concat(
[
for (i=[0:1:dim.x-2], j=[0:1:dim.y-2]) let(
idx1 = (i+0)*dim.y + j+0,
idx2 = (i+0)*dim.y + j+1,
idx3 = (i+1)*dim.y + j+0,
idx4 = (i+1)*dim.y + j+1
) each [[idx1, idx2, idx4], [idx1, idx4, idx3]]
], [
for (i=[0:1:dim.x-2]) let(
idx1 = dim.x*dim.y,
idx2 = dim.x*dim.y+dim.x+i,
idx3 = idx2+1
) [idx1,idx3,idx2]
], [
for (i=[0:1:dim.y-2]) let(
idx1 = dim.x*dim.y,
idx2 = dim.x*dim.y+dim.x*2+dim.y+i,
idx3 = idx2+1
) [idx1,idx2,idx3]
], [
for (i=[0:1:dim.x-2]) let(
idx1 = (i+0)*dim.y+0,
idx2 = (i+1)*dim.y+0,
idx3 = dim.x*dim.y+i,
idx4 = idx3+1
) each [[idx1, idx2, idx4], [idx1, idx4, idx3]]
], [
for (i=[0:1:dim.x-2]) let(
idx1 = (i+0)*dim.y+dim.y-1,
idx2 = (i+1)*dim.y+dim.y-1,
idx3 = dim.x*dim.y+dim.x+i,
idx4 = idx3+1
) each [[idx1, idx4, idx2], [idx1, idx3, idx4]]
], [
for (j=[0:1:dim.y-2]) let(
idx1 = j,
idx2 = j+1,
idx3 = dim.x*dim.y+dim.x*2+j,
idx4 = idx3+1
) each [[idx1, idx4, idx2], [idx1, idx3, idx4]]
], [
for (j=[0:1:dim.y-2]) let(
idx1 = (dim.x-1)*dim.y+j,
idx2 = idx1+1,
idx3 = dim.x*dim.y+dim.x*2+dim.y+j,
idx4 = idx3+1
) each [[idx1, idx2, idx4], [idx1, idx4, idx3]]
]
);
polyhedron(points=vertices, faces=faces, convexity=convexity);
vnf = heightfield(data=data, size=size, xrange=xrange, yrange=yrange, bottom=bottom, maxz=maxz, style=style);
attachable(anchor,spin,orient, vnf=vnf) {
vnf_polyhedron(vnf, convexity=convexity);
children();
}
}
function heightfield(data, size=[100,100], xrange=[-1:0.04:1], yrange=[-1:0.04:1], bottom=-20, maxz=100, style="default", anchor=CENTER, spin=0, orient=UP) =
assert(is_list(data) || is_function(data))
let(
size = is_num(size)? [size,size] : point2d(size),
xvals = is_list(data)
? [for (i=idx(data[0])) i]
: assert(is_list(xrange)||is_range(xrange)) [for (x=xrange) x],
yvals = is_list(data)
? [for (i=idx(data)) i]
: assert(is_list(yrange)||is_range(yrange)) [for (y=yrange) y],
xcnt = len(xvals),
minx = min(xvals),
maxx = max(xvals),
ycnt = len(yvals),
miny = min(yvals),
maxy = max(yvals),
verts = is_list(data) ? [
for (y = [0:1:ycnt-1]) [
for (x = [0:1:xcnt-1]) [
size.x * (x/(xcnt-1)-0.5),
size.y * (y/(ycnt-1)-0.5),
data[y][x]
]
]
] : [
for (y = yrange) [
for (x = xrange) let(
z = data(x,y)
) [
size.x * ((x-minx)/(maxx-minx)-0.5),
size.y * ((y-miny)/(maxy-miny)-0.5),
min(maxz, max(bottom+0.1, default(z,0)))
]
]
],
vnf = vnf_merge([
vnf_vertex_array(verts, style=style, reverse=true),
vnf_vertex_array([
verts[0],
[for (v=verts[0]) [v.x, v.y, bottom]],
]),
vnf_vertex_array([
[for (v=verts[ycnt-1]) [v.x, v.y, bottom]],
verts[ycnt-1],
]),
vnf_vertex_array([
[for (r=verts) let(v=r[0]) [v.x, v.y, bottom]],
[for (r=verts) let(v=r[0]) v],
]),
vnf_vertex_array([
[for (r=verts) let(v=r[xcnt-1]) v],
[for (r=verts) let(v=r[xcnt-1]) [v.x, v.y, bottom]],
]),
vnf_vertex_array([
[
for (v=verts[0]) [v.x, v.y, bottom],
for (r=verts) let(v=r[xcnt-1]) [v.x, v.y, bottom],
], [
for (r=verts) let(v=r[0]) [v.x, v.y, bottom],
for (v=verts[ycnt-1]) [v.x, v.y, bottom],
]
])
])
) reorient(anchor,spin,orient, vnf=vnf, p=vnf);
// vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap

View file

@ -1206,6 +1206,8 @@ module octagon(r, d, or, od, ir, id, side, rounding=0, realign=false, align_tip,
// w2 = The X axis width of the back end of the trapezoid.
// angle = If given in place of `h`, `w1`, or `w2`, then the missing value is calculated such that the right side has that angle away from the Y axis.
// shift = Scalar value to shift the back of the trapezoid along the X axis by. Default: 0
// rounding = The rounding radius for the corners. If given as a list of four numbers, gives individual radii for each corner, in the order [X+Y+,X-Y+,X-Y-,X+Y-]. Default: 0 (no rounding)
// chamfer = The Length of the chamfer faces at the corners. If given as a list of four numbers, gives individual chamfers for each corner, in the order [X+Y+,X-Y+,X-Y-,X+Y-]. Default: 0 (no chamfer)
// 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`
// Examples(2D):
@ -1217,42 +1219,68 @@ module octagon(r, d, or, od, ir, id, side, rounding=0, realign=false, align_tip,
// trapezoid(h=20, w2=10, angle=30);
// trapezoid(h=20, w2=30, angle=-30);
// trapezoid(w1=30, w2=10, angle=30);
// Example(2D): Chamferred Trapezoid
// trapezoid(h=30, w1=60, w2=40, chamfer=5);
// Example(2D): Rounded Trapezoid
// trapezoid(h=30, w1=60, w2=40, rounding=5);
// Example(2D): Mixed Chamfering and Rounding
// trapezoid(h=30, w1=60, w2=40, rounding=[5,0,10,0],chamfer=[0,8,0,15],$fa=1,$fs=1);
// Example(2D): Called as Function
// stroke(closed=true, trapezoid(h=30, w1=40, w2=20));
function trapezoid(h, w1, w2, angle, shift=0, anchor=CENTER, spin=0) =
function trapezoid(h, w1, w2, angle, shift=0, chamfer=0, rounding=0, anchor=CENTER, spin=0) =
assert(is_undef(h) || is_finite(h))
assert(is_undef(w1) || is_finite(w1))
assert(is_undef(w2) || is_finite(w2))
assert(is_undef(angle) || is_finite(angle))
assert(num_defined([h, w1, w2, angle]) == 3, "Must give exactly 3 of the arguments h, w1, w2, and angle.")
assert(is_finite(shift))
assert(is_finite(chamfer) || is_vector(chamfer,4))
assert(is_finite(rounding) || is_vector(rounding,4))
let(
simple = chamfer==0 && rounding==0,
h = !is_undef(h)? h : opp_ang_to_adj(abs(w2-w1)/2, abs(angle)),
w1 = !is_undef(w1)? w1 : w2 + 2*(adj_ang_to_opp(h, angle) + shift),
w2 = !is_undef(w2)? w2 : w1 - 2*(adj_ang_to_opp(h, angle) + shift),
path = [[w1/2,-h/2], [-w1/2,-h/2], [-w2/2+shift,h/2], [w2/2+shift,h/2]]
w2 = !is_undef(w2)? w2 : w1 - 2*(adj_ang_to_opp(h, angle) + shift)
)
assert(w1>=0 && w2>=0 && h>0, "Degenerate trapezoid geometry.")
reorient(anchor,spin, two_d=true, size=[w1,h], size2=w2, p=path);
assert(w1+w2>0, "Degenerate trapezoid geometry.")
let(
base_path = [
[w2/2+shift,h/2],
[-w2/2+shift,h/2],
[-w1/2,-h/2],
[w1/2,-h/2],
],
cpath = simple? base_path :
path_chamfer_and_rounding(
base_path, closed=true,
chamfer=chamfer,
rounding=rounding
),
path = reverse(cpath)
) simple?
reorient(anchor,spin, two_d=true, size=[w1,h], size2=w2, shift=shift, p=path) :
reorient(anchor,spin, two_d=true, path=path, p=path);
module trapezoid(h, w1, w2, angle, shift=0, anchor=CENTER, spin=0) {
assert(is_undef(h) || is_finite(h));
assert(is_undef(w1) || is_finite(w1));
assert(is_undef(w2) || is_finite(w2));
assert(is_undef(angle) || is_finite(angle));
assert(num_defined([h, w1, w2, angle]) == 3, "Must give exactly 3 of the arguments h, w1, w2, and angle.");
assert(is_finite(shift));
module trapezoid(h, w1, w2, angle, shift=0, chamfer=0, rounding=0, anchor=CENTER, spin=0) {
path = trapezoid(h=h, w1=w1, w2=w2, angle=angle, shift=shift, chamfer=chamfer, rounding=rounding);
union() {
simple = chamfer==0 && rounding==0;
h = !is_undef(h)? h : opp_ang_to_adj(abs(w2-w1)/2, abs(angle));
w1 = !is_undef(w1)? w1 : w2 + 2*(adj_ang_to_opp(h, angle) + shift);
w2 = !is_undef(w2)? w2 : w1 - 2*(adj_ang_to_opp(h, angle) + shift);
assert(w1>=0 && w2>=0 && h>0, "Degenerate trapezoid geometry.");
path = [[w1/2,-h/2], [-w1/2,-h/2], [-w2/2+shift,h/2], [w2/2+shift,h/2]];
attachable(anchor,spin, two_d=true, size=[w1,h], size2=w2) {
polygon(path);
children();
if (simple) {
attachable(anchor,spin, two_d=true, size=[w1,h], size2=w2, shift=shift) {
polygon(path);
children();
}
} else {
attachable(anchor,spin, two_d=true, path=path) {
polygon(path);
children();
}
}
}
}
@ -1293,21 +1321,22 @@ module teardrop2d(r, d, ang=45, cap_h, anchor=CENTER, spin=0)
function teardrop2d(r, d, ang=45, cap_h, anchor=CENTER, spin=0) =
let(
r = get_radius(r=r, d=d, dflt=1),
cord = 2 * r * cos(ang),
cord_h = r * sin(ang),
tip_y = (cord/2)/tan(ang),
cap_h = min((!is_undef(cap_h)? cap_h : tip_y+cord_h), tip_y+cord_h),
cap_w = cord * (1 - (cap_h - cord_h)/tip_y),
ang = min(ang,asin(cap_h/r)),
sa = 180 - ang,
ea = 360 + ang,
tanpt = polar_to_xy(r, ang),
tip_y = adj_ang_to_hyp(r, 90-ang),
cap_h = min(default(cap_h,tip_y), tip_y),
cap_w = tanpt.y >= cap_h
? hyp_opp_to_adj(r, cap_h)
: adj_ang_to_opp(tip_y-cap_h, ang),
ang2 = min(ang,atan2(cap_h,cap_w)),
sa = 180 - ang2,
ea = 360 + ang2,
steps = segs(r)*(ea-sa)/360,
step = (ea-sa)/steps,
path = deduplicate(
[
[ cap_w/2,cap_h],
[ cap_w,cap_h],
for (i=[0:1:steps]) let(a=ea-i*step) r*[cos(a),sin(a)],
[-cap_w/2,cap_h]
[-cap_w,cap_h]
], closed=true
),
maxx_idx = max_index(subindex(path,0)),
@ -1325,8 +1354,8 @@ function teardrop2d(r, d, ang=45, cap_h, anchor=CENTER, spin=0) =
// Arguments:
// r = The radius of the end circles.
// d = The diameter of the end circles.
// spread = The distance between the centers of the end circles.
// tangent = The angle in degrees of the tangent point for the joining arcs, measured away from the Y axis.
// spread = The distance between the centers of the end circles. Default: 10
// tangent = The angle in degrees of the tangent point for the joining arcs, measured away from the Y axis. Default: 30
// 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`
// Examples(2D):

273
tutorials/Mutators.md Normal file
View file

@ -0,0 +1,273 @@
# Mutators Tutorial
<!-- TOC -->
## 3D Space Halving
Sometimes you want to take a 3D shape like a sphere, and cut it in half.
The BOSL2 library provides a number of ways to do this:
```openscad
left_half() sphere(d=100);
```
```openscad
right_half() sphere(d=100);
```
```openscad
front_half() sphere(d=100);
```
```openscad
back_half() sphere(d=100);
```
```openscad
bottom_half() sphere(d=100);
```
```openscad
top_half() sphere(d=100);
```
You can use the `half_of()` module if you want to split space in a way not aligned with an axis:
```openscad
half_of([-1,0,-1]) sphere(d=100);
```
The plane of dissection can be shifted along the axis of any of these operators:
```openscad
left_half(x=20) sphere(d=100);
```
```openscad
back_half(y=-20) sphere(d=100);
```
```openscad
bottom_half(z=20) sphere(d=100);
```
```openscad
half_of([-1,0,-1], cp=[20,0,20]) sphere(d=100);
```
By default, these operators can be applied to objects that fit in a cube 1000 on a side. If you need
to apply these halving operators to objects larger than this, you can give the size in the `s=`
argument:
```openscad
bottom_half(s=2000) sphere(d=1500);
```
## 2D Plane Halving
To cut 2D shapes in half, you will need to add the `planar=true` argument:
```openscad
left_half(planar=true) circle(d=100);
```
```openscad
right_half(planar=true) circle(d=100);
```
```openscad
front_half(planar=true) circle(d=100);
```
```openscad
back_half(planar=true) circle(d=100);
```
## Chained Mutators
If you have a set of shapes that you want to do pair-wise hulling of, you can use `chain_hull()`:
```openscad
chain_hull() {
cube(5, center=true);
translate([30, 0, 0]) sphere(d=15);
translate([60, 30, 0]) cylinder(d=10, h=20);
translate([60, 60, 0]) cube([10,1,20], center=false);
}
```
## Extrusion Mutators
The OpenSCAD `linear_extrude()` module can take a 2D shape and extrude it vertically in a line:
```openscad
linear_extrude(height=30) zrot(45) square(40,center=true);
```
The `rotate_extrude()` module can take a 2D shape and rotate it around the Z axis.
```openscad
linear_extrude(height=30) left(30) zrot(45) square(40,center=true);
```
In a similar manner, the BOSL2 `cylindrical_extrude()` module can take a 2d shape and extrude it
out radially from the center of a cylinder:
```openscad
cylindrical_extrude(or=40, ir=35)
text(text="Hello World!", size=10, halign="center", valign="center");
```
## Offset Mutators
### Minkowski Difference
Openscad provides the `minkowski()` module to trace a shape over the entire surface of another shape:
```openscad
minkowski() {
union() {
cube([100,33,33], center=true);
cube([33,100,33], center=true);
cube([33,33,100], center=true);
}
sphere(r=8);
}
```
However, it doesn't provide the inverse of this operation; to remove a shape from the entire surface
of another object. For this, the BOSL2 library provides the `minkowski_difference()` module:
```openscad
minkowski_difference() {
union() {
cube([100,33,33], center=true);
cube([33,100,33], center=true);
cube([33,33,100], center=true);
}
sphere(r=8);
}
```
To perform a `minkowski_difference()` on 2D shapes, you need to supply the `planar=true` argument:
```openscad-2D
minkowski_difference(planar=true) {
union() {
square([100,33], center=true);
square([33,100], center=true);
}
circle(r=8);
}
```
### Round2d
The `round2d()` module lets you take a 2D shape and round inside and outside corners. The inner concave corners are rounded to the radius `ir=`, while the outer convex corners are rounded to the radius `or=`:
```openscad-2D
round2d(or=8) star(6, step=2, d=100);
```
```openscad-2D
round2d(ir=12) star(6, step=2, d=100);
```
```openscad-2D
round2d(or=8,ir=12) star(6, step=2, d=100);
```
You can use `r=` to effectively set both `ir=` and `or=` to the same value:
```openscad-2D
round2d(r=8) star(6, step=2, d=100);
```
### Shell2d
With the `shell2d()` module, you can take an arbitrary shape, and get the shell outline of it.
With a positive thickness, the shell is offset outwards from the original shape:
```openscad-2D
shell2d(thickness=5) star(5,step=2,d=100);
color("blue") stroke(star(5,step=2,d=100),closed=true);
```
With a negative thickness, the shell if inset from the original shape:
```openscad-2D
shell2d(thickness=-5) star(5,step=2,d=100);
color("blue") stroke(star(5,step=2,d=100),closed=true);
```
You can give a pair of thickness values if you want it both inset and outset from the original shape:
```openscad-2D
shell2d(thickness=[-5,5]) star(5,step=2,d=100);
color("blue") stroke(star(5,step=2,d=100),closed=true);
```
You can add rounding to the outside by passing a radius to the `or=` argument.
```openscad-2D
shell2d(thickness=-5,or=5) star(5,step=2,d=100);
```
If you need to pass different radii for the convex and concave corners of the outside, you can pass them as `or=[CONVEX,CONCAVE]`:
```openscad-2D
shell2d(thickness=-5,or=[5,10]) star(5,step=2,d=100);
```
A radius of 0 can be used to specify no rounding:
```openscad-2D
shell2d(thickness=-5,or=[5,0]) star(5,step=2,d=100);
```
You can add rounding to the inside by passing a radius to the `ir=` argument.
```openscad-2D
shell2d(thickness=-5,ir=5) star(5,step=2,d=100);
```
If you need to pass different radii for the convex and concave corners of the inside, you can pass them as `ir=[CONVEX,CONCAVE]`:
```openscad-2D
shell2d(thickness=-5,ir=[8,3]) star(5,step=2,d=100);
```
You can use `or=` and `ir=` together to get nice combined rounding effects:
```openscad-2D
shell2d(thickness=-5,or=[7,2],ir=[7,2]) star(5,step=2,d=100);
```
```openscad-2D
shell2d(thickness=-5,or=[5,0],ir=[5,0]) star(5,step=2,d=100);
```
### Round3d
### Offset3d
(To be Written)
## Color Manipulators
The built-in OpenSCAD `color()` module can let you set the RGB color of an object, but it's often
easier to select colors using other color schemes. You can use the HSL or Hue-Saturation-Lightness
color scheme with the `HSL()` module:
```openscad
for (h=[0:0.1:1], s=[0:0.1:1], l=[0:0.1:1]) {
translate(100*[h,s,l]) {
HSL(h*360,1-s,l) cube(10,center=true);
}
}
```
You can use the HSV or Hue-Saturation-Value color scheme with the `HSV()` module:
```openscad
for (h=[0:0.1:1], s=[0:0.1:1], v=[0:0.1:1]) {
translate(100*[h,s,v]) {
HSV(h*360,1-s,v) cube(10,center=true);
}
}
```

504
tutorials/Paths.md Normal file
View file

@ -0,0 +1,504 @@
# Paths, Polygons and Regions Tutorial
## Paths
A number of advanced features in BOSL2 rely on paths, which are just ordered lists of points.
First-off, some terminology:
- A 2D point is a vectors of X and Y axis position values. ie: `[3,4]` or `[7,-3]`.
- A 3D point is a vectors of X, Y and Z axis position values. ie: `[3,4,2]` or `[-7,5,3]`.
- A 2D path is simply a list of two or more 2D points. ie: `[[5,7], [1,-5], [-5,6]]`
- A 3D path is simply a list of two or more 3D points. ie: `[[5,7,-1], [1,-5,3], [-5,6,1]]`
- A polygon is a 2D (or planar 3D) path where the last point is assumed to connect to the first point.
- A region is a list of 2D polygons, where each polygon is XORed against all the others. ie: if one polygon is inside another, it makes a hole in the first polygon.
### Stroke
A path can be hard to visualize, since it's just a bunch of numbers in the source code.
One way to see the path is to pass it to `polygon()`:
```openscad-2D
path = [[0,0], [-10,10], [0,20], [10,20], [10,10]];
polygon(path);
```
Sometimes, however, it's easier to see just the path itself. For this, you can use the `stroke()` module.
At its most basic, `stroke()` just shows the path's line segments:
```openscad-2D
path = [[0,0], [-10,10], [0,20], [10,20], [10,10]];
stroke(path);
```
You can vary the width of the drawn path with the `width=` argument:
```openscad-2D
path = [[0,0], [-10,10], [0,20], [10,20], [10,10]];
stroke(path, width=3);
```
You can vary the line length along the path by giving a list of widths, one per point:
```openscad-2D
path = [[0,0], [-10,10], [0,20], [10,20], [10,10]];
stroke(path, width=[3,2,1,2,3]);
```
If a path is meant to represent a closed polygon, you can use `closed=true` to show it that way:
```openscad-2D
path = [[0,0], [-10,10], [0,20], [10,20], [10,10]];
stroke(path, closed=true);
```
The ends of the drawn path are normally capped with a "round" endcap, but there are other options:
```openscad-2D
path = [[0,0], [-10,10], [0,20], [10,20], [10,10]];
stroke(path, endcaps="round");
```
```openscad-2D
path = [[0,0], [-10,10], [0,20], [10,20], [10,10]];
stroke(path, endcaps="butt");
```
```openscad-2D
path = [[0,0], [-10,10], [0,20], [10,20], [10,10]];
stroke(path, endcaps="line");
```
```openscad-2D
path = [[0,0], [-10,10], [0,20], [10,20], [10,10]];
stroke(path, endcaps="tail");
```
```openscad-2D
path = [[0,0], [-10,10], [0,20], [10,20], [10,10]];
stroke(path, endcaps="arrow2");
```
For more standard supported endcap options, see the docs for [`stroke()`](shapes2d.scad#stroke).
The start and ending endcaps can be specified individually or separately, using `endcap1=` and `endcap2=`:
```openscad-2D
path = [[0,0], [-10,10], [0,20], [10,20], [10,10]];
stroke(path, endcap2="arrow2");
```
```openscad-2D
path = [[0,0], [-10,10], [0,20], [10,20], [10,10]];
stroke(path, endcap1="butt", endcap2="arrow2");
```
```openscad-2D
path = [[0,0], [-10,10], [0,20], [10,20], [10,10]];
stroke(path, endcap1="tail", endcap2="arrow");
```
The size of the endcaps will be relative to the width of the line where the endcap is to be placed:
```openscad-2D
path = [[0,0], [-10,10], [0,20], [10,20], [10,10]];
widths = [1, 1.25, 1.5, 1.75, 2];
stroke(path, width=widths, endcaps="arrow2");
```
If none of the standard endcaps are useful to you, it is possible to design your own, simply by
passing a path to the `endcaps=`, `endcap1=`, or `endcap2=` arguments. You may also need to give
`trim=` to tell it how far back to trim the main line, so it renders nicely. The values in the
endcap polygon, and in the `trim=` argument are relative to the line width. A value of 1 is one
line width size.
Untrimmed:
```openscad-2D
path = [[0,0], [-10,10], [0,20], [10,20], [10,10]];
dblarrow = [[0,0], [2,-3], [0.5,-2.3], [2,-4], [0.5,-3.5], [-0.5,-3.5], [-2,-4], [-0.5,-2.3], [-2,-3]];
stroke(path, endcaps=dblarrow);
```
Trimmed:
```openscad-2D
path = [[0,0], [-10,10], [0,20], [10,20], [10,10]];
dblarrow = [[0,0], [2,-3], [0.5,-2.3], [2,-4], [0.5,-3.5], [-0.5,-3.5], [-2,-4], [-0.5,-2.3], [-2,-3]];
stroke(path, trim=3.5, endcaps=dblarrow);
```
### Standard 2D Shape Polygons
BOSL2 will let you get the perimeter polygon for almost all of the standard 2D shapes simply by calling them like a function:
```openscad-2D
path = square(40, center=true);
stroke(path, closed=true, endcap2="arrow2");
```
```openscad-2D
path = rect([40,30], rounding=5, center=true);
stroke(path, closed=true, endcap2="arrow2");
```
```openscad-2D
path = trapezoid(w1=40, w2=20, h=30);
stroke(path, closed=true, endcap2="arrow2");
```
```openscad-2D
path = circle(d=50);
stroke(path, closed=true, endcap2="arrow2");
```
```openscad-2D
path = oval(d=[50,30]);
stroke(path, closed=true, endcap2="arrow2");
```
```openscad-2D
path = pentagon(d=50);
stroke(path, closed=true, endcap2="arrow2");
```
```openscad-2D
path = star(n=5, step=2, d=50);
stroke(path, closed=true, endcap2="arrow2");
```
### Arcs
Often, when you are constructing a path, you will want to add an arc. The `arc()` command lets you do that:
```openscad-2D
path = arc(r=30, angle=120);
stroke(path, endcap2="arrow2");
```
```openscad-2D
path = arc(d=60, angle=120);
stroke(path, endcap2="arrow2");
```
If you give the `N=` argument, you can control exactly how many points the arc is divided into:
```openscad-2D
path = arc(N=5, r=30, angle=120);
stroke(path, endcap2="arrow2");
```
With the `start=` argument, you can start the arc somewhere other than the X+ axis:
```openscad-2D
path = arc(start=45, r=30, angle=120);
stroke(path, endcap2="arrow2");
```
Alternatively, you can give starting and ending angles in a list in the `angle=` argument:
```openscad-2D
path = arc(angle=[120,45], r=30);
stroke(path, endcap2="arrow2");
```
The `cp=` argument lets you center the arc somewhere other than the origin:
```openscad-2D
path = arc(cp=[10,0], r=30, angle=120);
stroke(path, endcap2="arrow2");
```
The arc can also be given by three points on the arc:
```openscad-2D
pts = [[-15,10],[0,20],[35,-5]];
path = arc(points=pts);
stroke(path, endcap2="arrow2");
```
### Turtle Graphics
Another way you can create a path is using the `turtle()` command. It implements a simple path
description language that is similar to LOGO Turtle Graphics. The concept is that you have a virtial
turtle or cursor walking a path. It can "move" forward or backward, or turn "left" or "right" in
place:
```openscad-2D
path = turtle([
"move", 10,
"left", 90,
"move", 20,
"left", 135,
"move", 10*sqrt(2),
"right", 90,
"move", 10*sqrt(2),
"left", 135,
"move", 20
]);
stroke(path, endcap2="arrow2");
```
The position and the facing of the turtle/cursor updates after each command. The motion and turning
commands can also have default distances or angles given:
```openscad-2D
path = turtle([
"angle",360/6,
"length",10,
"move","turn",
"move","turn",
"move","turn",
"move","turn",
"move"
]);
stroke(path, endcap2="arrow2");
```
You can use "scale" to relatively scale up the default motion length:
```openscad-2D
path = turtle([
"angle",360/6,
"length",10,
"move","turn",
"move","turn",
"scale",2,
"move","turn",
"move","turn",
"scale",0.5,
"move"
]);
stroke(path, endcap2="arrow2");
```
Sequences of commands can be repeated using the "repeat" command:
```openscad-2D
path=turtle([
"angle",360/5,
"length",10,
"repeat",5,["move","turn"]
]);
stroke(path, endcap2="arrow2");
```
More complicated commands also exist, including those that form arcs:
```openscad-2D
path = turtle([
"move", 10,
"left", 90,
"move", 20,
"arcleft", 10, 180,
"move", 20
]);
stroke(path, endcap2="arrow2");
```
A comprehensive list of supported turtle commands can be found in the docs for [`turtle()`](shapes2d.scad#turtle).
### Transforming Paths and Polygons
To translate a path, you can just pass it to the `move()` (or up/down/left/right/fwd/back) function in the `p=` argument:
```openscad-2D
path = move([-15,-30], p=square(50,center=true));
stroke(path, closed=true, endcap2="arrow2");
```
```openscad-2D
path = fwd(30, p=square(50,center=true));
stroke(path, closed=true, endcap2="arrow2");
```
```openscad-2D
path = left(30, p=square(50,center=true));
stroke(path, closed=true, endcap2="arrow2");
```
To scale a path, you can just pass it to the `scale()` (or [xyz]scale) function in the `p=` argument:
```openscad-2D
path = scale([1.5,0.75], p=square(50,center=true));
stroke(path, closed=true, endcap2="arrow2");
```
```openscad-2D
path = xscale(1.5, p=square(50,center=true));
stroke(path, closed=true, endcap2="arrow2");
```
```openscad-2D
path = yscale(1.5, p=square(50,center=true));
stroke(path, closed=true, endcap2="arrow2");
```
To rotate a path, just can pass it to the `rot()` (or [xyz]rot) function in the `p=` argument:
```openscad-2D
path = rot(30, p=square(50,center=true));
stroke(path, closed=true, endcap2="arrow2");
```
```openscad-2D
path = zrot(30, p=square(50,center=true));
stroke(path, closed=true, endcap2="arrow2");
```
To mirror a path, just can pass it to the `mirror()` (or [xyz]flip) function in the `p=` argument:
```openscad-2D
path = mirror([1,1], p=trapezoid(w1=40, w2=10, h=25));
stroke(path, closed=true, endcap2="arrow2");
```
```openscad-2D
path = xflip(p=trapezoid(w1=40, w2=10, h=25));
stroke(path, closed=true, endcap2="arrow2");
```
```openscad-2D
path = yflip(p=trapezoid(w1=40, w2=10, h=25));
stroke(path, closed=true, endcap2="arrow2");
```
You can get raw transformation matrices for various transformations by calling them like a function without a `p=` argument:
```openscad-2D
mat = move([5,10,0]);
multmatrix(mat) square(50,center=true);
```
```openscad-2D
mat = scale([1.5,0.75,1]);
multmatrix(mat) square(50,center=true);
```
```openscad-2D
mat = rot(30);
multmatrix(mat) square(50,center=true);
```
Raw transformation matrices can be multiplied together to precalculate a compound transformation. For example, to scale a shape, then rotate it, then translate the result, you can do something like:
```openscad-2D
mat = move([5,10,0]) * rot(30) * scale([1.5,0.75,1]);
multmatrix(mat) square(50,center=true);
```
To apply a compound transformation matrix to a path, you can use the `apply()` function:
```openscad-2D
mat = move([5,10]) * rot(30, planar=true) * scale([1.5,0.75]);
path = square(50,center=true);
tpath = apply(mat, path);
stroke(tpath, endcap2="arrow2");
```
### Regions
A polygon is good to denote a single closed 2D shape with no holes in it. For more complex 2D
shapes, you will need to use regions. A region is a list of 2D polygons, where each polygon is
XORed against all the others. You can display a region using the `region()` module.
If you have a region with one polygon fully inside another, it makes a hole:
```openscad-2D
rgn = [square(50,center=true), circle(d=30)];
region(rgn);
```
If you have a region with multiple polygons that are not contained by any others, they make multiple discontiguous shapes:
```openscad-2D
rgn = [
move([-30, 20], p=square(20,center=true)),
move([ 0,-20], p=trapezoid(w1=20, w2=10, h=20)),
move([ 30, 20], p=square(20,center=true)),
];
region(rgn);
```
Region polygons can be nested abitrarily deep, in multiple discontiguous shapes:
```openscad-2D
rgn = [
for (d=[50:-10:10]) left(30, p=circle(d=d)),
for (d=[50:-10:10]) right(30, p=circle(d=d))
];
region(rgn);
```
A region with crossing polygons is somewhat poorly formed, but the intersection(s) of the polygons become holes:
```openscad-2D
rgn = [
left(15, p=circle(d=50)),
right(15, p=circle(d=50))
];
region(rgn);
```
### Boolean Region Geometry
Similarly to how OpenSCAD can perform operations like union/difference/intersection/offset on shape geometry,
the BOSL2 library lets you perform those same operations on regions:
```openscad-2D
rgn1 = [for (d=[40:-10:10]) circle(d=d)];
rgn2 = [square([60,12], center=true)];
rgn = union(rgn1, rgn2);
region(rgn);
```
```openscad-2D
rgn1 = [for (d=[40:-10:10]) circle(d=d)];
rgn2 = [square([60,12], center=true)];
rgn = difference(rgn1, rgn2);
region(rgn);
```
```openscad-2D
rgn1 = [for (d=[40:-10:10]) circle(d=d)];
rgn2 = [square([60,12], center=true)];
rgn = intersection(rgn1, rgn2);
region(rgn);
```
```openscad-2D
rgn1 = [for (d=[40:-10:10]) circle(d=d)];
rgn2 = [square([60,12], center=true)];
rgn = exclusive_or(rgn1, rgn2);
region(rgn);
```
```openscad-2D
orig_rgn = [star(n=5, step=2, d=50)];
rgn = offset(orig_rgn, r=-3, closed=true);
color("blue") region(orig_rgn);
region(rgn);
```
You can use regions for several useful things. If you wanted a grid of holes in your object that
form the shape given by a region, you can do that with `grid2d()`:
```openscad-3D
rgn = [
circle(d=100),
star(n=5,step=2,d=100,spin=90)
];
difference() {
cyl(h=5, d=120);
grid2d(size=[120,120], spacing=[4,4], inside=rgn) cyl(h=10,d=2);
}
```
You can also sweep a region through 3-space to make a solid:
```openscad-3D
$fa=1; $fs=1;
rgn = [ for (d=[50:-10:10]) circle(d=d) ];
tforms = [
for (a=[90:-5:0]) xrot(a, cp=[0,-70]),
for (a=[0:5:90]) xrot(a, cp=[0,70]),
move([0,150,-70]) * xrot(90),
];
sweep(rgn, tforms, closed=false, caps=true);
```

553
tutorials/Shapes2d.md Normal file
View file

@ -0,0 +1,553 @@
# 2D Shapes Tutorial
## Primitives
There are two built-in 2D primitive shapes that OpenSCAD provides: `square()`, and `circle()`.
The BOSL2 library 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:
```openscad-2D
square(100, center=false);
```
```openscad-2D
square(100, center=true);
```
```openscad-2D
square([60,40], center=true);
```
The BOSL2 library provides an enhanced equivalent to `square()` called `rect()`.
You can use it in the same way you use `square()`, but it also provides
extended functionality. For example, it allows you to round the corners:
```openscad-2D
rect([60,40], center=true, rounding=10);
```
Or chamfer them:
```openscad-2D
rect([60,40], center=true, chamfer=10);
```
You can even specify *which* corners get rounded or chamfered. If you pass a
list of four size numbers to the `rounding=` or `chamfer=` arguments, it will
give each corner its own size. In order, it goes from the back-right (quadrant I)
corner, counter-clockwise around to the back-left (quadrant II) corner, to the
forward-left (quadrant III) corner, to the forward-right (quadrant IV) corner:
```openscad-2DImgOnly
module text3d(text) color("black") text(
text=text, font="Times", size=10,
halign="center", valign="center"
);
translate([ 50, 50]) text3d("I");
translate([-50, 50]) text3d("II");
translate([-50,-50]) text3d("III");
translate([ 50,-50]) text3d("IV");
rect([90,80], center=true);
```
If a size is given as `0`, then there is no rounding and/or chamfering for
that quadrant's corner:
```openscad-2D
rect([60,40], center=true, rounding=[0,5,10,15]);
```
```openscad-2D
rect([60,40], center=true, chamfer=[0,5,10,15]);
```
You can give both `rounding=` and `chamfer=` arguments to mix rounding and
chamfering, but only if you specify per corner. If you want a rounding in
a corner, specify a 0 chamfer for that corner, and vice versa:
```openscad-2D
rect([60,40], center=true, rounding=[5,0,10,0], chamfer=[0,5,0,15]);
```
#### Anchors and Spin
Another way that `rect()` is enhanced over `square()`, is that you can anchor,
spin and attach it.
The `anchor=` argument is an alternative to `center=`, which allows more
alignment options. It takes a vector as a value, pointing roughly towards
the side or corner you want to align to the origin. For example, to align
the center of the back edge to the origin, set the anchor to `[0,1]`:
```openscad-2D
rect([60,40], anchor=[0,1]);
```
To align the front right corner to the origin:
```openscad-2D
rect([60,40], anchor=[1,-1]);
```
To center:
```openscad-2D
rect([60,40], anchor=[0,0]);
```
To make it clearer when giving vectors, there are several standard vector
constants defined:
Constant | Direction | Value
-------- | --------- | -----------
`LEFT` | X- | `[-1, 0, 0]`
`RIGHT` | X+ | `[ 1, 0, 0]`
`FRONT`/`FORWARD`/`FWD` | Y- | `[ 0,-1, 0]`
`BACK` | Y+ | `[ 0, 1, 0]`
`BOTTOM`/`BOT`/`BTM`/`DOWN` | Z- | `[ 0, 0,-1]` (3D only.)
`TOP`/`UP` | Z+ | `[ 0, 0, 1]` (3D only.)
`CENTER`/`CTR` | Centered | `[ 0, 0, 0]`
Note that even though these are 3D vectors, you can use most of them,
(except `UP`/`DOWN`, of course) for anchors in 2D shapes:
```openscad-2D
rect([60,40], anchor=BACK);
```
```openscad-2D
rect([60,40], anchor=CENTER);
```
You can add vectors together to point to corners:
```openscad-2D
rect([60,40], anchor=FRONT+RIGHT);
```
Finally, the `spin` argument can rotate the shape by a given number of degrees
clockwise:
```openscad-2D
rect([60,40], anchor=CENTER, spin=30);
```
Anchoring or centering is performed before the spin:
```openscad-2D
rect([60,40], anchor=BACK, spin=30);
```
Anchor points double as attachment points, so that you can attach other shapes:
```openscad-2D
rect([60,40],center=true)
show_anchors();
```
### 2D Circles and Ovals
The built-in `circle()` primitive can be used as expected:
```openscad-2D
circle(r=50);
```
```openscad-2D
circle(d=100);
```
```openscad-2D
circle(d=100, $fn=8);
```
The BOSL2 library also provides an enhanced equivalent of `circle()` called `oval()`.
You can use it in the same way you use `circle()`, but it also provides extended
functionality. For example, it allows more control over its size and orientation.
Since a circle in OpenSCAD can only be approximated by a regular polygon with
a number of straight sides, this can lead to size and shape inaccuracies.
To counter this, the `realign=` and `circum=` arguments are also provided.
The `realign=` argument, if set `true`, rotates the `oval()` by half the angle
between the sides:
```openscad-2D
oval(d=100, $fn=8, realign=true);
```
The `circum=` argument, if true, makes it so that the polygon forming the
`oval()` circumscribes the ideal circle instead of inscribing it.
Inscribing the ideal circle:
```openscad-2D
difference() {
circle(d=100, $fn=360);
oval(d=100, $fn=8);
}
```
Circumscribing the ideal circle:
```openscad-2D
difference() {
oval(d=100, $fn=8, circum=true);
circle(d=100, $fn=360);
}
```
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.
```openscad-2D
oval(r=50, anchor=BACK);
```
```openscad-2D
oval(r=50, anchor=FRONT+RIGHT);
```
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);
```
### Trapezoids
OpenSCAD doesn't provide a simple way to make 2D triangles, trapezoids, or parallelograms.
The BOSL2 library can provide all of these shapes with the `trapezoid()` module.
To make a simple triangle, just make one of the widths zero:
```openscad-2D
trapezoid(w1=50, w2=0, h=50);
```
To make a right triangle, you need to use the `shift=` argument, to shift the back of the trapezoid along the X axis:
```openscad-2D
trapezoid(w1=50, w2=0, h=50, shift=-25);
```
```openscad-2D
trapezoid(w1=50, w2=0, h=50, shift=25);
```
```openscad-2D
trapezoid(w1=0, w2=50, h=50, shift=-25);
```
```openscad-2D
trapezoid(w1=0, w2=50, h=50, shift=25);
```
You can make a trapezoid by specifying non-zero widths for both the front (`w1=`) and back (`w2=`):
```openscad-2D
trapezoid(w1=30, w2=50, h=50);
```
A parallelogram is just a matter of using the same width for front and back, with a shift along the X axis:
```openscad-2D
trapezoid(w1=50, w2=50, shift=20, h=50);
```
A quadrilateral can be made by having unequal, non-zero front (`w1=`) and back (`w2=`) widths, with the back shifted along the X axis:
```openscad-2D
trapezoid(w1=50, w2=30, shift=20, h=50);
```
You can use `anchor=` and `spin=`, just like with other attachable shapes. However, the anchor
points are based on the side angles of the faces, and may not be where you expect them:
```openscad-2D
trapezoid(w1=30, w2=50, h=50)
show_anchors();
```
### Regular N-Gons
OpenSCAD lets you make regular N-gons (pentagon, hexagon, etc) by using `circle()` with `$fn`.
While this is concise, it may be less than obvious at first glance:
```openscad-2D
circle(d=50, $fn=5);
```
The BOSL2 library has modules that are named more clearly:
```openscad-2D
pentagon(d=50);
```
```openscad-2D
hexagon(d=50);
```
```openscad-2D
octagon(d=50);
```
```openscad-2D
regular_ngon(n=7, d=50);
```
These modules also provide you with extra functionality.
They can be sized by side length:
```openscad-2D
pentagon(side=20);
```
They can be sized by circumscribed circle radius/diameter:
```openscad-2D
pentagon(ir=25);
pentagon(id=50);
```
They can be realigned by half a side's angle:
```openscad-2D
left(30) pentagon(d=50, realign=true);
right(30) pentagon(d=50, realign=false);
```
They can be rounded:
```openscad-2D
pentagon(d=50, rounding=10);
```
```openscad-2D
hexagon(d=50, rounding=10);
```
They also have somewhat different attachment behavior:
```openscad-2D
color("green") stroke(circle(d=50), closed=true);
oval(d=50,$fn=5)
attach(LEFT) color("blue") anchor_arrow2d();
```
```openscad-2D
pentagon(d=50)
attach(LEFT) color("blue") anchor_arrow2d();
```
You can use `anchor=` and `spin=`, just like with other attachable shapes. However, the anchor
points are based on where the anchor vector would intersect the side of the N-gon, and may not
be where you expect them:
```openscad-2D
pentagon(d=50)
show_anchors(custom=false);
```
N-gons also have named anchor points for their sides and tips:
```openscad-2D
pentagon(d=30)
show_anchors(std=false);
```
### Stars
The BOSL2 library has stars as a basic supported shape. They can have any number of points.
You can specify a star's shape by point count, inner and outer vertex radius/diameters:
```openscad-2D
star(n=3, id=10, d=50);
```
```openscad-2D
star(n=5, id=15, r=25);
```
```openscad-2D
star(n=10, id=30, d=50);
```
Or you can specify the star shape by point count and number of points to step:
```openscad-2D
star(n=7, step=2, d=50);
```
```openscad-2D
star(n=7, step=3, d=50);
```
If the `realign=` argument is given a true value, then the star will be rotated by half a point angle:
```openscad-2D
left(30) star(n=5, step=2, d=50);
right(30) star(n=5, step=2, d=50, realign=true);
```
The `align_tip=` argument can be given a vector so that you can align the first point in a specific direction:
```openscad-2D
star(n=5, ir=15, or=30, align_tip=BACK+LEFT)
attach("tip0") color("blue") anchor_arrow2d();
```
```openscad-2D
star(n=5, ir=15, or=30, align_tip=BACK+RIGHT)
attach("tip0") color("blue") anchor_arrow2d();
```
Similarly, the first indentation or pit can be oriented towards a specific vector with `align_pit=`:
```openscad-2D
star(n=5, ir=15, or=30, align_pit=BACK+LEFT)
attach("pit0") color("blue") anchor_arrow2d();
```
```openscad-2D
star(n=5, ir=15, or=30, align_pit=BACK+RIGHT)
attach("pit0") color("blue") anchor_arrow2d();
```
You can use `anchor=` and `spin=`, just like with other attachable shapes. However, the anchor
points are based on the furthest extents of the shape, and may not be where you expect them:
```openscad-2D
star(n=5, step=2, d=50)
show_anchors(custom=false);
```
Stars also have named anchor points for their pits, tips, and midpoints between tips:
```openscad-2D
star(n=5, step=2, d=40)
show_anchors(std=false);
```
### Teardrop2D
Often when 3D printing, you may want to make a circular hole in a vertical wall. If the hole is
too big, however, the overhang at the top of the hole can cause problems with printing on an
FDM/FFF printer. If you don't want to use support material, you can just use the teardrop shape.
The `teardrop2d()` module will let you make a 2D version of the teardrop shape, so that you can
extrude it later:
```openscad-2D
teardrop2d(r=20);
```
```openscad-2D
teardrop2d(d=50);
```
The default overhang angle is 45 degrees, but you can adjust that with the `ang=` argument:
```openscad-2D
teardrop2d(d=50, ang=30);
```
If you prefer to flatten the top of the teardrop, to encourage bridging, you can use the `cap_h=`
argument:
```openscad-2D
teardrop2d(d=50, cap_h=25);
```
```openscad-2D
teardrop2d(d=50, ang=30, cap_h=30);
```
You can use `anchor=` and `spin=`, just like with other attachable shapes. However, the anchor
points are based on the furthest extents of the shape, and may not be where you expect them:
```openscad-2D
teardrop2d(d=50, ang=30, cap_h=30)
show_anchors();
```
### Glued Circles
A more unusal shape that BOSL2 provides is Glued Circles. It's basically a pair of circles,
connected by what looks like a gloopy glued miniscus:
```openscad-2D
glued_circles(d=30, spread=40);
```
The `r=`/`d=` arguments can specify the radius or diameter of the two circles:
```openscad-2D
glued_circles(r=20, spread=45);
```
```openscad-2D
glued_circles(d=40, spread=45);
```
The `spread=` argument specifies the distance between the centers of the two circles:
```openscad-2D
glued_circles(d=30, spread=30);
```
```openscad-2D
glued_circles(d=30, spread=40);
```
The `tangent=` argument gives the angle of the tangent of the meniscus on the two circles:
```openscad-2D
glued_circles(d=30, spread=30, tangent=45);
```
```openscad-2D
glued_circles(d=30, spread=30, tangent=20);
```
```openscad-2D
glued_circles(d=30, spread=30, tangent=-20);
```
One useful thing you can do is to string a few `glued_circle()`s in a line then extrude them to make a ribbed wall:
```openscad-3D
$fn=36; s=10;
linear_extrude(height=50,convexity=16,center=true)
xcopies(s*sqrt(2),n=3)
glued_circles(d=s, spread=s*sqrt(2), tangent=45);
```
You can use `anchor=` and `spin=`, just like with other attachable shapes. However, the anchor
points are based on the furthest extents of the shape, and may not be where you expect them:
```openscad-2D
glued_circles(d=40, spread=40, tangent=45)
show_anchors();
```

View file

@ -1,233 +1,13 @@
# Basic Shapes Tutorial
## Primitives
There are 5 built-in primitive shapes that OpenSCAD provides.
`square()`, `circle()`, `cube()`, `cylinder()`, and `sphere()`.
The BOSL2 library extends or provides alternative to these shapes so
There are 3 built-in 3D primitive shapes that OpenSCAD provides: `cube()`, `cylinder()`,
and `sphere()`. The BOSL2 library extends and 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:
```openscad-2D
square(100, center=false);
```
```openscad-2D
square(100, center=true);
```
```openscad-2D
square([60,40], center=true);
```
The BOSL2 library provides an enhanced equivalent to `square()` called `rect()`.
You can use it in the same way you use `square()`, but it also provides
extended functionality. For example, it allows you to round the corners:
```openscad-2D
rect([60,40], center=true, rounding=10);
```
Or chamfer them:
```openscad-2D
rect([60,40], center=true, chamfer=10);
```
You can even specify *which* corners get rounded or chamfered. If you pass a
list of four size numbers to the `rounding=` or `chamfer=` arguments, it will
give each corner its own size. In order, it goes from the back-right (quadrant I)
corner, counter-clockwise around to the back-left (quadrant II) corner, to the
forward-left (quadrant III) corner, to the forward-right (quadrant IV) corner:
```openscad-2DImgOnly
module text3d(text) color("black") text(
text=text, font="Times", size=10,
halign="center", valign="center"
);
translate([ 50, 50]) text3d("I");
translate([-50, 50]) text3d("II");
translate([-50,-50]) text3d("III");
translate([ 50,-50]) text3d("IV");
rect([90,80], center=true);
```
If a size is given as `0`, then there is no rounding and/or chamfering for
that quadrant's corner:
```openscad-2D
rect([60,40], center=true, rounding=[0,5,10,15]);
```
```openscad-2D
rect([60,40], center=true, chamfer=[0,5,10,15]);
```
You can give both `rounding=` and `chamfer=` arguments to mix rounding and
chamfering, but only if you specify per corner. If you want a rounding in
a corner, specify a 0 chamfer for that corner, and vice versa:
```openscad-2D
rect([60,40], center=true, rounding=[5,0,10,0], chamfer=[0,5,0,15]);
```
#### Anchors and Spin
Another way that `rect()` is enhanced over `square()`, is that you can anchor,
spin and attach it.
The `anchor=` argument is an alternative to `center=`, which allows more
alignment options. It takes a vector as a value, pointing roughly towards
the side or corner you want to align to the origin. For example, to align
the center of the back edge to the origin, set the anchor to `[0,1]`:
```openscad-2D
rect([60,40], anchor=[0,1]);
```
To align the front right corner to the origin:
```openscad-2D
rect([60,40], anchor=[1,-1]);
```
To center:
```openscad-2D
rect([60,40], anchor=[0,0]);
```
To make it clearer when giving vectors, there are several standard vector
constants defined:
Constant | Direction | Value
-------- | --------- | -----------
`LEFT` | X- | `[-1, 0, 0]`
`RIGHT` | X+ | `[ 1, 0, 0]`
`FRONT`/`FORWARD`/`FWD` | Y- | `[ 0,-1, 0]`
`BACK` | Y+ | `[ 0, 1, 0]`
`BOTTOM`/`BOT`/`BTM`/`DOWN` | Z- | `[ 0, 0,-1]` (3D only.)
`TOP`/`UP` | Z+ | `[ 0, 0, 1]` (3D only.)
`CENTER`/`CTR` | Centered | `[ 0, 0, 0]`
Note that even though these are 3D vectors, you can use most of them,
(except `UP`/`DOWN`, of course) for anchors in 2D shapes:
```openscad-2D
rect([60,40], anchor=BACK);
```
```openscad-2D
rect([60,40], anchor=CENTER);
```
You can add vectors together to point to corners:
```openscad-2D
rect([60,40], anchor=FRONT+RIGHT);
```
Finally, the `spin` argument can rotate the shape by a given number of degrees
clockwise:
```openscad-2D
rect([60,40], anchor=CENTER, spin=30);
```
Anchoring or centering is performed before the spin:
```openscad-2D
rect([60,40], anchor=BACK, spin=30);
```
### 2D Circles
The built-in `circle()` primitive can be used as expected:
```openscad-2D
circle(r=50);
```
```openscad-2D
circle(d=100);
```
```openscad-2D
circle(d=100, $fn=8);
```
The BOSL2 library provides an enhanced equivalent of `circle()` called `oval()`.
You can use it in the same way you use `circle()`, but it also provides
extended functionality. For example, it allows more control over its size and
orientation.
Since a circle in OpenSCAD can only be approximated by a regular polygon with
a number of straight sides, this can lead to size and shape inaccuracies.
To counter this, the `realign=` and `circum=` arguments are also provided.
The `realign=` argument, if set `true`, rotates the `oval()` by half the angle
between the sides:
```openscad-2D
oval(d=100, $fn=8, realign=true);
```
The `circum=` argument, if true, makes it so that the polygon forming the
`oval()` circumscribes the ideal circle instead of inscribing it.
Inscribing the ideal circle:
```openscad-2D
difference() {
circle(d=100, $fn=360);
oval(d=100, $fn=8);
}
```
Circumscribing the ideal circle:
```openscad-2D
difference() {
oval(d=100, $fn=8, circum=true);
circle(d=100, $fn=360);
}
```
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.
```openscad-2D
oval(r=50, anchor=BACK);
```
```openscad-2D
oval(r=50, anchor=FRONT+RIGHT);
```
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);
```
### 3D Cubes
BOSL2 overrides the built-in `cube()` module. It still can be used as you
expect from the built-in:
BOSL2 overrides the built-in `cube()` module. It still can be used as you expect from the built-in:
```openscad-3D
cube(100);
@ -243,7 +23,7 @@ expect from the built-in:
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()`,
You can use `anchor=` similarly to how you use it with `rect()` or `oval()`,
except you can also anchor vertically in 3D, allowing anchoring to faces, edges,
and corners:
@ -546,11 +326,20 @@ The "stagger" style will stagger the triangulation of the vertical rows:
spheroid(d=100, style="stagger", $fn=20);
```
The "icosa"` style will make for roughly equal-sized triangles for the entire
sphere surface:
The "icosa" style will make for roughly equal-sized triangles for the entire
sphere surface, based on subdividing an icosahedron. This style will round the
effective `$fn` to a multiple of 5 when constructing the spheroid:
```openscad-3D
spheroid(d=100, style="icosa", $fn=20);
```
The "octa" style will also make for roughly equal-sized triangles for the entire
sphere surface, but based on subdividing an octahedron. This is useful in that it
guarantees vertices at the axis extrema. This style will round the effective `$fn`
to a multiple of 4 when constructing the spheroid:
```openscad-3D
spheroid(d=100, style="octa", $fn=20);
```

View file

@ -8,7 +8,7 @@
//////////////////////////////////////////////////////////////////////
BOSL_VERSION = [2,0,482];
BOSL_VERSION = [2,0,505];
// Section: BOSL Library Version Functions