feat(blenvy): significant breaking changes in the way components are handled

* to fix issues with hash collisions because of incomplete & incorect "component paths"
(ie the different nesting levels of the structs within components)
 * removed short name nested component path passing
 * changed the way the composite paths are created & the hash is generated
 * a lot of other related changes
 * also changed the registry's default path, to use the blenvy level assets path
This commit is contained in:
kaosat.dev 2024-06-14 23:15:51 +02:00
parent c282dab585
commit e86920168a
11 changed files with 54 additions and 49 deletions

View File

@ -97,6 +97,8 @@ Components:
- [x] add warning about hash colision (not much we can/ could do if it is the case ?) - [x] add warning about hash colision (not much we can/ could do if it is the case ?)
- [ ] double check weird collisions AND/OR reuse existing if applicable - [ ] double check weird collisions AND/OR reuse existing if applicable
- [x] annoying default path for registry, should be relative to the assets path
General things to solve: General things to solve:
- [x] save settings - [x] save settings

View File

@ -7,7 +7,7 @@ from . import process_enum
from . import process_list from . import process_list
from . import process_map from . import process_map
def process_component(registry, definition, update, extras=None, nesting = [], nesting_long_names = []): def process_component(registry, definition, update, extras=None, nesting_long_names = []):
long_name = definition['long_name'] long_name = definition['long_name']
short_name = definition["short_name"] short_name = definition["short_name"]
type_info = definition["typeInfo"] if "typeInfo" in definition else None type_info = definition["typeInfo"] if "typeInfo" in definition else None
@ -30,28 +30,32 @@ def process_component(registry, definition, update, extras=None, nesting = [], n
with_list = False with_list = False
with_map = False with_map = False
padding = " " * (len(nesting_long_names) + 1)
#print(f"{padding}process component", long_name, "nesting_long_names",nesting_long_names, "foo", has_properties, has_prefixItems, is_enum, is_list, is_map)
if has_properties: if has_properties:
__annotations__ = __annotations__ | process_structs.process_structs(registry, definition, properties, update, nesting, nesting_long_names) __annotations__ = __annotations__ | process_structs.process_structs(registry, definition, properties, update, nesting_long_names)
with_properties = True with_properties = True
tupple_or_struct = "struct" tupple_or_struct = "struct"
if has_prefixItems: if has_prefixItems:
__annotations__ = __annotations__ | process_tupples.process_tupples(registry, definition, prefixItems, update, nesting, nesting_long_names) __annotations__ = __annotations__ | process_tupples.process_tupples(registry, definition, prefixItems, update, nesting_long_names)
with_items = True with_items = True
tupple_or_struct = "tupple" tupple_or_struct = "tupple"
if is_enum: if is_enum:
__annotations__ = __annotations__ | process_enum.process_enum(registry, definition, update, nesting, nesting_long_names) __annotations__ = __annotations__ | process_enum.process_enum(registry, definition, update, nesting_long_names)
with_enum = True with_enum = True
if is_list: if is_list:
__annotations__ = __annotations__ | process_list.process_list(registry, definition, update, nesting, nesting_long_names) __annotations__ = __annotations__ | process_list.process_list(registry, definition, update, nesting_long_names)
with_list= True with_list= True
if is_map: if is_map:
__annotations__ = __annotations__ | process_map.process_map(registry, definition, update, nesting, nesting_long_names) __annotations__ = __annotations__ | process_map.process_map(registry, definition, update, nesting_long_names)
with_map = True with_map = True
# print("AFTER PROCESS", nesting_long_names, long_name)
field_names = [] field_names = []
for a in __annotations__: for a in __annotations__:
@ -61,7 +65,10 @@ def process_component(registry, definition, update, extras=None, nesting = [], n
extras = extras if extras is not None else { extras = extras if extras is not None else {
"long_name": long_name "long_name": long_name
} }
nesting_long_names = nesting_long_names + [long_name]
root_component = nesting_long_names[0] if len(nesting_long_names) > 0 else long_name root_component = nesting_long_names[0] if len(nesting_long_names) > 0 else long_name
# print("") # print("")
property_group_params = { property_group_params = {
**extras, **extras,
@ -78,7 +85,7 @@ def process_component(registry, definition, update, extras=None, nesting = [], n
-BasicTest => the registration & update callback of this one overwrites the first "basicTest" -BasicTest => the registration & update callback of this one overwrites the first "basicTest"
have not found a cleaner workaround so far have not found a cleaner workaround so far
""" """
property_group_name = registry.generate_propGroup_name(nesting, long_name) property_group_name = registry.generate_propGroup_name(nesting_long_names)
(property_group_pointer, property_group_class) = property_group_from_infos(property_group_name, property_group_params) (property_group_pointer, property_group_class) = property_group_from_infos(property_group_name, property_group_params)
# add our component propertyGroup to the registry # add our component propertyGroup to the registry
registry.register_component_propertyGroup(property_group_name, property_group_pointer) registry.register_component_propertyGroup(property_group_name, property_group_pointer)

View File

@ -1,21 +1,19 @@
from bpy.props import (StringProperty) from bpy.props import (StringProperty)
from . import process_component from . import process_component
def process_enum(registry, definition, update, nesting, nesting_long_names): def process_enum(registry, definition, update, nesting_long_names):
blender_property_mapping = registry.blender_property_mapping blender_property_mapping = registry.blender_property_mapping
short_name = definition["short_name"]
long_name = definition["long_name"] long_name = definition["long_name"]
type_def = definition["type"] if "type" in definition else None type_def = definition["type"] if "type" in definition else None
variants = definition["oneOf"] variants = definition["oneOf"]
nesting = nesting + [short_name] nesting_long_names = nesting_long_names + [long_name]
nesting_long_names = nesting_long_names = [long_name]
__annotations__ = {} __annotations__ = {}
original_type_name = "enum" original_type_name = "enum"
# print("processing enum", short_name, long_name, definition) #print("processing enum", long_name)#, definition)
if type_def == "object": if type_def == "object":
labels = [] labels = []
@ -28,12 +26,12 @@ def process_enum(registry, definition, update, nesting, nesting_long_names):
if "prefixItems" in variant: if "prefixItems" in variant:
#print("tupple variant in enum", variant) #print("tupple variant in enum", variant)
registry.add_custom_type(variant_name, variant) registry.add_custom_type(variant_name, variant)
(sub_component_group, _) = process_component.process_component(registry, variant, update, {"nested": True}, nesting, nesting_long_names) (sub_component_group, _) = process_component.process_component(registry, variant, update, {"nested": True}, nesting_long_names=nesting_long_names)
additional_annotations[variant_prefixed_name] = sub_component_group additional_annotations[variant_prefixed_name] = sub_component_group
elif "properties" in variant: elif "properties" in variant:
#print("struct variant in enum", variant) #print("struct variant in enum", variant)
registry.add_custom_type(variant_name, variant) registry.add_custom_type(variant_name, variant)
(sub_component_group, _) = process_component.process_component(registry, variant, update, {"nested": True}, nesting, nesting_long_names) (sub_component_group, _) = process_component.process_component(registry, variant, update, {"nested": True}, nesting_long_names=nesting_long_names)
additional_annotations[variant_prefixed_name] = sub_component_group additional_annotations[variant_prefixed_name] = sub_component_group
else: # for the cases where it's neither a tupple nor a structs: FIXME: not 100% sure of this else: # for the cases where it's neither a tupple nor a structs: FIXME: not 100% sure of this
#print("other variant in enum") #print("other variant in enum")

View File

@ -2,15 +2,13 @@ from bpy.props import (StringProperty, IntProperty, CollectionProperty)
from .utils import generate_wrapper_propertyGroup from .utils import generate_wrapper_propertyGroup
from . import process_component from . import process_component
def process_list(registry, definition, update, nesting=[], nesting_long_names=[]): def process_list(registry, definition, update, nesting_long_names=[]):
value_types_defaults = registry.value_types_defaults value_types_defaults = registry.value_types_defaults
type_infos = registry.type_infos type_infos = registry.type_infos
short_name = definition["short_name"]
long_name = definition["long_name"] long_name = definition["long_name"]
ref_name = definition["items"]["type"]["$ref"].replace("#/$defs/", "") ref_name = definition["items"]["type"]["$ref"].replace("#/$defs/", "")
nesting = nesting+[short_name]
nesting_long_names = nesting_long_names + [long_name] nesting_long_names = nesting_long_names + [long_name]
item_definition = type_infos[ref_name] item_definition = type_infos[ref_name]
@ -20,9 +18,9 @@ def process_list(registry, definition, update, nesting=[], nesting_long_names=[]
property_group_class = None property_group_class = None
#if the content of the list is a unit type, we need to generate a fake wrapper, otherwise we cannot use layout.prop(group, "propertyName") as there is no propertyName ! #if the content of the list is a unit type, we need to generate a fake wrapper, otherwise we cannot use layout.prop(group, "propertyName") as there is no propertyName !
if is_item_value_type: if is_item_value_type:
property_group_class = generate_wrapper_propertyGroup(long_name, item_long_name, definition["items"]["type"]["$ref"], registry, update) property_group_class = generate_wrapper_propertyGroup(long_name, item_long_name, definition["items"]["type"]["$ref"], registry, update, nesting_long_names=nesting_long_names)
else: else:
(_, list_content_group_class) = process_component.process_component(registry, item_definition, update, {"nested": True, "long_name": item_long_name}, nesting) (_, list_content_group_class) = process_component.process_component(registry, item_definition, update, {"nested": True, "long_name": item_long_name}, nesting_long_names=nesting_long_names)
property_group_class = list_content_group_class property_group_class = list_content_group_class
item_collection = CollectionProperty(type=property_group_class) item_collection = CollectionProperty(type=property_group_class)

View File

@ -2,14 +2,12 @@ from bpy.props import (StringProperty, IntProperty, CollectionProperty, PointerP
from .utils import generate_wrapper_propertyGroup from .utils import generate_wrapper_propertyGroup
from . import process_component from . import process_component
def process_map(registry, definition, update, nesting=[], nesting_long_names=[]): def process_map(registry, definition, update, nesting_long_names=[]):
value_types_defaults = registry.value_types_defaults value_types_defaults = registry.value_types_defaults
type_infos = registry.type_infos type_infos = registry.type_infos
short_name = definition["short_name"]
long_name = definition["long_name"] long_name = definition["long_name"]
nesting = nesting + [short_name]
nesting_long_names = nesting_long_names + [long_name] nesting_long_names = nesting_long_names + [long_name]
value_ref_name = definition["valueType"]["type"]["$ref"].replace("#/$defs/", "") value_ref_name = definition["valueType"]["type"]["$ref"].replace("#/$defs/", "")
@ -26,9 +24,9 @@ def process_map(registry, definition, update, nesting=[], nesting_long_names=[])
#if the content of the list is a unit type, we need to generate a fake wrapper, otherwise we cannot use layout.prop(group, "propertyName") as there is no propertyName ! #if the content of the list is a unit type, we need to generate a fake wrapper, otherwise we cannot use layout.prop(group, "propertyName") as there is no propertyName !
if is_key_value_type: if is_key_value_type:
keys_property_group_class = generate_wrapper_propertyGroup(f"{long_name}_keys", original_long_name, definition_link, registry, update) keys_property_group_class = generate_wrapper_propertyGroup(f"{long_name}_keys", original_long_name, definition_link, registry, update, nesting_long_names=nesting_long_names)
else: else:
(_, list_content_group_class) = process_component.process_component(registry, key_definition, update, {"nested": True, "long_name": original_long_name}, nesting, nesting_long_names) (_, list_content_group_class) = process_component.process_component(registry, key_definition, update, {"nested": True, "long_name": original_long_name}, nesting_long_names=nesting_long_names)
keys_property_group_class = list_content_group_class keys_property_group_class = list_content_group_class
keys_collection = CollectionProperty(type=keys_property_group_class) keys_collection = CollectionProperty(type=keys_property_group_class)
@ -45,9 +43,9 @@ def process_map(registry, definition, update, nesting=[], nesting_long_names=[])
#if the content of the list is a unit type, we need to generate a fake wrapper, otherwise we cannot use layout.prop(group, "propertyName") as there is no propertyName ! #if the content of the list is a unit type, we need to generate a fake wrapper, otherwise we cannot use layout.prop(group, "propertyName") as there is no propertyName !
if is_value_value_type: if is_value_value_type:
values_property_group_class = generate_wrapper_propertyGroup(f"{long_name}_values", original_long_name, definition_link, registry, update) values_property_group_class = generate_wrapper_propertyGroup(f"{long_name}_values", original_long_name, definition_link, registry, update, nesting_long_names)
else: else:
(_, list_content_group_class) = process_component.process_component(registry, value_definition, update, {"nested": True, "long_name": original_long_name}, nesting, nesting_long_names) (_, list_content_group_class) = process_component.process_component(registry, value_definition, update, {"nested": True, "long_name": original_long_name}, nesting_long_names)
values_property_group_class = list_content_group_class values_property_group_class = list_content_group_class
values_collection = CollectionProperty(type=values_property_group_class) values_collection = CollectionProperty(type=values_property_group_class)

View File

@ -1,16 +1,14 @@
from bpy.props import (StringProperty) from bpy.props import (StringProperty)
from . import process_component from . import process_component
def process_structs(registry, definition, properties, update, nesting, nesting_long_names): def process_structs(registry, definition, properties, update, nesting_long_names):
value_types_defaults = registry.value_types_defaults value_types_defaults = registry.value_types_defaults
blender_property_mapping = registry.blender_property_mapping blender_property_mapping = registry.blender_property_mapping
type_infos = registry.type_infos type_infos = registry.type_infos
long_name = definition["long_name"] long_name = definition["long_name"]
short_name = definition["short_name"]
__annotations__ = {} __annotations__ = {}
default_values = {} default_values = {}
nesting = nesting + [short_name]
nesting_long_names = nesting_long_names + [long_name] nesting_long_names = nesting_long_names + [long_name]
for property_name in properties.keys(): for property_name in properties.keys():
@ -35,7 +33,7 @@ def process_structs(registry, definition, properties, update, nesting, nesting_l
__annotations__[property_name] = blender_property __annotations__[property_name] = blender_property
else: else:
original_long_name = original["long_name"] original_long_name = original["long_name"]
(sub_component_group, _) = process_component.process_component(registry, original, update, {"nested": True, "long_name": original_long_name}, nesting, nesting_long_names) (sub_component_group, _) = process_component.process_component(registry, original, update, {"nested": True, "long_name": original_long_name}, nesting_long_names)
__annotations__[property_name] = sub_component_group __annotations__[property_name] = sub_component_group
# if there are sub fields, add an attribute "sub_fields" possibly a pointer property ? or add a standard field to the type , that is stored under "attributes" and not __annotations (better) # if there are sub fields, add an attribute "sub_fields" possibly a pointer property ? or add a standard field to the type , that is stored under "attributes" and not __annotations (better)
else: else:

View File

@ -1,14 +1,12 @@
from bpy.props import (StringProperty) from bpy.props import (StringProperty)
from . import process_component from . import process_component
def process_tupples(registry, definition, prefixItems, update, nesting=[], nesting_long_names=[]): def process_tupples(registry, definition, prefixItems, update, nesting_long_names=[]):
value_types_defaults = registry.value_types_defaults value_types_defaults = registry.value_types_defaults
blender_property_mapping = registry.blender_property_mapping blender_property_mapping = registry.blender_property_mapping
type_infos = registry.type_infos type_infos = registry.type_infos
long_name = definition["long_name"] long_name = definition["long_name"]
short_name = definition["short_name"]
nesting = nesting + [short_name]
nesting_long_names = nesting_long_names + [long_name] nesting_long_names = nesting_long_names + [long_name]
__annotations__ = {} __annotations__ = {}
@ -41,7 +39,7 @@ def process_tupples(registry, definition, prefixItems, update, nesting=[], nesti
__annotations__[property_name] = blender_property __annotations__[property_name] = blender_property
else: else:
original_long_name = original["long_name"] original_long_name = original["long_name"]
(sub_component_group, _) = process_component.process_component(registry, original, update, {"nested": True, "long_name": original_long_name}, nesting) (sub_component_group, _) = process_component.process_component(registry, original, update, {"nested": True, "long_name": original_long_name}, nesting_long_names=nesting_long_names)
__annotations__[property_name] = sub_component_group __annotations__[property_name] = sub_component_group
else: else:
# component not found in type_infos, generating placeholder # component not found in type_infos, generating placeholder

View File

@ -37,9 +37,9 @@ def generate_propertyGroups_for_components():
for component_name in type_infos: for component_name in type_infos:
definition = type_infos[component_name] definition = type_infos[component_name]
is_component = definition['isComponent'] if "isComponent" in definition else False is_component = definition['isComponent'] if "isComponent" in definition else False
root_property_name = component_name if is_component else None root_property_name = component_name# if is_component else None
#print("root property", root_property_name) print("root property", component_name,f"({is_component})")
process_component(registry, definition, update_calback_helper(definition, update_component, root_property_name), None, []) process_component(registry, definition, update_calback_helper(definition, update_component, root_property_name), extras=None, nesting_long_names=[])
# if we had to add any wrapper types on the fly, process them now # if we had to add any wrapper types on the fly, process them now
registry.process_custom_types() registry.process_custom_types()

View File

@ -8,12 +8,16 @@ from bpy_types import PropertyGroup
# this helper creates a "fake"/wrapper property group that is NOT a real type in the registry # this helper creates a "fake"/wrapper property group that is NOT a real type in the registry
# usefull for things like value types in list items etc # usefull for things like value types in list items etc
def generate_wrapper_propertyGroup(wrapped_type_long_name_name, item_long_name, definition_link, registry, update): def generate_wrapper_propertyGroup(wrapped_type_long_name, item_long_name, definition_link, registry, update, nesting_long_names=[]):
value_types_defaults = registry.value_types_defaults value_types_defaults = registry.value_types_defaults
blender_property_mapping = registry.blender_property_mapping blender_property_mapping = registry.blender_property_mapping
is_item_value_type = item_long_name in value_types_defaults is_item_value_type = item_long_name in value_types_defaults
wrapper_name = "wrapper_" + wrapped_type_long_name_name
wrapper_name = "wrapper_" + wrapped_type_long_name
#nesting = nesting + [short_name]
nesting_long_names = nesting_long_names + [wrapper_name]
wrapper_definition = { wrapper_definition = {
"isComponent": False, "isComponent": False,
@ -33,7 +37,7 @@ def generate_wrapper_propertyGroup(wrapped_type_long_name_name, item_long_name,
} }
# we generate a very small 'hash' for the component name # we generate a very small 'hash' for the component name
property_group_name = registry.generate_propGroup_name(nesting=[], longName=wrapper_name) property_group_name = registry.generate_propGroup_name(nesting=nesting_long_names)
registry.add_custom_type(wrapper_name, wrapper_definition) registry.add_custom_type(wrapper_name, wrapper_definition)

View File

@ -217,25 +217,27 @@ class ComponentsRegistry(PropertyGroup):
long_names_to_propgroup_names = {} long_names_to_propgroup_names = {}
# generate propGroup name from nesting level & longName: each longName + nesting is unique # generate propGroup name from nesting level: each longName + nesting is unique
def generate_propGroup_name(self, nesting, longName): def generate_propGroup_name(self, nesting):
#print("gen propGroup name for", shortName, nesting) #print("gen propGroup name for", shortName, nesting)
key = str(nesting) + longName if len(nesting) > 0 else longName key = str(nesting)
propGroupHash = tiger_hash(key) propGroupHash = tiger_hash(key)
propGroupName = propGroupHash + "_ui" propGroupName = propGroupHash + "_ui"
# check for collision # check for collision
#print("--computing hash for", nesting, longName) padding = " " * (len(nesting) + 1)
print(f"{padding}--computing hash for", nesting)
if propGroupName in self.long_names_to_propgroup_names.values(): if propGroupName in self.long_names_to_propgroup_names.values():
print(" WARNING !! you have a collision between the hash of multiple component names: collision for", nesting, longName) print(" WARNING !! you have a collision between the hash of multiple component names: collision for", nesting)
self.long_names_to_propgroup_names[key] = propGroupName self.long_names_to_propgroup_names[key] = propGroupName
return propGroupName return propGroupName
def get_propertyGroupName_from_longName(self, longName): def get_propertyGroupName_from_longName(self, longName):
return self.long_names_to_propgroup_names.get(longName, None) return self.long_names_to_propgroup_names.get(str([longName]), None)
########### ###########

View File

@ -62,15 +62,15 @@ class ComponentsSettings(PropertyGroup):
schema_path: StringProperty( schema_path: StringProperty(
name="schema path", name="schema path",
description="path to the registry schema file", description="path to the registry schema file (relative to the assets path)",
default="registry.json", default="registry.json",
update=save_settings update=save_settings
)# type: ignore )# type: ignore
schema_path_full: StringProperty( schema_path_full: StringProperty(
name="schema full path", name="schema full path",
description="path to the registry schema file", description="full path to the registry schema file",
get=lambda self: os.path.abspath(os.path.join(os.path.dirname(bpy.data.filepath), self.schema_path)) get=lambda self: os.path.abspath(os.path.join(bpy.context.window_manager.blenvy.assets_path_full, self.schema_path))
) # type: ignore ) # type: ignore
watcher_enabled: BoolProperty(name="Watcher_enabled", default=True, update=toggle_watcher)# type: ignore watcher_enabled: BoolProperty(name="Watcher_enabled", default=True, update=toggle_watcher)# type: ignore