// // (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. // 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, /// /// Menu for Analysis category /// Analysis, /// /// Menu for Massing category /// Massing, /// /// Menu for Selection category /// Selection } #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 } }