#RTCEUR Wish 5 – Who Created Those Warnings?

To help BIM Managers train or punish their Revit users, I was asked at RTC in Delft if we can use the API to figure out which users are responsible for each warning in your Revit file.

Here’s how that can be done by writing all warnings to a log file and then comparing warnings in the current file with those in the log.

public void RegisterFailureReporter()
{
    Application app = this.Application;
    Document doc = this.ActiveUIDocument.Document;
    if (doc.PathName == "")
    {
        TaskDialog.Show("Error","Please save the file and then repeat this command.");
        return;
    }
    app.FailuresProcessing += FailureReporter;
}

private void FailureReporter(object sender, Autodesk.Revit.DB.Events.FailuresProcessingEventArgs args)
{
    FailuresAccessor fa = args.GetFailuresAccessor();
    Document doc = fa.GetDocument();

    using (StreamWriter sw = new StreamWriter(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), Path.GetFileName(doc.PathName) + " Warning Creation Log.csv"), true))
    {
        foreach (FailureMessageAccessor fma in fa.GetFailureMessages(FailureSeverity.Warning))
        {
            sw.Write(DateTime.Now + "," + fa.GetDocument().Application.Username + ",");
            sw.Write(fma.GetDescriptionText().Replace(Environment.NewLine,"") + "," );
            foreach (ElementId id in fma.GetFailingElementIds())
            {
                // use UniqueId instead of ElementId because the UniqueId is stable across Save To Central while the ElementId property may change.  
                sw.Write(doc.GetElement(id).UniqueId + ",");
            }
            sw.Write(Environment.NewLine);
        }
    }
}

public void CheckCurrentWarningsInLog()
{
    Document doc = this.ActiveUIDocument.Document;
    if (doc.PathName == "")
    {
        TaskDialog.Show("Error","Please save the file and then repeat this command.");
        return;
    }    
    using (StreamReader warningsHTMLReader = new StreamReader(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), Path.GetFileNameWithoutExtension(doc.PathName) + "_Error Report.html")))
    {
        string line = "";
        while ((line = warningsHTMLReader.ReadLine()) != null)
        {
            if (!line.Contains(": id "))
                continue;

            string[] row = System.Text.RegularExpressions.Regex.Split(line, "id ");

            string id1 = row[1].Split(' ')[0];
            string uniqueId1 = doc.GetElement(new ElementId(int.Parse(id1))).UniqueId;
            string uniqueId2 = null;
            if (row.Count() == 3) // there are two element ids in this row
            {
                string id2 = row[2].Split(' ')[0];
                uniqueId2 = doc.GetElement(new ElementId(int.Parse(id2))).UniqueId;
            }

            using (StreamReader warningLogReader = new StreamReader(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), Path.GetFileName(doc.PathName) + " Warning Creation Log.csv")))
            {
                string lineLog = "";
                while ((lineLog = warningLogReader.ReadLine()) != null)
                {
                    if (lineLog.Contains(uniqueId1) || (uniqueId2 != null && lineLog.Contains(uniqueId1) && lineLog.Contains(uniqueId2)))
                    {
                        using (StreamWriter sw = new StreamWriter(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), Path.GetFileName(doc.PathName) + " Current Warnings With User Info.csv"), true))
                        {
                                   sw.WriteLine(lineLog);
                        }
                        break;
                    }
                }
            }
        }
    }
}

#RTCEUR Wish 4 – Find Native and RVT Link Penetrating Elements

This sample shows how the BoundingBoxIntersectsFilter can be used to find elements that penetrate ceilings in the model.

The host file contains 2 ceilings, 2 columns, and a RVT link that contains 3 Generic Model elements. There are also 3 shaft openings in the ceiling so that there is no interference between the ceilings and the elements going through them.

This API code analyzes the bounding box of each ceiling and finds all elements in all files (including the current RVT and links) whose bounding box intersects the ceiling’s bounding box. The results are shown in a dialog and written to a parameter of the ceiling category.

