Visualization of Deviation between Point Cloud and BIM

Comparison between point cloud and bim to detect construction deviations. My point cloud and BIM model were registered in cloudcompare, exported and converted to rcp format, and then imported into revit. It’s just like a big box packed in a small one.

I want to use dynamo to mark the colors of each interior wall based on the deviation of its corresponding point cloud and the color rules. Deviate from the large red and the small green.

As a beginner, I really have no idea what to do. My graduation thesis is about to be submitted

show us what you’ve done, tell us what you’ve researched, what you’ve investigated……

Typically Cloudcompare would be used for this (using the cloud-mesh distance tool)
You could possibly use Dynamo- but it might be reinventing the wheel

2 Likes

Let’s say you have 100 wall surfaces, and 1,000,000 points.

For each surface, you want to check it against all the points, and work out which one is closest?

Then colour the whole surface based on the nearest point ?

Or do you want a heatmap of each wall surface showing the distance from each point to the nearest (normal) surface?

What happens when the nearest point to the surface is inside the wall, or the nearest point is from the opposite side of the wall

With these numbers, there are a lot of permutations (100 million) to calculate. Would it be better to resample (thin out) the pointcloud so there is less data ??

These are the kind of things you’ll need to think through

This is describing the Autodesk Point Layout add-in, tutorial video of such here: https://www.autodesk.com/learn/ondemand/curated/implementing-the-point-layout-add-in-for-revit/3PczTdDwWMYLUNIuGXnhcA

The API calls to do this type of display are in the Analysis section of the API: Revit API 2026 Documentation.

That said, I don’t recommend reinventing the wheel unless you must @Zhongyu.WU24 - try the add-in/app first and if that doesn’t meet your needs check for others, and then progress to building your own tool. Dynamo isn’t ideal for this type of processing.

1 Like

yes

1 Like

Hi,

The Revit API has methods for retrieving points from a point cloud instance (filtered around a plane list).
A simple solution (though not necessarily very accurate) :

  1. Obtain the points on the face with PointCloudInstance.GetPoints().
  2. Then, if there are any, compare the UV coordinates of the center of a face with the average UV coordinates from the point clouds.

cloud_points_analysis_VS_Walls_2

Python code (works only with PythonNet3 or IronPython3, due to the use of Linq extensions)

Code
import clr
import sys
import System
from System.Collections.Generic import List
clr.AddReference('ProtoGeometry')
import Autodesk.DesignScript.Geometry as DS

clr.AddReference("System.Core")
clr.ImportExtensions(System.Linq)

# Revit API
clr.AddReference('RevitAPI')
import Autodesk
import Autodesk.Revit.DB as DB
from Autodesk.Revit.DB import *
from Autodesk.Revit.DB.Analysis import *
from Autodesk.Revit.DB.PointClouds import *


clr.AddReference('RevitServices')
import RevitServices
from RevitServices.Persistence import DocumentManager
from RevitServices.Transactions import TransactionManager
# Current Revit document
doc = DocumentManager.Instance.CurrentDBDocument

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

    
def view_prepare(view):
    """
    Prepare the given 3D view for analysis visualization.

    Parameters
    ----------
    view : Autodesk.Revit.DB.View
        The Revit view where analysis results will be displayed.
    """
    # Try to find existing analysis display style by name using a collector and LINQ predicate
    analysisDisplayStyle = FilteredElementCollector(doc).OfClass(AnalysisDisplayStyle)\
                            .FirstOrDefault(System.Func[DB.Element, System.Boolean](lambda x : x.Name == "Paint_Preview"))
    #
    if view.AnalysisDisplayStyleId == ElementId.InvalidElementId or analysisDisplayStyle is None :
        TransactionManager.Instance.EnsureInTransaction(doc)
        coloredSurfaceSettings = AnalysisDisplayColoredSurfaceSettings()
        coloredSurfaceSettings.ShowGridLines = False
        #
        colorSettings = AnalysisDisplayColorSettings()
        legendSettings = AnalysisDisplayLegendSettings()
        legendSettings.ShowLegend = False
        #
        analysisDisplayStyle = AnalysisDisplayStyle.CreateAnalysisDisplayStyle( doc, "Paint_Preview", coloredSurfaceSettings, colorSettings, legendSettings )
        view.AnalysisDisplayStyleId  = analysisDisplayStyle.Id
        # Commit transaction
        TransactionManager.Instance.TransactionTaskDone()
    
