Define center of Gravity for complicated element

We calculate based on single material assumption and 150 pcf. except for when a panel is insulated. Because there is such a massive weight reduction with insulation, we model those areas as pockets or block-outs, so the area of concrete is displaced accordingly. We do model the whole panel as one entity including the brick and return legs. We do void out areas for openings, reveals and false joints, but that is more for the form work and production more than for weight and CG accountability. When it comes to the reinforcing, which mainly consist of rebar and mesh, we don’t break that down and calculate their actual weights, we just assume the area of the concrete is replaced by the reinforcing and is inclusive in the 150 pcf.

Is there part of your code that you provided to where a total solid can be extracted out? Even though I am getting the centroid, for my other script, I also need to get the whole solid.

No I dont do a total solid in that script - I made it a few years ago because I kept running into cases where trying to generate a merged single solid in Dynamo kept failing (likely because a resultant edge was less than 1/32" or something weird like that - granted most of the time I’m getting C.G.s of 3000+ elements at a time so that might have had something to do with it…).

The method that @c.poupin posted does return a single solid (the last line - mergeSolid.ToProtoType() part is what does that).

I will try adding the last line to your code and see if that works.
Thanks for your help!

no - that wont work - i meant if you use the code @c.poupin wrote, you would get the total solid. to get my code to do that you’d have to do a lot more because i’m not merging the solids at all in my code.

Yeah sorry, I realized that once I got to looking at the code more.
Thanks again!

Thank you guys. I already found other way to define CG with Autocad and it’s correct when I did cross-checking in Revit (with uncomplicated element). It’s easy to solve this issue with Autocad 3D.
Thanks a lot for you guys supports.

Of course you guys @Ben_Osborne and @c.poupin assuming that the elements have same material. The density of materials will change the center of gravity. So you have to use a formal like = ρ*V to get mass.

1 Like

Sorry for digging this thread back up, but my question directly relates to it. My company is now getting into panels that have a steel frame attached. So now we are needing to take the density of the material into account in the center of gravity calc. Below is the code that I am using to get the solid of the list of elements and I need to a section that calcs the density. I am a novice when it comes to python for Dynamo, so would be grateful for any help or advice on what to do. Thanks in advance to any advice provided. I also apologize in advance as I don’t know how to adequately clip code from a node into the thread.

Load the Python Standard and DesignScript Libraries

import clr
clr.AddReference(‘RevitAPI’)
from Autodesk.Revit.DB import *

clr.AddReference(“RevitNodes”)
import Revit
clr.ImportExtensions(Revit.Elements)
clr.ImportExtensions(Revit.GeometryConversion)

clr.AddReference(“RevitServices”)
import RevitServices
from RevitServices.Persistence import DocumentManager
from RevitServices.Transactions import TransactionManager

def convert_geometry_instance(geo, elementlist):
for g in geo:
if str(g.GetType()) == ‘Autodesk.Revit.DB.GeometryInstance’:
elementlist = convert_geometry_instance(g.GetInstanceGeometry(), elementlist)
else:
try:
if g.Volume != 0:
elementlist.append(g)
except:
pass
return elementlist

doc = DocumentManager.Instance.CurrentDBDocument
items = UnwrapElement(IN[0])
detail_lvl = ViewDetailLevel.Fine
inc_invis = False
view = None
inserts = True
remove_inserts = False
revitlist = list()
dynlist = list()

TransactionManager.Instance.EnsureInTransaction(doc)

i = 0
for item in items:
geo_options = Options()
geo_options.DetailLevel = detail_lvl
geo_options.IncludeNonVisibleObjects = inc_invis
revitGeo = item.Geometry[geo_options]
try:
revit_geos = convert_geometry_instance(revitGeo, list())
revitlist.append(revit_geos)
except:
revitlist.append(list())
i += 1

TransactionManager.Instance.TransactionTaskDone()

volz=
cent=
for sol in revitlist:
for so in sol:
volz.append(so.Volume)
cent.append(so.ComputeCentroid())
totvol=sum(volz)
xs=
ys=
zs=
for i in range(0,len(volz)):
vol=volz[i]
cen=cent[i]
xs.append(cen.Xvol/totvol)
ys.append(cen.Y
vol/totvol)
zs.append(cen.Z*vol/totvol)

centroidpoint=Point.Create(XYZ(sum(xs),sum(ys),sum(zs))).ToProtoType()

OUT = (totvol,centroidpoint)

Hi @staylor,

I am also in the precast business. However, the shop drawing tool for precast automatically gives the COG. Unfortunately, it only works for the walls (panels) and slabs; sometimes, I have to work with sloped panels, and using this tool (shop drawing) is impossible. It can not work with geometry or stairs on the same story.

To avoid confusion about materials. Is it possible to get the center of gravity from the part?

@break113
I combined three options into one script: the code from @Ben_Osborne shown in this post above, OOTB Element.Geometry and Solid.Centroid nodes and the code below. At least one of the three options has always worked in the cases we have. From that point I just get the output from the one that works. If more than one option works, just take the first one from the returned list. Other than this, I don’t have any other methods developed.

import clr
import System
import sys
clr.AddReference('ProtoGeometry')
from Autodesk.DesignScript.Geometry import *
import Autodesk.DesignScript.Geometry as DS


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

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

clr.AddReference("RevitServices")
import RevitServices
from RevitServices.Persistence import DocumentManager
from RevitServices.Transactions import TransactionManager

	
def get_AllSolid(lst_elem, inc = 0):
	# sub functon
	def populate_lstGeo(g):
		if isinstance(g, Solid) and g.Volume > 0: 
			solids.append(g)
		elif isinstance(g, GeometryInstance):
			geoInst.append(g)
		else:pass
	#
	# main 	functon		
	global opt
	solids = []
	geoInst = []
	lst_elem = lst_elem if hasattr(lst_elem, "__iter__") else [lst_elem]
	if len(lst_elem) > 0 and inc < 2000:
		for elem in lst_elem:
			if isinstance(elem, GeometryInstance):
				for j in elem.GetInstanceGeometry():
					populate_lstGeo(j)
			else:
				geoSet = elem.get_Geometry(opt)
				for i in geoSet:
					populate_lstGeo(i)
		return solids + get_AllSolid(geoInst, inc + 1)
	else:
		return []
		

elem = UnwrapElement(IN[0])

opt = Options()
solids = get_AllSolid(elem)

ds_Solids = []
for s in solids:
	try:
		ds_Solid = s.ToProtoType()
		ds_Solids.append(ds_Solid)
	except Exception as ex:
		if "trim_with_edge_loops" in str(ex):
			# failed conversion DB.Solid to Prototype -> try get make solid with ByJoinedSurfaces 
			ds_face = []
			for f in s.Faces:
				try:
					ds_face.extend(f.ToProtoType())
				except:
					# failed conversion DB.Face to Prototype -> try get make Surface with PolyCurves.Patch 
					ds_edges = [c.ToProtoType() for curveloop in f.GetEdgesAsCurveLoops() for c in  curveloop]
					ds_poly_edges = PolyCurve.ByJoinedCurves(ds_edges)
					ds_face.append(ds_poly_edges.Patch())
			ds_Solid = DS.Solid.ByJoinedSurfaces(ds_face)
			ds_Solids.append(ds_Solid)
		else:
			raise Exception(ex)
		

OUT = ds_Solids
1 Like

Hi @staylor ,

Thanks for sharing, i think the way to use this script is to get this code into a Python node and the IN will be a select model ?

Cheers

Yes, copy the code into a python script node. The IN would be the actual element that you are wanting to get the CG of.

Hi @staylor ,

I tried with a precast stair and i get null, any advice?

Cheers

That isn’t null, it is a warning. What does the warning say?

Change this line
elem = UnwrapElement(IN[0])
to this
elem = UnwrapElement(IN[0]) if isinstance(IN[0], list) else [UnwrapElement(IN[0])]

Update the helper function in get_AllSolid

    # sub function
    def populate_lstGeo(g):
        if isinstance(g, Solid) and g.Volume > 0: 
            solids.append(g)
        elif isinstance(g, GeometryInstance):
            geoInst.append(g)
        elif isinstance(g, Architecture.Stairs):
            geoInst.extend(g.get_Geometry(opt))
        else:
            pass
1 Like

here another method using only the Revit API

import clr
import sys
import System
clr.AddReference('ProtoGeometry')
from Autodesk.DesignScript.Geometry import *
import Autodesk.DesignScript.Geometry as DS

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

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

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

def get_AllSolid(lst_elem, inc = 0):
    """
    Returns a list of all Solid instances from a list of Revit elements.

    - Parameters:
    lst_elem (list): List of Revit elements to extract Solid instances from.
    inc (int): Increment value for recursive calls to avoid excessive iterations. Default is 0.
    - Returns:
    list: List of Solid instances extracted from input Revit elements.
    """
    # sub functon
    def populate_lstGeo(g):
        if isinstance(g, Solid) and g.Volume > 0: 
            solids.append(g)
        elif isinstance(g, GeometryInstance):
            geoInst.append(g)
        else:pass
    #
    # main 	functon		
    global opt
    solids = []
    geoInst = []
    lst_elem = toList(lst_elem)
    if len(lst_elem) > 0 and inc < 2000:
        for elem in lst_elem:
            if isinstance(elem, GeometryInstance):
                for j in elem.GetInstanceGeometry():
                    populate_lstGeo(j)
            else:
                geoSet = elem.get_Geometry(opt)
                for i in geoSet:
                    populate_lstGeo(i)
        return solids + get_AllSolid(geoInst, inc + 1)
    else:
        return []
  
def calculate_COG(lst_pair_center_volume):
    """
    Calculate the center of gravity of a list of pairs containing a point and volume.

    - Parameters:
    lst_pair_center_volume (list): List of pairs containing a point and volume.
    - Returns:
    XYZ: The coordinates of the center of gravity as a XYZ object.
    """
    data = [[(pt.X, pt.Y, pt.Z), volume] for pt, volume in lst_pair_center_volume ]
    total_volume = sum(volume for _, volume in data)
    weighted_sum = [sum(centroid[i] * volume for centroid, volume in data) for i in range(3)]
    coords_centroid = [coord / total_volume for coord in weighted_sum]
    return  XYZ(*coords_centroid)
 
def toList(x):
    if isinstance(x, (list, dict)) or \
            (hasattr(x, "GetType") and x.GetType().GetInterface("ICollection") is not None):
        return x
    else : return [x]
 
lstelems = toList(UnwrapElement(IN[0]))
opt = Options()
stairs_run = []
for el in lstelems :
    if isinstance(el, DB.Architecture.Stairs):
        for stair_runId in el.GetStairsRuns():
            stairs_run.append(doc.GetElement(stair_runId))
lstelems.extend(stairs_run)

lst_pair_center_volume = [[s.ComputeCentroid(), s.Volume] for elem in lstelems for s in get_AllSolid(elem)]
centroid_xyz = calculate_COG(lst_pair_center_volume)
OUT = centroid_xyz.ToPoint()
4 Likes

display this message

Thanks, looks like is working how ever is there a way i can setup a dot or a symbol to revit to get a dimension ?

this script only works with stairs right? or is possible to use it with another geometry(wall,column or abutment)?

@break113
this should work for all type elements, you can post here if you have any problems

2 Likes