Attach any file (PDF, Word, XLS…) to your Revit file

Download

Purchase a license at PayPal

Advertisements

How to find drafting views with linked CAD

http://bitly.com/revitapi

New video lecture added to the Boost Your BIM Revit API course at Udemy.

Capture

http://bitly.com/revitapi

Store data from any file (PDF, XLS, DOC…) in your RVT

Would you like to store data from any file in your RVT?

An easy way to create multiple drafting views with imported images for every page of a PDF?

The ability to embed any file (from Excel, PDF, Word or anything else) in your RVT?

Find that labeled dimension

Have you ever gotten lost while searching and searching in a complex family for a labeled dimension? You know there is a dimension somewhere labeled “Jamb Width” or “Head Height” or whatever but you don’t know in what view that dimension lives?

Here is a macro that will find a dimension based on the name of its labeled parameter, open the dimension’s view, select the dimension and zoom to it.

dims

Where is Jamb Width?

dim 2

Found it!

public void findLabelView()
{
    Document doc = this.ActiveUIDocument.Document;
    UIDocument uidoc = this.ActiveUIDocument;
    foreach (Dimension dim in new FilteredElementCollector(doc)
        .OfClass(typeof(Dimension))
        .Cast<Dimension>())
    {
        FamilyParameter fp = null;
        try
        {
            fp = dim.FamilyLabel;
        }
        catch
        {
            continue;
        }
        
        if (fp != null && fp.Definition.Name == "Jamb Width")
        {
            uidoc.Selection.SetElementIds(new List<ElementId> { dim.Id});
            View ownerview = doc.GetElement(dim.OwnerViewId) as View;
            uidoc.ActiveView = ownerview;
            UIView uiview = uidoc.GetOpenUIViews().FirstOrDefault(q => q.ViewId == dim.OwnerViewId);
            BoundingBoxXYZ bbox = dim.get_BoundingBox(ownerview);
            uiview.ZoomAndCenterRectangle(bbox.Min, bbox.Max);
            break;
        }
    }
}

 

New in 2019.1 – Attached Detail Group API

The new 2019.1 release has added API for attached detail groups! Thanks Autodesk!

For example:

  • GetAvailableAttachedDetailGroupTypeIds returns the attached detail groups of a group or group type
  • HideAllAttachedDetailGroups & HideAttachedDetailGroups to hide some or or all of a model group’s attached detail groups in a specific view
  • ShowAttachedDetailGroups to show an attached detail group on a model group in a specific view

attached detail 1

On a technical note, GetAvailableAttachedDetailGroupTypeIds returns a C# “set”, a data structure that can store certain values, without any particular order, and no repeated values. A couple good resources to learn more about sets are:

public void GetAvailableAttachedDetailGroupTypeIds()
{
    Document doc = this.ActiveUIDocument.Document;
    UIDocument uidoc = this.ActiveUIDocument;
    Group modelGroup = doc.GetElement(uidoc.Selection.PickObject(ObjectType.Element, "Select Model Group")) as Group;
    HashSet attachedDetailIds = modelGroup.GetAvailableAttachedDetailGroupTypeIds() as HashSet;
    TaskDialog.Show("Attached detail groups", string.Join(Environment.NewLine, attachedDetailIds.Select(q => doc.GetElement(q).Name)));
}
public void HideDetailGroup()
{
    Document doc = this.ActiveUIDocument.Document;
    UIDocument uidoc = this.ActiveUIDocument;
    Group modelGroup = doc.GetElement(uidoc.Selection.PickObject(ObjectType.Element, "Select Model Group")) as Group;
    Group detailGroup = doc.GetElement(uidoc.Selection.PickObject(ObjectType.Element, "Select detail group to hide")) as Group;
    using (Transaction t = new Transaction(doc, "hide detail groups"))
    {
        t.Start();
        modelGroup.HideAttachedDetailGroups(doc.ActiveView, detailGroup.GroupType.Id);
        t.Commit();
    }
}
public void ShowDetailGroup()
{
    Document doc = this.ActiveUIDocument.Document;
    UIDocument uidoc = this.ActiveUIDocument;
    Group modelGroup = doc.GetElement(uidoc.Selection.PickObject(ObjectType.Element, "Select Model Group")) as Group;
    Element detailGroupType = new FilteredElementCollector(doc).OfClass(typeof(GroupType))
        .OfCategory(BuiltInCategory.OST_IOSAttachedDetailGroups)
        .FirstOrDefault(q => q.Name == "Door Dimensions A6");
        
    using (Transaction t = new Transaction(doc, "show detail group"))
    {
        t.Start();
        modelGroup.ShowAttachedDetailGroups(doc.ActiveView, detailGroupType.Id);
        t.Commit();
    }
}

 

#BILTNA2018 Wish granted: Select detail lines by Line Type

It would be nice if we could right click on a specific linetype and “select all instances” in view instead of having to select everything and then filter them out.

public void selectAllLineStyle()
{
    Document doc = this.ActiveUIDocument.Document;
    UIDocument uidoc = this.ActiveUIDocument;
    DetailLine line = doc.GetElement(uidoc.Selection.PickObject(ObjectType.Element, "Select detail line")) as DetailLine;
    
    // if you want only the lines in the current view, add "doc.ActiveView.Id" to the FilteredElementCollector
    List lines = new FilteredElementCollector(doc)
        .OfClass(typeof(CurveElement))
        .OfCategory(BuiltInCategory.OST_Lines)
        .Where(q => q is DetailLine)
        .Cast()
        .Where(q => q.LineStyle.Id == line.LineStyle.Id)
        .ToList();
    uidoc.Selection.SetElementIds(lines.Select(q => q.Id).ToList());
}

#BILTNA Wish 5: Isolate with Fade

Isolate command should fade and lock all other objects instead of hiding them completely. What use can i find in seeing an object out of context? I can’t compare it visually to anything. If i want to modify it and can’t relate any modification to any context (because it’s hidden)

public void isolateFade()
{
    Document doc = this.ActiveUIDocument.Document;
    UIDocument uidoc = this.ActiveUIDocument;
    ElementId elementIdToIsolate = null;
    try
    {
        elementIdToIsolate = uidoc.Selection.PickObject(ObjectType.Element, "Select element or ESC to reset the view").ElementId;
    }
    catch
    {
        
    }
    
    OverrideGraphicSettings ogsFade = new OverrideGraphicSettings();
    ogsFade.SetSurfaceTransparency(50);
    ogsFade.SetSurfaceForegroundPatternVisible(false);
    ogsFade.SetSurfaceBackgroundPatternVisible(false);
    ogsFade.SetHalftone(true);
    
    OverrideGraphicSettings ogsIsolate = new OverrideGraphicSettings();
    ogsIsolate.SetSurfaceTransparency(0);
    ogsIsolate.SetSurfaceForegroundPatternVisible(true);
    ogsIsolate.SetSurfaceBackgroundPatternVisible(true);
    ogsIsolate.SetHalftone(false);
    
    using (Transaction t = new Transaction(doc, "Isolate with Fade"))
    {
        t.Start();
        foreach (Element e in new FilteredElementCollector(doc, doc.ActiveView.Id).WhereElementIsNotElementType())
        {
            if (e.Id == elementIdToIsolate || elementIdToIsolate == null)
                doc.ActiveView.SetElementOverrides(e.Id, ogsIsolate);
           else
                doc.ActiveView.SetElementOverrides(e.Id, ogsFade);
        }
        t.Commit();
    }
}

#BILTNA Wish 4: Spell check for schedules

Why doesn’t Revit check spelling in schedules? No good reason that I know of, so here’s an investigation into how it might be done with the API. There’s also some discussion of JSON deserialization and communicating with the Bing Spell Check web API.

spellcheck

