Instant Autosesk Revit 2013 Customization with ... - Packt Publishing

30 downloads 88 Views 158KB Size Report
This is the bonus section of the book Instant Autosesk Revit 2013 Customization with .NET. How-to. Here we are going to cover recipes for enabling and ...
Instant Autosesk Revit 2013 Customization with .NET How-to: Bonus Section This is the bonus section of the book Instant Autosesk Revit 2013 Customization with .NET How-to. Here we are going to cover recipes for enabling and disabling control for commands and creating, loading, and placing the control in the family generated by the command.

Dynamically enable a control (Become an expert) There may come a time when you need to prevent the user from being able to click one of your command buttons. The Revit API supports enabling and disabling commands by implementing the IExternalCommandAvailability interface and applying it to a button class. Your reasons for disabling a command can be just about anything you want. Some examples might include scenarios such as the state of the current model not meeting your application's requirements, the model name may not meet a specific requirement that you specify, or maybe the wrong flavor of Revit was used to open the model.

Instant Autosesk Revit 2013 Customization with .NET How-to: Bonus Section

Getting ready We will implement an IExternalCommandAvailability interface where we allow the command to be enabled only when a door element is selected in the current model.

How to do it... 1. Add a new class named AvailabilityDoorSelected.vb. 2. Enter the IExternalCommandAvailability code as shown, which returns true only when a door element is selected in the model: Imports Autodesk.Revit.UI Imports Autodesk.Revit.DB ''' ''' Enables a Command Only if a ''' Door is Selected in the User Interface ''' Public Class AvailabilityDoorSelected Implements IExternalCommandAvailability ''' ''' Command Availability Implementation ''' Public Function IsCommandAvailable(appData As _ UIApplication, selectedCats As CategorySet) _ As Boolean Implements _ IExternalCommandAvailability.IsCommandAvailable ' Iterate Categories For i = 0 To selectedCats.Size Try ' Get the Category Dim m_c As Category = _ TryCast(selectedCats(i), Category) ' Is it a Door? If m_c.Name = "Doors" Then Return True Catch End Try Next 2

Instant Autosesk Revit 2013 Customization with .NET How-to: Bonus Section Return False End Function End Class

3. Open the ApplicationRibbon class and enter the following code right before the ' Success line in the AddPushButton function that will assign this availability implementation to our sample button: ' Availability - Door Selection m_pb.AvailabilityClassName = _ "Revit2013Samples_VB.AvailabilityDoorSelected"

4. Run the add-in in the debugger and open one of the sample project models that ship with Revit. 5. Select the door element in the model to enable the sample button. Deselect the door and the button will become disabled again.

How it works... Our AvailabilityDoorSelected class will execute anytime that a user control that this class is assigned to is either made visible in the Revit user interface or an element in the model is selected. Element selection will only execute this class when an assigned control is visible in the Revit UI. There is no limit to the number of user controls that you can assign to any class that implements IExternalCommandAvailability nor is there a limit to the amount of IExternalCommandAvailability implementations that you can use in your projects. The following illustration shows on the left-hand side how the main button looks without a door selected and on the right-hand side how it becomes enabled when a door is selected.

3

Instant Autosesk Revit 2013 Customization with .NET How-to: Bonus Section We could have generated a more complex scenario for our availability implementation, but in an effort to keep it simple we only included door selection as a requirement to enable our command. The possibilities for this functionality are endless and I'm sure you will find plenty of uses for it in your own projects in the future.

Creating, loading, and placing a family (Must know) Now that we know how to find elements, how about we create one? In this recipe, we will create a simple box extrusion, load it into the model, and then place an instance of it. We will use a window form this time so we can dynamically control the dimensions and family name being generated by our command. Our sample family creation command will utilize several functions to build the family and get it placed into the model. We will place most of the functions in their own module making them accessible by other commands if we need them. Functions put into modules should have their scope set to public anytime they will be accessed from other classes outside of the module.

Getting ready This sample implements IExternalCommand and will require its own entry into the .addin manifest file in order to run. Add the following entry to our Revit2013Samples_VB.addin file inside the RevitAddIns tag being careful not to nest it in any other Command, Application, or DBApplication: Sample - Ch3 Generic Model Family Revit2013Samples_VB.dll Revit2013Samples_VB.CommandGenericModel 14a6cba3-8563-4dc7-a026-47fe519a11a0 XXXX Packt Publishing Samples for 'Autodesk Revit 2013 Customization with .NET'

4

Instant Autosesk Revit 2013 Customization with .NET How-to: Bonus Section

How to do it... 1. Add a new module named ModuleCh3 and add the following four namespaces to the very top of the file: Imports Imports Imports Imports

Autodesk.Revit.DB Autodesk.Revit.ApplicationServices Autodesk.Revit.UI System.IO

2. We will need to use a family template to generate our new family. The GetGenericModelTemplatePath function attempts to locate the GenericModel.rft family template, and if found returns the path to this file. Add this function inside the ModuleCh3 declaration: ''' ''' Get the Family Template Path ''' Public Function GetGenericModelTemplatePath _ (p_app As Application) _ As String ' Base Family Template Location Dim _path As String _path = p_app.FamilyTemplatePath ' Generic Model Dim _gm As String _gm = _path + "\Generic Model.rft" If File.Exists(_gm) _ Then Return _gm ' Not Found Return "" End Function

