Free “View Parameter for Detail Items” app now available

A few months ago I posted the source code showing how a “View” field could be added to a Detail Item schedule, even though out-of-the-box Revit does not offer View as a schedule-able field. The compiled application can now be downloaded for free at the Autodesk App Store.

In-N-Out Parameters for Revit 2014 with enhanced unit support

Looking for a quick, easy, and inexpensive way to update family parameters?

I’ve created a 2014 install for In-N-Out Parameters. It is also enhanced so that it works with Length parameters of cm, mm, and other non-foot units. Get it at https://boostyourbim.wordpress.com/products/#InNOutParameters

Record view creation date in a shared parameter

Luke asked if it was possible to use the API to track the history of Revit view creation to a shared parameter. This allows a View List to be sorted by creation date.

Source code available at https://www.udemy.com/revitapi/?couponCode=%2425off-4158ef98

Live link between Parameters in Model & Detail Families

Here’s the implementation of a suggestion from @BigBadBIM at RTCNA:

“Map a parameter value of a model component to an detail component that live updates when model changes. Idea was to link detail components used for elec riser diagrams at old firm.”

The user is prompted to select two elements – a model component and a detail component. The id of the detail component is stored in a parameter in the model component. The value of the parameter (“Fred”, for testing) is copied from the model component to the detail component.

Then an updater is created so that any time this parameter is changed in the model component, the value is automatically copied into the detail component.

public void registerUpdaterForParamLink()
{
    UIDocument uidoc = this.ActiveUIDocument;
    Document doc = uidoc.Document;        

    Element parent = doc.GetElement(uidoc.Selection.PickObject(ObjectType.Element, "Select model component"));
    Element detail = doc.GetElement(uidoc.Selection.PickObject(ObjectType.Element, "Select detail component"));

    string paramName = "Fred";
    Parameter detailParam = detail.get_Parameter(paramName);
    Parameter parentParam = parent.get_Parameter(paramName);

    using (Transaction t = new Transaction(doc,"Bind Param"))
      {
        t.Start();
        parent.get_Parameter("Child ID").Set(detail.Id.IntegerValue);
        detailParam.Set(parentParam.AsString());
        t.Commit();
    }

    ParameterUpdater updater = new ParameterUpdater(this.Application.ActiveAddInId);
    UpdaterRegistry.RegisterUpdater(updater);
    UpdaterRegistry.AddTrigger(updater.GetUpdaterId(), new ElementClassFilter(typeof(FamilyInstance)), Element.GetChangeTypeParameter(parentParam.Id));
}

public class ParameterUpdater : IUpdater
{
    static AddInId m_appId;
    static UpdaterId m_updaterId;
    public ParameterUpdater(AddInId id)
    {
        m_appId = id;
        m_updaterId = new UpdaterId(m_appId, new Guid("F2FAF6B2-4C06-42d4-97C1-D1B4EB593EFF"));
    }
    public void Execute(UpdaterData data)
    {
        Document doc = data.GetDocument();
        Element parent = doc.GetElement(data.GetModifiedElementIds().First());
        if (parent.Category.Name == "Detail Items")
        	return;
        ElementId childID = new ElementId(parent.get_Parameter("Child ID").AsInteger());
        Element child = doc.GetElement(childID);
        string paramName = "Fred";
        child.get_Parameter(paramName).Set(parent.get_Parameter(paramName).AsString());
    }
    public string GetAdditionalInformation() { return "ParameterUpdater"; }
    public ChangePriority GetChangePriority() { return ChangePriority.FloorsRoofsStructuralWalls; }
    public UpdaterId GetUpdaterId() { return m_updaterId; }
    public string GetUpdaterName() { return "ParameterUpdater"; }
}

You can download an RVT to try this code at https://drive.google.com/file/d/0BwszsfY3OsZHZllJOEllWGl2aWs/edit?usp=sharing

“View” Field added to a Detail Item Schedule

