I was presented with an opportunity earlier this year to contribute to a book project that a coworker of mine in our New York office was working on by the name of James Vandezande, maybe you've heard of him?... He was looking for some help in getting an API chapter put together for his book entitled "Mastering Autodesk Revit Architecture 2011"
It sounded like a pretty cool opportunity, so I whipped up a quick 40 or so page chapter complete with a basic example for batch exporting Revit families... The chapter that I contributed is Chapter 24 "Under the Hood of Revit"...
The 1,122 page paperback book is out now and is available on Amazon!
Sunday, July 25, 2010
Friday, July 16, 2010
External Images to Individual Drafting Views
Have you ever had the need to generate hundreds or possibly thousands of drafting views to host photograph images for the purpose of tagging snapshot location and direction on a plan drawing???... Say maybe for a CA purpose?... or maybe for a facility assessment?... Well I was presented with such a problem and this is what I did to solve it since importing the several hundred photos into my model and generating the drafting views to go with them would have taken an eternity.
The way this app breaks down is that the user will be presented with a dialog that they can enter an override for the image width and/or height if they choose. There is a browse button that they will use to select multiple image files for import. After the use selects their images, Revit will generate a new drafting view named the same as the image file and then import the image into it, repeating until complete. The updates made to the Revit API in the 2011 products make this process capable of importing over 500 images in less than a minute (somehow)...
This code and any code I ever provide is of course provided as-is... use at your own risk.
This example utility utilizes one helper class to access and describe the parameter objects, one user form, and a main command class. First lets get the familiar main command entry point class out of the way...
The parameter helper class used to save data to the parameter when we find is provided below.
The user form is very simple and only contains a few basic controls. Follow the steps below to setup the form. I've added two buttons. One for browsing to the images and a cancel button. Two textboxes are placed to accept the user's optional dimension overrides for the imported images. A progress bar is then added along the bottom for process posting...
The form code is fairly simple as well. There are only a few basic subs and a few key form events to prevent the user from entering invalid characters into our textbox inputs (TextBoxHeight_KeyPress and TextBoxWidth_KeyPress).
We will utilize a method to retrieve the last element in the database in our image import loop so that we can easily retrieve our new image element for setting its width and or height overrides.
The way this app breaks down is that the user will be presented with a dialog that they can enter an override for the image width and/or height if they choose. There is a browse button that they will use to select multiple image files for import. After the use selects their images, Revit will generate a new drafting view named the same as the image file and then import the image into it, repeating until complete. The updates made to the Revit API in the 2011 products make this process capable of importing over 500 images in less than a minute (somehow)...
This code and any code I ever provide is of course provided as-is... use at your own risk.
This example utility utilizes one helper class to access and describe the parameter objects, one user form, and a main command class. First lets get the familiar main command entry point class out of the way...
Imports Autodesk.Revit.DB
Imports Autodesk.Revit.UI
Imports Autodesk.Revit.Attributes
''' <summary>
''' Import Images to Drafting Views
''' </summary>
<Transaction(TransactionMode.Automatic)> _
<Regeneration(RegenerationOption.Manual)> _
Public Class cmdDraftingViews
' Implement the RevitAPI entry
Implements IExternalCommand
' Post version date to title of form objects
Public Const appVer As String = "V2011.07.15"
' Main Command Entry
Public Function Execute(ByVal commandData As ExternalCommandData, _
ByRef message As String, _
ByVal elements As ElementSet) As Result _
Implements IExternalCommand.Execute
Try
Dim dlg As New formMain(commandData, appVer)
dlg.ShowDialog()
Return Result.Succeeded
Catch ex As Exception
Return Result.Failed
End Try
End Function
End ClassImports Autodesk.Revit
Imports Autodesk.Revit.DB
''' Helper class to define a parameter
Public Class clsPara
Private m_parameter As DB.Parameter
Public Sub New(ByVal parameter As DB.Parameter)
m_parameter = parameter
End Sub
Public Property Value() As String
Get
Try
Return GetParameterValue(m_parameter)
Catch
Return Nothing
End Try
End Get
Set(ByVal value As String)
Try
SetParameterValue(m_parameter, value)
Catch
End Try
End Set
End Property
Public Shared Function GetParameterValue(ByVal parameter As Parameter) As String
Select Case parameter.StorageType
Case StorageType.[Double]
Return parameter.AsDouble
Case StorageType.ElementId
Return parameter.AsElementId.ToString
Case StorageType.[Integer]
Return parameter.AsInteger
Case StorageType.None
Return parameter.AsValueString()
Case StorageType.[String]
Return parameter.AsString()
Case Else
Return ""
End Select
End Function
Public Shared Sub SetParameterValue(ByVal parameter As Parameter, ByVal value As Object)
If parameter.IsReadOnly Then
Exit Sub
End If
Select Case parameter.StorageType
Case StorageType.[Double]
parameter.SetValueString(TryCast(value, String))
Exit Select
Case StorageType.ElementId
Dim myElementId As DB.ElementId = DirectCast((value), DB.ElementId)
parameter.[Set](myElementId)
Exit Select
Case StorageType.[Integer]
parameter.SetValueString(TryCast(value, String))
Exit Select
Case StorageType.None
parameter.SetValueString(TryCast(value, String))
Exit Select
Case StorageType.[String]
parameter.[Set](TryCast(value, String))
Exit Select
Case Else
Exit Select
End Select
End Sub
End ClassThe user form is very simple and only contains a few basic controls. Follow the steps below to setup the form. I've added two buttons. One for browsing to the images and a cancel button. Two textboxes are placed to accept the user's optional dimension overrides for the imported images. A progress bar is then added along the bottom for process posting...
The form code is fairly simple as well. There are only a few basic subs and a few key form events to prevent the user from entering invalid characters into our textbox inputs (TextBoxHeight_KeyPress and TextBoxWidth_KeyPress).
We will utilize a method to retrieve the last element in the database in our image import loop so that we can easily retrieve our new image element for setting its width and or height overrides.
Imports Autodesk.Revit
Imports Autodesk.Revit.DB
Imports Autodesk.Revit.UI
Imports System.IO
Public Class formMain
Private m_SelectedJPG() As String
Private m_RvtDoc As DB.Document
Private m_width As Double
Private m_height As Double
Public Sub New(ByVal cmd As ExternalCommandData, ByVal apv As String)
InitializeComponent()
m_RvtDoc = cmd.Application.ActiveUIDocument.Document
Me.ProgressBar1.Visible = False
Me.Text = "Generate Drafting Views for Image Files - " & apv
End Sub
Private Function GetElementCount() As Integer
Return GetElements.ToElements().Count
End Function
Private Function GetElements() As DB.FilteredElementCollector
Dim collector As New DB.FilteredElementCollector(m_RvtDoc)
Return collector.WhereElementIsNotElementType()
End Function
' Return all database elements after the given number n.
Private Function GetElementsAfter(ByVal eInt As Integer, ByVal rvtDoc As Document) As List(Of DB.Element)
Dim elems As New List(Of DB.Element)
Dim fec As DB.FilteredElementCollector = GetElements()
Dim i As Integer = 0
For Each e As DB.Element In fec
i += 1
If eInt < i Then
elems.Add(e)
End If
Next
Return elems
End Function
Private Sub ImportImages()
' Collect the list of drafting views
Dim ViewsElements As New List(Of DB.Element)
Dim CollectorSheets As New DB.FilteredElementCollector(m_RvtDoc)
CollectorSheets.OfCategory(DB.BuiltInCategory.OST_Views)
ViewsElements = CollectorSheets.ToElements
' Start adding the views and importing the images
For i = 0 To UBound(m_SelectedJPG)
Dim imgFileName As String = Path.GetFileNameWithoutExtension(m_SelectedJPG(i))
' Only create the view if it does not already exist
For Each el As DB.Element In ViewsElements
If el.Name.ToUpper = imgFileName.ToUpper Then GoTo prepNextView
Next
' Only continue here if the view does not exist
Dim dv As DB.ViewDrafting = m_RvtDoc.Create.NewViewDrafting
dv.Name = imgFileName
' Link the image
ImportJPG(m_SelectedJPG(i), dv)
prepNextView:
Me.ProgressBar1.Increment(1)
Next
Me.Close()
End Sub
Public Function ImportJPG(ByVal m_importFileFullName As String, ByVal m_View As DB.ViewDrafting) As Boolean
' Get the last elementID
Dim eCnt As Integer = GetElementCount()
' Setup the image import options
Dim options As New ImageImportOptions
options.Placement = DB.BoxPlacement.Center
options.View = m_View
' Import the image
Dim element As DB.Element = Nothing
Dim imported As Boolean = m_RvtDoc.Import(m_importFileFullName, options, element)
' Test if user requires the width or height overriden
If m_height > 0 Or m_width > 0 Then
' Get the imported image element
Dim myElements As New List(Of DB.Element)
myElements = GetElementsAfter(eCnt, m_RvtDoc)
' Get the newest element
For Each e As DB.Element In myElements
' Test to see if it is a rasterimage
Try
If e.Category.Name.ToUpper = "RASTER IMAGES" Then
' Set the Width
Dim wParam As DB.Parameter = e.Parameter("Width")
Dim myWPara As New clsPara(wParam)
If m_width > 0 Then
myWPara.Value = m_width
End If
' Set the Height
Dim hParam As DB.Parameter = e.Parameter("Height")
Dim myEPara As New clsPara(hParam)
If m_height > 0 Then
myEPara.Value = m_height
End If
End If
Catch ex As Exception
End Try
Next
End If
Return imported
End Function
Private Sub ButtonSelectImages_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles ButtonSelectImages.Click
' Select the files
Me.OpenFileDialogJPG.ShowDialog()
If Me.OpenFileDialogJPG.FileNames.Count = 0 Then
MsgBox("Nothing to Process!", MsgBoxStyle.Critical, "Terminating")
Me.Close()
End If
m_SelectedJPG = Me.OpenFileDialogJPG.FileNames
With Me.ProgressBar1
.Minimum = 0
.Value = 0
.Maximum = Me.OpenFileDialogJPG.FileNames.Count
.Visible = True
End With
If Me.TextBoxWidth.Text <> "" Then
m_width = (Me.TextBoxWidth.Text / 12)
Else
m_width = 0
End If
If Me.TextBoxHeight.Text <> "" Then
m_height = (Me.TextBoxHeight.Text / 12)
Else
m_height = 0
End If
ImportImages()
End Sub
Private Sub ButtonCancel_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles ButtonCancel.Click
Me.Close()
End Sub
Private Sub TextBoxHeight_KeyPress(ByVal sender As Object, ByVal e As System.Windows.Forms.KeyPressEventArgs) Handles TextBoxHeight.KeyPress
Dim allowedChars As String = "0123456789."
If allowedChars.IndexOf(e.KeyChar) = -1 Then
' Invalid Character
e.Handled = True
End If
End Sub
Private Sub TextBoxWidth_KeyPress(ByVal sender As Object, ByVal e As System.Windows.Forms.KeyPressEventArgs) Handles TextBoxWidth.KeyPress
Dim allowedChars As String = "0123456789."
If allowedChars.IndexOf(e.KeyChar) = -1 Then
' Invalid Character
e.Handled = True
End If
End Sub
End Class
Thursday, July 15, 2010
Embedded Schedules in Revit 2011
Some of you expert Revit Architects might be wondering just what in the heck I'm talking about when I mention "Embedded Schedules" in Revit. This feature is currently only available in Revit MEP 2011.
It is possible however to generate a nested schedule in Revit MEP for use in the Revit Architecture product... you just wont be able to modify the nested schedule unless you open the model in Revit MEP 2011...
Revit MEP 2011 supports nesting schedules beneath Rooms, Spaces, and MEP Systems. This basically allows the user to report elements in the model grouped by Room, Space, or System while showing whatever data they choose for the parent as well as the child elements... confused yet?
First start by creating a new Room schedule in Revit MEP 2011. Select the parameters that you want to see for rooms and then select the "Embedded Schedule" tab...
It is possible however to generate a nested schedule in Revit MEP for use in the Revit Architecture product... you just wont be able to modify the nested schedule unless you open the model in Revit MEP 2011...
Revit MEP 2011 supports nesting schedules beneath Rooms, Spaces, and MEP Systems. This basically allows the user to report elements in the model grouped by Room, Space, or System while showing whatever data they choose for the parent as well as the child elements... confused yet?
First start by creating a new Room schedule in Revit MEP 2011. Select the parameters that you want to see for rooms and then select the "Embedded Schedule" tab...
To really see how this feature works, set the "Number" parameter as "Group By" and select "Header" and "Blank Line"
Then in the "Embedded Schedule" tab, check both the "Embedded Schedule" and "Show categories from all disciplines" checkboxes. Now you can choose the category that you want to schedule within rooms. Click the "Embedded Schedule Properties" button to select the parameters and general schedule settings that you want to use for your embedded schedule.
And finally... here's what we end up with...
Monday, June 28, 2010
Custom Electrical Panel Schedules??
Look for an updated post on this topic coming March 2011!... this post is very brief and doesn't contain much detail at all:...
This isn't quite dealing with any API, but notable nonetheless...
Perhaps one of the biggest reasons for electrical designers to upgrade to Revit MEP 2011 is the introduction of a fully customizable panel schedule management system. This may not sound like much, but in previous versions of Revit it wasn't even possible to show load classifications as well as several other information required by various jurisdictions. Those days are over...
Panel Schedule Templates are managed from the "Panel Schedule Templates" button in the "Manage" tab of the ribbon.
Selecting the "Manage Templates" button takes me to a dialog that I can duplicate, edit, create a new, or delete a panel schedule template. As you can see in the image to the right, there are various options for template type, configuration and along the top there is another tab to apply certain templates to panels that exist in my project.

