From 7f0781c0ce5204decaa5052b1b6e1e9f8fe3a826 Mon Sep 17 00:00:00 2001 From: Xavier Loux Date: Tue, 14 Feb 2023 16:55:21 +0100 Subject: [PATCH 1/8] vars renaming, better animation UI, more options for nla + save - UI updated - New options for set start/end animation time with NLA. --- Release log.txt | 11 +- blender-for-unrealengine/__init__.py | 14 +- blender-for-unrealengine/bfu_ui.py | 268 ++++++++++++------ blender-for-unrealengine/bfu_utils.py | 57 ++-- .../export/bfu_export_asset.py | 2 +- .../bfu_export_single_alembic_animation.py | 8 +- .../export/bfu_export_single_fbx_nla_anim.py | 8 +- 7 files changed, 243 insertions(+), 125 deletions(-) diff --git a/Release log.txt b/Release log.txt index faf4e522..d5051722 100644 --- a/Release log.txt +++ b/Release log.txt @@ -175,7 +175,7 @@ It is now possible to force the duration of an animation according to the scene - New: You can chose bone axis with skeletalMesh - New: Export use now a copy of the mesh for apply modifier, instance groups and collections - New: You can chose a presets for object global properties -- Change: AddOneAdditionalFramesAtTheEnd remplaced by StartFramesOffset and EndFramesOffset +- Change: AddOneAdditionalFramesAtTheEnd remplaced by bfu_anim_action_start_frame_offset and bfu_anim_action_end_frame_offset - Change: Export can use MetaData. You can activate in addon preference. - Change: It is now possible not to write the additional parameter, (.ini files) - Change: Import script sync the UE4 Content Browser to the imported asset (Vania python only) @@ -233,7 +233,7 @@ It is now possible to force the duration of an animation according to the scene - New: Show action(s) will now also display the frames range - New: Option for include or not the armature name in animation file name - Change: Works better with very complex rigs -- Change: StartFramesOffset set on 1 by default for animations cycles +- Change: bfu_anim_action_start_frame_offset set on 1 by default for animations cycles - Fixed: SocketsAdd90X break the socket export location - Fixed: modifier with no MESH type object can make script fail - Fixed: CheckArmatureMultipleRoots don't work good @@ -442,4 +442,9 @@ It is now possible to force the duration of an animation according to the scene - Fixed: StaticSocketsAdd90X option broke non-uniform scale sockets - Fixed: Collection static mesh did not use Sub Folder Name - Fixed: Socket transformations were not applied to Collection static mesh -- Fixed: Some collections remained hidden during export and exported files were incorrect \ No newline at end of file +- Fixed: Some collections remained hidden during export and exported files were incorrect + +== Rev 0.4.1 == + +- UI updated +- New options for set start/end animation time with NLA. \ No newline at end of file diff --git a/blender-for-unrealengine/__init__.py b/blender-for-unrealengine/__init__.py index db6a6a97..87b8f973 100644 --- a/blender-for-unrealengine/__init__.py +++ b/blender-for-unrealengine/__init__.py @@ -110,8 +110,11 @@ def register(): bpy.types.Scene.bfu_object_vertex_color_properties_expanded = bpy.props.BoolProperty() bpy.types.Scene.bfu_object_light_map_properties_expanded = bpy.props.BoolProperty() bpy.types.Scene.bfu_object_uv_map_properties_expanded = bpy.props.BoolProperty() - bpy.types.Scene.bfu_anim_properties_expanded = bpy.props.BoolProperty() - bpy.types.Scene.bfu_anim_advanced_properties_expanded = bpy.props.BoolProperty() + bpy.types.Scene.bfu_animation_action_properties_expanded = bpy.props.BoolProperty() + bpy.types.Scene.bfu_animation_action_advanced_properties_expanded = bpy.props.BoolProperty() + bpy.types.Scene.bfu_animation_nla_properties_expanded = bpy.props.BoolProperty() + bpy.types.Scene.bfu_animation_nla_advanced_properties_expanded = bpy.props.BoolProperty() + bpy.types.Scene.bfu_animation_advanced_properties_expanded = bpy.props.BoolProperty() bpy.types.Scene.bfu_skeleton_properties_expanded = bpy.props.BoolProperty() bpy.types.Scene.bfu_collection_properties_expanded = bpy.props.BoolProperty() bpy.types.Scene.bfu_object_advanced_properties_expanded = bpy.props.BoolProperty() @@ -167,8 +170,11 @@ def unregister(): del bpy.types.Scene.bfu_object_properties_expanded del bpy.types.Scene.bfu_object_import_properties_expanded - del bpy.types.Scene.bfu_anim_properties_expanded - del bpy.types.Scene.bfu_anim_advanced_properties_expanded + del bpy.types.Scene.bfu_animation_action_properties_expanded + del bpy.types.Scene.bfu_animation_action_advanced_properties_expanded + del bpy.types.Scene.bfu_animation_nla_properties_expanded + del bpy.types.Scene.bfu_animation_nla_advanced_properties_expanded + del bpy.types.Scene.bfu_animation_advanced_properties_expanded del bpy.types.Scene.bfu_collection_properties_expanded del bpy.types.Scene.bfu_object_advanced_properties_expanded del bpy.types.Scene.bfu_collision_socket_expanded diff --git a/blender-for-unrealengine/bfu_ui.py b/blender-for-unrealengine/bfu_ui.py index e0557d94..3a75432d 100644 --- a/blender-for-unrealengine/bfu_ui.py +++ b/blender-for-unrealengine/bfu_ui.py @@ -586,7 +586,7 @@ class BFU_PT_BlenderForUnrealObject(bpy.types.Panel): default=0 ) - bpy.types.Object.exportActionEnum = EnumProperty( + bpy.types.Object.bfu_anim_action_export_enum = EnumProperty( name="Action to export", description="Export procedure for actions (Animations and poses)", override={'LIBRARY_OVERRIDABLE'}, @@ -628,7 +628,7 @@ class BFU_PT_BlenderForUnrealObject(bpy.types.Panel): ) bpy.types.Object.PrefixNameToExport = StringProperty( - # properties used with ""export_specific_prefix" on exportActionEnum + # properties used with ""export_specific_prefix" on bfu_anim_action_export_enum name="Prefix name", description="Indicate the prefix of the actions that must be exported", override={'LIBRARY_OVERRIDABLE'}, @@ -636,8 +636,8 @@ class BFU_PT_BlenderForUnrealObject(bpy.types.Panel): default="Example_", ) - bpy.types.Object.AnimStartEndTimeEnum = EnumProperty( - name="Animation start/end time", + bpy.types.Object.bfu_anim_action_start_end_time_enum = EnumProperty( + name="Action Start/End Time", description="Set when animation starts and end", override={'LIBRARY_OVERRIDABLE'}, items=[ @@ -656,20 +656,20 @@ class BFU_PT_BlenderForUnrealObject(bpy.types.Panel): "Custom time", 'The time of all the animations of this object' + ' is defined by you.' + - ' Use "AnimCustomStartTime" and "AnimCustomEndTime"', + ' Use "bfu_anim_action_custom_start_frame" and "bfu_anim_action_custom_end_frame"', "HAND", 3), ] ) - bpy.types.Object.StartFramesOffset = IntProperty( + bpy.types.Object.bfu_anim_action_start_frame_offset = IntProperty( name="Offset at start frame", description="Offset for the start frame.", override={'LIBRARY_OVERRIDABLE'}, default=0 ) - bpy.types.Object.EndFramesOffset = IntProperty( + bpy.types.Object.bfu_anim_action_end_frame_offset = IntProperty( name="Offset at end frame", description=( "Offset for the end frame. +1" + @@ -680,20 +680,73 @@ class BFU_PT_BlenderForUnrealObject(bpy.types.Panel): default=0 ) - bpy.types.Object.AnimCustomStartTime = IntProperty( + bpy.types.Object.bfu_anim_action_custom_start_frame = IntProperty( name="Custom start time", description="Set when animation start", override={'LIBRARY_OVERRIDABLE'}, default=0 ) - bpy.types.Object.AnimCustomEndTime = IntProperty( + bpy.types.Object.bfu_anim_action_custom_end_frame = IntProperty( name="Custom end time", description="Set when animation end", override={'LIBRARY_OVERRIDABLE'}, default=1 ) + bpy.types.Object.bfu_anim_nla_start_end_time_enum = EnumProperty( + name="NLA Start/End Time", + description="Set when animation starts and end", + override={'LIBRARY_OVERRIDABLE'}, + items=[ + ("with_sceneframes", + "Scene time", + "Time will be equal to the scene time", + "SCENE_DATA", + 1), + ("with_customframes", + "Custom time", + 'The time of all the animations of this object' + + ' is defined by you.' + + ' Use "bfu_anim_action_custom_start_frame" and "bfu_anim_action_custom_end_frame"', + "HAND", + 2), + ] + ) + + bpy.types.Object.bfu_anim_nla_start_frame_offset = IntProperty( + name="Offset at start frame", + description="Offset for the start frame.", + override={'LIBRARY_OVERRIDABLE'}, + default=0 + ) + + bpy.types.Object.bfu_anim_nla_end_frame_offset = 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" + ), + override={'LIBRARY_OVERRIDABLE'}, + default=0 + ) + + bpy.types.Object.bfu_anim_nla_custom_start_frame = IntProperty( + name="Custom start time", + description="Set when animation start", + override={'LIBRARY_OVERRIDABLE'}, + default=0 + ) + + bpy.types.Object.bfu_anim_nla_custom_end_frame = IntProperty( + name="Custom end time", + description="Set when animation end", + override={'LIBRARY_OVERRIDABLE'}, + default=1 + ) + + bpy.types.Object.SampleAnimForExport = FloatProperty( name="Sampling Rate", description="How often to evaluate animated values (in frames)", @@ -716,7 +769,7 @@ class BFU_PT_BlenderForUnrealObject(bpy.types.Panel): default=0.0, ) - bpy.types.Object.ExportNLA = BoolProperty( + bpy.types.Object.bfu_anim_nla_use = BoolProperty( name="Export NLA (Nonlinear Animation)", description=( "If checked, exports the all animation of the scene with the NLA " + @@ -726,7 +779,7 @@ class BFU_PT_BlenderForUnrealObject(bpy.types.Panel): default=False ) - bpy.types.Object.NLAAnimName = StringProperty( + bpy.types.Object.bfu_anim_nla_export_name = StringProperty( name="NLA export name", description="Export NLA name (Don't work with Auto-Rig Pro for the moment.)", override={'LIBRARY_OVERRIDABLE'}, @@ -1062,7 +1115,7 @@ def execute(self, context): popup_title = "Action list" if len(animation_to_export) > 0: animationNumber = len(animation_to_export) - if obj.ExportNLA: + if obj.bfu_anim_nla_use: animationNumber += 1 popup_title = ( str(animationNumber) + @@ -1097,10 +1150,10 @@ def addAnimRow( GetActionType(action), frame_start, frame_end) - if obj.ExportNLA: + if obj.bfu_anim_nla_use: scene = context.scene addAnimRow( - obj.NLAAnimName, + obj.bfu_anim_nla_export_name, "NlAnim", str(scene.frame_start), str(scene.frame_end) @@ -1172,17 +1225,22 @@ class BFU_OT_AddObjectGlobalPropertiesPreset(AddPresetBase, Operator): 'obj.VertexOverrideColor', 'obj.VertexColorToUse', 'obj.VertexColorIndexToUse', - 'obj.exportActionEnum', + 'obj.bfu_anim_action_export_enum', 'obj.PrefixNameToExport', - 'obj.AnimStartEndTimeEnum', - 'obj.StartFramesOffset', - 'obj.EndFramesOffset', - 'obj.AnimCustomStartTime', - 'obj.AnimCustomEndTime', + 'obj.bfu_anim_action_start_end_time_enum', + 'obj.bfu_anim_nla_start_end_time_enum', + 'obj.bfu_anim_action_start_frame_offset', + 'obj.bfu_anim_action_end_frame_offset', + 'obj.bfu_anim_action_custom_start_frame', + 'obj.bfu_anim_action_custom_end_frame', + 'obj.bfu_anim_nla_start_frame_offset', + 'obj.bfu_anim_nla_end_frame_offset', + 'obj.bfu_anim_nla_custom_start_frame', + 'obj.bfu_anim_nla_custom_end_frame', 'obj.SampleAnimForExport', 'obj.SimplifyAnimForExport', - 'obj.ExportNLA', - 'obj.NLAAnimName', + 'obj.bfu_anim_nla_use', + 'obj.bfu_anim_nla_export_name', 'obj.bfu_anim_naming_type', 'obj.bfu_anim_naming_custom', 'obj.exportGlobalScale', @@ -1466,39 +1524,20 @@ def draw(self, contex): Ue4Skeleton.prop(obj, "bfu_target_skeleton_custom_ref") if bfu_ui_utils.DisplayPropertyFilter("OBJECT", "ANIM"): - bfu_ui_utils.LayoutSection(layout, "bfu_anim_properties_expanded", "Anim Properties") - if scene.bfu_anim_properties_expanded: - if obj is not None: - if obj.ExportEnum == "export_recursive" and not obj.ExportAsLod: + if obj is not None: + if obj.ExportEnum == "export_recursive" and not obj.ExportAsLod: + + bfu_ui_utils.LayoutSection(layout, "bfu_animation_action_properties_expanded", "Actions Properties") + if scene.bfu_animation_action_properties_expanded: if (GetAssetType(obj) == "SkeletalMesh" or GetAssetType(obj) == "Camera" or GetAssetType(obj) == "Alembic"): - # Action time - if obj.type != "CAMERA" and obj.bfu_export_procedure != "auto-rig-pro": - 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.prop(obj, 'bfu_anim_action_export_enum') + if obj.bfu_anim_action_export_enum == "export_specific_list": ActionListProperty.template_list( # type and unique id "BFU_UL_ActionExportTarget", "", @@ -1512,77 +1551,120 @@ def draw(self, contex): ActionListProperty.operator( "object.updateobjactionlist", icon='RECOVER_LAST') - if obj.exportActionEnum == "export_specific_prefix": + if obj.bfu_anim_action_export_enum == "export_specific_prefix": ActionListProperty.prop(obj, 'PrefixNameToExport') - # NLA - if GetAssetType(obj) == "SkeletalMesh": - NLAAnim = layout.row() - NLAAnim.prop(obj, 'ExportNLA') - NLAAnimChild = NLAAnim.column() - NLAAnimChild.enabled = obj.ExportNLA - NLAAnimChild.prop(obj, 'NLAAnimName') - if obj.bfu_export_procedure == "auto-rig-pro": - NLAAnim.enabled = False - NLAAnimChild.enabled = False - - # Animation fbx properties - if (GetAssetType(obj) != "Alembic"): - propsFbx = layout.row() - if obj.bfu_export_procedure != "auto-rig-pro": - propsFbx.prop(obj, 'SampleAnimForExport') - propsFbx.prop(obj, 'SimplifyAnimForExport') + # Action Time + if obj.type != "CAMERA" and obj.bfu_export_procedure != "auto-rig-pro": + ActionTimeProperty = layout.column() + ActionTimeProperty.enabled = obj.bfu_anim_action_export_enum != 'dont_export' + ActionTimeProperty.prop(obj, 'bfu_anim_action_start_end_time_enum') + if obj.bfu_anim_action_start_end_time_enum == "with_customframes": + OfsetTime = ActionTimeProperty.row() + OfsetTime.prop(obj, 'bfu_anim_action_custom_start_frame') + OfsetTime.prop(obj, 'bfu_anim_action_custom_end_frame') + if obj.bfu_anim_action_start_end_time_enum != "with_customframes": + OfsetTime = ActionTimeProperty.row() + OfsetTime.prop(obj, 'bfu_anim_action_start_frame_offset') + OfsetTime.prop(obj, 'bfu_anim_action_end_frame_offset') + + else: + layout.label( + text=( + "Note: animation start/end use scene frames" + + " with the camera for the sequencer.") + ) # Nomenclature if GetAssetType(obj) == "SkeletalMesh": export_anim_naming = layout.column() + export_anim_naming.enabled = obj.bfu_anim_action_export_enum != 'dont_export' export_anim_naming.prop(obj, 'bfu_anim_naming_type') if obj.bfu_anim_naming_type == "include_custom_name": export_anim_naming_text = export_anim_naming.column() export_anim_naming_text.prop(obj, 'bfu_anim_naming_custom') - # Armature export action list feedback - if GetAssetType(obj) == "SkeletalMesh": - layout.label( - text='Note: The Action with only one' + - ' frame are exported like Pose.') - ArmaturePropertyInfo = ( - layout.row().box().split(factor=0.75) - ) - ActionNum = len(GetActionToExport(obj)) - if obj.ExportNLA: - ActionNum += 1 - actionFeedback = ( - str(ActionNum) + - " Animation(s) will be exported with this object.") - ArmaturePropertyInfo.label( - text=actionFeedback, - icon='INFO') - ArmaturePropertyInfo.operator("object.showobjaction") + 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.)') - bfu_ui_utils.LayoutSection(layout, "bfu_anim_advanced_properties_expanded", "Animation advanced Properties") - if scene.bfu_anim_advanced_properties_expanded: - if obj is not None: - if obj.ExportEnum == "export_recursive": + bfu_ui_utils.LayoutSection(layout, "bfu_animation_action_advanced_properties_expanded", "Actions Advanced Properties") + if scene.bfu_animation_action_advanced_properties_expanded: if GetAssetType(obj) != "Alembic": transformProp = layout.column() + transformProp.enabled = obj.bfu_anim_action_export_enum != 'dont_export' transformProp.prop(obj, "MoveActionToCenterForExport") transformProp.prop(obj, "RotateActionToZeroForExport") + bfu_ui_utils.LayoutSection(layout, "bfu_animation_nla_properties_expanded", "NLA Properties") + if scene.bfu_animation_nla_properties_expanded: + # NLA + if GetAssetType(obj) == "SkeletalMesh": + NLAAnim = layout.row() + NLAAnim.prop(obj, 'bfu_anim_nla_use') + NLAAnimChild = NLAAnim.column() + NLAAnimChild.enabled = obj.bfu_anim_nla_use + NLAAnimChild.prop(obj, 'bfu_anim_nla_export_name') + if obj.bfu_export_procedure == "auto-rig-pro": + NLAAnim.enabled = False + NLAAnimChild.enabled = False + + # NLA Time + if obj.type != "CAMERA" and obj.bfu_export_procedure != "auto-rig-pro": + NLATimeProperty = layout.column() + NLATimeProperty.enabled = obj.bfu_anim_nla_use + NLATimeProperty.prop(obj, 'bfu_anim_nla_start_end_time_enum') + if obj.bfu_anim_nla_start_end_time_enum == "with_customframes": + OfsetTime = NLATimeProperty.row() + OfsetTime.prop(obj, 'bfu_anim_nla_custom_start_frame') + OfsetTime.prop(obj, 'bfu_anim_nla_custom_end_frame') + if obj.bfu_anim_nla_start_end_time_enum != "with_customframes": + OfsetTime = NLATimeProperty.row() + OfsetTime.prop(obj, 'bfu_anim_nla_start_frame_offset') + OfsetTime.prop(obj, 'bfu_anim_nla_end_frame_offset') + + bfu_ui_utils.LayoutSection(layout, "bfu_animation_nla_advanced_properties_expanded", "NLA Advanced Properties") + if scene.bfu_animation_nla_advanced_properties_expanded: + if GetAssetType(obj) != "Alembic": transformProp2 = layout.column() + transformProp2.enabled = obj.bfu_anim_nla_use transformProp2.prop(obj, "MoveNLAToCenterForExport") transformProp2.prop(obj, "RotateNLAToZeroForExport") + + bfu_ui_utils.LayoutSection(layout, "bfu_animation_advanced_properties_expanded", "Animation Advanced Properties") + if scene.bfu_animation_advanced_properties_expanded: + # Animation fbx properties + if (GetAssetType(obj) != "Alembic"): + propsFbx = layout.row() + if obj.bfu_export_procedure != "auto-rig-pro": + propsFbx.prop(obj, 'SampleAnimForExport') + propsFbx.prop(obj, 'SimplifyAnimForExport') + + # Armature export action list feedback + if GetAssetType(obj) == "SkeletalMesh": + layout.label( + text='Note: The Action with only one' + + ' frame are exported like Pose.') + ArmaturePropertyInfo = ( + layout.row().box().split(factor=0.75) + ) + ActionNum = len(GetActionToExport(obj)) + if obj.bfu_anim_nla_use: + ActionNum += 1 + actionFeedback = ( + str(ActionNum) + + " Animation(s) will be exported with this object.") + ArmaturePropertyInfo.label( + text=actionFeedback, + icon='INFO') + ArmaturePropertyInfo.operator("object.showobjaction") else: layout.label(text='(No properties to show.)') + else: + layout.label(text='(No properties to show.)') if bfu_ui_utils.DisplayPropertyFilter("OBJECT", "MISC"): bfu_ui_utils.LayoutSection(layout, "bfu_object_lod_properties_expanded", "Lod") @@ -2375,7 +2457,7 @@ def draw(self, context): action = asset.action.name elif (type(asset.action) is bpy.types.AnimData): # Nonlinear name - action = asset.obj.NLAAnimName + action = asset.obj.bfu_anim_nla_export_name else: action = "..." row.label( diff --git a/blender-for-unrealengine/bfu_utils.py b/blender-for-unrealengine/bfu_utils.py index 2b131165..191bffb2 100644 --- a/blender-for-unrealengine/bfu_utils.py +++ b/blender-for-unrealengine/bfu_utils.py @@ -513,27 +513,27 @@ def GetActionToExport(obj): return [] TargetActionToExport = [] # Action list - if obj.exportActionEnum == "dont_export": + if obj.bfu_anim_action_export_enum == "dont_export": return [] - if obj.exportActionEnum == "export_current": + if obj.bfu_anim_action_export_enum == "export_current": if obj.animation_data is not None: if obj.animation_data.action is not None: return [obj.animation_data.action] - elif obj.exportActionEnum == "export_specific_list": + elif obj.bfu_anim_action_export_enum == "export_specific_list": for action in bpy.data.actions: for targetAction in obj.exportActionList: if targetAction.use: if targetAction.name == action.name: TargetActionToExport.append(action) - elif obj.exportActionEnum == "export_specific_prefix": + elif obj.bfu_anim_action_export_enum == "export_specific_prefix": for action in bpy.data.actions: if fnmatch.fnmatchcase(action.name, obj.PrefixNameToExport+"*"): TargetActionToExport.append(action) - elif obj.exportActionEnum == "export_auto": + elif obj.bfu_anim_action_export_enum == "export_auto": TargetActionToExport = GetCachedExportAutoActionList(obj) return TargetActionToExport @@ -582,30 +582,55 @@ def GetDesiredActionStartEndTime(obj, action): endTime = startTime+1 return (startTime, endTime) - elif obj.AnimStartEndTimeEnum == "with_keyframes": + elif obj.bfu_anim_action_start_end_time_enum == "with_keyframes": # GetFirstActionFrame + Offset - startTime = int(action.frame_range.x) + obj.StartFramesOffset + startTime = int(action.frame_range.x) + obj.bfu_anim_action_start_frame_offset # GetLastActionFrame + Offset - endTime = int(action.frame_range.y) + obj.EndFramesOffset + endTime = int(action.frame_range.y) + obj.bfu_anim_action_end_frame_offset if endTime <= startTime: endTime = startTime+1 return (startTime, endTime) - elif obj.AnimStartEndTimeEnum == "with_sceneframes": - startTime = scene.frame_start + obj.StartFramesOffset - endTime = scene.frame_end + obj.EndFramesOffset + elif obj.bfu_anim_action_start_end_time_enum == "with_sceneframes": + startTime = scene.frame_start + obj.bfu_anim_action_start_frame_offset + endTime = scene.frame_end + obj.bfu_anim_action_end_frame_offset if endTime <= startTime: endTime = startTime+1 return (startTime, endTime) - elif obj.AnimStartEndTimeEnum == "with_customframes": - startTime = obj.AnimCustomStartTime - endTime = obj.AnimCustomEndTime + elif obj.bfu_anim_action_start_end_time_enum == "with_customframes": + startTime = obj.bfu_anim_action_custom_start_frame + endTime = obj.bfu_anim_action_custom_end_frame if endTime <= startTime: endTime = startTime+1 return (startTime, endTime) +def GetDesiredNLAStartEndTime(obj): + # Returns desired nla anim start/end time + # Return start with index 0 and end with index 1 + # EndTime should be a less one frame bigger than StartTime + + scene = bpy.context.scene + + + if obj.bfu_anim_nla_start_end_time_enum == "with_sceneframes": + startTime = scene.frame_start + obj.bfu_anim_nla_start_frame_offset + endTime = scene.frame_end + obj.bfu_anim_nla_end_frame_offset + if endTime <= startTime: + endTime = startTime+1 + + return (startTime, endTime) + + elif obj.bfu_anim_nla_start_end_time_enum == "with_customframes": + startTime = obj.bfu_anim_nla_custom_start_frame + endTime = obj.bfu_anim_nla_custom_end_frame + if endTime <= startTime: + endTime = startTime+1 + + return (startTime, endTime) + + def GetUseCustomLightMapResolution(obj): if obj.StaticMeshLightMapEnum == "Default": return False @@ -1184,7 +1209,7 @@ def __init__(self, obj, action, type): # NLA if scene.anin_export: - if obj.ExportNLA: + if obj.bfu_anim_nla_use: TargetAssetToExport.append(AssetToExport( obj, obj.animation_data, @@ -1357,7 +1382,7 @@ def GetNLAExportFileName(obj, fileType=".fbx"): if obj.bfu_anim_naming_type == "include_custom_name": ArmatureName = obj.bfu_anim_naming_custom+"_" - return ValidFilename(scene.anim_prefix_export_name+ArmatureName+obj.NLAAnimName+fileType) + return ValidFilename(scene.anim_prefix_export_name+ArmatureName+obj.bfu_anim_nla_export_name+fileType) def GetImportAssetScriptCommand(): diff --git a/blender-for-unrealengine/export/bfu_export_asset.py b/blender-for-unrealengine/export/bfu_export_asset.py index fa3326eb..bde7f287 100644 --- a/blender-for-unrealengine/export/bfu_export_asset.py +++ b/blender-for-unrealengine/export/bfu_export_asset.py @@ -259,7 +259,7 @@ def UpdateExportProgress(time=None): # NLA animation print("Start Export NLA(s)") if IsValidActionForExport(scene, obj, "NLA"): - if obj.ExportNLA: + if obj.bfu_anim_nla_use: # Save current start/end frame UserStartFrame = scene.frame_start UserEndFrame = scene.frame_end diff --git a/blender-for-unrealengine/export/bfu_export_single_alembic_animation.py b/blender-for-unrealengine/export/bfu_export_single_alembic_animation.py index 4422d8d9..7970ad64 100644 --- a/blender-for-unrealengine/export/bfu_export_single_alembic_animation.py +++ b/blender-for-unrealengine/export/bfu_export_single_alembic_animation.py @@ -86,8 +86,8 @@ def ExportSingleAlembicAnimation( SelectParentAndDesiredChilds(obj) - scene.frame_start += obj.StartFramesOffset - scene.frame_end += obj.EndFramesOffset + scene.frame_start += obj.bfu_anim_action_start_frame_offset + scene.frame_end += obj.bfu_anim_action_end_frame_offset # Export bpy.ops.wm.alembic_export( @@ -97,8 +97,8 @@ def ExportSingleAlembicAnimation( triangulate=True, ) - scene.frame_start -= obj.StartFramesOffset - scene.frame_end -= obj.EndFramesOffset + scene.frame_start -= obj.bfu_anim_action_start_frame_offset + scene.frame_end -= obj.bfu_anim_action_end_frame_offset for obj in scene.objects: ClearAllBFUTempVars(obj) diff --git a/blender-for-unrealengine/export/bfu_export_single_fbx_nla_anim.py b/blender-for-unrealengine/export/bfu_export_single_fbx_nla_anim.py index 9b3fa4b9..7c9694df 100644 --- a/blender-for-unrealengine/export/bfu_export_single_fbx_nla_anim.py +++ b/blender-for-unrealengine/export/bfu_export_single_fbx_nla_anim.py @@ -141,8 +141,8 @@ def ExportSingleFbxNLAAnim( RescaleRigConsraints(active, rrf) - # scene.frame_start += active.StartFramesOffset - # scene.frame_end += active.EndFramesOffset + scene.frame_start = GetDesiredNLAStartEndTime(active)[0] + scene.frame_end = GetDesiredNLAStartEndTime(active)[1] asset_name.SetExportName() @@ -181,8 +181,8 @@ def ExportSingleFbxNLAAnim( ) ResetArmaturePose(active) - # scene.frame_start -= active.StartFramesOffset - # scene.frame_end -= active.EndFramesOffset + # scene.frame_start -= active.bfu_anim_action_start_frame_offset + # scene.frame_end -= active.bfu_anim_action_end_frame_offset asset_name.ResetNames() From 9b5b5cbbcec6b4c218b714271a5e218301479d89 Mon Sep 17 00:00:00 2001 From: BleuRaven Date: Wed, 5 Apr 2023 12:48:41 +0200 Subject: [PATCH 2/8] Fix ConvertGeometryNodeAttributeToUV fail after 3.5 --- .../export/bfu_export_utils.py | 1303 +++++++++-------- 1 file changed, 653 insertions(+), 650 deletions(-) diff --git a/blender-for-unrealengine/export/bfu_export_utils.py b/blender-for-unrealengine/export/bfu_export_utils.py index a256e742..9814f57a 100644 --- a/blender-for-unrealengine/export/bfu_export_utils.py +++ b/blender-for-unrealengine/export/bfu_export_utils.py @@ -1,650 +1,653 @@ -# ====================== 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 - -if "bpy" in locals(): - import importlib - if "bfu_write_text" in locals(): - importlib.reload(bfu_write_text) - if "bfu_basics" in locals(): - importlib.reload(bfu_basics) - if "bfu_utils" in locals(): - importlib.reload(bfu_utils) - if "bfu_export_get_info" in locals(): - importlib.reload(bfu_export_get_info) - -from .. import bfu_write_text -from .. import bfu_basics -from ..bfu_basics import * -from .. import bfu_utils -from ..bfu_utils import * -from ..bbpl import utils - -from . import bfu_export_get_info -from .bfu_export_get_info import * - -dup_temp_name = "BFU_Temp" # DuplicateTemporarilyNameForUe4Export -Export_temp_preFix = "_ESO_Temp" # _ExportSubObject_TempName - - -def GetExportFullpath(dirpath, filename): - absdirpath = bpy.path.abspath(dirpath) - VerifiDirs(absdirpath) - return os.path.join(absdirpath, filename) - - -def ApplyProxyData(obj): - - # Apply proxy data if needed. - if GetExportProxyChild(obj) is not None: - - def ReasignProxySkeleton(newArmature, oldArmature): - for select in bpy.context.selected_objects: - if select.type == "CURVE": - for mod in select.modifiers: - if mod.type == "HOOK": - if mod.object == oldArmature: - matrix_inverse = mod.matrix_inverse.copy() - mod.object = newArmature - mod.matrix_inverse = matrix_inverse - - else: - for mod in select.modifiers: - if mod.type == 'ARMATURE': - if mod.object == oldArmature: - mod.object = newArmature - - for bone in newArmature.pose.bones: - for cons in bone.constraints: - if hasattr(cons, 'target'): - if cons.target == oldArmature: - cons.target = newArmature - else: - ChildProxyName = ( - cons.target.name + - "_UEProxyChild" - ) - if ChildProxyName in bpy.data.objects: - cons.target = bpy.data.objects[ChildProxyName] - - # Get old armature in selected objects - OldProxyChildArmature = None - for selectedObj in bpy.context.selected_objects: - if selectedObj != obj: - if selectedObj.type == "ARMATURE": - OldProxyChildArmature = selectedObj - - # Reasing parent + add to remove - if OldProxyChildArmature is not None: - ToRemove = [] - ToRemove.append(OldProxyChildArmature) - for selectedObj in bpy.context.selected_objects: - if selectedObj != obj: - if selectedObj.parent == OldProxyChildArmature: - # Reasing parent and keep position - SavedPos = selectedObj.matrix_world.copy() - selectedObj.name += "_UEProxyChild" - selectedObj.parent = obj - selectedObj.matrix_world = SavedPos - else: - ToRemove.append(selectedObj) - ReasignProxySkeleton(obj, OldProxyChildArmature) - SavedSelect = GetCurrentSelection() - RemovedObjects = CleanDeleteObjects(ToRemove) - SavedSelect.RemoveFromListByName(RemovedObjects) - SetCurrentSelection(SavedSelect) - - -def BakeArmatureAnimation(armature, frame_start, frame_end): - # Change to pose mode - SavedSelect = GetCurrentSelection() - bpy.ops.object.select_all(action='DESELECT') - SelectSpecificObject(armature) - bpy.ops.nla.bake( - frame_start=frame_start-10, - frame_end=frame_end+10, - only_selected=False, - visual_keying=True, - clear_constraints=True, - use_current_action=False, - bake_types={'POSE'} - ) - bpy.ops.object.select_all(action='DESELECT') - SetCurrentSelection(SavedSelect) - - -def DuplicateSelectForExport(new_name="duplicated Obj"): - # Note: Need look for a optimized duplicate, This is too long - - scene = bpy.context.scene - - class DuplicateData(): - def __init__(self): - self.data_to_remove = [] - self.origin_select = None - self.duplicate_select = None - - def SetOriginSelect(self): - select = bbpl.utils.UserSelectSave() - select.SaveCurrentSelect() - self.origin_select = select - - def SetDuplicateSelect(self): - select = bbpl.utils.UserSelectSave() - select.SaveCurrentSelect() - self.duplicate_select = select - - class DelegateOldData(): - # contain a data to remove and function for remove - - def __init__(self, data_name, data_type): - self.data_name = data_name - self.data_type = data_type - - def RemoveData(self): - RemoveUselessSpecificData(self.data_name, self.data_type) - - duplicate_data = DuplicateData() - duplicate_data.SetOriginSelect() - for user_selected in duplicate_data.origin_select.user_selecteds: - if user_selected: - SaveObjCurrentName(user_selected) - if user_selected.type == "ARMATURE": - SetObjProxyData(user_selected) - - data_to_remove = [] - - # Save action befor export - actionNames = [] - for action in bpy.data.actions: - actionNames.append(action.name) - - bpy.ops.object.duplicate() - - # Save the name for found after "Make Instances Real" - currentSelectNames = [] - for currentSelectName in bpy.context.selected_objects: - currentSelectNames.append(currentSelectName.name) - - for objSelect in currentSelectNames: - if objSelect not in bpy.context.selected_objects: - bpy.data.objects[objSelect].select_set(True) - - # Make sigle user and clean useless data. - for objScene in bpy.context.selected_objects: - if objScene.data is not None: - oldData = objScene.data.name - objScene.data = objScene.data.copy() - data_to_remove.append(DelegateOldData(oldData, objScene.type)) - - # Clean create actions by duplication - for action in bpy.data.actions: - if action.name not in actionNames: - bpy.data.actions.remove(action) - - duplicate_data.SetDuplicateSelect() - - return duplicate_data - - -def SetDuplicateNameForExport(duplicate_data, origin_prefix="or_"): - for user_selected in duplicate_data.origin_select.user_selecteds: - user_selected.name = origin_prefix+user_selected.name - - for user_selected in duplicate_data.duplicate_select.user_selecteds: - user_selected.name = GetObjOriginName(user_selected) - - -def ResetDuplicateNameAfterExport(duplicate_data): - for user_selected in duplicate_data.origin_select.user_selecteds: - user_selected.name = GetObjOriginName(user_selected) - ClearObjOriginNameVar(user_selected) - - -def MakeSelectVisualReal(): - select = bbpl.utils.UserSelectSave() - select.SaveCurrentSelect() - - # Save object list - previous_objects = [] - for obj in bpy.data.objects: - previous_objects.append(obj) - - # Visual Transform Apply - bpy.ops.object.visual_transform_apply() - - # Make Instances Real - bpy.ops.object.duplicates_make_real( - use_base_parent=True, - use_hierarchy=True - ) - - select.ResetSelectByName() - - # Select the new objects - for obj in bpy.data.objects: - if obj not in previous_objects: - obj.select_set(True) - -# Sockets - - -def SetSocketsExportName(obj): - ''' - Try to apply the custom SocketName - ''' - - scene = bpy.context.scene - for socket in GetSocketDesiredChild(obj): - if socket.bfu_use_socket_custom_Name: - if socket.bfu_socket_custom_Name not in scene.objects: - - # Save the previous name - socket["BFU_PreviousSocketName"] = socket.name - socket.name = "SOCKET_"+socket.bfu_socket_custom_Name - else: - print( - 'Can\'t rename socket "' + - socket.name + - '" to "'+socket.bfu_socket_custom_Name + - '".' - ) - - -def SetSocketsExportTransform(obj): - # Set socket Transform for Unreal - - addon_prefs = GetAddonPrefs() - for socket in GetSocketDesiredChild(obj): - socket["BFU_PreviousSocketScale"] = socket.scale - socket["BFU_PreviousSocketLocation"] = socket.location - socket["BFU_PreviousSocketRotationEuler"] = socket.rotation_euler - if GetShouldRescaleSocket(): - socket.delta_scale *= GetRescaleSocketFactor() - - if addon_prefs.staticSocketsAdd90X: - savedScale = socket.scale.copy() - savedLocation = socket.location.copy() - AddMat = mathutils.Matrix.Rotation(math.radians(90.0), 4, 'X') - socket.matrix_world = socket.matrix_world @ AddMat - socket.scale.x = savedScale.x - socket.scale.z = savedScale.y - socket.scale.y = savedScale.z - socket.location = savedLocation - - -def ResetSocketsExportName(obj): - # Reset socket Name - - scene = bpy.context.scene - for socket in GetSocketDesiredChild(obj): - if "BFU_PreviousSocketName" in socket: - socket.name = socket["BFU_PreviousSocketName"] - del socket["BFU_PreviousSocketName"] - - -def ResetSocketsTransform(obj): - # Reset socket Transform - - scene = bpy.context.scene - for socket in GetSocketDesiredChild(obj): - if "BFU_PreviousSocketScale" in socket: - socket.scale = socket["BFU_PreviousSocketScale"] - del socket["BFU_PreviousSocketScale"] - if "BFU_PreviousSocketLocation" in socket: - socket.location = socket["BFU_PreviousSocketLocation"] - del socket["BFU_PreviousSocketLocation"] - if "BFU_PreviousSocketRotationEuler" in socket: - socket.rotation_euler = socket["BFU_PreviousSocketRotationEuler"] - del socket["BFU_PreviousSocketRotationEuler"] - - -# Main asset - - -class PrepareExportName(): - def __init__(self, obj, is_armature): - # Rename temporarily the assets - if obj: - - self.target_object = obj - self.is_armature = is_armature - self.old_asset_name = "" - self.new_asset_name = "" - - scene = bpy.context.scene - if self.is_armature: - self.new_asset_name = GetDesiredExportArmatureName(obj) - else: - self.new_asset_name = obj.name # Keep the same name - - def SetExportName(self): - - ''' - Set the name of the asset for export - ''' - - scene = bpy.context.scene - obj = self.target_object - if obj.name != self.new_asset_name: - self.old_asset_name = obj.name - # Avoid same name for two assets - if self.new_asset_name in scene.objects: - confli_asset = scene.objects[self.new_asset_name] - confli_asset.name = dup_temp_name - obj.name = self.new_asset_name - - def ResetNames(self): - ''' - Reset names after export - ''' - - scene = bpy.context.scene - if self.old_asset_name != "": - obj = self.target_object - obj.name = self.old_asset_name - - if dup_temp_name in scene.objects: - armature = scene.objects[dup_temp_name] - armature.name = self.new_asset_name - - pass - -# UVs - - -def ConvertGeometryNodeAttributeToUV(obj): - # obj = bpy.context.active_object # Debug - if obj.convert_geometry_node_attribute_to_uv: - attrib_name = obj.convert_geometry_node_attribute_to_uv_name - - # I need apply the geometry modifier for get the data. - # So this work only when I do export of the duplicate object. - - if hasattr(obj.data, "attributes"): # Cuves has not attributes. - if attrib_name in obj.data.attributes: - - # TO DO: Bad why to do this. Need found a way to convert without using ops. - obj.data.attributes.active = obj.data.attributes[attrib_name] - SavedSelect = GetCurrentSelection() - SelectSpecificObject(obj) - bpy.ops.geometry.attribute_convert(mode='UV_MAP') - SetCurrentSelection(SavedSelect) - return - - attrib = obj.data.attributes[attrib_name] - - new_uv = obj.data.uv_layers.new(name=attrib_name) - uv_coords = [] - - attrib.data # TO DO: I don't understand why attrib.data is egal at zero just after a duplicate. - print('XXXXXXXXXXXX') - print(type(attrib.data)) - print('XXXXXXXXXXXX') - print(dir(attrib.data)) - print('XXXXXXXXXXXX') - print(attrib.data.values()) - print('XXXXXXXXXXXX') - attrib_data = [] - attrib.data.foreach_get('vector', attrib_data) - print(attrib_data) - - for fv_attrib in attrib.data: # FloatVectorAttributeValue - uv_coords.append(fv_attrib.vector) - uv_coords.append(attrib.data[0]) - - for loop in obj.data.loops: - new_uv.data[loop.index].uv[0] = uv_coords[loop.index][0] - new_uv.data[loop.index].uv[1] = uv_coords[loop.index][1] - - obj.data.attributes.remove(attrib_name) - - -def CorrectExtremUVAtExport(obj): - if obj.correct_extrem_uv_scale: - SavedSelect = GetCurrentSelection() - if GoToMeshEditMode(): - CorrectExtremeUV(2) - bbpl.utils.SafeModeSet('OBJECT') - SetCurrentSelection(SavedSelect) - return True - return False - -# Armature - - -def ConvertArmatureConstraintToModifiers(armature): - for obj in GetExportDesiredChilds(armature): - previous_enabled_armature_constraints = [] - - for const in obj.constraints: - if const.type == "ARMATURE": - if const.enabled is True: - previous_enabled_armature_constraints.append(const.name) - - # Disable constraint - const.enabled = False - - # Remove All Vertex Group - # TO DO: - - # Add Vertex Group - for target in const.targets: - bone_name = target.subtarget - group = obj.vertex_groups.new(name=bone_name) - - vertex_indices = range(0, len(obj.data.vertices)) - group.add(vertex_indices, 1.0, 'REPLACE') - - # Add armature modifier - mod = obj.modifiers.new("BFU_Const_"+const.name, "ARMATURE") - mod.object = armature - - # Save data for reset after export - obj["BFU_PreviousEnabledArmatureConstraints"] = previous_enabled_armature_constraints - - -def ResetArmatureConstraintToModifiers(armature): - for obj in GetExportDesiredChilds(armature): - if "BFU_PreviousEnabledArmatureConstraints" in obj: - for const_names in obj["BFU_PreviousEnabledArmatureConstraints"]: - const = obj.constraints[const_names] - - # Remove created armature for export - mod = obj.modifiers["BFU_Const_"+const_names] - obj.modifiers.remove(mod) - - # Remove created Vertex Group - for target in const.targets: - bone_name = target.subtarget - old_vertex_group = obj.vertex_groups[bone_name] - obj.vertex_groups.remove(old_vertex_group) - - # Reset all Vertex Groups - # TO DO: - - # Enable back constraint - const.enabled = True - -# Vertex Color - - -def SetVertexColorForUnrealExport(parent): - - objs = GetExportDesiredChilds(parent) - objs.append(parent) - - for obj in objs: - if obj.type == "MESH": - vced = VertexColorExportData(obj, parent) - if vced.export_type == "REPLACE": - - vertex_colors = utils.getVertexColors(obj) - - # Save the previous target - obj.data["BFU_PreviousTargetIndex"] = vertex_colors.active_index - - # Ser the vertex color for export - vertex_colors.active_index = vced.index - - -def ClearVertexColorForUnrealExport(parent): - - objs = GetExportDesiredChilds(parent) - objs.append(parent) - for obj in objs: - if obj.type == "MESH": - if "BFU_PreviousTargetIndex" in obj.data: - del obj.data["BFU_PreviousTargetIndex"] - - -def GetShouldRescaleRig(obj): - # This will return if the rig should be rescale. - - if obj.bfu_export_procedure == "auto-rig-pro": - return False - - addon_prefs = GetAddonPrefs() - if addon_prefs.rescaleFullRigAtExport == "auto": - if math.isclose( - bpy.context.scene.unit_settings.scale_length, - 0.01, - rel_tol=1e-5, - ): - - return False # False because that useless to rescale at 1 :v - else: - return True - if addon_prefs.rescaleFullRigAtExport == "custom_rescale": - return True - if addon_prefs.rescaleFullRigAtExport == "dont_rescale": - return False - return False - - -def GetRescaleRigFactor(): - # This will return the rescale factor. - - addon_prefs = GetAddonPrefs() - if addon_prefs.rescaleFullRigAtExport == "auto": - return 100 * bpy.context.scene.unit_settings.scale_length - else: - return addon_prefs.newRigScale # rigRescaleFactor - - -def GetShouldRescaleSocket(): - # This will return if the socket should be rescale. - - addon_prefs = GetAddonPrefs() - if addon_prefs.rescaleSocketsAtExport == "auto": - if bpy.context.scene.unit_settings.scale_length == 0.01: - return False # False because that useless to rescale at 1 :v - else: - return True - if addon_prefs.rescaleSocketsAtExport == "custom_rescale": - return True - if addon_prefs.rescaleSocketsAtExport == "dont_rescale": - return False - return False - - -def GetRescaleSocketFactor(): - # This will return the rescale factor. - - addon_prefs = GetAddonPrefs() - if addon_prefs.rescaleSocketsAtExport == "auto": - return 1/(100*bpy.context.scene.unit_settings.scale_length) - else: - return addon_prefs.staticSocketsImportedSize - - -def ExportAutoProRig( - filepath, - use_selection=True, - export_rig_name="root", - bake_anim=True, - anim_export_name_string="", - mesh_smooth_type="OFF", - arp_simplify_fac=0.0 - ): - - bpy.context.scene.arp_engine_type = 'unreal' - bpy.context.scene.arp_export_rig_type = 'mped' # types: 'humanoid', 'mped' - bpy.context.scene.arp_ge_sel_only = use_selection - - # Rig - bpy.context.scene.arp_export_twist = False - bpy.context.scene.arp_export_noparent = False - bpy.context.scene.arp_units_x100 = True - bpy.context.scene.arp_ue_root_motion = True - - # Anim - bpy.context.scene.arp_bake_actions = bake_anim - bpy.context.scene.arp_export_name_actions = True - bpy.context.scene.arp_export_name_string = anim_export_name_string - bpy.context.scene.arp_simplify_fac = arp_simplify_fac - - # Misc - bpy.context.scene.arp_mesh_smooth_type = mesh_smooth_type - bpy.context.scene.arp_use_tspace = False - bpy.context.scene.arp_fix_fbx_matrix = False - bpy.context.scene.arp_fix_fbx_rot = False - bpy.context.scene.arp_init_fbx_rot = False - bpy.context.scene.arp_bone_axis_primary_export = 'Y' - bpy.context.scene.arp_bone_axis_secondary_export = 'X' - bpy.context.scene.arp_export_rig_name = export_rig_name - - # export it - print("Start AutoProRig Export") - bpy.ops.id.arp_export_fbx_panel(filepath=filepath) - - -def ExportSingleAdditionalTrackCamera(dirpath, filename, obj): - # Export additional camera track for ue4 - # FocalLength - # FocusDistance - # Aperture - - absdirpath = bpy.path.abspath(dirpath) - VerifiDirs(absdirpath) - AdditionalTrack = bfu_write_text.WriteCameraAnimationTracks(obj) - return bfu_write_text.ExportSingleJson( - AdditionalTrack, - absdirpath, - filename - ) - - -def ExportAdditionalParameter(dirpath, unreal_exported_asset): - # Export additional parameter from static and skeletal mesh track for ue4 - # SocketsList - - filename = unreal_exported_asset.GetFilename("_AdditionalTrack.json") - - absdirpath = bpy.path.abspath(dirpath) - VerifiDirs(absdirpath) - AdditionalTrack = bfu_write_text.WriteSingleMeshAdditionalParameter(unreal_exported_asset) - return bfu_write_text.ExportSingleJson( - AdditionalTrack, - absdirpath, - filename - ) +# ====================== 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 + +if "bpy" in locals(): + import importlib + if "bfu_write_text" in locals(): + importlib.reload(bfu_write_text) + if "bfu_basics" in locals(): + importlib.reload(bfu_basics) + if "bfu_utils" in locals(): + importlib.reload(bfu_utils) + if "bfu_export_get_info" in locals(): + importlib.reload(bfu_export_get_info) + +from .. import bfu_write_text +from .. import bfu_basics +from ..bfu_basics import * +from .. import bfu_utils +from ..bfu_utils import * +from ..bbpl import utils + +from . import bfu_export_get_info +from .bfu_export_get_info import * + +dup_temp_name = "BFU_Temp" # DuplicateTemporarilyNameForUe4Export +Export_temp_preFix = "_ESO_Temp" # _ExportSubObject_TempName + + +def GetExportFullpath(dirpath, filename): + absdirpath = bpy.path.abspath(dirpath) + VerifiDirs(absdirpath) + return os.path.join(absdirpath, filename) + + +def ApplyProxyData(obj): + + # Apply proxy data if needed. + if GetExportProxyChild(obj) is not None: + + def ReasignProxySkeleton(newArmature, oldArmature): + for select in bpy.context.selected_objects: + if select.type == "CURVE": + for mod in select.modifiers: + if mod.type == "HOOK": + if mod.object == oldArmature: + matrix_inverse = mod.matrix_inverse.copy() + mod.object = newArmature + mod.matrix_inverse = matrix_inverse + + else: + for mod in select.modifiers: + if mod.type == 'ARMATURE': + if mod.object == oldArmature: + mod.object = newArmature + + for bone in newArmature.pose.bones: + for cons in bone.constraints: + if hasattr(cons, 'target'): + if cons.target == oldArmature: + cons.target = newArmature + else: + ChildProxyName = ( + cons.target.name + + "_UEProxyChild" + ) + if ChildProxyName in bpy.data.objects: + cons.target = bpy.data.objects[ChildProxyName] + + # Get old armature in selected objects + OldProxyChildArmature = None + for selectedObj in bpy.context.selected_objects: + if selectedObj != obj: + if selectedObj.type == "ARMATURE": + OldProxyChildArmature = selectedObj + + # Reasing parent + add to remove + if OldProxyChildArmature is not None: + ToRemove = [] + ToRemove.append(OldProxyChildArmature) + for selectedObj in bpy.context.selected_objects: + if selectedObj != obj: + if selectedObj.parent == OldProxyChildArmature: + # Reasing parent and keep position + SavedPos = selectedObj.matrix_world.copy() + selectedObj.name += "_UEProxyChild" + selectedObj.parent = obj + selectedObj.matrix_world = SavedPos + else: + ToRemove.append(selectedObj) + ReasignProxySkeleton(obj, OldProxyChildArmature) + SavedSelect = GetCurrentSelection() + RemovedObjects = CleanDeleteObjects(ToRemove) + SavedSelect.RemoveFromListByName(RemovedObjects) + SetCurrentSelection(SavedSelect) + + +def BakeArmatureAnimation(armature, frame_start, frame_end): + # Change to pose mode + SavedSelect = GetCurrentSelection() + bpy.ops.object.select_all(action='DESELECT') + SelectSpecificObject(armature) + bpy.ops.nla.bake( + frame_start=frame_start-10, + frame_end=frame_end+10, + only_selected=False, + visual_keying=True, + clear_constraints=True, + use_current_action=False, + bake_types={'POSE'} + ) + bpy.ops.object.select_all(action='DESELECT') + SetCurrentSelection(SavedSelect) + + +def DuplicateSelectForExport(new_name="duplicated Obj"): + # Note: Need look for a optimized duplicate, This is too long + + scene = bpy.context.scene + + class DuplicateData(): + def __init__(self): + self.data_to_remove = [] + self.origin_select = None + self.duplicate_select = None + + def SetOriginSelect(self): + select = bbpl.utils.UserSelectSave() + select.SaveCurrentSelect() + self.origin_select = select + + def SetDuplicateSelect(self): + select = bbpl.utils.UserSelectSave() + select.SaveCurrentSelect() + self.duplicate_select = select + + class DelegateOldData(): + # contain a data to remove and function for remove + + def __init__(self, data_name, data_type): + self.data_name = data_name + self.data_type = data_type + + def RemoveData(self): + RemoveUselessSpecificData(self.data_name, self.data_type) + + duplicate_data = DuplicateData() + duplicate_data.SetOriginSelect() + for user_selected in duplicate_data.origin_select.user_selecteds: + if user_selected: + SaveObjCurrentName(user_selected) + if user_selected.type == "ARMATURE": + SetObjProxyData(user_selected) + + data_to_remove = [] + + # Save action befor export + actionNames = [] + for action in bpy.data.actions: + actionNames.append(action.name) + + bpy.ops.object.duplicate() + + # Save the name for found after "Make Instances Real" + currentSelectNames = [] + for currentSelectName in bpy.context.selected_objects: + currentSelectNames.append(currentSelectName.name) + + for objSelect in currentSelectNames: + if objSelect not in bpy.context.selected_objects: + bpy.data.objects[objSelect].select_set(True) + + # Make sigle user and clean useless data. + for objScene in bpy.context.selected_objects: + if objScene.data is not None: + oldData = objScene.data.name + objScene.data = objScene.data.copy() + data_to_remove.append(DelegateOldData(oldData, objScene.type)) + + # Clean create actions by duplication + for action in bpy.data.actions: + if action.name not in actionNames: + bpy.data.actions.remove(action) + + duplicate_data.SetDuplicateSelect() + + return duplicate_data + + +def SetDuplicateNameForExport(duplicate_data, origin_prefix="or_"): + for user_selected in duplicate_data.origin_select.user_selecteds: + user_selected.name = origin_prefix+user_selected.name + + for user_selected in duplicate_data.duplicate_select.user_selecteds: + user_selected.name = GetObjOriginName(user_selected) + + +def ResetDuplicateNameAfterExport(duplicate_data): + for user_selected in duplicate_data.origin_select.user_selecteds: + user_selected.name = GetObjOriginName(user_selected) + ClearObjOriginNameVar(user_selected) + + +def MakeSelectVisualReal(): + select = bbpl.utils.UserSelectSave() + select.SaveCurrentSelect() + + # Save object list + previous_objects = [] + for obj in bpy.data.objects: + previous_objects.append(obj) + + # Visual Transform Apply + bpy.ops.object.visual_transform_apply() + + # Make Instances Real + bpy.ops.object.duplicates_make_real( + use_base_parent=True, + use_hierarchy=True + ) + + select.ResetSelectByName() + + # Select the new objects + for obj in bpy.data.objects: + if obj not in previous_objects: + obj.select_set(True) + +# Sockets + + +def SetSocketsExportName(obj): + ''' + Try to apply the custom SocketName + ''' + + scene = bpy.context.scene + for socket in GetSocketDesiredChild(obj): + if socket.bfu_use_socket_custom_Name: + if socket.bfu_socket_custom_Name not in scene.objects: + + # Save the previous name + socket["BFU_PreviousSocketName"] = socket.name + socket.name = "SOCKET_"+socket.bfu_socket_custom_Name + else: + print( + 'Can\'t rename socket "' + + socket.name + + '" to "'+socket.bfu_socket_custom_Name + + '".' + ) + + +def SetSocketsExportTransform(obj): + # Set socket Transform for Unreal + + addon_prefs = GetAddonPrefs() + for socket in GetSocketDesiredChild(obj): + socket["BFU_PreviousSocketScale"] = socket.scale + socket["BFU_PreviousSocketLocation"] = socket.location + socket["BFU_PreviousSocketRotationEuler"] = socket.rotation_euler + if GetShouldRescaleSocket(): + socket.delta_scale *= GetRescaleSocketFactor() + + if addon_prefs.staticSocketsAdd90X: + savedScale = socket.scale.copy() + savedLocation = socket.location.copy() + AddMat = mathutils.Matrix.Rotation(math.radians(90.0), 4, 'X') + socket.matrix_world = socket.matrix_world @ AddMat + socket.scale.x = savedScale.x + socket.scale.z = savedScale.y + socket.scale.y = savedScale.z + socket.location = savedLocation + + +def ResetSocketsExportName(obj): + # Reset socket Name + + scene = bpy.context.scene + for socket in GetSocketDesiredChild(obj): + if "BFU_PreviousSocketName" in socket: + socket.name = socket["BFU_PreviousSocketName"] + del socket["BFU_PreviousSocketName"] + + +def ResetSocketsTransform(obj): + # Reset socket Transform + + scene = bpy.context.scene + for socket in GetSocketDesiredChild(obj): + if "BFU_PreviousSocketScale" in socket: + socket.scale = socket["BFU_PreviousSocketScale"] + del socket["BFU_PreviousSocketScale"] + if "BFU_PreviousSocketLocation" in socket: + socket.location = socket["BFU_PreviousSocketLocation"] + del socket["BFU_PreviousSocketLocation"] + if "BFU_PreviousSocketRotationEuler" in socket: + socket.rotation_euler = socket["BFU_PreviousSocketRotationEuler"] + del socket["BFU_PreviousSocketRotationEuler"] + + +# Main asset + + +class PrepareExportName(): + def __init__(self, obj, is_armature): + # Rename temporarily the assets + if obj: + + self.target_object = obj + self.is_armature = is_armature + self.old_asset_name = "" + self.new_asset_name = "" + + scene = bpy.context.scene + if self.is_armature: + self.new_asset_name = GetDesiredExportArmatureName(obj) + else: + self.new_asset_name = obj.name # Keep the same name + + def SetExportName(self): + + ''' + Set the name of the asset for export + ''' + + scene = bpy.context.scene + obj = self.target_object + if obj.name != self.new_asset_name: + self.old_asset_name = obj.name + # Avoid same name for two assets + if self.new_asset_name in scene.objects: + confli_asset = scene.objects[self.new_asset_name] + confli_asset.name = dup_temp_name + obj.name = self.new_asset_name + + def ResetNames(self): + ''' + Reset names after export + ''' + + scene = bpy.context.scene + if self.old_asset_name != "": + obj = self.target_object + obj.name = self.old_asset_name + + if dup_temp_name in scene.objects: + armature = scene.objects[dup_temp_name] + armature.name = self.new_asset_name + + pass + +# UVs + + +def ConvertGeometryNodeAttributeToUV(obj): + # obj = bpy.context.active_object # Debug + if obj.convert_geometry_node_attribute_to_uv: + attrib_name = obj.convert_geometry_node_attribute_to_uv_name + + # I need apply the geometry modifier for get the data. + # So this work only when I do export of the duplicate object. + + if hasattr(obj.data, "attributes"): # Cuves has not attributes. + if attrib_name in obj.data.attributes: + + # TO DO: Bad why to do this. Need found a way to convert without using ops. + obj.data.attributes.active = obj.data.attributes[attrib_name] + SavedSelect = GetCurrentSelection() + SelectSpecificObject(obj) + if bpy.app.version >= (3, 5, 0): + bpy.ops.geometry.attribute_convert(mode='VERTEX_GROUP') + else: + bpy.ops.geometry.attribute_convert(mode='UV_MAP') + SetCurrentSelection(SavedSelect) + return + + attrib = obj.data.attributes[attrib_name] + + new_uv = obj.data.uv_layers.new(name=attrib_name) + uv_coords = [] + + attrib.data # TO DO: I don't understand why attrib.data is egal at zero just after a duplicate. + print('XXXXXXXXXXXX') + print(type(attrib.data)) + print('XXXXXXXXXXXX') + print(dir(attrib.data)) + print('XXXXXXXXXXXX') + print(attrib.data.values()) + print('XXXXXXXXXXXX') + attrib_data = [] + attrib.data.foreach_get('vector', attrib_data) + print(attrib_data) + + for fv_attrib in attrib.data: # FloatVectorAttributeValue + uv_coords.append(fv_attrib.vector) + uv_coords.append(attrib.data[0]) + + for loop in obj.data.loops: + new_uv.data[loop.index].uv[0] = uv_coords[loop.index][0] + new_uv.data[loop.index].uv[1] = uv_coords[loop.index][1] + + obj.data.attributes.remove(attrib_name) + + +def CorrectExtremUVAtExport(obj): + if obj.correct_extrem_uv_scale: + SavedSelect = GetCurrentSelection() + if GoToMeshEditMode(): + CorrectExtremeUV(2) + bbpl.utils.SafeModeSet('OBJECT') + SetCurrentSelection(SavedSelect) + return True + return False + +# Armature + + +def ConvertArmatureConstraintToModifiers(armature): + for obj in GetExportDesiredChilds(armature): + previous_enabled_armature_constraints = [] + + for const in obj.constraints: + if const.type == "ARMATURE": + if const.enabled is True: + previous_enabled_armature_constraints.append(const.name) + + # Disable constraint + const.enabled = False + + # Remove All Vertex Group + # TO DO: + + # Add Vertex Group + for target in const.targets: + bone_name = target.subtarget + group = obj.vertex_groups.new(name=bone_name) + + vertex_indices = range(0, len(obj.data.vertices)) + group.add(vertex_indices, 1.0, 'REPLACE') + + # Add armature modifier + mod = obj.modifiers.new("BFU_Const_"+const.name, "ARMATURE") + mod.object = armature + + # Save data for reset after export + obj["BFU_PreviousEnabledArmatureConstraints"] = previous_enabled_armature_constraints + + +def ResetArmatureConstraintToModifiers(armature): + for obj in GetExportDesiredChilds(armature): + if "BFU_PreviousEnabledArmatureConstraints" in obj: + for const_names in obj["BFU_PreviousEnabledArmatureConstraints"]: + const = obj.constraints[const_names] + + # Remove created armature for export + mod = obj.modifiers["BFU_Const_"+const_names] + obj.modifiers.remove(mod) + + # Remove created Vertex Group + for target in const.targets: + bone_name = target.subtarget + old_vertex_group = obj.vertex_groups[bone_name] + obj.vertex_groups.remove(old_vertex_group) + + # Reset all Vertex Groups + # TO DO: + + # Enable back constraint + const.enabled = True + +# Vertex Color + + +def SetVertexColorForUnrealExport(parent): + + objs = GetExportDesiredChilds(parent) + objs.append(parent) + + for obj in objs: + if obj.type == "MESH": + vced = VertexColorExportData(obj, parent) + if vced.export_type == "REPLACE": + + vertex_colors = utils.getVertexColors(obj) + + # Save the previous target + obj.data["BFU_PreviousTargetIndex"] = vertex_colors.active_index + + # Ser the vertex color for export + vertex_colors.active_index = vced.index + + +def ClearVertexColorForUnrealExport(parent): + + objs = GetExportDesiredChilds(parent) + objs.append(parent) + for obj in objs: + if obj.type == "MESH": + if "BFU_PreviousTargetIndex" in obj.data: + del obj.data["BFU_PreviousTargetIndex"] + + +def GetShouldRescaleRig(obj): + # This will return if the rig should be rescale. + + if obj.bfu_export_procedure == "auto-rig-pro": + return False + + addon_prefs = GetAddonPrefs() + if addon_prefs.rescaleFullRigAtExport == "auto": + if math.isclose( + bpy.context.scene.unit_settings.scale_length, + 0.01, + rel_tol=1e-5, + ): + + return False # False because that useless to rescale at 1 :v + else: + return True + if addon_prefs.rescaleFullRigAtExport == "custom_rescale": + return True + if addon_prefs.rescaleFullRigAtExport == "dont_rescale": + return False + return False + + +def GetRescaleRigFactor(): + # This will return the rescale factor. + + addon_prefs = GetAddonPrefs() + if addon_prefs.rescaleFullRigAtExport == "auto": + return 100 * bpy.context.scene.unit_settings.scale_length + else: + return addon_prefs.newRigScale # rigRescaleFactor + + +def GetShouldRescaleSocket(): + # This will return if the socket should be rescale. + + addon_prefs = GetAddonPrefs() + if addon_prefs.rescaleSocketsAtExport == "auto": + if bpy.context.scene.unit_settings.scale_length == 0.01: + return False # False because that useless to rescale at 1 :v + else: + return True + if addon_prefs.rescaleSocketsAtExport == "custom_rescale": + return True + if addon_prefs.rescaleSocketsAtExport == "dont_rescale": + return False + return False + + +def GetRescaleSocketFactor(): + # This will return the rescale factor. + + addon_prefs = GetAddonPrefs() + if addon_prefs.rescaleSocketsAtExport == "auto": + return 1/(100*bpy.context.scene.unit_settings.scale_length) + else: + return addon_prefs.staticSocketsImportedSize + + +def ExportAutoProRig( + filepath, + use_selection=True, + export_rig_name="root", + bake_anim=True, + anim_export_name_string="", + mesh_smooth_type="OFF", + arp_simplify_fac=0.0 + ): + + bpy.context.scene.arp_engine_type = 'unreal' + bpy.context.scene.arp_export_rig_type = 'mped' # types: 'humanoid', 'mped' + bpy.context.scene.arp_ge_sel_only = use_selection + + # Rig + bpy.context.scene.arp_export_twist = False + bpy.context.scene.arp_export_noparent = False + bpy.context.scene.arp_units_x100 = True + bpy.context.scene.arp_ue_root_motion = True + + # Anim + bpy.context.scene.arp_bake_actions = bake_anim + bpy.context.scene.arp_export_name_actions = True + bpy.context.scene.arp_export_name_string = anim_export_name_string + bpy.context.scene.arp_simplify_fac = arp_simplify_fac + + # Misc + bpy.context.scene.arp_mesh_smooth_type = mesh_smooth_type + bpy.context.scene.arp_use_tspace = False + bpy.context.scene.arp_fix_fbx_matrix = False + bpy.context.scene.arp_fix_fbx_rot = False + bpy.context.scene.arp_init_fbx_rot = False + bpy.context.scene.arp_bone_axis_primary_export = 'Y' + bpy.context.scene.arp_bone_axis_secondary_export = 'X' + bpy.context.scene.arp_export_rig_name = export_rig_name + + # export it + print("Start AutoProRig Export") + bpy.ops.id.arp_export_fbx_panel(filepath=filepath) + + +def ExportSingleAdditionalTrackCamera(dirpath, filename, obj): + # Export additional camera track for ue4 + # FocalLength + # FocusDistance + # Aperture + + absdirpath = bpy.path.abspath(dirpath) + VerifiDirs(absdirpath) + AdditionalTrack = bfu_write_text.WriteCameraAnimationTracks(obj) + return bfu_write_text.ExportSingleJson( + AdditionalTrack, + absdirpath, + filename + ) + + +def ExportAdditionalParameter(dirpath, unreal_exported_asset): + # Export additional parameter from static and skeletal mesh track for ue4 + # SocketsList + + filename = unreal_exported_asset.GetFilename("_AdditionalTrack.json") + + absdirpath = bpy.path.abspath(dirpath) + VerifiDirs(absdirpath) + AdditionalTrack = bfu_write_text.WriteSingleMeshAdditionalParameter(unreal_exported_asset) + return bfu_write_text.ExportSingleJson( + AdditionalTrack, + absdirpath, + filename + ) From a840249fe4d1bdcbe1d640707b651388689bb436 Mon Sep 17 00:00:00 2001 From: BleuRaven Date: Wed, 5 Apr 2023 12:52:53 +0200 Subject: [PATCH 3/8] - Update Export Button Name --- Release log.txt | 4 +- blender-for-unrealengine/bfu_ui.py | 6202 ++++++++++++++-------------- 2 files changed, 3104 insertions(+), 3102 deletions(-) diff --git a/Release log.txt b/Release log.txt index d5051722..17ad5a4f 100644 --- a/Release log.txt +++ b/Release log.txt @@ -447,4 +447,6 @@ It is now possible to force the duration of an animation according to the scene == Rev 0.4.1 == - UI updated -- New options for set start/end animation time with NLA. \ No newline at end of file +- Update Export Button Name +- New options for set start/end animation time with NLA. +- Fixed: ConvertGeometryNodeAttributeToUV fail after Blender 3.5 \ No newline at end of file diff --git a/blender-for-unrealengine/bfu_ui.py b/blender-for-unrealengine/bfu_ui.py index 3a75432d..79e8094b 100644 --- a/blender-for-unrealengine/bfu_ui.py +++ b/blender-for-unrealengine/bfu_ui.py @@ -1,3101 +1,3101 @@ -# ====================== 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 -import addon_utils -import time - -from .export import bfu_export_asset -from . import bfu_write_text -from . import bfu_basics -from .bfu_basics import * -from . import bfu_utils -from .bfu_utils import * -from .export import bfu_export_get_info -from .export.bfu_export_get_info import * -from . import bfu_check_potential_error -from . import bfu_ui_utils -from . import languages -from .languages import * - - -if "bpy" in locals(): - import importlib - if "bfu_export_asset" in locals(): - importlib.reload(bfu_export_asset) - if "bfu_write_text" in locals(): - importlib.reload(bfu_write_text) - if "bfu_basics" in locals(): - importlib.reload(bfu_basics) - if "bfu_utils" in locals(): - importlib.reload(bfu_utils) - if "bfu_export_get_info" in locals(): - importlib.reload(bfu_export_get_info) - if "bfu_check_potential_error" in locals(): - importlib.reload(bfu_check_potential_error) - if "bfu_ui_utils" in locals(): - importlib.reload(bfu_ui_utils) - if "languages" in locals(): - importlib.reload(languages) - - -from bpy.props import ( - StringProperty, - BoolProperty, - EnumProperty, - IntProperty, - FloatProperty, - FloatVectorProperty, - PointerProperty, - CollectionProperty, - ) - -from bpy.types import ( - Operator, - ) - - -class BFU_OT_ObjExportAction(bpy.types.PropertyGroup): - name: StringProperty(name="Action data name", default="Unknown", override={'LIBRARY_OVERRIDABLE'}) - use: BoolProperty(name="use this action", default=False, override={'LIBRARY_OVERRIDABLE'}) - - -class BFU_OT_SceneCollectionExport(bpy.types.PropertyGroup): - name: StringProperty(name="collection data name", default="Unknown", override={'LIBRARY_OVERRIDABLE'}) - use: BoolProperty(name="export this collection", default=False, override={'LIBRARY_OVERRIDABLE'}) - - -class BFU_PT_BlenderForUnrealObject(bpy.types.Panel): - # Unreal engine export panel - - bl_idname = "BFU_PT_BlenderForUnrealObject" - bl_label = "Blender for Unreal Engine" - bl_space_type = "VIEW_3D" - bl_region_type = "UI" - bl_category = "Unreal Engine" - - # Object Properties - bpy.types.Object.ExportEnum = EnumProperty( - name="Export type", - description="Export procedure", - override={'LIBRARY_OVERRIDABLE'}, - items=[ - ("auto", - "Auto", - "Export with the parent if 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=( - 'The name of sub folder.' + - ' You can now use ../ for up one directory.' - ), - override={'LIBRARY_OVERRIDABLE'}, - maxlen=64, - default="", - subtype='FILE_NAME' - ) - - bpy.types.Collection.exportFolderName = StringProperty( - name="Sub folder name", - description=( - 'The name of sub folder.' + - ' You can now use ../ for up one directory.' - ), - override={'LIBRARY_OVERRIDABLE'}, - maxlen=64, - default="", - subtype='FILE_NAME' - ) - - bpy.types.Object.bfu_export_fbx_camera = BoolProperty( - name="Export camera fbx", - description=( - 'True: export .Fbx object and AdditionalTrack.ini ' + - 'Else: export only AdditionalTrack.ini' - ), - override={'LIBRARY_OVERRIDABLE'}, - default=False, - ) - - bpy.types.Object.bfu_fix_axis_flippings = BoolProperty( - name="Fix camera axis flippings", - description=( - 'Disable only if you use extrem camera animation in one frame.' - ), - override={'LIBRARY_OVERRIDABLE'}, - default=True, - ) - - bpy.types.Object.bfu_export_procedure = EnumProperty( - name="Export procedure", - description=( - "This will define how the object should" + - " be exported in case you are using specific Rig." - ), - override={'LIBRARY_OVERRIDABLE'}, - items=[ - ("normal", - "Normal", - "No specific Rig.", - "ARMATURE_DATA", - 1), - ("auto-rig-pro", - "AutoRigPro", - "Export using AutoRigPro.", - "ARMATURE_DATA", - 2), - ] - ) - - bpy.types.Object.ExportAsAlembic = BoolProperty( - name="Export as Alembic animation", - description=( - "If true this mesh will be exported as a Alembic animation" - ), - override={'LIBRARY_OVERRIDABLE'}, - 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" - ), - override={'LIBRARY_OVERRIDABLE'}, - default=False - ) - - bpy.types.Object.ForceStaticMesh = BoolProperty( - name="Force staticMesh", - description="Force export asset like a StaticMesh if is ARMATURE type", - override={'LIBRARY_OVERRIDABLE'}, - 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)" - ), - override={'LIBRARY_OVERRIDABLE'}, - default=True - ) - - bpy.types.Object.bfu_use_custom_export_name = BoolProperty( - name="Export with custom name.", - description=( - "Specify a custom name for the exported file" - ), - override={'LIBRARY_OVERRIDABLE'}, - default=False - ) - - bpy.types.Object.bfu_custom_export_name = StringProperty( - name="", - description="The name of exported file", - override={'LIBRARY_OVERRIDABLE'}, - default="MyObjectExportName.fbx" - ) - - # Object Import Properties - - # Lod list - bpy.types.Object.Ue4Lod1 = PointerProperty( - name="LOD1", - description="Target objet for level of detail 01", - override={'LIBRARY_OVERRIDABLE'}, - type=bpy.types.Object - ) - - bpy.types.Object.Ue4Lod2 = PointerProperty( - name="LOD2", - description="Target objet for level of detail 02", - override={'LIBRARY_OVERRIDABLE'}, - type=bpy.types.Object - ) - - bpy.types.Object.Ue4Lod3 = PointerProperty( - name="LOD3", - description="Target objet for level of detail 03", - override={'LIBRARY_OVERRIDABLE'}, - type=bpy.types.Object - ) - - bpy.types.Object.Ue4Lod4 = PointerProperty( - name="LOD4", - description="Target objet for level of detail 04", - override={'LIBRARY_OVERRIDABLE'}, - type=bpy.types.Object - ) - - bpy.types.Object.Ue4Lod5 = PointerProperty( - name="LOD5", - description="Target objet for level of detail 05", - override={'LIBRARY_OVERRIDABLE'}, - 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", - override={'LIBRARY_OVERRIDABLE'}, - default=True - ) - - bpy.types.Object.bfu_skeleton_search_mode = EnumProperty( - name="Skeleton search mode", - description='Specify the skeleton location in Unreal', - override={'LIBRARY_OVERRIDABLE'}, - items=[ - ("auto", - "Auto", - "...", - 1), - ("custom_name", - "Custom name", - "Default location with custom name", - 2), - ("custom_path_name", - "Custom path and name", - "Set the custom light map resolution", - 3), - ("custom_reference", - "custom reference", - "Reference from Unreal.", - 4) - ] - ) - - bpy.types.Object.bfu_target_skeleton_custom_path = StringProperty( - name="", - description="The path of the Skeleton in Unreal. Skeleton not the skeletal mesh.", - override={'LIBRARY_OVERRIDABLE'}, - default="ImportedFbx" - ) - - bpy.types.Object.bfu_target_skeleton_custom_name = StringProperty( - name="", - description="The name of the Skeleton in Unreal. Skeleton not the skeletal mesh.", - override={'LIBRARY_OVERRIDABLE'}, - default="SKM_MySketonName_Skeleton" - ) - - bpy.types.Object.bfu_target_skeleton_custom_ref = StringProperty( - name="", - description=( - "The full reference of the skeleton in Unreal. " + - "(Use right clic on asset and copy reference.)" - ), - override={'LIBRARY_OVERRIDABLE'}, - default="SkeletalMesh'/Game/ImportedFbx/SKM_MySketonName_Skeleton.SKM_MySketonName_Skeleton'" - ) - - # StaticMeshImportData - # https://api.unrealengine.com/INT/API/Editor/UnrealEd/Factories/UFbxStaticMeshImportData/index.html - - bpy.types.Object.UseStaticMeshLODGroup = BoolProperty( - name="", - description='', - override={'LIBRARY_OVERRIDABLE'}, - 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" - ), - override={'LIBRARY_OVERRIDABLE'}, - maxlen=32, - default="SmallProp" - ) - - bpy.types.Object.StaticMeshLightMapEnum = EnumProperty( - name="Light map", - description='Specify how the light map resolution will be generated', - override={'LIBRARY_OVERRIDABLE'}, - items=[ - ("Default", - "Default", - "Has no effect on light maps", - 1), - ("CustomMap", - "Custom map", - "Set the custom light map resolution", - 2), - ("SurfaceArea", - "surface Area", - "Set light map resolution depending on the surface Area", - 3) - ] - ) - - bpy.types.Object.customStaticMeshLightMapRes = IntProperty( - name="Light Map resolution", - description="This is the resolution of the light map", - override={'LIBRARY_OVERRIDABLE'}, - soft_max=2048, - soft_min=16, - max=4096, # Max for unreal - min=4, # Min for unreal - default=64 - ) - - bpy.types.Object.computedStaticMeshLightMapRes = FloatProperty( - name="computed Light Map resolution", - description="This is the computed resolution of the light map", - override={'LIBRARY_OVERRIDABLE'}, - default=64.0 - ) - - bpy.types.Object.staticMeshLightMapSurfaceScale = FloatProperty( - name="Surface scale", - description="This is for resacle the surface Area value", - override={'LIBRARY_OVERRIDABLE'}, - min=0.00001, # Min for unreal - default=64 - ) - - bpy.types.Object.staticMeshLightMapRoundPowerOfTwo = BoolProperty( - name="Round power of 2", - description=( - "round Light Map resolution to nearest power of 2" - ), - default=True - ) - - bpy.types.Object.useStaticMeshLightMapWorldScale = BoolProperty( - name="Use world scale", - description=( - "If not that will use the object scale." - ), - override={'LIBRARY_OVERRIDABLE'}, - default=False - ) - - bpy.types.Object.GenerateLightmapUVs = BoolProperty( - name="Generate LightmapUVs", - description=( - "If checked, UVs for Lightmap will automatically be generated." - ), - override={'LIBRARY_OVERRIDABLE'}, - default=True, - ) - - bpy.types.Object.convert_geometry_node_attribute_to_uv = BoolProperty( - name="Convert Attribute To Uv", - description=( - "convert target geometry node attribute to UV when found." - ), - override={'LIBRARY_OVERRIDABLE'}, - default=True, - ) - - bpy.types.Object.convert_geometry_node_attribute_to_uv_name = StringProperty( - name="Attribute name", - description=( - "Name of the Attribute to convert" - ), - override={'LIBRARY_OVERRIDABLE'}, - default="UVMap", - ) - - bpy.types.Object.correct_extrem_uv_scale = BoolProperty( - name=(ti('correct_extrem_uv_scale_name')), - description=(tt('correct_extrem_uv_scale_desc')), - override={'LIBRARY_OVERRIDABLE'}, - default=False, - ) - - 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)." - ), - override={'LIBRARY_OVERRIDABLE'}, - 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" - ), - override={'LIBRARY_OVERRIDABLE'}, - # Vania python: - # https://docs.unrealengine.com/en-US/PythonAPI/class/MaterialSearchLocation.html?highlight=materialsearchlocation - # C++ API: - # 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", - "Under parent", - "Search for matching material recursively from parent folder.", - 2), - ("UnderRoot", - "Under root", - "Search for matching material recursively from root folder.", - 3), - ("AllAssets", - "All assets", - "Search for matching material in all assets folders.", - 4) - ] - ) - - bpy.types.Object.CollisionTraceFlag = EnumProperty( - name="Collision Complexity", - description="Collision Trace Flag", - override={'LIBRARY_OVERRIDABLE'}, - # Vania python - # https://docs.unrealengine.com/en-US/PythonAPI/class/CollisionTraceFlag.html - # C++ API - # 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", - override={'LIBRARY_OVERRIDABLE'}, - # Vania python - # https://docs.unrealengine.com/en-US/PythonAPI/class/VertexColorImportOption.html - # C++ API - # https://docs.unrealengine.com/en-US/API/Editor/UnrealEd/Factories/EVertexColorImportOption__Type/index.html - items=[ - ("IGNORE", "Ignore", - "Ignore vertex colors, and keep the existing mesh vertex colors.", 1), - ("OVERRIDE", "Override", - "Override all vertex colors with the specified color.", 2), - ("REPLACE", "Replace", - "Import the static mesh using the target vertex colors.", 0) - ], - default="REPLACE" - ) - - bpy.types.Object.VertexOverrideColor = FloatVectorProperty( - name="Vertex Override Color", - subtype='COLOR', - description="Specify override color in the case that VertexColorImportOption is set to Override", - override={'LIBRARY_OVERRIDABLE'}, - default=(1.0, 1.0, 1.0), - min=0.0, - max=1.0 - # Vania python - # https://docs.unrealengine.com/en-US/PythonAPI/class/FbxSkeletalMeshImportData.html - ) - - bpy.types.Object.VertexColorToUse = EnumProperty( - name="Vertex Color to use", - description="Specify which vertex colors should be imported", - override={'LIBRARY_OVERRIDABLE'}, - items=[ - ("FirstIndex", "First Index", - "Use the the first index in Object Data -> Vertex Color.", 0), - ("LastIndex", "Last Index", - "Use the the last index in Object Data -> Vertex Color.", 1), - ("ActiveIndex", "Active Render", - "Use the the active index in Object Data -> Vertex Color.", 2), - ("CustomIndex", "CustomIndex", - "Use a specific Vertex Color in Object Data -> Vertex Color.", 3) - ], - default="ActiveIndex" - ) - - bpy.types.Object.VertexColorIndexToUse = IntProperty( - name="Vertex color index", - description="Vertex Color index to use.", - override={'LIBRARY_OVERRIDABLE'}, - default=0 - ) - - bpy.types.Object.bfu_anim_action_export_enum = EnumProperty( - name="Action to export", - description="Export procedure for actions (Animations and poses)", - override={'LIBRARY_OVERRIDABLE'}, - 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", - override={'LIBRARY_OVERRIDABLE'}, - default=0 - ) - - bpy.types.Object.PrefixNameToExport = StringProperty( - # properties used with ""export_specific_prefix" on bfu_anim_action_export_enum - name="Prefix name", - description="Indicate the prefix of the actions that must be exported", - override={'LIBRARY_OVERRIDABLE'}, - maxlen=32, - default="Example_", - ) - - bpy.types.Object.bfu_anim_action_start_end_time_enum = EnumProperty( - name="Action Start/End Time", - description="Set when animation starts and end", - override={'LIBRARY_OVERRIDABLE'}, - 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 "bfu_anim_action_custom_start_frame" and "bfu_anim_action_custom_end_frame"', - "HAND", - 3), - ] - ) - - bpy.types.Object.bfu_anim_action_start_frame_offset = IntProperty( - name="Offset at start frame", - description="Offset for the start frame.", - override={'LIBRARY_OVERRIDABLE'}, - default=0 - ) - - bpy.types.Object.bfu_anim_action_end_frame_offset = 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" - ), - override={'LIBRARY_OVERRIDABLE'}, - default=0 - ) - - bpy.types.Object.bfu_anim_action_custom_start_frame = IntProperty( - name="Custom start time", - description="Set when animation start", - override={'LIBRARY_OVERRIDABLE'}, - default=0 - ) - - bpy.types.Object.bfu_anim_action_custom_end_frame = IntProperty( - name="Custom end time", - description="Set when animation end", - override={'LIBRARY_OVERRIDABLE'}, - default=1 - ) - - bpy.types.Object.bfu_anim_nla_start_end_time_enum = EnumProperty( - name="NLA Start/End Time", - description="Set when animation starts and end", - override={'LIBRARY_OVERRIDABLE'}, - items=[ - ("with_sceneframes", - "Scene time", - "Time will be equal to the scene time", - "SCENE_DATA", - 1), - ("with_customframes", - "Custom time", - 'The time of all the animations of this object' + - ' is defined by you.' + - ' Use "bfu_anim_action_custom_start_frame" and "bfu_anim_action_custom_end_frame"', - "HAND", - 2), - ] - ) - - bpy.types.Object.bfu_anim_nla_start_frame_offset = IntProperty( - name="Offset at start frame", - description="Offset for the start frame.", - override={'LIBRARY_OVERRIDABLE'}, - default=0 - ) - - bpy.types.Object.bfu_anim_nla_end_frame_offset = 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" - ), - override={'LIBRARY_OVERRIDABLE'}, - default=0 - ) - - bpy.types.Object.bfu_anim_nla_custom_start_frame = IntProperty( - name="Custom start time", - description="Set when animation start", - override={'LIBRARY_OVERRIDABLE'}, - default=0 - ) - - bpy.types.Object.bfu_anim_nla_custom_end_frame = IntProperty( - name="Custom end time", - description="Set when animation end", - override={'LIBRARY_OVERRIDABLE'}, - default=1 - ) - - - bpy.types.Object.SampleAnimForExport = FloatProperty( - name="Sampling Rate", - description="How often to evaluate animated values (in frames)", - override={'LIBRARY_OVERRIDABLE'}, - 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)" - ), - override={'LIBRARY_OVERRIDABLE'}, - # No simplification to up to 10% of current magnitude tolerance. - min=0.0, max=100.0, - soft_min=0.0, soft_max=10.0, - default=0.0, - ) - - bpy.types.Object.bfu_anim_nla_use = BoolProperty( - name="Export NLA (Nonlinear Animation)", - description=( - "If checked, exports the all animation of the scene with the NLA " + - "(Don't work with Auto-Rig Pro for the moment.)" - ), - override={'LIBRARY_OVERRIDABLE'}, - default=False - ) - - bpy.types.Object.bfu_anim_nla_export_name = StringProperty( - name="NLA export name", - description="Export NLA name (Don't work with Auto-Rig Pro for the moment.)", - override={'LIBRARY_OVERRIDABLE'}, - maxlen=64, - default="NLA_animation", - subtype='FILE_NAME' - ) - - bpy.types.Object.bfu_anim_naming_type = EnumProperty( - name="Naming type", - override={'LIBRARY_OVERRIDABLE'}, - items=[ - ('action_name', "Action name", 'Exemple: "Anim_MyAction"'), - ('include_armature_name', - "Include Armature Name", - 'Include armature name in animation export file name.' + - ' Exemple: "Anim_MyArmature_MyAction"'), - ('include_custom_name', - "Include custom name", - 'Include custom name in animation export file name.' + - ' Exemple: "Anim_MyCustomName_MyAction"'), - ], - default='action_name' - ) - - bpy.types.Object.bfu_anim_naming_custom = StringProperty( - name="Export name", - override={'LIBRARY_OVERRIDABLE'}, - default='MyCustomName' - ) - - bpy.types.Object.exportGlobalScale = FloatProperty( - name="Global scale", - description="Scale, change is not recommended with SkeletalMesh.", - override={'LIBRARY_OVERRIDABLE'}, - default=1.0 - ) - - bpy.types.Object.exportAxisForward = EnumProperty( - name="Axis Forward", - override={'LIBRARY_OVERRIDABLE'}, - 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", - override={'LIBRARY_OVERRIDABLE'}, - 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", - override={'LIBRARY_OVERRIDABLE'}, - items=[ - ('X', "X", ""), - ('Y', "Y", ""), - ('Z', "Z", ""), - ('-X', "-X", ""), - ('-Y', "-Y", ""), - ('-Z', "-Z", ""), - ], - default='Y', - ) - - bpy.types.Object.exporSecondaryBoneAxis = EnumProperty( - name="Secondary Axis Bone", - override={'LIBRARY_OVERRIDABLE'}, - 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)" - ), - override={'LIBRARY_OVERRIDABLE'}, - 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." - ), - override={'LIBRARY_OVERRIDABLE'}, - default=False - ) - - bpy.types.Object.MoveActionToCenterForExport = BoolProperty( - name="Move animation to center", - description=( - "(Action animation only) 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)" + - " Note: Unreal Engine ignore the position of the skeleton at the import." - ), - override={'LIBRARY_OVERRIDABLE'}, - default=True - ) - - bpy.types.Object.RotateActionToZeroForExport = BoolProperty( - name="Rotate Action to zero", - description=( - "(Action animation only) If true use object rotation else use scene rotation." + - " | If true the mesh will use zero rotation for export." - ), - override={'LIBRARY_OVERRIDABLE'}, - default=False - ) - - bpy.types.Object.MoveNLAToCenterForExport = BoolProperty( - name="Move NLA to center", - description=( - "(Non linear animation only) 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)" + - " Note: Unreal Engine ignore the position of the skeleton at the import." - ), - override={'LIBRARY_OVERRIDABLE'}, - default=True - ) - - bpy.types.Object.RotateNLAToZeroForExport = BoolProperty( - name="Rotate NLA to zero", - description=( - "(Non linear animation only) If true use object rotation else use scene rotation." + - " | If true the mesh will use zero rotation for export." - ), - override={'LIBRARY_OVERRIDABLE'}, - default=False - ) - - bpy.types.Object.AdditionalLocationForExport = FloatVectorProperty( - name="Additional location", - description=( - "This will add a additional absolute location to the mesh" - ), - override={'LIBRARY_OVERRIDABLE'}, - 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" - ), - override={'LIBRARY_OVERRIDABLE'}, - subtype="EULER", - default=(0, 0, 0) - ) - - # Scene and global - - bpy.types.Scene.active_CollectionExportList = IntProperty( - name="Active Collection", - description="Index of the currently active collection", - override={'LIBRARY_OVERRIDABLE'}, - default=0 - ) - - class BFU_OT_OpenDocumentationPage(Operator): - bl_label = "Documentation" - bl_idname = "object.bfu_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/wiki" - ) - return {'FINISHED'} - - class BFU_OT_CopyRegularCameraButton(Operator): - bl_label = "Copy Regular Camera for Unreal" - bl_idname = "object.copy_regular_camera_command" - bl_description = "Copy Regular Camera Script command" - - def execute(self, context): - obj = context.object - result = GetImportCameraScriptCommand([obj], False) - if result[0]: - setWindowsClipboard(result[1]) - self.report({'INFO'}, result[2]) - else: - self.report({'WARNING'}, result[2]) - return {'FINISHED'} - - class BFU_OT_CopyCineCameraButton(Operator): - bl_label = "Copy Cine Camera for Unreal" - bl_idname = "object.copy_cine_camera_command" - bl_description = "Copy Cine Camera Script command" - - def execute(self, context): - obj = context.object - result = GetImportCameraScriptCommand([obj], True) - if result[0]: - setWindowsClipboard(result[1]) - self.report({'INFO'}, result[2]) - else: - self.report({'WARNING'}, result[2]) - return {'FINISHED'} - - class BFU_OT_ComputLightMap(Operator): - bl_label = "Calculate surface area" - bl_idname = "object.computlightmap" - bl_description = "Click to calculate the surface of the object" - - def execute(self, context): - obj = context.object - obj.computedStaticMeshLightMapRes = GetExportRealSurfaceArea(obj) - self.report( - {'INFO'}, - "Light map area updated to " + str(round(obj.computedStaticMeshLightMapRes)) + ". " + - "Compunted Light map: " + str(GetCompuntedLightMap(obj))) - return {'FINISHED'} - - - # Animation : - - class BFU_UL_ActionExportTarget(bpy.types.UIList): - def draw_item( - self, - context, - layout, - data, - item, - icon, - active_data, - active_propname - ): - ActionIsValid = False - if item.name in bpy.data.actions: - bpy.data.actions[item.name] - ActionIsValid = True - - if self.layout_type in {'DEFAULT', 'COMPACT'}: - if ActionIsValid: # If action is valid - 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 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]: - 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 - useFromLast = SetUseFromLast(animSave, action.name) - obj.exportActionList[action.name].use = useFromLast - 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 - UpdateActionCache(obj) - animation_to_export = GetActionToExport(obj) - - popup_title = "Action list" - if len(animation_to_export) > 0: - animationNumber = len(animation_to_export) - if obj.bfu_anim_nla_use: - animationNumber += 1 - popup_title = ( - str(animationNumber) + - ' action(s) found for obj named "'+obj.name+'".' - ) - else: - popup_title = ( - 'No action found for obj named "' + - obj.name+'".') - - def draw(self, context): - col = self.layout.column() - - def addAnimRow( - action_name, - action_type, - frame_start, - frame_end): - row = col.row() - row.label( - text="- ["+action_name + - "] Frame "+frame_start+" to "+frame_end + - " ("+action_type+")" - ) - - for action in animation_to_export: - Frames = GetDesiredActionStartEndTime(obj, action) - frame_start = str(Frames[0]) - frame_end = str(Frames[1]) - addAnimRow( - action.name, - GetActionType(action), - frame_start, - frame_end) - if obj.bfu_anim_nla_use: - scene = context.scene - addAnimRow( - obj.bfu_anim_nla_export_name, - "NlAnim", - str(scene.frame_start), - str(scene.frame_end) - ) - - bpy.context.window_manager.popup_menu( - draw, - title=popup_title, - icon='ACTION' - ) - return {'FINISHED'} - - 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', - 'col = bpy.context.collection', - 'scene = bpy.context.scene' - ] - - # Properties to store in the preset - preset_values = [ - 'obj.exportFolderName', - 'col.exportFolderName', - 'obj.bfu_export_fbx_camera', - 'obj.bfu_fix_axis_flippings', - 'obj.ExportAsAlembic', - 'obj.ExportAsLod', - 'obj.ForceStaticMesh', - 'obj.exportDeformOnly', - 'obj.Ue4Lod1', - 'obj.Ue4Lod2', - 'obj.Ue4Lod3', - 'obj.Ue4Lod4', - 'obj.Ue4Lod5', - 'obj.CreatePhysicsAsset', - 'obj.bfu_skeleton_search_mode', - 'obj.bfu_target_skeleton_custom_path', - 'obj.bfu_target_skeleton_custom_name', - 'obj.bfu_target_skeleton_custom_ref', - 'obj.UseStaticMeshLODGroup', - 'obj.StaticMeshLODGroup', - 'obj.StaticMeshLightMapEnum', - 'obj.customStaticMeshLightMapRes', - 'obj.staticMeshLightMapSurfaceScale', - 'obj.staticMeshLightMapRoundPowerOfTwo', - 'obj.useStaticMeshLightMapWorldScale', - 'obj.GenerateLightmapUVs', - 'obj.convert_geometry_node_attribute_to_uv', - 'obj.convert_geometry_node_attribute_to_uv_name', - 'obj.correct_extrem_uv_scale', - 'obj.AutoGenerateCollision', - 'obj.MaterialSearchLocation', - 'obj.CollisionTraceFlag', - 'obj.VertexColorImportOption', - 'obj.VertexOverrideColor', - 'obj.VertexColorToUse', - 'obj.VertexColorIndexToUse', - 'obj.bfu_anim_action_export_enum', - 'obj.PrefixNameToExport', - 'obj.bfu_anim_action_start_end_time_enum', - 'obj.bfu_anim_nla_start_end_time_enum', - 'obj.bfu_anim_action_start_frame_offset', - 'obj.bfu_anim_action_end_frame_offset', - 'obj.bfu_anim_action_custom_start_frame', - 'obj.bfu_anim_action_custom_end_frame', - 'obj.bfu_anim_nla_start_frame_offset', - 'obj.bfu_anim_nla_end_frame_offset', - 'obj.bfu_anim_nla_custom_start_frame', - 'obj.bfu_anim_nla_custom_end_frame', - 'obj.SampleAnimForExport', - 'obj.SimplifyAnimForExport', - 'obj.bfu_anim_nla_use', - 'obj.bfu_anim_nla_export_name', - 'obj.bfu_anim_naming_type', - 'obj.bfu_anim_naming_custom', - 'obj.exportGlobalScale', - 'obj.exportAxisForward', - 'obj.exportAxisUp', - 'obj.exportPrimaryBaneAxis', - 'obj.exporSecondaryBoneAxis', - 'obj.MoveToCenterForExport', - 'obj.RotateToZeroForExport', - 'obj.MoveActionToCenterForExport', - 'obj.RotateActionToZeroForExport', - 'obj.MoveNLAToCenterForExport', - 'obj.RotateNLAToZeroForExport', - 'obj.AdditionalLocationForExport', - 'obj.AdditionalRotationForExport', - ] - - # Directory to store the presets - preset_subdir = 'blender-for-unrealengine/global-properties-presets' - - class BFU_UL_CollectionExportTarget(bpy.types.UIList): - - def draw_item( - self, - context, - layout, - data, - item, - icon, - active_data, - active_propname - ): - - CollectionIsValid = False - if item.name in bpy.data.collections: - bpy.data.collections[item.name] - CollectionIsValid = True - - if self.layout_type in {'DEFAULT', 'COMPACT'}: - if CollectionIsValid: # If action is valid - layout.prop( - bpy.data.collections[item.name], - "name", - text="", - emboss=False, - icon="OUTLINER_COLLECTION") - 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(scene): - # Update the provisional collection list known by the object - - def SetUseFromLast(list, CollectionName): - for item in list: - if item[0] == CollectionName: - if item[1]: - return True - return False - - colSave = [["", False]] - for col in scene.CollectionExportList: # CollectionProperty - name = col.name - use = col.use - colSave.append([name, use]) - scene.CollectionExportList.clear() - for col in bpy.data.collections: - scene.CollectionExportList.add().name = col.name - useFromLast = SetUseFromLast(colSave, col.name) - scene.CollectionExportList[col.name].use = useFromLast - UpdateExportCollectionList(context.scene) - return {'FINISHED'} - - class BFU_OT_ShowCollectionToExport(Operator): - bl_label = "Show collection(s)" - bl_idname = "object.showscenecollection" - bl_description = "Click to show collections to export" - - def execute(self, context): - scene = context.scene - collections = GetCollectionToExport(scene) - 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.name) - bpy.context.window_manager.popup_menu( - draw, - title=popup_title, - icon='GROUP') - return {'FINISHED'} - - def draw(self, contex): - scene = bpy.context.scene - obj = bpy.context.object - addon_prefs = GetAddonPrefs() - layout = self.layout - - version = "-1" - for addon in addon_utils.modules(): - if addon.bl_info['name'] == "Blender for UnrealEngine": - version = addon.bl_info.get('version', (-1, -1, -1)) - - credit_box = layout.box() - credit_box.label(text=ti('intro')+' Version: '+str(version)) - credit_box.operator("object.bfu_open_documentation_page", icon="HELP") - - row = 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 - - layout.row().prop(scene, "bfu_active_tab", expand=True) - if scene.bfu_active_tab == "OBJECT": - layout.row().prop(scene, "bfu_active_object_tab", expand=True) - if scene.bfu_active_tab == "SCENE": - layout.row().prop(scene, "bfu_active_scene_tab", expand=True) - - if bfu_ui_utils.DisplayPropertyFilter("OBJECT", "GENERAL"): - bfu_ui_utils.LayoutSection(layout, "bfu_object_properties_expanded", "Object Properties") - if scene.bfu_object_properties_expanded: - - if obj is None: - layout.row().label(text='No selected object.') - else: - - AssetType = layout.row() - AssetType.prop(obj, 'name', text="", icon='OBJECT_DATA') - # Show asset type - AssetType.label(text='('+GetAssetType(obj)+')') - - ExportType = layout.column() - ExportType.prop(obj, 'ExportEnum') - - if obj.type == "CAMERA": - CameraProp = layout.column() - CameraProp.operator("object.copy_regular_camera_command", icon="COPYDOWN") - CameraProp.operator("object.copy_cine_camera_command", icon="COPYDOWN") - - if obj.ExportEnum == "export_recursive": - - folderNameProperty = layout.column() - folderNameProperty.prop( - obj, - 'exportFolderName', - icon='FILE_FOLDER' - ) - - if obj.type == "CAMERA": - CameraProp.prop(obj, 'bfu_export_fbx_camera') - CameraProp.prop(obj, 'bfu_fix_axis_flippings') - - else: - ProxyProp = layout.column() - if GetExportAsProxy(obj): - ProxyProp.label( - text="The Armature was detected as a proxy." - ) - proxy_child = GetExportProxyChild(obj) - if proxy_child: - ProxyProp.label( - text="Proxy child: " + proxy_child.name - ) - else: - ProxyProp.label(text="Proxy child not found") - - export_procedure_prop = layout.column() - export_procedure_prop.prop(obj, 'bfu_export_procedure') - - if not GetExportAsProxy(obj): - AlembicProp = layout.column() - AlembicProp.prop(obj, 'ExportAsAlembic') - if obj.ExportAsAlembic: - AlembicProp.label( - text="(Alembic animation are exported" + - " with scene position.)" - ) - AlembicProp.label( - text="(Use import script for" + - " use the origin position.)" - ) - else: - if addon_prefs.useGeneratedScripts: - # Unreal python no longer support Skeletal mesh LODS import. - if GetAssetType(obj) != "SkeletalMesh": - LodProp = layout.column() - LodProp.prop(obj, 'ExportAsLod') - if not obj.ExportAsAlembic: - if obj.type == "ARMATURE": - AssetType2 = layout.column() - # Show asset type - AssetType2.prop(obj, "ForceStaticMesh") - if GetAssetType(obj) == "SkeletalMesh": - AssetType2.prop(obj, 'exportDeformOnly') - - if not GetExportAsProxy(obj): - # exportCustomName - exportCustomName = layout.row() - exportCustomName.prop(obj, "bfu_use_custom_export_name") - useCustomName = obj.bfu_use_custom_export_name - exportCustomNameText = exportCustomName.column() - exportCustomNameText.prop(obj, "bfu_custom_export_name") - exportCustomNameText.enabled = useCustomName - - bfu_ui_utils.LayoutSection(layout, "bfu_object_advanced_properties_expanded", "Object advanced Properties") - if scene.bfu_object_advanced_properties_expanded: - if obj is not None: - if obj.ExportEnum == "export_recursive": - - transformProp = layout.column() - if GetAssetType(obj) != "Alembic" and GetAssetType(obj) != "Camera": - transformProp.prop(obj, "MoveToCenterForExport") - transformProp.prop(obj, "RotateToZeroForExport") - transformProp.prop(obj, "AdditionalLocationForExport") - transformProp.prop(obj, "AdditionalRotationForExport") - transformProp.prop(obj, 'exportGlobalScale') - elif GetAssetType(obj) == "Camera": - transformProp.prop(obj, "AdditionalLocationForExport") - - 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.)') - - bfu_ui_utils.LayoutSection(layout, "bfu_skeleton_properties_expanded", "Skeleton") - if scene.bfu_skeleton_properties_expanded: - if addon_prefs.useGeneratedScripts and obj is not None: - if obj.ExportEnum == "export_recursive": - - # SkeletalMesh prop - if GetAssetType(obj) == "SkeletalMesh": - if not obj.ExportAsLod: - - Ue4Skeleton = layout.column() - Ue4Skeleton.prop(obj, "bfu_skeleton_search_mode") - if obj.bfu_skeleton_search_mode == "auto": - pass - if obj.bfu_skeleton_search_mode == "custom_name": - Ue4Skeleton.prop(obj, "bfu_target_skeleton_custom_name") - if obj.bfu_skeleton_search_mode == "custom_path_name": - Ue4Skeleton.prop(obj, "bfu_target_skeleton_custom_path") - Ue4Skeleton.prop(obj, "bfu_target_skeleton_custom_name") - if obj.bfu_skeleton_search_mode == "custom_reference": - Ue4Skeleton.prop(obj, "bfu_target_skeleton_custom_ref") - - if bfu_ui_utils.DisplayPropertyFilter("OBJECT", "ANIM"): - if obj is not None: - if obj.ExportEnum == "export_recursive" and not obj.ExportAsLod: - - bfu_ui_utils.LayoutSection(layout, "bfu_animation_action_properties_expanded", "Actions Properties") - if scene.bfu_animation_action_properties_expanded: - if (GetAssetType(obj) == "SkeletalMesh" or - GetAssetType(obj) == "Camera" or - GetAssetType(obj) == "Alembic"): - - if GetAssetType(obj) == "SkeletalMesh": - # Action list - ActionListProperty = layout.column() - ActionListProperty.prop(obj, 'bfu_anim_action_export_enum') - if obj.bfu_anim_action_export_enum == "export_specific_list": - ActionListProperty.template_list( - # type and unique id - "BFU_UL_ActionExportTarget", "", - # pointer to the CollectionProperty - obj, "exportActionList", - # pointer to the active identifier - obj, "active_ObjectAction", - maxrows=5, - rows=5 - ) - ActionListProperty.operator( - "object.updateobjactionlist", - icon='RECOVER_LAST') - if obj.bfu_anim_action_export_enum == "export_specific_prefix": - ActionListProperty.prop(obj, 'PrefixNameToExport') - - # Action Time - if obj.type != "CAMERA" and obj.bfu_export_procedure != "auto-rig-pro": - ActionTimeProperty = layout.column() - ActionTimeProperty.enabled = obj.bfu_anim_action_export_enum != 'dont_export' - ActionTimeProperty.prop(obj, 'bfu_anim_action_start_end_time_enum') - if obj.bfu_anim_action_start_end_time_enum == "with_customframes": - OfsetTime = ActionTimeProperty.row() - OfsetTime.prop(obj, 'bfu_anim_action_custom_start_frame') - OfsetTime.prop(obj, 'bfu_anim_action_custom_end_frame') - if obj.bfu_anim_action_start_end_time_enum != "with_customframes": - OfsetTime = ActionTimeProperty.row() - OfsetTime.prop(obj, 'bfu_anim_action_start_frame_offset') - OfsetTime.prop(obj, 'bfu_anim_action_end_frame_offset') - - else: - layout.label( - text=( - "Note: animation start/end use scene frames" + - " with the camera for the sequencer.") - ) - - # Nomenclature - if GetAssetType(obj) == "SkeletalMesh": - export_anim_naming = layout.column() - export_anim_naming.enabled = obj.bfu_anim_action_export_enum != 'dont_export' - export_anim_naming.prop(obj, 'bfu_anim_naming_type') - if obj.bfu_anim_naming_type == "include_custom_name": - export_anim_naming_text = export_anim_naming.column() - export_anim_naming_text.prop(obj, 'bfu_anim_naming_custom') - - - - else: - layout.label( - text='(This assets is not a SkeletalMesh or Camera)') - - bfu_ui_utils.LayoutSection(layout, "bfu_animation_action_advanced_properties_expanded", "Actions Advanced Properties") - if scene.bfu_animation_action_advanced_properties_expanded: - - if GetAssetType(obj) != "Alembic": - transformProp = layout.column() - transformProp.enabled = obj.bfu_anim_action_export_enum != 'dont_export' - transformProp.prop(obj, "MoveActionToCenterForExport") - transformProp.prop(obj, "RotateActionToZeroForExport") - - bfu_ui_utils.LayoutSection(layout, "bfu_animation_nla_properties_expanded", "NLA Properties") - if scene.bfu_animation_nla_properties_expanded: - # NLA - if GetAssetType(obj) == "SkeletalMesh": - NLAAnim = layout.row() - NLAAnim.prop(obj, 'bfu_anim_nla_use') - NLAAnimChild = NLAAnim.column() - NLAAnimChild.enabled = obj.bfu_anim_nla_use - NLAAnimChild.prop(obj, 'bfu_anim_nla_export_name') - if obj.bfu_export_procedure == "auto-rig-pro": - NLAAnim.enabled = False - NLAAnimChild.enabled = False - - # NLA Time - if obj.type != "CAMERA" and obj.bfu_export_procedure != "auto-rig-pro": - NLATimeProperty = layout.column() - NLATimeProperty.enabled = obj.bfu_anim_nla_use - NLATimeProperty.prop(obj, 'bfu_anim_nla_start_end_time_enum') - if obj.bfu_anim_nla_start_end_time_enum == "with_customframes": - OfsetTime = NLATimeProperty.row() - OfsetTime.prop(obj, 'bfu_anim_nla_custom_start_frame') - OfsetTime.prop(obj, 'bfu_anim_nla_custom_end_frame') - if obj.bfu_anim_nla_start_end_time_enum != "with_customframes": - OfsetTime = NLATimeProperty.row() - OfsetTime.prop(obj, 'bfu_anim_nla_start_frame_offset') - OfsetTime.prop(obj, 'bfu_anim_nla_end_frame_offset') - - bfu_ui_utils.LayoutSection(layout, "bfu_animation_nla_advanced_properties_expanded", "NLA Advanced Properties") - if scene.bfu_animation_nla_advanced_properties_expanded: - if GetAssetType(obj) != "Alembic": - transformProp2 = layout.column() - transformProp2.enabled = obj.bfu_anim_nla_use - transformProp2.prop(obj, "MoveNLAToCenterForExport") - transformProp2.prop(obj, "RotateNLAToZeroForExport") - - bfu_ui_utils.LayoutSection(layout, "bfu_animation_advanced_properties_expanded", "Animation Advanced Properties") - if scene.bfu_animation_advanced_properties_expanded: - # Animation fbx properties - if (GetAssetType(obj) != "Alembic"): - propsFbx = layout.row() - if obj.bfu_export_procedure != "auto-rig-pro": - propsFbx.prop(obj, 'SampleAnimForExport') - propsFbx.prop(obj, 'SimplifyAnimForExport') - - # Armature export action list feedback - if GetAssetType(obj) == "SkeletalMesh": - layout.label( - text='Note: The Action with only one' + - ' frame are exported like Pose.') - ArmaturePropertyInfo = ( - layout.row().box().split(factor=0.75) - ) - ActionNum = len(GetActionToExport(obj)) - if obj.bfu_anim_nla_use: - ActionNum += 1 - actionFeedback = ( - str(ActionNum) + - " Animation(s) will be exported with this object.") - ArmaturePropertyInfo.label( - text=actionFeedback, - icon='INFO') - ArmaturePropertyInfo.operator("object.showobjaction") - else: - layout.label(text='(No properties to show.)') - else: - layout.label(text='(No properties to show.)') - - if bfu_ui_utils.DisplayPropertyFilter("OBJECT", "MISC"): - bfu_ui_utils.LayoutSection(layout, "bfu_object_lod_properties_expanded", "Lod") - if scene.bfu_object_lod_properties_expanded: - if addon_prefs.useGeneratedScripts and obj is not None: - if obj.ExportEnum == "export_recursive": - - # Lod selection - if not obj.ExportAsLod: - # Unreal python no longer support Skeletal mesh LODS import. - if (GetAssetType(obj) == "StaticMesh"): - LodList = layout.column() - LodList.prop(obj, 'Ue4Lod1') - LodList.prop(obj, 'Ue4Lod2') - LodList.prop(obj, 'Ue4Lod3') - LodList.prop(obj, 'Ue4Lod4') - LodList.prop(obj, 'Ue4Lod5') - - # StaticMesh prop - if GetAssetType(obj) == "StaticMesh": - if not obj.ExportAsLod: - StaticMeshLODGroup = layout.row() - StaticMeshLODGroup.prop( - obj, - 'UseStaticMeshLODGroup', - text="") - SMLODGroupChild = StaticMeshLODGroup.column() - SMLODGroupChild.enabled = obj.UseStaticMeshLODGroup - SMLODGroupChild.prop( - obj, - 'StaticMeshLODGroup' - ) - - bfu_ui_utils.LayoutSection(layout, "bfu_object_collision_properties_expanded", "Collision") - if scene.bfu_object_collision_properties_expanded: - if addon_prefs.useGeneratedScripts and obj is not None: - if obj.ExportEnum == "export_recursive": - - # StaticMesh prop - if GetAssetType(obj) == "StaticMesh": - StaticMeshCollisionTraceFlag = layout.row() - StaticMeshCollisionTraceFlag.prop( - obj, - 'CollisionTraceFlag' - ) - if not obj.ExportAsLod: - AutoGenerateCollision = layout.row() - AutoGenerateCollision.prop( - obj, - 'AutoGenerateCollision' - ) - # SkeletalMesh prop - if GetAssetType(obj) == "SkeletalMesh": - if not obj.ExportAsLod: - CreatePhysicsAsset = layout.row() - CreatePhysicsAsset.prop(obj, "CreatePhysicsAsset") - - bfu_ui_utils.LayoutSection(layout, "bfu_object_material_properties_expanded", "Material") - if scene.bfu_object_material_properties_expanded: - if addon_prefs.useGeneratedScripts and obj is not None: - if obj.ExportEnum == "export_recursive": - - # MaterialSearchLocation - if not obj.ExportAsLod: - if (GetAssetType(obj) == "StaticMesh" or - GetAssetType(obj) == "SkeletalMesh" or - GetAssetType(obj) == "Alembic"): - MaterialSearchLocation = layout.row() - MaterialSearchLocation.prop( - obj, 'MaterialSearchLocation') - - bfu_ui_utils.LayoutSection(layout, "bfu_object_vertex_color_properties_expanded", "Vertex color") - if scene.bfu_object_vertex_color_properties_expanded: - if addon_prefs.useGeneratedScripts and obj is not None: - if obj.ExportEnum == "export_recursive": - - # Vertex color - StaticMeshVertexColorImportOption = layout.column() - StaticMeshVertexColorImportOption.prop(obj, 'VertexColorImportOption') - if obj.VertexColorImportOption == "OVERRIDE": - StaticMeshVertexColorImportOptionColor = StaticMeshVertexColorImportOption.row() - StaticMeshVertexColorImportOptionColor.prop(obj, 'VertexOverrideColor') - if obj.VertexColorImportOption == "REPLACE": - StaticMeshVertexColorImportOptionIndex = StaticMeshVertexColorImportOption.row() - StaticMeshVertexColorImportOptionIndex.prop(obj, 'VertexColorToUse') - if obj.VertexColorToUse == "CustomIndex": - StaticMeshVertexColorImportOptionIndexCustom = StaticMeshVertexColorImportOption.row() - StaticMeshVertexColorImportOptionIndexCustom.prop(obj, 'VertexColorIndexToUse') - - StaticMeshVertexColorFeedback = StaticMeshVertexColorImportOption.row() - if obj.type == "MESH": - vced = VertexColorExportData(obj) - if vced.export_type == "REPLACE": - my_text = 'Vertex color nammed "' + vced.name + '" will be used.' - StaticMeshVertexColorFeedback.label(text=my_text, icon='INFO') - else: - my_text = 'No vertex color found at this index.' - StaticMeshVertexColorFeedback.label(text=my_text, icon='ERROR') - else: - my_text = 'Vertex color property will be apply on the childrens.' - StaticMeshVertexColorFeedback.label(text=my_text, icon='INFO') - - bfu_ui_utils.LayoutSection(layout, "bfu_object_light_map_properties_expanded", "Light map") - if scene.bfu_object_light_map_properties_expanded: - if addon_prefs.useGeneratedScripts and obj is not None: - if obj.ExportEnum == "export_recursive": - - # Light map - if GetAssetType(obj) == "StaticMesh": - StaticMeshLightMapRes = layout.box() - StaticMeshLightMapRes.prop(obj, 'StaticMeshLightMapEnum') - if obj.StaticMeshLightMapEnum == "CustomMap": - CustomLightMap = StaticMeshLightMapRes.column() - CustomLightMap.prop(obj, 'customStaticMeshLightMapRes') - if obj.StaticMeshLightMapEnum == "SurfaceArea": - SurfaceAreaLightMap = StaticMeshLightMapRes.column() - SurfaceAreaLightMapButton = SurfaceAreaLightMap.row() - SurfaceAreaLightMapButton.operator("object.computlightmap", icon='TEXTURE') - SurfaceAreaLightMapButton.operator("object.computalllightmap", icon='TEXTURE') - SurfaceAreaLightMap.prop(obj, 'useStaticMeshLightMapWorldScale') - SurfaceAreaLightMap.prop(obj, 'staticMeshLightMapSurfaceScale') - SurfaceAreaLightMap.prop(obj, 'staticMeshLightMapRoundPowerOfTwo') - if obj.StaticMeshLightMapEnum != "Default": - CompuntedLightMap = str(GetCompuntedLightMap(obj)) - StaticMeshLightMapRes.label(text='Compunted light map: ' + CompuntedLightMap) - GenerateLightmapUVs = layout.row() - GenerateLightmapUVs.prop(obj, 'GenerateLightmapUVs') - - bfu_ui_utils.LayoutSection(layout, "bfu_object_uv_map_properties_expanded", "UV map") - if scene.bfu_object_uv_map_properties_expanded: - if obj.ExportEnum == "export_recursive": - # Geometry Node Uv - convert_geometry_node_attribute_to_uv = layout.column() - convert_geometry_node_attribute_to_uv_use = convert_geometry_node_attribute_to_uv.row() - convert_geometry_node_attribute_to_uv_use.prop(obj, 'convert_geometry_node_attribute_to_uv') - bfu_ui_utils.DocPageButton(convert_geometry_node_attribute_to_uv_use, "wiki/UV-Maps", "Geometry Node UV") - convert_geometry_node_attribute_to_uv_name = convert_geometry_node_attribute_to_uv.row() - convert_geometry_node_attribute_to_uv_name.prop(obj, 'convert_geometry_node_attribute_to_uv_name') - convert_geometry_node_attribute_to_uv_name.enabled = obj.convert_geometry_node_attribute_to_uv - - # Extreme UV Scale - correct_extrem_uv_scale = layout.row() - correct_extrem_uv_scale.prop(obj, 'correct_extrem_uv_scale') - bfu_ui_utils.DocPageButton(correct_extrem_uv_scale, "wiki/UV-Maps", "Extreme UV Scale") - - if bfu_ui_utils.DisplayPropertyFilter("SCENE", "GENERAL"): - bfu_ui_utils.LayoutSection(layout, "bfu_collection_properties_expanded", "Collection Properties") - if scene.bfu_collection_properties_expanded: - collectionListProperty = layout.column() - collectionListProperty.template_list( - # type and unique id - "BFU_UL_CollectionExportTarget", "", - # pointer to the CollectionProperty - scene, "CollectionExportList", - # pointer to the active identifier - scene, "active_CollectionExportList", - maxrows=5, - rows=5 - ) - collectionListProperty.operator( - "object.updatecollectionlist", - icon='RECOVER_LAST') - - if scene.active_CollectionExportList < len(scene.CollectionExportList): - col_name = scene.CollectionExportList[scene.active_CollectionExportList].name - if col_name in bpy.data.collections: - col = bpy.data.collections[col_name] - col_prop = layout - col_prop.prop(col, 'exportFolderName', icon='FILE_FOLDER') - - collectionPropertyInfo = layout.row().box().split(factor=0.75) - collectionNum = len(GetCollectionToExport(scene)) - collectionFeedback = ( - str(collectionNum) + - " Collection(s) will be exported.") - collectionPropertyInfo.label(text=collectionFeedback, icon='INFO') - collectionPropertyInfo.operator("object.showscenecollection") - layout.label(text='Note: The collection are exported like StaticMesh.') - - -class BFU_PT_BlenderForUnrealTool(bpy.types.Panel): - # Tool panel with Collisions And Sockets - - bl_idname = "BFU_PT_BlenderForUnrealTool" - bl_label = "Tool" - bl_space_type = "VIEW_3D" - bl_region_type = "UI" - bl_category = "Unreal Engine" - - bpy.types.Object.bfu_use_socket_custom_Name = BoolProperty( - name="Socket custom name", - description='Use a custom name in Unreal Engine for this socket?', - default=False - ) - - bpy.types.Object.bfu_socket_custom_Name = StringProperty( - name="", - description='', - default="MySocket" - ) - - class BFU_OT_CopyRegularCamerasButton(Operator): - bl_label = "Copy Regular Cameras for Unreal" - bl_idname = "object.copy_regular_cameras_command" - bl_description = "Copy Regular Cameras Script command" - - def execute(self, context): - objs = context.selected_objects - result = GetImportCameraScriptCommand(objs, False) - if result[0]: - setWindowsClipboard(result[1]) - self.report({'INFO'}, result[2]) - else: - self.report({'WARNING'}, result[2]) - return {'FINISHED'} - - class BFU_OT_CopyCineCamerasButton(Operator): - bl_label = "Copy Cine Cameras for Unreal" - bl_idname = "object.copy_cine_cameras_command" - bl_description = "Copy Cine Cameras Script command" - - def execute(self, context): - objs = context.selected_objects - result = GetImportCameraScriptCommand(objs, True) - if result[0]: - setWindowsClipboard(result[1]) - self.report({'INFO'}, result[2]) - else: - self.report({'WARNING'}, result[2]) - return {'FINISHED'} - - 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 UE4 Socket. (Static)") - 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("SKM_Socket") - if len(ConvertedObj) > 0: - self.report( - {'INFO'}, - str(len(ConvertedObj)) + - " object(s) of the selection have" + - " be converted to UE4 Socket. (Skeletal)") - else: - self.report( - {'WARNING'}, - "Please select two objects. " + - "(Active object is the owner of the socket)") - return {'FINISHED'} - - class BFU_OT_CopySkeletalSocketButton(Operator): - bl_label = "Copy Skeletal Mesh socket for Unreal" - bl_idname = "object.copy_skeletalsocket_command" - bl_description = "Copy Skeletal Socket Script command" - - def execute(self, context): - scene = context.scene - obj = context.object - if obj: - if obj.type == "ARMATURE": - setWindowsClipboard(GetImportSkeletalMeshSocketScriptCommand(obj)) - self.report( - {'INFO'}, - "Skeletal sockets copied. Paste in Unreal Engine Skeletal Mesh assets for import sockets. (Ctrl+V)") - return {'FINISHED'} - - class BFU_OT_ComputAllLightMap(Operator): - bl_label = "Calculate all surface area" - bl_idname = "object.computalllightmap" - bl_description = ( - "Click to calculate the surface of the all object in the scene" - ) - - def execute(self, context): - updated = UpdateAreaLightMapList() - self.report( - {'INFO'}, - "The light maps of " + str(updated) + - " object(s) have been updated." - ) - return {'FINISHED'} - - def draw(self, context): - - addon_prefs = GetAddonPrefs() - layout = self.layout - scene = bpy.context.scene - obj = context.object - - 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 ActiveTypeIsNot(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, include_active=True): - # Return True if a specific type is found - select = bpy.context.selected_objects - if not include_active: - if bpy.context.active_object: - if bpy.context.active_object in select: - select.remove(bpy.context.active_object) - - for obj in select: - if obj.type == targetType: - return True - return False - - ready_for_convert_collider = False - ready_for_convert_socket = False - - ''' - bfu_ui_utils.LayoutSection(layout, "bfu_export_type_expanded", "Export type") - if scene.bfu_export_type_expanded: - if not ActiveModeIs("OBJECT"): - layout.label(text="Switch to Object Mode.", icon='INFO') - else: - export_type_buttons = layout.row().split(factor=0.80) - export_type_cameras = export_type_buttons.column() - export_type_cameras.enabled = True - export_type_cameras.operator("object.converttoboxcollision", icon='MESH_CUBE') - ''' - - bfu_ui_utils.LayoutSection(layout, "bfu_camera_expanded", "Camera") - if scene.bfu_camera_expanded: - copy_camera_buttons = layout.column() - copy_camera_buttons.operator("object.copy_regular_cameras_command", icon="COPYDOWN") - copy_camera_buttons.operator("object.copy_cine_cameras_command", icon="COPYDOWN") - - bfu_ui_utils.LayoutSection(layout, "bfu_collision_socket_expanded", "Collision and Socket") - if scene.bfu_collision_socket_expanded: - if not ActiveModeIs("OBJECT"): - layout.label(text="Switch to Object Mode.", icon='INFO') - else: - if FoundTypeInSelect("MESH", False): - if ActiveTypeIsNot("ARMATURE") and len(bpy.context.selected_objects) > 1: - layout.label(text="Click on button for convert to collider.", icon='INFO') - ready_for_convert_collider = True - else: - layout.label(text="Select with [SHIFT] the collider owner.", icon='INFO') - - elif FoundTypeInSelect("EMPTY", False): - if ActiveTypeIsNot("ARMATURE") and len(bpy.context.selected_objects) > 1: - layout.label(text="Click on button for convert to Socket.", icon='INFO') - ready_for_convert_socket = True - else: - layout.label(text="Select with [SHIFT] the socket owner.", icon='INFO') - else: - layout.label(text="Select your collider Object(s) or socket Empty(s).", icon='INFO') - - convertButtons = layout.row().split(factor=0.80) - convertStaticCollisionButtons = convertButtons.column() - convertStaticCollisionButtons.enabled = ready_for_convert_collider - 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 = layout.row().split(factor=0.80) - convertStaticSocketButtons = convertButtons.column() - convertStaticSocketButtons.enabled = ready_for_convert_socket - convertStaticSocketButtons.operator( - "object.converttostaticsocket", - icon='OUTLINER_DATA_EMPTY') - - if addon_prefs.useGeneratedScripts: - - ready_for_convert_skeletal_socket = False - if not ActiveModeIs("OBJECT"): - if not ActiveTypeIs("ARMATURE"): - if not FoundTypeInSelect("EMPTY"): - layout.label(text="Switch to Object Mode.", icon='INFO') - else: - if FoundTypeInSelect("EMPTY"): - if ActiveTypeIs("ARMATURE") and len(bpy.context.selected_objects) > 1: - layout.label(text="Switch to Pose Mode.", icon='INFO') - else: - layout.label(text="Select with [SHIFT] the socket owner. (Armature)", icon='INFO') - else: - layout.label(text="Select your socket Empty(s).", icon='INFO') - - if ActiveModeIs("POSE") and ActiveTypeIs("ARMATURE") and FoundTypeInSelect("EMPTY"): - if len(bpy.context.selected_pose_bones) > 0: - layout.label(text="Click on button for convert to Socket.", icon='INFO') - ready_for_convert_skeletal_socket = True - else: - layout.label(text="Select the owner bone.", icon='INFO') - - convertButtons = self.layout.row().split(factor=0.80) - convertSkeletalSocketButtons = convertButtons.column() - convertSkeletalSocketButtons.enabled = ready_for_convert_skeletal_socket - convertSkeletalSocketButtons.operator( - "object.converttoskeletalsocket", - icon='OUTLINER_DATA_EMPTY') - - if obj is not None: - if obj.type == "EMPTY": - socketName = layout.column() - socketName.prop(obj, "bfu_use_socket_custom_Name") - socketNameText = socketName.column() - socketNameText.enabled = obj.bfu_use_socket_custom_Name - socketNameText.prop(obj, "bfu_socket_custom_Name") - - copy_skeletalsocket_buttons = layout.column() - copy_skeletalsocket_buttons.enabled = False - copy_skeletalsocket_buttons.operator( - "object.copy_skeletalsocket_command", - icon='COPYDOWN') - if obj is not None: - if obj.type == "ARMATURE": - copy_skeletalsocket_buttons.enabled = True - - bfu_ui_utils.LayoutSection(layout, "bfu_lightmap_expanded", "Light Map") - if scene.bfu_lightmap_expanded: - checkButton = layout.column() - checkButton.operator("object.computalllightmap", icon='TEXTURE') - - -class BFU_PT_BlenderForUnrealDebug(bpy.types.Panel): - # Debug panel for get dev info and test - - bl_idname = "BFU_PT_BlenderForUnrealDebug" - bl_label = "Debug" - bl_space_type = "VIEW_3D" - bl_region_type = "UI" - bl_category = "Unreal Engine" - - bpy.types.Object.bfu_use_socket_custom_Name = BoolProperty( - name="Socket custom name", - description='Use a custom name in Unreal Engine for this socket?', - default=False - ) - - bpy.types.Object.bfu_socket_custom_Name = StringProperty( - name="", - description='', - default="MySocket" - ) - - def draw(self, context): - addon_prefs = GetAddonPrefs() - layout = self.layout - scene = bpy.context.scene - obj = context.object - layout.label(text="This panel is only for Debug", icon='INFO') - if obj: - layout.label(text="Full path name as Static Mesh:") - layout.label(text="GetObjExportDir(local):" + GetObjExportDir(obj, False)) - layout.label(text="GetObjExportDir:" + GetObjExportDir(obj, True)) - layout.label(text="GetObjExportName:" + GetObjExportName(obj)) - layout.label(text="GetObjExportFileName:" + GetObjExportFileName(obj)) - if obj.type == "CAMERA": - layout.label(text="CameraPositionForUnreal (Loc):" + str(EvaluateCameraPositionForUnreal(obj)[0])) - layout.label(text="CameraPositionForUnreal (Rot):" + str(EvaluateCameraPositionForUnreal(obj)[1])) - layout.label(text="CameraPositionForUnreal (Scale):" + str(EvaluateCameraPositionForUnreal(obj)[2])) - - -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" - - # Prefix - bpy.types.Scene.static_mesh_prefix_export_name = bpy.props.StringProperty( - name="StaticMesh Prefix", - description="Prefix of staticMesh", - maxlen=32, - default="SM_") - - bpy.types.Scene.skeletal_mesh_prefix_export_name = bpy.props.StringProperty( - name="SkeletalMesh Prefix ", - description="Prefix of SkeletalMesh", - maxlen=32, - default="SKM_") - - bpy.types.Scene.skeleton_prefix_export_name = bpy.props.StringProperty( - name="skeleton Prefix ", - description="Prefix of skeleton", - 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="SKM_") - - 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=( - "The name of sub folder for animations New." + - " You can now use ../ for up one directory."), - maxlen=512, - 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") - - bpy.types.Scene.unreal_import_module = bpy.props.StringProperty( - name="Unreal import module", - description="Which module (plugin name) to import to. Default is 'Game', meaning it will be put into your project's /Content/ folder. If you wish to import to a plugin (for example a plugin called 'myPlugin'), just write its name here", - maxlen=512, - default='Game') - - bpy.types.Scene.unreal_import_location = bpy.props.StringProperty( - name="Unreal import location", - description="Unreal assets import location inside the module", - maxlen=512, - default='ImportedFbx') - - 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_mesh_prefix_export_name', - 'scene.skeletal_mesh_prefix_export_name', - 'scene.skeleton_prefix_export_name', - 'scene.alembic_prefix_export_name', - 'scene.anim_prefix_export_name', - 'scene.pose_prefix_export_name', - 'scene.camera_prefix_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', - # Import location: - 'scene.unreal_import_module', - 'scene.unreal_import_location', - ] - - # Directory to store the presets - preset_subdir = 'blender-for-unrealengine/nomenclature-presets' - - 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 - if obj: - if obj.type == "ARMATURE": - UpdateActionCache(obj) - - 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.bfu_anim_nla_export_name - 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" - text = "none" - - def execute(self, context): - correctedProperty = bfu_check_potential_error.CorrectBadProperty() - bfu_check_potential_error.UpdateNameHierarchy() - bfu_check_potential_error.UpdateUnrealPotentialError() - bpy.ops.object.openpotentialerror("INVOKE_DEFAULT", correctedProperty=correctedProperty) - return {'FINISHED'} - - class BFU_OT_OpenPotentialErrorPopup(Operator): - bl_label = "Open potential errors" - bl_idname = "object.openpotentialerror" - bl_description = "Open potential errors" - correctedProperty: bpy.props.IntProperty(default=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 = bfu_check_potential_error.TryToCorrectPotentialError(self.errorIndex) - self.report({'INFO'}, result) - return {'FINISHED'} - - class BFU_OT_SelectObjectButton(Operator): - bl_label = "Select(Object)" - bl_idname = "object.select_error_objet" - bl_description = "Select target Object." - errorIndex: bpy.props.IntProperty(default=-1) - - def execute(self, context): - result = bfu_check_potential_error.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 = bfu_check_potential_error.SelectPotentialErrorVertex(self.errorIndex) - return {'FINISHED'} - - class BFU_OT_SelectPoseBoneButton(Operator): - bl_label = "Select(PoseBone)" - bl_idname = "object.select_error_posebone" - bl_description = "Select target Pose Bone." - errorIndex: bpy.props.IntProperty(default=-1) - - def execute(self, context): - result = bfu_check_potential_error.SelectPotentialErrorPoseBone(self.errorIndex) - return {'FINISHED'} - - class BFU_OT_OpenPotentialErrorDocs(Operator): - bl_label = "Open docs" - bl_idname = "object.open_potential_error_docs" - bl_description = "Open potential error docs." - octicon: StringProperty(default="") - - def execute(self, context): - os.system( - "start \"\" " + - "https://github.com/xavier150/Blender-For-UnrealEngine-Addons/wiki/How-avoid-potential-errors" + - "#"+self.octicon) - return {'FINISHED'} - - def execute(self, context): - return {'FINISHED'} - - def invoke(self, context, event): - 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: - potentialErrorInfo = ( - str(self.correctedProperty) + - "- properties corrected.") - else: - potentialErrorInfo = "- No properties to correct." - - layout.label(text=popup_title) - layout.label(text="- Hierarchy names updated") - layout.label(text=potentialErrorInfo) - 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' - # ---- - - # Text - TextLine = myLine.column() - errorFullMsg = msgType+": "+error.text - splitedText = errorFullMsg.split("\n") - - for text, Line in enumerate(splitedText): - if (text < 1): - - FisrtTextLine = TextLine.row() - if (error.docsOcticon != "None"): # Doc button - props = FisrtTextLine.operator( - "object.open_potential_error_docs", - icon="HELP", - text="") - props.octicon = error.docsOcticon - - FisrtTextLine.label(text=Line, icon=msgIcon) - else: - TextLine.label(text=Line) - - # Select and fix button - 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): - if (error.selectObjectButton): - props = ButtonLine.operator( - "object.select_error_objet") - props.errorIndex = x - if (error.selectVertexButton): - props = ButtonLine.operator( - "object.select_error_vertex") - props.errorIndex = x - if (error.selectPoseBoneButton): - props = ButtonLine.operator( - "object.select_error_posebone") - 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 isReadyForExport(): - - 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 not CheckPluginIsActivated("io_scene_fbx"): - self.report( - {'WARNING'}, - 'Add-on FBX format is not activated!' + - ' Edit > Preferences > Add-ons > And check "FBX format"') - return False - - if not GetIfOneTypeCheck(): - self.report( - {'WARNING'}, - "No asset type is checked.") - return False - - if not len(GetFinalAssetToExport()) > 0: - self.report( - {'WARNING'}, - "Not found assets with" + - " \"Export recursive\" properties " + - "or collection to export.") - return False - - if not bpy.data.is_saved: - # Primary check if file is saved - # to avoid windows PermissionError - self.report( - {'WARNING'}, - "Please save this .blend file before export.") - return False - - if bbpl.basics.IsTweakmode(): - # Need exit Tweakmode because the Animation data is read only. - self.report( - {'WARNING'}, - "Exit Tweakmode in NLA Editor. [Tab]") - return False - - return True - - if not isReadyForExport(): - return {'FINISHED'} - - scene.UnrealExportedAssetsList.clear() - counter = CounterTimer() - bfu_check_potential_error.UpdateNameHierarchy() - bfu_export_asset.ExportForUnrealEngine() - bfu_write_text.WriteAllTextFiles() - - self.report( - {'INFO'}, - "Export of " + - str(len(scene.UnrealExportedAssetsList)) + - " asset(s) has been finalized in " + - str(round(counter.GetTime(), 2)) + - "seconds. Look in console for more info.") - print( - "=========================" + - " Exported asset(s) " + - "=========================") - print("") - lines = bfu_write_text.WriteExportLog().splitlines() - for line in lines: - print(line) - print("") - print( - "=========================" + - " ... " + - "=========================") - - return {'FINISHED'} - - class BFU_OT_CopyImportAssetScriptCommand(Operator): - bl_label = "Copy import script (Assets)" - bl_idname = "object.copy_importassetscript_command" - bl_description = "Copy Import Asset Script command" - - def execute(self, context): - scene = context.scene - setWindowsClipboard(GetImportAssetScriptCommand()) - self.report( - {'INFO'}, - "command for "+scene.file_import_asset_script_name + - " copied") - return {'FINISHED'} - - class BFU_OT_CopyImportSequencerScriptCommand(Operator): - bl_label = "Copy import script (Sequencer)" - bl_idname = "object.copy_importsequencerscript_command" - bl_description = "Copy Import Sequencer Script command" - - def execute(self, context): - scene = context.scene - setWindowsClipboard(GetImportSequencerScriptCommand()) - self.report( - {'INFO'}, - "command for "+scene.file_import_sequencer_script_name + - " copied") - 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=True - ) - - 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=True - ) - - bpy.types.Scene.camera_export = bpy.props.BoolProperty( - name="Camera(s)", - description="Check mark to export Camera(s)", - default=True - ) - - # 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.bfu_export_selection_filter = bpy.props.EnumProperty( - name="Selection filter", - items=[ - ('default', "No Filter", "Export as normal all objects with the recursive export option.", 0), - ('only_object', "Only selected", "Export only the selected object(s)", 1), - ('only_object_action', "Only selected and active action", - "Export only the selected object(s) and active action on this object", 2), - ], - description=( - "Choose what need be export from asset list."), - default="default" - ) - - def draw(self, context): - scene = context.scene - scene = context.scene - addon_prefs = GetAddonPrefs() - - # Categories : - layout = self.layout - - # Presets - row = self.layout.row(align=True) - row.menu('BFU_MT_NomenclaturePresets', text='Export Presets') - row.operator('object.add_nomenclature_preset', text='', icon='ADD') - row.operator( - 'object.add_nomenclature_preset', - text='', - icon='REMOVE').remove_active = True - - bfu_ui_utils.LayoutSection(layout, "bfu_nomenclature_properties_expanded", "Nomenclature") - if scene.bfu_nomenclature_properties_expanded: - - # Prefix - propsPrefix = self.layout.row() - propsPrefix = propsPrefix.column() - propsPrefix.prop(scene, 'static_mesh_prefix_export_name', icon='OBJECT_DATA') - propsPrefix.prop(scene, 'skeletal_mesh_prefix_export_name', icon='OBJECT_DATA') - propsPrefix.prop(scene, 'skeleton_prefix_export_name', icon='OBJECT_DATA') - propsPrefix.prop(scene, 'alembic_prefix_export_name', icon='OBJECT_DATA') - propsPrefix.prop(scene, 'anim_prefix_export_name', icon='OBJECT_DATA') - propsPrefix.prop(scene, 'pose_prefix_export_name', icon='OBJECT_DATA') - propsPrefix.prop(scene, 'camera_prefix_export_name', icon='OBJECT_DATA') - - # Sub folder - propsSub = self.layout.row() - propsSub = propsSub.column() - propsSub.prop(scene, 'anim_subfolder_name', icon='FILE_FOLDER') - - if addon_prefs.useGeneratedScripts: - unreal_import_module = propsSub.column() - unreal_import_module.prop( - scene, - 'unreal_import_module', - icon='FILE_FOLDER') - unreal_import_location = propsSub.column() - unreal_import_location.prop( - scene, - 'unreal_import_location', - icon='FILE_FOLDER') - - # File path - filePath = self.layout.row() - filePath = filePath.column() - filePath.prop(scene, 'export_static_file_path') - filePath.prop(scene, 'export_skeletal_file_path') - filePath.prop(scene, 'export_alembic_file_path') - filePath.prop(scene, 'export_camera_file_path') - filePath.prop(scene, 'export_other_file_path') - - # File name - fileName = self.layout.row() - fileName = fileName.column() - fileName.prop(scene, 'file_export_log_name', icon='FILE') - if addon_prefs.useGeneratedScripts: - fileName.prop( - scene, - 'file_import_asset_script_name', - icon='FILE') - fileName.prop( - scene, - 'file_import_sequencer_script_name', - icon='FILE') - - bfu_ui_utils.LayoutSection(layout, "bfu_export_filter_properties_expanded", "Export filters") - if scene.bfu_export_filter_properties_expanded: - - # Assets - row = layout.row() - col = row.column() - AssetsCol = row.column() - AssetsCol.label(text="Asset types to export", icon='PACKAGE') - AssetsCol.prop(scene, 'static_export') - AssetsCol.prop(scene, 'static_collection_export') - AssetsCol.prop(scene, 'skeletal_export') - AssetsCol.prop(scene, 'anin_export') - AssetsCol.prop(scene, 'alembic_export') - AssetsCol.prop(scene, 'camera_export') - layout.separator() - - # Additional file - FileCol = row.column() - FileCol.label(text="Additional file", icon='PACKAGE') - FileCol.prop(scene, 'text_ExportLog') - FileCol.prop(scene, 'text_ImportAssetScript') - FileCol.prop(scene, 'text_ImportSequenceScript') - if addon_prefs.useGeneratedScripts: - FileCol.prop(scene, 'text_AdditionalData') - - # exportProperty - export_by_select = layout.row() - export_by_select.prop(scene, 'bfu_export_selection_filter') - - bfu_ui_utils.LayoutSection(layout, "bfu_export_process_properties_expanded", "Export process") - if scene.bfu_export_process_properties_expanded: - - # 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(align=True) - checkButton.operator("object.checkpotentialerror", icon='FILE_TICK') - checkButton.operator("object.openpotentialerror", icon='LOOP_BACK', text="") - - exportButton = layout.row() - exportButton.scale_y = 2.0 - exportButton.operator("object.exportforunreal", icon='EXPORT') - - bfu_ui_utils.LayoutSection(layout, "bfu_script_tool_expanded", "Copy Import Script") - if scene.bfu_script_tool_expanded: - if addon_prefs.useGeneratedScripts: - copyButton = layout.row() - copyButton.operator("object.copy_importassetscript_command") - copyButton.operator("object.copy_importsequencerscript_command") - layout.label(text="Click on one of the buttons to copy the import command.", icon='INFO') - layout.label(text="Then paste it into the cmd console of unreal.") - layout.label(text="You need activate python plugins in Unreal Engine.") - - 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_BlenderForUnrealObject" - - 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_PT_BlenderForUnrealObject, - BFU_PT_BlenderForUnrealObject.BFU_MT_ObjectGlobalPropertiesPresets, - BFU_PT_BlenderForUnrealObject.BFU_OT_AddObjectGlobalPropertiesPreset, - BFU_PT_BlenderForUnrealObject.BFU_OT_OpenDocumentationPage, - BFU_PT_BlenderForUnrealObject.BFU_OT_CopyRegularCameraButton, - BFU_PT_BlenderForUnrealObject.BFU_OT_CopyCineCameraButton, - BFU_PT_BlenderForUnrealObject.BFU_OT_ComputLightMap, - BFU_PT_BlenderForUnrealObject.BFU_UL_ActionExportTarget, - BFU_PT_BlenderForUnrealObject.BFU_OT_UpdateObjActionListButton, - BFU_PT_BlenderForUnrealObject.BFU_OT_ShowActionToExport, - BFU_PT_BlenderForUnrealObject.BFU_UL_CollectionExportTarget, - BFU_PT_BlenderForUnrealObject.BFU_OT_UpdateCollectionButton, - BFU_PT_BlenderForUnrealObject.BFU_OT_ShowCollectionToExport, - - BFU_PT_BlenderForUnrealTool, - BFU_PT_BlenderForUnrealTool.BFU_OT_CopyRegularCamerasButton, - BFU_PT_BlenderForUnrealTool.BFU_OT_CopyCineCamerasButton, - BFU_PT_BlenderForUnrealTool.BFU_OT_ConvertToCollisionButtonBox, - BFU_PT_BlenderForUnrealTool.BFU_OT_ConvertToCollisionButtonCapsule, - BFU_PT_BlenderForUnrealTool.BFU_OT_ConvertToCollisionButtonSphere, - BFU_PT_BlenderForUnrealTool.BFU_OT_ConvertToCollisionButtonConvex, - BFU_PT_BlenderForUnrealTool.BFU_OT_ConvertToStaticSocketButton, - BFU_PT_BlenderForUnrealTool.BFU_OT_ConvertToSkeletalSocketButton, - BFU_PT_BlenderForUnrealTool.BFU_OT_CopySkeletalSocketButton, - BFU_PT_BlenderForUnrealTool.BFU_OT_ComputAllLightMap, - - # BFU_PT_BlenderForUnrealDebug, # Unhide for dev - - BFU_PT_Export, - BFU_PT_Export.BFU_MT_NomenclaturePresets, - BFU_PT_Export.BFU_OT_AddNomenclaturePreset, - BFU_PT_Export.BFU_OT_ShowAssetToExport, - BFU_PT_Export.BFU_OT_CheckPotentialErrorPopup, - BFU_PT_Export.BFU_OT_OpenPotentialErrorPopup, - BFU_PT_Export.BFU_OT_OpenPotentialErrorPopup.BFU_OT_FixitTarget, - BFU_PT_Export.BFU_OT_OpenPotentialErrorPopup.BFU_OT_SelectObjectButton, - BFU_PT_Export.BFU_OT_OpenPotentialErrorPopup.BFU_OT_SelectVertexButton, - BFU_PT_Export.BFU_OT_OpenPotentialErrorPopup.BFU_OT_SelectPoseBoneButton, - BFU_PT_Export.BFU_OT_OpenPotentialErrorPopup.BFU_OT_OpenPotentialErrorDocs, - BFU_PT_Export.BFU_OT_ExportForUnrealEngineButton, - BFU_PT_Export.BFU_OT_CopyImportAssetScriptCommand, - BFU_PT_Export.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, - options={'LIBRARY_EDITABLE'}, - override={'LIBRARY_OVERRIDABLE', 'USE_INSERTION'}, - ) - bpy.utils.register_class(BFU_OT_SceneCollectionExport) - bpy.types.Scene.CollectionExportList = CollectionProperty( - type=BFU_OT_SceneCollectionExport, - options={'LIBRARY_EDITABLE'}, - override={'LIBRARY_OVERRIDABLE', 'USE_INSERTION'}, - ) - - 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.types.VIEW3D_MT_uv_map.remove(menu_func) +# ====================== 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 +import addon_utils +import time + +from .export import bfu_export_asset +from . import bfu_write_text +from . import bfu_basics +from .bfu_basics import * +from . import bfu_utils +from .bfu_utils import * +from .export import bfu_export_get_info +from .export.bfu_export_get_info import * +from . import bfu_check_potential_error +from . import bfu_ui_utils +from . import languages +from .languages import * + + +if "bpy" in locals(): + import importlib + if "bfu_export_asset" in locals(): + importlib.reload(bfu_export_asset) + if "bfu_write_text" in locals(): + importlib.reload(bfu_write_text) + if "bfu_basics" in locals(): + importlib.reload(bfu_basics) + if "bfu_utils" in locals(): + importlib.reload(bfu_utils) + if "bfu_export_get_info" in locals(): + importlib.reload(bfu_export_get_info) + if "bfu_check_potential_error" in locals(): + importlib.reload(bfu_check_potential_error) + if "bfu_ui_utils" in locals(): + importlib.reload(bfu_ui_utils) + if "languages" in locals(): + importlib.reload(languages) + + +from bpy.props import ( + StringProperty, + BoolProperty, + EnumProperty, + IntProperty, + FloatProperty, + FloatVectorProperty, + PointerProperty, + CollectionProperty, + ) + +from bpy.types import ( + Operator, + ) + + +class BFU_OT_ObjExportAction(bpy.types.PropertyGroup): + name: StringProperty(name="Action data name", default="Unknown", override={'LIBRARY_OVERRIDABLE'}) + use: BoolProperty(name="use this action", default=False, override={'LIBRARY_OVERRIDABLE'}) + + +class BFU_OT_SceneCollectionExport(bpy.types.PropertyGroup): + name: StringProperty(name="collection data name", default="Unknown", override={'LIBRARY_OVERRIDABLE'}) + use: BoolProperty(name="export this collection", default=False, override={'LIBRARY_OVERRIDABLE'}) + + +class BFU_PT_BlenderForUnrealObject(bpy.types.Panel): + # Unreal engine export panel + + bl_idname = "BFU_PT_BlenderForUnrealObject" + bl_label = "Blender for Unreal Engine" + bl_space_type = "VIEW_3D" + bl_region_type = "UI" + bl_category = "Unreal Engine" + + # Object Properties + bpy.types.Object.ExportEnum = EnumProperty( + name="Export type", + description="Export procedure", + override={'LIBRARY_OVERRIDABLE'}, + items=[ + ("auto", + "Auto", + "Export with the parent if 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=( + 'The name of sub folder.' + + ' You can now use ../ for up one directory.' + ), + override={'LIBRARY_OVERRIDABLE'}, + maxlen=64, + default="", + subtype='FILE_NAME' + ) + + bpy.types.Collection.exportFolderName = StringProperty( + name="Sub folder name", + description=( + 'The name of sub folder.' + + ' You can now use ../ for up one directory.' + ), + override={'LIBRARY_OVERRIDABLE'}, + maxlen=64, + default="", + subtype='FILE_NAME' + ) + + bpy.types.Object.bfu_export_fbx_camera = BoolProperty( + name="Export camera fbx", + description=( + 'True: export .Fbx object and AdditionalTrack.ini ' + + 'Else: export only AdditionalTrack.ini' + ), + override={'LIBRARY_OVERRIDABLE'}, + default=False, + ) + + bpy.types.Object.bfu_fix_axis_flippings = BoolProperty( + name="Fix camera axis flippings", + description=( + 'Disable only if you use extrem camera animation in one frame.' + ), + override={'LIBRARY_OVERRIDABLE'}, + default=True, + ) + + bpy.types.Object.bfu_export_procedure = EnumProperty( + name="Export procedure", + description=( + "This will define how the object should" + + " be exported in case you are using specific Rig." + ), + override={'LIBRARY_OVERRIDABLE'}, + items=[ + ("normal", + "Normal", + "No specific Rig.", + "ARMATURE_DATA", + 1), + ("auto-rig-pro", + "AutoRigPro", + "Export using AutoRigPro.", + "ARMATURE_DATA", + 2), + ] + ) + + bpy.types.Object.ExportAsAlembic = BoolProperty( + name="Export as Alembic animation", + description=( + "If true this mesh will be exported as a Alembic animation" + ), + override={'LIBRARY_OVERRIDABLE'}, + 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" + ), + override={'LIBRARY_OVERRIDABLE'}, + default=False + ) + + bpy.types.Object.ForceStaticMesh = BoolProperty( + name="Force staticMesh", + description="Force export asset like a StaticMesh if is ARMATURE type", + override={'LIBRARY_OVERRIDABLE'}, + 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)" + ), + override={'LIBRARY_OVERRIDABLE'}, + default=True + ) + + bpy.types.Object.bfu_use_custom_export_name = BoolProperty( + name="Export with custom name.", + description=( + "Specify a custom name for the exported file" + ), + override={'LIBRARY_OVERRIDABLE'}, + default=False + ) + + bpy.types.Object.bfu_custom_export_name = StringProperty( + name="", + description="The name of exported file", + override={'LIBRARY_OVERRIDABLE'}, + default="MyObjectExportName.fbx" + ) + + # Object Import Properties + + # Lod list + bpy.types.Object.Ue4Lod1 = PointerProperty( + name="LOD1", + description="Target objet for level of detail 01", + override={'LIBRARY_OVERRIDABLE'}, + type=bpy.types.Object + ) + + bpy.types.Object.Ue4Lod2 = PointerProperty( + name="LOD2", + description="Target objet for level of detail 02", + override={'LIBRARY_OVERRIDABLE'}, + type=bpy.types.Object + ) + + bpy.types.Object.Ue4Lod3 = PointerProperty( + name="LOD3", + description="Target objet for level of detail 03", + override={'LIBRARY_OVERRIDABLE'}, + type=bpy.types.Object + ) + + bpy.types.Object.Ue4Lod4 = PointerProperty( + name="LOD4", + description="Target objet for level of detail 04", + override={'LIBRARY_OVERRIDABLE'}, + type=bpy.types.Object + ) + + bpy.types.Object.Ue4Lod5 = PointerProperty( + name="LOD5", + description="Target objet for level of detail 05", + override={'LIBRARY_OVERRIDABLE'}, + 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", + override={'LIBRARY_OVERRIDABLE'}, + default=True + ) + + bpy.types.Object.bfu_skeleton_search_mode = EnumProperty( + name="Skeleton search mode", + description='Specify the skeleton location in Unreal', + override={'LIBRARY_OVERRIDABLE'}, + items=[ + ("auto", + "Auto", + "...", + 1), + ("custom_name", + "Custom name", + "Default location with custom name", + 2), + ("custom_path_name", + "Custom path and name", + "Set the custom light map resolution", + 3), + ("custom_reference", + "custom reference", + "Reference from Unreal.", + 4) + ] + ) + + bpy.types.Object.bfu_target_skeleton_custom_path = StringProperty( + name="", + description="The path of the Skeleton in Unreal. Skeleton not the skeletal mesh.", + override={'LIBRARY_OVERRIDABLE'}, + default="ImportedFbx" + ) + + bpy.types.Object.bfu_target_skeleton_custom_name = StringProperty( + name="", + description="The name of the Skeleton in Unreal. Skeleton not the skeletal mesh.", + override={'LIBRARY_OVERRIDABLE'}, + default="SKM_MySketonName_Skeleton" + ) + + bpy.types.Object.bfu_target_skeleton_custom_ref = StringProperty( + name="", + description=( + "The full reference of the skeleton in Unreal. " + + "(Use right clic on asset and copy reference.)" + ), + override={'LIBRARY_OVERRIDABLE'}, + default="SkeletalMesh'/Game/ImportedFbx/SKM_MySketonName_Skeleton.SKM_MySketonName_Skeleton'" + ) + + # StaticMeshImportData + # https://api.unrealengine.com/INT/API/Editor/UnrealEd/Factories/UFbxStaticMeshImportData/index.html + + bpy.types.Object.UseStaticMeshLODGroup = BoolProperty( + name="", + description='', + override={'LIBRARY_OVERRIDABLE'}, + 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" + ), + override={'LIBRARY_OVERRIDABLE'}, + maxlen=32, + default="SmallProp" + ) + + bpy.types.Object.StaticMeshLightMapEnum = EnumProperty( + name="Light map", + description='Specify how the light map resolution will be generated', + override={'LIBRARY_OVERRIDABLE'}, + items=[ + ("Default", + "Default", + "Has no effect on light maps", + 1), + ("CustomMap", + "Custom map", + "Set the custom light map resolution", + 2), + ("SurfaceArea", + "surface Area", + "Set light map resolution depending on the surface Area", + 3) + ] + ) + + bpy.types.Object.customStaticMeshLightMapRes = IntProperty( + name="Light Map resolution", + description="This is the resolution of the light map", + override={'LIBRARY_OVERRIDABLE'}, + soft_max=2048, + soft_min=16, + max=4096, # Max for unreal + min=4, # Min for unreal + default=64 + ) + + bpy.types.Object.computedStaticMeshLightMapRes = FloatProperty( + name="computed Light Map resolution", + description="This is the computed resolution of the light map", + override={'LIBRARY_OVERRIDABLE'}, + default=64.0 + ) + + bpy.types.Object.staticMeshLightMapSurfaceScale = FloatProperty( + name="Surface scale", + description="This is for resacle the surface Area value", + override={'LIBRARY_OVERRIDABLE'}, + min=0.00001, # Min for unreal + default=64 + ) + + bpy.types.Object.staticMeshLightMapRoundPowerOfTwo = BoolProperty( + name="Round power of 2", + description=( + "round Light Map resolution to nearest power of 2" + ), + default=True + ) + + bpy.types.Object.useStaticMeshLightMapWorldScale = BoolProperty( + name="Use world scale", + description=( + "If not that will use the object scale." + ), + override={'LIBRARY_OVERRIDABLE'}, + default=False + ) + + bpy.types.Object.GenerateLightmapUVs = BoolProperty( + name="Generate LightmapUVs", + description=( + "If checked, UVs for Lightmap will automatically be generated." + ), + override={'LIBRARY_OVERRIDABLE'}, + default=True, + ) + + bpy.types.Object.convert_geometry_node_attribute_to_uv = BoolProperty( + name="Convert Attribute To Uv", + description=( + "convert target geometry node attribute to UV when found." + ), + override={'LIBRARY_OVERRIDABLE'}, + default=True, + ) + + bpy.types.Object.convert_geometry_node_attribute_to_uv_name = StringProperty( + name="Attribute name", + description=( + "Name of the Attribute to convert" + ), + override={'LIBRARY_OVERRIDABLE'}, + default="UVMap", + ) + + bpy.types.Object.correct_extrem_uv_scale = BoolProperty( + name=(ti('correct_extrem_uv_scale_name')), + description=(tt('correct_extrem_uv_scale_desc')), + override={'LIBRARY_OVERRIDABLE'}, + default=False, + ) + + 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)." + ), + override={'LIBRARY_OVERRIDABLE'}, + 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" + ), + override={'LIBRARY_OVERRIDABLE'}, + # Vania python: + # https://docs.unrealengine.com/en-US/PythonAPI/class/MaterialSearchLocation.html?highlight=materialsearchlocation + # C++ API: + # 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", + "Under parent", + "Search for matching material recursively from parent folder.", + 2), + ("UnderRoot", + "Under root", + "Search for matching material recursively from root folder.", + 3), + ("AllAssets", + "All assets", + "Search for matching material in all assets folders.", + 4) + ] + ) + + bpy.types.Object.CollisionTraceFlag = EnumProperty( + name="Collision Complexity", + description="Collision Trace Flag", + override={'LIBRARY_OVERRIDABLE'}, + # Vania python + # https://docs.unrealengine.com/en-US/PythonAPI/class/CollisionTraceFlag.html + # C++ API + # 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", + override={'LIBRARY_OVERRIDABLE'}, + # Vania python + # https://docs.unrealengine.com/en-US/PythonAPI/class/VertexColorImportOption.html + # C++ API + # https://docs.unrealengine.com/en-US/API/Editor/UnrealEd/Factories/EVertexColorImportOption__Type/index.html + items=[ + ("IGNORE", "Ignore", + "Ignore vertex colors, and keep the existing mesh vertex colors.", 1), + ("OVERRIDE", "Override", + "Override all vertex colors with the specified color.", 2), + ("REPLACE", "Replace", + "Import the static mesh using the target vertex colors.", 0) + ], + default="REPLACE" + ) + + bpy.types.Object.VertexOverrideColor = FloatVectorProperty( + name="Vertex Override Color", + subtype='COLOR', + description="Specify override color in the case that VertexColorImportOption is set to Override", + override={'LIBRARY_OVERRIDABLE'}, + default=(1.0, 1.0, 1.0), + min=0.0, + max=1.0 + # Vania python + # https://docs.unrealengine.com/en-US/PythonAPI/class/FbxSkeletalMeshImportData.html + ) + + bpy.types.Object.VertexColorToUse = EnumProperty( + name="Vertex Color to use", + description="Specify which vertex colors should be imported", + override={'LIBRARY_OVERRIDABLE'}, + items=[ + ("FirstIndex", "First Index", + "Use the the first index in Object Data -> Vertex Color.", 0), + ("LastIndex", "Last Index", + "Use the the last index in Object Data -> Vertex Color.", 1), + ("ActiveIndex", "Active Render", + "Use the the active index in Object Data -> Vertex Color.", 2), + ("CustomIndex", "CustomIndex", + "Use a specific Vertex Color in Object Data -> Vertex Color.", 3) + ], + default="ActiveIndex" + ) + + bpy.types.Object.VertexColorIndexToUse = IntProperty( + name="Vertex color index", + description="Vertex Color index to use.", + override={'LIBRARY_OVERRIDABLE'}, + default=0 + ) + + bpy.types.Object.bfu_anim_action_export_enum = EnumProperty( + name="Action to export", + description="Export procedure for actions (Animations and poses)", + override={'LIBRARY_OVERRIDABLE'}, + 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", + override={'LIBRARY_OVERRIDABLE'}, + default=0 + ) + + bpy.types.Object.PrefixNameToExport = StringProperty( + # properties used with ""export_specific_prefix" on bfu_anim_action_export_enum + name="Prefix name", + description="Indicate the prefix of the actions that must be exported", + override={'LIBRARY_OVERRIDABLE'}, + maxlen=32, + default="Example_", + ) + + bpy.types.Object.bfu_anim_action_start_end_time_enum = EnumProperty( + name="Action Start/End Time", + description="Set when animation starts and end", + override={'LIBRARY_OVERRIDABLE'}, + 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 "bfu_anim_action_custom_start_frame" and "bfu_anim_action_custom_end_frame"', + "HAND", + 3), + ] + ) + + bpy.types.Object.bfu_anim_action_start_frame_offset = IntProperty( + name="Offset at start frame", + description="Offset for the start frame.", + override={'LIBRARY_OVERRIDABLE'}, + default=0 + ) + + bpy.types.Object.bfu_anim_action_end_frame_offset = 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" + ), + override={'LIBRARY_OVERRIDABLE'}, + default=0 + ) + + bpy.types.Object.bfu_anim_action_custom_start_frame = IntProperty( + name="Custom start time", + description="Set when animation start", + override={'LIBRARY_OVERRIDABLE'}, + default=0 + ) + + bpy.types.Object.bfu_anim_action_custom_end_frame = IntProperty( + name="Custom end time", + description="Set when animation end", + override={'LIBRARY_OVERRIDABLE'}, + default=1 + ) + + bpy.types.Object.bfu_anim_nla_start_end_time_enum = EnumProperty( + name="NLA Start/End Time", + description="Set when animation starts and end", + override={'LIBRARY_OVERRIDABLE'}, + items=[ + ("with_sceneframes", + "Scene time", + "Time will be equal to the scene time", + "SCENE_DATA", + 1), + ("with_customframes", + "Custom time", + 'The time of all the animations of this object' + + ' is defined by you.' + + ' Use "bfu_anim_action_custom_start_frame" and "bfu_anim_action_custom_end_frame"', + "HAND", + 2), + ] + ) + + bpy.types.Object.bfu_anim_nla_start_frame_offset = IntProperty( + name="Offset at start frame", + description="Offset for the start frame.", + override={'LIBRARY_OVERRIDABLE'}, + default=0 + ) + + bpy.types.Object.bfu_anim_nla_end_frame_offset = 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" + ), + override={'LIBRARY_OVERRIDABLE'}, + default=0 + ) + + bpy.types.Object.bfu_anim_nla_custom_start_frame = IntProperty( + name="Custom start time", + description="Set when animation start", + override={'LIBRARY_OVERRIDABLE'}, + default=0 + ) + + bpy.types.Object.bfu_anim_nla_custom_end_frame = IntProperty( + name="Custom end time", + description="Set when animation end", + override={'LIBRARY_OVERRIDABLE'}, + default=1 + ) + + + bpy.types.Object.SampleAnimForExport = FloatProperty( + name="Sampling Rate", + description="How often to evaluate animated values (in frames)", + override={'LIBRARY_OVERRIDABLE'}, + 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)" + ), + override={'LIBRARY_OVERRIDABLE'}, + # No simplification to up to 10% of current magnitude tolerance. + min=0.0, max=100.0, + soft_min=0.0, soft_max=10.0, + default=0.0, + ) + + bpy.types.Object.bfu_anim_nla_use = BoolProperty( + name="Export NLA (Nonlinear Animation)", + description=( + "If checked, exports the all animation of the scene with the NLA " + + "(Don't work with Auto-Rig Pro for the moment.)" + ), + override={'LIBRARY_OVERRIDABLE'}, + default=False + ) + + bpy.types.Object.bfu_anim_nla_export_name = StringProperty( + name="NLA export name", + description="Export NLA name (Don't work with Auto-Rig Pro for the moment.)", + override={'LIBRARY_OVERRIDABLE'}, + maxlen=64, + default="NLA_animation", + subtype='FILE_NAME' + ) + + bpy.types.Object.bfu_anim_naming_type = EnumProperty( + name="Naming type", + override={'LIBRARY_OVERRIDABLE'}, + items=[ + ('action_name', "Action name", 'Exemple: "Anim_MyAction"'), + ('include_armature_name', + "Include Armature Name", + 'Include armature name in animation export file name.' + + ' Exemple: "Anim_MyArmature_MyAction"'), + ('include_custom_name', + "Include custom name", + 'Include custom name in animation export file name.' + + ' Exemple: "Anim_MyCustomName_MyAction"'), + ], + default='action_name' + ) + + bpy.types.Object.bfu_anim_naming_custom = StringProperty( + name="Export name", + override={'LIBRARY_OVERRIDABLE'}, + default='MyCustomName' + ) + + bpy.types.Object.exportGlobalScale = FloatProperty( + name="Global scale", + description="Scale, change is not recommended with SkeletalMesh.", + override={'LIBRARY_OVERRIDABLE'}, + default=1.0 + ) + + bpy.types.Object.exportAxisForward = EnumProperty( + name="Axis Forward", + override={'LIBRARY_OVERRIDABLE'}, + 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", + override={'LIBRARY_OVERRIDABLE'}, + 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", + override={'LIBRARY_OVERRIDABLE'}, + items=[ + ('X', "X", ""), + ('Y', "Y", ""), + ('Z', "Z", ""), + ('-X', "-X", ""), + ('-Y', "-Y", ""), + ('-Z', "-Z", ""), + ], + default='Y', + ) + + bpy.types.Object.exporSecondaryBoneAxis = EnumProperty( + name="Secondary Axis Bone", + override={'LIBRARY_OVERRIDABLE'}, + 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)" + ), + override={'LIBRARY_OVERRIDABLE'}, + 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." + ), + override={'LIBRARY_OVERRIDABLE'}, + default=False + ) + + bpy.types.Object.MoveActionToCenterForExport = BoolProperty( + name="Move animation to center", + description=( + "(Action animation only) 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)" + + " Note: Unreal Engine ignore the position of the skeleton at the import." + ), + override={'LIBRARY_OVERRIDABLE'}, + default=True + ) + + bpy.types.Object.RotateActionToZeroForExport = BoolProperty( + name="Rotate Action to zero", + description=( + "(Action animation only) If true use object rotation else use scene rotation." + + " | If true the mesh will use zero rotation for export." + ), + override={'LIBRARY_OVERRIDABLE'}, + default=False + ) + + bpy.types.Object.MoveNLAToCenterForExport = BoolProperty( + name="Move NLA to center", + description=( + "(Non linear animation only) 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)" + + " Note: Unreal Engine ignore the position of the skeleton at the import." + ), + override={'LIBRARY_OVERRIDABLE'}, + default=True + ) + + bpy.types.Object.RotateNLAToZeroForExport = BoolProperty( + name="Rotate NLA to zero", + description=( + "(Non linear animation only) If true use object rotation else use scene rotation." + + " | If true the mesh will use zero rotation for export." + ), + override={'LIBRARY_OVERRIDABLE'}, + default=False + ) + + bpy.types.Object.AdditionalLocationForExport = FloatVectorProperty( + name="Additional location", + description=( + "This will add a additional absolute location to the mesh" + ), + override={'LIBRARY_OVERRIDABLE'}, + 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" + ), + override={'LIBRARY_OVERRIDABLE'}, + subtype="EULER", + default=(0, 0, 0) + ) + + # Scene and global + + bpy.types.Scene.active_CollectionExportList = IntProperty( + name="Active Collection", + description="Index of the currently active collection", + override={'LIBRARY_OVERRIDABLE'}, + default=0 + ) + + class BFU_OT_OpenDocumentationPage(Operator): + bl_label = "Documentation" + bl_idname = "object.bfu_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/wiki" + ) + return {'FINISHED'} + + class BFU_OT_CopyRegularCameraButton(Operator): + bl_label = "Copy Regular Camera for Unreal" + bl_idname = "object.copy_regular_camera_command" + bl_description = "Copy Regular Camera Script command" + + def execute(self, context): + obj = context.object + result = GetImportCameraScriptCommand([obj], False) + if result[0]: + setWindowsClipboard(result[1]) + self.report({'INFO'}, result[2]) + else: + self.report({'WARNING'}, result[2]) + return {'FINISHED'} + + class BFU_OT_CopyCineCameraButton(Operator): + bl_label = "Copy Cine Camera for Unreal" + bl_idname = "object.copy_cine_camera_command" + bl_description = "Copy Cine Camera Script command" + + def execute(self, context): + obj = context.object + result = GetImportCameraScriptCommand([obj], True) + if result[0]: + setWindowsClipboard(result[1]) + self.report({'INFO'}, result[2]) + else: + self.report({'WARNING'}, result[2]) + return {'FINISHED'} + + class BFU_OT_ComputLightMap(Operator): + bl_label = "Calculate surface area" + bl_idname = "object.computlightmap" + bl_description = "Click to calculate the surface of the object" + + def execute(self, context): + obj = context.object + obj.computedStaticMeshLightMapRes = GetExportRealSurfaceArea(obj) + self.report( + {'INFO'}, + "Light map area updated to " + str(round(obj.computedStaticMeshLightMapRes)) + ". " + + "Compunted Light map: " + str(GetCompuntedLightMap(obj))) + return {'FINISHED'} + + + # Animation : + + class BFU_UL_ActionExportTarget(bpy.types.UIList): + def draw_item( + self, + context, + layout, + data, + item, + icon, + active_data, + active_propname + ): + ActionIsValid = False + if item.name in bpy.data.actions: + bpy.data.actions[item.name] + ActionIsValid = True + + if self.layout_type in {'DEFAULT', 'COMPACT'}: + if ActionIsValid: # If action is valid + 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 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]: + 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 + useFromLast = SetUseFromLast(animSave, action.name) + obj.exportActionList[action.name].use = useFromLast + 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 + UpdateActionCache(obj) + animation_to_export = GetActionToExport(obj) + + popup_title = "Action list" + if len(animation_to_export) > 0: + animationNumber = len(animation_to_export) + if obj.bfu_anim_nla_use: + animationNumber += 1 + popup_title = ( + str(animationNumber) + + ' action(s) found for obj named "'+obj.name+'".' + ) + else: + popup_title = ( + 'No action found for obj named "' + + obj.name+'".') + + def draw(self, context): + col = self.layout.column() + + def addAnimRow( + action_name, + action_type, + frame_start, + frame_end): + row = col.row() + row.label( + text="- ["+action_name + + "] Frame "+frame_start+" to "+frame_end + + " ("+action_type+")" + ) + + for action in animation_to_export: + Frames = GetDesiredActionStartEndTime(obj, action) + frame_start = str(Frames[0]) + frame_end = str(Frames[1]) + addAnimRow( + action.name, + GetActionType(action), + frame_start, + frame_end) + if obj.bfu_anim_nla_use: + scene = context.scene + addAnimRow( + obj.bfu_anim_nla_export_name, + "NlAnim", + str(scene.frame_start), + str(scene.frame_end) + ) + + bpy.context.window_manager.popup_menu( + draw, + title=popup_title, + icon='ACTION' + ) + return {'FINISHED'} + + 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', + 'col = bpy.context.collection', + 'scene = bpy.context.scene' + ] + + # Properties to store in the preset + preset_values = [ + 'obj.exportFolderName', + 'col.exportFolderName', + 'obj.bfu_export_fbx_camera', + 'obj.bfu_fix_axis_flippings', + 'obj.ExportAsAlembic', + 'obj.ExportAsLod', + 'obj.ForceStaticMesh', + 'obj.exportDeformOnly', + 'obj.Ue4Lod1', + 'obj.Ue4Lod2', + 'obj.Ue4Lod3', + 'obj.Ue4Lod4', + 'obj.Ue4Lod5', + 'obj.CreatePhysicsAsset', + 'obj.bfu_skeleton_search_mode', + 'obj.bfu_target_skeleton_custom_path', + 'obj.bfu_target_skeleton_custom_name', + 'obj.bfu_target_skeleton_custom_ref', + 'obj.UseStaticMeshLODGroup', + 'obj.StaticMeshLODGroup', + 'obj.StaticMeshLightMapEnum', + 'obj.customStaticMeshLightMapRes', + 'obj.staticMeshLightMapSurfaceScale', + 'obj.staticMeshLightMapRoundPowerOfTwo', + 'obj.useStaticMeshLightMapWorldScale', + 'obj.GenerateLightmapUVs', + 'obj.convert_geometry_node_attribute_to_uv', + 'obj.convert_geometry_node_attribute_to_uv_name', + 'obj.correct_extrem_uv_scale', + 'obj.AutoGenerateCollision', + 'obj.MaterialSearchLocation', + 'obj.CollisionTraceFlag', + 'obj.VertexColorImportOption', + 'obj.VertexOverrideColor', + 'obj.VertexColorToUse', + 'obj.VertexColorIndexToUse', + 'obj.bfu_anim_action_export_enum', + 'obj.PrefixNameToExport', + 'obj.bfu_anim_action_start_end_time_enum', + 'obj.bfu_anim_nla_start_end_time_enum', + 'obj.bfu_anim_action_start_frame_offset', + 'obj.bfu_anim_action_end_frame_offset', + 'obj.bfu_anim_action_custom_start_frame', + 'obj.bfu_anim_action_custom_end_frame', + 'obj.bfu_anim_nla_start_frame_offset', + 'obj.bfu_anim_nla_end_frame_offset', + 'obj.bfu_anim_nla_custom_start_frame', + 'obj.bfu_anim_nla_custom_end_frame', + 'obj.SampleAnimForExport', + 'obj.SimplifyAnimForExport', + 'obj.bfu_anim_nla_use', + 'obj.bfu_anim_nla_export_name', + 'obj.bfu_anim_naming_type', + 'obj.bfu_anim_naming_custom', + 'obj.exportGlobalScale', + 'obj.exportAxisForward', + 'obj.exportAxisUp', + 'obj.exportPrimaryBaneAxis', + 'obj.exporSecondaryBoneAxis', + 'obj.MoveToCenterForExport', + 'obj.RotateToZeroForExport', + 'obj.MoveActionToCenterForExport', + 'obj.RotateActionToZeroForExport', + 'obj.MoveNLAToCenterForExport', + 'obj.RotateNLAToZeroForExport', + 'obj.AdditionalLocationForExport', + 'obj.AdditionalRotationForExport', + ] + + # Directory to store the presets + preset_subdir = 'blender-for-unrealengine/global-properties-presets' + + class BFU_UL_CollectionExportTarget(bpy.types.UIList): + + def draw_item( + self, + context, + layout, + data, + item, + icon, + active_data, + active_propname + ): + + CollectionIsValid = False + if item.name in bpy.data.collections: + bpy.data.collections[item.name] + CollectionIsValid = True + + if self.layout_type in {'DEFAULT', 'COMPACT'}: + if CollectionIsValid: # If action is valid + layout.prop( + bpy.data.collections[item.name], + "name", + text="", + emboss=False, + icon="OUTLINER_COLLECTION") + 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(scene): + # Update the provisional collection list known by the object + + def SetUseFromLast(list, CollectionName): + for item in list: + if item[0] == CollectionName: + if item[1]: + return True + return False + + colSave = [["", False]] + for col in scene.CollectionExportList: # CollectionProperty + name = col.name + use = col.use + colSave.append([name, use]) + scene.CollectionExportList.clear() + for col in bpy.data.collections: + scene.CollectionExportList.add().name = col.name + useFromLast = SetUseFromLast(colSave, col.name) + scene.CollectionExportList[col.name].use = useFromLast + UpdateExportCollectionList(context.scene) + return {'FINISHED'} + + class BFU_OT_ShowCollectionToExport(Operator): + bl_label = "Show collection(s)" + bl_idname = "object.showscenecollection" + bl_description = "Click to show collections to export" + + def execute(self, context): + scene = context.scene + collections = GetCollectionToExport(scene) + 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.name) + bpy.context.window_manager.popup_menu( + draw, + title=popup_title, + icon='GROUP') + return {'FINISHED'} + + def draw(self, contex): + scene = bpy.context.scene + obj = bpy.context.object + addon_prefs = GetAddonPrefs() + layout = self.layout + + version = "-1" + for addon in addon_utils.modules(): + if addon.bl_info['name'] == "Blender for UnrealEngine": + version = addon.bl_info.get('version', (-1, -1, -1)) + + credit_box = layout.box() + credit_box.label(text=ti('intro')+' Version: '+str(version)) + credit_box.operator("object.bfu_open_documentation_page", icon="HELP") + + row = 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 + + layout.row().prop(scene, "bfu_active_tab", expand=True) + if scene.bfu_active_tab == "OBJECT": + layout.row().prop(scene, "bfu_active_object_tab", expand=True) + if scene.bfu_active_tab == "SCENE": + layout.row().prop(scene, "bfu_active_scene_tab", expand=True) + + if bfu_ui_utils.DisplayPropertyFilter("OBJECT", "GENERAL"): + bfu_ui_utils.LayoutSection(layout, "bfu_object_properties_expanded", "Object Properties") + if scene.bfu_object_properties_expanded: + + if obj is None: + layout.row().label(text='No selected object.') + else: + + AssetType = layout.row() + AssetType.prop(obj, 'name', text="", icon='OBJECT_DATA') + # Show asset type + AssetType.label(text='('+GetAssetType(obj)+')') + + ExportType = layout.column() + ExportType.prop(obj, 'ExportEnum') + + if obj.type == "CAMERA": + CameraProp = layout.column() + CameraProp.operator("object.copy_regular_camera_command", icon="COPYDOWN") + CameraProp.operator("object.copy_cine_camera_command", icon="COPYDOWN") + + if obj.ExportEnum == "export_recursive": + + folderNameProperty = layout.column() + folderNameProperty.prop( + obj, + 'exportFolderName', + icon='FILE_FOLDER' + ) + + if obj.type == "CAMERA": + CameraProp.prop(obj, 'bfu_export_fbx_camera') + CameraProp.prop(obj, 'bfu_fix_axis_flippings') + + else: + ProxyProp = layout.column() + if GetExportAsProxy(obj): + ProxyProp.label( + text="The Armature was detected as a proxy." + ) + proxy_child = GetExportProxyChild(obj) + if proxy_child: + ProxyProp.label( + text="Proxy child: " + proxy_child.name + ) + else: + ProxyProp.label(text="Proxy child not found") + + export_procedure_prop = layout.column() + export_procedure_prop.prop(obj, 'bfu_export_procedure') + + if not GetExportAsProxy(obj): + AlembicProp = layout.column() + AlembicProp.prop(obj, 'ExportAsAlembic') + if obj.ExportAsAlembic: + AlembicProp.label( + text="(Alembic animation are exported" + + " with scene position.)" + ) + AlembicProp.label( + text="(Use import script for" + + " use the origin position.)" + ) + else: + if addon_prefs.useGeneratedScripts: + # Unreal python no longer support Skeletal mesh LODS import. + if GetAssetType(obj) != "SkeletalMesh": + LodProp = layout.column() + LodProp.prop(obj, 'ExportAsLod') + if not obj.ExportAsAlembic: + if obj.type == "ARMATURE": + AssetType2 = layout.column() + # Show asset type + AssetType2.prop(obj, "ForceStaticMesh") + if GetAssetType(obj) == "SkeletalMesh": + AssetType2.prop(obj, 'exportDeformOnly') + + if not GetExportAsProxy(obj): + # exportCustomName + exportCustomName = layout.row() + exportCustomName.prop(obj, "bfu_use_custom_export_name") + useCustomName = obj.bfu_use_custom_export_name + exportCustomNameText = exportCustomName.column() + exportCustomNameText.prop(obj, "bfu_custom_export_name") + exportCustomNameText.enabled = useCustomName + + bfu_ui_utils.LayoutSection(layout, "bfu_object_advanced_properties_expanded", "Object advanced Properties") + if scene.bfu_object_advanced_properties_expanded: + if obj is not None: + if obj.ExportEnum == "export_recursive": + + transformProp = layout.column() + if GetAssetType(obj) != "Alembic" and GetAssetType(obj) != "Camera": + transformProp.prop(obj, "MoveToCenterForExport") + transformProp.prop(obj, "RotateToZeroForExport") + transformProp.prop(obj, "AdditionalLocationForExport") + transformProp.prop(obj, "AdditionalRotationForExport") + transformProp.prop(obj, 'exportGlobalScale') + elif GetAssetType(obj) == "Camera": + transformProp.prop(obj, "AdditionalLocationForExport") + + 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.)') + + bfu_ui_utils.LayoutSection(layout, "bfu_skeleton_properties_expanded", "Skeleton") + if scene.bfu_skeleton_properties_expanded: + if addon_prefs.useGeneratedScripts and obj is not None: + if obj.ExportEnum == "export_recursive": + + # SkeletalMesh prop + if GetAssetType(obj) == "SkeletalMesh": + if not obj.ExportAsLod: + + Ue4Skeleton = layout.column() + Ue4Skeleton.prop(obj, "bfu_skeleton_search_mode") + if obj.bfu_skeleton_search_mode == "auto": + pass + if obj.bfu_skeleton_search_mode == "custom_name": + Ue4Skeleton.prop(obj, "bfu_target_skeleton_custom_name") + if obj.bfu_skeleton_search_mode == "custom_path_name": + Ue4Skeleton.prop(obj, "bfu_target_skeleton_custom_path") + Ue4Skeleton.prop(obj, "bfu_target_skeleton_custom_name") + if obj.bfu_skeleton_search_mode == "custom_reference": + Ue4Skeleton.prop(obj, "bfu_target_skeleton_custom_ref") + + if bfu_ui_utils.DisplayPropertyFilter("OBJECT", "ANIM"): + if obj is not None: + if obj.ExportEnum == "export_recursive" and not obj.ExportAsLod: + + bfu_ui_utils.LayoutSection(layout, "bfu_animation_action_properties_expanded", "Actions Properties") + if scene.bfu_animation_action_properties_expanded: + if (GetAssetType(obj) == "SkeletalMesh" or + GetAssetType(obj) == "Camera" or + GetAssetType(obj) == "Alembic"): + + if GetAssetType(obj) == "SkeletalMesh": + # Action list + ActionListProperty = layout.column() + ActionListProperty.prop(obj, 'bfu_anim_action_export_enum') + if obj.bfu_anim_action_export_enum == "export_specific_list": + ActionListProperty.template_list( + # type and unique id + "BFU_UL_ActionExportTarget", "", + # pointer to the CollectionProperty + obj, "exportActionList", + # pointer to the active identifier + obj, "active_ObjectAction", + maxrows=5, + rows=5 + ) + ActionListProperty.operator( + "object.updateobjactionlist", + icon='RECOVER_LAST') + if obj.bfu_anim_action_export_enum == "export_specific_prefix": + ActionListProperty.prop(obj, 'PrefixNameToExport') + + # Action Time + if obj.type != "CAMERA" and obj.bfu_export_procedure != "auto-rig-pro": + ActionTimeProperty = layout.column() + ActionTimeProperty.enabled = obj.bfu_anim_action_export_enum != 'dont_export' + ActionTimeProperty.prop(obj, 'bfu_anim_action_start_end_time_enum') + if obj.bfu_anim_action_start_end_time_enum == "with_customframes": + OfsetTime = ActionTimeProperty.row() + OfsetTime.prop(obj, 'bfu_anim_action_custom_start_frame') + OfsetTime.prop(obj, 'bfu_anim_action_custom_end_frame') + if obj.bfu_anim_action_start_end_time_enum != "with_customframes": + OfsetTime = ActionTimeProperty.row() + OfsetTime.prop(obj, 'bfu_anim_action_start_frame_offset') + OfsetTime.prop(obj, 'bfu_anim_action_end_frame_offset') + + else: + layout.label( + text=( + "Note: animation start/end use scene frames" + + " with the camera for the sequencer.") + ) + + # Nomenclature + if GetAssetType(obj) == "SkeletalMesh": + export_anim_naming = layout.column() + export_anim_naming.enabled = obj.bfu_anim_action_export_enum != 'dont_export' + export_anim_naming.prop(obj, 'bfu_anim_naming_type') + if obj.bfu_anim_naming_type == "include_custom_name": + export_anim_naming_text = export_anim_naming.column() + export_anim_naming_text.prop(obj, 'bfu_anim_naming_custom') + + + + else: + layout.label( + text='(This assets is not a SkeletalMesh or Camera)') + + bfu_ui_utils.LayoutSection(layout, "bfu_animation_action_advanced_properties_expanded", "Actions Advanced Properties") + if scene.bfu_animation_action_advanced_properties_expanded: + + if GetAssetType(obj) != "Alembic": + transformProp = layout.column() + transformProp.enabled = obj.bfu_anim_action_export_enum != 'dont_export' + transformProp.prop(obj, "MoveActionToCenterForExport") + transformProp.prop(obj, "RotateActionToZeroForExport") + + bfu_ui_utils.LayoutSection(layout, "bfu_animation_nla_properties_expanded", "NLA Properties") + if scene.bfu_animation_nla_properties_expanded: + # NLA + if GetAssetType(obj) == "SkeletalMesh": + NLAAnim = layout.row() + NLAAnim.prop(obj, 'bfu_anim_nla_use') + NLAAnimChild = NLAAnim.column() + NLAAnimChild.enabled = obj.bfu_anim_nla_use + NLAAnimChild.prop(obj, 'bfu_anim_nla_export_name') + if obj.bfu_export_procedure == "auto-rig-pro": + NLAAnim.enabled = False + NLAAnimChild.enabled = False + + # NLA Time + if obj.type != "CAMERA" and obj.bfu_export_procedure != "auto-rig-pro": + NLATimeProperty = layout.column() + NLATimeProperty.enabled = obj.bfu_anim_nla_use + NLATimeProperty.prop(obj, 'bfu_anim_nla_start_end_time_enum') + if obj.bfu_anim_nla_start_end_time_enum == "with_customframes": + OfsetTime = NLATimeProperty.row() + OfsetTime.prop(obj, 'bfu_anim_nla_custom_start_frame') + OfsetTime.prop(obj, 'bfu_anim_nla_custom_end_frame') + if obj.bfu_anim_nla_start_end_time_enum != "with_customframes": + OfsetTime = NLATimeProperty.row() + OfsetTime.prop(obj, 'bfu_anim_nla_start_frame_offset') + OfsetTime.prop(obj, 'bfu_anim_nla_end_frame_offset') + + bfu_ui_utils.LayoutSection(layout, "bfu_animation_nla_advanced_properties_expanded", "NLA Advanced Properties") + if scene.bfu_animation_nla_advanced_properties_expanded: + if GetAssetType(obj) != "Alembic": + transformProp2 = layout.column() + transformProp2.enabled = obj.bfu_anim_nla_use + transformProp2.prop(obj, "MoveNLAToCenterForExport") + transformProp2.prop(obj, "RotateNLAToZeroForExport") + + bfu_ui_utils.LayoutSection(layout, "bfu_animation_advanced_properties_expanded", "Animation Advanced Properties") + if scene.bfu_animation_advanced_properties_expanded: + # Animation fbx properties + if (GetAssetType(obj) != "Alembic"): + propsFbx = layout.row() + if obj.bfu_export_procedure != "auto-rig-pro": + propsFbx.prop(obj, 'SampleAnimForExport') + propsFbx.prop(obj, 'SimplifyAnimForExport') + + # Armature export action list feedback + if GetAssetType(obj) == "SkeletalMesh": + layout.label( + text='Note: The Action with only one' + + ' frame are exported like Pose.') + ArmaturePropertyInfo = ( + layout.row().box().split(factor=0.75) + ) + ActionNum = len(GetActionToExport(obj)) + if obj.bfu_anim_nla_use: + ActionNum += 1 + actionFeedback = ( + str(ActionNum) + + " Animation(s) will be exported with this object.") + ArmaturePropertyInfo.label( + text=actionFeedback, + icon='INFO') + ArmaturePropertyInfo.operator("object.showobjaction") + else: + layout.label(text='(No properties to show.)') + else: + layout.label(text='(No properties to show.)') + + if bfu_ui_utils.DisplayPropertyFilter("OBJECT", "MISC"): + bfu_ui_utils.LayoutSection(layout, "bfu_object_lod_properties_expanded", "Lod") + if scene.bfu_object_lod_properties_expanded: + if addon_prefs.useGeneratedScripts and obj is not None: + if obj.ExportEnum == "export_recursive": + + # Lod selection + if not obj.ExportAsLod: + # Unreal python no longer support Skeletal mesh LODS import. + if (GetAssetType(obj) == "StaticMesh"): + LodList = layout.column() + LodList.prop(obj, 'Ue4Lod1') + LodList.prop(obj, 'Ue4Lod2') + LodList.prop(obj, 'Ue4Lod3') + LodList.prop(obj, 'Ue4Lod4') + LodList.prop(obj, 'Ue4Lod5') + + # StaticMesh prop + if GetAssetType(obj) == "StaticMesh": + if not obj.ExportAsLod: + StaticMeshLODGroup = layout.row() + StaticMeshLODGroup.prop( + obj, + 'UseStaticMeshLODGroup', + text="") + SMLODGroupChild = StaticMeshLODGroup.column() + SMLODGroupChild.enabled = obj.UseStaticMeshLODGroup + SMLODGroupChild.prop( + obj, + 'StaticMeshLODGroup' + ) + + bfu_ui_utils.LayoutSection(layout, "bfu_object_collision_properties_expanded", "Collision") + if scene.bfu_object_collision_properties_expanded: + if addon_prefs.useGeneratedScripts and obj is not None: + if obj.ExportEnum == "export_recursive": + + # StaticMesh prop + if GetAssetType(obj) == "StaticMesh": + StaticMeshCollisionTraceFlag = layout.row() + StaticMeshCollisionTraceFlag.prop( + obj, + 'CollisionTraceFlag' + ) + if not obj.ExportAsLod: + AutoGenerateCollision = layout.row() + AutoGenerateCollision.prop( + obj, + 'AutoGenerateCollision' + ) + # SkeletalMesh prop + if GetAssetType(obj) == "SkeletalMesh": + if not obj.ExportAsLod: + CreatePhysicsAsset = layout.row() + CreatePhysicsAsset.prop(obj, "CreatePhysicsAsset") + + bfu_ui_utils.LayoutSection(layout, "bfu_object_material_properties_expanded", "Material") + if scene.bfu_object_material_properties_expanded: + if addon_prefs.useGeneratedScripts and obj is not None: + if obj.ExportEnum == "export_recursive": + + # MaterialSearchLocation + if not obj.ExportAsLod: + if (GetAssetType(obj) == "StaticMesh" or + GetAssetType(obj) == "SkeletalMesh" or + GetAssetType(obj) == "Alembic"): + MaterialSearchLocation = layout.row() + MaterialSearchLocation.prop( + obj, 'MaterialSearchLocation') + + bfu_ui_utils.LayoutSection(layout, "bfu_object_vertex_color_properties_expanded", "Vertex color") + if scene.bfu_object_vertex_color_properties_expanded: + if addon_prefs.useGeneratedScripts and obj is not None: + if obj.ExportEnum == "export_recursive": + + # Vertex color + StaticMeshVertexColorImportOption = layout.column() + StaticMeshVertexColorImportOption.prop(obj, 'VertexColorImportOption') + if obj.VertexColorImportOption == "OVERRIDE": + StaticMeshVertexColorImportOptionColor = StaticMeshVertexColorImportOption.row() + StaticMeshVertexColorImportOptionColor.prop(obj, 'VertexOverrideColor') + if obj.VertexColorImportOption == "REPLACE": + StaticMeshVertexColorImportOptionIndex = StaticMeshVertexColorImportOption.row() + StaticMeshVertexColorImportOptionIndex.prop(obj, 'VertexColorToUse') + if obj.VertexColorToUse == "CustomIndex": + StaticMeshVertexColorImportOptionIndexCustom = StaticMeshVertexColorImportOption.row() + StaticMeshVertexColorImportOptionIndexCustom.prop(obj, 'VertexColorIndexToUse') + + StaticMeshVertexColorFeedback = StaticMeshVertexColorImportOption.row() + if obj.type == "MESH": + vced = VertexColorExportData(obj) + if vced.export_type == "REPLACE": + my_text = 'Vertex color nammed "' + vced.name + '" will be used.' + StaticMeshVertexColorFeedback.label(text=my_text, icon='INFO') + else: + my_text = 'No vertex color found at this index.' + StaticMeshVertexColorFeedback.label(text=my_text, icon='ERROR') + else: + my_text = 'Vertex color property will be apply on the childrens.' + StaticMeshVertexColorFeedback.label(text=my_text, icon='INFO') + + bfu_ui_utils.LayoutSection(layout, "bfu_object_light_map_properties_expanded", "Light map") + if scene.bfu_object_light_map_properties_expanded: + if addon_prefs.useGeneratedScripts and obj is not None: + if obj.ExportEnum == "export_recursive": + + # Light map + if GetAssetType(obj) == "StaticMesh": + StaticMeshLightMapRes = layout.box() + StaticMeshLightMapRes.prop(obj, 'StaticMeshLightMapEnum') + if obj.StaticMeshLightMapEnum == "CustomMap": + CustomLightMap = StaticMeshLightMapRes.column() + CustomLightMap.prop(obj, 'customStaticMeshLightMapRes') + if obj.StaticMeshLightMapEnum == "SurfaceArea": + SurfaceAreaLightMap = StaticMeshLightMapRes.column() + SurfaceAreaLightMapButton = SurfaceAreaLightMap.row() + SurfaceAreaLightMapButton.operator("object.computlightmap", icon='TEXTURE') + SurfaceAreaLightMapButton.operator("object.computalllightmap", icon='TEXTURE') + SurfaceAreaLightMap.prop(obj, 'useStaticMeshLightMapWorldScale') + SurfaceAreaLightMap.prop(obj, 'staticMeshLightMapSurfaceScale') + SurfaceAreaLightMap.prop(obj, 'staticMeshLightMapRoundPowerOfTwo') + if obj.StaticMeshLightMapEnum != "Default": + CompuntedLightMap = str(GetCompuntedLightMap(obj)) + StaticMeshLightMapRes.label(text='Compunted light map: ' + CompuntedLightMap) + GenerateLightmapUVs = layout.row() + GenerateLightmapUVs.prop(obj, 'GenerateLightmapUVs') + + bfu_ui_utils.LayoutSection(layout, "bfu_object_uv_map_properties_expanded", "UV map") + if scene.bfu_object_uv_map_properties_expanded: + if obj.ExportEnum == "export_recursive": + # Geometry Node Uv + convert_geometry_node_attribute_to_uv = layout.column() + convert_geometry_node_attribute_to_uv_use = convert_geometry_node_attribute_to_uv.row() + convert_geometry_node_attribute_to_uv_use.prop(obj, 'convert_geometry_node_attribute_to_uv') + bfu_ui_utils.DocPageButton(convert_geometry_node_attribute_to_uv_use, "wiki/UV-Maps", "Geometry Node UV") + convert_geometry_node_attribute_to_uv_name = convert_geometry_node_attribute_to_uv.row() + convert_geometry_node_attribute_to_uv_name.prop(obj, 'convert_geometry_node_attribute_to_uv_name') + convert_geometry_node_attribute_to_uv_name.enabled = obj.convert_geometry_node_attribute_to_uv + + # Extreme UV Scale + correct_extrem_uv_scale = layout.row() + correct_extrem_uv_scale.prop(obj, 'correct_extrem_uv_scale') + bfu_ui_utils.DocPageButton(correct_extrem_uv_scale, "wiki/UV-Maps", "Extreme UV Scale") + + if bfu_ui_utils.DisplayPropertyFilter("SCENE", "GENERAL"): + bfu_ui_utils.LayoutSection(layout, "bfu_collection_properties_expanded", "Collection Properties") + if scene.bfu_collection_properties_expanded: + collectionListProperty = layout.column() + collectionListProperty.template_list( + # type and unique id + "BFU_UL_CollectionExportTarget", "", + # pointer to the CollectionProperty + scene, "CollectionExportList", + # pointer to the active identifier + scene, "active_CollectionExportList", + maxrows=5, + rows=5 + ) + collectionListProperty.operator( + "object.updatecollectionlist", + icon='RECOVER_LAST') + + if scene.active_CollectionExportList < len(scene.CollectionExportList): + col_name = scene.CollectionExportList[scene.active_CollectionExportList].name + if col_name in bpy.data.collections: + col = bpy.data.collections[col_name] + col_prop = layout + col_prop.prop(col, 'exportFolderName', icon='FILE_FOLDER') + + collectionPropertyInfo = layout.row().box().split(factor=0.75) + collectionNum = len(GetCollectionToExport(scene)) + collectionFeedback = ( + str(collectionNum) + + " Collection(s) will be exported.") + collectionPropertyInfo.label(text=collectionFeedback, icon='INFO') + collectionPropertyInfo.operator("object.showscenecollection") + layout.label(text='Note: The collection are exported like StaticMesh.') + + +class BFU_PT_BlenderForUnrealTool(bpy.types.Panel): + # Tool panel with Collisions And Sockets + + bl_idname = "BFU_PT_BlenderForUnrealTool" + bl_label = "Tool" + bl_space_type = "VIEW_3D" + bl_region_type = "UI" + bl_category = "Unreal Engine" + + bpy.types.Object.bfu_use_socket_custom_Name = BoolProperty( + name="Socket custom name", + description='Use a custom name in Unreal Engine for this socket?', + default=False + ) + + bpy.types.Object.bfu_socket_custom_Name = StringProperty( + name="", + description='', + default="MySocket" + ) + + class BFU_OT_CopyRegularCamerasButton(Operator): + bl_label = "Copy Regular Cameras for Unreal" + bl_idname = "object.copy_regular_cameras_command" + bl_description = "Copy Regular Cameras Script command" + + def execute(self, context): + objs = context.selected_objects + result = GetImportCameraScriptCommand(objs, False) + if result[0]: + setWindowsClipboard(result[1]) + self.report({'INFO'}, result[2]) + else: + self.report({'WARNING'}, result[2]) + return {'FINISHED'} + + class BFU_OT_CopyCineCamerasButton(Operator): + bl_label = "Copy Cine Cameras for Unreal" + bl_idname = "object.copy_cine_cameras_command" + bl_description = "Copy Cine Cameras Script command" + + def execute(self, context): + objs = context.selected_objects + result = GetImportCameraScriptCommand(objs, True) + if result[0]: + setWindowsClipboard(result[1]) + self.report({'INFO'}, result[2]) + else: + self.report({'WARNING'}, result[2]) + return {'FINISHED'} + + 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 UE4 Socket. (Static)") + 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("SKM_Socket") + if len(ConvertedObj) > 0: + self.report( + {'INFO'}, + str(len(ConvertedObj)) + + " object(s) of the selection have" + + " be converted to UE4 Socket. (Skeletal)") + else: + self.report( + {'WARNING'}, + "Please select two objects. " + + "(Active object is the owner of the socket)") + return {'FINISHED'} + + class BFU_OT_CopySkeletalSocketButton(Operator): + bl_label = "Copy Skeletal Mesh socket for Unreal" + bl_idname = "object.copy_skeletalsocket_command" + bl_description = "Copy Skeletal Socket Script command" + + def execute(self, context): + scene = context.scene + obj = context.object + if obj: + if obj.type == "ARMATURE": + setWindowsClipboard(GetImportSkeletalMeshSocketScriptCommand(obj)) + self.report( + {'INFO'}, + "Skeletal sockets copied. Paste in Unreal Engine Skeletal Mesh assets for import sockets. (Ctrl+V)") + return {'FINISHED'} + + class BFU_OT_ComputAllLightMap(Operator): + bl_label = "Calculate all surface area" + bl_idname = "object.computalllightmap" + bl_description = ( + "Click to calculate the surface of the all object in the scene" + ) + + def execute(self, context): + updated = UpdateAreaLightMapList() + self.report( + {'INFO'}, + "The light maps of " + str(updated) + + " object(s) have been updated." + ) + return {'FINISHED'} + + def draw(self, context): + + addon_prefs = GetAddonPrefs() + layout = self.layout + scene = bpy.context.scene + obj = context.object + + 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 ActiveTypeIsNot(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, include_active=True): + # Return True if a specific type is found + select = bpy.context.selected_objects + if not include_active: + if bpy.context.active_object: + if bpy.context.active_object in select: + select.remove(bpy.context.active_object) + + for obj in select: + if obj.type == targetType: + return True + return False + + ready_for_convert_collider = False + ready_for_convert_socket = False + + ''' + bfu_ui_utils.LayoutSection(layout, "bfu_export_type_expanded", "Export type") + if scene.bfu_export_type_expanded: + if not ActiveModeIs("OBJECT"): + layout.label(text="Switch to Object Mode.", icon='INFO') + else: + export_type_buttons = layout.row().split(factor=0.80) + export_type_cameras = export_type_buttons.column() + export_type_cameras.enabled = True + export_type_cameras.operator("object.converttoboxcollision", icon='MESH_CUBE') + ''' + + bfu_ui_utils.LayoutSection(layout, "bfu_camera_expanded", "Camera") + if scene.bfu_camera_expanded: + copy_camera_buttons = layout.column() + copy_camera_buttons.operator("object.copy_regular_cameras_command", icon="COPYDOWN") + copy_camera_buttons.operator("object.copy_cine_cameras_command", icon="COPYDOWN") + + bfu_ui_utils.LayoutSection(layout, "bfu_collision_socket_expanded", "Collision and Socket") + if scene.bfu_collision_socket_expanded: + if not ActiveModeIs("OBJECT"): + layout.label(text="Switch to Object Mode.", icon='INFO') + else: + if FoundTypeInSelect("MESH", False): + if ActiveTypeIsNot("ARMATURE") and len(bpy.context.selected_objects) > 1: + layout.label(text="Click on button for convert to collider.", icon='INFO') + ready_for_convert_collider = True + else: + layout.label(text="Select with [SHIFT] the collider owner.", icon='INFO') + + elif FoundTypeInSelect("EMPTY", False): + if ActiveTypeIsNot("ARMATURE") and len(bpy.context.selected_objects) > 1: + layout.label(text="Click on button for convert to Socket.", icon='INFO') + ready_for_convert_socket = True + else: + layout.label(text="Select with [SHIFT] the socket owner.", icon='INFO') + else: + layout.label(text="Select your collider Object(s) or socket Empty(s).", icon='INFO') + + convertButtons = layout.row().split(factor=0.80) + convertStaticCollisionButtons = convertButtons.column() + convertStaticCollisionButtons.enabled = ready_for_convert_collider + 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 = layout.row().split(factor=0.80) + convertStaticSocketButtons = convertButtons.column() + convertStaticSocketButtons.enabled = ready_for_convert_socket + convertStaticSocketButtons.operator( + "object.converttostaticsocket", + icon='OUTLINER_DATA_EMPTY') + + if addon_prefs.useGeneratedScripts: + + ready_for_convert_skeletal_socket = False + if not ActiveModeIs("OBJECT"): + if not ActiveTypeIs("ARMATURE"): + if not FoundTypeInSelect("EMPTY"): + layout.label(text="Switch to Object Mode.", icon='INFO') + else: + if FoundTypeInSelect("EMPTY"): + if ActiveTypeIs("ARMATURE") and len(bpy.context.selected_objects) > 1: + layout.label(text="Switch to Pose Mode.", icon='INFO') + else: + layout.label(text="Select with [SHIFT] the socket owner. (Armature)", icon='INFO') + else: + layout.label(text="Select your socket Empty(s).", icon='INFO') + + if ActiveModeIs("POSE") and ActiveTypeIs("ARMATURE") and FoundTypeInSelect("EMPTY"): + if len(bpy.context.selected_pose_bones) > 0: + layout.label(text="Click on button for convert to Socket.", icon='INFO') + ready_for_convert_skeletal_socket = True + else: + layout.label(text="Select the owner bone.", icon='INFO') + + convertButtons = self.layout.row().split(factor=0.80) + convertSkeletalSocketButtons = convertButtons.column() + convertSkeletalSocketButtons.enabled = ready_for_convert_skeletal_socket + convertSkeletalSocketButtons.operator( + "object.converttoskeletalsocket", + icon='OUTLINER_DATA_EMPTY') + + if obj is not None: + if obj.type == "EMPTY": + socketName = layout.column() + socketName.prop(obj, "bfu_use_socket_custom_Name") + socketNameText = socketName.column() + socketNameText.enabled = obj.bfu_use_socket_custom_Name + socketNameText.prop(obj, "bfu_socket_custom_Name") + + copy_skeletalsocket_buttons = layout.column() + copy_skeletalsocket_buttons.enabled = False + copy_skeletalsocket_buttons.operator( + "object.copy_skeletalsocket_command", + icon='COPYDOWN') + if obj is not None: + if obj.type == "ARMATURE": + copy_skeletalsocket_buttons.enabled = True + + bfu_ui_utils.LayoutSection(layout, "bfu_lightmap_expanded", "Light Map") + if scene.bfu_lightmap_expanded: + checkButton = layout.column() + checkButton.operator("object.computalllightmap", icon='TEXTURE') + + +class BFU_PT_BlenderForUnrealDebug(bpy.types.Panel): + # Debug panel for get dev info and test + + bl_idname = "BFU_PT_BlenderForUnrealDebug" + bl_label = "Debug" + bl_space_type = "VIEW_3D" + bl_region_type = "UI" + bl_category = "Unreal Engine" + + bpy.types.Object.bfu_use_socket_custom_Name = BoolProperty( + name="Socket custom name", + description='Use a custom name in Unreal Engine for this socket?', + default=False + ) + + bpy.types.Object.bfu_socket_custom_Name = StringProperty( + name="", + description='', + default="MySocket" + ) + + def draw(self, context): + addon_prefs = GetAddonPrefs() + layout = self.layout + scene = bpy.context.scene + obj = context.object + layout.label(text="This panel is only for Debug", icon='INFO') + if obj: + layout.label(text="Full path name as Static Mesh:") + layout.label(text="GetObjExportDir(local):" + GetObjExportDir(obj, False)) + layout.label(text="GetObjExportDir:" + GetObjExportDir(obj, True)) + layout.label(text="GetObjExportName:" + GetObjExportName(obj)) + layout.label(text="GetObjExportFileName:" + GetObjExportFileName(obj)) + if obj.type == "CAMERA": + layout.label(text="CameraPositionForUnreal (Loc):" + str(EvaluateCameraPositionForUnreal(obj)[0])) + layout.label(text="CameraPositionForUnreal (Rot):" + str(EvaluateCameraPositionForUnreal(obj)[1])) + layout.label(text="CameraPositionForUnreal (Scale):" + str(EvaluateCameraPositionForUnreal(obj)[2])) + + +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" + + # Prefix + bpy.types.Scene.static_mesh_prefix_export_name = bpy.props.StringProperty( + name="StaticMesh Prefix", + description="Prefix of staticMesh", + maxlen=32, + default="SM_") + + bpy.types.Scene.skeletal_mesh_prefix_export_name = bpy.props.StringProperty( + name="SkeletalMesh Prefix ", + description="Prefix of SkeletalMesh", + maxlen=32, + default="SKM_") + + bpy.types.Scene.skeleton_prefix_export_name = bpy.props.StringProperty( + name="skeleton Prefix ", + description="Prefix of skeleton", + 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="SKM_") + + 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=( + "The name of sub folder for animations New." + + " You can now use ../ for up one directory."), + maxlen=512, + 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") + + bpy.types.Scene.unreal_import_module = bpy.props.StringProperty( + name="Unreal import module", + description="Which module (plugin name) to import to. Default is 'Game', meaning it will be put into your project's /Content/ folder. If you wish to import to a plugin (for example a plugin called 'myPlugin'), just write its name here", + maxlen=512, + default='Game') + + bpy.types.Scene.unreal_import_location = bpy.props.StringProperty( + name="Unreal import location", + description="Unreal assets import location inside the module", + maxlen=512, + default='ImportedFbx') + + 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_mesh_prefix_export_name', + 'scene.skeletal_mesh_prefix_export_name', + 'scene.skeleton_prefix_export_name', + 'scene.alembic_prefix_export_name', + 'scene.anim_prefix_export_name', + 'scene.pose_prefix_export_name', + 'scene.camera_prefix_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', + # Import location: + 'scene.unreal_import_module', + 'scene.unreal_import_location', + ] + + # Directory to store the presets + preset_subdir = 'blender-for-unrealengine/nomenclature-presets' + + 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 + if obj: + if obj.type == "ARMATURE": + UpdateActionCache(obj) + + 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.bfu_anim_nla_export_name + 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" + text = "none" + + def execute(self, context): + correctedProperty = bfu_check_potential_error.CorrectBadProperty() + bfu_check_potential_error.UpdateNameHierarchy() + bfu_check_potential_error.UpdateUnrealPotentialError() + bpy.ops.object.openpotentialerror("INVOKE_DEFAULT", correctedProperty=correctedProperty) + return {'FINISHED'} + + class BFU_OT_OpenPotentialErrorPopup(Operator): + bl_label = "Open potential errors" + bl_idname = "object.openpotentialerror" + bl_description = "Open potential errors" + correctedProperty: bpy.props.IntProperty(default=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 = bfu_check_potential_error.TryToCorrectPotentialError(self.errorIndex) + self.report({'INFO'}, result) + return {'FINISHED'} + + class BFU_OT_SelectObjectButton(Operator): + bl_label = "Select(Object)" + bl_idname = "object.select_error_objet" + bl_description = "Select target Object." + errorIndex: bpy.props.IntProperty(default=-1) + + def execute(self, context): + result = bfu_check_potential_error.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 = bfu_check_potential_error.SelectPotentialErrorVertex(self.errorIndex) + return {'FINISHED'} + + class BFU_OT_SelectPoseBoneButton(Operator): + bl_label = "Select(PoseBone)" + bl_idname = "object.select_error_posebone" + bl_description = "Select target Pose Bone." + errorIndex: bpy.props.IntProperty(default=-1) + + def execute(self, context): + result = bfu_check_potential_error.SelectPotentialErrorPoseBone(self.errorIndex) + return {'FINISHED'} + + class BFU_OT_OpenPotentialErrorDocs(Operator): + bl_label = "Open docs" + bl_idname = "object.open_potential_error_docs" + bl_description = "Open potential error docs." + octicon: StringProperty(default="") + + def execute(self, context): + os.system( + "start \"\" " + + "https://github.com/xavier150/Blender-For-UnrealEngine-Addons/wiki/How-avoid-potential-errors" + + "#"+self.octicon) + return {'FINISHED'} + + def execute(self, context): + return {'FINISHED'} + + def invoke(self, context, event): + 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: + potentialErrorInfo = ( + str(self.correctedProperty) + + "- properties corrected.") + else: + potentialErrorInfo = "- No properties to correct." + + layout.label(text=popup_title) + layout.label(text="- Hierarchy names updated") + layout.label(text=potentialErrorInfo) + 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' + # ---- + + # Text + TextLine = myLine.column() + errorFullMsg = msgType+": "+error.text + splitedText = errorFullMsg.split("\n") + + for text, Line in enumerate(splitedText): + if (text < 1): + + FisrtTextLine = TextLine.row() + if (error.docsOcticon != "None"): # Doc button + props = FisrtTextLine.operator( + "object.open_potential_error_docs", + icon="HELP", + text="") + props.octicon = error.docsOcticon + + FisrtTextLine.label(text=Line, icon=msgIcon) + else: + TextLine.label(text=Line) + + # Select and fix button + 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): + if (error.selectObjectButton): + props = ButtonLine.operator( + "object.select_error_objet") + props.errorIndex = x + if (error.selectVertexButton): + props = ButtonLine.operator( + "object.select_error_vertex") + props.errorIndex = x + if (error.selectPoseBoneButton): + props = ButtonLine.operator( + "object.select_error_posebone") + props.errorIndex = x + + class BFU_OT_ExportForUnrealEngineButton(Operator): + bl_label = "Export for Unreal Engine" + bl_idname = "object.exportforunreal" + bl_description = "Export all assets of this scene." + + def execute(self, context): + scene = bpy.context.scene + + def isReadyForExport(): + + 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 not CheckPluginIsActivated("io_scene_fbx"): + self.report( + {'WARNING'}, + 'Add-on FBX format is not activated!' + + ' Edit > Preferences > Add-ons > And check "FBX format"') + return False + + if not GetIfOneTypeCheck(): + self.report( + {'WARNING'}, + "No asset type is checked.") + return False + + if not len(GetFinalAssetToExport()) > 0: + self.report( + {'WARNING'}, + "Not found assets with" + + " \"Export recursive\" properties " + + "or collection to export.") + return False + + if not bpy.data.is_saved: + # Primary check if file is saved + # to avoid windows PermissionError + self.report( + {'WARNING'}, + "Please save this .blend file before export.") + return False + + if bbpl.basics.IsTweakmode(): + # Need exit Tweakmode because the Animation data is read only. + self.report( + {'WARNING'}, + "Exit Tweakmode in NLA Editor. [Tab]") + return False + + return True + + if not isReadyForExport(): + return {'FINISHED'} + + scene.UnrealExportedAssetsList.clear() + counter = CounterTimer() + bfu_check_potential_error.UpdateNameHierarchy() + bfu_export_asset.ExportForUnrealEngine() + bfu_write_text.WriteAllTextFiles() + + self.report( + {'INFO'}, + "Export of " + + str(len(scene.UnrealExportedAssetsList)) + + " asset(s) has been finalized in " + + str(round(counter.GetTime(), 2)) + + "seconds. Look in console for more info.") + print( + "=========================" + + " Exported asset(s) " + + "=========================") + print("") + lines = bfu_write_text.WriteExportLog().splitlines() + for line in lines: + print(line) + print("") + print( + "=========================" + + " ... " + + "=========================") + + return {'FINISHED'} + + class BFU_OT_CopyImportAssetScriptCommand(Operator): + bl_label = "Copy import script (Assets)" + bl_idname = "object.copy_importassetscript_command" + bl_description = "Copy Import Asset Script command" + + def execute(self, context): + scene = context.scene + setWindowsClipboard(GetImportAssetScriptCommand()) + self.report( + {'INFO'}, + "command for "+scene.file_import_asset_script_name + + " copied") + return {'FINISHED'} + + class BFU_OT_CopyImportSequencerScriptCommand(Operator): + bl_label = "Copy import script (Sequencer)" + bl_idname = "object.copy_importsequencerscript_command" + bl_description = "Copy Import Sequencer Script command" + + def execute(self, context): + scene = context.scene + setWindowsClipboard(GetImportSequencerScriptCommand()) + self.report( + {'INFO'}, + "command for "+scene.file_import_sequencer_script_name + + " copied") + 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=True + ) + + 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=True + ) + + bpy.types.Scene.camera_export = bpy.props.BoolProperty( + name="Camera(s)", + description="Check mark to export Camera(s)", + default=True + ) + + # 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.bfu_export_selection_filter = bpy.props.EnumProperty( + name="Selection filter", + items=[ + ('default', "No Filter", "Export as normal all objects with the recursive export option.", 0), + ('only_object', "Only selected", "Export only the selected object(s)", 1), + ('only_object_action', "Only selected and active action", + "Export only the selected object(s) and active action on this object", 2), + ], + description=( + "Choose what need be export from asset list."), + default="default" + ) + + def draw(self, context): + scene = context.scene + scene = context.scene + addon_prefs = GetAddonPrefs() + + # Categories : + layout = self.layout + + # Presets + row = self.layout.row(align=True) + row.menu('BFU_MT_NomenclaturePresets', text='Export Presets') + row.operator('object.add_nomenclature_preset', text='', icon='ADD') + row.operator( + 'object.add_nomenclature_preset', + text='', + icon='REMOVE').remove_active = True + + bfu_ui_utils.LayoutSection(layout, "bfu_nomenclature_properties_expanded", "Nomenclature") + if scene.bfu_nomenclature_properties_expanded: + + # Prefix + propsPrefix = self.layout.row() + propsPrefix = propsPrefix.column() + propsPrefix.prop(scene, 'static_mesh_prefix_export_name', icon='OBJECT_DATA') + propsPrefix.prop(scene, 'skeletal_mesh_prefix_export_name', icon='OBJECT_DATA') + propsPrefix.prop(scene, 'skeleton_prefix_export_name', icon='OBJECT_DATA') + propsPrefix.prop(scene, 'alembic_prefix_export_name', icon='OBJECT_DATA') + propsPrefix.prop(scene, 'anim_prefix_export_name', icon='OBJECT_DATA') + propsPrefix.prop(scene, 'pose_prefix_export_name', icon='OBJECT_DATA') + propsPrefix.prop(scene, 'camera_prefix_export_name', icon='OBJECT_DATA') + + # Sub folder + propsSub = self.layout.row() + propsSub = propsSub.column() + propsSub.prop(scene, 'anim_subfolder_name', icon='FILE_FOLDER') + + if addon_prefs.useGeneratedScripts: + unreal_import_module = propsSub.column() + unreal_import_module.prop( + scene, + 'unreal_import_module', + icon='FILE_FOLDER') + unreal_import_location = propsSub.column() + unreal_import_location.prop( + scene, + 'unreal_import_location', + icon='FILE_FOLDER') + + # File path + filePath = self.layout.row() + filePath = filePath.column() + filePath.prop(scene, 'export_static_file_path') + filePath.prop(scene, 'export_skeletal_file_path') + filePath.prop(scene, 'export_alembic_file_path') + filePath.prop(scene, 'export_camera_file_path') + filePath.prop(scene, 'export_other_file_path') + + # File name + fileName = self.layout.row() + fileName = fileName.column() + fileName.prop(scene, 'file_export_log_name', icon='FILE') + if addon_prefs.useGeneratedScripts: + fileName.prop( + scene, + 'file_import_asset_script_name', + icon='FILE') + fileName.prop( + scene, + 'file_import_sequencer_script_name', + icon='FILE') + + bfu_ui_utils.LayoutSection(layout, "bfu_export_filter_properties_expanded", "Export filters") + if scene.bfu_export_filter_properties_expanded: + + # Assets + row = layout.row() + col = row.column() + AssetsCol = row.column() + AssetsCol.label(text="Asset types to export", icon='PACKAGE') + AssetsCol.prop(scene, 'static_export') + AssetsCol.prop(scene, 'static_collection_export') + AssetsCol.prop(scene, 'skeletal_export') + AssetsCol.prop(scene, 'anin_export') + AssetsCol.prop(scene, 'alembic_export') + AssetsCol.prop(scene, 'camera_export') + layout.separator() + + # Additional file + FileCol = row.column() + FileCol.label(text="Additional file", icon='PACKAGE') + FileCol.prop(scene, 'text_ExportLog') + FileCol.prop(scene, 'text_ImportAssetScript') + FileCol.prop(scene, 'text_ImportSequenceScript') + if addon_prefs.useGeneratedScripts: + FileCol.prop(scene, 'text_AdditionalData') + + # exportProperty + export_by_select = layout.row() + export_by_select.prop(scene, 'bfu_export_selection_filter') + + bfu_ui_utils.LayoutSection(layout, "bfu_export_process_properties_expanded", "Export process") + if scene.bfu_export_process_properties_expanded: + + # 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(align=True) + checkButton.operator("object.checkpotentialerror", icon='FILE_TICK') + checkButton.operator("object.openpotentialerror", icon='LOOP_BACK', text="") + + exportButton = layout.row() + exportButton.scale_y = 2.0 + exportButton.operator("object.exportforunreal", icon='EXPORT') + + bfu_ui_utils.LayoutSection(layout, "bfu_script_tool_expanded", "Copy Import Script") + if scene.bfu_script_tool_expanded: + if addon_prefs.useGeneratedScripts: + copyButton = layout.row() + copyButton.operator("object.copy_importassetscript_command") + copyButton.operator("object.copy_importsequencerscript_command") + layout.label(text="Click on one of the buttons to copy the import command.", icon='INFO') + layout.label(text="Then paste it into the cmd console of unreal.") + layout.label(text="You need activate python plugins in Unreal Engine.") + + 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_BlenderForUnrealObject" + + 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_PT_BlenderForUnrealObject, + BFU_PT_BlenderForUnrealObject.BFU_MT_ObjectGlobalPropertiesPresets, + BFU_PT_BlenderForUnrealObject.BFU_OT_AddObjectGlobalPropertiesPreset, + BFU_PT_BlenderForUnrealObject.BFU_OT_OpenDocumentationPage, + BFU_PT_BlenderForUnrealObject.BFU_OT_CopyRegularCameraButton, + BFU_PT_BlenderForUnrealObject.BFU_OT_CopyCineCameraButton, + BFU_PT_BlenderForUnrealObject.BFU_OT_ComputLightMap, + BFU_PT_BlenderForUnrealObject.BFU_UL_ActionExportTarget, + BFU_PT_BlenderForUnrealObject.BFU_OT_UpdateObjActionListButton, + BFU_PT_BlenderForUnrealObject.BFU_OT_ShowActionToExport, + BFU_PT_BlenderForUnrealObject.BFU_UL_CollectionExportTarget, + BFU_PT_BlenderForUnrealObject.BFU_OT_UpdateCollectionButton, + BFU_PT_BlenderForUnrealObject.BFU_OT_ShowCollectionToExport, + + BFU_PT_BlenderForUnrealTool, + BFU_PT_BlenderForUnrealTool.BFU_OT_CopyRegularCamerasButton, + BFU_PT_BlenderForUnrealTool.BFU_OT_CopyCineCamerasButton, + BFU_PT_BlenderForUnrealTool.BFU_OT_ConvertToCollisionButtonBox, + BFU_PT_BlenderForUnrealTool.BFU_OT_ConvertToCollisionButtonCapsule, + BFU_PT_BlenderForUnrealTool.BFU_OT_ConvertToCollisionButtonSphere, + BFU_PT_BlenderForUnrealTool.BFU_OT_ConvertToCollisionButtonConvex, + BFU_PT_BlenderForUnrealTool.BFU_OT_ConvertToStaticSocketButton, + BFU_PT_BlenderForUnrealTool.BFU_OT_ConvertToSkeletalSocketButton, + BFU_PT_BlenderForUnrealTool.BFU_OT_CopySkeletalSocketButton, + BFU_PT_BlenderForUnrealTool.BFU_OT_ComputAllLightMap, + + # BFU_PT_BlenderForUnrealDebug, # Unhide for dev + + BFU_PT_Export, + BFU_PT_Export.BFU_MT_NomenclaturePresets, + BFU_PT_Export.BFU_OT_AddNomenclaturePreset, + BFU_PT_Export.BFU_OT_ShowAssetToExport, + BFU_PT_Export.BFU_OT_CheckPotentialErrorPopup, + BFU_PT_Export.BFU_OT_OpenPotentialErrorPopup, + BFU_PT_Export.BFU_OT_OpenPotentialErrorPopup.BFU_OT_FixitTarget, + BFU_PT_Export.BFU_OT_OpenPotentialErrorPopup.BFU_OT_SelectObjectButton, + BFU_PT_Export.BFU_OT_OpenPotentialErrorPopup.BFU_OT_SelectVertexButton, + BFU_PT_Export.BFU_OT_OpenPotentialErrorPopup.BFU_OT_SelectPoseBoneButton, + BFU_PT_Export.BFU_OT_OpenPotentialErrorPopup.BFU_OT_OpenPotentialErrorDocs, + BFU_PT_Export.BFU_OT_ExportForUnrealEngineButton, + BFU_PT_Export.BFU_OT_CopyImportAssetScriptCommand, + BFU_PT_Export.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, + options={'LIBRARY_EDITABLE'}, + override={'LIBRARY_OVERRIDABLE', 'USE_INSERTION'}, + ) + bpy.utils.register_class(BFU_OT_SceneCollectionExport) + bpy.types.Scene.CollectionExportList = CollectionProperty( + type=BFU_OT_SceneCollectionExport, + options={'LIBRARY_EDITABLE'}, + override={'LIBRARY_OVERRIDABLE', 'USE_INSERTION'}, + ) + + 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.types.VIEW3D_MT_uv_map.remove(menu_func) From 24df4a7a24fc1da3db987331dcab464ed069bfe9 Mon Sep 17 00:00:00 2001 From: BleuRaven Date: Wed, 5 Apr 2023 13:25:30 +0200 Subject: [PATCH 4/8] Bad fix in ConvertGeometryNodeAttributeToUV --- blender-for-unrealengine/export/bfu_export_utils.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/blender-for-unrealengine/export/bfu_export_utils.py b/blender-for-unrealengine/export/bfu_export_utils.py index 9814f57a..9fd56caf 100644 --- a/blender-for-unrealengine/export/bfu_export_utils.py +++ b/blender-for-unrealengine/export/bfu_export_utils.py @@ -388,7 +388,9 @@ def ConvertGeometryNodeAttributeToUV(obj): SavedSelect = GetCurrentSelection() SelectSpecificObject(obj) if bpy.app.version >= (3, 5, 0): - bpy.ops.geometry.attribute_convert(mode='VERTEX_GROUP') + pass + # UV_MAP mode removed ? + # bpy.ops.geometry.attribute_convert(mode='UV_MAP') else: bpy.ops.geometry.attribute_convert(mode='UV_MAP') SetCurrentSelection(SavedSelect) From 9b59a301ab5fac1b6eded37041276928c387c3c7 Mon Sep 17 00:00:00 2001 From: BleuRaven Date: Wed, 5 Apr 2023 14:49:27 +0200 Subject: [PATCH 5/8] - Fixed: ConvertGeometryNodeAttributeToUV fail after Blender 3.5 --- .../export/bfu_export_utils.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/blender-for-unrealengine/export/bfu_export_utils.py b/blender-for-unrealengine/export/bfu_export_utils.py index 9fd56caf..152850d3 100644 --- a/blender-for-unrealengine/export/bfu_export_utils.py +++ b/blender-for-unrealengine/export/bfu_export_utils.py @@ -383,16 +383,23 @@ def ConvertGeometryNodeAttributeToUV(obj): if hasattr(obj.data, "attributes"): # Cuves has not attributes. if attrib_name in obj.data.attributes: - # TO DO: Bad why to do this. Need found a way to convert without using ops. - obj.data.attributes.active = obj.data.attributes[attrib_name] + # TO DO: Bad why to do this. Need found a way to convert without using ops. + obj.data.attributes.active = obj.data.attributes[attrib_name] + + # Because a bug Blender set the wrong attribute as active in 3.5. + if obj.data.attributes.active != obj.data.attributes[attrib_name]: + for x, attribute in enumerate(obj.data.attributes): + if attribute.name == attrib_name: + obj.data.attributes.active_index = x + SavedSelect = GetCurrentSelection() SelectSpecificObject(obj) if bpy.app.version >= (3, 5, 0): - pass - # UV_MAP mode removed ? - # bpy.ops.geometry.attribute_convert(mode='UV_MAP') + if obj.data.attributes.active: + bpy.ops.geometry.attribute_convert(mode='GENERIC', domain='CORNER', data_type='FLOAT2') else: - bpy.ops.geometry.attribute_convert(mode='UV_MAP') + if obj.data.attributes.active: + bpy.ops.geometry.attribute_convert(mode='UV_MAP', domain='CORNER', data_type='FLOAT2') SetCurrentSelection(SavedSelect) return From ecaf245a139fc0addc4d8b18bf706ecc5aea2047 Mon Sep 17 00:00:00 2001 From: BleuRaven Date: Wed, 5 Apr 2023 15:03:49 +0200 Subject: [PATCH 6/8] - Fixed: Geometry To UV, Correct Extrem UV, Vertex Color, Sockets Transform and Name don't apply on child objects. --- Release log.txt | 3 +- .../export/bfu_export_single_skeletal_mesh.py | 412 +++++++++--------- .../export/bfu_export_single_static_mesh.py | 319 +++++++------- 3 files changed, 370 insertions(+), 364 deletions(-) diff --git a/Release log.txt b/Release log.txt index 17ad5a4f..268160c1 100644 --- a/Release log.txt +++ b/Release log.txt @@ -449,4 +449,5 @@ It is now possible to force the duration of an animation according to the scene - UI updated - Update Export Button Name - New options for set start/end animation time with NLA. -- Fixed: ConvertGeometryNodeAttributeToUV fail after Blender 3.5 \ No newline at end of file +- Fixed: ConvertGeometryNodeAttributeToUV fail after Blender 3.5 +- Fixed: Geometry To UV, Correct Extrem UV, Vertex Color, Sockets Transform and Name don't apply on child objects. \ No newline at end of file diff --git a/blender-for-unrealengine/export/bfu_export_single_skeletal_mesh.py b/blender-for-unrealengine/export/bfu_export_single_skeletal_mesh.py index f74b1c0a..0c533376 100644 --- a/blender-for-unrealengine/export/bfu_export_single_skeletal_mesh.py +++ b/blender-for-unrealengine/export/bfu_export_single_skeletal_mesh.py @@ -1,206 +1,208 @@ -# ====================== 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 -import os - -if "bpy" in locals(): - import importlib - if "bfu_write_text" in locals(): - importlib.reload(bfu_write_text) - if "bfu_basics" in locals(): - importlib.reload(bfu_basics) - if "bfu_utils" in locals(): - importlib.reload(bfu_utils) - if "bfu_check_potential_error" in locals(): - importlib.reload(bfu_check_potential_error) - if "bfu_export_utils" in locals(): - importlib.reload(bfu_export_utils) - - -from .. import bfu_write_text -from .. import bfu_basics -from ..bfu_basics import * -from .. import bfu_utils -from ..bfu_utils import * -from .. import bfu_check_potential_error - -from . import bfu_export_utils -from .bfu_export_utils import * - - -def ProcessSkeletalMeshExport(obj): - addon_prefs = GetAddonPrefs() - dirpath = GetObjExportDir(obj) - absdirpath = bpy.path.abspath(dirpath) - scene = bpy.context.scene - - MyAsset = scene.UnrealExportedAssetsList.add() - MyAsset.object = obj - MyAsset.skeleton_name = obj.name - MyAsset.asset_name = obj.name - MyAsset.folder_name = obj.exportFolderName - MyAsset.asset_type = bfu_utils.GetAssetType(obj) - MyAsset.StartAssetExport() - - ExportSingleSkeletalMesh(scene, dirpath, GetObjExportFileName(obj), obj) - file = MyAsset.files.add() - file.name = GetObjExportFileName(obj) - file.path = dirpath - file.type = "FBX" - - if not obj.ExportAsLod: - if (scene.text_AdditionalData and addon_prefs.useGeneratedScripts): - ExportAdditionalParameter(absdirpath, MyAsset) - file = MyAsset.files.add() - file.name = GetObjExportFileName(obj, "_AdditionalTrack.json") - file.path = dirpath - file.type = "AdditionalTrack" - - MyAsset.EndAssetExport(True) - return MyAsset - - -def ExportSingleSkeletalMesh( - originalScene, - dirpath, - filename, - obj - ): - - ''' - ##################################################### - #SKELETAL MESH - ##################################################### - ''' - # Export a single Mesh - - scene = bpy.context.scene - addon_prefs = GetAddonPrefs() - export_as_proxy = GetExportAsProxy(obj) - export_proxy_child = GetExportProxyChild(obj) - - bbpl.utils.SafeModeSet('OBJECT') - - SelectParentAndDesiredChilds(obj) - asset_name = PrepareExportName(obj, True) - duplicate_data = DuplicateSelectForExport() - SetDuplicateNameForExport(duplicate_data) - - ApplyNeededModifierToSelect() - - active = bpy.context.view_layer.objects.active - asset_name.target_object = active - - ConvertGeometryNodeAttributeToUV(active) - CorrectExtremUVAtExport(active) - - export_procedure = active.bfu_export_procedure - - if export_as_proxy: - ApplyProxyData(active) - - ApplyExportTransform(active, "Object") # Apply export transform before rescale - - # This will rescale the rig and unit scale to get a root bone egal to 1 - ShouldRescaleRig = GetShouldRescaleRig(active) - - if ShouldRescaleRig: - - rrf = GetRescaleRigFactor() # rigRescaleFactor - savedUnitLength = bpy.context.scene.unit_settings.scale_length - bpy.context.scene.unit_settings.scale_length = 0.01 # *= 1/rrf - - ApplySkeletalExportScale(active, rrf, is_a_proxy=export_as_proxy) - - meshType = GetAssetType(active) - - SetSocketsExportTransform(active) - SetSocketsExportName(active) - - # Set rename temporarily the Armature as "Armature" - - bfu_check_potential_error.UpdateNameHierarchy( - GetAllCollisionAndSocketsObj(bpy.context.selected_objects) - ) - - RemoveAllConsraints(active) - bpy.context.object.data.pose_position = 'REST' - - ConvertArmatureConstraintToModifiers(active) - SetVertexColorForUnrealExport(active) - - asset_name.SetExportName() - - if (export_procedure == "normal"): - pass - bpy.ops.export_scene.fbx( - filepath=GetExportFullpath(dirpath, filename), - check_existing=False, - use_selection=True, - global_scale=GetObjExportScale(active), - object_types={ - 'ARMATURE', - 'EMPTY', - 'CAMERA', - 'LIGHT', - 'MESH', - 'OTHER'}, - use_custom_props=addon_prefs.exportWithCustomProps, - mesh_smooth_type="FACE", - add_leaf_bones=False, - use_armature_deform_only=active.exportDeformOnly, - bake_anim=False, - use_metadata=addon_prefs.exportWithMetaData, - primary_bone_axis=active.exportPrimaryBaneAxis, - secondary_bone_axis=active.exporSecondaryBoneAxis, - axis_forward=active.exportAxisForward, - axis_up=active.exportAxisUp, - bake_space_transform=False - ) - - if (export_procedure == "auto-rig-pro"): - ExportAutoProRig( - filepath=fullpath, - # export_rig_name=GetDesiredExportArmatureName(active), - bake_anim=False, - mesh_smooth_type="FACE" - ) - - # This will rescale the rig and unit scale to get a root bone egal to 1 - if ShouldRescaleRig: - # Reset Curve an unit - bpy.context.scene.unit_settings.scale_length = savedUnitLength - - asset_name.ResetNames() - - ClearVertexColorForUnrealExport(active) - ResetArmatureConstraintToModifiers(active) - ResetSocketsExportName(active) - ResetSocketsTransform(active) - CleanDeleteObjects(bpy.context.selected_objects) - for data in duplicate_data.data_to_remove: - data.RemoveData() - - ResetDuplicateNameAfterExport(duplicate_data) - - for obj in scene.objects: +# ====================== 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 +import os + +if "bpy" in locals(): + import importlib + if "bfu_write_text" in locals(): + importlib.reload(bfu_write_text) + if "bfu_basics" in locals(): + importlib.reload(bfu_basics) + if "bfu_utils" in locals(): + importlib.reload(bfu_utils) + if "bfu_check_potential_error" in locals(): + importlib.reload(bfu_check_potential_error) + if "bfu_export_utils" in locals(): + importlib.reload(bfu_export_utils) + + +from .. import bfu_write_text +from .. import bfu_basics +from ..bfu_basics import * +from .. import bfu_utils +from ..bfu_utils import * +from .. import bfu_check_potential_error + +from . import bfu_export_utils +from .bfu_export_utils import * + + +def ProcessSkeletalMeshExport(obj): + addon_prefs = GetAddonPrefs() + dirpath = GetObjExportDir(obj) + absdirpath = bpy.path.abspath(dirpath) + scene = bpy.context.scene + + MyAsset = scene.UnrealExportedAssetsList.add() + MyAsset.object = obj + MyAsset.skeleton_name = obj.name + MyAsset.asset_name = obj.name + MyAsset.folder_name = obj.exportFolderName + MyAsset.asset_type = bfu_utils.GetAssetType(obj) + MyAsset.StartAssetExport() + + ExportSingleSkeletalMesh(scene, dirpath, GetObjExportFileName(obj), obj) + file = MyAsset.files.add() + file.name = GetObjExportFileName(obj) + file.path = dirpath + file.type = "FBX" + + if not obj.ExportAsLod: + if (scene.text_AdditionalData and addon_prefs.useGeneratedScripts): + ExportAdditionalParameter(absdirpath, MyAsset) + file = MyAsset.files.add() + file.name = GetObjExportFileName(obj, "_AdditionalTrack.json") + file.path = dirpath + file.type = "AdditionalTrack" + + MyAsset.EndAssetExport(True) + return MyAsset + + +def ExportSingleSkeletalMesh( + originalScene, + dirpath, + filename, + obj + ): + + ''' + ##################################################### + #SKELETAL MESH + ##################################################### + ''' + # Export a single Mesh + + scene = bpy.context.scene + addon_prefs = GetAddonPrefs() + export_as_proxy = GetExportAsProxy(obj) + export_proxy_child = GetExportProxyChild(obj) + + bbpl.utils.SafeModeSet('OBJECT') + + SelectParentAndDesiredChilds(obj) + asset_name = PrepareExportName(obj, True) + duplicate_data = DuplicateSelectForExport() + SetDuplicateNameForExport(duplicate_data) + + ApplyNeededModifierToSelect() + for obj in bpy.context.selected_objects: + ConvertGeometryNodeAttributeToUV(obj) + CorrectExtremUVAtExport(obj) + SetVertexColorForUnrealExport(obj) + SetSocketsExportTransform(obj) + SetSocketsExportName(obj) + + active = bpy.context.view_layer.objects.active + asset_name.target_object = active + + + + export_procedure = active.bfu_export_procedure + + if export_as_proxy: + ApplyProxyData(active) + + ApplyExportTransform(active, "Object") # Apply export transform before rescale + + # This will rescale the rig and unit scale to get a root bone egal to 1 + ShouldRescaleRig = GetShouldRescaleRig(active) + + if ShouldRescaleRig: + + rrf = GetRescaleRigFactor() # rigRescaleFactor + savedUnitLength = bpy.context.scene.unit_settings.scale_length + bpy.context.scene.unit_settings.scale_length = 0.01 # *= 1/rrf + + ApplySkeletalExportScale(active, rrf, is_a_proxy=export_as_proxy) + + meshType = GetAssetType(active) + + # Set rename temporarily the Armature as "Armature" + + bfu_check_potential_error.UpdateNameHierarchy( + GetAllCollisionAndSocketsObj(bpy.context.selected_objects) + ) + + RemoveAllConsraints(active) + bpy.context.object.data.pose_position = 'REST' + + ConvertArmatureConstraintToModifiers(active) + + + asset_name.SetExportName() + + if (export_procedure == "normal"): + pass + bpy.ops.export_scene.fbx( + filepath=GetExportFullpath(dirpath, filename), + check_existing=False, + use_selection=True, + global_scale=GetObjExportScale(active), + object_types={ + 'ARMATURE', + 'EMPTY', + 'CAMERA', + 'LIGHT', + 'MESH', + 'OTHER'}, + use_custom_props=addon_prefs.exportWithCustomProps, + mesh_smooth_type="FACE", + add_leaf_bones=False, + use_armature_deform_only=active.exportDeformOnly, + bake_anim=False, + use_metadata=addon_prefs.exportWithMetaData, + primary_bone_axis=active.exportPrimaryBaneAxis, + secondary_bone_axis=active.exporSecondaryBoneAxis, + axis_forward=active.exportAxisForward, + axis_up=active.exportAxisUp, + bake_space_transform=False + ) + + if (export_procedure == "auto-rig-pro"): + ExportAutoProRig( + filepath=fullpath, + # export_rig_name=GetDesiredExportArmatureName(active), + bake_anim=False, + mesh_smooth_type="FACE" + ) + + # This will rescale the rig and unit scale to get a root bone egal to 1 + if ShouldRescaleRig: + # Reset Curve an unit + bpy.context.scene.unit_settings.scale_length = savedUnitLength + + asset_name.ResetNames() + + ClearVertexColorForUnrealExport(active) + ResetArmatureConstraintToModifiers(active) + ResetSocketsExportName(active) + ResetSocketsTransform(active) + CleanDeleteObjects(bpy.context.selected_objects) + for data in duplicate_data.data_to_remove: + data.RemoveData() + + ResetDuplicateNameAfterExport(duplicate_data) + + for obj in scene.objects: ClearAllBFUTempVars(obj) \ No newline at end of file diff --git a/blender-for-unrealengine/export/bfu_export_single_static_mesh.py b/blender-for-unrealengine/export/bfu_export_single_static_mesh.py index 1a489c9b..9e079a9a 100644 --- a/blender-for-unrealengine/export/bfu_export_single_static_mesh.py +++ b/blender-for-unrealengine/export/bfu_export_single_static_mesh.py @@ -1,158 +1,161 @@ -# ====================== 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 - -if "bpy" in locals(): - import importlib - if "bfu_write_text" in locals(): - importlib.reload(bfu_write_text) - if "bfu_basics" in locals(): - importlib.reload(bfu_basics) - if "bfu_utils" in locals(): - importlib.reload(bfu_utils) - if "bfu_check_potential_error" in locals(): - importlib.reload(bfu_check_potential_error) - if "bfu_export_utils" in locals(): - importlib.reload(bfu_export_utils) - - -from .. import bfu_write_text -from .. import bfu_basics -from ..bfu_basics import * -from .. import bfu_utils -from ..bfu_utils import * -from .. import bfu_check_potential_error - -from . import bfu_export_utils -from .bfu_export_utils import * - - -def ProcessStaticMeshExport(obj): - addon_prefs = GetAddonPrefs() - dirpath = GetObjExportDir(obj) - absdirpath = bpy.path.abspath(dirpath) - scene = bpy.context.scene - - MyAsset = scene.UnrealExportedAssetsList.add() - MyAsset.object = obj - MyAsset.asset_name = obj.name - MyAsset.folder_name = obj.exportFolderName - MyAsset.asset_type = bfu_utils.GetAssetType(obj) - MyAsset.StartAssetExport() - - ExportSingleStaticMesh(dirpath, GetObjExportFileName(obj), obj) - file = MyAsset.files.add() - file.name = GetObjExportFileName(obj) - file.path = dirpath - file.type = "FBX" - - if not obj.ExportAsLod: - if (scene.text_AdditionalData and addon_prefs.useGeneratedScripts): - ExportAdditionalParameter(absdirpath, MyAsset) - file = MyAsset.files.add() - file.name = GetObjExportFileName(obj, "_AdditionalTrack.json") - file.path = dirpath - file.type = "AdditionalTrack" - - MyAsset.EndAssetExport(True) - return MyAsset - - -def ExportSingleStaticMesh( - dirpath, - filename, - obj - ): - - ''' - ##################################################### - #STATIC MESH - ##################################################### - ''' - # Export a single Mesh - - scene = bpy.context.scene - addon_prefs = GetAddonPrefs() - - bbpl.utils.SafeModeSet('OBJECT') - - SelectParentAndDesiredChilds(obj) - asset_name = PrepareExportName(obj, False) - duplicate_data = DuplicateSelectForExport() - SetDuplicateNameForExport(duplicate_data) - - MakeSelectVisualReal() - - ApplyNeededModifierToSelect() - - active = bpy.context.view_layer.objects.active - asset_name.target_object = active - - ConvertGeometryNodeAttributeToUV(active) - CorrectExtremUVAtExport(active) - - ApplyExportTransform(active, "Object") - - meshType = GetAssetType(active) - SetSocketsExportTransform(active) - SetSocketsExportName(active) - - bfu_check_potential_error.UpdateNameHierarchy( - GetAllCollisionAndSocketsObj(bpy.context.selected_objects) - ) - - SetVertexColorForUnrealExport(active) - - asset_name.SetExportName() - - bpy.ops.export_scene.fbx( - filepath=GetExportFullpath(dirpath, filename), - check_existing=False, - use_selection=True, - global_scale=GetObjExportScale(active), - object_types={'EMPTY', 'CAMERA', 'LIGHT', 'MESH', 'OTHER'}, - use_custom_props=addon_prefs.exportWithCustomProps, - mesh_smooth_type="FACE", - add_leaf_bones=False, - use_armature_deform_only=active.exportDeformOnly, - bake_anim=False, - use_metadata=addon_prefs.exportWithMetaData, - primary_bone_axis=active.exportPrimaryBaneAxis, - secondary_bone_axis=active.exporSecondaryBoneAxis, - axis_forward=active.exportAxisForward, - axis_up=active.exportAxisUp, - bake_space_transform=False - ) - - asset_name.ResetNames() - - ClearVertexColorForUnrealExport(active) - ResetSocketsExportName(active) - ResetSocketsTransform(active) - CleanDeleteObjects(bpy.context.selected_objects) - for data in duplicate_data.data_to_remove: - data.RemoveData() - - ResetDuplicateNameAfterExport(duplicate_data) - - for obj in scene.objects: - ClearAllBFUTempVars(obj) +# ====================== 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 + +if "bpy" in locals(): + import importlib + if "bfu_write_text" in locals(): + importlib.reload(bfu_write_text) + if "bfu_basics" in locals(): + importlib.reload(bfu_basics) + if "bfu_utils" in locals(): + importlib.reload(bfu_utils) + if "bfu_check_potential_error" in locals(): + importlib.reload(bfu_check_potential_error) + if "bfu_export_utils" in locals(): + importlib.reload(bfu_export_utils) + + +from .. import bfu_write_text +from .. import bfu_basics +from ..bfu_basics import * +from .. import bfu_utils +from ..bfu_utils import * +from .. import bfu_check_potential_error + +from . import bfu_export_utils +from .bfu_export_utils import * + + +def ProcessStaticMeshExport(obj): + addon_prefs = GetAddonPrefs() + dirpath = GetObjExportDir(obj) + absdirpath = bpy.path.abspath(dirpath) + scene = bpy.context.scene + + MyAsset = scene.UnrealExportedAssetsList.add() + MyAsset.object = obj + MyAsset.asset_name = obj.name + MyAsset.folder_name = obj.exportFolderName + MyAsset.asset_type = bfu_utils.GetAssetType(obj) + MyAsset.StartAssetExport() + + ExportSingleStaticMesh(dirpath, GetObjExportFileName(obj), obj) + file = MyAsset.files.add() + file.name = GetObjExportFileName(obj) + file.path = dirpath + file.type = "FBX" + + if not obj.ExportAsLod: + if (scene.text_AdditionalData and addon_prefs.useGeneratedScripts): + ExportAdditionalParameter(absdirpath, MyAsset) + file = MyAsset.files.add() + file.name = GetObjExportFileName(obj, "_AdditionalTrack.json") + file.path = dirpath + file.type = "AdditionalTrack" + + MyAsset.EndAssetExport(True) + return MyAsset + + +def ExportSingleStaticMesh( + dirpath, + filename, + obj + ): + + ''' + ##################################################### + #STATIC MESH + ##################################################### + ''' + # Export a single Mesh + + scene = bpy.context.scene + addon_prefs = GetAddonPrefs() + + bbpl.utils.SafeModeSet('OBJECT') + + SelectParentAndDesiredChilds(obj) + asset_name = PrepareExportName(obj, False) + duplicate_data = DuplicateSelectForExport() + SetDuplicateNameForExport(duplicate_data) + + MakeSelectVisualReal() + + ApplyNeededModifierToSelect() + for obj in bpy.context.selected_objects: + SetVertexColorForUnrealExport(obj) + ConvertGeometryNodeAttributeToUV(obj) + CorrectExtremUVAtExport(obj) + SetSocketsExportTransform(obj) + SetSocketsExportName(obj) + + + active = bpy.context.view_layer.objects.active + asset_name.target_object = active + + ApplyExportTransform(active, "Object") + + meshType = GetAssetType(active) + + + bfu_check_potential_error.UpdateNameHierarchy( + GetAllCollisionAndSocketsObj(bpy.context.selected_objects) + ) + + + + asset_name.SetExportName() + + bpy.ops.export_scene.fbx( + filepath=GetExportFullpath(dirpath, filename), + check_existing=False, + use_selection=True, + global_scale=GetObjExportScale(active), + object_types={'EMPTY', 'CAMERA', 'LIGHT', 'MESH', 'OTHER'}, + use_custom_props=addon_prefs.exportWithCustomProps, + mesh_smooth_type="FACE", + add_leaf_bones=False, + use_armature_deform_only=active.exportDeformOnly, + bake_anim=False, + use_metadata=addon_prefs.exportWithMetaData, + primary_bone_axis=active.exportPrimaryBaneAxis, + secondary_bone_axis=active.exporSecondaryBoneAxis, + axis_forward=active.exportAxisForward, + axis_up=active.exportAxisUp, + bake_space_transform=False + ) + + asset_name.ResetNames() + + ClearVertexColorForUnrealExport(active) + ResetSocketsExportName(active) + ResetSocketsTransform(active) + CleanDeleteObjects(bpy.context.selected_objects) + for data in duplicate_data.data_to_remove: + data.RemoveData() + + ResetDuplicateNameAfterExport(duplicate_data) + + for obj in scene.objects: + ClearAllBFUTempVars(obj) From ef7216c01d9460300ebbe2bc65b3b18bba7f6c08 Mon Sep 17 00:00:00 2001 From: BleuRaven Date: Fri, 7 Apr 2023 17:44:11 +0200 Subject: [PATCH 7/8] - Fixed: Export using AutoProRig fail. --- Release log.txt | 5 +++-- .../export/bfu_export_single_fbx_action.py | 2 +- .../export/bfu_export_single_fbx_nla_anim.py | 2 +- .../export/bfu_export_single_skeletal_mesh.py | 2 +- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/Release log.txt b/Release log.txt index 268160c1..a3afe210 100644 --- a/Release log.txt +++ b/Release log.txt @@ -1,4 +1,4 @@ -== Rev 0.1.3 == +== Rev 0.1.3 == Initial release. @@ -450,4 +450,5 @@ It is now possible to force the duration of an animation according to the scene - Update Export Button Name - New options for set start/end animation time with NLA. - Fixed: ConvertGeometryNodeAttributeToUV fail after Blender 3.5 -- Fixed: Geometry To UV, Correct Extrem UV, Vertex Color, Sockets Transform and Name don't apply on child objects. \ No newline at end of file +- Fixed: Geometry To UV, Correct Extrem UV, Vertex Color, Sockets Transform and Name don't apply on child objects. +- Fixed: Export using AutoProRig fail. \ No newline at end of file diff --git a/blender-for-unrealengine/export/bfu_export_single_fbx_action.py b/blender-for-unrealengine/export/bfu_export_single_fbx_action.py index 0c2ea4f5..61f7810b 100644 --- a/blender-for-unrealengine/export/bfu_export_single_fbx_action.py +++ b/blender-for-unrealengine/export/bfu_export_single_fbx_action.py @@ -193,7 +193,7 @@ def ExportSingleFbxAction( active.animation_data.action.name = TempName ExportAutoProRig( - filepath=fullpath, + filepath=GetExportFullpath(dirpath, filename), # export_rig_name=GetDesiredExportArmatureName(active), bake_anim=True, anim_export_name_string=active.animation_data.action.name, diff --git a/blender-for-unrealengine/export/bfu_export_single_fbx_nla_anim.py b/blender-for-unrealengine/export/bfu_export_single_fbx_nla_anim.py index 7c9694df..33b71097 100644 --- a/blender-for-unrealengine/export/bfu_export_single_fbx_nla_anim.py +++ b/blender-for-unrealengine/export/bfu_export_single_fbx_nla_anim.py @@ -172,7 +172,7 @@ def ExportSingleFbxNLAAnim( if (export_procedure == "auto-rig-pro"): ExportAutoProRig( - filepath=fullpath, + filepath=GetExportFullpath(dirpath, filename), # export_rig_name=GetDesiredExportArmatureName(active), bake_anim=True, anim_export_name_string=active.animation_data.action.name, diff --git a/blender-for-unrealengine/export/bfu_export_single_skeletal_mesh.py b/blender-for-unrealengine/export/bfu_export_single_skeletal_mesh.py index 0c533376..5ca78065 100644 --- a/blender-for-unrealengine/export/bfu_export_single_skeletal_mesh.py +++ b/blender-for-unrealengine/export/bfu_export_single_skeletal_mesh.py @@ -181,7 +181,7 @@ def ExportSingleSkeletalMesh( if (export_procedure == "auto-rig-pro"): ExportAutoProRig( - filepath=fullpath, + filepath=GetExportFullpath(dirpath, filename), # export_rig_name=GetDesiredExportArmatureName(active), bake_anim=False, mesh_smooth_type="FACE" From eb2916616e67913db2cb29324af0cd04fcfc59f5 Mon Sep 17 00:00:00 2001 From: BleuRaven Date: Fri, 7 Apr 2023 17:47:30 +0200 Subject: [PATCH 8/8] Clean --- Release log.txt | 4 +- blender-for-unrealengine/bfu_basics.py | 664 +-- blender-for-unrealengine/bfu_utils.py | 3803 ++++++++--------- .../export/bfu_export_get_info.py | 236 +- .../export/bfu_export_single_camera.py | 302 +- .../export/bfu_export_single_fbx_action.py | 2 +- .../export/bfu_export_single_fbx_nla_anim.py | 2 +- .../export/bfu_export_single_skeletal_mesh.py | 7 +- .../export/bfu_export_single_static_mesh.py | 4 - .../export/bfu_export_utils.py | 6 +- .../import/asset_import_script.py | 1074 ++--- 11 files changed, 3048 insertions(+), 3056 deletions(-) diff --git a/Release log.txt b/Release log.txt index a3afe210..e28504ec 100644 --- a/Release log.txt +++ b/Release log.txt @@ -1,4 +1,4 @@ -== Rev 0.1.3 == +== Rev 0.1.3 == Initial release. @@ -450,5 +450,5 @@ It is now possible to force the duration of an animation according to the scene - Update Export Button Name - New options for set start/end animation time with NLA. - Fixed: ConvertGeometryNodeAttributeToUV fail after Blender 3.5 -- Fixed: Geometry To UV, Correct Extrem UV, Vertex Color, Sockets Transform and Name don't apply on child objects. +- Fixed: Geometry To UV, Correct Extrem UV, Vertex Color, Sockets Transform and Name don't apply on child objects. - Fixed: Export using AutoProRig fail. \ No newline at end of file diff --git a/blender-for-unrealengine/bfu_basics.py b/blender-for-unrealengine/bfu_basics.py index 61ce870a..c000f0dd 100644 --- a/blender-for-unrealengine/bfu_basics.py +++ b/blender-for-unrealengine/bfu_basics.py @@ -1,332 +1,332 @@ -# ====================== 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 string -from pathlib import Path -import bpy -import shutil -import bmesh -import addon_utils -from mathutils import Vector -from mathutils import Quaternion - - -def GetAddonPrefs(): - return bpy.context.preferences.addons[__package__].preferences - - -def is_deleted(o): - if o and o is not None: - return not (o.name in bpy.data.objects) - else: - return True - - -def CheckPluginIsActivated(PluginName): - is_enabled, is_loaded = addon_utils.check(PluginName) - return is_enabled and is_loaded - - -def MoveToGlobalView(): - local_view_areas = [] - for area in bpy.context.screen.areas: - if area.type == 'VIEW_3D': - space = area.spaces[0] - if space.local_view: # check if using local view - local_view_areas.append(area) - - for local_view_area in local_view_areas: - for region in local_view_area.regions: - if region.type == 'WINDOW': - # override context and switch to global view - override = {'area': local_view_area, 'region': region} - bpy.ops.view3d.localview(override) - - return local_view_areas - - -def MoveToLocalView(local_view_areas): - # TO DO - pass - - -def GetCurrentSelection(): - # Return array for selected and the active - class MyClass(): - def __init__(self): - self.active = None - self.selected_objects = [] - self.old_name = [] - - def RemoveFromList(self, objs): - for x, obj in enumerate(objs): - if obj in self.selected_objects: - self.selected_objects.remove(obj) - self.old_name.remove(self.old_name[x]) - - def RemoveFromListByName(self, name_list): - - for obj_name in name_list: - if obj_name in self.old_name: - x = self.old_name.index(obj_name) - del self.selected_objects[x] - del self.old_name[x] - - def DebugObjectList(self): - print("##########################################") - print(self.selected_objects) - print(self.old_name) - - Selected = MyClass() - Selected.active = bpy.context.view_layer.objects.active - Selected.selected_objects = bpy.context.selected_objects.copy() - for sel in Selected.selected_objects: - Selected.old_name.append(sel.name) - return(Selected) - - -def SetCurrentSelection(selection): - # Get array select object and the active - - bpy.ops.object.select_all(action='DESELECT') - for x, obj in enumerate(selection.selected_objects): - if not is_deleted(obj): - if obj.name in bpy.context.window.view_layer.objects: - obj.select_set(True) - - if selection.active: - selection.active.select_set(True) - bpy.context.view_layer.objects.active = selection.active - else: - if len(selection.selected_objects) > 0: - selection.selected_objects[0].select_set(True) - bpy.context.view_layer.objects.active = selection.selected_objects[0] - - -def SelectSpecificObject(obj): - - bpy.ops.object.select_all(action='DESELECT') - if obj.name in bpy.context.window.view_layer.objects: - obj.select_set(True) - bpy.context.view_layer.objects.active = obj - - -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 nextPowerOfTwo(n): - # compute power of two greater than or equal to n - - # decrement n (to handle cases when n itself - # is a power of 2) - n = n - 1 - - # do till only one bit is left - while n & n - 1: - n = n & n - 1 # unset rightmost bit - - # n is now a power of two (less than n) - return n << 1 - - -def previousPowerOfTwo(n): - # compute power of two less than or equal to n - - # do till only one bit is left - while (n & n - 1): - n = n & n - 1 # unset rightmost bit - - # n is now a power of two (less than or equal to n) - return n - - -def nearestPowerOfTwo(value): - if value < 2: - return 2 - - a = previousPowerOfTwo(value) - b = nextPowerOfTwo(value) - - if value - a < b - value: - return a - else: - return b - - -def RemoveFolderTree(folder): - dirpath = Path(folder) - if dirpath.exists() and dirpath.is_dir(): - shutil.rmtree(dirpath, ignore_errors=True) - - -def GetChilds(obj): - # Get all direct childs of a object - - ChildsObj = [] - for childObj in bpy.data.objects: - if childObj.library is None: - pare = childObj.parent - if pare is not None: - if pare.name == obj.name: - ChildsObj.append(childObj) - - return ChildsObj - - -def getRootBoneParent(bone): - if bone.parent is not None: - return getRootBoneParent(bone.parent) - return bone - - -def getFirstDeformBoneParent(bone): - if bone.parent is not None: - if bone.use_deform is True: - return bone - else: - return getFirstDeformBoneParent(bone.parent) - return bone - - -def SetCollectionUse(collection): - # Set if collection is hide and selectable - collection.hide_viewport = False - collection.hide_select = False - layer_collection = bpy.context.view_layer.layer_collection - if collection.name in layer_collection.children: - layer_collection.children[collection.name].hide_viewport = False - else: - print(collection.name, " not found in view_layer.layer_collection") - - -def GetRecursiveChilds(obj): - # Get all recursive childs of a object - - saveObjs = [] - - def tryAppend(obj): - if obj.name in bpy.context.scene.objects: - saveObjs.append(obj) - - for newobj in GetChilds(obj): - for childs in GetRecursiveChilds(newobj): - tryAppend(childs) - tryAppend(newobj) - return saveObjs - - -def ConvertToConvexHull(obj): - # Convert obj to Convex Hull - mesh = obj.data - if not mesh.is_editmode: - bm = bmesh.new() - bm.from_mesh(mesh) # Mesh to Bmesh - convex_hull = bmesh.ops.convex_hull( - bm, input=bm.verts, - use_existing_faces=True - ) - # convex_hull = bmesh.ops.recalc_face_normals(bm, faces=bm.faces) - bm.to_mesh(mesh) # BMesh to Mesh - - -def VerifiDirs(directory): - # Check and create a folder if it does not exist - - if not os.path.exists(directory): - os.makedirs(directory) - return True - return False - - -def ValidDirName(directory): - # https://gist.github.com/seanh/93666 - # Normalizes string, removes non-alpha characters - # File name use - - illegal_chars = r':*?"<>|' - directory = ''.join(c for c in directory if c not in illegal_chars) - - return directory - - -def ValidFilename(filename): - # https://gist.github.com/seanh/93666 - # Normalizes string, removes non-alpha characters - # File name use - - illegal_chars = r'\/:*?"<>|' - valid_chars = "-_.() %s%s" % (string.ascii_letters, string.digits) - - filename = ''.join(c for c in filename if c not in illegal_chars) - filename = ''.join(c for c in filename if c in valid_chars) - - return filename - - -def ValidDefname(filename): - # https://gist.github.com/seanh/93666 - # Normalizes string, removes non-alpha characters - # Def name use - - 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 b in obj.pose.bones: - b.rotation_quaternion = Quaternion((0, 0, 0), 0) - b.rotation_euler = Vector((0, 0, 0)) - b.scale = Vector((1, 1, 1)) - b.location = Vector((0, 0, 0)) - - -def GetIfActionIsAssociated(action, bone_names): - for group in action.groups: - for fcurve in group.channels: - s = fcurve.data_path - start = s.find('["') - end = s.rfind('"]') - if start > 0 and end > 0: - substring = s[start+2:end] - if substring in bone_names: - return True - return False - - -def GetSurfaceArea(obj): - bm = bmesh.new() - bm.from_mesh(obj.data) - area = sum(f.calc_area() for f in bm.faces) - bm.free() - return area - - -def setWindowsClipboard(text): - bpy.context.window_manager.clipboard = text - # bpy.context.window_manager.clipboard.encode('utf8') +# ====================== 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 string +from pathlib import Path +import bpy +import shutil +import bmesh +import addon_utils +from mathutils import Vector +from mathutils import Quaternion + + +def GetAddonPrefs(): + return bpy.context.preferences.addons[__package__].preferences + + +def is_deleted(o): + if o and o is not None: + return not (o.name in bpy.data.objects) + else: + return True + + +def CheckPluginIsActivated(PluginName): + is_enabled, is_loaded = addon_utils.check(PluginName) + return is_enabled and is_loaded + + +def MoveToGlobalView(): + local_view_areas = [] + for area in bpy.context.screen.areas: + if area.type == 'VIEW_3D': + space = area.spaces[0] + if space.local_view: # check if using local view + local_view_areas.append(area) + + for local_view_area in local_view_areas: + for region in local_view_area.regions: + if region.type == 'WINDOW': + # override context and switch to global view + override = {'area': local_view_area, 'region': region} + bpy.ops.view3d.localview(override) + + return local_view_areas + + +def MoveToLocalView(local_view_areas): + # TO DO + pass + + +def GetCurrentSelection(): + # Return array for selected and the active + class MyClass(): + def __init__(self): + self.active = None + self.selected_objects = [] + self.old_name = [] + + def RemoveFromList(self, objs): + for x, obj in enumerate(objs): + if obj in self.selected_objects: + self.selected_objects.remove(obj) + self.old_name.remove(self.old_name[x]) + + def RemoveFromListByName(self, name_list): + + for obj_name in name_list: + if obj_name in self.old_name: + x = self.old_name.index(obj_name) + del self.selected_objects[x] + del self.old_name[x] + + def DebugObjectList(self): + print("##########################################") + print(self.selected_objects) + print(self.old_name) + + Selected = MyClass() + Selected.active = bpy.context.view_layer.objects.active + Selected.selected_objects = bpy.context.selected_objects.copy() + for sel in Selected.selected_objects: + Selected.old_name.append(sel.name) + return (Selected) + + +def SetCurrentSelection(selection): + # Get array select object and the active + + bpy.ops.object.select_all(action='DESELECT') + for x, obj in enumerate(selection.selected_objects): + if not is_deleted(obj): + if obj.name in bpy.context.window.view_layer.objects: + obj.select_set(True) + + if selection.active: + selection.active.select_set(True) + bpy.context.view_layer.objects.active = selection.active + else: + if len(selection.selected_objects) > 0: + selection.selected_objects[0].select_set(True) + bpy.context.view_layer.objects.active = selection.selected_objects[0] + + +def SelectSpecificObject(obj): + + bpy.ops.object.select_all(action='DESELECT') + if obj.name in bpy.context.window.view_layer.objects: + obj.select_set(True) + bpy.context.view_layer.objects.active = obj + + +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 nextPowerOfTwo(n): + # compute power of two greater than or equal to n + + # decrement n (to handle cases when n itself + # is a power of 2) + n = n - 1 + + # do till only one bit is left + while n & n - 1: + n = n & n - 1 # unset rightmost bit + + # n is now a power of two (less than n) + return n << 1 + + +def previousPowerOfTwo(n): + # compute power of two less than or equal to n + + # do till only one bit is left + while (n & n - 1): + n = n & n - 1 # unset rightmost bit + + # n is now a power of two (less than or equal to n) + return n + + +def nearestPowerOfTwo(value): + if value < 2: + return 2 + + a = previousPowerOfTwo(value) + b = nextPowerOfTwo(value) + + if value - a < b - value: + return a + else: + return b + + +def RemoveFolderTree(folder): + dirpath = Path(folder) + if dirpath.exists() and dirpath.is_dir(): + shutil.rmtree(dirpath, ignore_errors=True) + + +def GetChilds(obj): + # Get all direct childs of a object + + ChildsObj = [] + for childObj in bpy.data.objects: + if childObj.library is None: + pare = childObj.parent + if pare is not None: + if pare.name == obj.name: + ChildsObj.append(childObj) + + return ChildsObj + + +def getRootBoneParent(bone): + if bone.parent is not None: + return getRootBoneParent(bone.parent) + return bone + + +def getFirstDeformBoneParent(bone): + if bone.parent is not None: + if bone.use_deform is True: + return bone + else: + return getFirstDeformBoneParent(bone.parent) + return bone + + +def SetCollectionUse(collection): + # Set if collection is hide and selectable + collection.hide_viewport = False + collection.hide_select = False + layer_collection = bpy.context.view_layer.layer_collection + if collection.name in layer_collection.children: + layer_collection.children[collection.name].hide_viewport = False + else: + print(collection.name, " not found in view_layer.layer_collection") + + +def GetRecursiveChilds(obj): + # Get all recursive childs of a object + + saveObjs = [] + + def tryAppend(obj): + if obj.name in bpy.context.scene.objects: + saveObjs.append(obj) + + for newobj in GetChilds(obj): + for childs in GetRecursiveChilds(newobj): + tryAppend(childs) + tryAppend(newobj) + return saveObjs + + +def ConvertToConvexHull(obj): + # Convert obj to Convex Hull + mesh = obj.data + if not mesh.is_editmode: + bm = bmesh.new() + bm.from_mesh(mesh) # Mesh to Bmesh + convex_hull = bmesh.ops.convex_hull( + bm, input=bm.verts, + use_existing_faces=True + ) + # convex_hull = bmesh.ops.recalc_face_normals(bm, faces=bm.faces) + bm.to_mesh(mesh) # BMesh to Mesh + + +def VerifiDirs(directory): + # Check and create a folder if it does not exist + + if not os.path.exists(directory): + os.makedirs(directory) + return True + return False + + +def ValidDirName(directory): + # https://gist.github.com/seanh/93666 + # Normalizes string, removes non-alpha characters + # File name use + + illegal_chars = r':*?"<>|' + directory = ''.join(c for c in directory if c not in illegal_chars) + + return directory + + +def ValidFilename(filename): + # https://gist.github.com/seanh/93666 + # Normalizes string, removes non-alpha characters + # File name use + + illegal_chars = r'\/:*?"<>|' + valid_chars = "-_.() %s%s" % (string.ascii_letters, string.digits) + + filename = ''.join(c for c in filename if c not in illegal_chars) + filename = ''.join(c for c in filename if c in valid_chars) + + return filename + + +def ValidDefname(filename): + # https://gist.github.com/seanh/93666 + # Normalizes string, removes non-alpha characters + # Def name use + + 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 b in obj.pose.bones: + b.rotation_quaternion = Quaternion((0, 0, 0), 0) + b.rotation_euler = Vector((0, 0, 0)) + b.scale = Vector((1, 1, 1)) + b.location = Vector((0, 0, 0)) + + +def GetIfActionIsAssociated(action, bone_names): + for group in action.groups: + for fcurve in group.channels: + s = fcurve.data_path + start = s.find('["') + end = s.rfind('"]') + if start > 0 and end > 0: + substring = s[start+2:end] + if substring in bone_names: + return True + return False + + +def GetSurfaceArea(obj): + bm = bmesh.new() + bm.from_mesh(obj.data) + area = sum(f.calc_area() for f in bm.faces) + bm.free() + return area + + +def setWindowsClipboard(text): + bpy.context.window_manager.clipboard = text + # bpy.context.window_manager.clipboard.encode('utf8') diff --git a/blender-for-unrealengine/bfu_utils.py b/blender-for-unrealengine/bfu_utils.py index 191bffb2..ec600c35 100644 --- a/blender-for-unrealengine/bfu_utils.py +++ b/blender-for-unrealengine/bfu_utils.py @@ -1,1902 +1,1901 @@ -# ====================== 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 mathutils -import math -import time -import sys -from . import bbpl - -from math import degrees, radians, tan -from mathutils import Matrix - -if "bpy" in locals(): - import importlib - if "bfu_write_text" in locals(): - importlib.reload(bfu_write_text) - if "bfu_basics" in locals(): - importlib.reload(bfu_basics) - - -from . import bfu_write_text -from . import bfu_basics -from .bfu_basics import * - - -class SavedBones(): - - def __init__(self, bone): - if bone: - self.name = bone.name - self.select = bone.select - self.hide = bone.hide - - -class SavedViewLayerChildren(): - - def __init__(self, vlayer, childCol): - if childCol: - self.vlayer_name = vlayer.name - self.name = childCol.name - self.exclude = childCol.exclude - self.hide_viewport = childCol.hide_viewport - self.children = [] - - for children in childCol.children: - SavedViewLayerChildren(vlayer, children) - - -class MarkerSequence(): - def __init__(self, marker): - scene = bpy.context.scene - self.marker = marker - self.start = 0 - self.end = scene.frame_end - - if marker is not None: - self.start = marker.frame - - -class TimelineMarkerSequence(): - - def __init__(self): - scene = bpy.context.scene - timeline = scene.timeline_markers - self.marker_sequences = self.GetMarkerSequences(timeline) - - def GetMarkerSequences(self, timeline_markers): - if len(timeline_markers) == 0: - print("Scene has no timeline_markers.") - return [] - - def GetFisrtMarket(marker_list): - if len(marker_list) == 0: - return None - - best_marker = "" - best_marker_frame = 0 - init = False - - for marker in marker_list: - - if init: - if marker.frame < best_marker_frame: - best_marker = marker - best_marker_frame = marker.frame - else: - best_marker = marker - best_marker_frame = marker.frame - init = True - - return best_marker - - marker_list = [] - for marker in timeline_markers: - marker_list.append(marker) - - order_marker_list = [] - while len(marker_list) != 0: - first_marker = GetFisrtMarket(marker_list) - order_marker_list.append(first_marker) - marker_list.remove(first_marker) - - marker_sequences = [] - - for marker in order_marker_list: - marker_sequence = MarkerSequence(marker) - - if len(marker_sequences) > 0: - previous_marker_sequence = marker_sequences[-1] - previous_marker_sequence.end = marker.frame - 1 - - marker_sequences.append(marker_sequence) - - return marker_sequences - - def GetMarkerSequenceAtFrame(self, frame): - if self.marker_sequences: - for marker_sequence in self.marker_sequences: - # print(marker_sequence.start, marker_sequence.end, frame) - if frame >= marker_sequence.start and frame <= marker_sequence.end: - return marker_sequence - return None - - -class CounterTimer(): - - def __init__(self): - self.start = time.perf_counter() - - def ResetTime(self): - self.start = time.perf_counter() - - def GetTime(self): - return time.perf_counter()-self.start - - -def UpdateProgress(job_title, progress, time=None): - - length = 20 # modify this to change the length - block = int(round(length*progress)) - msg = "\r{0}: [{1}] {2}%".format( - job_title, - "#"*block + "-"*(length-block), - round(progress*100, 2)) - - if progress >= 1: - if time is not None: - msg += " DONE IN " + str(round(time, 2)) + "s\r\n" - else: - msg += " DONE\r\n" - - -def RemoveUselessSpecificData(name, type): - if type == "MESH": - if name in bpy.data.meshes: - oldData = bpy.data.meshes[name] - if oldData.users == 0: - bpy.data.meshes.remove(oldData) - - if type == "ARMATURE": - if name in bpy.data.armatures: - oldData = bpy.data.armatures[name] - if oldData.users == 0: - bpy.data.armatures.remove(oldData) - - -def CleanJoinSelect(): - view_layer = bpy.context.view_layer - if len(bpy.context.selected_objects) > 1: - if view_layer.objects.active is None: - view_layer.objects.active = bpy.context.selected_objects[0] - - if bpy.ops.object.convert.poll(): - bpy.ops.object.join() - - -def CleanDeleteSelect(): - - removed_objects = [] - oldDataToRemove = [] - for obj in bpy.context.selected_objects: - removed_objects.append(obj.name) - if obj.data is not None: - oldDataToRemove.append([obj.data.name, obj.type]) - - bpy.ops.object.delete() - - for data in oldDataToRemove: - RemoveUselessSpecificData(data[0], data[1]) - - return removed_objects - - -def CleanDeleteObjects(objs): - - objs = list(dict.fromkeys(objs)) - - removed_objects = [] - for obj in objs: - - souldRemoveData = False - if obj.data is not None: - oldDataToRemove = obj.data.name - oldDataTypeToRemove = obj.type - souldRemoveData = True - - removed_objects.append(obj.name) - bpy.data.objects.remove(obj) - - if souldRemoveData: - RemoveUselessSpecificData(oldDataToRemove, oldDataTypeToRemove) - - return removed_objects - - -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(list=None): - # Get any object that can be understood - # as a collision or a socket by unreal - - if list is not None: - objs = list - else: - objs = bpy.context.scene.objects - - colObjs = [obj for obj in objs 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): - if child.ExportEnum != "dont_export": - if child.name in bpy.context.window.view_layer.objects: - DesiredObj.append(child) - - return DesiredObj - - -def GetSocketDesiredChild(targetObj): - sockets = [] - for obj in GetExportDesiredChilds(targetObj): - if IsASocket(obj): - sockets.append(obj) - - return sockets - - -def GetSkeletalMeshSockets(obj): - if obj is None: - return - - addon_prefs = GetAddonPrefs() - data = {} - sockets = [] - - for socket in GetSocketDesiredChild(obj): - sockets.append(socket) - - if GetAssetType(obj) != "SkeletalMesh": - return - - data['Sockets'] = [] - # config.set('Sockets', '; SocketName, BoneName, Location, Rotation, Scale') - - for i, socket in enumerate(sockets): - if IsASocket(socket): - SocketName = socket.name[7:] - else: - socket.name - - if socket.parent.exportDeformOnly: - b = getFirstDeformBoneParent(socket.parent.data.bones[socket.parent_bone]) - else: - b = socket.parent.data.bones[socket.parent_bone] - - ResetArmaturePose(socket.parent) - # GetRelativePostion - bml = b.matrix_local # Bone - am = socket.parent.matrix_world # Armature - em = socket.matrix_world # Socket - RelativeMatrix = (bml.inverted() @ am.inverted() @ em) - t = RelativeMatrix.to_translation() - r = RelativeMatrix.to_euler() - s = socket.scale*addon_prefs.skeletalSocketsImportedSize - - # Convet to array for Json and convert value for Unreal - array_location = [t[0], t[1]*-1, t[2]] - array_rotation = [math.degrees(r[0]), math.degrees(r[1])*-1, math.degrees(r[2])*-1] - array_scale = [s[0], s[1], s[2]] - - MySocket = {} - MySocket["SocketName"] = SocketName - MySocket["BoneName"] = b.name.replace('.', '_') - MySocket["Location"] = array_location - MySocket["Rotation"] = array_rotation - MySocket["Scale"] = array_scale - data['Sockets'].append(MySocket) - - return data['Sockets'] - - -def GetSubObjectDesiredChild(targetObj): - sub_objects = [] - for obj in GetExportDesiredChilds(targetObj): - if IsASubObject(obj): - sub_objects.append(obj) - - return sub_objects - - -def RemoveAllConsraints(obj): - for b in obj.pose.bones: - for c in b.constraints: - b.constraints.remove(c) - - -def RescaleRigConsraints(obj, scale): - for b in obj.pose.bones: - for c in b.constraints: - # STRETCH_TO - if c.type == "STRETCH_TO": - c.rest_length *= scale # Can be bigger than 10?... wtf - - # LIMIT_LOCATION - if c.type == "LIMIT_LOCATION": - c.min_x *= scale - c.min_y *= scale - c.min_z *= scale - c.max_x *= scale - c.max_y *= scale - c.max_z *= scale - - # LIMIT_DISTANCE - if c.type == "LIMIT_DISTANCE": - c.distance *= scale - - -def RescaleShapeKeysCurve(obj, scale): - if obj.data.shape_keys is None: # Optimisation - return - if obj.data.shape_keys.animation_data is None: - return - if obj.data.shape_keys.animation_data.drivers is None: - return - - for driver in obj.data.shape_keys.animation_data.drivers: - for key in driver.keyframe_points: - key.co[1] *= scale - key.handle_left[1] *= scale - key.handle_right[1] *= scale - - for mod in driver.modifiers: - if mod.type == "GENERATOR": - mod.coefficients[0] *= scale # coef: + - mod.coefficients[1] *= scale # coef: x - - -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 GetCollectionToExport(scene): - colExport = [] - for col in scene.CollectionExportList: - if col.use: - if col.name in bpy.data.collections: - collection = bpy.data.collections[col.name] - colExport.append(collection) - return colExport - - -class CachedAction(): - - ''' - I can't use bpy.types.Scene or bpy.types.Object Property. - "Writing to ID classes in this context is not allowed" - So I use simple python var - ''' - - class ActionFromCache(): - # Info about actions from last cache. - def __init__(self, action): - self.total_action_fcurves_len = len(action.fcurves) - - def __init__(self): - self.name = "" - self.is_cached = False - self.stored_actions = [] - self.total_actions = [] - self.total_rig_bone_len = 0 - - def CheckCache(self, obj): - # Check if the cache need update - if self.name != obj.name: - self.is_cached = False - if len(bpy.data.actions) != len(self.total_actions): - self.is_cached = False - if len(obj.data.bones) != self.total_rig_bone_len: - self.is_cached = False - for action_name in self.stored_actions: - if action_name not in bpy.data.actions: - self.is_cached = False - - return self.is_cached - - def StoreActions(self, obj, actions): - # Update new cache - self.name = obj.name - action_name_list = [] - for action in actions: - action_name_list.append(action.name) - self.stored_actions = action_name_list - self.total_actions.clear() - for action in bpy.data.actions: - self.total_actions.append(self.ActionFromCache(action)) - self.total_rig_bone_len = len(obj.data.bones) - self.is_cached = True - # print("Stored action cache updated.") - - def GetStoredActions(self): - actions = [] - for action_name in self.stored_actions: - if action_name in bpy.data.actions: - actions.append(bpy.data.actions[action_name]) - return actions - - def Clear(self): - pass - - -MyCachedActions = CachedAction() - - -def UpdateActionCache(obj): - # Force update cache export auto action list - return GetCachedExportAutoActionList(obj, True) - - -def GetCachedExportAutoActionList(obj, force_update_cache=False): - # This will cheak if the action contains - # the same bones of the armature - - actions = [] - - # Use the cache - if force_update_cache: - MyCachedActions.is_cached = False - - if MyCachedActions.CheckCache(obj): - actions = MyCachedActions.GetStoredActions() - - else: - MyCachedActions.Clear() - - objBoneNames = [bone.name for bone in obj.data.bones] - for action in bpy.data.actions: - if action.library is None: - if GetIfActionIsAssociated(action, objBoneNames): - actions.append(action) - # Update the cache - MyCachedActions.StoreActions(obj, actions) - return actions - - -def GetActionToExport(obj): - # Returns only the actions that will be exported with the Armature - - if obj.ExportAsLod: - return [] - - TargetActionToExport = [] # Action list - if obj.bfu_anim_action_export_enum == "dont_export": - return [] - - if obj.bfu_anim_action_export_enum == "export_current": - if obj.animation_data is not None: - if obj.animation_data.action is not None: - return [obj.animation_data.action] - - elif obj.bfu_anim_action_export_enum == "export_specific_list": - for action in bpy.data.actions: - for targetAction in obj.exportActionList: - if targetAction.use: - if targetAction.name == action.name: - TargetActionToExport.append(action) - - elif obj.bfu_anim_action_export_enum == "export_specific_prefix": - for action in bpy.data.actions: - if fnmatch.fnmatchcase(action.name, obj.PrefixNameToExport+"*"): - TargetActionToExport.append(action) - - elif obj.bfu_anim_action_export_enum == "export_auto": - TargetActionToExport = GetCachedExportAutoActionList(obj) - - return TargetActionToExport - - -def EvaluateCameraPositionForUnreal(camera, previous_euler=mathutils.Euler()): - # Get Transfrom - matrix_y = Matrix.Rotation(radians(90.0), 4, 'Y') - matrix_x = Matrix.Rotation(radians(-90.0), 4, 'X') - matrix = camera.matrix_world @ matrix_y @ matrix_x - matrix_rotation_offset = Matrix.Rotation(camera.AdditionalRotationForExport.z, 4, 'Z') - loc = matrix.to_translation() * 100 * bpy.context.scene.unit_settings.scale_length - loc += camera.AdditionalLocationForExport - r = matrix.to_euler("XYZ", previous_euler) - s = matrix.to_scale() - - loc *= mathutils.Vector([1, -1, 1]) - array_rotation = [degrees(r[0]), degrees(r[1])*-1, degrees(r[2])*-1] # Roll Pith Yaw XYZ - array_transform = [loc, array_rotation, s] - - # array_location = [loc[0], loc[1]*-1, loc[2]] - # r = mathutils.Euler([degrees(r[0]), degrees(r[1])*-1, degrees(r[2])*-1], r.order) # Roll Pith Yaw XYZ - # array_transform = [array_location, r, s] - - return array_transform - - -def EvaluateCameraRotationForBlender(transform): - x = transform["rotation_x"] - y = transform["rotation_y"]*-1 - z = transform["rotation_z"]*-1 - euler = mathutils.Euler([x, y, z], "XYZ") - return euler - - -def GetDesiredActionStartEndTime(obj, action): - # Returns desired action or camera anim start/end time - # Return start with index 0 and end with index 1 - # EndTime should be a less one frame bigger than StartTime - - scene = bpy.context.scene - if obj.type == "CAMERA": - startTime = scene.frame_start - endTime = scene.frame_end - if endTime <= startTime: - endTime = startTime+1 - return (startTime, endTime) - - elif obj.bfu_anim_action_start_end_time_enum == "with_keyframes": - # GetFirstActionFrame + Offset - startTime = int(action.frame_range.x) + obj.bfu_anim_action_start_frame_offset - # GetLastActionFrame + Offset - endTime = int(action.frame_range.y) + obj.bfu_anim_action_end_frame_offset - if endTime <= startTime: - endTime = startTime+1 - return (startTime, endTime) - - elif obj.bfu_anim_action_start_end_time_enum == "with_sceneframes": - startTime = scene.frame_start + obj.bfu_anim_action_start_frame_offset - endTime = scene.frame_end + obj.bfu_anim_action_end_frame_offset - if endTime <= startTime: - endTime = startTime+1 - return (startTime, endTime) - - elif obj.bfu_anim_action_start_end_time_enum == "with_customframes": - startTime = obj.bfu_anim_action_custom_start_frame - endTime = obj.bfu_anim_action_custom_end_frame - if endTime <= startTime: - endTime = startTime+1 - return (startTime, endTime) - - -def GetDesiredNLAStartEndTime(obj): - # Returns desired nla anim start/end time - # Return start with index 0 and end with index 1 - # EndTime should be a less one frame bigger than StartTime - - scene = bpy.context.scene - - - if obj.bfu_anim_nla_start_end_time_enum == "with_sceneframes": - startTime = scene.frame_start + obj.bfu_anim_nla_start_frame_offset - endTime = scene.frame_end + obj.bfu_anim_nla_end_frame_offset - if endTime <= startTime: - endTime = startTime+1 - - return (startTime, endTime) - - elif obj.bfu_anim_nla_start_end_time_enum == "with_customframes": - startTime = obj.bfu_anim_nla_custom_start_frame - endTime = obj.bfu_anim_nla_custom_end_frame - if endTime <= startTime: - endTime = startTime+1 - - return (startTime, endTime) - - -def GetUseCustomLightMapResolution(obj): - if obj.StaticMeshLightMapEnum == "Default": - return False - return True - - -def GetExportRealSurfaceArea(obj): - scene = bpy.context.scene - - local_view_areas = MoveToGlobalView() - bbpl.utils.SafeModeSet('OBJECT') - - SavedSelect = GetCurrentSelection() - SelectParentAndDesiredChilds(obj) - - bpy.ops.object.duplicate() - bpy.ops.object.duplicates_make_real( - use_base_parent=True, - use_hierarchy=True - ) - - ApplyNeededModifierToSelect() - bpy.ops.object.parent_clear(type='CLEAR_KEEP_TRANSFORM') - for selectObj in bpy.context.selected_objects: - # Remove unable to convert mesh - if selectObj.type == "EMPTY" or selectObj.type == "CURVE": - CleanDeleteObjects([selectObj]) - - for selectObj in bpy.context.selected_objects: - # Remove collision box - if CheckIsCollision(selectObj): - CleanDeleteObjects([selectObj]) - - if bpy.context.view_layer.objects.active is None: - # When the active id a empty - bpy.context.view_layer.objects.active = bpy.context.selected_objects[0] - bpy.ops.object.convert(target='MESH') - - active = bpy.context.view_layer.objects.active - - CleanJoinSelect() - active = bpy.context.view_layer.objects.active - area = GetSurfaceArea(active) - CleanDeleteObjects(bpy.context.selected_objects) - SetCurrentSelection(SavedSelect) - MoveToLocalView(local_view_areas) - return area - - -def GetCompuntedLightMap(obj): - if obj.StaticMeshLightMapEnum == "Default": - return -1 - - if obj.StaticMeshLightMapEnum == "CustomMap": - return obj.customStaticMeshLightMapRes - - if obj.StaticMeshLightMapEnum == "SurfaceArea": - # Get the area - area = obj.computedStaticMeshLightMapRes - area **= 0.5 # Adapte for light map - - if obj.useStaticMeshLightMapWorldScale: - # Turn area at world scale - x = max(obj.scale.x, obj.scale.x*-1) - y = max(obj.scale.y, obj.scale.y*-1) - z = max(obj.scale.z, obj.scale.z*-1) - objScale = (x + y + z)/3 - area *= objScale - - # Computed light map equal light map scale for a plane vvv - area *= bpy.context.scene.unit_settings.scale_length - area *= obj.staticMeshLightMapSurfaceScale/2 - if obj.staticMeshLightMapRoundPowerOfTwo: - - return nearestPowerOfTwo(int(round(area))) - return int(round(area)) - - -def GetActionType(action): - # return action type - - if action.frame_range.y - action.frame_range.x == 1: - return "Pose" - return "Action" - - -def GetCollectionType(collection): - # return collection type - - return "Collection StaticMesh" - - -def GetIsAnimation(type): - # return True if type(string) is a animation - if (type == "NlAnim" or type == "Action" or type == "Pose"): - return True - return False - - -def GetAssetType(obj): - # Return asset type of a object - - if obj.type == "CAMERA": - return "Camera" - - if obj.ExportAsAlembic: - return "Alembic" - - if obj.type == "ARMATURE" and not obj.ForceStaticMesh: - return "SkeletalMesh" - - return "StaticMesh" - - -def CheckIsCollision(target): - # Return true if obj is a collision - for obj in GetAllCollisionObj(): - if obj == target: - return True - return False - - -def SelectCollectionObjects(collection): - # Selects only all objects that must be exported in a collection - selectedObjs = [] - bpy.ops.object.select_all(action='DESELECT') - for selectObj in collection.all_objects: - if selectObj.ExportEnum != "dont_export": - if selectObj.name in bpy.context.view_layer.objects: - selectObj.select_set(True) - selectedObjs.append(selectObj) - - if len(selectedObjs) > 0: - if selectedObjs[0].name in bpy.context.view_layer.objects: - bpy.context.view_layer.objects.active = selectedObjs[0] - - return selectedObjs - - -def GetExportAsProxy(obj): - if GetObjProxyChild(obj): - return True - - if obj.data: - if obj.data.library: - return True - return False - - -def GetExportProxyChild(obj): - - if GetObjProxyChild(obj): - return GetObjProxyChild(obj) - - scene = bpy.context.scene - if obj.data: - if obj.data.library: - for child_obj in scene.objects: - if child_obj != obj: - if child_obj.instance_collection: - if child_obj.instance_collection.library: - if child_obj.instance_collection.library == obj.data.library: - return child_obj - return None - - -def SelectParentAndDesiredChilds(obj): - # Selects only all child objects that must be exported with parent object - selectedObjs = [] - bpy.ops.object.select_all(action='DESELECT') - for selectObj in GetExportDesiredChilds(obj): - if selectObj.name in bpy.context.view_layer.objects: - if GetAssetType(obj) == "SkeletalMesh": - # With skeletal mesh the socket must be not exported, - # ue4 read it like a bone - if not fnmatch.fnmatchcase(selectObj.name, "SOCKET*"): - selectObj.select_set(True) - selectedObjs.append(selectObj) - else: - selectObj.select_set(True) - selectedObjs.append(selectObj) - - if obj.name in bpy.context.view_layer.objects: - obj.select_set(True) - - if GetExportAsProxy(obj): - proxy_child = GetExportProxyChild(obj) - if proxy_child is not None: - proxy_child.select_set(True) - - selectedObjs.append(obj) - if obj.name in bpy.context.view_layer.objects: - bpy.context.view_layer.objects.active = obj - return selectedObjs - - -def RemoveSocketFromSelectForProxyArmature(): - select = bbpl.utils.UserSelectSave() - select.SaveCurrentSelect() - # With skeletal mesh the socket must be not exported, - # ue4 read it like a bone - sockets = [] - for obj in bpy.context.selected_objects: - if fnmatch.fnmatchcase(obj.name, "SOCKET*"): - sockets.append(obj) - CleanDeleteObjects(sockets) - select.ResetSelectByName() - - -def GoToMeshEditMode(): - for obj in bpy.context.selected_objects: - if obj.type == "MESH": - bpy.context.view_layer.objects.active = obj - bbpl.utils.SafeModeSet('EDIT') - - return True - return False - - -def ApplyNeededModifierToSelect(): - - SavedSelect = GetCurrentSelection() - for obj in bpy.context.selected_objects: - if obj.type == "MESH": - SelectSpecificObject(obj) - for mod in [m for m in obj.modifiers if m.type != 'ARMATURE']: - if obj.data.shape_keys is None: - if obj.data.users > 1: - obj.data = obj.data.copy() - if bpy.ops.object.modifier_apply.poll(): - try: - bpy.ops.object.modifier_apply(modifier=mod.name) - except RuntimeError as ex: - # print the error incase its important... but continue - print(ex) - - SetCurrentSelection(SavedSelect) - - -def CorrectExtremeUV(stepScale=2): - - def GetHaveConnectedLoop(faceTarget): - # In bmesh faces - for loop in faceTarget.loops: - uv = loop[uv_lay].uv - for face in bm.faces: - if face.select: - if faceTarget != face: - for loop in face.loops: - if uv == loop[uv_lay].uv: - return True - return False - - def SelectRecursiveUVLinked(uv_lay): - - AddedFaces = [] - for v in [v for v in bm.verts if v.select]: - for f in v.link_faces: - if not f.select: - if GetHaveConnectedLoop(f): - AddedFaces.append(f) - f.select = True - - if len(AddedFaces) == 0: - return AddedFaces - else: - for addedFace in SelectRecursiveUVLinked(uv_lay): - AddedFaces.append(addedFace) - return AddedFaces - - def GetAllIsland(bm, uv_lay): - ToCheakFace = [] - Islands = [] - for face in bm.faces: - ToCheakFace.append(face) - - while len(ToCheakFace) > 0: - for face in bm.faces: - face.select = False - - ToCheakFace[-1].select = True - SelectRecursiveUVLinked(uv_lay) - - Island = [] - for face in bm.faces: - if face.select: - Island.append(face) - if face in ToCheakFace: - ToCheakFace.remove(face) - Islands.append(Island) - - return Islands - - def MoveItlandToCenter(faces, uv_lay, minDistance): - loop = faces[-1].loops[-1] - - x = round(loop[uv_lay].uv[0]/minDistance, 0)*minDistance - y = round(loop[uv_lay].uv[1]/minDistance, 0)*minDistance - - for face in faces: - for loop in face.loops: - loop[uv_lay].uv[0] -= x - loop[uv_lay].uv[1] -= y - - def IsValidForUvEdit(obj): - if obj.type == "MESH": - return True - return False - - for obj in bpy.context.selected_objects: - if IsValidForUvEdit(obj): - bm = bmesh.from_edit_mesh(obj.data) - - uv_lay = bm.loops.layers.uv.active - if uv_lay is None: - return - - for faces in GetAllIsland(bm, uv_lay): - uv_lay = bm.loops.layers.uv.active - MoveItlandToCenter(faces, uv_lay, stepScale) - - obj.data.update() - - -def ApplyExportTransform(obj, use_type="Object"): - - newMatrix = obj.matrix_world @ mathutils.Matrix.Translation((0, 0, 0)) - saveScale = obj.scale * 1 - - # Ref - # Moves object to the center of the scene for export - if use_type == "Object": - MoveToCenter = obj.MoveToCenterForExport - RotateToZero = obj.RotateToZeroForExport - - elif use_type == "Action": - MoveToCenter = obj.MoveActionToCenterForExport - RotateToZero = obj.RotateActionToZeroForExport - - elif use_type == "NLA": - MoveToCenter = obj.MoveNLAToCenterForExport - RotateToZero = obj.RotateNLAToZeroForExport - - else: - return - - if MoveToCenter: - mat_trans = mathutils.Matrix.Translation((0, 0, 0)) - mat_rot = newMatrix.to_quaternion().to_matrix() - newMatrix = mat_trans @ mat_rot.to_4x4() - - obj.matrix_world = newMatrix - # Turn object to the center of the scene for export - if RotateToZero: - mat_trans = mathutils.Matrix.Translation(newMatrix.to_translation()) - mat_rot = mathutils.Matrix.Rotation(0, 4, 'X') - newMatrix = mat_trans @ mat_rot - - eul = obj.AdditionalRotationForExport - loc = obj.AdditionalLocationForExport - - mat_rot = eul.to_matrix() - mat_loc = mathutils.Matrix.Translation(loc) - AddMat = mat_loc @ mat_rot.to_4x4() - - obj.matrix_world = newMatrix @ AddMat - obj.scale = saveScale - - -def ApplySkeletalExportScale(armature, rescale, target_animation_data=None, is_a_proxy=False): - - # This function will rescale the armature and applys the new scale - - armature.scale = armature.scale*rescale - # Save armature location - old_location = armature.location.copy() - - if target_animation_data is None: - armature_animation_data = bbpl.anim_utils.AnimationManagment() - armature_animation_data.SaveAnimationData(armature) - armature_animation_data.ClearAnimationData(armature) - else: - armature_animation_data = bbpl.anim_utils.AnimationManagment() - armature_animation_data.ClearAnimationData(armature) - - armature.location = (0, 0, 0) - - # Save childs location - ChildsLocation = [] - for Child in GetChilds(armature): - ChildsLocation.append([Child, Child.location.copy(), Child.matrix_parent_inverse.copy()]) - - if is_a_proxy: - selection = GetCurrentSelection() - bpy.ops.object.select_all(action='DESELECT') - armature.select_set(True) - - bpy.ops.object.transform_apply( - location=True, - scale=True, - rotation=True, - properties=True - ) - - if is_a_proxy: - SetCurrentSelection(selection) - - # Apply armature location - armature.location = old_location*rescale - - # Apply childs location - # I need work with matrix ChildLocation[0].matrix_parent_inverse - # But I don't understand how make it work. - for ChildLocation in ChildsLocation: - pass - - if target_animation_data is None: - armature_animation_data.SetAnimationData(armature, True) - else: - target_animation_data.SetAnimationData(armature, True) - - -def RescaleSelectCurveHook(scale): - - def GetRescaledMatrix(matrix, scale): - newMatrix = matrix.copy() - - newMatrix[0][0] *= 1 # Fix - newMatrix[0][1] *= 1 - newMatrix[0][2] *= 1 - newMatrix[0][3] *= scale - # --- - newMatrix[1][0] *= 1 - newMatrix[1][1] *= 1 # Fix - newMatrix[1][2] *= 1 # Fix - newMatrix[1][3] *= scale - # --- - newMatrix[2][0] *= 1 - newMatrix[2][1] *= 1 # Fix - newMatrix[2][2] *= 1 # Fix - newMatrix[2][3] *= scale - # --- - newMatrix[3][0] *= 1 - newMatrix[3][1] *= 1 - newMatrix[3][2] *= 1 - newMatrix[3][3] *= 1 # Fix - - return newMatrix - - for obj in bpy.context.selected_objects: - if obj.type == "CURVE": - for mod in obj.modifiers: - if mod.type == "HOOK": - scale_factor = 100 - mod.matrix_inverse = GetRescaledMatrix( - mod.matrix_inverse, - scale_factor - ) - for spline in obj.data.splines: - for bezier_point in spline.bezier_points: - bezier_point.radius *= scale - - -def RescaleActionCurve(action, scale): - for fcurve in action.fcurves: - if fcurve.data_path.split(".")[-1] == "location": - for key in fcurve.keyframe_points: - key.co[1] *= scale - key.handle_left[1] *= scale - key.handle_right[1] *= scale - - # Modifier - for mod in fcurve.modifiers: - if mod.type == "NOISE": - mod.strength *= scale - - -def RescaleAllActionCurve(bone_scale, scene_scale=1): - for action in bpy.data.actions: - print(action.name) - for fcurve in action.fcurves: - if fcurve.data_path == "location": - # Curve - for key in fcurve.keyframe_points: - key.co[1] *= scene_scale - key.handle_left[1] *= scene_scale - key.handle_right[1] *= scene_scale - - # Modifier - for mod in fcurve.modifiers: - if mod.type == "NOISE": - mod.strength *= scene_scale - - elif fcurve.data_path.split(".")[-1] == "location": - - # Curve - for key in fcurve.keyframe_points: - key.co[1] *= bone_scale - key.handle_left[1] *= bone_scale - key.handle_right[1] *= bone_scale - - # Modifier - for mod in fcurve.modifiers: - if mod.type == "NOISE": - mod.strength *= bone_scale - - -def GetFinalAssetToExport(): - # Returns all assets that will be exported - - def getHaveParentToExport(obj): - if obj.parent is not None: - if obj.parent.ExportEnum == 'export_recursive': - return obj.parent - else: - return getHaveParentToExport(obj.parent) - else: - return None - - scene = bpy.context.scene - export_filter = scene.bfu_export_selection_filter - - TargetAssetToExport = [] # Obj, Action, type - - class AssetToExport: - def __init__(self, obj, action, type): - self.obj = obj - self.action = action - self.type = type - - objList = [] - collectionList = [] - - if export_filter == "default": - objList = GetAllobjectsByExportType("export_recursive") - for col in GetCollectionToExport(scene): - collectionList.append(col.name) - - elif export_filter == "only_object" or export_filter == "only_object_action": - recuList = GetAllobjectsByExportType("export_recursive") - - for obj in bpy.context.selected_objects: - if obj in recuList: - if obj not in objList: - objList.append(obj) - parentTarget = getHaveParentToExport(obj) - if parentTarget is not None: - if parentTarget not in objList: - objList.append(parentTarget) - - for collection in collectionList: - # Collection - if scene.static_collection_export: - TargetAssetToExport.append(AssetToExport( - collection, - None, - "Collection StaticMesh")) - - for obj in objList: - if GetAssetType(obj) == "Alembic": - # Alembic - if scene.alembic_export: - TargetAssetToExport.append(AssetToExport( - obj, - None, - "Alembic")) - - if GetAssetType(obj) == "SkeletalMesh": - - # SkeletalMesh - if scene.skeletal_export: - TargetAssetToExport.append(AssetToExport( - obj, - None, - "SkeletalMesh")) - - # NLA - if scene.anin_export: - if obj.bfu_anim_nla_use: - TargetAssetToExport.append(AssetToExport( - obj, - obj.animation_data, - "NlAnim")) - - for action in GetActionToExport(obj): - if scene.bfu_export_selection_filter == "only_object_action": - if obj.animation_data: - if obj.animation_data.action == action: - TargetAssetToExport.append(AssetToExport(obj, action, "Action")) - else: - # Action - if scene.anin_export: - if GetActionType(action) == "Action": - TargetAssetToExport.append(AssetToExport(obj, action, "Action")) - - # Pose - if scene.anin_export: - if GetActionType(action) == "Pose": - TargetAssetToExport.append(AssetToExport(obj, action, "Pose")) - # Camera - if GetAssetType(obj) == "Camera" and scene.camera_export: - TargetAssetToExport.append(AssetToExport( - obj, - None, - "Camera")) - - # StaticMesh - if GetAssetType(obj) == "StaticMesh" and scene.static_export: - TargetAssetToExport.append(AssetToExport( - obj, - None, - "StaticMesh")) - - return TargetAssetToExport - - -def ValidFilenameForUnreal(filename): - # valid file name for unreal assets - extension = os.path.splitext(filename)[1] - newfilename = ValidFilename(os.path.splitext(filename)[0]) - return (''.join(c for c in newfilename if c != ".")+extension) - - -def ValidUnrealAssetsName(filename): - # Normalizes string, removes non-alpha characters - # Asset name in Unreal use - - filename = filename.replace('.', '_') - filename = filename.replace('(', '_') - filename = filename.replace(')', '_') - filename = filename.replace(' ', '_') - valid_chars = "-_%s%s" % (string.ascii_letters, string.digits) - filename = ''.join(c for c in filename if c in valid_chars) - return filename - - -def GetCollectionExportDir(col, abspath=False): - # Generate assset folder path - scene = bpy.context.scene - - dirpath = os.path.join( - scene.export_static_file_path, - col.exportFolderName) - - if abspath: - return bpy.path.abspath(dirpath) - else: - return dirpath - - -def GetObjExportName(obj): - # Return Proxy Name for Proxy and Object Name for other - if GetAssetType(obj) == "SkeletalMesh": - if GetExportAsProxy(obj): - proxy_child = GetExportProxyChild(obj) - if proxy_child is not None: - return proxy_child.name - return ValidFilename(obj.name) - - -def GetObjExportDir(obj, abspath=False): - # Generate assset folder path - folder_name = ValidDirName(obj.exportFolderName) - obj_name = ValidDirName(obj.name) # Fix obj name - - scene = bpy.context.scene - if GetAssetType(obj) == "SkeletalMesh": - dirpath = os.path.join( - scene.export_skeletal_file_path, - folder_name, - GetObjExportName(obj)) - if GetAssetType(obj) == "Alembic": - dirpath = os.path.join( - scene.export_alembic_file_path, - folder_name, - obj_name) - if GetAssetType(obj) == "StaticMesh": - dirpath = os.path.join( - scene.export_static_file_path, - folder_name) - if GetAssetType(obj) == "Camera": - dirpath = os.path.join( - scene.export_camera_file_path, - folder_name) - if abspath: - return bpy.path.abspath(dirpath) - - else: - return dirpath - - -def GetCollectionExportFileName(collection, fileType=".fbx"): - # Generate assset file name - - scene = bpy.context.scene - return scene.static_mesh_prefix_export_name+collection+fileType - - -def GetObjExportFileName(obj, fileType=".fbx"): - # Generate assset file name - - scene = bpy.context.scene - if obj.bfu_use_custom_export_name: - return ValidFilename(obj.bfu_custom_export_name+fileType) - assetType = GetAssetType(obj) - if assetType == "Camera": - return ValidFilename(scene.camera_prefix_export_name+obj.name+fileType) - elif assetType == "StaticMesh": - return ValidFilename(scene.static_mesh_prefix_export_name+obj.name+fileType) - elif assetType == "SkeletalMesh": - return ValidFilename(scene.skeletal_mesh_prefix_export_name+obj.name+fileType) - elif assetType == "Alembic": - return ValidFilename(scene.alembic_prefix_export_name+obj.name+fileType) - else: - return None - - -def GetActionExportFileName(obj, action, fileType=".fbx"): - # Generate action file name - - scene = bpy.context.scene - if obj.bfu_anim_naming_type == "include_armature_name": - ArmatureName = obj.name+"_" - if obj.bfu_anim_naming_type == "action_name": - ArmatureName = "" - if obj.bfu_anim_naming_type == "include_custom_name": - ArmatureName = obj.bfu_anim_naming_custom+"_" - - animType = GetActionType(action) - if animType == "NlAnim" or animType == "Action": - # Nla can be exported as action - return ValidFilename(scene.anim_prefix_export_name+ArmatureName+action.name+fileType) - - elif animType == "Pose": - return ValidFilename(scene.pose_prefix_export_name+ArmatureName+action.name+fileType) - - else: - return None - - -def GetNLAExportFileName(obj, fileType=".fbx"): - # Generate action file name - - scene = bpy.context.scene - if obj.bfu_anim_naming_type == "include_armature_name": - ArmatureName = obj.name+"_" - if obj.bfu_anim_naming_type == "action_name": - ArmatureName = "" - if obj.bfu_anim_naming_type == "include_custom_name": - ArmatureName = obj.bfu_anim_naming_custom+"_" - - return ValidFilename(scene.anim_prefix_export_name+ArmatureName+obj.bfu_anim_nla_export_name+fileType) - - -def GetImportAssetScriptCommand(): - scene = bpy.context.scene - fileName = scene.file_import_asset_script_name - absdirpath = bpy.path.abspath(scene.export_other_file_path) - fullpath = os.path.join(absdirpath, fileName) - addon_prefs = GetAddonPrefs() - return 'py "'+fullpath+'"' - - -def GetImportCameraScriptCommand(objs, CineCamera=True): - # Return (success, command) - - success = False - command = "" - report = "" - add_camera_num = 0 - - def AddCameraToCommand(camera): - if camera.type == "CAMERA": - t = "" - # Get Camera Data - scene = bpy.context.scene - frame_current = scene.frame_current - - # First I get the camera data. - # This is a very bad way to do this. I need do a new python file specific to camera with class to get data. - data = bfu_write_text.WriteCameraAnimationTracks(camera, frame_current, frame_current) - transform_track = data["Camera transform"][frame_current] - location_x = transform_track["location_x"] - location_y = transform_track["location_y"] - location_z = transform_track["location_z"] - rotation_x = transform_track["rotation_x"] - rotation_y = transform_track["rotation_y"] - rotation_z = transform_track["rotation_z"] - scale_x = transform_track["scale_x"] - scale_y = transform_track["scale_y"] - scale_z = transform_track["scale_z"] - NearClippingPlane = data["Camera NearClippingPlane"][frame_current] - FarClippingPlane = data["Camera FarClippingPlane"][frame_current] - FieldOfView = data["Camera FieldOfView"][frame_current] - FocalLength = data["Camera FocalLength"][frame_current] - SensorWidth = data["Camera SensorWidth"][frame_current] - SensorHeight = data["Camera SensorHeight"][frame_current] - FocusDistance = data["Camera FocusDistance"][frame_current] - Aperture = data["Camera Aperture"][frame_current] - AspectRatio = data["desired_screen_ratio"] - CameraName = camera.name - - # Actor - if CineCamera: - t += " " + "Begin Actor Class=/Script/CinematicCamera.CineCameraActor Name="+CameraName+" Archetype=/Script/CinematicCamera.CineCameraActor'/Script/CinematicCamera.Default__CineCameraActor'" + "\n" - else: - t += " " + "Begin Actor Class=/Script/Engine.CameraActor Name="+CameraName+" Archetype=/Script/Engine.CameraActor'/Script/Engine.Default__CameraActor'" + "\n" - - # Init SceneComponent - if CineCamera: - t += " " + "Begin Object Class=/Script/Engine.SceneComponent Name=\"SceneComponent\" Archetype=/Script/Engine.SceneComponent'/Script/CinematicCamera.Default__CineCameraActor:SceneComponent'" + "\n" - t += " " + "End Object" + "\n" - else: - t += " " + "Begin Object Class=/Script/Engine.SceneComponent Name=\"SceneComponent\" Archetype=/Script/Engine.SceneComponent'/Script/Engine.Default__CameraActor:SceneComponent'" + "\n" - t += " " + "End Object" + "\n" - - # Init CameraComponent - if CineCamera: - t += " " + "Begin Object Class=/Script/CinematicCamera.CineCameraComponent Name=\"CameraComponent\" Archetype=/Script/CinematicCamera.CineCameraComponent'/Script/CinematicCamera.Default__CineCameraActor:CameraComponent'" + "\n" - t += " " + "End Object" + "\n" - else: - t += " " + "Begin Object Class=/Script/Engine.CameraComponent Name=\"CameraComponent\" Archetype=/Script/Engine.CameraComponent'/Script/Engine.Default__CameraActor:CameraComponent'" + "\n" - t += " " + "End Object" + "\n" - - # SceneComponent - t += " " + "Begin Object Name=\"SceneComponent\"" + "\n" - t += " " + "RelativeLocation=(X="+str(location_x)+",Y="+str(location_y)+",Z="+str(location_z)+")" + "\n" - t += " " + "RelativeRotation=(Pitch="+str(rotation_y)+",Yaw="+str(rotation_z)+",Roll="+str(rotation_x)+")" + "\n" - t += " " + "RelativeScale3D=(X="+str(scale_x)+",Y="+str(scale_y)+",Z="+str(scale_z)+")" + "\n" - t += " " + "End Object" + "\n" - - # CameraComponent - t += " " + "Begin Object Name=\"CameraComponent\"" + "\n" - t += " " + "Filmback=(SensorWidth="+str(SensorWidth)+",SensorHeight="+str(SensorHeight)+", SensorAspectRatio="+str(AspectRatio)+")" + "\n" - t += " " + "CurrentAperture="+str(Aperture)+")" + "\n" - t += " " + "CurrentFocalLength="+str(FocalLength)+")" + "\n" - t += " " + "CurrentFocusDistance="+str(FocusDistance)+")" + "\n" - t += " " + "CurrentFocusDistance="+str(FocusDistance)+")" + "\n" - t += " " + "CustomNearClippingPlane="+str(NearClippingPlane)+")" + "\n" - t += " " + "FieldOfView="+str(FieldOfView)+")" + "\n" - t += " " + "AspectRatio="+str(AspectRatio)+")" + "\n" - t += " " + "End Object" + "\n" - - # Attach - t += " " + "CameraComponent=\"CameraComponent\"" + "\n" - t += " " + "SceneComponent=\"SceneComponent\"" + "\n" - t += " " + "RootComponent=\"SceneComponent\"" + "\n" - t += " " + "ActorLabel=\""+CameraName+"\"" + "\n" - - # Close - t += " " + "End Actor" + "\n" - return t - return None - - cameras = [] - for obj in objs: - if obj.type == "CAMERA": - cameras.append(obj) - - if len(cameras) == 0: - report = "Please select at least one camera." - return (success, command, report) - - # And I apply the camrta data to the copy paste text. - t = "Begin Map" + "\n" - t += " " + "Begin Level" + "\n" - for camera in cameras: - add_command = AddCameraToCommand(camera) - if add_command: - t += add_command - add_camera_num += 1 - - t += " " + "End Level" + "\n" - t += "Begin Surface" + "\n" - t += "End Surface" + "\n" - t += "End Object" + "\n" - - success = True - command = t - if CineCamera: - report = str(add_camera_num)+" Cine camera(s) copied. Paste in Unreal Engine scene for import the camera. (Ctrl+V)" - else: - report = str(add_camera_num)+" Regular camera(s) copied. Paste in Unreal Engine scene for import the camera. (Ctrl+V)" - - return (success, command, report) - - -def GetImportSkeletalMeshSocketScriptCommand(obj): - - if obj: - if obj.type == "ARMATURE": - sockets = GetSkeletalMeshSockets(obj) - t = "SocketCopyPasteBuffer" + "\n" - t += "NumSockets=" + str(len(sockets)) + "\n" - t += "IsOnSkeleton=1" + "\n" - for socket in sockets: - t += "Begin Object Class=/Script/Engine.SkeletalMeshSocket" + "\n" - t += "\t" + 'SocketName="' + socket["SocketName"] + '"' + "\n" - t += "\t" + 'BoneName="' + socket["BoneName"] + '"' + "\n" - loc = socket["Location"] - r = socket["Rotation"] - s = socket["Scale"] - t += "\t" + 'RelativeLocation=' + "(X="+str(loc[0])+",Y="+str(loc[1])+",Z="+str(loc[2])+")" + "\n" - t += "\t" + 'RelativeRotation=' + "(Pitch="+str(r[1])+",Yaw="+str(r[2])+",Roll="+str(r[0])+")" + "\n" - t += "\t" + 'RelativeScale=' + "(X="+str(s[0])+",Y="+str(s[1])+",Z="+str(s[2])+")" + "\n" - t += "End Object" + "\n" - return t - return "Please select an armature." - - -def GetImportSequencerScriptCommand(): - scene = bpy.context.scene - fileName = scene.file_import_sequencer_script_name - absdirpath = bpy.path.abspath(scene.export_other_file_path) - fullpath = os.path.join(absdirpath, fileName) - - addon_prefs = GetAddonPrefs() - return 'py "'+fullpath+'"' # Vania - - -def GetAnimSample(obj): - # return obj sample animation - return obj.SampleAnimForExport - - -def GetArmatureRootBones(obj): - rootBones = [] - if GetAssetType(obj) == "SkeletalMesh": - - if not obj.exportDeformOnly: - for bone in obj.data.bones: - if bone.parent is None: - rootBones.append(bone) - - if obj.exportDeformOnly: - for bone in obj.data.bones: - if bone.use_deform: - rootBone = getRootBoneParent(bone) - if rootBone not in rootBones: - rootBones.append(rootBone) - return rootBones - - -def GetDesiredExportArmatureName(obj): - addon_prefs = GetAddonPrefs() - single_root = len(GetArmatureRootBones(obj)) == 1 - if addon_prefs.add_skeleton_root_bone or single_root != 1: - return addon_prefs.skeleton_root_bone_name - return "Armature" - - -def GetObjExportScale(obj): - - return obj.exportGlobalScale - - -def GenerateUe4Name(name): - # Generate a new name with suffix number - - def IsValidName(testedName): - # Checks if objet end with number suffix - - if (testedName.split("_")[-1]).isnumeric(): - number = int(testedName.split("_")[-1]) - else: - # Last suffix is not a number - return False - - # 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 - - newName = "" - if IsValidName(name): - return name - else: - for num in range(0, 1000): - newName = name+"_"+str('%02d' % num) # Min two pad - if IsValidName(newName): - return newName - - return name - - -def CreateCollisionMaterial(): - addon_prefs = GetAddonPrefs() - - mat = bpy.data.materials.get("UE4Collision") - if mat is None: - mat = bpy.data.materials.new(name="UE4Collision") - - mat.diffuse_color = addon_prefs.collisionColor - 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]) - return mat - - -def Ue4SubObj_set(SubType): - # Convect obj to ue4 sub objects - # (Collisions Shapes or Socket) - - def DeselectAllWithoutActive(): - for obj in bpy.context.selected_objects: - if obj != bpy.context.active_object: - obj.select_set(False) - - ownerObj = bpy.context.active_object - ownerBone = bpy.context.active_pose_bone - objList = bpy.context.selected_objects - if ownerObj is None: - return [] - - ConvertedObjs = [] - - for obj in objList: - DeselectAllWithoutActive() - obj.select_set(True) - if obj != ownerObj: - - # SkeletalMesh Colider - if obj.type == 'MESH': - ConvertToConvexHull(obj) - obj.modifiers.clear() - obj.data - obj.data.materials.clear() - obj.active_material_index = 0 - obj.data.materials.append(CreateCollisionMaterial()) - - # Set the name of the Prefix depending on the - # type of collision in agreement with unreal FBX Pipeline - if SubType == "Box": - prefixName = "UBX_" - elif SubType == "Capsule": - prefixName = "UCP_" - elif SubType == "Sphere": - prefixName = "USP_" - elif SubType == "Convex": - prefixName = "UCX_" - - obj.name = GenerateUe4Name(prefixName+ownerObj.name) - obj.show_wire = True - obj.show_transparent = True - bpy.ops.object.parent_set(type='OBJECT', keep_transform=True) - ConvertedObjs.append(obj) - - # StaticMesh Socket - if obj.type == 'EMPTY' and SubType == "ST_Socket": - if ownerObj.type == 'MESH': - if not IsASocket(obj): - obj.name = GenerateUe4Name("SOCKET_"+obj.name) - bpy.ops.object.parent_set( - type='OBJECT', - keep_transform=True) - ConvertedObjs.append(obj) - - # SkeletalMesh Socket - if obj.type == 'EMPTY' and SubType == "SK_Socket": - if ownerObj.type == 'ARMATURE': - - if not IsASocket(obj): - obj.name = GenerateUe4Name("SOCKET_"+obj.name) - bpy.ops.object.parent_set(type='BONE') - ConvertedObjs.append(obj) - - DeselectAllWithoutActive() - for obj in objList: - obj.select_set(True) # Resets previous selected object - return ConvertedObjs - - -def UpdateUe4Name(SubType, objList): - # Convect obj to ue4 sub objects (Collisions Shapes or Socket) - - for obj in objList: - ownerObj = obj.parent - - if ownerObj is not None: - if obj != ownerObj: - - # SkeletalMesh Colider - if obj.type == 'MESH': - - # Set the name of the Prefix depending - # on the type of collision in agreement - # with unreal FBX Pipeline - - if SubType == "Box": - prefixName = "UBX_" - elif SubType == "Capsule": - prefixName = "UCP_" - elif SubType == "Sphere": - prefixName = "USP_" - elif SubType == "Convex": - prefixName = "UCX_" - - obj.name = GenerateUe4Name(prefixName+ownerObj.name) - - # StaticMesh Socket - if obj.type == 'EMPTY' and SubType == "ST_Socket": - if ownerObj.type == 'MESH': - if not IsASocket(obj): - obj.name = GenerateUe4Name("SOCKET_"+obj.name) - - # SkeletalMesh Socket - if obj.type == 'EMPTY' and SubType == "SK_Socket": - if ownerObj.type == 'ARMATURE': - if not IsASocket(obj): - obj.name = GenerateUe4Name("SOCKET_"+obj.name) - - -def IsASocket(obj): - ''' - Retrun True is object is an Socket. - https://docs.unrealengine.com/en-US/WorkingWithContent/Importing/FBX/StaticMeshes/#sockets - ''' - if obj.type == "EMPTY": - cap_name = obj.name.upper() - if cap_name.startswith("SOCKET_"): - return True - - return False - - -def IsACollision(obj): - ''' - Retrun True is object is an Collision. - https://docs.unrealengine.com/en-US/WorkingWithContent/Importing/FBX/StaticMeshes/#collision - ''' - if obj.type == "MESH": - cap_name = obj.name.upper() - if cap_name.startswith("UBX_"): - return True - elif cap_name.startswith("UCP_"): - return True - elif cap_name.startswith("USP_"): - return True - elif cap_name.startswith("UCX_"): - return True - - return False - - -def IsASubObject(obj): - ''' - Retrun True is object is an Socket or and Collision. - ''' - if IsASocket(obj) or IsACollision(obj): - return True - return False - - -def UpdateAreaLightMapList(list=None): - # Updates area LightMap - - if list is not None: - objs = list - else: - objs = [] - exportObjs = GetAllobjectsByExportType("export_recursive") - for exportObj in exportObjs: - if GetAssetType(exportObj) == "StaticMesh": - objs.append(exportObj) - - UpdatedRes = 0 - - counter = CounterTimer() - for obj in objs: - obj.computedStaticMeshLightMapRes = GetExportRealSurfaceArea(obj) - UpdatedRes += 1 - UpdateProgress( - "Update LightMap", - (UpdatedRes/len(objs)), - counter.GetTime()) - return UpdatedRes - - -def AddFrontEachLine(ImportScript, text="\t"): - - NewImportScript = "" - text_splited = ImportScript.split('\n') - for line in text_splited: - NewImportScript += text + line + "\n" - - return NewImportScript - - -# Custom property - - -def SetVarOnObject(obj, VarName, Value): - obj[VarName] = Value - - -def GetVarOnObject(obj, VarName): - return obj[VarName] - - -def HasVarOnObject(obj, VarName): - return VarName in obj - - -def ClearVarOnObject(obj, VarName): - if VarName in obj: - del obj[VarName] - - -def SaveObjCurrentName(obj): - # Save object current name as Custom property - SetVarOnObject(obj, "BFU_OriginName", obj.name) - - -def GetObjOriginName(obj): - return GetVarOnObject(obj, "BFU_OriginName") - - -def ClearObjOriginNameVar(obj): - ClearVarOnObject(obj, "BFU_OriginName") - - -def SetObjProxyData(obj): - # Save object proxy info as Custom property - SetVarOnObject(obj, "BFU_ExportAsProxy", GetExportAsProxy(obj)) - SetVarOnObject(obj, "BFU_ExportProxyChild", GetExportProxyChild(obj)) - - -def GetObjProxyChild(obj): - if (not HasVarOnObject(obj, "BFU_ExportAsProxy")): - return False - - if (not HasVarOnObject(obj, "BFU_ExportProxyChild")): - return False - - if GetVarOnObject(obj, "BFU_ExportAsProxy"): - return GetVarOnObject(obj, "BFU_ExportProxyChild") - return None - - -def ClearObjProxyDataVars(obj): - ClearVarOnObject(obj, "BFU_ExportAsProxy") - ClearVarOnObject(obj, "BFU_ExportProxyChild") - - -def ClearAllBFUTempVars(obj): - ClearVarOnObject(obj, "BFU_OriginName") - ClearVarOnObject(obj, "BFU_ExportAsProxy") - ClearVarOnObject(obj, "BFU_ExportProxyChild") +# ====================== 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 mathutils +import math +import time +import sys +from . import bbpl + +from math import degrees, radians, tan +from mathutils import Matrix + +if "bpy" in locals(): + import importlib + if "bfu_write_text" in locals(): + importlib.reload(bfu_write_text) + if "bfu_basics" in locals(): + importlib.reload(bfu_basics) + + +from . import bfu_write_text +from . import bfu_basics +from .bfu_basics import * + + +class SavedBones(): + + def __init__(self, bone): + if bone: + self.name = bone.name + self.select = bone.select + self.hide = bone.hide + + +class SavedViewLayerChildren(): + + def __init__(self, vlayer, childCol): + if childCol: + self.vlayer_name = vlayer.name + self.name = childCol.name + self.exclude = childCol.exclude + self.hide_viewport = childCol.hide_viewport + self.children = [] + + for children in childCol.children: + SavedViewLayerChildren(vlayer, children) + + +class MarkerSequence(): + def __init__(self, marker): + scene = bpy.context.scene + self.marker = marker + self.start = 0 + self.end = scene.frame_end + + if marker is not None: + self.start = marker.frame + + +class TimelineMarkerSequence(): + + def __init__(self): + scene = bpy.context.scene + timeline = scene.timeline_markers + self.marker_sequences = self.GetMarkerSequences(timeline) + + def GetMarkerSequences(self, timeline_markers): + if len(timeline_markers) == 0: + print("Scene has no timeline_markers.") + return [] + + def GetFisrtMarket(marker_list): + if len(marker_list) == 0: + return None + + best_marker = "" + best_marker_frame = 0 + init = False + + for marker in marker_list: + + if init: + if marker.frame < best_marker_frame: + best_marker = marker + best_marker_frame = marker.frame + else: + best_marker = marker + best_marker_frame = marker.frame + init = True + + return best_marker + + marker_list = [] + for marker in timeline_markers: + marker_list.append(marker) + + order_marker_list = [] + while len(marker_list) != 0: + first_marker = GetFisrtMarket(marker_list) + order_marker_list.append(first_marker) + marker_list.remove(first_marker) + + marker_sequences = [] + + for marker in order_marker_list: + marker_sequence = MarkerSequence(marker) + + if len(marker_sequences) > 0: + previous_marker_sequence = marker_sequences[-1] + previous_marker_sequence.end = marker.frame - 1 + + marker_sequences.append(marker_sequence) + + return marker_sequences + + def GetMarkerSequenceAtFrame(self, frame): + if self.marker_sequences: + for marker_sequence in self.marker_sequences: + # print(marker_sequence.start, marker_sequence.end, frame) + if frame >= marker_sequence.start and frame <= marker_sequence.end: + return marker_sequence + return None + + +class CounterTimer(): + + def __init__(self): + self.start = time.perf_counter() + + def ResetTime(self): + self.start = time.perf_counter() + + def GetTime(self): + return time.perf_counter()-self.start + + +def UpdateProgress(job_title, progress, time=None): + + length = 20 # modify this to change the length + block = int(round(length*progress)) + msg = "\r{0}: [{1}] {2}%".format( + job_title, + "#"*block + "-"*(length-block), + round(progress*100, 2)) + + if progress >= 1: + if time is not None: + msg += " DONE IN " + str(round(time, 2)) + "s\r\n" + else: + msg += " DONE\r\n" + + +def RemoveUselessSpecificData(name, type): + if type == "MESH": + if name in bpy.data.meshes: + oldData = bpy.data.meshes[name] + if oldData.users == 0: + bpy.data.meshes.remove(oldData) + + if type == "ARMATURE": + if name in bpy.data.armatures: + oldData = bpy.data.armatures[name] + if oldData.users == 0: + bpy.data.armatures.remove(oldData) + + +def CleanJoinSelect(): + view_layer = bpy.context.view_layer + if len(bpy.context.selected_objects) > 1: + if view_layer.objects.active is None: + view_layer.objects.active = bpy.context.selected_objects[0] + + if bpy.ops.object.convert.poll(): + bpy.ops.object.join() + + +def CleanDeleteSelect(): + + removed_objects = [] + oldDataToRemove = [] + for obj in bpy.context.selected_objects: + removed_objects.append(obj.name) + if obj.data is not None: + oldDataToRemove.append([obj.data.name, obj.type]) + + bpy.ops.object.delete() + + for data in oldDataToRemove: + RemoveUselessSpecificData(data[0], data[1]) + + return removed_objects + + +def CleanDeleteObjects(objs): + + objs = list(dict.fromkeys(objs)) + + removed_objects = [] + for obj in objs: + + souldRemoveData = False + if obj.data is not None: + oldDataToRemove = obj.data.name + oldDataTypeToRemove = obj.type + souldRemoveData = True + + removed_objects.append(obj.name) + bpy.data.objects.remove(obj) + + if souldRemoveData: + RemoveUselessSpecificData(oldDataToRemove, oldDataTypeToRemove) + + return removed_objects + + +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(list=None): + # Get any object that can be understood + # as a collision or a socket by unreal + + if list is not None: + objs = list + else: + objs = bpy.context.scene.objects + + colObjs = [obj for obj in objs 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): + if child.ExportEnum != "dont_export": + if child.name in bpy.context.window.view_layer.objects: + DesiredObj.append(child) + + return DesiredObj + + +def GetSocketDesiredChild(targetObj): + sockets = [] + for obj in GetExportDesiredChilds(targetObj): + if IsASocket(obj): + sockets.append(obj) + + return sockets + + +def GetSkeletalMeshSockets(obj): + if obj is None: + return + + addon_prefs = GetAddonPrefs() + data = {} + sockets = [] + + for socket in GetSocketDesiredChild(obj): + sockets.append(socket) + + if GetAssetType(obj) != "SkeletalMesh": + return + + data['Sockets'] = [] + # config.set('Sockets', '; SocketName, BoneName, Location, Rotation, Scale') + + for i, socket in enumerate(sockets): + if IsASocket(socket): + SocketName = socket.name[7:] + else: + socket.name + + if socket.parent.exportDeformOnly: + b = getFirstDeformBoneParent(socket.parent.data.bones[socket.parent_bone]) + else: + b = socket.parent.data.bones[socket.parent_bone] + + ResetArmaturePose(socket.parent) + # GetRelativePostion + bml = b.matrix_local # Bone + am = socket.parent.matrix_world # Armature + em = socket.matrix_world # Socket + RelativeMatrix = (bml.inverted() @ am.inverted() @ em) + t = RelativeMatrix.to_translation() + r = RelativeMatrix.to_euler() + s = socket.scale*addon_prefs.skeletalSocketsImportedSize + + # Convet to array for Json and convert value for Unreal + array_location = [t[0], t[1]*-1, t[2]] + array_rotation = [math.degrees(r[0]), math.degrees(r[1])*-1, math.degrees(r[2])*-1] + array_scale = [s[0], s[1], s[2]] + + MySocket = {} + MySocket["SocketName"] = SocketName + MySocket["BoneName"] = b.name.replace('.', '_') + MySocket["Location"] = array_location + MySocket["Rotation"] = array_rotation + MySocket["Scale"] = array_scale + data['Sockets'].append(MySocket) + + return data['Sockets'] + + +def GetSubObjectDesiredChild(targetObj): + sub_objects = [] + for obj in GetExportDesiredChilds(targetObj): + if IsASubObject(obj): + sub_objects.append(obj) + + return sub_objects + + +def RemoveAllConsraints(obj): + for b in obj.pose.bones: + for c in b.constraints: + b.constraints.remove(c) + + +def RescaleRigConsraints(obj, scale): + for b in obj.pose.bones: + for c in b.constraints: + # STRETCH_TO + if c.type == "STRETCH_TO": + c.rest_length *= scale # Can be bigger than 10?... wtf + + # LIMIT_LOCATION + if c.type == "LIMIT_LOCATION": + c.min_x *= scale + c.min_y *= scale + c.min_z *= scale + c.max_x *= scale + c.max_y *= scale + c.max_z *= scale + + # LIMIT_DISTANCE + if c.type == "LIMIT_DISTANCE": + c.distance *= scale + + +def RescaleShapeKeysCurve(obj, scale): + if obj.data.shape_keys is None: # Optimisation + return + if obj.data.shape_keys.animation_data is None: + return + if obj.data.shape_keys.animation_data.drivers is None: + return + + for driver in obj.data.shape_keys.animation_data.drivers: + for key in driver.keyframe_points: + key.co[1] *= scale + key.handle_left[1] *= scale + key.handle_right[1] *= scale + + for mod in driver.modifiers: + if mod.type == "GENERATOR": + mod.coefficients[0] *= scale # coef: + + mod.coefficients[1] *= scale # coef: x + + +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 GetCollectionToExport(scene): + colExport = [] + for col in scene.CollectionExportList: + if col.use: + if col.name in bpy.data.collections: + collection = bpy.data.collections[col.name] + colExport.append(collection) + return colExport + + +class CachedAction(): + + ''' + I can't use bpy.types.Scene or bpy.types.Object Property. + "Writing to ID classes in this context is not allowed" + So I use simple python var + ''' + + class ActionFromCache(): + # Info about actions from last cache. + def __init__(self, action): + self.total_action_fcurves_len = len(action.fcurves) + + def __init__(self): + self.name = "" + self.is_cached = False + self.stored_actions = [] + self.total_actions = [] + self.total_rig_bone_len = 0 + + def CheckCache(self, obj): + # Check if the cache need update + if self.name != obj.name: + self.is_cached = False + if len(bpy.data.actions) != len(self.total_actions): + self.is_cached = False + if len(obj.data.bones) != self.total_rig_bone_len: + self.is_cached = False + for action_name in self.stored_actions: + if action_name not in bpy.data.actions: + self.is_cached = False + + return self.is_cached + + def StoreActions(self, obj, actions): + # Update new cache + self.name = obj.name + action_name_list = [] + for action in actions: + action_name_list.append(action.name) + self.stored_actions = action_name_list + self.total_actions.clear() + for action in bpy.data.actions: + self.total_actions.append(self.ActionFromCache(action)) + self.total_rig_bone_len = len(obj.data.bones) + self.is_cached = True + # print("Stored action cache updated.") + + def GetStoredActions(self): + actions = [] + for action_name in self.stored_actions: + if action_name in bpy.data.actions: + actions.append(bpy.data.actions[action_name]) + return actions + + def Clear(self): + pass + + +MyCachedActions = CachedAction() + + +def UpdateActionCache(obj): + # Force update cache export auto action list + return GetCachedExportAutoActionList(obj, True) + + +def GetCachedExportAutoActionList(obj, force_update_cache=False): + # This will cheak if the action contains + # the same bones of the armature + + actions = [] + + # Use the cache + if force_update_cache: + MyCachedActions.is_cached = False + + if MyCachedActions.CheckCache(obj): + actions = MyCachedActions.GetStoredActions() + + else: + MyCachedActions.Clear() + + objBoneNames = [bone.name for bone in obj.data.bones] + for action in bpy.data.actions: + if action.library is None: + if GetIfActionIsAssociated(action, objBoneNames): + actions.append(action) + # Update the cache + MyCachedActions.StoreActions(obj, actions) + return actions + + +def GetActionToExport(obj): + # Returns only the actions that will be exported with the Armature + + if obj.ExportAsLod: + return [] + + TargetActionToExport = [] # Action list + if obj.bfu_anim_action_export_enum == "dont_export": + return [] + + if obj.bfu_anim_action_export_enum == "export_current": + if obj.animation_data is not None: + if obj.animation_data.action is not None: + return [obj.animation_data.action] + + elif obj.bfu_anim_action_export_enum == "export_specific_list": + for action in bpy.data.actions: + for targetAction in obj.exportActionList: + if targetAction.use: + if targetAction.name == action.name: + TargetActionToExport.append(action) + + elif obj.bfu_anim_action_export_enum == "export_specific_prefix": + for action in bpy.data.actions: + if fnmatch.fnmatchcase(action.name, obj.PrefixNameToExport+"*"): + TargetActionToExport.append(action) + + elif obj.bfu_anim_action_export_enum == "export_auto": + TargetActionToExport = GetCachedExportAutoActionList(obj) + + return TargetActionToExport + + +def EvaluateCameraPositionForUnreal(camera, previous_euler=mathutils.Euler()): + # Get Transfrom + matrix_y = Matrix.Rotation(radians(90.0), 4, 'Y') + matrix_x = Matrix.Rotation(radians(-90.0), 4, 'X') + matrix = camera.matrix_world @ matrix_y @ matrix_x + matrix_rotation_offset = Matrix.Rotation(camera.AdditionalRotationForExport.z, 4, 'Z') + loc = matrix.to_translation() * 100 * bpy.context.scene.unit_settings.scale_length + loc += camera.AdditionalLocationForExport + r = matrix.to_euler("XYZ", previous_euler) + s = matrix.to_scale() + + loc *= mathutils.Vector([1, -1, 1]) + array_rotation = [degrees(r[0]), degrees(r[1])*-1, degrees(r[2])*-1] # Roll Pith Yaw XYZ + array_transform = [loc, array_rotation, s] + + # array_location = [loc[0], loc[1]*-1, loc[2]] + # r = mathutils.Euler([degrees(r[0]), degrees(r[1])*-1, degrees(r[2])*-1], r.order) # Roll Pith Yaw XYZ + # array_transform = [array_location, r, s] + + return array_transform + + +def EvaluateCameraRotationForBlender(transform): + x = transform["rotation_x"] + y = transform["rotation_y"]*-1 + z = transform["rotation_z"]*-1 + euler = mathutils.Euler([x, y, z], "XYZ") + return euler + + +def GetDesiredActionStartEndTime(obj, action): + # Returns desired action or camera anim start/end time + # Return start with index 0 and end with index 1 + # EndTime should be a less one frame bigger than StartTime + + scene = bpy.context.scene + if obj.type == "CAMERA": + startTime = scene.frame_start + endTime = scene.frame_end + if endTime <= startTime: + endTime = startTime+1 + return (startTime, endTime) + + elif obj.bfu_anim_action_start_end_time_enum == "with_keyframes": + # GetFirstActionFrame + Offset + startTime = int(action.frame_range.x) + obj.bfu_anim_action_start_frame_offset + # GetLastActionFrame + Offset + endTime = int(action.frame_range.y) + obj.bfu_anim_action_end_frame_offset + if endTime <= startTime: + endTime = startTime+1 + return (startTime, endTime) + + elif obj.bfu_anim_action_start_end_time_enum == "with_sceneframes": + startTime = scene.frame_start + obj.bfu_anim_action_start_frame_offset + endTime = scene.frame_end + obj.bfu_anim_action_end_frame_offset + if endTime <= startTime: + endTime = startTime+1 + return (startTime, endTime) + + elif obj.bfu_anim_action_start_end_time_enum == "with_customframes": + startTime = obj.bfu_anim_action_custom_start_frame + endTime = obj.bfu_anim_action_custom_end_frame + if endTime <= startTime: + endTime = startTime+1 + return (startTime, endTime) + + +def GetDesiredNLAStartEndTime(obj): + # Returns desired nla anim start/end time + # Return start with index 0 and end with index 1 + # EndTime should be a less one frame bigger than StartTime + + scene = bpy.context.scene + + if obj.bfu_anim_nla_start_end_time_enum == "with_sceneframes": + startTime = scene.frame_start + obj.bfu_anim_nla_start_frame_offset + endTime = scene.frame_end + obj.bfu_anim_nla_end_frame_offset + if endTime <= startTime: + endTime = startTime+1 + + return (startTime, endTime) + + elif obj.bfu_anim_nla_start_end_time_enum == "with_customframes": + startTime = obj.bfu_anim_nla_custom_start_frame + endTime = obj.bfu_anim_nla_custom_end_frame + if endTime <= startTime: + endTime = startTime+1 + + return (startTime, endTime) + + +def GetUseCustomLightMapResolution(obj): + if obj.StaticMeshLightMapEnum == "Default": + return False + return True + + +def GetExportRealSurfaceArea(obj): + scene = bpy.context.scene + + local_view_areas = MoveToGlobalView() + bbpl.utils.SafeModeSet('OBJECT') + + SavedSelect = GetCurrentSelection() + SelectParentAndDesiredChilds(obj) + + bpy.ops.object.duplicate() + bpy.ops.object.duplicates_make_real( + use_base_parent=True, + use_hierarchy=True + ) + + ApplyNeededModifierToSelect() + bpy.ops.object.parent_clear(type='CLEAR_KEEP_TRANSFORM') + for selectObj in bpy.context.selected_objects: + # Remove unable to convert mesh + if selectObj.type == "EMPTY" or selectObj.type == "CURVE": + CleanDeleteObjects([selectObj]) + + for selectObj in bpy.context.selected_objects: + # Remove collision box + if CheckIsCollision(selectObj): + CleanDeleteObjects([selectObj]) + + if bpy.context.view_layer.objects.active is None: + # When the active id a empty + bpy.context.view_layer.objects.active = bpy.context.selected_objects[0] + bpy.ops.object.convert(target='MESH') + + active = bpy.context.view_layer.objects.active + + CleanJoinSelect() + active = bpy.context.view_layer.objects.active + area = GetSurfaceArea(active) + CleanDeleteObjects(bpy.context.selected_objects) + SetCurrentSelection(SavedSelect) + MoveToLocalView(local_view_areas) + return area + + +def GetCompuntedLightMap(obj): + if obj.StaticMeshLightMapEnum == "Default": + return -1 + + if obj.StaticMeshLightMapEnum == "CustomMap": + return obj.customStaticMeshLightMapRes + + if obj.StaticMeshLightMapEnum == "SurfaceArea": + # Get the area + area = obj.computedStaticMeshLightMapRes + area **= 0.5 # Adapte for light map + + if obj.useStaticMeshLightMapWorldScale: + # Turn area at world scale + x = max(obj.scale.x, obj.scale.x*-1) + y = max(obj.scale.y, obj.scale.y*-1) + z = max(obj.scale.z, obj.scale.z*-1) + objScale = (x + y + z)/3 + area *= objScale + + # Computed light map equal light map scale for a plane vvv + area *= bpy.context.scene.unit_settings.scale_length + area *= obj.staticMeshLightMapSurfaceScale/2 + if obj.staticMeshLightMapRoundPowerOfTwo: + + return nearestPowerOfTwo(int(round(area))) + return int(round(area)) + + +def GetActionType(action): + # return action type + + if action.frame_range.y - action.frame_range.x == 1: + return "Pose" + return "Action" + + +def GetCollectionType(collection): + # return collection type + + return "Collection StaticMesh" + + +def GetIsAnimation(type): + # return True if type(string) is a animation + if (type == "NlAnim" or type == "Action" or type == "Pose"): + return True + return False + + +def GetAssetType(obj): + # Return asset type of a object + + if obj.type == "CAMERA": + return "Camera" + + if obj.ExportAsAlembic: + return "Alembic" + + if obj.type == "ARMATURE" and not obj.ForceStaticMesh: + return "SkeletalMesh" + + return "StaticMesh" + + +def CheckIsCollision(target): + # Return true if obj is a collision + for obj in GetAllCollisionObj(): + if obj == target: + return True + return False + + +def SelectCollectionObjects(collection): + # Selects only all objects that must be exported in a collection + selectedObjs = [] + bpy.ops.object.select_all(action='DESELECT') + for selectObj in collection.all_objects: + if selectObj.ExportEnum != "dont_export": + if selectObj.name in bpy.context.view_layer.objects: + selectObj.select_set(True) + selectedObjs.append(selectObj) + + if len(selectedObjs) > 0: + if selectedObjs[0].name in bpy.context.view_layer.objects: + bpy.context.view_layer.objects.active = selectedObjs[0] + + return selectedObjs + + +def GetExportAsProxy(obj): + if GetObjProxyChild(obj): + return True + + if obj.data: + if obj.data.library: + return True + return False + + +def GetExportProxyChild(obj): + + if GetObjProxyChild(obj): + return GetObjProxyChild(obj) + + scene = bpy.context.scene + if obj.data: + if obj.data.library: + for child_obj in scene.objects: + if child_obj != obj: + if child_obj.instance_collection: + if child_obj.instance_collection.library: + if child_obj.instance_collection.library == obj.data.library: + return child_obj + return None + + +def SelectParentAndDesiredChilds(obj): + # Selects only all child objects that must be exported with parent object + selectedObjs = [] + bpy.ops.object.select_all(action='DESELECT') + for selectObj in GetExportDesiredChilds(obj): + if selectObj.name in bpy.context.view_layer.objects: + if GetAssetType(obj) == "SkeletalMesh": + # With skeletal mesh the socket must be not exported, + # ue4 read it like a bone + if not fnmatch.fnmatchcase(selectObj.name, "SOCKET*"): + selectObj.select_set(True) + selectedObjs.append(selectObj) + else: + selectObj.select_set(True) + selectedObjs.append(selectObj) + + if obj.name in bpy.context.view_layer.objects: + obj.select_set(True) + + if GetExportAsProxy(obj): + proxy_child = GetExportProxyChild(obj) + if proxy_child is not None: + proxy_child.select_set(True) + + selectedObjs.append(obj) + if obj.name in bpy.context.view_layer.objects: + bpy.context.view_layer.objects.active = obj + return selectedObjs + + +def RemoveSocketFromSelectForProxyArmature(): + select = bbpl.utils.UserSelectSave() + select.SaveCurrentSelect() + # With skeletal mesh the socket must be not exported, + # ue4 read it like a bone + sockets = [] + for obj in bpy.context.selected_objects: + if fnmatch.fnmatchcase(obj.name, "SOCKET*"): + sockets.append(obj) + CleanDeleteObjects(sockets) + select.ResetSelectByName() + + +def GoToMeshEditMode(): + for obj in bpy.context.selected_objects: + if obj.type == "MESH": + bpy.context.view_layer.objects.active = obj + bbpl.utils.SafeModeSet('EDIT') + + return True + return False + + +def ApplyNeededModifierToSelect(): + + SavedSelect = GetCurrentSelection() + for obj in bpy.context.selected_objects: + if obj.type == "MESH": + SelectSpecificObject(obj) + for mod in [m for m in obj.modifiers if m.type != 'ARMATURE']: + if obj.data.shape_keys is None: + if obj.data.users > 1: + obj.data = obj.data.copy() + if bpy.ops.object.modifier_apply.poll(): + try: + bpy.ops.object.modifier_apply(modifier=mod.name) + except RuntimeError as ex: + # print the error incase its important... but continue + print(ex) + + SetCurrentSelection(SavedSelect) + + +def CorrectExtremeUV(stepScale=2): + + def GetHaveConnectedLoop(faceTarget): + # In bmesh faces + for loop in faceTarget.loops: + uv = loop[uv_lay].uv + for face in bm.faces: + if face.select: + if faceTarget != face: + for loop in face.loops: + if uv == loop[uv_lay].uv: + return True + return False + + def SelectRecursiveUVLinked(uv_lay): + + AddedFaces = [] + for v in [v for v in bm.verts if v.select]: + for f in v.link_faces: + if not f.select: + if GetHaveConnectedLoop(f): + AddedFaces.append(f) + f.select = True + + if len(AddedFaces) == 0: + return AddedFaces + else: + for addedFace in SelectRecursiveUVLinked(uv_lay): + AddedFaces.append(addedFace) + return AddedFaces + + def GetAllIsland(bm, uv_lay): + ToCheakFace = [] + Islands = [] + for face in bm.faces: + ToCheakFace.append(face) + + while len(ToCheakFace) > 0: + for face in bm.faces: + face.select = False + + ToCheakFace[-1].select = True + SelectRecursiveUVLinked(uv_lay) + + Island = [] + for face in bm.faces: + if face.select: + Island.append(face) + if face in ToCheakFace: + ToCheakFace.remove(face) + Islands.append(Island) + + return Islands + + def MoveItlandToCenter(faces, uv_lay, minDistance): + loop = faces[-1].loops[-1] + + x = round(loop[uv_lay].uv[0]/minDistance, 0)*minDistance + y = round(loop[uv_lay].uv[1]/minDistance, 0)*minDistance + + for face in faces: + for loop in face.loops: + loop[uv_lay].uv[0] -= x + loop[uv_lay].uv[1] -= y + + def IsValidForUvEdit(obj): + if obj.type == "MESH": + return True + return False + + for obj in bpy.context.selected_objects: + if IsValidForUvEdit(obj): + bm = bmesh.from_edit_mesh(obj.data) + + uv_lay = bm.loops.layers.uv.active + if uv_lay is None: + return + + for faces in GetAllIsland(bm, uv_lay): + uv_lay = bm.loops.layers.uv.active + MoveItlandToCenter(faces, uv_lay, stepScale) + + obj.data.update() + + +def ApplyExportTransform(obj, use_type="Object"): + + newMatrix = obj.matrix_world @ mathutils.Matrix.Translation((0, 0, 0)) + saveScale = obj.scale * 1 + + # Ref + # Moves object to the center of the scene for export + if use_type == "Object": + MoveToCenter = obj.MoveToCenterForExport + RotateToZero = obj.RotateToZeroForExport + + elif use_type == "Action": + MoveToCenter = obj.MoveActionToCenterForExport + RotateToZero = obj.RotateActionToZeroForExport + + elif use_type == "NLA": + MoveToCenter = obj.MoveNLAToCenterForExport + RotateToZero = obj.RotateNLAToZeroForExport + + else: + return + + if MoveToCenter: + mat_trans = mathutils.Matrix.Translation((0, 0, 0)) + mat_rot = newMatrix.to_quaternion().to_matrix() + newMatrix = mat_trans @ mat_rot.to_4x4() + + obj.matrix_world = newMatrix + # Turn object to the center of the scene for export + if RotateToZero: + mat_trans = mathutils.Matrix.Translation(newMatrix.to_translation()) + mat_rot = mathutils.Matrix.Rotation(0, 4, 'X') + newMatrix = mat_trans @ mat_rot + + eul = obj.AdditionalRotationForExport + loc = obj.AdditionalLocationForExport + + mat_rot = eul.to_matrix() + mat_loc = mathutils.Matrix.Translation(loc) + AddMat = mat_loc @ mat_rot.to_4x4() + + obj.matrix_world = newMatrix @ AddMat + obj.scale = saveScale + + +def ApplySkeletalExportScale(armature, rescale, target_animation_data=None, is_a_proxy=False): + + # This function will rescale the armature and applys the new scale + + armature.scale = armature.scale*rescale + # Save armature location + old_location = armature.location.copy() + + if target_animation_data is None: + armature_animation_data = bbpl.anim_utils.AnimationManagment() + armature_animation_data.SaveAnimationData(armature) + armature_animation_data.ClearAnimationData(armature) + else: + armature_animation_data = bbpl.anim_utils.AnimationManagment() + armature_animation_data.ClearAnimationData(armature) + + armature.location = (0, 0, 0) + + # Save childs location + ChildsLocation = [] + for Child in GetChilds(armature): + ChildsLocation.append([Child, Child.location.copy(), Child.matrix_parent_inverse.copy()]) + + if is_a_proxy: + selection = GetCurrentSelection() + bpy.ops.object.select_all(action='DESELECT') + armature.select_set(True) + + bpy.ops.object.transform_apply( + location=True, + scale=True, + rotation=True, + properties=True + ) + + if is_a_proxy: + SetCurrentSelection(selection) + + # Apply armature location + armature.location = old_location*rescale + + # Apply childs location + # I need work with matrix ChildLocation[0].matrix_parent_inverse + # But I don't understand how make it work. + for ChildLocation in ChildsLocation: + pass + + if target_animation_data is None: + armature_animation_data.SetAnimationData(armature, True) + else: + target_animation_data.SetAnimationData(armature, True) + + +def RescaleSelectCurveHook(scale): + + def GetRescaledMatrix(matrix, scale): + newMatrix = matrix.copy() + + newMatrix[0][0] *= 1 # Fix + newMatrix[0][1] *= 1 + newMatrix[0][2] *= 1 + newMatrix[0][3] *= scale + # --- + newMatrix[1][0] *= 1 + newMatrix[1][1] *= 1 # Fix + newMatrix[1][2] *= 1 # Fix + newMatrix[1][3] *= scale + # --- + newMatrix[2][0] *= 1 + newMatrix[2][1] *= 1 # Fix + newMatrix[2][2] *= 1 # Fix + newMatrix[2][3] *= scale + # --- + newMatrix[3][0] *= 1 + newMatrix[3][1] *= 1 + newMatrix[3][2] *= 1 + newMatrix[3][3] *= 1 # Fix + + return newMatrix + + for obj in bpy.context.selected_objects: + if obj.type == "CURVE": + for mod in obj.modifiers: + if mod.type == "HOOK": + scale_factor = 100 + mod.matrix_inverse = GetRescaledMatrix( + mod.matrix_inverse, + scale_factor + ) + for spline in obj.data.splines: + for bezier_point in spline.bezier_points: + bezier_point.radius *= scale + + +def RescaleActionCurve(action, scale): + for fcurve in action.fcurves: + if fcurve.data_path.split(".")[-1] == "location": + for key in fcurve.keyframe_points: + key.co[1] *= scale + key.handle_left[1] *= scale + key.handle_right[1] *= scale + + # Modifier + for mod in fcurve.modifiers: + if mod.type == "NOISE": + mod.strength *= scale + + +def RescaleAllActionCurve(bone_scale, scene_scale=1): + for action in bpy.data.actions: + print(action.name) + for fcurve in action.fcurves: + if fcurve.data_path == "location": + # Curve + for key in fcurve.keyframe_points: + key.co[1] *= scene_scale + key.handle_left[1] *= scene_scale + key.handle_right[1] *= scene_scale + + # Modifier + for mod in fcurve.modifiers: + if mod.type == "NOISE": + mod.strength *= scene_scale + + elif fcurve.data_path.split(".")[-1] == "location": + + # Curve + for key in fcurve.keyframe_points: + key.co[1] *= bone_scale + key.handle_left[1] *= bone_scale + key.handle_right[1] *= bone_scale + + # Modifier + for mod in fcurve.modifiers: + if mod.type == "NOISE": + mod.strength *= bone_scale + + +def GetFinalAssetToExport(): + # Returns all assets that will be exported + + def getHaveParentToExport(obj): + if obj.parent is not None: + if obj.parent.ExportEnum == 'export_recursive': + return obj.parent + else: + return getHaveParentToExport(obj.parent) + else: + return None + + scene = bpy.context.scene + export_filter = scene.bfu_export_selection_filter + + TargetAssetToExport = [] # Obj, Action, type + + class AssetToExport: + def __init__(self, obj, action, type): + self.obj = obj + self.action = action + self.type = type + + objList = [] + collectionList = [] + + if export_filter == "default": + objList = GetAllobjectsByExportType("export_recursive") + for col in GetCollectionToExport(scene): + collectionList.append(col.name) + + elif export_filter == "only_object" or export_filter == "only_object_action": + recuList = GetAllobjectsByExportType("export_recursive") + + for obj in bpy.context.selected_objects: + if obj in recuList: + if obj not in objList: + objList.append(obj) + parentTarget = getHaveParentToExport(obj) + if parentTarget is not None: + if parentTarget not in objList: + objList.append(parentTarget) + + for collection in collectionList: + # Collection + if scene.static_collection_export: + TargetAssetToExport.append(AssetToExport( + collection, + None, + "Collection StaticMesh")) + + for obj in objList: + if GetAssetType(obj) == "Alembic": + # Alembic + if scene.alembic_export: + TargetAssetToExport.append(AssetToExport( + obj, + None, + "Alembic")) + + if GetAssetType(obj) == "SkeletalMesh": + + # SkeletalMesh + if scene.skeletal_export: + TargetAssetToExport.append(AssetToExport( + obj, + None, + "SkeletalMesh")) + + # NLA + if scene.anin_export: + if obj.bfu_anim_nla_use: + TargetAssetToExport.append(AssetToExport( + obj, + obj.animation_data, + "NlAnim")) + + for action in GetActionToExport(obj): + if scene.bfu_export_selection_filter == "only_object_action": + if obj.animation_data: + if obj.animation_data.action == action: + TargetAssetToExport.append(AssetToExport(obj, action, "Action")) + else: + # Action + if scene.anin_export: + if GetActionType(action) == "Action": + TargetAssetToExport.append(AssetToExport(obj, action, "Action")) + + # Pose + if scene.anin_export: + if GetActionType(action) == "Pose": + TargetAssetToExport.append(AssetToExport(obj, action, "Pose")) + # Camera + if GetAssetType(obj) == "Camera" and scene.camera_export: + TargetAssetToExport.append(AssetToExport( + obj, + None, + "Camera")) + + # StaticMesh + if GetAssetType(obj) == "StaticMesh" and scene.static_export: + TargetAssetToExport.append(AssetToExport( + obj, + None, + "StaticMesh")) + + return TargetAssetToExport + + +def ValidFilenameForUnreal(filename): + # valid file name for unreal assets + extension = os.path.splitext(filename)[1] + newfilename = ValidFilename(os.path.splitext(filename)[0]) + return (''.join(c for c in newfilename if c != ".")+extension) + + +def ValidUnrealAssetsName(filename): + # Normalizes string, removes non-alpha characters + # Asset name in Unreal use + + filename = filename.replace('.', '_') + filename = filename.replace('(', '_') + filename = filename.replace(')', '_') + filename = filename.replace(' ', '_') + valid_chars = "-_%s%s" % (string.ascii_letters, string.digits) + filename = ''.join(c for c in filename if c in valid_chars) + return filename + + +def GetCollectionExportDir(col, abspath=False): + # Generate assset folder path + scene = bpy.context.scene + + dirpath = os.path.join( + scene.export_static_file_path, + col.exportFolderName) + + if abspath: + return bpy.path.abspath(dirpath) + else: + return dirpath + + +def GetObjExportName(obj): + # Return Proxy Name for Proxy and Object Name for other + if GetAssetType(obj) == "SkeletalMesh": + if GetExportAsProxy(obj): + proxy_child = GetExportProxyChild(obj) + if proxy_child is not None: + return proxy_child.name + return ValidFilename(obj.name) + + +def GetObjExportDir(obj, abspath=False): + # Generate assset folder path + folder_name = ValidDirName(obj.exportFolderName) + obj_name = ValidDirName(obj.name) # Fix obj name + + scene = bpy.context.scene + if GetAssetType(obj) == "SkeletalMesh": + dirpath = os.path.join( + scene.export_skeletal_file_path, + folder_name, + GetObjExportName(obj)) + if GetAssetType(obj) == "Alembic": + dirpath = os.path.join( + scene.export_alembic_file_path, + folder_name, + obj_name) + if GetAssetType(obj) == "StaticMesh": + dirpath = os.path.join( + scene.export_static_file_path, + folder_name) + if GetAssetType(obj) == "Camera": + dirpath = os.path.join( + scene.export_camera_file_path, + folder_name) + if abspath: + return bpy.path.abspath(dirpath) + + else: + return dirpath + + +def GetCollectionExportFileName(collection, fileType=".fbx"): + # Generate assset file name + + scene = bpy.context.scene + return scene.static_mesh_prefix_export_name+collection+fileType + + +def GetObjExportFileName(obj, fileType=".fbx"): + # Generate assset file name + + scene = bpy.context.scene + if obj.bfu_use_custom_export_name: + return ValidFilename(obj.bfu_custom_export_name+fileType) + assetType = GetAssetType(obj) + if assetType == "Camera": + return ValidFilename(scene.camera_prefix_export_name+obj.name+fileType) + elif assetType == "StaticMesh": + return ValidFilename(scene.static_mesh_prefix_export_name+obj.name+fileType) + elif assetType == "SkeletalMesh": + return ValidFilename(scene.skeletal_mesh_prefix_export_name+obj.name+fileType) + elif assetType == "Alembic": + return ValidFilename(scene.alembic_prefix_export_name+obj.name+fileType) + else: + return None + + +def GetActionExportFileName(obj, action, fileType=".fbx"): + # Generate action file name + + scene = bpy.context.scene + if obj.bfu_anim_naming_type == "include_armature_name": + ArmatureName = obj.name+"_" + if obj.bfu_anim_naming_type == "action_name": + ArmatureName = "" + if obj.bfu_anim_naming_type == "include_custom_name": + ArmatureName = obj.bfu_anim_naming_custom+"_" + + animType = GetActionType(action) + if animType == "NlAnim" or animType == "Action": + # Nla can be exported as action + return ValidFilename(scene.anim_prefix_export_name+ArmatureName+action.name+fileType) + + elif animType == "Pose": + return ValidFilename(scene.pose_prefix_export_name+ArmatureName+action.name+fileType) + + else: + return None + + +def GetNLAExportFileName(obj, fileType=".fbx"): + # Generate action file name + + scene = bpy.context.scene + if obj.bfu_anim_naming_type == "include_armature_name": + ArmatureName = obj.name+"_" + if obj.bfu_anim_naming_type == "action_name": + ArmatureName = "" + if obj.bfu_anim_naming_type == "include_custom_name": + ArmatureName = obj.bfu_anim_naming_custom+"_" + + return ValidFilename(scene.anim_prefix_export_name+ArmatureName+obj.bfu_anim_nla_export_name+fileType) + + +def GetImportAssetScriptCommand(): + scene = bpy.context.scene + fileName = scene.file_import_asset_script_name + absdirpath = bpy.path.abspath(scene.export_other_file_path) + fullpath = os.path.join(absdirpath, fileName) + addon_prefs = GetAddonPrefs() + return 'py "'+fullpath+'"' + + +def GetImportCameraScriptCommand(objs, CineCamera=True): + # Return (success, command) + + success = False + command = "" + report = "" + add_camera_num = 0 + + def AddCameraToCommand(camera): + if camera.type == "CAMERA": + t = "" + # Get Camera Data + scene = bpy.context.scene + frame_current = scene.frame_current + + # First I get the camera data. + # This is a very bad way to do this. I need do a new python file specific to camera with class to get data. + data = bfu_write_text.WriteCameraAnimationTracks(camera, frame_current, frame_current) + transform_track = data["Camera transform"][frame_current] + location_x = transform_track["location_x"] + location_y = transform_track["location_y"] + location_z = transform_track["location_z"] + rotation_x = transform_track["rotation_x"] + rotation_y = transform_track["rotation_y"] + rotation_z = transform_track["rotation_z"] + scale_x = transform_track["scale_x"] + scale_y = transform_track["scale_y"] + scale_z = transform_track["scale_z"] + NearClippingPlane = data["Camera NearClippingPlane"][frame_current] + FarClippingPlane = data["Camera FarClippingPlane"][frame_current] + FieldOfView = data["Camera FieldOfView"][frame_current] + FocalLength = data["Camera FocalLength"][frame_current] + SensorWidth = data["Camera SensorWidth"][frame_current] + SensorHeight = data["Camera SensorHeight"][frame_current] + FocusDistance = data["Camera FocusDistance"][frame_current] + Aperture = data["Camera Aperture"][frame_current] + AspectRatio = data["desired_screen_ratio"] + CameraName = camera.name + + # Actor + if CineCamera: + t += " " + "Begin Actor Class=/Script/CinematicCamera.CineCameraActor Name="+CameraName+" Archetype=/Script/CinematicCamera.CineCameraActor'/Script/CinematicCamera.Default__CineCameraActor'" + "\n" + else: + t += " " + "Begin Actor Class=/Script/Engine.CameraActor Name="+CameraName+" Archetype=/Script/Engine.CameraActor'/Script/Engine.Default__CameraActor'" + "\n" + + # Init SceneComponent + if CineCamera: + t += " " + "Begin Object Class=/Script/Engine.SceneComponent Name=\"SceneComponent\" Archetype=/Script/Engine.SceneComponent'/Script/CinematicCamera.Default__CineCameraActor:SceneComponent'" + "\n" + t += " " + "End Object" + "\n" + else: + t += " " + "Begin Object Class=/Script/Engine.SceneComponent Name=\"SceneComponent\" Archetype=/Script/Engine.SceneComponent'/Script/Engine.Default__CameraActor:SceneComponent'" + "\n" + t += " " + "End Object" + "\n" + + # Init CameraComponent + if CineCamera: + t += " " + "Begin Object Class=/Script/CinematicCamera.CineCameraComponent Name=\"CameraComponent\" Archetype=/Script/CinematicCamera.CineCameraComponent'/Script/CinematicCamera.Default__CineCameraActor:CameraComponent'" + "\n" + t += " " + "End Object" + "\n" + else: + t += " " + "Begin Object Class=/Script/Engine.CameraComponent Name=\"CameraComponent\" Archetype=/Script/Engine.CameraComponent'/Script/Engine.Default__CameraActor:CameraComponent'" + "\n" + t += " " + "End Object" + "\n" + + # SceneComponent + t += " " + "Begin Object Name=\"SceneComponent\"" + "\n" + t += " " + "RelativeLocation=(X="+str(location_x)+",Y="+str(location_y)+",Z="+str(location_z)+")" + "\n" + t += " " + "RelativeRotation=(Pitch="+str(rotation_y)+",Yaw="+str(rotation_z)+",Roll="+str(rotation_x)+")" + "\n" + t += " " + "RelativeScale3D=(X="+str(scale_x)+",Y="+str(scale_y)+",Z="+str(scale_z)+")" + "\n" + t += " " + "End Object" + "\n" + + # CameraComponent + t += " " + "Begin Object Name=\"CameraComponent\"" + "\n" + t += " " + "Filmback=(SensorWidth="+str(SensorWidth)+",SensorHeight="+str(SensorHeight)+", SensorAspectRatio="+str(AspectRatio)+")" + "\n" + t += " " + "CurrentAperture="+str(Aperture)+")" + "\n" + t += " " + "CurrentFocalLength="+str(FocalLength)+")" + "\n" + t += " " + "CurrentFocusDistance="+str(FocusDistance)+")" + "\n" + t += " " + "CurrentFocusDistance="+str(FocusDistance)+")" + "\n" + t += " " + "CustomNearClippingPlane="+str(NearClippingPlane)+")" + "\n" + t += " " + "FieldOfView="+str(FieldOfView)+")" + "\n" + t += " " + "AspectRatio="+str(AspectRatio)+")" + "\n" + t += " " + "End Object" + "\n" + + # Attach + t += " " + "CameraComponent=\"CameraComponent\"" + "\n" + t += " " + "SceneComponent=\"SceneComponent\"" + "\n" + t += " " + "RootComponent=\"SceneComponent\"" + "\n" + t += " " + "ActorLabel=\""+CameraName+"\"" + "\n" + + # Close + t += " " + "End Actor" + "\n" + return t + return None + + cameras = [] + for obj in objs: + if obj.type == "CAMERA": + cameras.append(obj) + + if len(cameras) == 0: + report = "Please select at least one camera." + return (success, command, report) + + # And I apply the camrta data to the copy paste text. + t = "Begin Map" + "\n" + t += " " + "Begin Level" + "\n" + for camera in cameras: + add_command = AddCameraToCommand(camera) + if add_command: + t += add_command + add_camera_num += 1 + + t += " " + "End Level" + "\n" + t += "Begin Surface" + "\n" + t += "End Surface" + "\n" + t += "End Object" + "\n" + + success = True + command = t + if CineCamera: + report = str(add_camera_num)+" Cine camera(s) copied. Paste in Unreal Engine scene for import the camera. (Ctrl+V)" + else: + report = str(add_camera_num)+" Regular camera(s) copied. Paste in Unreal Engine scene for import the camera. (Ctrl+V)" + + return (success, command, report) + + +def GetImportSkeletalMeshSocketScriptCommand(obj): + + if obj: + if obj.type == "ARMATURE": + sockets = GetSkeletalMeshSockets(obj) + t = "SocketCopyPasteBuffer" + "\n" + t += "NumSockets=" + str(len(sockets)) + "\n" + t += "IsOnSkeleton=1" + "\n" + for socket in sockets: + t += "Begin Object Class=/Script/Engine.SkeletalMeshSocket" + "\n" + t += "\t" + 'SocketName="' + socket["SocketName"] + '"' + "\n" + t += "\t" + 'BoneName="' + socket["BoneName"] + '"' + "\n" + loc = socket["Location"] + r = socket["Rotation"] + s = socket["Scale"] + t += "\t" + 'RelativeLocation=' + "(X="+str(loc[0])+",Y="+str(loc[1])+",Z="+str(loc[2])+")" + "\n" + t += "\t" + 'RelativeRotation=' + "(Pitch="+str(r[1])+",Yaw="+str(r[2])+",Roll="+str(r[0])+")" + "\n" + t += "\t" + 'RelativeScale=' + "(X="+str(s[0])+",Y="+str(s[1])+",Z="+str(s[2])+")" + "\n" + t += "End Object" + "\n" + return t + return "Please select an armature." + + +def GetImportSequencerScriptCommand(): + scene = bpy.context.scene + fileName = scene.file_import_sequencer_script_name + absdirpath = bpy.path.abspath(scene.export_other_file_path) + fullpath = os.path.join(absdirpath, fileName) + + addon_prefs = GetAddonPrefs() + return 'py "'+fullpath+'"' # Vania + + +def GetAnimSample(obj): + # return obj sample animation + return obj.SampleAnimForExport + + +def GetArmatureRootBones(obj): + rootBones = [] + if GetAssetType(obj) == "SkeletalMesh": + + if not obj.exportDeformOnly: + for bone in obj.data.bones: + if bone.parent is None: + rootBones.append(bone) + + if obj.exportDeformOnly: + for bone in obj.data.bones: + if bone.use_deform: + rootBone = getRootBoneParent(bone) + if rootBone not in rootBones: + rootBones.append(rootBone) + return rootBones + + +def GetDesiredExportArmatureName(obj): + addon_prefs = GetAddonPrefs() + single_root = len(GetArmatureRootBones(obj)) == 1 + if addon_prefs.add_skeleton_root_bone or single_root != 1: + return addon_prefs.skeleton_root_bone_name + return "Armature" + + +def GetObjExportScale(obj): + + return obj.exportGlobalScale + + +def GenerateUe4Name(name): + # Generate a new name with suffix number + + def IsValidName(testedName): + # Checks if objet end with number suffix + + if (testedName.split("_")[-1]).isnumeric(): + number = int(testedName.split("_")[-1]) + else: + # Last suffix is not a number + return False + + # 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 + + newName = "" + if IsValidName(name): + return name + else: + for num in range(0, 1000): + newName = name+"_"+str('%02d' % num) # Min two pad + if IsValidName(newName): + return newName + + return name + + +def CreateCollisionMaterial(): + addon_prefs = GetAddonPrefs() + + mat = bpy.data.materials.get("UE4Collision") + if mat is None: + mat = bpy.data.materials.new(name="UE4Collision") + + mat.diffuse_color = addon_prefs.collisionColor + 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]) + return mat + + +def Ue4SubObj_set(SubType): + # Convect obj to ue4 sub objects + # (Collisions Shapes or Socket) + + def DeselectAllWithoutActive(): + for obj in bpy.context.selected_objects: + if obj != bpy.context.active_object: + obj.select_set(False) + + ownerObj = bpy.context.active_object + ownerBone = bpy.context.active_pose_bone + objList = bpy.context.selected_objects + if ownerObj is None: + return [] + + ConvertedObjs = [] + + for obj in objList: + DeselectAllWithoutActive() + obj.select_set(True) + if obj != ownerObj: + + # SkeletalMesh Colider + if obj.type == 'MESH': + ConvertToConvexHull(obj) + obj.modifiers.clear() + obj.data + obj.data.materials.clear() + obj.active_material_index = 0 + obj.data.materials.append(CreateCollisionMaterial()) + + # Set the name of the Prefix depending on the + # type of collision in agreement with unreal FBX Pipeline + if SubType == "Box": + prefixName = "UBX_" + elif SubType == "Capsule": + prefixName = "UCP_" + elif SubType == "Sphere": + prefixName = "USP_" + elif SubType == "Convex": + prefixName = "UCX_" + + obj.name = GenerateUe4Name(prefixName+ownerObj.name) + obj.show_wire = True + obj.show_transparent = True + bpy.ops.object.parent_set(type='OBJECT', keep_transform=True) + ConvertedObjs.append(obj) + + # StaticMesh Socket + if obj.type == 'EMPTY' and SubType == "ST_Socket": + if ownerObj.type == 'MESH': + if not IsASocket(obj): + obj.name = GenerateUe4Name("SOCKET_"+obj.name) + bpy.ops.object.parent_set( + type='OBJECT', + keep_transform=True) + ConvertedObjs.append(obj) + + # SkeletalMesh Socket + if obj.type == 'EMPTY' and SubType == "SK_Socket": + if ownerObj.type == 'ARMATURE': + + if not IsASocket(obj): + obj.name = GenerateUe4Name("SOCKET_"+obj.name) + bpy.ops.object.parent_set(type='BONE') + ConvertedObjs.append(obj) + + DeselectAllWithoutActive() + for obj in objList: + obj.select_set(True) # Resets previous selected object + return ConvertedObjs + + +def UpdateUe4Name(SubType, objList): + # Convect obj to ue4 sub objects (Collisions Shapes or Socket) + + for obj in objList: + ownerObj = obj.parent + + if ownerObj is not None: + if obj != ownerObj: + + # SkeletalMesh Colider + if obj.type == 'MESH': + + # Set the name of the Prefix depending + # on the type of collision in agreement + # with unreal FBX Pipeline + + if SubType == "Box": + prefixName = "UBX_" + elif SubType == "Capsule": + prefixName = "UCP_" + elif SubType == "Sphere": + prefixName = "USP_" + elif SubType == "Convex": + prefixName = "UCX_" + + obj.name = GenerateUe4Name(prefixName+ownerObj.name) + + # StaticMesh Socket + if obj.type == 'EMPTY' and SubType == "ST_Socket": + if ownerObj.type == 'MESH': + if not IsASocket(obj): + obj.name = GenerateUe4Name("SOCKET_"+obj.name) + + # SkeletalMesh Socket + if obj.type == 'EMPTY' and SubType == "SK_Socket": + if ownerObj.type == 'ARMATURE': + if not IsASocket(obj): + obj.name = GenerateUe4Name("SOCKET_"+obj.name) + + +def IsASocket(obj): + ''' + Retrun True is object is an Socket. + https://docs.unrealengine.com/en-US/WorkingWithContent/Importing/FBX/StaticMeshes/#sockets + ''' + if obj.type == "EMPTY": + cap_name = obj.name.upper() + if cap_name.startswith("SOCKET_"): + return True + + return False + + +def IsACollision(obj): + ''' + Retrun True is object is an Collision. + https://docs.unrealengine.com/en-US/WorkingWithContent/Importing/FBX/StaticMeshes/#collision + ''' + if obj.type == "MESH": + cap_name = obj.name.upper() + if cap_name.startswith("UBX_"): + return True + elif cap_name.startswith("UCP_"): + return True + elif cap_name.startswith("USP_"): + return True + elif cap_name.startswith("UCX_"): + return True + + return False + + +def IsASubObject(obj): + ''' + Retrun True is object is an Socket or and Collision. + ''' + if IsASocket(obj) or IsACollision(obj): + return True + return False + + +def UpdateAreaLightMapList(list=None): + # Updates area LightMap + + if list is not None: + objs = list + else: + objs = [] + exportObjs = GetAllobjectsByExportType("export_recursive") + for exportObj in exportObjs: + if GetAssetType(exportObj) == "StaticMesh": + objs.append(exportObj) + + UpdatedRes = 0 + + counter = CounterTimer() + for obj in objs: + obj.computedStaticMeshLightMapRes = GetExportRealSurfaceArea(obj) + UpdatedRes += 1 + UpdateProgress( + "Update LightMap", + (UpdatedRes/len(objs)), + counter.GetTime()) + return UpdatedRes + + +def AddFrontEachLine(ImportScript, text="\t"): + + NewImportScript = "" + text_splited = ImportScript.split('\n') + for line in text_splited: + NewImportScript += text + line + "\n" + + return NewImportScript + + +# Custom property + + +def SetVarOnObject(obj, VarName, Value): + obj[VarName] = Value + + +def GetVarOnObject(obj, VarName): + return obj[VarName] + + +def HasVarOnObject(obj, VarName): + return VarName in obj + + +def ClearVarOnObject(obj, VarName): + if VarName in obj: + del obj[VarName] + + +def SaveObjCurrentName(obj): + # Save object current name as Custom property + SetVarOnObject(obj, "BFU_OriginName", obj.name) + + +def GetObjOriginName(obj): + return GetVarOnObject(obj, "BFU_OriginName") + + +def ClearObjOriginNameVar(obj): + ClearVarOnObject(obj, "BFU_OriginName") + + +def SetObjProxyData(obj): + # Save object proxy info as Custom property + SetVarOnObject(obj, "BFU_ExportAsProxy", GetExportAsProxy(obj)) + SetVarOnObject(obj, "BFU_ExportProxyChild", GetExportProxyChild(obj)) + + +def GetObjProxyChild(obj): + if (not HasVarOnObject(obj, "BFU_ExportAsProxy")): + return False + + if (not HasVarOnObject(obj, "BFU_ExportProxyChild")): + return False + + if GetVarOnObject(obj, "BFU_ExportAsProxy"): + return GetVarOnObject(obj, "BFU_ExportProxyChild") + return None + + +def ClearObjProxyDataVars(obj): + ClearVarOnObject(obj, "BFU_ExportAsProxy") + ClearVarOnObject(obj, "BFU_ExportProxyChild") + + +def ClearAllBFUTempVars(obj): + ClearVarOnObject(obj, "BFU_OriginName") + ClearVarOnObject(obj, "BFU_ExportAsProxy") + ClearVarOnObject(obj, "BFU_ExportProxyChild") diff --git a/blender-for-unrealengine/export/bfu_export_get_info.py b/blender-for-unrealengine/export/bfu_export_get_info.py index 48a54772..5cf948b5 100644 --- a/blender-for-unrealengine/export/bfu_export_get_info.py +++ b/blender-for-unrealengine/export/bfu_export_get_info.py @@ -1,118 +1,118 @@ -# ====================== 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 .. import bfu_write_text -from .. import bfu_basics -from ..bfu_basics import * -from .. import bfu_utils -from ..bfu_utils import * -from enum import Enum - -from ..bbpl import utils - -import importlib -if "bfu_write_text" in locals(): - importlib.reload(bfu_write_text) -if "bfu_basics" in locals(): - importlib.reload(bfu_basics) -if "bfu_utils" in locals(): - importlib.reload(bfu_utils) - - -class VertexColorExportData: - def __init__(self, obj, parent=None): - self.obj = obj - self.parent = parent - self.export_type = "IGNORE" - self.name = "" - self.color = (1.0, 1.0, 1.0) - self.index = -1 - - if self.GetPropertyOwner(): - if self.GetPropertyOwner().VertexColorImportOption == "IGNORE": - self.export_type = "IGNORE" - - elif self.GetPropertyOwner().VertexColorImportOption == "OVERRIDE": - self.color = self.GetPropertyOwner().VertexOverrideColor - self.export_type = "OVERRIDE" - - elif self.GetPropertyOwner().VertexColorImportOption == "REPLACE": - index = self.GetChosenVertexIndex() - print(index) - if index != -1: - self.index = index - self.name = self.GetChosenVertexName() - self.export_type = "REPLACE" - - def GetPropertyOwner(self): - # Return the object to use for the property or return self if none - if self.parent: - return self.parent - return self.obj - - def GetChosenVertexIndex(self): - - obj = self.obj - if obj.type != "MESH": - return -1 - - VertexColorToUse = self.GetPropertyOwner().VertexColorToUse - VertexColorIndexToUse = self.GetPropertyOwner().VertexColorIndexToUse - - if obj: - if obj.data: - vertex_colors = utils.getVertexColors(obj) - if len(vertex_colors) > 0: - - if VertexColorToUse == "FirstIndex": - return 0 - - if VertexColorToUse == "LastIndex": - return len(vertex_colors)-1 - - if VertexColorToUse == "ActiveIndex": - return utils.getVertexColors_RenderColorIndex(obj) - - if VertexColorToUse == "CustomIndex": - if VertexColorIndexToUse < len(vertex_colors): - return VertexColorIndexToUse - return -1 - - def GetChosenVertexName(self): - - index = self.GetChosenVertexIndex() - if index == -1: - return "None" - - obj = self.obj - if obj: - if obj.type == "MESH": - if obj.data: - vertex_colors = utils.getVertexColors(obj) - if obj.VertexColorIndexToUse < len(vertex_colors): - return vertex_colors[index].name - - return "None" - - def GetVertexByIndex(self, index): - self.obj +# ====================== 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 .. import bfu_write_text +from .. import bfu_basics +from ..bfu_basics import * +from .. import bfu_utils +from ..bfu_utils import * +from enum import Enum + +from ..bbpl import utils + +import importlib +if "bfu_write_text" in locals(): + importlib.reload(bfu_write_text) +if "bfu_basics" in locals(): + importlib.reload(bfu_basics) +if "bfu_utils" in locals(): + importlib.reload(bfu_utils) + + +class VertexColorExportData: + def __init__(self, obj, parent=None): + self.obj = obj + self.parent = parent + self.export_type = "IGNORE" + self.name = "" + self.color = (1.0, 1.0, 1.0) + self.index = -1 + + if self.GetPropertyOwner(): + if self.GetPropertyOwner().VertexColorImportOption == "IGNORE": + self.export_type = "IGNORE" + + elif self.GetPropertyOwner().VertexColorImportOption == "OVERRIDE": + self.color = self.GetPropertyOwner().VertexOverrideColor + self.export_type = "OVERRIDE" + + elif self.GetPropertyOwner().VertexColorImportOption == "REPLACE": + index = self.GetChosenVertexIndex() + print(index) + if index != -1: + self.index = index + self.name = self.GetChosenVertexName() + self.export_type = "REPLACE" + + def GetPropertyOwner(self): + # Return the object to use for the property or return self if none + if self.parent: + return self.parent + return self.obj + + def GetChosenVertexIndex(self): + + obj = self.obj + if obj.type != "MESH": + return -1 + + VertexColorToUse = self.GetPropertyOwner().VertexColorToUse + VertexColorIndexToUse = self.GetPropertyOwner().VertexColorIndexToUse + + if obj: + if obj.data: + vertex_colors = utils.getVertexColors(obj) + if len(vertex_colors) > 0: + + if VertexColorToUse == "FirstIndex": + return 0 + + if VertexColorToUse == "LastIndex": + return len(vertex_colors)-1 + + if VertexColorToUse == "ActiveIndex": + return utils.getVertexColors_RenderColorIndex(obj) + + if VertexColorToUse == "CustomIndex": + if VertexColorIndexToUse < len(vertex_colors): + return VertexColorIndexToUse + return -1 + + def GetChosenVertexName(self): + + index = self.GetChosenVertexIndex() + if index == -1: + return "None" + + obj = self.obj + if obj: + if obj.type == "MESH": + if obj.data: + vertex_colors = utils.getVertexColors(obj) + if obj.VertexColorIndexToUse < len(vertex_colors): + return vertex_colors[index].name + + return "None" + + def GetVertexByIndex(self, index): + self.obj diff --git a/blender-for-unrealengine/export/bfu_export_single_camera.py b/blender-for-unrealengine/export/bfu_export_single_camera.py index 6cc5cd5b..8804c307 100644 --- a/blender-for-unrealengine/export/bfu_export_single_camera.py +++ b/blender-for-unrealengine/export/bfu_export_single_camera.py @@ -1,151 +1,151 @@ -# ====================== 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 - -if "bpy" in locals(): - import importlib - if "bfu_write_text" in locals(): - importlib.reload(bfu_write_text) - if "bfu_basics" in locals(): - importlib.reload(bfu_basics) - if "bfu_utils" in locals(): - importlib.reload(bfu_utils) - if "bfu_check_potential_error" in locals(): - importlib.reload(bfu_check_potential_error) - if "bfu_export_utils" in locals(): - importlib.reload(bfu_export_utils) - - -from .. import bfu_write_text -from .. import bfu_basics -from ..bfu_basics import * -from .. import bfu_utils -from ..bfu_utils import * -from .. import bfu_check_potential_error - -from . import bfu_export_utils -from .bfu_export_utils import * - - -def ProcessCameraExport(obj): - addon_prefs = GetAddonPrefs() - counter = CounterTimer() - dirpath = GetObjExportDir(obj) - absdirpath = bpy.path.abspath(dirpath) - scene = bpy.context.scene - - MyAsset = scene.UnrealExportedAssetsList.add() - MyAsset.object = obj - MyAsset.asset_name = obj.name - MyAsset.folder_name = obj.exportFolderName - MyAsset.asset_type = bfu_utils.GetAssetType(obj) - MyAsset.StartAssetExport() - - if obj.bfu_export_fbx_camera: - ExportSingleFbxCamera( - dirpath, - GetObjExportFileName(obj), - obj - ) - file = MyAsset.files.add() - file.name = GetObjExportFileName(obj) - file.path = dirpath - file.type = "FBX" - - if obj.ExportAsLod is False: - if (scene.text_AdditionalData and addon_prefs.useGeneratedScripts): - ExportSingleAdditionalTrackCamera( - dirpath, - GetObjExportFileName(obj, "_AdditionalTrack.json"), - obj - ) - file = MyAsset.files.add() - file.name = GetObjExportFileName(obj, "_AdditionalTrack.json") - file.path = dirpath - file.type = "AdditionalTrack" - - MyAsset.EndAssetExport(True) - return MyAsset - - -def ExportSingleFbxCamera( - dirpath, - filename, - obj - ): - - ''' - ##################################################### - #CAMERA - ##################################################### - ''' - # Export single camera - - scene = bpy.context.scene - addon_prefs = GetAddonPrefs() - - filename = ValidFilename(filename) - if obj.type != 'CAMERA': - return - - bbpl.utils.SafeModeSet('OBJECT') - - # Select and rescale camera for export - bpy.ops.object.select_all(action='DESELECT') - SelectSpecificObject(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] - - ExportCameraAsFBX = addon_prefs.exportCameraAsFBX - if ExportCameraAsFBX: - bpy.ops.export_scene.fbx( - filepath=GetExportFullpath(dirpath, filename), - check_existing=False, - use_selection=True, - global_scale=GetObjExportScale(obj), - object_types={'CAMERA'}, - use_custom_props=addon_prefs.exportWithCustomProps, - 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=GetAnimSample(obj), - bake_anim_simplify_factor=obj.SimplifyAnimForExport, - use_metadata=addon_prefs.exportWithMetaData, - primary_bone_axis=obj.exportPrimaryBaneAxis, - secondary_bone_axis=obj.exporSecondaryBoneAxis, - axis_forward=obj.exportAxisForward, - axis_up=obj.exportAxisUp, - bake_space_transform=False - ) - - # Reset camera scale - obj.delta_scale *= 100 - - for obj in scene.objects: - ClearAllBFUTempVars(obj) \ No newline at end of file +# ====================== 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 + +if "bpy" in locals(): + import importlib + if "bfu_write_text" in locals(): + importlib.reload(bfu_write_text) + if "bfu_basics" in locals(): + importlib.reload(bfu_basics) + if "bfu_utils" in locals(): + importlib.reload(bfu_utils) + if "bfu_check_potential_error" in locals(): + importlib.reload(bfu_check_potential_error) + if "bfu_export_utils" in locals(): + importlib.reload(bfu_export_utils) + + +from .. import bfu_write_text +from .. import bfu_basics +from ..bfu_basics import * +from .. import bfu_utils +from ..bfu_utils import * +from .. import bfu_check_potential_error + +from . import bfu_export_utils +from .bfu_export_utils import * + + +def ProcessCameraExport(obj): + addon_prefs = GetAddonPrefs() + counter = CounterTimer() + dirpath = GetObjExportDir(obj) + absdirpath = bpy.path.abspath(dirpath) + scene = bpy.context.scene + + MyAsset = scene.UnrealExportedAssetsList.add() + MyAsset.object = obj + MyAsset.asset_name = obj.name + MyAsset.folder_name = obj.exportFolderName + MyAsset.asset_type = bfu_utils.GetAssetType(obj) + MyAsset.StartAssetExport() + + if obj.bfu_export_fbx_camera: + ExportSingleFbxCamera( + dirpath, + GetObjExportFileName(obj), + obj + ) + file = MyAsset.files.add() + file.name = GetObjExportFileName(obj) + file.path = dirpath + file.type = "FBX" + + if obj.ExportAsLod is False: + if (scene.text_AdditionalData and addon_prefs.useGeneratedScripts): + ExportSingleAdditionalTrackCamera( + dirpath, + GetObjExportFileName(obj, "_AdditionalTrack.json"), + obj + ) + file = MyAsset.files.add() + file.name = GetObjExportFileName(obj, "_AdditionalTrack.json") + file.path = dirpath + file.type = "AdditionalTrack" + + MyAsset.EndAssetExport(True) + return MyAsset + + +def ExportSingleFbxCamera( + dirpath, + filename, + obj + ): + + ''' + ##################################################### + #CAMERA + ##################################################### + ''' + # Export single camera + + scene = bpy.context.scene + addon_prefs = GetAddonPrefs() + + filename = ValidFilename(filename) + if obj.type != 'CAMERA': + return + + bbpl.utils.SafeModeSet('OBJECT') + + # Select and rescale camera for export + bpy.ops.object.select_all(action='DESELECT') + SelectSpecificObject(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] + + ExportCameraAsFBX = addon_prefs.exportCameraAsFBX + if ExportCameraAsFBX: + bpy.ops.export_scene.fbx( + filepath=GetExportFullpath(dirpath, filename), + check_existing=False, + use_selection=True, + global_scale=GetObjExportScale(obj), + object_types={'CAMERA'}, + use_custom_props=addon_prefs.exportWithCustomProps, + 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=GetAnimSample(obj), + bake_anim_simplify_factor=obj.SimplifyAnimForExport, + use_metadata=addon_prefs.exportWithMetaData, + primary_bone_axis=obj.exportPrimaryBaneAxis, + secondary_bone_axis=obj.exporSecondaryBoneAxis, + axis_forward=obj.exportAxisForward, + axis_up=obj.exportAxisUp, + bake_space_transform=False + ) + + # Reset camera scale + obj.delta_scale *= 100 + + for obj in scene.objects: + ClearAllBFUTempVars(obj) diff --git a/blender-for-unrealengine/export/bfu_export_single_fbx_action.py b/blender-for-unrealengine/export/bfu_export_single_fbx_action.py index 61f7810b..b5372af9 100644 --- a/blender-for-unrealengine/export/bfu_export_single_fbx_action.py +++ b/blender-for-unrealengine/export/bfu_export_single_fbx_action.py @@ -57,7 +57,7 @@ def ProcessActionExport(obj, action): MyAsset.asset_name = bfu_utils.GetActionExportFileName(obj, action, "") MyAsset.folder_name = obj.exportFolderName MyAsset.asset_type = bfu_utils.GetActionType(action) - + MyAsset.StartAssetExport() ExportSingleFbxAction(scene, dirpath, GetActionExportFileName(obj, action), obj, action) diff --git a/blender-for-unrealengine/export/bfu_export_single_fbx_nla_anim.py b/blender-for-unrealengine/export/bfu_export_single_fbx_nla_anim.py index 33b71097..25d3859d 100644 --- a/blender-for-unrealengine/export/bfu_export_single_fbx_nla_anim.py +++ b/blender-for-unrealengine/export/bfu_export_single_fbx_nla_anim.py @@ -205,4 +205,4 @@ def ExportSingleFbxNLAAnim( ResetDuplicateNameAfterExport(duplicate_data) for obj in scene.objects: - ClearAllBFUTempVars(obj) \ No newline at end of file + ClearAllBFUTempVars(obj) diff --git a/blender-for-unrealengine/export/bfu_export_single_skeletal_mesh.py b/blender-for-unrealengine/export/bfu_export_single_skeletal_mesh.py index 5ca78065..cc4b821f 100644 --- a/blender-for-unrealengine/export/bfu_export_single_skeletal_mesh.py +++ b/blender-for-unrealengine/export/bfu_export_single_skeletal_mesh.py @@ -112,12 +112,10 @@ def ExportSingleSkeletalMesh( SetVertexColorForUnrealExport(obj) SetSocketsExportTransform(obj) SetSocketsExportName(obj) - + active = bpy.context.view_layer.objects.active asset_name.target_object = active - - export_procedure = active.bfu_export_procedure if export_as_proxy: @@ -149,7 +147,6 @@ def ExportSingleSkeletalMesh( ConvertArmatureConstraintToModifiers(active) - asset_name.SetExportName() if (export_procedure == "normal"): @@ -205,4 +202,4 @@ def ExportSingleSkeletalMesh( ResetDuplicateNameAfterExport(duplicate_data) for obj in scene.objects: - ClearAllBFUTempVars(obj) \ No newline at end of file + ClearAllBFUTempVars(obj) diff --git a/blender-for-unrealengine/export/bfu_export_single_static_mesh.py b/blender-for-unrealengine/export/bfu_export_single_static_mesh.py index 9e079a9a..d32593d5 100644 --- a/blender-for-unrealengine/export/bfu_export_single_static_mesh.py +++ b/blender-for-unrealengine/export/bfu_export_single_static_mesh.py @@ -110,7 +110,6 @@ def ExportSingleStaticMesh( SetSocketsExportTransform(obj) SetSocketsExportName(obj) - active = bpy.context.view_layer.objects.active asset_name.target_object = active @@ -118,13 +117,10 @@ def ExportSingleStaticMesh( meshType = GetAssetType(active) - bfu_check_potential_error.UpdateNameHierarchy( GetAllCollisionAndSocketsObj(bpy.context.selected_objects) ) - - asset_name.SetExportName() bpy.ops.export_scene.fbx( diff --git a/blender-for-unrealengine/export/bfu_export_utils.py b/blender-for-unrealengine/export/bfu_export_utils.py index 152850d3..b54bfd39 100644 --- a/blender-for-unrealengine/export/bfu_export_utils.py +++ b/blender-for-unrealengine/export/bfu_export_utils.py @@ -383,8 +383,8 @@ def ConvertGeometryNodeAttributeToUV(obj): if hasattr(obj.data, "attributes"): # Cuves has not attributes. if attrib_name in obj.data.attributes: - # TO DO: Bad why to do this. Need found a way to convert without using ops. - obj.data.attributes.active = obj.data.attributes[attrib_name] + # TO DO: Bad why to do this. Need found a way to convert without using ops. + obj.data.attributes.active = obj.data.attributes[attrib_name] # Because a bug Blender set the wrong attribute as active in 3.5. if obj.data.attributes.active != obj.data.attributes[attrib_name]: @@ -457,7 +457,7 @@ def ConvertArmatureConstraintToModifiers(armature): const.enabled = False # Remove All Vertex Group - # TO DO: + # TO DO: # Add Vertex Group for target in const.targets: diff --git a/blender-for-unrealengine/import/asset_import_script.py b/blender-for-unrealengine/import/asset_import_script.py index cff0d72e..8c0c8175 100644 --- a/blender-for-unrealengine/import/asset_import_script.py +++ b/blender-for-unrealengine/import/asset_import_script.py @@ -1,537 +1,537 @@ -# This script was generated with the addons Blender for UnrealEngine : https://github.com/xavier150/Blender-For-UnrealEngine-Addons -# It will import into Unreal Engine all the assets of type StaticMesh, SkeletalMesh, Animation and Pose -# The script must be used in Unreal Engine Editor with Python plugins : https://docs.unrealengine.com/en-US/Engine/Editor/ScriptingAndAutomation/Python -# Use this command in Unreal cmd consol: py "[ScriptLocation]\ImportSequencerScript.py" - - -from unittest import result -import sys -import os.path -import json - -try: # TO DO: Found a better way to check that. - import unreal -except ImportError: - import unreal_engine as unreal - - -def CheckTasks(): - - if GetUnrealVersion() >= 4.20: # TO DO: EditorAssetLibrary was added in witch version exactly? - if not hasattr(unreal, 'EditorAssetLibrary'): - print('--------------------------------------------------') - print('WARNING: Editor Scripting Utilities should be activated.') - print('Edit > Plugin > Scripting > Editor Scripting Utilities.') - return False - return True - - -def JsonLoad(json_file): - # Changed in Python 3.9: The keyword argument encoding has been removed. - if sys.version_info >= (3, 9): - return json.load(json_file) - else: - return json.load(json_file, encoding="utf8") - - -def JsonLoadFile(json_file_path): - if sys.version_info[0] < 3: - with open(json_file_path, "r") as json_file: - return JsonLoad(json_file) - else: - with open(json_file_path, "r", encoding="utf8") as json_file: - return JsonLoad(json_file) - - -def GetUnrealVersion(): - version = unreal.SystemLibrary.get_engine_version().split(".") - float_version = int(version[0]) + float(float(version[1])/100) - return float_version - - -def ImportAllAssets(): - - import string - - # Prepare process import - json_data_file = 'ImportAssetData.json' - dir_path = os.path.dirname(os.path.realpath(__file__)) - - import_assets_data = JsonLoadFile(os.path.join(dir_path, json_data_file)) - - unreal_import_location = import_assets_data['unreal_import_location'] - ImportedList = [] - ImportFailList = [] - - def ValidUnrealAssetsName(filename): - # Normalizes string, removes non-alpha characters - # Asset name in Unreal use - - filename = filename.replace('.', '_') - filename = filename.replace('(', '_') - filename = filename.replace(')', '_') - filename = filename.replace(' ', '_') - valid_chars = "-_%s%s" % (string.ascii_letters, string.digits) - filename = ''.join(c for c in filename if c in valid_chars) - return filename - - def GetAssetByType(type): - target_assets = [] - for asset in import_assets_data["assets"]: - if asset["type"] == type: - target_assets.append(asset) - return target_assets - - def ImportAsset(asset_data): - - counter = str(len(ImportedList)+1) + "/" + str(len(import_assets_data["assets"])) - print("Import asset " + counter + ": ", asset_data["name"]) - - if asset_data["type"] == "StaticMesh" or asset_data["type"] == "SkeletalMesh": - if "lod" in asset_data: - if asset_data["lod"] > 0: # Lod should not be imported here so return if lod is not 0. - return - - if asset_data["type"] == "Alembic": - FileType = "ABC" - else: - FileType = "FBX" - - asset_data["full_import_path"] # AssetImportPath - asset_data["fbx_path"] # fbx_file_path - asset_data["additional_tracks_path"] # additional_track_file_path - - def GetAdditionalData(): - if "additional_tracks_path" in asset_data: - if asset_data["additional_tracks_path"] is not None: - return JsonLoadFile(asset_data["additional_tracks_path"]) - return None - - additional_data = GetAdditionalData() - - def ImportTask(): - # New import task - # Property - - if asset_data["type"] == "Animation" or asset_data["type"] == "SkeletalMesh": - find_asset = unreal.find_asset(asset_data["animation_skeleton_path"]) - if isinstance(find_asset, unreal.Skeleton): - OriginSkeleton = find_asset - elif isinstance(find_asset, unreal.SkeletalMesh): - OriginSkeleton = find_asset.skeleton - else: - OriginSkeleton = None - if OriginSkeleton: - print("Setting skeleton asset: " + OriginSkeleton.get_full_name()) - else: - print("Could not find skeleton at the path: " + asset_data["animation_skeleton_path"]) - - # docs.unrealengine.com/4.26/en-US/PythonAPI/class/AssetImportTask.html - task = unreal.AssetImportTask() - - def GetStaticMeshImportData(): - if asset_data["type"] == "StaticMesh": - return task.get_editor_property('options').static_mesh_import_data - return None - - def GetSkeletalMeshImportData(): - if asset_data["type"] == "SkeletalMesh": - return task.get_editor_property('options').skeletal_mesh_import_data - return None - - def GetAnimationImportData(): - if asset_data["type"] == "Animation": - return task.get_editor_property('options').anim_sequence_import_data - return None - - def GetAlembicImportData(): - if asset_data["type"] == "Alembic": - return task.get_editor_property('options') - return None - - def GetMeshImportData(): - if asset_data["type"] == "StaticMesh": - return GetStaticMeshImportData() - if asset_data["type"] == "SkeletalMesh": - return GetSkeletalMeshImportData() - - return None - - if asset_data["type"] == "Alembic": - task.filename = asset_data["abc_path"] - else: - task.filename = asset_data["fbx_path"] - task.destination_path = os.path.normpath(asset_data["full_import_path"]).replace('\\','/') - task.automated = True - # task.automated = False #Debug for show dialog - task.save = True - task.replace_existing = True - - if asset_data["type"] == "Alembic": - task.set_editor_property('options', unreal.AbcImportSettings()) - else: - task.set_editor_property('options', unreal.FbxImportUI()) - - # Alembic - if GetAlembicImportData(): - GetAlembicImportData().static_mesh_settings.set_editor_property("merge_meshes", True) - GetAlembicImportData().set_editor_property("import_type", unreal.AlembicImportType.SKELETAL) - GetAlembicImportData().conversion_settings.set_editor_property("flip_u", False) - GetAlembicImportData().conversion_settings.set_editor_property("flip_v", True) - GetAlembicImportData().conversion_settings.set_editor_property("scale", unreal.Vector(100, -100, 100)) - GetAlembicImportData().conversion_settings.set_editor_property("rotation", unreal.Vector(90, 0, 0)) - - # Vertex color - vertex_override_color = None - vertex_color_import_option = None - if additional_data: - - vertex_color_import_option = unreal.VertexColorImportOption.REPLACE # Default - if "vertex_color_import_option" in additional_data: - if additional_data["vertex_color_import_option"] == "IGNORE": - vertex_color_import_option = unreal.VertexColorImportOption.IGNORE - elif additional_data["vertex_color_import_option"] == "OVERRIDE": - vertex_color_import_option = unreal.VertexColorImportOption.OVERRIDE - elif additional_data["vertex_color_import_option"] == "REPLACE": - vertex_color_import_option = unreal.VertexColorImportOption.REPLACE - - if "vertex_override_color" in additional_data: - vertex_override_color = unreal.LinearColor( - additional_data["vertex_override_color"][0], - additional_data["vertex_override_color"][1], - additional_data["vertex_override_color"][2] - ) - - # #################################[Change] - - # unreal.FbxImportUI - # https://docs.unrealengine.com/4.26/en-US/PythonAPI/class/FbxImportUI.html - - # Import transform - anim_sequence_import_data = GetAnimationImportData() - if anim_sequence_import_data: - anim_sequence_import_data.import_translation = unreal.Vector(0, 0, 0) - - # Vertex color - if vertex_color_import_option and GetMeshImportData(): - GetMeshImportData().set_editor_property('vertex_color_import_option', vertex_color_import_option) - - if vertex_override_color and GetMeshImportData(): - GetMeshImportData().set_editor_property('vertex_override_color', vertex_override_color.to_rgbe()) - - if asset_data["type"] == "Alembic": - task.get_editor_property('options').set_editor_property('import_type', unreal.AlembicImportType.SKELETAL) - - else: - if asset_data["type"] == "Animation" or asset_data["type"] == "SkeletalMesh": - if OriginSkeleton: - task.get_editor_property('options').set_editor_property('Skeleton', OriginSkeleton) - else: - if asset_data["type"] == "Animation": - ImportFailList.append('Skeleton ' + asset_data["animation_skeleton_path"] + ' Not found for ' + asset_data["name"] + ' asset.') - return - else: - print("Skeleton is not set, a new skeleton asset will be created...") - - - if asset_data["type"] == "StaticMesh": - task.get_editor_property('options').set_editor_property('original_import_type', unreal.FBXImportType.FBXIT_STATIC_MESH) - elif asset_data["type"] == "Animation": - task.get_editor_property('options').set_editor_property('original_import_type', unreal.FBXImportType.FBXIT_ANIMATION) - else: - task.get_editor_property('options').set_editor_property('original_import_type', unreal.FBXImportType.FBXIT_SKELETAL_MESH) - - if asset_data["type"] == "Animation": - task.get_editor_property('options').set_editor_property('import_materials', False) - else: - task.get_editor_property('options').set_editor_property('import_materials', True) - - task.get_editor_property('options').set_editor_property('import_textures', False) - - if asset_data["type"] == "Animation": - - task.get_editor_property('options').set_editor_property('import_animations', True) - task.get_editor_property('options').set_editor_property('import_mesh', False) - task.get_editor_property('options').set_editor_property('create_physics_asset',False) - else: - task.get_editor_property('options').set_editor_property('import_animations', False) - task.get_editor_property('options').set_editor_property('import_mesh', True) - if "create_physics_asset" in asset_data: - task.get_editor_property('options').set_editor_property('create_physics_asset', asset_data["create_physics_asset"]) - - # unreal.FbxMeshImportData - - if asset_data["type"] == "StaticMesh" or asset_data["type"] == "SkeletalMesh": - if "material_search_location" in asset_data: - # unreal.FbxTextureImportData - if asset_data["material_search_location"] == "Local": - task.get_editor_property('options').texture_import_data.set_editor_property('material_search_location', unreal.MaterialSearchLocation.LOCAL) - if asset_data["material_search_location"] == "UnderParent": - task.get_editor_property('options').texture_import_data.set_editor_property('material_search_location', unreal.MaterialSearchLocation.UNDER_PARENT) - if asset_data["material_search_location"] == "UnderRoot": - task.get_editor_property('options').texture_import_data.set_editor_property('material_search_location', unreal.MaterialSearchLocation.UNDER_ROOT) - if asset_data["material_search_location"] == "AllAssets": - task.get_editor_property('options').texture_import_data.set_editor_property('material_search_location', unreal.MaterialSearchLocation.ALL_ASSETS) - - if asset_data["type"] == "StaticMesh": - # unreal.FbxStaticMeshImportData - task.get_editor_property('options').static_mesh_import_data.set_editor_property('combine_meshes', True) - if "auto_generate_collision" in asset_data: - task.get_editor_property('options').static_mesh_import_data.set_editor_property('auto_generate_collision', asset_data["auto_generate_collision"]) - if "static_mesh_lod_group" in asset_data: - if asset_data["static_mesh_lod_group"]: - task.get_editor_property('options').static_mesh_import_data.set_editor_property('static_mesh_lod_group', asset_data["static_mesh_lod_group"]) - if "generate_lightmap_u_vs" in asset_data: - task.get_editor_property('options').static_mesh_import_data.set_editor_property('generate_lightmap_u_vs', asset_data["generate_lightmap_u_vs"]) - - if asset_data["type"] == "SkeletalMesh" or asset_data["type"] == "Animation": - # unreal.FbxSkeletalMeshImportData - task.get_editor_property('options').skeletal_mesh_import_data.set_editor_property('import_morph_targets', True) - task.get_editor_property('options').skeletal_mesh_import_data.set_editor_property('convert_scene', True) - task.get_editor_property('options').skeletal_mesh_import_data.set_editor_property('normal_import_method', unreal.FBXNormalImportMethod.FBXNIM_IMPORT_NORMALS_AND_TANGENTS) - - # ###############[ pre import ]################ - - # Check is the file alredy exit - if additional_data: - if "preview_import_path" in additional_data: - task_asset_full_path = task.destination_path+"/"+additional_data["preview_import_path"]+"."+additional_data["preview_import_path"] - find_asset = unreal.find_asset(task_asset_full_path) - if find_asset: - - # Vertex color - - asset_import_data = find_asset.get_editor_property('asset_import_data') - if vertex_color_import_option: - asset_import_data.set_editor_property('vertex_color_import_option', vertex_color_import_option) - - if vertex_override_color: - asset_import_data.set_editor_property('vertex_override_color', vertex_override_color.to_rgbe()) - - # ###############[ import asset ]################ - - print("Import task") - if asset_data["type"] == "Animation": - ''' - For animation the script will import a skeletal mesh and remove after. - If the skeletal mesh alredy exist try to remove. - ''' - - AssetName = asset_data["name"] - AssetName = ValidUnrealAssetsName(AssetName) - AssetPath = "SkeletalMesh'"+asset_data["full_import_path"]+"/"+AssetName+"."+AssetName+"'" - - if unreal.EditorAssetLibrary.does_asset_exist(AssetPath): - oldAsset = unreal.EditorAssetLibrary.find_asset_data(AssetPath) - if oldAsset.asset_class == "SkeletalMesh": - unreal.EditorAssetLibrary.delete_asset(AssetPath) - - unreal.AssetToolsHelpers.get_asset_tools().import_asset_tasks([task]) - - if len(task.imported_object_paths) > 0: - asset = unreal.find_asset(task.imported_object_paths[0]) - else: - asset = None - - if asset is None: - ImportFailList.append('Error zero imported object for: ' + asset_data["name"]) - return - - if asset_data["type"] == "Animation": - # For animation remove the extra mesh - p = task.imported_object_paths[0] - if type(unreal.find_asset(p)) is not unreal.AnimSequence: - animAssetName = p.split('.')[0]+'_anim.'+p.split('.')[1]+'_anim' - animAssetNameDesiredPath = p.split('.')[0]+'.'+p.split('.')[1] - animAsset = unreal.find_asset(animAssetName) - if animAsset is not None: - unreal.EditorAssetLibrary.delete_asset(p) - unreal.EditorAssetLibrary.rename_asset(animAssetName, animAssetNameDesiredPath) - asset = animAsset - else: - ImportFailList.append('animAsset ' + asset_data["name"] + ' not found for after inport: ' + animAssetName) - return - - # ###############[ Post treatment ]################ - - asset_import_data = asset.get_editor_property('asset_import_data') - if asset_data["type"] == "StaticMesh": - if "static_mesh_lod_group" in asset_data: - if asset_data["static_mesh_lod_group"]: - asset.set_editor_property('lod_group', asset_data["static_mesh_lod_group"]) - if "use_custom_light_map_resolution" in asset_data: - if asset_data["use_custom_light_map_resolution"]: - if "light_map_resolution" in asset_data: - asset.set_editor_property('light_map_resolution', asset_data["light_map_resolution"]) - - if "collision_trace_flag" in asset_data: - if asset_data["collision_trace_flag"] == "CTF_UseDefault": - asset.get_editor_property('body_setup').set_editor_property('collision_trace_flag', unreal.CollisionTraceFlag.CTF_USE_DEFAULT) - elif asset_data["collision_trace_flag"] == "CTF_UseSimpleAndComplex": - asset.get_editor_property('body_setup').set_editor_property('collision_trace_flag', unreal.CollisionTraceFlag.CTF_USE_SIMPLE_AND_COMPLEX) - elif asset_data["collision_trace_flag"] == "CTF_UseSimpleAsComplex": - asset.get_editor_property('body_setup').set_editor_property('collision_trace_flag', unreal.CollisionTraceFlag.CTF_USE_SIMPLE_AS_COMPLEX) - elif asset_data["collision_trace_flag"] == "CTF_UseComplexAsSimple": - asset.get_editor_property('body_setup').set_editor_property('collision_trace_flag', unreal.CollisionTraceFlag.CTF_USE_COMPLEX_AS_SIMPLE) - - if asset_data["type"] == "StaticMesh": - if "generate_lightmap_u_vs" in asset_data: - asset_import_data.set_editor_property('generate_lightmap_u_vs', asset_data["generate_lightmap_u_vs"]) # Import data - unreal.EditorStaticMeshLibrary.set_generate_lightmap_uv(asset, asset_data["generate_lightmap_u_vs"]) # Build settings at lod - - if asset_data["type"] == "SkeletalMesh": - asset_import_data.set_editor_property('normal_import_method', unreal.FBXNormalImportMethod.FBXNIM_IMPORT_NORMALS_AND_TANGENTS) - - # Socket - if asset_data["type"] == "SkeletalMesh": - # Import the SkeletalMesh socket(s) - sockets_to_add = additional_data["Sockets"] - skeleton = asset.get_editor_property('skeleton') - for socket in sockets_to_add: - old_socket = asset.find_socket(socket["SocketName"]) - if old_socket: - # Edit socket - pass - # old_socket.relative_location = socket["Location"] - # old_socket.relative_rotation = socket["Rotation"] - # old_socket.relative_scale = socket["Scale"] - - else: - # Create socket - pass - # new_socket = unreal.SkeletalMeshSocket(asset) - # new_socket.socket_name = socket["SocketName"] - # new_socket.bone_name = socket["BoneName"] - # new_socket.relative_location = socket["Location"] - # new_socket.relative_rotation = socket["Rotation"] - # new_socket.relative_scale = socket["Scale"] - # NEED UNREAL ENGINE IMPLEMENTATION IN PYTHON API. - # skeleton.add_socket(new_socket) - - # Lod - if asset_data["type"] == "StaticMesh" or asset_data["type"] == "SkeletalMesh": - if asset_data["type"] == "StaticMesh": - unreal.EditorStaticMeshLibrary.remove_lods(asset) # Import the StaticMesh lod(s) - - if asset_data["type"] == "SkeletalMesh" or asset_data["type"] == "StaticMesh": - - def ImportStaticLod(lod_name, lod_number): - if "LevelOfDetail" in additional_data: - if lod_name in additional_data["LevelOfDetail"]: - lodTask = unreal.AssetImportTask() - lodTask.filename = additional_data["LevelOfDetail"][lod_name] - destination_path = os.path.normpath(asset_data["full_import_path"]).replace('\\', '/') - lodTask.destination_path = destination_path - lodTask.automated = True - lodTask.replace_existing = True - - # Set vertex color import settings to replicate base StaticMesh's behaviour - if asset_data["type"] == "Alembic": - lodTask.set_editor_property('options', unreal.AbcImportSettings()) - else: - lodTask.set_editor_property('options', unreal.FbxImportUI()) - - lodTask.get_editor_property('options').static_mesh_import_data.set_editor_property('vertex_color_import_option', vertex_color_import_option) - lodTask.get_editor_property('options').static_mesh_import_data.set_editor_property('vertex_override_color', vertex_override_color.to_rgbe()) - - print(destination_path, additional_data["LevelOfDetail"][lod_name]) - unreal.AssetToolsHelpers.get_asset_tools().import_asset_tasks([lodTask]) - if len(lodTask.imported_object_paths) > 0: - lodAsset = unreal.find_asset(lodTask.imported_object_paths[0]) - slot_replaced = unreal.EditorStaticMeshLibrary.set_lod_from_static_mesh(asset, lod_number, lodAsset, 0, True) - unreal.EditorAssetLibrary.delete_asset(lodTask.imported_object_paths[0]) - - def ImportSkeletalLod(lod_name, lod_number): - if "LevelOfDetail" in additional_data: - if lod_name in additional_data["LevelOfDetail"]: - # Unreal python no longer support Skeletal mesh LODS import. - pass - - if asset_data["type"] == "StaticMesh": - ImportStaticLod("lod_1", 1) - ImportStaticLod("lod_2", 2) - ImportStaticLod("lod_3", 3) - ImportStaticLod("lod_4", 4) - ImportStaticLod("lod_5", 5) - - elif asset_data["type"] == "SkeletalMesh": - ImportSkeletalLod("lod_1", 1) - ImportSkeletalLod("lod_2", 2) - ImportSkeletalLod("lod_3", 3) - ImportSkeletalLod("lod_4", 4) - ImportSkeletalLod("lod_5", 5) - - # Vertex color - if vertex_override_color: - asset_import_data.set_editor_property('vertex_override_color', vertex_override_color.to_rgbe()) - - if vertex_color_import_option: - asset_import_data.set_editor_property('vertex_color_import_option', vertex_color_import_option) - - # #################################[EndChange] - if asset_data["type"] == "StaticMesh" or asset_data["type"] == "SkeletalMesh": - unreal.EditorAssetLibrary.save_loaded_asset(asset) - ImportedList.append([asset, asset_data["type"]]) - - ImportTask() - - # Process import - - print('========================= Import started ! =========================') - print(import_assets_data["assets"]) - - # Import assets with a specific order - - for asset in GetAssetByType("Alembic"): - ImportAsset(asset) - for asset in GetAssetByType("StaticMesh"): - ImportAsset(asset) - for asset in GetAssetByType("SkeletalMesh"): - ImportAsset(asset) - for asset in GetAssetByType("Animation"): - ImportAsset(asset) - - print('========================= Full import completed ! =========================') - - # import result - StaticMesh_ImportedList = [] - SkeletalMesh_ImportedList = [] - Alembic_ImportedList = [] - Animation_ImportedList = [] - for asset in ImportedList: - if asset[1] == 'StaticMesh': - StaticMesh_ImportedList.append(asset[0]) - elif asset[1] == 'SkeletalMesh': - SkeletalMesh_ImportedList.append(asset[0]) - elif asset[1] == 'Alembic': - Alembic_ImportedList.append(asset[0]) - else: - Animation_ImportedList.append(asset[0]) - - print('Imported StaticMesh: '+str(len(StaticMesh_ImportedList))) - print('Imported SkeletalMesh: '+str(len(SkeletalMesh_ImportedList))) - print('Imported Alembic: '+str(len(Alembic_ImportedList))) - print('Imported Animation: '+str(len(Animation_ImportedList))) - print('Import failled: '+str(len(ImportFailList))) - for error in ImportFailList: - print(error) - - # Select asset(s) in content browser - PathList = [] - for asset in (StaticMesh_ImportedList + SkeletalMesh_ImportedList + Alembic_ImportedList + Animation_ImportedList): - PathList.append(asset.get_path_name()) - unreal.EditorAssetLibrary.sync_browser_to_objects(PathList) - print('=========================') - - if len(ImportFailList) > 0: - return 'Some asset(s) could not be imported.' - else: - return 'Assets imported with success !' - - -print("Start importing assets.") - -if CheckTasks(): - print(ImportAllAssets()) - -print("Importing assets finished.") +# This script was generated with the addons Blender for UnrealEngine : https://github.com/xavier150/Blender-For-UnrealEngine-Addons +# It will import into Unreal Engine all the assets of type StaticMesh, SkeletalMesh, Animation and Pose +# The script must be used in Unreal Engine Editor with Python plugins : https://docs.unrealengine.com/en-US/Engine/Editor/ScriptingAndAutomation/Python +# Use this command in Unreal cmd consol: py "[ScriptLocation]\ImportSequencerScript.py" + + +from unittest import result +import sys +import os.path +import json + +try: # TO DO: Found a better way to check that. + import unreal +except ImportError: + import unreal_engine as unreal + + +def CheckTasks(): + + if GetUnrealVersion() >= 4.20: # TO DO: EditorAssetLibrary was added in witch version exactly? + if not hasattr(unreal, 'EditorAssetLibrary'): + print('--------------------------------------------------') + print('WARNING: Editor Scripting Utilities should be activated.') + print('Edit > Plugin > Scripting > Editor Scripting Utilities.') + return False + return True + + +def JsonLoad(json_file): + # Changed in Python 3.9: The keyword argument encoding has been removed. + if sys.version_info >= (3, 9): + return json.load(json_file) + else: + return json.load(json_file, encoding="utf8") + + +def JsonLoadFile(json_file_path): + if sys.version_info[0] < 3: + with open(json_file_path, "r") as json_file: + return JsonLoad(json_file) + else: + with open(json_file_path, "r", encoding="utf8") as json_file: + return JsonLoad(json_file) + + +def GetUnrealVersion(): + version = unreal.SystemLibrary.get_engine_version().split(".") + float_version = int(version[0]) + float(float(version[1])/100) + return float_version + + +def ImportAllAssets(): + + import string + + # Prepare process import + json_data_file = 'ImportAssetData.json' + dir_path = os.path.dirname(os.path.realpath(__file__)) + + import_assets_data = JsonLoadFile(os.path.join(dir_path, json_data_file)) + + unreal_import_location = import_assets_data['unreal_import_location'] + ImportedList = [] + ImportFailList = [] + + def ValidUnrealAssetsName(filename): + # Normalizes string, removes non-alpha characters + # Asset name in Unreal use + + filename = filename.replace('.', '_') + filename = filename.replace('(', '_') + filename = filename.replace(')', '_') + filename = filename.replace(' ', '_') + valid_chars = "-_%s%s" % (string.ascii_letters, string.digits) + filename = ''.join(c for c in filename if c in valid_chars) + return filename + + def GetAssetByType(type): + target_assets = [] + for asset in import_assets_data["assets"]: + if asset["type"] == type: + target_assets.append(asset) + return target_assets + + def ImportAsset(asset_data): + + counter = str(len(ImportedList)+1) + "/" + str(len(import_assets_data["assets"])) + print("Import asset " + counter + ": ", asset_data["name"]) + + if asset_data["type"] == "StaticMesh" or asset_data["type"] == "SkeletalMesh": + if "lod" in asset_data: + if asset_data["lod"] > 0: # Lod should not be imported here so return if lod is not 0. + return + + if asset_data["type"] == "Alembic": + FileType = "ABC" + else: + FileType = "FBX" + + asset_data["full_import_path"] # AssetImportPath + asset_data["fbx_path"] # fbx_file_path + asset_data["additional_tracks_path"] # additional_track_file_path + + def GetAdditionalData(): + if "additional_tracks_path" in asset_data: + if asset_data["additional_tracks_path"] is not None: + return JsonLoadFile(asset_data["additional_tracks_path"]) + return None + + additional_data = GetAdditionalData() + + def ImportTask(): + # New import task + # Property + + if asset_data["type"] == "Animation" or asset_data["type"] == "SkeletalMesh": + find_asset = unreal.find_asset(asset_data["animation_skeleton_path"]) + if isinstance(find_asset, unreal.Skeleton): + OriginSkeleton = find_asset + elif isinstance(find_asset, unreal.SkeletalMesh): + OriginSkeleton = find_asset.skeleton + else: + OriginSkeleton = None + if OriginSkeleton: + print("Setting skeleton asset: " + OriginSkeleton.get_full_name()) + else: + print("Could not find skeleton at the path: " + asset_data["animation_skeleton_path"]) + + # docs.unrealengine.com/4.26/en-US/PythonAPI/class/AssetImportTask.html + task = unreal.AssetImportTask() + + def GetStaticMeshImportData(): + if asset_data["type"] == "StaticMesh": + return task.get_editor_property('options').static_mesh_import_data + return None + + def GetSkeletalMeshImportData(): + if asset_data["type"] == "SkeletalMesh": + return task.get_editor_property('options').skeletal_mesh_import_data + return None + + def GetAnimationImportData(): + if asset_data["type"] == "Animation": + return task.get_editor_property('options').anim_sequence_import_data + return None + + def GetAlembicImportData(): + if asset_data["type"] == "Alembic": + return task.get_editor_property('options') + return None + + def GetMeshImportData(): + if asset_data["type"] == "StaticMesh": + return GetStaticMeshImportData() + if asset_data["type"] == "SkeletalMesh": + return GetSkeletalMeshImportData() + + return None + + if asset_data["type"] == "Alembic": + task.filename = asset_data["abc_path"] + else: + task.filename = asset_data["fbx_path"] + task.destination_path = os.path.normpath(asset_data["full_import_path"]).replace('\\', '/') + task.automated = True + # task.automated = False #Debug for show dialog + task.save = True + task.replace_existing = True + + if asset_data["type"] == "Alembic": + task.set_editor_property('options', unreal.AbcImportSettings()) + else: + task.set_editor_property('options', unreal.FbxImportUI()) + + # Alembic + if GetAlembicImportData(): + GetAlembicImportData().static_mesh_settings.set_editor_property("merge_meshes", True) + GetAlembicImportData().set_editor_property("import_type", unreal.AlembicImportType.SKELETAL) + GetAlembicImportData().conversion_settings.set_editor_property("flip_u", False) + GetAlembicImportData().conversion_settings.set_editor_property("flip_v", True) + GetAlembicImportData().conversion_settings.set_editor_property("scale", unreal.Vector(100, -100, 100)) + GetAlembicImportData().conversion_settings.set_editor_property("rotation", unreal.Vector(90, 0, 0)) + + # Vertex color + vertex_override_color = None + vertex_color_import_option = None + if additional_data: + + vertex_color_import_option = unreal.VertexColorImportOption.REPLACE # Default + if "vertex_color_import_option" in additional_data: + if additional_data["vertex_color_import_option"] == "IGNORE": + vertex_color_import_option = unreal.VertexColorImportOption.IGNORE + elif additional_data["vertex_color_import_option"] == "OVERRIDE": + vertex_color_import_option = unreal.VertexColorImportOption.OVERRIDE + elif additional_data["vertex_color_import_option"] == "REPLACE": + vertex_color_import_option = unreal.VertexColorImportOption.REPLACE + + if "vertex_override_color" in additional_data: + vertex_override_color = unreal.LinearColor( + additional_data["vertex_override_color"][0], + additional_data["vertex_override_color"][1], + additional_data["vertex_override_color"][2] + ) + + # #################################[Change] + + # unreal.FbxImportUI + # https://docs.unrealengine.com/4.26/en-US/PythonAPI/class/FbxImportUI.html + + # Import transform + anim_sequence_import_data = GetAnimationImportData() + if anim_sequence_import_data: + anim_sequence_import_data.import_translation = unreal.Vector(0, 0, 0) + + # Vertex color + if vertex_color_import_option and GetMeshImportData(): + GetMeshImportData().set_editor_property('vertex_color_import_option', vertex_color_import_option) + + if vertex_override_color and GetMeshImportData(): + GetMeshImportData().set_editor_property('vertex_override_color', vertex_override_color.to_rgbe()) + + if asset_data["type"] == "Alembic": + task.get_editor_property('options').set_editor_property('import_type', unreal.AlembicImportType.SKELETAL) + + else: + if asset_data["type"] == "Animation" or asset_data["type"] == "SkeletalMesh": + if OriginSkeleton: + task.get_editor_property('options').set_editor_property('Skeleton', OriginSkeleton) + else: + if asset_data["type"] == "Animation": + ImportFailList.append('Skeleton ' + asset_data["animation_skeleton_path"] + ' Not found for ' + asset_data["name"] + ' asset.') + return + else: + print("Skeleton is not set, a new skeleton asset will be created...") + + + if asset_data["type"] == "StaticMesh": + task.get_editor_property('options').set_editor_property('original_import_type', unreal.FBXImportType.FBXIT_STATIC_MESH) + elif asset_data["type"] == "Animation": + task.get_editor_property('options').set_editor_property('original_import_type', unreal.FBXImportType.FBXIT_ANIMATION) + else: + task.get_editor_property('options').set_editor_property('original_import_type', unreal.FBXImportType.FBXIT_SKELETAL_MESH) + + if asset_data["type"] == "Animation": + task.get_editor_property('options').set_editor_property('import_materials', False) + else: + task.get_editor_property('options').set_editor_property('import_materials', True) + + task.get_editor_property('options').set_editor_property('import_textures', False) + + if asset_data["type"] == "Animation": + + task.get_editor_property('options').set_editor_property('import_animations', True) + task.get_editor_property('options').set_editor_property('import_mesh', False) + task.get_editor_property('options').set_editor_property('create_physics_asset',False) + else: + task.get_editor_property('options').set_editor_property('import_animations', False) + task.get_editor_property('options').set_editor_property('import_mesh', True) + if "create_physics_asset" in asset_data: + task.get_editor_property('options').set_editor_property('create_physics_asset', asset_data["create_physics_asset"]) + + # unreal.FbxMeshImportData + + if asset_data["type"] == "StaticMesh" or asset_data["type"] == "SkeletalMesh": + if "material_search_location" in asset_data: + # unreal.FbxTextureImportData + if asset_data["material_search_location"] == "Local": + task.get_editor_property('options').texture_import_data.set_editor_property('material_search_location', unreal.MaterialSearchLocation.LOCAL) + if asset_data["material_search_location"] == "UnderParent": + task.get_editor_property('options').texture_import_data.set_editor_property('material_search_location', unreal.MaterialSearchLocation.UNDER_PARENT) + if asset_data["material_search_location"] == "UnderRoot": + task.get_editor_property('options').texture_import_data.set_editor_property('material_search_location', unreal.MaterialSearchLocation.UNDER_ROOT) + if asset_data["material_search_location"] == "AllAssets": + task.get_editor_property('options').texture_import_data.set_editor_property('material_search_location', unreal.MaterialSearchLocation.ALL_ASSETS) + + if asset_data["type"] == "StaticMesh": + # unreal.FbxStaticMeshImportData + task.get_editor_property('options').static_mesh_import_data.set_editor_property('combine_meshes', True) + if "auto_generate_collision" in asset_data: + task.get_editor_property('options').static_mesh_import_data.set_editor_property('auto_generate_collision', asset_data["auto_generate_collision"]) + if "static_mesh_lod_group" in asset_data: + if asset_data["static_mesh_lod_group"]: + task.get_editor_property('options').static_mesh_import_data.set_editor_property('static_mesh_lod_group', asset_data["static_mesh_lod_group"]) + if "generate_lightmap_u_vs" in asset_data: + task.get_editor_property('options').static_mesh_import_data.set_editor_property('generate_lightmap_u_vs', asset_data["generate_lightmap_u_vs"]) + + if asset_data["type"] == "SkeletalMesh" or asset_data["type"] == "Animation": + # unreal.FbxSkeletalMeshImportData + task.get_editor_property('options').skeletal_mesh_import_data.set_editor_property('import_morph_targets', True) + task.get_editor_property('options').skeletal_mesh_import_data.set_editor_property('convert_scene', True) + task.get_editor_property('options').skeletal_mesh_import_data.set_editor_property('normal_import_method', unreal.FBXNormalImportMethod.FBXNIM_IMPORT_NORMALS_AND_TANGENTS) + + # ###############[ pre import ]################ + + # Check is the file alredy exit + if additional_data: + if "preview_import_path" in additional_data: + task_asset_full_path = task.destination_path+"/"+additional_data["preview_import_path"]+"."+additional_data["preview_import_path"] + find_asset = unreal.find_asset(task_asset_full_path) + if find_asset: + + # Vertex color + + asset_import_data = find_asset.get_editor_property('asset_import_data') + if vertex_color_import_option: + asset_import_data.set_editor_property('vertex_color_import_option', vertex_color_import_option) + + if vertex_override_color: + asset_import_data.set_editor_property('vertex_override_color', vertex_override_color.to_rgbe()) + + # ###############[ import asset ]################ + + print("Import task") + if asset_data["type"] == "Animation": + ''' + For animation the script will import a skeletal mesh and remove after. + If the skeletal mesh alredy exist try to remove. + ''' + + AssetName = asset_data["name"] + AssetName = ValidUnrealAssetsName(AssetName) + AssetPath = "SkeletalMesh'"+asset_data["full_import_path"]+"/"+AssetName+"."+AssetName+"'" + + if unreal.EditorAssetLibrary.does_asset_exist(AssetPath): + oldAsset = unreal.EditorAssetLibrary.find_asset_data(AssetPath) + if oldAsset.asset_class == "SkeletalMesh": + unreal.EditorAssetLibrary.delete_asset(AssetPath) + + unreal.AssetToolsHelpers.get_asset_tools().import_asset_tasks([task]) + + if len(task.imported_object_paths) > 0: + asset = unreal.find_asset(task.imported_object_paths[0]) + else: + asset = None + + if asset is None: + ImportFailList.append('Error zero imported object for: ' + asset_data["name"]) + return + + if asset_data["type"] == "Animation": + # For animation remove the extra mesh + p = task.imported_object_paths[0] + if type(unreal.find_asset(p)) is not unreal.AnimSequence: + animAssetName = p.split('.')[0]+'_anim.'+p.split('.')[1]+'_anim' + animAssetNameDesiredPath = p.split('.')[0]+'.'+p.split('.')[1] + animAsset = unreal.find_asset(animAssetName) + if animAsset is not None: + unreal.EditorAssetLibrary.delete_asset(p) + unreal.EditorAssetLibrary.rename_asset(animAssetName, animAssetNameDesiredPath) + asset = animAsset + else: + ImportFailList.append('animAsset ' + asset_data["name"] + ' not found for after inport: ' + animAssetName) + return + + # ###############[ Post treatment ]################ + + asset_import_data = asset.get_editor_property('asset_import_data') + if asset_data["type"] == "StaticMesh": + if "static_mesh_lod_group" in asset_data: + if asset_data["static_mesh_lod_group"]: + asset.set_editor_property('lod_group', asset_data["static_mesh_lod_group"]) + if "use_custom_light_map_resolution" in asset_data: + if asset_data["use_custom_light_map_resolution"]: + if "light_map_resolution" in asset_data: + asset.set_editor_property('light_map_resolution', asset_data["light_map_resolution"]) + + if "collision_trace_flag" in asset_data: + if asset_data["collision_trace_flag"] == "CTF_UseDefault": + asset.get_editor_property('body_setup').set_editor_property('collision_trace_flag', unreal.CollisionTraceFlag.CTF_USE_DEFAULT) + elif asset_data["collision_trace_flag"] == "CTF_UseSimpleAndComplex": + asset.get_editor_property('body_setup').set_editor_property('collision_trace_flag', unreal.CollisionTraceFlag.CTF_USE_SIMPLE_AND_COMPLEX) + elif asset_data["collision_trace_flag"] == "CTF_UseSimpleAsComplex": + asset.get_editor_property('body_setup').set_editor_property('collision_trace_flag', unreal.CollisionTraceFlag.CTF_USE_SIMPLE_AS_COMPLEX) + elif asset_data["collision_trace_flag"] == "CTF_UseComplexAsSimple": + asset.get_editor_property('body_setup').set_editor_property('collision_trace_flag', unreal.CollisionTraceFlag.CTF_USE_COMPLEX_AS_SIMPLE) + + if asset_data["type"] == "StaticMesh": + if "generate_lightmap_u_vs" in asset_data: + asset_import_data.set_editor_property('generate_lightmap_u_vs', asset_data["generate_lightmap_u_vs"]) # Import data + unreal.EditorStaticMeshLibrary.set_generate_lightmap_uv(asset, asset_data["generate_lightmap_u_vs"]) # Build settings at lod + + if asset_data["type"] == "SkeletalMesh": + asset_import_data.set_editor_property('normal_import_method', unreal.FBXNormalImportMethod.FBXNIM_IMPORT_NORMALS_AND_TANGENTS) + + # Socket + if asset_data["type"] == "SkeletalMesh": + # Import the SkeletalMesh socket(s) + sockets_to_add = additional_data["Sockets"] + skeleton = asset.get_editor_property('skeleton') + for socket in sockets_to_add: + old_socket = asset.find_socket(socket["SocketName"]) + if old_socket: + # Edit socket + pass + # old_socket.relative_location = socket["Location"] + # old_socket.relative_rotation = socket["Rotation"] + # old_socket.relative_scale = socket["Scale"] + + else: + # Create socket + pass + # new_socket = unreal.SkeletalMeshSocket(asset) + # new_socket.socket_name = socket["SocketName"] + # new_socket.bone_name = socket["BoneName"] + # new_socket.relative_location = socket["Location"] + # new_socket.relative_rotation = socket["Rotation"] + # new_socket.relative_scale = socket["Scale"] + # NEED UNREAL ENGINE IMPLEMENTATION IN PYTHON API. + # skeleton.add_socket(new_socket) + + # Lod + if asset_data["type"] == "StaticMesh" or asset_data["type"] == "SkeletalMesh": + if asset_data["type"] == "StaticMesh": + unreal.EditorStaticMeshLibrary.remove_lods(asset) # Import the StaticMesh lod(s) + + if asset_data["type"] == "SkeletalMesh" or asset_data["type"] == "StaticMesh": + + def ImportStaticLod(lod_name, lod_number): + if "LevelOfDetail" in additional_data: + if lod_name in additional_data["LevelOfDetail"]: + lodTask = unreal.AssetImportTask() + lodTask.filename = additional_data["LevelOfDetail"][lod_name] + destination_path = os.path.normpath(asset_data["full_import_path"]).replace('\\', '/') + lodTask.destination_path = destination_path + lodTask.automated = True + lodTask.replace_existing = True + + # Set vertex color import settings to replicate base StaticMesh's behaviour + if asset_data["type"] == "Alembic": + lodTask.set_editor_property('options', unreal.AbcImportSettings()) + else: + lodTask.set_editor_property('options', unreal.FbxImportUI()) + + lodTask.get_editor_property('options').static_mesh_import_data.set_editor_property('vertex_color_import_option', vertex_color_import_option) + lodTask.get_editor_property('options').static_mesh_import_data.set_editor_property('vertex_override_color', vertex_override_color.to_rgbe()) + + print(destination_path, additional_data["LevelOfDetail"][lod_name]) + unreal.AssetToolsHelpers.get_asset_tools().import_asset_tasks([lodTask]) + if len(lodTask.imported_object_paths) > 0: + lodAsset = unreal.find_asset(lodTask.imported_object_paths[0]) + slot_replaced = unreal.EditorStaticMeshLibrary.set_lod_from_static_mesh(asset, lod_number, lodAsset, 0, True) + unreal.EditorAssetLibrary.delete_asset(lodTask.imported_object_paths[0]) + + def ImportSkeletalLod(lod_name, lod_number): + if "LevelOfDetail" in additional_data: + if lod_name in additional_data["LevelOfDetail"]: + # Unreal python no longer support Skeletal mesh LODS import. + pass + + if asset_data["type"] == "StaticMesh": + ImportStaticLod("lod_1", 1) + ImportStaticLod("lod_2", 2) + ImportStaticLod("lod_3", 3) + ImportStaticLod("lod_4", 4) + ImportStaticLod("lod_5", 5) + + elif asset_data["type"] == "SkeletalMesh": + ImportSkeletalLod("lod_1", 1) + ImportSkeletalLod("lod_2", 2) + ImportSkeletalLod("lod_3", 3) + ImportSkeletalLod("lod_4", 4) + ImportSkeletalLod("lod_5", 5) + + # Vertex color + if vertex_override_color: + asset_import_data.set_editor_property('vertex_override_color', vertex_override_color.to_rgbe()) + + if vertex_color_import_option: + asset_import_data.set_editor_property('vertex_color_import_option', vertex_color_import_option) + + # #################################[EndChange] + if asset_data["type"] == "StaticMesh" or asset_data["type"] == "SkeletalMesh": + unreal.EditorAssetLibrary.save_loaded_asset(asset) + ImportedList.append([asset, asset_data["type"]]) + + ImportTask() + + # Process import + + print('========================= Import started ! =========================') + print(import_assets_data["assets"]) + + # Import assets with a specific order + + for asset in GetAssetByType("Alembic"): + ImportAsset(asset) + for asset in GetAssetByType("StaticMesh"): + ImportAsset(asset) + for asset in GetAssetByType("SkeletalMesh"): + ImportAsset(asset) + for asset in GetAssetByType("Animation"): + ImportAsset(asset) + + print('========================= Full import completed ! =========================') + + # import result + StaticMesh_ImportedList = [] + SkeletalMesh_ImportedList = [] + Alembic_ImportedList = [] + Animation_ImportedList = [] + for asset in ImportedList: + if asset[1] == 'StaticMesh': + StaticMesh_ImportedList.append(asset[0]) + elif asset[1] == 'SkeletalMesh': + SkeletalMesh_ImportedList.append(asset[0]) + elif asset[1] == 'Alembic': + Alembic_ImportedList.append(asset[0]) + else: + Animation_ImportedList.append(asset[0]) + + print('Imported StaticMesh: '+str(len(StaticMesh_ImportedList))) + print('Imported SkeletalMesh: '+str(len(SkeletalMesh_ImportedList))) + print('Imported Alembic: '+str(len(Alembic_ImportedList))) + print('Imported Animation: '+str(len(Animation_ImportedList))) + print('Import failled: '+str(len(ImportFailList))) + for error in ImportFailList: + print(error) + + # Select asset(s) in content browser + PathList = [] + for asset in (StaticMesh_ImportedList + SkeletalMesh_ImportedList + Alembic_ImportedList + Animation_ImportedList): + PathList.append(asset.get_path_name()) + unreal.EditorAssetLibrary.sync_browser_to_objects(PathList) + print('=========================') + + if len(ImportFailList) > 0: + return 'Some asset(s) could not be imported.' + else: + return 'Assets imported with success !' + + +print("Start importing assets.") + +if CheckTasks(): + print(ImportAllAssets()) + +print("Importing assets finished.")