#!BPY
"""
Name: 'XNA Move centres to zero'
Blender: 248
Group: 'Object'
Tooltip: 'Moves the object centres to zero (0,0,0) leaving the model where it is'
""" 
__author__ = "JCBDigger http://games.DiscoverThat.co.uk"
__url__ = ("http://www.blender.org", "http://blenderartists.org", "http://games.DiscoverThat.co.uk")
__version__ = "002"

__bpydoc__ = """\
The purpose of this script is to make it easier when working with objects that are going 
to be exported to FBX for use with Microsoft XNA.

This script moves the mesh and armature of a model to zero (0,0,0) but transforms them so that 
they appear to be in the original location with just their centre moved.

The Autodesk FBX exporter adds a dummy root to the file which is always set at zero.  
If the model and especially the armature has a different centre the animations are  
likely to be distorted. 

"""
# --------------------------------------------------------------------------
# ***** BEGIN GPL LICENSE BLOCK *****
#
# 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 on the GNU web site for full
# details: http://www.gnu.org/licenses/gpl.html
#
# ***** END GPL LICENCE BLOCK *****
# --------------------------------------------------------------------------
# Useful links:
# http://www.blender.org/documentation/248PythonDoc/index.html
# --------------------------------------------------------------------------
# ***** HISTORY *****
# --------------------------------------------------------------------------
# JCBDigger http://games.DiscoverThat.co.uk
# Created March 2009
# Limitations:  This is not recursive for grand parents so mesh objects with 
# parents with parents (grand parents) might be messed up.
# --------------------------------------------------------------------------

import Blender, bpy
from Blender import Object, Mathutils, Window
from Blender.Mathutils import Matrix

# --------------------------------------------------------------------------
# FUNCTIONS start here
# --------------------------------------------------------------------------
def centre_start():
	print 'XNA Move centres to zero - started...'
	
	sceCurrent = bpy.data.scenes.active	# the current scene
	if GLOBALS['OBS_SELECTED'].val:
		objTemp = sceCurrent.objects.context	# selected objects that are visible see also Object.GetSelected()
	else:
		objTemp = sceCurrent.objects	# all objects in the scene
	
	objSelected = [objs for objs in objTemp]
	if not objSelected:
		Draw.PupMenu('No objects selected!')
		return
	bEditmode = Window.EditMode()    # save if we are in edit mode or not
	Window.EditMode(0) # must not be in editmode before getting the mesh
	for obj in objSelected:
		if obj.getType()=='Mesh' and GLOBALS['INC_MESH'].val:
			objParent = obj.getParent()
			if objParent:
				objIncluded = [objs for objs in objSelected if objs == objParent]	# is the parent already selected
				if not objIncluded:
					# if the parent is not already selected move the parent instead of the mesh
					# but only if it's an armature or another mesh
					# WARNING this is not recursive therefore parents with parents will be messed up
					if objParent.getType()=='Armature':
						print 'Move the parent armature'
						centre_armature(objParent)
					elif objParent.getType()=='Mesh':
						centre_mesh(objParent)
			else:
				# if the mesh does not have a parent move the mesh
				centre_mesh(obj)
		elif obj.getType()=='Armature' and GLOBALS['INC_ARMATURE'].val:
			centre_armature(obj)
				
	# Tidy up before exiting
	Window.EditMode(bEditmode) # put us back to the mode we were in before we started
	print 'XNA Move centres to zero - done'

def centre_mesh(objMesh):
	# check before calling this that we have a valid mesh object
	mtxMove = get_move_matrix(objMesh.getMatrix())	# just the translation
	print 'Centre mesh'
	objMesh.setLocation(0, 0, 0)
	mesh = objMesh.getData()	# uses the NMesh
	mesh.transform(mtxMove)
	mesh.update()	# only now are the changes stored
	return

def centre_armature(objArma):
	# check before calling this that we have a valid armature object
	mtxMove = get_move_matrix(objArma.getMatrix())	# just the translation
	print 'Centre armature'
	objArma.setLocation(0, 0, 0)
	move_child_meshes(objArma, mtxMove)	# this is necessary if the mesh parented with an armature is not already centred on zero
	arma = objArma.getData()
	arma.makeEditable()
	bones = arma.bones.values()
	for ebone in bones:
		ebone.head = ebone.head * mtxMove
		ebone.tail = ebone.tail * mtxMove
	arma.update()	# only now are the changes stored
	
