mottosso / cmdx

Fast and persistent subset of maya.cmds
https://mottosso.com/cmdx
BSD 2-Clause "Simplified" License
193 stars 36 forks source link

Animation Import/Export #6

Open mottosso opened 4 years ago

mottosso commented 4 years ago

Compatible with Maya 2015 SP4 --> Maya 2021+

Out of the many ways to export/import animation in Maya, from the built-in .atom format from 2013 to the .anim file format form 1993, here's a different take.

  1. ---> Export related animCurve nodes as a mayaAscii scene
  2. <--- Import animCurve nodes from the mayaAscii scene, elsewhere
  3. -><- Connect each node to their corresponding target

It is..

With a few caveats..

Each of which could potentially be solved with some further tinkering.

animio1


Usage

  1. Download cmdx to your ~/maya/scripts folder
  2. Copy/paste the implementation below, and use it like this..

Based on your current selection

# Custom suffix
fname = cmds.file("anim1.manim", expandName=True, query=True)

# From scene A
export_animation(fname)

# From scene B
import_animation(fname)


Implementation

import cmdx
from maya import cmds  # for select()

def export_animation(fname):
    """Export animation for selected nodes to `fname`

    Animation is exported as native Maya nodes, e.g. animCurveTU
    and later imported and re-connected to their original nodes

    Limitations:
        - No animation layers
        - Names much match exactly between exported and imported nodes

    """

    animation = []
    for node in cmdx.selection(type="transform"):  # Optionally limited to transform nodes

        # Find any curve connecting to this node
        for curve in node.connections():
            if not isinstance(curve, cmdx.AnimCurve):
                continue

            # Encode target connection as string attribute
            # for retrieval during `import_animation`
            if not curve.has_attr("target"):
                curve["target"] = cmdx.String()

            # Find the attribute to which this curve connects
            plug = curve.connection(plug=True)
            curve["target"] = plug.path()

            animation.append(curve)

    if not animation:
        cmds.warning(
            "Select objects in the scene to "
            "export any connected animation"
        )

        return cmds.warning("No animation found, see Script Editor for details")

    previous_selection = cmds.ls(selection=True)
    cmds.select(map(str, animation))

    try:
        cmds.file(
            fname,

            # Overwrite existing
            force=True,

            # Use our own suffix
            defaultExtensions=False,

            # Internal format, despite our format
            type="mayaAscii",

            exportSelected=True,

            # We don't want anything but the selected animation curves
            constructionHistory=False
        )

    except Exception:
        import traceback
        traceback.print_exc()
        cmds.warning("Something unexpected happened when trying to export, see Script Editor for details.")

    finally:
        cmds.select(previous_selection)

    print("Successfully exported '%s'" % fname)

def import_animation(fname):
    previous_selection = cmds.ls(selection=True)

    try:
        animation = cmds.file(fname, i=True, returnNewNodes=True)

    except Exception:
        import traceback
        traceback.print_exc()
        return cmds.warning(
            "Something unexpected happened when trying "
            "to import '%s', see Script Editor for details"
            % fname
        )

    finally:
        cmds.select(previous_selection)

    for curve in animation:
        curve = cmdx.encode(curve)

        if not curve.has_attr("target"):
            cmds.warning("Skipped: '%s' did not have the `target` "
                                  "attribute used to reconnect the animation"
                                  % curve)
            continue

        # Stored as "<node>.<attr>" e.g. "pCube1.tx"
        node, attr = curve["target"].read().rsplit(".", 1)

        try:
            target = cmdx.encode(node)
        except cmdx.ExistError:
            cmds.warning("Skipped: '%s' did not exist" % node)
            continue

        # Make the connection, replacing any existing
        curve["output"] >> target[attr]

    print("Successfully imported '%s'!" % fname)


Next Steps

  1. Avoid nameclashes When importing animation the second time, the curve nodes share a name which throws a warning. By importing into a namespace, any node matching the name outside of this namespace can be removed prior to import
  2. Search-and-replace To support alternative names, namespaces and multiple instances of the same character or scene
  3. Animation Layers It'd be a matter of exporting not just the curve, but the network leading into the node being animated, which would include the animation layer setup.

An example of how names clash.

animio2


When to Use

That is, why not use .anim or .atom? If you can, you probably should. The are already installed (or are they?), they've got documentation (or do they?), they've got more features (of value?) and others may be able to help out when things go south (or can they?).

I would use it when what I want is to export/import animation from one scene to another without fuss; when I don't remember my login details to HighEnd3D and aren't interested in experimenting with the various MEL alternatives on there, and when I haven't got the budget nor time to understand or implement support for ATOM.

YMMV :) Let me know what you think!