Skip to content

Commit 462d01a

Browse files
authored
feat(Navisworks): CNX-1043 Enable shared ribbon for v2 and v3 Navisworks connectors (#504)
* Add Speckle v2 and v3 launch buttons to the Navisworks ribbon - Added `LaunchSpeckleConnector` class with constants for command and plugin names - Updated `NavisworksRibbon.name` file to include display names for Speckle v2 and v3 - Updated `NavisworksRibbon.xaml` file to include ribbon panels and buttons for Speckle v2 and v3 - Updated `NavisworksRibbon.xaml.cs` file to include commands for launching Speckle v2 and v3 connectors - Added resources for the icons of the new buttons * Delete AppUtils.cs and Commands.cs, add Utilities.cs - Delete AppUtils.cs and Commands.cs (Replaced by v2 and v3 Tools) - Add Utilities.cs for plugin utilities functionality * Update Speckle Connector for Navisworks to use the new SpeckleV3Tool plugin and developer ID. Also, initialize the services with the correct version of Navisworks. * Update RibbonHandler class in Navisworks connector plugin: - Refactor the CanExecuteCommand method to handle different commandIds. - Add a static constructor to subscribe to the PluginRecordsChanged event. - Implement OnPluginRecordsChanged method to reset the cached state of V2 plugin availability. - Modify ExecuteCommand method to load and activate the appropriate plugin based on the commandId. - Add a new IsValidVersion method to check if the current version of Navisworks is compatible with the plugin. * Update ribbon handling logic in NavisworksRibbon.xaml.cs - Find the v2 plugin and update the availability state - Hide the v2 ribbon tab if it exists * Fix ribbon handling for Speckle v3 and v2 tools Refactor the code in `RibbonHandler` to improve the handling of commands for Speckle v3 and v2 tools. The changes include: - Extracting a new method `HandleCommand` to handle the loading and activation of plugins - Removing duplicated code by calling `HandleCommand` for both Speckle v3 and v2 tools - Adding null checks when finding plugin records - Returning early if plugin loading should be skipped * XMLDOC comments added - Added two summary comments to describe the purpose of the methods `CanExecuteCommand()` and `ExecuteCommand()`. * Add Speckle V2 and V3 tools to Navisworks plugin - Added `SpeckleV2Tool.cs` file with constants for Speckle V2 tool - Added `SpeckleV3Tool.cs` file with constants for Speckle V3 tool * Refactor Utilities.cs for Navisworks plugin - Add conditional compilation for DEBUG mode * Remove debug code and comments in Utilities.cs - Remove debug code that prints available plugins to the Debug output window - Remove unused using statement for System.Text * Refactor plugin activation logic in NavisworksRibbon.xaml.cs and Utilities.cs - Simplify ActivatePluginPane method in Utilities.cs - Remove unused parameter 'command' from ActivatePluginPane method * Update Speckle branding and UI elements - Changed display names for Speckle versions to reflect beta status. - Updated XAML to simplify ribbon panel structure. - Adjusted constants in the tool class for consistency with new naming. * Fix formatting and improve error messages - Added missing newlines for better readability in code. - Updated error message to enhance clarity for users. - Cleaned up XML formatting in project files for consistency. - Ensured all package references are properly formatted. * Add missing namespace for Navisworks integration - Included the HostApp namespace to improve functionality. - Ensures better access to required classes and methods. * Fix message box formatting in Navisworks plugin Updated the message box string concatenation for better readability.
1 parent 8ca43a1 commit 462d01a

18 files changed

+339
-270
lines changed

Connectors/Navisworks/Speckle.Connectors.NavisworksShared/Operations/Send/Settings/ToSpeckleSettingsManagerNavisworks.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ public OriginMode GetOriginMode(SenderModelCard modelCard)
7373
EvictCacheForModelCard(modelCard);
7474
}
7575
}
76+
7677
_originModeCache[modelCard.ModelCardId.NotNull()] = origin;
7778
return origin;
7879
}

Connectors/Navisworks/Speckle.Connectors.NavisworksShared/Plugin/Commands.cs

Lines changed: 0 additions & 7 deletions
This file was deleted.

