Skip to content

Commit 80f6ccf

Browse files
romangolevjmcouffin
authored andcommitted
feat: make assembly put dlls in a correct folder, scanning all available extensions
1 parent 4e98317 commit 80f6ccf

File tree

5 files changed

+160
-38
lines changed

5 files changed

+160
-38
lines changed

dev/pyRevitLoader/Source/PyRevitLoaderApplication.cs

Lines changed: 45 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,36 @@ private static void LoadAssembliesInFolder(string folder)
7474
}
7575

7676
private static Result ExecuteStartupScript(UIControlledApplication uiControlledApplication)
77+
{
78+
//TODO: Implement a switcher here to be able to switch between Python/C# loaders
79+
//return ExecuteStartUpPython(uiControlledApplication);
80+
return ExecuteStartUpCsharp(uiControlledApplication);
81+
}
82+
83+
public static Result ExecuteStartUpPython(UIControlledApplication uiControlledApplication)
84+
{
85+
// we need a UIApplication object to assign as `__revit__` in python...
86+
var versionNumber = uiControlledApplication.ControlledApplication.VersionNumber;
87+
var fieldName = int.Parse(versionNumber) >= 2017 ? "m_uiapplication" : "m_application";
88+
var fi = uiControlledApplication.GetType().GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Instance);
89+
90+
var uiApplication = (UIApplication)fi.GetValue(uiControlledApplication);
91+
// execute StartupScript
92+
Result result = Result.Succeeded;
93+
var startupScript = GetStartupScriptPath();
94+
if (startupScript != null)
95+
{
96+
var executor = new ScriptExecutor(uiApplication); // uiControlledApplication);
97+
result = executor.ExecuteScript(startupScript);
98+
if (result == Result.Failed)
99+
{
100+
TaskDialog.Show("Error Loading pyRevit", executor.Message);
101+
}
102+
}
103+
104+
return result;
105+
}
106+
public static Result ExecuteStartUpCsharp(UIControlledApplication uiControlledApplication)
77107
{
78108
try
79109
{
@@ -84,16 +114,27 @@ private static Result ExecuteStartupScript(UIControlledApplication uiControlledA
84114

85115
var services = new ServiceCollection();
86116
services.AddLogging(cfg => cfg.AddDebug());
87-
services.AddAssemblyBuilder();
88117

89-
services.AddSingleton<IExtensionManager, ExtensionManagerService>();
118+
// Add Revit UIApplication to services
119+
services.AddSingleton(uiApplication);
120+
121+
// Add known services
122+
services.AddSingleton<ICommandTypeGenerator, DefaultCommandTypeGenerator>();
90123
services.AddSingleton<IHookManager, DummyHookManager>();
91-
services.AddSingleton<IUIManager, DummyUIManager>();
124+
services.AddSingleton<IUIManager, UIManagerService>();
92125
services.AddSingleton<ISessionManager, SessionManagerService>();
126+
services.AddSingleton<IExtensionManager, ExtensionManagerService>();
127+
128+
// Register AssemblyBuilderService with explicit string parameter
129+
services.AddSingleton<AssemblyBuilderService>(sp =>
130+
new AssemblyBuilderService(
131+
sp.GetRequiredService<ICommandTypeGenerator>(),
132+
versionNumber
133+
)
134+
);
93135

94136
var serviceProvider = services.BuildServiceProvider();
95137
var sessionManager = serviceProvider.GetRequiredService<ISessionManager>();
96-
97138
sessionManager.LoadSessionAsync().Wait();
98139

99140
return Result.Succeeded;
@@ -104,7 +145,6 @@ private static Result ExecuteStartupScript(UIControlledApplication uiControlledA
104145
return Result.Failed;
105146
}
106147
}
107-
108148
private static string GetStartupScriptPath()
109149
{
110150
var loaderDir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);

dev/pyRevitLoader/pyRevitAssemblyBuilder/AssemblyMaker/AssemblyBuilderService.cs

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,35 +9,42 @@ namespace pyRevitAssemblyBuilder.AssemblyMaker
99
public class AssemblyBuilderService
1010
{
1111
private readonly ICommandTypeGenerator _typeGenerator;
12+
private readonly string _revitVersion;
1213

13-
public AssemblyBuilderService(ICommandTypeGenerator typeGenerator)
14+
public AssemblyBuilderService(ICommandTypeGenerator typeGenerator, string revitVersion)
1415
{
1516
_typeGenerator = typeGenerator;
17+
_revitVersion = revitVersion ?? throw new ArgumentNullException(nameof(revitVersion));
1618
}
1719

1820
public ExtensionAssemblyInfo BuildExtensionAssembly(IExtension extension)
1921
{
20-
string fileId = $"{extension.GetHash()}_{extension.Name}";
21-
string outputDir = Path.Combine(Path.GetTempPath(), "pyRevit", "assemblies");
22+
string extensionHash = GetStableHash(extension.GetHash() + _revitVersion).Substring(0, 16);
23+
string fileName = $"pyRevit_{_revitVersion}_{extensionHash}_{extension.Name}.dll";
24+
25+
string outputDir = Path.Combine(
26+
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
27+
"pyRevit",
28+
_revitVersion
29+
);
2230
Directory.CreateDirectory(outputDir);
2331

24-
string outputPath = Path.Combine(outputDir, fileId + ".dll");
32+
string outputPath = Path.Combine(outputDir, fileName);
2533

2634
var asmName = new AssemblyName(extension.Name)
2735
{
2836
Version = new Version(1, 0, 0, 0)
2937
};
3038

31-
string fileName = Path.GetFileNameWithoutExtension(outputPath);
32-
string fileNameWithExt = Path.GetFileName(outputPath);
39+
string fileNameWithoutExt = Path.GetFileNameWithoutExtension(outputPath);
3340

3441
#if NETFRAMEWORK
3542
var domain = AppDomain.CurrentDomain;
3643
var asmBuilder = domain.DefineDynamicAssembly(asmName, AssemblyBuilderAccess.RunAndSave, outputDir);
37-
var moduleBuilder = asmBuilder.DefineDynamicModule(fileName, fileNameWithExt);
44+
var moduleBuilder = asmBuilder.DefineDynamicModule(fileNameWithoutExt, fileName);
3845
#else
3946
var asmBuilder = AssemblyBuilder.DefineDynamicAssembly(asmName, AssemblyBuilderAccess.Run);
40-
var moduleBuilder = asmBuilder.DefineDynamicModule(fileName);
47+
var moduleBuilder = asmBuilder.DefineDynamicModule(fileNameWithoutExt);
4148
#endif
4249

4350
foreach (var cmd in extension.GetAllCommands())
@@ -46,7 +53,7 @@ public ExtensionAssemblyInfo BuildExtensionAssembly(IExtension extension)
4653
}
4754

4855
#if NETFRAMEWORK
49-
asmBuilder.Save(fileNameWithExt);
56+
asmBuilder.Save(fileName);
5057
#else
5158
var generator = new Lokad.ILPack.AssemblyGenerator();
5259
generator.GenerateAssembly(asmBuilder, outputPath);
@@ -68,5 +75,14 @@ private bool CheckIfExtensionAlreadyLoaded(string extensionName)
6875
}
6976
return false;
7077
}
78+
79+
private static string GetStableHash(string input)
80+
{
81+
using (var sha1 = System.Security.Cryptography.SHA1.Create())
82+
{
83+
var hash = sha1.ComputeHash(System.Text.Encoding.UTF8.GetBytes(input));
84+
return BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant();
85+
}
86+
}
7187
}
7288
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.IO;
4+
using System.Text.RegularExpressions;
5+
6+
namespace pyRevitAssemblyBuilder.Config
7+
{
8+
public class PyRevitConfig
9+
{
10+
public List<string> UserExtensions { get; } = new List<string>();
11+
12+
public static PyRevitConfig Load(string configPath)
13+
{
14+
var config = new PyRevitConfig();
15+
16+
if (!File.Exists(configPath))
17+
return config;
18+
19+
string currentSection = "";
20+
foreach (var line in File.ReadAllLines(configPath))
21+
{
22+
var trimmed = line.Trim();
23+
24+
if (string.IsNullOrEmpty(trimmed) || trimmed.StartsWith(";"))
25+
continue;
26+
27+
if (trimmed.StartsWith("[") && trimmed.EndsWith("]"))
28+
{
29+
currentSection = trimmed.Trim('[', ']');
30+
}
31+
else if (currentSection == "core")
32+
{
33+
var match = Regex.Match(trimmed, @"userextensions\s*=\s*\[(.*?)\]");
34+
if (match.Success)
35+
{
36+
var list = match.Groups[1].Value;
37+
var paths = Regex.Matches(list, "\"(.*?)\"");
38+
foreach (Match p in paths)
39+
{
40+
config.UserExtensions.Add(p.Groups[1].Value);
41+
}
42+
}
43+
}
44+
}
45+
46+
return config;
47+
}
48+
}
49+
}