The options to set include the overall panel schedule width, quantity of circuit slots shown, display of header/load summary/footer, and the border styles used. There are also options under the "Circuit Table" options to control the display of the phase load cells per circuit. When a single-phase panel is active, there is an option to hide the third phase columns as well.
This isn't quite dealing with any API, but notable nonetheless...
Perhaps one of the biggest reasons for electrical designers to upgrade to Revit MEP 2011 is the introduction of a fully customizable panel schedule management system. This may not sound like much, but in previous versions of Revit it wasn't even possible to show load classifications as well as several other information required by various jurisdictions. Those days are over...
Not only can you create and manage custom load classifications, but you can show them in your panel schedules if you choose! It is also possible to manage several different panel schedule templates and apply the one you choose to whatever panel schedule you want in your project independently!!
Panel Schedule Templates are managed from the "Panel Schedule Templates" button in the "Manage" tab of the ribbon.This is where you get access to edit the format of the templates as well as apply the templates to the panels that live in your current project.
Selecting the "Manage Templates" button takes me to a dialog that I can duplicate, edit, create a new, or delete a panel schedule template. As you can see in the image to the right, there are various options for template type, configuration and along the top there is another tab to apply certain templates to panels that exist in my project.Launching the edit mode for a panel schedule template will present a new mode for Revit where you can edit the arrangement and inclusions for your panel schedule data. You can actually control the font (type, size, style, and color) for each cell individually!
It is possible to add and remove columns and rows wherever you want them. You can also manage the display of the load cells by phase by picking the "Set Template Options" button while in the "Modify Panel Schedule Template" mode.

