What is version control, and why should you care?

  • Do you want your code safely backed up in the cloud?
  • Do you want to keep track of your code changes, when you change it, and why you changed it?
  • Do you want the ability to revert back to previous versions of your code?
  • Do you want to collaborate with other people at your company or elsewhere and make software together? This could include having multiple people writing code, contributing to a wiki, or reporting and tracking bugs and feature requests.

If you answered “no” to all of those questions, then this post is not for you. For everyone else… you need a version control system that records changes to a file or set of files over time so that you can recall specific versions later.

In this demonstration I am using these free tools that seem to work together very nicely:

  • Bitbucket – https://bitbucket.org/ – Free source code hosting with unlimited private repositories for up to 5 users.
  • Source Tree – http://sourcetreeapp.com/ – A free Git client for Windows & Mac. In my opinion, much better than running Git commands using the command-line. There are also other options like TortiseGit – https://code.google.com/p/tortoisegit/.
  • Git – http://git-scm.com/ – Git is a free and open source distributed version control system designed to handle everything from small to very large projects with speed and efficiency. Git is running behind the scenes at Bitbucket. You won’t run Git directly but it will be hard at work behind the scenes.

There are plenty of websites that do a great job explaining all the concepts of Git – commit, push, repository, etc. To learn more you might start at http://sixrevisions.com/resources/git-tutorials-beginners/

Transferring view templates, not in 2014

Here is a follow up to my recent post about using the new 2014 API to copy a view template from one project to another.

Q. What if you aren’t using Revit 2014 and therefore can’t use the API to copy between documents?

A. Use this macro to select the template then do the following steps by hand:

  1. Copy the selected element to the clipboard
  2. Switch to the target document
  3. Paste Aligned to Current View
public void SelectTemplateOfActiveView()
{
    Document doc = this.ActiveUIDocument.Document;
    UIDocument uidoc = this.ActiveUIDocument;

    // Get the view template assigned to the active view
    ElementId id = doc.ActiveView.ViewTemplateId;
    if (id == ElementId.InvalidElementId)
    {
        TaskDialog.Show("Error","Active view does not have a view template.");
        return;
    }
    Element viewTemplate = doc.GetElement(id);

    // create a new empty SelElementSet
    SelElementSet selSet = SelElementSet.Create();

    // add the view template to the SelElementSet
    selSet.Add(viewTemplate);

    // make this SelElementSet selected in the Revit UI
    uidoc.Selection.Elements = selSet;
}

Transferring just one View Template from Project to Project

Luke asks:

Let’s say you have 100 view templates in a project, and you make a new one.  You want to transfer only that new template to another project (not the other 100 View Templates).  If you use Transfer Project Standards (on View Templates and Filters), you will get the lot.  How can we transfer just one of them?

This is a great opportunity to show Revit 2014’s new ability to copy elements from one document to another.

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

    // get the id of the view template assigned to the active view
    ElementId templateId = doc.ActiveView.ViewTemplateId;
    if (templateId == ElementId.InvalidElementId)
    {
        TaskDialog.Show("Error", "Active view must have a view template assigned.");
        return;
    }

    // find the other open document in this session of Revit
    Application app = this.Application;
    Document otherDoc = app.Documents.Cast<Document>().Where(d => d.Title != doc.Title).FirstOrDefault();
    if (otherDoc == null)
    {
        TaskDialog.Show("Error", "There must be a 2nd document open.");
        return;
    }

    // add the template id to a collection
    // must have 'using System.Collections.ObjectModel;' to use 'new Collection'
    ICollection<ElementId> copyIds = new Collection<ElementId>();
    copyIds.Add(templateId);

    // create a default CopyPasteOptions
    CopyPasteOptions cpOpts = new CopyPasteOptions();

    // create a new transaction in the other document
    using (Transaction t = new Transaction(otherDoc, "Copy View Template"))
    {
        t.Start();
        // perform the copy into the other document using this new 2014 feature
        ElementTransformUtils.CopyElements(doc, copyIds, otherDoc, Transform.Identity, cpOpts);
        t.Commit();
    }
}

Beware closing windows in SharpDevelop while using Step Into

Step Into” is a great tool to use to see what is happening inside your macro. But beware! If you click this button it will crash SharpDevelop, crash Revit, and leave a zombie Revit.exe process that you need to kill with Task Manager. This is reproducible in 2013 but seems to be fixed in 2014.

Capture

Using BoundingBoxContainsPointFilter with Rooms & Filled Regions

One of the Revit users who I have been teaching how to use the Revit API had this question:

