Honored to be #RTCEUR Top 10 Speaker #3

Thank you to everyone who attended my class “Get the Data Out: Calculating & Computing What Revit Won’t Give You” last month in Budapest at RTC Europe. I’m glad you enjoyed the course and thanks for the high marks you gave the course. There were 50+ speakers at RTC Europe 2015 and it is an honor to be voted #3!

Quick Tip: Module name should not be a Class name

If you create a macro module named “Wall” and then do this:

namespace Wall
{
    [Autodesk.Revit.Attributes.Transaction(Autodesk.Revit.Attributes.TransactionMode.Manual)]
    [Autodesk.Revit.DB.Macros.AddInId("B1079B97-D3BA-430F-9109-F492B7F2891F")]
    public partial class ThisApplication
    {
        public void test()
        {
            Document doc = this.ActiveUIDocument.Document;
            Element e = new FilteredElementCollector(doc).OfClass(typeof(FamilyInstance)).FirstOrDefault();
        }

Everything will be OK. But if you try to use the Revit API class “Wall” in your code, the compiler will be confused because the module is named “Wall” and you will get an error like below.
So keep those module names unique and different!

Capture

#RTCEUR Wish 5! View Template (partially) exported

T.R. wished for the ability to “compare two view templates to identify/highlight differences. Or a way to spit them to txt/xls for compare”

We can get the data from all parameters stored as numbers, text, and element ids. We can also query the properties of the View class. In the sample code, this is done using reflection so code does not need to be written for each property individually.

Also, GetNonControlledTemplateParameterIds returns a list of parameters that are not marked as included when this view is used as a template. GetTemplateParameterIds returns a list of parameter ids that may be controlled when this view is assigned as a template.

Sample output of all parameter values:

------PARAMETERS---------
Category,-2000279
Category,-2000279
Color Scheme Location,1
Design Option,-1
Detail Level,3
Discipline,1
Display Model,0
Edited by,
Family,
Family,
Family and Type,
Family and Type,
Family Name,
Far Clipping,0
Learning Content,
Parts Visibility,1
Phase Filter,375
Scale Value    1:,100
Show Hidden Lines,1
Sun Path,0
Type,
Type Name,
View Scale,100
View Template,-1
Workset,172
------PROPERTIES---------
AnalysisDisplayStyleId,-1
AreAnalyticalModelCategoriesHidden,False
AreAnnotationCategoriesHidden,False
AreImportCategoriesHidden,False
AreModelCategoriesHidden,False
ArePointCloudsHidden,False
AssemblyInstanceId,-1
AssociatedAssemblyInstanceId,-1
CanBePrinted,False
CreatedPhaseId,-1
DemolishedPhaseId,-1
GroupId,-1
Id,9610
IsAssemblyView,False
IsTemplate,True
IsValidObject,True
LevelId,-1
Name,Architectural Section
OwnerViewId,-1
Pinned,False
RevealConstraintsMode,False
Title,Architectural Section
UniqueId,96dd64a0-c5ef-4337-8f9b-658b5c420b33-0000258a
ViewName,Architectural Section
ViewSpecific,False
ViewTemplateId,-1
---------------
Enable Sketchy Lines,False
Sketchy Line Extension,0
Sketchy Line Jitter,0
public void exportViewTemplate()
{
    Document doc = this.ActiveUIDocument.Document;
    string templateName = "Architectural Section";
    View template = new FilteredElementCollector(doc).OfClass(typeof(View)).Cast<View>()
        .FirstOrDefault(q => q.IsTemplate == true && q.Name == templateName);
    
    using (StreamWriter sw = new StreamWriter(@"C:\Users\harry_000\Desktop\rtc\View Template Export - " + templateName + ".txt"))
    {
        // quick and easy solution to get data from simple parameters
        sw.WriteLine("------PARAMETERS---------");
        foreach (Parameter p in template.Parameters.Cast<Parameter>().OrderBy(q => q.Definition.Name))
        {
            string val = "<undefined>";
            if (p.StorageType == StorageType.Double)
                val = p.AsDouble().ToString();
            else if (p.StorageType == StorageType.Integer)
                val = p.AsInteger().ToString();
            else if (p.StorageType == StorageType.ElementId)
                val = p.AsElementId().IntegerValue.ToString();
            else if (p.StorageType == StorageType.String)
                val = p.AsString();
            
            if (val != "<undefined>")
                sw.WriteLine(p.Definition.Name + "," + val);
        }
        
        // use reflection to get values from all properties of the view class
        sw.WriteLine("------PROPERTIES---------");
        foreach (System.Reflection.PropertyInfo pi in template.GetType().GetProperties().OrderBy(q => q.Name))
        {
            string propertyName = "";
            string propertyValue = "";
            getPropertyNameAndValue(template, pi, out propertyName, out propertyValue);
            
            if (propertyValue != "<undefined>")
                sw.WriteLine(propertyName + "," + propertyValue);
        }
        
        sw.WriteLine("---------------");
        
        // complex parameters require more work to export values individually
        ViewDisplaySketchyLines sketchyLines = template.GetSketchyLines();
        sw.WriteLine("Enable Sketchy Lines," + sketchyLines.EnableSketchyLines.ToString());
        sw.WriteLine("Sketchy Line Extension," + sketchyLines.Extension.ToString());
        sw.WriteLine("Sketchy Line Jitter," + sketchyLines.Jitter.ToString());
        
    }
}

private void getPropertyNameAndValue(object o, System.Reflection.PropertyInfo pi, out string propertyName, out string propertyValue)
{
    propertyName = pi.Name;
    propertyValue = "<undefined>";
    if (pi.PropertyType == typeof(ElementId) ||
        pi.PropertyType == typeof(Boolean) ||
        pi.PropertyType == typeof(String) ||
        pi.PropertyType == typeof(Enum))
    {
        try
        {
            propertyValue = pi.GetValue(o).ToString();
        }
        catch
        {}
    }
}

#RTCEUR Wish 4 granted! Change parameter value in multiple families

Doug asked ” How about a routine to change the value of a shared parameter in all families in a folder to the same specified value.”

public void setParamInFamilies()
{
    Application app = this.Application;
    foreach (string filename in Directory.GetFiles(@"C:\Users\harry_000\Desktop\rtc", "*.rfa"))
    {
        Document doc = app.OpenDocumentFile(filename);
        using (Transaction t = new Transaction(doc, "Set param value"))
        {
            t.Start();
            
            // get the parameter to set
            FamilyParameter param = doc.FamilyManager.get_Parameter("RTC Parameter");
            
            if (param == null)
                continue;
            
            // loop through all types in the family
            foreach (FamilyType ftype in doc.FamilyManager.Types)
            {
                // set the parameter value for this type
                doc.FamilyManager.Set(param, "Budapest");
            }
            t.Commit();
            doc.Save();
            doc.Close(false);
        }
    }
}

Code from today’s #RTCEUR advanced API course

Today’s course focused on how to use custom classes, overrides, custom enums, source code control (with https://bitbucket.org & https://www.sourcetreeapp.com) and more. Below is the code we developed during the class.

public void groupAudit()
{
    Document doc = this.ActiveUIDocument.Document;
    
    List<Group> groups = new FilteredElementCollector(doc).OfClass(typeof(Group)).Cast<Group>().ToList();
    List<GroupType> groupTypes = new FilteredElementCollector(doc).OfClass(typeof(GroupType)).Cast<GroupType>().ToList();
    
    List<Tuple<string, int, int>> groupData = new List<Tuple<string, int, int>>();
    
//			using (StreamWriter sw = new StreamWriter(@"C:\Users\harry_000\Desktop\RTC_EUR_2015-2015-10-28\RTC EUR 2015\Advanced API\\output.txt"))
//			{
        foreach (GroupType groupType in groupTypes)
        {
            //int numberOfElements = group.GetMemberIds().Count;
            
            int numberOfElementsInGroupType = 0;
            
//				foreach (Element element in new FilteredElementCollector(doc).OfClass(typeof(Group)))
//				{
//					Group g = element as Group;
//				}
            
            // old way without LINQ
//				foreach (Group group in new FilteredElementCollector(doc).OfClass(typeof(Group)).Cast<Group>())
//				{
//					if (group.GroupType.Id == groupType.Id)
//					{
//						countA = group.GetMemberIds().Count;
//						continue;
//					}
//				}
            
            // with LINQ - better
            foreach (Group group in new FilteredElementCollector(doc).OfClass(typeof(Group))
                     .Cast<Group>()
                     .Where(g => g.GroupType.Id == groupType.Id))
            {
                numberOfElementsInGroupType = group.GetMemberIds().Count;
            }
            
            int numberOfGroupInstances = new FilteredElementCollector(doc).OfClass(typeof(Group)).Cast<Group>()
                .Where(g => g.GroupType != null && g.GroupType.Id == groupType.Id).Count();
            
            TaskDialog.Show("number", groupType.Name + " = " + numberOfElementsInGroupType.ToString()
                           + Environment.NewLine 
                          + numberOfGroupInstances);
            
            // not good to mix collecting the data and using the data in the same loop
            // instead, better to collect the data, store it, and then use it later
            //sw.WriteLine(groupType.Name + ", " + numberOfGroupInstances + "," + numberOfElementsInGroupType);
            
            groupData.Add(new Tuple<string, int, int>(groupType.Name, numberOfGroupInstances, numberOfElementsInGroupType));
            
            //int count = new FilteredElementCollector(doc).OfClass(typeof(Group)).Cast<Group>()
        }
        
        //writeTupleToFile(groupData);

        
//	}
}

public void getOfficeGroupInfo()
{
    Document doc = this.ActiveUIDocument.Document;
    namedGroupDataImplementation(doc, GroupNamePrefix.Office);
}

public void getKitchenGroupInfo()
{
    Document doc = this.ActiveUIDocument.Document;
    namedGroupDataImplementation(doc, GroupNamePrefix.Kitchen);
}


public void namedGroupDataImplementation(Document doc, GroupNamePrefix prefix)
{
    
}

public enum GroupNamePrefix
{
Office,
Kitchen,
Bath
};

public void getDetailGroupInfo()
{
    Document doc = this.ActiveUIDocument.Document;
    groupDataImplementation(doc, BuiltInCategory.OST_IOSDetailGroups);
}

public void getModelGroupInfo()
{
    Document doc = this.ActiveUIDocument.Document;
    groupDataImplementation(doc, BuiltInCategory.OST_IOSModelGroups);
}

public void groupDataImplementation(Document doc, BuiltInCategory cat)
{
    
    List<Group> groups = new FilteredElementCollector(doc).OfClass(typeof(Group)).Cast<Group>().ToList();
    List<GroupType> groupTypes = new FilteredElementCollector(doc).OfClass(typeof(GroupType)).Cast<GroupType>()
        .ToList();
    
    // store data in a list of GroupData objects instead of a Tuple
    List<GroupData> groupData = new List<GroupData>();
    int numberOfElementsInGroupType = 0;
    foreach (GroupType groupType in groupTypes)
    {
        // homework - use the temporary transaction trick for group types with no group instances
// http://thebuildingcoder.typepad.com/blog/2012/11/temporary-transaction-trick-touchup.html				
        
        if (new FilteredElementCollector(doc).OfClass(typeof(Group)).Cast<Group>()
            .FirstOrDefault(q => q.GroupType.Id == groupType.Id && q.Category.Id.IntegerValue == (int)cat) == null)
            continue;
        
        foreach (Group group in new FilteredElementCollector(doc).OfClass(typeof(Group))
                 .Cast<Group>()
                 .Where(g => g.GroupType.Id == groupType.Id))
        {
            numberOfElementsInGroupType = group.GetMemberIds().Count;
            
            foreach (ElementId id in group.GetMemberIds())
            {
                Element e = doc.GetElement(id);
            }
            
        }
        
        int numberOfGroupInstances = new FilteredElementCollector(doc).OfClass(typeof(Group)).Cast<Group>()
            .Where(g => g.GroupType != null && g.GroupType.Id == groupType.Id).Count();
        
        groupData.Add(new GroupData(groupType.Name, numberOfGroupInstances, numberOfElementsInGroupType));
    }
    writeGroupDataToFile(groupData);
}



public void groupAuditUsingCustomClassToStoreData()
{
    Document doc = this.ActiveUIDocument.Document;
    
    List<Group> groups = new FilteredElementCollector(doc).OfClass(typeof(Group)).Cast<Group>().ToList();
    List<GroupType> groupTypes = new FilteredElementCollector(doc).OfClass(typeof(GroupType)).Cast<GroupType>().ToList();
    
    // store data in a list of GroupData objects instead of a Tuple
    List<GroupData> groupData = new List<GroupData>();
    int numberOfElementsInGroupType = 0;
    foreach (GroupType groupType in groupTypes)
    {
        foreach (Group group in new FilteredElementCollector(doc).OfClass(typeof(Group))
                 .Cast<Group>()
                 .Where(g => g.GroupType.Id == groupType.Id))
        {
            numberOfElementsInGroupType = group.GetMemberIds().Count;
        }
        
        int numberOfGroupInstances = new FilteredElementCollector(doc).OfClass(typeof(Group)).Cast<Group>()
            .Where(g => g.GroupType != null && g.GroupType.Id == groupType.Id).Count();
        
//				GroupData data = new GroupData();
//				data.name = groupType.Name;
//				data.numberOfElements = numberOfElementsInGroupType;
//				data.numberOfInstances = numberOfGroupInstances;
//				groupData.Add(data);
        
        //GroupData groupDataWithConstructor = new GroupData(groupType.Name, numberOfGroupInstances, numberOfElementsInGroupType);
        groupData.Add(new GroupData(groupType.Name, numberOfGroupInstances, numberOfElementsInGroupType));
        
        //groupData.Add(new Tuple<string, int, int>(groupType.Name, numberOfGroupInstances, numberOfElementsInGroupType));
    }
        
    writeGroupDataToFile(groupData);
    
    
}

public class GroupData
{
    public GroupData() {}
    
    public GroupData(string _Name, int NumberOfInstances, int NumberOfElements)
    {
        name = _Name;
        numberOfInstances = NumberOfInstances;
        numberOfElements = NumberOfElements;
    }
    public string name;
    public int numberOfInstances;
    public int numberOfElements;
}

private void writeGroupDataToFile(List<GroupData> data)
{
    // now use the data that was previously stored
    
    string filename = @"C:\Users\harry_000\Desktop\RTC_EUR_2015-2015-10-28\RTC EUR 2015\Advanced API\\output.txt";
    using (StreamWriter sw = new StreamWriter(filename))
    {
        foreach (GroupData gd in data)
        {
            sw.WriteLine(gd.name + "," + gd.numberOfElements + "," + gd.numberOfInstances);
        }
    }
    Process.Start(filename);
}

private void writeTupleToFile(List<Tuple<string, int, int>> data)
{
    // now use the data that was previously stored
    using (StreamWriter sw = new StreamWriter(@"C:\Users\harry_000\Desktop\RTC_EUR_2015-2015-10-28\RTC EUR 2015\Advanced API\\output.txt"))
    {
        foreach (Tuple<string, int, int> tup in data)
        {
            sw.WriteLine(tup.Item1 + "," + tup.Item2 + "," + tup.Item3);
        }
    }
}


private void writeTupleToFile(List<Tuple<string, int, int, int>> data)
{
    // now use the data that was previously stored
    using (StreamWriter sw = new StreamWriter(@"C:\Users\harry_000\Desktop\RTC_EUR_2015-2015-10-28\RTC EUR 2015\Advanced API\\output.txt"))
    {
        foreach (Tuple<string, int, int, int> tup in data)
        {
            sw.WriteLine(tup.Item1 + "," + tup.Item2 + "," + tup.Item3 + "," + tup.Item4);
        }
    }
}

#RTCEUR Wish 3 continued & discussed – automatic revision clouds

In the previous post I showed how the API can create a revision and cloud for elements selected by the user. Now, what if you wanted the revisioning to be done automatically every time the user makes a change, addition, or deletion?

It works nicely when the table is moved or a new, wall is created that does not join with other walls.

But what happens when a window is moved or a wall that joins to other walls is created? Too many elements are included in the revision cloud – not good! This is because when the user modifies an element hosted by the wall, Revit makes changes to both the hosted element (such as the door) and also the wall that hosts the door. The same issue comes up when a new wall joins with existing walls – they all are changed. GetModifiedElementIds does not let us differentiate between the element that the user changed and the elements that Revit changed because of the user change.

It might be possible to get the selected element using the Idling event, store its element id, and then in the updater only create the revision cloud around this element if it was modified.

Automatically creating a revision cloud around an element’s former location after it has been deleted is even harder. GetDeletedElementIds gives the ids of the deleted elements, but because they are deleted they no longer have a bounding box or location that you can get to define the curves of the revision cloud. To do this you would need to store the id and bounding box coordinates of every element in the model, and then retrieve that data after the element is deleted. If smoke starts coming out of your computer from doing this, don’t say I didn’t warn you.

public class AutoRevisionCloudUpdater : IUpdater
{
    static AddInId m_appId;
    static UpdaterId m_updaterId;
    public AutoRevisionCloudUpdater(AddInId id)
    {
        m_appId = id;
        m_updaterId = new UpdaterId(m_appId, new Guid("1BF2F6A2-4C06-42d4-97C1-D1B4EB593EFF"));
    }
    public void Execute(UpdaterData data)
    {
        Document doc = data.GetDocument();
        
        // create list of all modified and created elements
        List<ElementId> ids = data.GetModifiedElementIds().ToList();
        ids.AddRange(data.GetAddedElementIds().ToList());
        
        foreach (ElementId id in ids)
        {        
            makeRevCloud(doc, id);
        }
    }
    public string GetAdditionalInformation(){return "Auto Revision Cloud";}
    public ChangePriority GetChangePriority(){return ChangePriority.FloorsRoofsStructuralWalls;}
    public UpdaterId GetUpdaterId(){return m_updaterId;}
    public string GetUpdaterName(){return "Auto Revision Cloud";}
    
    private void makeRevCloud(Document doc, ElementId id)
    {
        Element e = doc.GetElement(id);
            
        // create new revision
        Revision rev = Revision.Create(doc);
        rev.RevisionDate = DateTime.Now.ToLongDateString();
        rev.Description = id.IntegerValue.ToString();
        
        // use bounding box of element and offset to create curves for revision cloud
        BoundingBoxXYZ bbox = e.get_BoundingBox(doc.ActiveView);
        List<Curve> curves = new List<Curve>();
        double offset = 2;
        XYZ pt1 = bbox.Min.Subtract(XYZ.BasisX.Multiply(offset)).Subtract(XYZ.BasisY.Multiply(offset));
        XYZ pt2 = new XYZ(bbox.Min.X - offset, bbox.Max.Y + offset, 0);
        XYZ pt3 = bbox.Max.Add(XYZ.BasisX.Multiply(offset)).Add(XYZ.BasisY.Multiply(offset));;
        XYZ pt4 = new XYZ(bbox.Max.X + offset, bbox.Min.Y - offset, 0);
        curves.Add(Line.CreateBound(pt1, pt2));
        curves.Add(Line.CreateBound(pt2, pt3));
        curves.Add(Line.CreateBound(pt3, pt4));
        curves.Add(Line.CreateBound(pt4, pt1));
        
        // create revision cloud
        RevisionCloud cloud = RevisionCloud.Create(doc, doc.ActiveView, rev.Id, curves);
    }
}

public void registerUpdaterForAutoRevisionCloud()
{
    UIDocument uidoc = this.ActiveUIDocument;
    Document doc = uidoc.Document;        

    AutoRevisionCloudUpdater updater = new AutoRevisionCloudUpdater(this.Application.ActiveAddInId);
    UpdaterRegistry.RegisterUpdater(updater);
    UpdaterRegistry.AddTrigger(updater.GetUpdaterId(), new ElementClassFilter(typeof(FamilyInstance)), Element.GetChangeTypeGeometry());
    UpdaterRegistry.AddTrigger(updater.GetUpdaterId(), new ElementClassFilter(typeof(FamilyInstance)), Element.GetChangeTypeElementAddition());
    UpdaterRegistry.AddTrigger(updater.GetUpdaterId(), new ElementClassFilter(typeof(Wall)), Element.GetChangeTypeGeometry());
    UpdaterRegistry.AddTrigger(updater.GetUpdaterId(), new ElementClassFilter(typeof(Wall)), Element.GetChangeTypeElementAddition());
}

#RTCEUR Wish 3 granted (part 1)! Multiple Element Revision Creation

Barrie asked: “Can you rev cloud all change elements in views on sheets and write update to rev cloud comments for tagging?”

This post shows how to prompt the user to select multiple elements then create a new revisions and a revision cloud around each element. A following post will discuss to what extent this could be automated to create revisions every time the model changes.

public void makeRevisionAndCloud()
{
    UIDocument uidoc = this.ActiveUIDocument;
    Document doc = this.ActiveUIDocument.Document;
    Application app = this.Application;
    
    // get elements from user selection
    List<Element> elements = new List<Element>();
    
    // string of ids of the selected elements to use for Revision description
    string description = "";
    foreach (Reference r in uidoc.Selection.PickObjects(ObjectType.Element))
    {
        elements.Add(doc.GetElement(r));
        description += r.ElementId.IntegerValue + " ";
    }
    
    using (Transaction t = new Transaction(doc, "Make Revision & Cloud"))
    {
        t.Start();
        
        // create new revision
        Revision rev = Revision.Create(doc);
        rev.RevisionDate = DateTime.Now.ToShortDateString();
        rev.Description = description;
        rev.IssuedBy = app.Username;
            
        // use bounding box of element and offset to create curves for revision cloud
        foreach (Element e in elements)
        {
            BoundingBoxXYZ bbox = e.get_BoundingBox(doc.ActiveView);
            List<Curve> curves = new List<Curve>();
            double offset = 2;
            XYZ pt1 = bbox.Min.Subtract(XYZ.BasisX.Multiply(offset)).Subtract(XYZ.BasisY.Multiply(offset));
            XYZ pt2 = new XYZ(bbox.Min.X - offset, bbox.Max.Y + offset, 0);
            XYZ pt3 = bbox.Max.Add(XYZ.BasisX.Multiply(offset)).Add(XYZ.BasisY.Multiply(offset));;
            XYZ pt4 = new XYZ(bbox.Max.X + offset, bbox.Min.Y - offset, 0);
            curves.Add(Line.CreateBound(pt1, pt2));
            curves.Add(Line.CreateBound(pt2, pt3));
            curves.Add(Line.CreateBound(pt3, pt4));
            curves.Add(Line.CreateBound(pt4, pt1));
            
            // create revision cloud
            RevisionCloud cloud = RevisionCloud.Create(doc, doc.ActiveView, rev.Id, curves);
            
            // set Comments of revision cloud
            cloud.get_Parameter(BuiltInParameter.ALL_MODEL_INSTANCE_COMMENTS)
                .Set(e.GetType().Name + "-" + e.Name + ": " + doc.Application.Username);
            
            // tag the revision cloud
            IndependentTag tag = doc.Create.NewTag(doc.ActiveView, cloud, true, TagMode.TM_ADDBY_CATEGORY, TagOrientation.Horizontal, pt3);
            tag.TagHeadPosition = pt3.Add(new XYZ(2,2,0));
        }
        t.Commit();
    }
}

Code from this morning’s class at #RTCEUR

Not at #RTCEUR?

Did something else with your morning?

Came to the class and would like the source code?

public void CountElements()
{
    try
    {
        Document doc = this.ActiveUIDocument.Document;
        ICollection<Element> walls = new FilteredElementCollector(doc)
            .OfCategory(BuiltInCategory.OST_Walls).WhereElementIsCurveDriven().ToList();
        // TaskDialog.Show("Count", "# of Walls " + walls.Count);
        
        string desktop = Environment.GetFolderPath(Environment.SpecialFolder.Desktop);
        
        // these two lines do the same thing
        string otherway = desktop + @"\" + @"RTC_EUR_2015-2015-10-28\RTC EUR 2015\You can be a Revit Programmer\output.";
		string pathToFile = Path.Combine(desktop, @"RTC_EUR_2015-2015-10-28\RTC EUR 2015\You can be a Revit Programmer\output.");
		
		//using (StreamWriter writer = new StreamWriter(@"C:\Users\harry_000\Desktop\RTC_EUR_2015-2015-10-28\RTC EUR 2015\You can be a Revit Programmer\output.", true))
		using (StreamWriter writer = new StreamWriter(pathToFile, true))
		{
			// Write doesn't add a new line at the end, WriteLine does
			// or Environment.NewLine to insert a line break
		    writer.WriteLine(walls.Count);
		    
		    foreach (Element element in walls) {
		    	
		    	writer.Write(element.Name + " " + element.Id.IntegerValue);
		    	// need to cast from Element to Wall
		    	
		    	Wall wall = element as Wall;
		    	
		    	
		    	Parameter paramVolume = wall.get_Parameter(BuiltInParameter.HOST_VOLUME_COMPUTED);
		    	StorageType storageVolume = paramVolume.StorageType;
		    	
		    	// match the method (AsDouble, AsInteger) with how the value is stored
		    	double volume = paramVolume.AsDouble();
		    	volume = UnitUtils.ConvertFromInternalUnits(volume, DisplayUnitType.DUT_CUBIC_METERS);
		    	writer.WriteLine(" " + wall.Width + " " + volume);
		    	
		    	writer.WriteLine();
		    	
		    }
		    
		}
	}
	catch (Exception ex)
	{
		TaskDialog.Show("",ex.Message);
	}
		
}

#RTCEUR Wish #2 Granted! Rename views

Jason asked “How about a find / replace tool for view names?”

Wish granted!

public void viewRename()
{
    string find = "Level";
    string replace = "LEV";
    
    Document doc = this.ActiveUIDocument.Document;
    string errors = "";
    using (Transaction t = new Transaction(doc, "Rename views"))
    {
        t.Start();
        // find all views whose name contains the "find" string
        foreach (Element view in new FilteredElementCollector(doc).OfClass(typeof(View))
                 .Where(q => q.Name.Contains(find)))
        {
            try
            {
                view.Name = view.Name.Replace(find, replace);
            }
            catch // error handling if there is already a view with what would be the new name
            {
                errors += view.Name + ", ";
            }
        }
        t.Commit();
     }
    
    // show error dialog if there are errors
    if (errors != "")
    {
        TaskDialog td = new TaskDialog("Error");
        td.MainInstruction = "Could not rename views because duplicate names would be created";
        td.MainContent = errors;
        td.Show();
    }
}

#RTCEUR Wish 1 granted!

Chris asked how the API could help copy a legend to multiple sheets. Wish granted!

public void legendOnSheets()
{
    Document doc = this.ActiveUIDocument.Document;
    
    // create list of element ids for the sheets that will get the legends
    List<ElementId> sheetIds = new List<ElementId>();
        
    // use SheetNumber to find the desired sheets
    // add each sheet to the list of sheet ids
    sheetIds.Add(new FilteredElementCollector(doc)
        .OfClass(typeof(ViewSheet))
        .Cast<ViewSheet>()
        .FirstOrDefault(q => q.SheetNumber == "A101").Id);
    sheetIds.Add(new FilteredElementCollector(doc)
        .OfClass(typeof(ViewSheet))
        .Cast<ViewSheet>()
        .FirstOrDefault(q => q.SheetNumber == "A102").Id);                
    sheetIds.Add(new FilteredElementCollector(doc)
        .OfClass(typeof(ViewSheet))
        .Cast<ViewSheet>()
        .FirstOrDefault(q => q.SheetNumber == "A103").Id);    
    
    // find the legend to put on the sheets
    // use ViewType.Legend and the view name to find the legend view
    Element legend = new FilteredElementCollector(doc)
        .OfClass(typeof(View))
        .Cast<View>()
        .FirstOrDefault(q => q.ViewType == ViewType.Legend && q.Name == "Legend 1");
    
    // create a transaction so that the document can be modified
    using (Transaction t = new Transaction(doc, "Legends on Sheets"))
    {
        // start the transaction
        t.Start();
        
        // loop through the list of sheet ids
        foreach (ElementId sheetid in sheetIds)
        {
            // create a new viewport for the legend on each sheet
            // place the legend view at the 0,0,0 location on each sheet
            // user will need to move the legend to the desired location
            Viewport.Create(doc, sheetid, legend.Id, new XYZ(0,0,0));
        }
        
        // commit the changes
        t.Commit();
    }
}

It’s #RTCEUR Wish List Time

Revit Technology Conference Europe is here, so its time for Boost Your BIM to grant your API wishes? What is this all about? Check out https://boostyourbim.wordpress.com/category/api-wish/rtc/ to see previous wishes granted. Submit yours as comments here or tweet to @BoostYourBIM with hastag #RTCEUR

What data do you want to get out of your RVTs?

RTC Europe is drawing near, where I will be giving a presentation “Get the Data Out: Calculating & Computing What Revit Won’t Give You

Your Revit models contain incredible amounts of valuable data. Often you can access this data using schedules, data export, and 3rd party tools. But in many cases, custom API development is needed to get the data you need. This course will present case studies showing the value of this approach.

As I am preparing the course, it would be great to learn about situations where you have wanted to get geometry or parameter data from your Revit model but have found it difficult or impossible. Please share your thoughts in the comments and I will follow up with anyone whose suggestions make it into the course material.

#BIMTHOUGHTS talks #RTCNA, @BoostYourBIM, and more

If you weren’t able to be at RTCNA last month, or want to hear about some of the classes you may not have been able to attend, you are in luck because Bill Debevc and the bimthoughts.com team have published Part 1 of their RTC Recap.

At 11:25 in the podcast, Bill talks about attending the Boost Your BIM “You can be a Revit Programmer” course, how SharpDevelop and writing macros makes the API more accessible, and the my role in the early days of Charles River Software and Revit Technology Corporation.

Have #Revit files to upgrade?

Moving to Revit 2016?

Bulk File Upgrader will save you lots of time and tedium. It has a 5-star rating at the Autodesk store and has been used by many happy Revit users. Autodesk has selected is as a Featured App because of its increasing popularity as more people with 100s or 1000s of files are looking to get them all saved in the new version.

https://apps.exchange.autodesk.com/RVT/en/Detail/Index?id=appstore.exchange.autodesk.com%3abulkfileupgrader_windows32and64%3aen

Capture

#RTCNA leftover – know your StorageType

A question came up at RTC about why a certain parameter could not be set with the API. Turns out that it was a parameter that stored an ElementId. In this case, trying to set the parameter with a string for the name of the desired element does not work. And to make it confusing, it fails silently with no warning, error, or exception.

When the parameter was set with an ElementId, all was well. See the example below where we need to use the ID of the Phase named “Existing” instead of just setting the parameter to the string “Existing”.

public void setPhase()
{
    Document doc = this.ActiveUIDocument.Document;
    Parameter phaseParam = doc.ActiveView.get_Parameter(BuiltInParameter.VIEW_PHASE);
    using (Transaction t = new Transaction(doc, "Set Phase"))
    {
        t.Start();
        
        // doesn't work, because phaseParam stores an ElementId, not a string
        // see the Parameter.StorageType Property for more info
        // phaseParam.Set("Existing");
        
        phaseParam.Set(new FilteredElementCollector(doc).OfClass(typeof(Phase)).FirstOrDefault(q => q.Name == "Existing").Id);
        
        t.Commit();
    }
}

#RTCNA wish 10 granted! Material replacement

Amy asked if the API can be used to find and replace materials

This implementation does that for wall types and could be extended for other objects where the material is stored in a different manner.

BEFORE

 before

AFTER

after


public void replaceMaterials()
{
    Document doc = this.ActiveUIDocument.Document;
    
    List<Tuple<string,string>> replacements = new List<Tuple<string, string>>();
    // old materal listed first, then new material that will replace it
    replacements.Add(new Tuple<string, string>("Brick, Common", "Brick, Orange"));
    replacements.Add(new Tuple<string, string>("Concrete Masonry Units", "Cherry"));

    using (Transaction t = new Transaction(doc, "Replace Materials"))
    {
        t.Start();
        foreach (WallType wt in new FilteredElementCollector(doc).OfClass(typeof(WallType)).Cast<WallType>())
        {
            CompoundStructure cs = wt.GetCompoundStructure();
            if (cs == null)
                continue;

            int idx = 0;
            foreach (CompoundStructureLayer layer in cs.GetLayers())
            {
                foreach (Tuple<string, string> tup in replacements)
                {
                    Element oldMaterial = new FilteredElementCollector(doc).OfClass(typeof(Material))
                        .FirstOrDefault(q => q.Name == tup.Item1);
                    Element newMaterial =  new FilteredElementCollector(doc).OfClass(typeof(Material))
                        .FirstOrDefault(q => q.Name == tup.Item2);
                    
                    if (layer.MaterialId == oldMaterial.Id)
                        cs.SetMaterialId(idx, newMaterial.Id);
                }
                idx++;
            }
            wt.SetCompoundStructure(cs);
        }
        t.Commit();
    }
}

#RTCNA wish 9 granted! Place instances of multiple families

Jason asked if the API can be used to “select a bunch of families from a folder and automatically place them in an .rvt

NOTE: For this code to run, you will need to add a reference to the SharpDevelop project as follows: Project – Add Reference – select System.Windows.Forms

public void placeMultipleFamilies()
{
    Document doc = this.ActiveUIDocument.Document;
    
    if (!(doc.ActiveView is ViewPlan))
    {
        TaskDialog.Show("Error","Command must be run from a plan view");
        return;
    }
    
    // use standard Windows File Open dialog to select files
    System.Windows.Forms.OpenFileDialog opendialog = new System.Windows.Forms.OpenFileDialog();
    // let user select multiple files
    opendialog.Multiselect = true;
    // set default directory
    opendialog.InitialDirectory = doc.Application.GetLibraryPaths().Values.First();
    opendialog.Filter = "Revit Families|*.rfa";
    System.Windows.Forms.DialogResult result = opendialog.ShowDialog();
    if (result == System.Windows.Forms.DialogResult.Cancel)
        return;
    
    Level level = doc.ActiveView.GenLevel;
    
    using (Transaction t = new Transaction(doc, "Place families"))
    {
        t.Start();
        double x = 0;
        double y = 0;
        foreach (string file in opendialog.FileNames)
        {
            // load the family
            Family f = null;
            doc.LoadFamily(file, out f);
            
            if (f == null)
                continue;
            
            // get a family symbol (aka family type) from the family
            FamilySymbol fs = f.Symbols.Cast<FamilySymbol>().FirstOrDefault();
            
            doc.Create.NewFamilyInstance(new XYZ(x, y, 0), fs, level, StructuralType.NonStructural);
             
            // increment coordinates for placing next instance
            x = x + 10;
            if (x > 50)
            {
                x = 0;
                y = 10;
            }
        }
        t.Commit();
    }
}

#RTCNA wish 8 granted! Delete unused elevation markers

Want to get rid of these?

Capture

public void deleteUnusedElevations()
{
    Document doc = this.ActiveUIDocument.Document;
    using (Transaction t = new Transaction(doc, "Delete unused elevations"))
    {
        t.Start();
        doc.Delete(new FilteredElementCollector(doc)
                   .OfClass(typeof(ElevationMarker))
                   .Cast<ElevationMarker>()
                   .Where(q => !q.HasElevations())
                   .Select(q => q.Id).ToList());
        t.Commit();
    }
}

#RTCNA wish 7 granted! Isolated 3d view for each workset

Create a 3d view for each workset, and in that view isolate the display of the elements in that workset.

Note that to find worksets we use a FilteredWorksetCollector (not a FilteredElementCollector) and a WorksetKindFilter to get only the user worksets (don’t want view, family, project standard worksets).

public void ViewWorksetIso()
{
    Document doc = this.ActiveUIDocument.Document;
    if (!doc.IsWorkshared)
        return;
    
    // get the 3d view type which is needed when creating 3d views
    ViewFamilyType vft = new FilteredElementCollector(doc)
        .OfClass(typeof(ViewFamilyType))
        .Cast<ViewFamilyType>()
        .FirstOrDefault(q => q.ViewFamily == ViewFamily.ThreeDimensional);
    
    using (Transaction t = new Transaction(doc, "workset view isolation"))
    {
        t.Start();
        
        // loop through all worksets (but only User worksets)
        foreach (Workset wset in new FilteredWorksetCollector(doc).WherePasses(new WorksetKindFilter(WorksetKind.UserWorkset)))
        {
            // create a 3d view
            View3D view = View3D.CreateIsometric(doc, vft.Id);
            
            // set the name of the view to match the name of the workset
            view.Name = "WORKSET - " + wset.Name;
            
            // isolate elements in the view, using a filter to find elements only in this workset
            view.IsolateElementsTemporary(new FilteredElementCollector(doc).WherePasses(new ElementWorksetFilter(wset.Id)).Select(q => q.Id).ToList());
        }
        t.Commit();
    }
}

#RTCNA wish 6 granted! Create assemblies from groups

Here is how to create an assembly and assembly views from a group

public void assemblyFromGroup()
{
    Document doc = this.ActiveUIDocument.Document;
    UIDocument uidoc = this.ActiveUIDocument;
    
    // prompt the user to select a group
    Group group = doc.GetElement(uidoc.Selection.PickObject(ObjectType.Element)) as Group;
    
    View v3d = null;
    View partlist = null;
    View section = null;
    using (Transaction t = new Transaction(doc,"Create Assembly From Group"))
    {
        t.Start();
        
        // ungroup the group, getting the list of elements in the group
        List<ElementId> ids = group.UngroupMembers().ToList();
        
        // get the category of the first element in the group
        // a category needs to be specified when creating the assembly
        Category cat = doc.GetElement(ids.First()).Category;
        
        // create the assembly
        AssemblyInstance ai = AssemblyInstance.Create(doc, ids, cat.Id);
        
        // create the assembly views
        v3d = AssemblyViewUtils.Create3DOrthographic(doc, ai.Id);
        partlist = AssemblyViewUtils.CreatePartList(doc, ai.Id);
        section = AssemblyViewUtils.CreateDetailSection(doc, ai.Id, AssemblyDetailViewOrientation.DetailSectionA);
        t.Commit();
    }
    // activate the new views so they are visible when the command ends
    // The active view can only be changed when there is no open transaction
    uidoc.ActiveView = v3d;
    uidoc.ActiveView = partlist;
    uidoc.ActiveView = section;
}