102cfcee64
Wraps torwanbukaj/ikea-brio-others-compatible-train-tracks-generator ( https://github.com/torwanbukaj/... ) as a third generator alongside holder and the universal scad playground. Approach -------- The upstream .scad ships as "library + one active example call". Used include<> (rather than use<>) so the library's top-level globals (track_width, plug/nest dimensions, $fn = 150, etc.) are available to the modules — `use<>` does NOT propagate variables. Commented out the upstream `track_tester();` call so include<> doesn't also emit the tester geometry every time. Per-render the backend builds a tiny wrapper SCAD on the fly: include <scad/train_tracks.scad> track(length=100, end1="plug", end2="nest", cutout=true, ...); and hands it to holder.render_source(). Part types exposed (9) ---------------------- - tester Calibration block (15-60mm) - track Straight (length + chamfers + grooves + plug/nest ends) - arc Curved (radius + angle, IKEA/Brio/J'adore radii in help text) - dogbone Nest-nest connector - intersection Crossing (angle + 2 lengths + 4 ends) - switch Turnout (left/right radius+angle, straight branch, common end) - snake S-curve (raised default target_length to 200 — the upstream modules's natural curvy-span at angle=45/radius=86 is ~150 and the assert fires below that) - adapter BRIO <-> IKEA system adapter (plug + nest side) - bridge Multi-part (overview/ground/slope/pillar — value_map translates the UI labels to the int 0..3 the SCAD wants) Files ----- - scad/train_tracks.scad Downloaded upstream (57 999 bytes), only modification is the // before the top-level track_tester() call. - tracks.py PART_SCHEMAS dict, build_wrapper_scad, render() that delegates to render_source. - app.py GET /api/tracks/params, POST /api/tracks/render, GET /tracks page route. - static/tracks.html Page with part selector + dynamic param form + shared viewer markup. Reuses holder.css. - static/js/tracks-app.js Controller. Switching the part select redraws the form (each part has its own schema). Ctrl+Enter renders, Cancel uses AbortController. Nav --- Tracks link added to topbar on holder / index / scad. Smoke test ---------- All 9 parts render with default params on the Manifold backend in under 0.5s each (output sizes 440 KB - 1.8 MB).
1522 lines
57 KiB
OpenSCAD
1522 lines
57 KiB
OpenSCAD
// 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<target_length) echo("Curvy part is shorter than the target length - that's OK!");
|
|
echo(str("Line-to-line distance: ", l1+l2-track_width));
|
|
|
|
difference() {
|
|
union() {
|
|
translate([track_width, 0, 0])
|
|
mirror([1, 0, 0])
|
|
track_arc(radius = radius, angle = angle,
|
|
end1 = "none", end2 = "none",
|
|
grooves = true,
|
|
both_sides = both_sides,
|
|
cutout = cutout,
|
|
cutout_corr = cutout_corr,
|
|
part_chamfers = true);
|
|
|
|
translate([-track_width+l1+l2, h1+h2, 0])
|
|
rotate([0, 0, 180])
|
|
mirror([1, 0, 0])
|
|
|
|
// Alternative approach - discarded
|
|
//translate([-track_width+l1+l2, h1+h2, 0])
|
|
//rotate([0, 0, 180])
|
|
//mirror([0, 1, 0])
|
|
|
|
track_arc(radius = radius, angle = angle,
|
|
end1 = "none", end2 = "none",
|
|
grooves = true,
|
|
both_sides = both_sides,
|
|
cutout = cutout,
|
|
cutout_corr = cutout_corr,
|
|
part_chamfers = true);
|
|
|
|
// Lower extension (close to (0,0))
|
|
translate([track_width, -side_ext, 0])
|
|
rotate([0, 0, 90])
|
|
track(length = side_ext,
|
|
cutout = false,
|
|
part_chamfers = true,
|
|
end1_chamfers = true,
|
|
end2_chamfers = false,
|
|
grooves = true,
|
|
end1 = "none", end2 = "none",
|
|
both_sides = both_sides);
|
|
|
|
// Upper extension (away from (0,0))
|
|
translate([l1+l2, h1+h2, 0])
|
|
rotate([0, 0, 90])
|
|
track(length = side_ext,
|
|
cutout = false,
|
|
end1_chamfers = false,
|
|
end2_chamfers = true,
|
|
grooves = true,
|
|
end1 = "none", end2 = "none",
|
|
both_sides = both_sides);
|
|
} //union end
|
|
|
|
if (end1 == "nest") {
|
|
rotate([0, 0, 0])
|
|
translate([track_width/2, -side_ext, 0])
|
|
rotate([0, 0, 90])
|
|
track_nest();
|
|
}
|
|
if (end2 == "nest") {
|
|
translate([l1+l2-track_width/2, h1+h2+side_ext, 0])
|
|
rotate([0, 0, -90])
|
|
track_nest();
|
|
}
|
|
|
|
}
|
|
// Generating plugs
|
|
if (end1 == "plug") {
|
|
translate([track_width/2, -side_ext, 0])
|
|
rotate([0, 0, -90])
|
|
track_plug();
|
|
}
|
|
if (end2 == "plug") {
|
|
translate([l1+l2-track_width/2, h1+h2+side_ext, 0])
|
|
rotate([0, 0, 90])
|
|
track_plug();
|
|
}
|
|
|
|
}
|
|
|
|
// ### INTERSECTION GENERATOR MODULE
|
|
// intersection() module allows to generate various types of intersections.
|
|
// Angle between intersecting paths can be defined as well as individual
|
|
// lengths of the paths. It is also possible to freely define paths' ending
|
|
// types.
|
|
// Input parameters:
|
|
// - angle - desired angle between two intersecting paths
|
|
// - lengthA, lengthB - length of each intersecting path
|
|
// - end1A, end2A - desired ends type for the path A ("plug", "nest" or "none")
|
|
// - end1B, end2B - desired ends type for the path B ("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 intersection(angle = 90,
|
|
lengthA = 80,
|
|
lengthB = 80,
|
|
end1A = "nest",
|
|
end2A = "plug",
|
|
end1B = "nest",
|
|
end2B = "plug",
|
|
both_sides = true) {
|
|
|
|
difference() {
|
|
union() {
|
|
translate([-lengthA/2, -track_width/2, 0])
|
|
track(length = lengthA,
|
|
cutout = false,
|
|
end1 = end1A,
|
|
end2 = end2A,
|
|
part_chamfers = true,
|
|
grooves = false);
|
|
rotate([0, 0, angle-90])
|
|
translate([track_width/2, -lengthB/2, 0])
|
|
rotate([0, 0, 90])
|
|
track(length = lengthB,
|
|
cutout = false,
|
|
end1 = end1B,
|
|
end2 = end2B,
|
|
part_chamfers = true,
|
|
grooves = false);
|
|
}
|
|
// Grooves on part A
|
|
translate([-lengthA/2-cc, -track_width/2, 0])
|
|
rotate([90, 0, 90])
|
|
linear_extrude(lengthA+2*cc)
|
|
grooves(both_sides);
|
|
// Grooves on part B
|
|
rotate([0, 0, angle-90])
|
|
translate([-track_width/2,lengthB/2+cc, 0])
|
|
rotate([90, 0, 0])
|
|
linear_extrude(lengthB+2*cc)
|
|
grooves(both_sides);
|
|
}
|
|
}
|
|
|
|
// ### STRAIGHT TRACK GENERATOR MODULE
|
|
// This module generates simple, straight track part of desired lengths
|
|
// and with desired endings.
|
|
// Input parameters:
|
|
// - length - desired length
|
|
// - cutout - controls if there should be a cutout inside the straight
|
|
// track body, this is to help the part to "lose some weight"
|
|
// -- true - cutout enabled
|
|
// -- false - cutout disabled
|
|
// - end1, end2 - desired ends ("plug", "nest" or "none")
|
|
// - part_chamfers - controls if the part should have chamfers along the part:
|
|
// -- true - chamfers enabled
|
|
// -- false - chamfers disabled
|
|
// - end1_chamfers, end2_chamfers - controls if the part should have chamfers
|
|
// along the part:
|
|
// -- true - chamfers enabled
|
|
// -- false - chamfers disabled
|
|
// - grooves - enables or disables generation of grooves:
|
|
// -- true - grooves enabled
|
|
// -- false - grooves disabled
|
|
// - 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 track(length = 20,
|
|
cutout = true,
|
|
end1 = "nest",
|
|
end2 = "plug",
|
|
part_chamfers = true,
|
|
end1_chamfers = true,
|
|
end2_chamfers = true,
|
|
grooves = true,
|
|
both_sides = false) {
|
|
|
|
b = track_nest_neck_length + track_nest_radius;
|
|
cutout_start = (end1 == "nest") ? tr_co_w + b : tr_co_w;
|
|
cutout_end = (end2 == "nest") ? tr_co_w + b : tr_co_w;
|
|
|
|
difference() {
|
|
rotate([90, 0, 90])
|
|
linear_extrude(length)
|
|
track_blueprint(grooves = grooves,
|
|
part_chamfers = part_chamfers,
|
|
both_sides = both_sides);
|
|
|
|
if (end1 == "nest") {
|
|
install_nest(nest_rotate = 0, length = 0);
|
|
}
|
|
if (end2 == "nest") {
|
|
install_nest(nest_rotate = 180, length = length);
|
|
}
|
|
|
|
if (cutout) {
|
|
if (length - cutout_start - cutout_end >= 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);
|
|
}
|
|
}
|
|
} |