public void scheduleSpellCheck()
{
    Document doc = this.ActiveUIDocument.Document;
    ViewSchedule viewSchedule = doc.ActiveView as ViewSchedule;
    ViewSheet viewSheet = doc.ActiveView as ViewSheet;
    if (viewSchedule == null && viewSheet == null)
    {
        TaskDialog.Show("Error", "Active a schedule or sheet before running this command");
        return;
    }
    SectionType sectionType = SectionType.Body;
    List<string> scheduleText = new List<string>();
    
    List schedules = new List();
    if (viewSheet != null)
    {
        schedules = new FilteredElementCollector(doc).OfClass(typeof(ScheduleSheetInstance)).Cast()
            .Where(q => q.OwnerViewId == viewSheet.Id)
            .Select(q => doc.GetElement(q.ScheduleId) as ViewSchedule)
            .Where(q => !q.IsTitleblockRevisionSchedule).ToList();
    }
    else
    {
        schedules.Add(viewSchedule);
    }
    
    foreach (ViewSchedule vs in schedules)
    {
        scheduleText.Add(vs.Name);
        for (int i = 0; i < getRowColumnCount(vs, sectionType, true); i++)
        {
            for (int j = 0; j < getRowColumnCount(vs, sectionType, false); j++)
            {
                try
                {
                    scheduleText.Add(vs.GetCellText(sectionType, i, j));
                }
                catch (Exception ex)
                {
                    
                }
            }
        }
        SpellCheck(string.Join(" - ", scheduleText));
    }
}

 static async void SpellCheck(string text)
{
    HttpClient client = new HttpClient();
    client.DefaultRequestHeaders.Add("Ocp-Apim-Subscription-Key", key);

    // The following headers are optional, but it is recommended they be treated as required.
    // These headers help the service return more accurate results.
    //client.DefaultRequestHeaders.Add("X-Search-Location", ClientLocation);
    //client.DefaultRequestHeaders.Add("X-MSEdge-ClientID", ClientId);
    //client.DefaultRequestHeaders.Add("X-MSEdge-ClientIP", ClientIp);

    HttpResponseMessage response = new HttpResponseMessage();
    string uri = host + path + params_;

    List<KeyValuePair<string, string>> values = new List<KeyValuePair<string, string>>();
    values.Add(new KeyValuePair<string, string>("text", text));

    using (FormUrlEncodedContent content = new FormUrlEncodedContent(values))
    {
        content.Headers.ContentType = new MediaTypeHeaderValue("application/x-www-form-urlencoded");
        response = await client.PostAsync(uri, content);
    }

    string client_id;
    IEnumerable<string> header_values = new List<string>();
    if (response.Headers.TryGetValues("X-MSEdge-ClientID", out header_values))
    {
        client_id = header_values.First();
        Console.WriteLine("Client ID: " + client_id);
    }

    string results = await response.Content.ReadAsStringAsync(); 
    TaskDialog.Show("JSON", JsonPrettyPrint(results));
    FlaggedTokenList flaggedTokenList = new JavaScriptSerializer().Deserialize(results);
    List<string> output = new List<string>();
    foreach (FlaggedToken flaggedToken in flaggedTokenList.flaggedTokens)
    {
        List<string> suggestions = flaggedToken.suggestions.Select(q => q.suggestion.ToString()).ToList();
        if (suggestions.Count == 0)
            continue;
        output.Add(flaggedToken.token + " : " + string.Join(",", suggestions));
    }
    TaskDialog.Show("output", string.Join(Environment.NewLine, output));
}

static string host = "https://api.cognitive.microsoft.com";
static string path = "/bing/v7.0/spellcheck?";

// For a list of available markets, go to:
// https://docs.microsoft.com/rest/api/cognitiveservices/bing-autosuggest-api-v7-reference#market-codes
static string params_ = "mkt=en-US&mode=proof";

// NOTE: Replace this example key with a valid subscription key.
static string key = "d46d0d618a7343dabe8a4355e9d30187";
         
class FlaggedTokenList
{
    public List flaggedTokens { get; set; }
}

class FlaggedToken
{
    public string token { get; set; }
    public List suggestions { get; set; }
}    

