Ejemplo n.º 1
0
	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
Ejemplo n.º 2
0
	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
Ejemplo n.º 3
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)
Ejemplo n.º 6
0
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))
Ejemplo n.º 7
0
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'
Ejemplo n.º 8
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