I came across the following design for a worm gear on thingiverse:

In contrast to a normal worm gear where very few teeth are in contact at any time, the majority of the teeth mesh, greatly reducing the friction and more evenly distributing the forces.

The author posted some instructions on reddit that describe how to produce the gears. This is achieved by taking a cross-section of the wheel gear, revolving whilst twisting to produce a toroidal shape, and then subtracting that from a cylinder to get the worm gear.

Here’s the torus shape that is to be subtracted:

(note: shown here with only 10 teeth to make structure easier to interpret)

This is then subtracted from a cylinder like this:

To get the final worm gear:

At first this seemed straightforward to achieve with OpenJsCad – create a polygon for the gear profile, then use solidFromSlices to translate and twist the gear profile. But there’s a catch – the polygon must be convex and the gear profile certainly is not!

I found an alternative – manually constructing the shape out of each individual polygon. This is not too tricky to achieve, but it is fairly slow. The torus is built out of a number of discrete pieces, with the number being a parameter that determines both the quality and the time taken to build the object. Each step is swept at a constant radius with a varying twist angle.

This gives us the vertices that make up the torus shape, the next step is make polygons between each edge:

var gp1 = gear_points[i];
var gp2 = gear_points[(i+1)%gear_points.length];
var gf1 = new CSG.Vector3D(gp1.x, gp1.y, 0).rotateX(90);
var gf2 = new CSG.Vector3D(gp2.x, gp2.y, 0).rotateX(90);
var gf11 = gf1.transform(M1);
var gf12 = gf1.transform(M2);
var gf21 = gf2.transform(M1);
var gf22 = gf2.transform(M2);
polygons.push(new CSG.Polygon([
new CSG.Vertex(gf11),
new CSG.Vertex(gf12),
new CSG.Vertex(gf22)
])
);
polygons.push(new CSG.Polygon([
new CSG.Vertex(gf11),
new CSG.Vertex(gf22),
new CSG.Vertex(gf21)
])
);

Here, gp1 and gp2 are adjacent edge points on the gear profile. M1 and M2 transforms these points for the current step and the next step, yielding 2 sets of points. The final step is to build two triangles between these points.

One slice of the torus looks like this:

Here’s the final result:

And the full code:

