529 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			OpenSCAD
		
	
	
	
	
	
			
		
		
	
	
			529 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			OpenSCAD
		
	
	
	
	
	
| 
 | |
| include <BOSL2/std.scad>;
 | |
| include <BOSL2/beziers.scad>;
 | |
| include <BOSL2/rounding.scad>;
 | |
| include <BOSL2/skin.scad>;
 | |
| include <BOSL2/threading.scad>;
 | |
| 
 | |
| include <DotScad/src/bend.scad>;
 | |
| include <DotScad/src/bend_extrude.scad>;
 | |
| include <DotScad/src/ellipse_extrude.scad>;
 | |
| include <DotScad/src/loft.scad>;
 | |
| 
 | |
| /************************************************/
 | |
| /*  Parameters                                  */
 | |
| /************************************************/
 | |
| 
 | |
| /* [Fin Specs] */
 | |
| 
 | |
| fin_height       = 51; // 10 inches in mm
 | |
| fin_width        = 120; // Width at the base in mm
 | |
| fin_top_withdraw = 10;
 | |
| fin_back_withraw = 20;
 | |
| 
 | |
| fin_start_angle  = 60; // Angle [Point 0 ]
 | |
| fin_sweep        = 25; // Sweep Angle [Point 1] 
 | |
| 
 | |
| fin_edge_angle  = 30;
 | |
| fin_edge_strength  = 30;
 | |
| 
 | |
| fin_counter_angle = 30;     // counter angle [Point 2] 
 | |
| fin_counter_strength = 10;  // Length [Point 2] 
 | |
| 
 | |
| // Length at the base in mm
 | |
| fin_base  = 114; 
 | |
| 
 | |
| 
 | |
| fin_width_tip   = 5; // Width at the tip in mm
 | |
| fin_thickness   = 8; // Thickness of the fin in mm
 | |
| 
 | |
| fin_end_angle   = 110;
 | |
| fin_end_strength   = 30;
 | |
| 
 | |
| // Back
 | |
| fin_back_height = 30; // Percent height
 | |
| fin_back_widthdraw = 20; // Percent height
 | |
| fin_back_angle = 20; // back angle
 | |
| fin_back_strength=30;
 | |
| 
 | |
| /* [Base Specs] */
 | |
| 
 | |
| base_tickness=8; 
 | |
| base_extra_thickness=20;
 | |
| 
 | |
| /* [Debugging] */
 | |
| 
 | |
| // Showing all layers
 | |
| show_debug_layers = false;
 | |
| 
 | |
| // Draw master profile
 | |
| draw_profile = true;
 | |
| 
 | |
| // Draw Fin
 | |
| build_fin = false;
 | |
| 
 | |
| // Build mold
 | |
| build_mold = true;
 | |
| 
 | |
| /* [Mold] */
 | |
| 
 | |
| // Mold base height
 | |
| mold_base_height = 10;
 | |
| mold_extra_width = 15;
 | |
| 
 | |
| mold_piston_height = 10;
 | |
| 
 | |
| resin_escape_diameter=4; 
 | |
| 
 | |
| // Diameter for screw holes in mm
 | |
| merge_holes_diameter = 6;
 | |
| piston_depth = 5;
 | |
| 
 | |
| /* [Rendering] */
 | |
| 
 | |
| // Rendering parts
 | |
| parts = "all";      // [all, top, bottom]
 | |
| 
 | |
| mold_part= "all";   // [all, top, bottom]
 | |
| // Scaling
 | |
| scale_factor = 1.0; // [0.1:0.1:2] 
 | |
| 
 | |
| // Rendering mold
 | |
| //render_mold = true;
 | |
| 
 | |
| 
 | |
| $fn=64;
 | |
| 
 | |
| /*************/
 | |
| /* Constants */
 | |
| /*************/
 | |
| 
 | |
| 
 | |
| // Define the offset value as a constant
 | |
| OFFSET = 0.01;
 | |
| 
 | |
| /************************************************/
 | |
| /* Derived variable */
 | |
| 
 | |
| mold_width  = fin_height +2 * mold_extra_width;
 | |
| mold_length = fin_width  +2 * mold_extra_width;
 | |
| mold_height = mold_base_height;
 | |
| 
 | |
| drilling_length = mold_base_height+mold_piston_height+2*OFFSET;
 | |