3. Why not add a material to our family while we're at it with a random color assignment? The GetMaterial function returns a material element by name if found in the model and optionally can create it if the createMissing argument is set to True. Add the GetMaterial function just beneath the GetGenericModelTemplatePath function inside the ModuleCh3 declaration: ''' ''' ''' '''

Get a material if it exists or create it if it does not 5

Instant Autosesk Revit 2013 Customization with .NET How-to: Bonus Section Public Function GetMaterial(p_name As String, p_doc As Document, p_transparency As Integer, createMissing As Boolean) _ As Material ' Collect all Material Dim m_colMat As New FilteredElementCollector(p_doc) m_colMat.OfCategory(BuiltInCategory.OST_Materials) ' Find the Material by Name For Each m As Material In m_colMat.ToElements If m.Name.ToLower = p_name.ToLower Then m.Transparency = p_transparency Return m End If Next ' Create? If createMissing = False Then Return Nothing ' Random Number Generator Dim m_r As New Random Try ' Create the material Dim m_matid As ElementId m_matid = Material.Create(p_doc, p_name) Dim m_material As Material m_material = TryCast(p_doc.GetElement(m_matid), Material) ' Random Color Dim m_color As New Color(m_r.Next(1, 255), m_r.Next(1, 255), m_r.Next(1, 255)) ' Apply the Color to the Material m_material.Color = m_color ' Set 50% Transparency m_material.Transparency = p_transparency

6

Instant Autosesk Revit 2013 Customization with .NET How-to: Bonus Section ' Return the Material Return m_material Catch End Try ' Failure Return Nothing End Function

4. We will apply the material to a new subcategory using a function called GetSubCategory. This function is similar to the one that we used to create the material in that it will return the category element if it is found in the model and optionally creating it if the createMissing argument is set to True. Add the GetSubCategory function just beneath the GetMaterial function inside the ModuleCh3 declaration: ''' ''' Get a subcategory if it exists or ''' create it if it does not ''' Public Function GetSubCategory(p_parentCategory As Category, p_subCategoryName As String, p_doc As Document, createMissing As Boolean) As Category ' Find the Subcategory by Name For Each c As Category In p_parentCategory.SubCategories If c.Name = p_subCategoryName Then Return c Exit For End If Next ' Create? If createMissing = False Then Return Nothing ' Create the Subcategory Try Dim m_subCat As Category = Nothing m_subCat = _ p_doc.Settings.Categories. _ NewSubcategory(p_parentCategory, 7

Instant Autosesk Revit 2013 Customization with .NET How-to: Bonus Section p_subCategoryName) ' Success Return m_subCat Catch End Try ' Failure Return Nothing End Function

5. The CreateFamily function is where the magic happens. This function uses the p_famTemplate argument to open and generate a new family from the family template, creating and applying a material on a subcategory with the same name. The geometry is built using the dimension arguments p_length, p_width, and p_height. The last part of the function saves the family file to the My Documents folder on the local machine. Add the CreateFamily function just beneath the GetSubCategory function inside the ModuleCh3 declaration: ''' ''' Generate the Family ''' Public Function CreateFamily(p_savePath As String, p_uiApp As UIApplication, p_famTemplate As String, p_parentCategory As Category, p_length As Double, p_width As Double, p_height As Double, Optional p_subCatname As String = "") _ As Boolean ' Family Document Dim m_FamDoc As Document m_FamDoc = _ p_uiApp.Application.NewFamilyDocument(p_famTemplate) ' Start a New Family Transaction Dim t As New Transaction(m_FamDoc, "Family Transaction") ' Start the Transaction If t.Start Then