“I want to renumber my rooms automatically in a floor plan view by getting the room’s location point and seeing if it falls within the boundary of a filled region. I would have a grid of filled regions overlaid on the plan, each with a unique comment, such as ‘A1’, ‘A2’, ‘B1’, etc. The code would write the comments value into the room’s number parameter.

My firm’s standard for room naming is to overlay a grid on top of the plan and number each room based on the coordinates of the grid (plus the level number in front). So a person has to do this manually which takes a while. My idea was to put the info into the filled regions (which when grouped could be reused over and over) and number the rooms automatically based on the room location point.”

We solved this as follows. The BoundingBoxContainsFilter is used to find the filled region that is on top of each room:

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

    using (Transaction t = new Transaction(doc,"Set Rooms By Region"))
    {
        t.Start();
        foreach (Room r in new FilteredElementCollector(doc).OfClass(typeof(SpatialElement)).OfCategory(BuiltInCategory.OST_Rooms).Cast<Room>().Where(q => q.Area > 0))
        {
            XYZ roomPoint = ((LocationPoint)r.Location).Point;
            IList<Element> overlapList = new FilteredElementCollector(doc,doc.ActiveView.Id).OfClass(typeof(FilledRegion)).WherePasses(new BoundingBoxContainsPointFilter(roomPoint)).ToList();
            if (overlapList.Count > 1)
            {
                TaskDialog.Show("Error","Skipping room " + r.Name + ". Overlaps with more than one filled region.");
                continue;
            }
            else if (overlapList.Count == 0)
            {
                continue;
            }
            else
            {
                Element e = overlapList.First();
                r.Number = e.get_Parameter("Comments").AsString();
            }
        }
        t.Commit();
    }
}

Net and Gross Wall Area

At Revit Forum it was asked how to get the gross area of a wall. Here’s a bit of code using Document.Regeneration() and Transaction.Rollback() to get the job done.

Capturepublic void NetWallArea()
{
    UIDocument uidoc = this.ActiveUIDocument;
    Document doc = uidoc.Document;
    foreach (Wall w in new FilteredElementCollector(doc).OfClass(typeof(Wall)).Cast<Wall>())
    {
        // get a reference to one of the wall's side faces
        Reference sideFaceRef = HostObjectUtils.GetSideFaces(w, ShellLayerType.Exterior).First();

        // get the geometry object associated with that reference
        Face netFace = w.GetGeometryObjectFromReference(sideFaceRef) as Face;

        // get the area of the face - this area does not include the area of the inserts that cut holes in the face
        double netArea = netFace.Area;

        double grossArea;
        using (Transaction t = new Transaction(doc,"delete inserts"))
        {
            t.Start();

            // delete all family inserts that are hosted by this wall
            foreach (FamilyInstance fi in new FilteredElementCollector(doc).OfClass(typeof(FamilyInstance)).Cast<FamilyInstance>().Where(q => q.Host != null && q.Host.Id == w.Id))
            {
                doc.Delete(fi.Id);
            }
            // regenerate the model to update the geometry with the inserts deleted
            doc.Regenerate();

            // get the gross area (area of the wall face now that the inserts are deleted)
            Face grossFace = w.GetGeometryObjectFromReference(sideFaceRef) as Face;
            grossArea = grossFace.Area;

            // rollback the transaction to restore the model to its original state
            t.RollBack();
        }
        TaskDialog.Show("Areas", "Net = " + netArea + "\nGross = " + grossArea);
    }
}

Print Sheets from Host & Linked RVT files

Jay suggested that it would be great to be able to print sheets from a linked RVT while in the host RVT.

Here’s a bit of code showing how the API can print the sheets both in the host file and the linked site file.

public void printLinked()
{
    Application app = this.Application;
    foreach (Document doc in app.Documents)
    {
       PrintManager printManager = doc.PrintManager;
       printManager.PrintToFile = true;

       printManager.SelectNewPrintDriver("CutePDF Writer");

        foreach (ViewSheet vs in new FilteredElementCollector(doc).OfClass(typeof(ViewSheet)).Cast<ViewSheet>())
        {
            printManager.PrintToFileName = @"C:\OutputFolder\" + Path.GetFileName(doc.PathName) + " " + vs.Name + " " + vs.SheetNumber + ".";
			printManager.SubmitPrint(vs);
		}
	}
}

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

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

Delete ALL Views

I was asked today at RTC if it would be possible to delete all views in a Revit file in preparation for publishing the model to other firms.

It was a little more complicated than I thought and is contrary to all those kumbaya ideas about sharing and collaboration, but if this is something you need to do, the API can save you a bunch of clicking in the Project Browser.

The 3D geometry in the model still exists after the command completes and you can use the UI to create new 3D and plan views. 

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