diff --git a/client/ayon_maya/api/plugin.py b/client/ayon_maya/api/plugin.py index cc5fae16..641e321c 100644 --- a/client/ayon_maya/api/plugin.py +++ b/client/ayon_maya/api/plugin.py @@ -789,7 +789,8 @@ def load( loaded_containers = [] for c in range(0, count): - namespace = lib.get_custom_namespace(custom_namespace) + if not namespace: + namespace = lib.get_custom_namespace(custom_namespace) group_name = "{}:{}".format( namespace, custom_group_name diff --git a/client/ayon_maya/plugins/load/load_anim.py b/client/ayon_maya/plugins/load/load_anim.py new file mode 100644 index 00000000..5e385c06 --- /dev/null +++ b/client/ayon_maya/plugins/load/load_anim.py @@ -0,0 +1,121 @@ +import os +import json + +import maya.cmds as cmds +import pymel.core as pm + +from ayon_maya.api import plugin +from ayon_core.pipeline.load.utils import get_representation_context +from ayon_core.pipeline.load import get_loaders_by_name +from ayon_maya.api import lib + + +class AnimLoader(plugin.Loader): + """Load anim on character""" + + product_types = {"animation"} + representations = {"anim"} + + label = "Load Anim" + order = -5 + icon = "code-fork" + color = "orange" + + def load(self, context, name, namespace, data): + project_name = context['project']['name'] + anim_file = self.filepath_from_context(context) + assets = context['version']['data'].get("assets", []) + current_asset = [asset for asset in assets if asset['namespace'] in anim_file] + if not current_asset: + self.log.warning(f"Asset not found in version data for animation file: {anim_file}") + return + current_asset = current_asset[0] + asset_context = get_representation_context(project_name, current_asset['representation_id']) + # check if the asset is already loaded + if not cmds.namespace(exists=current_asset['namespace']): + self.log.info(f"Asset namespace {current_asset['namespace']} does not exist, loading asset.") + data['attach_to_root'] = True + loader_classes = get_loaders_by_name() + reference_loader = loader_classes.get('ReferenceLoader')() + reference_loader.load(context=asset_context, name=asset_context['product']['name'], + namespace=current_asset['namespace'], options=data) + ctrl_set = pm.ls(f"{current_asset['namespace']}:{asset_context['product']['name']}_controls_SET") + if not ctrl_set: + self.log.warning("No control set found in instance data") + return + ctrls = pm.listConnections(ctrl_set[0], source=1, type='transform') + if not ctrls: + self.log.warning("No controls found in instance data") + return + self.read_anim(filepath=anim_file, objects=ctrls) + + + def read_anim(self, filepath, objects, namespace=None): + if not os.path.exists(filepath): + return [False, 'invalid filepath'] + with open(filepath, "r", encoding='utf-8') as reader: + anim_data = json.loads(reader.read()) + for j, obj in enumerate(objects): + obj_shot_name = obj.name() + obj_longname = obj.longName() + if namespace: + obj_longname = obj_longname.replace(namespace, '{namespace}') + ctrl_value = anim_data.get(obj_longname, []) + if not ctrl_value: + continue + for attrs in ctrl_value: + if not hasattr(obj, attrs): + continue + if attrs in ['lock']: + continue + try: + cur_attr = getattr(obj, attrs) + except AttributeError as e: + self.log.warning(e) + self.log.warning('skipping for {0} as attribute {1} was not found'.format(obj_shot_name, attrs)) + continue + key_type = ctrl_value[attrs]['type'] + if key_type == 'static': + key_value = ctrl_value[attrs]['value'] + connected = pm.listConnections(cur_attr, destination=False, source=True) + if not connected and not cur_attr.isLocked(): + cur_attr.set(key_value) + if key_type == 'keyed': + key_values = ctrl_value[attrs]['keys'] + infinity_data = ctrl_value[attrs]['infinity'] + pre_infinity = json.loads(infinity_data.get('preInfinity')) + post_infinity = json.loads(infinity_data.get('postInfinity')) + weighted_tangents = json.loads(infinity_data.get('weightedTangents')) + for keys in key_values: + time = json.loads(keys.get('key')) + value = json.loads(keys.get('value')) + breakdown = json.loads(keys.get('breakdown')) + tan_lock = json.loads(keys.get('lock')) + weight_lock = json.loads(keys.get('weightLock')) + in_type = json.loads(keys.get('inTangentType')) + out_type = json.loads(keys.get('outTangentType')) + tan1 = json.loads(keys.get('inAngle')) + tan2 = json.loads(keys.get('outAngle')) + weight1 = json.loads(keys.get('inWeight')) + weight2 = json.loads(keys.get('outWeight')) + pm.setKeyframe(cur_attr, time=time, value=value, bd=breakdown) + if weighted_tangents: + pm.keyTangent(cur_attr, weightedTangents=True, edit=True) + try: + pm.keyTangent(cur_attr, lock=tan_lock, time=time) + except Exception as e: + self.log.warning(e) + + if weighted_tangents: + pm.keyTangent(cur_attr, time=time, weightLock=weight_lock) + if in_type != 'fixed' and out_type != 'fixed': + pm.keyTangent(cur_attr, e=1, a=1, time=time, itt=in_type, ott=out_type) + if in_type == 'fixed' and out_type != 'fixed': + pm.keyTangent(cur_attr, e=1, a=1, time=time, inAngle=tan1, inWeight=weight1, itt=in_type, + ott=out_type) + if in_type == 'fixed' and out_type == 'fixed': + pm.keyTangent(cur_attr, e=1, a=1, time=time, inAngle=tan1, inWeight=weight1, outAngle=tan2, + outWeight=weight2, itt=in_type, ott=out_type) + + pm.setInfinity(cur_attr, poi=post_infinity, pri=pre_infinity) + return None diff --git a/client/ayon_maya/plugins/publish/export_anim_curve.py b/client/ayon_maya/plugins/publish/export_anim_curve.py new file mode 100644 index 00000000..062f1078 --- /dev/null +++ b/client/ayon_maya/plugins/publish/export_anim_curve.py @@ -0,0 +1,144 @@ +import os +import json +import pymel.core as pm +import maya.cmds as cmds + +from ayon_maya.api import plugin, pipeline +from pyblish.api import ExtractorOrder + + +class ExtractAnimCurve(plugin.MayaExtractorPlugin): + order = ExtractorOrder + label = "Extract Animation curves" + families = ["animation"] + hosts = ["maya"] + + def process(self, instance): + staging_dir = self.staging_dir(instance) + filename = "{0}.anim".format(instance.data['variant']) + out_path = os.path.join(staging_dir, filename) + controls = [x for x in instance.data['setMembers'] if x.endswith("controls_SET")] + if not controls: + self.log.warning("No controls found in instance data") + return + ctrls = pm.listConnections(controls[0], source=1, type='transform') + self.log.info(f"controls: {controls}") + self.write_anim(objects=ctrls, filepath=os.path.realpath(out_path)) + if "representations" not in instance.data: + instance.data["representations"] = [] + representation = { + 'name': 'anim', + 'ext': 'anim', + 'files': os.path.basename(out_path), + 'stagingDir': staging_dir.replace("\\", "/") + } + version_data = instance.data.get("versionData", {}) + assets = version_data.get("assets", []) + if not assets: + version_data["assets"] = [] + asset_data = self.get_asset_data(instance) + version_data["assets"].append(asset_data) + instance.data["versionData"] = version_data + self.log.info(f"representation: {representation}") + instance.data["representations"].append(representation) + + @staticmethod + def get_asset_data(instance): + members = [member.lstrip('|') for member in instance.data['setMembers']] + grp_name = members[0].split(':')[0] + containers = cmds.ls("{}*_CON".format(grp_name)) + rep_id = cmds.getAttr(containers[0] + '.representation') + name_space = cmds.getAttr(containers[0] + '.namespace') + product_name = cmds.getAttr(containers[0] + '.name') + asset_data = { + "namespace": name_space, + "product_name": product_name, + "representation_id": rep_id + } + return asset_data + + def write_anim(self, objects, filepath, namespace=None): + self.log.info(f"objects: {objects}") + self.log.info(f"Writing animation curves to {filepath}") + self.log.info(f"namespace: {namespace}") + anim_data = {} + for j, obj in enumerate(objects): + obj_shot_name = obj.name() + obj_longname = obj.longName() + if namespace: + obj_longname = obj_longname.replace(namespace, '{namespace}') + anim_data[obj_longname] = {} + + channels = obj.listConnections(type='animCurve', connections=True, s=1, d=0) + channel_dict = {} + for i, channel in enumerate(channels): + channel = channel[1] + split_name = obj_shot_name + channel_name = (channels[i][0].name().split(split_name + '.')[1]) + if channel_name not in channel_dict: + channel_dict[channel_name] = {} + channel_dict[channel_name]['type'] = 'keyed' + + keys = pm.animation.keyframe(channel, q=True) + values = pm.animation.keyframe(channel, q=True, valueChange=True) + breakdown = pm.animation.keyframe(channel, q=True, breakdown=True) + in_tangent_type = pm.animation.keyTangent(channel, q=True, inTangentType=True) + out_tangent_type = pm.animation.keyTangent(channel, q=True, outTangentType=True) + lock = pm.animation.keyTangent(channel, q=True, lock=True) + weight_lock = pm.animation.keyTangent(channel, q=True, weightLock=True) + in_angle = pm.animation.keyTangent(channel, q=True, inAngle=True) + out_angle = pm.animation.keyTangent(channel, q=True, outAngle=True) + in_weight = pm.animation.keyTangent(channel, q=True, inWeight=True) + out_weight = pm.animation.keyTangent(channel, q=True, outWeight=True) + weighted_tangents = pm.animation.keyTangent(channel, q=True, weightedTangents=True)[0] + + pre_infinity = channel.preInfinity.get() + post_infinity = channel.postInfinity.get() + channel_dict[channel_name]['infinity'] = { + 'preInfinity': json.dumps(pre_infinity), + 'postInfinity': json.dumps(post_infinity), + 'weightedTangents': json.dumps(weighted_tangents), + } + + channel_dict[channel_name]['keys'] = [] + for y, key in enumerate(keys): + bd = 0 + for bd_item in breakdown: + if bd_item == key: + bd = 1 + channel_dict[channel_name]['keys'].append({ + 'key': json.dumps(keys[y]), + 'value': json.dumps(values[y]), + 'breakdown': json.dumps(bd), + 'inTangentType': json.dumps(in_tangent_type[y]), + 'outTangentType': json.dumps(out_tangent_type[y]), + 'lock': json.dumps(lock[y]), + 'weightLock': json.dumps(weight_lock[y]), + 'inAngle': json.dumps(in_angle[y]), + 'outAngle': json.dumps(out_angle[y]), + 'inWeight': json.dumps(in_weight[y]), + 'outWeight': json.dumps(out_weight[y]) + }) + static_chans = pm.listAnimatable(obj) + for static_chan in static_chans: + test_it = pm.keyframe(static_chan, q=True) + connected = pm.listConnections(static_chan, destination=False, source=True) + if test_it or connected: + logger.warning('skipping for {0} as attribute {1} is connected'.format(obj_shot_name, static_chan)) + continue + if pm.nodeType(static_chan.name().split(".")[0]) == "camera": + static_name = static_chan.name().split('.')[1] + else: + static_name = static_chan.name().split(obj_shot_name + '.') + if not len(static_name) > 1: + continue + static_name = static_name[1] + if static_name not in channel_dict: + channel_dict[static_name] = {'type': 'static'} + channel_dict[static_name]['value'] = static_chan.get() + anim_data[obj_longname] = channel_dict + if not os.path.exists(os.path.dirname(filepath)): + os.makedirs(os.path.dirname(filepath)) + with open(filepath, 'w') as json_file: + json.dump(anim_data, json_file, indent=4) + return filepath