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

Thursday, December 15, 2011

How To Uninstall or Remove a Revit Add-In

I've been getting this question quite a bit recently:
"How do I remove an Add-In from Revit?"



This post will not focus on what an Add-In is nor what an Add-In manifest is... hopefully you already have an idea or can find out about those things here.

The uninstall solution depends on the version of Revit that you are running. The method that I will discuss in this post is suitable for Revit 2011 and newer. If you are using Revit 2010 then you just need to go ahead and upgrade your shiz.

There are two locations that Add-In manifests can be placed to load into Revit. The paths shown below are for Windows7. If you are still running Windows XP than it may be time to update your other shiz.

Machine Wide Location
The first is available to all users that log into the machine and requires administrative privileges to add files to.

  • C:\ProgramData\Autodesk\Revit\Addins\
User Profile Location
The user profile location will only load for the current user. This location does not require administrative privileges and therefor anyone can install Revit Add-Ins into their environment if they use this location.
  • %USERPROFILE%\AppData\Roaming\Autodesk\Revit\Addins\

Preventing the Add-In from Loading
There's a bunch of ways to prevent an Add-In from loading into Revit.

  • Removing the associated .addin file from either of the locations mentioned above will prevent the Add-In from loading into Revit. You can leave the .dll and all other files there if you want and they will not load without the associated .addin file instructing Revit to do so.
  • Another trick is to simply rename the extension of the .addin file to something like .addin.notloaded or something to keep it from loading.
  • Revit 2011 and 2012 do not load .addin files that are placed in sub-directories of these Add-In manifest directories either, so you could just simply create a sub-directory for the Add-Ins that you don't want to be loaded and place them in that sub directory.

Monday, August 1, 2011

Finding the Centroid of a Room Boundary

It's been a while since my last post and I'm sure most of you were like... "Where the hell is Don!".... it's ok! I'm still around. I've been busy working on stuff I can't talk about. Don't worry though, I'm not a good secret keeper.

So this post is going to explain something that a bunch of folks have issues with that involves finding the actual centroid of a polygon, or in this case a Room element. Now let's be careful not to confuse a centroid with a pair of midpoints taken from the furthest X and Y planes... a centroid is more closely described as the center of mass within a polygon.


Now this is done by taking a series of 2D points and running some tricky math on them and dividing the points by 6x the area of the polygon. So to make it simple for you guys, I've taken the liberty of sharing a couple of functions that makes this all possible. The samples here are in Revit 2011 format.

First you'll need a function that iterates through the boundary segments of a Room Element and builds up a series of 2D points taken from either endpoints of each segment (no need to worry about curved segments since they usually wont effect the centroid too much, or you can add the midpoint of the curve arc to get it closer).

This little Function will return a list of 2D PointF from a boundary of a Room element.


''' <summary>
    ''' Extract a List of 2D Points from a Room's Boundary
    ''' </summary>
    ''' <param name="p_room"></param>
    ''' <remarks></remarks>
    Private Sub ExtractBoundaryPointsFromRoom(p_room As Room)
        ' The Points List
        Dim m_pts As New List(Of PointF)
        ' The Z Height
        Dim m_z As Double = 0
        ' Work with the Boundary
        Dim m_bsaa As Autodesk.Revit.DB.Architecture.BoundarySegmentArrayArray = m_room.Boundary
        ' Segment Array at Floor Level
        For Each bsa As Autodesk.Revit.DB.Architecture.BoundarySegmentArray In m_bsaa
            Try
                For Each bs As Autodesk.Revit.DB.Architecture.BoundarySegment In bsa
                    Dim m_c As Curve = bs.Curve
                    ' First Endpoint
                    Dim m_EndPoint1 As XYZ = m_c.EndPoint(0)
                    Dim m_PointF1 As New PointF(m_EndPoint1(0), m_EndPoint1(1))
                    m_pts.Add(m_PointF1)
                    ' Second Endpoint
                    Dim m_EndPoint2 As XYZ = m_c.EndPoint(1)
                    Dim m_PointF2 As New PointF(m_EndPoint2(0), m_EndPoint2(1))
                    m_pts.Add(m_PointF2)
                    ' The Height
                    m_z = m_EndPoint1(2)
                Next
            Catch ex As Exception

            End Try
        Next
        ' Return the 2D Centroid
        Dim m_2Dcentroid As PointF = FindCentroid(m_pts.ToArray, m_room.Area)
        ' Add the Floor Level of Boundary for Z Elevation
        InsertionPoint = New XYZ(m_2Dcentroid.X, m_2Dcentroid.Y, m_z)
    End Sub

The Function below will take a list of points (first gathered from the segments array of a room) and convert them to a real life centroid in 2D format. The Z elevation is pretty easy to figure out for a room and what ever you're using this for is typically going to use 0 or a preset elevation for the result anyway.


''' <summary>
    ''' Find 2D Centroid
    ''' </summary>
    ''' <param name="pts">Collection of Points Describing the Polygon</param>
    ''' <param name="p_rmArea">The Area of the Polygon</param>
    ''' <returns>2D Point (Pointf)</returns>
    ''' <remarks>This Function Kicks Ass</remarks>
    Private Function FindCentroid(ByVal pts() As PointF, p_rmArea As Single) As PointF
        ' Add the First PT to the End of the Array (full circulation)
        ReDim Preserve pts(pts.Length)
        pts(pts.Length - 1) = New PointF(pts(0).X, pts(0).Y)
        ' Get the Centroid
        Dim X As Single = 0
        Dim Y As Single = 0
        Dim m_sf As Single
        ' This is Where the Magic Happens
        For i As Integer = 0 To pts.Length - 2
            m_sf = pts(i).X * pts(i + 1).Y - pts(i + 1).X * pts(i).Y
            X += (pts(i).X + pts(i + 1).X) * m_sf
            Y += (pts(i).Y + pts(i + 1).Y) * m_sf
        Next i
        ' Divide by 6X the Are of the Polygon
        X /= (6 * p_rmArea)
        Y /= (6 * p_rmArea)
        ' This is the Final Result
        Return New PointF(X, Y)
    End Function

