Spot Coordinates on Conduit Ends

I made an API tool to help place Spot Coordinates at the end of Conduits. If you have lots of conduit this can be tedious to do manually. It can be even more annoying to do manually if the conduit is overlapping so that, like in this example, in looks like in the plan view that there is only one conduit.

Leave a comment or send me an email if this would be useful to you.

before

Before

after

After

Edges from Toposurface Boundaries

The boundaries of Revit toposurfaces are not edges. Among other things, this means that topo boundaries can’t be used to create model lines, don’t work with linework, and can’t be selected to define the path of a sweep.

A reader asked if there is a good way to use the API to generate 3D reference lines from a toposurface that could be used as the path for sweeps to represent curbs and gutters for roads, parking lots and driveways. I wrote this bit of code to create model lines from the boundary points of a selected toposurface, but the result is not great, because many small lines are created where a single arc or spline would be preferable and these model lines will not automatically update when the toposurface changes.

Do any of the Revit Site experts out there have ideas about what a more complete solution might be?

public void topoBoundaryLine()
{
    Document doc = this.ActiveUIDocument.Document;
    UIDocument uidoc = this.ActiveUIDocument;
    Application app = this.Application;
    TopographySurface ts1 = doc.GetElement(uidoc.Selection.PickObject(ObjectType.Element)) as TopographySurface;
    IEnumerable<XYZ> points = ts1.GetPoints().Where(q => ts1.IsBoundaryPoint(q));

    using (Transaction t = new Transaction(doc,"Create Topo Boundary Lines"))
    {
        t.Start();
        XYZ prev = null;
        foreach (XYZ point in points)
        {
            XYZ pt1 = null;
            XYZ pt2 = null;

            if (prev == null)
            {
                pt1 = points.First();
                pt2 = points.Last();
            }
            else
            {
                pt1 = prev;
                pt2 = point;
            }

            Line line = app.Create.NewLineBound(pt1, pt2);                        
            XYZ v = pt1 - pt2;
            double dxy = Math.Abs(v.X) + Math.Abs(v.Y);
            XYZ w = (dxy > 0.0001) ? XYZ.BasisZ : XYZ.BasisY;
            XYZ norm = v.CrossProduct(w).Normalize();
            SketchPlane skplane = doc.Create.NewSketchPlane(app.Create.NewPlane(norm, pt2));
            ModelCurve mc = doc.Create.NewModelCurve(line, skplane);

            prev = point;
        }
        t.Commit();
    }
}

#AU2013 Wish granted – Delete topo points in elevation range

Kelly asked “Is it possible to delete points within a range of elevation values?”

Yes!


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

    double min = 20;
    double max = 30;

    TopographySurface topo = doc.GetElement(uidoc.Selection.PickObject(ObjectType.Element)) as TopographySurface;
    IList<XYZ> pointsToDelete = new List<XYZ>();

    foreach (XYZ pt in topo.GetPoints())
    {
        if (pt.Z > min && pt.Z < max)
            pointsToDelete.Add(pt);
    }

    using (TopographyEditScope tes = new TopographyEditScope(doc, "Topo Edit Scope"))
    {
        tes.Start(topo.Id);

            using (Transaction t = new Transaction(doc, "Update Topo"))
            {
                t.Start();
                topo.DeletePoints(pointsToDelete);
                t.Commit();
            }

        tes.Commit(new myFailuresPreprocessor());
    }
}

public class myFailuresPreprocessor : IFailuresPreprocessor
{
    public FailureProcessingResult PreprocessFailures(FailuresAccessor failuresAccessor)
    {
        return FailureProcessingResult.Continue;
    }
}

#AU2013 Wish granted! Convert multiple dimensions into one

Brok asked “Can you via #Revit API combine individual dims into a single string and remove the old dims?”

Yes!

The tricky part was figuring out how to avoid creating zero-length dimension segments between the references used by adjacent dimensions.

