def __init__(self, settings, util, export_location): self._logger = Logger(self.__class__.__name__) self._settings = settings self._util = util self._animSequences = AnimSequences(self._settings) self._exportLayers = ExportLayers(self._settings) self._export_location = export_location
def __init__(self, settings, data_location, script_location, generated_script_location): self._logger = Logger(self.__class__.__name__) self._settings = settings self._data_location = data_location self._script_location = script_location self._generated_script_location = generated_script_location self._disable_undo_counter = 0
def __init__(self, util, key, type, attribute_name, attribute_type, value_id): self._logger = Logger(self.__class__.__name__) self._util = util self._key = key self._type = type self._attribute_name = attribute_name self._attribute_type = attribute_type self._value_id = value_id self._names = []
def __init__(self): self._logger = Logger(self.__class__.__name__)
class SkeletonDefinition: def __init__(self): self._logger = Logger(self.__class__.__name__) def to_formatted_num(self, num): num_length = 7 if num == 0: # Remove negative sign for zero values num = 0 return "{0:.2f}".format(num).rjust(num_length) def to_formatted_text(self, text, text_width): text = text.ljust(text_width) text = text.replace(" ", "\t") if " " in text: text = text.replace(" ", "") text += "\t" return text def get_joint_name_width(self, skeleton_def): length = 0 for joint_chain in skeleton_def: for joint in joint_chain: length = max(length, len(joint.rpartition(":")[2])) return length def get_label(self, joint_name): if "root" in joint_name: return "\n# ROOT joint" elif "pelvis" in joint_name: return "\n# BODY joint chain" elif "thigh_lt" in joint_name: return "\n# Left LEG joint chain" elif "clavicle" in joint_name: return "\n# Left ARM joint chain" elif any(joint for joint in ("pinky", "ring", "middle", "index", "thumb") if joint in joint_name): return "\n# Left {0} finger joint chain".format(joint_name.split("_")[0].upper()) return None def display_script(self, skeleton_def): self._logger.info("# Skeleton Definition dump:") joint_name_width = self.get_joint_name_width(skeleton_def) for joint_chain in skeleton_def: print(self.get_label(joint_chain[0].rpartition(":")[2])) for joint in joint_chain: parent_node = joint.rpartition(":")[0] child_node = joint.rpartition(":")[2] parent_transform = [0, 0, 0] child_transform = cmds.xform(child_node, query=True, worldSpace=True, translation=True) if cmds.objExists(parent_node): parent_transform = cmds.xform(parent_node, query=True, worldSpace=True, translation=True) x = round((child_transform[0] - parent_transform[0]), 2) y = round((child_transform[1] - parent_transform[1]), 2) z = round((child_transform[2] - parent_transform[2]), 2) if joint == joint_chain[0]: print( "\tcreate_joint\t{0}\t{1}\t{2}\t{3}".format( self.to_formatted_text(child_node, joint_name_width), self.to_formatted_num(x), self.to_formatted_num(y), self.to_formatted_num(z), ) ) else: print( "\t\t\t\t\t{0}\t{1}\t{2}\t{3}\t\tlocation_relative".format( self.to_formatted_text(child_node, joint_name_width), self.to_formatted_num(x), self.to_formatted_num(y), self.to_formatted_num(z), ) ) print("\t;") self._logger.info("\t* Skeleton Definition dump complete.") cmds.confirmDialog( title="Dump Skeleton Definition", message="Dump complete.\n\nSee Script Editor for details. ", button="OK", ) self._logger.command_message("Dump complete. See Script Editor for details.") def dump(self): eval_joints = ["root"] skeleton_def = [] while len(eval_joints) > 0: current_node = eval_joints[0] eval_joints = eval_joints[1:] joint_def = [":{0}".format(current_node)] if current_node[-3:] == "_rt": # Skip the right part of skeleton tree continue child_nodes = cmds.listRelatives(current_node) if current_node == "root": # Split root from pelvis eval_joints += child_nodes else: while child_nodes is not None: joint_def.append("{0}:{1}".format(current_node, child_nodes[0])) eval_joints += child_nodes[1:] current_node = child_nodes[0] child_nodes = cmds.listRelatives(current_node) skeleton_def.append(joint_def) self.display_script(skeleton_def)
class Util: _CHAR_INDENT = '\t' _lock = threading.RLock() def __init__(self, settings, data_location, script_location, generated_script_location): self._logger = Logger(self.__class__.__name__) self._settings = settings self._data_location = data_location self._script_location = script_location self._generated_script_location = generated_script_location self._disable_undo_counter = 0 def generate_script(self, filename): parser_path = os.path.join(self._script_location, 'cnx_rigathon_parser.py') data_path = os.path.join(self._data_location, filename + '.txt') generated_script_path = os.path.join(self._generated_script_location, filename + '.py') command = '$PARSER_PATH -s $DATA_PATH -o $GENERATED_SCRIPT_PATH' \ .replace('$PARSER_PATH', parser_path) \ .replace('$DATA_PATH', data_path) \ .replace('$GENERATED_SCRIPT_PATH', generated_script_path) self._logger.info('# Generate rigathon script file: ' + generated_script_path + ' ...') try: self.disable_undo() if not os.path.isdir(self._generated_script_location): # Create directory os.makedirs(self._generated_script_location) stdout = subprocess.check_output('mayapy ' + command, shell=True) self._logger.log(stdout) except: self._logger.error(self._CHAR_INDENT + '* Failed generating rigathon script.') # Format exception lines = traceback.format_exc().splitlines() for line in lines: self._logger.error(self._CHAR_INDENT + self._CHAR_INDENT + '# ' + line) finally: self.enable_undo() def exec_generated_script(self, script_name): script_path = os.path.join(self._generated_script_location, script_name + '.py') self._logger.info('# Executing rigathon script file: ' + script_path + ' ...') try: self.disable_undo() imp.load_source('rigathon_script', script_path) self._logger.info(self._CHAR_INDENT + '* DONE!') except: self._logger.error(self._CHAR_INDENT + '* Failed executing rigathon script.') # Format exception lines = traceback.format_exc().splitlines() for line in lines: self._logger.error(self._CHAR_INDENT + self._CHAR_INDENT + '# ' + line) finally: self.enable_undo() def disable_undo(self): with self._lock: self._disable_undo_counter += 1 cmds.undoInfo(stateWithoutFlush=False) def enable_undo(self): with self._lock: self._disable_undo_counter -= 1 if self._disable_undo_counter == 0: cmds.undoInfo(stateWithoutFlush=True) def select_obj(self, objs, toggle = False, replace = False): if type(objs) is list: found_objs = [] missing = False for obj in objs: if cmds.objExists(obj): found_objs.append(obj) else: self._logger.warn('Rig control \'$rig_control\' not found.'.replace('$rig_control', obj)) missing = True if len(found_objs) > 0: if found_objs != cmds.ls(selection=True): cmds.select(found_objs, toggle=toggle, replace=replace) else: cmds.select(clear=True) if missing: self._logger.command_message('Warn: Some rig controls were not found. See Script Editor for details.') else: if cmds.objExists(objs): cmds.select(objs, toggle=toggle, replace=replace) else: self._logger.warn('Rig control \'$rig_control\' not found.'.replace('$rig_control', objs)) self._logger.command_message('Warn: Rig control not found. See Script Editor for details.') def message_box(self, title, message, *args): message = (''.ljust(7) + '\n').join(message.split('\n')) + ''.ljust(7) cmds.confirmDialog(title=title, message=message, button='OK') def custom_message_box(self, title, message, buttons, default_button, cancel_button): message = (''.ljust(7) + '\n').join(message.split('\n')) + ''.ljust(7) return cmds.confirmDialog(title=title, message=message, button=buttons, defaultButton=default_button, cancelButton=cancel_button, dismissString=cancel_button) def dialog_box_ok_cancel(self, title, message, value = '', *args): dlg_result = cmds.promptDialog(title=title, message=message, text=value, button=['OK', 'Cancel'], defaultButton='OK', cancelButton='Cancel', dismissString='Cancel') if dlg_result == 'OK': return cmds.promptDialog(query=True, text=True) else: return None def dialog_box_yes_no(self, title, message, text = '', default_button = 'Yes', *args): dlg_result = cmds.confirmDialog(title=title, message=message, button=['Yes', 'No'], defaultButton=default_button, cancelButton='No', dismissString='No') return dlg_result == 'Yes' def save_to_cache(self, name, value): self.disable_undo() try: section = 'cache' self._settings.save(name, value, section = section) finally: self.enable_undo() def load_from_cache(self, name, default_value = None): self.disable_undo() try: section = 'cache' return self._settings.load(name, default_value, section = section) finally: self.enable_undo() def to_matcher_category(self): return 'matchers' def to_matcher_name(self, character_name): return 'matcher_' + character_name def to_ui_name(self, name): return 'cnx_' + name def to_node_name(self, name): ref_name = self._settings.get_ref_name() if len(ref_name) > 0: ref_name += ':' return ref_name + name def to_proper_name(self, name): proper_name = '' for c in name: proper_name += c if c.isalnum() else '_' return proper_name def update_editor_view(self): cmds.viewFit() panels = cmds.getPanel(type='modelPanel') for panel in panels: modelEditor = cmds.modelPanel(panel, query=True, modelEditor=True) cmds.modelEditor(modelEditor, edit=True, displayAppearance='smoothShaded', displayTextures=True, textures=True, joints=False) def perform_maya_2015_window_resize_workaround(self, window_name): cmds.window(window_name, edit=True, resizeToFitChildren=True, widthHeight=(1, 1))
class FbxExportUI: def __init__(self, settings, util, export_location): self._logger = Logger(self.__class__.__name__) self._settings = settings self._util = util self._animSequences = AnimSequences(self._settings) self._exportLayers = ExportLayers(self._settings) self._export_location = export_location def create_panel(self, panel_width): cmds.frameLayout(self._util.to_ui_name('frm_fbx_export'), label='FBX Export', borderStyle='etchedOut', collapsable=True, collapse=True, enable=False, width=panel_width) self._init_anim_sequences_ui() self._init_export_layers_ui() cmds.setParent(upLevel=True) # ANIM SEQUENCE ================================================== [ public ] def init_anim_sequences(self): anim_sequence_names = None if cmds.objExists('anim_sequences'): anim_sequence_names = cmds.listRelatives('anim_sequences') self._animSequences.clear() if anim_sequence_names is not None: # Load items from settings for anim_sequence_name in anim_sequence_names: name_value = self._settings.load('name', '', anim_sequence_name, 'anim_sequences') start_value = self._settings.load('start', '', anim_sequence_name, 'anim_sequences') end_value = self._settings.load('end', '', anim_sequence_name, 'anim_sequences') active_value = self._settings.load('active', True, anim_sequence_name, 'anim_sequences') self._animSequences.add(name_value, start_value, end_value, active_value) if self._animSequences.get_size() == 0: # Add default item self._animSequences.add('InitialPose', '1', '2', True) self._animSequences.add('AnimSeq01', 'start', 'end', True) # Update UI self._update_anim_sequences_ui() def get_anim_sequences(self): return self._animSequences def add_anim_sequence(self, *args): selected_objs = cmds.ls(selection=True) self._util.disable_undo() try: start = str(int(cmds.playbackOptions(query=True, min=True))) end = str(int(cmds.playbackOptions(query=True, max=True))) name = 'AnimSeq' active = True anim_sequence = self._animSequences.add(name, start, end, active) name = anim_sequence.get_name() + str(anim_sequence.get_index() + 1).zfill(2) self.update_anim_sequence(anim_sequence.get_index(), '', 'name', name) cmds.evalDeferred(self._update_anim_sequences_ui) finally: self._util.select_obj(selected_objs) self._util.enable_undo() def update_anim_sequence(self, idx, widget_name, attribute, value, *args): self._util.disable_undo() try: if attribute == 'name': value = self._util.to_proper_name(value) self._animSequences.update(idx, name = value) if cmds.textField(widget_name, query=True, exists=True): cmds.textField(widget_name, edit=True, text=value, annotation=value) elif attribute == 'start': value = self._to_range_num(value) self._animSequences.update(idx, start = value) if cmds.textField(widget_name, query=True, exists=True): cmds.textField(widget_name, edit=True, text=value, annotation=value) elif attribute == 'end': value = self._to_range_num(value) self._animSequences.update(idx, end = value) if cmds.textField(widget_name, query=True, exists=True): cmds.textField(widget_name, edit=True, text=value, annotation=value) elif attribute == 'active': self._animSequences.update(idx, active = value) finally: self._util.enable_undo() def delete_anim_sequence(self, idx, *args): selected_objs = cmds.ls(selection=True) self._util.disable_undo() try: self._animSequences.remove(idx) cmds.evalDeferred(self._update_anim_sequences_ui) finally: self._util.select_obj(selected_objs) self._util.enable_undo() def move_anim_sequence_up(self, idx, *args): self._util.disable_undo() try: moved = self._animSequences.move_up(idx) if moved: cmds.evalDeferred(self._update_anim_sequences_ui) finally: self._util.enable_undo() def move_anim_sequence_down(self, idx, *args): self._util.disable_undo() try: moved = self._animSequences.move_down(idx) if moved: cmds.evalDeferred(self._update_anim_sequences_ui) finally: self._util.enable_undo() # EXPORT LAYER =================================================== [ public ] def init_export_layers(self): export_layer_names = None if cmds.objExists('export_layers'): export_layer_names = cmds.listRelatives('export_layers') self._exportLayers.clear() if export_layer_names is not None: # Load items from settings for export_layer_name in export_layer_names: name_value = self._settings.load('name', '', export_layer_name, 'export_layers') objects_value = self._settings.load('objects', '', export_layer_name, 'export_layers', as_list = True) active_value = self._settings.load('active', True, export_layer_name, 'export_layers') self._exportLayers.add(name_value, objects_value, active_value) # Add export objects from the scene fbx_export_layer = self._util.to_node_name('FBX_Export') character_name = self._settings.get_ref_name() objects = [] if character_name == '': # Set default character name character_name = 'Character' # Check if it hasn't been added before is_new_export_layer = True for exportLayer in self._exportLayers.get_list(): if character_name == exportLayer.get_name(): is_new_export_layer = False break # Check that FBX export layer exists is_fbx_export_layer_exists = cmds.objExists(fbx_export_layer) if is_new_export_layer and is_fbx_export_layer_exists: # Get export objects from the scene export_objects = cmds.editDisplayLayerMembers(fbx_export_layer, query=True) if export_objects is not None and len(export_objects) > 0: objects = export_objects self._exportLayers.add(character_name, objects, True) # Update UI self._update_export_layers_ui() def get_export_layers(self): return self._exportLayers def add_export_layer(self, idx): selected_objs = cmds.ls(selection=True) self._util.disable_undo() try: name = 'NewLayer' active = True export_layer = self._exportLayers.add(name, [], active) name = export_layer.get_name() + str(export_layer.get_index()).zfill(2) self.update_export_layer(export_layer.get_index(), '', 'name', name) cmds.evalDeferred(self._update_export_layers_ui) finally: self._util.select_obj(selected_objs) self._util.enable_undo() def update_export_layer(self, idx, widget_name, attribute, value, *args): self._util.disable_undo() try: if attribute == 'name': value = self._util.to_proper_name(value) self._exportLayers.update(idx, name = value) if cmds.textField(widget_name, query=True, exists=True): cmds.textField(widget_name, edit=True, text=value, annotation=value) elif attribute == 'active': self._exportLayers.update(idx, active = value) finally: self._util.enable_undo() def delete_export_layer(self, idx, *args): selected_objs = cmds.ls(selection=True) self._util.disable_undo() try: self._exportLayers.remove(idx) cmds.evalDeferred(self._update_export_layers_ui) finally: self._util.select_obj(selected_objs) self._util.enable_undo() def move_export_layer_up(self, idx, *args): self._util.disable_undo() try: moved = self._exportLayers.move_up(idx) if moved: cmds.evalDeferred(self._update_export_layers_ui) finally: self._util.enable_undo() def move_export_layer_down(self, idx, *args): self._util.disable_undo() try: moved = self._exportLayers.move_down(idx) if moved: cmds.evalDeferred(self._update_export_layers_ui) finally: self._util.enable_undo() def add_selected_objects_to_layer(self, idx, widget_name, *args): self._util.disable_undo() try: selected_objects = cmds.ls(selection=True) added = self._exportLayers.get(idx).add_objects(selected_objects) if added: objects = self._exportLayers.get(idx).get_objects() if objects is None: objects = [] self._exportLayers.update(idx, objects = objects) cmds.text(widget_name, edit=True, label=str(self._exportLayers.get(idx).get_size())) finally: self._util.enable_undo() def remove_selected_objects_from_layer(self, idx, widget_name, *args): self._util.disable_undo() try: selected_objects = cmds.ls(selection=True) removed = self._exportLayers.get(idx).remove_objects(selected_objects) if removed: objects = self._exportLayers.get(idx).get_objects() if objects is None: objects = [] self._exportLayers.update(idx, objects = objects) cmds.text(widget_name, edit=True, label=str(self._exportLayers.get(idx).get_size())) finally: self._util.enable_undo() def empty_objects_from_layer(self, idx, widget_name, *args): self._util.disable_undo() try: emptied = self._exportLayers.get(idx).clear_objects() if emptied: objects = self._exportLayers.get(idx).get_objects() if objects is None: objects = [] self._exportLayers.update(idx, objects = objects) cmds.text(widget_name, edit=True, label=str(self._exportLayers.get(idx).get_size())) finally: self._util.enable_undo() def select_objects_from_layer(self, idx, *args): export_objects = self._exportLayers.get(idx).get_objects() self._util.select_obj(export_objects) def fbx_export(self, idx, *args): dialog_title = '[Rig-a-thon] FBX Export' last_export_name = self._util.load_from_cache('last_export_name', '') export_name = self._util.dialog_box_ok_cancel(dialog_title, 'Enter export name:', last_export_name) if export_name is None or len(export_name.strip()) == 0: # Cancel export return self._util.save_to_cache('last_export_name', export_name) base_export_path = os.path.join(self._export_location, self._util.to_proper_name(export_name)) if os.path.exists(base_export_path): # Export path already exists allow_overwrite = self._util.dialog_box_yes_no(dialog_title, 'Export name already exists. Do you want to overwrite existing files? ') if not allow_overwrite: return # Get selected objects selected_objs = cmds.ls(selection=True) # Get initial time slider frame range min_frame = cmds.playbackOptions(query=True, min=True) max_frame = cmds.playbackOptions(query=True, max=True) try: # Get anim options fps = cmds.currentUnit(query=True, time=True) rotationInterp = 1 for option in cmds.optionVar(list=True): if option == 'rotationInterpolationDefault': rotationInterp = cmds.optionVar(query=option) self._logger.info('# FBX Export started [Time Unit: $fps, RotationInterp: $rotationInterp]' \ .replace('$fps', str(fps)) \ .replace('$rotationInterp', str(rotationInterp)) ) exported_fbx_paths = [] # Iterate thru animSequences and exportLayers anim_sequences = self._animSequences.get_list() export_layers = self._exportLayers.get_list() for export_layer in export_layers: export_all = idx == -1 export_selected = idx == export_layer.get_index() export_layer_active = export_layer.is_active() if export_all: if not export_layer_active: # Skip inactive layers continue elif not export_selected: continue export_objects = export_layer.get_objects() if len(export_objects) == 0: # No objects to export self._logger.warn('# Skipping export layer \'$layer\' - no objects to export.'.replace('$layer', export_layer.get_name())) continue missing_obj = False for export_object in export_objects: if not cmds.objExists(export_object): # Missing export object missing_obj = True break if missing_obj: self._logger.warn('# Skipping export layer \'$layer\' - missing object \'$object\'.' \ .replace('$layer', export_layer.get_name()) \ .replace('$object', export_object) \ ) continue for anim_sequence in anim_sequences: if anim_sequence.is_active(): export_path = self.export_anim(base_export_path, export_layer, anim_sequence, min_frame, max_frame) if export_path is not None: exported_fbx_paths.append(export_path) if len(exported_fbx_paths) > 0: self._logger.info('\t* FBX Export complete.') self._logger.info('# Exported FBX files:') for export_fbx_path in exported_fbx_paths: self._logger.info('\t- ' + export_fbx_path) cmds.confirmDialog(title=dialog_title, message='Export complete.\n\nSee Script Editor for details. ', button='OK') else: self._logger.info('\t* FBX Export complete. No objects were exported.') cmds.confirmDialog(title=dialog_title, message='Export failed.\n\nSee Script Editor for details. ', button='OK') self._logger.command_message('Export complete. See Script Editor for details.') finally: # Restore time slider frame range cmds.playbackOptions(min=min_frame, animationStartTime=min_frame) cmds.playbackOptions(max=max_frame, animationEndTime=max_frame) # Restore selections if len(selected_objs) > 0: cmds.select(selected_objs) else: cmds.select(clear=True) def export_anim(self, base_export_path, export_layer, anim_sequence, min_frame, max_frame): # Set new time slider frame range new_start_frame = anim_sequence.get_start() new_end_frame = anim_sequence.get_end() if new_start_frame == 'start': new_start_frame = str(int(min_frame)) if new_end_frame == 'end': new_end_frame = str(int(max_frame)) if int(new_start_frame) >= int(new_end_frame): self._logger.warn('# Skipping FBX Export on animation sequence \'$anim_sequence\' - End frame should be greater than Start frame.' \ .replace('$anim_sequence', anim_sequence.get_name()) \ ) return None cmds.playbackOptions(min=new_start_frame, animationStartTime=new_start_frame) cmds.playbackOptions(max=new_end_frame, animationEndTime=new_end_frame) # Format export path export_filename = '$layer_name_$anim_sequence_$start_$end.fbx' \ .replace('$layer_name', export_layer.get_name()) \ .replace('$anim_sequence', anim_sequence.get_name()) \ .replace('$start', str(new_start_frame)) \ .replace('$end', str(new_end_frame)) export_path = os.path.join(base_export_path, export_filename) self._logger.info('# Export path: ' + export_path) # Retrieve joint chains joint_chains = [] for export_object in export_layer.get_objects(): if cmds.nodeType(export_object) == 'joint': joint_chains.append(export_object) if len(joint_chains) == 0: self._logger.warn('# Skipping FBX Export on export layer \'$export_layer\' - No joints found.' \ .replace('$export_layer', export_layer.get_name()) \ ) return None # Clear keys for joint_chain in joint_chains: cmds.cutKey(joint_chain, hierarchy='both') # Bake animation self._logger.info('\t- Baking animation...') cmds.select(joint_chains, hierarchy=True) cmds.bakeResults(simulation=True, time=(new_start_frame, new_end_frame)) # Run an euler filter self._logger.info('\t- Running Euler filter...') cmds.select(joint_chains, hierarchy=True) cmds.filterCurve() # Set FBX properties mel.eval('FBXExportConstraints -v 1;') mel.eval('FBXExportCacheFile -v 0;') if not os.path.isdir(base_export_path): # Create directory os.makedirs(base_export_path) # Export as FBX self._logger.info('\t- Exporting...') cmds.select(export_layer.get_objects()) cmds.file(export_path, force=True, options='v=0;', type='FBX export', preserveReferences=True, exportSelected=True) self._logger.info('\t* DONE!') # Clear keys for joint_chain in joint_chains: cmds.cutKey(joint_chain, hierarchy='both') return export_path # ANIM SEQUENCE ================================================= [ private ] def _init_anim_sequences_ui(self): cmds.columnLayout(self._util.to_ui_name('col_anim_sequences'), adjustableColumn=True) cmds.setParent(upLevel=True) def _update_anim_sequences_ui(self): col1_width = 10 col2_width = 34 col3_width = 4 col4_width = 34 col6_width = 10 col7_width = 12 col8_width = 10 # Delete existing UI elements child_elements = cmds.columnLayout(self._util.to_ui_name('col_anim_sequences'), query=True, childArray=True) if child_elements is not None: cmds.deleteUI(child_elements) # Create new UI elements cmds.frameLayout(parent=self._util.to_ui_name('col_anim_sequences'), label=' Animation Sequences', borderStyle='out', font='obliqueLabelFont') cmds.setParent(upLevel=True) cmds.rowLayout(parent=self._util.to_ui_name('col_anim_sequences'), numberOfColumns=7, adjustableColumn=5) cmds.separator(style='none', width=col1_width) cmds.text(' Start', font='boldLabelFont', width=col2_width, align='left') cmds.separator(style='none', width=col3_width) cmds.text(' End', font='boldLabelFont', width=col4_width, align='left') cmds.text(' Animation', font='boldLabelFont', align='left') cmds.text('', font='boldLabelFont', align='left') cmds.text(' Sort', font='boldLabelFont', align='left') cmds.setParent(upLevel=True) for index in range(0, self._animSequences.get_size()): cmds.rowLayout(parent=self._util.to_ui_name('col_anim_sequences'), numberOfColumns=8, adjustableColumn=5) # Close button cmds.iconTextButton(label='x', style='textOnly', font='boldLabelFont', command=partial(self.delete_anim_sequence, index), width=col1_width) txtField_start_name = self._util.to_ui_name('txtField_anim_sequence_start_name' + str(index)) txtField_end_name = self._util.to_ui_name('txtField_anim_sequence_end_name' + str(index)) txtField_name_name = self._util.to_ui_name('txtField_anim_sequence_name_name' + str(index)) chk_active_name = self._util.to_ui_name('chk_anim_sequence_active_name' + str(index)) start_value = self._animSequences.get(index).get_start() end_value = self._animSequences.get(index).get_end() name_value = self._animSequences.get(index).get_name() active_value = self._animSequences.get(index).is_active() cmds.textField(txtField_start_name, text=start_value, annotation=start_value, changeCommand=partial(self.update_anim_sequence, index, txtField_start_name, 'start'), width=col2_width) cmds.text('-', width=col3_width) cmds.textField(txtField_end_name, text=end_value, annotation=end_value, changeCommand=partial(self.update_anim_sequence, index, txtField_end_name, 'end'), width=col4_width) cmds.textField(txtField_name_name, text=name_value, annotation=name_value, changeCommand=partial(self.update_anim_sequence, index, txtField_name_name, 'name')) cmds.checkBox(chk_active_name, value=active_value, label='', changeCommand=partial(self.update_anim_sequence, index, chk_active_name, 'active')) cmds.iconTextButton(label='^', style='textOnly', font='boldLabelFont', command=partial(self.move_anim_sequence_up, index), width=col7_width) cmds.iconTextButton(label='v', style='textOnly', font='boldLabelFont', command=partial(self.move_anim_sequence_down, index), width=col8_width) cmds.setParent(upLevel=True) cmds.rowLayout(parent=self._util.to_ui_name('col_anim_sequences'), numberOfColumns=2) cmds.separator(style='none', width=col1_width) cmds.button(label=' New ', command=partial(self.add_anim_sequence)) cmds.setParent(upLevel=True) # EXPORT LAYER ================================================== [ private ] def _init_export_layers_ui(self): cmds.columnLayout(self._util.to_ui_name('col_export_layers'), adjustableColumn=True) cmds.setParent(upLevel=True) def _update_export_layers_ui(self): col1_width = 10 col2_width = 12 col3_width = 18 col6_width = 12 col7_width = 10 col8_width = 10 # Delete existing UI elements child_elements = cmds.columnLayout(self._util.to_ui_name('col_export_layers'), query=True, childArray=True) if child_elements is not None: for element in child_elements: cmds.deleteUI(element) # Create new UI elements cmds.columnLayout(self._util.to_ui_name('col_export_layers'), edit=True) cmds.frameLayout(parent=self._util.to_ui_name('col_export_layers'), label=' Export Layers', borderStyle='out', font='obliqueLabelFont') cmds.setParent(upLevel=True) cmds.rowLayout(parent=self._util.to_ui_name('col_export_layers'), numberOfColumns=7, adjustableColumn=4) cmds.separator(style='none', width=col1_width) cmds.separator(style='none', width=col2_width) cmds.separator(style='none', width=col3_width) cmds.text(label='Layer', font='boldLabelFont', align='left') cmds.text(label='', font='boldLabelFont', align='left') cmds.text(label='', font='boldLabelFont', align='left') cmds.text(' Sort', font='boldLabelFont', align='left') cmds.setParent(upLevel=True) for index in range(0, self._exportLayers.get_size()): cmds.rowLayout(parent=self._util.to_ui_name('col_export_layers'), numberOfColumns=8, adjustableColumn=4) # Close button cmds.iconTextButton(label='x', style='textOnly', font='boldLabelFont', command=partial(self.delete_export_layer, index), width=col1_width) txt_object_count_name = self._util.to_ui_name('txt_export_layer_object_count' + str(index)) txtField_layer_name = self._util.to_ui_name('txtField_export_layer_layer_name' + str(index)) chk_active_name = self._util.to_ui_name('chk_export_layer_active_name' + str(index)) layer_value = self._exportLayers.get(index).get_name() active_value = self._exportLayers.get(index).is_active() cmds.text(txt_object_count_name, label=str(self._exportLayers.get(index).get_size()), annotation='Object count', width=col2_width) layer_btn = cmds.button(label='L', annotation='Layer Menu...', width=col3_width) cmds.textField(txtField_layer_name, text=layer_value, annotation=layer_value, changeCommand=partial(self.update_export_layer, index, txtField_layer_name, 'name')) cmds.button(label='Export', command=partial(self.fbx_export, index)) cmds.checkBox(chk_active_name, value=active_value, label='', changeCommand=partial(self.update_export_layer, index, chk_active_name, 'active')) cmds.iconTextButton(label='^', style='textOnly', font='boldLabelFont', command=partial(self.move_export_layer_up, index), width=col7_width) cmds.iconTextButton(label='v', style='textOnly', font='boldLabelFont', command=partial(self.move_export_layer_down, index), width=col8_width) cmds.setParent(upLevel=True) # Context menu cmds.popupMenu(parent=layer_btn, button=1) cmds.menuItem(label='Add Selected Objects', command=partial(self.add_selected_objects_to_layer, index, txt_object_count_name)) cmds.menuItem(label='Remove Selected Objects', command=partial(self.remove_selected_objects_from_layer, index, txt_object_count_name)) cmds.menuItem(divider=True) cmds.menuItem(label='Empty the Layer', command=partial(self.empty_objects_from_layer, index, txt_object_count_name)) cmds.menuItem(divider=True) cmds.menuItem(label='Select Objects', command=partial(self.select_objects_from_layer, index)) cmds.rowLayout(parent=self._util.to_ui_name('col_export_layers'), numberOfColumns=6, adjustableColumn=3) cmds.separator(style='none', width=col1_width) cmds.button(label=' New ', command=partial(self.add_export_layer)) cmds.separator(style='none') cmds.button(label=' Export All ', command=partial(self.fbx_export, -1)) cmds.separator(style='none', width=col6_width) cmds.separator(style='none', width=col7_width) cmds.setParent(upLevel=True) def _to_range_num(self, num): # Allow 'start' and 'end' as numeric values num = num.strip() if num == 'start' or num == 'end': return num numeric_only = '' for c in num: numeric_only += c if c.isnumeric() or c == '.' else '' num = numeric_only try: return str(int(float(num))) except: return '0'
class Widget: def __init__(self, util, key, type, attribute_name, attribute_type, value_id): self._logger = Logger(self.__class__.__name__) self._util = util self._key = key self._type = type self._attribute_name = attribute_name self._attribute_type = attribute_type self._value_id = value_id self._names = [] def create_new_name(self): # Create widget name name = self._create_widget_name() index = 0 while name in self._names: index += 1 name = self._create_widget_name() + str(index) self._names.append(name) return name def use_settings(self, settings): self._settings = settings def get_key(self): return self._key def get_type(self): return self._type def get_attribute_name(self): return self.attribute_name def get_attribute_type(self): return self.attribute_type def get_names(self): return self._names def get_value(self, default_value = None): value = default_value # Retrieve control's value if self._attribute_type == 'attribute': object_name = self._util.to_node_name(self._attribute_name.rpartition('.')[0]) if cmds.objExists(object_name): attribute_name = self._util.to_node_name(self._attribute_name) value = cmds.getAttr(attribute_name) elif self._attribute_type == 'settings': value = self._settings.load(self._attribute_name, default_value) else: raise Exception('Unknown attribute type ' + str(self._attribute_type)) return value def set_value(self, value): # Update control's value if self._attribute_type == 'attribute': attr_name = self._util.to_node_name(self._attribute_name) obj_name = attr_name.rpartition('.')[0] if not cmds.objExists(obj_name): self._logger.warn('Rig control \'$rig_control\' not found.'.replace('$rig_control', obj_name)) self._logger.command_message('Warn: Rig control not found. See Script Editor for details.') return False cmds.setAttr(self._util.to_node_name(self._attribute_name), value) elif self._attribute_type == 'settings': self._settings.save(self._attribute_name, value) else: raise Exception('Unknown attribute type ' + str(self._attribute_type)) # Update display control's value self.update_display_value() return True def update_display_value(self, default_value = None): value = self.get_value(default_value) for name in self._names: # Update display control's value if self._type == 'menuItem_radioButton': cmds.menuItem(name, edit=True, radioButton=(self._value_id == value)) elif self._type == 'optionMenu': # Verify if the value exists in the options options = [] for menu_item_name in cmds.optionMenu(name, query=True, itemListLong=True): options.append(cmds.menuItem(menu_item_name, query=True, label=True)) if value not in options: if default_value in options: value = default_value else: return cmds.optionMenu(name, edit=True, value=value) else: raise Exception('Unknown widget type ' + self._type) def _create_widget_name(self): return self._util.to_ui_name('$type_$key' \ .replace('$type', self._type) \ .replace('$key', self._key) \ )
class RigACharacterUI: # Base widget sizes _SIZE_WIDGET_WIDTH = 100 _SIZE_WIDGET_HEIGHT = 30 def __init__(self, util, characters_location, window_name): self._logger = Logger(self.__class__.__name__) self._util = util self._skeleton_definition = SkeletonDefinition() self._characters_location = characters_location self._window_name = window_name def show_window(self): if cmds.window(self._window_name, exists=True): return window = cmds.window(self._window_name, title='[Rig-a-thon] Rig a Character', iconName='Rig a Character', sizeable=False, toolbox=True) self._create_ui_panel() cmds.showWindow(window) self._util.perform_maya_2015_window_resize_workaround(self._window_name) def run_script_generate_skeleton(self, *args): title = 'Generate Skeleton' message = 'Select Skeleton Pose: T-Pose or Relaxed Pose?' buttons = ['T-Pose', 'Relaxed Pose', 'Cancel'] result = self._util.custom_message_box(title, message, buttons, buttons[0], buttons[2]) if result == 'T-Pose': self._util.generate_script('01_skeleton_definition_t_pose') self._util.exec_generated_script('01_skeleton_definition_t_pose') elif result == 'Relaxed Pose': self._util.generate_script('01_skeleton_definition_relaxed_pose') self._util.exec_generated_script('01_skeleton_definition_relaxed_pose') def run_script_add_skin_weight_influence(self, *args): self._util.generate_script('02_custom_add_skin_weight_influence') self._util.exec_generated_script('02_custom_add_skin_weight_influence') def run_script_add_anim_locators(self, *args): self._util.generate_script('03_animation_locators') self._util.exec_generated_script('03_animation_locators') def run_script_rig_it(self, *args): self._util.generate_script('04a_pre_rig_script') self._util.generate_script('04b_custom_pre_rig_script') self._util.generate_script('04c_rig_script') self._util.generate_script('04d_post_rig_script') self._util.generate_script('04e_custom_post_rig_script') self._util.exec_generated_script('04a_pre_rig_script') self._util.exec_generated_script('04b_custom_pre_rig_script') self._util.exec_generated_script('04c_rig_script') self._util.exec_generated_script('04d_post_rig_script') self._util.exec_generated_script('04e_custom_post_rig_script') def save_character(self, *args): dialog_title = 'Save Character' last_character_name = self._util.load_from_cache('last_character_name', '') character_name = self._util.dialog_box_ok_cancel(dialog_title, 'Enter a Character Name:', last_character_name) if character_name is None or len(character_name.strip()) == 0: # Save cancelled return self._util.save_to_cache('last_character_name', character_name) character_path = os.path.join(self._characters_location, character_name + '.mb') if os.path.exists(character_path): # Character already exists err_msg = 'Save failed.\n\nCharacter name \'$character_name\' already exists.'.replace('$character_name', character_name) self._util.message_box(dialog_title, err_msg) return if not os.path.isdir(self._characters_location): # Create directory os.makedirs(self._characters_location) cmds.file(rename=character_path) cmds.file(save=True, type='mayaBinary', force=True, prompt=True) if os.path.exists(character_path): # Save successful cmds.file(new=True, force=True) cmds.deleteUI(self._window_name) self._logger.command_message('Character saved as \'$character_path\'.'.replace('$character_path', character_path)) success_msg = 'Save successful.\n\nSee Script Editor for details.' self._util.message_box(dialog_title, success_msg) def run_script_pose_arms_in_t_pose(self, *args): self._util.generate_script('adv_pose_arms_in_t_pose') self._util.exec_generated_script('adv_pose_arms_in_t_pose') def run_script_recreate_skeleton_and_geometry(self, *args): result = self._util.dialog_box_yes_no('Recreate Skeleton and Geometry', 'WARNING! This will detach the skin from the geometry. Are you sure? ', default_button = 'No') if not result: return self._util.generate_script('adv_recreate_skeleton_and_geometry') self._util.exec_generated_script('adv_recreate_skeleton_and_geometry') def run_script_dump_skeleton_definition(self, *args): self._skeleton_definition.dump() def _create_ui_panel(self): widget_width = self._SIZE_WIDGET_WIDTH widget_height = self._SIZE_WIDGET_HEIGHT cmds.columnLayout(adjustableColumn=True) cmds.frameLayout(label='Rigging Scripts', borderStyle='etchedOut', collapsable=False, collapse=False, enable=True) cmds.rowColumnLayout(numberOfColumns=1) cmds.button(label='Generate Skeleton', command=partial(self.run_script_generate_skeleton), width=widget_width*2, height=widget_height) cmds.button(label='Add Skin Weight Influence', command=partial(self.run_script_add_skin_weight_influence), width=widget_width*2, height=widget_height) cmds.button(label='Add Anim Locators', command=partial(self.run_script_add_anim_locators), width=widget_width*2, height=widget_height) cmds.button(label='Rig It!', command=partial(self.run_script_rig_it), width=widget_width*2, height=widget_height) cmds.button(label='Save Character', command=partial(self.save_character), width=widget_width*2, height=widget_height) cmds.setParent(upLevel=True) cmds.setParent(upLevel=True) cmds.frameLayout(label='Advanced', borderStyle='etchedOut', collapsable=True, collapse=True, enable=True) cmds.rowColumnLayout(numberOfColumns=1) cmds.button(label='Pose Arms in T-Pose', command=partial(self.run_script_pose_arms_in_t_pose), width=widget_width*2, height=widget_height) cmds.button(label='Recreate Skeleton and Geometry', command=partial(self.run_script_recreate_skeleton_and_geometry), width=widget_width*2, height=widget_height) cmds.button(label='Dump Skeleton Definition', command=partial(self.run_script_dump_skeleton_definition), width=widget_width*2, height=widget_height) cmds.setParent(upLevel=True) cmds.setParent(upLevel=True) cmds.setParent(upLevel=True)
def __init__(self, util, characters_location, window_name): self._logger = Logger(self.__class__.__name__) self._util = util self._skeleton_definition = SkeletonDefinition() self._characters_location = characters_location self._window_name = window_name