That's all until next time...

Wednesday, April 27, 2011

Revit 2011 Worksharing User Name

Do you have a bunch of public workstations that your users use to access workshared Revit 2011 models? Have you ever noticed that the name that displays in the worksharing monitor does not match the actual user's name?... Well this is an issue that has been resolved in Revit 2012 but is till annoying in Revit 2011.


So if you're still wondering how to change this name (sigh), you can adjust it to read anything you like... just be careful that you do not have two names in any two machines that are the same and have the same model loaded at the same time (this really jacks up how Revit 2011 worksharing works and will cause some dumb stuff...)



So if you have a bunch (or just one) public walk-up or training machines that people use to access workshared Revit 2011 models, enter a name in the user name field that is descriptive as to the machine's use or location:

  • San Francisco Giants 2010 World Series Champion Test Machine #1
  • Training #1
  • 197th Floor Spaceship Walk-up
  • Etc.

Wednesday, March 2, 2011

Thou Shall Read Thy Dialog Box!!

This may appear to be a ridiculous post to most... but you would be amazed as to how many people call me to fix something with their model when they get a popup dialog that they have not seen before. They could solve their own problems 99% of the time if they would just simply READ the dialog box.

It really gets me though when people never read any warnings or dialogs at all and just close them no matter what. These are the people that tend to destroy stuff and don't even know it!

So please... if you're new to Revit or have been doing this stuff for 20 years... please... PLEASE read the dialogs and warning popups all the time!


...or the code ninjas will get you!!! I'm planning to start placing random jokes in my dialogs to urge people to want to start reading dialog boxes... I wish Autodesk could do the same

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.

Wednesday, January 19, 2011

Batch Family Fun for Everyone!!! Part 1

I bet you've been waking up recently in the middle of the night dreaming about how you could jockey the Revit API to batch process families, haven't you?... Well you're in luck because that's exactly what this post is for.

There's obviously a whole load of topics to dive into on batch processing families, so to keep it short and sweet I'll touch on how to open a family, add a shared parameter with a set value and close the family with the saved values. Later posts will build on even cooler family API processing ideas.

The first thing you will need to accomplish is get a reference to the local Revit application and document using a class that implements the IExternalCommand interface. The code belows shows the beginning of such a class...


