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

Extract Subcategory and Material Names from Multiple Families

From a discussion on Twitter last night, I put together an API solution to export the name of every subcategory and material in a group of RFAs.

In this sample it is hard-coded to check every RFA in the C:\ProgramData\Autodesk\RAC 2013\Libraries\US Imperial\Doors folder. The output is written to individual files in the TEMP folder (one file for material info & one file for category info).

The source code is available in my Udemy course on the Revit API

Using the API to resolve “identical instances in same place” warnings

The API can be used to parse a warnings HTML file exported from Revit and then delete the “less desirable” instance in each pair of instances in the same place.

The instance that gets deleted is the one

  • without a tag (if one instance has a tag)
  • with less instance parameter data

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 Security Guard – AlwaysRequirePassword & AccessRestricted

Thanks to feedback from reader Arno, I’ve added two new features to this app. I hope the provides the flexibility and control that everyone – or almost everyone 🙂 – would like. If not, please let me know.

  1. The password will be required to edit and open all family files if there is “AlwaysRequirePassword.txt” file in the BoostYourBIM-FamilySecurity.bundle\Contents folder.
  2. If this file does not exist, the password will only be required for families that contain a parameter named “AccessRestricted”. The value of this parameter does not matter, only whether or not it exists.

FREE “Family Security Guard” submitted to Autodesk App Store

Thanks for all the great feedback I got from the recent posts about RFA security. I’ve submitted “Family Security Guard” to Autodesk to have it published (for FREE!). I will let you know when it is ready for download here.

The user will be prompted for a password in two situations:

  1. In the project environment when they attempt to open the Family Types dialog
  2. When they attempt to open a family file

After the password is successfully entered, the user will not be prompted again for the password during this Revit session. The password can be set in the pwd.txt file in the BoostYourBIM-FamilySecurity.bundle\Contents folder.

NOTE: This app only protects families when this app is installed. There is no protection when someone without this app attempts to modify families. (I know many people would like to protect families all the time, but unfortunately Revit does not support this)

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?

More on Family Security – require password to open family files

Yesterday’s post was about putting password protection on the Edit Type button in the Properties dialog. It was great to see so many comments and page views from people interested in this.

Here is another part of limiting access to modifying families – preventing unauthorized users from opening family files in the Family Editor.

This could be combined with the Edit Type password requirement shown yesterday but here are a few issues that would need to be resolved. It would be great to hear your ideas on these.

  1. Should these restrictions be applied to all families? If not, how should specific families be marked? Would a Yes/No type parameter in each family make sense? (It would be nice to able to add a family parameter instead of a type parameter, but the Revit does not support that.) Another option would be to mark families by storing some extensible storage data, but that introduces its own complexities.

  2. Should there be password restrictions on both opening existing families and creating new families?
  3. If I made this available as a single compiled application, everyone who downloads it would have the same application with the same password. Is this a deal-breaker?
    Ideally every company would be able to set their own password, but having me generate a unique DLL for each firm seems a bit cumbersome for everyone.

Measuring Glass Area In a Door Above 2′

Following up on the previous post, here is how to put all the pieces together to find a door’s glass area above 2′

  1. Find the glass area of the door as-is
  2. Edit the door family
  3. Create a void to eliminate the bottom 2′ of glass
  4. Reload the door into the project
  5. Find the glass area of the modified door
  6. Rollback the changes to the door and the reloading of the door into the RVT

glassarea

