#!BPY

"""
Name: 'Gear'
Blender: 247
Group: 'AddMesh'
Submenu: 'Mesh'
Version: '0.99a'
Tooltip: 'Create gears/cogwheels'
"""

__author__ ="Michel Anders (varkenvarken)"
__version__ = "0.99a 2008/11/05"
__copyright__ = "GPL"
__url__ = ["author's site, http://www.swineworld.org/blender/gears"]

__doc__ = """\
Create gears/cogwheels. 

Inspired by the work of Stefano Selleri (http://www.selleri.org/Blender). 
Check www.swineworld.org/blender for a tutorial and additional info.

Parameters:<br>
Teeth: the number of teeth, equally spaced along the radius of the gear <br>
Less than 5 is treated special: this will create a wormwheel.

Radius: The radius of the gear. Gears that interact should be tangent (= touching each other) on their radii. <br>
A negative radius creates an inverted gear (a gear with teeth on the inside). 

Addendum: The amount the tip of a tooth extends above the radius 

Dedendum: The amount the through between two teeth extends below the radius 

Base: The thickness of the rim of the gear 

Width: The thickness of the gear 

Pressure angle: The slope of the tip of the teeth 

Helical angle: Teeth don't have to be perpendicular to the face of the gear. This angle determines the skew. 

Conical angle: Conical gears can be used to link non parallel axes. This angle determines the taper. <br>
E.g. 45 degrees can be used for perpendicular axes. 

Rack: Toggle this to get a single tooth that can be extended to a rack by an array modifier. 

Crown: A nonzero value creates a crownwheel (a gear with teeth that point downward) 

All other parameters should be considered experimental and may not to work
"""
# ***** BEGIN GPL LICENSE BLOCK *****
#
# Copyright (C) 2009, Michel Anders (varkenvarken)
#
# 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
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	See the
# 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 BPyAddMesh
import Blender
from math import cos, sin, tan, atan, asin, pi, radians as rad
from copy import deepcopy as dc

#constants
faces=[[0,5,6,1],[1,6,7,2],[2,7,8,3],[3,8,9,4],[6,10,11,7],[7,11,12,8],[10,13,14,11],[11,14,15,12]]
L=16 # number of vertices
#edgefaces
ef  = [5,6,10,13,14,15,12,8,9]
ef2 = [i+L for i in ef]
efc = 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

