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
This commit is contained in:
kaosat.dev 2024-06-28 00:14:30 +02:00
parent 49e797fea7
commit c2e73b7e8b
4 changed files with 161 additions and 30 deletions

View File

@ -173,6 +173,19 @@ Blender side:
- [x] fix weird issue with hashmaps with enums as values - [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] prevent attempting to add unexisting components to targets (ie when using the component search)
- [x] also for the bulk fix actions - [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 - [ ] hidden objects/collections only semi respected at export
- this is because blueprints are external ? - this is because blueprints are external ?
- [ ] verify based on gltf settings - [ ] verify based on gltf settings
@ -186,7 +199,7 @@ Blender side:
- [ ] disabled components - [ ] disabled components
- [ ] blueprint instances as children of blueprint instances - [ ] blueprint instances as children of blueprint instances
- [ ] blueprint instances as children of empties - [ ] blueprint instances as children of empties
- [ ] update testing files - [x] update testing files
- [ ] add option to 'split out' meshes from blueprints ? - [ ] add option to 'split out' meshes from blueprints ?

View File

@ -343,9 +343,13 @@ class BLENVY_PT_component_tools_panel(bpy.types.Panel):
row = layout.row() row = layout.row()
col = row.column() col = row.column()
operator = col.operator("blenvy.select_item", text=f"{target.name}({item_type_short})") selector_text = f"{target.name}({item_type_short})"
operator.target_name = target.name if target.library is None:
operator.item_type = item_type 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 = row.column()
col.label(text=status) 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 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 = [] 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 components_metadata = item.components_meta.components
object_component_names = [] object_component_names = []
for index, component_meta in enumerate(components_metadata): for index, component_meta in enumerate(components_metadata):
long_name = component_meta.long_name long_name = component_meta.long_name
if component_meta.invalid: if component_meta.invalid:
@ -408,10 +411,12 @@ class BLENVY_PT_component_tools_panel(bpy.types.Panel):
# Invalid (something is wrong) # Invalid (something is wrong)
# Unregistered (not in registry) # Unregistered (not in registry)
# Upgrade Needed (Old-style component) # Upgrade Needed (Old-style component)
status = None status = None
if custom_property not in blenvy_custom_properties and custom_property not in object_component_names: if custom_property not in blenvy_custom_properties:
status = "Upgrade Needed" if custom_property not in object_component_names:
status = "Upgrade Needed"
else:
status = "Other issue"
if status is not None: if status is not None:
upgreadable_entries.append((status, custom_property, item, item_type)) 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: for collection in bpy.data.collections:
if len(collection.keys()) > 0: 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") 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: if len(items_with_invalid_components) > 0:
self.draw_invalid_or_unregistered_header(layout, ["Item","Status", "Component", "Target"]) self.draw_invalid_or_unregistered_header(layout, ["Item","Status", "Component", "Target"])

View File

@ -4,12 +4,49 @@ from bpy.props import StringProperty, EnumProperty
from bpy_types import Operator from bpy_types import Operator
from blenvy.core.helpers_collections import (set_active_collection) 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): def get_collection_scene(collection):
for scene in bpy.data.scenes: for scene in bpy.data.scenes:
if scene.user_of_id(collection): if scene.user_of_id(collection):
return scene return scene
return None 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): class BLENVY_OT_item_select(Operator):
"""Select object by name""" """Select object by name"""
bl_idname = "blenvy.select_item" bl_idname = "blenvy.select_item"
@ -33,52 +70,121 @@ class BLENVY_OT_item_select(Operator):
description="target to select's name ", description="target to select's name ",
) # type: ignore ) # 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): def execute(self, context):
if self.target_name: if self.target_name:
if self.item_type == "OBJECT": if self.item_type == 'OBJECT':
object = bpy.data.objects[self.target_name] object = bpy.data.objects[self.target_name]
scenes_of_object = list(object.users_scene) scenes_of_object = list(object.users_scene)
if len(scenes_of_object) > 0: if len(scenes_of_object) > 0:
bpy.ops.object.select_all(action='DESELECT') bpy.ops.object.select_all(action='DESELECT')
bpy.context.window.scene = scenes_of_object[0] bpy.context.window.scene = scenes_of_object[0]
object.select_set(True) 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 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] collection = bpy.data.collections[self.target_name]
scene_of_collection = get_collection_scene(collection) scene_of_collection = get_collection_scene(collection)
if scene_of_collection is not None: if scene_of_collection is not None:
bpy.ops.object.select_all(action='DESELECT') bpy.ops.object.select_all(action='DESELECT')
bpy.context.window.scene = scene_of_collection 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) 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'} return {'FINISHED'}
def get_selected_item(context): def get_selected_item(context):
selection = None selection = None
#print("original context", context)
def get_outliner_area(): def get_outliner_area():
if bpy.context.area.type!='OUTLINER': if bpy.context.area.type!='OUTLINER':
for area in bpy.context.screen.areas: for area in bpy.context.screen.areas:
if area.type == 'OUTLINER': if area.type == 'OUTLINER':
return area return area
return None 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")
with bpy.context.temp_override(area=area, region=region): try:
#print("overriden context", bpy.context) selection_overrides = json.loads(context.window_manager.blenvy_item_selected_ids)
for obj in bpy.context.selected_ids: #print("selection_overrides", selection_overrides)
pass#print(f"Selected: {obj.name} - {type(obj)}") if selection_overrides["type"] == "OBJECT":
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) selection = bpy.data.objects[selection_overrides["name"]]
"""if selection is not None: elif selection_overrides["type"] == "COLLECTION":
print("selection", f"Selected: {selection.name} - {type(selection)}")""" 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
#print("SELECTIONS", context.selected_objects) 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)
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 return selection

View File

@ -81,7 +81,8 @@ def test_export_complex(setup_data):
} }
gltf_settings = { gltf_settings = {
"export_animations": True, "export_animations": True,
"export_optimize_animation_size": False "export_optimize_animation_size": False,
"export_apply":True
} }
# store settings for the auto_export part # store settings for the auto_export part
@ -120,12 +121,12 @@ def test_export_complex(setup_data):
user_asset.name = "yoho_audio" user_asset.name = "yoho_audio"
user_asset.path = "audio/fake.mp3" user_asset.path = "audio/fake.mp3"
# we have to cheat, since we cannot rely on the data injected when saving the library file # 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_blueprint"]["export_path"] = "blueprints/External_blueprint.glb"
#bpy.data.collections["External_blueprint2"]["export_path"] = "blueprints/External_blueprint2.glb" bpy.data.collections["External_blueprint2"]["export_path"] = "blueprints/External_blueprint2.glb"
#bpy.data.collections["External_blueprint3"]["export_path"] = "blueprints/External_blueprint3.glb" bpy.data.collections["External_blueprint3"]["export_path"] = "blueprints/External_blueprint3.glb"
# do the actual exporting
prepare_and_export() prepare_and_export()
# blueprint1 => has an instance, got changed, should export # blueprint1 => has an instance, got changed, should export