// // (C) Copyright 2003-2013 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. // using System; using System.Collections; using System.Diagnostics; using System.IO; using System.Reflection; using System.Windows.Forms; using System.Collections.Generic; using System.Windows.Media.Imaging; using Autodesk.Revit; using Autodesk.Revit.UI; using Autodesk.Revit.ApplicationServices; namespace RvtSamples { /// /// Main external application class. /// A generic menu generator application. /// Read a text file and add entries to the Revit menu. /// Any number and location of entries is supported. /// public class Application : IExternalApplication { /// /// Default pulldown menus for samples /// public enum DefaultPulldownMenus { /// /// Menu for Basics category /// Basics, /// /// Menu for Geometry category /// Geometry, /// /// Menu for Parameters category /// Parameters, /// /// Menu for Elements category /// Elements, /// /// Menu for Families category /// Families, /// /// Menu for Materials category /// Materials, /// /// Menu for Annotation category /// Annotation, /// /// Menu for Views category /// Views, /// /// Menu for Rooms/Spaces category /// RoomsAndSpaces, /// /// Menu for Data Exchange category /// DataExchange, /// /// Menu for MEP category /// MEP, /// /// Menu for Structure category /// Structure } #region Member Data /// /// Separator of category for samples have more than one category /// static char[] s_charSeparatorOfCategory = new char[] { ',' }; /// /// chars which will be trimmed in the file to include extra sample list files /// static char[] s_trimChars = new char[] { ' ', '"', '\'', '<', '>' }; /// /// The start symbol of lines to include extra sample list files /// static string s_includeSymbol = "#include"; /// /// Assembly directory /// static string s_assemblyDirectory = Path.GetDirectoryName( System.Reflection.Assembly.GetExecutingAssembly().Location ); /// /// Name of file which contains information required for menu items /// public const string m_fileNameStem = "RvtSamples.txt"; /// /// The controlled application of Revit /// private UIControlledApplication m_application; /// /// List contains all pulldown button items contain sample items /// private SortedList m_pulldownButtons = new SortedList(); /// /// List contains information of samples not belong to default pulldown menus /// private SortedList> m_customizedMenus = new SortedList>(); /// /// List contains information of samples belong to default pulldown menus /// private SortedList> m_defaultMenus = new SortedList>(); /// /// Panel for RvtSamples /// RibbonPanel m_panelRvtSamples; #endregion // Member Data #region IExternalApplication Members /// /// Implement this method to implement the external application which should be called when /// Revit starts before a file or default template is actually loaded. /// /// An object that is passed to the external application /// which contains the controlled application. /// Return the status of the external application. /// A result of Succeeded means that the external application successfully started. /// Cancelled can be used to signify that the user cancelled the external operation at /// some point. /// If false is returned then Revit should inform the user that the external application /// failed to load and the release the internal reference. public Autodesk.Revit.UI.Result OnStartup( UIControlledApplication application ) { m_application = application; Autodesk.Revit.UI.Result rc = Autodesk.Revit.UI.Result.Failed; string[] lines = null; int n = 0; int k = 0; try { // Check whether the file contains samples' list exists // If not, return failure string filename = m_fileNameStem; if( !GetFilepath( ref filename ) ) { ErrorMsg( m_fileNameStem + " not found." ); return rc; } // Read all lines from the file lines = ReadAllLinesWithInclude( filename ); // Remove comments lines = RemoveComments( lines ); // Add default pulldown menus of samples to Revit m_panelRvtSamples = application.CreateRibbonPanel( "RvtSamples" ); int i = 0; List pdData = new List( 3 ); foreach( string category in Enum.GetNames( typeof( DefaultPulldownMenus ) ) ) { if( ( i + 1 ) % 3 == 1 ) { pdData.Clear(); } // // Prepare PulldownButtonData for add stacked buttons operation // string displayName = GetDisplayNameByEnumName( category ); List sampleItems = new List(); m_defaultMenus.Add( category, sampleItems ); PulldownButtonData data = new PulldownButtonData( displayName, displayName ); pdData.Add( data ); // // Add stacked buttons to RvtSamples panel and set their display names and images // if( ( i + 1 ) % 3 == 0 ) { IList addedButtons = m_panelRvtSamples.AddStackedItems( pdData[0], pdData[1], pdData[2] ); foreach( RibbonItem item in addedButtons ) { String name = item.ItemText; string enumName = GetEnumNameByDisplayName( name ); PulldownButton button = item as PulldownButton; button.Image = new BitmapImage( new Uri( Path.Combine( s_assemblyDirectory, "Icons\\" + enumName + ".ico" ), UriKind.Absolute ) ); button.ToolTip = Properties.Resource.ResourceManager.GetString( enumName ); m_pulldownButtons.Add( name, button ); } } i++; } // // Add sample items to the pulldown buttons // n = lines.GetLength( 0 ); k = 0; while( k < n ) { AddSample( lines, n, ref k ); } AddSamplesToDefaultPulldownMenus(); AddCustomizedPulldownMenus(); rc = Autodesk.Revit.UI.Result.Succeeded; } catch( Exception e ) { string s = string.Format( "{0}: n = {1}, k = {2}, lines[k] = {3}", e.Message, n, k, ( k < n ? lines[k] : "eof" ) ); ErrorMsg( s ); } return rc; } /// /// Get a button's enum name by its display name /// /// display name /// enum name private string GetEnumNameByDisplayName( string name ) { string enumName = null; if( name.Equals( "Rooms/Spaces" ) ) { enumName = DefaultPulldownMenus.RoomsAndSpaces.ToString(); } else if( name.Equals( "Data Exchange" ) ) { enumName = DefaultPulldownMenus.DataExchange.ToString(); } else { enumName = name; } return enumName; } /// /// Get a button's display name by its enum name /// /// The button's enum name /// The button's display name private string GetDisplayNameByEnumName( string enumName ) { string displayName = null; if( enumName.Equals( DefaultPulldownMenus.RoomsAndSpaces.ToString() ) ) { displayName = "Rooms/Spaces"; } else if( enumName.Equals( DefaultPulldownMenus.DataExchange.ToString() ) ) { displayName = "Data Exchange"; } else { displayName = enumName; } return displayName; } /// /// Implement this method to implement the external application which should be called when /// Revit is about to exit,Any documents must have been closed before this method is called. /// /// An object that is passed to the external application /// which contains the controlled application. /// Return the status of the external application. /// A result of Succeeded means that the external application successfully shutdown. /// Cancelled can be used to signify that the user cancelled the external operation at /// some point. /// If false is returned then the Revit user should be warned of the failure of the external /// application to shut down correctly. public Autodesk.Revit.UI.Result OnShutdown( UIControlledApplication application ) { return Autodesk.Revit.UI.Result.Succeeded; } #endregion // IExternalApplication Members /// /// Display error message /// /// Message to display public void ErrorMsg( string msg ) { Debug.WriteLine( "RvtSamples: " + msg ); TaskDialog.Show( "RvtSamples", msg, TaskDialogCommonButtons.Ok ); } #region Parser /// /// Read file contents, including contents of files included in the current file /// /// Current file to be read /// All lines of file contents private string[] ReadAllLinesWithInclude( string filename ) { if( !File.Exists( filename ) ) { ErrorMsg( filename + " not found." ); return new string[] { }; } string[] lines = File.ReadAllLines( filename ); int n = lines.GetLength( 0 ); ArrayList all_lines = new ArrayList( n ); foreach( string line in lines ) { string s = line.TrimStart(); if( s.ToLower().StartsWith( s_includeSymbol ) ) { string filename2 = s.Substring( s_includeSymbol.Length ); filename2 = filename2.Trim( s_trimChars ); all_lines.AddRange( ReadAllLinesWithInclude( filename2 ) ); } else { all_lines.Add( line ); } } return all_lines.ToArray( typeof( string ) ) as string[]; } /// /// Get the input file path. /// Search and return the full path for the given file /// in the current exe directory or one or two directory levels higher. /// /// Input filename stem, output full file path /// True if found, false otherwise. bool GetFilepath( ref string filename ) { string path = Path.Combine( s_assemblyDirectory, filename ); bool rc = File.Exists( path ); // Get full path of the file if( rc ) { filename = Path.GetFullPath( path ); } return rc; } /// /// Remove all comments and empty lines from a given array of lines. /// Comments are delimited by '#' to the end of the line. /// string[] RemoveComments( string[] lines ) { int n = lines.GetLength( 0 ); string[] a = new string[n]; int i = 0; foreach( string line in lines ) { string s = line; int j = s.IndexOf( '#' ); if( 0 <= j ) { s = s.Substring( 0, j ); } s = s.Trim(); if( 0 < s.Length ) { a[i++] = s; } } string[] b = new string[i]; n = i; for( i = 0; i < n; ++i ) { b[i] = a[i]; } return b; } #endregion // Parser #region Menu Helpers /// /// Add a new command to the corresponding pulldown button. /// /// Array of lines defining sample's category, display name, description, large image, image, assembly and classname /// Total number of lines in array /// Current index in array void AddSample( string[] lines, int n, ref int i ) { if( n < i + 6 ) { throw new Exception( string.Format( "Incomplete record at line {0} of {1}", i, m_fileNameStem ) ); } string categories = lines[i++].Trim(); string displayName = lines[i++].Trim(); string description = lines[i++].Trim(); string largeImage = lines[i++].Remove( 0, 11 ).Trim(); string image = lines[i++].Remove( 0, 6 ).Trim(); string assembly = lines[i++].Trim(); string className = lines[i++].Trim(); if( !File.Exists( assembly ) ) // jeremy { ErrorMsg( string.Format( "Assembly '{0}' specified in line {1} of {2} not found", assembly, i, m_fileNameStem ) ); } bool testClassName = false; // jeremy if( testClassName ) { Debug.Print( "RvtSamples: testing command {0} in assembly '{1}'.", className, assembly ); try { // first load the revit api assembly, otherwise we cannot query the external app for its types: //Assembly revit = Assembly.LoadFrom( "C:/Program Files/Revit Architecture 2009/Program/RevitAPI.dll" ); //string root = "C:/Program Files/Autodesk Revit Architecture 2010/Program/"; //Assembly adWindows = Assembly.LoadFrom( root + "AdWindows.dll" ); //Assembly uiFramework = Assembly.LoadFrom( root + "UIFramework.dll" ); //Assembly revit = Assembly.LoadFrom( root + "RevitAPI.dll" ); // load the assembly into the current application domain: Assembly a = Assembly.LoadFrom( assembly ); if( null == a ) { ErrorMsg( string.Format( "Unable to load assembly '{0}' specified in line {1} of {2}", assembly, i, m_fileNameStem ) ); } else { // get the type to use: Type t = a.GetType( className ); if( null == t ) { ErrorMsg( string.Format( "External command class {0} in assembly '{1}' specified in line {2} of {3} not found", className, assembly, i, m_fileNameStem ) ); } else { // get the method to call: MethodInfo m = t.GetMethod( "Execute" ); if( null == m ) { ErrorMsg( string.Format( "External command class {0} in assembly '{1}' specified in line {2} of {3} does not define an Execute method", className, assembly, i, m_fileNameStem ) ); } } } } catch( Exception ex ) { ErrorMsg( string.Format( "Exception '{0}' \ntesting assembly '{1}' \nspecified in line {2} of {3}", ex.Message, assembly, i, m_fileNameStem ) ); } } // // If sample belongs to default category, add the sample item to the sample list of the default category // If not, store the information for adding to RvtSamples panel later // string[] entries = categories.Split( s_charSeparatorOfCategory, StringSplitOptions.RemoveEmptyEntries ); foreach( string value in entries ) { string category = value.Trim(); SampleItem item = new SampleItem( category, displayName, description, largeImage, image, assembly, className ); if( m_pulldownButtons.ContainsKey( category ) ) { m_defaultMenus.Values[m_defaultMenus.IndexOfKey( GetEnumNameByDisplayName( category ) )].Add( item ); } else if( m_customizedMenus.ContainsKey( category ) ) { List sampleItems = m_customizedMenus.Values[m_customizedMenus.IndexOfKey( category )]; sampleItems.Add( item ); } else { List sampleItems = new List(); sampleItems.Add( item ); m_customizedMenus.Add( category, sampleItems ); } } } /// /// Add sample item to pulldown menu /// /// Pulldown menu /// Sample item to be added private void AddSampleToPulldownMenu( PulldownButton pullDownButton, SampleItem item ) { PushButtonData pushButtonData = new PushButtonData( item.DisplayName, item.DisplayName, item.Assembly, item.ClassName ); PushButton pushButton = pullDownButton.AddPushButton( pushButtonData ); if( !string.IsNullOrEmpty( item.LargeImage ) ) { BitmapImage largeImageSource = new BitmapImage( new Uri( item.LargeImage, UriKind.Absolute ) ); pushButton.LargeImage = largeImageSource; } if( !string.IsNullOrEmpty( item.Image ) ) { BitmapImage imageSource = new BitmapImage( new Uri( item.Image, UriKind.Absolute ) ); pushButton.Image = imageSource; } pushButton.ToolTip = item.Description; } /// /// Comparer to sort sample items by their display name /// /// sample item 1 /// sample item 2 /// compare result private static int SortByDisplayName( SampleItem item1, SampleItem item2 ) { return string.Compare( item1.DisplayName, item2.DisplayName ); } /// /// Sort samples in one category by the sample items' display name /// /// samples to be sorted private void SortSampleItemsInOneCategory( SortedList> menus ) { int iCount = menus.Count; for( int j = 0; j < iCount; j++ ) { List sampleItems = menus.Values[j]; sampleItems.Sort( SortByDisplayName ); } } /// /// Add samples of categories in default categories /// private void AddSamplesToDefaultPulldownMenus() { int iCount = m_defaultMenus.Count; // Sort sample items in every category by display name SortSampleItemsInOneCategory( m_defaultMenus ); for( int i = 0; i < iCount; i++ ) { string category = m_defaultMenus.Keys[i]; List sampleItems = m_defaultMenus.Values[i]; PulldownButton menuButton = m_pulldownButtons.Values[m_pulldownButtons.IndexOfKey( GetDisplayNameByEnumName( category ) )]; foreach( SampleItem item in sampleItems ) { AddSampleToPulldownMenu( menuButton, item ); } } } /// /// Add samples of categories not in default categories /// private void AddCustomizedPulldownMenus() { int iCount = m_customizedMenus.Count; // Sort sample items in every category by display name SortSampleItemsInOneCategory( m_customizedMenus ); int i = 0; while( iCount >= 3 ) { string name = m_customizedMenus.Keys[i++]; PulldownButtonData data1 = new PulldownButtonData( name, name ); name = m_customizedMenus.Keys[i++]; PulldownButtonData data2 = new PulldownButtonData( name, name ); name = m_customizedMenus.Keys[i++]; PulldownButtonData data3 = new PulldownButtonData( name, name ); IList buttons = m_panelRvtSamples.AddStackedItems( data1, data2, data3 ); AddSamplesToStackedButtons( buttons ); iCount -= 3; } if( iCount == 2 ) { string name = m_customizedMenus.Keys[i++]; PulldownButtonData data1 = new PulldownButtonData( name, name ); name = m_customizedMenus.Keys[i++]; PulldownButtonData data2 = new PulldownButtonData( name, name ); IList buttons = m_panelRvtSamples.AddStackedItems( data1, data2 ); AddSamplesToStackedButtons( buttons ); } else if( iCount == 1 ) { string name = m_customizedMenus.Keys[i]; PulldownButtonData pulldownButtonData = new PulldownButtonData( name, name ); PulldownButton button = m_panelRvtSamples.AddItem( pulldownButtonData ) as PulldownButton; List sampleItems = m_customizedMenus.Values[m_customizedMenus.IndexOfKey( button.Name )]; foreach( SampleItem item in sampleItems ) { AddSampleToPulldownMenu( button, item ); } } } /// /// Add samples to corresponding pulldown button /// /// pulldown buttons private void AddSamplesToStackedButtons( IList buttons ) { foreach( RibbonItem rItem in buttons ) { PulldownButton button = rItem as PulldownButton; List sampleItems = m_customizedMenus.Values[m_customizedMenus.IndexOfKey( button.Name )]; foreach( SampleItem item in sampleItems ) { AddSampleToPulldownMenu( button, item ); } } } #endregion // Menu Helpers } }