Python Tools — Session 6B
Dynamic Rigid Body Simulation
2020.02.13 — Session 6B
Session Navigation
Overview
This session builds a complete dynamic scene: textured spheres are spawned on a grid, assigned random image materials, and dropped under gravity onto a randomly-deformed ground plane. It brings together material creation, rigid body physics, vertex queries, and boundary detection into one cohesive project.
Key Concepts
- Rigid Body Dynamics — Setting up active and passive rigid bodies with gravity fields for physics simulation
- Material Creation — Programmatically creating Blinn materials with file textures and connecting shader networks
- Random Image Assignment — Using os.listdir and random.choice to pick textures from a directory
- Vertex Boundary Detection — Using isOnBoundary() to check if vertices are on the edge of a mesh
- Scene Setup — Managing playback options, viewport display modes, and scene cleanup from code
Code
faces_project_code_commented.py — Full Dynamic Scene
"""
#############################################################################
filename faces_project_code_commented.py
author Matt Osbond
dP email m.osbond@digipen.edu
course CS115
Brief Description:
Session 4B - Thursday Jan 30th
Experimenting with creating a rigid body dynamics scene using Python
#############################################################################
"""
import pymel.core as pm
import random
import os
def get_random_image():
"""Grabs a random image from our directory and returns the path on disk
"""
# Hard coded path to the face images directory
dir = r"C:\path\to\your\file"
all_files = os.listdir(dir)
# Iterate through the list and update each element to include the root dir
# os.listdir only returns a list of names
for f in xrange(0, len(all_files)):
all_files[f] = os.path.join(dir, all_files[f])
# use random.choice to randomly pick an element, and return it
return random.choice(all_files)
def create_ball(position):
"""Creates a ball at the specified position
Will also assign a new material using a randomly picked texture
Args: world space position of the ball
Returns: the new ball object
"""
# Create a new ball and move it to te corect position
ball = pm.polySphere(sx=10, sy=10)[0]
pm.move(ball, position)
# Grab a random texture
texture = get_random_image()
# Create a new material using the new random texture
new_material = create_material(texture)
# Use the hyperShade to assign the new material (note, will work on the selected objects)
pm.hyperShade(assign=new_material)
# Return the ball object
return ball
def create_material(image_path):
"""Create a new blinn material using the given texture path as a diffuse input
Args: path on disk to the texture to us
Returns: the new material object
"""
# Create the new file node and assign the image path
file_node = pm.createNode("file", skipSelect=True)
file_node.fileTextureName.set(image_path)
# Create a new material
material = pm.createNode("blinn", skipSelect=True)
# Create a new empty shading group
sg = pm.sets(renderable=True, noSurfaceShader=True, empty=True)
# Connect the file node (texture) color to the material color input
pm.connectAttr( (file_node + ".outColor"), (material + ".color"), force=True)
# Connect the output color of the material to the shading group surfaceShader
pm.connectAttr( (material + ".outColor"), (sg + ".surfaceShader"), force=True)
# Return the new material object
return material
def create_dynamic_scene():
""" Main trigger function for the project
Runs all the operations
CAUTION: will force a new empty scene, casuing loss of work. The newFile is commented out for this reason
"""
# Comment out the newFile to be safe
#pm.newFile(force=True)
# Create the plane to put the ablls on and move it up
sky_plane = pm.polyPlane(w=20, h=20)[0]
pm.move(sky_plane, [0,20,0])
# Create an empty list for the balls - we'll need this in a bit
balls = list()
for vert in sky_plane.verts:
# Only create balls on the verts that are not on the edge
if not vert.isOnBoundary():
# Run the create_ball function, grab the resulting ball and add it to our list
ball = create_ball(vert.getPosition(space='world'))
balls.append(ball)
# Create a gravit field
grav = pm.gravity()
# Add a rigid body solver to all our balls at once (so they use the same solver)
pm.rigidBody(balls, active=True)
# Connect the gravity to the bal objects
pm.connectDynamic(balls, fields=grav)
# Cleanup the scene by removing the sky plane - we only used it for position reference
pm.delete(sky_plane)
# Create a LOW RESOLUTION ground plane and randomly move each vert - we've done this a few times now
ground_plane = pm.polyPlane(w=40, h=40, sx=6, sy=6)[0]
for vert in ground_plane.verts:
# Here, if the vert is on the edge, move it up higher
# That way we can create a sort of "bowl" shape to stop the balls all bounding off
if vert.isOnBoundary():
pm.move(vert, [0,10,0], relative=True)
else:
rand = random.uniform(-6,6)
pm.move(vert, [rand,rand,rand], relative=True)
# Smooth our low res ground
pm.polySmooth(ground_plane)
# Apply some color
pm.polyColorPerVertex(ground_plane, rgb=[0.38, 0.96, 0.37], cdo=True)
# Apply a passive (static) rigid body to the ground
pm.rigidBody(ground_plane, passive=True)
# Set the scene up
pm.playbackOptions(maxTime=200) # Set the timeline
pm.select(clear=True) # Select nothing
pm.viewFit() # With nothing selected, this will frame the entire scene
# This ONLY seems to work after you do it inside Maya (press "6") for the first time - will investigate!
pm.mel.DisplayShadedAndTextured()
# Trigger the whole process
create_dynamic_scene()