mirror of
https://github.com/ynput/ayon-core.git
synced 2025-12-24 21:04:40 +01:00
919 lines
29 KiB
Python
919 lines
29 KiB
Python
"""Maya Capture
|
|
|
|
Playblasting with independent viewport, camera and display options
|
|
|
|
"""
|
|
|
|
import re
|
|
import sys
|
|
import contextlib
|
|
import logging
|
|
|
|
from maya import cmds
|
|
from maya import mel
|
|
|
|
try:
|
|
from PySide2 import QtGui, QtWidgets
|
|
except ImportError:
|
|
from PySide import QtGui
|
|
QtWidgets = QtGui
|
|
|
|
version_info = (2, 3, 0)
|
|
|
|
__version__ = "%s.%s.%s" % version_info
|
|
__license__ = "MIT"
|
|
logger = logging.getLogger("capture")
|
|
|
|
|
|
def capture(camera=None,
|
|
width=None,
|
|
height=None,
|
|
filename=None,
|
|
start_frame=None,
|
|
end_frame=None,
|
|
frame=None,
|
|
format='qt',
|
|
compression='H.264',
|
|
quality=100,
|
|
off_screen=False,
|
|
viewer=True,
|
|
show_ornaments=True,
|
|
sound=None,
|
|
isolate=None,
|
|
maintain_aspect_ratio=True,
|
|
overwrite=False,
|
|
frame_padding=4,
|
|
raw_frame_numbers=False,
|
|
camera_options=None,
|
|
display_options=None,
|
|
viewport_options=None,
|
|
viewport2_options=None,
|
|
complete_filename=None,
|
|
log=None):
|
|
"""Playblast in an independent panel
|
|
|
|
Arguments:
|
|
camera (str, optional): Name of camera, defaults to "persp"
|
|
width (int, optional): Width of output in pixels
|
|
height (int, optional): Height of output in pixels
|
|
filename (str, optional): Name of output file. If
|
|
none is specified, no files are saved.
|
|
start_frame (float, optional): Defaults to current start frame.
|
|
end_frame (float, optional): Defaults to current end frame.
|
|
frame (float or tuple, optional): A single frame or list of frames.
|
|
Use this to capture a single frame or an arbitrary sequence of
|
|
frames.
|
|
format (str, optional): Name of format, defaults to "qt".
|
|
compression (str, optional): Name of compression, defaults to "H.264"
|
|
quality (int, optional): The quality of the output, defaults to 100
|
|
off_screen (bool, optional): Whether or not to playblast off screen
|
|
viewer (bool, optional): Display results in native player
|
|
show_ornaments (bool, optional): Whether or not model view ornaments
|
|
(e.g. axis icon, grid and HUD) should be displayed.
|
|
sound (str, optional): Specify the sound node to be used during
|
|
playblast. When None (default) no sound will be used.
|
|
isolate (list): List of nodes to isolate upon capturing
|
|
maintain_aspect_ratio (bool, optional): Modify height in order to
|
|
maintain aspect ratio.
|
|
overwrite (bool, optional): Whether or not to overwrite if file
|
|
already exists. If disabled and file exists and error will be
|
|
raised.
|
|
frame_padding (bool, optional): Number of zeros used to pad file name
|
|
for image sequences.
|
|
raw_frame_numbers (bool, optional): Whether or not to use the exact
|
|
frame numbers from the scene or capture to a sequence starting at
|
|
zero. Defaults to False. When set to True `viewer` can't be used
|
|
and will be forced to False.
|
|
camera_options (dict, optional): Supplied camera options,
|
|
using `CameraOptions`
|
|
display_options (dict, optional): Supplied display
|
|
options, using `DisplayOptions`
|
|
viewport_options (dict, optional): Supplied viewport
|
|
options, using `ViewportOptions`
|
|
viewport2_options (dict, optional): Supplied display
|
|
options, using `Viewport2Options`
|
|
complete_filename (str, optional): Exact name of output file. Use this
|
|
to override the output of `filename` so it excludes frame padding.
|
|
log (logger, optional): pass logger for logging messages.
|
|
|
|
Example:
|
|
>>> # Launch default capture
|
|
>>> capture()
|
|
>>> # Launch capture with custom viewport settings
|
|
>>> capture('persp', 800, 600,
|
|
... viewport_options={
|
|
... "displayAppearance": "wireframe",
|
|
... "grid": False,
|
|
... "polymeshes": True,
|
|
... },
|
|
... camera_options={
|
|
... "displayResolution": True
|
|
... }
|
|
... )
|
|
|
|
|
|
"""
|
|
global logger
|
|
if log:
|
|
logger = log
|
|
camera = camera or "persp"
|
|
|
|
# Ensure camera exists
|
|
if not cmds.objExists(camera):
|
|
raise RuntimeError("Camera does not exist: {0}".format(camera))
|
|
|
|
if width and height :
|
|
maintain_aspect_ratio = False
|
|
width = width or cmds.getAttr("defaultResolution.width")
|
|
height = height or cmds.getAttr("defaultResolution.height")
|
|
if maintain_aspect_ratio:
|
|
ratio = cmds.getAttr("defaultResolution.deviceAspectRatio")
|
|
height = round(width / ratio)
|
|
|
|
if start_frame is None:
|
|
start_frame = cmds.playbackOptions(minTime=True, query=True)
|
|
if end_frame is None:
|
|
end_frame = cmds.playbackOptions(maxTime=True, query=True)
|
|
|
|
# (#74) Bugfix: `maya.cmds.playblast` will raise an error when playblasting
|
|
# with `rawFrameNumbers` set to True but no explicit `frames` provided.
|
|
# Since we always know what frames will be included we can provide it
|
|
# explicitly
|
|
if raw_frame_numbers and frame is None:
|
|
frame = range(int(start_frame), int(end_frame) + 1)
|
|
|
|
# We need to wrap `completeFilename`, otherwise even when None is provided
|
|
# it will use filename as the exact name. Only when lacking as argument
|
|
# does it function correctly.
|
|
playblast_kwargs = dict()
|
|
if complete_filename:
|
|
playblast_kwargs['completeFilename'] = complete_filename
|
|
if frame is not None:
|
|
playblast_kwargs['frame'] = frame
|
|
if sound is not None:
|
|
playblast_kwargs['sound'] = sound
|
|
|
|
# We need to raise an error when the user gives a custom frame range with
|
|
# negative frames in combination with raw frame numbers. This will result
|
|
# in a minimal integer frame number : filename.-2147483648.png for any
|
|
# negative rendered frame
|
|
if frame and raw_frame_numbers:
|
|
check = frame if isinstance(frame, (list, tuple)) else [frame]
|
|
if any(f < 0 for f in check):
|
|
raise RuntimeError("Negative frames are not supported with "
|
|
"raw frame numbers and explicit frame numbers")
|
|
|
|
# (#21) Bugfix: `maya.cmds.playblast` suffers from undo bug where it
|
|
# always sets the currentTime to frame 1. By setting currentTime before
|
|
# the playblast call it'll undo correctly.
|
|
cmds.currentTime(cmds.currentTime(query=True))
|
|
|
|
padding = 10 # Extend panel to accommodate for OS window manager
|
|
|
|
with _independent_panel(width=width + padding,
|
|
height=height + padding,
|
|
off_screen=off_screen) as panel:
|
|
cmds.setFocus(panel)
|
|
|
|
all_playblast_kwargs = {
|
|
"compression": compression,
|
|
"format": format,
|
|
"percent": 100,
|
|
"quality": quality,
|
|
"viewer": viewer,
|
|
"startTime": start_frame,
|
|
"endTime": end_frame,
|
|
"offScreen": off_screen,
|
|
"showOrnaments": show_ornaments,
|
|
"forceOverwrite": overwrite,
|
|
"filename": filename,
|
|
"widthHeight": [width, height],
|
|
"rawFrameNumbers": raw_frame_numbers,
|
|
"framePadding": frame_padding
|
|
}
|
|
all_playblast_kwargs.update(playblast_kwargs)
|
|
|
|
if getattr(contextlib, "nested", None):
|
|
with contextlib.nested(
|
|
_disabled_inview_messages(),
|
|
_maintain_camera(panel, camera),
|
|
_applied_viewport_options(viewport_options, panel),
|
|
_applied_camera_options(camera_options, panel),
|
|
_applied_display_options(display_options),
|
|
_applied_viewport2_options(viewport2_options),
|
|
_isolated_nodes(isolate, panel),
|
|
_maintained_time()
|
|
):
|
|
output = cmds.playblast(**all_playblast_kwargs)
|
|
else:
|
|
with contextlib.ExitStack() as stack:
|
|
stack.enter_context(_disabled_inview_messages())
|
|
stack.enter_context(_maintain_camera(panel, camera))
|
|
stack.enter_context(
|
|
_applied_viewport_options(viewport_options, panel)
|
|
)
|
|
stack.enter_context(
|
|
_applied_camera_options(camera_options, panel)
|
|
)
|
|
stack.enter_context(
|
|
_applied_display_options(display_options)
|
|
)
|
|
stack.enter_context(
|
|
_applied_viewport2_options(viewport2_options)
|
|
)
|
|
stack.enter_context(_isolated_nodes(isolate, panel))
|
|
stack.enter_context(_maintained_time())
|
|
|
|
output = cmds.playblast(**all_playblast_kwargs)
|
|
|
|
return output
|
|
|
|
|
|
def snap(*args, **kwargs):
|
|
"""Single frame playblast in an independent panel.
|
|
|
|
The arguments of `capture` are all valid here as well, except for
|
|
`start_frame` and `end_frame`.
|
|
|
|
Arguments:
|
|
frame (float, optional): The frame to snap. If not provided current
|
|
frame is used.
|
|
clipboard (bool, optional): Whether to add the output image to the
|
|
global clipboard. This allows to easily paste the snapped image
|
|
into another application, eg. into Photoshop.
|
|
|
|
Keywords:
|
|
See `capture`.
|
|
|
|
"""
|
|
|
|
# capture single frame
|
|
frame = kwargs.pop('frame', cmds.currentTime(q=1))
|
|
kwargs['start_frame'] = frame
|
|
kwargs['end_frame'] = frame
|
|
kwargs['frame'] = frame
|
|
|
|
if not isinstance(frame, (int, float)):
|
|
raise TypeError("frame must be a single frame (integer or float). "
|
|
"Use `capture()` for sequences.")
|
|
|
|
# override capture defaults
|
|
format = kwargs.pop('format', "image")
|
|
compression = kwargs.pop('compression', "png")
|
|
viewer = kwargs.pop('viewer', False)
|
|
raw_frame_numbers = kwargs.pop('raw_frame_numbers', True)
|
|
kwargs['compression'] = compression
|
|
kwargs['format'] = format
|
|
kwargs['viewer'] = viewer
|
|
kwargs['raw_frame_numbers'] = raw_frame_numbers
|
|
|
|
# pop snap only keyword arguments
|
|
clipboard = kwargs.pop('clipboard', False)
|
|
|
|
# perform capture
|
|
output = capture(*args, **kwargs)
|
|
|
|
def replace(m):
|
|
"""Substitute # with frame number"""
|
|
return str(int(frame)).zfill(len(m.group()))
|
|
|
|
output = re.sub("#+", replace, output)
|
|
|
|
# add image to clipboard
|
|
if clipboard:
|
|
_image_to_clipboard(output)
|
|
|
|
return output
|
|
|
|
|
|
CameraOptions = {
|
|
"displayGateMask": False,
|
|
"displayResolution": False,
|
|
"displayFilmGate": False,
|
|
"displayFieldChart": False,
|
|
"displaySafeAction": False,
|
|
"displaySafeTitle": False,
|
|
"displayFilmPivot": False,
|
|
"displayFilmOrigin": False,
|
|
"overscan": 1.0,
|
|
"depthOfField": False,
|
|
}
|
|
|
|
DisplayOptions = {
|
|
"displayGradient": True,
|
|
"background": (0.631, 0.631, 0.631),
|
|
"backgroundTop": (0.535, 0.617, 0.702),
|
|
"backgroundBottom": (0.052, 0.052, 0.052),
|
|
}
|
|
|
|
# These display options require a different command to be queried and set
|
|
_DisplayOptionsRGB = set(["background", "backgroundTop", "backgroundBottom"])
|
|
|
|
ViewportOptions = {
|
|
# renderer
|
|
"rendererName": "vp2Renderer",
|
|
"fogging": False,
|
|
"fogMode": "linear",
|
|
"fogDensity": 1,
|
|
"fogStart": 1,
|
|
"fogEnd": 1,
|
|
"fogColor": (0, 0, 0, 0),
|
|
"shadows": False,
|
|
"displayTextures": True,
|
|
"displayLights": "default",
|
|
"useDefaultMaterial": False,
|
|
"wireframeOnShaded": False,
|
|
"displayAppearance": 'smoothShaded',
|
|
"selectionHiliteDisplay": False,
|
|
"headsUpDisplay": True,
|
|
# object display
|
|
"imagePlane": True,
|
|
"nurbsCurves": False,
|
|
"nurbsSurfaces": False,
|
|
"polymeshes": True,
|
|
"subdivSurfaces": False,
|
|
"planes": True,
|
|
"cameras": False,
|
|
"controlVertices": True,
|
|
"lights": False,
|
|
"grid": False,
|
|
"hulls": True,
|
|
"joints": False,
|
|
"ikHandles": False,
|
|
"deformers": False,
|
|
"dynamics": False,
|
|
"fluids": False,
|
|
"hairSystems": False,
|
|
"follicles": False,
|
|
"nCloths": False,
|
|
"nParticles": False,
|
|
"nRigids": False,
|
|
"dynamicConstraints": False,
|
|
"locators": False,
|
|
"manipulators": False,
|
|
"dimensions": False,
|
|
"handles": False,
|
|
"pivots": False,
|
|
"textures": False,
|
|
"strokes": False
|
|
}
|
|
|
|
Viewport2Options = {
|
|
"consolidateWorld": True,
|
|
"enableTextureMaxRes": False,
|
|
"bumpBakeResolution": 64,
|
|
"colorBakeResolution": 64,
|
|
"floatingPointRTEnable": True,
|
|
"floatingPointRTFormat": 1,
|
|
"gammaCorrectionEnable": False,
|
|
"gammaValue": 2.2,
|
|
"lineAAEnable": False,
|
|
"maxHardwareLights": 8,
|
|
"motionBlurEnable": False,
|
|
"motionBlurSampleCount": 8,
|
|
"motionBlurShutterOpenFraction": 0.2,
|
|
"motionBlurType": 0,
|
|
"multiSampleCount": 8,
|
|
"multiSampleEnable": False,
|
|
"singleSidedLighting": False,
|
|
"ssaoEnable": False,
|
|
"ssaoAmount": 1.0,
|
|
"ssaoFilterRadius": 16,
|
|
"ssaoRadius": 16,
|
|
"ssaoSamples": 16,
|
|
"textureMaxResolution": 4096,
|
|
"threadDGEvaluation": False,
|
|
"transparencyAlgorithm": 1,
|
|
"transparencyQuality": 0.33,
|
|
"useMaximumHardwareLights": True,
|
|
"vertexAnimationCache": 0,
|
|
"renderDepthOfField": 0
|
|
}
|
|
|
|
|
|
def apply_view(panel, **options):
|
|
"""Apply options to panel"""
|
|
|
|
camera = cmds.modelPanel(panel, camera=True, query=True)
|
|
|
|
# Display options
|
|
display_options = options.get("display_options", {})
|
|
_iteritems = getattr(display_options, "iteritems", display_options.items)
|
|
for key, value in _iteritems():
|
|
if key in _DisplayOptionsRGB:
|
|
cmds.displayRGBColor(key, *value)
|
|
else:
|
|
cmds.displayPref(**{key: value})
|
|
|
|
# Camera options
|
|
camera_options = options.get("camera_options", {})
|
|
_iteritems = getattr(camera_options, "iteritems", camera_options.items)
|
|
for key, value in _iteritems:
|
|
_safe_setAttr("{0}.{1}".format(camera, key), value)
|
|
|
|
# Viewport options
|
|
viewport_options = options.get("viewport_options", {})
|
|
_iteritems = getattr(viewport_options, "iteritems", viewport_options.items)
|
|
for key, value in _iteritems():
|
|
cmds.modelEditor(panel, edit=True, **{key: value})
|
|
|
|
viewport2_options = options.get("viewport2_options", {})
|
|
_iteritems = getattr(
|
|
viewport2_options, "iteritems", viewport2_options.items
|
|
)
|
|
for key, value in _iteritems():
|
|
attr = "hardwareRenderingGlobals.{0}".format(key)
|
|
_safe_setAttr(attr, value)
|
|
|
|
|
|
def parse_active_panel():
|
|
"""Parse the active modelPanel.
|
|
|
|
Raises
|
|
RuntimeError: When no active modelPanel an error is raised.
|
|
|
|
Returns:
|
|
str: Name of modelPanel
|
|
|
|
"""
|
|
|
|
panel = cmds.getPanel(withFocus=True)
|
|
|
|
# This happens when last focus was on panel
|
|
# that got deleted (e.g. `capture()` then `parse_active_view()`)
|
|
if not panel or "modelPanel" not in panel:
|
|
raise RuntimeError("No active model panel found")
|
|
|
|
return panel
|
|
|
|
|
|
def parse_active_view():
|
|
"""Parse the current settings from the active view"""
|
|
panel = parse_active_panel()
|
|
return parse_view(panel)
|
|
|
|
|
|
def parse_view(panel):
|
|
"""Parse the scene, panel and camera for their current settings
|
|
|
|
Example:
|
|
>>> parse_view("modelPanel1")
|
|
|
|
Arguments:
|
|
panel (str): Name of modelPanel
|
|
|
|
"""
|
|
|
|
camera = cmds.modelPanel(panel, query=True, camera=True)
|
|
|
|
# Display options
|
|
display_options = {}
|
|
for key in DisplayOptions:
|
|
if key in _DisplayOptionsRGB:
|
|
display_options[key] = cmds.displayRGBColor(key, query=True)
|
|
else:
|
|
display_options[key] = cmds.displayPref(query=True, **{key: True})
|
|
|
|
# Camera options
|
|
camera_options = {}
|
|
for key in CameraOptions:
|
|
camera_options[key] = cmds.getAttr("{0}.{1}".format(camera, key))
|
|
|
|
# Viewport options
|
|
viewport_options = {}
|
|
|
|
# capture plugin display filters first to ensure we never override
|
|
# built-in arguments if ever possible a plugin has similarly named
|
|
# plugin display filters (which it shouldn't!)
|
|
plugins = cmds.pluginDisplayFilter(query=True, listFilters=True)
|
|
for plugin in plugins:
|
|
plugin = str(plugin) # unicode->str for simplicity of the dict
|
|
state = cmds.modelEditor(panel, query=True, queryPluginObjects=plugin)
|
|
viewport_options[plugin] = state
|
|
|
|
for key in ViewportOptions:
|
|
viewport_options[key] = cmds.modelEditor(
|
|
panel, query=True, **{key: True})
|
|
|
|
viewport2_options = {}
|
|
for key in Viewport2Options.keys():
|
|
attr = "hardwareRenderingGlobals.{0}".format(key)
|
|
try:
|
|
viewport2_options[key] = cmds.getAttr(attr)
|
|
except ValueError:
|
|
continue
|
|
|
|
return {
|
|
"camera": camera,
|
|
"display_options": display_options,
|
|
"camera_options": camera_options,
|
|
"viewport_options": viewport_options,
|
|
"viewport2_options": viewport2_options
|
|
}
|
|
|
|
|
|
def parse_active_scene():
|
|
"""Parse active scene for arguments for capture()
|
|
|
|
*Resolution taken from render settings.
|
|
|
|
"""
|
|
|
|
time_control = mel.eval("$gPlayBackSlider = $gPlayBackSlider")
|
|
|
|
return {
|
|
"start_frame": cmds.playbackOptions(minTime=True, query=True),
|
|
"end_frame": cmds.playbackOptions(maxTime=True, query=True),
|
|
"width": cmds.getAttr("defaultResolution.width"),
|
|
"height": cmds.getAttr("defaultResolution.height"),
|
|
"compression": cmds.optionVar(query="playblastCompression"),
|
|
"filename": (cmds.optionVar(query="playblastFile")
|
|
if cmds.optionVar(query="playblastSaveToFile") else None),
|
|
"format": cmds.optionVar(query="playblastFormat"),
|
|
"off_screen": (True if cmds.optionVar(query="playblastOffscreen")
|
|
else False),
|
|
"show_ornaments": (True if cmds.optionVar(query="playblastShowOrnaments")
|
|
else False),
|
|
"quality": cmds.optionVar(query="playblastQuality"),
|
|
"sound": cmds.timeControl(time_control, q=True, sound=True) or None
|
|
}
|
|
|
|
|
|
def apply_scene(**options):
|
|
"""Apply options from scene
|
|
|
|
Example:
|
|
>>> apply_scene({"start_frame": 1009})
|
|
|
|
Arguments:
|
|
options (dict): Scene options
|
|
|
|
"""
|
|
|
|
if "start_frame" in options:
|
|
cmds.playbackOptions(minTime=options["start_frame"])
|
|
|
|
if "end_frame" in options:
|
|
cmds.playbackOptions(maxTime=options["end_frame"])
|
|
|
|
if "width" in options:
|
|
_safe_setAttr("defaultResolution.width", options["width"])
|
|
|
|
if "height" in options:
|
|
_safe_setAttr("defaultResolution.height", options["height"])
|
|
|
|
if "compression" in options:
|
|
cmds.optionVar(
|
|
stringValue=["playblastCompression", options["compression"]])
|
|
|
|
if "filename" in options:
|
|
cmds.optionVar(
|
|
stringValue=["playblastFile", options["filename"]])
|
|
|
|
if "format" in options:
|
|
cmds.optionVar(
|
|
stringValue=["playblastFormat", options["format"]])
|
|
|
|
if "off_screen" in options:
|
|
cmds.optionVar(
|
|
intValue=["playblastFormat", options["off_screen"]])
|
|
|
|
if "show_ornaments" in options:
|
|
cmds.optionVar(
|
|
intValue=["show_ornaments", options["show_ornaments"]])
|
|
|
|
if "quality" in options:
|
|
cmds.optionVar(
|
|
floatValue=["playblastQuality", options["quality"]])
|
|
|
|
|
|
@contextlib.contextmanager
|
|
def _applied_view(panel, **options):
|
|
"""Apply options to panel"""
|
|
|
|
original = parse_view(panel)
|
|
apply_view(panel, **options)
|
|
|
|
try:
|
|
yield
|
|
finally:
|
|
apply_view(panel, **original)
|
|
|
|
|
|
@contextlib.contextmanager
|
|
def _independent_panel(width, height, off_screen=False):
|
|
"""Create capture-window context without decorations
|
|
|
|
Arguments:
|
|
width (int): Width of panel
|
|
height (int): Height of panel
|
|
|
|
Example:
|
|
>>> with _independent_panel(800, 600):
|
|
... cmds.capture()
|
|
|
|
"""
|
|
|
|
# center panel on screen
|
|
screen_width, screen_height = _get_screen_size()
|
|
topLeft = [int((screen_height-height)/2.0),
|
|
int((screen_width-width)/2.0)]
|
|
|
|
window = cmds.window(width=width,
|
|
height=height,
|
|
topLeftCorner=topLeft,
|
|
menuBarVisible=False,
|
|
titleBar=False,
|
|
visible=not off_screen)
|
|
cmds.paneLayout()
|
|
panel = cmds.modelPanel(menuBarVisible=False,
|
|
label='CapturePanel')
|
|
|
|
# Hide icons under panel menus
|
|
bar_layout = cmds.modelPanel(panel, q=True, barLayout=True)
|
|
cmds.frameLayout(bar_layout, edit=True, collapse=True)
|
|
|
|
if not off_screen:
|
|
cmds.showWindow(window)
|
|
|
|
# Set the modelEditor of the modelPanel as the active view so it takes
|
|
# the playback focus. Does seem redundant with the `refresh` added in.
|
|
editor = cmds.modelPanel(panel, query=True, modelEditor=True)
|
|
cmds.modelEditor(editor, edit=True, activeView=True)
|
|
|
|
# Force a draw refresh of Maya so it keeps focus on the new panel
|
|
# This focus is required to force preview playback in the independent panel
|
|
cmds.refresh(force=True)
|
|
|
|
try:
|
|
yield panel
|
|
finally:
|
|
# Delete the panel to fix memory leak (about 5 mb per capture)
|
|
cmds.deleteUI(panel, panel=True)
|
|
cmds.deleteUI(window)
|
|
|
|
|
|
@contextlib.contextmanager
|
|
def _applied_camera_options(options, panel):
|
|
"""Context manager for applying `options` to `camera`"""
|
|
|
|
camera = cmds.modelPanel(panel, query=True, camera=True)
|
|
options = dict(CameraOptions, **(options or {}))
|
|
|
|
old_options = dict()
|
|
for opt in options.copy():
|
|
try:
|
|
old_options[opt] = cmds.getAttr(camera + "." + opt)
|
|
except:
|
|
sys.stderr.write("Could not get camera attribute "
|
|
"for capture: %s" % opt)
|
|
options.pop(opt)
|
|
|
|
_iteritems = getattr(options, "iteritems", options.items)
|
|
for opt, value in _iteritems():
|
|
if cmds.getAttr(camera + "." + opt, lock=True):
|
|
continue
|
|
else:
|
|
_safe_setAttr(camera + "." + opt, value)
|
|
|
|
try:
|
|
yield
|
|
finally:
|
|
if old_options:
|
|
_iteritems = getattr(old_options, "iteritems", old_options.items)
|
|
for opt, value in _iteritems():
|
|
#
|
|
if cmds.getAttr(camera + "." + opt, lock=True):
|
|
continue
|
|
else:
|
|
_safe_setAttr(camera + "." + opt, value)
|
|
|
|
|
|
@contextlib.contextmanager
|
|
def _applied_display_options(options):
|
|
"""Context manager for setting background color display options."""
|
|
|
|
options = dict(DisplayOptions, **(options or {}))
|
|
|
|
colors = ['background', 'backgroundTop', 'backgroundBottom']
|
|
preferences = ['displayGradient']
|
|
|
|
# Store current settings
|
|
original = {}
|
|
for color in colors:
|
|
original[color] = cmds.displayRGBColor(color, query=True) or []
|
|
|
|
for preference in preferences:
|
|
original[preference] = cmds.displayPref(
|
|
query=True, **{preference: True})
|
|
|
|
# Apply settings
|
|
for color in colors:
|
|
value = options[color]
|
|
cmds.displayRGBColor(color, *value)
|
|
|
|
for preference in preferences:
|
|
value = options[preference]
|
|
cmds.displayPref(**{preference: value})
|
|
|
|
try:
|
|
yield
|
|
|
|
finally:
|
|
# Restore original settings
|
|
for color in colors:
|
|
cmds.displayRGBColor(color, *original[color])
|
|
for preference in preferences:
|
|
cmds.displayPref(**{preference: original[preference]})
|
|
|
|
|
|
@contextlib.contextmanager
|
|
def _applied_viewport_options(options, panel):
|
|
"""Context manager for applying `options` to `panel`"""
|
|
|
|
options = dict(ViewportOptions, **(options or {}))
|
|
plugin_options = options.pop("pluginObjects", {})
|
|
|
|
# BUGFIX Maya 2020 some keys in viewport options dict may not be unicode
|
|
# This is a local OpenPype edit to capture.py for issue #4730
|
|
# TODO: Remove when dropping Maya 2020 compatibility
|
|
if int(cmds.about(version=True)) <= 2020:
|
|
options = {
|
|
str(key): value for key, value in options.items()
|
|
}
|
|
plugin_options = {
|
|
str(key): value for key, value in plugin_options.items()
|
|
}
|
|
|
|
# Backwards compatibility for `pluginObjects` flattened into `options`
|
|
# separate the plugin display filter options since they need to
|
|
# be set differently (see #55)
|
|
plugins = set(cmds.pluginDisplayFilter(query=True, listFilters=True))
|
|
for plugin in plugins:
|
|
if plugin in options:
|
|
plugin_options[plugin] = options.pop(plugin)
|
|
|
|
# default options
|
|
try:
|
|
cmds.modelEditor(panel, edit=True, **options)
|
|
except TypeError as e:
|
|
# Try to set as much as possible of the state by setting them one by
|
|
# one. This way we can also report the failing key values explicitly.
|
|
for key, value in options.items():
|
|
try:
|
|
cmds.modelEditor(panel, edit=True, **{key: value})
|
|
except TypeError:
|
|
logger.error("Failing to apply option '{}': {}".format(key,
|
|
value))
|
|
|
|
# plugin display filter options
|
|
for plugin, state in plugin_options.items():
|
|
cmds.modelEditor(panel, edit=True, pluginObjects=(plugin, state))
|
|
|
|
yield
|
|
|
|
|
|
@contextlib.contextmanager
|
|
def _applied_viewport2_options(options):
|
|
"""Context manager for setting viewport 2.0 options.
|
|
|
|
These options are applied by setting attributes on the
|
|
"hardwareRenderingGlobals" node.
|
|
|
|
"""
|
|
|
|
options = dict(Viewport2Options, **(options or {}))
|
|
|
|
# Store current settings
|
|
original = {}
|
|
for opt in options.copy():
|
|
try:
|
|
original[opt] = cmds.getAttr("hardwareRenderingGlobals." + opt)
|
|
except ValueError:
|
|
options.pop(opt)
|
|
|
|
# Apply settings
|
|
_iteritems = getattr(options, "iteritems", options.items)
|
|
for opt, value in _iteritems():
|
|
_safe_setAttr("hardwareRenderingGlobals." + opt, value)
|
|
|
|
try:
|
|
yield
|
|
finally:
|
|
# Restore previous settings
|
|
_iteritems = getattr(original, "iteritems", original.items)
|
|
for opt, value in _iteritems():
|
|
_safe_setAttr("hardwareRenderingGlobals." + opt, value)
|
|
|
|
|
|
@contextlib.contextmanager
|
|
def _isolated_nodes(nodes, panel):
|
|
"""Context manager for isolating `nodes` in `panel`"""
|
|
|
|
if nodes is not None:
|
|
cmds.isolateSelect(panel, state=True)
|
|
for obj in nodes:
|
|
cmds.isolateSelect(panel, addDagObject=obj)
|
|
yield
|
|
|
|
|
|
@contextlib.contextmanager
|
|
def _maintained_time():
|
|
"""Context manager for preserving (resetting) the time after the context"""
|
|
|
|
current_time = cmds.currentTime(query=1)
|
|
try:
|
|
yield
|
|
finally:
|
|
cmds.currentTime(current_time)
|
|
|
|
|
|
@contextlib.contextmanager
|
|
def _maintain_camera(panel, camera):
|
|
state = {}
|
|
|
|
if not _in_standalone():
|
|
cmds.lookThru(panel, camera)
|
|
else:
|
|
state = dict((camera, cmds.getAttr(camera + ".rnd"))
|
|
for camera in cmds.ls(type="camera"))
|
|
_safe_setAttr(camera + ".rnd", True)
|
|
|
|
try:
|
|
yield
|
|
finally:
|
|
_iteritems = getattr(state, "iteritems", state.items)
|
|
for camera, renderable in _iteritems():
|
|
_safe_setAttr(camera + ".rnd", renderable)
|
|
|
|
|
|
@contextlib.contextmanager
|
|
def _disabled_inview_messages():
|
|
"""Disable in-view help messages during the context"""
|
|
original = cmds.optionVar(q="inViewMessageEnable")
|
|
cmds.optionVar(iv=("inViewMessageEnable", 0))
|
|
try:
|
|
yield
|
|
finally:
|
|
cmds.optionVar(iv=("inViewMessageEnable", original))
|
|
|
|
|
|
def _image_to_clipboard(path):
|
|
"""Copies the image at path to the system's global clipboard."""
|
|
if _in_standalone():
|
|
raise Exception("Cannot copy to clipboard from Maya Standalone")
|
|
|
|
image = QtGui.QImage(path)
|
|
clipboard = QtWidgets.QApplication.clipboard()
|
|
clipboard.setImage(image, mode=QtGui.QClipboard.Clipboard)
|
|
|
|
|
|
def _get_screen_size():
|
|
"""Return available screen size without space occupied by taskbar"""
|
|
if _in_standalone():
|
|
return [0, 0]
|
|
|
|
rect = QtWidgets.QDesktopWidget().screenGeometry(-1)
|
|
return [rect.width(), rect.height()]
|
|
|
|
|
|
def _in_standalone():
|
|
return not hasattr(cmds, "about") or cmds.about(batch=True)
|
|
|
|
|
|
def _safe_setAttr(*args, **kwargs):
|
|
"""Wrapper to handle failures when attribute is locked.
|
|
|
|
Temporary hotfix until better approach (store value, unlock, set new,
|
|
return old, lock again) is implemented.
|
|
"""
|
|
try:
|
|
cmds.setAttr(*args, **kwargs)
|
|
except RuntimeError:
|
|
print("Cannot setAttr {}!".format(args))
|
|
|
|
|
|
# --------------------------------
|
|
#
|
|
# Apply version specific settings
|
|
#
|
|
# --------------------------------
|
|
|
|
version = mel.eval("getApplicationVersionAsFloat")
|
|
if version > 2015:
|
|
Viewport2Options.update({
|
|
"hwFogAlpha": 1.0,
|
|
"hwFogFalloff": 0,
|
|
"hwFogDensity": 0.1,
|
|
"hwFogEnable": False,
|
|
"holdOutDetailMode": 1,
|
|
"hwFogEnd": 100.0,
|
|
"holdOutMode": True,
|
|
"hwFogColorR": 0.5,
|
|
"hwFogColorG": 0.5,
|
|
"hwFogColorB": 0.5,
|
|
"hwFogStart": 0.0,
|
|
})
|
|
ViewportOptions.update({
|
|
"motionTrails": False
|
|
})
|