feat(blenvy): more work done on asset management

* helpers
 * ui tweaks
 * scanning of the whole asset tree added
 * cleaner injection of data at the collection/ scene level (wip)
This commit is contained in:
kaosat.dev 2024-05-15 23:32:01 +02:00
parent d0bc05fb96
commit d99a7ccd45
10 changed files with 188 additions and 62 deletions

View File

@ -33,12 +33,23 @@ Assets:
- [x] per blueprint for blueprint in lib scene - [x] per blueprint for blueprint in lib scene
- [ ] UI: - [ ] UI:
- [x] we need to display all direct assets (stored in the scene) - [x] we need to display all direct assets (stored in the scene)
- [ ] indirect assets: - [ ] indirect assets:
- [ ] the assets of local blueprints - QUESTION : do we want to include them in the list of assets per level ?
- this would enable pre-loading ALL the assets, but is not ideal in most other cases
- so add an option ?
- [ ] the assets of local blueprints
Blueprints: Blueprints:
- [x] on save: write IN THE COLLECTION PROPERTIES - [x] on save: write IN THE COLLECTION PROPERTIES
- list of assets - list of assets
- export path - export path
- [ ] blueprint selection for nested blueprints is broken - [ ] blueprint selection for nested blueprints is broken
General issues:
- there is no safeguard for naming collisions for naming across blender files
- this can cause an issue for assets list "parent"
- "parents" can only be blueprints
- they normally need/have unique export paths (otherwise, user error, perhaps show it ?)
- perhaps a simple hashing of the parent's path would be enought

View File

