Random Material generator

The post “Randomize Color and Material in Revit” seemed like a good reason for me to write a post about randomly setting materials.

First, some random number humor:

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

    // get all glass materials in document
    IEnumerable<Element> materials = new FilteredElementCollector(doc)
        .OfClass(typeof(Material))
        .Cast<Material>()
        .Where(q => q.MaterialClass == "Glass");

    using (Transaction t = new Transaction(doc,"Random Materials"))
    {
        t.Start();
        foreach (Element e in new FilteredElementCollector(doc).OfClass(typeof(FamilyInstance)).OfCategory(BuiltInCategory.OST_CurtainWallPanels))
        {
            Parameter materialParam = e.get_Parameter("Material");
            if (materialParam == null)
                continue;

            int randomNumber = new Random(Guid.NewGuid().GetHashCode()).Next(0,materials.Count());

            materialParam.Set(materials.ElementAt(randomNumber).Id);
        }
        t.Commit();
    }
}

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

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();
       }
}

Getting Materials While In a Family

The previous post found the materials in the door while in the RVT project environment. Using a different approach we can get the same data while in the RFA family environment.

// Find all the GenericForm elements in the document
// GenericForm is the base class for Blend, Extrusion, Sweep, etc.
foreach (Element element in new FilteredElementCollector(doc).OfClass(typeof(GenericForm)))
{
    // Get the data for the Material parameter of this GenericForm
    Parameter parameter = element.get_Parameter("Material");

    // The material parameter stores an ElementId
    ElementId id = parameter.AsElementId();

    // Use this ElementId to get the Material
    Material material = doc.GetElement(id) as Material;

    // An Options object will be needed to get the geometry of the element
    // All default values are acceptable for this usage
    Options geometryOptions = new Options();

    // The GenericForm contains only one GeometryObject which is a Solid
    Solid solid = element.get_Geometry(geometryOptions).First() as Solid;

    // Collect some info about the GenericForm, Material, and Solid
    info += element.Name + ", " + material.Name + ", " + material.MaterialClass + ", " + solid.SurfaceArea + "\n";
}
TaskDialog.Show("Material info",info);

materialsInFamily

Here we can get a value for each extrusion used to create the family. It is nice to see that they add up to the same values as in the previous post!

Calculating the glass in a door

A reader asks:

>> I’d be curious to know if it’s possible to pull areas from an object like a door. For example, if I selected the door, can I calculate the area of (object style) glass in the family?

Yes, and here is how to do it with the wonderful GetMaterialArea method:

Element element = doc.GetElement(uidoc.Selection.PickObject(ObjectType.Element));
string areaData = "";
foreach (Material material in element.Materials)
{
    areaData += material.Name + ", " + element.GetMaterialArea(material) + "\n";
}    
TaskDialog.Show("Materials & Area", areaData);

materialarea