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