@ -54,7 +54,7 @@ from .gltf_auto_export.ui.operators import (OT_OpenFolderbrowser, SCENES_LIST_OT
# asset management # asset management
from .assets.ui import Blenvy_assets from .assets.ui import Blenvy_assets
from .assets.assets_registry import AssetsRegistry from .assets.assets_registry import AssetsRegistry
from .assets.operators import OT_Add_asset_filebrowser, OT_add_bevy_asset, OT_remove_bevy_asset from .assets.operators import OT_Add_asset_filebrowser, OT_add_bevy_asset, OT_remove_bevy_asset, OT_test_bevy_assets
# blueprints management # blueprints management
from .blueprints.ui import GLTF_PT_auto_export_blueprints_list from .blueprints.ui import GLTF_PT_auto_export_blueprints_list
@ -149,6 +149,7 @@ classes = [
AssetsRegistry, AssetsRegistry,
OT_add_bevy_asset, OT_add_bevy_asset,
OT_remove_bevy_asset, OT_remove_bevy_asset,
OT_test_bevy_assets,
OT_Add_asset_filebrowser, OT_Add_asset_filebrowser,
Blenvy_assets, Blenvy_assets,

View File

@ -3,3 +3,8 @@ import json
def get_assets(scene_or_collection): def get_assets(scene_or_collection):
assets = json.loads(scene_or_collection.get('assets')) if 'assets' in scene_or_collection else [] assets = json.loads(scene_or_collection.get('assets')) if 'assets' in scene_or_collection else []
return assets return assets
def does_asset_exist(assets, asset_path):
in_list = [asset for asset in assets if (asset["path"] == asset_path)]
in_list = len(in_list) > 0
return in_list

View File

@ -7,50 +7,6 @@ from bpy_types import (PropertyGroup)
from bpy.props import (StringProperty, BoolProperty, FloatProperty, FloatVectorProperty, IntProperty, IntVectorProperty, EnumProperty, PointerProperty, CollectionProperty) from bpy.props import (StringProperty, BoolProperty, FloatProperty, FloatVectorProperty, IntProperty, IntVectorProperty, EnumProperty, PointerProperty, CollectionProperty)
def get_assets(scene, blueprints_data, addon_prefs):
export_root_path = getattr(addon_prefs, "export_root_path")
export_output_folder = getattr(addon_prefs,"export_output_folder")
export_levels_path = getattr(addon_prefs,"export_levels_path")
export_blueprints_path = getattr(addon_prefs, "export_blueprints_path")
export_gltf_extension = getattr(addon_prefs, "export_gltf_extension")
relative_blueprints_path = os.path.relpath(export_blueprints_path, export_root_path)
blueprint_instance_names_for_scene = blueprints_data.blueprint_instances_per_main_scene.get(scene.name, None)
blueprint_assets_list = []
if blueprint_instance_names_for_scene:
for blueprint_name in blueprint_instance_names_for_scene:
blueprint = blueprints_data.blueprints_per_name.get(blueprint_name, None)
if blueprint is not None:
print("BLUEPRINT", blueprint)
blueprint_exported_path = None
if blueprint.local:
blueprint_exported_path = os.path.join(relative_blueprints_path, f"{blueprint.name}{export_gltf_extension}")
else:
# get the injected path of the external blueprints
blueprint_exported_path = blueprint.collection['Export_path'] if 'Export_path' in blueprint.collection else None
print("foo", dict(blueprint.collection))
if blueprint_exported_path is not None:
blueprint_assets_list.append({"name": blueprint.name, "path": blueprint_exported_path})
# fetch images/textures
# see https://blender.stackexchange.com/questions/139859/how-to-get-absolute-file-path-for-linked-texture-image
textures = []
for ob in bpy.data.objects:
if ob.type == "MESH":
for mat_slot in ob.material_slots:
if mat_slot.material:
if mat_slot.material.node_tree:
textures.extend([x.image.filepath for x in mat_slot.material.node_tree.nodes if x.type=='TEX_IMAGE'])
print("textures", textures)
assets_list_name = f"assets_{scene.name}"
assets_list_data = {"blueprints": json.dumps(blueprint_assets_list), "sounds":[], "images":[]}
print("blueprint assets", blueprint_assets_list)
# this is where we store the information for all available assets # this is where we store the information for all available assets
# #
class AssetsRegistry(PropertyGroup): class AssetsRegistry(PropertyGroup):
@ -95,3 +51,4 @@ class AssetsRegistry(PropertyGroup):
def remove_asset(self, path): def remove_asset(self, path):
self.assets_list[:] = [asset for asset in self.assets_list if (asset["path"] != path)] self.assets_list[:] = [asset for asset in self.assets_list if (asset["path"] != path)]

View File

@ -0,0 +1,101 @@
import os
import json
import bpy
from .asset_helpers import does_asset_exist, get_assets
def scan_assets(scene, blueprints_data, addon_prefs):
export_root_path = getattr(addon_prefs, "export_root_path")
export_output_folder = getattr(addon_prefs,"export_output_folder")
export_levels_path = getattr(addon_prefs,"export_levels_path")
export_blueprints_path = getattr(addon_prefs, "export_blueprints_path")
export_gltf_extension = getattr(addon_prefs, "export_gltf_extension")
relative_blueprints_path = os.path.relpath(export_blueprints_path, export_root_path)
blueprint_instance_names_for_scene = blueprints_data.blueprint_instances_per_main_scene.get(scene.name, None)
blueprint_assets_list = []
if blueprint_instance_names_for_scene:
for blueprint_name in blueprint_instance_names_for_scene:
blueprint = blueprints_data.blueprints_per_name.get(blueprint_name, None)
if blueprint is not None:
print("BLUEPRINT", blueprint)
blueprint_exported_path = None
if blueprint.local:
blueprint_exported_path = os.path.join(relative_blueprints_path, f"{blueprint.name}{export_gltf_extension}")
else:
# get the injected path of the external blueprints
blueprint_exported_path = blueprint.collection['Export_path'] if 'Export_path' in blueprint.collection else None
print("foo", dict(blueprint.collection))
if blueprint_exported_path is not None:
blueprint_assets_list.append({"name": blueprint.name, "path": blueprint_exported_path})
# fetch images/textures
# see https://blender.stackexchange.com/questions/139859/how-to-get-absolute-file-path-for-linked-texture-image
textures = []
for ob in bpy.data.objects:
if ob.type == "MESH":
for mat_slot in ob.material_slots:
if mat_slot.material:
if mat_slot.material.node_tree:
textures.extend([x.image.filepath for x in mat_slot.material.node_tree.nodes if x.type=='TEX_IMAGE'])
print("textures", textures)
assets_list_name = f"assets_{scene.name}"
assets_list_data = {"blueprints": json.dumps(blueprint_assets_list), "sounds":[], "images":[]}
print("blueprint assets", blueprint_assets_list)
def get_blueprint_assets_tree(blueprint, blueprints_data, parent, addon_prefs):
export_blueprints_path = getattr(addon_prefs, "export_blueprints_path")
export_gltf_extension = getattr(addon_prefs, "export_gltf_extension")
assets_list = []
for blueprint_name in blueprint.nested_blueprints:
child_blueprint = blueprints_data.blueprints_per_name.get(blueprint_name, None)
if child_blueprint:
blueprint_exported_path = None
if blueprint.local:
blueprint_exported_path = os.path.join(export_blueprints_path, f"{child_blueprint.name}{export_gltf_extension}")
else:
# get the injected path of the external blueprints
blueprint_exported_path = child_blueprint.collection['export_path'] if 'export_path' in child_blueprint.collection else None
if blueprint_exported_path is not None:
assets_list.append({"name": child_blueprint.name, "path": blueprint_exported_path, "type": "MODEL", "internal": True, "parent": blueprint.name})
# and add sub stuff
sub_assets_lists = get_blueprint_assets_tree(child_blueprint, blueprints_data, parent=child_blueprint.name, addon_prefs=addon_prefs)
assets_list += sub_assets_lists
direct_assets = get_assets(blueprint.collection)
for asset in direct_assets:
asset["parent"] = parent
assets_list += direct_assets
return assets_list
def get_main_scene_assets_tree(main_scene, blueprints_data, addon_prefs):
export_blueprints_path = getattr(addon_prefs, "export_blueprints_path")
export_gltf_extension = getattr(addon_prefs, "export_gltf_extension")
blueprint_instance_names_for_scene = blueprints_data.blueprint_instances_per_main_scene.get(main_scene.name, None)
assets_list = get_assets(main_scene) # FIXME: problem, we already have the blueprint assets stored there
if blueprint_instance_names_for_scene:
for blueprint_name in blueprint_instance_names_for_scene:
blueprint = blueprints_data.blueprints_per_name.get(blueprint_name, None)
if blueprint is not None:
blueprint_exported_path = None
if blueprint.local:
blueprint_exported_path = os.path.join(export_blueprints_path, f"{blueprint.name}{export_gltf_extension}")
else:
# get the injected path of the external blueprints
blueprint_exported_path = blueprint.collection['export_path'] if 'export_path' in blueprint.collection else None
if blueprint_exported_path is not None and not does_asset_exist(assets_list, blueprint_exported_path):
assets_list.append({"name": blueprint.name, "path": blueprint_exported_path, "type": "MODEL", "internal": True, "parent": None})
assets_list += get_blueprint_assets_tree(blueprint, blueprints_data, parent=blueprint.name, addon_prefs=addon_prefs)
print("TOTAL ASSETS", assets_list)
return assets_list

View File

@ -4,6 +4,8 @@ import bpy
from bpy_types import (Operator) from bpy_types import (Operator)
from bpy.props import (BoolProperty, StringProperty, EnumProperty) from bpy.props import (BoolProperty, StringProperty, EnumProperty)
from .assets_scan import get_main_scene_assets_tree
from ..core.path_helpers import absolute_path_from_blend_file from ..core.path_helpers import absolute_path_from_blend_file
from ..settings import load_settings from ..settings import load_settings
@ -159,4 +161,27 @@ class OT_Add_asset_filebrowser(Operator, ImportHelper):
print("SELECTED ASSET PATH", asset_path) print("SELECTED ASSET PATH", asset_path)
return {'FINISHED'} return {'FINISHED'}
from types import SimpleNamespace
class OT_test_bevy_assets(Operator):
"""Test assets"""
bl_idname = "bevyassets.test"
bl_label = "test bevy assets"
bl_options = {"UNDO"}
def execute(self, context):
blueprints_registry = context.window_manager.blueprints_registry
blueprints_registry.add_blueprints_data()
blueprints_data = blueprints_registry.blueprints_data
settings = {"export_blueprints_path": "blueprints", "export_gltf_extension": ".glb"}
settings = SimpleNamespace(**settings)
for scene in bpy.data.scenes:
if scene.name != "Library":
assets_hierarchy = get_main_scene_assets_tree(scene, blueprints_data, settings)
scene["assets_hierarchy"] = json.dumps(assets_hierarchy)
return {'FINISHED'}

View File

@ -1,4 +1,6 @@
from types import SimpleNamespace
import bpy import bpy
from .assets_scan import get_main_scene_assets_tree
from .asset_helpers import get_assets from .asset_helpers import get_assets
@ -42,6 +44,8 @@ def draw_assets(layout, name, title, asset_registry, assets, target_type, target
else: else:
row.label(text="") row.label(text="")
return panel
class Blenvy_assets(bpy.types.Panel): class Blenvy_assets(bpy.types.Panel):
bl_space_type = 'VIEW_3D' bl_space_type = 'VIEW_3D'
bl_region_type = 'UI' bl_region_type = 'UI'
@ -52,29 +56,37 @@ class Blenvy_assets(bpy.types.Panel):
def poll(cls, context): def poll(cls, context):
return context.window_manager.blenvy.mode == 'ASSETS' return context.window_manager.blenvy.mode == 'ASSETS'
"""def draw_header(self, context):
layout = self.layout
name = ""
if context.collection is not None and context.collection.name == 'Scene Collection':
name = f"WORLD/LEVEL: {context.scene.name}"
else:
name = f"BLUEPRINT: {context.collection.name}"
layout.label(text=f"Assets For {name}")"""
def draw(self, context): def draw(self, context):
layout = self.layout layout = self.layout
layout.use_property_split = True layout.use_property_split = True
layout.use_property_decorate = False # No animation. layout.use_property_decorate = False # No animation.
layout.operator(operator="bevyassets.test")
asset_registry = context.window_manager.assets_registry asset_registry = context.window_manager.assets_registry
blueprints_registry = context.window_manager.blueprints_registry
blueprints_registry.add_blueprints_data()
blueprints_data = blueprints_registry.blueprints_data
name = "world" name = "world"
header, panel = layout.box().panel(f"assets{name}", default_closed=False) header, panel = layout.box().panel(f"assets{name}", default_closed=False)
header.label(text="World/Level Assets") header.label(text="World/Level Assets")
settings = {"export_blueprints_path": "blueprints", "export_gltf_extension": ".glb"}
settings = SimpleNamespace(**settings)
if panel: if panel:
for scene in bpy.data.scenes: for scene in bpy.data.scenes:
if scene.name != "Library": # FIXME: hack for testing if scene.name != "Library": # FIXME: hack for testing
get_main_scene_assets_tree(scene, blueprints_data, settings)
direct_assets = get_assets(scene) direct_assets = get_assets(scene)
row = panel.row() row = panel.row()
draw_assets(layout=row, name=scene.name, title=f"{scene.name} Assets", asset_registry=asset_registry, assets=direct_assets, target_type="SCENE", target_name=scene.name) scene_assets_panel = draw_assets(layout=row, name=scene.name, title=f"{scene.name} Assets", asset_registry=asset_registry, assets=direct_assets, target_type="SCENE", target_name=scene.name)
if scene.name in blueprints_data.blueprint_instances_per_main_scene:
for blueprint_name in blueprints_data.blueprint_instances_per_main_scene[scene.name].keys():
blueprint = blueprints_data.blueprints_per_name[blueprint_name]
blueprint_assets = get_assets(blueprint.collection)
if scene_assets_panel:
row = scene_assets_panel.row()
draw_assets(layout=row, name=blueprint.name, title=f"{blueprint.name} Assets", asset_registry=asset_registry, assets=blueprint_assets, target_type="BLUEPRINT", target_name=blueprint.name)

View File

@ -1,3 +1,4 @@
from types import SimpleNamespace
import bpy import bpy
import json import json
import os import os
@ -6,6 +7,10 @@ from pathlib import Path
from bpy_types import (PropertyGroup) from bpy_types import (PropertyGroup)
from bpy.props import (StringProperty, BoolProperty, FloatProperty, FloatVectorProperty, IntProperty, IntVectorProperty, EnumProperty, PointerProperty, CollectionProperty) from bpy.props import (StringProperty, BoolProperty, FloatProperty, FloatVectorProperty, IntProperty, IntVectorProperty, EnumProperty, PointerProperty, CollectionProperty)
from ..settings import load_settings
from ..gltf_auto_export.helpers.helpers_scenes import get_scenes
from .blueprints_scan import blueprints_scan
# this is where we store the information for all available Blueprints # this is where we store the information for all available Blueprints
class BlueprintsRegistry(PropertyGroup): class BlueprintsRegistry(PropertyGroup):
blueprints_data = {} blueprints_data = {}
@ -44,4 +49,12 @@ class BlueprintsRegistry(PropertyGroup):
def add_blueprint(self, blueprint): def add_blueprint(self, blueprint):
self.blueprints_list.append(blueprint) self.blueprints_list.append(blueprint)
def add_blueprints_data(self):
print("adding blueprints data")
addon_prefs = load_settings(".gltf_auto_export_settings")
print("addon_prefs", addon_prefs)
addon_prefs["export_marked_assets"] = False
addon_prefs = SimpleNamespace(**addon_prefs)
[main_scene_names, level_scenes, library_scene_names, library_scenes] = get_scenes(addon_prefs)
blueprints_data = blueprints_scan(level_scenes, library_scenes, addon_prefs)
self.blueprints_data = blueprints_data

View File

@ -110,7 +110,7 @@ def blueprints_scan(main_scenes, library_scenes, addon_prefs):
# #
collections.append(collection) collections.append(collection)
# add any collection that has an instance in the main scenes, but is not present in any of the scenes (IE NON LOCAL/ EXTERNAL) # EXTERNAL COLLECTIONS: add any collection that has an instance in the main scenes, but is not present in any of the scenes (IE NON LOCAL/ EXTERNAL)
for collection_name in external_collection_instances: for collection_name in external_collection_instances:
collection = bpy.data.collections[collection_name] collection = bpy.data.collections[collection_name]
blueprint = Blueprint(collection.name) blueprint = Blueprint(collection.name)

View File

@ -80,6 +80,7 @@ def auto_export(changes_per_scene, changed_export_parameters, addon_prefs):
inject_export_path_into_internal_blueprints(internal_blueprints=blueprints_data.internal_blueprints, export_blueprints_path=export_blueprints_path, gltf_extension=gltf_extension) inject_export_path_into_internal_blueprints(internal_blueprints=blueprints_data.internal_blueprints, export_blueprints_path=export_blueprints_path, gltf_extension=gltf_extension)
for blueprint in blueprints_data.blueprints: for blueprint in blueprints_data.blueprints:
bpy.context.window_manager.blueprints_registry.add_blueprint(blueprint) bpy.context.window_manager.blueprints_registry.add_blueprint(blueprint)
bpy.context.window_manager.blueprints_registry.add_blueprints_data()
if export_scene_settings: if export_scene_settings:
# inject/ update scene components # inject/ update scene components