class Suggestion
{
    public string suggestion { get; set; }
    public double score { get; set; }
}

        
private int getRowColumnCount(ViewSchedule view, SectionType sectionType, bool countRows)
{
    int ctr = 1;
    // loop over the columns of the schedule
    while (true)
    {
        try // GetCellText will throw an ArgumentException is the cell does not exist
        {
            if (countRows)
                view.GetCellText(sectionType, ctr, 1);
            else
                view.GetCellText(sectionType, 1, ctr);
            ctr++;
        }
        catch (Autodesk.Revit.Exceptions.ArgumentException)
        {
            return ctr;
        }
    }
}    

static string JsonPrettyPrint(string json)
{
    if (string.IsNullOrEmpty(json))
    {
        return string.Empty;
    }

    json = json.Replace(Environment.NewLine, "").Replace("\t", "");

    StringBuilder sb = new StringBuilder();
    bool quote = false;
    bool ignore = false;
    char last = ' ';
    int offset = 0;
    int indentLength = 3;

    foreach (char ch in json)
    {
        switch (ch)
        {
            case '"':
                if (!ignore) quote = !quote;
                break;
            case '\\':
                if (quote && last != '\\') ignore = true;
                break;
        }

        if (quote)
        {
            sb.Append(ch);
            if (last == '\\' && ignore) ignore = false;
        }
        else
        {
            switch (ch)
            {
                case '{':
                case '[':
                    sb.Append(ch);
                    sb.Append(Environment.NewLine);
                    sb.Append(new string(' ', ++offset * indentLength));
                    break;
                case '}':
                case ']':
                    sb.Append(Environment.NewLine);
                    sb.Append(new string(' ', --offset * indentLength));
                    sb.Append(ch);
                    break;
                case ',':
                    sb.Append(ch);
                    sb.Append(Environment.NewLine);
                    sb.Append(new string(' ', offset * indentLength));
                    break;
                case ':':
                    sb.Append(ch);
                    sb.Append(' ');
                    break;
                default:
                    if (quote || ch != ' ') sb.Append(ch);
                    break;
            }
        }
        last = ch;
    }

    return sb.ToString().Trim();
}

#BILTNA2018 Wish 3 Granted: Level remap to allow safe level deletion

The problem

If you delete a level in Revit, it deletes all objects on that level – no warning is given.

There should be a safe way of finding objects that are assigned to that level and reassigning them to a different level.

Perhaps Revit could automatically assign them to the level below?

Its a real problem if you create too many levels at the beginning of a project, you cant then delete some as you’ll lose information.

The solution – an API command to remap elements so they no longer reference the selected level
public void levelRemap()
{
    Document doc = this.ActiveUIDocument.Document;
    UIDocument uidoc = this.ActiveUIDocument;
    Level level = doc.GetElement(uidoc.Selection.PickObject(ObjectType.Element, "Select level")) as Level;
    
    Level levelBelow = new FilteredElementCollector(doc)
        .OfClass(typeof(Level))
        .Cast<Level>()
        .OrderBy(q => q.Elevation)
        .Where(q => q.Elevation <= level.Elevation)
        .FirstOrDefault();
    
    if (levelBelow == null)
    {
        TaskDialog.Show("Error", "No level below " + level.Elevation);
        return;
    }
    
    List<string> paramsToAdjust = new List<string> { "Base Offset", "Sill Height", "Top Offset"};
    
    List<Element> elements = new FilteredElementCollector(doc).WherePasses(new ElementLevelFilter(level.Id)).ToList();
    using (Transaction t = new Transaction(doc, "Level Remap"))
    {
        t.Start();
        foreach (Element e in elements)
        {
            foreach (Parameter p in e.Parameters)
            {
                if (p.StorageType != StorageType.ElementId || p.IsReadOnly)
                    continue;
                if (p.AsElementId() != level.Id)
                    continue;
                
                double elevationDiff = level.Elevation - levelBelow.Elevation;
                
                p.Set(levelBelow.Id);
                
                foreach (string paramName in paramsToAdjust)
                {
                    Parameter pToAdjust = e.LookupParameter(paramName);
                    if (pToAdjust != null && !pToAdjust.IsReadOnly)
                        pToAdjust.Set(pToAdjust.AsDouble() + elevationDiff);
                }
            }
        }
        t.Commit();
    }
}

