#====================== 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 # 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, 7, 3), #Rev 0.2.7.3 'blender': (2, 83, 1), 'location': 'View3D > UI > Unreal Engine 4', 'warning': '', "wiki_url": "https://github.com/xavier150/Blender-For-UnrealEngine-Addons/blob/master/Tuto/How%20export%20assets%20from%20Blender.md", 'tracker_url': '', 'support': 'COMMUNITY', 'category': 'Import-Export'} import os import bpy import fnmatch import time import addon_utils from bpy.props import ( StringProperty, BoolProperty, EnumProperty, IntProperty, FloatProperty, FloatVectorProperty, PointerProperty, CollectionProperty, ) from bpy.types import ( Operator, ) import importlib from . import bfu_ExportAsset importlib.reload(bfu_ExportAsset) from . import bfu_WriteText importlib.reload(bfu_WriteText) from . import bfu_Basics importlib.reload(bfu_Basics) from .bfu_Basics import * from . import bfu_Utils importlib.reload(bfu_Utils) from .bfu_Utils import * class BFU_AP_AddonPreferences(bpy.types.AddonPreferences): # this must match the addon name, use '__package__' # when defining this in a submodule of a python package. bl_idname = __name__ bakeArmatureAction : BoolProperty( name='Bake Armature animation', description='Bake Armature animation for export (Export will take more time)', default=False, ) correctExtremUVScale : BoolProperty( name='Correct Extrem UV Scale', description='Correct Extrem UV Scale for better UV quality in UE4 (Export will take more time)', default=False, ) removeSkeletonRootBone : BoolProperty( name='Remove root bone', description='Remove the armature root bone', default=True, ) skeletonRootBoneName : StringProperty( name='Skeleton root bone name', description='Name of the armature when exported. This is used to change the root bone name. If egal "Armature" Ue4 will remove the Armature root bone.', default="ArmatureRoot", ) rescaleFullRigAtExport : EnumProperty( name='Rescale exported rig', description='This will rescale the full rig at the export with the all constraints.', items = [ ("auto", "Auto", "Rescale only if the the Unit Scale is not = to 0.01", "SHADERFX", 1), ("custom_rescale", "Custom Rescale", "You can choose how rescale the rig at the export", "MODIFIER", 2), ("dont_rescale", "Dont Rescale", "Will not rescale the rig", "CANCEL", 3) ] ) newRigScale : FloatProperty( name='New scale', description='The new rig scale. AUTO: [New scale} = 100 * [Unit scale]', default=100, ) staticSocketsAdd90X : BoolProperty( name='Export StaticMesh Sockets with +90 degrees on X', description='On StaticMesh the sockets are auto imported by unreal with -90 degrees on X', default=True, ) rescaleSocketsAtExport : EnumProperty( name='Rescale exported sockets', description='This will rescale the all sockets at the export.', items = [ ("auto", "Auto", "Rescale only if the the Unit Scale is not = to 0.01", "SHADERFX", 1), ("custom_rescale", "Custom Rescale", "You can choose how rescale the sockets at the export", "MODIFIER", 2), ("dont_rescale", "Dont Rescale", "Will not rescale the sockets", "CANCEL", 3) ] ) staticSocketsImportedSize : FloatProperty( name='StaticMesh sockets import size', description='Size of the socket when imported in Unreal Engine. AUTO: 1 ( [New scale} = 100 / [Unit scale] )', default=1, ) skeletalSocketsImportedSize : FloatProperty( name='SkeletalMesh sockets import size', description='Size of the socket when imported in Unreal Engine. AUTO: 1 ( [New scale} = 100 / [Unit scale] )', default=1, ) ignoreNLAForAction : BoolProperty( name='Ignore NLA for Actions', description='This will export the action and ignore the all layer in Nonlinear Animation', default=False, ) exportWithCustomProps : BoolProperty( name='Export custom properties', description='Process export with custom properties (Can be used for Metadata)', default=False, ) exportWithMetaData : BoolProperty( name='Export meta data', description='Process export with meta data', default=False, ) revertExportPath : BoolProperty( name='Revert all export path at each export', description='will remove the folder of the all export path at each export', default=False, ) useGeneratedScripts : BoolProperty( name='Use generated script for import assets and sequencer.', description='If false the all properties that only works with import scripts will be disabled', default=True, ) use20TabScript : BoolProperty( name='Generate import script for 20Tab python intergration', description='Generate import script for 20Tab python intergration ( /!\ With vania python integration some features like StaticMesh Lod or SkeletalMesh Sockets integration do not work )', default=False, ) class BFU_OT_OpenDocumentationTargetPage(Operator): bl_label = "Documentation" bl_idname = "object.open_documentation_target_page" bl_description = "Clic for open documentation page on GitHub" octicon: StringProperty(default="") def execute(self, context): os.system("start \"\" https://github.com/xavier150/Blender-For-UnrealEngine-Addons/blob/master/Tuto/How%20export%20assets%20from%20Blender.md"+self.octicon) return {'FINISHED'} class BFU_OT_NewReleaseInfo(Operator): bl_label = "Open last release page" bl_idname = "object.new_release_info" bl_description = "Clic for open latest release page." def execute(self, context): os.system("start \"\" https://github.com/xavier150/Blender-For-UnrealEngine-Addons/releases/latest") return {'FINISHED'} def draw(self, context): layout = self.layout def LabelWithDocButton(tagetlayout, name, docOcticon): newRow = tagetlayout.row() newRow.label(text=name) docOperator = newRow.operator("object.open_documentation_target_page", icon= "HELP", text="") docOperator.octicon=docOcticon def PropWithDocButton(tagetlayout, name, docOcticon): newRow = tagetlayout.row() newRow.prop(self, name) docOperator = newRow.operator("object.open_documentation_target_page", icon= "HELP", text="") docOperator.octicon=docOcticon boxColumn = layout.column().split(factor = 0.5 ) rootBone = boxColumn.box() LabelWithDocButton(rootBone, "SKELETON & ROOT BONE", "#skeleton--root-bone") rootBone.prop(self, "removeSkeletonRootBone") rootBoneName = rootBone.column() rootBoneName.enabled = not self.removeSkeletonRootBone rootBoneName.prop(self, "skeletonRootBoneName") rootBone.prop(self, "rescaleFullRigAtExport") newRigScale = rootBone.column() newRigScale.enabled = self.rescaleFullRigAtExport == "custom_rescale" newRigScale.prop(self, "newRigScale") socket = boxColumn.box() socket.label(text='SOCKET') socket.prop(self, "staticSocketsAdd90X") socket.prop(self, "rescaleSocketsAtExport") socketRescale = socket.column() socketRescale.enabled = self.rescaleSocketsAtExport == "custom_rescale" socketRescale.prop(self, "staticSocketsImportedSize") socketRescale.prop(self, "skeletalSocketsImportedSize") boxColumn = layout.column().split(factor = 0.5 ) data = boxColumn.box() data.label(text='DATA') data.prop(self, "ignoreNLAForAction") PropWithDocButton(data, "correctExtremUVScale", "#uv") data.prop(self, "bakeArmatureAction") data.prop(self, "exportWithCustomProps") data.prop(self, "exportWithMetaData") data.prop(self, "revertExportPath") script = boxColumn.box() script.label(text='IMPORT SCRIPT') script.prop(self, "useGeneratedScripts") scriptProp = script.column() scriptProp.enabled = self.useGeneratedScripts scriptProp.prop(self, "use20TabScript") updateButton = layout.row() updateButton.scale_y = 2.0 updateButton.operator("object.new_release_info", icon= "TIME") class BFU_PT_BlenderForUnreal(bpy.types.Panel): #Unreal engine export panel bl_idname = "BFU_PT_BlenderForUnreal" bl_label = "Blender for Unreal Engine" bl_space_type = "VIEW_3D" bl_region_type = "UI" bl_category = "Unreal Engine 4" class BFU_MT_ObjectGlobalPropertiesPresets(bpy.types.Menu): bl_label = 'Global Properties Presets' preset_subdir = 'blender-for-unrealengine/global-properties-presets' preset_operator = 'script.execute_preset' draw = bpy.types.Menu.draw_preset from bl_operators.presets import AddPresetBase class BFU_OT_AddObjectGlobalPropertiesPreset(AddPresetBase, Operator): bl_idname = 'object.add_globalproperties_preset' bl_label = 'Add or remove a preset for Global properties' bl_description = 'Add or remove a preset for Global properties' preset_menu = 'BFU_MT_ObjectGlobalPropertiesPresets' # Common variable used for all preset values preset_defines = [ 'obj = bpy.context.object', 'scene = bpy.context.scene' ] # Properties to store in the preset preset_values = [ 'obj.exportFolderName', 'obj.ExportAsAlembic', 'obj.ExportAsLod', 'obj.ForceStaticMesh', 'obj.exportDeformOnly', 'obj.Ue4Lod1', 'obj.Ue4Lod2', 'obj.Ue4Lod3', 'obj.Ue4Lod4', 'obj.Ue4Lod5', 'obj.CreatePhysicsAsset', 'obj.UseStaticMeshLODGroup', 'obj.StaticMeshLODGroup', 'obj.UseStaticMeshLightMapRes', 'obj.StaticMeshLightMapRes', 'obj.GenerateLightmapUVs', 'obj.AutoGenerateCollision', 'obj.MaterialSearchLocation', 'obj.CollisionTraceFlag', 'obj.VertexColorImportOption', 'obj.exportActionEnum', 'obj.PrefixNameToExport', 'obj.AnimStartEndTimeEnum', 'obj.StartFramesOffset', 'obj.EndFramesOffset', 'obj.AnimCustomStartTime', 'obj.AnimCustomEndTime', 'obj.SampleAnimForExport', 'obj.SimplifyAnimForExport', 'obj.ExportNLA', 'obj.NLAAnimName', 'obj.exportGlobalScale', 'obj.exportAxisForward', 'obj.exportAxisUp', 'obj.exportPrimaryBaneAxis', 'obj.exporSecondaryBoneAxis', 'obj.MoveToCenterForExport', 'obj.RotateToZeroForExport', 'obj.AdditionalLocationForExport', 'obj.AdditionalRotationForExport', ] # Directory to store the presets preset_subdir = 'blender-for-unrealengine/global-properties-presets' class BFU_OT_OpenDocumentationPage(Operator): bl_label = "Documentation" bl_idname = "object.open_documentation_page" bl_description = "Clic for open documentation page on GitHub" def execute(self, context): os.system("start \"\" https://github.com/xavier150/Blender-For-UnrealEngine-Addons/blob/master/Tuto/How%20export%20assets%20from%20Blender.md") return {'FINISHED'} def draw(self, contex): releaseVersion = None#GetGitHubLastRelaseVersion() layout = self.layout docsButton = layout.row() docsButton.operator("object.open_documentation_page", icon= "HELP") class BFU_PT_ObjectProperties(bpy.types.Panel): #Is Object Properties panel bl_idname = "BFU_PT_ObjectProperties" bl_label = "Object Properties" bl_space_type = "VIEW_3D" bl_region_type = "UI" bl_category = "Unreal Engine 4" bl_parent_id = "BFU_PT_BlenderForUnreal" bpy.types.Object.ExportEnum = EnumProperty( name = "Export type", description = "Export procedure", items = [ ("auto", "Auto", "Exports only if one of the parents is \"Export recursive\"", "BOIDS", 1), ("export_recursive", "Export recursive", "Export self object and all children", "KEYINGSET", 2), ("dont_export", "Not exported", "Will never export", "CANCEL", 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.ExportAsProxy = BoolProperty( name="The armature is a Proxy ?", description="If true this mesh will be exported with a target child for keed to data", default=False ) bpy.types.Object.ExportProxyChild = PointerProperty( name="The armature proxy children", description="Select child proxy (The mesh animated by this armature)", type = bpy.types.Object ) bpy.types.Object.ExportAsAlembic = BoolProperty( name="Export as Alembic animation", description="If true this mesh will be exported as a Alembic animation", default=False ) bpy.types.Object.ExportAsLod = BoolProperty( name="Export as lod?", description="If true this mesh will be exported as a level of detail for another mesh", default=False ) 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 addon_prefs = bpy.context.preferences.addons["blender-for-unrealengine"].preferences if obj is not None: AssetType = layout.row() AssetType.prop(obj, 'name', text="", icon='OBJECT_DATA') AssetType.label(text='('+ GetAssetType(obj)+')') #Show asset type ExportType = layout.column() ExportType.prop(obj, 'ExportEnum') if obj.ExportEnum == "export_recursive": row = self.layout.row(align=True) row.menu('BFU_MT_ObjectGlobalPropertiesPresets', text='Global Properties Presets') row.operator('object.add_globalproperties_preset', text='', icon='ADD') row.operator('object.add_globalproperties_preset', text='', icon='REMOVE').remove_active = True folderNameProperty = layout.column() folderNameProperty.prop(obj, 'exportFolderName', icon='FILE_FOLDER') if obj.type != "CAMERA": ProxyProp = layout.column() ProxyProp.prop(obj, 'ExportAsProxy') if obj.ExportAsProxy == True: ProxyProp.prop(obj, 'ExportProxyChild') pass else: AlembicProp = layout.column() AlembicProp.prop(obj, 'ExportAsAlembic') if obj.ExportAsAlembic == True: AlembicProp.label(text="(Alembic animation are exported with scene position.)") AlembicProp.label(text="(Use import script for use origin position.)") else: if addon_prefs.useGeneratedScripts == True: LodProp = layout.column() LodProp.prop(obj, 'ExportAsLod') 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(text='(No properties to show.)') class BFU_PT_ObjectImportProperties(bpy.types.Panel): #Is Object Properties panel bl_idname = "BFU_PT_ObjectImportProperties" bl_label = "Object Import Properties" bl_space_type = "VIEW_3D" bl_region_type = "UI" bl_category = "Unreal Engine 4" bl_parent_id = "BFU_PT_BlenderForUnreal" #Lod list bpy.types.Object.Ue4Lod1 = PointerProperty( name = "LOD1", description = "Target objet for level of detail 01", type = bpy.types.Object ) bpy.types.Object.Ue4Lod2 = PointerProperty( name = "LOD2", description = "Target objet for level of detail 02", type = bpy.types.Object ) bpy.types.Object.Ue4Lod3 = PointerProperty( name = "LOD3", description = "Target objet for level of detail 03", type = bpy.types.Object ) bpy.types.Object.Ue4Lod4 = PointerProperty( name = "LOD4", description = "Target objet for level of detail 04", type = bpy.types.Object ) bpy.types.Object.Ue4Lod5 = PointerProperty( name = "LOD5", description = "Target objet for level of detail 05", type = bpy.types.Object ) #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 = "If checked, UVs for Lightmap will automatically be generated." , default=True, ) bpy.types.Object.AutoGenerateCollision = BoolProperty( name = "Auto Generate Collision", description = "If checked, collision will automatically be generated (ignored if custom collision is imported or used)." , 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", #Vania python -> #https://docs.unrealengine.com/en-US/PythonAPI/class/MaterialSearchLocation.html?highlight=materialsearchlocation #20tab python -> http://api.unrealengine.com/INT/API/Editor/UnrealEd/Factories/EMaterialSearchLocation/index.html 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) ] ) bpy.types.Object.CollisionTraceFlag = EnumProperty( name = "Collision Complexity", description = "Collision Trace Flag", #Vania python -> https://docs.unrealengine.com/en-US/PythonAPI/class/CollisionTraceFlag.html #20tab python ->https://api.unrealengine.com/INT/API/Runtime/Engine/PhysicsEngine/ECollisionTraceFlag/index.html items = [ ("CTF_UseDefault", "Project Default", "Create only complex shapes (per poly). Use complex shapes for all scene queries and collision tests. Can be used in simulation for static shapes only (i.e can be collided against but not moved through forces or velocity.", 1), ("CTF_UseSimpleAndComplex", "Use Simple And Complex", "Use project physics settings (DefaultShapeComplexity)", 2), ("CTF_UseSimpleAsComplex", "Use Simple as Complex", "Create both simple and complex shapes. Simple shapes are used for regular scene queries and collision tests. Complex shape (per poly) is used for complex scene queries.", 3), ("CTF_UseComplexAsSimple", "Use Complex as Simple", "Create only simple shapes. Use simple shapes for all scene queries and collision tests.", 4) ] ) bpy.types.Object.VertexColorImportOption = EnumProperty( name = "Vertex Color Import Option", description = "Specify how vertex colors should be imported", #Vania python -> https://docs.unrealengine.com/en-US/PythonAPI/class/VertexColorImportOption.html #20tab python -> https://docs.unrealengine.com/en-US/API/Editor/UnrealEd/Factories/EVertexColorImportOption__Type/index.html items = [ ("VCIO_Ignore", "Ignore", "Ignore vertex colors from the FBX file, and keep the existing mesh vertex colors.", 1), ("VCIO_Replace", "Replace", "Import the static mesh using the vertex colors from the FBX file.", 2) ] ) def draw(self, context): layout = self.layout obj = context.object addon_prefs = bpy.context.preferences.addons["blender-for-unrealengine"].preferences if addon_prefs.useGeneratedScripts == True: if obj is not None: if obj.ExportEnum == "export_recursive": #Lod selection if obj.ExportAsLod == False: if GetAssetType(obj) == "StaticMesh" or GetAssetType(obj) == "SkeletalMesh": LodList = layout.column() LodList.prop(obj, 'Ue4Lod1') LodList.prop(obj, 'Ue4Lod2') LodList.prop(obj, 'Ue4Lod3') LodList.prop(obj, 'Ue4Lod4') LodList.prop(obj, 'Ue4Lod5') #MaterialSearchLocation if obj.ExportAsLod == False: if GetAssetType(obj) == "StaticMesh" or GetAssetType(obj) == "SkeletalMesh" or GetAssetType(obj) == "Alembic": MaterialSearchLocation = layout.row() MaterialSearchLocation.prop(obj, 'MaterialSearchLocation') #StaticMesh prop if GetAssetType(obj) == "StaticMesh": if obj.ExportAsLod == False: StaticMeshLODGroup = layout.row() StaticMeshLODGroup.prop(obj, 'UseStaticMeshLODGroup', text="") StaticMeshLODGroupChild = StaticMeshLODGroup.column() StaticMeshLODGroupChild.enabled = obj.UseStaticMeshLODGroup StaticMeshLODGroupChild.prop(obj, 'StaticMeshLODGroup') StaticMeshCollisionTraceFlag = layout.row() StaticMeshCollisionTraceFlag.prop(obj, 'CollisionTraceFlag') StaticMeshVertexColorImportOption = layout.row() StaticMeshVertexColorImportOption.prop(obj, 'VertexColorImportOption') 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') if obj.ExportAsLod == False: AutoGenerateCollision = layout.row() AutoGenerateCollision.prop(obj, 'AutoGenerateCollision') #SkeletalMesh prop if GetAssetType(obj) == "SkeletalMesh": if obj.ExportAsLod == False: CreatePhysicsAsset = layout.row() CreatePhysicsAsset.prop(obj, "CreatePhysicsAsset") else: layout.label(text='(No properties to show.)') else: layout.label(text='(No properties to show.)') else: layout.label(text='(Generated scripts are deactivated.)') class BFU_OT_ObjExportAction(bpy.types.PropertyGroup): name: StringProperty(name="Action data name", default="Unknown") use: BoolProperty(name="use this action", default=False) class BFU_PT_AnimProperties(bpy.types.Panel): #Is Animation Properties panel bl_idname = "BFU_PT_AnimProperties" bl_label = "Animation Properties" bl_space_type = "VIEW_3D" bl_region_type = "UI" bl_category = "Unreal Engine 4" bl_parent_id = "BFU_PT_BlenderForUnreal" #Animation : class BFU_UL_ActionExportTarget(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) 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", 3), ("export_specific_prefix", "Export specific prefix", "Export only actions with a specific prefix or the beginning of the actions names", "SYNTAX_ON", 4), ("dont_export", "Not exported", "No action will be exported", "MATPLANE", 5), ("export_current", "Export Current", "Export only the current actions", "FILE_SCRIPT", 6), ] ) 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.StartFramesOffset = IntProperty( name = "Offset at start frame", description = "Offset for the start frame.", default=1 ) bpy.types.Object.EndFramesOffset = IntProperty( name = "Offset at end frame", description = "Offset for the end frame. +1 is recommended for the sequences, 0 is recommended for UnrealEngine cycles, -1 is recommended for Sketchfab cycles", default=0 ) 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, ) bpy.types.Object.ExportNLA = BoolProperty( name="Export NLA (Nonlinear Animation)", description="If checked, exports the all animation of the scene with the NLA", default=False ) bpy.types.Object.NLAAnimName = StringProperty( name = "NLA export name", description = 'Export NLA name', maxlen = 64, default = "NLA_animation", subtype = 'FILE_NAME' ) class BFU_OT_UpdateObjActionListButton(Operator): bl_label = "Update action list" bl_idname = "object.updateobjactionlist" 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 BFU_OT_ShowActionToExport(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) > 0: 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() Frames = GetDesiredActionStartEndTime(obj, action) row.label(text="- "+action.name+GetActionType(action)+" From "+str(Frames[0])+" to "+str(Frames[1]) ) 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" and obj.ExportAsLod == False: if GetAssetType(obj) == "SkeletalMesh" or GetAssetType(obj) == "Camera" or GetAssetType(obj) == "Alembic": #Action time if obj.type != "CAMERA": ActionTimeProperty = layout.column() ActionTimeProperty.prop(obj, 'AnimStartEndTimeEnum') if obj.AnimStartEndTimeEnum == "with_customframes": OfsetTime = ActionTimeProperty.row() OfsetTime.prop(obj, 'AnimCustomStartTime') OfsetTime.prop(obj, 'AnimCustomEndTime') if obj.AnimStartEndTimeEnum != "with_customframes": OfsetTime = ActionTimeProperty.row() OfsetTime.prop(obj, 'StartFramesOffset') OfsetTime.prop(obj, 'EndFramesOffset') else: layout.label(text="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( "BFU_UL_ActionExportTarget", "", # 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.updateobjactionlist", icon='RECOVER_LAST') if obj.exportActionEnum == "export_specific_prefix": ActionListProperty.prop(obj, 'PrefixNameToExport') #Action fbx properties if GetAssetType(obj) != "Alembic": 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(factor = 0.75 ) ActionNum = len(GetActionToExport(obj)) actionFeedback = str(ActionNum) + " Action(s) will be exported with this armature." ArmaturePropertyInfo.label(text=actionFeedback, icon='INFO') ArmaturePropertyInfo.operator("object.showobjaction") layout.label(text='Note: The Action with only one frame are exported like Pose.') if GetAssetType(obj) == "SkeletalMesh": NLAAnim = layout.row() NLAAnim.prop(obj, 'ExportNLA') NLAAnimChild = NLAAnim.column() NLAAnimChild.enabled = obj.ExportNLA NLAAnimChild.prop(obj, 'NLAAnimName') else: layout.label(text='(This assets is not a SkeletalMesh or Camera)') else: layout.label(text='(No properties to show.)') else: layout.label(text='(No properties to show.)') class BFU_OT_SceneCollectionExport(bpy.types.PropertyGroup): name: StringProperty(name="collection data name", default="Unknown") use: BoolProperty(name="export this collection", default=False) class BFU_PT_CollectionProperties(bpy.types.Panel): #Is Collection Properties panel bl_idname = "BFU_PT_CollectionProperties" bl_label = "Collection Properties panel" bl_space_type = "VIEW_3D" bl_region_type = "UI" bl_category = "Unreal Engine 4" bl_parent_id = "BFU_PT_BlenderForUnreal" class BFU_UL_CollectionExportTarget(bpy.types.UIList): def draw_item(self, context, layout, data, item, icon, active_data, active_propname): CollectionIsValid = False try: bpy.data.collections[item.name] CollectionIsValid = True except: pass if self.layout_type in {'DEFAULT', 'COMPACT'}: if CollectionIsValid: #If action is valid #layout.prop(item, "name", text="", emboss=False, icon="ACTION") #Debug only for see target line layout.prop(bpy.data.collections[item.name], "name", text="", emboss=False, icon="GROUP") layout.prop(item, "use", text="") else: dataText = ('Collection 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 BFU_OT_UpdateCollectionButton(Operator): bl_label = "Update collection list" bl_idname = "object.updatecollectionlist" bl_description = "Update collection list" def execute(self, context): def UpdateExportCollectionList(scn): #Update the provisional collection list known by the object def SetUseFromLast(list, CollectionName): for item in list: if item[0] == CollectionName: if item[1] == True: return True return False colSave = [["", False]] for col in scn.CollectionExportList: #CollectionProperty name = col.name use = col.use colSave.append([name, use]) scn.CollectionExportList.clear() for col in bpy.data.collections: scn.CollectionExportList.add().name = col.name scn.CollectionExportList[col.name].use = SetUseFromLast(colSave, col.name) UpdateExportCollectionList(context.scene) return {'FINISHED'} class BFU_OT_ShowCollectionToExport(Operator): bl_label = "Show action(s)" bl_idname = "object.showscenecollection" bl_description = "Click to show collections to export" def execute(self, context): scn = context.scene collections = GetCollectionToExport(scn) popup_title = "Collection list" if len(collections) > 0: popup_title = str(len(collections))+' collection(s) to export found.' else: popup_title = 'No collection to export found.' def draw(self, context): col = self.layout.column() for collection in collections: row = col.row() row.label(text="- "+collection) bpy.context.window_manager.popup_menu(draw, title=popup_title, icon='GROUP') return {'FINISHED'} bpy.types.Scene.active_CollectionExportList = IntProperty( name="Active Collection", description="Index of the currently active collection", default=0 ) def draw(self, context): scn = context.scene layout = self.layout collectionListProperty = layout.column() collectionListProperty.template_list( "BFU_UL_CollectionExportTarget", "", # type and unique id scn, "CollectionExportList", # pointer to the CollectionProperty scn, "active_CollectionExportList", # pointer to the active identifier maxrows=5, rows=5 ) collectionListProperty.operator("object.updatecollectionlist", icon='RECOVER_LAST') collectionPropertyInfo = layout.row().box().split(factor = 0.75 ) collectionNum = len(GetCollectionToExport(scn)) collectionFeedback = str(collectionNum) + " Collection(s) will be exported with this armature." collectionPropertyInfo.label(text=collectionFeedback, icon='INFO') collectionPropertyInfo.operator("object.showscenecollection") layout.label(text='Note: The collection are exported like StaticMesh.') class BFU_PT_AvancedObjectProperties(bpy.types.Panel): #Is Avanced object properties panel bl_idname = "BFU_PT_AvancedObjectProperties" bl_label = "Avanced object properties panel" bl_space_type = "VIEW_3D" bl_region_type = "UI" bl_category = "Unreal Engine 4" bl_parent_id = "BFU_PT_BlenderForUnreal" bpy.types.Object.exportGlobalScale = FloatProperty( name="Global scale", description="Scale, change is not recommended with SkeletalMesh.", default=1.0 ) bpy.types.Object.exportAxisForward = EnumProperty( name="Axis Forward", items=[ ('X', "X Forward", ""), ('Y', "Y Forward", ""), ('Z', "Z Forward", ""), ('-X', "-X Forward", ""), ('-Y', "-Y Forward", ""), ('-Z', "-Z Forward", ""), ], default='-Z', ) bpy.types.Object.exportAxisUp = EnumProperty( name="Axis Up", items=[ ('X', "X Up", ""), ('Y', "Y Up", ""), ('Z', "Z Up", ""), ('-X', "-X Up", ""), ('-Y', "-Y Up", ""), ('-Z', "-Z Up", ""), ], default='Y', ) bpy.types.Object.exportPrimaryBaneAxis = EnumProperty( name="Primary Axis Bone", items=[ ('X', "X", ""), ('Y', "Y", ""), ('Z', "Z", ""), ('-X', "-X", ""), ('-Y', "-Y", ""), ('-Z', "-Z", ""), ], default='Y', ) bpy.types.Object.exporSecondaryBoneAxis = EnumProperty( name="Secondary Axis Bone", items=[ ('X', "X", ""), ('Y', "Y", ""), ('Z', "Z", ""), ('-X', "-X", ""), ('-Y', "-Y", ""), ('-Z', "-Z", ""), ], default='X', ) bpy.types.Object.MoveToCenterForExport = BoolProperty( name="Move to center", description="If true use object origin else use scene origin. | If true the mesh will be moved to the center of the scene for export. (This is used so that the origin of the fbx file is the same as the mesh in blender)", default=True ) bpy.types.Object.RotateToZeroForExport = BoolProperty( name="Rotate to zero", description="If true use object rotation else use scene rotation. | If true the mesh will use zero rotation for export.", default=False ) bpy.types.Object.AdditionalLocationForExport = FloatVectorProperty( name="Additional location", description="This will add a additional absolute rotation to the mesh", subtype="TRANSLATION", default=(0,0,0) ) bpy.types.Object.AdditionalRotationForExport = FloatVectorProperty( name="Additional rotation", description="This will add a additional absolute rotation to the mesh", subtype="EULER", default=(0,0,0) ) def draw(self, context): scn = context.scene layout = self.layout obj = context.object if obj is not None: if obj.ExportEnum == "export_recursive": transformProp = layout.column() if GetAssetType(obj) != "Alembic": transformProp.prop(obj, "MoveToCenterForExport") transformProp.prop(obj, "RotateToZeroForExport") transformProp.prop(obj, "AdditionalLocationForExport") transformProp.prop(obj, "AdditionalRotationForExport") transformProp.prop(obj, 'exportGlobalScale') AxisProperty = layout.column() AxisProperty.prop(obj, 'exportAxisForward') AxisProperty.prop(obj, 'exportAxisUp') if GetAssetType(obj) == "SkeletalMesh": BoneAxisProperty = layout.column() BoneAxisProperty.prop(obj, 'exportPrimaryBaneAxis') BoneAxisProperty.prop(obj, 'exporSecondaryBoneAxis') else: layout.label(text='(No properties to show.)') class BFU_PT_CollisionsAndSockets(bpy.types.Panel): #Is Collisions And Sockets panel bl_idname = "BFU_PT_CollisionsAndSockets" bl_label = "Collisions And Sockets" bl_space_type = "VIEW_3D" bl_region_type = "UI" bl_category = "Unreal Engine 4" bl_parent_id = "BFU_PT_BlenderForUnreal" class BFU_OT_ConvertToCollisionButtonBox(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 = Ue4SubObj_set("Box") 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 BFU_OT_ConvertToCollisionButtonCapsule(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 = Ue4SubObj_set("Capsule") 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 BFU_OT_ConvertToCollisionButtonSphere(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 = Ue4SubObj_set("Sphere") 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 BFU_OT_ConvertToCollisionButtonConvex(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 = Ue4SubObj_set("Convex") 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 BFU_OT_ConvertToStaticSocketButton(Operator): bl_label = "Convert to StaticMesh socket" bl_idname = "object.converttostaticsocket" bl_description = "Convert selected Empty(s) to Unreal sockets ready for export (StaticMesh)" def execute(self, context): ConvertedObj = Ue4SubObj_set("ST_Socket") 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 socket)") return {'FINISHED'} class BFU_OT_ConvertToSkeletalSocketButton(Operator): bl_label = "Convert to SkeletalMesh socket" bl_idname = "object.converttoskeletalsocket" bl_description = "Convert selected Empty(s) to Unreal sockets ready for export (SkeletalMesh)" def execute(self, context): ConvertedObj = Ue4SubObj_set("SK_Socket") 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 socket)") return {'FINISHED'} def draw(self, context): addon_prefs = bpy.context.preferences.addons["blender-for-unrealengine"].preferences def ActiveModeIs(targetMode): #Return True is active mode == obj = bpy.context.active_object if obj is not None: if obj.mode == targetMode: return True return False def ActiveTypeIs(targetType): #Return True is active type == obj = bpy.context.active_object if obj is not None: if obj.type == targetType: return True return False def FoundTypeInSelect(targetType): #Return True if 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 layout = self.layout layout.label(text="Convert selected object to Unreal collision or socket", icon='PHYSICS') layout.label(text="Select your collider shape(s) or Empty(s) then the owner object.") convertButtons = layout.row().split(factor = 0.80 ) convertStaticCollisionButtons = convertButtons.column() convertStaticCollisionButtons.enabled = ActiveModeIs("OBJECT") and ActiveTypeIs("MESH") and FoundTypeInSelect("MESH") convertStaticCollisionButtons.operator("object.converttoboxcollision", icon='MESH_CUBE') convertStaticCollisionButtons.operator("object.converttoconvexcollision", icon='MESH_ICOSPHERE') convertStaticCollisionButtons.operator("object.converttocapsulecollision", icon='MESH_CAPSULE') convertStaticCollisionButtons.operator("object.converttospherecollision", icon='MESH_UVSPHERE') convertButtons = self.layout.row().split(factor = 0.80 ) convertStaticSocketButtons = convertButtons.column() convertStaticSocketButtons.enabled = ActiveModeIs("OBJECT") and ActiveTypeIs("MESH") and FoundTypeInSelect("EMPTY") convertStaticSocketButtons.operator("object.converttostaticsocket", icon='OUTLINER_DATA_EMPTY') if addon_prefs.useGeneratedScripts == True: layout.label(text="Select the Empty(s) then the owner bone in PoseMode.") convertButtons = self.layout.row().split(factor = 0.80 ) convertSkeletalSocketButtons = convertButtons.column() convertSkeletalSocketButtons.enabled = ActiveModeIs("POSE") and ActiveTypeIs("ARMATURE") and FoundTypeInSelect("EMPTY") convertSkeletalSocketButtons.operator("object.converttoskeletalsocket", icon='OUTLINER_DATA_EMPTY') class BFU_PT_Nomenclature(bpy.types.Panel): #Is FPS Export panel bl_idname = "BFU_PT_Nomenclature" bl_label = "Nomenclature" bl_space_type = "VIEW_3D" bl_region_type = "UI" bl_category = "Unreal Engine 4" bl_parent_id = "BFU_PT_BlenderForUnreal" class BFU_MT_NomenclaturePresets(bpy.types.Menu): bl_label = 'Nomenclature Presets' preset_subdir = 'blender-for-unrealengine/nomenclature-presets' preset_operator = 'script.execute_preset' draw = bpy.types.Menu.draw_preset from bl_operators.presets import AddPresetBase class BFU_OT_AddNomenclaturePreset(AddPresetBase, Operator): bl_idname = 'object.add_nomenclature_preset' bl_label = 'Add or remove a preset for Nomenclature' bl_description = 'Add or remove a preset for Nomenclature' preset_menu = 'BFU_MT_NomenclaturePresets' # Common variable used for all preset values preset_defines = [ 'obj = bpy.context.object', 'scene = bpy.context.scene' ] # Properties to store in the preset preset_values = [ 'scene.static_prefix_export_name', 'scene.skeletal_prefix_export_name', 'scene.alembic_prefix_export_name', 'scene.anim_prefix_export_name', 'scene.pose_prefix_export_name', 'scene.camera_prefix_export_name', 'scene.include_armature_export_name', 'scene.anim_subfolder_name', 'scene.export_static_file_path', 'scene.export_skeletal_file_path', 'scene.export_alembic_file_path', 'scene.export_camera_file_path', 'scene.export_other_file_path', 'scene.file_export_log_name', 'scene.file_import_asset_script_name', 'scene.file_import_sequencer_script_name', ] # Directory to store the presets preset_subdir = 'blender-for-unrealengine/nomenclature-presets' #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.alembic_prefix_export_name = bpy.props.StringProperty( name = "Alembic Prefix ", description = "Prefix of Alembic (SkeletalMesh in unreal)", 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_") bpy.types.Scene.include_armature_export_name = bpy.props.BoolProperty( name = "Include armature in animations file name", description = "Include armature name in animation export file name", default = True) #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 = os.path.join("//","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 = os.path.join("//","ExportedFbx","SkeletalMesh"), subtype = 'DIR_PATH') bpy.types.Scene.export_alembic_file_path = bpy.props.StringProperty( name = "Alembic export file path", description = "Choose a directory to export Alembic(s)", maxlen = 512, default = os.path.join("//","ExportedFbx","Alembic"), 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 = os.path.join("//","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 = os.path.join("//","ExportedFbx"), subtype = 'DIR_PATH') #File name bpy.types.Scene.file_export_log_name = bpy.props.StringProperty( name = "Export log name", description = "Export log name", maxlen = 64, default = "ExportLog.txt") bpy.types.Scene.file_import_asset_script_name = bpy.props.StringProperty( name = "Import asset script name", description = "Import asset script name", maxlen = 64, default = "ImportAssetScript.py") bpy.types.Scene.file_import_sequencer_script_name = bpy.props.StringProperty( name = "Import sequencer script Name", description = "Import sequencer script name", maxlen = 64, default = "ImportSequencerScript.py") def draw(self, context): scn = context.scene addon_prefs = bpy.context.preferences.addons["blender-for-unrealengine"].preferences #Presets row = self.layout.row(align=True) row.menu('BFU_MT_NomenclaturePresets', text='Nomenclature Presets') row.operator('object.add_nomenclature_preset', text='', icon='ADD') row.operator('object.add_nomenclature_preset', text='', icon='REMOVE').remove_active = True #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, 'alembic_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') propsPrefix.prop(scn, 'include_armature_export_name') #Sub folder propsSub = self.layout.row() propsSub = propsSub.column() propsSub.prop(scn, 'anim_subfolder_name', icon='FILE_FOLDER') #File path filePath = self.layout.row() filePath = filePath.column() filePath.prop(scn, 'export_static_file_path') filePath.prop(scn, 'export_skeletal_file_path') filePath.prop(scn, 'export_alembic_file_path') filePath.prop(scn, 'export_camera_file_path') filePath.prop(scn, 'export_other_file_path') #File name fileName = self.layout.row() fileName = fileName.column() fileName.prop(scn, 'file_export_log_name', icon='FILE') if addon_prefs.useGeneratedScripts == True: fileName.prop(scn, 'file_import_asset_script_name', icon='FILE') fileName.prop(scn, 'file_import_sequencer_script_name', icon='FILE') class BFU_PT_ImportScript(bpy.types.Panel): #Is Import script panel bl_idname = "BFU_PT_ImportScript" bl_label = "Import Script" bl_space_type = "VIEW_3D" bl_region_type = "UI" bl_category = "Unreal Engine 4" bl_parent_id = "BFU_PT_BlenderForUnreal" bpy.types.Scene.unreal_import_location = bpy.props.StringProperty( name = "Unreal import location", description = "Unreal assets import location in /Game/", maxlen = 512, default = 'ImportedFbx') bpy.types.Scene.unreal_levelsequence_import_location = bpy.props.StringProperty( name = "Unreal sequencer import location", description = "Unreal sequencer import location in /Game/", maxlen = 512, default = r'ImportedFbx/Sequencer') bpy.types.Scene.unreal_levelsequence_name = bpy.props.StringProperty( name = "Unreal sequencer name", description = "Unreal sequencer name", maxlen = 512, default = 'MySequence') def draw(self, context): scn = context.scene addon_prefs = bpy.context.preferences.addons["blender-for-unrealengine"].preferences #Sub folder if addon_prefs.useGeneratedScripts == True: propsSub = self.layout.row() propsSub = propsSub.column() propsSub.prop(scn, 'unreal_import_location', icon='FILE_FOLDER') propsSub.prop(scn, 'unreal_levelsequence_import_location', icon='FILE_FOLDER') propsSub.prop(scn, 'unreal_levelsequence_name', icon='FILE') else: self.layout.label(text='(Generated scripts are deactivated.)') class BFU_OT_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) class BFU_OT_UnrealPotentialError(bpy.types.PropertyGroup): type: IntProperty(default=0) #0:Info, 1:Warning, 2:Error object: PointerProperty(type=bpy.types.Object) vertexErrorType: StringProperty(default="None") #0:Info, 1:Warning, 2:Error itemName: StringProperty(default="None") text: StringProperty(default="Unknown") correctRef: StringProperty(default="None") correctlabel: StringProperty(default="Fix it !") correctDesc: StringProperty(default="Correct target error") class BFU_PT_Export(bpy.types.Panel): #Is Export panel bl_idname = "BFU_PT_Export" bl_label = "Export" bl_space_type = "VIEW_3D" bl_region_type = "UI" bl_category = "Unreal Engine 4" bl_parent_id = "BFU_PT_BlenderForUnreal" class BFU_OT_ShowAssetToExport(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.obj is not None: if asset.action is not None: if (type(asset.action) is bpy.types.Action): #Action name action = asset.action.name elif (type(asset.action) is bpy.types.AnimData): #Nonlinear name action = asset.obj.NLAAnimName else: action = "..." row.label(text="- ["+asset.obj.name+"] --> "+action+" ("+asset.type+")") else: if asset.type != "Collection StaticMesh": row.label(text="- "+asset.obj.name+" ("+asset.type+")") else: row.label(text="- "+asset.obj+" ("+asset.type+")") else: row.label(text="- ("+asset.type+")") bpy.context.window_manager.popup_menu(draw, title=popup_title, icon='PACKAGE') return {'FINISHED'} class BFU_OT_CheckPotentialErrorPopup(Operator): bl_label = "Check potential errors" bl_idname = "object.checkpotentialerror" bl_description = "Check potential errors" correctedProperty = 0 class BFU_OT_FixitTarget(Operator): bl_label = "Fix it !" bl_idname = "object.fixit_objet" 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'} class BFU_OT_SelectObjetButton(Operator): bl_label = "Select" bl_idname = "object.select_error_objet" bl_description = "Select target objet." errorIndex : bpy.props.IntProperty(default=-1) def execute(self, context): result = SelectPotentialErrorObject(self.errorIndex) return {'FINISHED'} class BFU_OT_SelectVertexButton(Operator): bl_label = "Select(Vertex)" bl_idname = "object.select_error_vertex" bl_description = "Select target vertex." errorIndex : bpy.props.IntProperty(default=-1) def execute(self, context): result = SelectPotentialErrorVertex(self.errorIndex) return {'FINISHED'} def execute(self, context): self.correctedProperty = CorrectBadProperty() UpdateNameHierarchy() UpdateUnrealPotentialError() return {'FINISHED'} def invoke(self, context, event): self.correctedProperty = CorrectBadProperty() UpdateNameHierarchy() UpdateUnrealPotentialError() wm = context.window_manager return wm.invoke_popup(self, width = 1020) 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(text=popup_title) layout.label(text="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(factor = 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 TextLine = myLine.column() splitedText = errorFullMsg.split("\n") for text, Line in enumerate(splitedText): if (text<1): TextLine.label(text=Line, icon=msgIcon) else: TextLine.label(text=Line) ButtonLine = myLine.column() if (error.correctRef != "None"): props = ButtonLine.operator("object.fixit_objet", text=error.correctlabel) props.errorIndex = x if (error.object is not None): props = ButtonLine.operator("object.select_error_objet") props.errorIndex = x if (error.vertexErrorType != "None"): props = ButtonLine.operator("object.select_error_vertex") props.errorIndex = x class BFU_OT_ExportForUnrealEngineButton(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 CheckIfFbxPluginIsActivated(): is_enabled, is_loaded = addon_utils.check("io_scene_fbx") if is_enabled == True and is_enabled == True: return True return False def GetIfOneTypeCheck(): if (scene.static_export or scene.static_collection_export or scene.skeletal_export or scene.anin_export or scene.alembic_export or scene.camera_export): return True else: return False if CheckIfFbxPluginIsActivated() == False: self.report({'WARNING'}, 'Add-on FBX format is not activated! Edit > Preferences > Add-ons > And check "FBX format"') return {'FINISHED'} if GetIfOneTypeCheck(): #Primary check if file is saved to avoid windows PermissionError if bpy.data.is_saved: scene.UnrealExportedAssetsList.clear() start_time = time.perf_counter() UpdateNameHierarchy() 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.perf_counter()-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 or collection to export.") else: self.report({'WARNING'}, "Please save this blend file before export") else: self.report({'WARNING'}, "No asset type is checked.") return {'FINISHED'} return {'FINISHED'} #Categories : bpy.types.Scene.static_export = bpy.props.BoolProperty( name = "StaticMesh(s)", description = "Check mark to export StaticMesh(s)", default = True ) bpy.types.Scene.static_collection_export = bpy.props.BoolProperty( name = "Collection(s) ", description = "Check mark to export Collection(s)", default = False ) bpy.types.Scene.skeletal_export = bpy.props.BoolProperty( name = "SkeletalMesh(s)", description = "Check mark to export SkeletalMesh(s)", 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.alembic_export = bpy.props.BoolProperty( name = "Alembic animation(s)", description = "Check mark to export Alembic animation(s)", default = False ) 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 file", default = True ) bpy.types.Scene.text_ImportAssetScript = bpy.props.BoolProperty( name = "Import assets script", description = "Check mark to write import asset script file", default = True ) bpy.types.Scene.text_ImportSequenceScript = bpy.props.BoolProperty( name = "Import sequence script", description = "Check mark to write import sequencer script file", default = True ) bpy.types.Scene.text_AdditionalData = bpy.props.BoolProperty( name = "Additional data", description = "Check mark to write additional data like parameter or anim tracks", default = True ) #exportProperty bpy.types.Scene.export_ExportOnlySelected = bpy.props.BoolProperty( name = "Export only select", description = "Check mark to export only selected export group. (export_recursive objects and auto childs) " , default = False ) def draw(self, context): scn = context.scene addon_prefs = bpy.context.preferences.addons["blender-for-unrealengine"].preferences #Categories : layout = self.layout row = layout.row() col = row.column() #Assets AssetsCol = row.column() AssetsCol.label(text="Asset types to export", icon='PACKAGE') AssetsCol.prop(scn, 'static_export') AssetsCol.prop(scn, 'static_collection_export') AssetsCol.prop(scn, 'skeletal_export') AssetsCol.prop(scn, 'anin_export') AssetsCol.prop(scn, 'alembic_export') AssetsCol.prop(scn, 'camera_export') layout.separator() #Additional file FileCol = row.column() FileCol.label(text="Additional file", icon='PACKAGE') FileCol.prop(scn, 'text_ExportLog') FileCol.prop(scn, 'text_ImportAssetScript') FileCol.prop(scn, 'text_ImportSequenceScript') if addon_prefs.useGeneratedScripts == True: FileCol.prop(scn, 'text_AdditionalData') #Feedback info : AssetNum = len(GetFinalAssetToExport()) AssetInfo = layout.row().box().split(factor = 0.75 ) AssetFeedback = str(AssetNum) + " Asset(s) will be exported." AssetInfo.label(text=AssetFeedback, icon='INFO') AssetInfo.operator("object.showasset") #Export button : checkButton = layout.row() checkButton.operator("object.checkpotentialerror", icon='FILE_TICK') #exportProperty exportOnlySelect = layout.row() exportOnlySelect.prop(scn, 'export_ExportOnlySelected') exportButton = layout.row() exportButton.scale_y = 2.0 exportButton.operator("object.exportforunreal", icon='EXPORT') class BFU_PT_Clipboard(bpy.types.Panel): #Is Clipboard panel bl_idname = "BFU_PT_Clipboard" bl_label = "Clipboard Copy" bl_space_type = "VIEW_3D" bl_region_type = "UI" bl_category = "Unreal Engine 4" bl_parent_id = "BFU_PT_BlenderForUnreal" class BFU_OT_CopyImportAssetScriptCommand(Operator): bl_label = "ImportAssetScript" bl_idname = "object.copy_importassetscript_command" bl_description = "Copy Import Asset Script command" def execute(self, context): scn = context.scene setWindowsClipboard(GetImportAssetScriptCommand()) self.report({'INFO'}, "command for "+scn.file_import_asset_script_name+" copied") return {'FINISHED'} class BFU_OT_CopyImportSequencerScriptCommand(Operator): bl_label = "ImportSequencerScript" bl_idname = "object.copy_importsequencerscript_command" bl_description = "Copy Import Sequencer Script command" def execute(self, context): scn = context.scene setWindowsClipboard(GetImportSequencerScriptCommand()) self.report({'INFO'}, "command for "+scn.file_import_sequencer_script_name+" copied") return {'FINISHED'} def draw(self, context): scn = context.scene layout = self.layout addon_prefs = bpy.context.preferences.addons["blender-for-unrealengine"].preferences if addon_prefs.useGeneratedScripts == True: layout.label(text="Click on one of the buttons to copy the import command.", icon='INFO') copyButton = layout.row() copyButton.operator("object.copy_importassetscript_command") copyButton.operator("object.copy_importsequencerscript_command") layout.label(text="Then you can paste it into the python console of unreal", icon='INFO') else: layout.label(text='(Generated scripts are deactivated.)') class BFU_PT_CorrectAndImprov(bpy.types.Panel): #Is Clipboard panel bl_idname = "BFU_PT_CorrectAndImprov" bl_label = "Correct and improv" bl_space_type = "VIEW_3D" bl_region_type = "UI" bl_category = "Unreal Engine 4 bis" bl_parent_id = "BFU_PT_BlenderForUnreal" class BFU_OT_CorrectExtremUV(Operator): bl_label = "Correct extrem UV For Unreal" bl_idname = "object.correct_extrem_uv" bl_description = "Correct extrem UV island of the selected object for better use in real time engines" bl_options = {'REGISTER', 'UNDO'} stepScale : bpy.props.IntProperty(name="Step scale", default=2, min=1, max=100) def execute(self, context): if bpy.context.active_object.mode == "EDIT": CorrectExtremeUV(stepScale = self.stepScale) self.report({'INFO'}, "UV corrected!" ) else: self.report({'WARNING'}, "Move to Edit mode for correct extrem UV." ) return {'FINISHED'} #############################[...]############################# classes = ( BFU_AP_AddonPreferences, BFU_AP_AddonPreferences.BFU_OT_NewReleaseInfo, BFU_AP_AddonPreferences.BFU_OT_OpenDocumentationTargetPage, BFU_PT_BlenderForUnreal, BFU_PT_BlenderForUnreal.BFU_MT_ObjectGlobalPropertiesPresets, BFU_PT_BlenderForUnreal.BFU_OT_AddObjectGlobalPropertiesPreset, BFU_PT_BlenderForUnreal.BFU_OT_OpenDocumentationPage, BFU_PT_ObjectProperties, BFU_PT_ObjectImportProperties, #BFU_OT_ObjExportAction, BFU_PT_AnimProperties, BFU_PT_AnimProperties.BFU_UL_ActionExportTarget, BFU_PT_AnimProperties.BFU_OT_UpdateObjActionListButton, BFU_PT_AnimProperties.BFU_OT_ShowActionToExport, BFU_PT_CollectionProperties, BFU_PT_CollectionProperties.BFU_UL_CollectionExportTarget, BFU_PT_CollectionProperties.BFU_OT_UpdateCollectionButton, BFU_PT_CollectionProperties.BFU_OT_ShowCollectionToExport, BFU_PT_AvancedObjectProperties, BFU_PT_CollisionsAndSockets, BFU_PT_CollisionsAndSockets.BFU_OT_ConvertToCollisionButtonBox, BFU_PT_CollisionsAndSockets.BFU_OT_ConvertToCollisionButtonCapsule, BFU_PT_CollisionsAndSockets.BFU_OT_ConvertToCollisionButtonSphere, BFU_PT_CollisionsAndSockets.BFU_OT_ConvertToCollisionButtonConvex, BFU_PT_CollisionsAndSockets.BFU_OT_ConvertToStaticSocketButton, BFU_PT_CollisionsAndSockets.BFU_OT_ConvertToSkeletalSocketButton, BFU_PT_Nomenclature, BFU_PT_Nomenclature.BFU_MT_NomenclaturePresets, BFU_PT_Nomenclature.BFU_OT_AddNomenclaturePreset, BFU_PT_ImportScript, #BFU_OT_UnrealExportedAsset, #BFU_OT_UnrealPotentialError, BFU_PT_Export, BFU_PT_Export.BFU_OT_ShowAssetToExport, BFU_PT_Export.BFU_OT_CheckPotentialErrorPopup, BFU_PT_Export.BFU_OT_CheckPotentialErrorPopup.BFU_OT_FixitTarget, BFU_PT_Export.BFU_OT_CheckPotentialErrorPopup.BFU_OT_SelectObjetButton, BFU_PT_Export.BFU_OT_CheckPotentialErrorPopup.BFU_OT_SelectVertexButton, BFU_PT_Export.BFU_OT_ExportForUnrealEngineButton, BFU_PT_Clipboard, BFU_PT_Clipboard.BFU_OT_CopyImportAssetScriptCommand, BFU_PT_Clipboard.BFU_OT_CopyImportSequencerScriptCommand, BFU_PT_CorrectAndImprov.BFU_OT_CorrectExtremUV ) def menu_func(self, context): layout = self.layout col = layout.column() col.separator(factor=1.0) col.operator(BFU_PT_CorrectAndImprov.BFU_OT_CorrectExtremUV.bl_idname) def register(): from bpy.utils import register_class for cls in classes: register_class(cls) bpy.utils.register_class(BFU_OT_ObjExportAction) bpy.types.Object.exportActionList = CollectionProperty(type=BFU_OT_ObjExportAction) bpy.utils.register_class(BFU_OT_SceneCollectionExport) bpy.types.Scene.CollectionExportList = CollectionProperty(type=BFU_OT_SceneCollectionExport) bpy.utils.register_class(BFU_OT_UnrealExportedAsset) bpy.types.Scene.UnrealExportedAssetsList = CollectionProperty(type=BFU_OT_UnrealExportedAsset) bpy.utils.register_class(BFU_OT_UnrealPotentialError) bpy.types.Scene.potentialErrorList = CollectionProperty(type=BFU_OT_UnrealPotentialError) bpy.types.VIEW3D_MT_uv_map.append(menu_func) def unregister(): from bpy.utils import unregister_class for cls in reversed(classes): unregister_class(cls) bpy.utils.unregister_class(BFU_OT_ObjExportAction) bpy.utils.unregister_class(BFU_OT_SceneCollectionExport) bpy.utils.unregister_class(BFU_OT_UnrealExportedAsset) bpy.utils.unregister_class(BFU_OT_UnrealPotentialError) bpy.types.VIEW3D_MT_uv_map.remove(menu_func) #register, unregister = bpy.utils.register_classes_factory(classes)