Planetary gears in FreeCAD (Pt 1)

In this post, we will write a python script for FreeCAD that generates planetary gear assemblies.

Scripting in FreeCAD

FreeCAD is an open-source parametric 3D CAD program. It is still in development, and has some way to go before it will be competing with commercial tools, but for some simple designs it is quite usable, and actually quite powerful when it comes to scripting.  For this post, I am using FreeCAD-daily build, version 0.18.

Every command done through the FreeCAD GUI is actually translated to a python command which actually carries out the action. You can view these commands by activating they python console (View -> Panels -> python console).

Another really cool feature is the Macro Recorder. If you want to repeat a number of actions to re-use again later, you can simply record them as a Macro. To to the menu “Macro -> Macro Recording…”, give it a name and start to record. When you are done, select “Macro -> Stop Macro Recording”. If you now open the dialog under “Macro -> Macros…”, you can see a list of all your recorded Macros. You can select one and execute it. More interestingly, you can also click “Edit…”, and a window with the python code will open. I find this a good way to get started with a script – record a few actions, look a the python code to learn how it works, and then edit and refine the code. To add additional commands to a Macro scripts, a good way is to carry out the action in the CAD window, and copy-paste the python commands from the console to the script.

I like to clean up the recorded code a bit. Some times it may contain references to the file name of the CAD document – this is not a good idea, as it won’t work in other documents. Replace those with:

App.ActiveDocument.SomeObject

Planetary gears

Planetary gear sets are very versatile, useful and compact, but maybe a little less intuitive to construct than regular gear trains. I set out to write a script that can auto-generate arbitrary planetary gear sets, with helical gears if desired.

There are a few key parameters to planetary gears: The number of teeth for the sun gear Ts, the planetary gears Tp, and the ring gear Tr, or at least two out of three, the remaining one can be calculated with the formula:

Tr = Ts + 2 * Tp

Further, we need to know the modulus of the gears (the pitch, or gear tooth size), the tooth profile, and the helix angle for helical gears. And of course the height, or thickness, of the gears.

Scripting a gear

To generate the actual gear profile, we can rely on FreeCADs involute gear generator:

involute = InvoluteGearFeature.makeInvoluteGear(name+"_involute")
involute.NumberOfTeeth = teeth
involute.ExternalGear = external
involute.HighPrecision = True
involute.PressureAngle = pressureAngle
involute.Modules = module
involuteRadius = module * teeth /2.0

This will generate a 2D profile with the desired number of teeth, modulus and involute tooth profile according to the pressure angle specified. We can also specify if we want an external or internal gear. The sun and planet gears are external gears, the ring gear is an internal gear.

Now, if we want a 3D gear that we can use, we need to extrude it. The easiest would be a straight extrude, but we may want helical gears instead. For this, we have to generate a helix:

helix = doc.addObject("Part::Helix",name+"_helix")
helix.Pitch = pi * 2.0 * involuteRadius * tan(pi*(90-abs(helixAngle))/180.0)
helix.Height = height
helix.Radius=involuteRadius
helix.Angle=0.00
if helixAngle>0:
	helix.LocalCoord=0
else:
	helix.LocalCoord=1

helix.Style=1
helix.Placement=Base.Placement(Base.Vector(0.00,0.00,0.00),Base.Rotation(0.00,0.00,0.00,1.00))
helix.Label=name+'_helix'