public void MesaureGlassArea()
{
    Document doc = this.ActiveUIDocument.Document;
    UIDocument uidoc = new UIDocument(doc);
    FamilyInstance instance = doc.GetElement(uidoc.Selection.PickObject(ObjectType.Element)) as FamilyInstance;

    // Get the surface area of glass in the family
    string info = "Total glass area in door = " + getGlassAreaInFamily(instance) + "\n";

    // Open and edit the family document
    Document familyDoc = doc.EditFamily(instance.Symbol.Family);

    // The family geometry is going to be edited (to create the void to cut off the bottom 2' of glass) and then reloaded into the RVT.
    // After this is done, call getGlassAreaInFamily to get the modified glass area.
    // But in the end we don't want to keep all the transactions created by CreateExtrusionAndCutGlass.
    // A transaction group can be used to rollback all the transactions that were committed inside the transaction group.
    using (TransactionGroup transactionGroup = new TransactionGroup(doc,"Glass measurement"))
    {
        transactionGroup.Start();

        // Cut off the bottom 2 feet of glass
        CreateExtrusionAndCutGlass(familyDoc);

        // Load the modified family back into the RVT
        // FamilyLoadOptions tells Revit to overwrite the existing family instances in the RVT
        FamilyLoadOptions loadOptions = new FamilyLoadOptions();
        familyDoc.LoadFamily(doc,loadOptions);

        // Get the surface area of glass in the modified family
        info += "Glass in door above 2 feet = " + getGlassAreaInFamily(instance);    

        // Rollback the transaction group so that the reloading of the family and modifications to the family are discarded.
        transactionGroup.RollBack();
    }

    TaskDialog.Show("Material info", info);

    // Close the family document. False means do not save the file
    familyDoc.Close(false);
}

private double getGlassAreaInFamily(Element element)
{
    foreach (Material material in element.Materials)
    {
        if (material.Name == "Glass")
            return element.GetMaterialArea(material);
    }    
    return 0;
}

// FamilyLoadOptions tells Revit what to do when loading the family into a document that already contains the family
public class FamilyLoadOptions : IFamilyLoadOptions
{
    // Always return true so that all existing families and their parameters are overwritten with the new family
    public bool OnFamilyFound(bool familyInUse, out bool overwriteParameterValues)
    {
        overwriteParameterValues = true;
        return true;
    }
    public bool OnSharedFamilyFound(Family sharedFamily, bool familyInUse, out FamilySource source, out bool overwriteParameterValues)
    {
        overwriteParameterValues = true;
        source = FamilySource.Family;
        return true;
    }
}

private void CreateExtrusionAndCutGlass(Document doc)
{
    Autodesk.Revit.ApplicationServices.Application app = doc.Application;

    // Height of the void extrusion
    double height = 2;

    // Four points to define corners of the rectangle to extrude
    XYZ pnt1 = new XYZ(-10, -10, 0);
    XYZ pnt2 = new XYZ(10, -10, 0);
    XYZ pnt3 = new XYZ(10, 10, 0);
    XYZ pnt4 = new XYZ(-10, 10, 0);

    // Create the four lines of the rectangle
    // These are internal "Line" elements, not model lines or detail lines
    Line line1 = app.Create.NewLine(pnt1, pnt2, true);
    Line line2 = app.Create.NewLine(pnt2, pnt3, true);
    Line line3 = app.Create.NewLine(pnt3, pnt4, true);
    Line line4 = app.Create.NewLine(pnt4, pnt1, true);

    // Put these lines into a CurveArray
    CurveArray curveArray = new CurveArray();
    curveArray.Append(line1);
    curveArray.Append(line2);
    curveArray.Append(line3);
    curveArray.Append(line4);

    // Put this array into a CureArrArray (an array of CurveArrays)
    // Extrusion creation uses a CureArrArray so you can extrusion multiple loops 
    CurveArrArray curveArrayArray = new CurveArrArray();
    curveArrayArray.Append(curveArray);

    // Create a plane at the origin with a normal in the up direction
    XYZ planeNormal = XYZ.BasisZ;
    XYZ planeOrigin = XYZ.Zero;
    Plane plane = app.Create.NewPlane(planeNormal, planeOrigin);

    Extrusion extrusion = null;

    using (Transaction t = new Transaction(doc, "Create Extrusion"))
       {
            t.Start();
            // Create a sketch plane to be used for the extrusion
            SketchPlane sketchPlane = doc.FamilyCreate.NewSketchPlane(plane);
            // Create the extrusion. The "false" specifies that it will be a void
            extrusion = doc.FamilyCreate.NewExtrusion(false, curveArrayArray, sketchPlane, height);
            t.Commit();
       }

    // Cut the other 3D elements with the new void using CombineElements and a CombinableElementArray
    CombinableElementArray ceArray = new CombinableElementArray();

    // Add the new void extrusion to the CombinableElementArray
    ceArray.Append(extrusion);

    // Add all GenericForm elements with the Glass subcategory to a CombinableElementArray
    foreach (GenericForm genericForm in new FilteredElementCollector(doc).OfClass(typeof(GenericForm)).Cast<GenericForm>())
    {
        Category category = genericForm.Subcategory;
        if (category != null && category.Name == "Glass")
        {
            ceArray.Append(genericForm);
        }
    }

    using (Transaction t = new Transaction(doc, "Combine Elements"))
       {
            t.Start();
            // Combine the elements so that the void will cut the solids
            GeomCombination geomCombination = doc.CombineElements(ceArray);
            t.Commit();
       }
}