8

Instant Autosesk Revit 2013 Customization with .NET How-to: Bonus Section Try ' Subcategory Dim m_c As Category = Nothing If Not String.IsNullOrEmpty(p_subCatname) Then ' Get/Create SubCategory m_c = GetSubCategory(p_parentCategory, p_subCatname, m_FamDoc, True) ' If Category If Not m_c Is Nothing Then ' Get/Create a Material Dim m_m As Material m_m = GetMaterial(p_subCatname, m_FamDoc, 50, True) If Not m_m Is Nothing Then Try ' Apply the Material to the Category m_c.Material = m_m Catch End Try End If End If End If ' Skethplane for Extrusion Dim m_plane As Plane m_plane = _ p_uiApp.Application.Create.NewPlane _ (New XYZ(0, 0, 1), New XYZ(0, 0, 0)) 9

Instant Autosesk Revit 2013 Customization with .NET How-to: Bonus Section Dim m_skplane As SketchPlane m_skplane = _ m_FamDoc.FamilyCreate.NewSketchPlane(m_plane) ' List of Points Dim m_pts As New List(Of XYZ) m_pts.Add(New XYZ(0, 0, 0)) m_pts.Add(New XYZ(0, p_width, 0)) m_pts.Add(New XYZ(p_length, p_width, 0)) m_pts.Add(New XYZ(p_length, 0, 0)) ' The Curve Array Dim profile As New CurveArray() Dim n As Integer = m_pts.Count For i As Integer = 0 To n - 1 Dim j As Integer = If((0 = i), n - 1, i - 1) profile.Append _ (p_uiApp.Application.Create.NewLineBound _ (m_pts(j), m_pts(i))) Next ' Array of Curve Arrays Dim m_caa As New CurveArrArray m_caa.Append(profile) ' Generate the Extrusion Dim m_extrusion As Extrusion m_extrusion = _ m_FamDoc.FamilyCreate.NewExtrusion(True, m_caa, m_skplane, p_height) ' Apply to Subcategory If Not m_c Is Nothing Then Try m_extrusion.Subcategory = m_c

10

Instant Autosesk Revit 2013 Customization with .NET How-to: Bonus Section Catch End Try End If ' Done Editing t.Commit() ' Save and Close the Family, ' Options to Replace Existing Dim m_opt As New SaveAsOptions m_opt.OverwriteExistingFile = True m_FamDoc.SaveAs(p_savePath, m_opt) m_FamDoc.Close() ' Success Return True Catch ex As Exception End Try End If ' Failed transaction t.RollBack() ' Failure Return False End Function

6. Now that we have a function that can create a family, we will need one to load it into the document. The LoadFamily function takes a path to an .rfa file along with a target document to load the family into. Add the following LoadFamily function just beneath the CreateFamily function inside the ModuleCh3 declaration: ''' ''' Load a Family into a Document ''' Public Function LoadFamily(p_path As String, p_doc As Document) As Family

11

Instant Autosesk Revit 2013 Customization with .NET How-to: Bonus Section Try ' Load the Family, tricky Byref Dim m_family As Family = Nothing p_doc.LoadFamily(p_path, m_family) ' Success Return m_family Catch ex As Exception End Try ' Failure Return Nothing End Function

7. The last function that we will add to ModuleCh3 is PlaceNonStructuralFamily. This function accepts an XYZ argument that is used for location placement. The p_family argument is the family element to place into the document represented in the p_doc argument. Add PlaceNonStructuralFamily after the LoadFamily function: ''' ''' Place a Family ''' Public Function PlaceNonStructuralFamily(p_xyz As XYZ, p_family As Family, p_doc As Document) As Boolean ' Get the Default Symbol from the Family Dim m_FamIterator As FamilySymbolSetIterator Try ' Retrieve the first symbol m_FamIterator = p_family.Symbols.ForwardIterator() m_FamIterator.MoveNext() ' Get the Symbol Element Dim m_FamSymbol As FamilySymbol m_FamSymbol = TryCast(m_FamIterator.Current, FamilySymbol)