Revit 2014 can schedule detail items, but some people still aren’t satisfied because out-of-the-box Revit doesn’t schedule the detail item’s view. Here’s an API solution for that.

// typically this would be done during OnStartup		
public void registerUpdater()
{
    MyUpdater updater = new MyUpdater(this.Application.ActiveAddInId);
    UpdaterRegistry.RegisterUpdater(updater);

    LogicalOrFilter orFilter =  new LogicalOrFilter
                               (new ElementClassFilter(typeof(View)),
                                 new ElementClassFilter(typeof(FamilyInstance)));

    UpdaterRegistry.AddTrigger(updater.GetUpdaterId(), orFilter, Element.GetChangeTypeAny());
    UpdaterRegistry.AddTrigger(updater.GetUpdaterId(), orFilter, Element.GetChangeTypeElementAddition());
}

public class MyUpdater : IUpdater
{
    static AddInId m_appId;
    static UpdaterId m_updaterId;
    public MyUpdater(AddInId id)
    {
        m_appId = id;
        m_updaterId = new UpdaterId(m_appId, new Guid("F2FBF6B2-4C06-42d4-97C1-D1B4EB593EFF"));
    }
    public void Execute(UpdaterData data)
    {
        Document doc = data.GetDocument();
        foreach (Element e in new FilteredElementCollector(doc).OfClass(typeof(FamilyInstance)).OfCategory(BuiltInCategory.OST_DetailComponents))
        {
            Element v = doc.GetElement(e.OwnerViewId);
            e.get_Parameter("View").Set(v.Name);                        
        }
    }
    public string GetAdditionalInformation() { return "Detail View Parameter"; }
    public ChangePriority GetChangePriority() { return ChangePriority.FloorsRoofsStructuralWalls; }
    public UpdaterId GetUpdaterId() { return m_updaterId; }
    public string GetUpdaterName() { return "Detail View Parameter"; }
}

Extending the Renumber command for Viewports & Detail Number

Here is a response to the comment from Jeff at https://boostyourbim.wordpress.com/2013/01/17/quick-way-to-renumber-doors-grids-and-levels/ on how to extend that tool to work with the Viewport > Detail Number parameter.

2012 2013
private Parameter getParameterForReference(Document doc, Reference r)
{
    Element e = doc.GetElement(r);
    Parameter p = null;
    if (e is Grid)
        p = e.get_Parameter("Name");
    else if (e is Autodesk.Revit.DB.Architecture.Room)
        p = e.get_Parameter("Number");
    else if (e is FamilyInstance)
        p = e.get_Parameter("Mark");
    else
    {
        p = e.get_Parameter("Detail Number");
        if (p == null)
        {
            TaskDialog.Show("Error", "Unsupported element");
            return null;
        }
    }
    return p;
}
private Parameter getParameterForReference(Document doc, Reference r)
{
    Element e = doc.GetElement(r);
    Parameter p = null;
    if (e is Grid)
        p = e.get_Parameter("Name");
    else if (e is Room)
        p = e.get_Parameter("Number");
    else if (e is FamilyInstance)
        p = e.get_Parameter("Mark");
    else if (e is Viewport) // Viewport class is new to Revit 2013 API
        p = e.get_Parameter("Detail Number");
    else
    {
        TaskDialog.Show("Error","Unsupported element");
        return null;
    }
    return p;
}    

The 2012 code can be used in 2013, but I like the 2013 version a bit better for being more explicit about which classes are being checked.

Finding Text Size as a Built-In Parameter

The TextNoteType class is where a property to find the size of a Text type would ideally be found. But it is not.

TextNoteType

However, all hope is not lost.

It is important to remember (and sometimes easy to forget), that there is also a huge set of parameters in the Revit API known as Built In Parameters. Many of these parameters work in the internal Revit code and not in the API, but many (such as TEXT_SIZE) are also perfectly usable in the API.

