' ' (C) Copyright 2003-2019 by Autodesk, Inc. ' ' Permission to use, copy, modify, and distribute this software in ' object code form for any purpose and without fee is hereby granted, ' provided that the above copyright notice appears in all copies and ' that both that copyright notice and the limited warranty and ' restricted rights notice below appear in all supporting ' documentation. ' ' AUTODESK PROVIDES THIS PROGRAM "AS IS" AND WITH ALL FAULTS. ' AUTODESK SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF ' MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE. AUTODESK, INC. ' DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE ' UNINTERRUPTED OR ERROR FREE. ' ' Use, duplication, or disclosure by the U.S. Government is subject to ' restrictions set forth in FAR 52.227-19 (Commercial Computer ' Software - Restricted Rights) and DFAR 252.227-7013(c)(1)(ii) ' (Rights in Technical Data and Computer Software), as applicable. ' Imports MsExcel = Microsoft.Office.Interop.Excel Imports Autodesk.Revit Imports Autodesk.Revit.UI Imports Autodesk.Revit.DB _ _ _ Public Class Command Implements IExternalCommand ''' ''' Implement this method as an external command for Revit. ''' ''' An object that is passed to the external application ''' which contains data related to the command, ''' such as the application object and active view. ''' A message that can be set by the external application ''' which will be displayed if a failure or cancellation is returned by ''' the external command. ''' A set of elements to which the external application ''' can add elements that are to be highlighted in case of failure or cancellation. ''' Return the status of the external command. ''' A result of Succeeded means that the API external method functioned as expected. ''' Cancelled can be used to signify that the user cancelled the external operation ''' at some point. Failure should be returned if the application is unable to proceed with ''' the operation. Public Function Execute(ByVal commandData As ExternalCommandData, ByRef message As String, ByVal elements As Autodesk.Revit.DB.ElementSet) _ As Autodesk.Revit.UI.Result Implements IExternalCommand.Execute ' Setup a default message in case any exceptions are thrown that we have not ' explicitly handled. On failure the message will be displayed by Revit message = "The sample failed" Execute = Autodesk.Revit.UI.Result.Failed Try ' Use Microsoft Office 2003 or later. Dim excel As MsExcel.Application excel = Nothing ' search the entire Revit project, grouping elements by category Dim sortedElements As System.Collections.Generic.Dictionary(Of String, ElementSet) sortedElements = GetSortedNonSymbolElements(commandData.Application, commandData.Application.ActiveUIDocument.Document) If (sortedElements Is Nothing) Then Return Execute End If ' loop through all the categories found and send them to Microsoft Excel Dim iter As System.Collections.Generic.Dictionary(Of String, ElementSet).Enumerator = sortedElements.GetEnumerator() Do While (iter.MoveNext()) ' the Revit map iterator provides access to the key as well as the values Dim categoryName As String = iter.Current.Key Dim elementSet As Autodesk.Revit.DB.ElementSet = iter.Current.Value Dim sendSuccess As Boolean = SendToExcel(excel, commandData.Application.Application, categoryName, elementSet) If (sendSuccess = False) Then Return Execute End If Loop ' change our result to successful Execute = Autodesk.Revit.UI.Result.Succeeded Return Execute Catch ex As Runtime.InteropServices.COMException message = "Something wrong with Microsoft Office Object library." Return Execute Catch ex As Exception message = ex.Message Return Execute End Try End Function ''' ''' LaunchExcel will try to launch Excel 2003 or later. It will then create an empty workbook ''' and remove all of the work sheets except one which will form the basis for the first ''' category that is sent to Excel by this sample ''' ''' Excel application ''' Function LaunchExcel() As MsExcel.Application Dim excel As MsExcel.Application = New MsExcel.Application() If (excel Is Nothing) Then Return Nothing End If ' make excel visible so that operations made are visible to the user excel.Visible = True ' Add a new workbook which will default to having 3 work sheets Dim workbook As MsExcel.Workbook = excel.Workbooks.Add() Dim worksheet As MsExcel.Worksheet ' remove all the sheets except one Do While workbook.Sheets.Count > 1 worksheet = workbook.Sheets.Item(1) worksheet.Delete() Loop Return excel End Function ''' ''' the SendToExcel method takes a category and a set of elements that belong to that category ''' and finds the common properties between those elements. Excel is then sent the names and ''' values of these properties, adding the category as a new sheet ''' ''' Excel application ''' Revit application ''' name of category ''' the elements in the category ''' If function succeed, return True. Otherwise False ''' Function SendToExcel(ByRef excel As MsExcel.Application, ByVal application As Autodesk.Revit.ApplicationServices.Application, _ ByVal categoryName As String, ByVal elementSet As Autodesk.Revit.DB.ElementSet) As Boolean 'Dim worksheet As Microsoft.Office.Interop.Excel.Worksheet Dim worksheet As MsExcel.Worksheet SendToExcel = False ' If excel is not running, then launch it which will result in one sheet remaining. If excel ' is already running then we need to create a new sheet. If (excel Is Nothing) Then excel = LaunchExcel() If (excel Is Nothing) Then Return SendToExcel End If worksheet = excel.ActiveSheet Else worksheet = excel.Worksheets.Add() End If ' Set the name of the sheet to be that of the category If (categoryName.Length > 31) Then worksheet.Name = categoryName.Substring(0, 31) Else worksheet.Name = categoryName End If ' using all the elements, find common properties amongst them Dim propertyNames As System.Collections.Generic.List(Of String) = GetCommonPropertyNames(application, elementSet) ' the first column in the sheet will be the Element ID of the element worksheet.Cells(1, 1).Value = "ID" ' now add the common property names as the column headers Dim propertyName As String Dim column As Integer = 2 For Each propertyName In propertyNames worksheet.Cells(1, column).Value = propertyName column = column + 1 Next ' devote a row to each element that belongs to the category Dim element As Autodesk.Revit.DB.Element Dim row As Integer = 2 For Each element In elementSet ' first column is the element id - display it as an integer worksheet.Cells(row, 1).Value = element.Id.ToString() ' retrieve all the values, in string form for the common properties of the element Dim values As System.Collections.Generic.Dictionary(Of String, String) = GetValuesOfNamedProperties(application, element, propertyNames) column = 2 If (values.Count > 0) Then For Each propertyName In propertyNames ' check to see if the element actually supports that property and then set the ' excel cell to be the value If (values.ContainsKey(propertyName)) Then worksheet.Cells(row, column).Value = values.Item(propertyName) End If column = column + 1 Next row = row + 1 End If Next SendToExcel = True End Function ''' ''' GetValuesOfNamedProperties takes a set of property names and looks up the values for those properties ''' returning them in a string form ''' ''' Revit application ''' element ''' names of properties ''' The map stores the values according to the names of properties ''' Function GetValuesOfNamedProperties(ByVal application As Autodesk.Revit.ApplicationServices.Application, _ ByVal element As Autodesk.Revit.DB.Element, ByVal propertyNames As System.Collections.Generic.List(Of String)) _ As System.Collections.Generic.Dictionary(Of String, String) Dim values As System.Collections.Generic.Dictionary(Of String, String) = New System.Collections.Generic.Dictionary(Of String, String) Dim parameter As Autodesk.Revit.DB.Parameter ' loop through all the parameters that the element has For Each parameter In element.Parameters ' the name for the parameter is held in the parameter definition If propertyNames.Contains(parameter.Definition.Name) Then Dim stringValue As String = "" ' take the internal type of the parameter and convert it into a string Select Case parameter.StorageType Case Autodesk.Revit.DB.StorageType.Double stringValue = parameter.AsDouble ' in the case of ElementId, retrieve the element, if possible, and use its name Case Autodesk.Revit.DB.StorageType.ElementId Dim paramElement As Autodesk.Revit.DB.Element = element.Document.GetElement(parameter.AsElementId) If Not (paramElement Is Nothing) Then stringValue = paramElement.Name End If Case Autodesk.Revit.DB.StorageType.Integer stringValue = parameter.AsInteger Case Autodesk.Revit.DB.StorageType.String stringValue = parameter.AsString Case Else End Select values.Add(parameter.Definition.Name, stringValue) End If Next GetValuesOfNamedProperties = values End Function ''' ''' GetCommonPropertyNames takes a set of elements and seeks the property names that are common between them ''' If an element does not support any properties at all it is ignored. ''' The process of finding the common elements is done by collecting all the names of parameters for ''' the first element and then removing those that are not used by the other elements ''' ''' Revit application ''' elements ''' A set of common properties ''' Function GetCommonPropertyNames(ByVal application As Autodesk.Revit.ApplicationServices.Application, _ ByVal elementSet As Autodesk.Revit.DB.ElementSet) As System.Collections.Generic.List(Of String) Dim commonProperties As System.Collections.Generic.List(Of String) = New System.Collections.Generic.List(Of String) ' the first element that we handle (that has properties), a Dim addAllProperties As Boolean = True ' loop through all the elements passed to the method Dim iter As IEnumerator iter = elementSet.ForwardIterator Do While (iter.MoveNext()) Dim element As Autodesk.Revit.DB.Element = iter.Current ' get the parameters that the element supports Dim parameters As Autodesk.Revit.DB.ParameterSet = element.Parameters If Not (parameters.IsEmpty) Then ' definitionNames will contain all the parameter names that this element supports Dim definitionNames As System.Collections.Generic.List(Of String) = New System.Collections.Generic.List(Of String) Dim paramIter As Autodesk.Revit.DB.ParameterSetIterator = parameters.ForwardIterator ' loop through all of the parameters and retrieve their names Do While paramIter.MoveNext() Dim parameter As Autodesk.Revit.DB.Parameter = paramIter.Current Dim definitionName As String = parameter.Definition.Name If (addAllProperties) Then commonProperties.Add(definitionName) End If definitionNames.Add(definitionName) Loop If addAllProperties Then addAllProperties = False Else 'now loop through all of the common parameter we have found so far and see if they are 'supported by the element. If not, then remove it from the common set Dim commonDefinitionNamesToRemove As System.Collections.Generic.List(Of String) = New System.Collections.Generic.List(Of String) Dim commonDefinitionName As String For Each commonDefinitionName In commonProperties If (definitionNames.Contains(commonDefinitionName) = False) Then commonDefinitionNamesToRemove.Add(commonDefinitionName) End If Next ' remove the uncommon parameters Dim commonDefinitionNameToRemove As String For Each commonDefinitionNameToRemove In commonDefinitionNamesToRemove commonProperties.Remove(commonDefinitionNameToRemove) Next End If End If Loop GetCommonPropertyNames = commonProperties End Function ''' ''' GetSortedNonSymbolElements searches the entire Revit project and sorts the elements based ''' upon category. Revit Symbols (Types) are ignored as we are only interested in instances of elements. ''' ''' Revit application ''' Revit document ''' The map stores all the elements according to their category ''' Function GetSortedNonSymbolElements(ByVal application As Autodesk.Revit.UI.UIApplication, _ ByVal document As Autodesk.Revit.DB.Document) As System.Collections.Generic.Dictionary(Of String, ElementSet) GetSortedNonSymbolElements = New System.Collections.Generic.Dictionary(Of String, ElementSet) 'get a set of element which is not elementType Dim filter As Autodesk.Revit.DB.ElementIsElementTypeFilter filter = New Autodesk.Revit.DB.ElementIsElementTypeFilter(True) Dim collector As Autodesk.Revit.DB.FilteredElementCollector collector = New Autodesk.Revit.DB.FilteredElementCollector(application.ActiveUIDocument.Document) collector.WherePasses(filter) Dim iter As IEnumerator iter = collector.GetElementIterator Do While (iter.MoveNext()) Dim element As Autodesk.Revit.DB.Element element = iter.Current If Not (TypeOf element Is Autodesk.Revit.DB.ElementType) Then ' retrieve the category of the element Dim category As Autodesk.Revit.DB.Category category = element.Category If Not (category Is Nothing) Then Dim elementSet As Autodesk.Revit.DB.ElementSet Try ' if this is a category that we have seen before, then add this element to that set, ' otherwise create a new set and add the element to it. If GetSortedNonSymbolElements.ContainsKey(category.Name) Then elementSet = GetSortedNonSymbolElements.Item(category.Name) Else elementSet = application.Application.Create.NewElementSet() GetSortedNonSymbolElements.Add(category.Name, elementSet) End If elementSet.Insert(element) Catch ex As Exception End Try End If End If Loop End Function End Class