feat(Blenvy): huge improvements to components UI:

* removed obsolete / overly complex pieces of UI
 * complete rework of component selections: much nicer, searchable, simpler ui
 * main component selector & per "error" component selectors , as well as bulk replace
ones are now all independant
 * overhauled a lot of helpers to work with both objects & collections
 * reworked ui ordering for upgrade/ rename
 * more clearer status display for invalid components
 * etc etc
This commit is contained in:
kaosat.dev 2024-06-11 18:12:10 +02:00
parent 295c387132
commit 9cb0c6262e
12 changed files with 169 additions and 158 deletions

View File

@ -43,7 +43,7 @@ Blueprints:
- [x] on save: write IN THE COLLECTION PROPERTIES
- list of assets
- export path
- [ ] blueprint selection for nested blueprints is broken
- [x] blueprint selection for nested blueprints is broken
- [ ] scan & inject on load
- [ ] scan & inject on save
@ -58,7 +58,7 @@ Components:
- [ ] 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
- [x] 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
@ -69,6 +69,16 @@ Components:
- [x] move saveable settings out to a settings file
- [x] update save & load
- [x] add handling of polling frequency & enabling
- [x] move advanced tools to components tab
- [ ] remove most of the (bulk) advanced tools, too complex, too unclear (even for me !) and of limited use
- component renaming should be kept, but perhaps simplified:
- if a renaming fails because the parameters are incompatible, nuke the old parameters
- perhaps just add a display list of all NON component custom properties, so the user can find them easilly ?
- [ ] status "unregistered" is often false and misleading
-> see in registry ui "for custom_property in object.keys():"
- [x] overhaul / improve the component selector (with built in searching, etc)
- [ ] remove select_component_name_to_replace
General things to solve:
@ -124,7 +134,9 @@ General issues:
- [x] remove BlueprintsList & replace is with assets list
- [ ] update main docs
- [ ] rename project to Blenvy
- [ ] replace all references to the old 2 add-ons with those to Blenvy
- [ ] rename repo to "Blenvy"
clear && pytest -svv --blender-template ../../testing/bevy_example/art/testing_library.blend --blender-executable /home/ckaos/tools/blender/blender-4.1.0-linux-x64/blender tests/test_bevy_integration_prepare.py && pytest -svv --blender-executable /home/ckaos/tools/blender/blender-4.1.0-linux-x64/blender tests/test_bevy_integration.py

View File

