[Bf-committers] Blender Need an evaluation

xavier loux xavierloux.loux at gmail.com
Sat Sep 9 15:36:58 CEST 2017


Hello, I have created an add-ons to easily export content from Blender to
Unreal Engine 4 and I need it to be evaluated and tested to submit it in
Blender. I have attached the .py file (version 0, 1, 1)

Thank you for your feedback.
With best regards, Loux Xavier

Add-ons wiki page :
https://wiki.blender.org/index.php/Extensions:2.6/Py/Scripts/Import-Export/Blender_For_UnrealEngine
Submission page : https://developer.blender.org/T52622
-------------- next part --------------
#====================== 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 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/>.
#  All rights reserved.
#
#======================= END GPL LICENSE BLOCK =============================

# ----------------------------------------------
#  This addons allows to easily export several objects at the same time in .fbx
#  for use in unreal engine 4 by removing the usual constraints
#  while respecting UE4 naming conventions and a clean tree structure.
#  It also contains a small toolkit for collisions and sockets
# ----------------------------------------------

bl_info = {
	'name': 'Blender for UnrealEngine',
	'description': "This add-ons allows to easily export several objects at the same time for use in unreal engine 4.",
	'author': 'Loux Xavier (BleuRaven)',
	'version': (0, 1, 1),
	'blender': (2, 78, 0),
	'location': 'View3D > Tool > Unreal Engine 4',
	'warning': '',
	"wiki_url": "http://wiki.blender.org/index.php/Extensions:2.6/Py/"
				"Scripts/Import-Export/Blender_For_UnrealEngine",
	'tracker_url': '',
	'support': 'COMMUNITY',
	'category': 'Import-Export'}

import os
from mathutils import Vector
from mathutils import Quaternion
import bpy
import fnmatch
from bpy.props import *
from bpy.types import Operator
import math
import mathutils
from bpy import data as bpy_data

#############################[Variables]#############################


exportedAssets = [] #List of exported objects [Assetsype , ExportPath] Reset with each export


#############################[Functions]#############################


def GetStringSceneProperty(properties): #Allows to get StringSceneProperty with properties name.
	prop = ""
	try:
		prop = bpy.context.scene[properties]
		return prop
	except:
		pass
	return prop


def ChecksRelationship(arrayA, arrayB): #Checks if it exits an identical variable in two lists
	for a in arrayA:
		for b in arrayB:
			if a == b:
				return True
	return False


def SelectParentAndDesiredChilds(obj): #Selects only all child objects that must be exported with parent objet
	bpy.ops.object.select_all(action='DESELECT')
	bpy.context.scene.objects.active = obj
	bpy.ops.object.select_grouped(type='CHILDREN_RECURSIVE')

	for unwantedObj in FindAllObjetsByExportType("dont_export"): #Deselect all objects that should not be exported
		unwantedObj.select = False
	obj.select = True


def ResetArmaturePose(obj): #Reset armature pose
	for x in obj.pose.bones:
		x.rotation_quaternion = Quaternion((0,0,0),0)
		x.scale = Vector((1,1,1))
		x.location = Vector((0,0,0))


def VerifiDirs(directory): #check and create a folder if it does not exist
	if not os.path.exists(directory):
		os.makedirs(directory)


def ExportSingleAnimation(obj, targetAction, dirpath, filename): #Export a single animation
	if obj.type == 'ARMATURE':
		bpy.ops.object.mode_set(mode = 'OBJECT')
		originalLoc = Vector((0,0,0))
		originalLoc =	originalLoc + obj.location #Save objet location
		obj.location = (0,0,0) #Moves object to the center of the scene for export

		SelectParentAndDesiredChilds(obj)
		ResetArmaturePose(obj)

		obj.animation_data.action = targetAction #Apply desired action
		keyframes = []
		for fcu in obj.animation_data.action.fcurves:
			for keyframe in fcu.keyframe_points:
				xCurve, yCurve = keyframe.co
				keyframes.append(xCurve)
		bpy.context.scene.frame_end = keyframes[-1] #Set end_frame on the final key the current action
		VerifiDirs(dirpath)
		fullpath = os.path.join( dirpath , filename )
		bpy.ops.export_scene.fbx(
			filepath=fullpath,
			check_existing=False,
			version='BIN7400',
			use_selection=True,
			object_types={'ARMATURE'},
			bake_anim=True,
			bake_anim_use_nla_strips=False,
			bake_anim_use_all_actions=False,
			bake_anim_force_startend_keying=True,
			)
		exportedAssets.append(["Animation", fullpath])
		obj.location = originalLoc #Move object to this saved location


