From d99a7ccd45286cba386d1f24e4066d0d9e4229c1 Mon Sep 17 00:00:00 2001 From: "kaosat.dev" Date: Wed, 15 May 2024 23:32:01 +0200 Subject: [PATCH] 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) --- tools/blenvy/TODO.md | 17 ++- tools/blenvy/__init__.py | 3 +- tools/blenvy/assets/asset_helpers.py | 5 + tools/blenvy/assets/assets_registry.py | 45 +------- tools/blenvy/assets/assets_scan.py | 101 ++++++++++++++++++ tools/blenvy/assets/operators.py | 27 ++++- tools/blenvy/assets/ui.py | 34 ++++-- .../blenvy/blueprints/blueprints_registry.py | 15 ++- tools/blenvy/blueprints/blueprints_scan.py | 2 +- .../auto_export/auto_export.py | 1 + 10 files changed, 188 insertions(+), 62 deletions(-) create mode 100644 tools/blenvy/assets/assets_scan.py diff --git a/tools/blenvy/TODO.md b/tools/blenvy/TODO.md index 8113f8c..9439cd9 100644 --- a/tools/blenvy/TODO.md +++ b/tools/blenvy/TODO.md @@ -33,12 +33,23 @@ Assets: - [x] per blueprint for blueprint in lib scene - [ ] UI: - [x] we need to display all direct assets (stored in the scene) - - [ ] indirect assets: - - [ ] the assets of local blueprints + - [ ] indirect assets: + - 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: - [x] on save: write IN THE COLLECTION PROPERTIES - list of assets - export path - - [ ] blueprint selection for nested blueprints is broken \ No newline at end of file + - [ ] 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 \ No newline at end of file diff --git a/tools/blenvy/__init__.py b/tools/blenvy/__init__.py index 093122e..769cd27 100644 --- a/tools/blenvy/__init__.py +++ b/tools/blenvy/__init__.py @@ -54,7 +54,7 @@ from .gltf_auto_export.ui.operators import (OT_OpenFolderbrowser, SCENES_LIST_OT # asset management from .assets.ui import Blenvy_assets 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 from .blueprints.ui import GLTF_PT_auto_export_blueprints_list @@ -149,6 +149,7 @@ classes = [ AssetsRegistry, OT_add_bevy_asset, OT_remove_bevy_asset, + OT_test_bevy_assets, OT_Add_asset_filebrowser, Blenvy_assets, diff --git a/tools/blenvy/assets/asset_helpers.py b/tools/blenvy/assets/asset_helpers.py index 157d8b9..ba8a0b1 100644 --- a/tools/blenvy/assets/asset_helpers.py +++ b/tools/blenvy/assets/asset_helpers.py @@ -3,3 +3,8 @@ import json def get_assets(scene_or_collection): assets = json.loads(scene_or_collection.get('assets')) if 'assets' in scene_or_collection else [] 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 \ No newline at end of file diff --git a/tools/blenvy/assets/assets_registry.py b/tools/blenvy/assets/assets_registry.py index 229863f..7780682 100644 --- a/tools/blenvy/assets/assets_registry.py +++ b/tools/blenvy/assets/assets_registry.py @@ -7,50 +7,6 @@ from bpy_types import (PropertyGroup) 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 # class AssetsRegistry(PropertyGroup): @@ -95,3 +51,4 @@ class AssetsRegistry(PropertyGroup): def remove_asset(self, path): self.assets_list[:] = [asset for asset in self.assets_list if (asset["path"] != path)] + diff --git a/tools/blenvy/assets/assets_scan.py b/tools/blenvy/assets/assets_scan.py new file mode 100644 index 0000000..02e7837 --- /dev/null +++ b/tools/blenvy/assets/assets_scan.py @@ -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 diff --git a/tools/blenvy/assets/operators.py b/tools/blenvy/assets/operators.py index 8188254..0143481 100644 --- a/tools/blenvy/assets/operators.py +++ b/tools/blenvy/assets/operators.py @@ -4,6 +4,8 @@ import bpy from bpy_types import (Operator) 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 ..settings import load_settings @@ -159,4 +161,27 @@ class OT_Add_asset_filebrowser(Operator, ImportHelper): print("SELECTED ASSET PATH", asset_path) - return {'FINISHED'} \ No newline at end of file + 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'} diff --git a/tools/blenvy/assets/ui.py b/tools/blenvy/assets/ui.py index eb1b705..2d73d47 100644 --- a/tools/blenvy/assets/ui.py +++ b/tools/blenvy/assets/ui.py @@ -1,4 +1,6 @@ +from types import SimpleNamespace import bpy +from .assets_scan import get_main_scene_assets_tree from .asset_helpers import get_assets @@ -42,6 +44,8 @@ def draw_assets(layout, name, title, asset_registry, assets, target_type, target else: row.label(text="") + return panel + class Blenvy_assets(bpy.types.Panel): bl_space_type = 'VIEW_3D' bl_region_type = 'UI' @@ -52,29 +56,37 @@ class Blenvy_assets(bpy.types.Panel): def poll(cls, context): 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): layout = self.layout layout.use_property_split = True layout.use_property_decorate = False # No animation. + layout.operator(operator="bevyassets.test") + 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" header, panel = layout.box().panel(f"assets{name}", default_closed=False) header.label(text="World/Level Assets") + settings = {"export_blueprints_path": "blueprints", "export_gltf_extension": ".glb"} + settings = SimpleNamespace(**settings) + if panel: for scene in bpy.data.scenes: if scene.name != "Library": # FIXME: hack for testing + get_main_scene_assets_tree(scene, blueprints_data, settings) + direct_assets = get_assets(scene) 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) diff --git a/tools/blenvy/blueprints/blueprints_registry.py b/tools/blenvy/blueprints/blueprints_registry.py index 2fb423d..6a144e4 100644 --- a/tools/blenvy/blueprints/blueprints_registry.py +++ b/tools/blenvy/blueprints/blueprints_registry.py @@ -1,3 +1,4 @@ +from types import SimpleNamespace import bpy import json import os @@ -6,6 +7,10 @@ from pathlib import Path from bpy_types import (PropertyGroup) 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 class BlueprintsRegistry(PropertyGroup): blueprints_data = {} @@ -44,4 +49,12 @@ class BlueprintsRegistry(PropertyGroup): def add_blueprint(self, 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 diff --git a/tools/blenvy/blueprints/blueprints_scan.py b/tools/blenvy/blueprints/blueprints_scan.py index 5ddd0d3..9eb638e 100644 --- a/tools/blenvy/blueprints/blueprints_scan.py +++ b/tools/blenvy/blueprints/blueprints_scan.py @@ -110,7 +110,7 @@ def blueprints_scan(main_scenes, library_scenes, addon_prefs): # 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: collection = bpy.data.collections[collection_name] blueprint = Blueprint(collection.name) diff --git a/tools/blenvy/gltf_auto_export/auto_export/auto_export.py b/tools/blenvy/gltf_auto_export/auto_export/auto_export.py index 0c25a54..1116183 100644 --- a/tools/blenvy/gltf_auto_export/auto_export/auto_export.py +++ b/tools/blenvy/gltf_auto_export/auto_export/auto_export.py @@ -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) for blueprint in blueprints_data.blueprints: bpy.context.window_manager.blueprints_registry.add_blueprint(blueprint) + bpy.context.window_manager.blueprints_registry.add_blueprints_data() if export_scene_settings: # inject/ update scene components