There are a few details buried in there: the involuteRadius is computed from the gear modulus times the number of teeth. It is more convenient for gears to specify the helix angle (as it has to match for all meshing gears), but FreeCAD expects a helix pitch. You can see the formula in the code for the conversion, based on the gear radius and the angle. Lastly, to deal with negative and positive helix angles, the LocalCoord is set accordingly (clockwise or counterclockwise, based on the sign of the angle. Note that helix.Angle is actually something else in FreeCAD – this is for creating tapered helices.

Now that we have the helix, we can use the Sweep operation along the helical guide path to extrude the 3D gear:

sweep = doc.addObject('Part::Sweep',name)
sweep.Sections=doc.getObject(name+'_involute')
sweep.Spine=(doc.getObject(name+'_helix'),[])
sweep.Solid=True
sweep.Frenet=True
sweep.Placement=Base.Placement(position, Base.Rotation(Base.Vector(0.0,0.0,1.0),rotation))
App.ActiveDocument.recompute()
Gui.ActiveDocument.getObject(name+'_involute').Visibility=False

Now we should have a 3D gear with the desired parameters. It is important here to set “sweep.Frenet” to True, to keep the extrusion direction along the axis of the helix. Without this, the extrusion will be rather random and crooked.

This is almost it – except for the ring gear. To create the external body, we need to create a cylinder (or other shape larger than the gear), and subtract the internal gear extrusion from it. I will spare the details here, it is in the complete code below.

Now we have a gear. I put all of these operations into a method, to be able to call it conveniently with different parameters. Here it is all together:

def makeGear(name, 
             teeth, 
             pressureAngle=20, 
             module=1, 
             helixAngle=10,  
             height=10, 
             bore=0, 
             external = True, 
             position = Base.Vector(0.00,0.00,0.00), 
             rotation =0, extrude=False):

Putting it together

With the help of this function, we can now generate a planetary gear set fairly easily. Let’s put it into a new function:

def makePlanetary(name, module=1.25, sun_teeth = 20, planet_teeth = 20, z_off=0, helix=10, height=10, extrude=True, bore=0):
	ring_teeth = 2*planet_teeth+sun_teeth
	mesh_distance = (sun_teeth+planet_teeth)*module/2.0
	
	planet_rotation = (1-(planet_teeth % 2))*180.0/planet_teeth
	ring_rotation = 180.0/ring_teeth + planet_rotation*planet_teeth/ring_teeth
	
	makeGear(name+"_sun", teeth = sun_teeth, pressureAngle=20, module=module, helixAngle=helix, height=height, bore=bore, position = Base.Vector(0.00,0.00,z_off), rotation =0, extrude=extrude)
	makeGear(name+"_planet", teeth = planet_teeth, pressureAngle=20, module=module, helixAngle=-helix, height=height, bore=bore, position = Base.Vector(mesh_distance,0,z_off), rotation =planet_rotation, extrude=extrude)
	makeGear(name+"_ring", teeth = ring_teeth, pressureAngle=20, module=module, helixAngle=-helix, height=height, bore=bore, position = Base.Vector(0.0,0.00,z_off), rotation =ring_rotation, external=False, extrude=extrude)
	App.ActiveDocument.recompute()

Again, there are some slightly tricky things hidden here for the position and relative rotation of the gears. The sun gear and ring gear are simply centered. The planet gear needs to be offset by a certain amount, and this is simply the radius of the sun gear plus the radius of the planet gear. Multiplying the number of teeth with the module will yield the correct radius so that the gears mesh perfectly. Of course this assumes perfect tolerances, so in reality we might want to add some tolerance here – I have not put this in yet. In 3D printing or CNC machining, it is also possible to dial in some tolerances then.

The relative rotation is a bit more interesting. The planet gear needs to mesh with the sun gear. If the planet has an odd number of teeth, this is already fine as is. For an even number of teeth, we need to rotate it by half a tooth angle. This is this formula:

planet_rotation = (1-(planet_teeth % 2))*180.0/planet_teeth

If the planet had not rotated, the ring gear needs to be rotated by half a tooth to mesh. So it needs to rotate an additional amount to account for the rotation of the planet gear, multiplied with the gear ratio between planet and ring:

ring_rotation = 180.0/ring_teeth + 
                planet_rotation*planet_teeth/ring_teeth

That’s it. And now we can call this function to create the gear we want:

makePlanetary("gear1", sun_teeth = 34, planet_teeth = 16, z_off=0, helix = 15, height=10, bore=6)

Tadaa!

In Part 2, we will look at compound planetary gears. In Part 3, we will write a script to animate these gears for visualisation.

Update:

The full, current code for planetary gear generation is now on GitHub:

https://github.com/codemakeshare/planetary_gears

Appendix

Here is the macro code in full:

# -*- coding: utf-8 -*-

# Macro Begin: 
import FreeCAD
import InvoluteGearFeature
import PartDesignGui
import Part
import Part,PartGui
from math import *
from FreeCAD import Base

def makeGear(name, teeth, pressureAngle=20, module=1, helixAngle=10,  height=10, bore=0, external = True, position = Base.Vector(0.00,0.00,0.00), rotation =0, extrude=False):

	#Gui.activateWorkbench("PartDesignWorkbench")
	involute = InvoluteGearFeature.makeInvoluteGear(name+"_involute")
	involute.NumberOfTeeth = teeth
	involute.ExternalGear = external
	involute.HighPrecision = True
	involute.PressureAngle = pressureAngle
	involute.Modules = module
	
	involuteRadius = module * teeth /2.0

	doc = App.ActiveDocument
	
	if extrude:
		helix = doc.addObject("Part::Helix",name+"_helix")
		helix.Pitch = pi * 2.0 * involuteRadius * tan(pi*(90-abs(helixAngle))/180.0)
		helix.Height = height
		helix.Radius=involuteRadius
		helix.Angle=0.00
		if helixAngle>0:
			helix.LocalCoord=0
		else:
			helix.LocalCoord=1
	
		helix.Style=1
		helix.Placement=Base.Placement(Base.Vector(0.00,0.00,0.00),Base.Rotation(0.00,0.00,0.00,1.00))
		helix.Label=name+'_helix'
		App.ActiveDocument.recompute()

		sweep = doc.addObject('Part::Sweep',name)
		sweep.Sections=doc.getObject(name+'_involute')
		sweep.Spine=(doc.getObject(name+'_helix'),[])
		sweep.Solid=True
		sweep.Frenet=True
	
		sweep.Placement=Base.Placement(position, Base.Rotation(Base.Vector(0.0,0.0,1.0),rotation))
		App.ActiveDocument.recompute()		
		Gui.ActiveDocument.getObject(name+'_involute').Visibility=False

		if external:  # make bore
			if bore>0:
				cylinder = doc.addObject("Part::Cylinder",name+"_bore")
				cylinder.Label = name+"_bore"
				cylinder.Radius = bore/2.0
				cylinder.Height = height+0.1
				cylinder.Placement=Base.Placement(position, Base.Rotation(Base.Vector(0.0,0.0,1.0),rotation))
				
				cut = doc.addObject("Part::Cut",name+"_ext")
				cut.Base = doc.getObject(name)
				cut.Tool = doc.getObject(name+"_bore")
		else: # make external ring
			cylinder = doc.addObject("Part::Cylinder",name+"_exthousing")
			cylinder.Label = name+"_exthousing"
			cylinder.Radius = involuteRadius+10
			cylinder.Height = height - 0.1
			cylinder.Placement=Base.Placement(position, Base.Rotation(Base.Vector(0.0,0.0,1.0),rotation))
			
			cut = doc.addObject("Part::Cut",name+"_ext")
			cut.Base = doc.getObject(name+"_exthousing")
			cut.Tool = doc.getObject(name)
			
	else:
		involute.Placement=Base.Placement(position, Base.Rotation(Base.Vector(0.0,0.0,1.0),rotation))
	App.ActiveDocument.recompute()
	
def makePlanetary(name, module=1.25, sun_teeth = 20, planet_teeth = 20, z_off=0, helix=10, height=10, extrude=True, bore=0):
	ring_teeth = 2*planet_teeth+sun_teeth
	mesh_distance = (sun_teeth+planet_teeth)*module/2.0
	
	planet_rotation = (1-(planet_teeth % 2))*180.0/planet_teeth
	ring_rotation = 180.0/ring_teeth + planet_rotation*planet_teeth/ring_teeth
	
	makeGear(name+"_sun", teeth = sun_teeth, pressureAngle=20, module=module, helixAngle=helix, height=height, bore=bore, position = Base.Vector(0.00,0.00,z_off), rotation =0, extrude=extrude)
	makeGear(name+"_planet", teeth = planet_teeth, pressureAngle=20, module=module, helixAngle=-helix, height=height, bore=bore, position = Base.Vector(mesh_distance,0,z_off), rotation =planet_rotation, extrude=extrude)
	makeGear(name+"_ring", teeth = ring_teeth, pressureAngle=20, module=module, helixAngle=-helix, height=height, bore=bore, position = Base.Vector(0.0,0.00,z_off), rotation =ring_rotation, external=False, extrude=extrude)
	App.ActiveDocument.recompute()

makePlanetary("first", sun_teeth = 34, planet_teeth = 16, z_off=0, helix = 15, height=10, bore=6)

Gui.SendMsgToActiveView("ViewFit")


 

Leave a Reply

Your email address will not be published. Required fields are marked *