
# ___ USER CONFIGURATION _________________________________

# auto-select wheel objects : enter the first characters of your wheel objects
# in the list below.
# wheelNames=['wheel']
wheelNames=['wheel']

# steering object name keyword.
# wheel.FT001 or wheelFT will turn right or left, wheel.001 won't.
RotateZ='FT'

#  steering wheels will spin when angle orientation/direction is less than this value. else backward.
angle=130.0

# out of true object name keyword and degrees :
OutOfTrue='DK'
ootAngle=10.0

# AND/OR :

# . add wheel object exact names, 
# . their diameter in blender units
#	'auto' will work in most of the case
#	in most of the case, the diameter is equals to the object DimZ value (select your gear then 'n' in the 3dview)
# . wether it's a steering wheel (True) or not (False - default)
# 
# wheellist = [  [ wheel name 1, diameter, steering] , [ wheel name 2, diameter, steering], ... ]
# examples :
# 	wheelList=[['wheel-Rear.R','auto']]
# 	wheelList=[['wheel.002','auto',False],['wheel.003','auto',False],['wheel.004','auto',True],['wheel.007','auto',True]]

# init frame : at this frame, the script is initialized.
# go there if you added a wheel or changed some values.
initFrame=1

# some infos in the console
debug=True
verbose=False

# ________________________________________________________

__bpydoc__ = """\
Go to User Preferences panel -> Animation section and enable the RotWheel item.
Start addon in tools panel of 3d view (left pane).

Name your wheel or sphere objects 'wheel' or 'wheelFT' (wheelFT for added steering) and key them in location only.
Wheels must not have any key in their rotY ipo channel.
See addon animation_rotwheel.py in text editor for all options.

"""
'''
***** BEGIN GPL LICENSE BLOCK *****
wheelrotation.py
rotate your wheels for you. animators paradize.
Copyright (C) 2009 Jerome Mahieux

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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

***** END GPL LICENCE BLOCK *****
'''

bl_info = {
	"name": "RotWheel",
	"author": "littleneo - adapted for 2.63 by Gert De Roost",
	"version": (0, 6, 3),
	"blender": (2, 6, 3),
	"location": "View3D > Tools",
	"description": "Rotate and steer wheels of your scene.",
	"warning": "",
	"wiki_url": "",
	"tracker_url": "",
	"category": "Animation"}

if "bpy" in locals():
    import imp


# --- BEGIN------------------------------
import bpy
import mathutils
from mathutils import *
import sys as osys
import math
from math import *


started = 0
dat = {}



class RotWheel(bpy.types.Operator):
	bl_idname = "mesh.rotwheel"
	bl_label = "RotWheel"
	bl_description = "Rotate and steer wheels of your scene. see mesh_rotwheel.py in text editor for config."
	bl_options = {"REGISTER"}
	
	@classmethod
	def poll(cls, context):
		obj = context.active_object
		return (obj and obj.type == 'MESH')

	def invoke(self, context, event):
		
		global started

		started = not(started)
		if started == 0:
			if len(bpy.app.handlers.frame_change_pre):
				bpy.app.handlers.frame_change_pre.remove(onframechange)		
			return {'FINISHED'}
		bpy.app.handlers.frame_change_pre.append(onframechange)		
		context.window_manager.modal_handler_add(self)
		
		return {'RUNNING_MODAL'}

	def modal(self, context, event):

		if started == 0:
			return {'FINISHED'}
		return {"PASS_THROUGH"}


def panel_func(self, context):
	
	self.layout.label(text="RotWheel:")
	if not(started):
		self.layout.operator("mesh.rotwheel", text="Activate")
	else:
		self.layout.operator("mesh.rotwheel", text="Deactivate")


def register():
	bpy.utils.register_module(__name__)
	bpy.types.VIEW3D_PT_tools_objectmode.append(panel_func)


def unregister():
	bpy.utils.unregister_module(__name__)
	bpy.types.VIEW3D_PT_tools_objectmode.remove(panel_func)

if __name__ == "__main__":
	register()






# -----FUNCTIONS----------------------
def readVec(side) :
	a=side[0]
	c=side[1]
	try :lenght=sqrt((a*a)+(c*c))
	except :
		if str(a)=='-1.#IND' : a=0
		if str(c)=='-1.#IND' : c=0
		lenght=0
		print ("bug")
	rot=atan2( c, a )*(180/pi)
	return lenght,rot
	
def dinvert(deg) :
	deg +=180
	if deg>180:deg-=360
	return deg
	
