Progress reporting plugin for Revit

hello and welcome to this blog post! today we’re going to explore some of the tools that are exposed to us through the Revit API (Application programming interface), in order to create a simple yet effective progress tracking addin that can help the team leaders and project managers to better supervise the overall design and documentation process. the main difference between using the API and Dynamo is that Dynamo has a set of predefined nodes that each perform a certain task once you run the dynamo graph. but as intuitive as that is, it also quite obviously creates a number of serious limitation for us as developers such as:

  • Dynamo cannot run in the background as the user is working.
  • A Dynamo graph is pretty much open to the public so anyone can read through it, thus it’s not an easy task to protect your code. (although ZeroTouch partially resolves this issue).
  • An optimized addin can run much faster than a dynamo graph (specially for more complex tasks across a larger project), not to mention that the user does not have to launch Dynamo at all.
  • you have the ability to work in a fully supported IDE with intellisense and other functionalities.
  • The ability to use either WinForms or WPF to design an easy to understand user interface.
  • Last but not least, it’s extremely easy to incorporate third party libraries and DLLs into our code.

so now that you’re familiar with the main benefits of developing a Revit addin, let us get started!


Before we start writing any code though, you need to prepare a suitable programming environment. that includes installing and setting up your IDE, as well as making sure that the code can execute once it has been written. If you’re not familiar with Visual Studio, C# and/or writing addins for Revit, I strongly recommend that you read and follow through this beginner’s tutorial on Autodesk Knowledge Network: My first Revit plugin, you’ll also need a solid understanding of C# and general programming concepts. this blog post is more or less an overview of the whole process and is not going to go through each and every detail.

The main entry point of our program is going to be a class with an IExternalApplication interface. Here we can tell Revit that We’re going to be creating an addin and that we want our addin to have it’s own panel inside of Revit. Then we’ll proceed with adding buttons to the predefined panel. Each button is then connected to a class that has an IExternalCommand interface. This is where the majority of our code lives, since it is considered a “valid API context” for us to make our API calls from. A “valid Revit API context” is a concept that you’ll be hearing about quite often if you’re into creating addins for Revit. What it means in simple terms is that: you cannot make API calls where ever you want in your code, rather Revit is very specific in how your addin can communicate with it. Usually the builtin methods of IExternalApplication and IExternalCommand are considered “valid API context”. now with that out of the way, let’s add our very own panel and buttons to Revit. For the sake of simplicity we’ll call our buttons ON and OFF. they will enable and disable the progress tracker respectively.

class App : IExternalApplication
    {       
        public Result OnStartup(UIControlledApplication a)
        {  
           //Creating the pannel  
           string tabName = "Automation";
           a.CreateRibbonTab(tabName);
           RibbonPanel panel = a.CreateRibbonPanel(tabName, "Custom Tools");
           PushButtonData SJO_BtnData = new PushButtonData("SJO_BtnData", "SJO", Assembly.GetExecutingAssembly().Location, "modlessForm1.Command")
           //Creating and adding the buttons
           //adding button for updater on
           PushButtonData UpdateOn = new PushButtonData("UpdateOn", "ON", Assembly.GetExecutingAssembly().Location, "modlessForm1.Command6")
            {
                ToolTip = "This turns the updater on",
            };
           PushButton UpdateOn_Btn = panel.AddItem(UpdateOn) as PushButton;
           //adding button for updater off
           PushButtonData UpdateOff = new PushButtonData("UpdateOff", "OFF", Assembly.GetExecutingAssembly().Location, "modlessForm1.Command7")
            {
                ToolTip = "This turns the updater off",
            };
           PushButton UpdateOff_Btn = panel.AddItem(UpdateOff) as PushButton;   
           return Result.Succeeded;
        }
    }
Our (ON and OFF) buttons have been successfully added to Revit.

