Skip to content

fbx

Tools to work with FBX.

FBXExtractor

Extract FBX from Maya.

This extracts reproducible FBX exports ignoring any of the settings set on the local machine in the FBX export options window.

All export settings are applied with the FBXExport* commands prior to the FBXExport call itself. The options can be overridden with their nice names as seen in the "options" property on this class.

For more information on FBX exports see: - https://knowledge.autodesk.com/support/maya/learn-explore/caas /CloudHelp/cloudhelp/2016/ENU/Maya/files/GUID-6CCE943A-2ED4-4CEE-96D4 -9CB19C28F4E0-htm.html - http://forums.cgsociety.org/archive/index.php?t-1032853.html - https://groups.google.com/forum/#!msg/python_inside_maya/cLkaSo361oE /LKs9hakE28kJ

Source code in client/ayon_maya/api/fbx.py
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
class FBXExtractor:
    """Extract FBX from Maya.

        This extracts reproducible FBX exports ignoring any of the settings set
        on the local machine in the FBX export options window.

        All export settings are applied with the `FBXExport*` commands prior
        to the `FBXExport` call itself. The options can be overridden with
        their
        nice names as seen in the "options" property on this class.

        For more information on FBX exports see:
        - https://knowledge.autodesk.com/support/maya/learn-explore/caas
        /CloudHelp/cloudhelp/2016/ENU/Maya/files/GUID-6CCE943A-2ED4-4CEE-96D4
        -9CB19C28F4E0-htm.html
        - http://forums.cgsociety.org/archive/index.php?t-1032853.html
        - https://groups.google.com/forum/#!msg/python_inside_maya/cLkaSo361oE
        /LKs9hakE28kJ

        """
    @property
    def options(self):
        """Overridable options for FBX Export

        Given in the following format
            - {NAME: EXPECTED TYPE}

        If the overridden option's type does not match,
        the option is not included and a warning is logged.

        """

        return {
            "cameras": bool,
            "smoothingGroups": bool,
            "hardEdges": bool,
            "tangents": bool,
            "smoothMesh": bool,
            "instances": bool,
            # "referencedContainersContent": bool, # deprecated in Maya 2016+
            "bakeComplexAnimation": bool,
            "bakeComplexStart": int,
            "bakeComplexEnd": int,
            "bakeComplexStep": int,
            "bakeResampleAnimation": bool,
            "useSceneName": bool,
            "quaternion": str,  # "euler"
            "shapes": bool,
            "skins": bool,
            "constraints": bool,
            "lights": bool,
            "embeddedTextures": bool,
            "includeChildren": bool,
            "inputConnections": bool,
            "upAxis": str,  # x, y or z,
            "triangulate": bool,
            "fileVersion": str,
            "skeletonDefinitions": bool,
            "referencedAssetsContent": bool
        }

    @property
    def default_options(self):
        """The default options for FBX extraction.

        This includes shapes, skins, constraints, lights and incoming
        connections and exports with the Y-axis as up-axis.

        By default this uses the time sliders start and end time.

        """

        start_frame = int(cmds.playbackOptions(query=True,
                                               animationStartTime=True))
        end_frame = int(cmds.playbackOptions(query=True,
                                             animationEndTime=True))

        return {
            "cameras": False,
            "smoothingGroups": True,
            "hardEdges": False,
            "tangents": False,
            "smoothMesh": True,
            "instances": False,
            "bakeComplexAnimation": True,
            "bakeComplexStart": start_frame,
            "bakeComplexEnd": end_frame,
            "bakeComplexStep": 1,
            "bakeResampleAnimation": True,
            "useSceneName": False,
            "quaternion": "euler",
            "shapes": True,
            "skins": True,
            "constraints": False,
            "lights": True,
            "embeddedTextures": False,
            "includeChildren": True,
            "inputConnections": True,
            "upAxis": "y",
            "triangulate": False,
            "fileVersion": "FBX202000",
            "skeletonDefinitions": False,
            "referencedAssetsContent": False
        }

    def __init__(self, log=None):
        # Ensure FBX plug-in is loaded
        self.log = log or logging.getLogger(self.__class__.__name__)
        cmds.loadPlugin("fbxmaya", quiet=True)

    def parse_overrides(self, instance, options):
        """Inspect data of instance to determine overridden options

        An instance may supply any of the overridable options
        as data, the option is then added to the extraction.

        """

        for key in instance.data:
            if key not in self.options:
                continue

            # Ensure the data is of correct type
            value = instance.data[key]
            if not isinstance(value, self.options[key]):
                self.log.warning(
                    "Overridden attribute {key} was of "
                    "the wrong type: {invalid_type} "
                    "- should have been {valid_type}".format(
                        key=key,
                        invalid_type=type(value).__name__,
                        valid_type=self.options[key].__name__))
                continue

            options[key] = value

        return options

    def set_options_from_instance(self, instance):
        """Sets FBX export options from data in the instance.

        Args:
            instance (Instance): Instance data.

        """
        # Parse export options
        options = self.default_options
        options = self.parse_overrides(instance, options)
        self.log.debug("Export options: {0}".format(options))

        # Collect the start and end including handles
        start = instance.data.get("frameStartHandle") or \
            instance.context.data.get("frameStartHandle")
        end = instance.data.get("frameEndHandle") or \
            instance.context.data.get("frameEndHandle")

        options['bakeComplexStart'] = start
        options['bakeComplexEnd'] = end

        # First apply the default export settings to be fully consistent
        # each time for successive publishes
        mel.eval("FBXResetExport")

        # Apply the FBX overrides through MEL since the commands
        # only work correctly in MEL according to online
        # available discussions on the topic
        _iteritems = getattr(options, "iteritems", options.items)
        for option, value in _iteritems():
            key = option[0].upper() + option[1:]  # uppercase first letter

            # Boolean must be passed as lower-case strings
            # as to MEL standards
            if isinstance(value, bool):
                value = str(value).lower()

            template = "FBXExport{0} {1}" if key == "UpAxis" else \
                "FBXExport{0} -v {1}"  # noqa
            cmd = template.format(key, value)
            self.log.debug(cmd)
            mel.eval(cmd)

        # Never show the UI or generate a log
        mel.eval("FBXExportShowUI -v false")
        mel.eval("FBXExportGenerateLog -v false")

    @staticmethod
    def export(members, path):
        # type: (list, str) -> None
        """Export members as FBX with given path.

        Args:
            members (list): List of members to export.
            path (str): Path to use for export.

        """
        # The export requires forward slashes because we need
        # to format it into a string in a mel expression
        path = path.replace("\\", "/")
        with maintained_selection():
            cmds.select(members, r=True, noExpand=True)
            mel.eval('FBXExport -f "{}" -s'.format(path))

