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

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++;
            }
        }
    }        
}

Dynamic Model Update, Exceptions, and Transactions (hint – look in the journal file)

In the last post I casually mentioned that during Dynamic Model Update we could delete the just-created elements. My first attempt was to add a transaction and Document.Delete(). That doesn’t work, and it also brings up a useful tip.

The error dialog shown on the right is Revit’s way of saying that your updater threw an exception, though it doesn’t give any technical details about the nature of the execption. “But what was the exception?” you will rightfully wonder! The answer is in the journal file, where in this case it explains:

' 2:< Exception caught from managed method RevitAPI::Autodesk.Revit.DB.TransactionStatus Start() <Autodesk.Revit.Exceptions.InvalidOperationException> <Starting a new transaction is not permitted. It could be because another transaction already started and has not been completed yet, or the document is in a state in which it cannot start a new transaction (e.g. during failure handling or a read-only mode, which could be either permanent or temporary).>

errorpublic void Execute(UpdaterData data)
{
    Document doc = data.GetDocument();
    Autodesk.Revit.ApplicationServices.Application app = doc.Application;
    foreach (ElementId addedElemId in data.GetAddedElementIds())
    {
        ImportInstance ii = doc.GetElement(addedElemId) as ImportInstance;
        if (ii.IsLinked == false)
            TaskDialog.Show("Hey!", app.Username + " - Maybe should should have linked that CAD instead of importing it.");

        // THIS DOESN'T WORK!
        Transaction t = new Transaction(doc,"delete that");
        t.Start();
        doc.Delete(ii);
        t.Commit();
    }
}

Thanks to that info in the journal file, we learn that we need to get rid of the transaction code. Doing so gives us this, which works fine to delete the import instance that was just created.

public void Execute(UpdaterData data)
{
    Document doc = data.GetDocument();
    Autodesk.Revit.ApplicationServices.Application app = doc.Application;
    foreach (ElementId addedElemId in data.GetAddedElementIds())
    {
        ImportInstance ii = doc.GetElement(addedElemId) as ImportInstance;
        if (ii.IsLinked == false)
            TaskDialog.Show("Hey!", app.Username + " - Maybe should should have linked that CAD instead of importing it.");
        doc.Delete(ii);
    }
}

Dynamic Model Update to warn or log after a user operation

Maybe the approach in the previous post, of completely shutting down the Import command, is too harsh. This example shows how a Dynamic Model Update macro can give a warning after the user imports a CAD file. For some introductory comments on Dynamic Model Update, see https://boostyourbim.wordpress.com/2012/12/17/automatically-run-api-code-when-your-model-changes.

Instead of (or in addition to) the TaskDialog warning used in the code below, you could do other things like write information to a log file (to submit to Human Resources and include in the Revit user’s permanent record!) or require the user to enter a password to complete the operation – if the wrong password is entered then delete the element id returned by data.GetAddedElementIds().

private void Module_Startup(object sender, EventArgs e)
{
    ImportInstanceUpdater updater = new ImportInstanceUpdater(this.Application.ActiveAddInId);
    try
    {
    UpdaterRegistry.RegisterUpdater(updater);
    UpdaterRegistry.AddTrigger(updater.GetUpdaterId(),
                               new ElementClassFilter(typeof(ImportInstance)),
                               Element.GetChangeTypeElementAddition());
    }
    catch {}
}

public class ImportInstanceUpdater : IUpdater
{
    static AddInId m_appId;
    static UpdaterId m_updaterId;
    // constructor takes the AddInId for the add-in associated with this updater
    public ImportInstanceUpdater(AddInId id)
    {
        m_appId = id;
        m_updaterId = new UpdaterId(m_appId, new Guid("FBFBF6A2-4C06-42d4-97C1-D1B4EB593EFF"));
    }
    public void Execute(UpdaterData data)
    {
        Document doc = data.GetDocument();
        Autodesk.Revit.ApplicationServices.Application app = doc.Application;
        foreach (ElementId addedElemId in data.GetAddedElementIds())
        {
            ImportInstance ii = doc.GetElement(addedElemId) as ImportInstance;
            if (ii.IsLinked == false)
                TaskDialog.Show("Hey!", app.Username + " - Maybe should should have linked that CAD instead of importing it.");
        }
    }
    public string GetAdditionalInformation(){return "Check to see if CAD file was imported";}
    public ChangePriority GetChangePriority(){return ChangePriority.FloorsRoofsStructuralWalls;}
     public UpdaterId GetUpdaterId(){return m_updaterId;}
    public string GetUpdaterName(){return "Import Check";}
}

Automatically Run API Code When Your Model Changes (Does this toilet make my wall look thin?)

Over at Revit SWAT there is a great post about how to modify a wall-hosted family so it gives an alert when the host wall is too thin. This topic is a great excuse for me to write about Dynamic Model Update, the Revit API feature that automatically runs API code when your Revit model changes.

The idea in this example is to alert the user when a toilet is placed on a wall that is too thin to hold its plumbing. To do this, we need to have API code that runs automatically when the model changes.

