feat(auto_export): continued refactoring & internal improvements

* continued restructure of auto_export internals
 * split out get_levels_to_export
 * simplified, cleaned up & made get_collections_to_export more efficient (skipping useless computations based
on settings)
 * moved more settings to addon_prefs, created boilerplate to create copies & inject additional params
 * modified tracker's use of the above, so that the actual list of future exports is displayed
 * a lot of other tweaks & cleanups
This commit is contained in:
kaosat.dev 2024-04-10 22:06:54 +02:00
parent c2dc0324c3
commit 6a1594188e
6 changed files with 156 additions and 91 deletions

View File

@ -1,9 +1,15 @@
import copy
import json
import os
from types import SimpleNamespace
import bpy
import traceback
from .preferences import AutoExportGltfAddonPreferences
from .get_collections_to_export import get_collections_to_export
from .get_levels_to_export import get_levels_to_export
from .get_standard_exporter_settings import get_standard_exporter_settings
from .export_main_scenes import export_main_scene
from .export_blueprints import check_if_blueprint_on_disk, check_if_blueprints_exist, export_blueprints_from_collections
@ -29,10 +35,37 @@ def auto_export(changes_per_scene, changed_export_parameters, addon_prefs):
export_blueprints = getattr(addon_prefs,"export_blueprints")
export_output_folder = getattr(addon_prefs,"export_output_folder")
export_models_path = os.path.join(folder_path, export_output_folder)
export_materials_library = getattr(addon_prefs,"export_materials_library")
export_scene_settings = getattr(addon_prefs,"export_scene_settings")
# standard gltf export settings are stored differently
standard_gltf_exporter_settings = get_standard_exporter_settings()
gltf_extension = standard_gltf_exporter_settings.get("export_format", 'GLB')
gltf_extension = '.glb' if gltf_extension == 'GLB' else '.gltf'
# here we do a bit of workaround by creating an override # TODO: do this at the "UI" level
export_blueprints_path = os.path.join(folder_path, export_output_folder, getattr(addon_prefs,"export_blueprints_path")) if getattr(addon_prefs,"export_blueprints_path") != '' else folder_path
#print('addon_prefs', AutoExportGltfAddonPreferences.__annotations__)#)addon_prefs.__annotations__)
if hasattr(addon_prefs, "__annotations__") :
tmp = {}
for k in AutoExportGltfAddonPreferences.__annotations__:
item = AutoExportGltfAddonPreferences.__annotations__[k]
print("tutu",k, item.keywords.get('default', None) )
default = item.keywords.get('default', None)
tmp[k] = default
for (k, v) in addon_prefs.properties.items():
tmp[k] = v
addon_prefs = SimpleNamespace(**tmp) #copy.deepcopy(addon_prefs)
addon_prefs.__annotations__ = tmp
addon_prefs.export_blueprints_path = export_blueprints_path
addon_prefs.export_gltf_extension = gltf_extension
addon_prefs.export_models_path = export_models_path
[main_scene_names, level_scenes, library_scene_names, library_scenes] = get_scenes(addon_prefs)
print("main scenes", main_scene_names, "library_scenes", library_scene_names)
@ -49,14 +82,34 @@ def auto_export(changes_per_scene, changed_export_parameters, addon_prefs):
# export
if export_blueprints:
print("EXPORTING")
# create parent relations for all collections
(collections, collections_to_export, main_scenes_to_export, library_collections, collections_per_scene, blueprint_hierarchy, export_levels_path, gltf_extension) = get_collections_to_export(folder_path, export_output_folder, changes_per_scene, changed_export_parameters, addon_prefs)
# get blueprints/collections infos
(collections, collections_to_export, library_collections, collections_per_scene) = get_collections_to_export(changes_per_scene, changed_export_parameters, addon_prefs)
# get level/main scenes infos
(main_scenes_to_export) = get_levels_to_export(changes_per_scene, changed_export_parameters, addon_prefs)
# since materials export adds components we need to call this before blueprints are exported
# export materials & inject materials components into relevant objects
if export_materials_library:
export_materials(collections, library_scenes, folder_path, addon_prefs)
# update the list of tracked exports
exports_total = len(collections_to_export) + len(main_scenes_to_export) + (1 if export_materials_library else 0)
bpy.context.window_manager.auto_export_tracker.exports_total = exports_total
bpy.context.window_manager.auto_export_tracker.exports_count = exports_total
print("-------------------------------")
#print("collections: all:", collections)
#print("collections: changed:", changed_collections)
#print("collections: not found on disk:", collections_not_on_disk)
print("collections: in library:", library_collections)
print("collections: to export:", collections_to_export)
print("collections: per_scene:", collections_per_scene)
print("-------------------------------")
print("BLUEPRINTS: to export:", collections_to_export)
print("-------------------------------")
print("MAIN SCENES: to export:", main_scenes_to_export)
print("-------------------------------")
# backup current active scene
old_current_scene = bpy.context.scene
# backup current selections
@ -65,24 +118,19 @@ def auto_export(changes_per_scene, changed_export_parameters, addon_prefs):
# first export any main/level/world scenes
if len(main_scenes_to_export) > 0:
print("export MAIN scenes")
for scene_name in main_scene_names:
# we have more relaxed rules to determine if the main scenes have changed : any change is ok, (allows easier handling of changes, render settings etc)
do_export_main_scene = not export_change_detection or changed_export_parameters or scene_name in changes_per_scene.keys() or not check_if_blueprint_on_disk(scene_name, export_levels_path, gltf_extension)
if do_export_main_scene:
print(" exporting scene:", scene_name)
export_main_scene(bpy.data.scenes[scene_name], folder_path, addon_prefs, library_collections)
for scene_name in main_scenes_to_export:
print(" exporting scene:", scene_name)
export_main_scene(bpy.data.scenes[scene_name], folder_path, addon_prefs, library_collections)
# now deal with blueprints/collections
do_export_library_scene = not export_change_detection or changed_export_parameters or len(collections_to_export) > 0 # export_library_scene_name in changes_per_scene.keys()
do_export_library_scene = not export_change_detection or changed_export_parameters or len(collections_to_export) > 0
if do_export_library_scene:
print("export LIBRARY")
# we only want to go through the library scenes where our collections to export are present
for (scene_name, collections_to_export) in collections_per_scene.items():
print(" exporting collections from scene:", scene_name)
print(" collections to export", collections_to_export)
library_scene = bpy.data.scenes[scene_name]
export_blueprints_from_collections(collections_to_export, library_scene, folder_path, addon_prefs, blueprint_hierarchy, collections)
export_blueprints_from_collections(collections_to_export, folder_path, addon_prefs, collections)
# reset current scene from backup
bpy.context.window.scene = old_current_scene

