Skip to content

Commit d4f3f32

Browse files
jmcouffinromangolev
authored andcommitted
feat: enhance session management with build strategy support and improved error logging
1 parent ad8e74f commit d4f3f32

File tree

13 files changed

+766
-188
lines changed

13 files changed

+766
-188
lines changed

dev/pyRevitLoader/Source/PyRevitLoaderApplication.cs

Lines changed: 42 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System;
22
using System.IO;
33
using System.Reflection;
4+
using System.Diagnostics;
45
using Autodesk.Revit.UI;
56
using Autodesk.Revit.Attributes;
67
using pyRevitAssemblyBuilder.AssemblyMaker;
@@ -70,9 +71,10 @@ private static void LoadAssembliesInFolder(string folder)
7071
{
7172
Assembly.LoadFrom(engineDll);
7273
}
73-
catch
74+
catch (Exception ex)
7475
{
75-
76+
// Log assembly load failures - some assemblies may fail to load and that's acceptable
77+
Trace.WriteLine($"Failed to load assembly '{engineDll}': {ex.Message}");
7678
}
7779
}
7880
}
@@ -143,7 +145,7 @@ public static Result ExecuteStartUpPython(UIControlledApplication uiControlledAp
143145
}
144146
}
145147

146-
public static Result LoadSession(object outputWindow = null)
148+
public static Result LoadSession(object outputWindow = null, string buildStrategy = null)
147149
{
148150
try
149151
{
@@ -164,49 +166,53 @@ public static Result LoadSession(object outputWindow = null)
164166
// Get the current Revit version
165167
var revitVersion = uiControlledApplication.ControlledApplication.VersionNumber;
166168

167-
// Create the build strategy enum value - default to ILPack
169+
// Determine build strategy: use provided parameter, or read from config, or default to ILPack
168170
var assemblyBuildStrategyType = typeof(pyRevitAssemblyBuilder.AssemblyMaker.AssemblyBuildStrategy);
169-
var strategyValue = Enum.Parse(assemblyBuildStrategyType, "ILPack");
171+
object strategyValue;
172+
173+
if (!string.IsNullOrEmpty(buildStrategy))
174+
{
175+
// Use provided build strategy from Python
176+
strategyValue = Enum.Parse(assemblyBuildStrategyType, buildStrategy);
177+
}
178+
else
179+
{
180+
// Fallback: read from config
181+
try
182+
{
183+
var config = PyRevitConfig.Load();
184+
var strategyName = config.NewLoaderRoslyn ? "Roslyn" : "ILPack";
185+
strategyValue = Enum.Parse(assemblyBuildStrategyType, strategyName);
186+
}
187+
catch
188+
{
189+
// Final fallback: default to ILPack
190+
strategyValue = Enum.Parse(assemblyBuildStrategyType, "ILPack");
191+
}
192+
}
170193

171-
// Instantiate the services (following the same pattern as Python code)
172-
var assemblyBuilderServiceType = typeof(pyRevitAssemblyBuilder.AssemblyMaker.AssemblyBuilderService);
173-
var assemblyBuilder = Activator.CreateInstance(
174-
assemblyBuilderServiceType,
194+
// Create services using factory
195+
var strategyEnum = (pyRevitAssemblyBuilder.AssemblyMaker.AssemblyBuildStrategy)strategyValue;
196+
var sessionManager = pyRevitAssemblyBuilder.SessionManager.ServiceFactory.CreateSessionManagerService(
175197
revitVersion,
176-
strategyValue
177-
);
178-
179-
var extensionManagerServiceType = typeof(pyRevitAssemblyBuilder.SessionManager.ExtensionManagerService);
180-
var extensionManager = Activator.CreateInstance(extensionManagerServiceType);
181-
182-
var hookManagerType = typeof(pyRevitAssemblyBuilder.SessionManager.HookManager);
183-
var hookManager = Activator.CreateInstance(hookManagerType);
184-
185-
var uiManagerServiceType = typeof(pyRevitAssemblyBuilder.SessionManager.UIManagerService);
186-
var uiManager = Activator.CreateInstance(
187-
uiManagerServiceType,
188-
uiApplication
189-
);
190-
191-
var sessionManagerServiceType = typeof(pyRevitAssemblyBuilder.SessionManager.SessionManagerService);
192-
var sessionManager = Activator.CreateInstance(
193-
sessionManagerServiceType,
194-
assemblyBuilder,
195-
extensionManager,
196-
hookManager,
197-
uiManager,
198-
outputWindow
199-
);
198+
strategyEnum,
199+
uiApplication,
200+
outputWindow);
200201

201202
// Load the session using the C# SessionManagerService
202-
var loadSessionMethod = sessionManagerServiceType.GetMethod("LoadSession");
203-
loadSessionMethod.Invoke(sessionManager, null);
203+
sessionManager.LoadSession();
204204

205205
return Result.Succeeded;
206206
}
207207
catch (Exception ex)
208208
{
209-
TaskDialog.Show("Error Loading C# Session", ex.ToString());
209+
// Log detailed error information before showing dialog
210+
Trace.WriteLine($"Error Loading C# Session: {ex}");
211+
Trace.WriteLine($"Stack Trace: {ex.StackTrace}");
212+
213+
// Show user-friendly error dialog
214+
TaskDialog.Show("Error Loading C# Session",
215+
$"An error occurred while loading the C# session:\n\n{ex.Message}\n\nCheck the output window for details.");
210216
return Result.Failed;
211217
}
212218
}

