ayon-core/pype/vendor/capture_gui/lib.py
2018-11-29 23:33:36 +01:00

396 lines
12 KiB
Python

# TODO: fetch Maya main window without shiboken that also doesn't crash
import sys
import logging
import json
import os
import glob
import subprocess
import contextlib
from collections import OrderedDict
import datetime
import maya.cmds as cmds
import maya.mel as mel
import maya.OpenMayaUI as omui
import capture
from .vendor.Qt import QtWidgets
try:
# PySide1
import shiboken
except ImportError:
# PySide2
import shiboken2 as shiboken
log = logging.getLogger(__name__)
# region Object types
OBJECT_TYPES = OrderedDict()
OBJECT_TYPES['NURBS Curves'] = 'nurbsCurves'
OBJECT_TYPES['NURBS Surfaces'] = 'nurbsSurfaces'
OBJECT_TYPES['NURBS CVs'] = 'controlVertices'
OBJECT_TYPES['NURBS Hulls'] = 'hulls'
OBJECT_TYPES['Polygons'] = 'polymeshes'
OBJECT_TYPES['Subdiv Surfaces'] = 'subdivSurfaces'
OBJECT_TYPES['Planes'] = 'planes'
OBJECT_TYPES['Lights'] = 'lights'
OBJECT_TYPES['Cameras'] = 'cameras'
OBJECT_TYPES['Image Planes'] = 'imagePlane'
OBJECT_TYPES['Joints'] = 'joints'
OBJECT_TYPES['IK Handles'] = 'ikHandles'
OBJECT_TYPES['Deformers'] = 'deformers'
OBJECT_TYPES['Dynamics'] = 'dynamics'
OBJECT_TYPES['Particle Instancers'] = 'particleInstancers'
OBJECT_TYPES['Fluids'] = 'fluids'
OBJECT_TYPES['Hair Systems'] = 'hairSystems'
OBJECT_TYPES['Follicles'] = 'follicles'
OBJECT_TYPES['nCloths'] = 'nCloths'
OBJECT_TYPES['nParticles'] = 'nParticles'
OBJECT_TYPES['nRigids'] = 'nRigids'
OBJECT_TYPES['Dynamic Constraints'] = 'dynamicConstraints'
OBJECT_TYPES['Locators'] = 'locators'
OBJECT_TYPES['Dimensions'] = 'dimensions'
OBJECT_TYPES['Pivots'] = 'pivots'
OBJECT_TYPES['Handles'] = 'handles'
OBJECT_TYPES['Textures Placements'] = 'textures'
OBJECT_TYPES['Strokes'] = 'strokes'
OBJECT_TYPES['Motion Trails'] = 'motionTrails'
OBJECT_TYPES['Plugin Shapes'] = 'pluginShapes'
OBJECT_TYPES['Clip Ghosts'] = 'clipGhosts'
OBJECT_TYPES['Grease Pencil'] = 'greasePencils'
OBJECT_TYPES['Manipulators'] = 'manipulators'
OBJECT_TYPES['Grid'] = 'grid'
OBJECT_TYPES['HUD'] = 'hud'
# endregion Object types
def get_show_object_types():
results = OrderedDict()
# Add the plug-in shapes
plugin_shapes = get_plugin_shapes()
results.update(plugin_shapes)
# We add default shapes last so plug-in shapes could
# never potentially overwrite any built-ins.
results.update(OBJECT_TYPES)
return results
def get_current_scenename():
path = cmds.file(query=True, sceneName=True)
if path:
return os.path.splitext(os.path.basename(path))[0]
return None
def get_current_camera():
"""Returns the currently active camera.
Searched in the order of:
1. Active Panel
2. Selected Camera Shape
3. Selected Camera Transform
Returns:
str: name of active camera transform
"""
# Get camera from active modelPanel (if any)
panel = cmds.getPanel(withFocus=True)
if cmds.getPanel(typeOf=panel) == "modelPanel":
cam = cmds.modelEditor(panel, query=True, camera=True)
# In some cases above returns the shape, but most often it returns the
# transform. Still we need to make sure we return the transform.
if cam:
if cmds.nodeType(cam) == "transform":
return cam
# camera shape is a shape type
elif cmds.objectType(cam, isAType="shape"):
parent = cmds.listRelatives(cam, parent=True, fullPath=True)
if parent:
return parent[0]
# Check if a camShape is selected (if so use that)
cam_shapes = cmds.ls(selection=True, type="camera")
if cam_shapes:
return cmds.listRelatives(cam_shapes,
parent=True,
fullPath=True)[0]
# Check if a transform of a camShape is selected
# (return cam transform if any)
transforms = cmds.ls(selection=True, type="transform")
if transforms:
cam_shapes = cmds.listRelatives(transforms, shapes=True, type="camera")
if cam_shapes:
return cmds.listRelatives(cam_shapes,
parent=True,
fullPath=True)[0]
def get_active_editor():
"""Return the active editor panel to playblast with"""
# fixes `cmds.playblast` undo bug
cmds.currentTime(cmds.currentTime(query=True))
panel = cmds.playblast(activeEditor=True)
return panel.split("|")[-1]
def get_current_frame():
return cmds.currentTime(query=True)
def get_time_slider_range(highlighted=True,
withinHighlighted=True,
highlightedOnly=False):
"""Return the time range from Maya's time slider.
Arguments:
highlighted (bool): When True if will return a selected frame range
(if there's any selection of more than one frame!) otherwise it
will return min and max playback time.
withinHighlighted (bool): By default Maya returns the highlighted range
end as a plus one value. When this is True this will be fixed by
removing one from the last number.
Returns:
list: List of two floats of start and end frame numbers.
"""
if highlighted is True:
gPlaybackSlider = mel.eval("global string $gPlayBackSlider; "
"$gPlayBackSlider = $gPlayBackSlider;")
if cmds.timeControl(gPlaybackSlider, query=True, rangeVisible=True):
highlightedRange = cmds.timeControl(gPlaybackSlider,
query=True,
rangeArray=True)
if withinHighlighted:
highlightedRange[-1] -= 1
return highlightedRange
if not highlightedOnly:
return [cmds.playbackOptions(query=True, minTime=True),
cmds.playbackOptions(query=True, maxTime=True)]
def get_current_renderlayer():
return cmds.editRenderLayerGlobals(query=True, currentRenderLayer=True)
def get_plugin_shapes():
"""Get all currently available plugin shapes
Returns:
dict: plugin shapes by their menu label and script name
"""
filters = cmds.pluginDisplayFilter(query=True, listFilters=True)
labels = [cmds.pluginDisplayFilter(f, query=True, label=True) for f in
filters]
return OrderedDict(zip(labels, filters))
def open_file(filepath):
"""Open file using OS default settings"""
if sys.platform.startswith('darwin'):
subprocess.call(('open', filepath))
elif os.name == 'nt':
os.startfile(filepath)
elif os.name == 'posix':
subprocess.call(('xdg-open', filepath))
else:
raise NotImplementedError("OS not supported: {0}".format(os.name))
def load_json(filepath):
"""open and read json, return read values"""
with open(filepath, "r") as f:
return json.load(f)
def _fix_playblast_output_path(filepath):
"""Workaround a bug in maya.cmds.playblast to return correct filepath.
When the `viewer` argument is set to False and maya.cmds.playblast does not
automatically open the playblasted file the returned filepath does not have
the file's extension added correctly.
To workaround this we just glob.glob() for any file extensions and assume
the latest modified file is the correct file and return it.
"""
# Catch cancelled playblast
if filepath is None:
log.warning("Playblast did not result in output path. "
"Playblast is probably interrupted.")
return
# Fix: playblast not returning correct filename (with extension)
# Lets assume the most recently modified file is the correct one.
if not os.path.exists(filepath):
directory = os.path.dirname(filepath)
filename = os.path.basename(filepath)
# check if the filepath is has frame based filename
# example : capture.####.png
parts = filename.split(".")
if len(parts) == 3:
query = os.path.join(directory, "{}.*.{}".format(parts[0],
parts[-1]))
files = glob.glob(query)
else:
files = glob.glob("{}.*".format(filepath))
if not files:
raise RuntimeError("Couldn't find playblast from: "
"{0}".format(filepath))
filepath = max(files, key=os.path.getmtime)
return filepath
def capture_scene(options):
"""Capture using scene settings.
Uses the view settings from "panel".
This ensures playblast is done as quicktime H.264 100% quality.
It forces showOrnaments to be off and does not render off screen.
Arguments:
options (dict): a collection of output options
Returns:
str: Full path to playblast file.
"""
filename = options.get("filename", "%TEMP%")
log.info("Capturing to: {0}".format(filename))
options = options.copy()
# Force viewer to False in call to capture because we have our own
# viewer opening call to allow a signal to trigger between playblast
# and viewer
options['viewer'] = False
# Remove panel key since it's internal value to capture_gui
options.pop("panel", None)
path = capture.capture(**options)
path = _fix_playblast_output_path(path)
return path
def browse(path=None):
"""Open a pop-up browser for the user"""
# Acquire path from user input if none defined
if path is None:
scene_path = cmds.file(query=True, sceneName=True)
# use scene file name as default name
default_filename = os.path.splitext(os.path.basename(scene_path))[0]
if not default_filename:
# Scene wasn't saved yet so found no valid name for playblast.
default_filename = "playblast"
# Default to images rule
default_root = os.path.normpath(get_project_rule("images"))
default_path = os.path.join(default_root, default_filename)
path = cmds.fileDialog2(fileMode=0,
dialogStyle=2,
startingDirectory=default_path)
if not path:
return
if isinstance(path, (tuple, list)):
path = path[0]
if path.endswith(".*"):
path = path[:-2]
# Bug-Fix/Workaround:
# Fix for playblasts that result in nesting of the
# extension (eg. '.mov.mov.mov') which happens if the format
# is defined in the filename used for saving.
extension = os.path.splitext(path)[-1]
if extension:
path = path[:-len(extension)]
return path
def default_output():
"""Return filename based on current scene name.
Returns:
str: A relative filename
"""
scene = get_current_scenename() or "playblast"
# get current datetime
timestamp = datetime.datetime.today()
str_timestamp = timestamp.strftime("%Y-%m-%d_%H-%M-%S")
filename = "{}_{}".format(scene, str_timestamp)
return filename
def get_project_rule(rule):
"""Get the full path of the rule of the project"""
workspace = cmds.workspace(query=True, rootDirectory=True)
folder = cmds.workspace(fileRuleEntry=rule)
if not folder:
log.warning("File Rule Entry '{}' has no value, please check if the "
"rule name is typed correctly".format(rule))
return os.path.join(workspace, folder)
def list_formats():
# Workaround for Maya playblast bug where undo would
# move the currentTime to frame one.
cmds.currentTime(cmds.currentTime(query=True))
return cmds.playblast(query=True, format=True)
def list_compressions(format='avi'):
# Workaround for Maya playblast bug where undo would
# move the currentTime to frame one.
cmds.currentTime(cmds.currentTime(query=True))
cmd = 'playblast -format "{0}" -query -compression'.format(format)
return mel.eval(cmd)
@contextlib.contextmanager
def no_undo():
"""Disable undo during the context"""
try:
cmds.undoInfo(stateWithoutFlush=False)
yield
finally:
cmds.undoInfo(stateWithoutFlush=True)
def get_maya_main_window():
"""Get the main Maya window as a QtGui.QMainWindow instance
Returns:
QtGui.QMainWindow: instance of the top level Maya windows
"""
ptr = omui.MQtUtil.mainWindow()
if ptr is not None:
return shiboken.wrapInstance(long(ptr), QtWidgets.QWidget)