def ExportSingleMesh(obj, dirpath, filename): #Export a single Mesh
	bpy.ops.object.mode_set(mode = 'OBJECT')
	originalLoc = Vector((0,0,0))
	originalLoc =	originalLoc + obj.location #Save objet location
	obj.location = (0,0,0) #Moves object to the center of the scene for export

	SelectParentAndDesiredChilds(obj)

	VerifiDirs(dirpath)
	fullpath = os.path.join( dirpath , filename )
	bpy.ops.export_scene.fbx(filepath=fullpath,
		check_existing=False,
		version='BIN7400',
		use_selection=True,
		bake_anim=False,
		)
	meshType = "StaticMesh"
	if obj.type == 'ARMATURE':
		meshType = "SkeletalMesh"
	exportedAssets.append([meshType , fullpath])
	obj.location = originalLoc #Move object to this saved location


def FindAllObjetsByExportType(exportType): #Find all objets with a ExportEnum property desired
	targetObj = []
	for obj in bpy.context.scene.objects:
		try:
			prop = obj.ExportEnum
			if prop == exportType:
				targetObj.append(obj)
		except:
			pass
	return(targetObj)

def GenerateUe4Name(name): #From a objet name generate a new name with by adding a suffix number


	def IsValidName(testedName): #Checks if an object uses this name. If not is a valid name
		for obj in bpy.context.scene.objects:
			if testedName == obj.name:
				return False
		return True


	valid = False
	number = 0
	newName = ""
	while valid == False:
		newName = name+"_"+str(number)
		if IsValidName(newName):
			valid = True
		else:
			number = number+1
	return newName


def ConvertEmptyToUe4Socket(): #Convert all selected empty to unreal socket
	if CheckIfCollisionAndSocketOwnerIsValid():
		ownerObjName = GetStringSceneProperty("CollisionAndSocketOwner")
		bpy.ops.object.mode_set(mode = 'OBJECT')
		ownerObj = bpy.data.objects[ownerObjName]
		for obj in bpy.context.selected_objects:
			if obj != ownerObj:
				if obj.type == 'EMPTY':
					obj.name = GenerateUe4Name("SOCKET_"+ownerObjName)
					obj.scale = (0.01,0.01,0.01)
					obj.empty_draw_size = 100
					if obj.parent != ownerObj.name:
						bpy.ops.object.select_all(action='DESELECT')
						obj.select = True
						bpy.context.scene.objects.active = ownerObj
						bpy.ops.object.parent_set(type='OBJECT', keep_transform=True)


def ConvertMeshToUe4Collision(collisionType): #Convert all selected mesh to unreal collisions
	ownerObjName = GetStringSceneProperty("CollisionAndSocketOwner")
	if CheckIfCollisionAndSocketOwnerIsValid():
		bpy.ops.object.mode_set(mode = 'OBJECT')
		ownerObj = bpy.data.objects[ownerObjName]
		prefixName = ""

		#Set the name of the Prefix depending on the type of collision in agreement with unreal FBX Pipeline
		if collisionType == "Box":
			prefixName = "UBX_"
		elif collisionType == "Capsule":
			prefixName = "UCP_"
		elif collisionType == "Sphere":
			prefixName = "USP_"
		elif collisionType == "Convex":
			prefixName = "UCX_"
		else:
			return

		mat = bpy.data.materials.get("UE4Collision")
		if mat is None:
			mat = bpy.data.materials.new(name="UE4Collision")
		mat.diffuse_color = (0, 0.6, 0)
		mat.alpha = 0.1
		mat.use_transparency = True

		for obj in bpy.context.selected_objects:
			if obj != ownerObj:
				if obj.type == 'MESH':
					obj.data.materials.clear()
					obj.data.materials.append(mat)



					obj.name = GenerateUe4Name(prefixName+ownerObjName)
					obj.show_wire = True
					obj.show_transparent = True
					if obj.parent != ownerObj.name:
						bpy.ops.object.select_all(action='DESELECT')
						obj.select = True
						bpy.context.scene.objects.active = ownerObj
						bpy.ops.object.parent_set(type='OBJECT', keep_transform=True)


