Gear script fully ported to 2.50 alpha 0

This is a rather long post. Because I connot access my own webserver yet want to share to code I will include the full code in the next post in this thread (otherwise I’ll exceed 10k characters)

What was needed to port it from 2.49 -> 2.50 alpha 0?

The basic functions that calculate the geometry (verts and faces) are unchanged
( add_tooth(), add_spoke2(), add_gear() )
These functions were designed to return lists of tuples (x,y,z) (for the vertices) and
lists of lists [i,k,l,m] (for the faces). Because the Blender 2.50 API does not provide
facilties to alter individual elements of the the verts and faces attributes of a mesh
directly we have to add the calculated vertices and faces in bulk by using the
mesh.add_geometry(nverts,nedges,nfaces) methodfolowed by
mesh.verts.foreach_set(“co”, verts_loc) and mesh.faces.foreach_set(“verts_raw”, faces).
Both the foreach_set() methods take flattened lists as arguments, not lists of tuples, so we
added a simple function to flatten a list of lists or tuples.
Also, the vertex group API is changed a little bit but the concepts are the same:

vertexgroup = ob.add_vertex_group('NAME_OF_VERTEXGROUP') # add a vertex group
for i in vertexgroup_vertex_indices:
    ob.add_vertex_to_group(i, vertexgroup, weight, 'ADD')

Now for some reason the name does not ‘stick’ and we have to set it this way: = 'NAME_OF_VERTEXGROUP'

Conversion to 2.50 also meant we could simply do away with our crude user interface.
Just definining the appropriate properties in the AddGear() operator will display the
properties in the Blender GUI with the added benefit of making it interactive: changing
a property will redo the AddGear() operator providing the user with instant feedback.
FInally we had to convert/throw away some print statements to print functions as Blender
nows uses Python 3.x
The most puzzling issue was that the built in Python zip() function changed its behavior.
In 3.x it returns a zip object (that can be iterated over) instead of a list of tuples. This meant
we could no longer use deepcopy(zip(…)) but had to convert the zip object to a list of tuples
The code to actually implement the AddGear() function is mostly copied from add_mesh_torus()
(distributed with Blender).

Unresolved issues:

  • removing doubles:
    the code that produces the teeth of the gears produces some duplicate vertices. The original
    script just called remove_doubles() but if we do that in 2.50 we have a problem. To apply
    the bpy.ops.mesh.remove_doubles() operator we have to change to edit mode. The moment
    we do that we loose to possibilty to interactively change the properties. Also changing back
    to object mode raises a strange exception (to investigate). So for now, removing doubles is left
    to the user once satisfied with the chosen setting for a gear,
  • no suitable icon:
    a rather minor point but I reused the torus icon for the add->mesh->gear menu entry as there
    doesn’t seem to be a generic mesh icon or a way to add custom icons. Too bad, but as this is
    just eye candy it’s no big deal.

the code:

# (c) 2009, Michel J. Anders (varkenvarken)
# add gears/cogwheels to the blender 2.50 add->mesh menu
# tested with the official blender 2.50 alpha 0 32-bit windows
#  This program is free software; you can redistribute it and/or
#  modify it under the terms of the GNU General Public License
#  as published by the Free Software Foundation; either version 2
#  of the License, or (at your option) any later version.
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  GNU General Public License for more details.
#  You should have received a copy of the GNU General Public License
#  along with this program; if not, write to the Free Software Foundation,
#  Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
import bpy
import Mathutils
from math import cos, sin, tan, atan, asin, pi,radians as rad
from copy import deepcopy as dc
def flatten(alist):
    "Flatten a list of lists or tuples."
    return sum([list(a) for a in alist],[])
L=16 # number of vertices
ef  = [5,6,10,13,14,15,12,8,9]
ef2 = [i+L for i in ef]
# in python 3, zip() returns a zip object so we have to force the result into a list of lists to keep
# deepcopy happy later on in the script.
efc = [ [i,j,k,l] for i,j,k,l in zip(ef[:-1],ef2[:-1],ef2[1:],ef[1:])]
vv  = [5,6,8,9,21,22,24,25] #vertices in a valley
tv  = [13,14,15,29,30,31]   #vertices on a tooth