The next step is to program each button. This is done through two IExternalCommand, one for each button. In the ON button command, we’ll have to trigger a windows form in order to collect relevant input data from the user. then we’ll proceed to subscribing to two very useful Revit events (OnDocumentChanged event and Idling event). The first event raises every time the user modifies the document in any way. The second event raises whenever Revit is ready to receive API calls, hence the name Idling event. we’ll be using both of these events in conjunction to run our command every time the user performs any changes to the document, but still we want to make sure that the code runs only when Revit is “Ready” to to receive calls. In other words when Revit is in Idling mode. This allows for our code to run alongside the normal tasks of Revit without any conflicts.

First we’ll be using FilteredElementCollector to collect all the views that exist in the project. Then we’ll store their names in variables so that we can populate our UI later on. Finally, we’ll call our form to show up.

UIApplication UiApp = commandData.Application;
            Application App = UiApp.Application;
            UIDocument UiDoc = UiApp.ActiveUIDocument;
            Document doc = UiDoc.Document;

            //assigning to global variable for the document
            GlobalVariables6.m_document = doc;
            GlobalVariables6.m_path = string.Empty;

            //select view
            FilteredElementCollector collector = new FilteredElementCollector(doc);
            List<Element> views = collector.OfCategory(BuiltInCategory.OST_Views).WhereElementIsNotElementType().ToList();

            //string viewName = "Progress";

            List<string> viewnames = new List<string>();
            foreach (Element item in views)
            {
                viewnames.Add(item.Name);
            }
            GlobalVariables6.m_viewNames = viewnames;

            //show the modal form
            using (Form3 viewForm = new Form3(this))
            {
                viewForm.ShowDialog();
            }

            foreach (Element item in views)
            {
                if (item.Name == GlobalVariables6.m_viewName)
                {
                    view = item;
                }
            }

            //subscribe to event if the conditions are met
            if (GlobalVariables6.m_run == true)
            {
                App.DocumentChanged += OnDocumentChanged;
                UiApp.Idling += OnIdlling;
            }

            return Result.Succeeded;

Since the form is modal, the code will stop executing right after the form opens and awaits user input. The form looks something like this:

Designing the user interface of our addin

Right after the user inputs the required information and presses OK, we’ll subscribe to the above mentioned events. keep in mind that subscribing to events must be handled with care. You should always unsubscribe from that event once you’re done using it. this is the rest of the command that will get executed post user input which includes both of the event handlers:

 public static void OnIdlling(object sender, IdlingEventArgs e)
        {
            if (GlobalVariables6.m_docChanged == true)
            {
                string path = GlobalVariables6.m_path + "/frame-" + frameNumber.ToString() + ".jpg";
                var opt = new ImageExportOptions
                {
                    Zoom = GlobalVariables6.m_zoom,
                    ZoomType = ZoomFitType.Zoom,
                    FilePath = path,
                    FitDirection = FitDirectionType.Horizontal,
                    HLRandWFViewsFileType = ImageFileType.JPEGLossless,
                    ImageResolution = ImageResolution.DPI_600,
                    ExportRange = ExportRange.SetOfViews,
                };
                IList<ElementId> viewId = new List<ElementId>();
                viewId.Add(view.Id);
                opt.SetViewsAndSheets(viewId);
                GlobalVariables6.m_document.ExportImage(opt);            
                frameNumber++;
                GlobalVariables6.m_docChanged = false;
            }
        }

        static int frameNumber = 1;

        public static void OnDocumentChanged(object sender, DocumentChangedEventArgs e)
        {
            GlobalVariables6.m_docChanged = true;
        }

