Net and Gross Wall Area

At Revit Forum it was asked how to get the gross area of a wall. Here’s a bit of code using Document.Regeneration() and Transaction.Rollback() to get the job done.

Capturepublic void NetWallArea()
{
    UIDocument uidoc = this.ActiveUIDocument;
    Document doc = uidoc.Document;
    foreach (Wall w in new FilteredElementCollector(doc).OfClass(typeof(Wall)).Cast<Wall>())
    {
        // get a reference to one of the wall's side faces
        Reference sideFaceRef = HostObjectUtils.GetSideFaces(w, ShellLayerType.Exterior).First();

        // get the geometry object associated with that reference
        Face netFace = w.GetGeometryObjectFromReference(sideFaceRef) as Face;

        // get the area of the face - this area does not include the area of the inserts that cut holes in the face
        double netArea = netFace.Area;

        double grossArea;
        using (Transaction t = new Transaction(doc,"delete inserts"))
        {
            t.Start();

            // delete all family inserts that are hosted by this wall
            foreach (FamilyInstance fi in new FilteredElementCollector(doc).OfClass(typeof(FamilyInstance)).Cast<FamilyInstance>().Where(q => q.Host != null && q.Host.Id == w.Id))
            {
                doc.Delete(fi.Id);
            }
            // regenerate the model to update the geometry with the inserts deleted
            doc.Regenerate();

            // get the gross area (area of the wall face now that the inserts are deleted)
            Face grossFace = w.GetGeometryObjectFromReference(sideFaceRef) as Face;
            grossArea = grossFace.Area;

            // rollback the transaction to restore the model to its original state
            t.RollBack();
        }
        TaskDialog.Show("Areas", "Net = " + netArea + "\nGross = " + grossArea);
    }
}
Advertisements

Getting the area of all major faces of a host

In the last post where the user was prompted to select a single face to get its area. Building on that idea, this sample prompts the user to select a host element (wall, floor, roof, etc). Then it shows the areas of all major (side, top, and/or bottom faces).

For a wall, the interior and exterior faces are combined into a single list with the Concat method. For a floor or roof, Concat is used to create a single list with references to both the top and bottom faces.

public void areaHostMajorFaces()
{
    Document doc = this.Document;    
    UIDocument uidoc = new UIDocument(doc);
    Reference r = uidoc.Selection.PickObject(ObjectType.Element, "Select a face of a wall, roof, or floor");
    Element e = doc.GetElement(r);
    HostObject hostObj = e as HostObject;
    if (hostObj == null)
    {
        TaskDialog.Show("Error", "Element selected is not a host object");
        return;
    }

    IList<Reference> references = null;

    if (hostObj is Wall)
    {
        IList<Reference> exterior = HostObjectUtils.GetSideFaces(hostObj, ShellLayerType.Exterior);
        IList<Reference> interior = HostObjectUtils.GetSideFaces(hostObj, ShellLayerType.Interior);
        references = new List<Reference>(exterior.Concat(interior));
    }
    else if (hostObj is RoofBase || hostObj is Floor)
    {
        IList<Reference> top = HostObjectUtils.GetTopFaces(hostObj);
        IList<Reference> bottom = HostObjectUtils.GetBottomFaces(hostObj);
        references = new List<Reference>(top.Concat(bottom));
    }

    string areas = "Areas of faces of element '" + e.Name + "' (" + e.Id + ")\n";
    foreach (Reference myRef in references)
    {
        Face face = e.GetGeometryObjectFromReference(myRef) as Face;
        areas += face.Area + "\n";
    }
    TaskDialog.Show("Area", areas);
}

Getting the area of a single face

At “Revit in Plain English“, Jay mentions that Revit reports a single area value for a wall, but that he wants that actual area of a specific face.

Here is a quick document macro (not an application macro like in my other samples) to get the area of any face.

public void areaOneFace()
{
    Document doc = this.Document;    
    UIDocument uidoc = new UIDocument(doc);
    Reference myRef = uidoc.Selection.PickObject(ObjectType.Face, "Select a face");
    Element e = doc.GetElement(myRef);
    Face face = e.GetGeometryObjectFromReference(myRef) as Face;
    TaskDialog.Show("Area", "Area of face selected on element '" + e.Name + "' (" + e.Id + ")\n" + face.Area);
}

When the wall is attached to the roof so that the area of each side face is unique, the schedule reports the wall area as 619. With the macro we find that the area of each face is 618.52 and 568.08.

Setting door position relative to nearby walls

A reader sent the image on the right and asked: “can we make door with specific distance to face of waDoorPositionll like 20 cm without every time need to adjust distance in temporary dimension ?”