# ------MAIN----------------------------
def onframechange(dummy):
	sc = bpy.context.scene
	f = sc.frame_current
	# INIT script values
	if f==initFrame :
		if debug : print ("\n.wheel rotation script v0.5 - init")


		# BUILD WHEEL LIST
		
		try : t=wheelList
		except : wheelList=[]
		
		# auto-select
		if len(wheelNames)> 0 :
			if debug :
				print ("\n  auto-select :")
				print ("  -------------")
			for w in wheelNames :
				if type(w)==str :
					wheelName=w
					dia='auto'
					addSteer=False
					oot=0
				else :
					wheelName=w[0]
					dia=w[1]
					if len(w) > 2 :
						steer=w[2]
						addSteer=True
						if len(w) > 3 : oot=w[3]
						else : oot=0
					else : addSteer=False
				for ob in sc.objects :
					if ob.name[0:len(wheelName)]==wheelName :
						upd=True
						for check in wheelList :
							if ob.name==check[0] : break
						else :
							wheelList.append([ob.name,dia])
							if addSteer : wheelList[-1].extend([steer,oot])
							if debug :print ('  found', ob.name)
		# rotateZ
		wi=0
		for wheel in list(wheelList) :
			try :
				if type(wheel)==str :
					wheelList[wi]=[wheel,'auto']
				ob=bpy.data.objects.get(wheelList[wi][0])
				
				if len(wheelList[wi])==2 and wheelList[wi][1] != 'follow' :
					# steering keyword ?
					if RotateZ in ob.name :
						rZ=True
						if len(ob.constraints) > 0 :
							for c in ob.constraints :
								if c.type == "FOLLOWPATH":
									rZ=False
									break
					else : rZ=False
						
					# out of true keyword ?
					if OutOfTrue in ob.name : oot=ootAngle
					else : oot=0			

					wheelList[wi].extend([rZ,oot])
				dat[ob.name+'Loc']=Vector(ob.matrix_world.to_translation())
				dat[ob.name+'Dir']=ob.matrix_world.to_euler()[2]*(180/pi)
				ob.rotation_euler[0]=oot
				ob.rotation_euler[1]=0.0
				wi +=1
			except :
				del wheelList[wi]
				print (' ',wheel[0],'not found')
		
			
		dat['wheelList']=wheelList
		if debug :
			print ("\n  wheel list :")
			print ("  ------------")
			for wheel in wheelList :
				print ('   %s :'%(wheel[0]),)
				if wheel[1] != 'follow' :
						print ('diameter %s,steering %s, OutOfTrue %s'%(wheel[1],wheel[2],wheel[3]))
				else : print ('follow mode')
						
	elif 'wheelList' in dat :
	# MAIN
		wi=0
		for wheel in list(dat['wheelList']) :
			#try :
				ob=bpy.data.objects.get(wheel[0])
				# d		vector frame - (frame-1) orientation
				# lg		B.U. / frame
				# obd	current orientation (as computed in frame-1)
				# pobd	frame -2 orientation

				loc=Vector(ob.matrix_world.to_translation())
				ploc=dat[ob.name+'Loc']
				vec=loc-ploc
				v,d=readVec(vec)
				lg=vec.length
				
				if lg > 0.0001 :
					# check diameter everytime (snow ball case etc)
					if wheel[1]=='auto' :
						dims=ob.bound_box
						dia=abs(dims[0][2]-dims[1][2])*ob.scale[2]
					elif wheel[1]=='follow' : pass
					else : dia=wheel[1]
					
					obd=ob.matrix_world.to_euler()[2]*(180/pi)
					pobd = dat[ob.name+'Dir']
					obdi = 0
	
					if abs(obd-pobd)>100 and abs(obd-pobd)<260 :
						obd=dinvert(obd)
						antiflip='antiflip'
					else :
						antiflip=''
						
					dangle=obd-d
					if dangle < -180 : dangle += 360
					if dangle > 180  : dangle -= 360	
					if abs(dangle) < angle:sign=1
					else :
						obdi=dinvert(obd)
						dangle=obdi-d
						if dangle<-180:dangle+=360
						if dangle>180:dangle-=360	
						if abs(dangle)<angle :
							sign=-1
							d=dinvert(d)
						else : sign=0
							
					# local Y - rotation :
					if wheel[1] != 'follow' :
						rot = (lg * 2 * pi) / (dia*pi)
						ob.rotation_euler[1] += sign * rot
						# local X - out of true for wheel only :
						if wheel[3] != 0 :
							ob.rotation_euler[0] = wheel[3] /(180/pi)

					# local Z  - steering :
					if wheel[1]=='follow' or wheel[2] :
						ob.rotation_euler[2] +=(d-obd)/(180/pi)
					
					dat[ob.name+'Loc']=loc
					dat[ob.name+'Dir']=obd
					wi +=1
					
					# debug for steering wheels :
					if verbose and len(wheel) > 2 and wheel[2] :
						print ('\n*',ob.name,' :')
						print ('frame  :',f)
						print ('Vec Deg :',d)
						print ('Ob Deg :',obd,pobd,obdi)
						print ('dir    :',sign,dangle)
						print ('lenght :',lg)
