feat(blenvy): components can now also be added to collections/blueprints directly

* this is in preparation for future support in bevy & for more coherence & practicality
 * overhauled metadata tooling
 * overhauled most of the operators
 * overhauled most of the UI
 * all the basics & more work !
 * unrelated: also started preping for auto_export operator removal
This commit is contained in:
kaosat.dev 2024-05-19 23:35:12 +02:00
parent 7ac0c5bca9
commit dc7422fe7a
15 changed files with 401 additions and 278 deletions

View File

@ -50,7 +50,22 @@ Blueprints:
- [ ] decide where & when to do & store blueprints data
Components:
- [ ] add support for adding components to collections
- [x] add support for adding components to collections
- [ ] upgrade all operators:
- [x] add
- [x] remove
- [x] copy & paste
- [ ] OT_rename_component
- [ ] Fix_Component_Operator
- [ ] add handling for core::ops::Range<f32> & other ranges
- [ ] fix is_component_valid that is used in gltf_auto_export
- Hashmap Support
- [x] fix parsing of keys's type either on Bevy side (prefered) or on the Blender side
- [x] fix weird issue with missing "0" property when adding new entry in empty hashmap => happens only if the values for the "setter" have never been set
- [ ] handle missing types in registry for keys & values
- [ ] Add correct upgrade handling from individual component to bevy_components
General things to solve:
- [x] save settings

View File