def ExportAllByList(targetObjets): #Export all objects that need to be exported
	if len(targetObjets) > 0:
		Scene = bpy.context.scene
		blendFileLoc = os.path.dirname(bpy.data.filepath)
		smPrefix = GetStringSceneProperty("StaticPrefixExportName")
		skPrefix = GetStringSceneProperty("SkeletalPrefixExportName")
		animPrefix = GetStringSceneProperty("AnimPrefixExportName")
		for obj in targetObjets:
			if obj.type == 'ARMATURE':
				exportDir = os.path.join( blendFileLoc, "ExportedFbx" , "SkeletalMesh", obj.name )
				ExportSingleMesh(obj, exportDir, skPrefix+obj.name+".fbx")
				for Action in bpy.data.actions:

					objBonesName = [bone.name for bone in obj.pose.bones]
					animBonesName = [curve.data_path.split('"')[1] for curve in Action.fcurves]

					if ChecksRelationship(objBonesName, animBonesName):
						print("Il ya une correlation")
						animExportDir = os.path.join( exportDir, "Anim" )
						ExportSingleAnimation(obj, Action, animExportDir, animPrefix+obj.name+"_"+Action.name+".fbx")
					else:
						print("Il n'y a pas de correlation")
			else:
				exportDir = os.path.join( blendFileLoc, "ExportedFbx" , "StaticMesh" )
				ExportSingleMesh(obj, exportDir, smPrefix+obj.name+".fbx")

def	CorrectBadProperty():
	Scene = bpy.context.scene
	foo_objs1 = [obj for obj in Scene.objects if
		fnmatch.fnmatchcase(obj.name, "UBX*") or
		fnmatch.fnmatchcase(obj.name, "UCX*") or
		fnmatch.fnmatchcase(obj.name, "UCP*") or
		fnmatch.fnmatchcase(obj.name, "USP*") or
		fnmatch.fnmatchcase(obj.name, "SOCKET*")]

	for u in foo_objs1:
		try:

			if u.ExportEnum == "export_and_childs":
				u.ExportEnum = "auto"
		except:
			pass


def ExportComplete(self): #Display a summary at the end of the export and reset "exportedAssets"
	if len(exportedAssets) > 0:
		self.report({'INFO'}, "Export of "+str(len(exportedAssets))+" asset(s) has been finalized ! ")
		self.report({'INFO'}, "Look in th console for more info.")
		print ("################## Exported asset(s) ##################")
		for asset in exportedAssets:
			print (asset[0]+" --> "+asset[1])
		print ("################## Exported asset(s) ##################")
	else:
		self.report({'WARNING'}, "Not found assets. with \"Export and child\" properties.")
		self.report({'OPERATOR'}, "Pleas select at least one object and set \"Export and child\" properties.")
	del exportedAssets[:]


#############################[Visual and UI]#############################

#### UI Function
def CheckIfCollisionAndSocketOwnerIsValid():
	for obj in bpy.data.objects:
		if GetStringSceneProperty("CollisionAndSocketOwner") == obj.name:
			owner = bpy.data.objects[GetStringSceneProperty("CollisionAndSocketOwner")]
			if owner.type != "ARMATURE":
				return True
	return False

#### Propertys
def initObjectProperties():
	bpy.types.Object.ExportEnum = EnumProperty(
	name = "Type of export ",
	description	 = "Export type of active object",
	items = [("auto", "Auto", "Export only if one parents is \"Export and child\"", "KEY_HLT", 1),
		("export_and_childs", "Export and childs", "Export self objet and all childs", "KEYINGSET", 2),
		("dont_export", "Dont export", "Will never export", "KEY_DEHLT", 3)])