public void FindPenetrations()
{
    Document hostDoc = this.ActiveUIDocument.Document;
    string data = "";

    LogicalOrFilter columnGenericModelfilter = new LogicalOrFilter(new ElementCategoryFilter(BuiltInCategory.OST_Columns), new ElementCategoryFilter(BuiltInCategory.OST_GenericModel));

    using (Transaction t = new Transaction(hostDoc, "Find Penetrations"))
    {
        t.Start();

        foreach (Ceiling ceiling in new FilteredElementCollector(hostDoc).OfClass(typeof(Ceiling)))
        {
            data += "--- Ceiling - " + ceiling.Name + " (id = " + ceiling.Id + ") ---\n";

            // define an Outline based on the Bounding Box of this ceiling
            Outline outline = new Outline(ceiling.get_BoundingBox(null).Min, ceiling.get_BoundingBox(null).Max);

            string paramData = "";

            // search for penetrations in all open documents which will include RVT Links and native geoemtry in this RVT
            foreach (Document doc in this.Application.Documents)
            {
                // find elements that pass the Column/Generic Model filter and whose Bounding Box intersects the outline of this Ceiling
                foreach (Element e in new FilteredElementCollector(doc).WherePasses(columnGenericModelfilter).WherePasses(new BoundingBoxIntersectsFilter(outline)))
                 {
                    data += e.Category.Name + " - " + e.Name + " - " + Path.GetFileName(e.Document.PathName) + " - " + e.Id.IntegerValue + "\n";
                    paramData += e.Name + " - " + Path.GetFileName(e.Document.PathName) + " - " + e.Id.IntegerValue + "\n";
                 }
            }

            ceiling.get_Parameter("Penetrating Elements").Set(paramData);
            data += "\n";
        }
        t.Commit();    
    }
    TaskDialog.Show("Intersections",data);
}

#RTCEUR Wish 3 – Copy View Types & Templates btw Documents

Q: Would be possible to create a tool that would “Transfer View Types” from one project to another?

Background: View Types have a couple of key properties, an assigned View Template, and if new view instances created from the Type should be dependent on the View Template. But the Revit UI does not allow view types to be transferred between projects.

A: Good news! With the API it is possible to determine the View Types defined in a project, read their associated parameters, create new View Types in a second Project, and set these parameters to match the values in the source project.

public void CopyViewTypes()
{
    Application app = this.Application;

    // source document is the active document
    Document sourceDoc = this.ActiveUIDocument.Document;

    // target document is an open file named "target.rvt"
    Document targetDoc = app.Documents.Cast<Document>().Where(q => Path.GetFileName(q.PathName) == "target.rvt").FirstOrDefault();

    // element ids for all instances of the ViewFamilyType class
    ICollection<ElementId> viewFamilyTypeIds = new FilteredElementCollector(sourceDoc).OfClass(typeof(ViewFamilyType)).ToElementIds();

    // element ids of the view templates used in View Types
    ICollection<ElementId> templateIds = new List<ElementId>();

    // list of pairs of strings - first item in each pair will be the name of the View Type - second item will be the name of the View Template used by that View Type 
    IList<Tuple<string, string>>  ssList = new List<Tuple<string, string>>();

    foreach (ViewFamilyType vft in new FilteredElementCollector(sourceDoc).OfClass(typeof(ViewFamilyType)))
    {
        // element id of the view template used in this View Type
        ElementId defaultTemplateId = vft.get_Parameter(BuiltInParameter.DEFAULT_VIEW_TEMPLATE).AsElementId();

        // go to the next View Type if there is no View Template assigned to this view type
        if (defaultTemplateId == ElementId.InvalidElementId)
            continue;

        templateIds.Add(defaultTemplateId);

        ssList.Add(new Tuple<string, string>(vft.Name, sourceDoc.GetElement(defaultTemplateId).Name));
    }

    CopyPasteOptions options = new CopyPasteOptions();

    using (Transaction t = new Transaction(targetDoc, "Copy View Types"))
    {
        t.Start();
        // copy the view templates
        ElementTransformUtils.CopyElements(sourceDoc, templateIds, targetDoc, Transform.Identity, options);
        // copy the view types
        ElementTransformUtils.CopyElements(sourceDoc, viewFamilyTypeIds, targetDoc, Transform.Identity, options);

        foreach (Tuple<string, string> tup in ssList)
        {
            Element vft = new FilteredElementCollector(targetDoc).OfClass(typeof(ViewFamilyType)).Where(q => q.Name == tup.Item1).FirstOrDefault();

            View template = new FilteredElementCollector(targetDoc).OfClass(typeof(View)).Cast<View>().Where(q => q.IsTemplate && q.Name == tup.Item2).FirstOrDefault();

            // assign the view template to the view type
            vft.get_Parameter(BuiltInParameter.DEFAULT_VIEW_TEMPLATE).Set(template.Id);
        }

        t.Commit();
    }
}

