Showing posts with label Revit Architecture 2011. Show all posts
Showing posts with label Revit Architecture 2011. Show all posts

Monday, February 28, 2011

The Rest of... Convert Rooms to 3D Masses

I got a little lazy and preoccupied yesterday so cut the post short on you, but you have to admit the suspense was pretty exciting! As I promised in yesterday's post on Convert Rooms to 3D Masses, this post will be the main meat of the whole utility.

My Test model contains five rooms boundarized by most of the common conditions that exist in a real design model. Some are partially surrounded by room separation lines, one entirely surrounded by room separation lines, one with a curved wall, etc.


Text first thing we need is a complete list collection of the rooms in our model. The code below does this for us and puts the list of room elements into m_Rooms:


' Get the list of rooms
Dim m_Collector As New FilteredElementCollector(m_Doc)
m_Collector.OfCategory(BuiltInCategory.OST_Rooms)
Dim m_Rooms As New List(Of Element)
m_Rooms = m_Collector.ToElements

Now we can start iterating our collection of rooms in a For Each loop with the beginning of that shown here:


' Iterate the list and gather a list of boundaries
        For Each x As Architecture.Room In m_Rooms

This next group of code shows how we will avoid working on unplaced rooms by making sure the Area is larger than 1. The second portion shows how we start the stop watch object used to quantify the time that it takes to generate a mass from each room. Then the path to our specialty equipment family template and the saveas function is called to give the family a name. Make sure that the directory being called already exists for this sample to work.


