[Bf-blender-cvs] SVN commit: /data/svn/bf-blender [11797] trunk/blender/release/scripts/ export_fbx.py: FBX export update

Campbell Barton cbarton at metavr.com
Thu Aug 23 18:34:15 CEST 2007


Revision: 11797
          http://projects.blender.org/plugins/scmsvn/viewcvs.php?view=rev&root=bf-blender&revision=11797
Author:   campbellbarton
Date:     2007-08-23 18:34:15 +0200 (Thu, 23 Aug 2007)

Log Message:
-----------
FBX export update
* added a user interface
* added support for exporting multiple actions as FBX's 'Takes'
* added support for one armature effecting multiple meshes
* added support for multiple armatures (bone names wont have namespace collisions)

Modified Paths:
--------------
    trunk/blender/release/scripts/export_fbx.py

Modified: trunk/blender/release/scripts/export_fbx.py
===================================================================
--- trunk/blender/release/scripts/export_fbx.py	2007-08-23 13:43:09 UTC (rev 11796)
+++ trunk/blender/release/scripts/export_fbx.py	2007-08-23 16:34:15 UTC (rev 11797)
@@ -40,15 +40,57 @@
 # ***** END GPL LICENCE BLOCK *****
 # --------------------------------------------------------------------------
 
+
+from math import degrees, atan, pi
+import time
+import os # only needed for batch export
+
 import Blender
+import bpy
+from Blender.Mathutils import Matrix, Vector, Euler, RotationMatrix, TranslationMatrix
+
 import BPyObject
 import BPyMesh
 import BPySys
 import BPyMessages
-import time
-from math import degrees, atan, pi
-from Blender.Mathutils import Matrix, Vector, Euler, RotationMatrix, TranslationMatrix
 
+
+def copy_file(source, dest):
+	file = open(source, 'rb')
+	data = file.read()
+	file.close()
+	
+	file = open(dest, 'wb')
+	file.write(data)
+	file.close()
+
+
+def copy_images(dest_dir, textures):
+	if not dest_dir.endswith(Blender.sys.sep):
+		dest_dir += sys.sep
+	
+	image_paths = {} # use set() later
+	for img in textures:
+		image_paths[Blender.sys.expendpath(img.filename)] = None
+
+
+	# Now copy images
+	copyCount = 0
+	for image_path in image_paths.itervalues():
+		if Blender.sys.exists(image_path):
+			# Make a name for the target path.
+			dest_image_path = dest_dir + image_path.split('\\')[-1].split('/')[-1]
+			if not Blender.sys.exists(dest_image_path): # Image isnt alredy there
+				print '\tCopying "%s" > "%s"' % (image_path, dest_image_path)
+				try:
+					copy_file(image_path, dest_image_path)
+					copyCount+=1
+				except:
+					print '\t\tWarning, file failed to copy, skipping.'
+	
+	print '\tCopied %d images' % copyCount
+	
+
 mtx_z90 = RotationMatrix(90, 3, 'z')
 mtx_x90 = RotationMatrix(90, 3, 'x')
 
@@ -75,32 +117,13 @@
 ZVEC  = Vector(0, 0,  1)
 ZVECN = Vector(0, 0, -1)
 
-ROT_ORDER = [\
-(0,1,2),\
-(1,2,0),\
-(2,0,1),\
-(2,1,0),\
-(1,0,2),\
-(0,2,1),\
-]
-
 # Used to add the scene name into the filename without using odd chars
 
 sane_name_mapping_ob = {}
 sane_name_mapping_mat = {}
 sane_name_mapping_tex = {}
+sane_name_mapping_take = {}
 
-# Change the order rotation is applied.
-MATRIX_IDENTITY_3x3 = Matrix([1,0,0],[0,1,0],[0,0,1])
-MATRIX_IDENTITY_4x4 = Matrix([1,0,0,0],[0,1,0,0],[0,0,1,0],[0,0,0,1])
-
-def eulerRotate(x,y,z, rot_order): 
-	# Clamp all values between 0 and 360, values outside this raise an error.
-	mats=[RotationMatrix(x%360,3,'x'), RotationMatrix(y%360,3,'y'), RotationMatrix(z%360,3,'z')]
-	# print rot_order
-	# Standard BVH multiplication order, apply the rotation in the order Z,X,Y
-	return (mats[rot_order[2]]*(mats[rot_order[1]]* (mats[rot_order[0]]* MATRIX_IDENTITY_3x3))).toEuler()
-
 def strip_path(p):
 	return p.split('\\')[-1].split('/')[-1]
 
@@ -112,124 +135,122 @@
 	except:		pass
 	
 	orig_name = name