This seemed like a good excuse to stop writing code dealing with Family Parameters (I was getting tired of writing it, were you getting tired of reading about it?).

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

    // establish an offset of 20cm that will be applied to the door and wall
    // Revit's internal units for distance is feet
    double doorToWallDistance = 20 / (12 * 2.54); // convert 20 cm to 1 ft

    try // try/catch block with PickObject is used to give the user a way to get out of the infinite loop of while (true)
        // PickObject throws an exception when user aborts from selection
    {
        while (true)
        {
            FamilyInstance door = doc.GetElement(uidoc.Selection.PickObject(ObjectType.Element, "Select a door. ESC when finished.").ElementId) as FamilyInstance;
            double doorWidth = door.Symbol.get_Parameter(BuiltInParameter.DOOR_WIDTH).AsDouble();

            Wall sideWall = doc.GetElement(uidoc.Selection.PickObject(ObjectType.Element, "Select a wall. ESC when finished.").ElementId) as Wall;
            double sideWallWidth = sideWall.Width;

            // get the curve that defines the centerline of the side wall
            LocationCurve sideWallLocationCurve = sideWall.Location as LocationCurve;
            Curve sideWallCurve = sideWallLocationCurve.Curve;

            // get the curve that defines the centerline of the wall that hosts the door
            Wall hostWall = door.Host as Wall;
            LocationCurve hostWallLocationCurve = hostWall.Location as LocationCurve;
            Curve hostWallCurve = hostWallLocationCurve.Curve;

            // find the point of intersection of these two wall curves (intersectionXYZ)
            // and the position of this point on the wall that hosts the door (intersectionParam)
            IntersectionResultArray ira = new IntersectionResultArray();
            SetComparisonResult scr = sideWallCurve.Intersect(hostWallCurve, out ira);
            var iter = ira.GetEnumerator();
            iter.MoveNext();
            IntersectionResult ir = iter.Current as IntersectionResult;
            XYZ intersectionXYZ = ir.XYZPoint;
            double intersectionParam = hostWallCurve.Project(intersectionXYZ).Parameter;

            // find the position of the door in its host wall
            LocationPoint doorPoint = door.Location as LocationPoint;
            XYZ doorXYZ = doorPoint.Point;
            double doorParam = hostWallCurve.Project(doorXYZ).Parameter;

            // compute the translation vector between the edge of the door closest to the wall and the side face of the wall
            XYZ translation = null;
            XYZ doorEdgeXYZ = null;
            XYZ intersectionOffsetXYZ = null;
            if (intersectionParam > doorParam)
            {
                intersectionOffsetXYZ = hostWallCurve.Evaluate(intersectionParam - doorToWallDistance - sideWallWidth/2, false);
                doorEdgeXYZ = hostWallCurve.Evaluate(doorParam + doorWidth/2, false);
                translation = intersectionOffsetXYZ.Subtract(doorEdgeXYZ);
            }
            else
            {
                intersectionOffsetXYZ = hostWallCurve.Evaluate(intersectionParam + doorToWallDistance + sideWallWidth/2, false);
                doorEdgeXYZ = hostWallCurve.Evaluate(doorParam - doorWidth/2, false);
                translation = doorEdgeXYZ.Subtract(intersectionOffsetXYZ).Negate();
            }

            // Move the door
            using (Transaction t = new Transaction(doc,"move door"))
            {
                t.Start();
                ElementTransformUtils.MoveElement(doc,door.Id,translation);
                t.Commit();
            }
        }
    }
    catch{}
}

Auto Generate 3D Walls from 2D Line floor plan (with a Selection pre-filter)

Darren asks at AUGI “Can Revit Architecture Auto Generate 3D from 2D floor plan?”

Here’s a little macro to do just that. Run the macro, select these 4 lines, and get these 4 walls.

walls from lines

Most of this should look familiar to code I have explained in previous posts.

The new piece is the ISelectionFilter. This applies a pre-filter to PickObjects so that when the user is prompted to select objects, the Select tool will only allow selections that pass the filter. In this case, the filter is defined so that it will return true only when ModelLines are selected. Nothing will be selected if the user clicks the mouse with the cursor over a wall, window, column, or anything else that it not a model line.

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

    // Get "Level 1"
    Level level = (from l in new FilteredElementCollector(doc).OfClass(typeof(Level)) where l.Name == "Level 1" select l).First() as Level;

    // Selection will be restricted to lines only. Attempts to click on other objects will be ignored
    ISelectionFilter lineFilter = new LineSelectionFilter();

    IList<Reference> lineRefs = uidoc.Selection.PickObjects(ObjectType.Element, lineFilter, "Pick lines");

    using (Transaction t = new Transaction(doc,"Create Walls From Lines"))
    {
        t.Start(); 
        foreach (Reference reference in lineRefs)
        {
            ModelLine modelLine = doc.GetElement(reference) as ModelLine;
            doc.Create.NewWall(modelLine.GeometryCurve, level, false); // false indicates that the wall's Structural property is false
        }
        t.Commit();
    }
}
public class LineSelectionFilter : ISelectionFilter
{
    // determine if the element should be accepted by the filter
    public bool AllowElement(Element element)
    {
        // Convert the element to a ModelLine
        ModelLine line = element as ModelLine;
        // line is null if the element is not a model line
        if (line == null)
        {
            return false;
        }
        // return true if the line is a model line
        return true;
    }

    // references will never be accepeted by this filter, so always return false
    public bool AllowReference(Reference refer, XYZ point)
    {return false;}
}

In the real world you may want to do this with many walls and use a filter or some other mechanism instead of selecting them individually. You would also want something more sophisticated to specify the base constraint of the walls instead of having them all at Level 1. Model lines don’t have a Level property, but you can get their Z location from ModelLine.GeometryCurve.Origin and then find a level with the same elevation.