From 69124938a6d4eec9a905e6326a0dcfc678d11ba1 Mon Sep 17 00:00:00 2001 From: Garth Minette Date: Tue, 29 Dec 2020 21:02:46 -0800 Subject: [PATCH 1/7] Added planer=true to minkowski_difference() and bounding_box(). Refactored shell2d(). --- mutators.scad | 229 ++++++++++++++++++++++++++------------------------ version.scad | 2 +- 2 files changed, 121 insertions(+), 110 deletions(-) diff --git a/mutators.scad b/mutators.scad index 80b9752..1e54c6f 100644 --- a/mutators.scad +++ b/mutators.scad @@ -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,41 @@ // } // 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 + union() { + rotate(-90) _xProjection() rotate(90) children(); // y axis + if(!planar) 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 +219,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 +525,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 +664,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); - } - } - } } diff --git a/version.scad b/version.scad index e533b27..9757673 100644 --- a/version.scad +++ b/version.scad @@ -8,7 +8,7 @@ ////////////////////////////////////////////////////////////////////// -BOSL_VERSION = [2,0,482]; +BOSL_VERSION = [2,0,483]; // Section: BOSL Library Version Functions From a9c3b1f244d21cf13fe99d0408deaff7a83884c6 Mon Sep 17 00:00:00 2001 From: Garth Minette Date: Tue, 29 Dec 2020 21:08:41 -0800 Subject: [PATCH 2/7] Initial mutators tutorial. --- tutorials/Mutators.md | 273 ++++++++++++++++++++++++++++++++++++++++++ version.scad | 2 +- 2 files changed, 274 insertions(+), 1 deletion(-) create mode 100644 tutorials/Mutators.md diff --git a/tutorials/Mutators.md b/tutorials/Mutators.md new file mode 100644 index 0000000..1b0df85 --- /dev/null +++ b/tutorials/Mutators.md @@ -0,0 +1,273 @@ +# Mutators Tutorial + + + +## 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(y=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); + } + } +``` + + diff --git a/version.scad b/version.scad index 1c6817a..e704828 100644 --- a/version.scad +++ b/version.scad @@ -8,7 +8,7 @@ ////////////////////////////////////////////////////////////////////// -BOSL_VERSION = [2,0,492]; +BOSL_VERSION = [2,0,493]; // Section: BOSL Library Version Functions From 36298b38d6118ec1ae609d71ff1585e2d669c94b Mon Sep 17 00:00:00 2001 From: Garth Minette Date: Tue, 29 Dec 2020 21:21:12 -0800 Subject: [PATCH 3/7] Added Mutators tutorial to generation list. --- scripts/make_tutorials.sh | 2 +- version.scad | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/make_tutorials.sh b/scripts/make_tutorials.sh index 9c0fee4..b99fb65 100755 --- a/scripts/make_tutorials.sh +++ b/scripts/make_tutorials.sh @@ -15,7 +15,7 @@ done if [[ "$FILES" != "" ]]; then PREVIEW_LIBS="$FILES" else - PREVIEW_LIBS="Transforms Distributors Shapes2d Shapes3d Paths FractalTree" + PREVIEW_LIBS="Shapes2d Shapes3d Transforms Distributors Mutators Paths FractalTree" fi dir="$(basename $PWD)" diff --git a/version.scad b/version.scad index e704828..1a85679 100644 --- a/version.scad +++ b/version.scad @@ -8,7 +8,7 @@ ////////////////////////////////////////////////////////////////////// -BOSL_VERSION = [2,0,493]; +BOSL_VERSION = [2,0,494]; // Section: BOSL Library Version Functions From 1a9b683890336f2d5b0b7fb5c41b726db3a50305 Mon Sep 17 00:00:00 2001 From: Garth Minette Date: Tue, 29 Dec 2020 21:23:02 -0800 Subject: [PATCH 4/7] Bottom half example fix. --- tutorials/Mutators.md | 2 +- version.scad | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tutorials/Mutators.md b/tutorials/Mutators.md index 1b0df85..3ad3eb1 100644 --- a/tutorials/Mutators.md +++ b/tutorials/Mutators.md @@ -47,7 +47,7 @@ The plane of dissection can be shifted along the axis of any of these operators: ``` ```openscad - bottom_half(y=20) sphere(d=100); + bottom_half(z=20) sphere(d=100); ``` ```openscad diff --git a/version.scad b/version.scad index 1a85679..10ffe9c 100644 --- a/version.scad +++ b/version.scad @@ -8,7 +8,7 @@ ////////////////////////////////////////////////////////////////////// -BOSL_VERSION = [2,0,494]; +BOSL_VERSION = [2,0,495]; // Section: BOSL Library Version Functions From 6fb034301606e2d7e61786b82cbf6f75ea914dab Mon Sep 17 00:00:00 2001 From: Garth Minette Date: Tue, 29 Dec 2020 22:04:47 -0800 Subject: [PATCH 5/7] minkowski_difference() 3d bugfix. --- mutators.scad | 13 +++++++++---- version.scad | 2 +- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/mutators.scad b/mutators.scad index 1e54c6f..2a2556f 100644 --- a/mutators.scad +++ b/mutators.scad @@ -49,11 +49,16 @@ module bounding_box(excess=0, planar=true) { // a bounding box with an offset of 1 in all axis module _oversize_bbox() { - minkowski() { - _xProjection() children(); // x axis - union() { + if (planar) { + minkowski() { + _xProjection() children(); // x axis rotate(-90) _xProjection() rotate(90) children(); // y axis - if(!planar) rotate([0,-90,0]) _xProjection() rotate([0,90,0]) children(); // z 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 } } } diff --git a/version.scad b/version.scad index 10ffe9c..ae8cf68 100644 --- a/version.scad +++ b/version.scad @@ -8,7 +8,7 @@ ////////////////////////////////////////////////////////////////////// -BOSL_VERSION = [2,0,495]; +BOSL_VERSION = [2,0,496]; // Section: BOSL Library Version Functions From 0299270f6a3c82973d2474fa61b640a1379b60ef Mon Sep 17 00:00:00 2001 From: Garth Minette Date: Wed, 30 Dec 2020 00:34:25 -0800 Subject: [PATCH 6/7] Added rounding and chamfering to trapezoid() --- paths.scad | 152 ++++++++++++++++++++++++++++++++++++++++++++++++++ shapes2d.scad | 60 ++++++++++++++------ version.scad | 2 +- 3 files changed, 197 insertions(+), 17 deletions(-) diff --git a/paths.scad b/paths.scad index fe14a50..0588310 100644 --- a/paths.scad +++ b/paths.scad @@ -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 3*[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. diff --git a/shapes2d.scad b/shapes2d.scad index 4fec9a3..8325bb8 100644 --- a/shapes2d.scad +++ b/shapes2d.scad @@ -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, shift=shift) { - 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(); + } } } } diff --git a/version.scad b/version.scad index ae8cf68..2aa024d 100644 --- a/version.scad +++ b/version.scad @@ -8,7 +8,7 @@ ////////////////////////////////////////////////////////////////////// -BOSL_VERSION = [2,0,496]; +BOSL_VERSION = [2,0,497]; // Section: BOSL Library Version Functions From 6e677498f919d4e78ddb428bde15299352477ccf Mon Sep 17 00:00:00 2001 From: Garth Minette Date: Wed, 30 Dec 2020 00:38:24 -0800 Subject: [PATCH 7/7] Example fix for path_chamfer_and_rounding() --- paths.scad | 2 +- version.scad | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/paths.scad b/paths.scad index 0588310..f7bfe29 100644 --- a/paths.scad +++ b/paths.scad @@ -432,7 +432,7 @@ function path_torsion(path, closed=false) = // stroke(path2, closed=true); // Example(2D): Per-Corner Chamfering // path = star(5, step=2, d=100); -// rs = [for (i=[0:1:4]) each 3*[i,i]]; +// 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 diff --git a/version.scad b/version.scad index 2aa024d..b242730 100644 --- a/version.scad +++ b/version.scad @@ -8,7 +8,7 @@ ////////////////////////////////////////////////////////////////////// -BOSL_VERSION = [2,0,497]; +BOSL_VERSION = [2,0,498]; // Section: BOSL Library Version Functions