The options to set include the overall panel schedule width, quantity of circuit slots shown, display of header/load summary/footer, and the border styles used. There are also options under the "Circuit Table" options to control the display of the phase load cells per circuit. When a single-phase panel is active, there is an option to hide the third phase columns as well.It is also possible to merge and unmerge cells, postcalculated values to a cell, adjust unit formats for a cell, individually manage borders for cells... the list goes on...
It is finally possible to display and document panel schedule information in a useful manner in Revit MEP!!
Friday, June 25, 2010
Autodesk University 2010
I submitted one Autodesk University class proposal back in May entitled "Leveraging the Tail End of the BIM Lifecycle with API" as a one hour lecture and was informed today that it was one of only 600+ classes accepted from the almost 3,500 proposals!!
The class track is centered around API but will also touch on various tips for configuring a Revit model for uses relating to facility management operations.
Target Audience:
Facility Managers, Developers, Project Managers, Building Owners
The class track is centered around API but will also touch on various tips for configuring a Revit model for uses relating to facility management operations.
Target Audience:
Facility Managers, Developers, Project Managers, Building Owners
Thursday, June 17, 2010
Revit Addin Manifest Tips and Tricks (debug AND normal load)
If the code below looks jacked up, stretch out your browser window...
While updating my employer's Revit 2010 utilities to 2011, I've stumbled upon some interesting behavior with the new add-in manifest files. I've discovered that if I do not include a file path in thetag of the manifest file that I can accomplish both debugging from Visual Studio 2008 as well as natural loading all with a few simple bits of code...
Prior to discovering this, I was using a separate 'DEBUG' manifest file pointing to my debug directory for the Visual Studio 2008 project and a separate 'PRODUCTION DISTRIBUTION' manifest file that pathed to the normal 'addin' directory for my tools to load into Revit 2011.
In order for this example to work, the add-in manifest file still needs to be located in the normal 'Addin' directory in order to load into Revit, but if I start my application from Visual Studio 2008 in debug mode, I have full access to my [target.DLL] even though my [target.DLL] is not loaded in the same directory as the default add-in directory. This may sound confusing, but actually works quite well in that it suites dual purpose:
When I fire up Revit, the DLL saved in the add-in directory is loaded into Revit. When I start Revit from Visual Studio to debug an application, the DLL in my Visual Studio debug directory is loaded into the Revit session.
Here's how I do it for a sample application that loads external DLL command files (the command files must be present in the same directory as the main application DLL being debugged for this example to work):
'*************************** ; end of VB.NET
The sample manifest file would look like:
'*************************** ; end of XML Manifest File
Notice there is no file path in the 'Assembly' tag?!?!?!?!
While updating my employer's Revit 2010 utilities to 2011, I've stumbled upon some interesting behavior with the new add-in manifest files. I've discovered that if I do not include a file path in the
Prior to discovering this, I was using a separate 'DEBUG' manifest file pointing to my debug directory for the Visual Studio 2008 project and a separate 'PRODUCTION DISTRIBUTION' manifest file that pathed to the normal 'addin' directory for my tools to load into Revit 2011.
In order for this example to work, the add-in manifest file still needs to be located in the normal 'Addin' directory in order to load into Revit, but if I start my application from Visual Studio 2008 in debug mode, I have full access to my [target.DLL] even though my [target.DLL] is not loaded in the same directory as the default add-in directory. This may sound confusing, but actually works quite well in that it suites dual purpose:
When I fire up Revit, the DLL saved in the add-in directory is loaded into Revit. When I start Revit from Visual Studio to debug an application, the DLL in my Visual Studio debug directory is loaded into the Revit session.
Here's how I do it for a sample application that loads external DLL command files (the command files must be present in the same directory as the main application DLL being debugged for this example to work):
Imports Autodesk.Revit.UI Imports Autodesk.Revit.Attributes Imports System.Windows.Media.Imaging Imports System.Reflection ''' <summary> ''' Main Ribbon Creation ''' </summary> <Transaction(TransactionMode.Automatic)> _ <Regeneration(RegenerationOption.Manual)> _ Public Class AppExample Implements IExternalApplication Public Sub AddRibbonPanel(ByVal a As UIControlledApplication) ' This 'myPath' variable is the trick to getting the dual debug/execution behavior!!!!!!! Dim myPath As String = Assembly.GetExecutingAssembly.Location.Substring( _ 0, (Assembly.GetExecutingAssembly.Location.LastIndexOf("\") + 1)) Dim rvtPanel As RibbonPanel = a.CreateRibbonPanel("RevitNet Customizations") ' DatabaseLink ; example dll - notice the variable path to the DLL????????????? 'myPath' Dim pbdDatabaseLink As New PushButtonData("Database Link", _ "Database" & vbCr & "Link", _ myPath & "RevitNet.DatabaseLink.dll", _ "RevitNet.DatabaseLink.cmdDatabaseLink") pbdDatabaseLink.Image = New BitmapImage(New Uri(myPath & "RevitNet.DatabaseLink_16.jpg")) pbdDatabaseLink.LargeImage = New BitmapImage(New Uri(myPath & "RevitNet.DatabaseLink_32.jpg")) pbdDatabaseLink.ToolTip = "Bidirectional Link to a Database" & vbCr & "From Revit 2011" ' Add the pushbutton to the ribbon panel Dim myButtonDBL As PushButton = rvtPanel.AddItem(pbdDatabaseLink) End Sub Public Function OnStartup(ByVal a As UIControlledApplication) _ As Result Implements IExternalApplication.OnStartup Try AddRibbonPanel(a) Return Result.Succeeded Catch ex As Exception Return Result.Failed End Try End Function Public Function OnShutdown(ByVal a As UIControlledApplication) _ As Result Implements IExternalApplication.OnShutdown Return Result.Succeeded End Function End Class
'*************************** ; end of VB.NET
The sample manifest file would look like:
<?xml version="1.0" encoding="utf-8" standalone="no"?> <RevitAddIns> <AddIn Type="Application"> <Name>RevitNet Utilities</Name> <Assembly>RevitNet.AppExample.dll</Assembly> <AddInId>2c372fa8-86c5-49f6-ae7b-fcc18481c841</AddInId> <FullClassName>RevitNet.AppExample</FullClassName> </AddIn> </RevitAddIns>
'*************************** ; end of XML Manifest File
Notice there is no file path in the 'Assembly' tag?!?!?!?!
Sunday, June 13, 2010
Autodesk AEC DevCamp
Well, I can't think of a better topic for my first post here, I just returned from Autodesk DevCamp in Boston. It was interesting discussing all of the new features of the Revit API with the guys at Autodesk that are the actual people developing the API's. It was also interesting meeting Michael Priestman who mans the Navisworks API development for Autodesk.
I think I've got all the technical information I need to turn my Revit scripting app into reality. Stay tuned for some posts where I'll share how I get it done.
I think I've got all the technical information I need to turn my Revit scripting app into reality. Stay tuned for some posts where I'll share how I get it done.
Subscribe to:
Posts (Atom)