def add_tooth(a,t,d,r,Ad,De,b,p,rack=0,crown=0.0):
 private function: calculate the vertex coords for a single side
 section of a gear tooth. returns them as a list of lists.
 C=[cos(i) for i in A] 
 S=[sin(i) for i in A]
 #Pressure angle calc
 O =Ad*tan(p)
 p =atan(O/Ra)
 if r<0 : p = -p
 if rack :
  S =[sin(t/4)*I for I in range(-2,3)]
  v=[(Rb,r*S[i],d) for I in range(5)]
  v.extend([(Rd,r*S[i],d) for I in range(5)])
  v.extend([(r,r*S[i],d) for I in range(1,4)])
  v.extend([(Ra,r*Sp[i],d) for I in range(1,4)])
 else :
  v=[(Rb*C[i],Rb*S[i],d) for I in range(5)]
  v.extend([(Rd*C[i],Rd*S[i],d) for I in range(5)])
  v.extend([(r*C[i],r*S[i],d+crown/3) for I in range(1,4)])
  v.extend([(Ra*Cp[i],Ra*Sp[i],d+crown) for I in range(1,4)])
 return v

def add_spoke2(a,t,d,r,De,b,s,w,l,gap=0,width=19):
 v  =[]
 ef =[]
 sf =[]
 if not gap :
  for N in range(width,1,-2) :
   ts = t/4
   tm = a + 2*ts
   te = asin(w/Rb)
   td = te - ts
   t4 = ts+td*(width-N)/(width-3.0)
   A=[tm+(i-int(N/2))*t4 for i in range(N)]
   C=[cos(i) for i in A] 
   S=[sin(i) for i in A]
   v.extend([ (Rb*I,Rb*J,d) for (I,J) in zip(C,S)])
   Rb= Rb-s
  for N in range(width,3,-2) :
   sf.extend([(i+n,i+1+n,i+2+n,i+N+n) for i in range(0,N-1,2)])
   sf.extend([(i+2+n,i+N+n,i+N+1+n,i+N+2+n) for i in range(0,N-3,2)])
   n = n + N
 return v,ef,ef2,sf

def add_gear(N,r,Ad,De,b,p,D=1,skew=0,conangle=0,rack=0,crown=0.0, spoke=0,spbevel=0.1,spwidth=0.2,splength=1.0,spresol=9):
 worm =0
 if N<5 : (worm,N)=(N,24)
 t   =2*pi/N
 if rack: N=1
 p     =rad(p)
 skew =rad(skew)
 scale   = (r - 2*D*tan(conangle) )/r
 f =[]
 v =[]
 tg=[] #vertexgroup of top vertices.
 vg=[] #vertexgroup of valley vertices
 if worm : (M,skew,D)=(range(32),rad(11.25),D/2)
 for W in M:
  l=0 #number of vertices
  for I in range(int(N)):
   for(s,d,c,first) in ((W*skew,W*2*D-D,1,1),((W+1)*skew,W*2*D+D,scale,0)):
    if worm and I%(int(N)/worm)!=0:
    if not worm or (W==0 and first) or (W==(len(M)-1) and not first) : 
     f.extend([ [j+l+fl for j in i]for i in dc(faces)])
    l += L
   print (len(f))
   print (dc(efc))
   f.extend([ [j+I*L*2+fl for j in i] for i in dc(efc)])
   print (len(f))
   tg.extend([i+I*L*2 for i in tv])
   vg.extend([i+I*L*2 for i in vv])
 # EXPERIMENTAL: add spokes
 if not worm and spoke>0 :
  for I in range(int(N)):
   s=0 # for test
   if I%spoke==0 :
    for d in (-D,D) :
     (sv,ef,ef2,sf) = add_spoke2(a+s,t,d,r*c,De*c,b*c,spbevel,spwidth,splength,0,spresol)
     f.extend([ [j+fl for j in i]for i in sf])
     fl += len(sv)
    d1 = fl-len(sv)
    d2 = fl-2*len(sv)
    f.extend([(i+d2,j+d2,j+d1,i+d1) for (i,j) in zip(ef[:-1],ef[1:])])
    f.extend([(i+d2,j+d2,j+d1,i+d1) for (i,j) in zip(ef2[:-1],ef2[1:])])
   else :
    for d in (-D,D) :
     (sv,ef,ef2,sf) = add_spoke2(a+s,t,d,r*c,De*c,b*c,spbevel,spwidth,splength,1,spresol)
     fl += len(sv)
    d1 = fl-len(sv)
    d2 = fl-2*len(sv)
    #f.extend([(i+d2,i+1+d2,i+1+d1,i+d1) for (i) in (0,1,2,3)])
    #f.extend([(i+d2,i+1+d2,i+1+d1,i+d1) for (i) in (5,6,7,8)])
 return flatten(v), flatten(f), tg, vg

