Welcome @JeffBodner!
I had some old code lying around that was close, so I adapted it slightly. Notice that here I use the creation of CAD vertices to represent the particle locations. This is probably not the most efficient approach, as directly creating mesh nodes is likely more efficient. But hopefully this is enough to get you started!
piston.stp (56.4 KB)
import math
import numpy
def compute_fcc_packing_in_volume( targetVolumeID, sphereDiameter, bbox ):
sphereVertexIDs = initialize_fcc_packing( sphereDiameter, bbox )
targetBody = cubit.body( targetVolumeID )
for v in sphereVertexIDs:
vertexCoordinates = cubit.vertex( v ).coordinates()
vertexInVolume = targetBody.point_containment( vertexCoordinates )
if vertexInVolume <= 0:
cubit.cmd( f"delete vertex {v}" )
def initialize_fcc_packing( sphereDiameter, bbox ):
create_fcc_background_grid( sphereDiameter, bbox )
latticeVolumeID = cubit.get_last_id( "volume" )
faceNodeIDs = get_element_faces( latticeVolumeID )
faceNodeIDs = extract_unique_element_faces( faceNodeIDs )
beginVertexID = cubit.get_last_id( "vertex" ) + 1
add_vertices_at_mesh_nodes( latticeVolumeID )
add_vertices_at_face_centers( latticeVolumeID )
endVertexID = cubit.get_last_id( "vertex" )
sphereVertexIDs = list( range( beginVertexID, endVertexID + 1 ) )
cubit.cmd( f"delete volume {latticeVolumeID}" )
return sphereVertexIDs
def create_fcc_background_grid( sphereDiameter, bbox ):
latticeConstant = 2 * math.sqrt( 2.0 ) * ( sphereDiameter / 2.0 )
xLength = bbox[3] - bbox[0]
yLength = bbox[4] - bbox[1]
zLength = bbox[5] - bbox[2]
nX = math.ceil( xLength / latticeConstant )
nY = math.ceil( yLength / latticeConstant )
nZ = math.ceil( zLength / latticeConstant )
xLength = nX * latticeConstant
yLength = nY * latticeConstant
zLength = nZ * latticeConstant
xCenter = ( bbox[0] + bbox[3] ) / 2.0
yCenter = ( bbox[1] + bbox[4] ) / 2.0
zCenter = ( bbox[2] + bbox[5] ) / 2.0
cubit.cmd( f"brick x {xLength} y {yLength} z {zLength}" )
latticeVolumeID = cubit.get_last_id( "volume" )
cubit.cmd( f"move volume {latticeVolumeID} x {xCenter} y {yCenter} z {zCenter}" )
cubit.cmd( f"volume {latticeVolumeID} scheme map" )
cubit.cmd( f"volume {latticeVolumeID} size {latticeConstant}" )
cubit.cmd( f"mesh volume {latticeVolumeID}" )
def get_element_face_nodal_connectivity( elementID ):
elemNodeIDs = cubit.get_connectivity( "hex", elementID )
nConn = [ [ elemNodeIDs[ 2 ], elemNodeIDs[ 1 ], elemNodeIDs[ 0 ], elemNodeIDs[ 3 ] ],
[ elemNodeIDs[ 2 ], elemNodeIDs[ 3 ], elemNodeIDs[ 7 ], elemNodeIDs[ 6 ] ],
[ elemNodeIDs[ 2 ], elemNodeIDs[ 6 ], elemNodeIDs[ 5 ], elemNodeIDs[ 1 ] ],
[ elemNodeIDs[ 4 ], elemNodeIDs[ 5 ], elemNodeIDs[ 6 ], elemNodeIDs[ 7 ] ],
[ elemNodeIDs[ 4 ], elemNodeIDs[ 7 ], elemNodeIDs[ 3 ], elemNodeIDs[ 0 ] ],
[ elemNodeIDs[ 4 ], elemNodeIDs[ 0 ], elemNodeIDs[ 1 ], elemNodeIDs[ 5 ] ] ]
return nConn
def get_element_faces( volumeID ):
elemIDs = cubit.parse_cubit_list( "hex", f"in volume {volumeID}" )
faceNodeIDs = [ ]
for e in elemIDs:
elemFaceNodeIDs = get_element_face_nodal_connectivity( e )
for f in range( 0, len( elemFaceNodeIDs ) ):
faceNodeIDs.append( elemFaceNodeIDs[ f ] )
return numpy.array( faceNodeIDs )
def extract_unique_element_faces( faceNodeIDs ):
tmp = faceNodeIDs
for i in range( 0, len( tmp ) ):
tmp[i].sort()
_, indices = numpy.unique(faceNodeIDs, axis = 0, return_index = True)
return faceNodeIDs[ indices ]
def compute_node_list_centroid( nodeIDList ):
centroid = numpy.array( [ 0.0, 0.0, 0.0 ] )
for n in nodeIDList:
centroid += cubit.get_nodal_coordinates( int( n ) )
centroid /= len( nodeIDList )
return centroid
def add_vertices_at_mesh_nodes( volumeID ):
nodeIDs = cubit.parse_cubit_list( "node", f"in volume {volumeID}" )
for n in nodeIDs:
x, y, z = cubit.get_nodal_coordinates( int( n ) )
cubit.cmd( f"create vertex location {x} {y} {z}" )
def add_vertices_at_face_centers( volumeID ):
faceNodeIDs = get_element_faces( volumeID )
faceNodeIDs = extract_unique_element_faces( faceNodeIDs )
for nodeIDList in faceNodeIDs:
x, y, z = compute_node_list_centroid( nodeIDList )
cubit.cmd( f"create vertex location {x} {y} {z}" )
def list_to_str( input_str ):
return " ".join( [ str( val ) for val in input_str ] )
###### SCRIPT ######
cubit.cmd( "reset" )
cubit.cmd( "echo off" )
cubit.cmd( "undo off" )
cubit.cmd( "journal off" )
cubit.cmd( "info off" )
cubit.cmd( "graphics perspective off ")
cubit.cmd( "graphics parallel scale 7.0398914 ")
cubit.cmd( "from 6.4723108 -17.271496 -16.445939 ")
cubit.cmd( "at 0 -1.75 0 ")
cubit.cmd( "up -0.084038083 -0.74097628 0.66625201 ")
cubit.cmd( "geometry visibility on" )
cubit.cmd('import step "piston.stp" heal')
targetVolumeID = 1
sphereDiameter = 0.25
bbox = cubit.volume( targetVolumeID ).bounding_box()
compute_fcc_packing_in_volume( targetVolumeID, sphereDiameter, bbox )
cubit.cmd("delete volume 1")
cubit.cmd("block 1 vertex all")
cubit.cmd(f"vertex all size {sphereDiameter}")
cubit.cmd("mesh vertex all")
cubit.cmd( "info on" )
cubit.cmd( "echo on" )
cubit.cmd( "undo on" )
cubit.cmd( "journal on" )
cubit.cmd( "geometry visibility on" )