mirror of
https://github.com/ynput/ayon-core.git
synced 2025-12-24 21:04:40 +01:00
fix adding capture-gui
This commit is contained in:
parent
f7cd93d723
commit
45eb1a858e
22 changed files with 3755 additions and 0 deletions
396
pype/vendor/capture_gui/lib.py
vendored
Normal file
396
pype/vendor/capture_gui/lib.py
vendored
Normal file
|
|
@ -0,0 +1,396 @@
|
|||
# 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)
|
||||
401
pype/vendor/capture_gui/plugin.py
vendored
Normal file
401
pype/vendor/capture_gui/plugin.py
vendored
Normal file
|
|
@ -0,0 +1,401 @@
|
|||
"""Plug-in system
|
||||
|
||||
Works similar to how OSs look for executables; i.e. a number of
|
||||
absolute paths are searched for a given match. The predicate for
|
||||
executables is whether or not an extension matches a number of
|
||||
options, such as ".exe" or ".bat".
|
||||
|
||||
In this system, the predicate is whether or not a fname ends with ".py"
|
||||
|
||||
"""
|
||||
|
||||
# Standard library
|
||||
import os
|
||||
import sys
|
||||
import types
|
||||
import logging
|
||||
import inspect
|
||||
|
||||
from .vendor.Qt import QtCore, QtWidgets
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
_registered_paths = list()
|
||||
_registered_plugins = dict()
|
||||
|
||||
|
||||
class classproperty(object):
|
||||
def __init__(self, getter):
|
||||
self.getter = getter
|
||||
|
||||
def __get__(self, instance, owner):
|
||||
return self.getter(owner)
|
||||
|
||||
|
||||
class Plugin(QtWidgets.QWidget):
|
||||
"""Base class for Option plug-in Widgets.
|
||||
|
||||
This is a regular Qt widget that can be added to the capture interface
|
||||
as an additional component, like a plugin.
|
||||
|
||||
The plug-ins are sorted in the interface by their `order` attribute and
|
||||
will be displayed in the main interface when `section` is set to "app"
|
||||
and displayed in the additional settings pop-up when set to "config".
|
||||
|
||||
When `hidden` is set to True the widget will not be shown in the interface.
|
||||
This could be useful as a plug-in that supplies solely default values to
|
||||
the capture GUI command.
|
||||
|
||||
"""
|
||||
|
||||
label = ""
|
||||
section = "app" # "config" or "app"
|
||||
hidden = False
|
||||
options_changed = QtCore.Signal()
|
||||
label_changed = QtCore.Signal(str)
|
||||
order = 0
|
||||
highlight = "border: 1px solid red;"
|
||||
validate_state = True
|
||||
|
||||
def on_playblast_finished(self, options):
|
||||
pass
|
||||
|
||||
def validate(self):
|
||||
"""
|
||||
Ensure outputs of the widget are possible, when errors are raised it
|
||||
will return a message with what has caused the error
|
||||
:return:
|
||||
"""
|
||||
errors = []
|
||||
return errors
|
||||
|
||||
def get_outputs(self):
|
||||
"""Return the options as set in this plug-in widget.
|
||||
|
||||
This is used to identify the settings to be used for the playblast.
|
||||
As such the values should be returned in a way that a call to
|
||||
`capture.capture()` would understand as arguments.
|
||||
|
||||
Args:
|
||||
panel (str): The active modelPanel of the user. This is passed so
|
||||
values could potentially be parsed from the active panel
|
||||
|
||||
Returns:
|
||||
dict: The options for this plug-in. (formatted `capture` style)
|
||||
|
||||
"""
|
||||
return dict()
|
||||
|
||||
def get_inputs(self, as_preset):
|
||||
"""Return widget's child settings.
|
||||
|
||||
This should provide a dictionary of input settings of the plug-in
|
||||
that results in a dictionary that can be supplied to `apply_input()`
|
||||
This is used to save the settings of the preset to a widget.
|
||||
|
||||
:param as_preset:
|
||||
:param as_presets: Toggle to mute certain input values of the widget
|
||||
:type as_presets: bool
|
||||
|
||||
Returns:
|
||||
dict: The currently set inputs of this widget.
|
||||
|
||||
"""
|
||||
return dict()
|
||||
|
||||
def apply_inputs(self, settings):
|
||||
"""Apply a dictionary of settings to the widget.
|
||||
|
||||
This should update the widget's inputs to the settings provided in
|
||||
the dictionary. This is used to apply settings from a preset.
|
||||
|
||||
Returns:
|
||||
None
|
||||
|
||||
"""
|
||||
pass
|
||||
|
||||
def initialize(self):
|
||||
"""
|
||||
This method is used to register any callbacks
|
||||
:return:
|
||||
"""
|
||||
pass
|
||||
|
||||
def uninitialize(self):
|
||||
"""
|
||||
Unregister any callback created when deleting the widget
|
||||
|
||||
A general explation:
|
||||
|
||||
The deletion method is an attribute that lives inside the object to be
|
||||
deleted, and that is the problem:
|
||||
Destruction seems not to care about the order of destruction,
|
||||
and the __dict__ that also holds the onDestroy bound method
|
||||
gets destructed before it is called.
|
||||
|
||||
Another solution is to use a weakref
|
||||
|
||||
:return: None
|
||||
"""
|
||||
pass
|
||||
|
||||
def __str__(self):
|
||||
return self.label or type(self).__name__
|
||||
|
||||
def __repr__(self):
|
||||
return u"%s.%s(%r)" % (__name__, type(self).__name__, self.__str__())
|
||||
|
||||
id = classproperty(lambda cls: cls.__name__)
|
||||
|
||||
|
||||
def register_plugin_path(path):
|
||||
"""Plug-ins are looked up at run-time from directories registered here
|
||||
|
||||
To register a new directory, run this command along with the absolute
|
||||
path to where you"re plug-ins are located.
|
||||
|
||||
Example:
|
||||
>>> import os
|
||||
>>> my_plugins = "/server/plugins"
|
||||
>>> register_plugin_path(my_plugins)
|
||||
'/server/plugins'
|
||||
|
||||
Returns:
|
||||
Actual path added, including any post-processing
|
||||
|
||||
"""
|
||||
|
||||
if path in _registered_paths:
|
||||
return log.warning("Path already registered: {0}".format(path))
|
||||
|
||||
_registered_paths.append(path)
|
||||
|
||||
return path
|
||||
|
||||
|
||||
def deregister_plugin_path(path):
|
||||
"""Remove a _registered_paths path
|
||||
|
||||
Raises:
|
||||
KeyError if `path` isn't registered
|
||||
|
||||
"""
|
||||
|
||||
_registered_paths.remove(path)
|
||||
|
||||
|
||||
def deregister_all_plugin_paths():
|
||||
"""Mainly used in tests"""
|
||||
_registered_paths[:] = []
|
||||
|
||||
|
||||
def registered_plugin_paths():
|
||||
"""Return paths added via registration
|
||||
|
||||
..note:: This returns a copy of the registered paths
|
||||
and can therefore not be modified directly.
|
||||
|
||||
"""
|
||||
|
||||
return list(_registered_paths)
|
||||
|
||||
|
||||
def registered_plugins():
|
||||
"""Return plug-ins added via :func:`register_plugin`
|
||||
|
||||
.. note:: This returns a copy of the registered plug-ins
|
||||
and can therefore not be modified directly
|
||||
|
||||
"""
|
||||
|
||||
return _registered_plugins.values()
|
||||
|
||||
|
||||
def register_plugin(plugin):
|
||||
"""Register a new plug-in
|
||||
|
||||
Arguments:
|
||||
plugin (Plugin): Plug-in to register
|
||||
|
||||
Raises:
|
||||
TypeError if `plugin` is not callable
|
||||
|
||||
"""
|
||||
|
||||
if not hasattr(plugin, "__call__"):
|
||||
raise TypeError("Plug-in must be callable "
|
||||
"returning an instance of a class")
|
||||
|
||||
if not plugin_is_valid(plugin):
|
||||
raise TypeError("Plug-in invalid: %s", plugin)
|
||||
|
||||
_registered_plugins[plugin.__name__] = plugin
|
||||
|
||||
|
||||
def plugin_paths():
|
||||
"""Collect paths from all sources.
|
||||
|
||||
This function looks at the three potential sources of paths
|
||||
and returns a list with all of them together.
|
||||
|
||||
The sources are:
|
||||
|
||||
- Registered paths using :func:`register_plugin_path`
|
||||
|
||||
Returns:
|
||||
list of paths in which plugins may be locat
|
||||
|
||||
"""
|
||||
|
||||
paths = list()
|
||||
|
||||
for path in registered_plugin_paths():
|
||||
if path in paths:
|
||||
continue
|
||||
paths.append(path)
|
||||
|
||||
return paths
|
||||
|
||||
|
||||
def discover(paths=None):
|
||||
"""Find and return available plug-ins
|
||||
|
||||
This function looks for files within paths registered via
|
||||
:func:`register_plugin_path`.
|
||||
|
||||
Arguments:
|
||||
paths (list, optional): Paths to discover plug-ins from.
|
||||
If no paths are provided, all paths are searched.
|
||||
|
||||
"""
|
||||
|
||||
plugins = dict()
|
||||
|
||||
# Include plug-ins from registered paths
|
||||
for path in paths or plugin_paths():
|
||||
path = os.path.normpath(path)
|
||||
|
||||
if not os.path.isdir(path):
|
||||
continue
|
||||
|
||||
for fname in os.listdir(path):
|
||||
if fname.startswith("_"):
|
||||
continue
|
||||
|
||||
abspath = os.path.join(path, fname)
|
||||
|
||||
if not os.path.isfile(abspath):
|
||||
continue
|
||||
|
||||
mod_name, mod_ext = os.path.splitext(fname)
|
||||
|
||||
if not mod_ext == ".py":
|
||||
continue
|
||||
|
||||
module = types.ModuleType(mod_name)
|
||||
module.__file__ = abspath
|
||||
|
||||
try:
|
||||
execfile(abspath, module.__dict__)
|
||||
|
||||
# Store reference to original module, to avoid
|
||||
# garbage collection from collecting it's global
|
||||
# imports, such as `import os`.
|
||||
sys.modules[mod_name] = module
|
||||
|
||||
except Exception as err:
|
||||
log.debug("Skipped: \"%s\" (%s)", mod_name, err)
|
||||
continue
|
||||
|
||||
for plugin in plugins_from_module(module):
|
||||
if plugin.id in plugins:
|
||||
log.debug("Duplicate plug-in found: %s", plugin)
|
||||
continue
|
||||
|
||||
plugins[plugin.id] = plugin
|
||||
|
||||
# Include plug-ins from registration.
|
||||
# Directly registered plug-ins take precedence.
|
||||
for name, plugin in _registered_plugins.items():
|
||||
if name in plugins:
|
||||
log.debug("Duplicate plug-in found: %s", plugin)
|
||||
continue
|
||||
plugins[name] = plugin
|
||||
|
||||
plugins = list(plugins.values())
|
||||
sort(plugins) # In-place
|
||||
|
||||
return plugins
|
||||
|
||||
|
||||
def plugins_from_module(module):
|
||||
"""Return plug-ins from module
|
||||
|
||||
Arguments:
|
||||
module (types.ModuleType): Imported module from which to
|
||||
parse valid plug-ins.
|
||||
|
||||
Returns:
|
||||
List of plug-ins, or empty list if none is found.
|
||||
|
||||
"""
|
||||
|
||||
plugins = list()
|
||||
|
||||
for name in dir(module):
|
||||
if name.startswith("_"):
|
||||
continue
|
||||
|
||||
# It could be anything at this point
|
||||
obj = getattr(module, name)
|
||||
|
||||
if not inspect.isclass(obj):
|
||||
continue
|
||||
|
||||
if not issubclass(obj, Plugin):
|
||||
continue
|
||||
|
||||
if not plugin_is_valid(obj):
|
||||
log.debug("Plug-in invalid: %s", obj)
|
||||
continue
|
||||
|
||||
plugins.append(obj)
|
||||
|
||||
return plugins
|
||||
|
||||
|
||||
def plugin_is_valid(plugin):
|
||||
"""Determine whether or not plug-in `plugin` is valid
|
||||
|
||||
Arguments:
|
||||
plugin (Plugin): Plug-in to assess
|
||||
|
||||
"""
|
||||
|
||||
if not plugin:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def sort(plugins):
|
||||
"""Sort `plugins` in-place
|
||||
|
||||
Their order is determined by their `order` attribute.
|
||||
|
||||
Arguments:
|
||||
plugins (list): Plug-ins to sort
|
||||
|
||||
"""
|
||||
|
||||
if not isinstance(plugins, list):
|
||||
raise TypeError("plugins must be of type list")
|
||||
|
||||
plugins.sort(key=lambda p: p.order)
|
||||
return plugins
|
||||
|
||||
|
||||
# Register default paths
|
||||
default_plugins_path = os.path.join(os.path.dirname(__file__), "plugins")
|
||||
register_plugin_path(default_plugins_path)
|
||||
141
pype/vendor/capture_gui/plugins/cameraplugin.py
vendored
Normal file
141
pype/vendor/capture_gui/plugins/cameraplugin.py
vendored
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
import maya.cmds as cmds
|
||||
from capture_gui.vendor.Qt import QtCore, QtWidgets
|
||||
|
||||
import capture_gui.lib as lib
|
||||
import capture_gui.plugin
|
||||
|
||||
|
||||
class CameraPlugin(capture_gui.plugin.Plugin):
|
||||
"""Camera widget.
|
||||
|
||||
Allows to select a camera.
|
||||
|
||||
"""
|
||||
id = "Camera"
|
||||
section = "app"
|
||||
order = 10
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super(CameraPlugin, self).__init__(parent=parent)
|
||||
|
||||
self._layout = QtWidgets.QHBoxLayout()
|
||||
self._layout.setContentsMargins(5, 0, 5, 0)
|
||||
self.setLayout(self._layout)
|
||||
|
||||
self.cameras = QtWidgets.QComboBox()
|
||||
self.cameras.setMinimumWidth(200)
|
||||
|
||||
self.get_active = QtWidgets.QPushButton("Get active")
|
||||
self.get_active.setToolTip("Set camera from currently active view")
|
||||
self.refresh = QtWidgets.QPushButton("Refresh")
|
||||
self.refresh.setToolTip("Refresh the list of cameras")
|
||||
|
||||
self._layout.addWidget(self.cameras)
|
||||
self._layout.addWidget(self.get_active)
|
||||
self._layout.addWidget(self.refresh)
|
||||
|
||||
# Signals
|
||||
self.connections()
|
||||
|
||||
# Force update of the label
|
||||
self.set_active_cam()
|
||||
self.on_update_label()
|
||||
|
||||
def connections(self):
|
||||
self.get_active.clicked.connect(self.set_active_cam)
|
||||
self.refresh.clicked.connect(self.on_refresh)
|
||||
|
||||
self.cameras.currentIndexChanged.connect(self.on_update_label)
|
||||
self.cameras.currentIndexChanged.connect(self.validate)
|
||||
|
||||
def set_active_cam(self):
|
||||
cam = lib.get_current_camera()
|
||||
self.on_refresh(camera=cam)
|
||||
|
||||
def select_camera(self, cam):
|
||||
if cam:
|
||||
# Ensure long name
|
||||
cameras = cmds.ls(cam, long=True)
|
||||
if not cameras:
|
||||
return
|
||||
cam = cameras[0]
|
||||
|
||||
# Find the index in the list
|
||||
for i in range(self.cameras.count()):
|
||||
value = str(self.cameras.itemText(i))
|
||||
if value == cam:
|
||||
self.cameras.setCurrentIndex(i)
|
||||
return
|
||||
|
||||
def validate(self):
|
||||
|
||||
errors = []
|
||||
camera = self.cameras.currentText()
|
||||
if not cmds.objExists(camera):
|
||||
errors.append("{} : Selected camera '{}' "
|
||||
"does not exist!".format(self.id, camera))
|
||||
self.cameras.setStyleSheet(self.highlight)
|
||||
else:
|
||||
self.cameras.setStyleSheet("")
|
||||
|
||||
return errors
|
||||
|
||||
def get_outputs(self):
|
||||
"""Return currently selected camera from combobox."""
|
||||
|
||||
idx = self.cameras.currentIndex()
|
||||
camera = str(self.cameras.itemText(idx)) if idx != -1 else None
|
||||
|
||||
return {"camera": camera}
|
||||
|
||||
def on_refresh(self, camera=None):
|
||||
"""Refresh the camera list with all current cameras in scene.
|
||||
|
||||
A currentIndexChanged signal is only emitted for the cameras combobox
|
||||
when the camera is different at the end of the refresh.
|
||||
|
||||
Args:
|
||||
camera (str): When name of camera is passed it will try to select
|
||||
the camera with this name after the refresh.
|
||||
|
||||
Returns:
|
||||
None
|
||||
|
||||
"""
|
||||
|
||||
cam = self.get_outputs()['camera']
|
||||
|
||||
# Get original selection
|
||||
if camera is None:
|
||||
index = self.cameras.currentIndex()
|
||||
if index != -1:
|
||||
camera = self.cameras.currentText()
|
||||
|
||||
self.cameras.blockSignals(True)
|
||||
|
||||
# Update the list with available cameras
|
||||
self.cameras.clear()
|
||||
|
||||
cam_shapes = cmds.ls(type="camera")
|
||||
cam_transforms = cmds.listRelatives(cam_shapes,
|
||||
parent=True,
|
||||
fullPath=True)
|
||||
self.cameras.addItems(cam_transforms)
|
||||
|
||||
# If original selection, try to reselect
|
||||
self.select_camera(camera)
|
||||
|
||||
self.cameras.blockSignals(False)
|
||||
|
||||
# If camera changed emit signal
|
||||
if cam != self.get_outputs()['camera']:
|
||||
idx = self.cameras.currentIndex()
|
||||
self.cameras.currentIndexChanged.emit(idx)
|
||||
|
||||
def on_update_label(self):
|
||||
|
||||
cam = self.cameras.currentText()
|
||||
cam = cam.rsplit("|", 1)[-1] # ensure short name
|
||||
self.label = "Camera ({0})".format(cam)
|
||||
|
||||
self.label_changed.emit(self.label)
|
||||
95
pype/vendor/capture_gui/plugins/codecplugin.py
vendored
Normal file
95
pype/vendor/capture_gui/plugins/codecplugin.py
vendored
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
from capture_gui.vendor.Qt import QtCore, QtWidgets
|
||||
|
||||
import capture_gui.lib as lib
|
||||
import capture_gui.plugin
|
||||
|
||||
|
||||
class CodecPlugin(capture_gui.plugin.Plugin):
|
||||
"""Codec widget.
|
||||
|
||||
Allows to set format, compression and quality.
|
||||
|
||||
"""
|
||||
id = "Codec"
|
||||
label = "Codec"
|
||||
section = "config"
|
||||
order = 50
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super(CodecPlugin, self).__init__(parent=parent)
|
||||
|
||||
self._layout = QtWidgets.QHBoxLayout()
|
||||
self._layout.setContentsMargins(0, 0, 0, 0)
|
||||
self.setLayout(self._layout)
|
||||
|
||||
self.format = QtWidgets.QComboBox()
|
||||
self.compression = QtWidgets.QComboBox()
|
||||
self.quality = QtWidgets.QSpinBox()
|
||||
self.quality.setMinimum(0)
|
||||
self.quality.setMaximum(100)
|
||||
self.quality.setValue(100)
|
||||
self.quality.setToolTip("Compression quality percentage")
|
||||
|
||||
self._layout.addWidget(self.format)
|
||||
self._layout.addWidget(self.compression)
|
||||
self._layout.addWidget(self.quality)
|
||||
|
||||
self.format.currentIndexChanged.connect(self.on_format_changed)
|
||||
|
||||
self.refresh()
|
||||
|
||||
# Default to format 'qt'
|
||||
index = self.format.findText("qt")
|
||||
if index != -1:
|
||||
self.format.setCurrentIndex(index)
|
||||
|
||||
# Default to compression 'H.264'
|
||||
index = self.compression.findText("H.264")
|
||||
if index != -1:
|
||||
self.compression.setCurrentIndex(index)
|
||||
|
||||
self.connections()
|
||||
|
||||
def connections(self):
|
||||
self.compression.currentIndexChanged.connect(self.options_changed)
|
||||
self.format.currentIndexChanged.connect(self.options_changed)
|
||||
self.quality.valueChanged.connect(self.options_changed)
|
||||
|
||||
def refresh(self):
|
||||
formats = sorted(lib.list_formats())
|
||||
self.format.clear()
|
||||
self.format.addItems(formats)
|
||||
|
||||
def on_format_changed(self):
|
||||
"""Refresh the available compressions."""
|
||||
|
||||
format = self.format.currentText()
|
||||
compressions = lib.list_compressions(format)
|
||||
self.compression.clear()
|
||||
self.compression.addItems(compressions)
|
||||
|
||||
def get_outputs(self):
|
||||
"""Get the plugin outputs that matches `capture.capture` arguments
|
||||
|
||||
Returns:
|
||||
dict: Plugin outputs
|
||||
|
||||
"""
|
||||
|
||||
return {"format": self.format.currentText(),
|
||||
"compression": self.compression.currentText(),
|
||||
"quality": self.quality.value()}
|
||||
|
||||
def get_inputs(self, as_preset):
|
||||
# a bit redundant but it will work when iterating over widgets
|
||||
# so we don't have to write an exception
|
||||
return self.get_outputs()
|
||||
|
||||
def apply_inputs(self, settings):
|
||||
codec_format = settings.get("format", 0)
|
||||
compr = settings.get("compression", 4)
|
||||
quality = settings.get("quality", 100)
|
||||
|
||||
self.format.setCurrentIndex(self.format.findText(codec_format))
|
||||
self.compression.setCurrentIndex(self.compression.findText(compr))
|
||||
self.quality.setValue(int(quality))
|
||||
47
pype/vendor/capture_gui/plugins/defaultoptionsplugin.py
vendored
Normal file
47
pype/vendor/capture_gui/plugins/defaultoptionsplugin.py
vendored
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
import capture
|
||||
import capture_gui.plugin
|
||||
|
||||
|
||||
class DefaultOptionsPlugin(capture_gui.plugin.Plugin):
|
||||
"""Invisible Plugin that supplies some default values to the gui.
|
||||
|
||||
This enures:
|
||||
- no HUD is present in playblasts
|
||||
- no overscan (`overscan` set to 1.0)
|
||||
- no title safe, action safe, gate mask, etc.
|
||||
- active sound is included in video playblasts
|
||||
|
||||
"""
|
||||
order = -1
|
||||
hidden = True
|
||||
|
||||
def get_outputs(self):
|
||||
"""Get the plugin outputs that matches `capture.capture` arguments
|
||||
|
||||
Returns:
|
||||
dict: Plugin outputs
|
||||
|
||||
"""
|
||||
|
||||
outputs = dict()
|
||||
|
||||
# use active sound track
|
||||
scene = capture.parse_active_scene()
|
||||
outputs['sound'] = scene['sound']
|
||||
|
||||
# override default settings
|
||||
outputs['show_ornaments'] = True # never show HUD or overlays
|
||||
|
||||
# override camera options
|
||||
outputs['camera_options'] = dict()
|
||||
outputs['camera_options']['overscan'] = 1.0
|
||||
outputs['camera_options']['displayFieldChart'] = False
|
||||
outputs['camera_options']['displayFilmGate'] = False
|
||||
outputs['camera_options']['displayFilmOrigin'] = False
|
||||
outputs['camera_options']['displayFilmPivot'] = False
|
||||
outputs['camera_options']['displayGateMask'] = False
|
||||
outputs['camera_options']['displayResolution'] = False
|
||||
outputs['camera_options']['displaySafeAction'] = False
|
||||
outputs['camera_options']['displaySafeTitle'] = False
|
||||
|
||||
return outputs
|
||||
179
pype/vendor/capture_gui/plugins/displayplugin.py
vendored
Normal file
179
pype/vendor/capture_gui/plugins/displayplugin.py
vendored
Normal file
|
|
@ -0,0 +1,179 @@
|
|||
import maya.cmds as cmds
|
||||
|
||||
from capture_gui.vendor.Qt import QtCore, QtWidgets
|
||||
import capture_gui.plugin
|
||||
import capture_gui.colorpicker as colorpicker
|
||||
|
||||
|
||||
# region GLOBALS
|
||||
|
||||
BACKGROUND_DEFAULT = [0.6309999823570251,
|
||||
0.6309999823570251,
|
||||
0.6309999823570251]
|
||||
|
||||
TOP_DEFAULT = [0.5350000262260437,
|
||||
0.6169999837875366,
|
||||
0.7020000219345093]
|
||||
|
||||
BOTTOM_DEFAULT = [0.052000001072883606,
|
||||
0.052000001072883606,
|
||||
0.052000001072883606]
|
||||
|
||||
COLORS = {"background": BACKGROUND_DEFAULT,
|
||||
"backgroundTop": TOP_DEFAULT,
|
||||
"backgroundBottom": BOTTOM_DEFAULT}
|
||||
|
||||
LABELS = {"background": "Background",
|
||||
"backgroundTop": "Top",
|
||||
"backgroundBottom": "Bottom"}
|
||||
# endregion GLOBALS
|
||||
|
||||
|
||||
class DisplayPlugin(capture_gui.plugin.Plugin):
|
||||
"""Plugin to apply viewport visibilities and settings"""
|
||||
|
||||
id = "Display Options"
|
||||
label = "Display Options"
|
||||
section = "config"
|
||||
order = 70
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super(DisplayPlugin, self).__init__(parent=parent)
|
||||
|
||||
self._colors = dict()
|
||||
|
||||
self._layout = QtWidgets.QVBoxLayout()
|
||||
self._layout.setContentsMargins(0, 0, 0, 0)
|
||||
self.setLayout(self._layout)
|
||||
|
||||
self.override = QtWidgets.QCheckBox("Override Display Options")
|
||||
|
||||
self.display_type = QtWidgets.QComboBox()
|
||||
self.display_type.addItems(["Solid", "Gradient"])
|
||||
|
||||
# create color columns
|
||||
self._color_layout = QtWidgets.QHBoxLayout()
|
||||
for label, default in COLORS.items():
|
||||
self.add_color_picker(self._color_layout, label, default)
|
||||
|
||||
# populate layout
|
||||
self._layout.addWidget(self.override)
|
||||
self._layout.addWidget(self.display_type)
|
||||
self._layout.addLayout(self._color_layout)
|
||||
|
||||
# ensure widgets are in the correct enable state
|
||||
self.on_toggle_override()
|
||||
|
||||
self.connections()
|
||||
|
||||
def connections(self):
|
||||
self.override.toggled.connect(self.on_toggle_override)
|
||||
self.override.toggled.connect(self.options_changed)
|
||||
self.display_type.currentIndexChanged.connect(self.options_changed)
|
||||
|
||||
def add_color_picker(self, layout, label, default):
|
||||
"""Create a column with a label and a button to select a color
|
||||
|
||||
Arguments:
|
||||
layout (QtWidgets.QLayout): Layout to add color picker to
|
||||
label (str): system name for the color type, e.g. : backgroundTop
|
||||
default (list): The default color values to start with
|
||||
|
||||
Returns:
|
||||
colorpicker.ColorPicker: a color picker instance
|
||||
|
||||
"""
|
||||
|
||||
column = QtWidgets.QVBoxLayout()
|
||||
label_widget = QtWidgets.QLabel(LABELS[label])
|
||||
|
||||
color_picker = colorpicker.ColorPicker()
|
||||
color_picker.color = default
|
||||
|
||||
column.addWidget(label_widget)
|
||||
column.addWidget(color_picker)
|
||||
|
||||
column.setAlignment(label_widget, QtCore.Qt.AlignCenter)
|
||||
|
||||
layout.addLayout(column)
|
||||
|
||||
# connect signal
|
||||
color_picker.valueChanged.connect(self.options_changed)
|
||||
|
||||
# store widget
|
||||
self._colors[label] = color_picker
|
||||
|
||||
return color_picker
|
||||
|
||||
def on_toggle_override(self):
|
||||
"""Callback when override is toggled.
|
||||
|
||||
Enable or disable the color pickers and background type widgets bases
|
||||
on the current state of the override checkbox
|
||||
|
||||
Returns:
|
||||
None
|
||||
|
||||
"""
|
||||
state = self.override.isChecked()
|
||||
self.display_type.setEnabled(state)
|
||||
for widget in self._colors.values():
|
||||
widget.setEnabled(state)
|
||||
|
||||
def display_gradient(self):
|
||||
"""Return whether the background should be displayed as gradient.
|
||||
|
||||
When True the colors will use the top and bottom color to define the
|
||||
gradient otherwise the background color will be used as solid color.
|
||||
|
||||
Returns:
|
||||
bool: Whether background is gradient
|
||||
|
||||
"""
|
||||
return self.display_type.currentText() == "Gradient"
|
||||
|
||||
def apply_inputs(self, settings):
|
||||
"""Apply the saved inputs from the inputs configuration
|
||||
|
||||
Arguments:
|
||||
settings (dict): The input settings to apply.
|
||||
|
||||
"""
|
||||
|
||||
for label, widget in self._colors.items():
|
||||
default = COLORS.get(label, [0, 0, 0]) # fallback default to black
|
||||
value = settings.get(label, default)
|
||||
widget.color = value
|
||||
|
||||
override = settings.get("override_display", False)
|
||||
self.override.setChecked(override)
|
||||
|
||||
def get_inputs(self, as_preset):
|
||||
inputs = {"override_display": self.override.isChecked()}
|
||||
for label, widget in self._colors.items():
|
||||
inputs[label] = widget.color
|
||||
|
||||
return inputs
|
||||
|
||||
def get_outputs(self):
|
||||
"""Get the plugin outputs that matches `capture.capture` arguments
|
||||
|
||||
Returns:
|
||||
dict: Plugin outputs
|
||||
|
||||
"""
|
||||
|
||||
outputs = {}
|
||||
if self.override.isChecked():
|
||||
outputs["displayGradient"] = self.display_gradient()
|
||||
for label, widget in self._colors.items():
|
||||
outputs[label] = widget.color
|
||||
else:
|
||||
# Parse active color settings
|
||||
outputs["displayGradient"] = cmds.displayPref(query=True,
|
||||
displayGradient=True)
|
||||
for key in COLORS.keys():
|
||||
color = cmds.displayRGBColor(key, query=True)
|
||||
outputs[key] = color
|
||||
|
||||
return {"display_options": outputs}
|
||||
95
pype/vendor/capture_gui/plugins/genericplugin.py
vendored
Normal file
95
pype/vendor/capture_gui/plugins/genericplugin.py
vendored
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
import maya.cmds as mc
|
||||
from capture_gui.vendor.Qt import QtCore, QtWidgets
|
||||
|
||||
import capture_gui.plugin
|
||||
import capture_gui.lib
|
||||
|
||||
|
||||
class GenericPlugin(capture_gui.plugin.Plugin):
|
||||
"""Widget for generic options"""
|
||||
id = "Generic"
|
||||
label = "Generic"
|
||||
section = "config"
|
||||
order = 100
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super(GenericPlugin, self).__init__(parent=parent)
|
||||
|
||||
layout = QtWidgets.QVBoxLayout(self)
|
||||
layout.setContentsMargins(0, 0, 0, 0)
|
||||
self.setLayout(layout)
|
||||
|
||||
isolate_view = QtWidgets.QCheckBox(
|
||||
"Use isolate view from active panel")
|
||||
off_screen = QtWidgets.QCheckBox("Render offscreen")
|
||||
|
||||
layout.addWidget(isolate_view)
|
||||
layout.addWidget(off_screen)
|
||||
|
||||
isolate_view.stateChanged.connect(self.options_changed)
|
||||
off_screen.stateChanged.connect(self.options_changed)
|
||||
|
||||
self.widgets = {
|
||||
"off_screen": off_screen,
|
||||
"isolate_view": isolate_view
|
||||
}
|
||||
|
||||
self.apply_inputs(self.get_defaults())
|
||||
|
||||
def get_defaults(self):
|
||||
return {
|
||||
"off_screen": True,
|
||||
"isolate_view": False
|
||||
}
|
||||
|
||||
def get_inputs(self, as_preset):
|
||||
"""Return the widget options
|
||||
|
||||
Returns:
|
||||
dict: The input settings of the widgets.
|
||||
|
||||
"""
|
||||
|
||||
inputs = dict()
|
||||
for key, widget in self.widgets.items():
|
||||
state = widget.isChecked()
|
||||
inputs[key] = state
|
||||
|
||||
return inputs
|
||||
|
||||
def apply_inputs(self, inputs):
|
||||
"""Apply the saved inputs from the inputs configuration
|
||||
|
||||
Arguments:
|
||||
inputs (dict): The input settings to apply.
|
||||
|
||||
"""
|
||||
|
||||
for key, widget in self.widgets.items():
|
||||
state = inputs.get(key, None)
|
||||
if state is not None:
|
||||
widget.setChecked(state)
|
||||
|
||||
return inputs
|
||||
|
||||
def get_outputs(self):
|
||||
"""Returns all the options from the widget
|
||||
|
||||
Returns: dictionary with the settings
|
||||
|
||||
"""
|
||||
|
||||
inputs = self.get_inputs(as_preset=False)
|
||||
outputs = dict()
|
||||
outputs['off_screen'] = inputs['off_screen']
|
||||
|
||||
import capture_gui.lib
|
||||
|
||||
# Get isolate view members of the active panel
|
||||
if inputs['isolate_view']:
|
||||
panel = capture_gui.lib.get_active_editor()
|
||||
filter_set = mc.modelEditor(panel, query=True, viewObjects=True)
|
||||
isolate = mc.sets(filter_set, query=True) if filter_set else None
|
||||
outputs['isolate'] = isolate
|
||||
|
||||
return outputs
|
||||
254
pype/vendor/capture_gui/plugins/ioplugin.py
vendored
Normal file
254
pype/vendor/capture_gui/plugins/ioplugin.py
vendored
Normal file
|
|
@ -0,0 +1,254 @@
|
|||
import os
|
||||
import logging
|
||||
from functools import partial
|
||||
|
||||
from capture_gui.vendor.Qt import QtCore, QtWidgets
|
||||
from capture_gui import plugin, lib
|
||||
from capture_gui import tokens
|
||||
|
||||
log = logging.getLogger("IO")
|
||||
|
||||
|
||||
class IoAction(QtWidgets.QAction):
|
||||
|
||||
def __init__(self, parent, filepath):
|
||||
super(IoAction, self).__init__(parent)
|
||||
|
||||
action_label = os.path.basename(filepath)
|
||||
|
||||
self.setText(action_label)
|
||||
self.setData(filepath)
|
||||
|
||||
# check if file exists and disable when false
|
||||
self.setEnabled(os.path.isfile(filepath))
|
||||
|
||||
# get icon from file
|
||||
info = QtCore.QFileInfo(filepath)
|
||||
icon_provider = QtWidgets.QFileIconProvider()
|
||||
self.setIcon(icon_provider.icon(info))
|
||||
|
||||
self.triggered.connect(self.open_object_data)
|
||||
|
||||
def open_object_data(self):
|
||||
lib.open_file(self.data())
|
||||
|
||||
|
||||
class IoPlugin(plugin.Plugin):
|
||||
"""Codec widget.
|
||||
|
||||
Allows to set format, compression and quality.
|
||||
|
||||
"""
|
||||
id = "IO"
|
||||
label = "Save"
|
||||
section = "app"
|
||||
order = 40
|
||||
max_recent_playblasts = 5
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super(IoPlugin, self).__init__(parent=parent)
|
||||
|
||||
self.recent_playblasts = list()
|
||||
|
||||
self._layout = QtWidgets.QVBoxLayout()
|
||||
self._layout.setContentsMargins(0, 0, 0, 0)
|
||||
self.setLayout(self._layout)
|
||||
|
||||
# region Checkboxes
|
||||
self.save_file = QtWidgets.QCheckBox(text="Save")
|
||||
self.open_viewer = QtWidgets.QCheckBox(text="View when finished")
|
||||
self.raw_frame_numbers = QtWidgets.QCheckBox(text="Raw frame numbers")
|
||||
|
||||
checkbox_hlayout = QtWidgets.QHBoxLayout()
|
||||
checkbox_hlayout.setContentsMargins(5, 0, 5, 0)
|
||||
checkbox_hlayout.addWidget(self.save_file)
|
||||
checkbox_hlayout.addWidget(self.open_viewer)
|
||||
checkbox_hlayout.addWidget(self.raw_frame_numbers)
|
||||
checkbox_hlayout.addStretch(True)
|
||||
# endregion Checkboxes
|
||||
|
||||
# region Path
|
||||
self.path_widget = QtWidgets.QWidget()
|
||||
|
||||
self.browse = QtWidgets.QPushButton("Browse")
|
||||
self.file_path = QtWidgets.QLineEdit()
|
||||
self.file_path.setPlaceholderText("(not set; using scene name)")
|
||||
tip = "Right click in the text field to insert tokens"
|
||||
self.file_path.setToolTip(tip)
|
||||
self.file_path.setStatusTip(tip)
|
||||
self.file_path.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
|
||||
self.file_path.customContextMenuRequested.connect(self.show_token_menu)
|
||||
|
||||
path_hlayout = QtWidgets.QHBoxLayout()
|
||||
path_hlayout.setContentsMargins(0, 0, 0, 0)
|
||||
path_label = QtWidgets.QLabel("Path:")
|
||||
path_label.setFixedWidth(30)
|
||||
|
||||
path_hlayout.addWidget(path_label)
|
||||
path_hlayout.addWidget(self.file_path)
|
||||
path_hlayout.addWidget(self.browse)
|
||||
self.path_widget.setLayout(path_hlayout)
|
||||
# endregion Path
|
||||
|
||||
# region Recent Playblast
|
||||
self.play_recent = QtWidgets.QPushButton("Play recent playblast")
|
||||
self.recent_menu = QtWidgets.QMenu()
|
||||
self.play_recent.setMenu(self.recent_menu)
|
||||
# endregion Recent Playblast
|
||||
|
||||
self._layout.addLayout(checkbox_hlayout)
|
||||
self._layout.addWidget(self.path_widget)
|
||||
self._layout.addWidget(self.play_recent)
|
||||
|
||||
# Signals / connections
|
||||
self.browse.clicked.connect(self.show_browse_dialog)
|
||||
self.file_path.textChanged.connect(self.options_changed)
|
||||
self.save_file.stateChanged.connect(self.options_changed)
|
||||
self.raw_frame_numbers.stateChanged.connect(self.options_changed)
|
||||
self.save_file.stateChanged.connect(self.on_save_changed)
|
||||
|
||||
# Ensure state is up-to-date with current settings
|
||||
self.on_save_changed()
|
||||
|
||||
def on_save_changed(self):
|
||||
"""Update the visibility of the path field"""
|
||||
|
||||
state = self.save_file.isChecked()
|
||||
if state:
|
||||
self.path_widget.show()
|
||||
else:
|
||||
self.path_widget.hide()
|
||||
|
||||
def show_browse_dialog(self):
|
||||
"""Set the filepath using a browser dialog.
|
||||
|
||||
:return: None
|
||||
"""
|
||||
|
||||
path = lib.browse()
|
||||
if not path:
|
||||
return
|
||||
|
||||
# Maya's browser return Linux based file paths to ensure Windows is
|
||||
# supported we use normpath
|
||||
path = os.path.normpath(path)
|
||||
|
||||
self.file_path.setText(path)
|
||||
|
||||
def add_playblast(self, item):
|
||||
"""
|
||||
Add an item to the previous playblast menu
|
||||
|
||||
:param item: full path to a playblast file
|
||||
:type item: str
|
||||
|
||||
:return: None
|
||||
"""
|
||||
|
||||
# If item already in the recent playblasts remove it so we are
|
||||
# sure to add it as the new first most-recent
|
||||
try:
|
||||
self.recent_playblasts.remove(item)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
# Add as first in the recent playblasts
|
||||
self.recent_playblasts.insert(0, item)
|
||||
|
||||
# Ensure the playblast list is never longer than maximum amount
|
||||
# by removing the older entries that are at the end of the list
|
||||
if len(self.recent_playblasts) > self.max_recent_playblasts:
|
||||
del self.recent_playblasts[self.max_recent_playblasts:]
|
||||
|
||||
# Rebuild the actions menu
|
||||
self.recent_menu.clear()
|
||||
for playblast in self.recent_playblasts:
|
||||
action = IoAction(parent=self, filepath=playblast)
|
||||
self.recent_menu.addAction(action)
|
||||
|
||||
def on_playblast_finished(self, options):
|
||||
"""Take action after the play blast is done"""
|
||||
playblast_file = options['filename']
|
||||
if not playblast_file:
|
||||
return
|
||||
self.add_playblast(playblast_file)
|
||||
|
||||
def get_outputs(self):
|
||||
"""Get the plugin outputs that matches `capture.capture` arguments
|
||||
|
||||
Returns:
|
||||
dict: Plugin outputs
|
||||
|
||||
"""
|
||||
|
||||
output = {"filename": None,
|
||||
"raw_frame_numbers": self.raw_frame_numbers.isChecked(),
|
||||
"viewer": self.open_viewer.isChecked()}
|
||||
|
||||
save = self.save_file.isChecked()
|
||||
if not save:
|
||||
return output
|
||||
|
||||
# get path, if nothing is set fall back to default
|
||||
# project/images/playblast
|
||||
path = self.file_path.text()
|
||||
if not path:
|
||||
path = lib.default_output()
|
||||
|
||||
output["filename"] = path
|
||||
|
||||
return output
|
||||
|
||||
def get_inputs(self, as_preset):
|
||||
inputs = {"name": self.file_path.text(),
|
||||
"save_file": self.save_file.isChecked(),
|
||||
"open_finished": self.open_viewer.isChecked(),
|
||||
"recent_playblasts": self.recent_playblasts,
|
||||
"raw_frame_numbers": self.raw_frame_numbers.isChecked()}
|
||||
|
||||
if as_preset:
|
||||
inputs["recent_playblasts"] = []
|
||||
|
||||
return inputs
|
||||
|
||||
def apply_inputs(self, settings):
|
||||
|
||||
directory = settings.get("name", None)
|
||||
save_file = settings.get("save_file", True)
|
||||
open_finished = settings.get("open_finished", True)
|
||||
raw_frame_numbers = settings.get("raw_frame_numbers", False)
|
||||
previous_playblasts = settings.get("recent_playblasts", [])
|
||||
|
||||
self.save_file.setChecked(save_file)
|
||||
self.open_viewer.setChecked(open_finished)
|
||||
self.raw_frame_numbers.setChecked(raw_frame_numbers)
|
||||
|
||||
for playblast in reversed(previous_playblasts):
|
||||
self.add_playblast(playblast)
|
||||
|
||||
self.file_path.setText(directory)
|
||||
|
||||
def token_menu(self):
|
||||
"""
|
||||
Build the token menu based on the registered tokens
|
||||
|
||||
:returns: Menu
|
||||
:rtype: QtWidgets.QMenu
|
||||
"""
|
||||
menu = QtWidgets.QMenu(self)
|
||||
registered_tokens = tokens.list_tokens()
|
||||
|
||||
for token, value in registered_tokens.items():
|
||||
label = "{} \t{}".format(token, value['label'])
|
||||
action = QtWidgets.QAction(label, menu)
|
||||
fn = partial(self.file_path.insert, token)
|
||||
action.triggered.connect(fn)
|
||||
menu.addAction(action)
|
||||
|
||||
return menu
|
||||
|
||||
def show_token_menu(self, pos):
|
||||
"""Show custom manu on position of widget"""
|
||||
menu = self.token_menu()
|
||||
globalpos = QtCore.QPoint(self.file_path.mapToGlobal(pos))
|
||||
menu.exec_(globalpos)
|
||||
48
pype/vendor/capture_gui/plugins/panzoomplugin.py
vendored
Normal file
48
pype/vendor/capture_gui/plugins/panzoomplugin.py
vendored
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
from capture_gui.vendor.Qt import QtCore, QtWidgets
|
||||
import capture_gui.plugin
|
||||
|
||||
|
||||
class PanZoomPlugin(capture_gui.plugin.Plugin):
|
||||
"""Pan/Zoom widget.
|
||||
|
||||
Allows to toggle whether you want to playblast with the camera's pan/zoom
|
||||
state or disable it during the playblast. When "Use pan/zoom from camera"
|
||||
is *not* checked it will force disable pan/zoom.
|
||||
|
||||
"""
|
||||
id = "PanZoom"
|
||||
label = "Pan/Zoom"
|
||||
section = "config"
|
||||
order = 110
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super(PanZoomPlugin, self).__init__(parent=parent)
|
||||
|
||||
self._layout = QtWidgets.QHBoxLayout()
|
||||
self._layout.setContentsMargins(5, 0, 5, 0)
|
||||
self.setLayout(self._layout)
|
||||
|
||||
self.pan_zoom = QtWidgets.QCheckBox("Use pan/zoom from camera")
|
||||
self.pan_zoom.setChecked(True)
|
||||
|
||||
self._layout.addWidget(self.pan_zoom)
|
||||
|
||||
self.pan_zoom.stateChanged.connect(self.options_changed)
|
||||
|
||||
def get_outputs(self):
|
||||
|
||||
if not self.pan_zoom.isChecked():
|
||||
return {"camera_options": {
|
||||
"panZoomEnabled": 1,
|
||||
"horizontalPan": 0.0,
|
||||
"verticalPan": 0.0,
|
||||
"zoom": 1.0}
|
||||
}
|
||||
else:
|
||||
return {}
|
||||
|
||||
def apply_inputs(self, settings):
|
||||
self.pan_zoom.setChecked(settings.get("pan_zoom", True))
|
||||
|
||||
def get_inputs(self, as_preset):
|
||||
return {"pan_zoom": self.pan_zoom.isChecked()}
|
||||
104
pype/vendor/capture_gui/plugins/rendererplugin.py
vendored
Normal file
104
pype/vendor/capture_gui/plugins/rendererplugin.py
vendored
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
import maya.cmds as cmds
|
||||
|
||||
from capture_gui.vendor.Qt import QtCore, QtWidgets
|
||||
import capture_gui.lib as lib
|
||||
import capture_gui.plugin
|
||||
|
||||
|
||||
class RendererPlugin(capture_gui.plugin.Plugin):
|
||||
"""Renderer plugin to control the used playblast renderer for viewport"""
|
||||
|
||||
id = "Renderer"
|
||||
label = "Renderer"
|
||||
section = "config"
|
||||
order = 60
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super(RendererPlugin, self).__init__(parent=parent)
|
||||
|
||||
layout = QtWidgets.QVBoxLayout(self)
|
||||
layout.setContentsMargins(0, 0, 0, 0)
|
||||
self.setLayout(layout)
|
||||
|
||||
# Get active renderers for viewport
|
||||
self._renderers = self.get_renderers()
|
||||
|
||||
# Create list of renderers
|
||||
self.renderers = QtWidgets.QComboBox()
|
||||
self.renderers.addItems(self._renderers.keys())
|
||||
|
||||
layout.addWidget(self.renderers)
|
||||
|
||||
self.apply_inputs(self.get_defaults())
|
||||
|
||||
# Signals
|
||||
self.renderers.currentIndexChanged.connect(self.options_changed)
|
||||
|
||||
def get_current_renderer(self):
|
||||
"""Get current renderer by internal name (non-UI)
|
||||
|
||||
Returns:
|
||||
str: Name of renderer.
|
||||
|
||||
"""
|
||||
renderer_ui = self.renderers.currentText()
|
||||
renderer = self._renderers.get(renderer_ui, None)
|
||||
if renderer is None:
|
||||
raise RuntimeError("No valid renderer: {0}".format(renderer_ui))
|
||||
|
||||
return renderer
|
||||
|
||||
def get_renderers(self):
|
||||
"""Collect all available renderers for playblast"""
|
||||
active_editor = lib.get_active_editor()
|
||||
renderers_ui = cmds.modelEditor(active_editor,
|
||||
query=True,
|
||||
rendererListUI=True)
|
||||
renderers_id = cmds.modelEditor(active_editor,
|
||||
query=True,
|
||||
rendererList=True)
|
||||
|
||||
renderers = dict(zip(renderers_ui, renderers_id))
|
||||
renderers.pop("Stub Renderer")
|
||||
|
||||
return renderers
|
||||
|
||||
def get_defaults(self):
|
||||
return {"rendererName": "vp2Renderer"}
|
||||
|
||||
def get_inputs(self, as_preset):
|
||||
return {"rendererName": self.get_current_renderer()}
|
||||
|
||||
def get_outputs(self):
|
||||
"""Get the plugin outputs that matches `capture.capture` arguments
|
||||
|
||||
Returns:
|
||||
dict: Plugin outputs
|
||||
|
||||
"""
|
||||
return {
|
||||
"viewport_options": {
|
||||
"rendererName": self.get_current_renderer()
|
||||
}
|
||||
}
|
||||
|
||||
def apply_inputs(self, inputs):
|
||||
"""Apply previous settings or settings from a preset
|
||||
|
||||
Args:
|
||||
inputs (dict): Plugin input settings
|
||||
|
||||
Returns:
|
||||
None
|
||||
|
||||
"""
|
||||
|
||||
reverse_lookup = {value: key for key, value in self._renderers.items()}
|
||||
renderer = inputs.get("rendererName", "vp2Renderer")
|
||||
renderer_ui = reverse_lookup.get(renderer)
|
||||
|
||||
if renderer_ui:
|
||||
index = self.renderers.findText(renderer_ui)
|
||||
self.renderers.setCurrentIndex(index)
|
||||
else:
|
||||
self.renderers.setCurrentIndex(1)
|
||||
199
pype/vendor/capture_gui/plugins/resolutionplugin.py
vendored
Normal file
199
pype/vendor/capture_gui/plugins/resolutionplugin.py
vendored
Normal file
|
|
@ -0,0 +1,199 @@
|
|||
import math
|
||||
from functools import partial
|
||||
|
||||
import maya.cmds as cmds
|
||||
from capture_gui.vendor.Qt import QtCore, QtWidgets
|
||||
|
||||
import capture_gui.lib as lib
|
||||
import capture_gui.plugin
|
||||
|
||||
|
||||
class ResolutionPlugin(capture_gui.plugin.Plugin):
|
||||
"""Resolution widget.
|
||||
|
||||
Allows to set scale based on set of options.
|
||||
|
||||
"""
|
||||
id = "Resolution"
|
||||
section = "app"
|
||||
order = 20
|
||||
|
||||
resolution_changed = QtCore.Signal()
|
||||
|
||||
ScaleWindow = "From Window"
|
||||
ScaleRenderSettings = "From Render Settings"
|
||||
ScaleCustom = "Custom"
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super(ResolutionPlugin, self).__init__(parent=parent)
|
||||
|
||||
self._layout = QtWidgets.QVBoxLayout()
|
||||
self._layout.setContentsMargins(0, 0, 0, 0)
|
||||
self.setLayout(self._layout)
|
||||
|
||||
# Scale
|
||||
self.mode = QtWidgets.QComboBox()
|
||||
self.mode.addItems([self.ScaleWindow,
|
||||
self.ScaleRenderSettings,
|
||||
self.ScaleCustom])
|
||||
self.mode.setCurrentIndex(1) # Default: From render settings
|
||||
|
||||
# Custom width/height
|
||||
self.resolution = QtWidgets.QWidget()
|
||||
self.resolution.setContentsMargins(0, 0, 0, 0)
|
||||
resolution_layout = QtWidgets.QHBoxLayout()
|
||||
resolution_layout.setContentsMargins(0, 0, 0, 0)
|
||||
resolution_layout.setSpacing(6)
|
||||
|
||||
self.resolution.setLayout(resolution_layout)
|
||||
width_label = QtWidgets.QLabel("Width")
|
||||
width_label.setFixedWidth(40)
|
||||
self.width = QtWidgets.QSpinBox()
|
||||
self.width.setMinimum(0)
|
||||
self.width.setMaximum(99999)
|
||||
self.width.setValue(1920)
|
||||
heigth_label = QtWidgets.QLabel("Height")
|
||||
heigth_label.setFixedWidth(40)
|
||||
self.height = QtWidgets.QSpinBox()
|
||||
self.height.setMinimum(0)
|
||||
self.height.setMaximum(99999)
|
||||
self.height.setValue(1080)
|
||||
|
||||
resolution_layout.addWidget(width_label)
|
||||
resolution_layout.addWidget(self.width)
|
||||
resolution_layout.addWidget(heigth_label)
|
||||
resolution_layout.addWidget(self.height)
|
||||
|
||||
self.scale_result = QtWidgets.QLineEdit()
|
||||
self.scale_result.setReadOnly(True)
|
||||
|
||||
# Percentage
|
||||
self.percent_label = QtWidgets.QLabel("Scale")
|
||||
self.percent = QtWidgets.QDoubleSpinBox()
|
||||
self.percent.setMinimum(0.01)
|
||||
self.percent.setValue(1.0) # default value
|
||||
self.percent.setSingleStep(0.05)
|
||||
|
||||
self.percent_presets = QtWidgets.QHBoxLayout()
|
||||
self.percent_presets.setSpacing(4)
|
||||
for value in [0.25, 0.5, 0.75, 1.0, 2.0]:
|
||||
btn = QtWidgets.QPushButton(str(value))
|
||||
self.percent_presets.addWidget(btn)
|
||||
btn.setFixedWidth(35)
|
||||
btn.clicked.connect(partial(self.percent.setValue, value))
|
||||
|
||||
self.percent_layout = QtWidgets.QHBoxLayout()
|
||||
self.percent_layout.addWidget(self.percent_label)
|
||||
self.percent_layout.addWidget(self.percent)
|
||||
self.percent_layout.addLayout(self.percent_presets)
|
||||
|
||||
# Resulting scale display
|
||||
self._layout.addWidget(self.mode)
|
||||
self._layout.addWidget(self.resolution)
|
||||
self._layout.addLayout(self.percent_layout)
|
||||
self._layout.addWidget(self.scale_result)
|
||||
|
||||
# refresh states
|
||||
self.on_mode_changed()
|
||||
self.on_resolution_changed()
|
||||
|
||||
# connect signals
|
||||
self.mode.currentIndexChanged.connect(self.on_mode_changed)
|
||||
self.mode.currentIndexChanged.connect(self.on_resolution_changed)
|
||||
self.percent.valueChanged.connect(self.on_resolution_changed)
|
||||
self.width.valueChanged.connect(self.on_resolution_changed)
|
||||
self.height.valueChanged.connect(self.on_resolution_changed)
|
||||
|
||||
# Connect options changed
|
||||
self.mode.currentIndexChanged.connect(self.options_changed)
|
||||
self.percent.valueChanged.connect(self.options_changed)
|
||||
self.width.valueChanged.connect(self.options_changed)
|
||||
self.height.valueChanged.connect(self.options_changed)
|
||||
|
||||
def on_mode_changed(self):
|
||||
"""Update the width/height enabled state when mode changes"""
|
||||
|
||||
if self.mode.currentText() != self.ScaleCustom:
|
||||
self.width.setEnabled(False)
|
||||
self.height.setEnabled(False)
|
||||
self.resolution.hide()
|
||||
else:
|
||||
self.width.setEnabled(True)
|
||||
self.height.setEnabled(True)
|
||||
self.resolution.show()
|
||||
|
||||
def _get_output_resolution(self):
|
||||
|
||||
options = self.get_outputs()
|
||||
return int(options["width"]), int(options["height"])
|
||||
|
||||
def on_resolution_changed(self):
|
||||
"""Update the resulting resolution label"""
|
||||
|
||||
width, height = self._get_output_resolution()
|
||||
label = "Result: {0}x{1}".format(width, height)
|
||||
|
||||
self.scale_result.setText(label)
|
||||
|
||||
# Update label
|
||||
self.label = "Resolution ({0}x{1})".format(width, height)
|
||||
self.label_changed.emit(self.label)
|
||||
|
||||
def get_outputs(self):
|
||||
"""Return width x height defined by the combination of settings
|
||||
|
||||
Returns:
|
||||
dict: width and height key values
|
||||
|
||||
"""
|
||||
mode = self.mode.currentText()
|
||||
panel = lib.get_active_editor()
|
||||
|
||||
if mode == self.ScaleCustom:
|
||||
width = self.width.value()
|
||||
height = self.height.value()
|
||||
|
||||
elif mode == self.ScaleRenderSettings:
|
||||
# width height from render resolution
|
||||
width = cmds.getAttr("defaultResolution.width")
|
||||
height = cmds.getAttr("defaultResolution.height")
|
||||
|
||||
elif mode == self.ScaleWindow:
|
||||
# width height from active view panel size
|
||||
if not panel:
|
||||
# No panel would be passed when updating in the UI as such
|
||||
# the resulting resolution can't be previewed. But this should
|
||||
# never happen when starting the capture.
|
||||
width = 0
|
||||
height = 0
|
||||
else:
|
||||
width = cmds.control(panel, query=True, width=True)
|
||||
height = cmds.control(panel, query=True, height=True)
|
||||
else:
|
||||
raise NotImplementedError("Unsupported scale mode: "
|
||||
"{0}".format(mode))
|
||||
|
||||
scale = [width, height]
|
||||
percentage = self.percent.value()
|
||||
scale = [math.floor(x * percentage) for x in scale]
|
||||
|
||||
return {"width": scale[0], "height": scale[1]}
|
||||
|
||||
def get_inputs(self, as_preset):
|
||||
return {"mode": self.mode.currentText(),
|
||||
"width": self.width.value(),
|
||||
"height": self.height.value(),
|
||||
"percent": self.percent.value()}
|
||||
|
||||
def apply_inputs(self, settings):
|
||||
# get value else fall back to default values
|
||||
mode = settings.get("mode", self.ScaleRenderSettings)
|
||||
width = int(settings.get("width", 1920))
|
||||
height = int(settings.get("height", 1080))
|
||||
percent = float(settings.get("percent", 1.0))
|
||||
|
||||
# set values
|
||||
self.mode.setCurrentIndex(self.mode.findText(mode))
|
||||
self.width.setValue(width)
|
||||
self.height.setValue(height)
|
||||
self.percent.setValue(percent)
|
||||
292
pype/vendor/capture_gui/plugins/timeplugin.py
vendored
Normal file
292
pype/vendor/capture_gui/plugins/timeplugin.py
vendored
Normal file
|
|
@ -0,0 +1,292 @@
|
|||
import sys
|
||||
import logging
|
||||
import re
|
||||
|
||||
import maya.OpenMaya as om
|
||||
from capture_gui.vendor.Qt import QtCore, QtWidgets
|
||||
|
||||
import capture_gui.lib
|
||||
import capture_gui.plugin
|
||||
|
||||
log = logging.getLogger("Time Range")
|
||||
|
||||
|
||||
def parse_frames(string):
|
||||
"""Parse the resulting frames list from a frame list string.
|
||||
|
||||
Examples
|
||||
>>> parse_frames("0-3;30")
|
||||
[0, 1, 2, 3, 30]
|
||||
>>> parse_frames("0,2,4,-10")
|
||||
[0, 2, 4, -10]
|
||||
>>> parse_frames("-10--5,-2")
|
||||
[-10, -9, -8, -7, -6, -5, -2]
|
||||
|
||||
Args:
|
||||
string (str): The string to parse for frames.
|
||||
|
||||
Returns:
|
||||
list: A list of frames
|
||||
|
||||
"""
|
||||
|
||||
result = list()
|
||||
if not string.strip():
|
||||
raise ValueError("Can't parse an empty frame string.")
|
||||
|
||||
if not re.match("^[-0-9,; ]*$", string):
|
||||
raise ValueError("Invalid symbols in frame string: {}".format(string))
|
||||
|
||||
for raw in re.split(";|,", string):
|
||||
|
||||
# Skip empty elements
|
||||
value = raw.strip().replace(" ", "")
|
||||
if not value:
|
||||
continue
|
||||
|
||||
# Check for sequences (1-20) including negatives (-10--8)
|
||||
sequence = re.search("(-?[0-9]+)-(-?[0-9]+)", value)
|
||||
|
||||
# Sequence
|
||||
if sequence:
|
||||
start, end = sequence.groups()
|
||||
frames = range(int(start), int(end) + 1)
|
||||
result.extend(frames)
|
||||
|
||||
# Single frame
|
||||
else:
|
||||
try:
|
||||
frame = int(value)
|
||||
except ValueError:
|
||||
raise ValueError("Invalid frame description: "
|
||||
"'{0}'".format(value))
|
||||
|
||||
result.append(frame)
|
||||
|
||||
if not result:
|
||||
# This happens when only spaces are entered with a separator like `,` or `;`
|
||||
raise ValueError("Unable to parse any frames from string: {}".format(string))
|
||||
|
||||
return result
|
||||
|
||||
|
||||
class TimePlugin(capture_gui.plugin.Plugin):
|
||||
"""Widget for time based options"""
|
||||
|
||||
id = "Time Range"
|
||||
section = "app"
|
||||
order = 30
|
||||
|
||||
RangeTimeSlider = "Time Slider"
|
||||
RangeStartEnd = "Start/End"
|
||||
CurrentFrame = "Current Frame"
|
||||
CustomFrames = "Custom Frames"
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super(TimePlugin, self).__init__(parent=parent)
|
||||
|
||||
self._event_callbacks = list()
|
||||
|
||||
self._layout = QtWidgets.QHBoxLayout()
|
||||
self._layout.setContentsMargins(5, 0, 5, 0)
|
||||
self.setLayout(self._layout)
|
||||
|
||||
self.mode = QtWidgets.QComboBox()
|
||||
self.mode.addItems([self.RangeTimeSlider,
|
||||
self.RangeStartEnd,
|
||||
self.CurrentFrame,
|
||||
self.CustomFrames])
|
||||
|
||||
frame_input_height = 20
|
||||
self.start = QtWidgets.QSpinBox()
|
||||
self.start.setRange(-sys.maxint, sys.maxint)
|
||||
self.start.setFixedHeight(frame_input_height)
|
||||
self.end = QtWidgets.QSpinBox()
|
||||
self.end.setRange(-sys.maxint, sys.maxint)
|
||||
self.end.setFixedHeight(frame_input_height)
|
||||
|
||||
# unique frames field
|
||||
self.custom_frames = QtWidgets.QLineEdit()
|
||||
self.custom_frames.setFixedHeight(frame_input_height)
|
||||
self.custom_frames.setPlaceholderText("Example: 1-20,25;50;75,100-150")
|
||||
self.custom_frames.setVisible(False)
|
||||
|
||||
self._layout.addWidget(self.mode)
|
||||
self._layout.addWidget(self.start)
|
||||
self._layout.addWidget(self.end)
|
||||
self._layout.addWidget(self.custom_frames)
|
||||
|
||||
# Connect callbacks to ensure start is never higher then end
|
||||
# and the end is never lower than start
|
||||
self.end.valueChanged.connect(self._ensure_start)
|
||||
self.start.valueChanged.connect(self._ensure_end)
|
||||
|
||||
self.on_mode_changed() # force enabled state refresh
|
||||
|
||||
self.mode.currentIndexChanged.connect(self.on_mode_changed)
|
||||
self.start.valueChanged.connect(self.on_mode_changed)
|
||||
self.end.valueChanged.connect(self.on_mode_changed)
|
||||
self.custom_frames.textChanged.connect(self.on_mode_changed)
|
||||
|
||||
def _ensure_start(self, value):
|
||||
self.start.setValue(min(self.start.value(), value))
|
||||
|
||||
def _ensure_end(self, value):
|
||||
self.end.setValue(max(self.end.value(), value))
|
||||
|
||||
def on_mode_changed(self, emit=True):
|
||||
"""Update the GUI when the user updated the time range or settings.
|
||||
|
||||
Arguments:
|
||||
emit (bool): Whether to emit the options changed signal
|
||||
|
||||
Returns:
|
||||
None
|
||||
|
||||
"""
|
||||
|
||||
mode = self.mode.currentText()
|
||||
if mode == self.RangeTimeSlider:
|
||||
start, end = capture_gui.lib.get_time_slider_range()
|
||||
self.start.setEnabled(False)
|
||||
self.end.setEnabled(False)
|
||||
self.start.setVisible(True)
|
||||
self.end.setVisible(True)
|
||||
self.custom_frames.setVisible(False)
|
||||
mode_values = int(start), int(end)
|
||||
elif mode == self.RangeStartEnd:
|
||||
self.start.setEnabled(True)
|
||||
self.end.setEnabled(True)
|
||||
self.start.setVisible(True)
|
||||
self.end.setVisible(True)
|
||||
self.custom_frames.setVisible(False)
|
||||
mode_values = self.start.value(), self.end.value()
|
||||
elif mode == self.CustomFrames:
|
||||
self.start.setVisible(False)
|
||||
self.end.setVisible(False)
|
||||
self.custom_frames.setVisible(True)
|
||||
mode_values = "({})".format(self.custom_frames.text())
|
||||
|
||||
# ensure validation state for custom frames
|
||||
self.validate()
|
||||
|
||||
else:
|
||||
self.start.setEnabled(False)
|
||||
self.end.setEnabled(False)
|
||||
self.start.setVisible(True)
|
||||
self.end.setVisible(True)
|
||||
self.custom_frames.setVisible(False)
|
||||
currentframe = int(capture_gui.lib.get_current_frame())
|
||||
mode_values = "({})".format(currentframe)
|
||||
|
||||
# Update label
|
||||
self.label = "Time Range {}".format(mode_values)
|
||||
self.label_changed.emit(self.label)
|
||||
|
||||
if emit:
|
||||
self.options_changed.emit()
|
||||
|
||||
def validate(self):
|
||||
errors = []
|
||||
|
||||
if self.mode.currentText() == self.CustomFrames:
|
||||
|
||||
# Reset
|
||||
self.custom_frames.setStyleSheet("")
|
||||
|
||||
try:
|
||||
parse_frames(self.custom_frames.text())
|
||||
except ValueError as exc:
|
||||
errors.append("{} : Invalid frame description: "
|
||||
"{}".format(self.id, exc))
|
||||
self.custom_frames.setStyleSheet(self.highlight)
|
||||
|
||||
return errors
|
||||
|
||||
def get_outputs(self, panel=""):
|
||||
"""Get the plugin outputs that matches `capture.capture` arguments
|
||||
|
||||
Returns:
|
||||
dict: Plugin outputs
|
||||
|
||||
"""
|
||||
|
||||
mode = self.mode.currentText()
|
||||
frames = None
|
||||
|
||||
if mode == self.RangeTimeSlider:
|
||||
start, end = capture_gui.lib.get_time_slider_range()
|
||||
|
||||
elif mode == self.RangeStartEnd:
|
||||
start = self.start.value()
|
||||
end = self.end.value()
|
||||
|
||||
elif mode == self.CurrentFrame:
|
||||
frame = capture_gui.lib.get_current_frame()
|
||||
start = frame
|
||||
end = frame
|
||||
|
||||
elif mode == self.CustomFrames:
|
||||
frames = parse_frames(self.custom_frames.text())
|
||||
start = None
|
||||
end = None
|
||||
else:
|
||||
raise NotImplementedError("Unsupported time range mode: "
|
||||
"{0}".format(mode))
|
||||
|
||||
return {"start_frame": start,
|
||||
"end_frame": end,
|
||||
"frame": frames}
|
||||
|
||||
def get_inputs(self, as_preset):
|
||||
return {"time": self.mode.currentText(),
|
||||
"start_frame": self.start.value(),
|
||||
"end_frame": self.end.value(),
|
||||
"frame": self.custom_frames.text()}
|
||||
|
||||
def apply_inputs(self, settings):
|
||||
# get values
|
||||
mode = self.mode.findText(settings.get("time", self.RangeTimeSlider))
|
||||
startframe = settings.get("start_frame", 1)
|
||||
endframe = settings.get("end_frame", 120)
|
||||
custom_frames = settings.get("frame", None)
|
||||
|
||||
# set values
|
||||
self.mode.setCurrentIndex(mode)
|
||||
self.start.setValue(int(startframe))
|
||||
self.end.setValue(int(endframe))
|
||||
if custom_frames is not None:
|
||||
self.custom_frames.setText(custom_frames)
|
||||
|
||||
def initialize(self):
|
||||
self._register_callbacks()
|
||||
|
||||
def uninitialize(self):
|
||||
self._remove_callbacks()
|
||||
|
||||
def _register_callbacks(self):
|
||||
"""Register maya time and playback range change callbacks.
|
||||
|
||||
Register callbacks to ensure Capture GUI reacts to changes in
|
||||
the Maya GUI in regards to time slider and current frame
|
||||
|
||||
"""
|
||||
|
||||
callback = lambda x: self.on_mode_changed(emit=False)
|
||||
|
||||
# this avoid overriding the ids on re-run
|
||||
currentframe = om.MEventMessage.addEventCallback("timeChanged",
|
||||
callback)
|
||||
timerange = om.MEventMessage.addEventCallback("playbackRangeChanged",
|
||||
callback)
|
||||
|
||||
self._event_callbacks.append(currentframe)
|
||||
self._event_callbacks.append(timerange)
|
||||
|
||||
def _remove_callbacks(self):
|
||||
"""Remove callbacks when closing widget"""
|
||||
for callback in self._event_callbacks:
|
||||
try:
|
||||
om.MEventMessage.removeCallback(callback)
|
||||
except RuntimeError, error:
|
||||
log.error("Encounter error : {}".format(error))
|
||||
292
pype/vendor/capture_gui/plugins/viewportplugin.py
vendored
Normal file
292
pype/vendor/capture_gui/plugins/viewportplugin.py
vendored
Normal file
|
|
@ -0,0 +1,292 @@
|
|||
from capture_gui.vendor.Qt import QtCore, QtWidgets
|
||||
import capture_gui.plugin
|
||||
import capture_gui.lib as lib
|
||||
import capture
|
||||
|
||||
|
||||
class ViewportPlugin(capture_gui.plugin.Plugin):
|
||||
"""Plugin to apply viewport visibilities and settings"""
|
||||
|
||||
id = "Viewport Options"
|
||||
label = "Viewport Options"
|
||||
section = "config"
|
||||
order = 70
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super(ViewportPlugin, self).__init__(parent=parent)
|
||||
|
||||
# set inherited attributes
|
||||
self.setObjectName(self.label)
|
||||
|
||||
# custom atttributes
|
||||
self.show_type_actions = list()
|
||||
|
||||
# get information
|
||||
self.show_types = lib.get_show_object_types()
|
||||
|
||||
# set main layout for widget
|
||||
self._layout = QtWidgets.QVBoxLayout()
|
||||
self._layout.setContentsMargins(0, 0, 0, 0)
|
||||
self.setLayout(self._layout)
|
||||
|
||||
# build
|
||||
# region Menus
|
||||
menus_vlayout = QtWidgets.QHBoxLayout()
|
||||
|
||||
# Display Lights
|
||||
self.display_light_menu = self._build_light_menu()
|
||||
self.display_light_menu.setFixedHeight(20)
|
||||
|
||||
# Show
|
||||
self.show_types_button = QtWidgets.QPushButton("Show")
|
||||
self.show_types_button.setFixedHeight(20)
|
||||
self.show_types_menu = self._build_show_menu()
|
||||
self.show_types_button.setMenu(self.show_types_menu)
|
||||
|
||||
# fill layout
|
||||
menus_vlayout.addWidget(self.display_light_menu)
|
||||
menus_vlayout.addWidget(self.show_types_button)
|
||||
|
||||
# endregion Menus
|
||||
|
||||
# region Checkboxes
|
||||
checkbox_layout = QtWidgets.QGridLayout()
|
||||
self.high_quality = QtWidgets.QCheckBox()
|
||||
self.high_quality.setText("Force Viewport 2.0 + AA")
|
||||
self.override_viewport = QtWidgets.QCheckBox("Override viewport "
|
||||
"settings")
|
||||
self.override_viewport.setChecked(True)
|
||||
|
||||
# two sided lighting
|
||||
self.two_sided_ligthing = QtWidgets.QCheckBox("Two Sided Ligthing")
|
||||
self.two_sided_ligthing.setChecked(False)
|
||||
|
||||
# show
|
||||
self.shadows = QtWidgets.QCheckBox("Shadows")
|
||||
self.shadows.setChecked(False)
|
||||
|
||||
checkbox_layout.addWidget(self.override_viewport, 0, 0)
|
||||
checkbox_layout.addWidget(self.high_quality, 0, 1)
|
||||
checkbox_layout.addWidget(self.two_sided_ligthing, 1, 0)
|
||||
checkbox_layout.addWidget(self.shadows, 1, 1)
|
||||
# endregion Checkboxes
|
||||
|
||||
self._layout.addLayout(checkbox_layout)
|
||||
self._layout.addLayout(menus_vlayout)
|
||||
|
||||
self.connections()
|
||||
|
||||
def connections(self):
|
||||
|
||||
self.high_quality.stateChanged.connect(self.options_changed)
|
||||
self.override_viewport.stateChanged.connect(self.options_changed)
|
||||
self.override_viewport.stateChanged.connect(self.on_toggle_override)
|
||||
|
||||
self.two_sided_ligthing.stateChanged.connect(self.options_changed)
|
||||
self.shadows.stateChanged.connect(self.options_changed)
|
||||
|
||||
self.display_light_menu.currentIndexChanged.connect(
|
||||
self.options_changed
|
||||
)
|
||||
|
||||
def _build_show_menu(self):
|
||||
"""Build the menu to select which object types are shown in the output.
|
||||
|
||||
Returns:
|
||||
QtGui.QMenu: The visibilities "show" menu.
|
||||
|
||||
"""
|
||||
|
||||
menu = QtWidgets.QMenu(self)
|
||||
menu.setObjectName("ShowShapesMenu")
|
||||
menu.setWindowTitle("Show")
|
||||
menu.setFixedWidth(180)
|
||||
menu.setTearOffEnabled(True)
|
||||
|
||||
# Show all check
|
||||
toggle_all = QtWidgets.QAction(menu, text="All")
|
||||
toggle_none = QtWidgets.QAction(menu, text="None")
|
||||
menu.addAction(toggle_all)
|
||||
menu.addAction(toggle_none)
|
||||
menu.addSeparator()
|
||||
|
||||
# add plugin shapes if any
|
||||
for shape in self.show_types:
|
||||
action = QtWidgets.QAction(menu, text=shape)
|
||||
action.setCheckable(True)
|
||||
# emit signal when the state is changed of the checkbox
|
||||
action.toggled.connect(self.options_changed)
|
||||
menu.addAction(action)
|
||||
self.show_type_actions.append(action)
|
||||
|
||||
# connect signals
|
||||
toggle_all.triggered.connect(self.toggle_all_visbile)
|
||||
toggle_none.triggered.connect(self.toggle_all_hide)
|
||||
|
||||
return menu
|
||||
|
||||
def _build_light_menu(self):
|
||||
"""Build lighting menu.
|
||||
|
||||
Create the menu items for the different types of lighting for
|
||||
in the viewport
|
||||
|
||||
Returns:
|
||||
None
|
||||
|
||||
"""
|
||||
|
||||
menu = QtWidgets.QComboBox(self)
|
||||
|
||||
# names cane be found in
|
||||
display_lights = (("Use Default Lighting", "default"),
|
||||
("Use All Lights", "all"),
|
||||
("Use Selected Lights", "active"),
|
||||
("Use Flat Lighting", "flat"),
|
||||
("Use No Lights", "none"))
|
||||
|
||||
for label, name in display_lights:
|
||||
menu.addItem(label, userData=name)
|
||||
|
||||
return menu
|
||||
|
||||
def on_toggle_override(self):
|
||||
"""Enable or disable show menu when override is checked"""
|
||||
state = self.override_viewport.isChecked()
|
||||
self.show_types_button.setEnabled(state)
|
||||
self.high_quality.setEnabled(state)
|
||||
self.display_light_menu.setEnabled(state)
|
||||
self.shadows.setEnabled(state)
|
||||
self.two_sided_ligthing.setEnabled(state)
|
||||
|
||||
def toggle_all_visbile(self):
|
||||
"""Set all object types off or on depending on the state"""
|
||||
for action in self.show_type_actions:
|
||||
action.setChecked(True)
|
||||
|
||||
def toggle_all_hide(self):
|
||||
"""Set all object types off or on depending on the state"""
|
||||
for action in self.show_type_actions:
|
||||
action.setChecked(False)
|
||||
|
||||
def get_show_inputs(self):
|
||||
"""Return checked state of show menu items
|
||||
|
||||
Returns:
|
||||
dict: The checked show states in the widget.
|
||||
|
||||
"""
|
||||
|
||||
show_inputs = {}
|
||||
# get all checked objects
|
||||
for action in self.show_type_actions:
|
||||
label = action.text()
|
||||
name = self.show_types.get(label, None)
|
||||
if name is None:
|
||||
continue
|
||||
show_inputs[name] = action.isChecked()
|
||||
|
||||
return show_inputs
|
||||
|
||||
def get_displaylights(self):
|
||||
"""Get and parse the currently selected displayLights options.
|
||||
|
||||
Returns:
|
||||
dict: The display light options
|
||||
|
||||
"""
|
||||
indx = self.display_light_menu.currentIndex()
|
||||
return {"displayLights": self.display_light_menu.itemData(indx),
|
||||
"shadows": self.shadows.isChecked(),
|
||||
"twoSidedLighting": self.two_sided_ligthing.isChecked()}
|
||||
|
||||
def get_inputs(self, as_preset):
|
||||
"""Return the widget options
|
||||
|
||||
Returns:
|
||||
dict: The input settings of the widgets.
|
||||
|
||||
"""
|
||||
inputs = {"high_quality": self.high_quality.isChecked(),
|
||||
"override_viewport_options": self.override_viewport.isChecked(),
|
||||
"displayLights": self.display_light_menu.currentIndex(),
|
||||
"shadows": self.shadows.isChecked(),
|
||||
"twoSidedLighting": self.two_sided_ligthing.isChecked()}
|
||||
|
||||
inputs.update(self.get_show_inputs())
|
||||
|
||||
return inputs
|
||||
|
||||
def apply_inputs(self, inputs):
|
||||
"""Apply the saved inputs from the inputs configuration
|
||||
|
||||
Arguments:
|
||||
settings (dict): The input settings to apply.
|
||||
|
||||
"""
|
||||
|
||||
# get input values directly from input given
|
||||
override_viewport = inputs.get("override_viewport_options", True)
|
||||
high_quality = inputs.get("high_quality", True)
|
||||
displaylight = inputs.get("displayLights", 0) # default lighting
|
||||
two_sided_ligthing = inputs.get("twoSidedLighting", False)
|
||||
shadows = inputs.get("shadows", False)
|
||||
|
||||
self.high_quality.setChecked(high_quality)
|
||||
self.override_viewport.setChecked(override_viewport)
|
||||
self.show_types_button.setEnabled(override_viewport)
|
||||
|
||||
# display light menu
|
||||
self.display_light_menu.setCurrentIndex(displaylight)
|
||||
self.shadows.setChecked(shadows)
|
||||
self.two_sided_ligthing.setChecked(two_sided_ligthing)
|
||||
|
||||
for action in self.show_type_actions:
|
||||
system_name = self.show_types[action.text()]
|
||||
state = inputs.get(system_name, True)
|
||||
action.setChecked(state)
|
||||
|
||||
def get_outputs(self):
|
||||
"""Get the plugin outputs that matches `capture.capture` arguments
|
||||
|
||||
Returns:
|
||||
dict: Plugin outputs
|
||||
|
||||
"""
|
||||
outputs = dict()
|
||||
|
||||
high_quality = self.high_quality.isChecked()
|
||||
override_viewport_options = self.override_viewport.isChecked()
|
||||
|
||||
if override_viewport_options:
|
||||
outputs['viewport2_options'] = dict()
|
||||
outputs['viewport_options'] = dict()
|
||||
|
||||
if high_quality:
|
||||
# force viewport 2.0 and AA
|
||||
outputs['viewport_options']['rendererName'] = 'vp2Renderer'
|
||||
outputs['viewport2_options']['multiSampleEnable'] = True
|
||||
outputs['viewport2_options']['multiSampleCount'] = 8
|
||||
|
||||
show_per_type = self.get_show_inputs()
|
||||
display_lights = self.get_displaylights()
|
||||
outputs['viewport_options'].update(show_per_type)
|
||||
outputs['viewport_options'].update(display_lights)
|
||||
else:
|
||||
# TODO: When this fails we should give the user a warning
|
||||
# Use settings from the active viewport
|
||||
outputs = capture.parse_active_view()
|
||||
|
||||
# Remove the display options and camera attributes
|
||||
outputs.pop("display_options", None)
|
||||
outputs.pop("camera", None)
|
||||
|
||||
# Remove the current renderer because there's already
|
||||
# renderer plug-in handling that
|
||||
outputs["viewport_options"].pop("rendererName", None)
|
||||
|
||||
# Remove all camera options except depth of field
|
||||
dof = outputs["camera_options"]["depthOfField"]
|
||||
outputs["camera_options"] = {"depthOfField": dof}
|
||||
|
||||
return outputs
|
||||
105
pype/vendor/capture_gui/presets.py
vendored
Normal file
105
pype/vendor/capture_gui/presets.py
vendored
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
import glob
|
||||
import os
|
||||
import logging
|
||||
|
||||
_registered_paths = []
|
||||
log = logging.getLogger("Presets")
|
||||
|
||||
|
||||
def discover(paths=None):
|
||||
"""Get the full list of files found in the registered folders
|
||||
|
||||
Args:
|
||||
paths (list, Optional): directories which host preset files or None.
|
||||
When None (default) it will list from the registered preset paths.
|
||||
|
||||
Returns:
|
||||
list: valid .json preset file paths.
|
||||
|
||||
"""
|
||||
|
||||
presets = []
|
||||
for path in paths or preset_paths():
|
||||
path = os.path.normpath(path)
|
||||
if not os.path.isdir(path):
|
||||
continue
|
||||
|
||||
# check for json files
|
||||
glob_query = os.path.abspath(os.path.join(path, "*.json"))
|
||||
filenames = glob.glob(glob_query)
|
||||
for filename in filenames:
|
||||
# skip private files
|
||||
if filename.startswith("_"):
|
||||
continue
|
||||
|
||||
# check for file size
|
||||
if not check_file_size(filename):
|
||||
log.warning("Filesize is smaller than 1 byte for file '%s'",
|
||||
filename)
|
||||
continue
|
||||
|
||||
if filename not in presets:
|
||||
presets.append(filename)
|
||||
|
||||
return presets
|
||||
|
||||
|
||||
def check_file_size(filepath):
|
||||
"""Check if filesize of the given file is bigger than 1.0 byte
|
||||
|
||||
Args:
|
||||
filepath (str): full filepath of the file to check
|
||||
|
||||
Returns:
|
||||
bool: Whether bigger than 1 byte.
|
||||
|
||||
"""
|
||||
|
||||
file_stats = os.stat(filepath)
|
||||
if file_stats.st_size < 1:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def preset_paths():
|
||||
"""Return existing registered preset paths
|
||||
|
||||
Returns:
|
||||
list: List of full paths.
|
||||
|
||||
"""
|
||||
|
||||
paths = list()
|
||||
for path in _registered_paths:
|
||||
# filter duplicates
|
||||
if path in paths:
|
||||
continue
|
||||
|
||||
if not os.path.exists(path):
|
||||
continue
|
||||
|
||||
paths.append(path)
|
||||
|
||||
return paths
|
||||
|
||||
|
||||
def register_preset_path(path):
|
||||
"""Add filepath to registered presets
|
||||
|
||||
:param path: the directory of the preset file(s)
|
||||
:type path: str
|
||||
|
||||
:return:
|
||||
"""
|
||||
if path in _registered_paths:
|
||||
return log.warning("Path already registered: %s", path)
|
||||
|
||||
_registered_paths.append(path)
|
||||
|
||||
return path
|
||||
|
||||
|
||||
# Register default user folder
|
||||
user_folder = os.path.expanduser("~")
|
||||
capture_gui_presets = os.path.join(user_folder, "CaptureGUI", "presets")
|
||||
register_preset_path(capture_gui_presets)
|
||||
BIN
pype/vendor/capture_gui/resources/config.png
vendored
Normal file
BIN
pype/vendor/capture_gui/resources/config.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 870 B |
BIN
pype/vendor/capture_gui/resources/import.png
vendored
Normal file
BIN
pype/vendor/capture_gui/resources/import.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 815 B |
BIN
pype/vendor/capture_gui/resources/reset.png
vendored
Normal file
BIN
pype/vendor/capture_gui/resources/reset.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 976 B |
BIN
pype/vendor/capture_gui/resources/save.png
vendored
Normal file
BIN
pype/vendor/capture_gui/resources/save.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 835 B |
68
pype/vendor/capture_gui/tokens.py
vendored
Normal file
68
pype/vendor/capture_gui/tokens.py
vendored
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
"""Token system
|
||||
|
||||
The capture gui application will format tokens in the filename.
|
||||
The tokens can be registered using `register_token`
|
||||
|
||||
"""
|
||||
from . import lib
|
||||
|
||||
_registered_tokens = dict()
|
||||
|
||||
|
||||
def format_tokens(string, options):
|
||||
"""Replace the tokens with the correlated strings
|
||||
|
||||
Arguments:
|
||||
string (str): filename of the playblast with tokens.
|
||||
options (dict): The parsed capture options.
|
||||
|
||||
Returns:
|
||||
str: The formatted filename with all tokens resolved
|
||||
|
||||
"""
|
||||
|
||||
if not string:
|
||||
return string
|
||||
|
||||
for token, value in _registered_tokens.items():
|
||||
if token in string:
|
||||
func = value['func']
|
||||
string = string.replace(token, func(options))
|
||||
|
||||
return string
|
||||
|
||||
|
||||
def register_token(token, func, label=""):
|
||||
assert token.startswith("<") and token.endswith(">")
|
||||
assert callable(func)
|
||||
_registered_tokens[token] = {"func": func, "label": label}
|
||||
|
||||
|
||||
def list_tokens():
|
||||
return _registered_tokens.copy()
|
||||
|
||||
|
||||
# register default tokens
|
||||
# scene based tokens
|
||||
def _camera_token(options):
|
||||
"""Return short name of camera from capture options"""
|
||||
camera = options['camera']
|
||||
camera = camera.rsplit("|", 1)[-1] # use short name
|
||||
camera = camera.replace(":", "_") # namespace `:` to `_`
|
||||
return camera
|
||||
|
||||
|
||||
register_token("<Camera>", _camera_token,
|
||||
label="Insert camera name")
|
||||
register_token("<Scene>", lambda options: lib.get_current_scenename() or "playblast",
|
||||
label="Insert current scene name")
|
||||
register_token("<RenderLayer>", lambda options: lib.get_current_renderlayer(),
|
||||
label="Insert active render layer name")
|
||||
|
||||
# project based tokens
|
||||
register_token("<Images>",
|
||||
lambda options: lib.get_project_rule("images"),
|
||||
label="Insert image directory of set project")
|
||||
register_token("<Movies>",
|
||||
lambda options: lib.get_project_rule("movie"),
|
||||
label="Insert movies directory of set project")
|
||||
1030
pype/vendor/capture_gui/vendor/Qt.py
vendored
Normal file
1030
pype/vendor/capture_gui/vendor/Qt.py
vendored
Normal file
File diff suppressed because it is too large
Load diff
0
pype/vendor/capture_gui/vendor/__init__.py
vendored
Normal file
0
pype/vendor/capture_gui/vendor/__init__.py
vendored
Normal file
9
pype/vendor/capture_gui/version.py
vendored
Normal file
9
pype/vendor/capture_gui/version.py
vendored
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
VERSION_MAJOR = 1
|
||||
VERSION_MINOR = 5
|
||||
VERSION_PATCH = 0
|
||||
|
||||
|
||||
version = '{}.{}.{}'.format(VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH)
|
||||
__version__ = version
|
||||
|
||||
__all__ = ['version', 'version_info', '__version__']
|
||||
Loading…
Add table
Add a link
Reference in a new issue