1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 | """ BYU Alembic Exporter (Ram's Horn Version) This script is intended to run from a shelf tool in Maya. When it is run it provides a dialog box for the user to select the rigged object they wish to export and then exports a separate Alembic file for each of the rigged objects the user selects, preserving any references made by internal parent contraints. This script assumes that all rigs have had a custom BYU Alembic Export attribute added to them at the transform node that is at the root of all the geometry in the rig and all rigs have been referenced into the scene through the Reference Editor. It also assumes that the scene exists in the internal structure of the BYU pipeline. Example: A scene contains Andre and his jackhammer as rigged objects. The jackhammer's animation is constrained to Andre's wrist through a parent constraint. When the shelf tool is clicked, the script displays a dialog box with two items available to select: Andre and The Jackhammer. When both are selected and the export button is clicked, the script searches the selected rigs for their BYU Alembic Export Flag. The script then searches for all the parent constraints in the rig. If the namespace of the parent constraint doesn't match the namespace of its target, the script adds the target to the list of objects to be exported in the same Alembic file. In this case, the parent constraint on the jackhammer is identified because Andre's wrist is not in the same namespace as the jackhammer. Based on the current shot number, the script finds the appropriate location to save the Alembic file in the pipeline. The filename is generated based on the name of the rig being exported All other Alembic Export options are set based on global preferences in the BYU pipeline. In this case, two Alembic files are generated with two separate MEL commands: Andre (Containing only Andre) and the Jackhammer (Containing the Jackhammer and Andre). """ from PyQt4.QtCore import * from PyQt4.QtGui import * import maya.cmds as cmds import maya.OpenMayaUI as omu from pymel.core import * import utilities as amu #asset manager utilities import os import sip WINDOW_WIDTH = 330 WINDOW_HEIGHT = 300 def maya_main_window(): ptr = omu.MQtUtil.mainWindow() return sip.wrapinstance(long(ptr), QObject) class AlembicExportDialog(QDialog): def __init__(self, parent=maya_main_window()): QDialog.__init__(self, parent) self.save_file() self.setWindowTitle('Select Objects for Export') self.setFixedSize(WINDOW_WIDTH, WINDOW_HEIGHT) self.create_layout() self.create_connections() self.create_export_list() #Create the Layout for the QT Dialog Box def create_layout(self): self.selection_list = self.create_selection_list() button_layout = self.create_button_layout() main_layout = QVBoxLayout() main_layout.setSpacing(2) main_layout.setMargin(2) main_layout.addWidget(self.selection_list) main_layout.addLayout(button_layout) self.setLayout(main_layout) #Set up the selection list to allow multiple selections def create_selection_list(self): selection_list = QListWidget() selection_list.setSelectionMode(QAbstractItemView.ExtendedSelection) selection_list.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Expanding) return selection_list #Add the Export and Cancel Buttons to the bottom and format them def create_button_layout(self): button_layout = QHBoxLayout() button_layout.setSpacing(2) button_layout.addStretch() self.export_button = QPushButton('Export Alembic') self.cancel_button = QPushButton('Cancel') button_layout.addWidget(self.export_button) button_layout.addWidget(self.cancel_button) return button_layout #Attach actions to the buttons def create_connections(self): self.connect(self.export_button, SIGNAL('clicked()'), self.export_alembic) self.connect(self.cancel_button, SIGNAL('clicked()'), self.close_dialog) #Populate selection list with list of loaded references def create_export_list(self): self.selection_list.clear() loaded_references = self.get_loaded_references() for ref in loaded_references: item = QListWidgetItem(ref) item.setText(ref) self.selection_list.addItem(item) self.selection_list.sortItems(0) #Some scenes have rigs that reference invalid files. Skip over any invalid references def get_loaded_references(self): references = cmds.ls(references=True) loaded=[] print "Loaded References: " for ref in references: print "Checking status of " + ref try: if cmds.referenceQuery(ref, isLoaded=True): loaded.append(ref) except: print "Warning: " + ref + " was not associated with a reference file" return loaded ######################################################################## # SLOTS ######################################################################## #Export a seperate Alembic file for each rig and save them to the appropriate shot directory def export_alembic(self): self.save_file() #Extract list of selected References selected_references = [] selected_list_items = self.selection_list.selectedItems() for item in selected_list_items: selected_references.append(item.text()) #Confirm the selection and export to Alembic file if self.show_confirm_alembic_dialog(selected_references) == 'Yes': loadPlugin("AbcExport") for ref in selected_references: abc_filepath = self.build_alembic_filepath(ref) print "Export to " + abc_filepath command = self.build_alembic_command(ref, abc_filepath) print command Mel.eval(command) self.close_dialog() def build_alembic_filepath(self, ref): #Get Shot Directory current_filepath = cmds.file(q=True, sceneName=True) checkout_filepath = os.path.join(amu.getUserCheckoutDir(), os.path.basename(os.path.dirname(current_filepath))) dest_filepath = amu.getCheckinDest(checkout_filepath) #Get Rig Name ref_filepath = cmds.referenceQuery(unicode(ref), filename=True) rig_name = os.path.basename(ref_filepath).split('.')[0] return os.path.join(os.path.dirname(dest_filepath), 'animation_cache', 'abc', rig_name +'.abc') def build_alembic_command(self, ref, abc_filepath): #Look for Alembic tag tagged = self.get_tagged_node(ref) if tagged == "": return "" #Look for Parent Contraints that depend on Animation Data outside of the current rig dependency_list = self.get_constraint_dependancies(ref) #Concatenate list of objects to be exported obj_string = "" obj_string = " ".join([obj_string, "-root %s"%(tagged.name())]) for dep in dependency_list: dep_ref = ls(dep) tagged = self.get_tagged_node(dep_ref[0]) obj_string = " ".join([obj_string, "-root %s"%(tagged.name())]) #Define start/end frames start_frame = cmds.playbackOptions(q=1, animationStartTime=True) - 5 end_frame = cmds.playbackOptions(q=1, animationEndTime=True) + 5 #Build Command command = 'AbcExport -j "%s -frameRange %s %s -step 0.25 -writeVisibility -nn -uv -file %s"'%(obj_string, str(start_frame), str(end_frame), abc_filepath) return command #Search for custom Alembic Export Tag def get_tagged_node(self, ref): #Perform a Depth First search of the tree structure within the rig ref_nodes = cmds.referenceQuery(unicode(ref), nodes=True) root_node = ls(ref_nodes[0]) #First node listed is always the root of our rigs if root_node[0].hasAttr("BYU_Alembic_Export_Flag"): tagged_node = root_node[0] else: tagged_node = self.get_tagged_children(root_node[0]) #The Alembic Tag is required. If it's not found, report the error. if tagged_node == "": self.show_no_tag_dialog(unicode(ref)) return "" print tagged_node return tagged_node #Recursively search children for Alembic Export Flag def get_tagged_children(self, node): for child in node.listRelatives(c=True): if child.hasAttr("BYU_Alembic_Export_Flag"): return child else: tagged_child = self.get_tagged_children(child) if tagged_child != "": return tagged_child return "" #Search parent contraints for dependencies outside of the current rig def get_constraint_dependancies(self, ref): ref_nodes = cmds.referenceQuery(unicode(ref), nodes=True) root_node = ls(ref_nodes[0]) #First node listed is always the root of our rigs dependency_list = self.get_dependant_children(root_node[0]) return dependency_list #List all the parent contraints in the file and check the namespace of their targets def get_dependant_children(self, node): dep_list = [] for constraint in node.listRelatives(ad=True, type="parentConstraint"): parent = constraint.listRelatives(p=True) constraintNS = parent[0].split(':')[0] target_list = cmds.parentConstraint(unicode(constraint), q=True, tl=True) targetNS = target_list[0].split(':')[0] if constraintNS != targetNS and targetNS not in dep_list: dep_list.append(targetNS + 'RN') print dep_list return dep_list def save_file(self): if not cmds.file(q=True, sceneName=True) == '': cmds.file(save=True, force=True) #save file def show_confirm_alembic_dialog(self, references): return cmds.confirmDialog( title = 'Export Alembic' , message = 'Export Alembic for:\n' + str(references) , button = ['Yes', 'No'] , defaultButton = 'Yes' , cancelButton = 'No' , dismissString = 'No') def show_no_tag_dialog(self, ref): return cmds.confirmDialog( title = 'No Alembic Tag Found' , message = 'Unable to locate Alembic Export tag for ' + ref + '.' , button = ['OK'] , defaultButton = 'OK' , cancelButton = 'OK' , dismissString = 'OK') def close_dialog(self): self.close() def go(): dialog = AlembicExportDialog() dialog.show() if __name__ == '__main__': go() |