BuiltInParam

Usage:

TextNote textNote = doc.GetElement(uidoc.Selection.PickObject(ObjectType.Element).ElementId) as TextNote;
TextNoteType textNoteType = doc.GetElement(textNote.GetTypeId()) as TextNoteType;
Parameter textSizeParam = textNoteType.get_Parameter(BuiltInParameter.TEXT_SIZE);
TaskDialog.Show("Text Size", textSizeParam.AsDouble().ToString());

Set adaptive component parameters with placement point coordinates

A reader asked how to compute and schedule the placement point coordinates of adaptive component families. Here is a sample using the Parameter API and Dynamic Model Update to get and set those values.

private void Module_Startup(object sender, EventArgs e)
{
    AdaptivePointParamUpdater updater = new AdaptivePointParamUpdater(this.Application.ActiveAddInId);

    try
    {
    UpdaterRegistry.RegisterUpdater(updater);
    UpdaterRegistry.AddTrigger(updater.GetUpdaterId(), new ElementClassFilter(typeof(FamilyInstance)), Element.GetChangeTypeElementAddition());
    UpdaterRegistry.AddTrigger(updater.GetUpdaterId(), new ElementClassFilter(typeof(FamilyInstance)), Element.GetChangeTypeGeometry());
    }
    catch{}
}

public class AdaptivePointParamUpdater : IUpdater
{
    static AddInId m_appId;
    static UpdaterId m_updaterId;
    public AdaptivePointParamUpdater(AddInId id)
    {
        m_appId = id;
        m_updaterId = new UpdaterId(m_appId, new Guid("1BF1F6A2-4C06-42d4-97C1-D1B4EB593EFF"));
    }
    public void Execute(UpdaterData data)
    {
        Document doc = data.GetDocument();
        Autodesk.Revit.ApplicationServices.Application app = doc.Application;
        foreach (ElementId id in data.GetAddedElementIds())
        {
            adaptivePointParams(data.GetDocument(), id);
        }
        foreach (ElementId id in data.GetModifiedElementIds())
        {
            adaptivePointParams(data.GetDocument(), id);
        }
    }
    public string GetAdditionalInformation(){return "Data about adaptive parameters";}
    public ChangePriority GetChangePriority(){return ChangePriority.FloorsRoofsStructuralWalls;}
     public UpdaterId GetUpdaterId(){return m_updaterId;}
    public string GetUpdaterName(){return "AdaptivePoints";}

    private void adaptivePointParams(Document doc, ElementId id)
    {
        FamilyInstance fi = doc.GetElement(id) as FamilyInstance;
        if (fi != null && AdaptiveComponentInstanceUtils.IsAdaptiveComponentInstance(fi))
        {
            int ctr = 1;
            foreach (ElementId elementId in AdaptiveComponentInstanceUtils.GetInstancePlacementPointElementRefIds(fi))
            {
                ReferencePoint rp = doc.GetElement(elementId) as ReferencePoint;
                XYZ position = rp.Position;
                if (ctr == 1)
                {
                    fi.get_Parameter("point1x").Set(Math.Round(position.X,3));
                    fi.get_Parameter("point1y").Set(Math.Round(position.Y,3));
                    fi.get_Parameter("point1z").Set(Math.Round(position.Z,3));
                }
                else if (ctr == 2)
                {
                    fi.get_Parameter("point2x").Set(Math.Round(position.X,3));
                    fi.get_Parameter("point2y").Set(Math.Round(position.Y,3));
                    fi.get_Parameter("point2z").Set(Math.Round(position.Z,3));
                }                    
                ctr++;
            }
        }
    }        
}

Bulk Family Parameter Editing Improvements

Thanks to everyone for their enthusiasm and feedback on my recent posts about exporting family parameters to Excel and modifying the parameter data.