from bpy.props import *
class AddGear(bpy.types.Operator):
    '''Add a gear mesh.'''
    bl_idname = "mesh.gear_add"
    bl_label = "Add Gear"
    bl_register = True
    bl_undo = True
    number_of_teeth = IntProperty(name="Number of Teeth",
                                  description="Number of teeth on the gear",
    radius = FloatProperty(name="Radius",
                           description="Radius of the gear, negative for crown gear",
                           default=1.0, min=-100.0, max=100.0)
    addendum = FloatProperty(name="Addendum",
                           description="Addendum, extent of tooth above radius",
                           default=0.1, min=0.01, max=100.0)
    dedendum = FloatProperty(name="Dedendum",
                           description="Dedendum, extent of tooth below radius",
                           default=0.1, min=0.0, max=100.0)
    angle = FloatProperty(name="Pressure Angle",
                           description="Pressure angle, skewness of tooth tip (degrees)",
                           default=20.0, min=0.0, max=45.0)
    base = FloatProperty(name="Base",
                           description="Base, extent of gear below radius",
                           default=0.2, min=0.0, max=100.0)
    width = FloatProperty(name="Width",
                           description="Width, thickness of gear",
                           default=0.2, min=0.05, max=100.0)
    skew = FloatProperty(name="Skewness",
                           description="Skew of teeth (degrees)",
                           default=0.0, min=-90.0, max=90.0)
    conangle = FloatProperty(name="Conical angle",
                           description="Conical angle of gear (degrees)",
                           default=0.0, min=0.0, max=90.0)
    crown = FloatProperty(name="Crown",
                           description="Inward pointing extend of crown teeth",
                           default=0.0, min=0.0, max=100.0)
    def execute(self, context):
        verts_loc, faces, tip_vertices, valley_vertices = add_gear(,
        mesh ="Gear")
        mesh.add_geometry(int(len(verts_loc) / 3), 0, int(len(faces) / 4))
        mesh.verts.foreach_set("co", verts_loc)
        mesh.faces.foreach_set("verts_raw", faces)
        scene = context.scene
        # ugh
        for ob in scene.objects:
            ob.selected = False
        ob_new ='MESH', "Gear") = mesh
        tipgroup = ob_new.add_vertex_group('Tips')
        # for some reason the name does not 'stick' and we have to set it this way: = 'Tips'
        for i in tip_vertices:
            ob_new.add_vertex_to_group(i, tipgroup, 1.0, 'ADD')
        valleygroup = ob_new.add_vertex_group('Valleys')
        # for some reason the name does not 'stick' and we have to set it this way: = 'Valleys'
        for i in valley_vertices:
            ob_new.add_vertex_to_group(i, valleygroup, 1.0, 'ADD')
 = ob_new
        ob_new.selected = True
        ob_new.location = tuple(context.scene.cursor_location)
        return ('FINISHED',)
# Register the operator
# Add to a menu
import dynamic_menu
menu_func = (lambda self, context: self.layout.operator(AddGear.bl_idname,
                                        text="Gear", icon='ICON_MESH_DONUT'))
menu_item = dynamic_menu.add(bpy.types.INFO_MT_mesh_add, menu_func)
if __name__ == "__main__":

there seems to be 4 scripts here

how do we organise this

is it all put into the same script file and run it

and where will this new panel appears in tool sidebar may be ?

do you think with the addition of Bmesh it will change the script again ?


ok, working with replaced upper case I to i at “for I in range” in second block. and it’s nice gear.

it’s pity that tweaking parameters is possible just at adding(as i wrote it already in another post)

Sweet! Beautiful script, easy to use, fast, I love it! Very handy for making quick gears. I’ll have to study the code more closely to see what’s going on under the hood.

You may want to fix the loops in “def add_tooth…”, the mix of lowercase and capitilized “i” doesn’t make blender happy on my end. Once that was changed it worked fine.

Oh, and there are some issues with the normals of the final object. The lower surface is facing out, but all the other faces are pointed inward. Other than that it looks perfect!

When porting a few of my scripts to 2.50 I noticed this as well. Just add the vertices of the face (i.e. when you call faces.extend) in counter-clockwise order and the normals will point outward.