| 
 | |
| 
 | |
| pt1_x = adj_ang_to_opp(fin_height,fin_sweep)+fin_base/2;
 | |
| pt1_y = fin_height;
 | |
| 
 | |
| //back_point = [fin_width-7,fin_height/2];
 | |
| tail_point = [fin_width-3,fin_height/5];
 | |
| 
 | |
| edge_point = [fin_width-fin_top_withdraw,fin_height-2];
 | |
| counter_edge_point = [fin_width-6,fin_height-8];
 | |
| 
 | |
| 
 | |
| 
 | |
| /******************/
 | |
| /* Profile points */
 | |
| /******************/
 | |
|  profile_points = [
 | |
|     // *****************
 | |
|     //   INITIAL POINT
 | |
|     // *****************    
 | |
|     [0,0],                                                          // Point 0 
 | |
|     [opp_ang_to_adj(fin_height/3,fin_start_angle),fin_height/3],    // Handle 0
 | |
|     // *****************
 | |
|     //   TOP POINT
 | |
|     // *****************    
 | |
|     [pt1_x-fin_base/5,pt1_y],                                       // Handle 1 (Start)    
 | |
|     [pt1_x,pt1_y],                                                  // Point 1
 | |
|     [pt1_x+fin_base/7,pt1_y],                                       // Handle 1 (End)
 | |
|     // *****************
 | |
|     //   EDGE POINT
 | |
|     // *****************
 | |
|     handle(edge_point,140 ,4),
 | |
|     edge_point,                                                     // Point Edge Point
 | |
|     handle(edge_point,-45,4),
 | |
|     // *****************
 | |
|     //   COUNTER EDGE POINT
 | |
|     // *****************
 | |
|     handle(counter_edge_point,120,4),
 | |
|     counter_edge_point,                                             // Point Counter Edge
 | |
|     handle(counter_edge_point,-110,13),    
 | |
|     // *****************
 | |
|     //   TAIL Point
 | |
|     // *****************
 | |
|     handle(tail_point,115 ,10),
 | |
|     tail_point,
 | |
|     handle(tail_point,0,0),    
 | |
|     // *****************
 | |
|     //   END POINT
 | |
|     // *****************
 | |
|     handle([fin_base,0],60,3),
 | |
|     [fin_base,0]                                                    // End point
 | |
| ];
 | |
| 
 | |
| profile_path  = translate_path( asCurve(profile_points),-fin_width/2 );
 | |
|                   
 | |
|                   
 | |
|                   
 | |
| function asCurve(points) = bezier_curve(points/*,N=3*/,splinesteps=128);
 | |
| function handle(point,angle,strength) = [
 | |
|     //point[0] + adj_ang_to_opp(strength,angle)   /** xAngleFactor(angle)*/,
 | |
|     //point[1] + /*(angle>0 ? strength :-strength)*/ strength  * yAngleFactor(angle),
 | |
|     point[0] + polar_to_xy(strength,angle)[0]   ,
 | |
|     point[1] + polar_to_xy(strength,angle)[1]   
 | |
|     ]; 
 | |
| 
 | |
| function xAngleFactor(angle) = angle > 0 && angle < 180 ? 1 : -1;
 | |
| function yAngleFactor(angle) = angle > -90 && angle < 90 ? 1 : -1;
 | |
| 
 | |
| //function counterTop(x,withdraw,height)                 = [x-withdraw, height ];
 | |
| function handleStart(x,withdraw,angle,strength) = [x+adj_ang_to_opp(strength,angle), x-withdraw +strength];
 | |
| function handleEnd(x,withdraw,angle,strength)   = [x-adj_ang_to_opp(strength,angle), x-withdraw -strength];
 | |
| function endHandle(angle)= 
 | |
|     angle < 90 ? 
 | |
|         [fin_base + adj_ang_to_opp(fin_end_strength,90-angle),fin_end_strength] 
 | |
|         : 
 | |
|         [fin_base - adj_ang_to_opp(fin_end_strength,angle-90),fin_end_strength] 
 | |
|         ;
 | |
| 
 | |
| //function backPoint() = [fin_base-(fin_back_widthdraw/100*fin_base),fin_back_height/100*fin_height];
 | |
| 
 | |
| function decrease_y(points, percentage) = [ for (p = points) [p[0], p[1] * (1 - percentage/100)]];
 | |