#BILTNA2018 Wish granted: Align a straight wall with a curved grid

The problem:

The solution:

public void alignWallToCurvedGrid()
{
    UIDocument uidoc = this.ActiveUIDocument;
    Document doc = this.ActiveUIDocument.Document;
    Wall wall = doc.GetElement(uidoc.Selection.PickObject(ObjectType.Element, "Select wall")) as Wall;
    Grid grid = doc.GetElement(uidoc.Selection.PickObject(ObjectType.Element, "Select grid")) as Grid;
    Curve gridCurve = grid.Curve;
    Line wallLine = (((LocationCurve)wall.Location).Curve as Line);
    XYZ wallDirection = wallLine.Direction;
    XYZ wallPerpendicular = XYZ.BasisZ.CrossProduct(wallDirection);
    
    XYZ intersection0 = intersect(wallLine.GetEndPoint(0), wallPerpendicular, gridCurve);
    XYZ intersection1 = intersect(wallLine.GetEndPoint(1), wallPerpendicular, gridCurve);
    XYZ intersectionMid = intersect(wallLine.Evaluate(0.5, true), wallPerpendicular, gridCurve);
    
    Arc arc = Arc.Create(intersection0, intersection1, intersectionMid);
    
    List wallChildren = new FilteredElementCollector(doc)
             .OfClass(typeof(FamilyInstance))
             .Cast()
        .Where(q => q.Host.Id == wall.Id).ToList();
    
    using (Transaction t = new Transaction(doc, "Wall to Curve"))
    {
        t.Start();
        Wall newWall = Wall.Create(doc, arc, wall.LevelId, false);
        newWall.WallType = doc.GetElement(wall.GetTypeId()) as WallType;
        foreach (FamilyInstance fi in wallChildren)
        {
            XYZ fiPoint = (fi.Location as LocationPoint).Point;
            XYZ fiIntersection = intersect(fiPoint, wallPerpendicular, gridCurve);
            if (fiIntersection != null)
            {
                FamilyInstance fiNew = doc.Create.NewFamilyInstance(fiIntersection, 
                fi.Symbol,
                newWall,
                doc.GetElement(fi.LevelId) as Level,
                StructuralType.NonStructural);
                
                if (fi.FacingFlipped)
                    fiNew.flipFacing();
                if (fi.HandFlipped)
                    fiNew.flipHand();
            }
        }
        doc.Delete(wall.Id);
        t.Commit();
    }
}

private XYZ intersect(XYZ point, XYZ direction, Curve curve)
{
    Line unbound = Line.CreateUnbound(new XYZ(point.X, point.Y, curve.GetEndPoint(0).Z), direction);
    IntersectionResultArray ira = null;
    unbound.Intersect(curve, out ira);
    if (ira == null)
    {
        TaskDialog td = new TaskDialog("Error");
        td.MainInstruction = "no intersection";
        td.MainContent = point.ToString() + Environment.NewLine + direction.ToString();
        td.Show();
            
        return null;
    }
    IntersectionResult ir = ira.Cast().First();
    return ir.XYZPoint;
}

#BILTNA2018 Wish granted: Remove warning when modifying type parameter values in schedule

This sample shows how to “Remove warning/pop-up when modifying type parameter values within a schedule” using the DialogBoxShowing event.

Please remove the warning/pop-up that appears when editing scheduled fields that are tied to type parameters.  This is EXTREMELY annoying when entering data for our MEP schedules.  I.E. Plumbing Fixtures, Air Devices, Lighting Fixtures, etc.

The popup reads:

“This change will be applied to all elements of type _______.”

In instances where we are filling out a schedule, we are forced to enter our data, press enter or click out of the cell, and then respond to this popup. EVERY TIME

public void registerWarningSuppresion()
{
    Document doc = this.ActiveUIDocument.Document;   
    Application app = doc.Application;
    UIApplication uiapp = new UIApplication(app);
    uiapp.DialogBoxShowing += new EventHandler(dismissTaskDialog);
}