Why don’t you use This forum is notorious for mangling code that you get out of a code box. Post it on shareCG, anywhere…

I don’t understand why this forum does not allow attachments…?

Dang, no scriptlinks?

What a loss…

@dudecon & @atom:

I put the script on my website: (navigation to your right for download or direct link: , just plonk it into the .blender/scripts/op/ directory)

Concerning those normals: I will try the clockwise trick. In the 2.49 version I just did a recalc_normals() but just like removing doubles that can only be done in edit mode and edit mode destroys the redo options of the tools window so that clearly not the way to go. I’ll have to check the source code of those operators to see if there is some way around this.

well done !
do you think it could be possible to call the dedicated gui panel again, when one clic again on a parametric-made object like your gears ?
an object tag defined by the user/script, linked to parametric values somewhere in the registry and to the add_xx script… mmm well.:confused: it would need a dedicated object class property for that I suppose… and also to check if the object is still eligible for parametric config.

The script works great, taking 2.5 limitations into account. The only strange thing I encountered was when you set the Number of Teeth to 4 (all other parameters at default).

About a suitable icon: how about using ICON_SCRIPTWIN? It looks like a gear to me.

So how do I activate the script? I have placed it here .blender/scripts/op/

I just realized that the scripts window no longer exists in Blender 2.5. How do I activate it?

Quick note, as of at least revision 25295 This script no longer works. In fact it also breaks the add->Torus feature. I think someone changed a command in the API making this script out of date. However I love it when it works.

EDIT: Ok slight correction. The script works but it just prevents both it and add Torus from showing up in the Add->Mesh menu.

EDIT2: Fix found. O line 359 replace icon=‘ICON_MESH_DONUT’ with icon=‘MESH_DONUT’. Credit goes to Theeth.

Wow, again nice for me. Good work! And an example how to do it …

In Blender 2.5 alpha 0 trying to set the number teeth to 1 (one) it needs some time, but works.
(first I thought it doesn’t)

hey varkenvarken, would you be interested in including this script for 2.5 alpha-1 ?

Ill try have remove doubles working by having a way to run an operator without registering or undo’ing.

Sorry for my late reply but sure, I’d love to. Just tell me what you need.

this script seems not to be working in current svn.
is any chance of updating it?

Actually, I already did :smiley: Only I forgot to replace the script on my website. The one mentioned now for 2.50 is tested against svn 26208. (it fixed the return values and the way a menu os registered).

Let me know if you find any difficulties still and I’ll try to fix it.

thanks, it works now.

How to increase the diameter without change the teeth size?? These is a necessary thing to make that gears ruining together.

Hi Varkenvarken

I noticed that you needed a Remove Doubles function for your Gears script.

I needed the same thing for my “Bolt Factory”.
With help from Guillaum I have a function that will do what you need.
I hope it helps.

# -------------------------------------------------------------------------- 
# Test Remove 
# -------------------------------------------------------------------------- 
# Copyright (C) 2010: Aaron Keith
# This program is free software; you can redistribute it and/or 
# modify it under the terms of the GNU General Public License 
# as published by the Free Software Foundation; either version 2 
# of the License, or (at your option) any later version. 
# This program is distributed in the hope that it will be useful, 
# but WITHOUT ANY WARRANTY; without even the implied warranty of 
# GNU General Public License for more details. 
# You should have received a copy of the GNU General Public License 
# along with this program; if not, write to the Free Software Foundation, 
# Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA. 
# ***** END GPL LICENCE BLOCK ***** 
# -------------------------------------------------------------------------- 

import bpy
# next two utility functions are stolen from

def unpack_list(list_of_tuples):
    l = []
    for t in list_of_tuples:
    return l

def unpack_face_list(list_of_tuples):
    l = []
    for t in list_of_tuples:
        face = [i for i in t]

        if len(face) != 3 and len(face) != 4:
            raise RuntimeError("{0} vertices in face.".format(len(face)))
        # rotate indices if the 4th is 0
        if len(face) == 4 and face[3] == 0:
            face = [face[3], face[0], face[1], face[2]]

        if len(face) == 3:

    return l

#Remove Doubles takes a list on Verts and a list of Faces and 
#removes the doubles, much like Blender does in edit mode.  
#It doesn’t have the range function so it will only remove 
#verts that are in exactly the same position.  The function 
#is useful because you can perform a “Remove Doubles” with out 
#having to enter Edit Mode. Having to enter edit mode has the 
#disadvantage of not being able to interactively change the properties.