| 
 | |
| function pathProfile(points) = bezpath_curve(points,N=3);
 | |
| 
 | |
| function remove_last(arr) = select(arr, 0, len(arr) - 2);
 | |
| 
 | |
| 
 | |
| // Surf Fin profile
 | |
| fin_profile         = pathProfile( profile_points );
 | |
| //vertical_profile    = pathProfile(vertical_shape);
 | |
| 
 | |
| // ****************
 | |
| // *  Slicing     *
 | |
| // ****************
 | |
| /*
 | |
| layer_0 = pathProcess(addBase(fin_profile));                     // Master layer with base
 | |
| layer_1 = contract(layer_0,-5);
 | |
| layer_2 = contract(layer_1,-4);
 | |
| layer_3 = contract(layer_2,-4);
 | |
| layer_4 = contract(layer_3,-2);
 | |
| 
 | |
| //echo ("layer_2",layer_2);
 | |
| //echo ("layer_3",layer_3);
 | |
| 
 | |
| 
 | |
| //layers = [layer_0,layer_1,layer_2,layer_3,layer_4];
 | |
| layers = [layer_0,layer_1,layer_2,layer_3];
 | |
| */
 | |
| 
 | |
| 
 | |
| 
 | |
| function pathProcess2(path) = subdivide_path(path,100);
 | |
| function pathProcess(path) = path;
 | |
| function expandPath(path,delta) = 
 | |
|     pathProcess(
 | |
|         offset(
 | |
|             deduplicate(path),
 | |
|             delta=delta,
 | |
|             chamfer=false,
 | |
|             same_length=true
 | |
|        )
 | |
|     );
 | |
| 
 | |
| // *****************
 | |
| // *  Fin Drawing  *
 | |
| // *****************
 | |
| if (build_fin) {
 | |
|     scale([scale_factor, scale_factor, scale_factor]) 
 | |
|         buildFin();
 | |
| }
 | |
| // *****************
 | |
| // *  Mold         *
 | |
| // *****************
 | |
| /*
 | |
| if (build_mold) {
 | |
|     back(mold_width/2-mold_extra_width-5)
 | |
|         // Bottom mold
 | |
| 
 | |
|         diff() cuboid( [ mold_length, mold_width, mold_height ],anchor=TOP ) {
 | |
|             // Bottom Insert
 | |
|             align(TOP,overlap=-OFFSET,inside=true) bottomInsert(); 
 | |
|                                 
 | |
|             // Drills
 | |
|             align(BOTTOM,[FRONT+LEFT,FRONT+RIGHT,BACK+LEFT,BACK+RIGHT],inset=10,overlap=-OFFSET)
 | |
|                 color("orange") tag("remove") drilling();
 | |
|                                 
 | |
|             // Top Mold
 | |
|             
 | |
|             if (mold_part != "bottom") align(TOP) 
 | |
|                 //color("Goldenrod")
 | |
|                 //color([255, 215, 0,0.1])
 | |
|                     up(OFFSET*3)
 | |
|                     diff() {
 | |
|                         color("Orange") cuboid([ mold_length, mold_width, mold_piston_height ]);
 | |
|                         position(TOP) down(OFFSET) tag("remove") resin_escape();
 | |
|                     }    
 | |
|             }
 | |
|         ;
 | |
|      
 | |
| }
 | |
| */
 | |
| 
 | |
| if (build_mold) {
 | |
|     back(mold_width/2-mold_extra_width-5)
 | |
|         // Bottom mold
 | |
| 
 | |
|         difference() { 
 | |
|             bottomCase();
 | |
|              
 | |
|             // Bottom Insert
 | |
|           //align(TOP,overlap=-OFFSET,inside=true) 
 | |
|             //up(piston_depth/2-OFFSET) 
 | |
|             down(piston_depth-OFFSET)
 | |
|                 fwd(20)
 | |
|                 bottomInsert(); 
 | |
|                                 
 | |
|             // Drills
 | |
|             //align(BOTTOM,[FRONT+LEFT,FRONT+RIGHT,BACK+LEFT,BACK+RIGHT],inset=10,overlap=-OFFSET)
 | |
|             //    color("orange") 
 | |
|                     //tag("remove") 
 | |
|             //            drilling();
 | |
|                                 
 | |
|             // Top Mold
 | |
|             
 | |
|             /*
 | |
|             if (mold_part != "bottom") align(TOP) 
 | |
|                 //color("Goldenrod")
 | |
|                 //color([255, 215, 0,0.1])
 | |
|                     up(OFFSET*3)
 | |
|                     diff() {
 | |
|                         color("Orange") cuboid([ mold_length, mold_width, mold_piston_height ]);
 | |
|                         position(TOP) down(OFFSET) tag("remove") resin_escape();
 | |
|                     }    
 | |
|              */       
 | |
|         }
 | |
| }
 | |
