diff --git a/src/DynamoCoreWpf/Extensions/ViewLoadedParams.cs b/src/DynamoCoreWpf/Extensions/ViewLoadedParams.cs index 3acd33c21b5..7155ebfbf93 100644 --- a/src/DynamoCoreWpf/Extensions/ViewLoadedParams.cs +++ b/src/DynamoCoreWpf/Extensions/ViewLoadedParams.cs @@ -86,6 +86,15 @@ internal ViewLoadedParams(DynamoView dynamoV, DynamoViewModel dynamoVM) : DynamoSelection.Instance.Selection.CollectionChanged += OnSelectionCollectionChanged; } + /// + /// Indicates whether Autodesk Identity (IDSDK) is currently initialized. View extensions + /// that depend on Autodesk Identity (e.g. sign-in, Autodesk Assistant) can use this to keep + /// their own UI (buttons, panels) in sync with Dynamo's IDSDK gating, without duplicating + /// the native initialization check or triggering the warning dialog Dynamo shows on its own + /// sign-in path. + /// + public bool IsIDSDKInitialized => dynamoViewModel.IsIDSDKInitialized(showWarning: false); + /// /// Adds a menu item to the extensions menu /// Items will be ordered alphabetically @@ -114,16 +123,19 @@ public void AddExtensionMenuItem(MenuItem menuItem) /// public void AddToExtensionsSideBar(IViewExtension viewExtension, ContentControl contentControl) { - bool added = dynamoView.AddOrFocusExtensionControl(viewExtension, contentControl); + var result = dynamoView.AddOrFocusExtensionControl(viewExtension, contentControl); - if (added) + switch (result) { - dynamoViewModel.Model.Logger.Log($"{viewExtension.Name} : {Wpf.Properties.Resources.ExtensionAdded}"); - } - else - { - dynamoViewModel.Model.Logger.Log($"{viewExtension.Name} : {Wpf.Properties.Resources.ExtensionAlreadyPresent}"); - + case DynamoView.ExtensionControlResult.Added: + dynamoViewModel.Model.Logger.Log($"{viewExtension.Name} : {Wpf.Properties.Resources.ExtensionAdded}"); + break; + case DynamoView.ExtensionControlResult.AlreadyPresent: + dynamoViewModel.Model.Logger.Log($"{viewExtension.Name} : {Wpf.Properties.Resources.ExtensionAlreadyPresent}"); + break; + case DynamoView.ExtensionControlResult.Blocked: + // Already logged by DisableExtensionWhenNoNetworkMode/DisableExtensionWhenIDSDKNotInitialized. + break; } } diff --git a/src/DynamoCoreWpf/PublicAPI.Unshipped.txt b/src/DynamoCoreWpf/PublicAPI.Unshipped.txt index f3201d6cf42..90409bffdaf 100644 --- a/src/DynamoCoreWpf/PublicAPI.Unshipped.txt +++ b/src/DynamoCoreWpf/PublicAPI.Unshipped.txt @@ -1,3 +1,4 @@ +Dynamo.Wpf.Extensions.ViewLoadedParams.IsIDSDKInitialized.get -> bool Dynamo.Controls.NetworkStatusIconConverter Dynamo.Controls.NetworkStatusIconConverter.Convert(object value, System.Type targetType, object parameter, System.Globalization.CultureInfo culture) -> object Dynamo.Controls.NetworkStatusIconConverter.ConvertBack(object value, System.Type targetType, object parameter, System.Globalization.CultureInfo culture) -> object diff --git a/src/DynamoCoreWpf/Views/Core/DynamoView.xaml.cs b/src/DynamoCoreWpf/Views/Core/DynamoView.xaml.cs index 6f228b05de3..49a7e8df0bd 100644 --- a/src/DynamoCoreWpf/Views/Core/DynamoView.xaml.cs +++ b/src/DynamoCoreWpf/Views/Core/DynamoView.xaml.cs @@ -528,15 +528,29 @@ private void OnPythonEngineUpgradeToastRequested(string msg, bool stayOpen, stri })); } + /// + /// Result of , distinguishing a genuinely blocked + /// extension (NoNetworkMode/IDSDK) from one that was simply already open. + /// + internal enum ExtensionControlResult + { + Added, + AlreadyPresent, + Blocked + } + /// /// Adds an extension control or if it already exists it makes sure it is focused. /// The control may be added as a window or a tab in the extension bar depending on settings. /// /// View extension adding the content /// Control being added - /// True if the control was added, false if it already existed - internal bool AddOrFocusExtensionControl(IViewExtension viewExtension, UIElement content) + internal ExtensionControlResult AddOrFocusExtensionControl(IViewExtension viewExtension, UIElement content) { + if (DisableExtensionWhenNoNetworkMode(viewExtension.UniqueId, viewExtension.Name, "opened") || + DisableExtensionWhenIDSDKNotInitialized(viewExtension.UniqueId, viewExtension.Name, "opened")) + return ExtensionControlResult.Blocked; + var window = ExtensionWindows.ContainsKey(viewExtension.Name) ? ExtensionWindows[viewExtension.Name] : null; var tab = FindExtensionTab(viewExtension); var addExtensionControl = window == null && tab == null; @@ -589,7 +603,7 @@ internal bool AddOrFocusExtensionControl(IViewExtension viewExtension, UIElement } } - return addExtensionControl; + return addExtensionControl ? ExtensionControlResult.Added : ExtensionControlResult.AlreadyPresent; } private ExtensionWindow AddExtensionWindow(IViewExtension viewExtension, UIElement content, WindowSettings windowSettings) @@ -1414,8 +1428,18 @@ private void DynamoLoadedViewExtensionHandler(ViewLoadedParams loadedParams, IEn continue; } + // The extension is still allowed to run Loaded() below, so any UI it registers + // (menu items, toolbar buttons) stays visible; only the automatic re-open of a + // previously-open panel is skipped. Extensions that depend on IDSDK are expected to + // gate their own entry points via ViewLoadedParams.IsIDSDKInitialized. + var idsdkNotInitialized = DisableExtensionWhenIDSDKNotInitialized(ext.UniqueId, ext.Name, "re-opened"); + ext.Loaded(loadedParams); - ReOpenSavedExtensionOnDynamoStartup(ext); + + if (!idsdkNotInitialized) + { + ReOpenSavedExtensionOnDynamoStartup(ext); + } } catch (Exception exc) { @@ -3414,5 +3438,24 @@ internal bool DisableExtensionWhenNoNetworkMode(string extensionId, string exten return false; } + + /// + /// Prevents the Autodesk Assistant and MCP View extensions from being loaded or opened when + /// Autodesk Identity (IDSDK) is not initialized, so the native "Create Assistant" call chain + /// that requires Identity never fires and cannot cascade into a series of error dialogs. + /// + internal bool DisableExtensionWhenIDSDKNotInitialized(string extensionId, string extensionName, string action) + { + if ((string.Equals(extensionId, AutodeskAssistantExtensionId, StringComparison.OrdinalIgnoreCase) || + string.Equals(extensionId, McpViewExtensionId, StringComparison.OrdinalIgnoreCase)) && + !dynamoViewModel.IsIDSDKInitialized(showWarning: false)) + { + Log($"Package/Extension {extensionName} not {action} because Autodesk Identity (IDSDK) is not initialized"); + + return true; + } + + return false; + } } } diff --git a/test/DynamoCoreWpf2Tests/ViewExtensions/ViewExtensionTests.cs b/test/DynamoCoreWpf2Tests/ViewExtensions/ViewExtensionTests.cs index 671b8ede44a..b2adbbc6ec7 100644 --- a/test/DynamoCoreWpf2Tests/ViewExtensions/ViewExtensionTests.cs +++ b/test/DynamoCoreWpf2Tests/ViewExtensions/ViewExtensionTests.cs @@ -4,6 +4,7 @@ using System.Reflection; using System.Windows; using System.Windows.Controls; +using Dynamo.Controls; using Dynamo.Engine; using Dynamo.Graph.Workspaces; using Dynamo.Models; @@ -410,7 +411,7 @@ public void LaunchTourClosesSidePanelViewExtensions() var dockedExtension = new GuidedTourSidePanelTestViewExtension(); var added = View.AddOrFocusExtensionControl(dockedExtension, new UserControl()); - Assert.IsTrue(added); + Assert.AreEqual(DynamoView.ExtensionControlResult.Added, added); var hasAddedExtensionTab = ViewModel.SideBarTabItems.OfType().Any(tab => tab.Tag is IViewExtension extension && diff --git a/test/DynamoCoreWpfTests/DynamoViewNoNetworkModeTests.cs b/test/DynamoCoreWpfTests/DynamoViewNoNetworkModeTests.cs index 8ee1ff19d87..8f44cd63145 100644 --- a/test/DynamoCoreWpfTests/DynamoViewNoNetworkModeTests.cs +++ b/test/DynamoCoreWpfTests/DynamoViewNoNetworkModeTests.cs @@ -1,6 +1,9 @@ +using System; +using System.Linq; using Dynamo.Controls; using Dynamo.Models; using Dynamo.ViewModels; +using Dynamo.Wpf.Extensions; using NUnit.Framework; using TestServices; @@ -100,5 +103,38 @@ public void UnrecognizedExtensionIsNotDisabledWhenNoNetworkModeIsEnabled() Assert.IsFalse(shouldDisable); } + + [Test] + public void AssistantTabCannotBeAddedViaSideBarWhenNoNetworkModeIsEnabled() + { + // Simulates the code path where an extension calls AddToExtensionsSideBar() after + // initial load (e.g. from IExtensionStorageAccess.WorkspaceOpened), which bypasses + // the DisableExtensionWhenNoNetworkMode guard in DynamoLoadedViewExtensionHandler. + // AddOrFocusExtensionControl must block these late attempts in NoNetworkMode. + // + // This is specifically exercising NoNetworkMode, not the IDSDK gate: the fixture's + // DynamoModel has no AuthProvider configured, so AuthenticationManager.IsIDSDKInitialized() + // is unconditionally true (see AuthenticationManager.IsIDSDKInitialized) and can't be + // the reason AddOrFocusExtensionControl returns Blocked here. + var stubExtension = new StubViewExtension(DynamoView.AutodeskAssistantExtensionId); + var result = View.AddOrFocusExtensionControl(stubExtension, null); + + Assert.AreEqual(DynamoView.ExtensionControlResult.Blocked, result); + Assert.IsFalse(ViewModel.SideBarTabItems + .OfType() + .Any(t => string.Equals(t.Uid, DynamoView.AutodeskAssistantExtensionId, + StringComparison.OrdinalIgnoreCase))); + } + + private class StubViewExtension : IViewExtension + { + public StubViewExtension(string uniqueId) { UniqueId = uniqueId; } + public string UniqueId { get; } + public string Name => "Stub"; + public void Startup(ViewStartupParams p) { } + public void Loaded(ViewLoadedParams p) { } + public void Shutdown() { } + public void Dispose() { } + } } }