Today, a special titbit for you:
How can I access Revit functionality from an external process?
As we have underlined many times in the past, the Revit API does not support any kind of asynchronous access.
The Idling event, however, provides a possibility to work around this limitation.
Daren Thomas, the author or RevitPythonShell, even presented a neat and clean pattern for semi asynchronous Idling API access.
Now Victor Chekalin, aka Виктор Чекалин, presents another very neat sample providing such an access. He says:
I have an idea how to access Revit from an external application.
The only official way to achieve this is through the Idling event and external events, of course.
The main idea:
- Inside Revit I create a service which receives queries from a stand-alone external process.
- The service receives a command from the external process and pushes it to the command pool.
- The Idling event handler looks at the command pool; if a command is waiting in the pool, it will be executed in the Idling event handler.
There is no risk. All commands are executed in the one and only Revit thread.
Here is the scheme of work:
In this implementation, I am using a WCF service. It would be equally possible to use any other technology or framework to handle the inter-application communication.
Everything you need to know about creating and using a WCF service is described in the WCF Getting Started Tutorial.
I created a simple demonstration project to test this idea. It implements the following minimal service demonstrating that we can access the Revit API, both to query it for information and execute and action that modifies the current BIM:
namespace RevitExternalAccessDemo { [ServiceContract] public interface IRevitExternalService { [OperationContract] string GetCurrentDocumentPath(); [OperationContract] bool CreateWall( XYZ startPoint, XYZ endPoint ); } }
It further requires:
- An external application to manage the Idling event subscription.
- An implementation of the service defined above.
- Two helper classes:
- One to contain the queued up tasks and handle locking issues.
- The other to redefine an own representation of the Revit API XYZ point class.
Here is a short video showing how it works:
I am happy to make the project available for everyone in its current state.
The source code is available on Github, both directly and as a Github zip archive file.
It is just a demo and provided as is.
To launch it on your computer, you must run Revit as administrator, because this is required by the WCF service.
Remember, Revit must be idling to launch a command from the external application.
In this kind of situation, calling the SetRaiseWithoutDelay method is of utmost importance. Without the call, the demo pauses. Even with it, some delay exists, and the CPU load grows to over 25% even if no operations are active in Revit.
A better solution exists – follow Arnošt Löbel suggestion: subscribe to the Idling event only when you really need it, and unsubscribe immediately afterwards, as soon as possible.
Obviously, we need to handle Idling only when we have a request queued up from the WCF service. The idea would be to subscribe to the Idling event when we receive a request from the WCF Service and unsubscribe when it has been processed.
Unfortunately, I have not yet implemented this idea in the code presented here.
Here are some sample code snippets from the current implementation.
First, here is the main external application implementation including:
- OnStartup, which creates the WCF service and subscribes to the Idling event.
- OnIdling, the Idling event handler, which dequeues and executes the next task.
- OnShutdown, unsubscribing and cleaning up.
class App : IExternalApplication { private const string serviceUrl = "http://localhost:56789/RevitExternalService"; private ServiceHost serviceHost; public Result OnStartup( UIControlledApplication a ) { a.Idling += OnIdling; Uri uri = new Uri( serviceUrl ); serviceHost = new ServiceHost( typeof( RevitExternalService ), uri ); try { serviceHost.AddServiceEndpoint( typeof( IRevitExternalService ), new WSHttpBinding(), "RevitExternalService" ); ServiceMetadataBehavior smb = new ServiceMetadataBehavior(); smb.HttpGetEnabled = true; serviceHost.Description.Behaviors.Add( smb ); serviceHost.Open(); } catch( Exception ex ) { a.ControlledApplication.WriteJournalComment( "Could not start WCF service.\r\n" + ex.ToString(), true ); } return Result.Succeeded; } private void OnIdling( object sender, IdlingEventArgs e ) { var uiApp = sender as UIApplication; Debug.Print( "OnIdling: {0}", DateTime.Now.ToString( "HH:mm:ss.fff" ) ); // Be careful! This loads the CPU: e.SetRaiseWithoutDelay(); if( !TaskContainer.Instance.HasTaskToPerform ) return; try { Debug.Print( "Start execute task: {0}", DateTime.Now.ToString( "HH:mm:ss.fff" ) ); var task = TaskContainer.Instance.DequeueTask(); task( uiApp ); Debug.Print( "Ending execute task: {0}", DateTime.Now.ToString( "HH:mm:ss.fff" ) ); } catch( Exception ex ) { uiApp.Application.WriteJournalComment( "RevitExternalService. An error occured " + "while executing the OnIdling event:\r\n" + ex.ToString(), true ); Debug.WriteLine( ex ); } } public Result OnShutdown( UIControlledApplication a ) { a.Idling -= OnIdling; if( serviceHost != null ) { serviceHost.Close(); } return Result.Succeeded; } }
Secondly, here is part of the RevitExternalService definition including one of its method implementations:
class RevitExternalService : IRevitExternalService { private string currentDocumentPath; private static readonly object _locker = new object(); private const int WAIT_TIMEOUT = 10000; // 10 seconds timeout public string GetCurrentDocumentPath() { Debug.Print( "Push task to the container: {0}", DateTime.Now.ToString( "HH:mm:ss.fff" ) ); lock( _locker ) { TaskContainer.Instance.EnqueueTask( GetDocumentPath ); // Wait when the task is completed Monitor.Wait( _locker, WAIT_TIMEOUT ); } Debug.Print( "Finish task: {0}", DateTime.Now.ToString( "HH:mm:ss.fff" ) ); return currentDocumentPath; } private void GetDocumentPath( UIApplication uiapp ) { try { currentDocumentPath = uiapp.ActiveUIDocument.Document.PathName; } finally { // Always release locker in finally block // to ensure to unlock locker object. lock( _locker ) { Monitor.Pulse( _locker ); } } } public bool CreateWall( XYZ startPoint, XYZ endPoint ) { // . . . } }
For completeness' sake, here is also VCRevitExternalAccessDemo.zip containing my personal version of the code, mainly for my own convenience :-)
Many thanks to Victor for very clear and efficient sample implementation!
By the way, don't miss the additional neat little feature demonstrated by the sample code above, the use of the Application WriteJournalComment method to add a comment to the Revit journal file.
Programmatic Creation of a Structural Engineering Plan
Saikat Bhattacharya discusses the details and provides sample code to create a new Revit EngineeringPlan view using the static ViewPlan.Create method and a ViewFamily.StructuralPlan ViewFamilyType.
Hi Jeremy,
I have implemented this pattern before and I noticed that one of my CPU cores was running at 100% when using e.SetRaiseWithoutDelay(). As you know, this is because Revit is constantly raising the Idling event. I would only suggest a minor change to the code as below:
if( !TaskContainer.Instance.HasTaskToPerform )
Threading.Thread.Sleep(1000);
return;
Though, this would increase latency by a second. You can try different values of sleep and see how it affects the CPU usage.
Posted by: KR | November 01, 2012 at 10:48
Dear KR,
Thank you for your valid and helpful suggestion.
It all depends on your needs, of course: immediate response to the Revit task request versus overall load on the CPU.
When in heaven's name are we ever going to be allowed to keep our cake and eat it too?
Cheers, Jeremy.
Posted by: Jeremy Tammik | November 01, 2012 at 11:05
Jeremy,
I was hoping that we could do more with the ExternalEvent when it was introduced with 2013. As you know, it does require the user to be active in order to be raised. It would be nice if this was not the case. Hopefully in 2014.
KR
Posted by: KR | November 01, 2012 at 11:08
Dear KR,
What is 2014? Never heard of it!
I do not expect any changes in the external events at all.
If you want any, please make sure a wish list item is submitted.
Cheers, Jeremy.
Posted by: Jeremy Tammik | November 01, 2012 at 11:11
Hello, KR.
I also don't think that ExternalEvent would help. External Events are internally still based on Revit idling
In my opinion the only correct subscribing and unsubscribing idling event can avoid CPU loading.
Regards, Victor.
Posted by: Account Deleted | November 01, 2012 at 23:04
Hi all,
there are sitations where OnIdling event won't be fired.
Think of error or warning dialogs.
In fact, each dialog which remains opened will block the event.
Additionally, there are types of views in which no transactions can be performed, for example all 3D views (excepting "{3D}") plus list views.
If you want Revit to show an element, think of handling the "Looking for good view" dialog which needs to be closed before proceeding, anyway.
Best regards,
Rudolf
Posted by: Rudolf Honke | November 05, 2012 at 03:14
Dear Rudolf,
I think you do not mean *all* 3D views except "{3D}", only all *perspective* views.
Cheers, Jeremy.
Posted by: Jeremy Tammik | November 05, 2012 at 06:41
Hi Jeremy,
yes, there are some sorts of Views which prevent the user from executing commands.
Perspective views, for example.
Buttons are greyed out/disabled if such a view is the active one (this is also version depending, e.g. in Schedule Views, you can execute commands in 2013 but not in 2012).
Thus, the service must check if any command can be performed at all.
Or think about the zero document environment...
Cheers,
Rudolf
Posted by: Rudolf Honke | November 05, 2012 at 07:40
Dear Rudolf,
Thank you for the valuable warnings.
Also, please be aware that the API can very well be active and used in a zero document context:
http://thebuildingcoder.typepad.com/blog/2011/02/enable-ribbon-items-in-zero-document-state.html
Cheers, Jeremy.
Posted by: Jeremy Tammik | November 05, 2012 at 08:37
Hi Jeremy,
I know very well that API may be *active* in zero document environment.
But of course there is no document database to work with, so there is no much sense in performing commands on this level, except opening files or returning some application level data, e.g. Revit product info, version etc., of course.
Best regards,
Rudolf
Posted by: Rudolf Honke | November 05, 2012 at 08:55
Dear Rudolf,
You could open an unlimited number of *background* documents. That should give you enough to play with. Who needs a user interface, or a human user, for that matter? Just another source of errors and confusion. :-)
Cheers, Jeremy.
Posted by: Jeremy Tammik | November 05, 2012 at 09:14
Hi Jeremy,
I've done *both* so far:
driving Revit from outside (11/12/13) and creating/opening (family) documents in the background.
When editing documents, it may happen that user interaction is needed.
We cannot handle all possible errors by the failure API (many, many BuiltInFailure definitions, and think about overriding the default settings accidentally; in fact we could suppress even severe errors without the user even noticing it).
So of course I who needs a user if you could do the job using a script ;-)
But no, noo, there will always some dialogs which must be handled by user.
Auto-clicking them, will be difficult because of localization issues and because of the fact that Revit (and its plug-ins) may use a mixture of WPF and WinForm controls. See UIAutomation for further information ;-)
Cheers,
Rudolf
Posted by: Rudolf Honke | November 05, 2012 at 09:36
Hi
This program is in revit 2013? and we should setup the revit sever 2013?
Posted by: Albert | August 23, 2014 at 07:49
Hi!!
I'm really want your help!!!!!!
I saw your blog (url:http://thebuildingcoder.typepad.com/blog/2012/11/drive-revit-through-a-wcf-service.html). Downloading the code, I installed the Revit 2013 in window sever2012 R2, Anything I tried!!!!!
But there is a problen "RevitExternalAccessClient.exe cannot connect Revit & revit has no response"
like this:http://localhost:56789 is a incorrect web site, Reject connection.127.0.0.1:56789. System.Net.Exception cannot connect to sever......................................................................
Can You Help Me???? PLEASE!!!!!!
Posted by: Albert | August 24, 2014 at 09:55
Dear Albert,
What we present here is not so much a program as a programming solution.
You can implement your own solution based on this in any version of Revit that you like. You do need it to support the Idling event or external events, though. Revit 2013 or anything newer than that is fine.
Revit Server is not required. This has nothing whatsoever to do with Revit Server.
Cheers, Jeremy.
Posted by: Jeremy Tammik | August 25, 2014 at 13:20
Dear Albert,
Please do not yell.
Thank you.
No sorry, I cannot provide much advice on this, beyond underlining that the example you refer to is a programming example.
Mainly, it presents source code that you can reuse in your own programming efforts.
You can implement your own solution based on it in any version of Revit that you like.
You do need it to support the Idling event or external events, though. Revit 2013 or any later version is fine.
I hope this helps.
Cheers, Jeremy.
Posted by: Jeremy Tammik | August 26, 2014 at 02:46