Visualization of Deviation between Point Cloud and BIM

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