def color_face_analysis(view, lst_face_analysis):
    """
    Color faces in a view using Spatial Field Manager analysis results.

    Parameters
    ----------
    view : Autodesk.Revit.DB.View
        Target view for spatial field analysis coloring.
    lst_face_analysis : list[tuple]
        Iterable of (face, face_ref, is_align_to_pts_cloud) where:
        - face : Autodesk.Revit.DB.Face
        - face_ref : Autodesk.Revit.DB.Reference
        - is_align_to_pts_cloud : bool, True if points are aligned to the face.
    """
    sfm = SpatialFieldManager.GetSpatialFieldManager(view) 
    if sfm is None:
        sfm = SpatialFieldManager.CreateSpatialFieldManager(view, 1)
    # Clear previous primitives
    sfm.Clear() 
    # Register a result schema once
    resultSchema = AnalysisResultSchema("Schema Name" , "Preview_Selection")
    schemaIndex = sfm.RegisterResult(resultSchema)
    # Iterate faces to create/update spatial field primitives
    for face, face_ref, is_align_to_pts_cloud in lst_face_analysis:
        idx = sfm.AddSpatialFieldPrimitive(face_ref)
        uvPts = List[UV]()
        bbuv = face.GetBoundingBox()
        uvPts.Add(bbuv.Min)
        uvPts.Add(bbuv.Max)
        pnts = FieldDomainPointsByUV(uvPts)
        if is_align_to_pts_cloud:
            doubleListA = List[System.Double]([0.0])
        else:
            doubleListA = List[System.Double]([10.0])
        vals = FieldValues(List[ValueAtPoint]([ValueAtPoint(doubleListA), ValueAtPoint(doubleListA)]))
        sfm.UpdateSpatialFieldPrimitive(idx, pnts, vals, schemaIndex)   

toList = lambda x : x if hasattr(x, "__iter__") and not isinstance(x, (str, System.String)) else [x]
    
# Inputs from Dynamo
point_cloud = UnwrapElement(IN[0])
tf = point_cloud.GetTransform()

lst_walls = toList(UnwrapElement(IN[1]))
threeDview = UnwrapElement(IN[2])

# Parameters controlling point sampling and tolerance
margin_factor_box = 0.05
averageDistance = 0.1
numberOfPoints = 2000

outPoints = []
debug = []
analysis_faces = []

view_prepare(threeDview)
for wall in lst_walls:
    bbx_wall = wall.get_BoundingBox(None)
    line_wall = wall.Location.Curve
    for layerType in [ShellLayerType.Interior, ShellLayerType.Exterior]:
        # create planes on each faces (6 faces)"
        Iplanes=List[DB.Plane]()
        #
        ref_face = DB.HostObjectUtils.GetSideFaces(wall, layerType).First()
        face = wall.GetGeometryObjectFromReference(ref_face)
        #
        bbox_uv = face.GetBoundingBox()
        center_uv = (bbox_uv.Min + bbox_uv.Max) / 2.0
        origin = face.Evaluate(center_uv)
        faceNormal = face.FaceNormal
        #
        planeA = DB.Plane.CreateByNormalAndOrigin(faceNormal.Negate(), origin + faceNormal * margin_factor_box)
        planeB = DB.Plane.CreateByNormalAndOrigin(faceNormal, origin - faceNormal * margin_factor_box)
        Iplanes.Add(planeA)
        Iplanes.Add(planeB)
        #
        planeC = DB.Plane.CreateByNormalAndOrigin(XYZ(0,0,1), bbx_wall.Min)
        planeD = DB.Plane.CreateByNormalAndOrigin(XYZ(0,0,-1), bbx_wall.Max)
        Iplanes.Add(planeC)
        Iplanes.Add(planeD)
        #
        planeF = DB.Plane.CreateByNormalAndOrigin(line_wall.GetEndPoint(0), line_wall.Direction)
        planeG = DB.Plane.CreateByNormalAndOrigin(line_wall.GetEndPoint(1), line_wall.Direction.Negate())
        Iplanes.Add(planeF)
        Iplanes.Add(planeG)
        #
        pcf = PointCloudFilterFactory.CreateMultiPlaneFilter(Iplanes)
        #
        points = point_cloud.GetPoints(pcf, averageDistance, numberOfPoints)
        xyz_pts = List[XYZ]([tf.OfPoint(XYZ(p.X, p.Y, p.Z)) for p in points])
        # Linq conversion
        # XYZ -> IntersectionResult -> filter by distance -> DB.UV
        uvPoints = xyz_pts.Where(System.Func[DB.XYZ, System.Boolean](lambda pt : abs((pt - origin).DotProduct(faceNormal)) < averageDistance))\
                            .Select[DB.XYZ, DB.IntersectionResult](System.Func[DB.XYZ, DB.IntersectionResult](lambda pt : face.Project(pt) ))\
                            .Where(System.Func[DB.IntersectionResult, System.Boolean](lambda interR : interR is not None))\
                            .Where(System.Func[DB.IntersectionResult, System.Boolean](lambda interR : interR.Distance < averageDistance))\
                            .Select[DB.IntersectionResult, DB.UV](System.Func[DB.IntersectionResult, DB.UV](lambda interR : interR.UVPoint ))\
                            .ToList()
        #
        # there are not enough points
        if len(uvPoints) < 100:
            analysis_faces.append([face, ref_face, False])
        else:
            # Linq aggregate function to compute sum and average UV
            sum_uv = uvPoints.Aggregate[DB.UV, DB.UV](
                                            UV.Zero, # accumulate init UV
                                            System.Func[DB.UV, DB.UV, DB.UV](
                                                        lambda acc_uv, next_uv:  acc_uv + next_uv 
                                                        )
                                                    )
            average_uv = sum_uv / len(uvPoints)
            analysis_faces.append([face, ref_face, center_uv.DistanceTo(average_uv) < 2 ])

color_face_analysis(threeDview, analysis_faces)
3 Likes