dev/pyRevitLoader/pyRevitAssemblyBuilder/SessionManager/ExtensionManagerService.cs

Lines changed: 40 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -4,54 +4,71 @@
44
using System.Linq;
55
using Microsoft.Extensions.DependencyInjection;
66
using pyRevitAssemblyBuilder.AssemblyMaker;
7+
using pyRevitAssemblyBuilder.Config;
78
using pyRevitAssemblyBuilder.Shared;
89

910
namespace pyRevitAssemblyBuilder.SessionManager
1011
{
1112
public class ExtensionManagerService : IExtensionManager
1213
{
13-
private readonly string _extensionsRoot;
1414
private readonly ICommandTypeGenerator _typeGenerator;
1515
private readonly IServiceProvider _serviceProvider;
16+
private readonly List<string> _extensionRoots;
1617

1718
private static readonly string[] SupportedBundleTypes = new[]
1819
{
1920
".pushbutton", ".pulldownbutton", ".splitbutton", ".stack", ".panel", ".tab"
2021
};
2122

22-
public ExtensionManagerService(string extensionsRoot = null)
23-
{
24-
_extensionsRoot = extensionsRoot ??
25-
Path.Combine(
26-
Path.GetDirectoryName(typeof(ExtensionManagerService).Assembly.Location),
27-
"..", "extensions"
28-
);
29-
_extensionsRoot = Path.GetFullPath(_extensionsRoot);
30-
}
31-
32-
public ExtensionManagerService(IServiceProvider serviceProvider, string extensionsRoot = null)
33-
: this(extensionsRoot)
23+
public ExtensionManagerService(IServiceProvider serviceProvider)
3424
{
3525
_serviceProvider = serviceProvider;
3626
_typeGenerator = _serviceProvider.GetRequiredService<ICommandTypeGenerator>();
27+
_extensionRoots = GetExtensionRoots();
3728
}
3829

3930
public IEnumerable<IExtension> GetInstalledExtensions()
4031
{
41-
if (!Directory.Exists(_extensionsRoot))
42-
yield break;
43-
44-
foreach (var dir in Directory.GetDirectories(_extensionsRoot))
32+
foreach (var root in _extensionRoots)
4533
{
46-
if (!dir.EndsWith(".extension", StringComparison.OrdinalIgnoreCase))
34+
if (!Directory.Exists(root))
4735
continue;
4836

49-
var extensionName = Path.GetFileNameWithoutExtension(dir);
50-
var metadata = LoadMetadata(dir);
51-
var commands = LoadCommands(dir);
52-
if (commands.Any())
53-
yield return new FileSystemExtension(extensionName, dir, commands, metadata);
37+
foreach (var dir in Directory.GetDirectories(root))
38+
{
39+
if (!dir.EndsWith(".extension", StringComparison.OrdinalIgnoreCase))
40+
continue;
41+
42+
var extensionName = Path.GetFileNameWithoutExtension(dir);
43+
var metadata = LoadMetadata(dir);
44+
var commands = LoadCommands(dir);
45+
if (commands.Any())
46+
yield return new FileSystemExtension(extensionName, dir, commands, metadata);
47+
}
48+
}
49+
}
50+
51+
private List<string> GetExtensionRoots()
52+
{
53+
var roots = new List<string>();
54+
55+
// Default: 4 folders up + extensions
56+
var current = Path.GetDirectoryName(typeof(ExtensionManagerService).Assembly.Location);
57+
var defaultPath = Path.GetFullPath(Path.Combine(current, "..", "..", "..", "..", "extensions"));
58+
roots.Add(defaultPath);
59+
60+
// Custom userextensions from config
61+
var configPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "pyRevit", "pyRevit_config.ini");
62+
if (File.Exists(configPath))
63+
{
64+
var config = PyRevitConfig.Load(configPath);
65+
if (config.UserExtensions != null && config.UserExtensions.Count > 0)
66+
{
67+
roots.AddRange(config.UserExtensions);
68+
}
5469
}
70+
71+
return roots;
5572
}
5673

5774
private IEnumerable<ICommandComponent> LoadCommands(string extensionPath)

dev/pyRevitLoader/pyRevitAssemblyBuilder/SessionManager/SessionManagerService.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ public async Task<string> LoadSessionAsync()
3838
foreach (var ext in extensions)
3939
{
4040
var assmInfo = _assemblyBuilder.BuildExtensionAssembly(ext);
41-
//_uiManager.BuildUI(ext, assmInfo);
41+
_uiManager.BuildUI(ext, assmInfo);
4242
_hookManager.RegisterHooks(ext);
4343
}
4444

0 commit comments

Comments
 (0)