feat(bevy_components):

* added tests
 * fixed a few issues
 * added small "attempt to fix" button for unit struct uis in case they are invalid
 * changed order of panels for clarity
This commit is contained in:
kaosat.dev 2024-03-07 14:48:53 +01:00
parent 50a7e138ec
commit cf3c647afb
6 changed files with 248 additions and 21 deletions

View File

@ -16,7 +16,7 @@ from bpy.props import (StringProperty)
from .helpers import load_settings
from .blueprints import CreateBlueprintOperator
from .components.operators import CopyComponentOperator, OT_rename_component, RemoveComponentFromAllObjectsOperator, RemoveComponentOperator, GenerateComponent_From_custom_property_Operator, PasteComponentOperator, AddComponentOperator, RenameHelper, Toggle_ComponentVisibility
from .components.operators import CopyComponentOperator, Fix_Component_Operator, OT_rename_component, RemoveComponentFromAllObjectsOperator, RemoveComponentOperator, GenerateComponent_From_custom_property_Operator, PasteComponentOperator, AddComponentOperator, RenameHelper, Toggle_ComponentVisibility
from .registry.registry import ComponentsRegistry,MissingBevyType
from .registry.operators import (COMPONENTS_OT_REFRESH_CUSTOM_PROPERTIES_ALL, COMPONENTS_OT_REFRESH_CUSTOM_PROPERTIES_CURRENT, COMPONENTS_OT_REFRESH_PROPGROUPS_FROM_CUSTOM_PROPERTIES_ALL, COMPONENTS_OT_REFRESH_PROPGROUPS_FROM_CUSTOM_PROPERTIES_CURRENT, OT_select_component_name_to_replace, OT_select_object, ReloadRegistryOperator, OT_OpenFilebrowser)
@ -88,6 +88,7 @@ classes = [
PasteComponentOperator,
RemoveComponentOperator,
RemoveComponentFromAllObjectsOperator,
Fix_Component_Operator,
OT_rename_component,
RenameHelper,
GenerateComponent_From_custom_property_Operator,
@ -114,8 +115,8 @@ classes = [
BEVY_COMPONENTS_PT_MainPanel,
BEVY_COMPONENTS_PT_ComponentsPanel,
BEVY_COMPONENTS_PT_Configuration,
BEVY_COMPONENTS_PT_AdvancedToolsPanel,
BEVY_COMPONENTS_PT_Configuration,
MISSING_TYPES_UL_List,
BEVY_COMPONENTS_PT_MissingTypesPanel,

View File

@ -237,6 +237,23 @@ def apply_propertyGroup_values_to_object_customProperties(object):
value = property_group_value_to_custom_property_value(propertyGroup, component_definition, registry, None)
object[component_name] = value
# apply component value(s) to custom property of a single component
def apply_propertyGroup_values_to_object_customProperties_for_component(object, component_name):
registry = bpy.context.window_manager.components_registry
print("yallah", component_name)
(_, propertyGroup) = upsert_component_in_object(object, component_name, registry)
component_definition = find_component_definition_from_short_name(component_name)
if component_definition != None:
print("merde")
value = property_group_value_to_custom_property_value(propertyGroup, component_definition, registry, None)
object[component_name] = value
components_metadata = object.components_meta.components
componentMeta = next(filter(lambda component: component["name"] == component_name, components_metadata), None)
if componentMeta:
print("here")
componentMeta.invalid = False
componentMeta.invalid_details = ""
def apply_customProperty_values_to_object_propertyGroups(object):
@ -258,6 +275,8 @@ def apply_customProperty_values_to_object_propertyGroups(object):
object["__disable__update"] = True # disable update callback while we set the values of the propertyGroup "tree" (as a propertyGroup can contain other propertyGroups)
property_group_value_from_custom_property_value(propertyGroup, component_definition, registry, customProperty_value)
del object["__disable__update"]
source_componentMeta.invalid = False
source_componentMeta.invalid_details = ""
# removes the given component from the object: removes both the custom property and the matching metadata from the object
def remove_component_from_object(object, component_name):

View File

@ -3,7 +3,7 @@ import json
import bpy
from bpy_types import Operator
from bpy.props import (StringProperty)
from .metadata import add_component_to_object, add_metadata_to_components_without_metadata, apply_customProperty_values_to_object_propertyGroups, copy_propertyGroup_values_to_another_object, find_component_definition_from_short_name, remove_component_from_object
from .metadata import add_component_to_object, add_metadata_to_components_without_metadata, apply_customProperty_values_to_object_propertyGroups, apply_propertyGroup_values_to_object_customProperties_for_component, copy_propertyGroup_values_to_another_object, find_component_definition_from_short_name, remove_component_from_object
class AddComponentOperator(Operator):
"""Add component to blueprint"""
@ -173,7 +173,7 @@ class RenameHelper(bpy.types.PropertyGroup):
class OT_rename_component(Operator):
"""Rename component"""
bl_idname = "object.rename_component"
bl_idname = "object.rename_bevy_component"
bl_label = "rename component"
bl_options = {"UNDO"}
@ -210,12 +210,7 @@ class OT_rename_component(Operator):
for index, object_name in enumerate(target_objects):
object = bpy.data.objects[object_name]
if object and original_name in object:
# get metadata
components_metadata = getattr(object, "components_meta", None)
component_meta = None
if components_metadata:
components_metadata = components_metadata.components
component_meta = next(filter(lambda component: component["name"] == new_name, components_metadata), None)
# copy data to new component, remove the old one
try:
object[new_name] = object[original_name]
@ -223,9 +218,14 @@ class OT_rename_component(Operator):
except Exception as error:
if '__disable__update' in object:
del object["__disable__update"] # make sure custom properties are updateable afterwards, even in the case of failure
if component_meta:
component_meta.invalid = True
component_meta.invalid_details = "unknow issue when renaming/transforming component, please remove it & add it back again"
# get metadata
components_metadata = getattr(object, "components_meta", None)
if components_metadata:
components_metadata = components_metadata.components
component_meta = next(filter(lambda component: component["name"] == new_name, components_metadata), None)
if component_meta:
component_meta.invalid = True
component_meta.invalid_details = "unknow issue when renaming/transforming component, please remove it & add it back again"
errors.append( "failed to copy old component value to new component: object: '" + object.name + "', error: " + str(error))
@ -237,16 +237,23 @@ class OT_rename_component(Operator):
except Exception as error:
if '__disable__update' in object:
del object["__disable__update"] # make sure custom properties are updateable afterwards, even in the case of failure
if component_meta:
component_meta.invalid = True
component_meta.invalid_details = "wrong custom property value, overwrite them by changing the values in the ui or change them & regenerate ('Update UI from ...button')"
components_metadata = getattr(object, "components_meta", None)
if components_metadata:
components_metadata = components_metadata.components
component_meta = next(filter(lambda component: component["name"] == new_name, components_metadata), None)
if component_meta:
component_meta.invalid = True
component_meta.invalid_details = "wrong custom property value, overwrite them by changing the values in the ui or change them & regenerate"
errors.append( "wrong custom property values to generate target component: object: '" + object.name + "', error: " + str(error))
progress = index / total
context.window_manager.components_rename_progress = progress
# now force refresh the ui
bpy.ops.wm.redraw_timer(type='DRAW_WIN_SWAP', iterations=1)
try:
# now force refresh the ui
bpy.ops.wm.redraw_timer(type='DRAW_WIN_SWAP', iterations=1)
except: pass # this is to allow this to run in cli/headless mode
if len(errors) > 0:
self.report({'ERROR'}, "Failed to rename component: Errors:" + str(errors))
@ -289,6 +296,31 @@ class GenerateComponent_From_custom_property_Operator(Operator):
return {'FINISHED'}
class Fix_Component_Operator(Operator):
"""attempt to fix component"""
bl_idname = "object.fix_bevy_component"
bl_label = "Fix component (attempts to)"
bl_options = {"UNDO"}
component_name: StringProperty(
name="component name",
description="component to fix",
) # type: ignore
def execute(self, context):
object = context.object
error = False
try:
apply_propertyGroup_values_to_object_customProperties_for_component(object, self.component_name)
except Exception as error:
if "__disable__update" in object:
del object["__disable__update"] # make sure custom properties are updateable afterwards, even in the case of failure
error = True
self.report({'ERROR'}, "Failed to fix component: Error:" + str(error))
if not error:
self.report({'INFO'}, "Sucessfully fixed component (please double check component & its custom property value)")
return {'FINISHED'}
class Toggle_ComponentVisibility(Operator):
"""toggles components visibility"""
bl_idname = "object.toggle_bevy_component_visibility"

View File

@ -1,7 +1,9 @@
import json
import bpy
from ..registry.operators import COMPONENTS_OT_REFRESH_CUSTOM_PROPERTIES_CURRENT
from .metadata import do_object_custom_properties_have_missing_metadata
from .operators import AddComponentOperator, CopyComponentOperator, RemoveComponentOperator, GenerateComponent_From_custom_property_Operator, PasteComponentOperator, Toggle_ComponentVisibility
from .operators import AddComponentOperator, CopyComponentOperator, Fix_Component_Operator, RemoveComponentOperator, GenerateComponent_From_custom_property_Operator, PasteComponentOperator, Toggle_ComponentVisibility
def draw_propertyGroup( propertyGroup, layout, nesting =[], rootName=None):
is_enum = getattr(propertyGroup, "with_enum")
@ -193,6 +195,16 @@ class BEVY_COMPONENTS_PT_ComponentsPanel(bpy.types.Panel):
row.label(text=error_message)
# "footer" with additional controls
if component_invalid:
if root_propertyGroup_name:
propertyGroup = getattr(component_meta, root_propertyGroup_name, None)
if propertyGroup:
unit_struct = len(propertyGroup.field_names) == 0
if unit_struct:
op = row.operator(Fix_Component_Operator.bl_idname, text="", icon="SHADERFX")
op.component_name = component_name
row.separator()
op = row.operator(RemoveComponentOperator.bl_idname, text="", icon="X")
op.component_name = component_name
row.separator()

View File

@ -113,8 +113,10 @@ class COMPONENTS_OT_REFRESH_PROPGROUPS_FROM_CUSTOM_PROPERTIES_CURRENT(Operator):
apply_customProperty_values_to_object_propertyGroups(object)
progress = 0.5
context.window_manager.components_from_custom_properties_progress = progress
# now force refresh the ui
bpy.ops.wm.redraw_timer(type='DRAW_WIN_SWAP', iterations=1)
try:
# now force refresh the ui
bpy.ops.wm.redraw_timer(type='DRAW_WIN_SWAP', iterations=1)
except:pass # ony run in ui
except Exception as error:
del object["__disable__update"] # make sure custom properties are updateable afterwards, even in the case of failure

View File

@ -0,0 +1,161 @@
import json
import re
import bpy
import pprint
import pytest
from .setup_data import setup_data
# small helpers
def get_component_metadata(object, component_name):
target_components_metadata = object.components_meta.components
component_meta = next(filter(lambda component: component["name"] == component_name, target_components_metadata), None)
return component_meta
def get_component_propGroup(registry, component_name, component_meta):
# component_type = registry.short_names_to_long_names[component_name]
# add_component_operator = bpy.ops.object.add_bevy_component
property_group_name = registry.get_propertyGroupName_from_shortName(component_name)
propertyGroup = getattr(component_meta, property_group_name, None)
return propertyGroup
def test_rename_component_single_unit_struct(setup_data):
registry = bpy.context.window_manager.components_registry
registry.schemaPath = setup_data["schema_path"]
bpy.ops.object.reload_registry()
rename_component_operator = bpy.ops.object.rename_bevy_component
object = bpy.context.object
source_component_name = "SomeOldUnitStruct"
target_component_name = "UnitTest"
object[source_component_name] = '()'
rename_component_operator(original_name=source_component_name, new_name=target_component_name, target_objects=json.dumps([object.name]))
is_old_component_in_object = source_component_name in object
is_new_component_in_object = target_component_name in object
assert is_old_component_in_object == False
assert is_new_component_in_object == True
assert object[target_component_name] == '()'
assert get_component_propGroup(registry, target_component_name, get_component_metadata(object, target_component_name)) != None
def test_rename_component_single_complex_struct(setup_data):
registry = bpy.context.window_manager.components_registry
registry.schemaPath = setup_data["schema_path"]
bpy.ops.object.reload_registry()
rename_component_operator = bpy.ops.object.rename_bevy_component
object = bpy.context.object
source_component_name = "ProxyCollider"
target_component_name = "Collider"
object[source_component_name] = 'Capsule(Vec3(x:1.0, y:2.0, z:0.0), Vec3(x:0.0, y:0.0, z:0.0), 3.0)'
rename_component_operator(original_name=source_component_name, new_name=target_component_name, target_objects=json.dumps([object.name]))
is_old_component_in_object = source_component_name in object
is_new_component_in_object = target_component_name in object
assert is_old_component_in_object == False
assert is_new_component_in_object == True
assert object[target_component_name] == 'Capsule(Vec3(x:1.0, y:2.0, z:0.0), Vec3(x:0.0, y:0.0, z:0.0), 3.0)'
assert get_component_propGroup(registry, target_component_name, get_component_metadata(object, target_component_name)) != None
def test_rename_component_single_error_handling(setup_data):
registry = bpy.context.window_manager.components_registry
registry.schemaPath = setup_data["schema_path"]
bpy.ops.object.reload_registry()
rename_component_operator = bpy.ops.object.rename_bevy_component
object = bpy.context.object
source_component_name = "SomeOldUnitStruct"
target_component_name = "UnitTest"
object[source_component_name] = 'Capsule(Vec3(x:1.0, y:2.0, z:0.0), Vec3(x:0.0, y:0.0, z:0.0), 3.0)'
expected_error = 'Error: Failed to rename component: Errors:["wrong custom property values to generate target component: object: \'Cube\', error: input string too big for a unit struct"]\n'
expected_error = re.escape(expected_error)
with pytest.raises(Exception, match=expected_error):
rename_component_operator(original_name=source_component_name, new_name=target_component_name, target_objects=json.dumps([object.name]))
target_component_metadata = get_component_metadata(object, target_component_name)
is_old_component_in_object = source_component_name in object
is_new_component_in_object = target_component_name in object
assert is_old_component_in_object == False
assert is_new_component_in_object == True
assert object[target_component_name] == 'Capsule(Vec3(x:1.0, y:2.0, z:0.0), Vec3(x:0.0, y:0.0, z:0.0), 3.0)'
assert get_component_propGroup(registry, target_component_name, target_component_metadata) != None
assert target_component_metadata.invalid == True
assert target_component_metadata.invalid_details == 'wrong custom property value, overwrite them by changing the values in the ui or change them & regenerate'
def test_rename_component_bulk(setup_data):
registry = bpy.context.window_manager.components_registry
registry.schemaPath = setup_data["schema_path"]
bpy.ops.object.reload_registry()
rename_component_operator = bpy.ops.object.rename_bevy_component
source_component_name = "SomeOldUnitStruct"
target_component_name = "UnitTest"
objects_names = []
for object in bpy.data.objects:
object[source_component_name] = '()'
objects_names.append(object.name)
# bulk rename
rename_component_operator(original_name=source_component_name, new_name=target_component_name, target_objects=json.dumps(objects_names))
for object in bpy.data.objects:
is_old_component_in_object = source_component_name in object
is_new_component_in_object = target_component_name in object
assert is_old_component_in_object == False
assert is_new_component_in_object == True
assert object[target_component_name] == '()'
assert get_component_propGroup(registry, target_component_name, get_component_metadata(object, target_component_name)) != None
def test_rename_component_single_error_handling_clean_errors(setup_data):
registry = bpy.context.window_manager.components_registry
registry.schemaPath = setup_data["schema_path"]
bpy.ops.object.reload_registry()
rename_component_operator = bpy.ops.object.rename_bevy_component
object = bpy.context.object
source_component_name = "SomeOldUnitStruct"
target_component_name = "UnitTest"
object[source_component_name] = 'Capsule(Vec3(x:1.0, y:2.0, z:0.0), Vec3(x:0.0, y:0.0, z:0.0), 3.0)'
expected_error = 'Error: Failed to rename component: Errors:["wrong custom property values to generate target component: object: \'Cube\', error: input string too big for a unit struct"]\n'
expected_error = re.escape(expected_error)
with pytest.raises(Exception, match=expected_error):
rename_component_operator(original_name=source_component_name, new_name=target_component_name, target_objects=json.dumps([object.name]))
target_component_metadata = get_component_metadata(object, target_component_name)
is_old_component_in_object = source_component_name in object
is_new_component_in_object = target_component_name in object
assert is_old_component_in_object == False
assert is_new_component_in_object == True
assert object[target_component_name] == 'Capsule(Vec3(x:1.0, y:2.0, z:0.0), Vec3(x:0.0, y:0.0, z:0.0), 3.0)'
assert get_component_propGroup(registry, target_component_name, target_component_metadata) != None
assert target_component_metadata.invalid == True
assert target_component_metadata.invalid_details == 'wrong custom property value, overwrite them by changing the values in the ui or change them & regenerate'
# if we fix the custom property value & regen the ui, it should be all good
regen_component_operator = bpy.ops.object.refresh_ui_from_custom_properties_current
object[target_component_name] = ''
regen_component_operator()
assert target_component_metadata.invalid == False