Filter Rule data – where is it hiding?

It is in there, but you’ve got to dig into some sub-classes and static functions.

This doesn’t implement every case, hopefully is enough to shed some light on how it works.

public void ListFilters()
{
    Document doc = this.ActiveUIDocument.Document;
    foreach (ParameterFilterElement pfe in new FilteredElementCollector(doc).OfClass(typeof(ParameterFilterElement)).Cast<ParameterFilterElement>())
    {        
        string ruleData = "";                    
        string categories = "";
            
        foreach (ElementId catid in pfe.GetCategories())
        {
            categories += doc.Settings.Categories.get_Item(((BuiltInCategory)catid.IntegerValue)).Name + ",";
        }
                 
        foreach (FilterRule rule in pfe.GetRules())
        {
            string comparator = "";
            string ruleValue = "";

            if (rule is FilterDoubleRule)
            {
                FilterDoubleRule fdr = rule as FilterDoubleRule;
                
                if (fdr.GetEvaluator().GetType().Equals(typeof(FilterNumericLess)))
                    comparator = "<";
                else if (fdr.GetEvaluator().GetType().Equals(typeof(FilterNumericGreater)))
                    comparator = ">";
                
                ruleValue = fdr.RuleValue.ToString();

            }
            else if (rule is FilterStringRule)
            {
                FilterStringRule fsr = rule as FilterStringRule;
                
                if (fsr.GetEvaluator().GetType().Equals(typeof(FilterStringBeginsWith)))
                    comparator = "starts with";
                else if (fsr.GetEvaluator().GetType().Equals(typeof(FilterStringEndsWith)))
                    comparator = "ends with";
                else if (fsr.GetEvaluator().GetType().Equals(typeof(FilterStringEquals)))
                    comparator = "=";
                else if (fsr.GetEvaluator().GetType().Equals(typeof(FilterStringContains)))
                    comparator = "contains";
                
                ruleValue = fsr.RuleString;
            }
            else if (rule is FilterIntegerRule)
            {
                FilterIntegerRule fir = rule as FilterIntegerRule;
                
                if (fir.GetEvaluator().GetType().Equals(typeof(FilterNumericEquals)))
                    comparator = "=";
                else if (fir.GetEvaluator().GetType().Equals(typeof(FilterNumericGreater)))
                    comparator = ">";
                
                // some parameters store an integer but the UI shows a text string
                // this text comes from the value of an enum such as WallFunction
                if (((BuiltInParameter)ParameterFilterElement.GetRuleParameter(rule).IntegerValue) == BuiltInParameter.FUNCTION_PARAM)
                {
                    ruleValue = ((WallFunction)fir.RuleValue).ToString();
                }
                else
                    ruleValue = fir.RuleValue.ToString();
            }
            
            string paramName = "";
            if (ParameterFilterElement.GetRuleParameter(rule).IntegerValue < 0)
                paramName = LabelUtils.GetLabelFor((BuiltInParameter)ParameterFilterElement.GetRuleParameter(rule).IntegerValue);
            else
                paramName = doc.GetElement(ParameterFilterElement.GetRuleParameter(rule)).Name;

            
            ruleData += "'" + paramName + "' " +
                comparator + " " +
                "'" + ruleValue.ToString() + "'" +
                Environment.NewLine;
        }
        TaskDialog td = new TaskDialog("Rule");
        td.MainInstruction = "Filter name: " + pfe.Name;
        td.MainContent = "Categories: " + categories + Environment.NewLine + Environment.NewLine + ruleData;
        td.Show();
    }
}

Revit Lookup 2017 Installer

If you want to experience the joys of Revit Lookup (the interactive Revit BIM database exploration tool) on Revit 2017 without downloading the source code and compiling it yourself, you can download an installer for it at https://drive.google.com/open?id=0BwszsfY3OsZHSG5yaGdhejcyNkk

Capture

Code Signing For Revit 2017

In 2017, Autodesk really wants developers to sign their DLLs (http://thebuildingcoder.typepad.com/blog/2016/04/whats-new-in-the-revit-2017-api.html#2.4)

If you don’t, Revit will show this scary dialog on startup

not signed

Dealing with this was a fairly annoying process, so to hopefully make it easier for others, here is what I did.

  1. Bought a 5 year certificate from http://codesigning.ksoftware.net/ for $365
  2. Sent them a bunch of documentation to prove that I am who I say I am (including signing up for a free listing at http://www.yellowpages.com/allston-ma/mip/boost-your-bim-526332700?lid=526332700)
  3. Got my “certificate” and generated a PFX file (if you have questions about this step, post in the comments and I will add more info)
  4. Added this to the Visual Studio post build event
    “C:\Program Files (x86)\Windows Kits\8.0\bin\x64\signtool.exe” sign /f “C:\Users\harry_000\Documents\Boost Your BIM\BoostYourBIM.pfx” /t “http://timestamp.comodoca.com/authenticode&#8221; /p <password> /v $(TargetPath)

The result is that the DLL has a signature as shown below.

properties

 

Find Column Base Offset Values

For someone who asked: “I’m trying to get the offset values. How should I proceed?”

public void columns()
{
    Document doc = this.ActiveUIDocument.Document;
    FilteredElementCollector collector = new FilteredElementCollector(doc)
        .OfClass(typeof(FamilyInstance))
        .WhereElementIsNotElementType()
        .OfCategory(BuiltInCategory.OST_StructuralColumns);
    foreach (Element e in collector)
    {
        Parameter baseOffsetParameter = e.get_Parameter(BuiltInParameter.FAMILY_BASE_LEVEL_OFFSET_PARAM);    
        double baseOffsetValue = baseOffsetParameter.AsDouble();
    }
}    

Transform that Family Instance Geometry

Oh, if I had a nickel for every time I forgot (and later remembered) that geometry of family instances needs to be transformed like this…

Transform transform = null;
if (elem is FamilyInstance)
{
    FamilyInstance fi = elem as FamilyInstance;
    transform = fi.GetTransform();
}
                    
PlanarFace pf = elem.GetGeometryObjectFromReference(r) as PlanarFace;
if (pf == null)
    continue;
XYZ norm = pf.FaceNormal;
if (transform != null)
{
    norm = transform.OfVector(norm);
}

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