Skip to content

Commit 1d6da91

Browse files
romangolevjmcouffin
authored andcommitted
feat: implement recursive UI builder
1 parent 80f6ccf commit 1d6da91

File tree

7 files changed

+221
-131
lines changed

7 files changed

+221
-131
lines changed

dev/pyRevitLoader/pyRevitAssemblyBuilder/SessionManager/DummyExtension.cs

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

dev/pyRevitLoader/pyRevitAssemblyBuilder/SessionManager/ExtensionManagerService.cs

Lines changed: 48 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,9 @@ public class ExtensionManagerService : IExtensionManager
1515
private readonly IServiceProvider _serviceProvider;
1616
private readonly List<string> _extensionRoots;
1717

18-
private static readonly string[] SupportedBundleTypes = new[]
18+
private static readonly string[] BundleTypes = new[]
1919
{
20-
".pushbutton", ".pulldownbutton", ".splitbutton", ".stack", ".panel", ".tab"
20+
".tab", ".panel", ".stack", ".splitbutton", ".splitpushbutton", ".pulldown", ".smartbutton", ".pushbutton"
2121
};
2222

2323
public ExtensionManagerService(IServiceProvider serviceProvider)
@@ -41,9 +41,10 @@ public IEnumerable<IExtension> GetInstalledExtensions()
4141

4242
var extensionName = Path.GetFileNameWithoutExtension(dir);
4343
var metadata = LoadMetadata(dir);
44-
var commands = LoadCommands(dir);
45-
if (commands.Any())
46-
yield return new FileSystemExtension(extensionName, dir, commands, metadata);
44+
var children = LoadBundleComponents(dir);
45+
46+
if (children.Any())
47+
yield return new FileSystemExtension(extensionName, dir, children, metadata);
4748
}
4849
}
4950
}
@@ -63,40 +64,51 @@ private List<string> GetExtensionRoots()
6364
{
6465
var config = PyRevitConfig.Load(configPath);
6566
if (config.UserExtensions != null && config.UserExtensions.Count > 0)
66-
{
6767
roots.AddRange(config.UserExtensions);
68-
}
6968
}
7069

7170
return roots;
7271
}
7372

74-
private IEnumerable<ICommandComponent> LoadCommands(string extensionPath)
73+
private IEnumerable<ICommandComponent> LoadBundleComponents(string baseDir)
7574
{
76-
var cmds = new List<ICommandComponent>();
75+
var components = new List<ICommandComponent>();
7776

78-
foreach (var bundleDir in Directory.GetDirectories(extensionPath, "*.*", SearchOption.AllDirectories))
77+
foreach (var dir in Directory.GetDirectories(baseDir))
7978
{
80-
var bundleType = Path.GetExtension(bundleDir).ToLowerInvariant();
81-
if (!SupportedBundleTypes.Contains(bundleType))
82-
continue;
83-
84-
var scriptPath = Path.Combine(bundleDir, "script.py");
85-
if (!File.Exists(scriptPath))
79+
var type = Path.GetExtension(dir).ToLowerInvariant();
80+
if (!BundleTypes.Contains(type))
8681
continue;
8782

88-
var name = Path.GetFileNameWithoutExtension(bundleDir);
89-
cmds.Add(new FileCommandComponent
90-
{
91-
Name = name,
92-
ScriptPath = scriptPath,
93-
Tooltip = $"Command: {name}",
94-
UniqueId = $"{Path.GetFileNameWithoutExtension(extensionPath)}.{name}",
95-
ExtensionName = Path.GetFileNameWithoutExtension(extensionPath)
96-
});
83+
components.Add(ParseComponent(dir, type));
9784
}
9885

99-
return cmds;
86+
return components;
87+
}
88+
89+
private FileCommandComponent ParseComponent(string dir, string type)
90+
{
91+
var name = Path.GetFileNameWithoutExtension(dir);
92+
var children = LoadBundleComponents(dir);
93+
var scriptPath = Path.Combine(dir, "script.py");
94+
95+
return new FileCommandComponent
96+
{
97+
Name = name,
98+
ScriptPath = File.Exists(scriptPath) ? scriptPath : null,
99+
Tooltip = $"Command: {name}",
100+
UniqueId = $"{Path.GetFileNameWithoutExtension(dir)}.{name}",
101+
ExtensionName = FindExtensionNameFromPath(dir),
102+
Type = type,
103+
Children = children.Cast<object>().ToList()
104+
};
105+
}
106+
107+
private string FindExtensionNameFromPath(string path)
108+
{
109+
var segments = path.Split(Path.DirectorySeparatorChar);
110+
var extDir = segments.FirstOrDefault(s => s.EndsWith(".extension"));
111+
return extDir != null ? Path.GetFileNameWithoutExtension(extDir) : "UnknownExtension";
100112
}
101113

