// Train tracks generator for IKEA Lillabo, Brio and others //// by torwan @ January 8th, 2023 // Description / Hints: // - See more details on https://www.thingiverse.com/thing:5598668 // - All dimensions in [mm]. // - Before printing any "bigger" part generated by this generator, // I highly recommend to print a tester-track relevant to the system // you posses. Use the "track_tester()" module to generate a tester. // - To print STL file, render the object using F6 and save // an STL file using F7. // - Standard lengths of the IKEA Lillabo straight tracks (without // a plug): 50mm, 100mm, 146mm, 205mm, 240mm. // - Standard arcs in the IKEA Lillabo tracks family (original tracks // measured): 45deg + inner radius 185mm which gives U-turn inner // diameter 370mm, 22.5deg + inner radius 180mm which gives U-turn // inner diameter 360mmm. // - Arc parameters used in J'adore system (sold e.g., by Rossmann): // 45deg, inner radius 86mm, => U-turn inner diameter 172mm // - Standard lengths of the Brio straight tracks (without a plug): // 145mm. // - $fn controls number of a circle fragments. I recommend to use // $fn = 300;, especially for larger arcs and bridge parts. Keep // in mind that the larger value of $fn, the longer rendering will // take, but also the smoother "roundy" parts of your tracks will // be. When tinkering around and playing with different parameters // values, I recommend using smaller $fn value unless you have rally // fast CPU/GPU PC configuration. //$fn = 300; $fn = 150; // ### General track dimensions // Track dimensions (based on original IKEA Lillabo and BRIO tracks). // You should not change these values unless you are going to tune this // generator to some really different system than the ones available // commonly on the market. track_width = 40; track_height = 12; track_well_depth = 2.9; track_well_width_top = 6.75; track_well_width_bottom = 4.75; track_well_spacing = 25.7; track_chamfer = 1.5; // ### DO NOT CHANGE BELOW DEFINITIONS UNLESS IT IS REALLY NECESSARY // ### (use the "general" variables to play with various dimensions instead) // Track Plug IKEA Lillabo (do not change) ikea_track_plug_radius = 6.25; // 6.25 ikea_track_plug_radius_ext = 6.5; // 6.5 for a dog-bone with springy plug ikea_track_plug_neck_width = 6.1; // 6.1 ikea_track_plug_neck_length = 11; // 11 ikea_track_plug_txt = "I"; // Track Nest IKEA Lillabo (do not change) ikea_track_nest_radius = 6.4; // 6.4 ikea_track_nest_neck_width = 6.3; // 6.3 ikea_track_nest_neck_length = 11; // 11 ikea_track_nest_txt = "I"; // Track Plug Brio (do not change) brio_track_plug_radius = 6.25; // 6.25 brio_track_plug_radius_ext = 6.5; // 6.5 for a dog-bone with springy plug brio_track_plug_neck_width = 6.1; // 6.1 brio_track_plug_neck_length = 12; // 12 brio_track_plug_txt = "B"; // Track Nest Brio (do not change) brio_track_nest_radius = 6.4; // 6.4 brio_track_nest_neck_width = 6.3; // 6.3 brio_track_nest_neck_length = 12; // 12 brio_track_nest_txt = "B"; // Bridge components (do not change) bridge_pillar_depth = 10; slope_straight_nest_length = 20; //length of the straight ending (with nest) slope_straight_plug_length = 10; //length of the straight ending (without plug) // Helpers (do not change) tr_wl_w_stick_out = (track_well_width_top-track_well_width_bottom)/2; tr_co_w = 0.75 * (track_well_spacing-track_well_width_top); // track cutout width cc = 0.001; switch_cut_ext = 1; // ### USE BELOW VARIABLES TO CHOOSE YOUR TARGET SYSTEM // ### Below variables are used by majority of modules in this library. // ### Feel free to define your own values here instead of using // ### pre-defined parameters for specific systems. // ### For example: track_plug_radius = 6.45; // Track Plug - choose your system here or define your own values track_plug_radius = ikea_track_plug_radius; track_plug_radius_ext = ikea_track_plug_radius_ext; track_plug_neck_width = ikea_track_plug_neck_width; track_plug_neck_length = ikea_track_plug_neck_length; track_plug_txt = ikea_track_plug_txt; // Track Nest - choose your system here or define your own values track_nest_radius = ikea_track_nest_radius; track_nest_neck_width = ikea_track_nest_neck_width; track_nest_neck_length = ikea_track_nest_neck_length; track_nest_txt = ikea_track_nest_txt; // ### TESTING / GENERATION AREA / EXAMPLES // ### Below I have provided many examples of how to call a generator module. // ### They should help to learn quickly how to generate a desired model. // ### When trying different module, always remember to comment back // ### a previously used one :) // ### If you have any doubts regarding some parameters, see detailed description // ### provided in the front of each module definition. // --------------------------------- //TRACK TESTER // NB: this top-level call was commented out by busbar-designer so the file // can be `include`d as a library without emitting any geometry. The /tracks // page wraps and calls the right module explicitly. //track_tester(); // --------------------------------- // TRACKS ADAPTER //tracks_adapter(length = 30, nest = "B", plug = "I"); //tracks_adapter(length = 30, nest = "I", plug = "B"); // --------------------------------- // STRAIGHT track examples /* track(length = 100, end1 = "plug", end2 = "nest", cutout = true); */ /* track(length = 25, cutout = true, end1 = "plug", end2 = "nest", part_chamfers = true, grooves = true); */ /* track(length = 50, cutout = true, end1 = "plug", end2 = "nest", part_chamfers = true, grooves = true); */ // --------------------------------- // ARC track examples //track_arc(); //track_arc(angle = 90, radius = 86, both_sides = true); //track_arc(angle = 45, radius = 185, both_sides = true); //track_arc(angle = 22.5, radius = 180); //track_arc(angle = -45, radius = 185, cutout = false, both_sides = false); /* track_arc(angle = -90, radius = 86, end1 = "nest", end2 = "plug", grooves = true, both_sides = true, cutout = true, cutout_corr = 0, part_chamfers = true); */ /* track_arc(angle = -45, radius = 86, end1 = "nest", end2 = "plug", grooves = true, both_sides = true, cutout = true, cutout_corr = 0, part_chamfers = true); */ /* track_arc(angle = 45, radius = 86, end1 = "nest", end2 = "nest", grooves = true, both_sides = true, cutout = true, cutout_corr = 0, part_chamfers = true); */ /* track_arc(angle = -45, radius = 185, end1 = "nest", end2 = "nest", grooves = true, both_sides = false, cutout = true, cutout_corr = 0, part_chamfers = true); */ /* track_arc(radius = 86, angle = -90, end1 = "plug", end2 = "plug", grooves = true, both_sides = true, cutout = true, cutout_corr = 15, part_chamfers = true); */ // --------------------------------- // DOGBONE //track_dogbone(); // --------------------------------- // INTERSECTION examples /* intersection(angle = 90, lengthA = 75, // 75mm is too short for Brio lengthB = 75, // 75mm is too short for Brio both_sides = true); */ /* intersection(angle = 45, lengthA = 146, lengthB = 146, end1A = "nest", end2A = "plug", end1B = "nest", end2B = "plug", both_sides = true); */ /* intersection(angle = 90, lengthA = 100, lengthB = track_width, end1A = "nest", end2A = "plug", end1B = "plug", end2B = "plug", both_sides = true); */ // --------------------------------- // BRIDGE set examples // - what_to_generate - controls what is generated by the module, // allowed values: // -- 0 (zero) - shows a bridge overview with calculation of // the bridge height // -- 1 - shows ONLY a bridge ground part // -- 2 - shows ONLY a bridge slope (upper) part // -- 3 - shows ONLY a bridge pillar part // Showcase 15 degrees, radius 100mm, 205mm straight part, 50mm pillar length: /* generate_bridge(what_to_generate = 0, bridge_angle = 15, slope_radius = 100, straight_part_l = 205, pillar_l = 50, cutout = true); */ // Showcase 20 degrees, radius 200mm, 146mm straight part, 50mm pillar length: /* generate_bridge(what_to_generate = 1, bridge_angle = 20, slope_radius = 200, straight_part_l = 146, pillar_l = 50, cutout = true); */ // Showcase 14 degrees, radius 100mm, 205mm straight part, 50mm pillar length: //generate_bridge(what_to_generate = 0); // --------------------------------- // SNAKE track examples /* snake_track(angle = 20, radius = 186, target_length = 150, end1 = "nest", end2 = "plug", cutout = false, both_sides = false, cutout_corr = 0); */ /* snake_track(angle = 35.775, radius = 86, cutout = true, cutout_corr = 2, target_length = 146, both_sides = true, end1 = "nest", end2 = "plug"); */ /* snake_track(angle = 70, radius = 80, target_length = 300, end1 = "nest", end2 = "plug", cutout = false, both_sides = false, cutout_corr = 0); */ // --------------------------------- // SWITCH track examples // If a model does not look good in the preview window, render it using F6 /* switch(angleR = 45, radiusR = 86, endR = "nest", angleL = 0, radiusL = 0, endL = "nest", lengthS = 100, endS = "nest", endCommon = "plug", both_sides = true); */ /* switch(angleR = 45, radiusR = 86, endR = "nest", angleL = 45, radiusL = 86, endL = "nest", lengthS = 0, endS = "none", endCommon = "nest", both_sides = true); */ /* switch(angleR = 45, radiusR = 86, endR = "nest", angleL = 45, radiusL = 86, endL = "plug", lengthS = 146, endS = "nest", endCommon = "nest", both_sides = true); */ /* switch(angleR = 45, radiusR = 86, endR = "plug", angleL = 45, radiusL = 86, endL = "plug", lengthS = 146, endS = "nest", endCommon = "nest", both_sides = true); */ /* switch(angleR = 45, radiusR = 86, endR = "nest", angleL = 45, radiusL = 86, endL = "nest", lengthS = 100, endS = "nest", endCommon = "nest", both_sides = true); */ /* switch(angleR = 90, radiusR = 86, endR = "plug", angleL = 45, radiusL = 86, endL = "plug", lengthS = 100, endS = "plug", endCommon = "nest", both_sides = true); */ // ########## End of testing area ########## // ### TRACK TESTER MODULE // Input parameters: // - tester_length - length of a tester without a plug module track_tester(tester_length = 25) { difference() { translate([0, 0, -track_height + track_well_depth + 0.4]) track(length = tester_length, cutout = true, end1 = "nest", end2 = "plug", part_chamfers = true, grooves = true); translate([-cc, -cc, -track_height]) cube([2*tester_length, 2*cc + track_width , track_height]); } } // ### SWITCH TRACK GENERATOR MODULE // Railway switch() module allows to generate various types of switches. // You can independently control if it has left and/or straight and/or // right leg. You can control what kind of ending each of leg has. // Input parameters: // - angleR - desired angle of the right turn // - radiusR - desired radius of the right turn, zero disables the leg // - endR - desired end type of the right leg ("plug", "nest" or "none") // - angleL, radiusL, endL - left leg parameters // - lengthS - length of the middle, straight leg // - endCommon - common end type "plug", "nest" or "none") // - both_sides - controls if the grooves should be generated only on // the top or also on the bottom: // -- true - grooves on both sides // -- false - grooves only on the top module switch(angleR = 45, radiusR = 86, endR = "nest", angleL = 45, radiusL = 86, endL = "plug", lengthS = 146, endS = "nest", endCommon = "nest", both_sides = true) { difference() { union() { // Left + Straight + Right main bodies // Arc to the left if (radiusL > 0) { track_arc(angle = angleL, radius = radiusL, part_chamfers = true, grooves = false, both_sides = false, cutout = false, end1=endCommon, end2="none"); } // Arc to the right if (radiusR > 0) { track_arc(angle = -angleR, radius = radiusR, part_chamfers = true, grooves = false, both_sides = false, cutout = false, end1=endCommon, end2="none"); } // Straight part if (lengthS > 0) { translate([track_width, 0, 0]) rotate([0, 0, 90]) track(length = lengthS, cutout = false, part_chamfers = true, grooves = false, end1 = endCommon, end2 = endS); } } //union // grooves and the nest (if requested) on the left turn if (radiusL > 0) { translate([-radiusL, 0, 0]) rotate_extrude(angle = angleL, convexity = 10) translate([radiusL, 0 , 0]) grooves(both_sides); // Nest if requested if (endL == "nest") { translate([-radiusL , 0, 0]) rotate([0, 0, angleL]) translate([radiusL + track_width/2, 0, 0]) rotate([0, 0, -90]) track_nest(); } // When the left turn is too "short", and the straight part // is requested, the right turn ends up inside the straight part. // In such a case, additional cutting is required to provide // sufficient amount of space for adjacent track. translate([-radiusL , 0, 0]) rotate([0, 0, angleL]) rotate_extrude(angle = angleL/4, convexity = 10) translate([radiusL, 0, 0]) translate([-switch_cut_ext/2, 0, 0]) square([track_width + switch_cut_ext, track_height]); } //(radiusL > 0) // grooves and the nest (if requested) on the right turn if (radiusR > 0) { translate([radiusR+track_width, 0, 0]) rotate_extrude(angle = -angleR, convexity = 10) translate([-radiusR-track_width, 0 , 0]) grooves(both_sides); // Nest if requested if (endR == "nest") { translate([radiusR+track_width, 0, 0]) rotate([0, 0, -angleR]) translate([-radiusR-track_width/2, 0, 0]) rotate([0, 0, -90]) track_nest(); } // When the right turn is too "short", and the straight part // is requested, the right turn ends up inside the straight part. // In such a case, additional cutting is required to provide // sufficient amount of space for adjacent track. translate([radiusR+track_width, 0, 0]) rotate([0, 0, -angleR]) rotate_extrude(angle = -angleR/4, convexity = 10) translate([-radiusR-track_width, 0, 0]) translate([-switch_cut_ext/2, 0, 0]) square([track_width + switch_cut_ext, track_height]); } // (radiusR > 0) // grooves on the straight part if (lengthS > 0) { translate([0, lengthS, 0]) rotate([90, 0, 0]) linear_extrude(lengthS) grooves(both_sides); } } //main difference // Generating plugs if (endL == "plug") { translate([-radiusL, 0, 0]) rotate([0, 0, angleL]) translate([radiusL + track_width/2, 0, 0]) rotate([0, 0, 90]) track_plug(); } if (endR == "plug") { translate([radiusR+track_width, 0, 0]) rotate([0, 0, -angleR]) translate([-radiusR - track_width/2, 0, 0]) rotate([0, 0, 90]) track_plug(); } } // ### ARC TRACK GENERATOR MODULE // track_arc() module allows to generate various types of arcs. // You can control angle and radius of generated arcs. // Input parameters: // - angle - desired angle of an arc: // -- negative values - arc turns right // -- positive values - arc turns left // - radius - desired radius of the arc // - end1, end2 - desired end type of the arc ("plug", "nest" or "none") // - grooves - enables or disables generation of grooves: // -- true - grooves enabled // -- false - grooves disabled // - cutout - controls if there should be a cutout inside the arc body, // this is to help the part to "lose some weight" // -- true - cutout enabled // -- false - cutout disabled // - cutout_corr - additional angle offset (in degrees) which will be // used by the generator for the cutout starting and ending angle, // in general only positive values make sense to shorten the cutout // angular length // - both_sides - controls if the grooves should be generated only on // the top or also on the bottom: // -- true - grooves on both sides // -- false - grooves only on the top // - part_chamfers - controls if the part should have chamfers: // -- true - chamfers enabled // -- false - chamfers disabled module track_arc(radius = 185, angle = 45, end1 = "plug", end2 = "nest", grooves = true, both_sides = false, cutout = true, cutout_corr = 0, //cutout start/end correction angle part_chamfers = true) { radius_corr = (angle > 0) ? radius : -radius - track_width; angle_abs = abs(angle); angle_dir = (angle >= 0) ? 1 : -1; a = radius+(track_width/2); b = track_nest_neck_length + track_nest_radius; cutout_start_angle = (end1 == "nest") ? cutout_corr + atan((tr_co_w + b) / a) : cutout_corr + atan(tr_co_w / a); cutout_end_angle = (end2 == "nest") ? cutout_corr + atan((tr_co_w + b) / a) : cutout_corr + atan(tr_co_w /a); // echo(str(cutout_start_angle)); // echo(str(cutout_end_angle)); translate([-radius_corr, 0, 0]) { difference() { rotate_extrude(angle = angle, convexity = 10) translate([radius_corr, 0 , 0]) track_blueprint(grooves, both_sides, part_chamfers); if (end1 == "nest") { rotate([0, 0, 0]) translate([radius_corr + track_width/2, 0, 0]) rotate([0, 0, 90]) track_nest(); } if (end2 == "nest") { rotate([0, 0, angle]) translate([radius_corr + track_width/2, 0, 0]) rotate([0, 0, -90]) track_nest(); } // Cutout if (cutout) { if ((angle_abs-cutout_end_angle-cutout_start_angle) > 0) { rotate([0, 0, angle_dir*cutout_start_angle]) { rotate_extrude(angle = angle-angle_dir*(cutout_start_angle+cutout_end_angle), convexity = 10) translate([radius_corr+track_width/2 - tr_co_w/2, -cc , 0]) square([tr_co_w, track_height + 2*cc]); translate([radius_corr+track_width/2, -cc , 0]) cylinder(d = tr_co_w, h = track_height + 2*cc); } rotate([0, 0, angle-angle_dir*cutout_end_angle]) { translate([radius_corr+track_width/2, -cc , 0]) cylinder(d = tr_co_w, h = track_height + 2*cc); } } //if (minimum length) } //if (cutout) } // Generating plugs if (end1 == "plug") { translate([radius_corr + track_width/2, 0, 0]) rotate([0, 0, -90]) track_plug(); } if (end2 == "plug") { rotate([0, 0, angle]) translate([radius_corr + track_width/2, 0, 0]) rotate([0, 0, 90]) track_plug(); } } } // ### SNAKE TRACK GENERATOR MODULE // Snake_track() module allows to generate various types of snake tracks. // Snake track consists of two adjacent arcs extended by two straight // tracks on both sides to reach total target length (along Y axis). // It can only shift the axis to the right, there is no option to generate // a snake track shifting to the left (TO DO in the future). Print it // with "both_sides = true" to get the grooves also on the bottom and you // can flip the part up-side-down to get the snake track shifting to the left. // Input parameters: // - angle - desired angle of a singular arc used to create the snake track, // only positive values make sense // - radius - desired radius of a singular arc the used to create a snake // track // - end1, end2 - desired end type of the snake track ("plug", "nest" or "none") // - cutout - controls if there should be cutouts inside the snake track body, // this is to help the part to "lose some weight" // -- true - cutout enabled // -- false - cutout disabled // - cutout_corr - additional angle offset (in degrees) which will be // used by the generator for the cutout starting and ending angle for // each arc part used to create the snake track, in general only positive // values make sense // - both_sides - controls if the grooves should be generated only on // the top or also on the bottom: // -- true - grooves on both sides // -- false - grooves only on the top module snake_track(angle = 45, radius = 86, cutout = true, cutout_corr = 0, target_length = 146, both_sides = true, end1 = "plug", end2 = "nest") { angle = (angle <= 90) ? angle : 90; h1 = sin(angle) * radius; h2 = sin(angle) * (radius + track_width); hf = tan(angle) * (radius + track_width); hf1 = hf - h1; hf2 = hf - h2; beta = 180 - 90 - angle; //l1 = tan(beta) * (hf - h1); //l2 = tan(beta) * (hf - h2); l1 = (angle != 90) ? tan(beta) * (hf - h1) : radius + track_width; l2 = (angle != 90) ? tan(beta) * (hf - h2) : radius + track_width; hc = h1+h2; // length of the curvy part side_ext = (target_length - hc)/2; echo(str("Length of the curvy part (along Y axis): ", hc)); assert(hc= 0) { hull() { translate([cutout_start, track_width/2, -cc]) cylinder(d = tr_co_w, h = track_height + 2*cc); translate([length - cutout_end, track_width/2, -cc]) cylinder(d = tr_co_w, h = track_height + 2*cc); } } //if (minimum length) } //if (cutout) // Corner chamfers if (end1_chamfers) { for (y = [ 0, track_width]) { translate([0, y, track_height/2]) rotate([0, 0, 45]) cube([track_chamfer, track_chamfer, track_height], center = true); } } if (end2_chamfers) { for (y = [ 0, track_width]) { translate([length, y, track_height/2]) rotate([0, 0, 45]) cube([track_chamfer, track_chamfer, track_height], center = true); } } } //main difference // Generating plugs if (end1 == "plug") { install_plug(180, 0); } if (end2 == "plug") { install_plug(0, length); } } // Helping module for track() module - installs a plug module install_plug(plug_rotate = 180, length = 0) { translate([length, track_width/2, 0]) rotate([0, 0, plug_rotate]) track_plug(); } // Helping module for track() module - installs a nest module install_nest(nest_rotate = 180, length = 0) { translate([length, track_width/2, 0]) rotate([0, 0, nest_rotate]) track_nest(); translate([length, track_width/2, track_height/2 - cc]) rotate([0, 0, 45]) cube([track_nest_neck_width, track_nest_neck_width, track_height + 2*cc], center = true); } // ### BRIDGE GENERATOR MODULE // Input parameters: // - what_to_generate - controls what is generated by the module, // allowed values: // -- 0 (zero) - shows a bridge overview with calculation of // the bridge height // -- 1 - shows ONLY a bridge ground part // -- 2 - shows ONLY a bridge slope (upper) part // -- 3 - shows ONLY a bridge pillar part // - bridge_angle - an angle used to generate "slope" parts of // the bridge components // - slope_radius - radius used when generating "slope" parts // - straight_part_l - straight track length which will be connecting // bridge ground and upper parts, this is needed in order to // calculate overall bridge height and to present an overview // - cutout - used for material saving, it cuts out the middle part // from a track body if the track path is of sufficient length: // -- true - the module will try to make a cutout // -- false - cutouts disabled // - pillar_l - a pillar length, used for generating a bridge pillar module generate_bridge(what_to_generate = 0, bridge_angle = 14, slope_radius = 100, straight_part_l = 205, cutout = true, pillar_l = 50) { // Computing all horizontal and vertical offsets y_off_bg = sin(bridge_angle)*slope_radius + cos(bridge_angle)*slope_straight_plug_length; y_off_sl = sin(bridge_angle)*slope_radius + cos(bridge_angle)*slope_straight_nest_length; h_off_bg = slope_radius*(1-cos(bridge_angle)) + sin(bridge_angle) * slope_straight_plug_length; y_off_straight = cos(bridge_angle) * straight_part_l; if (what_to_generate == 0 || what_to_generate == 1) track_bridge_ground(radius = slope_radius, angle = bridge_angle, cutout = cutout); if (what_to_generate == 0) color("LawnGreen") translate([track_width, y_off_bg, h_off_bg]) rotate([0, -bridge_angle, 90]) track(straight_part_l, cutout = cutout); if (what_to_generate == 0 || what_to_generate == 2) translate([track_width, y_off_bg + y_off_straight + y_off_sl, 0]) rotate([0, 0, 180]) track_bridge_slope(radius = slope_radius, angle = bridge_angle, cutout = cutout, slope_part_length = straight_part_l); if (what_to_generate == 0 || what_to_generate == 3) translate([0, y_off_bg + y_off_straight + y_off_sl + 100, 0]) track_bridge_straight(length = pillar_l, radius = slope_radius, angle = bridge_angle, cutout = cutout, slope_part_length = straight_part_l); // Bridge height sl_h = slope_radius - (cos(bridge_angle) * slope_radius); sl_str_h = sin(bridge_angle) * (straight_part_l + slope_straight_nest_length + slope_straight_plug_length); bridge_height = 2 * sl_h + sl_str_h; if (what_to_generate == 0) translate([0, y_off_bg + y_off_straight + y_off_sl + 50, bridge_height/6]) rotate([0, -90, 0]) color("OrangeRed") text(str(round(bridge_height), " mm"), size = bridge_height/6); echo("Bridge height (distance between ground and bottom layer of the top bridge track): ", str(bridge_height)); } // BRIDGE GROUND PART - called by generate_bridge() // Input parameters: // - angle - an angle used to generate the "slope" part of // the bridge ground part // - radius - radius used when generating the "slope" part // - cutout - used for material saving, it cuts out the middle part // from a track body if the track path is of sufficient length: // -- true - the module will try to make a cutout // -- false - cutouts disabled module track_bridge_ground(radius = 200, angle = 20, cutout = true) { sl_h = radius - (cos(angle) * radius); sl_dis = sin(angle) * radius; difference() { union() { rotate([0, 90, 0]) translate([-radius, 0, 0]) { rotate_extrude(angle = angle, convexity = 10) translate([radius, 0 , 0]) rotate([0, 0, 90]) track_blueprint(); // Low end (nest) translate([radius, 0, 0]) rotate([90, 0, -90]) track(length = slope_straight_nest_length, cutout = false, end1 = "none", end2 = "nest", part_chamfers = true, end1_chamfers = false, end2_chamfers = true, grooves = true, both_sides = false); // High end (plug) rotate([0, 0, angle]) translate([radius, slope_straight_plug_length, 0]) { rotate([90, 0, -90]) track(length = slope_straight_plug_length, cutout = false, end1 = "plug", end2 = "none", part_chamfers = true, end1_chamfers = true, end2_chamfers = false, grooves = true, both_sides = false); translate([0, -slope_straight_plug_length, track_width-track_chamfer]) rotate([0, 90, 0]) cube([track_width - 2 * track_chamfer, slope_straight_plug_length + track_plug_neck_length - track_plug_radius, 2.5]); } } } //union if (cutout) { if (true) { // TO DO: work out a rule hull() { translate([track_width/2, tr_co_w, 0]) cylinder(d = tr_co_w, h = 2*sl_h); translate([track_width/2, sl_dis - tr_co_w, 0]) cylinder(d = tr_co_w, h = 2*sl_h); } } //if (minimum length) } //if (cutout) } //difference // Pillar translate([track_chamfer, sl_dis-slope_straight_plug_length/2, 0]) { cube([track_width - 2*track_chamfer, bridge_pillar_depth, sl_h]); } } // BRIDGE UPPER SLOPE PART - called by generate_bridge() // Input parameters: // - angle - an angle used to generate the "slope" part of // the bridge upper slope part // - radius - radius used when generating the "slope" part // - cutout - used for material saving, it cuts out the middle part // from a track body if the track path is of sufficient length: // -- true - the module will try to make a cutout // -- false - cutouts disabled // - straight_part_l - straight track length which will be connecting // bridge ground and upper parts, this is needed in order to // calculate overall bridge height and to present an overview module track_bridge_slope(radius = 200, angle = 20, cutout = true, slope_part_length = 146) { sl_h = radius - (cos(angle) * radius); sl_dis = sin(angle) * radius; sl_str_h = sin(angle) * (slope_part_length + slope_straight_nest_length + slope_straight_plug_length); sl_pilr_corr_h = sin(angle) * bridge_pillar_depth; translate([0, 0, sl_str_h + 2 * sl_h]) difference() { union() { rotate([0, -90, 0]) translate([-radius, 0, -track_width]) { rotate_extrude(angle = angle, convexity = 10) translate([radius, 0 , 0]) rotate([180, 0, 90]) track_blueprint(); // High ending (nest) translate([radius, 0, track_width]) rotate([-90, 0, -90]) track(slope_straight_nest_length, cutout = false, end1 = "none", end2 = "nest", part_chamfers = true, end1_chamfers = false, end2_chamfers = true, grooves = true, both_sides = false); translate([radius, -slope_straight_nest_length-track_nest_neck_length, track_chamfer]) rotate([0, -90, 0]) cube([track_width - 2 * track_chamfer, slope_straight_nest_length + track_nest_neck_length, 2.5]); // Low ending (nest) rotate([0, 0, angle]) translate([radius, slope_straight_nest_length, track_width]) { rotate([-90, 0, -90]) track(slope_straight_nest_length, cutout = false, end1 = "nest", end2 = "none", part_chamfers = true, end1_chamfers = true, end2_chamfers = false, grooves = true, both_sides = false); translate([-2.5, -slope_straight_nest_length, -track_chamfer]) rotate([0, 90, 0]) cube([track_width - 2 * track_chamfer, slope_straight_nest_length + track_nest_neck_length, 2.5]); } } } //union if (cutout) { if (true) { //length condition to be added hull() { translate([track_width/2, 1.5 * tr_co_w, -sl_h]) cylinder(d = tr_co_w, h = 2*sl_h); translate([track_width/2, sl_dis - 1.5 * tr_co_w, -sl_h]) cylinder(d = tr_co_w, h = 2*sl_h); } } //if (minimum length) } //if (cutout) } //difference // Higher pillar translate([track_chamfer, -slope_straight_nest_length, 0]) { cube([track_width - 2*track_chamfer, bridge_pillar_depth, 2 * sl_h + sl_str_h]); } // Lower pillar translate([track_chamfer, sl_dis-bridge_pillar_depth, 0]) { cube([track_width - 2*track_chamfer, bridge_pillar_depth, sl_h + sl_str_h+sl_pilr_corr_h]); } // Pillars connecting bar translate([track_chamfer, -slope_straight_nest_length, 0]) cube([track_width - 2*track_chamfer, sl_dis+slope_straight_nest_length, 3]); /* // Bridge height echo("Bridge height (distance between ground and bottom layer of the top bridge track): ", str(2 * sl_h + sl_str_h)); */ } // BRIDGE PILLAR - called by generate_bridge() // Input parameters: // - length - length of the straight pillar track // - angle - an angle used to generate the "slope" parts of // the bridge components // - radius - radius used for generation of the "slope" bridge parts // - cutout - used for material saving, it cuts out the middle part // from a track body if the track path is of sufficient length: // -- true - the module will try to make a cutout // -- false - cutouts disabled // - straight_part_l - straight track length which will be connecting // bridge ground and upper parts, this is needed in order to // calculate overall bridge height and to present an overview module track_bridge_straight(length = 50, radius = 200, angle = 20, cutout = true, slope_part_length = 146) { sl_h = radius - (cos(angle) * radius); sl_dis = sin(angle) * radius; sl_str_h = sin(angle) * (slope_part_length + slope_straight_nest_length + slope_straight_plug_length); sl_pilr_corr_h = sin(angle) * bridge_pillar_depth; translate([0, 0, sl_str_h + 2 * sl_h]) rotate([0, 0, 90]) translate([0, -track_width, 0]) { // Straight track part track(length = length, cutout = cutout); // Nest side translate([track_nest_radius + track_plug_neck_length + track_chamfer, track_chamfer, -2.5]) rotate([0, 0, 90]) cube([track_width - 2 * track_chamfer, 2* track_nest_radius + track_nest_neck_length + track_chamfer, 2.5]); // Plug side translate([length + track_plug_neck_length-track_plug_radius, track_chamfer, -2.5]) rotate([0, 0, 90]) cube([track_width - 2 * track_chamfer, bridge_pillar_depth + track_plug_neck_length - track_plug_radius, 2.5]); } // First pillar translate([track_chamfer, -track_nest_radius, 0]) { cube([track_width - 2*track_chamfer, bridge_pillar_depth, 2 * sl_h + sl_str_h]); } // Second pillar translate([track_chamfer, length - bridge_pillar_depth, 0]) { cube([track_width - 2*track_chamfer, bridge_pillar_depth, 2 * sl_h + sl_str_h]); } // Pillars connecting bar translate([track_chamfer, -track_nest_radius, 0]) cube([track_width - 2*track_chamfer, length + track_nest_radius, 3]); } // ### DOG-BONE GENERATOR MODULE // This module generates a special part which connects two adjacent // nests. The part is named after its shape. // A dog-bone consists of two connected plugs. The plugs have // a little bit oversized heads (when compared to standard plugs) and // they usually give snug fit. You will need to use some force to // disconnect two parts (nests) connected with use of a dog-bone part. // Input parameters: // - track_plug_radius_ext - radius of a desired oversized plug head module track_dogbone(radius = track_plug_radius_ext) { cut = 1.25; shortenby = 0.4; offset = track_plug_neck_width/2 + cut/2; difference() { union() { translate([-shortenby, 0, 0]) track_plug(radius); translate([shortenby, 0, 0]) rotate([0, 0, 180]) track_plug(radius); } for(i=[offset, -offset]) { hull() { translate([track_plug_neck_length+radius/3, i, -cc])cylinder(d=cut, h = track_height+2*cc); translate([-track_plug_neck_length-+radius/3, i, -cc])cylinder(d=cut, h = track_height+2*cc); } } } } // Module track_plug() is used by multiple other modules for generating // a plug at a desired track ending module track_plug(radius = track_plug_radius, neck_w = track_plug_neck_width, neck_l = track_plug_neck_length){ translate([neck_l, 0, 0]) rotate([0, 0, 180]) { rotate_extrude(angle = 360) { polygon([[0, 0], [radius - track_chamfer/2, 0], [radius, track_chamfer/2], [radius, track_height - track_chamfer/2], [radius - track_chamfer/2, track_height], [0, track_height]]); } translate([0, -neck_w/2, 0]) cube([neck_l, neck_w, track_height]); } } // Module track_nest() is used by multiple other modules for generating // a nest at a desired track ending module track_nest(radius = track_nest_radius, neck_w = track_nest_neck_width, neck_l = track_nest_neck_length) { translate([neck_l-cc, 0, 0]) rotate([0, 0, 180]) { rotate_extrude(angle = 360) { #polygon([[0, -cc], [radius + track_chamfer/2, -cc], [radius, track_chamfer/2], [radius, track_height - track_chamfer/2], [radius + track_chamfer/2, track_height + 2*cc], [0, track_height + 2* cc]]); } translate([0, -neck_w/2, -cc]) difference() { cube([neck_l, neck_w, track_height + 2*cc]); translate([0, 0, 0]) { } } } } // Track shape blueprint module track_blueprint(grooves = true, both_sides = false, part_chamfers = true) { difference() { square([track_width, track_height]); // Corner champers if (part_chamfers) { part_chamfers(); } // Rail grooves if (grooves) if (both_sides) grooves(true); else grooves(false); } //difference } // Helping module to add chamfers to the track blueprint module part_chamfers() { for (x = [ 0, track_width]) { for (y = [ 0, track_height]) { translate([x, y, 0]) rotate([0, 0, 45]) square(track_chamfer, center = true); } } } // Helping module for chamfer shape module chamfer() { rotate([0, 0, 45]) square(track_chamfer, center = true); } // Helping module used for generation of track grooves module grooves(both_sides = false) { for (x = [-track_well_spacing/2, track_well_spacing/2]) { translate([track_width/2+x, track_height-track_well_depth+cc, 0]) rail_well(); if (both_sides) { translate([track_width/2+x, track_well_depth-cc, 0]) rotate([0, 0, 180]) rail_well(); } } } // Helping function which defines a grove shape module rail_well() { translate([-track_well_width_bottom/2, 0, 0]) polygon([[0, 0], [track_well_width_bottom, 0], [track_well_width_bottom+tr_wl_w_stick_out, track_well_depth], [-tr_wl_w_stick_out, track_well_depth]]); } // ### ADAPTER GENERATOR MODULE // This module generates an adapter between two different track systems. // It is possible to generate adapters of various length between IKEA // and BRIO or between one of them and another systems which dimensions // are defined in the general dimension variables at the beginning // of this file. // Input parameters: // - length - desired length of an adapter // - nest - nest system, "B" for BRIO, "I" for IKEA, else for a system // defined in the general dimension variables (track_plug_xxxxxxxx) // - plug - plug system, "B" for BRIO, "I" for IKEA, else for a system // defined in the general dimension variables (track_nest_xxxxxxxx) module tracks_adapter(length = 30, nest = "B", plug = "I") { txt_size_p = 9; txt_size_n = 8; a_nest_radius = (nest == "B") ? brio_track_nest_radius : (nest == "I") ? ikea_track_nest_radius : track_nest_radius; a_nest_neck_w = (nest == "B") ? brio_track_nest_neck_width : (nest == "I") ? ikea_track_nest_neck_width : track_nest_neck_width; a_nest_neck_l = (nest == "B") ? brio_track_nest_neck_length : (nest == "I") ? ikea_track_nest_neck_length : track_nest_neck_length; a_nest_letter = (nest == "B") ? brio_track_nest_txt : (nest == "I") ? ikea_track_nest_txt : track_nest_txt; a_plug_radius = (plug == "B") ? brio_track_plug_radius : (plug == "I") ? ikea_track_plug_radius : track_plug_radius; a_plug_neck_w = (plug == "B") ? brio_track_plug_neck_width : (plug == "I") ? ikea_track_plug_neck_width : track_plug_neck_width; a_plug_neck_l = (plug == "B") ? brio_track_plug_neck_length : (plug == "I") ? ikea_track_plug_neck_length : track_plug_neck_length; a_plug_letter = (plug == "B") ? brio_track_plug_txt : (plug == "I") ? ikea_track_plug_txt : track_plug_txt; difference() { rotate([90, 0, 90]) linear_extrude(length) track_blueprint(grooves = true, part_chamfers = true, both_sides = false); // Generating Nest translate([0, track_width/2, 0]) track_nest(radius = a_nest_radius, neck_w = a_nest_neck_w, neck_l = a_nest_neck_l); translate([0, track_width/2, track_height/2 - cc]) rotate([0, 0, 45]) cube([a_nest_neck_w, a_nest_neck_w, track_height + 2*cc], center = true); // Corner chamfers for (y = [ 0, track_width]) { translate([0, y, track_height/2]) rotate([0, 0, 45]) cube([track_chamfer, track_chamfer, track_height], center = true); } for (y = [ 0, track_width]) { translate([length, y, track_height/2]) rotate([0, 0, 45]) cube([track_chamfer, track_chamfer, track_height], center = true); } // Nest letter translate([a_nest_neck_l+a_nest_radius+2, track_width/2-a_nest_neck_w/2-txt_size_n/1.7, track_height-1.4]) { linear_extrude(1.5) text(a_nest_letter, font = "Courier New:style=Bold", size = txt_size_n); } } //main difference // Generating Plug difference() { translate([length, track_width/2, 0]) track_plug(radius = a_plug_radius, neck_w = a_plug_neck_w, neck_l = a_plug_neck_l); // Plug letter translate([length + a_plug_neck_l-txt_size_p/2+1, track_width/2-txt_size_p/2+0.75, track_height-1.4]) { linear_extrude(1.5) text(a_plug_letter, font = "Courier New:style=Bold", size = txt_size_p); } } }