Creating a Void to Cut Family Geometry

In a comment to “Calculating the glass in a door“, the question was posed:

“Let’s say I have a 7′ tall, full glass door. How complicated would it be to add constraints, or limits, to the area in which you want to calculate? Say I want to calculate the total are of glass between 2′ and 6′ (assuming the total glass area was greater than that).”

The place to start answering this question is with API code to create a void that eliminates the glass in the door below 2′. The image below was created with “Double-Glass 1.rfa”.

cutGlass

public void CreateExtrusionAndCutGlass()
{
    Document doc = this.ActiveUIDocument.Document;
    Autodesk.Revit.ApplicationServices.Application app = doc.Application;

    // Height of the void extrusion
    double height = 2;

    // Four points to define corners of the rectangle to extrude
    XYZ pnt1 = new XYZ(-10, -10, 0);
    XYZ pnt2 = new XYZ(10, -10, 0);
    XYZ pnt3 = new XYZ(10, 10, 0);
    XYZ pnt4 = new XYZ(-10, 10, 0);

    // Create the four lines of the rectangle
    // These are internal "Line" elements, not model lines or detail lines
    Line line1 = app.Create.NewLine(pnt1, pnt2, true);
    Line line2 = app.Create.NewLine(pnt2, pnt3, true);
    Line line3 = app.Create.NewLine(pnt3, pnt4, true);
    Line line4 = app.Create.NewLine(pnt4, pnt1, true);

    // Put these lines into a CurveArray
    CurveArray curveArray = new CurveArray();
    curveArray.Append(line1);
    curveArray.Append(line2);
    curveArray.Append(line3);
    curveArray.Append(line4);

    // Put this array into a CureArrArray (an array of CurveArrays)
    // Extrusion creation uses a CureArrArray so you can extrusion multiple loops 
    CurveArrArray curveArrayArray = new CurveArrArray();
    curveArrayArray.Append(curveArray);

    // Create a plane at the origin with a normal in the up direction
    XYZ planeNormal = XYZ.BasisZ;
    XYZ planeOrigin = XYZ.Zero;
    Plane plane = app.Create.NewPlane(planeNormal, planeOrigin);

    Extrusion extrusion = null;

    using (Transaction t = new Transaction(doc, "Create Extrusion"))
       {
            t.Start();
            // Create a sketch plane to be used for the extrusion
            SketchPlane sketchPlane = doc.FamilyCreate.NewSketchPlane(plane);
            // Create the extrusion. The "false" specifies that it will be a void
            extrusion = doc.FamilyCreate.NewExtrusion(false, curveArrayArray, sketchPlane, height);
            t.Commit();
       }

    // Cut the other 3D elements with the new void using CombineElements and a CombinableElementArray
    CombinableElementArray ceArray = new CombinableElementArray();

    // Add the new void extrusion to the CombinableElementArray
    ceArray.Append(extrusion);

    // Add all GenericForm elements with the Glass subcategory to a CombinableElementArray
    foreach (GenericForm genericForm in new FilteredElementCollector(doc).OfClass(typeof(GenericForm)).Cast<GenericForm>())
    {
        Category category = genericForm.Subcategory;
        if (category != null && category.Name == "Glass")
        {
            ceArray.Append(genericForm);
        }
    }

    using (Transaction t = new Transaction(doc, "Combine Elements"))
       {
            t.Start();
            // Combine the elements so that the void will cut the solids
            GeomCombination geomCombination = doc.CombineElements(ceArray);
            t.Commit();
       }
}

