├── 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 | ![unionround-doc-example](https://user-images.githubusercontent.com/10944617/130456818-c5fd43d1-e6df-4e88-8474-aed1a0c3ca31.png) 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 | --------------------------------------------------------------------------------