def initSceneProperties():
	bpy.types.Scene.CollisionAndSocketOwner = StringProperty(
		name = "Owner",
		description	 = "Enter the owner name of the collision or socket",
		default = "")

	bpy.types.Scene.StaticPrefixExportName = StringProperty(
		name = "StaticMesh Prefix",
		description	 = "Prefix of staticMesh when exported",
		maxlen = 255,
		default = "SM_")

	bpy.types.Scene.SkeletalPrefixExportName = StringProperty(
		name = "SkeletalMesh Prefix ",
		description	 = "Prefix of SkeletalMesh when exported",
		maxlen = 255,
		default = "SK_")

	bpy.types.Scene.AnimPrefixExportName = StringProperty(
		name = "AnimationSequence Prefix",
		description	 = "Prefix of AnimationSequence when exported",
		maxlen = 255,
		default = "Anim_")
	return


def ChecksProp(prop):
	try:
		value = prop["StaticPrefixExportName"]
	except:
		pass
		prop["StaticPrefixExportName"] = "SM_"
	try:
		value = prop["SkeletalPrefixExportName"]
	except:
		pass
		prop["SkeletalPrefixExportName"] = "SK_"
	try:
		value = prop["AnimPrefixExportName"]
	except:
		pass
		prop["AnimPrefixExportName"] = "Anim_"

	return

#### Panels
class ue4PropertiesPanel(bpy.types.Panel): #Is Objet Properties panel
	bl_idname = "panel.ue4.properties"
	bl_label = "Objet Properties"
	bl_space_type = "VIEW_3D"
	bl_region_type = "TOOLS"
	bl_category = "Unreal Engine 4"

	def draw(self, context):
		layout = self.layout
		try:
			ob = context.object
			layout.prop(ob, 'ExportEnum')
		except:
			pass
		row = self.layout.row().split(percentage = 0.80 )
		row = row.column()

		row.operator("object.selectexport")
		row.operator("object.deselectexport")


class ue4CollisionsAndSocketsPanel(bpy.types.Panel): #Is Collisions And Sockets panel
	bl_idname = "panel.ue4.collisionsandsockets"
	bl_label = "Collisions And Sockets"
	bl_space_type = "VIEW_3D"
	bl_region_type = "TOOLS"
	bl_category = "Unreal Engine 4"

	def draw(self, context):

		scene = context.scene
		layout = self.layout

		ownerSelect = layout.row().split(align=True, percentage=0.9)
		ownerSelect.prop_search(scene, "CollisionAndSocketOwner", scene, "objects")
		ownerSelect.operator("object.setownerbyactive", text="", icon='EYEDROPPER')



		layout.label("Convert selected objet to Unreal collision or socket", icon='PHYSICS')

		convertButtons = layout.row().split(percentage = 0.80 )
		convertButtons.active = CheckIfCollisionAndSocketOwnerIsValid()
		convertButtons.enabled = CheckIfCollisionAndSocketOwnerIsValid()
		convertButtons = convertButtons.column()
		convertButtons.operator("object.converttoboxcollision", icon='MESH_CUBE')
		convertButtons.operator("object.converttoconvexcollision", icon='MESH_ICOSPHERE')
		convertButtons.operator("object.converttocapsulecollision", icon='MESH_CAPSULE')
		convertButtons.operator("object.converttospherecollision", icon='SOLID')
		convertButtons.operator("object.converttosocket", icon='OUTLINER_DATA_EMPTY')

class ue4CheckCorrect(bpy.types.Panel): #Is Check and correct panel
	bl_idname = "panel.ue4.CheckCorrect"
	bl_label = "Check and correct"
	bl_space_type = "VIEW_3D"
	bl_region_type = "TOOLS"
	bl_category = "Unreal Engine 4"

	def draw(self, context):
		scn = context.scene
		props = self.layout.row().operator("object.correctproperty", icon='FILE_TICK')

class ue4ExportPanel(bpy.types.Panel): #Is Export panel
	bl_idname = "panel.ue4.export"
	bl_label = "Export"
	bl_space_type = "VIEW_3D"
	bl_region_type = "TOOLS"
	bl_category = "Unreal Engine 4"

	def draw(self, context):
		scn = context.scene
		self.layout.prop(scn, 'StaticPrefixExportName', icon='OBJECT_DATA')
		self.layout.prop(scn, 'SkeletalPrefixExportName', icon='OBJECT_DATA')
		self.layout.prop(scn, 'AnimPrefixExportName', icon='OBJECT_DATA')
		props = self.layout.row().operator("object.exportforunreal", icon='EXPORT')

