Creating a mass from Toposurface geometry

Following up this suggestion from Julien, here’s a look at how to create a massing family with geometry built from the mesh of a toposurface.

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

    TopographySurface topo = doc.GetElement(uidoc.Selection.PickObject(ObjectType.Element)) as TopographySurface;

    Mesh mesh = topo.get_Geometry(new Options()).First(q => q is Mesh) as Mesh;

    Document famDoc = app.NewFamilyDocument(@"C:\ProgramData\Autodesk\RAC 2014\Family Templates\English_I\Conceptual Mass\Mass.rft");

    using (Transaction t = new Transaction(famDoc,"Create massing surfaces"))
    {
        t.Start();

        for (int i = 0; i < mesh.NumTriangles; i++)
        {
            MeshTriangle mt = mesh.get_Triangle(i);
            makeForm(famDoc, mt.get_Vertex(0), mt.get_Vertex(1), mt.get_Vertex(2));

            if (i > 0 && i % 100 == 0)
            {
                TaskDialog td = new TaskDialog("Form Counter");
                td.CommonButtons = TaskDialogCommonButtons.Yes|TaskDialogCommonButtons.No;
                td.MainInstruction = i + " out of " + mesh.NumTriangles + " triangles processed. Do you want to continue?";
                if (td.Show() == TaskDialogResult.No)
                    break;
            }
        }
        t.Commit();
    }
    famDoc.LoadFamily(doc);
}

private Form makeForm(Document doc, XYZ pt1, XYZ pt2, XYZ pt3)
{
    Form form = null;

    XYZ u = pt2.Subtract(pt1);
    XYZ v = pt3.Subtract(pt1);
    double area = u.CrossProduct(v).GetLength()/2;
    if (area < 10)
        return null;

    ReferenceArray ra = new ReferenceArray();
    ra.Append(MakeCuveByPoints(doc, pt1, pt2).GeometryCurve.Reference);
    ra.Append(MakeCuveByPoints(doc, pt2, pt3).GeometryCurve.Reference);
    ra.Append(MakeCuveByPoints(doc, pt3, pt1).GeometryCurve.Reference);

    form = doc.FamilyCreate.NewFormByCap(true, ra);

    return form;
}

private CurveByPoints MakeCuveByPoints(Document doc, XYZ ptA, XYZ ptB)
{
    ReferencePointArray rpa = new ReferencePointArray();
    rpa.Append(doc.FamilyCreate.NewReferencePoint(ptA));
    rpa.Append(doc.FamilyCreate.NewReferencePoint(ptB));
    return doc.FamilyCreate.NewCurveByPoints(rpa);
}

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