Python Tools - Session 13A

Scene Archiver and Ring of Fire Final

2020.04.07 - Session 13A

Session Navigation

Overview

This session covers two topics. First, we complete the Ring of Fire tool by adding keyframe animation - the hoop now translates and rotates over the timeline, and we fix CV iteration to only use editable CVs. Second, we begin building a Scene Archiver tool that uses Python's os.path module to manipulate file paths, extract scene names, and create directory structures for archiving Maya scenes.

Key Concepts

  • Editable CV iteration - Use numCVs(editableOnly=True) to avoid extra CVs from NURBS circles
  • Keyframe animation - Set keyframes at start, middle, and end of timeline programmatically
  • Timeline manipulation - Query and set playback options via pm.playbackOptions
  • Path manipulation - Use os.path.basename, dirname, splitext, and join for file path operations
  • Directory creation - Use os.makedirs to create nested directory structures

Source Code - ring_of_fire.py (Final Version)

"""
#############################################################################
filename    ring_of_fire.py
author      [author]
course      [course]
Brief Description:
    Particle FX Tool (Final)
    To be used in conjunction with "ring_of_fire.ui" - place them next to each other
    Session 13A - Apr 7 2020
#############################################################################
"""

import pymel.core as pm
import os

from PySide2 import QtCore
from PySide2 import QtWidgets
from PySide2 import QtUiTools

from shiboken2 import wrapInstance

import maya.OpenMayaUI as omui

def get_maya_window():
    """Return the main Maya window
    """
    main_window_ptr = omui.MQtUtil.mainWindow()
    return wrapInstance(long(main_window_ptr), QtWidgets.QWidget)

def get_script_dir():
    """Returns the directory where the current script lives
    """
    script_file = os.path.abspath(__file__)
    return os.path.dirname(script_file)

class RingOfFireUI(QtWidgets.QDialog):

    def __init__(self, parent=get_maya_window()):
        super(RingOfFireUI, self).__init__(parent)

        self.setWindowTitle('Ring of Fire FX')

        ui_file_path = os.path.join(get_script_dir(), 'ring_of_fire.ui')

        qfile_object = QtCore.QFile(ui_file_path)
        qfile_object.open(QtCore.QFile.ReadOnly)

        loader = QtUiTools.QUiLoader()
        self.ui = loader.load(qfile_object, parentWidget=self)

        self.ui.btnCreateFX.clicked.connect(self.create_ring)

        qfile_object.close()
        self.show()

    def create_ring(self):
        """Query UI values and pass them to the creation function
        """
        number_of_emitters = self.ui.spnNumberOfEmitters.value()
        radius = self.ui.spnRadius.value()
        create(number_of_emitters, radius)

def create(emitter_count, hoop_radius):
    """Creates an animated particle FX hoop
    """
    print emitter_count, hoop_radius

    hoop = pm.circle(normal=[1,0,0], radius=hoop_radius, sections=emitter_count)[0]

    fx = pm.nParticle()

    # Iterate only over editable CVs (fixes extra CV issue from Part 1)
    for cv in xrange(0, hoop.numCVs(editableOnly=True)):
        pos = hoop.getCV(cv)
        print pos

        new_emitter = pm.emitter(pos=pos)
        pm.connectDynamic(fx, emitters=new_emitter)
        pm.parent(new_emitter, hoop)

    # Query the current timeline end frame
    end_frame = pm.playbackOptions(query=True, animationEndTime=True)

    # Force the timeline range to match the whole timeline
    pm.playbackOptions(min=1, max=end_frame)

    # Set the first set of keyframes
    pm.currentTime(1)
    hoop.ty.set(0)
    hoop.ry.set(0)
    pm.setKeyframe(hoop)

    # Set the middle set of keyframes
    pm.currentTime(end_frame/2)
    hoop.ty.set(15)
    hoop.ry.set(1000)
    pm.setKeyframe(hoop)

    # Set the last set of keyframes
    pm.currentTime(end_frame)
    hoop.ty.set(0)
    hoop.ry.set(2000)
    pm.setKeyframe(hoop)

def run():
    """Checks to see if the UI exists, and closes it
    """
    for ui_item in QtWidgets.qApp.allWidgets():
        if type(ui_item).__name__ == 'RingOfFireUI':
            ui_item.close()
    RingOfFireUI()

Source Code - archiver_part1.py

"""
#############################################################################
filename    archiver.py
author      [author]
course      [course]
Brief Description:
    Scene Archive Tool (Part 1)
    Project to cover the use of path manipulation and scene trawling
    Session 13A - Apr 7 2020
#############################################################################
"""
import pymel.core as pm
import os

# Get the current project directory
current_wd = pm.workspace.getcwd()

# Save the scene (commented out for now)
#pm.saveFile()

# Get full file path
scene_path = pm.sceneName()
print 'Scene Path = ', scene_path

# Get the scene file name
scene_name_with_ext = os.path.basename(scene_path)
print 'Scene File = ', scene_name_with_ext

# Get the name without extension
scene_name = os.path.splitext(scene_name_with_ext)[0]
print 'Scene Name = ', scene_name

# Get scene directory
scene_dir = os.path.dirname(scene_path)
print 'Scene Dir = ', scene_dir

# Create the target archive directory
target_dir = os.path.join(scene_dir, scene_name)
print 'Target Dir = ', target_dir

# Create the target Maya scene path
new_file_name = os.path.join(target_dir, scene_name_with_ext)
print 'Target File = ', new_file_name

# Create the target texture directory
texture_dir = os.path.join(target_dir, 'textures')
print 'Texture Dir = ', texture_dir

# Ensure the directories exist
if not os.path.exists(texture_dir):
    os.makedirs(texture_dir)
← Prev Next ->