Is Wall Supported?

Hi,
Is it possible to check if a wall is supported by a roof, floor, beam, another wall or structure from a linked model? Sometimes there will be a tolerance gap of 15-20mm between the wall and supporting structure.


You could do a check where you get the underside level of the wall, then take the gap away. (A minus B equals new level)

Then do a check for the slab level and see it it matches.

This will probably be a less computation intense calculation than trying to modify geometry and clashing them etc

Agree with @Brendan_Cassidy - geometry tends to be heavy and inconsistent. But as a general suggestion, I’d stay away from bounding boxes for anything other than relative location and closeness. You’re in a pretty old version of Dynamo by the looks of it, where bounding boxes are globally aligned, so anything outside of 90 degree increments is going to be skewed. Newer versions have an option for locally aligned bounding boxes.

Hi @Nick_Boyts, if there is a ‘floating wall’, will the base level method identify the wall as being supported even though it isn’t?

Can the bounding box be extended?

You seem to be doing something very similar to this thread, just in the opposite direction. See my response there for how you could identify “intersections” (you can include your tolerance) before you double check that floors actually exist at those locations.

Your case might actually be a bit simpler, because you can group walls and floors by level and then pull the wall centerlines onto the floor surface for intersections. If the walls don’t intersect or they were located outside your tolerance, they aren’t supported.

Hi @Nick_Boyts I’ve filtered the bottom face of the wall and the top face of the floor and used Surface.Thicken node for the tolerance. But the result is false? I’m not sure what I’m doing wrong.
Is Wall Supported script.dyn (54.4 KB)

I had some code sitting in my library that I have tweaked for you to review @_INN

This may assist in your workflow and integrating combined check cases can simplify things. This was only prepared for non-slope edited Floor’s and single ‘bottom-face’ wall configurations.

In any case, hope this sparks some thoughts. :+1:

WallBaseSupportCheckWithReport.dyn (20.0 KB)

Hi @Ewan_Opie, Thank you for sending this. I’ve tested the script and it is incorrectly flagging the walls as unsupported. I’ve attached the model for your review.
Is Wall Supported R25.rvt (2.5 MB)

It’s nearly impossible to follow a graph without knowing what’s passing through it. Can you repost this with all the node preview bubbles pinned? What version of Revit/Dynamo are you in? Are you testing with a “real” project or a small test file? If you can share a small test file with instances that are not working correctly, we can look into the specific case.

Hi @Nick_Boyts, Updated image below.
Upgraded to Revit 26. Model test file attached.
Is Wall Supported - Test File.rvt (2.5 MB)

Your list structures are a bit of a mess. You have a lot of nested lists in nested lists where you don’t need them. You’re also flattening where it doesn’t make sense. This is causing you to end up with mismatch list structures when you go to filter surfaces for intersections. For a start, Element.Faces already gives you surfaces - there’s no need to convert to topological faces and then back to surfaces with extra list dimensions. You’re also thickening all the wall surfaces instead of just the bottom face. This is giving you one “bottom” for each surface of the wall - not what you want. Once you have the directionally appropriate surface from each element, you can fully flatten your lists (since you have basic wall and floor geometry only). Try all that and see where it gets you.

EDIT: The other thing I noticed was that you’re only thickening the surface by 20mm when your Wall is offset by 15mm. The Surface.Thicken node extrudes the surface in both directions by equal amounts. Meaning your bottom of wall only dropped 10mm.

Here’s a cleaned up version that works.
Is Wall Supported script.dyn (42.3 KB)

1 Like

Ah, have a look at this variation of the code from earlier. If that doesn’t work keep persevering, you’ll get there :slightly_smiling_face:



import clr
clr.AddReference('RevitAPI')
from Autodesk.Revit.DB import *
from Autodesk.Revit.DB import XYZ

clr.AddReference('RevitNodes')
import Revit
clr.ImportExtensions(Revit.GeometryConversion)

# inputs
walls = IN[0] if isinstance(IN[0], list) else [IN[0]]
floor = IN[1]
z_tol_mm = IN[2]
threshold = IN[3]

# revit internal units are decimal feet
z_tol_ft = z_tol_mm / 304.8

def get_bottom_face(wall):
    rw = wall.InternalElement
    opt = Options()
    opt.ComputeReferences = True
    opt.DetailLevel = ViewDetailLevel.Fine

    for obj in rw.get_Geometry(opt):
        solid = None
        if isinstance(obj, GeometryInstance):
            for o in obj.GetInstanceGeometry():
                if isinstance(o, Solid) and o.Volume > 0:
                    solid = o
                    break
        elif isinstance(obj, Solid) and obj.Volume > 0:
            solid = obj
        if solid is None:
            continue
        for face in solid.Faces:
            # bottom face normal points straight down
            if isinstance(face, PlanarFace) and face.FaceNormal.Z < -0.99:
                return face
    return None

