Create new tab/button and make it call an External Command...examples

Create new tab/button and make it call an External Command...examples

sobon.konrad
Advocate Advocate
4,631 Views
7 Replies
Message 1 of 8

Create new tab/button and make it call an External Command...examples

sobon.konrad
Advocate
Advocate

This is me getting my feet wet for the first time with this process so it would be awesome if somone could walk me through it. 

 

I created my first ExternalCommand that I can now call from AddIns tab > External Commands. Can someone provide me with a good example of taking that External command and making it into its own Tab>Button on the Revit Ribbon. I want to eventually build more commands and add to my own tab. 

Here's my Command: 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Autodesk.Revit.DB;
using Autodesk.Revit.DB.Architecture;
using Autodesk.Revit.UI;
using Autodesk.Revit.UI.Selection;
using Autodesk.Revit.ApplicationServices;
using Autodesk.Revit.Attributes;

public class DetailLineFilter : ISelectionFilter
{
    public bool AllowElement(Element e)
    {
        return (e.Category.Id.IntegerValue.Equals(
          (int)BuiltInCategory.OST_Lines));
    }
    public bool AllowReference(Reference r, XYZ p)
    {
        return false;
    }
}

[TransactionAttribute(TransactionMode.Manual)]
[RegenerationAttribute(RegenerationOption.Manual)]
public class CurveTotalLength : IExternalCommand
{
    public Result Execute(
      ExternalCommandData commandData,
      ref string message,
      ElementSet elements)
    {
        //Get application and document objects
        UIApplication uiApp = commandData.Application;
        Document doc = uiApp.ActiveUIDocument.Document;
        UIDocument uidoc = uiApp.ActiveUIDocument;

        try
        {
            IList<Element> pickedRef = null;
            Selection sel = uiApp.ActiveUIDocument.Selection;
            DetailLineFilter selFilter = new DetailLineFilter();
            pickedRef = sel.PickElementsByRectangle(selFilter, "Select lines");

            List<double> lengthList = new List<double>();
            foreach(Element e in pickedRef)
            {
                DetailLine line = e as DetailLine;
                if (line != null)
                {
                    lengthList.Add(line.GeometryCurve.Length);
                }
            }

            string lengthFeet = Math.Round(lengthList.Sum(), 2).ToString() + " ft";
            string lengthMeters = Math.Round(lengthList.Sum() * 0.3048, 2).ToString() + " m";
            string lengthMilimeters = Math.Round(lengthList.Sum() * 304.8, 2).ToString() + " mm";
            string lengthInch = Math.Round(lengthList.Sum() * 12, 2).ToString() + " inch";

            StringBuilder sb = new StringBuilder();
            sb.AppendLine("Total Length is:");
            sb.AppendLine(lengthFeet);
            sb.AppendLine(lengthInch);
            sb.AppendLine(lengthMeters);
            sb.AppendLine(lengthMilimeters);

            TaskDialog.Show("Line Length", sb.ToString());

            return Result.Succeeded;
        }
        catch (Autodesk.Revit.Exceptions.OperationCanceledException)
        {
            return Result.Cancelled;
        }
        catch (Exception ex)
        {
            message = ex.Message;
            return Result.Failed;
        }
    }
}

Here's my addin file:


<?xml version="1.0" encoding="utf-8"?>
<RevitAddIns>
  <AddIn Type="Command">    
    <Assembly>
      D:\Stuff\RevitVisualStudio\CurveTotalLength\CurveTotalLength\bin\Debug\CurveTotalLength.dll
    </Assembly>
    <ClientId>502fe383-2648-4e98-adf8-5e6047f9dc34</ClientId>
    <FullClassName>CurveTotalLength</FullClassName>
    <Text>MeasureLength</Text>
    <VendorId>Grimshaw</VendorId>
    <VisibilityMode>AlwaysVisible</VisibilityMode>
  </AddIn>
</RevitAddIns>

Also, any general C# coding good practice comments are welcome. Like I said, I am just getting my feet wet with this as I was previously mostly using Python (Dynamo or IronPythonShell). 

Thanks! 

0 Likes
Accepted solutions (1)
4,632 Views
7 Replies
Replies (7)
Message 2 of 8

Anonymous
Not applicable

i invite you to read this : http://help.autodesk.com/view/RVT/2014/FRA/?guid=GUID-1547E521-59BD-4819-A989-F5A238B9F2B3

 

Ribbon and tabs are manage with "public Result.OnStartup(UIControlledApplication application)" method

in fact, when you start Revit, the addin load ribbon and tabs.

this is an example :

