OpenSCAD

From Openbike
Jump to navigation Jump to search


OpenSCAD.png

Sample code for designing python:

1. download Bezier example used for 3D contour seat (maths.scad, Render.scad)

2. Copy code below to your own <filename>.scad; the common modules can be kept in a separate file for reuse with new models, e.g. include <Renderer.scad> // disk pivot design from http://hpv.com.ua/2012/04/twist-python // Fixed version - bugs corrected from https://en.openbike.org/wiki/OpenSCAD

rider = true;

s = 1; // inch, 2.54 cm

pi = 3.14159265;

crank = 5.5 * s; // 140mm ring = 8 * s; tire = 1.5 * s; hub = 3 * s;

// Proportions bodyHeight = 10; armLength = 8; hand = [0.8, 0.6, 1.7]; // orientation

explode = 1; // 1 is normal, bigger "explodes" the parts.

// colors skin = [0.95, 0.75, 0.55]; c_tire = [0.4, 0.4, 0.4]; c_metal = [0.8, 0.8, 0.8];

a_seat = 30; // 20 to 70 degrees clock-wise

a_pivot = 60; a_front = 70; a_rear = a_front; // BUG FIX: was missing; a_rear was referenced but never defined a_BB = 85;

$fn = 20;

a_legs = [

   [80, 69, 56, 42, 42, 63, 78, 84],
   [96, 88, 68, 30, 20, 52, 74, 90],
   [0,  0,  0,  45, 35, 20,  0,  0],
   [43, 63, 78, 86, 80, 69, 57, 42],
   [20, 52, 74, 92, 96, 88, 62, 30],
   [45, 22,  0, 10,  0,  0, 20, 30],
   [-45, 23, -45, 20, -45,  45, -30,  45],
   [-45, 45, -20, 45, -45,  45, -45,  45]

]; // for animation

front_wheel = 24 * s; // 16 to 26 rear_wheel = front_wheel;

wheelbase = 45 * s; // compromise bt weight ratio * compactness

BB2FWA = max(14 * s, front_wheel / 2 + 2 * s); xBB = BB2FWA * sin(a_BB); zBB = BB2FWA * cos(a_BB);

d = -(front_wheel - rear_wheel) / 2;

h_front = BB2FWA + BB2FWA;

z_pivot = 4 * s * sin(a_pivot) - BB2FWA * cos(a_front); // vertical location on pivot

pivot2RWA = wheelbase - BB2FWA * sin(a_front) - 4 * s * cos(a_pivot);

h_mid = 25;

c_frame = [1, 0, 0]; // color

// ----------- Modules -----------------

include <Renderer.scad> include <maths.scad>

function rem8(u, v) = round((u / v - floor(u / v)) * 8);

// BUG FIX: Added `w` as a named parameter to the wheel() module. // Previously the module had no parameters but was called as wheel(w=front_wheel). module wheel(w) {

   rotate([90, 0, 0])
   {
       // spokes
       color(c_metal)
       %translate([0, 0, .75]) intersection() {
           cylinder(h = 1, r1 = w / 2, r2 = 1, center = true);
           for (alpha = [30, 90, 150, 210, 270, 330])
               // BUG FIX: rotate() in 3D context requires a vector; was rotate($t*360+alpha)
               rotate([0, 0, $t * 360 + alpha]) square([.25, w - 2 * tire], true);
       }
       %translate([0, 0, -.75]) intersection() {
           cylinder(h = 1, r1 = 1, r2 = w / 2, center = true);
           for (alpha = [0, 60, 120, 180, 240, 300])
               // BUG FIX: same rotate() fix as above
               rotate([0, 0, $t * 360 + alpha]) square([.25, w - 2 * tire], true);
       }
       // rim
       color(c_metal)
       difference() {
           circle(w / 2 - tire + s / 5);
           circle(w / 2 - tire - s / 10);
       }
       // hub
       color(c_metal)
       rotate([0, 0, 0]) roundedBox([2, 2, 4], 1, false);
       // tire
       color(c_tire)
       rotate_extrude(convexity = 10, $fn = 50) {
           translate([w / 2 - tire / 2, 0, 0])
           circle(r = tire / 2, $fn = 30);
       }
   }

}