// Here we define the user editable parameters:
function getParameterDefinitions() {
return [
{ name: 'numTeeth', caption: 'Number of teeth:', type: 'int', default: 60 },
{ name: 'circularPitch', caption: 'Circular pitch:', type: 'float', default: 3 },
{ name: 'pressureAngle', caption: 'Pressure angle:', type: 'float', default: 20 },
{ name: 'clearance', caption: 'Clearance:', type: 'float', default: 0.2 },
{ name: 'wheelHeight', caption: 'Height of wheel gear', default: 10},
{ name: 'stride', caption: 'Gearing stride', default: 1},
{ name: 'wormRadius', caption:'Radius of worm gear', type: 'float', default: 12},
{ name: 'wormGearCentreSupportRadius', caption: 'Radius of central support', type:'float', default: 6},
{ name: 'steps', caption:'Steps used to produce round elements', default: 20 },
];
}
function configureGearParams(params) {
params.twistAngle = params.stride*360/params.numTeeth;
var addendum = params.circularPitch / Math.PI;
var dedendum = addendum + params.clearance;
params.pitchRadius = params.numTeeth * params.circularPitch / (2 * Math.PI);
params.baseRadius = params.pitchRadius * Math.cos(Math.PI * params.pressureAngle / 180);
params.outerRadius = params.pitchRadius + addendum;
params.rootRadius = params.pitchRadius - dedendum;
return params;
}
// Main entry point; here we construct our solid:
function main(params) {
params = configureGearParams(params);
// set to 0 when using gear profile to subtract
params.clearance = 0;
// get the gear cross-section points
var gear_points = involuteGearProfile(params);
OpenJsCad.log("Wheel outer radius: "+params.outerRadius);
if(0) {
return worm_screw(params);
return worm_gear(params);
return worm_torus(params);
return wheel_gear(params);
}
var wheel = wheel_gear(params).setColor(0.8, 0.2, 0);
var worm = worm_gear(params);
var gearRotateRadius = params.baseRadius + params.wormGearCentreSupportRadius;
return wheel.union(worm.translate([gearRotateRadius, 0, 0]).rotateX(90));
//var worm = worm_gear(params);
//var wheel = wheel_gear(params);
}
function wheel_gear(params) {
// get the gear cross-section points
var gear_points = involuteGearProfile(params);
var gear_poly = new CSG.Polygon2D(gear_points);
return linear_extrude({
height: params.wheelHeight,
twist: params.twistAngle,
slices: params.steps,
}, gear_poly).translate([0, 0, -params.wheelHeight/2]);
}
function worm_torus(params) {
var clearance = params.clearance;
// get the gear cross-section points
var gear_points = involuteGearProfile(params);
var gearRotateRadius = params.baseRadius + params.wormGearCentreSupportRadius;
// Manually build polygons for each triangle
var polygons = [];
for(var s = 0; s < params.steps; s++) {
var a1 = s*360/params.steps;
var a2 = (s+1)*360/params.steps;
var mT = CSG.Matrix4x4.translation([gearRotateRadius, 0, 0]);
var mR1 = CSG.Matrix4x4.rotationZ(a1);
var mR2 = CSG.Matrix4x4.rotationZ(a2);
var mRTw1 = CSG.Matrix4x4.rotationY(params.twistAngle*s/params.steps);
var mRTw2 = CSG.Matrix4x4.rotationY(params.twistAngle*(s+1)/params.steps);
var M1 = mRTw1.multiply(mT).multiply(mR1);
var M2 = mRTw2.multiply(mT).multiply(mR2);
for(var i = 0; i < gear_points.length; i++) {
var gp1 = gear_points[i];
var gp2 = gear_points[(i+1)%gear_points.length];
var gf1 = new CSG.Vector3D(gp1.x, gp1.y, 0).rotateX(90);
var gf2 = new CSG.Vector3D(gp2.x, gp2.y, 0).rotateX(90);
var gf11 = gf1.transform(M1);
var gf12 = gf1.transform(M2);
var gf21 = gf2.transform(M1);
var gf22 = gf2.transform(M2);
polygons.push(new CSG.Polygon([
new CSG.Vertex(gf11),
new CSG.Vertex(gf12),
new CSG.Vertex(gf22)
])
);
polygons.push(new CSG.Polygon([
new CSG.Vertex(gf11),
new CSG.Vertex(gf22),
new CSG.Vertex(gf21)
])
);
}
}
var torus = CSG.fromPolygons(polygons);
return torus;
}
function worm_screw(params) {
var gearRotateRadius = params.baseRadius + params.wormGearCentreSupportRadius;
// this portion of the cylinder is the main bit in contact with wheel
// beyond this the wheel must be tapered to avoid interfering
var cylinderHeight = Math.sqrt(
params.outerRadius*params.outerRadius -
Math.pow(gearRotateRadius - params.wormRadius, 2));
var coneTop = CSG.cylinder({
start: [0, 0, cylinderHeight/2],
end: [0, 0, cylinderHeight/2 + params.wormRadius],
radiusStart: params.wormRadius,
radiusEnd: params.wormGearCentreSupportRadius,
resolution: params.steps,
});
var coneBottom = CSG.cylinder({
start: [0, 0, -cylinderHeight/2],
end: [0, 0, -(cylinderHeight/2 + params.wormRadius)],
radiusStart: params.wormRadius,
radiusEnd: params.wormGearCentreSupportRadius,
resolution: params.steps,
});
var screw = CSG.cylinder({
start: [0,0,-cylinderHeight/2],
end: [0,0,cylinderHeight/2],
radius:params.wormRadius,
resolution: params.steps,
});
return screw.union(coneTop).union(coneBottom);
}
function worm_gear(params) {
var torus = worm_torus(params);
var screw = worm_screw(params);
return screw.subtract(torus);
}
function involuteGearProfile(params) {
var maxtanlength = Math.sqrt(params.outerRadius*params.outerRadius -
params.baseRadius*params.baseRadius);
var maxangle = maxtanlength / params.baseRadius;
var tl_at_pitchcircle = Math.sqrt(params.pitchRadius*params.pitchRadius -
params.baseRadius*params.baseRadius);
var angle_at_pitchcircle = tl_at_pitchcircle / params.baseRadius;
var diffangle = angle_at_pitchcircle - Math.atan(angle_at_pitchcircle);
var angularToothWidthAtBase = Math.PI / params.numTeeth + 2*diffangle;
// build a single 2d tooth in the 'points' array:
var resolution = 5;
var points = [];
for(var i = 0; i <= resolution; i++)
{
// first side of the tooth:
var angle = maxangle * i / resolution;
var tanlength = angle * params.baseRadius;
var radvector = CSG.Vector2D.fromAngle(angle);
var tanvector = radvector.normal();
var p = radvector.times(params.baseRadius).plus(tanvector.times(tanlength));
points[i] = p;
// opposite side of the tooth:
radvector = CSG.Vector2D.fromAngle(angularToothWidthAtBase - angle);
tanvector = radvector.normal().negated();
p = radvector.times(params.baseRadius).plus(tanvector.times(tanlength));
points[2 * resolution + 1 - i] = p;
}
var gear_face_points = [];
var p = 0;
for(var j = 0; j < params.numTeeth; j++) {
var angle = j*360/params.numTeeth;
for(var i = 0; i < points.length; i++) {
gear_face_points[p++] = points[i].rotateZ(angle);
}
}
return gear_face_points;
}