Logical Center of Surface

Built some Python to find a “Logical Center Point” of a non-standard planar surface, based on tessellating the geometry. It’s not perfect by any means, but it may be useful for finding the center of rooms which have non-standard geometry (i.e. U shaped, L shaped, O shaped, etc.).

The two outputs are the center of the longest non-boundary tessellated edge, and the center of the largest triangle after tessellation. Your results may vary, but it might be a bit easier than what PointAtParameter gives for stuff like centering a room.

The Python script
########################################
############## Properties ##############
########################################
__author__ = 'Jacob Small'
__version__ = '0.1.0'
__description__ = "Attempts to find the logical center of a surface via tessellation"
__DynamoBuilds__ = "3.1"
__ReleaseNotes__ = """Not tested completely. Test thuroughly before using in production."""
__Dependancies__ = "none"
__Copyright__ = "2023, Autodesk Inc."
__license__ = "Apache 3.0"



########################################
### Configure the Python environment ###
########################################
### standard imports ###
import sys #add the sys class to the Python environment so we can work with the sys objects
import clr #add the CLR (common language runtime) class to the Python environment so we can work with .net libraries
import math #add the Math class to the Python environment so we can do math stuff
import random #add the Random module to the Python environment so we can impersonate a squirrel

### basic Dynamo imports ###
clr.AddReference('ProtoGeometry') #add the Dynamo geometry library to the CLR
from Autodesk.DesignScript import Geometry as DG #add the Dynamo geometry class using the alias DG, to ensure no overlap between class calls in Revit or Dynamo
clr.AddReference('DSCoreNodes') #add the standard Dynamo library to the CLR
import DSCore # import the standard Dynamo classes to the Python environment
clr.AddReference('GeometryColor') #add the Geometry color library to the CLR
from Modifiers import GeometryColor #import the GeometryColor class to the Python environment
import Dynamo.Visualization as DV #import the Dynamo visualization class so we can use it to tessellate



#########################################
###### Global variables and inputs ######
#########################################
srfs = IN[0] #data from the IN[0] port of the Dynamo environment
if srfs.__class__ != list: srfs = [srfs] #ensure the surfaces are a list
results = [] #an empty list to hold the results

#########################################
############## Code begins ##############
#########################################
for srf in srfs: #for each surface
    ### extract basic properties of the surface ###
    nrml = srf.NormalAtParameter(0.5,0.5) #the normal of the surface
    perim = DG.PolySurface.ByJoinedSurfaces([i.Extrude(nrml) for i in srf.PerimeterCurves()]) #the perimeter curves of the surface as a polysurface
    
    ### build the render package to tessellate the Dynamo geometry ###
    renderPackageFactory = DV.DefaultRenderPackageFactory() #buld a render package factor
    renderPackage = renderPackageFactory.CreateRenderPackage() #create the render package
    tessellationParams = renderPackageFactory.TessellationParameters #get the tessellation parameters
    tessellate = srf.Tessellate(renderPackage, tessellationParams) #tessellate the surface
    
    ### get the tessellation results ###
    verts = renderPackage.MeshVertices #get the verticies from the tessellation in the render package
    verts = [verts[i:i+3] for i in range(0, len(verts), 3)] #slice the tessellation into tripples
    verts = [DG.Point.ByCoordinates(i[0],i[1],i[2]) for i in verts] #generate points from the tripples
    tris = [verts[i:i+3] for i in range(0, len(verts), 3)] #slice the verts into tripples
    tris = [DG.Polygon.ByPoints(i) for i in tris] #generate polygons from the tripples
    tris = [i.Patch() for i in tris] #generate surfaces from the polygons
    
    ### get the center of the longest non-bounding edge ###
    polySurf = DG.PolySurface.ByJoinedSurfaces(tris) #convert the surfaces into a polysurface
    edges = [i.CurveGeometry for i in polySurf.Edges] #get the edges as curves
    edges = [i for i in edges if not i.PointAtParameter(0.5).DoesIntersect(perim)] #filter out edges which have a midpoint that intersects the perimeter of the surface
    lengths = [i.Length for i in edges] #get the lengths
    srt = sorted([i for i in lengths]) #get the lengths as a sorted list
    srt = [lengths.index(i) for i in srt] #get the index of each of the sorted lines
    edge = edges[srt[-1]] #get the edge at the last index from the sorted list of indexes - the longest edge
    edgePnt = edge.PointAtParameter(0.5) #get the midpoint of the edge
    
    ### get the center of the largest triangle ###
    areas = [i.Area for i in tris] #get the areas of the triangles
    srt = sorted([i for i in areas]) #get the areas as a new sorted list
    srt = [areas.index(i) for i in srt] #get the index ofeach of the areas
    tri = tris[srt[-1]] #Get the triangle at the last index - the largest area
    triPnt = tri.PointAtParameter(0.5,0.5) #get the midpoint of the triangle
    
    ### append the points to the results ###
    results.append([edgePnt,triPnt])#add the edge point and tripoint to the results

#########################################
##### Return the results to Dynamo ######
#########################################
OUT = results #return the results to the Dynamo environment
11 Likes

Interesting! So you assume that the tesselator will put the biggest triangle in the ‘fattest’ part of the geometry.

I recently found an article describing another method to do this I was considering using for odd shaped spaces, A new algorithm for finding a visual center of a polygon | by Mapbox | maps for developers (Link to Polylabel code is at the bottom)

1 Like

Yeah - quadtrees are quite useful for this sort of thing!

I wound up going this route as implementing them is a bit of a tricky task without some C# code (or other pre-compiled code) or external component. These are further complicated by the Python engine issues. While there is an octree (3d version of the quadtree) available somewhere, but I wasn’t sure about availability across versions and this was already consistently available, so I ran with it. :slight_smile: