Merge b51a0d38e2
into 9f21df035b
This commit is contained in:
commit
733628ec9f
|
@ -14,3 +14,7 @@ pub struct Animations {
|
||||||
/// this is for convenience, because currently , Bevy's gltf parsing inserts `AnimationPlayers` "one level down"
|
/// 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
|
/// ie armature/root for animated models, which means more complex queries to trigger animations that we want to avoid
|
||||||
pub struct AnimationPlayerLink(pub Entity);
|
pub struct AnimationPlayerLink(pub Entity);
|
||||||
|
|
||||||
|
#[derive(Component, Reflect, Default, Debug)]
|
||||||
|
#[reflect(Component)]
|
||||||
|
pub struct Animated;
|
|
@ -120,6 +120,7 @@ impl Plugin for BlueprintsPlugin {
|
||||||
.register_type::<MaterialInfo>()
|
.register_type::<MaterialInfo>()
|
||||||
.register_type::<SpawnHere>()
|
.register_type::<SpawnHere>()
|
||||||
.register_type::<Animations>()
|
.register_type::<Animations>()
|
||||||
|
.register_type::<Animated>()
|
||||||
.register_type::<BlueprintsList>()
|
.register_type::<BlueprintsList>()
|
||||||
.register_type::<Vec<String>>()
|
.register_type::<Vec<String>>()
|
||||||
.register_type::<HashMap<String, Vec<String>>>()
|
.register_type::<HashMap<String, Vec<String>>>()
|
||||||
|
|
Binary file not shown.
|
@ -20,87 +20,113 @@ def remove_unwanted_custom_properties(object):
|
||||||
for component_name in object.keys():
|
for component_name in object.keys():
|
||||||
if not is_component_valid(object, component_name):
|
if not is_component_valid(object, component_name):
|
||||||
to_remove.append(component_name)
|
to_remove.append(component_name)
|
||||||
|
|
||||||
for cp in custom_properties_to_filter_out + to_remove:
|
for cp in custom_properties_to_filter_out + to_remove:
|
||||||
if cp in object:
|
if cp in object:
|
||||||
del object[cp]
|
del object[cp]
|
||||||
|
|
||||||
def duplicate_object(object):
|
# TODO: rename actions ?
|
||||||
obj_copy = object.copy()
|
def copy_animation_data(source, target):
|
||||||
if object.data:
|
"""if source.data:
|
||||||
|
data = source.data.copy()
|
||||||
|
target.data = data"""
|
||||||
|
if source.animation_data and source.animation_data:
|
||||||
|
#print("copying animation data from", source.name, "to", target.name)
|
||||||
|
"""print("I have animation data")
|
||||||
|
ad = source.animation_data
|
||||||
|
if ad.action:
|
||||||
|
print(source.name,'uses',ad.action.name)
|
||||||
|
for t in ad.nla_tracks:
|
||||||
|
for s in t.strips:
|
||||||
|
print(source.name,'uses',s.action.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')
|
||||||
|
# we add an "animated" flag component
|
||||||
|
target['Animated'] = '()'
|
||||||
|
"""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))"""
|
||||||
|
|
||||||
|
def duplicate_object(object, parent, combine_mode, destination_collection, library_collections, legacy_mode, nester=""):
|
||||||
|
copy = None
|
||||||
|
if object.instance_type == 'COLLECTION' and (combine_mode == 'Split' or (combine_mode == 'EmbedExternal' and (object.instance_collection.name in library_collections)) ):
|
||||||
|
#print("creating empty for", object.name, object.instance_collection.name, library_collections, combine_mode)
|
||||||
|
collection_name = object.instance_collection.name
|
||||||
|
original_name = object.name
|
||||||
|
|
||||||
|
object.name = original_name + "____bak"
|
||||||
|
empty_obj = make_empty(original_name, object.location, object.rotation_euler, object.scale, destination_collection)
|
||||||
|
"""we inject the collection/blueprint name, as a component called 'BlueprintName', but we only do this in the empty, not the original object"""
|
||||||
|
empty_obj['BlueprintName'] = '"'+collection_name+'"' if legacy_mode else '("'+collection_name+'")'
|
||||||
|
empty_obj['SpawnHere'] = '()'
|
||||||
|
|
||||||
|
# we also inject a list of all sub blueprints, so that the bevy side can preload them
|
||||||
|
if not legacy_mode:
|
||||||
|
root_node = CollectionNode()
|
||||||
|
root_node.name = "root"
|
||||||
|
children_per_collection = {}
|
||||||
|
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":[]}
|
||||||
|
|
||||||
|
# we copy custom properties over from our original object to our empty
|
||||||
|
for component_name, component_value in object.items():
|
||||||
|
if component_name not in custom_properties_to_filter_out and is_component_valid(object, component_name): #copy only valid properties
|
||||||
|
empty_obj[component_name] = component_value
|
||||||
|
copy = empty_obj
|
||||||
|
else:
|
||||||
|
# for objects which are NOT collection instances
|
||||||
|
# we create a copy of our object and its children, to leave the original one as it is
|
||||||
|
original_name = object.name
|
||||||
|
object.name = original_name + "____bak"
|
||||||
|
copy = object.copy()
|
||||||
|
copy.name = original_name
|
||||||
|
# FIXME: orphan data comes from this one, not even sure if this copying is needed at all
|
||||||
|
"""if object.data:
|
||||||
data = object.data.copy()
|
data = object.data.copy()
|
||||||
obj_copy.data = data
|
obj_copy.data = data"""
|
||||||
if object.animation_data and object.animation_data.action:
|
|
||||||
obj_copy.animation_data.action = object.animation_data.action.copy()
|
|
||||||
return obj_copy
|
|
||||||
|
|
||||||
#also removes unwanted custom_properties for all objects in hiearchy
|
destination_collection.objects.link(copy)
|
||||||
def duplicate_object_recursive(object, parent, collection):
|
|
||||||
original_name = object.name
|
|
||||||
object.name = original_name + "____bak"
|
|
||||||
copy = duplicate_object(object)
|
|
||||||
copy.name = original_name
|
|
||||||
collection.objects.link(copy)
|
|
||||||
|
|
||||||
remove_unwanted_custom_properties(copy)
|
"""if object.parent == None:
|
||||||
|
if parent_empty is not None:
|
||||||
|
copy.parent = parent_empty
|
||||||
|
if object.animation_data:
|
||||||
|
copy['Animated'] = '()'"""
|
||||||
|
|
||||||
if parent:
|
print(nester, "copy", copy)
|
||||||
|
# do this both for empty replacements & normal copies
|
||||||
|
if parent is not None:
|
||||||
copy.parent = parent
|
copy.parent = parent
|
||||||
|
remove_unwanted_custom_properties(copy)
|
||||||
|
copy_animation_data(object, copy)
|
||||||
|
|
||||||
for child in object.children:
|
for child in object.children:
|
||||||
duplicate_object_recursive(child, copy, collection)
|
duplicate_object(child, copy, combine_mode, destination_collection, library_collections, legacy_mode, nester+" ")
|
||||||
return copy
|
|
||||||
|
|
||||||
|
|
||||||
# copies the contents of a collection into another one while replacing library instances with empties
|
# copies the contents of a collection into another one while replacing library instances with empties
|
||||||
def copy_hollowed_collection_into(source_collection, destination_collection, parent_empty=None, filter=None, library_collections=[], addon_prefs={}):
|
def copy_hollowed_collection_into(source_collection, destination_collection, parent_empty=None, filter=None, library_collections=[], addon_prefs={}):
|
||||||
collection_instances_combine_mode = getattr(addon_prefs, "collection_instances_combine_mode")
|
collection_instances_combine_mode = getattr(addon_prefs, "collection_instances_combine_mode")
|
||||||
legacy_mode = getattr(addon_prefs, "export_legacy_mode")
|
legacy_mode = getattr(addon_prefs, "export_legacy_mode")
|
||||||
collection_instances_combine_mode= collection_instances_combine_mode
|
collection_instances_combine_mode= collection_instances_combine_mode
|
||||||
|
|
||||||
for object in source_collection.objects:
|
for object in source_collection.objects:
|
||||||
|
if object.name.endswith("____bak"): # some objects could already have been handled, ignore them
|
||||||
|
continue
|
||||||
if filter is not None and filter(object) is False:
|
if filter is not None and filter(object) is False:
|
||||||
continue
|
continue
|
||||||
#check if a specific collection instance does not have an ovveride for combine_mode
|
#check if a specific collection instance does not have an ovveride for combine_mode
|
||||||
combine_mode = object['_combine'] if '_combine' in object else collection_instances_combine_mode
|
combine_mode = object['_combine'] if '_combine' in object else collection_instances_combine_mode
|
||||||
|
parent = parent_empty
|
||||||
if object.instance_type == 'COLLECTION' and (combine_mode == 'Split' or (combine_mode == 'EmbedExternal' and (object.instance_collection.name in library_collections)) ):
|
duplicate_object(object, parent, combine_mode, destination_collection, library_collections, legacy_mode)
|
||||||
#print("creating empty for", object.name, object.instance_collection.name, library_collections, combine_mode)
|
|
||||||
collection_name = object.instance_collection.name
|
# for every child-collection of the source, copy its content into a new sub-collection of the destination
|
||||||
original_name = object.name
|
|
||||||
|
|
||||||
object.name = original_name + "____bak"
|
|
||||||
empty_obj = make_empty(original_name, object.location, object.rotation_euler, object.scale, destination_collection)
|
|
||||||
"""we inject the collection/blueprint name, as a component called 'BlueprintName', but we only do this in the empty, not the original object"""
|
|
||||||
empty_obj['BlueprintName'] = '"'+collection_name+'"' if legacy_mode else '("'+collection_name+'")'
|
|
||||||
empty_obj['SpawnHere'] = '()'
|
|
||||||
|
|
||||||
# we also inject a list of all sub blueprints, so that the bevy side can preload them
|
|
||||||
if not legacy_mode:
|
|
||||||
root_node = CollectionNode()
|
|
||||||
root_node.name = "root"
|
|
||||||
children_per_collection = {}
|
|
||||||
print("collection stuff", original_name)
|
|
||||||
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":[]}
|
|
||||||
|
|
||||||
|
|
||||||
# we copy custom properties over from our original object to our empty
|
|
||||||
for component_name, component_value in object.items():
|
|
||||||
if component_name not in custom_properties_to_filter_out and is_component_valid(object, component_name): #copy only valid properties
|
|
||||||
empty_obj[component_name] = component_value
|
|
||||||
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
|
|
||||||
|
|
||||||
# for every sub-collection of the source, copy its content into a new sub-collection of the destination
|
|
||||||
for collection in source_collection.children:
|
for collection in source_collection.children:
|
||||||
original_name = collection.name
|
original_name = collection.name
|
||||||
collection.name = original_name + "____bak"
|
collection.name = original_name + "____bak"
|
||||||
|
@ -108,7 +134,6 @@ def copy_hollowed_collection_into(source_collection, destination_collection, par
|
||||||
|
|
||||||
if parent_empty is not None:
|
if parent_empty is not None:
|
||||||
collection_placeholder.parent = parent_empty
|
collection_placeholder.parent = parent_empty
|
||||||
|
|
||||||
copy_hollowed_collection_into(
|
copy_hollowed_collection_into(
|
||||||
source_collection = collection,
|
source_collection = collection,
|
||||||
destination_collection = destination_collection,
|
destination_collection = destination_collection,
|
||||||
|
@ -117,6 +142,8 @@ def copy_hollowed_collection_into(source_collection, destination_collection, par
|
||||||
library_collections = library_collections,
|
library_collections = library_collections,
|
||||||
addon_prefs=addon_prefs
|
addon_prefs=addon_prefs
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
|
@ -138,14 +165,14 @@ def clear_hollow_scene(temp_scene, original_root_collection):
|
||||||
# reset original names
|
# reset original names
|
||||||
restore_original_names(original_root_collection)
|
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_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:
|
for object in temp_scene_objects:
|
||||||
|
print("removing", object.name)
|
||||||
bpy.data.objects.remove(object, do_unlink=True)
|
bpy.data.objects.remove(object, do_unlink=True)
|
||||||
# remove the temporary scene
|
# 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
|
# convenience utility to get lists of scenes
|
||||||
def get_scenes(addon_prefs):
|
def get_scenes(addon_prefs):
|
||||||
|
|
|
@ -19,7 +19,6 @@ def setup_data(request):
|
||||||
|
|
||||||
def finalizer():
|
def finalizer():
|
||||||
print("\nPerforming teardown...")
|
print("\nPerforming teardown...")
|
||||||
get_orphan_data()
|
|
||||||
|
|
||||||
if os.path.exists(models_path):
|
if os.path.exists(models_path):
|
||||||
shutil.rmtree(models_path)
|
shutil.rmtree(models_path)
|
||||||
|
@ -38,7 +37,10 @@ def setup_data(request):
|
||||||
|
|
||||||
def get_orphan_data():
|
def get_orphan_data():
|
||||||
orphan_meshes = [m.name for m in bpy.data.meshes if m.users == 0]
|
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):
|
def test_export_do_not_export_blueprints(setup_data):
|
||||||
auto_export_operator = bpy.ops.export_scenes.auto_gltf
|
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"], "World.glb")) == True
|
||||||
assert os.path.exists(os.path.join(setup_data["models_path"], "library", "Blueprint1.glb")) == False
|
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):
|
def test_export_custom_blueprints_path(setup_data):
|
||||||
auto_export_operator = bpy.ops.export_scenes.auto_gltf
|
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"], "World.glb")) == True
|
||||||
assert os.path.exists(os.path.join(setup_data["models_path"], "another_library_path", "Blueprint1.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):
|
def test_export_materials_library(setup_data):
|
||||||
auto_export_operator = bpy.ops.export_scenes.auto_gltf
|
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["models_path"], "library", "Blueprint1.glb")) == True
|
||||||
assert os.path.exists(os.path.join(setup_data["materials_path"], "testing_materials_library.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):
|
def test_export_materials_library_custom_path(setup_data):
|
||||||
auto_export_operator = bpy.ops.export_scenes.auto_gltf
|
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["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["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 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
|
def test_export_collection_instances_combine_mode(setup_data): # TODO: change & check this
|
||||||
auto_export_operator = bpy.ops.export_scenes.auto_gltf
|
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.glb")) == True
|
||||||
assert os.path.exists(os.path.join(setup_data["models_path"], "World_dynamic.glb")) == False
|
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):
|
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", "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", "Blueprint4_nested.glb")) == True
|
||||||
assert os.path.exists(os.path.join(setup_data["models_path"], "library", "Blueprint5.glb")) == False
|
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):
|
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.glb")) == True
|
||||||
assert os.path.exists(os.path.join(setup_data["models_path"], "World_dynamic.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):
|
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"], "World.glb")) == True
|
||||||
assert os.path.exists(os.path.join(setup_data["models_path"], "library", "Blueprint1.glb")) == False
|
assert os.path.exists(os.path.join(setup_data["models_path"], "library", "Blueprint1.glb")) == False
|
||||||
|
assert len(get_orphan_data()) == 0
|
||||||
|
|
||||||
|
|
|
@ -56,7 +56,8 @@ def test_export_complex(setup_data):
|
||||||
# we use the global settings for that
|
# we use the global settings for that
|
||||||
export_props = {
|
export_props = {
|
||||||
"main_scene_names" : ['World'],
|
"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 = 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()
|
stored_settings.clear()
|
||||||
|
|
Loading…
Reference in New Issue