From c2e73b7e8be9a079964607137a715ea054b526c9 Mon Sep 17 00:00:00 2001 From: "kaosat.dev" Date: Fri, 28 Jun 2024 00:14:30 +0200 Subject: [PATCH] feat(Blenvy:blender): * fixed issues with selections * discovered more issues with selection button in "upgrade/rename" screen, partially fixed * improved visualization of selections for objects, collections, meshes & materials, including auto-switching to the correct properties tab for clarity * fixed ui issues in the upgrade components screen * made distinction between local & remote ones clearer * minor tweaks & improvements --- tools/blenvy/TODO.md | 15 +- .../add_ons/bevy_components/components/ui.py | 27 ++-- tools/blenvy/add_ons/bevy_components/utils.py | 136 ++++++++++++++++-- tools/blenvy/tests/test_bevy_integration.py | 13 +- 4 files changed, 161 insertions(+), 30 deletions(-) diff --git a/tools/blenvy/TODO.md b/tools/blenvy/TODO.md index c80e075..38b0f0b 100644 --- a/tools/blenvy/TODO.md +++ b/tools/blenvy/TODO.md @@ -173,6 +173,19 @@ Blender side: - [x] fix weird issue with hashmaps with enums as values - [x] prevent attempting to add unexisting components to targets (ie when using the component search) - [x] also for the bulk fix actions +- [x] selection of nested objects in collections IS NOT WORKING !!! AHH +- [ ] fix/ overhaul upgreadable components + - [x] add listing of upgradeable components for + - [x] meshes + - [x] materials + - [x] fix display of upgradeaeble components & co + - [x] add clear visual distinction between internal (selectable) & non selectable ones + - [x] do not make selection button available for external blueprints/collections + - [ ] perhaps do not show the other buttons & inputs either ? we cannot change the values of an external library file anyway + +- [ ] BLENVY_OT_item_select is missing handling for the other types (outside of object & collection) + - [ ] fix selection logic + - [ ] hidden objects/collections only semi respected at export - this is because blueprints are external ? - [ ] verify based on gltf settings @@ -186,7 +199,7 @@ Blender side: - [ ] disabled components - [ ] blueprint instances as children of blueprint instances - [ ] blueprint instances as children of empties -- [ ] update testing files +- [x] update testing files - [ ] add option to 'split out' meshes from blueprints ? diff --git a/tools/blenvy/add_ons/bevy_components/components/ui.py b/tools/blenvy/add_ons/bevy_components/components/ui.py index ffcc296..baeeacf 100644 --- a/tools/blenvy/add_ons/bevy_components/components/ui.py +++ b/tools/blenvy/add_ons/bevy_components/components/ui.py @@ -343,9 +343,13 @@ class BLENVY_PT_component_tools_panel(bpy.types.Panel): row = layout.row() col = row.column() - operator = col.operator("blenvy.select_item", text=f"{target.name}({item_type_short})") - operator.target_name = target.name - operator.item_type = item_type + selector_text = f"{target.name}({item_type_short})" + if target.library is None: + operator = col.operator("blenvy.select_item", text=selector_text, icon="FILE_BLEND") + operator.target_name = target.name + operator.item_type = item_type + else: + col.label(text=selector_text, icon="LIBRARY_DATA_DIRECT") col = row.column() col.label(text=status) @@ -383,11 +387,10 @@ class BLENVY_PT_component_tools_panel(bpy.types.Panel): blenvy_custom_properties = ['components_meta', 'bevy_components', 'user_assets', 'generated_assets' ] # some of our own hard coded custom properties that should be ignored upgreadable_entries = [] - if "components_meta" in item: + if "components_meta" in item or hasattr(item, "components_meta"): # FIXME; wrong way of determining components_metadata = item.components_meta.components object_component_names = [] - for index, component_meta in enumerate(components_metadata): long_name = component_meta.long_name if component_meta.invalid: @@ -408,10 +411,12 @@ class BLENVY_PT_component_tools_panel(bpy.types.Panel): # Invalid (something is wrong) # Unregistered (not in registry) # Upgrade Needed (Old-style component) - status = None - if custom_property not in blenvy_custom_properties and custom_property not in object_component_names: - status = "Upgrade Needed" + if custom_property not in blenvy_custom_properties: + if custom_property not in object_component_names: + status = "Upgrade Needed" + else: + status = "Other issue" if status is not None: upgreadable_entries.append((status, custom_property, item, item_type)) @@ -447,6 +452,12 @@ class BLENVY_PT_component_tools_panel(bpy.types.Panel): for collection in bpy.data.collections: if len(collection.keys()) > 0: upgreadable_entries += self.gather_invalid_item_data(collection, invalid_component_names, items_with_invalid_components, items_with_original_components, original_name, "COLLECTION") + for mesh in bpy.data.meshes: + if len(mesh.keys()) > 0: + upgreadable_entries += self.gather_invalid_item_data(mesh, invalid_component_names, items_with_invalid_components, items_with_original_components, original_name, "MESH") + for material in bpy.data.materials: + if len(material.keys()) > 0: + upgreadable_entries += self.gather_invalid_item_data(material, invalid_component_names, items_with_invalid_components, items_with_original_components, original_name, "MATERIAL") if len(items_with_invalid_components) > 0: self.draw_invalid_or_unregistered_header(layout, ["Item","Status", "Component", "Target"]) diff --git a/tools/blenvy/add_ons/bevy_components/utils.py b/tools/blenvy/add_ons/bevy_components/utils.py index 89b3688..0b957fa 100644 --- a/tools/blenvy/add_ons/bevy_components/utils.py +++ b/tools/blenvy/add_ons/bevy_components/utils.py @@ -4,12 +4,49 @@ from bpy.props import StringProperty, EnumProperty from bpy_types import Operator from blenvy.core.helpers_collections import (set_active_collection) +import json + +def select_area(context, area_name): + for area in context.screen.areas: + #if area.type == 'PROPERTIES' and context.object is not None and context.object.type not in ('LIGHT_PROBE', 'CAMERA', 'LIGHT', 'SPEAKER'): + # Set it the active space + print("SELECT AREA", area_name) + try: + area.spaces.active.context = area_name #'MATERIAL' # 'VIEW_LAYER', 'SCENE' etc. + except Exception as error: + print(f"failed to switch to area {area_name}: {error}") + break # OPTIONAL + def get_collection_scene(collection): for scene in bpy.data.scenes: if scene.user_of_id(collection): return scene return None +def get_object_by_name(name): + object = bpy.data.objects.get(name, None) + return object + +def get_object_scene(object): + object = bpy.data.objects.get(object.name, None) + if object is not None: + scenes_of_object = list(object.users_scene) + if len(scenes_of_object) > 0: + return scenes_of_object[0] + return None + + +def get_mesh_object(mesh): + for object in bpy.data.objects: + if isinstance(object.data, bpy.types.Mesh) and mesh.name == object.data.name: + return object + +def get_material_object(material): + for object in bpy.data.objects: + if isinstance(object.data, bpy.types.Mesh) and material.name in object.data.materials: + return object + + class BLENVY_OT_item_select(Operator): """Select object by name""" bl_idname = "blenvy.select_item" @@ -33,52 +70,121 @@ class BLENVY_OT_item_select(Operator): description="target to select's name ", ) # type: ignore + + @classmethod + def register(cls): + bpy.types.WindowManager.blenvy_item_selected_ids = StringProperty(default="{}") + + + @classmethod + def unregister(cls): + del bpy.types.WindowManager.blenvy_item_selected_ids + + def execute(self, context): + if self.target_name: - if self.item_type == "OBJECT": + if self.item_type == 'OBJECT': object = bpy.data.objects[self.target_name] scenes_of_object = list(object.users_scene) if len(scenes_of_object) > 0: bpy.ops.object.select_all(action='DESELECT') bpy.context.window.scene = scenes_of_object[0] object.select_set(True) + context.window_manager.blenvy_item_selected_ids = json.dumps({"name": object.name, "type": self.item_type}) + bpy.context.view_layer.objects.active = object - elif self.item_type == "COLLECTION": + select_area(context=context, area_name="OBJECT") + + elif self.item_type == 'COLLECTION': collection = bpy.data.collections[self.target_name] scene_of_collection = get_collection_scene(collection) if scene_of_collection is not None: bpy.ops.object.select_all(action='DESELECT') bpy.context.window.scene = scene_of_collection - bpy.context.view_layer.objects.active = None + #bpy.context.view_layer.objects.active = None + # + context.window_manager.blenvy_item_selected_ids = json.dumps({"name": collection.name, "type": self.item_type}) set_active_collection(bpy.context.window.scene, collection.name) + select_area(context=context, area_name="COLLECTION") + + elif self.item_type == 'MESH': + mesh = bpy.data.meshes[self.target_name] + mesh_object = get_mesh_object(mesh) + scene_of_item = get_object_scene(mesh_object) + if scene_of_item is not None: + bpy.ops.object.select_all(action='DESELECT') + bpy.context.window.scene = scene_of_item + mesh_object.select_set(True) + bpy.context.view_layer.objects.active = mesh_object + + context.window_manager.blenvy_item_selected_ids = json.dumps({"name": mesh.name, "type": self.item_type}) + select_area(context=context, area_name="DATA") + + elif self.item_type == 'MATERIAL': + # find object that uses material + material = bpy.data.materials[self.target_name] + material_object = get_material_object(material) + scene_of_item = get_object_scene(material_object) + select_area(context=context, area_name="MATERIAL") + print("scene_of_item", scene_of_item) + if scene_of_item is not None: + + bpy.ops.object.select_all(action='DESELECT') + bpy.context.window.scene = scene_of_item + #material_object.select_set(True) + bpy.context.view_layer.objects.active = material_object + + context.window_manager.blenvy_item_selected_ids = json.dumps({"name": material.name, "type": self.item_type}) + + select_area(context=context, area_name="MATERIAL") return {'FINISHED'} def get_selected_item(context): selection = None - #print("original context", context) def get_outliner_area(): if bpy.context.area.type!='OUTLINER': for area in bpy.context.screen.areas: if area.type == 'OUTLINER': return area return None + #print("original context", context) + - area = get_outliner_area() - if area is not None: - region = next(region for region in area.regions if region.type == "WINDOW") + try: + selection_overrides = json.loads(context.window_manager.blenvy_item_selected_ids) + #print("selection_overrides", selection_overrides) + if selection_overrides["type"] == "OBJECT": + selection = bpy.data.objects[selection_overrides["name"]] + elif selection_overrides["type"] == "COLLECTION": + selection = bpy.data.collections[selection_overrides["name"]] + if selection_overrides["type"] == "MESH": + selection = bpy.data.meshes[selection_overrides["name"]] + elif selection_overrides["type"] == "MATERIAL": + selection = bpy.data.materials[selection_overrides["name"]] + #print("SELECTION", selection) + #context.window_manager.blenvy_item_selected_ids = "{}" + except: pass - with bpy.context.temp_override(area=area, region=region): - #print("overriden context", bpy.context) - for obj in bpy.context.selected_ids: - pass#print(f"Selected: {obj.name} - {type(obj)}") - selection = bpy.context.selected_ids[len(bpy.context.selected_ids) - 1] if len(bpy.context.selected_ids)>0 else None #next(iter(bpy.context.selected_ids), None) - """if selection is not None: - print("selection", f"Selected: {selection.name} - {type(selection)}")""" + if selection is None: + area = get_outliner_area() + if area is not None: + region = next(region for region in area.regions if region.type == "WINDOW") + with bpy.context.temp_override(area=area, region=region): + #print("overriden context", bpy.context) + for obj in bpy.context.selected_ids: + print(f"Selected: {obj.name} - {type(obj)}") + number_of_selections = len(bpy.context.selected_ids) + selection = bpy.context.selected_ids[number_of_selections - 1] if number_of_selections > 0 else None #next(iter(bpy.context.selected_ids), None) - #print("SELECTIONS", context.selected_objects) + + + if selection is None: + number_of_selections = len(context.selected_objects) + selection = context.selected_objects[number_of_selections - 1] if number_of_selections > 0 else None return selection diff --git a/tools/blenvy/tests/test_bevy_integration.py b/tools/blenvy/tests/test_bevy_integration.py index 498eb0b..154e304 100644 --- a/tools/blenvy/tests/test_bevy_integration.py +++ b/tools/blenvy/tests/test_bevy_integration.py @@ -81,7 +81,8 @@ def test_export_complex(setup_data): } gltf_settings = { "export_animations": True, - "export_optimize_animation_size": False + "export_optimize_animation_size": False, + "export_apply":True } # store settings for the auto_export part @@ -120,12 +121,12 @@ def test_export_complex(setup_data): user_asset.name = "yoho_audio" user_asset.path = "audio/fake.mp3" - # we have to cheat, since we cannot rely on the data injected when saving the library file - #bpy.data.collections["External_blueprint"]["export_path"] = "blueprints/External_blueprint.glb" - #bpy.data.collections["External_blueprint2"]["export_path"] = "blueprints/External_blueprint2.glb" - #bpy.data.collections["External_blueprint3"]["export_path"] = "blueprints/External_blueprint3.glb" - + # we have to cheat, since we cannot rely on the data injected when saving the library file (since we are not saving it as part of the tests) + bpy.data.collections["External_blueprint"]["export_path"] = "blueprints/External_blueprint.glb" + bpy.data.collections["External_blueprint2"]["export_path"] = "blueprints/External_blueprint2.glb" + bpy.data.collections["External_blueprint3"]["export_path"] = "blueprints/External_blueprint3.glb" + # do the actual exporting prepare_and_export() # blueprint1 => has an instance, got changed, should export