private void dismissTaskDialog(object sender, DialogBoxShowingEventArgs args)
{
    TaskDialogShowingEventArgs e = args as TaskDialogShowingEventArgs;
    if (e == null)
        return;
    if (e.Message.StartsWith("This change will be applied to all elements of type"))
    {
        e.OverrideResult((int)TaskDialogCommonButtons.Ok);
    }
}

Learn the Revit API – new Project Units lecture

A new lecture showing how to use the Revit API to get, set, and modify Project Units has been added to the Learn the Revit API video course

Sign up today, save 25% off the course’s regular price, and learn how you can save time, reduce errors, and boost your BIM with the Revit API

Learn the Revit API – new lecture

A new lecture showing how to collect instance any type data – in this case, area and keynote values – from all elements in your RVT has been added to the Learn the Revit API video course

Sign up today, save 25% off the course’s regular price, and learn how you can save time, reduce errors, and boost your BIM with the Revit API

Revit Lookup 2019 installer

The wonderful Revit Lookup tool that let’s you peek under the hood into your Revit model has been updated by Autodesk

You can download the DLL and ADDIN files or use this Windows Installer that I’ve built to install it on your computer.

What do you want in the ADSK Cloud?

Forge” is Autodesk’s cloud-based developer platform, and Autodesk is hosting a Forge Accelerator in Boston from April 30-May 4.

So what is something you’d like to do with Revit models or other BIM data in the cloud? Leave some suggestions and maybe something nifty will come soon the Boost Your BIM labs.

Schedule slope of all roof or floor faces

A Revit user asks:

Is there any way to extract a Slope or angle of planar face through revit Api. I have modified flat roof by modifying sub elements, and would like to collect all surface angle or slopes, since we need to validate if all roof surfaces are in 2,5% inclinations. Is there anything which can be used through API for automatic evaluations of these conditions?

Yes, there is!

If you’d like this information to update when the floor is deleted or its geometry changes, that can be done with Dynamic Model Update.

public void GetSlopes()
{
    UIDocument uidoc = this.ActiveUIDocument;
    Document doc = uidoc.Document;
    Reference hostReference = uidoc.Selection.PickObject(ObjectType.Element, "Select floor or roof");
    HostObject host = doc.GetElement(hostReference) as HostObject;
    IList references = HostObjectUtils.GetTopFaces(host);
    
    TextNoteOptions textNoteOptions =  new TextNoteOptions();
    textNoteOptions.TypeId = new FilteredElementCollector(doc).OfClass(typeof(TextNoteType)).FirstOrDefault().Id;
        
    using (Transaction t = new Transaction(doc, "Slopes"))
    {
        t.Start();
        foreach (Reference r in references)
        {
            PlanarFace face = host.GetGeometryObjectFromReference(r) as PlanarFace;
            XYZ normal = face.FaceNormal;
            double angleRadians = normal.AngleTo(XYZ.BasisZ);
            double angleDegrees = angleRadians * 180 / Math.PI;
            XYZ origin = face.Origin;
            string slopeString = Math.Round((angleDegrees), 3).ToString();
            makePoint(doc, origin, slopeString, 1);
            TextNote.Create(doc, doc.ActiveView.Id, origin, slopeString, textNoteOptions);
        }
        t.Commit();
    }
    
}

public FamilyInstance makePoint(Document doc, XYZ pt, string comment, double radius)
{
    if (pt == null)
        return null;

    FamilyInstance ret = null;

    using (Transaction t = new Transaction(doc, "t"))
    {
        bool started = false;
        try
        {
            t.Start();
            started = true;
        }
        catch
        { }

        FamilySymbol fs = new FilteredElementCollector(doc)
            .OfClass(typeof(FamilySymbol))
            .Cast()
            .FirstOrDefault(q => q.Family.Name == "DiagPoint" && q.Name == "DiagPoint");
        
        if (fs != null)
        {
            if (!fs.IsActive)
                fs.Activate();
            ret = (FamilyInstance)doc.Create.NewFamilyInstance(pt, fs, Autodesk.Revit.DB.Structure.StructuralType.NonStructural);
            ret.LookupParameter("Comments").Set(comment);
            ret.LookupParameter("Radius").Set(radius);
        }
        if (started)
            t.Commit();
    }
    return ret;
}

