Here is a detailed description of one of the most exciting Revit 2014 API features that you should have heard about by now and promises numerous uses, prompted by a question from a developer on duplicating views from one document to another.
Another couple of uses of it cropped up in the past, so it seems like an overview is called for:
Question: I need to perform the following action programmatically:
Insert Tab > Insert from File > Insert view from file > Select File > Select view in this file.
Is there some way to achieve this via API in Revit?
Answer: Yes, you are in luck, this is covered by the new Revit 2014 API.
And even more luckily, the development team selected very good and suitable API samples, so the exact workflow you describe is covered by the new Revit 2014 DuplicateViews SDK sample illustrating the new copy and paste API.
Look at the second bullet item in the overview of
Revit 2014 API news,
mentioning the new copy and paste API.
Here is the description from the
What's New section of
the Revit API help file RevitAPI.chm, reproduced here:
support copy and paste of arbitrary elements. The first overload supports copy within documents, or from document to document. The second overload also support copying within one document or between two documents, but specifically supports copy and paste of view-specific elements.
The DuplicateViews SDK Sample
DuplicateViews copies and pastes drafting views and schedules from one document to another , demonstrating use of the CopyPasteOptions, ElementTransformUtils, FilteredElementCollector, IDuplicateTypeNamesHandler and IFailuresPreprocessor classes. The drafting view contents are also copied.
The DuplicateAcrossDocumentsCommand.cs module implements an external command to copy schedules and drafting views across documents and makes use of a number of useful utility classes defined in DuplicateViewUtils.cs.
In detail, DuplicateViews copies all drafting views, image views, and schedules from the currently active document into another document that is currently in memory. All drafting elements in the non-schedule views are also copied, as is all schedule formatting and filtering. It uses API tools to hide any duplicate types warnings that typically arise when doing this from the user interface.
To test this, open any two project documents in Revit. Set the active document to be the document from which you wish to copy views. Launch Add-ins > CopyPaste > Duplicate across documents and enjoy. A popup message will appear listing the number of schedules, drafting views and drafting elements copied by the command.
A couple of times, people mentioned issues caused by copied files, resulting in copied unique ids on certain elements, e.g. thus:
Question: Do you have any recommendation on how to handle unique ids in copied files?
We cannot tell people 'do not copy the files', because they sometimes put in a large effort to create a file that is used as a basic starting point for multiple projects.
Answer: Yes, you are in luck again. The copy and paste API should solve this.
Documents that are copied obviously contain exact copies the ids of their elements.
This also can happen when using a document as a template to create another document.
Elements created by copying them through the copy and paste API obviously are assigned new unique ids.
That should solve the issue.
Of course, there is no way to change the unique id of an element that already exists.
Managing Materials in Adsklib or Template Files
Question: I would like to know if it is possible to access data stored in adsklib files through API functions.
The desired functionality would be to load materials, appearance, physical and thermal assets from an external adsklib file to the current active project document.
The aim is to implement a custom materials library with our application using an adsklib file instead of a project template,
as described in the Autodesk wiki on
managing material libraries,
so only the required materials are included and not all, to decrease the project template size and speed up the material browser.
Is this possible? If so, how?
Answer: Nope, sorry, as far as I know, there is no API support for this.
From my naive grass roots perspective, the adsklib idea was born quite a few years back and has since been displaced by visions of cloud and mobile.
The file formats have died, and long live the new format, ubiquitous JSON or whatever.
You say you "have been looking into this subject for some time already" ... maybe it is time to start looking into something new?
I see two possibilities for moving forward, besides the solution that you mention of including all materials in the template file:
Store the materials in several separate adsklib files, and only load the ones you really need.
Use the new Revit 2014 copy and paste API functionality to copy and paste only the required materials from your or some other project.
Response: Thank you for thinking of other solutions.
Option 1 probably still requires API access to the adsklib file to programmatically transfer the material assets from the adsklib into the project.
I had a quick look at the copy and paste functionality and think this would be a good solution.
We could use a separate project or template file as our material library instead of the adsklib one and copy/paste the required materials into the current project.
Transferring Groups Between Projects
I have been working with the Revit API to create a Revit application that can transfer a group type from one project file to another. I am working on a very large project, so we have to separate it into several project files due to performance. We are using groups to ensure that rooms having the same characteristics always are the same and can be updated quickly. But the model separation creates a problem to ensure that all group types in all project files are updated. We tried to use the link functionality instead of groups, but when there are many instances of a link Revit become very slow. We also considered using family nesting to solve the problem, but we want to be able to include system families as well, so that solution won't work.
We have one project file where we create the Groups, use 'Save Group' to create a project file, then 'Load Group' or 'Reload Group' to get it in to the actual project file (we have 21 project files). So what I want is to create a Revit application that optimizes this workflow by transferring groups from one project file to another. But working with the API has raised a couple of questions:
Why are the user interface functions 'Load Group', 'Save Group', 'Reload group' not accessible from the API? Are there any plans to make them accessible?
If I want to transfer a group type from one project file to another, is the only way to do this to create every group member in the receiving project file using the API functions for every member, e.g. Wall.Create and then group the members?
Is there a way to transfer system families from one project file to another using the API? The user interface function transfer project standards is not available, are there any plans to make it available?
It seems like Revit is more appropriate for small projects, there are some plans to make Revit better for larger projects? For instance by sharing families (system and loadable) and groups across project files?
Answer: I am surprised that you say that Revit is more appropriate for small projects. I was under the impression that the opposite is true.
You may be surprised to hear that the answer to all your questions is yes.
You will be glad to hear that a number of improvements in Revit 2014 are specifically targeted at supporting still larger projects, and many of these improvements specifically target the family management and loading performance.
Regarding all of your other questions, I have one single big suggestion to make to you: please take a look at Revit 2014, and especially at the new copy and paste API functionality it provides.
I think that may meet your needs exactly and efficiently.
Response: I took a look at the Revit 2014 API and I must say that the copyElement method is just what I need ;-)
It is a much safer way to transfer groups from one project file to another.
Revit API use in Modeless Form Throws an Exception
Finally, on a completely different topic, several people making illegal use of the Revit API from an invalid context in Revit 2013, e.g. in a modeless form, are now running into a problem.
David Rock describes it in his
comment on
accessing the Revit main window handle:
Question: In Revit 2014 I'm having trouble showing a modeless form and calling a transaction whilst inside the form. It is producing an error stating that "starting a transaction from an external application running outside of API context is not allowed".
Answer: Yes, it looks as if you are calling the Revit API directly from your modeless dialogue running in another thread.
As you should know, actually, that was never supported.
Now, in Revit 2014, an exception is raised when you make such an attempt.
You are lucky that it worked so far without corrupting anything.
The solution is described in depth here on the blog: simply google for "modeless site:thebuildingcoder.typepad.com", or look at the discussions in the
Idling category.
In short, make use of the Idling event, or implement an external event, which is a simplified wrapper around that, and base you application on the ModelessForm_ExternalEvent and ModelessForm_IdlingEvent SDK samples.
One of my tasks today and tonight will be a minimal little contribution to an internal ADN meeting, where I was asked to explain briefly to my colleagues in just a couple of minutes what my job involves and what I have been up to for the last couple of months.
Tonight, since we have a session each for the western and eastern hemisphere, and the eastern one will require me to set my alarm for 2:30 tomorrow morning.
I thought I might as well share the result here with you also, to provide a rough idea for all of what my everyday work looks like.
Further down I'll also mention two recent cases, dealing with:
My spontaneous picture of my work life is that I never do anything at all except dealing with ADN cases and writing The Building Coder blog.
My main motivation is probably solving problems, but maybe even stronger than that an urge to help people in whatever manner possible.
Unfortunately for me, I seem to often find it more helpful just to shuffle information around that I receive from others, rather than performing my own research.
I would love to spend more time programming and exploring new issues myself, rather than managing information provided by others.
I have a large number of other interests besides that, such as reading, languages, nature, sports, especially climbing, dancing,
5rhythms and
dj-ing.
I also have four grown up kids who have left home by now and are out in the world doing their own things, such as
composing their own music.
Job-wise, I recently switched from a Windows computer to a Mac and still enthusiastic about that, busy retraining my fingers and compiling open source software.
I am a keyboard user and avoid touching the mouse or using the GUI whenever possible.
It will take much longer to train my fingers to all the new shortcuts, but they are starting to adapt.
I am overjoyed at having a Unix command console now again, after a couple of decades of cmd.exe.
When I offered to talk, I said that I would only be able to cover 30 seconds, since all I do is handle cases and blog, and that is quickly said.
Of course I also do some work creating presentations and conducting trainings at conferences and events such as
AU
(2,
3) or
DevCamp
(2,
lab).
Jim suggested I could mention my visit to Australia.
Yes, indeed, I went there and gave a
Revit APItraining,
followed by a
three-day DevLab.
It really was a great learning experience and good fun.
Some of the participants were extremely well versed in the Revit product usage, and the importance of knowing the product in depth before embarking on any add-in development plans was strongly reinforced.
In spite of that, I spent a while with one of the participants discussing how to implement a family and a command to toggle the family instance state between two conditions, until we discovered that we could save hundreds of lines of code, family complexity, workflow development and user training by just using the existing functionality already provided by Revit: define two different types for the two states, and let the user switch via the instance properties using standard built-in tools – a very impressive experience, cool.
What I Actually Did
That said, I do indeed spend almost all my time with cases and the blog.
In 2012 ADN provided a couple of hands-on classroom trainings.
The trend in some highly developed areas of the world seems to be going away from hands-on face-to-face trainings and meetings and moving towards virtual interactions and blogging instead.
So, to answer the question more precisely, I put together some statistics on the time I spent on various tasks in the year 2012 in per cent:
44% – ADN cases, special support cases, learning new Revit API features cases, newsletter articles
14% – Admin including meetings and participating in trainings
13% – Blogging
6% – Providing hands-on Revit API training
6% – Other stuff, including cloud & mobile, plug-in development, Mac learning and setup
I was a bit surprised by some of these results and find them interesting.
Maybe you do too.
The results are rather skewed, actually, since a lot of the work I do for the blog stems from case answering, trainings, and conference preparations, so maybe it would be more realistic to classify something between a fifth and a third of the case, conference and training work as blogging instead.
Basically, the intention of the blog is to eliminate the need for questions via ADN cases and discussion forum threads, and simultaneously the latter feed the former.
You could even go to the extreme of saying teat the blog is a filter for everything I do: if it is useful for the blog and can be shared in public, it is worthwhile doing; if not, not.
Therefore, let's return to the Revit API and present some notes on the two cases I mentioned above.
Determining Wall Layer Materials, Areas and Volumes
I recently started looking at foreign language cases as well as English ones, just for fun.
I am very impressed by the advances in automatic translation.
Here is a question on wall layers:
Simply passing that though Google translate yields the following:
How to get the wall thickness of the material, the name and other attributes?
I would like to ask the next wall of layered materials able to get what information?
I PickObject wall (the wall externally with bricks and metal studs keel composite wall) Element.
By Element.Materials access to MaterialSet, traverse various Material available wall material.
According Element.GetMaterialArea (Material), Element.GetMaterialVolume (Material) to obtain the volume and surface area of the Material.
Separate wall volume / surface area of the Material thickness is correct?
Intersecting walls, curved wall?
API can directly access to the thickness of the Material value?
Answer: Just as you say, the GetMaterialArea and GetMaterialVolume methods will return the volume and surface area of specific materials used within a wall.
This should work just fine for curved and intersecting walls as well.
You can also traverse the wall compound structure yourself to obtain more detailed information.
Here is my very first discussion exploring
wall compound layers.
If you need more exact values than those returned by the built-in API methods, you can also split the wall into separate parts for a detailed analysis.
This apparently helped, at least to get started in further exploration of this topic.
Determining Whether a Transaction is Open
The other case was in English right from the start:
Question: My Revit add-in presents a modeless form to my users.
How can I check from that context whether any other commands or transactions are open in Revit?
For example, the 'insert doors' command may be active.
If Revit is already waiting for user input, e.g. to insert a door, trying to start a transaction causes an error.
It would be useful to be able to check whether a transaction is already in progress.
Answer: As you hopefully know, the implementation to handle the Idling event needs to be really clean and clear and is a little bit tricky.
Therefore, I strongly suggest basing anything you do on the ModelessForm_IdlingEvent and ModelessForm_ExternalEvent SDK samples introduced in Revit 2013.
If you implemented your Idling event handler in any other way in the past, it might be a good idea revisiting it now and comparing notes with those samples.
Also note that using an external event is often a little bit simpler and more appropriate than using an Idling event handler.
Please look at the implementation of
my own tooltip for a more detailed explanation.
Basically, you never, ever, need to check for yourself whether a transaction is open at all.
Why? Because, to check whether a transaction is open, you need to use the Revit API. You cannot use the Revit API, though, unless you are in a valid Revit API context.
Mostly, when you are in a valid Revit API context, it is clear whether or not you can start a new transaction, since it depends to a large extent on the event handler that is currently being called.
Therefore, if your requirement is what I imagine it to be, I would simply suggest that you implement whatever you want to achieve based on the Revit SDK samples mentioned, and you will not need any further checks whether or not you can start a transaction, since it will be obvious: when your Idling event handler or external event Execute method is called, you can do what you like, and at all other times you can do nothing at all
Of course, in addition to being allowed to start a transaction at all, you may also have some restrictions due to read-only project documents etc., that also need to be gracefully dealt with.
Two methods are provided which may help with this: Document.IsModifiable andDocument.IsReadOnly.
Look at the Revit API help file RevitAPI.chm for further oinformation on their application and usage.
The Application IsQuiescent Property
In addition to this, the Revit API does provide the Application.IsQuiescent property to check whether the application is quiescent.
Unfortiunately, this method is not really useful for anything at all in the API right now.
Firstly, it is not at all related to transactions, thus one cannot make any assumption about starting a new transaction based on this.
If any document in the current application is in editing mode or an in-place family is being edited, it returns false.
Otherwise, true will be returned.
That might correspond pretty well to what you are looking for.
In reality, this property is by far not as useful as the two other ones mentioned above.
So, secondly, what it does tell you is whether you are currently in a command or edit mode or not.
However, as pointed out above, as an API programmer, you already know that.
For example, in your own external command, the method would return False, all the time.
Ditto in many events and updaters.
On the other hand, it should return True when called from Idling event, which again is obvious.
Just as a background information, this method was implemented in a very early attempt to mimic the AutoCAD API.
However, due to significant differences, the quiescent state is not equally important in Revit, and thus the method ends up not actually being used in any Revit code.
Response: Thank you, that helped.
I'm making a 'Content Library' function as a modeless form in Revit.
The form is intended to be open beside Revit when users work.
It provides content (RFA families) either by browsing by category or searching on family name, family type name, parameter name or parameter value on each of the family types.
The families are located by default on a website using REST. The database behind the website contains information about the family types, its parameters, and also thumbnails and a DWFX file of the RFA.
For users with slow internet connections I made it possible to change provider to connect to content in a local database instead of the online website.
The above worked fine until I implemented SendKey Esc x 2 to cancel insert family in Revit.
When you mentioned the Idling event I looked through my code again and found the 'load family into Revit' function called from outside the Idling event.
After moving this call into the Idling event handler all worked fine. :-)
Thursday was another very full and fruitful day at AU.
I gave my third and last class, the most exciting one in my eyes, on some UI and integration aspects.
I was unable to attend Iffat May's class introducing Python and using it in Revit and Vasari via RevitPythonShell, but I love her material so much I am including it anyway.
Adam Nagy presented a class addressing cloud and mobile development and the Revit Server API.
Finally, Arnošt Löbel provided a peek behind the scenes and into the depths of the fundamental frameworks underlying the Revit API.
I am including the class materials below, for your and my own convenience, and also to ensure that all this juicy stuff really is picked up and returned by Internet search engines.
Iffat provides an entire introductory tutorial to the Python programming language, and then shows how to apply it to solve a number of useful tasks in Revit and Vasari.
She makes use of Daren Thomas'
Revit Python shell,
originally implemented for Revit 2010, then for
Vasari,
Revit 2012 and Vasari 2.1,
and now available for Revit 2013.
RevitPythonShell introduces interactive scripting ability to Revit and Project Vasari.
Designers now have the ability to interactively design and manipulate Revit elements using algorithms and computational logic.
The class explores the Python structures, variables, data types, and flow control, and shows how to use them to create scripts to control Revit elements dynamically.
This class takes a deeper look at the new user interface and add-in integration functionality provided by the Autodesk Revit 2013 API.
It covers 2013 features including Options dialogue custom extensions using WPF components, subscription to Revit progress bar notifications, embedding and controlling a Revit view inside an add-in dialogue for preview purposes, the new drag and drop API, and the UIView.
This class is complementary to and expands on CP3272, a Snapshot of the Revit UI API:
Document management and View API
Revit progress bar notifications
Options dialogue WPF custom extensions
Embedding and controlling a Revit view
UIView and Windows coordinates
Drag and drop
From my point of view, one of the most exciting new features provided by the Revit 2013 UI API is the possibility to correlate between Windows screen device coordinates and Revit model coordinates provided by the UIView class, which I used to examine and implement my
own Revit tooltip,
which also makes use of the new ReferenceIntersector class and Idling event.
This has never previously been possible.
Now you can do anything you like using the Windows API and connect that intelligently with the underlying Revit model.
This class has two objectives: to introduce you to the Revit Server REST API and to introduce you to cloud and mobile programming.
Autodesk Revit Server is a server-based file storage system that we can use to store Revit files.
It has a public API that uses simple HTTP known as REST (or Representational State Transfer).
REST is a "style" for designing network applications, and it is used to communicate between the server and a client machine.
REST is simple, yet powerful enough. You can use it from anywhere where HTTP programming is available: from a desktop, a mobile device or a server-side application.
In this class, we discuss what REST is, using the context of Revit Server.
We then go one step further and show you how to create your own REST service in .NET.
We implement it as a notification system in the cloud and demonstrate with an Apple iPad/iPhone application in Apple iOS.
At the end of this class, you will be able to:
Use HTTP requests to interact with a REST API
Programmatically access and modify data available on Revit Server
A good understanding of core frameworks in the Revit API is a prerequisite for developing well behaved Revit add-ins.
Among the most important ones, the following frameworks play key roles in most applications:
External applications, commands, and events
Transactions phases
Regeneration and transaction modes
Updaters and other call-backs
Scoped objects and element validity
These concepts have been around for many releases, yet there are still facts about them that may not be completely understood.
This class summarizes the necessary basics, and also sheds some light on the behaviour that is normally hidden 'under the hood'.
Knowledge acquired during this class will help Revit developers to build more efficient, safer, and robust applications.
I recently pointed out that certain operations require
multiple transactions to
complete.
The dynamic model updater framework
DMU,
on the other hand, takes complete control over the transaction handling, making it impossible to incorporate such a multi-transaction operation.
The updater framework is designed to enable an add-in to incorporate its own additional behaviour into a running transaction to react to changes in the model.
The advantage is that the entire operation including the custom add-in behaviour is encapsulated in one single transaction.
This comes at a price, as said: the add-in has no control whatsoever over the transaction management.
It thus also obviously cannot add additional transactions into the process.
Let me present a powerful and generic workaround to this limitation, suggested by Rudolf Honke.
The element arrangement on terrain can be used to place trees, lampposts, and other objects to follow a curve and update automatically to changes in the curve:
The narration for these two videos is in German, by Mr Honke himself.
The dynamic insulation tool is also available in an English version, but that has not yet been uploaded.
Both of these tools generate and handle groups of elements and react dynamically to later changes of the model.
The reaction makes use of the DMU; there is no other choice.
However, the entire update operation requires ungrouping, modifying and regrouping of elements and cannot be achieved within the single transaction provided.
Additional transactions in the IUpdater would be required to handle this gracefully.
In lack of this, here is Rudolf's workaround:
In the updater, save a list of elements requiring update.
Subscribe to the Idling event.
In the Idling event handler, call the standard add-in command that has no restrictions on its transaction handling.
In the command, process the queued up elements and clear the list.
Unsubscribe from Idling.
In the actual implementation, the elements obviously maintain some data about their relationships, so the modification of an element can easily discover which other elements are affected.
Here are a few more details and several reasons why this is necessary:
The insulation tool needs to both delete and create new DetailCurves.
It also needs to create a new HermiteSpline which is not a DetailCurve at all, by the way.
By the way, accessing its ElementId during the insulation creation process requires a (temporary) DocumentChanged event, which is another topic that we will not explore any deeper here.
To avoid ForbiddenForDynamicUpdateExceptions, the creation is placed in a completely new transaction.
The dynamic fence and arranging-items-on-terrain tools require creation of new FamilyInstances, which should be avoided in the IUpdater context, as stated in the Revit API help file RevitAPI.chm.
The error handling needs to be able to use FailureHandlingOptions, which again means that the add-in has to have full access to the transaction itself.
The IUpdater context is fine for just setting parameters, but these tools need more than that, e.g. deleting elements, creating new ones, adding extensible storage data to handle the element connection logic, handling Failures, etc.
I recently ran into a different case where a developer was trying to make use of the InstanceVoidCutUtils class to insert a void on a modified wall within the updater Execute method.
This caused Revit to display an error message saying "Third party updater Xyz has experienced a problem and its action had to be canceled."
Removing the one single line triggering the void generation from the updater enables to operation to complete, but the void is still missing, of course.
That seems to me like a prime use case for Rudolf's approach as well.
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:
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.
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.
Here is my well-meant contribution to making this the most exciting Wednesday of your entire week.
For the first time in history, the Revit 2013 UIView class provides a possibility to convert back and forth between Revit model coordinates and Windows device screen points.
I am still a bit surprised that I have not yet received any questions about these exciting possibilities.
Well, anyway, that gives me a chance to finally dive into this exploration free of preconceptions.
Another Revit 2013 topic that I wanted to talk about for a long time is the new ReferenceIntersector class.
My previous attempts at publishing anything on it were thwarted by running into some unintended features which have meanwhile been attended to.
Now I can combine both of these topics into a single nice example, presenting my very own tooltips displaying any information I like based on the cursor location in the Revit BIM.
Does that sound cool, or what?
To give you an idea of what I am talking about, here is a
two-minute video depicting
a sample run demonstrating the final result, with the normal Revit tooltips and my own specialised ones displayed side by side.
To run the demo, I open up the basic architectural sample project, switch away from the perspective view, since add-ins are disabled there, select Level 2, turn on my own tooltips, hover around different elements to compare the Revit tooltip with my own one, and turn off my tooltips again:
Table of Contents
To display a useful tooltip, the user needs to be free to play around in the model.
Simultaneously, my add-in needs to be able to access the Windows cursor location, determine the corresponding Revit model coordinates from it, and query the Revit BIM to determine the information I would like to display.
This calls for an Idling event handler, adding a third topic to the two already mentioned above.
We thus end up with the following list, including hints on the respective approaches, solutions, and helper methods:
As you hopefully know by now, the implementation to handle the Idling event needs to be really clean and clear and is a little bit tricky.
I strongly suggest basing anything you do on the ModelessForm_IdlingEvent SDK sample.
If you implemented your Idling event handler in any other way in the past, it might be a good idea revisiting it now and comparing notes with that sample.
I based my WinTooltip external application on it, anyway.
It handles all the modeless form interaction in the external application implementation.
In my case, the modeless form is either a Visual Studio designer generated JtTooltipForm or a hand-built JtTooltipForm2.
The external application is instantiated as a singleton instance, and provides public access to that instance so that external commands can access it and use its functionality to show and hide the modeless form.
It implements an internal CloseForm method, public methods ShowForm and HideForm, called by the external commands, and the standard interface methods OnStartup and OnShutdown, like this:
///<summary>/// Singleton external application class instance.///</summary>internalstaticApp _app = null;
///<summary>/// Provide access to singleton class instance.///</summary>publicstaticApp Instance
{
get { return _app; }
}
///<summary>/// The tooltip form to display.///</summary>internalstaticJtTooltipForm2 _form = null;
///<summary>/// Dispose and null out form./// Return true if it was previously not disposed.///</summary>staticbool CloseForm()
{
bool rc = _form != null;
if( rc )
{
if( !_form.IsDisposed )
{
_form.Dispose();
}
_form = null;
}
return rc;
}
///<summary>/// Create and show the form, /// unless it already exists.///</summary>///<remarks>/// The external command invokes /// this on end-user request.///</remarks>publicvoid ShowForm( UIApplication uiapp )
{
// If we do not have a form yet, create and show itif( _form == null || _form.IsDisposed )
{
// Instantiate JtTooltipForm to use // the designer generated form.
_form = newJtTooltipForm2();
_form.Show();
// If we have a form, we need Idling too
uiapp.Idling += IdlingHandler;
}
}
///<summary>/// Hide the form.///</summary>///<remarks>/// The external command invokes /// this on end-user request.///</remarks>publicvoid HideForm( UIApplication uiapp )
{
if( CloseForm() )
{
// If the form was showing, we had subscribed
uiapp.Idling -= IdlingHandler;
}
}
publicResult OnStartup( UIControlledApplication a )
{
_app = this;
_form = null;
returnResult.Succeeded;
}
publicResult OnShutdown( UIControlledApplication a )
{
if( CloseForm() )
{
a.Idling -= IdlingHandler;
}
returnResult.Succeeded;
}
Helper Methods
Apart from the top-level management, the Idling event handler does all the rest of the work.
Before we get to the event handler itself, I'll present the three helper methods it uses:
GetActiveUiView: retrieve the active UIView.
GetView3d: retrieve the 3D view named "{3D}".
ElementDescription: return a descriptive text for a given Revit element.
We can get the active document view directly, but need some additional coding to determine the associated UIView, for example like this:
The ReferenceIntersector constructor requires a 3D view argument in which the ray tracing takes place.
In this sample, I assume that a suitable 3D view named "{3D}" is available and retrieve that using one single line of filtered element and LINQ driven code:
///<summary>/// Return the 3D view named "{3D}".///</summary>View3D GetView3d( Document doc )
{
returnnewFilteredElementCollector( doc )
.OfClass( typeof( View3D ) )
.Cast<View3D>()
.FirstOrDefault<View3D>(
v => v.Name.Equals( "{3D}" ) );
}
You can obviously modify this in any way you like and set up a suitable 3D view to suit your specific view setting, sectioning and filtering needs.
The location dependent BIM information displayed in the tooltip can be determined completely freely.
In my case, I simply detect what element the cursor is hovering over and use the ElementDescription method to put together a descriptive text for it:
///<summary>/// Return a string describing the given element:/// .NET type name,/// category name,/// family and symbol name for a family instance,/// element id and element name.///</summary>staticstring ElementDescription(
Element e )
{
if( null == e )
{
return"<null>";
}
// For a wall, the element name equals the// wall type name, which is equivalent to the// family name ...FamilyInstance fi = e asFamilyInstance;
string typeName = e.GetType().Name;
string categoryName = ( null == e.Category )
? string.Empty
: e.Category.Name + " ";
string familyName = ( null == fi )
? string.Empty
: fi.Symbol.Family.Name + " ";
string symbolName = ( null == fi
|| e.Name.Equals( fi.Symbol.Name ) )
? string.Empty
: fi.Symbol.Name + " ";
returnstring.Format( "{0} {1}{2}{3}<{4} {5}>",
typeName, categoryName, familyName,
symbolName, e.Id.IntegerValue, e.Name );
}
This is somewhat similar to and yet does not exactly match the information displayed by the standard Revit tooltip and other parts of the user interface, as you might have noticed in the
video recording.
Idling Event Handler
The Idling event handler implements the following steps, which we first discuss one by one before presenting the entire code in context:
Converting from the cursor position retrieved in Windows device coordinates to Revit model coordinates is possible by calculating the relative position in the UIView.
Here is an illustration assuming a screen resolution of 1600 x 1200 with a Revit view located at the rectangle 800,150 to 1500,650, displaying a plan view of part of the Revit model, with model coordinates ranging from 10,40,0 to 220,120,0.
The current cursor location is represented by the green spot:
The Windows device coordinate origin 0,0 is at the upper left, and Y values increase downwards, whereas the Revit model coordinate Y values increase upwards.
You can easily query the cursor position in device coordinates on the Windows screen from .NET framework at any time using the System.Windows.Forms.Cursor.Position property:
Point p = System.Windows.Forms.Cursor.Position;
Determine Revit Model Coordinates
The cursor location is obviously returned in Windows device coordinates, stored in the point (or vector) variable 'p'.
The aim is to determine the location indicated by this point in Revit model coordinates, for example by determining the vector v from the lower left corner of the Revit view to that location.
We can easily determine the relative position of p between the two corners of the UIView in Windows device coordinates by subtracting the lower left hand corner and dividing by the total width and height to obtain the relative width and height location values dx and dy like this:
From the UIView, we can also determine model coordinates of the view corners.
They are stored in a and b, respectively.
Now it is easy to calculate the cursor point 'q' in model coordinates from the two relative values like this:
IList<XYZ> corners = uiview.GetZoomCorners();
XYZ a = corners[0];
XYZ b = corners[1];
XYZ v = b - a;
XYZ q = a
+ dx * v.X * XYZ.BasisX
+ dy * v.Y * XYZ.BasisY;
Query Revit BIM for Information using ReferenceIntersector Ray Casting
Once we have the model space cursor location 'q', we can calculate a point beyond the model extents (hopefully) from which one can picture the user looking into the model.
The first element intersected by a ray cast from that point in the view direction should be the one we are interested in displaying information about.
Initially, the sample run was a bit boring, because the only element encountered when looking from straight above was always the roof.
I tweaked that to make the example more interesting, not by modifying the model itself – I use the standard architectural basic sample project rac_basic_sample_project.rvt – but by adding a filter to remove all roof category elements from the reference intersector results instead.
The exact elements of interest and information to display are entirely up to you and your needs to determine, of course.
The ray casting requires a 3D view to operate in.
In this case, I simply pick the one named "{3D}" returned by the GetView3d helper method.
You can set up your own view with specific graphics properties, section cuts, and only certain elements visible, if that better suits your requirements.
Here is the code determining the view, view direction and ray origin, setting up the intersector, determining the target element, and defining the tooltip text to display in the variable 's':
View3D view3d = GetView3d( doc );
XYZ viewdir = view.ViewDirection;
XYZ origin = q + 1000 * viewdir;
// Find all elements://ReferenceIntersector ri// = new ReferenceIntersector( view3d );// Find all elements except roofs:ElementFilter f = newElementCategoryFilter(
BuiltInCategory.OST_Roofs, true );
ReferenceIntersector ri
= newReferenceIntersector( f,
FindReferenceTarget.Element, view3d );
ReferenceWithContext rc
= ri.FindNearest( origin, -viewdir );
string s = "Element not found";
if( null != rc )
{
Reference r = rc.GetReference();
Element e = doc.GetElement( r );
s = ElementDescription( e );
}
Display Tooltip and the JtTooltipForm class
I originally used the Visual Studio designer to create a simple tooltip form named JtTooltipForm for me, then tweaked that auto-generated code to create one named JtTooltipForm2 myself programmatically from scratch.
Besides the constructor setting up the form and the Label object to display the tooltip text, it also manages an offset from the cursor position at which to display itself, provides a method to set the tooltip text, and overrides the OnShown and OnVisibleChanged methods to react appropriately to position changes:
///<summary>/// A tooltip window designed to move /// around with the cursor position.///</summary>classJtTooltipForm2 : Form
{
///<summary>/// The offset from the mouse pointer /// at which to show the form.///</summary>publicPoint Offset { get; set; }
///<summary>/// Tooltip text.///</summary>Label _label;
///<summary>/// Set the tooltip text.///</summary>publicvoid SetText( string s )
{
_label.Text = s;
}
///<summary>/// Move the window to an offset of mouse pointer.///</summary>protectedoverridevoid OnShown( EventArgs e )
{
base.OnShown( e );
Location = newPoint(
MousePosition.X + Offset.X,
MousePosition.Y + Offset.Y );
}
///<summary>/// Move the window to an offset of mouse pointer.///</summary>protectedoverridevoid OnVisibleChanged(
EventArgs e )
{
base.OnVisibleChanged( e );
if( Visible )
{
Location = newPoint(
MousePosition.X + Offset.X,
MousePosition.Y + Offset.Y );
}
}
public JtTooltipForm2()
{
Size = newSize( 200, 20 );
_label = newLabel();
SuspendLayout();
_label.AutoSize = false; // the label will not change its height automatically, only width, so switch off AutoSize to wrap text
_label.CausesValidation = false;
_label.Dock = DockStyle.Fill;
_label.Location = newPoint( 0, 0 );
_label.Size = newSize( 35, 13 );
_label.Parent = this;
AutoScaleDimensions = newSizeF( 6F, 13F );
AutoScaleMode = AutoScaleMode.Font;
BackColor = SystemColors.Info;
ClientSize = newSize( 200, 12 );
Controls.Add( _label );
FormBorderStyle = FormBorderStyle.None;
Name = "JtTooltipForm";
Opacity = 0.8D;
ShowInTaskbar = false;
TopMost = true;
TransparencyKey = Color.White;
ResumeLayout( false );
PerformLayout();
Offset = newPoint( 10, 0 );
}
}
You may note that I switched off the AutoSize property.
Setting it to true enables the label to automatically expand, and that just means getting wider to fit the text length.
The label will not change its height automatically, only width.
A neat trick to wrap text before putting it into the label that I spotted and did not make use of employs a regular expression to do the job:
Controlling the text and position from the Idling event handler is a trivial two-liner:
// Move tooltip to current cursor // location and set tooltip text.
_form.Location = p + newSize( _form.Offset );
_form.SetText( s );
Entire Idling Event Handler Implementation
For better readability, here is the entire Idling event handler implementation in all its glory.
It even includes one slight improvement that is not demonstrated by the ModelessForm_IdlingEvent SDK sample implementation:
If the Revit project is closed while the Idling event is still subscribed to, the UI document may be null.
One needs to add a check for that situation as well in the Idling handler, in case the hander relies on an open document.
///<summary>/// Idling event handler.///</summary>///<remarks>/// We keep the handler very simple. First check/// if we still have the form. If not, unsubscribe /// from Idling, for we no longer need it and it /// makes Revit speedier. If the form is around, /// check if it has a request ready and process /// it accordingly.///</remarks>publicvoid IdlingHandler(
object sender,
IdlingEventArgs args )
{
UIApplication uiapp = sender asUIApplication;
UIDocument uidoc = uiapp.ActiveUIDocument;
if( null == uidoc || _form.IsDisposed )
{
uiapp.Idling -= IdlingHandler;
}
else// form still exists
{
Document doc = uidoc.Document;
View view = doc.ActiveView;
UIView uiview = GetActiveUiView( uidoc );
Rectangle rect = uiview.GetWindowRectangle();
Point p = System.Windows.Forms.Cursor.Position;
double dx = (double) ( p.X - rect.Left )
/ ( rect.Right - rect.Left );
double dy = (double) ( p.Y - rect.Bottom )
/ ( rect.Top - rect.Bottom );
IList<XYZ> corners = uiview.GetZoomCorners();
XYZ a = corners[0];
XYZ b = corners[1];
XYZ v = b - a;
XYZ q = a
+ dx * v.X * XYZ.BasisX
+ dy * v.Y * XYZ.BasisY;
// If the current view happens to be a 3D view, // we could simply use it right away. In // general we have to find a different one to // run the ReferenceIntersector in.View3D view3d = GetView3d( doc );
XYZ viewdir = view.ViewDirection;
XYZ origin = q + 1000 * viewdir;
// Find all elements://ReferenceIntersector ri// = new ReferenceIntersector( view3d );// Find all elements except roofs:ElementFilter f = newElementCategoryFilter(
BuiltInCategory.OST_Roofs, true );
ReferenceIntersector ri
= newReferenceIntersector( f,
FindReferenceTarget.Element, view3d );
ReferenceWithContext rc
= ri.FindNearest( origin, -viewdir );
string s = "Element not found";
if( null != rc )
{
Reference r = rc.GetReference();
Element e = doc.GetElement( r );
s = ElementDescription( e );
}
// Move tooltip to current cursor // location and set tooltip text.
_form.Location = p + newSize( _form.Offset );
_form.SetText( s );
}
}
Toggle Tooltip On and Off Commands
To simplify the usage and cooperation with other add-ins, I implemented two trivial read-only external commands calling the external application ShowForm and HideForm methods to toggle my personal tooltips on and off:
I hope you find this as useful as I imagine it might become, and look forward to your feedback.
By the way, I will be talking about this sample and the possibilities it demonstrates along with many other topics in my Autodesk University presentation
CP4107 on
the new Revit 2013 UI API functionality, probably making this the most exciting one of my
three AU sessions.
Here is a
current snapshot of
the entire source code, Visual Studio solution and add-in manifest of the WinTooltip sample application, together the rest of my CP4107 sample material.
The latter is based on my
DevCamp Revit 2013 UI API samples.
Here are a couple of other items of interest before I wrap up, related to the Revit API, technology and purely human interst.
URLs and local system HTML links work fine out of the box.
A text file, however, needs special processing.
One way to launch it is by using the Process.Start method in the .NET System.Diagnostics namespace.
Saikat provides the source.
DesignScript
A very exciting new technology is now publicly available on Autodesk Labs:
DesignScript:
A powerful scripting language for exploratory programming.
Managing and distinguishing between a generative description of a design (as a script) and the resulting generated model.
A language to help designers build and analyze complex geometric models that would be difficult to model with interactive techniques.
Integrated into a host geometry application (currently AutoCAD).
The designer no longer directly models the resulting design: instead she develops a script whose execution generates the model.
This enables a completely different kind of design model to be created.
The design process is also different.
An apparently minor edit to the script can have a profound effect on the generated model, enabling the exploration of a vast array of alternatives, with much less effort than manual interactive modeling.
Here is a five-minute video explanation of
Design Computation by Robert Aish for Autodesk University last year:
Kean Walmsley provides a very nice explanation and
description of the DesignScript sample he
implemented for it together with Robert in 2008 for the Design Computation Symposium and AU mainstage presentation.
Now you can try it for yourself.
Unfortunately, DesignScript is not yet available for Revit...
Falk on Schafberg
For something completely unrelated to any technology at all, here is a nice little
video of
my friend
Falk recently
when we hiked over the
Wildhauser Schafberg (sheep
mountain) together, clearly illustrating some basic and sympathetic human and
ovine commonalities :-)
This morning I presented my second session, on some of the Revit 2013 UI API enhancements, in the Revit API beginner track.
After that, I returned back to the Revit API expert one to attend the presentations on modeless interaction and IFC before presenting my final session on the Revit MEP API.
Here then are my sessions of today, in chronological order:
Enhanced add-in integration is
an important topic in the Revit 2013 API.
This session discusses four of the add-in integration topics in more depth, building on a preceding presentation by Saikat Bhattacharya presenting a snapshot of the entire Revit UI API functionality:
All four of these are covered by existing SDK samples, the first by
ProgressNotifier (Events),
the others by the comprehensive
UIAPI sample.
For someone like me, feeling a strong affinity to
Winnie the Pooh, the
"bear of very little brain",
the existing SDK samples are way beyond simple comprehension and reuse, so I ended up creating four own extremely simple and minimal ones exercising these features in just a few lines of code each instead, to reduce the code and required audience comprehension power to an absolute minimum.
Revit progress bar notifications
To receive information about the current Revit progress bar status, you can subscribe to the ProgressChanged event.
The event handler will receive a ProgressChangedEventArgs instance providing the following properties:
Caption – progress bar caption describing operation in progress
Stage – current stage of the progress bar, one of Started, CaptionChanged, RangeChanged, PositionChanged, UserCancelled, Finished
LowerRange – lower limit of range, always zero
UpperRange – upper limit of range, any non-zero number
Position – Value between zero and UpperRange incremented with "PositionChanged" stage
The ProgressNotifier SDK sample displays a WPF-based dialogue box.
It provides a button to open and load a Revit model.
Loading the selected file will cause a number of progress bar actions to execute.
These actions are monitored and presented in a stack structure, since occasionally more than one progress bar event may be active simultaneously:
My new ProgressWatcher sample in much simpler and tracks all progress bar events by just printing the event argument data to the Visual Studio debug output window.
This can be achieved in a very few lines of code, basically only one each to subscribe to the event and to access and print the progress bar event data:
Saving the output to a text file will allow you to analyse in detail the progress bar event sequence and nesting.
Options dialogue WPF custom extensions
In Revit 2013, an add-in can add its own custom tabs to the standard Revit Options dialogue.
This is achieved by defining a WPF control to display and subscribing to the UIApplication DisplayingOptionsDialog event.
The event handler receives an DisplayingOptionsDialogEventArgs instance.
It has a PagesCount property providing the number of Options dialogue tabs including default Revit ones.
It also sports the AddTab method that can be used to add a tab, providing a name and a handler for it.
The handler information is encapsulated in the TabbedDialogExtension class.
The TabbedDialogExtension class provides a constructor taking two arguments, a WPF user control instance and an OK handler.
These cannot be changed later.
It provides methods to get and set contextual help, and properties to read the WPF control and OK handler.
It also provides properties to both get and set the cancel and 'restore defaults' handlers:
Control – the control
OnOKAction – the ok handler
OnCancelAction – the cancel handler
OnRestoreDefaultsAction – the restore defaults handler
The
UIAPI SDK
sample adds several custom tabs demonstrating most available features.
I created a minimal sample named AddOptionsTab which defines the simplest possible WPF control with one single button to click:
If you click the button, the corresponding event handler on the form is called, which simply displays a task dialog:
If this tab has ever been activated after the Options dialogue was opened, its OK, cancel and 'restore defaults' handlers are called, depending on the user actions.
Here is the entire user control source code defining its handlers:
The event handler code reacting to the Options dialogue displaying simply instantiates the user control, calls the TabbedDialogExtension constructor with it, and feeds that to the event handler arguments AddTab method:
void OnDisplayingOptionsDialog(
object sender,
DisplayingOptionsDialogEventArgs e )
{
UserControl1 c = newUserControl1(
"DevCamp User Control" );
e.AddTab( "DevCamp Custom Tab",
newTabbedDialogExtension(
c, c.OnOK ) );
}
The event handler is called each time the Options dialogue is invoked once the event has been subscribed to, which is achieved in a single line of code like this in the AddOptionsTab external command:
Another of the add-in integration enhancements enables you to embed and control a Revit view in your own form, dialogue or window.
The new PreviewControl class presents a preview control to browse the Revit model.
It takes two input arguments, a document and a view id.
The hosting form or window must be modal.
The View can be any graphical view, i.e. must be printable.
Perspective views are also supported, and all 3D Views can be manipulated by view cube.
The visibility and graphical settings of the view are effective and respected by the control.
The
UIAPI SDK
sample proves a demonstration of this as well, which allows you to interactively select any of the open documents to view, or even open a new one, and secondly presents a list of all the documents views for you to switch between.
Each time you change any of these two selections, the current preview control is discarded and a new one is instantiated with the updated input arguments.
These input arguments are the only way in which the add-in affects the view, all further interaction happens between the control and the user.
To use a Revit preview control, you simply create a standard .NET form and insert a WPF host in it.
A suitable host is the System.Windows.Forms.Integration.ElementHost.
You populate this with a new Revit preview control instance, e.g. like this
I created a minimal sample application PreviewControlSimple for this DevCamp class with much less code (and functionality than the UIAPI one, since it just displays the current view of a document with no further ado.
It does add one interesting twist, though: to show exactly how the hosting form is set up, I implemented that twice over, first using the Visual Studio designer as usual, and then converting the form to be completely programmatically generated.
Here is the DisplayRevitView method taking the document and view input arguments for the preview control.
It also takes a third argument for the main Revit window, so that the generated control can be hooked up to that as a parent window.
This defines an appropriate relationship between the two as far as the Windows OS is concerned, ensuring correct minimisation and restoration behaviour:
Calling this method from the external command mainline with the current document view is trivial.
I add one line of code to access the Revit main window:
The UIApplication provides the new static DoDragDrop method, with two overloads:
DoDragDrop( ICollection<string> ) –
initiate a standard built-in Revit drag and drop operation taking a collection of file names.
DoDragDrop( object, IDropHandler ) –
initiate a drag and drop operation with a custom drop implementation.
This method is designed for use in a modeless form.
Please note that this is currently the one and only Revit API method not requiring a valid Revit API context.
This method can be called from your modeless form at any time, even without Revit giving you explicit permission to interact with the API via some form of callback or notification, as required for all other Revit API calls.
The behaviour of drag and drop given a list of filenames depends on the type of files, as I already recently pointed out:
One AutoCAD format or image file dragged onto Revit
– A new import placement editor is started to import the file.
More than one AutoCAD format or image files dragged
– A new import placement editor is started for the first file.
One family file dragged onto Revit
– The family is loaded, and an editor started to place an instance.
More than one family file dragged onto Revit
– All the families are loaded.
More than one family file including other format files dragged
– Revit tries to open all the files.
Valid file or list of files is passed
– Revit does its best to use them appropriately.
Any files are not usable
– Failure is signalled to user, no exception is thrown, add-in is not notified.
Here again, I implemented a new DevCamp sample add-in DragDropApi to be able to show the necessary steps in a simpler context than the corresponding UIAPI example.
Arnošt Löbel already presented on this topic at Autodesk University 2011 in his class
CP5381 'Asynchronous Interactions and Managing Modeless UI with the Autodesk Revit API', as mentioned in the overview of the AU 2011
Revit and AEC API sessions.
The conclusion of the presentation is the following pledge that we are all strongly recommended to take:
I shall not call the API unless invoked by it
I shall not call the API from anywhere but the main thread
I shall call the API only, not directly the Revits UI
I shall use the Idling event cautiously and considerably
Here is a snapshot of the
entire slide deck.
This is not the final version, which will be posted soon after the end of DevCamp.
IFC Export Open Source
Angel Velez is senior principal engineer in the Revit development team and presented on the IFC exporter open source project, covering the following main topics:
Introduction
History of IFC in Revit
Why open source?
How the code Works
Overall structure of code
Key points of interest, e.g. element traversal, property sets
The Revit MEP API is a recurring topic of mine at Autodesk University, and I presented on this once again last year in my session
CP4453 'Everything
in Place with Autodesk Revit MEP Programming', as mentioned in the overview of the AU 2011
Revit and AEC API sessions.
The AU materials and the sample code that I use there was already
published and discussed.
Here is the
slide deck I
used today, based on the AU one and updated for Revit MEP 2013.
Please note again that all the materials provided above are just snapshots.
Soon after the conference completes, the complete and final materials will be published.
Winding Down and Winding Up Again
In the evening we went out for a DevTech dinner, Japanese and Chinese cuisine.
It was great to hang out with the guys for a while and wind down a bit:
Next stop is the DevLab tomorrow with a record number of almost fifty registered participants, so we will be way busy!
Time to wind up again.
No rest for the wicked!
These events are always great, and I'm looking forward to it a lot.
As I recently pointed out, no Revit API methods at all can be called outside a valid Revit API context, not even the new
DoDragDrop method or
Application.IsQuiescent property.
I already presented two convincing examples in the past of how you could work around a lack of direct API access by interacting intelligently with the user instead:
In the first, lack of certain API functionality was worked around by prompting the user to execute an action instead.
In the second, an action was detected that could not be undone programmatically, but a warning can easily be displayed allowing the user to undo if she so desires.
Here is an example by Paul Vella of Autodesk Consulting in Melbourne, Australia, of how you can easily and safely avoid a potentially complex and risky modeless interaction by providing an adequate user interface instead:
Question: I have a command that identifies a list of elements from the Revit model, and displays them in a modeless dialog to the user. The dialog is modeless so that the user can interact with the model, but I also want the user to be able to change the current selection (and zoom to that item) by selecting items from my dialog.
The zoom to part works, and the selection appears to change, but the properties panel does not update correctly.
Apparently the issue is that the Revit command has ended once the dialog has been displayed, and therefore my selection code runs outside of a command and lacks a valid Revit API context.
Is it possible to programmatically create a command context or otherwise to get my selection code to work, and have the properties panel update correctly?
One idea I had was to have a separate command button to do the selection, and call this programmatically from my form. But I could not work out how to programmatically click a PushButton on the ribbon.
Answer: Nope, I am sorry to say it is not possible to create a command context in the way you describe, nor does the Revit API provide any way to programmatically simulate a ribbon push button click.
Response: The solution I went with was to create a new button on the ribbon entitled 'Zoom to Sync Error'.
When clicked, this button determines the currently selected item in the modeless form, which is accessible through a singleton instance.
At that point, an external Windows application could be used to initiate a standard Revit drag and drop sequence, but we had no control over Revit's behaviour on receiving the dropped files.
This has changed in the
Revit 2013 API,
which provides a drag and drop API as part of the
add-in integration features
and includes the
UIAPI sample
in the Revit SDK to demonstrate its use, among several other new API features such as discipline dependent application availability, preview control, and a custom WPF options dialogue.
By the way, the UIAPI sample differs from many other SDK samples in a couple of points:
It is an external application, so you cannot use RvtSamples to load and run it.
You have to install it separately.
It is still rather undocumented in the Revit 2013 RP version of the SDK, in that it lacks the customary readme file and is not listed in SamplesReadMe.htm like the others.
To make up for that, here is a pretty detailed description of its drag and drop functionality, at least.
UIAPI SDK Sample Drag and Drop
The UIAPI drag and drop command displays the following modeless dialogue:
It presents a list of loaded furniture family symbols on the left and all furniture family files found by recursively searching the content library on the right.
UIApplication DoDragDrop Method Overloads
The UIApplication now provides two overloads of the new static method DoDragDrop for interacting with drag and drop events:
DoDragDrop( ICollection<string> ) to initiate a standard Revit drag and drop operation of a collection of file names on the Revit user interface.
This method and its same two overloads are also provided on the macro-specific ApplicationEntryPoint class, for Revit macro use only.
Drag and Drop a List of Files
In the first overload, the method argument holds a list of paths and names of files to be dropped on the Revit user interface, which causes the following default behaviour:
Only one AutoCAD format or image file dragged onto Revit: a new import placement editor will be started to import the file.
More than one AutoCAD format or image files dragged onto Revit: a new import placement editor will be started only for the first AutoCAD format or image file.
Only one family file dragged onto Revit: the family will be loaded, and an editor will be started to place the family.
More than one family file dragged onto Revit: all the families will be loaded.
More than one family file including other format files dragged onto Revit: Revit will try to open all the files.
If a valid file or list of files is passed, Revit will do its best to use them appropriately. If any files are not usable, failure will be signalled to the interactive Revit user, but no exception is thrown and the add-in will not be notified.
Here again, this behaviour is built into Revit and cannot be modified.
The code initiating the drag and drop from the modeless dialogue is as simple as this:
// Drag action from list boxprivatevoid listBox1_MouseMove(
object sender,
MouseEventArgs e )
{
if( System.Windows.Forms.Control.MouseButtons
== MouseButtons.Left )
{
FamilyListBoxMember member
= (FamilyListBoxMember) listBox1.SelectedItem;
// Use standard Revit drag and drop behaviorList<String> data = newList<String>();
data.Add( member.FullPath );
UIApplication.DoDragDrop( data );
}
}
In other words, dragging a file name from the list on the right onto Revit will trigger the same built-in default behaviour as dragging the same file from the Windows explorer.
Yet another Caveat against Invoking Revit API Methods Modelessly
Note that both this method and the one discussed below call the static UIApplication DoDragDrop method from a modeless dialogue, i.e. outside a valid Revit API context, which is unusual for the Revit API.
This is most likely going to be changed in the sample, as it will almost certainly cause serious problems such as unexpected results, corrupted documents, crashes, etc. if utilised in a commercial application.
A better approach would be to use the Idling or an external event to trigger the DragDrop activity at a later time.
The only safe option is to call DoDragDrop from an Idling or external event call-back, just like all other Revit API calls.
Actually, on further discussion, we realised that the situation is even worse: invoking the drag-and-drop method during both Idling and external events is never recommendable, because it is likely to cause
deadlock.
The problem is that the user interface is not expecting to be invoked during idling; the application is idling for the exact reason that there is no UI activity.
If UI operations are invoked in this situation, two things may happen:
The same message pump is re-entered with a new request, which cannot be processed, since the pump is still waiting for the idling event to be ended.
Another, secondary message pump is entered, which may eventually invoke another idling event, which may dead-lock the client application.
Unfortunately, this means that there really is no totally safe way to utilise this wonderful new API from a modeless dialogue.
We will obviously be taking a new look at this in future versions, but for now it is important to realise the risks of invoking the Revit UI during the Idling event.
Here are some ideas which may help relativate these worries:
The problem of dead-lock or re-entering idling will only be apparent if invoking Revit UI, for instance the interactive placement of a family instance
If the method only loads the family or does some other non-UI activity, it will be OK.
The tool works perfectly well if there is no other Revit tool active.
Of course, the best way to tell this is from Idling, which it seems cannot successfully call the call-back.
So once again: make sure that you test the scenario in which you plan to use this very carefully, and avoid all risks in a commercial implementation.
Drag and Drop with a Custom Handler
The second overload is much more exciting, because it allows us to define our own drop behaviour inside Revit.
This can only be used to define behaviour within the context of the add-in's own UI, though.
For example, it does not allow you to define new drag and drop behaviour for files which are not supported by the method above when dropped onto Revit from explorer, or from an unconnected application like Excel, AutoCAD, etc.
The drag operation must be initiated from a point where it can call DoDragDrop to Revit to allow it to complete.
It requires us to set up a handler for the drop event, which is derived from the IDropHandler interface, requiring then implementation of one single method, Execute.
In this sample, the drop handler expects the element id of a family symbol to be passed in and simply calls the PromptForFamilyInstancePlacement method on the symbol.
You are obviously completely free to pass in any data you like when invoking the handler, and can choose to do something completely different with it inside Revit on receipt:
///<summary>/// Custom handler for placement of loaded family types///</summary>publicclassLoadedFamilyDropHandler : IDropHandler
{
publicvoid Execute(
UIDocument doc,
object data )
{
ElementId familySymbolId = (ElementId) data;
FamilySymbol symbol = doc.Document.GetElement(
familySymbolId ) asFamilySymbol;
if( symbol != null )
{
doc.PromptForFamilyInstancePlacement(
symbol );
}
}
}
With the drop handler in place, we can initiate a custom drag and drop like this:
// Drag action from list viewprivatevoid listView_MouseMove(
object sender,
MouseEventArgs e )
{
if( System.Windows.Forms.Control.MouseButtons
== MouseButtons.Left )
{
ListViewItem selectedItem = this.listView1
.SelectedItems.Cast<ListViewItem>()
.FirstOrDefault<ListViewItem>();
if( selectedItem != null )
{
// Use custom Revit drag and drop behaviorLoadedFamilyDropHandler myhandler
= newLoadedFamilyDropHandler();
UIApplication.DoDragDrop(
selectedItem.Tag, myhandler );
}
}
}
Be sure to take a look at the other commands defined by the UIAPI SDK sample as well, because they are all pretty exciting and address a number of long-standing wish list items.
One of the areas of enhancement is the
Idling event and
interaction with modeless dialogues, which also was the subject of Arnošt Löbel's Autodesk University 2011 class
CP5381 on
asynchronous interactions and managing modeless UI.
As I mentioned, the enhancements and new functionality are demonstrated by the new
ModelessForm_ExternalEvent and ModelessForm_IdlingEvent SDK
samples.
Here is an overview of these enhancements from the recent ADN newsletter:
Overview
A number of features introduced in the Revit 2012 API have been enhanced in the Revit 2013 API based on the initial real-world experience gathered since the initial release of Revit 2012. For instance, this has led to the following added features related to the Idling and other events:
The Idling event has been changed to offer two different modes of behaviour. The default behaviour has changed, so pre-existing code that needs the original behaviour will have to explicitly select the non-default behaviour.
In the default mode, the event will be raised one single time each time Revit begins an idle session. Note that when the user is active in the Revit user interface, idle sessions begin whenever the mouse stops moving for a moment or when a command completes. However, if the user is not active in the user interface at all, Revit may not invoke additional idling sessions for quite some time; this means that an add-in may not be able to take advantage of time when the user leaves the machine completely idle for a period of time.
In the non-default mode, Revit keeps the idling session open and makes repeated calls to the event subscriber. In this mode, Revit will continue to make Idling calls even if the user is totally inactive. However, this can result in system performance degradation, because the CPU may remain fully engaged in serving Idling events.
You can specify the non-default Idling behaviour by calling the new SetRaiseWithoutDelay method on the IdlingEventArgs instance passed in to the event handler. This call has to be made each time the Idling event call-back is made. Revit will revert to the default single-call Idling behaviour as soon as this method is not called.
Idling Event with no Active Document
In contrast to the Revit 2012 behaviour, the Idling event now is invoked even when there is no document active in Revit.
External Events Framework
The Idling event enables an interaction between Revit and an external asynchronous process. The external process cannot directly invoke any Revit API methods, but it can send a request to be processed by the Idling event handler next time it is raised. One common use of this has turned out to be supporting the interaction of a modeless dialogue with Revit. In this kind of scenario, the Idling event handler never has anything to do except when such a request has been submitted, so most of the calls to it will simply return immediately.
This interaction is supported much simpler and much more efficiently by the new external events framework. It operates similarly to the Idling event with default frequency. Instead of implementing an event handler and setting up the handler to do nothing when there is no activity in your dialog, you do the following instead:
Implement an external event, derived from the IExternalEvent interface.
Create an instance of your external event by calling the ExternalEvent.Create method.
When the external asynchronous process triggers an event that requires a Revit action to be taken, call the Raise method on the external event instance.
Revit will wait for an available Idling time cycle and call the external event Execute method.
This framework is useful for modeless dialogue developers, because you can skip the default no-op implementation when nothing has happened, and just tell Revit when there is some work to do. It saves on the time required for Revit to make repeated calls to the Idling event when there is nothing to be done.
This is the complete specification of the external event interface:
// An interface to be executed when an external // event is raised.//// An instance of a class implementing this // interface will be registered with Revit first, // and every time the corresponding external event // is raised, the Execute method of this interface // will be invoked.publicinterfaceIExternalEventHandler
{
// Method called to handle the external event.void Execute( UIApplication app );
// String identification of the event handler.string GetName();
}
Here is an example snippet of code creating an instance of an external event and starting a new thread to drive it from the same application:
Here is part of the implementation of the Run method making use of the external event and calling its Raise method to request its Execute method to be called, in which the Revit API can be used again:
///<summary>/// External webcam event driver./// Check regularly whether the webcam image has /// been updated. If so, update the spatial field /// primitive with the new image data./// Currently, we only display a grey scale image./// Colour images could be handled as well by /// defining a custom colour palette.///</summary>staticvoid Run()
{
_running = true;
while( _running )
{
_data = newGreyscaleBitmapData(
_width, _height, _url );
byte[] hash = _data.HashValue;
if( null == _lastHash
|| 0 != CompareBytes( hash, _lastHash ) )
{
_lastHash = hash;
_event.Raise();
}
Thread.Sleep( _intervalMs );
}
}
RevitWebcam Revisited
I implemented the
RevitWebcam sample
created to demonstrate use of the Idling event when it was introduced in the Revit 2011 API, and recently
migrated it to Revit 2012 in
preparation for making use of this new 2013 functionality.
I now performed two further steps on it:
Enhance it with the new Idling functionality, and
Convert it to make use of an external event instead of the Idling event.
Here is
RevitWebcam_2013.zip containing
the following hopefully self-descriptive subfolders, each of which in turn contains a complete Visual Studio solution:
RevitWebcam2011
RevitWebcam2012_1_initial_migration
RevitWebcam2012_2_removed_warnings
RevitWebcam2012_3_final_cleanup
RevitWebcam2013_1_initial_migration
RevitWebcam2013_2_removed_warnings
RevitWebcam2013_3_idling_repetition
RevitWebcam2013_4_external_event
RevitWebcam2013_4_external_event_2
If you are interested in the evolution of the project, you can compare these with each other.
Please be aware that these projects are for demonstration purposes only and skip a lot of error checking.
For instance, they do nothing protect themselves against the user switching or closing the active project or making other changed to the environment while they are running, so they should not be used as examples of real world application implementations.
In the Revit 2012 API, the OpenAndActivateDocument method cannot be called from inside any event handler at all. Related to the new Idling and external event functionality, it may now be invoked from within event handlers under most circumstances, specifically under the following conditions:
From an external event if the original active document has no open transactions or transaction groups.
From any regular event handler including Idling and the new ApplicationInitialized event if
There is no active document open yet in Revit, and
The event is not nested in any other event or in the execution of an external command.
In general, the new ApplicationInitialized event is more suitable than the Idling one for opening and activating the first document.
Further Information
All the information above and more is provided in the What's New section of the Revit API help file RevitAPI.chm included with the Revit SDK.
Revit 2013 has been released, and I presented an overview of the
Revit 2013 API two
days ago.
As always, the use of most of the API features is demonstrated by various SDK samples.
For a better understanding of the use and contents of the Revit SDK in general, please refer to the
getting started and
self-preparation guides.
These samples are all related to the new
add-in integration functionality,
except for the RoutingPreferenceTools which obviously demonstrate some of the
MEP related enhancements.
By the way, the 'VSTA Samples' folder was renamed to 'Macro Samples' since
VSTA was
replaced by the open source
SharpDevelop IDE.
ModelessForm_ExternalEvent and ModelessForm_IdlingEvent
Both of these display and show how to interact with a modeless form.
One way to do this is to use the
Idling event initially provided in Revit 2012,
which we have discussed so much and in such depth in the past.
The ModelessForm_IdlingEvent sample should clarify many of the issues we dealt with, and is also related the material presented by Arnošt Löbel in his Autodesk University 2011 class
CP5381 on
asynchronous interactions and managing modeless UI.
The ModelessForm_ExternalEvent sample demonstrates an easier way to implement this interaction using the new external event interface.
ProgressNotifier
The ProgressNotifier sample displays progress information for an action in a stack data structure for easier analysis. It demonstrates how to subscribe to the ProgressNotify related events, access properties in the event handler arguments, and organize the subtransaction progress information into a stack.
RoutingPreferenceTools
The RoutingPreferenceTools sample provides a number of MEP pipe routing preference tools.
This sample contains three commands, one for analysis and reporting purposes, two for importing and exporting routing preferences to XML:
Routing Preference Analysis: Analyze the routing preferences of a given pipe type to check for common problems, using the routing preferences API to look at all rules and criteria for a given PipeType.
Routing Preference Builder with its two commands CommandReadPreferences and CommandWritePreferences: Set pipe type, fitting, and routing preferences in a project from data in an XML file and export these preferences to XML for archival, documentation, and collaboration purposes, allowing a user to work with routing preference data in a shareable XML format suitable for reuse in a wide variety of BIM management environments.
UIAPI
The UIAPI sample demonstrates a number of the new
add-in integration API features that
I already listed, including embedding a Revit view as WPF control inside its own dialogue, the new drag and drop API, and the Options dialogue support for custom extensions using arbitrary WPF components.
This sample was also shown at the DevDays 2011 conferences.
WorkThread
The WorkThread sample demonstrates utilizing the Idling event in a multi-threaded application to communicate with the Revit API from an external work thread.
I recently discussed the
elimination of warnings about
use of deprecated API methods from The Building Coder samples.
In a similar vein, and for similar reasons, I also decided to migrate to Revit 2012 and clean up the
RevitWebcam sample
that I implemented to demonstrate the use of the
Idling event when
it was first introduced in the Revit 2011 API.
Please note that this sample is intentionally not related to any BIM specific workflow.
A webcam just provides an obvious and accessible sample of a continuously updated and changing graphical dataset driven by something outside of Revit.
I use this to demonstrate that we can sample this data and create a graphical display of it within the Revit model with no user interaction whatsoever.
Other add-ins such as the MultithreadedCalculation Revit SDK sample simulate an externally changing data source.
A real-world example might be displaying the graphical analysis visualisation of an asynchronous cloud-based calculation.
I migrated the 2011 version to 2012 in several steps:
I could have simply copied the existing project and source code files and modified them one by one as required for the 2012 API.
Instead, I used a different approach that I find more efficient, since it requires less manual steps:
I created a completely new project using the Visual Studio
Revit add-in wizard and
then copied the relevant bits of source code that I wished to preserve from the original project to the new one.
Since the add-in wizard creates all the required settings, including generating and installing the add-in manifest, all in one single click, this approach requires less manual intervention.
As an additional bonus, it also guarantees that all the application settings are up-to-date and nothing is forgotten.
Here is
RevitWebcam2012_1_initial_migration.zip containing
the result of the flat migration, which compiles all right but exhibits one little problem when executed:
Adaption of the Idling Event Handler
The flat migration enables the add-in to be compiled successfully.
On running it, though, an exception is immediately thrown on the first call to the Idling event handler, since the type of the 'sender' argument changed from Application in the Revit 2011 API to UIApplication in the Revit 2012 API.
The code is still making use of some obsolete API calls, though, so I continued the migration to remove those as well:
Removal of Obsolete API Warnings
Compiling the flat migrated code described above results in the following warnings (copy and paste to an editor to see the untruncated lines):
------ Rebuild All started: Project: RevitWebcam, Configuration: Debug Any CPU ------
C:\a\src\revit\webcam\RevitWebcam\RevitWebcam\RevitWebcam\Command.cs(232,11): warning CS0618: 'Autodesk.Revit.DB.Analysis.AnalysisDisplayLegendSettings.SetTextTypeId(Autodesk.Revit.DB.ElementId, Autodesk.Revit.DB.Document)' is obsolete: 'this method will be obsolete from 2012.'
C:\a\src\revit\webcam\RevitWebcam\RevitWebcam\RevitWebcam\Command.cs(398,11): warning CS0618: 'Autodesk.Revit.DB.Analysis.SpatialFieldManager.UpdateSpatialFieldPrimitive(int, Autodesk.Revit.DB.Analysis.FieldDomainPoints, Autodesk.Revit.DB.Analysis.FieldValues)' is obsolete: 'This method is obsolete in Revit 2012; use the overload accepting the result index instead.'
C:\a\src\revit\webcam\RevitWebcam\RevitWebcam\RevitWebcam\Command.cs(386,23): warning CS0618: 'Autodesk.Revit.DB.Reference.GeometryObject' is obsolete: 'Property will be removed. Use Element.GetGeometryObjectFromReference(Reference) instead'
C:\a\src\revit\webcam\RevitWebcam\RevitWebcam\RevitWebcam\Command.cs(431,31): warning CS0618: 'Autodesk.Revit.DB.Reference.Element' is obsolete: 'Property will be removed. Use Document.GetElement(Reference) instead'
C:\a\src\revit\webcam\RevitWebcam\RevitWebcam\RevitWebcam\Command.cs(434,31): warning CS0618: 'Autodesk.Revit.DB.Reference.GeometryObject' is obsolete: 'Property will be removed. Use Element.GetGeometryObjectFromReference(Reference) instead'
Compile complete -- 0 errors, 5 warnings
To analyse the exact changes made to fix these, unpack the initial migration and the final version below in two directories side-by-side and use a file comparison tool.
Final Clean-up
Final clean-up included changes to comments and line breaks.
Here is a snapshot of RevitWebcam up and running in Revit 2012:
The image is upside-down now, which does not worry me, since this is all about getting it to display and update automatically at regular intervals, and all else is of no concern to us.
Disclaimer: This application does not adhere to the
recommended rules for
interacting with an external asynchronous process, e.g. a modeless dialogue, so this code should not be used as is for production use.
The code presented here is just a test showing some aspects of possible uses of the Revit API functionality.
It is by no means a production-level solution.
This actually applies to all the code provided in this blog anyway.
In truth, it applies just as well to many a commercial application that I find myself forced to use, much to my chagrin :-)
Developers have often asked how to programmatically synchronise with central, which is currently not supported by the Revit API.
I discussed this issue once again recently with Erik Eriksson of
White Arkitketer AB,
and he tested and verified that yet another risky workaround can be used to achieve this, as always
at your own peril.
Question: I have a worksharing project in which I am trying to open a file, make changes, synchronize the changes to the central file and then close the file.
I can complete all the tasks needed except synchronization part.
Is that not possible through the API?
I haven't found anything regarding this in the API manuals for Revit 2011 and 2012.
Is there a workaround for this?
Our project is a very complex hospital of 370 000 m2 and we have 17 different project files.
Making changes to these through the API would be a really powerful tool for us and we must be able to synchronize the changes to the central file quickly since there are 90 people working in these 17 files.
Any thoughts are welcome.
I've tried opening the central files, making changes, saving these and closing.
But that doesn't relinquish the elements changed.
Could this also be used to auto-confirm the synchronize with central dialog box that I can bring up using the API?
Answer: There are several different possible ways to suppress unwanted dialogues programmatically in Revit, both using the Revit and the Windows API:
The old method that was already available in Revit 2010 was to
use the DialogBoxShowing event, as you discovered, which can be used to dismiss a dialogue programmatically.
The Revit 2011 API also includes a comprehensive
failure processing API which
can used to do much more than reacting to the DialogBoxShowing event.
If all else fails and you just want to get rid of the dialogue box by any means, you can also use the Windows API to detect when it is displayed and dismiss it using my
dialogue clicker application.
In all three cases, you have to invest some research of your own to discover (in the debugger) what exact conditions you can use to reliably determine that the dialogue that is popping up really is the one you are interested in.
The approaches for that are described in the blog posts.
To optimise performance and minimise the impact and complexity, you might also want to set up your event handlers so that they are only active for a limited period of time, when you are expecting the unwanted dialogue to be displayed, and then remove them again after dismissing it.
Response: Success!
I used your solution to close the active document by
executing a SendKeys call in
a separate thread.
In principle I am doing the same thing and triggering the synchronisation using
I am only using the official UI functionality, and it should work irrespectively of whether a user or my SendKeys calls are driving it, shouldn't it?
Answer: Well, you should definitely pay close attention to the warnings of Arnošt Löbel, voiced in various blog posts, including the
disclaimer on
the very post you found, and yet again in all clarity in his class on this topic at Autodesk University 2011,
CP5381,
on asynchronous interactions and managing modeless UI, which would be well worth a blog post all on its own, at least.
Maybe using
UI Automation instead
might possibly be a slightly safer bet, since I heard rumours that the addressee of the SendKeys messages may sometimes get confused.
Response: Yes, indeed.
In order to make this as safe as possible, I execute it during an idling event to make sure that the document really is ready.
I also subscribe to the DocumentSynchronizedWithCentral to make sure that the document was synchronized.
I attended Arnošt's class during AU so I am very cautious with non-supported API-calls.
The advice to 'avoid Idling' may sound rather puritanical and a bit on the workaholic side, but I pay it special heed since it comes from Arnošt Löbel, Sr. Principal Engineer in the Revit development team, our resident Revit Idling expert, who already provided many (or almost all?) important insights in this and other related areas:
Arnošt also presented a class on the Idling event at Autodesk University 2011,
CP5381,
on asynchronous interactions and managing modeless UI, which would be well worth a blog post all on its own.
Arnošt clarifies that he is absolutely not against the Idling event, in general. The arguments below are only about using or not using Idling in one specific pattern – reacting to modifications of the model when both external well as internal data (in the model) could be changed in effect of the initial modification.
In such situations he does not recommend utilizing the Idling event, because it does not support the pattern well.
There are, of course, situations where Idling is very much in place and some scenarios when it is quite necessary, such as interaction with modeless dialogues and multithreaded add-ins.
With that out of the way, here is an interesting conversation with a number of new important hints between Arnošt and Joe Offord of
Enclos on
the pros and cons of
DMU, the dynamic model update mechanism,
DCE, the DocumentChanged event, and
IE, the Idling event.
Question: I have a process that is monitoring certain elements in a model via the DocumentChanged event. If I decide I need to perform a transaction on some of those elements I am storing the Document object via DocumentChangedEventArgs.GetDocument, along with some ElementIds, in a customized list that the Idling Event will process later.
I then subscribe to the Idling Event.
When the Idling Event triggers I retrieve the same document object from my list and perform transactions with it.
Is this a safe way of transferring elements between the DocumentChanged and Idling Events?
After attending Arnošt's class at AU I'm not 100% this transfer is "thread-safe".
If this isn't safe what would be a better way to define document-specific tasks for the Idling event to process?
I understand the DocumentChanged event does not me to make changes inside that event.
Answer: You present a design pattern that is not easy to solve or give a simple resolution for.
First of all, we need to know whether your approach is correct.
We have two patterns for applications to react model changes:
DMU – used when model changes trigger other changes in the same model
The document is modifiable, naturally
The changes are all within the document (the actions as well as the reactions)
You do not need to know about Undo and Redo because Revit handles it automatically
DocumentChanged – used when model changes trigger changes to external data
The document is not modifiable
The applied changes are made to external or non-model data
You need to know about Undo and Redo, because Revit does not manage the external data
You seem to be merging those two patterns.
As mentioned above, we need to determine whether this is necessary at all.
My recommendations and answers:
I, personally, do not recommend the DocumentChanged + Idling event, because – quite simply, it is not what Idling is for.
Is it a safe solution? Well, more appropriate question would be: Can it be implemented safely? The answer would be a yes; it can be implemented safely, but one needs to take into an account a lot of other things besides looking for DocumentChanged. When you have a gap between two calls into the API, you need to be aware of everything that can happen between those two times:
Document may be closed
Document may be saved, saved as, synced to central, reloaded latest, etc.
Besides all those other things that may happen between two events, it is indeed safe to rely on data gathered during the last DocumentChanged until the next Idling, because it is guaranteed that no other change has been done to the model (except the work-sharing operations mentioned above). To be absolutely certain about elements, always use Unique Ids over regular Ids. Unique Ids are guaranteed to works across work-sharing operations, regular Ids are not.
My preference is to do without the Idling event. I think that if I were challenged by the problem your customer is facing, I would try to implement it with DMU and DCh event only:
I would modify my external data on DCh even only
I would modify the model during DMU only
I would give a lot of thoughts to synchronize those two well
If I know more about the customer's project, I may be able to give more specific advices. Why do they need transactions if their tool is for is maintaining a live link between Revit elements and an external database, for example?
Response: Thank you for the feedback.
Let me try and further explain the problem I'm facing.
In Revit, I'm using a specific generic annotation family to track and quantify my objects in the model. We have an external database that stores all their properties. The annotation family has custom instance text parameters. In the external database, instances are tracked by their name and their host View name. I do not want to go
through reasons why we use an annotation symbol rather than modelling each object but I can assure you this is the most efficient method for us.
Via the API I've created an interface that allows the user to pick an object from the database and have it drawn on a Revit view as an AnnotationSymbol. The parameters are automatically filled out via
the API. I then have a routine that counts up all the object AnnotationSymbol instances in a particular view and manually draws them in a schedule using lines and text (as a Group: this is required because Revit will not create Schedules from
Generic Annotations). At the same time, all the instances of that object are uploaded to the external database.
Now, my goal is to have any changes to the model automatically sync with the external database. When a Document opens it collects all the existing annotation family instances and stores them in a global list using their ElementIds. I have
setup the DocumentChanged event set to track whether new instances of the annotation family have been created or previous instances have been deleted (by comparing the deleted ids). I would prefer to track the elements via UniqueId but
DocumentChanged only lists deleted elements via ElementId. If a change is flagged, I then subscribe to the IdlingEvent and store the Document HashCode and ElementId of the affected host View in global variables.
When IdlingEvent triggers I first verify the global variable of the Document is still open by comparing the stored HashCode with the open Document HashCodes. If I find a match, I then verify the ElementId is still a View element in the document.
If both are ok I then collect all the family annotation instances in the view, verify their parameters match what the external database is showing, sync the elements with the external database, and redraw the schedule on the sheet.
Hopefully this is making some sense. To recap, I need to do the following:
Verify the parameters in the AnnotationSymbol specified match the properties set in the external database.
Verify additions and deletions of the AnnotationSymbol instances are tracked "live" with the external database.
Keep the report and schedule on each view current with number of AnnotationSymbols on the view.
I realize this cannot always maintain a truly fool-proof connection with the external database so I also have an External Command that basically does the same thing that the Idling Event does.
I welcome any suggestions to deal with unique problem.
Answer: I still think that the DMU+DCE combinations is a superior solution to using IE+DCE.
Here is how it should work:
On DMU, look for added and deleted glass annotations. If there are any, sync with the database and update the schedule in the model as needed
On DCE, only do something if the operation was Undone, or Redone, or GroupRolledBack. Ignore the other operations. When there is a change in added or deleted glass annotations, update the database but do not change the model (because it is being taken care of automatically by Revit)
There will be no need for having global variables storing the document and/or view
Like I said, the IE+DCE can be used, in theory, but:
It is less natural an implementation and more complex too
There is a lot more the application needs to look for
There is no guarantee Idling would be raised after DCE and before a document is saved or closed
Therefore subscribing to DocumentSaving, DocumentSavingAs and DocumentClosing
Event with those additional events it is not guaranteed a document would be always in sync with the external database when it is saved
It is much less efficient, especially with a lot of undos and redos, because the external application must do what Revit would have otherwise done automatically and far more efficiently.
The Idling event, though it looks like a viable solution in many cases, it is a good approach in a few specific scenarios, mostly involving modeless dialogues and work threads. Beside these very specific scenarios, there is always a better way – more efficient, more practical, and easier too.
Again, the rule for this and similar patterns is rather simple:
If you need to react to a changes in a model by making other changes to it, use DMU
If you need to react to changes in a model by making changes in an external database, use DCE
If you need both, you use both DMU and DCE, but you still follow the above tactic
Response: Although I really like the intent of DMU, I still see some major limitations of it in 2012:
Inability to create new transactions inside the DMU
Inability to create group elements inside the DMU
DMU does not get called for rolled-backed or redone transactions (check me on this one)
Because of these limitations, I ended up using the (DCE+DMU)+IE for optimum performance.
The DCE and DMU events tracked changes to the model and called upon the IE when required.
The DCE tracks changes to AnnotationSymbols and DMU tracks changes to a specific View parameter.
The IE provided me the ultimate freedom to create transactions as I wish.
I understand there is a risk for things to change after DCE and before the IE.
Although the window for change is small, each programmer should decide how they want to deal with that risk.
Answer: I beg to disagree about the notion of DMU 'limitations'. Those listed above are not limitations, since DMU was designed exactly that way. No one is supposed to need a transaction and/or group inside a DMU, unless DMU is misused. Also, DMU does not need to be called at the event of Undo and Redo – if someone appears to need it, that someone does not use DMU correctly or is not clear on what the purpose of DMU is. I can only repeat what I wrote previously:
DMU – is for changes to the model in effect of other changes in the same model. All the changes will be and should be inside one transaction, because they are – obviously – related, which also mean that if one change is undone, the other (initial) changes need to be undone too. In another way – DMU is an extension of Revits regeneration, the last regeneration that always happens at the end of every transaction.
DCE – is for changes to external data reacting to modifications to the model. No transactions are needed here and the model is not supposed to be touched.
I agree that IE gives the ultimate freedom; I disagree that the freedom should be utilized in this particular case, but I also made it clear that it was 'I' who would not use IE if 'I' was the one solving this particular problem. I do not want to tell users what they have to do – they should have the freedom to do whatever they desire. I just provide an explanation for why one solution is more favourable than the other. IE has very limited use when it is efficient and useful – beyond that recommended use it becomes not so efficient and troublesome to maintain.
Extraneous Undo Entries
There is one more thing I do not 'like' about the Idling event, or – more precisely – about transactions created in the Idling event handler, when it is used outside of the recommended patterns (modeless dialogue, work threading, etc.). The transaction may come as a surprise to the unaware end user. The user pauses for a while, maybe leave her desk to get a cup of coffee, and when she returns there may be three new transactions on the undo stack. If I was her, I would most likely not like it. In contrast to that, changes made during DMU are expected – they are part of the action that is currently going on. There may not be transactions during DCE, so no surprises there. And if Idling is used to implement commands in a modeless dialogue, the user would not be surprised by an eventual transaction. In API programming it is not always about what way appears to be helpful to the programmer – it is about the end user. Revit addition should be made to best follow the natural flow of things. At least in my opinion.
Response: Yes I agree IE isn't the prettiest solution to most problems. The "mystery" transaction(s) created by the IE do affect the natural flow of the program. I tried my utmost to keep the IE duration as minimal as possible. I also took particular care to ensure I only created one transaction during the IE update. I'm sure the user would be shocked to see multiple phantom transactions created repeatedly.
My earlier use of the word "limitation" with regards to the DMU was probably misguided. For my particular work-flow, in my opinion, the DMU was simply not versatile enough. I needed to create groups on the fly and this is simply not permitted inside the DMU. I forgot to mention I also tried creating new families (in place of the groups) and load them into the project during the DMU but again, this is not permitted. I think this was the only thing that prevented me from using DMU+DCE without the IE.
Answer: Regarding your statement "only created one transaction during the IE update. I'm sure the user would be shocked to see multiple phantom transactions created repeatedly":
I did not even mean that one application would create multiple transactions in one single Idling event. But if more add-ins were written this way, and if the users happened to have, say 10 of them, there may be 10 or more transactions during one Idling, and a lot more in one coffee break (with Revit left unattended). It is because one change could lead to another (in a different add-in), which may lead back and trigger yet another change or changes in the original add-in, and so on. Everything would be tied together so tightly so it could be quite a mystery to the end user to figure the course of changes. And when the user decided to undo just some of the involuntary transactions and if the synchronization between Idling and DCE in the individual add-ins is not written perfectly and robust, the hell may as well open for the user (and her document) ;-) I am kidding, of course, but the fact remains that such situations happen in the real world and it is very hard to maintain. That is why I always recommend keeping transaction short and the changes atomic. Splitting one change into one-transaction-now and another transaction-later is almost always opening the door for troubles.
Again, this is my recommendation only.
Response: I think we all prefer Revit not open a gateway to hell but it's nice to know it has that much power! :)