#RTCEUR Wish 2 – Highlight and Isolate Warning Elements

@marcellosgamb suggested an API that runs thru every error in a model turns element w/error red then as u fix each error turn normal

There is no API access to the Revit “Review Warnings” data, so this implementation requires the user to export the warning data. The program then imports this HTML, parses it to find the element IDs, and then isolates and colors the elements in two 3D views.

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

    string filename = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "warnings.html");

    IList<ElementId> ids = new List<ElementId>();

    using (StreamReader sr = new StreamReader(filename))
    {
        string line = "";
        while ((line = sr.ReadLine()) != null)
        {
            string[] row = System.Text.RegularExpressions.Regex.Split(line,"id ");
            if (row.Length != 3)
            {
                continue;
            }
            string id1 = row[1].Split(' ')[0];
            string id2 = row[2].Split(' ')[0];

            ids.Add(new ElementId(int.Parse(id1)));
            ids.Add(new ElementId(int.Parse(id2)));
        }
    }    

    View isolateView = new FilteredElementCollector(doc).OfClass(typeof(View)).Cast<View>().Where(q => q.Name == "IsolateWarningElements").FirstOrDefault();
    View overrideView = new FilteredElementCollector(doc).OfClass(typeof(View)).Cast<View>().Where(q => q.Name == "OverrideWarningElements").FirstOrDefault();

    OverrideGraphicSettings ogs = new OverrideGraphicSettings();
    Color red = new Color(255, 0, 0);
    ogs.SetProjectionLineColor(red);
    ogs.SetProjectionLineWeight(8);

    Element solidFill = new FilteredElementCollector(doc).OfClass(typeof(FillPatternElement)).Where(q => q.Name.Contains("Solid")).First();

    ogs.SetProjectionFillPatternId(solidFill.Id);
    ogs.SetProjectionFillColor(new Color(0, 255,0));

    using (Transaction t = new Transaction(doc, "Isolate/Override Warnings"))
    {
        t.Start();
        isolateView.IsolateElementsTemporary(ids);

        foreach (ElementId id in ids)
        {
            overrideView.SetElementOverrides(id, ogs);
        }
        t.Commit();
    }
}

#RTCEUR Wish #1 – Push parameter from Host to Hosted

@Jbenoit44 suggested a tool to push a param from any Host (inc. Lev. and ref pl.) to any Hosted & reverse. Doors windows face base fam Parts etc

This implementation drives parameters from Hosts to their Family Instances.

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

    ElementCategoryFilter hostFilter = new ElementCategoryFilter(BuiltInCategory.OST_Walls);
    ElementCategoryFilter hostedFilter = new ElementCategoryFilter(BuiltInCategory.OST_Doors);

    string parameterName = "Rating";

    using (Transaction t = new Transaction(doc, "Set hosted parameters"))
    {
        t.Start();
        foreach (Element host in new FilteredElementCollector(doc).WherePasses(hostFilter))
        {
            Parameter paramHost = host.get_Parameter(parameterName);

            foreach (Element hosted in new FilteredElementCollector(doc)
                     .WherePasses(hostedFilter)
                     .OfClass(typeof(FamilyInstance))
                     .Cast<FamilyInstance>()
                     .Where(q => q.Host.Id == host.Id))
            {
                if (paramHost.StorageType == StorageType.String)
                    hosted.get_Parameter(parameterName).Set(paramHost.AsString());
                else if (paramHost.StorageType == StorageType.Double)
                    hosted.get_Parameter(parameterName).Set(paramHost.AsDouble());
                else if (paramHost.StorageType == StorageType.Integer)
                    hosted.get_Parameter(parameterName).Set(paramHost.AsInteger());
            }
        }
        t.Commit();
    }
}

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

Who are the users with elements editable?

