Sunday, December 26, 2010

Supplemental BIM Database Porn Part 2, Reporting Multiple Categories Together

This is part 2 of the highly anticipated and exciting mini series on supplemental BIM database design!

This post will focus on how data from two entirely different tables can be UNION queried together to aggregate counts and cost SUM's in a single report. The really cool part about how this works is that the fields don't have to match names or even data types between the two tables... I know, SUPER EXCITING~!!
The examples in this mini series have been built in MS Access 2007 in case you haven't noticed but could very easily be used with minor adjustments on SQL or my new favorite PostgreSQL.

A quick snapshot showing the 5 tables that we'll be working with is shown below. The type properties are in the category tables prefixed with "type" and the instance properties in the tables prefixed with "inst"...


UNION queries can only be built in MS Access 2007 using pure SQL syntax. This shouldn't be any big deal at all to a seasoned SQL DB Admin. To create a UNION query in MS Access 2007, click the "UNION" button in the query Design tab of the ribbon.


Union queries will show up in your MS Access 2007 Object Browser with a Union icon as shown below.


It is important to note that while the data types and names of the fields in each table can be entirely different when used in a UNION query, the quantity selected between each table must be equal. Select 5 fields in one table, then you better select 5 in the other. There is no limit to the amount of UNION queries you can run together, just remember to select the same quantity of fields in each of your table selections.


Another super important key is the order of the fields in each of your selection queries. That is to say that the first field in the first selection will list directly with the first field of the second selection query and so on, make sense? Great!


So what would the query look like? The example below shows the selection of all "Furniture" and "Furniture Systems" elements from four tables named "inst Furniture", "type Furniture", "inst Furniture Systems" and "type Furniture Systems." Each category holds a relationship between two tables for instance and type properties respectively. Yet another relationship to complicate matters further but also show the flexibility of such a query is the "ProjectInfo" table that holds the project information useful in populating report headers and footers.



The resulting query results (partial) looks like this (Quantity and TotalCost are aggregated across the two UNION queries in totally separate table groups):



Thursday, December 23, 2010

Supplemental BIM Database Porn Part 1, Introduction and Table Schema

Now that I've got your attention...

Have you ever wanted to know a little more about databases in the context of your everyday BIM related tasks but weren't quite sure where you should turn? Then this post is for you and if you're by yourself, nobody will be there to laugh at you! This is part 1 of what will more than likely be an entire action packed mini super series on how databases can help the BIM process and lead to your ability to conquer the world.

It is fairly common to manage design specifications for repetitive yet varying design elements for us in the AEC industry. Some example categories that are typically handled in such a way are Furniture, Furniture Systems, Door Hardware, Light Fixtures, and Casework to name a few. These items are commonly only tagged with a type value in the model and then synced with an external database where they can be further elaborated on.

So what's a good schema to use for a database design to solve such a task when your BIM application is say... Revit? Well, Revit stores data for it's elements in two basic forms that are basically entirely separate. Type data is stored in a "Family Symbol" where all of an element's "Type Parameters" live. The rest of the data describing an element in the model is stored as "Instance Parameters" within each instance of a placed symbol. So to mirror this in the database world, it makes sense to have one table to store an element's type properties and another related table to store the instance properties. It also makes good sense to have a matching pair of these tables for each Revit category that your elements belong to that you are interested in synchronizing.


A consistent naming strategy for these category based tables will also help you succeed. I like to use a simple prefix of "inst" for the instance properties and a prefix of "type" for the type property tables. I then just name each table with a suffix matching the Revit category name.

You may have also noticed in the image up above that the same "inst" and "type" prefixes are used for the primary keys of each table. This also helps with the relationships between the database and Revit. I prefer to use the "UniqueID" property for all elements as their primary key when synchronizing with a database. These values are pretty much guaranteed to never duplicate even across project models. The value returned by UniqueID is a hugungous GUID. I then use the UniqueID from the type element as a foreign key relationship to the instance tables. This is possible since the type element (family symbol) and the instances of these symbols as they are placed throughout the model are all unique! That is to say that the type element is entirely a separate object behind the scenes in the Revit API thus making a handy way to join them in a database schema.