Macro to determine if a Family is In-Place (with output to Text and Excel Files)

A recent post at whatrevitwants mentions some tools that can be used to find in-place families. Here is a tiny macro to do the same

public void findInPlaceFamilies()
{
    string info = "";
    Document doc = this.ActiveUIDocument.Document;
    foreach (Family family in new FilteredElementCollector(doc).OfClass(typeof(Family)).Cast<Family>())
    {
        if (family.IsInPlace)
            info += family.Name + "\n";
    }
    TaskDialog.Show("In Place Families", info);
}

This is also a good excuse to show how to do something other than show the string inside Revit in a TaskDialog. For example, we might want to write the data to a text file or to an Excel file.

Text File:

public void findInPlaceFamilies()
{
    Document doc = this.ActiveUIDocument.Document;

    // Use the @ before the filename to avoid errors related to the \ character
    // Alternative is to use \\ instead of \
    string filename = @"C:\Users\HP002\Documents\In Place Families.txt";

    // Create a StreamWriter object to write to a file
    using (System.IO.StreamWriter writer = new System.IO.StreamWriter(filename))
    {
        foreach (Family family in new FilteredElementCollector(doc).OfClass(typeof(Family)).Cast<Family>())
        {
            if (family.IsInPlace)
                writer.WriteLine(family.Name);
        }
    }    
    // open the text file
    System.Diagnostics.Process.Start(filename);
}

Excel File:

  1. Select “Project – Add Reference” from the SharpDevelop menu bar and add a reference to Microsoft.Office.Interop.Excel
    AddExcelReference
  2. Add these lines to the other “using” statements at the top of your file:
    using Microsoft.Office.Interop.Excel;
    using System.Reflection;
  3. Adding “Microsoft.Office.Interop.Excel” may result in errors like this if elsewhere in your macros you are using the Revit Parameter class:
    Ambiguous
    This happens because now you can no longer define variables as “Parameter” because both Autodesk.Revit.DB and Microsoft.Office.Interop.Excel contain definitions for “Parameter”. The compiler can’t guess which one you want to use.
    Go through these errors and replace “Parameter” with  “Autodesk.Revit.DB.Parameter” like this:

    Autodesk.Revit.DB.Parameter parameter = element.get_Parameter("Material");
    
public void findInPlaceFamilies()
{
    Document doc = this.ActiveUIDocument.Document;

    // set up Excel variables
    Application excelApp = new Application();
    Workbook workbook = excelApp.Workbooks.Add(Missing.Value);
    Worksheet worksheet = (Worksheet)workbook.ActiveSheet;

    string filename = @"C:\Users\HP002\Documents\In Place Families.xls";
    int rowCtr = 1;
    foreach (Family family in new FilteredElementCollector(doc).OfClass(typeof(Family)).Cast<Family>())
    {
        if (family.IsInPlace)
        {
            // write data to the next row in the spreadsheet
            worksheet.Cells[rowCtr,1] = family.Name;
            rowCtr++; // increment counter by one
        }
    }

    // Save the file
    workbook.SaveAs(filename,XlFileFormat.xlWorkbookNormal, 
        Missing.Value, Missing.Value, Missing.Value, Missing.Value, 
        XlSaveAsAccessMode.xlExclusive, Missing.Value, Missing.Value, Missing.Value, 
           Missing.Value, Missing.Value); 
    // Clean up the excel resources that were created
    workbook.Close();
    excelApp.Quit();

    System.Diagnostics.Process.Start(filename);
}

Don’t mind all that crazy “Missing.Value” stuff. The Excel API is strange but it works.