Modelling A Head Tube Lug In OpenSCAD

Recently I have been working on a project to build 3D printable lugs for a custom bike frame. I am using OpenSCAD to build this because I find the software to align very well to how I enjoy building things. As a software developer it is quite intuitive for building customizable 3D objects.

In this post I want to walk through my process of building the lower head tube lug using OpenSCAD. I do assume that you have an understanding of OpenSCAD, this is more of a guide than a beginner tutorial.

Here is the final result. I will walk through how it came to be. There is a cup at the bottom to hold bearings, two pipes that join at a given angle and some nice rounded fillets to make the part look nice.


Setup

The first thing to go over is the project setup.

1
2
3
4
5
6
include <utils.scad>;
include <tubes_variables.scad>;

use <Round-Anything/polyround.scad>;
use <Round-Anything/unionRoundMask.scad>;
use <tubes.scad>;

I import/use a few different scad files. utils holds a few helper methods that help make the modeling process easier. The ones I use are the span and arrange modules that make it easy to align things to vectors using linear algebra. Span takes an object, rotates it to the vector and scales it to the vectors length. Arrange takes an object and only rotates it along the vector. tubes_variables holds all of the bike frame geometry that I use later on.

In order to make this process easier I have written a few more helper modules. to_frame_coords takes a model that is in global coordinates and puts it into the correct position on the bike frame. to_lug_coords does the opposite, it takes a piece of the bike and moves it to global coordinates.

1
2
3
4
5
6
7
module to_frame_coords() {
translate(v_head_tube_bottom)rotate([-90,0,90-HEAD_ANGLE])children();
}

module to_lug_coords() {
rotate([90,90-HEAD_ANGLE,0])translate(-v_head_tube_bottom)children();
}

A note for those reading the code. My naming convention for variables is that constants are all uppercase (LIKE_THIS). Three dimensional vectors start with a v_ (v_like_this). Any modules or functions are simply lower snake case (like_this).

Bearing Cup

The first thing to build is the cup that will hold the bearing at the bottom of the head tube. The process for building parts like this is to start with a 2D version of it and then stretching it out into 3D in some way.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
module bearing_cup() {
bearing_cup_points=[
[0,0,0],
[BEARING_CATCH_RADIUS,0,0],
[BEARING_CATCH_RADIUS,-BEARING_CATCH_HEIGHT,0],
[BEARING_RADIUS,-BEARING_CATCH_HEIGHT,0],
[BEARING_RADIUS, -(BEARING_HEIGHT+BEARING_CATCH_HEIGHT), 0],
[BEARING_HOLDER_RADIUS, -(BEARING_HEIGHT+BEARING_CATCH_HEIGHT), 0.5],
[LUG_RADIUS, 10, 10],
[LUG_RADIUS, 12, 0],
[0, 12, 0],
];

rotate_extrude(angle = 360, convexity = 2)
polygon(polyRound(bearing_cup_points,10));
}

The cup is first modelled as a cross section that will be extruded in a circle to make a cylinder. Using the polyRound module from Round-Everything and setting up points, the section ends up looking like this. You see it does not look like a bearing holder yet but if you picture cutting a thin slice out of a bagel, this is kind of the same process in reverse.

Cup Cross Section

By applying rotate_extrude the cross section is turned into a cylinder by rotating it around an axis. The slice fills out and creates the rest of the bagel. Creating the final bearing cup as seen below.


Lug Top Cap

Now moving on to the top of the lug. This is purely for looks and will give the ends of the lug some nice smooth edges rather than sharp 90 degree ones. This follows exactly the same 2D to 3D procedure.

1
2
3
4
5
6
7
8
9
10
11
module lug_top() {
lug_top_points=[
[0,0,0],
[LUG_RADIUS, 0, 0],
[LUG_RADIUS, 1, 0.5],
[0, 1, 0],
];

rotate_extrude(angle = 360, convexity = 2)
polygon(polyRound(lug_top_points,10));
}

Start with the outline of a cross section. This time its just a rectangle with a single rounded corner.

Lug Top

Then rotate_extrude to produce the shape. Note how the rounded corner ends up flowing around the entire edge.


Tube Join

Finally we get to the interesting part, the join between both tubes. Unlike the first two sections this one is hard to build using the 2D to 3D method from before, so we are going to start directly in 3D.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
module tube_join() {
unionRoundMask(r=5){
translate([0,0,12])
cylinder(r=LUG_RADIUS, h=60-12);

to_lug_coords()
span(v_down_tube_connection_point, v_down_tube_top_join)
cylinder(r=LUG_RADIUS);

translate([-LUG_RADIUS,0,((60-12)/2) + 12])
scale([LUG_RADIUS*2,LUG_RADIUS*2,60-12])
cube(center=true);
}
}

Start by setting up the intersection. Create both tubes using cylinders. Move the two cylinder to the same location and angles of the tubes so that they join up where the tubes are connected. This creates the basic form but looks quite sharp where the two cylinders are connecting.


To smooth out the connection we are going to use a new tool. With the unionRoundMask tool of Round-Everything we can specify a cube as the rounding mask and have it create a nice fillet around the join. The mask lets us specify a 3D area where rounding should occur and then the tool will round all of the edges between the cylinders within that area.


Putting It All Together

Next we need to put all of our parts together into a single solid block. This will be the final 3D outline of our lug but without the holes inside for the tubes to slide into.

1
2
3
4
5
6
7
8
9
10
11
module solid_head_tube_bottom_lug() {
bearing_cup();
tube_join();

translate([0,0,60])lug_top();

to_lug_coords()
arrange(v_down_tube_connection_point, v_down_tube_top_join)
mirror([0,0,1])
lug_top();
}

Using the helpers from the start, we move all of the modules into their correct position in global coordinates. This creates the final solid part. Now its starting to look like the lug that we are building.


Hollowing It Out

Then finally we need to cut the space that each tube will slide into. Using some more helpers we move it on top of the frame and use the difference module to hollow out the part where the tubes will go. This cuts out everything where the lug and frame overlap and leaves perfectly sized holes within the final part.

1
2
3
4
5
6
7
8
9
module head_tube_bottom_lug() {
to_lug_coords()
difference(){
to_frame_coords()
solid_head_tube_bottom_lug();

inner_frame();
}
}

Now after rendering everything the head tube lug is finished.


Conclusion

This pretty much covers my process for making parts. As much as possible start with a 2D cutout of what you are building. Afterwards, extrude the cutout into a 3D object. Then apply various modifiers to the 3D object until it is the size that you are looking for.

I want to say a huge thank you to the Round-Everything library creators and maintainers because without them this piece would probably look a lot different. If you are trying to create nice looking functional parts in OpenSCAD I highly recommend using this library.

Written 08/01/2022