Matt asked this morning at RTC how to get a list of the users who own elements in a workshared file. Here’s a bit of code to do that.

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

    IList<string> users = new List<string>();

    // create an Or filter that takes ElementIsElementTypeFilter & the inverted version of ElementIsElementTypeFilter
    // the result is that the filter returns every element in the model
    foreach (Element e in  new FilteredElementCollector(doc)
        .WherePasses(new LogicalOrFilter
                    (new ElementIsElementTypeFilter(true),
                     new ElementIsElementTypeFilter(false))))
    {
        string owner = String.Empty;
        CheckoutStatus cs = WorksharingUtils.GetCheckoutStatus(doc, e.Id, out owner);
        if (owner != String.Empty && !users.Contains(owner))
            users.Add(owner);
    }

    string info = String.Empty;
    foreach (string s in users)
    {
        info += s + "\n";
    }
    TaskDialog.Show("Users = " + users.Count,"Users who own elements:\n" + info);
}

Automatically Synchronize With Central every hour

I don’t know why Erik wants to call this FaceMelter, but here is an example of how to use the Idling event and 2014’s new SynchronizeWithCentral method to automatically Synchronize With Central at a desired interval.

For testing purposes I set it to auto-synch every 3 minutes. You would probably want an interval a bit longer 🙂

autosynch

public static class myCommand
{
    // store the time when the last save occurred
    static DateTime lastSaveTime;

    public static void idleUpdate(object sender, IdlingEventArgs e)
    {
        // set an initial value for the last saved time
        if (lastSaveTime == DateTime.MinValue)
            lastSaveTime = DateTime.Now;

        // check the current time
        DateTime now = DateTime.Now;

        TimeSpan elapsedTime = now.Subtract(lastSaveTime);
        double minutes = elapsedTime.Minutes;

        UIApplication uiApp = sender as UIApplication;
        // write a comment to the journal file for diagnostic purposes
        uiApp.Application.WriteJournalComment("Idle check. Elapsed time = " + minutes,true);

        // don't do anything if less than 3 minutes since last auto-save
        if (minutes < 3)
            return;

        Document doc = uiApp.ActiveUIDocument.Document;
        if (!doc.IsWorkshared)
            return;

        TransactWithCentralOptions transact = new TransactWithCentralOptions();
        SynchronizeWithCentralOptions synch = new SynchronizeWithCentralOptions();
        synch.Comment = "Autosaved by the API at " + DateTime.Now;
        RelinquishOptions relinquishOptions = new RelinquishOptions(true);
        relinquishOptions.CheckedOutElements = true;
        synch.SetRelinquishOptions(relinquishOptions);

        uiApp.Application.WriteJournalComment("AutoSave To Central", true);
        doc.SynchronizeWithCentral(transact, synch);

        // update the last saved time
        lastSaveTime = DateTime.Now;
    }
}

class rtcApplication : IExternalApplication
{
    public static FailureDefinitionId failureDefinitionId = new FailureDefinitionId(new Guid("E7BC1F65-781D-48E8-AF37-1136B62913F5"));
    public Autodesk.Revit.UI.Result OnStartup(UIControlledApplication application)
    {
        // register the idling event when Revit starts
        application.Idling += new EventHandler<IdlingEventArgs>(myCommand.idleUpdate);
        return Result.Succeeded;
    }
    public Result OnShutdown(UIControlledApplication application)
    { return Result.Succeeded; }
}

View Category Overrides

Greetings from Revit Technology Conference NA in beautiful Vancouver! This is my first trip to RTC and it is great! Yesterday I gave a presentation titled “Raise the IQ of Your Revit model with the API” and then a fun and wide-ranging late-night API Q&A session with 20+ people.

Here’s a good question that I was asked this morning…

Q.  Is it possible to change the transparency of a category in a view in API? Basically the visibility/graphics dialog?

A. Yes, this is a new addition in the 2014 API. Below is a code sample and screenshot showing the result of this macro.

From the “2014 What’s New”:

Category override

Display of categories can be overridden. This can be done with the new class OverrideGraphicSettings and the new View methods:

  • SetCategoryOverrides
  • GetCategoryOverrides
  • IsOverrideValidForCategory
  • IsCategoryOverridable
public void setCatVis()
{
    UIDocument uidoc = this.ActiveUIDocument;
    Document doc = uidoc.Document;

    OverrideGraphicSettings ogs = new OverrideGraphicSettings();
    ogs.SetSurfaceTransparency(50);
    Category wallCat = doc.Settings.Categories.get_Item("Walls");

    using (Transaction t = new Transaction(doc,"Set Overrides"))
    {
        t.Start();
        doc.ActiveView.SetCategoryOverrides(wallCat.Id,ogs);
        t.Commit();
    }
}

Capture