Python Tools - Session 10A
Level of Detail (LOD) Creator
March 10, 2020
Session Navigation
Overview
This session creates a tool for generating multiple levels of detail (LOD) for 3D meshes. Users select a mesh and specify the number of LODs and reduction percentage. The tool duplicates the mesh and progressively reduces polygon count for each LOD level, then creates a Maya LOD group compatible with game engines.
Key Concepts
- Polygon reduction for LOD creation - using
pm.polyReduce()to lower triangle counts - Mesh duplication and progressive iteration - duplicating each LOD level from the previous one
- Maya LOD groups for game engine compatibility - using
pm.mel.LevelOfDetailGroup() - UI widget properties vs. values - understanding that UI widgets must be queried with
getValue() - Multiple LOD level chaining - passing each reduced mesh as the source for the next level
- xrange loops for iteration - counting through LOD levels with
xrange()
Code
The full LOD creator tool (lod_creator.py):
"""
#############################################################################
filename lod_creator.py
author Matt Osbond
dP email m.osbond@digipen.edu
course CS115
Brief Description:
Session 10A - Tuesday Mar 10th
Simple LOD tool
#############################################################################
"""
import pymel.core as pm
def create_lod_level(mesh_node, reduction):
"""Create a single new LOD level
This will duplicate the given mesh then reduce it by the given amount
"""
# Duplicate the mesh - rememering to capture the FIRST element in the returned list (the zero'th index)
dupe = pm.duplicate(mesh_node, rr=True)[0]
# Poly reduce the duplicate
pm.polyReduce(dupe, percentage=reduction)
# Return the duplicate mesh transform
return dupe
class LodUI():
"""Base UI class
"""
def __init__(self):
# Create a window handle name
# This is only ever seen by maya
# Note: DO NOT INCLUDE SPACES IN THIS! Things will break...
self.handle = 'LodCreator'
# Check if the window exists, and delete it if it does
if pm.window(self.handle, exists=True):
pm.deleteUI(self.handle)
# Create the window, defining the title and size
# We pass in the window handle first, as that forces Maya to use it - which means we can check for it (above)
with pm.window(self.handle, title='LOD Creator', width=400, height=100):
# Start a column layout
with pm.columnLayout():
# Create an integer slider to define the number of LODs
# This is cast to a "class property" (self.ui_numlods) so we can query it in the method "create_lods"
self.ui_numlods = pm.intSliderGrp(label='Num LODs', field=True, value=3, min=1, max=10)
# Create a float slider to control the reduction percentage
self.ui_perc = pm.floatSliderGrp(label='Reduce By', field=True, value=50, min=10, max=90)
# Create a button to trigger things
# The trigger command for the method "create_lods" is attached using a callback
pm.button(label='Create LODs', w=100, h=30, command=pm.Callback(self.create_lods))
# Show the window created with the window handle
pm.showWindow(self.handle)
def create_lods(self):
"""Class method to create the LODs on the selected objects
"""
# Query the selected objects
# We cast this to a new variable, as the selection will change as we go - we want the original selection
selected_objects = pm.ls(sl=True)
# Iterate through each selected object
for selected_obj in selected_objects:
# Create a new variable to track the current mesh - assign it to be the source mesh for now
current_mesh = selected_obj
# Create a list of all LOD levels for the current object
lod_levels = [current_mesh]
# FOR loop that will count up to the number in the slider field
# Note: the ui property "self.ui_perc" is a UI widget, NOT the actual value
# To get the value, we have to use "getValue()" to query the widget data
for i in xrange(0, self.ui_numlods.getValue()):
# Call the create LOD function, using the current mesh as the source
# However, we also re-cast the "current_mesh" variable to be the result from the function (the duplicate)
# This lets us "chain" the LODs, by passing in the previously created mesh
current_mesh = create_lod_level(current_mesh, self.ui_perc.getValue())
# Add the current LOD level to our LOD list for the current source mesh
lod_levels.append(current_mesh)
# Create an LOD group that's compatible with game engines
# Maya expects the meshes to be selected, so we have to select all meshes for the CURRENT source mesh
# (meaning, we do this once for each object in our original selection)
pm.select(lod_levels)
# Run the LOD group command
pm.mel.LevelOfDetailGroup()
Live coding notes from the session (notes.py):
import pymel.core as pm
import random
def create_lod_level(node, perc):
dupe = pm.duplicate(node, rr=True)[0]
pm.polyReduce(dupe, percentage=perc)
return dupe
class LodUI():
"""Base UI class
"""
def __init__(self):
"""Initialization
"""
# Set the window handle name. This is what Maya sees.
self.handle = 'LODCreator'
# Delete the window if it exists
if pm.window(self.handle, exists=True):
pm.deleteUI(self.handle)
# Delete the window preferences if they exist
if pm.windowPref(self.handle, exists=True):
pm.windowPref(self.handle, remove=True)
# Create the window using a context
with pm.window(self.handle, title='LOD Creator', w=400, h=100):
# Each layout can be creaed using contexts too
with pm.columnLayout():
# Number of LODs
self.ui_int_lodcount = pm.intSliderGrp(label='Num LODs', field=True, value=3, min=1, max=8)
# Reduction percentage
self.ui_float_perc = pm.floatSliderGrp(label='Reduce By', field=True, value=50, min=10, max=90)
# Create the three buttons, attaching each one their method
pm.button(label='Create LODs', w=100, h=40, command=pm.Callback(self.create_lods))
# Show the UI at the end of initialization
pm.showWindow(self.handle)
def create_lods(self):
selected_items = pm.ls(sl=True)
for node in selected_items:
if node.nodeType() == 'mesh' or node.getShape().nodeType() == 'mesh':
print ('Creating LODs for %s' %node)
lod_meshes = [node]
for i in xrange(0, self.ui_int_lodcount.getValue()):
print i
node = create_lod_level(node, self.ui_float_perc.getValue())
lod_meshes.append(node)
pm.select(lod_meshes)
pm.mel.LevelOfDetailGroup()
LodUI()