proper python environments

This commit is contained in:
Ondrej Samohel 2021-01-19 19:32:04 +01:00 committed by Ondřej Samohel
parent f7d943bec7
commit 8df840995e
No known key found for this signature in database
GPG key ID: 02376E18990A97C6
42 changed files with 302 additions and 81 deletions

833
pype/vendor/python/common/capture.py vendored Normal file
View file

@ -0,0 +1,833 @@
"""Maya Capture
Playblasting with independent viewport, camera and display options
"""
import re
import sys
import contextlib
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"
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):
"""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.
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
... }
... )
"""
camera = camera or "persp"
# Ensure camera exists
if not cmds.objExists(camera):
raise RuntimeError("Camera does not exist: {0}".format(camera))
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)
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(
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,
**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
}
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", {})
for key, value in display_options.iteritems():
if key in _DisplayOptionsRGB:
cmds.displayRGBColor(key, *value)
else:
cmds.displayPref(**{key: value})
# Camera options
camera_options = options.get("camera_options", {})
for key, value in camera_options.iteritems():
cmds.setAttr("{0}.{1}".format(camera, key), value)
# Viewport options
viewport_options = options.get("viewport_options", {})
for key, value in viewport_options.iteritems():
cmds.modelEditor(panel, edit=True, **{key: value})
viewport2_options = options.get("viewport2_options", {})
for key, value in viewport2_options.iteritems():
attr = "hardwareRenderingGlobals.{0}".format(key)
cmds.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:
cmds.setAttr("defaultResolution.width", options["width"])
if "height" in options:
cmds.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)
for opt, value in options.iteritems():
cmds.setAttr(camera + "." + opt, value)
try:
yield
finally:
if old_options:
for opt, value in old_options.iteritems():
cmds.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 {}))
# separate the plugin display filter options since they need to
# be set differently (see #55)
plugins = cmds.pluginDisplayFilter(query=True, listFilters=True)
plugin_options = dict()
for plugin in plugins:
if plugin in options:
plugin_options[plugin] = options.pop(plugin)
# default options
cmds.modelEditor(panel, edit=True, **options)
# 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
for opt, value in options.iteritems():
cmds.setAttr("hardwareRenderingGlobals." + opt, value)
try:
yield
finally:
# Restore previous settings
for opt, value in original.iteritems():
cmds.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"))
cmds.setAttr(camera + ".rnd", True)
try:
yield
finally:
for camera, renderable in state.iteritems():
cmds.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)
# --------------------------------
#
# 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
})