default_options property

The default options for FBX extraction.

This includes shapes, skins, constraints, lights and incoming connections and exports with the Y-axis as up-axis.

By default this uses the time sliders start and end time.

options property

Overridable options for FBX Export

Given in the following format - {NAME: EXPECTED TYPE}

If the overridden option's type does not match, the option is not included and a warning is logged.

export(members, path) staticmethod

Export members as FBX with given path.

Parameters:

Name Type Description Default
members list

List of members to export.

required
path str

Path to use for export.

required
Source code in client/ayon_maya/api/fbx.py
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
@staticmethod
def export(members, path):
    # type: (list, str) -> None
    """Export members as FBX with given path.

    Args:
        members (list): List of members to export.
        path (str): Path to use for export.

    """
    # The export requires forward slashes because we need
    # to format it into a string in a mel expression
    path = path.replace("\\", "/")
    with maintained_selection():
        cmds.select(members, r=True, noExpand=True)
        mel.eval('FBXExport -f "{}" -s'.format(path))

parse_overrides(instance, options)

Inspect data of instance to determine overridden options

An instance may supply any of the overridable options as data, the option is then added to the extraction.

Source code in client/ayon_maya/api/fbx.py
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
def parse_overrides(self, instance, options):
    """Inspect data of instance to determine overridden options

    An instance may supply any of the overridable options
    as data, the option is then added to the extraction.

    """

    for key in instance.data:
        if key not in self.options:
            continue

        # Ensure the data is of correct type
        value = instance.data[key]
        if not isinstance(value, self.options[key]):
            self.log.warning(
                "Overridden attribute {key} was of "
                "the wrong type: {invalid_type} "
                "- should have been {valid_type}".format(
                    key=key,
                    invalid_type=type(value).__name__,
                    valid_type=self.options[key].__name__))
            continue

        options[key] = value

    return options

set_options_from_instance(instance)

Sets FBX export options from data in the instance.

Parameters:

Name Type Description Default
instance Instance

Instance data.

required
Source code in client/ayon_maya/api/fbx.py
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
def set_options_from_instance(self, instance):
    """Sets FBX export options from data in the instance.

    Args:
        instance (Instance): Instance data.

    """
    # Parse export options
    options = self.default_options
    options = self.parse_overrides(instance, options)
    self.log.debug("Export options: {0}".format(options))

    # Collect the start and end including handles
    start = instance.data.get("frameStartHandle") or \
        instance.context.data.get("frameStartHandle")
    end = instance.data.get("frameEndHandle") or \
        instance.context.data.get("frameEndHandle")

    options['bakeComplexStart'] = start
    options['bakeComplexEnd'] = end

    # First apply the default export settings to be fully consistent
    # each time for successive publishes
    mel.eval("FBXResetExport")

    # Apply the FBX overrides through MEL since the commands
    # only work correctly in MEL according to online
    # available discussions on the topic
    _iteritems = getattr(options, "iteritems", options.items)
    for option, value in _iteritems():
        key = option[0].upper() + option[1:]  # uppercase first letter

        # Boolean must be passed as lower-case strings
        # as to MEL standards
        if isinstance(value, bool):
            value = str(value).lower()

        template = "FBXExport{0} {1}" if key == "UpAxis" else \
            "FBXExport{0} -v {1}"  # noqa
        cmd = template.format(key, value)
        self.log.debug(cmd)
        mel.eval(cmd)

    # Never show the UI or generate a log
    mel.eval("FBXExportShowUI -v false")
    mel.eval("FBXExportGenerateLog -v false")