@ -24,7 +24,6 @@ from .add_ons.bevy_components.registry.ui import (BEVY_COMPONENTS_PT_Configurati
from .add_ons.bevy_components.components.metadata import (ComponentMetadata, ComponentsMeta)
from .add_ons.bevy_components.components.lists import GENERIC_LIST_OT_actions, Generic_LIST_OT_AddItem, Generic_LIST_OT_RemoveItem, Generic_LIST_OT_SelectItem
from .add_ons.bevy_components.components.maps import GENERIC_MAP_OT_actions
from .add_ons.bevy_components.components.definitions_list import (ComponentDefinitionsList, ClearComponentDefinitionsList)
from .add_ons.bevy_components.components.ui import (BEVY_COMPONENTS_PT_ComponentsPanel)
from .add_ons.bevy_components.settings import ComponentsSettings
@ -80,9 +79,6 @@ classes = [
RenameHelper,
GenerateComponent_From_custom_property_Operator,
Toggle_ComponentVisibility,
ComponentDefinitionsList,
ClearComponentDefinitionsList,
ComponentMetadata,
ComponentsMeta,
@ -103,6 +99,7 @@ classes = [
BEVY_COMPONENTS_PT_ComponentsPanel,
BEVY_COMPONENTS_PT_AdvancedToolsPanel,
#BEVY_COMPONENTS_PT_Configuration,
MISSING_TYPES_UL_List,
BEVY_COMPONENTS_PT_MissingTypesPanel,

View File

@ -1,16 +1,9 @@
import json
import bpy
from blenvy.core.object_makers import (make_empty)
from blenvy.add_ons.bevy_components.utils import is_component_valid_and_enabled
from ..constants import custom_properties_to_filter_out
def is_component_valid_and_enabled(object, component_name):
if "components_meta" in object or hasattr(object, "components_meta"):
target_components_metadata = object.components_meta.components
component_meta = next(filter(lambda component: component["long_name"] == component_name, target_components_metadata), None)
if component_meta != None:
return component_meta.enabled and not component_meta.invalid
return True
def remove_unwanted_custom_properties(object):
to_remove = []
component_names = list(object.keys()) # to avoid 'IDPropertyGroup changed size during iteration' issues

View File

@ -39,14 +39,14 @@ def generate_temporary_scene_and_export(settings, gltf_export_settings, gltf_out
add_scene_property(temp_scene, 'assets_components', {"AllAssets": f"AllAssets({all_assets})".replace("'", '')})
# save active scene
original_scene = bpy.context.window.scene
active_scene = bpy.context.window.scene
# and selected collection
original_collection = bpy.context.view_layer.active_layer_collection
active_collection = bpy.context.view_layer.active_layer_collection
# and mode
original_mode = bpy.context.active_object.mode if bpy.context.active_object != None else None
active_mode = bpy.context.active_object.mode if bpy.context.active_object != None else None
# we change the mode to object mode, otherwise the gltf exporter is not happy
if original_mode != None and original_mode != 'OBJECT':
print("setting to object mode", original_mode)
if active_mode != None and active_mode != 'OBJECT':
print("setting to object mode", active_mode)
bpy.ops.object.mode_set(mode='OBJECT')
# we set our active scene to be this one : this is needed otherwise the stand-in empties get generated in the wrong scene
bpy.context.window.scene = temp_scene
@ -74,12 +74,12 @@ def generate_temporary_scene_and_export(settings, gltf_export_settings, gltf_out
tempScene_cleaner(temp_scene, scene_filler_data)
# reset active scene
bpy.context.window.scene = original_scene
bpy.context.window.scene = active_scene
# reset active collection
bpy.context.view_layer.active_layer_collection = original_collection
bpy.context.view_layer.active_layer_collection = active_collection
# reset mode
if original_mode != None:
bpy.ops.object.mode_set( mode = original_mode )
if active_mode is not None:
bpy.ops.object.mode_set( mode = active_mode )

View File

@ -1,57 +0,0 @@
import bpy
from bpy.props import (StringProperty)
# this one is for UI only, and its inner list contains a useable list of shortnames of components
class ComponentDefinitionsList(bpy.types.PropertyGroup):
# FIXME: not sure, hard coded exclude list, feels wrong
exclude = ['Parent', 'Children']
def add_component_to_ui_list(self, context):
#print("add components to ui_list")
items = []
type_infos = context.window_manager.components_registry.type_infos
for long_name in type_infos.keys():
definition = type_infos[long_name]
short_name = definition["short_name"]
is_component = definition['isComponent'] if "isComponent" in definition else False
if self.filter in short_name and is_component:
if not 'Handle' in short_name and not "Cow" in short_name and not "AssetId" in short_name and short_name not in self.exclude: # FIXME: hard coded, seems wrong
items.append((long_name, short_name, long_name))
items.sort(key=lambda a: a[1])
return items
@classmethod
def register(cls):
bpy.types.WindowManager.components_list = bpy.props.PointerProperty(type=ComponentDefinitionsList)
@classmethod
def unregister(cls):
del bpy.types.WindowManager.components_list
list : bpy.props.EnumProperty(
name="list",
description="list",
# items argument required to initialize, just filled with empty values
items = add_component_to_ui_list,
) # type: ignore
filter: StringProperty(
name="component filter",
description="filter for the components list",
options={'TEXTEDIT_UPDATE'}
) # type: ignore
class ClearComponentDefinitionsList(bpy.types.Operator):
''' clear list of bpy.context.collection.component_definitions '''
bl_label = "clear component definitions"
bl_idname = "components.clear_component_definitions"
def execute(self, context):
# create a new item, assign its properties
bpy.context.collection.component_definitions.clear()
return {'FINISHED'}

View File

@ -4,6 +4,7 @@ from bpy_types import (PropertyGroup)
from ..propGroups.conversions_from_prop_group import property_group_value_to_custom_property_value
from ..propGroups.conversions_to_prop_group import property_group_value_from_custom_property_value
from ..utils import add_component_to_ui_list
class ComponentMetadata(bpy.types.PropertyGroup):
short_name : bpy.props.StringProperty(
@ -43,6 +44,8 @@ class ComponentMetadata(bpy.types.PropertyGroup):
default=True
) # type: ignore
class ComponentsMeta(PropertyGroup):
infos_per_component: StringProperty(
name="infos per component",
@ -50,6 +53,12 @@ class ComponentsMeta(PropertyGroup):
) # type: ignore
components: bpy.props.CollectionProperty(type = ComponentMetadata) # type: ignore
# compone
component_selector: StringProperty(
search=add_component_to_ui_list
) # type: ignore
@classmethod
def register(cls):
# you can add components to both objects & collections

View File

@ -198,7 +198,7 @@ class RemoveComponentFromAllItemsOperator(Operator):
class RenameHelper(bpy.types.PropertyGroup):
original_name: bpy.props.StringProperty(name="") # type: ignore
new_name: bpy.props.StringProperty(name="") # type: ignore
target_name: bpy.props.StringProperty(name="") # type: ignore
#object: bpy.props.PointerProperty(type=bpy.types.Object)
@classmethod
@ -217,12 +217,12 @@ class OT_rename_component(Operator):
bl_options = {"UNDO"}
original_name: bpy.props.StringProperty(default="") # type: ignore
new_name: StringProperty(
name="new_name",
target_name: StringProperty(
name="target_name",
description="new name of component",
) # type: ignore
target_objects: bpy.props.StringProperty() # type: ignore
target_items: bpy.props.StringProperty() # type: ignore
@classmethod
def register(cls):
@ -237,28 +237,28 @@ class OT_rename_component(Operator):
type_infos = registry.type_infos
settings = context.window_manager.bevy_component_rename_helper
original_name = settings.original_name if self.original_name == "" else self.original_name
new_name = self.new_name
target_name = self.target_name
print("renaming components: original name", original_name, "new_name", self.new_name, "targets", self.target_objects)
target_objects = json.loads(self.target_objects)
print("renaming components: original name", original_name, "target_name", self.target_name, "targets", self.target_items)
target_items = json.loads(self.target_items)
errors = []
total = len(target_objects)
total = len(target_items)
if original_name != '' and new_name != '' and original_name != new_name and len(target_objects) > 0:
for index, item_name in enumerate(target_objects):
if original_name != '' and target_name != '' and original_name != target_name and len(target_items) > 0:
for index, item_name in enumerate(target_items):
object = bpy.data.objects[item_name]
if object and original_name in get_bevy_components(object) or original_name in object:
try:
# attempt conversion
rename_component(object=object, original_long_name=original_name, new_long_name=new_name)
rename_component(item=object, original_long_name=original_name, new_long_name=target_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
components_metadata = getattr(object, "components_meta", None)
if components_metadata:
components_metadata = components_metadata.components
component_meta = next(filter(lambda component: component["long_name"] == new_name, components_metadata), None)
component_meta = next(filter(lambda component: component["long_name"] == target_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"

View File

@ -172,7 +172,7 @@ class BEVY_COMPONENTS_PT_ComponentsPanel(bpy.types.Panel):
# 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)
#print("object", context.object, "active", context.active_object, "objects", context.selected_objects)
def draw(self, context):
object = next(iter(context.selected_objects), None)
@ -181,28 +181,27 @@ class BEVY_COMPONENTS_PT_ComponentsPanel(bpy.types.Panel):
# we get & load our component registry
registry = bpy.context.window_manager.components_registry
available_components = bpy.context.window_manager.components_list
selected_component = bpy.context.window_manager.blenvy.components.component_selector
registry_has_type_infos = registry.has_type_infos()
if object is not None:
draw_component_ui(layout, object, registry, available_components, registry_has_type_infos, context)
draw_component_ui(layout, object, registry, selected_component, registry_has_type_infos, context)
elif collection is not None:
draw_component_ui(layout, collection, registry, available_components, registry_has_type_infos, context)
draw_component_ui(layout, collection, registry, selected_component, 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):
def draw_component_ui(layout, object_or_collection, registry, selected_component, registry_has_type_infos, context):
row = layout.row(align=True)
row.prop(available_components, "list", text="Component")
row.prop(available_components, "filter",text="Filter")
row.prop(context.window_manager.blenvy.components, "component_selector", text="Component: ")
# 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 != ''
op.component_type = selected_component
row.enabled = selected_component != ''
layout.separator()
@ -249,7 +248,6 @@ def draw_component_ui(layout, object_or_collection, registry, available_componen
# 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)
@ -299,4 +297,4 @@ def draw_component_ui(layout, object_or_collection, registry, available_componen
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()
#row.separator()

View File

@ -0,0 +1,2 @@
# FIXME: not sure, hard coded exclude list ?
HIDDEN_COMPONENTS = ['Parent', 'Children']

View File

@ -69,7 +69,7 @@ class BEVY_COMPONENTS_PT_AdvancedToolsPanel(bpy.types.Panel):
@classmethod
def poll(cls, context):
return context.window_manager.blenvy.mode == 'TOOLS'
return context.window_manager.blenvy.mode == 'COMPONENTS'
def draw_invalid_or_unregistered_header(self, layout, items):
row = layout.row()
@ -78,17 +78,13 @@ class BEVY_COMPONENTS_PT_AdvancedToolsPanel(bpy.types.Panel):
col = row.column()
col.label(text=item)
def draw_invalid_or_unregistered(self, layout, status, component_name, target):
available_components = bpy.context.window_manager.components_list
registry = bpy.context.window_manager.components_registry
registry_has_type_infos = registry.has_type_infos()
selected_component = target.components_meta.component_selector
row = layout.row()
col = row.column()
col.label(text=component_name)
col = row.column()
operator = col.operator("object.select", text=target.name)
operator.target_name = target.name
@ -97,15 +93,21 @@ class BEVY_COMPONENTS_PT_AdvancedToolsPanel(bpy.types.Panel):
col.label(text=status)
col = row.column()
col.prop(available_components, "list", text="")
col.label(text=component_name)
col = row.column()
# each components_meta has a component selector to pick components from
components_meta = target.components_meta
col.prop(components_meta, "component_selector", text="")
col = row.column()
operator = col.operator("object.rename_bevy_component", text="", icon="SHADERFX") #rename
new_name = registry.type_infos[available_components.list]['long_name'] if available_components.list in registry.type_infos else ""
target_name = registry.type_infos[selected_component]['long_name'] if selected_component in registry.type_infos else ""
operator.original_name = component_name
operator.target_objects = json.dumps([target.name])
operator.new_name = new_name
col.enabled = registry_has_type_infos and component_name != "" and component_name != new_name
operator.target_items = json.dumps([target.name])
operator.target_name = target_name
col.enabled = registry_has_type_infos and component_name != "" and component_name != target_name
col = row.column()
@ -115,84 +117,100 @@ class BEVY_COMPONENTS_PT_AdvancedToolsPanel(bpy.types.Panel):
operator.item_type = get_selection_type(target)
col = row.column()
"""col = row.column()
col = row.column()
operator = col.operator("object.select_component_name_to_replace", text="", icon="EYEDROPPER") #text="select for rename",
operator.component_name = component_name
operator.component_name = component_name"""
def draw_invalid_item_entry(self, layout, item, invalid_component_names, items_with_invalid_components):
if "components_meta" in item:
components_metadata = item.components_meta.components
object_component_names = []
for index, component_meta in enumerate(components_metadata):
long_name = component_meta.long_name
if component_meta.invalid:
self.draw_invalid_or_unregistered(layout, "Invalid", long_name, item)
if not item.name in items_with_invalid_components:
items_with_invalid_components.append(item.name)
if not long_name in invalid_component_names:
invalid_component_names.append(long_name)
object_component_names.append(long_name)
for custom_property in item.keys():
# Invalid (something is wrong)
# Unregistered (not in registry)
# Upgrade Needed (Old-style component)
status = None
if custom_property != 'components_meta' and custom_property != 'bevy_components' and custom_property not in object_component_names:
status = "Upgrade Needed"
if status is not None:
self.draw_invalid_or_unregistered(layout, status, custom_property, item)
if not item.name in items_with_invalid_components:
items_with_invalid_components.append(item.name)
"""if not long_name in invalid_component_names:
invalid_component_names.append(custom_property)""" # FIXME
def draw(self, context):
layout = self.layout
registry = bpy.context.window_manager.components_registry
registry_has_type_infos = registry.has_type_infos()
selected_object = context.selected_objects[0] if len(context.selected_objects) > 0 else None
available_components = bpy.context.window_manager.components_list
selected_component = bpy.context.window_manager.blenvy.components.component_selector
row = layout.row()
box= row.box()
box.label(text="Invalid/ unregistered components")
objects_with_invalid_components = []
items_with_invalid_components = []
invalid_component_names = []
self.draw_invalid_or_unregistered_header(layout, ["Component", "Object", "Status", "Target"])
self.draw_invalid_or_unregistered_header(layout, ["Item","Status", "Component", "Target"])
for object in bpy.data.objects: # TODO: very inneficent
if len(object.keys()) > 0:
if "components_meta" in object:
components_metadata = object.components_meta.components
comp_names = []
for index, component_meta in enumerate(components_metadata):
long_name = component_meta.long_name
if component_meta.invalid:
self.draw_invalid_or_unregistered(layout, "Invalid", long_name, object)
if not object.name in objects_with_invalid_components:
objects_with_invalid_components.append(object.name)
if not long_name in invalid_component_names:
invalid_component_names.append(long_name)
self.draw_invalid_item_entry(layout, object, invalid_component_names, items_with_invalid_components)
for collection in bpy.data.collections:
if len(collection.keys()) > 0:
self.draw_invalid_item_entry(layout, collection, invalid_component_names, items_with_invalid_components)
comp_names.append(long_name)
for custom_property in object.keys():
if custom_property != 'components_meta' and custom_property != 'bevy_components' and custom_property not in comp_names:
self.draw_invalid_or_unregistered(layout, "Unregistered", custom_property, object)
if not object.name in objects_with_invalid_components:
objects_with_invalid_components.append(object.name)
"""if not long_name in invalid_component_names:
invalid_component_names.append(custom_property)""" # FIXME
layout.separator()
layout.separator()
original_name = bpy.context.window_manager.bevy_component_rename_helper.original_name
layout.label(text="------------------Bulk actions: Rename/ Upgrade -------------------")
original_name = bpy.context.window_manager.blenvy.components.source_component_selector
target_name = bpy.context.window_manager.blenvy.components.target_component_selector
row = layout.row()
col = row.column()
col.label(text="Original")
col.label(text="Component")
col = row.column()
col.label(text="New")
col.label(text="Target")
col = row.column()
col.label(text="------")
row = layout.row()
col = row.column()
box = col.box()
box.label(text=original_name)
col.prop(bpy.context.window_manager.blenvy.components, "source_component_selector", text="")
col = row.column()
col.prop(available_components, "list", text="")
#row.prop(available_components, "filter",text="Filter")
col.prop(bpy.context.window_manager.blenvy.components, "target_component_selector", text="")
col = row.column()
components_rename_progress = context.window_manager.components_rename_progress
print("components_rename_progress", components_rename_progress)
if components_rename_progress == -1.0:
operator = col.operator(OT_rename_component.bl_idname, text="apply", icon="SHADERFX")
operator.target_objects = json.dumps(objects_with_invalid_components)
new_name = registry.type_infos[available_components.list]['short_name'] if available_components.list in registry.type_infos else ""
operator.new_name = new_name
col.enabled = registry_has_type_infos and original_name != "" and original_name != new_name
operator.target_items = json.dumps(items_with_invalid_components)
operator.target_name = target_name
col.enabled = registry_has_type_infos and original_name != "" and original_name != target_name
else:
if hasattr(layout,"progress") : # only for Blender > 4.0
col.progress(factor = components_rename_progress, text=f"updating {components_rename_progress * 100.0:.2f}%")
@ -208,7 +226,7 @@ class BEVY_COMPONENTS_PT_AdvancedToolsPanel(bpy.types.Panel):
col.progress(factor = remove_components_progress, text=f"updating {remove_components_progress * 100.0:.2f}%")
layout.separator()
layout.separator()
"""layout.separator()
row = layout.row()
box= row.box()
box.label(text="Conversions between custom properties and components & vice-versa")
@ -266,7 +284,7 @@ class BEVY_COMPONENTS_PT_AdvancedToolsPanel(bpy.types.Panel):
if hasattr(layout,"progress") : # only for Blender > 4.0
layout.progress(factor = components_from_custom_properties_progress_all, text=f"updating {components_from_custom_properties_progress_all * 100.0:.2f}%")
"""
class BEVY_COMPONENTS_PT_MissingTypesPanel(bpy.types.Panel):
"""panel listing all the missing bevy types in the schema"""
bl_idname = "BEVY_COMPONENTS_PT_MissingTypesPanel"
@ -340,3 +358,4 @@ class MISSING_TYPES_UL_List(UIList):
layout.alignment = 'CENTER'
row = layout.row()
row.prop(item, "long_name", text="")

View File

@ -5,6 +5,7 @@ from bpy.props import (EnumProperty, PointerProperty, StringProperty, BoolProper
from blenvy.settings import load_settings, upsert_settings, generate_complete_settings_dict
from .propGroups.prop_groups import generate_propertyGroups_for_components
from .components.metadata import ensure_metadata_for_all_items
from .utils import add_component_to_ui_list
# list of settings we do NOT want to save
settings_black_list = ['settings_save_enabled', 'watcher_active']
@ -52,7 +53,6 @@ def watch_schema():
pass
return component_settings.watcher_poll_frequency if component_settings.watcher_enabled else None
class ComponentsSettings(PropertyGroup):
settings_save_path = ".blenvy_components_settings" # where to store data in bpy.texts
@ -91,6 +91,20 @@ class ComponentsSettings(PropertyGroup):
)# type: ignore
component_selector: StringProperty(
search=add_component_to_ui_list
)# type: ignore
source_component_selector: StringProperty(
search=add_component_to_ui_list
)# type: ignore
target_component_selector: StringProperty(
search=add_component_to_ui_list
)# type: ignore
@classmethod
def register(cls):
pass

View File

@ -1,4 +1,5 @@
import bpy
from .constants import HIDDEN_COMPONENTS
#FIXME: does not work if object is hidden !!
def get_selected_object_or_collection(context):
@ -15,4 +16,27 @@ def get_selection_type(selection):
if isinstance(selection, bpy.types.Object):
return 'Object'
if isinstance(selection, bpy.types.Collection):
return 'Collection'
return 'Collection'
def add_component_to_ui_list(self, context, _):
print("add components to ui_list")
items = []
type_infos = context.window_manager.components_registry.type_infos
for long_name in type_infos.keys():
definition = type_infos[long_name]
short_name = definition["short_name"]
is_component = definition['isComponent'] if "isComponent" in definition else False
"""if self.filter.lower() in short_name.lower() and is_component:"""
if not 'Handle' in short_name and not "Cow" in short_name and not "AssetId" in short_name and short_name not in HIDDEN_COMPONENTS: # FIXME: hard coded, seems wrong
items.append((long_name, short_name))
items.sort(key=lambda a: a[1])
return items
def is_component_valid_and_enabled(object, component_name):
if "components_meta" in object or hasattr(object, "components_meta"):
target_components_metadata = object.components_meta.components
component_meta = next(filter(lambda component: component["long_name"] == component_name, target_components_metadata), None)
if component_meta != None:
return component_meta.enabled and not component_meta.invalid
return True