Monday, February 28, 2011

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

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

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


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


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

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


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

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


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

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

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

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

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


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

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

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


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

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


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

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


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

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


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

            End Try

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


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

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


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

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

Sunday, February 27, 2011

Convert Rooms to 3D Masses

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

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

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


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


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

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



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


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

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

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


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

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


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

    End Sub

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


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

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

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

Friday, February 18, 2011

Top 10 Reasons Chuck Norris Would be a Great BIM Manager

I realize that I haven't posted any API stuff lately. I've been tied up with the BETA's as well as writing the API chapter for the next installment of "Mastering Autodesk Revit Architecture 2012"... Look for an essential post later this month on converting rooms to masses without tessellation (super fast).

It's amazing how much of a BIM Manager's time is taken up daily to cater to the occasional user's laziness or lack of fear. If BIM Manager's were more like Chuck Norris, our jobs would be amazingly more efficient and productive [wink]... So here are the top ten reasons on why I think Chuck Norris would be an excellent BIM Manager:


10.) User's would never miss a deadline for fear that they might have to become victim to his wrath

9.) Plotter's would never fail or run out of paper where Chuck Norris is BIM Manager

8.) Getting user's to pay attention in training sessions would never be easier

7.) Why?... Chuck Norris, that's why!

6.) Chuck Norris doesn't miss deadlines because they work only within HIS schedule!

5.) Chuck Norris could improve the functionality of Revit simply by starring it down

4.) All constraints would always be satisfied or die a painful round-house death

3.) No Revit model under Chuck Norris' responsibility would ever think of containing an error or perform slowly

2.) Chuck Norris would tell families to build themselves, and they would

1.) Not even Revit would dare give warnings to Chuck Norris

Tuesday, February 15, 2011

Best Idea for a Building... EVER

This post requires very little explanation, Pie day is next month!

Now this is Architectin'