spokefaces=((0,1,2,5),(2,3,4,7),(5,2,7,6),(5,6,9,8),(6,7,10,9),(11,8,13,12),(8,9,10,13),(13,10,15,14))

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.
	"""
	
	A=[a,a+t/4,a+t/2,a+3*t/4,a+t]
	C=[cos(i) for i in A] 
	S=[sin(i) for i in A]
	
	Ra=r+Ad
	Rd=r-De
	Rb=Rd-b
	
	#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)]
		Sp=[0,sin(-t/4+p),0,sin(t/4-p)]

		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 :
		Cp=[0,cos(a+t/4+p),cos(a+t/2),cos(a+3*t/4-p)]
		Sp=[0,sin(a+t/4+p),sin(a+t/2),sin(a+3*t/4-p)]

		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):
	"""
	EXPERIMENTAL private function: calculate the vertex coords for a single side
	section of a gearspoke. returns them as a list of lists.
	"""
	
	Rd=r-De
	Rb=Rd-b
	Rl=Rb
	
	v  =[]
	ef =[]
	ef2=[]
	sf =[]
	if not gap :
		for N in range(width,1,-2) :
			ef.append(len(v))
			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)])
			ef2.append(len(v)-1)
			Rb= Rb-s
		n=0
		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 Gear(N,r,Ad,De,b,p,D=1,skew=0,conangle=0,rack=0,crown=0.0, spoke=4,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)
	conangle=rad(conangle)
	skew	=rad(skew)
	scale   = (r - 2*D*tan(conangle) )/r
	
	f =[]
	v =[]
	tg=[] #vertexgroup of top vertices.
	vg=[] #vertexgroup of valley vertices
	
	
	M=[0]
	if worm : (M,skew,D)=(range(32),rad(11.25),D/2)
	
	for W in M:
		fl=W*N*L*2
		l=0	#number of vertices
		for I in range(int(N)):
			a=I*t
			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:
					v.extend(add_tooth(a+s,t,d,r-De,0.0,0.0,b,p))
				else:
					v.extend(add_tooth(a+s,t,d,r*c,Ad*c,De*c,b*c,p,rack,crown))
				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
							
			f.extend([ [j+I*L*2+fl for j in i] for i in dc(efc)])
			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 :
		fl=len(v)
		for I in range(int(N)):
			a=I*t
			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)
					v.extend(sv)
					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)
					v.extend(sv)
					fl += len(sv)
				d1 = fl-len(sv)
				d2 = fl-2*len(sv)
				print fl,d1,d2
				#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 v, f, tg, vg

def main():
	Draw = Blender.Draw
	PREF_N = Draw.Create(20)
	PREF_R = Draw.Create(5.0)
	PREF_A = Draw.Create(0.4)
	PREF_D = Draw.Create(0.4)
	PREF_B = Draw.Create(1.0)
	PREF_P = Draw.Create(20.0)
	PREF_W = Draw.Create(1.0)
	PREF_S = Draw.Create(0.0)
	PREF_C = Draw.Create(0.0)
	PREF_K = Draw.Create(0)
	PREF_CR= Draw.Create(0.0)
	# experimental spoke parameters
	PREF_SP= Draw.Create(0)
	PREF_SB= Draw.Create(0.1)
	PREF_SW= Draw.Create(0.2)
	PREF_SL= Draw.Create(1.0)
	PREF_SR= Draw.Create(7)
	
	if not Draw.PupBlock('Add Gear', [\
	('Number of Teeth:' 	, PREF_N,     1,  200, 'Number of teeth'),\
	('Radius:'				, PREF_R,  -100,  100, 'Radius of gear, negative for crown'),\
	('Addendum:'			, PREF_A,  0.01,   10, 'Addendum, extend of tooth above radius'),\
	('Dedendum:'			, PREF_D,  0.01,   10, 'Dedendum, extend of tooth below radius'),\
	('Base:'				, PREF_B,  0.01,   10, 'Base, extend of gear below radius'),\
	('Pressure Angle:'  	, PREF_P,     0,   45, 'Pressure angle, skewness of tooth tip'),\
	('Width:'		  		, PREF_W,  0.01,  100, 'Thickness of gear'),\
	('Helical Angle:'   	, PREF_S,   -45,   45, 'Helical angle, skewness of gear threads'),\
	('Conical Angle:'   	, PREF_C,   -60,   60, 'Conical angle, taper of gear'),\
	('Rack'		  			, PREF_K,              'Create a single tooth rack'),\
	('Crown:'   			, PREF_CR,  0.0,   10, 'Extend of crown'),\
	('Teeth per Spoke'		, PREF_SP,    0,  200, 'Create a spoke every Nth tooth'),\
	('Spoke Bevel'		  	, PREF_SB, 0.01, 10.0, 'Spoke Bevel'),\
	('Spoke Width'		  	, PREF_SW, 0.01,  2.0, 'Spoke Width'),\
	('Spoke Length'		  	, PREF_SL,  0.1, 10.0, 'Spoke Length'),\
	('Spoke Resolution'		, PREF_SR,    3,   21, 'Spoke Resulion (must be odd)'),\
	]\
	):
		return
	
	if PREF_SR.val %2 == 0 : PREF_SR.val += 1
		
	verts, faces, vtips, vvalleys = Gear(PREF_N.val, PREF_R.val, PREF_A.val, PREF_D.val, PREF_B.val,\
	 PREF_P.val, PREF_W.val, PREF_S.val, PREF_C.val, PREF_K.val, PREF_CR.val,\
	 PREF_SP.val, PREF_SB.val, PREF_SW.val, PREF_SL.val, PREF_SR.val)
	
	BPyAddMesh.add_mesh_simple('Gear', verts, [], faces)
	
	
	#make sure we are in editmode
	scn=Blender.Scene.getCurrent()
	ob=scn.objects.active
	editmode = Blender.Window.EditMode()
	if not editmode : Blender.Window.EditMode(1)
	me=ob.getData(mesh=True)
	#make sure we're NOT in editmode
	Blender.Window.EditMode(0)
	#useful vertex groups (ad before we remove doubles because that changes the indices
	me.addVertGroup('teethtips')
	me.addVertGroup('valleys')
	me.assignVertsToGroup('teethtips', vtips, 1.0,Blender.Mesh.AssignModes.REPLACE)
	me.assignVertsToGroup('valleys', vvalleys, 1.0,Blender.Mesh.AssignModes.REPLACE)
	n=me.remDoubles(0.001)
	print 'removed %s doubles' % n
	me.recalcNormals()
	for f in me.faces: f.smooth = 1
	Blender.Window.EditMode(editmode)


main()
