Today I discuss the next step in my IfcSpaceZoneBoundaries project.
This step deals with a completely different aspect than the previous discussions: how to prepare a Revit add-in for use in the DA4R or Design Automation for Revit environment, which entered public beta just last week.
I spent a considerable part of last weekend getting the DA4R version ready for testing, and figuring out how to best handle it in the VS solution:
First, let's reiterate and update description of the architecture and data flow.
The add-in functionality that we wish to make use of in the DA4R environment consists in opening a Revit file, extracting the room and zone data stored in it and saving that to CSV.
The Revit document is actually an .ifc.RVT
file created by the IFC linking-in process, currently executed on the desktop.
As it stands now, the .ifc.RVT
file has to be generated by linking in the IFC file using a standard Revit add-in on the desktop, because the required functionality provided by the revit-ifc open source is not yet available in DA4R:
.ifc.RVT
file..ifc.RVT
can be uploaded to DA4R and processed there. This requires no IFC functionality.Unfortunately, in this scenario, the IFC linking-in step takes place on the desktop, not in DA4R. Since the add-in implements it, it can be fully automated. A more direct solution would be to implement an IFC parser and extract the required information directly from there, bypassing the Revit conversion for that step.
Please also refer to these previous discussions leading up to where we are with this today:
IfcZone
elements using PythonI spent some thought on how to best prepare the existing add-in project for creating the DA4R version.
Create a separate branch in GitHub? Define compile-time switches? Implement a multi-target Visual Studio project?
In the end, I decided that the simplest and most obvious approach would probably be best:
Exporter
class libraryExporter
to do all the workAccordingly, the Visual Studio solution now contains three projects:
Both the desktop add-in and the Forge DA4R AppBundle
use the same Exporter
class library and only contain minimal architectural code.
In addition to the separation between desktop add-in and DA4R appbundle, the latter can also be switched back and forth for local testing versus real live Forge DA4R deployment, by defining or undefining the constant FORGE_DA4R_TEST_LOCALLY
in AppBundle/App.cs.
Here is a diff between the two states.
Depending on which state we are in, we use either the ApplicationInitialized
or the DesignAutomationReadyEvent
to run the application functionality.
Here is the entire external DB application implementation:
//#define FORGE_DA4R_TEST_LOCALLY #region Namespaces using System; using System.IO; using System.Reflection; using Autodesk.Revit.ApplicationServices; using Autodesk.Revit.DB; using Autodesk.Revit.DB.Events; using DesignAutomationFramework; using IfcSpaceZoneBoundaries.Exporter; #endregion namespace IfcSpaceZoneBoundaries.AppBundle { class App : IExternalDBApplication { /// <summary> /// Export all linked-in IFC document rooms and zones /// </summary> int ExportLinkedInIfcDocs( Application app ) { if( 0 == app.Documents.Size ) { string path = JtSettings.Instance .IfcRvtInputFilePath; Document doc = app.OpenDocumentFile( path ); if( doc == null ) { string s = string.Format( "Could not open document {0}.", path ); JtLogger.Log( s ); throw new InvalidOperationException( s ); } } return RoomZoneExporter.ExportAll( app ); } #if FORGE_DA4R_TEST_LOCALLY void OnApplicationInitialized( object sender, ApplicationInitializedEventArgs e ) { // `sender` is an Application instance: Application app = sender as Application; ExportLinkedInIfcDocs( app ); } #else // if not FORGE_DA4R_TEST_LOCALLY private void OnDesignAutomationReadyEvent( object sender, DesignAutomationReadyEventArgs e ) { // `sender` is an Application instance: Application app = sender as Application; ExportLinkedInIfcDocs( app ); } #endif // FORGE_DA4R_TEST_LOCALLY public ExternalDBApplicationResult OnStartup( ControlledApplication a ) { string path = Assembly.GetExecutingAssembly().Location; JtLogger.Init( Path.ChangeExtension( path, "log" ) ); JtSettings.Init( Path.ChangeExtension( path, "json" ) ); #if FORGE_DA4R_TEST_LOCALLY a.ApplicationInitialized += OnApplicationInitialized; #else // if not FORGE_DA4R_TEST_LOCALLY DesignAutomationBridge.DesignAutomationReadyEvent += OnDesignAutomationReadyEvent; #endif // FORGE_DA4R_TEST_LOCALLY return ExternalDBApplicationResult.Succeeded; } public ExternalDBApplicationResult OnShutdown( ControlledApplication a ) { JtSettings.Save(); JtLogger.Done(); return ExternalDBApplicationResult.Succeeded; } } }
I implemented the JtSettings
class to demonstrate defining and passing in input parameters to a DA4R app via an input parameter file.
I simplified and minimised its interface to reduce the amount of code to be duplicated in the add-in and appbundle, e.g., by converting it to a self-contained singleton.
The interface to read the user-defined settings in DA4R currently consists of just two lines of code, as you can see above:
JtSettings.Init
specifying the path to read fromstring path = JtSettings.Instance .IfcRvtInputFilePath;
Similarly to the enhanced settings class, I also streamlined the implementation of the JtLogger
warning, error and result message logging system to make it easy and minimal to use from the two environments.
I hope you enjoyed my analysis and description and wish you lots of fun and success getting started with your own DA4R projects!