mirror of
https://github.com/ynput/ayon-core.git
synced 2025-12-24 21:04:40 +01:00
223 lines
7.1 KiB
Python
223 lines
7.1 KiB
Python
import os
|
|
|
|
from maya import cmds
|
|
import maya.mel as mel
|
|
|
|
import pyblish.api
|
|
import avalon.maya
|
|
|
|
import pype.api
|
|
|
|
|
|
class ExtractFBX(pype.api.Extractor):
|
|
"""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
|
|
|
|
"""
|
|
|
|
order = pyblish.api.ExtractorOrder
|
|
label = "Extract FBX"
|
|
families = ["fbx"]
|
|
|
|
@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": int,
|
|
"bakeComplexStart": int,
|
|
"bakeComplexEnd": int,
|
|
"bakeComplexStep": int,
|
|
"bakeResampleAnimation": bool,
|
|
"animationOnly": bool,
|
|
"useSceneName": bool,
|
|
"quaternion": str, # "euler"
|
|
"shapes": bool,
|
|
"skins": bool,
|
|
"constraints": bool,
|
|
"lights": bool,
|
|
"embeddedTextures": bool,
|
|
"inputConnections": bool,
|
|
"upAxis": str, # x, y or z,
|
|
"triangulate": 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": False,
|
|
"hardEdges": False,
|
|
"tangents": False,
|
|
"smoothMesh": False,
|
|
"instances": False,
|
|
"bakeComplexAnimation": True,
|
|
"bakeComplexStart": start_frame,
|
|
"bakeComplexEnd": end_frame,
|
|
"bakeComplexStep": 1,
|
|
"bakeResampleAnimation": True,
|
|
"animationOnly": False,
|
|
"useSceneName": False,
|
|
"quaternion": "euler",
|
|
"shapes": True,
|
|
"skins": True,
|
|
"constraints": False,
|
|
"lights": True,
|
|
"embeddedTextures": True,
|
|
"inputConnections": True,
|
|
"upAxis": "y",
|
|
"triangulate": False
|
|
}
|
|
|
|
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 process(self, instance):
|
|
|
|
# Ensure FBX plug-in is loaded
|
|
cmds.loadPlugin("fbxmaya", quiet=True)
|
|
|
|
# Define output path
|
|
stagingDir = self.staging_dir(instance)
|
|
filename = "{0}.fbx".format(instance.name)
|
|
path = os.path.join(stagingDir, filename)
|
|
|
|
# The export requires forward slashes because we need
|
|
# to format it into a string in a mel expression
|
|
path = path.replace('\\', '/')
|
|
|
|
self.log.info("Extracting FBX to: {0}".format(path))
|
|
|
|
members = instance.data["setMembers"]
|
|
self.log.info("Members: {0}".format(members))
|
|
self.log.info("Instance: {0}".format(instance[:]))
|
|
|
|
# Parse export options
|
|
options = self.default_options
|
|
options = self.parse_overrides(instance, options)
|
|
self.log.info("Export options: {0}".format(options))
|
|
|
|
# Collect the start and end including handles
|
|
start = instance.data["startFrame"]
|
|
end = instance.data["endFrame"]
|
|
handles = instance.data.get("handles", 0)
|
|
if handles:
|
|
start -= handles
|
|
end += handles
|
|
|
|
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
|
|
for option, value in options.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} -v {1}"
|
|
if key == "UpAxis":
|
|
template = "FBXExport{0} {1}"
|
|
|
|
cmd = template.format(key, value)
|
|
self.log.info(cmd)
|
|
mel.eval(cmd)
|
|
|
|
# Never show the UI or generate a log
|
|
mel.eval("FBXExportShowUI -v false")
|
|
mel.eval("FBXExportGenerateLog -v false")
|
|
|
|
# Export
|
|
with avalon.maya.maintained_selection():
|
|
cmds.select(members, r=1, noExpand=True)
|
|
mel.eval('FBXExport -f "{}" -s'.format(path))
|
|
|
|
if "representations" not in instance.data:
|
|
instance.data["representations"] = []
|
|
|
|
representation = {
|
|
'name': 'mov',
|
|
'ext': '.mov',
|
|
'files': filename,
|
|
"stagingDir": stagingDir,
|
|
}
|
|
instance.data["representations"].append(representation)
|
|
|
|
|
|
self.log.info("Extract FBX successful to: {0}".format(path))
|