module BB() {

   rotate([90, 0, 0])
   {
       // chain ring
       color(c_metal)
       translate([0, 0, 1.25 * s])
       rotate_extrude() {
           // BUG FIX: scale([.5,.05]) circle(ring) is not valid OpenSCAD.
           // circle() takes a radius; the intent was a flattened torus cross-section.
           // Fixed: translate then use circle with explicit radii.
           translate([ring * 0.5, 0, 0])
           scale([1, 0.1]) circle(r = ring * 0.5);
       }
       // crank
       rotate([0, 0, (7 - rem8($t * 360, 90)) * 45])
       {
           color(c_metal)
           {
               translate([2 * s, 0,  2 * s]) square([6 * s, s], true);
               translate([-2 * s, 0, -2 * s]) square([6 * s, s], true);
           }
           // pedals
           color(c_tire)
           {
               translate([-5 * s, 0, -4 * s])
               rotate([90, 0, 80 + (7 - rem8($t * 360, 90)) * 45 + a_legs[6][7 - rem8($t * 360, 90)]])
               square([3 * s, 4 * s], true);
               translate([5 * s, 0, 4 * s])
               rotate([90, 0, 70 + (7 - rem8($t * 360, 90)) * 45 + a_legs[7][7 - rem8($t * 360, 90)]])
               square([3 * s, 4 * s], true);
           }
       }
       // BB axle
       color(c_metal) cylinder(h = 4 * s, r = s, center = true, $fn = 20);
   }

}

// rider based on groom from http://www.thingiverse.com/thing:3495 module veloRider() {

   // torso
   roundedBox([8, 5, bodyHeight + 2], 2.2, false);
   // rib cage
   translate([0, 0.1, 9 * 0.75 / 2]) roundedBox([9, 5.1, bodyHeight * 0.75 + 2], 2.5, false);
   // chest
   translate([0, 0.1, 5]) roundedBox([9.5, 5.3, bodyHeight * 0.5 + 2], 2.0, false);
   translate([0, 0, bodyHeight / 2 + 0.5])
   {
       // arms
       mirror([1, 0, 0]) rotate([-45, 0, 0]) arm(1.4, 3.5, armLength, 170, -15, 115, hand);
       translate([0, 0, 0.5]) rotate([-45, 0, 0]) arm(1.4, 3.5, armLength, 170, -15, 115, hand);
       rotate([-a_seat - 10, 0, 0])
       {
           // neck
           color(skin) translate([0, 0, 2]) cylinder(r1 = 1.3, r2 = 1.4, h = 2);
           // head
           color(skin)
           translate([0, 0, 6.5 + 5 * (explode - 1)])
           rotate([-4, -1, 0]) scale([2.4, 2.4, 3.2]) sphere($fn = 30);
           // helmet
           color(c_tire) translate([0, -5, 1.8 + 15 * (explode - 1)])
           {
               helmet();
               visor();
           }
       }
   }
   // legs
   color(c_tire) translate([0, -0.2, -bodyHeight / 2 + 0.5])
   {
       mirror([1, 0, 0]) leg(2, 2, armLength * 1.1, 2,
           a_legs[0][7 - rem8($t * 360, 90)],
           a_legs[1][7 - rem8($t * 360, 90)],
           [a_legs[2][7 - rem8($t * 360, 90)], 0, 0]);
       leg(2, 2, armLength * 1.1, 2,
           a_legs[3][7 - rem8($t * 360, 90)],
           a_legs[4][7 - rem8($t * 360, 90)],
           [a_legs[5][7 - rem8($t * 360, 90)], 0, 0]);
   }

} // BUG FIX: closing brace for veloRider() was missing; the module was never closed.

module helmet() {

   translate([0, 5, 5.5])
   difference() {
       sphere(3, center = true);
       translate([0, 0, -5.5]) cube(10, center = true);
   }

}