I’ve added the Shared Parameter GUIDs, the ability to modify parameters stored as text, integer, and double values, and UI for specifying folders and files. Are there other changes that would be valuable to make before I publish the tool?

Family Parameter Editing in Excel – Data Round-trip

Yesterday’s post was about getting Family Parameter data out of Revit and into Excel. Here you can see:

  1. Parameter data exported to Excel for every family in a specified directory
    For this example I hard-coded “C:\ProgramData\Autodesk\RAC 2013\Libraries\US Imperial\Columns” but it could be any directory you want
  2. Data modified in Excel for multiple families
  3. Data read back into the family files
  4. Modified families saved to disk
  5. Families manually opened to show the changes made with the API commands

Cool?

Bulk Family Parameter Editing in Excel

After the recent discussions about the OmniClass and UniFormat parameters, I’ve been thinking about more generic tools optimized for modifying multiple parameters for multiple families.

One option would be to build a big grid-like UI as part of a Revit API app where the parameters could be edited.

Easier would be to export everything to Excel, let you make changes there, and then modify the parameters by reading back in the Excel file. The first half of this process (Export to Excel) could look like this:

In its current state, this isn’t usable for parameters that store element IDs (like Material). But if you could read this data into Revit to set text and numeric parameter values, would it be helpful?

public void FamilyParametersToExcel()
{
    Autodesk.Revit.ApplicationServices.Application application = this.Application;
    string directory = @"C:\ProgramData\Autodesk\RAC 2013\Libraries\US Imperial\Columns";
    string s = "";

    // loop through all files in the directory
    foreach (string filename in Directory.GetFiles(directory))
    {
        s += "Family Name,Family Type,";
        Document doc = application.OpenDocumentFile(filename);

        if (!doc.IsFamilyDocument)
            return; // skip any files that are not families

        FamilyManager familyManager = doc.FamilyManager;

        // sort the family parameters alphabetically
        List<string> paramSorted = new List<string>();
        foreach (FamilyParameter fp in familyManager.Parameters)
        {
            paramSorted.Add(fp.Definition.Name);
        }
        paramSorted.Sort();

        // create a header row listing the parameter names for this family
        foreach (string paramName in paramSorted)
        {
            FamilyParameter familyParameter = familyManager.get_Parameter(paramName);
            s += familyParameter.Definition.Name + ",";
        }
        s += "\n";

        // create a row for each family type
        foreach (FamilyType familyType in familyManager.Types)
        {
            if (familyType.Name == " ") // skip the 'default' family type with no name
                continue;

            s += doc.PathName + "," + familyType.Name + ",";

            foreach (string paramName in paramSorted)
            {
                FamilyParameter familyParameter = familyManager.get_Parameter(paramName);
                s += ParameterValueForType(familyType, familyParameter) + ",";
            }
            s += "\n";
        }
        s += "\n";                
        doc.Close(false);
    }

    // write the CSV file to disk
    string outfile = directory.Replace(@"\","-").Replace("C:","") + "-Parameters.";
    outfile = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), outfile);
    using (StreamWriter streamWriter = new StreamWriter(outfile))
    {
        streamWriter.Write(s);
    }

    // open the CSV file
    Process.Start(outfile);
}

private string ParameterValueForType(FamilyType type, FamilyParameter p)
{
    // return a string with the value of the parameter for this type
    string paramValue = "";
    switch (p.StorageType)
    {
        case StorageType.Double:
            paramValue = type.AsDouble(p).ToString();
            break;
        case StorageType.ElementId:
            paramValue = type.AsElementId(p).IntegerValue.ToString();
            break;
        case StorageType.Integer:
            paramValue = type.AsInteger(p).ToString();
            break;
        case StorageType.String:
            paramValue = type.AsString(p);
            break;
    }
    if (paramValue == null)
        paramValue = "";

    return paramValue;
}
 

OmniClass API app to create & set shared type parameters