-	name = BPySys.cleanName(name)
+	if not name:
+		name = 'unnamed' # blank string, ASKING FOR TROUBLE!
+	else:
+		name = BPySys.cleanName(name)
+		
+		# Unlikely but make sure reserved names arnt used
+		if		name == 'Scene':		name = 'Scene_'
+		elif	name == 'blend_root':	name = 'blend_root_'
+	
 	dct[orig_name] = name
 	return name
 
 def sane_obname(data):		return sane_name(data, sane_name_mapping_ob)
 def sane_matname(data):		return sane_name(data, sane_name_mapping_mat)
 def sane_texname(data):		return sane_name(data, sane_name_mapping_tex)
+def sane_takename(data):	return sane_name(data, sane_name_mapping_take)
 
 
+def increment_string(t):
+	name = t
+	num = ''
+	while name and name[-1].isdigit():
+		num = name[-1] + num
+		name = name[:-1]
+	if num:	return '%s%d' % (name, int(num)+1)	
+	else:	return name + '_0'
+	
+
 # storage classes
 class my_bone_class:
-	def __init__(self, blenBone, blenArmature, blenMesh, fbxObName):
+	__slots__ =(\
+	  'blenName',\
+	  'blenBone',\
+	  'blenMeshes',\
+	  'blenArmature',\
+	  'restMatrix',\
+	  'parent',\
+	  'blenName',\
+	  'fbxName',\
+	  'fbxArmObName',\
+	  '__pose_bone',\
+	  '__anim_poselist')
+	
+	unique_names = set()
+	
+	def __init__(self, blenBone, blenArmature, fbxArmObName):
+		
+		# This is so 2 armatures dont have naming conflicts since FBX bones use object namespace
+		fbxName =			sane_obname(blenBone)
+		while fbxName in my_bone_class.unique_names:	fbxName = increment_string(fbxName)
+		self.fbxName = fbxName
+		my_bone_class.unique_names.add(fbxName)
+		
+		self.fbxArmObName =		fbxArmObName
+		
 		self.blenName =			blenBone.name
 		self.blenBone =			blenBone
-		self.blenBoneParent =	blenBone.parent
-		self.blenMesh =			blenMesh
+		self.blenMeshes =		{}					# fbxMeshObName : mesh
 		self.blenArmature =		blenArmature
 		self.restMatrix =		blenBone.matrix['ARMATURESPACE']
-		self.restMatrixInv =	self.restMatrix.copy().invert()
-		self.restMatrixLocal =	None # set later, need parent matrix
+		
+		# not used yet
+		# self.restMatrixInv =	self.restMatrix.copy().invert()
+		# self.restMatrixLocal =	None # set later, need parent matrix
+		
 		self.parent =			None
-		self.fbxName =			sane_obname(blenBone)
-		self.fbxObName =		fbxObName
 		
 		# not public
 		pose = blenArmature.getPose()
 		self.__pose_bone =		pose.bones[self.blenName]
-		self.__bone_parent =	blenBone.parent
-		self.__anim_poselist = {} # store a list if matricies here, (poseMatrix, head, tail)
+		
+		# store a list if matricies here, (poseMatrix, head, tail)
+		# {frame:posematrix, frame:posematrix, ...}
+		self.__anim_poselist = {}
 	
+	'''
 	def calcRestMatrixLocal(self):
 		if self.parent:
 			self.restMatrixLocal = self.restMatrix * self.parent.restMatrix.copy().invert()
 		else:
 			self.restMatrixLocal = self.restMatrix.copy()
-	
+	'''
 	def setPoseFrame(self, f):
 		# cache pose info here, frame must be set beforehand
+		
+		# Didnt end up needing head or tail, if we do - here it is.
+		'''
 		self.__anim_poselist[f] = (\
 			self.__pose_bone.poseMatrix.copy(),\
 			self.__pose_bone.head.copy(),\
 			self.__pose_bone.tail.copy() )
+		'''
+		
+		self.__anim_poselist[f] = self.__pose_bone.poseMatrix.copy()
 	
 	# get pose from frame.
 	def getPoseMatrix(self, f):
-		#return mtx4_z90 * self.__pose_bone.poseMatrix.copy()
-		#return self.__pose_bone.poseMatrix.copy()
-		return self.__anim_poselist[f][0].copy()
+		return self.__anim_poselist[f]
+	'''
 	def getPoseHead(self, f):
 		#return self.__pose_bone.head.copy()
 		return self.__anim_poselist[f][1].copy()
 	def getPoseTail(self, f):
 		#return self.__pose_bone.tail.copy()
 		return self.__anim_poselist[f][2].copy()
+	'''
 	# end
-
-	def getPoseMatrixLocal(self, frame):
-		if self.parent:
-			return self.getPoseMatrix(frame) * self.parent.getPoseMatrix(frame).invert()
-		else:
-			return self.getPoseMatrix(frame)
-		#return mtx4_z90 * mat
 	