def get_top_face(floor):
    rf = floor.InternalElement
    opt = Options()
    opt.ComputeReferences = True
    opt.DetailLevel = ViewDetailLevel.Fine
    rf.get_Geometry(opt)
    refs = HostObjectUtils.GetTopFaces(rf)
    if refs:
        return rf.GetGeometryObjectFromReference(refs[0])
    return None

def face_boundary(face):
    # just want the outer loop
    loops = face.GetEdgesAsCurveLoops()
    if not loops:
        return []
    return [c.GetEndPoint(0) for c in loops[0]]

def flatten_to_z(pts, z):
    return [XYZ(p.X, p.Y, z) for p in pts]

def poly_area(pts):
    # shoelace via cross product
    if len(pts) < 3:
        return 0
    total = XYZ(0, 0, 0)
    o = pts[0]
    for i in range(1, len(pts) - 1):
        v1 = pts[i].Subtract(o)
        v2 = pts[i+1].Subtract(o)
        c = v1.CrossProduct(v2)
        total = XYZ(total.X + c.X, total.Y + c.Y, total.Z + c.Z)
    return total.GetLength() / 2.0

def clip_polygon(subject, clip):
    # Sutherland-Hodgman - clips subject poly against clip poly
    # both must be at the same Z
    def inside(p, a, b):
        return (b.X - a.X) * (p.Y - a.Y) - (b.Y - a.Y) * (p.X - a.X) >= 0

    def intersect_edges(a, b, c, d):
        A1, B1 = b.Y - a.Y, a.X - b.X
        C1 = A1 * a.X + B1 * a.Y
        A2, B2 = d.Y - c.Y, c.X - d.X
        C2 = A2 * c.X + B2 * c.Y
        det = A1 * B2 - A2 * B1
        if abs(det) < 1e-10:
            return a
        return XYZ((B2*C1 - B1*C2)/det, (A1*C2 - A2*C1)/det, a.Z)

    out = list(subject)
    for i in range(len(clip)):
        if not out:
            break
        inp = list(out)
        out = []
        a, b = clip[i], clip[(i+1) % len(clip)]
        for j in range(len(inp)):
            cur, prev = inp[j], inp[j-1]
            if inside(cur, a, b):
                if not inside(prev, a, b):
                    out.append(intersect_edges(prev, cur, a, b))
                out.append(cur)
            elif inside(prev, a, b):
                out.append(intersect_edges(prev, cur, a, b))
    return out

results = []

floor_face = get_top_face(floor)
if floor_face is None:
    OUT = "couldn't get floor top face"
else:
    fz = floor_face.Origin.Z
    floor_pts = flatten_to_z(face_boundary(floor_face), fz)

    for wall in walls:
        try:
            wface = get_bottom_face(wall)

            if wface is None:
                results.append({
                    "Wall": str(wall),
                    "Within Z Tol": False,
                    "Wall Base Z (m)": None,
                    "Floor Top Z (m)": round(fz * 0.3048, 4),
                    "Z Distance (mm)": None,
                    "Wall Base Area (m2)": None,
                    "Overlap Area (m2)": None,
                    "Overlap %": None,
                    "Is Supported": False,
                    "Note": "no bottom face found",
                })
                continue

            wz = wface.Origin.Z
            zdist = wz - fz
            in_tol = 0 <= zdist <= z_tol_ft

            if not in_tol:
                results.append({
                    "Wall": str(wall),
                    "Within Z Tol": False,
                    "Wall Base Z (m)": round(wz * 0.3048, 4),
                    "Floor Top Z (m)": round(fz * 0.3048, 4),
                    "Z Distance (mm)": round(zdist * 304.8, 2),
                    "Wall Base Area (m2)": None,
                    "Overlap Area (m2)": None,
                    "Overlap %": None,
                    "Is Supported": False,
                    "Note": "wall base outside z tolerance",
                })
                continue

            # project wall base down to floor z and clip against floor boundary
            wpts = flatten_to_z(face_boundary(wface), fz)
            wall_area = poly_area(wpts)

            clipped = clip_polygon(wpts, floor_pts)
            overlap = poly_area(clipped) if len(clipped) >= 3 else 0

            pct = overlap / wall_area if wall_area > 0 else 0
            supported = pct >= threshold

            results.append({
                "Wall": str(wall),
                "Within Z Tol": True,
                "Wall Base Z (m)": round(wz * 0.3048, 4),
                "Floor Top Z (m)": round(fz * 0.3048, 4),
                "Z Distance (mm)": round(zdist * 304.8, 2),
                "Wall Base Area (m2)": round(wall_area * 0.092903, 4),
                "Overlap Area (m2)": round(overlap * 0.092903, 4),
                "Overlap %": round(pct * 100, 2),
                "Is Supported": supported,
                "Note": "OK" if supported else "insufficient overlap",
            })

        except Exception as e:
            results.append({"Wall": str(wall), "Is Supported": False, "Error": str(e)})

OUT = results
1 Like