The heart of our code is the OnIdling event handler. This is where all of the magic happens. a set of rules have been describe in this method that are responsible for the image import process. the process looks something like this:

  1. OnDocumentChanged event gets triggered by the user, which will in return assign a true value to the m_docChanged variable (this is a public static variable within a class called GlobalVariables6).
  2. Once the user is done modifying the document, Revit raises the Idling event which signals to our program that it’s safe to run.
  3. Our program checks to see whether or not the user has indeed made any changes using a conditional statement that checks the value of the m_docChanged variable.
  4. If it’s true, the program collects user inputs and uses them accordingly (save directory, view name and zoom level)
  5. We use document.ExportImage to export the images based on the predefined rules and condition.
  6. finally we increment the frameNumber integer that is used to name the frames.

Turning this off is very simple. We simply unsubscribe from the event and we’re done:

class Command7 : IExternalCommand
    {
        public Result Execute(ExternalCommandData commandData, ref string message, ElementSet elements)
        {           
            commandData.Application.Application.DocumentChanged -= Command6.OnDocumentChanged;
            commandData.Application.Idling -= Command6.OnIdlling;
            GlobalVariables6.m_run = false;
            return Result.Succeeded;
        }
    }

The plugin is now complete!

Every time the user makes any changes to the model, this plugin exports a preview of the image to the designated directory.

Here is the result of our work:

Introduction

Hello and welcome to CodedBIM! my name is Nasim Naji, I’m an architect from Iraqi Kurdistan and I’ve been working in the field of computational design for a while now. I have created countless custom workflows and solutions to aid me in my day to day challenges as an architect throughout my college years and beyond. I’m also a self taught C# and python programmer who thinks programming can help absolutely anyone and everyone to be more productive. and in some cases, even help them achieve results which are otherwise extremely time consuming, risky or outright impossible. even though I’ve occasionally used the knowledge in some rather cute personal projects outside of my field and profession, but the fact of the matter is, I mainly use software development and computer programming to aid me in my architectural career.

I have always felt that, the main strength of BIM, and it’s biggest advantage over traditional CAD solutions lies in the way it stores, handles and manipulates information in a raw and realistic way, and with the high level of complexity that BIM solutions such as Revit offer, I naturally had a desire to be able to freely and efficiently manipulate the model in meaningful ways. I first realized this fact when I started using an outstanding Autodesk product called Dynamo.


Dynamo’s node based user friendly interface can be used by architects and everyone else with minimum coding background….

Dynamo is a visual programming and non-destructive computational geometry modeling software which is available both as a standalone and an addin for Autodesk Revit and some other Autodesk products. with the innovative and user friendly node-based user interface of dynamo, I was introduced to the amazing world of visual programming and I can daringly say that I could never go back to using Revit without Dynamo. it has helped me tremendously over the years, both in school activities that were meant to be mostly artistic, as well as strictly realistic and accurate real world projects…

As amazing as dynamo is, and as much as I think that it helps smoothen out the learning curve of programming for AEC and it makes it easier to get into topics such as computational design, generative design, parametric architecture etc… but there is only so much that a visual programming tool can do. after using Dynamo for a while it quickly became obvious to me that learning a traditional software programming language can be extremely useful. for those of you who don’t know, Dynamo allows the user to write python code within it, so that’s where I started. one of the greatest advantages of writing python within Dynamo was the fact that now I had access to the Revit API and I could talk to Revit directly (if you don’t know what that is, Revit API is a set of Revit tools and functionalities that the developers have exposed for us to program). after learning python which is a relatively simple, but an extremely powerful language nevertheless, I slowly worked my way up from there, and I started experimenting with another more strongly typed language called C#, which quickly became my favorite language for writing Revit plugins and custom Dynamo nodes.


Now that you, the dear reader, know a little more about me and my past experiences, this brings us to CodedBIM and it’s purpose. CodedBIM is my first blog ever, and the main motivational force behind it’s creation is to serve as a platform, on which I can share my experiences in this field with the world, and hopefully potentially help others along the way. I really hope that you’ll enjoy your stay on my weblog and that we’ll be able to bring about an amazing community of professionals who are willing to excel in their work through exploring different alternatives and interdisciplinary collaboration.

Thank you very much for your time…

Nasim Naji Salim