module visor() { translate([4.5,-1,12]) rotate([30,0,180]) scale(3) linear_extrude_bezier( [[[0.7,-4,-.6], [1,-5,0.0], [2,-5,0.0], [2.3,-4,-.6]], [[.3,-2.5,-1], [.7,-2.5,0.4], [2.3,-2.5,0.4], [2.7,-2.5,-1]], [[.9,-2.3,-1.2], [1,-2.1,0.6], [2,-2.1,0.6], [2.1,-2.3,-1.2]], [[1.2,-2.1,-1.4], [1,-2,-1], [2,-2,-1], [1.8,-2.1,-1.4]]],

steps=10, thickness = 0.05*s); 

}

module leg(thickness, hipWidth, armLength, legSpread, kneeLift, kneeBend, footPos) {

   translate([4 * (explode - 1), 0, -10 * (explode - 1)])
   {
       // upper leg
       translate([hipWidth, 0, 0]) rotate([-kneeLift, 180 - legSpread, 0])
       {
           sphere(r = thickness * 1.3);
           cylinder(r1 = thickness * 1.2, r2 = thickness * 0.8, h = armLength);
           // joint
           translate([0, 0, armLength + 10 * (explode - 1)])
           {
               // lower leg
               sphere(r = thickness * 0.8);
               rotate([kneeBend, 0, 0])
               {
                   cylinder(r1 = thickness * 0.8, r2 = thickness * 0.5, h = armLength);
                   // foot
                   translate([0, 1.5, 1 + armLength + 10 * (explode - 1)])
                   rotate([footPos[0], footPos[1], footPos[2]])
                   roundedBox([2.5, 6, 2], thickness / 2);
               }
           }
       }
   }

}

module arm(thickness, shoulderWidth, armLength, armBend, elbowBend, elbowBendForward, hand) {

   translate([10 * (explode - 1), 0, 0])
   {
       // upper arm
       translate([shoulderWidth, 0, 0]) rotate([0, armBend, 0])
       {
           sphere(r = thickness * 1.3);
           cylinder(r1 = thickness * 1.2, r2 = thickness * 0.8, h = armLength);
           // joint
           translate([0, 0, armLength + 10 * (explode - 1)])
           {
               sphere(r = thickness * 0.8);
               rotate([-elbowBendForward, -elbowBend, 0])
               {
                   cylinder(r1 = thickness * 0.8, r2 = thickness * 0.6, h = armLength * 0.8);
                   // hand
                   color(skin)
                   translate([0, 0, armLength * 0.8 + 10 * (explode - 1)])
                   scale([hand[0], hand[1], hand[2]]) sphere(r = thickness);
               }
           }
       }
   }

}

// size is a vector [w, h, d] module roundedBox(size, radius, sidesonly) {

   rot = [[0, 0, 0], [90, 0, 90], [90, 90, 0]];
   if (sidesonly) {
       cube(size - [2 * radius, 0, 0], true);
       cube(size - [0, 2 * radius, 0], true);
       for (x = [radius - size[0] / 2, -radius + size[0] / 2],
            y = [radius - size[1] / 2, -radius + size[1] / 2]) {
           translate([x, y, 0]) cylinder(r = radius, h = size[2], center = true);
       }
   } else {
       cube([size[0],           size[1] - radius * 2, size[2] - radius * 2], center = true);
       cube([size[0] - radius * 2, size[1],           size[2] - radius * 2], center = true);
       cube([size[0] - radius * 2, size[1] - radius * 2, size[2]          ], center = true);
       for (axis = [0:2]) {
           for (x = [radius - size[axis] / 2,           -radius + size[axis] / 2],
                y = [radius - size[(axis+1)%3] / 2, -radius + size[(axis+1)%3] / 2]) {
               rotate(rot[axis])
               translate([x, y, 0])
               cylinder(h = size[(axis+2)%3] - 2 * radius, r = radius, center = true);
           }
       }
       for (x = [radius - size[0] / 2, -radius + size[0] / 2],
            y = [radius - size[1] / 2, -radius + size[1] / 2],
            z = [radius - size[2] / 2, -radius + size[2] / 2]) {
           translate([x, y, z]) sphere(radius);
       }
   }

}

