Monday, August 16, 2010

Stale Bread or Toast?

One of the longest burning issues between architects and engineers is ownership of engineering fixtures and the coordination that comes along with them. I can't tell you how many times I've seen a project go out for permit with the light fixture counts being out of whack between the Architectural RCP and the Electrical Lighting Plans...

Revit MEP now offers a solution for this, but it still takes some compromise between the two teams (this is usually where the break down occurs, but maybe technology can help bridge the gap)...

Revit MEP 2011 now offers the ability to "Copy Monitor" MEP fixture elements between referenced models!! MEP Fixture Elements include:
  • Lighting Fixtures
  • Plumbing Fixtures
  • Air Terminals (HVAC Grilles)
  • Mechanical Equipment (useful between Electrical and Mechanical model)
You may have noticed an extra button in the Collaborate tab of the Revit MEP 2011 ribbon that does not exist in Revit Architecture 2011... The "Coordination Settings" button


This new button gains access to the MEP Fixture monitoring settings as seen below...

You can map the architectural fixtures to your own custom fixtures!! It is important that the architect does not use length parameters to control the fixture mounting heights as this disrupts the location monitoring and will result in goofy coordination warnings. The architect must use the offset parameter to control mounting heights for non hosted fixtures.

There is a "Batch Copy" button in the main Copy Monitor feature making it easier than ever to coordinate fixtures between referenced models in Revit!

You now get notified when fixtures move, get deleted, or new ones get added to the Architectural model! When you select a monitored fixture, you will get a monitor icon above it as seen in the example below.


Opening a model with discrepancies identified between the link and the host model will be prompted as seen in the image below.


Sunday, July 25, 2010

All Your Books Are Belong to Me

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!

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...


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 Class

The parameter helper class used to save data to the parameter when we find is provided below.


Imports 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 Class

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.


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...


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...

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

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 the tag 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):


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.