├── LICENSE
├── MinkowskiRound.scad
├── README.md
├── examples
├── README.md
├── beamChain-1.scad
├── beamChain-2.scad
├── beamChain-3.scad
├── beamChain-4.scad
├── extrudeWithRadius.scad
├── mirrorPoints.scad
├── negative-polyRoundExtrude.scad
├── polyRoundExtrude.scad
├── polyround.scad
├── radii-conflict.scad
├── shell2d.scad
└── translateRadiiPoints.scad
├── images
├── InOutminkowski.png
├── PolyRoundexample3fn.png
├── example1.png
├── example2.png
├── formulas.png
├── mainminkowski.png
└── round2d.png
├── polyround.scad
├── roundAnythingExamples.scad
├── unionRoundMask-Doc.md
└── unionRoundMask.scad
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright 2020 Kurt Hutten
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4 |
5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6 |
7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
8 |
--------------------------------------------------------------------------------
/MinkowskiRound.scad:
--------------------------------------------------------------------------------
1 | // Library: MinkowskiRound.scad
2 | // Version: 1.0
3 | // Author: IrevDev
4 | // Copyright: 2020
5 | // License: MIT
6 |
7 | /*
8 | ---Modules
9 | round2d(outside radius,internal radius) -> takes 2d children
10 | This module will round 2d any shape
11 |
12 | minkowskiRound(outsideRadius,internalRadius,enable,envelopecube[x,y,z]) -> takes 3d children
13 | This module will round any 3d shape though it takes a long time, possibly more than 12 hours (use a low $fn value, 10-15 to begin with) so make sure enable is set to 0 untill you are ready for final render,the envelopecube must be bigger than the object you are rounding, default values are [500,500,500].
14 |
15 | minkowskiOutsideRound(radius,enable,envelopecube[x,y,z])
16 | minkowskiInsideRound(radius,enable,envelopecube[x,y,z])
17 | Both this modules do the same thing as minkowskiRound() but focus on either inside or outside radiuses, which means these modules use one less minkowski() (two instead of three) making them quicker if you don't want both inside and outside radiuses.
18 |
19 | --Examples
20 | */
21 |
22 | //round2d(1,6)difference(){square([20,20]);square([10,10]);}
23 | //minkowskiRound(3,1.5,1)theshape();
24 | //minkowskiInsideRound(3,1)theshape();
25 | //minkowskiOutsideRound(3,1)theshape();
26 |
27 | //---good test child for examples
28 | //module theshape(){//example shape
29 | // difference(){
30 | // cube([20,20,20]);
31 | // cube([10,10,10]);
32 | // }
33 | //}
34 |
35 | // $fn=20;
36 | // minkowskiRound(0.7,1.5,1,[50,50,50])union(){//--example in the thiniverse thumbnail/main image
37 | // cube([6,6,22]);
38 | // rotate([30,45,10])cylinder(h=22,d=10);
39 | // }//--I rendered this out with a $fn=25 and it took more than 12 hours on my computer
40 |
41 |
42 | module round2d(OR=3,IR=1){
43 | offset(OR){
44 | offset(-IR-OR){
45 | offset(IR){
46 | children();
47 | }
48 | }
49 | }
50 | }
51 |
52 | module minkowskiRound(OR=1,IR=1,enable=1,boundingEnvelope=[500,500,500]){
53 | if(enable==0){//do nothing if not enabled
54 | children();
55 | } else {
56 | minkowski(){//expand the now positive shape back out
57 | difference(){//make the negative shape positive again
58 | cube(boundingEnvelope-[0.1,0.1,0.1],center=true);
59 | minkowski(){//expand the negative shape inwards
60 | difference(){//create a negative of the children
61 | cube(boundingEnvelope,center=true);
62 | minkowski(){//expand the children
63 | children();
64 | sphere(IR);
65 | }
66 | }
67 | sphere(OR+IR);
68 | }
69 | }
70 | sphere(OR);
71 | }
72 | }
73 | }
74 |
75 | module minkowskiOutsideRound(r=1,enable=1,boundingEnvelope=[500,500,500]){
76 | if(enable==0){//do nothing if not enabled
77 | children();
78 | } else {
79 | minkowski(){//expand the now positive shape
80 | difference(){//make the negative positive
81 | cube(boundingEnvelope-[0.1,0.1,0.1],center=true);
82 | minkowski(){//expand the negative inwards
83 | difference(){//create a negative of the children
84 | cube(boundingEnvelope,center=true);
85 | children();
86 | }
87 | sphere(r);
88 | }
89 | }
90 | sphere(r);
91 | }
92 | }
93 | }
94 |
95 | module minkowskiInsideRound(r=1,enable=1,boundingEnvelope=[500,500,500]){
96 | if(enable==0){//do nothing if not enabled
97 | children();
98 | } else {
99 | difference(){//make the negative positive again
100 | cube(boundingEnvelope-[0.1,0.1,0.1],center=true);
101 | minkowski(){//expand the negative shape inwards
102 | difference(){//make the expanded children a negative shape
103 | cube(boundingEnvelope,center=true);
104 | minkowski(){//expand the children
105 | children();
106 | sphere(r);
107 | }
108 | }
109 | sphere(r);
110 | }
111 | }
112 | }
113 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Round-Anything
2 |
3 | Round-Anything is primarily a set of OpenSCAD utilities that help with rounding parts, but it also embodies a robust approach to developing OpenSCAD parts. The heart of the library a list of polygon points, with a 3rd radius parameter at each point. That is a series of [x, y, radius] points.
4 |
5 |
6 |
7 | ## The Why
8 |
9 | The truth is radii, internal radii in particular can be a real pain to add in openscad. and the more you move away from shapes with 90 degree angles the more difficult it becomes, effectively putting a complexity ceiling on parts you can produce in OpenScad. Because of how important radii in both making an appealing and strong part, reducing stress concentration etc, A library that focuses on radii as a core principle makes for a solid foundation for your parts. Furthermore the heart of the library revolves around the polygon, this is because we're leveraging the battle tested paradigm of extruding from 2d sketches of most CAD packages. I can't imagine making an OpenScad part without Round-Anything.
10 |
11 | ### Quick side-notes
12 |
13 | I'm currently working on a community website for "Code-CAD" (like OpenSCAD). A good way to think of it is codepen crossed with a thing repository. You can check it out at [cadhub.xyz](https://cadhub.xyz/) or it's [repo](https://github.com/Irev-Dev/cadhub).
14 |
15 | Also please submit examples of what you build with the library in the [discussions](https://github.com/Irev-Dev/Round-Anything/discussions), I'd love to see them. I also recommend you "watch" the repo with notifications turned on for the discussions to stay up-to-date.
16 |
17 | ## Documentation
18 |
19 | See an overview of the library in [video form](https://www.youtube.com/watch?v=laxv2wFKq8Q)
20 |
21 |
22 |
23 |
24 | [Written overview](https://kurthutten.com/blog/round-anything-a-pragmatic-approach-to-openscad-design/).
25 |
26 | [Full documentation of the API is here](https://kurthutten.com/blog/round-anything-api/).
27 |
28 | [Installation instructions](https://github.com/Irev-Dev/Round-Anything/discussions/21)
29 |
30 | ## Extra
31 |
32 | I [live streamed](https://www.youtube.com/watch?v=1Tegarwy69I&t=2s) the making of [this part](https://github.com/Irev-Dev/monitor-stand-turn-camera) using this library. I was able to make the bulk of this part quickly even with some complex radii involved thanks to the library.
33 |
34 |
35 |
36 | Below are some of the example parts that can be found in [roundAnythingExamples.scad](https://github.com/Irev-Dev/Round-Anything/blob/master/roundAnythingExamples.scad).
37 |
38 |
39 |
40 | ## Citation
41 | roundUnionMask Includes code based on examples from:
42 | Kogan, Jonathan (2017) "A New Computationally Efficient Method for Spacing n Points on a Sphere," Rose-Hulman Undergraduate Mathematics Journal: Vol. 18 : Iss. 2 , Article 5.
43 | Available at: [https://scholar.rose-hulman.edu/rhumj/vol18/iss2/5]
44 |
--------------------------------------------------------------------------------
/examples/README.md:
--------------------------------------------------------------------------------
1 | # Round-Anything examples
2 |
3 | These examples are mostly to go with the [library Documentation](https://learn.cadhub.xyz/docs/round-anything/overview) and so are best viewed there.
--------------------------------------------------------------------------------
/examples/beamChain-1.scad:
--------------------------------------------------------------------------------
1 | // beamChain example 1
2 |
3 | include
4 |
5 | function beamPoints(r1,r2,rStart=0,rEnd=0)=[
6 | [0, 0, rStart],
7 | [2, 8, 0 ],
8 | [5, 4, r1 ],
9 | [15, 10, r2 ],
10 | [17, 2, rEnd ]
11 | ];
12 |
13 | linear_extrude(1){
14 |
15 | // chained lines by themselves
16 | translate(){
17 | radiiPoints=beamPoints(0,0);
18 | polygon(polyRound(beamChain(radiiPoints,offset1=0.02, offset2=-0.02),20));
19 | }
20 |
21 |
22 | // Add some radii to the line transitions
23 | translate([0,-7,0]){
24 | radiiPoints=beamPoints(2,1);
25 | polygon(polyRound(beamChain(radiiPoints,offset1=0.02, offset2=-0.02),20));
26 | }
27 |
28 | // Give make the lines beams with some thickness
29 | translate([0,-7*2,0]){
30 | radiiPoints=beamPoints(2,1);
31 | polygon(polyRound(beamChain(radiiPoints,offset1=0.5, offset2=-0.5),20));
32 | }
33 |
34 | }
35 |
--------------------------------------------------------------------------------
/examples/beamChain-2.scad:
--------------------------------------------------------------------------------
1 | // beamChain example 2
2 |
3 | include
4 |
5 | function beamPoints(r1,r2,rStart=0,rEnd=0)=[
6 | [0, 0, rStart],
7 | [2, 8, 0 ],
8 | [5, 4, r1 ],
9 | [15, 10, r2 ],
10 | [17, 2, rEnd ]
11 | ];
12 |
13 | linear_extrude(1){
14 |
15 | // Add an angle to the start of the beam
16 | translate([0,-7*3,0]){
17 | radiiPoints=beamPoints(2,1);
18 | polygon(polyRound(beamChain(radiiPoints,offset1=0.5, offset2=-0.5, startAngle=45),20));
19 | }
20 |
21 | // Put a negative radius at the start for transationing to a flat surface
22 | translate([0,-7*4,0]){
23 | radiiPoints=beamPoints(2,1,rStart=-0.7);
24 | polygon(polyRound(beamChain(radiiPoints,offset1=0.5, offset2=-0.5, startAngle=45),20));
25 | }
26 |
27 | }
28 |
--------------------------------------------------------------------------------
/examples/beamChain-3.scad:
--------------------------------------------------------------------------------
1 | // beamChain example 3
2 |
3 | include
4 |
5 | function beamPoints(r1,r2,rStart=0,rEnd=0)=[
6 | [0, 0, rStart],
7 | [2, 8, 0 ],
8 | [5, 4, r1 ],
9 | [15, 10, r2 ],
10 | [17, 2, rEnd ]
11 | ];
12 |
13 | // Define more points for a polygon to be atteched to the end of the beam chain
14 | clipP=[
15 | [16, 1.2, 0 ],
16 | [16, 0, 0 ],
17 | [16.5, 0, 0 ],
18 | [16.5, 1, 0.2],
19 | [17.5, 1, 0.2],
20 | [17.5, 0, 0 ],
21 | [18, 0, 0 ],
22 | [18, 1.2, 0 ]
23 | ];
24 |
25 | linear_extrude(1){
26 | // end hook
27 | translate([-15,-7*5+3,0]){
28 | polygon(polyRound(clipP,20));
29 | }
30 |
31 | // Attached to the end of the beam chain by dividing the beam paths in forward and return and
32 | // concat other polygon inbetween
33 | translate([0,-7*6,0]){
34 | radiiPoints=beamPoints(2,1);
35 | forwardPath=beamChain(radiiPoints,offset1=0.5,startAngle=-15,mode=2);
36 | returnPath=revList(beamChain(radiiPoints,offset1=-0.5,startAngle=-15,mode=2));
37 | entirePath=concat(forwardPath,clipP,returnPath);
38 | polygon(polyRound(entirePath,20));
39 | }
40 |
41 | // Add transitioning radii into the end polygong
42 | translate([0,-7*7-2,0]){
43 | radiiPoints=beamPoints(2,1,rEnd=3);
44 | forwardPath=beamChain(radiiPoints,offset1=0.5,startAngle=-15,mode=2);
45 | returnPath=revList(beamChain(radiiPoints,offset1=-0.5,startAngle=-15,mode=2));
46 | entirePath=concat(forwardPath,clipP,returnPath);
47 | polygon(polyRound(entirePath,20));
48 | }
49 |
50 | }
51 |
--------------------------------------------------------------------------------
/examples/beamChain-4.scad:
--------------------------------------------------------------------------------
1 | // beamChain example 4
2 |
3 | include
4 |
5 | function beamPoints(r1,r2,rStart=0,rEnd=0)=[
6 | [0, 0, rStart],
7 | [2, 8, 0 ],
8 | [5, 4, r1 ],
9 | [15, 10, r2 ],
10 | [17, 2, rEnd ]
11 | ];
12 |
13 | linear_extrude(1){
14 |
15 | translate([0,-7*9,0]){
16 | // Define multiple shells from the the one set of points
17 | for(i=[0:2]){
18 | polygon(polyRound(beamChain(beamPoints(2,1),offset1=-1+i*0.4, offset2=-1+i*0.4+0.25),20));
19 | }
20 | }
21 |
22 | }
23 |
--------------------------------------------------------------------------------
/examples/extrudeWithRadius.scad:
--------------------------------------------------------------------------------
1 | // extrudeWithRadius example
2 |
3 | include
4 |
5 | radiiPoints=[
6 | [-4, 0, 1 ],
7 | [5, 3, 1.5 ],
8 | [0, 7, 0.1 ],
9 | [8, 7, 10 ],
10 | [20, 20, 0.8 ],
11 | [10, 0, 10 ]
12 | ];
13 | extrudeWithRadius(3,0.5,0.5,5)polygon(polyRound(radiiPoints,30));
14 |
--------------------------------------------------------------------------------
/examples/mirrorPoints.scad:
--------------------------------------------------------------------------------
1 | // mirrorPoints example
2 |
3 | include
4 |
5 | centerRadius=7;
6 | points=[[0,0,0],[2,8,0],[5,4,3],[15,10,0.5],[10,2,centerRadius]];
7 | mirroredPoints=mirrorPoints(points,0,[0,0]);
8 | linear_extrude(1)
9 | translate([0,-20,0])
10 | polygon(polyRound(mirroredPoints,20));
11 |
--------------------------------------------------------------------------------
/examples/negative-polyRoundExtrude.scad:
--------------------------------------------------------------------------------
1 | // negative polyRoundExtrude example
2 |
3 | include
4 |
5 | extrudeRadius = 0.8;
6 | extrudeHeight = 2;
7 | tiny = 0.005; // tiny value is used to stop artifacts from planes lining up perfectly
8 |
9 | radiiPoints=[
10 | [-7, -3, 0 ],
11 | [7, -3, 0 ],
12 | [0, 6, 1 ] // top of the triagle is rounded
13 | ];
14 | negativeRadiiPoints=[
15 | [-3, -1, 0 ],
16 | [3, -1, 0 ],
17 | [0, 3, 1 ] // top of the triagle is rounded
18 | ];
19 |
20 | difference() {
21 | polyRoundExtrude(radiiPoints,extrudeHeight, extrudeRadius, extrudeRadius,fn=20);
22 | translate([0,0,-tiny])
23 | polyRoundExtrude(negativeRadiiPoints,extrudeHeight+2*tiny, -extrudeRadius, -extrudeRadius,fn=20);
24 | }
25 |
--------------------------------------------------------------------------------
/examples/polyRoundExtrude.scad:
--------------------------------------------------------------------------------
1 | // polyRoundExtrude example
2 |
3 | include
4 |
5 | radiiPoints=[
6 | [10, 0, 10 ],
7 | [20, 20, 1.1],
8 | [8, 7, 10 ],
9 | [0, 7, 0.3],
10 | [5, 3, 0.1],
11 | [-4, 0, 1 ]
12 | ];
13 | polyRoundExtrude(radiiPoints,2,0.5,-0.8,fn=20);
14 |
--------------------------------------------------------------------------------
/examples/polyround.scad:
--------------------------------------------------------------------------------
1 | // polyRound example
2 |
3 | include
4 |
5 | radiiPoints=[
6 | [-4, 0, 1 ],
7 | [5, 3, 1.5 ],
8 | [0, 7, 0.1 ],
9 | [8, 7, 10 ],
10 | [20, 20, 0.8 ],
11 | [10, 0, 10 ]
12 | ];
13 | linear_extrude(3)polygon(polyRound(radiiPoints,30));
14 |
--------------------------------------------------------------------------------
/examples/radii-conflict.scad:
--------------------------------------------------------------------------------
1 | // radii conflict example
2 |
3 | include
4 |
5 | //example of radii conflict handling and debuging feature
6 | function makeRadiiPoints(r1, r2)=[
7 | [0, 0, 0 ],
8 | [0, 20, r1 ],
9 | [20, 20, r2 ],
10 | [20, 0, 0 ]
11 | ];
12 |
13 | linear_extrude(3){
14 | // the squre shape being 20 wide, two radii of 10 both fit into the shape (just)
15 | translate([-25,0,0])polygon(polyRound(makeRadiiPoints(10,10),50));
16 |
17 | //radii are too large and are reduced to fit and will be reduce to 10 and 10
18 | translate([0,0,0])polygon(polyRound(makeRadiiPoints(30,30),50));
19 |
20 | //radii are too large again and are reduced to fit, but keep their ratios r1 will go from 10 to 4 and r2 will go from 40 to 16
21 | translate([25,0,0])polygon(polyRound(makeRadiiPoints(10,40),50));
22 |
23 | //mode 2 = no radii limiting
24 | translate([50,0,0])polygon(polyRound(makeRadiiPoints(15,20),50,mode=2));
25 | }
26 |
--------------------------------------------------------------------------------
/examples/shell2d.scad:
--------------------------------------------------------------------------------
1 | // shell2d example
2 |
3 | include
4 |
5 | module gridpattern(memberW = 4, sqW = 12, iter = 5, r = 3){
6 | round2d(0, r)rotate([0, 0, 45])translate([-(iter * (sqW + memberW) + memberW) / 2, -(iter * (sqW + memberW) + memberW) / 2])difference(){
7 | square([(iter) * (sqW + memberW) + memberW, (iter) * (sqW + memberW) + memberW]);
8 | for (i = [0:iter - 1], j = [0:iter - 1]){
9 | translate([i * (sqW + memberW) + memberW, j * (sqW + memberW) + memberW])square([sqW, sqW]);
10 | }
11 | }
12 | }
13 |
14 | radiiPoints=[
15 | [-4, 0, 1 ],
16 | [5, 3, 1.5 ],
17 | [0, 7, 0.1 ],
18 | [8, 7, 10 ],
19 | [20, 20, 0.8 ],
20 | [10, 0, 10 ]
21 | ];
22 |
23 | linear_extrude(1){
24 | shell2d(-0.5)polygon(polyRound(radiiPoints,30));
25 | translate([0,-10,0])shell2d(-0.5){
26 | polygon(polyRound(radiiPoints,30));
27 | translate([8,8])gridpattern(memberW = 0.3, sqW = 1, iter = 17, r = 0.2);
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/examples/translateRadiiPoints.scad:
--------------------------------------------------------------------------------
1 | // translateRadiiPoints example
2 |
3 | include
4 |
5 | nutW=5.5; nutH=3; boltR=1.6;
6 | minT=2; minR=0.8;
7 | function nutCapture(startAndEndRadius=0)=[
8 | [-boltR, 0, startAndEndRadius],
9 | [-boltR, minT, 0],
10 | [-nutW/2, minT, minR],
11 | [-nutW/2, minT+nutH, minR],
12 | [nutW/2, minT+nutH, minR],
13 | [nutW/2, minT, minR],
14 | [boltR, minT, 0],
15 | [boltR, 0, startAndEndRadius],
16 | ];
17 | linear_extrude(3)translate([-5,0,0])polygon(polyRound(nutCapture(),20));
18 |
19 | negativeNutCapture=translateRadiiPoints(nutCapture(),tran=[5,0]);
20 | rotatedNegativeNutCapture=translateRadiiPoints(nutCapture(1),tran=[20,5],rot=90);
21 | aSquare=concat(
22 | [[0,0,0]],
23 | negativeNutCapture,
24 | [[20,0,0]],
25 | rotatedNegativeNutCapture,
26 | [[20,10,0]],
27 | [[0,10,0]]
28 | );
29 |
30 | linear_extrude(3)polygon(polyRound(aSquare,20));
31 |
--------------------------------------------------------------------------------
/images/InOutminkowski.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Irev-Dev/Round-Anything/061fef7c429628808e847696bb345a9b0ec6e279/images/InOutminkowski.png
--------------------------------------------------------------------------------
/images/PolyRoundexample3fn.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Irev-Dev/Round-Anything/061fef7c429628808e847696bb345a9b0ec6e279/images/PolyRoundexample3fn.png
--------------------------------------------------------------------------------
/images/example1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Irev-Dev/Round-Anything/061fef7c429628808e847696bb345a9b0ec6e279/images/example1.png
--------------------------------------------------------------------------------
/images/example2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Irev-Dev/Round-Anything/061fef7c429628808e847696bb345a9b0ec6e279/images/example2.png
--------------------------------------------------------------------------------
/images/formulas.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Irev-Dev/Round-Anything/061fef7c429628808e847696bb345a9b0ec6e279/images/formulas.png
--------------------------------------------------------------------------------
/images/mainminkowski.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Irev-Dev/Round-Anything/061fef7c429628808e847696bb345a9b0ec6e279/images/mainminkowski.png
--------------------------------------------------------------------------------
/images/round2d.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Irev-Dev/Round-Anything/061fef7c429628808e847696bb345a9b0ec6e279/images/round2d.png
--------------------------------------------------------------------------------
/polyround.scad:
--------------------------------------------------------------------------------
1 | // Library: round-anything
2 | // Version: 1.0
3 | // Author: IrevDev
4 | // Contributors: TLC123
5 | // Copyright: 2020
6 | // License: MIT
7 |
8 |
9 | function addZcoord(points,displacement)=[for(i=[0:len(points)-1])[points[i].x,points[i].y, displacement]];
10 | function translate3Dcoords(points,tran=[0,0,0],mult=[1,1,1])=[for(i=[0:len(points)-1])[
11 | (points[i].x*mult.x)+tran.x,
12 | (points[i].y*mult.y)+tran.y,
13 | (points[i].z*mult.z)+tran.z
14 | ]];
15 | function offsetPolygonPoints(points, offset=0)=
16 | // Work sthe same as the offset does, except for the fact that instead of a 2d shape
17 | // It works directly on polygon points
18 | // It returns the same number of points just offset into or, away from the original shape.
19 | // points= a series of x,y points[[x1,y1],[x2,y2],...]
20 | // offset= amount to offset by, negative numbers go inwards into the shape, positive numbers go out
21 | // return= a series of x,y points[[x1,y1],[x2,y2],...]
22 | let(
23 | isCWorCCW=sign(offset)*CWorCCW(points)*-1,
24 | lp=len(points)
25 | )
26 | [for(i=[0:lp-1]) parallelFollow([
27 | points[listWrap(i-1,lp)],
28 | points[i],
29 | points[listWrap(i+1,lp)],
30 | ],thick=offset,mode=isCWorCCW)];
31 |
32 | function reverseList(list) = [ for(i=[len(list) - 1:-1:0]) list[i] ];
33 |
34 | // Apply `reverseList` to the array of vertex indices for an array of faces
35 | function invertFaces(faces) = [ for(f=faces) reverseList(f) ];
36 |
37 | function makeCurvedPartOfPolyHedron(radiiPoints,r,fn,minR=0.01)=
38 | // this is a private function that I'm not expecting library users to use directly
39 | // radiiPoints= serise of x, y, r points
40 | // r= radius of curve that will be put on the end of the extrusion
41 | // fn= amount of subdivisions
42 | // minR= if one of the points in radiiPoints is less than r, it's likely to converge and form a sharp edge,
43 | // the min radius on these converged edges can be controled with minR, though because of legacy reasons it can't be 0, but can be a very small number.
44 | // return= array of [polyhedronPoints, Polyhedronfaces, theLength of a singe layer in the curve]
45 | let(
46 | lp=len(radiiPoints),
47 | radii=[for(i=[0:lp-1])radiiPoints[i].z],
48 | isCWorCCWOverall=CWorCCW(radiiPoints),
49 | dir=sign(r),
50 | absR=abs(r),
51 | fractionOffLp=1-1/fn,
52 | allPoints=[for(fraction=[0:1/fn:1])
53 | let(
54 | iterationOffset=dir*sqrt(sq(absR)-sq(fraction*absR))-dir*absR,
55 | theOffsetPoints=offsetPolygonPoints(radiiPoints,iterationOffset),
56 | polyRoundOffsetPoints=[for(i=[0:lp-1])
57 | let(
58 | pointsAboutCurrent=[
59 | theOffsetPoints[listWrap(i-1,lp)],
60 | theOffsetPoints[i],
61 | theOffsetPoints[listWrap(i+1,lp)]
62 | ],
63 | isCWorCCWLocal=CWorCCW(pointsAboutCurrent),
64 | isInternalRadius=(isCWorCCWLocal*isCWorCCWOverall)==-1,
65 | // the radius names are only true for positive r,
66 | // when are r is negative increasingRadius is actually decreasing and vice-vs
67 | // increasingRadiusWithPositiveR is just to verbose of a variable name for my liking
68 | increasingRadius=max(radii[i]-iterationOffset, minR),
69 | decreasingRadius=max(radii[i]+iterationOffset, minR)
70 | )
71 | [theOffsetPoints[i].x, theOffsetPoints[i].y, isInternalRadius? increasingRadius: decreasingRadius]
72 | ],
73 | pointsForThisLayer=polyRound(polyRoundOffsetPoints,fn)
74 | )
75 | addZcoord(pointsForThisLayer,fraction*absR)
76 | ],
77 | polyhedronPoints=flatternArray(allPoints),
78 | allLp=len(allPoints),
79 | layerLength=len(allPoints[0]),
80 | loopToSecondLastLayer=allLp-2,
81 | sideFaces=[for(layerIndex=[0:loopToSecondLastLayer])let(
82 | currentLayeroffset=layerIndex*layerLength,
83 | nextLayeroffset=(layerIndex+1)*layerLength,
84 | layerFaces=[for(subLayerIndex=[0:layerLength-1])
85 | [
86 | currentLayeroffset+subLayerIndex, currentLayeroffset + listWrap(subLayerIndex+1,layerLength), nextLayeroffset+listWrap(subLayerIndex+1,layerLength), nextLayeroffset+subLayerIndex]
87 | ]
88 | )layerFaces],
89 | polyhedronFaces=flatternArray(sideFaces)
90 | )
91 | [polyhedronPoints, polyhedronFaces, layerLength];
92 |
93 | function flatternRecursion(array, init=[], currentIndex=0)=
94 | // this is a private function, init and currentIndex are for the function's use
95 | // only for when it's calling itself, which is why there is a simplified version flatternArray that just calls this one
96 | // array= array to flattern by one level of nesting
97 | // init= the array used to cancat with the next call, only for when the function calls itself
98 | // currentIndex= so the function can keep track of how far it's progressed through the array, only for when it's calling itself
99 | // returns= flatterned array, by one level of nesting
100 | let(
101 | shouldKickOffRecursion=currentIndex==undef?1:0,
102 | isLastIndex=currentIndex+1==len(array)?1:0,
103 | flatArray=shouldKickOffRecursion?flatternRecursion(array,[],0):
104 | isLastIndex?concat(init,array[currentIndex]):
105 | flatternRecursion(array,concat(init,array[currentIndex]),currentIndex+1)
106 | )
107 | flatArray;
108 |
109 | function flatternArray(array)=
110 | // public version of flatternRecursion, has simplified params to avoid confusion
111 | // array= array to be flatterned
112 | // return= array that been flatterend by one level of nesting
113 | flatternRecursion(array);
114 |
115 | function offsetAllFacesBy(array,offset)=[
116 | // polyhedron faces are simply a list of indices to points, if your concat points together than you probably need to offset
117 | // your faces array to points to the right place in the new list
118 | // array= array of point indicies
119 | // offset= number to offset all indecies by
120 | // return= array of point indices (i.e. faces) with offset applied
121 | for(faceIndex=[0:len(array)-1])[
122 | for(pointIndex=[0:len(array[faceIndex])-1])array[faceIndex][pointIndex]+offset
123 | ]
124 | ];
125 |
126 | function extrudePolygonWithRadius(radiiPoints,h=5,r1=1,r2=1,fn=4)=
127 | // this basically calls makeCurvedPartOfPolyHedron twice to get the curved section of the final polyhedron
128 | // and then goes about assmbling them, as the side faces and the top and bottom face caps are missing
129 | // radiiPoints= series of [x,y,r] points,
130 | // h= height of the extrude (total including radius sections)
131 | // r1,r2= define the radius at the top and bottom of the extrud respectively, negative number flange out the extrude
132 | // fn= number of subdivisions
133 | // returns= [polyhedronPoints, polyhedronFaces]
134 | let(
135 | // top is the top curved part of the extrude
136 | top=makeCurvedPartOfPolyHedron(radiiPoints,r1,fn),
137 | topRadiusPoints=translate3Dcoords(top[0],[0,0,h-abs(r1)]),
138 | singeLayerLength=top[2],
139 | topRadiusFaces=top[1],
140 | radiusPointsLength=len(topRadiusPoints), // is the same length as bottomRadiusPoints
141 | // bottom is the bottom curved part of the extrude
142 | bottom=makeCurvedPartOfPolyHedron(radiiPoints,r2,fn),
143 | // Z axis needs to be multiplied by -1 to flip it so the radius is going in the right direction [1,1,-1]
144 | bottomRadiusPoints=translate3Dcoords(bottom[0],[0,0,abs(r2)],[1,1,-1]),
145 | // becaues the points will be all concatenated into the same array, and the bottom points come second, than
146 | // the original indices the faces are points towards are wrong and need to have an offset applied to them
147 | bottomRadiusFaces=offsetAllFacesBy(bottom[1],radiusPointsLength),
148 | // all of the side panel of the extrusion, connecting points from the inner layers of each
149 | // of the curved sections
150 | sideFaces=[for(i=[0:singeLayerLength-1])[
151 | i,
152 | listWrap(i+1,singeLayerLength),
153 | radiusPointsLength + listWrap(i+1,singeLayerLength),
154 | radiusPointsLength + i
155 | ]],
156 | // both of these caps are simple every point from the last layer of the radius points
157 | topCapFace=[for(i=[0:singeLayerLength-1])radiusPointsLength-singeLayerLength+i],
158 | bottomCapFace=[for(i=[0:singeLayerLength-1])radiusPointsLength*2-singeLayerLength+i],
159 | finalPolyhedronPoints=concat(topRadiusPoints,bottomRadiusPoints),
160 | finalPolyhedronFaces=concat(topRadiusFaces,invertFaces(bottomRadiusFaces),invertFaces(sideFaces),[topCapFace],invertFaces([bottomCapFace]))
161 | )
162 | [
163 | finalPolyhedronPoints,
164 | finalPolyhedronFaces
165 | ];
166 |
167 | module polyRoundExtrude(radiiPoints,length=5,r1=1,r2=1,fn=10,convexity=10) {
168 | assert(len(radiiPoints) > 2, str("There must be at least 3 radii points for polyRoundExtrude. ", radiiPoints, " is not long enough, you need ", 3 - len(radiiPoints), " more point/s. Example: polyRoundExtrude([[11,0,1],[20,20,1.1],[8,7,0.5]],2,0.5,-0.8,fn=8);"));
169 | if(len(radiiPoints) > 2) {
170 | orderedRadiiPoints = CWorCCW(radiiPoints) == 1
171 | ? reverseList(radiiPoints)
172 | : radiiPoints;
173 |
174 | polyhedronPointsNFaces=extrudePolygonWithRadius(orderedRadiiPoints,length,r1,r2,fn);
175 | polyhedron(points=polyhedronPointsNFaces[0], faces=polyhedronPointsNFaces[1], convexity=convexity);
176 | }
177 | }
178 |
179 |
180 | // testingInternals();
181 | module testingInternals(){
182 | //example of rounding random points, this has no current use but is a good demonstration
183 | random=[for(i=[0:20])[rnd(0,50),rnd(0,50),/*rnd(0,30)*/1000]];
184 | R =polyRound(random,7);
185 | translate([-25,25,0]){
186 | polyline(R);
187 | }
188 |
189 | //example of different modes of the CentreN2PointsArc() function 0=shortest arc, 1=longest arc, 2=CW, 3=CCW
190 | p1=[0,5];p2=[10,5];centre=[5,0];
191 | translate([60,0,0]){
192 | color("green"){
193 | polygon(CentreN2PointsArc(p1,p2,centre,0,20));//draws the shortest arc
194 | }
195 | color("cyan"){
196 | polygon(CentreN2PointsArc(p1,p2,centre,1,20));//draws the longest arc
197 | }
198 | }
199 | translate([75,0,0]){
200 | color("purple"){
201 | polygon(CentreN2PointsArc(p1,p2,centre,2,20));//draws the arc CW (which happens to be the short arc)
202 | }
203 | color("red"){
204 | polygon(CentreN2PointsArc(p2,p1,centre,2,20));//draws the arc CW but p1 and p2 swapped order resulting in the long arc being drawn
205 | }
206 | }
207 |
208 | radius=6;
209 | radiipoints=[[0,0,0],[10,20,radius],[20,0,0]];
210 | tangentsNcen=round3points(radiipoints);
211 | translate([10,0,0]){
212 | for(i=[0:2]){
213 | color("red")translate(getpoints(radiipoints)[i])circle(1);//plots the 3 input points
214 | color("cyan")translate(tangentsNcen[i])circle(1);//plots the two tangent poins and the circle centre
215 | }
216 | translate([tangentsNcen[2][0],tangentsNcen[2][1],-0.2])circle(r=radius,$fn=25);//draws the cirle
217 | %polygon(getpoints(radiipoints));//draws a polygon
218 | }
219 | }
220 |
221 | function polyRound(radiipoints,fn=5,mode=0)=
222 | /*Takes a list of radii points of the format [x,y,radius] and rounds each point
223 | with fn resolution
224 | mode=0 - automatic radius limiting - DEFAULT
225 | mode=1 - Debug, output radius reduction for automatic radius limiting
226 | mode=2 - No radius limiting*/
227 | let(
228 | p=getpoints(radiipoints), //make list of coordinates without radii
229 | Lp=len(p),
230 | //remove the middle point of any three colinear points, otherwise adding a radius to the middle of a straigh line causes problems
231 | radiiPointsWithoutTrippleColinear=[
232 | for(i=[0:len(p)-1]) if(
233 | // keep point if it isn't colinear or if the radius is 0
234 | !isColinear(
235 | p[listWrap(i-1,Lp)],
236 | p[listWrap(i+0,Lp)],
237 | p[listWrap(i+1,Lp)]
238 | )||
239 | p[listWrap(i+0,Lp)].z!=0
240 | ) radiipoints[listWrap(i+0,Lp)]
241 | ],
242 | newrp2=processRadiiPoints(radiiPointsWithoutTrippleColinear),
243 | plusMinusPointRange=mode==2?1:2,
244 | temp=[
245 | for(i=[0:len(newrp2)-1]) //for each point in the radii array
246 | let(
247 | thepoints=[for(j=[-plusMinusPointRange:plusMinusPointRange])newrp2[listWrap(i+j,len(newrp2))]],//collect 5 radii points
248 | temp2=mode==2?round3points(thepoints,fn):round5points(thepoints,fn,mode)
249 | )
250 | mode==1?temp2:newrp2[i][2]==0?
251 | [[newrp2[i][0],newrp2[i][1]]]: //return the original point if the radius is 0
252 | CentreN2PointsArc(temp2[0],temp2[1],temp2[2],0,fn) //return the arc if everything is normal
253 | ]
254 | )
255 | [for (a = temp) for (b = a) b];//flattern and return the array
256 |
257 | function round5points(rp,fn,debug=0)=
258 | rp[2][2]==0&&debug==0?[[rp[2][0],rp[2][1]]]://return the middle point if the radius is 0
259 | rp[2][2]==0&&debug==1?0://if debug is enabled and the radius is 0 return 0
260 | let(
261 | p=getpoints(rp), //get list of points
262 | r=[for(i=[1:3]) abs(rp[i][2])],//get the centre 3 radii
263 | //start by determining what the radius should be at point 3
264 | //find angles at points 2 , 3 and 4
265 | a2=cosineRuleAngle(p[0],p[1],p[2]),
266 | a3=cosineRuleAngle(p[1],p[2],p[3]),
267 | a4=cosineRuleAngle(p[2],p[3],p[4]),
268 | //find the distance between points 2&3 and between points 3&4
269 | d23=pointDist(p[1],p[2]),
270 | d34=pointDist(p[2],p[3]),
271 | //find the radius factors
272 | F23=(d23*tan(a2/2)*tan(a3/2))/(r[0]*tan(a3/2)+r[1]*tan(a2/2)),
273 | F34=(d34*tan(a3/2)*tan(a4/2))/(r[1]*tan(a4/2)+r[2]*tan(a3/2)),
274 | newR=min(r[1],F23*r[1],F34*r[1]),//use the smallest radius
275 | //now that the radius has been determined, find tangent points and circle centre
276 | tangD=newR/tan(a3/2),//distance to the tangent point from p3
277 | circD=newR/sin(a3/2),//distance to the circle centre from p3
278 | //find the angle from the p3
279 | an23=getAngle(p[1],p[2]),//angle from point 3 to 2
280 | an34=getAngle(p[3],p[2]),//angle from point 3 to 4
281 | //find tangent points
282 | t23=[p[2][0]-cos(an23)*tangD,p[2][1]-sin(an23)*tangD],//tangent point between points 2&3
283 | t34=[p[2][0]-cos(an34)*tangD,p[2][1]-sin(an34)*tangD],//tangent point between points 3&4
284 | //find circle centre
285 | tmid=getMidpoint(t23,t34),//midpoint between the two tangent points
286 | anCen=getAngle(tmid,p[2]),//angle from point 3 to circle centre
287 | cen=[p[2][0]-cos(anCen)*circD,p[2][1]-sin(anCen)*circD]
288 | )
289 | //circle center by offseting from point 3
290 | //determine the direction of rotation
291 | debug==1?//if debug in disabled return arc (default)
292 | (newR-r[1]):
293 | [t23,t34,cen];
294 |
295 | function round3points(rp,fn)=
296 | rp[1][2]==0?[[rp[1][0],rp[1][1]]]://return the middle point if the radius is 0
297 | let(
298 | p=getpoints(rp), //get list of points
299 | r=rp[1][2],//get the centre 3 radii
300 | ang=cosineRuleAngle(p[0],p[1],p[2]),//angle between the lines
301 | //now that the radius has been determined, find tangent points and circle centre
302 | tangD=r/tan(ang/2),//distance to the tangent point from p2
303 | circD=r/sin(ang/2),//distance to the circle centre from p2
304 | //find the angles from the p2 with respect to the postitive x axis
305 | angleFromPoint1ToPoint2=getAngle(p[0],p[1]),
306 | angleFromPoint2ToPoint3=getAngle(p[2],p[1]),
307 | //find tangent points
308 | t12=[p[1][0]-cos(angleFromPoint1ToPoint2)*tangD,p[1][1]-sin(angleFromPoint1ToPoint2)*tangD],//tangent point between points 1&2
309 | t23=[p[1][0]-cos(angleFromPoint2ToPoint3)*tangD,p[1][1]-sin(angleFromPoint2ToPoint3)*tangD],//tangent point between points 2&3
310 | //find circle centre
311 | tmid=getMidpoint(t12,t23),//midpoint between the two tangent points
312 | angCen=getAngle(tmid,p[1]),//angle from point 2 to circle centre
313 | cen=[p[1][0]-cos(angCen)*circD,p[1][1]-sin(angCen)*circD] //circle center by offseting from point 2
314 | )
315 | [t12,t23,cen];
316 |
317 | function parallelFollow(rp,thick=4,minR=1,mode=1)=
318 | //rp[1][2]==0?[rp[1][0],rp[1][1],0]://return the middle point if the radius is 0
319 | thick==0?[rp[1][0],rp[1][1],0]://return the middle point if the radius is 0
320 | let(
321 | p=getpoints(rp), //get list of points
322 | r=thick,//get the centre 3 radii
323 | ang=cosineRuleAngle(p[0],p[1],p[2]),//angle between the lines
324 | //now that the radius has been determined, find tangent points and circle centre
325 | tangD=r/tan(ang/2),//distance to the tangent point from p2
326 | sgn=CWorCCW(rp),//rotation of the three points cw or ccw?let(sgn=mode==0?1:-1)
327 | circD=mode*sgn*r/sin(ang/2),//distance to the circle centre from p2
328 | //find the angles from the p2 with respect to the postitive x axis
329 | angleFromPoint1ToPoint2=getAngle(p[0],p[1]),
330 | angleFromPoint2ToPoint3=getAngle(p[2],p[1]),
331 | //find tangent points
332 | t12=[p[1][0]-cos(angleFromPoint1ToPoint2)*tangD,p[1][1]-sin(angleFromPoint1ToPoint2)*tangD],//tangent point between points 1&2
333 | t23=[p[1][0]-cos(angleFromPoint2ToPoint3)*tangD,p[1][1]-sin(angleFromPoint2ToPoint3)*tangD],//tangent point between points 2&3
334 | //find circle centre
335 | tmid=getMidpoint(t12,t23),//midpoint between the two tangent points
336 | angCen=getAngle(tmid,p[1]),//angle from point 2 to circle centre
337 | cen=[p[1][0]-cos(angCen)*circD,p[1][1]-sin(angCen)*circD],//circle center by offseting from point 2
338 | outR=max(minR,rp[1][2]-thick*sgn*mode) //ensures radii are never too small.
339 | )
340 | concat(cen,outR);
341 |
342 | function is90or270(ang)=ang==90?1:ang==270?1:0;
343 |
344 | function findPoint(ang1,refpoint1,ang2,refpoint2,r=0)=
345 | // finds the intersection of two lines given two angles and points on those lines
346 | let(
347 | overrideX=is90or270(ang1)?
348 | refpoint1.x:
349 | is90or270(ang2)?
350 | refpoint2.x:
351 | 0,
352 | m1=tan(ang1),
353 | c1=refpoint1.y-m1*refpoint1.x,
354 | m2=tan(ang2),
355 | c2=refpoint2.y-m2*refpoint2.x,
356 | outputX=overrideX?overrideX:(c2-c1)/(m1-m2),
357 | outputY=is90or270(ang1)?m2*outputX+c2:m1*outputX+c1
358 | )
359 | [outputX,outputY,r];
360 |
361 | function beamChain(radiiPoints,offset1=0,offset2,mode=0,minR=0,startAngle,endAngle)=
362 | /*This function takes a series of radii points and plots points to run along side at a consistant distance, think of it as offset but for line instead of a polygon
363 | radiiPoints=radii points,
364 | offset1 & offset2= The two offsets that give the beam it's thickness. When using with mode=2 only offset1 is needed as there is no return path for the polygon
365 | minR=min radius, if all of your radii are set properly within the radii points this value can be ignored
366 | startAngle & endAngle= Angle at each end of the beam, different mode determine if this angle is relative to the ending legs of the beam or absolute.
367 | mode=1 - include endpoints startAngle&2 are relative to the angle of the last two points and equal 90deg if not defined
368 | mode=2 - Only the forward path is defined, useful for combining the beam with other radii points, see examples for a use-case.
369 | mode=3 - include endpoints startAngle&2 are absolute from the x axis and are 0 if not defined
370 | negative radiuses only allowed for the first and last radii points
371 |
372 | As it stands this function could probably be tidied a lot, but it works, I'll tidy later*/
373 | let(
374 | offset2undef=offset2==undef?1:0,
375 | offset2=offset2undef==1?0:offset2,
376 | CWorCCW1=sign(offset1)*CWorCCW(radiiPoints),
377 | CWorCCW2=sign(offset2)*CWorCCW(radiiPoints),
378 | offset1=abs(offset1),
379 | offset2b=abs(offset2),
380 | Lrp3=len(radiiPoints)-3,
381 | Lrp=len(radiiPoints),
382 | startAngle=mode==0&&startAngle==undef?
383 | getAngle(radiiPoints[0],radiiPoints[1])+90:
384 | mode==2&&startAngle==undef?
385 | 0:
386 | mode==0?
387 | getAngle(radiiPoints[0],radiiPoints[1])+startAngle:
388 | startAngle,
389 | endAngle=mode==0&&endAngle==undef?
390 | getAngle(radiiPoints[Lrp-1],radiiPoints[Lrp-2])+90:
391 | mode==2&&endAngle==undef?
392 | 0:
393 | mode==0?
394 | getAngle(radiiPoints[Lrp-1],radiiPoints[Lrp-2])+endAngle:
395 | endAngle,
396 | OffLn1=[for(i=[0:Lrp3]) offset1==0?radiiPoints[i+1]:parallelFollow([radiiPoints[i],radiiPoints[i+1],radiiPoints[i+2]],offset1,minR,mode=CWorCCW1)],
397 | OffLn2=[for(i=[0:Lrp3]) offset2==0?radiiPoints[i+1]:parallelFollow([radiiPoints[i],radiiPoints[i+1],radiiPoints[i+2]],offset2b,minR,mode=CWorCCW2)],
398 |
399 | Rp1=abs(radiiPoints[0].z),
400 | Rp2=abs(radiiPoints[Lrp-1].z),
401 |
402 | endP1aAngle = getAngle(radiiPoints[0],radiiPoints[1]),
403 | endP1a=findPoint(endP1aAngle, OffLn1[0], startAngle,radiiPoints[0], Rp1),
404 |
405 | endP1bAngle = getAngle(radiiPoints[Lrp-1],radiiPoints[Lrp-2]),
406 | endP1b=findPoint(endP1bAngle, OffLn1[len(OffLn1)-1], endAngle,radiiPoints[Lrp-1], Rp2),
407 |
408 | endP2aAngle = getAngle(radiiPoints[0],radiiPoints[1]),
409 | endP2a=findPoint(endP2aAngle, OffLn2[0], startAngle,radiiPoints[0], Rp1),
410 |
411 | endP2bAngle = getAngle(radiiPoints[Lrp-1],radiiPoints[Lrp-2]),
412 | endP2b=findPoint(endP2bAngle, OffLn2[len(OffLn1)-1], endAngle,radiiPoints[Lrp-1], Rp2),
413 |
414 | absEnda=getAngle(endP1a,endP2a),
415 | absEndb=getAngle(endP1b,endP2b),
416 | negRP1a=[cos(absEnda)*radiiPoints[0].z*10+endP1a.x, sin(absEnda)*radiiPoints[0].z*10+endP1a.y, 0.0],
417 | negRP2a=[cos(absEnda)*-radiiPoints[0].z*10+endP2a.x, sin(absEnda)*-radiiPoints[0].z*10+endP2a.y, 0.0],
418 | negRP1b=[cos(absEndb)*radiiPoints[Lrp-1].z*10+endP1b.x, sin(absEndb)*radiiPoints[Lrp-1].z*10+endP1b.y, 0.0],
419 | negRP2b=[cos(absEndb)*-radiiPoints[Lrp-1].z*10+endP2b.x, sin(absEndb)*-radiiPoints[Lrp-1].z*10+endP2b.y, 0.0],
420 | OffLn1b=(mode==0||mode==2)&&radiiPoints[0].z<0&&radiiPoints[Lrp-1].z<0?
421 | concat([negRP1a],[endP1a],OffLn1,[endP1b],[negRP1b])
422 | :(mode==0||mode==2)&&radiiPoints[0].z<0?
423 | concat([negRP1a],[endP1a],OffLn1,[endP1b])
424 | :(mode==0||mode==2)&&radiiPoints[Lrp-1].z<0?
425 | concat([endP1a],OffLn1,[endP1b],[negRP1b])
426 | :mode==0||mode==2?
427 | concat([endP1a],OffLn1,[endP1b])
428 | :
429 | OffLn1,
430 | OffLn2b=(mode==0||mode==2)&&radiiPoints[0].z<0&&radiiPoints[Lrp-1].z<0?
431 | concat([negRP2a],[endP2a],OffLn2,[endP2b],[negRP2b])
432 | :(mode==0||mode==2)&&radiiPoints[0].z<0?
433 | concat([negRP2a],[endP2a],OffLn2,[endP2b])
434 | :(mode==0||mode==2)&&radiiPoints[Lrp-1].z<0?
435 | concat([endP2a],OffLn2,[endP2b],[negRP2b])
436 | :mode==0||mode==2?
437 | concat([endP2a],OffLn2,[endP2b])
438 | :
439 | OffLn2
440 | )//end of let()
441 | offset2undef==1?OffLn1b:concat(OffLn2b,revList(OffLn1b));
442 |
443 | function revList(list)=//reverse list
444 | let(Llist=len(list)-1)
445 | [for(i=[0:Llist]) list[Llist-i]];
446 |
447 | function CWorCCW(p)=
448 | let(
449 | Lp=len(p),
450 | e=[for(i=[0:Lp-1])
451 | (p[listWrap(i+0,Lp)].x-p[listWrap(i+1,Lp)].x)*(p[listWrap(i+0,Lp)].y+p[listWrap(i+1,Lp)].y)
452 | ]
453 | )
454 | sign(polySum(e));
455 |
456 | function CentreN2PointsArc(p1,p2,cen,mode=0,fn)=
457 | /* This function plots an arc from p1 to p2 with fn increments using the cen as the centre of the arc.
458 | the mode determines how the arc is plotted
459 | mode==0, shortest arc possible
460 | mode==1, longest arc possible
461 | mode==2, plotted clockwise
462 | mode==3, plotted counter clockwise
463 | */
464 | let(
465 | isCWorCCW=CWorCCW([cen,p1,p2]),//determine the direction of rotation
466 | //determine the arc angle depending on the mode
467 | p1p2Angle=cosineRuleAngle(p2,cen,p1),
468 | arcAngle=
469 | mode==0?p1p2Angle:
470 | mode==1?p1p2Angle-360:
471 | mode==2&&isCWorCCW==-1?p1p2Angle:
472 | mode==2&&isCWorCCW== 1?p1p2Angle-360:
473 | mode==3&&isCWorCCW== 1?p1p2Angle:
474 | mode==3&&isCWorCCW==-1?p1p2Angle-360:
475 | cosineRuleAngle(p2,cen,p1),
476 | r=pointDist(p1,cen),//determine the radius
477 | p1Angle=getAngle(cen,p1) //angle of line 1
478 | )
479 | [for(i=[0:fn])
480 | let(angleIncrement=(arcAngle/fn)*i*isCWorCCW)
481 | [cos(p1Angle+angleIncrement)*r+cen.x,sin(p1Angle+angleIncrement)*r+cen.y]];
482 |
483 | function translateRadiiPoints(radiiPoints,tran=[0,0],rot=0)=
484 | [for(i=radiiPoints)
485 | let(
486 | a=getAngle([0,0],[i.x,i.y]),//get the angle of the this point
487 | h=pointDist([0,0],[i.x,i.y]) //get the hypotenuse/radius
488 | )
489 | [h*cos(a+rot)+tran.x,h*sin(a+rot)+tran.y,i.z]//calculate the point's new position
490 | ];
491 |
492 | module round2d(OR=3,IR=1){
493 | offset(OR,$fn=100){
494 | offset(-IR-OR,$fn=100){
495 | offset(IR,$fn=100){
496 | children();
497 | }
498 | }
499 | }
500 | }
501 |
502 | module shell2d(offset1,offset2=0,minOR=0,minIR=0){
503 | difference(){
504 | round2d(minOR,minIR){
505 | offset(max(offset1,offset2)){
506 | children(0);//original 1st child forms the outside of the shell
507 | }
508 | }
509 | round2d(minIR,minOR){
510 | difference(){//round the inside cutout
511 | offset(min(offset1,offset2)){
512 | children(0);//shrink the 1st child to form the inside of the shell
513 | }
514 | if($children>1){
515 | for(i=[1:$children-1]){
516 | children(i);//second child and onwards is used to add material to inside of the shell
517 | }
518 | }
519 | }
520 | }
521 | }
522 | }
523 |
524 | module internalSq(size,r,center=0){
525 | tran=center==1?[0,0]:size/2;
526 | translate(tran){
527 | square(size,true);
528 | offs=sin(45)*r;
529 | for(i=[-1,1],j=[-1,1]){
530 | translate([(size.x/2-offs)*i,(size.y/2-offs)*j])circle(r);
531 | }
532 | }
533 | }
534 |
535 | module extrudeWithRadius(length,r1=0,r2=0,fn=30){
536 | n1=sign(r1);n2=sign(r2);
537 | r1=abs(r1);r2=abs(r2);
538 | translate([0,0,r1]){
539 | linear_extrude(length-r1-r2){
540 | children();
541 | }
542 | }
543 | for(i=[0:fn-1]){
544 | translate([0,0,i/fn*r1]){
545 | linear_extrude(r1/fn+0.01){
546 | offset(n1*sqrt(sq(r1)-sq(r1-i/fn*r1))-n1*r1){
547 | children();
548 | }
549 | }
550 | }
551 | translate([0,0,length-r2+i/fn*r2]){
552 | linear_extrude(r2/fn+0.01){
553 | offset(n2*sqrt(sq(r2)-sq(i/fn*r2))-n2*r2){
554 | children();
555 | }
556 | }
557 | }
558 | }
559 | }
560 |
561 | function mirrorPoints(radiiPoints,rot=0,endAttenuation=[0,0])= //mirrors a list of points about Y, ignoring the first and last points and returning them in reverse order for use with polygon or polyRound
562 | let(
563 | a=translateRadiiPoints(radiiPoints,[0,0],-rot),
564 | temp3=[for(i=[0+endAttenuation[0]:len(a)-1-endAttenuation[1]])
565 | [a[i][0],-a[i][1],a[i][2]]
566 | ],
567 | temp=translateRadiiPoints(temp3,[0,0],rot),
568 | temp2=revList(temp3)
569 | )
570 | concat(radiiPoints,temp2);
571 |
572 | function processRadiiPoints(rp)=
573 | [for(i=[0:len(rp)-1])
574 | processRadiiPoints2(rp,i)
575 | ];
576 |
577 | function processRadiiPoints2(list,end=0,idx=0,result=0)=
578 | idx>=end+1?result:
579 | processRadiiPoints2(list,end,idx+1,relationalRadiiPoints(result,list[idx]));
580 |
581 | function cosineRuleBside(a,c,C)=c*cos(C)-sqrt(sq(a)+sq(c)+sq(cos(C))-sq(c));
582 |
583 | function absArelR(po,pn)=
584 | let(
585 | th2=atan(po[1]/po[0]),
586 | r2=sqrt(sq(po[0])+sq(po[1])),
587 | r3=cosineRuleBside(r2,pn[1],th2-pn[0])
588 | )
589 | [cos(pn[0])*r3,sin(pn[0])*r3,pn[2]];
590 |
591 | function relationalRadiiPoints(po,pi)=
592 | let(
593 | p0=pi[0],
594 | p1=pi[1],
595 | p2=pi[2],
596 | pv0=pi[3][0],
597 | pv1=pi[3][1],
598 | pt0=pi[3][2],
599 | pt1=pi[3][3],
600 | pn=
601 | (pv0=="y"&&pv1=="x")||(pv0=="r"&&pv1=="a")||(pv0=="y"&&pv1=="a")||(pv0=="x"&&pv1=="a")||(pv0=="y"&&pv1=="r")||(pv0=="x"&&pv1=="r")?
602 | [p1,p0,p2,concat(pv1,pv0,pt1,pt0)]:
603 | [p0,p1,p2,concat(pv0,pv1,pt0,pt1)],
604 | n0=pn[0],
605 | n1=pn[1],
606 | n2=pn[2],
607 | nv0=pn[3][0],
608 | nv1=pn[3][1],
609 | nt0=pn[3][2],
610 | nt1=pn[3][3],
611 | temp=
612 | pn[0]=="l"?
613 | [po[0],pn[1],pn[2]]
614 | :pn[1]=="l"?
615 | [pn[0],po[1],pn[2]]
616 | :nv0==undef?
617 | [pn[0],pn[1],pn[2]]//abs x, abs y as default when undefined
618 | :nv0=="a"?
619 | nv1=="r"?
620 | nt0=="a"?
621 | nt1=="a"||nt1==undef?
622 | [cos(n0)*n1,sin(n0)*n1,n2]//abs angle, abs radius
623 | :absArelR(po,pn)//abs angle rel radius
624 | :nt1=="r"||nt1==undef?
625 | [po[0]+cos(pn[0])*pn[1],po[1]+sin(pn[0])*pn[1],pn[2]]//rel angle, rel radius
626 | :[pn[0],pn[1],pn[2]]//rel angle, abs radius
627 | :nv1=="x"?
628 | nt0=="a"?
629 | nt1=="a"||nt1==undef?
630 | [pn[1],pn[1]*tan(pn[0]),pn[2]]//abs angle, abs x
631 | :[po[0]+pn[1],(po[0]+pn[1])*tan(pn[0]),pn[2]]//abs angle rel x
632 | :nt1=="r"||nt1==undef?
633 | [po[0]+pn[1],po[1]+pn[1]*tan(pn[0]),pn[2]]//rel angle, rel x
634 | :[pn[1],po[1]+(pn[1]-po[0])*tan(pn[0]),pn[2]]//rel angle, abs x
635 | :nt0=="a"?
636 | nt1=="a"||nt1==undef?
637 | [pn[1]/tan(pn[0]),pn[1],pn[2]]//abs angle, abs y
638 | :[(po[1]+pn[1])/tan(pn[0]),po[1]+pn[1],pn[2]]//abs angle rel y
639 | :nt1=="r"||nt1==undef?
640 | [po[0]+(pn[1]-po[0])/tan(90-pn[0]),po[1]+pn[1],pn[2]]//rel angle, rel y
641 | :[po[0]+(pn[1]-po[1])/tan(pn[0]),pn[1],pn[2]]//rel angle, abs y
642 | :nv0=="r"?
643 | nv1=="x"?
644 | nt0=="a"?
645 | nt1=="a"||nt1==undef?
646 | [pn[1],sign(pn[0])*sqrt(sq(pn[0])-sq(pn[1])),pn[2]]//abs radius, abs x
647 | :[po[0]+pn[1],sign(pn[0])*sqrt(sq(pn[0])-sq(po[0]+pn[1])),pn[2]]//abs radius rel x
648 | :nt1=="r"||nt1==undef?
649 | [po[0]+pn[1],po[1]+sign(pn[0])*sqrt(sq(pn[0])-sq(pn[1])),pn[2]]//rel radius, rel x
650 | :[pn[1],po[1]+sign(pn[0])*sqrt(sq(pn[0])-sq(pn[1]-po[0])),pn[2]]//rel radius, abs x
651 | :nt0=="a"?
652 | nt1=="a"||nt1==undef?
653 | [sign(pn[0])*sqrt(sq(pn[0])-sq(pn[1])),pn[1],pn[2]]//abs radius, abs y
654 | :[sign(pn[0])*sqrt(sq(pn[0])-sq(po[1]+pn[1])),po[1]+pn[1],pn[2]]//abs radius rel y
655 | :nt1=="r"||nt1==undef?
656 | [po[0]+sign(pn[0])*sqrt(sq(pn[0])-sq(pn[1])),po[1]+pn[1],pn[2]]//rel radius, rel y
657 | :[po[0]+sign(pn[0])*sqrt(sq(pn[0])-sq(pn[1]-po[1])),pn[1],pn[2]]//rel radius, abs y
658 | :nt0=="a"?
659 | nt1=="a"||nt1==undef?
660 | [pn[0],pn[1],pn[2]]//abs x, abs y
661 | :[pn[0],po[1]+pn[1],pn[2]]//abs x rel y
662 | :nt1=="r"||nt1==undef?
663 | [po[0]+pn[0],po[1]+pn[1],pn[2]]//rel x, rel y
664 | :[po[0]+pn[0],pn[1],pn[2]]//rel x, abs y
665 | )
666 | temp;
667 |
668 | function invtan(run,rise)=
669 | let(a=abs(atan(rise/run)))
670 | rise==0&&run>0?
671 | 0:rise>0&&run>0?
672 | a:rise>0&&run==0?
673 | 90:rise>0&&run<0?
674 | 180-a:rise==0&&run<0?
675 | 180:rise<0&&run<0?
676 | a+180:rise<0&&run==0?
677 | 270:rise<0&&run>0?
678 | 360-a:"error";
679 |
680 | function cosineRuleAngle(p1,p2,p3)=
681 | let(
682 | p12=abs(pointDist(p1,p2)),
683 | p13=abs(pointDist(p1,p3)),
684 | p23=abs(pointDist(p2,p3))
685 | )
686 | acos((sq(p23)+sq(p12)-sq(p13))/(2*p23*p12));
687 |
688 | function polySum(list, idx = 0, result = 0) =
689 | idx >= len(list) ? result : polySum(list, idx + 1, result + list[idx]);
690 |
691 | function sq(x)=x*x;
692 | function getGradient(p1,p2)=(p2.y-p1.y)/(p2.x-p1.x);
693 | function getAngle(p1,p2)=p1==p2?0:invtan(p2[0]-p1[0],p2[1]-p1[1]);
694 | function getMidpoint(p1,p2)=[(p1[0]+p2[0])/2,(p1[1]+p2[1])/2]; //returns the midpoint of two points
695 | function pointDist(p1,p2)=sqrt(abs(sq(p1[0]-p2[0])+sq(p1[1]-p2[1]))); //returns the distance between two points
696 | function isColinear(p1,p2,p3)=getGradient(p1,p2)==getGradient(p2,p3)?1:0;//return 1 if 3 points are colinear
697 | module polyline(p, width=0.3) {
698 | for(i=[0:max(0,len(p)-1)]){
699 | color([i*1/len(p),1-i*1/len(p),0,0.5])line(p[i],p[listWrap(i+1,len(p) )],width);
700 | }
701 | } // polyline plotter
702 | module line(p1, p2 ,width=0.3) { // single line plotter
703 | hull() {
704 | translate(p1){
705 | circle(width);
706 | }
707 | translate(p2){
708 | circle(width);
709 | }
710 | }
711 | }
712 |
713 | function getpoints(p)=[for(i=[0:len(p)-1])[p[i].x,p[i].y]];// gets [x,y]list of[x,y,r]list
714 | function listWrap(x,x_max=1,x_min=0) = (((x - x_min) % (x_max - x_min)) + (x_max - x_min)) % (x_max - x_min) + x_min; // wraps numbers inside boundaries
715 | function rnd(a = 1, b = 0, s = []) =
716 | s == [] ?
717 | (rands(min(a, b), max( a, b), 1)[0]):(rands(min(a, b), max(a, b), 1, s)[0]); // nice rands wrapper
718 |
--------------------------------------------------------------------------------
/roundAnythingExamples.scad:
--------------------------------------------------------------------------------
1 | include
2 |
3 | basicPolyRoundExample();
4 | // polyLineExample();
5 | // parametricPolyRoundExample();
6 | // experimentalParametricPolyRoundExample();
7 | // conflicResolutionExample();
8 | // translateRadiiPointsExample();
9 | // 2dShellExample();
10 | // beamChainExample();
11 | // mirrorPointsExample();
12 | // radiusExtrudeExample();
13 | // polyRoundExtrudeExample();
14 |
15 |
16 | // testing
17 | // testGeometries();
18 |
19 | module basicPolyRoundExample(){
20 | // polyLine is a dev helper. Aim is to show the points of the polygon and their order before
21 | // you're ready to move on to polyRound and a polygon
22 | radiiPoints=[[-4,0,1],[5,3,1.5],[0,7,0.1],[8,7,10],[20,20,0.8],[10,0,10]];
23 | polygon(polyRound(radiiPoints,30));
24 | %translate([0,0,0.3])polygon(getpoints(radiiPoints));//transparent copy of the polgon without rounding
25 | }
26 |
27 | module polyLineExample() {
28 | radiiPoints=[[-4,0,1],[5,3,1.5],[0,7,0.1],[8,7,10],[20,20,0.8],[10,0,10]];
29 | polyline(polyRound(radiiPoints,3), 0.05);
30 | translate([0,10,0])
31 | polyline(radiiPoints, 0.05);
32 | }
33 |
34 | module parametricPolyRoundExample() {
35 | //Example of how a parametric part might be designed with this tool
36 | width=20; height=25;
37 | slotW=8; slotH=15;
38 | slotPosition=8;
39 | minR=1.5; farcornerR=6;
40 | internalR=3;
41 | // radii points defined in terms of shape dimensions
42 | points=[
43 | [0, 0, farcornerR],
44 | [0, height, minR],
45 | [slotPosition, height, minR],
46 | [slotPosition, height-slotH, internalR],
47 | [slotPosition+slotW, height-slotH, internalR],
48 | [slotPosition+slotW, height, minR],
49 | [width, height, minR],
50 | [width, 0, minR]
51 | ];
52 | translate([-25,0,0]){
53 | polygon(polyRound(points,5));
54 | }
55 | %translate([-25,0,0.2])polygon(getpoints(points));//transparent copy of the polgon without rounding
56 | }
57 |
58 | module experimentalParametricPolyRoundExample() {
59 | //very similar to parametric example, but with some experimental syntax
60 | width=20; height=25;
61 | slotW=8; slotH=15;
62 | slotPosition=8;
63 | minR=1.5; farcornerR=6;
64 | internalR=3;
65 | // radii points defined in terms of shape dimensions
66 | points2=[
67 | [0, 0, farcornerR],
68 | ["l", height, minR],
69 | [slotPosition, "l", minR],
70 | ["l", height-slotH, internalR],
71 | [slotPosition+slotW, "l", internalR],
72 | ["l", height, minR],
73 | [width, "l", minR],
74 | ["l", height*0.2, minR],
75 | [45, 0, minR+5, "ayra"]
76 | ];
77 | translate([-50,0,0])polygon(polyRound(points2,5));
78 | %translate([-50,0,0.2])polygon(getpoints(processRadiiPoints(points2)));//transparent copy of the polgon without rounding
79 | }
80 |
81 | module conflicResolutionExample(){
82 | //example of radii conflict handling and debuging feature
83 | function makeRadiiPoints(r1, r2)=[[0,0,0],[0,20,r1],[20,20,r2],[20,0,0]];
84 |
85 | // the squre shape being 20 wide, two radii of 10 both fit into the shape (just)
86 | translate([-25,0,0])polygon(polyRound(makeRadiiPoints(10,10),50));
87 |
88 | //radii are too large and are reduced to fit and will be reduce to 10 and 10
89 | translate([0,0,0])polygon(polyRound(makeRadiiPoints(30,30),50));
90 |
91 | //radii are too large again and are reduced to fit, but keep their ratios r1 will go from 10 to 4 and r2 will go from 40 to 16
92 | translate([25,0,0])polygon(polyRound(makeRadiiPoints(10,40),50));
93 |
94 | //mode 2 = no radii limiting
95 | translate([50,0,0])polygon(polyRound(makeRadiiPoints(12,20),50,mode=2));
96 | }
97 |
98 | module translateRadiiPointsExample() {
99 | // This example shows how a list of points can be used multiple times in the same
100 | nutW=5.5; nutH=3; boltR=1.6;
101 | minT=2; minR=0.8;
102 | function nutCapture(startAndEndRadius=0)=[
103 | [-boltR, 0, startAndEndRadius],
104 | [-boltR, minT, 0],
105 | [-nutW/2, minT, minR],
106 | [-nutW/2, minT+nutH, minR],
107 | [nutW/2, minT+nutH, minR],
108 | [nutW/2, minT, minR],
109 | [boltR, minT, 0],
110 | [boltR, 0, startAndEndRadius],
111 | ];
112 |
113 | negativeNutCapture=translateRadiiPoints(nutCapture(),tran=[5,0]);
114 | rotatedNegativeNutCapture=translateRadiiPoints(nutCapture(1),tran=[20,5],rot=90);
115 | aSquare=concat(
116 | [[0,0,0]],
117 | negativeNutCapture,
118 | [[20,0,0]],
119 | rotatedNegativeNutCapture,
120 | [[20,10,0]],
121 | [[0,10,0]]
122 | );
123 | polygon(polyRound(aSquare,20));
124 | translate([-5,0,0])polygon(polyRound(nutCapture(),20));
125 | }
126 |
127 | module 2dShellExample(){
128 | radiiPoints=[[-4,0,1],[5,3,1.5],[0,7,0.1],[8,7,10],[20,20,0.8],[10,0,10]];
129 | linear_extrude(1)shell2d(-0.5)polygon(polyRound(radiiPoints,30));
130 | translate([0,-10,0])linear_extrude(1)shell2d(-0.5){
131 | polygon(polyRound(radiiPoints,30));
132 | translate([8,8])gridpattern(memberW = 0.3, sqW = 1, iter = 17, r = 0.2);
133 | }
134 | }
135 |
136 | module beamChainExample(){
137 | function beamPoints(r1,r2,rStart=0,rEnd=0)=[[0,0,rStart],[2,8,0],[5,4,r1],[15,10,r2],[17,2,rEnd]];
138 |
139 | // chained lines by themselves
140 | translate([0,0,0]){
141 | radiiPoints=beamPoints(0,0);
142 | for(i=[0: len(radiiPoints)-1]){color("red")translate([radiiPoints[i].x,radiiPoints[i].y,0])cylinder(d=0.2, h=1);}
143 | linear_extrude(1)polygon(polyRound(beamChain(radiiPoints,offset1=0.02, offset2=-0.02),20));
144 | }
145 |
146 |
147 | // Add some radii to the line transitions
148 | translate([0,-7,0]){
149 | radiiPoints=beamPoints(2,1);
150 | for(i=[0: len(radiiPoints)-1]){color("red")translate([radiiPoints[i].x,radiiPoints[i].y,0])cylinder(d=0.2, h=1);}
151 | linear_extrude(1)polygon(polyRound(beamChain(radiiPoints,offset1=0.02, offset2=-0.02),20));
152 | }
153 |
154 | // Give make the lines beams with some thickness
155 | translate([0,-7*2,0]){
156 | radiiPoints=beamPoints(2,1);
157 | linear_extrude(1)polygon(polyRound(beamChain(radiiPoints,offset1=0.5, offset2=-0.5),20));
158 | }
159 |
160 | // Add an angle to the start of the beam
161 | translate([0,-7*3,0]){
162 | radiiPoints=beamPoints(2,1);
163 | linear_extrude(1)polygon(polyRound(beamChain(radiiPoints,offset1=0.5, offset2=-0.5, startAngle=45),20));
164 | }
165 |
166 | // Put a negative radius at the start for transationing to a flat surface
167 | translate([0,-7*4,0]){
168 | radiiPoints=beamPoints(2,1,rStart=-0.7);
169 | linear_extrude(1)polygon(polyRound(beamChain(radiiPoints,offset1=0.5, offset2=-0.5, startAngle=45),20));
170 | }
171 |
172 | // Define more points for a polygon to be atteched to the end of the beam chain
173 | clipP=[[16,1.2,0],[16,0,0],[16.5,0,0],[16.5,1,0.2],[17.5,1,0.2],[17.5,0,0],[18,0,0],[18,1.2,0]];
174 | translate([-15,-7*5+3,0]){
175 | for(i=[0:len(clipP)-1]){color("red")translate([clipP[i].x,clipP[i].y,0])cylinder(d=0.2, h=1);}
176 | linear_extrude(1)polygon(polyRound(clipP,20));
177 | }
178 |
179 | // Attached to the end of the beam chain by dividing the beam paths in forward and return and
180 | // concat other polygon inbetween
181 | translate([0,-7*6,0]){
182 | radiiPoints=beamPoints(2,1);
183 | forwardPath=beamChain(radiiPoints,offset1=0.5,startAngle=-15,mode=2);
184 | returnPath=revList(beamChain(radiiPoints,offset1=-0.5,startAngle=-15,mode=2));
185 | entirePath=concat(forwardPath,clipP,returnPath);
186 | linear_extrude(1)polygon(polyRound(entirePath,20));
187 | }
188 |
189 | // Add transitioning radii into the end polygong
190 | translate([0,-7*7-2,0]){
191 | radiiPoints=beamPoints(2,1,rEnd=3);
192 | forwardPath=beamChain(radiiPoints,offset1=0.5,startAngle=-15,mode=2);
193 | returnPath=revList(beamChain(radiiPoints,offset1=-0.5,startAngle=-15,mode=2));
194 | entirePath=concat(forwardPath,clipP,returnPath);
195 | linear_extrude(1)polygon(polyRound(entirePath,20));
196 | }
197 |
198 | // Define multiple shells from the the one set of points
199 | translate([0,-7*9,0]){
200 | radiiPoints=beamPoints(2,1,rEnd=3);
201 | for(i=[0:2]){linear_extrude(1)polygon(polyRound(beamChain(radiiPoints,offset1=-1+i*0.4, offset2=-1+i*0.4+0.25),20));}
202 | }
203 | }
204 |
205 | module mirrorPointsExample(){
206 | function points(endR=0)=[[0,0,0],[2,8,0],[5,4,3],[15,10,0.5],[10,2,endR]];
207 | mirroredPoints=mirrorPoints(points(0),0,[1,0]);
208 | polygon(polyRound(mirroredPoints,20));
209 | mirroredPoints2=mirrorPoints(points(7),0,[1,0]);
210 | translate([0,-20,0])polygon(polyRound(mirroredPoints2,20));
211 | }
212 |
213 | module radiusExtrudeExample(){
214 | radiiPoints=[[-4,0,1],[5,3,1.5],[0,7,0.1],[8,7,10],[20,20,0.8],[10,0,10]];
215 | extrudeWithRadius(3,0.5,0.5,50)polygon(polyRound(radiiPoints,30));
216 | #translate([7,4,3])extrudeWithRadius(3,-0.5,0.95,50)circle(1,$fn=30);
217 | }
218 |
219 | module polyRoundExtrudeExample(){
220 | radiiPoints=[[10,0,10],[20,20,1.1],[8,7,10],[0,7,0.3],[5,3,0.1],[-4,0,1]];
221 | polyRoundExtrude(radiiPoints,2,0.5,-0.8,fn=8);
222 | }
223 |
224 | module gridpattern(memberW = 4, sqW = 12, iter = 5, r = 3){
225 | round2d(0, r)rotate([0, 0, 45])translate([-(iter * (sqW + memberW) + memberW) / 2, -(iter * (sqW + memberW) + memberW) / 2])difference(){
226 | square([(iter) * (sqW + memberW) + memberW, (iter) * (sqW + memberW) + memberW]);
227 | for (i = [0:iter - 1], j = [0:iter - 1]){
228 | translate([i * (sqW + memberW) + memberW, j * (sqW + memberW) + memberW])square([sqW, sqW]);
229 | }
230 | }
231 | }
232 |
233 |
234 | module testGeometries() {
235 | // Check these shapes preview (plus "thrown together") and render correctly with each PR
236 | points = [
237 | [0, 10, 5],
238 | [10, 0, 5],
239 | [0, -10, 5],
240 | [-10, 0, 5],
241 | ];
242 | reversedPoints = [
243 | [-10, 0, 5],
244 | [0, -10, 5],
245 | [10, 0, 5],
246 | [0, 10, 5],
247 | ];
248 | polyRoundExtrudeTestShape(points);
249 | translate([0,20,0])polyRoundExtrudeTestShape(reversedPoints);
250 |
251 | // Bug report submitted by @lopisan in issue #16, similar to #11, geometry breaks with 90 degree angles
252 | didBreakWhen0=0;
253 | issue16pointsa=[[0, 0, 0], [0+didBreakWhen0, 10, 0], [10, 10+didBreakWhen0, 0]];
254 | translate([20,0,0])linear_extrude(1)polygon(polyRound( beamChain(issue16pointsa, offset1=1, offset2=-1), 30));
255 |
256 | didBreakWhen0b=1e-6;
257 | issue16pointsb=[[0, 0, 0], [0+didBreakWhen0b, 10, 0], [10, 10+didBreakWhen0b, 0]];
258 | translate([20,15,0])linear_extrude(1)polygon(polyRound( beamChain(issue16pointsb, offset1=1, offset2=-1), 30));
259 |
260 | }
261 |
262 | module polyRoundExtrudeTestShape(points) {
263 | // make sure no faces are inverted
264 | difference() {
265 | translate([0, 0, -2.5]) polyRoundExtrude(points,r1=-1,r2=1);
266 | sphere(d=9);
267 | translate([0,0,7])sphere(d=9);
268 | }
269 | }
--------------------------------------------------------------------------------
/unionRoundMask-Doc.md:
--------------------------------------------------------------------------------
1 | ## unionRoundMask / unionRound
2 | ---
3 | ### Union with round fillet at selected places. Created for Round-Anything by TLC123 (2021).
4 | A shortcut for faster fillet union is enabled when the operation constrained to convex operands.
5 | For most cases unionRoundMask / unionRound replaces MinkowskiRound.
6 | Combined with a system of mask selectors, unionRound becomes even more versetile.
7 |
8 | 
9 |
10 | ## unionRoundMask
11 | ---
12 | Union with round fillet at selected places.
13 | ### module unionRoundMask(r=1, detail = 5 , q=70, epsilon = 1e-6, showMask = true , includeOperands = true)
14 |
15 | Masks are a method to perform unionRound on selected only areas,
16 | and circumvents the previous limitation to common convex work area.
17 | Mask are essentially just common primitives that is used to mark out areas by intersection.
18 |
19 | r:
20 | * approximate radius for fillet. Exact radius is dependant on crease angle.
21 | * detail: numbers of fillet segments. 1 is essensially a chamfer/bevel.
22 | * Set low for faster preview. ( $preview?3:10 )
23 |
24 | q:
25 | * determine how detailed clad operations are.
26 | * Set low for faster preview. ( $preview?30:70 )
27 |
28 | epsilon:
29 | * For debugging, leave as is.
30 |
31 | showMask:
32 | * For debugging, try it.
33 |
34 | includeOperands:
35 | * For debugging, render only fillet when false.
36 |
37 | ### usage:
38 | ````
39 | unionRoundMask( r = 1 , detail = $preview ? 3 : 10 , q = $preview ? 30 : 70 )
40 | {
41 | yourObject1();
42 | yourObject2();
43 | yourMask1();
44 | yourMask2();
45 | yourMask3();
46 | // ...
47 | // ...
48 | // ...
49 | }
50 | ````
51 |
52 | ---
53 | ## unionRound
54 | ---
55 | ### module unionRound(r=1, detail = 5 , q=70, epsilon = 1e-6 , includeOperands = true )
56 |
57 | Module unionRound is the underlying work module of unionRoundMask.
58 | It can be used by it self, in some cases faster but more raw.
59 |
60 | ### usage:
61 | ````
62 | unionRoundMask( r = 1 , detail = $preview ? 3 : 10 , q= $preview ? 30 : 70 )
63 | {
64 | yourObject1();
65 | yourObject2();
66 | }
67 | ````
68 |
69 | ---
70 | ## intersectionRound
71 | ---
72 | module intersectionRound(r, q=70, epsilon = 1e-6,showOperands = true)
73 | prototype module
74 | Undocumented for now.
75 |
76 | ---
77 | ## helpers
78 | ---
79 | ````
80 | module clad(r,q=70) // speed is limited to convex operand.
81 | module shell(r,q=70) // not in use.
82 | module inset(r,q=20) // speed is limited to convex operand.
83 | ````
84 | ---
85 | ## Citation
86 | ---
87 | ### roundUnionMask Includes code based on examples from:
88 | Kogan, Jonathan (2017)
89 | "A New Computationally Efficient Method for Spacing n Points on a Sphere,"
90 | Rose-Hulman Undergraduate Mathematics Journal: Vol. 18 : Iss. 2 , Article 5.
91 | Available at: [https://scholar.rose-hulman.edu/rhumj/vol18/iss2/5]
92 |
--------------------------------------------------------------------------------
/unionRoundMask.scad:
--------------------------------------------------------------------------------
1 | ////////////////////////////////////////////////////////
2 | /*
3 | unionRound() 1.0 Module by Torleif Ceder - TLC123 late summer 2021
4 | Pretty fast Union with radius, But limited to a subset of cases
5 | Usage
6 | unionRound( radius , detail )
7 | {
8 | YourObject1();
9 | YourObject2();
10 | }
11 | unionRoundMask (r, detail , epsilon ,showMask )
12 | {
13 | YourObject1();
14 | YourObject2();
15 | YourMask();
16 | YourMask();
17 |
18 | // ...
19 | // ...
20 | // ...
21 |
22 | }
23 | limitations:
24 | 0. Only really fast when boolean operands are convex,
25 | Minkowski is fast in that case.
26 | 1. Boolean operands may be concave but can only touch
27 | in a single convex area
28 | 2. Radius is of elliptic type and is only approximate r
29 | were operand intersect at perpendicular angle.
30 | */
31 | ////////////////////////////////////////////////////////
32 | // Demo code
33 | demo= false;
34 | if (demo)
35 | unionRoundMask( r=1.5 , detail= 5 , q=70, includeOperands = true) {
36 | cube([10,10,2],true);
37 | rotate([20,-10,0])cylinder(5,1,1,$fn=12);
38 | translate([0,0,1.5])cube([1.5,10,3],center=true); //mask
39 | rotate(90)
40 | translate([0,0,1.5])cube([3,10,3],center=true); //mask
41 | }
42 |
43 | // end of demo code
44 | //
45 | module unionRoundMask(r=1, detail = 5,q=70, epsilon = 1e-6, showMask = false, includeOperands = true) {
46 | //automask if none
47 | if($children <=2){
48 | unionRoundMask(r,detail,q,epsilon,showMask, includeOperands)
49 | {
50 | children(0);
51 | children(1);
52 | clad(max(r),q) intersection(){
53 | children(0);
54 | children(1);
55 | }
56 | }
57 | }
58 | else {
59 | union() {
60 | if(includeOperands){
61 | children(0);
62 | children(1);
63 | }
64 | if (showMask && $children > 2) %
65 | for (i = [2: max(2, $children - 1)]) children(i);
66 |
67 | if ($children > 2)
68 | for (i = [2: max(2, $children - 1)]) {
69 | intersection() {
70 | children(i);
71 |
72 | unionRound(r, detail,q, epsilon,includeOperands) {
73 | intersection() {
74 | children(0);
75 | children(i); // mask
76 | }
77 | intersection() {
78 | children(1);
79 | children(i); // mask
80 | }
81 | }
82 | }
83 | }
84 | }
85 | }
86 | }
87 |
88 |
89 | module unionRound(r=1, detail = 5,q=70, epsilon = 1e-6, includeOperands=true) {
90 | if(includeOperands){
91 | children(0);
92 | children(1);
93 | }
94 | step = 90 / detail;
95 | rx=is_list(r)?r[1]:r;
96 | ry=is_list(r)?r[0]:r;
97 | union()for (i = [0: detail-1]) {
98 | {
99 | x = rx - sin(i * step ) * rx;
100 | y = ry - cos(i * step ) * ry;
101 | xi = rx - sin((i * step + step) ) * rx;
102 | yi = ry - cos((i * step + step) ) * ry;
103 | // color(rands(0, 1, 3, i))
104 | hull() {
105 | intersection() {
106 | // shell(epsilon)
107 | clad(x,q) children(0);
108 | // shell(epsilon)
109 | clad(y,q) children(1);
110 | }
111 | intersection() {
112 | // shell(epsilon)
113 | clad(xi,q) children(0);
114 | // shell(epsilon)
115 | clad(yi,q) children(1);
116 | }
117 | }
118 | }
119 | }
120 | }
121 |
122 | // prototype module slow maybe on concave feature
123 | module intersectionRound(r, q=70, epsilon = 1e-6,showOperands = true) {
124 | %if (showOperands){children(0);
125 | children(1);}
126 |
127 | clad(r,q) inset(r,q)
128 | hull()intersection() {
129 | children(0);
130 | children(1);
131 | }
132 |
133 | }
134 |
135 | // unionRound helper expand by r
136 | module clad(r,q=70) {
137 | minkowski() {
138 | children();
139 | // icosphere(r,2);
140 | isosphere(r,q);
141 | }
142 | }
143 | // unionRound helper
144 | module shell(r,q=70) {
145 | difference() {
146 | clad(r,q) children();
147 | children();
148 | }
149 | }
150 |
151 | // inset 3d "negative offset", optimally on convex hull
152 | // else jagged inner corners by q quality factor
153 | module inset(r,q=20){
154 | a= generatepoints(q)*r;
155 | //#children();
156 | intersection_for(t=a){
157 | translate(t ) children();
158 | }
159 | }
160 |
161 |
162 | /*
163 | // The following is a sphere with some equidistant properties.
164 | // Not strictly necessary
165 |
166 | Kogan, Jonathan (2017) "A New Computationally Efficient Method for Spacing n Points on a Sphere," Rose-Hulman Undergraduate Mathematics Journal: Vol. 18 : Iss. 2 , Article 5.
167 | Available at: https://scholar.rose-hulman.edu/rhumj/vol18/iss2/5 */
168 |
169 | function sphericalcoordinate(x,y)= [cos(x )*cos(y ), sin(x )*cos(y ), sin(y )];
170 | function NX(n=70,x)=
171 | let(toDeg=57.2958,PI=acos(-1)/toDeg,
172 | start=(-1.+1./(n-1.)),increment=(2.-2./(n-1.))/(n-1.) )
173 | [ for (j= [0:n-1])let (s=start+j*increment )
174 | sphericalcoordinate( s*x*toDeg, PI/2.* sign(s)*(1.-sqrt(1.-abs(s)))*toDeg)];
175 | function generatepoints(n=70)= NX(n,0.1+1.2*n);
176 | module isosphere(r,q=70){
177 | a= generatepoints(q);
178 | scale(r)hull()polyhedron(a,[[for(i=[0:len(a)-1])i]]);
179 | }
180 |
--------------------------------------------------------------------------------