| 
 | |
| 
 | |
| //up(50)  bottomInsert();
 | |
| 
 | |
| module bottomCase() {
 | |
|     diff() cuboid( [ mold_length, mold_width, mold_height ],anchor=TOP )
 | |
|         // Drills
 | |
|         align(BOTTOM,[FRONT+LEFT,FRONT+RIGHT,BACK+LEFT,BACK+RIGHT],inset=10,overlap=-OFFSET)
 | |
|             //    color("orange") 
 | |
|                     tag("remove") 
 | |
|                         drilling();
 | |
| }
 | |
| 
 | |
| 
 | |
| // *****************
 | |
| // * Bottom insert *
 | |
| // *****************
 | |
| module bottomInsert() {
 | |
|     debug=true;
 | |
|     layer_profile       = pathProcess(profile_path); 
 | |
|     skin( [ layer_profile,expandPath(layer_profile,3) ], z=[0,piston_depth], slices=0 )
 | |
|         //up(debug ? 2 : 0)
 | |
|        up(OFFSET) 
 | |
|         buildFinSide(true)
 | |
|         ;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Build fin side
 | |
|  * 
 | |
|  * @param flip - Define it it is top or bottom fin side
 | |
|  */
 | |
| module buildFinSide(flip=false) {
 | |
|     mirror([0,0,flip ? 1 : 0])
 | |
|         color(flip ? "Gray" : "LightGray") /*up(fin_thickness/4)*/ /*down(fin_thickness) */
 | |
|         ellipse_extrude( fin_thickness/2) 
 | |
|             polygon(profile_path);
 | |
| } 
 | |
| 
 | |
| module resin_escape() {
 | |
|     color ("Blue") cylinder(h=drilling_length, r=resin_escape_diameter/2);
 | |
| }
 | |
| 
 | |
| // *****************
 | |
| // *  Drilling     *
 | |
| // *****************
 | |
| module drilling(){
 | |
| 
 | |
| //length = mold_base_height+mold_piston_height+10;
 | |
| 
 | |
| /*fwd(20)*/ color("Red") /*up(mold_piston_height+5)*/ up(-OFFSET)
 | |
| orient(DOWN)
 | |
|     threaded_rod(
 | |
|         d=merge_holes_diameter, 
 | |
|         l=drilling_length, 
 | |
|         pitch=1.5, 
 | |
|         anchor=BOTTOM,
 | |
|         //orient=BOTTOM
 | |
| 
 | |
|     );
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| //drilling();
 | |
| 
 | |
| module buildFin() {
 | |
|     if (false)
 | |
|     union(){
 | |
|         difference(){
 | |
|             union() {
 | |
|                 if (parts !=  "bottom") 
 | |
|                     //buildFinSide([layer_0,layer_1,layer_2],fin_thickness/2);
 | |
|                     buildFinSide();
 | |
|                 if (parts !=  "top")
 | |
|                     buildFinSide(true);
 | |
|                     //zflip() 
 | |
|                     //    buildFinSide([layer_0,layer_1,layer_2],fin_thickness/2);    
 | |
|             }
 | |
|             color("Red") cube([fin_base+20,base_extra_thickness+20,base_tickness+20],anchor=LEFT+BACK);
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| 
 | |
| module subtracted(anchor) {
 | |
|     color("Red") fwd(40) left(40) cube([fin_width+100,fin_height+100,20],anchor=anchor);
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| echo ("layerHeights(3,thickness):",layerHeights(3,base_tickness/2));
 | |
| 
 | |
| if (show_debug_layers) {
 | |
|     left(130) {
 | |
|         showDebugPath(layer_0);
 | |
|         showDebugPath(layer_1);
 | |
|         showDebugPath(layer_2);
 | |
|         
 | |
|         assert(is_path(layer_3),"Layer 3 is not a path");
 | |
|         showDebugPath(layer_3);
 | |
|         //showDebugPath(layer_4);        
 | |
|     }
 | |
|     left(230) 
 | |
|         showDebugPath(vertical_profile);
 | |
|     //showDebugPath(layer_3);
 | |
|     
 | |
| }
 | |
| if (draw_profile) {
 | |
|     // Draw fin profile
 | |
|     drawProfile( profile_points, true  );
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|  * Displays debug information for a path by visualizing its self-crossings.
 | |
|  * 
 | |
|  * @param path - The input path to analyze for self-crossings.
 | |
|  * 
 | |
|  * This module:
 | |
|  * - Splits the path at points where it intersects itself.
 | |
|  * - Renders each segment of the split path with different colors for easy identification.
 | |
|  */
 | |
| module showDebugPath(path) {
 | |
|     assert(is_path(path),"Path to show is not a path");
 | |
|     assert(is_path_simple(path),"Path is not simple");
 | |
|     rainbow(split_path_at_self_crossings(path)) 
 | |
|         stroke($item, closed=false, width=0.2);
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|  * Draws a profile based on Bezier path points with optional debug visualization.
 | |
|  * 
 | |
|  * @param points - Array of points defining the Bezier path.
 | |
|  * @param debug - Boolean flag to enable/disable debug visualization. Defaults to true.
 | |
|  * 
 | |
|  * This module:
 | |
|  * - Calculates the closest point on the Bezier path to a fixed point.
 | |
|  * - Draws the Bezier path with debug information if debug is true.
 | |
|  * - Optionally shows spheres at specific points for debugging (currently commented out).
 | |
|  */
 | |
| module drawProfile( points,debug = true ){
 | |
|     pt = [100,0];
 | |
|     pos = bezpath_closest_point(points, pt);
 | |
|     xy = bezpath_points(points,pos[0],pos[1]);
 | |
|     debug_bezier(points, N=3,width=0.2);
 | |
|     //color("red") translate(pt) sphere(r=6);
 | |
|     //color("blue") translate(xy) sphere(r=6);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Adds a base to the given path by extending it with additional points.
 | |
|  * 
 | |
|  * @param path - The original path to which the base will be added.
 | |
|  * @return A new path with an added base.
 | |
|  * 
 | |
|  * This function concatenates:
 | |
|  * - The original path (`pathProfile`).
 | |
|  * - A point `fin_base` which might represent the start or end of the base.
 | |
|  * - Points for additional thickness (`-base_extra_thickness`) at both ends of the base.
 | |
|  */
 | |
| function addBase(path) = concat(path,[[fin_base,-base_extra_thickness],[0,-base_extra_thickness]]);
 | |
| 
 | |
| 
 | |
| /**
 | |
|  * Calculates heights for n layers where the first layer starts at 0 and the last at fin_thickness.
 | |
|  * 
 | |
|  * @param n - Number of layers.
 | |
|  * @param fin_thickness - The total height to be divided.
 | |
|  * @return An array where each element represents the height of the top of each layer.
 | |
|  */
 | |
| function layerHeights(n, fin_thickness) = 
 | |
|     let(
 | |
|         layer_height = fin_thickness / (n - 1)
 | |
|     )
 | |
|     [ for (i = [0 : n-1]) i * layer_height ];
 | |
|     
 | |
| 
 | |
| /**
 | |
|  * Function to translate a path manually along X and Y
 | |
|  */ 
 | |
| function translate_path(path, dx=0, dy=0) = 
 | |
|     [for (p = path) [p[0] + dx, p[1] + dy]];    
 | |
|     
 | |
|     
 | |
|     
 | |
| // Function to print points of a path
 | |
| module print_path_points(path) {
 | |
|     for (i = [0:len(path)-1]) {
 | |
|         echo(str("\t Point ", i, ": [", path[i][0], ", ", path[i][1], "]"));
 | |
|     }
 | |
| }
 | |
|     
 | |
| echo ("**********************");
 | |
| echo ("*  Configuration     *");
 | |
| echo ("**********************");
 | |
| //echo ("Layers count"        ,len(layers));
 | |
| echo ("Base thickness"      ,str(base_tickness," mm"));
 | |
| //echo ("Layers heights"      ,layerHeights(len(layers),fin_thickness/2));    
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 |