#### Buttons
class SelectExportAndChildButton(bpy.types.Operator):
	bl_label = "Select all \"Export and childs\" objects"
	bl_idname = "object.selectexport"
	bl_description = "Select all root objects that will be exported"

	def execute(self, context):
		for obj in FindAllObjetsByExportType("export_and_childs"):
			obj.select = True
		return {'FINISHED'}


class DeselectExportAndChildButton(bpy.types.Operator):
	bl_label = "Deselect all \"Export and childs\" objects"
	bl_idname = "object.deselectexport"
	bl_description = "Deselect all root objects that will be exported"

	def execute(self, context):
		for obj in FindAllObjetsByExportType("export_and_childs"):
			obj.select = False
		return {'FINISHED'}

class SetOwnerByActive(bpy.types.Operator):
	bl_label = "Set owner by active selection"
	bl_idname = "object.setownerbyactive"
	bl_description = "Set owner by active selection"

	def execute(self, context):
		try:
			bpy.context.scene["CollisionAndSocketOwner"] = bpy.context.active_object.name
		except:
			pass
			bpy.context.scene["CollisionAndSocketOwner"] = ""
		return {'FINISHED'}

class ConvertToUECollisionButtonBox(bpy.types.Operator):
	bl_label = "Convert to box (UBX)"
	bl_idname = "object.converttoboxcollision"
	bl_description = "Convert selected mesh(s) to Unreal collision ready for export (Boxes type)"

	def execute(self, context):
		ConvertMeshToUe4Collision("Box")
		return {'FINISHED'}


class ConvertToUECollisionButtonCapsule(bpy.types.Operator):
	bl_label = "Convert to capsule (UCP)"
	bl_idname = "object.converttocapsulecollision"
	bl_description = "Convert selected mesh(s) to Unreal collision ready for export (Capsules type)"

	def execute(self, context):
		ConvertMeshToUe4Collision("Capsule")
		return {'FINISHED'}


class ConvertToUECollisionButtonSphere(bpy.types.Operator):
	bl_label = "Convert to sphere (USP)"
	bl_idname = "object.converttospherecollision"
	bl_description = "Convert selected mesh(s) to Unreal collision ready for export (Spheres type)"

	def execute(self, context):
		ConvertMeshToUe4Collision("Sphere")
		return {'FINISHED'}


class ConvertToUECollisionButtonConvex(bpy.types.Operator):
	bl_label = "Convert to convex shape (UCX)"
	bl_idname = "object.converttoconvexcollision"
	bl_description = "Convert selected mesh(s) to Unreal collision ready for export (Convex shapes type)"

	def execute(self, context):
		ConvertMeshToUe4Collision("Convex")
		return {'FINISHED'}


class ConvertToUESocketButton(bpy.types.Operator):
	bl_label = "Convert to socket (SOCKET)"
	bl_idname = "object.converttosocket"
	bl_description = "Convert selected empty(s) to Unreal sockets ready for export"

	def execute(self, context):
		ConvertEmptyToUe4Socket()
		return {'FINISHED'}


class ExportForUnrealEngineButton(bpy.types.Operator):
	bl_label = "Export for UnrealEngine 4"
	bl_idname = "object.exportforunreal"
	bl_description = "Export all objet intended for export in scene to fbx"

	def execute(self, context):
		ChecksProp(bpy.context.scene)
		ExportAllByList(FindAllObjetsByExportType("export_and_childs"))
		ExportComplete(self)
		return {'FINISHED'}

class CorrectBadPropertyButton(bpy.types.Operator):
	bl_label = "Correct bad property"
	bl_idname = "object.correctproperty"
	bl_description = "Corrects bad properties"

	def execute(self, context):
		CorrectBadProperty(self)
		return {'FINISHED'}


#############################[...]#############################


def register():
	bpy.utils.register_module(__name__)
	bpy.types.Scene.my_prop = bpy.props.StringProperty(default="default value")
	initObjectProperties()
	initSceneProperties()


def unregister():
	bpy.utils.unregister_module(__name__)


if __name__ == "__main__":
	register()


More information about the Bf-committers mailing list