I also generally use the name of the Revit parameter or property to name the corresponding database field name. String based parameters and properties should have a string formatted database field. Double formatted Revit parameters or properties should have their database fields formatted as decimal. Be careful with Revit parameters that are named using what are referred to as "Reserved Names" as these may result in strange unwanted features during synchronization. A list of these so called reserved words can be found at http://support.microsoft.com/kb/286335.

Stay tuned for future episodes of this action packed and exciting topic!!! We'll also get into how these tables and databases can be setup programmatic!!! Enormously exciting and guaranteed to help you pick up chicks at a bar!!

Monday, December 20, 2010

Parametric Holiday Tree from Charles Lee

Well, working for an Architectural firm definitely brings me closer to some strange and interesting people. One such interesting person I get to work with regularly is Mr. Charles Lee.


Charles was named this year's "Young Architect of the Year 2010" here in San Francisco and really loves the BIM stuff. Check out his biosarch blog based on BIO Design!

Here's Charlie's Revit model of a Holiday Tree... Thanks Charles! Maybe next time we can set this up as a shared model where everyone in the office could model their own ornaments to help decorate it! Next time we'll have to get that setup for our users...

Download

Friday, December 17, 2010

Wait for IT....

I know this is the worst blog post you will ever read in your human existence... but I just wanted you to know that I have a post on its way that is going to really bring the house down!!! Just waiting on the OK to post it!... Trust me... it's HUGE!

If I can't post it for some reason this week... it will have to wait until March (I wonder why)...

Tuesday, December 14, 2010

10 Pick up Lines All BIM Managers Must Know

If you lack a sense of humor or are easily offended then click an ad to the right and avoid reading this post! There will be other posts later that actually have a point!

As you can imagine, I have a lot of friends (pause) that poke fun at my line of work (I'm from Austin Texas). So I got into a bit of a challenge in comparing how my profession can just as easily be compared to theirs in terms of pick up lines to use at a bar. So here's the top ten pick up lines that ALL BIM managers need to know:

10.) These hands aren't just for constraining forms

9.) Have you ever been with a BIM Manager?

8.) All my models make it to stardom!

7.) Your forms are constrained quite efficiently

6.) I'm an expert at starting complex families...

5.) You have eyes that shine like the most elaborate curtainwall on the most perfect summer day

4.) You're more beautiful than any building I've ever modeled

3.) Speaking of Models, you're one of the best I've ever managed

2.) Has anyone ever 3D modeled you before?

1.) Wanna come back to my place and check out some sweet renderings?

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

Wednesday, December 8, 2010

Revit MEP Reuseable System Layouts Across Multiple Projects

Before anyone boo's me... this is a NON API post...

This one's for you HVAC guys out there struggling with the speed of Revit MEP. I know it can be a little painful at times, but boy is it nice to have an intelligent model as a finished product that you can really make use of throughout the CA and construction phase to help avoid change orders and SNAFU's in the field.

I'll have to warn you first about the performance hits that you may encounter if you fail to "UNGROUP" these systems after you place them into your model... PLEASE remember to ungroup all placements after they dropped into your model as these groups will really leave a mark in terms of performance if left as live groups.

OK... let's get started...

Have you ever worked on a building that uses repeatable VAV layouts or has numerous zones that for the most part are all basically the same in terms of elements placed to design it? This method can be used for just about anything you can imagine that utilizes system families, tags, text, and well... you get the idea.

I'm going to show you a way that you can build a warehouse of reusable system layouts complete with equipment, ductwork, and air terminals that you can reuse on all of your projects.

First let's start by drawing a full system complete with a VAV unit, all necessary ductwork, terminals and tags (you really could add anything you want to this).
Then let's select the items and create a group. Notice how you get two options for naming the groups? Tags will be placed into a nested group that you can name independently from the model group.

Now that you've got the group created, verify that your project browser displays the model group with a nested detail group beneath it like the image below.