public Result OnStartup(UIControlledApplication application){

//to create ribbon
string myRibbonName = "Ribbon Name";
application.CreateRibbonTab(myRibbonName);
string dll = @"C:\DATA\thisIsyourDLLassemblyFile.dll";
//to add panel
RibbonPanel MyPanel = application.CreateRibbonPanel(myRibbonName, "My Panel Name");

//to add Button with icon
Pushbutton myButton = (Pushbutton)MyPanel.AddItem(new PushButtonData("Button Name","Button Text", dll , "nameOfYourClassToexecute"));
//to add img to your button
myButton.LargeImage = new BitmapImage(new Uri("linkOfyourimage.png", UriKind.Absolute));

return Result.Succeeded;
}
0 Likes
Message 3 of 8

sobon.konrad
Advocate
Advocate

@Anonymous Thank you for replying. Yes, I have read this thread. I was able to use it previously to make ribbons/buttons for my RevitPythonShell version of this command. I am kind of asking for guidance in much more broader sense. I guess I am interested in seeing a sample Visual Studio project where if compiled it will create a simple RibbonTab/Button that calls another class (my TotalLength command). I just want to get an idea about how that is structured in VS so that I don't go ahead and create 15 DLLs for each command and then it becomes a management nightmare. 

Does anyone have an example like that? It could be the simplest of examples where command doest do anything more than ShowDialog box or something. This should give me an idea about how to structure it. I will take care of building out the Classes themselves and then adding buttons etc. 

Thanks! 

0 Likes
Message 4 of 8

rosalesduquej
Alumni
Alumni

Hi Konrad,

 

I think the Solution that Neoris is providing is valid. I'm posting here a sample on how the class that manages the ribbon could look. Your commands could be reference on each button you add to your new tab.

 

#region Namespaces

using Autodesk.Revit.UI;

using Autodesk.Revit.UI.Events;

using System;

using System.Collections.Generic;

using System.Linq;

using System.Reflection;

using System.Text;

using System.Threading.Tasks;

#endregion

 

namespace RibbonTab

{

    public class App : IExternalApplication

    {

        public Result OnStartup(UIControlledApplication a)

        {

            GenerateRibbonUI(a, this.GetType());

            return Result.Succeeded;

        }

 

        public Result OnShutdown(UIControlledApplication a)

        {

            return Result.Succeeded;

        }

 

        public void GenerateRibbonUI(UIControlledApplication uiApp, Type rootAssembly)

        {

            Type rootAssem = rootAssembly;

 

            Assembly assembly = Assembly.GetAssembly(rootAssem);

            string assemblyPath = assembly.Location;

 

            uiApp.CreateRibbonTab("TestApp");

 

            // Create Panel

            RibbonPanel panel = uiApp.CreateRibbonPanel("MyTab", Guid.NewGuid().ToString());

            panel.Title = "New Tab";

 

            // Button 1 of your command

            PushButtonData button1 = new PushButtonData("Name", "Text", assemblyPath, "classname String");

            panel.AddItem(button1);

 

            // Button 2 of your 2nd command

            PushButtonData button2 = new PushButtonData("Name2", "Text2", assemblyPath, "classname String");

            panel.AddItem(button2); 

           

        }

    }

}

 

Hope this gives you a better idea on how you could set up this. 

In case not I will invite you to check out a post by Jeremy Tammik, where he shares a Visual Studio solution to add a custom Tab to your ribbon. 

 

http://thebuildingcoder.typepad.com/blog/2009/12/custom-ribbon-tab.html

 

Hope this helps.

Cheers,



Jaime Rosales D.
Sr. Developer Consultant
Twitter | AEC ADN DevBlog
0 Likes
Message 5 of 8

sobon.konrad
Advocate
Advocate

I tried that file and here's what my error says: 

Capture.PNG

 

Here's my addin file:

<?xml version="1.0" encoding="utf-8" standalone="no"?>
<RevitAddIns>
<AddIn Type="Application">
                <Name>SampleApplication</Name>
                <Assembly>D:\Stuff\RevitVisualStudio\GrimshawRibbon\GrimshawRibbon\bin\Debug\GrimshawRibbon.dll</Assembly>
                <AddInId>604B1052-F742-4951-8576-C261D1993107</AddInId>
                <FullClassName>App</FullClassName>
                <VendorId>ADSK</VendorId>
                <VendorDescription>Autodesk, www.autodesk.com</VendorDescription>
</AddIn>
</RevitAddIns>

I referenced my CurveTotalLength dll into the example that you provided and set Copy Local to True so that it gets placed in the sample location on build. 

What am I missing? 

Thanks! 

