#!BPY

"""
Name: 'Gear'
Blender: 247
Group: 'AddMesh'
Submenu: 'Mesh'
Version: '0.99a'
Tip: 'Create gears/cogwheels'
"""

__author__ ="Michel Anders (varkenvarken)"
__version__ = "0.99a 2008/11/05"
__copyright__ = """
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 3 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, see <http://www.gnu.org/licenses/>.
"""
__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
"""


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()