Now to test if this thing works, right-click the model group from the above mentioned model group in the project browser and chose "Create Instance" and place one in your model (anywhere). It should look like the one below (without the tags... don't worry...).

Did you notice in the image above that you have an option button named "Attached Detail Group" when you select the model group that you just placed? Pick it and select the nested detail group name in the resulting dialog box.

Now your group should look a little more complete like the one below.

Now you can export your preconfigured system layout groups to a server where they can be shared across projects...


Use the "Load as Group" button in the "Insert" tab of the ribbon to add an externally saved group to a new project...


Tuesday, December 7, 2010

Human Readable Double Values for Parameters in the Revit API

This is a follow up or addition if you like to a previous post entitled A Classy Way to Handle Revit Parameters where I demonstrate a simple class for working with parameter objects in the Revit API.

This particular example is interesting in that we actually have a carnivorous way to get and set double formatted data by decimal as well as a more generic and simple way as string. It is obviously more difficult to calculate or aggregate values from a string representation value such as 6'-4" compared to 76.00 but sometimes the string representation is exactly what you need.

The sample code below demonstrates ho a variable initiated as a DB.Parameter is used to get and set the value from or to a string. The get will retrieve a value of 7'-0" or similarly you can set a value by entering 7'-0"...

Add the following snippet to the clsPara class to access the string values of a double formatted parameter:


''' <summary>
    ''' This property will return the string structured value for a double rather than a decimal
    ''' </summary>
    ''' <value></value>
    ''' <returns>A String</returns>
    ''' <remarks></remarks>
    Public Property DoubleValueAsString As String
        Get
            If m_parameter.StorageType = DB.StorageType.Double Then
                Try
                    ' Returns the human readable string representation of a double
                    Return m_parameter.AsValueString
                Catch ex As Exception
                    Return ""
                End Try
            Else
                Return ""
            End If
        End Get
        Set(ByVal value As String)
            If m_parameter.StorageType = DB.StorageType.Double Then
                Try
                    ' Sets the human readable string representation of a double (7'-0" or 150mm)
                    m_parameter.SetValueString(value)
                Catch
                End Try
            End If
        End Set
    End Property

Sunday, December 5, 2010

AU2010 CP333-1 .... 10001110100001011010100001101010001 1110100

Well I'm finally back and unwound from the trip to Las Vegas where I taught my first class for Autodesk University.

From what I could tell, about 140 (93 people completed a survey for me - THANKS) of the 186 that signed up for the class actually showed up and surprisingly, my speaker rating is an overall 4.469 out of 5! The comments people left were mostly right on point and next time I'll avoid the live code scrolling and slap the key stuff on some super duper slides and maybe even add some sound FX to keep people's senses going.

I just wished I had more than a tiny 60 minutes to present all the madness... I left a ton of information out of the presentation due to timing constraints... Oh well (enough complaining)...

William Lopez Campo mentioned my class as his favorite (I think he's just being nice ;) in his recently famous blog post entitled "AU 2010: My top 5 lists"... I have to say I read the whole post and couldn't agree more with what he wrote in there... right on point.

It was also really cool to meet all the industry badasses in regards to BIM in person (too many of them to name... you know who you are).



So the class was entitled "Leveraging the Tail End of the BIM Life Cycle with APIs" and was centered around how to build a powerful web environment where people could directly interact with data in a BIM model and even synchronize modified data back into the model if they so chose.

I'll be elaborating on this topic in future posts so don't go anywhere!!

Tuesday, November 30, 2010

San Francisco Wins HOK Best Visualization!!!

That's right... HOK San Francisco's own Travis Schmiesing won the 2010 HOK BIMie award for Best Visualization by a LANDSLIDE! Not only did he and Brian Campbell do all the work themselves, Travis presented their work and left the room in awe! The Animation presented was AMAZING. Truly professional!!

The project titles of which were presented have to be left anonymous for the time being, but each left us all speechless!! These projects will hopefully be given clearance to be mentioned in a later post on hokbimsolutions.blogspot.com !!

Four awards  were handed out in four distinct categories(winners from from left to right!!!):


Category C - Best Collaboration
Irena Zamozniak from the HOK Toronto Office (a real example of raising the bar)!!!

Category A - Best Concept Design:
Kevin Shumbera from the HOK Houston Office (he got lucky)!!!

Category B - Best Design Delivery
Travon Price out of the Washington D.C. Office (really cool presentation)!!!!!!

Category D - Best Visualization
Travis Schmiesing from my home San Francisco team office (... it was... "ok" :)  )!!!

Many thanks to the buildingSMART and ATG team as well as all of the sponsors and associated partners that helped kick the event over to the next level!!!

A truly successful event!!

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.