public void dimensionConsolidation()
{
    Document doc = this.ActiveUIDocument.Document;
    Application app = this.Application;
    UIDocument uidoc = new UIDocument(doc);

    IList<ElementId> idsToDelete = new List<ElementId>();
    ReferenceArray dimensionRefsForNewDimension = new ReferenceArray();

    // list of element ids and geometry objects used to weed out duplicate references
    IList<Tuple<ElementId,GeometryObject>> geomObjList = new List<Tuple<ElementId,GeometryObject>>();

    Line line = null;
    DimensionType dimType = null;
    IList<Element> elementList = uidoc.Selection.PickElementsByRectangle();    
    if (elementList.Count == 0)
        return;

    foreach (Element selectedElement in elementList)
    {
        Dimension d = selectedElement as Dimension;
        idsToDelete.Add(d.Id);

        // take the dimension line & dimension type from the first dimension
        if (line == null)
        {
            line = d.Curve as Line;
            dimType = d.DimensionType;
        }

        foreach (Reference dr in d.References)
        {
            Element thisElement = doc.GetElement(dr);
            GeometryObject thisGeomObj = thisElement.GetGeometryObjectFromReference(dr);

            // do not add references to the array if the array already contains a reference
            // to the same geometry element in the same element
            bool duplicate = false;
            foreach (Tuple<ElementId,GeometryObject> myTuple in geomObjList)
            {
                ElementId idInList = myTuple.Item1;
                GeometryObject geomObjInList = myTuple.Item2;
                if (thisElement.Id == idInList && thisGeomObj == geomObjInList)
                {
                    duplicate = true;
                    break;
                }
            }

            if (!duplicate)
            {
                dimensionRefsForNewDimension.Append(dr);
                geomObjList.Add(new Tuple<ElementId, GeometryObject>(thisElement.Id, thisGeomObj));
            }
        }
    }    

    using (Transaction t = new Transaction(doc, "Dimension Consolidation"))
    {
        t.Start();
        Dimension newDim = doc.Create.NewDimension(doc.ActiveView, line, dimensionRefsForNewDimension, dimType);
        doc.Delete(idsToDelete);
        t.Commit();
    }

}

#AU2013 Wish 4 – API-Based Performance Benchmarking

https://twitter.com/PragmaticPrxs says it would be great to have an API-based performance benchmarking tool.

This macro opens every view in the RVT, does some zoom in/zoom out, and iterates through all display styles.  With the Revit 2014 rac_basic_sample_project.rvt, how long does it take to run on your machine?

public void viewPerf()
{
    Document doc = this.ActiveUIDocument.Document;
    Application app = this.Application;
    UIDocument uidoc = new UIDocument(doc);

    Stopwatch sw = new Stopwatch();
    sw.Start();
    int ctr = 0;
    foreach (View v in new FilteredElementCollector(doc)
             .OfClass(typeof(View)).Cast<View>()
             .Where(q => !q.IsTemplate && q.ViewType != ViewType.Internal && q.ViewType != ViewType.ProjectBrowser && q.ViewType != ViewType.Undefined && q.ViewType != ViewType.SystemBrowser && q.ViewType != ViewType.Schedule && q.ViewType != ViewType.ColumnSchedule && q.ViewType != ViewType.PanelSchedule))
    {    
        uidoc.ActiveView = v;                
        uidoc.RefreshActiveView();
        UIView uiview = uidoc.GetOpenUIViews().Cast<UIView>().First(q => q.ViewId == v.Id);
        uidoc.RefreshActiveView();
        uiview.ZoomToFit();
        uidoc.RefreshActiveView();
        uiview.Zoom(2);
        uidoc.RefreshActiveView();
        uiview.Zoom(0.5);
        uidoc.RefreshActiveView();    
        switchDisplayStyles(v, uidoc);

        if (uidoc.GetOpenUIViews().Count > 1)
            uiview.Close();

        ctr++;    
    }
    sw.Stop();
    TaskDialog.Show("Time", sw.Elapsed.ToString());
}

private void switchDisplayStyles(View v, UIDocument uidoc)
{
    if (v.ViewType == ViewType.DraftingView || v.ViewType == ViewType.DrawingSheet || v.ViewType == ViewType.Detail || v.ViewType == ViewType.Legend || v.ViewType == ViewType.Rendering)
        return;

    try
    {
    using (Transaction t = new Transaction(uidoc.Document,"display style"))
    {
        t.Start();
        foreach (DisplayStyle ds in Enum.GetValues(typeof(DisplayStyle)))
        {
            if (ds == DisplayStyle.Undefined || ds == DisplayStyle.Rendering || ds == DisplayStyle.Raytrace)
                continue;

            v.DisplayStyle = ds;
            uidoc.RefreshActiveView();
        }
        t.RollBack();
    }
    }
    catch
    {
        TaskDialog.Show("d",v.ViewType.ToString() + " - " + v.ViewName);
    }
}

#AU2013 Wish #3 – Dockable web browser

Sean asked “Question for you: Is it possible to replace the #Revit Recent Files page with custom HTML, either local file or online?”

There is no straightforward way to replace the Recent Files page (it is built at run-time by Revit). But here are two ways to show a web browser as part of the Revit session.

  1. A modeless, independent dialog with a web browser is showed when Revit starts
  2. A dockable web browser is created after the first Revit file is opened