12

Instant Autosesk Revit 2013 Customization with .NET How-to: Bonus Section ' Place the Family as non structural Dim m_Instance As FamilyInstance m_Instance = p_doc.Create.NewFamilyInstance(p_xyz, m_FamSymbol, [Structure].StructuralType.NonStructural) ' Success Return True Catch ex As Exception End Try ' Failure Return False End Function

8. Now that our functions are all ready to build and place the family, we can create the user form that will be used to control the dimensions and file name for the family. Add a new windows form named form_FamilyExtrusion and add for the user two buttons and four textbox controls as shown in the following screenshot:

13

Instant Autosesk Revit 2013 Customization with .NET How-to: Bonus Section 9. Add the following four namespace imports to the very top of the form class file: Imports Imports Imports Imports

System.Environment Autodesk.Revit.ApplicationServices Autodesk.Revit.DB Autodesk.Revit.UI

10. Add the following private variable inside the form class declaration: Private Private Private Private

m_UiApp As UIApplication m_Doc As Document m_famCat As Category m_tpath As String

11. Add the form class constructor subroutine just beneath the private variable declarations. This subroutine performs the preliminary processing that we need to prepare our form with: ''' ''' Constructor ''' Public Sub New(p_doc As Document, p_uiApp As UIApplication) InitializeComponent() ' Widen Scope m_Doc = p_doc m_UiApp = p_uiApp ' Get the Template Path m_tpath = GetGenericModelTemplatePath(m_UiApp.Application) ' Model Category m_famCat = p_doc.Settings.Categories.Item _ (BuiltInCategory.OST_GenericModel) ' Defaults Me.TextBoxLength.Text = 4 Me.TextBoxWidth.Text = 4 Me.TextBoxHeight.Text = 2.5 Me.TextBoxName.Text = "PacktFamilySample.rfa" End Sub

14

Instant Autosesk Revit 2013 Customization with .NET How-to: Bonus Section 12. Add the family template response code to the form's Shown event: ''' ''' Nothing to do without the Template File :( ''' Private Sub form_FamilyExtrusion_Shown(sender As Object, e As System.EventArgs) Handles Me.Shown ' Did we get a valid template path? If String.IsNullOrEmpty(m_tpath) Then ' Inform the User MsgBox("Failed to locate the Family Template File!", MsgBoxStyle.Critical, "ERROR") ' Terminate Me.Close() End If End Sub

13. Double-click ButtonCancel and add the following code in its Click event: ''' ''' Cancel ''' Private Sub ButtonCancel_Click(sender As System.Object, e As System.EventArgs) _ Handles ButtonCancel.Click ' Close Me.Close() End Sub

14. Double-click ButtonOK and add the following code into its Click event. This code will step through the family creation, loading, and placement functions that we built in our ModuleCh3 module: ''' ''' Create and Place the Family ''' Private Sub ButtonOk_Click(sender As System.Object, e As System.EventArgs) _ Handles ButtonOk.Click 15

Instant Autosesk Revit 2013 Customization with .NET How-to: Bonus Section ' File Name Required If String.IsNullOrEmpty(Me.TextBoxName.Text) Then ' Inform user of Failure Dim m_td As New TaskDialog("Failure :(") m_td.MainInstruction = _ "Family Name is Required to Continue!" m_td.MainIcon = TaskDialogIcon.TaskDialogIconWarning m_td.Show() ' Failure Exit Sub Else ' File Extension Enforcement If Not Me.TextBoxName.Text.ToLower.EndsWith(".rfa") Then ' Add the missing extension Me.TextBoxName.Text += ".rfa" End If End If ' All dimensions must be If Me.TextBoxLength.Text Me.TextBoxLength.Text Me.TextBoxLength.Text

larger than 0 > 0 And > 0 And > 0 Then