102114
private ExtensionMetadata LoadMetadata(string extensionPath)
@@ -148,8 +160,15 @@ public FileSystemExtension(string name, string path, IEnumerable<ICommandCompone
148160
public string Name { get; }
149161
public string Directory { get; }
150162
public ExtensionMetadata Metadata { get; }
163+
151164
public string GetHash() => Directory.GetHashCode().ToString("X");
165+
152166
public IEnumerable<ICommandComponent> GetAllCommands() => _commands;
167+
168+
public IEnumerable<object> Children => _commands;
169+
public string Type => ".extension";
170+
171+
object IExtension.Children => Children;
153172
}
154173

155174
private class FileCommandComponent : ICommandComponent
@@ -159,6 +178,8 @@ private class FileCommandComponent : ICommandComponent
159178
public string Tooltip { get; set; }
160179
public string UniqueId { get; set; }
161180
public string ExtensionName { get; set; }
181+
public string Type { get; set; }
182+
public IEnumerable<object> Children { get; set; } = Enumerable.Empty<object>();
162183
}
163184

164185
public class ExtensionMetadata
@@ -168,4 +189,4 @@ public class ExtensionMetadata
168189
public string Description { get; set; }
169190
}
170191
}
171-
}
192+
}
Lines changed: 97 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,61 +1,122 @@
11
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
24
using Autodesk.Revit.UI;
3-
using Autodesk.Revit.ApplicationServices;
4-
using pyRevitAssemblyBuilder.Shared;
5-
using static System.Net.Mime.MediaTypeNames;
65
using pyRevitAssemblyBuilder.AssemblyMaker;
6+
using pyRevitAssemblyBuilder.Shared;
77

