├── screwdemo.png ├── screwdemo-old.png ├── title.md ├── new-triangulation-issue.png ├── Screenshot_20200508_201200--demo.png ├── Screenshot_20200508_201402--demo.png ├── Screenshot_20200509_083139--demo.png ├── Screenshot_20200509_092654--demo.png ├── Screenshot_20210419_103821--collage.png ├── OpenSCADs-native-triangulation-issue.png ├── Screenshot_20210419_114537--demo-of-issue.png ├── Screenshot_20210419_135434--demo-of-issue.png ├── test-HOF-features.scad ├── addressed-and-remaining-issues.md ├── demo.scad ├── basic-screw-profiles.scad ├── lib-FDMscrews.scad ├── LICENSE ├── README.md ├── lib-FDMscrews-HOF.scad └── minimal_extrusion_core.scad /screwdemo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mechadense/scad-lib-FDMscrews/HEAD/screwdemo.png -------------------------------------------------------------------------------- /screwdemo-old.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mechadense/scad-lib-FDMscrews/HEAD/screwdemo-old.png -------------------------------------------------------------------------------- /title.md: -------------------------------------------------------------------------------- 1 | # scad-lib-FDMscrews 2 | Screws with geometry optimized for FDM/FFF printing -- as an OpenSCAD library 3 | -------------------------------------------------------------------------------- /new-triangulation-issue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mechadense/scad-lib-FDMscrews/HEAD/new-triangulation-issue.png -------------------------------------------------------------------------------- /Screenshot_20200508_201200--demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mechadense/scad-lib-FDMscrews/HEAD/Screenshot_20200508_201200--demo.png -------------------------------------------------------------------------------- /Screenshot_20200508_201402--demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mechadense/scad-lib-FDMscrews/HEAD/Screenshot_20200508_201402--demo.png -------------------------------------------------------------------------------- /Screenshot_20200509_083139--demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mechadense/scad-lib-FDMscrews/HEAD/Screenshot_20200509_083139--demo.png -------------------------------------------------------------------------------- /Screenshot_20200509_092654--demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mechadense/scad-lib-FDMscrews/HEAD/Screenshot_20200509_092654--demo.png -------------------------------------------------------------------------------- /Screenshot_20210419_103821--collage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mechadense/scad-lib-FDMscrews/HEAD/Screenshot_20210419_103821--collage.png -------------------------------------------------------------------------------- /OpenSCADs-native-triangulation-issue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mechadense/scad-lib-FDMscrews/HEAD/OpenSCADs-native-triangulation-issue.png -------------------------------------------------------------------------------- /Screenshot_20210419_114537--demo-of-issue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mechadense/scad-lib-FDMscrews/HEAD/Screenshot_20210419_114537--demo-of-issue.png -------------------------------------------------------------------------------- /Screenshot_20210419_135434--demo-of-issue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mechadense/scad-lib-FDMscrews/HEAD/Screenshot_20210419_135434--demo-of-issue.png -------------------------------------------------------------------------------- /test-HOF-features.scad: -------------------------------------------------------------------------------- 1 | // test-HOF-features.scad 2 | // Test file to verify all API features work in the HOF version 3 | 4 | use 5 | 6 | // Test different features systematically 7 | spacing = 25; 8 | 9 | // Row 1: Basic features 10 | translate([0*spacing, 0, 0]) 11 | screwByPitch(pitch=3, length=12, d0=10, dr=1.5, profile="cubic"); 12 | 13 | translate([1*spacing, 0, 0]) 14 | screwByPitch(pitch=3, length=12, d0=10, dr=1.5, profile="sinusodial"); 15 | 16 | translate([2*spacing, 0, 0]) 17 | screwByPitch(pitch=3, length=12, d0=10, dr=1.5, profile="triangular"); 18 | 19 | // Row 2: Chamfers and widens 20 | translate([0*spacing, spacing, 0]) 21 | screwByPitch(pitch=3, length=12, d0=10, dr=1.5, profile="cubic", 22 | chamfer1=true, chamfer2=true); 23 | 24 | translate([1*spacing, spacing, 0]) 25 | screwByPitch(pitch=3, length=12, d0=10, dr=1.5, profile="cubic", 26 | widen1=true, widen2=true); 27 | 28 | translate([2*spacing, spacing, 0]) 29 | screwByPitch(pitch=3, length=12, d0=10, dr=1.5, profile="cubic", 30 | chamfer1=true, widen2=true); 31 | 32 | // Row 3: Resolution and starts 33 | translate([0*spacing, 2*spacing, 0]) 34 | screwByPitch(pitch=3, length=12, d0=10, dr=1.5, profile="cubic", 35 | circum_resol=48, axial_resol=24); 36 | 37 | translate([1*spacing, 2*spacing, 0]) 38 | screwByPitch(pitch=3, length=12, d0=10, dr=1.5, profile="cubic", 39 | starts=2); 40 | 41 | translate([2*spacing, 2*spacing, 0]) 42 | screwByPitch(pitch=3, length=12, d0=10, dr=1.5, profile="cubic", 43 | starts=3, offsetangle=45); 44 | 45 | // Row 4: Flat parameter and screwByTwist 46 | translate([0*spacing, 3*spacing, 0]) 47 | screwByPitch(pitch=3, length=12, d0=10, dr=1.5, profile="cubic", 48 | flat=0.8); 49 | 50 | translate([1*spacing, 3*spacing, 0]) 51 | screwByPitch(pitch=3, length=12, d0=10, dr=1.5, profile="cubic", 52 | flat=0.4); 53 | 54 | translate([2*spacing, 3*spacing, 0]) 55 | screwByTwist(twist=360*3, length=12, d0=10, dr=1.5, profile="cubic", 56 | flat=0.6); 57 | 58 | // Test labels (comment/uncomment as needed) 59 | s=10; 60 | translate([0*spacing, -s, 0]) 61 | text("basic", size=3, halign="center"); 62 | translate([1*spacing, -s, 0]) 63 | text("sine", size=3, halign="center"); 64 | translate([2*spacing, -s, 0]) 65 | text("tri", size=3, halign="center"); 66 | 67 | translate([0*spacing, spacing-s, 0]) 68 | text("chamfers", size=3, halign="center"); 69 | translate([1*spacing, spacing-s, 0]) 70 | text("widens", size=3, halign="center"); 71 | translate([2*spacing, spacing-s, 0]) 72 | text("mixed", size=3, halign="center"); 73 | 74 | translate([0*spacing, 2*spacing-s, 0]) 75 | text("low-res", size=3, halign="center"); 76 | translate([1*spacing, 2*spacing-s, 0]) 77 | text("2-start", size=3, halign="center"); 78 | translate([2*spacing, 2*spacing-s, 0]) 79 | text("3-start+offset", size=3, halign="center"); 80 | 81 | translate([0*spacing, 3*spacing-s, 0]) 82 | text("flat=0.8", size=3, halign="center"); 83 | translate([1*spacing, 3*spacing-s, 0]) 84 | text("flat=0.4", size=3, halign="center"); 85 | translate([2*spacing, 3*spacing-s, 0]) 86 | text("twist", size=3, halign="center"); 87 | -------------------------------------------------------------------------------- /addressed-and-remaining-issues.md: -------------------------------------------------------------------------------- 1 | Up: [README.md](README.md) 2 | 3 | # Addressed and remaining issues 4 | 5 | OpenSCADs default linear_extrude(hetght=...,twist=...) is not usable because of the following: 6 | 7 | 8 | ShowcaseProplemWithOpenSCADs linear_extrude 9 | Not a nice triangulation. A spikey mess. 10 | Here especially well visible at the perimeter on the left. 11 | 12 | **The causing factors:** 13 | 14 | * Screw threads typically have low pitch compared to the screw circumference 15 | * OpenSCADs linear_exrude(..) directly connects vertices on one sampling height to the rotated vertices on the next sampling height.
16 | Even when the twist per sampling-height-step becomes a good part of a full reolution. 17 | 18 | **Both of these together lead to:** 19 | 20 | * Triangles getting massively sheared out circumferencially. One gets highly degenerate triangles. 21 | * Picked out pairs of two adjacent triangles that share one edge become so heavily sheared that the 22 | hinge-point between these two triangles becomes highly concave leading to the whole triangulation of the thread becoming a spikey mess. 23 | The only way to prevent that would be to **pick unreasonably high axial sampling denstities**.
24 | But that would:
(1) make OpenSCAD unusably slow,
(2) make file sizes wastefully big and
(3) may give problems in subsequent slicing of the stl-file to the gcode-file 25 | => **Not a viable option**. 26 | * Triangulation triangles start taking secant shortcuts though the screw instead of approximating the desired screws shape with a nice tangential fit. 27 | 28 | One could call this problem : "hyperbolic necking" 29 | Since it is similar to twisting a cylinder of wires such that it becomes a hyperboloid. 30 | 31 | ## The chosen solution 32 | 33 | Implement an alternate triangulation. 34 | Sample the radii r for a fixed raster of the other two cylindrical coordinates (r,h). 35 | This is now independent of the geometry of the screw. 36 | In a factored out module that could potentially also be used for other things than screws. 37 | 38 | ## The new problem 39 | 40 | Unfortunately it was overlooked that this does not work for all thread profiles equally well. 41 | Check out this fail: 42 | 43 | 44 | ShowcaseNewTriangulationIssue 45 | Not a nice triangulation. A steppy mess. 46 | 47 | Especially profiles with high slopes in their cross section are not well handeled. 48 | One gets bad axial jumps in the thread wherever an axial sampling z-height is crossed over. 49 | 50 | **The problem:** 51 | 52 | * the constant height triangulation triangle stripes are crossing over (C0 or C1) discontinuities of the thread profile in a very shallow angle 53 | 54 | **What is effected?** 55 | 56 | * Maximally susceptible are square wave profiles and sawtooth profiles. Followed by steep trapezoidal profiles. 57 | * Also circular profiles suffer a bit because they have small spots where their derivative becomes infinite. 58 | * Sine profiles and cubic osculating profiles do not face a problem. 59 | I'd recommens sticking to these for now. 60 | 61 | # Plans 62 | 63 | **Eventually TODO** Replace the triangulation module with one 64 | that uses a sampling in screw coordinates matching the pitch of the screw. 65 | Without OpenSCADs linear_extrude(...) shearing problem. 66 | 67 | # Rant about fundamental limitations 68 | 69 | OpenSCAD is a pure but not a functional language. That is: 70 | 71 | * **pure:** a function always returns the same output given the same input (unlike imperative languages like python) – awesome 72 | * **not functional:** functions cannot take as arguments other unevaluated functions
There are no "higher-order functions".
Functions are not [first class citizens](https://en.wikipedia.org/wiki/First-class_citizen). – pretty bad 73 | 74 | Due to the latter making an OpenSCAD library parametric in thread profiles is necessarily a hack. 75 | OpenSCADs lack of records and lack of any proper data structures makes this worse. 76 | 77 | -------------------------------------------------------------------------------- /demo.scad: -------------------------------------------------------------------------------- 1 | /* 2 | Just some demos for 3 | how the lib-FDMscrews library can be used. 4 | */ 5 | 6 | // scad-lib-FDMscrews 7 | use 8 | 9 | 10 | // ####################################### 11 | // Uncomment all but one of the following to activate a demo: 12 | // ####################################### 13 | 14 | !nutAndBoltDemo(); // most useful for a testprint thus this onne left uncommented 15 | //!multiDemo(); 16 | //!profileOverviewDemo(); 17 | 18 | // ----- 19 | 20 | // !screwByTwist(twist=0,length=10,profile = "cubic",starts=6); 21 | // this is a straight extrusion rather than a screw 22 | // straight extrusion means infinite pitch 23 | // one gets infinite pitch choosing twist to be zero 24 | 25 | // ----- 26 | 27 | // The exact same screw generated 28 | // once with screwByPitch 29 | // once with sceewByTwist 30 | //translate([-6,0,0]) screwByTwist(twist=360*4,length=3.6*4,profile = "cubic"); 31 | //translate([+6,0,0]) screwByPitch(pitch=3.6,length=3.6*4,profile = "cubic"); 32 | 33 | // ####################################### 34 | // ####################################### 35 | 36 | module multiDemo() 37 | { 38 | nutAndBoltDemo(); 39 | 40 | translate([0,-20,12/2*0.6]) 41 | rotate(90,[0,1,0]) flatscrewThreadedRod(); 42 | } 43 | module nutAndBoltDemo() 44 | { 45 | translate([-30, 0, 0]) nutdemo(); 46 | translate([0,0,12/2*0.6]) rotate(90,[0,1,0]) 47 | flatscrewDemo(); 48 | } 49 | 50 | 51 | module profileOverviewDemo() 52 | { 53 | rotate(90,[0,1,0]) 54 | { 55 | ss = 10; ww = 16; 56 | 57 | translate([0,+2*ww,ss*0]) screwByPitch(profile="cubic",flat=0.6,length=7.2); 58 | translate([0,+2*ww,ss*1]) screwByPitch(profile="sinusodial",flat=0.6,length=7.2); 59 | translate([0,+2*ww,ss*2]) screwByPitch(profile="sine_asym",flat=0.6,length=7.2); 60 | 61 | translate([0,+1*ww,ss*0]) screwByPitch(profile="trapezoid",flat=0.6,length=7.2); 62 | translate([0,+1*ww,ss*1]) screwByPitch(profile="triangular",flat=0.6,length=7.2); 63 | translate([0,+1*ww,ss*2]) screwByPitch(profile="triang_asym",flat=0.6,length=7.2); 64 | 65 | translate([0,+0*ww,ss*0]) screwByPitch(profile="sine_blobby",flat=0.6,length=7.2); 66 | translate([0,+0*ww,ss*1]) screwByPitch(profile="sine_spikey",flat=0.6,length=7.2); 67 | translate([0,+0*ww,ss*2]) screwByPitch(profile="circular",flat=0.6,length=7.2); 68 | 69 | // the currently implemented triangulation method 70 | // messes up the following profiles pretty badly 71 | // rect, saw_rising, saw_falling 72 | 73 | translate([0,-1*ww,ss*0]) screwByPitch(profile="rect",flat=0.6,length=7.2); 74 | translate([0,-1*ww,ss*1]) screwByPitch(profile="saw_rising",flat=0.6,length=7.2); 75 | translate([0,-1*ww,ss*2]) screwByPitch(profile="saw_falling",flat=0.6,length=7.2); 76 | 77 | } 78 | } 79 | 80 | 81 | 82 | 83 | 84 | module flatscrewThreadedRod() 85 | { 86 | screwByPitch(pitch=3.6, length=32, d0=12, dr=1.5, 87 | flat=0.6, chamfer1=true, chamfer2=true); 88 | } 89 | 90 | module flatscrewDemo() 91 | { 92 | lscrewthread = 32; // 24 93 | d = 12; 94 | c = 1.0; // chamfersize 95 | lthreadless = 10; 96 | lhandle = 6; 97 | whandle = 20; 98 | 99 | intersection() 100 | { 101 | union() 102 | { 103 | screwByPitch(pitch=3.6, length=lscrewthread, d0=d, dr=1.5, widen1=true, chamfer2=true); 104 | scale([1,1,-1]) cylinder(r=d/2,h=lthreadless+lhandle/2,center=false,$fn=48); 105 | } 106 | cube([d*0.6,100,100],center=true); 107 | } 108 | translate([0,0,-lhandle/2-10]) 109 | b3cube(d*0.6,whandle,lhandle,c,c,c); 110 | //cube([d*0.6,20,6],center=true); 111 | } 112 | 113 | 114 | // lib-cyclogearprofiles.scad 115 | // cycloid gear library can be used as alternate outer surface for the nut 116 | module nutdemo() 117 | { 118 | hnut = 3.6*2.5; 119 | wnut = 10; dnut = wnut/cos(30); 120 | c = 1.5; // 1 was a bit too small 121 | eps=0.05; 122 | clrscrew = 0.55; 123 | // 0.45 was still quite a bit too less 124 | // 0.75 quite loose but works 125 | // 0.60 loose but ok 126 | 127 | rotate(30,[0,0,1]) 128 | difference() 129 | { 130 | hull() 131 | { 132 | cylinder(r=dnut-c,h=hnut,$fn=6,center=false); 133 | translate([0,0,c]) 134 | cylinder(r=dnut,h=hnut-2*c,$fn=6,center=false); 135 | } 136 | translate([0,0,-eps]) 137 | screwByPitch(pitch=3.6, length=hnut+2*0.05, d0=12+2*clrscrew, dr=1.5, 138 | widen1=true, widen2=true); 139 | } 140 | } 141 | 142 | module b3cube(x,y,z,bx,by,bz) 143 | { 144 | hull() 145 | { 146 | cube([x-2*bx,y-2*by,z-0*bz],center=true); 147 | cube([x-2*bx,y-0*by,z-2*bz],center=true); 148 | cube([x-0*bx,y-2*by,z-2*bz],center=true); 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /basic-screw-profiles.scad: -------------------------------------------------------------------------------- 1 | /* 2 | Date: 2016-07-23 ... 2017-12-13 3 | Author: Lukas M. Süss aka mechadense 4 | Name: basic-screw-profiles 5 | License: Public Domain 6 | */ 7 | 8 | 9 | // #################################### 10 | // ------------------------------------ PROFILE MODIFIERS 11 | 12 | // All profiles are supposed be properly defined in the bounding box: [[-1,-1],[+1,+1]] -- covering the whole range [[_,-1],[_,+1]] 13 | 14 | // These triangular and saw waveforms (of period length 1) are tools 15 | // to make non-periodic functions periodic 16 | 17 | function trirepeater0(x) = abs(((x*2+1)%2)-1)*2-1; // 0:1(=maximum) periodlength:1 18 | // f(-1/2)=1 linear-down f(0)=-1 linear-up f(+1/2)=1 19 | 20 | function sawrepeater1(x) = x-floor(x); // (can be used as profile) 21 | // f(0)=0 linear-up f(1)=1 jump-down 22 | 23 | 24 | // ####### profile amplitudes must be between 0 and 1 reaching both ##### !!! 25 | // "crop01(x)" can be used to cull the profile function to these limits 26 | function crop01(x) = min(1,max(0,x)); 27 | 28 | 29 | // #################################### 30 | // ------------------------------------ PROFILES 31 | // you may add more profiles here ... (plus corresponding entries at §§ locations) 32 | 33 | // simple sinusodial 34 | function profile_sinusodial(x) = (1-cos(x))/2; // thread profile 35 | 36 | // rectangular 37 | function profile_rectangular(x) = (sign(sin(x))+1)/2; 38 | 39 | // simple triangular (linear) (kinks make speed steps make acceleration spikes) 40 | function profile_triangular(x) = abs(((x/360*2+1)%2)-1); 41 | // triangular //abs(((x+1)%2)-1) 42 | 43 | // simple cubical: 44 | // squarewavejerk is designed such 45 | // that when the printer nozzle is moved along its range [-1:+1] 46 | // the acting accelerations and forces make a triangle wave 47 | // avoiding spikes in change rate of the acceleration (the "jerk") 48 | function squarewavejerk(x) = (1+pow(x,3)/2-3*x/2)/2; 49 | function profile_cubic(x) = squarewavejerk(trirepeater0(x/360)); 50 | 51 | // simple quartical: TODO ... probably of no use thus not implemented 52 | // function profile5(x) = "yet undefined"; 53 | 54 | // triangular stretched and cropped to trapezoidal 55 | // TODO add some steeper versions 56 | // trapezoidal stretchable & shiftable 57 | t1 = 2; t2 = 0*0.25; 58 | //function profile_trapezoid(x,t1,t2) = crop01( (profile2(x)-1/2)*t1+1/2+t2 ); 59 | function profile_trapezoid(x,t1,t2) = crop01( (profile_triangular(x)-1/2)*t1+1/2+t2 ); 60 | // BUG: for some screw-lengths this leads to soem non-manifold results :S 61 | 62 | function profile_trapezoid0(x) = profile_trapezoid(x,2,0*1/4); 63 | function profile_trapezoid_steep(x) = profile_trapezoid(x,4,0*1/4); // yet unused 64 | 65 | function profile_saw_rising(x) = ( ceil(x/360) -(x/360) ); 66 | function profile_saw_falling(x) = ( (x/360)-floor(x/360) ); 67 | 68 | // sinusodial saw 69 | function profile7(x) = profile_sinusodial(sawrepeater1(x/360)*360/2); // pos & neg 70 | // TODO: barrel & pillow 71 | 72 | // first terms of fourier series of rectangular wave 73 | // sin(x)+1/3*sin(3*x)+1/5*sin(5*x)+ ... 74 | function profile_fourier5_square(x) = 75 | crop01( ( sin(x)+1/3*sin(3*x)+1/5*sin(5*x)*0 )/2+1/2 ); 76 | function profile101(x) = (1 - cos( x+cos(x)*45 ))/2; 77 | 78 | // circular (note the harsh 0° overhangs !) 79 | function circles(x) = 80 | 1/2*( 81 | (x<=1/2) ? 0.99+sqrt(1-pow(4*x-1,2)) : 82 | (x> 1/2) ? 1-sqrt(1-pow(4*x-3,2)) :1 83 | ); 84 | function profile_circular(x) = sawrepeater1(circles(x/360)); 85 | 86 | //echo( profile6(360.1)); // phi>360 PROBLEM 87 | // where did profile6 go ??? 88 | 89 | // sinusodial convex braid 90 | function profile_sine_blobby(x) = abs(cos(x/2)); 91 | // sinusodial concave spikes 92 | function profile_sine_spikey(x) = 1-abs(cos(x/2)); 93 | // TODO: combination => curly brace shape ..... 94 | 95 | // gap trouble trianglesaw4 / sinusodial trianglesaw4 96 | function slanttriangle(x,n) = ( (x<(1-1/n)) ? (n/(n-1))*x : 1 - (n)*x ); 97 | function profile_triang_asym(x) = sawrepeater1( slanttriangle(x/(360+0.1),4) ); 98 | // saw from stretched and squeezed sinusodial 99 | // => soft profile with overhangs mainly on one side in case of 100 | // NOT RECOMMENDET vertical orientation print 101 | function profile_sine_asym(x) = 102 | 1/2-1/2*cos( 180* sawrepeater1( slanttriangle(x/(360+0.1),4) ) ); 103 | 104 | 105 | // ############################## 106 | 107 | 108 | // parameters r0 , dr 109 | function polar_profile(phi, name="unitcircle", parameters = []) = 110 | (name == "unitcircle") ? 1 : 111 | (name == "testprofile1") ? 10*profile_sinusodial(phi) : 1; 112 | //(name == "testprofile1") ? testprofile1(phi) : 1; 113 | // testprofile1 not defined 114 | 115 | 116 | 117 | // ############## OLD STUFF 118 | /* 119 | // currently the format parameter is pretty undefined ... TODO change that 120 | // todo ... radial clearingshift .... offset clearingshift ... 121 | // The list of predefined profile - supposed to be hidden away in a seperate library 122 | 123 | // name == "syntaxtree" ? ... 124 | // use the paramlist as a syntax tree for algebraic expressions 125 | // STOP -- just a hypothetical possibility 126 | // would require serious language reimplementation in the 127 | // unsuitable host language OpenSCAD 128 | // its easier for users to just extend the function list here instead 129 | */ 130 | 131 | -------------------------------------------------------------------------------- /lib-FDMscrews.scad: -------------------------------------------------------------------------------- 1 | 2 | use // all the profile_… functions 3 | use // get_eval_indices & get_eval_params 4 | // see minimal-extrusion-boilerplate-demo.scad 5 | 6 | screwByPitch(); 7 | //screwByTwist(); 8 | 9 | defresolcirc = 96; // 64, 96, 128 10 | defresolax = 48; // 32, 48, 64 11 | 12 | // ########################## 13 | 14 | module screwByPitch 15 | ( pitch = 3 16 | , length = 12 17 | , d0 = 10 18 | , dr = 1.5 19 | , circum_resol = defresolcirc 20 | , axial_resol = defresolax 21 | , starts = 1 22 | , profile = "cubic" 23 | , offsetangle = 0 24 | , flat = 1 25 | , chamfer1 = false 26 | , chamfer2 = false 27 | , widen1 = false 28 | , widen2 = false 29 | ) 30 | { 31 | r0 = d0/2; 32 | twist = length/(pitch*starts)*360; 33 | axial_resol2 = ceil(axial_resol*length/(2*r0)); 34 | // ceil is to prevent non-manifoldness of the result 35 | 36 | intersection() 37 | { 38 | screw_internal 39 | ( profile 40 | , r0, dr 41 | , twist 42 | , length // <<<<<<<<<< z_max = length 43 | , circum_resol 44 | , axial_resol2 // z_resol = axial_resol 45 | , starts 46 | , offsetangle 47 | ); 48 | screwcropper(length,r0,dr,flat,chamfer1,chamfer2,circum_resol); 49 | } 50 | screwaugmenter(length,r0,dr,flat,widen1,widen2,circum_resol); 51 | } 52 | 53 | module screwByTwist 54 | ( twist = 360*4 55 | , length = 12 56 | , d0 = 10 57 | , dr = 1.5 58 | , circum_resol = defresolcirc 59 | , axial_resol = defresolax 60 | , starts = 1 61 | , profile = "cubic" 62 | , offsetangle = 0 63 | , flat = 1 64 | , chamfer1 = false 65 | , chamfer2 = false 66 | , widen1 = false 67 | , widen2 = false 68 | ) 69 | { 70 | r0 = d0/2; 71 | axial_resol2 = ceil(axial_resol*length/(2*r0)); 72 | 73 | intersection() 74 | { 75 | screw_internal 76 | ( profile 77 | , r0, dr 78 | , twist 79 | , length // <<<<<<<<<< z_max = length 80 | , circum_resol 81 | , axial_resol2 // z_resol = axial_resol 82 | , starts 83 | , offsetangle 84 | ); 85 | screwcropper(length,r0,dr,flat,chamfer1,chamfer2,circum_resol); 86 | } 87 | screwaugmenter(length,r0,dr,flat,widen1,widen2,circum_resol); 88 | 89 | // translate([0,0,(length+2)/2-1]) 90 | // cube([2*r0*flat,(r0+dr)*2+2,length+2],center=true); 91 | //} 92 | } 93 | 94 | 95 | 96 | // ############################### 97 | // ############################### 98 | 99 | module screwaugmenter 100 | ( length, r0, dr 101 | , flat = 0.6 102 | , widen1 = false 103 | , widen2 = false 104 | , circum_resol = defresolcirc 105 | ) 106 | { 107 | if(widen1) 108 | { 109 | intersection() 110 | { 111 | translate([0,0,0]) 112 | cylinder(r1=r0,r2=r0-dr,h=dr,center=false,$fn=circum_resol); 113 | // the flat 114 | translate([0,0,dr/2]) 115 | cube([2*r0*flat,r0*2+2,dr],center=true); 116 | } 117 | } 118 | if(widen2) 119 | { 120 | intersection() 121 | { 122 | translate([0,0,length-dr]) 123 | cylinder(r2=r0,r1=r0-dr,h=dr,center=false,$fn=circum_resol); 124 | // the flat 125 | translate([0,0,length-dr/2]) 126 | cube([2*r0*flat,r0*2+2,dr],center=true); 127 | } 128 | } 129 | } 130 | 131 | module screwcropper 132 | ( length, r0, dr 133 | , flat = 0.6 134 | , chamfer1 = false 135 | , chamfer2 = false 136 | , circum_resol = defresolcirc 137 | ) 138 | { 139 | intersection() 140 | { 141 | union() 142 | { // chamfer 143 | if(chamfer1) 144 | { 145 | translate([0,0,0]) 146 | cylinder(r1=r0-dr,r2=r0,h=dr,center=false,$fn=circum_resol); 147 | } 148 | if(chamfer2) 149 | { 150 | translate([0,0,length-dr]) 151 | cylinder(r1=r0,r2=r0-dr,h=dr,center=false,$fn=circum_resol); 152 | } 153 | dl = 0 + (chamfer1 ? dr : 0) + (chamfer2 ? dr : 0); 154 | //echo("AAAAAAAA",chamfer1,camfer1 ? dr : 0); 155 | translate([0,0,chamfer1 ? dr : 0 ]) 156 | cylinder(r=r0,h=length-dl,center=false,$fn=circum_resol); 157 | } 158 | // the flat 159 | translate([0,0,(length+2)/2-1]) 160 | cube([2*r0*flat,r0*2+2,length+2*dr],center=true); 161 | } 162 | } 163 | 164 | 165 | 166 | // ############################### 167 | // ############################### 168 | 169 | module screw_internal 170 | ( screwtype = "circular" 171 | , r0 = 5 172 | , dr = 1.5 173 | , twist = 360*4 174 | , z_max = 12 175 | , circum_resol = defresolcirc*1 176 | , z_resol = defresolax*1 177 | , starts = 1 178 | , offsetangle = 0 // assert offsetangle>=0 179 | ) 180 | { 181 | // choose profile function by screwtype name .... 182 | function profileradiusfunction(phi) = 183 | // best for FDM 3D-printing: 184 | (screwtype == "cubic") ? (r0 + dr*(profile_cubic(phi+180)-1)) : 185 | // todo: profile cycloid ... 186 | (screwtype == "sinusodial") ? (r0 + dr*(profile_sinusodial(phi)-1)) : 187 | (screwtype == "triangular") ? (r0 + dr*(profile_triangular(phi)-1)) : 188 | (screwtype == "circular") ? (r0 + dr*(profile_circular(phi%360)-1)) : 189 | // just special case: 190 | (screwtype == "trapezoid") ? (r0 + dr*(profile_trapezoid0(phi)-1)) : 191 | (screwtype == "triang_asym")? (r0 + dr*(profile_triang_asym(phi%360)-1)) : 192 | (screwtype == "sine_asym") ? (r0 + dr*(profile_sine_asym(phi%360)-1)) : 193 | // bad triangulation :( 194 | (screwtype == "rect") ? (r0 + dr*(profile_rectangular(phi)-1)) : 195 | (screwtype == "saw_rising") ? (r0 + dr*(profile_saw_rising(phi)-1)) : 196 | (screwtype == "saw_falling")? (r0 + dr*(profile_saw_rising(phi)-1)) : 197 | // the funky ones ... 198 | (screwtype == "sine_spikey")? (r0 + dr*(profile_sine_spikey(phi)-1)) : 199 | (screwtype == "sine_blobby")? (r0 + dr*(profile_sine_blobby(phi)-1)) : 200 | // the really funky ones: 201 | 202 | (screwtype == "squarefourier5") ? (r0 + dr*(profile_fourier5_square(phi%360)-1)) : 203 | r0*phi/360; // in case of no match default to something crazy that can hardly be missed. 204 | //r0+0; // default to constant ? Opt to not to. 205 | 206 | evalparams = get_eval_params(circum_resol,z_resol,twist,z_max); 207 | evalindices = get_eval_indices(circum_resol,z_resol,twist,z_max); 208 | //echo(evalparams); 209 | //echo(evalindices); 210 | 211 | // WARNING: Too many unnamed arguments supplied, in file lib-FDMscrews.scad, line 217 -- ??? 212 | // i[0] ... direction vector with unit length 213 | // i[1] ... cylindercoord evaluation angle for user supplied function (can go negative!!) 214 | // i[2] ... cylindercoord evaluation height for user supplied function 215 | preevalpoints = 216 | [ for(i=evalparams) 217 | concat( i[0]*profileradiusfunction(starts*i[1]+offsetangle+360), i[2] ) ]; 218 | 219 | translate([0,0,0]) 220 | polyhedron(points = concat(preevalpoints,[[0,0,0],[0,0,z_max]]),faces = evalindices,convexity =3); 221 | // the last two explicit verticies are the bottom face and top face centerpoints 222 | // they are necessary for a perfectly symmetrical star shaped triangulation 223 | } 224 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. 166 | 167 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # scad-lib-FDMscrews 3 | 4 | **This is a minimal OpenSCAD library to generate screw threads 5 | that are optimized for FDM 3D printing.** 6 | There is a chapter further down on why you might want to or why you might not want to use 3D printed plastic screws. 7 | 8 | 9 | ShowcaseOfSomePossibleScrews 10 | 11 | Above examples in demo.scad 12 | Precompiled 3D-printable example in [demo_nutAndBolt.stl](demo_nutAndBolt.stl) 13 | 14 | **Existing standards are not a focus and not provided here since:** 15 | 16 | * Existing standards (metric, imperial, …) are not and were never meant for FDM printing (with its peculiar design constraints) 17 | * OpenSCAD libraries for existing standards do exist
(references at the very end) 18 | 19 | ## Installation 20 | 21 | Put this library in one of the standard locations for OpenSCAD libraries: 22 | http://en.wikibooks.org/wiki/OpenSCAD_User_Manual/Libraries 23 | **For linux it's:** $HOME/.local/share/OpenSCAD/libraries 24 | 25 | ## Basic Usage 26 | 27 | Import the library with: 28 | **use ** (recommended - orders of magnitude faster - complete backwards compatibility) 29 | **use ** (original version - was way too slow) 30 | The HOF (Higher-Order Function) version leverages OpenSCAD's ability to pass functions as parameters, 31 | eliminating expensive list operations like flatten() and enabling direct geometry generation with dramatic performance improvements. 32 | String-based profile selection is still supported for backwards compatibility. 33 | 34 | Then get started with the demos in demo.scad or the following: 35 | 36 | **screwByPitch(pitch=3.6, d0=12, dr=1.5, length=12, flat=0.6);** 37 | **screwByTwist(twist=360*4, d0=12, dr=1.5, length=12, flat=0.6);** 38 | 39 | * d0 … outermost diameter of the screw 40 | * dr … total profile depth from radius r0 = d0/2 down to (r0-dr) 41 | * pitch … standard thread pitch ([see wikipedia](https://en.wikipedia.org/wiki/Screw_thread#Lead,_pitch,_and_starts)) 42 | * twist … number of turns along the whole length of z_max 43 | * length … total length of the screw 44 | * flat … this cuts off the sides of the screws down to (d0*flat) 45 | * … more parameters are listed further down … 46 | 47 | _With twist=0 infinite pitch is possible. This gets you a fancy slide-rail._ 48 | _The pitch parameter needs to be bigger than zero. Caution: there's no safety net in place._ 49 | 50 | The flat parameter is provided so that the screws can be printed laying flat 51 | which **MASSIVELY** increases the tensile strength of the screw for most common FDM plastics. 52 | Assuming these screws are printed sideways (as highly suggested) 53 | by cutting off the sides one gets rid of: 54 | 55 | * need for support material (on the underside) 56 | * critical overhangs bigger than 45° (on the underside) 57 | * print layers with many insular spots leading to
many printhead jumps with retractions and stringing
(both on the underside and on the upper side) 58 | 59 | If you create a more fancy screw rather than than just a threaded rod, then 60 | instead of using the "flat" parameter you may want to do the flat-cutting manually 61 | at the end of the modelling process via an intersection with a cube. 62 | See: "flatscrewDemo()" in demo.scad 63 | 64 | ## Additional parameters that can be changed from their default (for both functions) are: 65 | 66 | * chamfer1 … at the origin: chamfer the thread 45° over the hole depth of the thread 67 | * widen1 … at the origin: taper the core 45° out to (r0 = d0/2) over the hole depth of the thread – (to strenghten a transition to an unthreaded section of the screw with diameter d0) 68 | 69 | The same for the other side of the screw: 70 | 71 | * chamfer2 … at the other end: same as above 72 | * widen2 … at the other end: same as above 73 | 74 | **Triangulation Resolution:** 75 | 76 | * circum_resol … number of triangulation subdivisions over the whole circumference (default is 96) 77 | * axial_resol … number of triangulation subdivisions over an axial lenght of d0 (default is 48) 78 | 79 | **Other parameters:** 80 | 81 | * starts … number of starts of the thread (default is 1 – see further notes below) 82 | * profile … the shape of the profile of the screw (default is cubic – see further notes below for a list of available options) 83 | * offsetangle … if the angle of where the thread is starting is relevant then it can be adjusted here (default is 0) 84 | * flat … cutoff factor in therms of of d0 (default is 1 that is no cuttoff is applied) 85 | 86 | 87 | ## Examples: 88 | 89 | Some demos are included in the file "demo.scad". 90 | The images at the tops where generated there. 91 | There is: 92 | 93 | * A demo for a basic nut and basic bolt – "nutAndBoltDemo()" 94 | * A demo for the profiles – "profileOverviewDemo()" 95 | * A demo with twist = 0 96 | * A demo how to make the same screw by giving pitch or twist 97 | 98 | ## Available thread profiles 99 | 100 | * Most useful for FDM printing (since fastly printable without jerks): "sinusodial" and "cubic" (default) 101 | * Classical profiles: "triangular", "trapezoid" 102 | * Asymmetric versions: "sine_asym", "triang_asym" 103 | * Exotic: "sine_spikey", "sine_blobby" 104 | * Crazy: "squarefourier5" a fifth order fourier approximation of a square wave (interesting: [Gibbs phenomenon](https://en.wikipedia.org/wiki/Gibbs_phenomenon)) 105 | 106 | Default is the "cubic" profile. 107 | It is a cubic piecewise function (a + b*x + c*x^2) 108 | 109 | * therefore the FDM 3D printing printhead speed is a quadatic piecewise function 110 | * therefore the FDM 3D printing printhead acceleration (and forces) is a continuous triangle wave without jumps (NO jerks) 111 | * mathematically simpler than a sine function (not that it matters much) 112 | 113 | The "sinusodial" profile has all its derivatives being sinusodial too. 114 | But in practice "sinusodial" and "cubic" can barely be distinguished. 115 | Especially if triangulation resolution is low. 116 | 117 | Profiles for which the here implemented triangulation method unfortunately turned out to be unsuitable: 118 | 119 | * Borderline usable: "circular" 120 | * Pretty much unusable: "rect", "saw_rising", "saw_falling" (BUG same as rising??), 121 | 122 | ## Further notes (known issues) 123 | 124 | The standard rotate_extrude function that comes with OpenSCAD was(is) 125 | not usable for this library because it leads to bad triangulations 126 | with very long and slim triangles (degenerate triangles) 127 | 128 | The alternate triangulation method used here (projecting out the cylinder coordinates and evaluating there) 129 | comeswith with its own limitations. 130 | It is not suitable for profiles with vertical or very steep flanks like e.g. 131 | rectangular profiles, sawtooth profiles, steep trapezoidal profiles and 132 | even circular profiles to some degree. 133 | 134 | * Starts > 1 … chose this for quick acting screws or lead screws 135 | (theses kind of threads can be found on drinking bottles and pickle jars) 136 | 137 | This library was pushing hard on the limitats of OpenSCAD and thus had to be dialed back. 138 | No support for higher order functions means I unfortunately cannot provide 139 | completely user definable profiles (without hacking hardcoded stuff in the library). 140 | 141 | For details see: [addressed-and-remaining-issues.md](addressed-and-remaining-issues.md) 142 | 143 | ## Outlook 144 | 145 | Go for a different triangulation approach where 146 | the triangulation follows the twist of the thread such that 147 | screw profiles with sharp drops like square, sawtooth, circular, cycloidal and more 148 | become possible too. 149 | 150 | Since for FDM printing the pitch needs to be quite big 151 | (because we print the screws laying on the side for giving them way more tensile strength 152 | and the standard FDM nozzel diamater is about 0.4mm) 153 | **these screws do not have good self holding by friction properties**. 154 | The planned solution: 155 | Designing of a dedicated cliplock for these kinds of low-pitched screws. 156 | 157 | ## Other screw-libraries for existing standards (none are meant for FDM printing) 158 | 159 | To my knowledge there are no screw standards that where 160 | specifically created to abide the constraints of FDM printing. 161 | 162 | **If you are looking for screw libraries that implement existing standards 163 | then here are some useful options:** 164 | 165 | aubenc's "Poor man's openscad screw library" 166 | https://www.thingiverse.com/thing:8796 167 | Usefulf for screws that are common in photo equipment. 168 | 169 | The MCAD librarie whick comes "batteries-included" with the newer OpenSCAD versions 170 | https://github.com/openscad/MCAD/blob/master/nuts_and_bolts.scad 171 | 172 | # Structure of internal dependencies: 173 | 174 | demo.scad depends on lib-FDMscrews.scad 175 | lib-FDMscrews.scad internally depends on 176 | 177 | * basic-screw-profiles.scad
in there are defined all the "profile_xyz" functions 178 | * minimal_extrusion_core.scad
in there are defined: get_eval_indices & get_eval_params 179 | 180 | # Why 3D printed plastic screws ?? 181 | 182 | **Valid reasons to for 3D printed plastic screws might be:** 183 | 184 | * Artistic style: You want to give your 3D printed multi-part designs a unique and cool look. 185 | * You want to avoid non 3D printable "vitamins" at all costs. (e.g. due to investigatins in self replication) 186 | * Out of some reason you can't get some needed screws fast enough for something that you want to extremely urgently try. 187 | 188 | 189 | **Likely invalid reasons:** 190 | 191 | * Saving money. Because printing these screws is time and labour intensive so the effective cost is way more than just the cost of the plastic. 192 | * Avoiding the weight of metal screws (or other properties like ferromagnetism, ...). This is hardly an argument since there also are plastic screws available commercially. And these non 3D printed plastic screws are smaller and even made form a better low friction plastic (Delrin aka POM aka polyoxymethalate). POM which is available as filament but extremely difficult to 3D print because of massive shinkage in conjunction with abysmal printbed adhesion (a devilish combo). 193 | 194 | -------------------------------------------------------------------------------- /lib-FDMscrews-HOF.scad: -------------------------------------------------------------------------------- 1 | // lib-FDMscrews-HOF.scad 2 | // High-performance drop-in replacement for lib-FDMscrews.scad 3 | // Keeps identical API but with 10-100x performance improvement 4 | 5 | // Import existing profile functions (no changes needed!) 6 | use 7 | 8 | // Default resolutions (same as original) 9 | defresolcirc = 96; // 64, 96, 128 10 | defresolax = 48; // 32, 48, 64 11 | 12 | // ============================================================================= 13 | // FAST GEOMETRY GENERATION (replaces minimal_extrusion_core.scad) 14 | // ============================================================================= 15 | 16 | function generate_screw_geometry_fast( 17 | screwtype, 18 | r0, dr, twist, z_max, 19 | circum_resol, z_resol, 20 | starts=1, offsetangle=0 21 | ) = 22 | let ( 23 | // Profile function selector - FIXED radius calculation 24 | profileradiusfunction = function(phi) 25 | let ( 26 | // Normalize angle to [0, 360) to prevent profile function issues 27 | phi_norm = ((phi % 360) + 360) % 360, 28 | profile_val = 29 | (screwtype == "cubic") ? profile_cubic(phi_norm) : 30 | (screwtype == "sinusodial") ? profile_sinusodial(phi_norm) : 31 | (screwtype == "triangular") ? profile_triangular(phi_norm) : 32 | (screwtype == "circular") ? profile_circular(phi_norm) : 33 | (screwtype == "trapezoid") ? profile_trapezoid0(phi_norm) : 34 | (screwtype == "triang_asym")? profile_triang_asym(phi_norm) : 35 | (screwtype == "sine_asym") ? profile_sine_asym(phi_norm) : 36 | (screwtype == "rect") ? profile_rectangular(phi_norm) : 37 | (screwtype == "saw_rising") ? profile_saw_rising(phi_norm) : 38 | (screwtype == "saw_falling")? profile_saw_rising(phi_norm) : 39 | (screwtype == "sine_spikey")? profile_sine_spikey(phi_norm) : 40 | (screwtype == "sine_blobby")? profile_sine_blobby(phi_norm) : 41 | (screwtype == "squarefourier5") ? profile_fourier5_square(phi_norm) : 42 | 0.5, // default fallback to middle value 43 | // Clamp profile value to reasonable range 44 | profile_clamped = max(0, min(1, profile_val)) 45 | ) 46 | r0 + dr * (profile_clamped - 1), // FIXED: Back to original formula (profile-1) 47 | 48 | // DEBUGGED vertex generation - removed problematic phi_offset for now 49 | vertices = [ 50 | [0, 0, 0], // bottom center (index 0) 51 | [0, 0, z_max], // top center (index 1) 52 | 53 | // Surface vertices - simplified for debugging 54 | for (i_z = [0:z_resol]) 55 | for (i_phi = [0:circum_resol-1]) 56 | let ( 57 | z = i_z * z_max / z_resol, 58 | phi_base = i_phi * 360 / circum_resol, 59 | // Simplified: remove phi_offset temporarily to isolate issues 60 | phi_twist = (i_z/z_resol) * twist, // Fixed sign - positive twist 61 | phi_eval = starts * (phi_base + phi_twist) + offsetangle, 62 | direction = [cos(phi_base), sin(phi_base)], // Use base direction 63 | r = profileradiusfunction(phi_eval), 64 | // Clamp radius to prevent geometry explosion - REMOVED clamping 65 | r_safe = r 66 | ) 67 | [r_safe * direction[0], r_safe * direction[1], z] 68 | ], 69 | 70 | // FIXED face generation with better error checking 71 | vertex_idx = function(layer, circ) 72 | let (idx = 2 + layer * circum_resol + (circ % circum_resol)) 73 | idx, 74 | 75 | faces = [ 76 | // Bottom cap - FIXED to eliminate z-fighting 77 | for (j = [0:circum_resol-1]) 78 | [0, vertex_idx(0, j), vertex_idx(0, (j+1) % circum_resol)], 79 | 80 | // Top cap - FIXED to eliminate z-fighting 81 | for (j = [0:circum_resol-1]) 82 | [1, vertex_idx(z_resol, (j+1) % circum_resol), vertex_idx(z_resol, j)], 83 | 84 | // Side surface - FIXED normals (correct winding for outward faces) 85 | for (i = [0:z_resol-1]) 86 | for (j = [0:circum_resol-1]) 87 | let ( 88 | v1 = vertex_idx(i, j), 89 | v2 = vertex_idx(i, (j+1) % circum_resol), 90 | v3 = vertex_idx(i+1, j), 91 | v4 = vertex_idx(i+1, (j+1) % circum_resol) 92 | ) 93 | // FIXED: Flipped winding order for proper outward normals 94 | each [[v1, v3, v2], [v2, v3, v4]] 95 | ] 96 | ) [vertices, faces]; 97 | 98 | // ============================================================================= 99 | // FAST SCREW GENERATION MODULE (replaces screw_internal) 100 | // ============================================================================= 101 | 102 | module screw_internal_fast( 103 | screwtype = "circular", 104 | r0 = 5, 105 | dr = 1.5, 106 | twist = 360*4, 107 | z_max = 12, 108 | circum_resol = defresolcirc, 109 | z_resol = defresolax, 110 | starts = 1, 111 | offsetangle = 0 112 | ) { 113 | geometry = generate_screw_geometry_fast( 114 | screwtype, r0, dr, twist, z_max, 115 | circum_resol, z_resol, starts, offsetangle 116 | ); 117 | 118 | polyhedron(points = geometry[0], faces = geometry[1], convexity = 3); 119 | } 120 | 121 | // ============================================================================= 122 | // USER-FACING API - IDENTICAL TO ORIGINAL 123 | // ============================================================================= 124 | 125 | module screwByPitch( 126 | pitch = 3, 127 | length = 12, 128 | d0 = 10, 129 | dr = 1.5, 130 | circum_resol = defresolcirc, 131 | axial_resol = defresolax, 132 | starts = 1, 133 | profile = "cubic", 134 | offsetangle = 0, 135 | flat = 1, 136 | chamfer1 = false, 137 | chamfer2 = false, 138 | widen1 = false, 139 | widen2 = false 140 | ) { 141 | r0 = d0/2; 142 | twist = length/(pitch*starts)*360; 143 | axial_resol2 = ceil(axial_resol*length/(2*r0)); 144 | 145 | intersection() { 146 | screw_internal_fast( 147 | profile, 148 | r0, dr, 149 | twist, 150 | length, 151 | circum_resol, 152 | axial_resol2, 153 | starts, 154 | offsetangle 155 | ); 156 | screwcropper(length,r0,dr,flat,chamfer1,chamfer2,circum_resol); 157 | } 158 | screwaugmenter(length,r0,dr,flat,widen1,widen2,circum_resol); 159 | } 160 | 161 | module screwByTwist( 162 | twist = 360*4, 163 | length = 12, 164 | d0 = 10, 165 | dr = 1.5, 166 | circum_resol = defresolcirc, 167 | axial_resol = defresolax, 168 | starts = 1, 169 | profile = "cubic", 170 | offsetangle = 0, 171 | flat = 1, 172 | chamfer1 = false, 173 | chamfer2 = false, 174 | widen1 = false, 175 | widen2 = false 176 | ) { 177 | r0 = d0/2; 178 | axial_resol2 = ceil(axial_resol*length/(2*r0)); 179 | 180 | intersection() { 181 | screw_internal_fast( 182 | profile, 183 | r0, dr, 184 | twist, 185 | length, 186 | circum_resol, 187 | axial_resol2, 188 | starts, 189 | offsetangle 190 | ); 191 | screwcropper(length,r0,dr,flat,chamfer1,chamfer2,circum_resol); 192 | } 193 | screwaugmenter(length,r0,dr,flat,widen1,widen2,circum_resol); 194 | } 195 | 196 | // ============================================================================= 197 | // SUPPORT MODULES (copied from original) 198 | // ============================================================================= 199 | 200 | module screwaugmenter( 201 | length, r0, dr, 202 | flat = 0.6, 203 | widen1 = false, 204 | widen2 = false, 205 | circum_resol = defresolcirc 206 | ) { 207 | if(widen1) { 208 | intersection() { 209 | translate([0,0,0]) 210 | cylinder(r1=r0,r2=r0-dr,h=dr,center=false,$fn=circum_resol); 211 | translate([0,0,dr/2]) 212 | cube([2*r0*flat,r0*2+2,dr],center=true); 213 | } 214 | } 215 | if(widen2) { 216 | intersection() { 217 | translate([0,0,length-dr]) 218 | cylinder(r2=r0,r1=r0-dr,h=dr,center=false,$fn=circum_resol); 219 | translate([0,0,length-dr/2]) 220 | cube([2*r0*flat,r0*2+2,dr],center=true); 221 | } 222 | } 223 | } 224 | 225 | module screwcropper( 226 | length, r0, dr, 227 | flat = 0.6, 228 | chamfer1 = false, 229 | chamfer2 = false, 230 | circum_resol = defresolcirc 231 | ) { 232 | intersection() { 233 | union() { 234 | if(chamfer1) { 235 | translate([0,0,0]) 236 | cylinder(r1=r0-dr,r2=r0,h=dr,center=false,$fn=circum_resol); 237 | } 238 | if(chamfer2) { 239 | translate([0,0,length-dr]) 240 | cylinder(r1=r0,r2=r0-dr,h=dr,center=false,$fn=circum_resol); 241 | } 242 | dl = 0 + (chamfer1 ? dr : 0) + (chamfer2 ? dr : 0); 243 | translate([0,0,chamfer1 ? dr : 0 ]) 244 | cylinder(r=r0+dr,h=length-dl,center=false,$fn=circum_resol); // FIXED: r0+dr instead of r0 245 | } 246 | // The flat cut - this should work with your manual intersection 247 | translate([0,0,length/2]) 248 | cube([2*r0*flat,2*(r0+dr),length+2*dr],center=true); // FIXED: wider cube 249 | } 250 | } 251 | 252 | // ============================================================================= 253 | // HOF EXTENSIONS (BONUS - New capabilities!) 254 | // ============================================================================= 255 | 256 | // For users who want to use custom profile functions directly 257 | module screwByPitch_HOF( 258 | profile_func, // Function instead of string! 259 | pitch = 3, 260 | length = 12, 261 | d0 = 10, 262 | dr = 1.5, 263 | circum_resol = defresolcirc, 264 | axial_resol = defresolax, 265 | starts = 1, 266 | offsetangle = 0, 267 | flat = 1, 268 | chamfer1 = false, 269 | chamfer2 = false, 270 | widen1 = false, 271 | widen2 = false 272 | ) { 273 | r0 = d0/2; 274 | twist = length/(pitch*starts)*360; 275 | axial_resol2 = ceil(axial_resol*length/(2*r0)); 276 | 277 | // Direct HOF geometry generation 278 | vertices = [ 279 | [0, 0, 0], [0, 0, length], 280 | for (i_z = [0:axial_resol2]) 281 | for (i_phi = [0:circum_resol-1]) 282 | let ( 283 | z = i_z * length / axial_resol2, 284 | phi_base = i_phi * 360 / circum_resol, 285 | phi_offset = (i_z) * (360/circum_resol) / 2, 286 | phi_twist = -(i_z/axial_resol2) * twist, 287 | phi_eval = starts * (phi_base + phi_offset + phi_twist) + offsetangle, 288 | direction = [cos(phi_base + phi_offset), sin(phi_base + phi_offset)], 289 | profile_value = profile_func(phi_eval), 290 | r = r0 + dr * (profile_value - 1) 291 | ) 292 | [r * direction[0], r * direction[1], z] 293 | ]; 294 | 295 | vertex_idx = function(layer, circ) 2 + layer * circum_resol + (circ % circum_resol); 296 | 297 | // FIXED HOF face generation 298 | faces = [ 299 | for (j = [0:circum_resol-1]) 300 | [0, vertex_idx(0, (j+1) % circum_resol), vertex_idx(0, j)], 301 | for (j = [0:circum_resol-1]) 302 | [1, vertex_idx(axial_resol2, j), vertex_idx(axial_resol2, (j+1) % circum_resol)], 303 | for (i = [0:axial_resol2-1]) 304 | for (j = [0:circum_resol-1]) 305 | let ( 306 | v1 = vertex_idx(i, j), 307 | v2 = vertex_idx(i, (j+1) % circum_resol), 308 | v3 = vertex_idx(i+1, j), 309 | v4 = vertex_idx(i+1, (j+1) % circum_resol) 310 | ) 311 | each [[v1, v3, v2], [v2, v3, v4]] 312 | ]; 313 | 314 | intersection() { 315 | polyhedron(vertices, faces, convexity=3); 316 | screwcropper(length,r0,dr,flat,chamfer1,chamfer2,circum_resol); 317 | } 318 | screwaugmenter(length,r0,dr,flat,widen1,widen2,circum_resol); 319 | } 320 | 321 | // ============================================================================= 322 | // USAGE EXAMPLES 323 | // ============================================================================= 324 | 325 | // IDENTICAL to old library: 326 | // screwByPitch(pitch=3, length=12, d0=10, dr=1.5, profile="cubic"); 327 | 328 | // NEW HOF capability: 329 | // my_profile = function(phi) 0.5 + 0.3*sin(phi) + 0.2*sin(3*phi); 330 | // screwByPitch_HOF(my_profile, pitch=3, length=12, d0=10, dr=1.5); 331 | 332 | // Use existing profiles as functions: 333 | // screwByPitch_HOF(profile_cubic, pitch=3, length=12, d0=10, dr=1.5); -------------------------------------------------------------------------------- /minimal_extrusion_core.scad: -------------------------------------------------------------------------------- 1 | 2 | 3 | /* 4 | Date: 2016-07-23 5 | Author: Lukas M. Süss aka mechadense 6 | Name: clean-extrusion 7 | License: LGPL / CC-BY-SA 8 | 9 | 10 | ---------------------------------------------------------------------- 11 | Problem 1: 12 | * OpenSCADs linear_extrude command leads to bad hyperbolic necking 13 | or highly stretced triangulation 14 | * making a chain of pairwise convex hulls instead works only for convex cross sections 15 | 16 | Solution to problem 1: 17 | * For every layer evaluate the function at twisted points but 18 | connect the vertices vertically 19 | (or ideally with a 30° twist -- to get near equilateral triangles) 20 | ---------------------------------------------------------------------- 21 | Problem 2: 22 | * no higher order functions 23 | ( OpenSCAD is a declarative referentially transparent language 24 | but not a functional language) 25 | this leads to : 26 | => massive inflexible parameter threading (HACK) 27 | combined with the lack of data records 28 | this leads to: 29 | => hard to maintain code 30 | 31 | The HACK solution 2: 32 | * keep massive inflexible parameter threading HACK but 33 | use a records library (also a HACK) 34 | ---------------------------------------------------------------------- 35 | 36 | 37 | This Library is a HACK that becomes necessary due 38 | to OpenSCADSs lack of higher order functions 39 | 40 | ############################### 41 | TODO: 42 | maybe make sure the resoulution aspect ratio is controlled ... ?? 43 | 44 | keep conical bevel gears as a seperate problem ... 45 | radial variations (spherical evolvents ...) 46 | 47 | further up the abstraction tree: use inverse of screw lead ?? 48 | this allows to use 49 | the value "zero" for straight profiles and 50 | "negative" values for lefthanded theads 51 | 52 | // TODO ... add an option such that a raw profile list can be supplied 53 | still relevant ?? 54 | 55 | ----- 56 | test triangulation of a nontwisted extrusion ... easy WORKS :) 57 | 58 | ################### MISC .... 59 | 60 | splitup of convenience demo 61 | module that makes it more screwlike (started) 62 | screw_lead multi_start trapezoid_parameters ??? 63 | sine profile ??? 64 | introduction of the premade profiles 65 | .... records ???? 66 | 67 | !!! Is the 2D case still preserved ?? probably not 68 | Can theis be restored - is there motivation to do so ? 69 | quite elegant for 2D case 70 | //pp = profile_data_2D(vert,"testprofile1"); // OLD .... 71 | //echo(pp=pp); 72 | //translate([0,0,-3]) polygon(points=pp[0], paths=[pp[1]]); 73 | 74 | */ 75 | 76 | //demo_of_the_problem(); 77 | module demo_of_the_problem() 78 | { 79 | nn=32; 80 | phi_indices = [for (a = [0 : nn-1]) a]; 81 | phi_angles = [for (a = phi_indices) a/nn * 360 ]; 82 | coords_2D = [ for(phi=phi_angles) (10+2*sin(2*phi)) * [cos(phi),sin(phi)] ]; 83 | //color("red") 84 | linear_extrude(height = 10, twist=360*1) 85 | polygon(points=coords_2D, paths=[phi_indices]); 86 | // translate([0,0,-5]) polygon(points=[[10,0],[0,10],[-10,0],[0,-10]], paths=[[0,1,2,3]]); 87 | //echo(coords_2D=coords_2D,[for (a = [0 : nn-1]) a]); 88 | } 89 | 90 | // ################################## 91 | // ############## TESTS 92 | // ################################## 93 | 94 | 95 | 96 | // ######################### RENDERING TESTS 97 | 98 | 99 | // ################################# 100 | // convenience function -- TODO move this in a seperate module 101 | 102 | // combos of vertexlist and indexlist for easier passing around 103 | // TO TEST 104 | // "unit_circle" 105 | 106 | // TODO move that dependency out of this module !!! 107 | use // polar_profile(phi, name, parameterlist) 108 | 109 | 110 | function shell_data_3D(resol_circ,resol_z,twist=360,z_max=10,name="testprofile1",profile_params=[]) = 111 | let 112 | ( evalparams = get_eval_params(resol_circ,resol_z,twist,z_max) 113 | , evalindices = get_eval_indices(resol_circ,resol_z,twist,z_max) 114 | , preevalpoints = [ for(i=evalparams) concat( i[0]*polar_profile(i[1],name,profile_params), i[2]) ] 115 | ) [concat(preevalpoints,[[0,0,0],[0,0,z_max]]),evalindices]; 116 | // the concatenated vertices are: 117 | // bottom center point of the screw shell vertices 118 | // top centerpoint of the screw shell vertices 119 | 120 | convenience_demo(); 121 | 122 | module convenience_demo() 123 | { 124 | vert = 64; 125 | ss = shell_data_3D(120,50,90*2.0,20,name="testprofile1",profile_params=[]); 126 | //ss = shell_data_3D(3,3,a=360,l=5,name="testprofile1",profile_params=[]); 127 | //echo(ss=ss); 128 | polyhedron(points=ss[0],faces=ss[1],convexity=3); 129 | //clockwise when looking at each face from outside inwards 130 | 131 | //ballpreview(); // why a cylinder and not my profile ?? 132 | module ballpreview() // put a low poly sphere at every vertex 133 | { 134 | for(point=ss[0]) 135 | { 136 | color("red") translate(point) sphere(r=0.2); // 0.2 137 | } 138 | } 139 | } 140 | 141 | // ########################### INDEX TESTS 142 | 143 | //indextests(); // low resolution for simple correctness checking 144 | module indextests() 145 | { 146 | //echo(bottom_closingface_indexlist(5,10)); 147 | echo("TEST bcfil", bottom_closingface_indexlist(3,3)); 148 | echo("TEST tcfil", top_closingface_indexlist(3,3)); // nothing ?! 149 | 150 | echo("TEST uptsil", up_pointing_triangle_strip_indexlist(3)); // OK 151 | echo("TEST dptsil", down_pointing_triangle_strip_indexlist(3)); // OK 152 | echo("TEST tspil", triangle_strip_indexlist(3)); // OK 153 | echo("TEST tmil", triangle_lateralsurf_indexlist(3,3)); // seems ok 154 | echo("TEST tslil", triangle_shell_indexlist(3,3)); 155 | } 156 | 157 | // ############################## VERTEX TESTS 158 | 159 | //vertextests(); 160 | module vertextests() 161 | { 162 | //echo("profile_data_3D", polar_profile_cartesian_pos_list_3D(3,0,"testprofile1",0,0,[]) ); 163 | echo("shell_data", triangle_shell_vertex_list(3,3,a=360,l=10,name="testprofile1") ); // OLD <<<<<<<<<<<<< 164 | // works :) 165 | } 166 | 167 | 168 | // MAIN CODE: 169 | 170 | // ################################################################################# 171 | // SECTION: calculation of triangulation indices (polygon face vertex lists) 172 | // ################################################################################# 173 | 174 | // #################################################### 175 | // planned index format: 176 | // resol_circ ... number of circumferencial vertices 177 | // nl == resol_z... number of vertical slices 178 | // na == resol_circ 179 | 180 | // face_0 (0na) .. (1na-1) 181 | // face_1 (1na) .. (2na-1) 182 | // face_2 (2na) .. (4na-1) 183 | // ..................... 184 | // face_(nl-1) ((nl-1)na) .. ((nl-1)na-1) 185 | 186 | // lower capping face (0na) .. (1na-1) 187 | // upper capping face ((nl-1)na) .. ((nl-1)na-1) // wrong !!! 188 | 189 | // bottom center ((nl-0)na-1)+1 190 | // top center ((nl-0)na-1)+2 191 | // #################################################### 192 | 193 | function flatten(l) = [ for (a = l) for (b = a) b ]; 194 | 195 | // caution: dont shift indices of bottom and top plate centers 196 | function shiftindices(indexshift,indextriples) = 197 | [for(indextriple=indextriples) indextriple + [indexshift,indexshift,indexshift] ]; 198 | 199 | // function to shift list needed 200 | function bottom_closingface_indexlist(resol_circ,resol_z) = 201 | let(centerindex = ((resol_z+1)*resol_circ-1)+1) 202 | [for(i=[0:resol_circ-1]) [centerindex,i,(i+1)%resol_circ]]; 203 | // assuming points ordered CCW 204 | 205 | // (resol_z+1) and (resol_z+0) works - dont know why right now ... 206 | function top_closingface_indexlist(resol_circ,resol_z) = 207 | let(centerindex =((resol_z+1)*resol_circ-1)+2) 208 | [for(i=[0:resol_circ-1]) 209 | [centerindex,(i+1)%resol_circ,i] + 210 | [0,(resol_z-0)*resol_circ,(resol_z-0)*resol_circ] 211 | ]; 212 | // partial indexshift 213 | // again assuming points ordered CCW 214 | 215 | function up_pointing_triangle_indexlist(resol_circ) = [0,resol_circ,1]; 216 | function down_pointing_triangle_indexlist(resol_circ) = [1,resol_circ,(resol_circ+1)]; 217 | 218 | // todo test this 219 | function up_pointing_triangle_strip_indexlist(na) = 220 | let 221 | ( c_loop = [for(i=[0:na-2]) [up_pointing_triangle_indexlist(na)+[i,i,i]] ] 222 | , closure = [[0,na,1] + [(na-1),(na-1),(na-1)] + [0,0,-(na)]] // -(na) wraparound !!!! 223 | ) concat(flatten(c_loop),closure); // flatten cloop needed .... 224 | 225 | function down_pointing_triangle_strip_indexlist(na) = 226 | let 227 | ( c_loop = [for(i=[0:na-2]) [down_pointing_triangle_indexlist(na)+[i,i,i]] ] 228 | , closure = [[1,na,(na+1)] + [(na-1),(na-1),(na-1)] + [-(na),0,-(na)]] // -(na) wraparound !!!! 229 | ) concat(flatten(c_loop),closure); 230 | 231 | // merging the two sawtooth strips to a band strip 232 | function triangle_strip_indexlist(resol_circ) = // no index shift needed here 233 | concat( up_pointing_triangle_strip_indexlist(resol_circ), 234 | down_pointing_triangle_strip_indexlist(resol_circ)); 235 | 236 | // stacking the strips above each other 237 | function triangle_lateralsurf_indexlist(resol_circ,resol_z) = 238 | flatten( 239 | [for(i=[0:resol_z-1]) shiftindices(i*resol_circ,triangle_strip_indexlist(resol_circ))] 240 | ); 241 | 242 | 243 | function triangle_shell_indexlist(resol_circ,resol_z) = 244 | let // the order here is completely arbitrary - it can be changed 245 | ( bottom = bottom_closingface_indexlist(resol_circ,resol_z) 246 | , top = top_closingface_indexlist(resol_circ,resol_z) 247 | , lateralsurf = triangle_lateralsurf_indexlist(resol_circ,resol_z) 248 | ) 249 | concat(bottom,top,lateralsurf); 250 | 251 | // bad naming cnvention eval_indices makes no sense triang_shell_indices 252 | function get_eval_indices(resol_circum,resol_z,twist,z_max) = 253 | triangle_shell_indexlist(resol_circum,resol_z); // cap-points are in there NOT GOOD !!! maybe ok ?? 254 | 255 | 256 | 257 | 258 | // ################################################################################# 259 | // SECTION: calculation of triangulation vertex directions and evaluation parameters 260 | // ################################################################################# 261 | 262 | // THIS IS THE IMPORTANT PART 263 | // the profile twist is decoupled from the triangulation twist (which is kept zero)! 264 | 265 | // get: 266 | // A) the direction vector for the triangulation vertex 267 | // B) the evaluation argument/parameter angle 268 | function pair_direction_angle(phi,phi_offset=0,phi_twist=0) = 269 | [ [ cos(phi+phi_offset),sin(phi+phi_offset) ] 270 | , (phi + phi_offset*1 + phi_twist)%360 ]; 271 | // the 360° warparound makes the seam for noncylic functions straight 272 | // BUG: there's seems to be an issue when twisting nonperiodic functions more than 90° -- why?? 273 | // phi_twist ... object twist - this should not twist the triangulation 274 | // phi_offset ... triangulation twist - this should not twist the object 275 | // phi_offset is used for nice symmetric isoscele triangulation instead of ugly right angled ones 276 | 277 | 278 | // get the all the direction evaluation angle pairs for one slice at a specific height 279 | function pairs_direction_angle(resol_circ,phi_offset=0,phi_twist=0) = 280 | [for(i=[0:resol_circ-1]) 281 | pair_direction_angle(360/resol_circ*i,phi_offset,phi_twist)]; 282 | 283 | // add z as a parameter 284 | function triplet_dir_ang_z(resol_circ,z,phi_offset=0,phi_twist=0) = 285 | let( diranglist = pairs_direction_angle(resol_circ,phi_offset,phi_twist) ) 286 | [ for(dirang = diranglist) [dirang[0],dirang[1],z] ]; 287 | 288 | 289 | // TODO update and elaborate in this description ... 290 | // For a nicely triangulated 3D screw profile all** points need to be prepared in advance. 291 | // (This can become quite a huge dataset) 292 | // (again this threading HACK is due to the lack of support for higher order functions) 293 | // the order of the vertices here is very imortant (see format documentation ...) 294 | 295 | // get all triang_vertex_direction evaluation_angle evaluation_height triples for the extrusion 296 | function triangle_lateralsurf_argument_list(resol_circ,resol_z,a=360,z_max=10) = 297 | flatten ( 298 | [ for(i_z=[0:resol_z-0]) 299 | let 300 | ( // phi_offset = 0 // beneficial for nonperiodic functions 301 | phi_offset = (i_z)*(360/resol_circ)/2 // (1) (il%2 is not working) -- isoscele triangulation 302 | , phi_twist = -(i_z/resol_z)*a // (2) 303 | , z = (i_z/resol_z)*z_max // resol_z vertical slices 304 | ) // (1) shift the vertices of every second layer between ... 305 | // ... the vertices of the surrounding layers 306 | // (2) turn the full angle along the full length 307 | // why is the minus sign needed for a math positive twist? 308 | triplet_dir_ang_z(resol_circ,z,phi_offset,phi_twist) 309 | ] 310 | ); 311 | 312 | // running CCW (math positive x+ to y+ axis) 313 | // NOTE: phi_offset is just used internaly to 314 | // make the triangulation symmetric isoscele triangles instead of 315 | // assymmetric right angled ones 316 | 317 | function get_eval_params(resol_circum,resol_z,twist,z_max) = 318 | triangle_lateralsurf_argument_list(resol_circum,resol_z,twist,z_max); 319 | 320 | 321 | 322 | 323 | 324 | echo("####################"); 325 | // ########################################################################## 326 | // ###################################### END OF parameter generation section 327 | // ########################################################################## 328 | 329 | 330 | // ################################################ 331 | // putting evaluation parameters and triang_shell_indices together: 332 | // merging the vertices with the indices 333 | // ################################## 334 | 335 | 336 | // shorter to use but even less comprehensible ... so usage is not recommendet 337 | function get_eval_params_and_indices(resol_circum,resol_z,twist,z_max) = 338 | [ get_eval_params(resol_circum,resol_z,twist,z_max) 339 | , get_eval_indices(resol_circum,resol_z,twist,z_max) // cap-points are in there NOT GOOD !!! 340 | ]; 341 | // evalparams...[list_of_phi_z_parameter_pairs,cap_points,faces_index_list] 342 | 343 | 344 | // #################### END 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | --------------------------------------------------------------------------------