' Avoid unplaced rooms
            If x.Area < 1 Then Continue For

            ' Stopwatcher - This comes FIRST
            Dim m_StopWatch As New Stopwatch
            m_StopWatch.Reset()
            m_StopWatch.Start()

            ' This is the path to our family template
            m_FamDoc = m_App.NewFamilyDocument("C:\Documents and Settings\All Users\Application Data\" & _
                                               "Autodesk\RAC 2011\Imperial Templates\Specialty Equipment.rft")

            ' The "C:\My Families\" directory needs to exist first obviously
            m_FamDoc.SaveAs("C:\My Families\" & x.UniqueId.ToString & ".rfa")

The next snip shows where we create a transaction for both the main model as well as the family document


' Start a new Model Transaction
            Dim m_Trans As New Transaction(m_Doc, "My Rooms to Masses - By Boundary")
            m_Trans.Start()

            ' Start a new Family Transaction
            Dim m_TransFam As New Transaction(m_FamDoc, "Transaction in Family Document")
            m_TransFam.Start()

Now in this next portion, we will create a new subcategory named using the department of the room and material named equally that we will apply to the extrusion that we build the room with. This is handy to display the masses in color related to their department assignment.


' Get the department name
            Dim m_para As New clsPara(x.Parameter("Department"))
            Dim m_SubCatName As String = "My_Rooms"
            If m_para.Value <> "" Then m_SubCatName = "My_Rooms_" & m_para.Value
            ' Subcategory named by Department
            Dim m_Subcategory As Category = Nothing
            Try
                ' Try to create the subcategory if it does not exist
                m_Subcategory = m_FamDoc.Settings.Categories.NewSubcategory(m_SECategory, m_SubCatName)
            Catch ex As Exception
                ' Get the subcategory object since it exists already
                Dim m_NameMap As CategoryNameMap = m_SECategory.SubCategories
                For Each x1 As Category In m_NameMap
                    If x1.Name = m_SubCatName Then
                        m_Subcategory = x1
                        Exit For
                    End If
                Next
            End Try
            ' Material named by Department
            Dim m_Material As Material = Nothing
            Try
                ' Try to create the material if it does not exist
                m_Material = m_FamDoc.Settings.Materials.AddWood(m_SubCatName)
            Catch ex As Exception
                ' Get the material object since it exists already
                m_Material = m_FamDoc.Settings.Materials.Item(m_SubCatName)
            End Try
            ' Apply the material to the subcategory
            m_Subcategory.Material = m_Material

Now for the part where we get the room boundary into a curve array that we will eventually use to extrude as a form representing a room element. The snip below iterates through the room boundary objects returning a CurveArray which just so happens to be the second to last process to get us to out required argument that we need to generate the 3D form, CurveArrArray!


' Get the room boundary
            Dim m_Boundary As Architecture.BoundarySegmentArrayArray = x.Boundary
            If m_Boundary Is Nothing Then Continue For
            ' The array of boundary curves
            Dim m_CurveArray As New CurveArray
            ' Iterate to gather the curve objects
            For i = 0 To m_Boundary.Size - 1
                ' Boundary segments array
                Dim m_SegAray As Architecture.BoundarySegmentArray = m_Boundary.Item(i)
                ' Segments Array
                For ii = 0 To m_SegAray.Size - 1
                    Dim m_Seg As Architecture.BoundarySegment = m_SegAray.Item(ii)
                    ' Add the segment curve to the array
                    m_CurveArray.Append(m_Seg.Curve)
                Next
            Next

The snip below shows how we build up the workplane that is required to extrude the form from in our family template. We then append the CurveArray to the final argument required to extrude our form... CurveArrArray:


' Simple insertion point
            Dim pt1 As New XYZ(0, 0, 0)
            ' Our normal point that points the extrusion directly up
            Dim ptNormal As New XYZ(0, 0, 1)
            ' The plane to extrude the mass from
            Dim m_Plane As Plane = m_AppCreate.NewPlane(ptNormal, pt1)
            Dim m_SketchPlane As SketchPlane = m_FamDoc.FamilyCreate.NewSketchPlane(m_Plane)
            ' Need to add the CurveArray to the final requirement to generate the form
            Dim m_CurveArArray As New CurveArrArray
            m_CurveArArray.Append(m_CurveArray)

Now we can generate the form and add the extrusion to out subcategory representing the department:


' Extrude the form
            Dim m_Extrusion As Extrusion = m_FamDoc.FamilyCreate.NewExtrusion(True, m_CurveArArray, m_SketchPlane, 8)
            Try
                m_Extrusion.Subcategory = m_Subcategory
            Catch ex As Exception

            End Try

The next snip shows how we load the family into the project and place it into the model at 0,0,0 so our coordinates used to generate the form will line up right where the room needs to be:


' Commit the Family Transaction
            m_TransFam.Commit()
            ' Load the Family into the Model
            Dim m_NewFamily As Family = m_FamDoc.LoadFamily(m_Doc)
            ' Create a reference to the latest family (we just created it)
            Dim m_FamilySymbolSetIterator As FamilySymbolSetIterator = m_NewFamily.Symbols.ForwardIterator()
            m_FamilySymbolSetIterator.MoveNext()
            Dim m_FamSymbol As FamilySymbol = TryCast(m_FamilySymbolSetIterator.Current, FamilySymbol)
            ' Place the Family at 0,0,0 since we used the same coordinates as the rooms to generate
            Dim m_FamilyInstance As FamilyInstance = m_Doc.Create.NewFamilyInstance(New XYZ(0, 0, 0), m_FamSymbol, [Structure].StructuralType.NonStructural)

So that's about it.... now we just need to cleanup and close the stopwatch objects and report the timing to the user:


' Commit the Model Transaction
            m_Trans.Commit()
            ' Elapsed Time Per Element
            m_StopWatch.Stop()
            ' Report the elapsed time
            MsgBox(m_StopWatch.Elapsed.TotalSeconds.ToString & " Seconds!", MsgBoxStyle.Information, "Elapsed Time!")

Now check out the results....!!! A fully 3D schedulable room mass (as a specialty equipment form)...

Sunday, February 27, 2011

Convert Rooms to 3D Masses

Well, it has been quite a while since I shared something pertinent to anytime of code or .NET. I've been busy with less exciting things and haven't really had the time, but that's about to change!

This post will touch on how to solve a common problem where designers want to see how a room looks in full 3D while in the programming (Room and Area Programming) stages to help figure out the relationships from one room to others. This is difficult to do with traditional rooms that Revit creates, mainly because they aren't real physical objects (and you cannot see the damn things in 3D).

Here is what my test model looks like with five simple rooms placed in. One surrounded entirely by room separation lines, one with curved walls, and some others for verification and proof that the tool will actually work in most conditions.


Create a new .NET 3.5 class project in Visual Studio 2010. You'll need to build in the Command class on your own this time... Add a form object and name it Form_Main. Add the following referenced to your project and import their name spaces as shown below:


Imports System
Imports System.Collections.Generic
Imports System.Diagnostics
Imports Autodesk.Revit.DB
Imports Autodesk.Revit.UI
Imports Autodesk.Revit.ApplicationServices
Imports Autodesk.Revit.Creation

Add two buttons to your form named "ButtonGenerateMasses" and "ButtonCancel"... their suggested placements are shown here:



Now we need to focus on what all variables we need to expose to our form class and how they will be used. Add the following private variables to this form class just beneath the class declaration:


Private m_CmdData As ExternalCommandData
    Private m_Doc As Autodesk.Revit.DB.Document
    Private m_FamDoc As Autodesk.Revit.DB.Document
    Private m_App As Autodesk.Revit.ApplicationServices.Application
    Private m_SECategory As Category
    Private m_AppCreate As Autodesk.Revit.Creation.Application

As you can see in the listing above, our variable requirements are actually quite simple. We're mainly concerned with document and application objects that are required to generate families in Revit.

The next item we'll code in is the base class constructor. Now keep in mind that one argument is required to generate an instance of this class, IExternalCommand. This argument is the same as the one required in the IExternalCommand's Execute function. The Constructor is shown here:


''' <summary>
    ''' General Class Constructor
    ''' </summary>
    ''' <param name="settings"></param>
    ''' <remarks></remarks>
    Public Sub New(ByVal settings As ExternalCommandData)
        ' Always call this when using a constructor for a form object
        InitializeComponent()
        ' Settings reference to UI and DB objects
        m_CmdData = settings
        ' Application and Document References
        m_App = m_CmdData.Application.Application
        m_Doc = m_CmdData.Application.ActiveUIDocument.Document
        ' Mass Category
        m_SECategory = m_Doc.Settings.Categories.Item(BuiltInCategory.OST_SpecialityEquipment)
        ' App creator
        m_AppCreate = m_App.Create
        ' Set the form title
        Me.Text = "Rooms to Masses"""
    End Sub

Now that we have all of the main framework ready to go, we can dig down into how the families are built using data from the room elements in the model. Create a new subroutine named GenerateMasses. This subroutine is where all the magic happens.


''' <summary>
    ''' Generate 3D Specialty Equipment Extrusions for Rooms
    ''' Specialty Equipment is Scheduleable
    ''' </summary>
    ''' <remarks></remarks>
    Private Sub GenerateMasses()

    End Sub

Before we get back into filling in the functionality in this subroutine, let's get the button assignments in our form out of the way. Double click each of the buttons in the form designer to autogenerate their click event functions and add the following very simple code to these functions:


''' <summary>
    ''' Close the App
    ''' </summary>
    ''' <param name="sender"></param>
    ''' <param name="e"></param>
    ''' <remarks></remarks>
    Private Sub ButtonCancel_Click(ByVal sender As System.Object, _
                                   ByVal e As System.EventArgs) _
                               Handles ButtonCancel.Click
        Me.Close()
    End Sub

    ''' <summary>
    ''' Launch GenerateMasses
    ''' </summary>
    ''' <param name="sender"></param>
    ''' <param name="e"></param>
    ''' <remarks></remarks>
    Private Sub ButtonGenerateMasses_Click(ByVal sender As System.Object, _
                                           ByVal e As System.EventArgs) _
                                       Handles ButtonGenerateMasses.Click
        GenerateMasses()
    End Sub

Now let's focus the rest of this post on what's missing!.... haha I know, you'll have to wait until tomorrow to see the ending. So stay tuned and come back tomorrow for the meat of this idea finalized into true usable code! Tomorrow's post will be on the completion of the GenerateMasses subroutine.

Thursday, December 9, 2010

Schedule Accumulated Road Centerline Lengths in Revit

Another NON API Post!!! Don't worry, I haven't converted...

Have you ever needed to schedule some sort of element's accumulated linear distance?... Say like a whole bunch of road centerlines for curb costing?... Here's a quick tip on how you can do just that?...

First create a new wall type and name it something like "Roads_CenterLine" and set it's construction width to something very skinny... say 0.05mm
Set the "Type Mark" for this new wall type to "RoadCenterline"... we'll use this to filter our schedule later.

Next draw or trace all of your road centerlines using this new wall type. Make sure that all of the walls that you draw using this new wall type are no taller than your cut plane in your view (very short). A wall height of 1mm works great.

Create a view filter based on the name of this wall and apply it to your current view. We'll use this to override the display of the walls so that when plotted they will look like a real centerline.
Apply the filter to your view and override the filter's display to whatever you want. My example modifies the linetype to a centerline...

Your centerline walls should look something like this one now:
So you obviously need to schedule the accumulated lengths of these centerlines, so create a new wall schedule and add the "Length" and "Family Type" fields.
Filter walls by "Type Mark" using contains "RoadCenterline" as the filter means.

Set the Grand Totals checkbox in the "Sorting/Grouping" tab so you get the totals.

Now hide the "Type Mark" field since you're only using it as a quantification filter.
Now check the "Calculate totals" box for the "Length" field... this is how we calculate the totals.
You're done! Your schedule should resemble something like this one:
BIM is easy...

Monday, November 29, 2010

By the Book Part 2 - Harvesting Families from a Project Model

This post is part 2 in response to the sample Revit API application I wrote for the book entitled "Mastering Revit Architecture 2011"...  in Chapter 24 "Under the Hood of Revit."


Well, I promised I would show you the updated family export code in my previous post "By the Book Part 1 - Harvesting Families from a Project Model."... so without further procrastination...

We'll get started off by setting the code to our export button illustrated below. All this does is hide the lower buttons to make room for the progress bar and then runs the export routine. When we're all done, we'll call the close function for the form and exit out.


''' <summary>
    ''' Export the families and then quietly close
    ''' </summary>
    ''' <param name="sender"></param>
    ''' <param name="e"></param>
    ''' <remarks></remarks>
    Private Sub ButtonExport_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles ButtonExport.Click
        Me.ButtonCancel.Visible = False
        Me.ButtonExport.Visible = False
        Me.ButtonSelectAll.Visible = False
        Me.ButtonSelectNone.Visible = False
        doExport()
        ' We're all done
        Me.Close()
    End Sub

I guess we should get the last remaining function out of the way as well before we dive into the export function. The function below is used to verify that all characters used to create a file name are valid for the Windows OS.


''' <summary>
    ''' Make sure the path does not contain any invalid file naming characters
    ''' </summary>
    ''' <param name="fileName">The Filename to check</param>
    ''' <returns>A string</returns>
    ''' <remarks></remarks>
    Private Function CheckValidFileName(ByVal fileName As String) As String
        For Each c In Path.GetInvalidFileNameChars()
            If fileName.Contains(c) Then
                ' Invalid filename characters detected...
                ' Could either replace characters or return empty
                Return ""
            End If
        Next
        Return fileName
    End Function

The export function starts out by first verifying that at least one category has been checked for export. Then a hashtable is used as a means to reference each selection (hashtables are FAST).


''' <summary>
    ''' This routine performs the exports
    ''' </summary>
    ''' <remarks></remarks>
    Private Sub doExport()
        ' Ony export families that belong to our selected categories!
        Dim m_SelectedCategories = Me.CheckedListBoxCategories.CheckedItems
        ' Do nothing if nothing selected
        If m_SelectedCategories.Count = 0 Then
            MsgBox("You did not select any categories..." & vbCr & "Nothing to do...", _
                   MsgBoxStyle.Information, "No Categories Selected! Exiting...")
            Exit Sub
        End If
        ' A hashtable comes in handy when verifying multiple situations... or 1
        Dim m_CatHash As New Hashtable
        For Each xItemC In m_SelectedCategories
            m_CatHash.Add(xItemC.ToString, "Category")
        Next

The next thing to do is make sure the target directory exists for the export.


Try ' If the parent export directory is missing, create it
            Directory.CreateDirectory(Replace(Me.LabelExportPath.Text, "/", "\", , , CompareMethod.Text))
        Catch ex As Exception
            ' Message to show any errors
            MsgBox(Err.Description, MsgBoxStyle.Information, Err.Source)
        End Try

With the main directory created, we can continue with the element collection and progress bar setup. The filter below grabs all "Type" elements from the model and turns the result into an easy to use list of DB.Element.


' Filter to get a set of elements that are elementType 
        Dim m_SymbFilter As New DB.ElementIsElementTypeFilter
        Dim collector As New DB.FilteredElementCollector(m_Doc)
        collector.WherePasses(m_SymbFilter)
        ' Create a list from the collector
        Dim FamilySymbols As New List(Of DB.Element)
        FamilySymbols = collector.ToElements
        ' Start the progressbar
        Dim iCnt As Integer = 0
        Dim iCntFam As Integer = FamilySymbols.Count
        Me.ProgressBar1.Visible = True
        Me.ProgressBar1.Minimum = 0
        Me.ProgressBar1.Maximum = iCntFam
        Me.ProgressBar1.Value = iCnt

Now we can iterate the element list and perform the necessary exports as external RFA files.


' The export process
For Each x As DB.Element In FamilySymbols
    If (TypeOf x Is DB.FamilySymbol) Then
        Dim m_category As DB.Category = x.Category
        If Not (m_category Is Nothing) Then
            ' Is it a selected category?
            If m_CatHash.Contains(m_category.Name) Then
                Dim m_ExportPath As String = ""
                Try ' Create the subdirectory
                    m_ExportPath = Me.LabelExportPath.Text & "\" & m_category.Name & "\"
                    Directory.CreateDirectory(Replace(m_ExportPath, "/", "\", , , CompareMethod.Text))
                Catch ex As Exception
                    ' Category subdirectory exists
                End Try
                Try ' The family element
                    Dim m_FamSymb As DB.FamilySymbol = x
                    Dim m_FamInst As DB.Family = m_FamSymb.Family
                    Dim m_FamName As String = m_FamInst.Name
                    ' Verify famname is valid filename and exists
                    If Dir$(m_ExportPath + m_FamName & ".rfa") = "" And CheckValidFileName(m_FamName) <> "" Then
                        Me.LabelFileName.Text = "...\" & m_category.Name & "\" & m_FamInst.Name
                        Dim famDoc As DB.Document = m_Doc.EditFamily(m_FamInst)
                        famDoc.SaveAs(m_ExportPath + m_FamName & ".rfa")
                        famDoc.Close(False)
                    End If
                Catch ex As Exception
                    ' Prevent hault on system families
                End Try
            End If
        End If
    End If
    ' Step the progress bar
    Me.ProgressBar1.Increment(1)
Next

That's it! Now you have the means to quickly harvest families from a Revit 2011 model.

If you have any questions or suggestions for other Revit 2011 code solutions, don't hesitate to leave a comment or ask a question.

Saturday, November 27, 2010

By the Book Part 1 - Harvesting Families from a Project Model

This post is part 1 in response to the sample Revit API application I wrote for the book entitled "Mastering Revit Architecture 2011"... in Chapter 24 "Under the Hood of Revit." The book has a five star amazon rating (I'm sure most of it is due to the insane quality of Chapter 24). It's also available on Kindle!!!


I'm sure you'll also be excited to know that ALL of the authors for the above mentioned book will be at Autodesk University 2010 this week in Las Vegas!... So if you like autographs, bring your book and start yourself a man hunt to find these guys!... weeeee

I'll have to admit, I wrote the original sample very quickly and could have done a better job in terms of its feature availability. For instance, I left out the ability to select categories to export! That's right, this post will add functionality for category selection!

First create a form named "form_Main" and add a checked listbox named "CheckedListBoxCategories" along with five buttons named "ButtonSelectAll", "ButtonSelectNone", "ButtonExport", "ButtonCancel", and "ButtonBrowse." Add a progress bar named "ProgressBar1" and a few labels named "LabelExportPath", "LabelFileName", and "LabelExport." When you're done, your form should resemble something close to the image below.


Now that we've got the interface all worked out, let's get the form class constructor put together. The code below shows the required imports along with the basic class constructor that will be called to eventually display the form.


Imports Autodesk.Revit
Imports System.Windows.Forms
Imports System.IO

Public Class form_FamilyExport

    Private m_App As UI.UIApplication = Nothing
    Private m_Doc As DB.Document

    ''' <summary>
    ''' Form class constructor, don't forget InitializeComponent()
    ''' </summary>
    ''' <param name="cmdData">The UI.ExternalCommandData object</param>
    ''' <param name="strAppVer">Application Version</param>
    ''' <remarks></remarks>
    Public Sub New(ByVal cmdData As UI.ExternalCommandData, ByVal strAppVer As String)
        InitializeComponent()
        ' Private variables
        m_App = cmdData.Application
        m_Doc = m_App.ActiveUIDocument.Document
        ' Form configurations
        Me.Text = "Batch Export Families - " & strAppVer
        Me.ProgressBar1.Visible = False
        Me.ButtonExport.Enabled = False
        ' Set default export path adjacent to model location
        ' If workshared, use the central model path
        If m_Doc.IsWorkshared = True Then
            Try
                Me.LabelExportPath.Text = Path.GetDirectoryName(m_Doc.WorksharingCentralFilename) & "\Exported Families\"
            Catch ex As Exception
                ' Detached model will not have a file path
            End Try
        Else
            Me.LabelExportPath.Text = Path.GetDirectoryName(m_Doc.PathName) & "\Exported Families\"
        End If
        ' Clear the list
        Me.CheckedListBoxCategories.CheckOnClick = True
        Me.CheckedListBoxCategories.Items.Clear()
        Me.LabelFileName.Text = ""
        Me.LabelExportPath.Text = ""
        ' Get all categories
        GetCategories()
    End Sub

End Class

Everything so far is the same as what we talk about in the book except for the call to a function named GetCategories(). This new category function is very basic and only collects the names of all categories available in your Revit environment. The results are then recorded into the checkedlistbox on our form. The function below collects the strings into a list first so we can sort them and then from the list to the listbox.


''' <summary>
    ''' Get a list of all 'non tag' categories
    ''' </summary>
    ''' <remarks></remarks>
    Private Sub GetCategories()
        ' Full list of Categories
        Dim categoryList As New List(Of String)
        For Each category As DB.Category In m_Doc.Settings.Categories
            categoryList.Add(category.Name)
        Next
        ' Alpha sort the list
        categoryList.Sort()
        ' Add categories to the listbox
        For Each x As String In categoryList
            If InStr(UCase(x), "TAGS", CompareMethod.Text) = 0 Then
                ' Add the category
                Me.CheckedListBoxCategories.Items.Add(x)
            End If
        Next
    End Sub

Now that we have a massive list of categories we should probably provide a quick means for selecting all or none of the items in the list. The code illustrates how the ButtonSelectNone and ButtonSelectAll buttons to their thing.


''' <summary>
    ''' Uncheck all items in the listbox
    ''' </summary>
    ''' <param name="sender"></param>
    ''' <param name="e"></param>
    ''' <remarks></remarks>
    Private Sub ButtonSelectNone_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles ButtonSelectNone.Click
        For i As Integer = 0 To CheckedListBoxCategories.Items.Count - 1
            CheckedListBoxCategories.SetItemChecked(i, False)
        Next
    End Sub

    ''' <summary>
    ''' Check all items in listbox
    ''' </summary>
    ''' <param name="sender"></param>
    ''' <param name="e"></param>
    ''' <remarks></remarks>
    Private Sub ButtonSelectAll_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles ButtonSelectAll.Click
        For i As Integer = 0 To CheckedListBoxCategories.Items.Count - 1
            CheckedListBoxCategories.SetItemChecked(i, True)
        Next
    End Sub

The updated export code will be demonstrated in "By the Book Part 2 - Harvesting Families from a Project Model", so stay tuned to see all that excitement!...

Thursday, November 25, 2010

A Classy Way to Handle Revit Parameters

You may have noticed some of my previous posts referencing a clsPara object. I've been getting lots of interesting questions regarding this object and how it's built and just what the heck it does. Well... I guess I'll share.

My clsPara class is entirely reusable and very handy for handling data transactions in and out of parameters. Not only does this object connect with built-in parameters, it also connects with element properties! I know, its SO super exciting (breathe).

Let's start from the beginning... create a new class named clsPara and add the basic Autodesk.Revit reference as illustrated below.


Imports Autodesk.Revit

''' <summary>
''' An awesome class used to define a parameter
''' </summary>
''' <remarks></remarks>
Public Class clsPara

End Class

The next thing we need to do is provide a constructor that we can use to build this class into a meaningful object. All we need to accept as an argument is a Revit parameter. We'll handle all the rest internally within the class in a very simple yet highly efficient manner.


Private m_parameter As DB.Parameter

''' <summary>
''' Constructor
''' </summary>
''' <param name="parameter">Revit Parameter Object</param>
''' <remarks></remarks>
Public Sub New(ByVal parameter As DB.Parameter)
   m_parameter = parameter
End Sub

Our new clsPara object can now be called from anywhere in the project with the following code and argument (where myParam is a valid Revit Parameter reference):


Dim myLittleParam As New clsPara(myParam)

The next step is to provide all the basic data interaction routines and properties. We'll start with the data interactions for retrieving data from a Revit Parameter within the class.


''' <summary>
    ''' Get a parameter's value
    ''' </summary>
    ''' <param name="parameter"></param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Shared Function GetParameterValue(ByVal parameter As DB.Parameter) As String
        'public static Object GetParameterValue(Parameter parameter) 
        Select Case parameter.StorageType
            Case DB.StorageType.[Double]
                'get value with unit, AsDouble() can get value without unit 
                Return parameter.AsDouble
            Case DB.StorageType.ElementId
                ' Returns Only the ElementID
                Return parameter.AsElementId.IntegerValue
            Case DB.StorageType.[Integer]
                'get value with unit, AsInteger() can get value without unit 
                Return parameter.AsInteger
            Case DB.StorageType.None
                Return parameter.AsValueString()
            Case DB.StorageType.[String]
                Return parameter.AsString()
            Case Else
                Return ""
        End Select
    End Function

Now another function to SET a value to a parameter:


''' <summary>
    ''' Set a Parameter's value
    ''' </summary>
    ''' <param name="parameter"></param>
    ''' <param name="value"></param>
    ''' <remarks></remarks>
    Public Shared Sub SetParameterValue(ByVal parameter As DB.Parameter, ByVal value As Object)
        'first,check whether this parameter is read only 
        If parameter.IsReadOnly Then
            Exit Sub
        End If
        Select Case parameter.StorageType
            Case DB.StorageType.[Double]
                'set value with unit, Set() can set value without unit 
                parameter.SetValueString(TryCast(value, String))
                Exit Select
            Case DB.StorageType.ElementId
                Dim myElementId As DB.ElementId = DirectCast((value), DB.ElementId)
                parameter.[Set](myElementId)
                'MsgBox("Reminder to finish elementid write routine...")
                Exit Select
            Case DB.StorageType.[Integer]
                'set value with unit, Set() can set value without unit 
                parameter.SetValueString(TryCast(value, String))
                Exit Select
            Case DB.StorageType.None
                parameter.SetValueString(TryCast(value, String))
                Exit Select
            Case DB.StorageType.[String]
                parameter.[Set](TryCast(value, String))
                Exit Select
            Case Else
                Exit Select
        End Select
    End Sub

Now that we can set and get a value for a Revit Parameter... let's add some functionality to react within the class using some handy properties!!! Exciting, I know....


''' <summary>
    ''' This property will return the value!!
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    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

    ''' <summary>
    ''' What's the unit type, anyway?
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public ReadOnly Property DisplayUnitType() As String
        Get
            Try
                Return m_parameter.DisplayUnitType.ToString
            Catch
                Return Nothing
            End Try
        End Get
    End Property

    ''' <summary>
    ''' True if this is a Read Only parameter such as Area!!
    ''' Will not fail when trying to write to a read-only parameter!!
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public ReadOnly Property ParameterIsReadOnly() As Boolean
        Get
            Try
                Return m_parameter.IsReadOnly
            Catch
                Return Nothing
            End Try
        End Get
    End Property

    ''' <summary>
    ''' Returns true or false if this is a Shared Parameter!
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public ReadOnly Property ParameterIsShared() As Boolean
        Get
            Try
                Return m_parameter.IsShared
            Catch
                Return Nothing
            End Try
        End Get
    End Property

    ''' <summary>
    ''' Returns the type of parameter, sometimes useful
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public ReadOnly Property ParaType() As String
        Get
            Try
                Return m_parameter.GetType.Name
            Catch
                Return Nothing
            End Try
        End Get
    End Property

    ''' <summary>
    ''' Returns the parameter name
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public ReadOnly Property ParaName() As String
        Get
            Try
                Return m_parameter.Definition.Name
            Catch
                Return Nothing
            End Try
        End Get
    End Property

    ''' <summary>
    ''' This property will return the data format!
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public ReadOnly Property Format() As String
        Get
            Try
                Return m_parameter.StorageType.ToString
            Catch
                Return Nothing
            End Try
        End Get
    End Property

... I know!!! Don't forget to breathe!!

Saturday, November 20, 2010

Tracking Revision Clouds in Revit 2011 can be a bit... Cloudy

Have you ever tried to schedule Revision Clouds in Revit 2011?... not so fun when you find that it is impossible... not to mention that if you select a revision cloud in a sheet and try the ol "Select All Instances" that the option is NOT supported for Revision Clouds... WTF....

Imagine if you had a means to export all revision clouds in your model to a neat and easy to view Excel document where you could see the sheet number of the sheet that the cloud displays on along with even the comments and revision date and sequence information... that would be pretty cool, huh?

I think this is a real short coming of Revit 2011, so here is a bit of code I used to solve the problem. I obviously cannot give you ALL of the code (what fun would that be?)... but I will share with you how I got ahold of the elements and then queried their view names and target sheet numbers and names.

First create a form and drop a filesave dialog from the tools menu and name it "SaveFileDialogCSV"... set the filetype filter to CSV... bla bla bla... The code below demonstrates the verification on the resulting file saved by the dialog. Eventually when satisfied there is a little function called "doScan" that actually scans the model for the Revision Cloud elements.


Me.SaveFileDialogCSV.ShowDialog()
If SaveFileDialogCSV.FileName <> "" Then
    If File.Exists(SaveFileDialogCSV.FileName) = True Then
        Try
            ' Start with a clean file
            File.Delete(SaveFileDialogCSV.FileName)
        Catch ex As Exception
        End Try
    End If
    ' Scan for Revision Cloud Elements
    doScan()
Else
    MsgBox("Please select a valid file name and location", MsgBoxStyle.Exclamation, "Error")
    Me.Close()
End If

The next section of code generates a couple lists. One list for the Revision Cloud elements in the project selected by category and one for the sheets that exist in the model. This sample will ignore revision cloud elements that are on views that are not placed on sheets since if they aren't on a sheet, they are not part of the documentation (sounded logical at the time). At the end, we make sure the model has at least one sheet before we continue.


' Collect Sheets
m_Sheets = New List(Of DB.Element)
Dim m_SheetCollector As New DB.FilteredElementCollector(m_Doc)
m_SheetCollector.OfCategory(DB.BuiltInCategory.OST_Sheets)
m_Sheets = m_SheetCollector.ToElements

' Collect Revision Clouds
m_RevClouds = New List(Of DB.Element)
Dim m_RevCloudCollector As New DB.FilteredElementCollector(m_Doc)
m_RevCloudCollector.OfCategory(DB.BuiltInCategory.OST_RevisionClouds)
m_RevClouds = m_RevCloudCollector.ToElements

' No sheets... no revisions!
If m_Sheets.Count < 1 Then Me.Close()

Now the next section saves the data it finds for each ViewSheet into a class named clsSheetMapper (not shown) so I can maintain an easy reference to the data between the clouds found on sheets and their host sheets.


' List all sheets and views for easy reference
        For Each x As DB.ViewSheet In m_Sheets
            ' Sheet element
            Dim m_Sht As New clsSheetMapper(x.Id.ToString, True)
            ' Sheet Number
            Dim m_ShtNumber As String = x.SheetNumber
            m_Sht.SheetNumber = m_ShtNumber
            ' Sheet Name
            Dim m_ShtName As String = x.Name
            m_Sht.SheetName = x.Name
            ' Add the view to the master list
            m_ViewsList.Add(m_Sht)
            ' Add the Views
            For Each y As DB.Element In x.Views
                ' View element
                Dim m_View As New clsSheetMapper(y.Id.ToString, False)
                ' Sheet Number
                m_View.SheetNumber = m_ShtNumber
                ' Sheet Name
                m_View.SheetName = m_ShtName
                ' View Name
                m_View.ViewName = y.Name
                ' Add the view to the master list
                m_ViewsList.Add(m_View)
            Next
        Next

The next bit of code finishes it up by scanning the revision cloud elements into a class named clsRevCloud I use to collect all of the view naming, sheet data and Revision Cloud parameter data into a single class and saving their parameter data to yet another secret class not shown named clsPara.


' Write the title line in our CSV file
        writeCSVline("Sheet Number, Sheet Name, View Name, ElementID, Revision Number, Revision Date, Comments, Mark, Issued To, Issued By")
        m_Revs.Clear()
        ' Process Revision Cloud Elements
        For Each m_RevCloud As DB.Element In m_RevClouds
            ' Create a matching Rev Item
            Dim m_RevItem As New clsRevcloud(m_RevCloud.Id.ToString)
            ' Test for viewID
            For Each x As clsSheetMapper In m_ViewsList
                Try
                    If x.ViewID = m_Doc.Element(m_RevCloud.OwnerViewId).Id.ToString Then
                        ' This is the view item
                        If x.SheetNumber IsNot Nothing Then
                            m_RevItem.SheetNumber = x.SheetNumber
                        End If
                        If x.SheetName IsNot Nothing Then
                            m_RevItem.SheetName = x.SheetName
                        End If
                        If x.ViewName IsNot Nothing Then
                            m_RevItem.ViewName = x.ViewName
                        End If
                        For Each y As DB.Parameter In m_RevCloud.Parameters
                            Dim myPara As New clsPara(y)
                            If myPara.Value IsNot Nothing Then
                                Select Case y.Definition.Name.ToUpper
                                    Case "REVISION NUMBER"
                                        m_RevItem.RevisionNumber = myPara.Value
                                    Case "REVISION DATE"
                                        m_RevItem.RevisionDate = myPara.Value
                                    Case "COMMENTS"
                                        m_RevItem.Comments = myPara.Value
                                    Case "MARK"
                                        m_RevItem.Mark = myPara.Value
                                    Case "ISSUED TO"
                                        m_RevItem.IssuedTo = myPara.Value
                                    Case "ISSUED BY"
                                        m_RevItem.IssuedBy = myPara.Value
                                End Select
                            End If
                        Next
                        Exit For
                    End If
                Catch ex As Exception
                    ' Some may not have an ownerID
                End Try
            Next
            m_Revs.Add(m_RevItem)
        Next

Now that we've got this handy list of classes with all of the sheet, view, and parameter data we can iterate them all and write the results to an external file... in this case it is a CSV file.


' Write all of the records
        For Each x As clsRevcloud In m_Revs
            ' Skip items without a sheet number
            If x.SheetNumber IsNot Nothing And x.SheetNumber <> "" Then
                Dim LineItem As String = ""
                LineItem = x.SheetNumber & ","
                LineItem = LineItem & x.SheetName & ","
                LineItem = LineItem & x.ViewName & ","
                LineItem = LineItem & x.ElementID & ","
                LineItem = LineItem & x.RevisionNumber & ","
                LineItem = LineItem & x.RevisionDate & ","
                LineItem = LineItem & x.Comments & ","
                LineItem = LineItem & x.Mark & ","
                LineItem = LineItem & x.IssuedTo & ","
                LineItem = LineItem & x.IssuedBy
                ' Write the Line
                writeCSVline(LineItem)
            End If
        Next

That's it... now if you ever have a HUGE project and a needy client that wants this data each time to issue a revision set, you can export a handy report for their use as well as for you to help keep track of your changes in your models.