#AU2013 Wish 2 Granted!

Nancy asked for “auto duplicating and placing views through Design Option set creation”

Here is a macro that creates new views and sheets, with a new view for each design option. Each duplicated view is set to display a different design option and is placed on a sheet in the same location as on the sheet hosting the main model viewport.

public void ViewDuplicateOptions()
{
    Document doc = this.ActiveUIDocument.Document;
    UIDocument uidoc = this.ActiveUIDocument;
    IEnumerable<DesignOption> designOptions = new FilteredElementCollector(doc).OfClass(typeof(DesignOption)).Cast<DesignOption>().Where(q => !q.IsPrimary);
    if (designOptions.Count() == 1)
    {
        TaskDialog.Show("Error","Only one design option exists. No views duplicated.");
        return;
    }

    IList<ViewSheet> newSheets = new List<ViewSheet>();

    IList<ViewSheet> viewSheets = new FilteredElementCollector(doc).OfClass(typeof(ViewSheet)).Cast<ViewSheet>().ToList();

    using (Transaction t = new Transaction(doc,"Duplicate Views by Option"))
    {
        t.Start();
        foreach (ViewSheet vs in viewSheets)
        {
            IEnumerable<Viewport> viewportsOnSheet = new FilteredElementCollector(doc).OfClass(typeof(Viewport)).Cast<Viewport>().Where(q => q.SheetId == vs.Id);
            if (viewportsOnSheet.Count() == 0)
                continue;

            FamilyInstance titleblock = new FilteredElementCollector(doc).OfClass(typeof(FamilyInstance)).OfCategory(BuiltInCategory.OST_TitleBlocks).Cast<FamilyInstance>().First(q => q.OwnerViewId == vs.Id);

            foreach (DesignOption dOpt in designOptions)
            {
                ViewSheet newSheet = ViewSheet.Create(doc, titleblock.GetTypeId());
                newSheet.Name = vs.Name + " - " + dOpt.Name;
                newSheets.Add(newSheet);
                foreach (Viewport vp in viewportsOnSheet)
                {
                    View view = doc.GetElement(vp.ViewId) as View;
                    XYZ vpCenter = vp.GetBoxCenter();
                    View newView = doc.GetElement(view.Duplicate(ViewDuplicateOption.WithDetailing)) as View;
                    newView.Name = view.Name.Replace("{","").Replace("}","") + " - " + dOpt.Name;
                    newView.get_Parameter("Visible In Option").Set(dOpt.Id);
                    Viewport newVp = Viewport.Create(doc, newSheet.Id, newView.Id, vpCenter);
                }
            }
        }
        t.Commit();
    }

    foreach(ViewSheet v in newSheets)
    {
        uidoc.ActiveView = v;
    }
}

#AU2013 Wish 1 Granted!

Matt asked to “duplicate scope box across models”. Open the source file (in this case named “scope boxes.rvt”), then activate the target document and run the macro.

public void CopyScopeBoxBtwDoc()
{
    Document doc = this.ActiveUIDocument.Document;
    Application app = this.Application;

    string filename = "scope boxes.rvt";

    Document sourceDoc = app.Documents.Cast<Document>().FirstOrDefault(q => q.PathName.EndsWith(filename));

    if (sourceDoc == null)
    {
        TaskDialog.Show("Error", "No open document named " + filename);
        return;
    }

    ICollection<ElementId> elementsToCopy = new FilteredElementCollector(sourceDoc).OfCategory(BuiltInCategory.OST_VolumeOfInterest).ToElementIds();

    using (Transaction t = new Transaction(doc,"Copy Scope Box from " + filename))
    {
        t.Start();
        ElementTransformUtils.CopyElements(sourceDoc, elementsToCopy, doc, Transform.Identity, new CopyPasteOptions());
        t.Commit();
    }

}

What would you like to see automated?

Here is the coding challenge for the AU BIM Hackathon. What would you like to see automated?

Code Challenge:  Automate Your Industry

Designers, engineers, and builders are constantly confronted by redundant, time consuming tasks that could easily be mitigated with some expertly applied automation.  This coding challenge asks participants to define an automation problem in the AECO industry and develop a working prototype.

Requirements…

  • Your solution must be coded or scripted (any language is acceptable)

  • Your solution must address an AECO industry automation problem

  • Any software is allowed.

Hypotheticals….

  • Can you speed up model production?

  • Can you enable better data integrations between project collaborators?

  • Can you streamline the feedback loop between design and analysis?