88
namespace pyRevitAssemblyBuilder.SessionManager
99
{
1010
public class UIManagerService : IUIManager
1111
{
12-
private readonly UIApplication _uiApplication;
12+
private readonly UIApplication _uiApp;
1313

14-
public UIManagerService(UIApplication uiApplication)
14+
public UIManagerService(UIApplication uiApp)
1515
{
16-
_uiApplication = uiApplication;
16+
_uiApp = uiApp;
1717
}
1818

1919
public void BuildUI(IExtension extension, ExtensionAssemblyInfo assemblyInfo)
2020
{
21-
string tabName = extension.Name;
22-
string panelName = "Commands";
21+
if (extension?.Children == null)
22+
return;
2323

24-
try
24+
foreach (var obj in extension.Children as IEnumerable<object> ?? Enumerable.Empty<object>())
2525
{
26-
_uiApplication.CreateRibbonTab(tabName);
26+
RecursivelyBuildUI(obj, null, null, extension.Name, assemblyInfo);
2727
}
28-
catch { }
28+
}
2929

30-
RibbonPanel panel = null;
31-
foreach (var existingPanel in _uiApplication.GetRibbonPanels(tabName))
30+
private void RecursivelyBuildUI(object obj, object parentComponent, RibbonPanel parentPanel, string tabName, ExtensionAssemblyInfo assemblyInfo)
31+
{
32+
var component = obj as ICommandComponent;
33+
if (component == null)
34+
return;
35+
36+
var type = CommandComponentTypeExtensions.FromExtension(component.Type);
37+
38+
switch (type)
3239
{
33-
if (existingPanel.Name == panelName)
34-
{
35-
panel = existingPanel;
40+
case CommandComponentType.Tab:
41+
try { _uiApp.CreateRibbonTab(component.Name); } catch { }
42+
foreach (var child in component.Children as IEnumerable<object> ?? Enumerable.Empty<object>())
43+
RecursivelyBuildUI(child, component, null, component.Name, assemblyInfo);
3644
break;
37-
}
38-
}
39-
if (panel == null)
40-
{
41-
panel = _uiApplication.CreateRibbonPanel(tabName, panelName);
42-
}
4345

44-
foreach (var cmd in extension.GetAllCommands())
45-
{
46-
var pushButtonData = new PushButtonData(
47-
name: cmd.Name,
48-
text: cmd.Name,
49-
assemblyName: assemblyInfo.Location,
50-
className: cmd.UniqueId);
51-
52-
var button = panel.AddItem(pushButtonData) as PushButton;
53-
if (!string.IsNullOrEmpty(cmd.Tooltip))
54-
{
55-
button.ToolTip = cmd.Tooltip;
56-
}
46+
case CommandComponentType.Panel:
47+
var panel = _uiApp.GetRibbonPanels(tabName).FirstOrDefault(p => p.Name == component.Name)
48+
?? _uiApp.CreateRibbonPanel(tabName, component.Name);
49+
foreach (var child in component.Children as IEnumerable<object> ?? Enumerable.Empty<object>())
50+
RecursivelyBuildUI(child, component, panel, tabName, assemblyInfo);
51+
break;
52+
53+
case CommandComponentType.Stack:
54+
var buttonDatas = new List<RibbonItemData>();
55+
56+
foreach (var child in component.Children as IEnumerable<object> ?? Enumerable.Empty<object>())
57+
{
58+
var subCmd = child as ICommandComponent;
59+
if (subCmd == null) continue;
60+
61+
var subType = CommandComponentTypeExtensions.FromExtension(subCmd.Type);
62+
switch (subType)
63+
{
64+
case CommandComponentType.PushButton:
65+
buttonDatas.Add(new PushButtonData(subCmd.Name, subCmd.Name, assemblyInfo.Location, subCmd.UniqueId));
66+
break;
67+
case CommandComponentType.PullDown:
68+
buttonDatas.Add(new PulldownButtonData(subCmd.Name, subCmd.Name));
69+
break;
70+
}
71+
}
72+
73+
if (buttonDatas.Count == 2)
74+
parentPanel?.AddStackedItems(buttonDatas[0], buttonDatas[1]);
75+
else if (buttonDatas.Count >= 3)
76+
parentPanel?.AddStackedItems(buttonDatas[0], buttonDatas[1], buttonDatas[2]);
77+
break;
78+
79+
case CommandComponentType.PushButton:
80+
case CommandComponentType.SmartButton:
81+
var pbData = new PushButtonData(component.Name, component.Name, assemblyInfo.Location, component.UniqueId);
82+
var btn = parentPanel?.AddItem(pbData) as PushButton;
83+
if (!string.IsNullOrEmpty(component.Tooltip))
84+
btn.ToolTip = component.Tooltip;
85+
break;
86+
87+
case CommandComponentType.PullDown:
88+
var pdBtnData = new PulldownButtonData(component.Name, component.Name);
89+
var pdBtn = parentPanel?.AddItem(pdBtnData) as PulldownButton;
90+
if (pdBtn == null) return;
91+
92+
foreach (var child in component.Children as IEnumerable<object> ?? Enumerable.Empty<object>())
93+
{
94+
if (child is ICommandComponent subCmd &&
95+
CommandComponentTypeExtensions.FromExtension(subCmd.Type) == CommandComponentType.PushButton)
96+
{
97+
var subData = new PushButtonData(subCmd.Name, subCmd.Name, assemblyInfo.Location, subCmd.UniqueId);
98+
pdBtn.AddPushButton(subData);
99+
}
100+
}
101+
break;
102+
103+
case CommandComponentType.SplitButton:
104+
case CommandComponentType.SplitPushButton:
105+
var splitData = new SplitButtonData(component.Name, component.Name);
106+
var splitBtn = parentPanel?.AddItem(splitData) as SplitButton;
107+
if (splitBtn == null) return;
108+
109+
foreach (var child in component.Children as IEnumerable<object> ?? Enumerable.Empty<object>())
110+
{
111+
if (child is ICommandComponent subCmd &&
112+
CommandComponentTypeExtensions.FromExtension(subCmd.Type) == CommandComponentType.PushButton)
113+
{
114+
var subData = new PushButtonData(subCmd.Name, subCmd.Name, assemblyInfo.Location, subCmd.UniqueId);
115+
splitBtn.AddPushButton(subData);
116+
}
117+
}
118+
break;
57119
}
58120
}
59-
60121
}
61122
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
namespace pyRevitAssemblyBuilder.Shared
2+
{
3+
public enum CommandComponentType
4+
{
5+
Unknown,
6+
Tab,
7+
Panel,
8+
PushButton,
9+
PullDown,
10+
SplitButton,
11+
SplitPushButton,
12+
Stack,
13+
SmartButton,
14+
PanelButton,
15+
LinkButton,
16+
InvokeButton,
17+
UrlButton,
18+
ContentButton,
19+
NoButton
20+
}
21+
22+
public static class CommandComponentTypeExtensions
23+
{
24+
public static CommandComponentType FromExtension(string ext)
25+
{
26+
switch (ext.ToLowerInvariant())
27+
{
28+
case ".tab": return CommandComponentType.Tab;
29+
case ".panel": return CommandComponentType.Panel;
30+
case ".pushbutton": return CommandComponentType.PushButton;
31+
case ".pulldown": return CommandComponentType.PullDown;
32+
case ".splitbutton": return CommandComponentType.SplitButton;
33+
case ".splitpushbutton": return CommandComponentType.SplitPushButton;
34+
case ".stack": return CommandComponentType.Stack;
35+
case ".smartbutton": return CommandComponentType.SmartButton;
36+
case ".panelbutton": return CommandComponentType.PanelButton;
37+
case ".linkbutton": return CommandComponentType.LinkButton;
38+
case ".invokebutton": return CommandComponentType.InvokeButton;
39+
case ".urlbutton": return CommandComponentType.UrlButton;
40+
case ".content": return CommandComponentType.ContentButton;
41+
case ".nobutton": return CommandComponentType.NoButton;
42+
default: return CommandComponentType.Unknown;
43+
}
44+
}
45+
46+
public static string ToExtension(this CommandComponentType type)
47+
{
48+
switch (type)
49+
{
50+
case CommandComponentType.Tab: return ".tab";
51+
case CommandComponentType.Panel: return ".panel";
52+
case CommandComponentType.PushButton: return ".pushbutton";
53+
case CommandComponentType.PullDown: return ".pulldown";
54+
case CommandComponentType.SplitButton: return ".splitbutton";
55+
case CommandComponentType.SplitPushButton: return ".splitpushbutton";
56+
case CommandComponentType.Stack: return ".stack";
57+
case CommandComponentType.SmartButton: return ".smartbutton";
58+
case CommandComponentType.PanelButton: return ".panelbutton";
59+
case CommandComponentType.LinkButton: return ".linkbutton";
60+
case CommandComponentType.InvokeButton: return ".invokebutton";
61+
case CommandComponentType.UrlButton: return ".urlbutton";
62+
case CommandComponentType.ContentButton: return ".content";
63+
case CommandComponentType.NoButton: return ".nobutton";
64+
default: return string.Empty;
65+
}
66+
}
67+
}
68+
}

0 commit comments

Comments
 (0)