dev/pyRevitLoader/pyRevitAssemblyBuilder/AssemblyMaker/AssemblyBuilderService.cs

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,24 @@
1515

1616
namespace pyRevitAssemblyBuilder.AssemblyMaker
1717
{
18+
/// <summary>
19+
/// Enumeration of available assembly build strategies.
20+
/// </summary>
1821
public enum AssemblyBuildStrategy
1922
{
23+
/// <summary>
24+
/// Build using Roslyn compiler (C# code generation).
25+
/// </summary>
2026
Roslyn,
27+
/// <summary>
28+
/// Build using ILPack (Reflection.Emit with IL packing).
29+
/// </summary>
2130
ILPack
2231
}
2332

33+
/// <summary>
34+
/// Service for building extension assemblies from parsed extensions.
35+
/// </summary>
2436
public class AssemblyBuilderService
2537
{
2638
private readonly string _revitVersion;
@@ -29,6 +41,12 @@ public class AssemblyBuilderService
2941
private static readonly string _baseDir = Path.GetDirectoryName(_executingAssemblyLocation);
3042
private static readonly string _appDataPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
3143

44+
/// <summary>
45+
/// Initializes a new instance of the <see cref="AssemblyBuilderService"/> class.
46+
/// </summary>
47+
/// <param name="revitVersion">The Revit version number (e.g., "2024").</param>
48+
/// <param name="buildStrategy">The build strategy to use for creating assemblies.</param>
49+
/// <exception cref="ArgumentNullException">Thrown when revitVersion is null.</exception>
3250
public AssemblyBuilderService(string revitVersion, AssemblyBuildStrategy buildStrategy)
3351
{
3452
_revitVersion = revitVersion ?? throw new ArgumentNullException(nameof(revitVersion));
@@ -70,6 +88,14 @@ public AssemblyBuilderService(string revitVersion, AssemblyBuildStrategy buildSt
7088
#endif
7189
}
7290

91+
/// <summary>
92+
/// Builds an assembly for the specified extension.
93+
/// </summary>
94+
/// <param name="extension">The parsed extension to build an assembly for.</param>
95+
/// <param name="libraryExtensions">Optional collection of library extensions to include as references.</param>
96+
/// <returns>Information about the built assembly.</returns>
97+
/// <exception cref="ArgumentNullException">Thrown when extension is null.</exception>
98+
/// <exception cref="Exception">Thrown when assembly building fails.</exception>
7399
public ExtensionAssemblyInfo BuildExtensionAssembly(ParsedExtension extension, IEnumerable<ParsedExtension> libraryExtensions = null)
74100
{
75101
if (extension == null)
@@ -192,8 +218,8 @@ private void UpdateReferencedAssemblies(HashSet<string> newModulePaths)
192218
try
193219
{
194220
// Get the environment dictionary from AppDomain
195-
const string envDictKey = "PYREVITEnvVarsDict";
196-
const string refedAssmsKey = "PYREVIT_REFEDASSMS";
221+
const string envDictKey = pyRevitAssemblyBuilder.SessionManager.Constants.ENV_DICT_KEY;
222+
const string refedAssmsKey = pyRevitAssemblyBuilder.SessionManager.Constants.REFED_ASSMS_KEY;
197223

198224
var envDict = AppDomain.CurrentDomain.GetData(envDictKey);
199225
if (envDict == null)
@@ -331,6 +357,12 @@ private static string GetStableHash(string input)
331357
return BitConverter.ToString(hash).Replace("-", string.Empty).ToLowerInvariant();
332358
}
333359

360+
/// <summary>
361+
/// Loads an assembly into the current AppDomain.
362+
/// </summary>
363+
/// <param name="info">Information about the assembly to load.</param>
364+
/// <exception cref="FileNotFoundException">Thrown when the assembly file is not found.</exception>
365+
/// <exception cref="Exception">Thrown when assembly loading fails.</exception>
334366
public void LoadAssembly(ExtensionAssemblyInfo info)
335367
{
336368
if (!File.Exists(info.Location))
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
namespace pyRevitAssemblyBuilder.SessionManager
2+
{
3+
/// <summary>
4+
/// Constants used throughout the SessionManager namespace.
5+
/// </summary>
6+
internal static class Constants
7+
{
8+
/// <summary>
9+
/// Marker file name used to identify pyRevit root directory.
10+
/// </summary>
11+
public const string PYREVIT_MARKER_FILE = "pyRevitfile";
12+
13+
/// <summary>
14+
/// Directory name for pyRevit library files.
15+
/// </summary>
16+
public const string PYREVIT_LIB_DIR = "pyrevitlib";
17+
18+
/// <summary>
19+
/// Directory name for site-packages.
20+
/// </summary>
21+
public const string SITE_PACKAGES_DIR = "site-packages";
22+
23+
/// <summary>
24+
/// Key for environment variables dictionary in AppDomain data.
25+
/// </summary>
26+
public const string ENV_DICT_KEY = "PYREVITEnvVarsDict";
27+
28+
/// <summary>
29+
/// Key for referenced assemblies in environment dictionary.
30+
/// </summary>
31+
public const string REFED_ASSMS_KEY = "PYREVIT_REFEDASSMS";
32+
}
33+
}
34+

dev/pyRevitLoader/pyRevitAssemblyBuilder/SessionManager/ExtensionManagerService.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,15 @@
55

66
namespace pyRevitAssemblyBuilder.SessionManager
77
{
8+
/// <summary>
9+
/// Service for managing and querying installed pyRevit extensions.
10+
/// </summary>
811
public class ExtensionManagerService
912
{
13+
/// <summary>
14+
/// Gets all installed extensions that are not disabled.
15+
/// </summary>
16+
/// <returns>An enumerable collection of parsed extensions.</returns>
1017
public IEnumerable<ParsedExtension> GetInstalledExtensions()
1118
{
1219
var installedExtensions = ExtensionParser.ParseInstalledExtensions();
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
using System;
2+
using Autodesk.Revit.UI;
3+
using pyRevitAssemblyBuilder.AssemblyMaker;
4+
using pyRevitExtensionParser;
5+
6+
namespace pyRevitAssemblyBuilder.SessionManager
7+
{
8+
/// <summary>
9+
/// Factory for creating service instances used by the session manager.
10+
/// </summary>
11+
public static class ServiceFactory
12+
{
13+
/// <summary>
14+
/// Creates an AssemblyBuilderService instance.
15+
/// </summary>
16+
/// <param name="revitVersion">The Revit version number.</param>
17+
/// <param name="buildStrategy">The build strategy to use.</param>
18+
/// <returns>A new AssemblyBuilderService instance.</returns>
19+
public static AssemblyBuilderService CreateAssemblyBuilderService(string revitVersion, AssemblyBuildStrategy buildStrategy)
20+
{
21+
return new AssemblyBuilderService(revitVersion, buildStrategy);
22+
}
23+
24+
/// <summary>
25+
/// Creates an ExtensionManagerService instance.
26+
/// </summary>
27+
/// <returns>A new ExtensionManagerService instance.</returns>
28+
public static ExtensionManagerService CreateExtensionManagerService()
29+
{
30+
return new ExtensionManagerService();
31+
}
32+
33+
/// <summary>
34+
/// Creates a HookManager instance.
35+
/// </summary>
36+
/// <returns>A new HookManager instance.</returns>
37+
public static HookManager CreateHookManager()
38+
{
39+
return new HookManager();
40+
}
41+
42+
/// <summary>
43+
/// Creates a UIManagerService instance.
44+
/// </summary>
45+
/// <param name="uiApplication">The Revit UIApplication instance.</param>
46+
/// <returns>A new UIManagerService instance.</returns>
47+
public static UIManagerService CreateUIManagerService(UIApplication uiApplication)
48+
{
49+
return new UIManagerService(uiApplication);
50+
}
51+
52+
/// <summary>
53+
/// Creates a SessionManagerService instance with all required dependencies.
54+
/// </summary>
55+
/// <param name="revitVersion">The Revit version number.</param>
56+
/// <param name="buildStrategy">The build strategy to use.</param>
57+
/// <param name="uiApplication">The Revit UIApplication instance.</param>
58+
/// <param name="outputWindow">Optional output window for logging.</param>
59+
/// <returns>A new SessionManagerService instance.</returns>
60+
public static SessionManagerService CreateSessionManagerService(
61+
string revitVersion,
62+
AssemblyBuildStrategy buildStrategy,
63+
UIApplication uiApplication,
64+
object outputWindow = null)
65+
{
66+
var assemblyBuilder = CreateAssemblyBuilderService(revitVersion, buildStrategy);
67+
var extensionManager = CreateExtensionManagerService();
68+
var hookManager = CreateHookManager();
69+
var uiManager = CreateUIManagerService(uiApplication);
70+
71+
return new SessionManagerService(
72+
assemblyBuilder,
73+
extensionManager,
74+
hookManager,
75+
uiManager,
76+
outputWindow);
77+
}
78+
}
79+
}
80+

0 commit comments

Comments
 (0)