module seat() {

   translate([0, -6 * s, 0]) rotate([-90, 0, 90]) scale(4)
   linear_extrude_bezier([
       [[.7, -6, -1],   [1, -6.5, -1.5], [2, -6.5, -1.5], [2.3, -6, -1]],
       [[0, -5,  1],    [1, -5,    1],   [2, -5,    1],   [3, -5,  1]],
       [[0, -4, -.5],   [1, -4,    0],   [2, -4,    0],   [3, -4, -.5]],
       [[0,  0,  0],    [1,  0.5,  0.5], [2,  0.5,  0.5], [3,  0,  0]]
   ], steps = 10, thickness = 0.2 * s);
   translate([0, -6 * s, 0]) rotate([-120, 0, 90]) scale(4)
   linear_extrude_bezier([
       [[0.3, 2.5, 0.5], [1, 3.5, 0], [2, 3.5, 0], [2.7, 2.5, 0.5]],
       [[0, 2, 1],        [1, 2,  1], [2, 2,  1],  [3, 2, 1]],
       [[0, 1, 1],        [1, 1,  1], [2, 1,  1],  [3, 1, 1]],
       [[0, 0, 0],        [1, 0,  .5],[2, 0,  .5], [3, 0, 0]]
   ], steps = 10, thickness = 0.25 * s);

}

module python01() {

   wheel(w = front_wheel);
   translate([-BB2FWA / 2 * sin(a_rear), 0, -BB2FWA / 2 * cos(a_rear)])
   rotate([0, a_front, 0]) color(c_frame)  // BUG FIX: was a_rear; rear module should use a_front for the fork angle
   roundedBox([1.5 * s, 0, BB2FWA + s], 0.75 * s, false);

}

module python02() {

   wheel(w = front_wheel);
   // front tube
   translate([-BB2FWA / 2 * sin(a_front), 0, -BB2FWA / 2 * cos(a_front)])
   rotate([0, a_front, 0]) color(c_frame)
   roundedBox([1.5 * s, 0, BB2FWA + s], 0.75 * s, false);
   translate([BB2FWA / 2 * sin(a_BB), 0, BB2FWA / 2 * cos(a_BB)])
   rotate([0, a_BB, 0]) color(c_frame)
   roundedBox([1.5 * s, 0, BB2FWA + s], 0.75 * s, false);
   translate([xBB, 0, zBB])
   BB();

}

// -------------- Assembly -------------

rotate([0, 0, $t * 360]) translate([wheelbase / 2, 0, 0]) {

   // rear
   translate([-BB2FWA - h_mid - 8 * s * cos(a_pivot), 0, 0])
   rotate([0, 0, 180]) translate([BB2FWA, 0, 0]) python01();
   // front
   python02();
   // front pivot
   color(c_metal)
   translate([-BB2FWA * sin(a_front) - 3, 0, -7])
   rotate([0, a_pivot - 90, 0])
   roundedBox([6.5 * s, 6.5 * s, 1 * s], 0.375 * s, false);
   // middle tube
   color(c_frame)
   {
       translate([-wheelbase + pivot2RWA - h_mid / 2 - 4, 0, -6.5])
       rotate([0, 98, 0])
       roundedBox([1.5 * s, 0 * s, h_mid + 1 * s], 0.75 * s, false);
   }
   // rider & seat
   translate([-47 * s + BB2FWA + crank, 0, z_pivot + 5 * s])
   {
       if (rider)
       {
           scale(2 * s)
           rotate([90 - a_seat, 0, -90])
           veloRider();
       }
       // BUG FIX: seat rendering was inside the veloRider() module definition
       // due to the missing closing brace. It belongs here in the assembly.
       % rotate([0, a_seat - 90, 0])
       translate([-6 * s, 0, -5.5 * s]) seat();
   }
   // platform (debug)
   %translate([-wheelbase / 2 - 5 * s, 0, -front_wheel / 2])
   cylinder(h = .1, r = wheelbase / 2 + front_wheel / 2, center = true);

}