View File

@ -7,7 +7,7 @@ from .export_gltf import (generate_gltf_export_preferences)
from ..helpers.helpers_scenes import clear_hollow_scene, copy_hollowed_collection_into
# export collections: all the collections that have an instance in the main scene AND any marked collections, even if they do not have instances
def export_collections(collections, folder_path, library_scene, addon_prefs, gltf_export_preferences, blueprint_hierarchy, library_collections):
def export_collections(collections, folder_path, addon_prefs, gltf_export_preferences, library_collections):
# save current active collection
active_collection = bpy.context.view_layer.active_layer_collection
@ -35,13 +35,13 @@ def export_collections(collections, folder_path, library_scene, addon_prefs, glt
bpy.context.view_layer.active_layer_collection = active_collection
def export_blueprints_from_collections(collections, library_scene, folder_path, addon_prefs, blueprint_hierarchy, library_collections):
def export_blueprints_from_collections(collections, folder_path, addon_prefs, library_collections):
export_output_folder = getattr(addon_prefs,"export_output_folder")
gltf_export_preferences = generate_gltf_export_preferences(addon_prefs)
export_blueprints_path = os.path.join(folder_path, export_output_folder, getattr(addon_prefs,"export_blueprints_path")) if getattr(addon_prefs,"export_blueprints_path") != '' else folder_path
try:
export_collections(collections, export_blueprints_path, library_scene, addon_prefs, gltf_export_preferences, blueprint_hierarchy, library_collections)
export_collections(collections, export_blueprints_path, addon_prefs, gltf_export_preferences, library_collections)
except Exception as error:
print("failed to export collections to gltf: ", error)
raise error

View File

@ -1,59 +1,59 @@
import os
import bpy
from .get_standard_exporter_settings import get_standard_exporter_settings
from .export_blueprints import check_if_blueprint_on_disk, check_if_blueprints_exist, export_blueprints_from_collections
from ..helpers.helpers_collections import get_exportable_collections
from ..helpers.helpers_collections import (get_collections_in_library, get_exportable_collections, get_collections_per_scene, find_collection_ascendant_target_collection)
from ..helpers.helpers_scenes import (get_scenes, )
def get_collections_to_export(folder_path, export_output_folder, changes_per_scene, changed_export_parameters, addon_prefs):
def get_collections_to_export(changes_per_scene, changed_export_parameters, addon_prefs):
export_change_detection = getattr(addon_prefs, "export_change_detection")
export_materials_library = getattr(addon_prefs,"export_materials_library")
export_gltf_extension = getattr(addon_prefs, "export_gltf_extension", ".glb")
export_blueprints_path = getattr(addon_prefs,"export_blueprints_path", "")
# standard gltf export settings are stored differently
standard_gltf_exporter_settings = get_standard_exporter_settings()
[main_scene_names, level_scenes, library_scene_names, library_scenes] = get_scenes(addon_prefs)
collection_parents = dict()
for collection in bpy.data.collections:
collection_parents[collection.name] = None
for collection in bpy.data.collections:
for ch in collection.children:
collection_parents[ch.name] = collection.name
# get a list of all collections actually in use
(collections, blueprint_hierarchy) = get_exportable_collections(level_scenes, library_scenes, addon_prefs)
collections_to_export = collections # just for clarity
# first check if all collections have already been exported before (if this is the first time the exporter is run
# in your current Blender session for example)
export_blueprints_path = os.path.join(folder_path, export_output_folder, getattr(addon_prefs,"export_blueprints_path")) if getattr(addon_prefs,"export_blueprints_path") != '' else folder_path
export_levels_path = os.path.join(folder_path, export_output_folder)
print("export_change_detection", export_change_detection, export_gltf_extension, export_blueprints_path)
gltf_extension = standard_gltf_exporter_settings.get("export_format", 'GLB')
gltf_extension = '.glb' if gltf_extension == 'GLB' else '.gltf'
collections_not_on_disk = check_if_blueprints_exist(collections, export_blueprints_path, gltf_extension)
changed_collections = []
# if the export parameters have changed, bail out early
# we need to re_export everything if the export parameters have been changed
if export_change_detection and not changed_export_parameters:
changed_collections = []
for scene, objects in changes_per_scene.items():
print(" changed scene", scene)
for obj_name, obj in objects.items():
object_collections = list(obj.users_collection) if hasattr(obj, 'users_collection') else []
object_collection_names = list(map(lambda collection: collection.name, object_collections))
# first check if all collections have already been exported before (if this is the first time the exporter is run
# in your current Blender session for example)
collections_not_on_disk = check_if_blueprints_exist(collections, export_blueprints_path, export_gltf_extension)
if len(object_collection_names) > 1:
print("ERRROR for",obj_name,"objects in multiple collections not supported")
else:
object_collection_name = object_collection_names[0] if len(object_collection_names) > 0 else None
#recurse updwards until we find one of our collections (or not)
matching_collection = find_collection_ascendant_target_collection(collection_parents, collections, object_collection_name)
if matching_collection is not None:
changed_collections.append(matching_collection)
# create parent relations for all collections # TODO: optimise this
collection_parents = dict()
for collection in bpy.data.collections:
collection_parents[collection.name] = None
for collection in bpy.data.collections:
for ch in collection.children:
collection_parents[ch.name] = collection.name
collections_to_export = list(set(changed_collections + collections_not_on_disk)) if export_change_detection else collections
# determine which collections have changed
for scene, objects in changes_per_scene.items():
print(" changed scene", scene)
for obj_name, obj in objects.items():
object_collections = list(obj.users_collection) if hasattr(obj, 'users_collection') else []
object_collection_names = list(map(lambda collection: collection.name, object_collections))
# we need to re_export everything if the export parameters have been changed # TODO: perhaps do this BEFORE the rest above for better perfs
collections_to_export = collections if changed_export_parameters else collections_to_export
if len(object_collection_names) > 1:
print("ERRROR for",obj_name,"objects in multiple collections not supported")
else:
object_collection_name = object_collection_names[0] if len(object_collection_names) > 0 else None
#recurse updwards until we find one of our collections (or not)
matching_collection = find_collection_ascendant_target_collection(collection_parents, collections, object_collection_name)
if matching_collection is not None:
changed_collections.append(matching_collection)
collections_to_export = list(set(changed_collections + collections_not_on_disk))
# this needs to be done based on all previously collected collections, not the ones that we filter out based on their presence in the library scenes
collections_per_scene = get_collections_per_scene(collections_to_export, library_scenes)
# collections that do not come from a library should not be exported as seperate blueprints
@ -61,27 +61,5 @@ def get_collections_to_export(folder_path, export_output_folder, changes_per_sce
library_collections = get_collections_in_library(library_scenes)
collections_to_export = list(set(collections_to_export).intersection(set(library_collections)))
main_scenes_to_export = [scene_name for scene_name in main_scene_names if not export_change_detection or changed_export_parameters or scene_name in changes_per_scene.keys() or not check_if_blueprint_on_disk(scene_name, export_levels_path, gltf_extension)]
# update the list of tracked exports
exports_total = len(collections_to_export) + len(main_scenes_to_export) + (1 if export_materials_library else 0)
bpy.context.window_manager.auto_export_tracker.exports_total = exports_total
bpy.context.window_manager.auto_export_tracker.exports_count = exports_total
print("-------------------------------")
print("collections: all:", collections)
print("collections: changed:", changed_collections)
print("collections: not found on disk:", collections_not_on_disk)
print("collections: in library:", library_collections)
print("collections: to export:", collections_to_export)
print("collections: per_scene:", collections_per_scene)
print("-------------------------------")
print("BLUEPRINTS: to export:", collections_to_export)
print("-------------------------------")
print("MAIN SCENES: to export:", main_scenes_to_export)
print("-------------------------------")
return (collections, collections_to_export, main_scenes_to_export, library_collections, collections_per_scene, blueprint_hierarchy, export_levels_path, gltf_extension)
# all collections, collections to export
return (collections, collections_to_export, library_collections, collections_per_scene)

View File

@ -0,0 +1,15 @@
import bpy
from .export_blueprints import check_if_blueprint_on_disk
from ..helpers.helpers_scenes import (get_scenes, )
def get_levels_to_export(changes_per_scene, changed_export_parameters, addon_prefs):
export_change_detection = getattr(addon_prefs, "export_change_detection")
export_gltf_extension = getattr(addon_prefs, "export_gltf_extension")
export_models_path = getattr(addon_prefs, "export_models_path")
[main_scene_names, level_scenes, library_scene_names, library_scenes] = get_scenes(addon_prefs)
# determine list of main scenes to export
# we have more relaxed rules to determine if the main scenes have changed : any change is ok, (allows easier handling of changes, render settings etc)
main_scenes_to_export = [scene_name for scene_name in main_scene_names if not export_change_detection or changed_export_parameters or scene_name in changes_per_scene.keys() or not check_if_blueprint_on_disk(scene_name, export_models_path, export_gltf_extension)]
return (main_scenes_to_export)

View File

@ -1,13 +1,18 @@
import json
import os
from types import SimpleNamespace
import bpy
from bpy.types import (PropertyGroup)
from bpy.props import (PointerProperty, IntProperty, StringProperty)
from .get_collections_to_export import get_collections_to_export
from ..constants import TEMPSCENE_PREFIX
from .internals import CollectionsToExport
from ..helpers.helpers_scenes import (get_scenes)
from ..helpers.helpers_collections import (get_exportable_collections)
from .preferences import AutoExportGltfAddonPreferences
class AutoExportTracker(PropertyGroup):
@ -135,16 +140,44 @@ class AutoExportTracker(PropertyGroup):
# get a list of exportable collections for display
# keep it simple, just use Simplenamespace for compatibility with the rest of our code
addon_prefs = SimpleNamespace(**get_auto_exporter_settings())
print("addon prefs", addon_prefs)
addon_prefs.export_marked_assets = True
[_, level_scenes, _, library_scenes] = get_scenes(addon_prefs)
(collections, _) = get_exportable_collections(level_scenes, library_scenes, addon_prefs)
tmp = {}
for k in AutoExportGltfAddonPreferences.__annotations__:
item = AutoExportGltfAddonPreferences.__annotations__[k]
print("tutu",k, item.keywords.get('default', None) )
default = item.keywords.get('default', None)
tmp[k] = default
auto_settings = get_auto_exporter_settings()
for k in auto_settings:
print("k", k, auto_settings[k])
tmp[k] = auto_settings[k]
tmp['__annotations__'] = tmp
# path to the current blend file
file_path = bpy.data.filepath
# Get the folder
folder_path = os.path.dirname(file_path)
export_output_folder =tmp["export_output_folder"]
export_models_path = os.path.join(folder_path, export_output_folder)
export_blueprints_path = os.path.join(folder_path, export_output_folder, tmp["export_blueprints_path"]) if tmp["export_blueprints_path"] != '' else folder_path
tmp["export_blueprints_path"] = export_blueprints_path
tmp["export_models_path"] = export_models_path
addon_prefs = SimpleNamespace(**tmp)
#
#addon_prefs.export_blueprints_path = export_blueprints_path
#addon_prefs.export_gltf_extension = gltf_extension
#addon_prefs.export_models_path = export_models_path
(collections, collections_to_export, library_collections, collections_per_scene) = get_collections_to_export(cls.changed_objects_per_scene, False, addon_prefs)
print("collections to export", collections_to_export)
try:
# we save this list of collections in the context
bpy.context.window_manager.exportedCollections.clear()
#TODO: add error handling for this
for collection_name in collections:
for collection_name in collections_to_export:
ui_info = bpy.context.window_manager.exportedCollections.add()
ui_info.name = collection_name
except Exception as error:

View File

@ -138,25 +138,16 @@ def get_collections_per_scene(collection_names, library_scenes):
return collections_per_scene
def get_collections_in_library(library_scenes):
"""all_collections = []
all_collection_names = []
for main_scene in main_scenes:
(collection_names, collections) = get_used_collections(main_scene)
all_collection_names = all_collection_names + list(collection_names)
all_collections = all_collections + collections"""
# now that we have the collections that are in use by collection instances, check if those collections are actully present in the library scenes
collections = []
collection_names = []
for library_scene in library_scenes:
root_collection = library_scene.collection
for collection in traverse_tree(root_collection):
collections.append(collection)
collection_names.append(collection.name)
return collection_names
def get_collection_hierarchy(root_col, levels=1):
"""Read hierarchy of the collections in the scene"""
level_lookup = {}