OmniTypeParamsTo follow up my Twitter conservation with Martijn and Aaron, here’s my take on what could be a useful app to create shared type parameters for OmniClass parameters

If you want to use the existing family-wide parameters “OmniClass Number” and “OmniClass Title” then select the “Built-In Family Parameters” radio button.

But if you want to use your own shared parameters so that each family type can have its own OmniClass values, then select the other radio button, select your shared parameter file, the family type, and the text parameters from that shared parameter file that should receive the OmniClass data.

When you push the OK button, the two shared parameters would be created as Type parameters in the family (if they don’t already exist). The OmniCode data selected in the grid control would be set for those 2 parameters.

Is this what would be useful?

Assembly Code & Description – By Type or Family?

I’m putting the finishing touches on the app I mentioned here for setting the UniFormat values for Assembly Code & Assembly Description. The Revit UI has  Assembly Code & Description as Type parameters, so each type of a family can have different values.

But does anyone ever give each Type of the same family a different Assembly Code? Or do you want them to act more like the OmniCode parameters, where every Type has the same Assembly Code?

API / Journal Hybrid to Set Revisions

As great as the Revit API is, there are still things that it doesn’t fully support, such as helping solve this problem:

When you select one sheet, you have the option to edit the “Revisions on Sheet” field.  When you select multiple sheets, that options goes away

Here is a hybrid approach that uses both the API and Revit journal files.

  • The video starts with an API command where the user specifies the set of sheets to modify (from saved View/Sheet Sets in the RVT file) and the revision number.
  • Then (starting at 0:25 in the video) it uses a journal file to make the revision changes with input from data stored by the API command.
  • After the file loads (at 1:05 in the video) there is approximately 1 second of rapid selection in the project browser and in the Properties dialog. This is the journal replay.
  • At the end of the video I move the cursor and select the 3 sheets to show that that the new revision has been set.

Search & Set App for OmniCode and UniFormat

I’ve put a searchable & sortable UI on the code I posted last week to improve the experience of setting these parameters. This probably isn’t something you are doing every day, but when you do I hope this will be a worthwhile improvement. It will be on the Autodesk App Exchange soon.

Quick way to renumber doors, grids, and family instances

A friend asked about a macro to renumber grids in the order they are picked. Here’s how:


public void renumberByPicks()
{
    UIDocument uidoc = this.ActiveUIDocument;
    Document doc = uidoc.Document;

    // list that will contain references to the selected elements
    IList<Reference> refList = new List<Reference>();
    try
    {
        // create a loop to repeatedly prompt the user to select an element
        while (true)
            refList.Add(uidoc.Selection.PickObject(ObjectType.Element, "Select elements in order to be renumbered. ESC when finished."));
    }
    // When the user hits ESC Revit will throw an OperationCanceledException which will get them out of the while loop
    catch
    {}

    using (Transaction t = new Transaction(doc,"Renumber"))
    {
        t.Start();
        // need to avoid encountering the error "The name entered is already in use. Enter a unique name."
        // for example, if there is already a grid 2 we can't renumber some other grid to 2
        // therefore, first renumber everny element to a temporary name then to the real one
        int ctr = 1;
        int startValue = 0;
        foreach (Reference r in refList)
        {
            Parameter p = getParameterForReference(doc, r);

            // get the value of the first element to use as the start value for the renumbering in the next loop
            if (ctr == 1)
                startValue = Convert.ToInt16(p.AsString()); 

            setParameterToValue(p, ctr + 12345); // hope this # is unused (could use Failure API to make this more robust
            ctr++;
        }

        ctr = startValue;
        foreach (Reference r in refList)
        {
            Parameter p = getParameterForReference(doc, r);
            setParameterToValue(p, ctr);
            ctr++;
        }
        t.Commit();
    }

}
private Parameter getParameterForReference(Document doc, Reference r)
{
    Element e = doc.GetElement(r);
    Parameter p = null;
    if (e is Grid)
        p = e.get_Parameter("Name");
    else if (e is Room)
        p = e.get_Parameter("Number");
    else if (e is FamilyInstance)
        p = e.get_Parameter("Mark");
    else
    {
        TaskDialog.Show("Error","Unsupported element");
        return null;
    }
    return p;
}        
private void setParameterToValue(Parameter p, int i)
{
    if (p.StorageType == StorageType.Integer)
        p.Set(i);
    else if (p.StorageType == StorageType.String)
        p.Set(i.ToString());    
}