But before coding such a real-time alert, here is a quick sample showing how to check all plumbing fixtures that are already in the model.

public void checkPlumbingWallWidth()
{
    Document doc = this.ActiveUIDocument.Document;
    string tooThin = "";
    SelElementSet selSet = SelElementSet.Create();
    foreach (FamilyInstance inst in (from i in new FilteredElementCollector(doc)
             .OfClass(typeof(FamilyInstance)) // find only FamilyInstances
             .OfCategory(BuiltInCategory.OST_PlumbingFixtures) // find only Plumbing Fixtures
             .Cast<FamilyInstance>() // cast to FamilyInstance so FamilyInstance.Host can be used
             where ((Wall)i.Host).Width < 0.5 // FamilyInstance.Host returns an Element, so cast it to a Wall so Wall.Width can be used
             select i))
    {
        tooThin += inst.Symbol.Family.Name + ", id = " + inst.Id + "\n";
        selSet.Add(inst); // add the instance to the selection set
    }
    UIDocument uidoc = new UIDocument(doc);
    uidoc.Selection.Elements = selSet; 
    uidoc.RefreshActiveView();
    TaskDialog.Show("Plumbing in Walls < 6\"",tooThin);
}

fixtures

For more info on the SelElementSet functionality to select the elements that are found, see https://boostyourbim.wordpress.com/2012/12/15/retrieving-and-selecting-elements-in-a-selectionfilterelement/


Now for the real-time alert!

With the code below, placing the toilet on the vertical (thicker) wall gives no warning. But placing one on the thin horizontal wall gives the warning shown.

wallwarning

Below is the code for 3 macros:

  • FamilyInstanceUpdater – the code that runs when the trigger occurs
  • RegisterUpdater to turn on the updater
  • UnregisterUpdater to turn off the updater
public class FamilyInstanceUpdater : IUpdater
{
    static AddInId m_appId;
    static UpdaterId m_updaterId;
    // constructor takes the AddInId for the add-in associated with this updater
    public FamilyInstanceUpdater(AddInId id)
    {
        m_appId = id;
        // every Updater must have a unique ID
        m_updaterId = new UpdaterId(m_appId, new Guid("FBFBF6B2-4C06-42d4-97C1-D1B4EB593EFF"));
    }
    public void Execute(UpdaterData data)
    {
        Document doc = data.GetDocument();

        // loop through the list of added elements
        foreach (ElementId addedElemId in data.GetAddedElementIds())
        {
            // check if the added element is a family instance of the Plumbing Fixtures category
            FamilyInstance instance = doc.GetElement(addedElemId) as FamilyInstance;
            if (instance != null && instance.Category.Name == "Plumbing Fixtures")
            {
                // Get the instance's host wall
                Wall wall = instance.Host as Wall;
                // Check that there is a host wall and that its width is greater than 6"
                // Revit API uses feet for distance measurements
                if (wall != null && wall.Width < 0.5)
                    TaskDialog.Show("Warning!", "Your wall is too thin!");
            }
        }
    }
    public string GetAdditionalInformation(){return "Family Instance host wall thickness check";}
    public ChangePriority GetChangePriority(){return ChangePriority.FloorsRoofsStructuralWalls;}
    public UpdaterId GetUpdaterId(){return m_updaterId;}
    public string GetUpdaterName(){return "Wall Thickness Check";}
}

// This command must be run to register the updater with Revit
// Often this is done as part of the Revit start-up process so that the updater is always active
public void RegisterUpdater()
{
    FamilyInstanceUpdater updater = new FamilyInstanceUpdater(this.Application.ActiveAddInId);
    UpdaterRegistry.RegisterUpdater(updater);

    // Trigger will occur only for FamilyInstance elements
    ElementClassFilter familyInstanceFilter = new ElementClassFilter(typeof(FamilyInstance));

    // GetChangeTypeElementAddition specifies that the triggger will occur when elements are added
    // Other options are GetChangeTypeAny, GetChangeTypeElementDeletion, GetChangeTypeGeometry, GetChangeTypeParameter
    UpdaterRegistry.AddTrigger(updater.GetUpdaterId(), familyInstanceFilter, Element.GetChangeTypeElementAddition());
}

public void UnregisterUpdater()
{
    FamilyInstanceUpdater updater = new FamilyInstanceUpdater(this.Application.ActiveAddInId);
    UpdaterRegistry.UnregisterUpdater(updater.GetUpdaterId());
}

I’ve tried to put a decent amount of description in the comments, for more info check out http://wikihelp.autodesk.com/Revit/enu/2013/Help/00006-API_Developer’s_Guide/0135-Advanced135/0152-Dynamic_152

Enhancement Idea:

  • Create a trigger that uses GetChangeTypeGeometry instead of GetChangeTypeElementAddition with a filter for Walls. If an existing wall that is hosting toilets becomes thinner (because its structure is changed or the wall type is changed) then the warning should be given.