diff --git a/CHANGELOG.md b/CHANGELOG.md
index dbc7df7..3a70768 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,10 +7,14 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
## [1.0.0] / 2025-01-20
### Features
- Initial release.
+### DesignApplication
+- Add `DesignApplicationLoader` to fix [Design Automation for Revit ignores PackageContents.xml configuration.](https://github.com/ricaun-io/RevitAddin.DA.Tester/issues/7)
+- Add `ExternalServer` to fix [Design Automation for Revit ActiveAddInId is null](https://github.com/ricaun-io/RevitAddin.DA.Tester/issues/9)
### Application
- Sample to execute `ricaun.Revit.DA` library.
### Tests
- Test `ricaun.Revit.DA` library.
+- Update `TestCase` with expected results. (`Reference`, `TargetFramework` and `ActiveAddInId`)
[vNext]: ../../compare/1.0.0...HEAD
[1.0.0]: ../../compare/1.0.0
\ No newline at end of file
diff --git a/Directory.Build.props b/Directory.Build.props
index ea76a19..a75d114 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -1,5 +1,5 @@
- 1.0.0-alpha
+ 1.0.0-beta
\ No newline at end of file
diff --git a/ricaun.Revit.DA.Example/Revit/App.cs b/ricaun.Revit.DA.Example/Revit/App.cs
index a7fbd9b..213e5a2 100644
--- a/ricaun.Revit.DA.Example/Revit/App.cs
+++ b/ricaun.Revit.DA.Example/Revit/App.cs
@@ -8,18 +8,14 @@ public class App : DesignApplication
{
public override bool Execute(Application application, string filePath, Document document)
{
- Console.WriteLine($"Execute: \t{application}");
- Console.WriteLine($"ActiveAddInId: \t{application.ActiveAddInId?.GetAddInName()}");
-
var output = RevitOutputModel.Create(application);
output.Save();
-
return true;
}
public override void OnStartup()
{
- Console.WriteLine($"ActiveAddInId: \t{ControlledApplication.ActiveAddInId?.GetAddInName()}");
+
}
public override void OnShutdown()
diff --git a/ricaun.Revit.DA.Tests/Tests.cs b/ricaun.Revit.DA.Tests/Tests.cs
index efe4630..b90fca6 100644
--- a/ricaun.Revit.DA.Tests/Tests.cs
+++ b/ricaun.Revit.DA.Tests/Tests.cs
@@ -9,8 +9,10 @@ namespace ricaun.Revit.DA.Tests
{
public class Tests : BundleFileTests
{
- [TestCase("2024", "4.7", "19", null)]
- [TestCase("2025", "4.7", "19", null)]
+ //[TestCase("2024", "4.7", "19", null)]
+ //[TestCase("2025", "4.7", "19", null)]
+ [TestCase("2024", "4.8", "21", "Example")]
+ [TestCase("2025", "8.0", "25", "Example")]
public async Task ExecuteDesignAutomation(string engine, string frameworkNameContain, string referenceContain, string addInNameContain)
{
var bundlePaths = GetBundles();
@@ -22,12 +24,12 @@ public async Task ExecuteDesignAutomation(string engine, string frameworkNameCon
Console.WriteLine(output.ToJson());
- Assert.IsTrue(output.VersionName.Contains(engine), $"VersionName contains engine {engine}");
- Assert.IsTrue(output.FrameworkName.Contains(frameworkNameContain), $"FrameworkName contains framework {frameworkNameContain}");
- Assert.IsTrue(output.Reference.Contains(referenceContain), $"Reference contains reference {referenceContain}");
+ Assert.IsTrue(output.VersionName.Contains(engine), $"VersionName {output.VersionName} not contains engine {engine}");
+ Assert.IsTrue(output.FrameworkName.Contains(frameworkNameContain), $"FrameworkName {output.FrameworkName} not contains framework {frameworkNameContain}");
+ Assert.IsTrue(output.Reference.Contains(referenceContain), $"Reference {output.Reference} not contains reference {referenceContain}");
if (output.AddInName is not null)
- Assert.IsTrue(output.AddInName.Contains(addInNameContain), $"AddinName contains addin {addInNameContain}");
+ Assert.IsTrue(output.AddInName.Contains(addInNameContain), $"AddinName {output.AddInName} not contains addin {addInNameContain}");
else
Assert.AreEqual(addInNameContain, output.AddInName);
}
diff --git a/ricaun.Revit.DA/DesignApplication.cs b/ricaun.Revit.DA/DesignApplication.cs
index ab0d9cb..6a9ce2c 100644
--- a/ricaun.Revit.DA/DesignApplication.cs
+++ b/ricaun.Revit.DA/DesignApplication.cs
@@ -1,18 +1,50 @@
using Autodesk.Revit.ApplicationServices;
using Autodesk.Revit.DB;
using DesignAutomationFramework;
+using ricaun.Revit.DA.ExternalServer;
+using ricaun.Revit.DA.Loader;
+using System;
namespace ricaun.Revit.DA
{
public abstract class DesignApplication : IExternalDBApplication, IDesignAutomation
{
+ ///
+ /// Use ExternalService to execute the IDesignAutomation.Execute, this make the Execute run in the AddIn context.
+ ///
+ public virtual bool UseExternalService => true;
public ControlledApplication ControlledApplication { get; private set; }
public abstract void OnStartup();
public abstract void OnShutdown();
public abstract bool Execute(Application application, string filePath, Document document);
+
+ private IExternalDBApplication designApplication;
+ private DesignAutomationSingleExternalServer externalServer;
public ExternalDBApplicationResult OnStartup(ControlledApplication application)
{
ControlledApplication = application;
+
+ designApplication = DesignApplicationLoader.LoadVersion(this);
+
+ if (designApplication is IExternalDBApplication)
+ {
+ return designApplication.OnStartup(application);
+ }
+
+ Console.WriteLine("----------------------------------------");
+ Console.WriteLine($"FullName: \t{GetType().Assembly.FullName}");
+ Console.WriteLine($"AddInName: \t{ControlledApplication.ActiveAddInId?.GetAddInName()}");
+ Console.WriteLine("----------------------------------------");
+
+ try
+ {
+ externalServer = new DesignAutomationSingleExternalServer(this).Register();
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"DesignAutomationSingleExternalServer: \t{ex.Message}");
+ }
+
OnStartup();
DesignAutomationBridge.DesignAutomationReadyEvent += DesignAutomationReadyEvent;
@@ -22,24 +54,45 @@ public ExternalDBApplicationResult OnStartup(ControlledApplication application)
public ExternalDBApplicationResult OnShutdown(ControlledApplication application)
{
ControlledApplication = application;
+
+ if (designApplication is IExternalDBApplication)
+ {
+ try
+ {
+ return designApplication.OnShutdown(application);
+ }
+ finally
+ {
+ DesignApplicationLoader.Dispose();
+ }
+ }
+
+ externalServer?.RemoveServer();
+
OnShutdown();
DesignAutomationBridge.DesignAutomationReadyEvent -= DesignAutomationReadyEvent;
return ExternalDBApplicationResult.Succeeded;
}
+
private void DesignAutomationReadyEvent(object sender, DesignAutomationReadyEventArgs e)
{
DesignAutomationBridge.DesignAutomationReadyEvent -= DesignAutomationReadyEvent;
var data = e.DesignAutomationData;
+ Console.WriteLine("--------------------------------------------------");
+ Console.WriteLine($"RevitApp: {data.RevitApp} \tFilePath: {data.FilePath} \tRevitDoc: {data.RevitDoc} \tAddInName:{data.RevitApp.ActiveAddInId?.GetAddInName()}");
+ Console.WriteLine("--------------------------------------------------");
+
+ if (externalServer is not null && UseExternalService)
+ {
+ e.Succeeded = externalServer.ExecuteService(data.RevitApp, data.FilePath, data.RevitDoc);
+ return;
+ }
+
e.Succeeded = Execute(data.RevitApp, data.FilePath, data.RevitDoc);
}
}
-
- public interface IDesignAutomation
- {
- bool Execute(Application application, string filePath, Document document);
- }
}
diff --git a/ricaun.Revit.DA/ExternalServer/DesignAutomationExternalData.cs b/ricaun.Revit.DA/ExternalServer/DesignAutomationExternalData.cs
new file mode 100644
index 0000000..f6fa46b
--- /dev/null
+++ b/ricaun.Revit.DA/ExternalServer/DesignAutomationExternalData.cs
@@ -0,0 +1,13 @@
+using Autodesk.Revit.ApplicationServices;
+using Autodesk.Revit.DB;
+using Autodesk.Revit.DB.ExternalService;
+
+namespace ricaun.Revit.DA.ExternalServer
+{
+ public class DesignAutomationExternalData : IExternalData
+ {
+ public Application Application { get; set; }
+ public string FilePath { get; set; }
+ public Document Document { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/ricaun.Revit.DA/ExternalServer/DesignAutomationSingleExternalServer.cs b/ricaun.Revit.DA/ExternalServer/DesignAutomationSingleExternalServer.cs
new file mode 100644
index 0000000..ec37b62
--- /dev/null
+++ b/ricaun.Revit.DA/ExternalServer/DesignAutomationSingleExternalServer.cs
@@ -0,0 +1,123 @@
+using Autodesk.Revit.ApplicationServices;
+using Autodesk.Revit.DB;
+using Autodesk.Revit.DB.ExternalService;
+using System;
+using System.Collections.Generic;
+
+namespace ricaun.Revit.DA.ExternalServer
+{
+ ///
+ /// DesignAutomationSingleExternalServer is a single server with a default single service.
+ ///
+ ///
+ /// This external server is used to execute IDesignAutomation when DesignAutomationReadyEvent is triggered.
+ /// Because the external server is registered before the Revit finish initialize the executed service run in the same ActiveAddIn when the external service is registered.
+ /// Fix the issue that DesignAutomationReadyEvent triggers without the ActiveAddIn context.
+ ///
+ public class DesignAutomationSingleExternalServer : ISingleServerService, IDesignAutomationExternalServer
+ {
+ private readonly IDesignAutomation designAutomation;
+ public DesignAutomationSingleExternalServer(IDesignAutomation designAutomation)
+ {
+ this.designAutomation = designAutomation;
+ }
+ public ExternalServiceId ServiceId { get; } = new ExternalServiceId(Guid.NewGuid());
+ public Guid ServerId { get; } = Guid.NewGuid();
+
+ #region ExecuteService
+
+ public bool ExecuteService(Application application, string filePath, Document document)
+ {
+ var externalData = new DesignAutomationExternalData()
+ {
+ Application = application,
+ FilePath = filePath,
+ Document = document,
+ };
+ return ExecuteService(externalData);
+ }
+
+ public bool ExecuteService(DesignAutomationExternalData externalData)
+ {
+ var service = ExternalServiceRegistry.GetService(ServiceId) as SingleServerService;
+ var result = ExternalServiceRegistry.ExecuteService(service.GetPublicAccessKey(), externalData);
+ Console.WriteLine($"ExecuteService: \t{result} \t{externalData}");
+ return result == ExternalServiceResult.Succeeded;
+ }
+
+ #endregion
+
+ #region Register/Unregister
+ public DesignAutomationSingleExternalServer Register()
+ {
+ var options = new ExternalServiceOptions()
+ {
+ IsPublic = true,
+ };
+ var privateKeyExecute = ExternalServiceRegistry.RegisterService(this, options);
+ return this.AddServer();
+ }
+ public DesignAutomationSingleExternalServer AddServer(IDesignAutomationExternalServer designAutomationExternalServer = null)
+ {
+ designAutomationExternalServer ??= this;
+ var service = ExternalServiceRegistry.GetService(ServiceId) as SingleServerService;
+ if (!service.IsRegisteredServerId(designAutomationExternalServer.GetServerId()))
+ {
+ service.AddServer(designAutomationExternalServer);
+ service.SetActiveServer(designAutomationExternalServer.GetServerId());
+ }
+ return this;
+ }
+ public DesignAutomationSingleExternalServer RemoveServer()
+ {
+ var service = ExternalServiceRegistry.GetService(ServiceId) as SingleServerService;
+ foreach (var guid in service.GetRegisteredServerIds())
+ {
+ service.RemoveServer(guid);
+ }
+ return this;
+ }
+ #endregion
+
+ #region ISingleServerService
+ public bool Execute(IExternalServer server, Document document, IExternalData data)
+ {
+ if (server is IDesignAutomationExternalServer designAutomationExternalServer)
+ {
+ return designAutomationExternalServer.Execute(data as DesignAutomationExternalData);
+ }
+ return false;
+ }
+ public bool IsValidServer(IExternalServer server)
+ {
+ return server is IDesignAutomationExternalServer;
+ }
+ #endregion
+
+ #region IDesignAutomationExternalServer
+ public bool Execute(DesignAutomationExternalData data)
+ {
+ return designAutomation.Execute(data.Application, data.FilePath, data.Document);
+ }
+
+ public Guid GetServerId() => ServerId;
+ #endregion
+
+ #region IExternalService
+ public string GetDescription() => GetType().Name;
+ public string GetName() => GetType().Name;
+ public ExternalServiceId GetServiceId() => ServiceId;
+ public string GetVendorId() => "ricaun";
+
+ ///
+ /// This method may only be invoked in a recordable service. Services registered as non-recordable never receive this call.
+ ///
+ public void OnServersChanged(Document document, ServerChangeCause cause, IList oldServers) { }
+
+ ///
+ /// This method may only be invoked in a recordable service. Services registered as non-recordable never receive this call.
+ ///
+ public DisparityResponse OnServersDisparity(Document document, IList oldServers) => DisparityResponse.ApplyDefaults;
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/ricaun.Revit.DA/ExternalServer/IDesignAutomationExternalServer.cs b/ricaun.Revit.DA/ExternalServer/IDesignAutomationExternalServer.cs
new file mode 100644
index 0000000..40c759d
--- /dev/null
+++ b/ricaun.Revit.DA/ExternalServer/IDesignAutomationExternalServer.cs
@@ -0,0 +1,10 @@
+using Autodesk.Revit.DB.ExternalService;
+using ricaun.Revit.DA.ExternalServer;
+
+namespace ricaun.Revit.DA.ExternalServer
+{
+ public interface IDesignAutomationExternalServer : IExternalServer
+ {
+ public bool Execute(DesignAutomationExternalData data);
+ }
+}
\ No newline at end of file
diff --git a/ricaun.Revit.DA/IDesignAutomation.cs b/ricaun.Revit.DA/IDesignAutomation.cs
new file mode 100644
index 0000000..e9b40bc
--- /dev/null
+++ b/ricaun.Revit.DA/IDesignAutomation.cs
@@ -0,0 +1,10 @@
+using Autodesk.Revit.ApplicationServices;
+using Autodesk.Revit.DB;
+
+namespace ricaun.Revit.DA
+{
+ public interface IDesignAutomation
+ {
+ bool Execute(Application application, string filePath, Document document);
+ }
+}
diff --git a/ricaun.Revit.DA/Loader/DesignApplicationLoader.cs b/ricaun.Revit.DA/Loader/DesignApplicationLoader.cs
new file mode 100644
index 0000000..0cc0063
--- /dev/null
+++ b/ricaun.Revit.DA/Loader/DesignApplicationLoader.cs
@@ -0,0 +1,87 @@
+using Autodesk.Revit.DB;
+using System;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+using System.Runtime.Versioning;
+
+namespace ricaun.Revit.DA.Loader
+{
+ internal static class DesignApplicationLoader
+ {
+ private static Assembly loadAssembly;
+ public static IExternalDBApplication LoadVersion(T designApplication) where T : DesignApplication
+ {
+ var type = designApplication.GetType();
+
+ var similar = AppDomain.CurrentDomain.GetAssemblies().Where(e => e.FullName == type.Assembly.FullName);
+ if (similar.Count() >= 2)
+ {
+ return null;
+ }
+
+ var location = type.Assembly.Location;
+ var revitAssemblyReference = type.Assembly.GetReferencedAssemblies().FirstOrDefault(e => e.Name.Equals("RevitAPI"));
+ var revitAssembly = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault(e => e.GetName().Name.Equals("RevitAPI"));
+
+ var revitReferenceVersion = revitAssemblyReference.Version.Major + 2000;
+ var revitVersion = revitAssembly.GetName().Version.Major + 2000;
+
+ Console.WriteLine("--------------------------------------------------");
+ Console.WriteLine($"DesignApplicationLoader: \t{revitVersion} -> {revitReferenceVersion}");
+
+ for (int version = revitVersion; version > revitReferenceVersion; version--)
+ {
+ var directory = Path.GetDirectoryName(location);
+ var directoryVersionRevit = Path.Combine(directory, "..", version.ToString());
+ var fileName = Path.Combine(directoryVersionRevit, Path.GetFileName(location));
+
+ //Console.WriteLine($"DesignApplicationLoader Try: \t{version}");
+
+ if (File.Exists(fileName))
+ {
+ fileName = new FileInfo(fileName).FullName;
+ Console.WriteLine($"DesignApplicationLoader File Exists: \t{fileName}");
+ Console.WriteLine($"DesignApplicationLoader Version: \t{version}");
+ Console.WriteLine($"DesignApplicationLoader LoadFile: \t{Path.GetFileName(fileName)}");
+ AppDomain.CurrentDomain.AssemblyResolve += LoadAssemblyResolve;
+ loadAssembly = Assembly.LoadFile(fileName);
+ break;
+ }
+ }
+
+ Console.WriteLine("----------------------------------------");
+
+ if (loadAssembly is not null)
+ {
+ var loadType = loadAssembly.GetType(type.FullName);
+
+ Console.WriteLine($"DesignApplicationLoader Type: {loadType}");
+ Console.WriteLine($"DesignApplicationLoader FrameworkName: \t{loadType.Assembly.GetCustomAttribute()?.FrameworkName}");
+ Console.WriteLine("----------------------------------------");
+
+ return Activator.CreateInstance(loadType) as IExternalDBApplication;
+ }
+
+ return null;
+ }
+
+ private static Assembly LoadAssemblyResolve(object sender, ResolveEventArgs args)
+ {
+ var assemblyName = new AssemblyName(args.Name);
+ var assemblyPath = Path.Combine(Path.GetDirectoryName(loadAssembly.Location), assemblyName.Name + ".dll");
+ if (File.Exists(assemblyPath))
+ {
+ var folderName = Path.GetFileName(Path.GetDirectoryName(assemblyPath));
+ Console.WriteLine($"AssemblyResolve LoadFile: {folderName}\\{assemblyName.Name + ".dll"}");
+ return Assembly.LoadFile(assemblyPath);
+ }
+ return null;
+ }
+
+ public static void Dispose()
+ {
+ AppDomain.CurrentDomain.AssemblyResolve -= LoadAssemblyResolve;
+ }
+ }
+}
\ No newline at end of file