Placing face-based families on instance faces (GetInstanceGeometry or GetSymbolGeometry???)

You want to place a “marker” family instance where ducts meet columns.

If the marker is an un-hosted generic model (the sphere) everything is great. But if the marker is face-based (the hexagonal disk), why are they floating in space?

Capture1

The disks are in the wrong place because the code below uses GetInstanceGeometry, The Revit API Developers Guide tells us:

GetSymbolGeometry() returns the geometry represented in the coordinate system of the family. Use this, for example, when you want a picture of the “generic” table without regards to the orientation and placement location within the project. This is also the only overload which returns the actual Revit geometry objects to you, and not copies. This is important because operations which use this geometry as input to creating other elements (for example, dimensioning or placement of face-based families) require the reference to the original geometry.

GetInstanceGeometry() returns the geometry represented in the coordinate system of the project where the instance is placed. Use this, for example, when you want a picture of the specific geometry of the instance in the project (for example, ensuring that tables are placed parallel to the walls of the room). This always returns a copy of the element geometry, so while it would be suitable for implementation of an exporter or a geometric analysis tool, it would not be appropriate to use this for the creation of other Revit elements referencing this geometry.

public void DuctColumnIntersect()
{
	Document doc = this.ActiveUIDocument.Document;
	UIDocument uidoc = this.ActiveUIDocument;
	
	Options option = new Options();
	option.ComputeReferences = true; // needed to place a family instance on a face of the column
	
	using (Transaction t = new Transaction(doc, "Duct Column Intersection"))
	{
		t.Start();			
		foreach (FamilyInstance columnInstance in new FilteredElementCollector(doc)
		         .OfClass(typeof(FamilyInstance))
		         .OfCategory(BuiltInCategory.OST_Columns))
		{				
			BoundingBoxXYZ bboxColumn = columnInstance.get_BoundingBox(null);

			List ducts = new FilteredElementCollector(doc)
				.OfClass(typeof(Duct))
				.OfCategory(BuiltInCategory.OST_DuctCurves)
				.WherePasses(new BoundingBoxIntersectsFilter(new Outline(bboxColumn.Min, bboxColumn.Max), 0.01))
				.Cast().ToList();
			
			GeometryInstance geomInst = columnInstance.get_Geometry(option)
				.Cast()
				.FirstOrDefault();
			Solid columnSolid = geomInst.GetInstanceGeometry()
				.Cast()
				.Where(q => q is Solid)
				.Cast()
				.FirstOrDefault(q => q.Faces.Size > 0);
								
			foreach (Face f in columnSolid.Faces)
			{
				foreach (Duct duct in ducts)
				{
					Curve ductCurve = ((LocationCurve)duct.Location).Curve;							
					IntersectionResultArray ira = null;
					SetComparisonResult scr = f.Intersect(ductCurve, out ira);
					if (ira == null)
						continue;
					IntersectionResult ir = ira.Cast().FirstOrDefault();
					if (ir == null)
						continue;
					XYZ pt = ir.XYZPoint;
					
					makePoint(doc, pt);
					makeHostedInstance(doc, pt, f);
				}
			}
		}
		t.Commit();
	}
}

private void makeHostedInstance(Document doc, XYZ pt, Face face)
{
    FamilySymbol fs = new FilteredElementCollector(doc)
    	.OfClass(typeof(FamilySymbol))
    	.Cast()
    	.FirstOrDefault(q => q.Family.FamilyCategory.Id.IntegerValue == (int)BuiltInCategory.OST_GenericModel && q.Family.Name == "hex" && q.Name == "hex");
    if (fs == null)
    	return;
    if (!fs.IsActive)
    	fs.Activate();
    FamilyInstance fi = (FamilyInstance)doc.Create.NewFamilyInstance(face, pt, XYZ.BasisZ, fs);
}

