diff --git a/blender-for-unrealengine/__init__.py b/blender-for-unrealengine/__init__.py
new file mode 100644
index 00000000..2064edb4
--- /dev/null
+++ b/blender-for-unrealengine/__init__.py
@@ -0,0 +1,1004 @@
+#====================== 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 .
+# 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
+# xavierloux.com
+# ----------------------------------------------
+
+
+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, 2, 0),
+ 'blender': (2, 79, 0),
+ 'location': 'View3D > Tool > Unreal Engine 4',
+ 'warning': '',
+ "wiki_url": "https://github.com/xavier150/blender-for-unrealengine-addons",
+ 'tracker_url': '',
+ 'support': 'COMMUNITY',
+ 'category': 'Import-Export'}
+
+
+import os
+import bpy
+import fnmatch
+import time
+from bpy.props import (
+ StringProperty,
+ BoolProperty,
+ EnumProperty,
+ IntProperty,
+ FloatProperty,
+ BoolVectorProperty,
+ PointerProperty,
+ CollectionProperty
+ )
+
+
+import importlib
+from . import bfu_exportasset
+importlib.reload(bfu_exportasset)
+from . import bfu_writetext
+importlib.reload(bfu_writetext)
+
+from .bfu_basics import *
+from .bfu_utils import *
+
+
+class ue4ObjectPropertiesPanel(bpy.types.Panel):
+ #Is Object Properties panel
+
+ bl_idname = "panel.ue4.obj-properties"
+ bl_label = "Object Properties"
+ bl_space_type = "VIEW_3D"
+ bl_region_type = "TOOLS"
+ bl_category = "Unreal Engine 4"
+
+ bpy.types.Object.ExportEnum = EnumProperty(
+ name = "Export type",
+ description = "Export procedure",
+ items = [
+ ("auto", "Auto", "Exports only if one of the parents is \"Export recursive\"", "KEY_HLT", 1),
+ ("export_recursive", "Export recursive", "Export self object and all children", "KEYINGSET", 2),
+ ("dont_export", "Not exported", "Will never export", "KEY_DEHLT", 3)
+ ]
+ )
+
+ bpy.types.Object.exportFolderName = StringProperty(
+ name = "Sub folder name",
+ description = 'Sub folder name. No Sub folder created if left empty',
+ maxlen = 64,
+ default = "",
+ subtype = 'FILE_NAME'
+ )
+
+ bpy.types.Object.ForceStaticMesh = BoolProperty(
+ name="Force staticMesh",
+ description="Force export asset like a StaticMesh if is ARMATURE type",
+ default=False
+ )
+
+ bpy.types.Object.exportDeformOnly = BoolProperty(
+ name="Export only deform Bones",
+ description="Only write deforming bones (and non-deforming ones when they have deforming children)",
+ default=True
+ )
+
+
+ def draw(self, context):
+
+
+ layout = self.layout
+ obj = context.object
+ if obj is not None:
+
+ AssetType = layout.row()
+ AssetType.prop(obj, 'name', text="", icon='OBJECT_DATA')
+ AssetType.label('('+ GetAssetType(obj)+')') #Show asset type
+
+ ExportType = layout.column()
+ ExportType.prop(obj, 'ExportEnum')
+
+ if obj.ExportEnum == "export_recursive":
+ folderNameProperty = layout.column()
+ folderNameProperty.prop(obj, 'exportFolderName', icon='FILE_FOLDER')
+
+ if obj.type == "ARMATURE":
+ AssetType2 = layout.column()
+ AssetType2.prop(obj, "ForceStaticMesh") #Show asset type
+ if GetAssetType(obj) == "SkeletalMesh":
+ AssetType2.prop(obj, 'exportDeformOnly')
+ else:
+ layout.label('Pleas select obj for show properties.')
+
+
+class ue4ObjectImportPropertiesPanel(bpy.types.Panel):
+ #Is Object Properties panel
+
+ bl_idname = "panel.ue4.obj-import-properties"
+ bl_label = "Object Import Properties"
+ bl_space_type = "VIEW_3D"
+ bl_region_type = "TOOLS"
+ bl_category = "Unreal Engine 4"
+
+ #ImportUI
+ #https://api.unrealengine.com/INT/API/Editor/UnrealEd/Factories/UFbxImportUI/index.html
+
+ bpy.types.Object.CreatePhysicsAsset = BoolProperty(
+ name = "Create PhysicsAsset",
+ description = "If checked, create a PhysicsAsset when is imported",
+ default=True
+ )
+
+ #StaticMeshImportData
+ #https://api.unrealengine.com/INT/API/Editor/UnrealEd/Factories/UFbxStaticMeshImportData/index.html
+
+ bpy.types.Object.UseStaticMeshLODGroup = BoolProperty(
+ name = "",
+ description = '',
+ default=False
+ )
+ bpy.types.Object.StaticMeshLODGroup = StringProperty(
+ name = "LOD Group",
+ description = "The LODGroup to associate with this mesh when it is imported. Default: LevelArchitecture, SmallProp, LargeProp, Deco, Vista, Foliage, HighDetail" ,
+ maxlen = 32,
+ default = "SmallProp"
+ )
+
+ bpy.types.Object.UseStaticMeshLightMapRes = BoolProperty(
+ name = "",
+ description = '',
+ default=False
+ )
+ bpy.types.Object.StaticMeshLightMapRes = IntProperty(
+ name = "Light Map resolution",
+ description = " This is the resolution of the light map" ,
+ soft_max = 2048,
+ soft_min = 16,
+ max = 4096, #Max for unreal
+ min = 4, #Min for unreal
+ default = 16
+ )
+
+ bpy.types.Object.GenerateLightmapUVs = BoolProperty(
+ name = "Generate LightmapUVs",
+ description = "" ,
+ default=True,
+ )
+
+ #SkeletalMeshImportData
+ #https://api.unrealengine.com/INT/API/Editor/UnrealEd/Factories/UFbxSkeletalMeshImportData/index.html
+
+ #UFbxTextureImportData
+ #https://api.unrealengine.com/INT/API/Editor/UnrealEd/Factories/UFbxTextureImportData/index.html
+
+ bpy.types.Object.MaterialSearchLocation = EnumProperty(
+ name = "Material search location",
+ description = "Specify where we should search for matching materials when importing",
+ items = [
+ ("Local", "Local", "Search for matching material in local import folder only.", 1),
+ ("UnderParent", "UnderParent", "Search for matching material recursively from parent folder.", 2),
+ ("UnderRoot", "UnderRoot", "Search for matching material recursively from root folder.", 3),
+ ("AllAssets", "AllAssets", "Search for matching material in all assets folders.", 4)
+ ]
+ )
+
+
+
+ def draw(self, context):
+
+
+ layout = self.layout
+ obj = context.object
+ if obj is not None:
+ if obj.ExportEnum == "export_recursive":
+
+ #StaticMesh and SkeletalMesh prop
+ if GetAssetType(obj) == "StaticMesh" or GetAssetType(obj) == "SkeletalMesh":
+ MaterialSearchLocation = layout.row()
+ MaterialSearchLocation.prop(obj, 'MaterialSearchLocation')
+
+ #StaticMesh prop
+ if GetAssetType(obj) == "StaticMesh":
+ StaticMeshLODGroup = layout.row()
+ StaticMeshLODGroup.prop(obj, 'UseStaticMeshLODGroup', text="")
+ StaticMeshLODGroupChild = StaticMeshLODGroup.column()
+ StaticMeshLODGroupChild.enabled = obj.UseStaticMeshLODGroup
+ StaticMeshLODGroupChild.prop(obj, 'StaticMeshLODGroup')
+
+ StaticMeshLightMapRes = layout.row()
+ StaticMeshLightMapRes.prop(obj, 'UseStaticMeshLightMapRes', text="")
+ StaticMeshLightMapResChild = StaticMeshLightMapRes.column()
+ StaticMeshLightMapResChild.enabled = obj.UseStaticMeshLightMapRes
+ StaticMeshLightMapResChild.prop(obj, 'StaticMeshLightMapRes')
+
+ GenerateLightmapUVs = layout.row()
+ GenerateLightmapUVs.prop(obj, 'GenerateLightmapUVs')
+
+
+ #SkeletalMesh prop
+ if GetAssetType(obj) == "SkeletalMesh":
+ CreatePhysicsAsset = layout.row()
+ CreatePhysicsAsset.prop(obj, "CreatePhysicsAsset")
+ layout.label('...')
+
+ else:
+ layout.label('...')
+ layout.label('Pleas select obj for show properties.')
+
+
+class ue4AnimPropertiesPanel(bpy.types.Panel):
+ #Is Animation Properties panel
+
+ bl_idname = "panel.ue4.Anim-properties"
+ bl_label = "Animation Properties"
+ bl_space_type = "VIEW_3D"
+ bl_region_type = "TOOLS"
+ bl_category = "Unreal Engine 4"
+
+ #Animation :
+
+ class ACTION_UL_ExportTarget(bpy.types.UIList):
+ def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
+ ActionIsValid = False
+ try:
+ bpy.data.actions[item.name]
+ ActionIsValid = True
+ except:
+ pass
+
+ if self.layout_type in {'DEFAULT', 'COMPACT'}:
+ if ActionIsValid: #If action is valid
+ #layout.prop(item, "name", text="", emboss=False, icon="ACTION") #Debug only for see target line
+ layout.prop(bpy.data.actions[item.name], "name", text="", emboss=False, icon="ACTION")
+ layout.prop(item, "use", text="")
+ else:
+ dataText = ('Action data named "' + item.name + '" Not Found. Please clic on update')
+ layout.label(text=dataText, icon="ERROR")
+ # Not optimised for 'GRID' layout type.
+ elif self.layout_type in {'GRID'}:
+ layout.alignment = 'CENTER'
+ layout.label(text="", icon_value=icon)
+
+ class ObjExportAction(bpy.types.PropertyGroup):
+ name = StringProperty(name="Action data name", default="Unknown")
+ use = BoolProperty(name="use this action", default=False)
+
+ bpy.utils.register_class(ACTION_UL_ExportTarget)
+ bpy.utils.register_class(ObjExportAction)
+
+ bpy.types.Object.exportActionList = CollectionProperty(
+ #properties used with ""export_specific_list" on exportActionEnum
+ type=ObjExportAction
+ )
+
+ bpy.types.Object.exportActionEnum = EnumProperty(
+ name = "Action to export",
+ description = "Export procedure for actions (Animations and poses)",
+ items = [
+ ("export_auto", "Export auto", "Export all actions connected to the bones names", "FILE_SCRIPT", 1),
+ ("export_specific_list", "Export specific list", "Export only actions that are checked in the list", "LINENUMBERS_ON", 2),
+ ("export_specific_prefix", "Export specific prefix", "Export only actions with a specific prefix or the beginning of the actions names", "SYNTAX_ON", 3),
+ ("dont_export", "Not exported", "No action will be exported", "MATPLANE", 4)
+ ]
+ )
+
+ bpy.types.Object.active_ObjectAction = IntProperty(
+ name="Active Scene Action",
+ description="Index of the currently active object action",
+ default=0
+ )
+
+
+ bpy.types.Object.PrefixNameToExport = StringProperty(
+ #properties used with ""export_specific_prefix" on exportActionEnum
+ name = "Prefix name",
+ description = "Indicate the prefix of the actions that must be exported",
+ maxlen = 32,
+ default = "Example_",
+ )
+
+ bpy.types.Object.AnimStartEndTimeEnum = EnumProperty(
+ name = "Animation start/end time",
+ description = "Set when animation starts and end",
+ items = [
+ ("with_keyframes", "Auto", "The time will be defined according to the first and the last frame", "KEYTYPE_KEYFRAME_VEC", 1),
+ ("with_sceneframes", "Scene time", "Time will be equal to the scene time", "SCENE_DATA", 2),
+ ("with_customframes", "Custom time", 'The time of all the animations of this object is defined by you. Use "AnimCustomStartTime" and "AnimCustomEndTime"', "HAND", 3),
+ ]
+ )
+
+ bpy.types.Object.AnimCustomStartTime = IntProperty(
+ name = "Custom start time",
+ description = "Set when animation start",
+ default=0
+ )
+
+ bpy.types.Object.AnimCustomEndTime = IntProperty(
+ name = "Custom end time",
+ description = "Set when animation end",
+ default=1
+ )
+
+
+ bpy.types.Object.SampleAnimForExport = FloatProperty(
+ name="Sampling Rate",
+ description="How often to evaluate animated values (in frames)",
+ min=0.01, max=100.0,
+ soft_min=0.01, soft_max=100.0,
+ default=1.0,
+ )
+
+ bpy.types.Object.SimplifyAnimForExport = FloatProperty(
+ name="Simplify animations",
+ description="How much to simplify baked values (0.0 to disable, the higher the more simplified)",
+ min=0.0, max=100.0, # No simplification to up to 10% of current magnitude tolerance.
+ soft_min=0.0, soft_max=10.0,
+ default=0.0,
+ )
+
+ class UpdateObjActionButton(bpy.types.Operator):
+ bl_label = "Update action list"
+ bl_idname = "object.updateobjaction"
+ bl_description = "Update action list"
+
+ def execute(self, context):
+ def UpdateExportActionList(obj):
+ #Update the provisional action list known by the object
+
+ def SetUseFromLast(list, ActionName):
+ for item in list:
+ if item[0] == ActionName:
+ if item[1] == True:
+ return True
+ return False
+
+ AnimSave = [["", False]]
+ for Anim in obj.exportActionList: #CollectionProperty
+ name = Anim.name
+ use = Anim.use
+ AnimSave.append([name, use])
+ obj.exportActionList.clear()
+ for action in bpy.data.actions:
+ obj.exportActionList.add().name = action.name
+ obj.exportActionList[action.name].use = SetUseFromLast(AnimSave, action.name)
+ UpdateExportActionList(bpy.context.object)
+ return {'FINISHED'}
+
+ class ShowActionToExport(bpy.types.Operator):
+ bl_label = "Show action(s)"
+ bl_idname = "object.showobjaction"
+ bl_description = "Click to show actions that are to be exported with this armature."
+
+ def execute(self, context):
+ obj = context.object
+ actions = GetActionToExport(obj)
+ popup_title = "Action list"
+ if len(actions) > 1:
+ popup_title = str(len(actions))+' action(s) found for obj named "'+obj.name+'".'
+ else:
+ popup_title = 'No actions found for obj named "'+obj.name+'".'
+
+ def draw(self, context):
+ col = self.layout.column()
+ for action in actions:
+ row = col.row()
+ row.label("- "+action.name+GetActionType(action))
+ bpy.context.window_manager.popup_menu(draw, title=popup_title, icon='ACTION')
+ return {'FINISHED'}
+
+
+ def draw(self, context):
+
+ layout = self.layout
+ obj = context.object
+ if obj is not None:
+ if obj.ExportEnum == "export_recursive":
+ if GetAssetType(obj) == "SkeletalMesh" or GetAssetType(obj) == "Camera":
+
+ #Action time
+ ActionTimeProperty = layout.column()
+ if obj.type != "CAMERA":
+ ActionTimeProperty.prop(obj, 'AnimStartEndTimeEnum')
+ if obj.AnimStartEndTimeEnum == "with_customframes":
+ ActionTimePropertyChild=ActionTimeProperty = layout.row()
+ ActionTimePropertyChild.prop(obj, 'AnimCustomStartTime')
+ ActionTimePropertyChild.prop(obj, 'AnimCustomEndTime')
+ else:
+ layout.label("Note: animation start/end use scene frames with the camera for the sequencer.")
+
+ if GetAssetType(obj) == "SkeletalMesh":
+ #Action list
+ ActionListProperty = layout.column()
+ ActionListProperty.prop(obj, 'exportActionEnum')
+ if obj.exportActionEnum == "export_specific_list":
+ ActionListProperty.template_list(
+ "ACTION_UL_ExportTarget", "", # type and unique id
+ obj, "exportActionList", # pointer to the CollectionProperty
+ obj, "active_ObjectAction", # pointer to the active identifier
+ maxrows=5,
+ rows=5
+ )
+ ActionListProperty.operator("object.updateobjaction", icon='RECOVER_LAST')
+ if obj.exportActionEnum == "export_specific_prefix":
+ ActionListProperty.prop(obj, 'PrefixNameToExport')
+
+ #Action fbx properties
+ propsFbx = layout.row()
+ propsFbx.prop(obj, 'SampleAnimForExport')
+ propsFbx.prop(obj, 'SimplifyAnimForExport')
+
+ #Armature export action list feedback
+ if GetAssetType(obj) == "SkeletalMesh":
+ ArmaturePropertyInfo = layout.row().box().split(percentage = 0.75 )
+ ActionNum = len(GetActionToExport(obj))
+ actionFeedback = str(ActionNum) + " Action(s) will be exported with this armature."
+ ArmaturePropertyInfo.label( actionFeedback, icon='INFO')
+ ArmaturePropertyInfo.operator("object.showobjaction")
+ layout.label('Note: The Action with only one frame are exported like Pose.')
+ else:
+ layout.label('This assets is not a SkeletalMesh or Camera')
+ else:
+ layout.label('...')
+
+
+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"
+
+ class ConvertToUECollisionButtonBox(bpy.types.Operator):
+ bl_label = "Convert to box (UBX)"
+ bl_idname = "object.converttoboxcollision"
+ bl_description = "Convert selected mesh(es) to Unreal collision ready for export (Boxes type)"
+
+ def execute(self, context):
+ ConvertedObj = ConvertToUe4SubObj("Box", bpy.context.selected_objects, True)
+ if len(ConvertedObj) > 0 :
+ self.report({'INFO'}, str(len(ConvertedObj)) + " object(s) of the selection have be converted to UE4 Box collisions." )
+ else :
+ self.report({'WARNING'}, "Please select two objects. (Active object is the owner of the collision)")
+ return {'FINISHED'}
+
+
+ class ConvertToUECollisionButtonCapsule(bpy.types.Operator):
+ bl_label = "Convert to capsule (UCP)"
+ bl_idname = "object.converttocapsulecollision"
+ bl_description = "Convert selected mesh(es) to Unreal collision ready for export (Capsules type)"
+
+ def execute(self, context):
+ ConvertedObj = ConvertToUe4SubObj("Capsule", bpy.context.selected_objects, True)
+ if len(ConvertedObj) > 0 :
+ self.report({'INFO'}, str(len(ConvertedObj)) + " object(s) of the selection have be converted to UE4 Capsule collisions." )
+ else :
+ self.report({'WARNING'}, "Please select two objects. (Active object is the owner of the collision)")
+ return {'FINISHED'}
+
+
+ class ConvertToUECollisionButtonSphere(bpy.types.Operator):
+ bl_label = "Convert to sphere (USP)"
+ bl_idname = "object.converttospherecollision"
+ bl_description = "Convert selected mesh(es) to Unreal collision ready for export (Spheres type)"
+
+ def execute(self, context):
+ ConvertedObj = ConvertToUe4SubObj("Sphere", bpy.context.selected_objects, True)
+ if len(ConvertedObj) > 0 :
+ self.report({'INFO'}, str(len(ConvertedObj)) + " object(s) of the selection have be converted to UE4 Sphere collisions." )
+ else :
+ self.report({'WARNING'}, "Please select two objects. (Active object is the owner of the collision)")
+ return {'FINISHED'}
+
+
+ class ConvertToUECollisionButtonConvex(bpy.types.Operator):
+ bl_label = "Convert to convex shape (UCX)"
+ bl_idname = "object.converttoconvexcollision"
+ bl_description = "Convert selected mesh(es) to Unreal collision ready for export (Convex shapes type)"
+
+ def execute(self, context):
+ ConvertedObj = ConvertToUe4SubObj("Convex", bpy.context.selected_objects, True)
+ if len(ConvertedObj) > 0 :
+ self.report({'INFO'}, str(len(ConvertedObj)) + " object(s) of the selection have be converted to UE4 Convex Shape collisions.")
+ else :
+ self.report({'WARNING'}, "Please select two objects. (Active object is the owner of the collision)")
+ 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):
+ ConvertedObj = ConvertToUe4SubObj("Socket", bpy.context.selected_objects, True)
+ if len(ConvertedObj) > 0 :
+ self.report({'INFO'}, str(len(ConvertedObj)) + " object(s) of the selection have be converted to to UE4 Socket." )
+ else :
+ self.report({'WARNING'}, "Please select two objects. (Active object is the owner of the collision)")
+ return {'FINISHED'}
+
+ def draw(self, context):
+
+ def FoundTypeInSelect(targetType): #Return True is a specific type is found
+ for obj in bpy.context.selected_objects:
+ if obj != bpy.context.active_object:
+ if obj.type == targetType:
+ return True
+ return False
+
+ self.layout.label("Convert selected object to Unreal collision or socket (Static Mesh only)", icon='PHYSICS')
+
+ convertMeshButtons = self.layout.row().split(percentage = 0.80 )
+ convertMeshButtons = convertMeshButtons.column()
+ convertMeshButtons.enabled = FoundTypeInSelect("MESH")
+ convertMeshButtons.operator("object.converttoboxcollision", icon='MESH_CUBE')
+ convertMeshButtons.operator("object.converttoconvexcollision", icon='MESH_ICOSPHERE')
+ convertMeshButtons.operator("object.converttocapsulecollision", icon='MESH_CAPSULE')
+ convertMeshButtons.operator("object.converttospherecollision", icon='SOLID')
+
+ convertMeshButtons = self.layout.row().split(percentage = 0.80 )
+ convertEmptyButtons = convertMeshButtons.column()
+ convertEmptyButtons.enabled = FoundTypeInSelect("EMPTY")
+ convertEmptyButtons.operator("object.converttosocket", icon='OUTLINER_DATA_EMPTY')
+
+
+class ue4NomenclaturePanel(bpy.types.Panel):
+ #Is FPS Export panel
+
+ bl_idname = "panel.ue4.exportnomenclature"
+ bl_label = "Nomenclature"
+ bl_space_type = "VIEW_3D"
+ bl_region_type = "TOOLS"
+ bl_category = "Unreal Engine 4"
+
+
+ #Prefix
+ bpy.types.Scene.static_prefix_export_name = bpy.props.StringProperty(
+ name = "StaticMesh Prefix",
+ description = "Prefix of staticMesh",
+ maxlen = 32,
+ default = "SM_")
+
+ bpy.types.Scene.skeletal_prefix_export_name = bpy.props.StringProperty(
+ name = "SkeletalMesh Prefix ",
+ description = "Prefix of SkeletalMesh",
+ maxlen = 32,
+ default = "SK_")
+
+ bpy.types.Scene.anim_prefix_export_name = bpy.props.StringProperty(
+ name = "AnimationSequence Prefix",
+ description = "Prefix of AnimationSequence",
+ maxlen = 32,
+ default = "Anim_")
+
+ bpy.types.Scene.pose_prefix_export_name = bpy.props.StringProperty(
+ name = "AnimationSequence(Pose) Prefix",
+ description = "Prefix of AnimationSequence with only one frame",
+ maxlen = 32,
+ default = "Pose_")
+
+ bpy.types.Scene.camera_prefix_export_name = bpy.props.StringProperty(
+ name = "Camera anim Prefix",
+ description = "Prefix of camera animations",
+ maxlen = 32,
+ default = "Cam_")
+
+ #Sub folder
+ bpy.types.Scene.anim_subfolder_name = bpy.props.StringProperty(
+ name = "Animations sub folder name",
+ description = "name of sub folder for animations",
+ maxlen = 32,
+ default = "Anim")
+
+ #File path
+ bpy.types.Scene.export_static_file_path = bpy.props.StringProperty(
+ name = "StaticMesh export file path",
+ description = "Choose a directory to export StaticMesh(s)",
+ maxlen = 512,
+ default = "//ExportedFbx\StaticMesh\\",
+ subtype = 'DIR_PATH')
+
+ bpy.types.Scene.export_skeletal_file_path = bpy.props.StringProperty(
+ name = "SkeletalMesh export file path",
+ description = "Choose a directory to export SkeletalMesh(s)",
+ maxlen = 512,
+ default = "//ExportedFbx\SkeletalMesh\\",
+ subtype = 'DIR_PATH')
+
+ bpy.types.Scene.export_camera_file_path = bpy.props.StringProperty(
+ name = "Camera export file path",
+ description = "Choose a directory to export Camera(s)",
+ maxlen = 512,
+ default = "//ExportedFbx\Sequencer\\",
+ subtype = 'DIR_PATH')
+
+ bpy.types.Scene.export_other_file_path = bpy.props.StringProperty(
+ name = "Other export file path",
+ description = "Choose a directory to export text file and other",
+ maxlen = 512,
+ default = "//ExportedFbx\Other\\",
+ subtype = 'DIR_PATH')
+
+
+ def draw(self, context):
+ scn = context.scene
+
+ #Prefix
+ propsPrefix = self.layout.row()
+ propsPrefix = propsPrefix.column()
+ propsPrefix.prop(scn, 'static_prefix_export_name', icon='OBJECT_DATA')
+ propsPrefix.prop(scn, 'skeletal_prefix_export_name', icon='OBJECT_DATA')
+ propsPrefix.prop(scn, 'anim_prefix_export_name', icon='OBJECT_DATA')
+ propsPrefix.prop(scn, 'pose_prefix_export_name', icon='OBJECT_DATA')
+ propsPrefix.prop(scn, 'camera_prefix_export_name', icon='OBJECT_DATA')
+
+ #Sub folder
+ propsSub = self.layout.row()
+ propsSub = propsSub.column()
+ propsSub.prop(scn, 'anim_subfolder_name', icon='FILE_FOLDER')
+
+ #File path
+ propsPath = self.layout.row()
+ propsPath = propsPath.column()
+ propsPath.prop(scn, 'export_static_file_path')
+ propsPath.prop(scn, 'export_skeletal_file_path')
+ propsPath.prop(scn, 'export_camera_file_path')
+ propsPath.prop(scn, 'export_other_file_path')
+
+
+class ue4ImportScriptPanel(bpy.types.Panel):
+ #Is Import script panel
+
+ bl_idname = "panel.ue4.importScript"
+ bl_label = "Import Script"
+ bl_space_type = "VIEW_3D"
+ bl_region_type = "TOOLS"
+ bl_category = "Unreal Engine 4"
+
+ bpy.types.Scene.unreal_import_location = bpy.props.StringProperty(
+ name = "Unreal import location",
+ description = "Unreal import location in /Game/",
+ maxlen = 512,
+ default = 'ImportedFbx')
+
+ bpy.types.Scene.unreal_levelsequence_reference = bpy.props.StringProperty(
+ name = "Unreal LevelSequence reference",
+ description = "Copy Reference from unreal ine Content Browser",
+ maxlen = 512,
+ default = "LevelSequence'/Game/ImportedFbx/MySequence.MySequence'")
+
+ def draw(self, context):
+ scn = context.scene
+
+ #Sub folder
+ propsSub = self.layout.row()
+ propsSub = propsSub.column()
+ propsSub.prop(scn, 'unreal_import_location', icon='FILE_FOLDER')
+ propsSub.prop(scn, 'unreal_levelsequence_reference', icon='FILE_FOLDER')
+
+
+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"
+
+ class UnrealExportedAsset(bpy.types.PropertyGroup):
+ #[AssetName , AssetType , ExportPath, ExportTime]
+ assetName = StringProperty(default="None")
+ assetType = StringProperty(default="None") #return from GetAssetType()
+ exportPath = StringProperty(default="None")
+ exportTime = FloatProperty(default=0)
+ object = PointerProperty(type=bpy.types.Object)
+
+ bpy.utils.register_class(UnrealExportedAsset)
+ bpy.types.Scene.UnrealExportedAssetsList = CollectionProperty(
+ type=UnrealExportedAsset
+ )
+
+ class UnrealPotentialError(bpy.types.PropertyGroup):
+ type = IntProperty(default=0) #0:Info, 1:Warning, 2:Error
+ object = PointerProperty(type=bpy.types.Object)
+ itemName = StringProperty(default="None")
+ text = StringProperty(default="Unknown")
+ correctRef = StringProperty(default="None")
+ correctlabel = StringProperty(default="Fix it !")
+ correctDesc = StringProperty(default="Correct target error")
+
+ bpy.utils.register_class(UnrealPotentialError)
+ bpy.types.Scene.potentialErrorList = CollectionProperty(
+ type=UnrealPotentialError
+ )
+
+ class ShowAssetToExport(bpy.types.Operator):
+ bl_label = "Show asset(s)"
+ bl_idname = "object.showasset"
+ bl_description = "Click to show assets that are to be exported."
+
+ def execute(self, context):
+ obj = context.object
+ assets = GetFinalAssetToExport()
+ popup_title = "Assets list"
+ if len(assets) > 0:
+ popup_title = str(len(assets))+' asset(s) will be exported.'
+ else:
+ popup_title = 'No exportable assets were found.'
+
+ def draw(self, context):
+ col = self.layout.column()
+ for asset in assets:
+ row = col.row()
+ if asset[0] is not None:
+ if asset[1] is not None:
+ row.label(" --> "+asset[1].name+" ("+asset[2]+")")
+ else:
+ row.label("- "+asset[0].name+" ("+asset[2]+")")
+ else:
+ row.label("- ("+asset[2]+")")
+ bpy.context.window_manager.popup_menu(draw, title=popup_title, icon='EXTERNAL_DATA')
+ return {'FINISHED'}
+
+
+ class CheckPotentialErrorPopup(bpy.types.Operator):
+ bl_label = "Check potential errors"
+ bl_idname = "object.checkpotentialerror"
+ bl_description = "Check potential errors"
+ correctedProperty = 0
+
+ class CorrectButton(bpy.types.Operator):
+ bl_label = "Fix it !"
+ bl_idname = "object.fixit"
+ bl_description = "Correct target error"
+ errorIndex = bpy.props.IntProperty(default=-1)
+
+ def execute(self, context):
+ result = TryToCorrectPotentialError(self.errorIndex)
+ self.report({'INFO'}, result)
+ return {'FINISHED'}
+
+ def execute(self, context):
+ self.report({'INFO'}, "ok")
+ return {'FINISHED'}
+
+ def invoke(self, context, event):
+ def CorrectBadProperty():
+ #Corrects bad properties
+ UpdatedProp = 0
+ for obj in GetAllCollisionAndSocketsObj():
+ if obj.ExportEnum == "export_recursive":
+ obj.ExportEnum = "auto"
+ UpdatedProp += 1
+ return UpdatedProp
+
+ def UpdateNameHierarchy():
+ #Updates hierarchy names
+ for obj in GetAllCollisionAndSocketsObj():
+ if fnmatch.fnmatchcase(obj.name, "UBX*"):
+ ConvertToUe4SubObj("Box", [obj])
+ if fnmatch.fnmatchcase(obj.name, "UCP*"):
+ ConvertToUe4SubObj("Capsule", [obj])
+ if fnmatch.fnmatchcase(obj.name, "USP*"):
+ ConvertToUe4SubObj("Sphere", [obj])
+ if fnmatch.fnmatchcase(obj.name, "UCX*"):
+ ConvertToUe4SubObj("Convex", [obj])
+ if fnmatch.fnmatchcase(obj.name, "SOCKET*"):
+ ConvertToUe4SubObj("Socket", [obj])
+
+ self.correctedProperty = CorrectBadProperty()
+ UpdateNameHierarchy()
+ UpdateUnrealPotentialError()
+ wm = context.window_manager
+ return wm.invoke_popup(self, width = 900)
+
+ def check(self, context):
+ return True
+
+ def draw(self, context):
+
+
+ layout = self.layout
+ if len(bpy.context.scene.potentialErrorList) > 0 :
+ popup_title = str(len(bpy.context.scene.potentialErrorList))+" potential error(s) found!"
+ else:
+ popup_title = "No potential error to correct!"
+
+ if self.correctedProperty > 0 :
+ CheckInfo = str(self.correctedProperty) + " properties corrected."
+ else:
+ CheckInfo = "no properties to correct."
+
+ layout.label(popup_title)
+ layout.label("Hierarchy names updated and " + CheckInfo)
+ layout.separator()
+ row = layout.row()
+ col = row.column()
+ for x in range(len(bpy.context.scene.potentialErrorList)):
+ error = bpy.context.scene.potentialErrorList[x]
+
+ myLine = col.box().split(percentage = 0.85 )
+ #myLine = col.split(percentage = 0.85 )
+ #----
+ if error.type == 0:
+ msgType = 'INFO'
+ msgIcon = 'INFO'
+ elif error.type == 1:
+ msgType = 'WARNING'
+ msgIcon = 'ERROR'
+ elif error.type == 2:
+ msgType = 'ERROR'
+ msgIcon = 'CANCEL'
+ #----
+ errorFullMsg = msgType+": "+error.text
+ myLine.label(text=errorFullMsg, icon=msgIcon)
+ if error.correctRef != "None":
+ props = myLine.operator("object.fixit", text=error.correctlabel)
+ props.errorIndex = x
+
+
+ class ExportForUnrealEngineButton(bpy.types.Operator):
+ bl_label = "Export for UnrealEngine 4"
+ bl_idname = "object.exportforunreal"
+ bl_description = "Export all assets of this scene."
+
+ def execute(self, context):
+ scene = bpy.context.scene
+ def GetIfOneTypeCheck():
+ if (scene.static_export
+ or scene.skeletal_export
+ or scene.anin_export
+ or scene.pose_export
+ or scene.camera_export):
+ return True
+ else:
+ return False
+
+ if GetIfOneTypeCheck():
+ #Primary check if file is saved to avoid windows PermissionError
+ if bpy.data.is_saved:
+ scene.UnrealExportedAssetsList.clear()
+ start_time = time.process_time()
+ bfu_exportasset.ExportForUnrealEngine()
+ bfu_writetext.WriteAllTextFiles()
+
+ if len(scene.UnrealExportedAssetsList) > 0:
+ self.report({'INFO'}, "Export of "+str(len(scene.UnrealExportedAssetsList))+
+ " asset(s) has been finalized in "+str(time.process_time()-start_time)+" sec. Look in console for more info.")
+ print("========================= Exported asset(s) =========================")
+ print("")
+ for line in bfu_writetext.WriteExportLog().splitlines():
+ print(line)
+ print("")
+ print("========================= ... =========================")
+ else:
+ self.report({'WARNING'}, "Not found assets. with \"Export and child\" properties.")
+ else:
+ self.report({'WARNING'}, "Please save this blend file before export")
+ else:
+ self.report({'WARNING'}, "No asset type is checked.")
+ return {'FINISHED'}
+
+
+ #Categories :
+ bpy.types.Scene.static_export = bpy.props.BoolProperty(
+ name = "StaticMesh(s)",
+ description = "Check mark to export StaticMesh(es)",
+ default = True
+ )
+
+ bpy.types.Scene.skeletal_export = bpy.props.BoolProperty(
+ name = "SkeletalMesh(s)",
+ description = "Check mark to export SkeletalMesh(es)",
+ default = True
+ )
+
+ bpy.types.Scene.anin_export = bpy.props.BoolProperty(
+ name = "Animation(s)",
+ description = "Check mark to export Animation(s)",
+ default = True
+ )
+
+ bpy.types.Scene.pose_export = bpy.props.BoolProperty(
+ name = "Pose(s)",
+ description = "Check mark to export Pose(s)",
+ default = True
+ )
+
+ bpy.types.Scene.camera_export = bpy.props.BoolProperty(
+ name = "Camera(s)",
+ description = "Check mark to export Camera(s)",
+ default = False
+ )
+
+ #Additional file
+ bpy.types.Scene.text_exportLog = bpy.props.BoolProperty(
+ name = "Export Log",
+ description = "Check mark to write export log in file",
+ default = True
+ )
+
+ bpy.types.Scene.text_ImportAssetScript = bpy.props.BoolProperty(
+ name = "Import assets script",
+ description = "Check mark to write import assets script in file",
+ default = True
+ )
+
+ bpy.types.Scene.text_ImportSequenceScript = bpy.props.BoolProperty(
+ name = "Import sequence script",
+ description = "Check mark to write import sequence script in file",
+ default = True
+ )
+
+
+ def draw(self, context):
+ scn = context.scene
+
+ #Categories :
+ layout = self.layout
+ row = layout.row()
+ col = row.column()
+
+ #Assets
+ AssetsCol = row.column()
+ AssetsCol.label("Asset types to export", icon='EXTERNAL_DATA')
+ AssetsCol.prop(scn, 'static_export')
+ AssetsCol.prop(scn, 'skeletal_export')
+ AssetsCol.prop(scn, 'anin_export')
+ AssetsCol.prop(scn, 'pose_export')
+ AssetsCol.prop(scn, 'camera_export')
+ layout.separator()
+ #Additional file
+ FileCol = row.column()
+ FileCol.label("Additional file", icon='EXTERNAL_DATA')
+ FileCol.prop(scn, 'text_exportLog')
+ FileCol.prop(scn, 'text_ImportAssetScript')
+ FileCol.prop(scn, 'text_ImportSequenceScript')
+
+ #Feedback info :
+ AssetNum = len(GetFinalAssetToExport())
+ AssetInfo = layout.row().box().split(percentage = 0.75 )
+ AssetFeedback = str(AssetNum) + " Asset(s) will be exported."
+ AssetInfo.label( AssetFeedback, icon='INFO')
+ AssetInfo.operator("object.showasset")
+ #Export button :
+ checkButton = layout.row()
+ checkButton.operator("object.checkpotentialerror", icon='FILE_TICK')
+ exportButton = layout.row()
+ exportButton.scale_y = 2.0
+ exportButton.operator("object.exportforunreal", icon='EXPORT')
+
+
+#############################[...]#############################
+
+
+def register():
+ bpy.utils.register_module(__name__)
+
+def unregister():
+ bpy.utils.unregister_module(__name__)
\ No newline at end of file
diff --git a/blender-for-unrealengine/bfu_basics.py b/blender-for-unrealengine/bfu_basics.py
new file mode 100644
index 00000000..2ed68079
--- /dev/null
+++ b/blender-for-unrealengine/bfu_basics.py
@@ -0,0 +1,79 @@
+#====================== 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 .
+# All rights reserved.
+#
+#======================= END GPL LICENSE BLOCK =============================
+
+import os
+import bpy
+from mathutils import Vector
+from mathutils import Quaternion
+
+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 GetChilds(obj):
+ #Get all direct childs of a object
+
+ ChildsObj = []
+ for childObj in bpy.data.objects:
+ pare = childObj.parent
+ if pare is not None:
+ if pare.name == obj.name:
+ ChildsObj.append(childObj)
+
+ return ChildsObj
+
+
+def GetRecursiveChilds(obj):
+ #Get all recursive childs of a object
+
+ saveObj = []
+ for newobj in GetChilds(obj):
+ for childs in GetRecursiveChilds(newobj):
+ saveObj.append(childs)
+ saveObj.append(newobj)
+ return saveObj
+
+
+def VerifiDirs(directory):
+ #Check and create a folder if it does not exist
+
+ if not os.path.exists(directory):
+ os.makedirs(directory)
+
+
+def ValidFilename(filename):
+ # remove not allowed characters
+ import string
+ valid_chars = "-_.() %s%s" % (string.ascii_letters, string.digits)
+ filename = ''.join(c for c in filename if c in valid_chars)
+ return filename
+
+
+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))
\ No newline at end of file
diff --git a/blender-for-unrealengine/bfu_exportasset.py b/blender-for-unrealengine/bfu_exportasset.py
new file mode 100644
index 00000000..300bead5
--- /dev/null
+++ b/blender-for-unrealengine/bfu_exportasset.py
@@ -0,0 +1,323 @@
+#====================== 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 .
+# All rights reserved.
+#
+#======================= END GPL LICENSE BLOCK =============================
+
+
+import bpy
+import time
+import math
+from .bfu_basics import *
+from .bfu_utils import *
+
+import importlib
+from . import bfu_writetext
+importlib.reload(bfu_writetext)
+
+def ExportSingleFbxAnimation(dirpath, filename, obj, targetAction, actionType):
+ #Export a single animation or pose
+
+
+ scene = bpy.context.scene
+ filename = ValidFilename(filename)
+ curr_time = time.process_time()
+ print(obj.name)
+ UserAction = obj.animation_data.action #Save current action
+
+ if bpy.ops.object.mode_set.poll():
+ bpy.ops.object.mode_set(mode='OBJECT')
+ originalLoc = Vector((0,0,0))
+ originalLoc = originalLoc + obj.location #Save object 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
+ scene.frame_start = GetDesiredActionStartEndTime(obj, targetAction)[0]
+ scene.frame_end = GetDesiredActionStartEndTime(obj, targetAction)[1]
+
+ absdirpath = bpy.path.abspath(dirpath)
+ VerifiDirs(absdirpath)
+ fullpath = os.path.join( absdirpath , filename )
+
+ bpy.ops.export_scene.fbx(
+ filepath=fullpath,
+ check_existing=False,
+ version='BIN7400',
+ use_selection=True,
+ object_types={'ARMATURE', 'MESH'},
+ add_leaf_bones=False,
+ use_armature_deform_only=obj.exportDeformOnly,
+ bake_anim=True,
+ bake_anim_use_nla_strips=False,
+ bake_anim_use_all_actions=False,
+ bake_anim_force_startend_keying=True,
+ bake_anim_step=obj.SampleAnimForExport,
+ bake_anim_simplify_factor=obj.SimplifyAnimForExport
+ )
+ obj.location = originalLoc #Resets previous object location
+ ResetArmaturePose(obj)
+ obj.animation_data.action = UserAction #Resets previous action
+ exportTime = time.process_time()-curr_time
+
+ MyAsset = scene.UnrealExportedAssetsList.add()
+ MyAsset.assetName = filename
+ MyAsset.assetType = actionType
+ MyAsset.exportPath = absdirpath
+ MyAsset.exportTime = exportTime
+ MyAsset.object = obj
+ return MyAsset
+
+
+def ExportSingleFbxMesh(dirpath, filename, obj):
+ #Export a single Mesh
+
+ scene = bpy.context.scene
+ filename = ValidFilename(filename)
+ curr_time = time.process_time()
+ if bpy.ops.object.mode_set.poll():
+ bpy.ops.object.mode_set(mode = 'OBJECT')
+ originalLoc = Vector((0,0,0))
+ originalLoc = originalLoc + obj.location #Save current object location
+ obj.location = (0,0,0) #Moves object to the center of the scene for export
+ #Set socket scale for Unreal
+ for socket in GetAllChildSocket(obj):
+ socket.delta_scale*=0.01
+
+ SelectParentAndDesiredChilds(obj)
+ absdirpath = bpy.path.abspath(dirpath)
+ VerifiDirs(absdirpath)
+ fullpath = os.path.join( absdirpath , filename )
+ meshType = GetAssetType(obj)
+
+ object_types={'ARMATURE', 'CAMERA', 'EMPTY', 'LAMP', 'MESH', 'OTHER'}
+ if meshType == "StaticMesh":
+ #Dont export ARMATURE with static mesh
+ object_types={'CAMERA', 'EMPTY', 'LAMP', 'MESH', 'OTHER'}
+
+ bpy.ops.export_scene.fbx(filepath=fullpath,
+ check_existing=False,
+ version='BIN7400',
+ use_selection=True,
+ object_types=object_types,
+ mesh_smooth_type="FACE",
+ add_leaf_bones=False,
+ use_armature_deform_only=obj.exportDeformOnly,
+ bake_anim=False
+ )
+
+ obj.location = originalLoc #Resets previous object location
+ exportTime = time.process_time()-curr_time
+
+ #Reset socket scale
+ for socket in GetAllChildSocket(obj):
+ socket.delta_scale*=100
+
+ MyAsset = scene.UnrealExportedAssetsList.add()
+ MyAsset.assetName = filename
+ MyAsset.assetType = meshType
+ MyAsset.exportPath = absdirpath
+ MyAsset.exportTime = exportTime
+ MyAsset.object = obj
+ return MyAsset
+
+
+def ExportSingleFbxCamera(dirpath, filename, obj):
+ #Export single camera
+
+ scene = bpy.context.scene
+ filename = ValidFilename(filename)
+ if obj.type != 'CAMERA':
+ return;
+ curr_time = time.process_time()
+ if bpy.ops.object.mode_set.poll():
+ bpy.ops.object.mode_set(mode = 'OBJECT')
+ bpy.ops.object.select_all(action='DESELECT')
+
+ #Select and rescale camera for export
+ obj.select = True
+ scene.objects.active = obj
+ obj.delta_scale*=0.01
+ if obj.animation_data is not None:
+ action = obj.animation_data.action
+ scene.frame_start = GetDesiredActionStartEndTime(obj, action)[0]
+ scene.frame_end = GetDesiredActionStartEndTime(obj, action)[1]
+
+ absdirpath = bpy.path.abspath(dirpath)
+ VerifiDirs(absdirpath)
+ fullpath = os.path.join( absdirpath , filename )
+
+ bpy.ops.export_scene.fbx(
+ filepath=fullpath,
+ check_existing=False,
+ axis_forward = ('-Z'), #-Z
+ axis_up = ("Y"), #Y
+ version='BIN7400',
+ use_selection=True,
+ object_types={'CAMERA'},
+ add_leaf_bones=False,
+ use_armature_deform_only=obj.exportDeformOnly,
+ bake_anim=True,
+ bake_anim_use_nla_strips=False,
+ bake_anim_use_all_actions=False,
+ bake_anim_force_startend_keying=True,
+ bake_anim_step=obj.SampleAnimForExport,
+ bake_anim_simplify_factor=obj.SimplifyAnimForExport
+ )
+
+ #Reset camera scale
+ obj.delta_scale*=100
+
+ exportTime = time.process_time()-curr_time
+
+ MyAsset = scene.UnrealExportedAssetsList.add()
+ MyAsset.assetName = filename
+ MyAsset.assetType = "Camera"
+ MyAsset.exportPath = absdirpath
+ MyAsset.exportTime = exportTime
+ MyAsset.object = obj
+ return MyAsset
+
+def ExportSingleAdditionalTrackCamera(dirpath, filename, obj):
+ #Export additional camera track for ue4
+ #FocalLength
+ #FocusDistance
+ #Aperture
+
+ absdirpath = bpy.path.abspath(dirpath)
+ VerifiDirs(absdirpath)
+ CameraAdditionalTrack = bfu_writetext.WriteSingleCameraAdditionalTrack(obj)
+ return bfu_writetext.ExportSingleText(CameraAdditionalTrack, absdirpath, filename)
+
+def ExportAllAssetByList(targetobjects):
+ #Export all objects that need to be exported from a list
+
+
+ if len(targetobjects) < 1:
+ return
+
+ scene = bpy.context.scene
+ wm = bpy.context.window_manager
+ wm.progress_begin(0, len(GetFinalAssetToExport()))
+
+ def UpdateProgress():
+ wm.progress_update(len(scene.UnrealExportedAssetsList))
+ UpdateProgress()
+
+ for obj in targetobjects:
+ if obj.ExportEnum == "export_recursive":
+
+ #Camera
+ if GetAssetType(obj) == "Camera" and scene.camera_export:
+ exportDir = os.path.join( scene.export_camera_file_path, obj.exportFolderName )
+ UserStartFrame = scene.frame_start #Save current start frame
+ UserEndFrame = scene.frame_end #Save current end frame
+ ExportSingleFbxCamera(exportDir, GetObjExportFileName(obj), obj)
+
+ ExportSingleAdditionalTrackCamera(exportDir, GetCameraTrackFileName(obj), obj)
+ scene.frame_start = UserStartFrame #Resets previous start frame
+ scene.frame_end = UserEndFrame #Resets previous end frame
+ UpdateProgress()
+
+ #StaticMesh
+ if GetAssetType(obj) == "StaticMesh" and scene.static_export:
+ exportDir = os.path.join( scene.export_static_file_path, obj.exportFolderName )
+ ExportSingleFbxMesh(exportDir, GetObjExportFileName(obj), obj)
+ UpdateProgress()
+
+ if GetAssetType(obj) == "SkeletalMesh":
+ exportDir = os.path.join( scene.export_skeletal_file_path , obj.exportFolderName , obj.name )
+ #SkeletalMesh
+ if scene.skeletal_export:
+ ExportSingleFbxMesh(exportDir, GetObjExportFileName(obj), obj)
+ UpdateProgress()
+
+ for action in GetActionToExport(obj):
+ animExportDir = os.path.join( exportDir, scene.anim_subfolder_name )
+ animType = GetActionType(action)
+
+ #Animation
+ if animType == "Animation" and bpy.context.scene.anin_export == True:
+ UserStartFrame = scene.frame_start #Save current start frame
+ UserEndFrame = scene.frame_end #Save current end frame
+ ExportSingleFbxAnimation(animExportDir, GetActionExportFileName(obj, action), obj, action, "Animation")
+ scene.frame_start = UserStartFrame #Resets previous start frame
+ scene.frame_end = UserEndFrame #Resets previous end frame
+ UpdateProgress()
+
+ #pose
+ if animType == "Pose" and bpy.context.scene.pose_export == True:
+ UserStartFrame = scene.frame_start #Save current start frame
+ UserEndFrame = scene.frame_end #Save current end frame
+ ExportSingleFbxAnimation(animExportDir, GetActionExportFileName(obj, action), obj, action, "Pose")
+ scene.frame_start = UserStartFrame #Resets previous start frame
+ scene.frame_end = UserEndFrame #Resets previous end frame
+ UpdateProgress()
+
+ wm.progress_end()
+
+
+
+
+def PrepareAndSaveDataForExport():
+
+ scene = bpy.context.scene
+ #----------------------------------------Save data
+ UserObjHide = []
+ UserObjHideSelect = []
+ for obj in scene.objects: #Save previous object visibility
+ UserObjHide.append(obj.hide)
+ UserObjHideSelect.append(obj.hide_select)
+ obj.hide = False
+ obj.hide_select = False
+
+ LayerVisibility = []
+ for x in range(20): #Save previous layer visibility
+ LayerVisibility.append(scene.layers[x])
+ scene.layers[x] = True
+
+ if obj is None:
+ scene.objects.active = bpy.data.objects[0]
+
+ UserActive = bpy.context.active_object #Save current active object
+ UserMode = None
+ if UserActive and UserActive.mode != 'OBJECT' and bpy.ops.object.mode_set.poll():
+ UserMode = UserActive.mode #Save current mode
+ bpy.ops.object.mode_set(mode='OBJECT')
+ UserSelected = bpy.context.selected_objects #Save current selected objects
+ #----------------------------------------
+
+
+ ExportAllAssetByList(GetAllobjectsByExportType("export_recursive"))
+
+
+ #----------------------------------------Reset data
+ for x in range(20):
+ scene.layers[x] = LayerVisibility[x]
+ bpy.ops.object.select_all(action='DESELECT')
+ for obj in UserSelected: obj.select = True #Resets previous selected object
+ scene.objects.active = UserActive #Resets previous active object
+ if UserActive and UserMode and bpy.ops.object.mode_set.poll():
+ bpy.ops.object.mode_set(mode=UserMode) #Resets previous mode
+ for x, obj in enumerate(scene.objects):
+ obj.hide = UserObjHide[x] #Resets previous object visibility
+ obj.hide_select = UserObjHideSelect[x] #Resets previous object visibility(select)
+ #----------------------------------------
+
+def ExportForUnrealEngine():
+ PrepareAndSaveDataForExport()
\ No newline at end of file
diff --git a/blender-for-unrealengine/bfu_utils.py b/blender-for-unrealengine/bfu_utils.py
new file mode 100644
index 00000000..811dc71d
--- /dev/null
+++ b/blender-for-unrealengine/bfu_utils.py
@@ -0,0 +1,678 @@
+#====================== 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 .
+# All rights reserved.
+#
+#======================= END GPL LICENSE BLOCK =============================
+
+
+import bpy
+import fnmatch
+import bmesh
+from .bfu_basics import *
+
+
+
+def GetAllobjectsByExportType(exportType):
+ #Find all objects with a specific ExportEnum property
+
+ targetObj = []
+ for obj in bpy.context.scene.objects:
+ prop = obj.ExportEnum
+ if prop == exportType:
+ targetObj.append(obj)
+ return(targetObj)
+
+
+def GetAllCollisionAndSocketsObj():
+ #Get any object that can be understood as a collision or a socket by unreal
+
+ colObjs = [obj for obj in bpy.context.scene.objects if
+ fnmatch.fnmatchcase(obj.name, "UBX*") or
+ fnmatch.fnmatchcase(obj.name, "UCP*") or
+ fnmatch.fnmatchcase(obj.name, "USP*") or
+ fnmatch.fnmatchcase(obj.name, "UCX*") or
+ fnmatch.fnmatchcase(obj.name, "SOCKET*")]
+ return colObjs
+
+
+def GetExportDesiredChilds(obj):
+ #Get only all child objects that must be exported with parent object
+
+ DesiredObj = []
+ for child in GetRecursiveChilds(obj):
+ print(child.name)
+ if child.ExportEnum != "dont_export":
+ DesiredObj.append(child)
+ return DesiredObj
+
+
+def GetAllChildSocket(targetObj):
+ socket = [obj for obj in GetRecursiveChilds(targetObj) if
+ fnmatch.fnmatchcase(obj.name, "SOCKET*")]
+ return socket
+
+
+def GetAllCollisionObj():
+ #Get any object that can be understood as a collision or a socket by unreal
+
+ colObjs = [obj for obj in bpy.context.scene.objects if
+ fnmatch.fnmatchcase(obj.name, "UBX*") or
+ fnmatch.fnmatchcase(obj.name, "UCP*") or
+ fnmatch.fnmatchcase(obj.name, "USP*") or
+ fnmatch.fnmatchcase(obj.name, "UCX*")]
+ return colObjs
+
+
+def GetActionToExport(obj):
+ #Returns only the actions that will be exported with the Armature
+
+ if obj.animation_data is None:
+ return []
+
+ TargetActionToExport = [] #Action list
+ if obj.exportActionEnum == "dont_export":
+ return []
+ elif obj.exportActionEnum == "export_specific_list":
+ for action in bpy.data.actions:
+ for targetAction in obj.exportActionList:
+ if targetAction.use == True:
+ if targetAction.name == action.name:
+ TargetActionToExport.append(action)
+
+ elif obj.exportActionEnum == "export_specific_prefix":
+ for action in bpy.data.actions:
+ if fnmatch.fnmatchcase(action.name, obj.PrefixNameToExport+"*"):
+ TargetActionToExport.append(action)
+ elif obj.exportActionEnum == "export_auto":
+ objBoneNames = [bone.name for bone in obj.data.bones]
+ for action in bpy.data.actions:
+ actionBoneNames = [group.name for group in action.groups]
+ if ChecksRelationship(objBoneNames, actionBoneNames):
+ TargetActionToExport.append(action)
+
+ return TargetActionToExport
+
+
+def GetDesiredActionStartEndTime(obj, action):
+ #Returns desired action or camera anim start/end time
+ #Return start with index 0 and end with index 1
+
+ scene = bpy.context.scene
+ if obj.type == "CAMERA":
+ startTime = scene.frame_start
+ endTime = scene.frame_end
+ return (startTime,endTime)
+
+ elif obj.AnimStartEndTimeEnum == "with_keyframes":
+ startTime = action.frame_range.x #GetFirstActionFrame
+ endTime = action.frame_range.y #GetLastActionFrame
+ return (startTime,endTime)
+
+ elif obj.AnimStartEndTimeEnum == "with_sceneframes":
+ startTime = scene.frame_start
+ endTime = scene.frame_end
+ return (startTime,endTime)
+
+ elif obj.AnimStartEndTimeEnum == "with_customframes":
+ startTime = obj.AnimCustomStartTime
+ endTime = obj.AnimCustomEndTime
+ return (startTime,endTime)
+
+
+def GetActionType(action):
+ #return action type
+
+ if action.frame_range.y - action.frame_range.x == 1:
+ return "Pose"
+ return "Animation"
+
+
+def GetAssetType(obj):
+ #Return asset type of a object
+
+ if obj.type == "ARMATURE":
+ if obj.ForceStaticMesh == False:
+ return "SkeletalMesh"
+ return "StaticMesh"
+ elif obj.type == "CAMERA":
+ return "Camera"
+ else:
+ return "StaticMesh"
+
+
+def CheckIsCollision(target):
+ #Return true if obj is a collision
+ for obj in GetAllCollisionObj():
+ if obj == target:
+ return True
+ return False
+
+
+def SelectParentAndDesiredChilds(obj):
+ #Selects only all child objects that must be exported with parent object
+
+ bpy.ops.object.select_all(action='DESELECT')
+ for selectObj in GetExportDesiredChilds(obj):
+ selectObj.select = True
+ obj.select = True
+ bpy.context.scene.objects.active = obj
+
+
+def GetFinalAssetToExport():
+ #Returns all assets that will be exported
+
+ scene = bpy.context.scene
+ TargetAassetToExport = [] #Obj, Action, type
+
+
+ for obj in GetAllobjectsByExportType("export_recursive"):
+ if GetAssetType(obj) == "SkeletalMesh":
+ #SkeletalMesh
+ if scene.skeletal_export:
+ TargetAassetToExport.append((obj,None,"SkeletalMesh"))
+ for action in GetActionToExport(obj):
+ #Animation
+ if scene.anin_export:
+ if GetActionType(action) == "Animation":
+ TargetAassetToExport.append((obj,action,"Animation"))
+ #Pose
+ if scene.pose_export:
+ if GetActionType(action) == "Pose":
+ TargetAassetToExport.append((obj,action,"Pose"))
+ #Camera
+ if GetAssetType(obj) == "Camera" and scene.camera_export:
+ TargetAassetToExport.append((obj,None,"Camera"))
+
+ #StaticMesh
+ if GetAssetType(obj) == "StaticMesh" and scene.static_export:
+ TargetAassetToExport.append((obj,None,"StaticMesh"))
+
+ return TargetAassetToExport
+
+
+def GetObjExportFileName(obj):
+ #Generate assset file name
+
+ scene = bpy.context.scene
+ assetType = GetAssetType(obj)
+ if assetType == "Camera":
+ return scene.camera_prefix_export_name+obj.name+".fbx"
+ elif assetType == "StaticMesh":
+ return scene.static_prefix_export_name+obj.name+".fbx"
+ elif assetType == "SkeletalMesh":
+ return scene.skeletal_prefix_export_name+obj.name+".fbx"
+ else:
+ return None
+
+
+def GetActionExportFileName(obj, action):
+ #Generate action file name
+
+ scene = bpy.context.scene
+ animType = GetActionType(action)
+ if animType == "Animation":
+ return scene.anim_prefix_export_name+obj.name+"_"+action.name+".fbx"
+ elif animType == "Pose":
+ return scene.pose_prefix_export_name+obj.name+"_"+action.name+".fbx"
+ else:
+ return None
+
+
+def GetCameraTrackFileName(camera):
+ #Generate additional camera track file name
+
+ scene = bpy.context.scene
+ return scene.camera_prefix_export_name+camera.name+"_AdditionalTrack.ini"
+
+
+def GenerateUe4Name(name):
+ #Generate a new name with 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 = 1
+ newName = ""
+ if IsValidName(name):
+ return name
+ else:
+ while valid == False:
+ newName = name+"_"+str(number)
+ if IsValidName(newName):
+ return newName
+ else:
+ number = number+1
+ return newName
+
+
+def ConvertToUe4SubObj(collisionType, objsToConvert, useActiveAsOwner=False):
+ #Convect obj to ue4 sub objects (Collisions Shapes or Socket)
+
+ ConvertedObjs = []
+
+ def ApplyConvexHull(obj):
+ mesh = obj.data
+ if not mesh.is_editmode:
+ bm = bmesh.new()
+ bm.from_mesh(mesh) #Mesh to Bmesh
+ acb = bmesh.ops.convex_hull(bm, input=bm.verts, use_existing_faces=True)
+ #acb = bmesh.ops.recalc_face_normals(bm, faces=bm.faces)
+ bm.to_mesh(mesh) #BMesh to Mesh
+
+ #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_"
+
+ 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
+ mat.use_nodes = False
+ if bpy.context.scene.render.engine == 'CYCLES':
+ #sets up the nodes to create a transparent material with GLSL mat in Cycle
+ mat.use_nodes = True
+ node_tree = mat.node_tree
+ nodes = node_tree.nodes
+ nodes.clear()
+ out = nodes.new('ShaderNodeOutputMaterial')
+ out.location = (0,0)
+ mix = nodes.new('ShaderNodeMixShader')
+ mix.location = (-200,000)
+ mix.inputs[0].default_value = (0.95)
+ diff = nodes.new('ShaderNodeBsdfDiffuse')
+ diff.location = (-400,100)
+ diff.inputs[0].default_value = (0, 0.6, 0, 1)
+ trans = nodes.new('ShaderNodeBsdfTransparent')
+ trans.location = (-400,-100)
+ trans.inputs[0].default_value = (0, 0.6, 0, 1)
+ node_tree.links.new(diff.outputs['BSDF'], mix.inputs[1])
+ node_tree.links.new(trans.outputs['BSDF'], mix.inputs[2])
+ node_tree.links.new(mix.outputs['Shader'], out.inputs[0])
+
+
+ #node
+
+ for obj in objsToConvert:
+ if useActiveAsOwner == True:
+ ownerObj = bpy.context.active_object
+ bpy.ops.object.parent_set(type='OBJECT', keep_transform=True)
+ else:
+ ownerObj = obj.parent
+ if ownerObj is not None:
+ if obj != ownerObj:
+ #Mesh
+ if obj.type == 'MESH':
+ ApplyConvexHull(obj)
+ obj.modifiers.clear()
+ obj.data
+ obj.data.materials.clear()
+ obj.active_material_index = 0
+ obj.data.materials.append(mat)
+ obj.name = GenerateUe4Name(prefixName+ownerObj.name)
+ obj.show_wire = True
+ obj.show_transparent = True
+ ConvertedObjs.append(obj)
+ #Socket
+ if obj.type == 'EMPTY' and collisionType == "Socket":
+ if ownerObj.type == 'MESH':
+ if not obj.name.startswith("SOCKET_"):
+ obj.name = GenerateUe4Name("SOCKET_"+obj.name)
+ ConvertedObjs.append(obj)
+ else:
+ print(obj.name+" is already a socket")
+ ConvertedObjs.append(obj)
+ return ConvertedObjs
+
+
+def UpdateUnrealPotentialError():
+ #Find and reset list of all potential error in scene
+
+
+ PotentialErrors = bpy.context.scene.potentialErrorList
+ PotentialErrors.clear()
+
+ #prepares the data to avoid unnecessary loops
+ objToCheck = []
+ for obj in GetAllobjectsByExportType("export_recursive"):
+ objToCheck.append(obj)
+ for obj2 in GetExportDesiredChilds(obj):
+ objToCheck.append(obj2)
+
+ MeshTypeToCheck = []
+ for obj in objToCheck:
+ if obj.type == 'MESH':
+ MeshTypeToCheck.append(obj)
+
+ MeshTypeWithoutCol = [] # is Mesh Type To Check Without Collision
+ for obj in MeshTypeToCheck:
+ if not CheckIsCollision(obj):
+ MeshTypeWithoutCol.append(obj)
+
+ def CheckObjType():
+ #Check if objects use a non-recommended type
+ for obj in objToCheck:
+ if obj.type == "SURFACE" or obj.type == "META" or obj.type == "FONT":
+ MyError = PotentialErrors.add()
+ MyError.type = 1
+ MyError.text = 'Object "'+obj.name+'" is a '+obj.type+'. The object of the type SURFACE, META and FONT is not recommended.'
+ MyError.object = obj
+ MyError.correctRef = "ConvertToMesh"
+ MyError.correctlabel = 'Convert to mesh'
+ def CheckShapeKeys():
+ for obj in MeshTypeToCheck:
+ if obj.data.shape_keys is not None:
+ #Check that no modifiers is destructive for the key shapes
+ if len(obj.data.shape_keys.key_blocks) > 0:
+ for modif in obj.modifiers:
+ if modif.type != "ARMATURE" :
+ MyError = PotentialErrors.add()
+ MyError.type = 2
+ MyError.object = obj
+ MyError.itemName = modif.name
+ MyError.text = 'In object "'+obj.name+'" the modifier '+modif.type+' named "'+modif.name+'" can destroy shape keys. Please use only Armature modifier with shape keys.'
+ MyError.correctRef = "RemoveModfier"
+ MyError.correctlabel = 'Remove modifier'
+
+ #Check that the key shapes are not out of bounds for Unreal
+ for key in obj.data.shape_keys.key_blocks:
+ #Min
+ if key.slider_min < -5:
+ MyError = PotentialErrors.add()
+ MyError.type = 1
+ MyError.object = obj
+ MyError.itemName = key.name
+ MyError.text = 'In object "'+obj.name+'" the shape key "'+key.name+'" is out of bounds for Unreal. The min range of must not be inferior to -5.'
+ MyError.correctRef = "SetKeyRangeMin"
+ MyError.correctlabel = 'Set min range to -5'
+
+ #Max
+ if key.slider_max > 5:
+ MyError = PotentialErrors.add()
+ MyError.type = 1
+ MyError.object = obj
+ MyError.itemName = key.name
+ MyError.text = 'In object "'+obj.name+'" the shape key "'+key.name+'" is out of bounds for Unreal. The max range of must not be superior to 5.'
+ MyError.correctRef = "SetKeyRangeMax"
+ MyError.correctlabel = 'Set max range to -5'
+
+ def CheckUVMaps():
+ #Check that the objects have at least one UV map valid
+ for obj in MeshTypeWithoutCol:
+ if len(obj.data.uv_layers) < 1:
+ MyError = PotentialErrors.add()
+ MyError.type = 1
+ MyError.text = 'Object "'+obj.name+'" does not have any UV Layer.'
+ MyError.object = obj
+ MyError.correctRef = "CreateUV"
+ MyError.correctlabel = 'Create Smart UV Project'
+
+ def CheckBadStaicMeshExportedLikeSkeletalMesh():
+ #Check if the correct object is defined as exportable
+ for obj in MeshTypeToCheck:
+ for modif in obj.modifiers:
+ if modif.type == "ARMATURE" :
+ if obj.ExportEnum == "export_recursive":
+ MyError = PotentialErrors.add()
+ MyError.type = 1
+ MyError.text = 'In object "'+obj.name+'" the modifier '+modif.type+' named "'+modif.name+'will not be applied when exported with StaticMesh assets.'
+
+ def CheckArmatureModNumber():
+ #check that there is no more than one Modifier ARMATURE at the same time
+ for obj in MeshTypeToCheck:
+ ArmatureModifiers = 0
+ for modif in obj.modifiers:
+ if modif.type == "ARMATURE" :
+ ArmatureModifiers = ArmatureModifiers + 1
+ if ArmatureModifiers > 1:
+ MyError = PotentialErrors.add()
+ MyError.type = 2
+ MyError.text = 'In object "'+obj.name+'" there are several Armature modifiers at the same time. Please use only one Armature modifier.'
+
+ def CheckArmatureModData():
+ #check the parameter of Modifier ARMATURE
+ for obj in MeshTypeToCheck:
+ for modif in obj.modifiers:
+ if modif.type == "ARMATURE" :
+ if modif.use_deform_preserve_volume == True:
+ MyError = PotentialErrors.add()
+ MyError.type = 2
+ MyError.text = 'In object "'+obj.name+'" the modifier '+modif.type+' named "'+modif.name+'". The parameter Preserve Volume must be set to False.'
+ MyError.object = obj
+ MyError.itemName = modif.name
+ MyError.correctRef = "PreserveVolume"
+ MyError.correctlabel = 'Set Preserve Volume to False'
+
+ def CheckArmatureBoneData():
+ #check the parameter of the ARMATURE bones
+ for obj in objToCheck:
+ if GetAssetType(obj) == "SkeletalMesh":
+ for bone in obj.data.bones:
+ if bone.bbone_segments > 1:
+ MyError = PotentialErrors.add()
+ MyError.type = 2
+ MyError.text = 'In object3 "'+obj.name+'" the bone named "'+bone.name+'". The parameter Bendy Bones / Segments must be set to 1.'
+ MyError.object = obj
+ MyError.itemName = bone.name
+ MyError.correctRef = "BoneSegments"
+ MyError.correctlabel = 'Set Bone Segments to 1'
+
+ if bone.use_inherit_scale == False:
+ MyError = PotentialErrors.add()
+ MyError.type = 2
+ MyError.text = 'In object2 "'+obj.name+'" the bone named "'+bone.name+'". The parameter Inherit Scale must be set to True.'
+ MyError.object = obj
+ MyError.itemName = bone.name
+ MyError.correctRef = "InheritScale"
+ MyError.correctlabel = 'Set Inherit Scale to True'
+
+ def CheckArmatureValidChild():
+ #Check that skeleton also has a mesh to export
+ for obj in objToCheck:
+ if GetAssetType(obj) == "SkeletalMesh":
+ childs = GetExportDesiredChilds(obj)
+ validChild = 0
+ for child in childs:
+ if child.type == "MESH":
+ validChild += 1
+ if validChild < 1:
+ MyError = PotentialErrors.add()
+ MyError.type = 2
+ MyError.text = 'Object "'+obj.name+'" is an Armature and does not have any valid children.'
+
+ def CheckVertexGroupWeight():
+ #Check that all vertex have a weight
+ for obj in objToCheck:
+ if GetAssetType(obj) == "SkeletalMesh":
+ childs = GetExportDesiredChilds(obj)
+ for child in childs:
+ if child.type == "MESH":
+ #Prepare data
+ VertexWithZeroWeight = 0
+ for vertex in child.data.vertices:
+ cumulateWeight = 0
+ if len(vertex.groups) > 0:
+ for group in vertex.groups:
+ cumulateWeight += group.weight
+ if not cumulateWeight > 0:
+ VertexWithZeroWeight += 1
+ else:
+ VertexWithZeroWeight += 1
+ #Result data
+ if VertexWithZeroWeight > 0:
+ MyError = PotentialErrors.add()
+ MyError.type = 1
+ MyError.text = 'Object named "'+child.name+'" contains '+str(VertexWithZeroWeight)+' vertex with zero cumulative weight.'
+
+
+ def CheckZeroScaleKeyframe():
+ #Check that animations do not use a invalid value
+ for action in bpy.data.actions:
+ for fcurve in action.fcurves:
+ if fcurve.data_path.split(".")[-1] == "scale":
+ for key in fcurve.keyframe_points:
+ xCurve, yCurve = key.co
+ if key.co[1] == 0:
+ MyError = PotentialErrors.add()
+ MyError.type = 2
+ MyError.text = 'In action "'+action.name+'" at frame '+str(key.co[0])+', the bone named "'+fcurve.data_path.split('"')[1]+'" has a zero value in scale transform. This is invalid in Unreal.'
+
+
+ CheckObjType()
+ CheckShapeKeys()
+ CheckUVMaps()
+ CheckBadStaicMeshExportedLikeSkeletalMesh()
+ CheckArmatureModNumber()
+ CheckArmatureModData()
+ CheckArmatureBoneData()
+ CheckArmatureValidChild()
+ CheckVertexGroupWeight()
+ CheckZeroScaleKeyframe()
+ return PotentialErrors
+
+
+def TryToCorrectPotentialError(errorIndex):
+ #Try to correct potential error
+
+ scene = bpy.context.scene
+ error = scene.potentialErrorList[errorIndex]
+ global successCorrect
+ successCorrect = False
+ #----------------------------------------Save data
+ UserActive = bpy.context.active_object #Save current active object
+ UserMode = None
+ if UserActive and UserActive.mode != 'OBJECT' and bpy.ops.object.mode_set.poll():
+ UserMode = UserActive.mode #Save current mode
+ bpy.ops.object.mode_set(mode='OBJECT')
+ UserSelected = bpy.context.selected_objects #Save current selected objects
+ #----------------------------------------
+ print("Start correct")
+ print("test START: "+error.correctRef)
+ def SelectObj(obj):
+ bpy.ops.object.select_all(action='DESELECT')
+ obj.select = True
+ bpy.context.scene.objects.active = obj
+
+
+ #Correction list
+
+ if error.correctRef == "ConvertToMesh":
+ obj = error.object
+ SelectObj(obj)
+ bpy.ops.object.convert(target='MESH')
+ successCorrect = True
+
+ print("testA: "+error.correctRef)
+ if error.correctRef == "SetKeyRangeMin":
+ obj = error.object
+ key = obj.data.shape_keys.key_blocks[error.itemName]
+ key.slider_min = -5
+ successCorrect = True
+
+ print("testB: "+error.correctRef)
+ if error.correctRef == "SetKeyRangeMax":
+ obj = error.object
+ key = obj.data.shape_keys.key_blocks[error.itemName]
+ key.slider_max = 5
+ successCorrect = True
+
+ if error.correctRef == "CreateUV":
+ obj = error.object
+ SelectObj(obj)
+ bpy.ops.uv.smart_project()
+ successCorrect = True
+
+ if error.correctRef == "RemoveModfier":
+ obj = error.object
+ mod = obj.modifiers[error.itemName]
+ obj.modifiers.remove(mod)
+ successCorrect = True
+
+ if error.correctRef == "PreserveVolume":
+ obj = error.object
+ mod = obj.modifiers[error.itemName]
+ mod.use_deform_preserve_volume = False
+ successCorrect = True
+
+ if error.correctRef == "BoneSegments":
+ obj = error.object
+ bone = obj.data.bones[error.itemName]
+ bone.bbone_segments = 1
+ successCorrect = True
+
+ if error.correctRef == "InheritScale":
+ obj = error.object
+ bone = obj.data.bones[error.itemName]
+ bone.use_inherit_scale = True
+ successCorrect = True
+
+ #----------------------------------------Reset data
+ bpy.ops.object.select_all(action='DESELECT')
+ for obj in UserSelected: obj.select = True #Resets previous selected object
+ scene.objects.active = UserActive #Resets previous active object
+ if UserActive and UserMode and bpy.ops.object.mode_set.poll():
+ bpy.ops.object.mode_set(mode=UserMode) #Resets previous mode
+ #----------------------------------------
+
+ if successCorrect == True:
+ scene.potentialErrorList.remove(errorIndex)
+ print("end correct, Error: " + error.correctRef)
+ scene.update()
+ return "Corrected"
+ print("end correct, Error not found")
+ return "Correct fail"
+
+ #Returns all assets that will be exported
+
+ scene = bpy.context.scene
+ TargetAassetToExport = [] #Obj, Action, type
+
+
+ for obj in GetAllobjectsByExportType("export_recursive"):
+ if GetAssetType(obj) == "SkeletalMesh":
+ #SkeletalMesh
+ if scene.skeletal_export:
+ TargetAassetToExport.append((obj,None,"SkeletalMesh"))
+ for action in GetActionToExport(obj):
+ #Animation
+ if scene.anin_export:
+ if GetActionType(action) == "Animation":
+ TargetAassetToExport.append((obj,action,"Animation"))
+ #Pose
+ if scene.pose_export:
+ if GetActionType(action) == "Pose":
+ TargetAassetToExport.append((obj,action,"Pose"))
+ #Camera
+ if GetAssetType(obj) == "Camera" and scene.camera_export:
+ TargetAassetToExport.append((obj,None,"Camera"))
+
+ #StaticMesh
+ if GetAssetType(obj) == "StaticMesh" and scene.static_export:
+ TargetAassetToExport.append((obj,None,"StaticMesh"))
+
+ return TargetAassetToExport
diff --git a/blender-for-unrealengine/bfu_writetext.py b/blender-for-unrealengine/bfu_writetext.py
new file mode 100644
index 00000000..e678ea6c
--- /dev/null
+++ b/blender-for-unrealengine/bfu_writetext.py
@@ -0,0 +1,476 @@
+#====================== 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 .
+# All rights reserved.
+#
+#======================= END GPL LICENSE BLOCK =============================
+
+
+import bpy
+import time
+from .bfu_basics import *
+from .bfu_utils import *
+
+
+def ExportSingleText(text, dirpath, filename):
+ #Export single text
+
+ filename = ValidFilename(filename)
+ curr_time = time.process_time()
+
+ absdirpath = bpy.path.abspath(dirpath)
+ VerifiDirs(absdirpath)
+ fullpath = os.path.join( absdirpath , filename )
+
+ with open(fullpath, "w") as file:
+ file.write(text)
+
+ exportTime = time.process_time()-curr_time
+ return([filename,"TextFile",absdirpath,exportTime]) #[AssetName , AssetType , ExportPath, ExportTime]
+
+
+def WriteExportLog():
+ #Write Export log with exported assets in scene.UnrealExportedAssetsList
+
+ def GetNumberByType(targetType):
+ foundNumber = 0
+ scene = bpy.context.scene
+ for assets in scene.UnrealExportedAssetsList:
+ if assets.assetType == targetType:
+ foundNumber = foundNumber + 1
+ return foundNumber
+
+ scene = bpy.context.scene
+ StaticNum = GetNumberByType("StaticMesh")
+ SkeletalNum = GetNumberByType("SkeletalMesh")
+ AnimNum = GetNumberByType("Animation")
+ PoseNum = GetNumberByType("Pose")
+ CameraNum = GetNumberByType("Camera")
+ OtherNum = len(scene.UnrealExportedAssetsList)-(StaticNum+SkeletalNum+AnimNum+PoseNum+CameraNum)
+ AssetNumberByType = str(StaticNum)+" StaticMesh(s) | "
+ AssetNumberByType += str(SkeletalNum)+" SkeletalMesh(s) | "
+ AssetNumberByType += str(AnimNum)+" Animation(s) | "
+ AssetNumberByType += str(PoseNum)+" Pose(s) | "
+ AssetNumberByType += str(CameraNum)+" Camera(s)"
+ AssetNumberByType += str(OtherNum)+" Other(s)" + "\n"
+
+ ExportLog = "..." + "\n"
+ ExportLog += AssetNumberByType
+ ExportLog += "\n"
+ for asset in scene.UnrealExportedAssetsList:
+ ExportLog +=("["+asset.assetType+"] -> "+"\""+asset.assetName+"\" exported in "+str(asset.exportTime)+" sec.\n")
+ ExportLog +=(asset.exportPath + "\n")
+ ExportLog += "\n"
+
+ return ExportLog
+
+
+def WriteImportAssetScript():
+ #Generate a script for import assets in Ue4
+ scene = bpy.context.scene
+
+ #Comment
+ ImportScript = "#This script was generated with the addons Blender for UnrealEngine : https://github.com/xavier150/Blender-For-UnrealEngine-Addons" + "\n"
+ ImportScript += "#It will import into Unreal Engine all the assets of type StaticMesh, SkeletalMesh, Animation and Pose" + "\n"
+ ImportScript += "#The script must be used in Unreal Engine Editor with UnrealEnginePython : https://github.com/20tab/UnrealEnginePython" + "\n"
+ ImportScript += "\n"
+ ImportScript += "import os.path" + "\n"
+ ImportScript += "import unreal_engine as ue" + "\n"
+ ImportScript += "from unreal_engine.classes import PyFbxFactory" + "\n"
+ ImportScript += "from unreal_engine.enums import EFBXImportType, EMaterialSearchLocation" + "\n"
+ ImportScript += "\n"
+
+ #Prepare var and process import
+ ImportScript += "#Prepare var and process import" + "\n"
+ ImportScript += "UnrealImportLocation = r'/Game/" + scene.unreal_import_location + "'" + "\n"
+ ImportScript += "ImportedAsset = []" + "\n"
+ ImportScript += "\n"
+ ImportScript += "print('========================= Import started ! =========================')" + "\n"
+ ImportScript += "\n"
+ ImportScript += "\n"
+ ImportScript += "\n"
+
+ #Import asset
+ for asset in scene.UnrealExportedAssetsList:
+ if (asset.assetType == "StaticMesh"
+ or asset.assetType == "SkeletalMesh"
+ or asset.assetType == "Animation"
+ or asset.assetType == "Pose"):
+
+ obj = asset.object
+ MeshRelatifImportLoc = obj.exportFolderName
+ AnimRelatifImportLoc = os.path.join( obj.exportFolderName, scene.anim_subfolder_name )
+ FbxLoc = (os.path.join(asset.exportPath, asset.assetName))
+ ImportScript += "################[ Import "+obj.name+" as "+asset.assetType+" type ]################" + "\n"
+
+ if asset.assetType == "StaticMesh":
+ ImportScript += "fbx_factory = PyFbxFactory()" + "\n"
+ ImportScript += "fbx_factory.ImportUI.bImportMaterials = True" + "\n"
+ ImportScript += "fbx_factory.ImportUI.bImportTextures = False" + "\n"
+ ImportScript += "fbx_factory.ImportUI.TextureImportData.MaterialSearchLocation = EMaterialSearchLocation." + obj.MaterialSearchLocation + "\n"
+ ImportScript += "fbx_factory.ImportUI.StaticMeshImportData.bCombineMeshes = True" + "\n"
+ ImportScript += "fbx_factory.ImportUI.StaticMeshImportData.bAutoGenerateCollision = False" + "\n"
+
+ if (obj.UseStaticMeshLODGroup == True):
+ ImportScript += "fbx_factory.ImportUI.StaticMeshImportData.StaticMeshLODGroup = '" + obj.StaticMeshLODGroup + "'" + "\n"
+ ImportScript += "fbx_factory.ImportUI.StaticMeshImportData.bGenerateLightmapUVs = " + str(obj.GenerateLightmapUVs) + "\n"
+
+ ImportScript += "FbxLoc = os.path.join(r'"+FbxLoc+"')" + "\n"
+ ImportScript += r"AssetImportLocation = os.path.join(UnrealImportLocation, r'"+MeshRelatifImportLoc+r"').replace('\\','/')" + "\n"
+ ImportScript += "AssetImportLocation = AssetImportLocation.rstrip('/')" + "\n"
+ #-----------Import
+ ImportScript += "try:" + "\n"
+ ImportScript += "\t" + "asset = fbx_factory.factory_import_object(FbxLoc, AssetImportLocation)" + "\n"
+ ImportScript += "\t" + "ImportedAsset.append(asset)" + "\n"
+ if (obj.UseStaticMeshLODGroup == True):
+ ImportScript += "\t" "asset.LODGroup = '" + obj.StaticMeshLODGroup + "'" + "\n"
+ if (obj.UseStaticMeshLightMapRes == True):
+ ImportScript += "\t" "asset.LightMapResolution = " + str(obj.StaticMeshLightMapRes) + "\n"
+ ImportScript += "except:" + "\n"
+ ImportScript += "\t" + "ImportedAsset.append('Import error for asset named \""+obj.name+"\" ')" + "\n"
+
+ if asset.assetType == "SkeletalMesh":
+ ImportScript += "fbx_factory = PyFbxFactory()" + "\n"
+ ImportScript += "fbx_factory.ImportUI.bImportMaterials = True" + "\n"
+ ImportScript += "fbx_factory.ImportUI.bImportTextures = False" + "\n"
+ ImportScript += "fbx_factory.ImportUI.TextureImportData.MaterialSearchLocation = EMaterialSearchLocation." + obj.MaterialSearchLocation + "\n"
+ ImportScript += "fbx_factory.ImportUI.bImportAnimations = False" + "\n"
+ ImportScript += "fbx_factory.ImportUI.SkeletalMeshImportData.bImportMorphTargets = True" + "\n"
+ ImportScript += "fbx_factory.ImportUI.bCreatePhysicsAsset = " + str(obj.CreatePhysicsAsset) + "\n"
+ ImportScript += "FbxLoc = os.path.join(r'"+FbxLoc+"')" + "\n"
+ ImportScript += r"AssetImportLocation = os.path.join(UnrealImportLocation, r'"+MeshRelatifImportLoc+r"').replace('\\','/')" + "\n"
+ ImportScript += "AssetImportLocation = AssetImportLocation.rstrip('/')" + "\n"
+ #-----------Import
+ ImportScript += "try:" + "\n"
+ ImportScript += "\t" + "asset = fbx_factory.factory_import_object(FbxLoc, AssetImportLocation)" + "\n"
+ ImportScript += "\t" + "ImportedAsset.append(asset)" + "\n"
+ ImportScript += "except:" + "\n"
+ ImportScript += "\t" + "ImportedAsset.append('Import error for asset named \""+obj.name+"\" ')" + "\n"
+
+ if (asset.assetType == "Animation" or asset.assetType == "Pose"):
+ SkeletonName = scene.skeletal_prefix_export_name+obj.name+"_Skeleton."+scene.skeletal_prefix_export_name+obj.name+"_Skeleton"
+ SkeletonLoc = os.path.join(obj.exportFolderName,SkeletonName)
+ ImportScript += "SkeletonLocation = os.path.join(UnrealImportLocation, r'" + SkeletonLoc + r"').replace('\\','/')" + "\n"
+ ImportScript += "OriginSkeleton = ue.find_asset(SkeletonLocation)" + "\n"
+ ImportScript += "if OriginSkeleton:" + "\n"
+ ImportScript += "\t" + "fbx_factory = PyFbxFactory()" + "\n"
+ ImportScript += "\t" + "fbx_factory.ImportUI.bImportMesh = False" + "\n"
+ ImportScript += "\t" + "fbx_factory.ImportUI.bCreatePhysicsAsset = False" + "\n"
+ ImportScript += "\t" + "fbx_factory.ImportUI.Skeleton = OriginSkeleton" + "\n"
+ ImportScript += "\t" + "fbx_factory.ImportUI.bImportAnimations = True" + "\n"
+ ImportScript += "\t" + "fbx_factory.ImportUI.MeshTypeToImport = EFBXImportType.FBXIT_Animation" + "\n"
+ ImportScript += "\t" + "fbx_factory.ImportUI.SkeletalMeshImportData.bImportMorphTargets = True" + "\n"
+ ImportScript += "\t" + "FbxLoc = os.path.join(r'"+FbxLoc+"')" + "\n"
+ ImportScript += "\t" + r"AssetImportLocation = os.path.join(UnrealImportLocation, r'"+AnimRelatifImportLoc+r"').replace('\\','/')" + "\n"
+ ImportScript += "\t" + "AssetImportLocation = AssetImportLocation.rstrip('/')" + "\n"
+ #-----------Import
+ ImportScript += "\t" + "try:" + "\n"
+ ImportScript += "\t\t" + "asset = fbx_factory.factory_import_object(FbxLoc, AssetImportLocation)" + "\n"
+ ImportScript += "\t\t" + "ImportedAsset.append(asset)" + "\n"
+ ImportScript += "\t" + "except:" + "\n"
+ ImportScript += "\t\t" + "ImportedAsset.append('Import error for asset named \""+obj.name+"\" ')" + "\n"
+ ImportScript += "else:" + "\n"
+ ImportScript += "\t" + "ImportedAsset.append('Skeleton \"'+SkeletonLocation+'\" Not found for \""+obj.name+"\" asset ')" + "\n"
+
+ ImportScript += "\n"
+ ImportScript += "\n"
+ ImportScript += "\n"
+
+ #import result
+ ImportScript += "print('========================= Imports completed ! =========================')" + "\n"
+ ImportScript += "\n"
+ ImportScript += "for asset in ImportedAsset:" + "\n"
+ ImportScript += "\t" + "print(asset)" + "\n"
+ ImportScript += "\n"
+ ImportScript += "print('=========================')" + "\n"
+
+ return ImportScript
+
+
+def WriteImportSequencerScript():
+ #Generate a script for import camera in Ue4 sequencer
+ scene = bpy.context.scene
+
+ #Comment
+ ImportScript = "#This script was generated with the addons Blender for UnrealEngine : https://github.com/xavier150/Blender-For-UnrealEngine-Addons" + "\n"
+ ImportScript += "#This script will import in unreal all camera in target sequencer" + "\n"
+ ImportScript += "#The script must be used in Unreal Engine Editor with UnrealEnginePython : https://github.com/20tab/UnrealEnginePython" + "\n"
+ ImportScript += "\n"
+ ImportScript += "import os.path" + "\n"
+ ImportScript += "import configparser" + "\n"
+ ImportScript += "import unreal_engine as ue" + "\n"
+ ImportScript += "from unreal_engine.classes import MovieSceneCameraCutTrack, MovieScene3DTransformSection, MovieScene3DTransformTrack, MovieSceneAudioTrack, CineCameraActor" + "\n"
+ ImportScript += "from unreal_engine.structs import FloatRange, FloatRangeBound, MovieSceneObjectBindingID, FrameRate" + "\n"
+ ImportScript += "from unreal_engine import FTransform, FVector, FColor" + "\n"
+ ImportScript += "from unreal_engine.enums import EMovieSceneObjectBindingSpace" + "\n"
+ ImportScript += "\n"
+
+ #Prepare def
+ ImportScript += "def AddSequencerSectionKeysByIniFile(SequencerSection, SectionFileName, FileLoc):" + "\n"
+ ImportScript += "\t" + "Config = configparser.ConfigParser()" + "\n"
+ ImportScript += "\t" + "Config.read(FileLoc)" + "\n"
+ ImportScript += "\t" + "for option in Config.options(SectionFileName):" + "\n"
+ ImportScript += "\t\t" + "frame = float(option)/"+str(scene.render.fps)+" #FrameRate" + "\n"
+ ImportScript += "\t\t" + "value = float(Config.get(SectionFileName, option))" + "\n"
+ ImportScript += "\t\t" + "SequencerSection.sequencer_section_add_key(frame,value)" + "\n"
+
+ #Prepare var and process import
+
+
+
+ ImportScript += 'seq = ue.find_asset("'+scene.unreal_levelsequence_reference+'")' + "\n"
+ ImportScript += 'if seq:' + "\n"
+ ImportScript += "\t" + 'print("Sequencer reference found")' + "\n"
+ ImportScript += "\t" + "ImportedCamera = [] #(CameraName, CameraGuid)" + "\n"
+ ImportScript += "\t" + 'print("========================= Import started ! =========================")' + "\n"
+ ImportScript += "\t" + "\n"
+ #Set frame rate
+ ImportScript += "\t" + "#Set frame rate" + "\n"
+ ImportScript += "\t" + "myFFrameRate = FrameRate()" + "\n"
+ ImportScript += "\t" + "myFFrameRate.Denominator = 1" + "\n"
+ ImportScript += "\t" + "myFFrameRate.Numerator = " + str(scene.render.fps) + "\n"
+ ImportScript += "\t" + "seq.MovieScene.DisplayRate = myFFrameRate" + "\n"
+ ImportScript += "\t" + "\n"
+ #Set playback range
+ StartPlayback = str(scene.frame_start/scene.render.fps)
+ EndPlayback = str(scene.frame_end/scene.render.fps)
+ ImportScript += "\t" + "#Set playback range" + "\n"
+ ImportScript += "\t" + "seq.sequencer_set_playback_range("+StartPlayback+", "+EndPlayback+")" + "\n"
+ ImportScript += "\t" + "camera_cut_track = seq.sequencer_add_camera_cut_track()" + "\n"
+ ImportScript += "\t" + "world = ue.get_editor_world()" + "\n"
+
+ ImportScript += "else:" + "\n"
+ ImportScript += "\t" + 'print("Sequencer reference not valid !")' + "\n"
+ ImportScript += "\n"
+ ImportScript += "\n"
+
+ #Import camera
+ for asset in scene.UnrealExportedAssetsList:
+ if (asset.assetType == "Camera"):
+ camera = asset.object
+ ImportScript += "#import " + camera.name + "\n"
+ ImportScript += "if seq:" + "\n"
+ ImportScript += "\t" + 'print("Start import ' + camera.name + '")' + "\n"
+ ImportScript += "\t" + "\n"
+
+ #Create spawnable camera
+ ImportScript += "\t" + "#Create spawnable camera" + "\n"
+ ImportScript += "\t" + "cine_camera_actor = world.actor_spawn(CineCameraActor) #Create camera" + "\n"
+ ImportScript += "\t" + "cine_camera_actor.set_actor_label('" + camera.name + "')" + "\n"
+ ImportScript += "\t" + "cine_camera_actor.CameraComponent.LensSettings.MinFStop = 0" + "\n"
+ ImportScript += "\t" + "cine_camera_actor.CameraComponent.LensSettings.MaxFStop = 1000" + "\n"
+ ImportScript += "\t" + "camera_spawnable_guid = seq.sequencer_make_new_spawnable(cine_camera_actor) #Add camera in sequencer" + "\n"
+ ImportScript += "\t" + "cine_camera_actor.actor_destroy()" + "\n"
+ ImportScript += "\t" + "ImportedCamera.append(('"+camera.name+"', camera_spawnable_guid))"
+
+ ImportScript += "\n"
+
+ #Import fbx transform
+ ImportScript += "\t" + "#Import fbx transform" + "\n"
+ FbxLoc = (os.path.join(asset.exportPath, GetObjExportFileName(camera)))
+ ImportScript += "\t" + "FbxLoc = os.path.join(r'"+FbxLoc+"')" + "\n"
+ ImportScript += "\t" + "for obj in seq.MovieScene.ObjectBindings:" + "\n"
+ ImportScript += "\t\t" + "if obj.ObjectGuid == ue.string_to_guid(camera_spawnable_guid):" + "\n"
+ ImportScript += "\t\t\t" + "transform_track = obj.tracks[0]" + "\n"
+ ImportScript += "\t\t\t" + "camera_section = transform_track.Sections[0]" + "\n"
+ ImportScript += "\t\t\t" + "camera_section.sequencer_import_fbx_transform(FbxLoc, '" + camera.name + "')" + "\n"
+ ImportScript += "\n"
+
+ #Import additional tracks
+ ImportScript += "\t" + "#Import additional tracks" + "\n"
+ TracksLoc = (os.path.join(asset.exportPath, GetCameraTrackFileName(camera)))
+ ImportScript += "\t" + "TracksLoc = os.path.join(r'"+TracksLoc+"')" + "\n"
+ ImportScript += "\t" + "Component = seq.MovieScene.ObjectBindings[-1]" + "\n"
+ ImportScript += "\t" + "sectionFocalLength = Component.Tracks[0].Sections[0]" + "\n"
+ ImportScript += "\t" + "AddSequencerSectionKeysByIniFile(sectionFocalLength, 'FocalLength', TracksLoc)" + "\n"
+ ImportScript += "\t" + "sectionFocusDistance = Component.Tracks[1].Sections[0]" + "\n"
+ ImportScript += "\t" + "AddSequencerSectionKeysByIniFile(sectionFocusDistance, 'FocusDistance', TracksLoc)" + "\n"
+ ImportScript += "\t" + "sectionAperture = Component.Tracks[2].Sections[0]" + "\n"
+ ImportScript += "\t" + "AddSequencerSectionKeysByIniFile(sectionAperture, 'Aperture', TracksLoc)" + "\n"
+ ImportScript += "\n"
+ ImportScript += "\n\n"
+
+ def getMarkerSceneSections():
+ scene = bpy.context.scene
+ markersOrderly = []
+ firstMarkersFrame = scene.frame_start
+ lastMarkersFrame = scene.frame_end+1
+
+
+ for marker in scene.timeline_markers:
+ #Re set first frame
+ if marker.frame < firstMarkersFrame:
+ firstMarkersFrame = marker.frame
+
+ for x in range(firstMarkersFrame, lastMarkersFrame):
+ for marker in scene.timeline_markers:
+ if marker.frame == x:
+ markersOrderly.append(marker)
+ #---
+ sectionCuts = []
+ for x in range(len(markersOrderly)):
+ if scene.frame_end > markersOrderly[x].frame:
+ startTime = markersOrderly[x].frame/scene.render.fps
+ if x+1 != len(markersOrderly):
+ EndTime = markersOrderly[x+1].frame/scene.render.fps
+ else:
+ EndTime = scene.frame_end/scene.render.fps
+ sectionCuts.append([startTime, EndTime, markersOrderly[x]])
+
+ return sectionCuts
+
+ for section in getMarkerSceneSections():
+ #Camera cut sections
+ ImportScript += "#Import camera cut section" + "\n"
+ ImportScript += "if seq:" + "\n"
+ ImportScript += "\t" + "camera_cut_section = camera_cut_track.sequencer_track_add_section()" + "\n"
+ if section[2].camera is not None:
+ if section[2].camera.ExportEnum == "export_recursive" or section[2].camera.ExportEnum == "auto":
+ ImportScript += "\t" + "for camera in ImportedCamera:" + "\n"
+ ImportScript += "\t\t" + "if camera[0] == '"+section[2].camera.name+"':" + "\n"
+ ImportScript += "\t\t\t" + "camera_cut_section.CameraBindingID = MovieSceneObjectBindingID( Guid=ue.string_to_guid( camera[1] ), Space=EMovieSceneObjectBindingSpace.Local )" + "\n"
+ else:
+ ImportScript += "\t" + "#Not camera found for this section" + "\n"
+ else:
+ ImportScript += "\t" + "#Not camera found for this section" + "\n"
+ ImportScript += "\t" + "camera_cut_section.sequencer_set_section_range("+str(section[0])+", "+str(section[1])+")" + "\n"
+
+ #import result
+
+ ImportScript += "if seq:" + "\n"
+ ImportScript += "\t" + "print('========================= Imports completed ! =========================')" + "\n"
+ ImportScript += "\t" + "\n"
+ ImportScript += "\t" + "for cam in ImportedCamera:" + "\n"
+ ImportScript += "\t\t" + "print(cam[0])" + "\n"
+ ImportScript += "\t" + "\n"
+ ImportScript += "\t" + "print('=========================')" + "\n"
+ ImportScript += "\t" + "seq.sequencer_changed(True)" + "\n"
+
+ return ImportScript
+
+
+def WriteSingleCameraAdditionalTrack(obj):
+
+ def getCameraFocusDistance(Camera, Target):
+ transA = Camera.matrix_world.copy()
+ transB = Target.matrix_world.copy()
+ transA.invert()
+ distance = (transA*transB).translation.z #Z is the Fosrward
+ if distance < 0:
+ distance *= -1
+ return distance
+
+ def getAllCamDistKeys(Camera, Target):
+ scene = bpy.context.scene
+ saveFrame = scene.frame_current #Save current frame
+ keys = []
+ for frame in range(scene.frame_start, scene.frame_end+1):
+ scene.frame_set(frame)
+ v = getCameraFocusDistance(Camera, Target)
+ keys.append((frame,v))
+ scene.frame_set(saveFrame) #Resets previous start frame
+ return keys
+
+
+ def getOneDataKeysByPath(obj,DataPath, DataValue, Frame):
+ scene = bpy.context.scene
+ if obj.data.animation_data is not None:
+ f = obj.data.animation_data.action.fcurves.find(DataPath)
+ if f is not None:
+ return f.evaluate(Frame)
+ return DataValue
+
+ def getAllDataKeysByPath(obj,DataPath, DataValue):
+ scene = bpy.context.scene
+ keys = []
+ if obj.data.animation_data is not None:
+ if obj.data.animation_data.action is not None:
+ f = obj.data.animation_data.action.fcurves.find(DataPath)
+ if f is not None:
+ for frame in range(scene.frame_start, scene.frame_end+1):
+ v = f.evaluate(frame)
+ keys.append((frame,v))
+ return keys
+ return[(scene.frame_start,DataValue)]
+
+ scene = bpy.context.scene
+ ImportScript = ";This file was generated with the addons Blender for UnrealEngine : https://github.com/xavier150/Blender-For-UnrealEngine-Addons" + "\n"
+ ImportScript += ";This file contains animation informations that is not supported with fbx files" + "\n"
+ ImportScript += ";The script must be used in Unreal Engine Editor with UnrealEnginePython : https://github.com/20tab/UnrealEnginePython" + "\n"
+ ImportScript += "\n\n\n"
+
+ #Get FocalLength keys
+ ImportScript += "[FocalLength]" + "\n"
+ for key in getAllDataKeysByPath(obj,"lens",obj.data.lens):
+ #Fov type return auto to lens
+ ImportScript += str(key[0])+": "+str(key[1]) + "\n"
+ ImportScript += "\n\n\n"
+
+ #Get FocusDistance keys
+ ImportScript += "[FocusDistance]" + "\n"
+ if obj.data.dof_object is None:
+ DataKeys = getAllDataKeysByPath(obj,"dof_distance",obj.data.dof_distance)
+ else:
+ DataKeys = getAllCamDistKeys(obj, obj.data.dof_object)
+ for key in DataKeys:
+ CorrectedValue = key[1]*100
+ if CorrectedValue > 0:
+ ImportScript += str(key[0])+": "+str(CorrectedValue) + "\n"
+ else:
+ ImportScript += str(key[0])+": "+str(100000) + "\n" #100000 is default value in ue4
+ ImportScript += "\n\n\n"
+
+ #Get Aperture (Depth of Field) keys
+ ImportScript += "[Aperture]" + "\n"
+ if scene.render.engine == 'CYCLES': #Work only with cycle.
+ if obj.data.cycles.aperture_type == 'FSTOP':
+ DataKeys = getAllDataKeysByPath(obj,"cycles.aperture_fstop",obj.data.cycles.aperture_fstop)
+ else:
+ DataKeys = getAllDataKeysByPath(obj,"cycles.aperture_size",obj.data.cycles.aperture_size)
+ for key in DataKeys:
+ CorrectedValue = key[1]
+ if obj.data.cycles.aperture_type == 'RADIUS':
+ #Convert radius to Fstop
+ FocalLength = getOneDataKeysByPath(obj,"lens",obj.data.lens,key[0])
+ if CorrectedValue == 0:
+ CorrectedValue = 64
+ else:
+ CorrectedValue = (FocalLength/(key[1]*2000))
+ ImportScript += str(key[0])+": "+str(CorrectedValue) + "\n"
+ else:
+ ImportScript += "0: 21\n" #21 is default value in ue4
+ ImportScript += "\n\n\n"
+ return ImportScript
+
+def WriteAllTextFiles():
+
+ scene = bpy.context.scene
+ if scene.text_exportLog:
+ exportLog = WriteExportLog()
+ if exportLog is not None:
+ exportLogFilename = "ExportLog.txt"
+ ExportSingleText(exportLog, scene.export_other_file_path, exportLogFilename)
+
+ if scene.text_ImportAssetScript:
+ ImportAssetScript = WriteImportAssetScript()
+ if ImportAssetScript is not None:
+ ImportAssetScriptFilename = "ImportAssetScript.py"
+ ExportSingleText(ImportAssetScript, scene.export_other_file_path, ImportAssetScriptFilename)
+
+ if scene.text_ImportSequenceScript:
+ ImportSequencerScript = WriteImportSequencerScript()
+ if ImportSequencerScript is not None:
+ ImportSequencerScriptFilename = "ImportSequencerScript.py"
+ ExportSingleText(ImportSequencerScript, scene.export_other_file_path, ImportSequencerScriptFilename)
+