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 json
import os import os
from types import SimpleNamespace
import bpy import bpy
import traceback import traceback
from .preferences import AutoExportGltfAddonPreferences
from .get_collections_to_export import get_collections_to_export 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_main_scenes import export_main_scene
from .export_blueprints import check_if_blueprint_on_disk, check_if_blueprints_exist, export_blueprints_from_collections 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_blueprints = getattr(addon_prefs,"export_blueprints")
export_output_folder = getattr(addon_prefs,"export_output_folder") 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_materials_library = getattr(addon_prefs,"export_materials_library")
export_scene_settings = getattr(addon_prefs,"export_scene_settings") 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) [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) 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 # export
if export_blueprints: if export_blueprints:
print("EXPORTING") print("EXPORTING")
# create parent relations for all collections # get blueprints/collections infos
(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) (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 # since materials export adds components we need to call this before blueprints are exported
# export materials & inject materials components into relevant objects # export materials & inject materials components into relevant objects
if export_materials_library: if export_materials_library:
export_materials(collections, library_scenes, folder_path, addon_prefs) 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 # backup current active scene
old_current_scene = bpy.context.scene old_current_scene = bpy.context.scene
# backup current selections # 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 # first export any main/level/world scenes
if len(main_scenes_to_export) > 0: if len(main_scenes_to_export) > 0:
print("export MAIN scenes") print("export MAIN scenes")
for scene_name in main_scene_names: for scene_name in 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) print(" exporting scene:", scene_name)
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) export_main_scene(bpy.data.scenes[scene_name], folder_path, addon_prefs, library_collections)
if do_export_main_scene:
print(" exporting scene:", scene_name)
export_main_scene(bpy.data.scenes[scene_name], folder_path, addon_prefs, library_collections)
# now deal with blueprints/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: if do_export_library_scene:
print("export LIBRARY") print("export LIBRARY")
# we only want to go through the library scenes where our collections to export are present # 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(): for (scene_name, collections_to_export) in collections_per_scene.items():
print(" exporting collections from scene:", scene_name) print(" exporting collections from scene:", scene_name)
print(" collections to export", collections_to_export) print(" collections to export", collections_to_export)
library_scene = bpy.data.scenes[scene_name] export_blueprints_from_collections(collections_to_export, folder_path, addon_prefs, collections)
export_blueprints_from_collections(collections_to_export, library_scene, folder_path, addon_prefs, blueprint_hierarchy, collections)
# reset current scene from backup # reset current scene from backup
bpy.context.window.scene = old_current_scene 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 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 # 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 # save current active collection
active_collection = bpy.context.view_layer.active_layer_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 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") export_output_folder = getattr(addon_prefs,"export_output_folder")
gltf_export_preferences = generate_gltf_export_preferences(addon_prefs) 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 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: 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: except Exception as error:
print("failed to export collections to gltf: ", error) print("failed to export collections to gltf: ", error)
raise error raise error

View File