private void makePoint(Document doc, XYZ pt)
{
    FamilySymbol fs = new FilteredElementCollector(doc)
    	.OfClass(typeof(FamilySymbol))
    	.Cast()
    	.FirstOrDefault(q => q.Family.FamilyCategory.Id.IntegerValue == (int)BuiltInCategory.OST_GenericModel && q.Family.Name == "DiagPoint" && q.Name == "DiagPoint");
    if (fs == null)
    	return;
    if (!fs.IsActive)
    	fs.Activate();
	FamilyInstance point = (FamilyInstance)doc.Create.NewFamilyInstance(pt, fs, Autodesk.Revit.DB.Structure.StructuralType.NonStructural);
}

Here is the modified code that we need to correctly place the face-based disks on the column faces.

  1. Use GetSymbolGeometry instead of GetInstanceGeometry when getting the Solid of the column. When we do this, the faces of the solid will be the faces of the symbol (as they must be to place family instances on those faces)
  2. Instead of using the duct’s location curve as-is (which will not intersect any faces of the symbol geometry), I create a new line from the endpoints of the duct’s curve after applying the inverse of the column’s transform to the endpoints. This new line will be in the symbol’s coordinate system so it can be used for the Face.Intersect test with the symbol faces.
  3. When we find the point where this transformed duct curve and symbol face intersect, we then need to apply the column instance’s transform to those intersection points. This moves the intersection point from the symbol’s coordinate system to the model’s coordinate system.
Solid columnSolid = geomInst.GetSymbolGeometry()
	.Cast()
	.Where(q => q is Solid)
	.Cast()
	.FirstOrDefault(q => q.Faces.Size > 0);

Transform transform = columnInstance.GetTransform();

foreach (Duct duct in ducts)
{
	Curve ductCurve = ((LocationCurve)duct.Location).Curve;
	Line lineTranformed = Line.CreateBound(transform.Inverse.OfPoint(ductCurve.GetEndPoint(0)), transform.Inverse.OfPoint(ductCurve.GetEndPoint(1)));
	
	foreach (Face f in columnSolid.Faces)
	{
		IntersectionResultArray ira = null;
		SetComparisonResult scr = f.Intersect(lineTranformed, out ira);
		if (ira == null)
			continue;
		IntersectionResult ir = ira.Cast().FirstOrDefault();
		if (ir == null)
			continue;
		XYZ pt = ir.XYZPoint;
		
		makePoint(doc, transform.OfPoint(pt));
		makeHostedInstance(doc, transform.OfPoint(pt), f);
	}
}

 

Hurray!

Capture3

Quick Tip – Change those GUIDs!

Do you like to copy/paste code from this blog, http://thebuildingcoder.typepad.com, http://spiderinnet.typepad.com/ or wherever? Don’t we all?

But remember, when you do this it is a good idea to change any GUIDs because you never know who else is copying that code and what collisions might result from GUID duplication.

For example:

http://thebuildingcoder.typepad.com/blog/2013/05/a-simpler-dockable-panel-sample.html

 DockablePaneId dpid = new DockablePaneId(
      new Guid( "{D7C963CE-B7CA-426A-8D51-6E8254D21157}" ) );

Its not RTCNA wishlist time!

Because it is wishlist time!

What is this all about? Check out https://boostyourbim.wordpress.com/category/api-wish/rtc/ to see previous wishes granted using the API to make Revit even better that it already is.

Submit your wishes as comments here or tweet to @BoostYourBIM with hashtags and (because what’s a conference without dueling hashtags)

Revit Lookup 2018 install

The wonderful Revit Lookup utility has been updated by Autodesk. You can get the 2018 source code at https://github.com/jeremytammik/RevitLookup/releases/tag/2018.0.0.0

If you don’t want to deal with source code and just want to use the tool, here is an installer for the compiled & signed DLL ready, courtesy of Boost Your BIM – https://drive.google.com/file/d/182W00Mk5Hj1FMHAo-xVnoFYlJ_s2Swrw/view