From 3d17bc5f26586855aba67b05ed1955c96ced078a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabi=C3=A1n=20C=C3=A1rdenas?= Date: Tue, 3 Feb 2026 16:33:35 -0600 Subject: [PATCH] Update for Blender 5.0 compatibility and minor fixes on default Actions --- .vscode/settings.json | 7 +- ActRec/Storage.json | 62 +++------ ActRec/actrec/functions/globals.py | 22 +-- ActRec/actrec/functions/shared.py | 6 + ActRec/actrec/operators/globals.py | 35 +++-- ActRec/actrec/preferences.py | 185 +++++++++---------------- ActRec/actrec/properties/categories.py | 19 +-- ActRec/actrec/properties/globals.py | 97 ++++--------- ActRec/actrec/properties/locals.py | 16 +-- 9 files changed, 163 insertions(+), 286 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 3a1b743..16773b5 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -44,7 +44,12 @@ ], "github-actions.workflows.pinned.workflows": [ ".github/workflows/unit_test_addon.yml" - ] + ], + //Use this lines to remove annorying blender new api errors on vscode + // "python.analysis.diagnosticSeverityOverrides": { + // "reportInvalidTypeForm": "none", + // "reportIndexIssue": "none" + // } } // ActRec_pref (bpy.types.Preferences): preferences of this addon diff --git a/ActRec/Storage.json b/ActRec/Storage.json index c1d6c7f..cd21f21 100644 --- a/ActRec/Storage.json +++ b/ActRec/Storage.json @@ -51,13 +51,13 @@ "id": "c7a7a7d9164611ecb20d70c94ef23b30" }, { - "id": "c7a7cecf164611ecbea570c94ef23b30" + "id": "c7a7f5bd164611ec8b7d70c94ef23b30" }, { - "id": "c7a7ced2164611ec9dcf70c94ef23b30" + "id": "c7a7cecf164611ecbea570c94ef23b30" }, { - "id": "c7a7f5bd164611ec8b7d70c94ef23b30" + "id": "c7a7ced2164611ec9dcf70c94ef23b30" } ], "areas": [ @@ -128,7 +128,7 @@ ], "actions": [ { - "icon": 3, + "icon": 47, "icon_name": "CANCEL", "id": "c7a1f271164611eca91770c94ef23b30", "label": "Delete", @@ -264,7 +264,7 @@ ] }, { - "icon": 289, + "icon": 334, "icon_name": "MESH_UVSPHERE", "id": "c7a73281164611eca36c70c94ef23b30", "label": "AddCubeSphere", @@ -333,7 +333,7 @@ ] }, { - "icon": 608, + "icon": 6, "icon_name": "AUTOMERGE_ON", "id": "c7a759ee164611ecb84c70c94ef23b30", "label": "Merge", @@ -362,10 +362,10 @@ ] }, { - "icon": 391, + "icon": 393, "icon_name": "ALIGN_CENTER", "id": "c7a780f7164611ecb42870c94ef23b30", - "label": "CheckerEdoge", + "label": "Checker Edge Loop", "description": "Play this Action Button", "macros": [ { @@ -391,7 +391,7 @@ ] }, { - "icon": 462, + "icon": 445, "icon_name": "MOD_CAST", "id": "c7a7a7d9164611ecb20d70c94ef23b30", "label": "Crease&Shap", @@ -420,7 +420,7 @@ ] }, { - "icon": 288, + "icon": 325, "icon_name": "MESH_CIRCLE", "id": "c7a7cecf164611ecbea570c94ef23b30", "label": "CreaseClear", @@ -449,7 +449,7 @@ ] }, { - "icon": 201, + "icon": 212, "icon_name": "GROUP_VERTEX", "id": "c7a7ced2164611ec9dcf70c94ef23b30", "label": "ResetShape", @@ -468,7 +468,7 @@ ] }, { - "icon": 555, + "icon": 566, "icon_name": "PIVOT_ACTIVE", "id": "c7a7f5bd164611ec8b7d70c94ef23b30", "label": "Origin To Select", @@ -527,7 +527,7 @@ ] }, { - "icon": 307, + "icon": 337, "icon_name": "META_CUBE", "id": "c7a81cb8164611ecb9ca70c94ef23b30", "label": "Seam&UV", @@ -625,7 +625,7 @@ ] }, { - "icon": 132, + "icon": 161, "icon_name": "UV_DATA", "id": "c7a843b2164611ec929870c94ef23b30", "label": "ClearSeam", @@ -702,7 +702,7 @@ ] }, { - "icon": 166, + "icon": 198, "icon_name": "TEXTURE_DATA", "id": "c7a8918b164611ecb21d70c94ef23b30", "label": "AlignLayout", @@ -761,7 +761,7 @@ ] }, { - "icon": 287, + "icon": 327, "icon_name": "MESH_CUBE", "id": "c7a8b816164611ecab0970c94ef23b30", "label": "AddCube", @@ -859,7 +859,7 @@ ] }, { - "icon": 127, + "icon": 142, "icon_name": "NODE_MATERIAL", "id": "146b8a47986011ed86faa8a1598ce02d", "label": "Subd Smooth", @@ -869,36 +869,16 @@ "icon": 0, "icon_name": "NONE", "id": "c7a40355164611ecb9cd70c94ef23b30", - "label": "Shade Smooth", + "label": "Shade Smooth by Angle", "command": "bpy.ops.object.shade_smooth()", "active": true, "ui_type": "", "operator_execution_context": "EXEC_DEFAULT" - }, - { - "icon": 0, - "icon_name": "NONE", - "id": "c7a42aa4164611ecba6570c94ef23b30", - "label": "Auto Smooth = True", - "command": "bpy.context.object.data.use_auto_smooth = True", - "active": true, - "ui_type": "", - "operator_execution_context": "EXEC_DEFAULT" - }, - { - "icon": 0, - "icon_name": "NONE", - "id": "c7a6be1e164611ec8ede70c94ef23b30", - "label": "Auto Smooth Angle = 3.14159", - "command": "bpy.context.object.data.auto_smooth_angle = 3.14159", - "active": true, - "ui_type": "", - "operator_execution_context": "EXEC_DEFAULT" } ] }, { - "icon": 127, + "icon": 142, "icon_name": "NODE_MATERIAL", "id": "c7a40353164611ecbaad70c94ef23b30", "label": "Subd Smooth", @@ -919,7 +899,7 @@ "icon_name": "NONE", "id": "35bf6e3e986011edb5cfa8a1598ce02d", "label": "Shade Smooth", - "command": "bpy.ops.object.shade_smooth(use_auto_smooth=True)", + "command": "bpy.ops.object.shade_smooth()", "active": true, "ui_type": "VIEW_3D", "operator_execution_context": "EXEC_DEFAULT" @@ -947,7 +927,7 @@ ] }, { - "icon": 256, + "icon": 30, "icon_name": "RESTRICT_SELECT_OFF", "id": "4275b287986211edb4bca8a1598ce02d", "label": "SelectCube", diff --git a/ActRec/actrec/functions/globals.py b/ActRec/actrec/functions/globals.py index d942163..44be9cc 100644 --- a/ActRec/actrec/functions/globals.py +++ b/ActRec/actrec/functions/globals.py @@ -133,22 +133,12 @@ def get_global_action_id(ActRec_pref: AR_preferences, id: str, index: int) -> Un return None -def get_global_action_ids(ActRec_pref: AR_preferences, id: str, index: int) -> list: - """ - get global action is inside a list or selected global actions if not found - - Args: - ActRec_pref (AR_preferences): preferences of this addon - id (str): id to check - index (int): index of action - - Returns: - list: list with ids of actions - """ - id = get_global_action_id(ActRec_pref, id, index) - if id is None: - return ActRec_pref.get("global_actions.selected_ids", []) - return [id] +def get_global_action_ids(ActRec_pref, id, index): + # BLENDER 5.0 FIX: Convert the internal string back to a list for the operators + raw_ids = ActRec_pref.global_selected_ids_internal + if raw_ids: + return raw_ids.split(",") + return [] def add_empty_action_keymap(id: str, km: KeyMap) -> KeyMapItem: diff --git a/ActRec/actrec/functions/shared.py b/ActRec/actrec/functions/shared.py index 91eb5ba..b393a8b 100644 --- a/ActRec/actrec/functions/shared.py +++ b/ActRec/actrec/functions/shared.py @@ -116,6 +116,12 @@ def property_to_python(property: Property, exclude: list = [], depth: int = 5) - Union[list, dict, str]: converts Collection, Arrays to lists and PointerProperty to dict """ # CollectionProperty are a list of PointerProperties + if hasattr(property, "bl_rna") and property.bl_rna.identifier == "AR_preferences": + return "Preferences Object (Skipped to prevent loop)" + + if depth <= 0: + return "max depth" + if depth <= 0: return "max depth" if isinstance(property, set): # Catch EnumProperty with EnumFlag diff --git a/ActRec/actrec/operators/globals.py b/ActRec/actrec/operators/globals.py index 2259a2e..ed65b96 100644 --- a/ActRec/actrec/operators/globals.py +++ b/ActRec/actrec/operators/globals.py @@ -40,7 +40,7 @@ class AR_OT_global_recategorize_action(shared.Id_based, Operator): @classmethod def poll(cls, context: Context) -> bool: ActRec_pref = get_preferences(context) - return len(ActRec_pref.global_actions) and len(ActRec_pref.get("global_actions.selected_ids", [])) + return len(ActRec_pref.global_actions) and bool(ActRec_pref.global_selected_ids_internal) def invoke(self, context: Context, event: Event) -> set[str]: return context.window_manager.invoke_props_dialog(self) @@ -570,7 +570,7 @@ class AR_OT_global_to_local(shared.Id_based, Operator): @ classmethod def poll(cls, context: Context) -> bool: ActRec_pref = get_preferences(context) - return len(ActRec_pref.global_actions) and len(ActRec_pref.get("global_actions.selected_ids", [])) + return len(ActRec_pref.global_actions) and bool(ActRec_pref.global_selected_ids_internal) def global_to_local(self, ActRec_pref: AR_preferences, action: AR_global_actions) -> None: """ @@ -615,14 +615,18 @@ class AR_OT_global_remove(shared.Id_based, Operator): @classmethod def description(cls, context: Context, properties: OperatorProperties) -> str: ActRec_pref = get_preferences(context) - ids = ActRec_pref.get("global_actions.selected_ids", []) - selected_actions_str = ", ".join(ActRec_pref.global_actions[id].label for id in ids) - return "Remove the selected actions\nActions: %s" % (selected_actions_str) + # BLENDER 5.0 FIX: Convert string back to list for the tooltip + raw_ids = ActRec_pref.global_selected_ids_internal + ids = raw_ids.split(",") if raw_ids else [] + + selected_labels = [ActRec_pref.global_actions[id].label for id in ids if id in ActRec_pref.global_actions] + selected_actions_str = ", ".join(selected_labels) + return "Remove the selected actions\nActions: %s" % (selected_actions_str if selected_actions_str else "None") @classmethod def poll(cls, context: Context) -> bool: ActRec_pref = get_preferences(context) - return len(ActRec_pref.global_actions) and len(ActRec_pref.get("global_actions.selected_ids", [])) + return len(ActRec_pref.global_actions) and bool(ActRec_pref.global_selected_ids_internal) def invoke(self, context: Context, event: Event) -> set[str]: return context.window_manager.invoke_confirm(self, event) @@ -651,7 +655,7 @@ class AR_OT_global_move_up(shared.Id_based, Operator): @classmethod def poll(cls, context: Context) -> bool: ActRec_pref = get_preferences(context) - return len(ActRec_pref.global_actions) and len(ActRec_pref.get("global_actions.selected_ids", [])) + return len(ActRec_pref.global_actions) and bool(ActRec_pref.global_selected_ids_internal) def execute(self, context: Context) -> set[str]: ActRec_pref = get_preferences(context) @@ -677,7 +681,7 @@ class AR_OT_global_move_down(shared.Id_based, Operator): @classmethod def poll(cls, context: Context) -> bool: ActRec_pref = get_preferences(context) - return len(ActRec_pref.global_actions) and len(ActRec_pref.get("global_actions.selected_ids", [])) + return len(ActRec_pref.global_actions) and bool(ActRec_pref.global_selected_ids_internal) def execute(self, context: Context) -> set[str]: ActRec_pref = get_preferences(context) @@ -702,11 +706,16 @@ class AR_OT_global_execute_action(shared.Id_based, Operator): bl_options = {'UNDO', 'INTERNAL'} @classmethod - def description(cls, context: Context, properties: OperatorProperties): - ActRec_pref = functions.get_preferences(context) - id = functions.get_global_action_id(ActRec_pref, properties.id, properties.index) - action = ActRec_pref.global_actions[id] - return action.description + def description(cls, context, properties): + ActRec_pref = get_preferences(context) + # BLENDER 5.0 FIX: Get list from the internal string property + raw_ids = ActRec_pref.global_selected_ids_internal + ids = raw_ids.split(",") if raw_ids else [] + + if len(ids) > 1: + return f"Selected Actions: {len(ids)}" + # ... rest of the existing description logic (e.g. return self.bl_description) + return cls.bl_description def execute(self, context: Context) -> set[str]: ActRec_pref = get_preferences(context) diff --git a/ActRec/actrec/preferences.py b/ActRec/actrec/preferences.py index e7a6fbf..f593e50 100644 --- a/ActRec/actrec/preferences.py +++ b/ActRec/actrec/preferences.py @@ -22,18 +22,24 @@ def get_preferences(): return # region Preferences - class AR_preferences(AddonPreferences): + global_selected_ids_internal: StringProperty(default="") bl_idname = __package__.split(".")[0] + # --- Stable Internal Properties for Blender 5.0 --- + is_loaded_internal: BoolProperty(default=False) + icon_path_internal: StringProperty(default="") + storage_path_internal: StringProperty(default="") + active_local_action_index_internal: IntProperty(default=0) + def update_is_loaded(self, context: Context) -> None: context.scene.name = context.scene.name def get_is_loaded(self) -> bool: - return self.get("is_loaded", False) and shared_data.data_loaded + return self.is_loaded_internal and shared_data.data_loaded def set_is_loaded(self, value: bool) -> None: - self["is_loaded"] = value + self.is_loaded_internal = value is_loaded: BoolProperty( name="INTERNAL", @@ -48,7 +54,7 @@ def set_is_loaded(self, value: bool) -> None: name="addon directory", default=os.path.dirname(os.path.dirname(__file__)), get=lambda self: self.bl_rna.properties['addon_directory'].default - ) # get the base addon directory + ) preference_tab: EnumProperty( items=[('settings', "Settings", ""), @@ -59,7 +65,6 @@ def set_is_loaded(self, value: bool) -> None: description="Switch between preference tabs" ) - # log log_amount: IntProperty( name="Log Amount", description="Number of log files kept\nChanges apply on the next launch of Blender", @@ -70,33 +75,22 @@ def set_is_loaded(self, value: bool) -> None: # icon manager def get_icon_path(self) -> str: - """ - getter of icon_path - fallback to relative path of the addon if folder doesn't exists - - Returns: - str: path of the folder - """ - origin_path = self.get('icon_path', 'Fallback') + origin_path = self.icon_path_internal + if origin_path == "": + origin_path = os.path.join(self.addon_directory, "Icons") + self.icon_path_internal = origin_path + if os.path.exists(origin_path): - return self['icon_path'] + return origin_path else: path = os.path.join(self.addon_directory, "Icons") - if origin_path != 'Fallback': - logger.error("ActRec ERROR: Icon Path \"%s\" don't exist, fallback to %s" % (origin_path, path)) - self['icon_path'] = path if not os.path.exists(path): os.makedirs(path, exist_ok=True) + self.icon_path_internal = path return path def set_icon_path(self, origin_path: str) -> None: - """setter of icon_path - creates new folder if needed - - Args: - origin_path (str): path of the new icon folder - """ - self['icon_path'] = origin_path + self.icon_path_internal = origin_path if not (os.path.exists(origin_path) and os.path.isdir(origin_path)): os.makedirs(origin_path, exist_ok=True) @@ -108,7 +102,6 @@ def set_icon_path(self, origin_path: str) -> None: set=set_icon_path ) - # Icon NONE: Global: BLANK1 (101), Local: MESH_PLANE (286) selected_icon: IntProperty( name="selected icon", description="only internal usage", @@ -134,35 +127,23 @@ def set_icon_path(self, origin_path: str) -> None: soft_min=0, soft_max=100, subtype='PERCENTAGE' - ) # used as slider + ) # locals local_actions: CollectionProperty(type=properties.AR_local_actions) def get_active_local_action_index(self) -> int: - """ - getter of active_local_action_index - - Returns: - int: index of the active local action - """ - value = self.get('active_local_action_index', 0) + value = self.active_local_action_index_internal actions_length = len(self.local_actions) - return value if value < actions_length else actions_length - 1 + return value if value < actions_length else max(0, actions_length - 1) def set_active_local_action_index(self, value: int): - """ - setter of active_local_action_index - sets a new local index if possible - - Args: - value (int): index to set - """ - ActRec_pref = get_preferences(bpy.context) + # Using context from argument is safer if available, but we'll use bpy.context for now + ActRec_pref = self # Inside the class, self is the pref object if not ActRec_pref.local_record_macros: actions_length = len(self.local_actions) - value = value if value < actions_length else actions_length - 1 - self['active_local_action_index'] = value if value >= 0 else actions_length - 1 + safe_value = value if value < actions_length else actions_length - 1 + self.active_local_action_index_internal = safe_value if safe_value >= 0 else 0 active_local_action_index: IntProperty( name="Select", @@ -179,21 +160,14 @@ def set_active_local_action_index(self, value: int): local_record_macros: BoolProperty(name="Record Macros", default=False) def hide_show_local_in_texteditor(self, context: Context): - """ - update function of hide_local_text - gets called every time the value of the hide_local_text is changed - hides/show the local action as text files in the texteditor of Blender - - Args: - context (Context): unused - """ if self.hide_local_text: for text in bpy.data.texts: - if text.lines[0].body.strip().startswith("###ActRec_pref###"): + if text.lines and text.lines[0].body.strip().startswith("###ActRec_pref###"): bpy.data.texts.remove(text) else: for action in self.local_actions: functions.local_action_to_text(action) + hide_local_text: BoolProperty( name="Hide Local Action in Texteditor", description="Hide the Local Action in the Texteditor", @@ -211,8 +185,7 @@ def hide_show_local_in_texteditor(self, context: Context): multiline_support_installing: BoolProperty(name="INTERNAL", default=False) multiline_support_dont_ask: BoolProperty( name="Don't Ask Again", - description="""Turns off the request for multiline support. -Can also be installed under Preferences > Add-ons > Action Recorder > Settings""", + description="Turns off the request for multiline support.", default=False ) @@ -237,31 +210,20 @@ def hide_show_local_in_texteditor(self, context: Context): # categories def get_storage_path(self) -> str: - """ - getter of storage_path - fallback to relative path of the addon if folder doesn't exists - - Returns: - str: path of the storage file - """ - origin_path = self.get('storage_path', 'Fallback') + origin_path = self.storage_path_internal + if origin_path == "": + origin_path = os.path.join(self.addon_directory, "Storage.json") + self.storage_path_internal = origin_path + if os.path.exists(origin_path): - return self['storage_path'] + return origin_path else: path = os.path.join(self.addon_directory, "Storage.json") - if origin_path != 'Fallback': - logger.error("ActRec ERROR: Storage Path \"%s\" don't exist, fallback to %s" % (origin_path, path)) - self['storage_path'] = path + self.storage_path_internal = path return path def set_storage_path(self, origin_path: str) -> None: - """ - setter of storage_path - - Args: - origin_path (str): path of the new storage file - """ - self['storage_path'] = origin_path + self.storage_path_internal = origin_path if os.path.exists(origin_path) and os.path.isfile(origin_path): return os.makedirs(os.path.dirname(origin_path), exist_ok=True) @@ -278,29 +240,25 @@ def set_storage_path(self, origin_path: str) -> None: categories: CollectionProperty(type=properties.AR_category) + # Added a stable storage for the selected category ID + selected_category_id: StringProperty(default="") + def get_selected_category(self) -> str: - """ - getter of selected_category - - Returns: - str: returns the id (uuid hex format) of the selected category - """ - return self.get("categories.selected_id", '') - selected_category: StringProperty(get=get_selected_category, default='') + return self.selected_category_id + + def set_selected_category(self, value: str): + self.selected_category_id = value + + selected_category: StringProperty(get=get_selected_category, set=set_selected_category, default='') show_all_categories: BoolProperty(name="Show All Categories", default=False) def draw(self, context: Context) -> None: - """ - draws the addon preferences - - Args: - context (Context): active blender context - """ - ActRec_pref = get_preferences(context) + ActRec_pref = self layout = self.layout col = layout.column() row = col.row(align=True) row.prop(ActRec_pref, 'preference_tab', expand=True) + if ActRec_pref.preference_tab == 'update': col.operator('wm.url_open', text="Release Notes").url = config.release_notes_url row = col.row() @@ -315,22 +273,15 @@ def draw(self, context: Context) -> None: col.label(text="A new Version is available (%s)" % ActRec_pref.version) else: col.label(text="You are using the latest Version (%s)" % ActRec_pref.version) + elif ActRec_pref.preference_tab == 'path': col.label(text='Action Storage Folder') row = col.row() - ops = row.operator( - "ar.preferences_directory_selector", - text="Select Action Button's Storage Folder", - icon='FILEBROWSER' - ) + ops = row.operator("ar.preferences_directory_selector", text="Select Action Button's Storage Folder", icon='FILEBROWSER') ops.preference_name = "storage_path" ops.path_extension = "Storage.json" - ops = row.operator( - "ar.preferences_recover_directory", - text="Recover Default Folder", - icon='FOLDER_REDIRECT' - ) + ops = row.operator("ar.preferences_recover_directory", text="Recover Default Folder", icon='FOLDER_REDIRECT') ops.preference_name = "storage_path" ops.path_extension = "Storage.json" row.operator('ar.preferences_open_explorer', text="", icon='FILEBROWSER').path = self.storage_path @@ -340,30 +291,20 @@ def draw(self, context: Context) -> None: box_row.label(text=self.storage_path) op = box_row.operator('ar.copy_text', text="", icon="COPYDOWN") op.text = self.storage_path + col.separator(factor=1.5) row = col.row().split(factor=0.5) row.label(text="Icon Storage Folder") row2 = row.row(align=True).split(factor=0.65, align=True) - row2.operator( - "ar.add_custom_icon", - text="Add Custom Icon", - icon='PLUS' - ) + row2.operator("ar.add_custom_icon", text="Add Custom Icon", icon='PLUS') row2.operator("ar.delete_custom_icon", text="Delete", icon='TRASH') + row = col.row() - ops = row.operator( - "ar.preferences_directory_selector", - text="Select Icon Storage Folder", - icon='FILEBROWSER' - ) + ops = row.operator("ar.preferences_directory_selector", text="Select Icon Storage Folder", icon='FILEBROWSER') ops.preference_name = "icon_path" ops.path_extension = "" - ops = row.operator( - "ar.preferences_recover_directory", - text="Recover Default Folder", - icon='FOLDER_REDIRECT' - ) + ops = row.operator("ar.preferences_recover_directory", text="Recover Default Folder", icon='FOLDER_REDIRECT') ops.preference_name = "icon_path" ops.path_extension = "Icons" row.operator('ar.preferences_open_explorer', text="", icon='FILEBROWSER').path = self.icon_path @@ -373,10 +314,12 @@ def draw(self, context: Context) -> None: box_row.label(text=self.icon_path) op = box_row.operator('ar.copy_text', text="", icon="COPYDOWN") op.text = self.icon_path + col.separator(factor=1.5) row2 = col.row(align=True).split(factor=0.7, align=True) row2.operator('ar.preferences_open_explorer', text="Open Log").path = log_sys.path row2.prop(self, 'log_amount') + elif ActRec_pref.preference_tab == 'keymap': col2 = col.column() kc = bpy.context.window_manager.keyconfigs.user @@ -393,6 +336,7 @@ def draw(self, context: Context) -> None: ) for kmi in [*ar_keymaps, *functions.get_all_action_keymaps(km)]: rna_keymap_ui.draw_kmi(kc.keymaps, kc, km, kmi, col2, 0) + elif ActRec_pref.preference_tab == 'settings': row = col.row() row.prop(self, 'auto_update') @@ -413,22 +357,23 @@ def draw(self, context: Context) -> None: row.operator('wm.url_open', text="Bug Report", icon='URL').url = config.bug_report_url # endregion - classes = [ AR_preferences ] # region Registration - def register(): for cls in classes: bpy.utils.register_class(cls) - def unregister(): - ActRec_pref = functions.get_preferences(bpy.context) - log.update_log_amount_in_config(ActRec_pref.log_amount) + # Attempt to get pref safely; if this fails, we skip log config update + try: + ActRec_pref = get_preferences(bpy.context) + log.update_log_amount_in_config(ActRec_pref.log_amount) + except: + pass for cls in classes: bpy.utils.unregister_class(cls) -# endregion +# endregion \ No newline at end of file diff --git a/ActRec/actrec/properties/categories.py b/ActRec/actrec/properties/categories.py index 3f0db8c..e5a494c 100644 --- a/ActRec/actrec/properties/categories.py +++ b/ActRec/actrec/properties/categories.py @@ -61,17 +61,13 @@ def get_selected(self) -> bool: return self.get("selected", False) def set_selected(self, value: bool): - """ - set the category as active, False will not change anything - - Args: - value (bool): state of category - """ ActRec_pref = get_preferences(bpy.context) - selected_id = ActRec_pref.get("categories.selected_id", "") - # implementation similar to a UIList (only one selection of all can be active) + # CHANGED: Use the new property we created in preferences.py + selected_id = ActRec_pref.selected_category_id + if value: - ActRec_pref["categories.selected_id"] = self.id + # CHANGED: Use the new property instead of ["categories.selected_id"] + ActRec_pref.selected_category_id = self.id self['selected'] = value category = ActRec_pref.categories.get(selected_id, None) if category: @@ -80,9 +76,8 @@ def set_selected(self, value: bool): self['selected'] = value label: StringProperty() - selected: BoolProperty(description='Select this Category', - name='Select', get=get_selected, set=set_selected) - actions: CollectionProperty(type=AR_category_actions) + selected: BoolProperty(description='Select this Category', name='Select', get=get_selected, set=set_selected) + actions: CollectionProperty(type=AR_category_actions) areas: CollectionProperty(type=AR_category_areas) # endregion diff --git a/ActRec/actrec/properties/globals.py b/ActRec/actrec/properties/globals.py index 7ad47a7..4ffe590 100644 --- a/ActRec/actrec/properties/globals.py +++ b/ActRec/actrec/properties/globals.py @@ -12,49 +12,54 @@ # region PropertyGroups - class AR_global_actions(shared.AR_action, PropertyGroup): def get_selected(self) -> bool: """ default Blender property getter - - Returns: - bool: selected state of action """ return self.get("selected", False) def set_selected(self, value: bool) -> None: """ - set selected macro or if ctrl is pressed multiple macros can be selected - if ctrl is not pressed all selected macros get deselected except the new selected. - - Args: - value (bool): state of selection + Updated for Blender 5.0 compatibility. + Uses a comma-separated string stored in preferences to track multi-selection. """ ActRec_pref = get_preferences(bpy.context) - selected_ids = list(ActRec_pref.get("global_actions.selected_ids", [])) + + # BLENDER 5.0 FIX: Retrieve string and convert to list + raw_ids = ActRec_pref.global_selected_ids_internal + selected_ids = raw_ids.split(",") if raw_ids else [] + # implementation similar to a UIList (only one selection of all can be active), # with extra multi selection by pressing ctrl - value |= (len(selected_ids) > 1) # used as bool or + value |= (len(selected_ids) > 1) if not value: self['selected'] = False + # Remove self from the string if it exists + if self.id in selected_ids: + selected_ids.remove(self.id) + ActRec_pref.global_selected_ids_internal = ",".join(selected_ids) return # uses check_ctrl operator to check for ctrl event ctrl_value = bpy.ops.ar.check_ctrl('INVOKE_DEFAULT') + # {'CANCELLED'} == ctrl is not pressed if selected_ids and ctrl_value == {'CANCELLED'}: - ActRec_pref["global_actions.selected_ids"] = [] + # Deselect others for selected_id in selected_ids: action = ActRec_pref.global_actions.get(selected_id, None) - if action is None: - continue - action.selected = (not bool(action)) + if action: + action['selected'] = False selected_ids.clear() - selected_ids.append(self.id) - ActRec_pref["global_actions.selected_ids"] = selected_ids - self['selected'] = value + + if self.id not in selected_ids: + selected_ids.append(self.id) + + # BLENDER 5.0 FIX: Save back as string + ActRec_pref.global_selected_ids_internal = ",".join(selected_ids) + self['selected'] = True selected: BoolProperty( default=False, @@ -67,22 +72,9 @@ def set_selected(self, value: bool) -> None: class AR_global_import_action(PropertyGroup): def get_use(self) -> bool: - """ - get state whether the action will be used to import - with extra check if the category of this action is also selected for import - - Returns: - bool: action import state - """ return self.get('use', True) and self.get('category.use', True) def set_use(self, value: bool) -> None: - """ - set state whether the action will be used to import - - Args: - value (bool): action import state - """ if self.get('category.use', True): self['use'] = value @@ -100,23 +92,10 @@ def set_use(self, value: bool) -> None: class AR_global_import_category(PropertyGroup): def get_use(self) -> bool: - """ - get state whether the category will be used to import - - Returns: - bool: category import state - """ return self.get("use", True) def set_use(self, value: bool) -> None: - """ - set state whether the category will be used to import - - Args: - value (bool): category import state - """ self['use'] = value - # needed for the action to check if there category is imported for action in self.actions: action['category.use'] = value @@ -135,22 +114,9 @@ def set_use(self, value: bool) -> None: class AR_global_export_action(shared.Id_based, PropertyGroup): def get_use(self) -> bool: - """ - get state whether the action will be used to export - with extra check if the category of this action is also selected for export or export_all is active - - Returns: - bool: action export state - """ return self.get("use", True) and self.get('category.use', True) or self.get('export_all', False) def set_use(self, value: bool) -> None: - """ - set state whether the action will be used to export - - Args: - value (bool): action export state - """ if self.get('category.use', True) and not self.get('export_all', False): self['use'] = value @@ -167,21 +133,9 @@ def set_use(self, value: bool) -> None: class AR_global_export_categories(shared.Id_based, PropertyGroup): def get_use(self) -> bool: - """ - get state whether the category will be used to export or export_all is active - - Returns: - bool: category export state - """ return self.get("use", True) or self.get("export_all", False) def set_use(self, value: bool) -> None: - """ - set state whether the category will be used to export - - Args: - value (bool): category export state - """ if self.get("export_all", False): return self['use'] = value @@ -200,7 +154,6 @@ def set_use(self, value: bool) -> None: ) # endregion - classes = [ AR_global_actions, AR_global_import_action, @@ -211,13 +164,11 @@ def set_use(self, value: bool) -> None: # region Registration - def register(): for cls in classes: bpy.utils.register_class(cls) - def unregister(): for cls in classes: bpy.utils.unregister_class(cls) -# endregion +# endregion \ No newline at end of file diff --git a/ActRec/actrec/properties/locals.py b/ActRec/actrec/properties/locals.py index a5f5708..00b7053 100644 --- a/ActRec/actrec/properties/locals.py +++ b/ActRec/actrec/properties/locals.py @@ -15,16 +15,12 @@ class AR_local_actions(shared.AR_action, PropertyGroup): def get_active_macro_index(self) -> int: - """ - get the active index of the local macro. - If the index is out of range the last index of all macros is passed on. - - Returns: - int: macro index - """ - value = self.get('active_macro_index', 0) - macros_length = len(self.macros) - return value if value < macros_length else macros_length - 1 + # Default to 0 instead of -1 if empty to satisfy UI layouts + value = self.get('active_macro_index', 0) + macros_length = len(self.macros) + if macros_length == 0: + return 0 + return value if value < macros_length else macros_length - 1 def set_active_macro_index(self, value: int) -> None: """