@ -1,59 +1,59 @@
import os import os
import bpy 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 .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_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_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, ) 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_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) [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 # get a list of all collections actually in use
(collections, blueprint_hierarchy) = get_exportable_collections(level_scenes, library_scenes, addon_prefs) (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 print("export_change_detection", export_change_detection, export_gltf_extension, export_blueprints_path)
# 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 # if the export parameters have changed, bail out early
export_levels_path = os.path.join(folder_path, export_output_folder) # we need to re_export everything if the export parameters have been changed
if export_change_detection and not changed_export_parameters:
changed_collections = []
gltf_extension = standard_gltf_exporter_settings.get("export_format", 'GLB') # first check if all collections have already been exported before (if this is the first time the exporter is run
gltf_extension = '.glb' if gltf_extension == 'GLB' else '.gltf' # in your current Blender session for example)
collections_not_on_disk = check_if_blueprints_exist(collections, export_blueprints_path, gltf_extension) collections_not_on_disk = check_if_blueprints_exist(collections, export_blueprints_path, export_gltf_extension)
changed_collections = []
for scene, objects in changes_per_scene.items(): # create parent relations for all collections # TODO: optimise this
print(" changed scene", scene) collection_parents = dict()
for obj_name, obj in objects.items(): for collection in bpy.data.collections:
object_collections = list(obj.users_collection) if hasattr(obj, 'users_collection') else [] collection_parents[collection.name] = None
object_collection_names = list(map(lambda collection: collection.name, object_collections)) for collection in bpy.data.collections:
for ch in collection.children:
collection_parents[ch.name] = collection.name
if len(object_collection_names) > 1: # determine which collections have changed
print("ERRROR for",obj_name,"objects in multiple collections not supported") for scene, objects in changes_per_scene.items():
else: print(" changed scene", scene)
object_collection_name = object_collection_names[0] if len(object_collection_names) > 0 else None for obj_name, obj in objects.items():
#recurse updwards until we find one of our collections (or not) object_collections = list(obj.users_collection) if hasattr(obj, 'users_collection') else []
matching_collection = find_collection_ascendant_target_collection(collection_parents, collections, object_collection_name) object_collection_names = list(map(lambda collection: collection.name, object_collections))
if matching_collection is not None:
changed_collections.append(matching_collection)
collections_to_export = list(set(changed_collections + collections_not_on_disk)) if export_change_detection else collections 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)
# 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 = list(set(changed_collections + collections_not_on_disk))
collections_to_export = collections if changed_export_parameters else collections_to_export
# 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_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 # 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) library_collections = get_collections_in_library(library_scenes)
collections_to_export = list(set(collections_to_export).intersection(set(library_collections))) collections_to_export = list(set(collections_to_export).intersection(set(library_collections)))
# all collections, collections to export
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)] return (collections, collections_to_export, library_collections, collections_per_scene)
# 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)

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 json
import os
from types import SimpleNamespace from types import SimpleNamespace
import bpy import bpy
from bpy.types import (PropertyGroup) from bpy.types import (PropertyGroup)
from bpy.props import (PointerProperty, IntProperty, StringProperty) from bpy.props import (PointerProperty, IntProperty, StringProperty)
from .get_collections_to_export import get_collections_to_export
from ..constants import TEMPSCENE_PREFIX from ..constants import TEMPSCENE_PREFIX
from .internals import CollectionsToExport from .internals import CollectionsToExport
from ..helpers.helpers_scenes import (get_scenes) from ..helpers.helpers_scenes import (get_scenes)
from ..helpers.helpers_collections import (get_exportable_collections) from ..helpers.helpers_collections import (get_exportable_collections)
from .preferences import AutoExportGltfAddonPreferences
class AutoExportTracker(PropertyGroup): class AutoExportTracker(PropertyGroup):
@ -135,16 +140,44 @@ class AutoExportTracker(PropertyGroup):
# get a list of exportable collections for display # get a list of exportable collections for display
# keep it simple, just use Simplenamespace for compatibility with the rest of our code # 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) tmp = {}
addon_prefs.export_marked_assets = True for k in AutoExportGltfAddonPreferences.__annotations__:
[_, level_scenes, _, library_scenes] = get_scenes(addon_prefs) item = AutoExportGltfAddonPreferences.__annotations__[k]
(collections, _) = get_exportable_collections(level_scenes, library_scenes, addon_prefs) 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: try:
# we save this list of collections in the context # we save this list of collections in the context
bpy.context.window_manager.exportedCollections.clear() bpy.context.window_manager.exportedCollections.clear()
#TODO: add error handling for this #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 = bpy.context.window_manager.exportedCollections.add()
ui_info.name = collection_name ui_info.name = collection_name
except Exception as error: except Exception as error:

View File

@ -138,25 +138,16 @@ def get_collections_per_scene(collection_names, library_scenes):
return collections_per_scene return collections_per_scene
def get_collections_in_library(library_scenes): 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 # 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 = [] collections = []
collection_names = [] collection_names = []
for library_scene in library_scenes: for library_scene in library_scenes:
root_collection = library_scene.collection root_collection = library_scene.collection
for collection in traverse_tree(root_collection): for collection in traverse_tree(root_collection):
collections.append(collection) collections.append(collection)
collection_names.append(collection.name) collection_names.append(collection.name)
return collection_names return collection_names
def get_collection_hierarchy(root_col, levels=1): def get_collection_hierarchy(root_col, levels=1):
"""Read hierarchy of the collections in the scene""" """Read hierarchy of the collections in the scene"""
level_lookup = {} level_lookup = {}