Search the Uniformat file & Set the Parameter

Steve makes a good observation in this post:

It would be nice if we could search for keywords when attempting to assign the parameters for both Omniclass and Uniformat. Sometimes the best category to use in Revit isn’t necessarily the correct category in these classification systems. This means a fair bit of time wasted hunting for an appropriate value. I search the source files to find keywords first which isn’t the most straightforward.

Here’s a proof-of-concept showing how it could be done without too much heavy lifting. I haven’t coded a UI for this as I mention in the source code comments, but getting the search string from the text note is a fine simplification to show what to do with the search string.

public void uniformatSearch()
{
    UIDocument uidoc = this.ActiveUIDocument;
    Document doc = this.ActiveUIDocument.Document;

    // select the element whose Uniformat parameter will be set
    Element e = doc.GetElement(uidoc.Selection.PickObject(ObjectType.Element, "Select the element to set Assembly Code."));

    // select a text note whose text indicates the string to search for
    // in a real app there would be UI to enter this search string, but this works for a proof of concept macro
    TextNote text = doc.GetElement(uidoc.Selection.PickObject(ObjectType.Element, "Select the text note with the search term.")) as TextNote;

    // the location of the Uniformat file
    string appData = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
    string uniformatFile = Path.Combine(appData, @"Autodesk\revit\Autodesk Revit Architecture 2013", "UniformatClassifications.txt");

    IList<string> matches = new List<string>();
    // read the uniformat file one line at a time
    using (StreamReader read = new StreamReader(uniformatFile))
    {
        string line;
        while ((line = read.ReadLine()) != null)
       {
            // add the line to the list of matches if it contains the search string
            if (line.Contains(text.Text))
                matches.Add(line);
       }
    }
    // show all matches
    TaskDialog.Show("Matches for " + text.Text, listToString(matches));

    // get the type of the selected element and its uniformat parameter
    ElementType eType = doc.GetElement(e.GetTypeId()) as ElementType;
    Parameter p = eType.get_Parameter(BuiltInParameter.UNIFORMAT_CODE);

    // set the value of the parameter to the first entry in the match list - good enough for this proof-of-concept
    using (Transaction t = new Transaction(doc,"Set Uniformat"))
    {
        t.Start();
        // the uniformat file is a tab-delimited file
        // the string to use to set the parameter is the first entry [0] in the array created by splitting the complete string
        p.Set(matches.First().Split('\t')[0]);
        t.Commit();
    }
}
private string listToString(IList<string> stringList)
{
    string ret = "";
    foreach (string s in stringList)
    {
        ret += s.Replace("\t"," ") + "\n";
    }
    return ret;
}

Getting Parameter Data

For “info347074” who asked at AUGI about getting the values of shared parameters that are shown in labels on a sheet, here is a little macro showing how it can be done. Getting the parameter values from the sheets is no different than getting the values from any other Revit element.

CADFile Parameter


public void GetSheetParams()
{
    Document doc = this.ActiveUIDocument.Document;
    string sheetInfo = "";
    foreach (Element e in new FilteredElementCollector(doc).OfClass(typeof(ViewSheet)))
    {
        // get the parameter
        Parameter parameter = e.get_Parameter("CADFile");

        // get the string value of the parameter
        string cadFileData = parameter.AsString();

        sheetInfo += cadFileData + "\n";
    }
    TaskDialog.Show("CADFile", sheetInfo);
}