#!BPY
"""
Name: 'XNA Rotate to Y up and back'
Blender: 248
Group: 'Object'
Tooltip: 'Rotate the selected objects to suit XNA and back.  May damage animations.'
""" 
__author__ = "JCBDigger http://games.DiscoverThat.co.uk"
__url__ = ("http://www.blender.org", "http://blenderartists.org", "http://games.DiscoverThat.co.uk")
__version__ = "004"

__bpydoc__ = """\
The purpose of this script is to make it easier when working with a model which needs to be facing 
away from the Z axis with the top in the Y direction as used by Microsoft XNA.

This script transforms the mesh and other objects leaving the rotation parameter of the object set 
at zero which is preferrable for objects exported using the Autodesk FBX for XNA exporter.

Rotation is about zero (0,0,0).

Choices:
1. From Blender to XNA which rotates to appear face down facing away from the 
default Front View camera.
2. From XNA back to Blender which assumes the model is already face down facing away from 
the camera.

Rotating the cameras and lamps is useful for working in Blender but make no difference 
for XNA.
"""
# --------------------------------------------------------------------------
# ***** 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
# --------------------------------------------------------------------------

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

# --------------------------------------------------------------------------
# Global Variables
# --------------------------------------------------------------------------
mtxRotyup = Matrix().identity() * RotationMatrix(-90,4,'x')
mtxRotzdown = Matrix().identity() * RotationMatrix(180,4,'y')
mtxRotzup = Matrix().identity() * RotationMatrix(90,4,'x')
mtxRotyfront = Matrix().identity() * RotationMatrix(180,4,'z')

# --------------------------------------------------------------------------
# FUNCTIONS start here
# --------------------------------------------------------------------------
def rotate_ob(bForward=True):
	print 'XNA Rotate to Y up and back - 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
	#Rotating.  This may take some time...
	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:
			print 'Rotate mesh'
			mesh = obj.getData()	# uses the NMesh
			if bForward:
				mesh.transform(mtxRotyup * mtxRotzdown)	# be careful the order of rotation matters
			else:
				mesh.transform(mtxRotzup * mtxRotyfront)	# be careful the order of rotation matters
			mesh.update()	# only now are the changes stored
		elif obj.getType()=='Armature' and GLOBALS['INC_ARMATURE'].val:
			print 'Rotate armature'
			arma = obj.getData()
			arma.makeEditable()
			bones = arma.bones.values()
			for ebone in bones:
				if bForward:
					ebone.head = ebone.head * mtxRotyup * mtxRotzdown
					ebone.tail = ebone.tail * mtxRotyup * mtxRotzdown
				else:
					ebone.head = ebone.head * mtxRotzup * mtxRotyfront
					ebone.tail = ebone.tail * mtxRotzup * mtxRotyfront
			arma.update()	# only now are the changes stored
		elif obj.getType()=='Lamp' and GLOBALS['INC_LAMP'].val:
			if bForward:
				obj.setMatrix(obj.getMatrix() * mtxRotyup * mtxRotzdown)
			else:
				obj.setMatrix(obj.getMatrix() * mtxRotzup * mtxRotyfront)
		elif obj.getType()=='Camera' and GLOBALS['INC_CAMERA'].val:
			if bForward:
				obj.setMatrix(obj.getMatrix() * mtxRotyup * mtxRotzdown)
			else:
				obj.setMatrix(obj.getMatrix() * mtxRotzup * mtxRotyfront)
		elif obj.getType()=='Empty' and GLOBALS['INC_EMPTY'].val:
			if bForward:
				obj.setMatrix(obj.getMatrix() * mtxRotyup * mtxRotzdown)
			else:
				obj.setMatrix(obj.getMatrix() * mtxRotzup * mtxRotyfront)
				
	# Tidy up before exiting
	Window.EditMode(bEditmode) # put us back to the mode we were in before we started
	print 'XNA Rotate - done'


# --------------------------------------------------------------------------
# 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
# works like radio buttons but requires the form to be redrawn to display the change to the other button
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_forward(e,v):
	GLOBALS['EVENT'] = e
	rotate_ob(True)

def do_back(e,v):
	GLOBALS['EVENT'] = e
	rotate_ob(False)

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 Rotate to Y up...', x+20,y+200, 200, 20)

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

	Draw.BeginAlign()
	GLOBALS['INC_EMPTY'] =		Draw.Toggle('Empty',	EVENT_NONE, x+20, y+140, 60, 20, GLOBALS['INC_EMPTY'].val,		'Include empty objects')
	GLOBALS['INC_CAMERA'] =		Draw.Toggle('Camera',	EVENT_NONE, x+80, y+140, 60, 20, GLOBALS['INC_CAMERA'].val,		'Include camera objects')
	GLOBALS['INC_LAMP'] =		Draw.Toggle('Lamp',		EVENT_NONE, x+140, y+140, 60, 20, GLOBALS['INC_LAMP'].val,		'Include lamp objects')
	GLOBALS['INC_ARMATURE'] =	Draw.Toggle('Armature',	EVENT_NONE, x+200,  y+140, 60, 20, GLOBALS['INC_ARMATURE'].val,	'Include armature objects')
	GLOBALS['INC_MESH'] =		Draw.Toggle('Mesh',		EVENT_NONE, x+260,  y+140, 80, 20, GLOBALS['INC_MESH'].val,		'Include mesh objects')
	Draw.EndAlign()
	
	Draw.Label('The mesh and armature are transformed rather than', x+20,y+110, 300, 20)
	Draw.Label('altering the object rotations.', x+20,y+90, 300, 20)
	Draw.Label('This might damage animations!', x+20,y+70, 300, 20)
	Draw.Label('Transforming the meshes can take a few moments.', x+20,y+45, 300, 20)

	Draw.PushButton('Rotate to suit XNA',	EVENT_EXIT, x+20, y-10, 180, 20,	'Rotate from Z up to XNA Y up face down in Blender', do_forward)
	Draw.PushButton('Rotate back',		EVENT_EXIT, x+210, y-10, 130, 20,	'Rotate from Y up back to Blender Z up', do_back)

	Draw.PushButton('Cancel',		EVENT_EXIT, x+240, y-50, 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_EMPTY'] =				Draw.Create(1)
	GLOBALS['INC_CAMERA'] =				Draw.Create(1)
	GLOBALS['INC_LAMP'] =				Draw.Create(1)
	GLOBALS['INC_ARMATURE'] =			Draw.Create(1)
	GLOBALS['INC_MESH'] =				Draw.Create(1)

	# So the radio type toggle buttons redraw.  Adds a bit of form flicker if you move the mouse about too much.
	while GLOBALS['EVENT'] != EVENT_EXIT:
		Draw.UIBlock(show_ui)

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