def get_move_matrix(mtxOrig):
	# Create a matrix with only the translation part
	mtxMove = Matrix().identity()
	mtxMove[3][0] = mtxOrig[3][0]
	mtxMove[3][1] = mtxOrig[3][1]
	mtxMove[3][2] = mtxOrig[3][2]
	mtxMove[3][3] = mtxOrig[3][3]
	return mtxMove
	
def move_child_meshes(objParent, mtxMove):
	# find all child meshes of an armature
	# they need to be transformed with the armature, but not necessarily centred
	if not objParent.getType()=='Armature':
		return

	sceCurrent = bpy.data.scenes.active	# the current scene
	objTemp = sceCurrent.objects	# all objects in the scene
	objSelected = [objs for objs in objTemp if objs.getParent() == objParent]	# just those who are a child of the armature
	for objChild in objSelected:
		if objChild.getType()=='Mesh':
			print 'Move mesh with the parent armature'
			mesh = objChild.getData()	# uses the NMesh
			mesh.transform(mtxMove)
			mesh.update()	# only now are the changes stored
	return

# --------------------------------------------------------------------------
# UI starts here
# --------------------------------------------------------------------------
from Blender import Draw, Window
EVENT_NONE = 0
EVENT_EXIT = 1
EVENT_REDRAW = 2

GLOBALS = {}

# toggle between selected and all scene objects
def do_selected(e,v):
	GLOBALS['EVENT'] = e
	GLOBALS['OBS_SCENE'].val = 0
	GLOBALS['OBS_SELECTED'].val = 1

def do_scene(e,v):
	GLOBALS['EVENT'] = e
	GLOBALS['OBS_SCENE'].val = 1
	GLOBALS['OBS_SELECTED'].val = 0

def do_centre(e,v):
	GLOBALS['EVENT'] = e
	centre_start()

def do_ui_exit(e,v):
	GLOBALS['EVENT'] = e

def show_ui():
	# Only to center the UI
	x,y = GLOBALS['MOUSE']
	x-=180; y-=0 # offset... just to get it centered
	
	Draw.Label('XNA Move centres to zero...', x+20,y+130, 200, 20)

	Draw.BeginAlign()
	GLOBALS['OBS_SELECTED'] =	Draw.Toggle('Selected Objects',		EVENT_REDRAW, x+20,  y+100, 160, 20, GLOBALS['OBS_SELECTED'].val,	'Centre selected objects on visible layers', do_selected)
	GLOBALS['OBS_SCENE'] =		Draw.Toggle('All Scene Objects',	EVENT_REDRAW, x+180,  y+100, 160, 20, GLOBALS['OBS_SCENE'].val,		'Centre all objects in this scene', do_scene)
	Draw.EndAlign()

	Draw.BeginAlign()
	GLOBALS['INC_ARMATURE'] =	Draw.Toggle('Armatures and children',	EVENT_NONE, x+20,  y+70, 160, 20, GLOBALS['INC_ARMATURE'].val,	'Include armature objects and any child meshes attached to the armature')
	GLOBALS['INC_MESH'] =		Draw.Toggle('Meshes and parents',		EVENT_NONE, x+180,  y+70, 160, 20, GLOBALS['INC_MESH'].val,		'Include mesh objects and their parent armature')
	Draw.EndAlign()
	
	Draw.Label('Selected objects will have their centres', x+20,y+40, 300, 20)
	Draw.Label('moved to zero (0,0,0).', x+20,y+20, 300, 20)

	Draw.PushButton('Move object centres',	EVENT_EXIT, x+140, y-10, 200, 20,	'Move object centres to zero', do_centre)
	Draw.PushButton('Cancel',		EVENT_EXIT, x+20, y-10, 100, 20,	'Exit without doing anything', do_ui_exit)
	
def init_ui():
	GLOBALS['EVENT'] = EVENT_REDRAW		# anything except EVENT_EXIT
	GLOBALS['MOUSE'] = [i/2 for i in Window.GetScreenSize()]

	# starting button selections
	GLOBALS['OBS_SELECTED'] =			Draw.Create(0)
	GLOBALS['OBS_SCENE'] =				Draw.Create(1)
	GLOBALS['INC_ARMATURE'] =			Draw.Create(1)
	GLOBALS['INC_MESH'] =				Draw.Create(1)

	# So the toggle buttons redraw.  Not nice but works.
	while GLOBALS['EVENT'] != EVENT_EXIT:
		Draw.UIBlock(show_ui)

# Script entry point
if __name__ == '__main__':
	init_ui()


