diff --git a/crates/bevy_gltf_blueprints/src/animation.rs b/crates/bevy_gltf_blueprints/src/animation.rs index 4a18ec4..96108e0 100644 --- a/crates/bevy_gltf_blueprints/src/animation.rs +++ b/crates/bevy_gltf_blueprints/src/animation.rs @@ -14,3 +14,7 @@ pub struct Animations { /// this is for convenience, because currently , Bevy's gltf parsing inserts `AnimationPlayers` "one level down" /// ie armature/root for animated models, which means more complex queries to trigger animations that we want to avoid pub struct AnimationPlayerLink(pub Entity); + +#[derive(Component, Reflect, Default, Debug)] +#[reflect(Component)] +pub struct Animated; \ No newline at end of file diff --git a/crates/bevy_gltf_blueprints/src/lib.rs b/crates/bevy_gltf_blueprints/src/lib.rs index 78642cd..13f2d36 100644 --- a/crates/bevy_gltf_blueprints/src/lib.rs +++ b/crates/bevy_gltf_blueprints/src/lib.rs @@ -120,6 +120,7 @@ impl Plugin for BlueprintsPlugin { .register_type::() .register_type::() .register_type::() + .register_type::() .register_type::() .register_type::>() .register_type::>>() diff --git a/testing/bevy_example/assets/testing.blend b/testing/bevy_example/assets/testing.blend index 5af50a4..7661545 100644 Binary files a/testing/bevy_example/assets/testing.blend and b/testing/bevy_example/assets/testing.blend differ diff --git a/tools/gltf_auto_export/helpers/helpers_scenes.py b/tools/gltf_auto_export/helpers/helpers_scenes.py index 55009c9..72d103d 100644 --- a/tools/gltf_auto_export/helpers/helpers_scenes.py +++ b/tools/gltf_auto_export/helpers/helpers_scenes.py @@ -27,13 +27,42 @@ def remove_unwanted_custom_properties(object): def duplicate_object(object): obj_copy = object.copy() - if object.data: + # FIXME: orphan data comes from this one + """if object.data: data = object.data.copy() - obj_copy.data = data - if object.animation_data and object.animation_data.action: - obj_copy.animation_data.action = object.animation_data.action.copy() + obj_copy.data = data""" + copy_animation_data(object, obj_copy) + """if object.animation_data and object.animation_data.action: + if obj_copy.animation_data == None: + obj_copy.animation_data_create() + obj_copy.animation_data.action = object.animation_data.action.copy()""" return obj_copy +# TODO: rename actions +def copy_animation_data(source, target): + """if source.data: + data = source.data.copy() + target.data = data""" + if source.animation_data and source.animation_data.action: + print("current action name", source.animation_data.action.name) + print("copying animation data from", source.name, "to", target.name) + + + """if target.animation_data == None: + target.animation_data_create() + target.animation_data.action = source.animation_data.action.copy()""" + # alternative method, using the build in link animation operator + with bpy.context.temp_override(active_object=source, selected_editable_objects=[target]): + bpy.ops.object.make_links_data(type='ANIMATION') + + """print("copying animation data for", source.name, target.animation_data) + + + properties = [p.identifier for p in source.animation_data.bl_rna.properties if not p.is_readonly] + for prop in properties: + print("copying stuff", prop) + setattr(target.animation_data, prop, getattr(source.animation_data, prop))""" + #also removes unwanted custom_properties for all objects in hiearchy def duplicate_object_recursive(object, parent, collection): original_name = object.name @@ -83,7 +112,16 @@ def copy_hollowed_collection_into(source_collection, destination_collection, par get_sub_collections([object.instance_collection], root_node, children_per_collection) empty_obj["BlueprintsList"] = f"({json.dumps(dict(children_per_collection))})" #empty_obj["Assets"] = {"Animations": [], "Materials": [], "Models":[], "Textures":[], "Audio":[], "Other":[]} - + if object.animation_data: + print("I have animation data") + ad = object.animation_data + if ad.action: + print(object.name,'uses',ad.action.name) + for t in ad.nla_tracks: + for s in t.strips: + print(object.name,'uses',s.action.name) + empty_obj['Animated'] = '()' + copy_animation_data(object, empty_obj) # we copy custom properties over from our original object to our empty for component_name, component_value in object.items(): @@ -92,13 +130,13 @@ def copy_hollowed_collection_into(source_collection, destination_collection, par if parent_empty is not None: empty_obj.parent = parent_empty else: - # we create a copy of our object and its children, to leave the original one as it is if object.parent == None: copy = duplicate_object_recursive(object, None, destination_collection) - if parent_empty is not None: - copy.parent = parent_empty + copy.parent = parent_empty + if object.animation_data: + copy['Animated'] = '()' # for every sub-collection of the source, copy its content into a new sub-collection of the destination for collection in source_collection.children: @@ -138,14 +176,14 @@ def clear_hollow_scene(temp_scene, original_root_collection): # reset original names restore_original_names(original_root_collection) - # remove empties (only needed when we go via ops ????) + # remove any data we created temp_root_collection = temp_scene.collection - temp_scene_objects = [o for o in temp_root_collection.objects] + temp_scene_objects = [o for o in temp_root_collection.all_objects] for object in temp_scene_objects: + print("removing", object.name) bpy.data.objects.remove(object, do_unlink=True) # remove the temporary scene - bpy.data.scenes.remove(temp_scene) - + bpy.data.scenes.remove(temp_scene, do_unlink=True) # convenience utility to get lists of scenes def get_scenes(addon_prefs): diff --git a/tools/gltf_auto_export/tests/test_basic.py b/tools/gltf_auto_export/tests/test_basic.py index 7d01954..8d3c1e5 100644 --- a/tools/gltf_auto_export/tests/test_basic.py +++ b/tools/gltf_auto_export/tests/test_basic.py @@ -19,7 +19,6 @@ def setup_data(request): def finalizer(): print("\nPerforming teardown...") - get_orphan_data() if os.path.exists(models_path): shutil.rmtree(models_path) @@ -38,7 +37,10 @@ def setup_data(request): def get_orphan_data(): orphan_meshes = [m.name for m in bpy.data.meshes if m.users == 0] - # print("orphan meshes before", orphan_meshes) + orphan_objects = [m.name for m in bpy.data.objects if m.users == 0] + + #print("orphan meshes before", orphan_meshes) + return orphan_meshes + orphan_objects def test_export_do_not_export_blueprints(setup_data): auto_export_operator = bpy.ops.export_scenes.auto_gltf @@ -61,6 +63,9 @@ def test_export_do_not_export_blueprints(setup_data): ) assert os.path.exists(os.path.join(setup_data["models_path"], "World.glb")) == True assert os.path.exists(os.path.join(setup_data["models_path"], "library", "Blueprint1.glb")) == False + orphan_data = get_orphan_data() + assert len(orphan_data) == 0 + def test_export_custom_blueprints_path(setup_data): auto_export_operator = bpy.ops.export_scenes.auto_gltf @@ -83,6 +88,7 @@ def test_export_custom_blueprints_path(setup_data): ) assert os.path.exists(os.path.join(setup_data["models_path"], "World.glb")) == True assert os.path.exists(os.path.join(setup_data["models_path"], "another_library_path", "Blueprint1.glb")) == True + assert len(get_orphan_data()) == 0 def test_export_materials_library(setup_data): auto_export_operator = bpy.ops.export_scenes.auto_gltf @@ -107,7 +113,7 @@ def test_export_materials_library(setup_data): assert os.path.exists(os.path.join(setup_data["models_path"], "library", "Blueprint1.glb")) == True assert os.path.exists(os.path.join(setup_data["materials_path"], "testing_materials_library.glb")) == True - + assert len(get_orphan_data()) == 0 def test_export_materials_library_custom_path(setup_data): auto_export_operator = bpy.ops.export_scenes.auto_gltf @@ -134,6 +140,7 @@ def test_export_materials_library_custom_path(setup_data): assert os.path.exists(os.path.join(setup_data["models_path"], "library", "Blueprint1.glb")) == True assert os.path.exists(os.path.join(setup_data["materials_path"], "testing_materials_library.glb")) == False assert os.path.exists(os.path.join(setup_data["other_materials_path"], "testing_materials_library.glb")) == True + assert len(get_orphan_data()) == 0 def test_export_collection_instances_combine_mode(setup_data): # TODO: change & check this auto_export_operator = bpy.ops.export_scenes.auto_gltf @@ -160,6 +167,7 @@ def test_export_collection_instances_combine_mode(setup_data): # TODO: change & assert os.path.exists(os.path.join(setup_data["models_path"], "World.glb")) == True assert os.path.exists(os.path.join(setup_data["models_path"], "World_dynamic.glb")) == False + assert len(get_orphan_data()) == 0 def test_export_do_not_export_marked_assets(setup_data): @@ -188,6 +196,7 @@ def test_export_do_not_export_marked_assets(setup_data): assert os.path.exists(os.path.join(setup_data["models_path"], "library", "Blueprint3.glb")) == True assert os.path.exists(os.path.join(setup_data["models_path"], "library", "Blueprint4_nested.glb")) == True assert os.path.exists(os.path.join(setup_data["models_path"], "library", "Blueprint5.glb")) == False + assert len(get_orphan_data()) == 0 def test_export_separate_dynamic_and_static_objects(setup_data): @@ -216,6 +225,7 @@ def test_export_separate_dynamic_and_static_objects(setup_data): assert os.path.exists(os.path.join(setup_data["models_path"], "World.glb")) == True assert os.path.exists(os.path.join(setup_data["models_path"], "World_dynamic.glb")) == True + assert len(get_orphan_data()) == 0 def test_export_should_not_generate_orphan_data(setup_data): @@ -239,4 +249,5 @@ def test_export_should_not_generate_orphan_data(setup_data): ) assert os.path.exists(os.path.join(setup_data["models_path"], "World.glb")) == True assert os.path.exists(os.path.join(setup_data["models_path"], "library", "Blueprint1.glb")) == False + assert len(get_orphan_data()) == 0 diff --git a/tools/gltf_auto_export/tests/test_bevy_integration.py b/tools/gltf_auto_export/tests/test_bevy_integration.py index e45114c..7c54fd4 100644 --- a/tools/gltf_auto_export/tests/test_bevy_integration.py +++ b/tools/gltf_auto_export/tests/test_bevy_integration.py @@ -56,7 +56,8 @@ def test_export_complex(setup_data): # we use the global settings for that export_props = { "main_scene_names" : ['World'], - "library_scene_names": ['Library'] + "library_scene_names": ['Library'], + # "export_format":'GLTF_SEPARATE' } stored_settings = bpy.data.texts[".gltf_auto_export_settings"] if ".gltf_auto_export_settings" in bpy.data.texts else bpy.data.texts.new(".gltf_auto_export_settings") stored_settings.clear()