' Full Family Name Dim m_fname As String m_fname = _ GetFolderPath(Environment.SpecialFolder.MyDocuments) m_fname += "\" & Me.TextBoxName.Text ' Create the Family If CreateFamily(m_fname, m_UiApp, m_tpath, m_famCat, Me.TextBoxLength.Text, Me.TextBoxWidth.Text, Me.TextBoxHeight.Text, 16

Instant Autosesk Revit 2013 Customization with .NET How-to: Bonus Section "PACKT") = True Then ' Start a New Transaction Dim t As New Transaction(m_Doc, "Family Placement") If t.Start Then ' Load the Family Dim m_f As Family m_f = LoadFamily(m_fname, m_Doc) ' Everything Working? If Not m_f Is Nothing Then ' Place the Family at 0,0,0 Dim m_xyz As New XYZ(0, 0, 0) If PlaceNonStructuralFamily(m_xyz, m_f, m_Doc) = True Then ' Commit Succes t.Commit() ' Inform user of Success! Dim m_td As New TaskDialog("Success! :)") m_td.MainInstruction = _ "Placed an instance of: " & m_fname & vbCr & " at 0,0,0" m_td.MainIcon = _ TaskDialogIcon.TaskDialogIconNone m_td.Show() Else ' Inform user of Failure Dim m_td As New TaskDialog("Failure :(") m_td.MainInstruction = _ "Failed to place family: " & m_fname m_td.MainIcon = _ TaskDialogIcon.TaskDialogIconWarning m_td.Show() End If 17

Instant Autosesk Revit 2013 Customization with .NET How-to: Bonus Section Else ' Failed to Load Dim m_td As New TaskDialog("Failure :(") m_td.MainInstruction = "Failed to load family :(" m_td.MainIcon = _ TaskDialogIcon.TaskDialogIconWarning m_td.Show() End If Else ' Failed to Create Dim m_td As New TaskDialog("Failure :(") m_td.MainInstruction = "Failed to create family :(" m_td.MainIcon = TaskDialogIcon.TaskDialogIconWarning m_td.Show() End If Else ' Inform user of Failure Dim m_td As New TaskDialog("Failure :(") m_td.MainInstruction = _ "All dims need to be larger than 0" m_td.MainIcon = TaskDialogIcon.TaskDialogIconWarning m_td.Show() End If End If ' Close Me.Close() End Sub

15. Add a new class named CommandGenericModel and copy the boilerplate code from CommandBoilerPlate into this class being careful to keep the new class name as CommandGenericModel.

18

Instant Autosesk Revit 2013 Customization with .NET How-to: Bonus Section 16. The only code that we need to add to our CommandGenericModel class is what is necessary to construct and display our user form. Place the following code between the ' Begin Code Here and ' Success lines: Dim m_doc As Document m_doc = _ commandData.Application.ActiveUIDocument.Document ' Construct the Form Using _d As New form_FamilyExtrusion(m_doc, commandData.Application) ' Display the Form _d.ShowDialog() End Using

17. Run the command and generate a family. 18. Open the 3D view and set the visibility style to Shaded With Edges so that you can see the random color that was applied to the material that the extrusion form's subcategory was set to. Keep in mind that the color of your extrusion may differ from the one shown here since we used a random color generator.

19

Instant Autosesk Revit 2013 Customization with .NET How-to: Bonus Section

How it works... All component families start from the family template, so the first problem that our code needed to solve was to locate the installed generic model family template and if it could not find it to inform the user and halt. The GetGenericModelTemplatePath function searches for a file named Generic Model.rft in the directory configured as the family template files location in your Revit installation. A fairly basic user form offers the means for the user to enter the dimensional sizes for each of the box extrusion sides along with a family name. A subcategory and material is then created before the boundary of the extrusion is sketched on a new plane and extruded as a new family document. The family is then loaded into the model and placed at 0, 0, 0.

There's more... Since we obviously do not want the user to be able to enter a non-numeric character into the textbox controls that we use for the dimensions of our box extrusion, we can control the allowable characters in each of these three user controls. Add the following code to the KeyPress event for each of the three dimensional control textboxes in our user form: If Not (Char.IsDigit(e.KeyChar) Or e.KeyChar = ".") Then e.Handled = True End If

This snippet of code will allow any numeric character as well as a decimal point and prevent any other character from being entered into the textbox.

20