Here is a very nice little sample on using the new Revit 2014 dockable panels by Håkan Wikemar of
AEC, Sweden.
In Håkan's words, it is close to the DockableDialogs SDK sample but easier to follow.
The DockableDialogs SDK sample demonstrates modeless dialog design, external events, and the new dockable dialogs UI API framework.
Håkan's DockableDialog sample described here focuses purely on the dockable panel functionality and nothing else.
One thing to note is that you need to register the panel before it can be displayed, and this needs to happen in a zero document state, i.e. with no project open.
In order for the external application ribbon panel to be accessible at all in zero document state, it needs to implement and register an availability class, and the associated command has to use manual transaction mode, as explained in
enabling ribbon items in zero document state.
Once this is done, the panel appears like this:
If you try to register it twice, an exception is thrown, naturally, saying "Cannot register the same dockable pane ID more than once. Parameter name: id".
Once the panel has been registered and a project activated, the commands to show and hide the dockable panel are presented:
Those two commands can happily use read-only transaction mode, since they do not modify the database in any way.
The dockable panel is initially displayed sharing the same floating window as the project browser:
It can be dragged off that pane to its own individual floating window:
Clicking 'Hide' removes it again.
Here is the full source code, which unfortunately sports rather long lines.
You can copy to a text editor or view the HTML source to see them in full, or simply download the zip file below:
In case you overlooked any of the following aspects, here is a list of the functionality demonstrated in the relatively few lines of code above:
Implementation of an external application and several external commands in the same assembly
Creation and population of a custom ribbon tab
Creation and population of a custom ribbon panel with command buttons
Loading of button images from .NET assembly resources
Implementation of a command availability class
Static enabling a command in zero document state
Dynamic toggling of command availability based on document state
Here is
DockableDialog.zip containing
the complete source code, Visual Studio solution and add-in manifest of this external application.
Many thanks to Håkan for this nice sample!
Addendum: Guy Robinson of redbolts.com adds to this and says:
I noticed the post on DockablePanes and Håkan's comment about finding the SDK sample confusing.
I did too.
I've been testing some UI ideas for a new app today and posted a basic version of something similar if interested.
I didn't want a register command on the ribbon and wanted more abstraction.
For instance, I didn't want the Page inheriting IDockablePaneProvider.
Therefore, registration is in the external application and uses some extension methods.
It is not perfect; it would require use of the Win32 API to capture the dock hiding/showing state if hidden from the dock itself.
Anyway, anyone interested can have a look at my
DockableUITest project on GitHub.
I am back from my vacation.
It was a wonderful break, and I feel ready and happy to get back to grips with everyday life and work again.
A query from a colleague caught up with me already on the way back, on exporting a whole bunch of selected wall panel parts to individual DXF files.
That gave me something nice and interesting to fiddle with during the nightly train ride up from Napoli to Milano and led to the following issues:
Before getting to that, though, here are a few final notes on my last vacation day in Napoli.
Last Day in Napoli
I had some wonderful pastry in the
Gran Bar Riviera pasticceria
on the Riviera di Chiaia.
Incidentally, an old palazzo partially collapsed there last week, just a few hundred meters away from where I was staying.
Further, I took a pair of old shoes to Gabriele, an extremely sweet and happy 86 years old shoemaker in the Spanish quarter.
He repaired them and they look better than new now.
He also invited us to coffee and told me his life story.
If you ever need a pair of shoes fixed, be sure to look him up in Vico Lungo del Gelso, 108, I-80134 Napoli :-)
Two final parting pictures capturing some of the decrepit charm of Naples...
Anyway, now I am back at work now again, and we return to the Revit API and my nocturnal dabbling on the train.
Exporting Individual Compound Wall Gyp Wallboard Parts
This query came in from my colleague William Spier, MEP & Design to Fabrication SME at Autodesk
(YouTube,
Revit Family Jewels).
He says:
Question: I would like to customise two pretty easy things.
The scenario is a compound wall on which I divide the gyp wallboard into standard (4’x8’) sheet size parts.
I need to export each of those sheets/parts to DXF format, but even though I have divided them into parts, Revit still groups them and exports them as one single DXF file – not as separate ones, like I need.
The only way around this is to isolate each part one at a time and export each one individually, which is WAY too laborious.
So I need to customize the export process so that:
Each part exports as a separate part, even though I globally selected them.
Each part exports named by its own unique code/identifier – whatever coding keeps each one distinct in the parts schedule.
Here is a snapshot from a simple sample project:
Answer: A first search for related API methods turn up the following potentially useful items:
Skipping the obsolete ones, it turns out to be really easy to implement a first working version that I packaged in an external command named ExportWallboard.
I first check that a valid document is provided and a 3D view is active that we can use for isolating and exporting each part:
if( null == doc )
{
ErrorMsg( "Please run this command in a valid"
+ " Revit project document." );
returnResult.Failed;
}
View view = doc.ActiveView;
if( null == view || !( view isView3D ) )
{
ErrorMsg( "Please run this command in a valid"
+ " 3D view." );
returnResult.Failed;
}
// Define the list of views to export, // including only the current 3D viewList<ElementId> viewIds = newList<ElementId>( 1 );
viewIds.Add( view.Id );
Basically, all that is required is to follow the steps suggested above:
Isolate each part
Define the DXF filename
Call the export method
Element e = doc.GetElement( id );
Debug.Assert( e isPart,
"expected parts only" );
Part part = e asPart;
ICollection<LinkElementId> lids
= part.GetSourceElementIds();
Debug.Assert( 1 == lids.Count,
"unexpected multiple part source elements." );
LinkElementId lid = lids.First<LinkElementId>();
ElementId hostId = lid.HostElementId;
ElementId linkedId = lid.LinkedElementId;
ElementId parentId = hostId;
ElementId partId = e.Id;
filename = string.Format( "{0}_{1}",
parentId, partId );
view.IsolateElementTemporary( partId );
doc.Export( _folder, filename, viewIds, opt );
The IsolateElementTemporary method requires a transaction, so even though the whole operation is theoretically read-only, we still need to specify manual transaction mode for this command.
We encapsulate the isolate and export method in a transaction that is later rolled back, so the model ends up unchanged after all.
Handling and Dismissing a Warning Message
However, a complication arises:
Calling the Export method with an isolated element in the current view displays a task dialogue warning message:
I repeatedly discussed how to automatically handle messages like this in the past, e.g. to
detach a workset.
To be notified of this message, we subscribe to the DialogShowing event:
Do not forget to unsubscribe afterwards.
In this case, we can do so at the end of the command.
To ensure that the unsubscription is performed whatever happens, regardless of any potential errors, I encapsulate the whole operation in a try statement and unsubscribe in its 'finally' clause.
try
{
// Register event handler for // "TaskDialog_Really_Print_Or_Export_Temp_View_Modes" // dialogue
uiapp.DialogBoxShowing
+= newEventHandler<DialogBoxShowingEventArgs>(
OnDialogBoxShowing );
DXFExportOptions opt = newDXFExportOptions();
string filename;
using( Transaction tx = newTransaction( doc ) )
{
tx.Start( "Transaction Name" );
foreach( ElementId id in ids )
{
Element e = doc.GetElement( id );
// . . .
view.IsolateElementTemporary( partId );
doc.Export( _folder, filename, viewIds, opt );
}
// We do not commit the transaction, because// we do not want any modifications saved.// The transaction is only created and started// because it is required by the// IsolateElementTemporary method.// Since the transaction is not committed, // the changes are automatically discarded.//tx.Commit();
}
}
finally
{
uiapp.DialogBoxShowing
-= newEventHandler<DialogBoxShowingEventArgs>(
OnDialogBoxShowing );
}
returnResult.Succeeded;
Initially, I did not yet know exactly which dialogue id to use to identify this specific message.
I therefore first implemented a dummy DialogBoxShowing event handler, triggered the event, and determined the dialogue id to use by re-running the command and looking at the event handler argument in the debugger.
As it turns out, the required dialogue id in our case is "TaskDialog_Really_Print_Or_Export_Temp_View_Modes".
We wish to retain the temporary isolate mode and export, i.e. select the second command link option.
Therefore, the final dialogue box showing event handler implementation becomes:
Adding Support for both Pre- and Post- Part Selection
In the initial implementation, I just went ahead and used the pre-selected set of parts defined by the user before launching the external command, accessible via the uidoc.Selection.Elements collection.
However, it is much more user friendly to also support post-selection.
For that case, it is also useful to implement a selection filter to simplify easy mass selection of the parts.
In order to handle the elements identically regardless of whether they were pre- or post-selected, I convert the selection set to a list of element ids in both cases.
For the pre-selection, I iterate over the pre-selected elements and test each one as follows:
// Iterate over all pre-selected partsList<ElementId> ids = null;
Selection sel = uidoc.Selection;
if( 0 < sel.Elements.Size )
{
foreach( Element e in sel.Elements )
{
if( !( e isPart ) )
{
ErrorMsg( "Please pre-select only gyp wallboard"
+ " parts before running this command." );
returnResult.Failed;
}
Part part = e asPart;
ICollection<LinkElementId> lids
= part.GetSourceElementIds();
if( 1 != lids.Count )
{
ErrorMsg( "Gyp wallboard parts has multiple"
+ " source elements." );
returnResult.Failed;
}
LinkElementId lid = lids.First<LinkElementId>();
ElementId hostId = lid.HostElementId;
ElementId linkedId = lid.LinkedElementId;
ElementId parentId = hostId;
ElementId partId = e.Id;
// Determine parent categoryElement parent = doc.GetElement( parentId );
Category cat = parent.Category;
ICollection<ElementId> cids
= part.GetSourceElementOriginalCategoryIds();
if( 1 != cids.Count )
{
ErrorMsg( "Gyp wallboard parts has multiple"
+ " source element categories." );
returnResult.Failed;
}
ElementId cid = cids.First<ElementId>();
//cat = doc.GetElement( id ) as Category;// Expected parent category is OST_WallsBuiltInCategory bic
= (BuiltInCategory)cid.IntegerValue;
if( BuiltInCategory.OST_Walls != bic )
{
ErrorMsg( "Pleqase pre-select only "
+ " gyp wallboard parts." );
returnResult.Failed;
}
if( null == ids )
{
ids = newList<ElementId>( 1 );
}
ids.Add( partId );
}
if( null == ids )
{
ErrorMsg( "Please pre-select only gyp wallboard"
+ " parts before running this command." );
returnResult.Failed;
}
}
The category of the source elements can be determined using the Part.GetSourceElementOriginalCategoryIds method, which unsurprisingly turns out to be OST_Walls.
I use that to check the part source element category like this in the final selection filter implementation:
classWallPartSelectionFilter : ISelectionFilter
{
publicbool AllowElement( Element e )
{
bool rc = false;
Part part = e asPart;
if( null != part )
{
ICollection<ElementId> cids
= part.GetSourceElementOriginalCategoryIds();
if( 1 == cids.Count )
{
ElementId cid = cids.First<ElementId>();
BuiltInCategory bic
= (BuiltInCategory)cid.IntegerValue;
rc = ( BuiltInCategory.OST_Walls == bic );
}
}
return rc;
}
publicbool AllowReference( Reference r, XYZ p )
{
returntrue;
}
}
For the post-selection, the selection filter ensures that no inappropriate parts can be selected, so no additional scan is required to test the references returned by the PickObjects method.
I can use a generic LINQ method to convert the collection of resulting references to the list of element ids:
// If no parts were pre-selected, // prompt for post-selectionif( null == ids )
{
IList<Reference> refs = null;
try
{
refs = sel.PickObjects( ObjectType.Element,
newWallPartSelectionFilter(),
"Please select wall parts." );
}
catch( Autodesk.Revit.Exceptions
.OperationCanceledException )
{
returnResult.Cancelled;
}
ids = newList<ElementId>(
refs.Select<Reference, ElementId>(
r => r.ElementId ) );
}
Here is
ExportWallboard03.zip including
the complete source code, Visual Studio solution and add-in manifest of the current state of this external command.
There are obviously still some implementation details to iron out.
Handling Temporary Transactions and Regeneration
Luckily, I took a closer look at the generated DXF output files before letting this command loose on humanity.
To my horror, I discovered that all files generated except the first one contain no geometry.
As it turns out, there is an issue with the regeneration and view settings in my original implementation.
After quite a bit of experimentation, I found out that I can successfully generate the individual files if I add the following steps:
Add code to switch off the first temporary isolation before applying the next one.
Commit the transaction to temporaily isolate each part before calling Export.
This requires encapsulating all the temporary transactions in a group and rolling back the entire group instead of the individual transactions, as described for the
temporary transaction trick touchup.
Separate the code to switch off the last temporary isolation and applying the new one into separate transactions.
The resulting loop exporting all the parts ends up looking like this:
using( TransactionGroup txg = newTransactionGroup( doc ) )
{
txg.Start( "Export Wall Parts" );
foreach( ElementId id in ids )
{
Element e = doc.GetElement( id );
Debug.Assert( e isPart,
"expected parts only" );
Part part = e asPart;
ICollection<LinkElementId> lids
= part.GetSourceElementIds();
Debug.Assert( 1 == lids.Count,
"unexpected multiple part source elements." );
LinkElementId lid = lids.First<LinkElementId>();
ElementId hostId = lid.HostElementId;
ElementId linkedId = lid.LinkedElementId;
ElementId parentId = hostId;
ElementId partId = e.Id;
filename = string.Format( "{0}_{1}",
parentId, partId );
if( view.IsTemporaryHideIsolateActive() )
{
using( Transaction tx = newTransaction( doc ) )
{
tx.Start( "Disable Temporary Isolate" );
view.DisableTemporaryViewMode(
TemporaryViewMode.TemporaryHideIsolate );
tx.Commit();
}
Debug.Assert( !view.IsTemporaryHideIsolateActive(),
"expected to turn off temporary hide/isolate" );
}
using( Transaction tx = newTransaction( doc ) )
{
tx.Start( "Export Wall Part "
+ partId.ToString() );
view.IsolateElementTemporary( partId ); // requires transaction//doc.Regenerate(); // this is insufficient
tx.Commit();
}
doc.Export( _folder, filename, viewIds, opt );
// We do not commit the transaction group, // because no modifications should be saved.// The transaction group is only created and // started to encapsulate the transactions // required by the IsolateElementTemporary // method. Since the transaction group is not // committed, the changes are automatically // discarded.//txg.Commit();
}
}
Here is
ExportWallboard04.zip including
the source code, Visual Studio solution and add-in manifest of the updated version.
I certainly expect more implementation details to crop up that need ironing out.
For instance, I could imagine adding some code to delete the PCP files that are generated together with the DXF output.
Or is there any reason to keep them?
I can also imagine that the part identification needs improving.
Currently, the command simply uses the part and its source element ids.
Maybe the part unique id would be better, or the parent element id needs checking, or is unnecessary snd can be completely removed.
Anyway, for a first stab, this implementation now looks pretty good to me.
Here is an interesting user interface contribution from Eduardo Teixeira of Autodesk:
Question: I am building a Revit add-in using the Revit API.
I would like to add a button for it to an existing out-of-the-box Revit ribbon panel using the Revit API in C#.
I could not find any documentation that shows how to do that.
Is it possible at all?
Answer: Yes, this can be done.
I am not certain whether it is officially supported, though.
You will have to explore on your own a bit.
The official API is limited.
You can however also use functionality from the .NET
UI Automation library
and from the AdWindows.dll .NET assembly included with Revit, similar to the one provided with AutoCAD.
Here are some previous discussion exploring related issues that might help:
Response: Thanks for the tip.
The article that really helped me was the first one you list.
I was able to add a button to the existing out-of-the-box Energy Analysis panel under the Analyze tab.
There are just two simple steps required achieve the desired result:
To add a custom button to the Analyze tab in the Revit Energy Analysis ribbon panel:
Add a reference to the AdWindows.dll .NET assembly provided with Revit.
Add the two snippets of source code listed below to the external application implementation:
Add a reference to the AdWindows.dll .NET assembly provided with Revit.
Don't forget to set the copy local flag to false on that.
This will also require references to the .NET WindowsBase and PresentationFramework assemblies, which in turn require PresentationCore and System.Xaml.
Creating and Adding the Button
In OnStartup, localise the existing tab and panel, instantiate a new ribbon button, add it to the tab, and subscribe to the UIElementActivated event.
Here is a simplified version of Eduardo's code set up to run on Jeremy's system and open the browser to display The Building Coder home page:
As you can see, I am triggering the reaction to the button click by subscribing to the UIElementActivated event.
Not sure if this is the correct way of doing it, but it works.
Handling the Event
In the event handler, check that the custom button was actually clicked and act on that, e.g. opening the browser at a specified URL.
Note that this requires no interaction with the Revit database, so there is no need to worry about a valid API context or transactions or any of that stuff.
Many thanks to Eduardo for sharing this!
Sample Run
Jeremy adds: for your convenience, here is
AddButton.zip containing
the source code, Visual Studio solution and add-in manifest for my version of this external application.
On my system, however, the button appears in the expected location but is disabled, so no action is triggered:
I am not sure why that happens.
Maybe Eduardo or somebody else can help resolve this.
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.
This greeting comes from my colleague Sandeep Kumar in
Bangalore, who explains:
Bhai Dooj is a festival of prayers from sister to brother, brother's protection for her sister.
May we all celebrate this Bhai Dooj with even more love and protection for our sisters and brothers.
Best wishes on this Bhai Dooj.
Thus fortified, let us turn to a Revit API issue, based on this excerpt from a useful little chat I had yesterday that might be of general use:
Implementing a Single Command for Multiple Buttons
Question: My external application displays a large number of all kinds of ribbon items.
Now I thought I might implement one single handler class for all of the external commands these items can trigger, instead of creating a separate one for each.
I would like to create only one CommandHandler class derived from IExternalCommand, and decide which function is required in its Execute method.
The problem I have is that the ExternalCommandData instance passed in does not provide any information about the source of the event, so there is no way of knowing which RibbonItem was clicked.
Is there a way to get that information, e.g. retrieve the name of the button clicked or something?
Answer: A good idea. It makes perfect sense. Unfortunately, no, this is not possible.
At least, the Revit API does not give you this access.
You might possibly have more luck using .NET
UI Automation.
It probably provides
UI Automation events that
can let you find out what button was clicked.
Using the standard Revit API functionality, you really do have to implement a separate command for each button.
You can easily funnel them all into the same handler yourself afterwards, if you like.
That would achieve almost what you want.
Implement a separate command for each button, determine which button was clicked, and then funnel all calls into the same command handler method.
The handler method can check the source of the event and branch out again appropriately.
Addendum: In his comment below, Rudolf Honke points to the very interesting discussion thread on
which pushbutton caused the ExternalCommand where
Revitalizer and
ollikat expound
on various alternative solutions, e.g. subscribing to the UIElementActivated event, creating commands at runtime and implementing one single base class inheriting IExternalCommand and other command classes derived from it.
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.
I have a situation where my slabs are cut by openings, however, and I need to retrieve the original uncut gross slab boundary.
Here is an example with two slabs A and B and an opening cutting out part of both of them:
The solution provided retrieves the cut out net slab boundary:
How can I obtain the following original gross uncut slab boundary?
Answer: One way to obtain the unmodified geometry of the slab is to use the temporary transaction trick to temporarily eliminate all openings and other elements affecting its shape.
It shows how the transaction handling for the temporary operation can be simply and cleanly encapsulated.
It analyses the material quantities of walls, floors, and roofs, and outputs the result to Excel by implementing the following steps:
Collect all roof elements
Iterate through each material found in each roof element
Find the net volume and area of the material
Store the material quantities
Write the results to the output file
Find the gross material quantities
Start a new transaction
Delete the elements that cut the host (doors, windows, openings)
Find the volume and area for each material
Store the material quantities
Write the results to the output file
Rollback the transaction
Repeat the above steps for walls and floors
Open the output file in Microsoft Excel
To determine the boundary lines of the floor slab with all opening removed, you could proceed in the same fashion as described in step 6 for obtaining the gross material quantities.
Temporarily delete then openings in a separate transaction, retrieve the original slab geometry, and abort the transaction to restore the original state.
This technique is easy to implement.
We reused this method for many different purposes a number of times, e.g.
Addendum: Arnošt Löbel pointed out that
this will not always work.
Some modifications require the transaction to be committed before they take full effect.
The
workaround for
that is to enclose it within a transaction group and roll back the group at the end.
Apphack @ AU
Do you have an interesting AutoCAD app you would like to publish?
Will you attend Autodesk University 2012 and would you like to win an attractive prize?
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 :-)
Here is a question on picking points while interacting with a modeless dialogue:
Question: I want to pick two points consecutively using PickPoint.
I am using a modeless dialog, and have a section of code that is called when a button is pressed.
However, after picking the first point, an exception is thrown saying "The user aborted the pick operation".
Do you have a solution for this?
Answer: I implemented a sample application providing an external command PickTwoPoints for you to demonstrate one possible solution.
I use the following code to generate and display a modeless dialogue to the user before the picking is initiated:
Form DisplayModelessForm( IWin32Window owner )
{
System.Windows.Forms.Form form
= new System.Windows.Forms.Form();
form.Size = newSize( 300, 100 );
form.Text = "Pick Two Points Modeless Form";
form.Show( owner );
return form;
}
The form is initially displayed, then hidden again before calling PickPoint.
As always when displaying a modeless form, I define its owner window to be the Revit main frame.
I use my JtWindowHandle wrapper class to convert the IntPtr returned by the Autodesk.Windows.ComponentManager ApplicationWindow property to an IWin32Window, which can be passed in to the form Show method to set its owner:
///<summary>/// Wrapper class for converting /// IntPtr to IWin32Window.///</summary>publicclassJtWindowHandle : IWin32Window
{
IntPtr _hwnd;
public JtWindowHandle( IntPtr h )
{
Debug.Assert( IntPtr.Zero != h,
"expected non-null window handle" );
_hwnd = h;
}
publicIntPtr Handle
{
get
{
return _hwnd;
}
}
}
The external command Execute method performs the following steps:
Determine the main Revit application window handle.
Display the dynamically generated modeless form.
Hide the form.
Select the two points.
Process the two resulting points, in this case by displaying them in the Visual Studio debug output window.
Redisplay the modeless form.
Close and terminate.
Here is the whole resulting Execute method mainline code implementing this:
publicResult Execute(
ExternalCommandData commandData,
refstring message,
ElementSet elements )
{
IWin32Window revit_window
= newJtWindowHandle(
ComponentManager.ApplicationWindow );
UIApplication uiapp = commandData.Application;
UIDocument uidoc = uiapp.ActiveUIDocument;
Selection sel = uidoc.Selection;
using( Form form = DisplayModelessForm(
revit_window ) )
{
form.Hide();
try
{
XYZ p = sel.PickPoint( "Point 1" );
XYZ q = sel.PickPoint( "Point 2" );
Debug.Print( string.Format(
"The two points you selected are {0} and {1}.",
PointString( p ), PointString( q ) ) );
}
catch( Exception e )
{
Debug.Print( e.Message );
}
form.Show();
}
returnResult.Succeeded;
}
As it now stands, this can obviously be packaged in an external command using read-only transaction mode, since the database is not affected in any way.
Here is
PickTwoPoints.zip containing
the complete source code, entire Visual Studio solution and add-in manifest of the PickTwoPoints external command.
Automatically Store Revit Model as Blob in the Cloud
On the cloud and mobile front, Saikat Bhattacharya published an article describing his
GbXmlCloudSync add-in demonstrating
how to use the DocumentSaved event and the Windows Azure blob storage service to automatically upload each new version of a Revit model to the cloud, for instance for environmental analysis.
On the same topic, here are two new short videos about the use of Autodesk BIM 360 for MEP and structural engineers.
For MEP, this can provide insight into energy consumption, improve visualisation, photorealistic presentation, and CFD simulation.
Structural enhancement possibilities include access to simulation tools for more informed decisions, conducting computational heavy simulation earlier and more often, and optimising structural workflows with static and finite analysis tools.
This could be the first step of a move towards the next generation of BIM for anyone, anywhere, at any time, with access to intelligent, model-based workflows through a broad range of cloud-based services providing mobility, accessibility, and infinite computing power.
Let me begin directly with some Revit API sample code today.
Here is a little external application showing how you can optionally prohibit family creation by subscribing to the cancellable DocumentCreating event:
classApp : IExternalApplication
{
void OnDocumentCreating(
object sender,
DocumentCreatingEventArgs e )
{
DocumentType typ = e.DocumentType;
if( DocumentType.Family == typ )
{
TaskDialog d = newTaskDialog(
"Open Family Editor?" );
d.MainInstruction = "Creating a new family "
+ "document... would you like to proceed?";
d.MainContent = string.Format(
"Document type: {0}\r\nCancellable: {1}",
typ, (e.Cancellable ? "Yes" : "No") );
d.CommonButtons = TaskDialogCommonButtons.Yes
| TaskDialogCommonButtons.No;
d.DefaultButton = TaskDialogResult.No;
if( TaskDialogResult.No == d.Show() )
{
e.Cancel();
}
}
}
publicResult OnStartup(
UIControlledApplication a )
{
a.ControlledApplication.DocumentCreating
+= newEventHandler<DocumentCreatingEventArgs>(
OnDocumentCreating );
returnResult.Succeeded;
}
publicResult OnShutdown( UIControlledApplication a )
{
returnResult.Succeeded;
}
}
Surprisingly short and simple, isn't it, and the code speaks for itself, does it not?
Here is
CancelFamilyCreation.zip containing
the full source code, Visual Studio solution and add-in manifest of this little add-in.
The UK National BIM Library and NBS
For some little less hard-core UK-related BIM news, Stephen Hamil of
RIBA Enterprises pointed
out a couple of interesting titbits related to NBS and the National BIM Library, a free resource for the UK construction industry:
Report from the first BIM user day with a number of big Autodesk users such as HOK, BDP, LOR, Balfour Beatty, Ryder, Ramboll...
Thank you, Stephen, for the note.
dpStuff Add-ins
Finally, I took a quick look at the free dpStuff Revit add-ins
ChangeTextCase and
NumberStuffByDirection,
which look both useful and interesting.
Thank you, Dima Chiriacov, for sharing these!
Here is another topic that recently kept me busy, with some helpful input from Martin Schmid and Steve Mycynek:
Selecting a Pipe Elbow Fitting
Question: I need to set the diameter of a pipe elbow fitting programmatically.
The elbow diameter obviously needs to be adapted to the pipe diameter.
The two pipes may also have different diameters.
I tried using the NewElbowFitting method to insert the elbow and simply pass in the two pipe connectors to it like this:
doc.Create.NewElbowFitting( con1, con2 );
This does successfully create an elbow fitting instance.
However, I don't see how to control which elbow symbol is inserted by this method.
Is there any way to influence this?
Answer: Calling the NewElbowFitting method is similar to using the Trim tool in the user interface.
When this method is called, Revit automatically chooses the appropriate elbow for you.
In Revit 2013, you can control the selection process through the routing preferences, whereas previous versions only enabled you to define one single default elbow, tee, etc.
The routing preferences support multiple settings for different materials and sizes.
The routing preferences API is demonstrated by the
RoutingPreferenceTools SDK sample.
It implements 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.
When you call NewElbowFitting, it inspects the pipe type
and finds the first elbow in the appropriate routing preferences.
If the elbow inlet and outlet sizes differ, you need a reducing elbow.
How to Use the Routing Preferences API to Set the Elbow Symbol
The NewElbowFitting method will insert the elbow symbol specified in the routing preferences.
Please note that while RP will choose the first symbol in the rule list that matches its criteria, you can always programmatically either set size criteria to ensure a later symbol is chosen for a given scenario, or re-order the rules using RoutingPreferenceManager RemoveRule and AddRule methods, at least temporarily, to force RP to choose a different elbow symbol than normal.
For example, if you have defined routing preference for 'Elbow Reducing - Threaded - MI - Class 150.rfa' and call this method, you will get an elbow that has different connection sizes as shown below on the left.
If you don't have a transitioning elbow, but you do have a reducer defined, you will get a result as below right.
Note that the reducer is quite odd in this particular model file :-)
In this case, both the Reducing Elbow and the Elbow + Reducer 'work'; which is used depends on their order in the definition of the RP.
The RP is the only way to influence this selection, both in the UI and in the API.
I Beg to Differ
In some cases, the outer diameter of the fitting and the pipes may differ.
This is a content issue.
Pipes and fittings are selected and connected based on the pipe 'Nominal Diameter' and the 'Nominal Diameter' of the fitting connector.
In the Pipe Settings, a user can make the 'outer diameter' of a pipe bigger or smaller than its nominal diameter.
Here is an example with the following pipe type properties:
Diameter 18 mm
Outer Diameter 18 mm
Inner Diameter 16 mm
Size 18 mm
The generic pipe elbow fitting connected to the pipes by calling the NewElbowFitting method has the following properties:
Nominal Diameter 18 mm
Size 18 mm - 18 mm
For a fitting, the geometry of the elbow is completely arbitrary.
If you would like the elbow and pipe to have the same outer diameter, there are two ways to achieve this:
Make the outer diameter of the pipes bigger.
Modify the geometry of the elbow to be smaller.
Welded pipe fittings, for example, would have the same outer diameter, whereas threaded fittings, PVC, and others, typically have a larger outer diameter than the pipe itself, because the pipe engages into the fitting.
Change Elbow Dimension
Saikat handled an independent but related case that also happened to catch my attention:
Question:
How can I change the diameter of an elbow pipe fitting, depending on the pipe diameter?
Answer by Saikat Bhattacharya:
To test this, I created a pipe with an elbow fitting attached at one end.
In the user interface, you can see that the nominal diameter parameter of the pipe is disabled, i.e. greyed out.
The only thing I can do interactively in the user interface is modify the Nominal Radius of the pipe.
Based on this observation, I wrote the following few lines of code to modify the Nominal Radius of the selected elbow fitting using the API and it worked well:
UIApplication uiApp = commandData.Application;
UIDocument uiDoc = uiApp.ActiveUIDocument;
Document doc = uiDoc.Document;
Transaction trans = newTransaction( doc );
trans.Start( "Change Diameter" );
foreach( Element e in uiDoc.Selection.Elements )
{
FamilyInstance i = e asFamilyInstance;
Parameter p = i.get_Parameter(
"Nominal Radius" );
p.Set( 10 * 0.08333 );
}
trans.Commit();
Finally, to wrap this up, a little note on another issue that also cropped up and really is completely unrelated :-)
Question: I would like to call my Revit add-in in a batch operation in order to process multiple Revit files.
The process would involve starting Revit, loading a model, invoking my add-in, and shutting down the application afterwards.
I don't see any obvious command line option to achieve this.
Is it possible at all?
Answer: Yes.
This can be easily addressed by creating an external application, implementing appropriate OnStartup and DocumentOpened events, and driving the whole thing through a journal file or even directly from the command line.
The whole EMEA DevTech team is meeting in Neuchâtel prior to the coming weekend's
annual Autodesk football tournament taking place here.
I won't be able to play myself, unfortunately, for the first time in many years, since I had a prior appointment with friends to go climb the
Spitzhorn mountain.
We also held a virtual meeting with Stephen Preston from the US, who is now our world-wide DevTech manager.
Here we are in virtual union, from left to right, Gary Wassell, Marat Mirgaleev, Vladimir Ananyev, Stephen (virtually), Jeremy, Adam Nagy and Philippe Leefsma:
Meanwhile, I finally got around to taking a quick look at a completely new exciting topic:
Here is another interactive interactive Revit programming environment by
Håkon Clausen of
Nosyko AS.
His RevitRubyShell is based on the interpreted language
Ruby programming language,
which is more modern than
Python and
inherently object oriented.
According to the introductory blurb on its home page, Ruby is a dynamic, open source programming language
with a focus on simplicity and productivity.
It has an elegant syntax that is natural to read and easy to write.
Kean Walmsley also had a look at it in its IronRuby incarnation for use in
programming AutoCAD.
He presents a first overview and introduction to Ruby in his initial post on
using IronRuby with AutoCAD.
Håkon says: I hope you will find this useful.
I use it a lot for rapid prototyping and testing when programming against the Revit API.
It sports the simplest installation I have ever seen for any Revit add-in whatsoever, the absolutely
ultimate one-click
RevitRubyShell installer,
which should be opened IE and will install the add-in directly, setting up an external application creating its
own simple single-button panel:
Clicking the button launches the Revit Ruby shell including a snippet of sample code that can be immediately executed by pressing the blue 'Run' arrow button:
Well worth looking at, and might prove a huge programming productivity booster.
Many thanks to Håkon for developing and sharing this!
I am now the proud owner of an Android tablet and happily thinking about things to do with it.
The first idea that comes to mind is working through something similar to Adam's
Revit model viewer for iOS that
Jim Quanci showed at the
AEC DevCamp.
Before getting into that, though, I should address some of the issues brought up by Senthil in
his
comment
on the discussion on
service-oriented architecture,
asking for information about Revit cloud development, e.g. a step by step process for cloud beginners.
Senthil also mentions the
cloud computing Revit demo on the
ADN cloud computing page,
its add-in manifest, assembly DLL file location, how to deploy it, how to interact with Revit through web services, and developing a service application in general.
Here are a couple of starting points for addressing these issues:
All of these topics are addressed in depth by Gopinath Taget's cloud and mobile presentation, both at Autodesk University last year and most recently at the AEC and Manufacturing DevCamps earlier this month, each of which boasted a separate track on cloud and mobile technologies.
Gopi's four sessions at the AEC DevCamp covered:
An Overview of Cloud Computing:
Learn what cloud computing is all about, what kind of applications can be written for and run on the cloud, when it is suitable to use the cloud, when it is not. Learn about the popular commercial cloud service providers including Amazon Web Services (AWS), Microsoft Azure and Google App Engine and how to use them. Learn the similarities and differences between the cloud services they provide, the advantages of using one over the others and the coverage and sophistication of the APIs provided to use their cloud services. The class includes demonstrations and code review of sample cloud applications for Autodesk Revit, AutoCAD Civil 3D, AutoCAD and AutoCAD WS.
State of the Art of Autodesk Cloud and Mobile Apps:
Learn about current Autodesk cloud services, their capabilities and APIs. We will talk about and demonstrate AutoCAD WS, Autodesk 360, and Autodesk Photofly web services in depth. Demonstrations and instruction will be based on Microsoft Windows, Apple's iOS and Google's Android. The class will end on an exploration where these web services are going and - at a high level - where Autodesks web services are headed.
Introduction to Mobile app development – Apple's iOS:
Learn about programming on iOS devices. Learn where you need to go and what you need to do to start programming on iOS. Learn about the SDKs important for graphics intensive software development including WebGL and OpenGL ES and how to get started working with them. This class includes a detailed "start to finish" look at development of a simple iOS mobile App with a basic user interface.
Introduction to Mobile app development – Google's Android:
Learn about programming on Android devices. Learn where you need to go and what you need to do to start programming on Android. Learn about the SDKs that would be important to a CAD developer like WebGL and OpenGL ES and how to get started on them. This class will also demonstrate creating a simple Android mobile App with simple user interface from start to finish.
Gopi's presentations and samples are currently publicly available from Buzzsaw together with all the rest of the AEC and Manufacturing
DevCamp 2012 material.
The Apollonian Gasket Cloud Service
Probably the greatest and deepest exploration of using cloud services over a wide variety of technologies and devices was created by Kean Walmsley.
He explores making use of just about all imaginable combinations of cloud service providers and clients.
The
Apollonian gasket cloud & mobile series summary boasts
the following impressive table of contents:
This really is a huge wealth of material and should provide ample words of wisdom for any brave seeker venturing out on these now no longer unmapped paths.
AEC Cloud Demo
Returning to my own much more modest efforts in this realm so far based on the Revit API, Senthil mentions the
AEC cloud demo on the
ADN cloud computing page.
That is an eleven minute recording of a very simple add-in running on Revit 2012, originally created for the DevDays conferences in the end of 2010.
I recently migrated the sample add-in to Revit 2013.
Here is
AecMatInfoClientRevit2013.zip containing
its entire source code, Visual Studio solution and add-in manifest.
As explained in the recording, it simply defines two commands to read data from certain Revit element parameters and store them in a simple cloud-hosted database, and vice versa to read data from the database and populate it back into the Revit parameters again.
Revit Viewer via OBJ
As said, the next thing I would like to look at is a Revit model viewer.
Instead of using Adam's approach via a custom geometry file format, I thought I might make use of the
Wavefront OBJ file
format, which seems to be pretty standard and compact.
My current tentative outline looks like this:
Describe Adam's Revit cloud and mobile demo (well, Adam is doing this himself):
RVT add-in exporting triangulated geometry faces via custom ASCII file format, uploading to cloud
Mobile device accessing cloud data, custom iOS viewer reading and displaying 3D view of custom format
Implement a RVT add-in that exports OBJ file format instead
Standard format, can be used ubiquitously
Optimise that file format to significantly reduce file size and increase speed and efficiency
View OBJ file format on mobile
Using standard viewer
Implement custom Android viewer
It will be interesting to see how I do during the next few days... especially for me :-)
Please note that the material is still not completely finalised.
Some items will still be updated.
The location specified above is only temporary and will change, so don't use it for any long-term links, or be ready to update thm once the final archival location is in place.
I am normally not very concerned about Revit content, although that aspect is often of equal or greater importance than the API.
Here is a little item that happened to catch my attention anyway:
A
library of non-proprietary generic construction details containing
over 120,000 entries in native Revit, DWG and PDF file formats is now publicly available.
It was built in 2008-2009 to an agreed set of standards within a single office, providing consistency throughout.
The library also includes over 1,000 Revit components to modify the details.
Being provided in RFA, RFT and DWG format, the details and components can easily be modified.
Here is an interesting little overview and comparison of possibilities to react efficiently to the addition of certain elements to the database.
Two easy ways to achieve this are by subscribing to the unspecific DocumentChanged event, or by registering a more specific DMU dynamic model updater.
If you subscribe to an event or register an updater, it is also important to keep in mind to unsubscribe or unregister when you are done with it.
In this case, the aim is to display a message box to the user when a new elevation view is added.
Subscribe to DocumentChanged to React to Elevation View Creation
It is easy to detect when a specific element is added, such as a new elevation view, for instance by subscribing to the DocumentChanged event. This can be done either in the OnStartup method of an external application or from an external command. In either case, you should pay attention to also unsubscribe from the event when it is no longer needed, for instance on application shutdown, at the end of the command, or from some other place.
In order to test the behaviour simply from within the standard framework of The Building Coder sample collection, I implemented a simple external command CmdElevationWatcher subscribing to the event.
The event handler receives a DocumentChangedEventArgs instance providing three separate collections of ids of added, deleted and modified elements.
We pass in the list of added ids to the FindElevationView method, which returns the first elevation view in the given element id collection if one exists, causing a message box listing it to be displayed.
Here is the entire code of this external command implementation:
///<summary>/// React to elevation view creation subscribing to DocumentChanged event///</summary>
[Transaction( TransactionMode.ReadOnly )]
classCmdElevationWatcher : IExternalCommand
{
///<summary>/// Return the first elevation view found in the /// given element id collection or null.///</summary>staticView FindElevationView(
Document doc,
ICollection<ElementId> ids )
{
View view = null;
foreach( ElementId id in ids )
{
view = doc.GetElement( id ) asView;
if( null != view
&& ViewType.Elevation == view.ViewType )
{
break;
}
view = null;
}
return view;
}
///<summary>/// DocumentChanged event handler///</summary>staticvoid OnDocumentChanged(
object sender,
DocumentChangedEventArgs e )
{
Document doc = e.GetDocument();
View view = FindElevationView(
doc, e.GetAddedElementIds() );
if( null != view )
{
string msg = string.Format(
"You just created an "
+ "elevation view '{0}'. Are you "
+ "sure you want to do that? "
+ "(Elevations don't show hidden line "
+ "detail, which makes them unsuitable "
+ "for core wall elevations etc.)",
view.Name );
TaskDialog.Show( "ElevationChecker", msg );
}
}
publicResult Execute(
ExternalCommandData commandData,
refstring message,
ElementSet elements )
{
UIApplication uiapp = commandData.Application;
Application app = uiapp.Application;
// Subscribe to DocumentChanged event
app.DocumentChanged
+= newEventHandler<DocumentChangedEventArgs>(
OnDocumentChanged );
returnResult.Succeeded;
}
}
After this command has been executed, the event is subscribed to.
Any subsequent elevation view insertion results in the message box being displayed, for instance when I duplicate the East elevation view:
This approach has two significant disadvantages:
It reacts erroneously when a family is loaded.
Its performance is suboptimal.
1. When a family is loaded into a project, its views are also inserted with it.
This insertion currently triggers a DocumentChanged event on the elevation views as well.
For instance, if I load the standard rectangular column 'family M_Rectangular Column.rfa', a similar message appears:
This is unintentional and potentially confusing.
2. The performance is not optimal, since DocumentChanged reacts to each and every modification of the document.
Furthermore, every single added element needs to be examined to check whether it is an elevation view.
Both of these disadvantages can easily be avoided by using the dynamic model update mechanism DMU instead.
Install Dynamic Model Updater to React to Elevation View Creation
As said, the dynamic model updater mechanism can easily be used to determine that specific elements have been added to the database, and it avoids the DocumentChanged event disadvantages mentioned above, since it reacts only to certain well-defined modifications on a certain well-defined set of elements used to set up the updater trigger.
First we need to implement our updater class to display a message if an elevation view is added:
///<summary>/// Updater notifying user if an /// elevation view was added.///</summary>publicclassElevationWatcherUpdater : IUpdater
{
staticAddInId _appId;
staticUpdaterId _updaterId;
public ElevationWatcherUpdater( AddInId id )
{
_appId = id;
_updaterId = newUpdaterId( _appId, newGuid(
"fafbf6b2-4c06-42d4-97c1-d1b4eb593eff" ) );
}
publicvoid Execute( UpdaterData data )
{
Document doc = data.GetDocument();
Application app = doc.Application;
foreach( ElementId id in
data.GetAddedElementIds() )
{
View view = doc.GetElement( id ) asView;
if( null != view
&& ViewType.Elevation == view.ViewType )
{
TaskDialog.Show( "ElevationWatcher Updater",
string.Format( "New elevation view '{0}'",
view.Name ) );
}
}
}
publicstring GetAdditionalInformation()
{
return"The Building Coder, "
+ "http://thebuildingcoder.typepad.com";
}
publicChangePriority GetChangePriority()
{
returnChangePriority.FloorsRoofsStructuralWalls;
}
publicUpdaterId GetUpdaterId()
{
return _updaterId;
}
publicstring GetUpdaterName()
{
return"ElevationWatcherUpdater";
}
}
With this in place, we can implement a second external test command CmdElevationWatcherUpdater to instantiate and register our updater and define a trigger for it.
The trigger reacts only to the creation of new view elements:
After this command has been executed once, the updater is registered and its trigger installed.
Like before, any subsequent elevation view insertion results in its message box being displayed, for instance when I again duplicate the East elevation view:
Unsubscribe from Event and Unregister Updater
Before letting this loose on the general public, I thought I would be a good citizen for the nonce and clean up after myself.
To do so, I modified both command implementations so that each call to the command toggles the event subscription or updater registration on and off.
In both cases, this was very easily achieved.
I simply implemented a static member variable to hold the event handler delegate or the updater and initialised it to null.
It looks like this for the DocumentChanged event handler:
///<summary>/// Keep a reference to the handler, so we know /// whether we have already registered and need /// to unregister or vice versa.///</summary>staticEventHandler<DocumentChangedEventArgs>
_handler = null;
It is even simpler for the updater:
///<summary>/// Keep a reference to the updater, so we know /// whether we have already registered and need /// to unregister or vice versa.///</summary>staticElevationWatcherUpdater _updater = null;
Each time the command is run, it either subscribes/registers and sets the reference if it was null previously, or unsubscribes/unregisters and nulls it in the other case.
Here is the relevant updated code snippet for the DocumentChanged event:
Here is
version 2013.0.99.0 of
The Building Coder samples including the two new commands.
I hope you find this comparison useful, appreciate how simple both of these mechanisms are to use, and understand the advantages offered by the DMU.
Addendum: As Victor very correctly points out below, I omitted highlighting the main difference between the DocumentChanged event and the DMU mechanism, since we have discussed it several times in the past:
The DocumentChanged event is not raised until after the transaction causing it has been closed, so the changes made cannot be cancelled.
If elements were deleted, they are gone by the time you receive the notification; all you receive from the event handler argument is their element ids, and you cannot even find out what type they were.
The updater is called within the same transaction as the modification causing it, providing much more control.
I returned safe and sound from Boston and the AEC DevCamp and started recuperating from the intensive week at the conference and DevLab.
I love being back home again!
The API wishlist survey with your feedback on your API needs has a strong influence on the direction of the future API, so please take the time to fill it out.
Our engineering teams use this feedback to help prioritise the areas of API enhancements in coming releases.
If you wish, you can leave your e-mail address and we will send you the final results when they become available.
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.
The first thing that happened here at DevCamp was that I was personally really deeply touched and honoured.
The conference was opened by Jim Quanci, director of ADN, the Autodesk Developer Network.
He asked all of us twenty-five-plus Autodesk employees up onto stage and introduced us one by one.
When the turn came to your humble author (or not so humble, after all), he asked all readers of this blog to raise their hands.
Almost every person in the room did.
Deep thanks to all DevCamp participants and to all you readers out there for your appreciation and support; it really helps motivate and improve the work of us all.
Grazie.
Jim's introduction wound up with some impressive cloud and mobile demos.
Adam presented his Revit 3D model cloud uploader and mobile device viewer, which I definitely want to write a separate post on as soon as possible, really soon now!
Come on Adam, let's do it!
Augusto presented his cloud based cost per unit for AEC materials demo.
Nicolas Magnon gave the keynote speech with interesting insights on the AEC market and current Autodesk developments and focus areas.
Some of the questions and answers to Nicolas were really interesting, and probably not public, even though this is a public event, as far as I know.
I don't even dare mention the two main Revit API topics that were discussed.
You'd have to be here, man.
After that we moved on to the various lectures, split into the following five parallel tracks:
Revit Beginner
Revit Expert
Infrastructure and Other
Cloud/Mobile and Other
Business and Other
I started off presenting the first session in the expert track, and then was able to hang around in the same room to attend the interesting presentations of members of the Revit API development team, going through the following topics, in chronological order:
I never got around to writing about this class in much detail here on the blog, mainly because the AU handout is so good and complete that there was never any need for it.
Here is an overview of the discussions related to this area that I published here anyway so far:
Anyway, the first session here at AEC DevCamp was based on that AU class, so I now updated the sample code and presentation for Revit 2013.
The migration was done in ten minutes after I got up this morning.
That also gave me a chance to admire the beautiful sunrise before the sun disappeared again up into the clouds above.
As in previous migrations, I simply updated the Revit API assembly references from 2012 to 2013, bumped the .NET framework version from 3.5 to 4, and replaced the obsolete Document Element property usages via the get_Element method to its new GetElement replacement.
There was one single further issue I ran into, in the wall face selection filter, which was using the obsolete Reference GeometryObject property.
I fixed that like this:
///<summary>/// Selection filter allowing only wall elements.///</summary>classWallFilter : ISelectionFilter
{
publicbool AllowElement( Element e )
{
return e isWall;
}
publicbool AllowReference( Reference r, XYZ p )
{
//return r.GeometryObject is Face; // 2012return r.ElementReferenceType == // 2013ElementReferenceType.REFERENCE_TYPE_SURFACE;
}
}
After migrating the add-in, I also rearranged it slightly.
I renamed the About command and added an external command Cmd_7_DataStorage to exercise the new Revit 2013 DataStorage element, based on
Victor's
DataStorage sample.
Here is the list of the commands now provided by this add-in, or rather a list of its project files:
It is worthwhile pointing out that the external application implementation is neat, since it generates the ribbon panel and the entries for the individual commands in a loop.
Here is the resulting panel:
The About box implementation is also neat, since it reads the information it displays from the add-in assembly DLL attributes instead of duplicating that information:
Here is
Estorage_2013.zip containing
the full updated sample application including its add-in manifest, Visual Studio solution and full source.
For good measure, here is the updated
slide deck as
well.
Materials, Physical Properties and Compound Structure
After my own session on extensible storage, I could stay put right there in the same room to join the one by Steven Mycynek on 'Revit Materials, Physical Properties and Compound Structure API Basics', with the following agenda:
Material Model History
Material Properties
Dealing with Units
Working with Materials
GbXML’s role in Revit
Family Thermal Properties
Layered Assemblies
Layered Assembly Thermal Properties
A few last enhancements
From here on, my notes are very fragmentary.
Hopefully, you will be able to access the DevCamp materials on the internet quite soon.
At that point, these notes might help you decide what to explore.
Actually, I added a
snapshot of the materials
in their current state below, so please refer to those for more details.
Revit 2011 material inheritance model, in 2012 property set, obsolete material subclasses.
Revit 2013 material asset model, material subclasses removed.
Why? Extensibility, add new assets instead of deriving.
Airmax, consolidate all material handling across AutoCAD, Inventor, Revit, all Autodesk products to share same material library.
Material property hierarchy, PropertySetElement, StructuralAsset and ThermalAsset, discoverable properties, new in 2013.
Asset types and properties.
Units: length in feet, all other units in SI units, making compound units a bit confusing.
UnitUtility.cs Steve's personal sample code...
ExporterIFCUtils.ConvertUnits...
There is no official API from display units back to system units, but Steve's sample includes the method DisplayUnitsToSystem.
FormatUnitValue<ValueType>...
Using ProjectUnits.get_FormatOptions( unitType )
Duplicating a material, a deep copy, including all assets; creating one from scratch.
FamilyThermalProperties...
Geometry API
After that, I attended Scott Conover's session on 'Geometry API in Autodesk Revit'.
It is a continuation of his AU 2011 presentation
CP4011 'Geometric Progression: Further Analysis of Geometry Using the Autodesk Revit 2012 API',
enhanced for the Revit 2013 API.
In consists of two main sections, on geometry extraction fundamentals and tools, and supports you in the following areas:
Extract and analyze the geometry of existing Revit elements
Create and manipulate temporary curve and solid geometry
Find elements by 3D intersection
Find elements by ray projection and filtering
Apply an ExtrusionAnalyzer to geometry
Utilize parts to analyze geometry of HostObjects and their layers
Extract and analyze the boundary geometry of rooms and spaces
Analyze the geometry of point clouds
More details are available on the developer guide wiki, and examples of use of all the tools discussed are provided in the sample materials.
Display solids on the graphics screen marking the areas around doors that must not be obstructed in order to ensure safe fire or other emergency egress.
The last session of the day in the Revit expert track was Arnošt Löbel's class on the Core Revit API Frameworks, covering the following topics:
Regeneration modes
Transaction modes
Transaction phases
Document modifiability
Element validity
Object lifespan
API events
External commands
API callbacks
Dynamic updaters
API Scopes
API Firewall
I would have loved to attend, but was unfortunately forced to flee, breathe some fresh air, escape the so-called air conditioning, go running, join the hashers, the
Boston Hash House Harriers,
the "drinking club with a running problem", that Michael Priestman introduced me to
two years back.
Once again, it was great fun.
Snapshot of Materials
Here is a snapshot of the materials of today's four Revit API expert sessions:
This year's Revit and Navisworks API wishlist surveys are now open, as
Mikako Harada pointed out on the AEC DevBlog.
Here are the direct links to them:
The API wishlist survey with your feedback on your API needs has a strong influence on the direction of the future API. so please take the time to fill it out.
Your feedback will not fall on deaf ears.
It will take less than five minutes to complete each survey.
Well, actually, to tell the truth, it took me almost ten to do the Revit one.
There are some pretty interesting questions in there.
Our engineering teams use this feedback to help prioritise the areas of API enhancements in coming releases.
If you wish, you can leave your e-mail address and we will send you the final results when they become available.
The deadline for the Revit and Navisworks surveys is June 1, 2012.
The wishlist surveys for other products will follow shortly.
AU instructors are some of the best in their industries and in academia today.
Please share your knowledge and become part of this prestigious group of experts.
The deadline to submit proposals is Tuesday May 22, 2012, just the day after tomorrow.