Connectors/Navisworks/Speckle.Connectors.NavisworksShared/Plugin/DockableConnectorPane.cs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using Microsoft.Extensions.DependencyInjection;
55
using Speckle.Connector.Navisworks.DependencyInjection;
66
using Speckle.Connector.Navisworks.HostApp;
7+
using Speckle.Connector.Navisworks.Plugin.Tools;
78
using Speckle.Connectors.Common;
89
using Speckle.Connectors.DUI;
910
using Speckle.Connectors.DUI.Eventing;
@@ -16,9 +17,9 @@ namespace Speckle.Connector.Navisworks.Plugin;
1617
[
1718
NAV.Plugins.DockPanePlugin(450, 750, FixedSize = false, AutoScroll = true, MinimumHeight = 410, MinimumWidth = 250),
1819
NAV.Plugins.Plugin(
19-
LaunchSpeckleConnector.PLUGIN,
20-
"Speckle",
21-
DisplayName = "Speckle (Beta)",
20+
SpeckleV3Tool.PLUGIN,
21+
SpeckleV3Tool.DEVELOPER_ID,
22+
DisplayName = SpeckleV3Tool.DISPLAY_NAME,
2223
Options = NAV.Plugins.PluginOptions.None,
2324
ToolTip = "Speckle Connector for Navisworks",
2425
ExtendedToolTip = "Next Gen Speckle Connector (Beta) for Navisworks"
@@ -39,7 +40,7 @@ public override Control CreateControlPane()
3940

4041
var services = new ServiceCollection();
4142

42-
services.Initialize(HostApplications.Navisworks, HostAppVersion.v2024);
43+
services.Initialize(HostApplications.Navisworks, SpeckleV3Tool.Version);
4344

4445
services.AddNavisworks();
4546
services.AddNavisworksConverter();

Connectors/Navisworks/Speckle.Connectors.NavisworksShared/Plugin/NavisworksRibbon.name

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@
22
# do NOT need translating.
33
$utf8
44

5-
# DisplayName=
6-
# SpeckleBeta
7-
8-
# Speckle.DisplayName=
9-
# SpeckleBeta
5+
DisplayName=Speckle
6+
Speckle.DisplayName=Speckle
7+
SpeckleV3.Title=Speckle (Beta)
8+
SpeckleV2.Title=Speckle
9+
Speckle_Launch.DisplayName=Speckle (Beta)
10+
Speckle_Launch_V2.DisplayName=Speckle

Connectors/Navisworks/Speckle.Connectors.NavisworksShared/Plugin/NavisworksRibbon.xaml

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,28 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
13
<RibbonControl
24
x:Uid="Speckle_Ribbon"
35
xmlns="clr-namespace:Autodesk.Windows;assembly=AdWindows"
4-
xmlns:gui="clr-namespace:Autodesk.Navisworks.Gui.Roamer.AIRLook;assembly=navisworks.gui.roamer"
5-
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
6+
xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
7+
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
8+
xmlns:gui="clr-namespace:Autodesk.Navisworks.Gui.Roamer.AIRLook;assembly=navisworks.gui.roamer">
69
<RibbonTab Id="Speckle" KeyTip="SP">
7-
<!-- SPECKLE -->
8-
<RibbonPanel x:Uid="RibbonPanel">
9-
<RibbonPanelSource Id="RibbonPanelSource" x:Uid="RibbonPanelSource" KeyTip="SP" Title="Speckle (Beta)">
10-
<gui:NWRibbonButton x:Uid="Button_Speckle_LaunchSpeckleConnector" IsVisible="False" Id="Speckle_Launch"
11-
Size="Large" KeyTip="S" ShowText="True" Orientation="Vertical" />
10+
<RibbonPanel x:Uid="Panel_Speckle">
11+
<RibbonPanelSource Id="SpeckleV3Source" x:Uid="PanelSource_Speckle_V3" Title="Speckle">
12+
<gui:NWRibbonButton
13+
x:Uid="Button_Speckle_V3"
14+
Id="Speckle_Launch"
15+
Size="Large"
16+
KeyTip="3"
17+
ShowText="True"
18+
Orientation="Vertical" />
19+
<gui:NWRibbonButton
20+
x:Uid="Button_Speckle_V2"
21+
Id="Speckle_Launch_V2"
22+
Size="Large"
23+
KeyTip="2"
24+
ShowText="True"
25+
Orientation="Vertical" />
1226
</RibbonPanelSource>
1327
</RibbonPanel>
1428
</RibbonTab>
Lines changed: 102 additions & 139 deletions
Original file line numberDiff line numberDiff line change
@@ -1,197 +1,160 @@
1-
using System.Diagnostics.CodeAnalysis;
21
using System.Windows.Forms;
3-
#if DEBUG
4-
using System.Text;
5-
#endif
2+
using Speckle.Connector.Navisworks.Plugin.Tools;
63

74
namespace Speckle.Connector.Navisworks.Plugin;
85

6+
/// <summary>
7+
/// Handles plugin state and ribbon management for the Speckle V3 and V2 connectors.
8+
/// </summary>
99
[
10-
NAV.Plugins.Plugin("SpeckleNavisworksNextGen", "Speckle", DisplayName = "Speckle (Beta)"),
11-
NAV.Plugins.Strings("NavisworksRibbon.name"),
10+
NAV.Plugins.Plugin(SpeckleV3Tool.PLUGIN_ID, SpeckleV3Tool.DEVELOPER_ID, DisplayName = SpeckleV3Tool.DISPLAY_NAME),
11+
NAV.Plugins.Strings(SpeckleV3Tool.RIBBON_STRINGS),
1212
NAV.Plugins.RibbonLayout("NavisworksRibbon.xaml"),
13-
NAV.Plugins.RibbonTab("Speckle", DisplayName = "Speckle", LoadForCanExecute = true),
13+
NAV.Plugins.RibbonTab(
14+
SpeckleV3Tool.RIBBON_TAB_ID,
15+
DisplayName = SpeckleV3Tool.RIBBON_TAB_DISPLAY_NAME,
16+
LoadForCanExecute = true
17+
),
1418
NAV.Plugins.Command(
15-
LaunchSpeckleConnector.COMMAND,
19+
SpeckleV3Tool.COMMAND,
1620
LoadForCanExecute = true,
17-
Icon = "Resources/s2logo16.png",
18-
LargeIcon = "Resources/s2logo32.png",
19-
ToolTip = "Next Gen Speckle Connector (Beta) for Navisworks",
20-
DisplayName = "Speckle (Beta)"
21+
Icon = "Resources/v3_logo16.png",
22+
LargeIcon = "Resources/v3_logo32.png",
23+
ToolTip = "Speckle Connector for Navisworks",
24+
DisplayName = "$Speckle_Launch.DisplayName"
2125
),
26+
NAV.Plugins.Command(
27+
SpeckleV2Tool.COMMAND,
28+
LoadForCanExecute = true,
29+
Icon = "Resources/v2_logo16.png",
30+
LargeIcon = "Resources/v2_logo32.png",
31+
ToolTip = "Legacy Speckle v2 Connector",
32+
DisplayName = "$Speckle_Launch_V2.DisplayName"
33+
)
2234
]
23-
[SuppressMessage(
24-
"design",
25-
"CA1812:Avoid uninstantiated internal classes",
26-
Justification = "Instantiated by Navisworks"
27-
)]
2835
internal sealed class RibbonHandler : NAV.Plugins.CommandHandlerPlugin
2936
{
30-
// ReSharper disable once CollectionNeverQueried.Local
31-
private static readonly Dictionary<NAV.Plugins.Plugin, bool> s_loadedPlugins = [];
37+
private static bool? s_isV2PluginAvailable; // Nullable to indicate uncached state.
38+
private static bool s_isV2RibbonHidden; // Tracks if the ribbon tab is already hidden.
3239

33-
/// <summary>
34-
/// Determines the state of a command in Navisworks.
35-
/// </summary>
36-
/// <param name="commandId">The ID of the command to check.</param>
37-
/// <returns>The state of the command.</returns>
38-
public override NAV.Plugins.CommandState CanExecuteCommand(string commandId) =>
39-
commandId == LaunchSpeckleConnector.COMMAND
40-
? new NAV.Plugins.CommandState(true)
41-
: new NAV.Plugins.CommandState(false);
40+
static RibbonHandler()
41+
{
42+
// Subscribe to the static PluginRecordsChanged event
43+
NAV.ApplicationParts.ApplicationPlugins.PluginRecordsChanged += OnPluginRecordsChanged;
44+
}
45+
46+
private static void OnPluginRecordsChanged(object sender, EventArgs e) => s_isV2PluginAvailable = null;
4247

4348
/// <summary>
44-
/// Loads a plugin in Navisworks.
49+
/// Determines whether a command can be executed and manages V2 plugin visibility.
4550
/// </summary>
46-
/// <param name="plugin">The name of the plugin to load.</param>
47-
/// <param name="notAutomatedCheck">Optional. Specifies whether to check if the application is automated. Default is true.</param>
48-
/// <param name="command">Optional. The command associated with the plugin. Default is an empty string.</param>
49-
private static void LoadPlugin(string plugin, bool notAutomatedCheck = true, string command = "")
51+
/// <param name="commandId">The command identifier to check.</param>
52+
/// <returns>A CommandState indicating whether the command can be executed.</returns>
53+
public override NAV.Plugins.CommandState CanExecuteCommand(string commandId)
5054
{
51-
if (ShouldSkipLoad(notAutomatedCheck))
55+
switch (commandId)
5256
{
53-
return;
54-
}
57+
case SpeckleV3Tool.COMMAND:
58+
return new NAV.Plugins.CommandState(true);
59+
case SpeckleV2Tool.COMMAND:
60+
{
61+
// Find the v2 plugin
62+
NAV.Plugins.PluginRecord? v2Plugin = PluginUtilities.FindV2Plugin();
63+
s_isV2PluginAvailable = v2Plugin != null;
5564

56-
if (ShouldSkipPluginLoad(plugin, command))
57-
{
58-
return;
59-
}
65+
// Pass the plugin to the method for managing ribbon visibility
66+
HideV2RibbonTab();
6067

61-
var pluginRecord = NavisworksApp.Plugins.FindPlugin(plugin + ".Speckle");
62-
if (pluginRecord is null)
63-
{
64-
return;
68+
return new NAV.Plugins.CommandState((bool)s_isV2PluginAvailable);
69+
}
70+
default:
71+
return new NAV.Plugins.CommandState(false);
6572
}
66-
67-
var loadedPlugin = pluginRecord.LoadedPlugin ?? pluginRecord.LoadPlugin();
68-
69-
ActivatePluginPane(pluginRecord, loadedPlugin, command);
7073
}
7174

72-
/// <summary>
73-
/// Checks whether the load should be skipped based on the notAutomatedCheck flag and application automation status.
74-
/// </summary>
75-
/// <param name="notAutomatedCheck">The flag indicating whether to check if the application is automated.</param>
76-
/// <returns>True if the load should be skipped, False otherwise.</returns>
77-
private static bool ShouldSkipLoad(bool notAutomatedCheck) => notAutomatedCheck && NavisworksApp.IsAutomated;
78-
79-
/// <summary>
80-
/// Checks whether the plugin load should be skipped based on the plugin and command values.
81-
/// </summary>
82-
/// <param name="plugin">The name of the plugin.</param>
83-
/// <param name="command">The command associated with the plugin.</param>
84-
/// <returns>True if the plugin load should be skipped, False otherwise.</returns>
85-
private static bool ShouldSkipPluginLoad(string plugin, string command) =>
86-
string.IsNullOrEmpty(plugin) || string.IsNullOrEmpty(command);
87-
88-
/// <summary>
89-
/// Activates the plugin's pane if it is of the right type.
90-
/// </summary>
91-
/// <param name="pluginRecord">The plugin record.</param>
92-
/// <param name="loadedPlugin">The loaded plugin instance.</param>
93-
/// <param name="command">The command associated with the plugin.</param>
94-
private static void ActivatePluginPane(NAV.Plugins.PluginRecord pluginRecord, object loadedPlugin, string command)
75+
private static void HideV2RibbonTab()
9576
{
96-
if (ShouldActivatePluginPane(pluginRecord))
77+
if (s_isV2RibbonHidden)
9778
{
98-
var dockPanePlugin = (NAV.Plugins.DockPanePlugin)loadedPlugin;
99-
dockPanePlugin.ActivatePane();
100-
101-
s_loadedPlugins[dockPanePlugin] = true;
79+
return; // Skip if already hidden.
10280
}
103-
else
81+
82+
var v2RibbonTab = Autodesk.Windows.ComponentManager.Ribbon.Tabs.FirstOrDefault(tab =>
83+
tab.Id == SpeckleV2Tool.RIBBON_TAB_ID + SpeckleV2Tool.PLUGIN_SUFFIX
84+
);
85+
86+
if (v2RibbonTab == null)
10487
{
105-
#if DEBUG
106-
ShowPluginInfoMessageBox();
107-
ShowPluginNotLoadedMessageBox(command);
108-
#endif
88+
return;
10989
}
90+
91+
Autodesk.Windows.ComponentManager.Ribbon.Tabs.Remove(v2RibbonTab);
92+
s_isV2RibbonHidden = true; // Mark as hidden to avoid redundant calls.
11093
}
11194

11295
/// <summary>
113-
/// Checks whether the plugin's pane should be activated based on the plugin record.
96+
/// Executes the specified command after validating the Navisworks version.
11497
/// </summary>
115-
/// <param name="pluginRecord">The plugin record.</param>
116-
/// <returns>True if the plugin's pane should be activated, False otherwise.</returns>
117-
private static bool ShouldActivatePluginPane(NAV.Plugins.PluginRecord pluginRecord) =>
118-
pluginRecord.IsLoaded && pluginRecord is NAV.Plugins.DockPanePluginRecord && pluginRecord.IsEnabled;
119-
98+
/// <param name="commandId">The command to execute.</param>
99+
/// <param name="parameters">Additional command parameters.</param>
100+
/// <returns>0 if successful, non-zero otherwise.</returns>
120101
public override int ExecuteCommand(string commandId, params string[] parameters)
121102
{
122-
// ReSharper disable once RedundantAssignment
123-
var buildVersion = string.Empty;
124-
125-
#if NAVIS2020
126-
buildVersion = "2020";
127-
#endif
128-
#if NAVIS2021
129-
buildVersion = "2021";
130-
#endif
131-
#if NAVIS2022
132-
buildVersion = "2022";
133-
#endif
134-
#if NAVIS2023
135-
buildVersion = "2023";
136-
#endif
137-
#if NAVIS2024
138-
buildVersion = "2024";
139-
#endif
140-
#if NAVIS2025
141-
buildVersion = "2025";
142-
#endif
143-
144-
// Version
145-
if (!NavisworksApp.Version.RuntimeProductName.Contains(buildVersion))
103+
if (!IsValidVersion())
146104
{
147-
MessageBox.Show(
148-
"This Add-In was built for Navisworks "
149-
+ buildVersion
150-
+ ", please contact jonathon@speckle.systems for assistance...",
151-
"Cannot Continue!",
152-
MessageBoxButtons.OK,
153-
MessageBoxIcon.Error
154-
);
155105
return 0;
156106
}
157107

158108
switch (commandId)
159109
{
160-
case LaunchSpeckleConnector.COMMAND:
161-
{
162-
LoadPlugin(LaunchSpeckleConnector.PLUGIN, command: commandId);
110+
case SpeckleV3Tool.COMMAND:
111+
HandleCommand(SpeckleV3Tool.PLUGIN, commandId);
163112
break;
164-
}
165113

114+
case SpeckleV2Tool.COMMAND:
115+
HandleCommand(SpeckleV2Tool.PLUGIN, $"{SpeckleV2Tool.PLUGIN}.{SpeckleV2Tool.DEVELOPER_ID}");
116+
break;
166117
default:
167118
{
168-
MessageBox.Show("You have clicked on an unexpected command with ID = '" + commandId + "'");
119+
MessageBox.Show($"You have clicked on an unexpected command with ID = '{commandId}'");
169120
break;
170121
}
171122
}
172123

173124
return 0;
174125
}
175126

176-
#if DEBUG
177-
/// <summary>
178-
/// Shows a message box displaying plugin information.
179-
/// </summary>
180-
private static void ShowPluginInfoMessageBox()
127+
private static void HandleCommand(string pluginId, string commandId)
181128
{
182-
var sb = new StringBuilder();
183-
foreach (var pr in NavisworksApp.Plugins.PluginRecords)
129+
if (PluginUtilities.ShouldSkipLoad(pluginId, commandId, true))
184130
{
185-
sb.AppendLine(pr.Name + ": " + pr.DisplayName + ", " + pr.Id);
131+
return;
186132
}
187133

188-
MessageBox.Show(sb.ToString());
134+
var pluginRecord = NavisworksApp.Plugins.FindPlugin(pluginId + SpeckleV3Tool.PLUGIN_SUFFIX);
135+
if (pluginRecord == null)
136+
{
137+
return;
138+
}
139+
140+
_ = pluginRecord.LoadedPlugin ?? pluginRecord.LoadPlugin();
141+
PluginUtilities.ActivatePluginPane(pluginRecord);
189142
}
190143

191-
/// <summary>
192-
/// Shows a message box indicating that the plugin was not loaded.
193-
/// </summary>
194-
/// <param name="command">The command associated with the plugin.</param>
195-
private static void ShowPluginNotLoadedMessageBox(string command) => MessageBox.Show(command + " Plugin not loaded.");
196-
#endif
144+
private static bool IsValidVersion()
145+
{
146+
if (NavisworksApp.Version.RuntimeProductName.Contains(SpeckleV3Tool.Version.ToString().Replace("v", "")))
147+
{
148+
return true;
149+
}
150+
151+
MessageBox.Show(
152+
$"This Add-In was built for Navisworks {SpeckleV3Tool.Version}, "
153+
+ $"please contact support@speckle.systems for assistance...",
154+
"Cannot Continue!",
155+
MessageBoxButtons.OK,
156+
MessageBoxIcon.Error
157+
);
158+
return false;
159+
}
197160
}

0 commit comments

Comments
 (0)