0 Likes
Message 6 of 8

rosalesduquej
Alumni
Alumni

Dear Konrad,

 

At first glance, I think your <FullClassName> is not complete. 

 

<FullClassName>(your NameSpace).App</FullClassName>

Let me know if that works.

 

Cheers, 



Jaime Rosales D.
Sr. Developer Consultant
Twitter | AEC ADN DevBlog
0 Likes
Message 7 of 8

sobon.konrad
Advocate
Advocate
Accepted solution
using System;
using System.Collections.Generic;
using System.Reflection;
using Autodesk.Revit.DB;
using Autodesk.Revit.UI;
using System.Windows.Media.Imaging;
using System.IO;
using System.Linq;

namespace GrimshawRibbon
{
    public class CsAddPanel : IExternalApplication
    {
        public Result OnStartup(UIControlledApplication application)
        {

        // Create a custom ribbon tab
        String tabName = "Grimshaw";
        application.CreateRibbonTab(tabName);

        // Life Safety Tools
        // Add a new ribbon panel
        RibbonPanel ribbonPanel = application.CreateRibbonPanel(tabName, "Grimshaw Architects");

        // Create a push button to trigger a command add it to the ribbon panel.
        string thisAssemblyPath = Assembly.GetExecutingAssembly().Location;
        PushButtonData buttonData = new PushButtonData("cmdCurveTotalLength",
           "Total Length", thisAssemblyPath, "GrimshawRibbon.CurveTotalLength");

        PushButton pushButton = ribbonPanel.AddItem(buttonData) as PushButton;
        pushButton.ToolTip = "Select Multiple Lines to Obtain Total Length";

        // Add image icon to 
        Uri uriImage = new Uri(@"D:\Stuff\RevitVisualStudio\CurveTotalLength\CurveTotalLength\bin\Debug\CurveTotalLength.png");
        BitmapImage largeImage = new BitmapImage(uriImage);
        pushButton.LargeImage = largeImage;

        // Create two push buttons
        // Project Management Commands
        PushButtonData pushButton1 = new PushButtonData("cmdWorkset3dView", "Workset: 3D Views",
            thisAssemblyPath, "GrimshawRibbon.Workset3dView");
        pushButton1.Image = new BitmapImage(new Uri(@"D:\Stuff\RevitVisualStudio\Workset3dView\Workset3dView\bin\Debug\favicon.png"));
        pushButton1.ToolTip = "Create one 3D View per workset with all elemets on that workset isolated.";

        PushButtonData pushButton2 = new PushButtonData("cmdWorkset3dView1", "My Button #2",
                thisAssemblyPath, "GrimshawRibbon.Workset3dView");
        pushButton2.Image = new BitmapImage(new Uri(@"D:\Stuff\GitHub\rps-sample-scripts\StartupScripts\two_small.png"));

        // Add the buttons to the panel
        List<RibbonItem> projectButtons = new List<RibbonItem>();
        projectButtons.AddRange(ribbonPanel.AddStackedItems(pushButton1, pushButton2));

        return Result.Succeeded;
        }

        public Result OnShutdown(UIControlledApplication application)
        {
            return Result.Succeeded;
        }
    }
}

I ended up doing the above and it works with this addin file:

 

<?xml version="1.0" encoding="utf-8" standalone="no"?>
<RevitAddIns>
  <AddIn Type="Application">
    <Name>Grimshaw Tools</Name>
    <Assembly>D:\Stuff\RevitVisualStudio\GrimshawRibbon\GrimshawRibbon\bin\Debug\GrimshawRibbon.dll</Assembly>
    <AddInId>604b1052-f742-4951-8576-c261d1993108</AddInId>
    <FullClassName>GrimshawRibbon.CsAddPanel</FullClassName>
    <VendorId>Grimshaw Architects</VendorId>
    <VendorDescription>Konrad K Sobon @ Grimshaw Architects</VendorDescription>
  </AddIn>
</RevitAddIns>

So now, I can just call my external command classes without actually having to create seperate dlls. It's nice. 

One more thing that I am struggling with is creating a Uri image path for images that i added to my project as Embedded Resource. Should I post that as a seperate question or can we continue this conversation here? 

Thank you! 

Message 8 of 8

rosalesduquej
Alumni
Alumni

 Dear Konrad,

 

I'm happy to hear everything worked for you 🙂 What you could do is mark the answer as the solution and please start a new post, so Future developers when looking for something like that will find it easier and will not get confused with previous conversations. 

 

Cheers,



Jaime Rosales D.
Sr. Developer Consultant
Twitter | AEC ADN DevBlog
0 Likes