-	def getPoseMatrixLocalTip(self):
-		#print "ASAS"
-		if self.parent:
-			
-			vec = self.parent.getPoseTail(frame) - self.parent.getPoseHead(frame)
-			#vec = self.parent.getPoseHead(frame) - self.parent.getPoseTail(frame)
-			mat = TranslationMatrix(vec) * self.parent.getPoseMatrix(frame)
-			#mat = self.parent.getPoseMatrix() * TranslationMatrix(vec).invert()
-			#print "  ASAS"
-			return mat * self.getPoseMatrix(frame)
-		else:
-			return self.getPoseMatrix(frame)
-	
-	def getPoseMatrixTip(self, frame):
-		vec = self.getPoseHead(frame) - self.getPoseTail(frame)
-		return TranslationMatrix(vec) * self.getPoseMatrix(frame)
-	
-	# This works in 1 test but not for all
-	
-	def getPoseMatrixLocal_RestRelative(self, frame):
-		
-		matrix= self.getPoseMatrix(frame)
-		rest_matrix = self.restMatrix.copy()
-		
-		if self.parent:
-			#matrix=						matrix * self.parent.getPoseMatrix(frame).invert()
-			matrix=						matrix * self.parent.getPoseMatrixTip(frame).invert()
-			rest_matrix=				rest_matrix * self.parent.restMatrixInv
-			rest_matrix = mtx4_x90 * rest_matrix
-		else:
-			rest_matrix = mtx4_z90n * rest_matrix
-		
-		return matrix * rest_matrix.invert()
-	
-	def getPoseMatrix_RestRelative(self, frame):
-		return self.getPoseMatrix(frame) * self.restMatrix.copy().invert()
-		
-	def getPoseMatrix_RestRelative_ZROT(self, frame):
-		# Works for rest bones with no loc/size/rot
-		# but failes othwrwise.
-		return (mtx4_z90 * self.getPoseMatrix(frame)) * (mtx4_z90 * self.restMatrix.copy()).invert()
-		
-		v = Vector(0,0,1) * self.restMatrix.copy().invert().rotationPart()
-		rest_z90 = RotationMatrix(90, 4, 'r', v)
-		
-		return (mtx4_z90 * self.getPoseMatrix(frame)) * (rest_z90 * self.restMatrix.copy()).invert()
-		
-	def getSomeMatrix2(self, frame):
-		return self.getPoseMatrixLocal(frame) * self.restMatrixLocal.copy()
-	
 	def getAnimMatrix(self, frame):
 		if not self.parent:
 			return mtx4_z90 * self.getPoseMatrix(frame)
 		else:
 			return (mtx4_z90 * self.getPoseMatrix(frame)) * (mtx4_z90 * self.parent.getPoseMatrix(frame)).invert()
+	
+	def flushAnimData(self):
+		self.__anim_poselist.clear()
 
 
 def mat4x4str(mat):
@@ -244,7 +265,67 @@
 
 '''
 
-def write_header(file):
+# This func can be called with just the filename
+def write(filename, batch_objects = None, \
+		EXP_OBS_SELECTED =			True,
+		EXP_MESH =					True,
+		EXP_MESH_APPLY_MOD =		True,
+		EXP_MESH_HQ_NORMALS =		False,
+		EXP_ARMATURE =				True,
+		EXP_LAMP =					True,
+		EXP_CAMERA =				True,
+		EXP_EMPTY =					True,
+		EXP_IMAGE_COPY =			False,
+		ANIM_ENABLE =				True,
+		ANIM_OPTIMIZE =				True,
+		ANIM_OPTIMIZE_PRECISSION =	6,
+		ANIM_ACTION_ALL =			True,
+		BATCH_ENABLE =				False,
+		BATCH_GROUP =				True,
+		BATCH_SCENE =				False,
+		BATCH_FILE_PREFIX =			'',
+		BATCH_OWN_DIR =				False
+	):
+	
+	"""
+	# ----------------- Batch support!
+	if BATCH_ENABLE:
+		# get the path component of filename
+		
+		
+		
+		# call this function within a loop with BATCH_ENABLE == False
+		if BATCH_GROUP:
+			data_seq = bpy.data.groups
+		else: # must be scene
+			data_seq = bpy.data.scenes
+		
+		
+		
+		
+		
+		for data in data_seq: # scene or group
+			newname = BATCH_FILE_PREFIX + BPySys.cleanName(data)
+			
+			if BATCH_OWN_DIR:
+				# make dir
+
+		return
+	
+	# end batch support
+	"""
+	
+	
+	
+	
+	print '\nFBX export starting...', filename
+	start_time = Blender.sys.time()
+	file = open(filename, 'w')
+	sce = bpy.data.scenes.active
+	world = sce.world
+	
+	
+	# ---------------------------- Write the header first
 	file.write(header_comment)
 	curtime = time.localtime()[0:6]
 	# 
@@ -270,13 +351,10 @@
 	
 	file.write('\nCreationTime: "%.4i-%.2i-%.2i %.2i:%.2i:%.2i:000"' % curtime)

@@ Diff output truncated at 10240 characters. @@




More information about the Bf-blender-cvs mailing list