Public Class ScribblesForBlog

    Private m_Manager As FamilyManager = Nothing
    Private m_App As UIApplication
    Private m_Doc As Document = Nothing
    Private m_SharedParamFile As DefinitionFile

    ''' <summary>
    ''' General Class Constructor
    ''' </summary>
    ''' <param name="CmdData"></param>
    ''' <remarks></remarks>
    Public Sub New(ByVal CmdData As ExternalCommandData)
        m_Doc = CmdData.Application.ActiveUIDocument.Document
        m_App = CmdData.Application
        If m_Doc.IsFamilyDocument Then m_Manager = m_Doc.FamilyManager
        ' Get a reference to the active shared parameter file if one exists
        Try
            m_SharedParamFile = m_App.Application.OpenSharedParameterFile
        Catch ex As Exception
            ' It is possible to have an empty value here (No Active Shared Parameter File)
        End Try
    End Sub

End Class

The next thing we need is a quick and simple means of opening and closing external family files by calling their file paths as an argument to open the file. The functions below will open and close the active family respectively:


''' <summary>
    ''' Open an external family file and set the active doc reference to this family
    ''' </summary>
    ''' <param name="m_FileName">Full path to a valid rfa file</param>
    ''' <returns>Set the document object to this family on success</returns>
    ''' <remarks>www.RevitNet.blogspot.com RULES</remarks>
    Public Function OpenExternalFamily(ByVal m_FileName As String) As Boolean
        Try
            ' Open the family and set the active document object to the family
            m_Doc = m_App.Application.OpenDocumentFile(m_FileName)

            ' Verify the active document is the correct family (paranoia)
            If m_Doc.IsFamilyDocument And m_Doc.PathName.ToUpper = m_FileName.ToUpper Then
                ' Update the reference to the active family manager
                m_Manager = m_Doc.FamilyManager
                ' We're done, success
                Return True
            End If
            ' If we got to this line then something failed
            m_Manager = Nothing
            Return False
        Catch ex As Exception
            Return False
        End Try
    End Function

    ''' <summary>
    ''' Close the current family file
    ''' </summary>
    ''' <param name="p_SaveFile">Set to True will save the file on close</param>
    ''' <returns>Optional save, default is YES</returns>
    ''' <remarks></remarks>
    Public Function CloseActiveFamily(Optional ByVal p_SaveFile As Boolean = True) As Boolean
        Try
            ' This should only run on family files
            If m_Doc.IsFamilyDocument Then
                ' True means to save the file 
                m_Doc.Close(p_SaveFile)
            End If
            ' We're done, success
            Return True
        Catch ex As Exception
            Return False
        End Try
    End Function

Now that we can open and close the families... lets add a shared parameter! That would be exciting, right? We can open a shared parameter using:


''' <summary>
    ''' Open as set the shared parameter file
    ''' </summary>
    ''' <param name="p_FileName">Full path to the shared parameter file</param>
    ''' <remarks></remarks>
    Public Sub OpenSharedParameterFile(ByVal p_FileName As String)
        If IO.File.Exists(p_FileName) Then
            Try
                m_App.Application.SharedParametersFilename = p_FileName
                m_SharedParamFile = m_App.Application.OpenSharedParameterFile
            Catch ex As Exception
                ' Should never fail... maybe a poorly formatted shared parameter file?
            End Try
        End If
    End Sub

And now for some real excitement! The code below will search your active shared parameter file for a specific parameter by name and then add it to the family and set a value to it!:


''' <summary>
    ''' Add a shared parameter to the current family and use a string
    ''' to set its value. A double formatted parameter can use 9'-4" (architectural) format
    ''' </summary>
    ''' <param name="SharedParameterName">The Name of the parameter to find and add</param>
    ''' <returns>True on Success</returns>
    ''' <remarks></remarks>
    Public Function AddSharedParameter(ByVal SharedParameterName As String, _
                                       ByVal SharedParameterValue As String, _
                                       ByVal isInstParameter As Boolean) As Boolean
        Try
            ' Make sure we are working with a valid family document
            If m_Doc.IsFamilyDocument Then
                ' make sure we have a valid shared parameter file
                If m_SharedParamFile IsNot Nothing Then
                    ' Iterate the Parameter Groups
                    For Each group As DefinitionGroup In m_SharedParamFile.Groups
                        ' Iterate the Parameters in the Group
                        For Each def As ExternalDefinition In group.Definitions
                            ' Do we have a match?
                            If def.Name.ToUpper <> SharedParameterName.ToUpper Then Continue For
                            ' We found the parameter that we're after, 
                            ' does it already exist in the family?
                            Dim param As FamilyParameter = m_Manager.Parameter(def.Name)
                            ' If we have a valid parameter object,
                            ' then it exists in the family (no need to add it)
                            If param IsNot Nothing Then
                                ' Start a new Family transaction
                                Dim m_TransFam As New Transaction(m_Doc, "Family Transaction")
                                m_TransFam.Start()
                                Try
                                    ' Set the value from string...
                                    '9'-6" can set to a double or a string
                                    m_Manager.SetValueString(param, SharedParameterValue)
                                    m_TransFam.Commit()
                                Catch ex As Exception
                                    ' Roll back on failure
                                    m_TransFam.RollBack()
                                End Try

                            Else ' Need to add the parameter...
                                ' Start a new Family transaction
                                Dim m_TransFam As New Transaction(m_Doc, "Family Transaction")
                                ' Add the parameter as type or instance
                                If param.IsInstance = isInstParameter Then
                                    m_TransFam.Start()
                                    Try ' First add the parameter
                                        m_Manager.AddParameter(def, def.ParameterGroup, isInstParameter)
                                        Dim fParam As FamilyParameter = m_Manager.Parameter(def.Name)
                                        If fParam IsNot Nothing Then
                                            ' Set the value from string...
                                            '9'-6" can set to a double or a string
                                            m_Manager.SetValueString(param, SharedParameterValue)
                                        End If
                                        m_TransFam.Commit()
                                    Catch ex As Exception
                                        ' Roll back on failure
                                        m_TransFam.RollBack()
                                    End Try

                                End If
                            End If
                        Next
                    Next
                End If
            End If
            Return True
        Catch ex As Exception
            ' Something did not go according to plan if we make it here
            Return False
        End Try
    End Function

So that's the basics! You now know how to open a family and add a shared parameter to it... Look for more batch family capabilities in future posts!

Tuesday, January 4, 2011

Visual Studio 2010 Revit 2011 Addin Templates

Have you begun using Visual Studio 2010 for your Addin development yet? Well if you have, then this post is for you! Just don't forget to always set your .NET Framework to 3.5

As you begin to develop with the Revit API, it can be annoying to have to duplicate your addin setup code and references to the Revit API. Did you know that you can create custom addin templates for your projects with all references and boiler plate code prepopulated? Just think of all the women that you can impress at the bar with a story about how you can do this!!!

Create a project with no special functionality but with all required references to the Revit API along with a basic command class setup the way you like and an application class as well. This will help make it easier to use the same template for both commands and applications. Links to the prebuilt template files that I use are at the bottom of this post.

You should maintain separate templates for each flavor of Revit that you develop for since the paths to the API references are different.


The provided "Command" class


'.NET common used namespaces
Imports System
Imports System.Windows.Forms
Imports System.Collections.Generic

'Revit.NET common used namespaces
Imports Autodesk.Revit.ApplicationServices
Imports Autodesk.Revit.Attributes
Imports Autodesk.Revit.DB
Imports Autodesk.Revit.UI
Imports Autodesk.Revit.UI.Selection

&lt;Transaction(TransactionMode.Automatic)> _
&lt;Regeneration(RegenerationOption.Manual)> _
Public Class Commands
    Implements IExternalCommand

    ''' &lt;summary>
    ''' Main entry point for every external command.
    ''' &lt;/summary>
    ''' &lt;param name="commandData">Provides access to the Revit app and docs&lt;/param>
    ''' &lt;param name="message">Return message&lt;/param>
    ''' &lt;param name="elements">elements&lt;/param>
    ''' &lt;returns>Cancelled, Failed or Succeeded Result code.&lt;/returns>
    Public Function Execute(ByVal commandData As ExternalCommandData, _
                            ByRef message As String, _
                            ByVal elements As ElementSet) As Result Implements IExternalCommand.Execute
        Try
            ' Add your code here


            Return Result.Succeeded
        Catch ex As Exception
            ' Add failure handling here

            Return Result.Failed
        End Try

    End Function
End Class


The provided "Application" class


'.NET common used namespaces
Imports System.Windows.Forms
Imports System.Collections.Generic

'Revit.NET common used namespaces
Imports Autodesk.Revit.ApplicationServices
Imports Autodesk.Revit.Attributes
Imports Autodesk.Revit.DB
Imports Autodesk.Revit.UI
Imports Autodesk.Revit.UI.Selection

&lt;Transaction(TransactionMode.Automatic)> _
 &lt;Regeneration(RegenerationOption.Manual)> _
Class Application
    Implements IExternalApplication
    ''' &lt;summary>
    ''' Implement the external application when Revit starts
    ''' before a file or default template is actually loaded.
    ''' &lt;/summary>
    ''' &lt;param name="application">Contains the controlled application.&lt;/param>
    ''' &lt;returns>Return the status &lt;/returns>
    Public Function OnStartup(ByVal application As UIControlledApplication) _
                    As Result Implements IExternalApplication.OnStartup
        ' Add your code here


        ' Return Success
        Return Result.Succeeded
    End Function

    ''' &lt;summary>
    ''' Implement the external application when Revit is about to exit.
    ''' Any documents must have been closed before this method is called.
    ''' &lt;/summary>
    ''' &lt;param name="application">Contains the controlled application.&lt;/param>
    ''' &lt;returns>Return the status&lt;/returns>
    Public Function OnShutdown(ByVal application As UIControlledApplication) _
                    As Result Implements IExternalApplication.OnShutdown

        ' Add your code here


        ' Return Success
        Return Result.Succeeded
    End Function
End Class

All you have to do is export a project as a template (the result will be a zip file) and place them under your current user's profile at:
"%USERPROFILE%\Documents\Visual Studio 2010\Templates\ProjectTemplates\Visual Basic".

It is important to place your templates into a sub directory (we'll name ours "Revit 2011") and to NOT unzip the template files. Leave the zip files intact and this will work just fine. Your directory structure should look something like the image below.


Links to the above mentioned templates can be found here (for those that do not want or know how to create their own):

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

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

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