diff --git a/Release log.txt b/Release log.txt index a1d198d9..ce016aa6 100644 --- a/Release log.txt +++ b/Release log.txt @@ -359,8 +359,11 @@ It is now possible to force the duration of an animation according to the scene - New: you can now choose a specific Vertex Paint to use when you Export. - New: You can now choose if you use scene or armature as origin for export in SkeletalMesh animations. - New: Manage more characters cultures in path or files names. +- New: Camera export take less time + new camera options in addon preferences - Change: The skeletal mesh are exported with scale at 1.0. - Change: You can use "Show Asset(s)" and "Show Action(s)" buttons for force to update the action cache. +- Change: Remove root bone modified to Add root bone. +- Change: Auto fix the "multiple root bones" potential error when export. - Fixed: Do a new preset do a script error. - Fixed: Export filter "Only select and active action" don't work with poses. - Fixed: The export action cache is not correctly updated. @@ -372,7 +375,9 @@ It is now possible to force the duration of an animation according to the scene - Fixed: With Unit Scale not at 0.01 SkeletalMesh have a wrong scale. - Fixed: Import NLA animation with script don't work. - Fixed: With Unit Scale not at 0.01 NLA animation will not export. +- Fixed: With Unit Scale not at 0.01 Blender 2.9.1 can crash with SkeletalMesh. - Fixed: Apply Wrong Bool Modifier do script error. - Fixed: Mesh with disabled Modifier do script error. - Fixed: Additional location in advanced properties don't work for the cameras. -- Fixed: Export Instanced complex Collections can break the exported mesh. \ No newline at end of file +- Fixed: Export Instanced complex Collections can break the exported mesh. +- Fixed: Camera Sequencer don't work in Unreal Engine 5. \ No newline at end of file diff --git a/blender-for-unrealengine/__init__.py b/blender-for-unrealengine/__init__.py index 08c70a7a..b5ab40ad 100644 --- a/blender-for-unrealengine/__init__.py +++ b/blender-for-unrealengine/__init__.py @@ -123,7 +123,8 @@ def register(): bpy.types.Scene.bfu_collision_socket_expanded = bpy.props.BoolProperty() bpy.types.Scene.bfu_lightmap_expanded = bpy.props.BoolProperty() bpy.types.Scene.bfu_nomenclature_properties_expanded = bpy.props.BoolProperty() - bpy.types.Scene.bfu_export_properties_expanded = bpy.props.BoolProperty() + bpy.types.Scene.bfu_export_filter_properties_expanded = bpy.props.BoolProperty() + bpy.types.Scene.bfu_export_process_properties_expanded = bpy.props.BoolProperty() bpy.types.Scene.bfu_script_tool_expanded = bpy.props.BoolProperty() bpy.types.Scene.bfu_active_object_tab = bpy.props.EnumProperty( @@ -160,7 +161,8 @@ def unregister(): del bpy.types.Scene.bfu_collision_socket_expanded del bpy.types.Scene.bfu_lightmap_expanded del bpy.types.Scene.bfu_nomenclature_properties_expanded - del bpy.types.Scene.bfu_export_properties_expanded + del bpy.types.Scene.bfu_export_filter_properties_expanded + del bpy.types.Scene.bfu_export_process_properties_expanded del bpy.types.Scene.bfu_script_tool_expanded del bpy.types.Scene.bfu_active_object_tab diff --git a/blender-for-unrealengine/bfu_addon_pref.py b/blender-for-unrealengine/bfu_addon_pref.py index fc14b504..45bda51b 100644 --- a/blender-for-unrealengine/bfu_addon_pref.py +++ b/blender-for-unrealengine/bfu_addon_pref.py @@ -83,13 +83,13 @@ class BFU_AP_AddonPreferences(bpy.types.AddonPreferences): default=False, ) - removeSkeletonRootBone: BoolProperty( - name=(ti('remove_skeleton_root_bone_name')), - description=(tt('remove_skeleton_root_bone_desc')), - default=True, + add_skeleton_root_bone: BoolProperty( + name=(ti('add_skeleton_root_bone_name')), + description=(tt('add_skeleton_root_bone_desc')), + default=False, ) - skeletonRootBoneName: StringProperty( + skeleton_root_bone_name: StringProperty( name=(ti('skeleton_root_bone_name_name')), description=(tt('skeleton_root_bone_name_desc')), default="ArmatureRoot", @@ -163,6 +163,18 @@ class BFU_AP_AddonPreferences(bpy.types.AddonPreferences): default=1, ) + exportCameraAsFBX: BoolProperty( + name=(ti('export_camera_as_fbx_name')), + description=(tt('export_camera_as_fbx_desc')), + default=False, + ) + + bakeOnlyKeyVisibleInCut: BoolProperty( + name=(ti('bake_only_key_visible_in_cut_name')), + description=(tt('bake_only_key_visible_in_cut_desc')), + default=True, + ) + ignoreNLAForAction: BoolProperty( name=(ti('ignore_nla_for_action_name')), description=(tt('ignore_nla_for_action_desc')), @@ -194,7 +206,7 @@ class BFU_AP_AddonPreferences(bpy.types.AddonPreferences): ) collisionColor: FloatVectorProperty( - name='Collision color.', + name=ti('collision_color_name'), description='Color of the collision in Blender', subtype='COLOR', size=4, @@ -203,7 +215,7 @@ class BFU_AP_AddonPreferences(bpy.types.AddonPreferences): ) notifyUnitScalePotentialError: BoolProperty( - name='Notify UnitScale (PotentialError)', + name=ti('notify_unit_scale_potential_error_name'), description=( 'Notify as potential error' + ' if the unit scale is not equal to 0.01.' @@ -263,25 +275,28 @@ def PropWithDocButton(tagetlayout, name, docOcticon): boxColumn = layout.column().split( factor=0.5 ) + ColumnLeft = boxColumn.column() + ColumnRight = boxColumn.column() - rootBone = boxColumn.box() + rootBone = ColumnLeft.box() LabelWithDocButton( rootBone, "SKELETON & ROOT BONE", "skeleton--root-bone" ) - rootBone.prop(self, "removeSkeletonRootBone") + rootBone.prop(self, "add_skeleton_root_bone") rootBoneName = rootBone.column() - rootBoneName.enabled = not self.removeSkeletonRootBone - rootBoneName.prop(self, "skeletonRootBoneName") + rootBoneName.enabled = self.add_skeleton_root_bone + rootBoneName.prop(self, "skeleton_root_bone_name") rootBone.prop(self, "rescaleFullRigAtExport") newRigScale = rootBone.column() newRigScale.enabled = self.rescaleFullRigAtExport == "custom_rescale" newRigScale.prop(self, "newRigScale") - socket = boxColumn.box() + ColumnLeft.label(text='') + socket = ColumnLeft.box() socket.label(text='SOCKET') socket.prop(self, "staticSocketsAdd90X") socket.prop(self, "rescaleSocketsAtExport") @@ -290,9 +305,13 @@ def PropWithDocButton(tagetlayout, name, docOcticon): socketRescale.prop(self, "staticSocketsImportedSize") socketRescale.prop(self, "skeletalSocketsImportedSize") - boxColumn = layout.column().split(factor=0.5) + ColumnLeft.label(text='') + camera = ColumnLeft.box() + camera.label(text='CAMERA') + camera.prop(self, "exportCameraAsFBX") + camera.prop(self, "bakeOnlyKeyVisibleInCut") - data = boxColumn.box() + data = ColumnRight.box() data.label(text='DATA') data.prop(self, "ignoreNLAForAction") PropWithDocButton(data, "correctExtremUVScale", "uv") @@ -301,17 +320,17 @@ def PropWithDocButton(tagetlayout, name, docOcticon): data.prop(self, "exportWithMetaData") data.prop(self, "revertExportPath") - script = boxColumn.box() - script.label(text='IMPORT SCRIPT') - script.prop(self, "useGeneratedScripts") - - boxColumn = layout.column().split(factor=0.5) - - other = boxColumn.box() + ColumnRight.label(text='') + other = ColumnRight.box() other.label(text='OTHER') other.prop(self, "collisionColor") other.prop(self, "notifyUnitScalePotentialError") + ColumnRight.label(text='') + script = ColumnRight.box() + script.label(text='IMPORT SCRIPT') + script.prop(self, "useGeneratedScripts") + updateButton = layout.row() updateButton.scale_y = 2.0 updateButton.operator("object.new_release_info", icon="TIME") diff --git a/blender-for-unrealengine/bfu_check_potential_error.py b/blender-for-unrealengine/bfu_check_potential_error.py index fced787d..b22bc91b 100644 --- a/blender-for-unrealengine/bfu_check_potential_error.py +++ b/blender-for-unrealengine/bfu_check_potential_error.py @@ -361,31 +361,21 @@ def CheckArmatureMultipleRoots(): # Check that skeleton have multiples roots for obj in objToCheck: if GetAssetType(obj) == "SkeletalMesh": - - rootBones = [] - 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) + rootBones = GetArmatureRootBones(obj) if len(rootBones) > 1: MyError = PotentialErrors.add() MyError.name = obj.name - MyError.type = 2 + MyError.type = 1 MyError.text = ( 'Object "'+obj.name + '" have Multiple roots bones.' + - ' Unreal only support single root bone.') - MyError.text += '\nRoot bones: ' + ' Unreal only support single root bone') + MyError.text += '\nA custom root bone will be added at the export.' + MyError.text += ' '+str(len(rootBones))+' root bones found: ' + MyError.text += '\n' for rootBone in rootBones: - MyError.text += rootBone.name+' ' + MyError.text += rootBone.name+', ' MyError.object = obj def CheckArmatureNoDeformBone(): diff --git a/blender-for-unrealengine/bfu_export_asset.py b/blender-for-unrealengine/bfu_export_asset.py index 7f872569..d3235f43 100644 --- a/blender-for-unrealengine/bfu_export_asset.py +++ b/blender-for-unrealengine/bfu_export_asset.py @@ -148,15 +148,26 @@ def ExportAllAssetByList(targetobjects, targetActionName, targetcollection): NumberAssetToExport = len(GetFinalAssetToExport()) - def UpdateProgress(time=None): - update_progress( - "Export assets", - len(scene.UnrealExportedAssetsList)/NumberAssetToExport, - time - ) - UpdateProgress() + def UpdateExportProgress(time=None): + exported_assets = len(scene.UnrealExportedAssetsList) + remain_assets = exported_assets/NumberAssetToExport + + wm = bpy.context.window_manager + + if remain_assets == NumberAssetToExport: + wm.progress_begin(0, remain_assets) + + wm.progress_update(exported_assets) + + if remain_assets == 0: + wm.progress_end() + + UpdateProgress("Export assets", remain_assets, time) + + UpdateExportProgress() # Export collections + print("Start Export collection(s)") if scene.static_collection_export: for col in GetCollectionToExport(scene): if col in targetcollection: @@ -169,13 +180,14 @@ def UpdateProgress(time=None): # Resets previous start/end frame scene.frame_start = UserStartFrame scene.frame_end = UserEndFrame - UpdateProgress() + UpdateExportProgress() # Export assets for obj in targetobjects: if obj.ExportEnum == "export_recursive": # Camera + print("Start Export camera(s)") if GetAssetType(obj) == "Camera" and IsValidObjectForExport(scene, obj): # Save current start/end frame UserStartFrame = scene.frame_start @@ -185,9 +197,10 @@ def UpdateProgress(time=None): # Resets previous start/end frame scene.frame_start = UserStartFrame scene.frame_end = UserEndFrame - UpdateProgress() + UpdateExportProgress() # StaticMesh + print("Start Export StaticMesh(s)") if GetAssetType(obj) == "StaticMesh" and IsValidObjectForExport(scene, obj): # Save current start/end frame @@ -198,11 +211,11 @@ def UpdateProgress(time=None): # Resets previous start/end frame scene.frame_start = UserStartFrame scene.frame_end = UserEndFrame - UpdateProgress() + UpdateExportProgress() # SkeletalMesh + print("Start Export SkeletalMesh(s)") if GetAssetType(obj) == "SkeletalMesh" and IsValidObjectForExport(scene, obj): - # Save current start/end frame UserStartFrame = scene.frame_start UserEndFrame = scene.frame_end @@ -211,9 +224,10 @@ def UpdateProgress(time=None): # Resets previous start/end frame scene.frame_start = UserStartFrame scene.frame_end = UserEndFrame - UpdateProgress() + UpdateExportProgress() # Alembic + print("Start Export Alembic(s)") if GetAssetType(obj) == "Alembic" and IsValidObjectForExport(scene, obj): # Save current start/end frame UserStartFrame = scene.frame_start @@ -223,9 +237,10 @@ def UpdateProgress(time=None): # Resets previous start/end frame scene.frame_start = UserStartFrame scene.frame_end = UserEndFrame - UpdateProgress() + UpdateExportProgress() # Action animation + print("Start Export Action(s)") if GetAssetType(obj) == "SkeletalMesh" and obj.visible_get(): for action in GetActionToExport(obj): if action.name in targetActionName: @@ -242,9 +257,10 @@ def UpdateProgress(time=None): # Resets previous start/end frame scene.frame_start = UserStartFrame scene.frame_end = UserEndFrame - UpdateProgress() + UpdateExportProgress() # NLA animation + print("Start Export NLA(s)") if IsValidActionForExport(scene, obj, "NLA"): if obj.ExportNLA: # Save current start/end frame @@ -256,7 +272,7 @@ def UpdateProgress(time=None): scene.frame_start = UserStartFrame scene.frame_end = UserEndFrame - UpdateProgress(counter.GetTime()) + UpdateExportProgress(counter.GetTime()) def ExportForUnrealEngine(): diff --git a/blender-for-unrealengine/bfu_export_single_camera.py b/blender-for-unrealengine/bfu_export_single_camera.py index 46ba79b5..b74b173f 100644 --- a/blender-for-unrealengine/bfu_export_single_camera.py +++ b/blender-for-unrealengine/bfu_export_single_camera.py @@ -117,28 +117,30 @@ def ExportSingleFbxCamera( VerifiDirs(absdirpath) fullpath = os.path.join(absdirpath, filename) - bpy.ops.export_scene.fbx( - filepath=fullpath, - 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 - ) + ExportCameraAsFBX = addon_prefs.exportCameraAsFBX + if ExportCameraAsFBX: + bpy.ops.export_scene.fbx( + filepath=fullpath, + 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 diff --git a/blender-for-unrealengine/bfu_export_single_fbx_action.py b/blender-for-unrealengine/bfu_export_single_fbx_action.py index 67541cf9..434a1b6d 100644 --- a/blender-for-unrealengine/bfu_export_single_fbx_action.py +++ b/blender-for-unrealengine/bfu_export_single_fbx_action.py @@ -91,7 +91,7 @@ def ExportSingleFbxAction( SelectParentAndDesiredChilds(obj) asset_name = PrepareExportName(obj, True) - data_to_remove = DuplicateSelectForExport() + data_to_remove = DuplicateSelectForExport(True) BaseTransform = obj.matrix_world.copy() active = bpy.context.view_layer.objects.active @@ -181,7 +181,7 @@ def ExportSingleFbxAction( ExportAutoProRig( filepath=fullpath, - # export_rig_name=GetDesiredExportArmatureName(), + # export_rig_name=GetDesiredExportArmatureName(active), bake_anim=True, anim_export_name_string=active.animation_data.action.name, mesh_smooth_type="FACE", diff --git a/blender-for-unrealengine/bfu_export_single_fbx_nla_anim.py b/blender-for-unrealengine/bfu_export_single_fbx_nla_anim.py index 7e1b061d..728e9949 100644 --- a/blender-for-unrealengine/bfu_export_single_fbx_nla_anim.py +++ b/blender-for-unrealengine/bfu_export_single_fbx_nla_anim.py @@ -86,7 +86,7 @@ def ExportSingleFbxNLAAnim( SelectParentAndDesiredChilds(obj) asset_name = PrepareExportName(obj, True) - data_to_remove = DuplicateSelectForExport() + data_to_remove = DuplicateSelectForExport(True) BaseTransform = obj.matrix_world.copy() active = bpy.context.view_layer.objects.active @@ -163,7 +163,7 @@ def ExportSingleFbxNLAAnim( if (export_procedure == "auto-rig-pro"): ExportAutoProRig( filepath=fullpath, - # export_rig_name=GetDesiredExportArmatureName(), + # export_rig_name=GetDesiredExportArmatureName(active), bake_anim=True, anim_export_name_string=active.animation_data.action.name, mesh_smooth_type="FACE", diff --git a/blender-for-unrealengine/bfu_export_single_skeletal_mesh.py b/blender-for-unrealengine/bfu_export_single_skeletal_mesh.py index 3420348c..f6f50738 100644 --- a/blender-for-unrealengine/bfu_export_single_skeletal_mesh.py +++ b/blender-for-unrealengine/bfu_export_single_skeletal_mesh.py @@ -110,6 +110,7 @@ def ExportSingleSkeletalMesh( # This will rescale the rig and unit scale to get a root bone egal to 1 ShouldRescaleRig = GetShouldRescaleRig(active) + if ShouldRescaleRig: rrf = GetRescaleRigFactor() # rigRescaleFactor @@ -169,7 +170,7 @@ def ExportSingleSkeletalMesh( if (export_procedure == "auto-rig-pro"): ExportAutoProRig( filepath=fullpath, - # export_rig_name=GetDesiredExportArmatureName(), + # export_rig_name=GetDesiredExportArmatureName(active), bake_anim=False, mesh_smooth_type="FACE" ) diff --git a/blender-for-unrealengine/bfu_export_utils.py b/blender-for-unrealengine/bfu_export_utils.py index db6b8cfc..15f6b1b1 100644 --- a/blender-for-unrealengine/bfu_export_utils.py +++ b/blender-for-unrealengine/bfu_export_utils.py @@ -121,7 +121,7 @@ def BakeArmatureAnimation(armature, frame_start, frame_end): SetCurrentSelection(SavedSelect) -def DuplicateSelectForExport(): +def DuplicateSelectForExport(apply_visual = False): # Note: Need look for a optimized duplicate, This is too long scene = bpy.context.scene @@ -150,8 +150,7 @@ def RemoveData(self): for currentSelectName in bpy.context.selected_objects: currentSelectNames.append(currentSelectName.name) - ApplyVisual = False - if ApplyVisual: + if apply_visual: # Visual Transform Apply bpy.ops.object.visual_transform_apply() @@ -217,7 +216,7 @@ def __init__(self, obj, is_armature): scene = bpy.context.scene if self.is_armature: - self.new_asset_name = GetDesiredExportArmatureName() + self.new_asset_name = GetDesiredExportArmatureName(obj) else: self.new_asset_name = obj.name # Keep the same name diff --git a/blender-for-unrealengine/bfu_ui.py b/blender-for-unrealengine/bfu_ui.py index 2dc61a68..37c245b2 100644 --- a/blender-for-unrealengine/bfu_ui.py +++ b/blender-for-unrealengine/bfu_ui.py @@ -2425,7 +2425,7 @@ def GetIfOneTypeCheck(): return {'FINISHED'} class BFU_OT_CopyImportAssetScriptCommand(Operator): - bl_label = "ImportAssetScript" + bl_label = "Copy import script (Assets)" bl_idname = "object.copy_importassetscript_command" bl_description = "Copy Import Asset Script command" @@ -2439,7 +2439,7 @@ def execute(self, context): return {'FINISHED'} class BFU_OT_CopyImportSequencerScriptCommand(Operator): - bl_label = "ImportSequencerScript" + bl_label = "Copy import script (Sequencer)" bl_idname = "object.copy_importsequencerscript_command" bl_description = "Copy Import Sequencer Script command" @@ -2517,8 +2517,8 @@ def execute(self, context): ) # exportProperty - bpy.types.Scene.bfu_export_filter = bpy.props.EnumProperty( - name="Export only select", + 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 select", "Export only the selected object(s)", 1), @@ -2526,8 +2526,7 @@ def execute(self, context): "Export only the selected object(s) and active action on this object", 2), ], description=( - "Check mark to export only selected export group." + - " (export_recursive objects and auto childs) "), + "Choose what need be export from asset list."), default="default" ) @@ -2599,8 +2598,8 @@ def draw(self, context): 'file_import_sequencer_script_name', icon='FILE') - bfu_ui_utils.LayoutSection(layout, "bfu_export_properties_expanded", "Export") - if scene.bfu_export_properties_expanded: + bfu_ui_utils.LayoutSection(layout, "bfu_export_filter_properties_expanded", "Export filters") + if scene.bfu_export_filter_properties_expanded: # Assets row = layout.row() @@ -2624,6 +2623,13 @@ def draw(self, context): 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) @@ -2636,10 +2642,6 @@ def draw(self, context): checkButton.operator("object.checkpotentialerror", icon='FILE_TICK') checkButton.operator("object.openpotentialerror", icon='LOOP_BACK', text="") - # exportProperty - exportOnlySelect = layout.row() - exportOnlySelect.prop(scene, 'bfu_export_filter') - exportButton = layout.row() exportButton.scale_y = 2.0 exportButton.operator("object.exportforunreal", icon='EXPORT') @@ -2647,15 +2649,13 @@ def draw(self, context): bfu_ui_utils.LayoutSection(layout, "bfu_script_tool_expanded", "Copy Import Script") if scene.bfu_script_tool_expanded: if addon_prefs.useGeneratedScripts: - layout.label( - text="Click on one of the buttons to copy the import command.", - icon='INFO') copyButton = layout.row() copyButton.operator("object.copy_importassetscript_command") copyButton.operator("object.copy_importsequencerscript_command") - layout.label( - text="Then you can paste it into the python console of unreal", - icon='INFO') + 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.)') diff --git a/blender-for-unrealengine/bfu_utils.py b/blender-for-unrealengine/bfu_utils.py index f77a0357..d357b3b1 100644 --- a/blender-for-unrealengine/bfu_utils.py +++ b/blender-for-unrealengine/bfu_utils.py @@ -225,6 +225,93 @@ def ResetSceneAtSave(self): layer_col_children.hide_viewport = childCol.hide_viewport +class NLA_Save(): + def __init__(self, nla_tracks): + self.nla_tracks_save = None + if nla_tracks is not None: + self.SaveTracks(nla_tracks) + + def SaveTracks(self, nla_tracks): + proxy_nla_tracks = [] + + for nla_track in nla_tracks: + proxy_nla_tracks.append(self.Proxy_NLA_Track(nla_track)) + self.nla_tracks_save = proxy_nla_tracks + + def ApplySaveOnTarget(self, target): + if target is None: + return + if target.animation_data is None: + return + for nla_track in self.nla_tracks_save: + new_nla_track = target.animation_data.nla_tracks.new() + #new_nla_track.active = nla_track.active + new_nla_track.is_solo = nla_track.is_solo + new_nla_track.lock = nla_track.lock + new_nla_track.mute = nla_track.mute + new_nla_track.name = nla_track.name + new_nla_track.select = nla_track.select + for strip in nla_track.strips: + if strip.action: + new_strip = new_nla_track.strips.new(strip.name, strip.frame_start, strip.action) + #new_strip.action = strip.action + new_strip.action_frame_end = strip.action_frame_end + new_strip.action_frame_start = strip.action_frame_start + #new_strip.active = strip.active + new_strip.blend_in = strip.blend_in + new_strip.blend_out = strip.blend_out + new_strip.blend_type = strip.blend_type + new_strip.extrapolation = strip.extrapolation + #new_strip.fcurves = strip.fcurves #TO DO + new_strip.frame_end = strip.frame_end + #new_strip.frame_start = strip.frame_start + new_strip.influence = strip.influence + #new_strip.modifiers = strip.modifiers #TO DO + new_strip.mute = strip.mute + #new_strip.name = strip.name + new_strip.repeat = strip.repeat + new_strip.scale = strip.scale + new_strip.select = strip.select + new_strip.strip_time = strip.strip_time + #new_strip.strips = strip.strips #TO DO + + class Proxy_NLA_Track(): + def __init__(self, nla_track): + if nla_track: + self.active = nla_track.active + self.is_solo = nla_track.is_solo + self.lock = nla_track.lock + self.mute = nla_track.mute + self.name = nla_track.name + self.select = nla_track.select + self.strips = [] + for strip in nla_track.strips: + self.strips.append(self.Proxy_NLA_Track_Strip(strip)) + + class Proxy_NLA_Track_Strip(): + def __init__(self, strip): + self.action = strip.action + self.action_frame_end = strip.action_frame_end + self.action_frame_start = strip.action_frame_start + self.active = strip.active + self.blend_in = strip.blend_in + self.blend_out = strip.blend_out + self.blend_type = strip.blend_type + self.extrapolation = strip.extrapolation + self.fcurves = strip.fcurves #TO DO + self.frame_end = strip.frame_end + self.frame_start = strip.frame_start + self.influence = strip.influence + self.modifiers = strip.modifiers #TO DO + self.mute = strip.mute + self.name = strip.name + self.repeat = strip.repeat + self.scale = strip.scale + self.select = strip.select + self.strip_time = strip.strip_time + #self.strips = strip.strips #TO DO + + class AnimationManagment(): def __init__(self): self.use_animation_data = False @@ -239,7 +326,7 @@ def SaveAnimationData(self, obj): self.action_extrapolation = obj.animation_data.action_extrapolation self.action_blend_type = obj.animation_data.action_blend_type self.action_influence = obj.animation_data.action_influence - self.nla_tracks_ref = obj.animation_data.nla_tracks + self.nla_tracks_save = NLA_Save(obj.animation_data.nla_tracks) self.use_animation_data = True else: self.use_animation_data = False @@ -257,7 +344,7 @@ def SetAnimationData(self, obj, copy_nla=False): obj.animation_data.action_extrapolation = self.action_extrapolation obj.animation_data.action_blend_type = self.action_blend_type obj.animation_data.action_influence = self.action_influence - + if copy_nla: #Clear nla_tracks nla_tracks_len = len(obj.animation_data.nla_tracks) @@ -265,36 +352,87 @@ def SetAnimationData(self, obj, copy_nla=False): obj.animation_data.nla_tracks.remove(obj.animation_data.nla_tracks[0]) #Add Current nla_tracks - for nla_track in self.nla_tracks_ref: - new_nla_track = obj.animation_data.nla_tracks.new() - #new_nla_track.active = nla_track.active - new_nla_track.is_solo = nla_track.is_solo - new_nla_track.lock = nla_track.lock - new_nla_track.mute = nla_track.mute - new_nla_track.name = nla_track.name - new_nla_track.select = nla_track.select - for strip in nla_track.strips: - new_strip = new_nla_track.strips.new(strip.name, strip.frame_start, strip.action) - #new_strip.action = strip.action - new_strip.action_frame_end = strip.action_frame_end - new_strip.action_frame_start = strip.action_frame_start - #new_strip.active = strip.active - new_strip.blend_in = strip.blend_in - new_strip.blend_out = strip.blend_out - new_strip.blend_type = strip.blend_type - new_strip.extrapolation = strip.extrapolation - #new_strip.fcurves = strip.fcurves #TO DO - new_strip.frame_end = strip.frame_end - #new_strip.frame_start = strip.frame_start - new_strip.influence = strip.influence - #new_strip.modifiers = strip.modifiers #TO DO - new_strip.mute = strip.mute - #new_strip.name = strip.name - new_strip.repeat = strip.repeat - new_strip.scale = strip.scale - new_strip.select = strip.select - new_strip.strip_time = strip.strip_time - #new_strip.strips = strip.strips #TO DO + + if self.nla_tracks_save is not None: + self.nla_tracks_save.ApplySaveOnTarget(obj) + + +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): + 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 def SafeModeSet(target_mode='OBJECT', obj=None): @@ -323,7 +461,8 @@ def GetTime(self): return time.perf_counter()-self.start -def update_progress(job_title, progress, time=None): +def UpdateProgress(job_title, progress, time=None): + length = 20 # modify this to change the length block = int(round(length*progress)) @@ -331,13 +470,12 @@ def update_progress(job_title, progress, time=None): 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" - sys.stdout.write(msg) - sys.stdout.flush() def RemoveUselessSpecificData(name, type): @@ -576,7 +714,7 @@ def StoreActions(self, obj, 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.") + #print("Stored action cache updated.") def GetStoredActions(self): actions = [] @@ -1019,6 +1157,7 @@ def ApplySkeletalExportScale(armature, rescale, target_animation_data = None): armature_animation_data = AnimationManagment() armature_animation_data.ClearAnimationData(armature) + armature.location = (0,0,0) bpy.ops.object.transform_apply( @@ -1027,6 +1166,7 @@ def ApplySkeletalExportScale(armature, rescale, target_animation_data = None): rotation=True, properties=True ) + armature.location = old_location*rescale @@ -1145,11 +1285,11 @@ def __init__(self, obj, action, type): objList = [] collectionList = [] - if scene.bfu_export_filter == "default": + if scene.bfu_export_selection_filter == "default": objList = GetAllobjectsByExportType("export_recursive") collectionList = GetCollectionToExport(scene) - elif scene.bfu_export_filter == "only_object" or scene.bfu_export_filter == "only_object_action": + elif scene.bfu_export_selection_filter == "only_object" or scene.bfu_export_selection_filter == "only_object_action": recuList = GetAllobjectsByExportType("export_recursive") for obj in bpy.context.selected_objects: @@ -1196,7 +1336,7 @@ def __init__(self, obj, action, type): "NlAnim")) for action in GetActionToExport(obj): - if scene.bfu_export_filter == "only_object_action": + 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")) @@ -1381,15 +1521,31 @@ def GetImportSequencerScriptCommand(): def GetAnimSample(obj): # return obj sample animation - # return 1000 #Debug return obj.SampleAnimForExport - -def GetDesiredExportArmatureName(): +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 = bpy.context.preferences.addons[__package__].preferences - if addon_prefs.removeSkeletonRootBone: - return "Armature" - return addon_prefs.skeletonRootBoneName + 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): @@ -1633,7 +1789,7 @@ def UpdateAreaLightMapList(list=None): for obj in objs: obj.computedStaticMeshLightMapRes = GetExportRealSurfaceArea(obj) UpdatedRes += 1 - update_progress( + UpdateProgress( "Update LightMap", (UpdatedRes/len(objs)), counter.GetTime()) diff --git a/blender-for-unrealengine/bfu_write_text.py b/blender-for-unrealengine/bfu_write_text.py index 40db82f3..ada16cb3 100644 --- a/blender-for-unrealengine/bfu_write_text.py +++ b/blender-for-unrealengine/bfu_write_text.py @@ -311,6 +311,7 @@ def getAllKeysByFcurves(obj, DataPath, DataValue, IsData=True): return keys return[(scene.frame_start, DataValue)] + class CameraDataAtFrame(): def __init__(self): @@ -323,62 +324,82 @@ def __init__(self): self.aperture_fstop = {} self.hide_viewport = {} - def EvaluateTracks(self, camera, frame_start, frame_end): - saveFrame = scene.frame_current - for frame in range(frame_start, frame_end+1): - scene.frame_set(frame) - - # Get Transfrom - - matrix = camera.matrix_world @ Matrix.Rotation(radians(90.0), 4, 'Y') @ Matrix.Rotation(radians(-90.0), 4, '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() - s = matrix.to_scale() - - array_location = [loc[0], loc[1]*-1, loc[2]] - array_rotation = [degrees(r[0]), degrees(r[1])*-1, degrees(r[2])*-1] - array_scale = [s[0], s[1], s[2]] - - transform = {} - transform["location_x"] = array_location[0] - transform["location_y"] = array_location[1] - transform["location_z"] = array_location[2] - transform["rotation_x"] = array_rotation[0] - transform["rotation_y"] = array_rotation[1] - transform["rotation_z"] = array_rotation[2] - transform["scale_x"] = array_scale[0] - transform["scale_y"] = array_scale[1] - transform["scale_z"] = array_scale[2] - self.transform_track[frame] = transform - - # Get FocalLength SensorWidth SensorHeight - self.lens[frame] = getOneKeysByFcurves(camera, "lens", camera.data.lens, frame) - self.sensor_width[frame] = getOneKeysByFcurves(camera, "sensor_width", camera.data.sensor_width, frame) - self.sensor_height[frame] = getOneKeysByFcurves(camera, "sensor_height", camera.data.sensor_height, frame) - - # Get FocusDistance - if camera.data.dof.focus_object is not None: - key = getCameraFocusDistance(camera, camera.data.dof.focus_object) * 100 * bpy.context.scene.unit_settings.scale_length + def EvaluateTracksAtFrame(self, camera, frame): + scene.frame_set(frame) - else: - key = getOneKeysByFcurves(camera, "dof.focus_distance", camera.data.dof.focus_distance, frame) * 100 * bpy.context.scene.unit_settings.scale_length + # Get Transfrom + matrix = camera.matrix_world @ Matrix.Rotation(radians(90.0), 4, 'Y') @ Matrix.Rotation(radians(-90.0), 4, '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() + s = matrix.to_scale() - if key > 0: - self.focus_distance[frame] = key - else: - self.focus_distance[frame] = 100000 # 100000 is default value in ue4 + array_location = [loc[0], loc[1]*-1, loc[2]] + array_rotation = [degrees(r[0]), degrees(r[1])*-1, degrees(r[2])*-1] + array_scale = [s[0], s[1], s[2]] + + transform = {} + transform["location_x"] = array_location[0] + transform["location_y"] = array_location[1] + transform["location_z"] = array_location[2] + transform["rotation_x"] = array_rotation[0] + transform["rotation_y"] = array_rotation[1] + transform["rotation_z"] = array_rotation[2] + transform["scale_x"] = array_scale[0] + transform["scale_y"] = array_scale[1] + transform["scale_z"] = array_scale[2] + self.transform_track[frame] = transform + + # Get FocalLength SensorWidth SensorHeight + self.lens[frame] = getOneKeysByFcurves(camera, "lens", camera.data.lens, frame) + self.sensor_width[frame] = getOneKeysByFcurves(camera, "sensor_width", camera.data.sensor_width, frame) + self.sensor_height[frame] = getOneKeysByFcurves(camera, "sensor_height", camera.data.sensor_height, frame) + + # Get FocusDistance + if camera.data.dof.focus_object is not None: + key = getCameraFocusDistance(camera, camera.data.dof.focus_object) * 100 * bpy.context.scene.unit_settings.scale_length + + else: + key = getOneKeysByFcurves(camera, "dof.focus_distance", camera.data.dof.focus_distance, frame) * 100 * bpy.context.scene.unit_settings.scale_length - # Write Aperture (Depth of Field) keys - if scene.render.engine == "BLENDER_EEVEE" or scene.render.engine == "CYCLES" or scene.render.engine == "BLENDER_WORKBENCH": - key = getOneKeysByFcurves(camera, "dof.aperture_fstop", camera.data.dof.aperture_fstop, frame) - self.aperture_fstop[frame] = key / bpy.context.scene.unit_settings.scale_length + if key > 0: + self.focus_distance[frame] = key + else: + self.focus_distance[frame] = 100000 # 100000 is default value in ue4 + + # Write Aperture (Depth of Field) keys + if scene.render.engine == "BLENDER_EEVEE" or scene.render.engine == "CYCLES" or scene.render.engine == "BLENDER_WORKBENCH": + key = getOneKeysByFcurves(camera, "dof.aperture_fstop", camera.data.dof.aperture_fstop, frame) + self.aperture_fstop[frame] = key / bpy.context.scene.unit_settings.scale_length + else: + self.aperture_fstop[frame] = 2.8 # 2.8 is default value in ue4 + + boolKey = getOneKeysByFcurves(camera, "hide_viewport", camera.hide_viewport, frame, False) + self.hide_viewport[frame] = (boolKey < 1) # Inversed for convert hide to spawn + + + def EvaluateTracks(self, camera, frame_start, frame_end): + scene = bpy.context.scene + addon_prefs = bpy.context.preferences.addons[__package__].preferences + + saveFrame = scene.frame_current + if camera is None: + return + + slms = TimelineMarkerSequence() + for frame in range(frame_start, frame_end+1): + + if addon_prefs.bakeOnlyKeyVisibleInCut: + marker_sequence = slms.GetMarkerSequenceAtFrame(frame) + if marker_sequence: + marker = marker_sequence.marker + if marker.camera == camera: + self.EvaluateTracksAtFrame(camera, frame) + else: - self.aperture_fstop[frame] = 2.8 # 2.8 is default value in ue4 + self.EvaluateTracksAtFrame(camera, frame) - boolKey = getOneKeysByFcurves(camera, "hide_viewport", camera.hide_viewport, frame, False) - self.hide_viewport[frame] = (boolKey < 1) # Inversed for convert hide to spawn scene.frame_set(saveFrame) pass diff --git a/blender-for-unrealengine/import/asset_import_script.py b/blender-for-unrealengine/import/asset_import_script.py index c48b8a94..6b29488a 100644 --- a/blender-for-unrealengine/import/asset_import_script.py +++ b/blender-for-unrealengine/import/asset_import_script.py @@ -21,14 +21,6 @@ def ImportAllAssets(): import json import string - ''' - if int(unreal.SystemLibrary.get_engine_version()[:4][2:]) >= 26: - import configparser as ConfigParser - else: - import ConfigParser - ''' - - # Prepare process import json_data_file = 'ImportAssetData.json' dir_path = os.path.dirname(os.path.realpath(__file__)) @@ -119,9 +111,10 @@ def ImportTask(): else: OriginSkeleton = None - + # 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 @@ -132,6 +125,13 @@ def GetSkeletalMeshImportData(): 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 GetMeshImportData(): if asset_data["type"] == "StaticMesh": return GetStaticMeshImportData() @@ -146,6 +146,7 @@ def GetMeshImportData(): 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 @@ -178,7 +179,12 @@ def GetMeshImportData(): # #################################[Change] # unreal.FbxImportUI - # https://docs.unrealengine.com/en-US/PythonAPI/class/FbxImportUI.html?highlight=fbximportui#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(): @@ -292,7 +298,8 @@ def GetMeshImportData(): if oldAsset.asset_class == "SkeletalMesh": unreal.EditorAssetLibrary.delete_asset(AssetPath) - print(unreal.AssetToolsHelpers.get_asset_tools().import_asset_tasks([task])) + 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: diff --git a/blender-for-unrealengine/import/sequencer_import_script.py b/blender-for-unrealengine/import/sequencer_import_script.py index 4d3677aa..94760d0a 100644 --- a/blender-for-unrealengine/import/sequencer_import_script.py +++ b/blender-for-unrealengine/import/sequencer_import_script.py @@ -27,13 +27,6 @@ def CreateSequencer(): import time import json - ''' - if int(unreal.SystemLibrary.get_engine_version()[:4][2:]) >= 26: - import configparser as ConfigParser - else: - import ConfigParser - ''' - # Prepare process import json_data_file = 'ImportSequencerData.json' dir_path = os.path.dirname(os.path.realpath(__file__)) @@ -52,6 +45,11 @@ def CreateSequencer(): unreal_import_location = sequence_data['unreal_import_location'] ImportedCamera = [] # (CameraName, CameraGuid) + def GetUnrealVersion(): + version = unreal.SystemLibrary.get_engine_version().split(".") + float_version = int(version[0]) + float(int(version[1])/100) + return float_version + def AddSequencerSectionTransformKeysByIniFile(sequencer_section, track_dict): for key in track_dict.keys(): value = track_dict[key] # (x,y,z x,y,z x,y,z) @@ -86,8 +84,7 @@ def AddSequencerSectionBoolKeysByIniFile(sequencer_section, track_dict): if seq is None: return 'ERROR: level sequencer factory_create fail' - print("Sequencer reference created") - print(seq) + print("Sequencer reference created", seq) # Process import print("========================= Import started ! =========================") @@ -103,7 +100,7 @@ def AddSequencerSectionBoolKeysByIniFile(sequencer_section, track_dict): seq.set_playback_start_seconds(startFrame/float(frameRateNumerator)) # set_playback_end_seconds camera_cut_track = seq.add_master_track(unreal.MovieSceneCameraCutTrack) camera_cut_track.set_editor_property('display_name', 'Imported Camera Cuts') - if int(unreal.SystemLibrary.get_engine_version()[:4][2:]) >= 26: + if GetUnrealVersion() >= 4.26: camera_cut_track.set_color_tint(unreal.Color(b=200, g=0, r=0, a=0)) else: pass @@ -164,13 +161,13 @@ def AddSequencerSectionBoolKeysByIniFile(sequencer_section, track_dict): TrackFocusDistance = camera_component_binding.add_track(unreal.MovieSceneFloatTrack) # Wtf this var name change every version or I do someting wrong??? :v - if int(unreal.SystemLibrary.get_engine_version()[:4][2:]) >= 26: + if GetUnrealVersion() >= 4.26: TrackFocusDistance.set_property_name_and_path('FocusSettings.ManualFocusDistance', 'FocusSettings.ManualFocusDistance') TrackFocusDistance.set_editor_property('display_name', 'Manual Focus Distance (Focus Settings)') - elif int(unreal.SystemLibrary.get_engine_version()[:4][2:]) >= 25: + elif GetUnrealVersion() >= 4.25: TrackFocusDistance.set_property_name_and_path('FocusSettings.ManualFocusDistance', 'FocusSettings.ManualFocusDistance') TrackFocusDistance.set_editor_property('display_name', 'Manual Focus Distance (Focus Settings)') - elif int(unreal.SystemLibrary.get_engine_version()[:4][2:]) >= 24: + elif GetUnrealVersion() >= 4.24: TrackFocusDistance.set_property_name_and_path('CurrentFocusDistance', 'CurrentFocusDistance') TrackFocusDistance.set_editor_property('display_name', 'Current Focus Distance') else: @@ -213,7 +210,7 @@ def AddSequencerSectionBoolKeysByIniFile(sequencer_section, track_dict): else: current_camera_binding = camera_binding - if int(unreal.SystemLibrary.get_engine_version()[:4][2:]) >= 26: + if GetUnrealVersion() >= 4.26: current_camera_binding.set_display_name(camera_data["name"]) else: pass @@ -265,7 +262,7 @@ def AddSequencerSectionBoolKeysByIniFile(sequencer_section, track_dict): print('=========================') # Select and open seq in content browser - if int(unreal.SystemLibrary.get_engine_version()[:4][2:]) >= 26: + if GetUnrealVersion() >= 4.26: unreal.AssetEditorSubsystem.open_editor_for_assets(unreal.AssetEditorSubsystem(), [unreal.load_asset(seq.get_path_name())]) else: unreal.AssetToolsHelpers.get_asset_tools().open_editor_for_assets([unreal.load_asset(seq.get_path_name())]) diff --git a/blender-for-unrealengine/languages/local_list/en_US.json b/blender-for-unrealengine/languages/local_list/en_US.json index 472276f3..74fb00e0 100644 --- a/blender-for-unrealengine/languages/local_list/en_US.json +++ b/blender-for-unrealengine/languages/local_list/en_US.json @@ -3,7 +3,7 @@ "intro": "Blender for Unreal Engine by Xavier Loux.", "bake_armature_action_name": "Bake Armature animation", "correct_extrem_uv_scale_name": "Correct Extrem UV Scale", - "remove_skeleton_root_bone_name": "Remove root bone", + "add_skeleton_root_bone_name": "Add root bone", "skeleton_root_bone_name_name": "Skeleton root bone name", "rescale_full_rig_at_export_name": "Rescale exported rig", "rescale_full_rig_at_export_auto_name": "Auto", @@ -17,13 +17,15 @@ "rescale_sockets_at_export_dont_rescale_name": "Dont Rescale", "static_sockets_imported_size_name": "StaticMesh sockets import size", "skeletal_sockets_imported_size_name": "SkeletalMesh sockets import size", + "export_camera_as_fbx_name": "Export camera as FBX", + "bake_only_key_visible_in_cut_name": "Bake Only Visible Cuts", "ignore_nla_for_action_name": "Ignore NLA for Actions", "export_with_custom_props_name": "Export custom properties", "export_with_meta_data_name": "Export meta data", "revert_export_path_name": "Revert all export path at each export.", "use_generated_scripts_name": "Use generated script for import assets and sequencer.", "collision_color_name": "Collision color", - "notify_unit_scale_potential_error_name": "Notify UnitScale (PotentialError)", + "notify_unit_scale_potential_error_name": "Notify UnitScale in potential error check", "write_text_additional_track_start": "This file was generated with the addons Blender for UnrealEngine : https://github.com/xavier150/Blender-For-UnrealEngine-Addons", "write_text_additional_track_end": "The script must be used in Unreal Engine Editor with Python plugins : https://docs.unrealengine.com/en-US/Engine/Editor/ScriptingAndAutomation/Python", "write_text_additional_track_camera": "This file contains additional Camera animation informations that is not supported with .fbx files", @@ -33,7 +35,7 @@ "tooltips": { "bake_armature_action_desc": "Bake Armature animation for export (Export will take more time).", "correct_extrem_uv_scale_desc": "Correct Extrem UV Scale for better UV quality in UE4 (Export will take more time).", - "remove_skeleton_root_bone_desc": "Remove the armature root bone.", + "add_skeleton_root_bone_desc": "Remove the armature root bone.", "skeleton_root_bone_name_desc": "Name of the armature when exported. This is used to change the root bone name. If egal \"Armature\" Ue4 will remove the Armature root bone.", "rescale_full_rig_at_export_desc": "This will rescale the full rig at the export with the all constraints.", "rescale_full_rig_at_export_auto_desc": "Rescale only if the the Unit Scale is not = to 0.01", @@ -47,6 +49,8 @@ "rescale_sockets_at_export_dont_rescale_desc": "Will not rescale the sockets. AUTO: 1 ([New scale} = 100 / [Unit scale]", "static_sockets_imported_size_desc": "ize of the socket when imported in Unreal Engine.", "skeletal_sockets_imported_size_desc": "Size of the socket when imported in Unreal Engine. AUTO: 1 ([New scale} = 100 / [Unit scale])", + "export_camera_as_fbx_desc": "You can Uncheck this for better export time when you export additional data (Export -> Export filter) and use sequencer import script.", + "bake_only_key_visible_in_cut_desc": "Bake camera only when visible in camera cuts.", "ignore_nla_for_action_desc": "This will export the action and ignore the all layer in Nonlinear Animation.", "export_with_custom_props_desc": "Process export with custom properties (Can be used for Metadata).", "export_with_meta_data_desc": "Process export with meta data.", diff --git a/docs/Examples/AssetsExample_CarScene.blend b/docs/Examples/AssetsExample_CarScene.blend index a8273d5d..aabaafdb 100644 Binary files a/docs/Examples/AssetsExample_CarScene.blend and b/docs/Examples/AssetsExample_CarScene.blend differ diff --git a/docs/Examples/AssetsExample_Collections.blend b/docs/Examples/AssetsExample_Collections.blend index ff992bd8..33f73905 100644 Binary files a/docs/Examples/AssetsExample_Collections.blend and b/docs/Examples/AssetsExample_Collections.blend differ diff --git a/docs/Examples/AssetsExample_MultipleRootBone.blend b/docs/Examples/AssetsExample_MultipleRootBone.blend new file mode 100644 index 00000000..00a51e45 Binary files /dev/null and b/docs/Examples/AssetsExample_MultipleRootBone.blend differ