def RemoveDoubles(verts,faces):
#        print('verts')
#        for i in verts:
#            print(i)
#        print('faces')
#        for i in faces:
#            print(i)

        new_verts = []
        new_faces = []
        dict_verts = {}

        for face in faces:
            new_face = []
            for face in face:
                co = tuple(verts[face])
                if co not in dict_verts:
                    dict_verts[co] = len(dict_verts)
                if dict_verts[co] not in new_face: 
            if len(new_face) == 3 or len(new_face) == 4:

#        print('new verts')
#        for i in new_verts:
#            print(i)
#        print('new faces')
#        for i in new_faces:
#            print(i)
        return new_verts,new_faces 

def Create_Mesh(context):
#    1------2
#    |\   / |    
#    | \ /  |  
#    |  4   |
#    | /  \ |
#    |/    \|
#    0------3
#    0 =  [0.0, 0.0, 0.0]
#    1 =  [0.0, 1.0, 0.0] 
#    2 =  [1.0, 1.0, 0.0]
#    3 =  [1.0, 0.0, 0.0]
#    4 =  [0.5,0.5,1.0]   
    verts = []
    faces = []
    verts += [[0.0, 0.0, 0.0] , [0.0, 1.0, 0.0],[1.0, 1.0, 0.0],[1.0, 0.0, 0.0]]
    faces += [[0, 1, 2, 3]] #0 1 2 3
    offset =  len(verts)
    verts += [[0.0, 0.0, 0.0],[1.0, 0.0, 0.0],[0.5,0.5,1.0]]
    faces += [[offset+0,offset+1,offset+2]] #0 3 4
    offset =  len(verts)
    verts += [[1.0, 0.0, 0.0],[1.0, 1.0, 0.0],[0.5,0.5,1.0]]
    faces += [[offset+0,offset+1,offset+2]] # 3 2 4

    offset =  len(verts)
    verts += [[1.0, 1.0, 0.0],[0.0, 1.0, 0.0],[0.5,0.5,1.0]]
    faces += [[offset+0,offset+1,offset+2]] # 2 1 4 
    offset =  len(verts)
    verts += [[0.0, 1.0, 0.0],[0.0, 0.0, 0.0],[0.5,0.5,1.0],[0.5,0.5,1.0]]  #4 points with 2 of them are the same
    faces += [[offset+0,offset+1,offset+2,offset+3]] # 1 0 4 4
    offset =  len(verts)
    verts += [[0.5,0.5,1.0],[0.5,0.5,1.0],[0.5,0.5,1.0]]  #3 points all of them the same
    faces += [[offset+0,offset+1,offset+2]] # 4 4 4
    offset =  len(verts)
    verts += [[0.0, 0.0, 0.0],[0.0, 0.0, 0.0],[0.5,0.5,1.0],[0.5,0.5,1.0]]  #4 points,  2 pairs are the same
    faces += [[offset+0,offset+1,offset+2,offset+3]] # 0 0  4 4
    print('Mesh verts')
    for i in verts:
    print('Mesh faces')
    for i in faces:
    verts, faces = RemoveDoubles(verts,faces)

    print('Mesh verts after Remove Doubles')
    for i in verts:
    print('Mesh faces after Remove Doubles')
    for i in faces:
    mesh ="Test")
    mesh.add_geometry((len(verts)), 0, int(len(faces)))
    mesh.verts.foreach_set("co", unpack_list(verts))
    mesh.faces.foreach_set("verts_raw", unpack_face_list(faces))
    scene = context.scene
    # ugh
    for ob in scene.objects:
        ob.selected = False

    ob_new ="Test_ob", mesh)
    ob_new.selected = True
    obj_act =

class ObjectButtonsPanel(bpy.types.Panel):
    bl_space_type = "VIEW_3D"
    bl_region_type = "TOOLS"
    bl_label = "Remove Doubles Test V1.00"

    def draw_header(self, context):
        layout = self.layout
        layout.label(text="", icon='PLUGIN')

    def draw(self,context):
        layout = self.layout
        row = layout.row()

class CUSTOM_OT_Create_Button(bpy.types.Operator):
    bl_idname = "CUSTOM_OT_Create_Button"
    bl_label = "Create"
    __doc__ = "Create Test"
    def invoke(self, context, event):

def register():
def unregister():

if __name__ == "__main__":