@ -17,7 +17,7 @@ from bpy.props import (StringProperty)
# components management
from .bevy_components.components.operators import CopyComponentOperator, Fix_Component_Operator, OT_rename_component, RemoveComponentFromAllObjectsOperator, RemoveComponentOperator, GenerateComponent_From_custom_property_Operator, PasteComponentOperator, AddComponentOperator, RenameHelper, Toggle_ComponentVisibility
from .bevy_components.components.operators import CopyComponentOperator, Fix_Component_Operator, OT_rename_component, RemoveComponentFromAllItemsOperator, RemoveComponentOperator, GenerateComponent_From_custom_property_Operator, PasteComponentOperator, AddComponentOperator, RenameHelper, Toggle_ComponentVisibility
from .bevy_components.registry.registry import ComponentsRegistry,MissingBevyType
from .bevy_components.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_OpenSchemaFileBrowser)
@ -74,7 +74,7 @@ classes = [
CopyComponentOperator,
PasteComponentOperator,
RemoveComponentOperator,
RemoveComponentFromAllObjectsOperator,
RemoveComponentFromAllItemsOperator,
Fix_Component_Operator,
OT_rename_component,
RenameHelper,

View File

@ -52,23 +52,26 @@ class ComponentsMeta(PropertyGroup):
@classmethod
def register(cls):
# you can add components to both objects & collections
bpy.types.Object.components_meta = PointerProperty(type=ComponentsMeta)
bpy.types.Collection.components_meta = PointerProperty(type=ComponentsMeta)
@classmethod
def unregister(cls):
del bpy.types.Object.components_meta
del bpy.types.Collection.components_meta
# remove no longer valid metadata from object
def cleanup_invalid_metadata(object):
bevy_components = get_bevy_components(object)
# remove no longer valid metadata from item
def cleanup_invalid_metadata(item):
bevy_components = get_bevy_components(item)
if len(bevy_components.keys()) == 0: # no components, bail out
return
components_metadata = object.components_meta.components
components_metadata = item.components_meta.components
to_remove = []
for index, component_meta in enumerate(components_metadata):
long_name = component_meta.long_name
if long_name not in bevy_components.keys():
print("component:", long_name, "present in metadata, but not in object")
print("component:", long_name, "present in metadata, but not in item")
to_remove.append(index)
for index in to_remove:
components_metadata.remove(index)
@ -81,20 +84,22 @@ def find_component_definition_from_long_name(long_name):
# FIXME: feels a bit heavy duty, should only be done
# if the components panel is active ?
def ensure_metadata_for_all_objects():
def ensure_metadata_for_all_items():
for object in bpy.data.objects:
add_metadata_to_components_without_metadata(object)
for collection in bpy.data.collections:
add_metadata_to_components_without_metadata(collection)
# returns whether an object has custom properties without matching metadata
def do_object_custom_properties_have_missing_metadata(object):
components_metadata = getattr(object, "components_meta", None)
# returns whether an item has custom properties without matching metadata
def do_item_custom_properties_have_missing_metadata(item):
components_metadata = getattr(item, "components_meta", None)
if components_metadata == None:
return True
components_metadata = components_metadata.components
missing_metadata = False
for component_name in get_bevy_components(object) :
for component_name in get_bevy_components(item) :
if component_name == "components_meta":
continue
component_meta = next(filter(lambda component: component["long_name"] == component_name, components_metadata), None)
@ -111,72 +116,72 @@ def do_object_custom_properties_have_missing_metadata(object):
import json
def upsert_bevy_component(object, long_name, value):
if not 'bevy_components' in object:
object['bevy_components'] = '{}'
bevy_components = json.loads(object['bevy_components'])
def upsert_bevy_component(item, long_name, value):
if not 'bevy_components' in item:
item['bevy_components'] = '{}'
bevy_components = json.loads(item['bevy_components'])
bevy_components[long_name] = value
object['bevy_components'] = json.dumps(bevy_components)
#object['bevy_components'][long_name] = value # Sigh, this does not work, hits Blender's 63 char length limit
item['bevy_components'] = json.dumps(bevy_components)
#item['bevy_components'][long_name] = value # Sigh, this does not work, hits Blender's 63 char length limit
def remove_bevy_component(object, long_name):
if 'bevy_components' in object:
bevy_components = json.loads(object['bevy_components'])
def remove_bevy_component(item, long_name):
if 'bevy_components' in item:
bevy_components = json.loads(item['bevy_components'])
if long_name in bevy_components:
del bevy_components[long_name]
object['bevy_components'] = json.dumps(bevy_components)
if long_name in object:
del object[long_name]
item['bevy_components'] = json.dumps(bevy_components)
if long_name in item:
del item[long_name]
def get_bevy_components(object):
if 'bevy_components' in object:
bevy_components = json.loads(object['bevy_components'])
def get_bevy_components(item):
if 'bevy_components' in item:
bevy_components = json.loads(item['bevy_components'])
return bevy_components
return {}
def get_bevy_component_value_by_long_name(object, long_name):
bevy_components = get_bevy_components(object)
def get_bevy_component_value_by_long_name(item, long_name):
bevy_components = get_bevy_components(item)
if len(bevy_components.keys()) == 0 :
return None
return bevy_components.get(long_name, None)
def is_bevy_component_in_object(object, long_name):
return get_bevy_component_value_by_long_name(object, long_name) is not None
def is_bevy_component_in_item(item, long_name):
return get_bevy_component_value_by_long_name(item, long_name) is not None
# adds metadata to object only if it is missing
def add_metadata_to_components_without_metadata(object):
# adds metadata to item only if it is missing
def add_metadata_to_components_without_metadata(item):
registry = bpy.context.window_manager.components_registry
for component_name in get_bevy_components(object) :
for component_name in get_bevy_components(item) :
if component_name == "components_meta":
continue
upsert_component_in_object(object, component_name, registry)
upsert_component_in_item(item, component_name, registry)
# adds a component to an object (including metadata) using the provided component definition & optional value
def add_component_to_object(object, component_definition, value=None):
cleanup_invalid_metadata(object)
if object is not None:
# print("add_component_to_object", component_definition)
# adds a component to an item (including metadata) using the provided component definition & optional value
def add_component_to_item(item, component_definition, value=None):
cleanup_invalid_metadata(item)
if item is not None:
# print("add_component_to_item", component_definition)
long_name = component_definition["long_name"]
registry = bpy.context.window_manager.components_registry
if not registry.has_type_infos():
raise Exception('registry type infos have not been loaded yet or are missing !')
definition = registry.type_infos[long_name]
# now we use our pre_generated property groups to set the initial value of our custom property
(_, propertyGroup) = upsert_component_in_object(object, long_name=long_name, registry=registry)
(_, propertyGroup) = upsert_component_in_item(item, long_name=long_name, registry=registry)
if value == None:
value = property_group_value_to_custom_property_value(propertyGroup, definition, registry, None)
else: # we have provided a value, that is a raw , custom property value, to set the value of the propertyGroup
object["__disable__update"] = True # disable update callback while we set the values of the propertyGroup "tree" (as a propertyGroup can contain other propertyGroups)
item["__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, definition, registry, value)
del object["__disable__update"]
del item["__disable__update"]
upsert_bevy_component(object, long_name, value)
upsert_bevy_component(item, long_name, value)
def upsert_component_in_object(object, long_name, registry):
# print("upsert_component_in_object", object, "component name", component_name)
def upsert_component_in_item(item, long_name, registry):
# print("upsert_component_in_item", item, "component name", component_name)
# TODO: upsert this part too ?
target_components_metadata = object.components_meta.components
target_components_metadata = item.components_meta.components
component_definition = registry.type_infos.get(long_name, None)
if component_definition != None:
short_name = component_definition["short_name"]
@ -213,15 +218,15 @@ def upsert_component_in_object(object, long_name, registry):
component_meta.enabled = False
component_meta.invalid = True
component_meta.invalid_details = "component not present in the schema, possibly renamed? Disabling for now"
# property_group_value_from_custom_property_value(propertyGroup, component_definition, registry, object[component_name])
# property_group_value_from_custom_property_value(propertyGroup, component_definition, registry, item[component_name])
return (component_meta, propertyGroup)
else:
return(None, None)
def copy_propertyGroup_values_to_another_object(source_object, target_object, component_name, registry):
if source_object == None or target_object == None or component_name == None:
def copy_propertyGroup_values_to_another_item(source_item, target_item, component_name, registry):
if source_item == None or target_item == None or component_name == None:
raise Exception('missing input data, cannot copy component propertryGroup')
component_definition = find_component_definition_from_long_name(component_name)
@ -230,82 +235,82 @@ def copy_propertyGroup_values_to_another_object(source_object, target_object, co
registry = bpy.context.window_manager.components_registry
source_components_metadata = source_object.components_meta.components
source_components_metadata = source_item.components_meta.components
source_componentMeta = next(filter(lambda component: component["long_name"] == long_name, source_components_metadata), None)
# matching component means we already have this type of component
source_propertyGroup = getattr(source_componentMeta, property_group_name)
# now deal with the target object
(_, target_propertyGroup) = upsert_component_in_object(target_object, component_name, registry)
# add to object
# now deal with the target item
(_, target_propertyGroup) = upsert_component_in_item(target_item, component_name, registry)
# add to item
value = property_group_value_to_custom_property_value(target_propertyGroup, component_definition, registry, None)
upsert_bevy_component(target_object, long_name, value)
upsert_bevy_component(target_item, long_name, value)
# copy the values over
for field_name in source_propertyGroup.field_names:
if field_name in source_propertyGroup:
target_propertyGroup[field_name] = source_propertyGroup[field_name]
apply_propertyGroup_values_to_object_customProperties(target_object)
apply_propertyGroup_values_to_item_customProperties(target_item)
# TODO: move to propgroups ?
def apply_propertyGroup_values_to_object_customProperties(object):
cleanup_invalid_metadata(object)
def apply_propertyGroup_values_to_item_customProperties(item):
cleanup_invalid_metadata(item)
registry = bpy.context.window_manager.components_registry
for component_name in get_bevy_components(object) :
for component_name in get_bevy_components(item) :
"""if component_name == "components_meta":
continue"""
(_, propertyGroup) = upsert_component_in_object(object, component_name, registry)
(_, propertyGroup) = upsert_component_in_item(item, component_name, registry)
component_definition = find_component_definition_from_long_name(component_name)
if component_definition != None:
value = property_group_value_to_custom_property_value(propertyGroup, component_definition, registry, None)
upsert_bevy_component(object=object, long_name=component_name, value=value)
upsert_bevy_component(item=item, long_name=component_name, value=value)
# apply component value(s) to custom property of a single component
def apply_propertyGroup_values_to_object_customProperties_for_component(object, component_name):
def apply_propertyGroup_values_to_item_customProperties_for_component(item, component_name):
registry = bpy.context.window_manager.components_registry
(_, propertyGroup) = upsert_component_in_object(object, component_name, registry)
(_, propertyGroup) = upsert_component_in_item(item, component_name, registry)
component_definition = find_component_definition_from_long_name(component_name)
if component_definition != None:
value = property_group_value_to_custom_property_value(propertyGroup, component_definition, registry, None)
object[component_name] = value
item[component_name] = value
components_metadata = object.components_meta.components
components_metadata = item.components_meta.components
componentMeta = next(filter(lambda component: component["long_name"] == component_name, components_metadata), None)
if componentMeta:
componentMeta.invalid = False
componentMeta.invalid_details = ""
def apply_customProperty_values_to_object_propertyGroups(object):
print("apply custom properties to ", object.name)
def apply_customProperty_values_to_item_propertyGroups(item):
print("apply custom properties to ", item.name)
registry = bpy.context.window_manager.components_registry
for component_name in get_bevy_components(object) :
for component_name in get_bevy_components(item) :
if component_name == "components_meta":
continue
component_definition = find_component_definition_from_long_name(component_name)
if component_definition != None:
property_group_name = registry.get_propertyGroupName_from_longName(component_name)
components_metadata = object.components_meta.components
components_metadata = item.components_meta.components
source_componentMeta = next(filter(lambda component: component["long_name"] == component_name, components_metadata), None)
# matching component means we already have this type of component
propertyGroup = getattr(source_componentMeta, property_group_name, None)
customProperty_value = get_bevy_component_value_by_long_name(object, component_name)
customProperty_value = get_bevy_component_value_by_long_name(item, component_name)
#value = property_group_value_to_custom_property_value(propertyGroup, component_definition, registry, None)
object["__disable__update"] = True # disable update callback while we set the values of the propertyGroup "tree" (as a propertyGroup can contain other propertyGroups)
item["__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"]
del item["__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):
# removes the given component from the item: removes both the custom property and the matching metadata from the item
def remove_component_from_item(item, component_name):
# remove the component value
remove_bevy_component(object, component_name)
remove_bevy_component(item, component_name)
# now remove the component's metadata
components_metadata = getattr(object, "components_meta", None)
components_metadata = getattr(item, "components_meta", None)
if components_metadata == None:
return False
@ -320,25 +325,25 @@ def remove_component_from_object(object, component_name):
components_metadata.remove(index)
return True
def add_component_from_custom_property(object):
add_metadata_to_components_without_metadata(object)
apply_customProperty_values_to_object_propertyGroups(object)
def add_component_from_custom_property(item):
add_metadata_to_components_without_metadata(item)
apply_customProperty_values_to_item_propertyGroups(item)
def rename_component(object, original_long_name, new_long_name):
def rename_component(item, original_long_name, new_long_name):
registry = bpy.context.window_manager.components_registry
type_infos = registry.type_infos
component_definition = type_infos[new_long_name]
component_ron_value = get_bevy_component_value_by_long_name(object=object, long_name=original_long_name)
if component_ron_value is None and original_long_name in object:
component_ron_value = object[original_long_name]
component_ron_value = get_bevy_component_value_by_long_name(item=item, long_name=original_long_name)
if component_ron_value is None and original_long_name in item:
component_ron_value = item[original_long_name]
remove_component_from_object(object, original_long_name)
add_component_to_object(object, component_definition, component_ron_value)
remove_component_from_item(item, original_long_name)
add_component_to_item(item, component_definition, component_ron_value)
def toggle_component(object, component_name):
components_in_object = object.components_meta.components
component_meta = next(filter(lambda component: component["long_name"] == component_name, components_in_object), None)
def toggle_component(item, component_name):
components_in_item = item.components_meta.components
component_meta = next(filter(lambda component: component["long_name"] == component_name, components_in_item), None)
if component_meta != None:
component_meta.visible = not component_meta.visible

View File

@ -4,12 +4,13 @@ import bpy
from bpy_types import Operator
from bpy.props import (StringProperty)
from .metadata import add_component_from_custom_property, add_component_to_object, apply_propertyGroup_values_to_object_customProperties_for_component, copy_propertyGroup_values_to_another_object, get_bevy_component_value_by_long_name, get_bevy_components, is_bevy_component_in_object, remove_component_from_object, rename_component, toggle_component
from .metadata import add_component_from_custom_property, add_component_to_item, apply_propertyGroup_values_to_item_customProperties_for_component, copy_propertyGroup_values_to_another_item, get_bevy_component_value_by_long_name, get_bevy_components, is_bevy_component_in_item, remove_component_from_item, rename_component, toggle_component
from ..utils import get_selected_object_or_collection
class AddComponentOperator(Operator):
"""Add Bevy component to object"""
"""Add Bevy component to object/collection"""
bl_idname = "object.add_bevy_component"
bl_label = "Add component to object Operator"
bl_label = "Add component to object/collection Operator"
bl_options = {"UNDO"}
component_type: StringProperty(
@ -18,14 +19,14 @@ class AddComponentOperator(Operator):
) # type: ignore
def execute(self, context):
object = context.object
print("adding component ", self.component_type, "to object '"+object.name+"'")
target = get_selected_object_or_collection(context)
print("adding component ", self.component_type, "to target '"+target.name+"'")
has_component_type = self.component_type != ""
if has_component_type and object != None:
if has_component_type and target != None:
type_infos = context.window_manager.components_registry.type_infos
component_definition = type_infos[self.component_type]
add_component_to_object(object, component_definition)
add_component_to_item(target, component_definition)
return {'FINISHED'}
@ -40,28 +41,36 @@ class CopyComponentOperator(Operator):
description="name of the component to copy",
) # type: ignore
source_object_name: StringProperty(
name="source object name",
description="name of the object to copy the component from",
source_item_name: StringProperty(
name="source item name",
description="name of the object/collection to copy the component from",
) # type: ignore
source_item_type: StringProperty(
name="source item type",
description="type of the object/collection to copy the component from",
) # type: ignore
@classmethod
def register(cls):
bpy.types.WindowManager.copied_source_component_name = StringProperty()
bpy.types.WindowManager.copied_source_object = StringProperty()
bpy.types.WindowManager.copied_source_item_name = StringProperty()
bpy.types.WindowManager.copied_source_item_type = StringProperty()
@classmethod
def unregister(cls):
del bpy.types.WindowManager.copied_source_component_name
del bpy.types.WindowManager.copied_source_object
del bpy.types.WindowManager.copied_source_item_name
del bpy.types.WindowManager.copied_source_item_type
def execute(self, context):
if self.source_component_name != '' and self.source_object_name != "":
if self.source_component_name != '' and self.source_item_name != "" and self.source_item_type != "":
context.window_manager.copied_source_component_name = self.source_component_name
context.window_manager.copied_source_object = self.source_object_name
context.window_manager.copied_source_item_name = self.source_item_name
context.window_manager.copied_source_item_type = self.source_item_type
else:
self.report({"ERROR"}, "The source object name / component name to copy a component from have not been specified")
self.report({"ERROR"}, "The source object/collection name or component name to copy a component from have not been specified")
return {'FINISHED'}
@ -73,28 +82,32 @@ class PasteComponentOperator(Operator):
bl_options = {"UNDO"}
def execute(self, context):
source_object_name = context.window_manager.copied_source_object
source_object = bpy.data.objects.get(source_object_name, None)
print("source object", source_object)
if source_object == None:
source_item_name = context.window_manager.copied_source_item_name
source_item_type = context.window_manager.copied_source_item_type
if source_item_type == 'Object':
source_item = bpy.data.objects.get(source_item_name, None)
elif source_item_type == 'Collection':
source_item = bpy.data.collections.get(source_item_name, None)
if source_item == None:
self.report({"ERROR"}, "The source object to copy a component from does not exist")
else:
component_name = context.window_manager.copied_source_component_name
component_value = get_bevy_component_value_by_long_name(source_object, component_name)
component_value = get_bevy_component_value_by_long_name(source_item, component_name)
if component_value is None:
self.report({"ERROR"}, "The source component to copy from does not exist")
else:
print("pasting component to object: component name:", str(component_name), "component value:" + str(component_value))
print (context.object)
print("pasting component to item:", source_item, "component name:", str(component_name), "component value:" + str(component_value))
registry = context.window_manager.components_registry
copy_propertyGroup_values_to_another_object(source_object, context.object, component_name, registry)
target_item = get_selected_object_or_collection(context)
copy_propertyGroup_values_to_another_item(source_item, target_item, component_name, registry)
return {'FINISHED'}
class RemoveComponentOperator(Operator):
"""Remove Bevy component from object"""
"""Remove Bevy component from object/collection"""
bl_idname = "object.remove_bevy_component"
bl_label = "Remove component from object Operator"
bl_label = "Remove component from object/collection Operator"
bl_options = {"UNDO"}
component_name: StringProperty(
@ -102,34 +115,44 @@ class RemoveComponentOperator(Operator):
description="component to delete",
) # type: ignore
object_name: StringProperty(
item_name: StringProperty(
name="object name",
description="object whose component to delete",
default=""
) # type: ignore
def execute(self, context):
if self.object_name == "":
object = context.object
else:
object = bpy.data.objects[self.object_name]
print("removing component ", self.component_name, "from object '"+object.name+"'")
item_type: StringProperty(
name="item type",
description="type of the object/collection to delete",
) # type: ignore
if object is not None and 'bevy_components' in object :
component_value = get_bevy_component_value_by_long_name(object, self.component_name)
def execute(self, context):
target = None
if self.item_name == "":
self.report({"ERROR"}, "The target to remove ("+ self.component_name +") from does not exist")
else:
if self.item_type == 'Object':
target = bpy.data.objects[self.item_name]
elif self.item_type == 'Collection':
target = bpy.data.collections[self.item_name]
print("removing component ", self.component_name, "from object '"+target.name+"'")
if target is not None and 'bevy_components' in target :
component_value = get_bevy_component_value_by_long_name(target, self.component_name)
if component_value is not None:
remove_component_from_object(object, self.component_name)
remove_component_from_item(target, self.component_name)
else :
self.report({"ERROR"}, "The component to remove ("+ self.component_name +") does not exist")
else:
self.report({"ERROR"}, "The object to remove ("+ self.component_name +") from does not exist")
self.report({"ERROR"}, "The target to remove ("+ self.component_name +") from does not exist")
return {'FINISHED'}
class RemoveComponentFromAllObjectsOperator(Operator):
"""Remove Bevy component from all object"""
class RemoveComponentFromAllItemsOperator(Operator):
"""Remove Bevy component from all items"""
bl_idname = "object.remove_bevy_component_all"
bl_label = "Remove component from all objects Operator"
bl_label = "Remove component from all items Operator"
bl_options = {"UNDO"}
component_name: StringProperty(
@ -146,17 +169,28 @@ class RemoveComponentFromAllObjectsOperator(Operator):
del bpy.types.WindowManager.components_remove_progress
def execute(self, context):
print("removing component ", self.component_name, "from all objects")
total = len(bpy.data.objects)
print("removing component ", self.component_name, "from all objects/collections")
total = len(bpy.data.objects) + len(bpy.data.collections)
for index, object in enumerate(bpy.data.objects):
if len(object.keys()) > 0:
if object is not None and is_bevy_component_in_object(object, self.component_name):
remove_component_from_object(object, self.component_name)
if object is not None and is_bevy_component_in_item(object, self.component_name):
remove_component_from_item(object, self.component_name)
progress = index / total
context.window_manager.components_remove_progress = progress
# now force refresh the ui
bpy.ops.wm.redraw_timer(type='DRAW_WIN_SWAP', iterations=1)
for index, collection in enumerate(bpy.data.collections):
if len(collection.keys()) > 0:
if collection is not None and is_bevy_component_in_item(collection, self.component_name):
remove_component_from_item(collection, self.component_name)
progress = index / total
context.window_manager.components_remove_progress = progress
# now force refresh the ui
bpy.ops.wm.redraw_timer(type='DRAW_WIN_SWAP', iterations=1)
context.window_manager.components_remove_progress = -1.0
return {'FINISHED'}
@ -212,8 +246,8 @@ class OT_rename_component(Operator):
total = len(target_objects)
if original_name != '' and new_name != '' and original_name != new_name and len(target_objects) > 0:
for index, object_name in enumerate(target_objects):
object = bpy.data.objects[object_name]
for index, item_name in enumerate(target_objects):
object = bpy.data.objects[item_name]
if object and original_name in get_bevy_components(object) or original_name in object:
try:
# attempt conversion
@ -293,7 +327,7 @@ class Fix_Component_Operator(Operator):
object = context.object
error = False
try:
apply_propertyGroup_values_to_object_customProperties_for_component(object, self.component_name)
apply_propertyGroup_values_to_item_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
@ -315,7 +349,7 @@ class Toggle_ComponentVisibility(Operator):
) # type: ignore
def execute(self, context):
object = context.object
toggle_component(object, self.component_name)
target = get_selected_object_or_collection(context)
toggle_component(target, self.component_name)
return {'FINISHED'}

View File

@ -1,8 +1,9 @@
import json
import bpy
from ..utils import get_selection_type
from ..registry.operators import COMPONENTS_OT_REFRESH_CUSTOM_PROPERTIES_CURRENT
from .metadata import do_object_custom_properties_have_missing_metadata, get_bevy_components
from .metadata import do_item_custom_properties_have_missing_metadata, get_bevy_components
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):
@ -158,11 +159,24 @@ class BEVY_COMPONENTS_PT_ComponentsPanel(bpy.types.Panel):
def draw_header(self, context):
layout = self.layout
name = context.object.name if context.object != None else ''
layout.label(text="Components For "+ name)
name = ""
target_type = ""
object = next(iter(context.selected_objects), None)
collection = context.collection
if object is not None:
name = object.name
target_type = "Object"
elif collection is not None:
name = collection.name
target_type = "Collection"
# name = context.object.name if context.object != None else ''
layout.label(text=f"Components for {name} ({target_type})")
print("object", context.object, "active", context.active_object, "objects", context.selected_objects)
def draw(self, context):
object = context.object
object = next(iter(context.selected_objects), None)
collection = context.collection
layout = self.layout
# we get & load our component registry
@ -171,109 +185,118 @@ class BEVY_COMPONENTS_PT_ComponentsPanel(bpy.types.Panel):
registry_has_type_infos = registry.has_type_infos()
if object is not None:
row = layout.row(align=True)
row.prop(available_components, "list", text="Component")
row.prop(available_components, "filter",text="Filter")
# add components
row = layout.row(align=True)
op = row.operator(AddComponentOperator.bl_idname, text="Add", icon="ADD")
op.component_type = available_components.list
row.enabled = available_components.list != ''
layout.separator()
# paste components
row = layout.row(align=True)
row.operator(PasteComponentOperator.bl_idname, text="Paste component ("+bpy.context.window_manager.copied_source_component_name+")", icon="PASTEDOWN")
row.enabled = registry_has_type_infos and context.window_manager.copied_source_object != ''
layout.separator()
# upgrate custom props to components
upgradeable_customProperties = registry.has_type_infos() and do_object_custom_properties_have_missing_metadata(context.object)
if upgradeable_customProperties:
row = layout.row(align=True)
op = row.operator(GenerateComponent_From_custom_property_Operator.bl_idname, text="generate components from custom properties" , icon="LOOP_FORWARDS")
layout.separator()
components_in_object = object.components_meta.components
#print("components_names", dict(components_bla).keys())
for component_name in sorted(get_bevy_components(object)) : # sorted by component name, practical
#print("component_name", component_name)
if component_name == "components_meta":
continue
# anything withouth metadata gets skipped, we only want to see real components, not all custom props
component_meta = next(filter(lambda component: component["long_name"] == component_name, components_in_object), None)
if component_meta == None:
continue
component_invalid = getattr(component_meta, "invalid")
invalid_details = getattr(component_meta, "invalid_details")
component_visible = getattr(component_meta, "visible")
single_field = False
# our whole row
box = layout.box()
row = box.row(align=True)
# "header"
row.alert = component_invalid
row.prop(component_meta, "enabled", text="")
row.label(text=component_name)
# we fetch the matching ui property group
root_propertyGroup_name = registry.get_propertyGroupName_from_longName(component_name)
"""print("root_propertyGroup_name", root_propertyGroup_name)"""
print("component_meta", component_meta, component_invalid)
if root_propertyGroup_name:
propertyGroup = getattr(component_meta, root_propertyGroup_name, None)
"""print("propertyGroup", propertyGroup)"""
if propertyGroup:
# if the component has only 0 or 1 field names, display inline, otherwise change layout
single_field = len(propertyGroup.field_names) < 2
prop_group_location = box.row(align=True).column()
"""if single_field:
prop_group_location = row.column(align=True)#.split(factor=0.9)#layout.row(align=False)"""
if component_visible:
if component_invalid:
error_message = invalid_details if component_invalid else "Missing component UI data, please reload registry !"
prop_group_location.label(text=error_message)
draw_propertyGroup(propertyGroup, prop_group_location, [root_propertyGroup_name], component_name)
else :
row.label(text="details hidden, click on toggle to display")
else:
error_message = invalid_details if component_invalid else "Missing component UI data, please reload registry !"
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()
op = row.operator(CopyComponentOperator.bl_idname, text="", icon="COPYDOWN")
op.source_component_name = component_name
op.source_object_name = object.name
row.separator()
#if not single_field:
toggle_icon = "TRIA_DOWN" if component_visible else "TRIA_RIGHT"
op = row.operator(Toggle_ComponentVisibility.bl_idname, text="", icon=toggle_icon)
op.component_name = component_name
#row.separator()
draw_component_ui(layout, object, registry, available_components, registry_has_type_infos, context)
elif collection is not None:
draw_component_ui(layout, collection, registry, available_components, registry_has_type_infos, context)
else:
layout.label(text ="Select an object to edit its components")
def draw_component_ui(layout, object_or_collection, registry, available_components, registry_has_type_infos, context):
row = layout.row(align=True)
row.prop(available_components, "list", text="Component")
row.prop(available_components, "filter",text="Filter")
# add components
row = layout.row(align=True)
op = row.operator(AddComponentOperator.bl_idname, text="Add", icon="ADD")
op.component_type = available_components.list
row.enabled = available_components.list != ''
layout.separator()
# paste components
row = layout.row(align=True)
row.operator(PasteComponentOperator.bl_idname, text="Paste component ("+bpy.context.window_manager.copied_source_component_name+")", icon="PASTEDOWN")
row.enabled = registry_has_type_infos and context.window_manager.copied_source_item_name != ''
layout.separator()
# upgrate custom props to components
upgradeable_customProperties = registry.has_type_infos() and do_item_custom_properties_have_missing_metadata(object_or_collection)
if upgradeable_customProperties:
row = layout.row(align=True)
op = row.operator(GenerateComponent_From_custom_property_Operator.bl_idname, text="generate components from custom properties" , icon="LOOP_FORWARDS")
layout.separator()
components_in_object = object_or_collection.components_meta.components
#print("components_names", dict(components_bla).keys())
for component_name in sorted(get_bevy_components(object_or_collection)) : # sorted by component name, practical
#print("component_name", component_name)
if component_name == "components_meta":
continue
# anything withouth metadata gets skipped, we only want to see real components, not all custom props
component_meta = next(filter(lambda component: component["long_name"] == component_name, components_in_object), None)
if component_meta == None:
continue
component_invalid = getattr(component_meta, "invalid")
invalid_details = getattr(component_meta, "invalid_details")
component_visible = getattr(component_meta, "visible")
single_field = False
# our whole row
box = layout.box()
row = box.row(align=True)
# "header"
row.alert = component_invalid
row.prop(component_meta, "enabled", text="")
row.label(text=component_name)
# we fetch the matching ui property group
root_propertyGroup_name = registry.get_propertyGroupName_from_longName(component_name)
"""print("root_propertyGroup_name", root_propertyGroup_name)"""
print("component_meta", component_meta, component_invalid)
if root_propertyGroup_name:
propertyGroup = getattr(component_meta, root_propertyGroup_name, None)
"""print("propertyGroup", propertyGroup)"""
if propertyGroup:
# if the component has only 0 or 1 field names, display inline, otherwise change layout
single_field = len(propertyGroup.field_names) < 2
prop_group_location = box.row(align=True).column()
"""if single_field:
prop_group_location = row.column(align=True)#.split(factor=0.9)#layout.row(align=False)"""
if component_visible:
if component_invalid:
error_message = invalid_details if component_invalid else "Missing component UI data, please reload registry !"
prop_group_location.label(text=error_message)
draw_propertyGroup(propertyGroup, prop_group_location, [root_propertyGroup_name], component_name)
else :
row.label(text="details hidden, click on toggle to display")
else:
error_message = invalid_details if component_invalid else "Missing component UI data, please reload registry !"
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
op.item_name = object_or_collection.name
op.item_type = get_selection_type(object_or_collection)
row.separator()
op = row.operator(CopyComponentOperator.bl_idname, text="", icon="COPYDOWN")
op.source_component_name = component_name
op.source_item_name = object_or_collection.name
op.source_item_type = get_selection_type(object_or_collection)
row.separator()
#if not single_field:
toggle_icon = "TRIA_DOWN" if component_visible else "TRIA_RIGHT"
op = row.operator(Toggle_ComponentVisibility.bl_idname, text="", icon=toggle_icon)
op.component_name = component_name
#row.separator()

View File

@ -1,30 +1,30 @@
import bpy
from .conversions_from_prop_group import property_group_value_to_custom_property_value
from .process_component import process_component
from .utils import update_calback_helper
from ..utils import get_selected_object_or_collection
import json
## main callback function, fired whenever any property changes, no matter the nesting level
def update_component(self, context, definition, component_name):
registry = bpy.context.window_manager.components_registry
current_object = bpy.context.object
update_disabled = current_object["__disable__update"] if "__disable__update" in current_object else False
current_object_or_collection = get_selected_object_or_collection(context)
update_disabled = current_object_or_collection["__disable__update"] if "__disable__update" in current_object_or_collection else False
update_disabled = registry.disable_all_object_updates or update_disabled # global settings
if update_disabled:
return
print("")
print("update in component", component_name, self, "current_object", current_object.name)
components_in_object = current_object.components_meta.components
components_in_object = current_object_or_collection.components_meta.components
component_meta = next(filter(lambda component: component["long_name"] == component_name, components_in_object), None)
if component_meta != None:
property_group_name = registry.get_propertyGroupName_from_longName(component_name)
property_group = getattr(component_meta, property_group_name)
# we use our helper to set the values
object = context.object
previous = json.loads(object['bevy_components'])
previous = json.loads(current_object_or_collection['bevy_components'])
previous[component_name] = property_group_value_to_custom_property_value(property_group, definition, registry, None)
object['bevy_components'] = json.dumps(previous)
current_object_or_collection['bevy_components'] = json.dumps(previous)
def generate_propertyGroups_for_components():

View File

@ -6,7 +6,7 @@ from bpy_extras.io_utils import ImportHelper
from ...settings import upsert_settings
from ..components.metadata import apply_customProperty_values_to_object_propertyGroups, apply_propertyGroup_values_to_object_customProperties, ensure_metadata_for_all_objects
from ..components.metadata import apply_customProperty_values_to_item_propertyGroups, apply_propertyGroup_values_to_item_customProperties, ensure_metadata_for_all_items
from ..propGroups.prop_groups import generate_propertyGroups_for_components
class ReloadRegistryOperator(Operator):
@ -27,7 +27,7 @@ class ReloadRegistryOperator(Operator):
print("")
print("")
print("")
ensure_metadata_for_all_objects()
ensure_metadata_for_all_items()
# now force refresh the ui
for area in context.screen.areas:
@ -57,7 +57,7 @@ class COMPONENTS_OT_REFRESH_CUSTOM_PROPERTIES_ALL(Operator):
total = len(bpy.data.objects)
for index, object in enumerate(bpy.data.objects):
apply_propertyGroup_values_to_object_customProperties(object)
apply_propertyGroup_values_to_item_customProperties(object)
progress = index / total
context.window_manager.custom_properties_from_components_progress_all = progress
# now force refresh the ui
@ -86,7 +86,7 @@ class COMPONENTS_OT_REFRESH_CUSTOM_PROPERTIES_CURRENT(Operator):
context.window_manager.custom_properties_from_components_progress = 0.5
# now force refresh the ui
bpy.ops.wm.redraw_timer(type='DRAW_WIN_SWAP', iterations=1)
apply_propertyGroup_values_to_object_customProperties(object)
apply_propertyGroup_values_to_item_customProperties(object)
context.window_manager.custom_properties_from_components_progress = -1.0
return {'FINISHED'}
@ -111,7 +111,7 @@ class COMPONENTS_OT_REFRESH_PROPGROUPS_FROM_CUSTOM_PROPERTIES_CURRENT(Operator):
object = context.object
error = False
try:
apply_customProperty_values_to_object_propertyGroups(object)
apply_customProperty_values_to_item_propertyGroups(object)
progress = 0.5
context.window_manager.components_from_custom_properties_progress = progress
try:
@ -153,7 +153,7 @@ class COMPONENTS_OT_REFRESH_PROPGROUPS_FROM_CUSTOM_PROPERTIES_ALL(Operator):
for index, object in enumerate(bpy.data.objects):
try:
apply_customProperty_values_to_object_propertyGroups(object)
apply_customProperty_values_to_item_propertyGroups(object)
except Exception as error:
del object["__disable__update"] # make sure custom properties are updateable afterwards, even in the case of failure
errors.append( "object: '" + object.name + "', error: " + str(error))

View File

@ -8,7 +8,7 @@ from bpy.props import (StringProperty, BoolProperty, FloatProperty, FloatVectorP
from ...settings import load_settings
from ..propGroups.prop_groups import generate_propertyGroups_for_components
from ..components.metadata import ComponentMetadata, ensure_metadata_for_all_objects
from ..components.metadata import ComponentMetadata, ensure_metadata_for_all_items
# helper class to store missing bevy types information
class MissingBevyType(bpy.types.PropertyGroup):
@ -286,7 +286,7 @@ class ComponentsRegistry(PropertyGroup):
self.schemaPath = settings["components_schemaPath"]
self.load_schema()
generate_propertyGroups_for_components()
ensure_metadata_for_all_objects()
ensure_metadata_for_all_items()
# we keep a list of component propertyGroup around

View File

@ -3,7 +3,7 @@ import bpy
from bpy_types import (UIList)
from bpy.props import (StringProperty)
from ..components.operators import OT_rename_component, RemoveComponentFromAllObjectsOperator, RemoveComponentOperator
from ..components.operators import OT_rename_component, RemoveComponentFromAllItemsOperator, RemoveComponentOperator
from .operators import(
COMPONENTS_OT_REFRESH_PROPGROUPS_FROM_CUSTOM_PROPERTIES_ALL,
COMPONENTS_OT_REFRESH_PROPGROUPS_FROM_CUSTOM_PROPERTIES_CURRENT,
@ -197,7 +197,7 @@ class BEVY_COMPONENTS_PT_AdvancedToolsPanel(bpy.types.Panel):
col = row.column()
remove_components_progress = context.window_manager.components_remove_progress
if remove_components_progress == -1.0:
operator = row.operator(RemoveComponentFromAllObjectsOperator.bl_idname, text="", icon="X")
operator = row.operator(RemoveComponentFromAllItemsOperator.bl_idname, text="", icon="X")
operator.component_name = context.window_manager.bevy_component_rename_helper.original_name
col.enabled = registry_has_type_infos and original_name != ""
else:

View File

@ -0,0 +1,18 @@
import bpy
#FIXME: does not work if object is hidden !!
def get_selected_object_or_collection(context):
target = None
object = next(iter(context.selected_objects), None)
collection = context.collection
if object is not None:
target = object
elif collection is not None:
target = collection
return target
def get_selection_type(selection):
if isinstance(selection, bpy.types.Object):
return 'Object'
if isinstance(selection, bpy.types.Collection):
return 'Collection'

View File

@ -44,10 +44,10 @@ class BLENVY_PT_SidePanel(bpy.types.Panel):
library_scene_active = False
active_collection = context.collection
print("BLA", blenvy.assets_path_full)
"""print("BLA", blenvy.assets_path_full)
print("BLA", blenvy.blueprints_path_full)
print("BLA", blenvy.levels_path_full)
print("BLA", blenvy.materials_path_full)
print("BLA", blenvy.materials_path_full)"""
# Now to actual drawing of the UI
target = row.box() if active_mode == 'COMPONENTS' else row

View File

@ -0,0 +1,28 @@
import bpy
from . import project_diff
from ...settings import are_settings_identical
from . import auto_export
# prepare export by gather the changes to the scenes & settings
def prepare_export():
blenvy = bpy.context.window_manager.blenvy
bpy.context.window_manager.auto_export_tracker.disable_change_detection()
auto_export_settings = blenvy.auto_export
if auto_export_settings.auto_export: # only do the actual exporting if auto export is actually enabled
# determine changed objects
previous_serialized_scene = None
current_serialized_scene = None
changes_per_scene = project_diff(previous_serialized_scene, current_serialized_scene)
# determine changed parameters
previous_settings = None
current_settings = None
params_changed = are_settings_identical(previous_settings, current_settings)
# do the actual export
auto_export(changes_per_scene, params_changed, blenvy)
# cleanup
# reset the list of changes in the tracker
bpy.context.window_manager.auto_export_tracker.clear_changes()
print("AUTO EXPORT DONE")
bpy.app.timers.register(bpy.context.window_manager.auto_export_tracker.enable_change_detection, first_interval=0.1)

View File

@ -226,7 +226,7 @@ def test_copy_paste_components(setup_data):
setattr(propertyGroup, propertyGroup.field_names[0], 25.0)
copy_component_operator = bpy.ops.object.copy_bevy_component
copy_component_operator(source_component_name=long_name, source_object_name=object.name)
copy_component_operator(source_component_name=long_name, source_item_name=object.name)
# ---------------------------------------
# TARGET object

View File

@ -4,7 +4,7 @@ import bpy
import pprint
import pytest
from ..bevy_components.components.metadata import get_bevy_component_value_by_long_name, get_bevy_components, is_bevy_component_in_object, upsert_bevy_component
from ..bevy_components.components.metadata import get_bevy_component_value_by_long_name, get_bevy_components, is_bevy_component_in_item, upsert_bevy_component
from .setup_data import setup_data
@ -37,8 +37,8 @@ def test_rename_component_single_unit_struct(setup_data):
rename_component_operator(original_name=source_component_name, new_name=target_component_name, target_objects=json.dumps([object.name]))
is_old_component_in_object = is_bevy_component_in_object(object, source_component_name)
is_new_component_in_object = is_bevy_component_in_object(object, target_component_name)
is_old_component_in_object = is_bevy_component_in_item(object, source_component_name)
is_new_component_in_object = is_bevy_component_in_item(object, target_component_name)
assert is_old_component_in_object == False
assert is_new_component_in_object == True
assert get_bevy_component_value_by_long_name(object, target_component_name) == '()'
@ -60,8 +60,8 @@ def test_rename_component_single_complex_struct(setup_data):
rename_component_operator(original_name=source_component_name, new_name=target_component_name, target_objects=json.dumps([object.name]))
is_old_component_in_object = is_bevy_component_in_object(object, source_component_name)
is_new_component_in_object = is_bevy_component_in_object(object, target_component_name)
is_old_component_in_object = is_bevy_component_in_item(object, source_component_name)
is_new_component_in_object = is_bevy_component_in_item(object, target_component_name)
assert is_old_component_in_object == False
assert is_new_component_in_object == True
assert get_bevy_component_value_by_long_name(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)'
@ -86,8 +86,8 @@ def test_rename_component_bulk(setup_data):
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 = is_bevy_component_in_object(object, source_component_name)
is_new_component_in_object = is_bevy_component_in_object(object, target_component_name)
is_old_component_in_object = is_bevy_component_in_item(object, source_component_name)
is_new_component_in_object = is_bevy_component_in_item(object, target_component_name)
assert is_old_component_in_object == False
assert is_new_component_in_object == True
assert get_bevy_component_value_by_long_name(object, target_component_name) == '()'
@ -113,8 +113,8 @@ def test_rename_component_single_error_handling(setup_data):
target_component_metadata = get_component_metadata(object, target_component_name)
is_old_component_in_object = is_bevy_component_in_object(object, source_component_name)
is_new_component_in_object = is_bevy_component_in_object(object, target_component_name)
is_old_component_in_object = is_bevy_component_in_item(object, source_component_name)
is_new_component_in_object = is_bevy_component_in_item(object, target_component_name)
assert is_old_component_in_object == False
assert is_new_component_in_object == True
assert get_bevy_component_value_by_long_name(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)'
@ -143,8 +143,8 @@ def test_rename_component_single_error_handling_clean_errors(setup_data):
target_component_metadata = get_component_metadata(object, target_component_name)
is_old_component_in_object = is_bevy_component_in_object(object, source_component_name)
is_new_component_in_object = is_bevy_component_in_object(object, target_component_name)
is_old_component_in_object = is_bevy_component_in_item(object, source_component_name)
is_new_component_in_object = is_bevy_component_in_item(object, target_component_name)
assert is_old_component_in_object == False
assert is_new_component_in_object == True
assert get_bevy_component_value_by_long_name(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)'