From 53ba993c2bc4afdfd7afb55bc2d62061dc268dde Mon Sep 17 00:00:00 2001 From: Che-Wei Wang Date: Fri, 13 Mar 2026 21:04:41 -0400 Subject: [PATCH] Fix toolbar tab discovery for newer Fusion 360 versions In newer Fusion 360 versions, toolbar tabs may not be ready during add-in startup, causing the add-in to fail silently. This adds: - Retry logic with delay to wait for tabs to become available - Multiple tab ID fallbacks (ToolsTab, UtilitiesTab) - Workspace-based tab lookup in addition to allToolbarTabs - Null checks in stop() to prevent errors during cleanup - Fix relative import references for thomasa88lib modules Co-Authored-By: Claude Opus 4.6 (1M context) --- AnyShortcut.py | 165 ++++++++++++++++++++++++++++++++++--------------- 1 file changed, 116 insertions(+), 49 deletions(-) diff --git a/AnyShortcut.py b/AnyShortcut.py index 67b7b24..2c45bdc 100644 --- a/AnyShortcut.py +++ b/AnyShortcut.py @@ -36,6 +36,7 @@ FILE_DIR = os.path.dirname(os.path.realpath(__file__)) # Import relative path to avoid namespace pollution +from . import thomasa88lib from .thomasa88lib import utils from .thomasa88lib import events from .thomasa88lib import manifest @@ -44,11 +45,11 @@ # Force modules to be fresh during development import importlib -importlib.reload(thomasa88lib.utils) -importlib.reload(thomasa88lib.events) -importlib.reload(thomasa88lib.manifest) -importlib.reload(thomasa88lib.error) -importlib.reload(thomasa88lib.timeline) +importlib.reload(utils) +importlib.reload(events) +importlib.reload(manifest) +importlib.reload(error) +importlib.reload(timeline) ENABLE_CMD_DEF_ID = 'thomasa88_anyShortcutList' PANEL_ID = 'thomasa88_anyShortcutPanel' @@ -411,62 +412,128 @@ def create(cmd_def_id, text, tooltip, resource_folder, handler): create_view_orientation_handler(view)) view_dropdown.controls.addCommand(c) -def run(context): - global app_ - global ui_ +setup_retries_ = 0 +MAX_SETUP_RETRIES = 5 +TAB_IDS_TO_TRY = ['ToolsTab', 'UtilitiesTab'] +WORKSPACE_IDS_TO_TRY = ['FusionSolidEnvironment'] + +def find_tools_tab(): + '''Try multiple methods and IDs to find the Tools/Utilities toolbar tab.''' + + # Method 1: Through workspace toolbar tabs (preferred for newer Fusion) + for ws_id in WORKSPACE_IDS_TO_TRY: + try: + workspace = ui_.workspaces.itemById(ws_id) + if workspace: + for tab_id in TAB_IDS_TO_TRY: + try: + tab = workspace.toolbarTabs.itemById(tab_id) + if tab: + return tab + except: + pass + except: + pass + + # Method 2: allToolbarTabs (works on older Fusion versions) + for tab_id in TAB_IDS_TO_TRY: + try: + tab = ui_.allToolbarTabs.itemById(tab_id) + if tab: + return tab + except: + pass + + return None + +def setup_ui(): global tracking_dropdown_ global builtin_dropdown_ global panel_ + global enable_cmd_def_ + global setup_retries_ + + tab = find_tools_tab() + + if not tab: + setup_retries_ += 1 + if setup_retries_ <= MAX_SETUP_RETRIES: + events_manager_.delay(setup_ui, secs=2) + return + else: + # Last resort: dump available tab info for debugging + debug_info = '' + try: + for ws_id in WORKSPACE_IDS_TO_TRY: + workspace = ui_.workspaces.itemById(ws_id) + if workspace: + tabs = workspace.toolbarTabs + for i in range(tabs.count): + t = tabs.item(i) + debug_info += f'\n {t.id}: {t.name}' + except: + debug_info = '\n (could not enumerate tabs)' + ui_.messageBox(f'{NAME}: Could not find the Tools/Utilities tab.\n' + f'Available tabs:{debug_info}\n\n' + f'Please report this to help fix the add-in.') + return + + panel_ = tab.toolbarPanels.itemById(PANEL_ID) + if panel_: + panel_.deleteMe() + panel_ = tab.toolbarPanels.add(PANEL_ID, f'{NAME}') + add_builtin_dropdown(panel_) + + tracking_dropdown_ = panel_.controls.itemById(TRACKING_DROPDOWN_ID) + if tracking_dropdown_: + tracking_dropdown_.deleteMe() + + tracking_dropdown_ = panel_.controls.addDropDown(f'Command Recorder', + './resources/tracker', + TRACKING_DROPDOWN_ID) + + enable_cmd_def_ = ui_.commandDefinitions.itemById(ENABLE_CMD_DEF_ID) + + if enable_cmd_def_: + enable_cmd_def_.deleteMe() + + # Cannot get checkbox to play nicely (won't update without collapsing + # the menu and the default checkbox icon is not showing...). + # See checkbox-test branch. + enable_cmd_def_ = ui_.commandDefinitions.addButtonDefinition( + ENABLE_CMD_DEF_ID, + f'Loading...', + '') + update_enable_text() + events_manager_.add_handler(event=enable_cmd_def_.commandCreated, + callback=enable_cmd_def__created_handler) + + enable_control = tracking_dropdown_.controls.addCommand(enable_cmd_def_) + enable_control.isPromoted = True + enable_control.isPromotedByDefault = True + tracking_dropdown_.controls.addSeparator() + +def run(context): + global app_ + global ui_ with error_catcher_: app_ = adsk.core.Application.get() ui_ = app_.userInterface - # Add the command to the tab. - tab = ui_.allToolbarTabs.itemById('ToolsTab') - - panel_ = tab.toolbarPanels.itemById(PANEL_ID) - if panel_: - panel_.deleteMe() - panel_ = tab.toolbarPanels.add(PANEL_ID, f'{NAME}') - add_builtin_dropdown(panel_) - - tracking_dropdown_ = panel_.controls.itemById(TRACKING_DROPDOWN_ID) - if tracking_dropdown_: - tracking_dropdown_.deleteMe() - - tracking_dropdown_ = panel_.controls.addDropDown(f'Command Recorder', - './resources/tracker', - TRACKING_DROPDOWN_ID) - - global enable_cmd_def_ - enable_cmd_def_ = ui_.commandDefinitions.itemById(ENABLE_CMD_DEF_ID) - - if enable_cmd_def_: - enable_cmd_def_.deleteMe() - - # Cannot get checkbox to play nicely (won't update without collapsing - # the menu and the default checkbox icon is not showing...). - # See checkbox-test branch. - enable_cmd_def_ = ui_.commandDefinitions.addButtonDefinition( - ENABLE_CMD_DEF_ID, - f'Loading...', - '') - update_enable_text() - events_manager_.add_handler(event=enable_cmd_def_.commandCreated, - callback=enable_cmd_def__created_handler) - - enable_control = tracking_dropdown_.controls.addCommand(enable_cmd_def_) - enable_control.isPromoted = True - enable_control.isPromotedByDefault = True - tracking_dropdown_.controls.addSeparator() + # UI toolbar tabs may not be ready during startup in newer Fusion versions. + # Try immediately, then retry with delay if needed. + setup_ui() def stop(context): with error_catcher_: events_manager_.clean_up() - tracking_dropdown_.deleteMe() - builtin_dropdown_.deleteMe() - panel_.deleteMe() + if tracking_dropdown_: + tracking_dropdown_.deleteMe() + if builtin_dropdown_: + builtin_dropdown_.deleteMe() + if panel_: + panel_.deleteMe() # Need to delete children?